diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-06 16:05:39 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-06 16:05:39 +0200 |
| commit | e76e27868b30a2b00b1ba42dd8e7ee6071e0c0d7 (patch) | |
| tree | 3c23483207d77adf3b67f290b67a57ce185fb1a1 /packages/meshbay-hub/src/meshbay_hub/static/transport.js | |
| parent | 0ed078c92cabab1dab0f70f321562032ea549ce6 (diff) | |
| download | meshbay-e76e27868b30a2b00b1ba42dd8e7ee6071e0c0d7.tar.gz | |
feat: groups refactor Phase 1 — root RO/RW model + shared directories UI
Replace the upload boolean with per-root writable/removable/ejected flags.
Backend: new ops (update_root, eject_root, plug_root), MNP 1.1 protocol
messages, live RootSet updates so API always reflects current state, CLI
root subcommand (add/remove/set/list/eject/plug).
Frontend: SharedDirectoriesTable with optimistic toggle switches, eject/plug
in Files and Settings, upload gated on root.writable, ejected-root filtering
in all media apps, updated Create Group wizard, 10-locale i18n.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/transport.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/transport.js | 62 |
1 files changed, 57 insertions, 5 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/transport.js b/packages/meshbay-hub/src/meshbay_hub/static/transport.js index ea1e70a..482574f 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/transport.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/transport.js @@ -77,7 +77,8 @@ const ADMIN_OP_TYPES = new Set([ 'photo_roots', 'musicbrainz_enabled', 'file_delete', 'dir_delete', 'member_upload', 'apps_enabled', 'set_scan_settings', 'member_revoke', - 'root_add', 'root_remove', 'member_unpin', 'gek_rotate', 'group_attach', + 'root_add', 'root_remove', 'root_update', 'root_eject', 'root_plug', + 'member_unpin', 'gek_rotate', 'group_attach', 'group_detach', 'invite_create', ]); @@ -217,7 +218,7 @@ window.addEventListener('hashchange', () => { // The `v: '0.1'` on every other message in this file is the historical value // and is read by nothing; it is left alone deliberately. The range is // negotiated once, at the start, not restated per message. -const MNP_V = '1.0'; +const MNP_V = '1.1'; const MNP_V_MIN = '1.0'; // Codes a NODE sends us, in its own vocabulary (meshbay_common/handshake.py's @@ -325,6 +326,7 @@ class MeshBayTransport { set onIndexSync(fn) { this._onIndexSync = fn; } set onIndexDelta(fn) { this._onIndexDelta = fn; } set onUploadPolicy(fn) { this._onUploadPolicy = fn; } + set onRootsChanged(fn) { this._onRootsChanged = fn; } set onAppsEnabled(fn) { this._onAppsEnabled = fn; } set onTmdbConfig(fn) { this._onTmdbConfig = fn; } set onTmdbEnabled(fn) { this._onTmdbEnabled = fn; } @@ -1452,11 +1454,12 @@ class MeshBayTransport { return msg; } - async addRoot(groupId, path, { name, kind, upload } = {}, signFn) { + async addRoot(groupId, path, { name, kind, writable, removable } = {}, signFn) { const msg = await this._sendAndWait({ - type: 'root_add', v: '0.1', + type: 'root_add', v: '1.1', group_id: groupId, path, - name: name || '', kind: kind || 'generic', upload: !!upload, + name: name || '', kind: kind || 'generic', + writable: !!writable, removable: !!removable, }); if (msg.type === 'error') throw new Error(msg.detail); if (msg.type === 'admin_challenge') { @@ -1477,6 +1480,48 @@ class MeshBayTransport { return msg; } + async updateRoot(groupId, rootName, { writable, removable } = {}, signFn) { + const updates = []; + if (writable !== undefined) updates.push(`rw=${writable ? 'on' : 'off'}`); + if (removable !== undefined) updates.push(`rem=${removable ? 'on' : 'off'}`); + const subject = updates.length ? `${rootName}:${updates.join(',')}` : rootName; + const msg = await this._sendAndWait({ + type: 'root_update', v: '1.1', + group_id: groupId, root_name: rootName, + ...(writable !== undefined && { writable }), + ...(removable !== undefined && { removable }), + }); + if (msg.type === 'error') throw new Error(msg.detail); + if (msg.type === 'admin_challenge') { + return this._authorizeAdminOp(msg, 'root_update', subject, signFn); + } + return msg; + } + + async ejectRoot(groupId, rootName, signFn) { + const msg = await this._sendAndWait({ + type: 'root_eject', v: '1.1', + group_id: groupId, root_name: rootName, + }); + if (msg.type === 'error') throw new Error(msg.detail); + if (msg.type === 'admin_challenge') { + return this._authorizeAdminOp(msg, 'root_eject', rootName, signFn); + } + return msg; + } + + async plugRoot(groupId, rootName, signFn) { + const msg = await this._sendAndWait({ + type: 'root_plug', v: '1.1', + group_id: groupId, root_name: rootName, + }); + if (msg.type === 'error') throw new Error(msg.detail); + if (msg.type === 'admin_challenge') { + return this._authorizeAdminOp(msg, 'root_plug', rootName, signFn); + } + return msg; + } + async unpinMember(userId, signFn) { const msg = await this._sendAndWait({ type: 'member_unpin', v: '0.1', user_id: userId, @@ -2277,6 +2322,13 @@ class MeshBayTransport { this._onMusicbrainzEnabled(Boolean(msg.enabled)); } + // A root's writable/removable flags changed, or a root was ejected/plugged. + // Broadcast to all peers so everyone sees the change. + if ((msg.type === 'root_update_ack' || msg.type === 'root_eject_ack' + || msg.type === 'root_plug_ack') && this._onRootsChanged) { + this._onRootsChanged(msg); + } + // The operator's node is scanning — never the entries themselves, just // enough to animate a presence dot. Pushed periodically while it runs, // plus once more on the transition back to idle (daemon.py |