From fa34af0f53fb984114793fdbc4f612d42b593f7b Mon Sep 17 00:00:00 2001 From: abose Date: Fri, 9 Jan 2026 17:06:31 +0530 Subject: [PATCH 1/4] fix: unhandled exception on network issues --- src/utils/client.js | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/src/utils/client.js b/src/utils/client.js index 9f13d68..02283f5 100644 --- a/src/utils/client.js +++ b/src/utils/client.js @@ -91,8 +91,18 @@ function _setupClientAndWaitForClose(connectedCb) { __receiveMessage(data); }); + let terminated = false; function _connectionTerminated(reason) { + if (terminated) { + return; + } + terminated = true; + console.log(reason); + // not set bufferRequests = true for unexpected failures. + // Real connection failures → reject immediately, don't buffer + // Hibernation → buffer and reconnect transparently + client.connectionEstablished = false; for (let [sequenceNumber, handler] of ID_TO_RESOLVE_REJECT_MAP) { handler.reject(reason); @@ -100,11 +110,21 @@ function _setupClientAndWaitForClose(connectedCb) { } resolve(); } - client.on('close', function () { - // https://websockets.spec.whatwg.org/#eventdef-websocket-error - // https://stackoverflow.com/questions/40084398/is-onclose-always-called-after-onerror-for-websocket - // we do not need to listen for error event as an error event is immediately followed by a close event. - _connectionTerminated('connection closed'); + + // ✅ THIS prevents the "Unhandled 'error' event" crash + client.on('error', function (err) { + // ECONNREFUSED, ENOTFOUND, TLS errors, etc. can land here + _connectionTerminated(err); + }); + + client.on('close', function (code, reasonBuf) { + // reasonBuf may be a Buffer in ws; keep it simple + _connectionTerminated(`connection closed (${code})`); + }); + + // Optional but useful: HTTP-level failures (proxies, 401, 403, 502...) + client.on('unexpected-response', function (req, res) { + _connectionTerminated(`unexpected-response: ${res.statusCode}`); }); }); } From ea04b8764054a2e412ee463d8b3338c966e12ef1 Mon Sep 17 00:00:00 2001 From: abose Date: Fri, 9 Jan 2026 17:28:41 +0530 Subject: [PATCH 2/4] fix: we were rejecting after resolve. this never caused problem as we never cancel the hybernation loop --- src/utils/client.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/utils/client.js b/src/utils/client.js index 02283f5..e504054 100644 --- a/src/utils/client.js +++ b/src/utils/client.js @@ -111,7 +111,6 @@ function _setupClientAndWaitForClose(connectedCb) { resolve(); } - // ✅ THIS prevents the "Unhandled 'error' event" crash client.on('error', function (err) { // ECONNREFUSED, ENOTFOUND, TLS errors, etc. can land here _connectionTerminated(err); @@ -151,13 +150,14 @@ function _cancelBackoffTimer() { } } -async function _setupAndMaintainConnection(firstConnectionCb, neverConnectedCB) { +async function _setupAndMaintainConnection(resolveOnFirstConnect, reject) { backoffTimer = null; function connected() { _resetBackoffTime(); - if(firstConnectionCb){ - firstConnectionCb("connected"); - firstConnectionCb = null; + if(resolveOnFirstConnect){ + resolveOnFirstConnect("connected"); + resolveOnFirstConnect = null; + reject = null; // setup hibernate timer on first connection activityInHibernateInterval = 1; hibernateTimer = setInterval(_checkActivityForHibernation, INACTIVITY_TIME_FOR_HIBERNATE); @@ -180,8 +180,8 @@ async function _setupAndMaintainConnection(firstConnectionCb, neverConnectedCB) client && client.userClosedConnectionCB && client.userClosedConnectionCB(); client = cocoDBEndPointURL = cocoAuthKey = null; id = 0; - if(neverConnectedCB){ - neverConnectedCB(new Error("user Cancelled")); + if(reject){ + reject(new Error("user Cancelled")); } } From 0eb27eef1921c4d1656bda5bc597a43bb86c6f9a Mon Sep 17 00:00:00 2001 From: abose Date: Fri, 9 Jan 2026 17:37:22 +0530 Subject: [PATCH 3/4] fix: better fences --- src/utils/client.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils/client.js b/src/utils/client.js index e504054..72968b6 100644 --- a/src/utils/client.js +++ b/src/utils/client.js @@ -239,7 +239,7 @@ export function close() { currentClient.userClosedConnection = true; currentClient.userClosedConnectionCB = function () { for(let entry of pendingSendMessages){ - entry.reject(); + entry.reject(new Error('Connection closed')); } pendingSendMessages = []; resolve(); @@ -259,7 +259,7 @@ export function close() { * @returns {string} A function that increments the id variable and returns the new value as a hexadecimal string. */ function getId() { - id++; + id++; // Will take about 300 years to run out at 1 million sustained tps to db with Number.MAX_SAFE_INTEGER return id.toString(16); } @@ -304,7 +304,7 @@ function _sendPendingMessages() { */ export function sendMessage(message) { // make a copy as the user may start modifying the object while we are sending it. - message = structuredClone(message); + message = JSON.parse(JSON.stringify(message)); // faster than structured clone for most cases return new Promise(function (resolve, reject) { if(bufferRequests){ if(pendingSendMessages.length > MAX_PENDING_SEND_BUFFER_SIZE){ From 456b64023a89e1e4df34d1c3e093d08c766ddac1 Mon Sep 17 00:00:00 2001 From: abose Date: Fri, 9 Jan 2026 17:53:04 +0530 Subject: [PATCH 4/4] fix: edge case --- src/utils/client.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/client.js b/src/utils/client.js index 72968b6..3c8783b 100644 --- a/src/utils/client.js +++ b/src/utils/client.js @@ -34,6 +34,7 @@ function _checkActivityForHibernation() { return; } if(!client || client.hibernating + || client.userClosedConnection || !client.connectionEstablished // cant hibernate if connection isnt already established/ is being establised || ID_TO_RESOLVE_REJECT_MAP.size > 0){ // if there are any pending responses, we cant hibernate return;