@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/react.cjs
CHANGED
|
@@ -74,7 +74,7 @@ function typeMapForStatus(status) {
|
|
|
74
74
|
|
|
75
75
|
// src/http.ts
|
|
76
76
|
var SDK_NAME = "@cross-deck/web";
|
|
77
|
-
var SDK_VERSION = "0.
|
|
77
|
+
var SDK_VERSION = "0.6.0";
|
|
78
78
|
var DEFAULT_BASE_URL = "https://api.cross-deck.com/v1";
|
|
79
79
|
var HttpClient = class {
|
|
80
80
|
constructor(config) {
|
|
@@ -152,19 +152,25 @@ var HttpClient = class {
|
|
|
152
152
|
var KEY_ANON = "anon_id";
|
|
153
153
|
var KEY_CDCUST = "cdcust_id";
|
|
154
154
|
var IdentityStore = class {
|
|
155
|
-
constructor(
|
|
156
|
-
this.
|
|
155
|
+
constructor(primary, prefix, secondary) {
|
|
156
|
+
this.primary = primary;
|
|
157
157
|
this.prefix = prefix;
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
158
|
+
this.secondary = secondary ?? null;
|
|
159
|
+
const anonFromPrimary = primary.getItem(prefix + KEY_ANON);
|
|
160
|
+
const cdcustFromPrimary = primary.getItem(prefix + KEY_CDCUST);
|
|
161
|
+
const anonFromSecondary = this.secondary?.getItem(prefix + KEY_ANON) ?? null;
|
|
162
|
+
const cdcustFromSecondary = this.secondary?.getItem(prefix + KEY_CDCUST) ?? null;
|
|
163
|
+
const anon = anonFromPrimary ?? anonFromSecondary;
|
|
164
|
+
const cdcust = cdcustFromPrimary ?? cdcustFromSecondary;
|
|
162
165
|
this.state = {
|
|
163
|
-
anonymousId:
|
|
164
|
-
crossdeckCustomerId:
|
|
166
|
+
anonymousId: anon ?? this.mintAnonymousId(),
|
|
167
|
+
crossdeckCustomerId: cdcust
|
|
165
168
|
};
|
|
166
|
-
if (!
|
|
167
|
-
|
|
169
|
+
if (!anonFromPrimary || !anonFromSecondary) {
|
|
170
|
+
this.writeBoth(prefix + KEY_ANON, this.state.anonymousId);
|
|
171
|
+
}
|
|
172
|
+
if (cdcust && (!cdcustFromPrimary || !cdcustFromSecondary)) {
|
|
173
|
+
this.writeBoth(prefix + KEY_CDCUST, cdcust);
|
|
168
174
|
}
|
|
169
175
|
}
|
|
170
176
|
/** Return the persisted anonymous device ID (always set). */
|
|
@@ -178,7 +184,7 @@ var IdentityStore = class {
|
|
|
178
184
|
/** Persist a newly-resolved Crossdeck customer ID. */
|
|
179
185
|
setCrossdeckCustomerId(value) {
|
|
180
186
|
this.state.crossdeckCustomerId = value;
|
|
181
|
-
this.
|
|
187
|
+
this.writeBoth(this.prefix + KEY_CDCUST, value);
|
|
182
188
|
}
|
|
183
189
|
/**
|
|
184
190
|
* Wipe persisted identity. Called by reset() — used when an end-user
|
|
@@ -186,13 +192,13 @@ var IdentityStore = class {
|
|
|
186
192
|
* pre-login session is a fresh customer in the identity graph.
|
|
187
193
|
*/
|
|
188
194
|
reset() {
|
|
189
|
-
this.
|
|
190
|
-
this.
|
|
195
|
+
this.deleteBoth(this.prefix + KEY_ANON);
|
|
196
|
+
this.deleteBoth(this.prefix + KEY_CDCUST);
|
|
191
197
|
this.state = {
|
|
192
198
|
anonymousId: this.mintAnonymousId(),
|
|
193
199
|
crossdeckCustomerId: null
|
|
194
200
|
};
|
|
195
|
-
this.
|
|
201
|
+
this.writeBoth(this.prefix + KEY_ANON, this.state.anonymousId);
|
|
196
202
|
}
|
|
197
203
|
/**
|
|
198
204
|
* Generate an anonymousId. Crockford-ish base32 timestamp + random
|
|
@@ -204,6 +210,30 @@ var IdentityStore = class {
|
|
|
204
210
|
const rand = randomChars(10);
|
|
205
211
|
return `anon_${ts}${rand}`;
|
|
206
212
|
}
|
|
213
|
+
writeBoth(key, value) {
|
|
214
|
+
try {
|
|
215
|
+
this.primary.setItem(key, value);
|
|
216
|
+
} catch {
|
|
217
|
+
}
|
|
218
|
+
if (this.secondary) {
|
|
219
|
+
try {
|
|
220
|
+
this.secondary.setItem(key, value);
|
|
221
|
+
} catch {
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
deleteBoth(key) {
|
|
226
|
+
try {
|
|
227
|
+
this.primary.removeItem(key);
|
|
228
|
+
} catch {
|
|
229
|
+
}
|
|
230
|
+
if (this.secondary) {
|
|
231
|
+
try {
|
|
232
|
+
this.secondary.removeItem(key);
|
|
233
|
+
} catch {
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
207
237
|
};
|
|
208
238
|
function randomChars(count) {
|
|
209
239
|
const alphabet = "0123456789abcdefghijklmnopqrstuvwxyz";
|
|
@@ -429,6 +459,59 @@ var MemoryStorage = class {
|
|
|
429
459
|
this.store.delete(key);
|
|
430
460
|
}
|
|
431
461
|
};
|
|
462
|
+
var CookieStorage = class {
|
|
463
|
+
constructor(options) {
|
|
464
|
+
this.maxAgeSec = options?.maxAgeSec ?? 63072e3;
|
|
465
|
+
this.secure = options?.secure ?? defaultSecure();
|
|
466
|
+
this.sameSite = options?.sameSite ?? "Lax";
|
|
467
|
+
}
|
|
468
|
+
getItem(key) {
|
|
469
|
+
if (!hasDocument()) return null;
|
|
470
|
+
const doc = globalThis.document;
|
|
471
|
+
const cookies = doc.cookie ? doc.cookie.split(/;\s*/) : [];
|
|
472
|
+
const prefix = encodeURIComponent(key) + "=";
|
|
473
|
+
for (const c of cookies) {
|
|
474
|
+
if (c.startsWith(prefix)) {
|
|
475
|
+
try {
|
|
476
|
+
return decodeURIComponent(c.slice(prefix.length));
|
|
477
|
+
} catch {
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
setItem(key, value) {
|
|
485
|
+
if (!hasDocument()) return;
|
|
486
|
+
const doc = globalThis.document;
|
|
487
|
+
const parts = [
|
|
488
|
+
`${encodeURIComponent(key)}=${encodeURIComponent(value)}`,
|
|
489
|
+
"Path=/",
|
|
490
|
+
`Max-Age=${this.maxAgeSec}`,
|
|
491
|
+
`SameSite=${this.sameSite}`
|
|
492
|
+
];
|
|
493
|
+
if (this.secure) parts.push("Secure");
|
|
494
|
+
try {
|
|
495
|
+
doc.cookie = parts.join("; ");
|
|
496
|
+
} catch {
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
removeItem(key) {
|
|
500
|
+
if (!hasDocument()) return;
|
|
501
|
+
const doc = globalThis.document;
|
|
502
|
+
const parts = [
|
|
503
|
+
`${encodeURIComponent(key)}=`,
|
|
504
|
+
"Path=/",
|
|
505
|
+
"Max-Age=0",
|
|
506
|
+
`SameSite=${this.sameSite}`
|
|
507
|
+
];
|
|
508
|
+
if (this.secure) parts.push("Secure");
|
|
509
|
+
try {
|
|
510
|
+
doc.cookie = parts.join("; ");
|
|
511
|
+
} catch {
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
};
|
|
432
515
|
function detectDefaultStorage() {
|
|
433
516
|
try {
|
|
434
517
|
const ls = globalThis.localStorage;
|
|
@@ -442,6 +525,17 @@ function detectDefaultStorage() {
|
|
|
442
525
|
}
|
|
443
526
|
return new MemoryStorage();
|
|
444
527
|
}
|
|
528
|
+
function defaultSecure() {
|
|
529
|
+
try {
|
|
530
|
+
const loc = globalThis.location;
|
|
531
|
+
return loc?.protocol === "https:";
|
|
532
|
+
} catch {
|
|
533
|
+
return false;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
function hasDocument() {
|
|
537
|
+
return typeof globalThis.document !== "undefined";
|
|
538
|
+
}
|
|
445
539
|
|
|
446
540
|
// src/device-info.ts
|
|
447
541
|
function isBrowser() {
|
|
@@ -542,6 +636,14 @@ var DEFAULT_AUTO_TRACK = {
|
|
|
542
636
|
deviceInfo: true
|
|
543
637
|
};
|
|
544
638
|
var SESSION_RESUME_THRESHOLD_MS = 30 * 60 * 1e3;
|
|
639
|
+
var EMPTY_ACQUISITION = {
|
|
640
|
+
utm_source: "",
|
|
641
|
+
utm_medium: "",
|
|
642
|
+
utm_campaign: "",
|
|
643
|
+
utm_content: "",
|
|
644
|
+
utm_term: "",
|
|
645
|
+
referrer: ""
|
|
646
|
+
};
|
|
545
647
|
var AutoTracker = class {
|
|
546
648
|
constructor(cfg, track) {
|
|
547
649
|
this.cfg = cfg;
|
|
@@ -577,6 +679,18 @@ var AutoTracker = class {
|
|
|
577
679
|
get currentSessionId() {
|
|
578
680
|
return this.session?.sessionId ?? null;
|
|
579
681
|
}
|
|
682
|
+
/**
|
|
683
|
+
* Per-session acquisition context — utm_* + referrer, captured once
|
|
684
|
+
* at session start. Returns empty strings when there's no session
|
|
685
|
+
* (Node, before init, after uninstall) so callers can spread without
|
|
686
|
+
* conditional logic. Bank-grade rule: capture once, attach to every
|
|
687
|
+
* event of the session, don't re-read on every track() (the URL
|
|
688
|
+
* changes via SPA pushState; the source-of-record is the URL we
|
|
689
|
+
* landed on).
|
|
690
|
+
*/
|
|
691
|
+
get currentAcquisition() {
|
|
692
|
+
return this.session?.acquisition ?? EMPTY_ACQUISITION;
|
|
693
|
+
}
|
|
580
694
|
// ---------- sessions ----------
|
|
581
695
|
installSessionTracking() {
|
|
582
696
|
this.session = this.startNewSession();
|
|
@@ -614,7 +728,8 @@ var AutoTracker = class {
|
|
|
614
728
|
sessionId: mintSessionId(),
|
|
615
729
|
startedAt: Date.now(),
|
|
616
730
|
hiddenAt: null,
|
|
617
|
-
endedSent: false
|
|
731
|
+
endedSent: false,
|
|
732
|
+
acquisition: captureAcquisition()
|
|
618
733
|
};
|
|
619
734
|
}
|
|
620
735
|
emitSessionStart() {
|
|
@@ -680,6 +795,26 @@ function mintSessionId() {
|
|
|
680
795
|
const ts = Date.now().toString(36);
|
|
681
796
|
return `sess_${ts}${randomChars(10)}`;
|
|
682
797
|
}
|
|
798
|
+
function captureAcquisition() {
|
|
799
|
+
if (!isBrowserSafe()) return { ...EMPTY_ACQUISITION };
|
|
800
|
+
const result = { ...EMPTY_ACQUISITION };
|
|
801
|
+
try {
|
|
802
|
+
const w = globalThis.window;
|
|
803
|
+
const params = new URLSearchParams(w.location.search ?? "");
|
|
804
|
+
result.utm_source = params.get("utm_source") ?? "";
|
|
805
|
+
result.utm_medium = params.get("utm_medium") ?? "";
|
|
806
|
+
result.utm_campaign = params.get("utm_campaign") ?? "";
|
|
807
|
+
result.utm_content = params.get("utm_content") ?? "";
|
|
808
|
+
result.utm_term = params.get("utm_term") ?? "";
|
|
809
|
+
} catch {
|
|
810
|
+
}
|
|
811
|
+
try {
|
|
812
|
+
const doc = globalThis.document;
|
|
813
|
+
if (typeof doc.referrer === "string") result.referrer = doc.referrer;
|
|
814
|
+
} catch {
|
|
815
|
+
}
|
|
816
|
+
return result;
|
|
817
|
+
}
|
|
683
818
|
|
|
684
819
|
// src/debug.ts
|
|
685
820
|
var SENSITIVE_KEY_PATTERNS = [
|
|
@@ -801,7 +936,10 @@ var CrossdeckClient = class {
|
|
|
801
936
|
sdkVersion: opts.sdkVersion
|
|
802
937
|
});
|
|
803
938
|
const effectiveStorage = persistIdentity ? storage : new MemoryStorage();
|
|
804
|
-
const
|
|
939
|
+
const useCookieRedundancy = persistIdentity && !options.storage && // honour caller's adapter choice
|
|
940
|
+
typeof globalThis.document !== "undefined";
|
|
941
|
+
const cookieStore = useCookieRedundancy ? new CookieStorage() : void 0;
|
|
942
|
+
const identity = new IdentityStore(effectiveStorage, opts.storagePrefix, cookieStore);
|
|
805
943
|
const entitlements = new EntitlementCache();
|
|
806
944
|
const events = new EventQueue({
|
|
807
945
|
http,
|
|
@@ -982,6 +1120,15 @@ var CrossdeckClient = class {
|
|
|
982
1120
|
const enriched = { ...s.deviceInfo };
|
|
983
1121
|
const sessionId = s.autoTracker?.currentSessionId;
|
|
984
1122
|
if (sessionId) enriched.sessionId = sessionId;
|
|
1123
|
+
const acquisition = s.autoTracker?.currentAcquisition;
|
|
1124
|
+
if (acquisition) {
|
|
1125
|
+
if (acquisition.utm_source) enriched.utm_source = acquisition.utm_source;
|
|
1126
|
+
if (acquisition.utm_medium) enriched.utm_medium = acquisition.utm_medium;
|
|
1127
|
+
if (acquisition.utm_campaign) enriched.utm_campaign = acquisition.utm_campaign;
|
|
1128
|
+
if (acquisition.utm_content) enriched.utm_content = acquisition.utm_content;
|
|
1129
|
+
if (acquisition.utm_term) enriched.utm_term = acquisition.utm_term;
|
|
1130
|
+
if (acquisition.referrer) enriched.referrer = acquisition.referrer;
|
|
1131
|
+
}
|
|
985
1132
|
if (properties) Object.assign(enriched, properties);
|
|
986
1133
|
const event = {
|
|
987
1134
|
eventId: this.mintEventId(),
|