diff --git a/src/app/browser/preferences/index.tsx b/src/app/browser/preferences/index.tsx index 3777e31..b84e2be 100644 --- a/src/app/browser/preferences/index.tsx +++ b/src/app/browser/preferences/index.tsx @@ -30,6 +30,8 @@ function _Preferences(props: { const [login_item, ,, forceRefreshLoginItem] = useAsync(useCallback(() => ipc.getLoginItemSettings(), [ipc])); + const [show_error_alerts, , show_error_alerts_state, forceRefreshErrorAlerts] = useAsync(useCallback(() => ipc.getShowErrorAlerts(), [ipc])); + const setOpenAtLogin = useCallback(async (open_at_login: boolean | 'mixed') => { await ipc.setLoginItemSettings({...login_item!, startup_enabled: !!open_at_login}); forceRefreshLoginItem(); @@ -39,6 +41,11 @@ function _Preferences(props: { forceRefreshLoginItem(); }, [ipc, login_item]); + const setShowErrorAlerts = useCallback(async (show_error_alerts: boolean | 'mixed') => { + await ipc.setShowErrorAlerts(!!show_error_alerts); + forceRefreshErrorAlerts(); + }, [ipc]); + const [discord_users, discord_users_error, discord_users_state, forceRefreshDiscordUsers] = useAsync(useCallback(() => ipc.getDiscordUsers(), [ipc])); @@ -97,7 +104,8 @@ function _Preferences(props: { useEventListener(events, 'window:refresh', () => ( forceRefreshAccounts(), forceRefreshLoginItem(), - forceRefreshDiscordUsers(), forceRefreshDiscordOptions() + forceRefreshDiscordUsers(), forceRefreshDiscordOptions(), + forceRefreshErrorAlerts() ), []); if (!users || @@ -280,6 +288,26 @@ function _Preferences(props: { {t('splatnet3.discord_help_2')} + + + + {t('miscellaneous.heading')} + + + + + setShowErrorAlerts(!(show_error_alerts ?? true))}> + {t('miscellaneous.show_error_alerts')} + + + {t('miscellaneous.show_error_alerts_help')} + + ; } diff --git a/src/app/i18n/locale/en-gb.ts b/src/app/i18n/locale/en-gb.ts index 4492354..6fbef42 100644 --- a/src/app/i18n/locale/en-gb.ts +++ b/src/app/i18n/locale/en-gb.ts @@ -292,6 +292,12 @@ export const preferences_window = { discord_help_1: 'Uses SplatNet 3 to retrieve additional presence information while playing Splatoon 3. You must be using a secondary Nintendo Account that is friends with your main account to fetch your presence, and the secondary account must be able to access SplatNet 3.', discord_help_2: 'When using a presence URL that returns Splatoon 3 data additional presence information will be shown regardless of this setting.', }, + + miscellaneous: { + heading: 'Miscellaneous', + show_error_alerts: 'Show error alerts', + show_error_alerts_help: 'Shows an alert when an error occurs while updating presence. If this is disabled, nxapi will delay updates after errors.', + }, }; export const friend_window = { diff --git a/src/app/main/ipc.ts b/src/app/main/ipc.ts index 44b7320..9713b11 100644 --- a/src/app/main/ipc.ts +++ b/src/app/main/ipc.ts @@ -90,6 +90,9 @@ export function setupIpc(appinstance: App, ipcMain: IpcMain) { handle('systemPreferences:getloginitem', () => appinstance.store.getLoginItem()); handle('systemPreferences:setloginitem', (e, settings: LoginItemOptions) => appinstance.store.setLoginItem(settings)); + handle('preferences:getshowerroralerts', () => storage.getItem('ShowErrorAlertsPreference').then(s => s ?? false)); + handle('preferences:setshowerroralerts', (e, show: boolean) => storage.setItem('ShowErrorAlertsPreference', show)); + handle('update:get', () => appinstance.updater.cache ?? appinstance.updater.check()); handle('update:check', () => appinstance.updater.check()); handle('statusupdates:get', () => appinstance.statusupdates.cache ?? []); diff --git a/src/app/main/monitor.ts b/src/app/main/monitor.ts index 75ead87..5b47712 100644 --- a/src/app/main/monitor.ts +++ b/src/app/main/monitor.ts @@ -69,6 +69,9 @@ export class PresenceMonitorManager { defaultId: 0, }); + const show_error_alerts: boolean = await this.app.store.storage.getItem('ShowErrorAlertsPreference') ?? false; + if (!show_error_alerts) return ErrorResult.DEFER; + if (response === 1) { return ErrorResult.RETRY; } @@ -396,6 +399,9 @@ export class PresenceMonitorManager { return LoopResult.OK; } + const show_error_alerts: boolean = await this.app.store.storage.getItem('ShowErrorAlertsPreference') ?? false; + if (!show_error_alerts) return LoopResult.DEFER_NEXT_UPDATE; + const {response} = await showErrorDialog({ message: err.name + ' updating presence monitor', error: err, diff --git a/src/app/preload/index.ts b/src/app/preload/index.ts index f94520e..472332b 100644 --- a/src/app/preload/index.ts +++ b/src/app/preload/index.ts @@ -50,6 +50,9 @@ const ipc = { getLoginItemSettings: () => inv('systemPreferences:getloginitem'), setLoginItemSettings: (settings: LoginItemOptions) => inv('systemPreferences:setloginitem', settings), + getShowErrorAlerts: () => inv('preferences:getshowerroralerts'), + setShowErrorAlerts: (show: boolean) => inv('preferences:setshowerroralerts', show), + getUpdateData: () => inv('update:get'), checkUpdates: () => inv('update:check'), getStatusUpdateData: () => inv('statusupdates:get'), diff --git a/src/discord/monitor/splatoon3.ts b/src/discord/monitor/splatoon3.ts index e80d3bd..83d1261 100644 --- a/src/discord/monitor/splatoon3.ts +++ b/src/discord/monitor/splatoon3.ts @@ -91,6 +91,7 @@ export default class SplatNet3Monitor extends EmbeddedLoop { debug('Error authenticating to SplatNet 3', err); const result = await this.discord_presence.handleError(err as Error); if (result === ErrorResult.RETRY) return this.init(); + if (result === ErrorResult.DEFER) return this.errors++, LoopResult.DEFER_NEXT_UPDATE; if (result === ErrorResult.STOP) return LoopResult.STOP; } @@ -168,6 +169,7 @@ export default class SplatNet3Monitor extends EmbeddedLoop { async handleError(err: Error) { const result = await this.discord_presence.handleError(err as Error); if (result === ErrorResult.RETRY) return LoopResult.OK_SKIP_INTERVAL; + if (result === ErrorResult.DEFER) return LoopResult.DEFER_NEXT_UPDATE; this.friend = null; this.discord_presence.refreshPresence(); diff --git a/src/discord/types.ts b/src/discord/types.ts index 787460b..86f8a1d 100644 --- a/src/discord/types.ts +++ b/src/discord/types.ts @@ -148,5 +148,6 @@ export interface ExternalMonitor extends EmbeddedLoop { export enum ErrorResult { STOP, RETRY, + DEFER, IGNORE, } diff --git a/src/util/loop.ts b/src/util/loop.ts index 6084488..0d4cdfd 100644 --- a/src/util/loop.ts +++ b/src/util/loop.ts @@ -4,6 +4,7 @@ const debug = createDebug('nxapi:util:loop'); export default abstract class Loop { update_interval = 60; + errors = 0; init(): void | Promise {} @@ -13,8 +14,11 @@ export default abstract class Loop { try { const result = init ? await this.init() : await this.update(); + this.errors = 0; + return result ?? (init ? LoopResult.OK_SKIP_INTERVAL : LoopResult.OK); } catch (err) { + this.errors++; return this.handleError(err as any); } } @@ -23,6 +27,10 @@ export default abstract class Loop { throw err; } + get next_update_interval() { + return this.update_interval * Math.min(this.errors / 2, 20); + } + private is_loop_active = 0; async loop(init = false) { @@ -38,6 +46,14 @@ export default abstract class Loop { await new Promise(rs => setTimeout(this.timeout_resolve = rs, this.update_interval * 1000)); } } + if (result === LoopResult.DEFER_NEXT_UPDATE) { + if (this.skip_interval_once) { + this.skip_interval_once = false; + } else { + await new Promise(rs => setTimeout(this.timeout_resolve = rs, this.next_update_interval * 1000)); + } + return LoopResult.DEFER_NEXT_UPDATE; + } if (result === LoopResult.STOP) { return LoopResult.STOP; } @@ -62,11 +78,13 @@ export default abstract class Loop { const LoopRunOk = Symbol('LoopRunOk'); const LoopRunOkSkipInterval = Symbol('LoopRunOkSkipInterval'); +const LoopRunIncrementInterval = Symbol('LoopRunIncrementInterval'); const LoopRunStop = Symbol('LoopRunStopNow'); export enum LoopResult { OK = LoopRunOk as any, OK_SKIP_INTERVAL = LoopRunOkSkipInterval as any, + DEFER_NEXT_UPDATE = LoopRunIncrementInterval as any, STOP = LoopRunStop as any, } @@ -92,25 +110,26 @@ export abstract class EmbeddedLoop extends Loop { private async _run() { this._running++; const i = this._running; + let init = true; try { - const result = await this.loop(true); - if (result === LoopResult.STOP) return; - while (i === this._running) { - const result = await this.loop(); + const result = await this.loop(init); + + if (init && result !== LoopResult.DEFER_NEXT_UPDATE) { + init = false; + } if (result === LoopResult.STOP) { - await this.onStop?.(); + if (!init) await this.onStop?.(); return; } } - if (this._running === 0 && !this.onStop) { + if (this._running === 0 && !init && !this.onStop) { // Run one more time after the loop ends const result = await this.loopRun(); } - } finally { this._running = 0; }