@fayz-ai/storefront 0.82.0 → 0.83.1
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/analytics/meta-pixel.d.ts.map +1 -1
- package/dist/analytics/posthog.d.ts +3 -0
- package/dist/analytics/posthog.d.ts.map +1 -1
- package/dist/app/router.d.ts +2 -2
- package/dist/chunk-4REZOGGW.js +153 -0
- package/dist/chunk-4REZOGGW.js.map +1 -0
- package/dist/{chunk-UJHCVRK3.js → chunk-7N5HKQBM.js} +2 -2
- package/dist/{chunk-UJHCVRK3.js.map → chunk-7N5HKQBM.js.map} +1 -1
- package/dist/{chunk-GI5BTJK6.js → chunk-G2R6OJQA.js} +4 -4
- package/dist/{chunk-GI5BTJK6.js.map → chunk-G2R6OJQA.js.map} +1 -1
- package/dist/{chunk-RYWQGQ4M.js → chunk-OV6DVQMB.js} +128 -115
- package/dist/chunk-OV6DVQMB.js.map +1 -0
- package/dist/components/delivery/StoreCover.d.ts +0 -17
- package/dist/components/delivery/StoreCover.d.ts.map +1 -1
- package/dist/document/config.d.ts +7 -5
- package/dist/document/config.d.ts.map +1 -1
- package/dist/edge.d.ts +1 -1
- package/dist/edge.d.ts.map +1 -1
- package/dist/edge.js +16 -26
- package/dist/edge.js.map +1 -1
- package/dist/experiments/assign.d.ts +41 -53
- package/dist/experiments/assign.d.ts.map +1 -1
- package/dist/experiments/react.d.ts +4 -3
- package/dist/experiments/react.d.ts.map +1 -1
- package/dist/host/edge-handler.d.ts +15 -12
- package/dist/host/edge-handler.d.ts.map +1 -1
- package/dist/host.js +4 -4
- package/dist/index.js +7 -7
- package/dist/lib/theme.d.ts +2 -0
- package/dist/lib/theme.d.ts.map +1 -1
- package/dist/render.js +4 -4
- package/dist/tanstack.js +3 -3
- package/package.json +4 -4
- package/dist/chunk-746BEU7H.js +0 -160
- package/dist/chunk-746BEU7H.js.map +0 -1
- package/dist/chunk-RYWQGQ4M.js.map +0 -1
package/dist/lib/theme.d.ts
CHANGED
|
@@ -104,6 +104,8 @@ export interface ThemeCssOptions {
|
|
|
104
104
|
selector?: string;
|
|
105
105
|
}
|
|
106
106
|
export declare function themeToCss(theme: StorefrontTheme, options?: ThemeCssOptions): string;
|
|
107
|
+
/** The Google Fonts stylesheet for a theme, or null when it names none. Exported for tests. */
|
|
108
|
+
export declare function googleFontsHref(theme: StorefrontTheme): string | null;
|
|
107
109
|
/** Injects theme CSS variables + Google Fonts. Mount once inside the app root
|
|
108
110
|
* for the store theme; a page-scoped theme mounts another instance with its
|
|
109
111
|
* own `selector`. */
|
package/dist/lib/theme.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../src/lib/theme.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../src/lib/theme.ts"],"names":[],"mappings":"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"}
|
package/dist/render.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { validateStore, hydrateStoreDocument } from './chunk-
|
|
1
|
+
import { validateStore, hydrateStoreDocument } from './chunk-G2R6OJQA.js';
|
|
2
2
|
import { buildRobotsTxt, buildSitemapXml } from './chunk-WCAQRATH.js';
|
|
3
3
|
export { buildRobotsTxt, buildSitemapXml } from './chunk-WCAQRATH.js';
|
|
4
|
-
import { registerStorefrontBlocks, registerStorefrontEntities, resetPageDiagnostics, StorefrontShell, themeToCss, serializePageData, resolveStorefrontProvider } from './chunk-
|
|
5
|
-
import { resolveConfig, withStaticRenderPass, StorefrontConfigProvider, resolvePageData, storefrontPaths, isPublicPath } from './chunk-
|
|
6
|
-
import './chunk-
|
|
4
|
+
import { registerStorefrontBlocks, registerStorefrontEntities, resetPageDiagnostics, StorefrontShell, themeToCss, serializePageData, resolveStorefrontProvider } from './chunk-OV6DVQMB.js';
|
|
5
|
+
import { resolveConfig, withStaticRenderPass, StorefrontConfigProvider, resolvePageData, storefrontPaths, isPublicPath } from './chunk-7N5HKQBM.js';
|
|
6
|
+
import './chunk-4REZOGGW.js';
|
|
7
7
|
import { renderToStaticMarkup } from 'react-dom/server';
|
|
8
8
|
import { historyRouterAdapter, setBlockErrorVisibility, setBlockEditorMode, staticRouterAdapter, buildSeoTags, errorMessage } from '@fayz-ai/core';
|
|
9
9
|
import { createMockShopProvider } from '@fayz-ai/shop/mock';
|
package/dist/tanstack.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { StorefrontConfigHost, ExperimentProvider, StorefrontShell } from './chunk-
|
|
2
|
-
import { PageDataScope, StorefrontRouterProvider } from './chunk-
|
|
3
|
-
import './chunk-
|
|
1
|
+
import { StorefrontConfigHost, ExperimentProvider, StorefrontShell } from './chunk-OV6DVQMB.js';
|
|
2
|
+
import { PageDataScope, StorefrontRouterProvider } from './chunk-7N5HKQBM.js';
|
|
3
|
+
import './chunk-4REZOGGW.js';
|
|
4
4
|
import { useEffect, useMemo, useState } from 'react';
|
|
5
5
|
import { useMatches, useRouter, useRouterState } from '@tanstack/react-router';
|
|
6
6
|
import { hostRouterAdapter } from '@fayz-ai/core';
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"fayz": {
|
|
4
4
|
"status": "beta"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.83.1",
|
|
7
7
|
"description": "Fayz SDK storefront kit — Shopify-style customer-facing ecommerce template components",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"sideEffects": false,
|
|
@@ -54,10 +54,10 @@
|
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"posthog-js": "^1.444.0",
|
|
56
56
|
"@fayz-ai/auth": "^0.79.0",
|
|
57
|
-
"@fayz-ai/core": "^0.79.0",
|
|
58
|
-
"@fayz-ai/sdk": "^0.79.0",
|
|
59
57
|
"@fayz-ai/shop": "^0.79.0",
|
|
60
|
-
"@fayz-ai/
|
|
58
|
+
"@fayz-ai/sdk": "^0.79.0",
|
|
59
|
+
"@fayz-ai/ui": "^0.79.0",
|
|
60
|
+
"@fayz-ai/core": "^0.79.0"
|
|
61
61
|
},
|
|
62
62
|
"peerDependencies": {
|
|
63
63
|
"@supabase/supabase-js": "^2.45.0",
|
package/dist/chunk-746BEU7H.js
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
// src/experiments/assign.ts
|
|
2
|
-
var CONTROL = "control";
|
|
3
|
-
var EXPERIMENT_HEADER = "x-fayz-experiments";
|
|
4
|
-
var VISITOR_COOKIE = "fz_vid";
|
|
5
|
-
var ASSIGNMENT_COOKIE = "fz_exp";
|
|
6
|
-
var ASSIGNMENT_TTL_SECONDS = 1800;
|
|
7
|
-
var NO_ASSIGNMENTS = Object.freeze({ flags: {} });
|
|
8
|
-
var FLAG_KEY = /^[A-Za-z0-9_-]{1,100}$/;
|
|
9
|
-
function validateExperiments(experiments) {
|
|
10
|
-
const issues = [];
|
|
11
|
-
for (const [key, experiment] of Object.entries(experiments ?? {})) {
|
|
12
|
-
if (!FLAG_KEY.test(key)) issues.push(`experiments.${key}: the key must be a PostHog flag key (letters, digits, - and _)`);
|
|
13
|
-
const variants = experiment?.variants ?? [];
|
|
14
|
-
if (!variants.includes(CONTROL)) issues.push(`experiments.${key}: variants must include "${CONTROL}"`);
|
|
15
|
-
if (variants.length < 2) issues.push(`experiments.${key}: an experiment needs at least two variants`);
|
|
16
|
-
if (new Set(variants).size !== variants.length) issues.push(`experiments.${key}: variants repeat`);
|
|
17
|
-
for (const variant of variants) {
|
|
18
|
-
if (!FLAG_KEY.test(variant)) issues.push(`experiments.${key}: variant "${variant}" is not a valid variant key`);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
return issues;
|
|
22
|
-
}
|
|
23
|
-
var BOT_UA = /bot\b|crawler|spider|crawling|slurp|bingpreview|facebookexternalhit|facebot|meta-externalagent|whatsapp|telegrambot|twitterbot|linkedinbot|discordbot|slackbot|embedly|pinterest|google-inspectiontool|googleother|lighthouse|headlesschrome|pagespeed|uptime|pingdom|curl\/|wget\/|python-requests|node-fetch|axios\//i;
|
|
24
|
-
function isBotUserAgent(userAgent) {
|
|
25
|
-
if (!userAgent) return true;
|
|
26
|
-
return BOT_UA.test(userAgent);
|
|
27
|
-
}
|
|
28
|
-
function readCookie(header, name) {
|
|
29
|
-
if (!header) return void 0;
|
|
30
|
-
for (const part of header.split(";")) {
|
|
31
|
-
const eq = part.indexOf("=");
|
|
32
|
-
if (eq < 0) continue;
|
|
33
|
-
if (part.slice(0, eq).trim() === name) return part.slice(eq + 1).trim();
|
|
34
|
-
}
|
|
35
|
-
return void 0;
|
|
36
|
-
}
|
|
37
|
-
function readVisitorId(cookieHeader, posthogKey) {
|
|
38
|
-
const posthog = readCookie(cookieHeader, `ph_${posthogKey}_posthog`);
|
|
39
|
-
if (posthog) {
|
|
40
|
-
try {
|
|
41
|
-
const id = JSON.parse(decodeURIComponent(posthog)).distinct_id;
|
|
42
|
-
if (typeof id === "string" && id) return id;
|
|
43
|
-
} catch {
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
const own = readCookie(cookieHeader, VISITOR_COOKIE);
|
|
47
|
-
return own && /^[A-Za-z0-9-]{8,64}$/.test(own) ? own : void 0;
|
|
48
|
-
}
|
|
49
|
-
function visitorCookie(id, secure = true) {
|
|
50
|
-
return `${VISITOR_COOKIE}=${id}; Path=/; Max-Age=31536000; SameSite=Lax${secure ? "; Secure" : ""}`;
|
|
51
|
-
}
|
|
52
|
-
function parsePayload(raw) {
|
|
53
|
-
if (typeof raw !== "string") return raw;
|
|
54
|
-
try {
|
|
55
|
-
return JSON.parse(raw);
|
|
56
|
-
} catch {
|
|
57
|
-
return raw;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
async function evaluateExperiments(input) {
|
|
61
|
-
const keys = Object.keys(input.experiments);
|
|
62
|
-
if (!keys.length || !input.posthogKey) return { distinctId: input.distinctId, flags: {} };
|
|
63
|
-
const doFetch = input.fetch ?? fetch;
|
|
64
|
-
const controller = new AbortController();
|
|
65
|
-
const timer = setTimeout(() => controller.abort(), input.timeoutMs ?? 400);
|
|
66
|
-
try {
|
|
67
|
-
const response = await doFetch(`${(input.apiHost ?? "https://us.i.posthog.com").replace(/\/$/, "")}/flags?v=2`, {
|
|
68
|
-
method: "POST",
|
|
69
|
-
headers: { "content-type": "application/json" },
|
|
70
|
-
body: JSON.stringify({ api_key: input.posthogKey, distinct_id: input.distinctId }),
|
|
71
|
-
signal: controller.signal
|
|
72
|
-
});
|
|
73
|
-
if (!response.ok) return { distinctId: input.distinctId, flags: {} };
|
|
74
|
-
const body = await response.json();
|
|
75
|
-
const flags = {};
|
|
76
|
-
for (const key of keys) {
|
|
77
|
-
const flag = body.flags?.[key];
|
|
78
|
-
const variant = flag?.enabled && typeof flag.variant === "string" ? flag.variant : void 0;
|
|
79
|
-
if (!variant || !input.experiments[key].variants.includes(variant)) continue;
|
|
80
|
-
const payload = parsePayload(flag?.metadata?.payload);
|
|
81
|
-
flags[key] = payload === void 0 || payload === null ? { variant } : { variant, payload };
|
|
82
|
-
}
|
|
83
|
-
return { distinctId: input.distinctId, flags, answered: true };
|
|
84
|
-
} catch {
|
|
85
|
-
return { distinctId: input.distinctId, flags: {} };
|
|
86
|
-
} finally {
|
|
87
|
-
clearTimeout(timer);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
function variantOf(assignments, key) {
|
|
91
|
-
return assignments?.flags[key]?.variant ?? CONTROL;
|
|
92
|
-
}
|
|
93
|
-
function experimentCacheSuffix(assignments, keys) {
|
|
94
|
-
return [...keys].sort().map((key) => [key, variantOf(assignments, key)]).filter(([, variant]) => variant !== CONTROL).map(([key, variant]) => `${key}:${variant}`).join(",");
|
|
95
|
-
}
|
|
96
|
-
function encodeExperimentHeader(assignments) {
|
|
97
|
-
return encodeURIComponent(JSON.stringify(assignments));
|
|
98
|
-
}
|
|
99
|
-
function decodeExperimentHeader(value) {
|
|
100
|
-
if (!value) return null;
|
|
101
|
-
try {
|
|
102
|
-
const parsed = JSON.parse(decodeURIComponent(value));
|
|
103
|
-
if (!parsed || typeof parsed !== "object" || typeof parsed.flags !== "object" || parsed.flags === null) return null;
|
|
104
|
-
const flags = {};
|
|
105
|
-
for (const [key, a] of Object.entries(parsed.flags)) {
|
|
106
|
-
if (a && typeof a === "object" && typeof a.variant === "string") {
|
|
107
|
-
flags[key] = a;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return {
|
|
111
|
-
...typeof parsed.distinctId === "string" && parsed.distinctId ? { distinctId: parsed.distinctId } : {},
|
|
112
|
-
flags
|
|
113
|
-
};
|
|
114
|
-
} catch {
|
|
115
|
-
return null;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
function experimentsFingerprint(experiments) {
|
|
119
|
-
const text = Object.keys(experiments).sort().map((key) => `${key}=${[...experiments[key].variants].sort().join("|")}`).join(";");
|
|
120
|
-
let hash = 2166136261;
|
|
121
|
-
for (let i = 0; i < text.length; i++) {
|
|
122
|
-
hash ^= text.charCodeAt(i);
|
|
123
|
-
hash = Math.imul(hash, 16777619);
|
|
124
|
-
}
|
|
125
|
-
return (hash >>> 0).toString(36);
|
|
126
|
-
}
|
|
127
|
-
function readAssignmentCookie(cookieHeader, input) {
|
|
128
|
-
const raw = readCookie(cookieHeader, ASSIGNMENT_COOKIE);
|
|
129
|
-
if (!raw) return null;
|
|
130
|
-
try {
|
|
131
|
-
const parsed = JSON.parse(decodeURIComponent(raw));
|
|
132
|
-
if (parsed.f !== experimentsFingerprint(input.experiments) || parsed.i !== input.distinctId) return null;
|
|
133
|
-
if (!parsed.v || typeof parsed.v !== "object") return null;
|
|
134
|
-
const flags = {};
|
|
135
|
-
for (const [key, variant] of Object.entries(parsed.v)) {
|
|
136
|
-
const declared = input.experiments[key];
|
|
137
|
-
if (!declared || typeof variant !== "string" || !declared.variants.includes(variant)) return null;
|
|
138
|
-
flags[key] = { variant };
|
|
139
|
-
}
|
|
140
|
-
return { distinctId: input.distinctId, flags, answered: true };
|
|
141
|
-
} catch {
|
|
142
|
-
return null;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
function assignmentCookie(assignments, experiments, secure = true) {
|
|
146
|
-
if (!assignments.answered || !assignments.distinctId) return null;
|
|
147
|
-
if (Object.values(assignments.flags).some((a) => a.payload !== void 0)) return null;
|
|
148
|
-
const value = {
|
|
149
|
-
f: experimentsFingerprint(experiments),
|
|
150
|
-
i: assignments.distinctId,
|
|
151
|
-
v: Object.fromEntries(Object.entries(assignments.flags).map(([key, a]) => [key, a.variant]))
|
|
152
|
-
};
|
|
153
|
-
const encoded = encodeURIComponent(JSON.stringify(value));
|
|
154
|
-
if (encoded.length > 3e3) return null;
|
|
155
|
-
return `${ASSIGNMENT_COOKIE}=${encoded}; Path=/; Max-Age=${ASSIGNMENT_TTL_SECONDS}; SameSite=Lax${secure ? "; Secure" : ""}`;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
export { CONTROL, EXPERIMENT_HEADER, NO_ASSIGNMENTS, VISITOR_COOKIE, assignmentCookie, decodeExperimentHeader, encodeExperimentHeader, evaluateExperiments, experimentCacheSuffix, isBotUserAgent, readAssignmentCookie, readVisitorId, validateExperiments, visitorCookie };
|
|
159
|
-
//# sourceMappingURL=chunk-746BEU7H.js.map
|
|
160
|
-
//# sourceMappingURL=chunk-746BEU7H.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/experiments/assign.ts"],"names":[],"mappings":"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","file":"chunk-746BEU7H.js","sourcesContent":["// ---------------------------------------------------------------------------\n// Experiment assignment, decided before the page renders.\n//\n// A storefront experiment is a PostHog multivariate feature flag. Which variant\n// a visitor sees is decided ONCE per request, on the server (the Worker in\n// front of the store), from a stable anonymous id — so the HTML that leaves\n// the server is already the variant: no flash of the control, no layout jump,\n// and the variant the shopper sees is the variant PostHog counts.\n//\n// Framework-free and React-free: the edge handler calls it, and the browser\n// only ever receives its result (see `ExperimentProvider`).\n//\n// Every failure answers 'control': no flags configured, PostHog slow or down,\n// a flag that is off, a variant the store did not declare, a crawler. An\n// experiment can lose data; it must never break or change the page for a\n// visitor it could not assign.\n// ---------------------------------------------------------------------------\n\n/** The variant every visitor gets when nothing else can be decided. */\nexport const CONTROL = 'control'\n\n/** Request header the edge hands the assignment to the renderer in. */\nexport const EXPERIMENT_HEADER = 'x-fayz-experiments'\n\n/** First-party cookie holding the anonymous id when PostHog has not set one yet. */\nexport const VISITOR_COOKIE = 'fz_vid'\n\n/**\n * First-party cookie remembering what PostHog answered for this visitor, so\n * the edge asks once per visitor per half hour instead of before every page —\n * a flags round trip in front of every navigation, cache hits included, is a\n * tax every shopper pays for a test most of them are not looking at.\n */\nexport const ASSIGNMENT_COOKIE = 'fz_exp'\n\n/** How long a remembered assignment is trusted. Stopping or re-rolling an\n * experiment in PostHog reaches every visitor within this window. */\nexport const ASSIGNMENT_TTL_SECONDS = 1800\n\n/** One experiment the store runs. */\nexport interface StorefrontExperiment {\n /**\n * The variant keys, as configured on the PostHog flag. Must include\n * 'control'. A variant PostHog answers that is not listed here is treated\n * as control: the page has no rendering for it.\n */\n variants: readonly string[]\n /** What is being tested and why — for whoever reads the config next. */\n description?: string\n}\n\n/** What a visitor sees for one experiment. */\nexport interface ExperimentAssignment {\n variant: string\n /** The flag's JSON payload for this variant, when the flag has one. */\n payload?: unknown\n}\n\n/** Everything the edge decided for one request. */\nexport interface ExperimentAssignments {\n /** The id the variants were computed for. Kept at the edge: it never\n * travels into the rendered (and cached) page — see `withAssignment`. */\n distinctId?: string\n /**\n * By flag key, ONLY what PostHog actually assigned — 'control' included when\n * PostHog said control. A key the store declares but that is missing here is\n * rendered as control but is not part of the experiment: it reports no\n * exposure, so a timeout never lands a visitor in the control group.\n */\n flags: Record<string, ExperimentAssignment>\n /** PostHog answered (as opposed to: not asked, slow, down, unparsable). */\n answered?: boolean\n}\n\n/** The empty decision: every experiment at control. */\nexport const NO_ASSIGNMENTS: ExperimentAssignments = Object.freeze({ flags: {} }) as ExperimentAssignments\n\nconst FLAG_KEY = /^[A-Za-z0-9_-]{1,100}$/\n\n/**\n * Problems with a store's experiment declarations, empty when valid. Checked\n * when the host is created, so a typo fails the build rather than silently\n * running a page with no variants.\n */\nexport function validateExperiments(experiments: Record<string, StorefrontExperiment> | undefined): string[] {\n const issues: string[] = []\n for (const [key, experiment] of Object.entries(experiments ?? {})) {\n if (!FLAG_KEY.test(key)) issues.push(`experiments.${key}: the key must be a PostHog flag key (letters, digits, - and _)`)\n const variants = experiment?.variants ?? []\n if (!variants.includes(CONTROL)) issues.push(`experiments.${key}: variants must include \"${CONTROL}\"`)\n if (variants.length < 2) issues.push(`experiments.${key}: an experiment needs at least two variants`)\n if (new Set(variants).size !== variants.length) issues.push(`experiments.${key}: variants repeat`)\n for (const variant of variants) {\n if (!FLAG_KEY.test(variant)) issues.push(`experiments.${key}: variant \"${variant}\" is not a valid variant key`)\n }\n }\n return issues\n}\n\n// Crawlers, link unfurlers and monitors. They always see control: what is\n// indexed and what a share card shows must not depend on a coin flip, and a\n// bot must not count as an exposure.\nconst BOT_UA =\n /bot\\b|crawler|spider|crawling|slurp|bingpreview|facebookexternalhit|facebot|meta-externalagent|whatsapp|telegrambot|twitterbot|linkedinbot|discordbot|slackbot|embedly|pinterest|google-inspectiontool|googleother|lighthouse|headlesschrome|pagespeed|uptime|pingdom|curl\\/|wget\\/|python-requests|node-fetch|axios\\//i\n\nexport function isBotUserAgent(userAgent: string | null | undefined): boolean {\n if (!userAgent) return true\n return BOT_UA.test(userAgent)\n}\n\nfunction readCookie(header: string | null | undefined, name: string): string | undefined {\n if (!header) return undefined\n for (const part of header.split(';')) {\n const eq = part.indexOf('=')\n if (eq < 0) continue\n if (part.slice(0, eq).trim() === name) return part.slice(eq + 1).trim()\n }\n return undefined\n}\n\n/**\n * The visitor's stable anonymous id, if the browser already has one:\n * posthog-js's own cookie first (so the edge evaluates flags for the very id\n * the browser reports events under), then the store's first-party cookie.\n */\nexport function readVisitorId(cookieHeader: string | null | undefined, posthogKey: string): string | undefined {\n const posthog = readCookie(cookieHeader, `ph_${posthogKey}_posthog`)\n if (posthog) {\n try {\n const id = (JSON.parse(decodeURIComponent(posthog)) as { distinct_id?: unknown }).distinct_id\n if (typeof id === 'string' && id) return id\n } catch {\n /* unreadable cookie: fall through to ours */\n }\n }\n const own = readCookie(cookieHeader, VISITOR_COOKIE)\n return own && /^[A-Za-z0-9-]{8,64}$/.test(own) ? own : undefined\n}\n\n/** `Set-Cookie` for a newly minted visitor id: a year, first-party, not readable cross-site. */\nexport function visitorCookie(id: string, secure = true): string {\n return `${VISITOR_COOKIE}=${id}; Path=/; Max-Age=31536000; SameSite=Lax${secure ? '; Secure' : ''}`\n}\n\ninterface FlagsV2Response {\n flags?: Record<string, { enabled?: boolean; variant?: string | null; metadata?: { payload?: unknown } }>\n}\n\nfunction parsePayload(raw: unknown): unknown {\n if (typeof raw !== 'string') return raw\n try {\n return JSON.parse(raw)\n } catch {\n return raw\n }\n}\n\nexport interface EvaluateExperimentsInput {\n /** The store's declared experiments. Only these keys are read from the answer. */\n experiments: Record<string, StorefrontExperiment>\n distinctId: string\n /** The PostHog project token (`phc_…`). */\n posthogKey: string\n /** Default `https://us.i.posthog.com`. */\n apiHost?: string\n /** PostHog is asked with a deadline; past it, every experiment is control. Default 400 ms. */\n timeoutMs?: number\n fetch?: typeof fetch\n}\n\n/**\n * Ask PostHog which variant this visitor is in, for every declared experiment.\n * Never throws and never waits past the deadline.\n */\nexport async function evaluateExperiments(input: EvaluateExperimentsInput): Promise<ExperimentAssignments> {\n const keys = Object.keys(input.experiments)\n if (!keys.length || !input.posthogKey) return { distinctId: input.distinctId, flags: {} }\n const doFetch = input.fetch ?? fetch\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), input.timeoutMs ?? 400)\n try {\n const response = await doFetch(`${(input.apiHost ?? 'https://us.i.posthog.com').replace(/\\/$/, '')}/flags?v=2`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ api_key: input.posthogKey, distinct_id: input.distinctId }),\n signal: controller.signal,\n })\n if (!response.ok) return { distinctId: input.distinctId, flags: {} }\n const body = (await response.json()) as FlagsV2Response\n const flags: Record<string, ExperimentAssignment> = {}\n for (const key of keys) {\n const flag = body.flags?.[key]\n const variant = flag?.enabled && typeof flag.variant === 'string' ? flag.variant : undefined\n // Off, missing, boolean-only, or a variant the page cannot render: control.\n if (!variant || !input.experiments[key]!.variants.includes(variant)) continue\n const payload = parsePayload(flag?.metadata?.payload)\n flags[key] = payload === undefined || payload === null ? { variant } : { variant, payload }\n }\n return { distinctId: input.distinctId, flags, answered: true }\n } catch {\n return { distinctId: input.distinctId, flags: {} }\n } finally {\n clearTimeout(timer)\n }\n}\n\n/** The variant of `key`, control when unassigned. */\nexport function variantOf(assignments: ExperimentAssignments | null | undefined, key: string): string {\n return assignments?.flags[key]?.variant ?? CONTROL\n}\n\n/**\n * The part of the edge cache key that depends on the visitor: one entry per\n * combination of variants, in a fixed order. Control-only visitors (and every\n * crawler) share the plain key with the page as it was before any experiment.\n */\nexport function experimentCacheSuffix(assignments: ExperimentAssignments, keys: readonly string[]): string {\n return [...keys]\n .sort()\n .map((key) => [key, variantOf(assignments, key)] as const)\n .filter(([, variant]) => variant !== CONTROL)\n .map(([key, variant]) => `${key}:${variant}`)\n .join(',')\n}\n\n/** The assignment as a header value (URI-encoded JSON: payloads may carry any text). */\nexport function encodeExperimentHeader(assignments: ExperimentAssignments): string {\n return encodeURIComponent(JSON.stringify(assignments))\n}\n\n/** The renderer's side: never throws, garbage reads as no assignment. */\nexport function decodeExperimentHeader(value: string | null | undefined): ExperimentAssignments | null {\n if (!value) return null\n try {\n const parsed = JSON.parse(decodeURIComponent(value)) as Partial<ExperimentAssignments>\n if (!parsed || typeof parsed !== 'object' || typeof parsed.flags !== 'object' || parsed.flags === null) return null\n const flags: Record<string, ExperimentAssignment> = {}\n for (const [key, a] of Object.entries(parsed.flags)) {\n if (a && typeof a === 'object' && typeof (a as ExperimentAssignment).variant === 'string') {\n flags[key] = a as ExperimentAssignment\n }\n }\n return {\n ...(typeof parsed.distinctId === 'string' && parsed.distinctId ? { distinctId: parsed.distinctId } : {}),\n flags,\n }\n } catch {\n return null\n }\n}\n\n/**\n * A short, stable fingerprint of the declared experiments. A remembered\n * assignment is only trusted for the same declarations: adding an experiment\n * or a variant re-asks PostHog on the next page instead of waiting out the TTL.\n */\nexport function experimentsFingerprint(experiments: Record<string, StorefrontExperiment>): string {\n const text = Object.keys(experiments)\n .sort()\n .map((key) => `${key}=${[...experiments[key]!.variants].sort().join('|')}`)\n .join(';')\n // FNV-1a, 32 bit: a cache discriminator, not a security boundary.\n let hash = 0x811c9dc5\n for (let i = 0; i < text.length; i++) {\n hash ^= text.charCodeAt(i)\n hash = Math.imul(hash, 0x01000193)\n }\n return (hash >>> 0).toString(36)\n}\n\ninterface AssignmentCookie {\n /** `experimentsFingerprint` of the declarations it was decided under. */\n f: string\n /** The visitor id it was decided for. */\n i: string\n /** Flag key → variant. */\n v: Record<string, string>\n}\n\n/**\n * The remembered assignment, when it is for this visitor and these\n * declarations. Tampering only lets a visitor pick a variant the store\n * declared — the same page any other visitor may get — so it is validated, not\n * signed; anything that does not validate is ignored and PostHog is asked.\n */\nexport function readAssignmentCookie(\n cookieHeader: string | null | undefined,\n input: { experiments: Record<string, StorefrontExperiment>; distinctId: string },\n): ExperimentAssignments | null {\n const raw = readCookie(cookieHeader, ASSIGNMENT_COOKIE)\n if (!raw) return null\n try {\n const parsed = JSON.parse(decodeURIComponent(raw)) as Partial<AssignmentCookie>\n if (parsed.f !== experimentsFingerprint(input.experiments) || parsed.i !== input.distinctId) return null\n if (!parsed.v || typeof parsed.v !== 'object') return null\n const flags: Record<string, ExperimentAssignment> = {}\n for (const [key, variant] of Object.entries(parsed.v)) {\n const declared = input.experiments[key]\n if (!declared || typeof variant !== 'string' || !declared.variants.includes(variant)) return null\n flags[key] = { variant }\n }\n return { distinctId: input.distinctId, flags, answered: true }\n } catch {\n return null\n }\n}\n\n/** `Set-Cookie` remembering an assignment PostHog made. Null when there is nothing worth remembering. */\nexport function assignmentCookie(\n assignments: ExperimentAssignments,\n experiments: Record<string, StorefrontExperiment>,\n secure = true,\n): string | null {\n if (!assignments.answered || !assignments.distinctId) return null\n // A variant with a payload is not remembered: the payload is the flag's to\n // change, and the cookie would freeze it. Those visitors are asked each page.\n if (Object.values(assignments.flags).some((a) => a.payload !== undefined)) return null\n const value: AssignmentCookie = {\n f: experimentsFingerprint(experiments),\n i: assignments.distinctId,\n v: Object.fromEntries(Object.entries(assignments.flags).map(([key, a]) => [key, a.variant])),\n }\n const encoded = encodeURIComponent(JSON.stringify(value))\n if (encoded.length > 3000) return null\n return `${ASSIGNMENT_COOKIE}=${encoded}; Path=/; Max-Age=${ASSIGNMENT_TTL_SECONDS}; SameSite=Lax${secure ? '; Secure' : ''}`\n}\n"]}
|