@cross-deck/web 1.7.0 → 1.9.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 +40 -0
- package/README.md +12 -6
- package/dist/crossdeck.umd.min.js +2 -2
- package/dist/crossdeck.umd.min.js.map +1 -1
- package/dist/error-codes.json +8 -1
- package/dist/index.cjs +113 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +30 -5
- package/dist/index.d.ts +30 -5
- package/dist/index.mjs +113 -14
- package/dist/index.mjs.map +1 -1
- package/dist/react.cjs +102 -3
- package/dist/react.cjs.map +1 -1
- package/dist/react.mjs +102 -3
- package/dist/react.mjs.map +1 -1
- package/dist/vue.cjs +102 -3
- package/dist/vue.cjs.map +1 -1
- package/dist/vue.mjs +102 -3
- package/dist/vue.mjs.map +1 -1
- package/package.json +3 -3
- package/dist/contracts.json +0 -546
package/dist/vue.mjs
CHANGED
|
@@ -11,6 +11,8 @@ var CrossdeckError = class _CrossdeckError extends Error {
|
|
|
11
11
|
this.requestId = payload.requestId;
|
|
12
12
|
this.status = payload.status;
|
|
13
13
|
this.retryAfterMs = payload.retryAfterMs;
|
|
14
|
+
this.minVersion = payload.minVersion;
|
|
15
|
+
this.surface = payload.surface;
|
|
14
16
|
Object.setPrototypeOf(this, _CrossdeckError.prototype);
|
|
15
17
|
}
|
|
16
18
|
};
|
|
@@ -31,7 +33,10 @@ async function crossdeckErrorFromResponse(res) {
|
|
|
31
33
|
message: envelope.message ?? `HTTP ${res.status}`,
|
|
32
34
|
requestId: envelope.request_id ?? requestId,
|
|
33
35
|
status: res.status,
|
|
34
|
-
retryAfterMs
|
|
36
|
+
retryAfterMs,
|
|
37
|
+
// PARK metadata, present only on a 426 / sdk_version_unsupported body.
|
|
38
|
+
minVersion: typeof envelope.minVersion === "string" ? envelope.minVersion : void 0,
|
|
39
|
+
surface: typeof envelope.surface === "string" ? envelope.surface : void 0
|
|
35
40
|
});
|
|
36
41
|
}
|
|
37
42
|
return new CrossdeckError({
|
|
@@ -67,7 +72,7 @@ function typeMapForStatus(status) {
|
|
|
67
72
|
}
|
|
68
73
|
|
|
69
74
|
// src/_version.ts
|
|
70
|
-
var SDK_VERSION = "1.
|
|
75
|
+
var SDK_VERSION = "1.9.0";
|
|
71
76
|
var SDK_NAME = "@cross-deck/web";
|
|
72
77
|
|
|
73
78
|
// src/http.ts
|
|
@@ -907,6 +912,18 @@ var EventQueue = class {
|
|
|
907
912
|
this.cancelTimer = null;
|
|
908
913
|
this.firstFlushFired = false;
|
|
909
914
|
this.nextRetryAt = null;
|
|
915
|
+
/**
|
|
916
|
+
* PARK state (HTTP 426 / `sdk_version_unsupported`). Once parked, the
|
|
917
|
+
* queue stops flushing — the server has told us our wire dialect is too
|
|
918
|
+
* old, and retrying the same payload only burns the user's battery and
|
|
919
|
+
* bandwidth and drips pointless rejects into the server logs until the
|
|
920
|
+
* app ships an upgraded SDK. The held events stay durable (persisted)
|
|
921
|
+
* and are delivered by the next boot's rehydrate, post-upgrade. Parked
|
|
922
|
+
* is per-instance: a fresh boot (the upgraded build) starts unparked.
|
|
923
|
+
*/
|
|
924
|
+
this.parked = false;
|
|
925
|
+
/** One developer-facing console warning per instance — never per-event spam. */
|
|
926
|
+
this.parkWarned = false;
|
|
910
927
|
this.retry = new RetryPolicy(cfg.retry ?? {});
|
|
911
928
|
this.persistent = cfg.persistentStore ?? null;
|
|
912
929
|
if (this.persistent) {
|
|
@@ -962,6 +979,7 @@ var EventQueue = class {
|
|
|
962
979
|
* flushes (pagehide / visibilitychange→hidden / beforeunload).
|
|
963
980
|
*/
|
|
964
981
|
async flush(options = {}) {
|
|
982
|
+
if (this.parked) return null;
|
|
965
983
|
let batch;
|
|
966
984
|
let batchId;
|
|
967
985
|
if (this.pendingBatch !== null && this.pendingBatchId !== null) {
|
|
@@ -1013,6 +1031,29 @@ var EventQueue = class {
|
|
|
1013
1031
|
} catch (err) {
|
|
1014
1032
|
const message = err instanceof Error ? err.message : String(err);
|
|
1015
1033
|
this.lastError = message;
|
|
1034
|
+
if (isVersionRejected(err)) {
|
|
1035
|
+
this.parked = true;
|
|
1036
|
+
this.buffer = [...batch, ...this.buffer];
|
|
1037
|
+
if (this.buffer.length > HARD_BUFFER_CAP) {
|
|
1038
|
+
const overflow = this.buffer.length - HARD_BUFFER_CAP;
|
|
1039
|
+
this.buffer.splice(0, overflow);
|
|
1040
|
+
this.dropped += overflow;
|
|
1041
|
+
}
|
|
1042
|
+
this.pendingBatch = null;
|
|
1043
|
+
this.pendingBatchId = null;
|
|
1044
|
+
this.inFlight -= batch.length;
|
|
1045
|
+
this.persistAll();
|
|
1046
|
+
this.cfg.onBufferChange?.(this.buffer.length);
|
|
1047
|
+
const minVersion = versionRejectionFloor(err);
|
|
1048
|
+
if (!this.parkWarned) {
|
|
1049
|
+
this.parkWarned = true;
|
|
1050
|
+
console.warn(
|
|
1051
|
+
`[Crossdeck] SDK outdated \u2014 the server is no longer accepting this version's event format. Your events are PARKED on-device (held, not lost) and will deliver automatically once you update @cross-deck/web${minVersion ? ` to >= ${minVersion}` : ""} and redeploy.`
|
|
1052
|
+
);
|
|
1053
|
+
}
|
|
1054
|
+
this.cfg.onParked?.({ minVersion, surface: versionRejectionSurface(err) });
|
|
1055
|
+
return null;
|
|
1056
|
+
}
|
|
1016
1057
|
if (isPermanent4xx(err)) {
|
|
1017
1058
|
const droppedCount = batch.length;
|
|
1018
1059
|
this.pendingBatch = null;
|
|
@@ -1131,8 +1172,22 @@ function isPermanent4xx(err) {
|
|
|
1131
1172
|
if (typeof status !== "number" || !Number.isFinite(status)) return false;
|
|
1132
1173
|
if (status < 400 || status >= 500) return false;
|
|
1133
1174
|
if (status === 408 || status === 429) return false;
|
|
1175
|
+
if (status === 426) return false;
|
|
1134
1176
|
return true;
|
|
1135
1177
|
}
|
|
1178
|
+
function isVersionRejected(err) {
|
|
1179
|
+
if (!err || typeof err !== "object") return false;
|
|
1180
|
+
if (err.status === 426) return true;
|
|
1181
|
+
return err.code === "sdk_version_unsupported";
|
|
1182
|
+
}
|
|
1183
|
+
function versionRejectionFloor(err) {
|
|
1184
|
+
const v = err?.minVersion;
|
|
1185
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
1186
|
+
}
|
|
1187
|
+
function versionRejectionSurface(err) {
|
|
1188
|
+
const v = err?.surface;
|
|
1189
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
1190
|
+
}
|
|
1136
1191
|
function defaultScheduler(fn, ms) {
|
|
1137
1192
|
const id = setTimeout(fn, ms);
|
|
1138
1193
|
if (typeof id.unref === "function") {
|
|
@@ -1318,6 +1373,16 @@ function hasDocument() {
|
|
|
1318
1373
|
return typeof globalThis.document !== "undefined";
|
|
1319
1374
|
}
|
|
1320
1375
|
|
|
1376
|
+
// src/read-cost-bridge.ts
|
|
1377
|
+
var BUCKETS_BRIDGE_KEY = "__crossdeckBucketsBridge__";
|
|
1378
|
+
function bridgeReadCost(ctx) {
|
|
1379
|
+
try {
|
|
1380
|
+
const setter = globalThis[BUCKETS_BRIDGE_KEY];
|
|
1381
|
+
if (typeof setter === "function") setter(ctx);
|
|
1382
|
+
} catch {
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1321
1386
|
// src/device-info.ts
|
|
1322
1387
|
function isBrowser() {
|
|
1323
1388
|
return typeof globalThis.window !== "undefined" && typeof globalThis.document !== "undefined" && typeof globalThis.navigator !== "undefined";
|
|
@@ -2873,6 +2938,13 @@ var CROSSDECK_ERROR_CODES = Object.freeze([
|
|
|
2873
2938
|
description: "A field is present but the value failed validation (wrong shape, wrong length, wrong enum value).",
|
|
2874
2939
|
resolution: "Read error.message for the specific field + reason. SDK-managed call sites should never emit this \u2014 file a bug if you do.",
|
|
2875
2940
|
retryable: false
|
|
2941
|
+
},
|
|
2942
|
+
{
|
|
2943
|
+
code: "sdk_version_unsupported",
|
|
2944
|
+
type: "version_error",
|
|
2945
|
+
description: "HTTP 426 \u2014 your installed SDK sends an event format the server no longer accepts. The data is good; only the wire dialect is too old. The SDK PARKS automatically: events are held on-device (paused, not lost) and deliver on the next launch after you upgrade.",
|
|
2946
|
+
resolution: "Update @cross-deck/web to at least the version in error.minVersion and redeploy \u2014 the parked queue backfills automatically. See https://cross-deck.com/docs/sdk-event-durability/.",
|
|
2947
|
+
retryable: false
|
|
2876
2948
|
}
|
|
2877
2949
|
]);
|
|
2878
2950
|
function getErrorCode(code) {
|
|
@@ -3418,7 +3490,8 @@ var BACKEND_WIRE_CODES = Object.freeze([
|
|
|
3418
3490
|
"google_not_supported",
|
|
3419
3491
|
"stripe_not_supported",
|
|
3420
3492
|
"missing_required_param",
|
|
3421
|
-
"invalid_param_value"
|
|
3493
|
+
"invalid_param_value",
|
|
3494
|
+
"sdk_version_unsupported"
|
|
3422
3495
|
]);
|
|
3423
3496
|
var VERIFIER_SDK_ERROR_CODES_CATALOGUE = {
|
|
3424
3497
|
contractId: "sdk-error-codes-catalogue",
|
|
@@ -4575,6 +4648,13 @@ var CrossdeckClient = class {
|
|
|
4575
4648
|
headline,
|
|
4576
4649
|
{ ...info }
|
|
4577
4650
|
);
|
|
4651
|
+
},
|
|
4652
|
+
onParked: (info) => {
|
|
4653
|
+
debug.emit(
|
|
4654
|
+
"sdk.parked",
|
|
4655
|
+
`[crossdeck] SDK parked \u2014 server no longer accepts this version's event format. Events held on-device (paused, not lost); update @cross-deck/web${info.minVersion ? ` to >= ${info.minVersion}` : ""} to resume.`,
|
|
4656
|
+
{ ...info }
|
|
4657
|
+
);
|
|
4578
4658
|
}
|
|
4579
4659
|
});
|
|
4580
4660
|
const deviceInfo = autoTrack.deviceInfo ? collectDeviceInfo({ appVersion: opts.appVersion ?? void 0 }) : opts.appVersion ? { appVersion: opts.appVersion } : {};
|
|
@@ -4792,6 +4872,7 @@ var CrossdeckClient = class {
|
|
|
4792
4872
|
message: "identify(userId) requires a non-empty userId."
|
|
4793
4873
|
});
|
|
4794
4874
|
}
|
|
4875
|
+
bridgeReadCost({ actor: userId });
|
|
4795
4876
|
if (!s.consent.analytics) {
|
|
4796
4877
|
s.debug.emit(
|
|
4797
4878
|
"sdk.consent_denied",
|
|
@@ -5444,6 +5525,7 @@ var CrossdeckClient = class {
|
|
|
5444
5525
|
}
|
|
5445
5526
|
this.state.autoTracker?.uninstall();
|
|
5446
5527
|
this.state.identity.reset();
|
|
5528
|
+
bridgeReadCost({ actor: void 0 });
|
|
5447
5529
|
this.state.entitlements.clearAll();
|
|
5448
5530
|
this.state.events.reset();
|
|
5449
5531
|
this.state.superProps.clear();
|
|
@@ -5534,6 +5616,23 @@ var CrossdeckClient = class {
|
|
|
5534
5616
|
const s = this.requireStarted();
|
|
5535
5617
|
return s.identity.crossdeckCustomerId ?? s.developerUserId ?? s.identity.anonymousId;
|
|
5536
5618
|
}
|
|
5619
|
+
/**
|
|
5620
|
+
* The device-scoped anonymous id the SDK minted on first boot and persists
|
|
5621
|
+
* across launches (stable until reset()). Public accessor so a server-to-
|
|
5622
|
+
* server flow or a block/suspension gate can pass the device identity to
|
|
5623
|
+
* POST /v1/resolve without reaching into private storage.
|
|
5624
|
+
*
|
|
5625
|
+
* Returns `null` BEFORE init() — there is no anon id yet, and a gate that
|
|
5626
|
+
* fires during early app boot should get a clean falsy, not a throw. (This is
|
|
5627
|
+
* deliberately softer than getCheckoutReference(), which requires init.)
|
|
5628
|
+
*
|
|
5629
|
+
* Note: /v1/resolve also accepts a VERIFIED identity (userId + idToken)
|
|
5630
|
+
* without an anonymousId, and that path is higher-trust — prefer it where the
|
|
5631
|
+
* user is authenticated.
|
|
5632
|
+
*/
|
|
5633
|
+
getAnonymousId() {
|
|
5634
|
+
return this.state ? this.state.identity.anonymousId : null;
|
|
5635
|
+
}
|
|
5537
5636
|
// ---------- private helpers ----------
|
|
5538
5637
|
requireStarted() {
|
|
5539
5638
|
if (!this.state) {
|