@coframe/sdk 1.1.39 → 1.1.41

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.
@@ -1125,7 +1125,7 @@ function warnEventsNotSent() {
1125
1125
  }
1126
1126
  }
1127
1127
 
1128
- const SDK_VERSION = '2.8.0';
1128
+ const SDK_VERSION = '2.9.0';
1129
1129
  const queuedEvents = [];
1130
1130
  const queuedMonitoringEvents = [];
1131
1131
  const queuedSessions = [];
@@ -1230,7 +1230,7 @@ function flushQueuedEvents(projectId) {
1230
1230
  queuedSessions.length = 0;
1231
1231
  }
1232
1232
  }
1233
- function queueEvent(coframe, sessionInfo) {
1233
+ function queueEvent(coframe, sessionInfo, heatmap) {
1234
1234
  const { projectId, sessionId, eventType, orgId, userToken } = sessionInfo;
1235
1235
  const data = {
1236
1236
  type: eventType,
@@ -1242,13 +1242,19 @@ function queueEvent(coframe, sessionInfo) {
1242
1242
  user_token: userToken,
1243
1243
  };
1244
1244
  const seg = sessionInfo.personalizationHeaders;
1245
- const metadata = seg ? { personalization_headers: { ...seg } } : {};
1246
- if (eventType === 'page_view') {
1247
- data.content = { universal_selector: {}, metadata };
1248
- }
1249
- else {
1250
- data.content = { metadata };
1251
- }
1245
+ const metadata = seg
1246
+ ? { personalization_headers: { ...seg } }
1247
+ : {};
1248
+ if (heatmap === null || heatmap === void 0 ? void 0 : heatmap.hm)
1249
+ metadata.hm = heatmap.hm;
1250
+ data.content =
1251
+ eventType === 'page_view'
1252
+ ? {
1253
+ universal_selector: {},
1254
+ metadata,
1255
+ screen_size: { width: window.innerWidth, height: window.innerHeight },
1256
+ }
1257
+ : { metadata, ...heatmap === null || heatmap === void 0 ? void 0 : heatmap.content };
1252
1258
  queueEventForBatchedSending(coframe, data);
1253
1259
  }
1254
1260
  function queueVariantViewEvent(coframe, { projectId, sessionId, orgId, userToken, experimentId, variantId, }) {
@@ -1263,7 +1269,7 @@ function queueVariantViewEvent(coframe, { projectId, sessionId, orgId, userToken
1263
1269
  user_token: userToken,
1264
1270
  });
1265
1271
  }
1266
- function sendPageEngagementEvent(coframe, eventType, event, { projectId, sessionId, duration = 0, userToken, personalizationHeaders, }) {
1272
+ function sendPageEngagementEvent(coframe, eventType, event, { projectId, sessionId, orgId, duration = 0, userToken, personalizationHeaders, collect, }) {
1267
1273
  var _a;
1268
1274
  if (event
1269
1275
  .composedPath()
@@ -1286,6 +1292,7 @@ function sendPageEngagementEvent(coframe, eventType, event, { projectId, session
1286
1292
  session_id: sessionId,
1287
1293
  time: new Date(),
1288
1294
  url: getCurrentPageUrl(),
1295
+ org_id: orgId,
1289
1296
  user_token: userToken,
1290
1297
  content: {
1291
1298
  universal_selector: getSelector(targetElement, {
@@ -1308,7 +1315,10 @@ function sendPageEngagementEvent(coframe, eventType, event, { projectId, session
1308
1315
  }),
1309
1316
  },
1310
1317
  };
1311
- queueEventForBatchedSending(coframe, data, Boolean(metricIds === null || metricIds === void 0 ? void 0 : metricIds.length));
1318
+ const isConversion = Boolean(metricIds === null || metricIds === void 0 ? void 0 : metricIds.length);
1319
+ if (!(collect === null || collect === void 0 ? void 0 : collect(data, realEventTarget, event, isConversion))) {
1320
+ queueEventForBatchedSending(coframe, data, isConversion);
1321
+ }
1312
1322
  const processingDuration = performance.now() - startTime;
1313
1323
  if (coframe.status) {
1314
1324
  if (!coframe.status.events) {
@@ -3382,6 +3392,235 @@ function notifyEventsFlushed() {
3382
3392
  }
3383
3393
  }
3384
3394
 
3395
+ const DEFAULT_BLOCK_SELECTOR = '[data-cf-block],.cf-block';
3396
+ const INTERACTIVE_SELECTOR = 'a,button,input,select,textarea,label,summary,[href],[onclick],[contenteditable],[tabindex]:not([tabindex="-1"]),[role=button],[role=link],[role=tab],[role=checkbox],[role=menuitem]';
3397
+ const HOLD_MS = 1000;
3398
+ const RAGE_WINDOW_MS = 700;
3399
+ const RAGE_RADIUS_PX = 30;
3400
+ const RAGE_CLICKS = 3;
3401
+ let enabled = false;
3402
+ let allPages = false;
3403
+ let coframe = null;
3404
+ let blockSelector = DEFAULT_BLOCK_SELECTOR;
3405
+ const held = [];
3406
+ let recent = [];
3407
+ let lastActivity = 0;
3408
+ let observer = null;
3409
+ let pageView = '';
3410
+ let maxDepth = 0;
3411
+ let engagedMs = 0;
3412
+ let visibleSince = null;
3413
+ let navigated = false;
3414
+ function meta(name) {
3415
+ const tag = document.querySelector(`meta[name="coframe:${name}"]`);
3416
+ return (tag === null || tag === void 0 ? void 0 : tag.getAttribute('content')) || undefined;
3417
+ }
3418
+ function heatmapActive() {
3419
+ return enabled && (allPages || !!meta('page'));
3420
+ }
3421
+ function pageFields(withHeight) {
3422
+ const fields = {
3423
+ page: meta('page'),
3424
+ ver: meta('version'),
3425
+ variant: meta('variant'),
3426
+ row: meta('row'),
3427
+ };
3428
+ if (withHeight)
3429
+ fields.doc_h = document.documentElement.scrollHeight;
3430
+ return fields;
3431
+ }
3432
+ function round3(value) {
3433
+ return Math.round(Math.min(1, Math.max(0, value)) * 1000) / 1000;
3434
+ }
3435
+ function markActivity() {
3436
+ lastActivity = Date.now();
3437
+ }
3438
+ function viewportBottom() {
3439
+ return Math.round(window.scrollY + window.innerHeight);
3440
+ }
3441
+ function restartCounters(depth) {
3442
+ maxDepth = depth;
3443
+ engagedMs = 0;
3444
+ visibleSince = document.hidden ? null : Date.now();
3445
+ }
3446
+ function syncPageView() {
3447
+ if (!coframe || coframe.sessionId === pageView)
3448
+ return;
3449
+ pageView = coframe.sessionId;
3450
+ if (navigated)
3451
+ navigated = false;
3452
+ else
3453
+ restartCounters(viewportBottom());
3454
+ }
3455
+ function onScroll() {
3456
+ markActivity();
3457
+ syncPageView();
3458
+ maxDepth = Math.max(maxDepth, viewportBottom());
3459
+ }
3460
+ function onVisibility() {
3461
+ if (document.hidden) {
3462
+ if (visibleSince !== null)
3463
+ engagedMs += Date.now() - visibleSince;
3464
+ visibleSince = null;
3465
+ releaseHeldClicks();
3466
+ }
3467
+ else if (visibleSince === null) {
3468
+ visibleSince = Date.now();
3469
+ }
3470
+ }
3471
+ function readBlockSelector(selector) {
3472
+ if (typeof selector !== 'string' || !selector)
3473
+ return DEFAULT_BLOCK_SELECTOR;
3474
+ try {
3475
+ document.createElement('div').matches(selector);
3476
+ return selector;
3477
+ }
3478
+ catch (_a) {
3479
+ return DEFAULT_BLOCK_SELECTOR;
3480
+ }
3481
+ }
3482
+ function startHeatmaps(target, project) {
3483
+ var _a, _b;
3484
+ const settings = (_a = project === null || project === void 0 ? void 0 : project.settings) === null || _a === void 0 ? void 0 : _a.heatmaps;
3485
+ if (enabled || !(settings === null || settings === void 0 ? void 0 : settings.enabled))
3486
+ return;
3487
+ enabled = true;
3488
+ allPages = !!settings.all_pages;
3489
+ blockSelector = readBlockSelector((_b = project.settings.session_replay) === null || _b === void 0 ? void 0 : _b.block_selector);
3490
+ coframe = target;
3491
+ syncPageView();
3492
+ document.addEventListener('scroll', onScroll, {
3493
+ capture: true,
3494
+ passive: true,
3495
+ });
3496
+ document.addEventListener('visibilitychange', onVisibility);
3497
+ window.addEventListener('pagehide', releaseHeldClicks);
3498
+ window.addEventListener('beforeunload', releaseHeldClicks);
3499
+ }
3500
+ function leaveFields() {
3501
+ if (!heatmapActive())
3502
+ return undefined;
3503
+ syncPageView();
3504
+ if (navigated)
3505
+ return undefined;
3506
+ return {
3507
+ content: {
3508
+ scroll_depth: Math.max(maxDepth, viewportBottom()),
3509
+ 'duration-ms': Math.round(engagedMs + (visibleSince === null ? 0 : Date.now() - visibleSince)),
3510
+ },
3511
+ hm: pageFields(true),
3512
+ };
3513
+ }
3514
+ function pageLeaveFields() {
3515
+ if (!enabled)
3516
+ return undefined;
3517
+ const fields = leaveFields();
3518
+ restartCounters(0);
3519
+ navigated = true;
3520
+ return fields;
3521
+ }
3522
+ function watchMutations() {
3523
+ if (observer || typeof MutationObserver !== 'function')
3524
+ return;
3525
+ observer = new MutationObserver(markActivity);
3526
+ observer.observe(document.documentElement, {
3527
+ attributes: true,
3528
+ characterData: true,
3529
+ childList: true,
3530
+ subtree: true,
3531
+ });
3532
+ }
3533
+ function settle(click) {
3534
+ const index = held.indexOf(click);
3535
+ if (index < 0)
3536
+ return;
3537
+ held.splice(index, 1);
3538
+ if (click.hm.kind !== 'rage') {
3539
+ const dead = click.candidate &&
3540
+ lastActivity < click.t &&
3541
+ document.activeElement === click.focused &&
3542
+ location.href === click.href;
3543
+ click.hm.kind = dead ? 'dead' : 'click';
3544
+ }
3545
+ if (coframe)
3546
+ queueEventForBatchedSending(coframe, click.data);
3547
+ if (observer && !held.some((h) => h.candidate)) {
3548
+ observer.disconnect();
3549
+ observer = null;
3550
+ }
3551
+ }
3552
+ function releaseHeldClicks() {
3553
+ for (const click of held.splice(0)) {
3554
+ if (click.hm.kind === undefined)
3555
+ click.hm.kind = null;
3556
+ if (coframe)
3557
+ queueEventForBatchedSending(coframe, click.data);
3558
+ }
3559
+ if (observer) {
3560
+ observer.disconnect();
3561
+ observer = null;
3562
+ }
3563
+ }
3564
+ function copyKey(node, id) {
3565
+ const items = [];
3566
+ for (let at = node; at; at = at.parentElement) {
3567
+ const item = at.getAttribute('data-el-item');
3568
+ if (item)
3569
+ items.unshift(item);
3570
+ }
3571
+ return items.length ? `${id}~${items.join('.')}` : id;
3572
+ }
3573
+ function collectClick(data, target, event, isConversion) {
3574
+ if (!target.closest || target.closest(blockSelector))
3575
+ return false;
3576
+ const content = data.content;
3577
+ const position = content.position;
3578
+ const hm = pageFields(true);
3579
+ const anchor = target.closest('[data-el]');
3580
+ if (anchor) {
3581
+ const id = anchor.getAttribute('data-el');
3582
+ hm.el = id ? copyKey(anchor, id) : undefined;
3583
+ const rect = anchor.getBoundingClientRect();
3584
+ if (rect.width > 0 && rect.height > 0) {
3585
+ hm.fx = round3((event.clientX - rect.left) / rect.width);
3586
+ hm.fy = round3((event.clientY - rect.top) / rect.height);
3587
+ }
3588
+ }
3589
+ content.metadata = {
3590
+ ...content.metadata,
3591
+ hm,
3592
+ };
3593
+ const now = Date.now();
3594
+ const click = {
3595
+ data,
3596
+ hm,
3597
+ x: position.x,
3598
+ y: position.y,
3599
+ t: now,
3600
+ candidate: !target.closest(INTERACTIVE_SELECTOR),
3601
+ focused: document.activeElement,
3602
+ href: location.href,
3603
+ };
3604
+ recent = recent.filter((r) => now - r.t <= RAGE_WINDOW_MS);
3605
+ recent.push(click);
3606
+ const cluster = recent.filter((r) => Math.abs(r.x - click.x) <= RAGE_RADIUS_PX &&
3607
+ Math.abs(r.y - click.y) <= RAGE_RADIUS_PX);
3608
+ if (cluster.length >= RAGE_CLICKS) {
3609
+ for (const r of cluster)
3610
+ r.hm.kind = 'rage';
3611
+ }
3612
+ if (isConversion) {
3613
+ if (hm.kind === undefined)
3614
+ hm.kind = null;
3615
+ return false;
3616
+ }
3617
+ held.push(click);
3618
+ if (click.candidate)
3619
+ watchMutations();
3620
+ setTimeout(() => settle(click), HOLD_MS);
3621
+ return true;
3622
+ }
3623
+
3385
3624
  function startSession(Coframe, cookiesEnabled, executionStartTime, personalizationHeaders, sessionContext, pageUrl = getCurrentPageUrl()) {
3386
3625
  const sessionPayload = [
3387
3626
  Coframe,
@@ -3408,7 +3647,7 @@ function startSession(Coframe, cookiesEnabled, executionStartTime, personalizati
3408
3647
  orgId: Coframe.orgId,
3409
3648
  userToken: Coframe.userToken,
3410
3649
  personalizationHeaders,
3411
- });
3650
+ }, heatmapActive() ? { hm: pageFields(false) } : undefined);
3412
3651
  }
3413
3652
  function reportInitTiming(Coframe, project, integrationStartTime, executionStartTime) {
3414
3653
  var _a;
@@ -3641,20 +3880,15 @@ function setupListeners(Coframe, executionStartTime) {
3641
3880
  sendPageEngagementEvent(Coframe, 'click', event, {
3642
3881
  projectId: Coframe.projectId,
3643
3882
  sessionId: Coframe.sessionId,
3883
+ orgId: Coframe.orgId,
3644
3884
  userToken: Coframe.userToken,
3645
3885
  personalizationHeaders: Coframe.personalizationHeaders,
3886
+ collect: heatmapActive() ? collectClick : undefined,
3646
3887
  });
3647
3888
  };
3648
3889
  document.addEventListener('click', handleClick, true);
3649
3890
  const handleEventFlush = debounce((eventType) => {
3650
- queueEvent(Coframe, {
3651
- projectId: Coframe.projectId,
3652
- sessionId: Coframe.sessionId,
3653
- eventType: eventType,
3654
- orgId: Coframe.orgId,
3655
- userToken: Coframe.userToken,
3656
- personalizationHeaders: Coframe.personalizationHeaders,
3657
- });
3891
+ queueLeaveEvent(Coframe, eventType, eventType === 'tab_show' ? undefined : leaveFields());
3658
3892
  flushQueuedEvents(Coframe.projectId);
3659
3893
  notifyEventsFlushed();
3660
3894
  }, 1);
@@ -3666,11 +3900,27 @@ function setupListeners(Coframe, executionStartTime) {
3666
3900
  handleEventFlush(eventType);
3667
3901
  });
3668
3902
  }
3903
+ function queueLeaveEvent(Coframe, eventType, fields) {
3904
+ queueEvent(Coframe, {
3905
+ projectId: Coframe.projectId,
3906
+ sessionId: Coframe.sessionId,
3907
+ eventType,
3908
+ orgId: Coframe.orgId,
3909
+ userToken: Coframe.userToken,
3910
+ personalizationHeaders: Coframe.personalizationHeaders,
3911
+ }, fields);
3912
+ }
3913
+ function sendPageLeave(Coframe) {
3914
+ const fields = pageLeaveFields();
3915
+ if (fields)
3916
+ queueLeaveEvent(Coframe, 'page_leave', fields);
3917
+ }
3669
3918
  function setupTracking(Coframe, projectResult, previewVariantId, executionStartTime, nonce, waitForCookiesEnabled, onConsentChange) {
3670
3919
  var _a, _b;
3671
3920
  const logger = Coframe.logger;
3672
3921
  const { project } = projectResult;
3673
3922
  setTrackOnlyActivatedFrames(!!((_a = project === null || project === void 0 ? void 0 : project.settings) === null || _a === void 0 ? void 0 : _a.track_only_activated_frames));
3923
+ startHeatmaps(Coframe, project);
3674
3924
  const analytics = createTreatmentAnalytics(Coframe, previewVariantId);
3675
3925
  setupListeners(Coframe, executionStartTime);
3676
3926
  const managedAnalyticsCallback = (_b = project === null || project === void 0 ? void 0 : project.settings) === null || _b === void 0 ? void 0 : _b.analytics_callback;
@@ -3786,6 +4036,7 @@ function setupSPANavigation(Coframe, project, config, previewVariantId, isSessio
3786
4036
  let navigationAbortController = null;
3787
4037
  observeUrlChanges(false, async (newPageUrl, _oldPageUrl) => {
3788
4038
  var _a;
4039
+ sendPageLeave(Coframe);
3789
4040
  if (navigationAbortController)
3790
4041
  navigationAbortController.abort();
3791
4042
  navigationAbortController = new AbortController();
@@ -1123,7 +1123,7 @@ function warnEventsNotSent() {
1123
1123
  }
1124
1124
  }
1125
1125
 
1126
- const SDK_VERSION = '2.8.0';
1126
+ const SDK_VERSION = '2.9.0';
1127
1127
  const queuedEvents = [];
1128
1128
  const queuedMonitoringEvents = [];
1129
1129
  const queuedSessions = [];
@@ -1228,7 +1228,7 @@ function flushQueuedEvents(projectId) {
1228
1228
  queuedSessions.length = 0;
1229
1229
  }
1230
1230
  }
1231
- function queueEvent(coframe, sessionInfo) {
1231
+ function queueEvent(coframe, sessionInfo, heatmap) {
1232
1232
  const { projectId, sessionId, eventType, orgId, userToken } = sessionInfo;
1233
1233
  const data = {
1234
1234
  type: eventType,
@@ -1240,13 +1240,19 @@ function queueEvent(coframe, sessionInfo) {
1240
1240
  user_token: userToken,
1241
1241
  };
1242
1242
  const seg = sessionInfo.personalizationHeaders;
1243
- const metadata = seg ? { personalization_headers: { ...seg } } : {};
1244
- if (eventType === 'page_view') {
1245
- data.content = { universal_selector: {}, metadata };
1246
- }
1247
- else {
1248
- data.content = { metadata };
1249
- }
1243
+ const metadata = seg
1244
+ ? { personalization_headers: { ...seg } }
1245
+ : {};
1246
+ if (heatmap === null || heatmap === void 0 ? void 0 : heatmap.hm)
1247
+ metadata.hm = heatmap.hm;
1248
+ data.content =
1249
+ eventType === 'page_view'
1250
+ ? {
1251
+ universal_selector: {},
1252
+ metadata,
1253
+ screen_size: { width: window.innerWidth, height: window.innerHeight },
1254
+ }
1255
+ : { metadata, ...heatmap === null || heatmap === void 0 ? void 0 : heatmap.content };
1250
1256
  queueEventForBatchedSending(coframe, data);
1251
1257
  }
1252
1258
  function queueVariantViewEvent(coframe, { projectId, sessionId, orgId, userToken, experimentId, variantId, }) {
@@ -1261,7 +1267,7 @@ function queueVariantViewEvent(coframe, { projectId, sessionId, orgId, userToken
1261
1267
  user_token: userToken,
1262
1268
  });
1263
1269
  }
1264
- function sendPageEngagementEvent(coframe, eventType, event, { projectId, sessionId, duration = 0, userToken, personalizationHeaders, }) {
1270
+ function sendPageEngagementEvent(coframe, eventType, event, { projectId, sessionId, orgId, duration = 0, userToken, personalizationHeaders, collect, }) {
1265
1271
  var _a;
1266
1272
  if (event
1267
1273
  .composedPath()
@@ -1284,6 +1290,7 @@ function sendPageEngagementEvent(coframe, eventType, event, { projectId, session
1284
1290
  session_id: sessionId,
1285
1291
  time: new Date(),
1286
1292
  url: getCurrentPageUrl(),
1293
+ org_id: orgId,
1287
1294
  user_token: userToken,
1288
1295
  content: {
1289
1296
  universal_selector: getSelector(targetElement, {
@@ -1306,7 +1313,10 @@ function sendPageEngagementEvent(coframe, eventType, event, { projectId, session
1306
1313
  }),
1307
1314
  },
1308
1315
  };
1309
- queueEventForBatchedSending(coframe, data, Boolean(metricIds === null || metricIds === void 0 ? void 0 : metricIds.length));
1316
+ const isConversion = Boolean(metricIds === null || metricIds === void 0 ? void 0 : metricIds.length);
1317
+ if (!(collect === null || collect === void 0 ? void 0 : collect(data, realEventTarget, event, isConversion))) {
1318
+ queueEventForBatchedSending(coframe, data, isConversion);
1319
+ }
1310
1320
  const processingDuration = performance.now() - startTime;
1311
1321
  if (coframe.status) {
1312
1322
  if (!coframe.status.events) {
@@ -3380,6 +3390,235 @@ function notifyEventsFlushed() {
3380
3390
  }
3381
3391
  }
3382
3392
 
3393
+ const DEFAULT_BLOCK_SELECTOR = '[data-cf-block],.cf-block';
3394
+ const INTERACTIVE_SELECTOR = 'a,button,input,select,textarea,label,summary,[href],[onclick],[contenteditable],[tabindex]:not([tabindex="-1"]),[role=button],[role=link],[role=tab],[role=checkbox],[role=menuitem]';
3395
+ const HOLD_MS = 1000;
3396
+ const RAGE_WINDOW_MS = 700;
3397
+ const RAGE_RADIUS_PX = 30;
3398
+ const RAGE_CLICKS = 3;
3399
+ let enabled = false;
3400
+ let allPages = false;
3401
+ let coframe = null;
3402
+ let blockSelector = DEFAULT_BLOCK_SELECTOR;
3403
+ const held = [];
3404
+ let recent = [];
3405
+ let lastActivity = 0;
3406
+ let observer = null;
3407
+ let pageView = '';
3408
+ let maxDepth = 0;
3409
+ let engagedMs = 0;
3410
+ let visibleSince = null;
3411
+ let navigated = false;
3412
+ function meta(name) {
3413
+ const tag = document.querySelector(`meta[name="coframe:${name}"]`);
3414
+ return (tag === null || tag === void 0 ? void 0 : tag.getAttribute('content')) || undefined;
3415
+ }
3416
+ function heatmapActive() {
3417
+ return enabled && (allPages || !!meta('page'));
3418
+ }
3419
+ function pageFields(withHeight) {
3420
+ const fields = {
3421
+ page: meta('page'),
3422
+ ver: meta('version'),
3423
+ variant: meta('variant'),
3424
+ row: meta('row'),
3425
+ };
3426
+ if (withHeight)
3427
+ fields.doc_h = document.documentElement.scrollHeight;
3428
+ return fields;
3429
+ }
3430
+ function round3(value) {
3431
+ return Math.round(Math.min(1, Math.max(0, value)) * 1000) / 1000;
3432
+ }
3433
+ function markActivity() {
3434
+ lastActivity = Date.now();
3435
+ }
3436
+ function viewportBottom() {
3437
+ return Math.round(window.scrollY + window.innerHeight);
3438
+ }
3439
+ function restartCounters(depth) {
3440
+ maxDepth = depth;
3441
+ engagedMs = 0;
3442
+ visibleSince = document.hidden ? null : Date.now();
3443
+ }
3444
+ function syncPageView() {
3445
+ if (!coframe || coframe.sessionId === pageView)
3446
+ return;
3447
+ pageView = coframe.sessionId;
3448
+ if (navigated)
3449
+ navigated = false;
3450
+ else
3451
+ restartCounters(viewportBottom());
3452
+ }
3453
+ function onScroll() {
3454
+ markActivity();
3455
+ syncPageView();
3456
+ maxDepth = Math.max(maxDepth, viewportBottom());
3457
+ }
3458
+ function onVisibility() {
3459
+ if (document.hidden) {
3460
+ if (visibleSince !== null)
3461
+ engagedMs += Date.now() - visibleSince;
3462
+ visibleSince = null;
3463
+ releaseHeldClicks();
3464
+ }
3465
+ else if (visibleSince === null) {
3466
+ visibleSince = Date.now();
3467
+ }
3468
+ }
3469
+ function readBlockSelector(selector) {
3470
+ if (typeof selector !== 'string' || !selector)
3471
+ return DEFAULT_BLOCK_SELECTOR;
3472
+ try {
3473
+ document.createElement('div').matches(selector);
3474
+ return selector;
3475
+ }
3476
+ catch (_a) {
3477
+ return DEFAULT_BLOCK_SELECTOR;
3478
+ }
3479
+ }
3480
+ function startHeatmaps(target, project) {
3481
+ var _a, _b;
3482
+ const settings = (_a = project === null || project === void 0 ? void 0 : project.settings) === null || _a === void 0 ? void 0 : _a.heatmaps;
3483
+ if (enabled || !(settings === null || settings === void 0 ? void 0 : settings.enabled))
3484
+ return;
3485
+ enabled = true;
3486
+ allPages = !!settings.all_pages;
3487
+ blockSelector = readBlockSelector((_b = project.settings.session_replay) === null || _b === void 0 ? void 0 : _b.block_selector);
3488
+ coframe = target;
3489
+ syncPageView();
3490
+ document.addEventListener('scroll', onScroll, {
3491
+ capture: true,
3492
+ passive: true,
3493
+ });
3494
+ document.addEventListener('visibilitychange', onVisibility);
3495
+ window.addEventListener('pagehide', releaseHeldClicks);
3496
+ window.addEventListener('beforeunload', releaseHeldClicks);
3497
+ }
3498
+ function leaveFields() {
3499
+ if (!heatmapActive())
3500
+ return undefined;
3501
+ syncPageView();
3502
+ if (navigated)
3503
+ return undefined;
3504
+ return {
3505
+ content: {
3506
+ scroll_depth: Math.max(maxDepth, viewportBottom()),
3507
+ 'duration-ms': Math.round(engagedMs + (visibleSince === null ? 0 : Date.now() - visibleSince)),
3508
+ },
3509
+ hm: pageFields(true),
3510
+ };
3511
+ }
3512
+ function pageLeaveFields() {
3513
+ if (!enabled)
3514
+ return undefined;
3515
+ const fields = leaveFields();
3516
+ restartCounters(0);
3517
+ navigated = true;
3518
+ return fields;
3519
+ }
3520
+ function watchMutations() {
3521
+ if (observer || typeof MutationObserver !== 'function')
3522
+ return;
3523
+ observer = new MutationObserver(markActivity);
3524
+ observer.observe(document.documentElement, {
3525
+ attributes: true,
3526
+ characterData: true,
3527
+ childList: true,
3528
+ subtree: true,
3529
+ });
3530
+ }
3531
+ function settle(click) {
3532
+ const index = held.indexOf(click);
3533
+ if (index < 0)
3534
+ return;
3535
+ held.splice(index, 1);
3536
+ if (click.hm.kind !== 'rage') {
3537
+ const dead = click.candidate &&
3538
+ lastActivity < click.t &&
3539
+ document.activeElement === click.focused &&
3540
+ location.href === click.href;
3541
+ click.hm.kind = dead ? 'dead' : 'click';
3542
+ }
3543
+ if (coframe)
3544
+ queueEventForBatchedSending(coframe, click.data);
3545
+ if (observer && !held.some((h) => h.candidate)) {
3546
+ observer.disconnect();
3547
+ observer = null;
3548
+ }
3549
+ }
3550
+ function releaseHeldClicks() {
3551
+ for (const click of held.splice(0)) {
3552
+ if (click.hm.kind === undefined)
3553
+ click.hm.kind = null;
3554
+ if (coframe)
3555
+ queueEventForBatchedSending(coframe, click.data);
3556
+ }
3557
+ if (observer) {
3558
+ observer.disconnect();
3559
+ observer = null;
3560
+ }
3561
+ }
3562
+ function copyKey(node, id) {
3563
+ const items = [];
3564
+ for (let at = node; at; at = at.parentElement) {
3565
+ const item = at.getAttribute('data-el-item');
3566
+ if (item)
3567
+ items.unshift(item);
3568
+ }
3569
+ return items.length ? `${id}~${items.join('.')}` : id;
3570
+ }
3571
+ function collectClick(data, target, event, isConversion) {
3572
+ if (!target.closest || target.closest(blockSelector))
3573
+ return false;
3574
+ const content = data.content;
3575
+ const position = content.position;
3576
+ const hm = pageFields(true);
3577
+ const anchor = target.closest('[data-el]');
3578
+ if (anchor) {
3579
+ const id = anchor.getAttribute('data-el');
3580
+ hm.el = id ? copyKey(anchor, id) : undefined;
3581
+ const rect = anchor.getBoundingClientRect();
3582
+ if (rect.width > 0 && rect.height > 0) {
3583
+ hm.fx = round3((event.clientX - rect.left) / rect.width);
3584
+ hm.fy = round3((event.clientY - rect.top) / rect.height);
3585
+ }
3586
+ }
3587
+ content.metadata = {
3588
+ ...content.metadata,
3589
+ hm,
3590
+ };
3591
+ const now = Date.now();
3592
+ const click = {
3593
+ data,
3594
+ hm,
3595
+ x: position.x,
3596
+ y: position.y,
3597
+ t: now,
3598
+ candidate: !target.closest(INTERACTIVE_SELECTOR),
3599
+ focused: document.activeElement,
3600
+ href: location.href,
3601
+ };
3602
+ recent = recent.filter((r) => now - r.t <= RAGE_WINDOW_MS);
3603
+ recent.push(click);
3604
+ const cluster = recent.filter((r) => Math.abs(r.x - click.x) <= RAGE_RADIUS_PX &&
3605
+ Math.abs(r.y - click.y) <= RAGE_RADIUS_PX);
3606
+ if (cluster.length >= RAGE_CLICKS) {
3607
+ for (const r of cluster)
3608
+ r.hm.kind = 'rage';
3609
+ }
3610
+ if (isConversion) {
3611
+ if (hm.kind === undefined)
3612
+ hm.kind = null;
3613
+ return false;
3614
+ }
3615
+ held.push(click);
3616
+ if (click.candidate)
3617
+ watchMutations();
3618
+ setTimeout(() => settle(click), HOLD_MS);
3619
+ return true;
3620
+ }
3621
+
3383
3622
  function startSession(Coframe, cookiesEnabled, executionStartTime, personalizationHeaders, sessionContext, pageUrl = getCurrentPageUrl()) {
3384
3623
  const sessionPayload = [
3385
3624
  Coframe,
@@ -3406,7 +3645,7 @@ function startSession(Coframe, cookiesEnabled, executionStartTime, personalizati
3406
3645
  orgId: Coframe.orgId,
3407
3646
  userToken: Coframe.userToken,
3408
3647
  personalizationHeaders,
3409
- });
3648
+ }, heatmapActive() ? { hm: pageFields(false) } : undefined);
3410
3649
  }
3411
3650
  function reportInitTiming(Coframe, project, integrationStartTime, executionStartTime) {
3412
3651
  var _a;
@@ -3639,20 +3878,15 @@ function setupListeners(Coframe, executionStartTime) {
3639
3878
  sendPageEngagementEvent(Coframe, 'click', event, {
3640
3879
  projectId: Coframe.projectId,
3641
3880
  sessionId: Coframe.sessionId,
3881
+ orgId: Coframe.orgId,
3642
3882
  userToken: Coframe.userToken,
3643
3883
  personalizationHeaders: Coframe.personalizationHeaders,
3884
+ collect: heatmapActive() ? collectClick : undefined,
3644
3885
  });
3645
3886
  };
3646
3887
  document.addEventListener('click', handleClick, true);
3647
3888
  const handleEventFlush = debounce((eventType) => {
3648
- queueEvent(Coframe, {
3649
- projectId: Coframe.projectId,
3650
- sessionId: Coframe.sessionId,
3651
- eventType: eventType,
3652
- orgId: Coframe.orgId,
3653
- userToken: Coframe.userToken,
3654
- personalizationHeaders: Coframe.personalizationHeaders,
3655
- });
3889
+ queueLeaveEvent(Coframe, eventType, eventType === 'tab_show' ? undefined : leaveFields());
3656
3890
  flushQueuedEvents(Coframe.projectId);
3657
3891
  notifyEventsFlushed();
3658
3892
  }, 1);
@@ -3664,11 +3898,27 @@ function setupListeners(Coframe, executionStartTime) {
3664
3898
  handleEventFlush(eventType);
3665
3899
  });
3666
3900
  }
3901
+ function queueLeaveEvent(Coframe, eventType, fields) {
3902
+ queueEvent(Coframe, {
3903
+ projectId: Coframe.projectId,
3904
+ sessionId: Coframe.sessionId,
3905
+ eventType,
3906
+ orgId: Coframe.orgId,
3907
+ userToken: Coframe.userToken,
3908
+ personalizationHeaders: Coframe.personalizationHeaders,
3909
+ }, fields);
3910
+ }
3911
+ function sendPageLeave(Coframe) {
3912
+ const fields = pageLeaveFields();
3913
+ if (fields)
3914
+ queueLeaveEvent(Coframe, 'page_leave', fields);
3915
+ }
3667
3916
  function setupTracking(Coframe, projectResult, previewVariantId, executionStartTime, nonce, waitForCookiesEnabled, onConsentChange) {
3668
3917
  var _a, _b;
3669
3918
  const logger = Coframe.logger;
3670
3919
  const { project } = projectResult;
3671
3920
  setTrackOnlyActivatedFrames(!!((_a = project === null || project === void 0 ? void 0 : project.settings) === null || _a === void 0 ? void 0 : _a.track_only_activated_frames));
3921
+ startHeatmaps(Coframe, project);
3672
3922
  const analytics = createTreatmentAnalytics(Coframe, previewVariantId);
3673
3923
  setupListeners(Coframe, executionStartTime);
3674
3924
  const managedAnalyticsCallback = (_b = project === null || project === void 0 ? void 0 : project.settings) === null || _b === void 0 ? void 0 : _b.analytics_callback;
@@ -3784,6 +4034,7 @@ function setupSPANavigation(Coframe, project, config, previewVariantId, isSessio
3784
4034
  let navigationAbortController = null;
3785
4035
  observeUrlChanges(false, async (newPageUrl, _oldPageUrl) => {
3786
4036
  var _a;
4037
+ sendPageLeave(Coframe);
3787
4038
  if (navigationAbortController)
3788
4039
  navigationAbortController.abort();
3789
4040
  navigationAbortController = new AbortController();
@@ -144,6 +144,10 @@ interface Metric {
144
144
  selector: SelectorCondition;
145
145
  js_binder?: string;
146
146
  }
147
+ interface HeatmapSettings {
148
+ enabled: boolean;
149
+ all_pages?: boolean;
150
+ }
147
151
  interface ProjectSettings {
148
152
  mutation_observer_active: boolean;
149
153
  track_only_activated_frames?: boolean;
@@ -152,6 +156,7 @@ interface ProjectSettings {
152
156
  bot_score_threshold?: number;
153
157
  analytics_callback?: AnalyticsCallback;
154
158
  session_replay?: SessionReplaySettings | null;
159
+ heatmaps?: HeatmapSettings | null;
155
160
  }
156
161
  interface Project {
157
162
  org_id: string;
@@ -164,6 +164,10 @@ interface Metric {
164
164
  selector: SelectorCondition;
165
165
  js_binder?: string;
166
166
  }
167
+ interface HeatmapSettings {
168
+ enabled: boolean;
169
+ all_pages?: boolean;
170
+ }
167
171
  interface ProjectSettings {
168
172
  mutation_observer_active: boolean;
169
173
  track_only_activated_frames?: boolean;
@@ -172,6 +176,7 @@ interface ProjectSettings {
172
176
  bot_score_threshold?: number;
173
177
  analytics_callback?: AnalyticsCallback;
174
178
  session_replay?: SessionReplaySettings | null;
179
+ heatmaps?: HeatmapSettings | null;
175
180
  }
176
181
  interface Project {
177
182
  org_id: string;
@@ -110,6 +110,10 @@ interface Metric {
110
110
  selector: SelectorCondition;
111
111
  js_binder?: string;
112
112
  }
113
+ interface HeatmapSettings {
114
+ enabled: boolean;
115
+ all_pages?: boolean;
116
+ }
113
117
  interface ProjectSettings {
114
118
  mutation_observer_active: boolean;
115
119
  track_only_activated_frames?: boolean;
@@ -118,6 +122,7 @@ interface ProjectSettings {
118
122
  bot_score_threshold?: number;
119
123
  analytics_callback?: AnalyticsCallback;
120
124
  session_replay?: SessionReplaySettings | null;
125
+ heatmaps?: HeatmapSettings | null;
121
126
  }
122
127
  interface Project {
123
128
  org_id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coframe/sdk",
3
- "version": "1.1.39",
3
+ "version": "1.1.41",
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",