@active-reach/web-sdk 1.0.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.
Files changed (80) hide show
  1. package/README.md +576 -0
  2. package/dist/aegis-sw.js +1 -0
  3. package/dist/aegis.min.js +2 -0
  4. package/dist/aegis.min.js.map +1 -0
  5. package/dist/analytics-B11keZ55.mjs +1854 -0
  6. package/dist/analytics-B11keZ55.mjs.map +1 -0
  7. package/dist/cdn.d.ts +18 -0
  8. package/dist/cdn.d.ts.map +1 -0
  9. package/dist/core/analytics.d.ts +47 -0
  10. package/dist/core/analytics.d.ts.map +1 -0
  11. package/dist/core/queue.d.ts +35 -0
  12. package/dist/core/queue.d.ts.map +1 -0
  13. package/dist/core/sdk-config-poller.d.ts +59 -0
  14. package/dist/core/sdk-config-poller.d.ts.map +1 -0
  15. package/dist/core/session.d.ts +41 -0
  16. package/dist/core/session.d.ts.map +1 -0
  17. package/dist/core/transport.d.ts +50 -0
  18. package/dist/core/transport.d.ts.map +1 -0
  19. package/dist/inapp/AegisInAppManager.d.ts +126 -0
  20. package/dist/inapp/AegisInAppManager.d.ts.map +1 -0
  21. package/dist/inapp/index.d.ts +4 -0
  22. package/dist/inapp/index.d.ts.map +1 -0
  23. package/dist/inapp/renderPreview.d.ts +25 -0
  24. package/dist/inapp/renderPreview.d.ts.map +1 -0
  25. package/dist/index.d.ts +23 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +4296 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/integrations/react.d.ts +19 -0
  30. package/dist/integrations/react.d.ts.map +1 -0
  31. package/dist/placements/AegisPlacementManager.d.ts +88 -0
  32. package/dist/placements/AegisPlacementManager.d.ts.map +1 -0
  33. package/dist/plugins/registry.d.ts +13 -0
  34. package/dist/plugins/registry.d.ts.map +1 -0
  35. package/dist/push/AegisWebPush.d.ts +51 -0
  36. package/dist/push/AegisWebPush.d.ts.map +1 -0
  37. package/dist/push/AegisWebPush.js +203 -0
  38. package/dist/push/AegisWebPush.js.map +1 -0
  39. package/dist/react.js +70 -0
  40. package/dist/react.js.map +1 -0
  41. package/dist/snippet.min.js +1 -0
  42. package/dist/triggers/TriggerEngine.d.ts +114 -0
  43. package/dist/triggers/TriggerEngine.d.ts.map +1 -0
  44. package/dist/triggers/index.d.ts +3 -0
  45. package/dist/triggers/index.d.ts.map +1 -0
  46. package/dist/types/config.d.ts +73 -0
  47. package/dist/types/config.d.ts.map +1 -0
  48. package/dist/types/events.d.ts +127 -0
  49. package/dist/types/events.d.ts.map +1 -0
  50. package/dist/types/index.d.ts +5 -0
  51. package/dist/types/index.d.ts.map +1 -0
  52. package/dist/types/plugin.d.ts +21 -0
  53. package/dist/types/plugin.d.ts.map +1 -0
  54. package/dist/types/type-safe-events.d.ts +14 -0
  55. package/dist/types/type-safe-events.d.ts.map +1 -0
  56. package/dist/utils/canonical-identity.d.ts +29 -0
  57. package/dist/utils/canonical-identity.d.ts.map +1 -0
  58. package/dist/utils/consent.d.ts +40 -0
  59. package/dist/utils/consent.d.ts.map +1 -0
  60. package/dist/utils/debounce.d.ts +3 -0
  61. package/dist/utils/debounce.d.ts.map +1 -0
  62. package/dist/utils/device.d.ts +6 -0
  63. package/dist/utils/device.d.ts.map +1 -0
  64. package/dist/utils/identity.d.ts +27 -0
  65. package/dist/utils/identity.d.ts.map +1 -0
  66. package/dist/utils/index.d.ts +7 -0
  67. package/dist/utils/index.d.ts.map +1 -0
  68. package/dist/utils/logger.d.ts +15 -0
  69. package/dist/utils/logger.d.ts.map +1 -0
  70. package/dist/utils/storage.d.ts +20 -0
  71. package/dist/utils/storage.d.ts.map +1 -0
  72. package/dist/utils/url-parser.d.ts +23 -0
  73. package/dist/utils/url-parser.d.ts.map +1 -0
  74. package/dist/utils/uuid.d.ts +4 -0
  75. package/dist/utils/uuid.d.ts.map +1 -0
  76. package/dist/widgets/AegisWidgetManager.d.ts +194 -0
  77. package/dist/widgets/AegisWidgetManager.d.ts.map +1 -0
  78. package/dist/widgets/index.d.ts +3 -0
  79. package/dist/widgets/index.d.ts.map +1 -0
  80. package/package.json +73 -0
