diff --git a/src/venues/near-intents/solver-relay-ws.mjs b/src/venues/near-intents/solver-relay-ws.mjs index fe32444..380510e 100644 --- a/src/venues/near-intents/solver-relay-ws.mjs +++ b/src/venues/near-intents/solver-relay-ws.mjs @@ -188,6 +188,7 @@ export async function startSolverRelayWs({ activeSocket.addEventListener('error', (error) => { if (activeSocket !== socket) return; + if (closingSockets.has(activeSocket)) return; connected = false; lastDisconnectedAt = new Date().toISOString(); rejectAllPending(new Error('Socket error')); diff --git a/src/venues/near-intents/ws.mjs b/src/venues/near-intents/ws.mjs index a358ef9..9bb3538 100644 --- a/src/venues/near-intents/ws.mjs +++ b/src/venues/near-intents/ws.mjs @@ -158,6 +158,7 @@ export async function startNearIntentsWs({ ws.addEventListener('error', (err) => { if (activeSocket !== ws) return; + if (closingSockets.has(ws)) return; connected = false; lastDisconnectedAt = new Date().toISOString(); logger?.error('socket_error', { diff --git a/test/near-intents-ws.test.mjs b/test/near-intents-ws.test.mjs index d943b62..056e3ac 100644 --- a/test/near-intents-ws.test.mjs +++ b/test/near-intents-ws.test.mjs @@ -105,7 +105,13 @@ test('near intents ingest reconnects after websocket error before open without r test('near intents websocket close is reentrant-safe when close emits an error', async () => { const mock = installMockWebSocket(); + const errors = []; const producer = { sendJson: async () => {} }; + const logger = { + error: (event) => errors.push(event), + info: () => {}, + warn: () => {}, + }; const client = await startNearIntentsWs({ apiKey: 'api-key', wsUrl: 'wss://relay.example/ws', @@ -113,6 +119,7 @@ test('near intents websocket close is reentrant-safe when close emits an error', producer, rawTopic: 'raw.near_intents.quote', normalizedTopic: 'norm.swap_demand', + logger, reconnectDelayMs: 1, }); @@ -127,6 +134,7 @@ test('near intents websocket close is reentrant-safe when close emits an error', await delay(10); assert.equal(mock.instances[0].closeCalls, 1); + assert.equal(errors.filter((event) => event === 'socket_error').length, 1); assert.equal(client.getState().connected, false); assert.ok(client.getState().reconnect_count >= 2); assert.ok(mock.instances.length >= 2); diff --git a/test/solver-relay-ws.test.mjs b/test/solver-relay-ws.test.mjs index 2623802..050f284 100644 --- a/test/solver-relay-ws.test.mjs +++ b/test/solver-relay-ws.test.mjs @@ -120,9 +120,16 @@ test('solver relay reconnects after websocket error even when no close event is test('solver relay websocket close is reentrant-safe when close emits an error', async () => { const mock = installMockWebSocket(); + const errors = []; + const logger = { + error: (event) => errors.push(event), + info: () => {}, + warn: () => {}, + }; const client = await startSolverRelayWs({ apiKey: 'api-key', wsUrl: 'wss://relay.example/ws', + logger, reconnectDelayMs: 1, }); @@ -137,6 +144,7 @@ test('solver relay websocket close is reentrant-safe when close emits an error', await delay(10); assert.equal(mock.instances[0].closeCalls, 1); + assert.equal(errors.filter((event) => event === 'socket_error').length, 1); assert.equal(client.getState().connected, false); assert.ok(client.getState().reconnect_count >= 2); assert.ok(mock.instances.length >= 2);