@tokenoftrust/storefront-runner 2.4.31 → 2.4.33

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.
@@ -38,7 +38,8 @@ const railId = `promo-${tenantId.replace(/[^a-z0-9]+/gi, "-")}-${(block.title ??
38
38
  alt={tile.imageAlt ?? tile.label}
39
39
  width={tile.imageWidth}
40
40
  height={tile.imageHeight}
41
- loading="lazy"
41
+ loading={tile.loading}
42
+ fetchpriority={tile.fetchpriority}
42
43
  class:list={tile.imageClass}
43
44
  />
44
45
  )}
@@ -70,13 +70,15 @@ export function promoTilesView(
70
70
  gridClass,
71
71
  tiles: block.tiles
72
72
  .filter((tile) => isSafeStorefrontHref(tile.href))
73
- .map((tile) => ({
73
+ .map((tile, index) => ({
74
74
  ...tile,
75
75
  href: withBase(basePath, tile.href),
76
76
  image: isSafeRehostedAssetPathForTenant(tile.image, tenantId) ? tile.image : undefined,
77
77
  imageAlt: tile.imageAlt ?? tile.label,
78
78
  imageWidth: isLogoStrip ? 160 : 800,
79
79
  imageHeight: isLogoStrip ? 80 : isPortrait ? 1000 : 450,
80
+ loading: index === 0 ? ("eager" as const) : ("lazy" as const),
81
+ fetchpriority: index === 0 ? ("high" as const) : undefined,
80
82
  imageClass: [
81
83
  "transition-transform duration-300 group-hover:scale-[1.02]",
82
84
  isOverlay && "absolute inset-0 h-full w-full object-cover group-hover:scale-105",
@@ -37,6 +37,19 @@ export function stampInlineTagNonces(html: string, nonce: string): string {
37
37
  );
38
38
  }
39
39
 
40
+ /**
41
+ * True when a CSP header already names a live nonce: a render-chain pass completed
42
+ * this response, stamping its header and document as one pair. Any further pass must
43
+ * serve it verbatim — re-stamping replaces the header's nonce but not the document's,
44
+ * and the browser then blocks every inline style and script on the page.
45
+ */
46
+ export function carriesLiveNonce(response: Response): boolean {
47
+ return CSP_HEADERS.some((header) => {
48
+ const value = response.headers.get(header);
49
+ return !!value && value.includes("'nonce-") && !value.includes(INTEGRATION_NONCE_PLACEHOLDER);
50
+ });
51
+ }
52
+
40
53
  /** A fresh, non-guessable per-response nonce. */
41
54
  export function freshNonce(): string {
42
55
  return crypto.randomUUID();
@@ -92,8 +92,7 @@ import {
92
92
  deriveSiteSurface,
93
93
  cspHeaderName,
94
94
  } from "@/lib/integration/browserEnvironment";
95
- import { freshNonce, stampResponseNonce } from "@/lib/integration/nonceStamp";
96
- import { takePinnedRouteRewriteResponse } from "@/lib/pinnedRouteRewriteBoundary";
95
+ import { carriesLiveNonce, freshNonce, stampResponseNonce } from "@/lib/integration/nonceStamp";
97
96
  import {
98
97
  composeIntegrationDeclarations,
99
98
  resolveIntegrationRenderState,
@@ -713,13 +712,11 @@ export async function runRenderChain(input: RenderChainInput): Promise<Response>
713
712
  ? await next(rewriteTarget)
714
713
  : await next();
715
714
 
716
- // A pinned generic-route fallback uses `context.rewrite()`, which runs the
717
- // rewritten route through this middleware and hands back a fully decorated,
718
- // CSP-stamped response. Preserve that inner response verbatim. Running the
719
- // outer decoration path again would inject preview helpers twice and replace
720
- // the CSP nonce without replacing the already-live nonce in the document.
721
- const pinnedRewriteResponse = takePinnedRouteRewriteResponse(response);
722
- if (pinnedRewriteResponse) return pinnedRewriteResponse;
715
+ // A nested pass of this middleware — an in-process `rewrite()`, or Astro re-running
716
+ // middleware for its error route — already decorated and nonce-stamped the
717
+ // response. Serve it verbatim: decorating again would inject preview helpers twice
718
+ // and replace the header's nonce while the document keeps the inner one.
719
+ if (carriesLiveNonce(response)) return response;
723
720
 
724
721
  // On-publish auto-refresh: inject the live-reload client into SHARED preview
725
722
  // (`test`) HTML pages ONLY — never `live` (keeps live clean + edge-cacheable).
@@ -14,7 +14,6 @@ import { breadcrumbLd } from "@/lib/jsonld";
14
14
  import { truncate } from "@/lib/format";
15
15
  import { rawHtmlPolicy, rawHtmlResponse, rawPageCanonicalUrl } from "@/lib/rawMarketingHtml";
16
16
  import { renderRawComplianceTop, renderRawComplianceBottom } from "@/lib/compliance/rawComplianceNotices";
17
- import { rewriteThroughRenderChain } from "@/lib/pinnedRouteRewriteBoundary";
18
17
  import { styleGuideAliasRewrite } from "@/lib/styleGuidePaths";
19
18
 
20
19
  const { tenant, canonicalBase, content, basePath, capabilities, features } = Astro.locals;
@@ -38,7 +37,7 @@ if (
38
37
  // never reskin the live site.
39
38
  const styleGuideTarget = styleGuideAliasRewrite(basePath, slug, Astro.url.search);
40
39
  if (styleGuideTarget !== undefined) {
41
- return rewriteThroughRenderChain(Astro, styleGuideTarget);
40
+ return Astro.rewrite(styleGuideTarget);
42
41
  }
43
42
 
44
43
  // Raw tenant-authored marketing subpage (vendor-injected HTML) — serve the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenoftrust/storefront-runner",
3
- "version": "2.4.31",
3
+ "version": "2.4.33",
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",
@@ -1,49 +0,0 @@
1
- /**
2
- * Private response marker for the in-process route rewrite boundary.
3
- *
4
- * `context.rewrite()` runs the rewritten request through middleware and returns
5
- * a complete response: preview helpers, security headers, CSP, and the live
6
- * nonce have already been applied. The outer render chain must pass those bytes
7
- * through instead of decorating and nonce-stamping them a second time — a second
8
- * stamp replaces the CSP header's nonce while the document keeps the inner one,
9
- * so the browser blocks every inline style and script on the page.
10
- *
11
- * The marker exists only while the inner response unwinds through the outer
12
- * middleware pass. `takePinnedRouteRewriteResponse` removes it before the
13
- * response reaches a client.
14
- *
15
- * Every in-process rewrite goes through {@link rewriteThroughRenderChain};
16
- * `pinnedRouteRewriteBoundary.test.ts` refuses a bare `.rewrite(` anywhere else.
17
- */
18
- export const PINNED_ROUTE_REWRITE_COMPLETE_HEADER =
19
- "x-tot-internal-pinned-route-rewrite-complete";
20
-
21
- export function markPinnedRouteRewriteComplete(response: Response): Response {
22
- response.headers.set(PINNED_ROUTE_REWRITE_COMPLETE_HEADER, "1");
23
- return response;
24
- }
25
-
26
- export function takePinnedRouteRewriteResponse(response: Response): Response | null {
27
- if (response.headers.get(PINNED_ROUTE_REWRITE_COMPLETE_HEADER) !== "1") return null;
28
-
29
- const headers = new Headers(response.headers);
30
- headers.delete(PINNED_ROUTE_REWRITE_COMPLETE_HEADER);
31
- return new Response(response.body, {
32
- status: response.status,
33
- statusText: response.statusText,
34
- headers,
35
- });
36
- }
37
-
38
- /**
39
- * Rewrite in-process and mark the completed response so the outer middleware pass
40
- * serves it verbatim. The response a rewrite hands back may be immutable, so it is
41
- * copied before the marker (and any caller headers) are set.
42
- */
43
- export async function rewriteThroughRenderChain(
44
- context: { rewrite(path: string): Promise<Response> },
45
- path: string,
46
- ): Promise<Response> {
47
- const rendered = await context.rewrite(path);
48
- return markPinnedRouteRewriteComplete(new Response(rendered.body, rendered));
49
- }