Aegis 0.32.3: WizardConnect waits between relay reconnects

When the relays were unreachable (after a sleep, or a relay offline) the
relay client reconnected with no delay at all, re-signing the queued
wallet_ready on every lap. On Theseus's main thread that spun a core for an
hour, grew the process to 2.5 GB and froze the browser. connect() now waits
2 s, 4 s ... up to 60 s between tries, and starts over once a connection
has held for a minute.
This commit is contained in:
Local Dev 2026-10-07 19:17:25 +02:00
parent a33854a926
commit eac7020820
2 changed files with 40 additions and 1 deletions

View file

@ -1,7 +1,7 @@
{
"id": "aegis",
"name": "Aegis Wallet",
"version": "0.32.2",
"version": "0.32.3",
"category": "plugin",
"description": "Multi-chain wallet (BCH, BTC, TRX, ETH, SOL, SC, DGB) derived from your Theseus vault. Dapps get window.bitcoincash and window.wizardconnect on any site; window.tronWeb / window.tronLink / window.ethereum / window.solana too. Every call needs your approval.",
"author": "Silent Mode",

View file

@ -23,7 +23,44 @@ const WALLET_ICON = "data:image/svg+xml;utf8," + encodeURIComponent(
</svg>`
);
// Reconnect backoff for WizardConnect relays.
//
// RelayClient.connect() resolves as soon as the subscription is created, before
// any socket opens. When every relay is unreachable (DNS down after a sleep, a
// relay offline) the pool closes the subscription at once, the connection
// manager hears "disconnected" and calls connect() again with no delay — its
// reconnectInterval only applies when connect() throws, which it never does.
// Every lap also re-wraps the queued wallet_ready (two Schnorr signatures and
// two ECDH per relay), all on Theseus's main thread: on 2026-10-07 that spun
// one core flat out for an hour, grew the process to 2.5 GB and froze the
// browser. Delaying connect() itself throttles the loop, whoever calls it: the
// first try is immediate, then 2 s, 4 s ... up to 60 s; a connection that has
// held for a minute starts the count over.
const RECONNECT_FIRST_MS = 2000;
const RECONNECT_MAX_MS = 60_000;
const RECONNECT_HEALTHY_MS = 60_000;
function throttleRelayReconnects(wcCore, { now = Date.now, sleep = (ms) => new Promise((r) => setTimeout(r, ms)) } = {}) {
const proto = wcCore?.RelayClient?.prototype;
if (!proto || typeof proto.connect !== "function" || proto.__aegisThrottled) return false;
const connect = proto.connect;
proto.connect = async function throttledConnect(...args) {
const st = this.__aegisReconnect || (this.__aegisReconnect = { last: 0, n: 0 });
if (now() - st.last > RECONNECT_HEALTHY_MS) st.n = 0;
if (st.n > 0) {
const gap = Math.min(RECONNECT_MAX_MS, RECONNECT_FIRST_MS * 2 ** (st.n - 1));
const wait = st.last + gap - now();
if (wait > 0) await sleep(wait);
}
st.n++;
st.last = now();
return connect.apply(this, args);
};
proto.__aegisThrottled = true;
return true;
}
module.exports = function makeWc({ HDKey, secp256k1, sha256, hkdf, WalletConnectionManager, wcCore, libauth, log = () => {}, api, approvalRequest }) {
throttleRelayReconnects(wcCore);
// ---- WalletAdapter --------------------------------------------------------
//
@ -265,3 +302,5 @@ module.exports = function makeWc({ HDKey, secp256k1, sha256, hkdf, WalletConnect
return { startForWallet, stopForWallet, connectUri, disconnect, disconnectOrigin, snapshot, onStateChange };
};
module.exports.throttleRelayReconnects = throttleRelayReconnects;