getu-attribution-v2-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,2017 @@
1
+ (function webpackUniversalModuleDefinition(root, factory) {
2
+ if(typeof exports === 'object' && typeof module === 'object')
3
+ module.exports = factory();
4
+ else if(typeof define === 'function' && define.amd)
5
+ define([], factory);
6
+ else if(typeof exports === 'object')
7
+ exports["GetuAIAttribution"] = factory();
8
+ else
9
+ root["GetuAIAttribution"] = factory();
10
+ })(this, () => {
11
+ return /******/ (() => { // webpackBootstrap
12
+ /******/ "use strict";
13
+ /******/ // The require scope
14
+ /******/ var __webpack_require__ = {};
15
+ /******/
16
+ /************************************************************************/
17
+ /******/ /* webpack/runtime/define property getters */
18
+ /******/ (() => {
19
+ /******/ // define getter functions for harmony exports
20
+ /******/ __webpack_require__.d = (exports, definition) => {
21
+ /******/ for(var key in definition) {
22
+ /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
23
+ /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
24
+ /******/ }
25
+ /******/ }
26
+ /******/ };
27
+ /******/ })();
28
+ /******/
29
+ /******/ /* webpack/runtime/hasOwnProperty shorthand */
30
+ /******/ (() => {
31
+ /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
32
+ /******/ })();
33
+ /******/
34
+ /************************************************************************/
35
+ var __webpack_exports__ = {};
36
+
37
+ // EXPORTS
38
+ __webpack_require__.d(__webpack_exports__, {
39
+ "default": () => (/* binding */ src)
40
+ });
41
+
42
+ // UNUSED EXPORTS: AttributionSDK, Currency, EventType, addUTMToURL, destroy, flush, getAttributionData, getCurrentUTMParams, getSDK, getStatus, init, trackEvent, trackFormSubmit, trackLogin, trackPageView, trackPurchase, trackSignup, waitForSDK
43
+
44
+ ;// ./src/types/index.ts
45
+ // Event types matching server-side enum exactly
46
+ var EventType;
47
+ (function (EventType) {
48
+ // Pre-conversion signals
49
+ EventType["PAGE_VIEW"] = "page_view";
50
+ EventType["VIDEO_PLAY"] = "video_play";
51
+ // Registration funnel
52
+ EventType["FORM_SUBMIT"] = "form_submit";
53
+ EventType["EMAIL_VERIFICATION"] = "email_verification";
54
+ // Login flow
55
+ EventType["LOGIN"] = "login";
56
+ // Signup flow
57
+ EventType["SIGNUP"] = "signup";
58
+ // Purchase funnel
59
+ EventType["PRODUCT_VIEW"] = "product_view";
60
+ EventType["ADD_TO_CART"] = "add_to_cart";
61
+ EventType["PURCHASE"] = "purchase";
62
+ })(EventType || (EventType = {}));
63
+ // Currency types
64
+ var Currency;
65
+ (function (Currency) {
66
+ Currency["USD"] = "USD";
67
+ })(Currency || (Currency = {}));
68
+ const defaultEndpoint = "https://attribution.getu.ai/attribution/api";
69
+ // Special events that should be sent immediately
70
+ const IMMEDIATE_EVENTS = [
71
+ EventType.PURCHASE,
72
+ EventType.LOGIN,
73
+ EventType.SIGNUP,
74
+ EventType.FORM_SUBMIT,
75
+ EventType.EMAIL_VERIFICATION,
76
+ ];
77
+
78
+ ;// ./src/utils/index.ts
79
+ // Generate unique ID
80
+ function generateId() {
81
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
82
+ const r = (Math.random() * 16) | 0;
83
+ const v = c === "x" ? r : (r & 0x3) | 0x8;
84
+ return v.toString(16);
85
+ });
86
+ }
87
+ // Generate timestamp in seconds
88
+ function getTimestamp() {
89
+ return Math.floor(Date.now() / 1000);
90
+ }
91
+ // Parse URL parameters
92
+ function parseUrlParams(url) {
93
+ const params = {};
94
+ try {
95
+ const urlObj = new URL(url);
96
+ urlObj.searchParams.forEach((value, key) => {
97
+ params[key] = value;
98
+ });
99
+ }
100
+ catch (error) {
101
+ // Handle invalid URLs
102
+ }
103
+ return params;
104
+ }
105
+ // Extract UTM parameters from URL
106
+ function extractUTMParams(url) {
107
+ const params = parseUrlParams(url);
108
+ const utmParams = {};
109
+ const utmKeys = [
110
+ "utm_source",
111
+ "utm_medium",
112
+ "utm_campaign",
113
+ "utm_term",
114
+ "utm_content",
115
+ ];
116
+ utmKeys.forEach((key) => {
117
+ if (params[key] && params[key].trim() !== "") {
118
+ utmParams[key] = params[key].trim();
119
+ }
120
+ });
121
+ return utmParams;
122
+ }
123
+ // Check if browser supports localStorage
124
+ function isLocalStorageSupported() {
125
+ try {
126
+ const test = "__localStorage_test__";
127
+ localStorage.setItem(test, test);
128
+ localStorage.removeItem(test);
129
+ return true;
130
+ }
131
+ catch {
132
+ return false;
133
+ }
134
+ }
135
+ // Check if browser supports IndexedDB
136
+ function isIndexedDBSupported() {
137
+ return "indexedDB" in window;
138
+ }
139
+ // Deep clone object
140
+ function deepClone(obj) {
141
+ if (obj === null || typeof obj !== "object") {
142
+ return obj;
143
+ }
144
+ if (obj instanceof Date) {
145
+ return new Date(obj.getTime());
146
+ }
147
+ if (obj instanceof Array) {
148
+ return obj.map((item) => deepClone(item));
149
+ }
150
+ if (typeof obj === "object") {
151
+ const cloned = {};
152
+ for (const key in obj) {
153
+ if (obj.hasOwnProperty(key)) {
154
+ cloned[key] = deepClone(obj[key]);
155
+ }
156
+ }
157
+ return cloned;
158
+ }
159
+ return obj;
160
+ }
161
+ // Debounce function
162
+ function debounce(func, wait) {
163
+ let timeout;
164
+ return (...args) => {
165
+ clearTimeout(timeout);
166
+ timeout = setTimeout(() => func(...args), wait);
167
+ };
168
+ }
169
+ // Throttle function
170
+ function throttle(func, limit) {
171
+ let inThrottle;
172
+ return (...args) => {
173
+ if (!inThrottle) {
174
+ func(...args);
175
+ inThrottle = true;
176
+ setTimeout(() => (inThrottle = false), limit);
177
+ }
178
+ };
179
+ }
180
+ // Retry function with exponential backoff
181
+ async function retry(fn, maxRetries = 3, baseDelay = 1000) {
182
+ let lastError;
183
+ for (let i = 0; i <= maxRetries; i++) {
184
+ try {
185
+ return await fn();
186
+ }
187
+ catch (error) {
188
+ lastError = error;
189
+ if (i === maxRetries) {
190
+ throw lastError;
191
+ }
192
+ const delay = baseDelay * Math.pow(2, i);
193
+ await new Promise((resolve) => setTimeout(resolve, delay));
194
+ }
195
+ }
196
+ throw lastError;
197
+ }
198
+ // Console logger implementation
199
+ class ConsoleLogger {
200
+ constructor(enabled = true) {
201
+ this.enabled = enabled;
202
+ }
203
+ debug(message, ...args) {
204
+ if (this.enabled && console.debug) {
205
+ console.debug(`[GetuAI Debug] ${message}`, ...args);
206
+ }
207
+ }
208
+ info(message, ...args) {
209
+ if (this.enabled && console.info) {
210
+ console.info(`[GetuAI Info] ${message}`, ...args);
211
+ }
212
+ }
213
+ warn(message, ...args) {
214
+ if (this.enabled && console.warn) {
215
+ console.warn(`[GetuAI Warn] ${message}`, ...args);
216
+ }
217
+ }
218
+ error(message, ...args) {
219
+ if (this.enabled && console.error) {
220
+ console.error(`[GetuAI Error] ${message}`, ...args);
221
+ }
222
+ }
223
+ }
224
+ // Get user agent info
225
+ function getUserAgent() {
226
+ return navigator.userAgent || "";
227
+ }
228
+ // Get referrer
229
+ function getReferrer() {
230
+ return document.referrer || "";
231
+ }
232
+ // Get current URL
233
+ function getCurrentUrl() {
234
+ return window.location.href;
235
+ }
236
+ // Get page title
237
+ function getPageTitle() {
238
+ return document.title || "";
239
+ }
240
+ // Check if user is online
241
+ function isOnline() {
242
+ return navigator.onLine;
243
+ }
244
+ // Generate session ID
245
+ function generateSessionId() {
246
+ return `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
247
+ }
248
+ // Validate email format
249
+ function isValidEmail(email) {
250
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
251
+ return emailRegex.test(email);
252
+ }
253
+ // Format currency
254
+ function formatCurrency(amount, currency = "USD") {
255
+ return new Intl.NumberFormat("en-US", {
256
+ style: "currency",
257
+ currency: currency,
258
+ }).format(amount);
259
+ }
260
+ // Add UTM parameters to URL
261
+ function addUTMToURL(url, utmParams) {
262
+ try {
263
+ const urlObj = new URL(url);
264
+ // Add UTM parameters if they don't already exist
265
+ Object.entries(utmParams).forEach(([key, value]) => {
266
+ if (value && !urlObj.searchParams.has(key)) {
267
+ urlObj.searchParams.set(key, value);
268
+ }
269
+ });
270
+ return urlObj.toString();
271
+ }
272
+ catch (error) {
273
+ // If URL parsing fails, return original URL
274
+ return url;
275
+ }
276
+ }
277
+ // Check if domain should be excluded from UTM passing
278
+ function shouldExcludeDomain(url, excludeDomains = []) {
279
+ try {
280
+ const urlObj = new URL(url);
281
+ const hostname = urlObj.hostname.toLowerCase();
282
+ return excludeDomains.some((domain) => {
283
+ const excludeDomain = domain.toLowerCase();
284
+ return (hostname === excludeDomain || hostname.endsWith(`.${excludeDomain}`));
285
+ });
286
+ }
287
+ catch (error) {
288
+ return false;
289
+ }
290
+ }
291
+ // Get domain from URL
292
+ function getDomainFromURL(url) {
293
+ try {
294
+ const urlObj = new URL(url);
295
+ return urlObj.hostname;
296
+ }
297
+ catch (error) {
298
+ return null;
299
+ }
300
+ }
301
+ // Check if URL is external (different domain)
302
+ function isExternalURL(url) {
303
+ try {
304
+ const urlObj = new URL(url);
305
+ const currentDomain = window.location.hostname;
306
+ return urlObj.hostname !== currentDomain;
307
+ }
308
+ catch (error) {
309
+ return false;
310
+ }
311
+ }
312
+ // Filter UTM parameters based on configuration
313
+ function filterUTMParams(utmData, allowedParams = ["utm_source", "utm_medium", "utm_campaign"]) {
314
+ const filtered = {};
315
+ allowedParams.forEach((param) => {
316
+ if (utmData[param]) {
317
+ filtered[param] = utmData[param];
318
+ }
319
+ });
320
+ return filtered;
321
+ }
322
+ // Get query string from URL
323
+ function getQueryString(url) {
324
+ try {
325
+ const targetUrl = url || window.location.href;
326
+ const urlObj = new URL(targetUrl);
327
+ return urlObj.search;
328
+ }
329
+ catch (error) {
330
+ return "";
331
+ }
332
+ }
333
+ // Get query parameters as object
334
+ function getQueryParams(url) {
335
+ try {
336
+ const targetUrl = url || window.location.href;
337
+ const urlObj = new URL(targetUrl);
338
+ const params = {};
339
+ urlObj.searchParams.forEach((value, key) => {
340
+ params[key] = value;
341
+ });
342
+ return params;
343
+ }
344
+ catch (error) {
345
+ return {};
346
+ }
347
+ }
348
+ // Clean URL by removing UTM parameters
349
+ function cleanURL() {
350
+ try {
351
+ const currentUrl = new URL(window.location.href);
352
+ const utmKeys = [
353
+ "utm_source",
354
+ "utm_medium",
355
+ "utm_campaign",
356
+ "utm_term",
357
+ "utm_content",
358
+ ];
359
+ let hasParams = false;
360
+ utmKeys.forEach((key) => {
361
+ if (currentUrl.searchParams.has(key)) {
362
+ currentUrl.searchParams.delete(key);
363
+ hasParams = true;
364
+ }
365
+ });
366
+ if (hasParams) {
367
+ // Use replaceState to avoid page reload and history entry
368
+ window.history.replaceState({}, document.title, currentUrl.toString());
369
+ }
370
+ }
371
+ catch (error) {
372
+ // Handle invalid URLs silently
373
+ console.warn("Failed to clean URL:", error);
374
+ }
375
+ }
376
+
377
+ ;// ./src/storage/index.ts
378
+
379
+ // LocalStorage implementation
380
+ class LocalStorageManager {
381
+ constructor(logger) {
382
+ this.logger = logger;
383
+ }
384
+ get(key) {
385
+ try {
386
+ if (!isLocalStorageSupported()) {
387
+ this.logger.warn("LocalStorage not supported");
388
+ return null;
389
+ }
390
+ const item = localStorage.getItem(key);
391
+ if (item === null) {
392
+ return null;
393
+ }
394
+ return JSON.parse(item);
395
+ }
396
+ catch (error) {
397
+ this.logger.error("Error reading from localStorage:", error);
398
+ return null;
399
+ }
400
+ }
401
+ set(key, value) {
402
+ try {
403
+ if (!isLocalStorageSupported()) {
404
+ this.logger.warn("LocalStorage not supported");
405
+ return;
406
+ }
407
+ localStorage.setItem(key, JSON.stringify(value));
408
+ }
409
+ catch (error) {
410
+ this.logger.error("Error writing to localStorage:", error);
411
+ // Handle quota exceeded
412
+ this.handleQuotaExceeded();
413
+ }
414
+ }
415
+ remove(key) {
416
+ try {
417
+ if (isLocalStorageSupported()) {
418
+ localStorage.removeItem(key);
419
+ }
420
+ }
421
+ catch (error) {
422
+ this.logger.error("Error removing from localStorage:", error);
423
+ }
424
+ }
425
+ clear() {
426
+ try {
427
+ if (isLocalStorageSupported()) {
428
+ localStorage.clear();
429
+ }
430
+ }
431
+ catch (error) {
432
+ this.logger.error("Error clearing localStorage:", error);
433
+ }
434
+ }
435
+ handleQuotaExceeded() {
436
+ // Try to clean up old data
437
+ try {
438
+ const keys = Object.keys(localStorage);
439
+ const attributionKeys = keys.filter((key) => key.startsWith("attribution_"));
440
+ if (attributionKeys.length > 0) {
441
+ // Remove oldest data first
442
+ attributionKeys.sort((a, b) => {
443
+ const aData = this.get(a);
444
+ const bData = this.get(b);
445
+ return (aData?.expiresAt || 0) - (bData?.expiresAt || 0);
446
+ });
447
+ // Remove oldest 20% of data
448
+ const toRemove = Math.ceil(attributionKeys.length * 0.2);
449
+ attributionKeys.slice(0, toRemove).forEach((key) => {
450
+ this.remove(key);
451
+ });
452
+ this.logger.info(`Cleaned up ${toRemove} old attribution records`);
453
+ }
454
+ }
455
+ catch (error) {
456
+ this.logger.error("Error during quota cleanup:", error);
457
+ }
458
+ }
459
+ }
460
+ // IndexedDB implementation
461
+ class IndexedDBManager {
462
+ constructor(logger) {
463
+ this.dbName = "attribution_events";
464
+ this.dbVersion = 1;
465
+ this.storeName = "events";
466
+ this.db = null;
467
+ this.logger = logger;
468
+ }
469
+ async init() {
470
+ if (!isIndexedDBSupported()) {
471
+ this.logger.warn("IndexedDB not supported");
472
+ return;
473
+ }
474
+ return new Promise((resolve, reject) => {
475
+ const request = indexedDB.open(this.dbName, this.dbVersion);
476
+ request.onerror = () => {
477
+ this.logger.error("Failed to open IndexedDB:", request.error);
478
+ reject(request.error);
479
+ };
480
+ request.onsuccess = () => {
481
+ this.db = request.result;
482
+ this.logger.info("IndexedDB initialized successfully");
483
+ resolve();
484
+ };
485
+ request.onupgradeneeded = (event) => {
486
+ const db = event.target.result;
487
+ if (!db.objectStoreNames.contains(this.storeName)) {
488
+ const store = db.createObjectStore(this.storeName, {
489
+ keyPath: "id",
490
+ autoIncrement: true,
491
+ });
492
+ store.createIndex("timestamp", "timestamp", { unique: false });
493
+ store.createIndex("sent", "sent", { unique: false });
494
+ store.createIndex("queued_at", "queued_at", { unique: false });
495
+ }
496
+ };
497
+ });
498
+ }
499
+ async addEvent(event) {
500
+ if (!this.db) {
501
+ throw new Error("IndexedDB not initialized");
502
+ }
503
+ return new Promise((resolve, reject) => {
504
+ const transaction = this.db.transaction([this.storeName], "readwrite");
505
+ const store = transaction.objectStore(this.storeName);
506
+ const eventRecord = {
507
+ ...event,
508
+ queued_at: Date.now(),
509
+ sent: false,
510
+ };
511
+ const request = store.add(eventRecord);
512
+ request.onsuccess = () => {
513
+ this.logger.debug("Event added to IndexedDB queue");
514
+ resolve();
515
+ };
516
+ request.onerror = () => {
517
+ this.logger.error("Failed to add event to IndexedDB:", request.error);
518
+ reject(request.error);
519
+ };
520
+ });
521
+ }
522
+ async getUnsentEvents(limit = 100) {
523
+ if (!this.db) {
524
+ return [];
525
+ }
526
+ return new Promise((resolve, reject) => {
527
+ const transaction = this.db.transaction([this.storeName], "readonly");
528
+ const store = transaction.objectStore(this.storeName);
529
+ const index = store.index("sent");
530
+ const request = index.getAll(IDBKeyRange.only(false), limit);
531
+ request.onsuccess = () => {
532
+ const events = request.result.map((record) => {
533
+ const { queued_at, sent, ...event } = record;
534
+ return event;
535
+ });
536
+ resolve(events);
537
+ };
538
+ request.onerror = () => {
539
+ this.logger.error("Failed to get unsent events:", request.error);
540
+ reject(request.error);
541
+ };
542
+ });
543
+ }
544
+ async markEventsAsSent(eventIds) {
545
+ if (!this.db || eventIds.length === 0) {
546
+ return;
547
+ }
548
+ return new Promise((resolve, reject) => {
549
+ const transaction = this.db.transaction([this.storeName], "readwrite");
550
+ const store = transaction.objectStore(this.storeName);
551
+ let completed = 0;
552
+ let hasError = false;
553
+ eventIds.forEach((eventId) => {
554
+ const getRequest = store.get(eventId);
555
+ getRequest.onsuccess = () => {
556
+ if (getRequest.result) {
557
+ const record = { ...getRequest.result, sent: true };
558
+ const putRequest = store.put(record);
559
+ putRequest.onsuccess = () => {
560
+ completed++;
561
+ if (completed === eventIds.length && !hasError) {
562
+ resolve();
563
+ }
564
+ };
565
+ putRequest.onerror = () => {
566
+ hasError = true;
567
+ this.logger.error("Failed to mark event as sent:", putRequest.error);
568
+ reject(putRequest.error);
569
+ };
570
+ }
571
+ else {
572
+ completed++;
573
+ if (completed === eventIds.length && !hasError) {
574
+ resolve();
575
+ }
576
+ }
577
+ };
578
+ getRequest.onerror = () => {
579
+ hasError = true;
580
+ this.logger.error("Failed to get event for marking as sent:", getRequest.error);
581
+ reject(getRequest.error);
582
+ };
583
+ });
584
+ });
585
+ }
586
+ async cleanupOldEvents(maxAge = 7 * 24 * 60 * 60 * 1000) {
587
+ if (!this.db) {
588
+ return;
589
+ }
590
+ const cutoffTime = Date.now() - maxAge;
591
+ return new Promise((resolve, reject) => {
592
+ const transaction = this.db.transaction([this.storeName], "readwrite");
593
+ const store = transaction.objectStore(this.storeName);
594
+ const index = store.index("queued_at");
595
+ const request = index.openCursor(IDBKeyRange.upperBound(cutoffTime));
596
+ request.onsuccess = () => {
597
+ const cursor = request.result;
598
+ if (cursor) {
599
+ cursor.delete();
600
+ cursor.continue();
601
+ }
602
+ else {
603
+ this.logger.info("Old events cleanup completed");
604
+ resolve();
605
+ }
606
+ };
607
+ request.onerror = () => {
608
+ this.logger.error("Failed to cleanup old events:", request.error);
609
+ reject(request.error);
610
+ };
611
+ });
612
+ }
613
+ async getQueueSize() {
614
+ if (!this.db) {
615
+ return 0;
616
+ }
617
+ return new Promise((resolve, reject) => {
618
+ const transaction = this.db.transaction([this.storeName], "readonly");
619
+ const store = transaction.objectStore(this.storeName);
620
+ const request = store.count();
621
+ request.onsuccess = () => {
622
+ resolve(request.result);
623
+ };
624
+ request.onerror = () => {
625
+ this.logger.error("Failed to get queue size:", request.error);
626
+ reject(request.error);
627
+ };
628
+ });
629
+ }
630
+ async clear() {
631
+ if (!this.db) {
632
+ return;
633
+ }
634
+ return new Promise((resolve, reject) => {
635
+ const transaction = this.db.transaction([this.storeName], "readwrite");
636
+ const store = transaction.objectStore(this.storeName);
637
+ const request = store.clear();
638
+ request.onsuccess = () => {
639
+ this.logger.info("IndexedDB queue cleared");
640
+ resolve();
641
+ };
642
+ request.onerror = () => {
643
+ this.logger.error("Failed to clear IndexedDB queue:", request.error);
644
+ reject(request.error);
645
+ };
646
+ });
647
+ }
648
+ }
649
+ // Attribution storage manager
650
+ class AttributionStorageManager {
651
+ constructor(logger) {
652
+ this.UTM_STORAGE_KEY = "attribution_utm_data";
653
+ this.SESSION_STORAGE_KEY = "attribution_session";
654
+ this.logger = logger;
655
+ this.localStorage = new LocalStorageManager(logger);
656
+ this.indexedDB = new IndexedDBManager(logger);
657
+ }
658
+ async init() {
659
+ await this.indexedDB.init();
660
+ }
661
+ // UTM data management
662
+ storeUTMData(utmData) {
663
+ try {
664
+ const existingData = this.getUTMData();
665
+ // Create default UTM data if none exists
666
+ const defaultUTMData = {
667
+ utm_source: "",
668
+ utm_medium: "",
669
+ utm_campaign: "",
670
+ utm_term: "",
671
+ utm_content: "",
672
+ timestamp: Date.now(),
673
+ };
674
+ const newData = {
675
+ firstTouch: existingData?.firstTouch || defaultUTMData,
676
+ lastTouch: existingData?.lastTouch || defaultUTMData,
677
+ touchpoints: existingData?.touchpoints || [],
678
+ ...utmData,
679
+ expiresAt: Date.now() + 30 * 24 * 60 * 60 * 1000, // 30 days
680
+ };
681
+ this.localStorage.set(this.UTM_STORAGE_KEY, newData);
682
+ this.logger.debug("UTM data stored successfully:", {
683
+ firstTouch: newData.firstTouch,
684
+ lastTouch: newData.lastTouch,
685
+ touchpointsCount: newData.touchpoints.length,
686
+ expiresAt: new Date(newData.expiresAt).toISOString(),
687
+ });
688
+ }
689
+ catch (error) {
690
+ this.logger.error("Failed to store UTM data:", error);
691
+ }
692
+ }
693
+ getUTMData() {
694
+ const data = this.localStorage.get(this.UTM_STORAGE_KEY);
695
+ if (data && data.expiresAt && data.expiresAt > Date.now()) {
696
+ return data;
697
+ }
698
+ // Clean up expired data
699
+ if (data) {
700
+ this.localStorage.remove(this.UTM_STORAGE_KEY);
701
+ }
702
+ return null;
703
+ }
704
+ // Session management
705
+ storeSession(session) {
706
+ this.localStorage.set(this.SESSION_STORAGE_KEY, session);
707
+ }
708
+ getSession() {
709
+ return this.localStorage.get(this.SESSION_STORAGE_KEY);
710
+ }
711
+ // Event queue management
712
+ async queueEvent(event) {
713
+ try {
714
+ await this.indexedDB.addEvent(event);
715
+ }
716
+ catch (error) {
717
+ this.logger.error("Failed to queue event:", error);
718
+ throw error;
719
+ }
720
+ }
721
+ async getUnsentEvents(limit = 100) {
722
+ return await this.indexedDB.getUnsentEvents(limit);
723
+ }
724
+ async markEventsAsSent(eventIds) {
725
+ await this.indexedDB.markEventsAsSent(eventIds);
726
+ }
727
+ async getQueueSize() {
728
+ return await this.indexedDB.getQueueSize();
729
+ }
730
+ async cleanupOldEvents() {
731
+ await this.indexedDB.cleanupOldEvents();
732
+ }
733
+ async clearQueue() {
734
+ await this.indexedDB.clear();
735
+ }
736
+ // Cleanup expired data
737
+ cleanupExpiredData() {
738
+ this.getUTMData(); // This will automatically clean up expired UTM data
739
+ }
740
+ }
741
+
742
+ ;// ./src/queue/index.ts
743
+
744
+
745
+ class EventQueueManager {
746
+ constructor(logger, apiKey, apiEndpoint, batchSize = 100, batchInterval = 5000, maxRetries = 3, retryDelay = 1000, sendEvents) {
747
+ this.queue = [];
748
+ this.processing = false;
749
+ this.batchTimer = null;
750
+ this.logger = logger;
751
+ this.apiKey = apiKey;
752
+ this.apiEndpoint = apiEndpoint;
753
+ this.batchSize = batchSize;
754
+ this.batchInterval = batchInterval;
755
+ this.maxRetries = maxRetries;
756
+ this.retryDelay = retryDelay;
757
+ this.sendEvents = sendEvents;
758
+ // Debounced process function to avoid excessive calls
759
+ this.debouncedProcess = debounce(this.process.bind(this), 100);
760
+ }
761
+ add(event) {
762
+ this.queue.push(event);
763
+ this.logger.debug(`Event added to queue: ${event.event_type}`);
764
+ // Check if this is an immediate event
765
+ if (IMMEDIATE_EVENTS.includes(event.event_type)) {
766
+ this.logger.debug(`Immediate event detected: ${event.event_type}, processing immediately`);
767
+ this.processImmediate(event);
768
+ }
769
+ else {
770
+ // Schedule batch processing
771
+ this.scheduleBatchProcessing();
772
+ }
773
+ }
774
+ async process() {
775
+ if (this.processing || this.queue.length === 0) {
776
+ return;
777
+ }
778
+ this.processing = true;
779
+ try {
780
+ const eventsToProcess = this.queue.splice(0, this.batchSize);
781
+ this.logger.debug(`Processing ${eventsToProcess.length} events from queue`);
782
+ const result = await this.sendEvents(eventsToProcess);
783
+ this.logger.info(`Successfully processed ${eventsToProcess.length} events`);
784
+ // Events are automatically removed from queue when successfully sent
785
+ // No need to manually clean up as they were already spliced from the queue
786
+ }
787
+ catch (error) {
788
+ this.logger.error("Failed to process events:", error);
789
+ // Put events back in queue for retry
790
+ const failedEvents = this.queue.splice(0, this.batchSize);
791
+ this.queue.unshift(...failedEvents);
792
+ // Schedule retry
793
+ setTimeout(() => {
794
+ this.processing = false;
795
+ this.debouncedProcess();
796
+ }, this.retryDelay);
797
+ return;
798
+ }
799
+ this.processing = false;
800
+ // Process remaining events if any
801
+ if (this.queue.length > 0) {
802
+ this.debouncedProcess();
803
+ }
804
+ }
805
+ async processImmediate(event) {
806
+ try {
807
+ this.logger.debug(`Processing immediate event: ${event.event_type}`);
808
+ await this.sendEvents([event]);
809
+ this.logger.info(`Immediate event processed successfully: ${event.event_type}`);
810
+ // Event is automatically removed from queue when successfully sent
811
+ }
812
+ catch (error) {
813
+ this.logger.error(`Failed to process immediate event: ${event.event_type}`, error);
814
+ // Add to queue for retry
815
+ this.queue.unshift(event);
816
+ }
817
+ }
818
+ scheduleBatchProcessing() {
819
+ if (this.batchTimer) {
820
+ clearTimeout(this.batchTimer);
821
+ }
822
+ this.batchTimer = setTimeout(() => {
823
+ this.debouncedProcess();
824
+ }, this.batchInterval);
825
+ }
826
+ clear() {
827
+ this.queue = [];
828
+ if (this.batchTimer) {
829
+ clearTimeout(this.batchTimer);
830
+ this.batchTimer = null;
831
+ }
832
+ this.processing = false;
833
+ this.logger.info("Event queue cleared");
834
+ }
835
+ size() {
836
+ return this.queue.length;
837
+ }
838
+ // Get queue statistics
839
+ getStats() {
840
+ return {
841
+ size: this.queue.length,
842
+ processing: this.processing,
843
+ };
844
+ }
845
+ // Force process all events in queue
846
+ async flush() {
847
+ this.logger.info("Flushing event queue");
848
+ while (this.queue.length > 0) {
849
+ await this.process();
850
+ }
851
+ }
852
+ }
853
+ // HTTP client for sending events
854
+ class EventHttpClient {
855
+ constructor(logger, apiKey, apiEndpoint, maxRetries = 3, retryDelay = 1000) {
856
+ this.logger = logger;
857
+ this.apiKey = apiKey;
858
+ this.apiEndpoint = apiEndpoint;
859
+ this.maxRetries = maxRetries;
860
+ this.retryDelay = retryDelay;
861
+ }
862
+ async sendEvents(events) {
863
+ if (events.length === 0) {
864
+ return;
865
+ }
866
+ // Transform events to match server format
867
+ const transformedEvents = events.map((event) => ({
868
+ event_id: event.event_id,
869
+ event_type: event.event_type,
870
+ tracking_user_id: event.tracking_user_id,
871
+ utm_source: event.utm_source || null,
872
+ utm_medium: event.utm_medium || null,
873
+ utm_campaign: event.utm_campaign || null,
874
+ utm_term: event.utm_term || null,
875
+ utm_content: event.utm_content || null,
876
+ revenue: event.revenue || null,
877
+ currency: event.currency || null,
878
+ event_data: event.event_data || null,
879
+ context: event.context || null,
880
+ timestamp: event.timestamp || getTimestamp(), // Convert to seconds
881
+ }));
882
+ const batchRequest = { events: transformedEvents };
883
+ await retry(async () => {
884
+ const response = await fetch(`${this.apiEndpoint}/attribution/events`, {
885
+ method: "POST",
886
+ headers: {
887
+ "Content-Type": "application/json",
888
+ Authorization: `Bearer ${this.apiKey}`,
889
+ },
890
+ body: JSON.stringify(batchRequest),
891
+ });
892
+ if (!response.ok) {
893
+ const errorText = await response.text();
894
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
895
+ }
896
+ const result = await response.json();
897
+ this.logger.debug("Events sent successfully:", result);
898
+ // Return the sent events for cleanup
899
+ return { result, sentEvents: events };
900
+ }, this.maxRetries, this.retryDelay);
901
+ }
902
+ async sendSingleEvent(event) {
903
+ await this.sendEvents([event]);
904
+ }
905
+ // Test connection
906
+ async testConnection() {
907
+ try {
908
+ const response = await fetch(`${this.apiEndpoint}/health`, {
909
+ method: "GET",
910
+ headers: {
911
+ Authorization: `Bearer ${this.apiKey}`,
912
+ },
913
+ });
914
+ return response.ok;
915
+ }
916
+ catch (error) {
917
+ this.logger.error("Connection test failed:", error);
918
+ return false;
919
+ }
920
+ }
921
+ }
922
+ // Memory queue fallback
923
+ class MemoryQueueManager {
924
+ constructor(logger, maxSize = 1000) {
925
+ this.queue = [];
926
+ this.logger = logger;
927
+ this.maxSize = maxSize;
928
+ }
929
+ add(event) {
930
+ if (this.queue.length >= this.maxSize) {
931
+ this.logger.warn("Memory queue full, removing oldest event");
932
+ this.queue.shift();
933
+ }
934
+ this.queue.push(event);
935
+ this.logger.debug(`Event added to memory queue: ${event.event_type}`);
936
+ }
937
+ async process() {
938
+ // Memory queue doesn't process automatically
939
+ // Events are stored in memory only
940
+ this.logger.debug("Memory queue process called (no-op)");
941
+ }
942
+ clear() {
943
+ this.queue = [];
944
+ this.logger.info("Memory queue cleared");
945
+ }
946
+ size() {
947
+ return this.queue.length;
948
+ }
949
+ // Get all events from memory queue
950
+ getAllEvents() {
951
+ return [...this.queue];
952
+ }
953
+ // Remove events from memory queue
954
+ removeEvents(eventIds) {
955
+ this.queue = this.queue.filter((event) => !eventIds.includes(event.event_id));
956
+ }
957
+ }
958
+
959
+ ;// ./src/core/AttributionSDK.ts
960
+
961
+
962
+
963
+
964
+ class AttributionSDK {
965
+ constructor(config) {
966
+ this.session = null;
967
+ this.initialized = false;
968
+ this.autoTrackEnabled = false;
969
+ this.pageViewTrackTimes = new Map();
970
+ // SPA tracking internals
971
+ this.spaTrackingEnabled = false;
972
+ this.lastTrackedPath = "";
973
+ this.originalPushState = null;
974
+ this.originalReplaceState = null;
975
+ this.popstateHandler = null;
976
+ this.config = {
977
+ apiEndpoint: defaultEndpoint,
978
+ batchSize: 100,
979
+ batchInterval: 5000,
980
+ maxRetries: 3,
981
+ retryDelay: 1000,
982
+ enableDebug: false,
983
+ autoTrack: false,
984
+ autoTrackPageView: false,
985
+ sessionTimeout: 30 * 60 * 1000, // 30 minutes
986
+ enableCrossDomainUTM: true,
987
+ crossDomainUTMParams: [
988
+ "utm_source",
989
+ "utm_medium",
990
+ "utm_campaign",
991
+ "utm_term",
992
+ "utm_content",
993
+ ],
994
+ excludeDomains: [],
995
+ autoCleanUTM: true,
996
+ pageViewDebounceInterval: 5000, // 5 seconds default
997
+ ...config,
998
+ };
999
+ this.logger = new ConsoleLogger(this.config.enableDebug);
1000
+ this.storage = new AttributionStorageManager(this.logger);
1001
+ this.httpClient = new EventHttpClient(this.logger, this.config.apiKey, this.config.apiEndpoint || defaultEndpoint, this.config.maxRetries, this.config.retryDelay);
1002
+ this.queue = new EventQueueManager(this.logger, this.config.apiKey, this.config.apiEndpoint || defaultEndpoint, this.config.batchSize, this.config.batchInterval, this.config.maxRetries, this.config.retryDelay, (events) => this.httpClient.sendEvents(events));
1003
+ }
1004
+ async init() {
1005
+ if (this.initialized) {
1006
+ this.logger.warn("SDK already initialized");
1007
+ return;
1008
+ }
1009
+ try {
1010
+ this.logger.info("Initializing GetuAI Attribution SDK");
1011
+ // Initialize storage
1012
+ await this.storage.init();
1013
+ // Initialize session
1014
+ this.initializeSession();
1015
+ // Extract and store UTM data from current URL
1016
+ this.extractAndStoreUTMData();
1017
+ // Setup auto-tracking if enabled
1018
+ if (this.config.autoTrack) {
1019
+ this.setupAutoTracking();
1020
+ }
1021
+ // Setup online/offline handlers
1022
+ this.setupNetworkHandlers();
1023
+ // Setup page visibility handlers
1024
+ this.setupVisibilityHandlers();
1025
+ // Setup beforeunload handler
1026
+ this.setupBeforeUnloadHandler();
1027
+ this.initialized = true;
1028
+ this.logger.info("🚀 GetuAI Attribution SDK initialized successfully");
1029
+ this.logger.info("📄 Auto track page view = " + this.config.autoTrackPageView);
1030
+ // Auto track page view if enabled (independent from autoTrack)
1031
+ // Also enables SPA route tracking automatically
1032
+ if (this.config.autoTrackPageView) {
1033
+ this.logger.info("📄 Auto track page view enabled (including SPA route tracking)");
1034
+ // Record the initial path for SPA tracking
1035
+ this.lastTrackedPath = this.getCurrentPath();
1036
+ // Setup SPA tracking for route changes
1037
+ this.setupSPATracking();
1038
+ setTimeout(() => {
1039
+ // Fire asynchronously; do not block initialization
1040
+ this.trackPageView()
1041
+ .then(() => {
1042
+ this.logger.info("✅ Auto track page view completed");
1043
+ })
1044
+ .catch((error) => this.logger.error("❌ Auto track page view failed:", error));
1045
+ }, 100);
1046
+ }
1047
+ }
1048
+ catch (error) {
1049
+ this.logger.error("Failed to initialize SDK:", error);
1050
+ throw error;
1051
+ }
1052
+ }
1053
+ // Track custom event
1054
+ async trackEvent(eventType, eventData, tracking_user_id, revenue, currency = Currency.USD) {
1055
+ if (!this.initialized) {
1056
+ this.logger.warn("SDK not initialized, event not tracked");
1057
+ return;
1058
+ }
1059
+ try {
1060
+ const currentUrl = getCurrentUrl();
1061
+ // Try to fetch public IP (best-effort)
1062
+ let ip_address = null;
1063
+ try {
1064
+ ip_address = await this.fetchPublicIP();
1065
+ }
1066
+ catch (e) {
1067
+ // ignore
1068
+ }
1069
+ const pageContext = {
1070
+ domain: typeof window !== "undefined" ? window.location.hostname : null,
1071
+ path: typeof window !== "undefined" ? window.location.pathname : null,
1072
+ title: getPageTitle(),
1073
+ referrer: getReferrer(),
1074
+ url: currentUrl.split("?")[0],
1075
+ querystring: getQueryString(currentUrl),
1076
+ query_params: getQueryParams(currentUrl),
1077
+ ip_address: ip_address,
1078
+ };
1079
+ const sessionContext = {
1080
+ session_id: this.session?.sessionId,
1081
+ start_time: this.session?.startTime,
1082
+ last_activity: this.session?.lastActivity,
1083
+ page_views: this.session?.pageViews,
1084
+ };
1085
+ const event = {
1086
+ event_id: generateId(),
1087
+ event_type: eventType,
1088
+ tracking_user_id: tracking_user_id,
1089
+ timestamp: getTimestamp(),
1090
+ event_data: eventData,
1091
+ context: { page: pageContext, session: sessionContext },
1092
+ revenue: revenue,
1093
+ currency: currency,
1094
+ ...this.getUTMParams(),
1095
+ };
1096
+ this.logger.debug(`Tracking event: ${eventType}`, event);
1097
+ // Add to queue
1098
+ this.queue.add(event);
1099
+ }
1100
+ catch (error) {
1101
+ this.logger.error(`Failed to track event ${eventType}:`, error);
1102
+ }
1103
+ }
1104
+ // Track page view
1105
+ async trackPageView(pageData, tracking_user_id) {
1106
+ const currentUrl = getCurrentUrl();
1107
+ // Use URL without query string as key for debouncing
1108
+ const pageKey = currentUrl.split("?")[0];
1109
+ const now = Date.now();
1110
+ const debounceInterval = this.config.pageViewDebounceInterval || 5000;
1111
+ // Check if this page was tracked recently
1112
+ const lastTrackTime = this.pageViewTrackTimes.get(pageKey);
1113
+ if (lastTrackTime && now - lastTrackTime < debounceInterval) {
1114
+ this.logger.debug(`Page view debounced: ${pageKey} (last tracked ${now - lastTrackTime}ms ago)`);
1115
+ return;
1116
+ }
1117
+ const pageEventData = {
1118
+ url: currentUrl,
1119
+ title: getPageTitle(),
1120
+ referrer: getReferrer(),
1121
+ user_agent: getUserAgent(),
1122
+ ...pageData,
1123
+ };
1124
+ await this.trackEvent(EventType.PAGE_VIEW, pageEventData, tracking_user_id);
1125
+ // Update last track time for this page
1126
+ this.pageViewTrackTimes.set(pageKey, now);
1127
+ // Clean up old entries to prevent memory leak (keep only entries from last hour)
1128
+ this.cleanupPageViewTrackTimes();
1129
+ }
1130
+ // Clean up old page view track times to prevent memory leak
1131
+ cleanupPageViewTrackTimes() {
1132
+ const oneHourAgo = Date.now() - 60 * 60 * 1000;
1133
+ for (const [pageKey, trackTime] of this.pageViewTrackTimes.entries()) {
1134
+ if (trackTime < oneHourAgo) {
1135
+ this.pageViewTrackTimes.delete(pageKey);
1136
+ }
1137
+ }
1138
+ }
1139
+ // Best-effort fetch of public IP address
1140
+ async fetchPublicIP() {
1141
+ try {
1142
+ const controller = new AbortController();
1143
+ const timeout = setTimeout(() => controller.abort(), 2000);
1144
+ const response = await fetch("https://api.ipify.org?format=json", {
1145
+ signal: controller.signal,
1146
+ headers: { Accept: "application/json" },
1147
+ });
1148
+ clearTimeout(timeout);
1149
+ if (!response.ok) {
1150
+ return null;
1151
+ }
1152
+ const data = await response.json();
1153
+ const ip = typeof data?.ip === "string" ? data.ip : null;
1154
+ return ip;
1155
+ }
1156
+ catch (error) {
1157
+ this.logger.debug("Public IP fetch failed", error);
1158
+ return null;
1159
+ }
1160
+ }
1161
+ // Track purchase
1162
+ async trackPurchase(tracking_user_id, revenue, currency = Currency.USD, purchaseData) {
1163
+ await this.trackEvent(EventType.PURCHASE, purchaseData, tracking_user_id, revenue, currency);
1164
+ }
1165
+ // Track login
1166
+ async trackLogin(tracking_user_id, loginData) {
1167
+ await this.trackEvent(EventType.LOGIN, loginData, tracking_user_id);
1168
+ }
1169
+ // Track signup
1170
+ async trackSignup(tracking_user_id, signupData) {
1171
+ await this.trackEvent(EventType.SIGNUP, signupData, tracking_user_id);
1172
+ }
1173
+ // Track form submission
1174
+ async trackFormSubmit(tracking_user_id, formData) {
1175
+ await this.trackEvent(EventType.FORM_SUBMIT, formData, tracking_user_id);
1176
+ }
1177
+ // Get attribution data
1178
+ getAttributionData() {
1179
+ return this.storage.getUTMData();
1180
+ }
1181
+ // Manually add UTM parameters to a URL
1182
+ addUTMToURL(url) {
1183
+ if (!this.config.enableCrossDomainUTM) {
1184
+ return url;
1185
+ }
1186
+ const attributionData = this.getAttributionData();
1187
+ if (!attributionData) {
1188
+ return url;
1189
+ }
1190
+ const utmParams = filterUTMParams(attributionData.lastTouch, this.config.crossDomainUTMParams);
1191
+ return addUTMToURL(url, utmParams);
1192
+ }
1193
+ // Get current UTM parameters as object
1194
+ getCurrentUTMParams() {
1195
+ const attributionData = this.getAttributionData();
1196
+ if (!attributionData) {
1197
+ return {};
1198
+ }
1199
+ return filterUTMParams(attributionData.lastTouch, this.config.crossDomainUTMParams);
1200
+ }
1201
+ // Get UTM parameters for events
1202
+ getUTMParams() {
1203
+ const attributionData = this.getAttributionData();
1204
+ if (!attributionData) {
1205
+ this.logger.debug("No attribution data available for UTM params");
1206
+ return {};
1207
+ }
1208
+ const utmParams = {
1209
+ utm_source: attributionData.lastTouch.utm_source || null,
1210
+ utm_medium: attributionData.lastTouch.utm_medium || null,
1211
+ utm_campaign: attributionData.lastTouch.utm_campaign || null,
1212
+ utm_term: attributionData.lastTouch.utm_term || null,
1213
+ utm_content: attributionData.lastTouch.utm_content || null,
1214
+ };
1215
+ // Filter out empty values and return null for empty strings
1216
+ const filteredParams = {};
1217
+ if (utmParams.utm_source && utmParams.utm_source.trim() !== "") {
1218
+ filteredParams.utm_source = utmParams.utm_source;
1219
+ }
1220
+ else {
1221
+ filteredParams.utm_source = null;
1222
+ }
1223
+ if (utmParams.utm_medium && utmParams.utm_medium.trim() !== "") {
1224
+ filteredParams.utm_medium = utmParams.utm_medium;
1225
+ }
1226
+ else {
1227
+ filteredParams.utm_medium = null;
1228
+ }
1229
+ if (utmParams.utm_campaign && utmParams.utm_campaign.trim() !== "") {
1230
+ filteredParams.utm_campaign = utmParams.utm_campaign;
1231
+ }
1232
+ else {
1233
+ filteredParams.utm_campaign = null;
1234
+ }
1235
+ if (utmParams.utm_term && utmParams.utm_term.trim() !== "") {
1236
+ filteredParams.utm_term = utmParams.utm_term;
1237
+ }
1238
+ else {
1239
+ filteredParams.utm_term = null;
1240
+ }
1241
+ if (utmParams.utm_content && utmParams.utm_content.trim() !== "") {
1242
+ filteredParams.utm_content = utmParams.utm_content;
1243
+ }
1244
+ else {
1245
+ filteredParams.utm_content = null;
1246
+ }
1247
+ this.logger.debug("UTM params for event:", filteredParams);
1248
+ return filteredParams;
1249
+ }
1250
+ // Initialize user session
1251
+ initializeSession() {
1252
+ const existingSession = this.storage.getSession();
1253
+ const now = Date.now();
1254
+ if (existingSession &&
1255
+ now - existingSession.lastActivity < this.config.sessionTimeout) {
1256
+ // Extend existing session
1257
+ this.session = {
1258
+ ...existingSession,
1259
+ lastActivity: now,
1260
+ };
1261
+ }
1262
+ else {
1263
+ // Create new session
1264
+ this.session = {
1265
+ sessionId: generateSessionId(),
1266
+ startTime: now,
1267
+ lastActivity: now,
1268
+ pageViews: 0,
1269
+ };
1270
+ }
1271
+ this.storage.storeSession(this.session);
1272
+ this.logger.debug("Session initialized:", this.session);
1273
+ }
1274
+ // Extract and store UTM data from current URL
1275
+ extractAndStoreUTMData() {
1276
+ const currentUrl = getCurrentUrl();
1277
+ const utmParams = extractUTMParams(currentUrl);
1278
+ this.logger.debug("Extracting UTM params from URL:", currentUrl);
1279
+ this.logger.debug("Found UTM params:", utmParams);
1280
+ if (Object.keys(utmParams).length === 0) {
1281
+ this.logger.debug("No UTM parameters found in URL");
1282
+ return;
1283
+ }
1284
+ const utmData = {
1285
+ utm_source: utmParams.utm_source || "",
1286
+ utm_medium: utmParams.utm_medium || "",
1287
+ utm_campaign: utmParams.utm_campaign || "",
1288
+ utm_term: utmParams.utm_term || "",
1289
+ utm_content: utmParams.utm_content || "",
1290
+ timestamp: Date.now(),
1291
+ };
1292
+ const existingData = this.getAttributionData();
1293
+ const newData = {
1294
+ firstTouch: existingData?.firstTouch || utmData,
1295
+ lastTouch: utmData,
1296
+ touchpoints: existingData?.touchpoints || [],
1297
+ expiresAt: Date.now() + 30 * 24 * 60 * 60 * 1000, // 30 days
1298
+ };
1299
+ // Add to touchpoints if different from last touch
1300
+ if (!existingData ||
1301
+ existingData.lastTouch.utm_source !== utmData.utm_source ||
1302
+ existingData.lastTouch.utm_campaign !== utmData.utm_campaign) {
1303
+ newData.touchpoints.push(utmData);
1304
+ }
1305
+ this.storage.storeUTMData(newData);
1306
+ this.logger.info("UTM data extracted and stored successfully:", utmData);
1307
+ // Clean URL after storing UTM data to avoid displaying parameters in plain text
1308
+ if (this.config.autoCleanUTM) {
1309
+ cleanURL();
1310
+ }
1311
+ }
1312
+ // Setup auto-tracking
1313
+ setupAutoTracking() {
1314
+ this.autoTrackEnabled = true;
1315
+ // Track form interactions
1316
+ this.setupFormTracking();
1317
+ // Track link clicks
1318
+ this.setupLinkTracking();
1319
+ this.logger.info("Auto-tracking enabled");
1320
+ }
1321
+ // Setup form tracking
1322
+ setupFormTracking() {
1323
+ document.addEventListener("submit", (event) => {
1324
+ try {
1325
+ const form = event.target;
1326
+ if (!form) {
1327
+ return;
1328
+ }
1329
+ const fieldValues = this.serializeFormFields(form);
1330
+ this.trackEvent(EventType.FORM_SUBMIT, {
1331
+ ...fieldValues,
1332
+ form_id: form.id || form.name,
1333
+ form_action: form.action,
1334
+ form_method: form.method,
1335
+ });
1336
+ }
1337
+ catch (error) {
1338
+ this.logger.error("Failed to auto-track form submit:", error);
1339
+ }
1340
+ });
1341
+ }
1342
+ // Serialize all fields within a form to a flat key-value map
1343
+ serializeFormFields(form) {
1344
+ const result = {};
1345
+ try {
1346
+ const formData = new FormData(form);
1347
+ // Accumulate multiple values for the same name as arrays
1348
+ for (const [name, value] of formData.entries()) {
1349
+ const serializedValue = this.serializeFormValue(value);
1350
+ if (Object.prototype.hasOwnProperty.call(result, name)) {
1351
+ const existing = result[name];
1352
+ if (Array.isArray(existing)) {
1353
+ existing.push(serializedValue);
1354
+ result[name] = existing;
1355
+ }
1356
+ else {
1357
+ result[name] = [existing, serializedValue];
1358
+ }
1359
+ }
1360
+ else {
1361
+ result[name] = serializedValue;
1362
+ }
1363
+ }
1364
+ // Ensure all named fields are represented, including unchecked radios/checkboxes
1365
+ const elements = Array.from(form.elements);
1366
+ // Group elements by name
1367
+ const nameToElements = new Map();
1368
+ for (const el of elements) {
1369
+ const name = el.name;
1370
+ if (!name || el.disabled)
1371
+ continue;
1372
+ if (!nameToElements.has(name))
1373
+ nameToElements.set(name, []);
1374
+ nameToElements.get(name).push(el);
1375
+ }
1376
+ nameToElements.forEach((els, name) => {
1377
+ // Determine the dominant type for a group (radio/checkbox take precedence)
1378
+ const hasRadio = els.some((e) => e.type === "radio");
1379
+ const hasCheckbox = els.some((e) => e.type === "checkbox");
1380
+ const hasFile = els.some((e) => e.type === "file");
1381
+ const hasSelectMultiple = els.some((e) => e.tagName === "SELECT" && e.multiple);
1382
+ const hasPassword = els.some((e) => e.type === "password");
1383
+ if (hasCheckbox) {
1384
+ const checkedValues = els
1385
+ .filter((e) => e.type === "checkbox")
1386
+ .filter((e) => e.checked)
1387
+ .map((e) => e.value || "on");
1388
+ if (!Object.prototype.hasOwnProperty.call(result, name)) {
1389
+ result[name] = checkedValues; // [] when none checked
1390
+ }
1391
+ else if (!Array.isArray(result[name])) {
1392
+ result[name] = [result[name], ...checkedValues];
1393
+ }
1394
+ return;
1395
+ }
1396
+ if (hasRadio) {
1397
+ const checked = els
1398
+ .filter((e) => e.type === "radio")
1399
+ .find((e) => e.checked);
1400
+ if (!Object.prototype.hasOwnProperty.call(result, name)) {
1401
+ result[name] = checked ? checked.value : null;
1402
+ }
1403
+ return;
1404
+ }
1405
+ if (hasSelectMultiple) {
1406
+ const select = els.find((e) => e.tagName === "SELECT" && e.multiple);
1407
+ if (select) {
1408
+ const selectedValues = Array.from(select.selectedOptions).map((opt) => opt.value);
1409
+ if (!Object.prototype.hasOwnProperty.call(result, name)) {
1410
+ result[name] = selectedValues; // [] when none selected
1411
+ }
1412
+ return;
1413
+ }
1414
+ }
1415
+ if (hasFile) {
1416
+ const input = els.find((e) => e.type === "file");
1417
+ if (input) {
1418
+ const files = input.files ? Array.from(input.files) : [];
1419
+ if (!Object.prototype.hasOwnProperty.call(result, name)) {
1420
+ result[name] = files.map((f) => this.serializeFormValue(f));
1421
+ }
1422
+ return;
1423
+ }
1424
+ }
1425
+ if (hasPassword) {
1426
+ // Mask password fields
1427
+ if (Object.prototype.hasOwnProperty.call(result, name)) {
1428
+ result[name] =
1429
+ typeof result[name] === "string" ? "*****" : result[name];
1430
+ }
1431
+ else {
1432
+ result[name] = "*****";
1433
+ }
1434
+ return;
1435
+ }
1436
+ // For other inputs/selects/textarea, ensure at least an entry exists
1437
+ if (!Object.prototype.hasOwnProperty.call(result, name)) {
1438
+ const first = els[0];
1439
+ if (first.tagName === "SELECT") {
1440
+ const select = first;
1441
+ result[name] = select.multiple
1442
+ ? Array.from(select.selectedOptions).map((o) => o.value)
1443
+ : select.value;
1444
+ }
1445
+ else if (first.type) {
1446
+ result[name] = first.value ?? "";
1447
+ }
1448
+ else {
1449
+ result[name] = first.value ?? "";
1450
+ }
1451
+ }
1452
+ });
1453
+ }
1454
+ catch (error) {
1455
+ this.logger.error("Failed to serialize form fields:", error);
1456
+ }
1457
+ return result;
1458
+ }
1459
+ // Convert FormData value to a serializable value
1460
+ serializeFormValue(value) {
1461
+ if (value instanceof File) {
1462
+ return {
1463
+ file_name: value.name,
1464
+ file_size: value.size,
1465
+ file_type: value.type,
1466
+ };
1467
+ }
1468
+ return value;
1469
+ }
1470
+ // Setup link tracking
1471
+ setupLinkTracking() {
1472
+ document.addEventListener("click", (event) => {
1473
+ const target = event.target;
1474
+ const link = target.closest("a");
1475
+ if (link) {
1476
+ const href = link.href;
1477
+ const isExternal = isExternalURL(href);
1478
+ if (isExternal) {
1479
+ // Handle cross-domain UTM passing
1480
+ this.handleCrossDomainUTM(link, event);
1481
+ }
1482
+ }
1483
+ });
1484
+ }
1485
+ // Handle cross-domain UTM parameter passing
1486
+ handleCrossDomainUTM(link, event) {
1487
+ if (!this.config.enableCrossDomainUTM) {
1488
+ return;
1489
+ }
1490
+ const href = link.href;
1491
+ // Check if domain should be excluded
1492
+ if (shouldExcludeDomain(href, this.config.excludeDomains)) {
1493
+ this.logger.debug(`Domain excluded from UTM passing: ${href}`);
1494
+ return;
1495
+ }
1496
+ // Get current UTM data
1497
+ const attributionData = this.getAttributionData();
1498
+ if (!attributionData) {
1499
+ this.logger.debug("No UTM data available for cross-domain passing");
1500
+ return;
1501
+ }
1502
+ // Filter UTM parameters based on configuration
1503
+ const utmParams = filterUTMParams(attributionData.lastTouch, this.config.crossDomainUTMParams);
1504
+ if (Object.keys(utmParams).length === 0) {
1505
+ this.logger.debug("No UTM parameters to pass");
1506
+ return;
1507
+ }
1508
+ // Add UTM parameters to URL
1509
+ const enhancedURL = addUTMToURL(href, utmParams);
1510
+ if (enhancedURL !== href) {
1511
+ // Update the link href
1512
+ link.href = enhancedURL;
1513
+ this.logger.debug("UTM parameters added to external link:", {
1514
+ original: href,
1515
+ enhanced: enhancedURL,
1516
+ utmParams,
1517
+ });
1518
+ this.logger.debug("Cross-domain UTM passed:", {
1519
+ link_url: enhancedURL,
1520
+ original_url: href,
1521
+ utm_params_passed: utmParams,
1522
+ });
1523
+ }
1524
+ }
1525
+ // Setup network handlers
1526
+ setupNetworkHandlers() {
1527
+ window.addEventListener("online", () => {
1528
+ this.logger.info("Network connection restored");
1529
+ this.queue.flush();
1530
+ });
1531
+ window.addEventListener("offline", () => {
1532
+ this.logger.warn("Network connection lost");
1533
+ });
1534
+ }
1535
+ // Setup visibility handlers
1536
+ setupVisibilityHandlers() {
1537
+ document.addEventListener("visibilitychange", () => {
1538
+ if (document.visibilityState === "visible") {
1539
+ this.updateSessionActivity();
1540
+ }
1541
+ });
1542
+ }
1543
+ // Setup beforeunload handler
1544
+ setupBeforeUnloadHandler() {
1545
+ window.addEventListener("beforeunload", () => {
1546
+ this.updateSessionActivity();
1547
+ this.queue.flush();
1548
+ });
1549
+ }
1550
+ // Get current path (pathname + search) for comparison
1551
+ getCurrentPath() {
1552
+ if (typeof window === "undefined")
1553
+ return "";
1554
+ return window.location.pathname + window.location.search;
1555
+ }
1556
+ // Setup SPA (Single Page Application) route tracking
1557
+ setupSPATracking() {
1558
+ if (typeof window === "undefined" || typeof history === "undefined") {
1559
+ this.logger.warn("⚠️ SPA tracking not available in this environment");
1560
+ return;
1561
+ }
1562
+ if (this.spaTrackingEnabled) {
1563
+ this.logger.warn("⚠️ SPA tracking already enabled");
1564
+ return;
1565
+ }
1566
+ this.spaTrackingEnabled = true;
1567
+ this.lastTrackedPath = this.getCurrentPath();
1568
+ // Store original methods
1569
+ this.originalPushState = history.pushState.bind(history);
1570
+ this.originalReplaceState = history.replaceState.bind(history);
1571
+ // Create a handler for route changes
1572
+ const handleRouteChange = (changeType) => {
1573
+ const newPath = this.getCurrentPath();
1574
+ // Only track if path actually changed
1575
+ if (newPath === this.lastTrackedPath) {
1576
+ this.logger.debug(`🔄 [SPA] Route change detected (${changeType}) but path unchanged: ${newPath}`);
1577
+ return;
1578
+ }
1579
+ this.logger.debug(`🔄 [SPA] Route change detected (${changeType}): ${this.lastTrackedPath} -> ${newPath}`);
1580
+ this.lastTrackedPath = newPath;
1581
+ // Delay tracking to allow page title and content to update (100ms is sufficient for most frameworks)
1582
+ setTimeout(() => {
1583
+ this.trackPageView()
1584
+ .then(() => {
1585
+ this.logger.debug(`✅ [SPA] Page view tracked for: ${newPath}`);
1586
+ })
1587
+ .catch((error) => {
1588
+ this.logger.error(`❌ [SPA] Failed to track page view:`, error);
1589
+ });
1590
+ }, 100);
1591
+ };
1592
+ // Override history.pushState
1593
+ history.pushState = (data, unused, url) => {
1594
+ const result = this.originalPushState(data, unused, url);
1595
+ handleRouteChange("pushState");
1596
+ return result;
1597
+ };
1598
+ // Override history.replaceState
1599
+ history.replaceState = (data, unused, url) => {
1600
+ const result = this.originalReplaceState(data, unused, url);
1601
+ handleRouteChange("replaceState");
1602
+ return result;
1603
+ };
1604
+ // Listen for popstate (browser back/forward)
1605
+ this.popstateHandler = () => {
1606
+ handleRouteChange("popstate");
1607
+ };
1608
+ window.addEventListener("popstate", this.popstateHandler);
1609
+ this.logger.info("🔄 SPA tracking setup completed");
1610
+ }
1611
+ // Cleanup SPA tracking (restore original methods)
1612
+ cleanupSPATracking() {
1613
+ if (!this.spaTrackingEnabled)
1614
+ return;
1615
+ // Restore original history methods
1616
+ if (this.originalPushState) {
1617
+ history.pushState = this.originalPushState;
1618
+ this.originalPushState = null;
1619
+ }
1620
+ if (this.originalReplaceState) {
1621
+ history.replaceState = this.originalReplaceState;
1622
+ this.originalReplaceState = null;
1623
+ }
1624
+ // Remove popstate listener
1625
+ if (this.popstateHandler) {
1626
+ window.removeEventListener("popstate", this.popstateHandler);
1627
+ this.popstateHandler = null;
1628
+ }
1629
+ this.spaTrackingEnabled = false;
1630
+ this.logger.info("🔄 SPA tracking cleaned up");
1631
+ }
1632
+ // Update session activity
1633
+ updateSessionActivity() {
1634
+ if (this.session) {
1635
+ this.session.lastActivity = Date.now();
1636
+ this.storage.storeSession(this.session);
1637
+ }
1638
+ }
1639
+ // Flush all pending events
1640
+ async flush() {
1641
+ await this.queue.flush();
1642
+ }
1643
+ // Get SDK status
1644
+ getStatus() {
1645
+ return {
1646
+ initialized: this.initialized,
1647
+ session: this.session,
1648
+ queueSize: this.queue.size(),
1649
+ online: isOnline(),
1650
+ crossDomainUTM: {
1651
+ enabled: this.config.enableCrossDomainUTM || true,
1652
+ currentParams: this.getCurrentUTMParams(),
1653
+ },
1654
+ spaTracking: {
1655
+ enabled: this.spaTrackingEnabled,
1656
+ currentPath: this.lastTrackedPath,
1657
+ },
1658
+ };
1659
+ }
1660
+ // Destroy SDK instance
1661
+ destroy() {
1662
+ this.queue.clear();
1663
+ this.autoTrackEnabled = false;
1664
+ this.cleanupSPATracking();
1665
+ this.initialized = false;
1666
+ this.logger.info("🗑️ SDK destroyed");
1667
+ }
1668
+ }
1669
+
1670
+ ;// ./src/index.ts
1671
+
1672
+
1673
+ // Global SDK instance
1674
+ let globalSDK = null;
1675
+ // Auto-initialization function
1676
+ function autoInit() {
1677
+ // Check if SDK is already initialized
1678
+ if (globalSDK) {
1679
+ return;
1680
+ }
1681
+ // Look for configuration in script tag
1682
+ const script = document.currentScript;
1683
+ if (!script) {
1684
+ console.warn("GetuAI SDK: Could not find script tag for auto-initialization");
1685
+ return;
1686
+ }
1687
+ const apiKey = script.getAttribute("data-api-key");
1688
+ if (!apiKey) {
1689
+ console.warn("GetuAI SDK: No API key provided. Please add data-api-key attribute to script tag.");
1690
+ return;
1691
+ }
1692
+ const apiEndpoint = script.getAttribute("data-api-endpoint") || defaultEndpoint;
1693
+ const config = {
1694
+ apiKey,
1695
+ apiEndpoint,
1696
+ enableDebug: script.getAttribute("data-debug") === "true",
1697
+ autoTrack: script.getAttribute("data-auto-track") === "true",
1698
+ autoTrackPageView: script.getAttribute("data-auto-track-page-view") === "true",
1699
+ autoCleanUTM: script.getAttribute("data-auto-clean-utm") !== "false",
1700
+ batchSize: parseInt(script.getAttribute("data-batch-size") || "100"),
1701
+ batchInterval: parseInt(script.getAttribute("data-batch-interval") || "5000"),
1702
+ };
1703
+ // Initialize SDK
1704
+ init(config);
1705
+ }
1706
+ // Initialize SDK with configuration
1707
+ async function init(config) {
1708
+ if (globalSDK) {
1709
+ console.warn("GetuAI SDK: Already initialized");
1710
+ return globalSDK;
1711
+ }
1712
+ try {
1713
+ globalSDK = new AttributionSDK(config);
1714
+ await globalSDK.init();
1715
+ // Expose SDK to global scope for debugging
1716
+ if (config.enableDebug) {
1717
+ window.GetuAISDK = globalSDK;
1718
+ }
1719
+ console.log("GetuAI Attribution SDK initialized successfully");
1720
+ // Dispatch initialization complete event
1721
+ if (typeof window !== "undefined") {
1722
+ const event = new CustomEvent("getuaiSDKReady", {
1723
+ detail: { sdk: globalSDK },
1724
+ });
1725
+ window.dispatchEvent(event);
1726
+ }
1727
+ return globalSDK;
1728
+ }
1729
+ catch (error) {
1730
+ console.error("GetuAI SDK: Failed to initialize:", error);
1731
+ // Dispatch initialization error event
1732
+ if (typeof window !== "undefined") {
1733
+ const event = new CustomEvent("getuaiSDKError", {
1734
+ detail: { error },
1735
+ });
1736
+ window.dispatchEvent(event);
1737
+ }
1738
+ throw error;
1739
+ }
1740
+ }
1741
+ // Get current SDK instance
1742
+ function getSDK() {
1743
+ return globalSDK;
1744
+ }
1745
+ // Wait for SDK to be ready
1746
+ function waitForSDK() {
1747
+ return new Promise((resolve, reject) => {
1748
+ if (globalSDK) {
1749
+ resolve(globalSDK);
1750
+ return;
1751
+ }
1752
+ // Listen for SDK ready event
1753
+ const handleReady = (event) => {
1754
+ window.removeEventListener("getuaiSDKReady", handleReady);
1755
+ window.removeEventListener("getuaiSDKError", handleError);
1756
+ resolve(event.detail.sdk);
1757
+ };
1758
+ const handleError = (event) => {
1759
+ window.removeEventListener("getuaiSDKReady", handleReady);
1760
+ window.removeEventListener("getuaiSDKError", handleError);
1761
+ reject(event.detail.error);
1762
+ };
1763
+ window.addEventListener("getuaiSDKReady", handleReady);
1764
+ window.addEventListener("getuaiSDKError", handleError);
1765
+ // Timeout after 10 seconds
1766
+ setTimeout(() => {
1767
+ window.removeEventListener("getuaiSDKReady", handleReady);
1768
+ window.removeEventListener("getuaiSDKError", handleError);
1769
+ reject(new Error("SDK initialization timeout"));
1770
+ }, 10000);
1771
+ });
1772
+ }
1773
+ // Track event
1774
+ async function trackEvent(eventType, eventData, tracking_user_id, revenue, currency = Currency.USD) {
1775
+ const sdk = getSDK();
1776
+ if (!sdk) {
1777
+ console.warn("GetuAI SDK: Not initialized. Call init() first.");
1778
+ return;
1779
+ }
1780
+ await sdk.trackEvent(eventType, eventData, tracking_user_id, revenue, currency);
1781
+ }
1782
+ // Track page view
1783
+ async function trackPageView(pageData, tracking_user_id) {
1784
+ const sdk = getSDK();
1785
+ if (!sdk) {
1786
+ console.warn("GetuAI SDK: Not initialized. Call init() first.");
1787
+ return;
1788
+ }
1789
+ await sdk.trackPageView(pageData, tracking_user_id);
1790
+ }
1791
+ // Track purchase
1792
+ async function trackPurchase(tracking_user_id, revenue, currency = Currency.USD, purchaseData) {
1793
+ const sdk = getSDK();
1794
+ if (!sdk) {
1795
+ console.warn("GetuAI SDK: Not initialized. Call init() first.");
1796
+ return;
1797
+ }
1798
+ await sdk.trackPurchase(tracking_user_id, revenue, currency, purchaseData);
1799
+ }
1800
+ // Track login
1801
+ async function trackLogin(tracking_user_id, loginData) {
1802
+ const sdk = getSDK();
1803
+ if (!sdk) {
1804
+ console.warn("GetuAI SDK: Not initialized. Call init() first.");
1805
+ return;
1806
+ }
1807
+ await sdk.trackLogin(tracking_user_id, loginData);
1808
+ }
1809
+ // Track signup
1810
+ async function trackSignup(tracking_user_id, signupData) {
1811
+ const sdk = getSDK();
1812
+ if (!sdk) {
1813
+ console.warn("GetuAI SDK: Not initialized. Call init() first.");
1814
+ return;
1815
+ }
1816
+ await sdk.trackSignup(tracking_user_id, signupData);
1817
+ }
1818
+ // Track form submission
1819
+ async function trackFormSubmit(tracking_user_id, formData) {
1820
+ const sdk = getSDK();
1821
+ if (!sdk) {
1822
+ console.warn("GetuAI SDK: Not initialized. Call init() first.");
1823
+ return;
1824
+ }
1825
+ await sdk.trackFormSubmit(tracking_user_id, formData);
1826
+ }
1827
+ // Get attribution data
1828
+ function getAttributionData() {
1829
+ const sdk = getSDK();
1830
+ if (!sdk) {
1831
+ return null;
1832
+ }
1833
+ return sdk.getAttributionData();
1834
+ }
1835
+ // Flush pending events
1836
+ async function flush() {
1837
+ const sdk = getSDK();
1838
+ if (!sdk) {
1839
+ console.warn("GetuAI SDK: Not initialized. Call init() first.");
1840
+ return;
1841
+ }
1842
+ await sdk.flush();
1843
+ }
1844
+ // Get SDK status
1845
+ function getStatus() {
1846
+ const sdk = getSDK();
1847
+ if (!sdk) {
1848
+ return null;
1849
+ }
1850
+ return sdk.getStatus();
1851
+ }
1852
+ // Add UTM to URL
1853
+ function src_addUTMToURL(url) {
1854
+ const sdk = getSDK();
1855
+ if (!sdk) {
1856
+ console.warn("GetuAI SDK: Not initialized. Call init() first.");
1857
+ return url;
1858
+ }
1859
+ return sdk.addUTMToURL(url);
1860
+ }
1861
+ // Get current UTM parameters
1862
+ function getCurrentUTMParams() {
1863
+ const sdk = getSDK();
1864
+ if (!sdk) {
1865
+ return {};
1866
+ }
1867
+ return sdk.getCurrentUTMParams();
1868
+ }
1869
+ // Destroy SDK
1870
+ function destroy() {
1871
+ if (globalSDK) {
1872
+ globalSDK.destroy();
1873
+ globalSDK = null;
1874
+ console.log("GetuAI SDK destroyed");
1875
+ }
1876
+ }
1877
+ // Extend AttributionSDK class with static methods
1878
+ class AttributionSDKStatic extends AttributionSDK {
1879
+ // Static method to initialize SDK
1880
+ static async init(config) {
1881
+ return await init(config);
1882
+ }
1883
+ // Static method to track event
1884
+ static async trackEvent(eventType, eventData, tracking_user_id, revenue, currency = Currency.USD) {
1885
+ return await trackEvent(eventType, eventData, tracking_user_id, revenue, currency);
1886
+ }
1887
+ // Static method to track page view
1888
+ static async trackPageView(pageData, tracking_user_id) {
1889
+ return await trackPageView(pageData, tracking_user_id);
1890
+ }
1891
+ // Static method to track purchase
1892
+ static async trackPurchase(tracking_user_id, revenue, currency = Currency.USD, purchaseData) {
1893
+ return await trackPurchase(tracking_user_id, revenue, currency, purchaseData);
1894
+ }
1895
+ // Static method to track login
1896
+ static async trackLogin(tracking_user_id, loginData) {
1897
+ return await trackLogin(tracking_user_id, loginData);
1898
+ }
1899
+ // Static method to track signup
1900
+ static async trackSignup(tracking_user_id, signupData) {
1901
+ return await trackSignup(tracking_user_id, signupData);
1902
+ }
1903
+ // Static method to track form submission
1904
+ static async trackFormSubmit(tracking_user_id, formData) {
1905
+ return await trackFormSubmit(tracking_user_id, formData);
1906
+ }
1907
+ // Static method to get attribution data
1908
+ static getAttributionData() {
1909
+ return getAttributionData();
1910
+ }
1911
+ // Static method to flush events
1912
+ static async flush() {
1913
+ return await flush();
1914
+ }
1915
+ // Static method to get status
1916
+ static getStatus() {
1917
+ return getStatus();
1918
+ }
1919
+ // Static method to add UTM to URL
1920
+ static addUTMToURL(url) {
1921
+ return src_addUTMToURL(url);
1922
+ }
1923
+ // Static method to get current UTM parameters
1924
+ static getCurrentUTMParams() {
1925
+ return getCurrentUTMParams();
1926
+ }
1927
+ // Static method to destroy SDK
1928
+ static destroy() {
1929
+ destroy();
1930
+ }
1931
+ // Static method to get SDK instance
1932
+ static getSDK() {
1933
+ return getSDK();
1934
+ }
1935
+ // Static method to wait for SDK
1936
+ static waitForSDK() {
1937
+ return waitForSDK();
1938
+ }
1939
+ }
1940
+ // Export types
1941
+
1942
+ // Export main functions
1943
+
1944
+ // Export AttributionSDK class for advanced usage
1945
+
1946
+ // Auto-initialize if script is loaded with data-api-key
1947
+ if (typeof document !== "undefined") {
1948
+ // Initialize immediately when script loads, don't wait for DOMContentLoaded
1949
+ // This ensures SDK is ready before any body scripts execute
1950
+ autoInit();
1951
+ }
1952
+ // Expose functions to global scope for browser usage
1953
+ if (typeof window !== "undefined") {
1954
+ window.getuaiSDK = {
1955
+ init,
1956
+ getSDK,
1957
+ waitForSDK,
1958
+ trackEvent,
1959
+ trackPageView,
1960
+ trackPurchase,
1961
+ trackLogin,
1962
+ trackSignup,
1963
+ trackFormSubmit,
1964
+ getAttributionData,
1965
+ flush,
1966
+ getStatus,
1967
+ addUTMToURL: src_addUTMToURL,
1968
+ getCurrentUTMParams,
1969
+ destroy,
1970
+ EventType: EventType,
1971
+ Currency: Currency,
1972
+ AttributionSDK: AttributionSDKStatic, // 暴露带静态方法的AttributionSDK类
1973
+ };
1974
+ // Also expose individual functions for backward compatibility
1975
+ window.init = init;
1976
+ window.waitForSDK = waitForSDK;
1977
+ window.trackEvent = trackEvent;
1978
+ window.trackPageView = trackPageView;
1979
+ window.trackPurchase = trackPurchase;
1980
+ window.trackLogin = trackLogin;
1981
+ window.trackSignup = trackSignup;
1982
+ window.trackFormSubmit = trackFormSubmit;
1983
+ window.getAttributionData = getAttributionData;
1984
+ window.flush = flush;
1985
+ window.getStatus = getStatus;
1986
+ window.addUTMToURL = src_addUTMToURL;
1987
+ window.getCurrentUTMParams = getCurrentUTMParams;
1988
+ window.destroy = destroy;
1989
+ window.AttributionSDK = AttributionSDKStatic; // 直接暴露带静态方法的AttributionSDK类
1990
+ }
1991
+ // Default export
1992
+ /* harmony default export */ const src = ({
1993
+ init,
1994
+ getSDK,
1995
+ waitForSDK,
1996
+ trackEvent,
1997
+ trackPageView,
1998
+ trackPurchase,
1999
+ trackLogin,
2000
+ trackSignup,
2001
+ trackFormSubmit,
2002
+ getAttributionData,
2003
+ flush,
2004
+ getStatus,
2005
+ addUTMToURL: src_addUTMToURL,
2006
+ getCurrentUTMParams,
2007
+ destroy,
2008
+ EventType: EventType,
2009
+ Currency: Currency,
2010
+ AttributionSDK: AttributionSDKStatic, // 包含带静态方法的AttributionSDK类
2011
+ });
2012
+
2013
+ __webpack_exports__ = __webpack_exports__["default"];
2014
+ /******/ return __webpack_exports__;
2015
+ /******/ })()
2016
+ ;
2017
+ });