@coframe/sdk 1.1.4 → 1.1.5

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.
@@ -2579,6 +2579,220 @@ function rebindMetrics(coframe, logger, metrics, nonce) {
2579
2579
  }
2580
2580
  }
2581
2581
 
2582
+ function getActivePreviewModes(sessionPreview) {
2583
+ if (!sessionPreview.isSessionPreviewActive ||
2584
+ !sessionPreview.previewVariantId) {
2585
+ return [];
2586
+ }
2587
+ return [
2588
+ {
2589
+ id: 'session-preview',
2590
+ label: 'Preview on',
2591
+ title: `Session preview is active for ${sessionPreview.previewVariantId}`,
2592
+ variantIds: sessionPreview.previewVariantId
2593
+ .split(',')
2594
+ .map((id) => id.trim())
2595
+ .filter(Boolean),
2596
+ },
2597
+ ];
2598
+ }
2599
+
2600
+ const PREVIEW_UI_PROTOCOL_VERSION = 1;
2601
+
2602
+ const SESSION_VARIANT_KEY = 'coframe.preview_variant';
2603
+ const PREVIEW_VARIANT_QUERY_PARAM = 'coframe_variant_id';
2604
+ const KEEP_PREVIEW_FOR_SESSION_QUERY_PARAM = 'keep_preview_for_session';
2605
+ let previewDismissed = false;
2606
+ function dismissSessionPreview() {
2607
+ previewDismissed = true;
2608
+ clearPreviewVariantId();
2609
+ const url = new URL(window.location.href);
2610
+ url.searchParams.delete(PREVIEW_VARIANT_QUERY_PARAM);
2611
+ url.searchParams.delete(KEEP_PREVIEW_FOR_SESSION_QUERY_PARAM);
2612
+ window.history.replaceState(window.history.state, '', url);
2613
+ window.location.reload();
2614
+ }
2615
+ function normalizeVariantIds(raw) {
2616
+ if (!raw)
2617
+ return null;
2618
+ const ids = raw
2619
+ .split(',')
2620
+ .map((id) => id.trim())
2621
+ .filter(Boolean);
2622
+ return ids.length > 0 ? ids.join(',') : null;
2623
+ }
2624
+ function readPreviewVariantId() {
2625
+ try {
2626
+ return sessionStorage.getItem(SESSION_VARIANT_KEY);
2627
+ }
2628
+ catch (_a) {
2629
+ return null;
2630
+ }
2631
+ }
2632
+ function persistPreviewVariantId(variantId) {
2633
+ const normalized = normalizeVariantIds(variantId);
2634
+ if (!normalized)
2635
+ return;
2636
+ try {
2637
+ sessionStorage.setItem(SESSION_VARIANT_KEY, normalized);
2638
+ }
2639
+ catch (_a) {
2640
+ }
2641
+ }
2642
+ function clearPreviewVariantId() {
2643
+ try {
2644
+ sessionStorage.removeItem(SESSION_VARIANT_KEY);
2645
+ }
2646
+ catch (_a) {
2647
+ }
2648
+ }
2649
+ function resolvePreviewSource(urlParams = getCurrentUrlSearchParams()) {
2650
+ return {
2651
+ urlVariantId: urlParams.get(PREVIEW_VARIANT_QUERY_PARAM),
2652
+ storedVariantId: readPreviewVariantId(),
2653
+ keepForSession: urlParams.get(KEEP_PREVIEW_FOR_SESSION_QUERY_PARAM) === 'true',
2654
+ };
2655
+ }
2656
+ function resolveSessionPreviewState() {
2657
+ if (previewDismissed) {
2658
+ return { isSessionPreviewActive: false, previewVariantId: null };
2659
+ }
2660
+ const previewSource = resolvePreviewSource();
2661
+ const urlVariantId = normalizeVariantIds(previewSource.urlVariantId);
2662
+ if (urlVariantId && previewSource.keepForSession) {
2663
+ return {
2664
+ isSessionPreviewActive: true,
2665
+ previewVariantId: urlVariantId,
2666
+ };
2667
+ }
2668
+ if (urlVariantId) {
2669
+ return { isSessionPreviewActive: false, previewVariantId: null };
2670
+ }
2671
+ const storedVariantId = normalizeVariantIds(previewSource.storedVariantId);
2672
+ return {
2673
+ isSessionPreviewActive: !!storedVariantId,
2674
+ previewVariantId: storedVariantId,
2675
+ };
2676
+ }
2677
+
2678
+ const PREVIEW_UI_SCRIPT_ID = 'coframe-preview-ui-script';
2679
+ const PREVIEW_UI_URL = "https://edge.cofra.me/preview-ui/latest.js";
2680
+ let loadPromise = null;
2681
+ let latestState = { modes: [] };
2682
+ let nonce;
2683
+ let logger;
2684
+ let mounted = false;
2685
+ function reportError(message, error) {
2686
+ const errorMessage = error instanceof Error ? error.message : String(error);
2687
+ if (logger) {
2688
+ logger.warn(message, 'preview_ui_unavailable', { error: errorMessage });
2689
+ return;
2690
+ }
2691
+ console.warn(`[Coframe] ${message}`, error);
2692
+ }
2693
+ function getPreviewUI() {
2694
+ const api = window.CoframePreviewUI;
2695
+ return (api === null || api === void 0 ? void 0 : api.protocolVersion) === PREVIEW_UI_PROTOCOL_VERSION ? api : undefined;
2696
+ }
2697
+ function loadPreviewUI() {
2698
+ const existing = getPreviewUI();
2699
+ if (existing)
2700
+ return Promise.resolve(existing);
2701
+ if (loadPromise)
2702
+ return loadPromise;
2703
+ loadPromise = new Promise((resolve, reject) => {
2704
+ const script = document.createElement('script');
2705
+ script.id = PREVIEW_UI_SCRIPT_ID;
2706
+ script.async = true;
2707
+ script.src = PREVIEW_UI_URL;
2708
+ if (nonce)
2709
+ script.nonce = nonce;
2710
+ script.onload = () => {
2711
+ const api = getPreviewUI();
2712
+ if (!api) {
2713
+ reject(new Error('Preview UI protocol is missing or incompatible'));
2714
+ return;
2715
+ }
2716
+ resolve(api);
2717
+ };
2718
+ script.onerror = () => {
2719
+ reject(new Error('Preview UI script failed to load'));
2720
+ };
2721
+ document.head.appendChild(script);
2722
+ });
2723
+ return loadPromise;
2724
+ }
2725
+ function renderPreviewUI(api) {
2726
+ try {
2727
+ if (latestState.modes.length === 0) {
2728
+ api.unmount();
2729
+ mounted = false;
2730
+ }
2731
+ else if (mounted) {
2732
+ api.update(latestState);
2733
+ }
2734
+ else {
2735
+ api.mount({ state: latestState, nonce, onDismiss: dismissPreviewPanel });
2736
+ mounted = true;
2737
+ }
2738
+ }
2739
+ catch (error) {
2740
+ reportError('Preview UI could not be rendered', error);
2741
+ }
2742
+ }
2743
+ function dismissPreviewPanel() {
2744
+ dismissSessionPreview();
2745
+ latestState = { modes: [] };
2746
+ const api = getPreviewUI();
2747
+ if (api)
2748
+ renderPreviewUI(api);
2749
+ }
2750
+ function modesWithVariants(sessionPreview, variantsAndElements, applied = true) {
2751
+ const activeModes = getActivePreviewModes(sessionPreview);
2752
+ if (activeModes.length === 0)
2753
+ return [];
2754
+ const currentPageVariantIds = new Set((variantsAndElements === null || variantsAndElements === void 0 ? void 0 : variantsAndElements.map(({ variant }) => variant.id)) || []);
2755
+ const slugById = new Map((variantsAndElements === null || variantsAndElements === void 0 ? void 0 : variantsAndElements.map(({ variant }) => [variant.id, variant.slug])) || []);
2756
+ return activeModes.map((mode) => ({
2757
+ id: mode.id,
2758
+ label: mode.label,
2759
+ title: mode.title,
2760
+ variants: mode.variantIds.map((id) => ({
2761
+ id,
2762
+ slug: slugById.get(id) || null,
2763
+ status: variantsAndElements
2764
+ ? applied && currentPageVariantIds.has(id)
2765
+ ? 'active'
2766
+ : 'inactive'
2767
+ : 'pending',
2768
+ })),
2769
+ }));
2770
+ }
2771
+ function resolveSessionPreviewPanel(sessionPreview, previewNonce, previewLogger) {
2772
+ const modes = modesWithVariants(sessionPreview);
2773
+ if (modes.length === 0)
2774
+ return;
2775
+ nonce = previewNonce;
2776
+ logger = previewLogger;
2777
+ latestState = { modes };
2778
+ const existing = getPreviewUI();
2779
+ if (existing) {
2780
+ renderPreviewUI(existing);
2781
+ return;
2782
+ }
2783
+ void loadPreviewUI()
2784
+ .then(renderPreviewUI)
2785
+ .catch((error) => reportError('Preview UI could not be loaded', error));
2786
+ }
2787
+ function updatePreviewPanel(variantsAndElements, sessionPreview, applied = true) {
2788
+ latestState = {
2789
+ modes: modesWithVariants(sessionPreview, variantsAndElements, applied),
2790
+ };
2791
+ const api = getPreviewUI();
2792
+ if (api)
2793
+ renderPreviewUI(api);
2794
+ }
2795
+
2582
2796
  let lastUrl = null;
