aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/transport.js
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/transport.js')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/transport.js109
1 files changed, 103 insertions, 6 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/transport.js b/packages/meshbay-hub/src/meshbay_hub/static/transport.js
index 4f6b656..dd39df8 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/transport.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/transport.js
@@ -106,6 +106,8 @@ class MeshBayTransport {
set onIndexDelta(fn) { this._onIndexDelta = fn; }
set onUploadPolicy(fn) { this._onUploadPolicy = fn; }
set onAppsEnabled(fn) { this._onAppsEnabled = fn; }
+ set onTmdbConfig(fn) { this._onTmdbConfig = fn; }
+ set onVideoRoot(fn) { this._onVideoRoot = fn; }
set onIndexProgress(fn) { this._onIndexProgress = fn; }
get sessionKeys() { return this._sessionKeys; }
@@ -465,6 +467,66 @@ class MeshBayTransport {
return msg;
}
+ /**
+ * TMDB metadata for one file (Videos app, docs/mediacenter.md §5.4).
+ * `path` is root+relpath, exactly what index_sync/index_delta already
+ * gave this browser — never a raw filesystem path constructed here.
+ * `confidence: 0` (no tmdb_id, no fields) means no confident match —
+ * the caller falls back to a thumbnail-only card (§4.1), not an error.
+ */
+ async fetchMediaMeta(path) {
+ const msg = await this._sendAndWait({ type: 'media_meta_req', v: '0.5', path });
+ if (msg.type === 'error') throw new Error(msg.detail);
+ return msg;
+ }
+
+ /**
+ * Turn TMDB lookups on/off node-wide, optionally set/clear a custom API
+ * token, and optionally set the language TMDB is queried in (e.g.
+ * "fr-FR") — one for the whole node, same reasoning as the token: one
+ * shared cache, not a per-viewer request. Signed like setAppsEnabled/
+ * setMemberUpload — an unsigned toggle would let any member turn on
+ * outbound third-party network traffic the operator never agreed to
+ * (docs/mediacenter.md §5.5, §8). `token: ''` explicitly clears a
+ * previously-set custom token; omit it (undefined/null), like
+ * `language`, to leave whatever is stored unchanged.
+ */
+ async setTmdbConfig(enabled, token, language, signFn) {
+ const msg = await this._sendAndWait({
+ type: 'tmdb_config', v: '0.5', enabled: Boolean(enabled),
+ token: token === undefined ? null : token,
+ language: language === undefined ? null : language,
+ });
+ if (msg.type === 'error') throw new Error(msg.detail);
+ if (msg.type === 'admin_challenge') {
+ // Must match the node's subject byte-for-byte (webrtc_server.py
+ // _do_tmdb_config): Python's f"{bool}" is "True"/"False", not JS's
+ // lowercase — and the token itself is never part of the subject
+ // (it would end up in the audit log in plaintext), only whether one
+ // was supplied. The language is not a secret, so it appears as-is.
+ const subject = `enabled=${enabled ? 'True' : 'False'},` +
+ `custom_token=${token ? 'yes' : 'no'},language=${language || 'default'}`;
+ return this._authorizeAdminOp(msg, 'tmdb_config', subject, signFn);
+ }
+ 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;
+ }
+
async fetchStreamSegment(fileId, segmentIndex, segmentDuration) {
const msg = await this._sendAndWait({
type: 'stream_seg',
@@ -1173,9 +1235,15 @@ class MeshBayTransport {
// *while* other traffic is in flight, so the fallback below would hand
// a pong to whatever was waiting — resolving a history request with a
// message that has no messages in it, and emptying the conversation.
+ // media_meta_req is the same shape as file_req: video-app.js fires
+ // one per visible poster-grid tile, several at a time — matching by
+ // arrival order handed one tile's TMDB result to a different tile
+ // whenever two responses reordered (reproduced live: which of two
+ // shows got the confident match flipped across reloads).
_key: obj.type === 'file_req'
? `chunk:${obj.file_id}:${obj.chunk_index}`
- : obj.type === 'ping' ? `ping:${obj.token}` : null,
+ : obj.type === 'ping' ? `ping:${obj.token}`
+ : obj.type === 'media_meta_req' ? `media_meta:${obj.path}` : null,
resolve: (msg) => { clearTimeout(timeout); this._pending.delete(id); resolve(msg); },
reject: (err) => { clearTimeout(timeout); this._pending.delete(id); reject(err); },
});
@@ -1276,6 +1344,23 @@ class MeshBayTransport {
this._onAppsEnabled(msg.apps || []);
}
+ // Node-wide (not per-group) — the operator changed whether TMDB is
+ // called at all, or supplied/cleared a custom token. `token_customized`
+ // only says whether one is set, never the token itself.
+ if (msg.type === 'tmdb_config_ack' && this._onTmdbConfig) {
+ this._onTmdbConfig({
+ enabled: Boolean(msg.enabled),
+ tokenCustomized: Boolean(msg.token_customized),
+ language: msg.language || '',
+ });
+ }
+
+ // Same shape: the operator changed which folder is the Videos app's
+ // entry point for this group.
+ if (msg.type === 'video_root_ack' && this._onVideoRoot) {
+ this._onVideoRoot(msg.path || '');
+ }
+
// 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
@@ -1316,11 +1401,11 @@ class MeshBayTransport {
return;
}
- // Incremental update — additions/deletions only, never the whole index.
- // Only ever arrives after the full index this browser already has (the
- // node's first push to a newly connected peer is always index_sync, see
- // daemon.py _broadcast_index_change), so there is always a base to
- // apply it to.
+ // Incremental update — additions/deletions/updates, never the whole
+ // index. Only ever arrives after the full index this browser already
+ // has (the node's first push to a newly connected peer is always
+ // index_sync, see daemon.py _broadcast_index_change), so there is
+ // always a base to apply it to.
if (msg.type === 'index_delta') {
if (this._onIndexDelta) this._onIndexDelta(msg);
return;
@@ -1364,6 +1449,18 @@ class MeshBayTransport {
return;
}
+ if (msg.type === 'media_meta_resp') {
+ const key = `media_meta:${msg.path}`;
+ for (const [, handler] of this._pending) {
+ if (handler._key === key) { handler.resolve(msg); return; }
+ }
+ // Nobody asked for this path any more (tile scrolled out and a fresh
+ // request superseded it, most likely) — must not fall through to the
+ // oldest pending request, which would hand a different tile's promise
+ // a TMDB result for a path it never asked about.
+ return;
+ }
+
// chat_hist_resp answers a `chat_hist` request, but under a different
// type string — unlike index_sync, which is asked for and answered under
// the same name, so the generic fallback below happens to work for it by