aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-10-10 09:57:17 +0200
committerChristophe Besson <cbesson@gmail.com>2026-10-10 09:57:29 +0200
commit876f4a351f859201ca68fbf8d585c86905571686 (patch)
tree7bf029b884ae427352e6c2e7af0cbba887dea824 /packages/meshbay-hub/src
parent42b562fcf312ddceb78438b5daea49d4c9b465c8 (diff)
downloadmeshbay-876f4a351f859201ca68fbf8d585c86905571686.tar.gz
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 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/de.js9
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/en.js9
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/es.js9
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js9
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/it.js9
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js9
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js9
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js9
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js9
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js9
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/node-page.js78
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/profile-page.js24
12 files changed, 190 insertions, 2 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
index c512fe5..1fc734c 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
@@ -542,6 +542,9 @@ export default {
'settings.node_key_placeholder': 'Öffentlichen Ed25519-Schlüssel des Nodes einfügen (base64)',
'settings.node_key_submit': 'Node verknüpfen',
'settings.node_key_success': 'Node erfolgreich verknüpft',
+ 'settings.node_key_unlink': "Trennen",
+ 'settings.node_key_unlink_confirm': "Den Node von Ihrem Konto trennen? Er stellt Ihre Gruppen nicht mehr bereit, bis wieder ein Node verknüpft ist.",
+ 'settings.node_key_unlinked': "Node getrennt",
'settings.node_key_current': 'Schlüssel des verknüpften Nodes',
// Sidebar
@@ -951,6 +954,12 @@ export default {
'node.pair_button': 'Diesen Browser koppeln',
'node.pair_success': 'Erfolgreich gekoppelt.',
'node.pair_pending': 'Kopplungscode bereit: Er wird verwendet, wenn Sie das nächste Mal eine Gruppe dieses Nodes öffnen.',
+ 'node.link_other_node': "Ihr Konto ist mit dem Node eines anderen Rechners verknüpft, daher kann dieser Node Ihre Gruppen nicht bereitstellen.",
+ 'node.link_here': "Stattdessen diesen Node verknüpfen",
+ 'node.link_here_confirm': "Diesen Node mit Ihrem Konto verknüpfen? Der derzeit verknüpfte Node auf einem anderen Rechner stellt Ihre Gruppen nicht mehr bereit, bis er erneut verknüpft wird.",
+ 'node.link_other_account': "Dieser Node ist für {account} eingerichtet, nicht für Sie, daher kann er Ihre Gruppen nicht bereitstellen.",
+ 'node.link_take_over': "Diesen Node für mein Konto verwenden",
+ 'node.link_linking': "Verknüpfen…",
'node.reload': 'Konfiguration neu laden',
'node.reloaded': 'Konfiguration neu geladen. Verzeichnisänderungen an bestehenden Gruppen sind jetzt aktiv.',
'node.restart_needed': 'Nach dem Hinzufügen: Gruppenschlüssel initialisieren (meshbay-node gek-init --group <name>).',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
index f5b3808..6f3be87 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
@@ -530,6 +530,9 @@ export default {
'settings.node_key_placeholder': 'Paste node Ed25519 public key (base64)',
'settings.node_key_submit': 'Link Node',
'settings.node_key_success': 'Node linked successfully',
+ 'settings.node_key_unlink': "Unlink",
+ 'settings.node_key_unlink_confirm': "Unlink the node from your account? It stops serving your groups until a node is linked again.",
+ 'settings.node_key_unlinked': "Node unlinked",
'settings.node_key_current': 'Linked node key',
// Sidebar
@@ -1093,6 +1096,12 @@ export default {
'node.pair_button': 'Pair this browser',
'node.pair_success': 'Paired successfully.',
'node.pair_pending': "Pairing code ready: it is used the next time you open one of this node's groups.",
+ 'node.link_other_node': "Your account is linked to the node of another machine, so this node cannot serve your groups.",
+ 'node.link_here': "Link this node instead",
+ 'node.link_here_confirm': "Link this node to your account? The node linked now, on another machine, stops serving your groups until it is linked again.",
+ 'node.link_other_account': "This node is set up for {account}, not for you, so it cannot serve your groups.",
+ 'node.link_take_over': "Use this node for my account",
+ 'node.link_linking': "Linking…",
'node.reload': 'Reload config',
'node.reloaded': 'Config reloaded. Root changes on existing groups are now active.',
'node.attach_group': 'Add group',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
index 6e152fb..47490d4 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
@@ -537,6 +537,9 @@ export default {
'settings.node_key_placeholder': 'Pegue la clave pública Ed25519 del node (base64)',
'settings.node_key_submit': 'Vincular el node',
'settings.node_key_success': 'Node vinculado correctamente',
+ 'settings.node_key_unlink': "Desvincular",
+ 'settings.node_key_unlink_confirm': "¿Desvincular el nodo de tu cuenta? Dejará de servir tus grupos hasta que se vuelva a vincular un nodo.",
+ 'settings.node_key_unlinked': "Nodo desvinculado",
'settings.node_key_current': 'Clave del node vinculado',
// Sidebar
@@ -945,6 +948,12 @@ export default {
'node.pair_button': 'Emparejar este navegador',
'node.pair_success': 'Emparejamiento exitoso.',
'node.pair_pending': 'Código de emparejamiento listo: se usará la próxima vez que abra uno de los grupos de este Node.',
+ 'node.link_other_node': "Tu cuenta está vinculada al nodo de otra máquina, así que este nodo no puede servir tus grupos.",
+ 'node.link_here': "Vincular este nodo en su lugar",
+ 'node.link_here_confirm': "¿Vincular este nodo a tu cuenta? El nodo vinculado ahora, en otra máquina, dejará de servir tus grupos hasta que se vuelva a vincular.",
+ 'node.link_other_account': "Este nodo está configurado para {account}, no para ti, así que no puede servir tus grupos.",
+ 'node.link_take_over': "Usar este nodo para mi cuenta",
+ 'node.link_linking': "Vinculando…",
'node.reload': 'Recargar configuración',
'node.reloaded': 'Configuración recargada. Los cambios de directorios en los grupos existentes ya están activos.',
'node.restart_needed': 'Después de añadir: inicialice la clave de grupo (meshbay-node gek-init --group <nombre>).',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
index 40c7501..54588b0 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
@@ -540,6 +540,9 @@ export default {
'settings.node_key_placeholder': 'Collez la clé publique Ed25519 du node (base64)',
'settings.node_key_submit': 'Associer le node',
'settings.node_key_success': 'Node associé avec succès',
+ 'settings.node_key_unlink': "Délier",
+ 'settings.node_key_unlink_confirm': "Délier le node de votre compte ? Il cessera de servir vos groupes jusqu'à ce qu'un node soit lié de nouveau.",
+ 'settings.node_key_unlinked': "Node délié",
'settings.node_key_current': 'Clé du node associé',
// Sidebar
@@ -948,6 +951,12 @@ export default {
'node.pair_button': 'Appairer ce navigateur',
'node.pair_success': 'Appairage réussi.',
'node.pair_pending': "Code d'appairage prêt : il sera utilisé à la prochaine ouverture d'un des groupes de ce node.",
+ 'node.link_other_node': "Votre compte est lié au node d'une autre machine : ce node-ci ne peut pas servir vos groupes.",
+ 'node.link_here': "Lier ce node à la place",
+ 'node.link_here_confirm': "Lier ce node à votre compte ? Le node lié actuellement, sur une autre machine, cessera de servir vos groupes jusqu'à ce qu'il soit lié de nouveau.",
+ 'node.link_other_account': "Ce node est configuré pour {account}, pas pour vous : il ne peut pas servir vos groupes.",
+ 'node.link_take_over': "Utiliser ce node pour mon compte",
+ 'node.link_linking': "Liaison…",
'node.reload': 'Recharger la configuration',
'node.reloaded': 'Configuration rechargée. Les modifications de répertoires sur les groupes existants sont maintenant actives.',
'node.restart_needed': 'Après l\'ajout : initialisez la clé de groupe (meshbay-node gek-init --group <name>).',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
index 29a1f5d..5d438a9 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
@@ -540,6 +540,9 @@ export default {
'settings.node_key_placeholder': 'Incolli la chiave pubblica Ed25519 del node (base64)',
'settings.node_key_submit': 'Collega il node',
'settings.node_key_success': 'Node collegato correttamente',
+ 'settings.node_key_unlink': "Scollega",
+ 'settings.node_key_unlink_confirm': "Scollegare il node dal tuo account? Smetterà di servire i tuoi gruppi finché non verrà collegato di nuovo un node.",
+ 'settings.node_key_unlinked': "Node scollegato",
'settings.node_key_current': 'Chiave del node collegato',
// Sidebar
@@ -947,6 +950,12 @@ export default {
'node.pair_button': 'Associa questo browser',
'node.pair_success': 'Associazione riuscita.',
'node.pair_pending': 'Codice di associazione pronto: verrà usato la prossima volta che apri uno dei gruppi di questo Node.',
+ 'node.link_other_node': "Il tuo account è collegato al node di un'altra macchina, quindi questo node non può servire i tuoi gruppi.",
+ 'node.link_here': "Collega invece questo node",
+ 'node.link_here_confirm': "Collegare questo node al tuo account? Il node collegato ora, su un'altra macchina, smetterà di servire i tuoi gruppi finché non verrà ricollegato.",
+ 'node.link_other_account': "Questo node è configurato per {account}, non per te, quindi non può servire i tuoi gruppi.",
+ 'node.link_take_over': "Usa questo node per il mio account",
+ 'node.link_linking': "Collegamento…",
'node.reload': 'Ricarica configurazione',
'node.reloaded': 'Configurazione ricaricata. Le modifiche alle directory dei gruppi esistenti sono ora attive.',
'node.restart_needed': "Dopo l'aggiunta: inizializzi la chiave di gruppo (meshbay-node gek-init --group <nome>).",
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
index 4cb4787..1505026 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
@@ -534,6 +534,9 @@ export default {
'settings.node_key_placeholder': 'node の Ed25519 公開鍵を貼り付け(base64)',
'settings.node_key_submit': 'node を連携',
'settings.node_key_success': 'node を連携しました',
+ 'settings.node_key_unlink': "リンク解除",
+ 'settings.node_key_unlink_confirm': "アカウントからノードのリンクを解除しますか?再びノードがリンクされるまで、グループを提供しなくなります。",
+ 'settings.node_key_unlinked': "ノードのリンクを解除しました",
'settings.node_key_current': '連携済みの node 鍵',
// Sidebar
@@ -935,6 +938,12 @@ export default {
'node.pair_button': 'このブラウザをペアリング',
'node.pair_success': 'ペアリングに成功しました。',
'node.pair_pending': 'ペアリングコードの準備ができました。この Node のグループを次に開いたときに使用されます。',
+ 'node.link_other_node': "アカウントは別のマシンのノードにリンクされているため、このノードではグループを提供できません。",
+ 'node.link_here': "代わりにこのノードをリンク",
+ 'node.link_here_confirm': "このノードをアカウントにリンクしますか?現在リンクされている別のマシンのノードは、再びリンクされるまでグループを提供しなくなります。",
+ 'node.link_other_account': "このノードはあなたではなく {account} 用に設定されているため、グループを提供できません。",
+ 'node.link_take_over': "このノードを自分のアカウントで使う",
+ 'node.link_linking': "リンク中…",
'node.reload': '設定を再読み込み',
'node.reloaded': '設定を再読み込みしました。既存グループのルート変更が反映されました。',
'node.restart_needed': '追加後、グループ鍵を初期化してください(meshbay-node gek-init --group <name>)。',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
index 926fadf..858acbf 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
@@ -541,6 +541,9 @@ export default {
'settings.node_key_placeholder': 'Plak de openbare Ed25519-sleutel van de node (base64)',
'settings.node_key_submit': 'Node koppelen',
'settings.node_key_success': 'Node gekoppeld',
+ 'settings.node_key_unlink': "Ontkoppelen",
+ 'settings.node_key_unlink_confirm': "De node van je account ontkoppelen? Hij bedient je groepen niet meer tot er weer een node is gekoppeld.",
+ 'settings.node_key_unlinked': "Node ontkoppeld",
'settings.node_key_current': 'Sleutel van de gekoppelde node',
// Sidebar
@@ -949,6 +952,12 @@ export default {
'node.pair_button': 'Deze browser koppelen',
'node.pair_success': 'Succesvol gekoppeld.',
'node.pair_pending': 'Koppelingscode klaar: die wordt gebruikt zodra u een van de groepen van deze Node opent.',
+ 'node.link_other_node': "Je account is gekoppeld aan de node van een andere computer, dus deze node kan je groepen niet bedienen.",
+ 'node.link_here': "Koppel in plaats daarvan deze node",
+ 'node.link_here_confirm': "Deze node aan je account koppelen? De nu gekoppelde node op een andere computer bedient je groepen niet meer tot hij opnieuw wordt gekoppeld.",
+ 'node.link_other_account': "Deze node is ingesteld voor {account}, niet voor jou, dus hij kan je groepen niet bedienen.",
+ 'node.link_take_over': "Deze node voor mijn account gebruiken",
+ 'node.link_linking': "Koppelen…",
'node.reload': 'Configuratie herladen',
'node.reloaded': 'Configuratie herladen. Mapwijzigingen op bestaande groepen zijn nu actief.',
'node.restart_needed': 'Na het toevoegen: initialiseer de groepssleutel (meshbay-node gek-init --group <naam>).',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
index 2f3a28c..57b4b8a 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
@@ -553,6 +553,9 @@ export default {
'settings.node_key_placeholder': 'Wklej klucz publiczny Ed25519 node (base64)',
'settings.node_key_submit': 'Powiąż node',
'settings.node_key_success': 'Node został powiązany',
+ 'settings.node_key_unlink': "Odłącz",
+ 'settings.node_key_unlink_confirm': "Odłączyć węzeł od twojego konta? Przestanie obsługiwać twoje grupy, dopóki nie zostanie ponownie powiązany węzeł.",
+ 'settings.node_key_unlinked': "Węzeł odłączony",
'settings.node_key_current': 'Klucz powiązanego node',
// Sidebar
@@ -967,6 +970,12 @@ export default {
'node.pair_button': 'Sparuj tę przeglądarkę',
'node.pair_success': 'Parowanie zakończone sukcesem.',
'node.pair_pending': 'Kod parowania gotowy: zostanie użyty przy następnym otwarciu jednej z grup tego Node.',
+ 'node.link_other_node': "Twoje konto jest powiązane z węzłem innego komputera, więc ten węzeł nie może obsługiwać twoich grup.",
+ 'node.link_here': "Powiąż zamiast tego ten węzeł",
+ 'node.link_here_confirm': "Powiązać ten węzeł z twoim kontem? Węzeł powiązany teraz, na innym komputerze, przestanie obsługiwać twoje grupy, dopóki nie zostanie ponownie powiązany.",
+ 'node.link_other_account': "Ten węzeł jest skonfigurowany dla {account}, nie dla ciebie, więc nie może obsługiwać twoich grup.",
+ 'node.link_take_over': "Użyj tego węzła dla mojego konta",
+ 'node.link_linking': "Wiązanie…",
'node.reload': 'Przeładuj konfigurację',
'node.reloaded': 'Konfiguracja przeładowana. Zmiany katalogów w istniejących grupach są teraz aktywne.',
'node.restart_needed': 'Po dodaniu: zainicjalizuj klucz grupy (meshbay-node gek-init --group <nazwa>).',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js
index faacbf6..23045fb 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js
@@ -539,6 +539,9 @@ export default {
'settings.node_key_placeholder': 'Cole a chave pública Ed25519 do node (base64)',
'settings.node_key_submit': 'Vincular o node',
'settings.node_key_success': 'Node vinculado com sucesso',
+ 'settings.node_key_unlink': "Desvincular",
+ 'settings.node_key_unlink_confirm': "Desvincular o node da sua conta? Ele deixará de servir seus grupos até que um node seja vinculado novamente.",
+ 'settings.node_key_unlinked': "Node desvinculado",
'settings.node_key_current': 'Chave do node vinculado',
// Sidebar
@@ -946,6 +949,12 @@ export default {
'node.pair_button': 'Parear este navegador',
'node.pair_success': 'Pareamento realizado com sucesso.',
'node.pair_pending': 'Código de pareamento pronto: ele será usado na próxima vez que você abrir um dos grupos deste Node.',
+ 'node.link_other_node': "Sua conta está vinculada ao node de outra máquina, então este node não pode servir seus grupos.",
+ 'node.link_here': "Vincular este node no lugar",
+ 'node.link_here_confirm': "Vincular este node à sua conta? O node vinculado agora, em outra máquina, deixará de servir seus grupos até ser vinculado novamente.",
+ 'node.link_other_account': "Este node está configurado para {account}, não para você, então não pode servir seus grupos.",
+ 'node.link_take_over': "Usar este node na minha conta",
+ 'node.link_linking': "Vinculando…",
'node.reload': 'Recarregar configuração',
'node.reloaded': 'Configuração recarregada. Alterações de diretórios em grupos existentes estão ativas.',
'node.restart_needed': 'Após adicionar: inicialize a chave do grupo (meshbay-node gek-init --group <nome>).',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js
index 232a4ca..8070b5f 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js
@@ -528,6 +528,9 @@ export default {
'settings.node_key_placeholder': '粘贴 node 的 Ed25519 公钥(base64)',
'settings.node_key_submit': '关联 node',
'settings.node_key_success': 'node 关联成功',
+ 'settings.node_key_unlink': "取消关联",
+ 'settings.node_key_unlink_confirm': "要从你的账户取消关联该节点吗?在再次关联节点之前,它将停止为你的群组提供服务。",
+ 'settings.node_key_unlinked': "已取消关联节点",
'settings.node_key_current': '已关联的 node 密钥',
// Sidebar
@@ -923,6 +926,12 @@ export default {
'node.pair_button': '配对此浏览器',
'node.pair_success': '配对成功。',
'node.pair_pending': '配对码已就绪:下次打开此 Node 的任一群组时将自动使用。',
+ 'node.link_other_node': "你的账户已关联到另一台机器的节点,因此此节点无法为你的群组提供服务。",
+ 'node.link_here': "改为关联此节点",
+ 'node.link_here_confirm': "将此节点关联到你的账户?当前关联的另一台机器上的节点将停止为你的群组提供服务,直到再次关联。",
+ 'node.link_other_account': "此节点是为 {account} 而不是为你设置的,因此无法为你的群组提供服务。",
+ 'node.link_take_over': "将此节点用于我的账户",
+ 'node.link_linking': "正在关联…",
'node.reload': '重新加载配置',
'node.reloaded': '配置已重新加载。对现有群组的目录更改现已生效。',
'node.restart_needed': '添加后请初始化群组密钥(meshbay-node gek-init --group <名称>)。',
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`
+ <div class="node-pair-banner node-link-banner">
+ <p>${problem === 'other_node'
+ ? t('node.link_other_node')
+ : 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>
+ ${err && html`<p class="error-msg">${err}</p>`}
+ </div>`;
+}
+
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')}</button>
</div>
<${NodeServicePanel} onChanged=${fetchStatus} token=${token} username=${username} />
+ <${NodeLinkBanner} info=${nodeInfo} token=${token} username=${username}
+ onLinked=${fetchStatus} />
${actionMsg && html`<div class="node-message">${actionMsg}</div>`}
${operatorPaired === false && nodeGroups.length > 0 && html`
<div class="node-pair-banner">
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 aac3c14..b52a8c6 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/profile-page.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/profile-page.js
@@ -370,6 +370,25 @@ export function ProfilePage({ user, onLogout }) {
}
}, [nodeKey, user.token]);
+ // Unlinking was only on the linked machine's own Node page, so an account
+ // whose node was on a machine since wiped or lost had no way out here; and a
+ // new machine's node will not take over a key that is still linked
+ // (main.js ensureNode). The hub accepts it from any session of the account.
+ const unlinkNodeKey = useCallback(async () => {
+ if (!await ask(t('settings.node_key_unlink_confirm'))) return;
+ setNodeKeyLoading(true);
+ setNodeKeyStatus('');
+ try {
+ await hubFetch('/v1/users/me/node_key', { method: 'DELETE', token: user.token });
+ setCurrentNodeKey(null);
+ setNodeKeyStatus(t('settings.node_key_unlinked'));
+ } catch (e) {
+ setNodeKeyStatus(e.message);
+ } finally {
+ setNodeKeyLoading(false);
+ }
+ }, [user.token]);
+
return html`
<div>
<h2>${t('profile.title')}</h2>
@@ -435,6 +454,9 @@ export function ProfilePage({ user, onLogout }) {
<div class="settings-row" style="margin-top:8px">
<span class="settings-label">${t('settings.node_key_current')}</span>
<code class="settings-value" style="font-size:0.8em;word-break:break-all">${currentNodeKey}</code>
+ <button class="admin-btn" onClick=${unlinkNodeKey} disabled=${nodeKeyLoading}>
+ ${t('settings.node_key_unlink')}
+ </button>
</div>
`}
<div style="display:flex;gap:8px;margin-top:10px;align-items:center">
@@ -448,7 +470,7 @@ export function ProfilePage({ user, onLogout }) {
</button>
</div>
${nodeKeyStatus && html`
- <p style="margin-top:6px;font-size:0.85em;color:${nodeKeyStatus === t('settings.node_key_success') ? 'var(--success)' : 'var(--error)'}">
+ <p style="margin-top:6px;font-size:0.85em;color:${[t('settings.node_key_success'), t('settings.node_key_unlinked')].includes(nodeKeyStatus) ? 'var(--success)' : 'var(--error)'}">
${nodeKeyStatus}
</p>
`}