getu-attribution-v2-sdk 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -0
- package/dist/core/AttributionSDK.d.ts +1 -0
- package/dist/core/AttributionSDK.d.ts.map +1 -1
- package/dist/core/AttributionSDK.js +26 -12
- package/dist/getuai-attribution.min.js +1 -1
- package/dist/index.esm.js +110 -13
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +110 -13
- package/dist/queue/index.d.ts.map +1 -1
- package/dist/queue/index.js +3 -0
- package/dist/storage/index.d.ts +6 -0
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +80 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ A JavaScript SDK for tracking user attribution and conversion events in web appl
|
|
|
9
9
|
- [Quick Start](#quick-start)
|
|
10
10
|
- [Configuration](#configuration)
|
|
11
11
|
- [User ID Management](#user-id-management)
|
|
12
|
+
- [Anonymous ID](#anonymous-id)
|
|
12
13
|
- [Event Tracking](#event-tracking)
|
|
13
14
|
- [Auto-Tracking](#auto-tracking)
|
|
14
15
|
- [SPA Support](#spa-support)
|
|
@@ -249,6 +250,30 @@ await trackPageView({ category: "homepage" }, "different_user_456");
|
|
|
249
250
|
|
|
250
251
|
---
|
|
251
252
|
|
|
253
|
+
## Anonymous ID
|
|
254
|
+
|
|
255
|
+
The SDK automatically generates a persistent `anonymous_id` (UUID, prefix `anon_`)
|
|
256
|
+
on first access — stored in cookie + localStorage with 1-year TTL, and shared
|
|
257
|
+
across subdomains. Every event payload carries it.
|
|
258
|
+
|
|
259
|
+
- Anonymous → Identified: `setUserId(userId)` keeps the same `anonymous_id` —
|
|
260
|
+
the user's pre-login activity stays attributed.
|
|
261
|
+
- Case X (account switch on same device): `setUserId(B)` after `setUserId(A)`
|
|
262
|
+
rotates `anonymous_id` automatically — A's and B's activity are split.
|
|
263
|
+
- Idempotent: `setUserId(A); setUserId(A)` is a no-op.
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
const sdk = window.GetuaiAttribution.init({ apiKey: "..." });
|
|
267
|
+
await sdk.init();
|
|
268
|
+
console.log(sdk.getAnonymousId()); // "anon_3f2a9b8c1d4e4f5a..."
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Backward compatibility: events from SDK v0.4.x (no `anonymous_id`) fall back
|
|
272
|
+
to `legacy_user_<tracking_user_id>` / `legacy_sess_<session_id>` /
|
|
273
|
+
`legacy_orphan_<uuid>` server-side. No action required by callers.
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
252
277
|
## Event Tracking
|
|
253
278
|
|
|
254
279
|
### Event Types
|
|
@@ -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;AA6BlB,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;IAqGrB,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;
|
|
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;AA6BlB,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;IAqGrB,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;IA4DV,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;IAsBxB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAgD/B,OAAO,CAAC,aAAa;IAarB,SAAS,IAAI,MAAM,GAAG,IAAI;IAK1B,cAAc,IAAI,MAAM;IAKxB,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;IA8CzB,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"}
|
|
@@ -149,10 +149,12 @@ export class AttributionSDK {
|
|
|
149
149
|
page_views: this.session?.pageViews,
|
|
150
150
|
};
|
|
151
151
|
// Use provided tracking_user_id or fallback to stored user ID
|
|
152
|
+
const anonymousId = this.storage.getAnonymousId();
|
|
152
153
|
const finalUserId = tracking_user_id || this.getUserId();
|
|
153
154
|
const event = {
|
|
154
155
|
event_id: generateId(),
|
|
155
156
|
event_type: eventType,
|
|
157
|
+
anonymous_id: anonymousId,
|
|
156
158
|
tracking_user_id: finalUserId || undefined,
|
|
157
159
|
timestamp: getTimestamp(),
|
|
158
160
|
event_data: eventData,
|
|
@@ -445,29 +447,37 @@ export class AttributionSDK {
|
|
|
445
447
|
}
|
|
446
448
|
}
|
|
447
449
|
}
|
|
448
|
-
// Set user ID
|
|
450
|
+
// Set user ID — handles Case X (account switch) by rotating anonymous_id +
|
|
451
|
+
// session_id. Anonymous → identified is idempotent on anonymous_id.
|
|
449
452
|
setUserId(userId) {
|
|
450
453
|
if (!userId || userId.trim() === "") {
|
|
451
454
|
this.logger.warn("Cannot set empty user ID");
|
|
452
455
|
return;
|
|
453
456
|
}
|
|
454
457
|
const trimmed = userId.trim();
|
|
458
|
+
const prevUserId = this.storage.getUserId();
|
|
459
|
+
// Idempotent: same user already set.
|
|
460
|
+
if (prevUserId === trimmed) {
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
// Case X — distinct previous user means account switch on the same device.
|
|
464
|
+
// Rotate anonymous_id so the new user's events are not stitched onto the
|
|
465
|
+
// previous user's visitor identity.
|
|
466
|
+
if (prevUserId && prevUserId !== trimmed) {
|
|
467
|
+
this.logger.info(`👥 User changed (${prevUserId} → ${trimmed}) — rotating anonymous_id + session`);
|
|
468
|
+
this.storage.rotateAnonymousId();
|
|
469
|
+
}
|
|
455
470
|
this.storage.setUserId(trimmed);
|
|
456
471
|
// Bind this user to the current session. If the session was already
|
|
457
|
-
// owned by a *different* non-empty user
|
|
458
|
-
//
|
|
459
|
-
// not share the same session_id.
|
|
460
|
-
//
|
|
461
|
-
// Note: during SDK init, setUserId() runs before initializeSession(),
|
|
462
|
-
// so this.session may be null here — that's fine, initializeSession()
|
|
463
|
-
// does its own reconciliation against storage.getUserId().
|
|
472
|
+
// owned by a *different* non-empty user, force a session rotation
|
|
473
|
+
// — distinct users' activity must not share the same session_id.
|
|
464
474
|
if (this.session) {
|
|
465
|
-
const
|
|
466
|
-
if (
|
|
467
|
-
this.logger.info(`🔄
|
|
475
|
+
const sessionPrevUserId = this.session.userId;
|
|
476
|
+
if (sessionPrevUserId && sessionPrevUserId !== trimmed) {
|
|
477
|
+
this.logger.info(`🔄 Session previously owned by ${sessionPrevUserId} — rotating session for ${trimmed}`);
|
|
468
478
|
this.rotateSession(trimmed);
|
|
469
479
|
}
|
|
470
|
-
else if (
|
|
480
|
+
else if (sessionPrevUserId !== trimmed) {
|
|
471
481
|
// Anonymous session learning the user's id — attach, no rotation.
|
|
472
482
|
this.session = { ...this.session, userId: trimmed };
|
|
473
483
|
this.storage.storeSession(this.session);
|
|
@@ -492,6 +502,10 @@ export class AttributionSDK {
|
|
|
492
502
|
getUserId() {
|
|
493
503
|
return this.storage.getUserId();
|
|
494
504
|
}
|
|
505
|
+
// Get the SDK's persistent anonymous_id (creates one on first access).
|
|
506
|
+
getAnonymousId() {
|
|
507
|
+
return this.storage.getAnonymousId();
|
|
508
|
+
}
|
|
495
509
|
// Remove user ID
|
|
496
510
|
removeUserId() {
|
|
497
511
|
this.storage.removeUserId();
|
|
@@ -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:()=>dt}),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],o={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 a(){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 h(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 g(){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 y(){return document.title||""}function w(){return`session_${Date.now()}_${Math.random().toString(36).substr(2,9)}`}function S(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 k(t,e=["utm_source","utm_medium","utm_campaign"]){const i={};return e.forEach(e=>{t[e]&&(i[e]=t[e])}),i}function E(t){try{const e=t||window.location.href;return new URL(e).search}catch(t){return""}}function v(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 I{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(o=>{const a=r.get(o);a.onsuccess=()=>{if(a.result){const o={...a.result,sent:!0},c=r.put(o);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()},a.onerror=()=>{s=!0,this.logger.error("Failed to get event for marking as sent:",a.error),i(a.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.SESSION_COOKIE_KEY="_getuai_session",this.ATTRIB_COOKIE_KEY="_getuai_attrib",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.ATTRIB_COOKIE_EXPIRES=30,this.SESSION_COOKIE_EXPIRES=1,this.cookieDomain=null,this.logger=t,this.localStorage=new b(t),this.indexedDB=new I(t)}setCookieDomain(t){this.cookieDomain=t,this.logger.debug(t?`🍪 Cookie domain set to ${t} (cross-subdomain sharing enabled)`:"🍪 Cookie domain not set — using host-only cookies")}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,{days:this.USER_ID_COOKIE_EXPIRES,domain:this.cookieDomain}),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(){let t=null;try{t=this.getCookie(this.USER_ID_KEY)}catch(t){this.logger.debug("Failed to get user ID from cookie:",t)}if(t){try{"undefined"!=typeof localStorage&&localStorage.getItem(this.USER_ID_KEY)!==t&&localStorage.setItem(this.USER_ID_KEY,t)}catch(t){}return t}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)}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="number"==typeof i?{days:i}:i,n=r.days??this.USER_ID_COOKIE_EXPIRES,s=new Date;s.setTime(s.getTime()+24*n*60*60*1e3);const o=r.secure??("undefined"!=typeof location&&"https:"===location.protocol),a=r.domain??this.cookieDomain,c=[`${t}=${encodeURIComponent(e)}`,`expires=${s.toUTCString()}`,"path=/","SameSite=Lax"];return a&&c.push(`domain=${a}`),o&&c.push("Secure"),document.cookie=c.join(";"),!0}catch(t){return this.logger.error("Failed to set cookie:",t),!1}}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=/;`,this.cookieDomain&&(document.cookie=`${t}=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/;domain=${this.cookieDomain};`)}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.writeAttribution(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.readAttributionFromCookie();if(t&&t.expiresAt&&t.expiresAt>Date.now())return t;t&&this.deleteCookie(this.ATTRIB_COOKIE_KEY);const e=this.localStorage.get(this.UTM_STORAGE_KEY);return e&&e.expiresAt&&e.expiresAt>Date.now()?e:(e&&this.localStorage.remove(this.UTM_STORAGE_KEY),null)}storeSession(t){try{const e=JSON.stringify(t);this.setCookie(this.SESSION_COOKIE_KEY,e,{days:this.SESSION_COOKIE_EXPIRES,domain:this.cookieDomain})}catch(t){this.logger.debug("Failed to store session in cookie:",t)}this.localStorage.set(this.SESSION_STORAGE_KEY,t)}getSession(){try{const t=this.getCookie(this.SESSION_COOKIE_KEY);if(t)return JSON.parse(t)}catch(t){this.logger.debug("Failed to parse session cookie:",t)}return this.localStorage.get(this.SESSION_STORAGE_KEY)}migrateLegacyStorage(){try{if(!this.getCookie(this.SESSION_COOKIE_KEY)){const t=this.localStorage.get(this.SESSION_STORAGE_KEY);t&&(this.setCookie(this.SESSION_COOKIE_KEY,JSON.stringify(t),{days:this.SESSION_COOKIE_EXPIRES,domain:this.cookieDomain}),this.logger.debug("↗️ Migrated legacy session to cookie"))}}catch(t){this.logger.debug("Session migration skipped:",t)}try{if(!this.getCookie(this.ATTRIB_COOKIE_KEY)){const t=this.localStorage.get(this.UTM_STORAGE_KEY);t&&t.expiresAt&&t.expiresAt>Date.now()&&(this.writeAttribution(t),this.logger.debug("↗️ Migrated legacy attribution data to cookie"))}}catch(t){this.logger.debug("Attribution migration skipped:",t)}try{if(this.cookieDomain){const t=this.getCookie(this.USER_ID_KEY),e="undefined"!=typeof localStorage?localStorage.getItem(this.USER_ID_KEY):null;!t&&e&&(this.setCookie(this.USER_ID_KEY,e,{days:this.USER_ID_COOKIE_EXPIRES,domain:this.cookieDomain}),this.logger.debug("↗️ Migrated legacy user ID to cross-subdomain cookie"))}}catch(t){this.logger.debug("User ID migration skipped:",t)}}writeAttribution(t){this.localStorage.set(this.UTM_STORAGE_KEY,t);const e=Math.max(0,t.expiresAt-Date.now()),i=Math.max(1,Math.ceil(e/864e5));let r=encodeURIComponent(JSON.stringify(t));if(r.length>3800){const e={...t,touchpoints:(t.touchpoints||[]).slice(-20)};if(r=encodeURIComponent(JSON.stringify(e)),r.length>3800){const t={...e,touchpoints:[]};r=encodeURIComponent(JSON.stringify(t))}return this.logger.debug("Attribution cookie payload trimmed to fit size limit"),void this.setCookie(this.ATTRIB_COOKIE_KEY,decodeURIComponent(r),{days:i,domain:this.cookieDomain})}this.setCookie(this.ATTRIB_COOKIE_KEY,JSON.stringify(t),{days:i,domain:this.cookieDomain})}readAttributionFromCookie(){try{const t=this.getCookie(this.ATTRIB_COOKIE_KEY);return t?JSON.parse(t):null}catch(t){return this.logger.debug("Failed to parse attribution cookie:",t),null}}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 A="0.4.1";class D{constructor(t,e,i,r=100,n=2e3,s=3,o=1e3,a){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=o,this.sendEvents=a,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 P{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||a(),custom_event_name:t.custom_event_name||void 0})),sdk_version:A};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||a(),custom_event_name:t.custom_event_name||void 0})),sdk_version:A},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||a(),custom_event_name:t.custom_event_name||void 0})),sdk_version:A},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}}}const U="__getuai_probe";function C(t){try{document.cookie=`${U}=1;domain=${t};path=/;SameSite=Lax`;const e=document.cookie.split(";").some(t=>t.trim().startsWith(`${U}=`));return e&&(document.cookie=`${U}=;domain=${t};path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT`),e}catch{return!1}}class O{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 P(this.logger,this.config.apiKey,this.config.apiEndpoint||n,this.config.maxRetries,this.config.retryDelay),this.queue=new D(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");const t=function(t){if(!t)return null;const e=t.trim();return e?e.startsWith(".")?e:"."+e:null}(this.config.domain)??function(){if(!function(){try{return"undefined"!=typeof document&&"string"==typeof document.cookie}catch{return!1}}())return null;const t="undefined"!=typeof location&&location.hostname||"";if(!t)return null;if("localhost"===t)return null;if(function(t){return!!/^\d+\.\d+\.\d+\.\d+$/.test(t)||!!t.includes(":")}(t))return null;const e=t.split(".").filter(Boolean);if(e.length<2)return null;for(let t=e.length-2;t>=0;t--){const i="."+e.slice(t).join(".");if(C(i))return i}return null}();this.storage.setCookieDomain(t),this.storage.init().catch(t=>{this.logger.warn("Storage initialization failed (IndexedDB may be unavailable), continuing with basic features:",t)});try{this.storage.migrateLegacyStorage()}catch(t){this.logger.debug("Legacy storage migration skipped:",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,o=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:y(),referrer:f(),url:e.split("?")[0],querystring:E(e),query_params:v(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},h=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:h||void 0,timestamp:a(),event_data:r,context:{page:l,session:d},revenue:s,currency:o,...i===t.CUSTOM&&c?{custom_event_name:c}:{},...this.getUTMParams(),device:g(),...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,s,o=e.USD){i&&""!==i.trim()?await this.trackEvent(t.CUSTOM,r,n,s,o,i.trim()):this.logger.error("trackCustomEvent requires a non-empty customEventName")}async trackPageView(e,i){const r=_(),n=r.split("?")[0],s=Date.now(),o=this.config.pageViewDebounceInterval||5e3,a=this.pageViewTrackTimes.get(n);if(a&&s-a<o)return void this.logger.debug(`Page view debounced: ${n} (last tracked ${s-a}ms ago)`);const c={url:r,title:y(),referrer:f(),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?S(t,k(e.lastTouch,this.config.crossDomainUTMParams)):t}getCurrentUTMParams(){const t=this.getAttributionData();return t?k(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){if(!t||""===t.trim())return void this.logger.warn("Cannot set empty user ID");const e=t.trim();if(this.storage.setUserId(e),this.session){const t=this.session.userId;t&&t!==e?(this.logger.info(`🔄 User changed (${t} → ${e}) — rotating session`),this.rotateSession(e)):t!==e&&(this.session={...this.session,userId:e},this.storage.storeSession(this.session))}this.logger.info(`👤 User ID set: ${e}`)}rotateSession(t){const e=Date.now();this.session={sessionId:w(),startTime:e,lastActivity:e,pageViews:0,userId:t},this.storage.storeSession(this.session)}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(),i=this.storage.getUserId()??void 0,r=t&&e-t.lastActivity<this.config.sessionTimeout,n=!(!t?.userId||!i||t.userId===i);r&&!n?this.session={...t,lastActivity:e,userId:t.userId??i}:(n&&this.logger.info(`🔄 Session owner changed (${t?.userId} → ${i}) — starting new session`),this.session={sessionId:w(),startTime:e,lastActivity:e,pageViews:0,userId:i}),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=h(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=h(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=h(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=h(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=h(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(),s={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||s.touchpoints.push(r),this.storage.storeUTMData(s),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(),s=e.type?.toLowerCase();if("button"===i||"submit"===s||"reset"===s)continue;const o=this.getFormFieldKey(e,t);o&&(n.has(o)||n.set(o,[]),n.get(o).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),o=t.some(t=>"SELECT"===t.tagName&&t.multiple),a=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(o){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(a)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(o))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=k(r.lastTouch,this.config.crossDomainUTMParams);if(0===Object.keys(n).length)return void this.logger.debug("No UTM parameters to pass");const s=S(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 K=null;async function R(t){if(K)return console.warn("GetuAI SDK: Already initialized"),K;try{if(K=new O(t),await K.init(),t.enableDebug&&(window.GetuAISDK=K),console.log("GetuAI Attribution SDK initialized successfully"),"undefined"!=typeof window){const t=new CustomEvent("getuaiSDKReady",{detail:{sdk:K}});window.dispatchEvent(t)}return K}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 K}function N(){return new Promise((t,e)=>{if(K)return void t(K);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 F(t,i,r,n,s=e.USD){const o=x();o?await o.trackEvent(t,i,r,n,s):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function L(t,e){const i=x();i?await i.trackPageView(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function M(t,i,r=e.USD,n){const s=x();s?await s.trackPurchase(t,i,r,n):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function z(t,e){const i=x();i?await i.trackLogin(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function V(t,e){const i=x();i?await i.trackSignup(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function $(t,e){const i=x();i?await i.trackFormSubmit(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function G(t,e){const i=x();i?await i.trackVideoPlay(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function B(t,e){const i=x();i?await i.trackEmailVerification(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function q(t,e){const i=x();i?await i.trackAuditApproved(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function Y(t){const e=x();e?await e.trackPurchaseAuto(t):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function j(t){const e=x();e?await e.trackLoginAuto(t):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function H(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 W(t,i,r,n,s=e.USD){const o=x();o?await o.trackCustomEvent(t,i,r,n,s):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function X(t,e){const i=x();i?await i.trackProductView(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function Q(t,e){const i=x();i?await i.trackAddToCart(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function Z(t,e){const i=x();i?await i.trackPageClick(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function tt(t,e,i){const r=x();r?await r.trackButtonClick(t,e,i):console.warn("GetuAI SDK: Not initialized. Call init() first.")}function et(){const t=x();return t?t.getAttributionData():null}async function it(){const t=x();t?await t.flush():console.warn("GetuAI SDK: Not initialized. Call init() first.")}function rt(){const t=x();return t?t.getStatus():null}function nt(t){const e=x();return e?e.addUTMToURL(t):(console.warn("GetuAI SDK: Not initialized. Call init() first."),t)}function st(){const t=x();return t?t.getCurrentUTMParams():{}}function ot(t){const e=x();e?e.setUserId(t):console.warn("GetuAI SDK: Not initialized. Call init() first.")}function at(){const t=x();return t?t.getUserId():(console.warn("GetuAI SDK: Not initialized. Call init() first."),null)}function ct(){const t=x();t?t.removeUserId():console.warn("GetuAI SDK: Not initialized. Call init() first.")}function ut(){K&&(K.destroy(),K=null,console.log("GetuAI SDK destroyed"))}class lt extends O{static async init(t){return await R(t)}static async trackEvent(t,i,r,n,s=e.USD){return await F(t,i,r,n,s)}static async trackPageView(t,e){return await L(t,e)}static async trackPurchase(t,i,r=e.USD,n){return await M(t,i,r,n)}static async trackLogin(t,e){return await z(t,e)}static async trackSignup(t,e){return await V(t,e)}static async trackFormSubmit(t,e){return await $(t,e)}static async trackVideoPlay(t,e){return await G(t,e)}static async trackEmailVerification(t,e){return await B(t,e)}static async trackPurchaseAuto(t){return await Y(t)}static async trackLoginAuto(t){return await j(t)}static async trackSignupAuto(t){return await H(t)}static async trackEmailVerificationAuto(t){return await J(t)}static async trackCustomEvent(t,i,r,n,s=e.USD){return await W(t,i,r,n,s)}static async trackProductView(t,e){return await X(t,e)}static async trackAddToCart(t,e){return await Q(t,e)}static async trackPageClick(t,e){return await Z(t,e)}static async trackButtonClick(t,e,i){return await tt(t,e,i)}static getAttributionData(){return et()}static async flush(){return await it()}static getStatus(){return rt()}static addUTMToURL(t){return nt(t)}static getCurrentUTMParams(){return st()}static setUserId(t){ot(t)}static getUserId(){return at()}static removeUserId(){ct()}static destroy(){ut()}static getSDK(){return x()}static waitForSDK(){return N()}}"undefined"!=typeof document&&function(){if(K)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?R({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,domain:t.getAttribute("data-domain")||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:R,getSDK:x,waitForSDK:N,trackEvent:F,trackPageView:L,trackPageClick:Z,trackButtonClick:tt,trackPurchase:M,trackLogin:z,trackSignup:V,trackFormSubmit:$,trackVideoPlay:G,trackEmailVerification:B,trackAuditApproved:q,trackCustomEvent:W,trackProductView:X,trackAddToCart:Q,trackPurchaseAuto:Y,trackLoginAuto:j,trackSignupAuto:H,trackEmailVerificationAuto:J,getAttributionData:et,flush:it,getStatus:rt,addUTMToURL:nt,getCurrentUTMParams:st,setUserId:ot,getUserId:at,removeUserId:ct,destroy:ut,EventType:t,Currency:e,AttributionSDK:lt},window.init=R,window.waitForSDK=N,window.trackEvent=F,window.trackPageView=L,window.trackPageClick=Z,window.trackButtonClick=tt,window.trackPurchase=M,window.trackLogin=z,window.trackSignup=V,window.trackFormSubmit=$,window.trackVideoPlay=G,window.trackEmailVerification=B,window.trackProductView=X,window.trackAddToCart=Q,window.trackPurchaseAuto=Y,window.trackLoginAuto=j,window.trackSignupAuto=H,window.trackEmailVerificationAuto=J,window.getAttributionData=et,window.flush=it,window.getStatus=rt,window.addUTMToURL=nt,window.getCurrentUTMParams=st,window.setUserId=ot,window.getUserId=at,window.removeUserId=ct,window.destroy=ut,window.AttributionSDK=lt);const dt={init:R,getSDK:x,waitForSDK:N,trackEvent:F,trackPageView:L,trackPageClick:Z,trackButtonClick:tt,trackPurchase:M,trackLogin:z,trackSignup:V,trackFormSubmit:$,trackVideoPlay:G,trackEmailVerification:B,trackAuditApproved:q,trackCustomEvent:W,trackProductView:X,trackAddToCart:Q,trackPurchaseAuto:Y,trackLoginAuto:j,trackSignupAuto:H,trackEmailVerificationAuto:J,getAttributionData:et,flush:it,getStatus:rt,addUTMToURL:nt,getCurrentUTMParams:st,setUserId:ot,getUserId:at,removeUserId:ct,destroy:ut,EventType:t,Currency:e,AttributionSDK:lt};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:()=>ft}),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",o=[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 a(){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 f{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 p(){return document.referrer||""}function _(){return window.location.href}function y(){return document.title||""}function w(){return`session_${Date.now()}_${Math.random().toString(36).substr(2,9)}`}function S(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 k(t,e=["utm_source","utm_medium","utm_campaign"]){const i={};return e.forEach(e=>{t[e]&&(i[e]=t[e])}),i}function E(t){try{const e=t||window.location.href;return new URL(e).search}catch(t){return""}}function v(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{}}}const I="ga_anon_id",b="ga_anon_id",T="anon_";function A(){if("undefined"!=typeof crypto&&"function"==typeof crypto.randomUUID)return T+crypto.randomUUID().replace(/-/g,"");let t="";for(let e=0;e<32;e++)t+=Math.floor(16*Math.random()).toString(16);return T+t}class D{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 P{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},o=r.add(n);o.onsuccess=()=>{this.logger.debug("Event added to IndexedDB queue"),e()},o.onerror=()=>{this.logger.error("Failed to add event to IndexedDB:",o.error),i(o.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,o=!1;t.forEach(s=>{const a=r.get(s);a.onsuccess=()=>{if(a.result){const s={...a.result,sent:!0},c=r.put(s);c.onsuccess=()=>{n++,n!==t.length||o||e()},c.onerror=()=>{o=!0,this.logger.error("Failed to mark event as sent:",c.error),i(c.error)}}else n++,n!==t.length||o||e()},a.onerror=()=>{o=!0,this.logger.error("Failed to get event for marking as sent:",a.error),i(a.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 U{constructor(t){this.UTM_STORAGE_KEY="attribution_utm_data",this.SESSION_STORAGE_KEY="attribution_session",this.SESSION_COOKIE_KEY="_getuai_session",this.ATTRIB_COOKIE_KEY="_getuai_attrib",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.ATTRIB_COOKIE_EXPIRES=30,this.SESSION_COOKIE_EXPIRES=1,this.cookieDomain=null,this.logger=t,this.localStorage=new D(t),this.indexedDB=new P(t)}setCookieDomain(t){this.cookieDomain=t,this.logger.debug(t?`🍪 Cookie domain set to ${t} (cross-subdomain sharing enabled)`:"🍪 Cookie domain not set — using host-only cookies")}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,{days:this.USER_ID_COOKIE_EXPIRES,domain:this.cookieDomain}),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(){let t=null;try{t=this.getCookie(this.USER_ID_KEY)}catch(t){this.logger.debug("Failed to get user ID from cookie:",t)}if(t){try{"undefined"!=typeof localStorage&&localStorage.getItem(this.USER_ID_KEY)!==t&&localStorage.setItem(this.USER_ID_KEY,t)}catch(t){}return t}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)}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)}}getAnonymousId(){try{const t=this.getCookie(I);if(t&&t.startsWith(T)){try{localStorage.setItem(b,t)}catch{}return t}}catch(t){this.logger.debug("anonymous_id cookie read failed",t)}try{const t="undefined"!=typeof localStorage?localStorage.getItem(b):null;if(t&&t.startsWith(T))return this.setAnonymousId(t),t}catch(t){this.logger.debug("anonymous_id localStorage read failed",t)}const t=A();return this.setAnonymousId(t),this.logger.info(`🆔 Generated new anonymous_id: ${t}`),t}setAnonymousId(t){if(t&&t.startsWith(T)){try{this.setCookie(I,t,{days:365,domain:this.cookieDomain})}catch(t){this.logger.error("anonymous_id cookie write failed",t)}try{"undefined"!=typeof localStorage&&localStorage.setItem(b,t)}catch(t){this.logger.error("anonymous_id localStorage write failed",t)}}else this.logger.warn(`Refusing to set malformed anonymous_id: ${t}`)}rotateAnonymousId(){const t=A();return this.setAnonymousId(t),this.logger.info(`🔄 Rotated anonymous_id: ${t}`),t}setCookie(t,e,i={}){try{const r="number"==typeof i?{days:i}:i,n=r.days??this.USER_ID_COOKIE_EXPIRES,o=new Date;o.setTime(o.getTime()+24*n*60*60*1e3);const s=r.secure??("undefined"!=typeof location&&"https:"===location.protocol),a=r.domain??this.cookieDomain,c=[`${t}=${encodeURIComponent(e)}`,`expires=${o.toUTCString()}`,"path=/","SameSite=Lax"];return a&&c.push(`domain=${a}`),s&&c.push("Secure"),document.cookie=c.join(";"),!0}catch(t){return this.logger.error("Failed to set cookie:",t),!1}}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=/;`,this.cookieDomain&&(document.cookie=`${t}=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/;domain=${this.cookieDomain};`)}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.writeAttribution(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.readAttributionFromCookie();if(t&&t.expiresAt&&t.expiresAt>Date.now())return t;t&&this.deleteCookie(this.ATTRIB_COOKIE_KEY);const e=this.localStorage.get(this.UTM_STORAGE_KEY);return e&&e.expiresAt&&e.expiresAt>Date.now()?e:(e&&this.localStorage.remove(this.UTM_STORAGE_KEY),null)}storeSession(t){try{const e=JSON.stringify(t);this.setCookie(this.SESSION_COOKIE_KEY,e,{days:this.SESSION_COOKIE_EXPIRES,domain:this.cookieDomain})}catch(t){this.logger.debug("Failed to store session in cookie:",t)}this.localStorage.set(this.SESSION_STORAGE_KEY,t)}getSession(){try{const t=this.getCookie(this.SESSION_COOKIE_KEY);if(t)return JSON.parse(t)}catch(t){this.logger.debug("Failed to parse session cookie:",t)}return this.localStorage.get(this.SESSION_STORAGE_KEY)}migrateLegacyStorage(){try{if(!this.getCookie(this.SESSION_COOKIE_KEY)){const t=this.localStorage.get(this.SESSION_STORAGE_KEY);t&&(this.setCookie(this.SESSION_COOKIE_KEY,JSON.stringify(t),{days:this.SESSION_COOKIE_EXPIRES,domain:this.cookieDomain}),this.logger.debug("↗️ Migrated legacy session to cookie"))}}catch(t){this.logger.debug("Session migration skipped:",t)}try{if(!this.getCookie(this.ATTRIB_COOKIE_KEY)){const t=this.localStorage.get(this.UTM_STORAGE_KEY);t&&t.expiresAt&&t.expiresAt>Date.now()&&(this.writeAttribution(t),this.logger.debug("↗️ Migrated legacy attribution data to cookie"))}}catch(t){this.logger.debug("Attribution migration skipped:",t)}try{if(this.cookieDomain){const t=this.getCookie(this.USER_ID_KEY),e="undefined"!=typeof localStorage?localStorage.getItem(this.USER_ID_KEY):null;!t&&e&&(this.setCookie(this.USER_ID_KEY,e,{days:this.USER_ID_COOKIE_EXPIRES,domain:this.cookieDomain}),this.logger.debug("↗️ Migrated legacy user ID to cross-subdomain cookie"))}}catch(t){this.logger.debug("User ID migration skipped:",t)}}writeAttribution(t){this.localStorage.set(this.UTM_STORAGE_KEY,t);const e=Math.max(0,t.expiresAt-Date.now()),i=Math.max(1,Math.ceil(e/864e5));let r=encodeURIComponent(JSON.stringify(t));if(r.length>3800){const e={...t,touchpoints:(t.touchpoints||[]).slice(-20)};if(r=encodeURIComponent(JSON.stringify(e)),r.length>3800){const t={...e,touchpoints:[]};r=encodeURIComponent(JSON.stringify(t))}return this.logger.debug("Attribution cookie payload trimmed to fit size limit"),void this.setCookie(this.ATTRIB_COOKIE_KEY,decodeURIComponent(r),{days:i,domain:this.cookieDomain})}this.setCookie(this.ATTRIB_COOKIE_KEY,JSON.stringify(t),{days:i,domain:this.cookieDomain})}readAttributionFromCookie(){try{const t=this.getCookie(this.ATTRIB_COOKIE_KEY);return t?JSON.parse(t):null}catch(t){return this.logger.debug("Failed to parse attribution cookie:",t),null}}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 C="0.5.0";class O{constructor(t,e,i,r=100,n=2e3,o=3,s=1e3,a){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=o,this.retryDelay=s,this.sendEvents=a,this.debouncedProcess=function(t){let e;return(...i)=>{clearTimeout(e),e=setTimeout(()=>t(...i),100)}}(this.process.bind(this))}add(t){if(o.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,anonymous_id:t.anonymous_id,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||a(),custom_event_name:t.custom_event_name||void 0})),sdk_version:C};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 o=i*Math.pow(2,n);await new Promise(t=>setTimeout(t,o))}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,anonymous_id:t.anonymous_id,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||a(),custom_event_name:t.custom_event_name||void 0})),sdk_version:C},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,anonymous_id:t.anonymous_id,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||a(),custom_event_name:t.custom_event_name||void 0})),sdk_version:C},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}}}const R="__getuai_probe";function x(t){try{document.cookie=`${R}=1;domain=${t};path=/;SameSite=Lax`;const e=document.cookie.split(";").some(t=>t.trim().startsWith(`${R}=`));return e&&(document.cookie=`${R}=;domain=${t};path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT`),e}catch{return!1}}class N{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 f(this.config.enableDebug),this.storage=new U(this.logger),this.httpClient=new K(this.logger,this.config.apiKey,this.config.apiEndpoint||n,this.config.maxRetries,this.config.retryDelay),this.queue=new O(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");const t=function(t){if(!t)return null;const e=t.trim();return e?e.startsWith(".")?e:"."+e:null}(this.config.domain)??function(){if(!function(){try{return"undefined"!=typeof document&&"string"==typeof document.cookie}catch{return!1}}())return null;const t="undefined"!=typeof location&&location.hostname||"";if(!t)return null;if("localhost"===t)return null;if(function(t){return!!/^\d+\.\d+\.\d+\.\d+$/.test(t)||!!t.includes(":")}(t))return null;const e=t.split(".").filter(Boolean);if(e.length<2)return null;for(let t=e.length-2;t>=0;t--){const i="."+e.slice(t).join(".");if(x(i))return i}return null}();this.storage.setCookieDomain(t),this.storage.init().catch(t=>{this.logger.warn("Storage initialization failed (IndexedDB may be unavailable), continuing with basic features:",t)});try{this.storage.migrateLegacyStorage()}catch(t){this.logger.debug("Legacy storage migration skipped:",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,o,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:y(),referrer:p(),url:e.split("?")[0],querystring:E(e),query_params:v(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=this.storage.getAnonymousId(),m=n||this.getUserId(),f={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,anonymous_id:g,tracking_user_id:m||void 0,timestamp:a(),event_data:r,context:{page:l,session:d},revenue:o,currency:s,...i===t.CUSTOM&&c?{custom_event_name:c}:{},...this.getUTMParams(),device:h(),...this.getAdParams()};this.logger.debug(`Tracking event: ${i}`,f),this.queue.add(f)}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,o,s=e.USD){i&&""!==i.trim()?await this.trackEvent(t.CUSTOM,r,n,o,s,i.trim()):this.logger.error("trackCustomEvent requires a non-empty customEventName")}async trackPageView(e,i){const r=_(),n=r.split("?")[0],o=Date.now(),s=this.config.pageViewDebounceInterval||5e3,a=this.pageViewTrackTimes.get(n);if(a&&o-a<s)return void this.logger.debug(`Page view debounced: ${n} (last tracked ${o-a}ms ago)`);const c={url:r,title:y(),referrer:p(),user_agent:navigator.userAgent||"",...e};await this.trackEvent(t.PAGE_VIEW,c,i),this.pageViewTrackTimes.set(n,o),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,o){await this.trackEvent(t.PURCHASE,o,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?S(t,k(e.lastTouch,this.config.crossDomainUTMParams)):t}getCurrentUTMParams(){const t=this.getAttributionData();return t?k(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){if(!t||""===t.trim())return void this.logger.warn("Cannot set empty user ID");const e=t.trim(),i=this.storage.getUserId();if(i!==e){if(i&&i!==e&&(this.logger.info(`👥 User changed (${i} → ${e}) — rotating anonymous_id + session`),this.storage.rotateAnonymousId()),this.storage.setUserId(e),this.session){const t=this.session.userId;t&&t!==e?(this.logger.info(`🔄 Session previously owned by ${t} — rotating session for ${e}`),this.rotateSession(e)):t!==e&&(this.session={...this.session,userId:e},this.storage.storeSession(this.session))}this.logger.info(`👤 User ID set: ${e}`)}}rotateSession(t){const e=Date.now();this.session={sessionId:w(),startTime:e,lastActivity:e,pageViews:0,userId:t},this.storage.storeSession(this.session)}getUserId(){return this.storage.getUserId()}getAnonymousId(){return this.storage.getAnonymousId()}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(),i=this.storage.getUserId()??void 0,r=t&&e-t.lastActivity<this.config.sessionTimeout,n=!(!t?.userId||!i||t.userId===i);r&&!n?this.session={...t,lastActivity:e,userId:t.userId??i}:(n&&this.logger.info(`🔄 Session owner changed (${t?.userId} → ${i}) — starting new session`),this.session={sessionId:w(),startTime:e,lastActivity:e,pageViews:0,userId:i}),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(),o={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||o.touchpoints.push(r),this.storage.storeUTMData(o),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(),o=e.type?.toLowerCase();if("button"===i||"submit"===o||"reset"===o)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),o=t.some(t=>"file"===t.type),s=t.some(t=>"SELECT"===t.tagName&&t.multiple),a=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(o){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(a)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,o]of Object.entries(t))if((n.toLowerCase()===r||n.toLowerCase().includes(r))&&o&&"string"==typeof o&&o.trim()){e[i]=o.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=k(r.lastTouch,this.config.crossDomainUTMParams);if(0===Object.keys(n).length)return void this.logger.debug("No UTM parameters to pass");const o=S(i,n);o!==i&&(t.href=o,this.logger.debug("UTM parameters added to external link:",{original:i,enhanced:o,utmParams:n}),this.logger.debug("Cross-domain UTM passed:",{link_url:o,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 F=null;async function M(t){if(F)return console.warn("GetuAI SDK: Already initialized"),F;try{if(F=new N(t),await F.init(),t.enableDebug&&(window.GetuAISDK=F),console.log("GetuAI Attribution SDK initialized successfully"),"undefined"!=typeof window){const t=new CustomEvent("getuaiSDKReady",{detail:{sdk:F}});window.dispatchEvent(t)}return F}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 L(){return F}function z(){return new Promise((t,e)=>{if(F)return void t(F);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 $(t,i,r,n,o=e.USD){const s=L();s?await s.trackEvent(t,i,r,n,o):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function V(t,e){const i=L();i?await i.trackPageView(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function G(t,i,r=e.USD,n){const o=L();o?await o.trackPurchase(t,i,r,n):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function B(t,e){const i=L();i?await i.trackLogin(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function q(t,e){const i=L();i?await i.trackSignup(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function Y(t,e){const i=L();i?await i.trackFormSubmit(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function j(t,e){const i=L();i?await i.trackVideoPlay(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function H(t,e){const i=L();i?await i.trackEmailVerification(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function J(t,e){const i=L();i?await i.trackAuditApproved(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function W(t){const e=L();e?await e.trackPurchaseAuto(t):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function X(t){const e=L();e?await e.trackLoginAuto(t):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function Q(t){const e=L();e?await e.trackSignupAuto(t):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function Z(t){const e=L();e?await e.trackEmailVerificationAuto(t):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function tt(t,i,r,n,o=e.USD){const s=L();s?await s.trackCustomEvent(t,i,r,n,o):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function et(t,e){const i=L();i?await i.trackProductView(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function it(t,e){const i=L();i?await i.trackAddToCart(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function rt(t,e){const i=L();i?await i.trackPageClick(t,e):console.warn("GetuAI SDK: Not initialized. Call init() first.")}async function nt(t,e,i){const r=L();r?await r.trackButtonClick(t,e,i):console.warn("GetuAI SDK: Not initialized. Call init() first.")}function ot(){const t=L();return t?t.getAttributionData():null}async function st(){const t=L();t?await t.flush():console.warn("GetuAI SDK: Not initialized. Call init() first.")}function at(){const t=L();return t?t.getStatus():null}function ct(t){const e=L();return e?e.addUTMToURL(t):(console.warn("GetuAI SDK: Not initialized. Call init() first."),t)}function ut(){const t=L();return t?t.getCurrentUTMParams():{}}function lt(t){const e=L();e?e.setUserId(t):console.warn("GetuAI SDK: Not initialized. Call init() first.")}function dt(){const t=L();return t?t.getUserId():(console.warn("GetuAI SDK: Not initialized. Call init() first."),null)}function gt(){const t=L();t?t.removeUserId():console.warn("GetuAI SDK: Not initialized. Call init() first.")}function ht(){F&&(F.destroy(),F=null,console.log("GetuAI SDK destroyed"))}class mt extends N{static async init(t){return await M(t)}static async trackEvent(t,i,r,n,o=e.USD){return await $(t,i,r,n,o)}static async trackPageView(t,e){return await V(t,e)}static async trackPurchase(t,i,r=e.USD,n){return await G(t,i,r,n)}static async trackLogin(t,e){return await B(t,e)}static async trackSignup(t,e){return await q(t,e)}static async trackFormSubmit(t,e){return await Y(t,e)}static async trackVideoPlay(t,e){return await j(t,e)}static async trackEmailVerification(t,e){return await H(t,e)}static async trackPurchaseAuto(t){return await W(t)}static async trackLoginAuto(t){return await X(t)}static async trackSignupAuto(t){return await Q(t)}static async trackEmailVerificationAuto(t){return await Z(t)}static async trackCustomEvent(t,i,r,n,o=e.USD){return await tt(t,i,r,n,o)}static async trackProductView(t,e){return await et(t,e)}static async trackAddToCart(t,e){return await it(t,e)}static async trackPageClick(t,e){return await rt(t,e)}static async trackButtonClick(t,e,i){return await nt(t,e,i)}static getAttributionData(){return ot()}static async flush(){return await st()}static getStatus(){return at()}static addUTMToURL(t){return ct(t)}static getCurrentUTMParams(){return ut()}static setUserId(t){lt(t)}static getUserId(){return dt()}static removeUserId(){gt()}static destroy(){ht()}static getSDK(){return L()}static waitForSDK(){return z()}}"undefined"!=typeof document&&function(){if(F)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?M({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,domain:t.getAttribute("data-domain")||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:M,getSDK:L,waitForSDK:z,trackEvent:$,trackPageView:V,trackPageClick:rt,trackButtonClick:nt,trackPurchase:G,trackLogin:B,trackSignup:q,trackFormSubmit:Y,trackVideoPlay:j,trackEmailVerification:H,trackAuditApproved:J,trackCustomEvent:tt,trackProductView:et,trackAddToCart:it,trackPurchaseAuto:W,trackLoginAuto:X,trackSignupAuto:Q,trackEmailVerificationAuto:Z,getAttributionData:ot,flush:st,getStatus:at,addUTMToURL:ct,getCurrentUTMParams:ut,setUserId:lt,getUserId:dt,removeUserId:gt,destroy:ht,EventType:t,Currency:e,AttributionSDK:mt},window.init=M,window.waitForSDK=z,window.trackEvent=$,window.trackPageView=V,window.trackPageClick=rt,window.trackButtonClick=nt,window.trackPurchase=G,window.trackLogin=B,window.trackSignup=q,window.trackFormSubmit=Y,window.trackVideoPlay=j,window.trackEmailVerification=H,window.trackProductView=et,window.trackAddToCart=it,window.trackPurchaseAuto=W,window.trackLoginAuto=X,window.trackSignupAuto=Q,window.trackEmailVerificationAuto=Z,window.getAttributionData=ot,window.flush=st,window.getStatus=at,window.addUTMToURL=ct,window.getCurrentUTMParams=ut,window.setUserId=lt,window.getUserId=dt,window.removeUserId=gt,window.destroy=ht,window.AttributionSDK=mt);const ft={init:M,getSDK:L,waitForSDK:z,trackEvent:$,trackPageView:V,trackPageClick:rt,trackButtonClick:nt,trackPurchase:G,trackLogin:B,trackSignup:q,trackFormSubmit:Y,trackVideoPlay:j,trackEmailVerification:H,trackAuditApproved:J,trackCustomEvent:tt,trackProductView:et,trackAddToCart:it,trackPurchaseAuto:W,trackLoginAuto:X,trackSignupAuto:Q,trackEmailVerificationAuto:Z,getAttributionData:ot,flush:st,getStatus:at,addUTMToURL:ct,getCurrentUTMParams:ut,setUserId:lt,getUserId:dt,removeUserId:gt,destroy:ht,EventType:t,Currency:e,AttributionSDK:mt};return r.default})());
|
package/dist/index.esm.js
CHANGED
|
@@ -458,6 +458,20 @@ function cleanURL() {
|
|
|
458
458
|
}
|
|
459
459
|
}
|
|
460
460
|
|
|
461
|
+
const ANONYMOUS_ID_COOKIE = "ga_anon_id";
|
|
462
|
+
const ANONYMOUS_ID_LS_KEY = "ga_anon_id";
|
|
463
|
+
const ANONYMOUS_ID_TTL_DAYS = 365;
|
|
464
|
+
const ANONYMOUS_ID_PREFIX = "anon_";
|
|
465
|
+
function generateAnonymousId() {
|
|
466
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
467
|
+
return ANONYMOUS_ID_PREFIX + crypto.randomUUID().replace(/-/g, "");
|
|
468
|
+
}
|
|
469
|
+
let hex = "";
|
|
470
|
+
for (let i = 0; i < 32; i++) {
|
|
471
|
+
hex += Math.floor(Math.random() * 16).toString(16);
|
|
472
|
+
}
|
|
473
|
+
return ANONYMOUS_ID_PREFIX + hex;
|
|
474
|
+
}
|
|
461
475
|
// Upper bound on the serialized attribution payload we'll put in a cookie.
|
|
462
476
|
// Chrome/Firefox cap individual cookies at 4096 bytes; 3800 leaves headroom
|
|
463
477
|
// for the name, attributes, and URL-encoding overhead.
|
|
@@ -855,6 +869,72 @@ class AttributionStorageManager {
|
|
|
855
869
|
this.logger.error("Failed to remove user ID from localStorage:", error);
|
|
856
870
|
}
|
|
857
871
|
}
|
|
872
|
+
/** Get persistent anonymous_id; create + persist one if absent. */
|
|
873
|
+
getAnonymousId() {
|
|
874
|
+
// 1) Try cookie first (cross-subdomain reads).
|
|
875
|
+
try {
|
|
876
|
+
const cookieVal = this.getCookie(ANONYMOUS_ID_COOKIE);
|
|
877
|
+
if (cookieVal && cookieVal.startsWith(ANONYMOUS_ID_PREFIX)) {
|
|
878
|
+
try {
|
|
879
|
+
localStorage.setItem(ANONYMOUS_ID_LS_KEY, cookieVal);
|
|
880
|
+
}
|
|
881
|
+
catch { }
|
|
882
|
+
return cookieVal;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
catch (e) {
|
|
886
|
+
this.logger.debug("anonymous_id cookie read failed", e);
|
|
887
|
+
}
|
|
888
|
+
// 2) Fallback: localStorage.
|
|
889
|
+
try {
|
|
890
|
+
const lsVal = typeof localStorage !== "undefined"
|
|
891
|
+
? localStorage.getItem(ANONYMOUS_ID_LS_KEY)
|
|
892
|
+
: null;
|
|
893
|
+
if (lsVal && lsVal.startsWith(ANONYMOUS_ID_PREFIX)) {
|
|
894
|
+
this.setAnonymousId(lsVal);
|
|
895
|
+
return lsVal;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
catch (e) {
|
|
899
|
+
this.logger.debug("anonymous_id localStorage read failed", e);
|
|
900
|
+
}
|
|
901
|
+
// 3) Generate new.
|
|
902
|
+
const id = generateAnonymousId();
|
|
903
|
+
this.setAnonymousId(id);
|
|
904
|
+
this.logger.info(`🆔 Generated new anonymous_id: ${id}`);
|
|
905
|
+
return id;
|
|
906
|
+
}
|
|
907
|
+
/** Persist anonymous_id to cookie + localStorage. */
|
|
908
|
+
setAnonymousId(id) {
|
|
909
|
+
if (!id || !id.startsWith(ANONYMOUS_ID_PREFIX)) {
|
|
910
|
+
this.logger.warn(`Refusing to set malformed anonymous_id: ${id}`);
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
try {
|
|
914
|
+
this.setCookie(ANONYMOUS_ID_COOKIE, id, {
|
|
915
|
+
days: ANONYMOUS_ID_TTL_DAYS,
|
|
916
|
+
domain: this.cookieDomain,
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
catch (e) {
|
|
920
|
+
this.logger.error("anonymous_id cookie write failed", e);
|
|
921
|
+
}
|
|
922
|
+
try {
|
|
923
|
+
if (typeof localStorage !== "undefined") {
|
|
924
|
+
localStorage.setItem(ANONYMOUS_ID_LS_KEY, id);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
catch (e) {
|
|
928
|
+
this.logger.error("anonymous_id localStorage write failed", e);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
/** Generate a fresh anonymous_id, persist it, and return it. Used on Case X. */
|
|
932
|
+
rotateAnonymousId() {
|
|
933
|
+
const newId = generateAnonymousId();
|
|
934
|
+
this.setAnonymousId(newId);
|
|
935
|
+
this.logger.info(`🔄 Rotated anonymous_id: ${newId}`);
|
|
936
|
+
return newId;
|
|
937
|
+
}
|
|
858
938
|
// Cookie helper methods
|
|
859
939
|
setCookie(name, value, optionsOrDays = {}) {
|
|
860
940
|
try {
|
|
@@ -1250,7 +1330,7 @@ class AttributionStorageManager {
|
|
|
1250
1330
|
* This version is automatically updated from package.json during build
|
|
1251
1331
|
* DO NOT manually edit this file - it will be overwritten during build
|
|
1252
1332
|
*/
|
|
1253
|
-
const SDK_VERSION = "0.
|
|
1333
|
+
const SDK_VERSION = "0.5.0";
|
|
1254
1334
|
|
|
1255
1335
|
class EventQueueManager {
|
|
1256
1336
|
constructor(logger, apiKey, apiEndpoint, batchSize = 100, batchInterval = 2000, maxRetries = 3, retryDelay = 1000, sendEvents) {
|
|
@@ -1411,6 +1491,7 @@ class EventHttpClient {
|
|
|
1411
1491
|
const transformedEvents = events.map((event) => ({
|
|
1412
1492
|
event_id: event.event_id,
|
|
1413
1493
|
event_type: event.event_type,
|
|
1494
|
+
anonymous_id: event.anonymous_id,
|
|
1414
1495
|
tracking_user_id: event.tracking_user_id,
|
|
1415
1496
|
utm_source: event.utm_source || null,
|
|
1416
1497
|
utm_medium: event.utm_medium || null,
|
|
@@ -1460,6 +1541,7 @@ class EventHttpClient {
|
|
|
1460
1541
|
const transformedEvents = events.map((event) => ({
|
|
1461
1542
|
event_id: event.event_id,
|
|
1462
1543
|
event_type: event.event_type,
|
|
1544
|
+
anonymous_id: event.anonymous_id,
|
|
1463
1545
|
tracking_user_id: event.tracking_user_id,
|
|
1464
1546
|
utm_source: event.utm_source || null,
|
|
1465
1547
|
utm_medium: event.utm_medium || null,
|
|
@@ -1510,6 +1592,7 @@ class EventHttpClient {
|
|
|
1510
1592
|
const transformedEvents = events.map((event) => ({
|
|
1511
1593
|
event_id: event.event_id,
|
|
1512
1594
|
event_type: event.event_type,
|
|
1595
|
+
anonymous_id: event.anonymous_id,
|
|
1513
1596
|
tracking_user_id: event.tracking_user_id,
|
|
1514
1597
|
utm_source: event.utm_source || null,
|
|
1515
1598
|
utm_medium: event.utm_medium || null,
|
|
@@ -1783,10 +1866,12 @@ class AttributionSDK {
|
|
|
1783
1866
|
page_views: this.session?.pageViews,
|
|
1784
1867
|
};
|
|
1785
1868
|
// Use provided tracking_user_id or fallback to stored user ID
|
|
1869
|
+
const anonymousId = this.storage.getAnonymousId();
|
|
1786
1870
|
const finalUserId = tracking_user_id || this.getUserId();
|
|
1787
1871
|
const event = {
|
|
1788
1872
|
event_id: generateId(),
|
|
1789
1873
|
event_type: eventType,
|
|
1874
|
+
anonymous_id: anonymousId,
|
|
1790
1875
|
tracking_user_id: finalUserId || undefined,
|
|
1791
1876
|
timestamp: getTimestamp(),
|
|
1792
1877
|
event_data: eventData,
|
|
@@ -2079,29 +2164,37 @@ class AttributionSDK {
|
|
|
2079
2164
|
}
|
|
2080
2165
|
}
|
|
2081
2166
|
}
|
|
2082
|
-
// Set user ID
|
|
2167
|
+
// Set user ID — handles Case X (account switch) by rotating anonymous_id +
|
|
2168
|
+
// session_id. Anonymous → identified is idempotent on anonymous_id.
|
|
2083
2169
|
setUserId(userId) {
|
|
2084
2170
|
if (!userId || userId.trim() === "") {
|
|
2085
2171
|
this.logger.warn("Cannot set empty user ID");
|
|
2086
2172
|
return;
|
|
2087
2173
|
}
|
|
2088
2174
|
const trimmed = userId.trim();
|
|
2175
|
+
const prevUserId = this.storage.getUserId();
|
|
2176
|
+
// Idempotent: same user already set.
|
|
2177
|
+
if (prevUserId === trimmed) {
|
|
2178
|
+
return;
|
|
2179
|
+
}
|
|
2180
|
+
// Case X — distinct previous user means account switch on the same device.
|
|
2181
|
+
// Rotate anonymous_id so the new user's events are not stitched onto the
|
|
2182
|
+
// previous user's visitor identity.
|
|
2183
|
+
if (prevUserId && prevUserId !== trimmed) {
|
|
2184
|
+
this.logger.info(`👥 User changed (${prevUserId} → ${trimmed}) — rotating anonymous_id + session`);
|
|
2185
|
+
this.storage.rotateAnonymousId();
|
|
2186
|
+
}
|
|
2089
2187
|
this.storage.setUserId(trimmed);
|
|
2090
2188
|
// Bind this user to the current session. If the session was already
|
|
2091
|
-
// owned by a *different* non-empty user
|
|
2092
|
-
//
|
|
2093
|
-
// not share the same session_id.
|
|
2094
|
-
//
|
|
2095
|
-
// Note: during SDK init, setUserId() runs before initializeSession(),
|
|
2096
|
-
// so this.session may be null here — that's fine, initializeSession()
|
|
2097
|
-
// does its own reconciliation against storage.getUserId().
|
|
2189
|
+
// owned by a *different* non-empty user, force a session rotation
|
|
2190
|
+
// — distinct users' activity must not share the same session_id.
|
|
2098
2191
|
if (this.session) {
|
|
2099
|
-
const
|
|
2100
|
-
if (
|
|
2101
|
-
this.logger.info(`🔄
|
|
2192
|
+
const sessionPrevUserId = this.session.userId;
|
|
2193
|
+
if (sessionPrevUserId && sessionPrevUserId !== trimmed) {
|
|
2194
|
+
this.logger.info(`🔄 Session previously owned by ${sessionPrevUserId} — rotating session for ${trimmed}`);
|
|
2102
2195
|
this.rotateSession(trimmed);
|
|
2103
2196
|
}
|
|
2104
|
-
else if (
|
|
2197
|
+
else if (sessionPrevUserId !== trimmed) {
|
|
2105
2198
|
// Anonymous session learning the user's id — attach, no rotation.
|
|
2106
2199
|
this.session = { ...this.session, userId: trimmed };
|
|
2107
2200
|
this.storage.storeSession(this.session);
|
|
@@ -2126,6 +2219,10 @@ class AttributionSDK {
|
|
|
2126
2219
|
getUserId() {
|
|
2127
2220
|
return this.storage.getUserId();
|
|
2128
2221
|
}
|
|
2222
|
+
// Get the SDK's persistent anonymous_id (creates one on first access).
|
|
2223
|
+
getAnonymousId() {
|
|
2224
|
+
return this.storage.getAnonymousId();
|
|
2225
|
+
}
|
|
2129
2226
|
// Remove user ID
|
|
2130
2227
|
removeUserId() {
|
|
2131
2228
|
this.storage.removeUserId();
|