diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-10 17:29:50 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-10 17:29:50 +0200 |
| commit | 4753c67816c774323e3ab4efc76d3259e8ded40d (patch) | |
| tree | 0ac0b8a3013aa483111ea249e3aa37ff49988891 /packages/meshbay-hub/src | |
| parent | 1dcedc77083b908b7b3b431bad679a4813884355 (diff) | |
| download | meshbay-4753c67816c774323e3ab4efc76d3259e8ded40d.tar.gz | |
refactor(spa): stop asking a node what version it is
`MNP_MIN_SUPPORTED` is the version this build speaks, so `check_version`
refuses everything below it at the handshake. Every capability the client was
gating on the node's version is therefore true of every peer it can reach:
* `supportsSealedUpload` — an upload is sealed or it is not sent;
* `supportsAppOps` — one `app_directories` op, and no `setVideoRoot` /
`setAudioRoot` / `setPhotoRoots` wrappers behind it;
* `supportsTransferSlots` and `Lease._skip()` — a lease is always real, so
there is no branch where a transfer runs without one;
* `legacyNode`, the read-only shared-directories table, and the two hints
telling an operator their node is too old to configure an app.
The version the node declares is still recorded, for diagnostics. Nothing
branches on it, and the comment says so, because a field kept "just in case" is
how the branches came back last time.
`test_mnp_1_0_node_compat.py` goes with them: it existed to hold the fallbacks
in place, and holding a fallback that cannot execute is how a suite starts
lying. The two locale strings for those hints are removed from all ten
catalogues.
Hub suite 872 passed (test_sticky_header deselected — failing before this).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AsoWC3GmhNdwVFomW3QjH3
Diffstat (limited to 'packages/meshbay-hub/src')
14 files changed, 17 insertions, 222 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/group-page.js b/packages/meshbay-hub/src/meshbay_hub/static/group-page.js index 0f442cf..341d37e 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/group-page.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/group-page.js @@ -145,10 +145,6 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs, // destination rather than a set of folders it reads. const [chatDirectory, setChatDirectory] = useState(''); const [chatLinkPreview, setChatLinkPreview] = useState(true); - // Whether this node speaks the operations MNP 1.1 added. False for one that - // predates them, and the Settings page then offers what that node can - // actually do rather than controls whose messages it drops unanswered. - const [nodeSupportsAppOps, setNodeSupportsAppOps] = useState(false); // MusicBrainz on/off (per-group) — docs/musicbay.md §3.2. const [musicbrainzConfig, setMusicbrainzConfig] = useState(null); const onPlayQueue = useCallback((tracks, startIndex) => { @@ -365,7 +361,6 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs, || (ack.audio_root ? [ack.audio_root] : []), photo: ack.photo_directories || ack.photo_roots || [], }); - setNodeSupportsAppOps(transport.supportsAppOps); setChatDirectory(ack.chat_directory || ''); setChatLinkPreview(ack.chat_link_preview !== false); setMusicbrainzConfig({ @@ -827,7 +822,6 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs, onScanSettings=${(s) => setScanSettings(s)} entries=${entries} nodeDirs=${nodeDirs} appSettings=${appSettings} - nodeSupportsAppOps=${nodeSupportsAppOps} ${/* The saving pane already knows what it asked for; this is so the page's own copy moves at the same time, rather than waiting for the ack it will not be handed (transport.js diff --git a/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js b/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js index dce5833..8e11088 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js @@ -40,7 +40,6 @@ import * as platform from './platform.js'; * transport — MeshBayTransport instance, or null when not connected * signFn — signing function for admin ops * nodeDetected — whether the loopback node API answers - * readOnly — suppress every edit control * onRootsChange — called after a change, to re-read the loopback list * onRefreshIndex — full index refresh. Not called after a root change: see * `run()` for why the node's own push is what settles it @@ -48,7 +47,7 @@ import * as platform from './platform.js'; * localRoots / onLocalRootsChange — the array, in "local" mode */ function SharedDirectoriesTable({ roots, groupId, transport, signFn, - nodeDetected: nodeAvail, readOnly, + nodeDetected: nodeAvail, onRootsChange, onRefreshIndex, mode = 'live', localRoots, onLocalRootsChange }) { @@ -116,7 +115,7 @@ function SharedDirectoriesTable({ roots, groupId, transport, signFn, // API is not (it is authorized by being on localhost with the run token). const overMnp = !isLocal && transport && transport.connected; const overLoopback = !isLocal && !overMnp && nodeAvail; - const canEdit = !readOnly && (isLocal || overMnp || overLoopback); + const canEdit = isLocal || overMnp || overLoopback; const rootUrl = (name, suffix = '') => '/api/groups/' + groupId + '/roots/' + encodeURIComponent(name) + suffix; @@ -401,7 +400,7 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, enabledApps, onEnabledApps, scanSettings, onScanSettings, entries, nodeDirs, - appSettings, nodeSupportsAppOps, + appSettings, onAppDirectories, onRefreshIndex, onPaired, onLeft }) { const [members, setMembers] = useState([]); @@ -693,14 +692,7 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, if (!transport || !transport.connected) { throw new Error(t('node.root_no_route')); } - // A node too old for the generic op still answers the three per-app - // messages that came before it, so an operator on one keeps the ability - // they had rather than being handed a control that times out. - if (transport.supportsAppOps) { - await transport.setAppDirectories(appKey, paths, adminSignFn); - } else { - await transport.setAppDirectoriesLegacy(appKey, paths, adminSignFn); - } + await transport.setAppDirectories(appKey, paths, adminSignFn); if (onAppDirectories) onAppDirectories(appKey, paths); }, [transportRef, adminSignFn, onAppDirectories]); @@ -898,19 +890,11 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, <p class="settings-hint">${t('settings_node.shared_directories_hint')}</p> ${!connected && nodeDetected && html` <p class="settings-hint">${t('settings_node.roots_offline_hint')}</p>`} - ${/* Read-only against a node that predates the root operations: - writable, removable, eject and plug have no older equivalent - to fall back to, and an unknown message type is dropped - unanswered — a thirty-second wait ending in a timeout, with - nothing on screen to say the node simply cannot do it. */''} - ${connected && !nodeSupportsAppOps && !nodeDetected && html` - <p class="settings-hint">${t('settings_node.roots_node_too_old')}</p>`} <${SharedDirectoriesTable} roots=${effectiveRoots} groupId=${groupId} transport=${transportRef.current} signFn=${adminSignFn} - readOnly=${connected && !nodeSupportsAppOps && !nodeDetected} nodeDetected=${nodeDetected} onRootsChange=${loadNodeInfo} onRefreshIndex=${onRefreshIndex} /> @@ -937,9 +921,7 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, disabled=${appsBusy} onChange=${() => toggleApp(app.key)} /> `}> - ${!nodeSupportsAppOps && app.key === 'chat' - ? html`<p class="settings-hint">${t('settings_node.app_node_too_old')}</p>` - : activeApps.includes(app.key) + ${activeApps.includes(app.key) ? html`<${app.Settings} roots=${effectiveRoots} dirs=${folderOptions} settings=${appSettings} 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 0f632e3..9d4aef8 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js @@ -896,8 +896,6 @@ export default { 'settings_app.tmdb_token_prompt': 'Registrieren Sie sich bei TMDB, um einen eigenen API-Schlüssel zu erzeugen.', 'settings_app.tmdb_token_link': 'Schlüssel holen', 'settings_node.roots_offline_hint': 'Nicht mit dem Node verbunden — Änderungen laufen über den lokalen Node und greifen beim nächsten Neuladen.', - 'settings_node.roots_node_too_old': 'Dieser Node ist älter als diese Seite: Er kann seine Verzeichnisse anzeigen, aber hier nicht ändern. Aktualisieren Sie ihn oder nutzen Sie die meshbay-node root-Befehle.', - 'settings_node.app_node_too_old': 'Dieser Node ist älter als diese Seite und hat für diese App noch keine Einstellung. Aktualisieren Sie ihn, um sie hier zu konfigurieren.', 'settings_node.directories_title': 'App-Verzeichnisse', 'settings_node.directories_hint': 'Freigegebene Ordner und welchen davon die Videos-, Musik- und Fotos-Apps als eigene(n) Einstiegspunkt(e) nutzen.', 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 cb7f4a0..8385caf 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js @@ -650,8 +650,6 @@ export default { 'settings_app.tmdb_token_prompt': 'Sign up on TMDB to generate your own API key.', 'settings_app.tmdb_token_link': 'Get a key', 'settings_node.roots_offline_hint': 'Not connected to the node — changes go through the local node instead, and take effect on its next reload.', - 'settings_node.roots_node_too_old': 'This node is older than this page: it can show its directories but not change them here. Update it, or use the meshbay-node root commands.', - 'settings_node.app_node_too_old': 'This node is older than this page and has no setting for this app yet. Update it to configure this here.', 'settings_node.directories_title': 'App directories', 'settings_node.directories_hint': 'Which shared folders the Videos, Music and Photos apps use as their entry point(s).', 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 fe1c13b..cb8f312 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js @@ -891,8 +891,6 @@ export default { 'settings_app.tmdb_token_prompt': 'Regístrate en TMDB para generar tu propia clave de API.', 'settings_app.tmdb_token_link': 'Obtener una clave', 'settings_node.roots_offline_hint': 'Sin conexión con el nodo: los cambios pasan por el nodo local y se aplican en su próxima recarga.', - 'settings_node.roots_node_too_old': 'Este nodo es más antiguo que esta página: puede mostrar sus directorios pero no cambiarlos aquí. Actualízalo o usa los comandos meshbay-node root.', - 'settings_node.app_node_too_old': 'Este nodo es más antiguo que esta página y aún no tiene ajustes para esta aplicación. Actualízalo para configurarla aquí.', 'settings_node.directories_title': 'Directorios de apps', 'settings_node.directories_hint': 'Carpetas compartidas, y cuál de ellas usan las apps de Vídeos, Música y Fotos como su(s) propio(s) punto(s) de entrada.', 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 ea3f60e..608da62 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js @@ -909,8 +909,6 @@ export default { 'settings_app.tmdb_token_prompt': 'Créez un compte TMDB pour générer votre propre clé d\'API.', 'settings_app.tmdb_token_link': 'Obtenir une clé', 'settings_node.roots_offline_hint': 'Non connecté au nœud — les changements passent par le nœud local et prennent effet à son prochain rechargement.', - 'settings_node.roots_node_too_old': 'Ce nœud est plus ancien que cette page : il peut afficher ses répertoires mais pas les modifier ici. Mettez-le à jour, ou utilisez les commandes meshbay-node root.', - 'settings_node.app_node_too_old': 'Ce nœud est plus ancien que cette page et n\'a pas encore de réglage pour cette application. Mettez-le à jour pour la configurer ici.', 'settings_node.directories_title': 'Répertoires des applications', 'settings_node.directories_hint': 'Quel(s) dossier(s) partagés les applications Vidéos, Musique et Photos utilisent comme leur(s) propre(s) point(s) d\'entrée.', 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 0687b25..be5fe29 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js @@ -905,8 +905,6 @@ export default { 'settings_app.tmdb_token_prompt': 'Registrati su TMDB per generare la tua chiave API.', 'settings_app.tmdb_token_link': 'Ottieni una chiave', 'settings_node.roots_offline_hint': 'Non connesso al nodo: le modifiche passano dal nodo locale e hanno effetto al successivo ricaricamento.', - 'settings_node.roots_node_too_old': 'Questo nodo è più vecchio di questa pagina: può mostrare le sue directory ma non modificarle qui. Aggiornalo, oppure usa i comandi meshbay-node root.', - 'settings_node.app_node_too_old': 'Questo nodo è più vecchio di questa pagina e non ha ancora impostazioni per questa applicazione. Aggiornalo per configurarla qui.', 'settings_node.directories_title': 'Directory delle app', 'settings_node.directories_hint': 'Cartelle condivise, e quale di esse le app Video, Musica e Foto usano come proprio/i punto/i di ingresso.', 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 a02e058..f2d94d9 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js @@ -889,8 +889,6 @@ export default { 'settings_app.tmdb_token_prompt': 'TMDB に登録して、自分の API キーを発行してください。', 'settings_app.tmdb_token_link': 'キーを取得', 'settings_node.roots_offline_hint': 'ノードに接続していません — 変更はローカルノード経由で行われ、次回の再読み込みで反映されます。', - 'settings_node.roots_node_too_old': 'このノードはこのページより古く、ディレクトリの表示はできますがここでの変更はできません。更新するか、meshbay-node root コマンドを使ってください。', - 'settings_node.app_node_too_old': 'このノードはこのページより古く、このアプリの設定をまだ持っていません。ここで設定するには更新してください。', 'settings_node.directories_title': 'アプリのディレクトリ', 'settings_node.directories_hint': '共有フォルダと、動画・音楽・写真の各アプリがそれぞれの起点として使用するフォルダです。', 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 0235d8a..f7f0775 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js @@ -907,8 +907,6 @@ export default { 'settings_app.tmdb_token_prompt': 'Meld u aan bij TMDB om uw eigen API-sleutel te maken.', 'settings_app.tmdb_token_link': 'Sleutel ophalen', 'settings_node.roots_offline_hint': 'Niet verbonden met de node — wijzigingen gaan via de lokale node en worden bij de volgende herlaadbeurt actief.', - 'settings_node.roots_node_too_old': 'Deze node is ouder dan deze pagina: hij kan zijn mappen tonen maar hier niet wijzigen. Werk hem bij, of gebruik de meshbay-node root-opdrachten.', - 'settings_node.app_node_too_old': 'Deze node is ouder dan deze pagina en heeft nog geen instelling voor deze app. Werk hem bij om die hier in te stellen.', 'settings_node.directories_title': 'App-mappen', 'settings_node.directories_hint': 'Gedeelde mappen, en welke daarvan de Video\'s-, Muziek- en Foto\'s-apps als eigen startpunt(en) gebruiken.', 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 af4a5bb..8c50790 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js @@ -933,8 +933,6 @@ export default { 'settings_app.tmdb_token_prompt': 'Zarejestruj się w TMDB, aby wygenerować własny klucz API.', 'settings_app.tmdb_token_link': 'Pobierz klucz', 'settings_node.roots_offline_hint': 'Brak połączenia z węzłem — zmiany przechodzą przez węzeł lokalny i zaczną działać po jego następnym przeładowaniu.', - 'settings_node.roots_node_too_old': 'Ten węzeł jest starszy niż ta strona: może pokazać swoje katalogi, ale nie zmieni ich tutaj. Zaktualizuj go albo użyj poleceń meshbay-node root.', - 'settings_node.app_node_too_old': 'Ten węzeł jest starszy niż ta strona i nie ma jeszcze ustawień tej aplikacji. Zaktualizuj go, aby skonfigurować ją tutaj.', 'settings_node.directories_title': 'Katalogi aplikacji', 'settings_node.directories_hint': 'Katalogi udostępnione oraz to, który z nich aplikacje Wideo, Muzyka i Zdjęcia traktują jako własny punkt (punkty) wejścia.', 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 f179c6f..8f903ba 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 @@ -892,8 +892,6 @@ export default { 'settings_app.tmdb_token_prompt': 'Cadastre-se no TMDB para gerar sua própria chave de API.', 'settings_app.tmdb_token_link': 'Obter uma chave', 'settings_node.roots_offline_hint': 'Sem conexão com o nó — as alterações passam pelo nó local e entram em vigor no próximo recarregamento.', - 'settings_node.roots_node_too_old': 'Este nó é mais antigo que esta página: ele pode mostrar seus diretórios, mas não alterá-los aqui. Atualize-o ou use os comandos meshbay-node root.', - 'settings_node.app_node_too_old': 'Este nó é mais antigo que esta página e ainda não tem configuração para este aplicativo. Atualize-o para configurá-lo aqui.', 'settings_node.directories_title': 'Diretórios de apps', 'settings_node.directories_hint': 'Pastas compartilhadas, e qual delas os apps Vídeos, Música e Fotos tratam como seu(s) próprio(s) ponto(s) de entrada.', 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 c89bbdc..c836de3 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 @@ -876,8 +876,6 @@ export default { 'settings_app.tmdb_token_prompt': '在 TMDB 注册以生成你自己的 API 密钥。', 'settings_app.tmdb_token_link': '获取密钥', 'settings_node.roots_offline_hint': '未连接到节点 — 变更将通过本地节点进行,并在其下次重新加载时生效。', - 'settings_node.roots_node_too_old': '该节点比本页面旧:它能显示自己的目录,但无法在此更改。请更新节点,或使用 meshbay-node root 命令。', - 'settings_node.app_node_too_old': '该节点比本页面旧,尚不支持此应用的设置。请更新节点后在此配置。', 'settings_node.directories_title': '应用目录', 'settings_node.directories_hint': '共享文件夹,以及“视频”“音乐”和“照片”应用各自使用哪个(些)作为入口。', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/transport.js b/packages/meshbay-hub/src/meshbay_hub/static/transport.js index 99e3fb9..2e3712c 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/transport.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/transport.js @@ -115,8 +115,7 @@ function _replayBroadcast(transport, msg) { } const ADMIN_OP_TYPES = new Set([ - 'tmdb_override', 'tmdb_rematch', 'tmdb_config', 'tmdb_enabled', 'video_root', 'audio_root', - 'photo_roots', + 'tmdb_override', 'tmdb_rematch', 'tmdb_config', 'tmdb_enabled', 'musicbrainz_enabled', 'file_delete', 'dir_delete', 'apps_enabled', 'set_scan_settings', 'member_revoke', 'root_add', 'root_remove', 'root_update', 'root_eject', 'root_plug', @@ -339,12 +338,6 @@ class Lease { this._wait = new Promise((resolve) => { this._granted = resolve; }); } - /** No slots on this node: behave as though one was granted at once. */ - _skip() { - this.state = 'granted'; - this._granted(); - } - _request() { // A closed channel is not a failure here, and must not throw: the transport // reconnects on its own, `_reopenTransfers` re-asks for every live lease @@ -417,7 +410,6 @@ class Lease { this.closed = true; clearTimeout(this._watchdog); this.transport._leases.delete(this.tr); - if (!this.transport.supportsTransferSlots) return; try { this.transport._send({ type: 'transfer_close', v: '0.1', tr: this.tr, reason }); @@ -530,48 +522,9 @@ class MeshBayTransport { /** The MNP version the connected node declared, or '' before a handshake. */ get nodeVersion() { return this._nodeVersion || ''; } - /** - * Whether this node hands out transfer slots. - * - * Read from the handshake ack rather than from the MNP version: the caps - * shipped before the version bump that will make leases compulsory, so for - * now a node either answers with `transfer_limits` or it predates all of - * this. A node that does not is asked for nothing and enforces nothing — - * every download behaves exactly as it did. - */ - get supportsTransferSlots() { return this._transferLimits !== null; } - /** This member's own caps in this group, or null when the node said nothing. */ get transferLimits() { return this._transferLimits; } - /** - * Whether the node speaks the per-root and per-app operations MNP 1.1 added: - * `root_update`/`root_eject`/`root_plug`, `app_directories`, - * `chat_directory`, `chat_link_preview`. - * - * An older node has no equivalent for the root ones at all, and answers the - * app ones through their three predecessors (`video_root`, `audio_root`, - * `photo_roots`). The caller chooses which; what it must not do is send a - * 1.1 message and wait, because an unknown type is logged and dropped. - */ - get supportsAppOps() { - const m = /^(\d+)\.(\d+)$/.exec(this._nodeVersion || ''); - if (!m) return false; - return (Number(m[1]) > 1) || (Number(m[1]) === 1 && Number(m[2]) >= 1); - } - /** - * Whether the node opens a sealed upload (MNP 2.0). - * - * A 1.x node reads `filename` and `data` off the message itself, finds - * neither — they are inside the seal — and answers "Missing filename or - * data", an error about the wrong thing that names no upload_id and so fails - * every upload in flight. Asked before sending rather than discovered after, - * for the same reason `supportsAppOps` is. - */ - get supportsSealedUpload() { - const m = /^(\d+)\.(\d+)$/.exec(this._nodeVersion || ''); - return !!m && Number(m[1]) >= 2; - } set onAppsEnabled(fn) { this._onAppsEnabled = fn; } set onAppDirectories(fn) { this._onAppDirectories = fn; } set onChatDirectory(fn) { this._onChatDirectory = fn; } @@ -867,11 +820,9 @@ class MeshBayTransport { // block, because everything below — the join, the proof, the sealed ack // — assumes both sides mean the same thing by each message. _checkNodeVersion(reply); - // Kept, not just checked. Several controls exist only on a node new - // enough to have them, and the alternative to asking is offering a - // button whose message an older node logs as unknown and never answers - // — a 30-second wait ending in a timeout, with nothing on screen to say - // the node simply cannot do this. + // Kept for diagnostics only. Nothing branches on it: the range check + // above is what decides whether these two can talk at all, and a peer it + // admits speaks every message in this file. this._nodeVersion = String(reply.v || ''); if (!window.MeshBayCrypto) { throw new Error('Node requires GEK proof but no crypto available'); @@ -1075,15 +1026,6 @@ class MeshBayTransport { Object.assign(ack, config); this._transferLimits = ack.transfer_limits || null; - // Tell the node which of this account's devices is on this connection. - // Deliberately after the ack, and gated on the node's own version rather - // than sent hopefully: a node that does not know the message answers - // nothing at all, which would leave a `device_hello` sitting in - // `_pending` for the full 30s — and the arrival-order fallback hands an - // unrouted reply to the *oldest* pending request, which right after a - // handshake is exactly this one. That is the routed-by-luck bug the chat - // ack comment above was written for; not repeating it. - this._nodeMnp = String(ack.v || ''); // From the *sealed* part of the ack: a forged epoch would have this // client sealing under a key the group has retired. this.chatEpoch = ack.chat_epoch || 0; @@ -1093,9 +1035,10 @@ class MeshBayTransport { this._chatKeysInFlight = null; this._roster = null; this._rosterInFlight = null; - // Not gated on a version any more: a node that reached this point speaks - // MNP 2.0, where identifying the device is what makes chat possible at - // all. `check_version` refused anything older before we got here. + // Tell the node which of this account's devices is on this connection, + // after the ack and unconditionally: a peer `check_version` admitted + // speaks this message, and identifying the device is what makes chat + // possible at all. await this._announceDevice().catch((e) => { console.warn('[MeshBay] device_hello failed — chat will not work:', e); }); @@ -1349,10 +1292,6 @@ class MeshBayTransport { openTransfer({ kind = 'download', bytes = 0, chunks = 0, onState = null } = {}) { const tr = _hex(crypto.getRandomValues(new Uint8Array(16))); const lease = new Lease(this, tr, kind, bytes, chunks, onState); - if (!this.supportsTransferSlots) { - lease._skip(); - return lease; - } this._leases.set(tr, lease); lease._request(); return lease; @@ -1360,7 +1299,6 @@ class MeshBayTransport { /** Re-ask for every live lease. Called after a reconnect. */ _reopenTransfers() { - if (!this.supportsTransferSlots) return; for (const lease of this._leases.values()) { // The node lost the lease with the session, so this is a fresh request // for the same `tr` — which the node treats as the same transfer rather @@ -1450,7 +1388,7 @@ class MeshBayTransport { } /** - * Correct a wrong automatic TMDB match. Signed like setVideoRoot/ + * Correct a wrong automatic TMDB match. Signed like * setTmdbConfig: it replaces what every member sees for a show/movie, * node-wide (media_cache is shared, not per-viewer) — an unsigned * override would let any member vandalize another show's metadata. @@ -1520,7 +1458,7 @@ class MeshBayTransport { * Whether TMDB lookups run for this group at all — per-group (2026-08-24, * used to be node-wide): a real media-library group and a test/demo group * on the same node need not share the decision to spend TMDB quota and - * make outbound requests. Signed like setVideoRoot — it decides whether + * make outbound requests. Signed like the rest — it decides whether * this group's members' Videos tab ever makes outbound TMDB traffic. */ async setTmdbEnabled(enabled, signFn) { @@ -1538,100 +1476,6 @@ class MeshBayTransport { return msg; } - /** - * Which folder (possibly a subfolder of a shared root) the Videos app - * treats as its entry point for this group. `path: ''` means the whole - * group index. Signed like setAppsEnabled — it decides what every - * member's Videos tab shows. - */ - async setVideoRoot(path, signFn) { - const clean = (path || '').replace(/^\/+|\/+$/g, ''); - const msg = await this._sendAndWait({ type: 'video_root', v: '0.5', path: clean }); - if (msg.type === 'error') throw new Error(msg.detail); - if (msg.type === 'admin_challenge') { - return this._authorizeAdminOp(msg, 'video_root', clean, signFn); - } - return msg; - } - - /** - * Same shape as setVideoRoot above — the Music app's own entry point. - */ - async setAudioRoot(path, signFn) { - const clean = (path || '').replace(/^\/+|\/+$/g, ''); - console.log('[MeshBay] setAudioRoot: sending request, path=', JSON.stringify(clean)); - const msg = await this._sendAndWait({ type: 'audio_root', v: '0.10', path: clean }); - console.log('[MeshBay] setAudioRoot: first reply =', msg); - if (msg.type === 'error') throw new Error(msg.detail); - if (msg.type === 'admin_challenge') { - return this._authorizeAdminOp(msg, 'audio_root', clean, signFn); - } - return msg; - } - - /** - * Which folder(s) the Photos app treats as its entry points for this - * group (docs/photos.md §2.1). Unlike setVideoRoot/setAudioRoot, `roots` - * is a whole set, replaced in one signed op — same shape as - * setAppsEnabled. The client normalizes the same way the node does - * (webrtc_server.py's `_do_photo_roots`: trim slashes, drop empties, - * dedupe, sort) so the subject built here matches byte-for-byte what the - * node signs the challenge against. - */ - async setPhotoRoots(roots, signFn) { - const clean = [...new Set( - (roots || []).map((r) => (r || '').replace(/^\/+|\/+$/g, '')).filter(Boolean), - )].sort(); - const msg = await this._sendAndWait({ type: 'photo_roots', v: '0.11', roots: clean }); - if (msg.type === 'error') throw new Error(msg.detail); - if (msg.type === 'admin_challenge') { - return this._authorizeAdminOp(msg, 'photo_roots', clean.join(','), signFn); - } - return msg; - } - - /** - * Point an application at folder(s) inside the group's shared directories. - * - * One method for every app, keyed by the app's registry name — the same - * generic op the node grew for the same reason (docs/refactor-groups.md - * §1.6). `setVideoRoot`, `setAudioRoot` and `setPhotoRoots` are still here - * and still work; nothing new should call them. - * - * The subject names the app as well as the paths, because an operator shown - * "Media/Films" alone cannot tell which application is about to be pointed - * at it, and two apps' challenges would otherwise be indistinguishable. - * Cleaned and sorted the same way the node does, so both sides build the - * same bytes to sign. - */ - /** - * The same instruction a node too old for `app_directories` understands. - * - * Videos, Music and Photos each had their own message before this, and they - * still work — so an operator on an un-upgraded node keeps the ability they - * had, rather than being handed a control that silently times out. Chat has - * no predecessor, which is why its settings are hidden rather than routed. - */ - async setAppDirectoriesLegacy(appKey, directories, signFn) { - const clean = [...new Set( - (directories || []).map((d) => (d || '').replace(/^\/+|\/+$/g, '')).filter(Boolean), - )].sort(); - if (appKey === 'photo') return this.setPhotoRoots(clean, signFn); - // One folder was all these two could carry. Sending several would store - // the first and silently drop the rest, so it is refused instead. - if (clean.length > 1) { - throw new Error( - 'This node is older than this page and can hold one folder per app. ' - + 'Update it, or choose a single folder.'); - } - const one = clean[0] || ''; - if (appKey === 'video') return this.setVideoRoot(one, signFn); - if (appKey === 'music') return this.setAudioRoot(one, signFn); - throw new Error( - 'This node is older than this page and cannot store this app\'s ' - + 'folders. Its operator has to update it.'); - } - async setAppDirectories(appKey, directories, signFn) { const clean = [...new Set( (directories || []).map((d) => (d || '').replace(/^\/+|\/+$/g, '')).filter(Boolean), @@ -2471,11 +2315,6 @@ class MeshBayTransport { throw new Error(`${file.name} is already being uploaded`); } if (!this._gekRaw) throw new Error('This group has no key on this device'); - if (!this.supportsSealedUpload) { - throw new Error( - 'This node is running an older MeshBay and cannot accept an upload ' - + 'from this page. Its operator has to update it.'); - } const C = window.MeshBayCrypto; const groupId = (this._connectArgs && this._connectArgs.groupId) || ''; this._inFlightUploads.add(file.name); @@ -3143,7 +2982,7 @@ class MeshBayTransport { // generic "oldest pending" fallback further down. Returns as soon as a // match resolves: this transport instance is the one that submitted // the request, and its own caller already updates local state from - // what *it* sent (setAppsEnabled/setVideoRoot/... callers all do + // what *it* sent (setAppsEnabled/setAppDirectories/... callers all do // `onX(next)` with their own local value, never by reading the ack), // so the broadcast-oriented per-type handlers below — there for every // *other* connected client learning the change — have nothing left to diff --git a/packages/meshbay-hub/src/meshbay_hub/static/video-app.js b/packages/meshbay-hub/src/meshbay_hub/static/video-app.js index f590d20..18b99af 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/video-app.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/video-app.js @@ -595,7 +595,7 @@ function SeasonMenu({ seasons, selected, selectedYear, onSelect }) { // ── operator: correct a wrong automatic TMDB match ────────────────────────── -// Same shape as group-settings.js's own signFn construction (setVideoRoot, +// Same shape as group-settings.js's own signFn construction (setAppDirectories, // setTmdbConfig, ...) — there is no group-wide "sign this" helper to share, // each caller builds one from the connection it already has. function buildSignFn(transportRef) { |