@cross-deck/node 1.6.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/CHANGELOG.md +60 -0
- package/README.md +11 -3
- package/dist/auto-events/index.d.mts +1 -1
- package/dist/auto-events/index.d.ts +1 -1
- package/dist/{crossdeck-server-C1Ue0rv4.d.mts → crossdeck-server-D9RvKxgA.d.mts} +21 -3
- package/dist/{crossdeck-server-C1Ue0rv4.d.ts → crossdeck-server-D9RvKxgA.d.ts} +21 -3
- package/dist/index.cjs +169 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +10 -4
- package/dist/index.d.ts +10 -4
- package/dist/index.mjs +169 -15
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/dist/contracts.json +0 -557
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { A as AliasIdentityInput, a as AliasResult, b as AuditDecision, c as AuditEntry, B as Breadcrumb, d as BreadcrumbCategory, e as BreadcrumbLevel, C as CROSSDECK_API_VERSION, f as CapturedError, g as Contract, h as ContractAppliesTo, i as ContractFailureInput, j as ContractPillar, k as ContractStatus, l as ContractTestRef, m as CrossdeckAuthenticationError, n as CrossdeckConfigurationError, o as CrossdeckContracts, p as CrossdeckError, q as CrossdeckErrorPayload, r as CrossdeckErrorType, s as CrossdeckInternalError, t as CrossdeckNetworkError, u as CrossdeckPermissionError, v as CrossdeckRateLimitError, w as CrossdeckServer, x as CrossdeckServerOptions, y as CrossdeckValidationError, D as DEFAULT_BASE_URL, z as DEFAULT_TIMEOUT_MS, E as Diagnostics, F as EntitlementCacheOptions, G as EntitlementMutationResult, H as EntitlementStore, I as EntitlementsListResponse, J as EntitlementsListener, K as Environment, L as ErrorCaptureConfig, M as ErrorLevel, N as EventProperties, O as ForgetResult, P as GrantDuration, Q as GrantEntitlementInput, R as GroupMembership, S as HeartbeatResponse, T as HttpRequestInfo, U as HttpResponseInfo, V as HttpRetriesConfig, W as IdentifyOptions, X as IdentityHints, Y as IngestOptions, Z as IngestResponse, _ as PublicEntitlement, $ as PurchaseResult, a0 as RequestOptions, a1 as RevokeEntitlementInput, a2 as RuntimeHost, a3 as RuntimeInfo, a4 as ServerEvent, a5 as StackFrame, a6 as StoredEntitlements, a7 as SyncPurchaseInput, a8 as makeCrossdeckError } from './crossdeck-server-
|
|
1
|
+
export { A as AliasIdentityInput, a as AliasResult, b as AuditDecision, c as AuditEntry, B as Breadcrumb, d as BreadcrumbCategory, e as BreadcrumbLevel, C as CROSSDECK_API_VERSION, f as CapturedError, g as Contract, h as ContractAppliesTo, i as ContractFailureInput, j as ContractPillar, k as ContractStatus, l as ContractTestRef, m as CrossdeckAuthenticationError, n as CrossdeckConfigurationError, o as CrossdeckContracts, p as CrossdeckError, q as CrossdeckErrorPayload, r as CrossdeckErrorType, s as CrossdeckInternalError, t as CrossdeckNetworkError, u as CrossdeckPermissionError, v as CrossdeckRateLimitError, w as CrossdeckServer, x as CrossdeckServerOptions, y as CrossdeckValidationError, D as DEFAULT_BASE_URL, z as DEFAULT_TIMEOUT_MS, E as Diagnostics, F as EntitlementCacheOptions, G as EntitlementMutationResult, H as EntitlementStore, I as EntitlementsListResponse, J as EntitlementsListener, K as Environment, L as ErrorCaptureConfig, M as ErrorLevel, N as EventProperties, O as ForgetResult, P as GrantDuration, Q as GrantEntitlementInput, R as GroupMembership, S as HeartbeatResponse, T as HttpRequestInfo, U as HttpResponseInfo, V as HttpRetriesConfig, W as IdentifyOptions, X as IdentityHints, Y as IngestOptions, Z as IngestResponse, _ as PublicEntitlement, $ as PurchaseResult, a0 as RequestOptions, a1 as RevokeEntitlementInput, a2 as RuntimeHost, a3 as RuntimeInfo, a4 as ServerEvent, a5 as StackFrame, a6 as StoredEntitlements, a7 as SyncPurchaseInput, a8 as makeCrossdeckError } from './crossdeck-server-D9RvKxgA.mjs';
|
|
2
2
|
import 'node:events';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -17,7 +17,7 @@ import 'node:events';
|
|
|
17
17
|
*
|
|
18
18
|
* Do NOT edit by hand — `node scripts/sync-sdk-versions.mjs`.
|
|
19
19
|
*/
|
|
20
|
-
declare const SDK_VERSION = "1.
|
|
20
|
+
declare const SDK_VERSION = "1.8.0";
|
|
21
21
|
declare const SDK_NAME = "@cross-deck/node";
|
|
22
22
|
|
|
23
23
|
/**
|
|
@@ -51,7 +51,7 @@ interface ErrorCodeEntry {
|
|
|
51
51
|
/** The string thrown as `CrossdeckError.code`. */
|
|
52
52
|
code: string;
|
|
53
53
|
/** Broad category — `CrossdeckError.type`. */
|
|
54
|
-
type: "authentication_error" | "permission_error" | "invalid_request_error" | "rate_limit_error" | "internal_error" | "network_error" | "configuration_error";
|
|
54
|
+
type: "authentication_error" | "permission_error" | "invalid_request_error" | "rate_limit_error" | "version_error" | "internal_error" | "network_error" | "configuration_error";
|
|
55
55
|
/** One-sentence description. Surfaced verbatim in dashboards. */
|
|
56
56
|
description: string;
|
|
57
57
|
/** What the developer should do. Imperative phrasing. */
|
|
@@ -271,6 +271,12 @@ declare const _CROSSDECK_ERROR_CODES: readonly [{
|
|
|
271
271
|
readonly description: "A field is present but the value failed validation.";
|
|
272
272
|
readonly resolution: "Read error.message for the field + reason. SDK-managed call sites should never emit this — file a bug if you do.";
|
|
273
273
|
readonly retryable: false;
|
|
274
|
+
}, {
|
|
275
|
+
readonly code: "sdk_version_unsupported";
|
|
276
|
+
readonly type: "version_error";
|
|
277
|
+
readonly description: "HTTP 426 — 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 in memory and deliver once you upgrade and restart.";
|
|
278
|
+
readonly resolution: "Update @cross-deck/node to at least the version in error.minVersion and restart — the held queue backfills. See https://cross-deck.com/docs/sdk-event-durability/.";
|
|
279
|
+
readonly retryable: false;
|
|
274
280
|
}];
|
|
275
281
|
/**
|
|
276
282
|
* Literal union of every code documented in `CROSSDECK_ERROR_CODES`.
|
|
@@ -495,7 +501,7 @@ declare function scrubPiiFromProperties(properties: Record<string, unknown>): Re
|
|
|
495
501
|
* - `sdk.no_durable_store`
|
|
496
502
|
* - `sdk.super_property_registered`
|
|
497
503
|
*/
|
|
498
|
-
type DebugSignal = "sdk.configured" | "sdk.first_event_sent" | "sdk.invalid_key" | "sdk.no_identity" | "sdk.entitlement_cache_used" | "sdk.entitlement_cache_warm" | "sdk.entitlement_cache_stale" | "sdk.entitlement_store_recovered" | "sdk.no_durable_store" | "sdk.purchase_evidence_sent" | "sdk.environment_mismatch" | "sdk.sensitive_property_warning" | "sdk.property_coerced" | "sdk.flush_retry_scheduled" | "sdk.flush_permanent_failure" | "sdk.flush_on_exit_started" | "sdk.flush_on_exit_completed" | "sdk.webhook_verified" | "sdk.runtime_detected" | "sdk.super_property_registered" | "sdk.boot_heartbeat_failed";
|
|
504
|
+
type DebugSignal = "sdk.configured" | "sdk.first_event_sent" | "sdk.invalid_key" | "sdk.no_identity" | "sdk.entitlement_cache_used" | "sdk.entitlement_cache_warm" | "sdk.entitlement_cache_stale" | "sdk.entitlement_store_recovered" | "sdk.no_durable_store" | "sdk.purchase_evidence_sent" | "sdk.environment_mismatch" | "sdk.sensitive_property_warning" | "sdk.property_coerced" | "sdk.flush_retry_scheduled" | "sdk.flush_permanent_failure" | "sdk.parked" | "sdk.flush_on_exit_started" | "sdk.flush_on_exit_completed" | "sdk.webhook_verified" | "sdk.runtime_detected" | "sdk.super_property_registered" | "sdk.boot_heartbeat_failed";
|
|
499
505
|
interface DebugContext {
|
|
500
506
|
[key: string]: unknown;
|
|
501
507
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { A as AliasIdentityInput, a as AliasResult, b as AuditDecision, c as AuditEntry, B as Breadcrumb, d as BreadcrumbCategory, e as BreadcrumbLevel, C as CROSSDECK_API_VERSION, f as CapturedError, g as Contract, h as ContractAppliesTo, i as ContractFailureInput, j as ContractPillar, k as ContractStatus, l as ContractTestRef, m as CrossdeckAuthenticationError, n as CrossdeckConfigurationError, o as CrossdeckContracts, p as CrossdeckError, q as CrossdeckErrorPayload, r as CrossdeckErrorType, s as CrossdeckInternalError, t as CrossdeckNetworkError, u as CrossdeckPermissionError, v as CrossdeckRateLimitError, w as CrossdeckServer, x as CrossdeckServerOptions, y as CrossdeckValidationError, D as DEFAULT_BASE_URL, z as DEFAULT_TIMEOUT_MS, E as Diagnostics, F as EntitlementCacheOptions, G as EntitlementMutationResult, H as EntitlementStore, I as EntitlementsListResponse, J as EntitlementsListener, K as Environment, L as ErrorCaptureConfig, M as ErrorLevel, N as EventProperties, O as ForgetResult, P as GrantDuration, Q as GrantEntitlementInput, R as GroupMembership, S as HeartbeatResponse, T as HttpRequestInfo, U as HttpResponseInfo, V as HttpRetriesConfig, W as IdentifyOptions, X as IdentityHints, Y as IngestOptions, Z as IngestResponse, _ as PublicEntitlement, $ as PurchaseResult, a0 as RequestOptions, a1 as RevokeEntitlementInput, a2 as RuntimeHost, a3 as RuntimeInfo, a4 as ServerEvent, a5 as StackFrame, a6 as StoredEntitlements, a7 as SyncPurchaseInput, a8 as makeCrossdeckError } from './crossdeck-server-
|
|
1
|
+
export { A as AliasIdentityInput, a as AliasResult, b as AuditDecision, c as AuditEntry, B as Breadcrumb, d as BreadcrumbCategory, e as BreadcrumbLevel, C as CROSSDECK_API_VERSION, f as CapturedError, g as Contract, h as ContractAppliesTo, i as ContractFailureInput, j as ContractPillar, k as ContractStatus, l as ContractTestRef, m as CrossdeckAuthenticationError, n as CrossdeckConfigurationError, o as CrossdeckContracts, p as CrossdeckError, q as CrossdeckErrorPayload, r as CrossdeckErrorType, s as CrossdeckInternalError, t as CrossdeckNetworkError, u as CrossdeckPermissionError, v as CrossdeckRateLimitError, w as CrossdeckServer, x as CrossdeckServerOptions, y as CrossdeckValidationError, D as DEFAULT_BASE_URL, z as DEFAULT_TIMEOUT_MS, E as Diagnostics, F as EntitlementCacheOptions, G as EntitlementMutationResult, H as EntitlementStore, I as EntitlementsListResponse, J as EntitlementsListener, K as Environment, L as ErrorCaptureConfig, M as ErrorLevel, N as EventProperties, O as ForgetResult, P as GrantDuration, Q as GrantEntitlementInput, R as GroupMembership, S as HeartbeatResponse, T as HttpRequestInfo, U as HttpResponseInfo, V as HttpRetriesConfig, W as IdentifyOptions, X as IdentityHints, Y as IngestOptions, Z as IngestResponse, _ as PublicEntitlement, $ as PurchaseResult, a0 as RequestOptions, a1 as RevokeEntitlementInput, a2 as RuntimeHost, a3 as RuntimeInfo, a4 as ServerEvent, a5 as StackFrame, a6 as StoredEntitlements, a7 as SyncPurchaseInput, a8 as makeCrossdeckError } from './crossdeck-server-D9RvKxgA.js';
|
|
2
2
|
import 'node:events';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -17,7 +17,7 @@ import 'node:events';
|
|
|
17
17
|
*
|
|
18
18
|
* Do NOT edit by hand — `node scripts/sync-sdk-versions.mjs`.
|
|
19
19
|
*/
|
|
20
|
-
declare const SDK_VERSION = "1.
|
|
20
|
+
declare const SDK_VERSION = "1.8.0";
|
|
21
21
|
declare const SDK_NAME = "@cross-deck/node";
|
|
22
22
|
|
|
23
23
|
/**
|
|
@@ -51,7 +51,7 @@ interface ErrorCodeEntry {
|
|
|
51
51
|
/** The string thrown as `CrossdeckError.code`. */
|
|
52
52
|
code: string;
|
|
53
53
|
/** Broad category — `CrossdeckError.type`. */
|
|
54
|
-
type: "authentication_error" | "permission_error" | "invalid_request_error" | "rate_limit_error" | "internal_error" | "network_error" | "configuration_error";
|
|
54
|
+
type: "authentication_error" | "permission_error" | "invalid_request_error" | "rate_limit_error" | "version_error" | "internal_error" | "network_error" | "configuration_error";
|
|
55
55
|
/** One-sentence description. Surfaced verbatim in dashboards. */
|
|
56
56
|
description: string;
|
|
57
57
|
/** What the developer should do. Imperative phrasing. */
|
|
@@ -271,6 +271,12 @@ declare const _CROSSDECK_ERROR_CODES: readonly [{
|
|
|
271
271
|
readonly description: "A field is present but the value failed validation.";
|
|
272
272
|
readonly resolution: "Read error.message for the field + reason. SDK-managed call sites should never emit this — file a bug if you do.";
|
|
273
273
|
readonly retryable: false;
|
|
274
|
+
}, {
|
|
275
|
+
readonly code: "sdk_version_unsupported";
|
|
276
|
+
readonly type: "version_error";
|
|
277
|
+
readonly description: "HTTP 426 — 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 in memory and deliver once you upgrade and restart.";
|
|
278
|
+
readonly resolution: "Update @cross-deck/node to at least the version in error.minVersion and restart — the held queue backfills. See https://cross-deck.com/docs/sdk-event-durability/.";
|
|
279
|
+
readonly retryable: false;
|
|
274
280
|
}];
|
|
275
281
|
/**
|
|
276
282
|
* Literal union of every code documented in `CROSSDECK_ERROR_CODES`.
|
|
@@ -495,7 +501,7 @@ declare function scrubPiiFromProperties(properties: Record<string, unknown>): Re
|
|
|
495
501
|
* - `sdk.no_durable_store`
|
|
496
502
|
* - `sdk.super_property_registered`
|
|
497
503
|
*/
|
|
498
|
-
type DebugSignal = "sdk.configured" | "sdk.first_event_sent" | "sdk.invalid_key" | "sdk.no_identity" | "sdk.entitlement_cache_used" | "sdk.entitlement_cache_warm" | "sdk.entitlement_cache_stale" | "sdk.entitlement_store_recovered" | "sdk.no_durable_store" | "sdk.purchase_evidence_sent" | "sdk.environment_mismatch" | "sdk.sensitive_property_warning" | "sdk.property_coerced" | "sdk.flush_retry_scheduled" | "sdk.flush_permanent_failure" | "sdk.flush_on_exit_started" | "sdk.flush_on_exit_completed" | "sdk.webhook_verified" | "sdk.runtime_detected" | "sdk.super_property_registered" | "sdk.boot_heartbeat_failed";
|
|
504
|
+
type DebugSignal = "sdk.configured" | "sdk.first_event_sent" | "sdk.invalid_key" | "sdk.no_identity" | "sdk.entitlement_cache_used" | "sdk.entitlement_cache_warm" | "sdk.entitlement_cache_stale" | "sdk.entitlement_store_recovered" | "sdk.no_durable_store" | "sdk.purchase_evidence_sent" | "sdk.environment_mismatch" | "sdk.sensitive_property_warning" | "sdk.property_coerced" | "sdk.flush_retry_scheduled" | "sdk.flush_permanent_failure" | "sdk.parked" | "sdk.flush_on_exit_started" | "sdk.flush_on_exit_completed" | "sdk.webhook_verified" | "sdk.runtime_detected" | "sdk.super_property_registered" | "sdk.boot_heartbeat_failed";
|
|
499
505
|
interface DebugContext {
|
|
500
506
|
[key: string]: unknown;
|
|
501
507
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -8,6 +8,8 @@ var CrossdeckError = class _CrossdeckError extends Error {
|
|
|
8
8
|
requestId;
|
|
9
9
|
status;
|
|
10
10
|
retryAfterMs;
|
|
11
|
+
minVersion;
|
|
12
|
+
surface;
|
|
11
13
|
constructor(payload) {
|
|
12
14
|
super(payload.message);
|
|
13
15
|
this.name = "CrossdeckError";
|
|
@@ -16,6 +18,8 @@ var CrossdeckError = class _CrossdeckError extends Error {
|
|
|
16
18
|
this.requestId = payload.requestId;
|
|
17
19
|
this.status = payload.status;
|
|
18
20
|
this.retryAfterMs = payload.retryAfterMs;
|
|
21
|
+
this.minVersion = payload.minVersion;
|
|
22
|
+
this.surface = payload.surface;
|
|
19
23
|
Object.setPrototypeOf(this, _CrossdeckError.prototype);
|
|
20
24
|
}
|
|
21
25
|
/**
|
|
@@ -37,6 +41,8 @@ var CrossdeckError = class _CrossdeckError extends Error {
|
|
|
37
41
|
requestId: this.requestId,
|
|
38
42
|
status: this.status,
|
|
39
43
|
retryAfterMs: this.retryAfterMs,
|
|
44
|
+
minVersion: this.minVersion,
|
|
45
|
+
surface: this.surface,
|
|
40
46
|
stack: this.stack
|
|
41
47
|
};
|
|
42
48
|
}
|
|
@@ -127,7 +133,10 @@ async function crossdeckErrorFromResponse(res) {
|
|
|
127
133
|
message: envelope.message ?? `HTTP ${res.status}`,
|
|
128
134
|
requestId: envelope.request_id ?? requestId,
|
|
129
135
|
status: res.status,
|
|
130
|
-
retryAfterMs
|
|
136
|
+
retryAfterMs,
|
|
137
|
+
// PARK metadata, present only on a 426 / sdk_version_unsupported body.
|
|
138
|
+
minVersion: typeof envelope.minVersion === "string" ? envelope.minVersion : void 0,
|
|
139
|
+
surface: typeof envelope.surface === "string" ? envelope.surface : void 0
|
|
131
140
|
});
|
|
132
141
|
}
|
|
133
142
|
return makeCrossdeckError({
|
|
@@ -320,7 +329,7 @@ function byteLength(s) {
|
|
|
320
329
|
import * as https from "https";
|
|
321
330
|
|
|
322
331
|
// src/_version.ts
|
|
323
|
-
var SDK_VERSION = "1.
|
|
332
|
+
var SDK_VERSION = "1.8.0";
|
|
324
333
|
var SDK_NAME = "@cross-deck/node";
|
|
325
334
|
|
|
326
335
|
// src/_diagnostic-telemetry.ts
|
|
@@ -340,7 +349,12 @@ var DIAGNOSTIC_TELEMETRY_ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
|
340
349
|
"run_id",
|
|
341
350
|
"test_file",
|
|
342
351
|
"test_name",
|
|
343
|
-
"device_class"
|
|
352
|
+
"device_class",
|
|
353
|
+
// verification_phase — categorical `boot` / `hot_path` bucket set by the
|
|
354
|
+
// runtime contract verifier layer. Declared in the shared diagnostics
|
|
355
|
+
// contract (contracts/diagnostics/contract-failed-payload-schema-lock.json)
|
|
356
|
+
// and carried by web/swift; node had drifted by omitting it.
|
|
357
|
+
"verification_phase"
|
|
344
358
|
]);
|
|
345
359
|
function filterDiagnosticPayload(payload) {
|
|
346
360
|
const filtered = {};
|
|
@@ -833,6 +847,16 @@ var EventQueue = class {
|
|
|
833
847
|
cancelTimer = null;
|
|
834
848
|
firstFlushFired = false;
|
|
835
849
|
nextRetryAt = null;
|
|
850
|
+
/**
|
|
851
|
+
* PARK state (HTTP 426 / `sdk_version_unsupported`). Once parked, the
|
|
852
|
+
* queue stops flushing — retrying a known-too-old payload only wastes
|
|
853
|
+
* bandwidth and drips pointless rejects into the server logs until the
|
|
854
|
+
* process restarts on an upgraded SDK. In-memory: a fresh process starts
|
|
855
|
+
* unparked, retries once, and either delivers (upgraded) or re-parks.
|
|
856
|
+
*/
|
|
857
|
+
parked = false;
|
|
858
|
+
/** One developer-facing console warning per process — never per-event spam. */
|
|
859
|
+
parkWarned = false;
|
|
836
860
|
retry;
|
|
837
861
|
/**
|
|
838
862
|
* Stable Idempotency-Key for the current in-flight batch. Minted
|
|
@@ -885,6 +909,7 @@ var EventQueue = class {
|
|
|
885
909
|
* this one settles. Strict ordering preserved.
|
|
886
910
|
*/
|
|
887
911
|
async flush() {
|
|
912
|
+
if (this.parked) return null;
|
|
888
913
|
let batch;
|
|
889
914
|
let batchId;
|
|
890
915
|
if (this.pendingBatch !== null && this.pendingBatchId !== null) {
|
|
@@ -932,6 +957,28 @@ var EventQueue = class {
|
|
|
932
957
|
} catch (err) {
|
|
933
958
|
const message = err instanceof Error ? err.message : String(err);
|
|
934
959
|
this.lastError = message;
|
|
960
|
+
if (isVersionRejected(err)) {
|
|
961
|
+
this.parked = true;
|
|
962
|
+
this.buffer = [...batch, ...this.buffer];
|
|
963
|
+
if (this.buffer.length > HARD_BUFFER_CAP) {
|
|
964
|
+
const overflow = this.buffer.length - HARD_BUFFER_CAP;
|
|
965
|
+
this.buffer.splice(0, overflow);
|
|
966
|
+
this.dropped += overflow;
|
|
967
|
+
}
|
|
968
|
+
this.pendingBatch = null;
|
|
969
|
+
this.pendingBatchId = null;
|
|
970
|
+
this.inFlight -= batch.length;
|
|
971
|
+
this.cfg.onBufferChange?.(this.buffer.length);
|
|
972
|
+
const minVersion = versionRejectionFloor(err);
|
|
973
|
+
if (!this.parkWarned) {
|
|
974
|
+
this.parkWarned = true;
|
|
975
|
+
console.warn(
|
|
976
|
+
`[Crossdeck] SDK outdated \u2014 the server is no longer accepting this version's event format. Events are PARKED in memory (held, not lost across this process) and will deliver once you update @cross-deck/node${minVersion ? ` to >= ${minVersion}` : ""} and restart. A restart before upgrade clears them \u2014 configure a disk queue for cross-restart durability.`
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
this.cfg.onParked?.({ minVersion, surface: versionRejectionSurface(err) });
|
|
980
|
+
return null;
|
|
981
|
+
}
|
|
935
982
|
if (isPermanent4xx(err)) {
|
|
936
983
|
const droppedCount = batch.length;
|
|
937
984
|
this.pendingBatch = null;
|
|
@@ -1030,8 +1077,22 @@ function isPermanent4xx(err) {
|
|
|
1030
1077
|
if (typeof status !== "number" || !Number.isFinite(status)) return false;
|
|
1031
1078
|
if (status < 400 || status >= 500) return false;
|
|
1032
1079
|
if (status === 408 || status === 429) return false;
|
|
1080
|
+
if (status === 426) return false;
|
|
1033
1081
|
return true;
|
|
1034
1082
|
}
|
|
1083
|
+
function isVersionRejected(err) {
|
|
1084
|
+
if (!err || typeof err !== "object") return false;
|
|
1085
|
+
if (err.status === 426) return true;
|
|
1086
|
+
return err.code === "sdk_version_unsupported";
|
|
1087
|
+
}
|
|
1088
|
+
function versionRejectionFloor(err) {
|
|
1089
|
+
const v = err?.minVersion;
|
|
1090
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
1091
|
+
}
|
|
1092
|
+
function versionRejectionSurface(err) {
|
|
1093
|
+
const v = err?.surface;
|
|
1094
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
1095
|
+
}
|
|
1035
1096
|
function defaultScheduler(fn, ms) {
|
|
1036
1097
|
const id = setTimeout(fn, ms);
|
|
1037
1098
|
if (typeof id.unref === "function") {
|
|
@@ -1314,6 +1375,7 @@ var ErrorTracker = class {
|
|
|
1314
1375
|
installFetchWrap() {
|
|
1315
1376
|
const origFetch = globalThis.fetch;
|
|
1316
1377
|
if (typeof origFetch !== "function") return;
|
|
1378
|
+
if (origFetch.__crossdeckWrapped__) return;
|
|
1317
1379
|
const tracker = this;
|
|
1318
1380
|
const wrapped = async (...args) => {
|
|
1319
1381
|
const input = args[0];
|
|
@@ -1354,6 +1416,7 @@ var ErrorTracker = class {
|
|
|
1354
1416
|
throw err;
|
|
1355
1417
|
}
|
|
1356
1418
|
};
|
|
1419
|
+
wrapped.__crossdeckWrapped__ = true;
|
|
1357
1420
|
globalThis.fetch = wrapped;
|
|
1358
1421
|
this.cleanups.push(() => {
|
|
1359
1422
|
if (globalThis.fetch === wrapped) globalThis.fetch = origFetch;
|
|
@@ -2559,6 +2622,9 @@ function safeJson(obj) {
|
|
|
2559
2622
|
|
|
2560
2623
|
// src/crossdeck-server.ts
|
|
2561
2624
|
var CrossdeckServer = class extends EventEmitter {
|
|
2625
|
+
// `!` (definite assignment): these are assigned on the real construction path,
|
|
2626
|
+
// but the singleton guard in the constructor can `return` an existing instance
|
|
2627
|
+
// before reaching them — that early return is the only path that skips them.
|
|
2562
2628
|
http;
|
|
2563
2629
|
sdkVersion;
|
|
2564
2630
|
baseUrl;
|
|
@@ -2654,6 +2720,13 @@ var CrossdeckServer = class extends EventEmitter {
|
|
|
2654
2720
|
this.appId = options.appId;
|
|
2655
2721
|
this.baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
|
|
2656
2722
|
this.env = inferEnvFromKey(options.secretKey);
|
|
2723
|
+
const _store = globalThis;
|
|
2724
|
+
const _singletonKey = `${options.secretKey}|${this.appId ?? ""}|${this.baseUrl}`;
|
|
2725
|
+
_store.__crossdeckServers__ ??= /* @__PURE__ */ new Map();
|
|
2726
|
+
const _existing = _store.__crossdeckServers__.get(_singletonKey);
|
|
2727
|
+
if (_existing) {
|
|
2728
|
+
return _existing;
|
|
2729
|
+
}
|
|
2657
2730
|
this.secretKeyPrefix = maskSecretKey(options.secretKey);
|
|
2658
2731
|
this.scrubPii = options.scrubPii !== false;
|
|
2659
2732
|
this.http = new HttpClient({
|
|
@@ -2735,6 +2808,13 @@ var CrossdeckServer = class extends EventEmitter {
|
|
|
2735
2808
|
error: info.lastError
|
|
2736
2809
|
});
|
|
2737
2810
|
},
|
|
2811
|
+
onParked: (info) => {
|
|
2812
|
+
this.debug.emit(
|
|
2813
|
+
"sdk.parked",
|
|
2814
|
+
`[crossdeck] SDK parked \u2014 server no longer accepts this version's event format. Events held (paused, not lost); update @cross-deck/node${info.minVersion ? ` to >= ${info.minVersion}` : ""} and restart to resume.`,
|
|
2815
|
+
{ ...info }
|
|
2816
|
+
);
|
|
2817
|
+
},
|
|
2738
2818
|
onFirstFlushSuccess: () => {
|
|
2739
2819
|
this.debug.emit("sdk.first_event_sent", "First batch landed.");
|
|
2740
2820
|
}
|
|
@@ -2788,6 +2868,17 @@ var CrossdeckServer = class extends EventEmitter {
|
|
|
2788
2868
|
this.emitBootTelemetryEvent();
|
|
2789
2869
|
});
|
|
2790
2870
|
}
|
|
2871
|
+
_store.__crossdeckServers__.set(_singletonKey, this);
|
|
2872
|
+
}
|
|
2873
|
+
/**
|
|
2874
|
+
* Clear the process-wide singleton cache. The SDK hands back the SAME instance
|
|
2875
|
+
* for the same credentials (the Next.js / serverless re-instantiation guard in
|
|
2876
|
+
* the constructor); this resets that so the next `new CrossdeckServer()` builds a
|
|
2877
|
+
* fresh instance. For TESTS (per-test isolation) and bespoke hot-reload teardown
|
|
2878
|
+
* only — production code never needs it.
|
|
2879
|
+
*/
|
|
2880
|
+
static clearSingletonCache() {
|
|
2881
|
+
globalThis.__crossdeckServers__?.clear();
|
|
2791
2882
|
}
|
|
2792
2883
|
/**
|
|
2793
2884
|
* Emit the honest "no cold-start durability" warning when the runtime
|
|
@@ -4309,6 +4400,13 @@ var _CROSSDECK_ERROR_CODES = Object.freeze([
|
|
|
4309
4400
|
description: "A field is present but the value failed validation.",
|
|
4310
4401
|
resolution: "Read error.message for the field + reason. SDK-managed call sites should never emit this \u2014 file a bug if you do.",
|
|
4311
4402
|
retryable: false
|
|
4403
|
+
},
|
|
4404
|
+
{
|
|
4405
|
+
code: "sdk_version_unsupported",
|
|
4406
|
+
type: "version_error",
|
|
4407
|
+
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 in memory and deliver once you upgrade and restart.",
|
|
4408
|
+
resolution: "Update @cross-deck/node to at least the version in error.minVersion and restart \u2014 the held queue backfills. See https://cross-deck.com/docs/sdk-event-durability/.",
|
|
4409
|
+
retryable: false
|
|
4312
4410
|
}
|
|
4313
4411
|
]);
|
|
4314
4412
|
function isCrossdeckErrorCode(code) {
|
|
@@ -4440,8 +4538,8 @@ function normaliseSecrets(input) {
|
|
|
4440
4538
|
}
|
|
4441
4539
|
|
|
4442
4540
|
// src/_contracts-bundled.ts
|
|
4443
|
-
var BUNDLED_IN = "@cross-deck/node@1.
|
|
4444
|
-
var SDK_VERSION2 = "1.
|
|
4541
|
+
var BUNDLED_IN = "@cross-deck/node@1.8.0";
|
|
4542
|
+
var SDK_VERSION2 = "1.8.0";
|
|
4445
4543
|
var BUNDLED_CONTRACTS = Object.freeze([
|
|
4446
4544
|
{
|
|
4447
4545
|
"id": "contract-failed-payload-schema-lock",
|
|
@@ -4563,7 +4661,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
4563
4661
|
"legal/security/index.html#diagnostic",
|
|
4564
4662
|
"legal/sdk-data/index.html#b-diagnostic"
|
|
4565
4663
|
],
|
|
4566
|
-
"bundledIn": "@cross-deck/node@1.
|
|
4664
|
+
"bundledIn": "@cross-deck/node@1.8.0"
|
|
4567
4665
|
},
|
|
4568
4666
|
{
|
|
4569
4667
|
"id": "documentation-honesty",
|
|
@@ -4595,7 +4693,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
4595
4693
|
],
|
|
4596
4694
|
"registeredAt": "2026-05-26",
|
|
4597
4695
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 7.1",
|
|
4598
|
-
"bundledIn": "@cross-deck/node@1.
|
|
4696
|
+
"bundledIn": "@cross-deck/node@1.8.0"
|
|
4599
4697
|
},
|
|
4600
4698
|
{
|
|
4601
4699
|
"id": "error-envelope-shape",
|
|
@@ -4634,7 +4732,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
4634
4732
|
],
|
|
4635
4733
|
"registeredAt": "2026-05-26",
|
|
4636
4734
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 8 (codifies existing contract)",
|
|
4637
|
-
"bundledIn": "@cross-deck/node@1.
|
|
4735
|
+
"bundledIn": "@cross-deck/node@1.8.0"
|
|
4638
4736
|
},
|
|
4639
4737
|
{
|
|
4640
4738
|
"id": "flush-interval-parity",
|
|
@@ -4679,7 +4777,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
4679
4777
|
],
|
|
4680
4778
|
"registeredAt": "2026-05-26",
|
|
4681
4779
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.3",
|
|
4682
|
-
"bundledIn": "@cross-deck/node@1.
|
|
4780
|
+
"bundledIn": "@cross-deck/node@1.8.0"
|
|
4683
4781
|
},
|
|
4684
4782
|
{
|
|
4685
4783
|
"id": "idempotency-key-deterministic",
|
|
@@ -4784,7 +4882,63 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
4784
4882
|
],
|
|
4785
4883
|
"registeredAt": "2026-05-26",
|
|
4786
4884
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 2.2.a + 2.2.b + 2.2.c",
|
|
4787
|
-
"bundledIn": "@cross-deck/node@1.
|
|
4885
|
+
"bundledIn": "@cross-deck/node@1.8.0"
|
|
4886
|
+
},
|
|
4887
|
+
{
|
|
4888
|
+
"id": "invalid-input-rejected-natively",
|
|
4889
|
+
"pillar": "errors",
|
|
4890
|
+
"status": "enforced",
|
|
4891
|
+
"claim": "No public SDK API ever crashes the host app, and invalid input never reaches the wire. Invalid input (empty event name, empty userId, out-of-range config such as a non-positive breadcrumb capacity, NaN/Infinity/oversize/cyclic property values) is rejected at the call site WITHOUT a fatal trap. The signalling IDIOM is per-language and intentionally NOT uniform: Web, Node, and React Native THROW a typed CrossdeckError synchronously (code missing_event_name / missing_user_id / invalid_request_error) \u2014 a normal, catchable JavaScript convention where an uncaught throw logs and the app continues; Swift DROPS with a debug-log signal (track_dropped / identify_dropped) to match its non-throwing fire-and-forget surface, and exposes the throwing equivalent only via identifyAndWait(userId:). What is UNIFORM is the invariant, not the mechanism: every SDK rejects the same inputs, no public fire-and-forget API contains a fatalError / assertionFailure / precondition reachable from customer input, and no rejected input is enqueued or transmitted. Swift additionally proves this in BOTH debug and release configuration, because precondition fires under -O while assertionFailure does not. The bug class this contract closes was never the per-language difference \u2014 it was the UNDECLARED difference: each SDK's public API documentation must state its own semantics explicitly (TS docs: 'throws on empty name'; Swift docs: 'drops and logs').",
|
|
4892
|
+
"appliesTo": [
|
|
4893
|
+
"web",
|
|
4894
|
+
"node",
|
|
4895
|
+
"react-native",
|
|
4896
|
+
"swift"
|
|
4897
|
+
],
|
|
4898
|
+
"codeRef": [
|
|
4899
|
+
"sdks/web/src/crossdeck.ts",
|
|
4900
|
+
"sdks/node/src/crossdeck-server.ts",
|
|
4901
|
+
"sdks/react-native/src/crossdeck.ts",
|
|
4902
|
+
"sdks/swift/Sources/Crossdeck/Crossdeck.swift",
|
|
4903
|
+
"sdks/swift/Sources/Crossdeck/Breadcrumbs.swift"
|
|
4904
|
+
],
|
|
4905
|
+
"testRef": [
|
|
4906
|
+
{
|
|
4907
|
+
"file": "sdks/web/tests/crossdeck.test.ts",
|
|
4908
|
+
"name": "track with empty name throws synchronously"
|
|
4909
|
+
},
|
|
4910
|
+
{
|
|
4911
|
+
"file": "sdks/web/tests/crossdeck.test.ts",
|
|
4912
|
+
"name": "rejects empty userId"
|
|
4913
|
+
},
|
|
4914
|
+
{
|
|
4915
|
+
"file": "sdks/node/tests/crossdeck-server.test.ts",
|
|
4916
|
+
"name": "track() throws CrossdeckError with code 'missing_event_name' when event name is empty"
|
|
4917
|
+
},
|
|
4918
|
+
{
|
|
4919
|
+
"file": "sdks/react-native/tests/crossdeck.test.ts",
|
|
4920
|
+
"name": "track('') throws CrossdeckError(missing_event_name) synchronously"
|
|
4921
|
+
},
|
|
4922
|
+
{
|
|
4923
|
+
"file": "sdks/react-native/tests/crossdeck.test.ts",
|
|
4924
|
+
"name": "identify('') rejects with CrossdeckError(missing_user_id)"
|
|
4925
|
+
},
|
|
4926
|
+
{
|
|
4927
|
+
"file": "sdks/swift/Tests/CrossdeckTests/CrossdeckPublicAPITests.swift",
|
|
4928
|
+
"name": "test_track_dropsEmptyName"
|
|
4929
|
+
},
|
|
4930
|
+
{
|
|
4931
|
+
"file": "sdks/swift/Tests/CrossdeckTests/CrossdeckPublicAPITests.swift",
|
|
4932
|
+
"name": "test_identifyAndWait_rejectsEmptyId"
|
|
4933
|
+
},
|
|
4934
|
+
{
|
|
4935
|
+
"file": "sdks/swift/Tests/CrossdeckTests/PublicAPIInputSafetyTests.swift",
|
|
4936
|
+
"name": "test_start_withZeroBreadcrumbCapacity_doesNotTrap"
|
|
4937
|
+
}
|
|
4938
|
+
],
|
|
4939
|
+
"registeredAt": "2026-06-11",
|
|
4940
|
+
"firstRegisteredIn": "swift trap-on-input class fix \u2014 first machine-tested Swift release",
|
|
4941
|
+
"bundledIn": "@cross-deck/node@1.8.0"
|
|
4788
4942
|
},
|
|
4789
4943
|
{
|
|
4790
4944
|
"id": "node-pii-scrubber",
|
|
@@ -4823,7 +4977,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
4823
4977
|
],
|
|
4824
4978
|
"registeredAt": "2026-05-26",
|
|
4825
4979
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.1",
|
|
4826
|
-
"bundledIn": "@cross-deck/node@1.
|
|
4980
|
+
"bundledIn": "@cross-deck/node@1.8.0"
|
|
4827
4981
|
},
|
|
4828
4982
|
{
|
|
4829
4983
|
"id": "node-shutdown-awaits-flush",
|
|
@@ -4856,7 +5010,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
4856
5010
|
],
|
|
4857
5011
|
"registeredAt": "2026-05-26",
|
|
4858
5012
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 5.4",
|
|
4859
|
-
"bundledIn": "@cross-deck/node@1.
|
|
5013
|
+
"bundledIn": "@cross-deck/node@1.8.0"
|
|
4860
5014
|
},
|
|
4861
5015
|
{
|
|
4862
5016
|
"id": "sdk-error-codes-catalogue",
|
|
@@ -4901,7 +5055,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
4901
5055
|
],
|
|
4902
5056
|
"registeredAt": "2026-05-26",
|
|
4903
5057
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 6.2",
|
|
4904
|
-
"bundledIn": "@cross-deck/node@1.
|
|
5058
|
+
"bundledIn": "@cross-deck/node@1.8.0"
|
|
4905
5059
|
},
|
|
4906
5060
|
{
|
|
4907
5061
|
"id": "sync-purchases-funnel-parity",
|
|
@@ -4934,7 +5088,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
4934
5088
|
],
|
|
4935
5089
|
"registeredAt": "2026-05-26",
|
|
4936
5090
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.5",
|
|
4937
|
-
"bundledIn": "@cross-deck/node@1.
|
|
5091
|
+
"bundledIn": "@cross-deck/node@1.8.0"
|
|
4938
5092
|
},
|
|
4939
5093
|
{
|
|
4940
5094
|
"id": "verifier-timestamp-mandatory",
|
|
@@ -4988,7 +5142,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
4988
5142
|
],
|
|
4989
5143
|
"registeredAt": "2026-05-26",
|
|
4990
5144
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 7.2",
|
|
4991
|
-
"bundledIn": "@cross-deck/node@1.
|
|
5145
|
+
"bundledIn": "@cross-deck/node@1.8.0"
|
|
4992
5146
|
}
|
|
4993
5147
|
]);
|
|
4994
5148
|
|