@coframe/sdk 1.1.3 → 1.1.4

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.
@@ -1105,8 +1105,11 @@ function createSession(data) {
1105
1105
  console.warn('[Coframe] cannot send event beacon, feature unavailable');
1106
1106
  }
1107
1107
  }
1108
- async function fetchProjectFromEdge(projectId, currentUrl, signal, userContext, headers) {
1108
+ async function fetchProjectFromEdge(projectId, currentUrl, signal, userContext, previewVariantId, headers) {
1109
1109
  const payload = { projectId, currentUrl };
1110
+ if (previewVariantId) {
1111
+ payload.previewVariantId = previewVariantId;
1112
+ }
1110
1113
  const safe = sanitizeUserContext(userContext);
1111
1114
  if (safe) {
1112
1115
  payload.userContext = safe;
@@ -2296,7 +2299,7 @@ function checkAllAudiences(metadata, project, personalizationHeaders, currentPag
2296
2299
  }
2297
2300
  return result;
2298
2301
  }
2299
- async function getVariantsAndElements(personalizationHeaders, project, previewVariantId, userToken, currentPageUrl, languages, browserPlatform, logger, nonce, laneClaims = {}) {
2302
+ async function getVariantsAndElements(personalizationHeaders, project, previewVariantId, userToken, currentPageUrl, languages, browserPlatform, logger, nonce, laneClaims = {}, silentFallback = false) {
2300
2303
  var _a, _b;
2301
2304
  const experimentsInfo = [];
2302
2305
  const variantsAndElements = [];
@@ -2325,10 +2328,13 @@ async function getVariantsAndElements(personalizationHeaders, project, previewVa
2325
2328
  hasActivatedFrames: true,
2326
2329
  });
2327
2330
  }
2328
- if (!skipAlert) {
2331
+ if (!skipAlert && !silentFallback) {
2329
2332
  window.alert(`[Coframe] Variant preview unavailable: ${previewVariantId}.`);
2330
2333
  }
2331
- return buildResponse({});
2334
+ if (silentFallback) ;
2335
+ else {
2336
+ return buildResponse({});
2337
+ }
2332
2338
  }
2333
2339
  const userVisitCount = Number.parseInt(getCookie(VISIT_COUNT_COOKIE_KEY) || '0', 10);
2334
2340
  const validatedClaims = validateClaims(laneClaims, project.lanes);
@@ -2599,6 +2605,125 @@ function observeUrlChanges(baseUrlMatch, onUrlChange) {
2599
2605
  patchHistoryMethod('replaceState');
2600
2606
  }
2601
2607
 
2608
+ const TIMEOUT_MAX_MS = 5000;
2609
+ function getFeatureFlagValue(cmd) {
2610
+ if (cmd.featureFlagSet !== undefined)
2611
+ return cmd.featureFlagSet;
2612
+ if (typeof cmd.emit === 'object' &&
2613
+ cmd.emit !== null &&
2614
+ 'featureFlagSet' in cmd.emit) {
2615
+ return cmd.emit.featureFlagSet;
2616
+ }
2617
+ return undefined;
2618
+ }
2619
+ async function resolveFeatureFlag(Coframe, timeoutMs) {
2620
+ const cmd = await waitForCmd((cmd) => typeof getFeatureFlagValue(cmd) === 'boolean', Coframe, timeoutMs);
2621
+ if (!cmd)
2622
+ return undefined;
2623
+ return getFeatureFlagValue(cmd);
2624
+ }
2625
+ async function resolveConfig(Coframe, revealPage) {
2626
+ const logger = Coframe.logger;
2627
+ const configCmd = await waitForCmd((cmd) => !!cmd.config, Coframe);
2628
+ if (!configCmd) {
2629
+ logger.error('Failed to wait for config cmd', 'wait_for_cmd_timeout');
2630
+ return null;
2631
+ }
2632
+ const config = configCmd.config;
2633
+ const timeoutMs = (config === null || config === void 0 ? void 0 : config.timeoutMs)
2634
+ ? Math.min(config.timeoutMs, TIMEOUT_MAX_MS)
2635
+ : TIMEOUT_MAX_MS;
2636
+ const integrationStartTime = (config === null || config === void 0 ? void 0 : config.startTime)
2637
+ ? new Date(config.startTime)
2638
+ : null;
2639
+ const executionStartTime = new Date();
2640
+ Coframe.batchSize = config.batchSize || 32;
2641
+ const gracefulExit = (msg) => {
2642
+ console.warn(`[Coframe] ${msg}`);
2643
+ revealPage();
2644
+ return null;
2645
+ };
2646
+ if (config.waitForFeatureFlag) {
2647
+ const flag = await resolveFeatureFlag(Coframe, timeoutMs);
2648
+ if (flag === undefined) {
2649
+ return gracefulExit('Feature flag was not set after timeout, graceful exit');
2650
+ }
2651
+ if (flag === false) {
2652
+ return gracefulExit('Feature flag is set to false, graceful exit');
2653
+ }
2654
+ }
2655
+ const projectId = config === null || config === void 0 ? void 0 : config.projectId;
2656
+ const warnMissingVariant = () => {
2657
+ if (applyVariantChangesStarted) {
2658
+ logger.warn('Failed to wait for variant rendered command', 'missing_variant_rendered');
2659
+ }
2660
+ };
2661
+ waitForCmd((cmd) => cmd.variantRendered === true || cmd.emit === 'variantRendered', Coframe, timeoutMs)
2662
+ .then((cmd) => {
2663
+ if (!cmd) {
2664
+ warnMissingVariant();
2665
+ }
2666
+ })
2667
+ .finally(() => {
2668
+ revealPage();
2669
+ });
2670
+ if (!projectId) {
2671
+ logger.error('No project ID', 'no_project_id', {
2672
+ description: 'No Coframe Page ID',
2673
+ });
2674
+ variantRendered();
2675
+ return null;
2676
+ }
2677
+ Coframe.projectId = projectId;
2678
+ const urlParams = getCurrentUrlSearchParams();
2679
+ const urlVariantId = urlParams.get('coframe_variant_id');
2680
+ const shouldSendEventsParam = urlParams.get('coframe_should_send_events');
2681
+ const keepForSession = urlParams.get('keep_preview_for_session') === 'true';
2682
+ let previewVariantId = null;
2683
+ let isSessionVariant = false;
2684
+ if (urlVariantId) {
2685
+ previewVariantId = urlVariantId;
2686
+ if (keepForSession) {
2687
+ persistPreviewVariantId(urlVariantId);
2688
+ }
2689
+ }
2690
+ else {
2691
+ const stored = readPreviewVariantId();
2692
+ if (stored) {
2693
+ previewVariantId = stored;
2694
+ isSessionVariant = true;
2695
+ }
2696
+ }
2697
+ setForceSendEvents(shouldSendEventsParam === 'true');
2698
+ setIsPreviewVariant(!!previewVariantId);
2699
+ setWaitingForCookies(!!config.waitForCookiesEnabled);
2700
+ return {
2701
+ config,
2702
+ timeoutMs,
2703
+ projectId,
2704
+ previewVariantId,
2705
+ isSessionVariant,
2706
+ integrationStartTime,
2707
+ executionStartTime,
2708
+ };
2709
+ }
2710
+ const SESSION_VARIANT_KEY = 'coframe.preview_variant';
2711
+ function readPreviewVariantId() {
2712
+ try {
2713
+ return sessionStorage.getItem(SESSION_VARIANT_KEY);
2714
+ }
2715
+ catch (_a) {
2716
+ return null;
2717
+ }
2718
+ }
2719
+ function persistPreviewVariantId(variantId) {
2720
+ try {
2721
+ sessionStorage.setItem(SESSION_VARIANT_KEY, variantId);
2722
+ }
2723
+ catch (_a) {
2724
+ }
2725
+ }
2726
+
2602
2727
  function startSession(Coframe, cookiesEnabled, executionStartTime, personalizationHeaders) {
2603
2728
  const sessionPayload = [
2604
2729
  Coframe,
@@ -2910,6 +3035,23 @@ function setupTracking(Coframe, projectResult, previewVariantId, executionStartT
2910
3035
  return analytics;
2911
3036
  }
2912
3037
 
3038
+ function resolvePreview(pageUrl, previewVariantId, isSessionVariant) {
3039
+ let urlPreviewVariantId = null;
3040
+ try {
3041
+ urlPreviewVariantId = new URL(pageUrl).searchParams.get('coframe_variant_id');
3042
+ }
3043
+ catch (_a) {
3044
+ urlPreviewVariantId = getCurrentUrlSearchParams().get('coframe_variant_id');
3045
+ }
3046
+ if (urlPreviewVariantId) {
3047
+ return { previewVariantId: urlPreviewVariantId, isSessionVariant: false };
3048
+ }
3049
+ if (previewVariantId) {
3050
+ return { previewVariantId, isSessionVariant };
3051
+ }
3052
+ const stored = readPreviewVariantId();
3053
+ return { previewVariantId: stored, isSessionVariant: !!stored };
3054
+ }
2913
3055
  async function awaitHydration(config, Coframe, project) {
2914
3056
  var _a, _b;
2915
3057
  if (!config.waitForHydration)
@@ -2942,7 +3084,7 @@ function setupFlushInterval(projectId) {
2942
3084
  }
2943
3085
  flushInterval = setInterval(() => flushQueuedEvents(projectId), FLUSH_EVENTS_TIME);
2944
3086
  }
2945
- function setupSPANavigation(Coframe, project, config, applyVariants) {
3087
+ function setupSPANavigation(Coframe, project, config, previewVariantId, isSessionVariant, applyVariants) {
2946
3088
  var _a;
2947
3089
  if (!((_a = project === null || project === void 0 ? void 0 : project.settings) === null || _a === void 0 ? void 0 : _a.mutation_observer_active))
2948
3090
  return;
@@ -2981,7 +3123,8 @@ function setupSPANavigation(Coframe, project, config, applyVariants) {
2981
3123
  }
2982
3124
  else {
2983
3125
  try {
2984
- const result = await fetchProjectFromEdge(Coframe.projectId, newPageUrl, signal, config.userContext);
3126
+ const preview = resolvePreview(newPageUrl, previewVariantId, isSessionVariant);
3127
+ const result = await fetchProjectFromEdge(Coframe.projectId, newPageUrl, signal, config.userContext, preview.previewVariantId || undefined);
2985
3128
  if (signal.aborted)
2986
3129
  return;
2987
3130
  if (result) {
@@ -3025,17 +3168,15 @@ function persistLaneClaims(claims, logger) {
3025
3168
  setCookie(LANE_COOKIE_KEY, encodeURIComponent(serialized), logger);
3026
3169
  }
3027
3170
  }
3028
- function createApplyVariants(Coframe, config, previewVariantId, executionStartTime, analytics, personalizationHeaders, laneStore) {
3171
+ function createApplyVariants(Coframe, config, previewVariantId, isSessionVariant, executionStartTime, analytics, personalizationHeaders, laneStore) {
3029
3172
  let isFirstApply = true;
3030
3173
  return async (pageUrl, pageProject) => {
3031
3174
  const sessionId = isFirstApply ? Coframe.sessionId : generateUUID();
3032
3175
  isFirstApply = false;
3033
3176
  Coframe.sessionId = sessionId;
3034
- const urlPreviewVariantId = getCurrentUrlSearchParams().get('coframe_variant_id') || null;
3035
- const updatedPreviewVariantId = urlPreviewVariantId !== previewVariantId
3036
- ? urlPreviewVariantId
3037
- : previewVariantId;
3038
- const { variantsAndElements, experimentsInfo, hasActivatedFrames, laneClaims, } = await getVariantsAndElements(personalizationHeaders, pageProject, updatedPreviewVariantId, Coframe.userToken, pageUrl, getBrowserLanguages(), getBrowserPlatform(), Coframe.logger, config.nonce, laneStore.claims);
3177
+ const preview = resolvePreview(pageUrl, previewVariantId, isSessionVariant);
3178
+ setIsPreviewVariant(!!preview.previewVariantId);
3179
+ const { variantsAndElements, experimentsInfo, hasActivatedFrames, laneClaims, } = await getVariantsAndElements(personalizationHeaders, pageProject, preview.previewVariantId, Coframe.userToken, pageUrl, getBrowserLanguages(), getBrowserPlatform(), Coframe.logger, config.nonce, laneStore.claims, preview.isSessionVariant);
3039
3180
  laneStore.claims = laneClaims;
3040
3181
  persistLaneClaims(laneClaims, Coframe.logger);
3041
3182
  Coframe.experimentInfo = experimentsInfo;
@@ -3058,7 +3199,7 @@ function createApplyVariants(Coframe, config, previewVariantId, executionStartTi
3058
3199
  };
3059
3200
  }
3060
3201
  async function modifyPage(Coframe, configResult, projectResult, analytics) {
3061
- const { config, timeoutMs, previewVariantId, integrationStartTime, executionStartTime, } = configResult;
3202
+ const { config, timeoutMs, previewVariantId, isSessionVariant, integrationStartTime, executionStartTime, } = configResult;
3062
3203
  const { project: cdnProject, personalizationHeaders } = projectResult;
3063
3204
  if (!(await awaitHydration(config, Coframe, cdnProject)))
3064
3205
  return;
@@ -3069,7 +3210,7 @@ async function modifyPage(Coframe, configResult, projectResult, analytics) {
3069
3210
  if (!(await ensureBody(Coframe, cdnProject, timeoutMs, config.nonce)))
3070
3211
  return;
3071
3212
  const laneStore = { claims: readLaneClaims() };
3072
- const applyVariants = createApplyVariants(Coframe, config, previewVariantId, executionStartTime, analytics, personalizationHeaders, laneStore);
3213
+ const applyVariants = createApplyVariants(Coframe, config, previewVariantId, isSessionVariant, executionStartTime, analytics, personalizationHeaders, laneStore);
3073
3214
  await applyVariants(getCurrentPageUrl(), cdnProject);
3074
3215
  if (!Coframe.variantsAndElements ||
3075
3216
  Coframe.variantsAndElements.length === 0) {
@@ -3077,96 +3218,10 @@ async function modifyPage(Coframe, configResult, projectResult, analytics) {
3077
3218
  variantRendered();
3078
3219
  }
3079
3220
  reportInitTiming(Coframe, cdnProject, integrationStartTime, executionStartTime);
3080
- setupSPANavigation(Coframe, cdnProject, config, applyVariants);
3221
+ setupSPANavigation(Coframe, cdnProject, config, previewVariantId, isSessionVariant, applyVariants);
3081
3222
  setupFlushInterval(Coframe.projectId);
3082
3223
  }
3083
3224
 
3084
- const TIMEOUT_MAX_MS = 5000;
3085
- function getFeatureFlagValue(cmd) {
3086
- if (cmd.featureFlagSet !== undefined)
3087
- return cmd.featureFlagSet;
3088
- if (typeof cmd.emit === 'object' &&
3089
- cmd.emit !== null &&
3090
- 'featureFlagSet' in cmd.emit) {
3091
- return cmd.emit.featureFlagSet;
3092
- }
3093
- return undefined;
3094
- }
3095
- async function resolveFeatureFlag(Coframe, timeoutMs) {
3096
- const cmd = await waitForCmd((cmd) => typeof getFeatureFlagValue(cmd) === 'boolean', Coframe, timeoutMs);
3097
- if (!cmd)
3098
- return undefined;
3099
- return getFeatureFlagValue(cmd);
3100
- }
3101
- async function resolveConfig(Coframe, revealPage) {
3102
- const logger = Coframe.logger;
3103
- const configCmd = await waitForCmd((cmd) => !!cmd.config, Coframe);
3104
- if (!configCmd) {
3105
- logger.error('Failed to wait for config cmd', 'wait_for_cmd_timeout');
3106
- return null;
3107
- }
3108
- const config = configCmd.config;
3109
- const timeoutMs = (config === null || config === void 0 ? void 0 : config.timeoutMs)
3110
- ? Math.min(config.timeoutMs, TIMEOUT_MAX_MS)
3111
- : TIMEOUT_MAX_MS;
3112
- const integrationStartTime = (config === null || config === void 0 ? void 0 : config.startTime)
3113
- ? new Date(config.startTime)
3114
- : null;
3115
- const executionStartTime = new Date();
3116
- Coframe.batchSize = config.batchSize || 32;
3117
- const gracefulExit = (msg) => {
3118
- console.warn(`[Coframe] ${msg}`);
3119
- revealPage();
3120
- return null;
3121
- };
3122
- if (config.waitForFeatureFlag) {
3123
- const flag = await resolveFeatureFlag(Coframe, timeoutMs);
3124
- if (flag === undefined) {
3125
- return gracefulExit('Feature flag was not set after timeout, graceful exit');
3126
- }
3127
- if (flag === false) {
3128
- return gracefulExit('Feature flag is set to false, graceful exit');
3129
- }
3130
- }
3131
- const projectId = config === null || config === void 0 ? void 0 : config.projectId;
3132
- const warnMissingVariant = () => {
3133
- if (applyVariantChangesStarted) {
3134
- logger.warn('Failed to wait for variant rendered command', 'missing_variant_rendered');
3135
- }
3136
- };
3137
- waitForCmd((cmd) => cmd.variantRendered === true || cmd.emit === 'variantRendered', Coframe, timeoutMs)
3138
- .then((cmd) => {
3139
- if (!cmd) {
3140
- warnMissingVariant();
3141
- }
3142
- })
3143
- .finally(() => {
3144
- revealPage();
3145
- });
3146
- if (!projectId) {
3147
- logger.error('No project ID', 'no_project_id', {
3148
- description: 'No Coframe Page ID',
3149
- });
3150
- variantRendered();
3151
- return null;
3152
- }
3153
- Coframe.projectId = projectId;
3154
- const urlParams = getCurrentUrlSearchParams();
3155
- const previewVariantId = urlParams.get('coframe_variant_id');
3156
- const shouldSendEventsParam = urlParams.get('coframe_should_send_events');
3157
- setForceSendEvents(shouldSendEventsParam === 'true');
3158
- setIsPreviewVariant(!!previewVariantId);
3159
- setWaitingForCookies(!!config.waitForCookiesEnabled);
3160
- return {
3161
- config,
3162
- timeoutMs,
3163
- projectId,
3164
- previewVariantId,
3165
- integrationStartTime,
3166
- executionStartTime,
3167
- };
3168
- }
3169
-
3170
3225
  async function resolveIdentity(config, Coframe, timeoutMs, gracefulExit) {
3171
3226
  const logger = Coframe.logger;
3172
3227
  let userToken;
@@ -3223,14 +3278,14 @@ async function resolveIdentity(config, Coframe, timeoutMs, gracefulExit) {
3223
3278
  return { userToken, applyCookieConsent, onConsentChange };
3224
3279
  }
3225
3280
 
3226
- async function resolveProject(Coframe, previewVariantId, config) {
3281
+ async function resolveProject(Coframe, previewVariantId, isSessionVariant, config) {
3227
3282
  var _a;
3228
3283
  const logger = Coframe.logger;
3229
3284
  const projectData = (_a = findLastCmd((c) => !!c.projectData)) === null || _a === void 0 ? void 0 : _a.projectData;
3230
3285
  let personalizationHeaders = (projectData === null || projectData === void 0 ? void 0 : projectData.personalizationHeaders) || null;
3231
- let project = projectData === null || projectData === void 0 ? void 0 : projectData.project;
3286
+ let project = isSessionVariant ? undefined : projectData === null || projectData === void 0 ? void 0 : projectData.project;
3232
3287
  if (!project) {
3233
- const edgeResult = await fetchProjectFromEdge(Coframe.projectId, config.currentUrl || getCurrentPageUrl(), undefined, config.userContext);
3288
+ const edgeResult = await fetchProjectFromEdge(Coframe.projectId, config.currentUrl || getCurrentPageUrl(), undefined, config.userContext, previewVariantId || undefined);
3234
3289
  personalizationHeaders = (edgeResult === null || edgeResult === void 0 ? void 0 : edgeResult.personalizationHeaders) || null;
3235
3290
  project = (edgeResult === null || edgeResult === void 0 ? void 0 : edgeResult.project) || undefined;
3236
3291
  }
@@ -3322,7 +3377,7 @@ async function init(options) {
3322
3377
  const configResult = await resolveConfig(Coframe, revealPage);
3323
3378
  if (!configResult)
3324
3379
  return;
3325
- const projectResult = await resolveProject(Coframe, configResult.previewVariantId, configResult.config);
3380
+ const projectResult = await resolveProject(Coframe, configResult.previewVariantId, configResult.isSessionVariant, configResult.config);
3326
3381
  if (!projectResult)
3327
3382
  return;
3328
3383
  Coframe.orgId = projectResult.project.org_id || '';
@@ -1103,8 +1103,11 @@ function createSession(data) {
1103
1103
  console.warn('[Coframe] cannot send event beacon, feature unavailable');
1104
1104
  }
1105
1105
  }
1106
- async function fetchProjectFromEdge(projectId, currentUrl, signal, userContext, headers) {
1106
+ async function fetchProjectFromEdge(projectId, currentUrl, signal, userContext, previewVariantId, headers) {
1107
1107
  const payload = { projectId, currentUrl };
1108
+ if (previewVariantId) {
1109
+ payload.previewVariantId = previewVariantId;
1110
+ }
1108
1111
  const safe = sanitizeUserContext(userContext);
1109
1112
  if (safe) {
1110
1113
  payload.userContext = safe;
@@ -2294,7 +2297,7 @@ function checkAllAudiences(metadata, project, personalizationHeaders, currentPag
2294
2297
  }
2295
2298
  return result;
2296
2299
  }
2297
- async function getVariantsAndElements(personalizationHeaders, project, previewVariantId, userToken, currentPageUrl, languages, browserPlatform, logger, nonce, laneClaims = {}) {
2300
+ async function getVariantsAndElements(personalizationHeaders, project, previewVariantId, userToken, currentPageUrl, languages, browserPlatform, logger, nonce, laneClaims = {}, silentFallback = false) {
2298
2301
  var _a, _b;
2299
2302
  const experimentsInfo = [];
2300
2303
  const variantsAndElements = [];
@@ -2323,10 +2326,13 @@ async function getVariantsAndElements(personalizationHeaders, project, previewVa
2323
2326
  hasActivatedFrames: true,
2324
2327
  });
2325
2328
  }
2326
- if (!skipAlert) {
2329
+ if (!skipAlert && !silentFallback) {
2327
2330
  window.alert(`[Coframe] Variant preview unavailable: ${previewVariantId}.`);
2328
2331
  }
2329
- return buildResponse({});
2332
+ if (silentFallback) ;
2333
+ else {
2334
+ return buildResponse({});
2335
+ }
2330
2336
  }
2331
2337
  const userVisitCount = Number.parseInt(getCookie(VISIT_COUNT_COOKIE_KEY) || '0', 10);
2332
2338
  const validatedClaims = validateClaims(laneClaims, project.lanes);
@@ -2597,6 +2603,125 @@ function observeUrlChanges(baseUrlMatch, onUrlChange) {
2597
2603
  patchHistoryMethod('replaceState');
2598
2604
  }
2599
2605
 
2606
+ const TIMEOUT_MAX_MS = 5000;
2607
+ function getFeatureFlagValue(cmd) {
2608
+ if (cmd.featureFlagSet !== undefined)
2609
+ return cmd.featureFlagSet;
2610
+ if (typeof cmd.emit === 'object' &&
2611
+ cmd.emit !== null &&
2612
+ 'featureFlagSet' in cmd.emit) {
2613
+ return cmd.emit.featureFlagSet;
2614
+ }
2615
+ return undefined;
2616
+ }
2617
+ async function resolveFeatureFlag(Coframe, timeoutMs) {
2618
+ const cmd = await waitForCmd((cmd) => typeof getFeatureFlagValue(cmd) === 'boolean', Coframe, timeoutMs);
2619
+ if (!cmd)
2620
+ return undefined;
2621
+ return getFeatureFlagValue(cmd);
2622
+ }
2623
+ async function resolveConfig(Coframe, revealPage) {
2624
+ const logger = Coframe.logger;
2625
+ const configCmd = await waitForCmd((cmd) => !!cmd.config, Coframe);
2626
+ if (!configCmd) {
2627
+ logger.error('Failed to wait for config cmd', 'wait_for_cmd_timeout');
2628
+ return null;
2629
+ }
2630
+ const config = configCmd.config;
2631
+ const timeoutMs = (config === null || config === void 0 ? void 0 : config.timeoutMs)
2632
+ ? Math.min(config.timeoutMs, TIMEOUT_MAX_MS)
2633
+ : TIMEOUT_MAX_MS;
2634
+ const integrationStartTime = (config === null || config === void 0 ? void 0 : config.startTime)
2635
+ ? new Date(config.startTime)
2636
+ : null;
2637
+ const executionStartTime = new Date();
2638
+ Coframe.batchSize = config.batchSize || 32;
2639
+ const gracefulExit = (msg) => {
2640
+ console.warn(`[Coframe] ${msg}`);
2641
+ revealPage();
2642
+ return null;
2643
+ };
2644
+ if (config.waitForFeatureFlag) {
2645
+ const flag = await resolveFeatureFlag(Coframe, timeoutMs);
2646
+ if (flag === undefined) {
2647
+ return gracefulExit('Feature flag was not set after timeout, graceful exit');
2648
+ }
2649
+ if (flag === false) {
2650
+ return gracefulExit('Feature flag is set to false, graceful exit');
2651
+ }
2652
+ }
2653
+ const projectId = config === null || config === void 0 ? void 0 : config.projectId;
2654
+ const warnMissingVariant = () => {
2655
+ if (applyVariantChangesStarted) {
2656
+ logger.warn('Failed to wait for variant rendered command', 'missing_variant_rendered');
2657
+ }
2658
+ };
2659
+ waitForCmd((cmd) => cmd.variantRendered === true || cmd.emit === 'variantRendered', Coframe, timeoutMs)
2660
+ .then((cmd) => {
2661
+ if (!cmd) {
2662
+ warnMissingVariant();
2663
+ }
2664
+ })
2665
+ .finally(() => {
2666
+ revealPage();
2667
+ });
2668
+ if (!projectId) {
2669
+ logger.error('No project ID', 'no_project_id', {
2670
+ description: 'No Coframe Page ID',
2671
+ });
2672
+ variantRendered();
2673
+ return null;
2674
+ }
2675
+ Coframe.projectId = projectId;
2676
+ const urlParams = getCurrentUrlSearchParams();
2677
+ const urlVariantId = urlParams.get('coframe_variant_id');
2678
+ const shouldSendEventsParam = urlParams.get('coframe_should_send_events');
2679
+ const keepForSession = urlParams.get('keep_preview_for_session') === 'true';
2680
+ let previewVariantId = null;
2681
+ let isSessionVariant = false;
2682
+ if (urlVariantId) {
2683
+ previewVariantId = urlVariantId;
2684
+ if (keepForSession) {
2685
+ persistPreviewVariantId(urlVariantId);
2686
+ }
2687
+ }
2688
+ else {
2689
+ const stored = readPreviewVariantId();
2690
+ if (stored) {
2691
+ previewVariantId = stored;
2692
+ isSessionVariant = true;
2693
+ }
2694
+ }
2695
+ setForceSendEvents(shouldSendEventsParam === 'true');
2696
+ setIsPreviewVariant(!!previewVariantId);
2697
+ setWaitingForCookies(!!config.waitForCookiesEnabled);
2698
+ return {
2699
+ config,
2700
+ timeoutMs,
2701
+ projectId,
2702
+ previewVariantId,
2703
+ isSessionVariant,
2704
+ integrationStartTime,
2705
+ executionStartTime,
2706
+ };
2707
+ }
2708
+ const SESSION_VARIANT_KEY = 'coframe.preview_variant';
2709
+ function readPreviewVariantId() {
2710
+ try {
2711
+ return sessionStorage.getItem(SESSION_VARIANT_KEY);
2712
+ }
2713
+ catch (_a) {
2714
+ return null;
2715
+ }
2716
+ }
2717
+ function persistPreviewVariantId(variantId) {
2718
+ try {
2719
+ sessionStorage.setItem(SESSION_VARIANT_KEY, variantId);
2720
+ }
2721
+ catch (_a) {
2722
+ }
2723
+ }
2724
+
2600
2725
  function startSession(Coframe, cookiesEnabled, executionStartTime, personalizationHeaders) {
2601
2726
  const sessionPayload = [
2602
2727
  Coframe,
@@ -2908,6 +3033,23 @@ function setupTracking(Coframe, projectResult, previewVariantId, executionStartT
2908
3033
  return analytics;
2909
3034
  }
2910
3035
 
3036
+ function resolvePreview(pageUrl, previewVariantId, isSessionVariant) {
3037
+ let urlPreviewVariantId = null;
3038
+ try {
3039
+ urlPreviewVariantId = new URL(pageUrl).searchParams.get('coframe_variant_id');
3040
+ }
3041
+ catch (_a) {
3042
+ urlPreviewVariantId = getCurrentUrlSearchParams().get('coframe_variant_id');
3043
+ }
3044
+ if (urlPreviewVariantId) {
3045
+ return { previewVariantId: urlPreviewVariantId, isSessionVariant: false };
3046
+ }
3047
+ if (previewVariantId) {
3048
+ return { previewVariantId, isSessionVariant };
3049
+ }
3050
+ const stored = readPreviewVariantId();
3051
+ return { previewVariantId: stored, isSessionVariant: !!stored };
3052
+ }
2911
3053
  async function awaitHydration(config, Coframe, project) {
2912
3054
  var _a, _b;
2913
3055
  if (!config.waitForHydration)
@@ -2940,7 +3082,7 @@ function setupFlushInterval(projectId) {
2940
3082
  }
2941
3083
  flushInterval = setInterval(() => flushQueuedEvents(projectId), FLUSH_EVENTS_TIME);
2942
3084
  }
2943
- function setupSPANavigation(Coframe, project, config, applyVariants) {
3085
+ function setupSPANavigation(Coframe, project, config, previewVariantId, isSessionVariant, applyVariants) {
2944
3086
  var _a;
2945
3087
  if (!((_a = project === null || project === void 0 ? void 0 : project.settings) === null || _a === void 0 ? void 0 : _a.mutation_observer_active))
2946
3088
  return;
@@ -2979,7 +3121,8 @@ function setupSPANavigation(Coframe, project, config, applyVariants) {
2979
3121
  }
2980
3122
  else {
2981
3123
  try {
2982
- const result = await fetchProjectFromEdge(Coframe.projectId, newPageUrl, signal, config.userContext);
3124
+ const preview = resolvePreview(newPageUrl, previewVariantId, isSessionVariant);
3125
+ const result = await fetchProjectFromEdge(Coframe.projectId, newPageUrl, signal, config.userContext, preview.previewVariantId || undefined);
2983
3126
  if (signal.aborted)
2984
3127
  return;
2985
3128
  if (result) {
@@ -3023,17 +3166,15 @@ function persistLaneClaims(claims, logger) {
3023
3166
  setCookie(LANE_COOKIE_KEY, encodeURIComponent(serialized), logger);
3024
3167
  }
3025
3168
  }
3026
- function createApplyVariants(Coframe, config, previewVariantId, executionStartTime, analytics, personalizationHeaders, laneStore) {
3169
+ function createApplyVariants(Coframe, config, previewVariantId, isSessionVariant, executionStartTime, analytics, personalizationHeaders, laneStore) {
3027
3170
  let isFirstApply = true;
3028
3171
  return async (pageUrl, pageProject) => {
3029
3172
  const sessionId = isFirstApply ? Coframe.sessionId : generateUUID();
3030
3173
  isFirstApply = false;
3031
3174
  Coframe.sessionId = sessionId;
3032
- const urlPreviewVariantId = getCurrentUrlSearchParams().get('coframe_variant_id') || null;
3033
- const updatedPreviewVariantId = urlPreviewVariantId !== previewVariantId
3034
- ? urlPreviewVariantId
3035
- : previewVariantId;
3036
- const { variantsAndElements, experimentsInfo, hasActivatedFrames, laneClaims, } = await getVariantsAndElements(personalizationHeaders, pageProject, updatedPreviewVariantId, Coframe.userToken, pageUrl, getBrowserLanguages(), getBrowserPlatform(), Coframe.logger, config.nonce, laneStore.claims);
3175
+ const preview = resolvePreview(pageUrl, previewVariantId, isSessionVariant);
3176
+ setIsPreviewVariant(!!preview.previewVariantId);
3177
+ const { variantsAndElements, experimentsInfo, hasActivatedFrames, laneClaims, } = await getVariantsAndElements(personalizationHeaders, pageProject, preview.previewVariantId, Coframe.userToken, pageUrl, getBrowserLanguages(), getBrowserPlatform(), Coframe.logger, config.nonce, laneStore.claims, preview.isSessionVariant);
3037
3178
  laneStore.claims = laneClaims;
3038
3179
  persistLaneClaims(laneClaims, Coframe.logger);
3039
3180
  Coframe.experimentInfo = experimentsInfo;
@@ -3056,7 +3197,7 @@ function createApplyVariants(Coframe, config, previewVariantId, executionStartTi
3056
3197
  };
3057
3198
  }
3058
3199
  async function modifyPage(Coframe, configResult, projectResult, analytics) {
3059
- const { config, timeoutMs, previewVariantId, integrationStartTime, executionStartTime, } = configResult;
3200
+ const { config, timeoutMs, previewVariantId, isSessionVariant, integrationStartTime, executionStartTime, } = configResult;
3060
3201
  const { project: cdnProject, personalizationHeaders } = projectResult;
3061
3202
  if (!(await awaitHydration(config, Coframe, cdnProject)))
3062
3203
  return;
@@ -3067,7 +3208,7 @@ async function modifyPage(Coframe, configResult, projectResult, analytics) {
3067
3208
  if (!(await ensureBody(Coframe, cdnProject, timeoutMs, config.nonce)))
3068
3209
  return;
3069
3210
  const laneStore = { claims: readLaneClaims() };
3070
- const applyVariants = createApplyVariants(Coframe, config, previewVariantId, executionStartTime, analytics, personalizationHeaders, laneStore);
3211
+ const applyVariants = createApplyVariants(Coframe, config, previewVariantId, isSessionVariant, executionStartTime, analytics, personalizationHeaders, laneStore);
3071
3212
  await applyVariants(getCurrentPageUrl(), cdnProject);
3072
3213
  if (!Coframe.variantsAndElements ||
3073
3214
  Coframe.variantsAndElements.length === 0) {
@@ -3075,96 +3216,10 @@ async function modifyPage(Coframe, configResult, projectResult, analytics) {
3075
3216
  variantRendered();
3076
3217
  }
3077
3218
  reportInitTiming(Coframe, cdnProject, integrationStartTime, executionStartTime);
3078
- setupSPANavigation(Coframe, cdnProject, config, applyVariants);
3219
+ setupSPANavigation(Coframe, cdnProject, config, previewVariantId, isSessionVariant, applyVariants);
3079
3220
  setupFlushInterval(Coframe.projectId);
3080
3221
  }
3081
3222
 
3082
- const TIMEOUT_MAX_MS = 5000;
3083
- function getFeatureFlagValue(cmd) {
3084
- if (cmd.featureFlagSet !== undefined)
3085
- return cmd.featureFlagSet;
3086
- if (typeof cmd.emit === 'object' &&
3087
- cmd.emit !== null &&
3088
- 'featureFlagSet' in cmd.emit) {
3089
- return cmd.emit.featureFlagSet;
3090
- }
3091
- return undefined;
3092
- }
3093
- async function resolveFeatureFlag(Coframe, timeoutMs) {
3094
- const cmd = await waitForCmd((cmd) => typeof getFeatureFlagValue(cmd) === 'boolean', Coframe, timeoutMs);
3095
- if (!cmd)
3096
- return undefined;
3097
- return getFeatureFlagValue(cmd);
3098
- }
3099
- async function resolveConfig(Coframe, revealPage) {
3100
- const logger = Coframe.logger;
3101
- const configCmd = await waitForCmd((cmd) => !!cmd.config, Coframe);
3102
- if (!configCmd) {
3103
- logger.error('Failed to wait for config cmd', 'wait_for_cmd_timeout');
3104
- return null;
3105
- }
3106
- const config = configCmd.config;
3107
- const timeoutMs = (config === null || config === void 0 ? void 0 : config.timeoutMs)
3108
- ? Math.min(config.timeoutMs, TIMEOUT_MAX_MS)
3109
- : TIMEOUT_MAX_MS;
3110
- const integrationStartTime = (config === null || config === void 0 ? void 0 : config.startTime)
3111
- ? new Date(config.startTime)
3112
- : null;
3113
- const executionStartTime = new Date();
3114
- Coframe.batchSize = config.batchSize || 32;
3115
- const gracefulExit = (msg) => {
3116
- console.warn(`[Coframe] ${msg}`);
3117
- revealPage();
3118
- return null;
3119
- };
3120
- if (config.waitForFeatureFlag) {
3121
- const flag = await resolveFeatureFlag(Coframe, timeoutMs);
3122
- if (flag === undefined) {
3123
- return gracefulExit('Feature flag was not set after timeout, graceful exit');
3124
- }
3125
- if (flag === false) {
3126
- return gracefulExit('Feature flag is set to false, graceful exit');
3127
- }
3128
- }
3129
- const projectId = config === null || config === void 0 ? void 0 : config.projectId;
3130
- const warnMissingVariant = () => {
3131
- if (applyVariantChangesStarted) {
3132
- logger.warn('Failed to wait for variant rendered command', 'missing_variant_rendered');
3133
- }
3134
- };
3135
- waitForCmd((cmd) => cmd.variantRendered === true || cmd.emit === 'variantRendered', Coframe, timeoutMs)
3136
- .then((cmd) => {
3137
- if (!cmd) {
3138
- warnMissingVariant();
3139
- }
3140
- })
3141
- .finally(() => {
3142
- revealPage();
3143
- });
3144
- if (!projectId) {
3145
- logger.error('No project ID', 'no_project_id', {
3146
- description: 'No Coframe Page ID',
3147
- });
3148
- variantRendered();
3149
- return null;
3150
- }
3151
- Coframe.projectId = projectId;
3152
- const urlParams = getCurrentUrlSearchParams();
3153
- const previewVariantId = urlParams.get('coframe_variant_id');
3154
- const shouldSendEventsParam = urlParams.get('coframe_should_send_events');
3155
- setForceSendEvents(shouldSendEventsParam === 'true');
3156
- setIsPreviewVariant(!!previewVariantId);
3157
- setWaitingForCookies(!!config.waitForCookiesEnabled);
3158
- return {
3159
- config,
3160
- timeoutMs,
3161
- projectId,
3162
- previewVariantId,
3163
- integrationStartTime,
3164
- executionStartTime,
3165
- };
3166
- }
3167
-
3168
3223
  async function resolveIdentity(config, Coframe, timeoutMs, gracefulExit) {
3169
3224
  const logger = Coframe.logger;
3170
3225
  let userToken;
@@ -3221,14 +3276,14 @@ async function resolveIdentity(config, Coframe, timeoutMs, gracefulExit) {
3221
3276
  return { userToken, applyCookieConsent, onConsentChange };
3222
3277
  }
3223
3278
 
3224
- async function resolveProject(Coframe, previewVariantId, config) {
3279
+ async function resolveProject(Coframe, previewVariantId, isSessionVariant, config) {
3225
3280
  var _a;
3226
3281
  const logger = Coframe.logger;
3227
3282
  const projectData = (_a = findLastCmd((c) => !!c.projectData)) === null || _a === void 0 ? void 0 : _a.projectData;
3228
3283
  let personalizationHeaders = (projectData === null || projectData === void 0 ? void 0 : projectData.personalizationHeaders) || null;
3229
- let project = projectData === null || projectData === void 0 ? void 0 : projectData.project;
3284
+ let project = isSessionVariant ? undefined : projectData === null || projectData === void 0 ? void 0 : projectData.project;
3230
3285
  if (!project) {
3231
- const edgeResult = await fetchProjectFromEdge(Coframe.projectId, config.currentUrl || getCurrentPageUrl(), undefined, config.userContext);
3286
+ const edgeResult = await fetchProjectFromEdge(Coframe.projectId, config.currentUrl || getCurrentPageUrl(), undefined, config.userContext, previewVariantId || undefined);
3232
3287
  personalizationHeaders = (edgeResult === null || edgeResult === void 0 ? void 0 : edgeResult.personalizationHeaders) || null;
3233
3288
  project = (edgeResult === null || edgeResult === void 0 ? void 0 : edgeResult.project) || undefined;
3234
3289
  }
@@ -3320,7 +3375,7 @@ async function init(options) {
3320
3375
  const configResult = await resolveConfig(Coframe, revealPage);
3321
3376
  if (!configResult)
3322
3377
  return;
3323
- const projectResult = await resolveProject(Coframe, configResult.previewVariantId, configResult.config);
3378
+ const projectResult = await resolveProject(Coframe, configResult.previewVariantId, configResult.isSessionVariant, configResult.config);
3324
3379
  if (!projectResult)
3325
3380
  return;
3326
3381
  Coframe.orgId = projectResult.project.org_id || '';
@@ -22,7 +22,7 @@ function sanitizeUserContext(userContext) {
22
22
  return userContext;
23
23
  }
24
24
 
25
- async function fetchProjectFromEdge(projectId, currentUrl, signal, userContext, headers) {
25
+ async function fetchProjectFromEdge(projectId, currentUrl, signal, userContext, previewVariantId, headers) {
26
26
  const payload = { projectId, currentUrl };
27
27
  const safe = sanitizeUserContext(userContext);
28
28
  if (safe) {
@@ -73,7 +73,7 @@ async function getProjectData(options) {
73
73
  const includeCSS = options.includeCSS !== false;
74
74
  const [projectResult, antiflickerCSS] = await Promise.all([
75
75
  includeProject
76
- ? fetchProjectFromEdge(options.projectId, options.currentUrl, undefined, options.userContext, options.headers)
76
+ ? fetchProjectFromEdge(options.projectId, options.currentUrl, undefined, options.userContext, undefined, options.headers)
77
77
  : Promise.resolve(null),
78
78
  includeCSS
79
79
  ? fetchAntiflickerFromEdge(options.projectId, options.currentUrl, options.userContext, options.enableElementAntiFlicker, options.headers)
@@ -20,7 +20,7 @@ function sanitizeUserContext(userContext) {
20
20
  return userContext;
21
21
  }
22
22
 
23
- async function fetchProjectFromEdge(projectId, currentUrl, signal, userContext, headers) {
23
+ async function fetchProjectFromEdge(projectId, currentUrl, signal, userContext, previewVariantId, headers) {
24
24
  const payload = { projectId, currentUrl };
25
25
  const safe = sanitizeUserContext(userContext);
26
26
  if (safe) {
@@ -71,7 +71,7 @@ async function getProjectData(options) {
71
71
  const includeCSS = options.includeCSS !== false;
72
72
  const [projectResult, antiflickerCSS] = await Promise.all([
73
73
  includeProject
74
- ? fetchProjectFromEdge(options.projectId, options.currentUrl, undefined, options.userContext, options.headers)
74
+ ? fetchProjectFromEdge(options.projectId, options.currentUrl, undefined, options.userContext, undefined, options.headers)
75
75
  : Promise.resolve(null),
76
76
  includeCSS
77
77
  ? fetchAntiflickerFromEdge(options.projectId, options.currentUrl, options.userContext, options.enableElementAntiFlicker, options.headers)
@@ -232,6 +232,7 @@ type ActivationCondition = {
232
232
  };
233
233
  type CoframeConfig = {
234
234
  projectId: string;
235
+ previewVariantId?: string;
235
236
  trackingOnly?: boolean;
236
237
  enableAntiFlickering?: boolean;
237
238
  enableElementAntiFlicker?: boolean;
@@ -235,6 +235,7 @@ type ActivationCondition = {
235
235
  };
236
236
  type CoframeConfig = {
237
237
  projectId: string;
238
+ previewVariantId?: string;
238
239
  trackingOnly?: boolean;
239
240
  enableAntiFlickering?: boolean;
240
241
  enableElementAntiFlicker?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coframe/sdk",
3
- "version": "1.1.3",
3
+ "version": "1.1.4",
4
4
  "description": "Coframe optimizes your website using generative AI to deliver maximum impact to your audience and allow your site to improve itself as you sleep. Learn more at coframe.com.",
5
5
  "main": "dist/pkg/cf.cjs.js",
6
6
  "module": "dist/pkg/cf.esm.mjs",