@cross-deck/web 1.3.1 → 1.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/CHANGELOG.md +43 -1
- package/README.md +13 -1
- package/dist/contracts.json +378 -0
- package/dist/crossdeck.umd.min.js +2 -2
- package/dist/crossdeck.umd.min.js.map +1 -1
- package/dist/error-codes.json +106 -1
- package/dist/index.cjs +940 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +177 -4
- package/dist/index.d.ts +177 -4
- package/dist/index.mjs +939 -22
- package/dist/index.mjs.map +1 -1
- package/dist/react.cjs +404 -22
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.mts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.mjs +404 -22
- package/dist/react.mjs.map +1 -1
- package/dist/{types-BzoKor4z.d.mts → types-Bu3jbmdq.d.mts} +4 -0
- package/dist/{types-BzoKor4z.d.ts → types-Bu3jbmdq.d.ts} +4 -0
- package/dist/vue.cjs +404 -22
- package/dist/vue.cjs.map +1 -1
- package/dist/vue.mjs +404 -22
- package/dist/vue.mjs.map +1 -1
- package/package.json +4 -3
package/dist/react.d.mts
CHANGED
package/dist/react.d.ts
CHANGED
package/dist/react.mjs
CHANGED
|
@@ -67,7 +67,7 @@ function typeMapForStatus(status) {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
// src/_version.ts
|
|
70
|
-
var SDK_VERSION = "1.
|
|
70
|
+
var SDK_VERSION = "1.4.2";
|
|
71
71
|
var SDK_NAME = "@cross-deck/web";
|
|
72
72
|
|
|
73
73
|
// src/http.ts
|
|
@@ -317,29 +317,258 @@ function randomChars(count) {
|
|
|
317
317
|
return out.join("");
|
|
318
318
|
}
|
|
319
319
|
|
|
320
|
+
// src/hash.ts
|
|
321
|
+
var K = new Uint32Array([
|
|
322
|
+
1116352408,
|
|
323
|
+
1899447441,
|
|
324
|
+
3049323471,
|
|
325
|
+
3921009573,
|
|
326
|
+
961987163,
|
|
327
|
+
1508970993,
|
|
328
|
+
2453635748,
|
|
329
|
+
2870763221,
|
|
330
|
+
3624381080,
|
|
331
|
+
310598401,
|
|
332
|
+
607225278,
|
|
333
|
+
1426881987,
|
|
334
|
+
1925078388,
|
|
335
|
+
2162078206,
|
|
336
|
+
2614888103,
|
|
337
|
+
3248222580,
|
|
338
|
+
3835390401,
|
|
339
|
+
4022224774,
|
|
340
|
+
264347078,
|
|
341
|
+
604807628,
|
|
342
|
+
770255983,
|
|
343
|
+
1249150122,
|
|
344
|
+
1555081692,
|
|
345
|
+
1996064986,
|
|
346
|
+
2554220882,
|
|
347
|
+
2821834349,
|
|
348
|
+
2952996808,
|
|
349
|
+
3210313671,
|
|
350
|
+
3336571891,
|
|
351
|
+
3584528711,
|
|
352
|
+
113926993,
|
|
353
|
+
338241895,
|
|
354
|
+
666307205,
|
|
355
|
+
773529912,
|
|
356
|
+
1294757372,
|
|
357
|
+
1396182291,
|
|
358
|
+
1695183700,
|
|
359
|
+
1986661051,
|
|
360
|
+
2177026350,
|
|
361
|
+
2456956037,
|
|
362
|
+
2730485921,
|
|
363
|
+
2820302411,
|
|
364
|
+
3259730800,
|
|
365
|
+
3345764771,
|
|
366
|
+
3516065817,
|
|
367
|
+
3600352804,
|
|
368
|
+
4094571909,
|
|
369
|
+
275423344,
|
|
370
|
+
430227734,
|
|
371
|
+
506948616,
|
|
372
|
+
659060556,
|
|
373
|
+
883997877,
|
|
374
|
+
958139571,
|
|
375
|
+
1322822218,
|
|
376
|
+
1537002063,
|
|
377
|
+
1747873779,
|
|
378
|
+
1955562222,
|
|
379
|
+
2024104815,
|
|
380
|
+
2227730452,
|
|
381
|
+
2361852424,
|
|
382
|
+
2428436474,
|
|
383
|
+
2756734187,
|
|
384
|
+
3204031479,
|
|
385
|
+
3329325298
|
|
386
|
+
]);
|
|
387
|
+
function utf8Bytes(input) {
|
|
388
|
+
if (typeof TextEncoder !== "undefined") {
|
|
389
|
+
return new TextEncoder().encode(input);
|
|
390
|
+
}
|
|
391
|
+
const out = [];
|
|
392
|
+
for (let i = 0; i < input.length; i++) {
|
|
393
|
+
let codePoint = input.charCodeAt(i);
|
|
394
|
+
if (codePoint >= 55296 && codePoint <= 56319 && i + 1 < input.length) {
|
|
395
|
+
const next = input.charCodeAt(i + 1);
|
|
396
|
+
if (next >= 56320 && next <= 57343) {
|
|
397
|
+
codePoint = 65536 + (codePoint - 55296 << 10) + (next - 56320);
|
|
398
|
+
i++;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (codePoint < 128) {
|
|
402
|
+
out.push(codePoint);
|
|
403
|
+
} else if (codePoint < 2048) {
|
|
404
|
+
out.push(192 | codePoint >> 6);
|
|
405
|
+
out.push(128 | codePoint & 63);
|
|
406
|
+
} else if (codePoint < 65536) {
|
|
407
|
+
out.push(224 | codePoint >> 12);
|
|
408
|
+
out.push(128 | codePoint >> 6 & 63);
|
|
409
|
+
out.push(128 | codePoint & 63);
|
|
410
|
+
} else {
|
|
411
|
+
out.push(240 | codePoint >> 18);
|
|
412
|
+
out.push(128 | codePoint >> 12 & 63);
|
|
413
|
+
out.push(128 | codePoint >> 6 & 63);
|
|
414
|
+
out.push(128 | codePoint & 63);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return new Uint8Array(out);
|
|
418
|
+
}
|
|
419
|
+
function sha256Hex(input) {
|
|
420
|
+
const bytes = utf8Bytes(input);
|
|
421
|
+
const bitLength = bytes.length * 8;
|
|
422
|
+
const blockCount = Math.floor((bytes.length + 9 + 63) / 64);
|
|
423
|
+
const padded = new Uint8Array(blockCount * 64);
|
|
424
|
+
padded.set(bytes);
|
|
425
|
+
padded[bytes.length] = 128;
|
|
426
|
+
const high = Math.floor(bitLength / 4294967296);
|
|
427
|
+
const low = bitLength >>> 0;
|
|
428
|
+
const lenOffset = padded.length - 8;
|
|
429
|
+
padded[lenOffset + 0] = high >>> 24 & 255;
|
|
430
|
+
padded[lenOffset + 1] = high >>> 16 & 255;
|
|
431
|
+
padded[lenOffset + 2] = high >>> 8 & 255;
|
|
432
|
+
padded[lenOffset + 3] = high & 255;
|
|
433
|
+
padded[lenOffset + 4] = low >>> 24 & 255;
|
|
434
|
+
padded[lenOffset + 5] = low >>> 16 & 255;
|
|
435
|
+
padded[lenOffset + 6] = low >>> 8 & 255;
|
|
436
|
+
padded[lenOffset + 7] = low & 255;
|
|
437
|
+
const H = new Uint32Array([
|
|
438
|
+
1779033703,
|
|
439
|
+
3144134277,
|
|
440
|
+
1013904242,
|
|
441
|
+
2773480762,
|
|
442
|
+
1359893119,
|
|
443
|
+
2600822924,
|
|
444
|
+
528734635,
|
|
445
|
+
1541459225
|
|
446
|
+
]);
|
|
447
|
+
const W = new Uint32Array(64);
|
|
448
|
+
for (let block = 0; block < blockCount; block++) {
|
|
449
|
+
const offset = block * 64;
|
|
450
|
+
for (let t = 0; t < 16; t++) {
|
|
451
|
+
W[t] = (padded[offset + t * 4] << 24 | padded[offset + t * 4 + 1] << 16 | padded[offset + t * 4 + 2] << 8 | padded[offset + t * 4 + 3]) >>> 0;
|
|
452
|
+
}
|
|
453
|
+
for (let t = 16; t < 64; t++) {
|
|
454
|
+
const w15 = W[t - 15];
|
|
455
|
+
const w2 = W[t - 2];
|
|
456
|
+
const s0 = (w15 >>> 7 | w15 << 25) ^ (w15 >>> 18 | w15 << 14) ^ w15 >>> 3;
|
|
457
|
+
const s1 = (w2 >>> 17 | w2 << 15) ^ (w2 >>> 19 | w2 << 13) ^ w2 >>> 10;
|
|
458
|
+
W[t] = W[t - 16] + s0 + W[t - 7] + s1 >>> 0;
|
|
459
|
+
}
|
|
460
|
+
let a = H[0], b = H[1], c = H[2], d = H[3];
|
|
461
|
+
let e = H[4], f = H[5], g = H[6], h = H[7];
|
|
462
|
+
for (let t = 0; t < 64; t++) {
|
|
463
|
+
const S1 = (e >>> 6 | e << 26) ^ (e >>> 11 | e << 21) ^ (e >>> 25 | e << 7);
|
|
464
|
+
const ch = e & f ^ ~e & g;
|
|
465
|
+
const temp1 = h + S1 + ch + K[t] + W[t] >>> 0;
|
|
466
|
+
const S0 = (a >>> 2 | a << 30) ^ (a >>> 13 | a << 19) ^ (a >>> 22 | a << 10);
|
|
467
|
+
const maj = a & b ^ a & c ^ b & c;
|
|
468
|
+
const temp2 = S0 + maj >>> 0;
|
|
469
|
+
h = g;
|
|
470
|
+
g = f;
|
|
471
|
+
f = e;
|
|
472
|
+
e = d + temp1 >>> 0;
|
|
473
|
+
d = c;
|
|
474
|
+
c = b;
|
|
475
|
+
b = a;
|
|
476
|
+
a = temp1 + temp2 >>> 0;
|
|
477
|
+
}
|
|
478
|
+
H[0] = H[0] + a >>> 0;
|
|
479
|
+
H[1] = H[1] + b >>> 0;
|
|
480
|
+
H[2] = H[2] + c >>> 0;
|
|
481
|
+
H[3] = H[3] + d >>> 0;
|
|
482
|
+
H[4] = H[4] + e >>> 0;
|
|
483
|
+
H[5] = H[5] + f >>> 0;
|
|
484
|
+
H[6] = H[6] + g >>> 0;
|
|
485
|
+
H[7] = H[7] + h >>> 0;
|
|
486
|
+
}
|
|
487
|
+
let hex = "";
|
|
488
|
+
for (let i = 0; i < 8; i++) {
|
|
489
|
+
hex += H[i].toString(16).padStart(8, "0");
|
|
490
|
+
}
|
|
491
|
+
return hex;
|
|
492
|
+
}
|
|
493
|
+
|
|
320
494
|
// src/entitlement-cache.ts
|
|
321
495
|
var DEFAULT_STALE_AFTER_MS = 24 * 60 * 60 * 1e3;
|
|
322
|
-
var
|
|
496
|
+
var ANON_SUFFIX = "_anon";
|
|
497
|
+
var INDEX_SUFFIX = "_index";
|
|
498
|
+
var EntitlementCache = class _EntitlementCache {
|
|
323
499
|
/**
|
|
324
|
-
* @param storage
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
* @param
|
|
329
|
-
*
|
|
330
|
-
*
|
|
500
|
+
* @param storage Device storage adapter. When omitted (tests) or
|
|
501
|
+
* a MemoryStorage (strict-consent / no-persistence
|
|
502
|
+
* mode) the cache is session-only — durability is
|
|
503
|
+
* simply absent, never wrong.
|
|
504
|
+
* @param storageKeyPrefix Prefix used to derive per-user storage keys
|
|
505
|
+
* (`<prefix>:<sha256(userId)>`). Default
|
|
506
|
+
* `crossdeck:entitlements`. The trailing user
|
|
507
|
+
* suffix is filled at identify() / reset()
|
|
508
|
+
* time — see [[setUserKey]].
|
|
509
|
+
* @param staleAfterMs Age past which last-known-good is flagged stale
|
|
510
|
+
* even without a failed refresh. Default 24h.
|
|
331
511
|
*/
|
|
332
|
-
constructor(storage,
|
|
512
|
+
constructor(storage, storageKeyPrefix = "crossdeck:entitlements", staleAfterMs = DEFAULT_STALE_AFTER_MS) {
|
|
333
513
|
this.all = [];
|
|
334
514
|
this.lastUpdated = 0;
|
|
335
515
|
this.lastRefreshFailedAt = 0;
|
|
336
516
|
this.listeners = /* @__PURE__ */ new Set();
|
|
337
517
|
this.listenerErrorCount = 0;
|
|
518
|
+
this.currentSuffix = ANON_SUFFIX;
|
|
338
519
|
this.storage = storage;
|
|
339
|
-
this.
|
|
520
|
+
this.storageKeyPrefix = storageKeyPrefix;
|
|
340
521
|
this.staleAfterMs = staleAfterMs;
|
|
341
522
|
this.hydrate();
|
|
342
523
|
}
|
|
524
|
+
/** The full storage key the current-user blob is persisted under. */
|
|
525
|
+
get storageKey() {
|
|
526
|
+
return `${this.storageKeyPrefix}:${this.currentSuffix}`;
|
|
527
|
+
}
|
|
528
|
+
/** Key of the index blob — a JSON array of every suffix we've
|
|
529
|
+
* written. Used by clearAll() to scope a logout-wipe. */
|
|
530
|
+
get indexKey() {
|
|
531
|
+
return `${this.storageKeyPrefix}:${INDEX_SUFFIX}`;
|
|
532
|
+
}
|
|
533
|
+
/** Derive a stable suffix for a developerUserId via SHA-256. The
|
|
534
|
+
* raw userId never lands in the storage key — protects against
|
|
535
|
+
* accidentally leaking email-style identifiers through DevTools
|
|
536
|
+
* inspection. Pass `null` to switch back to the anonymous slot. */
|
|
537
|
+
static suffixForUserId(userId) {
|
|
538
|
+
if (userId == null || userId === "") return ANON_SUFFIX;
|
|
539
|
+
return sha256Hex(userId);
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Switch the cache to a different user's storage slot. Bank-grade
|
|
543
|
+
* three-layer isolation:
|
|
544
|
+
* (a) Physical key separation — `<prefix>:<sha256(userId)>` so
|
|
545
|
+
* a user-switch can't physically read prior user's data
|
|
546
|
+
* even if the in-memory clear was skipped.
|
|
547
|
+
* (b) Unconditional in-memory clear — invoked whenever the
|
|
548
|
+
* active suffix changes, even on same-id re-identify.
|
|
549
|
+
* (c) Re-hydrate from the new slot — a returning user observes
|
|
550
|
+
* their last-known-good cache from storage immediately.
|
|
551
|
+
*
|
|
552
|
+
* Caller (identify() / reset()) MUST invoke this BEFORE the next
|
|
553
|
+
* setFromList() so the write lands under the right key.
|
|
554
|
+
*/
|
|
555
|
+
setUserKey(userId) {
|
|
556
|
+
const nextSuffix = _EntitlementCache.suffixForUserId(userId);
|
|
557
|
+
if (nextSuffix === this.currentSuffix) {
|
|
558
|
+
this.all = [];
|
|
559
|
+
this.lastUpdated = 0;
|
|
560
|
+
this.lastRefreshFailedAt = 0;
|
|
561
|
+
this.notify();
|
|
562
|
+
this.hydrate();
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
this.currentSuffix = nextSuffix;
|
|
566
|
+
this.all = [];
|
|
567
|
+
this.lastUpdated = 0;
|
|
568
|
+
this.lastRefreshFailedAt = 0;
|
|
569
|
+
this.hydrate();
|
|
570
|
+
this.notify();
|
|
571
|
+
}
|
|
343
572
|
/**
|
|
344
573
|
* Sync read — true iff the entitlement is currently granting access.
|
|
345
574
|
*
|
|
@@ -409,12 +638,13 @@ var EntitlementCache = class {
|
|
|
409
638
|
this.lastUpdated = Date.now();
|
|
410
639
|
this.lastRefreshFailedAt = 0;
|
|
411
640
|
this.persist();
|
|
641
|
+
this.recordSuffixInIndex(this.currentSuffix);
|
|
412
642
|
this.notify();
|
|
413
643
|
}
|
|
414
644
|
/**
|
|
415
|
-
* Wipe
|
|
416
|
-
*
|
|
417
|
-
*
|
|
645
|
+
* Wipe the CURRENT user's slot. Used internally when a single
|
|
646
|
+
* user's cache needs to be invalidated without affecting other
|
|
647
|
+
* persisted slots. The full-logout path is [[clearAll]].
|
|
418
648
|
*/
|
|
419
649
|
clear() {
|
|
420
650
|
this.all = [];
|
|
@@ -426,6 +656,40 @@ var EntitlementCache = class {
|
|
|
426
656
|
} catch {
|
|
427
657
|
}
|
|
428
658
|
}
|
|
659
|
+
this.removeSuffixFromIndex(this.currentSuffix);
|
|
660
|
+
this.notify();
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Logout-grade wipe — bank-grade contract: removes EVERY per-user
|
|
664
|
+
* entitlement slot the SDK has ever written on this device, then
|
|
665
|
+
* clears the index. Used by `Crossdeck.reset()` so a logout on a
|
|
666
|
+
* shared device can never leave another user's entitlements
|
|
667
|
+
* readable (layer (c) of the v1.4.0 isolation fix).
|
|
668
|
+
*
|
|
669
|
+
* After clearAll(), the cache is back to anonymous + empty.
|
|
670
|
+
*/
|
|
671
|
+
clearAll() {
|
|
672
|
+
this.all = [];
|
|
673
|
+
this.lastUpdated = 0;
|
|
674
|
+
this.lastRefreshFailedAt = 0;
|
|
675
|
+
this.currentSuffix = ANON_SUFFIX;
|
|
676
|
+
if (this.storage) {
|
|
677
|
+
const suffixes = this.readIndex();
|
|
678
|
+
for (const suffix of suffixes) {
|
|
679
|
+
try {
|
|
680
|
+
this.storage.removeItem(`${this.storageKeyPrefix}:${suffix}`);
|
|
681
|
+
} catch {
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
try {
|
|
685
|
+
this.storage.removeItem(`${this.storageKeyPrefix}:${ANON_SUFFIX}`);
|
|
686
|
+
} catch {
|
|
687
|
+
}
|
|
688
|
+
try {
|
|
689
|
+
this.storage.removeItem(this.indexKey);
|
|
690
|
+
} catch {
|
|
691
|
+
}
|
|
692
|
+
}
|
|
429
693
|
this.notify();
|
|
430
694
|
}
|
|
431
695
|
/**
|
|
@@ -475,6 +739,47 @@ var EntitlementCache = class {
|
|
|
475
739
|
} catch {
|
|
476
740
|
}
|
|
477
741
|
}
|
|
742
|
+
/** Read the index of all per-user suffixes the SDK has written. */
|
|
743
|
+
readIndex() {
|
|
744
|
+
if (!this.storage) return [];
|
|
745
|
+
try {
|
|
746
|
+
const raw = this.storage.getItem(this.indexKey);
|
|
747
|
+
if (!raw) return [];
|
|
748
|
+
const parsed = JSON.parse(raw);
|
|
749
|
+
if (Array.isArray(parsed)) {
|
|
750
|
+
return parsed.filter((x) => typeof x === "string");
|
|
751
|
+
}
|
|
752
|
+
return [];
|
|
753
|
+
} catch {
|
|
754
|
+
return [];
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
/** Add a suffix to the persisted index. Idempotent. */
|
|
758
|
+
recordSuffixInIndex(suffix) {
|
|
759
|
+
if (!this.storage) return;
|
|
760
|
+
const existing = this.readIndex();
|
|
761
|
+
if (existing.includes(suffix)) return;
|
|
762
|
+
existing.push(suffix);
|
|
763
|
+
try {
|
|
764
|
+
this.storage.setItem(this.indexKey, JSON.stringify(existing));
|
|
765
|
+
} catch {
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
/** Remove a suffix from the persisted index. No-op if absent. */
|
|
769
|
+
removeSuffixFromIndex(suffix) {
|
|
770
|
+
if (!this.storage) return;
|
|
771
|
+
const existing = this.readIndex();
|
|
772
|
+
const next = existing.filter((s) => s !== suffix);
|
|
773
|
+
if (next.length === existing.length) return;
|
|
774
|
+
try {
|
|
775
|
+
if (next.length === 0) {
|
|
776
|
+
this.storage.removeItem(this.indexKey);
|
|
777
|
+
} else {
|
|
778
|
+
this.storage.setItem(this.indexKey, JSON.stringify(next));
|
|
779
|
+
}
|
|
780
|
+
} catch {
|
|
781
|
+
}
|
|
782
|
+
}
|
|
478
783
|
notify() {
|
|
479
784
|
if (this.listeners.size === 0) return;
|
|
480
785
|
const snapshot = this.all.slice();
|
|
@@ -489,6 +794,34 @@ var EntitlementCache = class {
|
|
|
489
794
|
}
|
|
490
795
|
};
|
|
491
796
|
|
|
797
|
+
// src/idempotency-key.ts
|
|
798
|
+
function formatAsUuid(hex) {
|
|
799
|
+
return [
|
|
800
|
+
hex.slice(0, 8),
|
|
801
|
+
hex.slice(8, 12),
|
|
802
|
+
hex.slice(12, 16),
|
|
803
|
+
hex.slice(16, 20),
|
|
804
|
+
hex.slice(20, 32)
|
|
805
|
+
].join("-");
|
|
806
|
+
}
|
|
807
|
+
function deriveIdempotencyKeyForPurchase(body) {
|
|
808
|
+
let identifier;
|
|
809
|
+
if (body.rail === "apple") {
|
|
810
|
+
identifier = body.signedTransactionInfo ?? "";
|
|
811
|
+
} else if (body.rail === "google") {
|
|
812
|
+
identifier = body.purchaseToken ?? "";
|
|
813
|
+
} else {
|
|
814
|
+
identifier = "";
|
|
815
|
+
}
|
|
816
|
+
if (!identifier) {
|
|
817
|
+
throw new Error(
|
|
818
|
+
`deriveIdempotencyKeyForPurchase: no stable identifier in body (rail=${body.rail}). Apple needs signedTransactionInfo; Google needs purchaseToken.`
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
const namespaced = `crossdeck:purchases/sync:${body.rail}:${identifier}`;
|
|
822
|
+
return formatAsUuid(sha256Hex(namespaced));
|
|
823
|
+
}
|
|
824
|
+
|
|
492
825
|
// src/retry-policy.ts
|
|
493
826
|
var DEFAULT_BASE = 1e3;
|
|
494
827
|
var DEFAULT_MAX = 6e4;
|
|
@@ -2751,6 +3084,10 @@ var CrossdeckClient = class {
|
|
|
2751
3084
|
this.state.errors?.uninstall();
|
|
2752
3085
|
} catch {
|
|
2753
3086
|
}
|
|
3087
|
+
try {
|
|
3088
|
+
void this.state.events.flush({ keepalive: true });
|
|
3089
|
+
} catch {
|
|
3090
|
+
}
|
|
2754
3091
|
}
|
|
2755
3092
|
if (!options.publicKey || !options.publicKey.startsWith("cd_pub_")) {
|
|
2756
3093
|
throw new CrossdeckError({
|
|
@@ -2798,7 +3135,12 @@ var CrossdeckClient = class {
|
|
|
2798
3135
|
// load still flushes if the user leaves quickly (the keepalive
|
|
2799
3136
|
// pagehide handler picks up anything that doesn't); long enough
|
|
2800
3137
|
// that bursts of clicks coalesce into one network round-trip.
|
|
2801
|
-
|
|
3138
|
+
// v1.4.0 Phase 3.3 — flush interval default parity. Pre-
|
|
3139
|
+
// v1.4.0: Web/Node 1500ms, RN/Swift/Android 5000ms. All
|
|
3140
|
+
// converged on 2000ms (the Stripe-adjacent industry norm)
|
|
3141
|
+
// so cross-platform funnels show events landing at the
|
|
3142
|
+
// same cadence on every SDK. Per-instance override stays.
|
|
3143
|
+
eventFlushIntervalMs: options.eventFlushIntervalMs ?? 2e3,
|
|
2802
3144
|
sdkVersion: options.sdkVersion ?? SDK_VERSION,
|
|
2803
3145
|
autoTrack,
|
|
2804
3146
|
appVersion: options.appVersion ?? null
|
|
@@ -3026,14 +3368,10 @@ var CrossdeckClient = class {
|
|
|
3026
3368
|
};
|
|
3027
3369
|
if (options?.email) body.email = options.email;
|
|
3028
3370
|
if (traits) body.traits = traits;
|
|
3371
|
+
s.entitlements.setUserKey(userId);
|
|
3029
3372
|
const result = await s.http.request("POST", "/identity/alias", {
|
|
3030
3373
|
body
|
|
3031
3374
|
});
|
|
3032
|
-
const priorCdcust = s.identity.crossdeckCustomerId;
|
|
3033
|
-
const cacheHasEntries = s.entitlements.list().length > 0;
|
|
3034
|
-
if (priorCdcust && result.crossdeckCustomerId && priorCdcust !== result.crossdeckCustomerId || !priorCdcust && cacheHasEntries) {
|
|
3035
|
-
s.entitlements.clear();
|
|
3036
|
-
}
|
|
3037
3375
|
s.identity.setCrossdeckCustomerId(result.crossdeckCustomerId);
|
|
3038
3376
|
s.developerUserId = userId;
|
|
3039
3377
|
return result;
|
|
@@ -3338,6 +3676,37 @@ var CrossdeckClient = class {
|
|
|
3338
3676
|
* trip happens in the background. To flush before the page unloads,
|
|
3339
3677
|
* call flush().
|
|
3340
3678
|
*/
|
|
3679
|
+
/**
|
|
3680
|
+
* Emit `crossdeck.contract_failed` with the canonical property
|
|
3681
|
+
* shape (`contract_id`, `sdk_version`, `sdk_platform`,
|
|
3682
|
+
* `failure_reason`, `run_context`, `run_id`). Goes through the
|
|
3683
|
+
* standard track() pipeline — same consent gate, same queue,
|
|
3684
|
+
* same ingest, no new endpoint.
|
|
3685
|
+
*
|
|
3686
|
+
* Wire the call from a test hook, dogfood failure path, or
|
|
3687
|
+
* customer contract-verification harness; see
|
|
3688
|
+
* `contracts/README.md` for the per-test-framework hook recipes.
|
|
3689
|
+
*/
|
|
3690
|
+
reportContractFailure(input) {
|
|
3691
|
+
const props = {
|
|
3692
|
+
contract_id: input.contractId,
|
|
3693
|
+
sdk_version: SDK_VERSION,
|
|
3694
|
+
sdk_platform: "web",
|
|
3695
|
+
failure_reason: input.failureReason,
|
|
3696
|
+
run_context: input.runContext,
|
|
3697
|
+
run_id: input.runId
|
|
3698
|
+
};
|
|
3699
|
+
if (input.testRef) {
|
|
3700
|
+
props.test_file = input.testRef.file;
|
|
3701
|
+
props.test_name = input.testRef.name;
|
|
3702
|
+
}
|
|
3703
|
+
if (input.extra) {
|
|
3704
|
+
for (const [k, v] of Object.entries(input.extra)) {
|
|
3705
|
+
if (props[k] === void 0) props[k] = v;
|
|
3706
|
+
}
|
|
3707
|
+
}
|
|
3708
|
+
this.track("crossdeck.contract_failed", props);
|
|
3709
|
+
}
|
|
3341
3710
|
track(name, properties) {
|
|
3342
3711
|
const s = this.requireStarted();
|
|
3343
3712
|
if (!name) {
|
|
@@ -3474,11 +3843,24 @@ var CrossdeckClient = class {
|
|
|
3474
3843
|
});
|
|
3475
3844
|
}
|
|
3476
3845
|
const rail = input.rail ?? "apple";
|
|
3846
|
+
const body = { ...input, rail };
|
|
3847
|
+
const idempotencyKey = deriveIdempotencyKeyForPurchase(body);
|
|
3477
3848
|
const result = await s.http.request("POST", "/purchases/sync", {
|
|
3478
|
-
body
|
|
3849
|
+
body,
|
|
3850
|
+
idempotencyKey
|
|
3479
3851
|
});
|
|
3480
3852
|
s.identity.setCrossdeckCustomerId(result.crossdeckCustomerId);
|
|
3481
3853
|
s.entitlements.setFromList(result.entitlements);
|
|
3854
|
+
try {
|
|
3855
|
+
const sourceProductId = result.entitlements[0]?.source.productId;
|
|
3856
|
+
const sourceSubscriptionId = result.entitlements[0]?.source.subscriptionId;
|
|
3857
|
+
const props = { rail };
|
|
3858
|
+
if (sourceProductId) props.productId = sourceProductId;
|
|
3859
|
+
if (sourceSubscriptionId) props.subscriptionId = sourceSubscriptionId;
|
|
3860
|
+
if (result.idempotent_replay) props.idempotent_replay = true;
|
|
3861
|
+
this.track("purchase.completed", props);
|
|
3862
|
+
} catch {
|
|
3863
|
+
}
|
|
3482
3864
|
s.debug.emit(
|
|
3483
3865
|
"sdk.purchase_evidence_sent",
|
|
3484
3866
|
"StoreKit transaction forwarded. Waiting for backend verification.",
|
|
@@ -3534,7 +3916,7 @@ var CrossdeckClient = class {
|
|
|
3534
3916
|
}
|
|
3535
3917
|
this.state.autoTracker?.uninstall();
|
|
3536
3918
|
this.state.identity.reset();
|
|
3537
|
-
this.state.entitlements.
|
|
3919
|
+
this.state.entitlements.clearAll();
|
|
3538
3920
|
this.state.events.reset();
|
|
3539
3921
|
this.state.superProps.clear();
|
|
3540
3922
|
this.state.breadcrumbs.clear();
|