@coframe/sdk 1.1.10 → 1.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -47,9 +47,12 @@ installations:
47
47
 
48
48
  - `cf_disable` skips initialization and reveals any antiflicker state.
49
49
  - `coframe_variant_id` previews one or more comma-separated variant IDs.
50
- - `keep_preview_for_session=true` keeps that preview active for the tab.
51
50
  - `coframe_should_send_events=true` allows events during preview for testing.
52
51
 
52
+ When `enableDebug: true` is set for the project, open the Launcher with
53
+ `cf_debug=true` and use **Keep across navigation** in the Preview Session
54
+ tool to keep the selected variants active for the current tab.
55
+
53
56
  For SSR, pass the complete request URL, including its query string, as
54
57
  `currentUrl`. This is required for query-sensitive targeting and allows
55
58
  `getProjectData()` to short-circuit before any request when `cf_disable` is
@@ -859,6 +859,88 @@ const isSegmentEmpty = (segment) => {
859
859
  return segment.xpath === '';
860
860
  };
861
861
 
862
+ function parseQueryString(query) {
863
+ const params = {};
864
+ const cleanQuery = query.startsWith('?') ? query.slice(1) : query;
865
+ if (!cleanQuery)
866
+ return params;
867
+ for (const pair of cleanQuery.split('&')) {
868
+ if (!pair)
869
+ continue;
870
+ const [key, value = ''] = pair.split('=').map(decodeURIComponent);
871
+ if (!(key in params)) {
872
+ params[key] = value;
873
+ }
874
+ }
875
+ return params;
876
+ }
877
+ function createURLSearchParams(query) {
878
+ if (typeof URLSearchParams !== 'undefined') {
879
+ const nativeParams = new URLSearchParams(query);
880
+ return {
881
+ get: (key) => nativeParams.get(key),
882
+ };
883
+ }
884
+ const simpleParams = parseQueryString(query);
885
+ return {
886
+ get: (key) => { var _a; return (_a = simpleParams[key]) !== null && _a !== void 0 ? _a : null; },
887
+ };
888
+ }
889
+ function getUrlSearchParams(url) {
890
+ const searchIndex = url.indexOf('?');
891
+ const firstHashIndex = url.indexOf('#');
892
+ if (searchIndex === -1 ||
893
+ (firstHashIndex !== -1 && firstHashIndex < searchIndex)) {
894
+ return createURLSearchParams('');
895
+ }
896
+ const hashIndex = url.indexOf('#', searchIndex);
897
+ const search = hashIndex !== -1
898
+ ? url.substring(searchIndex, hashIndex)
899
+ : url.substring(searchIndex);
900
+ return createURLSearchParams(search);
901
+ }
902
+
903
+ const COFRAME_DEBUG_QUERY_PARAM = 'cf_debug';
904
+ const COFRAME_TOOL_INTERACTION_ATTRIBUTE = 'data-cf-t';
905
+ const COFRAME_DEBUG_TOOLS_SESSION_STORAGE_KEY = 'cf.tools.enabled';
906
+ const COFRAME_DEBUG_TOOLS_STATE_STORAGE_KEY = 'cf.tools.session';
907
+ const ENABLED_VALUE = 'true';
908
+ function enableDebugToolsSession() {
909
+ try {
910
+ window.sessionStorage.setItem(COFRAME_DEBUG_TOOLS_SESSION_STORAGE_KEY, ENABLED_VALUE);
911
+ }
912
+ catch (_a) {
913
+ }
914
+ }
915
+ function clearDebugToolsSession() {
916
+ try {
917
+ window.sessionStorage.removeItem(COFRAME_DEBUG_TOOLS_SESSION_STORAGE_KEY);
918
+ window.sessionStorage.removeItem(COFRAME_DEBUG_TOOLS_STATE_STORAGE_KEY);
919
+ }
920
+ catch (_a) {
921
+ }
922
+ }
923
+ function isDebugToolsSessionEnabled(allowed) {
924
+ if (!allowed) {
925
+ clearDebugToolsSession();
926
+ return false;
927
+ }
928
+ const enabledByUrl = getUrlSearchParams(getCurrentPageUrl()).get(COFRAME_DEBUG_QUERY_PARAM) ===
929
+ ENABLED_VALUE;
930
+ if (enabledByUrl) {
931
+ enableDebugToolsSession();
932
+ return true;
933
+ }
934
+ try {
935
+ const enabled = window.sessionStorage.getItem(COFRAME_DEBUG_TOOLS_SESSION_STORAGE_KEY) ===
936
+ ENABLED_VALUE;
937
+ return enabled;
938
+ }
939
+ catch (_a) {
940
+ return false;
941
+ }
942
+ }
943
+
862
944
  const USER_CONTEXT_KEY_PREFIX = 'x-ctx-';
863
945
  const MAX_USER_CONTEXT_JSON_BYTES = 4096;
864
946
  function userContextJsonByteLength(userContext) {
@@ -1037,6 +1119,12 @@ function queueVariantViewEvent(coframe, { projectId, sessionId, orgId, userToken
1037
1119
  }
1038
1120
  function sendPageEngagementEvent(coframe, eventType, event, { projectId, sessionId, duration = 0, userToken, personalizationHeaders, }) {
1039
1121
  var _a;
1122
+ if (event
1123
+ .composedPath()
1124
+ .some((target) => target instanceof Element &&
1125
+ target.hasAttribute(COFRAME_TOOL_INTERACTION_ATTRIBUTE))) {
1126
+ return;
1127
+ }
1040
1128
  const startTime = performance.now();
1041
1129
  const x = event.clientX + window.scrollX;
1042
1130
  const y = event.clientY + window.scrollY;
@@ -2216,44 +2304,8 @@ function debounce(func, wait) {
2216
2304
  }, wait);
2217
2305
  };
2218
2306
  }
2219
- function parseQueryString(query) {
2220
- const params = {};
2221
- const cleanQuery = query.startsWith('?') ? query.slice(1) : query;
2222
- if (!cleanQuery)
2223
- return params;
2224
- for (const pair of cleanQuery.split('&')) {
2225
- if (!pair)
2226
- continue;
2227
- const [key, value = ''] = pair.split('=').map(decodeURIComponent);
2228
- if (!(key in params)) {
2229
- params[key] = value;
2230
- }
2231
- }
2232
- return params;
2233
- }
2234
- function createURLSearchParams(query) {
2235
- if (typeof URLSearchParams !== 'undefined') {
2236
- const nativeParams = new URLSearchParams(query);
2237
- return {
2238
- get: (key) => nativeParams.get(key),
2239
- };
2240
- }
2241
- const simpleParams = parseQueryString(query);
2242
- return {
2243
- get: (key) => { var _a; return (_a = simpleParams[key]) !== null && _a !== void 0 ? _a : null; },
2244
- };
2245
- }
2246
2307
  function getCurrentUrlSearchParams() {
2247
- const url = getCurrentPageUrl();
2248
- const searchIndex = url.indexOf('?');
2249
- if (searchIndex === -1) {
2250
- return createURLSearchParams('');
2251
- }
2252
- const hashIndex = url.indexOf('#', searchIndex);
2253
- const search = hashIndex !== -1
2254
- ? url.substring(searchIndex, hashIndex)
2255
- : url.substring(searchIndex);
2256
- return createURLSearchParams(search);
2308
+ return getUrlSearchParams(getCurrentPageUrl());
2257
2309
  }
