@coframe/sdk 1.1.9 → 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
@@ -40,6 +40,24 @@ await init({
40
40
 
41
41
  `init()` accepts `config` and optional `projectData`, so npm consumers do not need to manually populate `window.CFQ` first.
42
42
 
43
+ ### URL controls
44
+
45
+ The npm entry uses the same browser URL controls as the script-tag and Edge
46
+ installations:
47
+
48
+ - `cf_disable` skips initialization and reveals any antiflicker state.
49
+ - `coframe_variant_id` previews one or more comma-separated variant IDs.
50
+ - `coframe_should_send_events=true` allows events during preview for testing.
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
+
56
+ For SSR, pass the complete request URL, including its query string, as
57
+ `currentUrl`. This is required for query-sensitive targeting and allows
58
+ `getProjectData()` to short-circuit before any request when `cf_disable` is
59
+ present.
60
+
43
61
  ### React
44
62
 
45
63
  ```tsx
@@ -60,6 +78,8 @@ export default function App({ children }) {
60
78
  ```
61
79
 
62
80
  Mount the provider once at the root of your app. It initializes the SDK on mount and keeps treatment state available to child components.
81
+ Keep it mounted across client-side route changes; runtime commands and the SDK's
82
+ navigation observer handle subsequent page changes without remounting it.
63
83
 
64
84
  ### Server prefetch
65
85
 
@@ -148,6 +168,10 @@ import { CoframeProvider } from '@coframe/sdk/react'
148
168
  </CoframeProvider>
149
169
  ```
150
170
 
171
+ If your privacy policy permits treatment analytics before cookie consent, use
172
+ `onTreatmentAppliedBulkV2WithoutConsent` instead. It has the same callback
173
+ arguments and does not fire again when consent is later granted.
174
+
151
175
  Outside React, you can also register callbacks from the main entry:
152
176
 
153
177
  ```ts
@@ -159,6 +183,7 @@ import { onLog, onTreatmentApplied } from '@coframe/sdk'
159
183
  - `getProjectData()` is intended for SSR and edge runtimes that can provide a full request URL and request headers.
160
184
  - In Pages Router or Remix, per-request server loaders are the most straightforward place to call it.
161
185
  - In App Router, keep the provider mounted in a persistent shell, but be careful about request URL handling in `layout.tsx` if you need query-sensitive SSR targeting.
186
+ - The npm runtime reveals antiflicker styles but cannot insert them early enough to protect the initial paint. Render `antiflickerCSS` from `getProjectData()` in the server document head, or place equivalent static CSS and a timeout fallback there before application content.
162
187
 
163
188
  ## Package layout
164
189
 
@@ -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;
@@ -1823,6 +1911,7 @@ const waitForBody = (timeout = 10000) => {
1823
1911
  function revealPage() {
1824
1912
  document.dispatchEvent(new CustomEvent('coframe:show', {}));
1825
1913
  document.documentElement.style.setProperty('--cf-show', '1');
1914
+ document.documentElement.style.setProperty('--cf-visibility', 'visible');
1826
1915
  }
1827
1916
  async function replaceVariants(variants, orgId, logger, nonce) {
1828
1917
  const failedReplacements = [];
@@ -2215,44 +2304,8 @@ function debounce(func, wait) {
2215
2304
  }, wait);
2216
2305
  };
2217
2306
  }
2218
- function parseQueryString(query) {
2219
- const params = {};
2220
- const cleanQuery = query.startsWith('?') ? query.slice(1) : query;
2221
- if (!cleanQuery)
2222
- return params;
2223
- for (const pair of cleanQuery.split('&')) {
2224
- if (!pair)
2225
- continue;
2226
- const [key, value = ''] = pair.split('=').map(decodeURIComponent);
2227
- if (!(key in params)) {
2228
- params[key] = value;
2229
- }
2230
- }
2231
- return params;
2232
- }
2233
- function createURLSearchParams(query) {
2234
- if (typeof URLSearchParams !== 'undefined') {
2235
- const nativeParams = new URLSearchParams(query);
2236
- return {
2237
- get: (key) => nativeParams.get(key),
2238
- };
2239
- }
2240
- const simpleParams = parseQueryString(query);
2241
- return {
2242
- get: (key) => { var _a; return (_a = simpleParams[key]) !== null && _a !== void 0 ? _a : null; },
2243
- };
2244
- }
2245
2307
  function getCurrentUrlSearchParams() {
2246
- const url = getCurrentPageUrl();
2247
- const searchIndex = url.indexOf('?');
2248
- if (searchIndex === -1) {
2249
- return createURLSearchParams('');
2250
- }
2251
- const hashIndex = url.indexOf('#', searchIndex);
2252
- const search = hashIndex !== -1
2253
- ? url.substring(searchIndex, hashIndex)
2254
- : url.substring(searchIndex);
2255
- return createURLSearchParams(search);
2308
+ return getUrlSearchParams(getCurrentPageUrl());
2256
2309
  }
2257
2310
 
2258
2311
  const defaultScale = 100000;
@@ -2487,6 +2540,44 @@ async function getVariantsAndElements(personalizationHeaders, project, previewVa
2487
2540
  });
