From e3dafb558eda180a45823ba0014781a447904c87 Mon Sep 17 00:00:00 2001 From: Yostra Date: Tue, 3 Mar 2026 23:46:40 +0100 Subject: [PATCH] stable --- .../src/tests/unit/websocket-client.test.ts | 90 +------------------ packages/sync-engine/src/websocket-client.ts | 33 +------ 2 files changed, 4 insertions(+), 119 deletions(-) diff --git a/packages/sync-engine/src/tests/unit/websocket-client.test.ts b/packages/sync-engine/src/tests/unit/websocket-client.test.ts index ed3e6a38..d8809fb7 100644 --- a/packages/sync-engine/src/tests/unit/websocket-client.test.ts +++ b/packages/sync-engine/src/tests/unit/websocket-client.test.ts @@ -270,19 +270,6 @@ describe('websocket-client', () => { }) it('should detect stale connection when no pong received within PONG_WAIT', async () => { - // Use a longer reconnect delay so stale detection can occur before proactive reconnect - vi.stubGlobal( - 'fetch', - vi.fn().mockResolvedValue({ - ok: true, - json: () => - Promise.resolve({ - ...mockSessionResponse, - reconnect_delay: 60, // 60s reconnect delay - }), - }) - ) - const { createStripeWebSocketClient } = await import('../../websocket-client') const onError = vi.fn() @@ -310,82 +297,7 @@ describe('websocket-client', () => { }) }) - describe('Proactive reconnection', () => { - it('should use server-provided reconnect_delay for reconnect interval', async () => { - const { createStripeWebSocketClient } = await import('../../websocket-client') - - const onReady = vi.fn() - await createStripeWebSocketClient({ - stripeApiKey: 'sk_test_123', - onEvent: vi.fn(), - onReady, - }) - - // Wait for runLoop to create WebSocket - await vi.advanceTimersByTimeAsync(0) - - const wsInstance = wsInstances[0] - wsInstance._triggerOpen() - wsInstance._triggerPong() // Keep connection alive - - expect(onReady).toHaveBeenCalledTimes(1) - - // Advance to just before reconnect interval (5s from session) - await vi.advanceTimersByTimeAsync(4900) - wsInstance._triggerPong() - - // Should not have reconnected yet - expect(wsInstances.length).toBe(1) - - // Advance past reconnect interval - await vi.advanceTimersByTimeAsync(200) - - // Should have created a new WebSocket for reconnection - expect(wsInstances.length).toBe(2) - }) - - it('should use default 60s reconnect interval when server does not provide one', async () => { - vi.stubGlobal( - 'fetch', - vi.fn().mockResolvedValue({ - ok: true, - json: () => - Promise.resolve({ - ...mockSessionResponse, - reconnect_delay: 0, // No server-provided delay - }), - }) - ) - - const { createStripeWebSocketClient } = await import('../../websocket-client') - - await createStripeWebSocketClient({ - stripeApiKey: 'sk_test_123', - onEvent: vi.fn(), - }) - - // Wait for runLoop to create WebSocket - await vi.advanceTimersByTimeAsync(0) - - const wsInstance = wsInstances[0] - wsInstance._triggerOpen() - - // Keep connection alive with pongs - for (let i = 0; i < 6; i++) { - await vi.advanceTimersByTimeAsync(9000) - wsInstance._triggerPong() - } - - // Should not have reconnected yet (only 54s passed) - expect(wsInstances.length).toBe(1) - - // Advance to trigger 60s reconnect - await vi.advanceTimersByTimeAsync(7000) - - // Should have created a new WebSocket - expect(wsInstances.length).toBe(2) - }) - + describe('Reconnection', () => { it('should reconnect immediately on unexpected disconnect', async () => { const { createStripeWebSocketClient } = await import('../../websocket-client') diff --git a/packages/sync-engine/src/websocket-client.ts b/packages/sync-engine/src/websocket-client.ts index f81718c4..bef94498 100644 --- a/packages/sync-engine/src/websocket-client.ts +++ b/packages/sync-engine/src/websocket-client.ts @@ -6,7 +6,6 @@ const CLI_VERSION = '1.33.0' const PONG_WAIT = 10 * 1000 // 10 seconds - max time to wait for pong const PING_PERIOD = (PONG_WAIT * 2) / 10 // 2 seconds - send ping before pong timeout const CONNECT_ATTEMPT_WAIT = 10 * 1000 // 10 seconds - retry interval on connection failure -const DEFAULT_RECONNECT_INTERVAL = 60 * 1000 // 60 seconds - proactive reconnect interval export interface WebhookProcessingResult { status: number @@ -117,14 +116,8 @@ export async function createStripeWebSocketClient( // Create session const session = await createCliSession(stripeApiKey) - // Server-controlled reconnect interval (default 60s) - const reconnectInterval = session.reconnect_delay - ? session.reconnect_delay * 1000 - : DEFAULT_RECONNECT_INTERVAL - let ws: WebSocket | null = null let pingInterval: NodeJS.Timeout | null = null - let reconnectTimer: NodeJS.Timeout | null = null let connected = false let shouldRun = true let lastPongReceived: number = Date.now() @@ -138,10 +131,6 @@ export async function createStripeWebSocketClient( clearInterval(pingInterval) pingInterval = null } - if (reconnectTimer) { - clearTimeout(reconnectTimer) - reconnectTimer = null - } if (ws) { ws.removeAllListeners() if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) { @@ -331,30 +320,14 @@ export async function createStripeWebSocketClient( if (!shouldRun) break - // 2. Connection established - wait for one of these events: - // - stop() called - // - unexpected disconnect (notifyClose) - // - proactive reconnect timer fires + // 2. Connection established - wait for disconnect or stop(). + // Ping/pong heartbeat detects stale connections and terminates them, + // which triggers the close handler and unblocks this promise. await new Promise((resolve) => { - // Set up notifyClose signal notifyCloseResolve = resolve - - // Set up stop signal stopResolve = resolve - - // Set up proactive reconnect timer - reconnectTimer = setTimeout(() => { - // Proactive reconnection to prevent stale connections - cleanupConnection() - resolve() - }, reconnectInterval) }) - // Clean up before next iteration or exit - if (reconnectTimer) { - clearTimeout(reconnectTimer) - reconnectTimer = null - } notifyCloseResolve = null stopResolve = null }