@fayz-ai/storefront 0.85.2 → 0.85.3

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/dist/edge.js CHANGED
@@ -154,7 +154,11 @@ function deferredEntryScript(src) {
154
154
  function hydrateAfterFirstPaint(response) {
155
155
  const Rewriter = globalThis.HTMLRewriter;
156
156
  if (!Rewriter || !(response.headers.get("content-type") ?? "").includes("text/html")) return response;
157
- return new Rewriter().on('script[type="module"][src]', {
157
+ return new Rewriter().on('link[rel="modulepreload"]', {
158
+ element(el) {
159
+ el.remove();
160
+ }
161
+ }).on('script[type="module"][src]', {
158
162
  element(el) {
159
163
  const src = el.getAttribute("src");
160
164
  if (!src || !el.hasAttribute("async") || src.includes("<")) return;
package/dist/edge.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/analytics/edge.ts","../src/host/edge-handler.ts"],"names":["response"],"mappings":"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","file":"edge.js","sourcesContent":["/**\n * The store's own door to PostHog, for a Worker (or any fetch-style) host.\n *\n * Events sent to the store's domain under `/ingest` are not dropped by ad\n * blockers the way requests to a known analytics host are, and the store needs\n * no PostHog setting of its own: every store forwards to the same project.\n *\n * if (url.pathname.startsWith('/ingest')) return posthogProxy(request)\n */\nexport interface PostHogProxyOptions {\n /** The path the storefront's analytics client sends to. Default `/ingest`. */\n prefix?: string\n /** PostHog's region. Default `us`. */\n region?: 'us' | 'eu'\n}\n\nexport function isPostHogProxyPath(pathname: string, prefix = '/ingest'): boolean {\n return pathname === prefix || pathname.startsWith(`${prefix}/`)\n}\n\nexport async function posthogProxy(request: Request, options: PostHogProxyOptions = {}): Promise<Response> {\n const prefix = options.prefix ?? '/ingest'\n const region = options.region ?? 'us'\n const url = new URL(request.url)\n const path = url.pathname.slice(prefix.length) || '/'\n // The SDK's script and its remote config live on the assets host.\n const host = path.startsWith('/static/') || path.startsWith('/array/')\n ? `${region}-assets.i.posthog.com`\n : `${region}.i.posthog.com`\n const target = new URL(`https://${host}${path}${url.search}`)\n\n const headers = new Headers(request.headers)\n headers.delete('cookie')\n headers.set('host', host)\n // PostHog geolocates from the visitor, not from the Worker.\n const ip = request.headers.get('cf-connecting-ip') ?? request.headers.get('x-forwarded-for')\n if (ip) headers.set('x-forwarded-for', ip)\n\n const init: RequestInit = { method: request.method, headers, redirect: 'manual' }\n if (request.method !== 'GET' && request.method !== 'HEAD') init.body = await request.arrayBuffer()\n const response = await fetch(target, init)\n const out = new Response(response.body, response)\n out.headers.delete('set-cookie')\n return out\n}\n","import { isPostHogProxyPath, posthogProxy } from '../analytics/edge'\nimport {\n EXPERIMENT_HEADER,\n NO_ASSIGNMENTS,\n assignExperiments,\n encodeExperimentHeader,\n experimentCacheSuffix,\n runningExperiments,\n isBotUserAgent,\n readVisitorId,\n readPinnedVisitorId,\n isPinnableVisitorId,\n visitorCookie,\n type ExperimentAssignments,\n type StorefrontExperiment,\n} from '../experiments/assign'\n\n// ---------------------------------------------------------------------------\n// The Worker in front of a server-rendered store.\n//\n// Five jobs a store's request handler has before the framework renders\n// anything, first written for artorious-shop and the same for every store:\n//\n// 1. Analytics through the store's own domain (`/ingest` → PostHog), so ad\n// blockers that drop requests to a known analytics host do not drop it.\n// 2. The store's photos from the store's own origin. The bytes live in\n// Supabase Storage; a photo addressed at the storage project is a second\n// origin the browser pays DNS + TLS for (measured 156–241 ms TTFB against\n// 112–141 ms same-origin). Only public objects of the `shop-images`\n// bucket are forwarded — this is not a general proxy.\n// 3. A real edge cache. Public pages already leave with `Cache-Control:\n// public, s-maxage=…`, but on workers.dev nothing in front honours it, so\n// every view paid the full render. The Workers Cache API makes the header\n// mean what it says. Marketing parameters (utm_*, fbclid…) are dropped\n// from the key: ad traffic hits the same entry as everyone else.\n// 4. `X-Robots-Tag: noindex` on any host that is not the store's address, so\n// the deployment URL never competes with the domain.\n// 5. A/B assignment. With a running experiment, every page request is\n// assigned its variants HERE, before render, locally with PostHog's\n// bucketing (experiments/assign.ts — no network), and the edge cache is\n// keyed by them: one entry per combination of variants, so a cached page\n// is never one visitor's variant served to another.\n// ---------------------------------------------------------------------------\n\n/** `/storage/v1/object/public/shop-images/…` or the `render/image` variant of it. */\nconst STORAGE_PATH = /^\\/storage\\/v1\\/(object|render\\/image)\\/public\\/(shop-images\\/[^?#]+)$/\n\n/** Query parameters the renderer understands. Anything else is dropped rather\n * than forwarded, so a crawler cannot mint unlimited distinct variants. */\nconst RENDER_PARAMS = new Set(['width', 'height', 'quality', 'format', 'resize'])\n\n/** A year: the object path carries a timestamp and is never overwritten. */\nconst IMMUTABLE = 'public, max-age=31536000, s-maxage=31536000, immutable'\n\nconst TRACKING_PARAM = /^(utm_[a-z0-9_]+|fbclid|gclid|gbraid|wbraid|msclkid|ttclid|igshid|mc_cid|mc_eid|_ga|ref)$/i\n\nexport function isStorageRequest(url: URL): boolean {\n return STORAGE_PATH.test(url.pathname)\n}\n\n/**\n * Which format this visitor gets — the only part of the request, besides the\n * URL, that changes the bytes. The renderer answers WebP or the original\n * depending on `accept` and declares no `Vary`, so every cache in front of it\n * has to put the answer in the key itself.\n */\nexport function imageFormatKey(request: Request): 'webp' | 'orig' {\n return /image\\/webp/.test(request.headers.get('accept') ?? '') ? 'webp' : 'orig'\n}\n\n/** Answer a storage request from Supabase, on the store's own origin. */\nexport async function serveStorageImage(request: Request, url: URL, supabaseUrl: string | undefined): Promise<Response> {\n const match = STORAGE_PATH.exec(url.pathname)\n if (!match) return new Response('Not found', { status: 404 })\n const base = supabaseUrl?.replace(/\\/$/, '')\n if (!base) return new Response('Storage is not configured', { status: 502 })\n\n const [, kind, objectPath] = match\n const upstream = new URL(`${base}/storage/v1/${kind}/public/${objectPath}`)\n for (const [key, value] of url.searchParams) {\n if (RENDER_PARAMS.has(key)) upstream.searchParams.set(key, value)\n }\n // A canonical `accept`, not the visitor's verbatim: two browsers that both\n // take WebP must produce the same upstream request.\n const webp = imageFormatKey(request) === 'webp'\n const response = await fetch(upstream.toString(), { headers: { accept: webp ? 'image/webp,image/*;q=0.8' : 'image/*' } })\n if (!response.ok) {\n // Upstream's status is passed through (a deleted photo must not look like\n // a 200), never its cache directives.\n return new Response(response.status === 404 ? 'Not found' : 'Upstream error', {\n status: response.status === 404 ? 404 : 502,\n headers: { 'cache-control': 'public, max-age=60' },\n })\n }\n const headers = new Headers()\n headers.set('content-type', response.headers.get('content-type') ?? 'image/jpeg')\n headers.set('cache-control', IMMUTABLE)\n headers.set('x-image-origin', 'supabase')\n headers.set('vary', 'Accept')\n return new Response(response.body, { status: 200, headers })\n}\n\n/** The cache key for a request, or null when it is not cacheable. `variant`\n * is the experiment part (`experimentCacheSuffix`); empty means control. */\nexport function edgeCacheKey(request: Request, buildId: string, variant = ''): Request | null {\n if (request.method !== 'GET') return null\n const url = new URL(request.url)\n for (const key of [...url.searchParams.keys()]) {\n if (TRACKING_PARAM.test(key)) url.searchParams.delete(key)\n }\n url.hash = ''\n if (isStorageRequest(url)) url.searchParams.set('_fmt', imageFormatKey(request))\n // Cached HTML names this build's hashed chunks, and a deploy deletes them.\n // Keyed by build, old pages stop matching the moment the new Worker serves.\n url.searchParams.set('_build', buildId)\n if (variant) url.searchParams.set('_exp', variant)\n return new Request(url.toString(), { method: 'GET' })\n}\n\n/** Only what the response itself declares shareable and fresh for a while. */\nexport function edgeCacheable(response: Response): boolean {\n if (response.status !== 200) return false\n const cc = response.headers.get('cache-control') ?? ''\n return /\\bpublic\\b/.test(cc) && /\\bs-maxage=[1-9]\\d*/.test(cc) && !/\\b(no-store|private)\\b/.test(cc)\n}\n\nfunction withHeader(response: Response, name: string, value: string): Response {\n const headers = new Headers(response.headers)\n headers.set(name, value)\n return new Response(response.body, { status: response.status, statusText: response.statusText, headers })\n}\n\n/** Adds cookies without touching the ones the page already sets. */\nfunction withCookies(response: Response, cookies: readonly string[]): Response {\n if (!cookies.length) return response\n const headers = new Headers(response.headers)\n for (const cookie of cookies) headers.append('set-cookie', cookie)\n return new Response(response.body, { status: response.status, statusText: response.statusText, headers })\n}\n\nexport interface StorefrontEdgeOptions {\n /** The framework's own handler (TanStack Start's `handler.fetch`). */\n render(request: Request): Response | Promise<Response>\n /** From the store host: `host.isCanonicalHost`. */\n isCanonicalHost(url: URL): boolean\n /** One id per build, for the cache key. */\n buildId: string\n /** The Supabase project the photos live in (`PUBLIC_SUPABASE_URL`). */\n supabaseUrl?: string\n /** Keeps the Worker alive past the response (`waitUntil` from 'cloudflare:workers'). */\n waitUntil(promise: Promise<unknown>): void\n /** The Workers cache (`caches.default`). Omitted: no edge cache. */\n cache?: Cache\n /**\n * Development: NEVER cache. The Cloudflare vite plugin runs the Worker in\n * workerd locally too, and an entry cannot be purged, only outlived — a\n * price edited and reloaded shows the old one with nothing in the browser\n * able to help.\n */\n dev?: boolean\n /**\n * A/B tests: the store's declared experiments (`config.experiments`).\n * Nothing running: no assignment, no cookie, the cache key is the page's\n * alone — exactly the handler without tests.\n */\n experiments?: StorefrontEdgeExperiments\n /**\n * Start the client bundle after the first frame is painted (see\n * `hydrateAfterFirstPaint`). Off by default; a server-rendered store whose\n * HTML is complete without JavaScript turns it on.\n */\n hydrateAfterFirstPaint?: boolean\n}\n\nexport interface StorefrontEdgeExperiments {\n declared: Record<string, StorefrontExperiment> | undefined\n /**\n * The PostHog project token (`phc_…`), the same the storefront sends with —\n * used only to read PostHog's own cookie, so a returning visitor is bucketed\n * by the id PostHog already knows them by. Optional: without it, `fz_vid`.\n */\n posthogKey?: string\n}\n\n/** A page a visitor navigates to — not an asset, a photo, a feed or a POST. */\nfunction isPageRequest(request: Request, url: URL): boolean {\n if (request.method !== 'GET') return false\n if (isStorageRequest(url) || isPostHogProxyPath(url.pathname)) return false\n // Hashed bundles, fonts, images, the sitemap: anything with an extension.\n if (/\\.[a-z0-9]{1,8}$/i.test(url.pathname)) return false\n // TanStack's server functions and the router's data requests.\n if (url.pathname.startsWith('/_serverFn') || url.pathname.startsWith('/_server')) return false\n return true\n}\n\ninterface PageAssignment {\n assignments: ExperimentAssignments\n /** The cache-key part; empty for control. */\n variant: string\n /** Cookies for THIS visitor's response: a new id. */\n cookies: string[]\n}\n\n/**\n * Decide this request's variants — locally, with PostHog's bucketing, no\n * network. Never throws.\n */\nexport async function assignForRequest(request: Request, options: StorefrontEdgeExperiments): Promise<PageAssignment> {\n const running = runningExperiments(options.declared)\n const keys = Object.keys(running)\n const none: PageAssignment = { assignments: NO_ASSIGNMENTS, variant: '', cookies: [] }\n // Nothing running: no id, no cookie, the page's plain cache entry.\n if (!keys.length) return none\n // Crawlers and unfurlers see control and get no id: what is indexed and\n // shared must not depend on a coin flip, and a bot is not an exposure.\n if (isBotUserAgent(request.headers.get('user-agent'))) return none\n const cookieHeader = request.headers.get('cookie')\n // The bucket id is pinned in `fz_vid` the first time and read from there\n // ever after. A returning visitor without one is pinned to the anonymous id\n // PostHog already knows them by; an identified id (an e-mail) is never\n // stored — they get a fresh anonymous one.\n const pinned = readPinnedVisitorId(cookieHeader)\n const known = pinned ?? readVisitorId(cookieHeader, options.posthogKey ?? '')\n const distinctId = known && isPinnableVisitorId(known) ? known : crypto.randomUUID()\n const cookies = pinned ? [] : [visitorCookie(distinctId, new URL(request.url).protocol === 'https:')]\n try {\n const assignments = await assignExperiments({ experiments: running, distinctId })\n return { assignments, variant: experimentCacheSuffix(assignments, keys), cookies }\n } catch {\n // No SubtleCrypto (not a Worker, not a modern runtime): everyone control.\n return { ...none, cookies }\n }\n}\n\n/**\n * The request the renderer sees: the same, plus the variants — and ONLY the\n * variants. The visitor id stays out of the header on purpose: the rendered\n * HTML is cached and shared by every visitor in the same variant bucket, and\n * an id in it would hand one visitor's identity to the next. The browser\n * reads its own id from the cookie (`readVisitorId(document.cookie, …)`).\n */\nfunction withAssignment(request: Request, assignments: ExperimentAssignments): Request {\n const headers = new Headers(request.headers)\n headers.delete(EXPERIMENT_HEADER) // never trust one the visitor sent\n if (Object.keys(assignments.flags).length) {\n headers.set(EXPERIMENT_HEADER, encodeExperimentHeader({ flags: assignments.flags }))\n }\n return new Request(request, { headers })\n}\n\n/** The Worker's `fetch`, from the store's pieces. */\nexport function createStorefrontEdge(options: StorefrontEdgeOptions): (request: Request) => Promise<Response> {\n async function respond(request: Request): Promise<Response> {\n const url = new URL(request.url)\n if (isStorageRequest(url)) return serveStorageImage(request, url, options.supabaseUrl)\n const rendered = await options.render(request)\n const response = options.hydrateAfterFirstPaint ? hydrateAfterFirstPaint(rendered) : rendered\n if (options.isCanonicalHost(url)) return response\n return withHeader(response, 'x-robots-tag', 'noindex, nofollow')\n }\n\n return async (incoming) => {\n const url = new URL(incoming.url)\n // Analytics leaves through the store's own domain, never cached.\n if (isPostHogProxyPath(url.pathname)) return posthogProxy(incoming)\n\n // Variants first: they change the HTML, so they are part of the cache key.\n const page = options.experiments && isPageRequest(incoming, url)\n ? await assignForRequest(incoming, options.experiments)\n : null\n const request = page ? withAssignment(incoming, page.assignments) : incoming\n // Cookies are added to what THIS visitor receives, never to the cached\n // copy (a shared entry carrying one visitor's id would hand it to the\n // next), and appended, so a cookie the page itself sets survives.\n const finish = (response: Response) => withCookies(response, page?.cookies ?? [])\n\n if (options.dev || !options.cache) return finish(await respond(request))\n\n const key = edgeCacheKey(request, options.buildId, page?.variant ?? '')\n if (!key) return finish(await respond(request))\n const hit = await options.cache.match(key)\n if (hit) return finish(withHeader(hit, 'x-edge-cache', 'HIT'))\n\n const response = await respond(request)\n if (!edgeCacheable(response)) return finish(withHeader(response, 'x-edge-cache', 'BYPASS'))\n options.waitUntil(options.cache.put(key, response.clone()))\n return finish(withHeader(response, 'x-edge-cache', 'MISS'))\n }\n}\n\n/** Minimal shape of the Workers runtime's streaming HTML rewriter. */\ninterface HtmlRewriterLike {\n on(selector: string, handlers: { element(el: HtmlElementLike): void }): HtmlRewriterLike\n transform(response: Response): Response\n}\ninterface HtmlElementLike {\n getAttribute(name: string): string | null\n hasAttribute(name: string): boolean\n removeAttribute(name: string): void\n setInnerContent(content: string, options?: { html?: boolean }): void\n}\n\n/** The inline loader that replaces the entry tag: import once a frame is painted. */\nexport function deferredEntryScript(src: string): string {\n // rAF runs before the next paint; the timeout queued from it runs after.\n return `requestAnimationFrame(function(){setTimeout(function(){import(${JSON.stringify(src)})},0)})`\n}\n\n/**\n * Let the server-rendered page paint before the client bundle runs.\n *\n * TanStack Start emits its entry as `<script type=\"module\" async src>`, which\n * executes the moment it arrives. On a fast connection that is BEFORE the\n * browser's first paint, so a page that was complete as HTML sat blank while\n * ~1 MB of JavaScript was evaluated and hydrated — and Lighthouse, seeing the\n * script run before the paint, counted the whole bundle as render-blocking\n * (FCP 1.0 s one run, 3.3 s the next). The entry is rewritten into an inline\n * module that imports the same URL after the first frame; its\n * `<link rel=\"modulepreload\">` still downloads it in parallel, so nothing\n * arrives later — it only runs later, after the shopper sees the page.\n *\n * Only HTML responses, only an `async` module entry with a `src`; everything\n * else passes through. Without the Workers `HTMLRewriter` (tests, Node) it is a\n * no-op.\n */\nexport function hydrateAfterFirstPaint(response: Response): Response {\n const Rewriter = (globalThis as { HTMLRewriter?: new () => HtmlRewriterLike }).HTMLRewriter\n if (!Rewriter || !(response.headers.get('content-type') ?? '').includes('text/html')) return response\n return new Rewriter()\n .on('script[type=\"module\"][src]', {\n element(el) {\n const src = el.getAttribute('src')\n if (!src || !el.hasAttribute('async') || src.includes('<')) return\n el.removeAttribute('src')\n el.removeAttribute('async')\n el.setInnerContent(deferredEntryScript(src), { html: true })\n },\n })\n .transform(response)\n}\n"]}
1
+ {"version":3,"sources":["../src/analytics/edge.ts","../src/host/edge-handler.ts"],"names":["response"],"mappings":"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","file":"edge.js","sourcesContent":["/**\n * The store's own door to PostHog, for a Worker (or any fetch-style) host.\n *\n * Events sent to the store's domain under `/ingest` are not dropped by ad\n * blockers the way requests to a known analytics host are, and the store needs\n * no PostHog setting of its own: every store forwards to the same project.\n *\n * if (url.pathname.startsWith('/ingest')) return posthogProxy(request)\n */\nexport interface PostHogProxyOptions {\n /** The path the storefront's analytics client sends to. Default `/ingest`. */\n prefix?: string\n /** PostHog's region. Default `us`. */\n region?: 'us' | 'eu'\n}\n\nexport function isPostHogProxyPath(pathname: string, prefix = '/ingest'): boolean {\n return pathname === prefix || pathname.startsWith(`${prefix}/`)\n}\n\nexport async function posthogProxy(request: Request, options: PostHogProxyOptions = {}): Promise<Response> {\n const prefix = options.prefix ?? '/ingest'\n const region = options.region ?? 'us'\n const url = new URL(request.url)\n const path = url.pathname.slice(prefix.length) || '/'\n // The SDK's script and its remote config live on the assets host.\n const host = path.startsWith('/static/') || path.startsWith('/array/')\n ? `${region}-assets.i.posthog.com`\n : `${region}.i.posthog.com`\n const target = new URL(`https://${host}${path}${url.search}`)\n\n const headers = new Headers(request.headers)\n headers.delete('cookie')\n headers.set('host', host)\n // PostHog geolocates from the visitor, not from the Worker.\n const ip = request.headers.get('cf-connecting-ip') ?? request.headers.get('x-forwarded-for')\n if (ip) headers.set('x-forwarded-for', ip)\n\n const init: RequestInit = { method: request.method, headers, redirect: 'manual' }\n if (request.method !== 'GET' && request.method !== 'HEAD') init.body = await request.arrayBuffer()\n const response = await fetch(target, init)\n const out = new Response(response.body, response)\n out.headers.delete('set-cookie')\n return out\n}\n","import { isPostHogProxyPath, posthogProxy } from '../analytics/edge'\nimport {\n EXPERIMENT_HEADER,\n NO_ASSIGNMENTS,\n assignExperiments,\n encodeExperimentHeader,\n experimentCacheSuffix,\n runningExperiments,\n isBotUserAgent,\n readVisitorId,\n readPinnedVisitorId,\n isPinnableVisitorId,\n visitorCookie,\n type ExperimentAssignments,\n type StorefrontExperiment,\n} from '../experiments/assign'\n\n// ---------------------------------------------------------------------------\n// The Worker in front of a server-rendered store.\n//\n// Five jobs a store's request handler has before the framework renders\n// anything, first written for artorious-shop and the same for every store:\n//\n// 1. Analytics through the store's own domain (`/ingest` → PostHog), so ad\n// blockers that drop requests to a known analytics host do not drop it.\n// 2. The store's photos from the store's own origin. The bytes live in\n// Supabase Storage; a photo addressed at the storage project is a second\n// origin the browser pays DNS + TLS for (measured 156–241 ms TTFB against\n// 112–141 ms same-origin). Only public objects of the `shop-images`\n// bucket are forwarded — this is not a general proxy.\n// 3. A real edge cache. Public pages already leave with `Cache-Control:\n// public, s-maxage=…`, but on workers.dev nothing in front honours it, so\n// every view paid the full render. The Workers Cache API makes the header\n// mean what it says. Marketing parameters (utm_*, fbclid…) are dropped\n// from the key: ad traffic hits the same entry as everyone else.\n// 4. `X-Robots-Tag: noindex` on any host that is not the store's address, so\n// the deployment URL never competes with the domain.\n// 5. A/B assignment. With a running experiment, every page request is\n// assigned its variants HERE, before render, locally with PostHog's\n// bucketing (experiments/assign.ts — no network), and the edge cache is\n// keyed by them: one entry per combination of variants, so a cached page\n// is never one visitor's variant served to another.\n// ---------------------------------------------------------------------------\n\n/** `/storage/v1/object/public/shop-images/…` or the `render/image` variant of it. */\nconst STORAGE_PATH = /^\\/storage\\/v1\\/(object|render\\/image)\\/public\\/(shop-images\\/[^?#]+)$/\n\n/** Query parameters the renderer understands. Anything else is dropped rather\n * than forwarded, so a crawler cannot mint unlimited distinct variants. */\nconst RENDER_PARAMS = new Set(['width', 'height', 'quality', 'format', 'resize'])\n\n/** A year: the object path carries a timestamp and is never overwritten. */\nconst IMMUTABLE = 'public, max-age=31536000, s-maxage=31536000, immutable'\n\nconst TRACKING_PARAM = /^(utm_[a-z0-9_]+|fbclid|gclid|gbraid|wbraid|msclkid|ttclid|igshid|mc_cid|mc_eid|_ga|ref)$/i\n\nexport function isStorageRequest(url: URL): boolean {\n return STORAGE_PATH.test(url.pathname)\n}\n\n/**\n * Which format this visitor gets — the only part of the request, besides the\n * URL, that changes the bytes. The renderer answers WebP or the original\n * depending on `accept` and declares no `Vary`, so every cache in front of it\n * has to put the answer in the key itself.\n */\nexport function imageFormatKey(request: Request): 'webp' | 'orig' {\n return /image\\/webp/.test(request.headers.get('accept') ?? '') ? 'webp' : 'orig'\n}\n\n/** Answer a storage request from Supabase, on the store's own origin. */\nexport async function serveStorageImage(request: Request, url: URL, supabaseUrl: string | undefined): Promise<Response> {\n const match = STORAGE_PATH.exec(url.pathname)\n if (!match) return new Response('Not found', { status: 404 })\n const base = supabaseUrl?.replace(/\\/$/, '')\n if (!base) return new Response('Storage is not configured', { status: 502 })\n\n const [, kind, objectPath] = match\n const upstream = new URL(`${base}/storage/v1/${kind}/public/${objectPath}`)\n for (const [key, value] of url.searchParams) {\n if (RENDER_PARAMS.has(key)) upstream.searchParams.set(key, value)\n }\n // A canonical `accept`, not the visitor's verbatim: two browsers that both\n // take WebP must produce the same upstream request.\n const webp = imageFormatKey(request) === 'webp'\n const response = await fetch(upstream.toString(), { headers: { accept: webp ? 'image/webp,image/*;q=0.8' : 'image/*' } })\n if (!response.ok) {\n // Upstream's status is passed through (a deleted photo must not look like\n // a 200), never its cache directives.\n return new Response(response.status === 404 ? 'Not found' : 'Upstream error', {\n status: response.status === 404 ? 404 : 502,\n headers: { 'cache-control': 'public, max-age=60' },\n })\n }\n const headers = new Headers()\n headers.set('content-type', response.headers.get('content-type') ?? 'image/jpeg')\n headers.set('cache-control', IMMUTABLE)\n headers.set('x-image-origin', 'supabase')\n headers.set('vary', 'Accept')\n return new Response(response.body, { status: 200, headers })\n}\n\n/** The cache key for a request, or null when it is not cacheable. `variant`\n * is the experiment part (`experimentCacheSuffix`); empty means control. */\nexport function edgeCacheKey(request: Request, buildId: string, variant = ''): Request | null {\n if (request.method !== 'GET') return null\n const url = new URL(request.url)\n for (const key of [...url.searchParams.keys()]) {\n if (TRACKING_PARAM.test(key)) url.searchParams.delete(key)\n }\n url.hash = ''\n if (isStorageRequest(url)) url.searchParams.set('_fmt', imageFormatKey(request))\n // Cached HTML names this build's hashed chunks, and a deploy deletes them.\n // Keyed by build, old pages stop matching the moment the new Worker serves.\n url.searchParams.set('_build', buildId)\n if (variant) url.searchParams.set('_exp', variant)\n return new Request(url.toString(), { method: 'GET' })\n}\n\n/** Only what the response itself declares shareable and fresh for a while. */\nexport function edgeCacheable(response: Response): boolean {\n if (response.status !== 200) return false\n const cc = response.headers.get('cache-control') ?? ''\n return /\\bpublic\\b/.test(cc) && /\\bs-maxage=[1-9]\\d*/.test(cc) && !/\\b(no-store|private)\\b/.test(cc)\n}\n\nfunction withHeader(response: Response, name: string, value: string): Response {\n const headers = new Headers(response.headers)\n headers.set(name, value)\n return new Response(response.body, { status: response.status, statusText: response.statusText, headers })\n}\n\n/** Adds cookies without touching the ones the page already sets. */\nfunction withCookies(response: Response, cookies: readonly string[]): Response {\n if (!cookies.length) return response\n const headers = new Headers(response.headers)\n for (const cookie of cookies) headers.append('set-cookie', cookie)\n return new Response(response.body, { status: response.status, statusText: response.statusText, headers })\n}\n\nexport interface StorefrontEdgeOptions {\n /** The framework's own handler (TanStack Start's `handler.fetch`). */\n render(request: Request): Response | Promise<Response>\n /** From the store host: `host.isCanonicalHost`. */\n isCanonicalHost(url: URL): boolean\n /** One id per build, for the cache key. */\n buildId: string\n /** The Supabase project the photos live in (`PUBLIC_SUPABASE_URL`). */\n supabaseUrl?: string\n /** Keeps the Worker alive past the response (`waitUntil` from 'cloudflare:workers'). */\n waitUntil(promise: Promise<unknown>): void\n /** The Workers cache (`caches.default`). Omitted: no edge cache. */\n cache?: Cache\n /**\n * Development: NEVER cache. The Cloudflare vite plugin runs the Worker in\n * workerd locally too, and an entry cannot be purged, only outlived — a\n * price edited and reloaded shows the old one with nothing in the browser\n * able to help.\n */\n dev?: boolean\n /**\n * A/B tests: the store's declared experiments (`config.experiments`).\n * Nothing running: no assignment, no cookie, the cache key is the page's\n * alone — exactly the handler without tests.\n */\n experiments?: StorefrontEdgeExperiments\n /**\n * Start the client bundle after the first frame is painted (see\n * `hydrateAfterFirstPaint`). Off by default; a server-rendered store whose\n * HTML is complete without JavaScript turns it on.\n */\n hydrateAfterFirstPaint?: boolean\n}\n\nexport interface StorefrontEdgeExperiments {\n declared: Record<string, StorefrontExperiment> | undefined\n /**\n * The PostHog project token (`phc_…`), the same the storefront sends with —\n * used only to read PostHog's own cookie, so a returning visitor is bucketed\n * by the id PostHog already knows them by. Optional: without it, `fz_vid`.\n */\n posthogKey?: string\n}\n\n/** A page a visitor navigates to — not an asset, a photo, a feed or a POST. */\nfunction isPageRequest(request: Request, url: URL): boolean {\n if (request.method !== 'GET') return false\n if (isStorageRequest(url) || isPostHogProxyPath(url.pathname)) return false\n // Hashed bundles, fonts, images, the sitemap: anything with an extension.\n if (/\\.[a-z0-9]{1,8}$/i.test(url.pathname)) return false\n // TanStack's server functions and the router's data requests.\n if (url.pathname.startsWith('/_serverFn') || url.pathname.startsWith('/_server')) return false\n return true\n}\n\ninterface PageAssignment {\n assignments: ExperimentAssignments\n /** The cache-key part; empty for control. */\n variant: string\n /** Cookies for THIS visitor's response: a new id. */\n cookies: string[]\n}\n\n/**\n * Decide this request's variants — locally, with PostHog's bucketing, no\n * network. Never throws.\n */\nexport async function assignForRequest(request: Request, options: StorefrontEdgeExperiments): Promise<PageAssignment> {\n const running = runningExperiments(options.declared)\n const keys = Object.keys(running)\n const none: PageAssignment = { assignments: NO_ASSIGNMENTS, variant: '', cookies: [] }\n // Nothing running: no id, no cookie, the page's plain cache entry.\n if (!keys.length) return none\n // Crawlers and unfurlers see control and get no id: what is indexed and\n // shared must not depend on a coin flip, and a bot is not an exposure.\n if (isBotUserAgent(request.headers.get('user-agent'))) return none\n const cookieHeader = request.headers.get('cookie')\n // The bucket id is pinned in `fz_vid` the first time and read from there\n // ever after. A returning visitor without one is pinned to the anonymous id\n // PostHog already knows them by; an identified id (an e-mail) is never\n // stored — they get a fresh anonymous one.\n const pinned = readPinnedVisitorId(cookieHeader)\n const known = pinned ?? readVisitorId(cookieHeader, options.posthogKey ?? '')\n const distinctId = known && isPinnableVisitorId(known) ? known : crypto.randomUUID()\n const cookies = pinned ? [] : [visitorCookie(distinctId, new URL(request.url).protocol === 'https:')]\n try {\n const assignments = await assignExperiments({ experiments: running, distinctId })\n return { assignments, variant: experimentCacheSuffix(assignments, keys), cookies }\n } catch {\n // No SubtleCrypto (not a Worker, not a modern runtime): everyone control.\n return { ...none, cookies }\n }\n}\n\n/**\n * The request the renderer sees: the same, plus the variants — and ONLY the\n * variants. The visitor id stays out of the header on purpose: the rendered\n * HTML is cached and shared by every visitor in the same variant bucket, and\n * an id in it would hand one visitor's identity to the next. The browser\n * reads its own id from the cookie (`readVisitorId(document.cookie, …)`).\n */\nfunction withAssignment(request: Request, assignments: ExperimentAssignments): Request {\n const headers = new Headers(request.headers)\n headers.delete(EXPERIMENT_HEADER) // never trust one the visitor sent\n if (Object.keys(assignments.flags).length) {\n headers.set(EXPERIMENT_HEADER, encodeExperimentHeader({ flags: assignments.flags }))\n }\n return new Request(request, { headers })\n}\n\n/** The Worker's `fetch`, from the store's pieces. */\nexport function createStorefrontEdge(options: StorefrontEdgeOptions): (request: Request) => Promise<Response> {\n async function respond(request: Request): Promise<Response> {\n const url = new URL(request.url)\n if (isStorageRequest(url)) return serveStorageImage(request, url, options.supabaseUrl)\n const rendered = await options.render(request)\n const response = options.hydrateAfterFirstPaint ? hydrateAfterFirstPaint(rendered) : rendered\n if (options.isCanonicalHost(url)) return response\n return withHeader(response, 'x-robots-tag', 'noindex, nofollow')\n }\n\n return async (incoming) => {\n const url = new URL(incoming.url)\n // Analytics leaves through the store's own domain, never cached.\n if (isPostHogProxyPath(url.pathname)) return posthogProxy(incoming)\n\n // Variants first: they change the HTML, so they are part of the cache key.\n const page = options.experiments && isPageRequest(incoming, url)\n ? await assignForRequest(incoming, options.experiments)\n : null\n const request = page ? withAssignment(incoming, page.assignments) : incoming\n // Cookies are added to what THIS visitor receives, never to the cached\n // copy (a shared entry carrying one visitor's id would hand it to the\n // next), and appended, so a cookie the page itself sets survives.\n const finish = (response: Response) => withCookies(response, page?.cookies ?? [])\n\n if (options.dev || !options.cache) return finish(await respond(request))\n\n const key = edgeCacheKey(request, options.buildId, page?.variant ?? '')\n if (!key) return finish(await respond(request))\n const hit = await options.cache.match(key)\n if (hit) return finish(withHeader(hit, 'x-edge-cache', 'HIT'))\n\n const response = await respond(request)\n if (!edgeCacheable(response)) return finish(withHeader(response, 'x-edge-cache', 'BYPASS'))\n options.waitUntil(options.cache.put(key, response.clone()))\n return finish(withHeader(response, 'x-edge-cache', 'MISS'))\n }\n}\n\n/** Minimal shape of the Workers runtime's streaming HTML rewriter. */\ninterface HtmlRewriterLike {\n on(selector: string, handlers: { element(el: HtmlElementLike): void }): HtmlRewriterLike\n transform(response: Response): Response\n}\ninterface HtmlElementLike {\n getAttribute(name: string): string | null\n remove(): void\n hasAttribute(name: string): boolean\n removeAttribute(name: string): void\n setInnerContent(content: string, options?: { html?: boolean }): void\n}\n\n/** The inline loader that replaces the entry tag: import once a frame is painted. */\nexport function deferredEntryScript(src: string): string {\n // rAF runs before the next paint; the timeout queued from it runs after.\n return `requestAnimationFrame(function(){setTimeout(function(){import(${JSON.stringify(src)})},0)})`\n}\n\n/**\n * Let the server-rendered page paint before the client bundle runs.\n *\n * TanStack Start emits its entry as `<script type=\"module\" async src>`, which\n * executes the moment it arrives. On a fast connection that is BEFORE the\n * browser's first paint, so a page that was complete as HTML sat blank while\n * ~1 MB of JavaScript was evaluated and hydrated — and Lighthouse, seeing the\n * script run before the paint, counted the whole bundle as render-blocking\n * (FCP 1.0 s one run, 3.3 s the next). The entry is rewritten into an inline\n * module that imports the same URL after the first frame; its\n * `<link rel=\"modulepreload\">` still downloads it in parallel, so nothing\n * arrives later — it only runs later, after the shopper sees the page.\n *\n * Only HTML responses, only an `async` module entry with a `src`; everything\n * else passes through. Without the Workers `HTMLRewriter` (tests, Node) it is a\n * no-op.\n */\nexport function hydrateAfterFirstPaint(response: Response): Response {\n const Rewriter = (globalThis as { HTMLRewriter?: new () => HtmlRewriterLike }).HTMLRewriter\n if (!Rewriter || !(response.headers.get('content-type') ?? '').includes('text/html')) return response\n return new Rewriter()\n // The entry's modulepreloads go too. A modulepreload makes the browser\n // compile the module the moment it lands — before the first paint on a\n // fast connection, the very work this rewrite moves after it — and on a\n // slow one it competes with the page's LCP image for the same pipe. The\n // after-paint import() fetches the bundle (and its chunks) itself, once.\n // (A plain rel=preload is NOT an alternative: Chrome does not reuse it for\n // a module import, and the bundle downloads twice — measured, 0.85.1.)\n .on('link[rel=\"modulepreload\"]', {\n element(el) {\n el.remove()\n },\n })\n .on('script[type=\"module\"][src]', {\n element(el) {\n const src = el.getAttribute('src')\n if (!src || !el.hasAttribute('async') || src.includes('<')) return\n el.removeAttribute('src')\n el.removeAttribute('async')\n el.setInnerContent(deferredEntryScript(src), { html: true })\n },\n })\n .transform(response)\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"edge-handler.d.ts","sourceRoot":"","sources":["../../src/host/edge-handler.ts"],"names":[],"mappings":"AACA,OAAO,EAYL,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EAC1B,MAAM,uBAAuB,CAAA;AAyC9B,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAElD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAEhE;AAED,yEAAyE;AACzE,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,QAAQ,CAAC,CA6BtH;AAED;6EAC6E;AAC7E,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,SAAK,GAAG,OAAO,GAAG,IAAI,CAa5F;AAED,8EAA8E;AAC9E,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAIzD;AAgBD,MAAM,WAAW,qBAAqB;IACpC,sEAAsE;IACtE,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IACtD,mDAAmD;IACnD,eAAe,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAA;IAClC,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAA;IACf,uEAAuE;IACvE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,wFAAwF;IACxF,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAA;IAC1C,oEAAoE;IACpE,KAAK,CAAC,EAAE,KAAK,CAAA;IACb;;;;;OAKG;IACH,GAAG,CAAC,EAAE,OAAO,CAAA;IACb;;;;OAIG;IACH,WAAW,CAAC,EAAE,yBAAyB,CAAA;IACvC;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAA;CACjC;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,GAAG,SAAS,CAAA;IAC1D;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAaD,UAAU,cAAc;IACtB,WAAW,EAAE,qBAAqB,CAAA;IAClC,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAA;IACf,qDAAqD;IACrD,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAAC,cAAc,CAAC,CAyBpH;AAkBD,qDAAqD;AACrD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,qBAAqB,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAqC5G;AAcD,qFAAqF;AACrF,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGvD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,QAAQ,GAAG,QAAQ,CAcnE"}
1
+ {"version":3,"file":"edge-handler.d.ts","sourceRoot":"","sources":["../../src/host/edge-handler.ts"],"names":[],"mappings":"AACA,OAAO,EAYL,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EAC1B,MAAM,uBAAuB,CAAA;AAyC9B,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAElD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAEhE;AAED,yEAAyE;AACzE,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,QAAQ,CAAC,CA6BtH;AAED;6EAC6E;AAC7E,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,SAAK,GAAG,OAAO,GAAG,IAAI,CAa5F;AAED,8EAA8E;AAC9E,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAIzD;AAgBD,MAAM,WAAW,qBAAqB;IACpC,sEAAsE;IACtE,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IACtD,mDAAmD;IACnD,eAAe,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAA;IAClC,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAA;IACf,uEAAuE;IACvE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,wFAAwF;IACxF,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAA;IAC1C,oEAAoE;IACpE,KAAK,CAAC,EAAE,KAAK,CAAA;IACb;;;;;OAKG;IACH,GAAG,CAAC,EAAE,OAAO,CAAA;IACb;;;;OAIG;IACH,WAAW,CAAC,EAAE,yBAAyB,CAAA;IACvC;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAA;CACjC;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,GAAG,SAAS,CAAA;IAC1D;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAaD,UAAU,cAAc;IACtB,WAAW,EAAE,qBAAqB,CAAA;IAClC,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAA;IACf,qDAAqD;IACrD,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAAC,cAAc,CAAC,CAyBpH;AAkBD,qDAAqD;AACrD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,qBAAqB,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAqC5G;AAeD,qFAAqF;AACrF,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGvD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,QAAQ,GAAG,QAAQ,CA0BnE"}
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "fayz": {
4
4
  "status": "beta"
5
5
  },
6
- "version": "0.85.2",
6
+ "version": "0.85.3",
7
7
  "description": "Fayz SDK storefront kit — Shopify-style customer-facing ecommerce template components",
8
8
  "type": "module",
9
9
  "sideEffects": false,
@@ -55,9 +55,9 @@
55
55
  "posthog-js": "^1.444.0",
56
56
  "@fayz-ai/core": "^0.79.0",
57
57
  "@fayz-ai/sdk": "^0.79.0",
58
- "@fayz-ai/auth": "^0.79.0",
59
58
  "@fayz-ai/shop": "^0.79.0",
60
- "@fayz-ai/ui": "^0.79.0"
59
+ "@fayz-ai/ui": "^0.79.0",
60
+ "@fayz-ai/auth": "^0.79.0"
61
61
  },
62
62
  "peerDependencies": {
63
63
  "@supabase/supabase-js": "^2.45.0",