From 055fe2dff49d99edded3acebca26858bd61af2bd Mon Sep 17 00:00:00 2001 From: Samuel Elliott Date: Sun, 27 Nov 2022 16:19:56 +0000 Subject: [PATCH] Handle timeouts as temporary errors --- src/common/notify.ts | 8 ++++++-- src/common/presence.ts | 13 +++++++++++-- src/util/misc.ts | 6 +++++- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/common/notify.ts b/src/common/notify.ts index e90ca9c..b508524 100644 --- a/src/common/notify.ts +++ b/src/common/notify.ts @@ -7,7 +7,7 @@ import { ErrorResponse } from '../api/util.js'; import { SavedToken } from './auth/coral.js'; import { SplatNet2RecordsMonitor } from './splatnet2/monitor.js'; import Loop, { LoopResult } from '../util/loop.js'; -import { getTitleIdFromEcUrl, hrduration } from '../util/misc.js'; +import { getTitleIdFromEcUrl, hrduration, TemporaryErrorSymbol } from '../util/misc.js'; import { CoralUser } from './users.js'; const debug = createDebug('nxapi:nso:notify'); @@ -187,7 +187,11 @@ export async function handleError( err: ErrorResponse | NodeJS.ErrnoException, loop: Loop, ): Promise { - if ('code' in err && (err as any).type === 'system' && err.code === 'ETIMEDOUT') { + if (TemporaryErrorSymbol in err && err[TemporaryErrorSymbol]) { + debug('Temporary error, waiting %ds before retrying', loop.update_interval, err); + + return LoopResult.OK; + } else if ('code' in err && (err as any).type === 'system' && err.code === 'ETIMEDOUT') { debug('Request timed out, waiting %ds before retrying', loop.update_interval, err); return LoopResult.OK; diff --git a/src/common/presence.ts b/src/common/presence.ts index 48a3b6f..62452a5 100644 --- a/src/common/presence.ts +++ b/src/common/presence.ts @@ -11,6 +11,7 @@ import Loop, { LoopResult } from '../util/loop.js'; import { getTitleIdFromEcUrl } from '../index.js'; import { parseLinkHeader } from '../util/http.js'; import { getUserAgent } from '../util/useragent.js'; +import { TemporaryErrorSymbol } from '../util/misc.js'; const debug = createDebug('nxapi:nso:presence'); const debugEventStream = createDebug('nxapi:nso:presence:sse'); @@ -619,7 +620,13 @@ export class ZncProxyDiscordPresence extends Loop { let timeout: NodeJS.Timeout; let timeout_interval = 90000; - const ontimeout = () => events.dispatchEvent({type: 'error', message: 'Timeout'} as any); + const ontimeout = () => { + const err = new Error('Timeout') as any; + err.type = 'error'; + err[TemporaryErrorSymbol] = true; + Object.defineProperty(err, 'detail', {enumerable: false, value: err}); + events.dispatchEvent(err); + }; events.onopen = event => { debugEventStream('EventSource connected', event); @@ -697,7 +704,9 @@ export class ZncProxyDiscordPresence extends Loop { debugEventStream('EventSource error', event); events.close(); - if ((event as any).message) { + if (event instanceof Error) { + rj(event); + } else if ((event as any).message) { const err = new Error((event as any).message); Object.assign(err, event); rj(err); diff --git a/src/util/misc.ts b/src/util/misc.ts index ee3f79d..4206a84 100644 --- a/src/util/misc.ts +++ b/src/util/misc.ts @@ -1,4 +1,6 @@ +export const TemporaryErrorSymbol = Symbol('TemporaryError'); + export function getTitleIdFromEcUrl(url: string) { const match = url.match(/^https:\/\/ec\.nintendo\.com\/apps\/([0-9a-f]{16})\//); return match?.[1] ?? null; @@ -31,7 +33,9 @@ export function timeoutSignal(ms = 60 * 1000) { const controller = new AbortController(); const timeout = setTimeout(() => { - controller.abort(new Error('Timeout')); + const err = new Error('Timeout'); + Object.assign(err, TemporaryErrorSymbol, true); + controller.abort(err); }, ms); return [controller.signal, () => clearTimeout(timeout), controller] as const;