refactor: split discord gateway helpers

This commit is contained in:
Peter Steinberger
2026-01-08 20:23:06 +01:00
parent bf67b29a0e
commit 393c414e90
3 changed files with 81 additions and 61 deletions

View File

@@ -2,7 +2,7 @@ import { EventEmitter } from "node:events";
import { describe, expect, it, vi } from "vitest"; import { describe, expect, it, vi } from "vitest";
import { waitForDiscordGatewayStop } from "./monitor.js"; import { waitForDiscordGatewayStop } from "./monitor.gateway.js";
describe("waitForDiscordGatewayStop", () => { describe("waitForDiscordGatewayStop", () => {
it("resolves on abort and disconnects gateway", async () => { it("resolves on abort and disconnects gateway", async () => {
@@ -46,4 +46,16 @@ describe("waitForDiscordGatewayStop", () => {
abort.abort(); abort.abort();
expect(disconnect).toHaveBeenCalledTimes(1); 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();
});
}); });

View File

@@ -0,0 +1,63 @@
import type { EventEmitter } from "node:events";
export type DiscordGatewayHandle = {
emitter?: Pick<EventEmitter, "on" | "removeListener">;
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<void> {
const { gateway, abortSignal, onGatewayError } = params;
const emitter = gateway?.emitter;
return await new Promise<void>((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);
});
}

View File

@@ -1,5 +1,3 @@
import type { EventEmitter } from "node:events";
import { import {
ChannelType, ChannelType,
Client, Client,
@@ -63,6 +61,10 @@ import type { RuntimeEnv } from "../runtime.js";
import { loadWebMedia } from "../web/media.js"; import { loadWebMedia } from "../web/media.js";
import { resolveDiscordAccount } from "./accounts.js"; import { resolveDiscordAccount } from "./accounts.js";
import { chunkDiscordText } from "./chunk.js"; import { chunkDiscordText } from "./chunk.js";
import {
getDiscordGatewayEmitter,
waitForDiscordGatewayStop,
} from "./monitor.gateway.js";
import { fetchDiscordApplicationId } from "./probe.js"; import { fetchDiscordApplicationId } from "./probe.js";
import { reactMessageDiscord, sendMessageDiscord } from "./send.js"; import { reactMessageDiscord, sendMessageDiscord } from "./send.js";
import { normalizeDiscordToken } from "./token.js"; import { normalizeDiscordToken } from "./token.js";
@@ -84,62 +86,6 @@ type DiscordMediaInfo = {
placeholder: string; placeholder: string;
}; };
type DiscordGatewayHandle = {
emitter?: Pick<EventEmitter, "on" | "removeListener">;
disconnect?: () => void;
};
export async function waitForDiscordGatewayStop(params: {
gateway?: DiscordGatewayHandle;
abortSignal?: AbortSignal;
onGatewayError?: (err: unknown) => void;
}): Promise<void> {
const { gateway, abortSignal, onGatewayError } = params;
const emitter = gateway?.emitter;
return await new Promise<void>((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 = { type DiscordHistoryEntry = {
sender: string; sender: string;
body: string; body: string;
@@ -461,8 +407,7 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
runtime.log?.(`logged in to discord${botUserId ? ` as ${botUserId}` : ""}`); runtime.log?.(`logged in to discord${botUserId ? ` as ${botUserId}` : ""}`);
const gateway = client.getPlugin<GatewayPlugin>("gateway"); const gateway = client.getPlugin<GatewayPlugin>("gateway");
const gatewayEmitter = (gateway as unknown as { emitter?: EventEmitter }) const gatewayEmitter = getDiscordGatewayEmitter(gateway);
?.emitter;
await waitForDiscordGatewayStop({ await waitForDiscordGatewayStop({
gateway: gateway gateway: gateway
? { ? {