diff options
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/node-page.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/node-page.js | 54 |
1 files changed, 28 insertions, 26 deletions
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 88167d3..bc22cbd 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, hubFetch } from './hub-client.js'; +import { HUB, session, hubFetch, linkedNodeKeys } from './hub-client.js'; // ── Node management (D5) ──────────────────────────────────────────────────── // @@ -23,25 +23,27 @@ export function pairedFrom(result) { } // 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) { +// 'other_account' when it is set up for another account or hub, 'not_linked' +// when its key is not among the account's linked keys. An account links one +// key per machine it runs a node on, so linking this one cuts off no other. +// Signing in links it (main.js ensureNode) but never over another account's +// node; both cases 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 key linked or unlinked on the Profile page changes the answer. +// `linkedKeys` is null until the hub has answered, so nothing is claimed before. +export function nodeLinkProblem(info, linkedKeys, 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'; + if (Array.isArray(linkedKeys) && info.pk_node_ed25519 + && !linkedKeys.includes(info.pk_node_ed25519)) return 'not_linked'; return null; } function NodeLinkBanner({ info, token, username, onLinked }) { - const [linkedKey, setLinkedKey] = useState(null); + const [linkedKeys, setLinkedKeys] = useState(null); const [busy, setBusy] = useState(false); const [err, setErr] = useState(''); const nodeKey = info && info.pk_node_ed25519; @@ -49,33 +51,33 @@ function NodeLinkBanner({ info, token, username, onLinked }) { 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); }) + linkedNodeKeys(token) + .then((r) => { + if (!cancelled) setLinkedKeys(((r && r.keys) || []).map(k => k.pk_node_ed25519)); + }) .catch(() => {}); return () => { cancelled = true; }; }, [username, token, nodeKey]); - const problem = nodeLinkProblem(info, linkedKey, username, HUB); + const problem = nodeLinkProblem(info, linkedKeys, 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. + // Not linked: this node's key is added beside the account's others, and a + // node waiting for its link signs in on its next attempt. Not node:start, + // which leaves a node that answers "running" alone. 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') { + if (problem === 'not_linked') { 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); + setLinkedKeys([...(linkedKeys || []), nodeKey]); if (onLinked) onLinked(); } catch (e) { setErr(platform.bridgeMessage(e)); @@ -86,12 +88,12 @@ function NodeLinkBanner({ info, token, username, onLinked }) { return html` <div class="node-pair-banner node-link-banner"> - <p>${problem === 'other_node' - ? t('node.link_other_node') + <p>${problem === 'not_linked' + ? t('node.link_not_linked') : t('node.link_other_account', { account: `${info.username} @ ${info.hub_url}` })}</p> <button class="btn btn-primary btn-small" disabled=${busy} onClick=${takeOver}> ${busy ? t('node.link_linking') - : problem === 'other_node' ? t('node.link_here') : t('node.link_take_over')}</button> + : problem === 'not_linked' ? t('node.link_here') : t('node.link_take_over')}</button> ${err && html`<p class="error-msg">${err}</p>`} </div>`; } |