conductor-remote 1.97.0 → 1.98.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.html CHANGED
@@ -25,8 +25,8 @@
25
25
  <title>Conductor Remote</title>
26
26
  <!-- Runs before the module bundle so it can catch a stale shell that fails to boot. -->
27
27
  <script src="/self-heal.js"></script>
28
- <script type="module" crossorigin src="/assets/index-BoLYPDBp.js"></script>
29
- <link rel="stylesheet" crossorigin href="/assets/index-CGDfc1aU.css">
28
+ <script type="module" crossorigin src="/assets/index-CWS0Qyzc.js"></script>
29
+ <link rel="stylesheet" crossorigin href="/assets/index-0UqQA-X1.css">
30
30
  <link rel="manifest" href="/manifest.webmanifest"></head>
31
31
  <body>
32
32
  <div id="root"></div>
package/dist/sw.js CHANGED
@@ -1 +1 @@
1
- if(!self.define){let e,s={};const i=(i,n)=>(i=new URL(i+".js",n).href,s[i]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=i,e.onload=s,document.head.appendChild(e)}else e=i,importScripts(i),s()}).then(()=>{let e=s[i];if(!e)throw new Error(`Module ${i} didn’t register its module`);return e}));self.define=(n,r)=>{const o=e||("document"in self?document.currentScript.src:"")||location.href;if(s[o])return;let c={};const l=e=>i(e,o),t={module:{uri:o},exports:c,require:l};s[o]=Promise.all(n.map(e=>t[e]||l(e))).then(e=>(r(...e),c))}}define(["./workbox-9c191d2f"],function(e){"use strict";importScripts("/push-sw.js"),self.addEventListener("message",e=>{e.data&&"SKIP_WAITING"===e.data.type&&self.skipWaiting()}),e.clientsClaim(),e.precacheAndRoute([{url:"self-heal.js",revision:"49bd63adb25a09341f8d2610e8bd3c76"},{url:"push-sw.js",revision:"e7ef44deca46c0539e6ff7bba5eb815e"},{url:"index.html",revision:"e6f874d91bc832cf0c1e4456929aaa09"},{url:"assets/workbox-window.prod.es5-BBnX5xw4.js",revision:null},{url:"assets/index-CGDfc1aU.css",revision:null},{url:"assets/index-BoLYPDBp.js",revision:null},{url:"apple-touch-icon.png",revision:"1127bb396b4648add53dce3f22c92aee"},{url:"icon-192.png",revision:"c5e01ac58768627e18ee7b8b6a9239ef"},{url:"icon-512.png",revision:"a40638c55e310312457a621c9a0002c8"},{url:"icon-maskable-512.png",revision:"a9b0d962686287452216492cd2247499"},{url:"icon.svg",revision:"c1aee186821798733dd477e69a0ef243"},{url:"manifest.webmanifest",revision:"cf88fbc5755108a7fe0616fa160a8a15"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("/index.html"),{denylist:[/^\/api\//]}))});
1
+ if(!self.define){let e,s={};const i=(i,n)=>(i=new URL(i+".js",n).href,s[i]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=i,e.onload=s,document.head.appendChild(e)}else e=i,importScripts(i),s()}).then(()=>{let e=s[i];if(!e)throw new Error(`Module ${i} didn’t register its module`);return e}));self.define=(n,r)=>{const o=e||("document"in self?document.currentScript.src:"")||location.href;if(s[o])return;let l={};const c=e=>i(e,o),t={module:{uri:o},exports:l,require:c};s[o]=Promise.all(n.map(e=>t[e]||c(e))).then(e=>(r(...e),l))}}define(["./workbox-9c191d2f"],function(e){"use strict";importScripts("/push-sw.js"),self.addEventListener("message",e=>{e.data&&"SKIP_WAITING"===e.data.type&&self.skipWaiting()}),e.clientsClaim(),e.precacheAndRoute([{url:"self-heal.js",revision:"49bd63adb25a09341f8d2610e8bd3c76"},{url:"push-sw.js",revision:"e7ef44deca46c0539e6ff7bba5eb815e"},{url:"index.html",revision:"51edeb5f6522b16d55023377170a1bec"},{url:"assets/workbox-window.prod.es5-BBnX5xw4.js",revision:null},{url:"assets/index-CWS0Qyzc.js",revision:null},{url:"assets/index-0UqQA-X1.css",revision:null},{url:"apple-touch-icon.png",revision:"1127bb396b4648add53dce3f22c92aee"},{url:"icon-192.png",revision:"c5e01ac58768627e18ee7b8b6a9239ef"},{url:"icon-512.png",revision:"a40638c55e310312457a621c9a0002c8"},{url:"icon-maskable-512.png",revision:"a9b0d962686287452216492cd2247499"},{url:"icon.svg",revision:"c1aee186821798733dd477e69a0ef243"},{url:"manifest.webmanifest",revision:"cf88fbc5755108a7fe0616fa160a8a15"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("/index.html"),{denylist:[/^\/api\//]}))});
@@ -33,8 +33,10 @@ const exec = promisify(execFile);
33
33
  // Keep the receipt additive and version-1-readable: an installed relay and a
34
34
  // source `yarn dev` relay can briefly share this file during an update.
35
35
  const STORE_VERSION = 1;
36
+ const PREVIEW_ADVERTISEMENT_VERSION = 1;
36
37
  const PORT_WAIT_MS = 15_000;
37
38
  const PORT_SNAPSHOT_TTL_MS = 5000;
39
+ const MAX_PREVIEW_ADVERTISEMENT_BYTES = 64 * 1024;
38
40
  function recordKey(workspaceId, targetPort) {
39
41
  return `${workspaceId}:${targetPort}`;
40
42
  }
@@ -323,6 +325,7 @@ function forwardUrl(record, previewUrl) {
323
325
  }
324
326
  export class DevServerController {
325
327
  storeFile;
328
+ advertisementDir;
326
329
  bin;
327
330
  host;
328
331
  records = new Map();
@@ -332,12 +335,15 @@ export class DevServerController {
332
335
  actions = new Map();
333
336
  constructor(storeFile = path.join(stateDir(), 'dev-forwards.json')) {
334
337
  this.storeFile = storeFile;
338
+ this.advertisementDir = path.join(path.dirname(storeFile), 'dev-preview-advertisements');
335
339
  this.bin = tailscaleBin();
336
340
  this.host = this.bin ? magicDnsName(this.bin) : null;
337
341
  this.load();
338
342
  }
339
343
  /**
340
- * Let a running application publish the exact URLs it wants opened.
344
+ * Let a running application publish the exact URLs it wants opened. A source
345
+ * relay and the installed relay are separate processes, so the advertisement
346
+ * is also written to a private workspace-scoped manifest they can both read.
341
347
  *
342
348
  * The producer owns every path, query parameter and fragment — including any
343
349
  * bootstrap credential. The forwarding layer only accepts loopback HTTP URLs
@@ -348,9 +354,73 @@ export class DevServerController {
348
354
  return;
349
355
  if (!previews.length) {
350
356
  this.advertisedPreviews.delete(workspaceId);
357
+ this.removeOwnPreviewAdvertisement(workspaceId);
351
358
  return;
352
359
  }
353
- this.advertisedPreviews.set(workspaceId, previews.slice(0, 10).map(preview => ({ name: preview.name, url: preview.url })));
360
+ const published = previews.slice(0, 10).map(preview => ({ name: preview.name, url: preview.url }));
361
+ this.advertisedPreviews.set(workspaceId, published);
362
+ const advertisement = {
363
+ version: PREVIEW_ADVERTISEMENT_VERSION,
364
+ workspaceId,
365
+ ownerPid: process.pid,
366
+ previews: published
367
+ };
368
+ const file = this.previewAdvertisementFile(workspaceId);
369
+ const temporary = `${file}.${process.pid}.tmp`;
370
+ try {
371
+ fs.mkdirSync(this.advertisementDir, { recursive: true, mode: 0o700 });
372
+ fs.writeFileSync(temporary, `${JSON.stringify(advertisement, null, 2)}\n`, { mode: 0o600 });
373
+ fs.chmodSync(temporary, 0o600);
374
+ fs.renameSync(temporary, file);
375
+ }
376
+ catch (err) {
377
+ try {
378
+ fs.unlinkSync(temporary);
379
+ }
380
+ catch { }
381
+ console.warn(`⚠ could not publish dev-server previews (${err instanceof Error ? err.message : err})`);
382
+ }
383
+ }
384
+ previewAdvertisementFile(workspaceId) {
385
+ const key = crypto.createHash('sha256').update(workspaceId).digest('hex');
386
+ return path.join(this.advertisementDir, `${key}.json`);
387
+ }
388
+ readPreviewAdvertisement(workspaceId) {
389
+ try {
390
+ const file = this.previewAdvertisementFile(workspaceId);
391
+ if (fs.statSync(file).size > MAX_PREVIEW_ADVERTISEMENT_BYTES)
392
+ return [];
393
+ const value = JSON.parse(fs.readFileSync(file, 'utf8'));
394
+ if (value.version !== PREVIEW_ADVERTISEMENT_VERSION ||
395
+ value.workspaceId !== workspaceId ||
396
+ !Number.isInteger(value.ownerPid) ||
397
+ (value.ownerPid ?? 0) <= 0 ||
398
+ !processAlive(value.ownerPid) ||
399
+ !Array.isArray(value.previews))
400
+ return [];
401
+ return value.previews
402
+ .flatMap(preview => preview &&
403
+ typeof preview.url === 'string' &&
404
+ preview.url.length <= 8192 &&
405
+ (preview.name === undefined || (typeof preview.name === 'string' && preview.name.length <= 256))
406
+ ? [{ name: preview.name, url: preview.url }]
407
+ : [])
408
+ .slice(0, 10);
409
+ }
410
+ catch {
411
+ return [];
412
+ }
413
+ }
414
+ removeOwnPreviewAdvertisement(workspaceId) {
415
+ const file = this.previewAdvertisementFile(workspaceId);
416
+ try {
417
+ const value = JSON.parse(fs.readFileSync(file, 'utf8'));
418
+ if (value.workspaceId === workspaceId && value.ownerPid === process.pid)
419
+ fs.unlinkSync(file);
420
+ }
421
+ catch {
422
+ // Missing, malformed or owned by another producer: there is nothing of ours to remove.
423
+ }
354
424
  }
355
425
  refreshTailscale() {
356
426
  this.bin ??= tailscaleBin();
@@ -410,7 +480,7 @@ export class DevServerController {
410
480
  const configured = resolvePreviewTargets(previewUrlSettings(workspace), basePort);
411
481
  if (configured.length)
412
482
  return configured;
413
- const advertised = resolvePreviewTargets(this.advertisedPreviews.get(workspace.id) ?? [], basePort);
483
+ const advertised = resolvePreviewTargets(this.advertisedPreviews.get(workspace.id) ?? this.readPreviewAdvertisement(workspace.id), basePort);
414
484
  if (advertised.length)
415
485
  return advertised;
416
486
  const detected = resolvePreviewTargets(detectedUrls.map(url => ({ url })), basePort);
@@ -55,8 +55,9 @@ if (cfg.devWebPort !== undefined && process.env.CONDUCTOR_WORKSPACE_ID) {
55
55
  const preview = new URL(`http://localhost:${cfg.devWebPort}/`);
56
56
  preview.hash = new URLSearchParams({ token: cfg.token }).toString();
57
57
  // `yarn dev` is itself one application behind Conductor's Run button. Publish
58
- // the same canonical URL printed below; DevServerController treats it exactly
59
- // like any other full preview URL and has no knowledge of relay authentication.
58
+ // the same canonical URL printed below. The private live advertisement crosses
59
+ // into the installed relay that normally serves the phone; its forwarding code
60
+ // still treats this exactly like any full URL and knows nothing about the token.
60
61
  devServers.advertisePreviewUrls(process.env.CONDUCTOR_WORKSPACE_ID, [{ name: 'Conductor Remote', url: preview.href }]);
61
62
  }
62
63
  const STAGED_ATTACHMENTS_DIR = path.join(stateDir(), 'attachment-staging');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conductor-remote",
3
- "version": "1.97.0",
3
+ "version": "1.98.0",
4
4
  "type": "module",
5
5
  "packageManager": "yarn@4.15.0",
6
6
  "description": "Phone control panel for local Conductor agents. Reads ride SQLite + git; prompts ride Conductor's own dispatch path.",