summaryrefslogtreecommitdiffstats
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.js29
1 files changed, 29 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 7528a92..065ccc0 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/transport.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/transport.js
@@ -74,6 +74,7 @@ function _aborted() {
// one.
const ADMIN_OP_TYPES = new Set([
'tmdb_override', 'tmdb_config', 'tmdb_enabled', 'video_root', 'audio_root',
+ 'photo_roots',
'musicbrainz_config', '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',
@@ -133,6 +134,7 @@ class MeshBayTransport {
set onTmdbEnabled(fn) { this._onTmdbEnabled = fn; }
set onVideoRoot(fn) { this._onVideoRoot = fn; }
set onAudioRoot(fn) { this._onAudioRoot = fn; }
+ set onPhotoRoots(fn) { this._onPhotoRoots = fn; }
set onMusicbrainzConfig(fn) { this._onMusicbrainzConfig = fn; }
set onMusicbrainzEnabled(fn) { this._onMusicbrainzEnabled = fn; }
set onIndexProgress(fn) { this._onIndexProgress = fn; }
@@ -645,6 +647,27 @@ class MeshBayTransport {
}
/**
+ * 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;
+ }
+
+ /**
* MusicBrainz metadata for one track's path (Music app, docs/musicbay.md
* §4.3) — same shape as fetchMediaMeta, minus a season/episode concept:
* album-level (release), resolved from the track's own artist/album
@@ -1645,6 +1668,12 @@ class MeshBayTransport {
this._onAudioRoot(msg.path || '');
}
+ // Same shape: the operator replaced the Photos app's whole root set
+ // for this group (docs/photos.md §2.1).
+ if (msg.type === 'photo_roots_ack' && this._onPhotoRoots) {
+ this._onPhotoRoots(msg.roots || []);
+ }
+
// Node-wide, like tmdb_config_ack above — no token equivalent to hide,
// only whether a contact string is configured (docs/musicbay.md §3.2).
if (msg.type === 'musicbrainz_config_ack' && this._onMusicbrainzConfig) {