From b1ebcdeb9082457972c41a47e77494902335d262 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Wed, 30 Sep 2026 17:13:40 +0200 Subject: feat: browser access, decided in the desktop application Off for an account made there: its identities stay on the device and nothing is left on nodes. Turned on from the Profile page behind a native confirmation; each node is settled when its group next opens. The hub keeps a mirror a browser reads to say why a group will not open; it grants nothing. Co-Authored-By: Claude Opus 5.5 --- .../src/meshbay_hub/static/profile-page.js | 56 +++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) (limited to 'packages/meshbay-hub/src/meshbay_hub/static/profile-page.js') diff --git a/packages/meshbay-hub/src/meshbay_hub/static/profile-page.js b/packages/meshbay-hub/src/meshbay_hub/static/profile-page.js index cd00f03..7c36951 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/profile-page.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/profile-page.js @@ -6,7 +6,7 @@ import { ask } from './ask.js'; import { Icon } from './icon.js'; import * as platform from './platform.js'; import { - hubFetch, HUB, session, setAuth, + hubFetch, HUB, session, setAuth, mirrorBrowserAccess, _storeBundleKey, _loadBundleKey, _storeRecoveryKey, } from './hub-client.js'; @@ -241,6 +241,46 @@ export function ProfilePage({ user, onLogout }) { } }, [rkInput, user]); + // ── Browser access (docs/MESHBAY_DESIGN.md §3.7) ──────────────────────── + // Decided in the desktop application, which keeps the identities: there it + // is a switch, and turning it on is confirmed by the application itself. A + // browser only reads the hub's mirror, to say why it cannot open a group. + const [baNative, setBaNative] = useState(false); + const [baOn, setBaOn] = useState(null); + const [baBusy, setBaBusy] = useState(false); + const [baMsg, setBaMsg] = useState(''); + useEffect(() => { + let gone = false; + (async () => { + const native = await platform.nativeKeys(); + if (gone) return; + setBaNative(native); + if (native) { + const on = await platform.keys.browserAccess(user.userId); + if (!gone) setBaOn(on); + return; + } + try { + const prefs = await hubFetch('/v1/users/me/preferences', { token: user.token }); + if (!gone) setBaOn((prefs || {}).browser_access !== 'off'); + } catch { /* shown as nothing */ } + })(); + return () => { gone = true; }; + }, [user]); + const baSet = useCallback(async (on) => { + setBaBusy(true); + setBaMsg(''); + try { + setBaOn(await platform.keys.setBrowserAccess(user.userId, on)); + await mirrorBrowserAccess(user.token, user.userId); + setBaMsg(t('settings.browser_access_next_open')); + } catch (err) { + setBaMsg(platform.bridgeMessage(err)); + } finally { + setBaBusy(false); + } + }, [user]); + useEffect(() => { hubFetch('/v1/users/me', { token: user.token }) .then(data => { @@ -539,6 +579,20 @@ export function ProfilePage({ user, onLogout }) { `} + ${baOn !== null && (baNative || baOn === false) && html` +
+

${t('settings.browser_access')}

+ ${baNative ? html` +

+ ${baOn ? t('settings.browser_access_on_hint') : t('settings.browser_access_off_hint')} +

+ + ${baMsg && html`

${baMsg}

`} + ` : html`

${t('settings.browser_access_off_in_browser')}

`} +
`} +

${t('settings.sessions_heading')}

${t('settings.sign_out_everywhere_hint')}

-- cgit v1.2.3