@cross-deck/web 1.7.0 → 1.8.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/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.7.0";
101
+ var SDK_VERSION = "1.8.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") {
@@ -2899,6 +2954,13 @@ var CROSSDECK_ERROR_CODES = Object.freeze([
2899
2954
  description: "A field is present but the value failed validation (wrong shape, wrong length, wrong enum value).",
2900
2955
  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
2956
  retryable: false
2957
+ },
2958
+ {
2959
+ code: "sdk_version_unsupported",
2960
+ type: "version_error",
2961
+ 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.",
2962
+ 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/.",
2963
+ retryable: false
2902
2964
  }
2903
2965
  ]);
2904
2966
  function getErrorCode(code) {
@@ -3444,7 +3506,8 @@ var BACKEND_WIRE_CODES = Object.freeze([
3444
3506
  "google_not_supported",
3445
3507
  "stripe_not_supported",
3446
3508
  "missing_required_param",
3447
- "invalid_param_value"
3509
+ "invalid_param_value",
3510
+ "sdk_version_unsupported"
3448
3511
  ]);
3449
3512
  var VERIFIER_SDK_ERROR_CODES_CATALOGUE = {
3450
3513
  contractId: "sdk-error-codes-catalogue",
@@ -4601,6 +4664,13 @@ var CrossdeckClient = class {
4601
4664
  headline,
4602
4665
  { ...info }
4603
4666
  );
4667
+ },
4668
+ onParked: (info) => {
4669
+ debug.emit(
4670
+ "sdk.parked",
4671
+ `[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.`,
4672
+ { ...info }
4673
+ );
4604
4674
  }
4605
4675
  });
4606
4676
  const deviceInfo = autoTrack.deviceInfo ? collectDeviceInfo({ appVersion: opts.appVersion ?? void 0 }) : opts.appVersion ? { appVersion: opts.appVersion } : {};