diff --git a/src/cli/presence-server.ts b/src/cli/presence-server.ts index 48077ce..bec9766 100644 --- a/src/cli/presence-server.ts +++ b/src/cli/presence-server.ts @@ -441,6 +441,14 @@ function createApp( res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify(response, replacer)); } catch (err) { + if (err && 'type' in err && 'code' in err && (err as any).type === 'system') { + const code: string = (err as any).code; + + if (code === 'ETIMEDOUT' || code === 'ENOTFOUND' || code === 'EAI_AGAIN') { + res.setHeader('Retry-After', '60'); + } + } + res.statusCode = 500; res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ diff --git a/src/common/presence.ts b/src/common/presence.ts index f9fdf41..68ce91a 100644 --- a/src/common/presence.ts +++ b/src/common/presence.ts @@ -15,6 +15,7 @@ const debugSplatnet2 = createDebug('nxapi:nso:presence:splatnet2'); const MAX_CONNECT_ATTEMPTS = Infinity; // 10 const RECONNECT_INTERVAL = 5000; // 5 seconds +const MAX_PROXY_AUTO_RETRY = 10; interface SavedPresence { presence: Presence; @@ -529,17 +530,40 @@ export class ZncProxyDiscordPresence extends Loop { } async init() { - await this.update(); - - return LoopResult.OK; + return await this.update() ?? LoopResult.OK; } - async update() { - const [presence, user, data] = await getPresenceFromUrl(this.presence_url); - this.last_data = data; + protected proxy_temporary_errors = 0; - await this.discord.updatePresenceForDiscord(presence, user); - await this.updatePresenceForSplatNet2Monitor(presence, this.presence_url); + async update() { + try { + const [presence, user, data] = await getPresenceFromUrl(this.presence_url); + this.last_data = data; + this.proxy_temporary_errors = 0; + + await this.discord.updatePresenceForDiscord(presence, user); + await this.updatePresenceForSplatNet2Monitor(presence, this.presence_url); + } catch (err) { + if (err instanceof ErrorResponse) { + const retry_after = err.response.headers.get('Retry-After'); + if (!retry_after || !/^\d+$/.test(retry_after)) throw err; + + debug('Received error response - suggests waiting %ds before retrying', retry_after, err.data); + + this.proxy_temporary_errors++; + if (this.proxy_temporary_errors > MAX_PROXY_AUTO_RETRY) throw err; + + // Still report the error to ZncDiscordPresenceClient to prevent presence being stuck + // on repeated errors + this.discord.onError(err); + + await new Promise(rs => setTimeout(this.timeout_resolve = rs, parseInt(retry_after) * 1000)); + + return LoopResult.OK_SKIP_INTERVAL; + } + + throw err; + } } async onStop() { diff --git a/src/util/loop.ts b/src/util/loop.ts index adfc9bc..93410a1 100644 --- a/src/util/loop.ts +++ b/src/util/loop.ts @@ -49,7 +49,7 @@ export default abstract class Loop { } private skip_interval_once = false; - private timeout_resolve: ((value: void) => void) | null = null; + protected timeout_resolve: ((value: void) => void) | null = null; skipIntervalInCurrentLoop() { debug('Skip update interval', this.is_loop_active);