@active-reach/web-sdk 1.10.0 → 1.11.1

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/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { S as Storage, l as logger, A as Aegis } from "./analytics-Cc4-QQBf.mjs";
2
- import { B, E, N, R, m } from "./analytics-Cc4-QQBf.mjs";
1
+ import { S as Storage, l as logger, A as Aegis } from "./analytics-6PR9ERDS.mjs";
2
+ import { B, E, N, R, m } from "./analytics-6PR9ERDS.mjs";
3
3
  import { AegisWebPush } from "./push/AegisWebPush.js";
4
4
  function debounce(func, wait) {
5
5
  let timeoutId = null;
@@ -1024,10 +1024,10 @@ const _AegisInAppManager = class _AegisInAppManager {
1024
1024
  this.log(`Error parsing SSE event: ${error}`, "error");
1025
1025
  }
1026
1026
  });
1027
- this.eventSource.addEventListener("heartbeat", (event) => {
1027
+ this.eventSource.addEventListener("heartbeat", () => {
1028
1028
  this.log("SSE heartbeat received");
1029
1029
  });
1030
- this.eventSource.addEventListener("error", (error) => {
1030
+ this.eventSource.addEventListener("error", () => {
1031
1031
  var _a;
1032
1032
  this.log("SSE connection error", "error");
1033
1033
  if (((_a = this.eventSource) == null ? void 0 : _a.readyState) === EventSource.CLOSED) {
@@ -3216,7 +3216,7 @@ class AegisPlacementManager {
3216
3216
  wrapper.innerHTML = this.sanitizeHTML(html);
3217
3217
  const links = wrapper.querySelectorAll("a");
3218
3218
  links.forEach((link) => {
3219
- link.addEventListener("click", (e) => {
3219
+ link.addEventListener("click", () => {
3220
3220
  this.trackEvent(content.placement_id, content.variant_id, "click");
3221
3221
  });
3222
3222
  });
@@ -3349,10 +3349,10 @@ class AegisPlacementManager {
3349
3349
  this.log(`Error parsing SSE event: ${error}`, true);
3350
3350
  }
3351
3351
  });
3352
- this.eventSource.addEventListener("heartbeat", (event) => {
3352
+ this.eventSource.addEventListener("heartbeat", () => {
3353
3353
  this.log("SSE heartbeat received");
3354
3354
  });
3355
- this.eventSource.addEventListener("error", (error) => {
3355
+ this.eventSource.addEventListener("error", () => {
3356
3356
  var _a;
3357
3357
  this.log("SSE connection error", true);
3358
3358
  if (((_a = this.eventSource) == null ? void 0 : _a.readyState) === EventSource.CLOSED) {
@@ -3519,6 +3519,28 @@ class TriggerEngine {
3519
3519
  this.visibilityChangeEnabled = false;
3520
3520
  this.backButtonEnabled = false;
3521
3521
  this.backButtonFired = false;
3522
+ this.rageClickEnabled = false;
3523
+ this.rageClickConfig = { threshold: 3, windowMs: 1e3 };
3524
+ this.rageClickBuffer = /* @__PURE__ */ new Map();
3525
+ this.rageClickFiredInBurst = /* @__PURE__ */ new Set();
3526
+ this.hoverEnabled = false;
3527
+ this.hoverStartAt = /* @__PURE__ */ new Map();
3528
+ this.hoverLastMs = /* @__PURE__ */ new Map();
3529
+ this.hoverThresholds = /* @__PURE__ */ new Map([
3530
+ ["price", [1500]],
3531
+ ["cta", [1500]]
3532
+ ]);
3533
+ this.hoverThresholdsFired = /* @__PURE__ */ new Map();
3534
+ this.hoverThresholdTimers = /* @__PURE__ */ new Map();
3535
+ this.mouseVelEnabled = false;
3536
+ this.mouseVelConfig = {
3537
+ threshold: 1.5,
3538
+ windowMs: 400,
3539
+ cooldownMs: 5e3
3540
+ };
3541
+ this.mouseSamples = [];
3542
+ this.mouseVelLast = 0;
3543
+ this.mouseVelLastFiredAt = 0;
3522
3544
  this.handleScroll = () => {
3523
3545
  const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
3524
3546
  const scrollHeight = document.documentElement.scrollHeight - window.innerHeight;
@@ -3663,6 +3685,204 @@ class TriggerEngine {
3663
3685
  registerBackButton() {
3664
3686
  this.backButtonEnabled = true;
3665
3687
  }
3688
+ /**
3689
+ * Enable rage-click detection (P2 Task 1).
3690
+ *
3691
+ * After calling this, feed click events via `noteClick(intent)`.
3692
+ * SelectorBinder (P1 Task 5) is the upstream source; the wiring lives
3693
+ * in IntentSnapshotCollector (P2 Task 5). Calling `noteClick` without
3694
+ * a prior `registerRageClick()` is a no-op so legacy callers don't
3695
+ * accidentally enable the feature.
3696
+ */
3697
+ registerRageClick(config) {
3698
+ this.rageClickEnabled = true;
3699
+ if (config) {
3700
+ this.rageClickConfig = {
3701
+ threshold: config.threshold ?? this.rageClickConfig.threshold,
3702
+ windowMs: config.windowMs ?? this.rageClickConfig.windowMs
3703
+ };
3704
+ }
3705
+ }
3706
+ /**
3707
+ * Feed a click event for rage-click detection. Returns the current
3708
+ * count within-window for the intent — useful for the snapshot
3709
+ * collector (P2 Task 5) which updates IntentRuleEvaluator with
3710
+ * `rage_click_count` continuously, not just at the burst threshold.
3711
+ *
3712
+ * Idempotent w.r.t. `registerRageClick` — when not enabled, this
3713
+ * returns 0 without buffering.
3714
+ */
3715
+ noteClick(intent, timestamp = Date.now()) {
3716
+ if (!this.rageClickEnabled) return 0;
3717
+ const buf = this.rageClickBuffer.get(intent) ?? [];
3718
+ const cutoff = timestamp - this.rageClickConfig.windowMs;
3719
+ let trimStart = 0;
3720
+ while (trimStart < buf.length && buf[trimStart] < cutoff) trimStart++;
3721
+ const recent = trimStart === 0 ? buf : buf.slice(trimStart);
3722
+ recent.push(timestamp);
3723
+ this.rageClickBuffer.set(intent, recent);
3724
+ if (recent.length < this.rageClickConfig.threshold) {
3725
+ this.rageClickFiredInBurst.delete(intent);
3726
+ }
3727
+ if (recent.length >= this.rageClickConfig.threshold && !this.rageClickFiredInBurst.has(intent)) {
3728
+ this.rageClickFiredInBurst.add(intent);
3729
+ this.emit("rage_click", {
3730
+ intent,
3731
+ count: recent.length,
3732
+ window_ms: this.rageClickConfig.windowMs
3733
+ });
3734
+ }
3735
+ return recent.length;
3736
+ }
3737
+ /** Snapshot the current rage_click count for an intent. The
3738
+ * IntentSnapshotCollector (P2 Task 5) calls this when building the
3739
+ * `rage_click_count` signal value for the IntentRuleEvaluator. */
3740
+ getRageClickCount(intent) {
3741
+ if (!this.rageClickEnabled) return 0;
3742
+ const buf = this.rageClickBuffer.get(intent);
3743
+ if (!buf) return 0;
3744
+ const cutoff = Date.now() - this.rageClickConfig.windowMs;
3745
+ let live = 0;
3746
+ for (const ts of buf) {
3747
+ if (ts >= cutoff) live++;
3748
+ }
3749
+ return live;
3750
+ }
3751
+ /**
3752
+ * Enable hover-dwell tracking (P2 Task 2). After registration, feed
3753
+ * hover events via `noteHoverStart(intent, ts)` / `noteHoverEnd`.
3754
+ * SelectorBinder (P1 Task 5) is the upstream source; wiring lands in
3755
+ * P2 Task 5.
3756
+ */
3757
+ registerHoverDwell(config) {
3758
+ this.hoverEnabled = true;
3759
+ if (config == null ? void 0 : config.thresholdsByIntent) {
3760
+ for (const [intent, thresholds] of Object.entries(config.thresholdsByIntent)) {
3761
+ if (thresholds) this.hoverThresholds.set(intent, thresholds);
3762
+ }
3763
+ }
3764
+ }
3765
+ /** Record the start of a hover on the named intent. Schedules
3766
+ * per-threshold timers that emit `<intent>_hover_dwell_<ms>` when
3767
+ * the active hover passes each registered threshold. */
3768
+ noteHoverStart(intent, timestamp = Date.now()) {
3769
+ if (!this.hoverEnabled) return;
3770
+ if (this.hoverStartAt.has(intent)) {
3771
+ this.noteHoverEnd(intent, timestamp);
3772
+ }
3773
+ this.hoverStartAt.set(intent, timestamp);
3774
+ this.hoverThresholdsFired.set(intent, /* @__PURE__ */ new Set());
3775
+ const thresholds = this.hoverThresholds.get(intent);
3776
+ if (thresholds) {
3777
+ const timers = [];
3778
+ for (const ms of thresholds) {
3779
+ const timerId = setTimeout(() => {
3780
+ if (!this.hoverStartAt.has(intent)) return;
3781
+ const firedSet = this.hoverThresholdsFired.get(intent);
3782
+ if (firedSet == null ? void 0 : firedSet.has(ms)) return;
3783
+ firedSet == null ? void 0 : firedSet.add(ms);
3784
+ this.emit(`${intent}_hover_dwell_${ms}`, {
3785
+ intent,
3786
+ threshold_ms: ms,
3787
+ actual_ms: Date.now() - timestamp
3788
+ });
3789
+ }, ms);
3790
+ timers.push(timerId);
3791
+ }
3792
+ this.hoverThresholdTimers.set(intent, timers);
3793
+ }
3794
+ }
3795
+ /** Record the end of a hover on the named intent. Updates the sticky
3796
+ * `lastHoverMs` so `getHoverMs` keeps returning the last-known
3797
+ * duration until the next hover_start. */
3798
+ noteHoverEnd(intent, timestamp = Date.now()) {
3799
+ if (!this.hoverEnabled) return;
3800
+ const startAt = this.hoverStartAt.get(intent);
3801
+ if (startAt === void 0) return;
3802
+ const dwell = Math.max(0, timestamp - startAt);
3803
+ this.hoverLastMs.set(intent, dwell);
3804
+ this.hoverStartAt.delete(intent);
3805
+ const pending = this.hoverThresholdTimers.get(intent);
3806
+ if (pending) {
3807
+ for (const id of pending) clearTimeout(id);
3808
+ this.hoverThresholdTimers.delete(intent);
3809
+ }
3810
+ this.hoverThresholdsFired.delete(intent);
3811
+ }
3812
+ /**
3813
+ * Enable mouse-velocity-to-top tracking (P2 Task 3). After
3814
+ * registration, feed positions via `noteMousePosition(x, y, ts?)`.
3815
+ * IntentSnapshotCollector (P2 Task 5) wires window 'mousemove' to
3816
+ * this method, with throttling to ~100ms per the plan.
3817
+ */
3818
+ registerMouseVelocityToTop(config) {
3819
+ this.mouseVelEnabled = true;
3820
+ if (config) {
3821
+ this.mouseVelConfig = {
3822
+ threshold: config.threshold ?? this.mouseVelConfig.threshold,
3823
+ windowMs: config.windowMs ?? this.mouseVelConfig.windowMs,
3824
+ cooldownMs: config.cooldownMs ?? this.mouseVelConfig.cooldownMs
3825
+ };
3826
+ }
3827
+ }
3828
+ /** Feed a mouse-position sample. Recomputes the rolling-window
3829
+ * velocity-to-top and fires `mouse_velocity_to_top` when the value
3830
+ * crosses the configured threshold (subject to cooldown). */
3831
+ noteMousePosition(x, y, timestamp = Date.now()) {
3832
+ if (!this.mouseVelEnabled) return 0;
3833
+ const cutoff = timestamp - this.mouseVelConfig.windowMs;
3834
+ let trimStart = 0;
3835
+ while (trimStart < this.mouseSamples.length && this.mouseSamples[trimStart].ts < cutoff) {
3836
+ trimStart++;
3837
+ }
3838
+ if (trimStart > 0) this.mouseSamples = this.mouseSamples.slice(trimStart);
3839
+ this.mouseSamples.push({ x, y, ts: timestamp });
3840
+ if (this.mouseSamples.length < 2) {
3841
+ this.mouseVelLast = 0;
3842
+ return 0;
3843
+ }
3844
+ const oldest = this.mouseSamples[0];
3845
+ const newest = this.mouseSamples[this.mouseSamples.length - 1];
3846
+ const dt = newest.ts - oldest.ts;
3847
+ if (dt <= 0) {
3848
+ this.mouseVelLast = 0;
3849
+ return 0;
3850
+ }
3851
+ const velocity = -(newest.y - oldest.y) / dt;
3852
+ this.mouseVelLast = velocity;
3853
+ const cooldownOk = this.mouseVelLastFiredAt === 0 || timestamp - this.mouseVelLastFiredAt >= this.mouseVelConfig.cooldownMs;
3854
+ if (velocity >= this.mouseVelConfig.threshold && cooldownOk) {
3855
+ this.mouseVelLastFiredAt = timestamp;
3856
+ this.emit("mouse_velocity_to_top", {
3857
+ velocity,
3858
+ threshold: this.mouseVelConfig.threshold,
3859
+ window_ms: this.mouseVelConfig.windowMs,
3860
+ samples: this.mouseSamples.length
3861
+ });
3862
+ }
3863
+ return velocity;
3864
+ }
3865
+ /** Snapshot the current velocity-to-top in px/ms (positive = up).
3866
+ * Returns 0 when not enabled or insufficient samples. */
3867
+ getMouseVelocityToTop() {
3868
+ return this.mouseVelEnabled ? this.mouseVelLast : 0;
3869
+ }
3870
+ /**
3871
+ * Snapshot the hover dwell for an intent.
3872
+ * - Currently hovering: returns `Date.now() - hover_start_at`.
3873
+ * - Hovered before, not currently: returns the most recent hover's
3874
+ * duration (sticky — the rule evaluator queries this AFTER
3875
+ * mouseleave to see "did Priya hover the price ≥ 1.5s?").
3876
+ * - Never hovered: returns 0.
3877
+ */
3878
+ getHoverMs(intent) {
3879
+ if (!this.hoverEnabled) return 0;
3880
+ const startAt = this.hoverStartAt.get(intent);
3881
+ if (startAt !== void 0) {
3882
+ return Math.max(0, Date.now() - startAt);
3883
+ }
3884
+ return this.hoverLastMs.get(intent) ?? 0;
3885
+ }
3666
3886
  start() {
3667
3887
  if (this.isStarted) {
3668
3888
  return;
@@ -4543,7 +4763,14 @@ class AegisWidgetManager {
4543
4763
  this.widgets = [];
4544
4764
  this.isInitialized = false;
4545
4765
  this.log("AegisWidgets destroyed");
4766
+ this.emitEvent("destroyed", {});
4546
4767
  }
4768
+ /**
4769
+ * Fire a lifecycle event to the consumer-supplied callback. Public-API
4770
+ * hook configured via `AegisWidgetConfig.onEvent`. Currently only used
4771
+ * by external callers passing their own logger; internal widget lifecycle
4772
+ * events get wired through here as they're added.
4773
+ */
4547
4774
  emitEvent(eventType, data) {
4548
4775
  if (this.onEvent) {
4549
4776
  try {
@@ -6395,10 +6622,8 @@ class AegisWidgetManager {
6395
6622
  class AegisMessageRuntime {
6396
6623
  constructor(config) {
6397
6624
  this.initialized = false;
6398
- this.ownedTriggerEngine = null;
6399
6625
  const triggerEngine = config.triggerEngine ?? new TriggerEngine();
6400
6626
  const ownsTriggerEngine = !config.triggerEngine;
6401
- if (ownsTriggerEngine) this.ownedTriggerEngine = triggerEngine;
6402
6627
  this.widgets = new AegisWidgetManager({
6403
6628
  writeKey: config.writeKey,
6404
6629
  apiHost: config.apiHost,
@@ -6495,7 +6720,6 @@ class AegisMessageRuntime {
6495
6720
  var _a, _b;
6496
6721
  (_b = (_a = this.inApp).destroy) == null ? void 0 : _b.call(_a);
6497
6722
  this.widgets.destroy();
6498
- this.ownedTriggerEngine = null;
6499
6723
  this.initialized = false;
6500
6724
  }
6501
6725
  /**