diff options
Diffstat (limited to 'packages/meshbay-hub/src')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/search-page.js | 38 | ||||
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/transport.js | 26 |
2 files changed, 59 insertions, 5 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/search-page.js b/packages/meshbay-hub/src/meshbay_hub/static/search-page.js index 593760c..679b3f9 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/search-page.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/search-page.js @@ -27,7 +27,22 @@ const BATCH_SIZE = 3; // good. grenet (one shared group) never hit it; cbesson (many) always did. const MAX_POOL_SIZE = 12; const DEBOUNCE_MS = 200; -const SEARCH_TIMEOUT = 10000; +// How long a connection attempt may make **no progress**, not how long it may +// take. Search used a flat 10 s for the whole of `connect()`, which is the hub +// round trip, ICE gathering (capped at 4 s in transport.js), DTLS, the +// DataChannel opening and the handshake's own round trips. Opening the same +// group from the sidebar has no such deadline and gets the transport's own 30 s, +// so a link slow enough to need twelve seconds — a phone on 4G — failed here and +// worked from there, for the same work against the same node. +// +// A node that is not answering produces no progress event and still fails in +// `SEARCH_STALL_MS`, which is what keeps the fan-out bounded: `fetchAllIndexes` +// goes in batches of three and waits for the slowest of each, so the number that +// matters for a page full of unreachable groups is this one, unchanged. +// `SEARCH_MAX_MS` bounds the other case — a node that answers ICE and then stops +// — because a deadline that only ever resets has none. +const SEARCH_STALL_MS = 10000; +const SEARCH_MAX_MS = 30000; const SEARCH_VIDEO_ROOT = '__search__'; const SEARCH_AUDIO_ROOT = '__search__'; const SEARCH_PHOTO_ROOTS = ['__search_photos__']; @@ -55,20 +70,33 @@ async function connectToGroup(hubBase, groupId, token, bundleKey, username, user for (const n of nodesData.nodes) { const transport = new window.MeshBayTransport(hubBase, live); transport.onNeedToken = async () => (await ensureFreshToken()) || token; - let timer; + let stallTimer, capTimer; + const stopTimers = () => { + clearTimeout(stallTimer); + clearTimeout(capTimer); + transport.onConnectProgress = null; + }; try { const ack = await Promise.race([ transport.connect( n.node_id, live, groupId, null, null, bundleKey, username, userId, null), new Promise((_, reject) => { - timer = setTimeout(() => reject(new Error('Connection timeout')), SEARCH_TIMEOUT); + const giveUp = () => reject(new Error('Connection timeout')); + stallTimer = setTimeout(giveUp, SEARCH_STALL_MS); + capTimer = setTimeout(giveUp, SEARCH_MAX_MS); + // Each step the transport reports — the peer answering ICE, the + // channel opening — buys another window, never more than the cap. + transport.onConnectProgress = () => { + clearTimeout(stallTimer); + stallTimer = setTimeout(giveUp, SEARCH_STALL_MS); + }; }), ]); - clearTimeout(timer); + stopTimers(); return { transport, ack }; } catch (e) { - clearTimeout(timer); + stopTimers(); lastErr = e; try { transport.close(); } catch {} if (e.reason && e.reason !== 'not_hosted') throw e; diff --git a/packages/meshbay-hub/src/meshbay_hub/static/transport.js b/packages/meshbay-hub/src/meshbay_hub/static/transport.js index 1a28fb0..72c831f 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/transport.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/transport.js @@ -582,6 +582,23 @@ class MeshBayTransport { get newNodeBundleRecovery() { return this._newNodeBundleRecovery || null; } set newNodeBundleRecovery(v) { this._newNodeBundleRecovery = v; } + /** + * Tell a caller that connecting is getting somewhere. + * + * Only a caller that imposes its own deadline on `connect()` sets + * `onConnectProgress`, and only Search does: everywhere else a connection is + * opened one at a time and waits out the budgets in here. It exists so that + * deadline can measure *stalling* rather than elapsed time — a link slow + * enough to need twelve seconds is not the same thing as a node that is not + * answering, and a fixed number cannot tell them apart. + * + * Never lets a caller's callback break the connection it is reporting on. + */ + _noteConnectProgress(phase) { + if (!this.onConnectProgress) return; + try { this.onConnectProgress(phase); } catch { /* the caller's problem */ } + } + async connect(nodeId, jwtToken, groupId, gekRaw, sessionKeys, bundleKey, username, userId, joinCode, recoveryKey) { // Remembered for _reconnectLoop, which calls connect() again with these @@ -633,6 +650,7 @@ class MeshBayTransport { clearTimeout(timeout); this._connected = true; trace('channel_open', {}); + this._noteConnectProgress('channel_open'); resolve(); }; }); @@ -695,6 +713,14 @@ class MeshBayTransport { pc.oniceconnectionstatechange = () => { console.log('[MeshBay] ICE state:', pc.iceConnectionState); trace('ice_state', { state: pc.iceConnectionState }); + // `connected`/`completed` is the first moment this browser knows the peer + // is there at all: a candidate pair answered. `checking` is not — it is + // this side trying addresses that may all be dead. + if (pc === this._pc + && (pc.iceConnectionState === 'connected' + || pc.iceConnectionState === 'completed')) { + this._noteConnectProgress('ice_connected'); + } }; // Diagnostic-only: a periodic health ping and a resume-triggered one, so |