From c1b3b810bd400d991814b74e30bab726f04e1938 Mon Sep 17 00:00:00 2001 From: DioChuks Date: Wed, 29 Jul 2026 23:27:08 +0100 Subject: [PATCH 1/3] chore: added websocket pkg --- .../api-clients/websocket-client/index.js | 39 +++++++++++++------ .../api-clients/websocket-client/package.json | 2 +- pnpm-lock.yaml | 6 +++ 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/examples/api-clients/websocket-client/index.js b/examples/api-clients/websocket-client/index.js index 6bf84b3f..39bc05c2 100644 --- a/examples/api-clients/websocket-client/index.js +++ b/examples/api-clients/websocket-client/index.js @@ -24,7 +24,11 @@ let nodeCount = 0; let startTime = null; let stopped = false; // set once the trace completes/errors so we stop reconnecting let reconnectAttempts = 0; +let isReconnecting = false; + const MAX_RECONNECT_DELAY = 5000; // cap exponential backoff at 5s +const MAX_RECONNECT_ATTEMPTS = 10; +const INITIAL_RECONNECT_DELAY = 1000; // Build the request payload, dynamically augmenting it with the resume cursor // once we have received at least one trace node. @@ -36,11 +40,12 @@ function buildRequest() { return request; } -function connect() { +function connectToTraceStream() { ws = new WebSocket(WS_URL); ws.on('open', () => { reconnectAttempts = 0; + isReconnecting = false; if (startTime === null) startTime = Date.now(); if (lastSeenNodeId === null) { @@ -123,28 +128,40 @@ function connect() { }); ws.on('error', (err) => { - console.error('WebSocket error:', err.message); - // Do not exit here: the 'close' handler below drives reconnection. + console.error(`WebSocket error: ${err.message || err}`); + reconnect(); }); ws.on('close', () => { - console.log('\nConnection closed'); if (stopped) { process.exit(0); } - scheduleReconnect(); + reconnect(); }); } -function scheduleReconnect() { +function reconnect() { + if (stopped || isReconnecting) return; + isReconnecting = true; + reconnectAttempts++; - // Exponential backoff (500ms, 1s, 2s, 4s, capped at MAX_RECONNECT_DELAY). + if (reconnectAttempts > MAX_RECONNECT_ATTEMPTS) { + console.error(`\n❌ Maximum reconnect attempts (${MAX_RECONNECT_ATTEMPTS}) reached. Exiting.`); + process.exit(1); + return; + } + const delay = Math.min( MAX_RECONNECT_DELAY, - 500 * 2 ** (reconnectAttempts - 1) + INITIAL_RECONNECT_DELAY * Math.pow(2, reconnectAttempts - 1) ); - console.log(`Reconnecting in ${delay}ms (attempt ${reconnectAttempts})...`); - setTimeout(connect, delay); + + console.log(`Connection lost. Reconnecting in ${delay / 1000}s...`); + + setTimeout(() => { + isReconnecting = false; + connectToTraceStream(); + }, delay); } process.on('SIGINT', () => { @@ -154,4 +171,4 @@ process.on('SIGINT', () => { process.exit(0); }); -connect(); +connectToTraceStream(); diff --git a/examples/api-clients/websocket-client/package.json b/examples/api-clients/websocket-client/package.json index 7f90a765..dba46049 100644 --- a/examples/api-clients/websocket-client/package.json +++ b/examples/api-clients/websocket-client/package.json @@ -7,6 +7,6 @@ "start": "node index.js" }, "dependencies": { - "ws": "^8.18.0" + "ws": "^8.19.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 813cbdbd..f2bfe948 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -119,6 +119,12 @@ importers: specifier: ^5.7.0 version: 5.9.3 + examples/api-clients/websocket-client: + dependencies: + ws: + specifier: ^8.19.0 + version: 8.19.0 + extensions/vscode: devDependencies: '@eslint/js': From 5c41799e09af81c705fe358190d7bfdb7136c92f Mon Sep 17 00:00:00 2001 From: DioChuks Date: Wed, 29 Jul 2026 23:47:25 +0100 Subject: [PATCH 2/3] feat: Ignore terminal events from superseded sockets --- .../api-clients/websocket-client/index.js | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/examples/api-clients/websocket-client/index.js b/examples/api-clients/websocket-client/index.js index 39bc05c2..2c49760c 100644 --- a/examples/api-clients/websocket-client/index.js +++ b/examples/api-clients/websocket-client/index.js @@ -41,9 +41,11 @@ function buildRequest() { } function connectToTraceStream() { - ws = new WebSocket(WS_URL); + const socket = new WebSocket(WS_URL); + ws = socket; - ws.on('open', () => { + socket.on('open', () => { + if (ws !== socket) return; reconnectAttempts = 0; isReconnecting = false; if (startTime === null) startTime = Date.now(); @@ -55,10 +57,11 @@ function connectToTraceStream() { } console.log(`Requesting trace for: ${TX_HASH}\n`); - ws.send(JSON.stringify(buildRequest())); + socket.send(JSON.stringify(buildRequest())); }); - ws.on('message', (data) => { + socket.on('message', (data) => { + if (ws !== socket) return; try { const message = JSON.parse(data.toString()); @@ -109,13 +112,13 @@ function connectToTraceStream() { console.log(` Server duration: ${message.duration_ms}ms`); console.log(` Client duration: ${duration}ms`); stopped = true; - ws.close(); + socket.close(); break; case 'trace_error': console.error('\n\n❌ Trace error:', message.error); stopped = true; - ws.close(); + socket.close(); process.exit(1); break; @@ -127,12 +130,14 @@ function connectToTraceStream() { } }); - ws.on('error', (err) => { + socket.on('error', (err) => { + if (ws !== socket) return; console.error(`WebSocket error: ${err.message || err}`); reconnect(); }); - ws.on('close', () => { + socket.on('close', () => { + if (ws !== socket) return; if (stopped) { process.exit(0); } @@ -159,8 +164,8 @@ function reconnect() { console.log(`Connection lost. Reconnecting in ${delay / 1000}s...`); setTimeout(() => { - isReconnecting = false; connectToTraceStream(); + isReconnecting = false; }, delay); } From f52284c6cad6d837d3d24345dce11951ae964aa5 Mon Sep 17 00:00:00 2001 From: DioChuks Date: Thu, 30 Jul 2026 00:01:06 +0100 Subject: [PATCH 3/3] feat bound each WebSocket connection attempt with a handshake timeout --- examples/api-clients/websocket-client/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/api-clients/websocket-client/index.js b/examples/api-clients/websocket-client/index.js index 2c49760c..4fbc658a 100644 --- a/examples/api-clients/websocket-client/index.js +++ b/examples/api-clients/websocket-client/index.js @@ -29,6 +29,7 @@ let isReconnecting = false; const MAX_RECONNECT_DELAY = 5000; // cap exponential backoff at 5s const MAX_RECONNECT_ATTEMPTS = 10; const INITIAL_RECONNECT_DELAY = 1000; +const HANDSHAKE_TIMEOUT = 5000; // Build the request payload, dynamically augmenting it with the resume cursor // once we have received at least one trace node. @@ -41,7 +42,7 @@ function buildRequest() { } function connectToTraceStream() { - const socket = new WebSocket(WS_URL); + const socket = new WebSocket(WS_URL, { handshakeTimeout: HANDSHAKE_TIMEOUT }); ws = socket; socket.on('open', () => {