diff options
Diffstat (limited to 'packages/meshbay-hub')
14 files changed, 81 insertions, 12 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/app.js b/packages/meshbay-hub/src/meshbay_hub/static/app.js index c1913c0..b9466d0 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/app.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/app.js @@ -429,7 +429,7 @@ function Nav({ user, theme, onThemeChange, onLogout, onMenuToggle, unreadCount, // ── Sidebar ────────────────────────────────────────────────────────────────── -function Sidebar({ groups, presence, indexProgressPct, route, menuOpen, role, hasNodeKey, +function Sidebar({ groups, presence, indexProgressPct, route, menuOpen, role, showNode, allowPublicGroups = true }) { const isStaff = role === 'moderator' || role === 'admin'; return html` @@ -449,7 +449,7 @@ function Sidebar({ groups, presence, indexProgressPct, route, menuOpen, role, ha <a class="sidebar-item ${route === '/search' ? 'active' : ''}" href="#/search"><${Icon} name="search" /> ${t('sidebar.search')}</a> </div> - ${platform.capabilities.nodeAdmin && hasNodeKey && html` + ${platform.capabilities.nodeAdmin && showNode && html` <div class="sidebar-section"> <div class="sidebar-heading">${t('sidebar.node')}</div> <a class="sidebar-item ${route === '/node' ? 'active' : ''}" @@ -781,6 +781,9 @@ function App() { const [notifDisabled, setNotifDisabled] = useState(false); const [userPrefs, setUserPrefs] = useState({}); const [hasNodeKey, setHasNodeKey] = useState(false); + // A node on this machine, linked to the account or not: the Node section is + // where it is looked after, so it cannot wait for a link to show up. + const [localNode, setLocalNode] = useState(false); // Instance policy, fetched once, unauthenticated. `null` until it answers; // treat unknown as "allowed" so a slow hub never blocks a legitimate private // group — the hub refuses a public one server-side regardless. @@ -1041,6 +1044,25 @@ function App() { fetchNotifications(); }, [user]); + // The node this application ships, set up, started and linked for whoever + // signs in (main.js `ensureNode`): installing it is all it should take. + // Keyed on the account, not the session object -- the token rotates, and a + // renewal is not a sign-in. + const signedInAs = user ? user.username : null; + useEffect(() => { + setLocalNode(false); + if (!signedInAs || !platform.capabilities.nodeAdmin || !platform.node.available) return; + let cancelled = false; + platform.node.installed() + .then((r) => { if (!cancelled) setLocalNode(Boolean(r && r.installed)); }) + .catch(() => {}); + const live = userRef.current; + platform.node.ensure({ username: live.username, token: live.token }) + .then(() => { if (!cancelled) refreshNodeKey(); }) + .catch(() => {}); + return () => { cancelled = true; }; + }, [signedInAs]); + // After an invitation is accepted: the group is ours now, and only the hub // knows its row the way `/mine` answers it. const reloadGroups = useCallback(() => { @@ -1316,7 +1338,7 @@ function App() { .catch(() => {}); refreshNodeKey(); }} />`; - } else if (route === '/node' && platform.capabilities.nodeAdmin && hasNodeKey) { + } else if (route === '/node' && platform.capabilities.nodeAdmin && (hasNodeKey || localNode)) { page = html`<${LazyNodePage} groups=${groups} token=${user.token} username=${user.username} />`; } else if (groupId) { page = html`<${GroupPage} @@ -1380,7 +1402,7 @@ function App() { menuOpen=${menuOpen} role=${user.role} allowPublicGroups=${allowPublicGroups} - hasNodeKey=${hasNodeKey} />`} + showNode=${hasNodeKey || localNode} />`} ${menuOpen && html`<div class="overlay visible" onClick=${() => setMenuOpen(false)} />`} <main class="main"> 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 f4151f7..4dff605 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js @@ -939,6 +939,7 @@ export default { 'node.pair_code_placeholder': 'Kopplungscode', '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.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 84607e7..90c043e 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js @@ -1079,6 +1079,7 @@ export default { 'node.pair_code_placeholder': 'Pairing code', '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.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 28ad85e..7b46b17 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js @@ -933,6 +933,7 @@ export default { 'node.pair_code_placeholder': 'Código de emparejamiento', '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.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 9e8dbbe..d3f9200 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js @@ -936,6 +936,7 @@ export default { 'node.pair_code_placeholder': "Code d'appairage", '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.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 493455d..715738e 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js @@ -935,6 +935,7 @@ export default { 'node.pair_code_placeholder': 'Codice di associazione', '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.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 35a639a..23358c2 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js @@ -923,6 +923,7 @@ export default { 'node.pair_code_placeholder': 'ペアリングコード', 'node.pair_button': 'このブラウザをペアリング', 'node.pair_success': 'ペアリングに成功しました。', + 'node.pair_pending': 'ペアリングコードの準備ができました。この Node のグループを次に開いたときに使用されます。', '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 73731cf..5e53084 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js @@ -937,6 +937,7 @@ export default { 'node.pair_code_placeholder': 'Koppelingscode', '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.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 849c632..5d6d852 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js @@ -955,6 +955,7 @@ export default { 'node.pair_code_placeholder': 'Kod parowania', '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.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 5672b60..6fe50f6 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 @@ -934,6 +934,7 @@ export default { 'node.pair_code_placeholder': 'Código de pareamento', '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.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 26025ef..b3886ff 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 @@ -911,6 +911,7 @@ export default { 'node.pair_code_placeholder': '配对码', 'node.pair_button': '配对此浏览器', 'node.pair_success': '配对成功。', + 'node.pair_pending': '配对码已就绪:下次打开此 Node 的任一群组时将自动使用。', '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 9d230c1..04f4abc 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 } from './hub-client.js'; +import { HUB, session } from './hub-client.js'; // ── Node management (D5) ──────────────────────────────────────────────────── // @@ -108,6 +108,9 @@ function NodeServicePanel({ onChanged, token, username }) { const KNOWN_STATES = ['active', 'inactive', 'failed', 'activating', 'deactivating']; const stateKey = KNOWN_STATES.includes(info.activeState) ? info.activeState : 'unknown'; const running = info.activeState === 'active' || info.activeState === 'activating'; + // A node process that does not answer ('unknown': on its way up or down, or + // stuck) is still one to stop -- only "nothing there" takes Stop away. + const stoppable = info.activeState !== 'inactive'; const dot = info.activeState === 'active' ? 'online' : info.activeState === 'failed' ? 'offline' : 'unknown'; const label = info.installed ? t('node.service_state_' + stateKey) @@ -132,7 +135,7 @@ function NodeServicePanel({ onChanged, token, username }) { onClick=${() => act('start', () => platform.node.start({ hubUrl: HUB, username, token }))}> ${busy === 'start' ? t('node.service_starting') : t('node.service_start')}</button> ${info.installed && html` - <button class="btn btn-small btn-secondary" disabled=${!!busy || !running} + <button class="btn btn-small btn-secondary" disabled=${!!busy || !stoppable} onClick=${() => act('stop', () => platform.node.service.stop())}> ${busy === 'stop' ? t('node.service_stopping') : t('node.service_stop')}</button> <button class="btn btn-small btn-secondary" disabled=${!!busy} @@ -520,13 +523,17 @@ export function NodePage({ groups, token, username }) { setPairBusy(true); setPairStatus(''); try { + // An operator code is redeemed over MNP, with the account's identity in + // a group -- this page has no transport. It used to store the code where + // nothing read it and say "Paired successfully", and the banner came back + // on the next refresh. Queued the way the Create Group wizard queues it: + // the next group of this node to open redeems it. const result = await nodeOp('pairOperator'); if (result && result.code) { await platform.node.setPairingCode(result.code); + session.pendingJoinCode = result.code; } - setPairStatus('paired'); - setOperatorPaired(true); - await refresh(); + setPairStatus('pending'); } catch (err) { setPairStatus(platform.bridgeMessage(err)); } finally { @@ -712,15 +719,15 @@ export function NodePage({ groups, token, username }) { </div> <${NodeServicePanel} onChanged=${fetchStatus} token=${token} username=${username} /> ${actionMsg && html`<div class="node-message">${actionMsg}</div>`} - ${operatorPaired === false && html` + ${operatorPaired === false && nodeGroups.length > 0 && html` <div class="node-pair-banner"> <p>${t('node.pair_needed')}</p> <button class="btn btn-primary btn-small" disabled=${pairBusy} onClick=${doPairOperator}> ${pairBusy ? t('node.settings_saving') : t('node.pair_button')}</button> - ${pairStatus === 'paired' - ? html`<p class="success-msg">${t('node.pair_success')}</p>` + ${pairStatus === 'pending' + ? html`<p class="success-msg">${t('node.pair_pending')}</p>` : pairStatus ? html`<p class="error-msg">${pairStatus}</p>` : null} </div> `} diff --git a/packages/meshbay-hub/src/meshbay_hub/static/platform.js b/packages/meshbay-hub/src/meshbay_hub/static/platform.js index 28817c7..e3b3d2d 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/platform.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/platform.js @@ -253,6 +253,16 @@ export const node = { return bridge.node.start(opts); }, /** + * After sign-in: set up, start and link the node this application ships, + * for the signed-in account ({username, token}). Leaves alone a node set up + * for another account and an account linked to another node. Resolves to + * `{ state }`; a build or platform with nothing to do answers 'unsupported'. + */ + async ensure(opts) { + if (!bridge || !bridge.node || !bridge.node.ensure) return { state: 'unsupported' }; + return bridge.node.ensure(opts); + }, + /** * One of the local node's operations, by name (`NODE_OPS` in the desktop * client's main.js). The page never names a route: the main process checks * the arguments, builds the request, and asks the person itself before diff --git a/packages/meshbay-hub/tests/test_node_page_pairing.py b/packages/meshbay-hub/tests/test_node_page_pairing.py index 435488e..c716e0a 100644 --- a/packages/meshbay-hub/tests/test_node_page_pairing.py +++ b/packages/meshbay-hub/tests/test_node_page_pairing.py @@ -46,3 +46,23 @@ def test_the_banner_needs_an_explicit_false(): "the pairing banner must not show for a node that has not read its roster") assert "useState(null)" in src.split("const [operatorPaired", 1)[1].split("\n", 1)[0] assert "setOperatorPaired(!!" not in src + + +def test_pair_this_browser_queues_the_code_instead_of_claiming_success(): + """It stored the code where nothing read it, said "Paired successfully", + and the banner came back on the next refresh. The code is redeemed over + MNP by the next group of this node to open, as the wizard's is.""" + src = _source() + pair = src.split("const doPairOperator = useCallback(", 1)[1].split("}, [refresh]);", 1)[0] + assert "session.pendingJoinCode = result.code;" in pair + assert "setOperatorPaired(true)" not in pair + assert "import { HUB, session } from './hub-client.js';" in src + + +def test_no_pairing_banner_for_a_node_with_no_group_yet(): + """A node set up at sign-in hosts nothing yet: there is nothing to pair + for until a group exists, and creating one pairs.""" + src = _source() + banner = src.index('class="node-pair-banner"') + guard = src.rindex("${", 0, banner) + assert "nodeGroups.length > 0" in src[guard:banner] |