aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-06-27 13:43:39 -0700
committerAdam Soltys <[email protected]>2026-06-27 13:43:39 -0700
commit5de1b09a26745f3ba1ba54e3e666266c2620352c (patch)
tree923704cd795898fe1fd29f35ba9911e5196233cf /src
parent90fe7a2ca754d39bc363417180b0b8b267972adb (diff)
downloadcoinosv3-5de1b09a26745f3ba1ba54e3e666266c2620352c.tar.gz
coinosv3-5de1b09a26745f3ba1ba54e3e666266c2620352c.zip
Harden Electrum WS recovery: fail in-flight calls on drop, faster half-open detect
After a socket drop the in-flight _rpcCall requests were never rejected (only stopRealtime did that), so dead calls held every _rpcPump slot until they timed out — stalling the post-reconnect refresh by ~10s even though the new socket and its pushes were already live. Now reject pending calls in ws.onclose and in _reconnectNow (which nulls onclose), so the pump frees instantly and the on-open refreshLive runs right away. Also tighten the half-open watchdog: ping every 10s and recycle the socket after 21s of silence (two unanswered pings) instead of 45s, so a silently-dead connection — no onclose, no online/visibility event — resumes pushing in ~20s instead of ~45s. Verified via CDP: force-closing the live socket reconnects in ~0.6s and the next deposit pushes in ~0.4s (UI ~1.2s). Co-Authored-By: Claude Opus 4.8 <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/wallet.js16
1 files changed, 12 insertions, 4 deletions
diff --git a/src/wallet.js b/src/wallet.js
index 5882ee2..7236a9f 100644
--- a/src/wallet.js
+++ b/src/wallet.js
@@ -1481,16 +1481,18 @@ export class Wallet {
if (typeof WebSocket === 'undefined' || !this.wsUrl()) return;
this._wsWant = true;
- // Heartbeat: ping every 15s; if no message (incl. pong) for 45s the socket
- // is half-open (died without firing onclose) — force a reconnect.
+ // Heartbeat: ping every 10s; if nothing comes back (incl. our own pong) for
+ // 21s the socket is half-open (died without firing onclose) — recycle it.
+ // The trigger is two unanswered pings, so one slow round-trip won't churn,
+ // but a silently-dead socket now recovers in ~20s instead of ~45s.
this._hbTimer = setInterval(() => {
if (this.offline || !this._ws || this._ws.readyState !== 1) return;
- if (Date.now() - this._lastMsg > 45000) {
+ if (Date.now() - this._lastMsg > 21000) {
this._reconnectNow();
} else {
this._rpcSend('server.ping', []);
}
- }, 15000);
+ }, 10000);
// Reconnect + refresh when the tab refocuses or the network returns.
if (!this._wakeHooked && typeof document !== 'undefined') {
@@ -1518,6 +1520,8 @@ export class Wallet {
} catch {}
this._ws = null;
}
+ // onclose was nulled above so it won't fire — fail in-flight calls here too.
+ this._rejectPending('socket recycled');
this.live = false;
this._connectWs();
}
@@ -1581,6 +1585,10 @@ export class Wallet {
ws.onclose = () => {
this.live = false;
this._ws = null;
+ // Fail any in-flight data calls so the _rpcCall pump frees its slots
+ // immediately; otherwise dead calls hold the pump until they time out and
+ // the post-reconnect refresh stalls behind them (~10s of stale UI).
+ this._rejectPending('socket closed');
this.emit();
this._scheduleReconnect();
};