Files
clawdbot/src/gateway/server-close.ts
Peter Steinberger c379191f80 chore: migrate to oxlint and oxfmt
Co-authored-by: Christoph Nakazawa <christoph.pojer@gmail.com>
2026-01-14 15:02:19 +00:00

124 lines
4.0 KiB
TypeScript

import type { Server as HttpServer } from "node:http";
import type { WebSocketServer } from "ws";
import type { CanvasHostHandler, CanvasHostServer } from "../canvas-host/server.js";
import { type ChannelId, listChannelPlugins } from "../channels/plugins/index.js";
import { stopGmailWatcher } from "../hooks/gmail-watcher.js";
import type { NodeBridgeServer } from "../infra/bridge/server.js";
import type { PluginServicesHandle } from "../plugins/services.js";
export function createGatewayCloseHandler(params: {
bonjourStop: (() => Promise<void>) | null;
tailscaleCleanup: (() => Promise<void>) | null;
canvasHost: CanvasHostHandler | null;
canvasHostServer: CanvasHostServer | null;
bridge: NodeBridgeServer | null;
stopChannel: (name: ChannelId, accountId?: string) => Promise<void>;
pluginServices: PluginServicesHandle | null;
cron: { stop: () => void };
heartbeatRunner: { stop: () => void };
nodePresenceTimers: Map<string, ReturnType<typeof setInterval>>;
broadcast: (event: string, payload: unknown, opts?: { dropIfSlow?: boolean }) => void;
tickInterval: ReturnType<typeof setInterval>;
healthInterval: ReturnType<typeof setInterval>;
dedupeCleanup: ReturnType<typeof setInterval>;
agentUnsub: (() => void) | null;
heartbeatUnsub: (() => void) | null;
chatRunState: { clear: () => void };
clients: Set<{ socket: { close: (code: number, reason: string) => void } }>;
configReloader: { stop: () => Promise<void> };
browserControl: { stop: () => Promise<void> } | null;
wss: WebSocketServer;
httpServer: HttpServer;
}) {
return async (opts?: { reason?: string; restartExpectedMs?: number | null }) => {
const reasonRaw = typeof opts?.reason === "string" ? opts.reason.trim() : "";
const reason = reasonRaw || "gateway stopping";
const restartExpectedMs =
typeof opts?.restartExpectedMs === "number" && Number.isFinite(opts.restartExpectedMs)
? Math.max(0, Math.floor(opts.restartExpectedMs))
: null;
if (params.bonjourStop) {
try {
await params.bonjourStop();
} catch {
/* ignore */
}
}
if (params.tailscaleCleanup) {
await params.tailscaleCleanup();
}
if (params.canvasHost) {
try {
await params.canvasHost.close();
} catch {
/* ignore */
}
}
if (params.canvasHostServer) {
try {
await params.canvasHostServer.close();
} catch {
/* ignore */
}
}
if (params.bridge) {
try {
await params.bridge.close();
} catch {
/* ignore */
}
}
for (const plugin of listChannelPlugins()) {
await params.stopChannel(plugin.id);
}
if (params.pluginServices) {
await params.pluginServices.stop().catch(() => {});
}
await stopGmailWatcher();
params.cron.stop();
params.heartbeatRunner.stop();
for (const timer of params.nodePresenceTimers.values()) {
clearInterval(timer);
}
params.nodePresenceTimers.clear();
params.broadcast("shutdown", {
reason,
restartExpectedMs,
});
clearInterval(params.tickInterval);
clearInterval(params.healthInterval);
clearInterval(params.dedupeCleanup);
if (params.agentUnsub) {
try {
params.agentUnsub();
} catch {
/* ignore */
}
}
if (params.heartbeatUnsub) {
try {
params.heartbeatUnsub();
} catch {
/* ignore */
}
}
params.chatRunState.clear();
for (const c of params.clients) {
try {
c.socket.close(1012, "service restart");
} catch {
/* ignore */
}
}
params.clients.clear();
await params.configReloader.stop().catch(() => {});
if (params.browserControl) {
await params.browserControl.stop().catch(() => {});
}
await new Promise<void>((resolve) => params.wss.close(() => resolve()));
await new Promise<void>((resolve, reject) =>
params.httpServer.close((err) => (err ? reject(err) : resolve())),
);
};
}