@tokenoftrust/storefront-runner 2.2.3 → 2.2.5

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.
@@ -59,14 +59,16 @@ export const referenceTheme: ThemeTokens = {
59
59
  },
60
60
  },
61
61
  brand: {
62
- logoLight: "/brand/northwind-logo-light.svg",
63
- logoDark: "/brand/northwind-logo-dark.svg",
64
62
  favicon: "/favicon.svg",
65
- // No ogImage default: this object is the merge BASE for every tenant
66
- // (schema.ts mergeTheme(referenceTheme, tenant.theme_tokens)), not just the
67
- // reference/demo tenant's own theme. A hardcoded default here silently
68
- // became every tenant's og:image unless they declared their own -- the
69
- // northwind-og.svg placeholder shipped live on tokenoftrust.com this way.
63
+ // No logoLight/logoDark/ogImage default: this object is the merge BASE for every
64
+ // tenant (schema.ts mergeTheme(referenceTheme, tenant.theme_tokens)), not just the
65
+ // reference/demo tenant's own theme. A hardcoded default here silently becomes
66
+ // every tenant's value unless they declare their own -- exactly how the
67
+ // northwind-og.svg og:image placeholder shipped live on tokenoftrust.com. The two
68
+ // northwind logo SVGs removed here were the same bug, caught on the same sweep:
69
+ // any dark-header tenant with no declared logo would silently show "Northwind"
70
+ // branding in its own site header (Layout.astro) and Organization JSON-LD
71
+ // (jsonld.ts) instead of falling back to its text wordmark / generic favicon.
70
72
  motion: "full",
71
73
  },
72
74
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenoftrust/storefront-runner",
3
- "version": "2.2.3",
3
+ "version": "2.2.5",
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",
@@ -15,6 +15,7 @@
15
15
  "./image-finding-dependencies": "./src/image-finding-dependencies.ts",
16
16
  "./image-finding-acceptance": "./src/image-finding-acceptance.ts",
17
17
  "./image-preview-rewrite-overlay": "./src/image-preview-rewrite-overlay.ts",
18
+ "./hosted-image-media-types": "./src/hosted-image-media-types.ts",
18
19
  "./tenant-embed-defaults": "./src/tenant-embed-defaults.mjs",
19
20
  "./integration/lifecycle-host-asset": "./src/integration/lifecycle-host-asset.mjs",
20
21
  "./integration/native-ga4-sandbox-asset": "./src/integration/native-ga4-sandbox-asset.mjs"
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Single source of truth for hosted-image media-type <-> extension tables.
3
+ *
4
+ * These used to be independent literal objects copy-pasted across the S3 and
5
+ * R2 publish paths -- which is exactly how one of them silently fell out of
6
+ * sync (the SVG entry landed in scripts/publish/lib/hosted-original-publication.mjs's
7
+ * EXT map but not apps/storefront/src/lib/change/publication/hostedOriginalPoolStage.ts's
8
+ * MEDIA_TYPES map, so a tenant with a pinned SVG favicon staged to CloudFront but
9
+ * was silently rejected on the Cloudflare/R2 mirror). Importing from here instead
10
+ * of re-declaring the literal is what makes that class of drift impossible, not just
11
+ * tested against.
12
+ *
13
+ * Two separate tables, two separate trust boundaries -- do not merge them:
14
+ *
15
+ * - PINNED_ASSET_*: a tenant's locally-pinned bundle asset (/_a/<sha256>.<ext>,
16
+ * e.g. a favicon) -- a hash-verified, already-reviewed file. Includes SVG.
17
+ * - CAPTURABLE_RASTER_MEDIA_TYPES: what a *remote* capture (original-image-capture.ts)
18
+ * may fetch and rehost. Deliberately EXCLUDES svg: an externally-sourced SVG can
19
+ * embed <script> and is an XSS vector, so only local, already-reviewed SVGs may
20
+ * ever reach the pinned-asset path above.
21
+ */
22
+
23
+ export const PINNED_ASSET_EXTENSION_BY_MEDIA_TYPE: Readonly<Record<string, string>> = Object.freeze({
24
+ "image/png": ".png",
25
+ "image/jpeg": ".jpg",
26
+ "image/gif": ".gif",
27
+ "image/webp": ".webp",
28
+ "image/avif": ".avif",
29
+ "image/svg+xml": ".svg",
30
+ });
31
+
32
+ export const PINNED_ASSET_MEDIA_TYPE_BY_EXTENSION: Readonly<Record<string, string>> = Object.freeze(
33
+ Object.fromEntries(
34
+ Object.entries(PINNED_ASSET_EXTENSION_BY_MEDIA_TYPE).map(([mediaType, ext]) => [ext, mediaType]),
35
+ ),
36
+ );
37
+
38
+ export const CAPTURABLE_RASTER_MEDIA_TYPES = Object.freeze([
39
+ "image/png", "image/jpeg", "image/gif", "image/webp", "image/avif",
40
+ ] as const);
41
+
42
+ export type CapturableRasterMediaType = (typeof CAPTURABLE_RASTER_MEDIA_TYPES)[number];
43
+
44
+ /** Comma-joined for an HTTP `Accept` header -- derived, never a second literal. */
45
+ export const CAPTURABLE_RASTER_ACCEPT_HEADER = CAPTURABLE_RASTER_MEDIA_TYPES.join(",");
@@ -136,8 +136,9 @@ export interface ThemeShapeTokens {
136
136
  }
137
137
 
138
138
  export interface ThemeBrandTokens {
139
- logoLight: string;
140
- logoDark: string;
139
+ /** No platform default: a tenant with no declared logo gets the text wordmark, never a shared placeholder image (same rule as ogImage below). */
140
+ logoLight?: string;
141
+ logoDark?: string;
141
142
  favicon: string;
142
143
  /** No platform default: a tenant with no declared og:image gets no og:image tag, never a shared placeholder. */
143
144
  ogImage?: string;