@tokenoftrust/storefront-runner 2.0.30 → 2.0.32
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/apps/storefront/astro.config.mjs +2 -0
- package/apps/storefront/env.d.ts +0 -2
- package/apps/storefront/integrations/google-measurement-sandbox-asset.mjs +97 -0
- package/apps/storefront/public/platform/google-measurement/v2/entry.mjs +13 -0
- package/apps/storefront/src/layouts/Layout.astro +3 -48
- package/apps/storefront/src/lib/analytics/budgets.json +0 -1
- package/apps/storefront/src/lib/analytics/index.ts +1 -4
- package/apps/storefront/src/lib/analytics/webVitals.ts +42 -4
- package/apps/storefront/src/lib/integration/tenantDeclarations.ts +1 -1
- package/apps/storefront/src/lib/monitoring/manifest.ts +1 -2
- package/package.json +1 -1
- package/packages/public-runtime/package.json +2 -1
- package/packages/public-runtime/src/csp.ts +2 -2
- package/packages/public-runtime/src/integration/consent-broker.ts +370 -13
- package/packages/public-runtime/src/integration/csp-compiler.ts +3 -6
- package/packages/public-runtime/src/integration/events.ts +5 -5
- package/packages/public-runtime/src/integration/index.ts +1 -0
- package/packages/public-runtime/src/integration/interaction-tracking-gate.ts +1 -2
- package/packages/public-runtime/src/integration/lifecycle-boot.ts +28 -14
- package/packages/public-runtime/src/integration/lifecycle-host-entry.ts +2 -0
- package/packages/public-runtime/src/integration/lifecycle-runtime.ts +256 -22
- package/packages/public-runtime/src/integration/managed-analytics-value-safety.ts +9 -0
- package/packages/public-runtime/src/integration/native-ga4-sandbox-asset.d.mts +2 -0
- package/packages/public-runtime/src/integration/native-ga4-sandbox-asset.mjs +3 -0
- package/packages/public-runtime/src/integration/native-ga4-sandbox-entry.ts +207 -0
- package/packages/public-runtime/src/integration/native-ga4-sandbox.ts +168 -0
- package/packages/public-runtime/src/integration/native-ga4.ts +111 -215
- package/packages/public-runtime/src/integration/platform-adapter-runtime.ts +18 -9
- package/packages/public-runtime/src/integration/platform-adapters.ts +20 -9
- package/packages/public-runtime/src/integration/render-phases.ts +28 -2
- package/packages/public-runtime/src/integration/version.ts +2 -2
- package/packages/public-runtime/tests/fixtures/integration/commerce-event.example.json +1 -1
- package/packages/public-runtime/tests/fixtures/integration/commerce-order-event.example.json +1 -1
- package/packages/public-runtime/tests/fixtures/integration/integration-manifest-v2.example.json +1 -1
- package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/manifest.json +1 -1
- package/scripts/tenant/lint-embeds.mjs +0 -8
- package/apps/storefront/public/platform/google-measurement/v1/entry.mjs +0 -19
|
@@ -59,6 +59,7 @@ import tailwindcss from "@tailwindcss/vite";
|
|
|
59
59
|
import { tenantHotReload } from "./dev-plugins/tenant-hot-reload.mjs";
|
|
60
60
|
import { materializeGuard } from "./integrations/materialize-guard.mjs";
|
|
61
61
|
import { lifecycleHostAsset } from "./integrations/lifecycle-host-asset.mjs";
|
|
62
|
+
import { googleMeasurementSandboxAsset } from "./integrations/google-measurement-sandbox-asset.mjs";
|
|
62
63
|
|
|
63
64
|
// SSR on Cloudflare Workers. `output: 'server'` because every response is
|
|
64
65
|
// tenant-resolved at the edge from the Host header (see src/middleware).
|
|
@@ -99,6 +100,7 @@ export default defineConfig({
|
|
|
99
100
|
// stable same-origin URL so BOTH document mounts load the same boot — the raw
|
|
100
101
|
// marketing mount has no bundler to hoist a module import through.
|
|
101
102
|
lifecycleHostAsset(),
|
|
103
|
+
googleMeasurementSandboxAsset(),
|
|
102
104
|
preact({ compat: true }),
|
|
103
105
|
// Astro's file-based router ignores ANY path segment starting with "_" (see
|
|
104
106
|
// `astro/dist/core/routing/create-manifest.js`), so a page literally named
|
package/apps/storefront/env.d.ts
CHANGED
|
@@ -61,7 +61,6 @@ interface CloudflareEnv {
|
|
|
61
61
|
TOT_API_KEY?: string;
|
|
62
62
|
TOT_SECRET_KEY?: string;
|
|
63
63
|
TOT_ACCESS_TOKEN?: string;
|
|
64
|
-
PUBLIC_CF_ANALYTICS_TOKEN?: string;
|
|
65
64
|
/** HMAC secret for signing/verifying customization preview tokens. */
|
|
66
65
|
CUSTOMIZATION_PREVIEW_SECRET?: string;
|
|
67
66
|
/**
|
|
@@ -260,7 +259,6 @@ interface ImportMetaEnv {
|
|
|
260
259
|
readonly TOT_API_KEY?: string;
|
|
261
260
|
readonly TOT_SECRET_KEY?: string;
|
|
262
261
|
readonly TOT_ACCESS_TOKEN?: string;
|
|
263
|
-
readonly PUBLIC_CF_ANALYTICS_TOKEN?: string;
|
|
264
262
|
readonly CUSTOMIZATION_PREVIEW_SECRET?: string;
|
|
265
263
|
}
|
|
266
264
|
interface ImportMeta {
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/** Emit the reviewed GA4 runtime as one import-free, CSP-hashed static document. */
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import { dirname } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import {
|
|
8
|
+
NATIVE_GA4_SANDBOX_ASSET_PATH,
|
|
9
|
+
} from "@tot/public-runtime/integration/native-ga4-sandbox-asset";
|
|
10
|
+
|
|
11
|
+
const SANDBOX_ENTRY = fileURLToPath(new URL(
|
|
12
|
+
"../../../packages/public-runtime/src/integration/native-ga4-sandbox-entry.ts",
|
|
13
|
+
import.meta.url,
|
|
14
|
+
));
|
|
15
|
+
|
|
16
|
+
export async function bundleGoogleMeasurementSandboxScript() {
|
|
17
|
+
const require = createRequire(import.meta.url);
|
|
18
|
+
const esbuild = createRequire(require.resolve("astro/package.json"))("esbuild");
|
|
19
|
+
const result = await esbuild.build({
|
|
20
|
+
entryPoints: [SANDBOX_ENTRY],
|
|
21
|
+
bundle: true,
|
|
22
|
+
write: false,
|
|
23
|
+
metafile: true,
|
|
24
|
+
format: "iife",
|
|
25
|
+
platform: "browser",
|
|
26
|
+
target: "es2022",
|
|
27
|
+
minify: true,
|
|
28
|
+
treeShaking: true,
|
|
29
|
+
legalComments: "none",
|
|
30
|
+
sourcemap: false,
|
|
31
|
+
logLevel: "silent",
|
|
32
|
+
});
|
|
33
|
+
if (result.outputFiles.length !== 1) {
|
|
34
|
+
throw new Error(`GA4 sandbox emitted ${result.outputFiles.length} files; expected one.`);
|
|
35
|
+
}
|
|
36
|
+
for (const output of Object.values(result.metafile.outputs)) {
|
|
37
|
+
if ((output.imports ?? []).length > 0) throw new Error("GA4 sandbox must be import-free.");
|
|
38
|
+
}
|
|
39
|
+
const code = result.outputFiles[0].text.trim();
|
|
40
|
+
if (/<\/script/i.test(code)) throw new Error("GA4 sandbox bundle contains a closing script token.");
|
|
41
|
+
return code;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function googleMeasurementSandboxCsp(code) {
|
|
45
|
+
const hash = createHash("sha256").update(code).digest("base64");
|
|
46
|
+
const script = `'sha256-${hash}' https://www.googletagmanager.com`;
|
|
47
|
+
return [
|
|
48
|
+
"default-src 'none'",
|
|
49
|
+
"base-uri 'none'",
|
|
50
|
+
"object-src 'none'",
|
|
51
|
+
"form-action 'none'",
|
|
52
|
+
"frame-src 'none'",
|
|
53
|
+
"worker-src 'none'",
|
|
54
|
+
`script-src ${script}`,
|
|
55
|
+
`script-src-elem ${script}`,
|
|
56
|
+
"img-src https://*.google-analytics.com https://www.googletagmanager.com",
|
|
57
|
+
"connect-src https://*.google-analytics.com https://*.analytics.google.com https://www.googletagmanager.com",
|
|
58
|
+
].join("; ");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function buildGoogleMeasurementSandboxHtml() {
|
|
62
|
+
const code = await bundleGoogleMeasurementSandboxScript();
|
|
63
|
+
const csp = googleMeasurementSandboxCsp(code);
|
|
64
|
+
return `<!doctype html>\n<meta charset="utf-8">\n<meta http-equiv="Content-Security-Policy" content="${csp}">\n<title>Analytics transport</title>\n<script>${code}</script>\n`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function googleMeasurementSandboxAsset() {
|
|
68
|
+
let clientDir = null;
|
|
69
|
+
return {
|
|
70
|
+
name: "tot-google-measurement-sandbox-asset",
|
|
71
|
+
hooks: {
|
|
72
|
+
"astro:config:done": ({ config }) => { clientDir = config.build.client; },
|
|
73
|
+
"astro:server:setup": ({ server }) => {
|
|
74
|
+
server.middlewares.use((req, res, next) => {
|
|
75
|
+
const path = (req.url ?? "").split("?")[0];
|
|
76
|
+
if (path !== `/${NATIVE_GA4_SANDBOX_ASSET_PATH}`) return next();
|
|
77
|
+
buildGoogleMeasurementSandboxHtml().then(
|
|
78
|
+
(html) => {
|
|
79
|
+
res.setHeader("content-type", "text/html; charset=utf-8");
|
|
80
|
+
res.setHeader("cache-control", "no-store");
|
|
81
|
+
res.end(html);
|
|
82
|
+
},
|
|
83
|
+
(error) => next(error),
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
"astro:build:done": async ({ logger }) => {
|
|
88
|
+
if (!clientDir) throw new Error("the GA4 sandbox has no resolved client output dir");
|
|
89
|
+
const html = await buildGoogleMeasurementSandboxHtml();
|
|
90
|
+
const target = fileURLToPath(new URL(NATIVE_GA4_SANDBOX_ASSET_PATH, clientDir));
|
|
91
|
+
await mkdir(dirname(target), { recursive: true });
|
|
92
|
+
await writeFile(target, html, "utf8");
|
|
93
|
+
logger.info(`GA4 sandbox → /${NATIVE_GA4_SANDBOX_ASSET_PATH} (${html.length} bytes)`);
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Reviewed parent-side marker for the null-origin GA4 platform adapter. */
|
|
2
|
+
export const platformAdapterEntry = Object.freeze({
|
|
3
|
+
schemaVersion: 1,
|
|
4
|
+
origin: "platform-adapter",
|
|
5
|
+
executionTier: "platform-adapter",
|
|
6
|
+
appId: "platform.google-measurement",
|
|
7
|
+
version: "1.0.0",
|
|
8
|
+
entry: Object.freeze({
|
|
9
|
+
url: "/platform/google-measurement/v2/entry.mjs",
|
|
10
|
+
format: "module",
|
|
11
|
+
}),
|
|
12
|
+
runtime: "native-ga4-v2",
|
|
13
|
+
});
|
|
@@ -222,10 +222,6 @@ const { gridColumns: footerColumns, legalLinks: bottomLinks } = splitFooterLegal
|
|
|
222
222
|
const legalBase = chrome.footer.bottom?.copyright ?? `© ${new Date().getFullYear()} ${tenant.displayName}`;
|
|
223
223
|
const legalLine = `${legalBase}${chrome.footer.bottom?.note ? ` · ${chrome.footer.bottom.note}` : ""}`;
|
|
224
224
|
|
|
225
|
-
const analyticsToken = import.meta.env.PUBLIC_CF_ANALYTICS_TOKEN;
|
|
226
|
-
const analyticsEnabled =
|
|
227
|
-
analyticsToken && !/REPLACE|PLACEHOLDER/i.test(analyticsToken);
|
|
228
|
-
|
|
229
225
|
// Private-app widgets — global.footer slot. Resolved once here so it
|
|
230
226
|
// renders for BOTH footer paths below (the tenant-owned chrome.html splice
|
|
231
227
|
// and the generic <Footer>) instead of being owned by either. No dark-mode
|
|
@@ -293,46 +289,6 @@ const footerWidgets = await renderWidgetSlot(
|
|
|
293
289
|
alternates={alternates}
|
|
294
290
|
/>
|
|
295
291
|
|
|
296
|
-
{
|
|
297
|
-
/* Cloudflare Web Analytics is a non-essential measurement destination —
|
|
298
|
-
gate it on the SAME neutral consent broker every vendor loader reads
|
|
299
|
-
(ext-kernel-04 `consent.defaults` phase, published above as
|
|
300
|
-
`window.__TOT_INTEGRATION_CONSENT__`). The token rides a <meta> tag
|
|
301
|
-
(any script attribute, incl. define:vars, forces is:inline and drops
|
|
302
|
-
module processing — this script needs a real import) rather than a
|
|
303
|
-
page global; the script fail-closes like every other consumer of that
|
|
304
|
-
global: an absent/tampered snapshot denies, so an unconfigured CMP
|
|
305
|
-
means this never loads rather than loading ungated. */
|
|
306
|
-
analyticsEnabled && <meta name="cf-beacon-token" content={analyticsToken} />
|
|
307
|
-
}
|
|
308
|
-
{
|
|
309
|
-
analyticsEnabled && (
|
|
310
|
-
<script>
|
|
311
|
-
import {
|
|
312
|
-
parseConsentSnapshot,
|
|
313
|
-
isPurposeEligible,
|
|
314
|
-
defaultConsentSnapshot,
|
|
315
|
-
INTEGRATION_CONSENT_GLOBAL,
|
|
316
|
-
} from "@tot/public-runtime";
|
|
317
|
-
|
|
318
|
-
const snapshot =
|
|
319
|
-
parseConsentSnapshot((window as unknown as Record<string, unknown>)[INTEGRATION_CONSENT_GLOBAL]) ??
|
|
320
|
-
defaultConsentSnapshot();
|
|
321
|
-
|
|
322
|
-
const token = document
|
|
323
|
-
.querySelector('meta[name="cf-beacon-token"]')
|
|
324
|
-
?.getAttribute("content");
|
|
325
|
-
|
|
326
|
-
if (token && isPurposeEligible(snapshot, "analytics")) {
|
|
327
|
-
const beacon = document.createElement("script");
|
|
328
|
-
beacon.defer = true;
|
|
329
|
-
beacon.src = "https://static.cloudflareinsights.com/beacon.min.js";
|
|
330
|
-
beacon.setAttribute("data-cf-beacon", JSON.stringify({ token }));
|
|
331
|
-
document.head.appendChild(beacon);
|
|
332
|
-
}
|
|
333
|
-
</script>
|
|
334
|
-
)
|
|
335
|
-
}
|
|
336
292
|
</head>
|
|
337
293
|
<body>
|
|
338
294
|
{/* body.start fallback boundary (ext-kernel-04) — the renderer-owned mount
|
|
@@ -582,11 +538,10 @@ const footerWidgets = await renderWidgetSlot(
|
|
|
582
538
|
{/* Field Web Vitals (RUM) — measures LCP/INP/CLS for real visitors and
|
|
583
539
|
beacons per-route samples to the RUM sink. ~1KB, no framework; no-ops
|
|
584
540
|
where PerformanceObserver is absent. This is the per-route-template
|
|
585
|
-
signal the `core-web-vitals` monitor consumes
|
|
586
|
-
beacon above only gives aggregate field CWV). */}
|
|
541
|
+
signal the `core-web-vitals` monitor consumes. */}
|
|
587
542
|
<script>
|
|
588
|
-
import {
|
|
589
|
-
|
|
543
|
+
import { startConsentGatedWebVitals } from "@/lib/analytics/webVitals";
|
|
544
|
+
startConsentGatedWebVitals();
|
|
590
545
|
</script>
|
|
591
546
|
|
|
592
547
|
{/* Protected-surface tamper sentinel (ext-policy-09, D-R7) — a DETECTIVE
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Privacy-friendly analytics (brief §5 Analytics).
|
|
3
3
|
*
|
|
4
|
-
* Phase 1 ships
|
|
5
|
-
* 1. Pageviews — Cloudflare Web Analytics, a single beacon script injected in
|
|
6
|
-
* Layout.astro when PUBLIC_CF_ANALYTICS_TOKEN is set (no cookies, no PII).
|
|
7
|
-
* 2. Commerce events — product impression/click/PDP-view instrumented NOW to a
|
|
4
|
+
* Phase 1 ships commerce events — product impression/click/PDP-view instrumented NOW to a
|
|
8
5
|
* NO-OP sink, so Phase 2 conversion work inherits the call sites for free.
|
|
9
6
|
*
|
|
10
7
|
* The event API is intentionally tiny and synchronous-looking; swap the sink
|
|
@@ -24,12 +24,16 @@
|
|
|
24
24
|
* + bucketing in node; `startWebVitals` is the thin browser wiring, guarded so
|
|
25
25
|
* importing this module in a non-browser context (SSR, tests) is a no-op.
|
|
26
26
|
*
|
|
27
|
-
* Field CWV is emitted
|
|
28
|
-
*
|
|
29
|
-
* this beacon adds the PER-ROUTE-TEMPLATE breakdown the budget gate + the
|
|
30
|
-
* regression alert (`rumAlert.ts`) reason over, which CF's aggregate cannot.
|
|
27
|
+
* Field CWV is emitted through the consent-gated per-route-template beacon that the
|
|
28
|
+
* budget gate and regression alert (`rumAlert.ts`) reason over.
|
|
31
29
|
*/
|
|
32
30
|
import { classifyPath, PERF_BUDGETS, type PerfBudgetManifest, type RouteClass } from "./perfBudgets.js";
|
|
31
|
+
import {
|
|
32
|
+
INTEGRATION_CONSENT_GLOBAL,
|
|
33
|
+
VIEWER_ANALYTICS_CONSENT_CHANGE_EVENT,
|
|
34
|
+
isPurposeEligible,
|
|
35
|
+
parseConsentSnapshot,
|
|
36
|
+
} from "@tot/public-runtime";
|
|
33
37
|
|
|
34
38
|
/** The three field metrics we collect. INP is field-only (no lab equivalent). */
|
|
35
39
|
export type WebVitalName = "lcp" | "inp" | "cls";
|
|
@@ -267,3 +271,37 @@ export function startWebVitals(
|
|
|
267
271
|
inpObs.disconnect();
|
|
268
272
|
};
|
|
269
273
|
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Keep field telemetry behind the same authenticated viewer-consent snapshot as
|
|
277
|
+
* every managed analytics adapter. Withdrawal tears down observers before they
|
|
278
|
+
* can flush; a later grant starts a fresh collector for the current document.
|
|
279
|
+
*/
|
|
280
|
+
export function startConsentGatedWebVitals(
|
|
281
|
+
start: () => () => void = () => startWebVitals(),
|
|
282
|
+
): () => void {
|
|
283
|
+
if (typeof window === "undefined") return () => {};
|
|
284
|
+
const win = window as unknown as Record<string, unknown>;
|
|
285
|
+
let stopCollector: (() => void) | null = null;
|
|
286
|
+
let disposed = false;
|
|
287
|
+
const sync = () => {
|
|
288
|
+
if (disposed) return;
|
|
289
|
+
const snapshot = parseConsentSnapshot(win[INTEGRATION_CONSENT_GLOBAL]);
|
|
290
|
+
const eligible = snapshot !== null && isPurposeEligible(snapshot, "analytics");
|
|
291
|
+
if (eligible && !stopCollector) {
|
|
292
|
+
stopCollector = start();
|
|
293
|
+
} else if (!eligible && stopCollector) {
|
|
294
|
+
const stop = stopCollector;
|
|
295
|
+
stopCollector = null;
|
|
296
|
+
stop();
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
window.addEventListener(VIEWER_ANALYTICS_CONSENT_CHANGE_EVENT, sync);
|
|
300
|
+
sync();
|
|
301
|
+
return () => {
|
|
302
|
+
disposed = true;
|
|
303
|
+
window.removeEventListener(VIEWER_ANALYTICS_CONSENT_CHANGE_EVENT, sync);
|
|
304
|
+
stopCollector?.();
|
|
305
|
+
stopCollector = null;
|
|
306
|
+
};
|
|
307
|
+
}
|
|
@@ -69,7 +69,7 @@ const DEMO_ANALYTICS_MANIFEST: IntegrationManifest = {
|
|
|
69
69
|
digest: `sha256:${DEMO_ANALYTICS_APP_ID}-${DEMO_ANALYTICS_VERSION}`,
|
|
70
70
|
publisher: "Token of Trust",
|
|
71
71
|
},
|
|
72
|
-
compatibility: { storefrontBrowserContract: ">=
|
|
72
|
+
compatibility: { storefrontBrowserContract: ">=3.0.0 <4.0.0", configurationSchemaVersion: 1 },
|
|
73
73
|
browser: {
|
|
74
74
|
executionTier: "sandbox",
|
|
75
75
|
entry: { asset: DEMO_ANALYTICS_ENTRY_URL, format: "module", phase: "document.deferred" },
|
|
@@ -258,8 +258,7 @@ export const MONITORS: readonly Monitor[] = [
|
|
|
258
258
|
runbook: "bad-content-policy",
|
|
259
259
|
wiring: "needs-external",
|
|
260
260
|
notes:
|
|
261
|
-
"Reference F14's RUM signal by name; do
|
|
262
|
-
"(PUBLIC_CF_ANALYTICS_TOKEN, injected in Layout.astro) gives field CWV today; F14's beacon gives per-template detail.",
|
|
261
|
+
"Reference F14's consent-gated RUM signal by name; do not duplicate its analytics implementation.",
|
|
263
262
|
},
|
|
264
263
|
] as const;
|
|
265
264
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/storefront-runner",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.32",
|
|
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",
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"exports": {
|
|
11
11
|
".": "./src/index.ts",
|
|
12
12
|
"./blog-visibility": "./src/blog-visibility.mjs",
|
|
13
|
-
"./integration/lifecycle-host-asset": "./src/integration/lifecycle-host-asset.mjs"
|
|
13
|
+
"./integration/lifecycle-host-asset": "./src/integration/lifecycle-host-asset.mjs",
|
|
14
|
+
"./integration/native-ga4-sandbox-asset": "./src/integration/native-ga4-sandbox-asset.mjs"
|
|
14
15
|
},
|
|
15
16
|
"scripts": {
|
|
16
17
|
"typecheck": "tsc --noEmit",
|
|
@@ -78,7 +78,7 @@ export const CSP_REPORT_GROUP = "csp-endpoint";
|
|
|
78
78
|
* origin) per request.
|
|
79
79
|
*/
|
|
80
80
|
export const STOREFRONT_CSP_ALLOWLIST = {
|
|
81
|
-
scriptSrc: [
|
|
81
|
+
scriptSrc: [],
|
|
82
82
|
// Web fonts are self-hosted (@fontsource, bundled same-origin) — no
|
|
83
83
|
// fonts.googleapis.com stylesheet or fonts.gstatic.com woff2 to allowlist;
|
|
84
84
|
// 'self' + `data:` (both added by buildCsp) cover style-src + font-src,
|
|
@@ -87,7 +87,7 @@ export const STOREFRONT_CSP_ALLOWLIST = {
|
|
|
87
87
|
fontSrc: [],
|
|
88
88
|
// Product imagery is rehosted to R2 or served from remote CDNs.
|
|
89
89
|
imgSrc: ["https:"],
|
|
90
|
-
connectSrc: [
|
|
90
|
+
connectSrc: [],
|
|
91
91
|
} as const;
|
|
92
92
|
|
|
93
93
|
/**
|