getu-attribution-v2-sdk 0.3.6 → 0.3.7
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/core/AttributionSDK.d.ts +1 -0
- package/dist/core/AttributionSDK.d.ts.map +1 -1
- package/dist/core/AttributionSDK.js +36 -3
- package/dist/getuai-attribution.min.js +1 -1
- package/dist/index.esm.js +202 -4
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +202 -4
- package/dist/types/index.d.ts +13 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/index.d.ts +14 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +166 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -58,6 +58,7 @@ export declare class AttributionSDK {
|
|
|
58
58
|
addUTMToURL(url: string): string;
|
|
59
59
|
getCurrentUTMParams(): Record<string, string>;
|
|
60
60
|
private getUTMParams;
|
|
61
|
+
private getAdParams;
|
|
61
62
|
private initializeUserId;
|
|
62
63
|
setUserId(userId: string): void;
|
|
63
64
|
getUserId(): string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AttributionSDK.d.ts","sourceRoot":"","sources":["../../src/core/AttributionSDK.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EAET,SAAS,EACT,eAAe,EAEf,QAAQ,EACR,WAAW,EAGX,UAAU,EAEX,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"AttributionSDK.d.ts","sourceRoot":"","sources":["../../src/core/AttributionSDK.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EAET,SAAS,EACT,eAAe,EAEf,QAAQ,EACR,WAAW,EAGX,UAAU,EAEX,MAAM,UAAU,CAAC;AAyBlB,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,UAAU,CAAkB;IACpC,OAAO,CAAC,KAAK,CAAoB;IACjC,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,gBAAgB,CAAkB;IAC1C,OAAO,CAAC,kBAAkB,CAAkC;IAC5D,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,oBAAoB,CAAuC;IACnE,OAAO,CAAC,wBAAwB,CAAkB;IAElD,OAAO,CAAC,kBAAkB,CAAkB;IAC5C,OAAO,CAAC,eAAe,CAAc;IACrC,OAAO,CAAC,iBAAiB,CAAyC;IAClE,OAAO,CAAC,oBAAoB,CAA4C;IACxE,OAAO,CAAC,eAAe,CAAiD;gBAE5D,MAAM,EAAE,SAAS;IA+CvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAqFrB,UAAU,CACd,SAAS,EAAE,SAAS,EACpB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC/B,gBAAgB,CAAC,EAAE,MAAM,EACzB,OAAO,CAAC,EAAE,MAAM,EAChB,QAAQ,GAAE,QAAuB,EACjC,iBAAiB,CAAC,EAAE,MAAM,GACzB,OAAO,CAAC,IAAI,CAAC;IA0DV,gBAAgB,CACpB,eAAe,EAAE,MAAM,EACvB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC/B,gBAAgB,CAAC,EAAE,MAAM,EACzB,OAAO,CAAC,EAAE,MAAM,EAChB,QAAQ,GAAE,QAAuB,GAChC,OAAO,CAAC,IAAI,CAAC;IAgBV,aAAa,CACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC9B,gBAAgB,CAAC,EAAE,MAAM,GACxB,OAAO,CAAC,IAAI,CAAC;IAoChB,OAAO,CAAC,yBAAyB;IASjC,OAAO,CAAC,qBAAqB;YAmBf,aAAa;IAsBrB,aAAa,CACjB,gBAAgB,EAAE,MAAM,EACxB,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,QAAuB,EACjC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GACjC,OAAO,CAAC,IAAI,CAAC;IAWV,UAAU,CACd,gBAAgB,EAAE,MAAM,EACxB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC9B,OAAO,CAAC,IAAI,CAAC;IAKV,WAAW,CACf,gBAAgB,EAAE,MAAM,EACxB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC/B,OAAO,CAAC,IAAI,CAAC;IAKV,eAAe,CACnB,gBAAgB,CAAC,EAAE,MAAM,EACzB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC7B,OAAO,CAAC,IAAI,CAAC;IAKV,cAAc,CAClB,gBAAgB,CAAC,EAAE,MAAM,EACzB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC9B,OAAO,CAAC,IAAI,CAAC;IAKV,sBAAsB,CAC1B,gBAAgB,EAAE,MAAM,EACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GACrC,OAAO,CAAC,IAAI,CAAC;IASV,kBAAkB,CACtB,gBAAgB,EAAE,MAAM,EACxB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC9B,OAAO,CAAC,IAAI,CAAC;IASV,iBAAiB,CAAC,OAAO,EAAE;QAC/B,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,CAAC,EAAE,QAAQ,CAAC;QACpB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACnC,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBX,cAAc,CAAC,OAAO,EAAE;QAC5B,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAChC,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,GAAG,OAAO,CAAC,IAAI,CAAC;IAYX,eAAe,CAAC,OAAO,EAAE;QAC7B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACjC,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,GAAG,OAAO,CAAC,IAAI,CAAC;IAYX,0BAA0B,CAAC,OAAO,EAAE;QACxC,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACvC,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBX,gBAAgB,CACpB,gBAAgB,CAAC,EAAE,MAAM,EACzB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAChC,OAAO,CAAC,IAAI,CAAC;IASV,cAAc,CAClB,gBAAgB,CAAC,EAAE,MAAM,EACzB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC7B,OAAO,CAAC,IAAI,CAAC;IAKV,cAAc,CAClB,gBAAgB,CAAC,EAAE,MAAM,EACzB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC9B,OAAO,CAAC,IAAI,CAAC;IAKV,gBAAgB,CACpB,UAAU,CAAC,EAAE,MAAM,EACnB,gBAAgB,CAAC,EAAE,MAAM,EACzB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC9B,OAAO,CAAC,IAAI,CAAC;IAShB,kBAAkB,IAAI,eAAe,GAAG,IAAI;IAK5C,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAmBhC,mBAAmB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAa7C,OAAO,CAAC,YAAY;IAkDpB,OAAO,CAAC,WAAW;IA0BnB,OAAO,CAAC,gBAAgB;IAqBxB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAW/B,SAAS,IAAI,MAAM,GAAG,IAAI;IAK1B,YAAY,IAAI,IAAI;IAKpB;;;;;;;;;;;;;OAaG;IACG,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAgClE;;OAEG;IACH,aAAa,IAAI,UAAU,GAAG,IAAI;IAIlC;;OAEG;IACH,YAAY,IAAI,MAAM,GAAG,IAAI;IAK7B,OAAO,CAAC,iBAAiB;IA4BzB,OAAO,CAAC,sBAAsB;IA8D9B,OAAO,CAAC,iBAAiB;IAazB,OAAO,CAAC,iBAAiB;IAkCzB,OAAO,CAAC,mBAAmB;IAiK3B,OAAO,CAAC,eAAe;IA6BvB,OAAO,CAAC,qBAAqB;IAU7B,OAAO,CAAC,kBAAkB;IAW1B;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAwCzB,OAAO,CAAC,iBAAiB;IAkBzB,OAAO,CAAC,oBAAoB;IAqD5B,OAAO,CAAC,oBAAoB;IAY5B,OAAO,CAAC,uBAAuB;IAa/B,OAAO,CAAC,wBAAwB;IAWhC,OAAO,CAAC,aAAa;YAwCP,kBAAkB;IAiBhC,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,gBAAgB;IAmFxB,OAAO,CAAC,kBAAkB;IAwB1B,OAAO,CAAC,qBAAqB;IAQvB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAK5B,SAAS,IAAI;QACX,WAAW,EAAE,OAAO,CAAC;QACrB,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;QAC5B,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,OAAO,CAAC;QAChB,cAAc,EAAE;YACd,OAAO,EAAE,OAAO,CAAC;YACjB,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;SACvC,CAAC;QACF,WAAW,EAAE;YACX,OAAO,EAAE,OAAO,CAAC;YACjB,WAAW,EAAE,MAAM,CAAC;SACrB,CAAC;KACH;IAkBD,OAAO,IAAI,IAAI;CAOhB"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { EventType, Currency, defaultEndpoint, DEFAULT_FIELD_MAPPINGS, } from "../types";
|
|
2
2
|
import { AttributionStorageManager } from "../storage";
|
|
3
3
|
import { EventQueueManager, EventHttpClient } from "../queue";
|
|
4
|
-
import { generateId, getTimestamp, extractUTMParams, getCurrentUrl, getReferrer, getUserAgent, getPageTitle, generateSessionId, isOnline, ConsoleLogger, addUTMToURL, shouldExcludeDomain, isExternalURL, filterUTMParams, cleanURL, getQueryString, getQueryParams, } from "../utils";
|
|
4
|
+
import { generateId, getTimestamp, extractUTMParams, extractAdParams, detectDevice, getCurrentUrl, getReferrer, getUserAgent, getPageTitle, generateSessionId, isOnline, ConsoleLogger, addUTMToURL, shouldExcludeDomain, isExternalURL, filterUTMParams, cleanURL, getQueryString, getQueryParams, } from "../utils";
|
|
5
5
|
export class AttributionSDK {
|
|
6
6
|
constructor(config) {
|
|
7
7
|
this.session = null;
|
|
@@ -148,6 +148,8 @@ export class AttributionSDK {
|
|
|
148
148
|
? { custom_event_name }
|
|
149
149
|
: {}),
|
|
150
150
|
...this.getUTMParams(),
|
|
151
|
+
device: detectDevice(),
|
|
152
|
+
...this.getAdParams(),
|
|
151
153
|
};
|
|
152
154
|
this.logger.debug(`Tracking event: ${eventType}`, event);
|
|
153
155
|
// Add to queue
|
|
@@ -391,6 +393,25 @@ export class AttributionSDK {
|
|
|
391
393
|
this.logger.debug("UTM params for event:", filteredParams);
|
|
392
394
|
return filteredParams;
|
|
393
395
|
}
|
|
396
|
+
// Get ad-platform tracking params (adgroup_id, ad_id, click_id) from stored
|
|
397
|
+
// last-touch attribution data. These flow alongside UTM params and are
|
|
398
|
+
// captured from ad platform click URLs (Google Ads, Meta, TikTok, etc.).
|
|
399
|
+
getAdParams() {
|
|
400
|
+
const attributionData = this.getAttributionData();
|
|
401
|
+
if (!attributionData) {
|
|
402
|
+
return {};
|
|
403
|
+
}
|
|
404
|
+
const last = attributionData.lastTouch;
|
|
405
|
+
const nonEmpty = (v) => v && typeof v === "string" && v.trim() !== "" ? v : null;
|
|
406
|
+
return {
|
|
407
|
+
adgroup_id: nonEmpty(last.adgroup_id),
|
|
408
|
+
ad_id: nonEmpty(last.ad_id),
|
|
409
|
+
adgroup_id_source: nonEmpty(last.adgroup_id_source),
|
|
410
|
+
ad_id_source: nonEmpty(last.ad_id_source),
|
|
411
|
+
click_id: nonEmpty(last.click_id),
|
|
412
|
+
click_id_source: nonEmpty(last.click_id_source),
|
|
413
|
+
};
|
|
414
|
+
}
|
|
394
415
|
// Initialize user ID
|
|
395
416
|
initializeUserId() {
|
|
396
417
|
// If userId is provided in config, set it (will override existing)
|
|
@@ -504,10 +525,16 @@ export class AttributionSDK {
|
|
|
504
525
|
extractAndStoreUTMData() {
|
|
505
526
|
const currentUrl = getCurrentUrl();
|
|
506
527
|
const utmParams = extractUTMParams(currentUrl);
|
|
528
|
+
const adParams = extractAdParams(currentUrl);
|
|
507
529
|
this.logger.debug("Extracting UTM params from URL:", currentUrl);
|
|
508
530
|
this.logger.debug("Found UTM params:", utmParams);
|
|
509
|
-
|
|
510
|
-
|
|
531
|
+
this.logger.debug("Found ad params:", adParams);
|
|
532
|
+
// Capture if UTM, ad IDs, or click IDs are present
|
|
533
|
+
if (Object.keys(utmParams).length === 0 &&
|
|
534
|
+
!adParams.adgroup_id &&
|
|
535
|
+
!adParams.ad_id &&
|
|
536
|
+
!adParams.click_id) {
|
|
537
|
+
this.logger.debug("No UTM, ad, or click parameters found in URL");
|
|
511
538
|
return;
|
|
512
539
|
}
|
|
513
540
|
const utmData = {
|
|
@@ -516,6 +543,12 @@ export class AttributionSDK {
|
|
|
516
543
|
utm_campaign: utmParams.utm_campaign || "",
|
|
517
544
|
utm_term: utmParams.utm_term || "",
|
|
518
545
|
utm_content: utmParams.utm_content || "",
|
|
546
|
+
adgroup_id: adParams.adgroup_id || "",
|
|
547
|
+
ad_id: adParams.ad_id || "",
|
|
548
|
+
adgroup_id_source: adParams.adgroup_id_source || "",
|
|
549
|
+
ad_id_source: adParams.ad_id_source || "",
|
|
550
|
+
click_id: adParams.click_id || "",
|
|
551
|
+
click_id_source: adParams.click_id_source || "",
|
|
519
552
|
timestamp: Date.now(),
|
|
520
553
|
};
|
|
521
554
|
const existingData = this.getAttributionData();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.GetuAIAttribution=e():t.GetuAIAttribution=e()}(this,()=>(()=>{"use strict";var t,e,i={d:(t,e)=>{for(var r in e)i.o(e,r)&&!i.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:e[r]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},r={};i.d(r,{default:()=>it}),function(t){t.PAGE_VIEW="page_view",t.PAGE_CLICK="page_click",t.BUTTON_CLICK="button_click",t.VIDEO_PLAY="video_play",t.FORM_SUBMIT="form_submit",t.EMAIL_VERIFICATION="email_verification",t.LOGIN="login",t.SIGNUP="signup",t.PRODUCT_VIEW="product_view",t.ADD_TO_CART="add_to_cart",t.PURCHASE="purchase",t.AUDIT_APPROVED="audit_approved",t.CUSTOM="custom"}(t||(t={})),function(t){t.USD="USD"}(e||(e={}));const n="https://attribution.getu.ai/attribution/api",s=[t.PURCHASE,t.LOGIN,t.SIGNUP,t.FORM_SUBMIT,t.EMAIL_VERIFICATION,t.AUDIT_APPROVED],a={email:["email","e-mail","mail","user_email","email_address"],name:["name","full_name","fullname","username","display_name"],first_name:["first_name","firstname","fname","given_name"],last_name:["last_name","lastname","lname","surname","family_name"],phone:["phone","telephone","mobile","phone_number","tel"],company_name:["company","company_name","organization","org","business"],title:["title","job_title","position","role","job"]};function o(){return Math.floor(Date.now()/1e3)}function c(){try{const t="__localStorage_test__";return localStorage.setItem(t,t),localStorage.removeItem(t),!0}catch{return!1}}class u{constructor(t=!0){this.enabled=t}debug(t,...e){this.enabled&&console.debug&&console.debug(`[GetuAI Debug] ${t}`,...e)}info(t,...e){this.enabled&&console.info&&console.info(`[GetuAI Info] ${t}`,...e)}warn(t,...e){this.enabled&&console.warn&&console.warn(`[GetuAI Warn] ${t}`,...e)}error(t,...e){this.enabled&&console.error&&console.error(`[GetuAI Error] ${t}`,...e)}}function l(){return document.referrer||""}function d(){return window.location.href}function h(){return document.title||""}function g(t,e){try{const i=new URL(t);return Object.entries(e).forEach(([t,e])=>{e&&!i.searchParams.has(t)&&i.searchParams.set(t,e)}),i.toString()}catch(e){return t}}function m(t,e=["utm_source","utm_medium","utm_campaign"]){const i={};return e.forEach(e=>{t[e]&&(i[e]=t[e])}),i}function p(t){try{const e=t||window.location.href;return new URL(e).search}catch(t){return""}}function f(t){try{const e=t||window.location.href,i=new URL(e),r={};return i.searchParams.forEach((t,e)=>{r[e]=t}),r}catch(t){return{}}}class w{constructor(t){this.logger=t}get(t){try{if(!c())return this.logger.warn("LocalStorage not supported"),null;const e=localStorage.getItem(t);return null===e?null:JSON.parse(e)}catch(t){return this.logger.error("Error reading from localStorage:",t),null}}set(t,e){try{if(!c())return void this.logger.warn("LocalStorage not supported");localStorage.setItem(t,JSON.stringify(e))}catch(t){this.logger.error("Error writing to localStorage:",t),this.handleQuotaExceeded()}}remove(t){try{c()&&localStorage.removeItem(t)}catch(t){this.logger.error("Error removing from localStorage:",t)}}clear(){try{c()&&localStorage.clear()}catch(t){this.logger.error("Error clearing localStorage:",t)}}handleQuotaExceeded(){try{const t=Object.keys(localStorage).filter(t=>t.startsWith("attribution_"));if(t.length>0){t.sort((t,e)=>{const i=this.get(t),r=this.get(e);return(i?.expiresAt||0)-(r?.expiresAt||0)});const e=Math.ceil(.2*t.length);t.slice(0,e).forEach(t=>{this.remove(t)}),this.logger.info(`Cleaned up ${e} old attribution records`)}}catch(t){this.logger.error("Error during quota cleanup:",t)}}}class y{constructor(t){this.dbName="attribution_events",this.dbVersion=1,this.storeName="events",this.db=null,this.logger=t}async init(){if("indexedDB"in window)return new Promise((t,e)=>{const i=indexedDB.open(this.dbName,this.dbVersion);i.onerror=()=>{this.logger.error("Failed to open IndexedDB:",i.error),e(i.error)},i.onsuccess=()=>{this.db=i.result,this.logger.info("IndexedDB initialized successfully"),t()},i.onupgradeneeded=t=>{const e=t.target.result;if(!e.objectStoreNames.contains(this.storeName)){const t=e.createObjectStore(this.storeName,{keyPath:"id",autoIncrement:!0});t.createIndex("timestamp","timestamp",{unique:!1}),t.createIndex("sent","sent",{unique:!1}),t.createIndex("queued_at","queued_at",{unique:!1})}}});this.logger.warn("IndexedDB not supported")}async addEvent(t){if(!this.db)throw new Error("IndexedDB not initialized");return new Promise((e,i)=>{const r=this.db.transaction([this.storeName],"readwrite").objectStore(this.storeName),n={...t,queued_at:Date.now(),sent:!1},s=r.add(n);s.onsuccess=()=>{this.logger.debug("Event added to IndexedDB queue"),e()},s.onerror=()=>{this.logger.error("Failed to add event to IndexedDB:",s.error),i(s.error)}})}async getUnsentEvents(t=100){return this.db?new Promise((e,i)=>{const r=this.db.transaction([this.storeName],"readonly").objectStore(this.storeName).index("sent").getAll(IDBKeyRange.only(!1),t);r.onsuccess=()=>{const t=r.result.map(t=>{const{queued_at:e,sent:i,...r}=t;return r});e(t)},r.onerror=()=>{this.logger.error("Failed to get unsent events:",r.error),i(r.error)}}):[]}async markEventsAsSent(t){if(this.db&&0!==t.length)return new Promise((e,i)=>{const r=this.db.transaction([this.storeName],"readwrite").objectStore(this.storeName);let n=0,s=!1;t.forEach(a=>{const o=r.get(a);o.onsuccess=()=>{if(o.result){const a={...o.result,sent:!0},c=r.put(a);c.onsuccess=()=>{n++,n!==t.length||s||e()},c.onerror=()=>{s=!0,this.logger.error("Failed to mark event as sent:",c.error),i(c.error)}}else n++,n!==t.length||s||e()},o.onerror=()=>{s=!0,this.logger.error("Failed to get event for marking as sent:",o.error),i(o.error)}})})}async cleanupOldEvents(t=6048e5){if(!this.db)return;const e=Date.now()-t;return new Promise((t,i)=>{const r=this.db.transaction([this.storeName],"readwrite").objectStore(this.storeName).index("queued_at").openCursor(IDBKeyRange.upperBound(e));r.onsuccess=()=>{const e=r.result;e?(e.delete(),e.continue()):(this.logger.info("Old events cleanup completed"),t())},r.onerror=()=>{this.logger.error("Failed to cleanup old events:",r.error),i(r.error)}})}async getQueueSize(){return this.db?new Promise((t,e)=>{const i=this.db.transaction([this.storeName],"readonly").objectStore(this.storeName).count();i.onsuccess=()=>{t(i.result)},i.onerror=()=>{this.logger.error("Failed to get queue size:",i.error),e(i.error)}}):0}async clear(){if(this.db)return new Promise((t,e)=>{const i=this.db.transaction([this.storeName],"readwrite").objectStore(this.storeName).clear();i.onsuccess=()=>{this.logger.info("IndexedDB queue cleared"),t()},i.onerror=()=>{this.logger.error("Failed to clear IndexedDB queue:",i.error),e(i.error)}})}}class _{constructor(t){this.UTM_STORAGE_KEY="attribution_utm_data",this.SESSION_STORAGE_KEY="attribution_session",this.USER_ID_KEY="getuai_user_id",this.PENDING_EVENTS_KEY="getuai_pending_events_v1",this.USER_TRAITS_KEY="getuai_user_traits",this.USER_ID_COOKIE_EXPIRES=365,this.logger=t,this.localStorage=new w(t),this.indexedDB=new y(t)}setUserId(t){if(!t||""===t.trim())return void this.logger.warn("Cannot set empty user ID");const e=t.trim();try{this.setCookie(this.USER_ID_KEY,e,this.USER_ID_COOKIE_EXPIRES),this.logger.debug(`👤 User ID stored in cookie: ${e}`)}catch(t){this.logger.error("Failed to store user ID in cookie:",t)}try{"undefined"!=typeof localStorage&&(localStorage.setItem(this.USER_ID_KEY,e),this.logger.debug(`👤 User ID stored in localStorage: ${e}`))}catch(t){this.logger.error("Failed to store user ID in localStorage:",t)}}getUserId(){try{if("undefined"!=typeof localStorage){const t=localStorage.getItem(this.USER_ID_KEY);if(t)return t}}catch(t){this.logger.debug("Failed to get user ID from localStorage:",t)}try{const t=this.getCookie(this.USER_ID_KEY);if(t){try{"undefined"!=typeof localStorage&&localStorage.setItem(this.USER_ID_KEY,t)}catch(t){}return t}}catch(t){this.logger.debug("Failed to get user ID from cookie:",t)}return null}removeUserId(){try{this.deleteCookie(this.USER_ID_KEY),this.logger.debug("👤 User ID removed from cookie")}catch(t){this.logger.error("Failed to remove user ID from cookie:",t)}try{"undefined"!=typeof localStorage&&(localStorage.removeItem(this.USER_ID_KEY),this.logger.debug("👤 User ID removed from localStorage"))}catch(t){this.logger.error("Failed to remove user ID from localStorage:",t)}}setCookie(t,e,i){try{const r=new Date;r.setTime(r.getTime()+24*i*60*60*1e3);const n=`${t}=${encodeURIComponent(e)};expires=${r.toUTCString()};path=/;SameSite=Lax`;document.cookie=n}catch(t){this.logger.error("Failed to set cookie:",t)}}getCookie(t){try{const e=t+"=",i=document.cookie.split(";");for(let t=0;t<i.length;t++){let r=i[t];for(;" "===r.charAt(0);)r=r.substring(1,r.length);if(0===r.indexOf(e))return decodeURIComponent(r.substring(e.length,r.length))}return null}catch(t){return this.logger.error("Failed to get cookie:",t),null}}deleteCookie(t){try{document.cookie=`${t}=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/;`}catch(t){this.logger.error("Failed to delete cookie:",t)}}async init(){await this.indexedDB.init()}getPendingEvents(){try{const t=this.localStorage.get(this.PENDING_EVENTS_KEY);return Array.isArray(t)?t:[]}catch(t){return this.logger.debug("Failed to read pending events:",t),[]}}appendPendingEvents(t){if(t&&0!==t.length)try{const e=[...this.getPendingEvents(),...t].slice(-500);this.localStorage.set(this.PENDING_EVENTS_KEY,e)}catch(t){this.logger.debug("Failed to append pending events:",t)}}removePendingEventsByIds(t){if(t&&0!==t.length)try{const e=this.getPendingEvents();if(0===e.length)return;const i=new Set(t),r=e.filter(t=>!i.has(t.event_id));0===r.length?this.localStorage.remove(this.PENDING_EVENTS_KEY):this.localStorage.set(this.PENDING_EVENTS_KEY,r)}catch(t){this.logger.debug("Failed to remove pending events:",t)}}clearPendingEvents(){try{this.localStorage.remove(this.PENDING_EVENTS_KEY)}catch(t){this.logger.debug("Failed to clear pending events:",t)}}storeUTMData(t){try{const e=this.getUTMData(),i={utm_source:"",utm_medium:"",utm_campaign:"",utm_term:"",utm_content:"",timestamp:Date.now()},r={firstTouch:e?.firstTouch||i,lastTouch:e?.lastTouch||i,touchpoints:e?.touchpoints||[],...t,expiresAt:Date.now()+2592e6};this.localStorage.set(this.UTM_STORAGE_KEY,r),this.logger.debug("UTM data stored successfully:",{firstTouch:r.firstTouch,lastTouch:r.lastTouch,touchpointsCount:r.touchpoints.length,expiresAt:new Date(r.expiresAt).toISOString()})}catch(t){this.logger.error("Failed to store UTM data:",t)}}getUTMData(){const t=this.localStorage.get(this.UTM_STORAGE_KEY);return t&&t.expiresAt&&t.expiresAt>Date.now()?t:(t&&this.localStorage.remove(this.UTM_STORAGE_KEY),null)}storeSession(t){this.localStorage.set(this.SESSION_STORAGE_KEY,t)}getSession(){return this.localStorage.get(this.SESSION_STORAGE_KEY)}async queueEvent(t){try{await this.indexedDB.addEvent(t)}catch(t){throw this.logger.error("Failed to queue event:",t),t}}async getUnsentEvents(t=100){return await this.indexedDB.getUnsentEvents(t)}async markEventsAsSent(t){await this.indexedDB.markEventsAsSent(t)}async getQueueSize(){return await this.indexedDB.getQueueSize()}async cleanupOldEvents(){await this.indexedDB.cleanupOldEvents()}async clearQueue(){await this.indexedDB.clear()}cleanupExpiredData(){this.getUTMData()}setUserTraits(t){try{const e={...this.getUserTraits()||{},...t};"undefined"!=typeof localStorage&&(localStorage.setItem(this.USER_TRAITS_KEY,JSON.stringify(e)),this.logger.debug("User traits stored:",e))}catch(t){this.logger.error("Failed to store user traits:",t)}}getUserTraits(){try{if("undefined"!=typeof localStorage){const t=localStorage.getItem(this.USER_TRAITS_KEY);return t?JSON.parse(t):null}return null}catch(t){return this.logger.error("Failed to get user traits:",t),null}}clearUserTraits(){try{"undefined"!=typeof localStorage&&(localStorage.removeItem(this.USER_TRAITS_KEY),this.logger.debug("User traits cleared"))}catch(t){this.logger.error("Failed to clear user traits:",t)}}}const v="0.3.6";class S{constructor(t,e,i,r=100,n=2e3,s=3,a=1e3,o){this.queue=[],this.processing=!1,this.batchTimer=null,this.retryCounts=new Map,this.logger=t,this.apiKey=e,this.apiEndpoint=i,this.batchSize=r,this.batchInterval=n,this.maxRetries=s,this.retryDelay=a,this.sendEvents=o,this.debouncedProcess=function(t){let e;return(...i)=>{clearTimeout(e),e=setTimeout(()=>t(...i),100)}}(this.process.bind(this))}add(t){if(s.includes(t.event_type))return this.logger.debug(`Immediate event detected: ${t.event_type}, sending immediately`),void this.processImmediate(t);this.queue.push(t),this.logger.debug(`Event added to queue: ${t.event_type}`),this.scheduleBatchProcessing()}async process(){if(this.processing||0===this.queue.length)return;this.processing=!0;const t=this.queue.splice(0,this.batchSize);try{this.logger.debug(`Processing ${t.length} events from queue`),await this.sendEvents(t),this.resetRetryCounts(t),this.logger.info(`Successfully processed ${t.length} events`)}catch(e){return this.logger.error("Failed to process events:",e),this.enqueueBatchForRetry(t),void setTimeout(()=>{this.processing=!1,this.debouncedProcess()},this.retryDelay)}this.processing=!1,this.queue.length>0&&this.debouncedProcess()}async processImmediate(t){try{this.logger.debug(`Processing immediate event: ${t.event_type}`),await this.sendEvents([t]),this.resetRetryCounts([t]),this.logger.info(`Immediate event processed successfully: ${t.event_type}`)}catch(e){this.logger.error(`Failed to process immediate event: ${t.event_type}`,e),this.enqueueBatchForRetry([t])}}scheduleBatchProcessing(){this.batchTimer&&clearTimeout(this.batchTimer),this.batchTimer=setTimeout(()=>{this.debouncedProcess()},this.batchInterval)}clear(){this.queue=[],this.batchTimer&&(clearTimeout(this.batchTimer),this.batchTimer=null),this.processing=!1,this.logger.info("Event queue cleared")}size(){return this.queue.length}getStats(){return{size:this.queue.length,processing:this.processing}}async flush(){for(this.logger.info("Flushing event queue");this.queue.length>0;)await this.process()}drainAll(){const t=this.queue.splice(0,this.queue.length);return this.batchTimer&&(clearTimeout(this.batchTimer),this.batchTimer=null),this.processing=!1,t}enqueueBatchForRetry(t){const e=[];for(const i of t){const t=(this.retryCounts.get(i.event_id)||0)+1;this.retryCounts.set(i.event_id,t),t>this.maxRetries?(this.logger.warn(`Dropping event after max retries (${this.maxRetries}): ${i.event_type} (${i.event_id})`),this.retryCounts.delete(i.event_id)):e.push(i)}e.length>0&&this.queue.unshift(...e)}resetRetryCounts(t){for(const e of t)this.retryCounts.delete(e.event_id)}}class k{constructor(t,e,i,r=3,n=1e3){this.logger=t,this.apiKey=e,this.apiEndpoint=i,this.maxRetries=r,this.retryDelay=n}async sendEvents(t){if(0===t.length)return;const e={events:t.map(t=>({event_id:t.event_id,event_type:t.event_type,tracking_user_id:t.tracking_user_id,utm_source:t.utm_source||null,utm_medium:t.utm_medium||null,utm_campaign:t.utm_campaign||null,utm_term:t.utm_term||null,utm_content:t.utm_content||null,revenue:t.revenue||null,currency:t.currency||null,event_data:t.event_data||null,context:t.context||null,timestamp:t.timestamp||o(),custom_event_name:t.custom_event_name||void 0})),sdk_version:v};await async function(t,e=3,i=1e3){let r;for(let n=0;n<=e;n++)try{return await t()}catch(t){if(r=t,n===e)throw r;const s=i*Math.pow(2,n);await new Promise(t=>setTimeout(t,s))}throw r}(async()=>{const i=await fetch(`${this.apiEndpoint}/attribution/events`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`},body:JSON.stringify(e)});if(!i.ok){const t=await i.text();throw new Error(`HTTP ${i.status}: ${t}`)}const r=await i.json();return this.logger.debug("Events sent successfully:",r),{result:r,sentEvents:t}},this.maxRetries,this.retryDelay)}async sendEventsKeepalive(t){if(0===t.length)return;const e={events:t.map(t=>({event_id:t.event_id,event_type:t.event_type,tracking_user_id:t.tracking_user_id,utm_source:t.utm_source||null,utm_medium:t.utm_medium||null,utm_campaign:t.utm_campaign||null,utm_term:t.utm_term||null,utm_content:t.utm_content||null,revenue:t.revenue||null,currency:t.currency||null,event_data:t.event_data||null,context:t.context||null,timestamp:t.timestamp||o(),custom_event_name:t.custom_event_name||void 0})),sdk_version:v},i=await fetch(`${this.apiEndpoint}/attribution/events`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`},body:JSON.stringify(e),keepalive:!0});if(!i.ok)throw new Error(`HTTP ${i.status}`)}sendEventsBeacon(t){if(0===t.length)return!0;if("undefined"==typeof navigator||"function"!=typeof navigator.sendBeacon)return!1;const e={events:t.map(t=>({event_id:t.event_id,event_type:t.event_type,tracking_user_id:t.tracking_user_id,utm_source:t.utm_source||null,utm_medium:t.utm_medium||null,utm_campaign:t.utm_campaign||null,utm_term:t.utm_term||null,utm_content:t.utm_content||null,revenue:t.revenue||null,currency:t.currency||null,event_data:t.event_data||null,context:t.context||null,timestamp:t.timestamp||o(),custom_event_name:t.custom_event_name||void 0})),sdk_version:v},i=`${this.apiEndpoint}/attribution/events_beacon?api_key=${encodeURIComponent(this.apiKey)}`,r=new Blob([JSON.stringify(e)],{type:"text/plain"});return navigator.sendBeacon(i,r)}async sendSingleEvent(t){await this.sendEvents([t])}async testConnection(){try{return(await fetch(`${this.apiEndpoint}/health`,{method:"GET",headers:{Authorization:`Bearer ${this.apiKey}`}})).ok}catch(t){return this.logger.error("Connection test failed:",t),!1}}}class E{constructor(t){this.session=null,this.initialized=!1,this.autoTrackEnabled=!1,this.pageViewTrackTimes=new Map,this.cachedPublicIP=null,this.publicIPFetchPromise=null,this.initialPageViewTriggered=!1,this.spaTrackingEnabled=!1,this.lastTrackedPath="",this.originalPushState=null,this.originalReplaceState=null,this.popstateHandler=null,this.config={apiEndpoint:n,batchSize:100,batchInterval:2e3,maxRetries:3,retryDelay:1e3,enableDebug:!1,autoTrack:!1,autoTrackPageView:!1,sessionTimeout:18e5,enableCrossDomainUTM:!0,crossDomainUTMParams:["utm_source","utm_medium","utm_campaign","utm_term","utm_content"],excludeDomains:[],autoCleanUTM:!0,pageViewDebounceInterval:5e3,...t},this.logger=new u(this.config.enableDebug),this.storage=new _(this.logger),this.httpClient=new k(this.logger,this.config.apiKey,this.config.apiEndpoint||n,this.config.maxRetries,this.config.retryDelay),this.queue=new S(this.logger,this.config.apiKey,this.config.apiEndpoint||n,this.config.batchSize,this.config.batchInterval,this.config.maxRetries,this.config.retryDelay,t=>this.httpClient.sendEvents(t))}async init(){if(this.initialized)this.logger.warn("SDK already initialized");else try{this.logger.info("Initializing GetuAI Attribution SDK"),this.storage.init().catch(t=>{this.logger.warn("Storage initialization failed (IndexedDB may be unavailable), continuing with basic features:",t)}),this.initializeUserId(),this.initializeSession(),this.extractAndStoreUTMData(),this.config.autoTrack&&this.setupAutoTracking(),this.setupNetworkHandlers(),this.setupVisibilityHandlers(),this.setupBeforeUnloadHandler(),this.initialized=!0,this.logger.info("🚀 GetuAI Attribution SDK initialized successfully"),this.logger.info("📄 Auto track page view = "+this.config.autoTrackPageView),this.retryPendingEvents().catch(t=>{this.logger.warn("⚠️ Pending events retry failed",t)}),this.config.autoTrackPageView&&(this.logger.info("📄 Auto track page view enabled (including SPA route tracking)"),this.lastTrackedPath=this.getCurrentPath(),this.setupSPATracking(),this.initialPageViewTriggered||(this.initialPageViewTriggered=!0,Promise.resolve().then(()=>this.trackPageView()).then(()=>this.queue.process()).then(()=>{this.logger.info("✅ Auto track page view completed")}).catch(t=>this.logger.error("❌ Auto track page view failed:",t))))}catch(t){throw this.logger.error("Failed to initialize SDK:",t),t}}async trackEvent(i,r,n,s,a=e.USD,c){if(this.initialized)try{const e=d(),u=this.cachedPublicIP;this.ensurePublicIPFetched();const g={domain:"undefined"!=typeof window?window.location.hostname:null,path:"undefined"!=typeof window?window.location.pathname:null,title:h(),referrer:l(),url:e.split("?")[0],querystring:p(e),query_params:f(e),ip_address:u},m={session_id:this.session?.sessionId,start_time:this.session?.startTime,last_activity:this.session?.lastActivity,page_views:this.session?.pageViews},w=n||this.getUserId(),y={event_id:"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(t){const e=16*Math.random()|0;return("x"===t?e:3&e|8).toString(16)}),event_type:i,tracking_user_id:w||void 0,timestamp:o(),event_data:r,context:{page:g,session:m},revenue:s,currency:a,...i===t.CUSTOM&&c?{custom_event_name:c}:{},...this.getUTMParams()};this.logger.debug(`Tracking event: ${i}`,y),this.queue.add(y)}catch(t){this.logger.error(`Failed to track event ${i}:`,t)}else this.logger.warn("SDK not initialized, event not tracked")}async trackCustomEvent(i,r,n,s,a=e.USD){i&&""!==i.trim()?await this.trackEvent(t.CUSTOM,r,n,s,a,i.trim()):this.logger.error("trackCustomEvent requires a non-empty customEventName")}async trackPageView(e,i){const r=d(),n=r.split("?")[0],s=Date.now(),a=this.config.pageViewDebounceInterval||5e3,o=this.pageViewTrackTimes.get(n);if(o&&s-o<a)return void this.logger.debug(`Page view debounced: ${n} (last tracked ${s-o}ms ago)`);const c={url:r,title:h(),referrer:l(),user_agent:navigator.userAgent||"",...e};await this.trackEvent(t.PAGE_VIEW,c,i),this.pageViewTrackTimes.set(n,s),this.cleanupPageViewTrackTimes()}cleanupPageViewTrackTimes(){const t=Date.now()-36e5;for(const[e,i]of this.pageViewTrackTimes.entries())i<t&&this.pageViewTrackTimes.delete(e)}ensurePublicIPFetched(){this.cachedPublicIP||this.publicIPFetchPromise||(this.publicIPFetchPromise=this.fetchPublicIP().then(t=>(t&&(this.cachedPublicIP=t),t)).catch(()=>null).finally(()=>{this.publicIPFetchPromise=null}))}async fetchPublicIP(){try{const t=new AbortController,e=setTimeout(()=>t.abort(),2e3),i=await fetch("https://api.ipify.org?format=json",{signal:t.signal,headers:{Accept:"application/json"}});if(clearTimeout(e),!i.ok)return null;const r=await i.json();return"string"==typeof r?.ip?r.ip:null}catch(t){return this.logger.debug("Public IP fetch failed",t),null}}async trackPurchase(i,r,n=e.USD,s){await this.trackEvent(t.PURCHASE,s,i,r,n)}async trackLogin(e,i){await this.trackEvent(t.LOGIN,i,e)}async trackSignup(e,i){await this.trackEvent(t.SIGNUP,i,e)}async trackFormSubmit(e,i){await this.trackEvent(t.FORM_SUBMIT,i,e)}async trackVideoPlay(e,i){await this.trackEvent(t.VIDEO_PLAY,i,e)}async trackEmailVerification(e,i){await this.trackEvent(t.EMAIL_VERIFICATION,i,e)}async trackAuditApproved(e,i){await this.trackEvent(t.AUDIT_APPROVED,i,e)}async trackPurchaseAuto(i){const r=i.tracking_user_id||this.getUserId();r?await this.trackEvent(t.PURCHASE,i.purchaseData,r,i.revenue,i.currency||e.USD):this.logger.error("❌ trackPurchaseAuto requires tracking_user_id. Please set user ID using setUserId() or pass it in options.tracking_user_id.")}async trackLoginAuto(e){const i=e.tracking_user_id||this.getUserId();i?await this.trackEvent(t.LOGIN,e.loginData,i):this.logger.error("❌ trackLoginAuto requires tracking_user_id. Please set user ID using setUserId() or pass it in options.tracking_user_id.")}async trackSignupAuto(e){const i=e.tracking_user_id||this.getUserId();i?await this.trackEvent(t.SIGNUP,e.signupData,i):this.logger.error("❌ trackSignupAuto requires tracking_user_id. Please set user ID using setUserId() or pass it in options.tracking_user_id.")}async trackEmailVerificationAuto(e){const i=e.tracking_user_id||this.getUserId();i?await this.trackEvent(t.EMAIL_VERIFICATION,e.verificationData,i):this.logger.error("❌ trackEmailVerificationAuto requires tracking_user_id. Please set user ID using setUserId() or pass it in options.tracking_user_id.")}async trackProductView(e,i){await this.trackEvent(t.PRODUCT_VIEW,i,e)}async trackAddToCart(e,i){await this.trackEvent(t.ADD_TO_CART,i,e)}async trackPageClick(e,i){await this.trackEvent(t.PAGE_CLICK,i,e)}async trackButtonClick(e,i,r){await this.trackEvent(t.BUTTON_CLICK,{button_name:e,...r},i)}getAttributionData(){return this.storage.getUTMData()}addUTMToURL(t){if(!this.config.enableCrossDomainUTM)return t;const e=this.getAttributionData();return e?g(t,m(e.lastTouch,this.config.crossDomainUTMParams)):t}getCurrentUTMParams(){const t=this.getAttributionData();return t?m(t.lastTouch,this.config.crossDomainUTMParams):{}}getUTMParams(){const t=this.getAttributionData();if(!t)return this.logger.debug("No attribution data available for UTM params"),{};const e={utm_source:t.lastTouch.utm_source||null,utm_medium:t.lastTouch.utm_medium||null,utm_campaign:t.lastTouch.utm_campaign||null,utm_term:t.lastTouch.utm_term||null,utm_content:t.lastTouch.utm_content||null},i={};return e.utm_source&&""!==e.utm_source.trim()?i.utm_source=e.utm_source:i.utm_source=null,e.utm_medium&&""!==e.utm_medium.trim()?i.utm_medium=e.utm_medium:i.utm_medium=null,e.utm_campaign&&""!==e.utm_campaign.trim()?i.utm_campaign=e.utm_campaign:i.utm_campaign=null,e.utm_term&&""!==e.utm_term.trim()?i.utm_term=e.utm_term:i.utm_term=null,e.utm_content&&""!==e.utm_content.trim()?i.utm_content=e.utm_content:i.utm_content=null,this.logger.debug("UTM params for event:",i),i}initializeUserId(){if(this.config.userId)this.setUserId(this.config.userId),this.logger.info(`👤 User ID initialized from config: ${this.config.userId}`);else{const t=this.getUserId();t?this.logger.debug(`👤 Existing user ID found: ${t}`):this.logger.debug("👤 No user ID found, will be set when user identifies")}}setUserId(t){t&&""!==t.trim()?(this.storage.setUserId(t),this.logger.info(`👤 User ID set: ${t}`)):this.logger.warn("Cannot set empty user ID")}getUserId(){return this.storage.getUserId()}removeUserId(){this.storage.removeUserId(),this.logger.info("👤 User ID removed")}async identify(e,i){this.initialized?e&&""!==e.trim()?(this.setUserId(e),i&&(this.storage.setUserTraits(i),this.logger.info(`User identified: ${e}`,i)),await this.trackEvent(t.FORM_SUBMIT,{_identify:!0,_user_traits:i,tracking_user_id:e},e)):this.logger.warn("Cannot identify with empty user ID"):this.logger.warn("SDK not initialized, identify call queued")}getUserTraits(){return this.storage.getUserTraits()}getSessionId(){return this.session?.sessionId||null}initializeSession(){const t=this.storage.getSession(),e=Date.now();t&&e-t.lastActivity<this.config.sessionTimeout?this.session={...t,lastActivity:e}:this.session={sessionId:`session_${Date.now()}_${Math.random().toString(36).substr(2,9)}`,startTime:e,lastActivity:e,pageViews:0},this.storage.storeSession(this.session),this.logger.debug("Session initialized:",this.session)}extractAndStoreUTMData(){const t=d(),e=function(t){const e=function(t){const e={};try{new URL(t).searchParams.forEach((t,i)=>{e[i]=t})}catch(t){}return e}(t),i={};return["utm_source","utm_medium","utm_campaign","utm_term","utm_content"].forEach(t=>{e[t]&&""!==e[t].trim()&&(i[t]=e[t].trim())}),i}(t);if(this.logger.debug("Extracting UTM params from URL:",t),this.logger.debug("Found UTM params:",e),0===Object.keys(e).length)return void this.logger.debug("No UTM parameters found in URL");const i={utm_source:e.utm_source||"",utm_medium:e.utm_medium||"",utm_campaign:e.utm_campaign||"",utm_term:e.utm_term||"",utm_content:e.utm_content||"",timestamp:Date.now()},r=this.getAttributionData(),n={firstTouch:r?.firstTouch||i,lastTouch:i,touchpoints:r?.touchpoints||[],expiresAt:Date.now()+2592e6};r&&r.lastTouch.utm_source===i.utm_source&&r.lastTouch.utm_campaign===i.utm_campaign||n.touchpoints.push(i),this.storage.storeUTMData(n),this.logger.info("UTM data extracted and stored successfully:",i),this.config.autoCleanUTM&&function(){try{const t=new URL(window.location.href);let e=!1;["utm_source","utm_medium","utm_campaign","utm_term","utm_content"].forEach(i=>{t.searchParams.has(i)&&(t.searchParams.delete(i),e=!0)}),e&&window.history.replaceState({},document.title,t.toString())}catch(t){console.warn("Failed to clean URL:",t)}}()}setupAutoTracking(){this.autoTrackEnabled=!0,this.setupFormTracking(),this.setupLinkTracking(),this.logger.info("Auto-tracking enabled")}setupFormTracking(){document.addEventListener("submit",e=>{try{const i=e.target;if(!i)return;const r=this.serializeFormFields(i),n=this.extractLeadFields(r);n.email&&(this.storage.setUserTraits(n),this.logger.debug("Lead fields extracted from form:",n)),this.trackEvent(t.FORM_SUBMIT,{...r,_lead_fields:n,form_id:i.id||i.name,form_action:i.action,form_method:i.method})}catch(t){this.logger.error("Failed to auto-track form submit:",t)}})}serializeFormFields(t){const e={};try{const i=new FormData(t);for(const[t,r]of i.entries()){const i=this.serializeFormValue(r);if(Object.prototype.hasOwnProperty.call(e,t)){const r=e[t];Array.isArray(r)?(r.push(i),e[t]=r):e[t]=[r,i]}else e[t]=i}const r=Array.from(t.elements),n=new Map;for(let t=0;t<r.length;t++){const e=r[t];if(e.disabled)continue;const i=e.tagName.toLowerCase(),s=e.type?.toLowerCase();if("button"===i||"submit"===s||"reset"===s)continue;const a=this.getFormFieldKey(e,t);a&&(n.has(a)||n.set(a,[]),n.get(a).push(e))}n.forEach((t,i)=>{const r=t.some(t=>"radio"===t.type),n=t.some(t=>"checkbox"===t.type),s=t.some(t=>"file"===t.type),a=t.some(t=>"SELECT"===t.tagName&&t.multiple),o=t.some(t=>"password"===t.type);if(n){const r=t.filter(t=>"checkbox"===t.type).filter(t=>t.checked).map(t=>t.value||"on");return void(Object.prototype.hasOwnProperty.call(e,i)?Array.isArray(e[i])||(e[i]=[e[i],...r]):e[i]=r)}if(r){const r=t.filter(t=>"radio"===t.type).find(t=>t.checked);return void(Object.prototype.hasOwnProperty.call(e,i)||(e[i]=r?r.value:null))}if(a){const r=t.find(t=>"SELECT"===t.tagName&&t.multiple);if(r){const t=Array.from(r.selectedOptions).map(t=>t.value);return void(Object.prototype.hasOwnProperty.call(e,i)||(e[i]=t))}}if(s){const r=t.find(t=>"file"===t.type);if(r){const t=r.files?Array.from(r.files):[];return void(Object.prototype.hasOwnProperty.call(e,i)||(e[i]=t.map(t=>this.serializeFormValue(t))))}}if(o)Object.prototype.hasOwnProperty.call(e,i)?e[i]="string"==typeof e[i]?"*****":e[i]:e[i]="*****";else if(!Object.prototype.hasOwnProperty.call(e,i)){const r=t[0];if("SELECT"===r.tagName){const t=r;e[i]=t.multiple?Array.from(t.selectedOptions).map(t=>t.value):t.value}else r.type,e[i]=r.value??""}})}catch(t){this.logger.error("Failed to serialize form fields:",t)}return e}getFormFieldKey(t,e){const i=t.name;if(i)return i;if(t.id)return`id_${t.id}`;const r=t.getAttribute("aria-label");if(r&&r.trim())return`aria_${this.normalizeFormFieldKey(r)}`;const n=t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement?t.placeholder:"";return n&&n.trim()?`ph_${this.normalizeFormFieldKey(n)}_${e}`:`field_${e}`}normalizeFormFieldKey(t){return t.trim().toLowerCase().replace(/\s+/g,"_").replace(/[^a-z0-9_]/g,"").slice(0,40)}serializeFormValue(t){return t instanceof File?{file_name:t.name,file_size:t.size,file_type:t.type}:t}extractLeadFields(t){const e={};for(const[i,r]of Object.entries(a))if(!e[i])for(const n of r||[]){const r=n.toLowerCase();for(const[n,s]of Object.entries(t))if((n.toLowerCase()===r||n.toLowerCase().includes(r))&&s&&"string"==typeof s&&s.trim()){e[i]=s.trim();break}if(e[i])break}return e.name||!e.first_name&&!e.last_name||(e.name=[e.first_name,e.last_name].filter(Boolean).join(" ").trim()),e}setupLinkTracking(){document.addEventListener("click",t=>{const e=t.target.closest("a");e&&function(t){try{const e=new URL(t),i=window.location.hostname;return e.hostname!==i}catch(t){return!1}}(e.href)&&this.handleCrossDomainUTM(e,t)})}handleCrossDomainUTM(t,e){if(!this.config.enableCrossDomainUTM)return;const i=t.href;if(function(t,e=[]){try{const i=new URL(t).hostname.toLowerCase();return e.some(t=>{const e=t.toLowerCase();return i===e||i.endsWith(`.${e}`)})}catch(t){return!1}}(i,this.config.excludeDomains))return void this.logger.debug(`Domain excluded from UTM passing: ${i}`);const r=this.getAttributionData();if(!r)return void this.logger.debug("No UTM data available for cross-domain passing");const n=m(r.lastTouch,this.config.crossDomainUTMParams);if(0===Object.keys(n).length)return void this.logger.debug("No UTM parameters to pass");const s=g(i,n);s!==i&&(t.href=s,this.logger.debug("UTM parameters added to external link:",{original:i,enhanced:s,utmParams:n}),this.logger.debug("Cross-domain UTM passed:",{link_url:s,original_url:i,utm_params_passed:n}))}setupNetworkHandlers(){window.addEventListener("online",()=>{this.logger.info("Network connection restored"),this.queue.flush()}),window.addEventListener("offline",()=>{this.logger.warn("Network connection lost")})}setupVisibilityHandlers(){document.addEventListener("visibilitychange",()=>{"hidden"!==document.visibilityState?"visible"===document.visibilityState&&this.updateSessionActivity():this.flushOnUnload("visibilitychange:hidden")})}setupBeforeUnloadHandler(){window.addEventListener("beforeunload",()=>{this.flushOnUnload("beforeunload")}),window.addEventListener("pagehide",()=>{this.flushOnUnload("pagehide")})}flushOnUnload(t){try{this.updateSessionActivity();const e=this.queue.drainAll();if(0===e.length)return;if(this.storage.appendPendingEvents(e),this.logger.debug(`🚚 Unload flush triggered: ${t}`,{count:e.length}),this.httpClient.sendEventsBeacon(e))return void this.storage.removePendingEventsByIds(e.map(t=>t.event_id));const i=20;for(let t=0;t<e.length;t+=i){const r=e.slice(t,t+i);this.httpClient.sendEventsKeepalive(r).then(()=>{this.storage.removePendingEventsByIds(r.map(t=>t.event_id))}).catch(t=>{this.logger.debug("Unload keepalive send failed",t)})}}catch(t){this.logger.debug("flushOnUnload failed",t)}}async retryPendingEvents(){const t=this.storage.getPendingEvents();if(0!==t.length){this.logger.info(`📦 Retrying pending events: ${t.length}`);try{await this.httpClient.sendEvents(t),this.storage.removePendingEventsByIds(t.map(t=>t.event_id)),this.logger.info(`✅ Pending events sent: ${t.length}`)}catch(t){this.logger.warn("⚠️ Pending events retry failed",t)}}}getCurrentPath(){return"undefined"==typeof window?"":window.location.pathname+window.location.search}setupSPATracking(){if("undefined"==typeof window||"undefined"==typeof history)return void this.logger.warn("⚠️ SPA tracking not available in this environment");if(this.spaTrackingEnabled)return void this.logger.warn("⚠️ SPA tracking already enabled");this.spaTrackingEnabled=!0,this.lastTrackedPath=this.getCurrentPath(),this.originalPushState=history.pushState.bind(history),this.originalReplaceState=history.replaceState.bind(history);const t=t=>{const e=this.getCurrentPath();e!==this.lastTrackedPath?(this.logger.debug(`🔄 [SPA] Route change detected (${t}): ${this.lastTrackedPath} -> ${e}`),this.lastTrackedPath=e,setTimeout(()=>{this.trackPageView().then(()=>this.queue.process()).then(()=>{this.logger.debug(`✅ [SPA] Page view tracked for: ${e}`)}).catch(t=>{this.logger.error("❌ [SPA] Failed to track page view:",t)})},100)):this.logger.debug(`🔄 [SPA] Route change detected (${t}) but path unchanged: ${e}`)};history.pushState=(e,i,r)=>{const n=this.originalPushState(e,i,r);return t("pushState"),n},history.replaceState=(e,i,r)=>{const n=this.originalReplaceState(e,i,r);return t("replaceState"),n},this.popstateHandler=()=>{t("popstate")},window.addEventListener("popstate",this.popstateHandler),this.logger.info("🔄 SPA tracking setup completed")}cleanupSPATracking(){this.spaTrackingEnabled&&(this.originalPushState&&(history.pushState=this.originalPushState,this.originalPushState=null),this.originalReplaceState&&(history.replaceState=this.originalReplaceState,this.originalReplaceState=null),this.popstateHandler&&(window.removeEventListener("popstate",this.popstateHandler),this.popstateHandler=null),this.spaTrackingEnabled=!1,this.logger.info("🔄 SPA tracking cleaned up"))}updateSessionActivity(){this.session&&(this.session.lastActivity=Date.now(),this.storage.storeSession(this.session))}async flush(){await this.queue.flush()}getStatus(){return{initialized:this.initialized,session:this.session,queueSize:this.queue.size(),online:navigator.onLine,crossDomainUTM:{enabled:this.config.enableCrossDomainUTM||!0,currentParams:this.getCurrentUTMParams()},spaTracking:{enabled:this.spaTrackingEnabled,currentPath:this.lastTrackedPath}}}destroy(){this.queue.clear(),this.autoTrackEnabled=!1,this.cleanupSPATracking(),this.initialized=!1,this.logger.info("🗑️ SDK destroyed")}}let b=null;async function T(t){if(b)return console.warn("GetuAI SDK: Already initialized"),b;try{if(b=new E(t),await b.init(),t.enableDebug&&(window.GetuAISDK=b),console.log("GetuAI Attribution SDK initialized successfully"),"undefined"!=typeof window){const t=new CustomEvent("getuaiSDKReady",{detail:{sdk:b}});window.dispatchEvent(t)}return b}catch(t){if(console.error("GetuAI SDK: Failed to initialize:",t),"undefined"!=typeof window){const e=new CustomEvent("getuaiSDKError",{detail:{error:t}});window.dispatchEvent(e)}throw t}}function I(){return b}function D(){return new Promise((t,e)=>{if(b)return void t(b);const i=e=>{window.removeEventListener("getuaiSDKReady",i),window.removeEventListener("getuaiSDKError",r),t(e.detail.sdk)},r=t=>{window.removeEventListener("getuaiSDKReady",i),window.removeEventListener("getuaiSDKError",r),e(t.detail.error)};window.addEventListener("getuaiSDKReady",i),window.addEventListener("getuaiSDKError",r),setTimeout(()=>{window.removeEventListener("getuaiSDKReady",i),window.removeEventListener("getuaiSDKError",r),e(new Error("SDK initialization timeout"))},1e4)})}async function A(t,i,r,n,s=e.USD){const a=I();a?await a.trackEvent(t,i,r,n,s):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function P(t,e){const i=I();i?await i.trackPageView(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function U(t,i,r=e.USD,n){const s=I();s?await s.trackPurchase(t,i,r,n):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function C(t,e){const i=I();i?await i.trackLogin(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function x(t,e){const i=I();i?await i.trackSignup(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function K(t,e){const i=I();i?await i.trackFormSubmit(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function N(t,e){const i=I();i?await i.trackVideoPlay(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function R(t,e){const i=I();i?await i.trackEmailVerification(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function F(t,e){const i=I();i?await i.trackAuditApproved(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function O(t){const e=I();e?await e.trackPurchaseAuto(t):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function L(t){const e=I();e?await e.trackLoginAuto(t):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function z(t){const e=I();e?await e.trackSignupAuto(t):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function M(t){const e=I();e?await e.trackEmailVerificationAuto(t):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function V(t,i,r,n,s=e.USD){const a=I();a?await a.trackCustomEvent(t,i,r,n,s):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function G(t,e){const i=I();i?await i.trackProductView(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function $(t,e){const i=I();i?await i.trackAddToCart(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function B(t,e){const i=I();i?await i.trackPageClick(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function q(t,e,i){const r=I();r?await r.trackButtonClick(t,e,i):console.warn("GetuAI SDK: Not initialized. Call init() first.")}function j(){const t=I();return t?t.getAttributionData():null}async function Y(){const t=I();t?await t.flush():console.warn("GetuAI SDK: Not initialized. Call init() first.")}function H(){const t=I();return t?t.getStatus():null}function J(t){const e=I();return e?e.addUTMToURL(t):(console.warn("GetuAI SDK: Not initialized. Call init() first."),t)}function W(){const t=I();return t?t.getCurrentUTMParams():{}}function Q(t){const e=I();e?e.setUserId(t):console.warn("GetuAI SDK: Not initialized. Call init() first.")}function X(){const t=I();return t?t.getUserId():(console.warn("GetuAI SDK: Not initialized. Call init() first."),null)}function Z(){const t=I();t?t.removeUserId():console.warn("GetuAI SDK: Not initialized. Call init() first.")}function tt(){b&&(b.destroy(),b=null,console.log("GetuAI SDK destroyed"))}class et extends E{static async init(t){return await T(t)}static async trackEvent(t,i,r,n,s=e.USD){return await A(t,i,r,n,s)}static async trackPageView(t,e){return await P(t,e)}static async trackPurchase(t,i,r=e.USD,n){return await U(t,i,r,n)}static async trackLogin(t,e){return await C(t,e)}static async trackSignup(t,e){return await x(t,e)}static async trackFormSubmit(t,e){return await K(t,e)}static async trackVideoPlay(t,e){return await N(t,e)}static async trackEmailVerification(t,e){return await R(t,e)}static async trackPurchaseAuto(t){return await O(t)}static async trackLoginAuto(t){return await L(t)}static async trackSignupAuto(t){return await z(t)}static async trackEmailVerificationAuto(t){return await M(t)}static async trackCustomEvent(t,i,r,n,s=e.USD){return await V(t,i,r,n,s)}static async trackProductView(t,e){return await G(t,e)}static async trackAddToCart(t,e){return await $(t,e)}static async trackPageClick(t,e){return await B(t,e)}static async trackButtonClick(t,e,i){return await q(t,e,i)}static getAttributionData(){return j()}static async flush(){return await Y()}static getStatus(){return H()}static addUTMToURL(t){return J(t)}static getCurrentUTMParams(){return W()}static setUserId(t){Q(t)}static getUserId(){return X()}static removeUserId(){Z()}static destroy(){tt()}static getSDK(){return I()}static waitForSDK(){return D()}}"undefined"!=typeof document&&function(){if(b)return;const t=document.currentScript;if(!t)return void console.warn("GetuAI SDK: Could not find script tag for auto-initialization");const e=t.getAttribute("data-api-key");e?T({apiKey:e,apiEndpoint:t.getAttribute("data-api-endpoint")||n,enableDebug:"true"===t.getAttribute("data-debug"),autoTrack:"true"===t.getAttribute("data-auto-track"),autoTrackPageView:"true"===t.getAttribute("data-auto-track-page-view"),autoCleanUTM:"false"!==t.getAttribute("data-auto-clean-utm"),batchSize:parseInt(t.getAttribute("data-batch-size")||"100"),batchInterval:parseInt(t.getAttribute("data-batch-interval")||"2000"),userId:t.getAttribute("data-user-id")||void 0}):console.warn("GetuAI SDK: No API key provided. Please add data-api-key attribute to script tag.")}(),"undefined"!=typeof window&&(window.getuaiSDK={init:T,getSDK:I,waitForSDK:D,trackEvent:A,trackPageView:P,trackPageClick:B,trackButtonClick:q,trackPurchase:U,trackLogin:C,trackSignup:x,trackFormSubmit:K,trackVideoPlay:N,trackEmailVerification:R,trackAuditApproved:F,trackCustomEvent:V,trackProductView:G,trackAddToCart:$,trackPurchaseAuto:O,trackLoginAuto:L,trackSignupAuto:z,trackEmailVerificationAuto:M,getAttributionData:j,flush:Y,getStatus:H,addUTMToURL:J,getCurrentUTMParams:W,setUserId:Q,getUserId:X,removeUserId:Z,destroy:tt,EventType:t,Currency:e,AttributionSDK:et},window.init=T,window.waitForSDK=D,window.trackEvent=A,window.trackPageView=P,window.trackPageClick=B,window.trackButtonClick=q,window.trackPurchase=U,window.trackLogin=C,window.trackSignup=x,window.trackFormSubmit=K,window.trackVideoPlay=N,window.trackEmailVerification=R,window.trackProductView=G,window.trackAddToCart=$,window.trackPurchaseAuto=O,window.trackLoginAuto=L,window.trackSignupAuto=z,window.trackEmailVerificationAuto=M,window.getAttributionData=j,window.flush=Y,window.getStatus=H,window.addUTMToURL=J,window.getCurrentUTMParams=W,window.setUserId=Q,window.getUserId=X,window.removeUserId=Z,window.destroy=tt,window.AttributionSDK=et);const it={init:T,getSDK:I,waitForSDK:D,trackEvent:A,trackPageView:P,trackPageClick:B,trackButtonClick:q,trackPurchase:U,trackLogin:C,trackSignup:x,trackFormSubmit:K,trackVideoPlay:N,trackEmailVerification:R,trackAuditApproved:F,trackCustomEvent:V,trackProductView:G,trackAddToCart:$,trackPurchaseAuto:O,trackLoginAuto:L,trackSignupAuto:z,trackEmailVerificationAuto:M,getAttributionData:j,flush:Y,getStatus:H,addUTMToURL:J,getCurrentUTMParams:W,setUserId:Q,getUserId:X,removeUserId:Z,destroy:tt,EventType:t,Currency:e,AttributionSDK:et};return r.default})());
|
|
1
|
+
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.GetuAIAttribution=e():t.GetuAIAttribution=e()}(this,()=>(()=>{"use strict";var t,e,i={d:(t,e)=>{for(var r in e)i.o(e,r)&&!i.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:e[r]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},r={};i.d(r,{default:()=>ct}),function(t){t.PAGE_VIEW="page_view",t.PAGE_CLICK="page_click",t.BUTTON_CLICK="button_click",t.VIDEO_PLAY="video_play",t.FORM_SUBMIT="form_submit",t.EMAIL_VERIFICATION="email_verification",t.LOGIN="login",t.SIGNUP="signup",t.PRODUCT_VIEW="product_view",t.ADD_TO_CART="add_to_cart",t.PURCHASE="purchase",t.AUDIT_APPROVED="audit_approved",t.CUSTOM="custom"}(t||(t={})),function(t){t.USD="USD"}(e||(e={}));const n="https://attribution.getu.ai/attribution/api",a=[t.PURCHASE,t.LOGIN,t.SIGNUP,t.FORM_SUBMIT,t.EMAIL_VERIFICATION,t.AUDIT_APPROVED],s={email:["email","e-mail","mail","user_email","email_address"],name:["name","full_name","fullname","username","display_name"],first_name:["first_name","firstname","fname","given_name"],last_name:["last_name","lastname","lname","surname","family_name"],phone:["phone","telephone","mobile","phone_number","tel"],company_name:["company","company_name","organization","org","business"],title:["title","job_title","position","role","job"]};function o(){return Math.floor(Date.now()/1e3)}const c=["adgroup_id","ad_group_id","adgroupid","adset_id","tt_adgroup_id","adgroup"],u=["ad_id","adid","creative_id","creative","tt_ad_id"],l=["gclid","gbraid","wbraid","fbclid","ttclid","msclkid","twclid","li_fat_id"],d=/^\d{7,15}$/;function g(t,e){const i=e.toLowerCase();for(const e of Object.keys(t))if(e.toLowerCase()===i){const i=t[e];for(const t of i)if(t&&""!==t.trim())return t.trim()}}function h(){const t="undefined"!=typeof navigator&&navigator.userAgent||"";return/iPad|Tablet|PlayBook|Silk/i.test(t)?"tablet":/Mobi|Android|iPhone|iPod/i.test(t)?"mobile":"desktop"}function m(){try{const t="__localStorage_test__";return localStorage.setItem(t,t),localStorage.removeItem(t),!0}catch{return!1}}class p{constructor(t=!0){this.enabled=t}debug(t,...e){this.enabled&&console.debug&&console.debug(`[GetuAI Debug] ${t}`,...e)}info(t,...e){this.enabled&&console.info&&console.info(`[GetuAI Info] ${t}`,...e)}warn(t,...e){this.enabled&&console.warn&&console.warn(`[GetuAI Warn] ${t}`,...e)}error(t,...e){this.enabled&&console.error&&console.error(`[GetuAI Error] ${t}`,...e)}}function f(){return document.referrer||""}function _(){return window.location.href}function w(){return document.title||""}function y(t,e){try{const i=new URL(t);return Object.entries(e).forEach(([t,e])=>{e&&!i.searchParams.has(t)&&i.searchParams.set(t,e)}),i.toString()}catch(e){return t}}function v(t,e=["utm_source","utm_medium","utm_campaign"]){const i={};return e.forEach(e=>{t[e]&&(i[e]=t[e])}),i}function k(t){try{const e=t||window.location.href;return new URL(e).search}catch(t){return""}}function S(t){try{const e=t||window.location.href,i=new URL(e),r={};return i.searchParams.forEach((t,e)=>{r[e]=t}),r}catch(t){return{}}}class b{constructor(t){this.logger=t}get(t){try{if(!m())return this.logger.warn("LocalStorage not supported"),null;const e=localStorage.getItem(t);return null===e?null:JSON.parse(e)}catch(t){return this.logger.error("Error reading from localStorage:",t),null}}set(t,e){try{if(!m())return void this.logger.warn("LocalStorage not supported");localStorage.setItem(t,JSON.stringify(e))}catch(t){this.logger.error("Error writing to localStorage:",t),this.handleQuotaExceeded()}}remove(t){try{m()&&localStorage.removeItem(t)}catch(t){this.logger.error("Error removing from localStorage:",t)}}clear(){try{m()&&localStorage.clear()}catch(t){this.logger.error("Error clearing localStorage:",t)}}handleQuotaExceeded(){try{const t=Object.keys(localStorage).filter(t=>t.startsWith("attribution_"));if(t.length>0){t.sort((t,e)=>{const i=this.get(t),r=this.get(e);return(i?.expiresAt||0)-(r?.expiresAt||0)});const e=Math.ceil(.2*t.length);t.slice(0,e).forEach(t=>{this.remove(t)}),this.logger.info(`Cleaned up ${e} old attribution records`)}}catch(t){this.logger.error("Error during quota cleanup:",t)}}}class E{constructor(t){this.dbName="attribution_events",this.dbVersion=1,this.storeName="events",this.db=null,this.logger=t}async init(){if("indexedDB"in window)return new Promise((t,e)=>{const i=indexedDB.open(this.dbName,this.dbVersion);i.onerror=()=>{this.logger.error("Failed to open IndexedDB:",i.error),e(i.error)},i.onsuccess=()=>{this.db=i.result,this.logger.info("IndexedDB initialized successfully"),t()},i.onupgradeneeded=t=>{const e=t.target.result;if(!e.objectStoreNames.contains(this.storeName)){const t=e.createObjectStore(this.storeName,{keyPath:"id",autoIncrement:!0});t.createIndex("timestamp","timestamp",{unique:!1}),t.createIndex("sent","sent",{unique:!1}),t.createIndex("queued_at","queued_at",{unique:!1})}}});this.logger.warn("IndexedDB not supported")}async addEvent(t){if(!this.db)throw new Error("IndexedDB not initialized");return new Promise((e,i)=>{const r=this.db.transaction([this.storeName],"readwrite").objectStore(this.storeName),n={...t,queued_at:Date.now(),sent:!1},a=r.add(n);a.onsuccess=()=>{this.logger.debug("Event added to IndexedDB queue"),e()},a.onerror=()=>{this.logger.error("Failed to add event to IndexedDB:",a.error),i(a.error)}})}async getUnsentEvents(t=100){return this.db?new Promise((e,i)=>{const r=this.db.transaction([this.storeName],"readonly").objectStore(this.storeName).index("sent").getAll(IDBKeyRange.only(!1),t);r.onsuccess=()=>{const t=r.result.map(t=>{const{queued_at:e,sent:i,...r}=t;return r});e(t)},r.onerror=()=>{this.logger.error("Failed to get unsent events:",r.error),i(r.error)}}):[]}async markEventsAsSent(t){if(this.db&&0!==t.length)return new Promise((e,i)=>{const r=this.db.transaction([this.storeName],"readwrite").objectStore(this.storeName);let n=0,a=!1;t.forEach(s=>{const o=r.get(s);o.onsuccess=()=>{if(o.result){const s={...o.result,sent:!0},c=r.put(s);c.onsuccess=()=>{n++,n!==t.length||a||e()},c.onerror=()=>{a=!0,this.logger.error("Failed to mark event as sent:",c.error),i(c.error)}}else n++,n!==t.length||a||e()},o.onerror=()=>{a=!0,this.logger.error("Failed to get event for marking as sent:",o.error),i(o.error)}})})}async cleanupOldEvents(t=6048e5){if(!this.db)return;const e=Date.now()-t;return new Promise((t,i)=>{const r=this.db.transaction([this.storeName],"readwrite").objectStore(this.storeName).index("queued_at").openCursor(IDBKeyRange.upperBound(e));r.onsuccess=()=>{const e=r.result;e?(e.delete(),e.continue()):(this.logger.info("Old events cleanup completed"),t())},r.onerror=()=>{this.logger.error("Failed to cleanup old events:",r.error),i(r.error)}})}async getQueueSize(){return this.db?new Promise((t,e)=>{const i=this.db.transaction([this.storeName],"readonly").objectStore(this.storeName).count();i.onsuccess=()=>{t(i.result)},i.onerror=()=>{this.logger.error("Failed to get queue size:",i.error),e(i.error)}}):0}async clear(){if(this.db)return new Promise((t,e)=>{const i=this.db.transaction([this.storeName],"readwrite").objectStore(this.storeName).clear();i.onsuccess=()=>{this.logger.info("IndexedDB queue cleared"),t()},i.onerror=()=>{this.logger.error("Failed to clear IndexedDB queue:",i.error),e(i.error)}})}}class T{constructor(t){this.UTM_STORAGE_KEY="attribution_utm_data",this.SESSION_STORAGE_KEY="attribution_session",this.USER_ID_KEY="getuai_user_id",this.PENDING_EVENTS_KEY="getuai_pending_events_v1",this.USER_TRAITS_KEY="getuai_user_traits",this.USER_ID_COOKIE_EXPIRES=365,this.logger=t,this.localStorage=new b(t),this.indexedDB=new E(t)}setUserId(t){if(!t||""===t.trim())return void this.logger.warn("Cannot set empty user ID");const e=t.trim();try{this.setCookie(this.USER_ID_KEY,e,this.USER_ID_COOKIE_EXPIRES),this.logger.debug(`👤 User ID stored in cookie: ${e}`)}catch(t){this.logger.error("Failed to store user ID in cookie:",t)}try{"undefined"!=typeof localStorage&&(localStorage.setItem(this.USER_ID_KEY,e),this.logger.debug(`👤 User ID stored in localStorage: ${e}`))}catch(t){this.logger.error("Failed to store user ID in localStorage:",t)}}getUserId(){try{if("undefined"!=typeof localStorage){const t=localStorage.getItem(this.USER_ID_KEY);if(t)return t}}catch(t){this.logger.debug("Failed to get user ID from localStorage:",t)}try{const t=this.getCookie(this.USER_ID_KEY);if(t){try{"undefined"!=typeof localStorage&&localStorage.setItem(this.USER_ID_KEY,t)}catch(t){}return t}}catch(t){this.logger.debug("Failed to get user ID from cookie:",t)}return null}removeUserId(){try{this.deleteCookie(this.USER_ID_KEY),this.logger.debug("👤 User ID removed from cookie")}catch(t){this.logger.error("Failed to remove user ID from cookie:",t)}try{"undefined"!=typeof localStorage&&(localStorage.removeItem(this.USER_ID_KEY),this.logger.debug("👤 User ID removed from localStorage"))}catch(t){this.logger.error("Failed to remove user ID from localStorage:",t)}}setCookie(t,e,i){try{const r=new Date;r.setTime(r.getTime()+24*i*60*60*1e3);const n=`${t}=${encodeURIComponent(e)};expires=${r.toUTCString()};path=/;SameSite=Lax`;document.cookie=n}catch(t){this.logger.error("Failed to set cookie:",t)}}getCookie(t){try{const e=t+"=",i=document.cookie.split(";");for(let t=0;t<i.length;t++){let r=i[t];for(;" "===r.charAt(0);)r=r.substring(1,r.length);if(0===r.indexOf(e))return decodeURIComponent(r.substring(e.length,r.length))}return null}catch(t){return this.logger.error("Failed to get cookie:",t),null}}deleteCookie(t){try{document.cookie=`${t}=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/;`}catch(t){this.logger.error("Failed to delete cookie:",t)}}async init(){await this.indexedDB.init()}getPendingEvents(){try{const t=this.localStorage.get(this.PENDING_EVENTS_KEY);return Array.isArray(t)?t:[]}catch(t){return this.logger.debug("Failed to read pending events:",t),[]}}appendPendingEvents(t){if(t&&0!==t.length)try{const e=[...this.getPendingEvents(),...t].slice(-500);this.localStorage.set(this.PENDING_EVENTS_KEY,e)}catch(t){this.logger.debug("Failed to append pending events:",t)}}removePendingEventsByIds(t){if(t&&0!==t.length)try{const e=this.getPendingEvents();if(0===e.length)return;const i=new Set(t),r=e.filter(t=>!i.has(t.event_id));0===r.length?this.localStorage.remove(this.PENDING_EVENTS_KEY):this.localStorage.set(this.PENDING_EVENTS_KEY,r)}catch(t){this.logger.debug("Failed to remove pending events:",t)}}clearPendingEvents(){try{this.localStorage.remove(this.PENDING_EVENTS_KEY)}catch(t){this.logger.debug("Failed to clear pending events:",t)}}storeUTMData(t){try{const e=this.getUTMData(),i={utm_source:"",utm_medium:"",utm_campaign:"",utm_term:"",utm_content:"",timestamp:Date.now()},r={firstTouch:e?.firstTouch||i,lastTouch:e?.lastTouch||i,touchpoints:e?.touchpoints||[],...t,expiresAt:Date.now()+2592e6};this.localStorage.set(this.UTM_STORAGE_KEY,r),this.logger.debug("UTM data stored successfully:",{firstTouch:r.firstTouch,lastTouch:r.lastTouch,touchpointsCount:r.touchpoints.length,expiresAt:new Date(r.expiresAt).toISOString()})}catch(t){this.logger.error("Failed to store UTM data:",t)}}getUTMData(){const t=this.localStorage.get(this.UTM_STORAGE_KEY);return t&&t.expiresAt&&t.expiresAt>Date.now()?t:(t&&this.localStorage.remove(this.UTM_STORAGE_KEY),null)}storeSession(t){this.localStorage.set(this.SESSION_STORAGE_KEY,t)}getSession(){return this.localStorage.get(this.SESSION_STORAGE_KEY)}async queueEvent(t){try{await this.indexedDB.addEvent(t)}catch(t){throw this.logger.error("Failed to queue event:",t),t}}async getUnsentEvents(t=100){return await this.indexedDB.getUnsentEvents(t)}async markEventsAsSent(t){await this.indexedDB.markEventsAsSent(t)}async getQueueSize(){return await this.indexedDB.getQueueSize()}async cleanupOldEvents(){await this.indexedDB.cleanupOldEvents()}async clearQueue(){await this.indexedDB.clear()}cleanupExpiredData(){this.getUTMData()}setUserTraits(t){try{const e={...this.getUserTraits()||{},...t};"undefined"!=typeof localStorage&&(localStorage.setItem(this.USER_TRAITS_KEY,JSON.stringify(e)),this.logger.debug("User traits stored:",e))}catch(t){this.logger.error("Failed to store user traits:",t)}}getUserTraits(){try{if("undefined"!=typeof localStorage){const t=localStorage.getItem(this.USER_TRAITS_KEY);return t?JSON.parse(t):null}return null}catch(t){return this.logger.error("Failed to get user traits:",t),null}}clearUserTraits(){try{"undefined"!=typeof localStorage&&(localStorage.removeItem(this.USER_TRAITS_KEY),this.logger.debug("User traits cleared"))}catch(t){this.logger.error("Failed to clear user traits:",t)}}}const I="0.3.7";class P{constructor(t,e,i,r=100,n=2e3,a=3,s=1e3,o){this.queue=[],this.processing=!1,this.batchTimer=null,this.retryCounts=new Map,this.logger=t,this.apiKey=e,this.apiEndpoint=i,this.batchSize=r,this.batchInterval=n,this.maxRetries=a,this.retryDelay=s,this.sendEvents=o,this.debouncedProcess=function(t){let e;return(...i)=>{clearTimeout(e),e=setTimeout(()=>t(...i),100)}}(this.process.bind(this))}add(t){if(a.includes(t.event_type))return this.logger.debug(`Immediate event detected: ${t.event_type}, sending immediately`),void this.processImmediate(t);this.queue.push(t),this.logger.debug(`Event added to queue: ${t.event_type}`),this.scheduleBatchProcessing()}async process(){if(this.processing||0===this.queue.length)return;this.processing=!0;const t=this.queue.splice(0,this.batchSize);try{this.logger.debug(`Processing ${t.length} events from queue`),await this.sendEvents(t),this.resetRetryCounts(t),this.logger.info(`Successfully processed ${t.length} events`)}catch(e){return this.logger.error("Failed to process events:",e),this.enqueueBatchForRetry(t),void setTimeout(()=>{this.processing=!1,this.debouncedProcess()},this.retryDelay)}this.processing=!1,this.queue.length>0&&this.debouncedProcess()}async processImmediate(t){try{this.logger.debug(`Processing immediate event: ${t.event_type}`),await this.sendEvents([t]),this.resetRetryCounts([t]),this.logger.info(`Immediate event processed successfully: ${t.event_type}`)}catch(e){this.logger.error(`Failed to process immediate event: ${t.event_type}`,e),this.enqueueBatchForRetry([t])}}scheduleBatchProcessing(){this.batchTimer&&clearTimeout(this.batchTimer),this.batchTimer=setTimeout(()=>{this.debouncedProcess()},this.batchInterval)}clear(){this.queue=[],this.batchTimer&&(clearTimeout(this.batchTimer),this.batchTimer=null),this.processing=!1,this.logger.info("Event queue cleared")}size(){return this.queue.length}getStats(){return{size:this.queue.length,processing:this.processing}}async flush(){for(this.logger.info("Flushing event queue");this.queue.length>0;)await this.process()}drainAll(){const t=this.queue.splice(0,this.queue.length);return this.batchTimer&&(clearTimeout(this.batchTimer),this.batchTimer=null),this.processing=!1,t}enqueueBatchForRetry(t){const e=[];for(const i of t){const t=(this.retryCounts.get(i.event_id)||0)+1;this.retryCounts.set(i.event_id,t),t>this.maxRetries?(this.logger.warn(`Dropping event after max retries (${this.maxRetries}): ${i.event_type} (${i.event_id})`),this.retryCounts.delete(i.event_id)):e.push(i)}e.length>0&&this.queue.unshift(...e)}resetRetryCounts(t){for(const e of t)this.retryCounts.delete(e.event_id)}}class A{constructor(t,e,i,r=3,n=1e3){this.logger=t,this.apiKey=e,this.apiEndpoint=i,this.maxRetries=r,this.retryDelay=n}async sendEvents(t){if(0===t.length)return;const e={events:t.map(t=>({event_id:t.event_id,event_type:t.event_type,tracking_user_id:t.tracking_user_id,utm_source:t.utm_source||null,utm_medium:t.utm_medium||null,utm_campaign:t.utm_campaign||null,utm_term:t.utm_term||null,utm_content:t.utm_content||null,revenue:t.revenue||null,currency:t.currency||null,event_data:t.event_data||null,context:t.context||null,timestamp:t.timestamp||o(),custom_event_name:t.custom_event_name||void 0})),sdk_version:I};await async function(t,e=3,i=1e3){let r;for(let n=0;n<=e;n++)try{return await t()}catch(t){if(r=t,n===e)throw r;const a=i*Math.pow(2,n);await new Promise(t=>setTimeout(t,a))}throw r}(async()=>{const i=await fetch(`${this.apiEndpoint}/attribution/events`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`},body:JSON.stringify(e)});if(!i.ok){const t=await i.text();throw new Error(`HTTP ${i.status}: ${t}`)}const r=await i.json();return this.logger.debug("Events sent successfully:",r),{result:r,sentEvents:t}},this.maxRetries,this.retryDelay)}async sendEventsKeepalive(t){if(0===t.length)return;const e={events:t.map(t=>({event_id:t.event_id,event_type:t.event_type,tracking_user_id:t.tracking_user_id,utm_source:t.utm_source||null,utm_medium:t.utm_medium||null,utm_campaign:t.utm_campaign||null,utm_term:t.utm_term||null,utm_content:t.utm_content||null,revenue:t.revenue||null,currency:t.currency||null,event_data:t.event_data||null,context:t.context||null,timestamp:t.timestamp||o(),custom_event_name:t.custom_event_name||void 0})),sdk_version:I},i=await fetch(`${this.apiEndpoint}/attribution/events`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`},body:JSON.stringify(e),keepalive:!0});if(!i.ok)throw new Error(`HTTP ${i.status}`)}sendEventsBeacon(t){if(0===t.length)return!0;if("undefined"==typeof navigator||"function"!=typeof navigator.sendBeacon)return!1;const e={events:t.map(t=>({event_id:t.event_id,event_type:t.event_type,tracking_user_id:t.tracking_user_id,utm_source:t.utm_source||null,utm_medium:t.utm_medium||null,utm_campaign:t.utm_campaign||null,utm_term:t.utm_term||null,utm_content:t.utm_content||null,revenue:t.revenue||null,currency:t.currency||null,event_data:t.event_data||null,context:t.context||null,timestamp:t.timestamp||o(),custom_event_name:t.custom_event_name||void 0})),sdk_version:I},i=`${this.apiEndpoint}/attribution/events_beacon?api_key=${encodeURIComponent(this.apiKey)}`,r=new Blob([JSON.stringify(e)],{type:"text/plain"});return navigator.sendBeacon(i,r)}async sendSingleEvent(t){await this.sendEvents([t])}async testConnection(){try{return(await fetch(`${this.apiEndpoint}/health`,{method:"GET",headers:{Authorization:`Bearer ${this.apiKey}`}})).ok}catch(t){return this.logger.error("Connection test failed:",t),!1}}}class D{constructor(t){this.session=null,this.initialized=!1,this.autoTrackEnabled=!1,this.pageViewTrackTimes=new Map,this.cachedPublicIP=null,this.publicIPFetchPromise=null,this.initialPageViewTriggered=!1,this.spaTrackingEnabled=!1,this.lastTrackedPath="",this.originalPushState=null,this.originalReplaceState=null,this.popstateHandler=null,this.config={apiEndpoint:n,batchSize:100,batchInterval:2e3,maxRetries:3,retryDelay:1e3,enableDebug:!1,autoTrack:!1,autoTrackPageView:!1,sessionTimeout:18e5,enableCrossDomainUTM:!0,crossDomainUTMParams:["utm_source","utm_medium","utm_campaign","utm_term","utm_content"],excludeDomains:[],autoCleanUTM:!0,pageViewDebounceInterval:5e3,...t},this.logger=new p(this.config.enableDebug),this.storage=new T(this.logger),this.httpClient=new A(this.logger,this.config.apiKey,this.config.apiEndpoint||n,this.config.maxRetries,this.config.retryDelay),this.queue=new P(this.logger,this.config.apiKey,this.config.apiEndpoint||n,this.config.batchSize,this.config.batchInterval,this.config.maxRetries,this.config.retryDelay,t=>this.httpClient.sendEvents(t))}async init(){if(this.initialized)this.logger.warn("SDK already initialized");else try{this.logger.info("Initializing GetuAI Attribution SDK"),this.storage.init().catch(t=>{this.logger.warn("Storage initialization failed (IndexedDB may be unavailable), continuing with basic features:",t)}),this.initializeUserId(),this.initializeSession(),this.extractAndStoreUTMData(),this.config.autoTrack&&this.setupAutoTracking(),this.setupNetworkHandlers(),this.setupVisibilityHandlers(),this.setupBeforeUnloadHandler(),this.initialized=!0,this.logger.info("🚀 GetuAI Attribution SDK initialized successfully"),this.logger.info("📄 Auto track page view = "+this.config.autoTrackPageView),this.retryPendingEvents().catch(t=>{this.logger.warn("⚠️ Pending events retry failed",t)}),this.config.autoTrackPageView&&(this.logger.info("📄 Auto track page view enabled (including SPA route tracking)"),this.lastTrackedPath=this.getCurrentPath(),this.setupSPATracking(),this.initialPageViewTriggered||(this.initialPageViewTriggered=!0,Promise.resolve().then(()=>this.trackPageView()).then(()=>this.queue.process()).then(()=>{this.logger.info("✅ Auto track page view completed")}).catch(t=>this.logger.error("❌ Auto track page view failed:",t))))}catch(t){throw this.logger.error("Failed to initialize SDK:",t),t}}async trackEvent(i,r,n,a,s=e.USD,c){if(this.initialized)try{const e=_(),u=this.cachedPublicIP;this.ensurePublicIPFetched();const l={domain:"undefined"!=typeof window?window.location.hostname:null,path:"undefined"!=typeof window?window.location.pathname:null,title:w(),referrer:f(),url:e.split("?")[0],querystring:k(e),query_params:S(e),ip_address:u},d={session_id:this.session?.sessionId,start_time:this.session?.startTime,last_activity:this.session?.lastActivity,page_views:this.session?.pageViews},g=n||this.getUserId(),m={event_id:"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(t){const e=16*Math.random()|0;return("x"===t?e:3&e|8).toString(16)}),event_type:i,tracking_user_id:g||void 0,timestamp:o(),event_data:r,context:{page:l,session:d},revenue:a,currency:s,...i===t.CUSTOM&&c?{custom_event_name:c}:{},...this.getUTMParams(),device:h(),...this.getAdParams()};this.logger.debug(`Tracking event: ${i}`,m),this.queue.add(m)}catch(t){this.logger.error(`Failed to track event ${i}:`,t)}else this.logger.warn("SDK not initialized, event not tracked")}async trackCustomEvent(i,r,n,a,s=e.USD){i&&""!==i.trim()?await this.trackEvent(t.CUSTOM,r,n,a,s,i.trim()):this.logger.error("trackCustomEvent requires a non-empty customEventName")}async trackPageView(e,i){const r=_(),n=r.split("?")[0],a=Date.now(),s=this.config.pageViewDebounceInterval||5e3,o=this.pageViewTrackTimes.get(n);if(o&&a-o<s)return void this.logger.debug(`Page view debounced: ${n} (last tracked ${a-o}ms ago)`);const c={url:r,title:w(),referrer:f(),user_agent:navigator.userAgent||"",...e};await this.trackEvent(t.PAGE_VIEW,c,i),this.pageViewTrackTimes.set(n,a),this.cleanupPageViewTrackTimes()}cleanupPageViewTrackTimes(){const t=Date.now()-36e5;for(const[e,i]of this.pageViewTrackTimes.entries())i<t&&this.pageViewTrackTimes.delete(e)}ensurePublicIPFetched(){this.cachedPublicIP||this.publicIPFetchPromise||(this.publicIPFetchPromise=this.fetchPublicIP().then(t=>(t&&(this.cachedPublicIP=t),t)).catch(()=>null).finally(()=>{this.publicIPFetchPromise=null}))}async fetchPublicIP(){try{const t=new AbortController,e=setTimeout(()=>t.abort(),2e3),i=await fetch("https://api.ipify.org?format=json",{signal:t.signal,headers:{Accept:"application/json"}});if(clearTimeout(e),!i.ok)return null;const r=await i.json();return"string"==typeof r?.ip?r.ip:null}catch(t){return this.logger.debug("Public IP fetch failed",t),null}}async trackPurchase(i,r,n=e.USD,a){await this.trackEvent(t.PURCHASE,a,i,r,n)}async trackLogin(e,i){await this.trackEvent(t.LOGIN,i,e)}async trackSignup(e,i){await this.trackEvent(t.SIGNUP,i,e)}async trackFormSubmit(e,i){await this.trackEvent(t.FORM_SUBMIT,i,e)}async trackVideoPlay(e,i){await this.trackEvent(t.VIDEO_PLAY,i,e)}async trackEmailVerification(e,i){await this.trackEvent(t.EMAIL_VERIFICATION,i,e)}async trackAuditApproved(e,i){await this.trackEvent(t.AUDIT_APPROVED,i,e)}async trackPurchaseAuto(i){const r=i.tracking_user_id||this.getUserId();r?await this.trackEvent(t.PURCHASE,i.purchaseData,r,i.revenue,i.currency||e.USD):this.logger.error("❌ trackPurchaseAuto requires tracking_user_id. Please set user ID using setUserId() or pass it in options.tracking_user_id.")}async trackLoginAuto(e){const i=e.tracking_user_id||this.getUserId();i?await this.trackEvent(t.LOGIN,e.loginData,i):this.logger.error("❌ trackLoginAuto requires tracking_user_id. Please set user ID using setUserId() or pass it in options.tracking_user_id.")}async trackSignupAuto(e){const i=e.tracking_user_id||this.getUserId();i?await this.trackEvent(t.SIGNUP,e.signupData,i):this.logger.error("❌ trackSignupAuto requires tracking_user_id. Please set user ID using setUserId() or pass it in options.tracking_user_id.")}async trackEmailVerificationAuto(e){const i=e.tracking_user_id||this.getUserId();i?await this.trackEvent(t.EMAIL_VERIFICATION,e.verificationData,i):this.logger.error("❌ trackEmailVerificationAuto requires tracking_user_id. Please set user ID using setUserId() or pass it in options.tracking_user_id.")}async trackProductView(e,i){await this.trackEvent(t.PRODUCT_VIEW,i,e)}async trackAddToCart(e,i){await this.trackEvent(t.ADD_TO_CART,i,e)}async trackPageClick(e,i){await this.trackEvent(t.PAGE_CLICK,i,e)}async trackButtonClick(e,i,r){await this.trackEvent(t.BUTTON_CLICK,{button_name:e,...r},i)}getAttributionData(){return this.storage.getUTMData()}addUTMToURL(t){if(!this.config.enableCrossDomainUTM)return t;const e=this.getAttributionData();return e?y(t,v(e.lastTouch,this.config.crossDomainUTMParams)):t}getCurrentUTMParams(){const t=this.getAttributionData();return t?v(t.lastTouch,this.config.crossDomainUTMParams):{}}getUTMParams(){const t=this.getAttributionData();if(!t)return this.logger.debug("No attribution data available for UTM params"),{};const e={utm_source:t.lastTouch.utm_source||null,utm_medium:t.lastTouch.utm_medium||null,utm_campaign:t.lastTouch.utm_campaign||null,utm_term:t.lastTouch.utm_term||null,utm_content:t.lastTouch.utm_content||null},i={};return e.utm_source&&""!==e.utm_source.trim()?i.utm_source=e.utm_source:i.utm_source=null,e.utm_medium&&""!==e.utm_medium.trim()?i.utm_medium=e.utm_medium:i.utm_medium=null,e.utm_campaign&&""!==e.utm_campaign.trim()?i.utm_campaign=e.utm_campaign:i.utm_campaign=null,e.utm_term&&""!==e.utm_term.trim()?i.utm_term=e.utm_term:i.utm_term=null,e.utm_content&&""!==e.utm_content.trim()?i.utm_content=e.utm_content:i.utm_content=null,this.logger.debug("UTM params for event:",i),i}getAdParams(){const t=this.getAttributionData();if(!t)return{};const e=t.lastTouch,i=t=>t&&"string"==typeof t&&""!==t.trim()?t:null;return{adgroup_id:i(e.adgroup_id),ad_id:i(e.ad_id),adgroup_id_source:i(e.adgroup_id_source),ad_id_source:i(e.ad_id_source),click_id:i(e.click_id),click_id_source:i(e.click_id_source)}}initializeUserId(){if(this.config.userId)this.setUserId(this.config.userId),this.logger.info(`👤 User ID initialized from config: ${this.config.userId}`);else{const t=this.getUserId();t?this.logger.debug(`👤 Existing user ID found: ${t}`):this.logger.debug("👤 No user ID found, will be set when user identifies")}}setUserId(t){t&&""!==t.trim()?(this.storage.setUserId(t),this.logger.info(`👤 User ID set: ${t}`)):this.logger.warn("Cannot set empty user ID")}getUserId(){return this.storage.getUserId()}removeUserId(){this.storage.removeUserId(),this.logger.info("👤 User ID removed")}async identify(e,i){this.initialized?e&&""!==e.trim()?(this.setUserId(e),i&&(this.storage.setUserTraits(i),this.logger.info(`User identified: ${e}`,i)),await this.trackEvent(t.FORM_SUBMIT,{_identify:!0,_user_traits:i,tracking_user_id:e},e)):this.logger.warn("Cannot identify with empty user ID"):this.logger.warn("SDK not initialized, identify call queued")}getUserTraits(){return this.storage.getUserTraits()}getSessionId(){return this.session?.sessionId||null}initializeSession(){const t=this.storage.getSession(),e=Date.now();t&&e-t.lastActivity<this.config.sessionTimeout?this.session={...t,lastActivity:e}:this.session={sessionId:`session_${Date.now()}_${Math.random().toString(36).substr(2,9)}`,startTime:e,lastActivity:e,pageViews:0},this.storage.storeSession(this.session),this.logger.debug("Session initialized:",this.session)}extractAndStoreUTMData(){const t=_(),e=function(t){const e=function(t){const e={};try{new URL(t).searchParams.forEach((t,i)=>{e[i]=t})}catch(t){}return e}(t),i={};return["utm_source","utm_medium","utm_campaign","utm_term","utm_content"].forEach(t=>{e[t]&&""!==e[t].trim()&&(i[t]=e[t].trim())}),i}(t),i=function(t){const e=function(t){const e={};try{const i=new URL(t),r=new Set;i.searchParams.forEach((t,e)=>{r.add(e)}),r.forEach(t=>{const r=i.searchParams.getAll(t);e[t]=r.filter(t=>null!=t&&""!==t)})}catch(t){}return e}(t),i={};for(const t of c){const r=g(e,t);if(void 0!==r){/^\{.+\}$/.test(r)?(i.adgroup_id=r,i.adgroup_id_source="valuetrack"):(i.adgroup_id=r,i.adgroup_id_source="explicit");break}}if(!i.adgroup_id){const t=g(e,"utm_term")?e[Object.keys(e).find(t=>"utm_term"===t.toLowerCase())]:[];for(const e of t){const t=(e||"").trim();if(d.test(t)){i.adgroup_id=t,i.adgroup_id_source="utm_term_numeric";break}}}for(const t of u){const r=g(e,t);if(void 0!==r){/^\{.+\}$/.test(r)?(i.ad_id=r,i.ad_id_source="valuetrack"):(i.ad_id=r,i.ad_id_source="explicit");break}}if(!i.ad_id){const t=g(e,"utm_content")?e[Object.keys(e).find(t=>"utm_content"===t.toLowerCase())]:[];for(const e of t){const t=(e||"").trim();if(d.test(t)){i.ad_id=t,i.ad_id_source="utm_content_numeric";break}}}for(const t of l){const r=g(e,t);if(r&&""!==r.trim()){i.click_id=r.trim(),i.click_id_source=t;break}}return i}(t);if(this.logger.debug("Extracting UTM params from URL:",t),this.logger.debug("Found UTM params:",e),this.logger.debug("Found ad params:",i),0===Object.keys(e).length&&!i.adgroup_id&&!i.ad_id&&!i.click_id)return void this.logger.debug("No UTM, ad, or click parameters found in URL");const r={utm_source:e.utm_source||"",utm_medium:e.utm_medium||"",utm_campaign:e.utm_campaign||"",utm_term:e.utm_term||"",utm_content:e.utm_content||"",adgroup_id:i.adgroup_id||"",ad_id:i.ad_id||"",adgroup_id_source:i.adgroup_id_source||"",ad_id_source:i.ad_id_source||"",click_id:i.click_id||"",click_id_source:i.click_id_source||"",timestamp:Date.now()},n=this.getAttributionData(),a={firstTouch:n?.firstTouch||r,lastTouch:r,touchpoints:n?.touchpoints||[],expiresAt:Date.now()+2592e6};n&&n.lastTouch.utm_source===r.utm_source&&n.lastTouch.utm_campaign===r.utm_campaign||a.touchpoints.push(r),this.storage.storeUTMData(a),this.logger.info("UTM data extracted and stored successfully:",r),this.config.autoCleanUTM&&function(){try{const t=new URL(window.location.href);let e=!1;["utm_source","utm_medium","utm_campaign","utm_term","utm_content"].forEach(i=>{t.searchParams.has(i)&&(t.searchParams.delete(i),e=!0)}),e&&window.history.replaceState({},document.title,t.toString())}catch(t){console.warn("Failed to clean URL:",t)}}()}setupAutoTracking(){this.autoTrackEnabled=!0,this.setupFormTracking(),this.setupLinkTracking(),this.logger.info("Auto-tracking enabled")}setupFormTracking(){document.addEventListener("submit",e=>{try{const i=e.target;if(!i)return;const r=this.serializeFormFields(i),n=this.extractLeadFields(r);n.email&&(this.storage.setUserTraits(n),this.logger.debug("Lead fields extracted from form:",n)),this.trackEvent(t.FORM_SUBMIT,{...r,_lead_fields:n,form_id:i.id||i.name,form_action:i.action,form_method:i.method})}catch(t){this.logger.error("Failed to auto-track form submit:",t)}})}serializeFormFields(t){const e={};try{const i=new FormData(t);for(const[t,r]of i.entries()){const i=this.serializeFormValue(r);if(Object.prototype.hasOwnProperty.call(e,t)){const r=e[t];Array.isArray(r)?(r.push(i),e[t]=r):e[t]=[r,i]}else e[t]=i}const r=Array.from(t.elements),n=new Map;for(let t=0;t<r.length;t++){const e=r[t];if(e.disabled)continue;const i=e.tagName.toLowerCase(),a=e.type?.toLowerCase();if("button"===i||"submit"===a||"reset"===a)continue;const s=this.getFormFieldKey(e,t);s&&(n.has(s)||n.set(s,[]),n.get(s).push(e))}n.forEach((t,i)=>{const r=t.some(t=>"radio"===t.type),n=t.some(t=>"checkbox"===t.type),a=t.some(t=>"file"===t.type),s=t.some(t=>"SELECT"===t.tagName&&t.multiple),o=t.some(t=>"password"===t.type);if(n){const r=t.filter(t=>"checkbox"===t.type).filter(t=>t.checked).map(t=>t.value||"on");return void(Object.prototype.hasOwnProperty.call(e,i)?Array.isArray(e[i])||(e[i]=[e[i],...r]):e[i]=r)}if(r){const r=t.filter(t=>"radio"===t.type).find(t=>t.checked);return void(Object.prototype.hasOwnProperty.call(e,i)||(e[i]=r?r.value:null))}if(s){const r=t.find(t=>"SELECT"===t.tagName&&t.multiple);if(r){const t=Array.from(r.selectedOptions).map(t=>t.value);return void(Object.prototype.hasOwnProperty.call(e,i)||(e[i]=t))}}if(a){const r=t.find(t=>"file"===t.type);if(r){const t=r.files?Array.from(r.files):[];return void(Object.prototype.hasOwnProperty.call(e,i)||(e[i]=t.map(t=>this.serializeFormValue(t))))}}if(o)Object.prototype.hasOwnProperty.call(e,i)?e[i]="string"==typeof e[i]?"*****":e[i]:e[i]="*****";else if(!Object.prototype.hasOwnProperty.call(e,i)){const r=t[0];if("SELECT"===r.tagName){const t=r;e[i]=t.multiple?Array.from(t.selectedOptions).map(t=>t.value):t.value}else r.type,e[i]=r.value??""}})}catch(t){this.logger.error("Failed to serialize form fields:",t)}return e}getFormFieldKey(t,e){const i=t.name;if(i)return i;if(t.id)return`id_${t.id}`;const r=t.getAttribute("aria-label");if(r&&r.trim())return`aria_${this.normalizeFormFieldKey(r)}`;const n=t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement?t.placeholder:"";return n&&n.trim()?`ph_${this.normalizeFormFieldKey(n)}_${e}`:`field_${e}`}normalizeFormFieldKey(t){return t.trim().toLowerCase().replace(/\s+/g,"_").replace(/[^a-z0-9_]/g,"").slice(0,40)}serializeFormValue(t){return t instanceof File?{file_name:t.name,file_size:t.size,file_type:t.type}:t}extractLeadFields(t){const e={};for(const[i,r]of Object.entries(s))if(!e[i])for(const n of r||[]){const r=n.toLowerCase();for(const[n,a]of Object.entries(t))if((n.toLowerCase()===r||n.toLowerCase().includes(r))&&a&&"string"==typeof a&&a.trim()){e[i]=a.trim();break}if(e[i])break}return e.name||!e.first_name&&!e.last_name||(e.name=[e.first_name,e.last_name].filter(Boolean).join(" ").trim()),e}setupLinkTracking(){document.addEventListener("click",t=>{const e=t.target.closest("a");e&&function(t){try{const e=new URL(t),i=window.location.hostname;return e.hostname!==i}catch(t){return!1}}(e.href)&&this.handleCrossDomainUTM(e,t)})}handleCrossDomainUTM(t,e){if(!this.config.enableCrossDomainUTM)return;const i=t.href;if(function(t,e=[]){try{const i=new URL(t).hostname.toLowerCase();return e.some(t=>{const e=t.toLowerCase();return i===e||i.endsWith(`.${e}`)})}catch(t){return!1}}(i,this.config.excludeDomains))return void this.logger.debug(`Domain excluded from UTM passing: ${i}`);const r=this.getAttributionData();if(!r)return void this.logger.debug("No UTM data available for cross-domain passing");const n=v(r.lastTouch,this.config.crossDomainUTMParams);if(0===Object.keys(n).length)return void this.logger.debug("No UTM parameters to pass");const a=y(i,n);a!==i&&(t.href=a,this.logger.debug("UTM parameters added to external link:",{original:i,enhanced:a,utmParams:n}),this.logger.debug("Cross-domain UTM passed:",{link_url:a,original_url:i,utm_params_passed:n}))}setupNetworkHandlers(){window.addEventListener("online",()=>{this.logger.info("Network connection restored"),this.queue.flush()}),window.addEventListener("offline",()=>{this.logger.warn("Network connection lost")})}setupVisibilityHandlers(){document.addEventListener("visibilitychange",()=>{"hidden"!==document.visibilityState?"visible"===document.visibilityState&&this.updateSessionActivity():this.flushOnUnload("visibilitychange:hidden")})}setupBeforeUnloadHandler(){window.addEventListener("beforeunload",()=>{this.flushOnUnload("beforeunload")}),window.addEventListener("pagehide",()=>{this.flushOnUnload("pagehide")})}flushOnUnload(t){try{this.updateSessionActivity();const e=this.queue.drainAll();if(0===e.length)return;if(this.storage.appendPendingEvents(e),this.logger.debug(`🚚 Unload flush triggered: ${t}`,{count:e.length}),this.httpClient.sendEventsBeacon(e))return void this.storage.removePendingEventsByIds(e.map(t=>t.event_id));const i=20;for(let t=0;t<e.length;t+=i){const r=e.slice(t,t+i);this.httpClient.sendEventsKeepalive(r).then(()=>{this.storage.removePendingEventsByIds(r.map(t=>t.event_id))}).catch(t=>{this.logger.debug("Unload keepalive send failed",t)})}}catch(t){this.logger.debug("flushOnUnload failed",t)}}async retryPendingEvents(){const t=this.storage.getPendingEvents();if(0!==t.length){this.logger.info(`📦 Retrying pending events: ${t.length}`);try{await this.httpClient.sendEvents(t),this.storage.removePendingEventsByIds(t.map(t=>t.event_id)),this.logger.info(`✅ Pending events sent: ${t.length}`)}catch(t){this.logger.warn("⚠️ Pending events retry failed",t)}}}getCurrentPath(){return"undefined"==typeof window?"":window.location.pathname+window.location.search}setupSPATracking(){if("undefined"==typeof window||"undefined"==typeof history)return void this.logger.warn("⚠️ SPA tracking not available in this environment");if(this.spaTrackingEnabled)return void this.logger.warn("⚠️ SPA tracking already enabled");this.spaTrackingEnabled=!0,this.lastTrackedPath=this.getCurrentPath(),this.originalPushState=history.pushState.bind(history),this.originalReplaceState=history.replaceState.bind(history);const t=t=>{const e=this.getCurrentPath();e!==this.lastTrackedPath?(this.logger.debug(`🔄 [SPA] Route change detected (${t}): ${this.lastTrackedPath} -> ${e}`),this.lastTrackedPath=e,setTimeout(()=>{this.trackPageView().then(()=>this.queue.process()).then(()=>{this.logger.debug(`✅ [SPA] Page view tracked for: ${e}`)}).catch(t=>{this.logger.error("❌ [SPA] Failed to track page view:",t)})},100)):this.logger.debug(`🔄 [SPA] Route change detected (${t}) but path unchanged: ${e}`)};history.pushState=(e,i,r)=>{const n=this.originalPushState(e,i,r);return t("pushState"),n},history.replaceState=(e,i,r)=>{const n=this.originalReplaceState(e,i,r);return t("replaceState"),n},this.popstateHandler=()=>{t("popstate")},window.addEventListener("popstate",this.popstateHandler),this.logger.info("🔄 SPA tracking setup completed")}cleanupSPATracking(){this.spaTrackingEnabled&&(this.originalPushState&&(history.pushState=this.originalPushState,this.originalPushState=null),this.originalReplaceState&&(history.replaceState=this.originalReplaceState,this.originalReplaceState=null),this.popstateHandler&&(window.removeEventListener("popstate",this.popstateHandler),this.popstateHandler=null),this.spaTrackingEnabled=!1,this.logger.info("🔄 SPA tracking cleaned up"))}updateSessionActivity(){this.session&&(this.session.lastActivity=Date.now(),this.storage.storeSession(this.session))}async flush(){await this.queue.flush()}getStatus(){return{initialized:this.initialized,session:this.session,queueSize:this.queue.size(),online:navigator.onLine,crossDomainUTM:{enabled:this.config.enableCrossDomainUTM||!0,currentParams:this.getCurrentUTMParams()},spaTracking:{enabled:this.spaTrackingEnabled,currentPath:this.lastTrackedPath}}}destroy(){this.queue.clear(),this.autoTrackEnabled=!1,this.cleanupSPATracking(),this.initialized=!1,this.logger.info("🗑️ SDK destroyed")}}let U=null;async function C(t){if(U)return console.warn("GetuAI SDK: Already initialized"),U;try{if(U=new D(t),await U.init(),t.enableDebug&&(window.GetuAISDK=U),console.log("GetuAI Attribution SDK initialized successfully"),"undefined"!=typeof window){const t=new CustomEvent("getuaiSDKReady",{detail:{sdk:U}});window.dispatchEvent(t)}return U}catch(t){if(console.error("GetuAI SDK: Failed to initialize:",t),"undefined"!=typeof window){const e=new CustomEvent("getuaiSDKError",{detail:{error:t}});window.dispatchEvent(e)}throw t}}function x(){return U}function K(){return new Promise((t,e)=>{if(U)return void t(U);const i=e=>{window.removeEventListener("getuaiSDKReady",i),window.removeEventListener("getuaiSDKError",r),t(e.detail.sdk)},r=t=>{window.removeEventListener("getuaiSDKReady",i),window.removeEventListener("getuaiSDKError",r),e(t.detail.error)};window.addEventListener("getuaiSDKReady",i),window.addEventListener("getuaiSDKError",r),setTimeout(()=>{window.removeEventListener("getuaiSDKReady",i),window.removeEventListener("getuaiSDKError",r),e(new Error("SDK initialization timeout"))},1e4)})}async function N(t,i,r,n,a=e.USD){const s=x();s?await s.trackEvent(t,i,r,n,a):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function R(t,e){const i=x();i?await i.trackPageView(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function O(t,i,r=e.USD,n){const a=x();a?await a.trackPurchase(t,i,r,n):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function F(t,e){const i=x();i?await i.trackLogin(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function L(t,e){const i=x();i?await i.trackSignup(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function M(t,e){const i=x();i?await i.trackFormSubmit(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function z(t,e){const i=x();i?await i.trackVideoPlay(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function V(t,e){const i=x();i?await i.trackEmailVerification(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function G(t,e){const i=x();i?await i.trackAuditApproved(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function $(t){const e=x();e?await e.trackPurchaseAuto(t):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function B(t){const e=x();e?await e.trackLoginAuto(t):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function q(t){const e=x();e?await e.trackSignupAuto(t):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function j(t){const e=x();e?await e.trackEmailVerificationAuto(t):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function Y(t,i,r,n,a=e.USD){const s=x();s?await s.trackCustomEvent(t,i,r,n,a):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function H(t,e){const i=x();i?await i.trackProductView(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function J(t,e){const i=x();i?await i.trackAddToCart(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function W(t,e){const i=x();i?await i.trackPageClick(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function Q(t,e,i){const r=x();r?await r.trackButtonClick(t,e,i):console.warn("GetuAI SDK: Not initialized. Call init() first.")}function X(){const t=x();return t?t.getAttributionData():null}async function Z(){const t=x();t?await t.flush():console.warn("GetuAI SDK: Not initialized. Call init() first.")}function tt(){const t=x();return t?t.getStatus():null}function et(t){const e=x();return e?e.addUTMToURL(t):(console.warn("GetuAI SDK: Not initialized. Call init() first."),t)}function it(){const t=x();return t?t.getCurrentUTMParams():{}}function rt(t){const e=x();e?e.setUserId(t):console.warn("GetuAI SDK: Not initialized. Call init() first.")}function nt(){const t=x();return t?t.getUserId():(console.warn("GetuAI SDK: Not initialized. Call init() first."),null)}function at(){const t=x();t?t.removeUserId():console.warn("GetuAI SDK: Not initialized. Call init() first.")}function st(){U&&(U.destroy(),U=null,console.log("GetuAI SDK destroyed"))}class ot extends D{static async init(t){return await C(t)}static async trackEvent(t,i,r,n,a=e.USD){return await N(t,i,r,n,a)}static async trackPageView(t,e){return await R(t,e)}static async trackPurchase(t,i,r=e.USD,n){return await O(t,i,r,n)}static async trackLogin(t,e){return await F(t,e)}static async trackSignup(t,e){return await L(t,e)}static async trackFormSubmit(t,e){return await M(t,e)}static async trackVideoPlay(t,e){return await z(t,e)}static async trackEmailVerification(t,e){return await V(t,e)}static async trackPurchaseAuto(t){return await $(t)}static async trackLoginAuto(t){return await B(t)}static async trackSignupAuto(t){return await q(t)}static async trackEmailVerificationAuto(t){return await j(t)}static async trackCustomEvent(t,i,r,n,a=e.USD){return await Y(t,i,r,n,a)}static async trackProductView(t,e){return await H(t,e)}static async trackAddToCart(t,e){return await J(t,e)}static async trackPageClick(t,e){return await W(t,e)}static async trackButtonClick(t,e,i){return await Q(t,e,i)}static getAttributionData(){return X()}static async flush(){return await Z()}static getStatus(){return tt()}static addUTMToURL(t){return et(t)}static getCurrentUTMParams(){return it()}static setUserId(t){rt(t)}static getUserId(){return nt()}static removeUserId(){at()}static destroy(){st()}static getSDK(){return x()}static waitForSDK(){return K()}}"undefined"!=typeof document&&function(){if(U)return;const t=document.currentScript;if(!t)return void console.warn("GetuAI SDK: Could not find script tag for auto-initialization");const e=t.getAttribute("data-api-key");e?C({apiKey:e,apiEndpoint:t.getAttribute("data-api-endpoint")||n,enableDebug:"true"===t.getAttribute("data-debug"),autoTrack:"true"===t.getAttribute("data-auto-track"),autoTrackPageView:"true"===t.getAttribute("data-auto-track-page-view"),autoCleanUTM:"false"!==t.getAttribute("data-auto-clean-utm"),batchSize:parseInt(t.getAttribute("data-batch-size")||"100"),batchInterval:parseInt(t.getAttribute("data-batch-interval")||"2000"),userId:t.getAttribute("data-user-id")||void 0}):console.warn("GetuAI SDK: No API key provided. Please add data-api-key attribute to script tag.")}(),"undefined"!=typeof window&&(window.getuaiSDK={init:C,getSDK:x,waitForSDK:K,trackEvent:N,trackPageView:R,trackPageClick:W,trackButtonClick:Q,trackPurchase:O,trackLogin:F,trackSignup:L,trackFormSubmit:M,trackVideoPlay:z,trackEmailVerification:V,trackAuditApproved:G,trackCustomEvent:Y,trackProductView:H,trackAddToCart:J,trackPurchaseAuto:$,trackLoginAuto:B,trackSignupAuto:q,trackEmailVerificationAuto:j,getAttributionData:X,flush:Z,getStatus:tt,addUTMToURL:et,getCurrentUTMParams:it,setUserId:rt,getUserId:nt,removeUserId:at,destroy:st,EventType:t,Currency:e,AttributionSDK:ot},window.init=C,window.waitForSDK=K,window.trackEvent=N,window.trackPageView=R,window.trackPageClick=W,window.trackButtonClick=Q,window.trackPurchase=O,window.trackLogin=F,window.trackSignup=L,window.trackFormSubmit=M,window.trackVideoPlay=z,window.trackEmailVerification=V,window.trackProductView=H,window.trackAddToCart=J,window.trackPurchaseAuto=$,window.trackLoginAuto=B,window.trackSignupAuto=q,window.trackEmailVerificationAuto=j,window.getAttributionData=X,window.flush=Z,window.getStatus=tt,window.addUTMToURL=et,window.getCurrentUTMParams=it,window.setUserId=rt,window.getUserId=nt,window.removeUserId=at,window.destroy=st,window.AttributionSDK=ot);const ct={init:C,getSDK:x,waitForSDK:K,trackEvent:N,trackPageView:R,trackPageClick:W,trackButtonClick:Q,trackPurchase:O,trackLogin:F,trackSignup:L,trackFormSubmit:M,trackVideoPlay:z,trackEmailVerification:V,trackAuditApproved:G,trackCustomEvent:Y,trackProductView:H,trackAddToCart:J,trackPurchaseAuto:$,trackLoginAuto:B,trackSignupAuto:q,trackEmailVerificationAuto:j,getAttributionData:X,flush:Z,getStatus:tt,addUTMToURL:et,getCurrentUTMParams:it,setUserId:rt,getUserId:nt,removeUserId:at,destroy:st,EventType:t,Currency:e,AttributionSDK:ot};return r.default})());
|
package/dist/index.esm.js
CHANGED
|
@@ -62,7 +62,7 @@ function generateId() {
|
|
|
62
62
|
function getTimestamp() {
|
|
63
63
|
return Math.floor(Date.now() / 1000);
|
|
64
64
|
}
|
|
65
|
-
// Parse URL parameters
|
|
65
|
+
// Parse URL parameters — LAST value wins for duplicate keys (existing behavior)
|
|
66
66
|
function parseUrlParams(url) {
|
|
67
67
|
const params = {};
|
|
68
68
|
try {
|
|
@@ -76,6 +76,28 @@ function parseUrlParams(url) {
|
|
|
76
76
|
}
|
|
77
77
|
return params;
|
|
78
78
|
}
|
|
79
|
+
// Parse URL parameters preserving ALL values for duplicate keys.
|
|
80
|
+
// Use this when duplicate keys carry distinct signals — e.g. Google Ads
|
|
81
|
+
// tracking templates like `utm_term={adgroupid}&utm_term={keyword}` on the
|
|
82
|
+
// same URL need both values to recover adgroup_id.
|
|
83
|
+
function parseUrlParamsMulti(url) {
|
|
84
|
+
const params = {};
|
|
85
|
+
try {
|
|
86
|
+
const urlObj = new URL(url);
|
|
87
|
+
const seenKeys = new Set();
|
|
88
|
+
urlObj.searchParams.forEach((_value, key) => {
|
|
89
|
+
seenKeys.add(key);
|
|
90
|
+
});
|
|
91
|
+
seenKeys.forEach((key) => {
|
|
92
|
+
const all = urlObj.searchParams.getAll(key);
|
|
93
|
+
params[key] = all.filter((v) => v != null && v !== "");
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
// Handle invalid URLs
|
|
98
|
+
}
|
|
99
|
+
return params;
|
|
100
|
+
}
|
|
79
101
|
// Extract UTM parameters from URL
|
|
80
102
|
function extractUTMParams(url) {
|
|
81
103
|
const params = parseUrlParams(url);
|
|
@@ -94,6 +116,149 @@ function extractUTMParams(url) {
|
|
|
94
116
|
});
|
|
95
117
|
return utmParams;
|
|
96
118
|
}
|
|
119
|
+
// Known aliases, in priority order. First hit wins. Matching is case-insensitive
|
|
120
|
+
// for Google aliases; Microsoft-style PascalCase (AdGroupId) is matched via the
|
|
121
|
+
// lowercased form because URLSearchParams preserves case but our iteration
|
|
122
|
+
// normalizes when comparing.
|
|
123
|
+
const ADGROUP_ALIASES = [
|
|
124
|
+
"adgroup_id",
|
|
125
|
+
"ad_group_id",
|
|
126
|
+
"adgroupid",
|
|
127
|
+
"adset_id",
|
|
128
|
+
"tt_adgroup_id",
|
|
129
|
+
"adgroup",
|
|
130
|
+
];
|
|
131
|
+
const AD_ALIASES = [
|
|
132
|
+
"ad_id",
|
|
133
|
+
"adid",
|
|
134
|
+
"creative_id",
|
|
135
|
+
"creative",
|
|
136
|
+
"tt_ad_id",
|
|
137
|
+
];
|
|
138
|
+
// Click identifiers in priority order — gclid is the dominant Google one,
|
|
139
|
+
// gbraid/wbraid are iOS privacy-preserving variants.
|
|
140
|
+
const CLICK_ID_ALIASES = [
|
|
141
|
+
"gclid",
|
|
142
|
+
"gbraid",
|
|
143
|
+
"wbraid",
|
|
144
|
+
"fbclid",
|
|
145
|
+
"ttclid",
|
|
146
|
+
"msclkid",
|
|
147
|
+
"twclid",
|
|
148
|
+
"li_fat_id",
|
|
149
|
+
];
|
|
150
|
+
// Google ValueTrack IDs are 64-bit numeric. Empirically 7–15 digits covers
|
|
151
|
+
// observed campaign / adgroup / ad / keyword IDs without false-matching short
|
|
152
|
+
// numeric keywords.
|
|
153
|
+
const NUMERIC_ID_RE = /^\d{7,15}$/;
|
|
154
|
+
// Case-insensitive lookup into a multi-value param map.
|
|
155
|
+
function firstValueCI(multi, key) {
|
|
156
|
+
const lower = key.toLowerCase();
|
|
157
|
+
for (const k of Object.keys(multi)) {
|
|
158
|
+
if (k.toLowerCase() === lower) {
|
|
159
|
+
const vals = multi[k];
|
|
160
|
+
for (const v of vals) {
|
|
161
|
+
if (v && v.trim() !== "")
|
|
162
|
+
return v.trim();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
// Extract ad-platform tracking parameters from a URL, covering:
|
|
169
|
+
// - Explicit named params (adgroup_id / ad_group_id / adset_id / tt_adgroup_id / ...)
|
|
170
|
+
// - Unreplaced ValueTrack placeholders (e.g. `adgroup_id={adgroupid}`)
|
|
171
|
+
// - Tier-2 numeric inference (Google advertisers configure utm_term={adgroupid}
|
|
172
|
+
// AND utm_term={keyword} in the same tracking template → one utm_term value
|
|
173
|
+
// is a numeric ID and the other is free-text keyword)
|
|
174
|
+
// - Click identifiers (gclid / gbraid / wbraid / fbclid / ttclid / msclkid /
|
|
175
|
+
// twclid / li_fat_id)
|
|
176
|
+
function extractAdParams(url) {
|
|
177
|
+
const multi = parseUrlParamsMulti(url);
|
|
178
|
+
const result = {};
|
|
179
|
+
// --- Adgroup ID -------------------------------------------------------
|
|
180
|
+
for (const alias of ADGROUP_ALIASES) {
|
|
181
|
+
const v = firstValueCI(multi, alias);
|
|
182
|
+
if (v === undefined)
|
|
183
|
+
continue;
|
|
184
|
+
// ValueTrack literal that wasn't replaced by Google (advertiser config error)
|
|
185
|
+
if (/^\{.+\}$/.test(v)) {
|
|
186
|
+
result.adgroup_id = v;
|
|
187
|
+
result.adgroup_id_source = "valuetrack";
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
result.adgroup_id = v;
|
|
191
|
+
result.adgroup_id_source = "explicit";
|
|
192
|
+
}
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
// Tier-2: any utm_term value that looks like a numeric ID
|
|
196
|
+
if (!result.adgroup_id) {
|
|
197
|
+
const utmTerms = firstValueCI(multi, "utm_term")
|
|
198
|
+
? multi[Object.keys(multi).find((k) => k.toLowerCase() === "utm_term")]
|
|
199
|
+
: [];
|
|
200
|
+
for (const v of utmTerms) {
|
|
201
|
+
const trimmed = (v || "").trim();
|
|
202
|
+
if (NUMERIC_ID_RE.test(trimmed)) {
|
|
203
|
+
result.adgroup_id = trimmed;
|
|
204
|
+
result.adgroup_id_source = "utm_term_numeric";
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// --- Ad ID ------------------------------------------------------------
|
|
210
|
+
for (const alias of AD_ALIASES) {
|
|
211
|
+
const v = firstValueCI(multi, alias);
|
|
212
|
+
if (v === undefined)
|
|
213
|
+
continue;
|
|
214
|
+
if (/^\{.+\}$/.test(v)) {
|
|
215
|
+
result.ad_id = v;
|
|
216
|
+
result.ad_id_source = "valuetrack";
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
result.ad_id = v;
|
|
220
|
+
result.ad_id_source = "explicit";
|
|
221
|
+
}
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
// Tier-2: utm_content numeric → likely {creative}
|
|
225
|
+
if (!result.ad_id) {
|
|
226
|
+
const utmContents = firstValueCI(multi, "utm_content")
|
|
227
|
+
? multi[Object.keys(multi).find((k) => k.toLowerCase() === "utm_content")]
|
|
228
|
+
: [];
|
|
229
|
+
for (const v of utmContents) {
|
|
230
|
+
const trimmed = (v || "").trim();
|
|
231
|
+
if (NUMERIC_ID_RE.test(trimmed)) {
|
|
232
|
+
result.ad_id = trimmed;
|
|
233
|
+
result.ad_id_source = "utm_content_numeric";
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
// --- Click ID (first non-empty wins, priority order) ------------------
|
|
239
|
+
for (const alias of CLICK_ID_ALIASES) {
|
|
240
|
+
const v = firstValueCI(multi, alias);
|
|
241
|
+
if (v && v.trim() !== "") {
|
|
242
|
+
result.click_id = v.trim();
|
|
243
|
+
result.click_id_source = alias;
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
// Detect device type from navigator.userAgent.
|
|
250
|
+
// Returns 'desktop' | 'mobile' | 'tablet'. Tablet checked before mobile
|
|
251
|
+
// because iPad UA strings can contain 'Mobile'.
|
|
252
|
+
function detectDevice() {
|
|
253
|
+
const ua = (typeof navigator !== "undefined" && navigator.userAgent) || "";
|
|
254
|
+
if (/iPad|Tablet|PlayBook|Silk/i.test(ua)) {
|
|
255
|
+
return "tablet";
|
|
256
|
+
}
|
|
257
|
+
if (/Mobi|Android|iPhone|iPod/i.test(ua)) {
|
|
258
|
+
return "mobile";
|
|
259
|
+
}
|
|
260
|
+
return "desktop";
|
|
261
|
+
}
|
|
97
262
|
// Check if browser supports localStorage
|
|
98
263
|
function isLocalStorageSupported() {
|
|
99
264
|
try {
|
|
@@ -890,7 +1055,7 @@ class AttributionStorageManager {
|
|
|
890
1055
|
* This version is automatically updated from package.json during build
|
|
891
1056
|
* DO NOT manually edit this file - it will be overwritten during build
|
|
892
1057
|
*/
|
|
893
|
-
const SDK_VERSION = "0.3.
|
|
1058
|
+
const SDK_VERSION = "0.3.7";
|
|
894
1059
|
|
|
895
1060
|
class EventQueueManager {
|
|
896
1061
|
constructor(logger, apiKey, apiEndpoint, batchSize = 100, batchInterval = 2000, maxRetries = 3, retryDelay = 1000, sendEvents) {
|
|
@@ -1339,6 +1504,8 @@ class AttributionSDK {
|
|
|
1339
1504
|
? { custom_event_name }
|
|
1340
1505
|
: {}),
|
|
1341
1506
|
...this.getUTMParams(),
|
|
1507
|
+
device: detectDevice(),
|
|
1508
|
+
...this.getAdParams(),
|
|
1342
1509
|
};
|
|
1343
1510
|
this.logger.debug(`Tracking event: ${eventType}`, event);
|
|
1344
1511
|
// Add to queue
|
|
@@ -1582,6 +1749,25 @@ class AttributionSDK {
|
|
|
1582
1749
|
this.logger.debug("UTM params for event:", filteredParams);
|
|
1583
1750
|
return filteredParams;
|
|
1584
1751
|
}
|
|
1752
|
+
// Get ad-platform tracking params (adgroup_id, ad_id, click_id) from stored
|
|
1753
|
+
// last-touch attribution data. These flow alongside UTM params and are
|
|
1754
|
+
// captured from ad platform click URLs (Google Ads, Meta, TikTok, etc.).
|
|
1755
|
+
getAdParams() {
|
|
1756
|
+
const attributionData = this.getAttributionData();
|
|
1757
|
+
if (!attributionData) {
|
|
1758
|
+
return {};
|
|
1759
|
+
}
|
|
1760
|
+
const last = attributionData.lastTouch;
|
|
1761
|
+
const nonEmpty = (v) => v && typeof v === "string" && v.trim() !== "" ? v : null;
|
|
1762
|
+
return {
|
|
1763
|
+
adgroup_id: nonEmpty(last.adgroup_id),
|
|
1764
|
+
ad_id: nonEmpty(last.ad_id),
|
|
1765
|
+
adgroup_id_source: nonEmpty(last.adgroup_id_source),
|
|
1766
|
+
ad_id_source: nonEmpty(last.ad_id_source),
|
|
1767
|
+
click_id: nonEmpty(last.click_id),
|
|
1768
|
+
click_id_source: nonEmpty(last.click_id_source),
|
|
1769
|
+
};
|
|
1770
|
+
}
|
|
1585
1771
|
// Initialize user ID
|
|
1586
1772
|
initializeUserId() {
|
|
1587
1773
|
// If userId is provided in config, set it (will override existing)
|
|
@@ -1695,10 +1881,16 @@ class AttributionSDK {
|
|
|
1695
1881
|
extractAndStoreUTMData() {
|
|
1696
1882
|
const currentUrl = getCurrentUrl();
|
|
1697
1883
|
const utmParams = extractUTMParams(currentUrl);
|
|
1884
|
+
const adParams = extractAdParams(currentUrl);
|
|
1698
1885
|
this.logger.debug("Extracting UTM params from URL:", currentUrl);
|
|
1699
1886
|
this.logger.debug("Found UTM params:", utmParams);
|
|
1700
|
-
|
|
1701
|
-
|
|
1887
|
+
this.logger.debug("Found ad params:", adParams);
|
|
1888
|
+
// Capture if UTM, ad IDs, or click IDs are present
|
|
1889
|
+
if (Object.keys(utmParams).length === 0 &&
|
|
1890
|
+
!adParams.adgroup_id &&
|
|
1891
|
+
!adParams.ad_id &&
|
|
1892
|
+
!adParams.click_id) {
|
|
1893
|
+
this.logger.debug("No UTM, ad, or click parameters found in URL");
|
|
1702
1894
|
return;
|
|
1703
1895
|
}
|
|
1704
1896
|
const utmData = {
|
|
@@ -1707,6 +1899,12 @@ class AttributionSDK {
|
|
|
1707
1899
|
utm_campaign: utmParams.utm_campaign || "",
|
|
1708
1900
|
utm_term: utmParams.utm_term || "",
|
|
1709
1901
|
utm_content: utmParams.utm_content || "",
|
|
1902
|
+
adgroup_id: adParams.adgroup_id || "",
|
|
1903
|
+
ad_id: adParams.ad_id || "",
|
|
1904
|
+
adgroup_id_source: adParams.adgroup_id_source || "",
|
|
1905
|
+
ad_id_source: adParams.ad_id_source || "",
|
|
1906
|
+
click_id: adParams.click_id || "",
|
|
1907
|
+
click_id_source: adParams.click_id_source || "",
|
|
1710
1908
|
timestamp: Date.now(),
|
|
1711
1909
|
};
|
|
1712
1910
|
const existingData = this.getAttributionData();
|