2258
2310
 
2259
2311
  const defaultScale = 100000;
@@ -2488,6 +2540,44 @@ async function getVariantsAndElements(personalizationHeaders, project, previewVa
2488
2540
  });
2489
2541
  }
2490
2542
 
2543
+ const COFRAME_PREVIEW_SESSION_STORAGE_KEY = 'coframe.preview_variant';
2544
+ const COFRAME_PREVIEW_VARIANT_QUERY_PARAM = 'coframe_variant_id';
2545
+ function normalizePreviewVariantId(raw) {
2546
+ if (!raw)
2547
+ return null;
2548
+ const ids = raw
2549
+ .split(',')
2550
+ .map((id) => id.trim())
2551
+ .filter(Boolean);
2552
+ return ids.length > 0 ? ids.join(',') : null;
2553
+ }
2554
+ function readPreviewVariantIdFromUrl(url = window.location.href) {
2555
+ try {
2556
+ return normalizePreviewVariantId(getUrlSearchParams(url).get(COFRAME_PREVIEW_VARIANT_QUERY_PARAM));
2557
+ }
2558
+ catch (_a) {
2559
+ return null;
2560
+ }
2561
+ }
2562
+ function readStoredPreviewVariantId() {
2563
+ try {
2564
+ return normalizePreviewVariantId(window.sessionStorage.getItem(COFRAME_PREVIEW_SESSION_STORAGE_KEY));
2565
+ }
2566
+ catch (_a) {
2567
+ return null;
2568
+ }
2569
+ }
2570
+ function resolvePreviewSource(url = window.location.href) {
2571
+ const urlVariantId = readPreviewVariantIdFromUrl(url);
2572
+ const storedVariantId = readStoredPreviewVariantId();
2573
+ return {
2574
+ effectiveVariantId: urlVariantId || storedVariantId,
2575
+ isKeptForSession: Boolean(storedVariantId && storedVariantId === (urlVariantId || storedVariantId)),
2576
+ storedVariantId,
2577
+ urlVariantId,
2578
+ };
2579
+ }
2580
+
2491
2581
  function buildSessionContext(isInitialApply) {
2492
2582
  const context = {};
2493
2583
  const viewport = getScreenSize();
@@ -2641,220 +2731,6 @@ function rebindMetrics(coframe, logger, metrics, nonce) {
2641
2731
  }
2642
2732
  }
2643
2733
 
