






























@@ -3,13 +3,22 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
33import type { WebSocketServer } from "ws";
44import type { ResolvedGatewayAuth } from "../auth.js";
556-const { attachGatewayWsMessageHandlerMock } = vi.hoisted(() => ({
7-attachGatewayWsMessageHandlerMock: vi.fn(),
8-}));
6+const { attachGatewayWsMessageHandlerMock, broadcastPresenceSnapshotMock, upsertPresenceMock } =
7+vi.hoisted(() => ({
8+attachGatewayWsMessageHandlerMock: vi.fn(),
9+broadcastPresenceSnapshotMock: vi.fn(),
10+upsertPresenceMock: vi.fn(),
11+}));
9121013vi.mock("./ws-connection/message-handler.js", () => ({
1114attachGatewayWsMessageHandler: attachGatewayWsMessageHandlerMock,
1215}));
16+vi.mock("../../infra/system-presence.js", () => ({
17+upsertPresence: upsertPresenceMock,
18+}));
19+vi.mock("./presence-events.js", () => ({
20+broadcastPresenceSnapshot: broadcastPresenceSnapshotMock,
21+}));
13221423import { attachGatewayWsConnectionHandler } from "./ws-connection.js";
1524import { resolveSharedGatewaySessionGeneration } from "./ws-shared-generation.js";
@@ -122,6 +131,8 @@ async function connectTestWs(
122131describe("attachGatewayWsConnectionHandler", () => {
123132beforeEach(() => {
124133attachGatewayWsMessageHandlerMock.mockReset();
134+broadcastPresenceSnapshotMock.mockReset();
135+upsertPresenceMock.mockReset();
125136});
126137127138afterEach(() => {
@@ -234,4 +245,78 @@ describe("attachGatewayWsConnectionHandler", () => {
234245vi.advanceTimersByTime(25_000);
235246expect(socket.ping).toHaveBeenCalledTimes(1);
236247});
248+249+it("skips node presence disconnects for stale reconnected sockets", async () => {
250+const listeners = new Map<string, (...args: unknown[]) => void>();
251+const unregister = vi.fn(() => null);
252+const wss = {
253+on: vi.fn((event: string, handler: (...args: unknown[]) => void) => {
254+listeners.set(event, handler);
255+}),
256+} as unknown as WebSocketServer;
257+const socket = Object.assign(new EventEmitter(), {
258+_socket: {
259+remoteAddress: "127.0.0.1",
260+remotePort: 1234,
261+localAddress: "127.0.0.1",
262+localPort: 5678,
263+},
264+send: vi.fn(),
265+close: vi.fn(),
266+});
267+const upgradeReq = {
268+headers: { host: "127.0.0.1:19001" },
269+socket: { localAddress: "127.0.0.1" },
270+};
271+272+attachGatewayWsConnectionHandler({
273+ wss,
274+clients: new Set(),
275+preauthConnectionBudget: { release: vi.fn() } as never,
276+port: 19001,
277+resolvedAuth: createResolvedAuth("token"),
278+gatewayMethods: [],
279+events: [],
280+refreshHealthSnapshot: vi.fn(),
281+logGateway: createLogger() as never,
282+logHealth: createLogger() as never,
283+logWsControl: createLogger() as never,
284+extraHandlers: {},
285+broadcast: vi.fn(),
286+buildRequestContext: () =>
287+({
288+unsubscribeAllSessionEvents: vi.fn(),
289+nodeRegistry: { unregister },
290+nodeUnsubscribeAll: vi.fn(),
291+}) as never,
292+});
293+294+const onConnection = listeners.get("connection");
295+expect(onConnection).toBeTypeOf("function");
296+onConnection?.(socket, upgradeReq);
297+await waitForLazyMessageHandler();
298+299+const passed = attachGatewayWsMessageHandlerMock.mock.calls[0]?.[0] as {
300+setClient: (client: unknown) => boolean;
301+};
302+expect(
303+passed.setClient({
304+ socket,
305+connect: {
306+role: "node",
307+client: { id: "openclaw-macos", mode: "node" },
308+device: { id: "node-1" },
309+},
310+connId: "conn-old",
311+presenceKey: "node-1",
312+usesSharedGatewayAuth: false,
313+}),
314+).toBe(true);
315+316+socket.emit("close", 1000, Buffer.from("stale"));
317+318+expect(unregister).toHaveBeenCalledTimes(1);
319+expect(upsertPresenceMock).not.toHaveBeenCalled();
320+expect(broadcastPresenceSnapshotMock).not.toHaveBeenCalled();
321+});
237322});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。