From 876f4a351f859201ca68fbf8d585c86905571686 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Sat, 10 Oct 2026 09:57:17 +0200 Subject: fix(client): say when the account's node is elsewhere, and let go of it Signing in on a desktop links this machine's node to the account, but never over a key already linked (that would cut off the user's other machine) nor a node set up for another account. On a real install both left the node reading "Running" while the hub refused it in a loop, and nothing said why. And the only way to unlink was the linked machine's own Node page, of no use once that machine is gone. - The Node page works out, from the node and the hub each time it looks, whether this node can serve the signed-in account (nodeLinkProblem), and says why not: "Link this node instead" (asked first) puts this node's key on the account; "Use this node for my account" switches a node set up for another account through node:start, which takeOver now lets past a node that answers "running". The first version went through node:start for both, and clicking it on a real install did nothing: a node signed in before the account was linked elsewhere answers "running". - The Profile page unlinks the account's node (DELETE /v1/users/me/node_key), from any machine. Co-Authored-By: Claude Opus 5.5 --- .../src/meshbay_hub/static/node-page.js | 78 +++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) (limited to 'packages/meshbay-hub/src/meshbay_hub/static/node-page.js') diff --git a/packages/meshbay-hub/src/meshbay_hub/static/node-page.js b/packages/meshbay-hub/src/meshbay_hub/static/node-page.js index 11cd07e..56166c6 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/node-page.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/node-page.js @@ -6,7 +6,7 @@ import { t } from './i18n.js'; import { ask } from './ask.js'; import * as platform from './platform.js'; import { Icon } from './icon.js'; -import { HUB, session } from './hub-client.js'; +import { HUB, session, hubFetch } from './hub-client.js'; // ── Node management (D5) ──────────────────────────────────────────────────── // @@ -22,6 +22,80 @@ export function pairedFrom(result) { return v === true || v === false ? v : null; } +// Why this machine's node cannot serve the signed-in account, if it cannot: +// 'other_account' when it is set up for another account or hub, 'other_node' +// when the account is linked to another node's key. Signing in links this +// node (main.js ensureNode) but never over either, since that would cut off +// the user's other machine or someone else's node; both left a node reading +// "Running" that the hub refused in a loop, with nothing here saying why +// (found on a real install, 2026-10-10). Worked out from the node and the hub +// each time the page looks, not kept from sign-in: a link made or removed on +// the Profile page or on the other machine changes the answer. +export function nodeLinkProblem(info, linkedKey, username, hub) { + if (!info || !username) return null; + const sameHub = (a, b) => String(a || '').replace(/\/+$/, '') === String(b || '').replace(/\/+$/, ''); + if ((info.username && info.username !== username) + || (info.hub_url && hub && !sameHub(info.hub_url, hub))) return 'other_account'; + if (linkedKey && info.pk_node_ed25519 && linkedKey !== info.pk_node_ed25519) return 'other_node'; + return null; +} + +function NodeLinkBanner({ info, token, username, onLinked }) { + const [linkedKey, setLinkedKey] = useState(null); + const [busy, setBusy] = useState(false); + const [err, setErr] = useState(''); + const nodeKey = info && info.pk_node_ed25519; + + useEffect(() => { + if (!username || !token) return; + let cancelled = false; + hubFetch(`/v1/users/${encodeURIComponent(username)}/pubkeys`, { token }) + .then((k) => { if (!cancelled) setLinkedKey((k && k.pk_node_ed25519) || null); }) + .catch(() => {}); + return () => { cancelled = true; }; + }, [username, token, nodeKey]); + + const problem = nodeLinkProblem(info, linkedKey, username, HUB); + if (!problem) return null; + + // Another node's key: this node's replaces it on the account, and a node + // waiting for its account signs in on its next attempt. Not node:start, + // which leaves a node that answers "running" alone, and one can (signed in + // before the account was linked elsewhere). Another account: node:start + // switches it, after asking, restarts it and links it. + const takeOver = async () => { + if (problem === 'other_node' && !await ask(t('node.link_here_confirm'))) return; + setBusy(true); + setErr(''); + try { + if (problem === 'other_node') { + await hubFetch('/v1/users/me/node_key', { + method: 'PUT', token, body: { pk_node_ed25519: nodeKey }, + }); + } else { + await platform.node.start({ hubUrl: HUB, username, token, takeOver: true }); + } + setLinkedKey(nodeKey); + if (onLinked) onLinked(); + } catch (e) { + setErr(platform.bridgeMessage(e)); + } finally { + setBusy(false); + } + }; + + return html` + `; +} + function NodeServicePanel({ onChanged, token, username }) { const [info, setInfo] = useState(null); const [busy, setBusy] = useState(''); @@ -725,6 +799,8 @@ export function NodePage({ groups, token, username }) { ${t('node.reload')} <${NodeServicePanel} onChanged=${fetchStatus} token=${token} username=${username} /> + <${NodeLinkBanner} info=${nodeInfo} token=${token} username=${username} + onLinked=${fetchStatus} /> ${actionMsg && html`
${actionMsg}
`} ${operatorPaired === false && nodeGroups.length > 0 && html`
-- cgit v1.2.3