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