2644
- function getActivePreviewModes(sessionPreview) {
2645
- if (!sessionPreview.isSessionPreviewActive ||
2646
- !sessionPreview.previewVariantId) {
2647
- return [];
2648
- }
2649
- return [
2650
- {
2651
- id: 'session-preview',
2652
- label: 'Preview on',
2653
- title: `Session preview is active for ${sessionPreview.previewVariantId}`,
2654
- variantIds: sessionPreview.previewVariantId
2655
- .split(',')
2656
- .map((id) => id.trim())
2657
- .filter(Boolean),
2658
- },
2659
- ];
2660
- }
2661
-
2662
- const PREVIEW_UI_PROTOCOL_VERSION = 1;
2663
-
2664
- const SESSION_VARIANT_KEY = 'coframe.preview_variant';
2665
- const PREVIEW_VARIANT_QUERY_PARAM = 'coframe_variant_id';
2666
- const KEEP_PREVIEW_FOR_SESSION_QUERY_PARAM = 'keep_preview_for_session';
2667
- let previewDismissed = false;
2668
- function dismissSessionPreview() {
2669
- previewDismissed = true;
2670
- clearPreviewVariantId();
2671
- const url = new URL(window.location.href);
2672
- url.searchParams.delete(PREVIEW_VARIANT_QUERY_PARAM);
2673
- url.searchParams.delete(KEEP_PREVIEW_FOR_SESSION_QUERY_PARAM);
2674
- window.history.replaceState(window.history.state, '', url);
2675
- window.location.reload();
2676
- }
2677
- function normalizeVariantIds(raw) {
2678
- if (!raw)
2679
- return null;
2680
- const ids = raw
2681
- .split(',')
2682
- .map((id) => id.trim())
2683
- .filter(Boolean);
2684
- return ids.length > 0 ? ids.join(',') : null;
2685
- }
2686
- function readPreviewVariantId() {
2687
- try {
2688
- return sessionStorage.getItem(SESSION_VARIANT_KEY);
2689
- }
2690
- catch (_a) {
2691
- return null;
2692
- }
2693
- }
2694
- function persistPreviewVariantId(variantId) {
2695
- const normalized = normalizeVariantIds(variantId);
2696
- if (!normalized)
2697
- return;
2698
- try {
2699
- sessionStorage.setItem(SESSION_VARIANT_KEY, normalized);
2700
- }
2701
- catch (_a) {
2702
- }
2703
- }
2704
- function clearPreviewVariantId() {
2705
- try {
2706
- sessionStorage.removeItem(SESSION_VARIANT_KEY);
2707
- }
2708
- catch (_a) {
2709
- }
2710
- }
2711
- function resolvePreviewSource(urlParams = getCurrentUrlSearchParams()) {
2712
- return {
2713
- urlVariantId: urlParams.get(PREVIEW_VARIANT_QUERY_PARAM),
2714
- storedVariantId: readPreviewVariantId(),
2715
- keepForSession: urlParams.get(KEEP_PREVIEW_FOR_SESSION_QUERY_PARAM) === 'true',
2716
- };
2717
- }
2718
- function resolveSessionPreviewState() {
2719
- if (previewDismissed) {
2720
- return { isSessionPreviewActive: false, previewVariantId: null };
2721
- }
2722
- const previewSource = resolvePreviewSource();
2723
- const urlVariantId = normalizeVariantIds(previewSource.urlVariantId);
2724
- if (urlVariantId && previewSource.keepForSession) {
2725
- return {
2726
- isSessionPreviewActive: true,
2727
- previewVariantId: urlVariantId,
2728
- };
2729
- }
2730
- if (urlVariantId) {
2731
- return { isSessionPreviewActive: false, previewVariantId: null };
2732
- }
2733
- const storedVariantId = normalizeVariantIds(previewSource.storedVariantId);
2734
- return {
2735
- isSessionPreviewActive: !!storedVariantId,
2736
- previewVariantId: storedVariantId,
2737
- };
2738
- }
2739
-
2740
- const PREVIEW_UI_SCRIPT_ID = 'coframe-preview-ui-script';
2741
- const PREVIEW_UI_URL = "https://edge.cofra.me/preview-ui/latest.js";
2742
- let loadPromise = null;
2743
- let latestState = { modes: [] };
2744
- let nonce;
2745
- let logger;
2746
- let mounted = false;
2747
- function reportError(message, error) {
2748
- const errorMessage = error instanceof Error ? error.message : String(error);
2749
- if (logger) {
2750
- logger.warn(message, 'preview_ui_unavailable', { error: errorMessage });
2751
- return;
2752
- }
2753
- console.warn(`[Coframe] ${message}`, error);
2754
- }
2755
- function getPreviewUI() {
2756
- const api = window.CoframePreviewUI;
2757
- return (api === null || api === void 0 ? void 0 : api.protocolVersion) === PREVIEW_UI_PROTOCOL_VERSION ? api : undefined;
2758
- }
2759
- function loadPreviewUI() {
2760
- const existing = getPreviewUI();
2761
- if (existing)
2762
- return Promise.resolve(existing);
2763
- if (loadPromise)
2764
- return loadPromise;
2765
- loadPromise = new Promise((resolve, reject) => {
2766
- const script = document.createElement('script');
2767
- script.id = PREVIEW_UI_SCRIPT_ID;
2768
- script.async = true;
2769
- script.src = PREVIEW_UI_URL;
2770
- if (nonce)
2771
- script.nonce = nonce;
2772
- script.onload = () => {
2773
- const api = getPreviewUI();
2774
- if (!api) {
2775
- reject(new Error('Preview UI protocol is missing or incompatible'));
2776
- return;
2777
- }
2778
- resolve(api);
2779
- };
2780
- script.onerror = () => {
2781
- reject(new Error('Preview UI script failed to load'));
2782
- };
2783
- document.head.appendChild(script);
2784
- });
2785
- return loadPromise;
2786
- }
2787
- function renderPreviewUI(api) {
2788
- try {
2789
- if (latestState.modes.length === 0) {
2790
- api.unmount();
2791
- mounted = false;
2792
- }
2793
- else if (mounted) {
2794
- api.update(latestState);
2795
- }
2796
- else {
2797
- api.mount({ state: latestState, nonce, onDismiss: dismissPreviewPanel });
2798
- mounted = true;
2799
- }
2800
- }
2801
- catch (error) {
2802
- reportError('Preview UI could not be rendered', error);
2803
- }
2804
- }
2805
- function dismissPreviewPanel() {
2806
- dismissSessionPreview();
2807
- latestState = { modes: [] };
2808
- const api = getPreviewUI();
2809
- if (api)
2810
- renderPreviewUI(api);
2811
- }
2812
- function modesWithVariants(sessionPreview, variantsAndElements, applied = true) {
2813
- const activeModes = getActivePreviewModes(sessionPreview);
2814
- if (activeModes.length === 0)
2815
- return [];
2816
- const currentPageVariantIds = new Set((variantsAndElements === null || variantsAndElements === void 0 ? void 0 : variantsAndElements.map(({ variant }) => variant.id)) || []);
2817
- const slugById = new Map((variantsAndElements === null || variantsAndElements === void 0 ? void 0 : variantsAndElements.map(({ variant }) => [variant.id, variant.slug])) || []);
2818
- return activeModes.map((mode) => ({
2819
- id: mode.id,
2820
- label: mode.label,
2821
- title: mode.title,
2822
- variants: mode.variantIds.map((id) => ({
2823
- id,
2824
- slug: slugById.get(id) || null,
2825
- status: variantsAndElements
2826
- ? applied && currentPageVariantIds.has(id)
2827
- ? 'active'
2828
- : 'inactive'
2829
- : 'pending',
2830
- })),
2831
- }));
2832
- }
2833
- function resolveSessionPreviewPanel(sessionPreview, previewNonce, previewLogger) {
2834
- const modes = modesWithVariants(sessionPreview);
2835
- if (modes.length === 0)
2836
- return;
2837
- nonce = previewNonce;
2838
- logger = previewLogger;
2839
- latestState = { modes };
2840
- const existing = getPreviewUI();
2841
- if (existing) {
2842
- renderPreviewUI(existing);
2843
- return;
2844
- }
2845
- void loadPreviewUI()
2846
- .then(renderPreviewUI)
2847
- .catch((error) => reportError('Preview UI could not be loaded', error));
2848
- }
2849
- function updatePreviewPanel(variantsAndElements, sessionPreview, applied = true) {
2850
- latestState = {
2851
- modes: modesWithVariants(sessionPreview, variantsAndElements, applied),
2852
- };
2853
- const api = getPreviewUI();
2854
- if (api)
2855
- renderPreviewUI(api);
2856
- }
2857
-
2858
2734
  let lastUrl = null;