2488
2541
  }
2489
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
+
2490
2581
  function buildSessionContext(isInitialApply) {
2491
2582
  const context = {};
2492
2583
  const viewport = getScreenSize();
@@ -2640,220 +2731,6 @@ function rebindMetrics(coframe, logger, metrics, nonce) {
2640
2731
  }
2641
2732
  }
2642
2733
 
2643
- function getActivePreviewModes(sessionPreview) {
2644
- if (!sessionPreview.isSessionPreviewActive ||
2645
- !sessionPreview.previewVariantId) {
2646
- return [];
2647
- }
2648
- return [
2649
- {
2650
- id: 'session-preview',
2651
- label: 'Preview on',
2652
- title: `Session preview is active for ${sessionPreview.previewVariantId}`,
2653
- variantIds: sessionPreview.previewVariantId
2654
- .split(',')
2655
- .map((id) => id.trim())
2656
- .filter(Boolean),
2657
- },
2658
- ];
2659
- }
2660
-
2661
- const PREVIEW_UI_PROTOCOL_VERSION = 1;
2662
-
2663
- const SESSION_VARIANT_KEY = 'coframe.preview_variant';
2664
- const PREVIEW_VARIANT_QUERY_PARAM = 'coframe_variant_id';
2665
- const KEEP_PREVIEW_FOR_SESSION_QUERY_PARAM = 'keep_preview_for_session';
2666
- let previewDismissed = false;
2667
- function dismissSessionPreview() {
2668
- previewDismissed = true;
2669
- clearPreviewVariantId();
2670
- const url = new URL(window.location.href);
2671
- url.searchParams.delete(PREVIEW_VARIANT_QUERY_PARAM);
2672
- url.searchParams.delete(KEEP_PREVIEW_FOR_SESSION_QUERY_PARAM);
2673
- window.history.replaceState(window.history.state, '', url);
2674
- window.location.reload();
2675
- }
2676
- function normalizeVariantIds(raw) {
2677
- if (!raw)
2678
- return null;
2679
- const ids = raw
2680
- .split(',')
2681
- .map((id) => id.trim())
2682
- .filter(Boolean);
2683
- return ids.length > 0 ? ids.join(',') : null;
2684
- }
2685
- function readPreviewVariantId() {
2686
- try {
2687
- return sessionStorage.getItem(SESSION_VARIANT_KEY);
2688
- }
2689
- catch (_a) {
2690
- return null;
2691
- }
2692
- }
2693
- function persistPreviewVariantId(variantId) {
2694
- const normalized = normalizeVariantIds(variantId);
2695
- if (!normalized)
2696
- return;
2697
- try {
2698
- sessionStorage.setItem(SESSION_VARIANT_KEY, normalized);
2699
- }
2700
- catch (_a) {
2701
- }
2702
- }
2703
- function clearPreviewVariantId() {
2704
- try {
2705
- sessionStorage.removeItem(SESSION_VARIANT_KEY);
2706
- }
2707
- catch (_a) {
2708
- }
2709
- }
2710
- function resolvePreviewSource(urlParams = getCurrentUrlSearchParams()) {
2711
- return {
2712
- urlVariantId: urlParams.get(PREVIEW_VARIANT_QUERY_PARAM),
2713
- storedVariantId: readPreviewVariantId(),
2714
- keepForSession: urlParams.get(KEEP_PREVIEW_FOR_SESSION_QUERY_PARAM) === 'true',
2715
- };
2716
- }
2717
- function resolveSessionPreviewState() {
2718
- if (previewDismissed) {
2719
- return { isSessionPreviewActive: false, previewVariantId: null };
2720
- }
2721
- const previewSource = resolvePreviewSource();
2722
- const urlVariantId = normalizeVariantIds(previewSource.urlVariantId);
2723
- if (urlVariantId && previewSource.keepForSession) {
2724
- return {
2725
- isSessionPreviewActive: true,
2726
- previewVariantId: urlVariantId,
2727
- };
2728
- }
2729
- if (urlVariantId) {
2730
- return { isSessionPreviewActive: false, previewVariantId: null };
2731
- }
2732
- const storedVariantId = normalizeVariantIds(previewSource.storedVariantId);
2733
- return {
2734
- isSessionPreviewActive: !!storedVariantId,
2735
- previewVariantId: storedVariantId,
2736
- };
2737
- }
2738
-
2739
- const PREVIEW_UI_SCRIPT_ID = 'coframe-preview-ui-script';
2740
- const PREVIEW_UI_URL = "https://edge.cofra.me/preview-ui/latest.js";
2741
- let loadPromise = null;
2742
- let latestState = { modes: [] };
2743
- let nonce;
2744
- let logger;
2745
- let mounted = false;
2746
- function reportError(message, error) {
2747
- const errorMessage = error instanceof Error ? error.message : String(error);
2748
- if (logger) {
2749
- logger.warn(message, 'preview_ui_unavailable', { error: errorMessage });
2750
- return;
2751
- }
2752
- console.warn(`[Coframe] ${message}`, error);
2753
- }
2754
- function getPreviewUI() {
2755
- const api = window.CoframePreviewUI;
2756
- return (api === null || api === void 0 ? void 0 : api.protocolVersion) === PREVIEW_UI_PROTOCOL_VERSION ? api : undefined;
2757
- }
2758
- function loadPreviewUI() {
2759
- const existing = getPreviewUI();
2760
- if (existing)
2761
- return Promise.resolve(existing);
2762
- if (loadPromise)
2763
- return loadPromise;
2764
- loadPromise = new Promise((resolve, reject) => {
2765
- const script = document.createElement('script');
2766
- script.id = PREVIEW_UI_SCRIPT_ID;
2767
- script.async = true;
2768
- script.src = PREVIEW_UI_URL;
2769
- if (nonce)
2770
- script.nonce = nonce;
2771
- script.onload = () => {
2772
- const api = getPreviewUI();
2773
- if (!api) {
2774
- reject(new Error('Preview UI protocol is missing or incompatible'));
2775
- return;
2776
- }
2777
- resolve(api);
2778
- };
2779
- script.onerror = () => {
2780
- reject(new Error('Preview UI script failed to load'));
2781
- };
2782
- document.head.appendChild(script);
2783
- });
2784
- return loadPromise;
2785
- }
2786
- function renderPreviewUI(api) {
2787
- try {
2788
- if (latestState.modes.length === 0) {
2789
- api.unmount();
2790
- mounted = false;
2791
- }
2792
- else if (mounted) {
2793
- api.update(latestState);
2794
- }
2795
- else {
2796
- api.mount({ state: latestState, nonce, onDismiss: dismissPreviewPanel });
2797
- mounted = true;
2798
- }
2799
- }
2800
- catch (error) {
2801
- reportError('Preview UI could not be rendered', error);
2802
- }
2803
- }
2804
- function dismissPreviewPanel() {
2805
- dismissSessionPreview();
2806
- latestState = { modes: [] };
2807
- const api = getPreviewUI();
2808
- if (api)
2809
- renderPreviewUI(api);
2810
- }
2811
- function modesWithVariants(sessionPreview, variantsAndElements, applied = true) {
2812
- const activeModes = getActivePreviewModes(sessionPreview);
2813
- if (activeModes.length === 0)
2814
- return [];
2815
- const currentPageVariantIds = new Set((variantsAndElements === null || variantsAndElements === void 0 ? void 0 : variantsAndElements.map(({ variant }) => variant.id)) || []);
2816
- const slugById = new Map((variantsAndElements === null || variantsAndElements === void 0 ? void 0 : variantsAndElements.map(({ variant }) => [variant.id, variant.slug])) || []);
2817
- return activeModes.map((mode) => ({
2818
- id: mode.id,
2819
- label: mode.label,
2820
- title: mode.title,
2821
- variants: mode.variantIds.map((id) => ({
2822
- id,
2823
- slug: slugById.get(id) || null,
2824
- status: variantsAndElements
2825
- ? applied && currentPageVariantIds.has(id)
2826
- ? 'active'
2827
- : 'inactive'
2828
- : 'pending',
2829
- })),
2830
- }));
2831
- }
2832
- function resolveSessionPreviewPanel(sessionPreview, previewNonce, previewLogger) {
2833
- const modes = modesWithVariants(sessionPreview);
2834
- if (modes.length === 0)
2835
- return;
2836
- nonce = previewNonce;
2837
- logger = previewLogger;
2838
- latestState = { modes };
2839
- const existing = getPreviewUI();
2840
- if (existing) {
2841
- renderPreviewUI(existing);
2842
- return;
2843
- }
2844
- void loadPreviewUI()
2845
- .then(renderPreviewUI)
2846
- .catch((error) => reportError('Preview UI could not be loaded', error));
2847
- }
2848
- function updatePreviewPanel(variantsAndElements, sessionPreview, applied = true) {
2849
- latestState = {
2850
- modes: modesWithVariants(sessionPreview, variantsAndElements, applied),
2851
- };
2852
- const api = getPreviewUI();
2853
- if (api)
2854
- renderPreviewUI(api);
2855
- }
2856
-
2857
2734
  let lastUrl = null;
