diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-10-17 13:29:25 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-10-17 13:29:25 +0200 |
| commit | 165847da1ca3c3be627b5968bc77e9a11b3385cb (patch) | |
| tree | 603329a801d1304fdba833c1a8f4cd8c4ce4e7c6 /packages/meshbay-hub/src/meshbay_hub/static/group-page.js | |
| parent | a1d18a2957d167444c6fc6c2e4ec8628509a7009 (diff) | |
| download | meshbay-165847da1ca3c3be627b5968bc77e9a11b3385cb.tar.gz | |
feat(android): ask for the local network when a group opens
Android 17 makes it a permission for an application targeting SDK 37.
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 | 46 |
1 files changed, 45 insertions, 1 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 728ffc37..45049a16 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/group-page.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/group-page.js @@ -31,6 +31,10 @@ import { clearPending, nodePkFromLink, pendingFor } from './invite-link.js'; // each try is a connection. const DEVICE_RETRY_MS = 5000; +// Whether this application has asked for the local network yet in this run: +// once, when the first group is opened, and again only when the person says so. +let lanAsked = false; + /** The node's `expires_at` (ISO) in epoch seconds; 0 when it gave none. */ function _untilSeconds(iso) { const ms = Date.parse(iso || ''); @@ -240,6 +244,9 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs, const [passInput, setPassInput] = useState(''); const [passBusy, setPassBusy] = useState(false); const [retryKey, setRetryKey] = useState(0); + // 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(''); const transportRef = useRef(null); const gekRef = useRef(null); // One refresh per group: if a fresh token still says we are not a member, we @@ -398,6 +405,7 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs, const connect = async () => { setStatus('discovering'); setError(''); + setLanBlocked(''); // Belongs to the connection about to be made, not the group just left. setDeviceReady(false); gekRef.current = null; @@ -425,6 +433,16 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs, // from the node, or creates one there on a first join. const identity = null; + // Android 17 asks before this application reaches the local network, + // where the node usually is: when a group is first opened rather than + // at start, and a refusal is explained below if it is why the group + // does not open (local_network_blocked). + if (platform.localNetwork.available && !lanAsked) { + lanAsked = true; + try { await platform.localNetwork.request(); } catch { /* the connection says the rest */ } + if (cancelled) return; + } + setStatus('connecting'); // Renewed here rather than taken from the prop. This effect no longer // re-runs when the token rotates (see the dependency list below), so @@ -706,7 +724,14 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs, // 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') { + 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'); } @@ -1038,6 +1063,25 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs, <button class="admin-btn" style="margin-left:8px;font-size:0.9em" onClick=${() => setRetryKey(k => k + 1)}>${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); + }}>${t('group.lan_allow')}</button>`} + ${lanBlocked === 'denied' && html` + <p class="settings-hint">${t('group.lan_settings_hint')}</p> + <button class="admin-btn" style="margin-bottom:12px" onClick=${() => { + // Back from the settings is back in the foreground: try again then. + const back = () => { + if (document.visibilityState !== 'visible') return; + document.removeEventListener('visibilitychange', back); + setRetryKey(k => k + 1); + }; + document.addEventListener('visibilitychange', back); + platform.localNetwork.openSettings().catch(() => { + document.removeEventListener('visibilitychange', back); + }); + }}>${t('group.lan_open_settings')}</button>`} ${needsDevice && nativeKeys && html` <div class="invite-form" style="margin-bottom:12px"> <h4>${t('device.add_title')}</h4> |