@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.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.7.0";
75
+ var SDK_VERSION = "1.8.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") {
@@ -2873,6 +2928,13 @@ var CROSSDECK_ERROR_CODES = Object.freeze([
2873
2928
  description: "A field is present but the value failed validation (wrong shape, wrong length, wrong enum value).",
2874
2929
  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
2930
  retryable: false
2931
+ },
2932
+ {
2933
+ code: "sdk_version_unsupported",
2934
+ type: "version_error",
2935
+ 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.",
2936
+ 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/.",
2937
+ retryable: false
2876
2938
  }
2877
2939
  ]);
2878
2940
  function getErrorCode(code) {
@@ -3418,7 +3480,8 @@ var BACKEND_WIRE_CODES = Object.freeze([
3418
3480
  "google_not_supported",
3419
3481
  "stripe_not_supported",
3420
3482
  "missing_required_param",
3421
- "invalid_param_value"
3483
+ "invalid_param_value",
3484
+ "sdk_version_unsupported"
3422
3485
  ]);
3423
3486
  var VERIFIER_SDK_ERROR_CODES_CATALOGUE = {
3424
3487
  contractId: "sdk-error-codes-catalogue",
@@ -4575,6 +4638,13 @@ var CrossdeckClient = class {
4575
4638
  headline,
4576
4639
  { ...info }
4577
4640
  );
4641
+ },
4642
+ onParked: (info) => {
4643
+ debug.emit(
4644
+ "sdk.parked",
4645
+ `[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.`,
4646
+ { ...info }
4647
+ );
4578
4648
  }
4579
4649
  });
4580
4650
  const deviceInfo = autoTrack.deviceInfo ? collectDeviceInfo({ appVersion: opts.appVersion ?? void 0 }) : opts.appVersion ? { appVersion: opts.appVersion } : {};