@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/aegis.min.js +1 -1
- package/dist/aegis.min.js.map +1 -1
- package/dist/inapp/AegisInAppManager.d.ts +3 -9
- package/dist/inapp/AegisInAppManager.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +128 -847
- package/dist/index.js.map +1 -1
- package/dist/runtime/AegisMessageRuntime.d.ts +18 -11
- package/dist/runtime/AegisMessageRuntime.d.ts.map +1 -1
- package/dist/triggers/TriggerEngine.d.ts.map +1 -1
- package/dist/triggers/behaviouralTriggers.d.ts +41 -0
- package/dist/triggers/behaviouralTriggers.d.ts.map +1 -0
- package/dist/widgets/AegisWidgetManager.d.ts +3 -71
- package/dist/widgets/AegisWidgetManager.d.ts.map +1 -1
- package/dist/widgets/index.d.ts +1 -1
- package/dist/widgets/index.d.ts.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
15494
|
-
triggerEngine
|
|
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
|
-
*
|
|
15733
|
-
*
|
|
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 ?? [];
|