@cross-deck/web 1.6.4 → 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/index.cjs CHANGED
@@ -43,6 +43,8 @@ var CrossdeckError = class _CrossdeckError extends Error {
43
43
  this.requestId = payload.requestId;
44
44
  this.status = payload.status;
45
45
  this.retryAfterMs = payload.retryAfterMs;
46
+ this.minVersion = payload.minVersion;
47
+ this.surface = payload.surface;
46
48
  Object.setPrototypeOf(this, _CrossdeckError.prototype);
47
49
  }
48
50
  };
@@ -63,7 +65,10 @@ async function crossdeckErrorFromResponse(res) {
63
65
  message: envelope.message ?? `HTTP ${res.status}`,
64
66
  requestId: envelope.request_id ?? requestId,
65
67
  status: res.status,
66
- retryAfterMs
68
+ retryAfterMs,
69
+ // PARK metadata, present only on a 426 / sdk_version_unsupported body.
70
+ minVersion: typeof envelope.minVersion === "string" ? envelope.minVersion : void 0,
71
+ surface: typeof envelope.surface === "string" ? envelope.surface : void 0
67
72
  });
68
73
  }
69
74
  return new CrossdeckError({
@@ -99,7 +104,7 @@ function typeMapForStatus(status) {
99
104
  }
100
105
 
101
106
  // src/_version.ts
102
- var SDK_VERSION = "1.6.4";
107
+ var SDK_VERSION = "1.8.0";
103
108
  var SDK_NAME = "@cross-deck/web";
104
109
 
105
110
  // src/http.ts
@@ -939,6 +944,18 @@ var EventQueue = class {
939
944
  this.cancelTimer = null;
940
945
  this.firstFlushFired = false;
941
946
  this.nextRetryAt = null;
947
+ /**
948
+ * PARK state (HTTP 426 / `sdk_version_unsupported`). Once parked, the
949
+ * queue stops flushing — the server has told us our wire dialect is too
950
+ * old, and retrying the same payload only burns the user's battery and
951
+ * bandwidth and drips pointless rejects into the server logs until the
952
+ * app ships an upgraded SDK. The held events stay durable (persisted)
953
+ * and are delivered by the next boot's rehydrate, post-upgrade. Parked
954
+ * is per-instance: a fresh boot (the upgraded build) starts unparked.
955
+ */
956
+ this.parked = false;
957
+ /** One developer-facing console warning per instance — never per-event spam. */
958
+ this.parkWarned = false;
942
959
  this.retry = new RetryPolicy(cfg.retry ?? {});
943
960
  this.persistent = cfg.persistentStore ?? null;
944
961
  if (this.persistent) {
@@ -994,6 +1011,7 @@ var EventQueue = class {
994
1011
  * flushes (pagehide / visibilitychange→hidden / beforeunload).
995
1012
  */
996
1013
  async flush(options = {}) {
1014
+ if (this.parked) return null;
997
1015
  let batch;
998
1016
  let batchId;
999
1017
  if (this.pendingBatch !== null && this.pendingBatchId !== null) {
@@ -1015,9 +1033,13 @@ var EventQueue = class {
1015
1033
  const env = this.cfg.envelope();
1016
1034
  const result = await this.cfg.http.request("POST", "/events", {
1017
1035
  body: {
1018
- // NorthStar §13.1 batch envelope. The backend validates these
1019
- // against the API-key-resolved app and rejects mismatches
1020
- // loudly (env_mismatch).
1036
+ // Event Envelope v1 batch envelope (backend/docs/
1037
+ // event-envelope-spec-v1.md §1). `envelopeVersion` is the
1038
+ // schema/wire version the server parses against ("can I parse
1039
+ // this?") and is DISTINCT from `sdk.version` ("which build is in
1040
+ // the wild?") — two questions, two fields, never conflated. The
1041
+ // backend refuses payloads with a missing/unknown envelopeVersion.
1042
+ envelopeVersion: 1,
1021
1043
  appId: env.appId,
1022
1044
  environment: env.environment,
1023
1045
  sdk: env.sdk,
@@ -1041,6 +1063,29 @@ var EventQueue = class {
1041
1063
  } catch (err) {
1042
1064
  const message = err instanceof Error ? err.message : String(err);
1043
1065
  this.lastError = message;
1066
+ if (isVersionRejected(err)) {
1067
+ this.parked = true;
1068
+ this.buffer = [...batch, ...this.buffer];
1069
+ if (this.buffer.length > HARD_BUFFER_CAP) {
1070
+ const overflow = this.buffer.length - HARD_BUFFER_CAP;
1071
+ this.buffer.splice(0, overflow);
1072
+ this.dropped += overflow;
1073
+ }
1074
+ this.pendingBatch = null;
1075
+ this.pendingBatchId = null;
1076
+ this.inFlight -= batch.length;
1077
+ this.persistAll();
1078
+ this.cfg.onBufferChange?.(this.buffer.length);
1079
+ const minVersion = versionRejectionFloor(err);
1080
+ if (!this.parkWarned) {
1081
+ this.parkWarned = true;
1082
+ console.warn(
1083
+ `[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.`
1084
+ );
1085
+ }
1086
+ this.cfg.onParked?.({ minVersion, surface: versionRejectionSurface(err) });
1087
+ return null;
1088
+ }
1044
1089
  if (isPermanent4xx(err)) {
1045
1090
  const droppedCount = batch.length;
1046
1091
  this.pendingBatch = null;
@@ -1159,8 +1204,22 @@ function isPermanent4xx(err) {
1159
1204
  if (typeof status !== "number" || !Number.isFinite(status)) return false;
1160
1205
  if (status < 400 || status >= 500) return false;
1161
1206
  if (status === 408 || status === 429) return false;
1207
+ if (status === 426) return false;
1162
1208
  return true;
1163
1209
  }
1210
+ function isVersionRejected(err) {
1211
+ if (!err || typeof err !== "object") return false;
1212
+ if (err.status === 426) return true;
1213
+ return err.code === "sdk_version_unsupported";
1214
+ }
1215
+ function versionRejectionFloor(err) {
1216
+ const v = err?.minVersion;
1217
+ return typeof v === "string" && v.length > 0 ? v : void 0;
1218
+ }
1219
+ function versionRejectionSurface(err) {
1220
+ const v = err?.surface;
1221
+ return typeof v === "string" && v.length > 0 ? v : void 0;
1222
+ }
1164
1223
  function defaultScheduler(fn, ms) {
1165
1224
  const id = setTimeout(fn, ms);
1166
1225
  if (typeof id.unref === "function") {
@@ -1472,6 +1531,15 @@ var AutoTracker = class {
1472
1531
  this.cleanups = [];
1473
1532
  /** Last time we flushed lastActivityAt to storage (throttle gate). */
1474
1533
  this.lastPersistAt = 0;
1534
+ /**
1535
+ * Event Envelope v1 §3 — per-session monotonic sequence counter.
1536
+ * The single owner of the seq counter lives here, alongside all other
1537
+ * session state. Incremented atomically at track() time (via nextSeq()),
1538
+ * reset to 0 whenever a new session boundary is crossed (startNewSession
1539
+ * path + resetSession). Persists across background/foreground within a
1540
+ * session — only a real session boundary (not a tab hide/show) resets it.
1541
+ */
1542
+ this._sessionSeq = 0;
1475
1543
  /**
1476
1544
  * Stable per-page-view identifier. Minted at every `page.viewed`
1477
1545
  * emission and attached to every subsequent event until the next
@@ -1556,6 +1624,20 @@ var AutoTracker = class {
1556
1624
  get currentAcquisition() {
1557
1625
  return this.session?.acquisition ?? EMPTY_ACQUISITION;
1558
1626
  }
1627
+ /**
1628
+ * Event Envelope v1 §3 — return the next seq value for the current session
1629
+ * and advance the counter. Called SYNCHRONOUSLY at track() time, before any
1630
+ * async dispatch, so the seq assignment order is deterministic (call order,
1631
+ * not scheduler luck). The counter is reset to 0 at every session boundary;
1632
+ * it is NOT reset by tab hide/show (spec §3 clause 1: backgrounding does not
1633
+ * reset seq). When no session is active (Node, before init, after uninstall),
1634
+ * returns 0 and leaves the counter unchanged — fallback, not an error.
1635
+ */
1636
+ nextSeq() {
1637
+ const seq = this._sessionSeq;
1638
+ this._sessionSeq += 1;
1639
+ return seq;
1640
+ }
1559
1641
  // ---------- sessions ----------
1560
1642
  installSessionTracking() {
1561
1643
  const now = Date.now();
@@ -1607,6 +1689,7 @@ var AutoTracker = class {
1607
1689
  }
1608
1690
  startNewSession() {
1609
1691
  const now = Date.now();
1692
+ this._sessionSeq = 0;
1610
1693
  return {
1611
1694
  sessionId: mintSessionId(),
1612
1695
  startedAt: now,
@@ -2335,6 +2418,40 @@ function writeJson(storage, key, value) {
2335
2418
  }
2336
2419
  }
2337
2420
 
2421
+ // src/internal-opt-out.ts
2422
+ var INTERNAL_OPT_OUT_PROPERTY = "$crossdeck_internal";
2423
+ var STORAGE_KEY = "crossdeck.internalOptOut";
2424
+ function localStore() {
2425
+ try {
2426
+ return typeof localStorage !== "undefined" ? localStorage : null;
2427
+ } catch {
2428
+ return null;
2429
+ }
2430
+ }
2431
+ function processInternalOptOutUrl() {
2432
+ try {
2433
+ const search = typeof location !== "undefined" ? location.search : "";
2434
+ const params = new URLSearchParams(search || "");
2435
+ if (!params.has("crossdeck_internal")) return;
2436
+ const store = localStore();
2437
+ if (!store) return;
2438
+ const v = params.get("crossdeck_internal");
2439
+ if (v === "1" || v === "true") {
2440
+ store.setItem(STORAGE_KEY, "1");
2441
+ } else if (v === "0" || v === "false") {
2442
+ store.removeItem(STORAGE_KEY);
2443
+ }
2444
+ } catch {
2445
+ }
2446
+ }
2447
+ function isInternalOptOut() {
2448
+ try {
2449
+ return localStore()?.getItem(STORAGE_KEY) === "1";
2450
+ } catch {
2451
+ return false;
2452
+ }
2453
+ }
2454
+
2338
2455
  // src/web-vitals.ts
2339
2456
  var WebVitalsTracker = class {
2340
2457
  constructor(cfg, report) {
@@ -2843,6 +2960,13 @@ var CROSSDECK_ERROR_CODES = Object.freeze([
2843
2960
  description: "A field is present but the value failed validation (wrong shape, wrong length, wrong enum value).",
2844
2961
  resolution: "Read error.message for the specific field + reason. SDK-managed call sites should never emit this \u2014 file a bug if you do.",
2845
2962
  retryable: false
2963
+ },
2964
+ {
2965
+ code: "sdk_version_unsupported",
2966
+ type: "version_error",
2967
+ 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.",
2968
+ 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/.",
2969
+ retryable: false
2846
2970
  }
2847
2971
  ]);
2848
2972
  function getErrorCode(code) {
@@ -2865,15 +2989,6 @@ var VerifierReporter = class {
2865
2989
  constructor(ctx) {
2866
2990
  this.ctx = ctx;
2867
2991
  this.reentrancyDepth = 0;
2868
- /** The live verifier set — attached by the SDK right after init so the
2869
- * emit path can schema-lock its OWN outgoing telemetry against the real
2870
- * payload. Null until attached (boot-time reports still work without it). */
2871
- this.verifiers = null;
2872
- }
2873
- /** Hand the reporter the live verifier set so `reportFail` can run the
2874
- * `onReportContractFailure` hook on the exact payload it's about to emit. */
2875
- attachVerifiers(verifiers) {
2876
- this.verifiers = verifiers;
2877
2992
  }
2878
2993
  /**
2879
2994
  * Report a verifier result. Pass `operation` for hot-path results
@@ -2902,7 +3017,7 @@ var VerifierReporter = class {
2902
3017
  if (this.reentrancyDepth > 0) return;
2903
3018
  this.reentrancyDepth += 1;
2904
3019
  try {
2905
- const payload = {
3020
+ this.ctx.emitTelemetry({
2906
3021
  contract_id: result.contractId,
2907
3022
  sdk_version: this.ctx.sdkVersion,
2908
3023
  sdk_platform: "web",
@@ -2910,13 +3025,7 @@ var VerifierReporter = class {
2910
3025
  run_context: this.ctx.runContext,
2911
3026
  run_id: this.ctx.runId,
2912
3027
  verification_phase: phase
2913
- };
2914
- if (this.verifiers) {
2915
- runOnReportContractFailure(this.verifiers, this, this.ctx, {
2916
- outgoingPayload: payload
2917
- });
2918
- }
2919
- this.ctx.emitTelemetry(payload);
3028
+ });
2920
3029
  } finally {
2921
3030
  this.reentrancyDepth -= 1;
2922
3031
  }
@@ -3206,31 +3315,9 @@ var VERIFIER_FLUSH_INTERVAL_PARITY = {
3206
3315
  "eventFlushIntervalMs default = 2000ms (Web/Node/RN/Swift/Android parity)",
3207
3316
  nowMs() - t0
3208
3317
  );
3209
- },
3210
- // Hot-path hook — on every real track() we re-affirm the LIVE configured
3211
- // flush interval (carried on the observation), so the parity guarantee is
3212
- // verified against the running SDK in the customer flow, not just a
3213
- // canonical constant at boot. It can't change per-event, but observing it
3214
- // per-event is what makes the contract genuinely runtime-verified.
3215
- hooks: {
3216
- onTrack(obs) {
3217
- const t0 = nowMs();
3218
- const CANONICAL_DEFAULT_MS = 2e3;
3219
- const ms = obs.flushIntervalMs;
3220
- if (typeof ms !== "number" || ms < 100 || ms > 6e4) {
3221
- return fail(
3222
- "flush-interval-parity",
3223
- `live eventFlushIntervalMs=${ms} outside reasonable bounds [100, 60000]`,
3224
- nowMs() - t0
3225
- );
3226
- }
3227
- return pass(
3228
- "flush-interval-parity",
3229
- ms === CANONICAL_DEFAULT_MS ? "live eventFlushIntervalMs = 2000ms (canonical parity value)" : `live eventFlushIntervalMs = ${ms}ms (permitted override; canonical default 2000ms)`,
3230
- nowMs() - t0
3231
- );
3232
- }
3233
3318
  }
3319
+ // No hot-path hook — the flush interval is set once at start() and
3320
+ // never changes per-operation.
3234
3321
  };
3235
3322
  function buildFlushIntervalVerifier(configuredIntervalMs) {
3236
3323
  return {
@@ -3357,26 +3444,6 @@ var CONTRACT_FAILED_FORBIDDEN_FIELDS = [
3357
3444
  "referrer",
3358
3445
  "deviceId"
3359
3446
  ];
3360
- function contractFailedSchemaViolation(keys) {
3361
- const allowed = /* @__PURE__ */ new Set([
3362
- ...CONTRACT_FAILED_REQUIRED_FIELDS,
3363
- ...CONTRACT_FAILED_OPTIONAL_FIELDS
3364
- ]);
3365
- const forbidden = new Set(CONTRACT_FAILED_FORBIDDEN_FIELDS);
3366
- for (const required of CONTRACT_FAILED_REQUIRED_FIELDS) {
3367
- if (!keys.includes(required)) return `missing required field: ${required}`;
3368
- }
3369
- for (const k of keys) {
3370
- if (forbidden.has(k)) return `forbidden field on wire: ${k}`;
3371
- }
3372
- for (const k of keys) {
3373
- if (!allowed.has(k)) {
3374
- return `unrecognised field on wire: ${k} (not in required \u222A optional)`;
3375
- }
3376
- }
3377
- return null;
3378
- }
3379
- var CONTRACT_FAILED_OK_EVIDENCE = `fields \u2286 required(${CONTRACT_FAILED_REQUIRED_FIELDS.length}) \u222A optional(${CONTRACT_FAILED_OPTIONAL_FIELDS.length}); ${CONTRACT_FAILED_FORBIDDEN_FIELDS.length} forbidden absent`;
3380
3447
  var VERIFIER_CONTRACT_FAILED_PAYLOAD_SCHEMA_LOCK = {
3381
3448
  contractId: "contract-failed-payload-schema-lock",
3382
3449
  bootTest() {
@@ -3391,35 +3458,43 @@ var VERIFIER_CONTRACT_FAILED_PAYLOAD_SCHEMA_LOCK = {
3391
3458
  verification_phase: "boot"
3392
3459
  };
3393
3460
  const keys = Object.keys(syntheticPayload);
3394
- const violation = contractFailedSchemaViolation(keys);
3395
- if (violation) {
3396
- return fail("contract-failed-payload-schema-lock", violation, nowMs() - t0);
3461
+ const allowed = /* @__PURE__ */ new Set([
3462
+ ...CONTRACT_FAILED_REQUIRED_FIELDS,
3463
+ ...CONTRACT_FAILED_OPTIONAL_FIELDS
3464
+ ]);
3465
+ const forbidden = new Set(CONTRACT_FAILED_FORBIDDEN_FIELDS);
3466
+ for (const required of CONTRACT_FAILED_REQUIRED_FIELDS) {
3467
+ if (!keys.includes(required)) {
3468
+ return fail(
3469
+ "contract-failed-payload-schema-lock",
3470
+ `missing required field: ${required}`,
3471
+ nowMs() - t0
3472
+ );
3473
+ }
3474
+ }
3475
+ for (const k of keys) {
3476
+ if (forbidden.has(k)) {
3477
+ return fail(
3478
+ "contract-failed-payload-schema-lock",
3479
+ `forbidden field on wire: ${k}`,
3480
+ nowMs() - t0
3481
+ );
3482
+ }
3483
+ }
3484
+ for (const k of keys) {
3485
+ if (!allowed.has(k)) {
3486
+ return fail(
3487
+ "contract-failed-payload-schema-lock",
3488
+ `unrecognised field on wire: ${k} (not in required \u222A optional)`,
3489
+ nowMs() - t0
3490
+ );
3491
+ }
3397
3492
  }
3398
3493
  return pass(
3399
3494
  "contract-failed-payload-schema-lock",
3400
- `${keys.length} ${CONTRACT_FAILED_OK_EVIDENCE}`,
3495
+ `${keys.length} fields \u2286 required(${CONTRACT_FAILED_REQUIRED_FIELDS.length}) \u222A optional(${CONTRACT_FAILED_OPTIONAL_FIELDS.length}); ${CONTRACT_FAILED_FORBIDDEN_FIELDS.length} forbidden absent`,
3401
3496
  nowMs() - t0
3402
3497
  );
3403
- },
3404
- // Hot-path hook — fires on the REAL reportFail emit (VerifierReporter,
3405
- // inside the re-entrancy guard) against the exact payload about to go on
3406
- // the wire. This is the assertion the guard at reportFail was built in
3407
- // anticipation of: every reliability-channel write is schema-locked in the
3408
- // field, against real data, not just the synthetic boot mirror.
3409
- hooks: {
3410
- onReportContractFailure(obs) {
3411
- const t0 = nowMs();
3412
- const keys = Object.keys(obs.outgoingPayload);
3413
- const violation = contractFailedSchemaViolation(keys);
3414
- if (violation) {
3415
- return fail("contract-failed-payload-schema-lock", violation, nowMs() - t0);
3416
- }
3417
- return pass(
3418
- "contract-failed-payload-schema-lock",
3419
- `${keys.length} ${CONTRACT_FAILED_OK_EVIDENCE}`,
3420
- nowMs() - t0
3421
- );
3422
- }
3423
3498
  }
3424
3499
  };
3425
3500
  var BACKEND_WIRE_CODES = Object.freeze([
@@ -3437,7 +3512,8 @@ var BACKEND_WIRE_CODES = Object.freeze([
3437
3512
  "google_not_supported",
3438
3513
  "stripe_not_supported",
3439
3514
  "missing_required_param",
3440
- "invalid_param_value"
3515
+ "invalid_param_value",
3516
+ "sdk_version_unsupported"
3441
3517
  ]);
3442
3518
  var VERIFIER_SDK_ERROR_CODES_CATALOGUE = {
3443
3519
  contractId: "sdk-error-codes-catalogue",
@@ -3470,39 +3546,6 @@ var VERIFIER_SDK_ERROR_CODES_CATALOGUE = {
3470
3546
  nowMs() - t0
3471
3547
  );
3472
3548
  }
3473
- },
3474
- // Hot-path hook — when the SDK parses a real wire error, assert the code
3475
- // the customer actually hit carries usable remediation in the shipped
3476
- // catalogue. Scoped to BACKEND_WIRE_CODES (the contract's set); other
3477
- // codes are out of scope and pass. This turns the catalogue from a
3478
- // boot-only completeness claim into a per-error field assertion: a backend
3479
- // code that ships without remediation is caught the first time a customer
3480
- // hits it, not only if the off-by-default boot self-test ran.
3481
- hooks: {
3482
- onErrorParse(obs) {
3483
- const t0 = nowMs();
3484
- const code = obs.errorCode;
3485
- if (!code || !BACKEND_WIRE_CODES.includes(code)) {
3486
- return pass(
3487
- "sdk-error-codes-catalogue",
3488
- `wire code "${code || "(none)"}" is out of the backend-catalogue scope`,
3489
- nowMs() - t0
3490
- );
3491
- }
3492
- const entry = getErrorCode(code);
3493
- if (!entry || !entry.description || entry.description.trim().length === 0 || !entry.resolution || entry.resolution.trim().length === 0) {
3494
- return fail(
3495
- "sdk-error-codes-catalogue",
3496
- `backend wire code "${code}" hit in the field has no description+resolution in the shipped catalogue`,
3497
- nowMs() - t0
3498
- );
3499
- }
3500
- return pass(
3501
- "sdk-error-codes-catalogue",
3502
- `backend wire code "${code}" carries description + resolution`,
3503
- nowMs() - t0
3504
- );
3505
- }
3506
3549
  }
3507
3550
  };
3508
3551
  var STATIC_VERIFIERS = Object.freeze([
@@ -3626,24 +3669,6 @@ function runOnErrorParse(verifiers, reporter, ctx, obs) {
3626
3669
  reporter.report(result, "hot_path", "errorParse");
3627
3670
  }
3628
3671
  }
3629
- function runOnReportContractFailure(verifiers, reporter, ctx, obs) {
3630
- if (ctx.disableContractAssertions) return;
3631
- for (const verifier of verifiers) {
3632
- const hook = verifier.hooks?.onReportContractFailure;
3633
- if (!hook) continue;
3634
- let result;
3635
- try {
3636
- result = hook(obs);
3637
- } catch (err) {
3638
- result = fail(
3639
- verifier.contractId,
3640
- `hook threw: ${err.message?.slice(0, 80) ?? "unknown"}`,
3641
- 0
3642
- );
3643
- }
3644
- reporter.report(result, "hot_path", "reportContractFailure");
3645
- }
3646
- }
3647
3672
  function defaultDebugModeFlag() {
3648
3673
  try {
3649
3674
  if (typeof process !== "undefined" && process.env) {
@@ -3795,18 +3820,18 @@ function isInAppFrame(filename) {
3795
3820
  if (/\/crossdeck\.umd\.min\.js$/.test(filename)) return false;
3796
3821
  return true;
3797
3822
  }
3798
- function fingerprintError(message, frames, location) {
3823
+ function fingerprintError(message, frames, location2) {
3799
3824
  const inAppFrames = frames.filter((f) => f.in_app).slice(0, 3);
3800
3825
  const parts = [
3801
3826
  (message || "").slice(0, 200),
3802
3827
  ...inAppFrames.map((f) => `${f.function}@${f.filename}:${f.lineno}`)
3803
3828
  ];
3804
- if (inAppFrames.length === 0 && location) {
3829
+ if (inAppFrames.length === 0 && location2) {
3805
3830
  const loc = [
3806
- location.errorType ?? "",
3807
- location.filename ?? "",
3808
- location.lineno ?? "",
3809
- location.colno ?? ""
3831
+ location2.errorType ?? "",
3832
+ location2.filename ?? "",
3833
+ location2.lineno ?? "",
3834
+ location2.colno ?? ""
3810
3835
  ].join(":");
3811
3836
  if (loc !== ":::") parts.push(loc);
3812
3837
  }
@@ -4476,9 +4501,6 @@ var CrossdeckClient = class {
4476
4501
  this.verifiers = null;
4477
4502
  this.verifierReporter = null;
4478
4503
  this.verifierCtx = null;
4479
- // The live configured event-flush interval, surfaced to the track-path
4480
- // verifier so flush-interval-parity validates the real cadence per event.
4481
- this.flushIntervalMs = 2e3;
4482
4504
  }
4483
4505
  /**
4484
4506
  * Boot the SDK. Idempotent — calling init twice with the same options
@@ -4648,6 +4670,13 @@ var CrossdeckClient = class {
4648
4670
  headline,
4649
4671
  { ...info }
4650
4672
  );
4673
+ },
4674
+ onParked: (info) => {
4675
+ debug.emit(
4676
+ "sdk.parked",
4677
+ `[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.`,
4678
+ { ...info }
4679
+ );
4651
4680
  }
4652
4681
  });
4653
4682
  const deviceInfo = autoTrack.deviceInfo ? collectDeviceInfo({ appVersion: opts.appVersion ?? void 0 }) : opts.appVersion ? { appVersion: opts.appVersion } : {};
@@ -4655,6 +4684,7 @@ var CrossdeckClient = class {
4655
4684
  persistIdentity ? effectiveStorage : new MemoryStorage(),
4656
4685
  opts.storagePrefix
4657
4686
  );
4687
+ processInternalOptOutUrl();
4658
4688
  const consent = new ConsentManager({ respectDnt: options.respectDnt === true });
4659
4689
  if (consent.isDntDenied) {
4660
4690
  debug.emit(
@@ -4758,8 +4788,6 @@ var CrossdeckClient = class {
4758
4788
  this.verifierReporter = new VerifierReporter(this.verifierCtx);
4759
4789
  const flushVerifier = buildFlushIntervalVerifier(opts.eventFlushIntervalMs);
4760
4790
  this.verifiers = Object.freeze([...STATIC_VERIFIERS, flushVerifier]);
4761
- this.flushIntervalMs = opts.eventFlushIntervalMs;
4762
- this.verifierReporter.attachVerifiers(this.verifiers);
4763
4791
  if (localDevMode) {
4764
4792
  const verifyAtBootLocal = options.verifyContractsAtBoot ?? debugDefault;
4765
4793
  if (verifyAtBootLocal && this.verifierReporter) {
@@ -5310,8 +5338,27 @@ var CrossdeckClient = class {
5310
5338
  );
5311
5339
  }
5312
5340
  }
5341
+ const seq = s.autoTracker?.nextSeq() ?? 0;
5342
+ const occurrenceTimestamp = Date.now();
5313
5343
  s.autoTracker?.markActivity();
5314
- const enriched = { ...s.deviceInfo };
5344
+ const wireContext = {};
5345
+ const di = s.deviceInfo;
5346
+ if (di.os) wireContext.os = di.os;
5347
+ if (di.osVersion) wireContext.osVersion = di.osVersion;
5348
+ const appVer = s.options.appVersion;
5349
+ if (appVer) wireContext.appVersion = appVer;
5350
+ wireContext.sdkName = SDK_NAME;
5351
+ wireContext.sdkVersion = s.options.sdkVersion;
5352
+ if (di.locale) wireContext.locale = di.locale;
5353
+ if (di.timezone) wireContext.timezone = di.timezone;
5354
+ if (di.browser) wireContext.browser = di.browser;
5355
+ if (di.browserVersion) wireContext.browserVersion = di.browserVersion;
5356
+ const enriched = {};
5357
+ if (di.screenWidth !== void 0) enriched.screenWidth = di.screenWidth;
5358
+ if (di.screenHeight !== void 0) enriched.screenHeight = di.screenHeight;
5359
+ if (di.viewportWidth !== void 0) enriched.viewportWidth = di.viewportWidth;
5360
+ if (di.viewportHeight !== void 0) enriched.viewportHeight = di.viewportHeight;
5361
+ if (di.devicePixelRatio !== void 0) enriched.devicePixelRatio = di.devicePixelRatio;
5315
5362
  const sessionId = s.autoTracker?.currentSessionId;
5316
5363
  if (sessionId) enriched.sessionId = sessionId;
5317
5364
  const pageviewId = s.autoTracker?.currentPageviewId;
@@ -5334,17 +5381,24 @@ var CrossdeckClient = class {
5334
5381
  }
5335
5382
  }
5336
5383
  const supers = s.superProps.getSuperProperties();
5337
- Object.assign(enriched, supers);
5384
+ for (const k of Object.keys(supers)) {
5385
+ if (!(k in enriched)) enriched[k] = supers[k];
5386
+ }
5338
5387
  const groupIds = s.superProps.getGroupIds();
5339
5388
  if (Object.keys(groupIds).length > 0) {
5340
5389
  enriched.$groups = groupIds;
5341
5390
  }
5342
5391
  Object.assign(enriched, validation.properties);
5392
+ if (isInternalOptOut()) {
5393
+ enriched[INTERNAL_OPT_OUT_PROPERTY] = true;
5394
+ }
5343
5395
  const finalProperties = s.scrubPii ? scrubPiiFromProperties(enriched) : enriched;
5344
5396
  const event = {
5345
5397
  eventId: this.mintEventId(),
5346
5398
  name,
5347
- timestamp: Date.now(),
5399
+ timestamp: occurrenceTimestamp,
5400
+ seq,
5401
+ context: wireContext,
5348
5402
  properties: finalProperties
5349
5403
  };
5350
5404
  Object.assign(event, this.identityHintForEvent());
@@ -5354,8 +5408,7 @@ var CrossdeckClient = class {
5354
5408
  callerProperties: validation.properties,
5355
5409
  superProperties: supers,
5356
5410
  deviceProperties: s.deviceInfo,
5357
- mergedProperties: enriched,
5358
- flushIntervalMs: this.flushIntervalMs
5411
+ mergedProperties: enriched
5359
5412
  });
5360
5413
  }
5361
5414
  if (!isError && !isWebVital) {
@@ -5700,8 +5753,8 @@ function installUnloadFlush(onUnload) {
5700
5753
  }
5701
5754
 
5702
5755
  // src/_contracts-bundled.ts
5703
- var BUNDLED_IN = "@cross-deck/web@1.6.4";
5704
- var SDK_VERSION2 = "1.6.4";
5756
+ var BUNDLED_IN = "@cross-deck/web@1.8.0";
5757
+ var SDK_VERSION2 = "1.8.0";
5705
5758
  var BUNDLED_CONTRACTS = Object.freeze([
5706
5759
  {
5707
5760
  "id": "contract-failed-payload-schema-lock",
@@ -5823,7 +5876,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5823
5876
  "legal/security/index.html#diagnostic",
5824
5877
  "legal/sdk-data/index.html#b-diagnostic"
5825
5878
  ],
5826
- "bundledIn": "@cross-deck/web@1.6.4",
5879
+ "bundledIn": "@cross-deck/web@1.8.0",
5827
5880
  "runtimeVerified": true
5828
5881
  },
5829
5882
  {
@@ -5863,7 +5916,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5863
5916
  ],
5864
5917
  "registeredAt": "2026-05-26",
5865
5918
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 8 (codifies existing contract)",
5866
- "bundledIn": "@cross-deck/web@1.6.4",
5919
+ "bundledIn": "@cross-deck/web@1.8.0",
5867
5920
  "runtimeVerified": true
5868
5921
  },
5869
5922
  {
@@ -5909,7 +5962,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5909
5962
  ],
5910
5963
  "registeredAt": "2026-05-26",
5911
5964
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.3",
5912
- "bundledIn": "@cross-deck/web@1.6.4",
5965
+ "bundledIn": "@cross-deck/web@1.8.0",
5913
5966
  "runtimeVerified": true
5914
5967
  },
5915
5968
  {
@@ -6015,7 +6068,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
6015
6068
  ],
6016
6069
  "registeredAt": "2026-05-26",
6017
6070
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 2.2.a + 2.2.b + 2.2.c",
6018
- "bundledIn": "@cross-deck/web@1.6.4",
6071
+ "bundledIn": "@cross-deck/web@1.8.0",
6019
6072
  "runtimeVerified": true
6020
6073
  },
6021
6074
  {
@@ -6043,7 +6096,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
6043
6096
  ],
6044
6097
  "registeredAt": "2026-05-26",
6045
6098
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 5.5",
6046
- "bundledIn": "@cross-deck/web@1.6.4",
6099
+ "bundledIn": "@cross-deck/web@1.8.0",
6047
6100
  "runtimeVerified": false
6048
6101
  },
6049
6102
  {
@@ -6123,7 +6176,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
6123
6176
  ],
6124
6177
  "registeredAt": "2026-05-26",
6125
6178
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 1.3 (web/RN) + dogfood-gap fix (swift + android)",
6126
- "bundledIn": "@cross-deck/web@1.6.4",
6179
+ "bundledIn": "@cross-deck/web@1.8.0",
6127
6180
  "runtimeVerified": true
6128
6181
  },
6129
6182
  {
@@ -6169,7 +6222,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
6169
6222
  ],
6170
6223
  "registeredAt": "2026-05-26",
6171
6224
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 6.2",
6172
- "bundledIn": "@cross-deck/web@1.6.4",
6225
+ "bundledIn": "@cross-deck/web@1.8.0",
6173
6226
  "runtimeVerified": true
6174
6227
  },
6175
6228
  {
@@ -6211,7 +6264,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
6211
6264
  ],
6212
6265
  "registeredAt": "2026-05-26",
6213
6266
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.2",
6214
- "bundledIn": "@cross-deck/web@1.6.4",
6267
+ "bundledIn": "@cross-deck/web@1.8.0",
6215
6268
  "runtimeVerified": true
6216
6269
  },
6217
6270
  {
@@ -6245,7 +6298,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
6245
6298
  ],
6246
6299
  "registeredAt": "2026-05-26",
6247
6300
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.5",
6248
- "bundledIn": "@cross-deck/web@1.6.4",
6301
+ "bundledIn": "@cross-deck/web@1.8.0",
6249
6302
  "runtimeVerified": false
6250
6303
  }
6251
6304
  ]);