aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-client/src/main.js
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-client/src/main.js')
-rw-r--r--packages/meshbay-client/src/main.js52
1 files changed, 35 insertions, 17 deletions
diff --git a/packages/meshbay-client/src/main.js b/packages/meshbay-client/src/main.js
index fa4f3ec..f582b57 100644
--- a/packages/meshbay-client/src/main.js
+++ b/packages/meshbay-client/src/main.js
@@ -1269,11 +1269,11 @@ function registerBridge() {
//
// The renderer never sees the session token. It names an operation and this
// process executes it — the same pattern as hub:fetch. The token is read
- // from the daemon's data directory, cached for the lifetime of this process,
- // and never exposed through the preload.
+ // from the daemon's data directory on every call and never exposed through
+ // the preload. Not cached: the daemon writes a new one each run and deletes
+ // it on stop, so a cached copy answered 401 after every node restart, and an
+ // operation asked before any page had called detect() had none at all.
- let _nodeToken = null;
- let _nodePort = 18000;
let _nodePairingCode = null;
function nodeConfigPath() {
@@ -1305,16 +1305,20 @@ function registerBridge() {
}
}
+ function nodeEndpoint() {
+ const nc = readNodeConfig();
+ const token = readNodeToken(nc ? nc.dataDir : meshbayDataDir());
+ return token ? { token, port: nc ? nc.uiPort : 18000 } : null;
+ }
+
handle('node:detect', async () => {
const nc = readNodeConfig();
if (!nc) return { detected: false, configured: false };
const token = readNodeToken(nc.dataDir);
if (!token) return { detected: false, configured: true };
- _nodeToken = token;
- _nodePort = nc.uiPort;
try {
const r = await fetch(
- `http://127.0.0.1:${_nodePort}/api/status?t=${_nodeToken}`,
+ `http://127.0.0.1:${nc.uiPort}/api/status?t=${token}`,
{ signal: AbortSignal.timeout(3000) });
if (!r.ok) return { detected: false, configured: true };
const status = await r.json();
@@ -1889,8 +1893,6 @@ function registerBridge() {
const READY = ['running', 'waiting_for_node_key', 'waiting_for_account',
'waiting_for_hub', 'starting'];
if (!READY.includes(status.status)) return null;
- _nodeToken = token;
- _nodePort = port;
return { pk_node_ed25519: status.pk_node_ed25519 || '', status: status.status,
version: status.version || '' };
} catch { return null; }
@@ -2235,10 +2237,11 @@ function registerBridge() {
handle('node:op', async (_e, name, args) => {
const op = Object.hasOwn(NODE_OPS, String(name)) ? NODE_OPS[String(name)] : null;
if (!op) throw new Error(`Refused: unknown node operation ${String(name)}`);
- if (!_nodeToken) throw new Error('Node not detected');
+ const endpoint = nodeEndpoint();
+ if (!endpoint) throw new Error('Node not detected');
const [method, apiPath, body] = await op(anObject(args));
const sep = apiPath.includes('?') ? '&' : '?';
- const url = `http://127.0.0.1:${_nodePort}${apiPath}${sep}t=${_nodeToken}`;
+ const url = `http://127.0.0.1:${endpoint.port}${apiPath}${sep}t=${endpoint.token}`;
const init = { method };
if (body !== undefined && body !== null) {
init.headers = { 'Content-Type': 'application/json' };
@@ -2333,16 +2336,31 @@ function registerBridge() {
handle('cast:devices', async () => castChromecast.devices());
- handle('cast:chromecast:connect', async (_e, { deviceId, mediaUrl, subtitle }) => {
- return castChromecast.connect(deviceId, mediaUrl,
- subtitle === undefined ? castRelay.subtitle : subtitle);
+ // A whole file for the receiver — a photo, a music track — in three steps,
+ // so that a large one never crosses in a single message.
+ handle('cast:file:begin', async (_e, f) => castRelay.beginFile(f || {}));
+ handle('cast:file:write', async (_e, data) => castRelay.writeFile(Buffer.from(data)));
+ handle('cast:file:end', async () => castRelay.endFile());
+
+ // The relay's file is loaded as what the relay says it is, anything else as
+ // the stream; the page does not get to say which.
+ const loadOf = (mediaUrl, subtitle) => (
+ mediaUrl && mediaUrl === castRelay.fileUrl
+ ? { subtitle: null, file: { contentType: castRelay.fileType, meta: castRelay.fileMeta } }
+ : { subtitle: subtitle === undefined ? castRelay.subtitle : subtitle, file: null });
+
+ handle('cast:chromecast:connect', async (_e, { deviceId, mediaUrl, subtitle, startAt }) => {
+ const l = loadOf(mediaUrl, subtitle);
+ return castChromecast.connect(deviceId, mediaUrl, l.subtitle, l.file, l.file ? startAt : 0);
});
- handle('cast:chromecast:reload', async (_e, { mediaUrl, subtitle }) => {
- return castChromecast.reload(mediaUrl,
- subtitle === undefined ? castRelay.subtitle : subtitle);
+ handle('cast:chromecast:reload', async (_e, { mediaUrl, subtitle, startAt }) => {
+ const l = loadOf(mediaUrl, subtitle);
+ return castChromecast.reload(mediaUrl, l.subtitle, l.file, l.file ? startAt : 0);
});
+ handle('cast:chromecast:seek', async (_e, seconds) => castChromecast.seek(seconds));
+
// The player becomes a remote while casting: these drive the receiver.
handle('cast:chromecast:pause', async () => castChromecast.pause());
handle('cast:chromecast:play', async () => castChromecast.play());