@active-reach/web-sdk 1.40.0 → 1.41.0

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
@@ -5037,22 +5037,35 @@ class TriggerEngine {
5037
5037
  callbacks.delete(callback);
5038
5038
  }
5039
5039
  }
5040
+ // A register* call after start() takes effect at once: in-app campaigns
5041
+ // (and so their triggers) arrive after the engine is already running.
5040
5042
  registerScrollDepth(depthPercent) {
5043
+ const firstTarget = this.scrollDepthTargets.size === 0;
5041
5044
  this.scrollDepthTargets.add(depthPercent);
5045
+ if (!this.isStarted) return;
5046
+ if (firstTarget) {
5047
+ this.attachScrollListener();
5048
+ } else {
5049
+ this.handleScroll();
5050
+ }
5042
5051
  }
5043
5052
  registerTimeOnPage(seconds) {
5044
5053
  if (!this.timeOnPageTargets.has(seconds)) {
5054
+ const elapsedMs = this.isStarted && this.pageLoadTime ? Date.now() - this.pageLoadTime : 0;
5045
5055
  const timerId = window.setTimeout(() => {
5046
5056
  this.emit(`time_on_page_${seconds}`, {
5047
5057
  seconds,
5048
5058
  page_url: redactUrl(window.location.href)
5049
5059
  });
5050
5060
  this.timeOnPageTargets.delete(seconds);
5051
- }, seconds * 1e3);
5061
+ }, Math.max(0, seconds * 1e3 - elapsedMs));
5052
5062
  this.timeOnPageTargets.set(seconds, timerId);
5053
5063
  }
5054
5064
  }
5055
5065
  registerExitIntent() {
5066
+ if (this.isStarted && !this.exitIntentEnabled) {
5067
+ this.attachExitIntentListener();
5068
+ }
5056
5069
  this.exitIntentEnabled = true;
5057
5070
  }
5058
5071
  registerInactivity(idleSeconds) {
@@ -5064,13 +5077,16 @@ class TriggerEngine {
5064
5077
  idle_seconds: idleSeconds,
5065
5078
  actual_idle_time: idleTime
5066
5079
  });
5080
+ this.inactivityTargets.delete(idleSeconds);
5067
5081
  }
5068
- this.inactivityTargets.delete(idleSeconds);
5069
5082
  }, idleSeconds * 1e3);
5070
5083
  this.inactivityTargets.set(idleSeconds, timerId);
5071
5084
  }
5072
5085
  }
