From 97ad9405691f62807ce3fe381d8624794c3fc1f6 Mon Sep 17 00:00:00 2001 From: Tomer Shlasky Date: Sun, 22 Feb 2026 17:07:49 +0200 Subject: [PATCH] fix(realtime): prevent telemetry 401s on reconnection --- .../sdk/src/realtime/telemetry-reporter.ts | 5 ++-- packages/sdk/tests/unit.test.ts | 25 ++++++++----------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/packages/sdk/src/realtime/telemetry-reporter.ts b/packages/sdk/src/realtime/telemetry-reporter.ts index a2a24bd..29a8d30 100644 --- a/packages/sdk/src/realtime/telemetry-reporter.ts +++ b/packages/sdk/src/realtime/telemetry-reporter.ts @@ -97,13 +97,14 @@ export class TelemetryReporter implements ITelemetryReporter { this.sendReport(false); } - /** Stop the reporter and send a final report with keepalive. */ + /** Stop the reporter and discard any buffered data. */ stop(): void { if (this.intervalId !== null) { clearInterval(this.intervalId); this.intervalId = null; } - this.sendReport(true); + this.statsBuffer = []; + this.diagnosticsBuffer = []; } /** diff --git a/packages/sdk/tests/unit.test.ts b/packages/sdk/tests/unit.test.ts index c1cbde6..9eaae8e 100644 --- a/packages/sdk/tests/unit.test.ts +++ b/packages/sdk/tests/unit.test.ts @@ -1522,7 +1522,7 @@ describe("Subscribe Client", () => { } }); - it("buffers pre-session telemetry diagnostics and flushes them after session_id", async () => { + it("buffers pre-session telemetry diagnostics and discards them on disconnect", async () => { const { createRealTimeClient } = await import("../src/realtime/client.js"); const { WebRTCManager } = await import("../src/realtime/webrtc-manager.js"); @@ -1587,17 +1587,11 @@ describe("Subscribe Client", () => { }); } + // disconnect() calls stop() which discards buffered data instead of sending it. + // This avoids 401s when the API key has been invalidated on reconnect. client.disconnect(); - await vi.waitFor(() => { - expect(fetchMock).toHaveBeenCalledTimes(1); - }); - - const [, options] = fetchMock.mock.calls[0]; - const body = JSON.parse(options.body); - expect(body.diagnostics).toHaveLength(1); - expect(body.diagnostics[0].name).toBe("phaseTiming"); - expect(body.diagnostics[0].data.phase).toBe("websocket"); + expect(fetchMock).not.toHaveBeenCalled(); } finally { connectSpy.mockRestore(); stateSpy.mockRestore(); @@ -2255,7 +2249,7 @@ describe("TelemetryReporter", () => { } }); - it("stop sends final report with keepalive", async () => { + it("stop discards buffered data without sending a request", async () => { const { TelemetryReporter } = await import("../src/realtime/telemetry-reporter.js"); const fetchMock = vi.fn().mockResolvedValue({ ok: true }); @@ -2279,9 +2273,12 @@ describe("TelemetryReporter", () => { reporter.stop(); - expect(fetchMock).toHaveBeenCalledTimes(1); - const [, options] = fetchMock.mock.calls[0]; - expect(options.keepalive).toBe(true); + // stop() should NOT send any network request + expect(fetchMock).not.toHaveBeenCalled(); + + // flush() after stop() should be a no-op (buffers were cleared) + reporter.flush(); + expect(fetchMock).not.toHaveBeenCalled(); } finally { vi.unstubAllGlobals(); }