@coframe/sdk 1.1.14 → 1.1.21

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.
@@ -22,6 +22,19 @@ if (!Array.prototype.find) {
22
22
  enumerable: false,
23
23
  });
24
24
  }
25
+ if (!Array.prototype.includes) {
26
+ Object.defineProperty(Array.prototype, 'includes', {
27
+ value: function (searchElement) {
28
+ if (this == null) {
29
+ throw new TypeError('Array.prototype.includes called on null or undefined');
30
+ }
31
+ return Array.prototype.indexOf.call(this, searchElement) !== -1;
32
+ },
33
+ writable: true,
34
+ configurable: true,
35
+ enumerable: false,
36
+ });
37
+ }
25
38
  if (!Array.prototype.flatMap) {
26
39
  Object.defineProperty(Array.prototype, 'flatMap', {
27
40
  value: function (callback, thisArg) {
@@ -1064,10 +1077,41 @@ function warnEventsNotSent() {
1064
1077
  }
1065
1078
  }
1066
1079
 
1067
- const SDK_VERSION = '2.6.0';
1080
+ const SDK_VERSION = '2.7.0';
1068
1081
  const queuedEvents = [];
1069
1082
  const queuedMonitoringEvents = [];
1070
1083
  const queuedSessions = [];
1084
+ let ingestUrl;
1085
+ const COMPILED_INGEST_BASE = "https://ingest.app.coframe.com/ingest/v2";
1086
+ function configureIngestUrl(url) {
1087
+ const trimmed = url === null || url === void 0 ? void 0 : url.trim().replace(/\/+$/, '');
1088
+ ingestUrl = trimmed || undefined;
1089
+ }
1090
+ function resolveIngestUrl(defaultUrl) {
1091
+ if (!ingestUrl || !defaultUrl.startsWith(COMPILED_INGEST_BASE))
1092
+ return defaultUrl;
1093
+ return ingestUrl + defaultUrl.slice(COMPILED_INGEST_BASE.length);
1094
+ }
1095
+ const MAX_BEACON_BATCH_BYTES = 48 * 1024;
1096
+ function getBeaconPayloadBytes(data) {
1097
+ return new Blob([JSON.stringify(data)]).size;
1098
+ }
1099
+ function chunkEventsByByteLimit(events) {
1100
+ const chunks = [];
1101
+ let current = [];
1102
+ for (const event of events) {
1103
+ if (current.length > 0 &&
1104
+ getBeaconPayloadBytes([...current, event]) > MAX_BEACON_BATCH_BYTES) {
1105
+ chunks.push(current);
1106
+ current = [];
1107
+ }
1108
+ current.push(event);
1109
+ }
1110
+ if (current.length > 0) {
1111
+ chunks.push(current);
1112
+ }
1113
+ return chunks;
1114
+ }
1071
1115
  function queueMonitoringEvent(coframe, data) {
1072
1116
  queuedMonitoringEvents.push(data);
1073
1117
  if (queuedMonitoringEvents.length >= coframe.batchSize) {
@@ -1120,11 +1164,15 @@ function flushQueuedEvents(projectId) {
1120
1164
  return;
1121
1165
  }
1122
1166
  if (queuedEvents.length > 0) {
1123
- sendEventToBackend(projectId, queuedEvents, 'batched_events');
1167
+ for (const chunk of chunkEventsByByteLimit(queuedEvents)) {
1168
+ sendEventToBackend(projectId, chunk, 'batched_events');
1169
+ }
1124
1170
  queuedEvents.length = 0;
1125
1171
  }
1126
1172
  if (queuedMonitoringEvents.length > 0) {
1127
- sendEventToBackend(projectId, queuedMonitoringEvents, 'batched_monitoring');
1173
+ for (const chunk of chunkEventsByByteLimit(queuedMonitoringEvents)) {
1174
+ sendEventToBackend(projectId, chunk, 'batched_monitoring');
1175
+ }
1128
1176
  queuedMonitoringEvents.length = 0;
1129
1177
  }
1130
1178
  if (queuedSessions.length > 0) {
@@ -1226,7 +1274,7 @@ function sendPageEngagementEvent(coframe, eventType, event, { projectId, session
1226
1274
  }
1227
1275
  }
1228
1276
  function sendEventToBackend(projectId, data, eventCategory = 'events') {
1229
- const eventsEndpoint = `${"https://ingest.app.coframe.com/ingest/v2"}/${eventCategory}/${projectId}/`;
1277
+ const eventsEndpoint = resolveIngestUrl(`${"https://ingest.app.coframe.com/ingest/v2"}/${eventCategory}/${projectId}/`);
1230
1278
  if (navigator.sendBeacon) {
1231
1279
  const textData = JSON.stringify(data);
1232
1280
  const blob = new Blob([textData], { type: 'text/plain' });
@@ -1239,7 +1287,7 @@ function sendEventToBackend(projectId, data, eventCategory = 'events') {
1239
1287
  }
1240
1288
  }
1241
1289
  function createSession(data) {
1242
- const sessionApiUrl = `${"https://ingest.app.coframe.com/ingest/v2"}/session_result/`;
1290
+ const sessionApiUrl = resolveIngestUrl(`${"https://ingest.app.coframe.com/ingest/v2"}/session_result/`);
1243
1291
  if (navigator.sendBeacon) {
1244
1292
  const textData = JSON.stringify(data);
1245
1293
  const blob = new Blob([textData], { type: 'text/plain' });
@@ -1251,11 +1299,14 @@ function createSession(data) {
1251
1299
  console.warn('[Coframe] cannot send event beacon, feature unavailable');
1252
1300
  }
1253
1301
  }
1254
- async function fetchProjectFromEdge(projectId, currentUrl, signal, userContext, previewVariantId, headers) {
1302
+ async function fetchProjectFromEdge(projectId, currentUrl, signal, userContext, previewVariantId, headers, referrer) {
1255
1303
  const payload = { projectId, currentUrl };
1256
1304
  if (previewVariantId) {
1257
1305
  payload.previewVariantId = previewVariantId;
1258
1306
  }
1307
+ if (referrer !== undefined) {
1308
+ payload.referrer = referrer;
1309
+ }
1259
1310
  const safe = sanitizeUserContext(userContext);
1260
1311
  if (safe) {
1261
1312
  payload.userContext = safe;
@@ -1276,6 +1327,26 @@ async function fetchProjectFromEdge(projectId, currentUrl, signal, userContext,
1276
1327
  return null;
1277
1328
  }
1278
1329
  }
1330
+ async function fetchProject(projectUrl, projectId, currentUrl, signal, userContext, previewVariantId, headers, referrer) {
1331
+ if (!projectUrl) {
1332
+ return fetchProjectFromEdge(projectId, currentUrl, signal, userContext, previewVariantId, headers, referrer);
1333
+ }
1334
+ try {
1335
+ const response = await fetch(projectUrl, { headers, signal });
1336
+ if (!response.ok)
1337
+ return null;
1338
+ return {
1339
+ project: (await response.json()),
1340
+ personalizationHeaders: {},
1341
+ };
1342
+ }
1343
+ catch (error) {
1344
+ if (error instanceof DOMException && error.name === 'AbortError') {
1345
+ return null;
1346
+ }
1347
+ return null;
1348
+ }
1349
+ }
1279
1350
 
1280
1351
  const isPageElement = (element) => {
1281
1352
  return (element === document.body ||
@@ -2127,6 +2198,78 @@ function resolveLaneWinners(claims, reachableByLane, lanes, userToken) {
2127
2198
  return excluded;
2128
2199
  }
2129
2200
 
2201
+ var CoInspectorMessageType;
2202
+ (function (CoInspectorMessageType) {
2203
+ CoInspectorMessageType["onReady"] = "onReady";
2204
+ CoInspectorMessageType["onHoverElement"] = "onHoverElement";
2205
+ CoInspectorMessageType["onClickElement"] = "onClickElement";
2206
+ CoInspectorMessageType["onPageUrlChange"] = "onPageUrlChange";
2207
+ CoInspectorMessageType["onCreateNewElement"] = "onCreateNewElement";
2208
+ CoInspectorMessageType["onCancelNewElement"] = "onCancelNewElement";
2209
+ CoInspectorMessageType["onCopy"] = "onCopy";
2210
+ CoInspectorMessageType["onCloseDomInspectorPanel"] = "onCloseDomInspectorPanel";
2211
+ CoInspectorMessageType["onEditElement"] = "onEditElement";
2212
+ CoInspectorMessageType["onDeleteElement"] = "onDeleteElement";
2213
+ CoInspectorMessageType["onScroll"] = "onScroll";
2214
+ CoInspectorMessageType["setAllRects"] = "setAllRects";
2215
+ CoInspectorMessageType["onUpdateRects"] = "onUpdateRects";
2216
+ CoInspectorMessageType["DisableMouseTracking"] = "DisableMouseTracking";
2217
+ CoInspectorMessageType["EnableMouseTracking"] = "EnableMouseTracking";
2218
+ CoInspectorMessageType["onLeaveElement"] = "onLeaveElement";
2219
+ })(CoInspectorMessageType || (CoInspectorMessageType = {}));
2220
+ var CoMainSiteMessageType;
2221
+ (function (CoMainSiteMessageType) {
2222
+ CoMainSiteMessageType["ActiveInspect"] = "ActiveInspect";
2223
+ CoMainSiteMessageType["FreezeInspect"] = "FreezeInspect";
2224
+ CoMainSiteMessageType["SetElementChanges"] = "SetElementChanges";
2225
+ CoMainSiteMessageType["ShowDomInspector"] = "ShowDomInspector";
2226
+ CoMainSiteMessageType["RestoreOriginal"] = "RestoreOriginal";
2227
+ CoMainSiteMessageType["CreateNewElement"] = "CreateNewElement";
2228
+ CoMainSiteMessageType["ClickHoverElement"] = "ClickHoverElement";
2229
+ CoMainSiteMessageType["ReloadIframe"] = "ReloadIframe";
2230
+ })(CoMainSiteMessageType || (CoMainSiteMessageType = {}));
2231
+
2232
+ const COFRAME_PREVIEW_SESSION_STORAGE_KEY = 'coframe.preview_variant';
2233
+ const COFRAME_PREVIEW_VARIANT_QUERY_PARAM = 'coframe_variant_id';
2234
+ const ROLLOUTS_PREVIEW_ID = 'rollouts';
2235
+ function splitPreviewVariantIds(raw) {
2236
+ var _a;
2237
+ return ((_a = raw === null || raw === void 0 ? void 0 : raw.split(',').map((id) => id.trim()).filter(Boolean)) !== null && _a !== void 0 ? _a : []);
2238
+ }
2239
+ function isPreviewSentinel(id) {
2240
+ return id === GLOBAL_HOLDOUT || id === ROLLOUTS_PREVIEW_ID;
2241
+ }
2242
+ function normalizePreviewVariantId(raw) {
2243
+ const ids = splitPreviewVariantIds(raw);
2244
+ return ids.length > 0 ? ids.join(',') : null;
2245
+ }
2246
+ function readPreviewVariantIdFromUrl(url = window.location.href) {
2247
+ try {
2248
+ return normalizePreviewVariantId(getUrlSearchParams(url).get(COFRAME_PREVIEW_VARIANT_QUERY_PARAM));
2249
+ }
2250
+ catch (_a) {
2251
+ return null;
2252
+ }
2253
+ }
2254
+ function readStoredPreviewVariantId() {
2255
+ try {
2256
+ return normalizePreviewVariantId(window.sessionStorage.getItem(COFRAME_PREVIEW_SESSION_STORAGE_KEY));
2257
+ }
2258
+ catch (_a) {
2259
+ return null;
2260
+ }
2261
+ }
2262
+ function resolvePreviewSource(url = window.location.href) {
2263
+ const urlVariantId = readPreviewVariantIdFromUrl(url);
2264
+ const storedVariantId = readStoredPreviewVariantId();
2265
+ return {
2266
+ effectiveVariantId: urlVariantId || storedVariantId,
2267
+ isKeptForSession: Boolean(storedVariantId && storedVariantId === (urlVariantId || storedVariantId)),
2268
+ storedVariantId,
2269
+ urlVariantId,
2270
+ };
2271
+ }
2272
+
2130
2273
  function isElementVisible(el) {
2131
2274
  const rect = el.getBoundingClientRect();
2132
2275
  return rect.top >= 0 && rect.bottom <= window.innerHeight;
@@ -2166,11 +2309,11 @@ function checkActivationCondition(condition, personalizationHeaders, currentUrl,
2166
2309
  }
2167
2310
  if (condition.url &&
2168
2311
  condition.url !== '*' &&
2169
- !urlsMatch(condition.url, currentUrl)) {
2312
+ !urlConditionMatches(condition.url, currentUrl, false, logger)) {
2170
2313
  return false;
2171
2314
  }
2172
2315
  if (condition.base_url_matches &&
2173
- !urlsMatch(condition.base_url_matches, currentUrl, true)) {
2316
+ !urlConditionMatches(condition.base_url_matches, currentUrl, true, logger)) {
2174
2317
  return false;
2175
2318
  }
2176
2319
  if (condition.url_regex) {
@@ -2284,6 +2427,18 @@ const normalizeUrl = (url, baseUrlMatch) => {
2284
2427
  }
2285
2428
  return `${hostname}${pathname}${parsedUrl.search}${parsedUrl.hash}`;
2286
2429
  };
2430
+ function urlConditionMatches(conditionUrl, currentUrl, baseUrlMatch, logger) {
2431
+ try {
2432
+ return urlsMatch(conditionUrl, currentUrl, baseUrlMatch);
2433
+ }
2434
+ catch (_a) {
2435
+ logger.error('Error parsing URL condition', 'url_condition_error', {
2436
+ conditionUrl,
2437
+ currentUrl,
2438
+ });
2439
+ return false;
2440
+ }
2441
+ }
2287
2442
  function urlsMatch(conditionUrl, currentUrl, baseUrlMatch) {
2288
2443
  if (currentUrl.includes('localhost') || currentUrl.includes('0.0.0.0')) {
2289
2444
  const conditionUrlObj = new URL(conditionUrl);
@@ -2394,15 +2549,15 @@ const inRange = (bins, scale, seed, uid) => {
2394
2549
  }
2395
2550
  return false;
2396
2551
  };
2397
- function checkAllAudiences(metadata, project, personalizationHeaders, currentPageUrl, languages, browserPlatform, userToken, userVisitCount, nonce, logger) {
2552
+ function checkAllAudiences(metadata, ctx) {
2398
2553
  var _a;
2399
2554
  const audienceIds = (_a = metadata.activation_condition.audience_ids) !== null && _a !== void 0 ? _a : [];
2400
2555
  let result = true;
2401
2556
  for (const audienceId of audienceIds) {
2402
- const audience = project.audiences[audienceId];
2557
+ const audience = ctx.project.audiences[audienceId];
2403
2558
  if (!audience)
2404
2559
  continue;
2405
- const activated = checkActivationCondition(audience.selector, personalizationHeaders, currentPageUrl, languages, browserPlatform, userToken, userVisitCount, (js, args) => executeJs(js, args, { isExpression: true, nonce }), logger);
2560
+ const activated = checkActivationCondition(audience.selector, ctx.personalizationHeaders, ctx.currentPageUrl, ctx.languages, ctx.browserPlatform, ctx.userToken, ctx.userVisitCount, (js, args) => executeJs(js, args, { isExpression: true, nonce: ctx.nonce }), ctx.logger);
2406
2561
  if (activated === false)
2407
2562
  return false;
2408
2563
  if (activated !== true)
@@ -2411,9 +2566,8 @@ function checkAllAudiences(metadata, project, personalizationHeaders, currentPag
2411
2566
  return result;
2412
2567
  }
2413
2568
  async function getVariantsAndElements(personalizationHeaders, project, previewVariantId, userToken, currentPageUrl, languages, browserPlatform, logger, nonce, laneClaims = {}, silentFallback = false) {
2414
- var _a, _b;
2569
+ var _a;
2415
2570
  const experimentsInfo = [];
2416
- const variantsAndElements = [];
2417
2571
  const cloakBot = !!((_a = project.settings) === null || _a === void 0 ? void 0 : _a.bot_cloaking_enabled) &&
2418
2572
  personalizationHeaders['x-is-bot'] === 'true';
2419
2573
  const buildResponse = ({ variantsAndElements, experimentsInfo, hasActivatedFrames: frameActivated = false, laneClaims: meClaims = laneClaims, }) => {
@@ -2424,39 +2578,30 @@ async function getVariantsAndElements(personalizationHeaders, project, previewVa
2424
2578
  laneClaims: meClaims,
2425
2579
  };
2426
2580
  };
2427
- const previewVariantIds = (_b = previewVariantId === null || previewVariantId === void 0 ? void 0 : previewVariantId.split(',').map((id) => id.trim()).filter(Boolean)) !== null && _b !== void 0 ? _b : [];
2581
+ const userVisitCount = Number.parseInt(getCookie(VISIT_COUNT_COOKIE_KEY) || '0', 10);
2582
+ const ctx = {
2583
+ project,
2584
+ personalizationHeaders,
2585
+ userToken,
2586
+ currentPageUrl,
2587
+ languages,
2588
+ browserPlatform,
2589
+ userVisitCount,
2590
+ nonce,
2591
+ logger,
2592
+ };
2593
+ const validatedClaims = validateClaims(laneClaims, project.lanes);
2594
+ const previewVariantIds = splitPreviewVariantIds(previewVariantId);
2428
2595
  if (previewVariantIds.length > 0) {
2429
- const filteredVariantsAndElements = project.frames
2430
- .flatMap((frame) => frame.experiments.flatMap((experiment) => experiment.variants.map((variant) => ({
2431
- variant,
2432
- elements: frame.elements,
2433
- }))))
2434
- .filter((item) => previewVariantIds.includes(item.variant.id));
2435
- const skipAlert = previewVariantIds.some((id) => id === project.holdout_info.holdout_variant_id || id === GLOBAL_HOLDOUT);
2436
- if (filteredVariantsAndElements.length > 0) {
2596
+ const variantsAndElements = previewVariants(previewVariantIds, validatedClaims, ctx, silentFallback);
2597
+ if (variantsAndElements) {
2437
2598
  return buildResponse({
2438
- variantsAndElements: filteredVariantsAndElements,
2439
- hasActivatedFrames: true,
2599
+ variantsAndElements,
2600
+ hasActivatedFrames: variantsAndElements.length > 0,
2440
2601
  });
2441
2602
  }
2442
- if (!skipAlert && !silentFallback) {
2443
- window.alert(`[Coframe] Variant preview unavailable: ${previewVariantId}.`);
2444
- }
2445
- if (silentFallback) ;
2446
- else {
2447
- return buildResponse({});
2448
- }
2449
2603
  }
2450
- const userVisitCount = Number.parseInt(getCookie(VISIT_COUNT_COOKIE_KEY) || '0', 10);
2451
- const validatedClaims = validateClaims(laneClaims, project.lanes);
2452
- const activeFrames = project.frames.filter((frame) => {
2453
- if (!frame.experiments)
2454
- return false;
2455
- const frameActivated = checkActivationCondition(frame.activation_condition, personalizationHeaders, currentPageUrl, languages, browserPlatform, userToken, userVisitCount, (js, args) => executeJs(js, args, { isExpression: true, nonce }), logger);
2456
- if (!frameActivated)
2457
- return false;
2458
- return true;
2459
- });
2604
+ const activeFrames = activateFrames(ctx);
2460
2605
  if (activeFrames.length === 0) {
2461
2606
  return buildResponse({ laneClaims: validatedClaims });
2462
2607
  }
@@ -2474,7 +2619,6 @@ async function getVariantsAndElements(personalizationHeaders, project, previewVa
2474
2619
  level: GLOBAL_HOLDOUT,
2475
2620
  });
2476
2621
  return buildResponse({
2477
- variantsAndElements,
2478
2622
  experimentsInfo,
2479
2623
  hasActivatedFrames: true,
2480
2624
  laneClaims: validatedClaims,
@@ -2489,7 +2633,27 @@ async function getVariantsAndElements(personalizationHeaders, project, previewVa
2489
2633
  final_state: false,
2490
2634
  level: GLOBAL_HOLDOUT,
2491
2635
  });
2492
- const evalAudiences = (metadata) => checkAllAudiences(metadata, project, personalizationHeaders, currentPageUrl, languages, browserPlatform, userToken, userVisitCount, nonce, logger) !== false;
2636
+ const assignment = assignFrames(activeFrames, validatedClaims, ctx);
2637
+ return buildResponse({
2638
+ ...assignment,
2639
+ experimentsInfo: [...experimentsInfo, ...assignment.experimentsInfo],
2640
+ });
2641
+ }
2642
+ function activateFrames(ctx) {
2643
+ return ctx.project.frames.filter((frame) => {
2644
+ if (!frame.experiments)
2645
+ return false;
2646
+ const frameActivated = checkActivationCondition(frame.activation_condition, ctx.personalizationHeaders, ctx.currentPageUrl, ctx.languages, ctx.browserPlatform, ctx.userToken, ctx.userVisitCount, (js, args) => executeJs(js, args, { isExpression: true, nonce: ctx.nonce }), ctx.logger);
2647
+ if (!frameActivated)
2648
+ return false;
2649
+ return true;
2650
+ });
2651
+ }
2652
+ function assignFrames(activeFrames, validatedClaims, ctx) {
2653
+ const { project, userToken, logger } = ctx;
2654
+ const experimentsInfo = [];
2655
+ const variantsAndElements = [];
2656
+ const evalAudiences = (metadata) => checkAllAudiences(metadata, ctx) !== false;
2493
2657
  const audiencePass = new Map();
2494
2658
  const reachableByLane = {};
2495
2659
  for (const frame of activeFrames) {
@@ -2582,50 +2746,49 @@ async function getVariantsAndElements(personalizationHeaders, project, previewVa
2582
2746
  variantsAndElements.push({ variant, elements });
2583
2747
  }
2584
2748
  }
2585
- return buildResponse({
2749
+ return {
2586
2750
  variantsAndElements,
2587
2751
  experimentsInfo,
2588
2752
  hasActivatedFrames,
2589
2753
  laneClaims: updatedClaims,
2590
- });
2754
+ };
2591
2755
  }
2592
-
2593
- const COFRAME_PREVIEW_SESSION_STORAGE_KEY = 'coframe.preview_variant';
2594
- const COFRAME_PREVIEW_VARIANT_QUERY_PARAM = 'coframe_variant_id';
2595
- function normalizePreviewVariantId(raw) {
2596
- if (!raw)
2756
+ function previewVariants(previewVariantIds, validatedClaims, ctx, silentFallback) {
2757
+ const previewRollouts = previewVariantIds.includes(ROLLOUTS_PREVIEW_ID);
2758
+ const requestedVariantIds = previewVariantIds.filter((id) => !isPreviewSentinel(id));
2759
+ const requestedVariants = findVariants(ctx.project.frames, requestedVariantIds);
2760
+ const requestedVariantsMissing = requestedVariantIds.length > 0 && requestedVariants.length === 0;
2761
+ if (requestedVariantsMissing && !previewRollouts && silentFallback) {
2597
2762
  return null;
2598
- const ids = raw
2599
- .split(',')
2600
- .map((id) => id.trim())
2601
- .filter(Boolean);
2602
- return ids.length > 0 ? ids.join(',') : null;
2603
- }
2604
- function readPreviewVariantIdFromUrl(url = window.location.href) {
2605
- try {
2606
- return normalizePreviewVariantId(getUrlSearchParams(url).get(COFRAME_PREVIEW_VARIANT_QUERY_PARAM));
2607
2763
  }
2608
- catch (_a) {
2609
- return null;
2764
+ const requestedHoldout = requestedVariantIds.includes(ctx.project.holdout_info.holdout_variant_id);
2765
+ if (requestedVariantsMissing && !requestedHoldout && !silentFallback) {
2766
+ window.alert(`[Coframe] Variant preview unavailable: ${previewVariantIds.join(',')}.`);
2610
2767
  }
2768
+ const rolledOutVariants = previewRollouts && !previewVariantIds.includes(GLOBAL_HOLDOUT)
2769
+ ? assignFrames(keepRolledOutExperiments(activateFrames(ctx)), validatedClaims, ctx).variantsAndElements
2770
+ : [];
2771
+ return uniqueByVariantId([...rolledOutVariants, ...requestedVariants]);
2611
2772
  }
2612
- function readStoredPreviewVariantId() {
2613
- try {
2614
- return normalizePreviewVariantId(window.sessionStorage.getItem(COFRAME_PREVIEW_SESSION_STORAGE_KEY));
2615
- }
2616
- catch (_a) {
2617
- return null;
2618
- }
2773
+ function findVariants(frames, variantIds) {
2774
+ return frames.flatMap((frame) => frame.experiments.flatMap((experiment) => experiment.variants
2775
+ .filter((variant) => variantIds.includes(variant.id))
2776
+ .map((variant) => ({ variant, elements: frame.elements }))));
2619
2777
  }
2620
- function resolvePreviewSource(url = window.location.href) {
2621
- const urlVariantId = readPreviewVariantIdFromUrl(url);
2622
- const storedVariantId = readStoredPreviewVariantId();
2623
- return {
2624
- effectiveVariantId: urlVariantId || storedVariantId,
2625
- isKeptForSession: Boolean(storedVariantId && storedVariantId === (urlVariantId || storedVariantId)),
2626
- storedVariantId,
2627
- urlVariantId,
2628
- };
2778
+ function uniqueByVariantId(items) {
2779
+ const seen = {};
2780
+ return items.filter(({ variant }) => {
2781
+ if (seen[variant.id])
2782
+ return false;
2783
+ seen[variant.id] = true;
2784
+ return true;
2785
+ });
2786
+ }
2787
+ function keepRolledOutExperiments(frames) {
2788
+ return frames.map((frame) => ({
2789
+ ...frame,
2790
+ experiments: frame.experiments.filter((experiment) => experiment.metadata.serving_mode === 'winner'),
2791
+ }));
2629
2792
  }
2630
2793
 
2631
2794
  function buildSessionContext(isInitialApply) {
@@ -2911,29 +3074,27 @@ function createTreatmentAnalytics(Coframe, previewVariantId) {
2911
3074
  });
2912
3075
  }
2913
3076
  }
2914
- if (previewVariantId) {
2915
- const previewIds = previewVariantId.split(',');
2916
- for (const id of previewIds) {
2917
- treatmentsData.push({
2918
- impressionId: sessionId,
2919
- userToken,
2920
- experimentId: `preview_id_${id}`,
2921
- holdoutExperimentId: `preview_holdout_id_${id}`,
2922
- variantId: id,
2923
- inGlobalHoldout: false,
2924
- isBaseline: false,
2925
- });
2926
- treatmentsDataV2.push({
2927
- coframe_experiment_id: `preview_id_${id}`,
2928
- coframe_experiment_slug: `preview_id_${id}`,
2929
- coframe_variant_id: id,
2930
- coframe_variant_slug: id,
2931
- coframe_user_token: userToken,
2932
- coframe_impression_id: sessionId,
2933
- coframe_in_global_holdout: false,
2934
- coframe_in_exp_baseline: false,
2935
- });
2936
- }
3077
+ const previewIds = splitPreviewVariantIds(previewVariantId).filter((id) => !isPreviewSentinel(id));
3078
+ for (const id of previewIds) {
3079
+ treatmentsData.push({
3080
+ impressionId: sessionId,
3081
+ userToken,
3082
+ experimentId: `preview_id_${id}`,
3083
+ holdoutExperimentId: `preview_holdout_id_${id}`,
3084
+ variantId: id,
3085
+ inGlobalHoldout: false,
3086
+ isBaseline: false,
3087
+ });
3088
+ treatmentsDataV2.push({
3089
+ coframe_experiment_id: `preview_id_${id}`,
3090
+ coframe_experiment_slug: `preview_id_${id}`,
3091
+ coframe_variant_id: id,
3092
+ coframe_variant_slug: id,
3093
+ coframe_user_token: userToken,
3094
+ coframe_impression_id: sessionId,
3095
+ coframe_in_global_holdout: false,
3096
+ coframe_in_exp_baseline: false,
3097
+ });
2937
3098
  }
2938
3099
  Coframe.treatmentsData = treatmentsData;
2939
3100
  Coframe.treatmentsDataV2 = treatmentsDataV2;
@@ -3178,12 +3339,12 @@ async function ensureBody(Coframe, cdnProject, timeoutMs, nonce) {
3178
3339
  }
3179
3340
  const FLUSH_EVENTS_TIME = 1000 * 3;
3180
3341
  let flushInterval = null;
3181
- function setupFlushInterval(projectId) {
3182
- flushQueuedEvents(projectId);
3342
+ function setupFlushInterval(Coframe) {
3343
+ flushQueuedEvents(Coframe.projectId);
3183
3344
  if (flushInterval !== null) {
3184
3345
  clearInterval(flushInterval);
3185
3346
  }
3186
- flushInterval = setInterval(() => flushQueuedEvents(projectId), FLUSH_EVENTS_TIME);
3347
+ flushInterval = setInterval(() => flushQueuedEvents(Coframe.projectId), FLUSH_EVENTS_TIME);
3187
3348
  }
3188
3349
  function setupSPANavigation(Coframe, project, config, previewVariantId, isSessionVariant, applyVariants) {
3189
3350
  var _a;
@@ -3211,6 +3372,7 @@ function setupSPANavigation(Coframe, project, config, previewVariantId, isSessio
3211
3372
  observer.observe(document.body, { childList: true, subtree: true });
3212
3373
  let navigationAbortController = null;
3213
3374
  observeUrlChanges(false, async (newPageUrl, _oldPageUrl) => {
3375
+ var _a;
3214
3376
  await new Promise((resolve) => requestAnimationFrame(resolve));
3215
3377
  infiniteLoopProtection = 0;
3216
3378
  if (navigationAbortController)
@@ -3218,27 +3380,33 @@ function setupSPANavigation(Coframe, project, config, previewVariantId, isSessio
3218
3380
  navigationAbortController = new AbortController();
3219
3381
  const { signal } = navigationAbortController;
3220
3382
  let pageProject = project;
3383
+ let pagePersonalizationHeaders = Coframe.personalizationHeaders;
3221
3384
  const cached = getCachedFrames(newPageUrl);
3222
3385
  if (cached) {
3223
3386
  pageProject = cached.project;
3387
+ pagePersonalizationHeaders = {
3388
+ ...cached.personalizationHeaders,
3389
+ 'x-visit-url': newPageUrl,
3390
+ };
3224
3391
  }
3225
3392
  else {
3226
3393
  try {
3227
3394
  const preview = resolvePreview(newPageUrl, previewVariantId, isSessionVariant);
3228
- const result = await fetchProjectFromEdge(Coframe.projectId, newPageUrl, signal, config.userContext, preview.previewVariantId || undefined);
3395
+ const result = await fetchProject(config.projectUrl, Coframe.projectId, newPageUrl, signal, config.userContext, preview.previewVariantId || undefined, undefined, (_a = config.referrer) !== null && _a !== void 0 ? _a : getBrowserReferrer());
3229
3396
  if (signal.aborted)
3230
3397
  return;
3231
3398
  if (result) {
3232
3399
  pageProject = result.project;
3400
+ pagePersonalizationHeaders = result.personalizationHeaders;
3233
3401
  cacheFrames(newPageUrl, result);
3234
3402
  }
3235
3403
  }
3236
- catch (_a) {
3404
+ catch (_b) {
3237
3405
  }
3238
3406
  }
3239
3407
  if (signal.aborted)
3240
3408
  return;
3241
- await applyVariants(newPageUrl, pageProject);
3409
+ await applyVariants(newPageUrl, pageProject, pagePersonalizationHeaders);
3242
3410
  });
3243
3411
  }
3244
3412
  function readLaneClaims() {
@@ -3269,13 +3437,14 @@ function persistLaneClaims(claims, logger) {
3269
3437
  setCookie(LANE_COOKIE_KEY, encodeURIComponent(serialized), logger);
3270
3438
  }
3271
3439
  }
3272
- function createApplyVariants(Coframe, config, previewVariantId, isSessionVariant, executionStartTime, analytics, personalizationHeaders, laneStore) {
3440
+ function createApplyVariants(Coframe, config, previewVariantId, isSessionVariant, executionStartTime, analytics, laneStore) {
3273
3441
  let isFirstApply = true;
3274
- return async (pageUrl, pageProject) => {
3442
+ return async (pageUrl, pageProject, personalizationHeaders) => {
3275
3443
  const sessionId = isFirstApply ? Coframe.sessionId : generateUUID();
3276
3444
  const sessionContext = buildSessionContext(isFirstApply);
3277
3445
  isFirstApply = false;
3278
3446
  Coframe.sessionId = sessionId;
3447
+ Coframe.personalizationHeaders = personalizationHeaders;
3279
3448
  const preview = resolvePreview(pageUrl, previewVariantId, isSessionVariant);
3280
3449
  setIsPreviewVariant(!!preview.previewVariantId);
3281
3450
  const { variantsAndElements, experimentsInfo, hasActivatedFrames, laneClaims, } = await getVariantsAndElements(personalizationHeaders, pageProject, preview.previewVariantId, Coframe.userToken, pageUrl, getBrowserLanguages(), getBrowserPlatform(), Coframe.logger, config.nonce, laneStore.claims, preview.isSessionVariant);
@@ -3312,8 +3481,8 @@ async function modifyPage(Coframe, configResult, projectResult, analytics) {
3312
3481
  if (!(await ensureBody(Coframe, cdnProject, timeoutMs, config.nonce)))
3313
3482
  return;
3314
3483
  const laneStore = { claims: readLaneClaims() };
3315
- const applyVariants = createApplyVariants(Coframe, config, previewVariantId, isSessionVariant, executionStartTime, analytics, personalizationHeaders, laneStore);
3316
- await applyVariants(getCurrentPageUrl(), cdnProject);
3484
+ const applyVariants = createApplyVariants(Coframe, config, previewVariantId, isSessionVariant, executionStartTime, analytics, laneStore);
3485
+ await applyVariants(getCurrentPageUrl(), cdnProject, personalizationHeaders);
3317
3486
  if (!Coframe.variantsAndElements ||
3318
3487
  Coframe.variantsAndElements.length === 0) {
3319
3488
  revealPage();
@@ -3321,7 +3490,7 @@ async function modifyPage(Coframe, configResult, projectResult, analytics) {
3321
3490
  }
3322
3491
  reportInitTiming(Coframe, cdnProject, integrationStartTime, executionStartTime);
3323
3492
  setupSPANavigation(Coframe, cdnProject, config, previewVariantId, isSessionVariant, applyVariants);
3324
- setupFlushInterval(Coframe.projectId);
3493
+ setupFlushInterval(Coframe);
3325
3494
  }
3326
3495
 
3327
3496
  const TIMEOUT_MAX_MS = 5000;
@@ -3357,6 +3526,7 @@ async function resolveConfig(Coframe, revealPage) {
3357
3526
  : null;
3358
3527
  const executionStartTime = new Date();
3359
3528
  Coframe.batchSize = config.batchSize || 32;
3529
+ configureIngestUrl(config.ingestUrl);
3360
3530
  const gracefulExit = (msg) => {
3361
3531
  console.warn(`[Coframe] ${msg}`);
3362
3532
  revealPage();
@@ -3478,13 +3648,13 @@ async function resolveIdentity(config, Coframe, timeoutMs, gracefulExit) {
3478
3648
  }
3479
3649
 
3480
3650
  async function resolveProject(Coframe, previewVariantId, isSessionVariant, config) {
3481
- var _a;
3651
+ var _a, _b;
3482
3652
  const logger = Coframe.logger;
3483
3653
  const projectData = (_a = findLastCmd((c) => !!c.projectData)) === null || _a === void 0 ? void 0 : _a.projectData;
3484
3654
  let personalizationHeaders = (projectData === null || projectData === void 0 ? void 0 : projectData.personalizationHeaders) || null;
3485
3655
  let project = isSessionVariant ? undefined : projectData === null || projectData === void 0 ? void 0 : projectData.project;
3486
3656
  if (!project) {
3487
- const edgeResult = await fetchProjectFromEdge(Coframe.projectId, config.currentUrl || getCurrentPageUrl(), undefined, config.userContext, previewVariantId || undefined);
3657
+ const edgeResult = await fetchProject(config.projectUrl, Coframe.projectId, config.currentUrl || getCurrentPageUrl(), undefined, config.userContext, previewVariantId || undefined, undefined, (_b = config.referrer) !== null && _b !== void 0 ? _b : getBrowserReferrer());
3488
3658
  personalizationHeaders = (edgeResult === null || edgeResult === void 0 ? void 0 : edgeResult.personalizationHeaders) || null;
3489
3659
  project = (edgeResult === null || edgeResult === void 0 ? void 0 : edgeResult.project) || undefined;
3490
3660
  }
@@ -8,7 +8,7 @@ const CoframeContext = React.createContext({ treatments: null });
8
8
  function CoframeProvider({ projectConfig, projectData, onTreatmentApplied: onTreatmentAppliedProp, onTreatmentAppliedBulk: onTreatmentAppliedBulkProp, onTreatmentAppliedBulkV2, onTreatmentAppliedBulkV2WithoutConsent, onLog: onLogProp, children, }) {
9
9
  const [treatments, setTreatments] = React.useState(null);
10
10
  React.useEffect(() => {
11
- var _a, _b;
11
+ var _a, _b, _c;
12
12
  if (/cf_disable/.test(window.location.search)) {
13
13
  sdk.init().catch((e) => {
14
14
  console.error('[Coframe] Error disabling SDK', e);
@@ -33,6 +33,7 @@ function CoframeProvider({ projectConfig, projectData, onTreatmentApplied: onTre
33
33
  ...projectConfig,
34
34
  startTime: (_a = projectConfig.startTime) !== null && _a !== void 0 ? _a : Date.now(),
35
35
  currentUrl: (_b = projectConfig.currentUrl) !== null && _b !== void 0 ? _b : window.location.href,
36
+ referrer: (_c = projectConfig.referrer) !== null && _c !== void 0 ? _c : document.referrer,
36
37
  },
37
38
  ...(projectData ? { projectData } : {}),
38
39
  })
@@ -7,7 +7,7 @@ const CoframeContext = createContext({ treatments: null });
7
7
  function CoframeProvider({ projectConfig, projectData, onTreatmentApplied: onTreatmentAppliedProp, onTreatmentAppliedBulk: onTreatmentAppliedBulkProp, onTreatmentAppliedBulkV2, onTreatmentAppliedBulkV2WithoutConsent, onLog: onLogProp, children, }) {
8
8
  const [treatments, setTreatments] = useState(null);
9
9
  useEffect(() => {
10
- var _a, _b;
10
+ var _a, _b, _c;
11
11
  if (/cf_disable/.test(window.location.search)) {
12
12
  init().catch((e) => {
13
13
  console.error('[Coframe] Error disabling SDK', e);
@@ -32,6 +32,7 @@ function CoframeProvider({ projectConfig, projectData, onTreatmentApplied: onTre
32
32
  ...projectConfig,
33
33
  startTime: (_a = projectConfig.startTime) !== null && _a !== void 0 ? _a : Date.now(),
34
34
  currentUrl: (_b = projectConfig.currentUrl) !== null && _b !== void 0 ? _b : window.location.href,
35
+ referrer: (_c = projectConfig.referrer) !== null && _c !== void 0 ? _c : document.referrer,
35
36
  },
36
37
  ...(projectData ? { projectData } : {}),
37
38
  })