5073
5086
  registerScrollVelocity(config) {
5087
+ if (this.isStarted && !this.scrollVelocityEnabled) {
5088
+ this.attachScrollVelocityListener();
5089
+ }
5074
5090
  this.scrollVelocityEnabled = true;
5075
5091
  if (config) {
5076
5092
  this.scrollVelocityConfig = {
@@ -6013,6 +6029,70 @@ function markReturningVisitor() {
6013
6029
  } catch {
6014
6030
  }
6015
6031
  }
6032
+ const DEFAULT_SCROLL_DEPTH_PERCENT = 50;
6033
+ const DEFAULT_TIME_ON_PAGE_SECONDS = 30;
6034
+ const DEFAULT_IDLE_SECONDS = 30;
6035
+ function positiveNumber(raw, fallback) {
6036
+ const n = Number(raw);
6037
+ return Number.isFinite(n) && n > 0 ? n : fallback;
6038
+ }
6039
+ function behaviouralTriggerEvent(trigger) {
6040
+ if (!trigger) return null;
6041
+ const cfg = trigger.config || {};
6042
+ switch (trigger.type) {
6043
+ case "exit_intent":
6044
+ return "exit_intent";
6045
+ case "scroll_depth":
6046
+ return `scroll_depth_${positiveNumber(cfg.depth_percent, DEFAULT_SCROLL_DEPTH_PERCENT)}`;
6047
+ case "time_on_page":
6048
+ return `time_on_page_${positiveNumber(cfg.seconds, DEFAULT_TIME_ON_PAGE_SECONDS)}`;
6049
+ case "inactivity":
6050
+ return `inactivity_${positiveNumber(cfg.idle_seconds, DEFAULT_IDLE_SECONDS)}`;
6051
+ default:
6052
+ return null;
6053
+ }
6054
+ }
6055
+ function triggerFromEngineEvent(eventName) {
6056
+ if (eventName === "exit_intent") return { type: "exit_intent" };
6057
+ const match = /^(inactivity|time_on_page|scroll_depth)_(\d+)$/.exec(eventName);
6058
+ if (!match) return null;
6059
+ const n = Number(match[2]);
6060
+ if (match[1] === "inactivity") return { type: "inactivity", config: { idle_seconds: n } };
6061
+ if (match[1] === "time_on_page") return { type: "time_on_page", config: { seconds: n } };
6062
+ return { type: "scroll_depth", config: { depth_percent: n } };
6063
+ }
6064
+ function armBehaviouralTriggers(engine, triggers, armed, fire, isMobile) {
6065
+ const newlyArmed = [];
6066
+ for (const trigger of triggers) {
6067
+ const eventName = behaviouralTriggerEvent(trigger);
6068
+ if (!eventName || armed.has(eventName)) continue;
6069
+ armed.add(eventName);
6070
+ newlyArmed.push(eventName);
6071
+ const cfg = trigger.config || {};
6072
+ switch (trigger.type) {
6073
+ case "exit_intent":
6074
+ if (isMobile) {
6075
+ engine.registerScrollVelocity();
6076
+ engine.on("mobile_exit_intent", () => fire("exit_intent"));
6077
+ } else {
6078
+ engine.registerExitIntent();
6079
+ engine.on("exit_intent", () => fire("exit_intent"));
6080
+ }
6081
+ continue;
6082
+ case "scroll_depth":
6083
+ engine.registerScrollDepth(positiveNumber(cfg.depth_percent, DEFAULT_SCROLL_DEPTH_PERCENT));
6084
+ break;
6085
+ case "time_on_page":
6086
+ engine.registerTimeOnPage(positiveNumber(cfg.seconds, DEFAULT_TIME_ON_PAGE_SECONDS));
6087
+ break;
6088
+ case "inactivity":
6089
+ engine.registerInactivity(positiveNumber(cfg.idle_seconds, DEFAULT_IDLE_SECONDS));
6090
+ break;
6091
+ }
6092
+ engine.on(eventName, () => fire(eventName));
6093
+ }
6094
+ return newlyArmed;
6095
+ }
6016
6096
  function readAnonIdFromStorage$1() {
6017
6097
  if (typeof document === "undefined") return void 0;
6018
6098
  try {
@@ -6108,7 +6188,6 @@ const _AegisInAppManager = class _AegisInAppManager {
6108
6188
  this.propertyId = config.propertyId;
6109
6189
  this.debugMode = config.debugMode || false;
6110
6190
  this.enableSSE = config.enableSSE === true;
6111
- this.onInteractiveCampaign = config.onInteractiveCampaign;
6112
6191
  this.onActionHandler = config.onAction;
6113
6192
  if (config.productUrl) this.productUrl = config.productUrl;
6114
6193
  this.getWorkspaceId = config.getWorkspaceId;
@@ -9036,6 +9115,9 @@ const _AegisInAppManager = class _AegisInAppManager {
9036
9115
  * matches config.product_id (string or string[])
9037
9116
  * - `delay` : client-side setTimeout — evaluated at armeng
9038
9117
  * time (kicked off from `displayCampaign`), not here.
9118
+ * - `exit_intent` / `scroll_depth` / `time_on_page` / `inactivity` : the
9119
+ * TriggerEngine event named by `behaviouralTriggerEvent`;
9120
+ * AegisMessageRuntime arms the engine and forwards it.
9039
9121
  */
9040
9122
  onClientEvent(eventName, eventData = {}) {
9041
9123
  for (const c of this.campaigns) {
@@ -9065,8 +9147,10 @@ const _AegisInAppManager = class _AegisInAppManager {
9065
9147
  );
9066
9148
  return wanted.includes(actual);
9067
9149
  }
9068
- default:
9069
- return false;
9150
+ default: {
9151
+ const behavioural = behaviouralTriggerEvent(trigger);
9152
+ return behavioural !== null && behavioural === eventName;
9153
+ }
9070
9154
  }
9071
9155
  }
9072
9156
  displayCampaign(campaign) {
@@ -9290,11 +9374,7 @@ const _AegisInAppManager = class _AegisInAppManager {
9290
9374
  break;
9291
9375
  case "spin_wheel":
9292
9376
  case "scratch_card":
9293
- if (this.onInteractiveCampaign) {
9294
- this.onInteractiveCampaign(campaign);
9295
- } else {
9296
- this.renderGamificationOverlay(campaign, ic, bg, text);
9297
- }
9377
+ this.renderGamificationOverlay(campaign, ic, bg, text);
9298
9378
  break;
9299
9379
  default:
9300
9380
  this.log(`Unknown interactive sub_type: ${campaign.sub_type}`, "warn");
@@ -13275,39 +13355,6 @@ class AegisWidgetManager {
13275
13355
  this.preloadWidgetAssets();
13276
13356
  }
13277
13357
  }
13278
- /**
13279
- * Render an in-app campaign whose sub_type routes it to the
13280
- * gamification-widget code path (spin_wheel / scratch_card).
13281
- * InAppCampaign is adapted to the minimal WidgetConfig shape that
13282
- * the internal renderers already understand — no rewrite needed.
13283
- *
13284
- * AegisMessageRuntime wires this method as AegisInAppManager's
13285
- * `onInteractiveCampaign` callback so impressions from the inbox
13286
- * prefetch bundle actually land on screen.
13287
- */
13288
- renderInteractiveCampaign(campaign) {
13289
- if (this.isDestroyed) return;
13290
- if (typeof document === "undefined") return;
13291
- const subType = campaign.sub_type;
13292
- if (subType !== "spin_wheel" && subType !== "scratch_card") {
13293
- this.log(`renderInteractiveCampaign: unsupported sub_type "${subType}"`, true);
13294
- return;
13295
- }
13296
- const widget = {
13297
- widget_id: campaign.id,
13298
- widget_type: subType,
13299
- name: campaign.title,
13300
- config: campaign.interactive_config ?? {},
13301
- priority: campaign.priority ?? 0
13302
- };
13303
- if (this.renderedWidgets.has(widget.widget_id)) return;
13304
- this.renderedWidgets.add(widget.widget_id);
13305
- if (subType === "spin_wheel") {
13306
- this.renderSpinWheel(widget);
13307
- } else {
13308
- this.renderScratchCard(widget);
13309
- }
13310
- }
13311
13358
  /**
13312
13359
  * Tear down everything the manager installed: trigger listeners (if
13313
13360
  * we own the TriggerEngine), injected style sheets, and any rendered
@@ -13483,15 +13530,6 @@ class AegisWidgetManager {
13483
13530
  this.log(`Preloading exit intent image: ${imageUrl}`);
13484
13531
  }
13485
13532
  }
13486
- const spinWheelConfig = this.prefetchWidgetConfigs.spin_wheel;
13487
- if (spinWheelConfig == null ? void 0 : spinWheelConfig.enabled) {
13488
- const imageUrl = spinWheelConfig.image_url;
13489
- if (imageUrl) {
13490
- const img = new Image();
13491
- img.src = imageUrl;
13492
- this.log(`Preloading spin wheel image: ${imageUrl}`);
13493
- }
13494
- }
13495
13533
  } catch (error) {
13496
13534
  this.log(`Error preloading widget assets: ${error}`, true);
13497
13535
  }
@@ -13588,67 +13626,7 @@ class AegisWidgetManager {
13588
13626
  if (!this.enablePrefetch || !this.triggerEngine) {
13589
13627
  return;
13590
13628
  }
13591
- const spinWheelConfig = this.prefetchWidgetConfigs.spin_wheel;
13592
13629
  const exitIntentConfig = this.prefetchWidgetConfigs.exit_intent;
13593
- if (spinWheelConfig && spinWheelConfig.enabled) {
13594
- const isMobile2 = this.isMobileDevice();
13595
- const mobileConfig2 = (exitIntentConfig == null ? void 0 : exitIntentConfig.mobile_triggers) || {};
13596
- const handleSpinWheelIntent = () => {
13597
- const widgetId = `prefetch_spin_wheel_${Date.now()}`;
13598
- if (this.renderedWidgets.has(widgetId)) {
13599
- return;
13600
- }
13601
- this.renderedWidgets.add(widgetId);
13602
- const detectedCart = this.detectPlatformCart();
13603
- if (spinWheelConfig.type === "cart_recovery" && detectedCart) {
13604
- this.cartData = detectedCart;
13605
- this.renderSpinWheelWidget(widgetId, spinWheelConfig);
13606
- this.sendCartAbandonmentBeacon();
13607
- } else if (spinWheelConfig.type === "lead_gen") {
13608
- this.renderSpinWheelWidget(widgetId, spinWheelConfig);
13609
- }
13610
- };
13611
- if (isMobile2) {
13612
- const scrollVelocityEnabled = mobileConfig2.scroll_velocity !== false;
13613
- if (scrollVelocityEnabled) {
13614
- this.triggerEngine.registerScrollVelocity({
13615
- threshold: mobileConfig2.scroll_threshold || 0.5,
13616
- minScrollPosition: mobileConfig2.scroll_min_position || 100,
13617
- cooldown: mobileConfig2.scroll_cooldown || 5e3
13618
- });
13619
- this.triggerEngine.on("mobile_exit_intent", handleSpinWheelIntent);
13620
- this.log("Registered mobile scroll velocity trigger for spin wheel");
13621
- }
13622
- const idleTimerEnabled = mobileConfig2.idle_timer !== false;
13623
- if (idleTimerEnabled) {
13624
- const idleSeconds = mobileConfig2.idle_seconds || 15;
13625
- this.triggerEngine.registerInactivity(idleSeconds);
13626
- this.triggerEngine.on(`inactivity_${idleSeconds}`, handleSpinWheelIntent);
13627
- this.log(`Registered mobile idle timer trigger for spin wheel: ${idleSeconds}s`);
13628
- }
13629
- const visibilityChangeEnabled = mobileConfig2.visibility_change !== false;
13630
- if (visibilityChangeEnabled) {
13631
- const detectedCart = this.detectPlatformCart();
13632
- if (detectedCart && detectedCart.cart_items && detectedCart.cart_items.length > 0) {
13633
- this.triggerEngine.registerVisibilityChange();
13634
- this.triggerEngine.on("visibility_hidden", handleSpinWheelIntent);
13635
- this.log("Registered mobile visibility change trigger for spin wheel (cart detected)");
13636
- }
13637
- }
13638
- const backButtonEnabled = mobileConfig2.back_button === true;
13639
- if (backButtonEnabled) {
13640
- this.triggerEngine.registerBackButton();
13641
- this.triggerEngine.on("back_button", handleSpinWheelIntent);
13642
- this.log("Registered mobile back button trigger for spin wheel");
13643
- }
13644
- this.log(`Setup mobile spin wheel: type=${spinWheelConfig.type}, priority=${spinWheelConfig.priority}`);
13645
- } else {
13646
- this.triggerEngine.registerExitIntent();
13647
- this.triggerEngine.on("exit_intent", handleSpinWheelIntent);
13648
- this.log(`Setup desktop spin wheel exit intent: type=${spinWheelConfig.type}, priority=${spinWheelConfig.priority}`);
13649
- }
13650
- return;
13651
- }
13652
13630
  if (!exitIntentConfig || !exitIntentConfig.enabled) {
13653
13631
  this.log("No exit intent config found in prefetch");
13654
13632
  return;
@@ -13765,12 +13743,6 @@ class AegisWidgetManager {
13765
13743
  case "chat_bubble":
13766
13744
  this.renderChatBubble(widget);
13767
13745
  break;
13768
- case "spin_wheel":
13769
- this.renderSpinWheel(widget);
13770
- break;
13771
- case "scratch_card":
13772
- this.renderScratchCard(widget);
13773
- break;
13774
13746
  case "toast":
13775
13747
  this.renderToast(widget);
13776
13748
  break;
@@ -13839,531 +13811,6 @@ class AegisWidgetManager {
13839
13811
  bubble.setAttribute("data-aegis-widget-root", "");
13840
13812
  document.body.appendChild(bubble);
13841
13813
  }
13842
- renderSpinWheel(widget) {
13843
- const overlay = document.createElement("div");
13844
- overlay.className = "aegis-gamification-overlay";
13845
- overlay.setAttribute("data-widget-id", widget.widget_id);
13846
- overlay.style.cssText = `
13847
- position: fixed;
13848
- top: 0;
13849
- left: 0;
13850
- right: 0;
13851
- bottom: 0;
13852
- background: rgba(0, 0, 0, 0.7);
13853
- z-index: 1000000;
13854
- display: flex;
13855
- align-items: center;
13856
- justify-content: center;
13857
- animation: aegisFadeIn 0.3s ease-out;
13858
- `;
13859
- const modal = document.createElement("div");
13860
- modal.className = "aegis-spin-wheel-modal";
13861
- modal.style.cssText = `
13862
- background: white;
13863
- border-radius: 16px;
13864
- padding: 32px;
13865
- max-width: 500px;
13866
- width: 90%;
13867
- box-shadow: 0 10px 40px rgba(0,0,0,0.3);
13868
- text-align: center;
13869
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
13870
- animation: aegisScaleIn 0.3s ease-out;
13871
- `;
13872
- const title = document.createElement("h2");
13873
- title.textContent = widget.config.title || "Spin to Win!";
13874
- title.style.cssText = "margin: 0 0 16px 0; font-size: 24px; font-weight: 700; color: #1a1a1a;";
13875
- modal.appendChild(title);
13876
- const description = document.createElement("p");
13877
- description.textContent = widget.config.description || "Try your luck and win exclusive prizes!";
13878
- description.style.cssText = "margin: 0 0 24px 0; font-size: 14px; color: #666;";
13879
- modal.appendChild(description);
13880
- const segments = Array.isArray(
13881
- widget.config.segments
13882
- ) ? widget.config.segments : [
13883
- { label: "Prize 1", color: "#ff6b6b" },
13884
- { label: "Prize 2", color: "#feca57" },
13885
- { label: "Prize 3", color: "#48dbfb" },
13886
- { label: "Prize 4", color: "#ff6348" }
13887
- ];
13888
- const SVG_NS2 = "http://www.w3.org/2000/svg";
13889
- const wheel = document.createElementNS(SVG_NS2, "svg");
13890
- wheel.setAttribute("viewBox", "-160 -160 320 320");
13891
- wheel.setAttribute("width", "300");
13892
- wheel.setAttribute("height", "300");
13893
- wheel.style.cssText = "margin: 0 auto 24px; display: block;";
13894
- const n = segments.length;
13895
- const anglePer = 2 * Math.PI / n;
13896
- const radius = 140;
13897
- for (let i = 0; i < n; i++) {
13898
- const start = i * anglePer - Math.PI / 2;
13899
- const end = start + anglePer;
13900
- const x1 = Math.cos(start) * radius;
13901
- const y1 = Math.sin(start) * radius;
13902
- const x2 = Math.cos(end) * radius;
13903
- const y2 = Math.sin(end) * radius;
13904
- const largeArc = anglePer > Math.PI ? 1 : 0;
13905
- const path = document.createElementNS(SVG_NS2, "path");
13906
- path.setAttribute(
13907
- "d",
13908
- `M 0 0 L ${x1.toFixed(2)} ${y1.toFixed(2)} A ${radius} ${radius} 0 ${largeArc} 1 ${x2.toFixed(2)} ${y2.toFixed(2)} Z`
13909
- );
13910
- path.setAttribute("fill", this.sanitizeColor(segments[i].color || "#cccccc"));
13911
- path.setAttribute("stroke", "#ffffff");
13912
- path.setAttribute("stroke-width", "2");
13913
- wheel.appendChild(path);
13914
- const labelAngle = start + anglePer / 2;
13915
- const lx = Math.cos(labelAngle) * radius * 0.65;
13916
- const ly = Math.sin(labelAngle) * radius * 0.65;
13917
- const text = document.createElementNS(SVG_NS2, "text");
13918
- text.setAttribute("x", lx.toFixed(2));
13919
- text.setAttribute("y", ly.toFixed(2));
13920
- text.setAttribute("fill", "#ffffff");
13921
- text.setAttribute("font-size", "13");
13922
- text.setAttribute("font-weight", "600");
13923
- text.setAttribute("text-anchor", "middle");
13924
- text.setAttribute("dominant-baseline", "middle");
13925
- text.textContent = segments[i].label || `#${i + 1}`;
13926
- wheel.appendChild(text);
13927
- }
13928
- modal.appendChild(wheel);
13929
- const spinButton = document.createElement("button");
13930
- spinButton.textContent = "SPIN NOW!";
13931
- spinButton.style.cssText = `
13932
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
13933
- color: white;
13934
- border: none;
13935
- padding: 16px 48px;
13936
- border-radius: 8px;
13937
- font-weight: 700;
13938
- font-size: 16px;
13939
- cursor: pointer;
13940
- margin-bottom: 16px;
13941
- `;
13942
- spinButton.addEventListener("click", async () => {
13943
- spinButton.disabled = true;
13944
- spinButton.textContent = "SPINNING...";
13945
- try {
13946
- const prize = await this.generatePrize(widget.config.config_id);
13947
- wheel.style.animation = "aegisSpin 2s ease-out";
13948
- setTimeout(() => {
13949
- this.showPrizeResult(modal, prize);
13950
- }, 2e3);
13951
- } catch (error) {
13952
- this.log(`Error generating prize: ${error}`, true);
13953
- spinButton.disabled = false;
13954
- spinButton.textContent = "TRY AGAIN";
13955
- }
13956
- });
13957
- modal.appendChild(spinButton);
13958
- const closeButton = document.createElement("button");
13959
- closeButton.textContent = "×";
13960
- closeButton.setAttribute("aria-label", "Close");
13961
- closeButton.style.cssText = `
13962
- position: absolute;
13963
- top: 16px;
13964
- right: 16px;
13965
- background: transparent;
13966
- border: none;
13967
- font-size: 28px;
13968
- cursor: pointer;
13969
- color: #999;
13970
- `;
13971
- closeButton.addEventListener("click", () => {
13972
- this.trackEvent(widget.widget_id, "dismiss");
13973
- document.body.removeChild(overlay);
13974
- this.renderedWidgets.delete(widget.widget_id);
13975
- });
13976
- modal.style.position = "relative";
13977
- modal.appendChild(closeButton);
13978
- overlay.appendChild(modal);
13979
- this.addAnimationStyles();
13980
- overlay.setAttribute("data-aegis-widget-root", "");
13981
- document.body.appendChild(overlay);
13982
- }
13983
- renderSpinWheelWidget(widgetId, config) {
13984
- if (this.isDestroyed) return;
13985
- const cart = this.detectPlatformCart();
13986
- if (config.type === "cart_recovery" && !cart) {
13987
- this.log("Spin wheel requires cart, but none detected");
13988
- return;
13989
- }
13990
- if (cart) {
13991
- this.cartData = cart;
13992
- }
13993
- const customization = this.cssCustomization.spinWheel || {};
13994
- const accentColor = customization.accentColor || config.accent_color || "#FF6B6B";
13995
- const backgroundColor = customization.backgroundColor || config.background_color || "#FFFFFF";
13996
- const textColor = customization.textColor || config.text_color || "#333333";
13997
- const buttonColor = customization.buttonColor || config.button_color || accentColor;
13998
- const wheelColors = customization.wheelColors || ["#FF6B6B", "#4ECDC4", "#FFE66D", "#95E1D3"];
13999
- const overlay = document.createElement("div");
14000
- overlay.className = "aegis-spin-wheel-overlay";
14001
- overlay.setAttribute("data-widget-id", widgetId);
14002
- overlay.style.cssText = `
14003
- position: fixed;
14004
- top: 0;
14005
- left: 0;
14006
- width: 100%;
14007
- height: 100%;
14008
- background: rgba(0, 0, 0, 0.7);
14009
- display: flex;
14010
- align-items: center;
14011
- justify-content: center;
14012
- z-index: 999999;
14013
- animation: aegisFadeIn 0.3s ease;
14014
- `;
14015
- const modal = document.createElement("div");
14016
- modal.className = "aegis-spin-wheel-modal";
14017
- modal.style.cssText = `
14018
- background: ${this.sanitizeColor(backgroundColor)};
14019
- border-radius: 16px;
14020
- padding: 32px;
14021
- max-width: 500px;
14022
- width: 90%;
14023
- box-shadow: 0 20px 60px rgba(0,0,0,0.3);
14024
- position: relative;
14025
- animation: aegisScaleIn 0.4s ease;
14026
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
14027
- `;
14028
- const closeBtn = document.createElement("button");
14029
- closeBtn.innerHTML = lucideSvg("x", { size: 16 });
14030
- closeBtn.setAttribute("aria-label", "Close");
14031
- closeBtn.className = "aegis-spin-wheel-close";
14032
- closeBtn.style.cssText = `
14033
- position: absolute;
14034
- top: 16px;
14035
- right: 16px;
14036
- background: none;
14037
- border: none;
14038
- font-size: 24px;
14039
- cursor: pointer;
14040
- color: #999;
14041
- `;
14042
- closeBtn.onclick = () => {
14043
- this.trackEvent(widgetId, "dismiss", { type: "spin_wheel" });
14044
- document.body.removeChild(overlay);
14045
- this.renderedWidgets.delete(widgetId);
14046
- };
14047
- modal.appendChild(closeBtn);
14048
- const title = document.createElement("h2");
14049
- title.textContent = this.interpolateCartVariables(config.title || "Spin to Win!");
14050
- title.style.cssText = `
14051
- margin: 0 0 16px 0;
14052
- font-size: 28px;
14053
- font-weight: bold;
14054
- text-align: center;
14055
- color: ${this.sanitizeColor(textColor)};
14056
- `;
14057
- modal.appendChild(title);
14058
- if (cart) {
14059
- const subtitle = document.createElement("p");
14060
- subtitle.textContent = `Complete your ${cart.cart_currency} ${cart.cart_total.toFixed(2)} order and save!`;
14061
- subtitle.style.cssText = `
14062
- margin: 0 0 24px 0;
14063
- font-size: 16px;
14064
- text-align: center;
14065
- color: #666;
14066
- `;
14067
- modal.appendChild(subtitle);
14068
- }
14069
- const wheelContainer = document.createElement("div");
14070
- wheelContainer.className = "aegis-wheel-container";
14071
- wheelContainer.style.cssText = `
14072
- width: 300px;
14073
- height: 300px;
14074
- margin: 0 auto 24px auto;
14075
- background: conic-gradient(
14076
- from 0deg,
14077
- ${wheelColors[0]} 0deg 90deg,
14078
- ${wheelColors[1]} 90deg 180deg,
14079
- ${wheelColors[2]} 180deg 270deg,
14080
- ${wheelColors[3]} 270deg 360deg
14081
- );
14082
- border-radius: 50%;
14083
- position: relative;
14084
- display: flex;
14085
- align-items: center;
14086
- justify-content: center;
14087
- `;
14088
- const form = document.createElement("form");
14089
- form.className = "aegis-spin-form";
14090
- form.style.cssText = "display: block;";
14091
- const phoneInput = document.createElement("input");
14092
- phoneInput.type = "tel";
14093
- phoneInput.placeholder = "Enter your phone number";
14094
- phoneInput.required = true;
14095
- phoneInput.style.cssText = `
14096
- width: 100%;
14097
- padding: 14px;
14098
- border: 2px solid #ddd;
14099
- border-radius: 8px;
14100
- font-size: 16px;
14101
- margin-bottom: 12px;
14102
- box-sizing: border-box;
14103
- `;
14104
- form.appendChild(phoneInput);
14105
- const emailInput = document.createElement("input");
14106
- emailInput.type = "email";
14107
- emailInput.placeholder = "Enter your email (optional)";
14108
- emailInput.style.cssText = `
14109
- width: 100%;
14110
- padding: 14px;
14111
- border: 2px solid #ddd;
14112
- border-radius: 8px;
14113
- font-size: 16px;
14114
- margin-bottom: 12px;
14115
- box-sizing: border-box;
14116
- `;
14117
- form.appendChild(emailInput);
14118
- const nameInput = document.createElement("input");
14119
- nameInput.type = "text";
14120
- nameInput.placeholder = "Enter your name (optional)";
14121
- nameInput.style.cssText = `
14122
- width: 100%;
14123
- padding: 14px;
14124
- border: 2px solid #ddd;
14125
- border-radius: 8px;
14126
- font-size: 16px;
14127
- margin-bottom: 12px;
14128
- box-sizing: border-box;
14129
- `;
14130
- form.appendChild(nameInput);
14131
- const collectBirthday = config.collect_birthday === true;
14132
- let birthdayInput = null;
14133
- if (collectBirthday) {
14134
- const birthdayId = `aegis-spin-dob-${widgetId}`;
14135
- const birthdayLabel = document.createElement("label");
14136
- birthdayLabel.htmlFor = birthdayId;
14137
- birthdayLabel.textContent = "Birthday (optional) — for a birthday treat";
14138
- birthdayLabel.style.cssText = `
14139
- display: block;
14140
- font-size: 12px;
14141
- font-weight: 600;
14142
- opacity: 0.75;
14143
- margin-bottom: 4px;
14144
- color: ${this.sanitizeColor(textColor)};
14145
- `;
14146
- form.appendChild(birthdayLabel);
14147
- birthdayInput = document.createElement("input");
14148
- birthdayInput.id = birthdayId;
14149
- birthdayInput.type = "date";
14150
- birthdayInput.max = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
14151
- birthdayInput.style.cssText = `
14152
- width: 100%;
14153
- padding: 14px;
14154
- border: 2px solid #ddd;
14155
- border-radius: 8px;
14156
- font-size: 16px;
14157
- margin-bottom: 12px;
14158
- box-sizing: border-box;
14159
- `;
14160
- form.appendChild(birthdayInput);
14161
- }
14162
- const submitButton = document.createElement("button");
14163
- submitButton.type = "submit";
14164
- submitButton.textContent = "Spin the Wheel!";
14165
- submitButton.style.cssText = `
14166
- width: 100%;
14167
- padding: 14px;
14168
- background: ${this.sanitizeColor(buttonColor)};
14169
- color: white;
14170
- border: none;
14171
- border-radius: 8px;
14172
- font-size: 16px;
14173
- font-weight: 700;
14174
- cursor: pointer;
14175
- `;
14176
- form.appendChild(submitButton);
14177
- const errorMessage = document.createElement("div");
14178
- errorMessage.className = "aegis-spin-error";
14179
- errorMessage.style.cssText = `
14180
- color: #d32f2f;
14181
- font-size: 14px;
14182
- margin-top: 12px;
14183
- text-align: center;
14184
- display: none;
14185
- `;
14186
- form.appendChild(errorMessage);
14187
- form.addEventListener("submit", async (e) => {
14188
- e.preventDefault();
14189
- const phone = phoneInput.value.trim();
14190
- const email = emailInput.value.trim();
14191
- const name = nameInput.value.trim();
14192
- const dateOfBirth = birthdayInput ? birthdayInput.value.trim() : "";
14193
- if (!this.validatePhone(phone)) {
14194
- errorMessage.textContent = "Please enter a valid phone number (e.g., +1234567890)";
14195
- errorMessage.style.display = "block";
14196
- return;
14197
- }
14198
- if (email && !this.validateEmail(email)) {
14199
- errorMessage.textContent = "Please enter a valid email address";
14200
- errorMessage.style.display = "block";
14201
- return;
14202
- }
14203
- errorMessage.style.display = "none";
14204
- submitButton.disabled = true;
14205
- submitButton.textContent = "Spinning...";
14206
- wheelContainer.style.animation = "aegisSpin 2s ease-out";
14207
- setTimeout(async () => {
14208
- try {
14209
- const prize = await this.submitSpinWheel({
14210
- phone,
14211
- email,
14212
- name,
14213
- dateOfBirth,
14214
- cart,
14215
- widgetId
14216
- });
14217
- this.showSpinWheelPrize(modal, prize);
14218
- this.trackEvent(widgetId, "submit", {
14219
- type: "spin_wheel",
14220
- prize_label: prize.prize_label,
14221
- has_email: !!email
14222
- });
14223
- } catch (error) {
14224
- this.log(`Error submitting spin wheel: ${error}`, true);
14225
- errorMessage.textContent = "Failed to submit. Please try again.";
14226
- errorMessage.style.display = "block";
14227
- submitButton.disabled = false;
14228
- submitButton.textContent = "Spin the Wheel!";
14229
- wheelContainer.style.animation = "";
14230
- }
14231
- }, 2e3);
14232
- });
14233
- modal.appendChild(wheelContainer);
14234
- modal.appendChild(form);
14235
- overlay.appendChild(modal);
14236
- this.addAnimationStyles();
14237
- overlay.setAttribute("data-aegis-widget-root", "");
14238
- document.body.appendChild(overlay);
14239
- this.trackEvent(widgetId, "show", { type: "spin_wheel" });
14240
- }
14241
- validatePhone(phone) {
14242
- const e164Regex = /^\+?[1-9]\d{1,14}$/;
14243
- return e164Regex.test(phone.replace(/[\s()-]/g, ""));
14244
- }
14245
- validateEmail(email) {
14246
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
14247
- return emailRegex.test(email);
14248
- }
14249
- async submitSpinWheel(data) {
14250
- var _a, _b, _c, _d, _e;
14251
- const url = `${this.apiHost}/v1/widgets/spin-wheel/submit`;
14252
- const headers = {
14253
- "X-Aegis-Write-Key": this.writeKey,
14254
- "Content-Type": "application/json"
14255
- };
14256
- if (this.organizationId) headers["X-Organization-ID"] = this.organizationId;
14257
- const geoRegion = this.detectGeoRegion();
14258
- const deviceType = this.getDeviceType();
14259
- const utmParams = this.getUTMParams();
14260
- const body = {
14261
- phone: data.phone.replace(/[\s()-]/g, ""),
14262
- email: data.email || void 0,
14263
- name: data.name || void 0,
14264
- // Omitted when blank — the server distinguishes "not given" from a value
14265
- // it could not parse, and neither is an empty string.
14266
- date_of_birth: data.dateOfBirth || void 0,
14267
- cart_id: ((_a = data.cart) == null ? void 0 : _a.cart_id) || `web_${Date.now()}`,
14268
- cart_token: (_b = data.cart) == null ? void 0 : _b.cart_id,
14269
- cart_total: ((_c = data.cart) == null ? void 0 : _c.cart_total) || 0,
14270
- cart_currency: ((_d = data.cart) == null ? void 0 : _d.cart_currency) || "USD",
14271
- cart_items: ((_e = data.cart) == null ? void 0 : _e.cart_items) || [],
14272
- // Redacted: a stored cart URL is re-sent later as a recovery link, and a
14273
- // credential in it (`?t=` / `?k=`) must not outlive this page view.
14274
- cart_url: redactUrl(window.location.href),
14275
- platform: "web",
14276
- geo_region: geoRegion,
14277
- device_type: deviceType,
14278
- session_id: this.getSessionId() || void 0,
14279
- anonymous_id: this.getAnonymousId() || void 0,
14280
- utm_source: utmParams.utm_source,
14281
- utm_medium: utmParams.utm_medium,
14282
- utm_campaign: utmParams.utm_campaign
14283
- };
14284
- const response = await fetch(url, {
14285
- method: "POST",
14286
- headers,
14287
- body: JSON.stringify(body),
14288
- credentials: "include"
14289
- });
14290
- if (!response.ok) {
14291
- const errorText = await response.text();
14292
- throw new Error(`Failed to submit spin wheel: ${response.status} - ${errorText}`);
14293
- }
14294
- return await response.json();
14295
- }
14296
- showSpinWheelPrize(modal, prize) {
14297
- modal.innerHTML = "";
14298
- const resultContainer = document.createElement("div");
14299
- resultContainer.style.cssText = "text-align: center; padding: 40px 20px;";
14300
- const emoji = document.createElement("div");
14301
- emoji.textContent = "🎉";
14302
- emoji.style.cssText = "font-size: 60px; margin-bottom: 20px;";
14303
- resultContainer.appendChild(emoji);
14304
- const title = document.createElement("h2");
14305
- title.textContent = "Congratulations!";
14306
- title.style.cssText = "margin: 0 0 12px 0; font-size: 28px; font-weight: 700; color: #1a73e8;";
14307
- resultContainer.appendChild(title);
14308
- const prizeLabel = document.createElement("p");
14309
- prizeLabel.textContent = prize.prize_label;
14310
- prizeLabel.style.cssText = "margin: 0 0 20px 0; font-size: 20px; font-weight: 600; color: #333;";
14311
- resultContainer.appendChild(prizeLabel);
14312
- if (prize.coupon_code) {
14313
- const couponContainer = document.createElement("div");
14314
- couponContainer.style.cssText = `
14315
- background: #f5f5f5;
14316
- padding: 16px;
14317
- border-radius: 8px;
14318
- margin-bottom: 20px;
14319
- border: 2px dashed #1a73e8;
14320
- `;
14321
- const couponLabel = document.createElement("div");
14322
- couponLabel.textContent = "Your coupon code:";
14323
- couponLabel.style.cssText = "font-size: 12px; color: #666; margin-bottom: 8px;";
14324
- couponContainer.appendChild(couponLabel);
14325
- const couponCode = document.createElement("div");
14326
- couponCode.textContent = prize.coupon_code;
14327
- couponCode.style.cssText = "font-size: 24px; font-weight: 700; color: #1a73e8; letter-spacing: 2px;";
14328
- couponContainer.appendChild(couponCode);
14329
- resultContainer.appendChild(couponContainer);
14330
- }
14331
- const message = document.createElement("p");
14332
- message.textContent = "Check your WhatsApp/SMS for your discount code and cart link!";
14333
- message.style.cssText = "margin: 0 0 20px 0; font-size: 14px; color: #666;";
14334
- resultContainer.appendChild(message);
14335
- const closeButton = document.createElement("button");
14336
- closeButton.textContent = "Close";
14337
- closeButton.style.cssText = `
14338
- background: #1a73e8;
14339
- color: white;
14340
- border: none;
14341
- padding: 12px 32px;
14342
- border-radius: 8px;
14343
- font-weight: 600;
14344
- font-size: 14px;
14345
- cursor: pointer;
14346
- `;
14347
- closeButton.addEventListener("click", () => {
14348
- const overlay = modal.parentElement;
14349
- if (overlay && document.body.contains(overlay)) {
14350
- document.body.removeChild(overlay);
14351
- }
14352
- });
14353
- resultContainer.appendChild(closeButton);
14354
- modal.appendChild(resultContainer);
14355
- }
14356
- detectGeoRegion() {
14357
- const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
14358
- if (timezone.includes("America")) return "north_america";
14359
- if (timezone.includes("Europe")) return "europe";
14360
- if (timezone.includes("Asia/Kolkata") || timezone.includes("Asia/Calcutta")) return "india";
14361
- if (timezone.includes("Asia/Singapore") || timezone.includes("Asia/Bangkok") || timezone.includes("Asia/Jakarta")) return "southeast_asia";
14362
- if (timezone.includes("Asia/Dubai") || timezone.includes("Asia/Riyadh")) return "middle_east";
14363
- if (timezone.includes("America") && (timezone.includes("Sao_Paulo") || timezone.includes("Buenos_Aires"))) return "latin_america";
14364
- if (timezone.includes("Australia") || timezone.includes("Pacific/Auckland")) return "oceania";
14365
- return "north_america";
14366
- }
14367
13814
  getUTMParams() {
14368
13815
  const params = new URLSearchParams(window.location.search);
14369
13816
  return {
@@ -14372,119 +13819,6 @@ class AegisWidgetManager {
14372
13819
  utm_campaign: params.get("utm_campaign") || void 0
14373
13820
  };
14374
13821
  }
14375
- getSessionId() {
14376
- return sessionStorage.getItem("aegis_session_id");
14377
- }
14378
- getAnonymousId() {
14379
- return localStorage.getItem("aegis_anonymous_id");
14380
- }
14381
- renderScratchCard(widget) {
14382
- const overlay = document.createElement("div");
14383
- overlay.className = "aegis-gamification-overlay";
14384
- overlay.setAttribute("data-widget-id", widget.widget_id);
14385
- overlay.style.cssText = `
14386
- position: fixed;
14387
- top: 0;
14388
- left: 0;
14389
- right: 0;
14390
- bottom: 0;
14391
- background: rgba(0, 0, 0, 0.7);
14392
- z-index: 1000000;
14393
- display: flex;
14394
- align-items: center;
14395
- justify-content: center;
14396
- animation: aegisFadeIn 0.3s ease-out;
14397
- `;
14398
- const modal = document.createElement("div");
14399
- modal.className = "aegis-scratch-card-modal";
14400
- modal.style.cssText = `
14401
- background: white;
14402
- border-radius: 16px;
14403
- padding: 32px;
14404
- max-width: 500px;
14405
- width: 90%;
14406
- box-shadow: 0 10px 40px rgba(0,0,0,0.3);
14407
- text-align: center;
14408
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
14409
- animation: aegisScaleIn 0.3s ease-out;
14410
- `;
14411
- const title = document.createElement("h2");
14412
- title.textContent = widget.config.title || "Scratch & Win!";
14413
- title.style.cssText = "margin: 0 0 16px 0; font-size: 24px; font-weight: 700; color: #1a1a1a;";
14414
- modal.appendChild(title);
14415
- const description = document.createElement("p");
14416
- description.textContent = widget.config.description || "Scratch to reveal your prize!";
14417
- description.style.cssText = "margin: 0 0 24px 0; font-size: 14px; color: #666;";
14418
- modal.appendChild(description);
14419
- const canvas = document.createElement("canvas");
14420
- canvas.width = 300;
14421
- canvas.height = 200;
14422
- canvas.style.cssText = `
14423
- border-radius: 8px;
14424
- cursor: pointer;
14425
- margin: 0 auto 24px;
14426
- display: block;
14427
- box-shadow: 0 4px 12px rgba(0,0,0,0.1);
14428
- `;
14429
- modal.appendChild(canvas);
14430
- const ctx = canvas.getContext("2d");
14431
- if (ctx) {
14432
- ctx.fillStyle = "#c0c0c0";
14433
- ctx.fillRect(0, 0, canvas.width, canvas.height);
14434
- ctx.fillStyle = "#888";
14435
- ctx.font = "20px Arial";
14436
- ctx.textAlign = "center";
14437
- ctx.fillText("Scratch here!", canvas.width / 2, canvas.height / 2);
14438
- let isScratching = false;
14439
- const scratch = (x, y) => {
14440
- ctx.globalCompositeOperation = "destination-out";
14441
- ctx.beginPath();
14442
- ctx.arc(x, y, 20, 0, Math.PI * 2);
14443
- ctx.fill();
14444
- };
14445
- canvas.addEventListener("mousedown", () => {
14446
- isScratching = true;
14447
- });
14448
- canvas.addEventListener("mouseup", () => {
14449
- isScratching = false;
14450
- });
14451
- canvas.addEventListener("mousemove", (e) => {
14452
- if (isScratching) {
14453
- const rect = canvas.getBoundingClientRect();
14454
- scratch(e.clientX - rect.left, e.clientY - rect.top);
14455
- }
14456
- });
14457
- }
14458
- const scratchButton = document.createElement("button");
14459
- scratchButton.textContent = "REVEAL PRIZE";
14460
- scratchButton.style.cssText = `
14461
- background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
14462
- color: white;
14463
- border: none;
14464
- padding: 16px 48px;
14465
- border-radius: 8px;
14466
- font-weight: 700;
14467
- font-size: 16px;
14468
- cursor: pointer;
14469
- `;
14470
- scratchButton.addEventListener("click", async () => {
14471
- scratchButton.disabled = true;
14472
- scratchButton.textContent = "REVEALING...";
14473
- try {
14474
- const prize = await this.generatePrize(widget.config.config_id);
14475
- this.showPrizeResult(modal, prize);
14476
- } catch (error) {
14477
- this.log(`Error generating prize: ${error}`, true);
14478
- scratchButton.disabled = false;
14479
- scratchButton.textContent = "TRY AGAIN";
14480
- }
14481
- });
14482
- modal.appendChild(scratchButton);
14483
- overlay.appendChild(modal);
14484
- this.addAnimationStyles();
14485
- overlay.setAttribute("data-aegis-widget-root", "");
14486
- document.body.appendChild(overlay);
14487
- }
14488
13822
  renderToast(widget) {
14489
13823
  const { message, icon, duration } = widget.config;
14490
13824
  const position = widget.position || "bottom_left";
@@ -14757,83 +14091,6 @@ class AegisWidgetManager {
14757
14091
  overlay.setAttribute("data-aegis-widget-root", "");
14758
14092
  document.body.appendChild(overlay);
14759
14093
  }
14760
- async generatePrize(configId) {
14761
- const url = `${this.apiHost}/v1/widgets/gamification/generate-prize`;
14762
- const headers = {
14763
- "X-Aegis-Write-Key": this.writeKey,
14764
- "Content-Type": "application/json"
14765
- };
14766
- if (this.userId) headers["X-User-ID"] = this.userId;
14767
- if (this.contactId) headers["X-Contact-ID"] = this.contactId;
14768
- if (this.contactId && this.contactToken) headers["X-Contact-Token"] = this.contactToken;
14769
- if (this.organizationId) headers["X-Organization-ID"] = this.organizationId;
14770
- const response = await fetch(url, {
14771
- method: "POST",
14772
- headers,
14773
- body: JSON.stringify({ config_id: configId }),
14774
- credentials: "include"
14775
- });
14776
- if (!response.ok) {
14777
- throw new Error(`Failed to generate prize: ${response.status}`);
14778
- }
14779
- return await response.json();
14780
- }
14781
- showPrizeResult(modal, prize) {
14782
- modal.innerHTML = "";
14783
- const resultContainer = document.createElement("div");
14784
- resultContainer.style.cssText = "text-align: center; padding: 40px 20px;";
14785
- const emoji = document.createElement("div");
14786
- emoji.textContent = "🎉";
14787
- emoji.style.cssText = "font-size: 60px; margin-bottom: 20px;";
14788
- resultContainer.appendChild(emoji);
14789
- const title = document.createElement("h2");
14790
- title.textContent = "Congratulations!";
14791
- title.style.cssText = "margin: 0 0 12px 0; font-size: 28px; font-weight: 700; color: #1a73e8;";
14792
- resultContainer.appendChild(title);
14793
- const prizeLabel = document.createElement("p");
14794
- prizeLabel.textContent = prize.prize_label;
14795
- prizeLabel.style.cssText = "margin: 0 0 20px 0; font-size: 20px; font-weight: 600; color: #333;";
14796
- resultContainer.appendChild(prizeLabel);
14797
- if (prize.coupon_code) {
14798
- const couponContainer = document.createElement("div");
14799
- couponContainer.style.cssText = `
14800
- background: #f5f5f5;
14801
- padding: 16px;
14802
- border-radius: 8px;
14803
- margin-bottom: 20px;
14804
- border: 2px dashed #1a73e8;
14805
- `;
14806
- const couponLabel = document.createElement("div");
14807
- couponLabel.textContent = "Your coupon code:";
14808
- couponLabel.style.cssText = "font-size: 12px; color: #666; margin-bottom: 8px;";
14809
- couponContainer.appendChild(couponLabel);
14810
- const couponCode = document.createElement("div");
14811
- couponCode.textContent = prize.coupon_code;
14812
- couponCode.style.cssText = "font-size: 24px; font-weight: 700; color: #1a73e8; letter-spacing: 2px;";
14813
- couponContainer.appendChild(couponCode);
14814
- resultContainer.appendChild(couponContainer);
14815
- }
14816
- const closeButton = document.createElement("button");
14817
- closeButton.textContent = "Close";
14818
- closeButton.style.cssText = `
14819
- background: #1a73e8;
14820
- color: white;
14821
- border: none;
14822
- padding: 12px 32px;
14823
- border-radius: 8px;
14824
- font-weight: 600;
14825
- font-size: 14px;
14826
- cursor: pointer;
14827
- `;
14828
- closeButton.addEventListener("click", () => {
14829
- const overlay = modal.parentElement;
14830
- if (overlay && document.body.contains(overlay)) {
14831
- document.body.removeChild(overlay);
14832
- }
14833
- });
14834
- resultContainer.appendChild(closeButton);
14835
- modal.appendChild(resultContainer);
14836
- }
14837
14094
  async submitFeedback(widgetId, data) {
14838
14095
  var _a;
14839
14096
  const url = `${this.apiHost}/v1/widgets/track-event`;
@@ -15389,8 +14646,9 @@ const _AegisMessageRuntime = class _AegisMessageRuntime {
15389
14646
  constructor(config) {
15390
14647
  var _a;
15391
14648
  this.initialized = false;
14649
+ this.armedInAppTriggers = /* @__PURE__ */ new Set();
15392
14650
  {
15393
- const v = "1.40.0";
14651
+ const v = "1.41.0";
15394
14652
  try {
15395
14653
  globalThis.__AEGIS_SDK_VERSION__ = v;
15396
14654
  } catch {
@@ -15409,6 +14667,8 @@ const _AegisMessageRuntime = class _AegisMessageRuntime {
15409
14667
  }
15410
14668
  const triggerEngine = config.triggerEngine ?? new TriggerEngine();
15411
14669
  const ownsTriggerEngine = !config.triggerEngine;
14670
+ this.triggerEngine = triggerEngine;
14671
+ this.ownsTriggerEngine = ownsTriggerEngine;
15412
14672
  this.contactId = config.contactId;
15413
14673
  this.contactToken = config.contactToken;
15414
14674
  this.pairingApiHost = config.apiHost ?? "";
@@ -15445,12 +14705,6 @@ const _AegisMessageRuntime = class _AegisMessageRuntime {
15445
14705
  propertyId: config.propertyId,
15446
14706
  debugMode: config.debugMode,
15447
14707
  enableSSE: config.enableSSE,
15448
- // Route gamification sub_types straight to the widget renderer —
15449
- // replaces the old CustomEvent('aegis:render-widget') bridge which
15450
- // had no listener and silently dropped impressions.
15451
- onInteractiveCampaign: (campaign) => {
15452
- this.widgets.renderInteractiveCampaign(campaign);
15453
- },
15454
14708
  getWorkspaceId: config.getWorkspaceId,
15455
14709
  // First-party cart reader — renderers (progress-bar) read live cart
15456
14710
  // state from here instead of `window.*` cart globals. See setCartState().
@@ -15489,13 +14743,36 @@ const _AegisMessageRuntime = class _AegisMessageRuntime {
15489
14743
  debugMode: config.debugMode,
15490
14744
  ...config.chat
15491
14745
  });
15492
- const proactive = ((_a = config.chat) == null ? void 0 : _a.proactiveTriggers) ?? [];
15493
- for (const evt of proactive) {
15494
- triggerEngine.on(evt, () => {
14746
+ const proactive = (((_a = config.chat) == null ? void 0 : _a.proactiveTriggers) ?? []).map(triggerFromEngineEvent);
14747
+ armBehaviouralTriggers(
14748
+ triggerEngine,
14749
+ proactive,
14750
+ /* @__PURE__ */ new Set(),
14751
+ () => {
15495
14752
  var _a2;
15496
14753
  return (_a2 = this.chat) == null ? void 0 : _a2.openPanel();
15497
- });
15498
- }
14754
+ },
14755
+ _AegisMessageRuntime.hasNoHover()
14756
+ );
14757
+ }
14758
+ this.unsubscribeCampaignsLoaded = this.inApp.onCampaignsLoaded((campaigns) => {
14759
+ armBehaviouralTriggers(
14760
+ triggerEngine,
14761
+ campaigns.map((c) => c.client_trigger),
14762
+ this.armedInAppTriggers,
14763
+ (eventName) => this.inApp.onClientEvent(eventName),
14764
+ _AegisMessageRuntime.hasNoHover()
14765
+ );
14766
+ });
14767
+ }
14768
+ /** No hovering pointer (phones, tablets): exit intent is a fast scroll up,
14769
+ * not the mouse leaving the window. */
14770
+ static hasNoHover() {
14771
+ var _a, _b;
14772
+ try {
14773
+ return typeof window !== "undefined" && ((_b = (_a = window.matchMedia) == null ? void 0 : _a.call(window, "(hover: none)")) == null ? void 0 : _b.matches) === true;
14774
+ } catch {
14775
+ return false;
15499
14776
  }
15500
14777
  }
15501
14778
  /**
@@ -15512,6 +14789,9 @@ const _AegisMessageRuntime = class _AegisMessageRuntime {
15512
14789
  this.widgets.initialize()
15513
14790
  ]);
15514
14791
  (_a = this.chat) == null ? void 0 : _a.initialize();
14792
+ if (this.ownsTriggerEngine) {
14793
+ this.triggerEngine.start();
14794
+ }
15515
14795
  if (this.contactId) {
15516
14796
  void this.contactScores.fetchForContact(this.contactId, { contactToken: this.contactToken });
15517
14797
  }
@@ -15721,6 +15001,7 @@ const _AegisMessageRuntime = class _AegisMessageRuntime {
15721
15001
  */
15722
15002
  destroy() {
15723
15003
  var _a, _b, _c;
15004
+ this.unsubscribeCampaignsLoaded();
15724
15005
  (_b = (_a = this.inApp).destroy) == null ? void 0 : _b.call(_a);
15725
15006
  this.widgets.destroy();
15726
15007
  (_c = this.chat) == null ? void 0 : _c.destroy();
@@ -15729,8 +15010,8 @@ const _AegisMessageRuntime = class _AegisMessageRuntime {
15729
15010
  /**
15730
15011
  * Current armed campaigns visible to the InAppManager. Accessible for
15731
15012
  * the Prefetch Inspector and for debugging. WidgetManager's prefetched
15732
- * spin_wheel / scratch_card configs are NOT in this list — they live
15733
- * in `this.widgets` and have their own lifecycle.
15013
+ * exit-intent popup config is NOT in this list — it lives in
15014
+ * `this.widgets` and has its own lifecycle.
15734
15015
  */
15735
15016
  getCampaigns() {
15736
15017
  return this.inApp.campaigns ?? [];