@tokenoftrust/storefront-runner 4.3.2 → 4.3.4
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/storefront-runner",
|
|
3
|
-
"version": "4.3.
|
|
3
|
+
"version": "4.3.4",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"description": "World-shareable storefront runner: multi-tenant renderer on Astro/Cloudflare. No control plane.",
|
|
6
6
|
"packageManager": "pnpm@11.9.0",
|
|
@@ -23,6 +23,8 @@ export interface CspOptions {
|
|
|
23
23
|
styleSrc?: readonly string[];
|
|
24
24
|
fontSrc?: readonly string[];
|
|
25
25
|
imgSrc?: readonly string[];
|
|
26
|
+
/** `<video>`/`<audio>` sources (`media-src`). */
|
|
27
|
+
mediaSrc?: readonly string[];
|
|
26
28
|
connectSrc?: readonly string[];
|
|
27
29
|
/** Sandboxed-widget origins. */
|
|
28
30
|
frameSrc?: readonly string[];
|
|
@@ -68,6 +70,9 @@ export function buildCsp(opts: CspOptions): string {
|
|
|
68
70
|
// so allowing `data:` here is the same tolerated relaxation as img-src.
|
|
69
71
|
"font-src": ["'self'", "data:", ...(opts.fontSrc ?? [])],
|
|
70
72
|
"img-src": ["'self'", "data:", ...(opts.imgSrc ?? [])],
|
|
73
|
+
// Without an explicit media-src, `<video>`/`<audio>` fall back to
|
|
74
|
+
// `default-src 'self'` and every cross-origin clip is refused.
|
|
75
|
+
"media-src": ["'self'", ...(opts.mediaSrc ?? [])],
|
|
71
76
|
"connect-src": ["'self'", ...(opts.connectSrc ?? [])],
|
|
72
77
|
"frame-src": ["'self'", ...(opts.frameSrc ?? [])],
|
|
73
78
|
};
|
|
@@ -98,6 +103,10 @@ export const STOREFRONT_CSP_ALLOWLIST = {
|
|
|
98
103
|
fontSrc: [],
|
|
99
104
|
// Product imagery is rehosted to R2 or served from remote CDNs.
|
|
100
105
|
imgSrc: ["https:"],
|
|
106
|
+
// Tenant video and audio stay at their source URLs (the tenant repo holds no
|
|
107
|
+
// media binaries), so media is granted over https exactly like imagery. Media
|
|
108
|
+
// cannot execute script, so this is the same tolerated relaxation as img-src.
|
|
109
|
+
mediaSrc: ["https:"],
|
|
101
110
|
connectSrc: [],
|
|
102
111
|
} as const;
|
|
103
112
|
|
|
@@ -155,9 +155,9 @@ function pythonJson(mode, pid) {
|
|
|
155
155
|
encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 3000,
|
|
156
156
|
}));
|
|
157
157
|
} catch (error) {
|
|
158
|
-
const
|
|
159
|
-
if (
|
|
160
|
-
if (
|
|
158
|
+
const stderr = Buffer.isBuffer(error?.stderr) ? error.stderr.toString("utf8") : String(error?.stderr ?? "");
|
|
159
|
+
if (stderr.includes("process_gone")) return null;
|
|
160
|
+
if (stderr.includes("process_children_unbounded")) throw fixedError("refresh_process_children_unbounded");
|
|
161
161
|
throw error;
|
|
162
162
|
}
|
|
163
163
|
}
|
|
@@ -183,6 +183,7 @@ function linuxIdentity(pid) {
|
|
|
183
183
|
function childPids(pid) {
|
|
184
184
|
if (process.platform === "darwin") {
|
|
185
185
|
const listed = pythonJson("children", pid);
|
|
186
|
+
if (listed === null) return [];
|
|
186
187
|
if (!Array.isArray(listed) || listed.some((child) => !Number.isInteger(child))) throw fixedError("refresh_process_children_unbounded");
|
|
187
188
|
return listed;
|
|
188
189
|
}
|
|
@@ -200,7 +201,8 @@ function childPids(pid) {
|
|
|
200
201
|
export function processIdentity(pid) {
|
|
201
202
|
if (!Number.isSafeInteger(pid) || pid < 2) throw fixedError("refresh_process_identity_invalid");
|
|
202
203
|
const info = process.platform === "darwin" ? pythonJson("identity", pid) : linuxIdentity(pid);
|
|
203
|
-
if (
|
|
204
|
+
if (info === null) return null;
|
|
205
|
+
if (!Array.isArray(info.argv) || info.argv.some((part) => typeof part !== "string")) throw fixedError("refresh_process_identity_invalid");
|
|
204
206
|
if (info.uid !== process.getuid?.()) throw fixedError("refresh_process_owner_mismatch");
|
|
205
207
|
let cwd;
|
|
206
208
|
if (process.platform === "darwin") {
|
|
@@ -320,9 +322,10 @@ function descendantsOf(pid) {
|
|
|
320
322
|
for (const child of childPids(parent)) {
|
|
321
323
|
visit(child);
|
|
322
324
|
try {
|
|
323
|
-
|
|
325
|
+
const identity = processIdentity(child);
|
|
326
|
+
if (identity) out.push(identity);
|
|
324
327
|
} catch (error) {
|
|
325
|
-
if (error?.message !== "refresh_process_identity_invalid") throw error;
|
|
328
|
+
if (error?.message !== "refresh_process_identity_invalid" && error?.message !== "refresh_process_changed") throw error;
|
|
326
329
|
}
|
|
327
330
|
}
|
|
328
331
|
};
|
|
@@ -341,6 +344,7 @@ export const realEffects = {
|
|
|
341
344
|
if (git(checkout, "status", "--porcelain", "--untracked-files=no")) throw fixedError("refresh_preview_tracked_dirty");
|
|
342
345
|
const pid = Number(receipt?.pid ?? options.pid);
|
|
343
346
|
const owned = processIdentity(pid);
|
|
347
|
+
if (!owned) throw fixedError("refresh_process_changed");
|
|
344
348
|
const app = realpathSync(join(checkout, "apps/storefront"));
|
|
345
349
|
const volume = privateIdentity(join(preview, "local-state"));
|
|
346
350
|
const environment = privateIdentity(join(preview, "apps/storefront/.dev.vars"), true);
|
|
@@ -449,9 +453,18 @@ export const realEffects = {
|
|
|
449
453
|
|| environment.dev !== prior.environment.dev || environment.ino !== prior.environment.ino || environment.uid !== prior.environment.uid
|
|
450
454
|
|| environment.mode !== prior.environment.mode || environment.digest !== prior.environment.digest) throw fixedError("refresh_private_state_changed");
|
|
451
455
|
},
|
|
452
|
-
async stop(owned) {
|
|
456
|
+
async stop(owned, hooks = {}) {
|
|
453
457
|
const descendants = descendantsOf(owned.pid);
|
|
454
|
-
|
|
458
|
+
// Test seam: kill a listed descendant after enumeration and before the signal loop.
|
|
459
|
+
if (typeof hooks.afterEnumerate === "function") await hooks.afterEnumerate(descendants);
|
|
460
|
+
for (const identity of [...descendants, owned]) {
|
|
461
|
+
try {
|
|
462
|
+
signalOwned(identity);
|
|
463
|
+
} catch (error) {
|
|
464
|
+
const gone = error?.message === "refresh_process_changed" || error?.code === "ESRCH";
|
|
465
|
+
if (identity === owned || !gone) throw error;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
455
468
|
for (let attempt = 0; attempt < 50; attempt += 1) {
|
|
456
469
|
let live = false;
|
|
457
470
|
for (const identity of [...descendants, owned]) {
|
|
@@ -484,17 +497,24 @@ export const realEffects = {
|
|
|
484
497
|
child.unref();
|
|
485
498
|
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
486
499
|
if (failed) {
|
|
487
|
-
try {
|
|
500
|
+
try {
|
|
501
|
+
const identity = child.pid ? processIdentity(child.pid) : null;
|
|
502
|
+
if (identity) await this.stop(identity);
|
|
503
|
+
} catch { /* An exited root cannot be signalled. */ }
|
|
488
504
|
throw fixedError("refresh_start_failed");
|
|
489
505
|
}
|
|
490
506
|
try {
|
|
491
507
|
const identity = processIdentity(child.pid);
|
|
508
|
+
if (!identity) throw fixedError("refresh_process_changed");
|
|
492
509
|
await fetchLocal("/health", previewBase());
|
|
493
510
|
return identity;
|
|
494
511
|
} catch { await delay(200); }
|
|
495
512
|
}
|
|
496
513
|
if (child.pid) {
|
|
497
|
-
|
|
514
|
+
const identity = processIdentity(child.pid);
|
|
515
|
+
if (identity) {
|
|
516
|
+
try { await this.stop(identity); } catch { throw fixedError("refresh_start_cleanup_failed"); }
|
|
517
|
+
}
|
|
498
518
|
}
|
|
499
519
|
throw fixedError("refresh_start_timeout");
|
|
500
520
|
},
|