package/dist/index.js ADDED
@@ -0,0 +1,4296 @@
1
+ import { l as logger, A as Aegis } from "./analytics-B11keZ55.mjs";
2
+ import { AegisWebPush } from "./push/AegisWebPush.js";
3
+ function debounce(func, wait) {
4
+ let timeoutId = null;
5
+ return function debounced(...args) {
6
+ if (timeoutId !== null) {
7
+ clearTimeout(timeoutId);
8
+ }
9
+ timeoutId = setTimeout(() => {
10
+ func(...args);
11
+ timeoutId = null;
12
+ }, wait);
13
+ };
14
+ }
15
+ function throttle(func, limit) {
16
+ let inThrottle = false;
17
+ return function throttled(...args) {
18
+ if (!inThrottle) {
19
+ func(...args);
20
+ inThrottle = true;
21
+ setTimeout(() => {
22
+ inThrottle = false;
23
+ }, limit);
24
+ }
25
+ };
26
+ }
27
+ class AegisInAppManager {
28
+ constructor(config) {
29
+ this.campaigns = [];
30
+ this.displayedCampaigns = /* @__PURE__ */ new Set();
31
+ this.isInitialized = false;
32
+ this.reconnectAttempts = 0;
33
+ this.maxReconnectAttempts = 5;
34
+ this.writeKey = config.writeKey;
35
+ this.apiHost = config.apiHost || "https://api.aegis.ai";
36
+ this.userId = config.userId;
37
+ this.contactId = config.contactId;
38
+ this.organizationId = config.organizationId;
39
+ this.debugMode = config.debugMode || false;
40
+ this.enableSSE = config.enableSSE !== false;
41
+ }
42
+ async initialize() {
43
+ if (this.isInitialized) {
44
+ this.log("AegisInApp already initialized");
45
+ return;
46
+ }
47
+ if (typeof localStorage !== "undefined") {
48
+ localStorage.setItem("aegis_returning_user", "1");
49
+ }
50
+ await this.refreshCampaigns();
51
+ if (this.enableSSE && this.organizationId) {
52
+ this.connectSSE();
53
+ }
54
+ this.isInitialized = true;
55
+ this.log("AegisInApp initialized successfully");
56
+ }
57
+ updateUserId(userId) {
58
+ this.userId = userId;
59
+ this.refreshCampaigns();
60
+ }
61
+ updateContactId(contactId) {
62
+ this.contactId = contactId;
63
+ this.disconnectSSE();
64
+ if (this.enableSSE && this.organizationId) {
65
+ this.connectSSE();
66
+ }
67
+ this.refreshCampaigns();
68
+ }
69
+ connectSSE() {
70
+ if (this.eventSource) {
71
+ this.disconnectSSE();
72
+ }
73
+ if (!this.organizationId) {
74
+ this.log("Cannot connect SSE without organization ID", "warn");
75
+ return;
76
+ }
77
+ const url = new URL("/v1/stream/realtime", this.apiHost);
78
+ const headers = {
79
+ "X-Aegis-Write-Key": this.writeKey,
80
+ "X-Organization-ID": this.organizationId
81
+ };
82
+ if (this.contactId) {
83
+ headers["X-Contact-ID"] = this.contactId;
84
+ }
85
+ const queryParams = new URLSearchParams();
86
+ Object.entries(headers).forEach(([key, value]) => {
87
+ queryParams.append(key, value);
88
+ });
89
+ this.eventSource = new EventSource(`${url}?${queryParams.toString()}`);
90
+ this.eventSource.addEventListener("open", () => {
91
+ this.log("SSE connection established");
92
+ this.reconnectAttempts = 0;
93
+ });
94
+ this.eventSource.addEventListener("in_app_campaign_updated", (event) => {
95
+ try {
96
+ const data = JSON.parse(event.data);
97
+ this.log(`Received in-app campaign update: ${data.campaign_id}`);
98
+ this.refreshCampaigns();
99
+ } catch (error) {
100
+ this.log(`Error parsing SSE event: ${error}`, "error");
101
+ }
102
+ });
103
+ this.eventSource.addEventListener("heartbeat", (event) => {
104
+ this.log("SSE heartbeat received");
105
+ });
106
+ this.eventSource.addEventListener("error", (error) => {
107
+ var _a;
108
+ this.log("SSE connection error", "error");
109
+ if (((_a = this.eventSource) == null ? void 0 : _a.readyState) === EventSource.CLOSED) {
110
+ this.attemptReconnect();
111
+ }
112
+ });
113
+ }
114
+ disconnectSSE() {
115
+ if (this.eventSource) {
116
+ this.eventSource.close();
117
+ this.eventSource = void 0;
118
+ this.log("SSE connection closed");
119
+ }
120
+ }
121
+ attemptReconnect() {
122
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
123
+ this.log("Max reconnect attempts reached, giving up", "warn");
124
+ return;
125
+ }
126
+ this.reconnectAttempts++;
127
+ const delay = Math.min(1e3 * Math.pow(2, this.reconnectAttempts), 3e4);
128
+ this.log(`Reconnecting SSE in ${delay}ms (attempt ${this.reconnectAttempts})`);
129
+ setTimeout(() => {
130
+ if (this.isInitialized && this.enableSSE && this.organizationId) {
131
+ this.connectSSE();
132
+ }
133
+ }, delay);
134
+ }
135
+ async refreshCampaigns() {
136
+ try {
137
+ const context = new URLSearchParams({
138
+ device_type: this.detectDeviceType(),
139
+ page_url: typeof window !== "undefined" ? window.location.pathname : "/"
140
+ });
141
+ context.set("is_new_user", this.isNewUser() ? "true" : "false");
142
+ const url = `${this.apiHost}/v1/in-app/active?${context.toString()}`;
143
+ const headers = {
144
+ "X-Aegis-Write-Key": this.writeKey,
145
+ "Content-Type": "application/json"
146
+ };
147
+ if (this.userId) {
148
+ headers["X-User-ID"] = this.userId;
149
+ }
150
+ if (this.contactId) {
151
+ headers["X-Contact-ID"] = this.contactId;
152
+ }
153
+ if (this.organizationId) {
154
+ headers["X-Organization-ID"] = this.organizationId;
155
+ }
156
+ const abAssignments = this.getABAssignments();
157
+ if (Object.keys(abAssignments).length > 0) {
158
+ headers["X-AB-Assignments"] = btoa(JSON.stringify(abAssignments));
159
+ }
160
+ const response = await fetch(url, {
161
+ method: "GET",
162
+ headers
163
+ });
164
+ if (!response.ok) {
165
+ throw new Error(`Failed to fetch campaigns: ${response.status}`);
166
+ }
167
+ this.campaigns = await response.json();
168
+ this.processABAssignments(this.campaigns);
169
+ this.log(`Fetched ${this.campaigns.length} campaigns`);
170
+ this.tryDisplayNextCampaign();
171
+ } catch (error) {
172
+ this.log(`Error refreshing campaigns: ${error}`, "error");
173
+ }
174
+ }
175
+ // --- Client Context Helpers (Critical Fix B) ---
176
+ detectDeviceType() {
177
+ if (typeof navigator === "undefined") return "desktop";
178
+ const ua = navigator.userAgent;
179
+ if (/Mobi|Android/i.test(ua)) return "mobile";
180
+ if (/iPad|Tablet/i.test(ua)) return "tablet";
181
+ return "desktop";
182
+ }
183
+ isNewUser() {
184
+ if (typeof localStorage === "undefined") return true;
185
+ return !localStorage.getItem("aegis_returning_user");
186
+ }
187
+ // --- A/B Assignment Persistence (Critical Fix C) ---
188
+ getABAssignments() {
189
+ if (typeof localStorage === "undefined") return {};
190
+ try {
191
+ return JSON.parse(localStorage.getItem("aegis_ab_assignments") || "{}");
192
+ } catch {
193
+ return {};
194
+ }
195
+ }
196
+ processABAssignments(campaigns) {
197
+ if (typeof localStorage === "undefined") return;
198
+ const stored = this.getABAssignments();
199
+ for (const campaign of campaigns) {
200
+ if (campaign.assigned_variant_id) {
201
+ stored[campaign.id] = campaign.assigned_variant_id;
202
+ }
203
+ }
204
+ localStorage.setItem("aegis_ab_assignments", JSON.stringify(stored));
205
+ }
206
+ getVariantId(campaignId) {
207
+ const assignments = this.getABAssignments();
208
+ return assignments[campaignId] ?? void 0;
209
+ }
210
+ tryDisplayNextCampaign() {
211
+ const campaign = this.campaigns.find((c) => !this.displayedCampaigns.has(c.id));
212
+ if (campaign) {
213
+ this.displayCampaign(campaign);
214
+ }
215
+ }
216
+ displayCampaign(campaign) {
217
+ this.displayedCampaigns.add(campaign.id);
218
+ const interactiveSubTypes = /* @__PURE__ */ new Set([
219
+ "spin_wheel",
220
+ "scratch_card",
221
+ "nps_survey",
222
+ "quiz",
223
+ "countdown_offer",
224
+ "star_rating",
225
+ "quick_poll"
226
+ ]);
227
+ if (campaign.sub_type && interactiveSubTypes.has(campaign.sub_type)) {
228
+ this.renderInteractive(campaign);
229
+ this.trackEvent(campaign.id, "impression");
230
+ return;
231
+ }
232
+ switch (campaign.type) {
233
+ case "modal":
234
+ this.renderModal(campaign);
235
+ break;
236
+ case "banner":
237
+ this.renderBanner(campaign);
238
+ break;
239
+ case "full_screen":
240
+ this.renderFullScreen(campaign);
241
+ break;
242
+ case "half_interstitial":
243
+ this.renderHalfInterstitial(campaign);
244
+ break;
245
+ case "alert":
246
+ this.renderAlert(campaign);
247
+ break;
248
+ case "pip":
249
+ this.renderPIP(campaign);
250
+ break;
251
+ case "tooltip":
252
+ this.renderTooltip(campaign);
253
+ break;
254
+ }
255
+ this.trackEvent(campaign.id, "impression");
256
+ }
257
+ /**
258
+ * Renders interactive sub-type campaigns (spin wheel, NPS, quiz, etc.)
259
+ * using DOM-safe rendering. These sub-types use the campaign's
260
+ * interactive_config payload for type-specific behavior.
261
+ */
262
+ renderInteractive(campaign) {
263
+ const ic = campaign.interactive_config || {};
264
+ const bg = this.sanitizeColor(campaign.background_color || "#4169e1");
265
+ const text = this.sanitizeColor(campaign.text_color || "#ffffff");
266
+ switch (campaign.sub_type) {
267
+ case "nps_survey":
268
+ this.renderNPSSurvey(campaign, ic, bg, text);
269
+ break;
270
+ case "countdown_offer":
271
+ this.renderCountdownOffer(campaign, ic, bg, text);
272
+ break;
273
+ case "star_rating":
274
+ this.renderStarRating(campaign, ic, bg, text);
275
+ break;
276
+ case "quick_poll":
277
+ this.renderQuickPoll(campaign, ic, bg, text);
278
+ break;
279
+ case "quiz":
280
+ this.renderQuiz(campaign, ic, bg, text);
281
+ break;
282
+ case "spin_wheel":
283
+ case "scratch_card":
284
+ this.log(`${campaign.sub_type} delegated to AegisWidgetManager`);
285
+ if (typeof window !== "undefined") {
286
+ window.dispatchEvent(new CustomEvent("aegis:render-widget", {
287
+ detail: { campaign, sub_type: campaign.sub_type }
288
+ }));
289
+ }
290
+ break;
291
+ default:
292
+ this.log(`Unknown interactive sub_type: ${campaign.sub_type}`, "warn");
293
+ this.renderModal(campaign);
294
+ }
295
+ }
296
+ // --- Interactive Renderers ---
297
+ renderNPSSurvey(campaign, ic, bg, text) {
298
+ const overlay = this.createOverlay("aegis-in-app-nps-overlay");
299
+ const modal = document.createElement("div");
300
+ modal.style.cssText = `
301
+ max-width: 360px; width: 90%; border-radius: 16px; overflow: hidden;
302
+ background: ${bg}; color: ${text}; animation: aegisScaleIn 0.3s ease;
303
+ box-shadow: 0 20px 60px rgba(0,0,0,0.15);
304
+ `;
305
+ const body = document.createElement("div");
306
+ body.style.cssText = "padding: 24px; text-align: center;";
307
+ const question = document.createElement("div");
308
+ question.style.cssText = "font-size: 16px; font-weight: 700; margin-bottom: 16px;";
309
+ question.textContent = ic.nps_question || "How likely are you to recommend us?";
310
+ body.appendChild(question);
311
+ const scale = document.createElement("div");
312
+ scale.style.cssText = "display: flex; gap: 4px; justify-content: center; flex-wrap: wrap; margin-bottom: 12px;";
313
+ for (let i = 0; i <= 10; i++) {
314
+ const btn = document.createElement("span");
315
+ btn.style.cssText = `
316
+ width: 28px; height: 28px; border-radius: 6px; display: flex;
317
+ align-items: center; justify-content: center; font-size: 11px;
318
+ font-weight: 600; cursor: pointer; background: ${text}33; color: ${text};
319
+ transition: transform 0.1s;
320
+ `;
321
+ btn.textContent = String(i);
322
+ btn.addEventListener("click", () => {
323
+ this.trackEvent(campaign.id, "clicked");
324
+ });
325
+ scale.appendChild(btn);
326
+ }
327
+ body.appendChild(scale);
328
+ const labels = document.createElement("div");
329
+ labels.style.cssText = "display: flex; justify-content: space-between; font-size: 11px; opacity: 0.6; margin-bottom: 16px;";
330
+ const notLikely = document.createElement("span");
331
+ notLikely.textContent = "Not likely";
332
+ const veryLikely = document.createElement("span");
333
+ veryLikely.textContent = "Very likely";
334
+ labels.appendChild(notLikely);
335
+ labels.appendChild(veryLikely);
336
+ body.appendChild(labels);
337
+ this.addCloseButton(body, overlay, campaign.id);
338
+ modal.appendChild(body);
339
+ overlay.appendChild(modal);
340
+ this.addAnimationStyles();
341
+ document.body.appendChild(overlay);
342
+ }
343
+ renderCountdownOffer(campaign, ic, bg, text) {
344
+ const overlay = this.createOverlay("aegis-in-app-countdown-overlay");
345
+ const modal = document.createElement("div");
346
+ modal.style.cssText = `
347
+ max-width: 320px; width: 90%; border-radius: 16px; overflow: hidden;
348
+ background: ${bg}; color: ${text}; animation: aegisScaleIn 0.3s ease;
349
+ box-shadow: 0 20px 60px rgba(0,0,0,0.15);
350
+ `;
351
+ const body = document.createElement("div");
352
+ body.style.cssText = "padding: 24px; text-align: center;";
353
+ const title = document.createElement("div");
354
+ title.style.cssText = "font-size: 20px; font-weight: 700; margin-bottom: 8px;";
355
+ title.textContent = campaign.title || "Flash Sale";
356
+ body.appendChild(title);
357
+ const label = document.createElement("div");
358
+ label.style.cssText = "font-size: 13px; opacity: 0.8; margin-bottom: 12px;";
359
+ label.textContent = ic.countdown_label || "Sale ends in:";
360
+ body.appendChild(label);
361
+ const digits = document.createElement("div");
362
+ digits.style.cssText = "display: flex; gap: 8px; justify-content: center; margin-bottom: 16px;";
363
+ const digitStyle = `padding: 8px 12px; border-radius: 8px; font-size: 24px; font-weight: 700; font-family: monospace; background: ${text}22;`;
364
+ for (const val of ["00", ":", "00", ":", "00"]) {
365
+ const el = document.createElement("span");
366
+ if (val === ":") {
367
+ el.style.cssText = "font-size: 24px; font-weight: 700; align-self: center;";
368
+ } else {
369
+ el.style.cssText = digitStyle;
370
+ }
371
+ el.textContent = val;
372
+ digits.appendChild(el);
373
+ }
374
+ body.appendChild(digits);
375
+ const targetStr = ic.countdown_target;
376
+ if (targetStr) {
377
+ const target = new Date(targetStr).getTime();
378
+ const update = () => {
379
+ const diff = Math.max(0, target - Date.now());
380
+ const h = String(Math.floor(diff / 36e5)).padStart(2, "0");
381
+ const m = String(Math.floor(diff % 36e5 / 6e4)).padStart(2, "0");
382
+ const s = String(Math.floor(diff % 6e4 / 1e3)).padStart(2, "0");
383
+ const spans = digits.querySelectorAll("span");
384
+ if (spans.length >= 5) {
385
+ spans[0].textContent = h;
386
+ spans[2].textContent = m;
387
+ spans[4].textContent = s;
388
+ }
389
+ if (diff > 0) requestAnimationFrame(update);
390
+ };
391
+ update();
392
+ }
393
+ if (campaign.body) {
394
+ const desc = document.createElement("div");
395
+ desc.style.cssText = "font-size: 14px; opacity: 0.85; margin-bottom: 16px;";
396
+ desc.textContent = campaign.body;
397
+ body.appendChild(desc);
398
+ }
399
+ if (campaign.button_text) {
400
+ const btn = this.createCTAButton(campaign, bg, text);
401
+ body.appendChild(btn);
402
+ }
403
+ this.addCloseButton(body, overlay, campaign.id);
404
+ modal.appendChild(body);
405
+ overlay.appendChild(modal);
406
+ this.addAnimationStyles();
407
+ document.body.appendChild(overlay);
408
+ }
409
+ renderStarRating(campaign, ic, bg, text) {
410
+ const overlay = this.createOverlay("aegis-in-app-rating-overlay");
411
+ const modal = document.createElement("div");
412
+ modal.style.cssText = `
413
+ max-width: 320px; width: 90%; border-radius: 16px; overflow: hidden;
414
+ background: ${bg}; color: ${text}; animation: aegisScaleIn 0.3s ease;
415
+ box-shadow: 0 20px 60px rgba(0,0,0,0.15);
416
+ `;
417
+ const body = document.createElement("div");
418
+ body.style.cssText = "padding: 24px; text-align: center;";
419
+ const title = document.createElement("div");
420
+ title.style.cssText = "font-size: 18px; font-weight: 700; margin-bottom: 16px;";
421
+ title.textContent = campaign.title || "Rate your experience";
422
+ body.appendChild(title);
423
+ const stars = document.createElement("div");
424
+ stars.style.cssText = "display: flex; gap: 8px; justify-content: center; margin-bottom: 16px;";
425
+ const maxStars = ic.rating_scale || 5;
426
+ for (let i = 1; i <= maxStars; i++) {
427
+ const star = document.createElement("span");
428
+ star.style.cssText = "font-size: 32px; cursor: pointer; transition: transform 0.1s;";
429
+ star.textContent = "☆";
430
+ star.addEventListener("click", () => {
431
+ this.trackEvent(campaign.id, "clicked");
432
+ });
433
+ star.addEventListener("mouseenter", () => {
434
+ star.style.transform = "scale(1.2)";
435
+ });
436
+ star.addEventListener("mouseleave", () => {
437
+ star.style.transform = "scale(1)";
438
+ });
439
+ stars.appendChild(star);
440
+ }
441
+ body.appendChild(stars);
442
+ this.addCloseButton(body, overlay, campaign.id);
443
+ modal.appendChild(body);
444
+ overlay.appendChild(modal);
445
+ this.addAnimationStyles();
446
+ document.body.appendChild(overlay);
447
+ }
448
+ renderQuickPoll(campaign, ic, bg, text) {
449
+ const overlay = this.createOverlay("aegis-in-app-poll-overlay");
450
+ const modal = document.createElement("div");
451
+ modal.style.cssText = `
452
+ max-width: 320px; width: 90%; border-radius: 16px; overflow: hidden;
453
+ background: ${bg}; color: ${text}; animation: aegisScaleIn 0.3s ease;
454
+ box-shadow: 0 20px 60px rgba(0,0,0,0.15);
455
+ `;
456
+ const body = document.createElement("div");
457
+ body.style.cssText = "padding: 24px; text-align: center;";
458
+ const title = document.createElement("div");
459
+ title.style.cssText = "font-size: 16px; font-weight: 700; margin-bottom: 16px;";
460
+ title.textContent = campaign.title || "Quick question";
461
+ body.appendChild(title);
462
+ const options = ic.poll_options || [];
463
+ const optionsList = document.createElement("div");
464
+ optionsList.style.cssText = "display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px;";
465
+ for (const opt of options) {
466
+ const optBtn = document.createElement("button");
467
+ optBtn.style.cssText = `
468
+ padding: 10px 16px; border-radius: 10px; border: 1px solid ${text}33;
469
+ background: transparent; color: ${text}; font-size: 14px; cursor: pointer;
470
+ text-align: left; transition: background 0.15s;
471
+ `;
472
+ optBtn.textContent = opt;
473
+ optBtn.addEventListener("click", () => {
474
+ this.trackEvent(campaign.id, "clicked");
475
+ });
476
+ optionsList.appendChild(optBtn);
477
+ }
478
+ body.appendChild(optionsList);
479
+ this.addCloseButton(body, overlay, campaign.id);
480
+ modal.appendChild(body);
481
+ overlay.appendChild(modal);
482
+ this.addAnimationStyles();
483
+ document.body.appendChild(overlay);
484
+ }
485
+ renderQuiz(campaign, ic, bg, text) {
486
+ const overlay = this.createOverlay("aegis-in-app-quiz-overlay");
487
+ const modal = document.createElement("div");
488
+ modal.style.cssText = `
489
+ max-width: 360px; width: 90%; border-radius: 16px; overflow: hidden;
490
+ background: ${bg}; color: ${text}; animation: aegisScaleIn 0.3s ease;
491
+ box-shadow: 0 20px 60px rgba(0,0,0,0.15);
492
+ `;
493
+ const body = document.createElement("div");
494
+ body.style.cssText = "padding: 24px; text-align: center;";
495
+ const title = document.createElement("div");
496
+ title.style.cssText = "font-size: 18px; font-weight: 700; margin-bottom: 8px;";
497
+ title.textContent = campaign.title || "Quiz";
498
+ body.appendChild(title);
499
+ const questions = ic.questions || [];
500
+ let currentQ = 0;
501
+ const renderQuestion = () => {
502
+ while (body.childNodes.length > 1) {
503
+ body.removeChild(body.lastChild);
504
+ }
505
+ if (currentQ >= questions.length) {
506
+ const result = document.createElement("div");
507
+ result.style.cssText = "font-size: 16px; margin: 16px 0;";
508
+ result.textContent = ic.thank_you_message || "Thanks for completing the quiz!";
509
+ body.appendChild(result);
510
+ this.trackEvent(campaign.id, "clicked");
511
+ this.addCloseButton(body, overlay, campaign.id);
512
+ return;
513
+ }
514
+ const q = questions[currentQ];
515
+ const progress = document.createElement("div");
516
+ progress.style.cssText = "font-size: 12px; opacity: 0.6; margin-bottom: 12px;";
517
+ progress.textContent = `Question ${currentQ + 1} of ${questions.length}`;
518
+ body.appendChild(progress);
519
+ const questionText = document.createElement("div");
520
+ questionText.style.cssText = "font-size: 14px; font-weight: 600; margin-bottom: 16px;";
521
+ questionText.textContent = q.question;
522
+ body.appendChild(questionText);
523
+ const optionsDiv = document.createElement("div");
524
+ optionsDiv.style.cssText = "display: flex; flex-direction: column; gap: 8px;";
525
+ for (const opt of q.options) {
526
+ const optBtn = document.createElement("button");
527
+ optBtn.style.cssText = `
528
+ padding: 10px 16px; border-radius: 10px; border: 1px solid ${text}33;
529
+ background: transparent; color: ${text}; font-size: 14px; cursor: pointer;
530
+ text-align: left; transition: background 0.15s;
531
+ `;
532
+ optBtn.textContent = opt;
533
+ optBtn.addEventListener("click", () => {
534
+ currentQ++;
535
+ renderQuestion();
536
+ });
537
+ optionsDiv.appendChild(optBtn);
538
+ }
539
+ body.appendChild(optionsDiv);
540
+ this.addCloseButton(body, overlay, campaign.id);
541
+ };
542
+ renderQuestion();
543
+ modal.appendChild(body);
544
+ overlay.appendChild(modal);
545
+ this.addAnimationStyles();
546
+ document.body.appendChild(overlay);
547
+ }
548
+ // --- Shared Rendering Helpers ---
549
+ createOverlay(className) {
550
+ const overlay = document.createElement("div");
551
+ overlay.className = className;
552
+ overlay.style.cssText = `
553
+ position: fixed; top: 0; left: 0; right: 0; bottom: 0;
554
+ background: rgba(0,0,0,0.5); display: flex; align-items: center;
555
+ justify-content: center; z-index: 99999; animation: aegisFadeIn 0.3s ease;
556
+ `;
557
+ return overlay;
558
+ }
559
+ createCTAButton(campaign, bg, text) {
560
+ const btn = document.createElement("button");
561
+ btn.style.cssText = `
562
+ display: inline-block; padding: 10px 28px; border-radius: 999px;
563
+ font-size: 14px; font-weight: 600; cursor: pointer; border: none;
564
+ background: ${text}; color: ${bg}; transition: transform 0.15s;
565
+ `;
566
+ btn.textContent = campaign.button_text || "OK";
567
+ btn.addEventListener("click", () => {
568
+ this.trackEvent(campaign.id, "clicked");
569
+ if (campaign.action_url) {
570
+ const safeUrl = this.sanitizeUrl(campaign.action_url);
571
+ if (safeUrl) window.open(safeUrl, "_blank");
572
+ }
573
+ });
574
+ return btn;
575
+ }
576
+ addCloseButton(container, overlay, campaignId) {
577
+ const close = document.createElement("div");
578
+ close.style.cssText = "margin-top: 12px; font-size: 12px; opacity: 0.6; cursor: pointer;";
579
+ close.textContent = "Close";
580
+ close.addEventListener("click", () => {
581
+ this.trackEvent(campaignId, "dismissed");
582
+ this.removeModal(overlay);
583
+ });
584
+ container.appendChild(close);
585
+ }
586
+ renderBanner(campaign) {
587
+ const banner = document.createElement("div");
588
+ banner.className = "aegis-in-app-banner";
589
+ banner.setAttribute("data-campaign-id", campaign.id);
590
+ banner.style.cssText = `
591
+ position: fixed;
592
+ top: 0;
593
+ left: 0;
594
+ right: 0;
595
+ background: ${this.sanitizeColor(campaign.background_color || "#1a73e8")};
596
+ color: ${this.sanitizeColor(campaign.text_color || "#ffffff")};
597
+ padding: 16px;
598
+ z-index: 999999;
599
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
600
+ display: flex;
601
+ align-items: center;
602
+ justify-content: space-between;
603
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
604
+ animation: aegisSlideDown 0.3s ease-out;
605
+ `;
606
+ const contentContainer = document.createElement("div");
607
+ contentContainer.style.cssText = "flex: 1; display: flex; align-items: center; gap: 12px;";
608
+ if (campaign.image_url) {
609
+ const img = document.createElement("img");
610
+ const safeUrl = this.sanitizeUrl(campaign.image_url);
611
+ if (safeUrl) {
612
+ img.src = safeUrl;
613
+ img.alt = "";
614
+ img.style.cssText = "width: 40px; height: 40px; border-radius: 4px; object-fit: cover;";
615
+ contentContainer.appendChild(img);
616
+ }
617
+ }
618
+ const textContainer = document.createElement("div");
619
+ textContainer.style.cssText = "flex: 1;";
620
+ const title = document.createElement("div");
621
+ title.textContent = campaign.title;
622
+ title.style.cssText = "font-weight: 600; font-size: 14px; margin-bottom: 4px;";
623
+ textContainer.appendChild(title);
624
+ const body = document.createElement("div");
625
+ body.textContent = campaign.body;
626
+ body.style.cssText = "font-size: 13px; opacity: 0.9;";
627
+ textContainer.appendChild(body);
628
+ contentContainer.appendChild(textContainer);
629
+ const actionsContainer = document.createElement("div");
630
+ actionsContainer.style.cssText = "display: flex; align-items: center; gap: 12px; margin-left: 16px;";
631
+ if (campaign.action_url && campaign.button_text) {
632
+ const ctaButton = document.createElement("button");
633
+ ctaButton.textContent = campaign.button_text;
634
+ ctaButton.style.cssText = `
635
+ background: white;
636
+ color: ${this.sanitizeColor(campaign.background_color || "#1a73e8")};
637
+ border: none;
638
+ padding: 8px 16px;
639
+ border-radius: 4px;
640
+ font-weight: 600;
641
+ cursor: pointer;
642
+ font-size: 13px;
643
+ white-space: nowrap;
644
+ `;
645
+ ctaButton.addEventListener("click", () => {
646
+ this.trackEvent(campaign.id, "clicked");
647
+ const safeUrl = this.sanitizeUrl(campaign.action_url);
648
+ if (safeUrl) {
649
+ window.location.href = safeUrl;
650
+ }
651
+ this.removeBanner(banner);
652
+ });
653
+ actionsContainer.appendChild(ctaButton);
654
+ }
655
+ const closeButton = document.createElement("button");
656
+ closeButton.textContent = "✕";
657
+ closeButton.setAttribute("aria-label", "Close");
658
+ closeButton.style.cssText = `
659
+ background: transparent;
660
+ border: none;
661
+ color: inherit;
662
+ font-size: 20px;
663
+ cursor: pointer;
664
+ padding: 0;
665
+ width: 24px;
666
+ height: 24px;
667
+ display: flex;
668
+ align-items: center;
669
+ justify-content: center;
670
+ opacity: 0.7;
671
+ `;
672
+ closeButton.addEventListener("click", () => {
673
+ this.trackEvent(campaign.id, "dismissed");
674
+ this.removeBanner(banner);
675
+ });
676
+ actionsContainer.appendChild(closeButton);
677
+ banner.appendChild(contentContainer);
678
+ banner.appendChild(actionsContainer);
679
+ this.addAnimationStyles();
680
+ document.body.appendChild(banner);
681
+ }
682
+ renderModal(campaign) {
683
+ const overlay = document.createElement("div");
684
+ overlay.className = "aegis-in-app-modal-overlay";
685
+ overlay.setAttribute("data-campaign-id", campaign.id);
686
+ overlay.style.cssText = `
687
+ position: fixed;
688
+ top: 0;
689
+ left: 0;
690
+ right: 0;
691
+ bottom: 0;
692
+ background: rgba(0, 0, 0, 0.5);
693
+ z-index: 1000000;
694
+ display: flex;
695
+ align-items: center;
696
+ justify-content: center;
697
+ animation: aegisFadeIn 0.3s ease-out;
698
+ `;
699
+ const modal = document.createElement("div");
700
+ modal.className = "aegis-in-app-modal";
701
+ modal.style.cssText = `
702
+ background: white;
703
+ border-radius: 8px;
704
+ max-width: 500px;
705
+ width: 90%;
706
+ max-height: 80vh;
707
+ overflow: auto;
708
+ box-shadow: 0 10px 40px rgba(0,0,0,0.2);
709
+ animation: aegisScaleIn 0.3s ease-out;
710
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
711
+ `;
712
+ if (campaign.image_url) {
713
+ const img = document.createElement("img");
714
+ const safeUrl = this.sanitizeUrl(campaign.image_url);
715
+ if (safeUrl) {
716
+ img.src = safeUrl;
717
+ img.alt = "";
718
+ img.style.cssText = "width: 100%; height: 200px; object-fit: cover; border-radius: 8px 8px 0 0;";
719
+ modal.appendChild(img);
720
+ }
721
+ }
722
+ const content = document.createElement("div");
723
+ content.style.cssText = "padding: 24px;";
724
+ const title = document.createElement("h2");
725
+ title.textContent = campaign.title;
726
+ title.style.cssText = "margin: 0 0 12px 0; font-size: 20px; font-weight: 600; color: #1a1a1a;";
727
+ content.appendChild(title);
728
+ const body = document.createElement("p");
729
+ body.textContent = campaign.body;
730
+ body.style.cssText = "margin: 0 0 20px 0; font-size: 15px; line-height: 1.5; color: #4a4a4a;";
731
+ content.appendChild(body);
732
+ const actions = document.createElement("div");
733
+ actions.style.cssText = "display: flex; gap: 12px; justify-content: flex-end;";
734
+ if (campaign.action_url && campaign.button_text) {
735
+ const ctaButton = document.createElement("button");
736
+ ctaButton.textContent = campaign.button_text;
737
+ ctaButton.style.cssText = `
738
+ background: ${this.sanitizeColor(campaign.background_color || "#1a73e8")};
739
+ color: ${this.sanitizeColor(campaign.text_color || "#ffffff")};
740
+ border: none;
741
+ padding: 10px 24px;
742
+ border-radius: 6px;
743
+ font-weight: 600;
744
+ cursor: pointer;
745
+ font-size: 14px;
746
+ `;
747
+ ctaButton.addEventListener("click", () => {
748
+ this.trackEvent(campaign.id, "clicked");
749
+ const safeUrl = this.sanitizeUrl(campaign.action_url);
750
+ if (safeUrl) {
751
+ window.location.href = safeUrl;
752
+ }
753
+ this.removeModal(overlay);
754
+ });
755
+ actions.appendChild(ctaButton);
756
+ }
757
+ const closeButton = document.createElement("button");
758
+ closeButton.textContent = "Close";
759
+ closeButton.style.cssText = `
760
+ background: transparent;
761
+ border: 1px solid #d0d0d0;
762
+ color: #4a4a4a;
763
+ padding: 10px 24px;
764
+ border-radius: 6px;
765
+ font-weight: 600;
766
+ cursor: pointer;
767
+ font-size: 14px;
768
+ `;
769
+ closeButton.addEventListener("click", () => {
770
+ this.trackEvent(campaign.id, "dismissed");
771
+ this.removeModal(overlay);
772
+ });
773
+ actions.appendChild(closeButton);
774
+ content.appendChild(actions);
775
+ modal.appendChild(content);
776
+ overlay.appendChild(modal);
777
+ this.addAnimationStyles();
778
+ document.body.appendChild(overlay);
779
+ }
780
+ renderFullScreen(campaign) {
781
+ const overlay = document.createElement("div");
782
+ overlay.className = "aegis-in-app-fullscreen-overlay";
783
+ overlay.setAttribute("data-campaign-id", campaign.id);
784
+ overlay.style.cssText = `
785
+ position: fixed;
786
+ top: 0;
787
+ left: 0;
788
+ right: 0;
789
+ bottom: 0;
790
+ background: ${this.sanitizeColor(campaign.background_color || "#ffffff")};
791
+ color: ${this.sanitizeColor(campaign.text_color || "#1a1a1a")};
792
+ z-index: 1000001;
793
+ display: flex;
794
+ flex-direction: column;
795
+ align-items: center;
796
+ justify-content: center;
797
+ padding: 40px 20px;
798
+ animation: aegisFadeIn 0.3s ease-out;
799
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
800
+ overflow-y: auto;
801
+ `;
802
+ if (campaign.image_url) {
803
+ const img = document.createElement("img");
804
+ const safeUrl = this.sanitizeUrl(campaign.image_url);
805
+ if (safeUrl) {
806
+ img.src = safeUrl;
807
+ img.alt = "";
808
+ img.style.cssText = "max-width: 100%; max-height: 40vh; object-fit: contain; margin-bottom: 32px;";
809
+ overlay.appendChild(img);
810
+ }
811
+ }
812
+ const contentContainer = document.createElement("div");
813
+ contentContainer.style.cssText = "max-width: 600px; text-align: center;";
814
+ const title = document.createElement("h1");
815
+ title.textContent = campaign.title;
816
+ title.style.cssText = "margin: 0 0 16px 0; font-size: 32px; font-weight: 700;";
817
+ contentContainer.appendChild(title);
818
+ const body = document.createElement("p");
819
+ body.textContent = campaign.body;
820
+ body.style.cssText = "margin: 0 0 32px 0; font-size: 18px; line-height: 1.6; opacity: 0.9;";
821
+ contentContainer.appendChild(body);
822
+ if (campaign.action_url && campaign.button_text) {
823
+ const ctaButton = document.createElement("button");
824
+ ctaButton.textContent = campaign.button_text;
825
+ ctaButton.style.cssText = `
826
+ background: ${this.sanitizeColor(campaign.text_color || "#1a73e8")};
827
+ color: ${this.sanitizeColor(campaign.background_color || "#ffffff")};
828
+ border: none;
829
+ padding: 16px 48px;
830
+ border-radius: 8px;
831
+ font-weight: 700;
832
+ font-size: 18px;
833
+ cursor: pointer;
834
+ margin-bottom: 16px;
835
+ `;
836
+ ctaButton.addEventListener("click", () => {
837
+ this.trackEvent(campaign.id, "clicked");
838
+ const safeUrl = this.sanitizeUrl(campaign.action_url);
839
+ if (safeUrl) {
840
+ window.location.href = safeUrl;
841
+ }
842
+ this.removeModal(overlay);
843
+ });
844
+ contentContainer.appendChild(ctaButton);
845
+ }
846
+ overlay.appendChild(contentContainer);
847
+ const closeButton = document.createElement("button");
848
+ closeButton.textContent = "✕";
849
+ closeButton.setAttribute("aria-label", "Close");
850
+ closeButton.style.cssText = `
851
+ position: absolute;
852
+ top: 20px;
853
+ right: 20px;
854
+ background: transparent;
855
+ border: none;
856
+ color: inherit;
857
+ font-size: 32px;
858
+ cursor: pointer;
859
+ padding: 0;
860
+ width: 48px;
861
+ height: 48px;
862
+ display: flex;
863
+ align-items: center;
864
+ justify-content: center;
865
+ opacity: 0.6;
866
+ `;
867
+ closeButton.addEventListener("click", () => {
868
+ this.trackEvent(campaign.id, "dismissed");
869
+ this.removeModal(overlay);
870
+ });
871
+ overlay.appendChild(closeButton);
872
+ this.addAnimationStyles();
873
+ document.body.appendChild(overlay);
874
+ }
875
+ renderHalfInterstitial(campaign) {
876
+ const overlay = document.createElement("div");
877
+ overlay.className = "aegis-in-app-half-interstitial-overlay";
878
+ overlay.setAttribute("data-campaign-id", campaign.id);
879
+ overlay.style.cssText = `
880
+ position: fixed;
881
+ top: 0;
882
+ left: 0;
883
+ right: 0;
884
+ bottom: 0;
885
+ background: rgba(0, 0, 0, 0.5);
886
+ z-index: 1000000;
887
+ display: flex;
888
+ align-items: flex-end;
889
+ justify-content: center;
890
+ animation: aegisFadeIn 0.3s ease-out;
891
+ `;
892
+ const modal = document.createElement("div");
893
+ modal.className = "aegis-in-app-half-interstitial";
894
+ modal.style.cssText = `
895
+ background: white;
896
+ border-radius: 16px 16px 0 0;
897
+ width: 100%;
898
+ max-width: 600px;
899
+ max-height: 60vh;
900
+ overflow: auto;
901
+ box-shadow: 0 -4px 20px rgba(0,0,0,0.2);
902
+ animation: aegisSlideUp 0.3s ease-out;
903
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
904
+ `;
905
+ if (campaign.image_url) {
906
+ const img = document.createElement("img");
907
+ const safeUrl = this.sanitizeUrl(campaign.image_url);
908
+ if (safeUrl) {
909
+ img.src = safeUrl;
910
+ img.alt = "";
911
+ img.style.cssText = "width: 100%; height: 200px; object-fit: cover;";
912
+ modal.appendChild(img);
913
+ }
914
+ }
915
+ const content = document.createElement("div");
916
+ content.style.cssText = "padding: 32px 24px;";
917
+ const title = document.createElement("h2");
918
+ title.textContent = campaign.title;
919
+ title.style.cssText = "margin: 0 0 12px 0; font-size: 24px; font-weight: 700; color: #1a1a1a;";
920
+ content.appendChild(title);
921
+ const body = document.createElement("p");
922
+ body.textContent = campaign.body;
923
+ body.style.cssText = "margin: 0 0 24px 0; font-size: 16px; line-height: 1.5; color: #4a4a4a;";
924
+ content.appendChild(body);
925
+ if (campaign.action_url && campaign.button_text) {
926
+ const ctaButton = document.createElement("button");
927
+ ctaButton.textContent = campaign.button_text;
928
+ ctaButton.style.cssText = `
929
+ background: ${this.sanitizeColor(campaign.background_color || "#1a73e8")};
930
+ color: ${this.sanitizeColor(campaign.text_color || "#ffffff")};
931
+ border: none;
932
+ padding: 14px 32px;
933
+ border-radius: 8px;
934
+ font-weight: 700;
935
+ font-size: 16px;
936
+ cursor: pointer;
937
+ width: 100%;
938
+ `;
939
+ ctaButton.addEventListener("click", () => {
940
+ this.trackEvent(campaign.id, "clicked");
941
+ const safeUrl = this.sanitizeUrl(campaign.action_url);
942
+ if (safeUrl) {
943
+ window.location.href = safeUrl;
944
+ }
945
+ this.removeModal(overlay);
946
+ });
947
+ content.appendChild(ctaButton);
948
+ }
949
+ modal.appendChild(content);
950
+ const closeButton = document.createElement("button");
951
+ closeButton.textContent = "✕";
952
+ closeButton.setAttribute("aria-label", "Close");
953
+ closeButton.style.cssText = `
954
+ position: absolute;
955
+ top: 16px;
956
+ right: 16px;
957
+ background: rgba(255,255,255,0.9);
958
+ border: none;
959
+ color: #333;
960
+ font-size: 24px;
961
+ cursor: pointer;
962
+ width: 36px;
963
+ height: 36px;
964
+ border-radius: 50%;
965
+ display: flex;
966
+ align-items: center;
967
+ justify-content: center;
968
+ box-shadow: 0 2px 8px rgba(0,0,0,0.2);
969
+ `;
970
+ closeButton.addEventListener("click", () => {
971
+ this.trackEvent(campaign.id, "dismissed");
972
+ this.removeModal(overlay);
973
+ });
974
+ modal.style.position = "relative";
975
+ modal.appendChild(closeButton);
976
+ overlay.appendChild(modal);
977
+ this.addAnimationStyles();
978
+ document.body.appendChild(overlay);
979
+ }
980
+ renderAlert(campaign) {
981
+ const overlay = document.createElement("div");
982
+ overlay.className = "aegis-in-app-alert-overlay";
983
+ overlay.setAttribute("data-campaign-id", campaign.id);
984
+ overlay.style.cssText = `
985
+ position: fixed;
986
+ top: 0;
987
+ left: 0;
988
+ right: 0;
989
+ bottom: 0;
990
+ background: rgba(0, 0, 0, 0.6);
991
+ z-index: 1000000;
992
+ display: flex;
993
+ align-items: center;
994
+ justify-content: center;
995
+ animation: aegisFadeIn 0.2s ease-out;
996
+ `;
997
+ const alert = document.createElement("div");
998
+ alert.className = "aegis-in-app-alert";
999
+ alert.style.cssText = `
1000
+ background: white;
1001
+ border-radius: 12px;
1002
+ max-width: 320px;
1003
+ width: 85%;
1004
+ box-shadow: 0 10px 40px rgba(0,0,0,0.3);
1005
+ animation: aegisScaleIn 0.2s ease-out;
1006
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
1007
+ overflow: hidden;
1008
+ `;
1009
+ const content = document.createElement("div");
1010
+ content.style.cssText = "padding: 24px 20px; text-align: center;";
1011
+ const title = document.createElement("h3");
1012
+ title.textContent = campaign.title;
1013
+ title.style.cssText = "margin: 0 0 8px 0; font-size: 18px; font-weight: 700; color: #1a1a1a;";
1014
+ content.appendChild(title);
1015
+ const body = document.createElement("p");
1016
+ body.textContent = campaign.body;
1017
+ body.style.cssText = "margin: 0; font-size: 14px; line-height: 1.4; color: #666;";
1018
+ content.appendChild(body);
1019
+ alert.appendChild(content);
1020
+ const buttonContainer = document.createElement("div");
1021
+ buttonContainer.style.cssText = "display: flex; border-top: 1px solid #e0e0e0;";
1022
+ if (campaign.action_url && campaign.button_text) {
1023
+ const ctaButton = document.createElement("button");
1024
+ ctaButton.textContent = campaign.button_text;
1025
+ ctaButton.style.cssText = `
1026
+ flex: 1;
1027
+ background: transparent;
1028
+ border: none;
1029
+ border-right: 1px solid #e0e0e0;
1030
+ color: #1a73e8;
1031
+ padding: 14px;
1032
+ font-weight: 600;
1033
+ font-size: 16px;
1034
+ cursor: pointer;
1035
+ `;
1036
+ ctaButton.addEventListener("click", () => {
1037
+ this.trackEvent(campaign.id, "clicked");
1038
+ const safeUrl = this.sanitizeUrl(campaign.action_url);
1039
+ if (safeUrl) {
1040
+ window.location.href = safeUrl;
1041
+ }
1042
+ this.removeModal(overlay);
1043
+ });
1044
+ buttonContainer.appendChild(ctaButton);
1045
+ }
1046
+ const cancelButton = document.createElement("button");
1047
+ cancelButton.textContent = "Cancel";
1048
+ cancelButton.style.cssText = `
1049
+ flex: 1;
1050
+ background: transparent;
1051
+ border: none;
1052
+ color: #666;
1053
+ padding: 14px;
1054
+ font-weight: 600;
1055
+ font-size: 16px;
1056
+ cursor: pointer;
1057
+ `;
1058
+ cancelButton.addEventListener("click", () => {
1059
+ this.trackEvent(campaign.id, "dismissed");
1060
+ this.removeModal(overlay);
1061
+ });
1062
+ buttonContainer.appendChild(cancelButton);
1063
+ alert.appendChild(buttonContainer);
1064
+ overlay.appendChild(alert);
1065
+ this.addAnimationStyles();
1066
+ document.body.appendChild(overlay);
1067
+ }
1068
+ renderPIP(campaign) {
1069
+ const pip = document.createElement("div");
1070
+ pip.className = "aegis-in-app-pip";
1071
+ pip.setAttribute("data-campaign-id", campaign.id);
1072
+ pip.style.cssText = `
1073
+ position: fixed;
1074
+ bottom: 20px;
1075
+ right: 20px;
1076
+ width: 320px;
1077
+ background: black;
1078
+ border-radius: 12px;
1079
+ overflow: hidden;
1080
+ box-shadow: 0 8px 24px rgba(0,0,0,0.3);
1081
+ z-index: 999999;
1082
+ animation: aegisSlideUp 0.3s ease-out;
1083
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
1084
+ `;
1085
+ if (campaign.video_url) {
1086
+ const video = document.createElement("video");
1087
+ const safeUrl = this.sanitizeUrl(campaign.video_url);
1088
+ if (safeUrl) {
1089
+ video.src = safeUrl;
1090
+ video.controls = true;
1091
+ video.autoplay = true;
1092
+ video.muted = true;
1093
+ video.style.cssText = "width: 100%; display: block;";
1094
+ pip.appendChild(video);
1095
+ }
1096
+ } else if (campaign.image_url) {
1097
+ const img = document.createElement("img");
1098
+ const safeUrl = this.sanitizeUrl(campaign.image_url);
1099
+ if (safeUrl) {
1100
+ img.src = safeUrl;
1101
+ img.alt = "";
1102
+ img.style.cssText = "width: 100%; display: block;";
1103
+ pip.appendChild(img);
1104
+ }
1105
+ }
1106
+ const overlay = document.createElement("div");
1107
+ overlay.style.cssText = `
1108
+ position: absolute;
1109
+ bottom: 0;
1110
+ left: 0;
1111
+ right: 0;
1112
+ background: linear-gradient(to top, rgba(0,0,0,0.9), transparent);
1113
+ padding: 40px 16px 16px;
1114
+ color: white;
1115
+ `;
1116
+ const title = document.createElement("div");
1117
+ title.textContent = campaign.title;
1118
+ title.style.cssText = "font-size: 14px; font-weight: 600; margin-bottom: 4px;";
1119
+ overlay.appendChild(title);
1120
+ const body = document.createElement("div");
1121
+ body.textContent = campaign.body;
1122
+ body.style.cssText = "font-size: 12px; opacity: 0.9;";
1123
+ overlay.appendChild(body);
1124
+ pip.appendChild(overlay);
1125
+ const closeButton = document.createElement("button");
1126
+ closeButton.textContent = "✕";
1127
+ closeButton.setAttribute("aria-label", "Close");
1128
+ closeButton.style.cssText = `
1129
+ position: absolute;
1130
+ top: 8px;
1131
+ right: 8px;
1132
+ background: rgba(0,0,0,0.6);
1133
+ border: none;
1134
+ color: white;
1135
+ font-size: 18px;
1136
+ cursor: pointer;
1137
+ width: 28px;
1138
+ height: 28px;
1139
+ border-radius: 50%;
1140
+ display: flex;
1141
+ align-items: center;
1142
+ justify-content: center;
1143
+ `;
1144
+ closeButton.addEventListener("click", () => {
1145
+ this.trackEvent(campaign.id, "dismissed");
1146
+ pip.style.animation = "aegisSlideDown 0.3s ease-out";
1147
+ setTimeout(() => {
1148
+ if (pip.parentNode) {
1149
+ pip.parentNode.removeChild(pip);
1150
+ }
1151
+ }, 300);
1152
+ });
1153
+ pip.appendChild(closeButton);
1154
+ if (campaign.action_url) {
1155
+ pip.style.cursor = "pointer";
1156
+ pip.addEventListener("click", (e) => {
1157
+ if (e.target !== closeButton) {
1158
+ this.trackEvent(campaign.id, "clicked");
1159
+ const safeUrl = this.sanitizeUrl(campaign.action_url);
1160
+ if (safeUrl) {
1161
+ window.open(safeUrl, "_blank");
1162
+ }
1163
+ }
1164
+ });
1165
+ }
1166
+ this.addAnimationStyles();
1167
+ document.body.appendChild(pip);
1168
+ }
1169
+ removeBanner(banner) {
1170
+ banner.style.animation = "aegisSlideUp 0.3s ease-out";
1171
+ setTimeout(() => {
1172
+ if (banner.parentNode) {
1173
+ banner.parentNode.removeChild(banner);
1174
+ }
1175
+ }, 300);
1176
+ }
1177
+ removeModal(overlay) {
1178
+ overlay.style.animation = "aegisFadeOut 0.3s ease-out";
1179
+ setTimeout(() => {
1180
+ if (overlay.parentNode) {
1181
+ overlay.parentNode.removeChild(overlay);
1182
+ }
1183
+ }, 300);
1184
+ }
1185
+ renderTooltip(campaign) {
1186
+ const ic = campaign.interactive_config || {};
1187
+ const anchorSelector = ic.tooltip_anchor_selector || "[data-aegis-tooltip]";
1188
+ const preferredPosition = ic.tooltip_position || "bottom";
1189
+ const anchor = document.querySelector(anchorSelector);
1190
+ if (!anchor) {
1191
+ this.log(`Tooltip anchor not found: ${anchorSelector}`, "warn");
1192
+ return;
1193
+ }
1194
+ const bg = this.sanitizeColor(campaign.background_color || "#1a1a1a");
1195
+ const textColor = this.sanitizeColor(campaign.text_color || "#ffffff");
1196
+ const tooltip = document.createElement("div");
1197
+ tooltip.className = "aegis-in-app-tooltip";
1198
+ tooltip.setAttribute("data-campaign-id", campaign.id);
1199
+ tooltip.style.cssText = `
1200
+ position: absolute; z-index: 1000001; max-width: 280px; width: max-content;
1201
+ background: ${bg}; color: ${textColor}; border-radius: 10px; padding: 14px 16px;
1202
+ box-shadow: 0 8px 24px rgba(0,0,0,0.18); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
1203
+ animation: aegisScaleIn 0.2s ease-out; pointer-events: auto;
1204
+ `;
1205
+ const arrow = document.createElement("div");
1206
+ arrow.className = "aegis-tooltip-arrow";
1207
+ arrow.style.cssText = `
1208
+ position: absolute; width: 10px; height: 10px; background: ${bg};
1209
+ transform: rotate(45deg);
1210
+ `;
1211
+ const closeBtn = document.createElement("button");
1212
+ closeBtn.textContent = "×";
1213
+ closeBtn.style.cssText = `
1214
+ position: absolute; top: 6px; right: 8px; background: none; border: none;
1215
+ color: ${textColor}; opacity: 0.6; font-size: 16px; cursor: pointer; padding: 0; line-height: 1;
1216
+ `;
1217
+ const dismiss = () => {
1218
+ tooltip.style.animation = "aegisFadeOut 0.2s ease-out";
1219
+ setTimeout(() => {
1220
+ var _a;
1221
+ (_a = tooltip.parentNode) == null ? void 0 : _a.removeChild(tooltip);
1222
+ }, 200);
1223
+ this.trackEvent(campaign.id, "dismissed");
1224
+ };
1225
+ closeBtn.addEventListener("click", (e) => {
1226
+ e.stopPropagation();
1227
+ dismiss();
1228
+ });
1229
+ if (campaign.title) {
1230
+ const title = document.createElement("div");
1231
+ title.textContent = campaign.title;
1232
+ title.style.cssText = "font-size: 13px; font-weight: 700; margin-bottom: 4px; padding-right: 16px;";
1233
+ tooltip.appendChild(title);
1234
+ }
1235
+ const body = document.createElement("div");
1236
+ body.textContent = campaign.body;
1237
+ body.style.cssText = "font-size: 12px; line-height: 1.4; opacity: 0.9;";
1238
+ tooltip.appendChild(body);
1239
+ if (campaign.button_text && campaign.action_url) {
1240
+ const cta = document.createElement("a");
1241
+ cta.textContent = campaign.button_text;
1242
+ cta.href = campaign.action_url;
1243
+ cta.style.cssText = `
1244
+ display: inline-block; margin-top: 8px; font-size: 12px; font-weight: 600;
1245
+ color: ${textColor}; text-decoration: underline; cursor: pointer;
1246
+ `;
1247
+ cta.addEventListener("click", () => {
1248
+ this.trackEvent(campaign.id, "clicked");
1249
+ });
1250
+ tooltip.appendChild(cta);
1251
+ }
1252
+ tooltip.appendChild(closeBtn);
1253
+ tooltip.appendChild(arrow);
1254
+ document.body.appendChild(tooltip);
1255
+ const anchorRect = anchor.getBoundingClientRect();
1256
+ const tooltipRect = tooltip.getBoundingClientRect();
1257
+ const scrollX = window.scrollX;
1258
+ const scrollY = window.scrollY;
1259
+ const gap = 10;
1260
+ let top = 0;
1261
+ let left = 0;
1262
+ let arrowTop = "";
1263
+ let arrowLeft = "";
1264
+ let arrowBottom = "";
1265
+ let arrowRight = "";
1266
+ switch (preferredPosition) {
1267
+ case "top":
1268
+ top = anchorRect.top + scrollY - tooltipRect.height - gap;
1269
+ left = anchorRect.left + scrollX + (anchorRect.width - tooltipRect.width) / 2;
1270
+ arrowBottom = "-5px";
1271
+ arrowLeft = `${tooltipRect.width / 2 - 5}px`;
1272
+ break;
1273
+ case "left":
1274
+ top = anchorRect.top + scrollY + (anchorRect.height - tooltipRect.height) / 2;
1275
+ left = anchorRect.left + scrollX - tooltipRect.width - gap;
1276
+ arrowRight = "-5px";
1277
+ arrowTop = `${tooltipRect.height / 2 - 5}px`;
1278
+ break;
1279
+ case "right":
1280
+ top = anchorRect.top + scrollY + (anchorRect.height - tooltipRect.height) / 2;
1281
+ left = anchorRect.right + scrollX + gap;
1282
+ arrowLeft = "-5px";
1283
+ arrowTop = `${tooltipRect.height / 2 - 5}px`;
1284
+ break;
1285
+ default:
1286
+ top = anchorRect.bottom + scrollY + gap;
1287
+ left = anchorRect.left + scrollX + (anchorRect.width - tooltipRect.width) / 2;
1288
+ arrowTop = "-5px";
1289
+ arrowLeft = `${tooltipRect.width / 2 - 5}px`;
1290
+ break;
1291
+ }
1292
+ left = Math.max(8, Math.min(left, window.innerWidth + scrollX - tooltipRect.width - 8));
1293
+ top = Math.max(8, top);
1294
+ tooltip.style.top = `${top}px`;
1295
+ tooltip.style.left = `${left}px`;
1296
+ arrow.style.top = arrowTop || "";
1297
+ arrow.style.left = arrowLeft || "";
1298
+ arrow.style.bottom = arrowBottom || "";
1299
+ arrow.style.right = arrowRight || "";
1300
+ const outsideClickHandler = (e) => {
1301
+ if (!tooltip.contains(e.target) && !anchor.contains(e.target)) {
1302
+ document.removeEventListener("click", outsideClickHandler);
1303
+ dismiss();
1304
+ }
1305
+ };
1306
+ setTimeout(() => document.addEventListener("click", outsideClickHandler), 100);
1307
+ }
1308
+ addAnimationStyles() {
1309
+ if (document.getElementById("aegis-in-app-styles")) {
1310
+ return;
1311
+ }
1312
+ const style = document.createElement("style");
1313
+ style.id = "aegis-in-app-styles";
1314
+ style.textContent = `
1315
+ @keyframes aegisSlideDown {
1316
+ from { transform: translateY(-100%); opacity: 0; }
1317
+ to { transform: translateY(0); opacity: 1; }
1318
+ }
1319
+
1320
+ @keyframes aegisSlideUp {
1321
+ from { transform: translateY(0); opacity: 1; }
1322
+ to { transform: translateY(-100%); opacity: 0; }
1323
+ }
1324
+
1325
+ @keyframes aegisFadeIn {
1326
+ from { opacity: 0; }
1327
+ to { opacity: 1; }
1328
+ }
1329
+
1330
+ @keyframes aegisFadeOut {
1331
+ from { opacity: 1; }
1332
+ to { opacity: 0; }
1333
+ }
1334
+
1335
+ @keyframes aegisScaleIn {
1336
+ from { transform: scale(0.9); opacity: 0; }
1337
+ to { transform: scale(1); opacity: 1; }
1338
+ }
1339
+ `;
1340
+ document.head.appendChild(style);
1341
+ }
1342
+ sanitizeUrl(url) {
1343
+ try {
1344
+ const parsedUrl = new URL(url, window.location.origin);
1345
+ if (parsedUrl.protocol === "javascript:" || parsedUrl.protocol === "data:") {
1346
+ this.log(`Blocked unsafe URL: ${url}`, "error");
1347
+ return null;
1348
+ }
1349
+ return parsedUrl.href;
1350
+ } catch (error) {
1351
+ this.log(`Invalid URL: ${url}`, "error");
1352
+ return null;
1353
+ }
1354
+ }
1355
+ sanitizeColor(color) {
1356
+ if (/^#[0-9A-Fa-f]{3,6}$/.test(color)) {
1357
+ return color;
1358
+ }
1359
+ if (/^rgb\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*\)$/.test(color)) {
1360
+ return color;
1361
+ }
1362
+ if (/^rgba\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*[\d.]+\s*\)$/.test(color)) {
1363
+ return color;
1364
+ }
1365
+ const namedColors = ["white", "black", "red", "green", "blue", "yellow", "orange", "purple", "pink", "gray", "transparent"];
1366
+ if (namedColors.includes(color.toLowerCase())) {
1367
+ return color;
1368
+ }
1369
+ return "#000000";
1370
+ }
1371
+ /**
1372
+ * Track in-app event via the standard Event Ingress pipeline.
1373
+ *
1374
+ * Events flow: SDK → Event Ingress → Kafka → Governance Consumer
1375
+ * → Event Engine → ClickHouse (in_app_events + events_with_ttl)
1376
+ *
1377
+ * The event_type uses the canonical prefix `in_app.` so the event
1378
+ * switchboard maps it to the correct canonical form:
1379
+ * in_app.impression → engagement.view
1380
+ * in_app.clicked → engagement.click
1381
+ * in_app.dismissed → engagement.dismiss
1382
+ */
1383
+ async trackEvent(campaignId, eventType) {
1384
+ try {
1385
+ const url = `${this.apiHost}/api/v1/events`;
1386
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1387
+ const externalEventId = `inapp_${campaignId}_${eventType}_${Date.now()}`;
1388
+ const ingressEvent = {
1389
+ tenant_id: this.organizationId || "unknown",
1390
+ source: "in_app_sdk",
1391
+ external_event_id: externalEventId,
1392
+ event_type: `in_app.${eventType}`,
1393
+ timestamp: now,
1394
+ payload: {
1395
+ campaign_id: campaignId,
1396
+ event_type: eventType,
1397
+ user_id: this.userId,
1398
+ contact_id: this.contactId,
1399
+ platform: "web",
1400
+ variant_id: this.getVariantId(campaignId) ?? void 0
1401
+ },
1402
+ trace_id: externalEventId,
1403
+ metadata: {
1404
+ write_key: this.writeKey
1405
+ }
1406
+ };
1407
+ fetch(url, {
1408
+ method: "POST",
1409
+ headers: {
1410
+ "Content-Type": "application/json",
1411
+ "X-Aegis-Write-Key": this.writeKey
1412
+ },
1413
+ body: JSON.stringify(ingressEvent)
1414
+ }).catch((error) => {
1415
+ this.log(`Error tracking event: ${error}`, "error");
1416
+ });
1417
+ this.log(`Tracked ${eventType} event for campaign ${campaignId}`);
1418
+ } catch (error) {
1419
+ this.log(`Error tracking event: ${error}`, "error");
1420
+ }
1421
+ }
1422
+ log(message, level = "log") {
1423
+ if (this.debugMode) {
1424
+ console[level](`[AegisInApp] ${message}`);
1425
+ }
1426
+ }
1427
+ destroy(options) {
1428
+ this.disconnectSSE();
1429
+ document.querySelectorAll(
1430
+ ".aegis-in-app-banner, .aegis-in-app-modal-overlay, .aegis-in-app-fullscreen-overlay, .aegis-in-app-half-interstitial-overlay, .aegis-in-app-alert-overlay, .aegis-in-app-pip, .aegis-in-app-nps-overlay, .aegis-in-app-countdown-overlay, .aegis-in-app-rating-overlay, .aegis-in-app-poll-overlay, .aegis-in-app-quiz-overlay"
1431
+ ).forEach((el) => {
1432
+ if (el.parentNode) {
1433
+ el.parentNode.removeChild(el);
1434
+ }
1435
+ });
1436
+ const styles = document.getElementById("aegis-in-app-styles");
1437
+ if (styles && styles.parentNode) {
1438
+ styles.parentNode.removeChild(styles);
1439
+ }
1440
+ if ((options == null ? void 0 : options.clearABState) && typeof localStorage !== "undefined") {
1441
+ localStorage.removeItem("aegis_ab_assignments");
1442
+ }
1443
+ this.isInitialized = false;
1444
+ this.log("AegisInApp destroyed");
1445
+ }
1446
+ }
1447
+ function renderPreview(config) {
1448
+ document.querySelectorAll(
1449
+ '[class^="aegis-in-app-"]'
1450
+ ).forEach((el) => {
1451
+ if (el.parentNode) {
1452
+ el.parentNode.removeChild(el);
1453
+ }
1454
+ });
1455
+ if (!config) return;
1456
+ const manager = new AegisInAppManager({
1457
+ writeKey: "preview-mode",
1458
+ apiHost: "",
1459
+ debugMode: false,
1460
+ enableSSE: false
1461
+ });
1462
+ const m = manager;
1463
+ m.trackEvent = async () => {
1464
+ };
1465
+ m.addAnimationStyles();
1466
+ m.displayCampaign(config);
1467
+ }
1468
+ class AegisPlacementManager {
1469
+ constructor(config) {
1470
+ this.placements = /* @__PURE__ */ new Map();
1471
+ this.slots = /* @__PURE__ */ new Map();
1472
+ this.renderedSlots = /* @__PURE__ */ new Set();
1473
+ this.isInitialized = false;
1474
+ this.reconnectAttempts = 0;
1475
+ this.maxReconnectAttempts = 5;
1476
+ this.writeKey = config.writeKey;
1477
+ this.apiHost = config.apiHost || "https://api.aegis.ai";
1478
+ this.userId = config.userId;
1479
+ this.contactId = config.contactId;
1480
+ this.organizationId = config.organizationId;
1481
+ this.debugMode = config.debugMode || false;
1482
+ this.enableSSE = config.enableSSE !== false;
1483
+ }
1484
+ async initialize() {
1485
+ if (this.isInitialized) {
1486
+ this.log("AegisPlacements already initialized");
1487
+ return;
1488
+ }
1489
+ await this.refreshPlacements();
1490
+ if (this.enableSSE && this.organizationId) {
1491
+ this.connectSSE();
1492
+ }
1493
+ this.isInitialized = true;
1494
+ this.log("AegisPlacements initialized successfully");
1495
+ }
1496
+ register(placementId, options) {
1497
+ const slot = {
1498
+ placementId,
1499
+ ...options
1500
+ };
1501
+ this.slots.set(placementId, slot);
1502
+ this.log(`Registered placement slot: ${placementId}`);
1503
+ const existingContent = this.placements.get(placementId);
1504
+ if (existingContent) {
1505
+ this.renderSlot(slot, existingContent);
1506
+ } else if (options.fallbackContent) {
1507
+ this.renderFallback(slot);
1508
+ }
1509
+ }
1510
+ unregister(placementId) {
1511
+ this.slots.delete(placementId);
1512
+ this.renderedSlots.delete(placementId);
1513
+ this.log(`Unregistered placement slot: ${placementId}`);
1514
+ }
1515
+ async refreshPlacements() {
1516
+ try {
1517
+ const placementIds = Array.from(this.slots.keys());
1518
+ if (placementIds.length === 0) {
1519
+ this.log("No registered slots, skipping refresh");
1520
+ return;
1521
+ }
1522
+ const url = `${this.apiHost}/v1/placements/content?placement_ids=${placementIds.join(",")}`;
1523
+ const headers = {
1524
+ "X-Aegis-Write-Key": this.writeKey,
1525
+ "X-Device-Type": this.getDeviceType(),
1526
+ "X-Platform": "web"
1527
+ };
1528
+ if (this.userId) headers["X-User-ID"] = this.userId;
1529
+ if (this.contactId) headers["X-Contact-ID"] = this.contactId;
1530
+ if (this.organizationId) headers["X-Organization-ID"] = this.organizationId;
1531
+ const response = await fetch(url, { headers });
1532
+ if (!response.ok) {
1533
+ throw new Error(`Failed to fetch placements: ${response.status}`);
1534
+ }
1535
+ const data = await response.json();
1536
+ this.placements.clear();
1537
+ for (const placement of data.placements || []) {
1538
+ this.placements.set(placement.placement_id, placement);
1539
+ }
1540
+ this.renderAllSlots();
1541
+ this.log(`Refreshed ${this.placements.size} placements`);
1542
+ } catch (error) {
1543
+ this.log(`Error refreshing placements: ${error}`, true);
1544
+ }
1545
+ }
1546
+ renderAllSlots() {
1547
+ for (const [placementId, slot] of this.slots.entries()) {
1548
+ const content = this.placements.get(placementId);
1549
+ if (content) {
1550
+ this.renderSlot(slot, content);
1551
+ } else if (slot.fallbackContent) {
1552
+ this.renderFallback(slot);
1553
+ }
1554
+ }
1555
+ }
1556
+ renderSlot(slot, content) {
1557
+ try {
1558
+ if (content.content_type === "dynamic_injection") {
1559
+ this.renderDynamicInjection(content);
1560
+ } else {
1561
+ const container = document.getElementById(slot.containerId);
1562
+ if (!container) {
1563
+ this.log(`Container not found: ${slot.containerId}`, true);
1564
+ return;
1565
+ }
1566
+ container.innerHTML = "";
1567
+ switch (content.content_type) {
1568
+ case "banner":
1569
+ this.renderBanner(container, content);
1570
+ break;
1571
+ case "card":
1572
+ this.renderCard(container, content);
1573
+ break;
1574
+ case "carousel":
1575
+ this.renderCarousel(container, content);
1576
+ break;
1577
+ case "video":
1578
+ this.renderVideo(container, content);
1579
+ break;
1580
+ case "html":
1581
+ this.renderHTML(container, content);
1582
+ break;
1583
+ default:
1584
+ this.log(`Unknown content type: ${content.content_type}`, true);
1585
+ return;
1586
+ }
1587
+ }
1588
+ if (!this.renderedSlots.has(slot.placementId)) {
1589
+ this.trackEvent(content.placement_id, content.variant_id, "impression");
1590
+ this.renderedSlots.add(slot.placementId);
1591
+ }
1592
+ if (slot.onRender) {
1593
+ slot.onRender(content);
1594
+ }
1595
+ this.log(`Rendered placement: ${content.placement_id} (${content.content_type})`);
1596
+ } catch (error) {
1597
+ this.log(`Error rendering placement: ${error}`, true);
1598
+ if (slot.onError) {
1599
+ slot.onError(error);
1600
+ }
1601
+ }
1602
+ }
1603
+ renderBanner(container, content) {
1604
+ const { title, body, image_url, cta_text, cta_url, background_color, text_color } = content.content;
1605
+ const banner = document.createElement("div");
1606
+ banner.className = "aegis-placement-banner";
1607
+ banner.style.cssText = `
1608
+ background: ${this.sanitizeColor(background_color || "#1a73e8")};
1609
+ color: ${this.sanitizeColor(text_color || "#ffffff")};
1610
+ padding: 24px;
1611
+ border-radius: 8px;
1612
+ display: flex;
1613
+ align-items: center;
1614
+ gap: 16px;
1615
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
1616
+ `;
1617
+ if (image_url) {
1618
+ const img = document.createElement("img");
1619
+ const safeUrl = this.sanitizeUrl(image_url);
1620
+ if (safeUrl) {
1621
+ img.src = safeUrl;
1622
+ img.alt = "";
1623
+ img.style.cssText = "width: 80px; height: 80px; border-radius: 8px; object-fit: cover;";
1624
+ banner.appendChild(img);
1625
+ }
1626
+ }
1627
+ const textContainer = document.createElement("div");
1628
+ textContainer.style.cssText = "flex: 1;";
1629
+ if (title) {
1630
+ const titleEl = document.createElement("div");
1631
+ titleEl.textContent = title;
1632
+ titleEl.style.cssText = "font-size: 18px; font-weight: 600; margin-bottom: 8px;";
1633
+ textContainer.appendChild(titleEl);
1634
+ }
1635
+ if (body) {
1636
+ const bodyEl = document.createElement("div");
1637
+ bodyEl.textContent = body;
1638
+ bodyEl.style.cssText = "font-size: 14px; opacity: 0.9;";
1639
+ textContainer.appendChild(bodyEl);
1640
+ }
1641
+ banner.appendChild(textContainer);
1642
+ if (cta_text && cta_url) {
1643
+ const ctaButton = document.createElement("button");
1644
+ ctaButton.textContent = cta_text;
1645
+ ctaButton.style.cssText = `
1646
+ background: white;
1647
+ color: ${this.sanitizeColor(background_color || "#1a73e8")};
1648
+ border: none;
1649
+ padding: 12px 24px;
1650
+ border-radius: 6px;
1651
+ font-weight: 600;
1652
+ cursor: pointer;
1653
+ font-size: 14px;
1654
+ white-space: nowrap;
1655
+ `;
1656
+ ctaButton.addEventListener("click", () => {
1657
+ this.trackEvent(content.placement_id, content.variant_id, "click");
1658
+ const safeUrl = this.sanitizeUrl(cta_url);
1659
+ if (safeUrl) {
1660
+ window.location.href = safeUrl;
1661
+ }
1662
+ });
1663
+ banner.appendChild(ctaButton);
1664
+ }
1665
+ container.appendChild(banner);
1666
+ }
1667
+ renderCard(container, content) {
1668
+ const { title, body, image_url, cta_text, cta_url } = content.content;
1669
+ const card = document.createElement("div");
1670
+ card.className = "aegis-placement-card";
1671
+ card.style.cssText = `
1672
+ background: white;
1673
+ border-radius: 12px;
1674
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
1675
+ overflow: hidden;
1676
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
1677
+ `;
1678
+ if (image_url) {
1679
+ const img = document.createElement("img");
1680
+ const safeUrl = this.sanitizeUrl(image_url);
1681
+ if (safeUrl) {
1682
+ img.src = safeUrl;
1683
+ img.alt = title || "";
1684
+ img.style.cssText = "width: 100%; height: 200px; object-fit: cover;";
1685
+ card.appendChild(img);
1686
+ }
1687
+ }
1688
+ const cardBody = document.createElement("div");
1689
+ cardBody.style.cssText = "padding: 20px;";
1690
+ if (title) {
1691
+ const titleEl = document.createElement("h3");
1692
+ titleEl.textContent = title;
1693
+ titleEl.style.cssText = "margin: 0 0 12px 0; font-size: 20px; font-weight: 600; color: #1a1a1a;";
1694
+ cardBody.appendChild(titleEl);
1695
+ }
1696
+ if (body) {
1697
+ const bodyEl = document.createElement("p");
1698
+ bodyEl.textContent = body;
1699
+ bodyEl.style.cssText = "margin: 0 0 16px 0; font-size: 14px; color: #666; line-height: 1.5;";
1700
+ cardBody.appendChild(bodyEl);
1701
+ }
1702
+ if (cta_text && cta_url) {
1703
+ const ctaButton = document.createElement("button");
1704
+ ctaButton.textContent = cta_text;
1705
+ ctaButton.style.cssText = `
1706
+ background: #1a73e8;
1707
+ color: white;
1708
+ border: none;
1709
+ padding: 10px 20px;
1710
+ border-radius: 6px;
1711
+ font-weight: 600;
1712
+ cursor: pointer;
1713
+ font-size: 14px;
1714
+ `;
1715
+ ctaButton.addEventListener("click", () => {
1716
+ this.trackEvent(content.placement_id, content.variant_id, "click");
1717
+ const safeUrl = this.sanitizeUrl(cta_url);
1718
+ if (safeUrl) {
1719
+ window.location.href = safeUrl;
1720
+ }
1721
+ });
1722
+ cardBody.appendChild(ctaButton);
1723
+ }
1724
+ card.appendChild(cardBody);
1725
+ container.appendChild(card);
1726
+ }
1727
+ renderCarousel(container, content) {
1728
+ const { items } = content.content;
1729
+ if (!Array.isArray(items) || items.length === 0) {
1730
+ this.log("Carousel items empty or invalid", true);
1731
+ return;
1732
+ }
1733
+ const carousel = document.createElement("div");
1734
+ carousel.className = "aegis-placement-carousel";
1735
+ carousel.style.cssText = `
1736
+ display: flex;
1737
+ gap: 16px;
1738
+ overflow-x: auto;
1739
+ padding: 8px 0;
1740
+ scroll-behavior: smooth;
1741
+ -webkit-overflow-scrolling: touch;
1742
+ `;
1743
+ for (const item of items) {
1744
+ const carouselItem = document.createElement("div");
1745
+ carouselItem.style.cssText = `
1746
+ flex: 0 0 250px;
1747
+ background: white;
1748
+ border-radius: 8px;
1749
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
1750
+ overflow: hidden;
1751
+ cursor: pointer;
1752
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
1753
+ `;
1754
+ if (item.image_url) {
1755
+ const img = document.createElement("img");
1756
+ const safeUrl = this.sanitizeUrl(item.image_url);
1757
+ if (safeUrl) {
1758
+ img.src = safeUrl;
1759
+ img.alt = item.title || "";
1760
+ img.style.cssText = "width: 100%; height: 150px; object-fit: cover;";
1761
+ carouselItem.appendChild(img);
1762
+ }
1763
+ }
1764
+ const itemContent = document.createElement("div");
1765
+ itemContent.style.cssText = "padding: 12px;";
1766
+ if (item.title) {
1767
+ const title = document.createElement("div");
1768
+ title.textContent = item.title;
1769
+ title.style.cssText = "font-size: 14px; font-weight: 600; margin-bottom: 4px; color: #1a1a1a;";
1770
+ itemContent.appendChild(title);
1771
+ }
1772
+ if (item.price) {
1773
+ const price = document.createElement("div");
1774
+ price.textContent = item.price;
1775
+ price.style.cssText = "font-size: 16px; font-weight: 700; color: #1a73e8;";
1776
+ itemContent.appendChild(price);
1777
+ }
1778
+ carouselItem.appendChild(itemContent);
1779
+ if (item.url) {
1780
+ carouselItem.addEventListener("click", () => {
1781
+ this.trackEvent(content.placement_id, content.variant_id, "click", { item_index: items.indexOf(item) });
1782
+ const safeUrl = this.sanitizeUrl(item.url);
1783
+ if (safeUrl) {
1784
+ window.location.href = safeUrl;
1785
+ }
1786
+ });
1787
+ }
1788
+ carousel.appendChild(carouselItem);
1789
+ }
1790
+ container.appendChild(carousel);
1791
+ }
1792
+ renderVideo(container, content) {
1793
+ const { video_url, poster_url, autoplay, muted } = content.content;
1794
+ if (!video_url) {
1795
+ this.log("Video URL missing", true);
1796
+ return;
1797
+ }
1798
+ const safeVideoUrl = this.sanitizeUrl(video_url);
1799
+ if (!safeVideoUrl) {
1800
+ this.log("Invalid video URL", true);
1801
+ return;
1802
+ }
1803
+ const video = document.createElement("video");
1804
+ video.src = safeVideoUrl;
1805
+ video.controls = true;
1806
+ video.autoplay = autoplay || false;
1807
+ video.muted = muted || false;
1808
+ video.style.cssText = "width: 100%; border-radius: 8px;";
1809
+ if (poster_url) {
1810
+ const safePosterUrl = this.sanitizeUrl(poster_url);
1811
+ if (safePosterUrl) {
1812
+ video.poster = safePosterUrl;
1813
+ }
1814
+ }
1815
+ video.addEventListener("play", () => {
1816
+ this.trackEvent(content.placement_id, content.variant_id, "click", { action: "video_play" });
1817
+ });
1818
+ container.appendChild(video);
1819
+ }
1820
+ renderHTML(container, content) {
1821
+ const { html } = content.content;
1822
+ if (!html) {
1823
+ this.log("HTML content missing", true);
1824
+ return;
1825
+ }
1826
+ const wrapper = document.createElement("div");
1827
+ wrapper.className = "aegis-placement-html";
1828
+ wrapper.innerHTML = this.sanitizeHTML(html);
1829
+ const links = wrapper.querySelectorAll("a");
1830
+ links.forEach((link) => {
1831
+ link.addEventListener("click", (e) => {
1832
+ this.trackEvent(content.placement_id, content.variant_id, "click");
1833
+ });
1834
+ });
1835
+ container.appendChild(wrapper);
1836
+ }
1837
+ renderDynamicInjection(content) {
1838
+ const { html } = content.content;
1839
+ const cssSelector = content.css_selector;
1840
+ const injectionMode = content.injection_mode || "replace";
1841
+ if (!cssSelector) {
1842
+ this.log("CSS selector missing for dynamic injection", true);
1843
+ return;
1844
+ }
1845
+ if (!html) {
1846
+ this.log("HTML content missing for dynamic injection", true);
1847
+ return;
1848
+ }
1849
+ let targetElement = null;
1850
+ try {
1851
+ targetElement = document.querySelector(cssSelector);
1852
+ } catch (error) {
1853
+ this.log(`Invalid CSS selector: ${cssSelector}`, true);
1854
+ return;
1855
+ }
1856
+ if (!targetElement) {
1857
+ this.log(`Target element not found for selector: ${cssSelector}`, true);
1858
+ return;
1859
+ }
1860
+ const wrapper = document.createElement("div");
1861
+ wrapper.className = "aegis-dynamic-injection";
1862
+ wrapper.setAttribute("data-placement-id", content.placement_id);
1863
+ wrapper.setAttribute("data-variant-id", content.variant_id);
1864
+ wrapper.innerHTML = this.sanitizeHTML(html);
1865
+ const links = wrapper.querySelectorAll("a");
1866
+ links.forEach((link) => {
1867
+ link.addEventListener("click", () => {
1868
+ this.trackEvent(content.placement_id, content.variant_id, "click");
1869
+ });
1870
+ });
1871
+ switch (injectionMode) {
1872
+ case "replace":
1873
+ targetElement.innerHTML = "";
1874
+ targetElement.appendChild(wrapper);
1875
+ this.log(`Replaced content in ${cssSelector}`, false);
1876
+ break;
1877
+ case "append":
1878
+ targetElement.appendChild(wrapper);
1879
+ this.log(`Appended content to ${cssSelector}`, false);
1880
+ break;
1881
+ case "prepend":
1882
+ targetElement.insertBefore(wrapper, targetElement.firstChild);
1883
+ this.log(`Prepended content to ${cssSelector}`, false);
1884
+ break;
1885
+ default:
1886
+ this.log(`Unknown injection mode: ${injectionMode}`, true);
1887
+ return;
1888
+ }
1889
+ this.log(`Dynamically injected content for ${content.placement_id} into ${cssSelector} (${injectionMode})`);
1890
+ }
1891
+ renderFallback(slot) {
1892
+ if (!slot.fallbackContent) return;
1893
+ const container = document.getElementById(slot.containerId);
1894
+ if (!container) return;
1895
+ container.innerHTML = slot.fallbackContent;
1896
+ this.log(`Rendered fallback content for: ${slot.placementId}`);
1897
+ }
1898
+ async trackEvent(placementId, variantId, eventType, metadata = {}) {
1899
+ try {
1900
+ const url = `${this.apiHost}/v1/placements/track`;
1901
+ const headers = {
1902
+ "Content-Type": "application/json",
1903
+ "X-Aegis-Write-Key": this.writeKey,
1904
+ "X-Device-Type": this.getDeviceType(),
1905
+ "X-Platform": "web"
1906
+ };
1907
+ if (this.userId) headers["X-User-ID"] = this.userId;
1908
+ if (this.contactId) headers["X-Contact-ID"] = this.contactId;
1909
+ if (this.organizationId) headers["X-Organization-ID"] = this.organizationId;
1910
+ const body = {
1911
+ placement_id: placementId,
1912
+ variant_id: variantId,
1913
+ event_type: eventType,
1914
+ metadata: {
1915
+ device_type: this.getDeviceType(),
1916
+ platform: "web",
1917
+ ...metadata
1918
+ }
1919
+ };
1920
+ fetch(url, {
1921
+ method: "POST",
1922
+ headers,
1923
+ body: JSON.stringify(body)
1924
+ }).catch((err) => this.log(`Track event failed: ${err}`, true));
1925
+ this.log(`Tracked ${eventType}: ${placementId}`);
1926
+ } catch (error) {
1927
+ this.log(`Error tracking event: ${error}`, true);
1928
+ }
1929
+ }
1930
+ connectSSE() {
1931
+ if (this.eventSource) {
1932
+ this.disconnectSSE();
1933
+ }
1934
+ if (!this.organizationId) {
1935
+ this.log("Cannot connect SSE without organization ID", true);
1936
+ return;
1937
+ }
1938
+ const url = new URL("/v1/stream/realtime", this.apiHost);
1939
+ const headers = {
1940
+ "X-Aegis-Write-Key": this.writeKey,
1941
+ "X-Organization-ID": this.organizationId
1942
+ };
1943
+ if (this.contactId) {
1944
+ headers["X-Contact-ID"] = this.contactId;
1945
+ }
1946
+ const queryParams = new URLSearchParams();
1947
+ Object.entries(headers).forEach(([key, value]) => {
1948
+ queryParams.append(key, value);
1949
+ });
1950
+ this.eventSource = new EventSource(`${url}?${queryParams.toString()}`);
1951
+ this.eventSource.addEventListener("open", () => {
1952
+ this.log("SSE connection established");
1953
+ this.reconnectAttempts = 0;
1954
+ });
1955
+ this.eventSource.addEventListener("placement_content_updated", (event) => {
1956
+ try {
1957
+ const data = JSON.parse(event.data);
1958
+ this.log(`Received placement content update: ${data.placement_id}`);
1959
+ this.refreshPlacements();
1960
+ } catch (error) {
1961
+ this.log(`Error parsing SSE event: ${error}`, true);
1962
+ }
1963
+ });
1964
+ this.eventSource.addEventListener("heartbeat", (event) => {
1965
+ this.log("SSE heartbeat received");
1966
+ });
1967
+ this.eventSource.addEventListener("error", (error) => {
1968
+ var _a;
1969
+ this.log("SSE connection error", true);
1970
+ if (((_a = this.eventSource) == null ? void 0 : _a.readyState) === EventSource.CLOSED) {
1971
+ this.attemptReconnect();
1972
+ }
1973
+ });
1974
+ }
1975
+ disconnectSSE() {
1976
+ if (this.eventSource) {
1977
+ this.eventSource.close();
1978
+ this.eventSource = void 0;
1979
+ this.log("SSE connection closed");
1980
+ }
1981
+ }
1982
+ attemptReconnect() {
1983
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
1984
+ this.log("Max reconnect attempts reached, giving up", true);
1985
+ return;
1986
+ }
1987
+ this.reconnectAttempts++;
1988
+ const delay = Math.min(1e3 * Math.pow(2, this.reconnectAttempts), 3e4);
1989
+ this.log(`Reconnecting SSE in ${delay}ms (attempt ${this.reconnectAttempts})`);
1990
+ setTimeout(() => {
1991
+ if (this.isInitialized && this.enableSSE && this.organizationId) {
1992
+ this.connectSSE();
1993
+ }
1994
+ }, delay);
1995
+ }
1996
+ getDeviceType() {
1997
+ const width = window.innerWidth;
1998
+ if (width < 768) return "mobile_web";
1999
+ if (width < 1024) return "tablet_web";
2000
+ return "desktop_web";
2001
+ }
2002
+ sanitizeUrl(url) {
2003
+ try {
2004
+ const parsed = new URL(url, window.location.href);
2005
+ if (parsed.protocol === "javascript:" || parsed.protocol === "data:") {
2006
+ this.log(`Blocked unsafe URL protocol: ${parsed.protocol}`, true);
2007
+ return null;
2008
+ }
2009
+ return parsed.href;
2010
+ } catch {
2011
+ this.log(`Invalid URL: ${url}`, true);
2012
+ return null;
2013
+ }
2014
+ }
2015
+ sanitizeColor(color) {
2016
+ const hexPattern = /^#[0-9A-Fa-f]{3,6}$/;
2017
+ const rgbPattern = /^rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*(,\s*[\d.]+\s*)?\)$/;
2018
+ const namedColors = ["white", "black", "red", "blue", "green", "yellow", "transparent"];
2019
+ if (hexPattern.test(color) || rgbPattern.test(color) || namedColors.includes(color.toLowerCase())) {
2020
+ return color;
2021
+ }
2022
+ this.log(`Invalid color: ${color}`, true);
2023
+ return "#000000";
2024
+ }
2025
+ sanitizeHTML(html) {
2026
+ const tempDiv = document.createElement("div");
2027
+ tempDiv.textContent = html;
2028
+ return tempDiv.innerHTML;
2029
+ }
2030
+ log(message, isError = false) {
2031
+ if (this.debugMode || isError) {
2032
+ console[isError ? "error" : "log"](`[AegisPlacements] ${message}`);
2033
+ }
2034
+ }
2035
+ destroy() {
2036
+ this.disconnectSSE();
2037
+ this.slots.clear();
2038
+ this.placements.clear();
2039
+ this.renderedSlots.clear();
2040
+ this.isInitialized = false;
2041
+ this.log("AegisPlacements destroyed");
2042
+ }
2043
+ }
2044
+ class AegisWidgetManager {
2045
+ constructor(config) {
2046
+ this.widgets = [];
2047
+ this.renderedWidgets = /* @__PURE__ */ new Set();
2048
+ this.isInitialized = false;
2049
+ this.prefetchWidgetConfigs = {};
2050
+ this.writeKey = config.writeKey;
2051
+ this.apiHost = config.apiHost || "https://api.aegis.ai";
2052
+ this.userId = config.userId;
2053
+ this.contactId = config.contactId;
2054
+ this.organizationId = config.organizationId;
2055
+ this.debugMode = config.debugMode || false;
2056
+ this.triggerEngine = config.triggerEngine;
2057
+ this.enablePrefetch = config.enablePrefetch !== false;
2058
+ this.cssCustomization = config.cssCustomization || {};
2059
+ this.onEvent = config.onEvent;
2060
+ this.sourcePlatform = config.sourcePlatform;
2061
+ }
2062
+ updateCSSCustomization(customization) {
2063
+ this.cssCustomization = { ...this.cssCustomization, ...customization };
2064
+ this.log("CSS customization updated");
2065
+ }
2066
+ emitEvent(eventType, data) {
2067
+ if (this.onEvent) {
2068
+ try {
2069
+ this.onEvent(eventType, data);
2070
+ } catch (error) {
2071
+ this.log(`Error in event callback: ${error}`, true);
2072
+ }
2073
+ }
2074
+ }
2075
+ async initialize() {
2076
+ if (this.isInitialized) {
2077
+ this.log("AegisWidgets already initialized");
2078
+ return;
2079
+ }
2080
+ if (this.enablePrefetch && this.contactId) {
2081
+ await this.fetchPrefetchConfigs();
2082
+ this.preloadWidgetAssets();
2083
+ }
2084
+ await this.fetchWidgets();
2085
+ this.renderImmediateWidgets();
2086
+ this.setupTriggerListeners();
2087
+ this.setupExitIntentWithPrefetch();
2088
+ this.isInitialized = true;
2089
+ this.log("AegisWidgets initialized successfully");
2090
+ }
2091
+ setCartData(cartData) {
2092
+ this.cartData = cartData;
2093
+ this.log(`Cart data updated: ${cartData.cart_items.length} items, total: ${cartData.cart_currency} ${cartData.cart_total}`);
2094
+ }
2095
+ detectPlatformCart() {
2096
+ const shopifyCart = this.normalizeShopifyCart();
2097
+ if (shopifyCart) {
2098
+ this.log("Detected Shopify cart via browser globals");
2099
+ return shopifyCart;
2100
+ }
2101
+ const wooCart = this.normalizeWooCart();
2102
+ if (wooCart) {
2103
+ this.log("Detected WooCommerce cart via injected data");
2104
+ return wooCart;
2105
+ }
2106
+ const magentoCart = this.normalizeMagentoCart();
2107
+ if (magentoCart) {
2108
+ this.log("Detected Magento cart via localStorage");
2109
+ return magentoCart;
2110
+ }
2111
+ if (this.cartData) {
2112
+ this.log("Using manually set cart data");
2113
+ return this.cartData;
2114
+ }
2115
+ return null;
2116
+ }
2117
+ normalizeShopifyCart() {
2118
+ var _a;
2119
+ try {
2120
+ if (typeof window === "undefined") return null;
2121
+ const win = window;
2122
+ if ((_a = win.Shopify) == null ? void 0 : _a.checkout) {
2123
+ const checkout = win.Shopify.checkout;
2124
+ return {
2125
+ cart_id: checkout.token || `shopify_${Date.now()}`,
2126
+ cart_total: parseFloat(checkout.total_price) || 0,
2127
+ cart_currency: checkout.currency || "USD",
2128
+ cart_items: (checkout.line_items || []).map((item) => ({
2129
+ product_id: String(item.product_id || item.id),
2130
+ product_name: item.title || item.product_title,
2131
+ quantity: item.quantity || 1,
2132
+ price: parseFloat(item.price) || 0
2133
+ }))
2134
+ };
2135
+ }
2136
+ const cartJsonEl = document.getElementById("cart-json");
2137
+ if (cartJsonEl == null ? void 0 : cartJsonEl.textContent) {
2138
+ const cart = JSON.parse(cartJsonEl.textContent);
2139
+ return {
2140
+ cart_id: cart.token || `shopify_${Date.now()}`,
2141
+ cart_total: (cart.total_price || 0) / 100,
2142
+ cart_currency: cart.currency || "USD",
2143
+ cart_items: (cart.items || []).map((item) => ({
2144
+ product_id: String(item.product_id || item.id),
2145
+ product_name: item.product_title || item.title,
2146
+ quantity: item.quantity || 1,
2147
+ price: (item.price || 0) / 100
2148
+ }))
2149
+ };
2150
+ }
2151
+ return null;
2152
+ } catch (error) {
2153
+ this.log(`Error detecting Shopify cart: ${error}`, true);
2154
+ return null;
2155
+ }
2156
+ }
2157
+ normalizeWooCart() {
2158
+ try {
2159
+ if (typeof window === "undefined") return null;
2160
+ const win = window;
2161
+ if (win.aegis_cart) {
2162
+ const cart = win.aegis_cart;
2163
+ return {
2164
+ cart_id: cart.cart_id || `woo_${Date.now()}`,
2165
+ cart_total: parseFloat(cart.cart_total) || 0,
2166
+ cart_currency: cart.cart_currency || "USD",
2167
+ cart_items: (cart.cart_items || []).map((item) => ({
2168
+ product_id: String(item.product_id),
2169
+ product_name: item.product_name,
2170
+ quantity: item.quantity || 1,
2171
+ price: parseFloat(item.price) || 0
2172
+ }))
2173
+ };
2174
+ }
2175
+ return null;
2176
+ } catch (error) {
2177
+ this.log(`Error detecting WooCommerce cart: ${error}`, true);
2178
+ return null;
2179
+ }
2180
+ }
2181
+ normalizeMagentoCart() {
2182
+ try {
2183
+ if (typeof window === "undefined" || !window.localStorage) return null;
2184
+ const mageCacheStr = localStorage.getItem("mage-cache-storage");
2185
+ if (!mageCacheStr) return null;
2186
+ const mageCache = JSON.parse(mageCacheStr);
2187
+ if (!(mageCache == null ? void 0 : mageCache.cart)) return null;
2188
+ const cart = mageCache.cart;
2189
+ return {
2190
+ cart_id: cart.quote_id || cart.id || `magento_${Date.now()}`,
2191
+ cart_total: parseFloat(cart.subtotalAmount || cart.subtotal || 0),
2192
+ cart_currency: cart.currencyCode || "USD",
2193
+ cart_items: (cart.items || []).map((item) => ({
2194
+ product_id: String(item.item_id || item.product_id),
2195
+ product_name: item.product_name || item.name,
2196
+ quantity: item.qty || 1,
2197
+ price: parseFloat(item.product_price_value || item.price || 0)
2198
+ }))
2199
+ };
2200
+ } catch (error) {
2201
+ this.log(`Error detecting Magento cart: ${error}`, true);
2202
+ return null;
2203
+ }
2204
+ }
2205
+ preloadWidgetAssets() {
2206
+ try {
2207
+ const exitIntentConfig = this.prefetchWidgetConfigs.exit_intent;
2208
+ if (exitIntentConfig == null ? void 0 : exitIntentConfig.enabled) {
2209
+ const imageUrl = exitIntentConfig.image_url;
2210
+ if (imageUrl) {
2211
+ const img = new Image();
2212
+ img.src = imageUrl;
2213
+ this.log(`Preloading exit intent image: ${imageUrl}`);
2214
+ }
2215
+ }
2216
+ const spinWheelConfig = this.prefetchWidgetConfigs.spin_wheel;
2217
+ if (spinWheelConfig == null ? void 0 : spinWheelConfig.enabled) {
2218
+ const imageUrl = spinWheelConfig.image_url;
2219
+ if (imageUrl) {
2220
+ const img = new Image();
2221
+ img.src = imageUrl;
2222
+ this.log(`Preloading spin wheel image: ${imageUrl}`);
2223
+ }
2224
+ }
2225
+ } catch (error) {
2226
+ this.log(`Error preloading widget assets: ${error}`, true);
2227
+ }
2228
+ }
2229
+ async fetchPrefetchConfigs() {
2230
+ try {
2231
+ const startTime = performance.now();
2232
+ const url = `${this.apiHost}/v1/widgets/config/prefetch`;
2233
+ const headers = {
2234
+ "X-Aegis-Write-Key": this.writeKey
2235
+ };
2236
+ if (this.contactId) headers["X-Contact-ID"] = this.contactId;
2237
+ if (this.organizationId) headers["X-Organization-ID"] = this.organizationId;
2238
+ const response = await fetch(url, { headers });
2239
+ if (!response.ok) {
2240
+ throw new Error(`Failed to fetch prefetch configs: ${response.status}`);
2241
+ }
2242
+ this.prefetchWidgetConfigs = await response.json();
2243
+ const elapsed = performance.now() - startTime;
2244
+ this.log(`Fetched prefetch widget configs in ${elapsed.toFixed(2)}ms`);
2245
+ } catch (error) {
2246
+ this.log(`Error fetching prefetch configs: ${error}`, true);
2247
+ }
2248
+ }
2249
+ async fetchWidgets() {
2250
+ try {
2251
+ const url = `${this.apiHost}/v1/widgets/config`;
2252
+ const headers = {
2253
+ "X-Aegis-Write-Key": this.writeKey,
2254
+ "X-Device-Platform": "web",
2255
+ "X-Device-Type": this.getDeviceType()
2256
+ };
2257
+ if (this.userId) headers["X-User-ID"] = this.userId;
2258
+ if (this.contactId) headers["X-Contact-ID"] = this.contactId;
2259
+ if (this.organizationId) headers["X-Organization-ID"] = this.organizationId;
2260
+ if (this.sourcePlatform) headers["X-Source-Platform"] = this.sourcePlatform;
2261
+ const response = await fetch(url, { headers });
2262
+ if (!response.ok) {
2263
+ throw new Error(`Failed to fetch widgets: ${response.status}`);
2264
+ }
2265
+ this.widgets = await response.json();
2266
+ this.log(`Fetched ${this.widgets.length} widgets`);
2267
+ } catch (error) {
2268
+ this.log(`Error fetching widgets: ${error}`, true);
2269
+ }
2270
+ }
2271
+ renderImmediateWidgets() {
2272
+ const immediateWidgets = this.widgets.filter(
2273
+ (w) => !w.trigger_rules || w.trigger_rules.type === "immediate"
2274
+ );
2275
+ immediateWidgets.forEach((widget) => this.renderWidget(widget));
2276
+ }
2277
+ setupTriggerListeners() {
2278
+ if (!this.triggerEngine) {
2279
+ this.log("TriggerEngine not provided, skipping trigger-based widgets");
2280
+ return;
2281
+ }
2282
+ this.widgets.forEach((widget) => {
2283
+ if (!widget.trigger_rules) return;
2284
+ const { type, config } = widget.trigger_rules;
2285
+ switch (type) {
2286
+ case "exit_intent":
2287
+ this.triggerEngine.registerExitIntent();
2288
+ this.triggerEngine.on("exit_intent", () => {
2289
+ if (!this.renderedWidgets.has(widget.widget_id)) {
2290
+ this.renderWidget(widget);
2291
+ }
2292
+ });
2293
+ break;
2294
+ case "scroll_depth":
2295
+ const depthPercent = (config == null ? void 0 : config.depth_percent) || 50;
2296
+ this.triggerEngine.registerScrollDepth(depthPercent);
2297
+ this.triggerEngine.on(`scroll_depth_${depthPercent}`, () => {
2298
+ if (!this.renderedWidgets.has(widget.widget_id)) {
2299
+ this.renderWidget(widget);
2300
+ }
2301
+ });
2302
+ break;
2303
+ case "time_on_page":
2304
+ const seconds = (config == null ? void 0 : config.seconds) || 30;
2305
+ this.triggerEngine.registerTimeOnPage(seconds);
2306
+ this.triggerEngine.on(`time_on_page_${seconds}`, () => {
2307
+ if (!this.renderedWidgets.has(widget.widget_id)) {
2308
+ this.renderWidget(widget);
2309
+ }
2310
+ });
2311
+ break;
2312
+ }
2313
+ });
2314
+ }
2315
+ setupExitIntentWithPrefetch() {
2316
+ if (!this.enablePrefetch || !this.triggerEngine) {
2317
+ return;
2318
+ }
2319
+ const spinWheelConfig = this.prefetchWidgetConfigs.spin_wheel;
2320
+ const exitIntentConfig = this.prefetchWidgetConfigs.exit_intent;
2321
+ if (spinWheelConfig && spinWheelConfig.enabled) {
2322
+ const isMobile2 = this.isMobileDevice();
2323
+ const mobileConfig2 = (exitIntentConfig == null ? void 0 : exitIntentConfig.mobile_triggers) || {};
2324
+ const handleSpinWheelIntent = () => {
2325
+ const widgetId = `prefetch_spin_wheel_${Date.now()}`;
2326
+ if (this.renderedWidgets.has(widgetId)) {
2327
+ return;
2328
+ }
2329
+ this.renderedWidgets.add(widgetId);
2330
+ const detectedCart = this.detectPlatformCart();
2331
+ if (spinWheelConfig.type === "cart_recovery" && detectedCart) {
2332
+ this.cartData = detectedCart;
2333
+ this.renderSpinWheelWidget(widgetId, spinWheelConfig);
2334
+ this.sendCartAbandonmentBeacon();
2335
+ } else if (spinWheelConfig.type === "lead_gen") {
2336
+ this.renderSpinWheelWidget(widgetId, spinWheelConfig);
2337
+ }
2338
+ };
2339
+ if (isMobile2) {
2340
+ const scrollVelocityEnabled = mobileConfig2.scroll_velocity !== false;
2341
+ if (scrollVelocityEnabled) {
2342
+ this.triggerEngine.registerScrollVelocity({
2343
+ threshold: mobileConfig2.scroll_threshold || 0.5,
2344
+ minScrollPosition: mobileConfig2.scroll_min_position || 100,
2345
+ cooldown: mobileConfig2.scroll_cooldown || 5e3
2346
+ });
2347
+ this.triggerEngine.on("mobile_exit_intent", handleSpinWheelIntent);
2348
+ this.log("Registered mobile scroll velocity trigger for spin wheel");
2349
+ }
2350
+ const idleTimerEnabled = mobileConfig2.idle_timer !== false;
2351
+ if (idleTimerEnabled) {
2352
+ const idleSeconds = mobileConfig2.idle_seconds || 15;
2353
+ this.triggerEngine.registerInactivity(idleSeconds);
2354
+ this.triggerEngine.on(`inactivity_${idleSeconds}`, handleSpinWheelIntent);
2355
+ this.log(`Registered mobile idle timer trigger for spin wheel: ${idleSeconds}s`);
2356
+ }
2357
+ const visibilityChangeEnabled = mobileConfig2.visibility_change !== false;
2358
+ if (visibilityChangeEnabled) {
2359
+ const detectedCart = this.detectPlatformCart();
2360
+ if (detectedCart && detectedCart.cart_items && detectedCart.cart_items.length > 0) {
2361
+ this.triggerEngine.registerVisibilityChange();
2362
+ this.triggerEngine.on("visibility_hidden", handleSpinWheelIntent);
2363
+ this.log("Registered mobile visibility change trigger for spin wheel (cart detected)");
2364
+ }
2365
+ }
2366
+ const backButtonEnabled = mobileConfig2.back_button === true;
2367
+ if (backButtonEnabled) {
2368
+ this.triggerEngine.registerBackButton();
2369
+ this.triggerEngine.on("back_button", handleSpinWheelIntent);
2370
+ this.log("Registered mobile back button trigger for spin wheel");
2371
+ }
2372
+ this.log(`Setup mobile spin wheel: type=${spinWheelConfig.type}, priority=${spinWheelConfig.priority}`);
2373
+ } else {
2374
+ this.triggerEngine.registerExitIntent();
2375
+ this.triggerEngine.on("exit_intent", handleSpinWheelIntent);
2376
+ this.log(`Setup desktop spin wheel exit intent: type=${spinWheelConfig.type}, priority=${spinWheelConfig.priority}`);
2377
+ }
2378
+ return;
2379
+ }
2380
+ if (!exitIntentConfig || !exitIntentConfig.enabled) {
2381
+ this.log("No exit intent config found in prefetch");
2382
+ return;
2383
+ }
2384
+ const isMobile = this.isMobileDevice();
2385
+ const mobileConfig = exitIntentConfig.mobile_triggers || {};
2386
+ const handleExitIntent = () => {
2387
+ const widgetId = `prefetch_exit_intent_${Date.now()}`;
2388
+ if (this.renderedWidgets.has(widgetId)) {
2389
+ return;
2390
+ }
2391
+ this.renderedWidgets.add(widgetId);
2392
+ const detectedCart = this.detectPlatformCart();
2393
+ if (exitIntentConfig.type === "cart_recovery" && detectedCart) {
2394
+ this.cartData = detectedCart;
2395
+ this.renderCartRecoveryPopup(widgetId, exitIntentConfig);
2396
+ this.sendCartAbandonmentBeacon();
2397
+ } else if (exitIntentConfig.type === "lead_gen") {
2398
+ this.renderLeadGenPopup(widgetId, exitIntentConfig);
2399
+ } else {
2400
+ this.log("Unknown exit intent type or missing cart data");
2401
+ }
2402
+ };
2403
+ if (isMobile) {
2404
+ const scrollVelocityEnabled = mobileConfig.scroll_velocity !== false;
2405
+ if (scrollVelocityEnabled) {
2406
+ this.triggerEngine.registerScrollVelocity({
2407
+ threshold: mobileConfig.scroll_threshold || 0.5,
2408
+ minScrollPosition: mobileConfig.scroll_min_position || 100,
2409
+ cooldown: mobileConfig.scroll_cooldown || 5e3
2410
+ });
2411
+ this.triggerEngine.on("mobile_exit_intent", handleExitIntent);
2412
+ this.log("Registered mobile scroll velocity trigger");
2413
+ }
2414
+ const idleTimerEnabled = mobileConfig.idle_timer !== false;
2415
+ if (idleTimerEnabled) {
2416
+ const idleSeconds = mobileConfig.idle_seconds || 15;
2417
+ this.triggerEngine.registerInactivity(idleSeconds);
2418
+ this.triggerEngine.on(`inactivity_${idleSeconds}`, handleExitIntent);
2419
+ this.log(`Registered mobile idle timer trigger: ${idleSeconds}s`);
2420
+ }
2421
+ const visibilityChangeEnabled = mobileConfig.visibility_change !== false;
2422
+ if (visibilityChangeEnabled) {
2423
+ const detectedCart = this.detectPlatformCart();
2424
+ if (detectedCart && detectedCart.cart_items && detectedCart.cart_items.length > 0) {
2425
+ this.triggerEngine.registerVisibilityChange();
2426
+ this.triggerEngine.on("visibility_hidden", handleExitIntent);
2427
+ this.log("Registered mobile visibility change trigger (cart detected)");
2428
+ } else {
2429
+ this.log("Skipped visibility change trigger (no cart items)");
2430
+ }
2431
+ }
2432
+ const backButtonEnabled = mobileConfig.back_button === true;
2433
+ if (backButtonEnabled) {
2434
+ this.triggerEngine.registerBackButton();
2435
+ this.triggerEngine.on("back_button", handleExitIntent);
2436
+ this.log("Registered mobile back button trigger (aggressive mode)");
2437
+ }
2438
+ this.log(`Setup mobile exit intent: type=${exitIntentConfig.type}, priority=${exitIntentConfig.priority}`);
2439
+ } else {
2440
+ this.triggerEngine.registerExitIntent();
2441
+ this.triggerEngine.on("exit_intent", handleExitIntent);
2442
+ this.log(`Setup desktop exit intent: type=${exitIntentConfig.type}, priority=${exitIntentConfig.priority}`);
2443
+ }
2444
+ }
2445
+ sendCartAbandonmentBeacon() {
2446
+ if (!this.cartData) {
2447
+ this.log("No cart data available for beacon", true);
2448
+ return;
2449
+ }
2450
+ const beaconData = {
2451
+ organization_id: this.organizationId || "default",
2452
+ contact_id: this.contactId,
2453
+ cart_id: this.cartData.cart_id,
2454
+ cart_total: this.cartData.cart_total,
2455
+ cart_currency: this.cartData.cart_currency,
2456
+ cart_items: this.cartData.cart_items,
2457
+ user_email: this.userId,
2458
+ abandoned_at: (/* @__PURE__ */ new Date()).toISOString(),
2459
+ page_url: window.location.href,
2460
+ referrer: document.referrer
2461
+ };
2462
+ const beaconUrl = `${this.apiHost}/v1/widgets/beacon/cart-abandoned`;
2463
+ if (navigator.sendBeacon) {
2464
+ const blob = new Blob([JSON.stringify(beaconData)], { type: "application/json" });
2465
+ const sent = navigator.sendBeacon(beaconUrl, blob);
2466
+ if (sent) {
2467
+ this.log("Cart abandonment beacon sent successfully");
2468
+ } else {
2469
+ this.log("Failed to send cart abandonment beacon", true);
2470
+ }
2471
+ } else {
2472
+ fetch(beaconUrl, {
2473
+ method: "POST",
2474
+ headers: {
2475
+ "Content-Type": "application/json",
2476
+ "X-Aegis-Write-Key": this.writeKey
2477
+ },
2478
+ body: JSON.stringify(beaconData),
2479
+ keepalive: true
2480
+ }).catch((err) => {
2481
+ this.log(`Error sending cart abandonment via fetch: ${err}`, true);
2482
+ });
2483
+ }
2484
+ }
2485
+ renderWidget(widget) {
2486
+ if (this.renderedWidgets.has(widget.widget_id)) {
2487
+ return;
2488
+ }
2489
+ this.renderedWidgets.add(widget.widget_id);
2490
+ switch (widget.widget_type) {
2491
+ case "chat_bubble":
2492
+ this.renderChatBubble(widget);
2493
+ break;
2494
+ case "spin_wheel":
2495
+ this.renderSpinWheel(widget);
2496
+ break;
2497
+ case "scratch_card":
2498
+ this.renderScratchCard(widget);
2499
+ break;
2500
+ case "toast":
2501
+ this.renderToast(widget);
2502
+ break;
2503
+ case "feedback_form":
2504
+ this.renderFeedbackForm(widget);
2505
+ break;
2506
+ case "exit_intent_popup":
2507
+ this.renderExitIntentPopup(widget);
2508
+ break;
2509
+ default:
2510
+ this.log(`Unknown widget type: ${widget.widget_type}`, true);
2511
+ }
2512
+ this.trackEvent(widget.widget_id, "show");
2513
+ }
2514
+ renderChatBubble(widget) {
2515
+ const { text, icon_url, link_url, background_color, text_color } = widget.config;
2516
+ const position = widget.position || "bottom_right";
2517
+ const bubble = document.createElement("div");
2518
+ bubble.className = "aegis-chat-bubble";
2519
+ bubble.setAttribute("data-widget-id", widget.widget_id);
2520
+ const positionStyles = {
2521
+ bottom_right: "bottom: 20px; right: 20px;",
2522
+ bottom_left: "bottom: 20px; left: 20px;",
2523
+ top_right: "top: 20px; right: 20px;",
2524
+ top_left: "top: 20px; left: 20px;"
2525
+ };
2526
+ bubble.style.cssText = `
2527
+ position: fixed;
2528
+ ${positionStyles[position] || positionStyles.bottom_right}
2529
+ background: ${this.sanitizeColor(background_color || "#25D366")};
2530
+ color: ${this.sanitizeColor(text_color || "#ffffff")};
2531
+ padding: 16px 24px;
2532
+ border-radius: 50px;
2533
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
2534
+ cursor: pointer;
2535
+ z-index: 999999;
2536
+ display: flex;
2537
+ align-items: center;
2538
+ gap: 12px;
2539
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
2540
+ font-weight: 600;
2541
+ font-size: 14px;
2542
+ animation: aegisBubbleIn 0.3s ease-out;
2543
+ `;
2544
+ if (icon_url) {
2545
+ const icon = document.createElement("img");
2546
+ const safeUrl = this.sanitizeUrl(icon_url);
2547
+ if (safeUrl) {
2548
+ icon.src = safeUrl;
2549
+ icon.alt = "";
2550
+ icon.style.cssText = "width: 24px; height: 24px;";
2551
+ bubble.appendChild(icon);
2552
+ }
2553
+ }
2554
+ const textEl = document.createElement("span");
2555
+ textEl.textContent = text || "Chat with us";
2556
+ bubble.appendChild(textEl);
2557
+ bubble.addEventListener("click", () => {
2558
+ this.trackEvent(widget.widget_id, "click");
2559
+ const safeUrl = this.sanitizeUrl(link_url);
2560
+ if (safeUrl) {
2561
+ window.open(safeUrl, "_blank");
2562
+ }
2563
+ });
2564
+ this.addAnimationStyles();
2565
+ document.body.appendChild(bubble);
2566
+ }
2567
+ renderSpinWheel(widget) {
2568
+ const overlay = document.createElement("div");
2569
+ overlay.className = "aegis-gamification-overlay";
2570
+ overlay.setAttribute("data-widget-id", widget.widget_id);
2571
+ overlay.style.cssText = `
2572
+ position: fixed;
2573
+ top: 0;
2574
+ left: 0;
2575
+ right: 0;
2576
+ bottom: 0;
2577
+ background: rgba(0, 0, 0, 0.7);
2578
+ z-index: 1000000;
2579
+ display: flex;
2580
+ align-items: center;
2581
+ justify-content: center;
2582
+ animation: aegisFadeIn 0.3s ease-out;
2583
+ `;
2584
+ const modal = document.createElement("div");
2585
+ modal.className = "aegis-spin-wheel-modal";
2586
+ modal.style.cssText = `
2587
+ background: white;
2588
+ border-radius: 16px;
2589
+ padding: 32px;
2590
+ max-width: 500px;
2591
+ width: 90%;
2592
+ box-shadow: 0 10px 40px rgba(0,0,0,0.3);
2593
+ text-align: center;
2594
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
2595
+ animation: aegisScaleIn 0.3s ease-out;
2596
+ `;
2597
+ const title = document.createElement("h2");
2598
+ title.textContent = widget.config.title || "Spin to Win!";
2599
+ title.style.cssText = "margin: 0 0 16px 0; font-size: 24px; font-weight: 700; color: #1a1a1a;";
2600
+ modal.appendChild(title);
2601
+ const description = document.createElement("p");
2602
+ description.textContent = widget.config.description || "Try your luck and win exclusive prizes!";
2603
+ description.style.cssText = "margin: 0 0 24px 0; font-size: 14px; color: #666;";
2604
+ modal.appendChild(description);
2605
+ const wheelPlaceholder = document.createElement("div");
2606
+ wheelPlaceholder.style.cssText = `
2607
+ width: 300px;
2608
+ height: 300px;
2609
+ margin: 0 auto 24px;
2610
+ background: linear-gradient(45deg, #ff6b6b, #feca57, #48dbfb, #ff6348);
2611
+ border-radius: 50%;
2612
+ display: flex;
2613
+ align-items: center;
2614
+ justify-content: center;
2615
+ font-size: 48px;
2616
+ color: white;
2617
+ `;
2618
+ wheelPlaceholder.textContent = "🎰";
2619
+ modal.appendChild(wheelPlaceholder);
2620
+ const spinButton = document.createElement("button");
2621
+ spinButton.textContent = "SPIN NOW!";
2622
+ spinButton.style.cssText = `
2623
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
2624
+ color: white;
2625
+ border: none;
2626
+ padding: 16px 48px;
2627
+ border-radius: 8px;
2628
+ font-weight: 700;
2629
+ font-size: 16px;
2630
+ cursor: pointer;
2631
+ margin-bottom: 16px;
2632
+ `;
2633
+ spinButton.addEventListener("click", async () => {
2634
+ spinButton.disabled = true;
2635
+ spinButton.textContent = "SPINNING...";
2636
+ try {
2637
+ const prize = await this.generatePrize(widget.config.config_id);
2638
+ wheelPlaceholder.style.animation = "aegisSpin 2s ease-out";
2639
+ setTimeout(() => {
2640
+ this.showPrizeResult(modal, prize);
2641
+ }, 2e3);
2642
+ } catch (error) {
2643
+ this.log(`Error generating prize: ${error}`, true);
2644
+ spinButton.disabled = false;
2645
+ spinButton.textContent = "TRY AGAIN";
2646
+ }
2647
+ });
2648
+ modal.appendChild(spinButton);
2649
+ const closeButton = document.createElement("button");
2650
+ closeButton.textContent = "×";
2651
+ closeButton.setAttribute("aria-label", "Close");
2652
+ closeButton.style.cssText = `
2653
+ position: absolute;
2654
+ top: 16px;
2655
+ right: 16px;
2656
+ background: transparent;
2657
+ border: none;
2658
+ font-size: 28px;
2659
+ cursor: pointer;
2660
+ color: #999;
2661
+ `;
2662
+ closeButton.addEventListener("click", () => {
2663
+ this.trackEvent(widget.widget_id, "dismiss");
2664
+ document.body.removeChild(overlay);
2665
+ this.renderedWidgets.delete(widget.widget_id);
2666
+ });
2667
+ modal.style.position = "relative";
2668
+ modal.appendChild(closeButton);
2669
+ overlay.appendChild(modal);
2670
+ this.addAnimationStyles();
2671
+ document.body.appendChild(overlay);
2672
+ }
2673
+ renderSpinWheelWidget(widgetId, config) {
2674
+ const cart = this.detectPlatformCart();
2675
+ if (config.type === "cart_recovery" && !cart) {
2676
+ this.log("Spin wheel requires cart, but none detected");
2677
+ return;
2678
+ }
2679
+ if (cart) {
2680
+ this.cartData = cart;
2681
+ }
2682
+ const customization = this.cssCustomization.spinWheel || {};
2683
+ const accentColor = customization.accentColor || config.accent_color || "#FF6B6B";
2684
+ const backgroundColor = customization.backgroundColor || config.background_color || "#FFFFFF";
2685
+ const textColor = customization.textColor || config.text_color || "#333333";
2686
+ const buttonColor = customization.buttonColor || config.button_color || accentColor;
2687
+ const wheelColors = customization.wheelColors || ["#FF6B6B", "#4ECDC4", "#FFE66D", "#95E1D3"];
2688
+ const overlay = document.createElement("div");
2689
+ overlay.className = "aegis-spin-wheel-overlay";
2690
+ overlay.setAttribute("data-widget-id", widgetId);
2691
+ overlay.style.cssText = `
2692
+ position: fixed;
2693
+ top: 0;
2694
+ left: 0;
2695
+ width: 100%;
2696
+ height: 100%;
2697
+ background: rgba(0, 0, 0, 0.7);
2698
+ display: flex;
2699
+ align-items: center;
2700
+ justify-content: center;
2701
+ z-index: 999999;
2702
+ animation: aegisFadeIn 0.3s ease;
2703
+ `;
2704
+ const modal = document.createElement("div");
2705
+ modal.className = "aegis-spin-wheel-modal";
2706
+ modal.style.cssText = `
2707
+ background: ${this.sanitizeColor(backgroundColor)};
2708
+ border-radius: 16px;
2709
+ padding: 32px;
2710
+ max-width: 500px;
2711
+ width: 90%;
2712
+ box-shadow: 0 20px 60px rgba(0,0,0,0.3);
2713
+ position: relative;
2714
+ animation: aegisScaleIn 0.4s ease;
2715
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
2716
+ `;
2717
+ const closeBtn = document.createElement("button");
2718
+ closeBtn.innerHTML = "✕";
2719
+ closeBtn.className = "aegis-spin-wheel-close";
2720
+ closeBtn.style.cssText = `
2721
+ position: absolute;
2722
+ top: 16px;
2723
+ right: 16px;
2724
+ background: none;
2725
+ border: none;
2726
+ font-size: 24px;
2727
+ cursor: pointer;
2728
+ color: #999;
2729
+ `;
2730
+ closeBtn.onclick = () => {
2731
+ this.trackEvent(widgetId, "dismiss", { type: "spin_wheel" });
2732
+ document.body.removeChild(overlay);
2733
+ this.renderedWidgets.delete(widgetId);
2734
+ };
2735
+ modal.appendChild(closeBtn);
2736
+ const title = document.createElement("h2");
2737
+ title.textContent = this.interpolateCartVariables(config.title || "Spin to Win!");
2738
+ title.style.cssText = `
2739
+ margin: 0 0 16px 0;
2740
+ font-size: 28px;
2741
+ font-weight: bold;
2742
+ text-align: center;
2743
+ color: ${this.sanitizeColor(textColor)};
2744
+ `;
2745
+ modal.appendChild(title);
2746
+ if (cart) {
2747
+ const subtitle = document.createElement("p");
2748
+ subtitle.textContent = `Complete your ${cart.cart_currency} ${cart.cart_total.toFixed(2)} order and save!`;
2749
+ subtitle.style.cssText = `
2750
+ margin: 0 0 24px 0;
2751
+ font-size: 16px;
2752
+ text-align: center;
2753
+ color: #666;
2754
+ `;
2755
+ modal.appendChild(subtitle);
2756
+ }
2757
+ const wheelContainer = document.createElement("div");
2758
+ wheelContainer.className = "aegis-wheel-container";
2759
+ wheelContainer.style.cssText = `
2760
+ width: 300px;
2761
+ height: 300px;
2762
+ margin: 0 auto 24px auto;
2763
+ background: conic-gradient(
2764
+ from 0deg,
2765
+ ${wheelColors[0]} 0deg 90deg,
2766
+ ${wheelColors[1]} 90deg 180deg,
2767
+ ${wheelColors[2]} 180deg 270deg,
2768
+ ${wheelColors[3]} 270deg 360deg
2769
+ );
2770
+ border-radius: 50%;
2771
+ position: relative;
2772
+ display: flex;
2773
+ align-items: center;
2774
+ justify-content: center;
2775
+ `;
2776
+ const form = document.createElement("form");
2777
+ form.className = "aegis-spin-form";
2778
+ form.style.cssText = "display: block;";
2779
+ const phoneInput = document.createElement("input");
2780
+ phoneInput.type = "tel";
2781
+ phoneInput.placeholder = "Enter your phone number";
2782
+ phoneInput.required = true;
2783
+ phoneInput.style.cssText = `
2784
+ width: 100%;
2785
+ padding: 14px;
2786
+ border: 2px solid #ddd;
2787
+ border-radius: 8px;
2788
+ font-size: 16px;
2789
+ margin-bottom: 12px;
2790
+ box-sizing: border-box;
2791
+ `;
2792
+ form.appendChild(phoneInput);
2793
+ const emailInput = document.createElement("input");
2794
+ emailInput.type = "email";
2795
+ emailInput.placeholder = "Enter your email (optional)";
2796
+ emailInput.style.cssText = `
2797
+ width: 100%;
2798
+ padding: 14px;
2799
+ border: 2px solid #ddd;
2800
+ border-radius: 8px;
2801
+ font-size: 16px;
2802
+ margin-bottom: 12px;
2803
+ box-sizing: border-box;
2804
+ `;
2805
+ form.appendChild(emailInput);
2806
+ const nameInput = document.createElement("input");
2807
+ nameInput.type = "text";
2808
+ nameInput.placeholder = "Enter your name (optional)";
2809
+ nameInput.style.cssText = `
2810
+ width: 100%;
2811
+ padding: 14px;
2812
+ border: 2px solid #ddd;
2813
+ border-radius: 8px;
2814
+ font-size: 16px;
2815
+ margin-bottom: 12px;
2816
+ box-sizing: border-box;
2817
+ `;
2818
+ form.appendChild(nameInput);
2819
+ const submitButton = document.createElement("button");
2820
+ submitButton.type = "submit";
2821
+ submitButton.textContent = "Spin the Wheel!";
2822
+ submitButton.style.cssText = `
2823
+ width: 100%;
2824
+ padding: 14px;
2825
+ background: ${this.sanitizeColor(buttonColor)};
2826
+ color: white;
2827
+ border: none;
2828
+ border-radius: 8px;
2829
+ font-size: 16px;
2830
+ font-weight: 700;
2831
+ cursor: pointer;
2832
+ `;
2833
+ form.appendChild(submitButton);
2834
+ const errorMessage = document.createElement("div");
2835
+ errorMessage.className = "aegis-spin-error";
2836
+ errorMessage.style.cssText = `
2837
+ color: #d32f2f;
2838
+ font-size: 14px;
2839
+ margin-top: 12px;
2840
+ text-align: center;
2841
+ display: none;
2842
+ `;
2843
+ form.appendChild(errorMessage);
2844
+ form.addEventListener("submit", async (e) => {
2845
+ e.preventDefault();
2846
+ const phone = phoneInput.value.trim();
2847
+ const email = emailInput.value.trim();
2848
+ const name = nameInput.value.trim();
2849
+ if (!this.validatePhone(phone)) {
2850
+ errorMessage.textContent = "Please enter a valid phone number (e.g., +1234567890)";
2851
+ errorMessage.style.display = "block";
2852
+ return;
2853
+ }
2854
+ if (email && !this.validateEmail(email)) {
2855
+ errorMessage.textContent = "Please enter a valid email address";
2856
+ errorMessage.style.display = "block";
2857
+ return;
2858
+ }
2859
+ errorMessage.style.display = "none";
2860
+ submitButton.disabled = true;
2861
+ submitButton.textContent = "Spinning...";
2862
+ wheelContainer.style.animation = "aegisSpin 2s ease-out";
2863
+ setTimeout(async () => {
2864
+ try {
2865
+ const prize = await this.submitSpinWheel({
2866
+ phone,
2867
+ email,
2868
+ name,
2869
+ cart,
2870
+ widgetId
2871
+ });
2872
+ this.showSpinWheelPrize(modal, prize);
2873
+ this.trackEvent(widgetId, "submit", {
2874
+ type: "spin_wheel",
2875
+ prize_label: prize.prize_label,
2876
+ has_email: !!email
2877
+ });
2878
+ } catch (error) {
2879
+ this.log(`Error submitting spin wheel: ${error}`, true);
2880
+ errorMessage.textContent = "Failed to submit. Please try again.";
2881
+ errorMessage.style.display = "block";
2882
+ submitButton.disabled = false;
2883
+ submitButton.textContent = "Spin the Wheel!";
2884
+ wheelContainer.style.animation = "";
2885
+ }
2886
+ }, 2e3);
2887
+ });
2888
+ modal.appendChild(wheelContainer);
2889
+ modal.appendChild(form);
2890
+ overlay.appendChild(modal);
2891
+ this.addAnimationStyles();
2892
+ document.body.appendChild(overlay);
2893
+ this.trackEvent(widgetId, "show", { type: "spin_wheel" });
2894
+ }
2895
+ validatePhone(phone) {
2896
+ const e164Regex = /^\+?[1-9]\d{1,14}$/;
2897
+ return e164Regex.test(phone.replace(/[\s()-]/g, ""));
2898
+ }
2899
+ validateEmail(email) {
2900
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2901
+ return emailRegex.test(email);
2902
+ }
2903
+ async submitSpinWheel(data) {
2904
+ var _a, _b, _c, _d, _e;
2905
+ const url = `${this.apiHost}/v1/widgets/spin-wheel/submit`;
2906
+ const headers = {
2907
+ "X-Aegis-Write-Key": this.writeKey,
2908
+ "Content-Type": "application/json"
2909
+ };
2910
+ if (this.organizationId) headers["X-Organization-ID"] = this.organizationId;
2911
+ const geoRegion = this.detectGeoRegion();
2912
+ const deviceType = this.getDeviceType();
2913
+ const utmParams = this.getUTMParams();
2914
+ const body = {
2915
+ phone: data.phone.replace(/[\s()-]/g, ""),
2916
+ email: data.email || void 0,
2917
+ name: data.name || void 0,
2918
+ cart_id: ((_a = data.cart) == null ? void 0 : _a.cart_id) || `web_${Date.now()}`,
2919
+ cart_token: (_b = data.cart) == null ? void 0 : _b.cart_id,
2920
+ cart_total: ((_c = data.cart) == null ? void 0 : _c.cart_total) || 0,
2921
+ cart_currency: ((_d = data.cart) == null ? void 0 : _d.cart_currency) || "USD",
2922
+ cart_items: ((_e = data.cart) == null ? void 0 : _e.cart_items) || [],
2923
+ cart_url: window.location.href,
2924
+ platform: "web",
2925
+ geo_region: geoRegion,
2926
+ device_type: deviceType,
2927
+ session_id: this.getSessionId() || void 0,
2928
+ anonymous_id: this.getAnonymousId() || void 0,
2929
+ utm_source: utmParams.utm_source,
2930
+ utm_medium: utmParams.utm_medium,
2931
+ utm_campaign: utmParams.utm_campaign
2932
+ };
2933
+ const response = await fetch(url, {
2934
+ method: "POST",
2935
+ headers,
2936
+ body: JSON.stringify(body)
2937
+ });
2938
+ if (!response.ok) {
2939
+ const errorText = await response.text();
2940
+ throw new Error(`Failed to submit spin wheel: ${response.status} - ${errorText}`);
2941
+ }
2942
+ return await response.json();
2943
+ }
2944
+ showSpinWheelPrize(modal, prize) {
2945
+ modal.innerHTML = "";
2946
+ const resultContainer = document.createElement("div");
2947
+ resultContainer.style.cssText = "text-align: center; padding: 40px 20px;";
2948
+ const emoji = document.createElement("div");
2949
+ emoji.textContent = "🎉";
2950
+ emoji.style.cssText = "font-size: 60px; margin-bottom: 20px;";
2951
+ resultContainer.appendChild(emoji);
2952
+ const title = document.createElement("h2");
2953
+ title.textContent = "Congratulations!";
2954
+ title.style.cssText = "margin: 0 0 12px 0; font-size: 28px; font-weight: 700; color: #1a73e8;";
2955
+ resultContainer.appendChild(title);
2956
+ const prizeLabel = document.createElement("p");
2957
+ prizeLabel.textContent = prize.prize_label;
2958
+ prizeLabel.style.cssText = "margin: 0 0 20px 0; font-size: 20px; font-weight: 600; color: #333;";
2959
+ resultContainer.appendChild(prizeLabel);
2960
+ if (prize.coupon_code) {
2961
+ const couponContainer = document.createElement("div");
2962
+ couponContainer.style.cssText = `
2963
+ background: #f5f5f5;
2964
+ padding: 16px;
2965
+ border-radius: 8px;
2966
+ margin-bottom: 20px;
2967
+ border: 2px dashed #1a73e8;
2968
+ `;
2969
+ const couponLabel = document.createElement("div");
2970
+ couponLabel.textContent = "Your coupon code:";
2971
+ couponLabel.style.cssText = "font-size: 12px; color: #666; margin-bottom: 8px;";
2972
+ couponContainer.appendChild(couponLabel);
2973
+ const couponCode = document.createElement("div");
2974
+ couponCode.textContent = prize.coupon_code;
2975
+ couponCode.style.cssText = "font-size: 24px; font-weight: 700; color: #1a73e8; letter-spacing: 2px;";
2976
+ couponContainer.appendChild(couponCode);
2977
+ resultContainer.appendChild(couponContainer);
2978
+ }
2979
+ const message = document.createElement("p");
2980
+ message.textContent = "Check your WhatsApp/SMS for your discount code and cart link!";
2981
+ message.style.cssText = "margin: 0 0 20px 0; font-size: 14px; color: #666;";
2982
+ resultContainer.appendChild(message);
2983
+ const closeButton = document.createElement("button");
2984
+ closeButton.textContent = "Close";
2985
+ closeButton.style.cssText = `
2986
+ background: #1a73e8;
2987
+ color: white;
2988
+ border: none;
2989
+ padding: 12px 32px;
2990
+ border-radius: 8px;
2991
+ font-weight: 600;
2992
+ font-size: 14px;
2993
+ cursor: pointer;
2994
+ `;
2995
+ closeButton.addEventListener("click", () => {
2996
+ const overlay = modal.parentElement;
2997
+ if (overlay && document.body.contains(overlay)) {
2998
+ document.body.removeChild(overlay);
2999
+ }
3000
+ });
3001
+ resultContainer.appendChild(closeButton);
3002
+ modal.appendChild(resultContainer);
3003
+ }
3004
+ detectGeoRegion() {
3005
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
3006
+ if (timezone.includes("America")) return "north_america";
3007
+ if (timezone.includes("Europe")) return "europe";
3008
+ if (timezone.includes("Asia/Kolkata") || timezone.includes("Asia/Calcutta")) return "india";
3009
+ if (timezone.includes("Asia/Singapore") || timezone.includes("Asia/Bangkok") || timezone.includes("Asia/Jakarta")) return "southeast_asia";
3010
+ if (timezone.includes("Asia/Dubai") || timezone.includes("Asia/Riyadh")) return "middle_east";
3011
+ if (timezone.includes("America") && (timezone.includes("Sao_Paulo") || timezone.includes("Buenos_Aires"))) return "latin_america";
3012
+ if (timezone.includes("Australia") || timezone.includes("Pacific/Auckland")) return "oceania";
3013
+ return "north_america";
3014
+ }
3015
+ getUTMParams() {
3016
+ const params = new URLSearchParams(window.location.search);
3017
+ return {
3018
+ utm_source: params.get("utm_source") || void 0,
3019
+ utm_medium: params.get("utm_medium") || void 0,
3020
+ utm_campaign: params.get("utm_campaign") || void 0
3021
+ };
3022
+ }
3023
+ getSessionId() {
3024
+ return sessionStorage.getItem("aegis_session_id");
3025
+ }
3026
+ getAnonymousId() {
3027
+ return localStorage.getItem("aegis_anonymous_id");
3028
+ }
3029
+ renderScratchCard(widget) {
3030
+ const overlay = document.createElement("div");
3031
+ overlay.className = "aegis-gamification-overlay";
3032
+ overlay.setAttribute("data-widget-id", widget.widget_id);
3033
+ overlay.style.cssText = `
3034
+ position: fixed;
3035
+ top: 0;
3036
+ left: 0;
3037
+ right: 0;
3038
+ bottom: 0;
3039
+ background: rgba(0, 0, 0, 0.7);
3040
+ z-index: 1000000;
3041
+ display: flex;
3042
+ align-items: center;
3043
+ justify-content: center;
3044
+ animation: aegisFadeIn 0.3s ease-out;
3045
+ `;
3046
+ const modal = document.createElement("div");
3047
+ modal.className = "aegis-scratch-card-modal";
3048
+ modal.style.cssText = `
3049
+ background: white;
3050
+ border-radius: 16px;
3051
+ padding: 32px;
3052
+ max-width: 500px;
3053
+ width: 90%;
3054
+ box-shadow: 0 10px 40px rgba(0,0,0,0.3);
3055
+ text-align: center;
3056
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
3057
+ animation: aegisScaleIn 0.3s ease-out;
3058
+ `;
3059
+ const title = document.createElement("h2");
3060
+ title.textContent = widget.config.title || "Scratch & Win!";
3061
+ title.style.cssText = "margin: 0 0 16px 0; font-size: 24px; font-weight: 700; color: #1a1a1a;";
3062
+ modal.appendChild(title);
3063
+ const description = document.createElement("p");
3064
+ description.textContent = widget.config.description || "Scratch to reveal your prize!";
3065
+ description.style.cssText = "margin: 0 0 24px 0; font-size: 14px; color: #666;";
3066
+ modal.appendChild(description);
3067
+ const canvas = document.createElement("canvas");
3068
+ canvas.width = 300;
3069
+ canvas.height = 200;
3070
+ canvas.style.cssText = `
3071
+ border-radius: 8px;
3072
+ cursor: pointer;
3073
+ margin: 0 auto 24px;
3074
+ display: block;
3075
+ box-shadow: 0 4px 12px rgba(0,0,0,0.1);
3076
+ `;
3077
+ modal.appendChild(canvas);
3078
+ const ctx = canvas.getContext("2d");
3079
+ if (ctx) {
3080
+ ctx.fillStyle = "#c0c0c0";
3081
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
3082
+ ctx.fillStyle = "#888";
3083
+ ctx.font = "20px Arial";
3084
+ ctx.textAlign = "center";
3085
+ ctx.fillText("Scratch here!", canvas.width / 2, canvas.height / 2);
3086
+ let isScratching = false;
3087
+ const scratch = (x, y) => {
3088
+ ctx.globalCompositeOperation = "destination-out";
3089
+ ctx.beginPath();
3090
+ ctx.arc(x, y, 20, 0, Math.PI * 2);
3091
+ ctx.fill();
3092
+ };
3093
+ canvas.addEventListener("mousedown", () => {
3094
+ isScratching = true;
3095
+ });
3096
+ canvas.addEventListener("mouseup", () => {
3097
+ isScratching = false;
3098
+ });
3099
+ canvas.addEventListener("mousemove", (e) => {
3100
+ if (isScratching) {
3101
+ const rect = canvas.getBoundingClientRect();
3102
+ scratch(e.clientX - rect.left, e.clientY - rect.top);
3103
+ }
3104
+ });
3105
+ }
3106
+ const scratchButton = document.createElement("button");
3107
+ scratchButton.textContent = "REVEAL PRIZE";
3108
+ scratchButton.style.cssText = `
3109
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
3110
+ color: white;
3111
+ border: none;
3112
+ padding: 16px 48px;
3113
+ border-radius: 8px;
3114
+ font-weight: 700;
3115
+ font-size: 16px;
3116
+ cursor: pointer;
3117
+ `;
3118
+ scratchButton.addEventListener("click", async () => {
3119
+ scratchButton.disabled = true;
3120
+ scratchButton.textContent = "REVEALING...";
3121
+ try {
3122
+ const prize = await this.generatePrize(widget.config.config_id);
3123
+ this.showPrizeResult(modal, prize);
3124
+ } catch (error) {
3125
+ this.log(`Error generating prize: ${error}`, true);
3126
+ scratchButton.disabled = false;
3127
+ scratchButton.textContent = "TRY AGAIN";
3128
+ }
3129
+ });
3130
+ modal.appendChild(scratchButton);
3131
+ overlay.appendChild(modal);
3132
+ this.addAnimationStyles();
3133
+ document.body.appendChild(overlay);
3134
+ }
3135
+ renderToast(widget) {
3136
+ const { message, icon, duration } = widget.config;
3137
+ const position = widget.position || "bottom_left";
3138
+ const toast = document.createElement("div");
3139
+ toast.className = "aegis-toast";
3140
+ toast.setAttribute("data-widget-id", widget.widget_id);
3141
+ const positionStyles = {
3142
+ bottom_left: "bottom: 20px; left: 20px;",
3143
+ bottom_right: "bottom: 20px; right: 20px;",
3144
+ top_left: "top: 20px; left: 20px;",
3145
+ top_right: "top: 20px; right: 20px;"
3146
+ };
3147
+ toast.style.cssText = `
3148
+ position: fixed;
3149
+ ${positionStyles[position] || positionStyles.bottom_left}
3150
+ background: white;
3151
+ padding: 16px 20px;
3152
+ border-radius: 8px;
3153
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
3154
+ z-index: 999998;
3155
+ display: flex;
3156
+ align-items: center;
3157
+ gap: 12px;
3158
+ max-width: 350px;
3159
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
3160
+ animation: aegisToastIn 0.3s ease-out;
3161
+ `;
3162
+ if (icon) {
3163
+ const iconEl = document.createElement("div");
3164
+ iconEl.textContent = icon;
3165
+ iconEl.style.cssText = "font-size: 20px;";
3166
+ toast.appendChild(iconEl);
3167
+ }
3168
+ const messageEl = document.createElement("div");
3169
+ messageEl.textContent = message || "Someone just made a purchase!";
3170
+ messageEl.style.cssText = "flex: 1; font-size: 13px; color: #333;";
3171
+ toast.appendChild(messageEl);
3172
+ this.addAnimationStyles();
3173
+ document.body.appendChild(toast);
3174
+ setTimeout(() => {
3175
+ toast.style.animation = "aegisToastOut 0.3s ease-out";
3176
+ setTimeout(() => {
3177
+ if (document.body.contains(toast)) {
3178
+ document.body.removeChild(toast);
3179
+ }
3180
+ this.renderedWidgets.delete(widget.widget_id);
3181
+ }, 300);
3182
+ }, duration || 5e3);
3183
+ }
3184
+ renderFeedbackForm(widget) {
3185
+ const overlay = document.createElement("div");
3186
+ overlay.className = "aegis-feedback-overlay";
3187
+ overlay.setAttribute("data-widget-id", widget.widget_id);
3188
+ overlay.style.cssText = `
3189
+ position: fixed;
3190
+ top: 0;
3191
+ right: 0;
3192
+ bottom: 0;
3193
+ width: 400px;
3194
+ background: white;
3195
+ box-shadow: -4px 0 20px rgba(0,0,0,0.15);
3196
+ z-index: 1000000;
3197
+ padding: 32px;
3198
+ overflow-y: auto;
3199
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
3200
+ animation: aegisSlideInRight 0.3s ease-out;
3201
+ `;
3202
+ const title = document.createElement("h2");
3203
+ title.textContent = widget.config.title || "We value your feedback!";
3204
+ title.style.cssText = "margin: 0 0 8px 0; font-size: 20px; font-weight: 600; color: #1a1a1a;";
3205
+ overlay.appendChild(title);
3206
+ const description = document.createElement("p");
3207
+ description.textContent = widget.config.description || "Help us improve your experience.";
3208
+ description.style.cssText = "margin: 0 0 24px 0; font-size: 14px; color: #666;";
3209
+ overlay.appendChild(description);
3210
+ const form = document.createElement("form");
3211
+ const label = document.createElement("label");
3212
+ label.textContent = "How likely are you to recommend us? (0-10)";
3213
+ label.style.cssText = "display: block; font-size: 14px; font-weight: 600; margin-bottom: 8px; color: #333;";
3214
+ form.appendChild(label);
3215
+ const input = document.createElement("input");
3216
+ input.type = "number";
3217
+ input.min = "0";
3218
+ input.max = "10";
3219
+ input.style.cssText = `
3220
+ width: 100%;
3221
+ padding: 12px;
3222
+ border: 1px solid #ddd;
3223
+ border-radius: 6px;
3224
+ font-size: 14px;
3225
+ margin-bottom: 16px;
3226
+ `;
3227
+ form.appendChild(input);
3228
+ const textareaLabel = document.createElement("label");
3229
+ textareaLabel.textContent = "Tell us more (optional)";
3230
+ textareaLabel.style.cssText = "display: block; font-size: 14px; font-weight: 600; margin-bottom: 8px; color: #333;";
3231
+ form.appendChild(textareaLabel);
3232
+ const textarea = document.createElement("textarea");
3233
+ textarea.rows = 4;
3234
+ textarea.style.cssText = `
3235
+ width: 100%;
3236
+ padding: 12px;
3237
+ border: 1px solid #ddd;
3238
+ border-radius: 6px;
3239
+ font-size: 14px;
3240
+ resize: vertical;
3241
+ margin-bottom: 16px;
3242
+ font-family: inherit;
3243
+ `;
3244
+ form.appendChild(textarea);
3245
+ const submitButton = document.createElement("button");
3246
+ submitButton.type = "submit";
3247
+ submitButton.textContent = "Submit Feedback";
3248
+ submitButton.style.cssText = `
3249
+ width: 100%;
3250
+ background: #1a73e8;
3251
+ color: white;
3252
+ border: none;
3253
+ padding: 12px;
3254
+ border-radius: 6px;
3255
+ font-weight: 600;
3256
+ font-size: 14px;
3257
+ cursor: pointer;
3258
+ `;
3259
+ form.appendChild(submitButton);
3260
+ form.addEventListener("submit", async (e) => {
3261
+ e.preventDefault();
3262
+ submitButton.disabled = true;
3263
+ submitButton.textContent = "Submitting...";
3264
+ try {
3265
+ await this.submitFeedback(widget.widget_id, {
3266
+ score: parseInt(input.value),
3267
+ comment: textarea.value
3268
+ });
3269
+ overlay.innerHTML = '<div style="text-align: center; padding: 60px 20px;"><h3 style="margin: 0 0 12px 0; color: #1a73e8;">Thank you!</h3><p style="margin: 0; color: #666;">Your feedback helps us improve.</p></div>';
3270
+ setTimeout(() => {
3271
+ document.body.removeChild(overlay);
3272
+ }, 2e3);
3273
+ } catch (error) {
3274
+ this.log(`Error submitting feedback: ${error}`, true);
3275
+ submitButton.disabled = false;
3276
+ submitButton.textContent = "Submit Feedback";
3277
+ }
3278
+ });
3279
+ overlay.appendChild(form);
3280
+ const closeButton = document.createElement("button");
3281
+ closeButton.textContent = "×";
3282
+ closeButton.setAttribute("aria-label", "Close");
3283
+ closeButton.style.cssText = `
3284
+ position: absolute;
3285
+ top: 16px;
3286
+ right: 16px;
3287
+ background: transparent;
3288
+ border: none;
3289
+ font-size: 28px;
3290
+ cursor: pointer;
3291
+ color: #999;
3292
+ `;
3293
+ closeButton.addEventListener("click", () => {
3294
+ this.trackEvent(widget.widget_id, "dismiss");
3295
+ document.body.removeChild(overlay);
3296
+ this.renderedWidgets.delete(widget.widget_id);
3297
+ });
3298
+ overlay.appendChild(closeButton);
3299
+ this.addAnimationStyles();
3300
+ document.body.appendChild(overlay);
3301
+ }
3302
+ renderExitIntentPopup(widget) {
3303
+ const { title, description, cta_text, cta_url, image_url } = widget.config;
3304
+ const overlay = document.createElement("div");
3305
+ overlay.className = "aegis-exit-popup-overlay";
3306
+ overlay.setAttribute("data-widget-id", widget.widget_id);
3307
+ overlay.style.cssText = `
3308
+ position: fixed;
3309
+ top: 0;
3310
+ left: 0;
3311
+ right: 0;
3312
+ bottom: 0;
3313
+ background: rgba(0, 0, 0, 0.7);
3314
+ z-index: 1000000;
3315
+ display: flex;
3316
+ align-items: center;
3317
+ justify-content: center;
3318
+ animation: aegisFadeIn 0.3s ease-out;
3319
+ `;
3320
+ const modal = document.createElement("div");
3321
+ modal.className = "aegis-exit-popup-modal";
3322
+ modal.style.cssText = `
3323
+ background: white;
3324
+ border-radius: 16px;
3325
+ max-width: 600px;
3326
+ width: 90%;
3327
+ overflow: hidden;
3328
+ box-shadow: 0 10px 40px rgba(0,0,0,0.3);
3329
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
3330
+ animation: aegisScaleIn 0.3s ease-out;
3331
+ `;
3332
+ if (image_url) {
3333
+ const img = document.createElement("img");
3334
+ const safeUrl = this.sanitizeUrl(image_url);
3335
+ if (safeUrl) {
3336
+ img.src = safeUrl;
3337
+ img.alt = "";
3338
+ img.style.cssText = "width: 100%; height: 250px; object-fit: cover;";
3339
+ modal.appendChild(img);
3340
+ }
3341
+ }
3342
+ const content = document.createElement("div");
3343
+ content.style.cssText = "padding: 32px;";
3344
+ const titleEl = document.createElement("h2");
3345
+ titleEl.textContent = title || "Wait! Don't leave yet!";
3346
+ titleEl.style.cssText = "margin: 0 0 16px 0; font-size: 28px; font-weight: 700; color: #1a1a1a;";
3347
+ content.appendChild(titleEl);
3348
+ const descEl = document.createElement("p");
3349
+ descEl.textContent = description || "Get 10% off your first order!";
3350
+ descEl.style.cssText = "margin: 0 0 24px 0; font-size: 16px; color: #666; line-height: 1.5;";
3351
+ content.appendChild(descEl);
3352
+ if (cta_text && cta_url) {
3353
+ const ctaButton = document.createElement("button");
3354
+ ctaButton.textContent = cta_text;
3355
+ ctaButton.style.cssText = `
3356
+ background: #1a73e8;
3357
+ color: white;
3358
+ border: none;
3359
+ padding: 16px 32px;
3360
+ border-radius: 8px;
3361
+ font-weight: 600;
3362
+ font-size: 16px;
3363
+ cursor: pointer;
3364
+ width: 100%;
3365
+ `;
3366
+ ctaButton.addEventListener("click", () => {
3367
+ this.trackEvent(widget.widget_id, "click");
3368
+ const safeUrl = this.sanitizeUrl(cta_url);
3369
+ if (safeUrl) {
3370
+ window.location.href = safeUrl;
3371
+ }
3372
+ });
3373
+ content.appendChild(ctaButton);
3374
+ }
3375
+ modal.appendChild(content);
3376
+ const closeButton = document.createElement("button");
3377
+ closeButton.textContent = "×";
3378
+ closeButton.setAttribute("aria-label", "Close");
3379
+ closeButton.style.cssText = `
3380
+ position: absolute;
3381
+ top: 16px;
3382
+ right: 16px;
3383
+ background: white;
3384
+ border: none;
3385
+ font-size: 28px;
3386
+ cursor: pointer;
3387
+ color: #666;
3388
+ width: 40px;
3389
+ height: 40px;
3390
+ border-radius: 50%;
3391
+ box-shadow: 0 2px 8px rgba(0,0,0,0.2);
3392
+ `;
3393
+ closeButton.addEventListener("click", () => {
3394
+ this.trackEvent(widget.widget_id, "dismiss");
3395
+ document.body.removeChild(overlay);
3396
+ this.renderedWidgets.delete(widget.widget_id);
3397
+ });
3398
+ modal.style.position = "relative";
3399
+ modal.appendChild(closeButton);
3400
+ overlay.appendChild(modal);
3401
+ this.addAnimationStyles();
3402
+ document.body.appendChild(overlay);
3403
+ }
3404
+ async generatePrize(configId) {
3405
+ const url = `${this.apiHost}/v1/widgets/gamification/generate-prize`;
3406
+ const headers = {
3407
+ "X-Aegis-Write-Key": this.writeKey,
3408
+ "Content-Type": "application/json"
3409
+ };
3410
+ if (this.userId) headers["X-User-ID"] = this.userId;
3411
+ if (this.contactId) headers["X-Contact-ID"] = this.contactId;
3412
+ if (this.organizationId) headers["X-Organization-ID"] = this.organizationId;
3413
+ const response = await fetch(url, {
3414
+ method: "POST",
3415
+ headers,
3416
+ body: JSON.stringify({ config_id: configId })
3417
+ });
3418
+ if (!response.ok) {
3419
+ throw new Error(`Failed to generate prize: ${response.status}`);
3420
+ }
3421
+ return await response.json();
3422
+ }
3423
+ showPrizeResult(modal, prize) {
3424
+ modal.innerHTML = "";
3425
+ const resultContainer = document.createElement("div");
3426
+ resultContainer.style.cssText = "text-align: center; padding: 40px 20px;";
3427
+ const emoji = document.createElement("div");
3428
+ emoji.textContent = "🎉";
3429
+ emoji.style.cssText = "font-size: 60px; margin-bottom: 20px;";
3430
+ resultContainer.appendChild(emoji);
3431
+ const title = document.createElement("h2");
3432
+ title.textContent = "Congratulations!";
3433
+ title.style.cssText = "margin: 0 0 12px 0; font-size: 28px; font-weight: 700; color: #1a73e8;";
3434
+ resultContainer.appendChild(title);
3435
+ const prizeLabel = document.createElement("p");
3436
+ prizeLabel.textContent = prize.prize_label;
3437
+ prizeLabel.style.cssText = "margin: 0 0 20px 0; font-size: 20px; font-weight: 600; color: #333;";
3438
+ resultContainer.appendChild(prizeLabel);
3439
+ if (prize.coupon_code) {
3440
+ const couponContainer = document.createElement("div");
3441
+ couponContainer.style.cssText = `
3442
+ background: #f5f5f5;
3443
+ padding: 16px;
3444
+ border-radius: 8px;
3445
+ margin-bottom: 20px;
3446
+ border: 2px dashed #1a73e8;
3447
+ `;
3448
+ const couponLabel = document.createElement("div");
3449
+ couponLabel.textContent = "Your coupon code:";
3450
+ couponLabel.style.cssText = "font-size: 12px; color: #666; margin-bottom: 8px;";
3451
+ couponContainer.appendChild(couponLabel);
3452
+ const couponCode = document.createElement("div");
3453
+ couponCode.textContent = prize.coupon_code;
3454
+ couponCode.style.cssText = "font-size: 24px; font-weight: 700; color: #1a73e8; letter-spacing: 2px;";
3455
+ couponContainer.appendChild(couponCode);
3456
+ resultContainer.appendChild(couponContainer);
3457
+ }
3458
+ const closeButton = document.createElement("button");
3459
+ closeButton.textContent = "Close";
3460
+ closeButton.style.cssText = `
3461
+ background: #1a73e8;
3462
+ color: white;
3463
+ border: none;
3464
+ padding: 12px 32px;
3465
+ border-radius: 8px;
3466
+ font-weight: 600;
3467
+ font-size: 14px;
3468
+ cursor: pointer;
3469
+ `;
3470
+ closeButton.addEventListener("click", () => {
3471
+ const overlay = modal.parentElement;
3472
+ if (overlay && document.body.contains(overlay)) {
3473
+ document.body.removeChild(overlay);
3474
+ }
3475
+ });
3476
+ resultContainer.appendChild(closeButton);
3477
+ modal.appendChild(resultContainer);
3478
+ }
3479
+ async submitFeedback(widgetId, data) {
3480
+ const url = `${this.apiHost}/v1/widgets/track-event`;
3481
+ const headers = {
3482
+ "X-Aegis-Write-Key": this.writeKey,
3483
+ "Content-Type": "application/json"
3484
+ };
3485
+ if (this.userId) headers["X-User-ID"] = this.userId;
3486
+ if (this.contactId) headers["X-Contact-ID"] = this.contactId;
3487
+ if (this.organizationId) headers["X-Organization-ID"] = this.organizationId;
3488
+ const response = await fetch(url, {
3489
+ method: "POST",
3490
+ headers,
3491
+ body: JSON.stringify({
3492
+ widget_id: widgetId,
3493
+ event_type: "submit",
3494
+ event_data: data
3495
+ })
3496
+ });
3497
+ if (!response.ok) {
3498
+ throw new Error(`Failed to submit feedback: ${response.status}`);
3499
+ }
3500
+ }
3501
+ async trackEvent(widgetId, eventType, metadata) {
3502
+ try {
3503
+ const url = `${this.apiHost}/v1/widgets/track-event`;
3504
+ const headers = {
3505
+ "X-Aegis-Write-Key": this.writeKey,
3506
+ "Content-Type": "application/json"
3507
+ };
3508
+ if (this.userId) headers["X-User-ID"] = this.userId;
3509
+ if (this.contactId) headers["X-Contact-ID"] = this.contactId;
3510
+ if (this.organizationId) headers["X-Organization-ID"] = this.organizationId;
3511
+ await fetch(url, {
3512
+ method: "POST",
3513
+ headers,
3514
+ body: JSON.stringify({
3515
+ widget_id: widgetId,
3516
+ event_type: eventType,
3517
+ event_data: metadata || {}
3518
+ })
3519
+ });
3520
+ } catch (error) {
3521
+ this.log(`Error tracking widget event: ${error}`, true);
3522
+ }
3523
+ }
3524
+ sanitizeUrl(url) {
3525
+ if (!url) return null;
3526
+ try {
3527
+ const parsed = new URL(url, window.location.href);
3528
+ if (parsed.protocol === "javascript:" || parsed.protocol === "data:") {
3529
+ this.log(`Blocked dangerous URL: ${url}`, true);
3530
+ return null;
3531
+ }
3532
+ return parsed.href;
3533
+ } catch {
3534
+ this.log(`Invalid URL: ${url}`, true);
3535
+ return null;
3536
+ }
3537
+ }
3538
+ sanitizeColor(color) {
3539
+ if (/^#[0-9A-F]{3,8}$/i.test(color)) {
3540
+ return color;
3541
+ }
3542
+ if (/^rgb\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*\)$/i.test(color)) {
3543
+ return color;
3544
+ }
3545
+ if (/^rgba\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*[\d.]+\s*\)$/i.test(color)) {
3546
+ return color;
3547
+ }
3548
+ return "#1a73e8";
3549
+ }
3550
+ getDeviceType() {
3551
+ const ua = navigator.userAgent;
3552
+ if (/(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i.test(ua)) {
3553
+ return "tablet";
3554
+ }
3555
+ if (/Mobile|Android|iP(hone|od)|IEMobile|BlackBerry|Kindle|Silk-Accelerated|(hpw|web)OS|Opera M(obi|ini)/.test(ua)) {
3556
+ return "mobile";
3557
+ }
3558
+ return "desktop";
3559
+ }
3560
+ isMobileDevice() {
3561
+ const deviceType = this.getDeviceType();
3562
+ return deviceType === "mobile" || deviceType === "tablet";
3563
+ }
3564
+ addAnimationStyles() {
3565
+ if (document.getElementById("aegis-widget-animations")) {
3566
+ return;
3567
+ }
3568
+ const style = document.createElement("style");
3569
+ style.id = "aegis-widget-animations";
3570
+ style.textContent = `
3571
+ @keyframes aegisFadeIn {
3572
+ from { opacity: 0; }
3573
+ to { opacity: 1; }
3574
+ }
3575
+
3576
+ @keyframes aegisScaleIn {
3577
+ from { transform: scale(0.8); opacity: 0; }
3578
+ to { transform: scale(1); opacity: 1; }
3579
+ }
3580
+
3581
+ @keyframes aegisBubbleIn {
3582
+ from { transform: translateY(20px); opacity: 0; }
3583
+ to { transform: translateY(0); opacity: 1; }
3584
+ }
3585
+
3586
+ @keyframes aegisToastIn {
3587
+ from { transform: translateX(-100%); opacity: 0; }
3588
+ to { transform: translateX(0); opacity: 1; }
3589
+ }
3590
+
3591
+ @keyframes aegisToastOut {
3592
+ from { transform: translateX(0); opacity: 1; }
3593
+ to { transform: translateX(-100%); opacity: 0; }
3594
+ }
3595
+
3596
+ @keyframes aegisSlideInRight {
3597
+ from { transform: translateX(100%); }
3598
+ to { transform: translateX(0); }
3599
+ }
3600
+
3601
+ @keyframes aegisSpin {
3602
+ from { transform: rotate(0deg); }
3603
+ to { transform: rotate(1440deg); }
3604
+ }
3605
+ `;
3606
+ document.head.appendChild(style);
3607
+ }
3608
+ renderCartRecoveryPopup(widgetId, config) {
3609
+ const overlay = document.createElement("div");
3610
+ overlay.className = "aegis-exit-popup-overlay";
3611
+ overlay.setAttribute("data-widget-id", widgetId);
3612
+ overlay.style.cssText = `
3613
+ position: fixed;
3614
+ top: 0;
3615
+ left: 0;
3616
+ right: 0;
3617
+ bottom: 0;
3618
+ background: rgba(0, 0, 0, 0.7);
3619
+ z-index: 1000000;
3620
+ display: flex;
3621
+ align-items: center;
3622
+ justify-content: center;
3623
+ animation: aegisFadeIn 0.3s ease-out;
3624
+ `;
3625
+ const modal = document.createElement("div");
3626
+ modal.className = "aegis-cart-recovery-modal";
3627
+ modal.style.cssText = `
3628
+ background: ${this.sanitizeColor(config.background_color || "#FFFFFF")};
3629
+ color: ${this.sanitizeColor(config.text_color || "#333333")};
3630
+ border-radius: 16px;
3631
+ max-width: 600px;
3632
+ width: 90%;
3633
+ padding: 40px;
3634
+ box-shadow: 0 10px 40px rgba(0,0,0,0.3);
3635
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
3636
+ animation: aegisScaleIn 0.3s ease-out;
3637
+ position: relative;
3638
+ `;
3639
+ const title = document.createElement("h2");
3640
+ const titleText = this.interpolateCartVariables(config.title || "Complete Your Order");
3641
+ title.textContent = titleText;
3642
+ title.style.cssText = `margin: 0 0 16px 0; font-size: 28px; font-weight: 700; color: ${this.sanitizeColor(config.accent_color || "#FF6B00")};`;
3643
+ modal.appendChild(title);
3644
+ const message = document.createElement("p");
3645
+ const messageText = this.interpolateCartVariables(config.message || "Your cart is waiting for you!");
3646
+ message.textContent = messageText;
3647
+ message.style.cssText = "margin: 0 0 24px 0; font-size: 16px; line-height: 1.5;";
3648
+ modal.appendChild(message);
3649
+ if (config.show_cart_items && this.cartData) {
3650
+ const cartItems = document.createElement("div");
3651
+ cartItems.style.cssText = "margin: 0 0 24px 0; padding: 16px; background: rgba(0,0,0,0.05); border-radius: 8px;";
3652
+ const cartTitle = document.createElement("div");
3653
+ cartTitle.textContent = `${this.cartData.cart_items.length} items in your cart`;
3654
+ cartTitle.style.cssText = "font-weight: 600; margin-bottom: 12px;";
3655
+ cartItems.appendChild(cartTitle);
3656
+ this.cartData.cart_items.slice(0, 3).forEach((item) => {
3657
+ const itemDiv = document.createElement("div");
3658
+ itemDiv.textContent = `${item.quantity}× ${item.product_name || item.product_id}`;
3659
+ itemDiv.style.cssText = "font-size: 14px; margin-bottom: 4px;";
3660
+ cartItems.appendChild(itemDiv);
3661
+ });
3662
+ modal.appendChild(cartItems);
3663
+ }
3664
+ if (config.discount_code) {
3665
+ const discountBox = document.createElement("div");
3666
+ discountBox.style.cssText = `
3667
+ margin: 0 0 24px 0;
3668
+ padding: 16px;
3669
+ background: ${this.sanitizeColor(config.accent_color || "#FF6B00")};
3670
+ color: white;
3671
+ border-radius: 8px;
3672
+ text-align: center;
3673
+ font-weight: 700;
3674
+ font-size: 18px;
3675
+ `;
3676
+ discountBox.textContent = `Use code: ${config.discount_code} for ${config.discount_percentage || 10}% off!`;
3677
+ modal.appendChild(discountBox);
3678
+ }
3679
+ if (config.show_timer && config.timer_minutes) {
3680
+ const timer = document.createElement("div");
3681
+ timer.style.cssText = "margin: 0 0 24px 0; text-align: center; font-size: 14px; color: #999;";
3682
+ timer.textContent = `⏰ Offer expires in ${config.timer_minutes} minutes`;
3683
+ modal.appendChild(timer);
3684
+ }
3685
+ const ctaButton = document.createElement("button");
3686
+ ctaButton.textContent = config.cta_text || "Complete Checkout";
3687
+ ctaButton.style.cssText = `
3688
+ background: ${this.sanitizeColor(config.accent_color || "#FF6B00")};
3689
+ color: white;
3690
+ border: none;
3691
+ padding: 16px 32px;
3692
+ border-radius: 8px;
3693
+ font-weight: 700;
3694
+ font-size: 16px;
3695
+ cursor: pointer;
3696
+ width: 100%;
3697
+ `;
3698
+ ctaButton.addEventListener("click", () => {
3699
+ this.trackEvent(widgetId, "click", { type: "cart_recovery" });
3700
+ const checkoutUrl = this.sanitizeUrl(config.cta_url || "/checkout");
3701
+ if (checkoutUrl) {
3702
+ window.location.href = checkoutUrl;
3703
+ }
3704
+ });
3705
+ modal.appendChild(ctaButton);
3706
+ const closeButton = document.createElement("button");
3707
+ closeButton.textContent = "×";
3708
+ closeButton.setAttribute("aria-label", "Close");
3709
+ closeButton.style.cssText = `
3710
+ position: absolute;
3711
+ top: 16px;
3712
+ right: 16px;
3713
+ background: transparent;
3714
+ border: none;
3715
+ font-size: 28px;
3716
+ cursor: pointer;
3717
+ color: #999;
3718
+ `;
3719
+ closeButton.addEventListener("click", () => {
3720
+ this.trackEvent(widgetId, "dismiss", { type: "cart_recovery" });
3721
+ document.body.removeChild(overlay);
3722
+ this.renderedWidgets.delete(widgetId);
3723
+ });
3724
+ modal.appendChild(closeButton);
3725
+ overlay.appendChild(modal);
3726
+ this.addAnimationStyles();
3727
+ document.body.appendChild(overlay);
3728
+ this.trackEvent(widgetId, "show", { type: "cart_recovery", tier: config.tier });
3729
+ }
3730
+ renderLeadGenPopup(widgetId, config) {
3731
+ const overlay = document.createElement("div");
3732
+ overlay.className = "aegis-exit-popup-overlay";
3733
+ overlay.setAttribute("data-widget-id", widgetId);
3734
+ overlay.style.cssText = `
3735
+ position: fixed;
3736
+ top: 0;
3737
+ left: 0;
3738
+ right: 0;
3739
+ bottom: 0;
3740
+ background: rgba(0, 0, 0, 0.7);
3741
+ z-index: 1000000;
3742
+ display: flex;
3743
+ align-items: center;
3744
+ justify-content: center;
3745
+ animation: aegisFadeIn 0.3s ease-out;
3746
+ `;
3747
+ const modal = document.createElement("div");
3748
+ modal.className = "aegis-leadgen-modal";
3749
+ modal.style.cssText = `
3750
+ background: white;
3751
+ border-radius: 16px;
3752
+ max-width: 500px;
3753
+ width: 90%;
3754
+ padding: 40px;
3755
+ box-shadow: 0 10px 40px rgba(0,0,0,0.3);
3756
+ text-align: center;
3757
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
3758
+ animation: aegisScaleIn 0.3s ease-out;
3759
+ position: relative;
3760
+ `;
3761
+ const title = document.createElement("h2");
3762
+ title.textContent = config.title || "Get 10% Off Your First Order!";
3763
+ title.style.cssText = "margin: 0 0 16px 0; font-size: 28px; font-weight: 700; color: #1a1a1a;";
3764
+ modal.appendChild(title);
3765
+ const message = document.createElement("p");
3766
+ message.textContent = config.message || "Subscribe to our newsletter and get an exclusive discount code.";
3767
+ message.style.cssText = "margin: 0 0 24px 0; font-size: 16px; color: #666;";
3768
+ modal.appendChild(message);
3769
+ const form = document.createElement("form");
3770
+ const emailInput = document.createElement("input");
3771
+ emailInput.type = "email";
3772
+ emailInput.placeholder = "Enter your email";
3773
+ emailInput.required = true;
3774
+ emailInput.style.cssText = `
3775
+ width: 100%;
3776
+ padding: 14px;
3777
+ border: 2px solid #ddd;
3778
+ border-radius: 8px;
3779
+ font-size: 16px;
3780
+ margin-bottom: 16px;
3781
+ box-sizing: border-box;
3782
+ `;
3783
+ form.appendChild(emailInput);
3784
+ const submitButton = document.createElement("button");
3785
+ submitButton.type = "submit";
3786
+ submitButton.textContent = "Get My Discount";
3787
+ submitButton.style.cssText = `
3788
+ width: 100%;
3789
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
3790
+ color: white;
3791
+ border: none;
3792
+ padding: 16px 32px;
3793
+ border-radius: 8px;
3794
+ font-weight: 700;
3795
+ font-size: 16px;
3796
+ cursor: pointer;
3797
+ `;
3798
+ form.appendChild(submitButton);
3799
+ form.addEventListener("submit", async (e) => {
3800
+ e.preventDefault();
3801
+ submitButton.disabled = true;
3802
+ submitButton.textContent = "Submitting...";
3803
+ try {
3804
+ await this.trackEvent(widgetId, "submit", {
3805
+ type: "lead_gen",
3806
+ email: emailInput.value
3807
+ });
3808
+ modal.innerHTML = `
3809
+ <div style="text-align: center; padding: 20px;">
3810
+ <div style="font-size: 48px; margin-bottom: 16px;">✅</div>
3811
+ <h3 style="margin: 0 0 12px 0; color: #1a73e8;">Thank You!</h3>
3812
+ <p style="margin: 0; color: #666;">Check your email for your discount code!</p>
3813
+ </div>
3814
+ `;
3815
+ setTimeout(() => {
3816
+ document.body.removeChild(overlay);
3817
+ }, 2500);
3818
+ } catch (error) {
3819
+ this.log(`Error submitting lead gen form: ${error}`, true);
3820
+ submitButton.disabled = false;
3821
+ submitButton.textContent = "Get My Discount";
3822
+ }
3823
+ });
3824
+ modal.appendChild(form);
3825
+ const closeButton = document.createElement("button");
3826
+ closeButton.textContent = "×";
3827
+ closeButton.setAttribute("aria-label", "Close");
3828
+ closeButton.style.cssText = `
3829
+ position: absolute;
3830
+ top: 16px;
3831
+ right: 16px;
3832
+ background: transparent;
3833
+ border: none;
3834
+ font-size: 28px;
3835
+ cursor: pointer;
3836
+ color: #999;
3837
+ `;
3838
+ closeButton.addEventListener("click", () => {
3839
+ this.trackEvent(widgetId, "dismiss", { type: "lead_gen" });
3840
+ document.body.removeChild(overlay);
3841
+ this.renderedWidgets.delete(widgetId);
3842
+ });
3843
+ modal.appendChild(closeButton);
3844
+ overlay.appendChild(modal);
3845
+ this.addAnimationStyles();
3846
+ document.body.appendChild(overlay);
3847
+ this.trackEvent(widgetId, "show", { type: "lead_gen" });
3848
+ }
3849
+ interpolateCartVariables(template) {
3850
+ if (!this.cartData) return template;
3851
+ return template.replace(/\{\{cart_total\}\}/g, `${this.cartData.cart_currency} ${this.cartData.cart_total.toFixed(2)}`).replace(/\{\{cart_currency\}\}/g, this.cartData.cart_currency).replace(/\{\{cart_items_count\}\}/g, this.cartData.cart_items.length.toString());
3852
+ }
3853
+ log(message, isError = false) {
3854
+ if (this.debugMode || isError) {
3855
+ const method = isError ? "error" : "log";
3856
+ console[method](`[AegisWidgets] ${message}`);
3857
+ }
3858
+ }
3859
+ }
3860
+ class TriggerEngine {
3861
+ constructor() {
3862
+ this.listeners = /* @__PURE__ */ new Map();
3863
+ this.isStarted = false;
3864
+ this.scrollDepthTargets = /* @__PURE__ */ new Set();
3865
+ this.scrollDepthReached = /* @__PURE__ */ new Set();
3866
+ this.timeOnPageTargets = /* @__PURE__ */ new Map();
3867
+ this.exitIntentEnabled = false;
3868
+ this.exitIntentFired = false;
3869
+ this.inactivityTargets = /* @__PURE__ */ new Map();
3870
+ this.lastActivityTime = Date.now();
3871
+ this.scrollVelocityEnabled = false;
3872
+ this.scrollVelocityFired = false;
3873
+ this.scrollVelocityConfig = {
3874
+ threshold: 0.5,
3875
+ minScrollPosition: 100,
3876
+ cooldown: 5e3
3877
+ };
3878
+ this.lastScrollY = 0;
3879
+ this.lastScrollTime = Date.now();
3880
+ this.visibilityChangeEnabled = false;
3881
+ this.backButtonEnabled = false;
3882
+ this.backButtonFired = false;
3883
+ this.handleScroll = () => {
3884
+ const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
3885
+ const scrollHeight = document.documentElement.scrollHeight - window.innerHeight;
3886
+ const scrollPercent = scrollHeight > 0 ? scrollTop / scrollHeight * 100 : 0;
3887
+ for (const targetDepth of this.scrollDepthTargets) {
3888
+ if (scrollPercent >= targetDepth && !this.scrollDepthReached.has(targetDepth)) {
3889
+ this.scrollDepthReached.add(targetDepth);
3890
+ this.emit(`scroll_depth_${targetDepth}`, {
3891
+ depth_percent: targetDepth,
3892
+ actual_percent: scrollPercent,
3893
+ scroll_top: scrollTop,
3894
+ scroll_height: scrollHeight
3895
+ });
3896
+ }
3897
+ }
3898
+ };
3899
+ this.handleExitIntent = (event) => {
3900
+ if (this.exitIntentFired) {
3901
+ return;
3902
+ }
3903
+ if (event.clientY < 10) {
3904
+ this.exitIntentFired = true;
3905
+ this.emit("exit_intent", {
3906
+ client_y: event.clientY,
3907
+ page_url: window.location.href,
3908
+ time_on_page: this.pageLoadTime ? (Date.now() - this.pageLoadTime) / 1e3 : 0
3909
+ });
3910
+ }
3911
+ };
3912
+ this.handleScrollVelocity = () => {
3913
+ if (this.scrollVelocityFired) {
3914
+ return;
3915
+ }
3916
+ const currentY = window.scrollY || document.documentElement.scrollTop;
3917
+ const currentTime = Date.now();
3918
+ const timeDiff = currentTime - this.lastScrollTime;
3919
+ if (timeDiff > 100) {
3920
+ const distance = this.lastScrollY - currentY;
3921
+ const velocity = Math.abs(distance / timeDiff);
3922
+ if (distance > 0 && velocity > this.scrollVelocityConfig.threshold && currentY > this.scrollVelocityConfig.minScrollPosition) {
3923
+ this.scrollVelocityFired = true;
3924
+ this.emit("mobile_exit_intent", {
3925
+ scroll_velocity: velocity,
3926
+ scroll_distance: distance,
3927
+ current_position: currentY,
3928
+ page_url: window.location.href,
3929
+ time_on_page: this.pageLoadTime ? (Date.now() - this.pageLoadTime) / 1e3 : 0
3930
+ });
3931
+ this.scrollVelocityCooldownTimer = window.setTimeout(() => {
3932
+ this.scrollVelocityFired = false;
3933
+ }, this.scrollVelocityConfig.cooldown);
3934
+ }
3935
+ this.lastScrollY = currentY;
3936
+ this.lastScrollTime = currentTime;
3937
+ }
3938
+ };
3939
+ this.handleVisibilityChange = () => {
3940
+ if (document.hidden) {
3941
+ this.emit("visibility_hidden", {
3942
+ page_url: window.location.href,
3943
+ time_on_page: this.pageLoadTime ? (Date.now() - this.pageLoadTime) / 1e3 : 0
3944
+ });
3945
+ } else {
3946
+ this.emit("visibility_visible", {
3947
+ page_url: window.location.href
3948
+ });
3949
+ }
3950
+ };
3951
+ this.handleBackButton = () => {
3952
+ if (this.backButtonFired) {
3953
+ return;
3954
+ }
3955
+ this.backButtonFired = true;
3956
+ window.history.pushState(null, "", window.location.href);
3957
+ this.emit("back_button", {
3958
+ page_url: window.location.href,
3959
+ time_on_page: this.pageLoadTime ? (Date.now() - this.pageLoadTime) / 1e3 : 0
3960
+ });
3961
+ };
3962
+ this.handleActivity = () => {
3963
+ this.lastActivityTime = Date.now();
3964
+ };
3965
+ }
3966
+ on(eventType, callback) {
3967
+ if (!this.listeners.has(eventType)) {
3968
+ this.listeners.set(eventType, /* @__PURE__ */ new Set());
3969
+ }
3970
+ this.listeners.get(eventType).add(callback);
3971
+ }
3972
+ off(eventType, callback) {
3973
+ const callbacks = this.listeners.get(eventType);
3974
+ if (callbacks) {
3975
+ callbacks.delete(callback);
3976
+ }
3977
+ }
3978
+ registerScrollDepth(depthPercent) {
3979
+ this.scrollDepthTargets.add(depthPercent);
3980
+ }
3981
+ registerTimeOnPage(seconds) {
3982
+ if (!this.timeOnPageTargets.has(seconds)) {
3983
+ const timerId = window.setTimeout(() => {
3984
+ this.emit(`time_on_page_${seconds}`, {
3985
+ seconds,
3986
+ page_url: window.location.href
3987
+ });
3988
+ this.timeOnPageTargets.delete(seconds);
3989
+ }, seconds * 1e3);
3990
+ this.timeOnPageTargets.set(seconds, timerId);
3991
+ }
3992
+ }
3993
+ registerExitIntent() {
3994
+ this.exitIntentEnabled = true;
3995
+ }
3996
+ registerInactivity(idleSeconds) {
3997
+ if (!this.inactivityTargets.has(idleSeconds)) {
3998
+ const timerId = window.setTimeout(() => {
3999
+ const idleTime = (Date.now() - this.lastActivityTime) / 1e3;
4000
+ if (idleTime >= idleSeconds) {
4001
+ this.emit(`inactivity_${idleSeconds}`, {
4002
+ idle_seconds: idleSeconds,
4003
+ actual_idle_time: idleTime
4004
+ });
4005
+ }
4006
+ this.inactivityTargets.delete(idleSeconds);
4007
+ }, idleSeconds * 1e3);
4008
+ this.inactivityTargets.set(idleSeconds, timerId);
4009
+ }
4010
+ }
4011
+ registerScrollVelocity(config) {
4012
+ this.scrollVelocityEnabled = true;
4013
+ if (config) {
4014
+ this.scrollVelocityConfig = {
4015
+ threshold: config.threshold ?? this.scrollVelocityConfig.threshold,
4016
+ minScrollPosition: config.minScrollPosition ?? this.scrollVelocityConfig.minScrollPosition,
4017
+ cooldown: config.cooldown ?? this.scrollVelocityConfig.cooldown
4018
+ };
4019
+ }
4020
+ }
4021
+ registerVisibilityChange() {
4022
+ this.visibilityChangeEnabled = true;
4023
+ }
4024
+ registerBackButton() {
4025
+ this.backButtonEnabled = true;
4026
+ }
4027
+ start() {
4028
+ if (this.isStarted) {
4029
+ return;
4030
+ }
4031
+ this.pageLoadTime = Date.now();
4032
+ this.lastActivityTime = Date.now();
4033
+ this.lastScrollY = window.scrollY || 0;
4034
+ this.lastScrollTime = Date.now();
4035
+ if (this.scrollDepthTargets.size > 0) {
4036
+ this.attachScrollListener();
4037
+ }
4038
+ if (this.exitIntentEnabled) {
4039
+ this.attachExitIntentListener();
4040
+ }
4041
+ if (this.scrollVelocityEnabled) {
4042
+ this.attachScrollVelocityListener();
4043
+ }
4044
+ if (this.visibilityChangeEnabled) {
4045
+ this.attachVisibilityChangeListener();
4046
+ }
4047
+ if (this.backButtonEnabled) {
4048
+ this.attachBackButtonListener();
4049
+ }
4050
+ this.attachActivityListeners();
4051
+ this.startInactivityCheck();
4052
+ this.isStarted = true;
4053
+ }
4054
+ stop() {
4055
+ if (!this.isStarted) {
4056
+ return;
4057
+ }
4058
+ this.removeScrollListener();
4059
+ this.removeExitIntentListener();
4060
+ this.removeScrollVelocityListener();
4061
+ this.removeVisibilityChangeListener();
4062
+ this.removeBackButtonListener();
4063
+ this.removeActivityListeners();
4064
+ this.timeOnPageTargets.forEach((timerId) => clearTimeout(timerId));
4065
+ this.timeOnPageTargets.clear();
4066
+ this.inactivityTargets.forEach((timerId) => clearTimeout(timerId));
4067
+ this.inactivityTargets.clear();
4068
+ if (this.inactivityCheckInterval) {
4069
+ clearInterval(this.inactivityCheckInterval);
4070
+ this.inactivityCheckInterval = void 0;
4071
+ }
4072
+ if (this.scrollVelocityCooldownTimer) {
4073
+ clearTimeout(this.scrollVelocityCooldownTimer);
4074
+ this.scrollVelocityCooldownTimer = void 0;
4075
+ }
4076
+ this.isStarted = false;
4077
+ }
4078
+ reset() {
4079
+ this.scrollDepthReached.clear();
4080
+ this.exitIntentFired = false;
4081
+ this.scrollVelocityFired = false;
4082
+ this.backButtonFired = false;
4083
+ this.pageLoadTime = Date.now();
4084
+ this.lastActivityTime = Date.now();
4085
+ this.lastScrollY = window.scrollY || 0;
4086
+ this.lastScrollTime = Date.now();
4087
+ if (this.scrollVelocityCooldownTimer) {
4088
+ clearTimeout(this.scrollVelocityCooldownTimer);
4089
+ this.scrollVelocityCooldownTimer = void 0;
4090
+ }
4091
+ }
4092
+ attachScrollListener() {
4093
+ window.addEventListener("scroll", this.handleScroll, { passive: true });
4094
+ this.handleScroll();
4095
+ }
4096
+ removeScrollListener() {
4097
+ window.removeEventListener("scroll", this.handleScroll);
4098
+ }
4099
+ attachExitIntentListener() {
4100
+ document.addEventListener("mouseleave", this.handleExitIntent);
4101
+ }
4102
+ removeExitIntentListener() {
4103
+ document.removeEventListener("mouseleave", this.handleExitIntent);
4104
+ }
4105
+ attachScrollVelocityListener() {
4106
+ window.addEventListener("scroll", this.handleScrollVelocity, { passive: true });
4107
+ }
4108
+ removeScrollVelocityListener() {
4109
+ window.removeEventListener("scroll", this.handleScrollVelocity);
4110
+ }
4111
+ attachVisibilityChangeListener() {
4112
+ document.addEventListener("visibilitychange", this.handleVisibilityChange);
4113
+ }
4114
+ removeVisibilityChangeListener() {
4115
+ document.removeEventListener("visibilitychange", this.handleVisibilityChange);
4116
+ }
4117
+ attachBackButtonListener() {
4118
+ if (typeof window !== "undefined" && window.history) {
4119
+ window.history.pushState(null, "", window.location.href);
4120
+ window.addEventListener("popstate", this.handleBackButton);
4121
+ }
4122
+ }
4123
+ removeBackButtonListener() {
4124
+ window.removeEventListener("popstate", this.handleBackButton);
4125
+ }
4126
+ attachActivityListeners() {
4127
+ const events = ["mousedown", "mousemove", "keypress", "scroll", "touchstart", "click"];
4128
+ events.forEach((event) => {
4129
+ document.addEventListener(event, this.handleActivity, { passive: true });
4130
+ });
4131
+ }
4132
+ removeActivityListeners() {
4133
+ const events = ["mousedown", "mousemove", "keypress", "scroll", "touchstart", "click"];
4134
+ events.forEach((event) => {
4135
+ document.removeEventListener(event, this.handleActivity);
4136
+ });
4137
+ }
4138
+ startInactivityCheck() {
4139
+ this.inactivityCheckInterval = window.setInterval(() => {
4140
+ const idleTime = (Date.now() - this.lastActivityTime) / 1e3;
4141
+ for (const [idleSeconds] of this.inactivityTargets) {
4142
+ if (idleTime >= idleSeconds) {
4143
+ this.emit(`inactivity_${idleSeconds}`, {
4144
+ idle_seconds: idleSeconds,
4145
+ actual_idle_time: idleTime
4146
+ });
4147
+ const timerId = this.inactivityTargets.get(idleSeconds);
4148
+ if (timerId) {
4149
+ clearTimeout(timerId);
4150
+ this.inactivityTargets.delete(idleSeconds);
4151
+ }
4152
+ }
4153
+ }
4154
+ }, 1e3);
4155
+ }
4156
+ emit(eventType, data) {
4157
+ const event = {
4158
+ type: eventType,
4159
+ data,
4160
+ timestamp: Date.now()
4161
+ };
4162
+ const callbacks = this.listeners.get(eventType);
4163
+ if (callbacks) {
4164
+ callbacks.forEach((callback) => {
4165
+ try {
4166
+ callback(event);
4167
+ } catch (error) {
4168
+ console.error(`Error in trigger callback for ${eventType}:`, error);
4169
+ }
4170
+ });
4171
+ }
4172
+ const wildcardCallbacks = this.listeners.get("*");
4173
+ if (wildcardCallbacks) {
4174
+ wildcardCallbacks.forEach((callback) => {
4175
+ try {
4176
+ callback(event);
4177
+ } catch (error) {
4178
+ console.error(`Error in wildcard trigger callback:`, error);
4179
+ }
4180
+ });
4181
+ }
4182
+ }
4183
+ }
4184
+ class SdkConfigPoller {
4185
+ constructor(options) {
4186
+ this.currentETag = null;
4187
+ this.currentConfig = null;
4188
+ this.pollTimer = null;
4189
+ this.listeners = [];
4190
+ this.options = {
4191
+ pollIntervalMs: 3e5,
4192
+ // 5 minutes
4193
+ ...options
4194
+ };
4195
+ }
4196
+ /**
4197
+ * Fetch config once (on init) and start polling.
4198
+ */
4199
+ async start() {
4200
+ const config = await this.fetchConfig();
4201
+ this.pollTimer = setInterval(() => {
4202
+ this.fetchConfig().catch((err) => {
4203
+ logger.warn("SDK config poll failed:", err);
4204
+ });
4205
+ }, this.options.pollIntervalMs);
4206
+ return config;
4207
+ }
4208
+ /**
4209
+ * Stop polling.
4210
+ */
4211
+ stop() {
4212
+ if (this.pollTimer) {
4213
+ clearInterval(this.pollTimer);
4214
+ this.pollTimer = null;
4215
+ }
4216
+ }
4217
+ /**
4218
+ * Register a callback for config changes.
4219
+ */
4220
+ onChange(callback) {
4221
+ this.listeners.push(callback);
4222
+ return () => {
4223
+ const idx = this.listeners.indexOf(callback);
4224
+ if (idx >= 0) this.listeners.splice(idx, 1);
4225
+ };
4226
+ }
4227
+ /**
4228
+ * Get current cached config.
4229
+ */
4230
+ getConfig() {
4231
+ return this.currentConfig;
4232
+ }
4233
+ /**
4234
+ * Fetch config with ETag/304 support.
4235
+ */
4236
+ async fetchConfig() {
4237
+ try {
4238
+ const headers = {
4239
+ "X-Aegis-Write-Key": this.options.writeKey,
4240
+ "Accept": "application/json"
4241
+ };
4242
+ if (this.options.organizationId) {
4243
+ headers["X-Organization-ID"] = this.options.organizationId;
4244
+ }
4245
+ if (this.currentETag) {
4246
+ headers["If-None-Match"] = this.currentETag;
4247
+ }
4248
+ const response = await fetch(`${this.options.apiHost}/v1/sdk/config`, {
4249
+ method: "GET",
4250
+ headers
4251
+ });
4252
+ if (response.status === 304) {
4253
+ return this.currentConfig;
4254
+ }
4255
+ if (!response.ok) {
4256
+ logger.warn(`SDK config fetch failed: HTTP ${response.status}`);
4257
+ return this.currentConfig;
4258
+ }
4259
+ const config = await response.json();
4260
+ const newETag = response.headers.get("ETag");
4261
+ if (newETag) {
4262
+ this.currentETag = newETag;
4263
+ }
4264
+ const changed = !this.currentConfig || JSON.stringify(this.currentConfig) !== JSON.stringify(config);
4265
+ this.currentConfig = config;
4266
+ if (changed) {
4267
+ for (const listener of this.listeners) {
4268
+ try {
4269
+ listener(config);
4270
+ } catch (err) {
4271
+ logger.warn("SDK config change listener error:", err);
4272
+ }
4273
+ }
4274
+ }
4275
+ return config;
4276
+ } catch (err) {
4277
+ logger.warn("SDK config fetch error:", err);
4278
+ return this.currentConfig;
4279
+ }
4280
+ }
4281
+ }
4282
+ const aegis = new Aegis();
4283
+ export {
4284
+ Aegis,
4285
+ AegisInAppManager,
4286
+ AegisPlacementManager,
4287
+ AegisWebPush,
4288
+ AegisWidgetManager,
4289
+ SdkConfigPoller,
4290
+ TriggerEngine,
4291
+ debounce,
4292
+ aegis as default,
4293
+ renderPreview,
4294
+ throttle
4295
+ };
4296
+ //# sourceMappingURL=index.js.map