summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/transport.js40
1 files changed, 39 insertions, 1 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/transport.js b/packages/meshbay-hub/src/meshbay_hub/static/transport.js
index 4eb7ac2..35f729e 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/transport.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/transport.js
@@ -237,6 +237,27 @@ class MeshBayTransport {
this._inReconnectAttempt = false;
this._onReconnected = null;
this._onNeedToken = null;
+ // Cuts the backoff wait short the moment the page is foregrounded again —
+ // found live to matter: a screen lock throttles the tab's own timers
+ // along with everything else, so a backoff already counting down when the
+ // phone locked can run for minutes of *wall clock* past its nominal delay
+ // before it next gets to run at all. Set once, here, rather than inside
+ // connect() like the diagnostic listener above it — this one has to
+ // survive every reconnect attempt, not restart with each one.
+ this._reconnectWakeResolve = null;
+ this._onVisibilityWake = () => {
+ if (document.visibilityState === 'visible') this._wakeReconnect();
+ };
+ document.addEventListener('visibilitychange', this._onVisibilityWake);
+ }
+
+ /** Cuts short a reconnect currently backing off (see _reconnectLoop). A
+ * no-op when nothing is waiting, so this is safe to call unconditionally. */
+ _wakeReconnect() {
+ if (this._reconnectWakeResolve) {
+ this._reconnectWakeResolve();
+ this._reconnectWakeResolve = null;
+ }
}
get connected() { return this._connected; }
@@ -367,6 +388,14 @@ class MeshBayTransport {
// a trace captures exactly what state the connection was in right as the
// page comes back from being backgrounded/locked — never active unless
// trace mode is on (see TRACE_KEY above).
+ //
+ // connect() runs again on every reconnect attempt (see _reconnectLoop),
+ // and each run used to add its own listener/interval on top of the
+ // previous one without ever removing it — confirmed live: 8 failed
+ // attempts during one screen lock left 8 duplicate `visibility` trace
+ // lines firing off the same real event. Disposing of the prior instance
+ // first is what keeps this to one.
+ if (this._diagCleanup) { this._diagCleanup(); this._diagCleanup = null; }
if (traceEnabled()) {
const healthPing = async (reason) => {
const before = {
@@ -673,7 +702,14 @@ class MeshBayTransport {
this._reconnectAttempts += 1;
const delayMs = Math.min(30000, 1000 * 2 ** (this._reconnectAttempts - 1));
trace('reconnect_wait', { attempt: this._reconnectAttempts, delay_ms: delayMs });
- await new Promise((r) => setTimeout(r, delayMs));
+ // Interruptible: _wakeReconnect (fired on visibilitychange → visible)
+ // resolves this immediately instead of waiting out the rest of a
+ // backoff that was mostly spent while nothing could succeed anyway.
+ await new Promise((resolve) => {
+ const timer = setTimeout(resolve, delayMs);
+ this._reconnectWakeResolve = () => { clearTimeout(timer); resolve(); };
+ });
+ this._reconnectWakeResolve = null;
if (this._closed) return;
try {
// Best-effort: these are already unusable, but leaving them wired up
@@ -1757,6 +1793,8 @@ class MeshBayTransport {
// only skips reconnecting because of this flag, not because "closed" is
// absent from its own trigger condition.
this._closed = true;
+ document.removeEventListener('visibilitychange', this._onVisibilityWake);
+ this._wakeReconnect();
if (this._diagCleanup) { this._diagCleanup(); this._diagCleanup = null; }
if (this._channel) this._channel.close();
if (this._pc) this._pc.close();