Skip to content

Commit ef951a8

Browse files
authored
[UI/UX] Improve error handling on BE and FE (#1363)
* wip: show error message if no games found * feat: add Error components * i18n: updated keys * chore: typo * fix: check * feat: show error message on gamepage * chore: added more checks * feat: added more messages and error handlers * chore: change invalid path message * feat: add context menu to Settings * fix: steam runtime setting not updating * fix: type conversion on logger * feat: add showdialog param to logger * fix: wrong type coertions * chore: log level adjustments * fix: epic webview login not working * fix: logged in status not changing * feat: handle other login errors * chore: refactor login FE a bit * feat: deal with memory cache failure on install * fix: error handler error * chore: pr comments * chore: pr comments 2 * chore: function naming * feat: refactor and deal with deleted folders * feat: gog forceUninstall * fix: use legendary uninstall command * chore: pr comments * fix: imports and tests * feat: check if gog game folder exists * i18n: updated keys * feat: get runtimes from steam libraries * i18n: updated keys
1 parent d024ddb commit ef951a8

File tree

97 files changed

+1377
-541
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

97 files changed

+1377
-541
lines changed

electron/config.ts

+26-7
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,15 @@ abstract class GlobalConfig {
5757
// Config file exists, detect its version.
5858
else {
5959
// Check version field in the config.
60-
version = JSON.parse(readFileSync(heroicConfigPath, 'utf-8'))['version']
60+
try {
61+
version = JSON.parse(readFileSync(heroicConfigPath, 'utf-8'))['version']
62+
} catch (error) {
63+
logError(
64+
`Config file is corrupted, please check ${heroicConfigPath}`,
65+
LogPrefix.Backend
66+
)
67+
version = 'v0'
68+
}
6169
// Legacy config file without a version field, it's a v0 config.
6270
if (!version) {
6371
version = 'v0'
@@ -411,12 +419,23 @@ class GlobalConfigV0 extends GlobalConfig {
411419
return this.getFactoryDefaults()
412420
}
413421

414-
let settings = JSON.parse(readFileSync(heroicConfigPath, 'utf-8'))
415-
settings = {
416-
...(await this.getFactoryDefaults()),
417-
...settings.defaultSettings
418-
} as AppSettings
419-
return settings
422+
try {
423+
let settings = JSON.parse(readFileSync(heroicConfigPath, 'utf-8'))
424+
settings = {
425+
...(await this.getFactoryDefaults()),
426+
...settings.defaultSettings
427+
} as AppSettings
428+
return settings
429+
} catch (error) {
430+
logError(
431+
`Config file is corrupted, please check ${heroicConfigPath}`,
432+
LogPrefix.Backend
433+
)
434+
const settings = {
435+
...(await this.getFactoryDefaults())
436+
}
437+
return settings
438+
}
420439
}
421440

422441
public async getCustomWinePaths(): Promise<Set<WineInstallation>> {

electron/game_config.ts

+26-8
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,15 @@ abstract class GameConfig {
4949
// Config file exists, detect its version.
5050
else {
5151
// Check version field in the config.
52-
version = JSON.parse(readFileSync(path, 'utf-8'))['version']
52+
try {
53+
version = JSON.parse(readFileSync(path, 'utf-8'))['version']
54+
} catch (error) {
55+
logError(
56+
`Config file is corrupted, please check ${path}`,
57+
LogPrefix.Backend
58+
)
59+
version = 'v0'
60+
}
5361
// Legacy config file without a version field, it's a v0 config.
5462
if (!version) {
5563
version = 'v0'
@@ -189,13 +197,23 @@ class GameConfigV0 extends GameConfig {
189197
if (!existsSync(this.path)) {
190198
return { ...GlobalConfig.get().config } as GameSettings
191199
}
192-
const settings = JSON.parse(readFileSync(this.path, 'utf-8'))
193-
// Take defaults, then overwrite if explicitly set values exist.
194-
// The settings defined work as overrides.
195-
return {
196-
...GlobalConfig.get().config,
197-
...settings[this.appName]
198-
} as GameSettings
200+
try {
201+
const settings = JSON.parse(readFileSync(this.path, 'utf-8'))
202+
// Take defaults, then overwrite if explicitly set values exist.
203+
// The settings defined work as overrides.
204+
return {
205+
...GlobalConfig.get().config,
206+
...settings[this.appName]
207+
} as GameSettings
208+
} catch (error) {
209+
logError(
210+
`Config file is corrupted, please check ${this.path}`,
211+
LogPrefix.Backend
212+
)
213+
return {
214+
...GlobalConfig.get().config
215+
}
216+
}
199217
}
200218

201219
public async resetToDefaults() {

electron/games.ts

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ abstract class Game {
3535
abstract moveInstall(newInstallPath: string): Promise<string>
3636
abstract repair(): Promise<ExecResult>
3737
abstract stop(): Promise<void>
38+
abstract forceUninstall(): Promise<void>
3839
abstract syncSaves(arg: string, path: string): Promise<ExecResult>
3940
abstract uninstall(): Promise<ExecResult>
4041
abstract update(): Promise<{ status: 'done' | 'error' }>

electron/gog/electronStores.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,13 @@ const configStore = new Store({
1111

1212
const apiInfoCache = new Store({
1313
cwd: 'gog_store',
14-
name: 'api_info_cache'
14+
name: 'api_info_cache',
15+
clearInvalidConfig: true
16+
})
17+
const libraryStore = new Store({
18+
cwd: 'gog_store',
19+
name: 'library',
20+
clearInvalidConfig: true
1521
})
16-
const libraryStore = new Store({ cwd: 'gog_store', name: 'library' })
1722

1823
export { configStore, installedGamesStore, apiInfoCache, libraryStore }

electron/gog/games.ts

+33-8
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ import {
1515
InstallArgs,
1616
LaunchResult,
1717
GOGLoginData,
18-
InstalledInfo,
19-
InstallProgress
18+
InstalledInfo
2019
} from 'types'
2120
import { existsSync, rmSync } from 'graceful-fs'
2221
import {
@@ -28,7 +27,7 @@ import {
2827
} from '../constants'
2928
import { configStore, installedGamesStore } from '../gog/electronStores'
3029
import { logError, logInfo, LogPrefix, logWarning } from '../logger/logger'
31-
import { execAsync, getFileSize, getSteamRuntime } from '../utils'
30+
import { errorHandler, execAsync, getFileSize, getSteamRuntime } from '../utils'
3231
import { GOGUser } from './user'
3332
import {
3433
launchCleanup,
@@ -109,8 +108,15 @@ class GOGGame extends Game {
109108
LogPrefix.Gog
110109
)
111110
}
112-
await GOGLibrary.get().importGame(JSON.parse(res.stdout), path)
113-
return res
111+
try {
112+
await GOGLibrary.get().importGame(JSON.parse(res.stdout), path)
113+
return res
114+
} catch (error) {
115+
logError(
116+
['Failed to import', `${this.appName}:`, res.error],
117+
LogPrefix.Gog
118+
)
119+
}
114120
}
115121

116122
public onInstallOrUpdateOutput(
@@ -224,7 +230,7 @@ class GOGGame extends Game {
224230
buildId: isLinuxNative ? '' : installInfo.game.buildId
225231
}
226232
const array: Array<InstalledInfo> =
227-
(installedGamesStore.get('installed') as Array<InstalledInfo>) || []
233+
(installedGamesStore.get('installed', []) as Array<InstalledInfo>) || []
228234
array.push(installedData)
229235
installedGamesStore.set('installed', array)
230236
GOGLibrary.get().refreshInstalled()
@@ -251,6 +257,14 @@ class GOGGame extends Game {
251257
(await GameConfig.get(this.appName).getSettings())
252258
const gameInfo = GOGLibrary.get().getGameInfo(this.appName)
253259

260+
if (!existsSync(gameInfo.install.install_path)) {
261+
errorHandler({
262+
error: 'appears to be deleted',
263+
runner: 'gog',
264+
appName: gameInfo.app_name
265+
})
266+
}
267+
254268
const {
255269
success: launchPrepSuccess,
256270
failureReason: launchPrepFailReason,
@@ -513,7 +527,7 @@ class GOGGame extends Game {
513527
res.stderr = stderr
514528
})
515529
.catch((error) => {
516-
res.error = error
530+
res.error = `${error}`
517531
})
518532
} else {
519533
rmSync(object.install_path, { recursive: true })
@@ -611,7 +625,7 @@ class GOGGame extends Game {
611625
if (GOGUser.isTokenExpired()) {
612626
await GOGUser.refreshToken()
613627
}
614-
const credentials = configStore.get('credentials') as GOGLoginData
628+
const credentials = configStore.get('credentials', {}) as GOGLoginData
615629

616630
const installPlatform = gameData.install.platform
617631
const logPath = join(heroicGamesConfigPath, this.appName + '.log')
@@ -643,6 +657,17 @@ class GOGGame extends Game {
643657

644658
return runWineCommand(await this.getSettings(), command, altWineBin, wait)
645659
}
660+
661+
async forceUninstall(): Promise<void> {
662+
const installed = installedGamesStore.get(
663+
'installed',
664+
[]
665+
) as Array<InstalledInfo>
666+
const newInstalled = installed.filter((g) => g.appName !== this.appName)
667+
installedGamesStore.set('installed', newInstalled)
668+
const mainWindow = BrowserWindow.getFocusedWindow()
669+
mainWindow.webContents.send('refreshLibrary', 'gog')
670+
}
646671
}
647672

648673
export { GOGGame }

electron/gog/library.ts

+19-12
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ export class GOGLibrary {
8686
}
8787

8888
const gamesObjects: GameInfo[] = []
89-
const gamesArray = libraryStore.get('games') as GameInfo[]
89+
const gamesArray = libraryStore.get('games', []) as GameInfo[]
9090
for (const game of gameApiArray as GOGGameInfo[]) {
9191
let unifiedObject = gamesArray
9292
? gamesArray.find((value) => value.app_name === String(game.id))
@@ -182,7 +182,7 @@ export class GOGLibrary {
182182
}
183183

184184
const gogInfo = JSON.parse(res.stdout)
185-
const libraryArray = libraryStore.get('games') as GameInfo[]
185+
const libraryArray = libraryStore.get('games', []) as GameInfo[]
186186
const gameObjectIndex = libraryArray.findIndex(
187187
(value) => value.app_name === appName
188188
)
@@ -219,7 +219,7 @@ export class GOGLibrary {
219219
*/
220220
public refreshInstalled() {
221221
const installedArray =
222-
(installedGamesStore.get('installed') as Array<InstalledInfo>) || []
222+
(installedGamesStore.get('installed', []) as Array<InstalledInfo>) || []
223223
this.installedGames.clear()
224224
installedArray.forEach((value) => {
225225
this.installedGames.set(value.appName, value)
@@ -230,7 +230,7 @@ export class GOGLibrary {
230230
const cachedGameData = this.library.get(appName)
231231

232232
const installedArray =
233-
(installedGamesStore.get('installed') as Array<InstalledInfo>) || []
233+
(installedGamesStore.get('installed', []) as Array<InstalledInfo>) || []
234234

235235
const gameIndex = installedArray.findIndex(
236236
(value) => value.appName === appName
@@ -488,18 +488,25 @@ export class GOGLibrary {
488488
logInfo(`Loading playTask data from ${infoFilePath}`, LogPrefix.Backend)
489489
const fileData = readFileSync(infoFilePath, { encoding: 'utf-8' })
490490

491-
const jsonData = JSON.parse(fileData)
492-
const playTasks = jsonData.playTasks
491+
try {
492+
const jsonData = JSON.parse(fileData)
493+
const playTasks = jsonData.playTasks
493494

494-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
495-
const primary = playTasks.find((value: any) => value?.isPrimary)
495+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
496+
const primary = playTasks.find((value: any) => value?.isPrimary)
496497

497-
const workingDir = primary?.workingDir
498+
const workingDir = primary?.workingDir
498499

499-
if (workingDir) {
500-
return join(workingDir, primary.path)
500+
if (workingDir) {
501+
return join(workingDir, primary.path)
502+
}
503+
return primary.path
504+
} catch (error) {
505+
logError(
506+
`Error reading ${fileData}, could not complete operation`,
507+
LogPrefix.Gog
508+
)
501509
}
502-
return primary.path
503510
}
504511

505512
return ''

electron/gog/user.ts

+14-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import axios from 'axios'
22
import { logError, logInfo, LogPrefix, logWarning } from '../logger/logger'
33
import { GOGLoginData } from '../types'
44
import { configStore, libraryStore } from '../gog/electronStores'
5+
import { errorHandler } from '../utils'
56

67
const gogAuthenticateUrl =
78
'https://auth.gog.com/token?client_id=46899977096215655&client_secret=9d85c43b1482497dbbce61f6e4aa173a433796eeae2ca8c5f6129f2dc4de46d9&grant_type=authorization_code&redirect_uri=https%3A%2F%2Fembed.gog.com%2Fon_login_success%3Forigin%3Dclient&code='
@@ -72,14 +73,17 @@ export class GOGUser {
7273
return this.refreshToken()
7374
}
7475

75-
return configStore.get('credentials') as GOGLoginData
76+
return configStore.get('credentials', {}) as GOGLoginData
7677
}
7778

7879
/**
7980
* Refreshes token and returns new credentials
8081
*/
8182
public static async refreshToken(): Promise<GOGLoginData | null> {
82-
const user: GOGLoginData = configStore.get('credentials') as GOGLoginData
83+
const user: GOGLoginData = configStore.get(
84+
'credentials',
85+
{}
86+
) as GOGLoginData
8387
logInfo('Refreshing access_token', LogPrefix.Gog)
8488
if (user) {
8589
const response = await axios
@@ -103,12 +107,19 @@ export class GOGUser {
103107
return data
104108
} else {
105109
logError('No credentials, auth required', LogPrefix.Gog)
110+
errorHandler({
111+
error: 'No credentials',
112+
runner: 'GOG'
113+
})
106114
return null
107115
}
108116
}
109117

110118
public static isTokenExpired() {
111-
const user: GOGLoginData = configStore.get('credentials') as GOGLoginData
119+
const user: GOGLoginData = configStore.get(
120+
'credentials',
121+
null
122+
) as GOGLoginData
112123
if (!user) {
113124
return true
114125
}

0 commit comments

Comments
 (0)