

























@@ -682,6 +682,160 @@ describe("GatewayBrowserClient", () => {
682682client.stop();
683683});
684684685+it("keeps hello callback errors inside connect dispatch", async () => {
686+const consoleError = vi.spyOn(console, "error").mockImplementation(() => undefined);
687+const onHello = vi.fn(() => {
688+throw new Error("hello callback failed");
689+});
690+const client = new GatewayBrowserClient({
691+url: "ws://127.0.0.1:18789",
692+token: "shared-auth-token",
693+ onHello,
694+});
695+696+try {
697+const { ws, connectFrame } = await startConnect(client);
698+ws.emitMessage({
699+type: "res",
700+id: connectFrame.id,
701+ok: true,
702+payload: {
703+type: "hello-ok",
704+protocol: 4,
705+auth: { role: "operator", scopes: [] },
706+},
707+});
708+709+await vi.waitFor(() => expect(onHello).toHaveBeenCalledOnce());
710+await Promise.resolve();
711+expect(ws.lastClose).toBeNull();
712+expect(consoleError).toHaveBeenCalledWith(
713+"[gateway] hello handler error:",
714+expect.any(Error),
715+);
716+} finally {
717+client.stop();
718+consoleError.mockRestore();
719+}
720+});
721+722+it("keeps close callback errors from blocking reconnect scheduling", async () => {
723+useNodeFakeTimers();
724+const consoleError = vi.spyOn(console, "error").mockImplementation(() => undefined);
725+const onClose = vi.fn(() => {
726+throw new Error("close callback failed");
727+});
728+const client = new GatewayBrowserClient({
729+url: "ws://127.0.0.1:18789",
730+token: "shared-auth-token",
731+ onClose,
732+});
733+734+try {
735+const { ws } = await startConnect(client);
736+737+expect(() => ws.emitClose(1006, "socket lost")).not.toThrow();
738+await vi.advanceTimersByTimeAsync(800);
739+740+expect(onClose).toHaveBeenCalledWith({
741+code: 1006,
742+reason: "socket lost",
743+error: undefined,
744+});
745+expect(consoleError).toHaveBeenCalledWith(
746+"[gateway] close handler error:",
747+expect.any(Error),
748+);
749+expect(wsInstances).toHaveLength(2);
750+} finally {
751+client.stop();
752+consoleError.mockRestore();
753+vi.useRealTimers();
754+}
755+});
756+757+it("keeps gap callback errors from blocking event delivery", () => {
758+const consoleError = vi.spyOn(console, "error").mockImplementation(() => undefined);
759+const onGap = vi.fn(() => {
760+throw new Error("gap callback failed");
761+});
762+const onEvent = vi.fn();
763+const listener = vi.fn();
764+const client = new GatewayBrowserClient({
765+url: "ws://127.0.0.1:18789",
766+token: "shared-auth-token",
767+ onGap,
768+ onEvent,
769+});
770+771+try {
772+client.addEventListener(listener);
773+client.start();
774+const ws = getLatestWebSocket();
775+776+ws.emitMessage({ type: "event", event: "session.updated", seq: 1 });
777+onEvent.mockClear();
778+listener.mockClear();
779+780+expect(() =>
781+ws.emitMessage({ type: "event", event: "session.updated", seq: 3 }),
782+).not.toThrow();
783+784+expect(onGap).toHaveBeenCalledWith({ expected: 2, received: 3 });
785+expect(onEvent).toHaveBeenCalledWith(
786+expect.objectContaining({ event: "session.updated", seq: 3 }),
787+);
788+expect(listener).toHaveBeenCalledWith(
789+expect.objectContaining({ event: "session.updated", seq: 3 }),
790+);
791+expect(consoleError).toHaveBeenCalledWith("[gateway] gap handler error:", expect.any(Error));
792+793+onGap.mockClear();
794+ws.emitMessage({ type: "event", event: "session.updated", seq: 4 });
795+expect(onGap).not.toHaveBeenCalled();
796+} finally {
797+client.stop();
798+consoleError.mockRestore();
799+}
800+});
801+802+it("keeps event callback errors from blocking event listeners", () => {
803+const consoleError = vi.spyOn(console, "error").mockImplementation(() => undefined);
804+const onEvent = vi.fn(() => {
805+throw new Error("event callback failed");
806+});
807+const listener = vi.fn();
808+const client = new GatewayBrowserClient({
809+url: "ws://127.0.0.1:18789",
810+token: "shared-auth-token",
811+ onEvent,
812+});
813+814+try {
815+client.addEventListener(listener);
816+client.start();
817+const ws = getLatestWebSocket();
818+819+expect(() =>
820+ws.emitMessage({ type: "event", event: "session.updated", seq: 1 }),
821+).not.toThrow();
822+823+expect(onEvent).toHaveBeenCalledWith(
824+expect.objectContaining({ event: "session.updated", seq: 1 }),
825+);
826+expect(listener).toHaveBeenCalledWith(
827+expect.objectContaining({ event: "session.updated", seq: 1 }),
828+);
829+expect(consoleError).toHaveBeenCalledWith(
830+"[gateway] event handler error:",
831+expect.any(Error),
832+);
833+} finally {
834+client.stop();
835+consoleError.mockRestore();
836+}
837+});
838+685839it("prefers explicit shared auth over cached device tokens", async () => {
686840const client = new GatewayBrowserClient({
687841url: "ws://127.0.0.1:18789",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。