




























@@ -76,6 +76,7 @@ class MockWebSocket {
7676autoCloseOnClose = true;
7777readyState = MockWebSocket.CONNECTING;
7878readonly options: unknown;
79+_socket?: { getPeerCertificate: () => { fingerprint256?: string } };
79808081constructor(_url: string, options?: unknown) {
8182this.options = options;
@@ -85,6 +86,13 @@ class MockWebSocket {
8586}
8687}
878889+setPeerFingerprint(fingerprint256: string): void {
90+Object.defineProperty(this, "_socket", {
91+configurable: true,
92+value: { getPeerCertificate: () => ({ fingerprint256 }) },
93+});
94+}
95+8896on(event: "open", handler: WsEventHandlers["open"]): void;
8997on(event: "message", handler: WsEventHandlers["message"]): void;
9098on(event: "close", handler: WsEventHandlers["close"]): void;
@@ -715,6 +723,12 @@ describe("GatewayClient close handling", () => {
715723expect(onClose).toHaveBeenCalledWith(
7167241008,
717725"unauthorized: DEVICE token mismatch (rotate/reissue device token)",
726+{
727+phase: "pre-hello",
728+socketOpened: false,
729+transportValidated: false,
730+transientPreHelloCleanClose: false,
731+},
718732);
719733client.stop();
720734});
@@ -732,7 +746,12 @@ describe("GatewayClient close handling", () => {
732746expect(logDebugMock).toHaveBeenCalledWith(
733747expect.stringContaining("failed clearing stale device-auth token"),
734748);
735-expect(onClose).toHaveBeenCalledWith(1008, "unauthorized: device token mismatch");
749+expect(onClose).toHaveBeenCalledWith(1008, "unauthorized: device token mismatch", {
750+phase: "pre-hello",
751+socketOpened: false,
752+transportValidated: false,
753+transientPreHelloCleanClose: false,
754+});
736755client.stop();
737756});
738757@@ -744,7 +763,39 @@ describe("GatewayClient close handling", () => {
744763getLatestWs().emitClose(1008, "unauthorized: signature invalid");
745764746765expect(clearDeviceAuthTokenMock).not.toHaveBeenCalled();
747-expect(onClose).toHaveBeenCalledWith(1008, "unauthorized: signature invalid");
766+expect(onClose).toHaveBeenCalledWith(1008, "unauthorized: signature invalid", {
767+phase: "pre-hello",
768+socketOpened: false,
769+transportValidated: false,
770+transientPreHelloCleanClose: false,
771+});
772+client.stop();
773+});
774+775+it("marks an opened TLS transport unvalidated when fingerprint verification fails", () => {
776+const onClose = vi.fn();
777+const onConnectError = vi.fn();
778+const client = new GatewayClient({
779+url: "wss://127.0.0.1:18789",
780+tlsFingerprint: "expected",
781+ onClose,
782+ onConnectError,
783+});
784+785+client.start();
786+const ws = getLatestWs();
787+ws.setPeerFingerprint("different");
788+ws.emitOpen();
789+790+expect(onConnectError).toHaveBeenCalledWith(
791+expect.objectContaining({ message: "gateway tls fingerprint mismatch" }),
792+);
793+expect(onClose).toHaveBeenCalledWith(1008, "gateway tls fingerprint mismatch", {
794+phase: "pre-hello",
795+socketOpened: true,
796+transportValidated: false,
797+transientPreHelloCleanClose: false,
798+});
748799client.stop();
749800});
750801@@ -757,7 +808,12 @@ describe("GatewayClient close handling", () => {
757808client.start();
758809expect(() => getLatestWs().emitClose(1008, "unauthorized: signature invalid")).not.toThrow();
759810760-expect(onClose).toHaveBeenCalledWith(1008, "unauthorized: signature invalid");
811+expect(onClose).toHaveBeenCalledWith(1008, "unauthorized: signature invalid", {
812+phase: "pre-hello",
813+socketOpened: false,
814+transportValidated: false,
815+transientPreHelloCleanClose: false,
816+});
761817expect(logDebugMock).toHaveBeenCalledWith(
762818"gateway client close handler error: Error: close callback failed",
763819);
@@ -822,6 +878,8 @@ describe("GatewayClient close handling", () => {
822878);
823879expect(onClose).toHaveBeenCalledWith(1000, "", {
824880phase: "pre-hello",
881+socketOpened: true,
882+transportValidated: true,
825883transientPreHelloCleanClose: true,
826884});
827885@@ -906,9 +964,16 @@ describe("GatewayClient close handling", () => {
906964907965expect(onClose).toHaveBeenNthCalledWith(1, 1000, "", {
908966phase: "pre-hello",
967+socketOpened: true,
968+transportValidated: true,
969+transientPreHelloCleanClose: true,
970+});
971+expect(onClose).toHaveBeenNthCalledWith(2, 1000, "", {
972+phase: "pre-hello",
973+socketOpened: true,
974+transportValidated: true,
909975transientPreHelloCleanClose: true,
910976});
911-expect(onClose).toHaveBeenNthCalledWith(2, 1000, "");
912977expect(onConnectError).toHaveBeenCalledOnce();
913978expect(onConnectError.mock.calls[0]?.[0]).toMatchObject({
914979message: "gateway closed (1000): ",
@@ -1035,7 +1100,12 @@ describe("GatewayClient close handling", () => {
10351100getLatestWs().emitClose(1008, "unauthorized: device token mismatch");
1036110110371102expect(clearDeviceAuthTokenMock).not.toHaveBeenCalled();
1038-expect(onClose).toHaveBeenCalledWith(1008, "unauthorized: device token mismatch");
1103+expect(onClose).toHaveBeenCalledWith(1008, "unauthorized: device token mismatch", {
1104+phase: "pre-hello",
1105+socketOpened: false,
1106+transportValidated: false,
1107+transientPreHelloCleanClose: false,
1108+});
10391109client.stop();
10401110});
10411111});
@@ -1801,7 +1871,12 @@ describe("GatewayClient connect auth payload", () => {
18011871expect(logDebugMock).toHaveBeenCalledWith(
18021872"gateway client reconnect paused handler error: Error: paused callback failed",
18031873);
1804-expect(onClose).toHaveBeenCalledWith(1008, "connect failed");
1874+expect(onClose).toHaveBeenCalledWith(1008, "connect failed", {
1875+phase: "pre-hello",
1876+socketOpened: true,
1877+transportValidated: true,
1878+transientPreHelloCleanClose: false,
1879+});
18051880});
1806188118071882it("does not auto-reconnect on CLIENT_VERSION_MISMATCH connect failures", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。