diff --git a/src/discord-bridge.ts b/src/discord-bridge.ts index 7118a698..e43d0525 100644 --- a/src/discord-bridge.ts +++ b/src/discord-bridge.ts @@ -769,8 +769,9 @@ export class DiscordBridge { } activeStartupHint = this.cfg.enabled && this.cfg.token && this.cfg.startupHint ? DISCORD_STARTUP_HINT : undefined; // A save that changed neither the switch nor the token keeps the gateway session, so it can resume. + // A gateway that gave up (a fatal close, such as a missing intent) is replaced, or a fix could never take. const want = this.cfg.enabled && this.cfg.token && !this.held ? this.cfg.token : ''; - if (this.gateway && want && want === this.gatewayToken) return; + if (this.gateway && this.gateway.state !== 'error' && want && want === this.gatewayToken) return; this.gateway?.stop(); this.gateway = null; this.gatewayToken = want; diff --git a/test/discord-bridge.test.ts b/test/discord-bridge.test.ts index 222eb9b6..9f69fa70 100644 --- a/test/discord-bridge.test.ts +++ b/test/discord-bridge.test.ts @@ -586,6 +586,30 @@ describe('DiscordBridge', () => { b.stop(); }); + it('replaces a gateway that gave up on a fatal close when the same config is saved again', async () => { + const sockets: FakeSocket[] = []; + const b = new DiscordBridge({ + configDir: dir, + fetchImpl: async () => jsonRes({ id: 'msg' }), + sendInput, + socketFactory: () => { + const s = new FakeSocket(); + sockets.push(s); + return s; + }, + now: () => t, + sleep: async () => undefined, + }); + await b.reload(); + await vi.waitFor(() => expect(sockets).toHaveLength(1)); + sockets[0].drop(4014); // Message Content Intent off: fatal, no reconnect + expect(b.gatewayState).toBe('error'); + await b.reload(); + await vi.waitFor(() => expect(sockets).toHaveLength(2)); + expect(b.gatewayState).not.toBe('error'); + b.stop(); + }); + it('keeps the gateway session across a save that changes neither the switch nor the token', async () => { const b = bridge(); await b.reload();