@cross-deck/web 1.6.1 → 1.6.3
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 +43 -0
- package/dist/contracts.json +58 -12
- package/dist/crossdeck.umd.min.js +2 -2
- package/dist/crossdeck.umd.min.js.map +1 -1
- package/dist/error-codes.json +1 -1
- package/dist/index.cjs +343 -218
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +18 -3
- package/dist/index.d.ts +18 -3
- package/dist/index.mjs +343 -218
- package/dist/index.mjs.map +1 -1
- package/dist/react.cjs +278 -4
- package/dist/react.cjs.map +1 -1
- package/dist/react.mjs +278 -4
- package/dist/react.mjs.map +1 -1
- package/dist/vue.cjs +278 -4
- package/dist/vue.cjs.map +1 -1
- package/dist/vue.mjs +278 -4
- package/dist/vue.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -99,7 +99,7 @@ function typeMapForStatus(status) {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
// src/_version.ts
|
|
102
|
-
var SDK_VERSION = "1.6.
|
|
102
|
+
var SDK_VERSION = "1.6.3";
|
|
103
103
|
var SDK_NAME = "@cross-deck/web";
|
|
104
104
|
|
|
105
105
|
// src/http.ts
|
|
@@ -1524,6 +1524,13 @@ var AutoTracker = class {
|
|
|
1524
1524
|
markActivity() {
|
|
1525
1525
|
if (!this.session) return;
|
|
1526
1526
|
const now = Date.now();
|
|
1527
|
+
if (now - this.session.lastActivityAt >= SESSION_RESUME_THRESHOLD_MS) {
|
|
1528
|
+
this.pageviewId = null;
|
|
1529
|
+
this.session = this.startNewSession();
|
|
1530
|
+
this.persistSession();
|
|
1531
|
+
this.emitSessionStart();
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1527
1534
|
this.session.lastActivityAt = now;
|
|
1528
1535
|
if (now - this.lastPersistAt >= ACTIVITY_PERSIST_THROTTLE_MS) {
|
|
1529
1536
|
this.persistSession();
|
|
@@ -1577,7 +1584,6 @@ var AutoTracker = class {
|
|
|
1577
1584
|
} else if (doc2.visibilityState === "visible") {
|
|
1578
1585
|
const idleFor = Date.now() - this.session.lastActivityAt;
|
|
1579
1586
|
if (idleFor >= SESSION_RESUME_THRESHOLD_MS) {
|
|
1580
|
-
this.emitSessionEnd();
|
|
1581
1587
|
this.pageviewId = null;
|
|
1582
1588
|
this.session = this.startNewSession();
|
|
1583
1589
|
this.persistSession();
|
|
@@ -2639,6 +2645,210 @@ function sendDiagnosticTelemetry(payload) {
|
|
|
2639
2645
|
}
|
|
2640
2646
|
}
|
|
2641
2647
|
|
|
2648
|
+
// src/error-codes.ts
|
|
2649
|
+
var CROSSDECK_ERROR_CODES = Object.freeze([
|
|
2650
|
+
// ----- Configuration -----
|
|
2651
|
+
{
|
|
2652
|
+
code: "invalid_public_key",
|
|
2653
|
+
type: "configuration_error",
|
|
2654
|
+
description: "The publishable key passed to Crossdeck.init() doesn't start with cd_pub_.",
|
|
2655
|
+
resolution: "Copy the key from your Crossdeck dashboard \u2192 API keys page.",
|
|
2656
|
+
retryable: false
|
|
2657
|
+
},
|
|
2658
|
+
{
|
|
2659
|
+
code: "missing_app_id",
|
|
2660
|
+
type: "configuration_error",
|
|
2661
|
+
description: "Crossdeck.init() was called without an appId.",
|
|
2662
|
+
resolution: "Add appId to your init options \u2014 find it in the dashboard's Apps page.",
|
|
2663
|
+
retryable: false
|
|
2664
|
+
},
|
|
2665
|
+
{
|
|
2666
|
+
code: "invalid_environment",
|
|
2667
|
+
type: "configuration_error",
|
|
2668
|
+
description: "Crossdeck.init() requires environment: 'production' | 'sandbox'.",
|
|
2669
|
+
resolution: 'Pass the literal string "production" or "sandbox" \u2014 no other values are accepted.',
|
|
2670
|
+
retryable: false
|
|
2671
|
+
},
|
|
2672
|
+
{
|
|
2673
|
+
code: "environment_mismatch",
|
|
2674
|
+
type: "configuration_error",
|
|
2675
|
+
description: "The publishable key's env prefix doesn't match the declared environment option.",
|
|
2676
|
+
resolution: "Either change `environment` to match the key prefix (cd_pub_test_ \u2194 sandbox, cd_pub_live_ \u2194 production), or swap the key for one minted in the right env.",
|
|
2677
|
+
retryable: false
|
|
2678
|
+
},
|
|
2679
|
+
{
|
|
2680
|
+
code: "not_initialized",
|
|
2681
|
+
type: "configuration_error",
|
|
2682
|
+
description: "An SDK method was called before Crossdeck.init().",
|
|
2683
|
+
resolution: "Call Crossdeck.init({ appId, publicKey, environment }) once at app startup before any other method.",
|
|
2684
|
+
retryable: false
|
|
2685
|
+
},
|
|
2686
|
+
// ----- Identify / track / purchase argument validation -----
|
|
2687
|
+
{
|
|
2688
|
+
code: "missing_user_id",
|
|
2689
|
+
type: "invalid_request_error",
|
|
2690
|
+
description: "identify() was called with an empty userId.",
|
|
2691
|
+
resolution: "Pass a stable, non-empty user identifier from your auth layer \u2014 never a hardcoded placeholder.",
|
|
2692
|
+
retryable: false
|
|
2693
|
+
},
|
|
2694
|
+
{
|
|
2695
|
+
code: "missing_event_name",
|
|
2696
|
+
type: "invalid_request_error",
|
|
2697
|
+
description: "track() was called without an event name.",
|
|
2698
|
+
resolution: "Pass a non-empty string as the first argument.",
|
|
2699
|
+
retryable: false
|
|
2700
|
+
},
|
|
2701
|
+
{
|
|
2702
|
+
code: "missing_group_type",
|
|
2703
|
+
type: "invalid_request_error",
|
|
2704
|
+
description: "group() was called without a group type.",
|
|
2705
|
+
resolution: 'Pass a non-empty type (e.g. "org", "team") as the first argument.',
|
|
2706
|
+
retryable: false
|
|
2707
|
+
},
|
|
2708
|
+
{
|
|
2709
|
+
code: "missing_signed_transaction_info",
|
|
2710
|
+
type: "invalid_request_error",
|
|
2711
|
+
description: "syncPurchases() was called without StoreKit 2 signed transaction info.",
|
|
2712
|
+
resolution: "Pass the JWS string from Transaction.currentEntitlements / Transaction.updates.",
|
|
2713
|
+
retryable: false
|
|
2714
|
+
},
|
|
2715
|
+
// ----- Network / transport -----
|
|
2716
|
+
{
|
|
2717
|
+
code: "fetch_failed",
|
|
2718
|
+
type: "network_error",
|
|
2719
|
+
description: "The underlying fetch() call failed (typically a network outage or DNS issue).",
|
|
2720
|
+
resolution: "Check the user's network. The SDK will retry automatically with exponential backoff.",
|
|
2721
|
+
retryable: true
|
|
2722
|
+
},
|
|
2723
|
+
{
|
|
2724
|
+
code: "request_timeout",
|
|
2725
|
+
type: "network_error",
|
|
2726
|
+
description: "A request was aborted after the configured timeoutMs (default 15s).",
|
|
2727
|
+
resolution: "Check the user's connection. Increase timeoutMs in init options if the user is on a known-slow network.",
|
|
2728
|
+
retryable: true
|
|
2729
|
+
},
|
|
2730
|
+
{
|
|
2731
|
+
code: "invalid_json_response",
|
|
2732
|
+
type: "internal_error",
|
|
2733
|
+
description: "The server returned a 2xx with an unparseable body.",
|
|
2734
|
+
resolution: "Likely a transient backend bug. Retry; if it persists, contact support with the requestId.",
|
|
2735
|
+
retryable: true
|
|
2736
|
+
},
|
|
2737
|
+
// ----- Backend-emitted codes (v1.4.0 Phase 6.2 backfill) -----
|
|
2738
|
+
// Mirror of backend/src/api/v1-errors.ts ApiErrorCode. A developer
|
|
2739
|
+
// hitting any of these on the wire can look them up via
|
|
2740
|
+
// getErrorCode(code) for a canonical remediation step instead of
|
|
2741
|
+
// hunting through Slack history.
|
|
2742
|
+
{
|
|
2743
|
+
code: "missing_api_key",
|
|
2744
|
+
type: "authentication_error",
|
|
2745
|
+
description: "No Authorization header (or Crossdeck-Api-Key header) on the request.",
|
|
2746
|
+
resolution: "Make sure Crossdeck.init({ publicKey }) was called with a cd_pub_\u2026 key before the request fired. Re-check your env-vars in CI / Docker if the key is empty at runtime.",
|
|
2747
|
+
retryable: false
|
|
2748
|
+
},
|
|
2749
|
+
{
|
|
2750
|
+
code: "invalid_api_key",
|
|
2751
|
+
type: "authentication_error",
|
|
2752
|
+
description: "The API key is malformed, unknown, or doesn't resolve to a project.",
|
|
2753
|
+
resolution: "Copy the key from your Crossdeck dashboard \u2192 API keys. Confirm prefix is cd_pub_test_ / cd_pub_live_ for client SDKs and cd_sk_test_ / cd_sk_live_ for the Node server SDK.",
|
|
2754
|
+
retryable: false
|
|
2755
|
+
},
|
|
2756
|
+
{
|
|
2757
|
+
code: "key_revoked",
|
|
2758
|
+
type: "authentication_error",
|
|
2759
|
+
description: "The API key was revoked in the Crossdeck dashboard.",
|
|
2760
|
+
resolution: "Mint a fresh key in the dashboard \u2192 API keys \u2192 Create new, swap it in, and redeploy. The revoked key cannot be reactivated.",
|
|
2761
|
+
retryable: false
|
|
2762
|
+
},
|
|
2763
|
+
{
|
|
2764
|
+
code: "identity_token_invalid",
|
|
2765
|
+
type: "authentication_error",
|
|
2766
|
+
description: "The Firebase / Apple / Google ID token supplied with the request didn't verify against the dashboard's configured signers.",
|
|
2767
|
+
resolution: "Refresh the token client-side (Firebase auth.currentUser.getIdToken(true)) and retry. If the failure persists, confirm the signer is registered under dashboard \u2192 Authentication \u2192 Identity sources.",
|
|
2768
|
+
retryable: true
|
|
2769
|
+
},
|
|
2770
|
+
{
|
|
2771
|
+
code: "origin_not_allowed",
|
|
2772
|
+
type: "permission_error",
|
|
2773
|
+
description: "The Origin header isn't in the project's Allowed origins list.",
|
|
2774
|
+
resolution: "Add the origin (e.g. https://app.example.com) under dashboard \u2192 Settings \u2192 Allowed origins. Wildcards like https://*.example.com are supported.",
|
|
2775
|
+
retryable: false
|
|
2776
|
+
},
|
|
2777
|
+
{
|
|
2778
|
+
code: "bundle_id_not_allowed",
|
|
2779
|
+
type: "permission_error",
|
|
2780
|
+
description: "The iOS bundle ID sent via X-Crossdeck-Bundle-Id isn't registered under this app's Apple identity lock.",
|
|
2781
|
+
resolution: "Add the bundle ID under dashboard \u2192 Apps \u2192 <your app> \u2192 iOS bundle IDs.",
|
|
2782
|
+
retryable: false
|
|
2783
|
+
},
|
|
2784
|
+
{
|
|
2785
|
+
code: "package_name_not_allowed",
|
|
2786
|
+
type: "permission_error",
|
|
2787
|
+
description: "The Android package name sent via X-Crossdeck-Package-Name isn't registered under this app's Android identity lock.",
|
|
2788
|
+
resolution: "Add the package name under dashboard \u2192 Apps \u2192 <your app> \u2192 Android package names.",
|
|
2789
|
+
retryable: false
|
|
2790
|
+
},
|
|
2791
|
+
{
|
|
2792
|
+
code: "env_mismatch",
|
|
2793
|
+
type: "permission_error",
|
|
2794
|
+
description: "The request env (inferred from key prefix) doesn't match the resolved app's configured env.",
|
|
2795
|
+
resolution: "Use a cd_pub_live_ / cd_sk_live_ key with a production app, cd_pub_test_ / cd_sk_test_ with a sandbox app. The two cannot cross.",
|
|
2796
|
+
retryable: false
|
|
2797
|
+
},
|
|
2798
|
+
{
|
|
2799
|
+
code: "idempotency_key_in_use",
|
|
2800
|
+
type: "invalid_request_error",
|
|
2801
|
+
description: "An Idempotency-Key was reused for a request with a different body (Stripe-grade contract).",
|
|
2802
|
+
resolution: "Generate a fresh key for a different transaction, or reuse the key only with the EXACT same body. The v1.4.0 SDKs derive keys deterministically from the body so this should never fire on SDK-managed calls.",
|
|
2803
|
+
retryable: false
|
|
2804
|
+
},
|
|
2805
|
+
{
|
|
2806
|
+
code: "rate_limited",
|
|
2807
|
+
type: "rate_limit_error",
|
|
2808
|
+
description: "Request rate exceeded the project's per-second cap.",
|
|
2809
|
+
resolution: "Honour the Retry-After header \u2014 the SDK does this automatically on managed retries. If you're hitting it from a custom code path, throttle to <100 req/s/key.",
|
|
2810
|
+
retryable: true
|
|
2811
|
+
},
|
|
2812
|
+
{
|
|
2813
|
+
code: "internal_error",
|
|
2814
|
+
type: "internal_error",
|
|
2815
|
+
description: "Server-side issue. Safe to retry with backoff.",
|
|
2816
|
+
resolution: "The SDK retries automatically. If your code paths through to this error, contact support with the requestId from the response envelope.",
|
|
2817
|
+
retryable: true
|
|
2818
|
+
},
|
|
2819
|
+
{
|
|
2820
|
+
code: "google_not_supported",
|
|
2821
|
+
type: "invalid_request_error",
|
|
2822
|
+
description: "POST /purchases/sync with rail=google is gated until the Play Developer API reconciliation worker ships.",
|
|
2823
|
+
resolution: "Until v1.5+, Google Play purchases verify via Real-time Developer Notifications. The SDK auto-track path handles this transparently for Android consumers.",
|
|
2824
|
+
retryable: false
|
|
2825
|
+
},
|
|
2826
|
+
{
|
|
2827
|
+
code: "stripe_not_supported",
|
|
2828
|
+
type: "invalid_request_error",
|
|
2829
|
+
description: "POST /purchases/sync with rail=stripe is unsupported \u2014 Stripe Checkout's redirect flow uses platform webhooks instead.",
|
|
2830
|
+
resolution: "Wire Stripe via the standard Checkout / Customer Portal flow; Crossdeck reconciles via the platform webhook automatically. No SDK call needed.",
|
|
2831
|
+
retryable: false
|
|
2832
|
+
},
|
|
2833
|
+
{
|
|
2834
|
+
code: "missing_required_param",
|
|
2835
|
+
type: "invalid_request_error",
|
|
2836
|
+
description: "A required field is absent from the request body.",
|
|
2837
|
+
resolution: "Inspect the error.message \u2014 the missing field name is included verbatim. Refer to the SDK's TypeScript types for the canonical request shape.",
|
|
2838
|
+
retryable: false
|
|
2839
|
+
},
|
|
2840
|
+
{
|
|
2841
|
+
code: "invalid_param_value",
|
|
2842
|
+
type: "invalid_request_error",
|
|
2843
|
+
description: "A field is present but the value failed validation (wrong shape, wrong length, wrong enum value).",
|
|
2844
|
+
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
|
+
retryable: false
|
|
2846
|
+
}
|
|
2847
|
+
]);
|
|
2848
|
+
function getErrorCode(code) {
|
|
2849
|
+
return CROSSDECK_ERROR_CODES.find((e) => e.code === code);
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2642
2852
|
// src/_contract-verifiers.ts
|
|
2643
2853
|
function buildVerifierContext(opts) {
|
|
2644
2854
|
return {
|
|
@@ -3163,13 +3373,64 @@ var VERIFIER_CONTRACT_FAILED_PAYLOAD_SCHEMA_LOCK = {
|
|
|
3163
3373
|
);
|
|
3164
3374
|
}
|
|
3165
3375
|
};
|
|
3376
|
+
var BACKEND_WIRE_CODES = Object.freeze([
|
|
3377
|
+
"missing_api_key",
|
|
3378
|
+
"invalid_api_key",
|
|
3379
|
+
"key_revoked",
|
|
3380
|
+
"identity_token_invalid",
|
|
3381
|
+
"origin_not_allowed",
|
|
3382
|
+
"bundle_id_not_allowed",
|
|
3383
|
+
"package_name_not_allowed",
|
|
3384
|
+
"env_mismatch",
|
|
3385
|
+
"idempotency_key_in_use",
|
|
3386
|
+
"rate_limited",
|
|
3387
|
+
"internal_error",
|
|
3388
|
+
"google_not_supported",
|
|
3389
|
+
"stripe_not_supported",
|
|
3390
|
+
"missing_required_param",
|
|
3391
|
+
"invalid_param_value"
|
|
3392
|
+
]);
|
|
3393
|
+
var VERIFIER_SDK_ERROR_CODES_CATALOGUE = {
|
|
3394
|
+
contractId: "sdk-error-codes-catalogue",
|
|
3395
|
+
bootTest() {
|
|
3396
|
+
const t0 = nowMs();
|
|
3397
|
+
try {
|
|
3398
|
+
const missing = [];
|
|
3399
|
+
for (const code of BACKEND_WIRE_CODES) {
|
|
3400
|
+
const entry = getErrorCode(code);
|
|
3401
|
+
if (!entry || !entry.description || entry.description.trim().length === 0 || !entry.resolution || entry.resolution.trim().length === 0) {
|
|
3402
|
+
missing.push(code);
|
|
3403
|
+
}
|
|
3404
|
+
}
|
|
3405
|
+
if (missing.length > 0) {
|
|
3406
|
+
return fail(
|
|
3407
|
+
"sdk-error-codes-catalogue",
|
|
3408
|
+
`catalogue missing description+resolution for backend code(s): ${missing.join(", ")}`,
|
|
3409
|
+
nowMs() - t0
|
|
3410
|
+
);
|
|
3411
|
+
}
|
|
3412
|
+
return pass(
|
|
3413
|
+
"sdk-error-codes-catalogue",
|
|
3414
|
+
`all ${BACKEND_WIRE_CODES.length} backend wire codes carry description + resolution`,
|
|
3415
|
+
nowMs() - t0
|
|
3416
|
+
);
|
|
3417
|
+
} catch (err) {
|
|
3418
|
+
return fail(
|
|
3419
|
+
"sdk-error-codes-catalogue",
|
|
3420
|
+
`boot test threw: ${err.message?.slice(0, 80) ?? "unknown error"}`,
|
|
3421
|
+
nowMs() - t0
|
|
3422
|
+
);
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
};
|
|
3166
3426
|
var STATIC_VERIFIERS = Object.freeze([
|
|
3167
3427
|
VERIFIER_PER_USER_CACHE_ISOLATION,
|
|
3168
3428
|
VERIFIER_IDEMPOTENCY_KEY_DETERMINISTIC,
|
|
3169
3429
|
VERIFIER_ERROR_ENVELOPE_SHAPE,
|
|
3170
3430
|
VERIFIER_FLUSH_INTERVAL_PARITY,
|
|
3171
3431
|
VERIFIER_SUPER_PROPERTY_MERGE_PRECEDENCE,
|
|
3172
|
-
VERIFIER_CONTRACT_FAILED_PAYLOAD_SCHEMA_LOCK
|
|
3432
|
+
VERIFIER_CONTRACT_FAILED_PAYLOAD_SCHEMA_LOCK,
|
|
3433
|
+
VERIFIER_SDK_ERROR_CODES_CATALOGUE
|
|
3173
3434
|
]);
|
|
3174
3435
|
async function runBootSelfTest(verifiers, reporter, ctx) {
|
|
3175
3436
|
if (ctx.disableContractAssertions) {
|
|
@@ -3682,7 +3943,7 @@ var ErrorTracker = class {
|
|
|
3682
3943
|
}
|
|
3683
3944
|
return response;
|
|
3684
3945
|
} catch (err) {
|
|
3685
|
-
if (this.opts.isConsented() && !url.includes("api.cross-deck.com")) {
|
|
3946
|
+
if (this.opts.isConsented() && !url.includes("api.cross-deck.com") && !isClientNetworkNoise(url, err, w)) {
|
|
3686
3947
|
this.captureHttp({
|
|
3687
3948
|
url,
|
|
3688
3949
|
method,
|
|
@@ -4081,6 +4342,19 @@ function isSelfRequest(requestUrl, selfHostname) {
|
|
|
4081
4342
|
return false;
|
|
4082
4343
|
}
|
|
4083
4344
|
}
|
|
4345
|
+
function isClientNetworkNoise(requestUrl, err, w) {
|
|
4346
|
+
if (err instanceof Error && err.name === "AbortError") return true;
|
|
4347
|
+
const nav = w.navigator;
|
|
4348
|
+
if (nav && nav.onLine === false) return true;
|
|
4349
|
+
const pageHref = w.location?.href;
|
|
4350
|
+
const pageOrigin = w.location?.origin;
|
|
4351
|
+
if (!pageOrigin) return false;
|
|
4352
|
+
try {
|
|
4353
|
+
return new URL(requestUrl, pageHref ?? pageOrigin).origin === pageOrigin;
|
|
4354
|
+
} catch {
|
|
4355
|
+
return false;
|
|
4356
|
+
}
|
|
4357
|
+
}
|
|
4084
4358
|
|
|
4085
4359
|
// src/crossdeck.ts
|
|
4086
4360
|
var CrossdeckClient = class {
|
|
@@ -5321,213 +5595,9 @@ function installUnloadFlush(onUnload) {
|
|
|
5321
5595
|
};
|
|
5322
5596
|
}
|
|
5323
5597
|
|
|
5324
|
-
// src/error-codes.ts
|
|
5325
|
-
var CROSSDECK_ERROR_CODES = Object.freeze([
|
|
5326
|
-
// ----- Configuration -----
|
|
5327
|
-
{
|
|
5328
|
-
code: "invalid_public_key",
|
|
5329
|
-
type: "configuration_error",
|
|
5330
|
-
description: "The publishable key passed to Crossdeck.init() doesn't start with cd_pub_.",
|
|
5331
|
-
resolution: "Copy the key from your Crossdeck dashboard \u2192 API keys page.",
|
|
5332
|
-
retryable: false
|
|
5333
|
-
},
|
|
5334
|
-
{
|
|
5335
|
-
code: "missing_app_id",
|
|
5336
|
-
type: "configuration_error",
|
|
5337
|
-
description: "Crossdeck.init() was called without an appId.",
|
|
5338
|
-
resolution: "Add appId to your init options \u2014 find it in the dashboard's Apps page.",
|
|
5339
|
-
retryable: false
|
|
5340
|
-
},
|
|
5341
|
-
{
|
|
5342
|
-
code: "invalid_environment",
|
|
5343
|
-
type: "configuration_error",
|
|
5344
|
-
description: "Crossdeck.init() requires environment: 'production' | 'sandbox'.",
|
|
5345
|
-
resolution: 'Pass the literal string "production" or "sandbox" \u2014 no other values are accepted.',
|
|
5346
|
-
retryable: false
|
|
5347
|
-
},
|
|
5348
|
-
{
|
|
5349
|
-
code: "environment_mismatch",
|
|
5350
|
-
type: "configuration_error",
|
|
5351
|
-
description: "The publishable key's env prefix doesn't match the declared environment option.",
|
|
5352
|
-
resolution: "Either change `environment` to match the key prefix (cd_pub_test_ \u2194 sandbox, cd_pub_live_ \u2194 production), or swap the key for one minted in the right env.",
|
|
5353
|
-
retryable: false
|
|
5354
|
-
},
|
|
5355
|
-
{
|
|
5356
|
-
code: "not_initialized",
|
|
5357
|
-
type: "configuration_error",
|
|
5358
|
-
description: "An SDK method was called before Crossdeck.init().",
|
|
5359
|
-
resolution: "Call Crossdeck.init({ appId, publicKey, environment }) once at app startup before any other method.",
|
|
5360
|
-
retryable: false
|
|
5361
|
-
},
|
|
5362
|
-
// ----- Identify / track / purchase argument validation -----
|
|
5363
|
-
{
|
|
5364
|
-
code: "missing_user_id",
|
|
5365
|
-
type: "invalid_request_error",
|
|
5366
|
-
description: "identify() was called with an empty userId.",
|
|
5367
|
-
resolution: "Pass a stable, non-empty user identifier from your auth layer \u2014 never a hardcoded placeholder.",
|
|
5368
|
-
retryable: false
|
|
5369
|
-
},
|
|
5370
|
-
{
|
|
5371
|
-
code: "missing_event_name",
|
|
5372
|
-
type: "invalid_request_error",
|
|
5373
|
-
description: "track() was called without an event name.",
|
|
5374
|
-
resolution: "Pass a non-empty string as the first argument.",
|
|
5375
|
-
retryable: false
|
|
5376
|
-
},
|
|
5377
|
-
{
|
|
5378
|
-
code: "missing_group_type",
|
|
5379
|
-
type: "invalid_request_error",
|
|
5380
|
-
description: "group() was called without a group type.",
|
|
5381
|
-
resolution: 'Pass a non-empty type (e.g. "org", "team") as the first argument.',
|
|
5382
|
-
retryable: false
|
|
5383
|
-
},
|
|
5384
|
-
{
|
|
5385
|
-
code: "missing_signed_transaction_info",
|
|
5386
|
-
type: "invalid_request_error",
|
|
5387
|
-
description: "syncPurchases() was called without StoreKit 2 signed transaction info.",
|
|
5388
|
-
resolution: "Pass the JWS string from Transaction.currentEntitlements / Transaction.updates.",
|
|
5389
|
-
retryable: false
|
|
5390
|
-
},
|
|
5391
|
-
// ----- Network / transport -----
|
|
5392
|
-
{
|
|
5393
|
-
code: "fetch_failed",
|
|
5394
|
-
type: "network_error",
|
|
5395
|
-
description: "The underlying fetch() call failed (typically a network outage or DNS issue).",
|
|
5396
|
-
resolution: "Check the user's network. The SDK will retry automatically with exponential backoff.",
|
|
5397
|
-
retryable: true
|
|
5398
|
-
},
|
|
5399
|
-
{
|
|
5400
|
-
code: "request_timeout",
|
|
5401
|
-
type: "network_error",
|
|
5402
|
-
description: "A request was aborted after the configured timeoutMs (default 15s).",
|
|
5403
|
-
resolution: "Check the user's connection. Increase timeoutMs in init options if the user is on a known-slow network.",
|
|
5404
|
-
retryable: true
|
|
5405
|
-
},
|
|
5406
|
-
{
|
|
5407
|
-
code: "invalid_json_response",
|
|
5408
|
-
type: "internal_error",
|
|
5409
|
-
description: "The server returned a 2xx with an unparseable body.",
|
|
5410
|
-
resolution: "Likely a transient backend bug. Retry; if it persists, contact support with the requestId.",
|
|
5411
|
-
retryable: true
|
|
5412
|
-
},
|
|
5413
|
-
// ----- Backend-emitted codes (v1.4.0 Phase 6.2 backfill) -----
|
|
5414
|
-
// Mirror of backend/src/api/v1-errors.ts ApiErrorCode. A developer
|
|
5415
|
-
// hitting any of these on the wire can look them up via
|
|
5416
|
-
// getErrorCode(code) for a canonical remediation step instead of
|
|
5417
|
-
// hunting through Slack history.
|
|
5418
|
-
{
|
|
5419
|
-
code: "missing_api_key",
|
|
5420
|
-
type: "authentication_error",
|
|
5421
|
-
description: "No Authorization header (or Crossdeck-Api-Key header) on the request.",
|
|
5422
|
-
resolution: "Make sure Crossdeck.init({ publicKey }) was called with a cd_pub_\u2026 key before the request fired. Re-check your env-vars in CI / Docker if the key is empty at runtime.",
|
|
5423
|
-
retryable: false
|
|
5424
|
-
},
|
|
5425
|
-
{
|
|
5426
|
-
code: "invalid_api_key",
|
|
5427
|
-
type: "authentication_error",
|
|
5428
|
-
description: "The API key is malformed, unknown, or doesn't resolve to a project.",
|
|
5429
|
-
resolution: "Copy the key from your Crossdeck dashboard \u2192 API keys. Confirm prefix is cd_pub_test_ / cd_pub_live_ for client SDKs and cd_sk_test_ / cd_sk_live_ for the Node server SDK.",
|
|
5430
|
-
retryable: false
|
|
5431
|
-
},
|
|
5432
|
-
{
|
|
5433
|
-
code: "key_revoked",
|
|
5434
|
-
type: "authentication_error",
|
|
5435
|
-
description: "The API key was revoked in the Crossdeck dashboard.",
|
|
5436
|
-
resolution: "Mint a fresh key in the dashboard \u2192 API keys \u2192 Create new, swap it in, and redeploy. The revoked key cannot be reactivated.",
|
|
5437
|
-
retryable: false
|
|
5438
|
-
},
|
|
5439
|
-
{
|
|
5440
|
-
code: "identity_token_invalid",
|
|
5441
|
-
type: "authentication_error",
|
|
5442
|
-
description: "The Firebase / Apple / Google ID token supplied with the request didn't verify against the dashboard's configured signers.",
|
|
5443
|
-
resolution: "Refresh the token client-side (Firebase auth.currentUser.getIdToken(true)) and retry. If the failure persists, confirm the signer is registered under dashboard \u2192 Authentication \u2192 Identity sources.",
|
|
5444
|
-
retryable: true
|
|
5445
|
-
},
|
|
5446
|
-
{
|
|
5447
|
-
code: "origin_not_allowed",
|
|
5448
|
-
type: "permission_error",
|
|
5449
|
-
description: "The Origin header isn't in the project's Allowed origins list.",
|
|
5450
|
-
resolution: "Add the origin (e.g. https://app.example.com) under dashboard \u2192 Settings \u2192 Allowed origins. Wildcards like https://*.example.com are supported.",
|
|
5451
|
-
retryable: false
|
|
5452
|
-
},
|
|
5453
|
-
{
|
|
5454
|
-
code: "bundle_id_not_allowed",
|
|
5455
|
-
type: "permission_error",
|
|
5456
|
-
description: "The iOS bundle ID sent via X-Crossdeck-Bundle-Id isn't registered under this app's Apple identity lock.",
|
|
5457
|
-
resolution: "Add the bundle ID under dashboard \u2192 Apps \u2192 <your app> \u2192 iOS bundle IDs.",
|
|
5458
|
-
retryable: false
|
|
5459
|
-
},
|
|
5460
|
-
{
|
|
5461
|
-
code: "package_name_not_allowed",
|
|
5462
|
-
type: "permission_error",
|
|
5463
|
-
description: "The Android package name sent via X-Crossdeck-Package-Name isn't registered under this app's Android identity lock.",
|
|
5464
|
-
resolution: "Add the package name under dashboard \u2192 Apps \u2192 <your app> \u2192 Android package names.",
|
|
5465
|
-
retryable: false
|
|
5466
|
-
},
|
|
5467
|
-
{
|
|
5468
|
-
code: "env_mismatch",
|
|
5469
|
-
type: "permission_error",
|
|
5470
|
-
description: "The request env (inferred from key prefix) doesn't match the resolved app's configured env.",
|
|
5471
|
-
resolution: "Use a cd_pub_live_ / cd_sk_live_ key with a production app, cd_pub_test_ / cd_sk_test_ with a sandbox app. The two cannot cross.",
|
|
5472
|
-
retryable: false
|
|
5473
|
-
},
|
|
5474
|
-
{
|
|
5475
|
-
code: "idempotency_key_in_use",
|
|
5476
|
-
type: "invalid_request_error",
|
|
5477
|
-
description: "An Idempotency-Key was reused for a request with a different body (Stripe-grade contract).",
|
|
5478
|
-
resolution: "Generate a fresh key for a different transaction, or reuse the key only with the EXACT same body. The v1.4.0 SDKs derive keys deterministically from the body so this should never fire on SDK-managed calls.",
|
|
5479
|
-
retryable: false
|
|
5480
|
-
},
|
|
5481
|
-
{
|
|
5482
|
-
code: "rate_limited",
|
|
5483
|
-
type: "rate_limit_error",
|
|
5484
|
-
description: "Request rate exceeded the project's per-second cap.",
|
|
5485
|
-
resolution: "Honour the Retry-After header \u2014 the SDK does this automatically on managed retries. If you're hitting it from a custom code path, throttle to <100 req/s/key.",
|
|
5486
|
-
retryable: true
|
|
5487
|
-
},
|
|
5488
|
-
{
|
|
5489
|
-
code: "internal_error",
|
|
5490
|
-
type: "internal_error",
|
|
5491
|
-
description: "Server-side issue. Safe to retry with backoff.",
|
|
5492
|
-
resolution: "The SDK retries automatically. If your code paths through to this error, contact support with the requestId from the response envelope.",
|
|
5493
|
-
retryable: true
|
|
5494
|
-
},
|
|
5495
|
-
{
|
|
5496
|
-
code: "google_not_supported",
|
|
5497
|
-
type: "invalid_request_error",
|
|
5498
|
-
description: "POST /purchases/sync with rail=google is gated until the Play Developer API reconciliation worker ships.",
|
|
5499
|
-
resolution: "Until v1.5+, Google Play purchases verify via Real-time Developer Notifications. The SDK auto-track path handles this transparently for Android consumers.",
|
|
5500
|
-
retryable: false
|
|
5501
|
-
},
|
|
5502
|
-
{
|
|
5503
|
-
code: "stripe_not_supported",
|
|
5504
|
-
type: "invalid_request_error",
|
|
5505
|
-
description: "POST /purchases/sync with rail=stripe is unsupported \u2014 Stripe Checkout's redirect flow uses platform webhooks instead.",
|
|
5506
|
-
resolution: "Wire Stripe via the standard Checkout / Customer Portal flow; Crossdeck reconciles via the platform webhook automatically. No SDK call needed.",
|
|
5507
|
-
retryable: false
|
|
5508
|
-
},
|
|
5509
|
-
{
|
|
5510
|
-
code: "missing_required_param",
|
|
5511
|
-
type: "invalid_request_error",
|
|
5512
|
-
description: "A required field is absent from the request body.",
|
|
5513
|
-
resolution: "Inspect the error.message \u2014 the missing field name is included verbatim. Refer to the SDK's TypeScript types for the canonical request shape.",
|
|
5514
|
-
retryable: false
|
|
5515
|
-
},
|
|
5516
|
-
{
|
|
5517
|
-
code: "invalid_param_value",
|
|
5518
|
-
type: "invalid_request_error",
|
|
5519
|
-
description: "A field is present but the value failed validation (wrong shape, wrong length, wrong enum value).",
|
|
5520
|
-
resolution: "Read error.message for the specific field + reason. SDK-managed call sites should never emit this \u2014 file a bug if you do.",
|
|
5521
|
-
retryable: false
|
|
5522
|
-
}
|
|
5523
|
-
]);
|
|
5524
|
-
function getErrorCode(code) {
|
|
5525
|
-
return CROSSDECK_ERROR_CODES.find((e) => e.code === code);
|
|
5526
|
-
}
|
|
5527
|
-
|
|
5528
5598
|
// src/_contracts-bundled.ts
|
|
5529
|
-
var BUNDLED_IN = "@cross-deck/web@1.6.
|
|
5530
|
-
var SDK_VERSION2 = "1.6.
|
|
5599
|
+
var BUNDLED_IN = "@cross-deck/web@1.6.3";
|
|
5600
|
+
var SDK_VERSION2 = "1.6.3";
|
|
5531
5601
|
var BUNDLED_CONTRACTS = Object.freeze([
|
|
5532
5602
|
{
|
|
5533
5603
|
"id": "contract-failed-payload-schema-lock",
|
|
@@ -5649,7 +5719,8 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
5649
5719
|
"legal/security/index.html#diagnostic",
|
|
5650
5720
|
"legal/sdk-data/index.html#b-diagnostic"
|
|
5651
5721
|
],
|
|
5652
|
-
"bundledIn": "@cross-deck/web@1.6.
|
|
5722
|
+
"bundledIn": "@cross-deck/web@1.6.3",
|
|
5723
|
+
"runtimeVerified": true
|
|
5653
5724
|
},
|
|
5654
5725
|
{
|
|
5655
5726
|
"id": "error-envelope-shape",
|
|
@@ -5688,7 +5759,8 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
5688
5759
|
],
|
|
5689
5760
|
"registeredAt": "2026-05-26",
|
|
5690
5761
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 8 (codifies existing contract)",
|
|
5691
|
-
"bundledIn": "@cross-deck/web@1.6.
|
|
5762
|
+
"bundledIn": "@cross-deck/web@1.6.3",
|
|
5763
|
+
"runtimeVerified": true
|
|
5692
5764
|
},
|
|
5693
5765
|
{
|
|
5694
5766
|
"id": "flush-interval-parity",
|
|
@@ -5733,7 +5805,8 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
5733
5805
|
],
|
|
5734
5806
|
"registeredAt": "2026-05-26",
|
|
5735
5807
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.3",
|
|
5736
|
-
"bundledIn": "@cross-deck/web@1.6.
|
|
5808
|
+
"bundledIn": "@cross-deck/web@1.6.3",
|
|
5809
|
+
"runtimeVerified": true
|
|
5737
5810
|
},
|
|
5738
5811
|
{
|
|
5739
5812
|
"id": "idempotency-key-deterministic",
|
|
@@ -5838,7 +5911,8 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
5838
5911
|
],
|
|
5839
5912
|
"registeredAt": "2026-05-26",
|
|
5840
5913
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 2.2.a + 2.2.b + 2.2.c",
|
|
5841
|
-
"bundledIn": "@cross-deck/web@1.6.
|
|
5914
|
+
"bundledIn": "@cross-deck/web@1.6.3",
|
|
5915
|
+
"runtimeVerified": true
|
|
5842
5916
|
},
|
|
5843
5917
|
{
|
|
5844
5918
|
"id": "init-reentry-drains-prior-queue",
|
|
@@ -5865,7 +5939,8 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
5865
5939
|
],
|
|
5866
5940
|
"registeredAt": "2026-05-26",
|
|
5867
5941
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 5.5",
|
|
5868
|
-
"bundledIn": "@cross-deck/web@1.6.
|
|
5942
|
+
"bundledIn": "@cross-deck/web@1.6.3",
|
|
5943
|
+
"runtimeVerified": false
|
|
5869
5944
|
},
|
|
5870
5945
|
{
|
|
5871
5946
|
"id": "per-user-cache-isolation",
|
|
@@ -5944,7 +6019,8 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
5944
6019
|
],
|
|
5945
6020
|
"registeredAt": "2026-05-26",
|
|
5946
6021
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 1.3 (web/RN) + dogfood-gap fix (swift + android)",
|
|
5947
|
-
"bundledIn": "@cross-deck/web@1.6.
|
|
6022
|
+
"bundledIn": "@cross-deck/web@1.6.3",
|
|
6023
|
+
"runtimeVerified": true
|
|
5948
6024
|
},
|
|
5949
6025
|
{
|
|
5950
6026
|
"id": "sdk-error-codes-catalogue",
|
|
@@ -5958,9 +6034,14 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
5958
6034
|
"codeRef": [
|
|
5959
6035
|
"sdks/web/src/error-codes.ts",
|
|
5960
6036
|
"sdks/node/src/error-codes.ts",
|
|
6037
|
+
"sdks/web/src/_contract-verifiers.ts",
|
|
5961
6038
|
"backend/src/api/v1-errors.ts"
|
|
5962
6039
|
],
|
|
5963
6040
|
"testRef": [
|
|
6041
|
+
{
|
|
6042
|
+
"file": "sdks/web/tests/contract-verifiers.test.ts",
|
|
6043
|
+
"name": "sdk-error-codes-catalogue covers every backend wire code with remediation"
|
|
6044
|
+
},
|
|
5964
6045
|
{
|
|
5965
6046
|
"file": "sdks/web/tests/error-codes-backfill.test.ts",
|
|
5966
6047
|
"name": "includes backend code"
|
|
@@ -5984,7 +6065,50 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
5984
6065
|
],
|
|
5985
6066
|
"registeredAt": "2026-05-26",
|
|
5986
6067
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 6.2",
|
|
5987
|
-
"bundledIn": "@cross-deck/web@1.6.
|
|
6068
|
+
"bundledIn": "@cross-deck/web@1.6.3",
|
|
6069
|
+
"runtimeVerified": true
|
|
6070
|
+
},
|
|
6071
|
+
{
|
|
6072
|
+
"id": "super-property-merge-precedence",
|
|
6073
|
+
"pillar": "analytics",
|
|
6074
|
+
"status": "enforced",
|
|
6075
|
+
"claim": "Every Crossdeck SDK merges event properties with the precedence device < super < caller (caller-supplied values win over registered super-properties, which win over auto-attached device info). Pre-v1.4.0 Swift had it INVERTED (super < device < caller \u2014 device clobbered super), so a `register('plan', 'pro')` super-property was silently overridden by auto-attached device fields whenever keys collided. Cross-SDK funnel queries on super-property keys returned different answers per platform.",
|
|
6076
|
+
"appliesTo": [
|
|
6077
|
+
"web",
|
|
6078
|
+
"swift"
|
|
6079
|
+
],
|
|
6080
|
+
"codeRef": [
|
|
6081
|
+
"sdks/web/src/super-properties.ts",
|
|
6082
|
+
"sdks/web/src/_contract-verifiers.ts",
|
|
6083
|
+
"sdks/swift/Sources/Crossdeck/EventPropertyMerge.swift",
|
|
6084
|
+
"sdks/swift/Sources/Crossdeck/Crossdeck.swift"
|
|
6085
|
+
],
|
|
6086
|
+
"testRef": [
|
|
6087
|
+
{
|
|
6088
|
+
"file": "sdks/web/tests/contract-verifiers.test.ts",
|
|
6089
|
+
"name": "super-property-merge-precedence verifies caller > super > device"
|
|
6090
|
+
},
|
|
6091
|
+
{
|
|
6092
|
+
"file": "sdks/swift/Tests/CrossdeckTests/EventPropertyMergeTests.swift",
|
|
6093
|
+
"name": "test_super_overrides_device"
|
|
6094
|
+
},
|
|
6095
|
+
{
|
|
6096
|
+
"file": "sdks/swift/Tests/CrossdeckTests/EventPropertyMergeTests.swift",
|
|
6097
|
+
"name": "test_caller_overrides_super"
|
|
6098
|
+
},
|
|
6099
|
+
{
|
|
6100
|
+
"file": "sdks/swift/Tests/CrossdeckTests/EventPropertyMergeTests.swift",
|
|
6101
|
+
"name": "test_full_precedence_chain"
|
|
6102
|
+
},
|
|
6103
|
+
{
|
|
6104
|
+
"file": "sdks/swift/Tests/CrossdeckTests/EventPropertyMergeTests.swift",
|
|
6105
|
+
"name": "test_matchesWebNodeRNPrecedence"
|
|
6106
|
+
}
|
|
6107
|
+
],
|
|
6108
|
+
"registeredAt": "2026-05-26",
|
|
6109
|
+
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.2",
|
|
6110
|
+
"bundledIn": "@cross-deck/web@1.6.3",
|
|
6111
|
+
"runtimeVerified": true
|
|
5988
6112
|
},
|
|
5989
6113
|
{
|
|
5990
6114
|
"id": "sync-purchases-funnel-parity",
|
|
@@ -6017,7 +6141,8 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
6017
6141
|
],
|
|
6018
6142
|
"registeredAt": "2026-05-26",
|
|
6019
6143
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.5",
|
|
6020
|
-
"bundledIn": "@cross-deck/web@1.6.
|
|
6144
|
+
"bundledIn": "@cross-deck/web@1.6.3",
|
|
6145
|
+
"runtimeVerified": false
|
|
6021
6146
|
}
|
|
6022
6147
|
]);
|
|
6023
6148
|
|