From 393c414e905185f94224779e44a9d87e5acecd52 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 8 Jan 2026 20:23:06 +0100 Subject: [PATCH] refactor: split discord gateway helpers --- src/discord/monitor.gateway.test.ts | 14 ++++++- src/discord/monitor.gateway.ts | 63 ++++++++++++++++++++++++++++ src/discord/monitor.ts | 65 +++-------------------------- 3 files changed, 81 insertions(+), 61 deletions(-) create mode 100644 src/discord/monitor.gateway.ts diff --git a/src/discord/monitor.gateway.test.ts b/src/discord/monitor.gateway.test.ts index fc7d62784..26685da72 100644 --- a/src/discord/monitor.gateway.test.ts +++ b/src/discord/monitor.gateway.test.ts @@ -2,7 +2,7 @@ import { EventEmitter } from "node:events"; import { describe, expect, it, vi } from "vitest"; -import { waitForDiscordGatewayStop } from "./monitor.js"; +import { waitForDiscordGatewayStop } from "./monitor.gateway.js"; describe("waitForDiscordGatewayStop", () => { it("resolves on abort and disconnects gateway", async () => { @@ -46,4 +46,16 @@ describe("waitForDiscordGatewayStop", () => { abort.abort(); expect(disconnect).toHaveBeenCalledTimes(1); }); + + it("resolves on abort without a gateway", async () => { + const abort = new AbortController(); + + const promise = waitForDiscordGatewayStop({ + abortSignal: abort.signal, + }); + + abort.abort(); + + await expect(promise).resolves.toBeUndefined(); + }); }); diff --git a/src/discord/monitor.gateway.ts b/src/discord/monitor.gateway.ts new file mode 100644 index 000000000..d09df288b --- /dev/null +++ b/src/discord/monitor.gateway.ts @@ -0,0 +1,63 @@ +import type { EventEmitter } from "node:events"; + +export type DiscordGatewayHandle = { + emitter?: Pick; + disconnect?: () => void; +}; + +export function getDiscordGatewayEmitter( + gateway?: unknown, +): EventEmitter | undefined { + return (gateway as { emitter?: EventEmitter } | undefined)?.emitter; +} + +export async function waitForDiscordGatewayStop(params: { + gateway?: DiscordGatewayHandle; + abortSignal?: AbortSignal; + onGatewayError?: (err: unknown) => void; +}): Promise { + const { gateway, abortSignal, onGatewayError } = params; + const emitter = gateway?.emitter; + return await new Promise((resolve, reject) => { + let settled = false; + const cleanup = () => { + abortSignal?.removeEventListener("abort", onAbort); + emitter?.removeListener("error", onGatewayErrorEvent); + }; + const finishResolve = () => { + if (settled) return; + settled = true; + cleanup(); + try { + gateway?.disconnect?.(); + } finally { + resolve(); + } + }; + const finishReject = (err: unknown) => { + if (settled) return; + settled = true; + cleanup(); + try { + gateway?.disconnect?.(); + } finally { + reject(err); + } + }; + const onAbort = () => { + finishResolve(); + }; + const onGatewayErrorEvent = (err: unknown) => { + onGatewayError?.(err); + finishReject(err); + }; + + if (abortSignal?.aborted) { + onAbort(); + return; + } + + abortSignal?.addEventListener("abort", onAbort, { once: true }); + emitter?.on("error", onGatewayErrorEvent); + }); +} diff --git a/src/discord/monitor.ts b/src/discord/monitor.ts index 5d845fe2e..754d5f302 100644 --- a/src/discord/monitor.ts +++ b/src/discord/monitor.ts @@ -1,5 +1,3 @@ -import type { EventEmitter } from "node:events"; - import { ChannelType, Client, @@ -63,6 +61,10 @@ import type { RuntimeEnv } from "../runtime.js"; import { loadWebMedia } from "../web/media.js"; import { resolveDiscordAccount } from "./accounts.js"; import { chunkDiscordText } from "./chunk.js"; +import { + getDiscordGatewayEmitter, + waitForDiscordGatewayStop, +} from "./monitor.gateway.js"; import { fetchDiscordApplicationId } from "./probe.js"; import { reactMessageDiscord, sendMessageDiscord } from "./send.js"; import { normalizeDiscordToken } from "./token.js"; @@ -84,62 +86,6 @@ type DiscordMediaInfo = { placeholder: string; }; -type DiscordGatewayHandle = { - emitter?: Pick; - disconnect?: () => void; -}; - -export async function waitForDiscordGatewayStop(params: { - gateway?: DiscordGatewayHandle; - abortSignal?: AbortSignal; - onGatewayError?: (err: unknown) => void; -}): Promise { - const { gateway, abortSignal, onGatewayError } = params; - const emitter = gateway?.emitter; - return await new Promise((resolve, reject) => { - let settled = false; - const cleanup = () => { - abortSignal?.removeEventListener("abort", onAbort); - emitter?.removeListener("error", onGatewayErrorEvent); - }; - const finishResolve = () => { - if (settled) return; - settled = true; - cleanup(); - try { - gateway?.disconnect?.(); - } finally { - resolve(); - } - }; - const finishReject = (err: unknown) => { - if (settled) return; - settled = true; - cleanup(); - try { - gateway?.disconnect?.(); - } finally { - reject(err); - } - }; - const onAbort = () => { - finishResolve(); - }; - const onGatewayErrorEvent = (err: unknown) => { - onGatewayError?.(err); - finishReject(err); - }; - - if (abortSignal?.aborted) { - onAbort(); - return; - } - - abortSignal?.addEventListener("abort", onAbort, { once: true }); - emitter?.on("error", onGatewayErrorEvent); - }); -} - type DiscordHistoryEntry = { sender: string; body: string; @@ -461,8 +407,7 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) { runtime.log?.(`logged in to discord${botUserId ? ` as ${botUserId}` : ""}`); const gateway = client.getPlugin("gateway"); - const gatewayEmitter = (gateway as unknown as { emitter?: EventEmitter }) - ?.emitter; + const gatewayEmitter = getDiscordGatewayEmitter(gateway); await waitForDiscordGatewayStop({ gateway: gateway ? {