@volter/tabnode 0.5.35 → 0.5.37

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/__sw__.js CHANGED
@@ -578,7 +578,7 @@ async function virtualContext(event) {
578
578
  // virtual request asks for its port, so a restart does not send the
579
579
  // preview's own requests to the network.
580
580
  if (hosts.size === 0 && ((client && client.frameType === 'nested') || (event.request.mode === 'navigate' && (event.request.destination === 'iframe' || event.request.destination === 'frame')))) {
581
- await askForInit();
581
+ await askForInit(event.clientId);
582
582
  }
583
583
  const contextUrl = event.request.referrer || (client ? client.url : '');
584
584
  if (contextUrl) {
@@ -607,9 +607,16 @@ function ownPath(pathname) {
607
607
  return pathname.length > 1 && pathname.endsWith('/') ? pathname.slice(0, -1) : pathname;
608
608
  }
609
609
 
610
- /** Asks every window for the page's registrations and waits for them, as a request waits for its port. */
611
- async function askForInit() {
612
- const allClients = await self.clients.matchAll({ type: 'window' });
610
+ /**
611
+ * Asks every window for the page's registrations and waits for them, as a
612
+ * request waits for its port. Only another window can answer: a page's own
613
+ * document and scripts come before its `init`, and a frame being opened is no
614
+ * window yet, so with no other window each waited out the whole ask (a page
615
+ * opened on an origin whose worker had restarted took 5 s per request).
616
+ */
617
+ async function askForInit(requesterId) {
618
+ const allClients = (await self.clients.matchAll({ type: 'window' })).filter((client) => client.id !== requesterId);
619
+ if (allClients.length === 0) return;
613
620
  for (const client of allClients) client.postMessage({ type: 'sw-needs-init' });
614
621
  await new Promise((resolve) => {
615
622
  const check = setInterval(() => { if (hosts.size > 0) { clearInterval(check); resolve(); } }, 50);
@@ -739,7 +746,7 @@ async function withDocumentPrelude(event, response) {
739
746
  */
740
747
  async function handleVirtualRequest(request, port, path, rooted = false) {
741
748
  try {
742
- if (hosts.size === 0) await askForInit();
749
+ if (hosts.size === 0) await askForInit(requestEvents.get(request)?.clientId);
743
750
  const host = await hostForRequest(request, port);
744
751
  const registration = host ? host.flowControlledPorts.get(port) : undefined;
745
752
  // Build headers object
@@ -796,6 +803,16 @@ async function handleVirtualRequest(request, port, path, rooted = false) {
796
803
  bodyBase64Length: response.bodyBase64?.length,
797
804
  });
798
805
 
806
+ // The frame's embedding policy goes on every answer, an empty one too:
807
+ // the page is cross-origin isolated, and a document without these
808
+ // headers is blocked in its frame (an empty 200 lost the preview).
809
+ const respHeaders = new Headers(response.headers);
810
+ respHeaders.set('Cross-Origin-Embedder-Policy', 'credentialless');
811
+ respHeaders.set('Cross-Origin-Opener-Policy', 'same-origin');
812
+ respHeaders.set('Cross-Origin-Resource-Policy', 'cross-origin');
813
+ // Remove any headers that might block iframe loading
814
+ respHeaders.delete('X-Frame-Options');
815
+
799
816
  // Decode base64 body and create response
800
817
  let finalResponse;
801
818
  if (response.bodyBase64 && response.bodyBase64.length > 0) {
@@ -803,19 +820,13 @@ async function handleVirtualRequest(request, port, path, rooted = false) {
803
820
  const bytes = base64ToBytes(response.bodyBase64);
804
821
  DEBUG && console.log('[SW] Decoded body length:', bytes.length);
805
822
 
806
- // Use Blob to ensure proper body handling
807
- const blob = new Blob([bytes], { type: response.headers['Content-Type'] || 'application/octet-stream' });
823
+ // Use Blob to ensure proper body handling. Its type is the server's,
824
+ // or none: a document the server gave no type is the browser's to
825
+ // sniff, and an invented octet-stream made a page a download.
826
+ const declared = new Headers(response.headers).get('content-type');
827
+ const blob = new Blob([bytes], declared ? { type: declared } : {});
808
828
  DEBUG && console.log('[SW] Created blob size:', blob.size);
809
829
 
810
- // Merge response headers with CORP/COEP headers to allow iframe embedding
811
- // The parent page has COEP: credentialless, so we need matching headers
812
- const respHeaders = new Headers(response.headers);
813
- respHeaders.set('Cross-Origin-Embedder-Policy', 'credentialless');
814
- respHeaders.set('Cross-Origin-Opener-Policy', 'same-origin');
815
- respHeaders.set('Cross-Origin-Resource-Policy', 'cross-origin');
816
- // Remove any headers that might block iframe loading
817
- respHeaders.delete('X-Frame-Options');
818
-
819
830
  finalResponse = new Response(blob, {
820
831
  status: response.statusCode,
821
832
  statusText: response.statusMessage,
@@ -832,7 +843,7 @@ async function handleVirtualRequest(request, port, path, rooted = false) {
832
843
  finalResponse = new Response(null, {
833
844
  status: response.statusCode,
834
845
  statusText: response.statusMessage,
835
- headers: response.headers,
846
+ headers: respHeaders,
836
847
  });
837
848
  }
838
849
 
@@ -73561,10 +73561,7 @@ function createNodeResolver(options) {
73561
73561
  const packageName = packageNameOf2(specifier);
73562
73562
  const packageRoot = `${nodeModules}/${packageName}`;
73563
73563
  const pkg = isDirectory(packageRoot) ? manifest(packageRoot) : null;
73564
- if (pkg && pkg.exports !== void 0 && pkg.exports !== null) {
73565
- const found = loadPackageExports(packageRoot, pkg, specifier, packageName);
73566
- if (found) return found;
73567
- }
73564
+ if (pkg && pkg.exports !== void 0 && pkg.exports !== null) return loadPackageExports(packageRoot, pkg, specifier, packageName);
73568
73565
  const path2 = `${nodeModules}/${specifier}`;
73569
73566
  return loadAsFile(path2) ?? loadAsDirectory(path2);
73570
73567
  };
@@ -78595,27 +78592,27 @@ function __substrateFollowAwaits(code2) {
78595
78592
  };
78596
78593
  const isFunction = (node) => node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
78597
78594
  const visit = (node, framed, parent) => {
78595
+ const leading = [];
78598
78596
  if (isFunction(node)) {
78599
78597
  framed = node.async && node.body.type === "BlockStatement";
78600
78598
  if (framed) {
78601
78599
  const directives = node.body.body.filter((statement) => statement.directive !== void 0);
78602
- const at3 = directives.length ? directives[directives.length - 1].end : node.body.start + 1;
78603
- insert(at3, FOLLOW_TAKE);
78600
+ leading.push([directives.length ? directives[directives.length - 1].end : node.body.start + 1, FOLLOW_TAKE]);
78604
78601
  }
78605
78602
  } else if (node.type === "AwaitExpression") {
78606
78603
  insert(node.start, framed ? FOLLOW_RESUME_FRAMED : FOLLOW_RESUME_TAKEN);
78607
78604
  insert(node.end, `${FOLLOW_MARK})`);
78608
78605
  } else if (framed && node.type === "CatchClause") {
78609
- insert(node.body.start + 1, FOLLOW_RESTORE);
78606
+ leading.push([node.body.start + 1, FOLLOW_RESTORE]);
78610
78607
  } else if (framed && node.type === "TryStatement" && node.finalizer) {
78611
- insert(node.finalizer.start + 1, FOLLOW_RESTORE);
78608
+ leading.push([node.finalizer.start + 1, FOLLOW_RESTORE]);
78612
78609
  } else if (framed && node.type === "ForOfStatement" && node.await) {
78613
78610
  const whole = (parent == null ? void 0 : parent.type) === "LabeledStatement" ? parent : node;
78614
78611
  insert(whole.start, `{${FOLLOW_MARK}`);
78615
78612
  insert(whole.end, `;${FOLLOW_RESTORE}${FOLLOW_MARK}}`);
78616
- if (node.body.type === "BlockStatement") insert(node.body.start + 1, FOLLOW_RESTORE);
78613
+ if (node.body.type === "BlockStatement") leading.push([node.body.start + 1, FOLLOW_RESTORE]);
78617
78614
  else {
78618
- insert(node.body.start, `{${FOLLOW_MARK}${FOLLOW_RESTORE}`);
78615
+ leading.push([node.body.start, `{${FOLLOW_MARK}${FOLLOW_RESTORE}`]);
78619
78616
  insert(node.body.end, `${FOLLOW_MARK}}`);
78620
78617
  }
78621
78618
  }
@@ -78627,6 +78624,7 @@ function __substrateFollowAwaits(code2) {
78627
78624
  for (const item of child) if (item && typeof item.type === "string") visit(item, framed, node);
78628
78625
  } else if (typeof child.type === "string") visit(child, framed, node);
78629
78626
  }
78627
+ for (const [at3, text] of leading) insert(at3, text);
78630
78628
  };
78631
78629
  visit(ast, false, void 0);
78632
78630
  return edits.length ? applyReplacements(code2, edits) : code2;
@@ -78666,7 +78664,7 @@ function __substratePrepareBody(rawCode, resolvedPath, format2, process2) {
78666
78664
  }
78667
78665
  return __substrateFollowAwaits(code2);
78668
78666
  }
78669
- const PREPARED_MODULES_DIR = "/.tabnode/prepared";
78667
+ const PREPARED_MODULES_DIR = "/opt/.tabnode/prepared";
78670
78668
  const PREPARED_MODULES_FORMAT = "tabnode-prepared-2";
78671
78669
  function preparedModuleKey(rawCode, resolvedPath) {
78672
78670
  var _a3;
package/dist/index.mjs CHANGED
@@ -33156,10 +33156,7 @@ function createNodeResolver(options) {
33156
33156
  const packageName = packageNameOf2(specifier);
33157
33157
  const packageRoot = `${nodeModules}/${packageName}`;
33158
33158
  const pkg = isDirectory(packageRoot) ? manifest(packageRoot) : null;
33159
- if (pkg && pkg.exports !== void 0 && pkg.exports !== null) {
33160
- const found = loadPackageExports(packageRoot, pkg, specifier, packageName);
33161
- if (found) return found;
33162
- }
33159
+ if (pkg && pkg.exports !== void 0 && pkg.exports !== null) return loadPackageExports(packageRoot, pkg, specifier, packageName);
33163
33160
  const path2 = `${nodeModules}/${specifier}`;
33164
33161
  return loadAsFile(path2) ?? loadAsDirectory(path2);
33165
33162
  };
@@ -38253,27 +38250,27 @@ function __substrateFollowAwaits(code2) {
38253
38250
  };
38254
38251
  const isFunction = (node) => node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
38255
38252
  const visit = (node, framed, parent) => {
38253
+ const leading = [];
38256
38254
  if (isFunction(node)) {
38257
38255
  framed = node.async && node.body.type === "BlockStatement";
38258
38256
  if (framed) {
38259
38257
  const directives = node.body.body.filter((statement) => statement.directive !== void 0);
38260
- const at2 = directives.length ? directives[directives.length - 1].end : node.body.start + 1;
38261
- insert(at2, FOLLOW_TAKE);
38258
+ leading.push([directives.length ? directives[directives.length - 1].end : node.body.start + 1, FOLLOW_TAKE]);
38262
38259
  }
38263
38260
  } else if (node.type === "AwaitExpression") {
38264
38261
  insert(node.start, framed ? FOLLOW_RESUME_FRAMED : FOLLOW_RESUME_TAKEN);
38265
38262
  insert(node.end, `${FOLLOW_MARK})`);
38266
38263
  } else if (framed && node.type === "CatchClause") {
38267
- insert(node.body.start + 1, FOLLOW_RESTORE);
38264
+ leading.push([node.body.start + 1, FOLLOW_RESTORE]);
38268
38265
  } else if (framed && node.type === "TryStatement" && node.finalizer) {
38269
- insert(node.finalizer.start + 1, FOLLOW_RESTORE);
38266
+ leading.push([node.finalizer.start + 1, FOLLOW_RESTORE]);
38270
38267
  } else if (framed && node.type === "ForOfStatement" && node.await) {
38271
38268
  const whole = (parent == null ? void 0 : parent.type) === "LabeledStatement" ? parent : node;
38272
38269
  insert(whole.start, `{${FOLLOW_MARK}`);
38273
38270
  insert(whole.end, `;${FOLLOW_RESTORE}${FOLLOW_MARK}}`);
38274
- if (node.body.type === "BlockStatement") insert(node.body.start + 1, FOLLOW_RESTORE);
38271
+ if (node.body.type === "BlockStatement") leading.push([node.body.start + 1, FOLLOW_RESTORE]);
38275
38272
  else {
38276
- insert(node.body.start, `{${FOLLOW_MARK}${FOLLOW_RESTORE}`);
38273
+ leading.push([node.body.start, `{${FOLLOW_MARK}${FOLLOW_RESTORE}`]);
38277
38274
  insert(node.body.end, `${FOLLOW_MARK}}`);
38278
38275
  }
38279
38276
  }
@@ -38285,6 +38282,7 @@ function __substrateFollowAwaits(code2) {
38285
38282
  for (const item of child) if (item && typeof item.type === "string") visit(item, framed, node);
38286
38283
  } else if (typeof child.type === "string") visit(child, framed, node);
38287
38284
  }
38285
+ for (const [at2, text] of leading) insert(at2, text);
38288
38286
  };
38289
38287
  visit(ast, false, void 0);
38290
38288
  return edits.length ? applyReplacements(code2, edits) : code2;
@@ -38324,7 +38322,7 @@ function __substratePrepareBody(rawCode, resolvedPath, format2, process2) {
38324
38322
  }
38325
38323
  return __substrateFollowAwaits(code2);
38326
38324
  }
38327
- const PREPARED_MODULES_DIR = "/.tabnode/prepared";
38325
+ const PREPARED_MODULES_DIR = "/opt/.tabnode/prepared";
38328
38326
  const PREPARED_MODULES_FORMAT = "tabnode-prepared-2";
38329
38327
  function preparedModuleKey(rawCode, resolvedPath) {
38330
38328
  var _a2;
@@ -40293,7 +40291,7 @@ class WorkerRuntime {
40293
40291
  this.worker = new Worker(
40294
40292
  new URL(
40295
40293
  /* @vite-ignore */
40296
- "/assets/runtime-worker-DEgP1CYS.js",
40294
+ "/assets/runtime-worker-BloPQBYA.js",
40297
40295
  import.meta.url
40298
40296
  ),
40299
40297
  { type: "module" }
package/dist/runtime.d.ts CHANGED
@@ -16,8 +16,11 @@ export { pendingGuestTimers, stopGuestTimers } from './guest-timers';
16
16
  * from, in this directory of the tab's filesystem, and the loader takes it in
17
17
  * place of the passes when the file it read hashes to one. A body the loader
18
18
  * prepares for a file the image has none for is kept there the same way.
19
+ * Under `/opt`, a root a tab's store keeps, so a host can link the image's
20
+ * bodies there once from a store they share instead of writing each into
21
+ * every process's memory.
19
22
  */
20
- export declare const PREPARED_MODULES_DIR = "/.tabnode/prepared";
23
+ export declare const PREPARED_MODULES_DIR = "/opt/.tabnode/prepared";
21
24
  /** The name a prepared body goes under: the hash of the file as read, and how it is compiled. Undefined for a file no body is prepared for. */
22
25
  export declare function preparedModuleKey(rawCode: string, resolvedPath: string): string | undefined;
23
26
  /** The body the loader would compile for this file, with no load hooks and no type stripping: what the image carries. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/tabnode",
3
- "version": "0.5.35",
3
+ "version": "0.5.37",
4
4
  "description": "Node's own library, in a browser tab.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -158,14 +158,17 @@ export function createNodeResolver(options: NodeResolverOptions): NodeResolver {
158
158
  return parts[0]!.startsWith("@") && parts.length > 1 ? `${parts[0]}/${parts[1]}` : parts[0]!;
159
159
  };
160
160
  // LOAD_NODE_MODULES from one directory: exports, else the package's own entry or a file under it.
161
+ // A package with `exports` is answered by the map alone, as Node answers it:
162
+ // a subpath the map does not name, or names to a missing file, does not
163
+ // resolve. Falling through to the file under the package made
164
+ // `require.resolve("<pkg>/package.json")` succeed where Node throws, and a
165
+ // program that asks that question to decide what it does (Vite config that
166
+ // excludes a package it cannot resolve) decided differently in the tab.
161
167
  const loadFromNodeModules = (nodeModules: string, specifier: string): string | null => {
162
168
  const packageName = packageNameOf(specifier);
163
169
  const packageRoot = `${nodeModules}/${packageName}`;
164
170
  const pkg = isDirectory(packageRoot) ? manifest(packageRoot) : null;
165
- if (pkg && pkg.exports !== undefined && pkg.exports !== null) {
166
- const found = loadPackageExports(packageRoot, pkg, specifier, packageName);
167
- if (found) return found;
168
- }
171
+ if (pkg && pkg.exports !== undefined && pkg.exports !== null) return loadPackageExports(packageRoot, pkg, specifier, packageName);
169
172
  const path = `${nodeModules}/${specifier}`;
170
173
  return loadAsFile(path) ?? loadAsDirectory(path);
171
174
  };
package/src/runtime.ts CHANGED
@@ -468,28 +468,35 @@ function __substrateFollowAwaits(code: string): string {
468
468
  const isFunction = (node: any): boolean => node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression';
469
469
  // `framed`: the nearest function is async and holds the frame in ASYNC_FRAME.
470
470
  const visit = (node: any, framed: boolean, parent: any): void => {
471
+ // What opens a block (the frame's take in an async body, the restore
472
+ // at the start of a catch, a finally or a for-await body) goes before
473
+ // anything in it: where the block's first statement is an `await`
474
+ // (`async function f(){await g()}`, `finally{await h()}`, as minified code
475
+ // writes them), both share an offset, and edits at one offset land in
476
+ // reverse of the order they were made. So these are made after the
477
+ // block's own edits, and land first.
478
+ const leading: Array<[number, string]> = [];
471
479
  if (isFunction(node)) {
472
480
  framed = node.async && node.body.type === 'BlockStatement';
473
481
  if (framed) {
474
482
  const directives = node.body.body.filter((statement: any) => statement.directive !== undefined);
475
- const at = directives.length ? directives[directives.length - 1].end : node.body.start + 1;
476
- insert(at, FOLLOW_TAKE);
483
+ leading.push([directives.length ? directives[directives.length - 1].end : node.body.start + 1, FOLLOW_TAKE]);
477
484
  }
478
485
  } else if (node.type === 'AwaitExpression') {
479
486
  insert(node.start, framed ? FOLLOW_RESUME_FRAMED : FOLLOW_RESUME_TAKEN);
480
487
  insert(node.end, `${FOLLOW_MARK})`);
481
488
  } else if (framed && node.type === 'CatchClause') {
482
- insert(node.body.start + 1, FOLLOW_RESTORE);
489
+ leading.push([node.body.start + 1, FOLLOW_RESTORE]);
483
490
  } else if (framed && node.type === 'TryStatement' && node.finalizer) {
484
- insert(node.finalizer.start + 1, FOLLOW_RESTORE);
491
+ leading.push([node.finalizer.start + 1, FOLLOW_RESTORE]);
485
492
  } else if (framed && node.type === 'ForOfStatement' && node.await) {
486
493
  // The loop's own wrap first: where its end is the body's, an insertion
487
494
  // made later lands before it, so the body's brace closes inside.
488
495
  const whole = parent?.type === 'LabeledStatement' ? parent : node;
489
496
  insert(whole.start, `{${FOLLOW_MARK}`);
490
497
  insert(whole.end, `;${FOLLOW_RESTORE}${FOLLOW_MARK}}`);
491
- if (node.body.type === 'BlockStatement') insert(node.body.start + 1, FOLLOW_RESTORE);
492
- else { insert(node.body.start, `{${FOLLOW_MARK}${FOLLOW_RESTORE}`); insert(node.body.end, `${FOLLOW_MARK}}`); }
498
+ if (node.body.type === 'BlockStatement') leading.push([node.body.start + 1, FOLLOW_RESTORE]);
499
+ else { leading.push([node.body.start, `{${FOLLOW_MARK}${FOLLOW_RESTORE}`]); insert(node.body.end, `${FOLLOW_MARK}}`); }
493
500
  }
494
501
  for (const key of Object.keys(node)) {
495
502
  if (key === 'type' || key === 'start' || key === 'end' || key === 'loc' || key === 'range') continue;
@@ -499,6 +506,7 @@ function __substrateFollowAwaits(code: string): string {
499
506
  for (const item of child) if (item && typeof item.type === 'string') visit(item, framed, node);
500
507
  } else if (typeof child.type === 'string') visit(child, framed, node);
501
508
  }
509
+ for (const [at, text] of leading) insert(at, text);
502
510
  };
503
511
  visit(ast, false, undefined);
504
512
  return edits.length ? applyReplacements(code, edits) : code;
@@ -589,8 +597,11 @@ function __substratePrepareBody(rawCode: string, resolvedPath: string, format: s
589
597
  * from, in this directory of the tab's filesystem, and the loader takes it in
590
598
  * place of the passes when the file it read hashes to one. A body the loader
591
599
  * prepares for a file the image has none for is kept there the same way.
600
+ * Under `/opt`, a root a tab's store keeps, so a host can link the image's
601
+ * bodies there once from a store they share instead of writing each into
602
+ * every process's memory.
592
603
  */
593
- export const PREPARED_MODULES_DIR = '/.tabnode/prepared';
604
+ export const PREPARED_MODULES_DIR = '/opt/.tabnode/prepared';
594
605
  const PREPARED_MODULES_FORMAT = 'tabnode-prepared-2';
595
606
  /** The name a prepared body goes under: the hash of the file as read, and how it is compiled. Undefined for a file no body is prepared for. */
596
607
  export function preparedModuleKey(rawCode: string, resolvedPath: string): string | undefined {