@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/react.cjs
CHANGED
|
@@ -37,6 +37,8 @@ var CrossdeckError = class _CrossdeckError extends Error {
|
|
|
37
37
|
this.requestId = payload.requestId;
|
|
38
38
|
this.status = payload.status;
|
|
39
39
|
this.retryAfterMs = payload.retryAfterMs;
|
|
40
|
+
this.minVersion = payload.minVersion;
|
|
41
|
+
this.surface = payload.surface;
|
|
40
42
|
Object.setPrototypeOf(this, _CrossdeckError.prototype);
|
|
41
43
|
}
|
|
42
44
|
};
|
|
@@ -57,7 +59,10 @@ async function crossdeckErrorFromResponse(res) {
|
|
|
57
59
|
message: envelope.message ?? `HTTP ${res.status}`,
|
|
58
60
|
requestId: envelope.request_id ?? requestId,
|
|
59
61
|
status: res.status,
|
|
60
|
-
retryAfterMs
|
|
62
|
+
retryAfterMs,
|
|
63
|
+
// PARK metadata, present only on a 426 / sdk_version_unsupported body.
|
|
64
|
+
minVersion: typeof envelope.minVersion === "string" ? envelope.minVersion : void 0,
|
|
65
|
+
surface: typeof envelope.surface === "string" ? envelope.surface : void 0
|
|
61
66
|
});
|
|
62
67
|
}
|
|
63
68
|
return new CrossdeckError({
|
|
@@ -93,7 +98,7 @@ function typeMapForStatus(status) {
|
|
|
93
98
|
}
|
|
94
99
|
|
|
95
100
|
// src/_version.ts
|
|
96
|
-
var SDK_VERSION = "1.
|
|
101
|
+
var SDK_VERSION = "1.9.0";
|
|
97
102
|
var SDK_NAME = "@cross-deck/web";
|
|
98
103
|
|
|
99
104
|
// src/http.ts
|
|
@@ -933,6 +938,18 @@ var EventQueue = class {
|
|
|
933
938
|
this.cancelTimer = null;
|
|
934
939
|
this.firstFlushFired = false;
|
|
935
940
|
this.nextRetryAt = null;
|
|
941
|
+
/**
|
|
942
|
+
* PARK state (HTTP 426 / `sdk_version_unsupported`). Once parked, the
|
|
943
|
+
* queue stops flushing — the server has told us our wire dialect is too
|
|
944
|
+
* old, and retrying the same payload only burns the user's battery and
|
|
945
|
+
* bandwidth and drips pointless rejects into the server logs until the
|
|
946
|
+
* app ships an upgraded SDK. The held events stay durable (persisted)
|
|
947
|
+
* and are delivered by the next boot's rehydrate, post-upgrade. Parked
|
|
948
|
+
* is per-instance: a fresh boot (the upgraded build) starts unparked.
|
|
949
|
+
*/
|
|
950
|
+
this.parked = false;
|
|
951
|
+
/** One developer-facing console warning per instance — never per-event spam. */
|
|
952
|
+
this.parkWarned = false;
|
|
936
953
|
this.retry = new RetryPolicy(cfg.retry ?? {});
|
|
937
954
|
this.persistent = cfg.persistentStore ?? null;
|
|
938
955
|
if (this.persistent) {
|
|
@@ -988,6 +1005,7 @@ var EventQueue = class {
|
|
|
988
1005
|
* flushes (pagehide / visibilitychange→hidden / beforeunload).
|
|
989
1006
|
*/
|
|
990
1007
|
async flush(options = {}) {
|
|
1008
|
+
if (this.parked) return null;
|
|
991
1009
|
let batch;
|
|
992
1010
|
let batchId;
|
|
993
1011
|
if (this.pendingBatch !== null && this.pendingBatchId !== null) {
|
|
@@ -1039,6 +1057,29 @@ var EventQueue = class {
|
|
|
1039
1057
|
} catch (err) {
|
|
1040
1058
|
const message = err instanceof Error ? err.message : String(err);
|
|
1041
1059
|
this.lastError = message;
|
|
1060
|
+
if (isVersionRejected(err)) {
|
|
1061
|
+
this.parked = true;
|
|
1062
|
+
this.buffer = [...batch, ...this.buffer];
|
|
1063
|
+
if (this.buffer.length > HARD_BUFFER_CAP) {
|
|
1064
|
+
const overflow = this.buffer.length - HARD_BUFFER_CAP;
|
|
1065
|
+
this.buffer.splice(0, overflow);
|
|
1066
|
+
this.dropped += overflow;
|
|
1067
|
+
}
|
|
1068
|
+
this.pendingBatch = null;
|
|
1069
|
+
this.pendingBatchId = null;
|
|
1070
|
+
this.inFlight -= batch.length;
|
|
1071
|
+
this.persistAll();
|
|
1072
|
+
this.cfg.onBufferChange?.(this.buffer.length);
|
|
1073
|
+
const minVersion = versionRejectionFloor(err);
|
|
1074
|
+
if (!this.parkWarned) {
|
|
1075
|
+
this.parkWarned = true;
|
|
1076
|
+
console.warn(
|
|
1077
|
+
`[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.`
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
this.cfg.onParked?.({ minVersion, surface: versionRejectionSurface(err) });
|
|
1081
|
+
return null;
|
|
1082
|
+
}
|
|
1042
1083
|
if (isPermanent4xx(err)) {
|
|
1043
1084
|
const droppedCount = batch.length;
|
|
1044
1085
|
this.pendingBatch = null;
|
|
@@ -1157,8 +1198,22 @@ function isPermanent4xx(err) {
|
|
|
1157
1198
|
if (typeof status !== "number" || !Number.isFinite(status)) return false;
|
|
1158
1199
|
if (status < 400 || status >= 500) return false;
|
|
1159
1200
|
if (status === 408 || status === 429) return false;
|
|
1201
|
+
if (status === 426) return false;
|
|
1160
1202
|
return true;
|
|
1161
1203
|
}
|
|
1204
|
+
function isVersionRejected(err) {
|
|
1205
|
+
if (!err || typeof err !== "object") return false;
|
|
1206
|
+
if (err.status === 426) return true;
|
|
1207
|
+
return err.code === "sdk_version_unsupported";
|
|
1208
|
+
}
|
|
1209
|
+
function versionRejectionFloor(err) {
|
|
1210
|
+
const v = err?.minVersion;
|
|
1211
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
1212
|
+
}
|
|
1213
|
+
function versionRejectionSurface(err) {
|
|
1214
|
+
const v = err?.surface;
|
|
1215
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
1216
|
+
}
|
|
1162
1217
|
function defaultScheduler(fn, ms) {
|
|
1163
1218
|
const id = setTimeout(fn, ms);
|
|
1164
1219
|
if (typeof id.unref === "function") {
|
|
@@ -1344,6 +1399,16 @@ function hasDocument() {
|
|
|
1344
1399
|
return typeof globalThis.document !== "undefined";
|
|
1345
1400
|
}
|
|
1346
1401
|
|
|
1402
|
+
// src/read-cost-bridge.ts
|
|
1403
|
+
var BUCKETS_BRIDGE_KEY = "__crossdeckBucketsBridge__";
|
|
1404
|
+
function bridgeReadCost(ctx) {
|
|
1405
|
+
try {
|
|
1406
|
+
const setter = globalThis[BUCKETS_BRIDGE_KEY];
|
|
1407
|
+
if (typeof setter === "function") setter(ctx);
|
|
1408
|
+
} catch {
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1347
1412
|
// src/device-info.ts
|
|
1348
1413
|
function isBrowser() {
|
|
1349
1414
|
return typeof globalThis.window !== "undefined" && typeof globalThis.document !== "undefined" && typeof globalThis.navigator !== "undefined";
|
|
@@ -2899,6 +2964,13 @@ var CROSSDECK_ERROR_CODES = Object.freeze([
|
|
|
2899
2964
|
description: "A field is present but the value failed validation (wrong shape, wrong length, wrong enum value).",
|
|
2900
2965
|
resolution: "Read error.message for the specific field + reason. SDK-managed call sites should never emit this \u2014 file a bug if you do.",
|
|
2901
2966
|
retryable: false
|
|
2967
|
+
},
|
|
2968
|
+
{
|
|
2969
|
+
code: "sdk_version_unsupported",
|
|
2970
|
+
type: "version_error",
|
|
2971
|
+
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.",
|
|
2972
|
+
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/.",
|
|
2973
|
+
retryable: false
|
|
2902
2974
|
}
|
|
2903
2975
|
]);
|
|
2904
2976
|
function getErrorCode(code) {
|
|
@@ -3444,7 +3516,8 @@ var BACKEND_WIRE_CODES = Object.freeze([
|
|
|
3444
3516
|
"google_not_supported",
|
|
3445
3517
|
"stripe_not_supported",
|
|
3446
3518
|
"missing_required_param",
|
|
3447
|
-
"invalid_param_value"
|
|
3519
|
+
"invalid_param_value",
|
|
3520
|
+
"sdk_version_unsupported"
|
|
3448
3521
|
]);
|
|
3449
3522
|
var VERIFIER_SDK_ERROR_CODES_CATALOGUE = {
|
|
3450
3523
|
contractId: "sdk-error-codes-catalogue",
|
|
@@ -4601,6 +4674,13 @@ var CrossdeckClient = class {
|
|
|
4601
4674
|
headline,
|
|
4602
4675
|
{ ...info }
|
|
4603
4676
|
);
|
|
4677
|
+
},
|
|
4678
|
+
onParked: (info) => {
|
|
4679
|
+
debug.emit(
|
|
4680
|
+
"sdk.parked",
|
|
4681
|
+
`[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.`,
|
|
4682
|
+
{ ...info }
|
|
4683
|
+
);
|
|
4604
4684
|
}
|
|
4605
4685
|
});
|
|
4606
4686
|
const deviceInfo = autoTrack.deviceInfo ? collectDeviceInfo({ appVersion: opts.appVersion ?? void 0 }) : opts.appVersion ? { appVersion: opts.appVersion } : {};
|
|
@@ -4818,6 +4898,7 @@ var CrossdeckClient = class {
|
|
|
4818
4898
|
message: "identify(userId) requires a non-empty userId."
|
|
4819
4899
|
});
|
|
4820
4900
|
}
|
|
4901
|
+
bridgeReadCost({ actor: userId });
|
|
4821
4902
|
if (!s.consent.analytics) {
|
|
4822
4903
|
s.debug.emit(
|
|
4823
4904
|
"sdk.consent_denied",
|
|
@@ -5470,6 +5551,7 @@ var CrossdeckClient = class {
|
|
|
5470
5551
|
}
|
|
5471
5552
|
this.state.autoTracker?.uninstall();
|
|
5472
5553
|
this.state.identity.reset();
|
|
5554
|
+
bridgeReadCost({ actor: void 0 });
|
|
5473
5555
|
this.state.entitlements.clearAll();
|
|
5474
5556
|
this.state.events.reset();
|
|
5475
5557
|
this.state.superProps.clear();
|
|
@@ -5560,6 +5642,23 @@ var CrossdeckClient = class {
|
|
|
5560
5642
|
const s = this.requireStarted();
|
|
5561
5643
|
return s.identity.crossdeckCustomerId ?? s.developerUserId ?? s.identity.anonymousId;
|
|
5562
5644
|
}
|
|
5645
|
+
/**
|
|
5646
|
+
* The device-scoped anonymous id the SDK minted on first boot and persists
|
|
5647
|
+
* across launches (stable until reset()). Public accessor so a server-to-
|
|
5648
|
+
* server flow or a block/suspension gate can pass the device identity to
|
|
5649
|
+
* POST /v1/resolve without reaching into private storage.
|
|
5650
|
+
*
|
|
5651
|
+
* Returns `null` BEFORE init() — there is no anon id yet, and a gate that
|
|
5652
|
+
* fires during early app boot should get a clean falsy, not a throw. (This is
|
|
5653
|
+
* deliberately softer than getCheckoutReference(), which requires init.)
|
|
5654
|
+
*
|
|
5655
|
+
* Note: /v1/resolve also accepts a VERIFIED identity (userId + idToken)
|
|
5656
|
+
* without an anonymousId, and that path is higher-trust — prefer it where the
|
|
5657
|
+
* user is authenticated.
|
|
5658
|
+
*/
|
|
5659
|
+
getAnonymousId() {
|
|
5660
|
+
return this.state ? this.state.identity.anonymousId : null;
|
|
5661
|
+
}
|
|
5563
5662
|
// ---------- private helpers ----------
|
|
5564
5663
|
requireStarted() {
|
|
5565
5664
|
if (!this.state) {
|