2583
2797
  function observeUrlChanges(baseUrlMatch, onUrlChange) {
2584
2798
  const tryTrack = () => {
@@ -2605,125 +2819,6 @@ function observeUrlChanges(baseUrlMatch, onUrlChange) {
2605
2819
  patchHistoryMethod('replaceState');
2606
2820
  }
2607
2821
 
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
-
2727
2822
  function startSession(Coframe, cookiesEnabled, executionStartTime, personalizationHeaders) {
2728
2823
  const sessionPayload = [
2729
2824
  Coframe,
@@ -3182,6 +3277,8 @@ function createApplyVariants(Coframe, config, previewVariantId, isSessionVariant
3182
3277
  Coframe.experimentInfo = experimentsInfo;
3183
3278
  Coframe.variantsAndElements = variantsAndElements;
3184
3279
  setFrameActivated(hasActivatedFrames);
3280
+ const sessionPreview = resolveSessionPreviewState();
3281
+ updatePreviewPanel(variantsAndElements, sessionPreview, !config.trackingOnly);
3185
3282
  if (Coframe.debug) {
3186
3283
  Coframe.debug.project = pageProject;
3187
3284
  Coframe.debug.frames = pageProject.frames;
@@ -3201,6 +3298,11 @@ function createApplyVariants(Coframe, config, previewVariantId, isSessionVariant
3201
3298
  async function modifyPage(Coframe, configResult, projectResult, analytics) {
3202
3299
  const { config, timeoutMs, previewVariantId, isSessionVariant, integrationStartTime, executionStartTime, } = configResult;
3203
3300
  const { project: cdnProject, personalizationHeaders } = projectResult;
3301
+ if (resolveSessionPreviewState().isSessionPreviewActive) {
3302
+ waitForBody(timeoutMs)
3303
+ .then(() => resolveSessionPreviewPanel(resolveSessionPreviewState(), config.nonce, Coframe.logger))
3304
+ .catch(() => { });
3305
+ }
3204
3306
  if (!(await awaitHydration(config, Coframe, cdnProject)))
3205
3307
  return;
3206
3308
  if (cdnProject.tags) {
@@ -3222,6 +3324,105 @@ async function modifyPage(Coframe, configResult, projectResult, analytics) {
3222
3324
  setupFlushInterval(Coframe.projectId);
3223
3325
  }
3224
3326
 
3327
+ const TIMEOUT_MAX_MS = 5000;
3328
+ function getFeatureFlagValue(cmd) {
3329
+ if (cmd.featureFlagSet !== undefined)
3330
+ return cmd.featureFlagSet;
3331
+ if (typeof cmd.emit === 'object' &&
3332
+ cmd.emit !== null &&
3333
+ 'featureFlagSet' in cmd.emit) {
3334
+ return cmd.emit.featureFlagSet;
3335
+ }
3336
+ return undefined;
3337
+ }
3338
+ async function resolveFeatureFlag(Coframe, timeoutMs) {
3339
+ const cmd = await waitForCmd((cmd) => typeof getFeatureFlagValue(cmd) === 'boolean', Coframe, timeoutMs);
3340
+ if (!cmd)
3341
+ return undefined;
3342
+ return getFeatureFlagValue(cmd);
3343
+ }
3344
+ async function resolveConfig(Coframe, revealPage) {
3345
+ const logger = Coframe.logger;
3346
+ const configCmd = await waitForCmd((cmd) => !!cmd.config, Coframe);
3347
+ if (!configCmd) {
3348
+ logger.error('Failed to wait for config cmd', 'wait_for_cmd_timeout');
3349
+ return null;
3350
+ }
3351
+ const config = configCmd.config;
3352
+ const timeoutMs = (config === null || config === void 0 ? void 0 : config.timeoutMs)
3353
+ ? Math.min(config.timeoutMs, TIMEOUT_MAX_MS)
3354
+ : TIMEOUT_MAX_MS;
3355
+ const integrationStartTime = (config === null || config === void 0 ? void 0 : config.startTime)
3356
+ ? new Date(config.startTime)
3357
+ : null;
3358
+ const executionStartTime = new Date();
3359
+ Coframe.batchSize = config.batchSize || 32;
3360
+ const gracefulExit = (msg) => {
3361
+ console.warn(`[Coframe] ${msg}`);
3362
+ revealPage();
3363
+ return null;
3364
+ };
3365
+ if (config.waitForFeatureFlag) {
3366
+ const flag = await resolveFeatureFlag(Coframe, timeoutMs);
3367
+ if (flag === undefined) {
3368
+ return gracefulExit('Feature flag was not set after timeout, graceful exit');
3369
+ }
3370
+ if (flag === false) {
3371
+ return gracefulExit('Feature flag is set to false, graceful exit');
3372
+ }
3373
+ }
3374
+ const projectId = config === null || config === void 0 ? void 0 : config.projectId;
3375
+ const warnMissingVariant = () => {
3376
+ if (applyVariantChangesStarted) {
3377
+ logger.warn('Failed to wait for variant rendered command', 'missing_variant_rendered');
3378
+ }
3379
+ };
3380
+ waitForCmd((cmd) => cmd.variantRendered === true || cmd.emit === 'variantRendered', Coframe, timeoutMs)
3381
+ .then((cmd) => {
3382
+ if (!cmd) {
3383
+ warnMissingVariant();
3384
+ }
3385
+ })
3386
+ .finally(() => {
3387
+ revealPage();
3388
+ });
3389
+ if (!projectId) {
3390
+ logger.error('No project ID', 'no_project_id', {
3391
+ description: 'No Coframe Page ID',
3392
+ });
3393
+ variantRendered();
3394
+ return null;
3395
+ }
3396
+ Coframe.projectId = projectId;
3397
+ const urlParams = getCurrentUrlSearchParams();
3398
+ const previewSource = resolvePreviewSource(urlParams);
3399
+ const shouldSendEventsParam = urlParams.get('coframe_should_send_events');
3400
+ let previewVariantId = null;
3401
+ let isSessionVariant = false;
3402
+ if (previewSource.urlVariantId) {
3403
+ previewVariantId = previewSource.urlVariantId;
3404
+ if (previewSource.keepForSession) {
3405
+ persistPreviewVariantId(previewSource.urlVariantId);
3406
+ }
3407
+ }
3408
+ else if (previewSource.storedVariantId) {
3409
+ previewVariantId = previewSource.storedVariantId;
3410
+ isSessionVariant = true;
3411
+ }
3412
+ setForceSendEvents(shouldSendEventsParam === 'true');
3413
+ setIsPreviewVariant(!!previewVariantId);
3414
+ setWaitingForCookies(!!config.waitForCookiesEnabled);
3415
+ return {
3416
+ config,
3417
+ timeoutMs,
3418
+ projectId,
3419
+ previewVariantId,
3420
+ isSessionVariant,
3421
+ integrationStartTime,
3422
+ executionStartTime,
3423
+ };
3424
+ }
3425
+
3225
3426
  async function resolveIdentity(config, Coframe, timeoutMs, gracefulExit) {
3226
3427
  const logger = Coframe.logger;
3227
3428
  let userToken;
@@ -2577,6 +2577,220 @@ function rebindMetrics(coframe, logger, metrics, nonce) {
2577
2577
  }
2578
2578
  }
2579
2579
 
2580
+ function getActivePreviewModes(sessionPreview) {
2581
+ if (!sessionPreview.isSessionPreviewActive ||
2582
+ !sessionPreview.previewVariantId) {
2583
+ return [];
2584
+ }
2585
+ return [
2586
+ {
2587
+ id: 'session-preview',
2588
+ label: 'Preview on',
2589
+ title: `Session preview is active for ${sessionPreview.previewVariantId}`,
2590
+ variantIds: sessionPreview.previewVariantId
2591
+ .split(',')
2592
+ .map((id) => id.trim())
2593
+ .filter(Boolean),
2594
+ },
2595
+ ];
2596
+ }
2597
+
2598
+ const PREVIEW_UI_PROTOCOL_VERSION = 1;
2599
+
2600
+ const SESSION_VARIANT_KEY = 'coframe.preview_variant';
2601
+ const PREVIEW_VARIANT_QUERY_PARAM = 'coframe_variant_id';
2602
+ const KEEP_PREVIEW_FOR_SESSION_QUERY_PARAM = 'keep_preview_for_session';
2603
+ let previewDismissed = false;
2604
+ function dismissSessionPreview() {
2605
+ previewDismissed = true;
2606
+ clearPreviewVariantId();
2607
+ const url = new URL(window.location.href);
2608
+ url.searchParams.delete(PREVIEW_VARIANT_QUERY_PARAM);
2609
+ url.searchParams.delete(KEEP_PREVIEW_FOR_SESSION_QUERY_PARAM);
2610
+ window.history.replaceState(window.history.state, '', url);
2611
+ window.location.reload();
2612
+ }
2613
+ function normalizeVariantIds(raw) {
2614
+ if (!raw)
2615
+ return null;
2616
+ const ids = raw
2617
+ .split(',')
2618
+ .map((id) => id.trim())
2619
+ .filter(Boolean);
2620
+ return ids.length > 0 ? ids.join(',') : null;
2621
+ }
2622
+ function readPreviewVariantId() {
2623
+ try {
2624
+ return sessionStorage.getItem(SESSION_VARIANT_KEY);
2625
+ }
2626
+ catch (_a) {
2627
+ return null;
2628
+ }
2629
+ }
2630
+ function persistPreviewVariantId(variantId) {
2631
+ const normalized = normalizeVariantIds(variantId);
2632
+ if (!normalized)
2633
+ return;
2634
+ try {
2635
+ sessionStorage.setItem(SESSION_VARIANT_KEY, normalized);
2636
+ }
2637
+ catch (_a) {
2638
+ }
2639
+ }
2640
+ function clearPreviewVariantId() {
2641
+ try {
2642
+ sessionStorage.removeItem(SESSION_VARIANT_KEY);
2643
+ }
2644
+ catch (_a) {
2645
+ }
2646
+ }
2647
+ function resolvePreviewSource(urlParams = getCurrentUrlSearchParams()) {
2648
+ return {
2649
+ urlVariantId: urlParams.get(PREVIEW_VARIANT_QUERY_PARAM),
2650
+ storedVariantId: readPreviewVariantId(),
2651
+ keepForSession: urlParams.get(KEEP_PREVIEW_FOR_SESSION_QUERY_PARAM) === 'true',
2652
+ };
2653
+ }
2654
+ function resolveSessionPreviewState() {
2655
+ if (previewDismissed) {
2656
+ return { isSessionPreviewActive: false, previewVariantId: null };
2657
+ }
2658
+ const previewSource = resolvePreviewSource();
2659
+ const urlVariantId = normalizeVariantIds(previewSource.urlVariantId);
2660
+ if (urlVariantId && previewSource.keepForSession) {
2661
+ return {
2662
+ isSessionPreviewActive: true,
2663
+ previewVariantId: urlVariantId,
2664
+ };
2665
+ }
2666
+ if (urlVariantId) {
2667
+ return { isSessionPreviewActive: false, previewVariantId: null };
2668
+ }
2669
+ const storedVariantId = normalizeVariantIds(previewSource.storedVariantId);
2670
+ return {
2671
+ isSessionPreviewActive: !!storedVariantId,
2672
+ previewVariantId: storedVariantId,
2673
+ };
2674
+ }
2675
+
2676
+ const PREVIEW_UI_SCRIPT_ID = 'coframe-preview-ui-script';
2677
+ const PREVIEW_UI_URL = "https://edge.cofra.me/preview-ui/latest.js";
2678
+ let loadPromise = null;
2679
+ let latestState = { modes: [] };
2680
+ let nonce;
2681
+ let logger;
2682
+ let mounted = false;
2683
+ function reportError(message, error) {
2684
+ const errorMessage = error instanceof Error ? error.message : String(error);
2685
+ if (logger) {
2686
+ logger.warn(message, 'preview_ui_unavailable', { error: errorMessage });
2687
+ return;
2688
+ }
2689
+ console.warn(`[Coframe] ${message}`, error);
2690
+ }
2691
+ function getPreviewUI() {
2692
+ const api = window.CoframePreviewUI;
2693
+ return (api === null || api === void 0 ? void 0 : api.protocolVersion) === PREVIEW_UI_PROTOCOL_VERSION ? api : undefined;
2694
+ }
2695
+ function loadPreviewUI() {
2696
+ const existing = getPreviewUI();
2697
+ if (existing)
2698
+ return Promise.resolve(existing);
2699
+ if (loadPromise)
2700
+ return loadPromise;
2701
+ loadPromise = new Promise((resolve, reject) => {
2702
+ const script = document.createElement('script');
2703
+ script.id = PREVIEW_UI_SCRIPT_ID;
2704
+ script.async = true;
2705
+ script.src = PREVIEW_UI_URL;
2706
+ if (nonce)
2707
+ script.nonce = nonce;
2708
+ script.onload = () => {
2709
+ const api = getPreviewUI();
2710
+ if (!api) {
2711
+ reject(new Error('Preview UI protocol is missing or incompatible'));
2712
+ return;
2713
+ }
2714
+ resolve(api);
2715
+ };
2716
+ script.onerror = () => {
2717
+ reject(new Error('Preview UI script failed to load'));
2718
+ };
2719
+ document.head.appendChild(script);
2720
+ });
2721
+ return loadPromise;
2722
+ }
2723
+ function renderPreviewUI(api) {
2724
+ try {
2725
+ if (latestState.modes.length === 0) {
2726
+ api.unmount();
2727
+ mounted = false;
2728
+ }
2729
+ else if (mounted) {
2730
+ api.update(latestState);
2731
+ }
2732
+ else {
2733
+ api.mount({ state: latestState, nonce, onDismiss: dismissPreviewPanel });
2734
+ mounted = true;
2735
+ }
2736
+ }
2737
+ catch (error) {
2738
+ reportError('Preview UI could not be rendered', error);
2739
+ }
2740
+ }
2741
+ function dismissPreviewPanel() {
2742
+ dismissSessionPreview();
2743
+ latestState = { modes: [] };
2744
+ const api = getPreviewUI();
2745
+ if (api)
2746
+ renderPreviewUI(api);
2747
+ }
2748
+ function modesWithVariants(sessionPreview, variantsAndElements, applied = true) {
2749
+ const activeModes = getActivePreviewModes(sessionPreview);
2750
+ if (activeModes.length === 0)
2751
+ return [];
2752
+ const currentPageVariantIds = new Set((variantsAndElements === null || variantsAndElements === void 0 ? void 0 : variantsAndElements.map(({ variant }) => variant.id)) || []);
2753
+ const slugById = new Map((variantsAndElements === null || variantsAndElements === void 0 ? void 0 : variantsAndElements.map(({ variant }) => [variant.id, variant.slug])) || []);
2754
+ return activeModes.map((mode) => ({
2755
+ id: mode.id,
2756
+ label: mode.label,
2757
+ title: mode.title,
2758
+ variants: mode.variantIds.map((id) => ({
2759
+ id,
2760
+ slug: slugById.get(id) || null,
2761
+ status: variantsAndElements
2762
+ ? applied && currentPageVariantIds.has(id)
2763
+ ? 'active'
2764
+ : 'inactive'
2765
+ : 'pending',
2766
+ })),
2767
+ }));
2768
+ }
2769
+ function resolveSessionPreviewPanel(sessionPreview, previewNonce, previewLogger) {
2770
+ const modes = modesWithVariants(sessionPreview);
2771
+ if (modes.length === 0)
2772
+ return;
2773
+ nonce = previewNonce;
2774
+ logger = previewLogger;
2775
+ latestState = { modes };
2776
+ const existing = getPreviewUI();
2777
+ if (existing) {
2778
+ renderPreviewUI(existing);
2779
+ return;
2780
+ }
2781
+ void loadPreviewUI()
2782
+ .then(renderPreviewUI)
2783
+ .catch((error) => reportError('Preview UI could not be loaded', error));
2784
+ }
2785
+ function updatePreviewPanel(variantsAndElements, sessionPreview, applied = true) {
2786
+ latestState = {
2787
+ modes: modesWithVariants(sessionPreview, variantsAndElements, applied),
2788
+ };
2789
+ const api = getPreviewUI();
2790
+ if (api)
2791
+ renderPreviewUI(api);
2792
+ }
2793
+
2580
2794
  let lastUrl = null;
2581
2795
  function observeUrlChanges(baseUrlMatch, onUrlChange) {
2582
2796
  const tryTrack = () => {
@@ -2603,125 +2817,6 @@ function observeUrlChanges(baseUrlMatch, onUrlChange) {
2603
2817
  patchHistoryMethod('replaceState');
2604
2818
  }
2605
2819
 
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
-
2725
2820
  function startSession(Coframe, cookiesEnabled, executionStartTime, personalizationHeaders) {
2726
2821
  const sessionPayload = [
2727
2822
  Coframe,
@@ -3180,6 +3275,8 @@ function createApplyVariants(Coframe, config, previewVariantId, isSessionVariant
3180
3275
  Coframe.experimentInfo = experimentsInfo;
3181
3276
  Coframe.variantsAndElements = variantsAndElements;
3182
3277
  setFrameActivated(hasActivatedFrames);
3278
+ const sessionPreview = resolveSessionPreviewState();
3279
+ updatePreviewPanel(variantsAndElements, sessionPreview, !config.trackingOnly);
3183
3280
  if (Coframe.debug) {
3184
3281
  Coframe.debug.project = pageProject;
3185
3282
  Coframe.debug.frames = pageProject.frames;
@@ -3199,6 +3296,11 @@ function createApplyVariants(Coframe, config, previewVariantId, isSessionVariant
3199
3296
  async function modifyPage(Coframe, configResult, projectResult, analytics) {
3200
3297
  const { config, timeoutMs, previewVariantId, isSessionVariant, integrationStartTime, executionStartTime, } = configResult;
3201
3298
  const { project: cdnProject, personalizationHeaders } = projectResult;
3299
+ if (resolveSessionPreviewState().isSessionPreviewActive) {
3300
+ waitForBody(timeoutMs)
3301
+ .then(() => resolveSessionPreviewPanel(resolveSessionPreviewState(), config.nonce, Coframe.logger))
3302
+ .catch(() => { });
3303
+ }
3202
3304
  if (!(await awaitHydration(config, Coframe, cdnProject)))
3203
3305
  return;
3204
3306
  if (cdnProject.tags) {
@@ -3220,6 +3322,105 @@ async function modifyPage(Coframe, configResult, projectResult, analytics) {
3220
3322
  setupFlushInterval(Coframe.projectId);
3221
3323
  }
3222
3324
 
3325
+ const TIMEOUT_MAX_MS = 5000;
3326
+ function getFeatureFlagValue(cmd) {
3327
+ if (cmd.featureFlagSet !== undefined)
3328
+ return cmd.featureFlagSet;
3329
+ if (typeof cmd.emit === 'object' &&
3330
+ cmd.emit !== null &&
3331
+ 'featureFlagSet' in cmd.emit) {
3332
+ return cmd.emit.featureFlagSet;
3333
+ }
3334
+ return undefined;
3335
+ }
3336
+ async function resolveFeatureFlag(Coframe, timeoutMs) {
3337
+ const cmd = await waitForCmd((cmd) => typeof getFeatureFlagValue(cmd) === 'boolean', Coframe, timeoutMs);
3338
+ if (!cmd)
3339
+ return undefined;
3340
+ return getFeatureFlagValue(cmd);
3341
+ }
3342
+ async function resolveConfig(Coframe, revealPage) {
3343
+ const logger = Coframe.logger;
3344
+ const configCmd = await waitForCmd((cmd) => !!cmd.config, Coframe);
3345
+ if (!configCmd) {
3346
+ logger.error('Failed to wait for config cmd', 'wait_for_cmd_timeout');
3347
+ return null;
3348
+ }
3349
+ const config = configCmd.config;
3350
+ const timeoutMs = (config === null || config === void 0 ? void 0 : config.timeoutMs)
3351
+ ? Math.min(config.timeoutMs, TIMEOUT_MAX_MS)
3352
+ : TIMEOUT_MAX_MS;
3353
+ const integrationStartTime = (config === null || config === void 0 ? void 0 : config.startTime)
3354
+ ? new Date(config.startTime)
3355
+ : null;
3356
+ const executionStartTime = new Date();
3357
+ Coframe.batchSize = config.batchSize || 32;
3358
+ const gracefulExit = (msg) => {
3359
+ console.warn(`[Coframe] ${msg}`);
3360
+ revealPage();
3361
+ return null;
3362
+ };
3363
+ if (config.waitForFeatureFlag) {
3364
+ const flag = await resolveFeatureFlag(Coframe, timeoutMs);
3365
+ if (flag === undefined) {
3366
+ return gracefulExit('Feature flag was not set after timeout, graceful exit');
3367
+ }
3368
+ if (flag === false) {
3369
+ return gracefulExit('Feature flag is set to false, graceful exit');
3370
+ }
3371
+ }
3372
+ const projectId = config === null || config === void 0 ? void 0 : config.projectId;
3373
+ const warnMissingVariant = () => {
3374
+ if (applyVariantChangesStarted) {
3375
+ logger.warn('Failed to wait for variant rendered command', 'missing_variant_rendered');
3376
+ }
3377
+ };
3378
+ waitForCmd((cmd) => cmd.variantRendered === true || cmd.emit === 'variantRendered', Coframe, timeoutMs)
3379
+ .then((cmd) => {
3380
+ if (!cmd) {
3381
+ warnMissingVariant();
3382
+ }
3383
+ })
3384
+ .finally(() => {
3385
+ revealPage();
3386
+ });
3387
+ if (!projectId) {
3388
+ logger.error('No project ID', 'no_project_id', {
3389
+ description: 'No Coframe Page ID',
3390
+ });
3391
+ variantRendered();
3392
+ return null;
3393
+ }
3394
+ Coframe.projectId = projectId;
3395
+ const urlParams = getCurrentUrlSearchParams();
3396
+ const previewSource = resolvePreviewSource(urlParams);
3397
+ const shouldSendEventsParam = urlParams.get('coframe_should_send_events');
3398
+ let previewVariantId = null;
3399
+ let isSessionVariant = false;
3400
+ if (previewSource.urlVariantId) {
3401
+ previewVariantId = previewSource.urlVariantId;
3402
+ if (previewSource.keepForSession) {
3403
+ persistPreviewVariantId(previewSource.urlVariantId);
3404
+ }
3405
+ }
3406
+ else if (previewSource.storedVariantId) {
3407
+ previewVariantId = previewSource.storedVariantId;
3408
+ isSessionVariant = true;
3409
+ }
3410
+ setForceSendEvents(shouldSendEventsParam === 'true');
3411
+ setIsPreviewVariant(!!previewVariantId);
3412
+ setWaitingForCookies(!!config.waitForCookiesEnabled);
3413
+ return {
3414
+ config,
3415
+ timeoutMs,
3416
+ projectId,
3417
+ previewVariantId,
3418
+ isSessionVariant,
3419
+ integrationStartTime,
3420
+ executionStartTime,
3421
+ };
3422
+ }
3423
+
3223
3424
  async function resolveIdentity(config, Coframe, timeoutMs, gracefulExit) {
3224
3425
  const logger = Coframe.logger;
3225
3426
  let userToken;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coframe/sdk",
3
- "version": "1.1.4",
3
+ "version": "1.1.5",
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",