2858
2735
  function observeUrlChanges(baseUrlMatch, onUrlChange) {
2859
2736
  const tryTrack = () => {
@@ -3208,21 +3085,23 @@ function setupTracking(Coframe, projectResult, previewVariantId, executionStartT
3208
3085
  }
3209
3086
 
3210
3087
  function resolvePreview(pageUrl, previewVariantId, isSessionVariant) {
3211
- let urlPreviewVariantId = null;
3212
- try {
3213
- urlPreviewVariantId = new URL(pageUrl).searchParams.get('coframe_variant_id');
3214
- }
3215
- catch (_a) {
3216
- 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
+ };
3217
3094
  }
3218
- if (urlPreviewVariantId) {
3219
- return { previewVariantId: urlPreviewVariantId, isSessionVariant: false };
3095
+ if (previewSource.storedVariantId) {
3096
+ return {
3097
+ previewVariantId: previewSource.storedVariantId,
3098
+ isSessionVariant: true,
3099
+ };
3220
3100
  }
3221
3101
  if (previewVariantId) {
3222
3102
  return { previewVariantId, isSessionVariant };
3223
3103
  }
3224
- const stored = readPreviewVariantId();
3225
- return { previewVariantId: stored, isSessionVariant: !!stored };
3104
+ return { previewVariantId: null, isSessionVariant: false };
3226
3105
  }
3227
3106
  async function awaitHydration(config, Coframe, project) {
3228
3107
  var _a, _b;
@@ -3355,8 +3234,6 @@ function createApplyVariants(Coframe, config, previewVariantId, isSessionVariant
3355
3234
  Coframe.experimentInfo = experimentsInfo;
3356
3235
  Coframe.variantsAndElements = variantsAndElements;
3357
3236
  setFrameActivated(hasActivatedFrames);
3358
- const sessionPreview = resolveSessionPreviewState();
3359
- updatePreviewPanel(variantsAndElements, sessionPreview, !config.trackingOnly);
3360
3237
  if (Coframe.debug) {
3361
3238
  Coframe.debug.project = pageProject;
3362
3239
  Coframe.debug.frames = pageProject.frames;
@@ -3376,11 +3253,6 @@ function createApplyVariants(Coframe, config, previewVariantId, isSessionVariant
3376
3253
  async function modifyPage(Coframe, configResult, projectResult, analytics) {
3377
3254
  const { config, timeoutMs, previewVariantId, isSessionVariant, integrationStartTime, executionStartTime, } = configResult;
3378
3255
  const { project: cdnProject, personalizationHeaders } = projectResult;
3379
- if (resolveSessionPreviewState().isSessionPreviewActive) {
3380
- waitForBody(timeoutMs)
3381
- .then(() => resolveSessionPreviewPanel(resolveSessionPreviewState(), config.nonce, Coframe.logger))
3382
- .catch(() => { });
3383
- }
3384
3256
  if (!(await awaitHydration(config, Coframe, cdnProject)))
3385
3257
  return;
3386
3258
  if (cdnProject.tags) {
@@ -3473,15 +3345,13 @@ async function resolveConfig(Coframe, revealPage) {
3473
3345
  }
3474
3346
  Coframe.projectId = projectId;
3475
3347
  const urlParams = getCurrentUrlSearchParams();
3476
- const previewSource = resolvePreviewSource(urlParams);
3348
+ const previewSource = resolvePreviewSource();
3477
3349
  const shouldSendEventsParam = urlParams.get('coframe_should_send_events');
3478
3350
  let previewVariantId = null;
3479
3351
  let isSessionVariant = false;
3480
3352
  if (previewSource.urlVariantId) {
3481
3353
  previewVariantId = previewSource.urlVariantId;
3482
- if (previewSource.keepForSession) {
3483
- persistPreviewVariantId(previewSource.urlVariantId);
3484
- }
3354
+ isSessionVariant = previewSource.isKeptForSession;
3485
3355
  }
3486
3356
  else if (previewSource.storedVariantId) {
3487
3357
  previewVariantId = previewSource.storedVariantId;
@@ -3581,6 +3451,66 @@ async function resolveProject(Coframe, previewVariantId, isSessionVariant, confi
3581
3451
  };
3582
3452
  }
3583
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
+
3584
3514
  function injectOptions(options) {
3585
3515
  var _a;
3586
3516
  if (options.config) {
@@ -3646,6 +3576,12 @@ const Coframe = {
3646
3576
  };
3647
3577
  Coframe.logger = createLogger(() => Coframe);
3648
3578
  async function init(options) {
3579
+ if (typeof window !== 'undefined' &&
3580
+ /cf_disable/.test(window.location.search)) {
3581
+ console.warn('[Coframe] Disabled via URL parameter');
3582
+ revealPage();
3583
+ return;
3584
+ }
3649
3585
  if (options)
3650
3586
  injectOptions(options);
3651
3587
  const gracefulExit = (msg) => {
@@ -3656,6 +3592,7 @@ async function init(options) {
3656
3592
  const configResult = await resolveConfig(Coframe, revealPage);
3657
3593
  if (!configResult)
3658
3594
  return;
3595
+ loadLauncherIfEnabled(configResult.config.enableDebug === true, configResult.config.nonce);
3659
3596
  const projectResult = await resolveProject(Coframe, configResult.previewVariantId, configResult.isSessionVariant, configResult.config);
3660
3597
  if (!projectResult)
3661
3598
  return;