@cross-deck/web 0.5.0 → 0.6.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/CHANGELOG.md +35 -0
- package/README.md +19 -0
- package/dist/index.cjs +164 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +30 -2
- package/dist/index.d.ts +30 -2
- package/dist/index.mjs +164 -17
- package/dist/index.mjs.map +1 -1
- package/dist/react.cjs +164 -17
- package/dist/react.cjs.map +1 -1
- package/dist/react.mjs +164 -17
- package/dist/react.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -46,7 +46,7 @@ function typeMapForStatus(status) {
|
|
|
46
46
|
|
|
47
47
|
// src/http.ts
|
|
48
48
|
var SDK_NAME = "@cross-deck/web";
|
|
49
|
-
var SDK_VERSION = "0.
|
|
49
|
+
var SDK_VERSION = "0.6.0";
|
|
50
50
|
var DEFAULT_BASE_URL = "https://api.cross-deck.com/v1";
|
|
51
51
|
var HttpClient = class {
|
|
52
52
|
constructor(config) {
|
|
@@ -124,19 +124,25 @@ var HttpClient = class {
|
|
|
124
124
|
var KEY_ANON = "anon_id";
|
|
125
125
|
var KEY_CDCUST = "cdcust_id";
|
|
126
126
|
var IdentityStore = class {
|
|
127
|
-
constructor(
|
|
128
|
-
this.
|
|
127
|
+
constructor(primary, prefix, secondary) {
|
|
128
|
+
this.primary = primary;
|
|
129
129
|
this.prefix = prefix;
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
130
|
+
this.secondary = secondary ?? null;
|
|
131
|
+
const anonFromPrimary = primary.getItem(prefix + KEY_ANON);
|
|
132
|
+
const cdcustFromPrimary = primary.getItem(prefix + KEY_CDCUST);
|
|
133
|
+
const anonFromSecondary = this.secondary?.getItem(prefix + KEY_ANON) ?? null;
|
|
134
|
+
const cdcustFromSecondary = this.secondary?.getItem(prefix + KEY_CDCUST) ?? null;
|
|
135
|
+
const anon = anonFromPrimary ?? anonFromSecondary;
|
|
136
|
+
const cdcust = cdcustFromPrimary ?? cdcustFromSecondary;
|
|
134
137
|
this.state = {
|
|
135
|
-
anonymousId:
|
|
136
|
-
crossdeckCustomerId:
|
|
138
|
+
anonymousId: anon ?? this.mintAnonymousId(),
|
|
139
|
+
crossdeckCustomerId: cdcust
|
|
137
140
|
};
|
|
138
|
-
if (!
|
|
139
|
-
|
|
141
|
+
if (!anonFromPrimary || !anonFromSecondary) {
|
|
142
|
+
this.writeBoth(prefix + KEY_ANON, this.state.anonymousId);
|
|
143
|
+
}
|
|
144
|
+
if (cdcust && (!cdcustFromPrimary || !cdcustFromSecondary)) {
|
|
145
|
+
this.writeBoth(prefix + KEY_CDCUST, cdcust);
|
|
140
146
|
}
|
|
141
147
|
}
|
|
142
148
|
/** Return the persisted anonymous device ID (always set). */
|
|
@@ -150,7 +156,7 @@ var IdentityStore = class {
|
|
|
150
156
|
/** Persist a newly-resolved Crossdeck customer ID. */
|
|
151
157
|
setCrossdeckCustomerId(value) {
|
|
152
158
|
this.state.crossdeckCustomerId = value;
|
|
153
|
-
this.
|
|
159
|
+
this.writeBoth(this.prefix + KEY_CDCUST, value);
|
|
154
160
|
}
|
|
155
161
|
/**
|
|
156
162
|
* Wipe persisted identity. Called by reset() — used when an end-user
|
|
@@ -158,13 +164,13 @@ var IdentityStore = class {
|
|
|
158
164
|
* pre-login session is a fresh customer in the identity graph.
|
|
159
165
|
*/
|
|
160
166
|
reset() {
|
|
161
|
-
this.
|
|
162
|
-
this.
|
|
167
|
+
this.deleteBoth(this.prefix + KEY_ANON);
|
|
168
|
+
this.deleteBoth(this.prefix + KEY_CDCUST);
|
|
163
169
|
this.state = {
|
|
164
170
|
anonymousId: this.mintAnonymousId(),
|
|
165
171
|
crossdeckCustomerId: null
|
|
166
172
|
};
|
|
167
|
-
this.
|
|
173
|
+
this.writeBoth(this.prefix + KEY_ANON, this.state.anonymousId);
|
|
168
174
|
}
|
|
169
175
|
/**
|
|
170
176
|
* Generate an anonymousId. Crockford-ish base32 timestamp + random
|
|
@@ -176,6 +182,30 @@ var IdentityStore = class {
|
|
|
176
182
|
const rand = randomChars(10);
|
|
177
183
|
return `anon_${ts}${rand}`;
|
|
178
184
|
}
|
|
185
|
+
writeBoth(key, value) {
|
|
186
|
+
try {
|
|
187
|
+
this.primary.setItem(key, value);
|
|
188
|
+
} catch {
|
|
189
|
+
}
|
|
190
|
+
if (this.secondary) {
|
|
191
|
+
try {
|
|
192
|
+
this.secondary.setItem(key, value);
|
|
193
|
+
} catch {
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
deleteBoth(key) {
|
|
198
|
+
try {
|
|
199
|
+
this.primary.removeItem(key);
|
|
200
|
+
} catch {
|
|
201
|
+
}
|
|
202
|
+
if (this.secondary) {
|
|
203
|
+
try {
|
|
204
|
+
this.secondary.removeItem(key);
|
|
205
|
+
} catch {
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
179
209
|
};
|
|
180
210
|
function randomChars(count) {
|
|
181
211
|
const alphabet = "0123456789abcdefghijklmnopqrstuvwxyz";
|
|
@@ -401,6 +431,59 @@ var MemoryStorage = class {
|
|
|
401
431
|
this.store.delete(key);
|
|
402
432
|
}
|
|
403
433
|
};
|
|
434
|
+
var CookieStorage = class {
|
|
435
|
+
constructor(options) {
|
|
436
|
+
this.maxAgeSec = options?.maxAgeSec ?? 63072e3;
|
|
437
|
+
this.secure = options?.secure ?? defaultSecure();
|
|
438
|
+
this.sameSite = options?.sameSite ?? "Lax";
|
|
439
|
+
}
|
|
440
|
+
getItem(key) {
|
|
441
|
+
if (!hasDocument()) return null;
|
|
442
|
+
const doc = globalThis.document;
|
|
443
|
+
const cookies = doc.cookie ? doc.cookie.split(/;\s*/) : [];
|
|
444
|
+
const prefix = encodeURIComponent(key) + "=";
|
|
445
|
+
for (const c of cookies) {
|
|
446
|
+
if (c.startsWith(prefix)) {
|
|
447
|
+
try {
|
|
448
|
+
return decodeURIComponent(c.slice(prefix.length));
|
|
449
|
+
} catch {
|
|
450
|
+
return null;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
setItem(key, value) {
|
|
457
|
+
if (!hasDocument()) return;
|
|
458
|
+
const doc = globalThis.document;
|
|
459
|
+
const parts = [
|
|
460
|
+
`${encodeURIComponent(key)}=${encodeURIComponent(value)}`,
|
|
461
|
+
"Path=/",
|
|
462
|
+
`Max-Age=${this.maxAgeSec}`,
|
|
463
|
+
`SameSite=${this.sameSite}`
|
|
464
|
+
];
|
|
465
|
+
if (this.secure) parts.push("Secure");
|
|
466
|
+
try {
|
|
467
|
+
doc.cookie = parts.join("; ");
|
|
468
|
+
} catch {
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
removeItem(key) {
|
|
472
|
+
if (!hasDocument()) return;
|
|
473
|
+
const doc = globalThis.document;
|
|
474
|
+
const parts = [
|
|
475
|
+
`${encodeURIComponent(key)}=`,
|
|
476
|
+
"Path=/",
|
|
477
|
+
"Max-Age=0",
|
|
478
|
+
`SameSite=${this.sameSite}`
|
|
479
|
+
];
|
|
480
|
+
if (this.secure) parts.push("Secure");
|
|
481
|
+
try {
|
|
482
|
+
doc.cookie = parts.join("; ");
|
|
483
|
+
} catch {
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
};
|
|
404
487
|
function detectDefaultStorage() {
|
|
405
488
|
try {
|
|
406
489
|
const ls = globalThis.localStorage;
|
|
@@ -414,6 +497,17 @@ function detectDefaultStorage() {
|
|
|
414
497
|
}
|
|
415
498
|
return new MemoryStorage();
|
|
416
499
|
}
|
|
500
|
+
function defaultSecure() {
|
|
501
|
+
try {
|
|
502
|
+
const loc = globalThis.location;
|
|
503
|
+
return loc?.protocol === "https:";
|
|
504
|
+
} catch {
|
|
505
|
+
return false;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
function hasDocument() {
|
|
509
|
+
return typeof globalThis.document !== "undefined";
|
|
510
|
+
}
|
|
417
511
|
|
|
418
512
|
// src/device-info.ts
|
|
419
513
|
function isBrowser() {
|
|
@@ -514,6 +608,14 @@ var DEFAULT_AUTO_TRACK = {
|
|
|
514
608
|
deviceInfo: true
|
|
515
609
|
};
|
|
516
610
|
var SESSION_RESUME_THRESHOLD_MS = 30 * 60 * 1e3;
|
|
611
|
+
var EMPTY_ACQUISITION = {
|
|
612
|
+
utm_source: "",
|
|
613
|
+
utm_medium: "",
|
|
614
|
+
utm_campaign: "",
|
|
615
|
+
utm_content: "",
|
|
616
|
+
utm_term: "",
|
|
617
|
+
referrer: ""
|
|
618
|
+
};
|
|
517
619
|
var AutoTracker = class {
|
|
518
620
|
constructor(cfg, track) {
|
|
519
621
|
this.cfg = cfg;
|
|
@@ -549,6 +651,18 @@ var AutoTracker = class {
|
|
|
549
651
|
get currentSessionId() {
|
|
550
652
|
return this.session?.sessionId ?? null;
|
|
551
653
|
}
|
|
654
|
+
/**
|
|
655
|
+
* Per-session acquisition context — utm_* + referrer, captured once
|
|
656
|
+
* at session start. Returns empty strings when there's no session
|
|
657
|
+
* (Node, before init, after uninstall) so callers can spread without
|
|
658
|
+
* conditional logic. Bank-grade rule: capture once, attach to every
|
|
659
|
+
* event of the session, don't re-read on every track() (the URL
|
|
660
|
+
* changes via SPA pushState; the source-of-record is the URL we
|
|
661
|
+
* landed on).
|
|
662
|
+
*/
|
|
663
|
+
get currentAcquisition() {
|
|
664
|
+
return this.session?.acquisition ?? EMPTY_ACQUISITION;
|
|
665
|
+
}
|
|
552
666
|
// ---------- sessions ----------
|
|
553
667
|
installSessionTracking() {
|
|
554
668
|
this.session = this.startNewSession();
|
|
@@ -586,7 +700,8 @@ var AutoTracker = class {
|
|
|
586
700
|
sessionId: mintSessionId(),
|
|
587
701
|
startedAt: Date.now(),
|
|
588
702
|
hiddenAt: null,
|
|
589
|
-
endedSent: false
|
|
703
|
+
endedSent: false,
|
|
704
|
+
acquisition: captureAcquisition()
|
|
590
705
|
};
|
|
591
706
|
}
|
|
592
707
|
emitSessionStart() {
|
|
@@ -652,6 +767,26 @@ function mintSessionId() {
|
|
|
652
767
|
const ts = Date.now().toString(36);
|
|
653
768
|
return `sess_${ts}${randomChars(10)}`;
|
|
654
769
|
}
|
|
770
|
+
function captureAcquisition() {
|
|
771
|
+
if (!isBrowserSafe()) return { ...EMPTY_ACQUISITION };
|
|
772
|
+
const result = { ...EMPTY_ACQUISITION };
|
|
773
|
+
try {
|
|
774
|
+
const w = globalThis.window;
|
|
775
|
+
const params = new URLSearchParams(w.location.search ?? "");
|
|
776
|
+
result.utm_source = params.get("utm_source") ?? "";
|
|
777
|
+
result.utm_medium = params.get("utm_medium") ?? "";
|
|
778
|
+
result.utm_campaign = params.get("utm_campaign") ?? "";
|
|
779
|
+
result.utm_content = params.get("utm_content") ?? "";
|
|
780
|
+
result.utm_term = params.get("utm_term") ?? "";
|
|
781
|
+
} catch {
|
|
782
|
+
}
|
|
783
|
+
try {
|
|
784
|
+
const doc = globalThis.document;
|
|
785
|
+
if (typeof doc.referrer === "string") result.referrer = doc.referrer;
|
|
786
|
+
} catch {
|
|
787
|
+
}
|
|
788
|
+
return result;
|
|
789
|
+
}
|
|
655
790
|
|
|
656
791
|
// src/debug.ts
|
|
657
792
|
var SENSITIVE_KEY_PATTERNS = [
|
|
@@ -773,7 +908,10 @@ var CrossdeckClient = class {
|
|
|
773
908
|
sdkVersion: opts.sdkVersion
|
|
774
909
|
});
|
|
775
910
|
const effectiveStorage = persistIdentity ? storage : new MemoryStorage();
|
|
776
|
-
const
|
|
911
|
+
const useCookieRedundancy = persistIdentity && !options.storage && // honour caller's adapter choice
|
|
912
|
+
typeof globalThis.document !== "undefined";
|
|
913
|
+
const cookieStore = useCookieRedundancy ? new CookieStorage() : void 0;
|
|
914
|
+
const identity = new IdentityStore(effectiveStorage, opts.storagePrefix, cookieStore);
|
|
777
915
|
const entitlements = new EntitlementCache();
|
|
778
916
|
const events = new EventQueue({
|
|
779
917
|
http,
|
|
@@ -954,6 +1092,15 @@ var CrossdeckClient = class {
|
|
|
954
1092
|
const enriched = { ...s.deviceInfo };
|
|
955
1093
|
const sessionId = s.autoTracker?.currentSessionId;
|
|
956
1094
|
if (sessionId) enriched.sessionId = sessionId;
|
|
1095
|
+
const acquisition = s.autoTracker?.currentAcquisition;
|
|
1096
|
+
if (acquisition) {
|
|
1097
|
+
if (acquisition.utm_source) enriched.utm_source = acquisition.utm_source;
|
|
1098
|
+
if (acquisition.utm_medium) enriched.utm_medium = acquisition.utm_medium;
|
|
1099
|
+
if (acquisition.utm_campaign) enriched.utm_campaign = acquisition.utm_campaign;
|
|
1100
|
+
if (acquisition.utm_content) enriched.utm_content = acquisition.utm_content;
|
|
1101
|
+
if (acquisition.utm_term) enriched.utm_term = acquisition.utm_term;
|
|
1102
|
+
if (acquisition.referrer) enriched.referrer = acquisition.referrer;
|
|
1103
|
+
}
|
|
957
1104
|
if (properties) Object.assign(enriched, properties);
|
|
958
1105
|
const event = {
|
|
959
1106
|
eventId: this.mintEventId(),
|