@coframe/sdk 1.1.25 → 1.1.26

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
@@ -51,6 +51,7 @@ await init({
51
51
  projectId: 'your-project-id',
52
52
  projectUrl: 'https://cdn.example.com/coframe/project.json',
53
53
  ingestUrl: 'https://www.example.com/ingest/v2',
54
+ replayScriptUrl: 'https://www.example.com/replay/latest.js',
54
55
  enableDebug: false,
55
56
  },
56
57
  })
@@ -65,11 +66,14 @@ only has to forward `<ingestUrl>/*` to `https://ingest.app.coframe.com/ingest/v2
65
66
  with the path unchanged. Both overrides are optional and retain the compiled
66
67
  Coframe endpoints when omitted.
67
68
 
69
+ `replayScriptUrl` replaces the session replay recorder script URL. A proxy must
70
+ forward it to `<edge>/replay/latest.js` unchanged.
71
+
68
72
  Configuring either `projectUrl` or `ingestUrl` makes CMAB experiments use
69
73
  deterministic local assignment without calling the Edge assignment API.
70
- Configure both overrides when the browser must avoid the SDK-compiled Coframe
71
- project and ingest endpoints entirely. Debug tools remain controlled by
72
- `enableDebug`.
74
+ Configure all three overrides when the browser must avoid the SDK-compiled
75
+ Coframe project, ingest and replay script endpoints entirely. Debug tools
76
+ remain controlled by `enableDebug`.
73
77
 
74
78
  ### URL controls
75
79
 
@@ -3231,6 +3231,20 @@ function observeUrlChanges(baseUrlMatch, onUrlChange) {
3231
3231
  patchHistoryMethod('replaceState');
3232
3232
  }
3233
3233
 
3234
+ const callbacks = [];
3235
+ function onEventsFlushed(callback) {
3236
+ callbacks.push(callback);
3237
+ }
3238
+ function notifyEventsFlushed() {
3239
+ const pendingCallbacks = callbacks.splice(0);
3240
+ for (const callback of pendingCallbacks) {
3241
+ try {
3242
+ callback();
3243
+ }
3244
+ catch (_a) { }
3245
+ }
3246
+ }
3247
+
3234
3248
  function startSession(Coframe, cookiesEnabled, executionStartTime, personalizationHeaders, sessionContext, pageUrl = getCurrentPageUrl()) {
3235
3249
  const sessionPayload = [
3236
3250
  Coframe,
@@ -3505,6 +3519,7 @@ function setupListeners(Coframe, executionStartTime) {
3505
3519
  personalizationHeaders: Coframe.personalizationHeaders,
3506
3520
  });
3507
3521
  flushQueuedEvents(Coframe.projectId);
3522
+ notifyEventsFlushed();
3508
3523
  }, 1);
3509
3524
  window.addEventListener('beforeunload', () => handleEventFlush('before_unload'));
3510
3525
  window.addEventListener('pagehide', () => handleEventFlush('page_hide'));
@@ -3778,6 +3793,97 @@ async function modifyPage(Coframe, configResult, projectResult, analytics) {
3778
3793
  setupFlushInterval(Coframe);
3779
3794
  }
3780
3795
 
3796
+ let startRequested = false;
3797
+ let replayScriptUrl;
3798
+ const COMPILED_REPLAY_SCRIPT_URL = `${"https://edge.cofra.me"}/replay/latest.js`;
3799
+ function configureReplayScriptUrl(url) {
3800
+ replayScriptUrl = (url === null || url === void 0 ? void 0 : url.trim()) || undefined;
3801
+ }
3802
+ function createMonitoringPayload(coframe, monitoringType, metadata) {
3803
+ return {
3804
+ event_id: generateUUID(),
3805
+ monitoring_type: monitoringType,
3806
+ local_time: new Date().toISOString(),
3807
+ project_id: coframe.projectId,
3808
+ session_id: coframe.sessionId,
3809
+ url: location.href,
3810
+ org_id: coframe.orgId,
3811
+ metadata,
3812
+ };
3813
+ }
3814
+ function reportStartFailure(coframe, reason) {
3815
+ try {
3816
+ queueMonitoringEvent(coframe, createMonitoringPayload(coframe, 'replay_start_failed', { reason }));
3817
+ }
3818
+ catch (_a) { }
3819
+ }
3820
+ function createStartContext(coframe, settings, forceSample) {
3821
+ return {
3822
+ projectId: coframe.projectId,
3823
+ ingestUrl: resolveIngestUrl("https://ingest.app.coframe.com/ingest/v2"),
3824
+ settings,
3825
+ forceSample,
3826
+ getPageViewId: () => coframe.sessionId,
3827
+ getUserToken: () => coframe.userToken,
3828
+ getVariantIds: () => { var _a; return ((_a = coframe.experimentInfo) !== null && _a !== void 0 ? _a : []).map((experiment) => experiment.variant_id); },
3829
+ canSend: shouldSendEvents,
3830
+ onEventsFlushed: (callback) => {
3831
+ if (queuedEvents.length === 0 && queuedSessions.length === 0) {
3832
+ callback();
3833
+ }
3834
+ else {
3835
+ onEventsFlushed(callback);
3836
+ }
3837
+ },
3838
+ report: (type, metadata) => queueMonitoringEvent(coframe, createMonitoringPayload(coframe, type, metadata)),
3839
+ };
3840
+ }
3841
+ function startReplayIfEnabled(coframe, project, nonce) {
3842
+ var _a;
3843
+ try {
3844
+ const settings = (_a = project.settings) === null || _a === void 0 ? void 0 : _a.session_replay;
3845
+ if ((settings === null || settings === void 0 ? void 0 : settings.enabled) !== true ||
3846
+ typeof CompressionStream !== 'function' ||
3847
+ !shouldSendEvents() ||
3848
+ startRequested) {
3849
+ return;
3850
+ }
3851
+ let forceSample = false;
3852
+ try {
3853
+ forceSample = localStorage.getItem('coframe.enableRecording') === 'true';
3854
+ }
3855
+ catch (_b) { }
3856
+ const script = document.createElement('script');
3857
+ script.id = 'coframe-replay-script';
3858
+ script.async = true;
3859
+ script.src = replayScriptUrl !== null && replayScriptUrl !== void 0 ? replayScriptUrl : COMPILED_REPLAY_SCRIPT_URL;
3860
+ if (nonce)
3861
+ script.nonce = nonce;
3862
+ script.onload = () => {
3863
+ try {
3864
+ const replay = window.CoframeReplay;
3865
+ if ((replay === null || replay === void 0 ? void 0 : replay.protocolVersion) === 1) {
3866
+ replay.start(createStartContext(coframe, settings, forceSample));
3867
+ }
3868
+ else {
3869
+ reportStartFailure(coframe, 'incompatible_protocol');
3870
+ }
3871
+ }
3872
+ catch (error) {
3873
+ reportStartFailure(coframe, error instanceof Error ? error.message : String(error));
3874
+ }
3875
+ };
3876
+ script.onerror = () => {
3877
+ reportStartFailure(coframe, 'script_load_failed');
3878
+ };
3879
+ startRequested = true;
3880
+ document.head.appendChild(script);
3881
+ }
3882
+ catch (error) {
3883
+ reportStartFailure(coframe, error instanceof Error ? error.message : String(error));
3884
+ }
3885
+ }
3886
+
3781
3887
  const TIMEOUT_MAX_MS = 5000;
3782
3888
  const MAX_NOT_RENDERED_REASON_CHARS = 200;
3783
3889
  function isReportedSize(size) {
@@ -3845,6 +3951,7 @@ async function resolveConfig(Coframe, revealPage) {
3845
3951
  const executionStartTime = new Date();
3846
3952
  Coframe.batchSize = config.batchSize || 32;
3847
3953
  configureIngestUrl(config.ingestUrl);
3954
+ configureReplayScriptUrl(config.replayScriptUrl);
3848
3955
  const gracefulExit = (msg) => {
3849
3956
  console.warn(`[Coframe] ${msg}`);
3850
3957
  revealPage();
@@ -4155,6 +4262,12 @@ async function init(options) {
4155
4262
  return;
4156
4263
  const analytics = setupTracking(Coframe, projectResult, configResult.previewVariantId, configResult.executionStartTime, configResult.config.nonce, configResult.config.waitForCookiesEnabled, identity.onConsentChange);
4157
4264
  await modifyPage(Coframe, configResult, projectResult, analytics);
4265
+ try {
4266
+ startReplayIfEnabled(Coframe, projectResult.project, configResult.config.nonce);
4267
+ }
4268
+ catch (error) {
4269
+ console.warn('[Coframe] Failed to start session replay', error);
4270
+ }
4158
4271
  }
4159
4272
  Coframe.debug = {
4160
4273
  getSelector,
@@ -3229,6 +3229,20 @@ function observeUrlChanges(baseUrlMatch, onUrlChange) {
3229
3229
  patchHistoryMethod('replaceState');
3230
3230
  }
3231
3231
 
3232
+ const callbacks = [];
3233
+ function onEventsFlushed(callback) {
3234
+ callbacks.push(callback);
3235
+ }
3236
+ function notifyEventsFlushed() {
3237
+ const pendingCallbacks = callbacks.splice(0);
3238
+ for (const callback of pendingCallbacks) {
3239
+ try {
3240
+ callback();
3241
+ }
3242
+ catch (_a) { }
3243
+ }
3244
+ }
3245
+
3232
3246
  function startSession(Coframe, cookiesEnabled, executionStartTime, personalizationHeaders, sessionContext, pageUrl = getCurrentPageUrl()) {
3233
3247
  const sessionPayload = [
3234
3248
  Coframe,
@@ -3503,6 +3517,7 @@ function setupListeners(Coframe, executionStartTime) {
3503
3517
  personalizationHeaders: Coframe.personalizationHeaders,
3504
3518
  });
3505
3519
  flushQueuedEvents(Coframe.projectId);
3520
+ notifyEventsFlushed();
3506
3521
  }, 1);
3507
3522
  window.addEventListener('beforeunload', () => handleEventFlush('before_unload'));
3508
3523
  window.addEventListener('pagehide', () => handleEventFlush('page_hide'));
@@ -3776,6 +3791,97 @@ async function modifyPage(Coframe, configResult, projectResult, analytics) {
3776
3791
  setupFlushInterval(Coframe);
3777
3792
  }
3778
3793
 
3794
+ let startRequested = false;
3795
+ let replayScriptUrl;
3796
+ const COMPILED_REPLAY_SCRIPT_URL = `${"https://edge.cofra.me"}/replay/latest.js`;
3797
+ function configureReplayScriptUrl(url) {
3798
+ replayScriptUrl = (url === null || url === void 0 ? void 0 : url.trim()) || undefined;
3799
+ }
3800
+ function createMonitoringPayload(coframe, monitoringType, metadata) {
3801
+ return {
3802
+ event_id: generateUUID(),
3803
+ monitoring_type: monitoringType,
3804
+ local_time: new Date().toISOString(),
3805
+ project_id: coframe.projectId,
3806
+ session_id: coframe.sessionId,
3807
+ url: location.href,
3808
+ org_id: coframe.orgId,
3809
+ metadata,
3810
+ };
3811
+ }
3812
+ function reportStartFailure(coframe, reason) {
3813
+ try {
3814
+ queueMonitoringEvent(coframe, createMonitoringPayload(coframe, 'replay_start_failed', { reason }));
3815
+ }
3816
+ catch (_a) { }
3817
+ }
3818
+ function createStartContext(coframe, settings, forceSample) {
3819
+ return {
3820
+ projectId: coframe.projectId,
3821
+ ingestUrl: resolveIngestUrl("https://ingest.app.coframe.com/ingest/v2"),
3822
+ settings,
3823
+ forceSample,
3824
+ getPageViewId: () => coframe.sessionId,
3825
+ getUserToken: () => coframe.userToken,
3826
+ getVariantIds: () => { var _a; return ((_a = coframe.experimentInfo) !== null && _a !== void 0 ? _a : []).map((experiment) => experiment.variant_id); },
3827
+ canSend: shouldSendEvents,
3828
+ onEventsFlushed: (callback) => {
3829
+ if (queuedEvents.length === 0 && queuedSessions.length === 0) {
3830
+ callback();
3831
+ }
3832
+ else {
3833
+ onEventsFlushed(callback);
3834
+ }
3835
+ },
3836
+ report: (type, metadata) => queueMonitoringEvent(coframe, createMonitoringPayload(coframe, type, metadata)),
3837
+ };
3838
+ }
3839
+ function startReplayIfEnabled(coframe, project, nonce) {
3840
+ var _a;
3841
+ try {
3842
+ const settings = (_a = project.settings) === null || _a === void 0 ? void 0 : _a.session_replay;
3843
+ if ((settings === null || settings === void 0 ? void 0 : settings.enabled) !== true ||
3844
+ typeof CompressionStream !== 'function' ||
3845
+ !shouldSendEvents() ||
3846
+ startRequested) {
3847
+ return;
3848
+ }
3849
+ let forceSample = false;
3850
+ try {
3851
+ forceSample = localStorage.getItem('coframe.enableRecording') === 'true';
3852
+ }
3853
+ catch (_b) { }
3854
+ const script = document.createElement('script');
3855
+ script.id = 'coframe-replay-script';
3856
+ script.async = true;
3857
+ script.src = replayScriptUrl !== null && replayScriptUrl !== void 0 ? replayScriptUrl : COMPILED_REPLAY_SCRIPT_URL;
3858
+ if (nonce)
3859
+ script.nonce = nonce;
3860
+ script.onload = () => {
3861
+ try {
3862
+ const replay = window.CoframeReplay;
3863
+ if ((replay === null || replay === void 0 ? void 0 : replay.protocolVersion) === 1) {
3864
+ replay.start(createStartContext(coframe, settings, forceSample));
3865
+ }
3866
+ else {
3867
+ reportStartFailure(coframe, 'incompatible_protocol');
3868
+ }
3869
+ }
3870
+ catch (error) {
3871
+ reportStartFailure(coframe, error instanceof Error ? error.message : String(error));
3872
+ }
3873
+ };
3874
+ script.onerror = () => {
3875
+ reportStartFailure(coframe, 'script_load_failed');
3876
+ };
3877
+ startRequested = true;
3878
+ document.head.appendChild(script);
3879
+ }
3880
+ catch (error) {
3881
+ reportStartFailure(coframe, error instanceof Error ? error.message : String(error));
3882
+ }
3883
+ }
3884
+
3779
3885
  const TIMEOUT_MAX_MS = 5000;
3780
3886
  const MAX_NOT_RENDERED_REASON_CHARS = 200;
3781
3887
  function isReportedSize(size) {
@@ -3843,6 +3949,7 @@ async function resolveConfig(Coframe, revealPage) {
3843
3949
  const executionStartTime = new Date();
3844
3950
  Coframe.batchSize = config.batchSize || 32;
3845
3951
  configureIngestUrl(config.ingestUrl);
3952
+ configureReplayScriptUrl(config.replayScriptUrl);
3846
3953
  const gracefulExit = (msg) => {
3847
3954
  console.warn(`[Coframe] ${msg}`);
3848
3955
  revealPage();
@@ -4153,6 +4260,12 @@ async function init(options) {
4153
4260
  return;
4154
4261
  const analytics = setupTracking(Coframe, projectResult, configResult.previewVariantId, configResult.executionStartTime, configResult.config.nonce, configResult.config.waitForCookiesEnabled, identity.onConsentChange);
4155
4262
  await modifyPage(Coframe, configResult, projectResult, analytics);
4263
+ try {
4264
+ startReplayIfEnabled(Coframe, projectResult.project, configResult.config.nonce);
4265
+ }
4266
+ catch (error) {
4267
+ console.warn('[Coframe] Failed to start session replay', error);
4268
+ }
4156
4269
  }
4157
4270
  Coframe.debug = {
4158
4271
  getSelector,
@@ -74,6 +74,15 @@ type ModalTrigger = 'exit' | 'scroll' | 'enter' | 'timer';
74
74
  interface AnalyticsCallback {
75
75
  callback: string;
76
76
  }
77
+ interface SessionReplaySettings {
78
+ enabled: boolean;
79
+ sample_rate: number;
80
+ mask_text_selector: string;
81
+ block_selector: string;
82
+ url_deny_patterns: string[];
83
+ max_session_minutes: number;
84
+ max_session_bytes: number;
85
+ }
77
86
  interface TreatmentData {
78
87
  impressionId: string;
79
88
  userToken: string;
@@ -141,6 +150,7 @@ interface ProjectSettings {
141
150
  bot_cloaking_enabled?: boolean;
142
151
  bot_score_threshold?: number;
143
152
  analytics_callback?: AnalyticsCallback;
153
+ session_replay?: SessionReplaySettings | null;
144
154
  }
145
155
  interface Project {
146
156
  org_id: string;
@@ -252,6 +262,7 @@ type CoframeConfig = {
252
262
  currentUrl?: string;
253
263
  projectUrl?: string;
254
264
  ingestUrl?: string;
265
+ replayScriptUrl?: string;
255
266
  referrer?: string;
256
267
  userContext?: Record<string, unknown>;
257
268
  nonce?: string;
@@ -93,6 +93,15 @@ type ModalTrigger = 'exit' | 'scroll' | 'enter' | 'timer';
93
93
  interface AnalyticsCallback {
94
94
  callback: string;
95
95
  }
96
+ interface SessionReplaySettings {
97
+ enabled: boolean;
98
+ sample_rate: number;
99
+ mask_text_selector: string;
100
+ block_selector: string;
101
+ url_deny_patterns: string[];
102
+ max_session_minutes: number;
103
+ max_session_bytes: number;
104
+ }
96
105
  interface TreatmentData {
97
106
  impressionId: string;
98
107
  userToken: string;
@@ -160,6 +169,7 @@ interface ProjectSettings {
160
169
  bot_cloaking_enabled?: boolean;
161
170
  bot_score_threshold?: number;
162
171
  analytics_callback?: AnalyticsCallback;
172
+ session_replay?: SessionReplaySettings | null;
163
173
  }
164
174
  interface Project {
165
175
  org_id: string;
@@ -271,6 +281,7 @@ type CoframeConfig = {
271
281
  currentUrl?: string;
272
282
  projectUrl?: string;
273
283
  ingestUrl?: string;
284
+ replayScriptUrl?: string;
274
285
  referrer?: string;
275
286
  userContext?: Record<string, unknown>;
276
287
  nonce?: string;
@@ -74,6 +74,15 @@ type ModalTrigger = 'exit' | 'scroll' | 'enter' | 'timer';
74
74
  interface AnalyticsCallback {
75
75
  callback: string;
76
76
  }
77
+ interface SessionReplaySettings {
78
+ enabled: boolean;
79
+ sample_rate: number;
80
+ mask_text_selector: string;
81
+ block_selector: string;
82
+ url_deny_patterns: string[];
83
+ max_session_minutes: number;
84
+ max_session_bytes: number;
85
+ }
77
86
  interface Audience {
78
87
  selector: ActivationCondition;
79
88
  }
@@ -107,6 +116,7 @@ interface ProjectSettings {
107
116
  bot_cloaking_enabled?: boolean;
108
117
  bot_score_threshold?: number;
109
118
  analytics_callback?: AnalyticsCallback;
119
+ session_replay?: SessionReplaySettings | null;
110
120
  }
111
121
  interface Project {
112
122
  org_id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coframe/sdk",
3
- "version": "1.1.25",
3
+ "version": "1.1.26",
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",
@@ -98,6 +98,8 @@
98
98
  }
99
99
  },
100
100
  "dependencies": {
101
+ "@rrweb/record": "2.1.0",
102
+ "fflate": "0.8.3",
101
103
  "typescript": "^6.0.2"
102
104
  },
103
105
  "devDependencies": {
@@ -136,4 +138,4 @@
136
138
  "ua-parser-js": "^2.0.10"
137
139
  },
138
140
  "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
139
- }
141
+ }