diff options
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/transport.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/transport.js | 53 |
1 files changed, 53 insertions, 0 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/transport.js b/packages/meshbay-hub/src/meshbay_hub/static/transport.js index 785b926..32f8539 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/transport.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/transport.js @@ -335,6 +335,25 @@ class MeshBayTransport { set onIndexDelta(fn) { this._onIndexDelta = fn; } set onUploadPolicy(fn) { this._onUploadPolicy = fn; } set onRootsChanged(fn) { this._onRootsChanged = fn; } + + /** The MNP version the connected node declared, or '' before a handshake. */ + get nodeVersion() { return this._nodeVersion || ''; } + + /** + * 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); + } set onAppsEnabled(fn) { this._onAppsEnabled = fn; } set onAppDirectories(fn) { this._onAppDirectories = fn; } set onChatDirectory(fn) { this._onChatDirectory = fn; } @@ -600,6 +619,12 @@ 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. + this._nodeVersion = String(reply.v || ''); if (!window.MeshBayCrypto) { throw new Error('Node requires GEK proof but no crypto available'); } @@ -1195,6 +1220,34 @@ class MeshBayTransport { * 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), |