@fast-simon/dashboard-utilities 1.0.160-beta.2 → 1.0.160
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.
|
@@ -153,8 +153,13 @@ export function buildLiveSessionParams(data, requestType = 'collection', persona
|
|
|
153
153
|
// SPC only sent with collections (not supported in search serving)
|
|
154
154
|
const filteredSpc = requestType === 'collection' ? filterAccumulator(data.spc, limits) : undefined;
|
|
155
155
|
// spv OR spc enables the preview — a cart-only session (size personalization) is valid.
|
|
156
|
+
// Active personalization types ALSO enable it with an empty session: sites that run the
|
|
157
|
+
// size-availability correction from n=0 (empty cart -> population-prior baseline) still need
|
|
158
|
+
// the request to carry `personalization`, otherwise serving's use_filter_personalization stays
|
|
159
|
+
// off and the cold-start size down-ranking a real first-time shopper sees can't be previewed.
|
|
160
|
+
const hasTypes = personalizationTypes.length > 0;
|
|
156
161
|
return {
|
|
157
|
-
personalization_preview: (filteredSpv || filteredSpc) ? {
|
|
162
|
+
personalization_preview: (filteredSpv || filteredSpc || hasTypes) ? {
|
|
158
163
|
enabled: true,
|
|
159
164
|
spvJson: filteredSpv ? JSON.stringify(filteredSpv) : '',
|
|
160
165
|
spcJson: filteredSpc ? JSON.stringify(filteredSpc) : '',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"liveSessionPreview.js","sourceRoot":"","sources":["../../src/utils/liveSessionPreview.ts"],"names":[],"mappings":"AA0BA,MAAM,UAAU,GAAmC;IAC/C,UAAU,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE;IAChD,MAAM,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE;CAC7C,CAAC;AAEF,MAAM,uBAAuB,GAAG,2BAA2B,CAAC;AAC5D,MAAM,kBAAkB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,qDAAqD;AAEhG,sEAAsE;AACtE,IAAI,UAAU,GAAG,EAAE,CAAC;AAEpB;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAAY;IAChD,UAAU,GAAG,IAAI,CAAC;AACtB,CAAC;AAED,SAAS,iBAAiB;IACtB,OAAO,UAAU,CAAC,CAAC,CAAC,GAAG,uBAAuB,IAAI,UAAU,EAAE,CAAC,CAAC,CAAC,uBAAuB,CAAC;AAC7F,CAAC;AAED,8CAA8C;AAC9C,MAAM,SAAS,GAAG,kCAAkC,CAAC;AAErD;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,WAAoB;IACrD,MAAM,GAAG,GAAG,WAAW,aAAX,WAAW,cAAX,WAAW,GAAI,IAAI,CAAC,GAAG,EAAE,CAAC;IACtC,IAAI,EAAE,GAAG,GAAG,CAAC;IACb,MAAM,QAAQ,GAAG,IAAI,KAAK,CAAC,EAAE,CAAC,CAAC;IAC/B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE;QACzB,QAAQ,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;QACnC,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;KAC5B;IACD,MAAM,QAAQ,GAAG,IAAI,KAAK,CAAC,EAAE,CAAC,CAAC;IAC/B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE;QACzB,QAAQ,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;KAC3D;IACD,OAAO,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACvD,CAAC;AAED,MAAM,WAAW,GAAoB;IACjC,OAAO,EAAE,KAAK;IACd,YAAY,EAAE,EAAE;IAChB,GAAG,EAAE,EAAE;IACP,OAAO,EAAE,EAAE;IACX,GAAG,EAAE,EAAE;IACP,OAAO,EAAE,EAAE;IACX,SAAS,EAAE,CAAC;IACZ,UAAU,EAAE,CAAC;CAChB,CAAC;AAEF,MAAM,UAAU,kBAAkB;IAC9B,IAAI;QACA,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAC;QACzD,IAAI,MAAM,EAAE;YACR,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAClC,IAAI,CAAC,MAAM,CAAC,OAAO;gBAAE,MAAM,CAAC,OAAO,GAAG,EAAE,CAAC;YACzC,mFAAmF;YACnF,kFAAkF;YAClF,+EAA+E;YAC/E,MAAM,CAAC,GAAG,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACpG,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC5G,IAAI,CAAC,MAAM,CAAC,UAAU;gBAAE,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC,SAAS,IAAI,CAAC,CAAC;YAClE,OAAO,MAAM,CAAC;SACjB;KACJ;IAAC,OAAO,CAAC,EAAE;QACR,wBAAwB;KAC3B;IACD,yBAAY,WAAW,EAAG;AAC9B,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,IAAqB;IACpD,YAAY,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;AACpE,CAAC;AAED;;GAEG;AACH,SAAS,gBAAgB,CAAC,IAAqB;IAC3C,IAAI,CAAC,IAAI,CAAC,UAAU;QAAE,OAAO,KAAK,CAAC;IACnC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,kBAAkB,CAAC;AAChE,CAAC;AAED;;GAEG;AACH,SAAS,YAAY,CAAC,IAAqB;IACvC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,OAAO,IAAI,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,SAAS,iBAAiB,CACtB,GAA2B,EAC3B,MAAiB;IAEjB,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,MAAM,CAAC,UAAU,GAAG,KAAK,CAAC;IACzE,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACpC,qCAAqC;IACrC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAE3C,MAAM,MAAM,GAAG,OAAO;SACjB,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,IAAI,MAAM,CAAC;SACjC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;SAC3B,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IAE1B,IAAI,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG;QAAE,OAAO,SAAS,CAAC;IACjD,OAAO,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;AACtC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,SAAiB,EAAE,IAAqB;IACnE,MAAM,IAAI,GAAG,YAAY,CAAC,kBAAkB,EAAE,CAAC,CAAC;IAChD,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IACpD,IAAI,IAAI;QAAE,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC;IACzC,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACzB,OAAO,IAAI,CAAC;AAChB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,iBAAyB,EAAE,IAAqB;IAC7E,2FAA2F;IAC3F,iDAAiD;IACjD,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE;QACnC,OAAO,kBAAkB,EAAE,CAAC;KAC/B;IACD,MAAM,IAAI,GAAG,YAAY,CAAC,kBAAkB,EAAE,CAAC,CAAC;IAChD,IAAI,CAAC,GAAG,CAAC,iBAAiB,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAC5D,IAAI,IAAI;QAAE,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,GAAG,IAAI,CAAC;IACjD,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACzB,OAAO,IAAI,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAAqB,EAAE,cAA2B,YAAY,EAAE,uBAAiC,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;IAK9J,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE;QACf,OAAO,EAAE,uBAAuB,EAAE,IAAI,EAAE,aAAa,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,EAAE,CAAC;KACpG;IAED,MAAM,MAAM,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;IACvC,MAAM,WAAW,GAAG,iBAAiB,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACxD,mEAAmE;IACnE,MAAM,WAAW,GAAG,WAAW,KAAK,YAAY,CAAC,CAAC,CAAC,iBAAiB,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAEnG,wFAAwF;IACxF,OAAO;QACH,uBAAuB,EAAE,CAAC,WAAW,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC;YACpD,OAAO,EAAE,IAAI;YACb,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE;YACvD,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE;YACvD,KAAK,EAAE,oBAAoB;SAC9B,CAAC,CAAC,CAAC,IAAI;QACR,aAAa,EAAE,IAAI,CAAC,YAAY,IAAI,SAAS;QAC7C,oBAAoB,EAAE,uBAAuB,CAAC,IAAI,CAAC,SAAS,CAAC;KAChE,CAAC;AACN,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAA4B;IAC1D,MAAM,IAAI,GAAG,YAAY,CAAC,kBAAkB,EAAE,CAAC,CAAC;IAChD,MAAM,cAAc,GAAG,OAAO,KAAK,WAAW;QAC1C,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAE,aAAa;QACrD,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;IACjB,IAAI,CAAC,YAAY,GAAG,oBAAoB,CAAC,cAAc,CAAC,CAAC;IACzD,IAAI,CAAC,SAAS,GAAG,cAAc,CAAC;IAChC,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACzB,OAAO,IAAI,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB;IAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,MAAM,IAAI,GAAoB;QAC1B,OAAO,EAAE,IAAI;QACb,YAAY,EAAE,oBAAoB,EAAE;QACpC,GAAG,EAAE,EAAE;QACP,OAAO,EAAE,EAAE;QACX,GAAG,EAAE,EAAE;QACP,OAAO,EAAE,EAAE;QACX,SAAS,EAAE,GAAG;QACd,UAAU,EAAE,GAAG;KAClB,CAAC;IACF,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACzB,OAAO,IAAI,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB;IAC5B,MAAM,QAAQ,GAAG,kBAAkB,EAAE,CAAC;IACtC,IAAI,QAAQ,CAAC,YAAY,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,EAAE;QACtD,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC;QACxB,QAAQ,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACjC,kBAAkB,CAAC,QAAQ,CAAC,CAAC;QAC7B,OAAO,QAAQ,CAAC;KACnB;IACD,uCAAuC;IACvC,OAAO,gBAAgB,EAAE,CAAC;AAC9B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe;IAC3B,MAAM,IAAI,GAAG,kBAAkB,EAAE,CAAC;IAClC,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;IACrB,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACzB,OAAO,IAAI,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,SAAiB;IAC5C,IAAI,CAAC,SAAS;QAAE,OAAO,KAAK,CAAC;IAC7B,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,GAAG,KAAK,CAAC,CAAC;IAClE,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IACrD,MAAM,OAAO,GAAG,YAAY,GAAG,EAAE,CAAC;IAClC,IAAI,IAAI,GAAG,CAAC;QAAE,OAAO,GAAG,IAAI,KAAK,KAAK,GAAG,CAAC;IAC1C,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,GAAG,KAAK,KAAK,OAAO,GAAG,CAAC;IAC9C,OAAO,GAAG,OAAO,GAAG,CAAC;AACzB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,uBAAuB,CAAC,SAAiB;IACrD,IAAI,CAAC,SAAS;QAAE,OAAO,KAAK,CAAC;IAC7B,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC;AACjF,CAAC","sourcesContent":["export interface SpvProductMeta {\n title: string;\n image?: string;\n url?: string;\n size?: string; // cart-adds only: the size that was added (a product can be carted in several sizes)\n}\n\nexport interface LiveSessionData {\n enabled: boolean;\n sessionToken: string;\n spv: Record<string, number>; // {product_id: unix_timestamp}\n spvMeta: Record<string, SpvProductMeta>; // {product_id: {title, image}} — display info only\n spc: Record<string, number>; // {\"product_id::variant_id\": unix_timestamp} — cart actions\n spcMeta: Record<string, SpvProductMeta>; // {\"product_id::variant_id\": {title, image, url, size}} — per cart-add, display info only\n createdAt: number; // unix timestamp ms when token was created\n lastActive: number; // unix timestamp ms of last activity (matches storefront 30-min timeout)\n}\n\nexport type RequestType = 'collection' | 'search';\n\ninterface SpvLimits {\n min: number;\n max: number;\n expiryDays: number;\n}\n\nconst SPV_LIMITS: Record<RequestType, SpvLimits> = {\n collection: { min: 1, max: 10, expiryDays: 365 },\n search: { min: 3, max: 50, expiryDays: 5 },\n};\n\nconst LIVE_SESSION_KEY_PREFIX = 'ai_explainer_live_session';\nconst SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes — matches storefront SESSION_TOKEN_LIFE\n\n// Current site scope — must be set before any live session operations\nlet _siteScope = '';\n\n/**\n * Set the site scope for live session storage. Must be called when the site changes.\n * This ensures SPV/token are isolated per site.\n */\nexport function setLiveSessionSiteScope(uuid: string): void {\n _siteScope = uuid;\n}\n\nfunction getLiveSessionKey(): string {\n return _siteScope ? `${LIVE_SESSION_KEY_PREFIX}_${_siteScope}` : LIVE_SESSION_KEY_PREFIX;\n}\n\n// Crockford Base32 alphabet for ULID encoding\nconst CROCKFORD = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';\n\n/**\n * Generate a ULID-based session token matching production format: 'g' + 26-char ULID.\n * @param timestampMs - Optional timestamp in ms to encode (defaults to Date.now()).\n * Use a past timestamp to simulate a \"returning\" user (e.g., Date.now() - 2 * 86400000).\n */\nexport function generateSessionToken(timestampMs?: number): string {\n const now = timestampMs ?? Date.now();\n let ts = now;\n const timePart = new Array(10);\n for (let i = 9; i >= 0; i--) {\n timePart[i] = CROCKFORD[ts & 0x1f];\n ts = Math.floor(ts / 32);\n }\n const randPart = new Array(16);\n for (let i = 0; i < 16; i++) {\n randPart[i] = CROCKFORD[Math.floor(Math.random() * 32)];\n }\n return 'g' + timePart.join('') + randPart.join('');\n}\n\nconst defaultData: LiveSessionData = {\n enabled: false,\n sessionToken: '',\n spv: {},\n spvMeta: {},\n spc: {},\n spcMeta: {},\n createdAt: 0,\n lastActive: 0,\n};\n\nexport function getLiveSessionData(): LiveSessionData {\n try {\n const stored = localStorage.getItem(getLiveSessionKey());\n if (stored) {\n const parsed = JSON.parse(stored);\n if (!parsed.spvMeta) parsed.spvMeta = {};\n // spc / spcMeta are keyed by \"product_id::variant_id\". Drop any key without \"::\" —\n // serving silently ignores such keys and they'd corrupt the product-id derivation\n // (cartedProductIds). Also covers data from an earlier bare-variant-id format.\n parsed.spc = Object.fromEntries(Object.entries(parsed.spc || {}).filter(([k]) => k.includes('::')));\n parsed.spcMeta = Object.fromEntries(Object.entries(parsed.spcMeta || {}).filter(([k]) => k.includes('::')));\n if (!parsed.lastActive) parsed.lastActive = parsed.createdAt || 0;\n return parsed;\n }\n } catch (e) {\n // Ignore parsing errors\n }\n return { ...defaultData };\n}\n\nexport function setLiveSessionData(data: LiveSessionData): void {\n localStorage.setItem(getLiveSessionKey(), JSON.stringify(data));\n}\n\n/**\n * Check if the session has timed out (30+ min of inactivity).\n */\nfunction isSessionExpired(data: LiveSessionData): boolean {\n if (!data.lastActive) return false;\n return (Date.now() - data.lastActive) >= SESSION_TIMEOUT_MS;\n}\n\n/**\n * Touch the session (update lastActive). Called on every meaningful action.\n */\nfunction touchSession(data: LiveSessionData): LiveSessionData {\n data.lastActive = Date.now();\n return data;\n}\n\n/**\n * Filter and cap an accumulator (SPV or SPC) for a specific request type.\n */\nfunction filterAccumulator(\n acc: Record<string, number>,\n limits: SpvLimits\n): Record<string, number> | undefined {\n const cutoff = Math.floor(Date.now() / 1000) - limits.expiryDays * 86400;\n const entries = Object.entries(acc);\n // Fast path: skip filtering if empty\n if (entries.length === 0) return undefined;\n\n const recent = entries\n .filter(([_, ts]) => ts >= cutoff)\n .sort((a, b) => b[1] - a[1])\n .slice(0, limits.max);\n\n if (recent.length < limits.min) return undefined;\n return Object.fromEntries(recent);\n}\n\n/**\n * Add a product view to SPV. Touches session. Persists to localStorage.\n * @param meta - Optional product title/image for display in the session panel.\n */\nexport function addProductView(productId: string, meta?: SpvProductMeta): LiveSessionData {\n const data = touchSession(getLiveSessionData());\n data.spv[productId] = Math.floor(Date.now() / 1000);\n if (meta) data.spvMeta[productId] = meta;\n setLiveSessionData(data);\n return data;\n}\n\n/**\n * Add a cart-add to SPC. Touches session. Persists to localStorage.\n * @param productVariantKey - must be \"product_id::variant_id\" to match the serving parser\n * (a key without \"::\" is silently dropped by serving's _init_spc_cart; treated as a no-op here).\n * @param meta - optional product title/image/url/size for display in the session panel, keyed by\n * the full \"product_id::variant_id\" so each size of a product is its own cart-add.\n */\nexport function addProductToCart(productVariantKey: string, meta?: SpvProductMeta): LiveSessionData {\n // Validate before touching the session: serving drops keys without \"::\", so a bad key is a\n // no-op and shouldn't mutate the session at all.\n if (!productVariantKey.includes('::')) {\n return getLiveSessionData();\n }\n const data = touchSession(getLiveSessionData());\n data.spc[productVariantKey] = Math.floor(Date.now() / 1000);\n if (meta) data.spcMeta[productVariantKey] = meta;\n setLiveSessionData(data);\n return data;\n}\n\n/**\n * Convert LiveSessionData into the params format that updateSettingsAndRefetch expects.\n * Also touches the session (updates lastActive).\n */\n/**\n * @param personalizationTypes - Available filter types from the backend (e.g., ['gender', 'size', 'type']).\n * Defaults to all known types if not provided.\n */\nexport function buildLiveSessionParams(data: LiveSessionData, requestType: RequestType = 'collection', personalizationTypes: string[] = ['gender', 'size', 'type']): {\n personalization_preview: { enabled: boolean; spvJson: string; spcJson: string; types: string[] } | null;\n session_token: string | undefined;\n user_segment_preview: 'none' | 'new' | 'returning';\n} {\n if (!data.enabled) {\n return { personalization_preview: null, session_token: undefined, user_segment_preview: 'none' };\n }\n\n const limits = SPV_LIMITS[requestType];\n const filteredSpv = filterAccumulator(data.spv, limits);\n // SPC only sent with collections (not supported in search serving)\n const filteredSpc = requestType === 'collection' ? filterAccumulator(data.spc, limits) : undefined;\n\n // spv OR spc enables the preview — a cart-only session (size personalization) is valid.\n return {\n personalization_preview: (filteredSpv || filteredSpc) ? {\n enabled: true,\n spvJson: filteredSpv ? JSON.stringify(filteredSpv) : '',\n spcJson: filteredSpc ? JSON.stringify(filteredSpc) : '',\n types: personalizationTypes,\n } : null,\n session_token: data.sessionToken || undefined,\n user_segment_preview: getUserSegmentFromToken(data.createdAt),\n };\n}\n\n/**\n * Regenerate session token with a specific age for new/returning segment testing.\n * \"new\" → token created now (age < 1 day). \"returning\" → token created 2 days ago.\n * Preserves SPV/SPC — only the token changes.\n */\nexport function setSessionSegment(segment: 'new' | 'returning'): LiveSessionData {\n const data = touchSession(getLiveSessionData());\n const tokenTimestamp = segment === 'returning'\n ? Date.now() - 2 * 24 * 60 * 60 * 1000 // 2 days ago\n : Date.now();\n data.sessionToken = generateSessionToken(tokenTimestamp);\n data.createdAt = tokenTimestamp;\n setLiveSessionData(data);\n return data;\n}\n\n/**\n * Reset live session: fresh token, clear SPV + SPC.\n */\nexport function resetLiveSession(): LiveSessionData {\n const now = Date.now();\n const data: LiveSessionData = {\n enabled: true,\n sessionToken: generateSessionToken(),\n spv: {},\n spvMeta: {},\n spc: {},\n spcMeta: {},\n createdAt: now,\n lastActive: now,\n };\n setLiveSessionData(data);\n return data;\n}\n\n/**\n * Start a live session. Resumes if existing and not expired.\n * If expired (30+ min inactive), resets to a fresh session (matching storefront behavior).\n */\nexport function startLiveSession(): LiveSessionData {\n const existing = getLiveSessionData();\n if (existing.sessionToken && !isSessionExpired(existing)) {\n existing.enabled = true;\n existing.lastActive = Date.now();\n setLiveSessionData(existing);\n return existing;\n }\n // No session or expired — create fresh\n return resetLiveSession();\n}\n\n/**\n * Stop live session (pause). Token, SPV and SPC remain for potential resume.\n */\nexport function stopLiveSession(): LiveSessionData {\n const data = getLiveSessionData();\n data.enabled = false;\n setLiveSessionData(data);\n return data;\n}\n\n/**\n * Format token age as human-readable string.\n */\nexport function formatTokenAge(createdAt: number): string {\n if (!createdAt) return 'N/A';\n const totalMinutes = Math.floor((Date.now() - createdAt) / 60000);\n const days = Math.floor(totalMinutes / 1440);\n const hours = Math.floor((totalMinutes % 1440) / 60);\n const minutes = totalMinutes % 60;\n if (days > 0) return `${days}d ${hours}h`;\n if (hours > 0) return `${hours}h ${minutes}m`;\n return `${minutes}m`;\n}\n\n/**\n * Get user segment derived from token age.\n */\nexport function getUserSegmentFromToken(createdAt: number): 'new' | 'returning' {\n if (!createdAt) return 'new';\n return (Date.now() - createdAt) >= 24 * 60 * 60 * 1000 ? 'returning' : 'new';\n}\n"]}
|
|
1
|
+
{"version":3,"file":"liveSessionPreview.js","sourceRoot":"","sources":["../../src/utils/liveSessionPreview.ts"],"names":[],"mappings":"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","sourcesContent":["export interface SpvProductMeta {\n title: string;\n image?: string;\n url?: string;\n size?: string; // cart-adds only: the size that was added (a product can be carted in several sizes)\n}\n\nexport interface LiveSessionData {\n enabled: boolean;\n sessionToken: string;\n spv: Record<string, number>; // {product_id: unix_timestamp}\n spvMeta: Record<string, SpvProductMeta>; // {product_id: {title, image}} — display info only\n spc: Record<string, number>; // {\"product_id::variant_id\": unix_timestamp} — cart actions\n spcMeta: Record<string, SpvProductMeta>; // {\"product_id::variant_id\": {title, image, url, size}} — per cart-add, display info only\n createdAt: number; // unix timestamp ms when token was created\n lastActive: number; // unix timestamp ms of last activity (matches storefront 30-min timeout)\n}\n\nexport type RequestType = 'collection' | 'search';\n\ninterface SpvLimits {\n min: number;\n max: number;\n expiryDays: number;\n}\n\nconst SPV_LIMITS: Record<RequestType, SpvLimits> = {\n collection: { min: 1, max: 10, expiryDays: 365 },\n search: { min: 3, max: 50, expiryDays: 5 },\n};\n\nconst LIVE_SESSION_KEY_PREFIX = 'ai_explainer_live_session';\nconst SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes — matches storefront SESSION_TOKEN_LIFE\n\n// Current site scope — must be set before any live session operations\nlet _siteScope = '';\n\n/**\n * Set the site scope for live session storage. Must be called when the site changes.\n * This ensures SPV/token are isolated per site.\n */\nexport function setLiveSessionSiteScope(uuid: string): void {\n _siteScope = uuid;\n}\n\nfunction getLiveSessionKey(): string {\n return _siteScope ? `${LIVE_SESSION_KEY_PREFIX}_${_siteScope}` : LIVE_SESSION_KEY_PREFIX;\n}\n\n// Crockford Base32 alphabet for ULID encoding\nconst CROCKFORD = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';\n\n/**\n * Generate a ULID-based session token matching production format: 'g' + 26-char ULID.\n * @param timestampMs - Optional timestamp in ms to encode (defaults to Date.now()).\n * Use a past timestamp to simulate a \"returning\" user (e.g., Date.now() - 2 * 86400000).\n */\nexport function generateSessionToken(timestampMs?: number): string {\n const now = timestampMs ?? Date.now();\n let ts = now;\n const timePart = new Array(10);\n for (let i = 9; i >= 0; i--) {\n timePart[i] = CROCKFORD[ts & 0x1f];\n ts = Math.floor(ts / 32);\n }\n const randPart = new Array(16);\n for (let i = 0; i < 16; i++) {\n randPart[i] = CROCKFORD[Math.floor(Math.random() * 32)];\n }\n return 'g' + timePart.join('') + randPart.join('');\n}\n\nconst defaultData: LiveSessionData = {\n enabled: false,\n sessionToken: '',\n spv: {},\n spvMeta: {},\n spc: {},\n spcMeta: {},\n createdAt: 0,\n lastActive: 0,\n};\n\nexport function getLiveSessionData(): LiveSessionData {\n try {\n const stored = localStorage.getItem(getLiveSessionKey());\n if (stored) {\n const parsed = JSON.parse(stored);\n if (!parsed.spvMeta) parsed.spvMeta = {};\n // spc / spcMeta are keyed by \"product_id::variant_id\". Drop any key without \"::\" —\n // serving silently ignores such keys and they'd corrupt the product-id derivation\n // (cartedProductIds). Also covers data from an earlier bare-variant-id format.\n parsed.spc = Object.fromEntries(Object.entries(parsed.spc || {}).filter(([k]) => k.includes('::')));\n parsed.spcMeta = Object.fromEntries(Object.entries(parsed.spcMeta || {}).filter(([k]) => k.includes('::')));\n if (!parsed.lastActive) parsed.lastActive = parsed.createdAt || 0;\n return parsed;\n }\n } catch (e) {\n // Ignore parsing errors\n }\n return { ...defaultData };\n}\n\nexport function setLiveSessionData(data: LiveSessionData): void {\n localStorage.setItem(getLiveSessionKey(), JSON.stringify(data));\n}\n\n/**\n * Check if the session has timed out (30+ min of inactivity).\n */\nfunction isSessionExpired(data: LiveSessionData): boolean {\n if (!data.lastActive) return false;\n return (Date.now() - data.lastActive) >= SESSION_TIMEOUT_MS;\n}\n\n/**\n * Touch the session (update lastActive). Called on every meaningful action.\n */\nfunction touchSession(data: LiveSessionData): LiveSessionData {\n data.lastActive = Date.now();\n return data;\n}\n\n/**\n * Filter and cap an accumulator (SPV or SPC) for a specific request type.\n */\nfunction filterAccumulator(\n acc: Record<string, number>,\n limits: SpvLimits\n): Record<string, number> | undefined {\n const cutoff = Math.floor(Date.now() / 1000) - limits.expiryDays * 86400;\n const entries = Object.entries(acc);\n // Fast path: skip filtering if empty\n if (entries.length === 0) return undefined;\n\n const recent = entries\n .filter(([_, ts]) => ts >= cutoff)\n .sort((a, b) => b[1] - a[1])\n .slice(0, limits.max);\n\n if (recent.length < limits.min) return undefined;\n return Object.fromEntries(recent);\n}\n\n/**\n * Add a product view to SPV. Touches session. Persists to localStorage.\n * @param meta - Optional product title/image for display in the session panel.\n */\nexport function addProductView(productId: string, meta?: SpvProductMeta): LiveSessionData {\n const data = touchSession(getLiveSessionData());\n data.spv[productId] = Math.floor(Date.now() / 1000);\n if (meta) data.spvMeta[productId] = meta;\n setLiveSessionData(data);\n return data;\n}\n\n/**\n * Add a cart-add to SPC. Touches session. Persists to localStorage.\n * @param productVariantKey - must be \"product_id::variant_id\" to match the serving parser\n * (a key without \"::\" is silently dropped by serving's _init_spc_cart; treated as a no-op here).\n * @param meta - optional product title/image/url/size for display in the session panel, keyed by\n * the full \"product_id::variant_id\" so each size of a product is its own cart-add.\n */\nexport function addProductToCart(productVariantKey: string, meta?: SpvProductMeta): LiveSessionData {\n // Validate before touching the session: serving drops keys without \"::\", so a bad key is a\n // no-op and shouldn't mutate the session at all.\n if (!productVariantKey.includes('::')) {\n return getLiveSessionData();\n }\n const data = touchSession(getLiveSessionData());\n data.spc[productVariantKey] = Math.floor(Date.now() / 1000);\n if (meta) data.spcMeta[productVariantKey] = meta;\n setLiveSessionData(data);\n return data;\n}\n\n/**\n * Convert LiveSessionData into the params format that updateSettingsAndRefetch expects.\n * Also touches the session (updates lastActive).\n */\n/**\n * @param personalizationTypes - Available filter types from the backend (e.g., ['gender', 'size', 'type']).\n * Defaults to all known types if not provided.\n */\nexport function buildLiveSessionParams(data: LiveSessionData, requestType: RequestType = 'collection', personalizationTypes: string[] = ['gender', 'size', 'type']): {\n personalization_preview: { enabled: boolean; spvJson: string; spcJson: string; types: string[] } | null;\n session_token: string | undefined;\n user_segment_preview: 'none' | 'new' | 'returning';\n} {\n if (!data.enabled) {\n return { personalization_preview: null, session_token: undefined, user_segment_preview: 'none' };\n }\n\n const limits = SPV_LIMITS[requestType];\n const filteredSpv = filterAccumulator(data.spv, limits);\n // SPC only sent with collections (not supported in search serving)\n const filteredSpc = requestType === 'collection' ? filterAccumulator(data.spc, limits) : undefined;\n\n // spv OR spc enables the preview — a cart-only session (size personalization) is valid.\n // Active personalization types ALSO enable it with an empty session: sites that run the\n // size-availability correction from n=0 (empty cart -> population-prior baseline) still need\n // the request to carry `personalization`, otherwise serving's use_filter_personalization stays\n // off and the cold-start size down-ranking a real first-time shopper sees can't be previewed.\n const hasTypes = personalizationTypes.length > 0;\n return {\n personalization_preview: (filteredSpv || filteredSpc || hasTypes) ? {\n enabled: true,\n spvJson: filteredSpv ? JSON.stringify(filteredSpv) : '',\n spcJson: filteredSpc ? JSON.stringify(filteredSpc) : '',\n types: personalizationTypes,\n } : null,\n session_token: data.sessionToken || undefined,\n user_segment_preview: getUserSegmentFromToken(data.createdAt),\n };\n}\n\n/**\n * Regenerate session token with a specific age for new/returning segment testing.\n * \"new\" → token created now (age < 1 day). \"returning\" → token created 2 days ago.\n * Preserves SPV/SPC — only the token changes.\n */\nexport function setSessionSegment(segment: 'new' | 'returning'): LiveSessionData {\n const data = touchSession(getLiveSessionData());\n const tokenTimestamp = segment === 'returning'\n ? Date.now() - 2 * 24 * 60 * 60 * 1000 // 2 days ago\n : Date.now();\n data.sessionToken = generateSessionToken(tokenTimestamp);\n data.createdAt = tokenTimestamp;\n setLiveSessionData(data);\n return data;\n}\n\n/**\n * Reset live session: fresh token, clear SPV + SPC.\n */\nexport function resetLiveSession(): LiveSessionData {\n const now = Date.now();\n const data: LiveSessionData = {\n enabled: true,\n sessionToken: generateSessionToken(),\n spv: {},\n spvMeta: {},\n spc: {},\n spcMeta: {},\n createdAt: now,\n lastActive: now,\n };\n setLiveSessionData(data);\n return data;\n}\n\n/**\n * Start a live session. Resumes if existing and not expired.\n * If expired (30+ min inactive), resets to a fresh session (matching storefront behavior).\n */\nexport function startLiveSession(): LiveSessionData {\n const existing = getLiveSessionData();\n if (existing.sessionToken && !isSessionExpired(existing)) {\n existing.enabled = true;\n existing.lastActive = Date.now();\n setLiveSessionData(existing);\n return existing;\n }\n // No session or expired — create fresh\n return resetLiveSession();\n}\n\n/**\n * Stop live session (pause). Token, SPV and SPC remain for potential resume.\n */\nexport function stopLiveSession(): LiveSessionData {\n const data = getLiveSessionData();\n data.enabled = false;\n setLiveSessionData(data);\n return data;\n}\n\n/**\n * Format token age as human-readable string.\n */\nexport function formatTokenAge(createdAt: number): string {\n if (!createdAt) return 'N/A';\n const totalMinutes = Math.floor((Date.now() - createdAt) / 60000);\n const days = Math.floor(totalMinutes / 1440);\n const hours = Math.floor((totalMinutes % 1440) / 60);\n const minutes = totalMinutes % 60;\n if (days > 0) return `${days}d ${hours}h`;\n if (hours > 0) return `${hours}h ${minutes}m`;\n return `${minutes}m`;\n}\n\n/**\n * Get user segment derived from token age.\n */\nexport function getUserSegmentFromToken(createdAt: number): 'new' | 'returning' {\n if (!createdAt) return 'new';\n return (Date.now() - createdAt) >= 24 * 60 * 60 * 1000 ? 'returning' : 'new';\n}\n"]}
|
|
@@ -11,8 +11,9 @@ export declare function setPersonalizationPreviewSettings(settings: Personalizat
|
|
|
11
11
|
* Build spv, spc and personalization param values from settings.
|
|
12
12
|
* spv (views) and/or spc (cart-adds) are sent whenever preview is enabled and at least one of them
|
|
13
13
|
* has data — a cart-only session (size personalization) is valid without any views.
|
|
14
|
-
* personalization (filter types) is
|
|
15
|
-
*
|
|
14
|
+
* personalization (filter types) is sent whenever preview is enabled and types is non-empty, even
|
|
15
|
+
* with an empty session — the n=0 population-prior baseline (e.g. size availability on empty-cart
|
|
16
|
+
* sites) needs the signal to reach serving so use_filter_personalization turns on.
|
|
16
17
|
*/
|
|
17
18
|
export declare function buildPersonalizationParams(settings: PersonalizationPreviewSettings): {
|
|
18
19
|
spv: string | undefined;
|
|
@@ -18,20 +18,24 @@ export function setPersonalizationPreviewSettings(settings) {
|
|
|
18
18
|
* Build spv, spc and personalization param values from settings.
|
|
19
19
|
* spv (views) and/or spc (cart-adds) are sent whenever preview is enabled and at least one of them
|
|
20
20
|
* has data — a cart-only session (size personalization) is valid without any views.
|
|
21
|
-
* personalization (filter types) is
|
|
22
|
-
*
|
|
21
|
+
* personalization (filter types) is sent whenever preview is enabled and types is non-empty, even
|
|
22
|
+
* with an empty session — the n=0 population-prior baseline (e.g. size availability on empty-cart
|
|
23
|
+
* sites) needs the signal to reach serving so use_filter_personalization turns on.
|
|
23
24
|
*/
|
|
24
25
|
export function buildPersonalizationParams(settings) {
|
|
25
26
|
var _a;
|
|
26
27
|
const spvTrimmed = settings.spvJson.trim();
|
|
27
28
|
const spcTrimmed = ((_a = settings.spcJson) !== null && _a !== void 0 ? _a : '').trim();
|
|
28
|
-
|
|
29
|
+
const personalization = settings.types.length > 0 ? settings.types.join(',') : undefined;
|
|
30
|
+
// Nothing to send only when disabled, or when there is neither a session signal (spv/spc)
|
|
31
|
+
// NOR active personalization types.
|
|
32
|
+
if (!settings.enabled || (!spvTrimmed && !spcTrimmed && !personalization)) {
|
|
29
33
|
return { spv: undefined, spc: undefined, personalization: undefined };
|
|
30
34
|
}
|
|
31
35
|
return {
|
|
32
36
|
spv: spvTrimmed || undefined,
|
|
33
37
|
spc: spcTrimmed || undefined,
|
|
34
|
-
personalization
|
|
38
|
+
personalization,
|
|
35
39
|
};
|
|
36
40
|
}
|
|
37
41
|
//# sourceMappingURL=personalizationPreview.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"personalizationPreview.js","sourceRoot":"","sources":["../../src/utils/personalizationPreview.ts"],"names":[],"mappings":"AASA,MAAM,2BAA2B,GAAG,sCAAsC,CAAC;AAE3E,MAAM,UAAU,iCAAiC;IAC7C,IAAI;QACA,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,2BAA2B,CAAC,CAAC;QACjE,IAAI,MAAM,EAAE;YACR,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;SAC7B;KACJ;IAAC,OAAO,CAAC,EAAE;QACR,wBAAwB;KAC3B;IACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC;AAC9D,CAAC;AAED,MAAM,UAAU,iCAAiC,CAAC,QAAwC;IACtF,YAAY,CAAC,OAAO,CAAC,2BAA2B,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;AAChF,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"personalizationPreview.js","sourceRoot":"","sources":["../../src/utils/personalizationPreview.ts"],"names":[],"mappings":"AASA,MAAM,2BAA2B,GAAG,sCAAsC,CAAC;AAE3E,MAAM,UAAU,iCAAiC;IAC7C,IAAI;QACA,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,2BAA2B,CAAC,CAAC;QACjE,IAAI,MAAM,EAAE;YACR,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;SAC7B;KACJ;IAAC,OAAO,CAAC,EAAE;QACR,wBAAwB;KAC3B;IACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC;AAC9D,CAAC;AAED,MAAM,UAAU,iCAAiC,CAAC,QAAwC;IACtF,YAAY,CAAC,OAAO,CAAC,2BAA2B,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;AAChF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,0BAA0B,CAAC,QAAwC;;IAK/E,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;IAC3C,MAAM,UAAU,GAAG,CAAC,MAAA,QAAQ,CAAC,OAAO,mCAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACnD,MAAM,eAAe,GAAG,QAAQ,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACzF,0FAA0F;IAC1F,oCAAoC;IACpC,IAAI,CAAC,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC,UAAU,IAAI,CAAC,UAAU,IAAI,CAAC,eAAe,CAAC,EAAE;QACvE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE,eAAe,EAAE,SAAS,EAAE,CAAC;KACzE;IACD,OAAO;QACH,GAAG,EAAE,UAAU,IAAI,SAAS;QAC5B,GAAG,EAAE,UAAU,IAAI,SAAS;QAC5B,eAAe;KAClB,CAAC;AACN,CAAC","sourcesContent":["export type PersonalizationType = 'gender' | 'size' | 'type';\n\nexport interface PersonalizationPreviewSettings {\n enabled: boolean;\n spvJson: string;\n spcJson?: string; // cart-adds JSON: {\"product_id::variant_id\": timestamp}; populated by the live session, collection-only\n types: PersonalizationType[];\n}\n\nconst PERSONALIZATION_PREVIEW_KEY = 'ai_explainer_personalization_preview';\n\nexport function getPersonalizationPreviewSettings(): PersonalizationPreviewSettings {\n try {\n const stored = localStorage.getItem(PERSONALIZATION_PREVIEW_KEY);\n if (stored) {\n return JSON.parse(stored);\n }\n } catch (e) {\n // Ignore parsing errors\n }\n return { enabled: false, spvJson: '', types: ['gender'] };\n}\n\nexport function setPersonalizationPreviewSettings(settings: PersonalizationPreviewSettings): void {\n localStorage.setItem(PERSONALIZATION_PREVIEW_KEY, JSON.stringify(settings));\n}\n\n/**\n * Build spv, spc and personalization param values from settings.\n * spv (views) and/or spc (cart-adds) are sent whenever preview is enabled and at least one of them\n * has data — a cart-only session (size personalization) is valid without any views.\n * personalization (filter types) is sent whenever preview is enabled and types is non-empty, even\n * with an empty session — the n=0 population-prior baseline (e.g. size availability on empty-cart\n * sites) needs the signal to reach serving so use_filter_personalization turns on.\n */\nexport function buildPersonalizationParams(settings: PersonalizationPreviewSettings): {\n spv: string | undefined;\n spc: string | undefined;\n personalization: string | undefined;\n} {\n const spvTrimmed = settings.spvJson.trim();\n const spcTrimmed = (settings.spcJson ?? '').trim();\n const personalization = settings.types.length > 0 ? settings.types.join(',') : undefined;\n // Nothing to send only when disabled, or when there is neither a session signal (spv/spc)\n // NOR active personalization types.\n if (!settings.enabled || (!spvTrimmed && !spcTrimmed && !personalization)) {\n return { spv: undefined, spc: undefined, personalization: undefined };\n }\n return {\n spv: spvTrimmed || undefined,\n spc: spcTrimmed || undefined,\n personalization,\n };\n}\n\n"]}
|