diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-10-17 18:37:35 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-10-17 18:37:35 +0200 |
| commit | 8125df6d8149368ed8a84c9db6c5e37bfccb5a15 (patch) | |
| tree | 6eba4f399c7a9b6e785f4d28bc197fb31923d0a3 /packages/meshbay-hub/src/meshbay_hub/static/group-page.js | |
| parent | d5ba9552e27bac71168178f73a4c977a4d36dc1c (diff) | |
| download | meshbay-8125df6d8149368ed8a84c9db6c5e37bfccb5a15.tar.gz | |
fix(spa): a reconnect refused the local network says so
Each failed attempt reaches the group page, which shows the same message
and buttons as at opening while the reconnect goes on. Try now gives up
an attempt still waiting on ICE and starts the next at once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/group-page.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/group-page.js | 68 |
1 files changed, 52 insertions, 16 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/group-page.js b/packages/meshbay-hub/src/meshbay_hub/static/group-page.js index 45049a16..304a28fb 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/group-page.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/group-page.js @@ -247,6 +247,9 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs, // In the Android application, when the local network was refused: whether // Android will still ask ("prompt") or only its settings can change it. const [lanBlocked, setLanBlocked] = useState(''); + // Set while the message is a reconnect refused the local network: the + // reconnect that gets through takes it away. + const lanLostRef = useRef(false); const transportRef = useRef(null); const gekRef = useRef(null); // One refresh per group: if a fresh token still says we are not a member, we @@ -394,6 +397,7 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs, // Dropped by the teardown below, so a transport handed on to a running // download (`releaseWhenIdle`) stops driving a page that is gone. let offReconnect = null; + let offRefused = null; let deviceRetry = null; // The cache is written here and read only by the search page. It used to @@ -402,10 +406,27 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs, // cache landing afterwards. Files shows what the node says, or says it // cannot reach the node. + // This device dropped the node's local addresses (transport.js + // localNetworkRefused): a setting to change here, not a node to wait for. + // Android 17 gates it per application, Chrome from 155. In the application, + // whether Android will still ask or only its settings can change it. + const explainLanRefusal = async () => { + if (!platform.localNetwork.available) { + return t(/Android/i.test(navigator.userAgent) + ? 'group.lan_blocked_android' : 'group.lan_blocked_browser'); + } + try { + const state = await platform.localNetwork.state(); + if (!cancelled) setLanBlocked(state === 'granted' ? '' : state); + } catch { /* the message stands alone */ } + return t('group.lan_blocked_app'); + }; + const connect = async () => { setStatus('discovering'); setError(''); setLanBlocked(''); + lanLostRef.current = false; // Belongs to the connection about to be made, not the group just left. setDeviceReady(false); gekRef.current = null; @@ -632,8 +653,24 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs, // full fetch rather than a delta: this session was never told what it // missed, and a delta is computed against a snapshot only the node // has. + // Connected on mobile data, then on the node's Wi-Fi without the + // permission: every attempt is refused and the reconnect goes on, so a + // permission granted or mobile data again reaches the group by itself. + // Said while it lasts, over the view the group had. + offRefused = transport.addReconnectFailureListener(async (e) => { + if (cancelled || e.reason !== 'local_network_blocked') return; + const message = await explainLanRefusal(); + if (cancelled) return; + lanLostRef.current = true; + setError(message); + }); offReconnect = transport.addReconnectListener((reack) => { if (cancelled) return; + if (lanLostRef.current) { + lanLostRef.current = false; + setError(''); + setLanBlocked(''); + } (async () => { try { applyAck(reack); @@ -721,19 +758,9 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs, if (err.reason === 'no_keys') setNeedsPass(true); // An identity sealed before the pepper: only the operator can clear it. if (err.reason === 'bundle_format_retired') err.message = t('group.bundle_format_retired'); - // This device dropped the node's local addresses (transport.js - // localNetworkRefused): a setting to change here, not a node to wait - // for. Android 17 gates it per application, Chrome from 155. - if (err.reason === 'local_network_blocked' && platform.localNetwork.available) { - err.message = t('group.lan_blocked_app'); - try { - const state = await platform.localNetwork.state(); - if (cancelled) return; - setLanBlocked(state === 'granted' ? '' : state); - } catch { /* the message stands alone */ } - } else if (err.reason === 'local_network_blocked') { - err.message = t(/Android/i.test(navigator.userAgent) - ? 'group.lan_blocked_android' : 'group.lan_blocked_browser'); + if (err.reason === 'local_network_blocked') { + err.message = await explainLanRefusal(); + if (cancelled) return; } // A key this node has never pinned, for an account it knows. The way in // is a device already trusted here, not an operator — which is the @@ -787,6 +814,7 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs, return () => { cancelled = true; if (offReconnect) { offReconnect(); offReconnect = null; } + if (offRefused) { offRefused(); offRefused = null; } clearTimeout(deviceRetry); // Nothing will update this group's dock row once the page lets go of it. reportIndexPush(groupId, null); @@ -814,6 +842,14 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs, // it; nothing in between should disturb a working connection. }, [groupId, Boolean(token), retryKey]); + // A connection still reconnecting is asked to try now rather than torn down: + // what it carries (a download, a film) goes on once it gets through. + const retry = useCallback(() => { + const transport = transportRef.current; + if (transport && transport.reconnecting) transport.retryNow(); + else setRetryKey((k) => k + 1); + }, []); + // A group that could not be reached is tried again when the device joins // another network: from the Wi-Fi to mobile data, or back, is what decides // whether the node can be reached, and in the Android application there is @@ -1061,12 +1097,12 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs, <p class="settings-hint" style="margin-bottom:12px">${t('group.browser_access_off')}</p>`} ${error && html`<div class="error-msg" style="margin-bottom:12px">${error}${' '} <button class="admin-btn" style="margin-left:8px;font-size:0.9em" - onClick=${() => setRetryKey(k => k + 1)}>${t('group.retry')}</button> + onClick=${retry}>${t('group.retry')}</button> </div>`} ${lanBlocked === 'prompt' && html` <button class="admin-btn" style="margin-bottom:12px" onClick=${async () => { try { await platform.localNetwork.request(); } catch { /* retried either way */ } - setRetryKey(k => k + 1); + retry(); }}>${t('group.lan_allow')}</button>`} ${lanBlocked === 'denied' && html` <p class="settings-hint">${t('group.lan_settings_hint')}</p> @@ -1075,7 +1111,7 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs, const back = () => { if (document.visibilityState !== 'visible') return; document.removeEventListener('visibilitychange', back); - setRetryKey(k => k + 1); + retry(); }; document.addEventListener('visibilitychange', back); platform.localNetwork.openSettings().catch(() => { |