2/*
3 * Make a `jupyter lite build` output cross-origin isolated:
4 * 1. copy coi-serviceworker.js into the output root, and
5 * 2. inject a <script> that registers it into every generated page's <head>.
6 *
7 * This synthesizes COOP/COEP headers client-side, so SharedArrayBuffer — and
8 * thus numbl's cooperative cell interruption (the Stop button) — works on hosts
9 * that can't set response headers, notably GitHub Pages. See
10 * demo/coi-serviceworker.js.
11 *
12 * The <script> src is relative to each page's depth, all pointing at the single
13 * root copy, so the service worker registers at the site root scope regardless
14 * of the base path (works under both `/` locally and `/<repo>/` on Pages).
15 *
16 * Usage: node demo/inject-coi.mjs <output-dir>
17 */
18import {
19 readFileSync,
20 writeFileSync,
21 copyFileSync,
22 readdirSync
23} from 'node:fs';
24import { join, relative, dirname, sep } from 'node:path';
25import { fileURLToPath } from 'node:url';
27const here = dirname(fileURLToPath(import.meta.url));
28const outDir = process.argv[2];
29if (!outDir) {
30 console.error('Usage: node demo/inject-coi.mjs <output-dir>');
31 process.exit(1);
32}
34const SW = 'coi-serviceworker.js';
35copyFileSync(join(here, SW), join(outDir, SW));
37function* htmlFiles(dir) {
38 for (const entry of readdirSync(dir, { withFileTypes: true })) {
39 const p = join(dir, entry.name);
40 if (entry.isDirectory()) {
41 yield* htmlFiles(p);
42 } else if (entry.isFile() && entry.name.endsWith('.html')) {
43 yield p;
44 }
45 }
46}
48let count = 0;
49for (const file of htmlFiles(outDir)) {
50 const html = readFileSync(file, 'utf8');
51 if (html.includes(SW)) {
52 continue; // already injected
53 }
54 const depth = relative(outDir, dirname(file)).split(sep).filter(Boolean)
55 .length;
56 const prefix = depth === 0 ? './' : '../'.repeat(depth);
57 const tag = `<script src="${prefix}${SW}"></script>`;
58 // Inject as the first thing in <head> so isolation is gained (and the
59 // one-time reload happens) before the heavy app bundle loads.
60 const replaced = html.replace(/<head(\s[^>]*)?>/i, m => `${m}\n ${tag}`);
61 if (replaced === html) {
62 continue; // no <head>, skip
63 }
64 writeFileSync(file, replaced);
65 count++;
66}
67console.log(`coi: injected ${SW} into ${count} page(s) under ${outDir}`);