2859
2735
  function observeUrlChanges(baseUrlMatch, onUrlChange) {
2860
2736
  const tryTrack = () => {
@@ -3209,21 +3085,23 @@ function setupTracking(Coframe, projectResult, previewVariantId, executionStartT
3209
3085
  }
3210
3086
 
3211
3087
  function resolvePreview(pageUrl, previewVariantId, isSessionVariant) {
3212
- let urlPreviewVariantId = null;
3213
- try {
3214
- urlPreviewVariantId = new URL(pageUrl).searchParams.get('coframe_variant_id');
3215
- }
3216
- catch (_a) {
3217
- urlPreviewVariantId = getCurrentUrlSearchParams().get('coframe_variant_id');
3088
+ const previewSource = resolvePreviewSource(pageUrl);
3089
+ if (previewSource.urlVariantId) {
3090
+ return {
3091
+ previewVariantId: previewSource.urlVariantId,
3092
+ isSessionVariant: previewSource.isKeptForSession,
3093
+ };
3218
3094
  }
3219
- if (urlPreviewVariantId) {
3220
- return { previewVariantId: urlPreviewVariantId, isSessionVariant: false };
3095
+ if (previewSource.storedVariantId) {
3096
+ return {
3097
+ previewVariantId: previewSource.storedVariantId,
3098
+ isSessionVariant: true,
3099
+ };
3221
3100
  }
3222
3101
  if (previewVariantId) {
3223
3102
  return { previewVariantId, isSessionVariant };
3224
3103
  }
3225
- const stored = readPreviewVariantId();
3226
- return { previewVariantId: stored, isSessionVariant: !!stored };
3104
+ return { previewVariantId: null, isSessionVariant: false };
3227
3105
  }
3228
3106
  async function awaitHydration(config, Coframe, project) {
3229
3107
  var _a, _b;
@@ -3356,8 +3234,6 @@ function createApplyVariants(Coframe, config, previewVariantId, isSessionVariant
3356
3234
  Coframe.experimentInfo = experimentsInfo;
3357
3235
  Coframe.variantsAndElements = variantsAndElements;
3358
3236
  setFrameActivated(hasActivatedFrames);
3359
- const sessionPreview = resolveSessionPreviewState();
3360
- updatePreviewPanel(variantsAndElements, sessionPreview, !config.trackingOnly);
3361
3237
  if (Coframe.debug) {
3362
3238
  Coframe.debug.project = pageProject;
3363
3239
  Coframe.debug.frames = pageProject.frames;
@@ -3377,11 +3253,6 @@ function createApplyVariants(Coframe, config, previewVariantId, isSessionVariant
3377
3253
  async function modifyPage(Coframe, configResult, projectResult, analytics) {
3378
3254
  const { config, timeoutMs, previewVariantId, isSessionVariant, integrationStartTime, executionStartTime, } = configResult;
3379
3255
  const { project: cdnProject, personalizationHeaders } = projectResult;
3380
- if (resolveSessionPreviewState().isSessionPreviewActive) {
3381
- waitForBody(timeoutMs)
3382
- .then(() => resolveSessionPreviewPanel(resolveSessionPreviewState(), config.nonce, Coframe.logger))
3383
- .catch(() => { });
3384
- }
3385
3256
  if (!(await awaitHydration(config, Coframe, cdnProject)))
3386
3257
  return;
3387
3258
  if (cdnProject.tags) {
@@ -3474,15 +3345,13 @@ async function resolveConfig(Coframe, revealPage) {
3474
3345
  }
3475
3346
  Coframe.projectId = projectId;
3476
3347
  const urlParams = getCurrentUrlSearchParams();
3477
- const previewSource = resolvePreviewSource(urlParams);
3348
+ const previewSource = resolvePreviewSource();
3478
3349
  const shouldSendEventsParam = urlParams.get('coframe_should_send_events');
3479
3350
  let previewVariantId = null;
3480
3351
  let isSessionVariant = false;
3481
3352
  if (previewSource.urlVariantId) {
3482
3353
  previewVariantId = previewSource.urlVariantId;
3483
- if (previewSource.keepForSession) {
3484
- persistPreviewVariantId(previewSource.urlVariantId);
3485
- }
3354
+ isSessionVariant = previewSource.isKeptForSession;
3486
3355
  }
3487
3356
  else if (previewSource.storedVariantId) {
3488
3357
  previewVariantId = previewSource.storedVariantId;
@@ -3582,6 +3451,66 @@ async function resolveProject(Coframe, previewVariantId, isSessionVariant, confi
3582
3451
  };
3583
3452
  }
3584
3453
 
3454
+ const COFRAME_LAUNCHER_PROTOCOL_VERSION = 1;
3455
+
3456
+ const LAUNCHER_SCRIPT_ID = 'coframe-tools-launcher-script';
3457
+ const LAUNCHER_URL = "https://edge.cofra.me/launcher/latest.js";
3458
+ let loading = false;
3459
+ function getLauncher() {
3460
+ const launcher = window.CoframeLauncher;
3461
+ return (launcher === null || launcher === void 0 ? void 0 : launcher.protocolVersion) === COFRAME_LAUNCHER_PROTOCOL_VERSION
3462
+ ? launcher
3463
+ : undefined;
3464
+ }
3465
+ function exitDebugSession() {
3466
+ clearDebugToolsSession();
3467
+ const url = new URL(window.location.href);
3468
+ url.searchParams.delete(COFRAME_DEBUG_QUERY_PARAM);
3469
+ window.history.replaceState(window.history.state, '', url);
3470
+ }
3471
+ function mountLauncher(nonce) {
3472
+ const launcher = getLauncher();
3473
+ if (!launcher)
3474
+ return false;
3475
+ launcher.mount({ nonce, onExitDebugSession: exitDebugSession });
3476
+ return true;
3477
+ }
3478
+ function loadLauncherIfEnabled(enabled, nonce) {
3479
+ if (typeof window === 'undefined')
3480
+ return;
3481
+ let sessionEnabled = false;
3482
+ try {
3483
+ sessionEnabled = isDebugToolsSessionEnabled(enabled);
3484
+ }
3485
+ catch (error) {
3486
+ console.warn('[Coframe] Unable to determine Debug Tools session', error);
3487
+ return;
3488
+ }
3489
+ if (!sessionEnabled || mountLauncher(nonce) || loading) {
3490
+ return;
3491
+ }
3492
+ loading = true;
3493
+ const script = document.createElement('script');
3494
+ script.id = LAUNCHER_SCRIPT_ID;
3495
+ script.async = true;
3496
+ script.src = LAUNCHER_URL;
3497
+ if (nonce)
3498
+ script.nonce = nonce;
3499
+ script.onload = () => {
3500
+ loading = false;
3501
+ if (!mountLauncher(nonce)) {
3502
+ document.head.removeChild(script);
3503
+ console.warn('[Coframe] Launcher protocol is missing or incompatible');
3504
+ }
3505
+ };
3506
+ script.onerror = () => {
3507
+ loading = false;
3508
+ document.head.removeChild(script);
3509
+ console.warn('[Coframe] Launcher script failed to load');
3510
+ };
3511
+ document.head.appendChild(script);
3512
+ }
3513
+
3585
3514
  function injectOptions(options) {
3586
3515
  var _a;
3587
3516
  if (options.config) {
@@ -3663,6 +3592,7 @@ async function init(options) {
3663
3592
  const configResult = await resolveConfig(Coframe, revealPage);
3664
3593
  if (!configResult)
3665
3594
  return;
3595
+ loadLauncherIfEnabled(configResult.config.enableDebug === true, configResult.config.nonce);
3666
3596
  const projectResult = await resolveProject(Coframe, configResult.previewVariantId, configResult.isSessionVariant, configResult.config);
3667
3597
  if (!projectResult)
3668
3598
  return;
@@ -857,6 +857,88 @@ const isSegmentEmpty = (segment) => {
857
857
  return segment.xpath === '';
858
858
  };
859
859
 
860
+ function parseQueryString(query) {
861
+ const params = {};
862
+ const cleanQuery = query.startsWith('?') ? query.slice(1) : query;
863
+ if (!cleanQuery)
864
+ return params;
865
+ for (const pair of cleanQuery.split('&')) {
866
+ if (!pair)
867
+ continue;
868
+ const [key, value = ''] = pair.split('=').map(decodeURIComponent);
869
+ if (!(key in params)) {
870
+ params[key] = value;
871
+ }
872
+ }
873
+ return params;
874
+ }
875
+ function createURLSearchParams(query) {
876
+ if (typeof URLSearchParams !== 'undefined') {
877
+ const nativeParams = new URLSearchParams(query);
878
+ return {
879
+ get: (key) => nativeParams.get(key),
880
+ };
881
+ }
882
+ const simpleParams = parseQueryString(query);
883
+ return {
884
+ get: (key) => { var _a; return (_a = simpleParams[key]) !== null && _a !== void 0 ? _a : null; },
885
+ };
886
+ }
887
+ function getUrlSearchParams(url) {
888
+ const searchIndex = url.indexOf('?');
889
+ const firstHashIndex = url.indexOf('#');
890
+ if (searchIndex === -1 ||
891
+ (firstHashIndex !== -1 && firstHashIndex < searchIndex)) {
892
+ return createURLSearchParams('');
893
+ }
894
+ const hashIndex = url.indexOf('#', searchIndex);
895
+ const search = hashIndex !== -1
896
+ ? url.substring(searchIndex, hashIndex)
897
+ : url.substring(searchIndex);
898
+ return createURLSearchParams(search);
899
+ }
900
+
901
+ const COFRAME_DEBUG_QUERY_PARAM = 'cf_debug';
902
+ const COFRAME_TOOL_INTERACTION_ATTRIBUTE = 'data-cf-t';
903
+ const COFRAME_DEBUG_TOOLS_SESSION_STORAGE_KEY = 'cf.tools.enabled';
904
+ const COFRAME_DEBUG_TOOLS_STATE_STORAGE_KEY = 'cf.tools.session';
905
+ const ENABLED_VALUE = 'true';
906
+ function enableDebugToolsSession() {
907
+ try {
908
+ window.sessionStorage.setItem(COFRAME_DEBUG_TOOLS_SESSION_STORAGE_KEY, ENABLED_VALUE);
909
+ }
910
+ catch (_a) {
911
+ }
912
+ }
913
+ function clearDebugToolsSession() {
914
+ try {
915
+ window.sessionStorage.removeItem(COFRAME_DEBUG_TOOLS_SESSION_STORAGE_KEY);
916
+ window.sessionStorage.removeItem(COFRAME_DEBUG_TOOLS_STATE_STORAGE_KEY);
917
+ }
918
+ catch (_a) {
919
+ }
920
+ }
921
+ function isDebugToolsSessionEnabled(allowed) {
922
+ if (!allowed) {
923
+ clearDebugToolsSession();
924
+ return false;
925
+ }
926
+ const enabledByUrl = getUrlSearchParams(getCurrentPageUrl()).get(COFRAME_DEBUG_QUERY_PARAM) ===
927
+ ENABLED_VALUE;
928
+ if (enabledByUrl) {
929
+ enableDebugToolsSession();
930
+ return true;
931
+ }
932
+ try {
933
+ const enabled = window.sessionStorage.getItem(COFRAME_DEBUG_TOOLS_SESSION_STORAGE_KEY) ===
934
+ ENABLED_VALUE;
935
+ return enabled;
936
+ }
937
+ catch (_a) {
938
+ return false;
939
+ }
940
+ }
941
+
860
942
  const USER_CONTEXT_KEY_PREFIX = 'x-ctx-';
861
943
  const MAX_USER_CONTEXT_JSON_BYTES = 4096;
862
944
  function userContextJsonByteLength(userContext) {
@@ -1035,6 +1117,12 @@ function queueVariantViewEvent(coframe, { projectId, sessionId, orgId, userToken
1035
1117
  }
1036
1118
  function sendPageEngagementEvent(coframe, eventType, event, { projectId, sessionId, duration = 0, userToken, personalizationHeaders, }) {
1037
1119
  var _a;
1120
+ if (event
1121
+ .composedPath()
1122
+ .some((target) => target instanceof Element &&
1123
+ target.hasAttribute(COFRAME_TOOL_INTERACTION_ATTRIBUTE))) {
1124
+ return;
1125
+ }
1038
1126
  const startTime = performance.now();
1039
1127
  const x = event.clientX + window.scrollX;
1040
1128
  const y = event.clientY + window.scrollY;
@@ -2214,44 +2302,8 @@ function debounce(func, wait) {
2214
2302
  }, wait);
2215
2303
  };
2216
2304
  }
2217
- function parseQueryString(query) {
2218
- const params = {};
2219
- const cleanQuery = query.startsWith('?') ? query.slice(1) : query;
2220
- if (!cleanQuery)
2221
- return params;
2222
- for (const pair of cleanQuery.split('&')) {
2223
- if (!pair)
2224
- continue;
2225
- const [key, value = ''] = pair.split('=').map(decodeURIComponent);
2226
- if (!(key in params)) {
2227
- params[key] = value;
2228
- }
2229
- }
2230
- return params;
2231
- }
2232
- function createURLSearchParams(query) {
2233
- if (typeof URLSearchParams !== 'undefined') {
2234
- const nativeParams = new URLSearchParams(query);
2235
- return {
2236
- get: (key) => nativeParams.get(key),
2237
- };
2238
- }
2239
- const simpleParams = parseQueryString(query);
2240
- return {
2241
- get: (key) => { var _a; return (_a = simpleParams[key]) !== null && _a !== void 0 ? _a : null; },
2242
- };
2243
- }
2244
2305
  function getCurrentUrlSearchParams() {
2245
- const url = getCurrentPageUrl();
2246
- const searchIndex = url.indexOf('?');
2247
- if (searchIndex === -1) {
2248
- return createURLSearchParams('');
2249
- }
2250
- const hashIndex = url.indexOf('#', searchIndex);
2251
- const search = hashIndex !== -1
2252
- ? url.substring(searchIndex, hashIndex)
2253
- : url.substring(searchIndex);
2254
- return createURLSearchParams(search);
2306
+ return getUrlSearchParams(getCurrentPageUrl());
2255
2307
  }
2256
2308
 
2257
2309
  const defaultScale = 100000;
@@ -2486,6 +2538,44 @@ async function getVariantsAndElements(personalizationHeaders, project, previewVa
2486
2538
  });
2487
2539
  }
2488
2540
 
2541
+ const COFRAME_PREVIEW_SESSION_STORAGE_KEY = 'coframe.preview_variant';
2542
+ const COFRAME_PREVIEW_VARIANT_QUERY_PARAM = 'coframe_variant_id';
2543
+ function normalizePreviewVariantId(raw) {
2544
+ if (!raw)
2545
+ return null;
2546
+ const ids = raw
2547
+ .split(',')
2548
+ .map((id) => id.trim())
2549
+ .filter(Boolean);
2550
+ return ids.length > 0 ? ids.join(',') : null;
2551
+ }
2552
+ function readPreviewVariantIdFromUrl(url = window.location.href) {
2553
+ try {
2554
+ return normalizePreviewVariantId(getUrlSearchParams(url).get(COFRAME_PREVIEW_VARIANT_QUERY_PARAM));
2555
+ }
2556
+ catch (_a) {
2557
+ return null;
2558
+ }
2559
+ }
2560
+ function readStoredPreviewVariantId() {
2561
+ try {
2562
+ return normalizePreviewVariantId(window.sessionStorage.getItem(COFRAME_PREVIEW_SESSION_STORAGE_KEY));
2563
+ }
2564
+ catch (_a) {
2565
+ return null;
2566
+ }
2567
+ }
2568
+ function resolvePreviewSource(url = window.location.href) {
2569
+ const urlVariantId = readPreviewVariantIdFromUrl(url);
2570
+ const storedVariantId = readStoredPreviewVariantId();
2571
+ return {
2572
+ effectiveVariantId: urlVariantId || storedVariantId,
2573
+ isKeptForSession: Boolean(storedVariantId && storedVariantId === (urlVariantId || storedVariantId)),
2574
+ storedVariantId,
2575
+ urlVariantId,
2576
+ };
2577
+ }
2578
+
2489
2579
  function buildSessionContext(isInitialApply) {
2490
2580
  const context = {};
2491
2581
  const viewport = getScreenSize();
@@ -2639,220 +2729,6 @@ function rebindMetrics(coframe, logger, metrics, nonce) {
2639
2729
  }
2640
2730
  }
2641
2731
 
2642
- function getActivePreviewModes(sessionPreview) {
2643
- if (!sessionPreview.isSessionPreviewActive ||
2644
- !sessionPreview.previewVariantId) {
2645
- return [];
2646
- }
2647
- return [
2648
- {
2649
- id: 'session-preview',
2650
- label: 'Preview on',
2651
- title: `Session preview is active for ${sessionPreview.previewVariantId}`,
2652
- variantIds: sessionPreview.previewVariantId
2653
- .split(',')
2654
- .map((id) => id.trim())
2655
- .filter(Boolean),
2656
- },
2657
- ];
2658
- }
2659
-
2660
- const PREVIEW_UI_PROTOCOL_VERSION = 1;
2661
-
2662
- const SESSION_VARIANT_KEY = 'coframe.preview_variant';
2663
- const PREVIEW_VARIANT_QUERY_PARAM = 'coframe_variant_id';
2664
- const KEEP_PREVIEW_FOR_SESSION_QUERY_PARAM = 'keep_preview_for_session';
2665
- let previewDismissed = false;
2666
- function dismissSessionPreview() {
2667
- previewDismissed = true;
2668
- clearPreviewVariantId();
2669
- const url = new URL(window.location.href);
2670
- url.searchParams.delete(PREVIEW_VARIANT_QUERY_PARAM);
2671
- url.searchParams.delete(KEEP_PREVIEW_FOR_SESSION_QUERY_PARAM);
2672
- window.history.replaceState(window.history.state, '', url);
2673
- window.location.reload();
2674
- }
2675
- function normalizeVariantIds(raw) {
2676
- if (!raw)
2677
- return null;
2678
- const ids = raw
2679
- .split(',')
2680
- .map((id) => id.trim())
2681
- .filter(Boolean);
2682
- return ids.length > 0 ? ids.join(',') : null;
2683
- }
2684
- function readPreviewVariantId() {
2685
- try {
2686
- return sessionStorage.getItem(SESSION_VARIANT_KEY);
2687
- }
2688
- catch (_a) {
2689
- return null;
2690
- }
2691
- }
2692
- function persistPreviewVariantId(variantId) {
2693
- const normalized = normalizeVariantIds(variantId);
2694
- if (!normalized)
2695
- return;
2696
- try {
2697
- sessionStorage.setItem(SESSION_VARIANT_KEY, normalized);
2698
- }
2699
- catch (_a) {
2700
- }
2701
- }
2702
- function clearPreviewVariantId() {
2703
- try {
2704
- sessionStorage.removeItem(SESSION_VARIANT_KEY);
2705
- }
2706
- catch (_a) {
2707
- }
2708
- }
2709
- function resolvePreviewSource(urlParams = getCurrentUrlSearchParams()) {
2710
- return {
2711
- urlVariantId: urlParams.get(PREVIEW_VARIANT_QUERY_PARAM),
2712
- storedVariantId: readPreviewVariantId(),
2713
- keepForSession: urlParams.get(KEEP_PREVIEW_FOR_SESSION_QUERY_PARAM) === 'true',
2714
- };
2715
- }
2716
- function resolveSessionPreviewState() {
2717
- if (previewDismissed) {
2718
- return { isSessionPreviewActive: false, previewVariantId: null };
2719
- }
2720
- const previewSource = resolvePreviewSource();
2721
- const urlVariantId = normalizeVariantIds(previewSource.urlVariantId);
2722
- if (urlVariantId && previewSource.keepForSession) {
2723
- return {
2724
- isSessionPreviewActive: true,
2725
- previewVariantId: urlVariantId,
2726
- };
2727
- }
2728
- if (urlVariantId) {
2729
- return { isSessionPreviewActive: false, previewVariantId: null };
2730
- }
2731
- const storedVariantId = normalizeVariantIds(previewSource.storedVariantId);
2732
- return {
2733
- isSessionPreviewActive: !!storedVariantId,
2734
- previewVariantId: storedVariantId,
2735
- };
2736
- }
2737
-
2738
- const PREVIEW_UI_SCRIPT_ID = 'coframe-preview-ui-script';
2739
- const PREVIEW_UI_URL = "https://edge.cofra.me/preview-ui/latest.js";
2740
- let loadPromise = null;
2741
- let latestState = { modes: [] };
2742
- let nonce;
2743
- let logger;
2744
- let mounted = false;
2745
- function reportError(message, error) {
2746
- const errorMessage = error instanceof Error ? error.message : String(error);
2747
- if (logger) {
2748
- logger.warn(message, 'preview_ui_unavailable', { error: errorMessage });
2749
- return;
2750
- }
2751
- console.warn(`[Coframe] ${message}`, error);
2752
- }
2753
- function getPreviewUI() {
2754
- const api = window.CoframePreviewUI;
2755
- return (api === null || api === void 0 ? void 0 : api.protocolVersion) === PREVIEW_UI_PROTOCOL_VERSION ? api : undefined;
2756
- }
2757
- function loadPreviewUI() {
2758
- const existing = getPreviewUI();
2759
- if (existing)
2760
- return Promise.resolve(existing);
2761
- if (loadPromise)
2762
- return loadPromise;
2763
- loadPromise = new Promise((resolve, reject) => {
2764
- const script = document.createElement('script');
2765
- script.id = PREVIEW_UI_SCRIPT_ID;
2766
- script.async = true;
2767
- script.src = PREVIEW_UI_URL;
2768
- if (nonce)
2769
- script.nonce = nonce;
2770
- script.onload = () => {
2771
- const api = getPreviewUI();
2772
- if (!api) {
2773
- reject(new Error('Preview UI protocol is missing or incompatible'));
2774
- return;
2775
- }
2776
- resolve(api);
2777
- };
2778
- script.onerror = () => {
2779
- reject(new Error('Preview UI script failed to load'));
2780
- };
2781
- document.head.appendChild(script);
2782
- });
2783
- return loadPromise;
2784
- }
2785
- function renderPreviewUI(api) {
2786
- try {
2787
- if (latestState.modes.length === 0) {
2788
- api.unmount();
2789
- mounted = false;
2790
- }
2791
- else if (mounted) {
2792
- api.update(latestState);
2793
- }
2794
- else {
2795
- api.mount({ state: latestState, nonce, onDismiss: dismissPreviewPanel });
2796
- mounted = true;
2797
- }
2798
- }
2799
- catch (error) {
2800
- reportError('Preview UI could not be rendered', error);
2801
- }
2802
- }
2803
- function dismissPreviewPanel() {
2804
- dismissSessionPreview();
2805
- latestState = { modes: [] };
2806
- const api = getPreviewUI();
2807
- if (api)
2808
- renderPreviewUI(api);
2809
- }
2810
- function modesWithVariants(sessionPreview, variantsAndElements, applied = true) {
2811
- const activeModes = getActivePreviewModes(sessionPreview);
2812
- if (activeModes.length === 0)
2813
- return [];
2814
- const currentPageVariantIds = new Set((variantsAndElements === null || variantsAndElements === void 0 ? void 0 : variantsAndElements.map(({ variant }) => variant.id)) || []);
2815
- const slugById = new Map((variantsAndElements === null || variantsAndElements === void 0 ? void 0 : variantsAndElements.map(({ variant }) => [variant.id, variant.slug])) || []);
2816
- return activeModes.map((mode) => ({
2817
- id: mode.id,
2818
- label: mode.label,
2819
- title: mode.title,
2820
- variants: mode.variantIds.map((id) => ({
2821
- id,
2822
- slug: slugById.get(id) || null,
2823
- status: variantsAndElements
2824
- ? applied && currentPageVariantIds.has(id)
2825
- ? 'active'
2826
- : 'inactive'
2827
- : 'pending',
2828
- })),
2829
- }));
2830
- }
2831
- function resolveSessionPreviewPanel(sessionPreview, previewNonce, previewLogger) {
2832
- const modes = modesWithVariants(sessionPreview);
2833
- if (modes.length === 0)
2834
- return;
2835
- nonce = previewNonce;
2836
- logger = previewLogger;
2837
- latestState = { modes };
2838
- const existing = getPreviewUI();
2839
- if (existing) {
2840
- renderPreviewUI(existing);
2841
- return;
2842
- }
2843
- void loadPreviewUI()
2844
- .then(renderPreviewUI)
2845
- .catch((error) => reportError('Preview UI could not be loaded', error));
2846
- }
2847
- function updatePreviewPanel(variantsAndElements, sessionPreview, applied = true) {
2848
- latestState = {
2849
- modes: modesWithVariants(sessionPreview, variantsAndElements, applied),
2850
- };
2851
- const api = getPreviewUI();
2852
- if (api)
2853
- renderPreviewUI(api);
2854
- }
2855
-
2856
2732
  let lastUrl = null;
2857
2733
  function observeUrlChanges(baseUrlMatch, onUrlChange) {
2858
2734
  const tryTrack = () => {
@@ -3207,21 +3083,23 @@ function setupTracking(Coframe, projectResult, previewVariantId, executionStartT
3207
3083
  }
3208
3084
 
3209
3085
  function resolvePreview(pageUrl, previewVariantId, isSessionVariant) {
3210
- let urlPreviewVariantId = null;
3211
- try {
3212
- urlPreviewVariantId = new URL(pageUrl).searchParams.get('coframe_variant_id');
3213
- }
3214
- catch (_a) {
3215
- urlPreviewVariantId = getCurrentUrlSearchParams().get('coframe_variant_id');
3086
+ const previewSource = resolvePreviewSource(pageUrl);
3087
+ if (previewSource.urlVariantId) {
3088
+ return {
3089
+ previewVariantId: previewSource.urlVariantId,
3090
+ isSessionVariant: previewSource.isKeptForSession,
3091
+ };
3216
3092
  }
3217
- if (urlPreviewVariantId) {
3218
- return { previewVariantId: urlPreviewVariantId, isSessionVariant: false };
3093
+ if (previewSource.storedVariantId) {
3094
+ return {
3095
+ previewVariantId: previewSource.storedVariantId,
3096
+ isSessionVariant: true,
3097
+ };
3219
3098
  }
3220
3099
  if (previewVariantId) {
3221
3100
  return { previewVariantId, isSessionVariant };
3222
3101
  }
3223
- const stored = readPreviewVariantId();
3224
- return { previewVariantId: stored, isSessionVariant: !!stored };
3102
+ return { previewVariantId: null, isSessionVariant: false };
3225
3103
  }
3226
3104
  async function awaitHydration(config, Coframe, project) {
3227
3105
  var _a, _b;
@@ -3354,8 +3232,6 @@ function createApplyVariants(Coframe, config, previewVariantId, isSessionVariant
3354
3232
  Coframe.experimentInfo = experimentsInfo;
3355
3233
  Coframe.variantsAndElements = variantsAndElements;
3356
3234
  setFrameActivated(hasActivatedFrames);
3357
- const sessionPreview = resolveSessionPreviewState();
3358
- updatePreviewPanel(variantsAndElements, sessionPreview, !config.trackingOnly);
3359
3235
  if (Coframe.debug) {
3360
3236
  Coframe.debug.project = pageProject;
3361
3237
  Coframe.debug.frames = pageProject.frames;
@@ -3375,11 +3251,6 @@ function createApplyVariants(Coframe, config, previewVariantId, isSessionVariant
3375
3251
  async function modifyPage(Coframe, configResult, projectResult, analytics) {
3376
3252
  const { config, timeoutMs, previewVariantId, isSessionVariant, integrationStartTime, executionStartTime, } = configResult;
3377
3253
  const { project: cdnProject, personalizationHeaders } = projectResult;
3378
- if (resolveSessionPreviewState().isSessionPreviewActive) {
3379
- waitForBody(timeoutMs)
3380
- .then(() => resolveSessionPreviewPanel(resolveSessionPreviewState(), config.nonce, Coframe.logger))
3381
- .catch(() => { });
3382
- }
3383
3254
  if (!(await awaitHydration(config, Coframe, cdnProject)))
3384
3255
  return;
3385
3256
  if (cdnProject.tags) {
@@ -3472,15 +3343,13 @@ async function resolveConfig(Coframe, revealPage) {
3472
3343
  }
3473
3344
  Coframe.projectId = projectId;
3474
3345
  const urlParams = getCurrentUrlSearchParams();
3475
- const previewSource = resolvePreviewSource(urlParams);
3346
+ const previewSource = resolvePreviewSource();
3476
3347
  const shouldSendEventsParam = urlParams.get('coframe_should_send_events');
3477
3348
  let previewVariantId = null;
3478
3349
  let isSessionVariant = false;
3479
3350
  if (previewSource.urlVariantId) {
3480
3351
  previewVariantId = previewSource.urlVariantId;
3481
- if (previewSource.keepForSession) {
3482
- persistPreviewVariantId(previewSource.urlVariantId);
3483
- }
3352
+ isSessionVariant = previewSource.isKeptForSession;
3484
3353
  }
3485
3354
  else if (previewSource.storedVariantId) {
3486
3355
  previewVariantId = previewSource.storedVariantId;
@@ -3580,6 +3449,66 @@ async function resolveProject(Coframe, previewVariantId, isSessionVariant, confi
3580
3449
  };
3581
3450
  }
3582
3451
 
3452
+ const COFRAME_LAUNCHER_PROTOCOL_VERSION = 1;
3453
+
3454
+ const LAUNCHER_SCRIPT_ID = 'coframe-tools-launcher-script';
3455
+ const LAUNCHER_URL = "https://edge.cofra.me/launcher/latest.js";
3456
+ let loading = false;
3457
+ function getLauncher() {
3458
+ const launcher = window.CoframeLauncher;
3459
+ return (launcher === null || launcher === void 0 ? void 0 : launcher.protocolVersion) === COFRAME_LAUNCHER_PROTOCOL_VERSION
3460
+ ? launcher
3461
+ : undefined;
3462
+ }
3463
+ function exitDebugSession() {
3464
+ clearDebugToolsSession();
3465
+ const url = new URL(window.location.href);
3466
+ url.searchParams.delete(COFRAME_DEBUG_QUERY_PARAM);
3467
+ window.history.replaceState(window.history.state, '', url);
3468
+ }
3469
+ function mountLauncher(nonce) {
3470
+ const launcher = getLauncher();
3471
+ if (!launcher)
3472
+ return false;
3473
+ launcher.mount({ nonce, onExitDebugSession: exitDebugSession });
3474
+ return true;
3475
+ }
3476
+ function loadLauncherIfEnabled(enabled, nonce) {
3477
+ if (typeof window === 'undefined')
3478
+ return;
3479
+ let sessionEnabled = false;
3480
+ try {
3481
+ sessionEnabled = isDebugToolsSessionEnabled(enabled);
3482
+ }
3483
+ catch (error) {
3484
+ console.warn('[Coframe] Unable to determine Debug Tools session', error);
3485
+ return;
3486
+ }
3487
+ if (!sessionEnabled || mountLauncher(nonce) || loading) {
3488
+ return;
3489
+ }
3490
+ loading = true;
3491
+ const script = document.createElement('script');
3492
+ script.id = LAUNCHER_SCRIPT_ID;
3493
+ script.async = true;
3494
+ script.src = LAUNCHER_URL;
3495
+ if (nonce)
3496
+ script.nonce = nonce;
3497
+ script.onload = () => {
3498
+ loading = false;
3499
+ if (!mountLauncher(nonce)) {
3500
+ document.head.removeChild(script);
3501
+ console.warn('[Coframe] Launcher protocol is missing or incompatible');
3502
+ }
3503
+ };
3504
+ script.onerror = () => {
3505
+ loading = false;
3506
+ document.head.removeChild(script);
3507
+ console.warn('[Coframe] Launcher script failed to load');
3508
+ };
3509
+ document.head.appendChild(script);
3510
+ }
3511
+
3583
3512
  function injectOptions(options) {
3584
3513
  var _a;
3585
3514
  if (options.config) {
@@ -3661,6 +3590,7 @@ async function init(options) {
3661
3590
  const configResult = await resolveConfig(Coframe, revealPage);
3662
3591
  if (!configResult)
3663
3592
  return;
3593
+ loadLauncherIfEnabled(configResult.config.enableDebug === true, configResult.config.nonce);
3664
3594
  const projectResult = await resolveProject(Coframe, configResult.previewVariantId, configResult.isSessionVariant, configResult.config);
3665
3595
  if (!projectResult)
3666
3596
  return;
@@ -248,6 +248,7 @@ type CoframeConfig = {
248
248
  currentUrl?: string;
249
249
  userContext?: Record<string, unknown>;
250
250
  nonce?: string;
251
+ enableDebug?: boolean;
251
252
  };
252
253
  type ConversionEventPayload = {
253
254
  metricId?: string;
@@ -251,6 +251,7 @@ type CoframeConfig = {
251
251
  currentUrl?: string;
252
252
  userContext?: Record<string, unknown>;
253
253
  nonce?: string;
254
+ enableDebug?: boolean;
254
255
  };
255
256
  type BotReason = 'none' | 'cf_bot_score' | 'cf_verified_bot' | 'force_bot_param';
256
257
  type PersonalizationHeaders = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coframe/sdk",
3
- "version": "1.1.10",
3
+ "version": "1.1.11",
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",
@@ -42,7 +42,8 @@
42
42
  "lint:fix!": "biome check --write --unsafe .",
43
43
  "deploy:cdn": "./bin/publish.sh",
44
44
  "deploy:cdn:prod": "SDK_ENV=prod ./bin/publish.sh",
45
- "dev": "NODE_ENV=production EXTERNAL_PORT=3004 rollup -c -w"
45
+ "dev": "NODE_ENV=production EXTERNAL_PORT=3004 rollup -c -w",
46
+ "dev:local-edge": "COFRAME_EDGE_URL=http://localhost:8787 NODE_ENV=production EXTERNAL_PORT=3004 rollup -c -w"
46
47
  },
47
48
  "author": "Coframe",
48
49
  "keywords": [
@@ -130,4 +131,4 @@
130
131
  "ua-parser-js": "^2.0.10"
131
132
  },
132
133
  "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
133
- }
134
+ }