aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/node-page.js
diff options
context:
space:
mode:
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.js54
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>`;
}