@cross-deck/react-native 1.5.1 → 1.5.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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/_version.ts","../src/http.ts","../src/identity.ts","../src/hash.ts","../src/entitlement-cache.ts","../src/idempotency-key.ts","../src/retry-policy.ts","../src/event-queue.ts","../src/event-storage.ts","../src/storage.ts","../src/device-info.ts","../src/debug.ts","../src/event-validation.ts","../src/super-properties.ts","../src/consent.ts","../src/breadcrumbs.ts","../src/_diagnostic-telemetry.ts","../src/stack-parser.ts","../src/error-capture.ts","../src/crossdeck.ts","../src/_contracts-bundled.ts","../src/contracts.ts"],"sourcesContent":["/**\n * @cross-deck/react-native — public entry point.\n *\n * The default export is a singleton `Crossdeck` instance. Most apps\n * want exactly one client; instantiate `CrossdeckClient` directly if\n * you need isolated instances (e.g. one per tenant in a multi-tenant\n * RN shell).\n */\n\nexport { Crossdeck, CrossdeckClient } from \"./crossdeck\";\nexport { CrossdeckError, parseRetryAfterHeader } from \"./errors\";\nexport { MemoryStorage, AsyncStorageAdapter } from \"./storage\";\nexport { SDK_NAME, SDK_VERSION, DEFAULT_BASE_URL } from \"./http\";\nexport { scrubPii, scrubPiiFromProperties } from \"./consent\";\nexport { CrossdeckContracts } from \"./contracts\";\nexport type {\n Contract,\n ContractPillar,\n ContractStatus,\n ContractAppliesTo,\n ContractTestRef,\n ContractFailureInput,\n} from \"./contracts\";\n\nexport type {\n CrossdeckOptions,\n IdentifyOptions,\n GroupTraits,\n EventProperties,\n KeyValueStorage,\n PublicEntitlement,\n EntitlementsListResponse,\n AliasResult,\n PurchaseResult,\n HeartbeatResponse,\n Diagnostics,\n Environment,\n Platform,\n AuditRail,\n} from \"./types\";\nexport type { ConsentState } from \"./consent\";\nexport type { DeviceInfo } from \"./device-info\";\nexport type { CrossdeckErrorType, CrossdeckErrorPayload } from \"./errors\";\nexport type { Breadcrumb, BreadcrumbCategory, BreadcrumbLevel } from \"./breadcrumbs\";\nexport type { CapturedError, ErrorLevel } from \"./error-capture\";\nexport type { StackFrame } from \"./stack-parser\";\nexport type { EntitlementsListener } from \"./entitlement-cache\";\n","/**\n * Stripe-style error wrapper for @cross-deck/react-native.\n *\n * Wire shape is shared with @cross-deck/web and @cross-deck/node so\n * cross-platform catch blocks read identically:\n *\n * try {\n * await Crossdeck.identify(\"user_847\");\n * } catch (err) {\n * if (err instanceof CrossdeckError && err.code === \"invalid_api_key\") { … }\n * }\n *\n * Retry-After parsing is identical to web — the event queue's retry\n * policy honours the server-supplied delay when it exceeds the\n * computed backoff (a 429 `Retry-After: 120` wins over a base+jitter\n * of 800ms, capped at 24h as a sanity guard).\n */\n\nexport type CrossdeckErrorType =\n | \"authentication_error\"\n | \"permission_error\"\n | \"invalid_request_error\"\n | \"rate_limit_error\"\n | \"internal_error\"\n | \"network_error\"\n | \"configuration_error\";\n\nexport interface CrossdeckErrorPayload {\n type: CrossdeckErrorType;\n code: string;\n message: string;\n /** Server-issued request ID. Echoed in support tickets. */\n requestId?: string;\n /** HTTP status code if the error came from an API response. */\n status?: number;\n /**\n * Server-suggested wait (in milliseconds) before retrying. Populated\n * from the `Retry-After` response header on 429 / 503. Consumers MUST\n * honour this — the server is telling you the safe rate.\n */\n retryAfterMs?: number;\n}\n\nexport class CrossdeckError extends Error {\n public readonly type: CrossdeckErrorType;\n public readonly code: string;\n public readonly requestId?: string;\n public readonly status?: number;\n public readonly retryAfterMs?: number;\n\n constructor(payload: CrossdeckErrorPayload) {\n super(payload.message);\n this.name = \"CrossdeckError\";\n this.type = payload.type;\n this.code = payload.code;\n this.requestId = payload.requestId;\n this.status = payload.status;\n this.retryAfterMs = payload.retryAfterMs;\n // Restore prototype chain — needed when downlevelled past ES2015\n // (Hermes pre-0.74, Babel transpiled targets).\n Object.setPrototypeOf(this, CrossdeckError.prototype);\n }\n}\n\n/**\n * Build a CrossdeckError from a non-OK fetch Response. Reads the\n * Stripe-style envelope `{ error: { type, code, message, request_id } }`.\n * Falls back to a generic shape if the body isn't valid JSON.\n */\nexport async function crossdeckErrorFromResponse(\n res: Response,\n): Promise<CrossdeckError> {\n const requestId = res.headers.get(\"x-request-id\") ?? undefined;\n const retryAfterMs = parseRetryAfterHeader(res.headers.get(\"retry-after\"));\n let body: unknown;\n try {\n body = await res.json();\n } catch {\n body = null;\n }\n const envelope = (body as {\n error?: Partial<CrossdeckErrorPayload> & { request_id?: string };\n })?.error;\n if (envelope && typeof envelope.type === \"string\" && typeof envelope.code === \"string\") {\n return new CrossdeckError({\n type: envelope.type as CrossdeckErrorType,\n code: envelope.code,\n message: envelope.message ?? `HTTP ${res.status}`,\n requestId: envelope.request_id ?? requestId,\n status: res.status,\n retryAfterMs,\n });\n }\n return new CrossdeckError({\n type: typeMapForStatus(res.status),\n code: `http_${res.status}`,\n message: `HTTP ${res.status} ${res.statusText || \"\"}`.trim(),\n requestId,\n status: res.status,\n retryAfterMs,\n });\n}\n\n/**\n * Parse the `Retry-After` header per RFC 7231 §7.1.3. Two forms:\n * - delta-seconds: \"Retry-After: 120\" → 120_000 ms\n * - HTTP-date: \"Retry-After: Wed, 21 Oct 2026 07:28:00 GMT\"\n * → max(0, target - now) ms\n *\n * Returns undefined when the header is missing, malformed, or in the past.\n */\nexport function parseRetryAfterHeader(value: string | null): number | undefined {\n if (!value) return undefined;\n const trimmed = value.trim();\n if (!trimmed) return undefined;\n if (/^\\d+(\\.\\d+)?$/.test(trimmed)) {\n const secs = Number(trimmed);\n if (!Number.isFinite(secs) || secs < 0) return undefined;\n return Math.round(secs * 1000);\n }\n if (!/[a-zA-Z,/:]/.test(trimmed)) return undefined;\n const target = Date.parse(trimmed);\n if (!Number.isFinite(target)) return undefined;\n const delta = target - Date.now();\n return delta > 0 ? delta : 0;\n}\n\nfunction typeMapForStatus(status: number): CrossdeckErrorType {\n if (status === 401) return \"authentication_error\";\n if (status === 403) return \"permission_error\";\n if (status === 429) return \"rate_limit_error\";\n if (status >= 400 && status < 500) return \"invalid_request_error\";\n return \"internal_error\";\n}\n","/**\n * SDK version constant — generated by `scripts/sync-sdk-versions.mjs`.\n *\n * Single source of truth: the `version` field in this package's\n * package.json. The sync script writes this file so that\n * `SDK_VERSION` is a plain TypeScript literal at runtime — no\n * runtime JSON-import gotcha (Node ESM requires\n * `with { type: \"json\" }` to import JSON as ESM, and the published\n * dist file would otherwise fail to load).\n *\n * Drift protection: `node scripts/sync-sdk-versions.mjs --check` (the\n * CI gate) flags this file when it falls out of sync with package.json.\n * Bumping `package.json` without re-running the sync script fails CI.\n *\n * Do NOT edit by hand — `node scripts/sync-sdk-versions.mjs`.\n */\nexport const SDK_VERSION = \"1.5.1\";\nexport const SDK_NAME = \"@cross-deck/react-native\";\n","/**\n * HTTP transport for the SDK. Single fetch wrapper used by every\n * endpoint call. Adds the Bearer token and SDK version header, parses\n * responses, normalises errors to CrossdeckError.\n *\n * Uses RN's native `fetch` (Hermes 0.74+ ships it; older RN shims it\n * via the same Polyfill module React Native registers in\n * InitializeCore). No axios, no fetch-shim transitive deps.\n */\n\nimport { CrossdeckError, crossdeckErrorFromResponse } from \"./errors\";\nimport { SDK_NAME, SDK_VERSION } from \"./_version\";\n\nexport { SDK_NAME, SDK_VERSION };\n\nexport const DEFAULT_BASE_URL = \"https://api.cross-deck.com/v1\";\nexport const DEFAULT_TIMEOUT_MS = 15_000;\n\nexport interface HttpClientConfig {\n publicKey: string;\n baseUrl: string;\n sdkVersion: string;\n /**\n * iOS Bundle ID (e.g. `com.acme.app`). Sent as\n * `X-Crossdeck-Bundle-Id` on every request when the runtime is\n * iOS, so the backend's `isBundleIdAllowed()` can enforce the\n * identity lock against the bundleId stored on the iOS app key.\n *\n * Bank-grade contract: the backend rejects iOS requests without\n * a matching bundle ID with 403 / bundle_id_not_allowed.\n *\n * Source from the consumer's native shell. With `expo-application`:\n * `import { applicationId } from \"expo-application\";`\n * With `react-native-device-info`:\n * `import DeviceInfo from \"react-native-device-info\";\n * DeviceInfo.getBundleId()`\n * Or from the bare RN bridge as `NativeModules.RNCConfig.bundleId`\n * when you ship the platform identifier yourself.\n */\n bundleId?: string;\n /**\n * Android package name / applicationId (e.g. `com.acme.app`). Sent\n * as `X-Crossdeck-Package-Name` on every request when the runtime\n * is Android. Same enforcement shape as `bundleId`: the backend\n * rejects Android requests without a matching package name with\n * 403 / package_name_not_allowed.\n *\n * On bare RN, source from\n * `NativeModules.PlatformConstants.packageName` or\n * `expo-application.applicationId`.\n */\n packageName?: string;\n /**\n * Default request timeout in ms. Per-call `options.timeoutMs`\n * overrides. Caller's `options.timeoutMs: 0` disables the timeout\n * entirely (useful for tests that intentionally hang).\n *\n * Stripe-grade default: 15s. Long enough that a slow-3G mobile\n * keeps the request alive; short enough that a captive portal or\n * a hung connection doesn't sit forever. Without this, fetch()\n * inherits the runtime's default (which on Hermes can be 5+\n * minutes) and a single bad network can lock up the entire event\n * queue.\n */\n timeoutMs?: number;\n}\n\nexport interface HttpRequestOptions {\n body?: unknown;\n query?: Record<string, string | undefined>;\n /**\n * Per-request timeout override (ms). Defaults to the client's\n * `timeoutMs` (15s). Pass 0 to disable the timeout entirely —\n * only sensible for tests.\n */\n timeoutMs?: number;\n /**\n * Stripe-style idempotency key. When set, the SDK adds\n * `Idempotency-Key: <value>` to the request. Reuses the SAME key\n * across retries of the SAME logical operation so the server can\n * short-circuit duplicate work without per-event dedup.\n *\n * The SDK supplies this for every batch flush — see `event-queue.ts`.\n */\n idempotencyKey?: string;\n}\n\nexport class HttpClient {\n constructor(private readonly config: HttpClientConfig) {}\n\n /**\n * Issue a request. `path` is relative to the configured baseUrl\n * (\"/entitlements\", \"/identity/alias\", etc.).\n *\n * Throws CrossdeckError on:\n * - Network failure (`type: \"network_error\"`)\n * - Non-2xx response (typed from the body envelope)\n * - JSON parse failure on a 2xx (treated as `internal_error`)\n */\n async request<T>(\n method: \"GET\" | \"POST\",\n path: string,\n options: HttpRequestOptions = {},\n ): Promise<T> {\n const url = this.buildUrl(path, options.query);\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.config.publicKey}`,\n \"Crossdeck-Sdk-Version\": `${SDK_NAME}@${this.config.sdkVersion}`,\n Accept: \"application/json\",\n };\n // Per-platform identity claim — the backend's isIdentityAllowed()\n // enforces these against bundleId / packageName stored on the\n // app key. The caller passes the right identifier at SDK init\n // time (Platform.OS determines which is meaningful); we send\n // BOTH when supplied so a single SDK instance dogfooded across\n // bare iOS + bare Android picks up the right header per\n // platform without the SDK doing runtime detection.\n if (this.config.bundleId) {\n headers[\"X-Crossdeck-Bundle-Id\"] = this.config.bundleId;\n }\n if (this.config.packageName) {\n headers[\"X-Crossdeck-Package-Name\"] = this.config.packageName;\n }\n if (options.idempotencyKey) {\n // Stripe pattern: same key on retries → server can\n // short-circuit duplicate work without inspecting the body.\n headers[\"Idempotency-Key\"] = options.idempotencyKey;\n }\n // Body is always a JSON-serialised string when present. We avoid\n // the BodyInit DOM type so the SDK doesn't need lib.dom in\n // tsconfig — RN's fetch accepts string bodies in every supported\n // engine (Hermes 0.74+, JSC, Node test runtimes).\n let bodyInit: string | undefined;\n if (options.body !== undefined) {\n headers[\"Content-Type\"] = \"application/json\";\n bodyInit = JSON.stringify(options.body);\n }\n\n // ----- Abort timeout -----\n // Wire up an AbortController so a stuck connection (captive\n // portal, satellite link, DNS hang) doesn't lock the queue\n // forever. Per-call `timeoutMs: 0` disables, otherwise fall back\n // to client default (15s).\n const effectiveTimeout =\n options.timeoutMs ?? this.config.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const controller =\n typeof AbortController !== \"undefined\" && effectiveTimeout > 0\n ? new AbortController()\n : null;\n let timeoutHandle: ReturnType<typeof setTimeout> | null = null;\n if (controller && effectiveTimeout > 0) {\n timeoutHandle = setTimeout(() => controller.abort(), effectiveTimeout);\n }\n\n let response: Response;\n try {\n response = await fetch(url, {\n method,\n headers,\n body: bodyInit,\n signal: controller?.signal,\n });\n } catch (err) {\n const aborted = controller?.signal?.aborted === true;\n throw new CrossdeckError({\n type: \"network_error\",\n code: aborted ? \"request_timeout\" : \"fetch_failed\",\n message: aborted\n ? `Request to ${path} aborted after ${effectiveTimeout}ms`\n : err instanceof Error\n ? err.message\n : \"fetch failed\",\n });\n } finally {\n if (timeoutHandle !== null) clearTimeout(timeoutHandle);\n }\n\n if (!response.ok) {\n throw await crossdeckErrorFromResponse(response);\n }\n\n // 204 No Content — return undefined cast as T (callers that\n // don't expect a body shouldn't read it).\n if (response.status === 204) return undefined as T;\n\n try {\n return (await response.json()) as T;\n } catch {\n throw new CrossdeckError({\n type: \"internal_error\",\n code: \"invalid_json_response\",\n message: \"Server returned a 2xx with an unparseable body.\",\n requestId: response.headers.get(\"x-request-id\") ?? undefined,\n status: response.status,\n });\n }\n }\n\n /** Exposed for the error-capture self-skip wiring. */\n get baseUrl(): string {\n return this.config.baseUrl;\n }\n\n private buildUrl(path: string, query?: Record<string, string | undefined>): string {\n const base = this.config.baseUrl.replace(/\\/+$/, \"\");\n const cleanPath = path.startsWith(\"/\") ? path : `/${path}`;\n let url = base + cleanPath;\n if (query) {\n const params = new URLSearchParams();\n for (const [k, v] of Object.entries(query)) {\n if (typeof v === \"string\" && v.length > 0) params.append(k, v);\n }\n const qs = params.toString();\n if (qs) url += (url.includes(\"?\") ? \"&\" : \"?\") + qs;\n }\n return url;\n }\n}\n\n/**\n * Extract the hostname from a URL string for use as the\n * `selfHostname` field on the ErrorTracker. Returns null on\n * malformed input. Lowercased for case-insensitive comparison\n * (`Api.Cross-Deck.com` and `api.cross-deck.com` are the same host).\n */\nexport function extractSelfHostname(baseUrl: string | undefined | null): string | null {\n if (!baseUrl || typeof baseUrl !== \"string\") return null;\n try {\n return new URL(baseUrl).hostname.toLowerCase();\n } catch {\n return null;\n }\n}\n\n/**\n * True when the request URL targets the SDK's own backend hostname.\n * Used by the fetch wrapper to skip captureHttp on Crossdeck's own\n * requests — otherwise a Crossdeck-side outage would recurse\n * (captureHttp → enqueue → /events → fail → captureHttp → …).\n *\n * Strict hostname compare (not substring) so a path like\n * `https://api.cross-deck.com.attacker.example/...` doesn't falsely\n * match `api.cross-deck.com`. Falls back to `false` on malformed\n * URLs — the SDK only ever uses absolute URLs, so a relative URL\n * can't be the SDK's own request.\n */\nexport function isSelfRequest(\n requestUrl: string,\n selfHostname: string | null | undefined,\n): boolean {\n if (!selfHostname || !requestUrl) return false;\n try {\n return new URL(requestUrl).hostname.toLowerCase() === selfHostname;\n } catch {\n return false;\n }\n}\n","/**\n * Identity persistence for the RN SDK.\n *\n * Two values are tracked, mirroring @cross-deck/web exactly:\n *\n * anonymousId — generated on first boot. Persists for the\n * install lifetime so pre-login events stay\n * attached to the same identity graph entry.\n * crossdeckCustomerId — populated after the first identify() or\n * getEntitlements() that resolves a customer.\n * Persisted so subsequent app launches read\n * entitlements directly without an alias call.\n *\n * RN-specific divergence from web:\n *\n * - Storage is ASYNC. Hydration happens in `loadAll()` which\n * `Crossdeck.init()` awaits before any track()/identify() can\n * fire. Subsequent reads are SYNCHRONOUS from the in-memory\n * cache; writes fan out to the async storage fire-and-forget.\n * This matches RN's reality (AsyncStorage cannot be made sync)\n * while preserving the web API shape.\n *\n * - No cookie redundancy. The web SDK writes anonymousId to BOTH\n * localStorage AND a 1st-party cookie because either can be\n * wiped independently (ITP, clear-site-data). RN has only\n * AsyncStorage; native iOS/Android SDKs can fall back to\n * Keychain/KeyStore for reinstall-survival, but the JS layer\n * cannot. Documented honestly — app uninstall means identity\n * reset.\n */\n\nimport type { KeyValueStorage } from \"./types\";\n\nconst KEY_ANON = \"anon_id\";\nconst KEY_CDCUST = \"cdcust_id\";\nconst KEY_DEV_UID = \"developer_user_id\";\n\ninterface IdentityState {\n anonymousId: string;\n crossdeckCustomerId: string | null;\n developerUserId: string | null;\n}\n\nexport class IdentityStore {\n private state: IdentityState | null = null;\n private loaded = false;\n\n constructor(\n private readonly storage: KeyValueStorage,\n private readonly prefix: string,\n ) {}\n\n /**\n * Hydrate from durable storage. `Crossdeck.init()` awaits this\n * before any track()/identify() can fire. If no anonymousId is\n * found we mint one and persist it.\n *\n * Safe to call multiple times — second+ calls are no-ops.\n */\n async loadAll(): Promise<void> {\n if (this.loaded) return;\n const [anon, cdcust, dev] = await Promise.all([\n this.storage.getItem(this.prefix + KEY_ANON),\n this.storage.getItem(this.prefix + KEY_CDCUST),\n this.storage.getItem(this.prefix + KEY_DEV_UID),\n ]);\n const anonymousId = anon ?? mintAnonymousId();\n this.state = {\n anonymousId,\n crossdeckCustomerId: cdcust ?? null,\n developerUserId: dev ?? null,\n };\n if (!anon) {\n // First-launch — persist the fresh anonymousId so the next\n // launch reads it back instead of minting a new one (which\n // would break identity-graph continuity).\n this.fireAndForget(\n this.storage.setItem(this.prefix + KEY_ANON, anonymousId),\n );\n }\n this.loaded = true;\n }\n\n /** Sync read — only valid after loadAll() has resolved. */\n get anonymousId(): string {\n this.ensureLoaded();\n return this.state!.anonymousId;\n }\n\n /** Sync read — null when no customer has been resolved yet. */\n get crossdeckCustomerId(): string | null {\n this.ensureLoaded();\n return this.state!.crossdeckCustomerId;\n }\n\n /** Sync read — null when identify() has not been called this install. */\n get developerUserId(): string | null {\n this.ensureLoaded();\n return this.state!.developerUserId;\n }\n\n /** Persist a newly-resolved Crossdeck customer ID. */\n setCrossdeckCustomerId(value: string): void {\n this.ensureLoaded();\n this.state!.crossdeckCustomerId = value;\n this.fireAndForget(\n this.storage.setItem(this.prefix + KEY_CDCUST, value),\n );\n }\n\n /** Persist the developer-supplied user ID across launches. */\n setDeveloperUserId(value: string | null): void {\n this.ensureLoaded();\n this.state!.developerUserId = value;\n if (value === null) {\n this.fireAndForget(this.storage.removeItem(this.prefix + KEY_DEV_UID));\n } else {\n this.fireAndForget(this.storage.setItem(this.prefix + KEY_DEV_UID, value));\n }\n }\n\n /**\n * Wipe persisted identity. Called by reset() — used when an\n * end-user logs out. After reset the SDK mints a new anonymousId\n * so the next pre-login session is a fresh customer in the\n * identity graph.\n */\n reset(): void {\n this.ensureLoaded();\n const fresh = mintAnonymousId();\n this.state = {\n anonymousId: fresh,\n crossdeckCustomerId: null,\n developerUserId: null,\n };\n this.fireAndForget(this.storage.removeItem(this.prefix + KEY_CDCUST));\n this.fireAndForget(this.storage.removeItem(this.prefix + KEY_DEV_UID));\n this.fireAndForget(this.storage.setItem(this.prefix + KEY_ANON, fresh));\n }\n\n private ensureLoaded(): void {\n if (!this.loaded) {\n throw new Error(\n \"IdentityStore: loadAll() must complete before reading identity. \" +\n \"This is an internal SDK bug — please report.\",\n );\n }\n }\n\n private fireAndForget(promise: Promise<unknown>): void {\n promise.catch(() => {\n // Best-effort persistence — the in-memory cache is authoritative\n // for this session. Silent failure here is documented behaviour\n // for AsyncStorage adapters that can throw under quota / IO\n // pressure.\n });\n }\n}\n\n/**\n * Generate an anonymousId. Crockford-ish base36 timestamp + random\n * suffix. Same shape Stripe / Segment / others use — sortable,\n * log-friendly, no PII.\n */\nexport function mintAnonymousId(): string {\n const ts = Date.now().toString(36);\n const rand = randomChars(10);\n return `anon_${ts}${rand}`;\n}\n\n/**\n * Generate a cryptographically-random short string. Uses\n * `crypto.getRandomValues` when available (Hermes 0.74+, modern JSC,\n * Node's webcrypto), else falls back to Math.random with a\n * time-tail.\n *\n * The fallback is safe here because anonymousId entropy doesn't\n * need to resist offline brute force; it needs to be\n * unique-with-overwhelming-probability across one device's lifetime.\n */\nexport function randomChars(count: number): string {\n const alphabet = \"0123456789abcdefghijklmnopqrstuvwxyz\";\n const out: string[] = [];\n const cryptoApi = (globalThis as {\n crypto?: { getRandomValues?: (a: Uint8Array) => Uint8Array };\n }).crypto;\n if (cryptoApi?.getRandomValues) {\n const buf = new Uint8Array(count);\n cryptoApi.getRandomValues(buf);\n for (let i = 0; i < count; i++) {\n out.push(alphabet[buf[i]! % alphabet.length] ?? \"0\");\n }\n } else {\n for (let i = 0; i < count; i++) {\n out.push(alphabet[Math.floor(Math.random() * alphabet.length)] ?? \"0\");\n }\n }\n return out.join(\"\");\n}\n","/**\n * Minimal synchronous SHA-256 implementation (FIPS 180-4).\n *\n * Mirrors `sdks/web/src/hash.ts` line-for-line — kept duplicated\n * intentionally so each public SDK package ships zero shared\n * source (`publish-sdk-to-public-repo.mjs` copies per-SDK trees\n * verbatim). The two files MUST stay byte-identical except for\n * import lines.\n *\n * Why a pure-JS impl on React Native: SubtleCrypto.digest is not\n * available on stock Hermes/JSC. A polyfill (`react-native-quick-\n * crypto`) is common but a hard dep we shouldn't take. Pure JS\n * SHA-256 is ~80 lines, ~3KB minified, and runs in well under a\n * millisecond for typical userId inputs — the cost is\n * unobservable on the identify() hot path.\n *\n * Used to derive a per-user storage suffix for the entitlement\n * cache so each developerUserId's data lives under a physically\n * separate AsyncStorage key. Bank-grade isolation contract: even\n * a botched identify() that skips the in-memory clear cannot\n * cross-read a different user's cached entitlements.\n */\n\nconst K = new Uint32Array([\n 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,\n 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,\n 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,\n 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,\n 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,\n 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,\n 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,\n 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,\n 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,\n 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,\n 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,\n]);\n\nfunction utf8Bytes(input: string): Uint8Array {\n if (typeof TextEncoder !== \"undefined\") {\n return new TextEncoder().encode(input);\n }\n const out: number[] = [];\n for (let i = 0; i < input.length; i++) {\n let codePoint = input.charCodeAt(i);\n if (codePoint >= 0xd800 && codePoint <= 0xdbff && i + 1 < input.length) {\n const next = input.charCodeAt(i + 1);\n if (next >= 0xdc00 && next <= 0xdfff) {\n codePoint = 0x10000 + ((codePoint - 0xd800) << 10) + (next - 0xdc00);\n i++;\n }\n }\n if (codePoint < 0x80) {\n out.push(codePoint);\n } else if (codePoint < 0x800) {\n out.push(0xc0 | (codePoint >> 6));\n out.push(0x80 | (codePoint & 0x3f));\n } else if (codePoint < 0x10000) {\n out.push(0xe0 | (codePoint >> 12));\n out.push(0x80 | ((codePoint >> 6) & 0x3f));\n out.push(0x80 | (codePoint & 0x3f));\n } else {\n out.push(0xf0 | (codePoint >> 18));\n out.push(0x80 | ((codePoint >> 12) & 0x3f));\n out.push(0x80 | ((codePoint >> 6) & 0x3f));\n out.push(0x80 | (codePoint & 0x3f));\n }\n }\n return new Uint8Array(out);\n}\n\nexport function sha256Hex(input: string): string {\n const bytes = utf8Bytes(input);\n const bitLength = bytes.length * 8;\n const blockCount = Math.floor((bytes.length + 9 + 63) / 64);\n const padded = new Uint8Array(blockCount * 64);\n padded.set(bytes);\n padded[bytes.length] = 0x80;\n const high = Math.floor(bitLength / 0x100000000);\n const low = bitLength >>> 0;\n const lenOffset = padded.length - 8;\n padded[lenOffset + 0] = (high >>> 24) & 0xff;\n padded[lenOffset + 1] = (high >>> 16) & 0xff;\n padded[lenOffset + 2] = (high >>> 8) & 0xff;\n padded[lenOffset + 3] = high & 0xff;\n padded[lenOffset + 4] = (low >>> 24) & 0xff;\n padded[lenOffset + 5] = (low >>> 16) & 0xff;\n padded[lenOffset + 6] = (low >>> 8) & 0xff;\n padded[lenOffset + 7] = low & 0xff;\n\n const H = new Uint32Array([\n 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c,\n 0x1f83d9ab, 0x5be0cd19,\n ]);\n const W = new Uint32Array(64);\n\n // Non-null assertions throughout: the loop bounds + Uint8Array /\n // Uint32Array allocations guarantee every indexed access is\n // in-bounds. TypeScript's noUncheckedIndexedAccess flags the\n // `T | undefined` shape regardless; the `!` here is correctness-\n // preserving suppression, not a hope-and-pray.\n for (let block = 0; block < blockCount; block++) {\n const offset = block * 64;\n for (let t = 0; t < 16; t++) {\n W[t] =\n ((padded[offset + t * 4]! << 24) |\n (padded[offset + t * 4 + 1]! << 16) |\n (padded[offset + t * 4 + 2]! << 8) |\n padded[offset + t * 4 + 3]!) >>>\n 0;\n }\n for (let t = 16; t < 64; t++) {\n const w15 = W[t - 15]!;\n const w2 = W[t - 2]!;\n const s0 = ((w15 >>> 7) | (w15 << 25)) ^ ((w15 >>> 18) | (w15 << 14)) ^ (w15 >>> 3);\n const s1 = ((w2 >>> 17) | (w2 << 15)) ^ ((w2 >>> 19) | (w2 << 13)) ^ (w2 >>> 10);\n W[t] = (W[t - 16]! + s0 + W[t - 7]! + s1) >>> 0;\n }\n\n let a = H[0]!, b = H[1]!, c = H[2]!, d = H[3]!;\n let e = H[4]!, f = H[5]!, g = H[6]!, h = H[7]!;\n\n for (let t = 0; t < 64; t++) {\n const S1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7));\n const ch = (e & f) ^ (~e & g);\n const temp1 = (h + S1 + ch + K[t]! + W[t]!) >>> 0;\n const S0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10));\n const maj = (a & b) ^ (a & c) ^ (b & c);\n const temp2 = (S0 + maj) >>> 0;\n h = g;\n g = f;\n f = e;\n e = (d + temp1) >>> 0;\n d = c;\n c = b;\n b = a;\n a = (temp1 + temp2) >>> 0;\n }\n\n H[0] = (H[0]! + a) >>> 0;\n H[1] = (H[1]! + b) >>> 0;\n H[2] = (H[2]! + c) >>> 0;\n H[3] = (H[3]! + d) >>> 0;\n H[4] = (H[4]! + e) >>> 0;\n H[5] = (H[5]! + f) >>> 0;\n H[6] = (H[6]! + g) >>> 0;\n H[7] = (H[7]! + h) >>> 0;\n }\n\n let hex = \"\";\n for (let i = 0; i < 8; i++) {\n hex += H[i]!.toString(16).padStart(8, \"0\");\n }\n return hex;\n}\n","/**\n * Durable last-known-good cache of the customer's entitlements.\n *\n * This cache is NOT a second source of truth. Crossdeck remains the\n * only source; this is the SDK's local copy of what the server last\n * told us — a cache that doesn't forget during a network partition.\n *\n * Durability contract (the RevenueCat model):\n * - Every successful server read is persisted to device storage\n * (AsyncStorage, via the SDK's storage adapter).\n * - On SDK boot the cache hydrates from storage, so `isEntitled()`\n * answers correctly from the very first call after `init()`\n * resolves — no cold-start window where a returning Pro\n * customer reads as free.\n * - When the server is unreachable, the SDK keeps serving the\n * last entitlements it successfully fetched. A failed refresh\n * never reaches `setFromList()`, so it cannot clear the cache;\n * only a SUCCESSFUL fetch replaces it. An outage can never fail\n * a paying customer down to free.\n * - Staleness alone never returns false. Each entitlement is\n * honoured against its OWN `validUntil` instead — a time-based\n * trial expiry still applies even mid-partition.\n * - Staleness is VISIBLE, not silent. Once a refresh ATTEMPT\n * fails (or the data ages past `staleAfterMs`) the cache is\n * marked stale — `isStale` / `freshness` are surfaced in\n * `diagnostics()`. It keeps serving last-known-good; the\n * staleness is no longer hidden.\n *\n * Reactive listener API\n * ---------------------\n * `subscribe(listener)` registers a callback fired every time the\n * cache mutates (setFromList or clear) — the foundation for any\n * future RN hook (`useEntitlement(key)`). Semantics:\n * - Fired AFTER the mutation, so the listener sees fresh state.\n * - Fire-and-forget: a throwing listener is swallowed (and\n * counted) so a buggy consumer can't crash the SDK or other\n * listeners.\n * - Unsubscribe is idempotent.\n *\n * RN-specific divergence from web:\n *\n * - `hydrate()` is ASYNC. `Crossdeck.init()` awaits it before any\n * `isEntitled()` call would see an empty cache. Subsequent\n * reads are sync from the in-memory cache; writes fan out to\n * async storage fire-and-forget.\n */\n\nimport { sha256Hex } from \"./hash\";\nimport type { KeyValueStorage, PublicEntitlement } from \"./types\";\n\nexport type EntitlementsListener = (entitlements: PublicEntitlement[]) => void;\n\n/** Shape of the blob persisted to device storage. Versioned for forward-compat. */\ninterface PersistedCache {\n v: 1;\n entitlements: PublicEntitlement[];\n lastUpdated: number;\n}\n\n/** Default staleness window — data older than this is flagged even with no failed refresh. */\nconst DEFAULT_STALE_AFTER_MS = 24 * 60 * 60 * 1000; // 24h\n\n/** Anonymous suffix used before identify() has been called. */\nconst ANON_SUFFIX = \"_anon\";\n\n/** Suffix for the index entry that tracks every per-user key we've\n * written. Used by clearAll() to scope a logout-wipe to ONLY\n * Crossdeck keys, never the host app's own AsyncStorage. */\nconst INDEX_SUFFIX = \"_index\";\n\nexport class EntitlementCache {\n private all: PublicEntitlement[] = [];\n private lastUpdated = 0;\n private lastRefreshFailedAt = 0;\n private listeners = new Set<EntitlementsListener>();\n private listenerErrorCount = 0;\n private hydratedSuffixes = new Set<string>();\n private readonly storage: KeyValueStorage;\n private readonly storageKeyPrefix: string;\n private readonly staleAfterMs: number;\n private currentSuffix: string = ANON_SUFFIX;\n\n /**\n * @param storage Device storage adapter.\n * @param storageKeyPrefix Prefix used to derive per-user storage keys\n * (`<prefix>:<sha256(userId)>`). Default\n * `crossdeck:entitlements`. The trailing\n * user suffix is filled at identify() /\n * reset() time — see [[setUserKey]].\n * @param staleAfterMs Age past which last-known-good is flagged stale\n * even without a failed refresh. Default 24h.\n */\n constructor(\n storage: KeyValueStorage,\n storageKeyPrefix = \"crossdeck:entitlements\",\n staleAfterMs = DEFAULT_STALE_AFTER_MS,\n ) {\n this.storage = storage;\n this.storageKeyPrefix = storageKeyPrefix;\n this.staleAfterMs = staleAfterMs;\n }\n\n /** The full storage key the current-user blob is persisted under. */\n private get storageKey(): string {\n return `${this.storageKeyPrefix}:${this.currentSuffix}`;\n }\n\n /** Key of the index blob — a JSON array of every suffix we've\n * written. Used by clearAll() to scope a logout-wipe. */\n private get indexKey(): string {\n return `${this.storageKeyPrefix}:${INDEX_SUFFIX}`;\n }\n\n /** Derive a stable suffix for a developerUserId via SHA-256. */\n static suffixForUserId(userId: string | null): string {\n if (userId == null || userId === \"\") return ANON_SUFFIX;\n return sha256Hex(userId);\n }\n\n /**\n * Load last-known-good from device storage for the CURRENT\n * suffix. Run during `Crossdeck.init()` (anonymous slot) and\n * after every [[setUserKey]] switch. Idempotent per suffix —\n * a repeat call for the same suffix is a no-op.\n */\n async hydrate(): Promise<void> {\n const suffix = this.currentSuffix;\n if (this.hydratedSuffixes.has(suffix)) return;\n try {\n const raw = await this.storage.getItem(this.storageKey);\n if (raw) {\n const parsed = JSON.parse(raw) as PersistedCache;\n if (parsed && parsed.v === 1 && Array.isArray(parsed.entitlements)) {\n this.all = parsed.entitlements;\n this.lastUpdated =\n typeof parsed.lastUpdated === \"number\" ? parsed.lastUpdated : 0;\n }\n }\n } catch {\n // Storage unavailable / corrupt blob → start empty.\n }\n this.hydratedSuffixes.add(suffix);\n }\n\n /**\n * Switch the cache to a different user's storage slot. Bank-grade\n * three-layer isolation (v1.4.0 Phase 1.3):\n * (a) Physical key separation — `<prefix>:<sha256(userId)>` so\n * a user-switch can't physically read prior user's data\n * even if the in-memory clear was skipped.\n * (b) Unconditional in-memory clear — invoked whenever the\n * active suffix changes, even on same-id re-identify.\n * (c) Re-hydrate from the new slot — a returning user observes\n * their last-known-good cache from storage immediately.\n *\n * Caller (identify() / reset()) MUST `await` this BEFORE the\n * next `setFromList()` so the write lands under the right key.\n */\n async setUserKey(userId: string | null): Promise<void> {\n const nextSuffix = EntitlementCache.suffixForUserId(userId);\n // Always wipe in-memory + clear stale-fail flags so an\n // unconditional clear-on-identify is honoured even on\n // same-id re-identify.\n this.all = [];\n this.lastUpdated = 0;\n this.lastRefreshFailedAt = 0;\n if (nextSuffix !== this.currentSuffix) {\n this.currentSuffix = nextSuffix;\n // Force re-hydrate even if we'd previously hydrated this\n // suffix — the durable blob may have been overwritten by a\n // parallel tab / app instance.\n this.hydratedSuffixes.delete(nextSuffix);\n }\n await this.hydrate();\n this.notify();\n }\n\n /**\n * Sync read — true iff the entitlement is currently granting\n * access. Served from last-known-good: a stale cache (server\n * unreachable since the last successful fetch) still answers true\n * for a still-valid entitlement. The ONLY thing that turns it\n * false is the entitlement's own expiry (`validUntil`) — never\n * overall cache staleness.\n */\n isEntitled(key: string): boolean {\n const nowSec = Date.now() / 1000;\n return this.all.some(\n (e) =>\n e.key === key &&\n e.isActive &&\n (e.validUntil == null || e.validUntil > nowSec),\n );\n }\n\n /** Full snapshot for callers that need source / validUntil details. */\n list(): PublicEntitlement[] {\n return this.all.slice();\n }\n\n /** When the cache was last refreshed from the server. 0 means \"never\". */\n get freshness(): number {\n return this.lastUpdated;\n }\n\n /**\n * Whether the cache is knowingly serving older-than-trustworthy\n * data. True when the most recent refresh ATTEMPT failed (Crossdeck\n * unreachable since the last success), OR when last-known-good has\n * aged past `staleAfterMs`.\n *\n * `isStale` never changes what `isEntitled()` returns — the cache\n * still serves last-known-good. It exists so the staleness is\n * observable (`diagnostics()`) instead of an unbounded silent\n * window where a revoked customer holds access with nobody able\n * to see it.\n */\n get isStale(): boolean {\n if (this.lastRefreshFailedAt > this.lastUpdated) return true;\n return (\n this.lastUpdated > 0 &&\n Date.now() - this.lastUpdated > this.staleAfterMs\n );\n }\n\n /** Epoch ms of the last failed refresh attempt. 0 if none since the last success. */\n get refreshFailedAt(): number {\n return this.lastRefreshFailedAt;\n }\n\n get listenerErrors(): number {\n return this.listenerErrorCount;\n }\n\n /**\n * Record that a refresh attempt failed (Crossdeck unreachable /\n * transient error). The SDK's getEntitlements() calls this in its\n * catch path. Does NOT touch the cached entitlements —\n * last-known-good keeps serving — it only flips `isStale` so the\n * staleness shows up in `diagnostics()` rather than being silent.\n */\n markRefreshFailed(): void {\n this.lastRefreshFailedAt = Date.now();\n }\n\n /**\n * Replace the cache with a fresh server response and persist it\n * to device storage so it survives an app cold launch.\n *\n * Called ONLY after a successful server read — a failed fetch\n * throws before it reaches here, so last-known-good is preserved\n * through an outage. A success also clears the stale flag.\n */\n setFromList(entitlements: PublicEntitlement[]): void {\n this.all = entitlements.slice();\n this.lastUpdated = Date.now();\n this.lastRefreshFailedAt = 0;\n this.persist();\n void this.recordSuffixInIndex(this.currentSuffix);\n this.notify();\n }\n\n /**\n * Wipe the CURRENT user's slot. Used internally when a single\n * user's cache needs to be invalidated. The full-logout path is\n * [[clearAll]].\n */\n clear(): void {\n this.all = [];\n this.lastUpdated = 0;\n this.lastRefreshFailedAt = 0;\n const suffix = this.currentSuffix;\n void this.storage.removeItem(this.storageKey).catch(() => {});\n void this.removeSuffixFromIndex(suffix);\n this.notify();\n }\n\n /**\n * Logout-grade wipe — bank-grade contract: removes EVERY per-user\n * entitlement slot the SDK has ever written on this device, then\n * clears the index. Used by `Crossdeck.reset()` so a logout on a\n * shared device can never leave another user's entitlements\n * readable (layer (c) of the v1.4.0 isolation fix).\n *\n * Async to honour the AsyncStorage contract; safe to `void` if\n * the caller doesn't need to await teardown completion.\n */\n async clearAll(): Promise<void> {\n this.all = [];\n this.lastUpdated = 0;\n this.lastRefreshFailedAt = 0;\n this.currentSuffix = ANON_SUFFIX;\n this.hydratedSuffixes.clear();\n const suffixes = await this.readIndex();\n await Promise.all(\n suffixes.map((s) =>\n this.storage.removeItem(`${this.storageKeyPrefix}:${s}`).catch(() => {}),\n ),\n );\n // Also remove the anonymous slot explicitly — it may not have\n // been indexed if the cache was wiped before its first write.\n await this.storage.removeItem(`${this.storageKeyPrefix}:${ANON_SUFFIX}`).catch(() => {});\n await this.storage.removeItem(this.indexKey).catch(() => {});\n this.notify();\n }\n\n /**\n * Subscribe to cache mutations. Returns an idempotent unsubscribe\n * fn. The listener fires AFTER `setFromList()` or `clear()` with\n * the current snapshot.\n */\n subscribe(listener: EntitlementsListener): () => void {\n this.listeners.add(listener);\n let unsubscribed = false;\n return () => {\n if (unsubscribed) return;\n unsubscribed = true;\n this.listeners.delete(listener);\n };\n }\n\n // ----- Durable persistence -----\n\n private persist(): void {\n let blob: string;\n try {\n const data: PersistedCache = {\n v: 1,\n entitlements: this.all,\n lastUpdated: this.lastUpdated,\n };\n blob = JSON.stringify(data);\n } catch {\n return;\n }\n void this.storage.setItem(this.storageKey, blob).catch(() => {\n // Quota / IO error — silent degrade. In-memory still works for\n // this session; we just lose cross-launch durability.\n });\n }\n\n /** Read the index of all per-user suffixes the SDK has written. */\n private async readIndex(): Promise<string[]> {\n try {\n const raw = await this.storage.getItem(this.indexKey);\n if (!raw) return [];\n const parsed = JSON.parse(raw);\n if (Array.isArray(parsed)) {\n return parsed.filter((x): x is string => typeof x === \"string\");\n }\n return [];\n } catch {\n return [];\n }\n }\n\n /** Add a suffix to the persisted index. Idempotent. */\n private async recordSuffixInIndex(suffix: string): Promise<void> {\n const existing = await this.readIndex();\n if (existing.includes(suffix)) return;\n existing.push(suffix);\n await this.storage.setItem(this.indexKey, JSON.stringify(existing)).catch(() => {});\n }\n\n /** Remove a suffix from the persisted index. No-op if absent. */\n private async removeSuffixFromIndex(suffix: string): Promise<void> {\n const existing = await this.readIndex();\n const next = existing.filter((s) => s !== suffix);\n if (next.length === existing.length) return;\n if (next.length === 0) {\n await this.storage.removeItem(this.indexKey).catch(() => {});\n } else {\n await this.storage.setItem(this.indexKey, JSON.stringify(next)).catch(() => {});\n }\n }\n\n private notify(): void {\n if (this.listeners.size === 0) return;\n const snapshot = this.all.slice();\n const listenersSnapshot = [...this.listeners];\n for (const listener of listenersSnapshot) {\n try {\n listener(snapshot);\n } catch {\n this.listenerErrorCount += 1;\n }\n }\n }\n}\n","/**\n * Deterministic Idempotency-Key derivation for /purchases/sync.\n *\n * Phase 2.2 of bank-grade reconciliation v1.4.0. Mirrors\n * `sdks/web/src/idempotency-key.ts` byte-for-byte except for the\n * import line — the publish-sdk-to-public-repo contract copies\n * each per-SDK source tree verbatim, so duplication keeps each\n * package self-contained.\n */\n\nimport { sha256Hex } from \"./hash\";\n\nexport interface PurchaseSyncIdentity {\n rail: \"apple\" | \"google\" | \"stripe\" | string;\n signedTransactionInfo?: string;\n purchaseToken?: string;\n}\n\nexport function formatAsUuid(hex: string): string {\n return [\n hex.slice(0, 8),\n hex.slice(8, 12),\n hex.slice(12, 16),\n hex.slice(16, 20),\n hex.slice(20, 32),\n ].join(\"-\");\n}\n\nexport function deriveIdempotencyKeyForPurchase(body: PurchaseSyncIdentity): string {\n let identifier: string;\n if (body.rail === \"apple\") {\n identifier = body.signedTransactionInfo ?? \"\";\n } else if (body.rail === \"google\") {\n identifier = body.purchaseToken ?? \"\";\n } else {\n identifier = \"\";\n }\n if (!identifier) {\n throw new Error(\n `deriveIdempotencyKeyForPurchase: no stable identifier in body ` +\n `(rail=${body.rail}). Apple needs signedTransactionInfo; ` +\n `Google needs purchaseToken.`,\n );\n }\n const namespaced = `crossdeck:purchases/sync:${body.rail}:${identifier}`;\n return formatAsUuid(sha256Hex(namespaced));\n}\n","/**\n * Retry policy for the event-queue flush.\n *\n * Verbatim port of @cross-deck/web's retry-policy. After a failed\n * flush, the queue waits before re-attempting — otherwise a flapping\n * backend causes a hot loop, and a 429 \"slow down\" goes ignored.\n *\n * Policy:\n * - Exponential backoff: `base * 2^attempts`, capped at `maxMs`.\n * - Full jitter: result is multiplied by Math.random() so 100 SDK\n * instances retrying the same downed endpoint don't all hammer\n * at the same instant.\n * - 429 / 503 `Retry-After`: ALWAYS honour the server-supplied\n * delay when it's larger than our computed backoff. Capped at\n * 24h as a sanity guard against absurd values (server bug /\n * HTTP-date clock-skew). The server knows its own capacity\n * better than we do; ignoring it gets your IP blocked.\n * - Reset on success.\n *\n * Pure: no state on the function, no timers. The EventQueue owns\n * timers; the policy owns the math.\n */\n\nexport interface RetryPolicyOptions {\n baseMs?: number;\n maxMs?: number;\n factor?: number;\n /** Number of consecutive failures before flagging diagnostics. Default 8. */\n failuresBeforeWarn?: number;\n}\n\nconst DEFAULT_BASE = 1000;\nconst DEFAULT_MAX = 60_000;\nconst DEFAULT_FACTOR = 2;\nconst DEFAULT_WARN = 8;\n\nexport function computeNextDelay(\n attempts: number,\n retryAfterMs: number | undefined,\n options: RetryPolicyOptions = {},\n random: () => number = Math.random,\n): number {\n const base = options.baseMs ?? DEFAULT_BASE;\n const max = options.maxMs ?? DEFAULT_MAX;\n const factor = options.factor ?? DEFAULT_FACTOR;\n const safeAttempts = Math.min(attempts, 30);\n const ceiling = Math.min(max, base * Math.pow(factor, safeAttempts));\n const jittered = ceiling * random();\n // Honour the server-supplied delay above our computed window, but\n // cap at 24h as a sanity guard (server bug / HTTP-date clock-skew\n // could produce absurd values that would wedge the queue for years\n // otherwise).\n if (retryAfterMs !== undefined) {\n const ABSOLUTE_MAX_MS = 24 * 60 * 60 * 1000;\n const honoured = Math.min(ABSOLUTE_MAX_MS, retryAfterMs);\n if (honoured > jittered) return honoured;\n }\n return Math.max(0, Math.round(jittered));\n}\n\nexport class RetryPolicy {\n private attempts = 0;\n constructor(private readonly options: RetryPolicyOptions = {}) {}\n\n get consecutiveFailures(): number {\n return this.attempts;\n }\n\n get isWarning(): boolean {\n return this.attempts >= (this.options.failuresBeforeWarn ?? DEFAULT_WARN);\n }\n\n nextDelay(retryAfterMs?: number, random: () => number = Math.random): number {\n const delay = computeNextDelay(this.attempts, retryAfterMs, this.options, random);\n this.attempts += 1;\n return delay;\n }\n\n recordSuccess(): void {\n this.attempts = 0;\n }\n}\n","/**\n * Local event queue + batched flush.\n *\n * Why a queue: track() is called from hot paths (button taps, screen\n * mounts) and shouldn't block the UI on a network round-trip. Events\n * go into a local buffer, flushed in bursts.\n *\n * Flush triggers:\n * - Buffer reaches batchSize (default 20) → flush immediately.\n * - intervalMs of inactivity (default 5000ms) → flush idle batch.\n * - flush() called explicitly (e.g. when the app is backgrounding).\n *\n * Bank-grade durability + idempotency contract:\n *\n * - `pendingBatch` slot. Events spliced for the current flush sit\n * here until the server confirms them. On a retryable failure\n * the same batch is re-attempted with the SAME\n * `Idempotency-Key` so the backend can short-circuit duplicate\n * work (Stripe pattern). On success the slot clears + buffer\n * drains.\n *\n * - `persistAll()`. Persisted blob always carries\n * `[...pendingBatch, ...buffer]` so an app crash mid-flight\n * replays the in-flight batch on the next launch. The backend\n * dedupes via Firestore create-only on (projectId, eventId), so\n * re-sending events that may have already landed is safe.\n *\n * - Exponential backoff with full jitter (see retry-policy.ts) on\n * network / 5xx / 408 / 429 failures. Honours server\n * `Retry-After` when bigger than the computed window.\n *\n * - 4xx hard-stop (`isPermanent4xx`). 400 / 401 / 403 / 404 / 422\n * etc. drop the batch loudly: `onPermanentFailure` callback +\n * `console.error` regardless of debug mode + `dropped` counter\n * increments. Pre-fix every error retried forever with the same\n * key, silently growing the backlog while customers thought\n * events were landing.\n *\n * - Hard buffer cap (1000 events). Past the cap we evict the\n * OLDEST events and increment `dropped` so the developer can\n * see the loss in `diagnostics()`.\n */\n\nimport type { HttpClient } from \"./http\";\nimport type { CrossdeckError } from \"./errors\";\nimport type { EventProperties, IngestResponse } from \"./types\";\nimport { RetryPolicy, type RetryPolicyOptions } from \"./retry-policy\";\nimport type { PersistentEventStore } from \"./event-storage\";\nimport { randomChars } from \"./identity\";\n\nconst HARD_BUFFER_CAP = 1000;\n\nexport interface QueuedEvent {\n eventId: string;\n name: string;\n timestamp: number;\n properties: EventProperties;\n // identity hint — at least anonymousId is always set\n developerUserId?: string;\n anonymousId?: string;\n crossdeckCustomerId?: string;\n}\n\nexport interface BatchEnvelope {\n appId: string;\n environment: \"production\" | \"sandbox\";\n sdk: { name: string; version: string };\n}\n\nexport interface EventQueueConfig {\n http: HttpClient;\n batchSize: number;\n intervalMs: number;\n /**\n * Returns the NorthStar §13.1 envelope to attach to each batch\n * POST. It's a function (not a value) so a future config swap can\n * update the envelope without re-instantiating the queue.\n */\n envelope: () => BatchEnvelope;\n /** Schedule a function to run after `ms` ms. Default: setTimeout. */\n scheduler?: (fn: () => void, ms: number) => () => void;\n /** Called when the SDK drops events because the buffer is full. */\n onDrop?: (dropped: number) => void;\n /** Called once after the first successful flush — drives the §16 \"First event sent\" signal. */\n onFirstFlushSuccess?: () => void;\n /**\n * Durable persistence. When supplied, every buffer mutation is\n * written through to the store; on `hydrate()`, persisted events\n * are loaded back into the buffer.\n */\n persistentStore?: PersistentEventStore;\n /** Retry policy overrides for failed flushes. */\n retry?: RetryPolicyOptions;\n /**\n * Called whenever an item is added to the buffer or removed by a\n * successful flush. Exposed so the host SDK can surface live\n * queue stats via `diagnostics()` without polling.\n */\n onBufferChange?: (size: number) => void;\n /**\n * Surface for the SDK's debug logger to record retry scheduling +\n * persistence events. Fired async — never throws.\n */\n onRetryScheduled?: (info: {\n delayMs: number;\n consecutiveFailures: number;\n retryAfterMs?: number;\n lastError: string;\n }) => void;\n /**\n * Fired when the queue DROPS a batch because the server returned\n * a permanent 4xx (anything except 408 / 429). The host SDK\n * should surface this loudly — pre-fix the queue retried 4xx\n * errors forever with the same Idempotency-Key, silently growing\n * the backlog while the customer thought events were landing.\n * Common causes:\n * - 401: publishable key revoked / rotated\n * - 403: lacking permission for the project\n * - 400/422: malformed batch (schema mismatch, oversized event)\n * - 404: endpoint doesn't exist (typo'd baseUrl)\n */\n onPermanentFailure?: (info: {\n status: number;\n droppedCount: number;\n lastError: string;\n }) => void;\n}\n\nexport interface EventQueueStats {\n buffered: number;\n dropped: number;\n inFlight: number;\n lastFlushAt: number;\n lastError: string | null;\n /** Consecutive flush failures since the last success. */\n consecutiveFailures: number;\n /** Set when the next flush is scheduled by the retry policy. */\n nextRetryAt: number | null;\n}\n\nexport class EventQueue {\n private buffer: QueuedEvent[] = [];\n /**\n * In-flight events that have been spliced from `buffer` for the\n * current batch but haven't yet been confirmed (success or\n * permanent failure). On a retry-driven re-flush we re-use this\n * slot alongside `pendingBatchId` so the Stripe-style\n * `Idempotency-Key` is preserved across retries of the SAME\n * logical batch.\n */\n private pendingBatch: QueuedEvent[] | null = null;\n private pendingBatchId: string | null = null;\n private dropped = 0;\n private inFlight = 0;\n private lastFlushAt = 0;\n private lastError: string | null = null;\n private cancelTimer: (() => void) | null = null;\n private firstFlushFired = false;\n private nextRetryAt: number | null = null;\n private readonly retry: RetryPolicy;\n private readonly persistent: PersistentEventStore | null;\n\n constructor(private readonly cfg: EventQueueConfig) {\n this.retry = new RetryPolicy(cfg.retry ?? {});\n this.persistent = cfg.persistentStore ?? null;\n }\n\n /**\n * Async hydration. Called by `Crossdeck.init()` before any\n * track() call can fire so the persisted queue is rehydrated +\n * an idle flush is scheduled for it. RN-specific: the web SDK\n * does this synchronously in its constructor (localStorage is\n * sync); we can't.\n */\n async hydrate(): Promise<void> {\n if (!this.persistent) return;\n const restored = await this.persistent.load();\n if (restored.length === 0) return;\n if (restored.length > HARD_BUFFER_CAP) {\n this.dropped += restored.length - HARD_BUFFER_CAP;\n this.buffer = restored.slice(restored.length - HARD_BUFFER_CAP);\n } else {\n this.buffer = restored;\n }\n this.cfg.onBufferChange?.(this.buffer.length);\n // Schedule an immediate idle flush so rehydrated events land\n // on the next tick — even if no new track() call comes in.\n this.scheduleIdleFlush();\n }\n\n enqueue(event: QueuedEvent): void {\n this.buffer.push(event);\n if (this.buffer.length > HARD_BUFFER_CAP) {\n const overflow = this.buffer.length - HARD_BUFFER_CAP;\n this.buffer.splice(0, overflow);\n this.dropped += overflow;\n this.cfg.onDrop?.(overflow);\n }\n this.cfg.onBufferChange?.(this.buffer.length);\n this.persistAll();\n if (this.buffer.length >= this.cfg.batchSize) {\n void this.flush();\n } else {\n this.scheduleIdleFlush();\n }\n }\n\n /**\n * Flush the buffer to /v1/events. Resolves when the network call\n * completes (success or failure).\n *\n * Three terminal states from one call:\n * - 2xx success: pendingBatch cleared, persisted state collapses\n * to just `buffer` (any new events that arrived during\n * in-flight).\n * - 4xx permanent (except 408/429): pendingBatch DROPPED,\n * `dropped` increments, `onPermanentFailure` fires. The server\n * is telling us our request is malformed / key revoked / no\n * permission — retrying with the same key forever just grows\n * the queue.\n * - 5xx / network / 408 / 429: pendingBatch + batchId stay;\n * backoff schedules a retry; the next `flush()` re-uses both.\n */\n async flush(): Promise<IngestResponse | null> {\n // Resume an in-flight batch retry path: if we already have a\n // pending batch (prior flush failed, retry timer / caller is\n // re-invoking), re-attempt with the SAME batchId. Stripe\n // Idempotency-Key reuse contract.\n let batch: QueuedEvent[];\n let batchId: string;\n if (this.pendingBatch !== null && this.pendingBatchId !== null) {\n batch = this.pendingBatch;\n batchId = this.pendingBatchId;\n } else {\n if (this.buffer.length === 0) return null;\n batch = this.buffer.splice(0);\n batchId = this.mintBatchId();\n this.pendingBatch = batch;\n this.pendingBatchId = batchId;\n this.inFlight += batch.length;\n this.cfg.onBufferChange?.(this.buffer.length);\n // Persisted state continues to include this batch via\n // persistAll() until the server confirms it — that's the\n // durability fix.\n this.persistAll();\n }\n this.cancelTimerIfSet();\n this.nextRetryAt = null;\n\n try {\n const env = this.cfg.envelope();\n const result = await this.cfg.http.request<IngestResponse>(\"POST\", \"/events\", {\n body: {\n appId: env.appId,\n environment: env.environment,\n sdk: env.sdk,\n events: batch,\n },\n idempotencyKey: batchId,\n });\n this.lastFlushAt = Date.now();\n this.lastError = null;\n this.inFlight -= batch.length;\n this.pendingBatch = null;\n this.pendingBatchId = null;\n this.retry.recordSuccess();\n // Persisted blob collapses to just `buffer` (which may include\n // new enqueues that arrived while this batch was in flight).\n this.persistAll();\n if (!this.firstFlushFired) {\n this.firstFlushFired = true;\n this.cfg.onFirstFlushSuccess?.();\n }\n return result;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n this.lastError = message;\n\n // Permanent failures (4xx except 408/429) are NOT retryable.\n // Drop the batch loudly.\n if (isPermanent4xx(err)) {\n const droppedCount = batch.length;\n this.pendingBatch = null;\n this.pendingBatchId = null;\n this.inFlight -= droppedCount;\n this.dropped += droppedCount;\n this.persistAll();\n this.cfg.onDrop?.(droppedCount);\n this.cfg.onPermanentFailure?.({\n status: (err as { status?: number }).status ?? 0,\n droppedCount,\n lastError: message,\n });\n return null;\n }\n\n // Retryable failure. pendingBatch + pendingBatchId stay set;\n // the next scheduler-driven flush re-uses both. Persisted\n // state is unchanged from the entry path — it still includes\n // `[...pendingBatch, ...buffer]`.\n const retryAfterMs = extractRetryAfterMs(err);\n const delay = this.retry.nextDelay(retryAfterMs);\n this.scheduleRetry(delay);\n this.cfg.onRetryScheduled?.({\n delayMs: delay,\n consecutiveFailures: this.retry.consecutiveFailures,\n retryAfterMs,\n lastError: message,\n });\n return null;\n }\n }\n\n /** Cancel any pending timer and clear in-memory state. Wipes durable store too. */\n reset(): void {\n this.cancelTimerIfSet();\n this.nextRetryAt = null;\n this.buffer = [];\n this.pendingBatch = null;\n this.pendingBatchId = null;\n this.dropped = 0;\n this.inFlight = 0;\n this.lastError = null;\n this.retry.recordSuccess();\n void this.persistent?.clear();\n this.cfg.onBufferChange?.(0);\n // Note: we deliberately do NOT reset firstFlushFired — the\n // \"First event sent\" signal is a one-time onboarding moment per\n // SDK instance lifetime, not per-identity.\n }\n\n getStats(): EventQueueStats {\n return {\n buffered: this.buffer.length,\n dropped: this.dropped,\n inFlight: this.inFlight,\n lastFlushAt: this.lastFlushAt,\n lastError: this.lastError,\n consecutiveFailures: this.retry.consecutiveFailures,\n nextRetryAt: this.nextRetryAt,\n };\n }\n\n /**\n * The Idempotency-Key of the in-flight pending batch (if any).\n * Exposed for testing the Stripe-style retry-reuse contract.\n */\n get pendingIdempotencyKey(): string | null {\n return this.pendingBatchId;\n }\n\n // ---------- internal ----------\n\n private persistAll(): void {\n if (!this.persistent) return;\n if (this.pendingBatch === null) {\n this.persistent.save(this.buffer);\n return;\n }\n this.persistent.save([...this.pendingBatch, ...this.buffer]);\n }\n\n private scheduleIdleFlush(): void {\n this.cancelTimerIfSet();\n const sched = this.cfg.scheduler ?? defaultScheduler;\n this.cancelTimer = sched(() => {\n void this.flush();\n }, this.cfg.intervalMs);\n }\n\n private scheduleRetry(delayMs: number): void {\n this.cancelTimerIfSet();\n this.nextRetryAt = Date.now() + delayMs;\n const sched = this.cfg.scheduler ?? defaultScheduler;\n this.cancelTimer = sched(() => {\n void this.flush();\n }, delayMs);\n }\n\n private cancelTimerIfSet(): void {\n if (this.cancelTimer) {\n this.cancelTimer();\n this.cancelTimer = null;\n }\n }\n\n private mintBatchId(): string {\n return `batch_${Date.now().toString(36)}${randomChars(10)}`;\n }\n}\n\nfunction extractRetryAfterMs(err: unknown): number | undefined {\n if (err && typeof err === \"object\" && \"retryAfterMs\" in err) {\n const v = (err as CrossdeckError).retryAfterMs;\n return typeof v === \"number\" && Number.isFinite(v) && v >= 0 ? v : undefined;\n }\n return undefined;\n}\n\n/**\n * True when the error represents a permanent 4xx response that\n * SHOULDN'T be retried. Excludes 408 Request Timeout and 429 Too\n * Many Requests — both indicate transient state where the SAME\n * request (with the SAME Idempotency-Key) can succeed on a retry.\n *\n * Anything that isn't a CrossdeckError-shaped object with a numeric\n * status field returns false (network errors / fetch failures fall\n * here — those ARE retryable). Conservative default: only flag as\n * permanent when we have strong evidence from the server.\n */\nfunction isPermanent4xx(err: unknown): boolean {\n if (!err || typeof err !== \"object\") return false;\n const status = (err as { status?: unknown }).status;\n if (typeof status !== \"number\" || !Number.isFinite(status)) return false;\n if (status < 400 || status >= 500) return false;\n if (status === 408 || status === 429) return false;\n return true;\n}\n\nfunction defaultScheduler(fn: () => void, ms: number): () => void {\n const id = setTimeout(fn, ms);\n // setTimeout on Hermes supports .unref() on newer versions; the\n // typeof check is defensive for older runtimes that don't.\n if (typeof (id as unknown as { unref?: () => void }).unref === \"function\") {\n try {\n (id as unknown as { unref: () => void }).unref();\n } catch {\n // ignore — unref is best-effort\n }\n }\n return () => clearTimeout(id);\n}\n","/**\n * Durable event-queue persistence.\n *\n * Why this exists: the in-memory event-queue is fragile. Three\n * failure modes lose data without durable persistence:\n *\n * 1. App backgrounding under iOS / Android low-memory pressure\n * where the OS reclaims the JS context before the queue can\n * flush.\n * 2. App crash before the queue's idle-flush timer fires.\n * 3. Network down for longer than the user's session — events\n * queued while offline disappear when the process tears down.\n *\n * Stripe / Segment / PostHog all persist queued events to a durable\n * store (localStorage on web, AsyncStorage on RN, IndexedDB for\n * very large queues) and replay them on the next boot. We do the\n * same here with AsyncStorage as the default backing store.\n *\n * Failure modes handled gracefully:\n * - Storage throws (quota exceeded, IO error) → silent degrade\n * to in-memory only. The SDK keeps working; the durability\n * guarantee is best-effort.\n * - Persisted blob unparseable on next boot (manual corruption,\n * schema drift) → drop silently, fresh empty queue. Don't crash\n * the consumer app on a bad storage value.\n *\n * The storage key is `${prefix}queue.v1` to leave room for future\n * format migrations.\n *\n * RN-specific divergence from web:\n *\n * - All reads/writes are async. `load()` is awaited once at\n * boot from `EventQueue.hydrate()`; subsequent saves are\n * debounced via microtask + fire-and-forget. The web SDK can\n * do sync localStorage writes; we cannot.\n */\n\nimport type { KeyValueStorage } from \"./types\";\nimport type { QueuedEvent } from \"./event-queue\";\n\nexport interface PersistentEventStoreOptions {\n storage: KeyValueStorage;\n prefix: string;\n}\n\n/**\n * Wire format for persisted batches. Versioned so a future change to\n * QueuedEvent shape can be detected + ignored cleanly.\n */\ninterface PersistedQueue {\n version: 1;\n events: QueuedEvent[];\n}\n\nexport class PersistentEventStore {\n private readonly key: string;\n private writeScheduled = false;\n // Pending snapshot captured on the most recent save() call. A\n // debounced microtask picks up the latest ref when it fires, so\n // bursts of enqueue() calls coalesce into one persistence write.\n private pendingSnapshot: QueuedEvent[] | null = null;\n\n constructor(private readonly options: PersistentEventStoreOptions) {\n this.key = `${options.prefix}queue.v1`;\n }\n\n /**\n * Read the persisted queue on boot. Returns an empty array (with\n * no warning) when nothing is stored, the blob is malformed, or\n * storage is unavailable. Caller is responsible for treating\n * duplicates from the persisted queue as the SAME events\n * (eventId-based dedup on the backend).\n */\n async load(): Promise<QueuedEvent[]> {\n let raw: string | null;\n try {\n raw = await this.options.storage.getItem(this.key);\n } catch {\n return [];\n }\n if (!raw) return [];\n try {\n const parsed = JSON.parse(raw) as PersistedQueue;\n if (!parsed || parsed.version !== 1 || !Array.isArray(parsed.events)) {\n return [];\n }\n return parsed.events;\n } catch {\n // Corrupt blob — drop silently. Next save() overwrites.\n return [];\n }\n }\n\n /**\n * Schedule a write of the current buffer. Debounced via microtask\n * so a burst of enqueue() calls coalesces into one persistence\n * write. Writes are best-effort: if storage throws (quota / IO),\n * we swallow and rely on the in-memory buffer.\n */\n save(snapshot: readonly QueuedEvent[]): void {\n // Defensive copy so a later mutation of the buffer doesn't\n // change what we're about to persist.\n this.pendingSnapshot = snapshot.slice();\n if (this.writeScheduled) return;\n this.writeScheduled = true;\n queueMicrotask(() => this.flushWrite());\n }\n\n /** Wipe the persisted blob. Used by reset() (logout). */\n async clear(): Promise<void> {\n this.pendingSnapshot = null;\n this.writeScheduled = false;\n try {\n await this.options.storage.removeItem(this.key);\n } catch {\n // ignore\n }\n }\n\n private flushWrite(): void {\n this.writeScheduled = false;\n const snapshot = this.pendingSnapshot;\n this.pendingSnapshot = null;\n if (snapshot === null) return;\n\n if (snapshot.length === 0) {\n void this.options.storage.removeItem(this.key).catch(() => {});\n return;\n }\n\n const blob: PersistedQueue = { version: 1, events: snapshot };\n let serialised: string;\n try {\n serialised = JSON.stringify(blob);\n } catch {\n return;\n }\n void this.options.storage.setItem(this.key, serialised).catch(() => {\n // Quota exceeded / IO error — silent degrade. The in-memory\n // buffer is still authoritative; we just lose cross-launch\n // durability for this batch.\n });\n }\n}\n","/**\n * Storage adapters for SDK-persisted state on React Native.\n *\n * Two flavours:\n * - AsyncStorage (default when @react-native-async-storage/async-storage\n * resolves at runtime)\n * - in-memory (fallback, or explicit when the host app already\n * manages persistence — encrypted vaults, SecureStore, MMKV)\n *\n * RN does NOT ship localStorage. The SDK requires an ASYNC contract\n * here (Promise<string | null>) rather than the web SDK's sync one\n * because AsyncStorage is async and shimming sync-over-async would\n * either block the JS thread or invent stale reads. Every callsite\n * in this SDK awaits storage operations, so the cost is paid\n * honestly.\n *\n * Identity continuity caveats (documented honestly):\n * 1. AsyncStorage is cleared on app uninstall — there's no\n * equivalent to Safari's \"cleared site data but not the cookie\"\n * recovery. Native iOS/Android SDKs can reach Keychain/KeyStore\n * for reinstall-survival; the JS SDK can't.\n * 2. AsyncStorage is unencrypted on disk. We never persist\n * sensitive tokens — only the anonymousId, customerId, queued\n * events, super-properties, and entitlement cache. If the host\n * app needs encryption, pass a SecureStore-backed adapter.\n * 3. `persistIdentity: false` forces MemoryStorage so app shells\n * that defer to a consent gate can postpone any disk write\n * until the user opts in.\n */\n\nimport type { KeyValueStorage } from \"./types\";\n\n/**\n * In-memory storage. Cleared when the JS context tears down (app\n * cold launch, dev-tools reload). Use when you want session-scoped\n * identity with no on-disk trace.\n */\nexport class MemoryStorage implements KeyValueStorage {\n private store = new Map<string, string>();\n async getItem(key: string): Promise<string | null> {\n return this.store.get(key) ?? null;\n }\n async setItem(key: string, value: string): Promise<void> {\n this.store.set(key, value);\n }\n async removeItem(key: string): Promise<void> {\n this.store.delete(key);\n }\n}\n\n/**\n * AsyncStorage-backed adapter. Resolves the underlying module lazily\n * via indirect require() so apps that opt out of persistence (or\n * are running in an environment that doesn't ship AsyncStorage —\n * Storybook snapshots, vitest under node) don't pay the import cost\n * or hit a hard module-not-found.\n *\n * Failures degrade silently to null/no-op rather than throwing — a\n * broken storage layer should look identical to \"no value present\"\n * to the rest of the SDK. The diagnostic surface\n * (`Crossdeck.diagnostics()`) is the right place to surface\n * persistence health.\n */\nexport class AsyncStorageAdapter implements KeyValueStorage {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private store: any | null;\n\n constructor() {\n this.store = loadAsyncStorage();\n }\n\n async getItem(key: string): Promise<string | null> {\n if (!this.store) return null;\n try {\n const v = await this.store.getItem(key);\n return typeof v === \"string\" ? v : null;\n } catch {\n return null;\n }\n }\n\n async setItem(key: string, value: string): Promise<void> {\n if (!this.store) return;\n try {\n await this.store.setItem(key, value);\n } catch {\n /* quota / IO error — silent */\n }\n }\n\n async removeItem(key: string): Promise<void> {\n if (!this.store) return;\n try {\n await this.store.removeItem(key);\n } catch {\n /* silent */\n }\n }\n\n get available(): boolean {\n return this.store !== null;\n }\n}\n\n/**\n * Pick the best-available default storage. AsyncStorage when it\n * loads, MemoryStorage otherwise. Caller can override via\n * `Crossdeck.init({ storage: ... })` for SecureStore / MMKV /\n * encrypted adapters.\n */\nexport function detectDefaultStorage(): KeyValueStorage {\n const adapter = new AsyncStorageAdapter();\n if (adapter.available) return adapter;\n return new MemoryStorage();\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction loadAsyncStorage(): any | null {\n try {\n // Indirect require() so static analyzers / bundlers don't choke\n // when AsyncStorage isn't installed. RN's Metro resolves this at\n // runtime only if the package is present.\n const req = (\n globalThis as { require?: (id: string) => unknown }\n ).require;\n if (typeof req !== \"function\") return null;\n const mod = req(\"@react-native-async-storage/async-storage\") as\n | { default?: unknown }\n | undefined;\n if (!mod) return null;\n const candidate = (mod as { default?: unknown }).default ?? mod;\n if (\n candidate &&\n typeof (candidate as { getItem?: unknown }).getItem === \"function\"\n ) {\n return candidate;\n }\n return null;\n } catch {\n return null;\n }\n}\n","/**\n * Device + environment enrichment for React Native.\n *\n * Auto-attached to every event the SDK emits when the developer\n * opts into deviceInfo enrichment. Caller-supplied event properties\n * always override auto-detected ones.\n *\n * Privacy posture:\n * - No fingerprinting (no canvas hashes, no font enumeration).\n * - No precise geolocation (only timezone + locale, both of which\n * the runtime exposes to every app anyway).\n * - No IP collection — the backend logs the request IP for\n * rate-limit purposes; it isn't stored on the event document.\n * - All fields are typed enums or short strings; we never echo\n * back raw User-Agent equivalents to avoid surfacing\n * fingerprintable detail in dashboards.\n *\n * RN-specific sources:\n * - `Platform.OS` for ios / android (web for RN-Web).\n * - `Platform.Version` for OS version (number on Android, string\n * on iOS).\n * - `Platform.constants?.{Model,Brand,Manufacturer}` on Android\n * (iOS doesn't expose without a native module).\n * - `Dimensions.get(\"screen\")` + `.get(\"window\")` for screen size.\n * - `Intl.DateTimeFormat().resolvedOptions()` for locale +\n * timezone (Hermes 0.74+ supports the Intl API; older Hermes\n * degrades to null silently).\n */\n\nexport interface DeviceInfo {\n os?: \"ios\" | \"android\" | \"web\" | \"windows\" | \"macos\" | string;\n osVersion?: string;\n model?: string;\n brand?: string;\n manufacturer?: string;\n isPad?: boolean;\n isTV?: boolean;\n locale?: string;\n timezone?: string;\n screenWidth?: number;\n screenHeight?: number;\n windowWidth?: number;\n windowHeight?: number;\n scale?: number;\n fontScale?: number;\n /** Caller-supplied. Set via Crossdeck.init({ appVersion: \"1.2.3\" }). */\n appVersion?: string;\n}\n\n/**\n * Collect every safe-to-attach environment field. Returns an empty\n * object outside an RN runtime — caller can pass appVersion via the\n * `extra` argument when running under vitest / node test fixtures.\n */\nexport function collectDeviceInfo(extra?: { appVersion?: string }): DeviceInfo {\n const info: DeviceInfo = {};\n if (extra?.appVersion) info.appVersion = extra.appVersion;\n\n // ----- Platform module (RN core) -----\n const Platform = loadPlatform();\n if (Platform) {\n info.os = Platform.OS;\n if (Platform.Version !== undefined) {\n info.osVersion = String(Platform.Version);\n }\n if (Platform.isPad) info.isPad = true;\n if (Platform.isTV) info.isTV = true;\n const c = Platform.constants;\n if (c && typeof c === \"object\") {\n if (typeof c.Model === \"string\") info.model = c.Model;\n if (typeof c.Brand === \"string\") info.brand = c.Brand;\n if (typeof c.Manufacturer === \"string\") info.manufacturer = c.Manufacturer;\n }\n }\n\n // ----- Dimensions module (RN core) -----\n const Dimensions = loadDimensions();\n if (Dimensions) {\n try {\n const screen = Dimensions.get(\"screen\");\n if (screen) {\n info.screenWidth = screen.width;\n info.screenHeight = screen.height;\n if (screen.scale !== undefined) info.scale = screen.scale;\n if (screen.fontScale !== undefined) info.fontScale = screen.fontScale;\n }\n } catch {\n /* ignore */\n }\n try {\n const win = Dimensions.get(\"window\");\n if (win) {\n info.windowWidth = win.width;\n info.windowHeight = win.height;\n }\n } catch {\n /* ignore */\n }\n }\n\n // ----- Locale + timezone via Intl (Hermes 0.74+ / modern JSC) -----\n try {\n const opts = Intl.DateTimeFormat().resolvedOptions();\n if (opts.locale) info.locale = opts.locale;\n if (opts.timeZone) info.timezone = opts.timeZone;\n } catch {\n /* runtime without Intl — leave both null */\n }\n\n return info;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction loadPlatform(): any | null {\n return safeRequire(\"react-native\", \"Platform\");\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction loadDimensions(): any | null {\n return safeRequire(\"react-native\", \"Dimensions\");\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction safeRequire(moduleId: string, named: string): any | null {\n try {\n const req = (\n globalThis as { require?: (id: string) => unknown }\n ).require;\n if (typeof req !== \"function\") return null;\n const mod = req(moduleId) as Record<string, unknown> | undefined;\n if (!mod) return null;\n return mod[named] ?? null;\n } catch {\n return null;\n }\n}\n","/**\n * Debug signal vocabulary per NorthStar §16.\n *\n * The SDK speaks a small fixed vocabulary of signals so the\n * dashboard's onboarding checklist can show \"we saw your first event\"\n * without parsing free-form console output. When debug mode is\n * enabled the signals also log to `console.info` so a developer\n * doing copy-paste integration sees actionable feedback live.\n *\n * Signal names are STABLE — adding new ones is fine, renaming is a\n * breaking change because the dashboard onboarding step keys off\n * them.\n */\n\nexport type DebugSignal =\n | \"sdk.configured\"\n | \"sdk.first_event_sent\"\n | \"sdk.invalid_key\"\n | \"sdk.no_identity\"\n | \"sdk.entitlement_cache_used\"\n | \"sdk.purchase_evidence_sent\"\n | \"sdk.environment_mismatch\"\n | \"sdk.sensitive_property_warning\"\n | \"sdk.property_coerced\"\n | \"sdk.queue_persisted\"\n | \"sdk.queue_restored\"\n | \"sdk.flush_retry_scheduled\"\n // Emitted when the queue drops a batch because the server returned\n // a permanent 4xx (key revoked, malformed batch, etc.). Always\n // loud, regardless of debug mode — see the console.error in\n // crossdeck.ts.\n | \"sdk.flush_permanent_failure\"\n | \"sdk.consent_changed\"\n | \"sdk.consent_denied\"\n | \"sdk.pii_scrubbed\";\n\nexport interface DebugContext {\n [key: string]: unknown;\n}\n\n/**\n * Names that almost always indicate PII or secret data. Used by\n * `track()` to warn the developer when a property key looks\n * dangerous. Per NorthStar §15 these are reject/warn-on-sight values;\n * we warn rather than reject because the developer might genuinely\n * want a property called e.g. \"tokens_remaining\".\n */\nconst SENSITIVE_KEY_PATTERNS: readonly RegExp[] = [\n /^email$/i,\n /^password$/i,\n /^token$/i,\n /^secret$/i,\n /^card$/i,\n /^phone$/i,\n /password/i,\n /credit_?card/i,\n];\n\nexport function findSensitivePropertyKeys(\n properties: Record<string, unknown> | undefined,\n): string[] {\n if (!properties) return [];\n const hits: string[] = [];\n for (const k of Object.keys(properties)) {\n if (SENSITIVE_KEY_PATTERNS.some((re) => re.test(k))) hits.push(k);\n }\n return hits;\n}\n\nexport interface DebugLogger {\n enabled: boolean;\n emit(signal: DebugSignal, message: string, context?: DebugContext): void;\n}\n\nexport class ConsoleDebugLogger implements DebugLogger {\n enabled = false;\n private seen = new Set<DebugSignal>();\n\n emit(signal: DebugSignal, message: string, context?: DebugContext): void {\n if (!this.enabled) return;\n // For one-shot signals (sdk.configured, sdk.first_event_sent,\n // sdk.environment_mismatch) suppress duplicates within a session\n // so a chatty app doesn't spam the console with the same message.\n if (ONCE_SIGNALS.has(signal)) {\n if (this.seen.has(signal)) return;\n this.seen.add(signal);\n }\n const ctx = context ? ` ${safeJson(context)}` : \"\";\n // eslint-disable-next-line no-console\n console.info(`[crossdeck:${signal}] ${message}${ctx}`);\n }\n}\n\nconst ONCE_SIGNALS = new Set<DebugSignal>([\n \"sdk.configured\",\n \"sdk.first_event_sent\",\n \"sdk.environment_mismatch\",\n]);\n\nfunction safeJson(obj: unknown): string {\n try {\n return JSON.stringify(obj);\n } catch {\n return \"[unserialisable context]\";\n }\n}\n","/**\n * Property validation + coercion for `track()` events.\n *\n * Why this exists: the public `EventProperties` type is\n * `Record<string, unknown>` — developers can (and will) put anything\n * in there. Without a sanitiser, JSON.stringify at flush time will\n * throw on a function, a BigInt, a circular reference, or a Map, and\n * the WHOLE BATCH gets re-buffered every flush attempt until the\n * offending event is manually purged. Stripe-grade SDKs sanitise at\n * the call site so one bad property can't poison the queue.\n *\n * Contract:\n * - Drop functions / symbols / undefined values (with a warning).\n * - Coerce Date → ISO string, BigInt → string, Error → { name, message, stack }.\n * - Truncate string values longer than `maxStringLength` (default 1024).\n * - Replace circular refs with `\"[circular]\"`. ANCESTOR-only check —\n * a legitimate DAG (`{a: shared, b: shared}`) is NOT flagged; only\n * a real cycle (parent re-encountered via a descendant) triggers.\n * - Cap total serialised size at `maxBatchPropertyBytes` (default 8192).\n *\n * Pure function — no I/O, no console calls. Caller decides how to\n * surface warnings (debug log, telemetry counter, etc.).\n */\n\nimport type { EventProperties } from \"./types\";\n\nexport interface ValidationOptions {\n maxStringLength?: number;\n maxBatchPropertyBytes?: number;\n /**\n * Hard cap on depth of object/array nesting. Anything deeper is\n * coerced to \"[depth-exceeded]\". Defaults to 5 — covers most real\n * shapes (e.g. nested API responses) without letting a circular\n * structure consume the call stack via recursion.\n */\n maxDepth?: number;\n}\n\nexport interface ValidationWarning {\n kind:\n | \"dropped_function\"\n | \"dropped_symbol\"\n | \"dropped_undefined\"\n | \"coerced_date\"\n | \"coerced_bigint\"\n | \"coerced_error\"\n | \"coerced_map\"\n | \"coerced_set\"\n | \"truncated_string\"\n | \"circular_reference\"\n | \"depth_exceeded\"\n | \"non_serialisable\"\n | \"size_cap_exceeded\";\n key: string;\n}\n\nexport interface ValidationResult {\n properties: EventProperties;\n warnings: ValidationWarning[];\n}\n\nconst DEFAULT_MAX_STRING = 1024;\nconst DEFAULT_MAX_BYTES = 8 * 1024;\nconst DEFAULT_MAX_DEPTH = 5;\n\n/**\n * Validate + coerce a property bag. Always returns a NEW object — the\n * caller's input is never mutated.\n */\nexport function validateEventProperties(\n input: EventProperties | undefined,\n options: ValidationOptions = {},\n): ValidationResult {\n const warnings: ValidationWarning[] = [];\n if (!input) return { properties: {}, warnings };\n\n const maxStringLength = options.maxStringLength ?? DEFAULT_MAX_STRING;\n const maxBatchPropertyBytes = options.maxBatchPropertyBytes ?? DEFAULT_MAX_BYTES;\n const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;\n\n // Ancestor-only circular detection: add the object/array to `seen`\n // before recursing into its children, REMOVE it after. A\n // re-encounter while a value is still in the set means it's an\n // ancestor of the current node (a real cycle). Sibling sharing —\n // two properties pointing at the same sub-object (a legitimate\n // DAG, e.g. an event with `{user: shared, owner: shared}`) — is\n // NOT a cycle and must NOT be flagged.\n const seen = new Set<object>();\n\n const visit = (\n value: unknown,\n key: string,\n depth: number,\n ): { keep: boolean; value: unknown } => {\n if (depth > maxDepth) {\n warnings.push({ kind: \"depth_exceeded\", key });\n return { keep: true, value: \"[depth-exceeded]\" };\n }\n if (value === null) return { keep: true, value: null };\n const t = typeof value;\n if (t === \"string\") {\n const s = value as string;\n if (s.length > maxStringLength) {\n warnings.push({ kind: \"truncated_string\", key });\n return { keep: true, value: s.slice(0, maxStringLength - 1) + \"…\" };\n }\n return { keep: true, value: s };\n }\n if (t === \"number\") {\n if (!Number.isFinite(value as number)) {\n warnings.push({ kind: \"non_serialisable\", key });\n return { keep: true, value: null };\n }\n return { keep: true, value };\n }\n if (t === \"boolean\") return { keep: true, value };\n if (t === \"bigint\") {\n warnings.push({ kind: \"coerced_bigint\", key });\n return { keep: true, value: (value as bigint).toString() };\n }\n if (t === \"function\") {\n warnings.push({ kind: \"dropped_function\", key });\n return { keep: false, value: undefined };\n }\n if (t === \"symbol\") {\n warnings.push({ kind: \"dropped_symbol\", key });\n return { keep: false, value: undefined };\n }\n if (t === \"undefined\") {\n warnings.push({ kind: \"dropped_undefined\", key });\n return { keep: false, value: undefined };\n }\n\n if (value instanceof Date) {\n warnings.push({ kind: \"coerced_date\", key });\n const iso = Number.isFinite(value.getTime()) ? value.toISOString() : null;\n return { keep: true, value: iso };\n }\n if (value instanceof Error) {\n warnings.push({ kind: \"coerced_error\", key });\n return {\n keep: true,\n value: {\n name: value.name,\n message: value.message,\n stack: typeof value.stack === \"string\" ? value.stack.slice(0, maxStringLength) : undefined,\n },\n };\n }\n if (value instanceof Map) {\n warnings.push({ kind: \"coerced_map\", key });\n const obj: Record<string, unknown> = {};\n for (const [k, v] of value.entries()) {\n const subKey = typeof k === \"string\" ? k : String(k);\n const result = visit(v, `${key}.${subKey}`, depth + 1);\n if (result.keep) obj[subKey] = result.value;\n }\n return { keep: true, value: obj };\n }\n if (value instanceof Set) {\n warnings.push({ kind: \"coerced_set\", key });\n const arr: unknown[] = [];\n let i = 0;\n for (const v of value.values()) {\n const result = visit(v, `${key}[${i}]`, depth + 1);\n if (result.keep) arr.push(result.value);\n i++;\n }\n return { keep: true, value: arr };\n }\n\n if (Array.isArray(value)) {\n if (seen.has(value)) {\n warnings.push({ kind: \"circular_reference\", key });\n return { keep: true, value: \"[circular]\" };\n }\n seen.add(value);\n const out: unknown[] = [];\n for (let i = 0; i < value.length; i++) {\n const result = visit(value[i], `${key}[${i}]`, depth + 1);\n if (result.keep) out.push(result.value);\n }\n // Delete on exit — the array is no longer an ancestor of any\n // sibling visit. Sibling DAG sharing is fine.\n seen.delete(value);\n return { keep: true, value: out };\n }\n\n if (t === \"object\") {\n const obj = value as Record<string, unknown>;\n if (seen.has(obj)) {\n warnings.push({ kind: \"circular_reference\", key });\n return { keep: true, value: \"[circular]\" };\n }\n seen.add(obj);\n const out: Record<string, unknown> = {};\n for (const k of Object.keys(obj)) {\n const result = visit(obj[k], `${key}.${k}`, depth + 1);\n if (result.keep) out[k] = result.value;\n }\n seen.delete(obj);\n return { keep: true, value: out };\n }\n\n warnings.push({ kind: \"non_serialisable\", key });\n try {\n return { keep: true, value: String(value) };\n } catch {\n return { keep: false, value: undefined };\n }\n };\n\n const cleaned: Record<string, unknown> = {};\n for (const k of Object.keys(input)) {\n const result = visit(input[k], k, 0);\n if (result.keep) cleaned[k] = result.value;\n }\n\n // Final pass: enforce overall byte cap. JSON.stringify the cleaned\n // bag; if too large, drop properties (largest-first) until under.\n const serialised = safeStringify(cleaned);\n if (serialised && byteLength(serialised) > maxBatchPropertyBytes) {\n warnings.push({ kind: \"size_cap_exceeded\", key: \"*\" });\n const sizes = Object.keys(cleaned)\n .map((k) => ({ k, size: byteLength(safeStringify(cleaned[k]) ?? \"\") }))\n .sort((a, b) => b.size - a.size);\n let currentSize = byteLength(serialised);\n for (const { k } of sizes) {\n if (currentSize <= maxBatchPropertyBytes) break;\n currentSize -= sizes.find((s) => s.k === k)!.size;\n delete cleaned[k];\n }\n cleaned.__truncated = true;\n }\n\n return { properties: cleaned, warnings };\n}\n\nfunction safeStringify(v: unknown): string | null {\n try {\n return JSON.stringify(v) ?? null;\n } catch {\n return null;\n }\n}\n\nfunction byteLength(s: string): number {\n if (typeof TextEncoder !== \"undefined\") {\n return new TextEncoder().encode(s).length;\n }\n return s.length * 4;\n}\n","/**\n * Super properties + group analytics — Mixpanel pattern.\n *\n * **Super properties** are key/value pairs the developer registers\n * ONCE via `Crossdeck.register({ plan: \"pro\" })` that get attached\n * to every subsequent event of that SDK instance. They're the\n * single most-used feature in Mixpanel-style analytics: \"every event\n * from this user should have `plan` and `appVersion` on it\" instead\n * of remembering to pass them on every track() call.\n *\n * **Groups** are organisational identifiers: a customer might\n * belong to an `org` (\"acme\"), a `team` (\"design\"), and a `plan`\n * (\"enterprise\"). Each event carries `$groups.{type}: id` so B2B\n * dashboards can pivot.\n *\n * Both surfaces live in this module because they share two traits:\n * - They're set once, attached to every event automatically.\n * - They persist across app launches via the same storage layer\n * the SDK uses for identity.\n *\n * The store is reset on `Crossdeck.reset()` (logout) — both super\n * properties and groups are cleared because their lifetime is tied\n * to the identified user, not the SDK instance.\n *\n * RN-specific divergence from web:\n *\n * - Storage is ASYNC. Hydration via `loadAll()` runs once during\n * `Crossdeck.init()`; subsequent reads are sync from the\n * in-memory cache, writes fan out to async storage\n * fire-and-forget. Same pattern as IdentityStore.\n */\n\nimport type { KeyValueStorage } from \"./types\";\n\nconst KEY_SUPER = \"super_props\";\nconst KEY_GROUPS = \"groups\";\n\nexport class SuperPropertyStore {\n private superProps: Record<string, unknown> = {};\n private groups: Record<string, { id: string; traits?: Record<string, unknown> }> = {};\n private loaded = false;\n\n constructor(\n private readonly storage: KeyValueStorage,\n private readonly prefix: string,\n ) {}\n\n /**\n * Hydrate from durable storage. Called by Crossdeck.init() before\n * any track() can fire so super-props are present on the very\n * first event of the session. Safe to call multiple times — second+\n * calls are no-ops.\n */\n async loadAll(): Promise<void> {\n if (this.loaded) return;\n const [supersRaw, groupsRaw] = await Promise.all([\n this.storage.getItem(this.prefix + KEY_SUPER),\n this.storage.getItem(this.prefix + KEY_GROUPS),\n ]);\n this.superProps = parseJson<Record<string, unknown>>(supersRaw) ?? {};\n this.groups = parseJson(groupsRaw) ?? {};\n this.loaded = true;\n }\n\n // ---------- super properties ----------\n\n /**\n * Merge new keys into the super-property bag. Returns a snapshot\n * of the resulting bag. Values that are `null` are deleted (the\n * explicit \"stop tracking this key\" idiom — Mixpanel semantics).\n */\n register(props: Record<string, unknown>): Record<string, unknown> {\n for (const [k, v] of Object.entries(props)) {\n if (v === null) {\n delete this.superProps[k];\n } else if (v !== undefined) {\n this.superProps[k] = v;\n }\n }\n this.writeJson(this.prefix + KEY_SUPER, this.superProps);\n return { ...this.superProps };\n }\n\n /** Remove a single super-property key. Idempotent. */\n unregister(key: string): void {\n if (key in this.superProps) {\n delete this.superProps[key];\n this.writeJson(this.prefix + KEY_SUPER, this.superProps);\n }\n }\n\n /** Snapshot of the current super-property bag. */\n getSuperProperties(): Record<string, unknown> {\n return { ...this.superProps };\n }\n\n // ---------- groups ----------\n\n /**\n * Set a group membership. Passing `id: null` clears the\n * membership for that group type — the SDK stops attaching it to\n * events.\n */\n setGroup(type: string, id: string | null, traits?: Record<string, unknown>): void {\n if (id === null) {\n delete this.groups[type];\n } else {\n this.groups[type] = traits !== undefined ? { id, traits } : { id };\n }\n this.writeJson(this.prefix + KEY_GROUPS, this.groups);\n }\n\n /**\n * Snapshot of the current groups map, keyed by group type.\n * Returned shape mirrors what the SDK attaches to every event as\n * `$groups.{type}`.\n */\n getGroups(): Record<string, { id: string; traits?: Record<string, unknown> }> {\n return JSON.parse(JSON.stringify(this.groups));\n }\n\n /**\n * The flat `{ type: id }` projection used for event-attachment.\n * Stable for fast every-event merge — we don't want to JSON-clone\n * on each track() call.\n */\n getGroupIds(): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [type, info] of Object.entries(this.groups)) {\n out[type] = info.id;\n }\n return out;\n }\n\n /** Wipe both bags. Called by Crossdeck.reset() (logout). */\n clear(): void {\n this.superProps = {};\n this.groups = {};\n void this.storage.removeItem(this.prefix + KEY_SUPER).catch(() => {});\n void this.storage.removeItem(this.prefix + KEY_GROUPS).catch(() => {});\n }\n\n private writeJson(key: string, value: unknown): void {\n let s: string;\n try {\n s = JSON.stringify(value);\n } catch {\n return;\n }\n void this.storage.setItem(key, s).catch(() => {\n // Best-effort — in-memory cache stays authoritative for this\n // session. Cross-launch persistence is lost on this one write\n // but the next register/setGroup will retry.\n });\n }\n}\n\nfunction parseJson<T>(raw: string | null): T | null {\n if (!raw) return null;\n try {\n return JSON.parse(raw) as T;\n } catch {\n return null;\n }\n}\n","/**\n * Consent gating + PII scrub.\n *\n * Three consent dimensions (GDPR / CCPA-grade kill switches), each\n * defaulting to \"granted\" but runtime-overridable via\n * `Crossdeck.consent({...})`:\n *\n * analytics — track(), identify(), auto-emissions. Off → events\n * drop silently, no network calls fire.\n * marketing — paid-traffic click IDs and referrer URL. Off → these\n * get scrubbed before they ever land in the event bag.\n * errors — error capture. Off → no error.* events emitted.\n *\n * No `respectDnt` option on React Native — there's no `navigator` or\n * Do-Not-Track header equivalent in the RN runtime. The developer is\n * responsible for wiring whatever consent mechanism their app uses\n * (App Tracking Transparency on iOS, custom UI elsewhere) into\n * `Crossdeck.consent({...})` calls.\n *\n * PII scrub: defence-in-depth regex pass over every string property\n * value. Sentinel tokens (`<email>`, `<card>`) match the backend's\n * scrub (backend/src/api/lib/scrub.ts) so the same event scrubbed at\n * SDK or backend layers carries the same dashboard-aggregation key.\n * Recursive — nested plain objects + arrays-of-objects are walked,\n * so a `{user:{email:\"x@y.com\"}}` payload ships scrubbed.\n */\n\nexport interface ConsentState {\n analytics: boolean;\n marketing: boolean;\n errors: boolean;\n}\n\nconst ALL_GRANTED: ConsentState = {\n analytics: true,\n marketing: true,\n errors: true,\n};\n\nexport class ConsentManager {\n private state: ConsentState = { ...ALL_GRANTED };\n\n /**\n * Merge new dimensions onto the current state. Returns the resulting\n * snapshot.\n */\n set(partial: Partial<ConsentState>): ConsentState {\n for (const k of Object.keys(partial) as Array<keyof ConsentState>) {\n const v = partial[k];\n if (typeof v === \"boolean\") this.state[k] = v;\n }\n return { ...this.state };\n }\n\n get(): ConsentState {\n return { ...this.state };\n }\n\n get analytics(): boolean {\n return this.state.analytics;\n }\n get marketing(): boolean {\n return this.state.marketing;\n }\n get errors(): boolean {\n return this.state.errors;\n }\n}\n\n// ============================================================\n// PII scrubbing\n// ============================================================\n\n/**\n * Email-shaped pattern. Reasonably restrictive — matches RFC 5322's\n * \"obs-local-part\" common case (the practical 99% of emails).\n */\nconst EMAIL_PATTERN =\n /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/g;\n\n/**\n * Card-number-shaped pattern. Matches sequences of 13-19 digits\n * separated by space or hyphen — the format every payment form\n * accepts. Anchored on a digit at both ends so trailing separators\n * aren't pulled into the match.\n */\nconst CARD_PATTERN = /\\b\\d(?:[ -]?\\d){12,18}\\b/g;\n\n// Sentinel tokens — aligned with backend/src/api/lib/scrub.ts which\n// uses <email>, <card>, <uuid>, <cdcust>, <crossdeck_secret_key>,\n// <aws_access_key>. Mismatched tokens between SDK and backend\n// scrubbers would split dashboard aggregation (same event arriving\n// via two paths carries two different sentinels).\nconst REPLACEMENT_EMAIL = \"<email>\";\nconst REPLACEMENT_CARD = \"<card>\";\n\n/**\n * Scrub a single string value: replace email-shaped substrings with\n * `<email>` and card-number-shaped substrings with `<card>`. Returns\n * the original string when nothing matched.\n *\n * Implementation note: `.replace()` is called unconditionally rather\n * than gating on `.test()`. The /g regexes are module-level so\n * `.test()` carries `lastIndex` state between calls — a prior match\n * leaves `lastIndex` mid-string and the next `.test()` can falsely\n * return false on a string that DOES match. `.replace(/g)` always\n * scans the full string regardless of `lastIndex`.\n */\nexport function scrubPii(value: string): string {\n if (!value) return value;\n return value\n .replace(EMAIL_PATTERN, REPLACEMENT_EMAIL)\n .replace(CARD_PATTERN, REPLACEMENT_CARD);\n}\n\n/**\n * Walk an event's properties and replace PII-shaped strings in place.\n * Returns a new object with strings scrubbed; non-string values pass\n * through unchanged.\n *\n * Defensive copy — the input is never altered. Caller can pass the\n * result straight to the queue.\n *\n * Recursive: nested plain objects + arrays are walked. Without this,\n * an event like `{user:{email:\"wes@…\"}}` would ship the email\n * unscrubbed because the top-level value is an object, not a string.\n * Every captured-error report ships nested `frames[]` / `breadcrumbs[]`\n * / `context{}` / `http{}` shapes through here — this is the SDK's\n * #1 PII protection beyond the SDK boundary.\n *\n * Date / Map / Set / Error / class instances pass through untouched\n * (those are the validateEventProperties sanitiser's job — this is\n * the PII regex pass only).\n */\nexport function scrubPiiFromProperties(\n properties: Record<string, unknown>,\n): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const k of Object.keys(properties)) {\n out[k] = scrubValue(properties[k]);\n }\n return out;\n}\n\nfunction scrubValue(v: unknown): unknown {\n if (typeof v === \"string\") return scrubPii(v);\n if (Array.isArray(v)) return v.map(scrubValue);\n if (v && typeof v === \"object\" && (v as object).constructor === Object) {\n // Plain objects only — Date, Map, Set, Error, RegExp, class\n // instances are left untouched so we don't accidentally mutate\n // an Error's `message` and confuse downstream error reporting.\n return scrubPiiFromProperties(v as Record<string, unknown>);\n }\n return v;\n}\n","/**\n * Breadcrumb ring buffer — context attached to every error report.\n *\n * Sentry / Datadog / Bugsnag all ship the same idea: keep a rolling\n * record of the last N \"things the user did\" (screen mounts, custom\n * events, network calls). When an error fires, attach the buffer so\n * the engineer reading the error can see exactly how the user got\n * into the broken state. The single most powerful debugging signal\n * in error monitoring — without breadcrumbs, errors are stack traces\n * with no story.\n *\n * Implementation: a circular buffer with a fixed cap. Old entries are\n * evicted as new ones arrive. The default cap (50) is enough to\n * cover ~5 minutes of typical user activity without ballooning the\n * error payload — Sentry uses 100 by default but the SDK is more\n * aggressive about size since we ship breadcrumbs over the wire with\n * every error, not as a separate batch.\n *\n * Privacy: breadcrumbs auto-emit from the same auto-tracking sources\n * as analytics events (when auto-track is on). Custom crumbs added\n * via Crossdeck.addBreadcrumb() pass through the same property\n * sanitiser as track() events.\n */\n\nexport type BreadcrumbCategory =\n | \"navigation\"\n | \"ui.click\"\n | \"ui.input\"\n | \"http\"\n | \"console\"\n | \"custom\"\n | \"info\";\n\nexport type BreadcrumbLevel = \"debug\" | \"info\" | \"warning\" | \"error\";\n\nexport interface Breadcrumb {\n /** epoch ms */\n timestamp: number;\n category: BreadcrumbCategory;\n level?: BreadcrumbLevel;\n /** Short human-readable description. */\n message?: string;\n /** Arbitrary key/value context for the crumb. */\n data?: Record<string, unknown>;\n}\n\nexport class BreadcrumbBuffer {\n private items: Breadcrumb[] = [];\n constructor(private readonly maxSize: number = 50) {}\n\n add(crumb: Breadcrumb): void {\n this.items.push(crumb);\n if (this.items.length > this.maxSize) {\n this.items.shift();\n }\n }\n\n /** Defensive copy — caller can read freely without mutating buffer state. */\n snapshot(): Breadcrumb[] {\n return this.items.slice();\n }\n\n clear(): void {\n this.items = [];\n }\n\n get size(): number {\n return this.items.length;\n }\n}\n","/**\n * _diagnostic-telemetry.ts (React Native SDK)\n *\n * Single-fire reliability telemetry for the SDK. Carries the\n * `crossdeck.contract_failed` event ONE WAY to the Crossdeck\n * reliability endpoint — NEVER the customer's appId, NEVER the\n * customer's track() pipeline, NEVER visible in the customer's\n * dashboard.\n *\n * Why this exists\n * ───────────────────────────────────────────────────────────────────\n * Crossdeck is an independent controller for SDK Diagnostic\n * Telemetry (Privacy Policy §6, \"Flow B\"). The legitimate-interest\n * basis depends on the payload remaining diagnostic-only: no\n * end-user identifiers, no free-form text, no stack frames. The\n * schema-lock contract at\n * `contracts/diagnostics/contract-failed-payload-schema-lock.json`\n * fixes the wire shape; this module is the call site that has to\n * honour it.\n *\n * Why bypass the existing HttpClient\n * ───────────────────────────────────────────────────────────────────\n * The HttpClient is configured for the customer's project (their\n * API key, their endpoint). Routing reliability telemetry through\n * it would (a) bill against the customer's event quota and (b)\n * show individual contract failures in their dashboard, which is\n * neither the customer's nor Crossdeck's intent. A separate one-way\n * path is the structural guarantee.\n *\n * PROVISIONING NOTE\n * ───────────────────────────────────────────────────────────────────\n * The reliability endpoint URL + publishable key below are LITERAL\n * CONSTANTS shipped in the SDK. Until the reliability project is\n * minted, the placeholder values disable telemetry — the function\n * returns early without making a request. After provisioning, swap\n * the placeholders for the real values; the same values go into the\n * backend at backend/src/api/v1-sdk-diagnostic.ts.\n */\n\nimport { SDK_NAME, SDK_VERSION } from \"./_version\";\n\n/** Reliability endpoint URL. Hardcoded — never read from config. */\nexport const DIAGNOSTIC_TELEMETRY_ENDPOINT =\n \"https://api.cross-deck.com/v1/sdk/diagnostic\";\n\n/** Reliability project's publishable key. Hardcoded constant. */\nexport const DIAGNOSTIC_TELEMETRY_PUBLISHABLE_KEY =\n \"cd_pub_RELIABILITY_PLACEHOLDER_TO_BE_PROVISIONED\";\n\n/**\n * Whether the telemetry is enabled. Disabled while the reliability\n * project is unprovisioned (placeholder key in place).\n */\nexport function isDiagnosticTelemetryEnabled(): boolean {\n return !DIAGNOSTIC_TELEMETRY_PUBLISHABLE_KEY.startsWith(\n \"cd_pub_RELIABILITY_PLACEHOLDER\",\n );\n}\n\n/**\n * The exhaustive set of fields the payload may contain — mirrors the\n * schema-lock contract.\n */\nexport const DIAGNOSTIC_TELEMETRY_ALLOWED_KEYS: ReadonlySet<string> = new Set([\n \"contract_id\",\n \"sdk_version\",\n \"sdk_platform\",\n \"failure_reason\",\n \"run_context\",\n \"run_id\",\n \"test_file\",\n \"test_name\",\n \"device_class\",\n]);\n\n/**\n * Fire-and-forget POST to the reliability endpoint. Returns\n * immediately. Never throws — failures are silently dropped so the\n * customer's app is not affected by reliability-endpoint\n * availability.\n *\n * React Native ships fetch from a global polyfill (whatwg-fetch on\n * Android, NSURLSession bridge on iOS). We never need a different\n * code path between the platforms — the only requirement is that\n * fetch exists; if it doesn't (custom runtime, jest without polyfill)\n * we silently drop.\n *\n * @param payload key/value map of payload fields. Keys not in\n * {@link DIAGNOSTIC_TELEMETRY_ALLOWED_KEYS} are dropped before\n * serialisation.\n */\nexport function filterDiagnosticPayload(\n payload: Record<string, string>,\n): Record<string, string> {\n const filtered: Record<string, string> = {};\n for (const [k, v] of Object.entries(payload)) {\n if (DIAGNOSTIC_TELEMETRY_ALLOWED_KEYS.has(k) && typeof v === \"string\") {\n filtered[k] = v;\n }\n }\n return filtered;\n}\n\nexport function sendDiagnosticTelemetry(\n payload: Record<string, string>,\n): void {\n if (!isDiagnosticTelemetryEnabled()) return;\n const filtered = filterDiagnosticPayload(payload);\n if (Object.keys(filtered).length === 0) return;\n\n const body = JSON.stringify(filtered);\n const f = (globalThis as { fetch?: typeof fetch }).fetch;\n if (typeof f !== \"function\") return;\n\n try {\n void f(DIAGNOSTIC_TELEMETRY_ENDPOINT, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${DIAGNOSTIC_TELEMETRY_PUBLISHABLE_KEY}`,\n \"Crossdeck-Sdk-Version\": `${SDK_NAME}@${SDK_VERSION}`,\n },\n body,\n }).catch(() => {\n // Fire-and-forget; never propagate a rejection.\n });\n } catch {\n // Swallow synchronous throws.\n }\n}\n","/**\n * Stack-trace parser — normalises Hermes / JSC / V8 stack strings\n * into a common frame shape.\n *\n * Why hand-rolled, not stack-trace-js or error-stack-parser libraries:\n * those weigh 5–15 KB after minification and we'd be pulling in their\n * full feature matrix just for the parser. The patterns below cover\n * the four shapes any modern JS engine emits, totalling ~80 lines.\n *\n * The output frame shape mirrors what Sentry's `mechanism: { type:\n * 'generic' }` events ship, so future source-map symbolication on the\n * Crossdeck backend has a stable input to work against.\n *\n * Defensive: never throws. An unparseable line becomes a `raw` frame\n * with just the literal text. Engineers reading errors still get the\n * raw stack as fallback.\n */\n\nexport interface StackFrame {\n /** Function name, or \"?\" if anonymous / unparseable. */\n function: string;\n /** Source file URL the frame ran in. Empty when unknown. */\n filename: string;\n /** 1-indexed line number, or 0 when unknown. */\n lineno: number;\n /** 1-indexed column number, or 0 when unknown. */\n colno: number;\n /**\n * True when the frame is in the app's own code (best-effort:\n * detected by filename not being a known third-party path).\n * Helps the dashboard's \"your code vs library code\" view.\n */\n in_app: boolean;\n /** Raw line from the stack string for debugging when parse fails. */\n raw: string;\n}\n\n/**\n * Parse a stack string into an array of frames. Returns an empty\n * array when the input is unparseable — caller should always treat\n * the original `error.stack` as the source of truth for display.\n */\nexport function parseStack(stack: string | undefined | null): StackFrame[] {\n if (!stack || typeof stack !== \"string\") return [];\n const lines = stack.split(\"\\n\");\n const frames: StackFrame[] = [];\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n const frame = parseLine(trimmed);\n if (frame) frames.push(frame);\n }\n return frames;\n}\n\nfunction parseLine(line: string): StackFrame | null {\n // Hermes / V8 — with parens\n // Example: at handleClick (/path/to/app.bundle:42:18)\n // at Object.handleClick (.../app.bundle?platform=ios:42:18)\n let m = /^at\\s+(.+?)\\s+\\((.+?):(\\d+):(\\d+)\\)$/.exec(line);\n if (m) {\n return buildFrame({\n function: m[1]!,\n filename: m[2]!,\n lineno: parseInt(m[3]!, 10),\n colno: parseInt(m[4]!, 10),\n raw: line,\n });\n }\n\n // V8 — anonymous, no parens\n // Example: at /path/to/app.bundle:42:18\n m = /^at\\s+(.+?):(\\d+):(\\d+)$/.exec(line);\n if (m) {\n return buildFrame({\n function: \"?\",\n filename: m[1]!,\n lineno: parseInt(m[2]!, 10),\n colno: parseInt(m[3]!, 10),\n raw: line,\n });\n }\n\n // Hermes legacy / JSC — @-separator\n // Example: handleClick@/path/to/app.bundle:42:18\n m = /^(.*?)@(.+?):(\\d+):(\\d+)$/.exec(line);\n if (m) {\n return buildFrame({\n function: m[1]! || \"?\",\n filename: m[2]!,\n lineno: parseInt(m[3]!, 10),\n colno: parseInt(m[4]!, 10),\n raw: line,\n });\n }\n\n // Header line (e.g. \"TypeError: foo is not a function\") — return\n // null so caller skips it.\n if (/^\\w*Error/.test(line) || !line.includes(\":\")) {\n return null;\n }\n\n // Unparseable but plausibly a frame — keep it as raw.\n return {\n function: \"?\",\n filename: \"\",\n lineno: 0,\n colno: 0,\n in_app: true,\n raw: line,\n };\n}\n\nfunction buildFrame(input: {\n function: string;\n filename: string;\n lineno: number;\n colno: number;\n raw: string;\n}): StackFrame {\n return {\n function: input.function || \"?\",\n filename: input.filename,\n lineno: Number.isFinite(input.lineno) ? input.lineno : 0,\n colno: Number.isFinite(input.colno) ? input.colno : 0,\n in_app: isInAppFrame(input.filename),\n raw: input.raw,\n };\n}\n\n/**\n * Best-effort \"is this frame in the app's own code or a third-party\n * source we should de-emphasise in the UI\".\n *\n * Out-of-app heuristics in the RN context: the React Native runtime\n * itself (node_modules/react-native/*), the SDK's own module, and\n * any vendored polyfill (Hermes intl, etc.).\n */\nfunction isInAppFrame(filename: string): boolean {\n if (!filename) return true;\n if (/\\/node_modules\\/react-native\\//.test(filename)) return false;\n if (/\\bInitializeCore\\.js$/.test(filename)) return false;\n if (/\\b@cross-deck\\/react-native\\b/.test(filename)) return false;\n if (/\\/node_modules\\/@react-native\\//.test(filename)) return false;\n if (/\\/node_modules\\/expo\\//.test(filename)) return false;\n return true;\n}\n\n/**\n * Fingerprint an error for grouping. SHA-flavoured — we don't need\n * cryptographic strength, we need \"two errors with the same call\n * site produce the same key\". The Crossdeck backend may refine the\n * grouping further once source maps are uploaded.\n *\n * Input: the message + the first ≤3 in-app frames. When no frames\n * are available (cross-origin script error, non-Error throws,\n * unhandledrejection of a primitive), the optional `location`\n * fallback contributes filename:lineno:colno so otherwise-identical\n * \"Unknown error\" events from different call sites stay separate.\n *\n * Output: a short hex string usable as a Firestore doc id segment.\n */\nexport function fingerprintError(\n message: string,\n frames: StackFrame[],\n location?: {\n filename?: string | null;\n lineno?: number | null;\n colno?: number | null;\n errorType?: string | null;\n } | null,\n): string {\n const inAppFrames = frames.filter((f) => f.in_app).slice(0, 3);\n const parts = [\n (message || \"\").slice(0, 200),\n ...inAppFrames.map((f) => `${f.function}@${f.filename}:${f.lineno}`),\n ];\n if (inAppFrames.length === 0 && location) {\n const loc = [\n location.errorType ?? \"\",\n location.filename ?? \"\",\n location.lineno ?? \"\",\n location.colno ?? \"\",\n ].join(\":\");\n if (loc !== \":::\") parts.push(loc);\n }\n return djb2Hex(parts.join(\"|\"));\n}\n\n/**\n * djb2 — small, fast non-cryptographic string hash. 32-bit output\n * encoded as 8-char hex. Stable across runtimes; deterministic.\n */\nfunction djb2Hex(input: string): string {\n let h = 5381;\n for (let i = 0; i < input.length; i++) {\n h = ((h << 5) + h + input.charCodeAt(i)) | 0;\n }\n return (h >>> 0).toString(16).padStart(8, \"0\");\n}\n","/**\n * Error capture for React Native.\n *\n * Catches every error source the RN runtime can hand us and ships\n * them as Crossdeck events. The pipeline reuses the analytics queue:\n * - Same durable persistence (errors survive backgrounding /\n * low-memory teardown)\n * - Same exponential backoff (a flapping server doesn't flood\n * errors past the rate limit)\n * - Same Idempotency-Key (duplicate batches dedup server-side)\n * - Same consent gate (`consent.errors`)\n * - Same PII scrub on properties before they leave\n *\n * Error sources captured (each toggleable):\n * 1. `ErrorUtils.setGlobalHandler` — uncaught synchronous +\n * unhandled promise rejections (RN wires both through the\n * same global handler since RN 0.63+; older versions ship a\n * separate `HermesInternal.enablePromiseRejectionTracker`).\n * 2. `globalThis.fetch` wrap — HTTP errors the app code didn't\n * catch. 5xx + network failures fire.\n * 3. `Crossdeck.captureError(err)` — manual API for try/catch\n * blocks.\n * 4. `Crossdeck.captureMessage(msg)` — non-error events you want\n * to surface as issues.\n *\n * Defensive design rules:\n * - The error handler must NEVER throw — if our own code crashes\n * while reporting an error, we'd take down the host app's\n * error handler too. Every callback is wrapped in try/swallow.\n * - Recursion guard: a `_reporting` flag prevents the SDK from\n * reporting its own errors recursively forever.\n * - Rate limited per-fingerprint: max N reports per minute to\n * defend against runaway loops (e.g. an error in setInterval).\n * - Self-skip: requests to the configured `selfHostname` (derived\n * from `baseUrl`) never trigger captureHttp — otherwise a\n * Crossdeck-side outage would recurse (captureHttp → enqueue\n * → /events → fail → captureHttp → ∞).\n */\n\nimport {\n parseStack,\n fingerprintError,\n type StackFrame,\n} from \"./stack-parser\";\nimport type { BreadcrumbBuffer, Breadcrumb } from \"./breadcrumbs\";\nimport { isSelfRequest } from \"./http\";\n\nexport type ErrorLevel = \"error\" | \"warning\" | \"info\";\n\nexport interface CapturedError {\n /** When the error fired (epoch ms). */\n timestamp: number;\n kind:\n | \"error.unhandled\"\n | \"error.unhandledrejection\"\n | \"error.handled\"\n | \"error.message\"\n | \"error.http\";\n level: ErrorLevel;\n message: string;\n errorType: string | null;\n frames: StackFrame[];\n rawStack: string | null;\n filename: string | null;\n lineno: number | null;\n colno: number | null;\n fingerprint: string;\n breadcrumbs: Breadcrumb[];\n context: Record<string, unknown>;\n tags: Record<string, string>;\n http?: {\n url: string;\n method: string;\n status: number;\n statusText?: string;\n };\n}\n\nexport interface ErrorCaptureConfig {\n enabled: boolean;\n /** Catch ErrorUtils.setGlobalHandler. Default true. */\n globalHandler: boolean;\n /** Wrap globalThis.fetch to capture 5xx + network failures. Default true. */\n wrapFetch: boolean;\n /** Drop errors matching these substrings or regexes. */\n ignoreErrors: Array<string | RegExp>;\n /** Sample rate, 0–1. 1.0 = send every error. */\n sampleRate: number;\n /** Maximum errors per fingerprint per minute. Default 5. */\n maxPerFingerprintPerMinute: number;\n /** Total cap per session, regardless of fingerprint. Default 100. */\n maxPerSession: number;\n}\n\nexport const DEFAULT_ERROR_CAPTURE: ErrorCaptureConfig = {\n enabled: true,\n globalHandler: true,\n wrapFetch: true,\n ignoreErrors: [\n // Hermes promise-rejection wrapper boilerplate that's never\n // actionable for the app developer.\n \"Possible Unhandled Promise Rejection (id: 0)\",\n ],\n sampleRate: 1.0,\n maxPerFingerprintPerMinute: 5,\n maxPerSession: 100,\n};\n\nexport interface ErrorTrackerOptions {\n config: ErrorCaptureConfig;\n breadcrumbs: BreadcrumbBuffer;\n /** Called with each captured error. Forwards into the event queue. */\n report: (err: CapturedError) => void;\n /** Called to read the current developer-supplied context bag. */\n getContext: () => Record<string, unknown>;\n /** Called to read the current developer-supplied tag bag. */\n getTags: () => Record<string, string>;\n /**\n * Pre-send hook GETTER. The tracker invokes this on EVERY captured\n * error to resolve the current hook reference, then calls the\n * resolved function with the error (returning `null` to drop, or\n * a modified `CapturedError` to forward).\n *\n * Getter shape — not a static function — so\n * `setErrorBeforeSend()` can install or replace the hook after\n * `init()` without re-creating the tracker. Returning `null` from\n * the GETTER means \"no hook configured\" and the report goes\n * through unmodified.\n */\n beforeSend?: () => ((err: CapturedError) => CapturedError | null) | null;\n /**\n * Whether the consent dimension `errors` is currently granted.\n * Checked at capture time so a flip via Crossdeck.consent() takes\n * effect immediately.\n */\n isConsented: () => boolean;\n /**\n * The SDK's own backend hostname (derived from\n * `CrossdeckOptions.baseUrl` at construction time). Used to skip\n * captureHttp for our own requests. Null / omitted when extraction\n * fails (malformed URL) OR when the test harness doesn't supply\n * one — the tracker falls through to \"capture everything\" rather\n * than swallow.\n */\n selfHostname?: string | null;\n}\n\nexport class ErrorTracker {\n private installed = false;\n private cleanups: Array<() => void> = [];\n private _reporting = false;\n private sessionCount = 0;\n private fingerprintWindow = new Map<string, number[]>();\n\n constructor(private readonly opts: ErrorTrackerOptions) {}\n\n install(): void {\n if (this.installed) return;\n if (!this.opts.config.enabled) return;\n\n if (this.opts.config.globalHandler) this.installGlobalHandler();\n if (this.opts.config.wrapFetch) this.installFetchWrap();\n\n this.installed = true;\n }\n\n uninstall(): void {\n for (const fn of this.cleanups.splice(0)) {\n try {\n fn();\n } catch {\n // ignore\n }\n }\n this.installed = false;\n }\n\n /**\n * Manual API. Either an Error instance or any unknown value (we\n * coerce). Returns silently — never throws.\n */\n captureError(\n error: unknown,\n options?: {\n context?: Record<string, unknown>;\n tags?: Record<string, string>;\n level?: ErrorLevel;\n },\n ): void {\n if (!this.opts.isConsented()) return;\n try {\n const captured = this.buildFromUnknown(\n error,\n \"error.handled\",\n options?.level ?? \"error\",\n );\n if (options?.context)\n captured.context = { ...captured.context, ...options.context };\n if (options?.tags) captured.tags = { ...captured.tags, ...options.tags };\n this.maybeReport(captured);\n } catch {\n // self-protection — never let our own code crash the caller.\n }\n }\n\n /**\n * Capture a non-error event as an issue. For \"we hit a\n * soft-warning code path\" / \"deprecated API used\" kinds of\n * signals. Pairs with Sentry's captureMessage().\n */\n captureMessage(message: string, level: ErrorLevel = \"info\"): void {\n if (!this.opts.isConsented()) return;\n try {\n const captured: CapturedError = {\n timestamp: Date.now(),\n kind: \"error.message\",\n level,\n message,\n errorType: null,\n frames: [],\n rawStack: null,\n filename: null,\n lineno: null,\n colno: null,\n fingerprint: fingerprintError(message, []),\n breadcrumbs: this.opts.breadcrumbs.snapshot(),\n context: this.opts.getContext(),\n tags: this.opts.getTags(),\n };\n this.maybeReport(captured);\n } catch {\n // swallow\n }\n }\n\n // ============================================================\n // Listener installation\n // ============================================================\n\n private installGlobalHandler(): void {\n // RN's `ErrorUtils` is a global polyfill that wraps every JS\n // execution context. `setGlobalHandler` lets us chain in front\n // of the default handler (RN's red-box developer overlay) so\n // we get every uncaught error AND the dev experience stays\n // intact.\n const g = globalThis as unknown as {\n ErrorUtils?: {\n getGlobalHandler?: () => (error: Error, isFatal?: boolean) => void;\n setGlobalHandler?: (\n handler: (error: Error, isFatal?: boolean) => void,\n ) => void;\n };\n };\n const ErrorUtils = g.ErrorUtils;\n if (!ErrorUtils?.setGlobalHandler || !ErrorUtils?.getGlobalHandler) return;\n\n const prior = ErrorUtils.getGlobalHandler();\n const handler = (error: Error, isFatal?: boolean): void => {\n if (!this._reporting && this.opts.isConsented()) {\n try {\n this._reporting = true;\n const captured = this.buildFromUnknown(\n error,\n \"error.unhandled\",\n isFatal ? \"error\" : \"warning\",\n );\n this.maybeReport(captured);\n } catch {\n // swallow\n } finally {\n this._reporting = false;\n }\n }\n // Always defer to the prior handler so RN's red-box / OS\n // crash reporter still fires. We're additive, not\n // replacement.\n if (prior) {\n try {\n prior(error, isFatal);\n } catch {\n // swallow\n }\n }\n };\n\n ErrorUtils.setGlobalHandler(handler);\n this.cleanups.push(() => {\n // Best-effort restore — if a later library wrapped us, leave\n // their wrapper in place (matches web's fetch-wrap policy).\n if (prior) ErrorUtils.setGlobalHandler!(prior);\n });\n }\n\n /**\n * Wrap globalThis.fetch so failed HTTP requests get\n * auto-captured. We do NOT call 4xx an \"error\" (those are often\n * expected — auth required, validation failed). Only 5xx +\n * network failures fire.\n */\n private installFetchWrap(): void {\n const origFetch = globalThis.fetch;\n if (typeof origFetch !== \"function\") return;\n const tracker = this;\n const wrapped: typeof fetch = async (\n ...args: Parameters<typeof fetch>\n ): Promise<Response> => {\n const input = args[0];\n const init = args[1] ?? {};\n const url =\n typeof input === \"string\" ? input : (input as Request)?.url ?? \"\";\n const method = (init.method || \"GET\").toUpperCase();\n const start = Date.now();\n\n // Skip self-requests for breadcrumbs too — an error report's\n // crumb trail showing \"POST https://api.cross-deck.com/v1/events\"\n // entries is noise the engineer doesn't care about.\n if (!isSelfRequest(url, tracker.opts.selfHostname)) {\n tracker.opts.breadcrumbs.add({\n timestamp: start,\n category: \"http\",\n message: `${method} ${url}`,\n data: { url, method },\n });\n }\n\n try {\n const response = await origFetch(...args);\n if (response.status >= 500 && tracker.opts.isConsented()) {\n if (!isSelfRequest(url, tracker.opts.selfHostname)) {\n tracker.captureHttp({\n url,\n method,\n status: response.status,\n statusText: response.statusText,\n });\n }\n }\n return response;\n } catch (err) {\n // Genuine network failure (DNS, connection refused).\n if (\n tracker.opts.isConsented() &&\n !isSelfRequest(url, tracker.opts.selfHostname)\n ) {\n tracker.captureHttp({\n url,\n method,\n status: 0,\n statusText: err instanceof Error ? err.message : \"network error\",\n });\n }\n throw err;\n }\n };\n globalThis.fetch = wrapped;\n this.cleanups.push(() => {\n // Only restore if we're still the active wrapper. If another\n // observability tool installed AFTER us, leave their wrapper\n // in place.\n if (globalThis.fetch === wrapped) globalThis.fetch = origFetch;\n });\n }\n\n // ============================================================\n // Build + report\n // ============================================================\n\n private buildFromUnknown(\n value: unknown,\n kind: CapturedError[\"kind\"],\n level: ErrorLevel,\n ): CapturedError {\n const coerced = coerceErrorPayload(value);\n const isErrorInstance = value instanceof Error;\n const rawStack = isErrorInstance ? (value as Error).stack ?? null : null;\n const frames = parseStack(rawStack);\n const context = this.opts.getContext();\n if (coerced.extras) {\n context.__error_extras = coerced.extras;\n }\n return {\n timestamp: Date.now(),\n kind,\n level,\n message: coerced.message,\n errorType: coerced.errorType,\n frames,\n rawStack,\n filename: frames[0]?.filename ?? null,\n lineno: frames[0]?.lineno ?? null,\n colno: frames[0]?.colno ?? null,\n fingerprint: fingerprintError(coerced.message, frames, {\n errorType: coerced.errorType,\n }),\n breadcrumbs: this.opts.breadcrumbs.snapshot(),\n context,\n tags: this.opts.getTags(),\n };\n }\n\n private captureHttp(info: {\n url: string;\n method: string;\n status: number;\n statusText?: string;\n }): void {\n try {\n const message = `HTTP ${info.status} ${info.method} ${info.url}`;\n const captured: CapturedError = {\n timestamp: Date.now(),\n kind: \"error.http\",\n level: \"error\",\n message,\n errorType: \"HTTPError\",\n frames: [],\n rawStack: null,\n filename: info.url,\n lineno: null,\n colno: null,\n fingerprint: fingerprintError(\n `HTTP ${info.status} ${info.method}`,\n [],\n { filename: info.url, errorType: \"HTTPError\" },\n ),\n breadcrumbs: this.opts.breadcrumbs.snapshot(),\n context: this.opts.getContext(),\n tags: this.opts.getTags(),\n http: info,\n };\n this.maybeReport(captured);\n } catch {\n // swallow\n }\n }\n\n // ============================================================\n // Reporting pipeline — filter / sample / rate-limit / send\n // ============================================================\n\n private maybeReport(err: CapturedError): void {\n if (this.sessionCount >= this.opts.config.maxPerSession) return;\n if (this.shouldIgnore(err)) return;\n if (!this.passesSample(err)) return;\n if (!this.passesRateLimit(err)) return;\n\n // beforeSend hook — last chance to scrub or drop. Resolve the\n // current hook through the getter on every call so a hook\n // installed via `setErrorBeforeSend()` AFTER init() takes effect\n // on THIS error, not just future ones.\n let finalErr: CapturedError | null = err;\n const hook = this.opts.beforeSend?.();\n if (hook) {\n try {\n finalErr = hook(err);\n } catch {\n // A buggy beforeSend hook must NOT swallow the error\n // report. Fall back to the original.\n finalErr = err;\n }\n if (!finalErr) return;\n }\n\n this.sessionCount += 1;\n try {\n this.opts.report(finalErr);\n } catch {\n // swallow — report() failure is best-effort.\n }\n }\n\n private shouldIgnore(err: CapturedError): boolean {\n for (const pat of this.opts.config.ignoreErrors) {\n if (typeof pat === \"string\" && err.message.includes(pat)) return true;\n if (pat instanceof RegExp && pat.test(err.message)) return true;\n }\n return false;\n }\n\n private passesSample(err: CapturedError): boolean {\n if (this.opts.config.sampleRate >= 1) return true;\n if (this.opts.config.sampleRate <= 0) return false;\n // Deterministic per-fingerprint sampling — a given user always\n // either always sends a given error or never does, no flapping.\n const hashByte = parseInt(err.fingerprint.slice(0, 2), 16);\n return hashByte / 255 < this.opts.config.sampleRate;\n }\n\n private passesRateLimit(err: CapturedError): boolean {\n const windowMs = 60_000;\n const now = Date.now();\n const max = this.opts.config.maxPerFingerprintPerMinute;\n const arr = this.fingerprintWindow.get(err.fingerprint) ?? [];\n const fresh = arr.filter((t) => now - t < windowMs);\n if (fresh.length >= max) {\n this.fingerprintWindow.set(err.fingerprint, fresh);\n return false;\n }\n fresh.push(now);\n this.fingerprintWindow.set(err.fingerprint, fresh);\n return true;\n }\n}\n\n// ============================================================\n// Unknown-value coercion\n// ============================================================\n\ninterface CoercedPayload {\n message: string;\n errorType: string | null;\n extras: Record<string, unknown> | null;\n}\n\nfunction coerceErrorPayload(v: unknown): CoercedPayload {\n if (v === null) return { message: \"(thrown: null)\", errorType: null, extras: null };\n if (v === undefined) return { message: \"(thrown: undefined)\", errorType: null, extras: null };\n if (typeof v === \"string\") return { message: v, errorType: null, extras: null };\n if (typeof v === \"number\" || typeof v === \"boolean\" || typeof v === \"bigint\") {\n return { message: String(v), errorType: typeof v, extras: null };\n }\n if (typeof v === \"symbol\") {\n return { message: v.toString(), errorType: \"symbol\", extras: null };\n }\n if (typeof v === \"function\") {\n return {\n message: `(thrown function: ${v.name || \"anonymous\"})`,\n errorType: \"function\",\n extras: null,\n };\n }\n\n if (v instanceof Error) {\n const errorType = v.name || v.constructor?.name || \"Error\";\n const message =\n typeof v.message === \"string\" && v.message.length > 0\n ? v.message\n : errorType;\n const extras: Record<string, unknown> = {};\n for (const key of [\"code\", \"status\", \"statusCode\", \"errno\", \"cause\"] as const) {\n const val = (v as unknown as Record<string, unknown>)[key];\n if (val !== undefined && typeof val !== \"function\") {\n extras[key] = safeClone(val);\n }\n }\n for (const key of Object.keys(v)) {\n if (key === \"message\" || key === \"stack\" || key === \"name\" || key === \"cause\") continue;\n if (key in extras) continue;\n const val = (v as unknown as Record<string, unknown>)[key];\n if (typeof val === \"function\") continue;\n extras[key] = safeClone(val);\n }\n return {\n message,\n errorType,\n extras: Object.keys(extras).length > 0 ? extras : null,\n };\n }\n\n // Plain object — try JSON, fall back to \"[Object]\".\n try {\n const s = JSON.stringify(v);\n return {\n message: s && s.length < 200 ? s : \"[Object]\",\n errorType: (v as { constructor?: { name?: string } })?.constructor?.name ?? \"Object\",\n extras: null,\n };\n } catch {\n return { message: \"[Object]\", errorType: \"Object\", extras: null };\n }\n}\n\nfunction safeClone(v: unknown): unknown {\n if (v == null) return v;\n const t = typeof v;\n if (t === \"string\" || t === \"number\" || t === \"boolean\") return v;\n if (t === \"bigint\") return String(v);\n try {\n const s = JSON.stringify(v);\n return s === undefined ? String(v) : JSON.parse(s);\n } catch {\n return String(v);\n }\n}\n","/**\n * Public API surface for @cross-deck/react-native.\n *\n * Usage:\n *\n * import { Crossdeck } from \"@cross-deck/react-native\";\n *\n * Crossdeck.init({\n * appId: \"app_rn_xxx\",\n * publicKey: \"cd_pub_live_…\",\n * environment: \"production\",\n * });\n *\n * await Crossdeck.identify(\"user_847\");\n * const ents = await Crossdeck.getEntitlements();\n * if (Crossdeck.isEntitled(\"pro\")) {\n * showPro();\n * }\n * Crossdeck.track(\"paywall_shown\", { variant: \"v3\" });\n *\n * Lifecycle:\n *\n * - `init()` returns void but kicks off async hydration (identity,\n * super-props, entitlement cache, persisted event queue) in the\n * background. The returned `ready` promise is awaited internally\n * by every async method (`identify`, `track`, `flush`,\n * `getEntitlements`, etc.) so callers don't need to babysit it.\n * - Sync methods (`isEntitled`, `getSuperProperties`,\n * `diagnostics`) read in-memory state. Until `init()` has fired\n * they return sensible empties (false, {}, the \"not-started\"\n * diagnostics shape).\n * - `reset()` is fully sync; identity/storage wipe fans out\n * fire-and-forget to AsyncStorage.\n */\n\nimport { CrossdeckError } from \"./errors\";\nimport {\n HttpClient,\n DEFAULT_BASE_URL,\n SDK_NAME,\n SDK_VERSION,\n extractSelfHostname,\n} from \"./http\";\nimport { IdentityStore, randomChars } from \"./identity\";\nimport { EntitlementCache, type EntitlementsListener } from \"./entitlement-cache\";\nimport { deriveIdempotencyKeyForPurchase } from \"./idempotency-key\";\nimport { EventQueue, type QueuedEvent } from \"./event-queue\";\nimport { PersistentEventStore } from \"./event-storage\";\nimport { detectDefaultStorage, MemoryStorage } from \"./storage\";\nimport { collectDeviceInfo, type DeviceInfo } from \"./device-info\";\nimport { ConsoleDebugLogger, findSensitivePropertyKeys, type DebugLogger } from \"./debug\";\nimport { validateEventProperties } from \"./event-validation\";\nimport { SuperPropertyStore } from \"./super-properties\";\nimport { ConsentManager, scrubPiiFromProperties, type ConsentState } from \"./consent\";\nimport { BreadcrumbBuffer, type Breadcrumb } from \"./breadcrumbs\";\nimport type { ContractFailureInput } from \"./contracts\";\nimport { sendDiagnosticTelemetry } from \"./_diagnostic-telemetry\";\nimport {\n DEFAULT_ERROR_CAPTURE,\n ErrorTracker,\n type CapturedError,\n type ErrorCaptureConfig,\n type ErrorLevel,\n} from \"./error-capture\";\nimport type {\n AliasResult,\n CrossdeckOptions,\n Diagnostics,\n EntitlementsListResponse,\n Environment,\n EventProperties,\n GroupTraits,\n HeartbeatResponse,\n IdentifyOptions,\n PublicEntitlement,\n PurchaseResult,\n Platform,\n} from \"./types\";\n\n/**\n * Snapshot of call-time-volatile state captured at `track()` entry\n * and threaded through `trackPostHydration()`. Without this, the\n * post-hydration body would read state mutated AFTER the caller's\n * track() returned — see the comment on `track()` for the racing\n * pattern. Currently scoped to `sessionId` (the only volatile axis\n * the v1.4.0 contract tests pin); add more fields here as the\n * enrichment layer grows.\n */\ninterface TrackCallSnapshot {\n sessionId: string | null;\n}\n\ninterface InternalState {\n http: HttpClient;\n identity: IdentityStore;\n entitlements: EntitlementCache;\n events: EventQueue;\n errors: ErrorTracker | null;\n breadcrumbs: BreadcrumbBuffer;\n errorContext: Record<string, unknown>;\n errorTags: Record<string, string>;\n errorBeforeSend: ((err: CapturedError) => CapturedError | null) | null;\n superProps: SuperPropertyStore;\n consent: ConsentManager;\n scrubPii: boolean;\n deviceInfo: DeviceInfo;\n options: Required<\n Omit<\n CrossdeckOptions,\n \"storage\" | \"sdkVersion\" | \"appVersion\" | \"debug\" | \"scrubPii\" | \"errorCapture\"\n >\n > & {\n sdkVersion: string;\n appVersion: string | null;\n };\n debug: DebugLogger;\n developerUserId: string | null;\n /** v1.4.0 Phase 3.4 — currently-active session id (set by the\n * host via setSessionId(...)). Attached to every track event so\n * cross-platform funnel queries reconcile with web SDK sessions. */\n sessionId: string | null;\n lastServerTime: number | null;\n lastClientTime: number | null;\n /** Promise that resolves when async hydration completes. */\n ready: Promise<void>;\n /** True once init() has fully returned (synchronous portion done). */\n started: boolean;\n /** True once the async hydration in `ready` has completed. */\n hydrated: boolean;\n /**\n * AppState subscription handle so re-init / teardown can detach\n * the listener cleanly. RN apps that hot-reload would otherwise\n * pile up duplicate handlers each module reload.\n */\n appStateSubscription: { remove: () => void } | null;\n}\n\nexport class CrossdeckClient {\n private state: InternalState | null = null;\n\n /**\n * Boot the SDK. Returns void synchronously but kicks off async\n * hydration in the background. Callers can `await\n * Crossdeck.identify(...)` etc. directly — the SDK awaits its own\n * `ready` promise internally.\n *\n * Idempotent — calling init twice with the same options is a\n * no-op; calling with different options tears down the prior\n * tracker and replaces the configuration.\n */\n init(options: CrossdeckOptions): void {\n if (this.state) {\n // Re-init — tear down listeners (error tracker fetch wrap +\n // AppState subscription) before reconstructing. Otherwise\n // duplicate global handlers pile up on every hot-reload in\n // dev and on every test re-init.\n try {\n this.state.errors?.uninstall();\n } catch {\n /* ignore */\n }\n try {\n this.state.appStateSubscription?.remove();\n } catch {\n /* ignore */\n }\n // v1.4.0 Phase 5.5 — drain the prior EventQueue's pending\n // setTimeout BEFORE we replace this.state. Pre-fix the timer\n // would fire AFTER the state swap, firing against new\n // http/identity references with old-init events — a\n // cross-identity leak risk during HMR / config swap. flush()\n // cancels the timer (see EventQueue.cancelTimerIfSet) and\n // ships queued events out under the prior init's identity.\n //\n // CRITICAL: do NOT clear the persistent event store here.\n // The durable AsyncStorage queue belongs to the SDK lifetime,\n // not the init() lifetime — a survived crash mid-flush\n // re-hydrates on the next init.\n try {\n void this.state.events.flush();\n } catch {\n /* ignore */\n }\n }\n\n if (!options.publicKey || !options.publicKey.startsWith(\"cd_pub_\")) {\n throw new CrossdeckError({\n type: \"configuration_error\",\n code: \"invalid_public_key\",\n message: \"Crossdeck.init requires a publishable key starting with cd_pub_.\",\n });\n }\n if (!options.appId) {\n throw new CrossdeckError({\n type: \"configuration_error\",\n code: \"missing_app_id\",\n message:\n \"Crossdeck.init requires an appId. Find yours in the Crossdeck dashboard.\",\n });\n }\n if (options.environment !== \"production\" && options.environment !== \"sandbox\") {\n throw new CrossdeckError({\n type: \"configuration_error\",\n code: \"invalid_environment\",\n message: 'Crossdeck.init requires environment: \"production\" | \"sandbox\".',\n });\n }\n const keyEnv = inferEnvFromKey(options.publicKey);\n if (keyEnv && keyEnv !== options.environment) {\n throw new CrossdeckError({\n type: \"configuration_error\",\n code: \"environment_mismatch\",\n message: `Crossdeck.init: environment \"${options.environment}\" disagrees with key prefix (${keyEnv}). Reconcile your Crossdeck.init({ environment }) with the publishable key prefix.`,\n });\n }\n\n const storage = options.storage ?? detectDefaultStorage();\n const persistIdentity = options.persistIdentity ?? true;\n const opts: InternalState[\"options\"] = {\n appId: options.appId,\n publicKey: options.publicKey,\n environment: options.environment,\n baseUrl: options.baseUrl ?? DEFAULT_BASE_URL,\n persistIdentity,\n storagePrefix: options.storagePrefix ?? \"crossdeck:\",\n autoHeartbeat: options.autoHeartbeat ?? true,\n eventFlushBatchSize: options.eventFlushBatchSize ?? 20,\n // v1.4.0 Phase 3.3 — flush interval default parity at 2000ms\n // across every SDK. Per-instance override stays.\n eventFlushIntervalMs: options.eventFlushIntervalMs ?? 2000,\n sdkVersion: options.sdkVersion ?? SDK_VERSION,\n appVersion: options.appVersion ?? null,\n platform: options.platform ?? detectPlatform(),\n timeoutMs: options.timeoutMs ?? 15_000,\n // Per-platform identity claims for the bank-grade identity\n // lock. Empty string means \"not supplied\" — the HTTP layer\n // skips the header in that case and the backend will reject\n // with bundle_id_not_allowed / package_name_not_allowed at\n // first request if the project requires the lock.\n bundleId: options.bundleId ?? \"\",\n packageName: options.packageName ?? \"\",\n };\n\n const debug = new ConsoleDebugLogger();\n debug.enabled = options.debug === true;\n\n const http = new HttpClient({\n publicKey: opts.publicKey,\n baseUrl: opts.baseUrl,\n sdkVersion: opts.sdkVersion,\n timeoutMs: opts.timeoutMs,\n // Per-platform identity claims — sent as X-Crossdeck-Bundle-Id\n // / X-Crossdeck-Package-Name. Backend enforces these against\n // the app key's stored identity (bank-grade fail-closed).\n bundleId: options.bundleId,\n packageName: options.packageName,\n });\n\n // Identity continuity. When persistIdentity is off (typical\n // during a strict-consent flow before opt-in) we fall back to\n // in-memory only and write nothing to AsyncStorage.\n const effectiveStorage = persistIdentity ? storage : new MemoryStorage();\n const identity = new IdentityStore(effectiveStorage, opts.storagePrefix);\n const entitlements = new EntitlementCache(\n effectiveStorage,\n opts.storagePrefix + \"entitlements\",\n );\n const persistentEvents = persistIdentity\n ? new PersistentEventStore({ storage: effectiveStorage, prefix: opts.storagePrefix })\n : null;\n\n const events = new EventQueue({\n http,\n batchSize: opts.eventFlushBatchSize,\n intervalMs: opts.eventFlushIntervalMs,\n envelope: () => ({\n appId: opts.appId,\n environment: opts.environment,\n sdk: { name: SDK_NAME, version: opts.sdkVersion },\n }),\n persistentStore: persistentEvents ?? undefined,\n onFirstFlushSuccess: () => {\n debug.emit(\n \"sdk.first_event_sent\",\n \"First telemetry event received. View it in Live Events.\",\n { appId: opts.appId, environment: opts.environment },\n );\n },\n onRetryScheduled: (info) => {\n debug.emit(\n \"sdk.flush_retry_scheduled\",\n `Event flush failed (${info.lastError}). Retrying in ${info.delayMs}ms (attempt ${info.consecutiveFailures}).`,\n { ...info },\n );\n },\n onPermanentFailure: (info) => {\n // Bank-grade rule: a permanent 4xx that's dropping events\n // MUST be loud regardless of debug mode. Pre-fix the queue\n // retried 4xx forever silently and the customer never knew\n // their key was revoked.\n const headline = `[crossdeck] Event batch DROPPED (status ${info.status}): ${info.lastError}. ${info.droppedCount} event(s) lost — check your publishable key + app config.`;\n // eslint-disable-next-line no-console\n console.error(headline);\n debug.emit(\"sdk.flush_permanent_failure\", headline, { ...info });\n },\n });\n\n const deviceInfo: DeviceInfo = collectDeviceInfo({\n appVersion: opts.appVersion ?? undefined,\n });\n\n const superProps = new SuperPropertyStore(\n persistIdentity ? effectiveStorage : new MemoryStorage(),\n opts.storagePrefix,\n );\n\n const consent = new ConsentManager();\n const breadcrumbs = new BreadcrumbBuffer(50);\n\n this.state = {\n http,\n identity,\n entitlements,\n events,\n errors: null,\n breadcrumbs,\n errorContext: {},\n errorTags: {},\n errorBeforeSend: null,\n superProps,\n consent,\n scrubPii: options.scrubPii !== false,\n deviceInfo,\n options: opts,\n debug,\n developerUserId: null,\n sessionId: null,\n lastServerTime: null,\n lastClientTime: null,\n started: false,\n hydrated: false,\n ready: Promise.resolve(),\n appStateSubscription: null,\n };\n\n // Wire AppState observer for background persist + flush. When the\n // app moves out of `active` the SDK persists the buffer to\n // AsyncStorage immediately and triggers a best-effort flush\n // (Android gives ~tens of seconds before suspension; iOS gives a\n // few seconds — enough for a small batch). Without this, an RN\n // app that backgrounds during a buffered idle window loses every\n // buffered event when the OS later evicts the process.\n //\n // Mirrors the Web SDK's `pagehide` + `visibilitychange` wiring and\n // the Swift SDK's UIApplication.willResignActive observer.\n try {\n const RN = require(\"react-native\");\n const AppState = RN?.AppState;\n if (AppState && typeof AppState.addEventListener === \"function\") {\n const sub = AppState.addEventListener(\"change\", (next: string) => {\n if (next === \"background\" || next === \"inactive\") {\n // Both Android background + iOS inactive (e.g. app\n // switcher) get the same treatment — persist + try\n // to drain. Caller's flush() returns a Promise we\n // intentionally don't await; AppState callbacks run\n // synchronously and any unfinished flush continues in\n // the background-execution budget.\n try {\n // flush() persists the buffer to disk synchronously\n // (via the internal persistAll path) AND triggers a\n // best-effort network ship. We don't await — AppState\n // callbacks are synchronous; the ship continues in\n // whatever background-execution budget the OS allows.\n void this.state?.events.flush().catch(() => {\n /* permanent-failure callback handles error routing */\n });\n debug.emit(\"sdk.queue_persisted\", \"persisted on AppState background\");\n } catch {\n /* listener never crashes the app */\n }\n }\n });\n this.state.appStateSubscription = sub;\n }\n } catch {\n // react-native AppState unavailable — happens in JVM unit\n // tests, web-only build environments. SDK still functions;\n // just no auto-flush on background. Consumer can wire their\n // own AppState observer + call Crossdeck.flush() manually.\n }\n\n // Error capture — install BEFORE async hydration so an error\n // during boot still surfaces. consented gate keeps reports\n // gated on `consent.errors`.\n const wantErrorCapture = options.errorCapture !== false;\n if (wantErrorCapture) {\n const tracker = new ErrorTracker({\n config: { ...DEFAULT_ERROR_CAPTURE, enabled: true },\n breadcrumbs,\n report: (err) => this.reportError(err),\n getContext: () => ({ ...this.state!.errorContext }),\n getTags: () => ({ ...this.state!.errorTags }),\n beforeSend: () => this.state!.errorBeforeSend,\n isConsented: () => this.state!.consent.errors,\n selfHostname: extractSelfHostname(opts.baseUrl),\n });\n this.state.errors = tracker;\n tracker.install();\n }\n\n debug.emit(\n \"sdk.configured\",\n `Crossdeck connected to ${opts.appId} in ${opts.environment} mode.`,\n {\n appId: opts.appId,\n environment: opts.environment,\n sdkVersion: opts.sdkVersion,\n },\n );\n\n // Kick off async hydration. Every public async method awaits\n // `state.ready` before reading identity / cache / queue state\n // so the caller can `await Crossdeck.identify(...)` immediately\n // after `Crossdeck.init(...)` without manual sequencing.\n this.state.ready = (async () => {\n await Promise.all([\n identity.loadAll(),\n superProps.loadAll(),\n entitlements.hydrate(),\n events.hydrate(),\n ]);\n this.state!.hydrated = true;\n })();\n this.state.started = true;\n\n if (opts.autoHeartbeat) {\n // Fire-and-forget — heartbeat failure shouldn't block init().\n void this.state.ready.then(() => this.heartbeat()).catch(() => undefined);\n }\n }\n\n /**\n * Link the anonymous device to a developer-supplied user ID.\n * Caches the resolved Crossdeck customer for follow-up calls.\n *\n * Accepts an optional `traits` bag — profile data (name, plan,\n * signupDate, role) persisted on the Crossdeck customer record.\n */\n async identify(userId: string, options?: IdentifyOptions): Promise<AliasResult> {\n const s = this.requireStarted();\n if (!userId) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_user_id\",\n message: \"identify(userId) requires a non-empty userId.\",\n });\n }\n await s.ready;\n\n if (!s.consent.analytics) {\n s.debug.emit(\n \"sdk.consent_denied\",\n \"identify() skipped — consent denied for analytics.\",\n );\n return {\n object: \"alias_result\",\n crossdeckCustomerId: s.identity.crossdeckCustomerId ?? \"\",\n linked: [],\n mergePending: false,\n env: s.options.environment,\n };\n }\n\n const traitsValidation =\n options?.traits !== undefined\n ? validateEventProperties(options.traits)\n : null;\n const traits =\n traitsValidation && Object.keys(traitsValidation.properties).length > 0\n ? traitsValidation.properties\n : undefined;\n\n const body: Record<string, unknown> = {\n userId,\n anonymousId: s.identity.anonymousId,\n };\n if (options?.email) body.email = options.email;\n if (traits) body.traits = traits;\n\n // Bank-grade three-layer entitlement-cache isolation (v1.4.0\n // Phase 1.3). Switch the cache slot BEFORE the alias POST so a\n // mid-flight failure can't leave the cache pointing at the\n // prior user. setUserKey:\n // (a) hashes the new userId into a physically separate\n // AsyncStorage suffix — `crossdeck:entitlements:<sha256>`,\n // (b) unconditionally wipes the in-memory snapshot (no\n // conditional gating — every identify() guarantees a\n // fresh slot),\n // (c) rehydrates from the new slot so a returning user sees\n // their last-known-good immediately.\n await s.entitlements.setUserKey(userId);\n\n const result = await s.http.request<AliasResult>(\"POST\", \"/identity/alias\", {\n body,\n });\n s.identity.setCrossdeckCustomerId(result.crossdeckCustomerId);\n s.identity.setDeveloperUserId(userId);\n s.developerUserId = userId;\n return result;\n }\n\n /**\n * Register super-properties — Mixpanel pattern. Once set, every\n * subsequent event of THIS SDK instance carries these keys on its\n * properties bag automatically.\n */\n register(properties: Record<string, unknown>): Record<string, unknown> {\n const s = this.requireStarted();\n const validation = validateEventProperties(properties);\n return s.superProps.register(validation.properties);\n }\n\n /** Remove a single super-property key. Idempotent. */\n unregister(key: string): void {\n const s = this.requireStarted();\n s.superProps.unregister(key);\n }\n\n /** Snapshot of the current super-property bag. */\n getSuperProperties(): Record<string, unknown> {\n if (!this.state) return {};\n return this.state.superProps.getSuperProperties();\n }\n\n /**\n * Associate the current user with a group (org, team, account).\n * Mixpanel / Segment \"Group Analytics\" pattern.\n */\n group(type: string, id: string | null, traits?: GroupTraits): void {\n const s = this.requireStarted();\n if (!type) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_group_type\",\n message: \"group(type, id) requires a non-empty type.\",\n });\n }\n const sanitisedTraits = traits\n ? validateEventProperties(traits).properties\n : undefined;\n s.superProps.setGroup(type, id, sanitisedTraits);\n }\n\n /** Snapshot of the current groups map keyed by type. */\n getGroups(): Record<string, { id: string; traits?: Record<string, unknown> }> {\n if (!this.state) return {};\n return this.state.superProps.getGroups();\n }\n\n /** Update consent state. See `ConsentState` for the dimensions. */\n consent(state: Partial<ConsentState>): ConsentState {\n const s = this.requireStarted();\n const next = s.consent.set(state);\n s.debug.emit(\"sdk.consent_changed\", \"Consent state updated.\", { ...next });\n return next;\n }\n\n /** Snapshot of the current consent state. */\n consentStatus(): ConsentState {\n if (!this.state) {\n return { analytics: true, marketing: true, errors: true };\n }\n return this.state.consent.get();\n }\n\n // ============================================================\n // Error capture surface\n // ============================================================\n\n /** Manually capture an error from a try/catch block. */\n captureError(\n error: unknown,\n options?: {\n context?: Record<string, unknown>;\n tags?: Record<string, string>;\n level?: ErrorLevel;\n },\n ): void {\n if (!this.state?.errors) return;\n this.state.errors.captureError(error, options);\n }\n\n /** Capture a non-error event you want to surface as an issue. */\n captureMessage(message: string, level: ErrorLevel = \"info\"): void {\n if (!this.state?.errors) return;\n this.state.errors.captureMessage(message, level);\n }\n\n setTag(key: string, value: string): void {\n if (!this.state) return;\n this.state.errorTags[key] = value;\n }\n\n setTags(tags: Record<string, string>): void {\n if (!this.state) return;\n Object.assign(this.state.errorTags, tags);\n }\n\n setContext(name: string, data: Record<string, unknown>): void {\n if (!this.state) return;\n this.state.errorContext[name] = data;\n }\n\n addBreadcrumb(crumb: Breadcrumb): void {\n if (!this.state) return;\n this.state.breadcrumbs.add(crumb);\n }\n\n /**\n * Install a pre-send hook for errors. Return null to drop, or a\n * modified `CapturedError` to scrub / rewrite. Sentry's\n * beforeSend pattern — the only way to redact app-specific PII\n * (auth tokens in URLs, etc.) before the report leaves the\n * device.\n */\n setErrorBeforeSend(\n hook: ((err: CapturedError) => CapturedError | null) | null,\n ): void {\n if (!this.state) return;\n this.state.errorBeforeSend = hook;\n }\n\n private reportError(err: CapturedError): void {\n const properties: EventProperties = {\n fingerprint: err.fingerprint,\n level: err.level,\n errorType: err.errorType,\n message: err.message,\n stack: err.rawStack ?? undefined,\n frames: err.frames,\n filename: err.filename ?? undefined,\n lineno: err.lineno ?? undefined,\n colno: err.colno ?? undefined,\n tags: err.tags,\n context: err.context,\n breadcrumbs: err.breadcrumbs,\n http: err.http,\n };\n for (const k of Object.keys(properties)) {\n if (properties[k] === undefined) delete properties[k];\n }\n this.track(err.kind, properties);\n }\n\n /**\n * GDPR/CCPA right to be forgotten. Calls\n * `/v1/identity/forget` to schedule server-side deletion, then\n * wipes all local state (identity, entitlements, queue,\n * super-props, breadcrumbs).\n */\n async forget(): Promise<void> {\n const s = this.requireStarted();\n await s.ready;\n const identityQuery = this.identityQueryParams();\n try {\n await s.http.request<{ object: \"forgot\" }>(\"POST\", \"/identity/forget\", {\n body: { ...identityQuery },\n });\n } catch (err) {\n s.debug.emit(\n \"sdk.consent_denied\",\n `forget() server call failed (${err instanceof Error ? err.message : String(err)}). Local state wiped anyway.`,\n );\n }\n this.reset();\n }\n\n /**\n * Read the current customer's active entitlements from the\n * server. Updates the local cache so subsequent `isEntitled()`\n * calls answer synchronously.\n */\n async getEntitlements(): Promise<PublicEntitlement[]> {\n const s = this.requireStarted();\n await s.ready;\n const query = this.identityQueryParams();\n let result: EntitlementsListResponse;\n try {\n result = await s.http.request<EntitlementsListResponse>(\n \"GET\",\n \"/entitlements\",\n { query },\n );\n } catch (err) {\n s.entitlements.markRefreshFailed();\n throw err;\n }\n if (result.crossdeckCustomerId) {\n s.identity.setCrossdeckCustomerId(result.crossdeckCustomerId);\n }\n s.entitlements.setFromList(result.data);\n return result.data;\n }\n\n /**\n * Synchronous read from the durable local cache — answers from\n * last-known-good. The cache hydrates from device storage during\n * init() so a returning paying customer reads true even before\n * the session's first network round-trip. Returns false for a\n * genuinely new install that has never completed a\n * `getEntitlements()`, or for an entitlement past its own\n * `validUntil`.\n */\n isEntitled(key: string): boolean {\n if (!this.state) return false;\n return this.state.entitlements.isEntitled(key);\n }\n\n /** Snapshot of the local entitlement cache. */\n listEntitlements(): PublicEntitlement[] {\n if (!this.state) return [];\n return this.state.entitlements.list();\n }\n\n /**\n * Subscribe to entitlement-cache changes. Returns an idempotent\n * unsubscribe fn. The listener fires AFTER `getEntitlements()`\n * warms the cache, after `syncPurchases()` delivers fresh\n * entitlements, and on `reset()` to fire the empty-cache state\n * for logout flows.\n *\n * Listener errors are swallowed (a buggy consumer must not crash\n * the SDK or other listeners).\n */\n onEntitlementsChange(listener: EntitlementsListener): () => void {\n const s = this.requireStarted();\n return s.entitlements.subscribe(listener);\n }\n\n /**\n * Queue a telemetry event. Returns immediately — the network\n * round-trip happens in the background. Call `flush()` to force\n * an immediate send (e.g. when the app is backgrounding).\n *\n * RN-specific contract: identity hydration is async (AsyncStorage),\n * so a `track()` call fired in the same tick as `init()` reaches\n * the identity store before `loadAll()` has resolved. We defer the\n * post-validation portion via `s.ready.then(...)` in that case so\n * the event lands AFTER hydration with the right identity hint\n * stamped. Common-case `track()` after hydration runs entirely\n * synchronously.\n */\n /**\n * Emit `crossdeck.contract_failed` to the Crossdeck reliability\n * endpoint — single-fire, one-way, never visible in the customer's\n * dashboard. Goes over a dedicated HTTP path with the reliability\n * publishable key embedded at build time; the customer's track()\n * pipeline never carries `crossdeck.*` events. This is the\n * independent-controller flow described in Privacy Policy §6\n * (\"Flow B\"). The wire shape is fixed by the schema-lock contract\n * at `contracts/diagnostics/contract-failed-payload-schema-lock.json`.\n */\n reportContractFailure(input: ContractFailureInput): void {\n const payload: Record<string, string> = {\n contract_id: input.contractId,\n sdk_version: SDK_VERSION,\n sdk_platform: \"react-native\",\n failure_reason: input.failureReason,\n run_context: input.runContext,\n run_id: input.runId,\n };\n if (input.testRef) {\n payload.test_file = input.testRef.file;\n payload.test_name = input.testRef.name;\n }\n if (input.deviceClass) {\n payload.device_class = input.deviceClass;\n }\n sendDiagnosticTelemetry(payload);\n }\n\n track(name: string, properties?: EventProperties): void {\n const s = this.requireStarted();\n if (!name) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_event_name\",\n message: \"track(name) requires a non-empty name.\",\n });\n }\n // Capture call-time-volatile state BEFORE deferring through\n // `s.ready.then(...)`. Without this snapshot, two pre-hydration\n // `track()` calls separated by `setSessionId(...)` (or any other\n // mutation) would both read the LATEST value when the deferred\n // bodies fire post-hydration — silently rewriting the first\n // event with the second event's state. The Web SDK has no\n // hydration window so this race only exists on RN.\n const callTimeSnapshot: TrackCallSnapshot = {\n sessionId: s.sessionId,\n };\n if (!s.hydrated) {\n void s.ready.then(() => this.trackPostHydration(s, name, properties, callTimeSnapshot));\n return;\n }\n this.trackPostHydration(s, name, properties, callTimeSnapshot);\n }\n\n /**\n * The body of `track()` — everything after the synchronous\n * validation. Split out so the public `track()` can defer this\n * portion until async identity hydration completes (RN-specific —\n * see `track()` jsdoc).\n */\n private trackPostHydration(\n s: InternalState,\n name: string,\n properties: EventProperties | undefined,\n callTimeSnapshot: TrackCallSnapshot,\n ): void {\n // Consent gate. error.* events gate on consent.errors; everything\n // else gates on consent.analytics.\n const isError = name.startsWith(\"error.\");\n const consentGateOk = isError ? s.consent.errors : s.consent.analytics;\n if (!consentGateOk) {\n if (s.debug.enabled) {\n s.debug.emit(\n \"sdk.consent_denied\",\n `Dropped event \"${name}\" — consent denied.`,\n );\n }\n return;\n }\n\n // PII property-name warning (debug mode only).\n if (s.debug.enabled && properties) {\n const flagged = findSensitivePropertyKeys(properties);\n if (flagged.length > 0) {\n s.debug.emit(\n \"sdk.sensitive_property_warning\",\n `Event \"${name}\" has potentially sensitive property names: ${flagged.join(\", \")}. Crossdeck is privacy-first — avoid sending PII unless intentional.`,\n { eventName: name, flagged },\n );\n }\n }\n\n // §16 \"No identity\" — only emit once per session.\n if (\n s.debug.enabled &&\n !s.developerUserId &&\n !s.identity.crossdeckCustomerId\n ) {\n s.debug.emit(\n \"sdk.no_identity\",\n \"Using anonymous user until identify(userId) is called.\",\n );\n }\n\n // Validate + coerce caller-supplied properties.\n const validation = validateEventProperties(properties);\n if (s.debug.enabled && validation.warnings.length > 0) {\n for (const w of validation.warnings) {\n s.debug.emit(\n \"sdk.property_coerced\",\n `Event \"${name}\" property ${JSON.stringify(w.key)} was ${w.kind.replace(/_/g, \" \")} during validation.`,\n { eventName: name, key: w.key, kind: w.kind },\n );\n }\n }\n\n // Enrichment layer order (later wins on key conflict):\n // 1. Device info\n // 2. Super properties\n // 3. Group memberships\n // 4. SessionId (v1.4.0 Phase 3.4 — funnel parity with web)\n // 5. Caller-supplied properties (sanitised)\n const enriched: EventProperties = { ...s.deviceInfo };\n const supers = s.superProps.getSuperProperties();\n for (const k of Object.keys(supers)) {\n if (!(k in enriched)) enriched[k] = supers[k];\n }\n const groupIds = s.superProps.getGroupIds();\n if (Object.keys(groupIds).length > 0) {\n enriched.$groups = groupIds;\n }\n // v1.4.0 Phase 3.4 — attach sessionId so RN events reconcile\n // with the web SDK's session-anchored funnel queries. RN\n // doesn't own session lifecycle (the host's AppState +\n // nav library do); call setSessionId() from your AppState\n // change listener to populate this. Read the call-time\n // snapshot so two pre-hydration track() calls separated by\n // setSessionId(...) keep their respective session anchors.\n if (callTimeSnapshot.sessionId) {\n enriched.sessionId = callTimeSnapshot.sessionId;\n }\n Object.assign(enriched, validation.properties);\n\n // PII scrub — defensive regex pass before the event lands in\n // the queue.\n const finalProperties = s.scrubPii\n ? scrubPiiFromProperties(enriched)\n : enriched;\n\n const event: QueuedEvent = {\n eventId: this.mintEventId(),\n name,\n timestamp: Date.now(),\n properties: finalProperties,\n };\n Object.assign(event, this.identityHintForEvent());\n s.events.enqueue(event);\n\n // Breadcrumb emission — every analytics event becomes a\n // breadcrumb so error reports carry the context of what the\n // user was doing just before the crash. Don't emit a breadcrumb\n // for error events themselves (circular).\n if (!isError) {\n const category = name.startsWith(\"page.\") || name.startsWith(\"screen.\")\n ? \"navigation\"\n : name.startsWith(\"element.\") || name === \"session.started\"\n ? \"ui.click\"\n : \"custom\";\n s.breadcrumbs.add({\n timestamp: event.timestamp,\n category,\n message: name,\n data: properties ? { ...properties } : undefined,\n });\n }\n }\n\n /** Force-flush queued events. Useful from AppState background transitions. */\n async flush(): Promise<void> {\n const s = this.requireStarted();\n await s.ready;\n await s.events.flush();\n }\n\n /**\n * Forward purchase evidence to the backend for verification +\n * entitlement projection. RN apps typically wire this from\n * `react-native-iap` callbacks for Apple StoreKit 2 + Google\n * Billing receipts.\n */\n async syncPurchases(input: {\n rail?: \"apple\" | \"google\";\n signedTransactionInfo?: string;\n signedRenewalInfo?: string;\n purchaseToken?: string;\n appAccountToken?: string;\n }): Promise<PurchaseResult> {\n const s = this.requireStarted();\n await s.ready;\n const rail = input.rail ?? \"apple\";\n if (rail === \"apple\" && !input.signedTransactionInfo) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_signed_transaction_info\",\n message:\n \"syncPurchases (apple) requires a signedTransactionInfo string from StoreKit 2.\",\n });\n }\n if (rail === \"google\" && !input.purchaseToken) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_purchase_token\",\n message:\n \"syncPurchases (google) requires a purchaseToken string from Google Billing.\",\n });\n }\n const body = { ...input, rail };\n // Phase 2.2 bank-grade contract: deterministic Idempotency-Key\n // from the body. Same input → same key → backend short-circuits\n // with idempotent_replay: true on retry.\n const idempotencyKey = deriveIdempotencyKeyForPurchase(body);\n const result = await s.http.request<PurchaseResult>(\"POST\", \"/purchases/sync\", {\n body,\n idempotencyKey,\n });\n s.identity.setCrossdeckCustomerId(result.crossdeckCustomerId);\n s.entitlements.setFromList(result.entitlements);\n // Phase 3.5 (v1.4.0) — emit purchase.completed so RN manual\n // syncPurchases callers show up on the same funnel as the\n // Swift/Android auto-track path. Schema mirrors the native\n // auto-track shape on event name + rail/productId.\n try {\n const sourceProductId = result.entitlements[0]?.source.productId;\n const sourceSubscriptionId = result.entitlements[0]?.source.subscriptionId;\n const props: Record<string, unknown> = { rail };\n if (sourceProductId) props.productId = sourceProductId;\n if (sourceSubscriptionId) props.subscriptionId = sourceSubscriptionId;\n if (result.idempotent_replay) props.idempotent_replay = true;\n this.track(\"purchase.completed\", props);\n } catch {\n // defensive\n }\n s.debug.emit(\n \"sdk.purchase_evidence_sent\",\n `${rail === \"apple\" ? \"StoreKit\" : \"Google Billing\"} purchase evidence forwarded. Waiting for backend verification.`,\n { rail },\n );\n return result;\n }\n\n /**\n * v1.4.0 Phase 3.4 — set the active session id. RN doesn't own\n * session lifecycle (that's the host's AppState + nav library);\n * the host calls `setSessionId()` from its AppState change\n * listener so every subsequent `track()` event carries the\n * `sessionId` property — matches the web SDK's session-anchored\n * funnel queries.\n *\n * ```ts\n * import { AppState } from \"react-native\";\n *\n * let sessionId = uuid();\n * AppState.addEventListener(\"change\", (next) => {\n * if (next === \"active\") {\n * // New session if backgrounded > 30 min.\n * sessionId = uuid();\n * Crossdeck.setSessionId(sessionId);\n * } else if (next === \"background\") {\n * void Crossdeck.flush();\n * }\n * });\n * Crossdeck.setSessionId(sessionId);\n * ```\n *\n * Pass `null` to clear (between sessions, on logout, etc).\n */\n setSessionId(sessionId: string | null): void {\n const s = this.requireStarted();\n s.sessionId = sessionId ?? null;\n if (s.debug.enabled) {\n s.debug.emit(\n \"sdk.configured\",\n sessionId\n ? `Session id set to ${sessionId}; subsequent track events will carry it.`\n : \"Session id cleared; subsequent track events will omit it.\",\n );\n }\n }\n\n /** Toggle verbose diagnostic logging. */\n setDebugMode(enabled: boolean): void {\n const s = this.requireStarted();\n s.debug.enabled = enabled;\n if (enabled) {\n s.debug.emit(\n \"sdk.configured\",\n `Debug mode enabled for ${s.options.appId} in ${s.options.environment} mode.`,\n { appId: s.options.appId, environment: s.options.environment },\n );\n }\n }\n\n /**\n * Send the boot heartbeat. Called automatically by init() unless\n * `autoHeartbeat: false`. Captures clock skew between client and\n * server for diagnostics.\n */\n async heartbeat(): Promise<HeartbeatResponse> {\n const s = this.requireStarted();\n await s.ready;\n const result = await s.http.request<HeartbeatResponse>(\"GET\", \"/sdk/heartbeat\");\n if (typeof result?.serverTime === \"number\" && Number.isFinite(result.serverTime)) {\n s.lastServerTime = result.serverTime;\n s.lastClientTime = Date.now();\n }\n return result;\n }\n\n /**\n * Wipe persisted identity + entitlement cache + super-props +\n * breadcrumbs + queue. Use on logout. The next pre-login session\n * generates a fresh anonymousId and starts a new identity-graph\n * entry.\n */\n reset(): void {\n if (!this.state) return;\n if (this.state.developerUserId) {\n try {\n this.track(\"user.signed_out\", { auto: true });\n } catch {\n /* defensive — reset() must be bulletproof for logout flows */\n }\n }\n this.state.identity.reset();\n // Logout-grade wipe: removes EVERY per-user entitlement slot on\n // this device (layer (c) of the v1.4.0 isolation fix). A shared\n // device can never leave another user's entitlements readable\n // after a logout. Fire-and-forget — reset() stays synchronous\n // to preserve its existing public contract.\n void this.state.entitlements.clearAll();\n this.state.events.reset();\n this.state.superProps.clear();\n this.state.breadcrumbs.clear();\n this.state.errorContext = {};\n this.state.errorTags = {};\n this.state.developerUserId = null;\n // Null clock-skew snapshot on reset — these values belong to\n // the pre-logout session.\n this.state.lastServerTime = null;\n this.state.lastClientTime = null;\n }\n\n /**\n * Diagnostic snapshot. Stable shape regardless of whether\n * init() has been called — callers don't need to narrow on\n * `started` to access `events` or `entitlements`.\n */\n diagnostics(): Diagnostics {\n if (!this.state) {\n return {\n started: false,\n anonymousId: null,\n crossdeckCustomerId: null,\n developerUserId: null,\n sdkVersion: null,\n baseUrl: null,\n platform: null,\n clock: { lastServerTime: null, lastClientTime: null, skewMs: null },\n entitlements: { count: 0, lastUpdated: 0, stale: false, listenerErrors: 0 },\n events: {\n buffered: 0,\n dropped: 0,\n inFlight: 0,\n lastFlushAt: 0,\n lastError: null,\n consecutiveFailures: 0,\n nextRetryAt: null,\n },\n };\n }\n const s = this.state;\n const skewMs =\n s.lastServerTime !== null && s.lastClientTime !== null\n ? s.lastClientTime - s.lastServerTime\n : null;\n return {\n started: true,\n anonymousId: s.hydrated ? s.identity.anonymousId : null,\n crossdeckCustomerId: s.hydrated ? s.identity.crossdeckCustomerId : null,\n developerUserId: s.developerUserId,\n sdkVersion: s.options.sdkVersion,\n baseUrl: s.options.baseUrl,\n platform: s.options.platform,\n clock: {\n lastServerTime: s.lastServerTime,\n lastClientTime: s.lastClientTime,\n skewMs,\n },\n entitlements: {\n count: s.hydrated ? s.entitlements.list().length : 0,\n lastUpdated: s.hydrated ? s.entitlements.freshness : 0,\n stale: s.hydrated ? s.entitlements.isStale : false,\n listenerErrors: s.entitlements.listenerErrors,\n },\n events: s.events.getStats(),\n };\n }\n\n // ---------- private helpers ----------\n\n private requireStarted(): InternalState {\n if (!this.state) {\n throw new CrossdeckError({\n type: \"configuration_error\",\n code: \"not_initialized\",\n message:\n \"Call Crossdeck.init({ appId, publicKey, environment }) before any other method.\",\n });\n }\n return this.state;\n }\n\n /**\n * Build the identity query for /v1/entitlements. Priority:\n * crossdeckCustomerId > developerUserId > anonymousId\n */\n private identityQueryParams(): Record<string, string | undefined> {\n const s = this.requireStarted();\n if (s.identity.crossdeckCustomerId) {\n return { customerId: s.identity.crossdeckCustomerId };\n }\n if (s.developerUserId) return { userId: s.developerUserId };\n return { anonymousId: s.identity.anonymousId };\n }\n\n /**\n * Embed every known identity axis on the event. Send everything we\n * know; let the warehouse count by whichever axis matches the\n * question. Each field is at most 32 bytes — sending three on\n * every event costs ~80 bytes per request.\n */\n private identityHintForEvent(): Pick<\n QueuedEvent,\n \"developerUserId\" | \"anonymousId\" | \"crossdeckCustomerId\"\n > {\n const s = this.requireStarted();\n const hint: Pick<\n QueuedEvent,\n \"developerUserId\" | \"anonymousId\" | \"crossdeckCustomerId\"\n > = {\n anonymousId: s.identity.anonymousId,\n };\n if (s.developerUserId) hint.developerUserId = s.developerUserId;\n if (s.identity.crossdeckCustomerId) {\n hint.crossdeckCustomerId = s.identity.crossdeckCustomerId;\n }\n return hint;\n }\n\n private mintEventId(): string {\n const ts = Date.now().toString(36);\n return `evt_${ts}${randomChars(8)}`;\n }\n}\n\n/**\n * Default singleton — most consumers want one SDK instance per app.\n * Creating extra instances is fine; just `new CrossdeckClient()`.\n */\nexport const Crossdeck = new CrossdeckClient();\n\n// ============================================================\n// Internal helpers\n// ============================================================\n\n/**\n * Derive the env from a publishable key prefix.\n * cd_pub_test_… → \"sandbox\"\n * cd_pub_live_… → \"production\"\n * cd_pub_… → null (legacy / unprefixed — env can't be inferred)\n */\nfunction inferEnvFromKey(publicKey: string): Environment | null {\n if (publicKey.startsWith(\"cd_pub_test_\")) return \"sandbox\";\n if (publicKey.startsWith(\"cd_pub_live_\")) return \"production\";\n return null;\n}\n\n/**\n * Best-effort runtime platform detection via react-native's\n * `Platform.OS`. Returns \"web\" as the safe default in non-RN\n * runtimes (vitest under node, Storybook, etc.) so backend\n * validators don't reject.\n */\nfunction detectPlatform(): Platform {\n try {\n const req = (globalThis as { require?: (id: string) => unknown }).require;\n if (typeof req !== \"function\") return \"web\";\n const mod = req(\"react-native\") as { Platform?: { OS?: string } } | undefined;\n const os = mod?.Platform?.OS;\n if (os === \"ios\" || os === \"android\" || os === \"web\") return os;\n return \"web\";\n } catch {\n return \"web\";\n }\n}\n","// AUTO-GENERATED by scripts/emit-bundled-contracts.mjs — DO NOT EDIT.\n// Source of truth: contracts/**/*.json at the monorepo root.\n// Regenerated on every `npm run build`. The public API in\n// src/contracts.ts reads from this file.\n\nimport type { Contract } from \"./contracts\";\n\nexport const BUNDLED_IN = \"@cross-deck/react-native@1.5.1\" as const;\nexport const SDK_VERSION = \"1.5.1\" as const;\n\nexport const BUNDLED_CONTRACTS: readonly Contract[] = Object.freeze([\n {\n \"id\": \"contract-failed-payload-schema-lock\",\n \"pillar\": \"diagnostics\",\n \"status\": \"enforced\",\n \"claim\": \"The `crossdeck.contract_failed` event payload contains ONLY the named diagnostic fields and never any end-user personal data. The wire shape is fixed — adding a new field requires (1) a pull request that updates this contract's `allowedFields` set, (2) a Privacy Policy §6 amendment, and (3) the Customer Disclosure Template / SDK Data Collection Reference §B updates. Per-SDK assertion tests enforce the field set on every release. The `verification_phase` field is a categorical bucket — values are restricted to `boot` (the SDK self-test ran on Crossdeck.start) or `hot_path` (a verifier observed a real customer-triggered operation). The categorical nature is what preserves the diagnostic-only-not-personal classification. This is the structural guarantee that backs the independent-controller lawful basis in the Privacy Policy: the payload remains diagnostic-only, not personal, so the legitimate-interest analysis stays valid as the SDK evolves.\",\n \"appliesTo\": [\n \"web\",\n \"node\",\n \"swift\",\n \"android\",\n \"react-native\"\n ],\n \"allowedFields\": {\n \"required\": [\n \"contract_id\",\n \"sdk_version\",\n \"sdk_platform\",\n \"failure_reason\",\n \"run_context\",\n \"run_id\"\n ],\n \"optional\": [\n \"test_file\",\n \"test_name\",\n \"device_class\",\n \"verification_phase\"\n ],\n \"forbidden\": [\n \"anonymousId\",\n \"developerUserId\",\n \"crossdeckCustomerId\",\n \"email\",\n \"ip\",\n \"user_agent\",\n \"message\",\n \"stack\",\n \"stack_trace\",\n \"frames\",\n \"exception_message\",\n \"url\",\n \"path\",\n \"screen\",\n \"title\",\n \"label\",\n \"text\",\n \"ariaLabel\",\n \"accessibilityLabel\",\n \"contentDescription\",\n \"session_id\",\n \"sessionId\"\n ]\n },\n \"transport\": \"Telemetry is single-fire to the Crossdeck reliability endpoint only — NOT the customer's appId. The customer's track() pipeline never carries `crossdeck.*` events; the customer's dashboard never shows individual contract failures. Operational telemetry flows one-way to the Crossdeck operations team for SDK reliability purposes (legitimate interest, independent-controller flow per Privacy Policy §6). The reliability endpoint is hardcoded at SDK build time; the publishable key for the reliability project is embedded as a constant and rejects writes that don't match the schema.\",\n \"codeRef\": [\n \"sdks/web/src/crossdeck.ts\",\n \"sdks/node/src/crossdeck-server.ts\",\n \"sdks/swift/Sources/Crossdeck/Crossdeck.swift\",\n \"sdks/swift/Sources/Crossdeck/_DiagnosticTelemetry.swift\",\n \"sdks/android/crossdeck/src/main/kotlin/com/crossdeck/Crossdeck.kt\",\n \"sdks/android/crossdeck/src/main/kotlin/com/crossdeck/_DiagnosticTelemetry.kt\",\n \"sdks/react-native/src/crossdeck.ts\",\n \"backend/src/api/v1-sdk-diagnostic.ts\",\n \"sdks/web/src/_diagnostic-telemetry.ts\",\n \"sdks/node/src/_diagnostic-telemetry.ts\",\n \"sdks/react-native/src/_diagnostic-telemetry.ts\"\n ],\n \"testRef\": [\n {\n \"file\": \"sdks/web/tests/contract-failed-schema-lock.test.ts\",\n \"name\": \"reportContractFailure payload conforms to schema-lock\"\n },\n {\n \"file\": \"sdks/node/tests/contract-failed-schema-lock.test.ts\",\n \"name\": \"reportContractFailure payload conforms to schema-lock\"\n },\n {\n \"file\": \"sdks/swift/Tests/CrossdeckTests/ContractFailedSchemaLockTests.swift\",\n \"name\": \"test_reportContractFailure_payloadFieldsAreInAllowList\"\n },\n {\n \"file\": \"sdks/swift/Tests/CrossdeckTests/ContractFailedSchemaLockTests.swift\",\n \"name\": \"test_reportContractFailure_doesNotEnterCustomerTrackPipeline\"\n },\n {\n \"file\": \"sdks/android/crossdeck/src/test/kotlin/com/crossdeck/ContractFailedSchemaLockTest.kt\",\n \"name\": \"reportContractFailure payload conforms to schema-lock\"\n },\n {\n \"file\": \"sdks/android/crossdeck/src/test/kotlin/com/crossdeck/ContractFailedSchemaLockTest.kt\",\n \"name\": \"reportContractFailure does not enter customer track pipeline\"\n },\n {\n \"file\": \"sdks/react-native/tests/contract-failed-schema-lock.test.ts\",\n \"name\": \"reportContractFailure payload conforms to schema-lock\"\n },\n {\n \"file\": \"backend/tests/unit/v1-sdk-diagnostic.test.ts\",\n \"name\": \"forbidden fields are enumerated in the schema-lock contract\"\n },\n {\n \"file\": \"backend/tests/unit/v1-sdk-diagnostic.test.ts\",\n \"name\": \"required fields are enumerated in the schema-lock contract\"\n },\n {\n \"file\": \"backend/tests/unit/v1-sdk-diagnostic.test.ts\",\n \"name\": \"regression guard: never returns a raw IP\"\n },\n {\n \"file\": \"backend/tests/unit/v1-sdk-diagnostic.test.ts\",\n \"name\": \"verification_phase is in the optional field set\"\n }\n ],\n \"registeredAt\": \"2026-05-27\",\n \"firstRegisteredIn\": \"Diagnostic telemetry single-fire + schema-lock — independent-controller flow\",\n \"privacyReferences\": [\n \"legal/privacy/index.html#sdk-diagnostic\",\n \"legal/customer-disclosure/index.html#flow-b\",\n \"legal/security/index.html#diagnostic\",\n \"legal/sdk-data/index.html#b-diagnostic\"\n ],\n \"bundledIn\": \"@cross-deck/react-native@1.5.1\"\n },\n {\n \"id\": \"error-envelope-shape\",\n \"pillar\": \"errors\",\n \"status\": \"enforced\",\n \"claim\": \"Every v1 REST endpoint returns errors in a Stripe-shape envelope: `{ error: { type, code, message, request_id } }` where `type` is one of authentication_error / permission_error / invalid_request_error / rate_limit_error / internal_error (the wire vocabulary in backend/src/api/v1-errors.ts ApiErrorType). HTTP status parity: invalid_request_error → 400, authentication_error → 401, permission_error → 403, rate_limit_error → 429, internal_error → 500. SDK-side clients parse this shape via `crossdeckErrorFromResponse` (Web/Node/RN) / `crossdeckErrorFrom(response:)` (Swift) / `crossdeckErrorFromResponse` (Android) and surface the request_id verbatim so support traces are end-to-end joinable. Firebase callable endpoints (managed-keys / dashboard auth) use the Firebase HttpsError envelope instead — this contract applies to REST /v1/* only.\",\n \"appliesTo\": [\n \"web\",\n \"node\",\n \"react-native\",\n \"swift\",\n \"android\",\n \"backend\"\n ],\n \"codeRef\": [\n \"backend/src/api/v1-errors.ts\",\n \"sdks/web/src/errors.ts\",\n \"sdks/node/src/errors.ts\",\n \"sdks/react-native/src/errors.ts\",\n \"sdks/swift/Sources/Crossdeck/Errors.swift\",\n \"sdks/android/crossdeck/src/main/kotlin/com/crossdeck/Errors.kt\"\n ],\n \"testRef\": [\n {\n \"file\": \"sdks/swift/Tests/CrossdeckTests/ErrorsTests.swift\",\n \"name\": \"test_errorEnvelope_fallsBackOnGarbageBody\"\n },\n {\n \"file\": \"sdks/swift/Tests/CrossdeckTests/ErrorsTests.swift\",\n \"name\": \"test_errorEnvelope_reads_XRequestId_fallback\"\n },\n {\n \"file\": \"sdks/android/crossdeck/src/test/kotlin/com/crossdeck/ErrorTypeWireVocabTest.kt\",\n \"name\": \"backend 500 response parses to INTERNAL_ERROR\"\n }\n ],\n \"registeredAt\": \"2026-05-26\",\n \"firstRegisteredIn\": \"bank-grade reconciliation v1.4.0 — phase 8 (codifies existing contract)\",\n \"bundledIn\": \"@cross-deck/react-native@1.5.1\"\n },\n {\n \"id\": \"flush-interval-parity\",\n \"pillar\": \"analytics\",\n \"status\": \"enforced\",\n \"claim\": \"Every Crossdeck SDK defaults its event-queue flush interval to 2000ms — the Stripe-adjacent industry norm. Pre-v1.4.0 the defaults disagreed (Web/Node 1500ms; RN/Swift/Android 5000ms), so cross-platform funnels saw events landing at different cadences. Per-instance override stays — call sites can still tune it freely.\",\n \"appliesTo\": [\n \"web\",\n \"node\",\n \"react-native\",\n \"swift\",\n \"android\"\n ],\n \"codeRef\": [\n \"sdks/web/src/crossdeck.ts\",\n \"sdks/node/src/crossdeck-server.ts\",\n \"sdks/react-native/src/crossdeck.ts\",\n \"sdks/swift/Sources/Crossdeck/EventQueue.swift\",\n \"sdks/android/crossdeck/src/main/kotlin/com/crossdeck/EventQueue.kt\"\n ],\n \"testRef\": [\n {\n \"file\": \"sdks/swift/Sources/Crossdeck/EventQueue.swift\",\n \"name\": \"flushIntervalMs: Int = 2_000\"\n },\n {\n \"file\": \"sdks/android/crossdeck/src/main/kotlin/com/crossdeck/EventQueue.kt\",\n \"name\": \"flushIntervalMs: Long = 2_000L\"\n },\n {\n \"file\": \"sdks/web/src/crossdeck.ts\",\n \"name\": \"options.eventFlushIntervalMs ?? 2000\"\n },\n {\n \"file\": \"sdks/node/src/crossdeck-server.ts\",\n \"name\": \"options.eventFlushIntervalMs ?? 2000\"\n },\n {\n \"file\": \"sdks/react-native/src/crossdeck.ts\",\n \"name\": \"options.eventFlushIntervalMs ?? 2000\"\n }\n ],\n \"registeredAt\": \"2026-05-26\",\n \"firstRegisteredIn\": \"bank-grade reconciliation v1.4.0 — phase 3.3\",\n \"bundledIn\": \"@cross-deck/react-native@1.5.1\"\n },\n {\n \"id\": \"idempotency-key-deterministic\",\n \"pillar\": \"revenue\",\n \"status\": \"enforced\",\n \"claim\": \"syncPurchases() on every SDK derives a deterministic Idempotency-Key from the request body (UUID-shaped SHA-256 of `crossdeck:purchases/sync:<rail>:<jws|token>`). Same input -> same key. Backend short-circuits same-key-same-body retries by returning the cached response (status + body) with `idempotent_replay: true` flag in the body AND `Idempotent-Replayed: true` response header. Same-key-different-body returns 400 `idempotency_key_in_use`. 24-hour TTL matches Stripe. Cache only stores 2xx responses — 4xx/5xx pass through so callers can fix bugs and retry. Helper returns nil/throws on missing identifier (no silent random fallback). Cross-SDK parity is CI-pinned: deriveForPurchase('apple', 'eyJ.jws.sig') MUST equal 'a66b1640-efaf-bb4d-1261-6650033bf111' on every SDK.\",\n \"appliesTo\": [\n \"web\",\n \"node\",\n \"react-native\",\n \"swift\",\n \"android\",\n \"backend\"\n ],\n \"codeRef\": [\n \"sdks/web/src/idempotency-key.ts\",\n \"sdks/web/src/crossdeck.ts\",\n \"sdks/react-native/src/idempotency-key.ts\",\n \"sdks/react-native/src/crossdeck.ts\",\n \"sdks/node/src/idempotency-key.ts\",\n \"sdks/node/src/crossdeck-server.ts\",\n \"sdks/swift/Sources/Crossdeck/IdempotencyKey.swift\",\n \"sdks/swift/Sources/Crossdeck/Crossdeck.swift\",\n \"sdks/android/crossdeck/src/main/kotlin/com/crossdeck/IdempotencyKey.kt\",\n \"sdks/android/crossdeck/src/main/kotlin/com/crossdeck/Crossdeck.kt\",\n \"backend/src/lib/idempotency-response-cache.ts\",\n \"backend/src/api/v1-purchases.ts\"\n ],\n \"testRef\": [\n {\n \"file\": \"sdks/web/tests/idempotency-key.test.ts\",\n \"name\": \"cross-SDK oracle — apple JWS pins canonical vector\"\n },\n {\n \"file\": \"sdks/web/tests/idempotency-key.test.ts\",\n \"name\": \"is deterministic: same body twice -> identical key\"\n },\n {\n \"file\": \"sdks/web/tests/idempotency-key.test.ts\",\n \"name\": \"same identifier under different rails -> different keys\"\n },\n {\n \"file\": \"sdks/web/tests/idempotency-key.test.ts\",\n \"name\": \"never silently falls back to a random key on missing identifier\"\n },\n {\n \"file\": \"sdks/react-native/tests/idempotency-key.test.ts\",\n \"name\": \"is deterministic\"\n },\n {\n \"file\": \"sdks/react-native/tests/idempotency-key.test.ts\",\n \"name\": \"cross-SDK oracle — apple JWS pins canonical vector\"\n },\n {\n \"file\": \"sdks/node/tests/idempotency-key.test.ts\",\n \"name\": \"is deterministic\"\n },\n {\n \"file\": \"sdks/node/tests/idempotency-key.test.ts\",\n \"name\": \"rail namespacing prevents cross-rail collisions\"\n },\n {\n \"file\": \"sdks/node/tests/idempotency-key.test.ts\",\n \"name\": \"apple JWS produces the canonical pinned UUID across all 5 SDKs\"\n },\n {\n \"file\": \"backend/tests/unit/idempotency-response-cache.test.ts\",\n \"name\": \"is deterministic for the same input\"\n },\n {\n \"file\": \"backend/tests/unit/idempotency-response-cache.test.ts\",\n \"name\": \"injects idempotent_replay: true into a JSON object body\"\n },\n {\n \"file\": \"backend/tests/unit/idempotency-response-cache.test.ts\",\n \"name\": \"matches Stripe's 24-hour idempotency window\"\n },\n {\n \"file\": \"sdks/swift/Tests/CrossdeckTests/IdempotencyKeyTests.swift\",\n \"name\": \"test_crossSdkOracle_appleJWS\"\n },\n {\n \"file\": \"sdks/swift/Tests/CrossdeckTests/IdempotencyKeyTests.swift\",\n \"name\": \"test_railNamespacing_preventsCrossRailCollisions\"\n },\n {\n \"file\": \"sdks/swift/Tests/CrossdeckTests/IdempotencyKeyTests.swift\",\n \"name\": \"test_missingIdentifier_returnsNil\"\n },\n {\n \"file\": \"sdks/android/crossdeck/src/test/kotlin/com/crossdeck/IdempotencyKeyTest.kt\",\n \"name\": \"cross-SDK oracle for apple JWS\"\n },\n {\n \"file\": \"sdks/android/crossdeck/src/test/kotlin/com/crossdeck/IdempotencyKeyTest.kt\",\n \"name\": \"rail namespacing prevents cross-rail collisions\"\n },\n {\n \"file\": \"sdks/android/crossdeck/src/test/kotlin/com/crossdeck/IdempotencyKeyTest.kt\",\n \"name\": \"missing identifier returns null - never silent random fallback\"\n }\n ],\n \"registeredAt\": \"2026-05-26\",\n \"firstRegisteredIn\": \"bank-grade reconciliation v1.4.0 — phase 2.2.a + 2.2.b + 2.2.c\",\n \"bundledIn\": \"@cross-deck/react-native@1.5.1\"\n },\n {\n \"id\": \"init-reentry-drains-prior-queue\",\n \"pillar\": \"lifecycle\",\n \"status\": \"enforced\",\n \"claim\": \"Web + RN init() re-entry drains the prior EventQueue's pending setTimeout BEFORE replacing this.state. Pre-v1.4.0 the teardown handled autoTracker/webVitals/errors/unloadFlush but NOT events, so the prior queue's timer would fire AFTER the state swap — sending old-init events against new-init http + identity references (cross-identity leak during HMR / config swap / multi-tenant SDK shells). The teardown CANNOT call persistent.clear() — the durable queue belongs to the SDK lifetime, not the init() lifetime, and a survived crash mid-flush re-hydrates on the next init.\",\n \"appliesTo\": [\n \"web\",\n \"react-native\"\n ],\n \"codeRef\": [\n \"sdks/web/src/crossdeck.ts\",\n \"sdks/react-native/src/crossdeck.ts\"\n ],\n \"testRef\": [\n {\n \"file\": \"sdks/web/tests/init-reentry.test.ts\",\n \"name\": \"re-init drains the prior queue's pending timer before swapping state\"\n },\n {\n \"file\": \"sdks/web/tests/init-reentry.test.ts\",\n \"name\": \"re-init does NOT wipe the durable event store\"\n }\n ],\n \"registeredAt\": \"2026-05-26\",\n \"firstRegisteredIn\": \"bank-grade reconciliation v1.4.0 — phase 5.5\",\n \"bundledIn\": \"@cross-deck/react-native@1.5.1\"\n },\n {\n \"id\": \"per-user-cache-isolation\",\n \"pillar\": \"entitlements\",\n \"status\": \"enforced\",\n \"claim\": \"Every identify(userId) switches the entitlement cache to a physically separate per-user storage slot — `crossdeck:entitlements:<sha256(userId)>` — and unconditionally wipes the in-memory snapshot. A user-switch on a shared device CANNOT cross-read a prior user's cached entitlements, even if the in-memory clear is somehow skipped, because the storage keys are physically separate. reset() wipes every per-user slot via the persisted index.\",\n \"appliesTo\": [\n \"web\",\n \"react-native\",\n \"swift\",\n \"android\"\n ],\n \"codeRef\": [\n \"sdks/web/src/entitlement-cache.ts\",\n \"sdks/web/src/hash.ts\",\n \"sdks/web/src/crossdeck.ts\",\n \"sdks/react-native/src/entitlement-cache.ts\",\n \"sdks/react-native/src/hash.ts\",\n \"sdks/react-native/src/crossdeck.ts\",\n \"sdks/swift/Sources/Crossdeck/EntitlementCache.swift\",\n \"sdks/swift/Sources/Crossdeck/IdempotencyKey.swift\",\n \"sdks/swift/Sources/Crossdeck/Crossdeck.swift\",\n \"sdks/android/crossdeck/src/main/kotlin/com/crossdeck/EntitlementCache.kt\",\n \"sdks/android/crossdeck/src/main/kotlin/com/crossdeck/IdempotencyKey.kt\",\n \"sdks/android/crossdeck/src/main/kotlin/com/crossdeck/Crossdeck.kt\"\n ],\n \"testRef\": [\n {\n \"file\": \"sdks/web/tests/entitlement-cache-isolation.test.ts\",\n \"name\": \"identify(B) makes A's entitlements unreachable from in-memory\"\n },\n {\n \"file\": \"sdks/web/tests/entitlement-cache-isolation.test.ts\",\n \"name\": \"clearAll() removes every per-user storage key plus the index\"\n },\n {\n \"file\": \"sdks/web/tests/entitlement-cache-isolation.test.ts\",\n \"name\": \"a second cache instance reading A's storage suffix CANNOT see B's data\"\n },\n {\n \"file\": \"sdks/react-native/tests/entitlement-cache-isolation.test.ts\",\n \"name\": \"identify(B) makes A's entitlements unreachable from in-memory\"\n },\n {\n \"file\": \"sdks/react-native/tests/entitlement-cache-isolation.test.ts\",\n \"name\": \"removes every per-user storage key plus the index\"\n },\n {\n \"file\": \"sdks/swift/Tests/CrossdeckTests/EntitlementCacheIsolationTests.swift\",\n \"name\": \"test_identifyB_makesAEntitlementsUnreachable\"\n },\n {\n \"file\": \"sdks/swift/Tests/CrossdeckTests/EntitlementCacheIsolationTests.swift\",\n \"name\": \"test_identifiedWritesLandUnderPerUserSha256Key\"\n },\n {\n \"file\": \"sdks/swift/Tests/CrossdeckTests/EntitlementCacheIsolationTests.swift\",\n \"name\": \"test_clearAll_removesEveryPerUserStorageKeyPlusIndex\"\n },\n {\n \"file\": \"sdks/android/crossdeck/src/test/kotlin/com/crossdeck/EntitlementCacheIsolationTest.kt\",\n \"name\": \"identified writes land under per-user sha256 key\"\n },\n {\n \"file\": \"sdks/android/crossdeck/src/test/kotlin/com/crossdeck/EntitlementCacheIsolationTest.kt\",\n \"name\": \"identify B makes A entitlements unreachable from in-memory\"\n },\n {\n \"file\": \"sdks/android/crossdeck/src/test/kotlin/com/crossdeck/EntitlementCacheIsolationTest.kt\",\n \"name\": \"clearAll removes every per-user storage key plus the index\"\n },\n {\n \"file\": \"sdks/android/crossdeck/src/test/kotlin/com/crossdeck/EntitlementCacheIsolationTest.kt\",\n \"name\": \"a fresh cache bound to A's key CANNOT read B's blob\"\n }\n ],\n \"registeredAt\": \"2026-05-26\",\n \"firstRegisteredIn\": \"bank-grade reconciliation v1.4.0 — phase 1.3 (web/RN) + dogfood-gap fix (swift + android)\",\n \"bundledIn\": \"@cross-deck/react-native@1.5.1\"\n },\n {\n \"id\": \"rn-session-id-enrichment\",\n \"pillar\": \"analytics\",\n \"status\": \"enforced\",\n \"claim\": \"RN SDK's track() pipeline attaches a `sessionId` property to every event when the host has called `setSessionId(...)` — parity with the web SDK's session-anchored funnel queries. Pre-v1.4.0 the enrichment merged device + super + groups + caller but never carried sessionId, so cross-platform funnels on session anchors returned zero RN rows. The host owns session lifecycle (AppState + nav library); the SDK exposes setSessionId() / setSessionId(null) for the host to drive. Caller-supplied sessionId in properties still wins on conflict (matches the Phase 3.2 caller > super > device precedence chain).\",\n \"appliesTo\": [\n \"react-native\"\n ],\n \"codeRef\": [\n \"sdks/react-native/src/crossdeck.ts\"\n ],\n \"testRef\": [\n {\n \"file\": \"sdks/react-native/tests/session-id-enrichment.test.ts\",\n \"name\": \"track() events carry sessionId after setSessionId() is called\"\n },\n {\n \"file\": \"sdks/react-native/tests/session-id-enrichment.test.ts\",\n \"name\": \"track() events do NOT carry sessionId before setSessionId() is called\"\n },\n {\n \"file\": \"sdks/react-native/tests/session-id-enrichment.test.ts\",\n \"name\": \"setSessionId(null) clears the active session\"\n },\n {\n \"file\": \"sdks/react-native/tests/session-id-enrichment.test.ts\",\n \"name\": \"caller-supplied sessionId property overrides setSessionId() value (Phase 3.2 precedence)\"\n }\n ],\n \"registeredAt\": \"2026-05-26\",\n \"firstRegisteredIn\": \"bank-grade reconciliation v1.4.0 — phase 3.4\",\n \"bundledIn\": \"@cross-deck/react-native@1.5.1\"\n },\n {\n \"id\": \"sync-purchases-funnel-parity\",\n \"pillar\": \"analytics\",\n \"status\": \"enforced\",\n \"claim\": \"Manual syncPurchases() emits a `purchase.completed` analytics event on success across ALL SDKs (Web / Node / RN / Swift / Android). Pre-v1.4.0 only Swift/Android auto-track emitted it — Web/Node/RN manual calls + Swift/Android manual calls fired ZERO analytics. Schema mirrors the auto-track event name + rail/productId/subscriptionId so cross-platform funnels reconcile on every payment path. When the backend short-circuits via the v1.4.0 idempotency cache, the event also carries `idempotent_replay: true`.\",\n \"appliesTo\": [\n \"web\",\n \"node\",\n \"react-native\",\n \"swift\",\n \"android\"\n ],\n \"codeRef\": [\n \"sdks/web/src/crossdeck.ts\",\n \"sdks/node/src/crossdeck-server.ts\",\n \"sdks/react-native/src/crossdeck.ts\",\n \"sdks/swift/Sources/Crossdeck/Crossdeck.swift\",\n \"sdks/android/crossdeck/src/main/kotlin/com/crossdeck/Crossdeck.kt\"\n ],\n \"testRef\": [\n {\n \"file\": \"sdks/web/tests/sync-purchases-funnel.test.ts\",\n \"name\": \"emits purchase.completed after a successful sync\"\n },\n {\n \"file\": \"sdks/web/tests/sync-purchases-funnel.test.ts\",\n \"name\": \"carries idempotent_replay=true when backend replied from cache\"\n }\n ],\n \"registeredAt\": \"2026-05-26\",\n \"firstRegisteredIn\": \"bank-grade reconciliation v1.4.0 — phase 3.5\",\n \"bundledIn\": \"@cross-deck/react-native@1.5.1\"\n }\n]) as readonly Contract[];\n","/**\n * Public, typed accessor for the bank-grade behavioural contracts\n * this SDK ships. The full architecture — schema, distribution,\n * audit loop, pillar taxonomy — lives in `contracts/README.md`\n * at the monorepo root.\n *\n * Why a typed surface (vs. plain JSON access): contract IDs and\n * pillar names are part of Crossdeck's public commitment to\n * customers. Reading them through `CrossdeckContracts` means the\n * compiler catches drift the moment a contract is renamed or\n * retired. Tools that consume contracts at runtime (dashboards,\n * AI assistants, customer integration tests) get the exact same\n * shape every SDK ships, with no parsing layer to drift.\n *\n * --- BINARY STABILITY ---\n * `Contract` is treated as an evolving — but back-compat — wire\n * shape. Fields may be added in any minor release. Existing\n * fields will not be removed or repurposed except in a major\n * version bump, even if all known contracts stop using them.\n * Customers can rely on `id`, `pillar`, `status`, `appliesTo`,\n * `codeRef`, `testRef`, `registeredAt`, `firstRegisteredIn`,\n * and `bundledIn` being present on every contract in every\n * future minor/patch release of this SDK.\n */\n\nimport {\n BUNDLED_CONTRACTS,\n BUNDLED_IN,\n SDK_VERSION,\n} from \"./_contracts-bundled\";\n\nexport type ContractPillar =\n | \"revenue\"\n | \"entitlements\"\n | \"analytics\"\n | \"webhooks\"\n | \"errors\"\n | \"lifecycle\"\n | \"identity\";\n\nexport type ContractStatus = \"enforced\" | \"proposed\" | \"retired\";\n\nexport type ContractAppliesTo =\n | \"web\"\n | \"node\"\n | \"react-native\"\n | \"swift\"\n | \"android\"\n | \"backend\";\n\nexport interface ContractTestRef {\n readonly file: string;\n readonly name: string;\n}\n\nexport interface Contract {\n readonly id: string;\n readonly pillar: ContractPillar;\n readonly status: ContractStatus;\n readonly claim: string;\n readonly appliesTo: readonly ContractAppliesTo[];\n readonly codeRef: readonly string[];\n readonly testRef: readonly ContractTestRef[];\n readonly registeredAt: string;\n readonly firstRegisteredIn: string;\n readonly bundledIn: string;\n}\n\n/**\n * Typed entry point to the bank-grade contracts bundled with this\n * SDK release. Stable, side-effect-free, tree-shakeable.\n *\n * @example Audit at app boot\n * ```ts\n * import { CrossdeckContracts } from \"@cross-deck/react-native\";\n *\n * for (const c of CrossdeckContracts.all()) {\n * console.log(`[crossdeck] ${c.id} (${c.pillar})`);\n * }\n * ```\n */\nexport const CrossdeckContracts = {\n all(): readonly Contract[] {\n return BUNDLED_CONTRACTS.filter((c) => c.status === \"enforced\");\n },\n allIncludingHistorical(): readonly Contract[] {\n return BUNDLED_CONTRACTS;\n },\n byId(id: string): Contract | undefined {\n return BUNDLED_CONTRACTS.find((c) => c.id === id);\n },\n byPillar(pillar: ContractPillar): readonly Contract[] {\n return BUNDLED_CONTRACTS.filter(\n (c) => c.pillar === pillar && c.status === \"enforced\",\n );\n },\n withStatus(status: ContractStatus): readonly Contract[] {\n return BUNDLED_CONTRACTS.filter((c) => c.status === status);\n },\n sdkVersion: SDK_VERSION,\n bundledIn: BUNDLED_IN,\n\n /**\n * Resolve a failing test back to the contract it exercises.\n * Used by test-framework hooks to find the contract id of a\n * failed contract test so `reportContractFailure(...)` can stamp\n * the right `contract_id` on the emitted event.\n */\n findByTestName(name: string): Contract | undefined {\n return BUNDLED_CONTRACTS.find((c) =>\n c.testRef.some((ref) => ref.name === name),\n );\n },\n} as const;\n\n/**\n * Input to {@link Crossdeck.reportContractFailure}.\n *\n * SCHEMA-LOCK: this interface's field set is exhaustively named. No\n * free-form `extra: Record<string, unknown>` — the schema-lock\n * contract at\n * `contracts/diagnostics/contract-failed-payload-schema-lock.json`\n * forbids unbounded fields.\n */\nexport interface ContractFailureInput {\n contractId: string;\n /**\n * Short categorical-ish label — the SDK convention is to keep\n * this under 128 chars and stable across runs. Never an\n * end-user-supplied string.\n */\n failureReason: string;\n runContext: \"ci\" | \"dogfood\" | \"customer-app\";\n runId: string;\n testRef?: { file: string; name: string };\n /**\n * Optional coarse device class, e.g. \"ios-phone\", \"android-tablet\".\n * A categorical bucket, not a device identifier.\n */\n deviceClass?: string;\n}\n"],"mappings":"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","names":["SDK_VERSION","SDK_VERSION"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/_version.ts","../src/http.ts","../src/identity.ts","../src/hash.ts","../src/entitlement-cache.ts","../src/idempotency-key.ts","../src/retry-policy.ts","../src/event-queue.ts","../src/event-storage.ts","../src/storage.ts","../src/device-info.ts","../src/debug.ts","../src/event-validation.ts","../src/super-properties.ts","../src/consent.ts","../src/breadcrumbs.ts","../src/_diagnostic-telemetry.ts","../src/stack-parser.ts","../src/error-capture.ts","../src/crossdeck.ts","../src/_contracts-bundled.ts","../src/contracts.ts"],"sourcesContent":["/**\n * @cross-deck/react-native — public entry point.\n *\n * The default export is a singleton `Crossdeck` instance. Most apps\n * want exactly one client; instantiate `CrossdeckClient` directly if\n * you need isolated instances (e.g. one per tenant in a multi-tenant\n * RN shell).\n */\n\nexport { Crossdeck, CrossdeckClient } from \"./crossdeck\";\nexport { CrossdeckError, parseRetryAfterHeader } from \"./errors\";\nexport { MemoryStorage, AsyncStorageAdapter } from \"./storage\";\nexport { SDK_NAME, SDK_VERSION, DEFAULT_BASE_URL } from \"./http\";\nexport { scrubPii, scrubPiiFromProperties } from \"./consent\";\nexport { CrossdeckContracts } from \"./contracts\";\nexport type {\n Contract,\n ContractPillar,\n ContractStatus,\n ContractAppliesTo,\n ContractTestRef,\n ContractFailureInput,\n} from \"./contracts\";\n\nexport type {\n CrossdeckOptions,\n IdentifyOptions,\n GroupTraits,\n EventProperties,\n KeyValueStorage,\n PublicEntitlement,\n EntitlementsListResponse,\n AliasResult,\n PurchaseResult,\n HeartbeatResponse,\n Diagnostics,\n Environment,\n Platform,\n AuditRail,\n} from \"./types\";\nexport type { ConsentState } from \"./consent\";\nexport type { DeviceInfo } from \"./device-info\";\nexport type { CrossdeckErrorType, CrossdeckErrorPayload } from \"./errors\";\nexport type { Breadcrumb, BreadcrumbCategory, BreadcrumbLevel } from \"./breadcrumbs\";\nexport type { CapturedError, ErrorLevel } from \"./error-capture\";\nexport type { StackFrame } from \"./stack-parser\";\nexport type { EntitlementsListener } from \"./entitlement-cache\";\n","/**\n * Stripe-style error wrapper for @cross-deck/react-native.\n *\n * Wire shape is shared with @cross-deck/web and @cross-deck/node so\n * cross-platform catch blocks read identically:\n *\n * try {\n * await Crossdeck.identify(\"user_847\");\n * } catch (err) {\n * if (err instanceof CrossdeckError && err.code === \"invalid_api_key\") { … }\n * }\n *\n * Retry-After parsing is identical to web — the event queue's retry\n * policy honours the server-supplied delay when it exceeds the\n * computed backoff (a 429 `Retry-After: 120` wins over a base+jitter\n * of 800ms, capped at 24h as a sanity guard).\n */\n\nexport type CrossdeckErrorType =\n | \"authentication_error\"\n | \"permission_error\"\n | \"invalid_request_error\"\n | \"rate_limit_error\"\n | \"internal_error\"\n | \"network_error\"\n | \"configuration_error\";\n\nexport interface CrossdeckErrorPayload {\n type: CrossdeckErrorType;\n code: string;\n message: string;\n /** Server-issued request ID. Echoed in support tickets. */\n requestId?: string;\n /** HTTP status code if the error came from an API response. */\n status?: number;\n /**\n * Server-suggested wait (in milliseconds) before retrying. Populated\n * from the `Retry-After` response header on 429 / 503. Consumers MUST\n * honour this — the server is telling you the safe rate.\n */\n retryAfterMs?: number;\n}\n\nexport class CrossdeckError extends Error {\n public readonly type: CrossdeckErrorType;\n public readonly code: string;\n public readonly requestId?: string;\n public readonly status?: number;\n public readonly retryAfterMs?: number;\n\n constructor(payload: CrossdeckErrorPayload) {\n super(payload.message);\n this.name = \"CrossdeckError\";\n this.type = payload.type;\n this.code = payload.code;\n this.requestId = payload.requestId;\n this.status = payload.status;\n this.retryAfterMs = payload.retryAfterMs;\n // Restore prototype chain — needed when downlevelled past ES2015\n // (Hermes pre-0.74, Babel transpiled targets).\n Object.setPrototypeOf(this, CrossdeckError.prototype);\n }\n}\n\n/**\n * Build a CrossdeckError from a non-OK fetch Response. Reads the\n * Stripe-style envelope `{ error: { type, code, message, request_id } }`.\n * Falls back to a generic shape if the body isn't valid JSON.\n */\nexport async function crossdeckErrorFromResponse(\n res: Response,\n): Promise<CrossdeckError> {\n const requestId = res.headers.get(\"x-request-id\") ?? undefined;\n const retryAfterMs = parseRetryAfterHeader(res.headers.get(\"retry-after\"));\n let body: unknown;\n try {\n body = await res.json();\n } catch {\n body = null;\n }\n const envelope = (body as {\n error?: Partial<CrossdeckErrorPayload> & { request_id?: string };\n })?.error;\n if (envelope && typeof envelope.type === \"string\" && typeof envelope.code === \"string\") {\n return new CrossdeckError({\n type: envelope.type as CrossdeckErrorType,\n code: envelope.code,\n message: envelope.message ?? `HTTP ${res.status}`,\n requestId: envelope.request_id ?? requestId,\n status: res.status,\n retryAfterMs,\n });\n }\n return new CrossdeckError({\n type: typeMapForStatus(res.status),\n code: `http_${res.status}`,\n message: `HTTP ${res.status} ${res.statusText || \"\"}`.trim(),\n requestId,\n status: res.status,\n retryAfterMs,\n });\n}\n\n/**\n * Parse the `Retry-After` header per RFC 7231 §7.1.3. Two forms:\n * - delta-seconds: \"Retry-After: 120\" → 120_000 ms\n * - HTTP-date: \"Retry-After: Wed, 21 Oct 2026 07:28:00 GMT\"\n * → max(0, target - now) ms\n *\n * Returns undefined when the header is missing, malformed, or in the past.\n */\nexport function parseRetryAfterHeader(value: string | null): number | undefined {\n if (!value) return undefined;\n const trimmed = value.trim();\n if (!trimmed) return undefined;\n if (/^\\d+(\\.\\d+)?$/.test(trimmed)) {\n const secs = Number(trimmed);\n if (!Number.isFinite(secs) || secs < 0) return undefined;\n return Math.round(secs * 1000);\n }\n if (!/[a-zA-Z,/:]/.test(trimmed)) return undefined;\n const target = Date.parse(trimmed);\n if (!Number.isFinite(target)) return undefined;\n const delta = target - Date.now();\n return delta > 0 ? delta : 0;\n}\n\nfunction typeMapForStatus(status: number): CrossdeckErrorType {\n if (status === 401) return \"authentication_error\";\n if (status === 403) return \"permission_error\";\n if (status === 429) return \"rate_limit_error\";\n if (status >= 400 && status < 500) return \"invalid_request_error\";\n return \"internal_error\";\n}\n","/**\n * SDK version constant — generated by `scripts/sync-sdk-versions.mjs`.\n *\n * Single source of truth: the `version` field in this package's\n * package.json. The sync script writes this file so that\n * `SDK_VERSION` is a plain TypeScript literal at runtime — no\n * runtime JSON-import gotcha (Node ESM requires\n * `with { type: \"json\" }` to import JSON as ESM, and the published\n * dist file would otherwise fail to load).\n *\n * Drift protection: `node scripts/sync-sdk-versions.mjs --check` (the\n * CI gate) flags this file when it falls out of sync with package.json.\n * Bumping `package.json` without re-running the sync script fails CI.\n *\n * Do NOT edit by hand — `node scripts/sync-sdk-versions.mjs`.\n */\nexport const SDK_VERSION = \"1.5.3\";\nexport const SDK_NAME = \"@cross-deck/react-native\";\n","/**\n * HTTP transport for the SDK. Single fetch wrapper used by every\n * endpoint call. Adds the Bearer token and SDK version header, parses\n * responses, normalises errors to CrossdeckError.\n *\n * Uses RN's native `fetch` (Hermes 0.74+ ships it; older RN shims it\n * via the same Polyfill module React Native registers in\n * InitializeCore). No axios, no fetch-shim transitive deps.\n */\n\nimport { CrossdeckError, crossdeckErrorFromResponse } from \"./errors\";\nimport { SDK_NAME, SDK_VERSION } from \"./_version\";\n\nexport { SDK_NAME, SDK_VERSION };\n\nexport const DEFAULT_BASE_URL = \"https://api.cross-deck.com/v1\";\nexport const DEFAULT_TIMEOUT_MS = 15_000;\n\nexport interface HttpClientConfig {\n publicKey: string;\n baseUrl: string;\n sdkVersion: string;\n /**\n * iOS Bundle ID (e.g. `com.acme.app`). Sent as\n * `X-Crossdeck-Bundle-Id` on every request when the runtime is\n * iOS, so the backend's `isBundleIdAllowed()` can enforce the\n * identity lock against the bundleId stored on the iOS app key.\n *\n * Bank-grade contract: the backend rejects iOS requests without\n * a matching bundle ID with 403 / bundle_id_not_allowed.\n *\n * Source from the consumer's native shell. With `expo-application`:\n * `import { applicationId } from \"expo-application\";`\n * With `react-native-device-info`:\n * `import DeviceInfo from \"react-native-device-info\";\n * DeviceInfo.getBundleId()`\n * Or from the bare RN bridge as `NativeModules.RNCConfig.bundleId`\n * when you ship the platform identifier yourself.\n */\n bundleId?: string;\n /**\n * Android package name / applicationId (e.g. `com.acme.app`). Sent\n * as `X-Crossdeck-Package-Name` on every request when the runtime\n * is Android. Same enforcement shape as `bundleId`: the backend\n * rejects Android requests without a matching package name with\n * 403 / package_name_not_allowed.\n *\n * On bare RN, source from\n * `NativeModules.PlatformConstants.packageName` or\n * `expo-application.applicationId`.\n */\n packageName?: string;\n /**\n * Default request timeout in ms. Per-call `options.timeoutMs`\n * overrides. Caller's `options.timeoutMs: 0` disables the timeout\n * entirely (useful for tests that intentionally hang).\n *\n * Stripe-grade default: 15s. Long enough that a slow-3G mobile\n * keeps the request alive; short enough that a captive portal or\n * a hung connection doesn't sit forever. Without this, fetch()\n * inherits the runtime's default (which on Hermes can be 5+\n * minutes) and a single bad network can lock up the entire event\n * queue.\n */\n timeoutMs?: number;\n}\n\nexport interface HttpRequestOptions {\n body?: unknown;\n query?: Record<string, string | undefined>;\n /**\n * Per-request timeout override (ms). Defaults to the client's\n * `timeoutMs` (15s). Pass 0 to disable the timeout entirely —\n * only sensible for tests.\n */\n timeoutMs?: number;\n /**\n * Stripe-style idempotency key. When set, the SDK adds\n * `Idempotency-Key: <value>` to the request. Reuses the SAME key\n * across retries of the SAME logical operation so the server can\n * short-circuit duplicate work without per-event dedup.\n *\n * The SDK supplies this for every batch flush — see `event-queue.ts`.\n */\n idempotencyKey?: string;\n}\n\nexport class HttpClient {\n constructor(private readonly config: HttpClientConfig) {}\n\n /**\n * Issue a request. `path` is relative to the configured baseUrl\n * (\"/entitlements\", \"/identity/alias\", etc.).\n *\n * Throws CrossdeckError on:\n * - Network failure (`type: \"network_error\"`)\n * - Non-2xx response (typed from the body envelope)\n * - JSON parse failure on a 2xx (treated as `internal_error`)\n */\n async request<T>(\n method: \"GET\" | \"POST\",\n path: string,\n options: HttpRequestOptions = {},\n ): Promise<T> {\n const url = this.buildUrl(path, options.query);\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.config.publicKey}`,\n \"Crossdeck-Sdk-Version\": `${SDK_NAME}@${this.config.sdkVersion}`,\n Accept: \"application/json\",\n };\n // Per-platform identity claim — the backend's isIdentityAllowed()\n // enforces these against bundleId / packageName stored on the\n // app key. The caller passes the right identifier at SDK init\n // time (Platform.OS determines which is meaningful); we send\n // BOTH when supplied so a single SDK instance dogfooded across\n // bare iOS + bare Android picks up the right header per\n // platform without the SDK doing runtime detection.\n if (this.config.bundleId) {\n headers[\"X-Crossdeck-Bundle-Id\"] = this.config.bundleId;\n }\n if (this.config.packageName) {\n headers[\"X-Crossdeck-Package-Name\"] = this.config.packageName;\n }\n if (options.idempotencyKey) {\n // Stripe pattern: same key on retries → server can\n // short-circuit duplicate work without inspecting the body.\n headers[\"Idempotency-Key\"] = options.idempotencyKey;\n }\n // Body is always a JSON-serialised string when present. We avoid\n // the BodyInit DOM type so the SDK doesn't need lib.dom in\n // tsconfig — RN's fetch accepts string bodies in every supported\n // engine (Hermes 0.74+, JSC, Node test runtimes).\n let bodyInit: string | undefined;\n if (options.body !== undefined) {\n headers[\"Content-Type\"] = \"application/json\";\n bodyInit = JSON.stringify(options.body);\n }\n\n // ----- Abort timeout -----\n // Wire up an AbortController so a stuck connection (captive\n // portal, satellite link, DNS hang) doesn't lock the queue\n // forever. Per-call `timeoutMs: 0` disables, otherwise fall back\n // to client default (15s).\n const effectiveTimeout =\n options.timeoutMs ?? this.config.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const controller =\n typeof AbortController !== \"undefined\" && effectiveTimeout > 0\n ? new AbortController()\n : null;\n let timeoutHandle: ReturnType<typeof setTimeout> | null = null;\n if (controller && effectiveTimeout > 0) {\n timeoutHandle = setTimeout(() => controller.abort(), effectiveTimeout);\n }\n\n let response: Response;\n try {\n response = await fetch(url, {\n method,\n headers,\n body: bodyInit,\n signal: controller?.signal,\n });\n } catch (err) {\n const aborted = controller?.signal?.aborted === true;\n throw new CrossdeckError({\n type: \"network_error\",\n code: aborted ? \"request_timeout\" : \"fetch_failed\",\n message: aborted\n ? `Request to ${path} aborted after ${effectiveTimeout}ms`\n : err instanceof Error\n ? err.message\n : \"fetch failed\",\n });\n } finally {\n if (timeoutHandle !== null) clearTimeout(timeoutHandle);\n }\n\n if (!response.ok) {\n throw await crossdeckErrorFromResponse(response);\n }\n\n // 204 No Content — return undefined cast as T (callers that\n // don't expect a body shouldn't read it).\n if (response.status === 204) return undefined as T;\n\n try {\n return (await response.json()) as T;\n } catch {\n throw new CrossdeckError({\n type: \"internal_error\",\n code: \"invalid_json_response\",\n message: \"Server returned a 2xx with an unparseable body.\",\n requestId: response.headers.get(\"x-request-id\") ?? undefined,\n status: response.status,\n });\n }\n }\n\n /** Exposed for the error-capture self-skip wiring. */\n get baseUrl(): string {\n return this.config.baseUrl;\n }\n\n private buildUrl(path: string, query?: Record<string, string | undefined>): string {\n const base = this.config.baseUrl.replace(/\\/+$/, \"\");\n const cleanPath = path.startsWith(\"/\") ? path : `/${path}`;\n let url = base + cleanPath;\n if (query) {\n const params = new URLSearchParams();\n for (const [k, v] of Object.entries(query)) {\n if (typeof v === \"string\" && v.length > 0) params.append(k, v);\n }\n const qs = params.toString();\n if (qs) url += (url.includes(\"?\") ? \"&\" : \"?\") + qs;\n }\n return url;\n }\n}\n\n/**\n * Extract the hostname from a URL string for use as the\n * `selfHostname` field on the ErrorTracker. Returns null on\n * malformed input. Lowercased for case-insensitive comparison\n * (`Api.Cross-Deck.com` and `api.cross-deck.com` are the same host).\n */\nexport function extractSelfHostname(baseUrl: string | undefined | null): string | null {\n if (!baseUrl || typeof baseUrl !== \"string\") return null;\n try {\n return new URL(baseUrl).hostname.toLowerCase();\n } catch {\n return null;\n }\n}\n\n/**\n * True when the request URL targets the SDK's own backend hostname.\n * Used by the fetch wrapper to skip captureHttp on Crossdeck's own\n * requests — otherwise a Crossdeck-side outage would recurse\n * (captureHttp → enqueue → /events → fail → captureHttp → …).\n *\n * Strict hostname compare (not substring) so a path like\n * `https://api.cross-deck.com.attacker.example/...` doesn't falsely\n * match `api.cross-deck.com`. Falls back to `false` on malformed\n * URLs — the SDK only ever uses absolute URLs, so a relative URL\n * can't be the SDK's own request.\n */\nexport function isSelfRequest(\n requestUrl: string,\n selfHostname: string | null | undefined,\n): boolean {\n if (!selfHostname || !requestUrl) return false;\n try {\n return new URL(requestUrl).hostname.toLowerCase() === selfHostname;\n } catch {\n return false;\n }\n}\n","/**\n * Identity persistence for the RN SDK.\n *\n * Two values are tracked, mirroring @cross-deck/web exactly:\n *\n * anonymousId — generated on first boot. Persists for the\n * install lifetime so pre-login events stay\n * attached to the same identity graph entry.\n * crossdeckCustomerId — populated after the first identify() or\n * getEntitlements() that resolves a customer.\n * Persisted so subsequent app launches read\n * entitlements directly without an alias call.\n *\n * RN-specific divergence from web:\n *\n * - Storage is ASYNC. Hydration happens in `loadAll()` which\n * `Crossdeck.init()` awaits before any track()/identify() can\n * fire. Subsequent reads are SYNCHRONOUS from the in-memory\n * cache; writes fan out to the async storage fire-and-forget.\n * This matches RN's reality (AsyncStorage cannot be made sync)\n * while preserving the web API shape.\n *\n * - No cookie redundancy. The web SDK writes anonymousId to BOTH\n * localStorage AND a 1st-party cookie because either can be\n * wiped independently (ITP, clear-site-data). RN has only\n * AsyncStorage; native iOS/Android SDKs can fall back to\n * Keychain/KeyStore for reinstall-survival, but the JS layer\n * cannot. Documented honestly — app uninstall means identity\n * reset.\n */\n\nimport type { KeyValueStorage } from \"./types\";\n\nconst KEY_ANON = \"anon_id\";\nconst KEY_CDCUST = \"cdcust_id\";\nconst KEY_DEV_UID = \"developer_user_id\";\n\ninterface IdentityState {\n anonymousId: string;\n crossdeckCustomerId: string | null;\n developerUserId: string | null;\n}\n\nexport class IdentityStore {\n private state: IdentityState | null = null;\n private loaded = false;\n\n constructor(\n private readonly storage: KeyValueStorage,\n private readonly prefix: string,\n ) {}\n\n /**\n * Hydrate from durable storage. `Crossdeck.init()` awaits this\n * before any track()/identify() can fire. If no anonymousId is\n * found we mint one and persist it.\n *\n * Safe to call multiple times — second+ calls are no-ops.\n */\n async loadAll(): Promise<void> {\n if (this.loaded) return;\n const [anon, cdcust, dev] = await Promise.all([\n this.storage.getItem(this.prefix + KEY_ANON),\n this.storage.getItem(this.prefix + KEY_CDCUST),\n this.storage.getItem(this.prefix + KEY_DEV_UID),\n ]);\n const anonymousId = anon ?? mintAnonymousId();\n this.state = {\n anonymousId,\n crossdeckCustomerId: cdcust ?? null,\n developerUserId: dev ?? null,\n };\n if (!anon) {\n // First-launch — persist the fresh anonymousId so the next\n // launch reads it back instead of minting a new one (which\n // would break identity-graph continuity).\n this.fireAndForget(\n this.storage.setItem(this.prefix + KEY_ANON, anonymousId),\n );\n }\n this.loaded = true;\n }\n\n /** Sync read — only valid after loadAll() has resolved. */\n get anonymousId(): string {\n this.ensureLoaded();\n return this.state!.anonymousId;\n }\n\n /** Sync read — null when no customer has been resolved yet. */\n get crossdeckCustomerId(): string | null {\n this.ensureLoaded();\n return this.state!.crossdeckCustomerId;\n }\n\n /** Sync read — null when identify() has not been called this install. */\n get developerUserId(): string | null {\n this.ensureLoaded();\n return this.state!.developerUserId;\n }\n\n /** Persist a newly-resolved Crossdeck customer ID. */\n setCrossdeckCustomerId(value: string): void {\n this.ensureLoaded();\n this.state!.crossdeckCustomerId = value;\n this.fireAndForget(\n this.storage.setItem(this.prefix + KEY_CDCUST, value),\n );\n }\n\n /** Persist the developer-supplied user ID across launches. */\n setDeveloperUserId(value: string | null): void {\n this.ensureLoaded();\n this.state!.developerUserId = value;\n if (value === null) {\n this.fireAndForget(this.storage.removeItem(this.prefix + KEY_DEV_UID));\n } else {\n this.fireAndForget(this.storage.setItem(this.prefix + KEY_DEV_UID, value));\n }\n }\n\n /**\n * Wipe persisted identity. Called by reset() — used when an\n * end-user logs out. After reset the SDK mints a new anonymousId\n * so the next pre-login session is a fresh customer in the\n * identity graph.\n */\n reset(): void {\n this.ensureLoaded();\n const fresh = mintAnonymousId();\n this.state = {\n anonymousId: fresh,\n crossdeckCustomerId: null,\n developerUserId: null,\n };\n this.fireAndForget(this.storage.removeItem(this.prefix + KEY_CDCUST));\n this.fireAndForget(this.storage.removeItem(this.prefix + KEY_DEV_UID));\n this.fireAndForget(this.storage.setItem(this.prefix + KEY_ANON, fresh));\n }\n\n private ensureLoaded(): void {\n if (!this.loaded) {\n throw new Error(\n \"IdentityStore: loadAll() must complete before reading identity. \" +\n \"This is an internal SDK bug — please report.\",\n );\n }\n }\n\n private fireAndForget(promise: Promise<unknown>): void {\n promise.catch(() => {\n // Best-effort persistence — the in-memory cache is authoritative\n // for this session. Silent failure here is documented behaviour\n // for AsyncStorage adapters that can throw under quota / IO\n // pressure.\n });\n }\n}\n\n/**\n * Generate an anonymousId. Crockford-ish base36 timestamp + random\n * suffix. Same shape Stripe / Segment / others use — sortable,\n * log-friendly, no PII.\n */\nexport function mintAnonymousId(): string {\n const ts = Date.now().toString(36);\n const rand = randomChars(10);\n return `anon_${ts}${rand}`;\n}\n\n/**\n * Generate a cryptographically-random short string. Uses\n * `crypto.getRandomValues` when available (Hermes 0.74+, modern JSC,\n * Node's webcrypto), else falls back to Math.random with a\n * time-tail.\n *\n * The fallback is safe here because anonymousId entropy doesn't\n * need to resist offline brute force; it needs to be\n * unique-with-overwhelming-probability across one device's lifetime.\n */\nexport function randomChars(count: number): string {\n const alphabet = \"0123456789abcdefghijklmnopqrstuvwxyz\";\n const out: string[] = [];\n const cryptoApi = (globalThis as {\n crypto?: { getRandomValues?: (a: Uint8Array) => Uint8Array };\n }).crypto;\n if (cryptoApi?.getRandomValues) {\n const buf = new Uint8Array(count);\n cryptoApi.getRandomValues(buf);\n for (let i = 0; i < count; i++) {\n out.push(alphabet[buf[i]! % alphabet.length] ?? \"0\");\n }\n } else {\n for (let i = 0; i < count; i++) {\n out.push(alphabet[Math.floor(Math.random() * alphabet.length)] ?? \"0\");\n }\n }\n return out.join(\"\");\n}\n","/**\n * Minimal synchronous SHA-256 implementation (FIPS 180-4).\n *\n * Mirrors `sdks/web/src/hash.ts` line-for-line — kept duplicated\n * intentionally so each public SDK package ships zero shared\n * source (`publish-sdk-to-public-repo.mjs` copies per-SDK trees\n * verbatim). The two files MUST stay byte-identical except for\n * import lines.\n *\n * Why a pure-JS impl on React Native: SubtleCrypto.digest is not\n * available on stock Hermes/JSC. A polyfill (`react-native-quick-\n * crypto`) is common but a hard dep we shouldn't take. Pure JS\n * SHA-256 is ~80 lines, ~3KB minified, and runs in well under a\n * millisecond for typical userId inputs — the cost is\n * unobservable on the identify() hot path.\n *\n * Used to derive a per-user storage suffix for the entitlement\n * cache so each developerUserId's data lives under a physically\n * separate AsyncStorage key. Bank-grade isolation contract: even\n * a botched identify() that skips the in-memory clear cannot\n * cross-read a different user's cached entitlements.\n */\n\nconst K = new Uint32Array([\n 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,\n 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,\n 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,\n 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,\n 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,\n 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,\n 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,\n 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,\n 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,\n 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,\n 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,\n]);\n\nfunction utf8Bytes(input: string): Uint8Array {\n if (typeof TextEncoder !== \"undefined\") {\n return new TextEncoder().encode(input);\n }\n const out: number[] = [];\n for (let i = 0; i < input.length; i++) {\n let codePoint = input.charCodeAt(i);\n if (codePoint >= 0xd800 && codePoint <= 0xdbff && i + 1 < input.length) {\n const next = input.charCodeAt(i + 1);\n if (next >= 0xdc00 && next <= 0xdfff) {\n codePoint = 0x10000 + ((codePoint - 0xd800) << 10) + (next - 0xdc00);\n i++;\n }\n }\n if (codePoint < 0x80) {\n out.push(codePoint);\n } else if (codePoint < 0x800) {\n out.push(0xc0 | (codePoint >> 6));\n out.push(0x80 | (codePoint & 0x3f));\n } else if (codePoint < 0x10000) {\n out.push(0xe0 | (codePoint >> 12));\n out.push(0x80 | ((codePoint >> 6) & 0x3f));\n out.push(0x80 | (codePoint & 0x3f));\n } else {\n out.push(0xf0 | (codePoint >> 18));\n out.push(0x80 | ((codePoint >> 12) & 0x3f));\n out.push(0x80 | ((codePoint >> 6) & 0x3f));\n out.push(0x80 | (codePoint & 0x3f));\n }\n }\n return new Uint8Array(out);\n}\n\nexport function sha256Hex(input: string): string {\n const bytes = utf8Bytes(input);\n const bitLength = bytes.length * 8;\n const blockCount = Math.floor((bytes.length + 9 + 63) / 64);\n const padded = new Uint8Array(blockCount * 64);\n padded.set(bytes);\n padded[bytes.length] = 0x80;\n const high = Math.floor(bitLength / 0x100000000);\n const low = bitLength >>> 0;\n const lenOffset = padded.length - 8;\n padded[lenOffset + 0] = (high >>> 24) & 0xff;\n padded[lenOffset + 1] = (high >>> 16) & 0xff;\n padded[lenOffset + 2] = (high >>> 8) & 0xff;\n padded[lenOffset + 3] = high & 0xff;\n padded[lenOffset + 4] = (low >>> 24) & 0xff;\n padded[lenOffset + 5] = (low >>> 16) & 0xff;\n padded[lenOffset + 6] = (low >>> 8) & 0xff;\n padded[lenOffset + 7] = low & 0xff;\n\n const H = new Uint32Array([\n 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c,\n 0x1f83d9ab, 0x5be0cd19,\n ]);\n const W = new Uint32Array(64);\n\n // Non-null assertions throughout: the loop bounds + Uint8Array /\n // Uint32Array allocations guarantee every indexed access is\n // in-bounds. TypeScript's noUncheckedIndexedAccess flags the\n // `T | undefined` shape regardless; the `!` here is correctness-\n // preserving suppression, not a hope-and-pray.\n for (let block = 0; block < blockCount; block++) {\n const offset = block * 64;\n for (let t = 0; t < 16; t++) {\n W[t] =\n ((padded[offset + t * 4]! << 24) |\n (padded[offset + t * 4 + 1]! << 16) |\n (padded[offset + t * 4 + 2]! << 8) |\n padded[offset + t * 4 + 3]!) >>>\n 0;\n }\n for (let t = 16; t < 64; t++) {\n const w15 = W[t - 15]!;\n const w2 = W[t - 2]!;\n const s0 = ((w15 >>> 7) | (w15 << 25)) ^ ((w15 >>> 18) | (w15 << 14)) ^ (w15 >>> 3);\n const s1 = ((w2 >>> 17) | (w2 << 15)) ^ ((w2 >>> 19) | (w2 << 13)) ^ (w2 >>> 10);\n W[t] = (W[t - 16]! + s0 + W[t - 7]! + s1) >>> 0;\n }\n\n let a = H[0]!, b = H[1]!, c = H[2]!, d = H[3]!;\n let e = H[4]!, f = H[5]!, g = H[6]!, h = H[7]!;\n\n for (let t = 0; t < 64; t++) {\n const S1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7));\n const ch = (e & f) ^ (~e & g);\n const temp1 = (h + S1 + ch + K[t]! + W[t]!) >>> 0;\n const S0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10));\n const maj = (a & b) ^ (a & c) ^ (b & c);\n const temp2 = (S0 + maj) >>> 0;\n h = g;\n g = f;\n f = e;\n e = (d + temp1) >>> 0;\n d = c;\n c = b;\n b = a;\n a = (temp1 + temp2) >>> 0;\n }\n\n H[0] = (H[0]! + a) >>> 0;\n H[1] = (H[1]! + b) >>> 0;\n H[2] = (H[2]! + c) >>> 0;\n H[3] = (H[3]! + d) >>> 0;\n H[4] = (H[4]! + e) >>> 0;\n H[5] = (H[5]! + f) >>> 0;\n H[6] = (H[6]! + g) >>> 0;\n H[7] = (H[7]! + h) >>> 0;\n }\n\n let hex = \"\";\n for (let i = 0; i < 8; i++) {\n hex += H[i]!.toString(16).padStart(8, \"0\");\n }\n return hex;\n}\n","/**\n * Durable last-known-good cache of the customer's entitlements.\n *\n * This cache is NOT a second source of truth. Crossdeck remains the\n * only source; this is the SDK's local copy of what the server last\n * told us — a cache that doesn't forget during a network partition.\n *\n * Durability contract (the RevenueCat model):\n * - Every successful server read is persisted to device storage\n * (AsyncStorage, via the SDK's storage adapter).\n * - On SDK boot the cache hydrates from storage, so `isEntitled()`\n * answers correctly from the very first call after `init()`\n * resolves — no cold-start window where a returning Pro\n * customer reads as free.\n * - When the server is unreachable, the SDK keeps serving the\n * last entitlements it successfully fetched. A failed refresh\n * never reaches `setFromList()`, so it cannot clear the cache;\n * only a SUCCESSFUL fetch replaces it. An outage can never fail\n * a paying customer down to free.\n * - Staleness alone never returns false. Each entitlement is\n * honoured against its OWN `validUntil` instead — a time-based\n * trial expiry still applies even mid-partition.\n * - Staleness is VISIBLE, not silent. Once a refresh ATTEMPT\n * fails (or the data ages past `staleAfterMs`) the cache is\n * marked stale — `isStale` / `freshness` are surfaced in\n * `diagnostics()`. It keeps serving last-known-good; the\n * staleness is no longer hidden.\n *\n * Reactive listener API\n * ---------------------\n * `subscribe(listener)` registers a callback fired every time the\n * cache mutates (setFromList or clear) — the foundation for any\n * future RN hook (`useEntitlement(key)`). Semantics:\n * - Fired AFTER the mutation, so the listener sees fresh state.\n * - Fire-and-forget: a throwing listener is swallowed (and\n * counted) so a buggy consumer can't crash the SDK or other\n * listeners.\n * - Unsubscribe is idempotent.\n *\n * RN-specific divergence from web:\n *\n * - `hydrate()` is ASYNC. `Crossdeck.init()` awaits it before any\n * `isEntitled()` call would see an empty cache. Subsequent\n * reads are sync from the in-memory cache; writes fan out to\n * async storage fire-and-forget.\n */\n\nimport { sha256Hex } from \"./hash\";\nimport type { KeyValueStorage, PublicEntitlement } from \"./types\";\n\nexport type EntitlementsListener = (entitlements: PublicEntitlement[]) => void;\n\n/** Shape of the blob persisted to device storage. Versioned for forward-compat. */\ninterface PersistedCache {\n v: 1;\n entitlements: PublicEntitlement[];\n lastUpdated: number;\n}\n\n/** Default staleness window — data older than this is flagged even with no failed refresh. */\nconst DEFAULT_STALE_AFTER_MS = 24 * 60 * 60 * 1000; // 24h\n\n/** Anonymous suffix used before identify() has been called. */\nconst ANON_SUFFIX = \"_anon\";\n\n/** Suffix for the index entry that tracks every per-user key we've\n * written. Used by clearAll() to scope a logout-wipe to ONLY\n * Crossdeck keys, never the host app's own AsyncStorage. */\nconst INDEX_SUFFIX = \"_index\";\n\nexport class EntitlementCache {\n private all: PublicEntitlement[] = [];\n private lastUpdated = 0;\n private lastRefreshFailedAt = 0;\n private listeners = new Set<EntitlementsListener>();\n private listenerErrorCount = 0;\n private hydratedSuffixes = new Set<string>();\n private readonly storage: KeyValueStorage;\n private readonly storageKeyPrefix: string;\n private readonly staleAfterMs: number;\n private currentSuffix: string = ANON_SUFFIX;\n\n /**\n * @param storage Device storage adapter.\n * @param storageKeyPrefix Prefix used to derive per-user storage keys\n * (`<prefix>:<sha256(userId)>`). Default\n * `crossdeck:entitlements`. The trailing\n * user suffix is filled at identify() /\n * reset() time — see [[setUserKey]].\n * @param staleAfterMs Age past which last-known-good is flagged stale\n * even without a failed refresh. Default 24h.\n */\n constructor(\n storage: KeyValueStorage,\n storageKeyPrefix = \"crossdeck:entitlements\",\n staleAfterMs = DEFAULT_STALE_AFTER_MS,\n ) {\n this.storage = storage;\n this.storageKeyPrefix = storageKeyPrefix;\n this.staleAfterMs = staleAfterMs;\n }\n\n /** The full storage key the current-user blob is persisted under. */\n private get storageKey(): string {\n return `${this.storageKeyPrefix}:${this.currentSuffix}`;\n }\n\n /** Key of the index blob — a JSON array of every suffix we've\n * written. Used by clearAll() to scope a logout-wipe. */\n private get indexKey(): string {\n return `${this.storageKeyPrefix}:${INDEX_SUFFIX}`;\n }\n\n /** Derive a stable suffix for a developerUserId via SHA-256. */\n static suffixForUserId(userId: string | null): string {\n if (userId == null || userId === \"\") return ANON_SUFFIX;\n return sha256Hex(userId);\n }\n\n /**\n * Load last-known-good from device storage for the CURRENT\n * suffix. Run during `Crossdeck.init()` (anonymous slot) and\n * after every [[setUserKey]] switch. Idempotent per suffix —\n * a repeat call for the same suffix is a no-op.\n */\n async hydrate(): Promise<void> {\n const suffix = this.currentSuffix;\n if (this.hydratedSuffixes.has(suffix)) return;\n try {\n const raw = await this.storage.getItem(this.storageKey);\n if (raw) {\n const parsed = JSON.parse(raw) as PersistedCache;\n if (parsed && parsed.v === 1 && Array.isArray(parsed.entitlements)) {\n this.all = parsed.entitlements;\n this.lastUpdated =\n typeof parsed.lastUpdated === \"number\" ? parsed.lastUpdated : 0;\n }\n }\n } catch {\n // Storage unavailable / corrupt blob → start empty.\n }\n this.hydratedSuffixes.add(suffix);\n }\n\n /**\n * Switch the cache to a different user's storage slot. Bank-grade\n * three-layer isolation (v1.4.0 Phase 1.3):\n * (a) Physical key separation — `<prefix>:<sha256(userId)>` so\n * a user-switch can't physically read prior user's data\n * even if the in-memory clear was skipped.\n * (b) Unconditional in-memory clear — invoked whenever the\n * active suffix changes, even on same-id re-identify.\n * (c) Re-hydrate from the new slot — a returning user observes\n * their last-known-good cache from storage immediately.\n *\n * Caller (identify() / reset()) MUST `await` this BEFORE the\n * next `setFromList()` so the write lands under the right key.\n */\n async setUserKey(userId: string | null): Promise<void> {\n const nextSuffix = EntitlementCache.suffixForUserId(userId);\n // Always wipe in-memory + clear stale-fail flags so an\n // unconditional clear-on-identify is honoured even on\n // same-id re-identify.\n this.all = [];\n this.lastUpdated = 0;\n this.lastRefreshFailedAt = 0;\n if (nextSuffix !== this.currentSuffix) {\n this.currentSuffix = nextSuffix;\n // Force re-hydrate even if we'd previously hydrated this\n // suffix — the durable blob may have been overwritten by a\n // parallel tab / app instance.\n this.hydratedSuffixes.delete(nextSuffix);\n }\n await this.hydrate();\n this.notify();\n }\n\n /**\n * Sync read — true iff the entitlement is currently granting\n * access. Served from last-known-good: a stale cache (server\n * unreachable since the last successful fetch) still answers true\n * for a still-valid entitlement. The ONLY thing that turns it\n * false is the entitlement's own expiry (`validUntil`) — never\n * overall cache staleness.\n */\n isEntitled(key: string): boolean {\n const nowSec = Date.now() / 1000;\n return this.all.some(\n (e) =>\n e.key === key &&\n e.isActive &&\n (e.validUntil == null || e.validUntil > nowSec),\n );\n }\n\n /** Full snapshot for callers that need source / validUntil details. */\n list(): PublicEntitlement[] {\n return this.all.slice();\n }\n\n /** When the cache was last refreshed from the server. 0 means \"never\". */\n get freshness(): number {\n return this.lastUpdated;\n }\n\n /**\n * Whether the cache is knowingly serving older-than-trustworthy\n * data. True when the most recent refresh ATTEMPT failed (Crossdeck\n * unreachable since the last success), OR when last-known-good has\n * aged past `staleAfterMs`.\n *\n * `isStale` never changes what `isEntitled()` returns — the cache\n * still serves last-known-good. It exists so the staleness is\n * observable (`diagnostics()`) instead of an unbounded silent\n * window where a revoked customer holds access with nobody able\n * to see it.\n */\n get isStale(): boolean {\n if (this.lastRefreshFailedAt > this.lastUpdated) return true;\n return (\n this.lastUpdated > 0 &&\n Date.now() - this.lastUpdated > this.staleAfterMs\n );\n }\n\n /** Epoch ms of the last failed refresh attempt. 0 if none since the last success. */\n get refreshFailedAt(): number {\n return this.lastRefreshFailedAt;\n }\n\n get listenerErrors(): number {\n return this.listenerErrorCount;\n }\n\n /**\n * Record that a refresh attempt failed (Crossdeck unreachable /\n * transient error). The SDK's getEntitlements() calls this in its\n * catch path. Does NOT touch the cached entitlements —\n * last-known-good keeps serving — it only flips `isStale` so the\n * staleness shows up in `diagnostics()` rather than being silent.\n */\n markRefreshFailed(): void {\n this.lastRefreshFailedAt = Date.now();\n }\n\n /**\n * Replace the cache with a fresh server response and persist it\n * to device storage so it survives an app cold launch.\n *\n * Called ONLY after a successful server read — a failed fetch\n * throws before it reaches here, so last-known-good is preserved\n * through an outage. A success also clears the stale flag.\n */\n setFromList(entitlements: PublicEntitlement[]): void {\n this.all = entitlements.slice();\n this.lastUpdated = Date.now();\n this.lastRefreshFailedAt = 0;\n this.persist();\n void this.recordSuffixInIndex(this.currentSuffix);\n this.notify();\n }\n\n /**\n * Wipe the CURRENT user's slot. Used internally when a single\n * user's cache needs to be invalidated. The full-logout path is\n * [[clearAll]].\n */\n clear(): void {\n this.all = [];\n this.lastUpdated = 0;\n this.lastRefreshFailedAt = 0;\n const suffix = this.currentSuffix;\n void this.storage.removeItem(this.storageKey).catch(() => {});\n void this.removeSuffixFromIndex(suffix);\n this.notify();\n }\n\n /**\n * Logout-grade wipe — bank-grade contract: removes EVERY per-user\n * entitlement slot the SDK has ever written on this device, then\n * clears the index. Used by `Crossdeck.reset()` so a logout on a\n * shared device can never leave another user's entitlements\n * readable (layer (c) of the v1.4.0 isolation fix).\n *\n * Async to honour the AsyncStorage contract; safe to `void` if\n * the caller doesn't need to await teardown completion.\n */\n async clearAll(): Promise<void> {\n this.all = [];\n this.lastUpdated = 0;\n this.lastRefreshFailedAt = 0;\n this.currentSuffix = ANON_SUFFIX;\n this.hydratedSuffixes.clear();\n const suffixes = await this.readIndex();\n await Promise.all(\n suffixes.map((s) =>\n this.storage.removeItem(`${this.storageKeyPrefix}:${s}`).catch(() => {}),\n ),\n );\n // Also remove the anonymous slot explicitly — it may not have\n // been indexed if the cache was wiped before its first write.\n await this.storage.removeItem(`${this.storageKeyPrefix}:${ANON_SUFFIX}`).catch(() => {});\n await this.storage.removeItem(this.indexKey).catch(() => {});\n this.notify();\n }\n\n /**\n * Subscribe to cache mutations. Returns an idempotent unsubscribe\n * fn. The listener fires AFTER `setFromList()` or `clear()` with\n * the current snapshot.\n */\n subscribe(listener: EntitlementsListener): () => void {\n this.listeners.add(listener);\n let unsubscribed = false;\n return () => {\n if (unsubscribed) return;\n unsubscribed = true;\n this.listeners.delete(listener);\n };\n }\n\n // ----- Durable persistence -----\n\n private persist(): void {\n let blob: string;\n try {\n const data: PersistedCache = {\n v: 1,\n entitlements: this.all,\n lastUpdated: this.lastUpdated,\n };\n blob = JSON.stringify(data);\n } catch {\n return;\n }\n void this.storage.setItem(this.storageKey, blob).catch(() => {\n // Quota / IO error — silent degrade. In-memory still works for\n // this session; we just lose cross-launch durability.\n });\n }\n\n /** Read the index of all per-user suffixes the SDK has written. */\n private async readIndex(): Promise<string[]> {\n try {\n const raw = await this.storage.getItem(this.indexKey);\n if (!raw) return [];\n const parsed = JSON.parse(raw);\n if (Array.isArray(parsed)) {\n return parsed.filter((x): x is string => typeof x === \"string\");\n }\n return [];\n } catch {\n return [];\n }\n }\n\n /** Add a suffix to the persisted index. Idempotent. */\n private async recordSuffixInIndex(suffix: string): Promise<void> {\n const existing = await this.readIndex();\n if (existing.includes(suffix)) return;\n existing.push(suffix);\n await this.storage.setItem(this.indexKey, JSON.stringify(existing)).catch(() => {});\n }\n\n /** Remove a suffix from the persisted index. No-op if absent. */\n private async removeSuffixFromIndex(suffix: string): Promise<void> {\n const existing = await this.readIndex();\n const next = existing.filter((s) => s !== suffix);\n if (next.length === existing.length) return;\n if (next.length === 0) {\n await this.storage.removeItem(this.indexKey).catch(() => {});\n } else {\n await this.storage.setItem(this.indexKey, JSON.stringify(next)).catch(() => {});\n }\n }\n\n private notify(): void {\n if (this.listeners.size === 0) return;\n const snapshot = this.all.slice();\n const listenersSnapshot = [...this.listeners];\n for (const listener of listenersSnapshot) {\n try {\n listener(snapshot);\n } catch {\n this.listenerErrorCount += 1;\n }\n }\n }\n}\n","/**\n * Deterministic Idempotency-Key derivation for /purchases/sync.\n *\n * Phase 2.2 of bank-grade reconciliation v1.4.0. Mirrors\n * `sdks/web/src/idempotency-key.ts` byte-for-byte except for the\n * import line — the publish-sdk-to-public-repo contract copies\n * each per-SDK source tree verbatim, so duplication keeps each\n * package self-contained.\n */\n\nimport { sha256Hex } from \"./hash\";\n\nexport interface PurchaseSyncIdentity {\n rail: \"apple\" | \"google\" | \"stripe\" | string;\n signedTransactionInfo?: string;\n purchaseToken?: string;\n}\n\nexport function formatAsUuid(hex: string): string {\n return [\n hex.slice(0, 8),\n hex.slice(8, 12),\n hex.slice(12, 16),\n hex.slice(16, 20),\n hex.slice(20, 32),\n ].join(\"-\");\n}\n\nexport function deriveIdempotencyKeyForPurchase(body: PurchaseSyncIdentity): string {\n let identifier: string;\n if (body.rail === \"apple\") {\n identifier = body.signedTransactionInfo ?? \"\";\n } else if (body.rail === \"google\") {\n identifier = body.purchaseToken ?? \"\";\n } else {\n identifier = \"\";\n }\n if (!identifier) {\n throw new Error(\n `deriveIdempotencyKeyForPurchase: no stable identifier in body ` +\n `(rail=${body.rail}). Apple needs signedTransactionInfo; ` +\n `Google needs purchaseToken.`,\n );\n }\n const namespaced = `crossdeck:purchases/sync:${body.rail}:${identifier}`;\n return formatAsUuid(sha256Hex(namespaced));\n}\n","/**\n * Retry policy for the event-queue flush.\n *\n * Verbatim port of @cross-deck/web's retry-policy. After a failed\n * flush, the queue waits before re-attempting — otherwise a flapping\n * backend causes a hot loop, and a 429 \"slow down\" goes ignored.\n *\n * Policy:\n * - Exponential backoff: `base * 2^attempts`, capped at `maxMs`.\n * - Full jitter: result is multiplied by Math.random() so 100 SDK\n * instances retrying the same downed endpoint don't all hammer\n * at the same instant.\n * - 429 / 503 `Retry-After`: ALWAYS honour the server-supplied\n * delay when it's larger than our computed backoff. Capped at\n * 24h as a sanity guard against absurd values (server bug /\n * HTTP-date clock-skew). The server knows its own capacity\n * better than we do; ignoring it gets your IP blocked.\n * - Reset on success.\n *\n * Pure: no state on the function, no timers. The EventQueue owns\n * timers; the policy owns the math.\n */\n\nexport interface RetryPolicyOptions {\n baseMs?: number;\n maxMs?: number;\n factor?: number;\n /** Number of consecutive failures before flagging diagnostics. Default 8. */\n failuresBeforeWarn?: number;\n}\n\nconst DEFAULT_BASE = 1000;\nconst DEFAULT_MAX = 60_000;\nconst DEFAULT_FACTOR = 2;\nconst DEFAULT_WARN = 8;\n\nexport function computeNextDelay(\n attempts: number,\n retryAfterMs: number | undefined,\n options: RetryPolicyOptions = {},\n random: () => number = Math.random,\n): number {\n const base = options.baseMs ?? DEFAULT_BASE;\n const max = options.maxMs ?? DEFAULT_MAX;\n const factor = options.factor ?? DEFAULT_FACTOR;\n const safeAttempts = Math.min(attempts, 30);\n const ceiling = Math.min(max, base * Math.pow(factor, safeAttempts));\n const jittered = ceiling * random();\n // Honour the server-supplied delay above our computed window, but\n // cap at 24h as a sanity guard (server bug / HTTP-date clock-skew\n // could produce absurd values that would wedge the queue for years\n // otherwise).\n if (retryAfterMs !== undefined) {\n const ABSOLUTE_MAX_MS = 24 * 60 * 60 * 1000;\n const honoured = Math.min(ABSOLUTE_MAX_MS, retryAfterMs);\n if (honoured > jittered) return honoured;\n }\n return Math.max(0, Math.round(jittered));\n}\n\nexport class RetryPolicy {\n private attempts = 0;\n constructor(private readonly options: RetryPolicyOptions = {}) {}\n\n get consecutiveFailures(): number {\n return this.attempts;\n }\n\n get isWarning(): boolean {\n return this.attempts >= (this.options.failuresBeforeWarn ?? DEFAULT_WARN);\n }\n\n nextDelay(retryAfterMs?: number, random: () => number = Math.random): number {\n const delay = computeNextDelay(this.attempts, retryAfterMs, this.options, random);\n this.attempts += 1;\n return delay;\n }\n\n recordSuccess(): void {\n this.attempts = 0;\n }\n}\n","/**\n * Local event queue + batched flush.\n *\n * Why a queue: track() is called from hot paths (button taps, screen\n * mounts) and shouldn't block the UI on a network round-trip. Events\n * go into a local buffer, flushed in bursts.\n *\n * Flush triggers:\n * - Buffer reaches batchSize (default 20) → flush immediately.\n * - intervalMs of inactivity (default 5000ms) → flush idle batch.\n * - flush() called explicitly (e.g. when the app is backgrounding).\n *\n * Bank-grade durability + idempotency contract:\n *\n * - `pendingBatch` slot. Events spliced for the current flush sit\n * here until the server confirms them. On a retryable failure\n * the same batch is re-attempted with the SAME\n * `Idempotency-Key` so the backend can short-circuit duplicate\n * work (Stripe pattern). On success the slot clears + buffer\n * drains.\n *\n * - `persistAll()`. Persisted blob always carries\n * `[...pendingBatch, ...buffer]` so an app crash mid-flight\n * replays the in-flight batch on the next launch. The backend\n * dedupes via Firestore create-only on (projectId, eventId), so\n * re-sending events that may have already landed is safe.\n *\n * - Exponential backoff with full jitter (see retry-policy.ts) on\n * network / 5xx / 408 / 429 failures. Honours server\n * `Retry-After` when bigger than the computed window.\n *\n * - 4xx hard-stop (`isPermanent4xx`). 400 / 401 / 403 / 404 / 422\n * etc. drop the batch loudly: `onPermanentFailure` callback +\n * `console.error` regardless of debug mode + `dropped` counter\n * increments. Pre-fix every error retried forever with the same\n * key, silently growing the backlog while customers thought\n * events were landing.\n *\n * - Hard buffer cap (1000 events). Past the cap we evict the\n * OLDEST events and increment `dropped` so the developer can\n * see the loss in `diagnostics()`.\n */\n\nimport type { HttpClient } from \"./http\";\nimport type { CrossdeckError } from \"./errors\";\nimport type { EventProperties, IngestResponse } from \"./types\";\nimport { RetryPolicy, type RetryPolicyOptions } from \"./retry-policy\";\nimport type { PersistentEventStore } from \"./event-storage\";\nimport { randomChars } from \"./identity\";\n\nconst HARD_BUFFER_CAP = 1000;\n\nexport interface QueuedEvent {\n eventId: string;\n name: string;\n timestamp: number;\n properties: EventProperties;\n // identity hint — at least anonymousId is always set\n developerUserId?: string;\n anonymousId?: string;\n crossdeckCustomerId?: string;\n}\n\nexport interface BatchEnvelope {\n appId: string;\n environment: \"production\" | \"sandbox\";\n sdk: { name: string; version: string };\n}\n\nexport interface EventQueueConfig {\n http: HttpClient;\n batchSize: number;\n intervalMs: number;\n /**\n * Returns the NorthStar §13.1 envelope to attach to each batch\n * POST. It's a function (not a value) so a future config swap can\n * update the envelope without re-instantiating the queue.\n */\n envelope: () => BatchEnvelope;\n /** Schedule a function to run after `ms` ms. Default: setTimeout. */\n scheduler?: (fn: () => void, ms: number) => () => void;\n /** Called when the SDK drops events because the buffer is full. */\n onDrop?: (dropped: number) => void;\n /** Called once after the first successful flush — drives the §16 \"First event sent\" signal. */\n onFirstFlushSuccess?: () => void;\n /**\n * Durable persistence. When supplied, every buffer mutation is\n * written through to the store; on `hydrate()`, persisted events\n * are loaded back into the buffer.\n */\n persistentStore?: PersistentEventStore;\n /** Retry policy overrides for failed flushes. */\n retry?: RetryPolicyOptions;\n /**\n * Called whenever an item is added to the buffer or removed by a\n * successful flush. Exposed so the host SDK can surface live\n * queue stats via `diagnostics()` without polling.\n */\n onBufferChange?: (size: number) => void;\n /**\n * Surface for the SDK's debug logger to record retry scheduling +\n * persistence events. Fired async — never throws.\n */\n onRetryScheduled?: (info: {\n delayMs: number;\n consecutiveFailures: number;\n retryAfterMs?: number;\n lastError: string;\n }) => void;\n /**\n * Fired when the queue DROPS a batch because the server returned\n * a permanent 4xx (anything except 408 / 429). The host SDK\n * should surface this loudly — pre-fix the queue retried 4xx\n * errors forever with the same Idempotency-Key, silently growing\n * the backlog while the customer thought events were landing.\n * Common causes:\n * - 401: publishable key revoked / rotated\n * - 403: lacking permission for the project\n * - 400/422: malformed batch (schema mismatch, oversized event)\n * - 404: endpoint doesn't exist (typo'd baseUrl)\n */\n onPermanentFailure?: (info: {\n status: number;\n droppedCount: number;\n lastError: string;\n }) => void;\n}\n\nexport interface EventQueueStats {\n buffered: number;\n dropped: number;\n inFlight: number;\n lastFlushAt: number;\n lastError: string | null;\n /** Consecutive flush failures since the last success. */\n consecutiveFailures: number;\n /** Set when the next flush is scheduled by the retry policy. */\n nextRetryAt: number | null;\n}\n\nexport class EventQueue {\n private buffer: QueuedEvent[] = [];\n /**\n * In-flight events that have been spliced from `buffer` for the\n * current batch but haven't yet been confirmed (success or\n * permanent failure). On a retry-driven re-flush we re-use this\n * slot alongside `pendingBatchId` so the Stripe-style\n * `Idempotency-Key` is preserved across retries of the SAME\n * logical batch.\n */\n private pendingBatch: QueuedEvent[] | null = null;\n private pendingBatchId: string | null = null;\n private dropped = 0;\n private inFlight = 0;\n private lastFlushAt = 0;\n private lastError: string | null = null;\n private cancelTimer: (() => void) | null = null;\n private firstFlushFired = false;\n private nextRetryAt: number | null = null;\n private readonly retry: RetryPolicy;\n private readonly persistent: PersistentEventStore | null;\n\n constructor(private readonly cfg: EventQueueConfig) {\n this.retry = new RetryPolicy(cfg.retry ?? {});\n this.persistent = cfg.persistentStore ?? null;\n }\n\n /**\n * Async hydration. Called by `Crossdeck.init()` before any\n * track() call can fire so the persisted queue is rehydrated +\n * an idle flush is scheduled for it. RN-specific: the web SDK\n * does this synchronously in its constructor (localStorage is\n * sync); we can't.\n */\n async hydrate(): Promise<void> {\n if (!this.persistent) return;\n const restored = await this.persistent.load();\n if (restored.length === 0) return;\n if (restored.length > HARD_BUFFER_CAP) {\n this.dropped += restored.length - HARD_BUFFER_CAP;\n this.buffer = restored.slice(restored.length - HARD_BUFFER_CAP);\n } else {\n this.buffer = restored;\n }\n this.cfg.onBufferChange?.(this.buffer.length);\n // Schedule an immediate idle flush so rehydrated events land\n // on the next tick — even if no new track() call comes in.\n this.scheduleIdleFlush();\n }\n\n enqueue(event: QueuedEvent): void {\n this.buffer.push(event);\n if (this.buffer.length > HARD_BUFFER_CAP) {\n const overflow = this.buffer.length - HARD_BUFFER_CAP;\n this.buffer.splice(0, overflow);\n this.dropped += overflow;\n this.cfg.onDrop?.(overflow);\n }\n this.cfg.onBufferChange?.(this.buffer.length);\n this.persistAll();\n if (this.buffer.length >= this.cfg.batchSize) {\n void this.flush();\n } else {\n this.scheduleIdleFlush();\n }\n }\n\n /**\n * Flush the buffer to /v1/events. Resolves when the network call\n * completes (success or failure).\n *\n * Three terminal states from one call:\n * - 2xx success: pendingBatch cleared, persisted state collapses\n * to just `buffer` (any new events that arrived during\n * in-flight).\n * - 4xx permanent (except 408/429): pendingBatch DROPPED,\n * `dropped` increments, `onPermanentFailure` fires. The server\n * is telling us our request is malformed / key revoked / no\n * permission — retrying with the same key forever just grows\n * the queue.\n * - 5xx / network / 408 / 429: pendingBatch + batchId stay;\n * backoff schedules a retry; the next `flush()` re-uses both.\n */\n async flush(): Promise<IngestResponse | null> {\n // Resume an in-flight batch retry path: if we already have a\n // pending batch (prior flush failed, retry timer / caller is\n // re-invoking), re-attempt with the SAME batchId. Stripe\n // Idempotency-Key reuse contract.\n let batch: QueuedEvent[];\n let batchId: string;\n if (this.pendingBatch !== null && this.pendingBatchId !== null) {\n batch = this.pendingBatch;\n batchId = this.pendingBatchId;\n } else {\n if (this.buffer.length === 0) return null;\n batch = this.buffer.splice(0);\n batchId = this.mintBatchId();\n this.pendingBatch = batch;\n this.pendingBatchId = batchId;\n this.inFlight += batch.length;\n this.cfg.onBufferChange?.(this.buffer.length);\n // Persisted state continues to include this batch via\n // persistAll() until the server confirms it — that's the\n // durability fix.\n this.persistAll();\n }\n this.cancelTimerIfSet();\n this.nextRetryAt = null;\n\n try {\n const env = this.cfg.envelope();\n const result = await this.cfg.http.request<IngestResponse>(\"POST\", \"/events\", {\n body: {\n appId: env.appId,\n environment: env.environment,\n sdk: env.sdk,\n events: batch,\n },\n idempotencyKey: batchId,\n });\n this.lastFlushAt = Date.now();\n this.lastError = null;\n this.inFlight -= batch.length;\n this.pendingBatch = null;\n this.pendingBatchId = null;\n this.retry.recordSuccess();\n // Persisted blob collapses to just `buffer` (which may include\n // new enqueues that arrived while this batch was in flight).\n this.persistAll();\n if (!this.firstFlushFired) {\n this.firstFlushFired = true;\n this.cfg.onFirstFlushSuccess?.();\n }\n return result;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n this.lastError = message;\n\n // Permanent failures (4xx except 408/429) are NOT retryable.\n // Drop the batch loudly.\n if (isPermanent4xx(err)) {\n const droppedCount = batch.length;\n this.pendingBatch = null;\n this.pendingBatchId = null;\n this.inFlight -= droppedCount;\n this.dropped += droppedCount;\n this.persistAll();\n this.cfg.onDrop?.(droppedCount);\n this.cfg.onPermanentFailure?.({\n status: (err as { status?: number }).status ?? 0,\n droppedCount,\n lastError: message,\n });\n return null;\n }\n\n // Retryable failure. pendingBatch + pendingBatchId stay set;\n // the next scheduler-driven flush re-uses both. Persisted\n // state is unchanged from the entry path — it still includes\n // `[...pendingBatch, ...buffer]`.\n const retryAfterMs = extractRetryAfterMs(err);\n const delay = this.retry.nextDelay(retryAfterMs);\n this.scheduleRetry(delay);\n this.cfg.onRetryScheduled?.({\n delayMs: delay,\n consecutiveFailures: this.retry.consecutiveFailures,\n retryAfterMs,\n lastError: message,\n });\n return null;\n }\n }\n\n /** Cancel any pending timer and clear in-memory state. Wipes durable store too. */\n reset(): void {\n this.cancelTimerIfSet();\n this.nextRetryAt = null;\n this.buffer = [];\n this.pendingBatch = null;\n this.pendingBatchId = null;\n this.dropped = 0;\n this.inFlight = 0;\n this.lastError = null;\n this.retry.recordSuccess();\n void this.persistent?.clear();\n this.cfg.onBufferChange?.(0);\n // Note: we deliberately do NOT reset firstFlushFired — the\n // \"First event sent\" signal is a one-time onboarding moment per\n // SDK instance lifetime, not per-identity.\n }\n\n getStats(): EventQueueStats {\n return {\n buffered: this.buffer.length,\n dropped: this.dropped,\n inFlight: this.inFlight,\n lastFlushAt: this.lastFlushAt,\n lastError: this.lastError,\n consecutiveFailures: this.retry.consecutiveFailures,\n nextRetryAt: this.nextRetryAt,\n };\n }\n\n /**\n * The Idempotency-Key of the in-flight pending batch (if any).\n * Exposed for testing the Stripe-style retry-reuse contract.\n */\n get pendingIdempotencyKey(): string | null {\n return this.pendingBatchId;\n }\n\n // ---------- internal ----------\n\n private persistAll(): void {\n if (!this.persistent) return;\n if (this.pendingBatch === null) {\n this.persistent.save(this.buffer);\n return;\n }\n this.persistent.save([...this.pendingBatch, ...this.buffer]);\n }\n\n private scheduleIdleFlush(): void {\n this.cancelTimerIfSet();\n const sched = this.cfg.scheduler ?? defaultScheduler;\n this.cancelTimer = sched(() => {\n void this.flush();\n }, this.cfg.intervalMs);\n }\n\n private scheduleRetry(delayMs: number): void {\n this.cancelTimerIfSet();\n this.nextRetryAt = Date.now() + delayMs;\n const sched = this.cfg.scheduler ?? defaultScheduler;\n this.cancelTimer = sched(() => {\n void this.flush();\n }, delayMs);\n }\n\n private cancelTimerIfSet(): void {\n if (this.cancelTimer) {\n this.cancelTimer();\n this.cancelTimer = null;\n }\n }\n\n private mintBatchId(): string {\n return `batch_${Date.now().toString(36)}${randomChars(10)}`;\n }\n}\n\nfunction extractRetryAfterMs(err: unknown): number | undefined {\n if (err && typeof err === \"object\" && \"retryAfterMs\" in err) {\n const v = (err as CrossdeckError).retryAfterMs;\n return typeof v === \"number\" && Number.isFinite(v) && v >= 0 ? v : undefined;\n }\n return undefined;\n}\n\n/**\n * True when the error represents a permanent 4xx response that\n * SHOULDN'T be retried. Excludes 408 Request Timeout and 429 Too\n * Many Requests — both indicate transient state where the SAME\n * request (with the SAME Idempotency-Key) can succeed on a retry.\n *\n * Anything that isn't a CrossdeckError-shaped object with a numeric\n * status field returns false (network errors / fetch failures fall\n * here — those ARE retryable). Conservative default: only flag as\n * permanent when we have strong evidence from the server.\n */\nfunction isPermanent4xx(err: unknown): boolean {\n if (!err || typeof err !== \"object\") return false;\n const status = (err as { status?: unknown }).status;\n if (typeof status !== \"number\" || !Number.isFinite(status)) return false;\n if (status < 400 || status >= 500) return false;\n if (status === 408 || status === 429) return false;\n return true;\n}\n\nfunction defaultScheduler(fn: () => void, ms: number): () => void {\n const id = setTimeout(fn, ms);\n // setTimeout on Hermes supports .unref() on newer versions; the\n // typeof check is defensive for older runtimes that don't.\n if (typeof (id as unknown as { unref?: () => void }).unref === \"function\") {\n try {\n (id as unknown as { unref: () => void }).unref();\n } catch {\n // ignore — unref is best-effort\n }\n }\n return () => clearTimeout(id);\n}\n","/**\n * Durable event-queue persistence.\n *\n * Why this exists: the in-memory event-queue is fragile. Three\n * failure modes lose data without durable persistence:\n *\n * 1. App backgrounding under iOS / Android low-memory pressure\n * where the OS reclaims the JS context before the queue can\n * flush.\n * 2. App crash before the queue's idle-flush timer fires.\n * 3. Network down for longer than the user's session — events\n * queued while offline disappear when the process tears down.\n *\n * Stripe / Segment / PostHog all persist queued events to a durable\n * store (localStorage on web, AsyncStorage on RN, IndexedDB for\n * very large queues) and replay them on the next boot. We do the\n * same here with AsyncStorage as the default backing store.\n *\n * Failure modes handled gracefully:\n * - Storage throws (quota exceeded, IO error) → silent degrade\n * to in-memory only. The SDK keeps working; the durability\n * guarantee is best-effort.\n * - Persisted blob unparseable on next boot (manual corruption,\n * schema drift) → drop silently, fresh empty queue. Don't crash\n * the consumer app on a bad storage value.\n *\n * The storage key is `${prefix}queue.v1` to leave room for future\n * format migrations.\n *\n * RN-specific divergence from web:\n *\n * - All reads/writes are async. `load()` is awaited once at\n * boot from `EventQueue.hydrate()`; subsequent saves are\n * debounced via microtask + fire-and-forget. The web SDK can\n * do sync localStorage writes; we cannot.\n */\n\nimport type { KeyValueStorage } from \"./types\";\nimport type { QueuedEvent } from \"./event-queue\";\n\nexport interface PersistentEventStoreOptions {\n storage: KeyValueStorage;\n prefix: string;\n}\n\n/**\n * Wire format for persisted batches. Versioned so a future change to\n * QueuedEvent shape can be detected + ignored cleanly.\n */\ninterface PersistedQueue {\n version: 1;\n events: QueuedEvent[];\n}\n\nexport class PersistentEventStore {\n private readonly key: string;\n private writeScheduled = false;\n // Pending snapshot captured on the most recent save() call. A\n // debounced microtask picks up the latest ref when it fires, so\n // bursts of enqueue() calls coalesce into one persistence write.\n private pendingSnapshot: QueuedEvent[] | null = null;\n\n constructor(private readonly options: PersistentEventStoreOptions) {\n this.key = `${options.prefix}queue.v1`;\n }\n\n /**\n * Read the persisted queue on boot. Returns an empty array (with\n * no warning) when nothing is stored, the blob is malformed, or\n * storage is unavailable. Caller is responsible for treating\n * duplicates from the persisted queue as the SAME events\n * (eventId-based dedup on the backend).\n */\n async load(): Promise<QueuedEvent[]> {\n let raw: string | null;\n try {\n raw = await this.options.storage.getItem(this.key);\n } catch {\n return [];\n }\n if (!raw) return [];\n try {\n const parsed = JSON.parse(raw) as PersistedQueue;\n if (!parsed || parsed.version !== 1 || !Array.isArray(parsed.events)) {\n return [];\n }\n return parsed.events;\n } catch {\n // Corrupt blob — drop silently. Next save() overwrites.\n return [];\n }\n }\n\n /**\n * Schedule a write of the current buffer. Debounced via microtask\n * so a burst of enqueue() calls coalesces into one persistence\n * write. Writes are best-effort: if storage throws (quota / IO),\n * we swallow and rely on the in-memory buffer.\n */\n save(snapshot: readonly QueuedEvent[]): void {\n // Defensive copy so a later mutation of the buffer doesn't\n // change what we're about to persist.\n this.pendingSnapshot = snapshot.slice();\n if (this.writeScheduled) return;\n this.writeScheduled = true;\n queueMicrotask(() => this.flushWrite());\n }\n\n /** Wipe the persisted blob. Used by reset() (logout). */\n async clear(): Promise<void> {\n this.pendingSnapshot = null;\n this.writeScheduled = false;\n try {\n await this.options.storage.removeItem(this.key);\n } catch {\n // ignore\n }\n }\n\n private flushWrite(): void {\n this.writeScheduled = false;\n const snapshot = this.pendingSnapshot;\n this.pendingSnapshot = null;\n if (snapshot === null) return;\n\n if (snapshot.length === 0) {\n void this.options.storage.removeItem(this.key).catch(() => {});\n return;\n }\n\n const blob: PersistedQueue = { version: 1, events: snapshot };\n let serialised: string;\n try {\n serialised = JSON.stringify(blob);\n } catch {\n return;\n }\n void this.options.storage.setItem(this.key, serialised).catch(() => {\n // Quota exceeded / IO error — silent degrade. The in-memory\n // buffer is still authoritative; we just lose cross-launch\n // durability for this batch.\n });\n }\n}\n","/**\n * Storage adapters for SDK-persisted state on React Native.\n *\n * Two flavours:\n * - AsyncStorage (default when @react-native-async-storage/async-storage\n * resolves at runtime)\n * - in-memory (fallback, or explicit when the host app already\n * manages persistence — encrypted vaults, SecureStore, MMKV)\n *\n * RN does NOT ship localStorage. The SDK requires an ASYNC contract\n * here (Promise<string | null>) rather than the web SDK's sync one\n * because AsyncStorage is async and shimming sync-over-async would\n * either block the JS thread or invent stale reads. Every callsite\n * in this SDK awaits storage operations, so the cost is paid\n * honestly.\n *\n * Identity continuity caveats (documented honestly):\n * 1. AsyncStorage is cleared on app uninstall — there's no\n * equivalent to Safari's \"cleared site data but not the cookie\"\n * recovery. Native iOS/Android SDKs can reach Keychain/KeyStore\n * for reinstall-survival; the JS SDK can't.\n * 2. AsyncStorage is unencrypted on disk. We never persist\n * sensitive tokens — only the anonymousId, customerId, queued\n * events, super-properties, and entitlement cache. If the host\n * app needs encryption, pass a SecureStore-backed adapter.\n * 3. `persistIdentity: false` forces MemoryStorage so app shells\n * that defer to a consent gate can postpone any disk write\n * until the user opts in.\n */\n\nimport type { KeyValueStorage } from \"./types\";\n\n/**\n * In-memory storage. Cleared when the JS context tears down (app\n * cold launch, dev-tools reload). Use when you want session-scoped\n * identity with no on-disk trace.\n */\nexport class MemoryStorage implements KeyValueStorage {\n private store = new Map<string, string>();\n async getItem(key: string): Promise<string | null> {\n return this.store.get(key) ?? null;\n }\n async setItem(key: string, value: string): Promise<void> {\n this.store.set(key, value);\n }\n async removeItem(key: string): Promise<void> {\n this.store.delete(key);\n }\n}\n\n/**\n * AsyncStorage-backed adapter. Resolves the underlying module lazily\n * via indirect require() so apps that opt out of persistence (or\n * are running in an environment that doesn't ship AsyncStorage —\n * Storybook snapshots, vitest under node) don't pay the import cost\n * or hit a hard module-not-found.\n *\n * Failures degrade silently to null/no-op rather than throwing — a\n * broken storage layer should look identical to \"no value present\"\n * to the rest of the SDK. The diagnostic surface\n * (`Crossdeck.diagnostics()`) is the right place to surface\n * persistence health.\n */\nexport class AsyncStorageAdapter implements KeyValueStorage {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private store: any | null;\n\n constructor() {\n this.store = loadAsyncStorage();\n }\n\n async getItem(key: string): Promise<string | null> {\n if (!this.store) return null;\n try {\n const v = await this.store.getItem(key);\n return typeof v === \"string\" ? v : null;\n } catch {\n return null;\n }\n }\n\n async setItem(key: string, value: string): Promise<void> {\n if (!this.store) return;\n try {\n await this.store.setItem(key, value);\n } catch {\n /* quota / IO error — silent */\n }\n }\n\n async removeItem(key: string): Promise<void> {\n if (!this.store) return;\n try {\n await this.store.removeItem(key);\n } catch {\n /* silent */\n }\n }\n\n get available(): boolean {\n return this.store !== null;\n }\n}\n\n/**\n * Pick the best-available default storage. AsyncStorage when it\n * loads, MemoryStorage otherwise. Caller can override via\n * `Crossdeck.init({ storage: ... })` for SecureStore / MMKV /\n * encrypted adapters.\n */\nexport function detectDefaultStorage(): KeyValueStorage {\n const adapter = new AsyncStorageAdapter();\n if (adapter.available) return adapter;\n return new MemoryStorage();\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction loadAsyncStorage(): any | null {\n try {\n // Indirect require() so static analyzers / bundlers don't choke\n // when AsyncStorage isn't installed. RN's Metro resolves this at\n // runtime only if the package is present.\n const req = (\n globalThis as { require?: (id: string) => unknown }\n ).require;\n if (typeof req !== \"function\") return null;\n const mod = req(\"@react-native-async-storage/async-storage\") as\n | { default?: unknown }\n | undefined;\n if (!mod) return null;\n const candidate = (mod as { default?: unknown }).default ?? mod;\n if (\n candidate &&\n typeof (candidate as { getItem?: unknown }).getItem === \"function\"\n ) {\n return candidate;\n }\n return null;\n } catch {\n return null;\n }\n}\n","/**\n * Device + environment enrichment for React Native.\n *\n * Auto-attached to every event the SDK emits when the developer\n * opts into deviceInfo enrichment. Caller-supplied event properties\n * always override auto-detected ones.\n *\n * Privacy posture:\n * - No fingerprinting (no canvas hashes, no font enumeration).\n * - No precise geolocation (only timezone + locale, both of which\n * the runtime exposes to every app anyway).\n * - No IP collection — the backend logs the request IP for\n * rate-limit purposes; it isn't stored on the event document.\n * - All fields are typed enums or short strings; we never echo\n * back raw User-Agent equivalents to avoid surfacing\n * fingerprintable detail in dashboards.\n *\n * RN-specific sources:\n * - `Platform.OS` for ios / android (web for RN-Web).\n * - `Platform.Version` for OS version (number on Android, string\n * on iOS).\n * - `Platform.constants?.{Model,Brand,Manufacturer}` on Android\n * (iOS doesn't expose without a native module).\n * - `Dimensions.get(\"screen\")` + `.get(\"window\")` for screen size.\n * - `Intl.DateTimeFormat().resolvedOptions()` for locale +\n * timezone (Hermes 0.74+ supports the Intl API; older Hermes\n * degrades to null silently).\n */\n\nexport interface DeviceInfo {\n os?: \"ios\" | \"android\" | \"web\" | \"windows\" | \"macos\" | string;\n osVersion?: string;\n model?: string;\n brand?: string;\n manufacturer?: string;\n isPad?: boolean;\n isTV?: boolean;\n locale?: string;\n timezone?: string;\n screenWidth?: number;\n screenHeight?: number;\n windowWidth?: number;\n windowHeight?: number;\n scale?: number;\n fontScale?: number;\n /** Caller-supplied. Set via Crossdeck.init({ appVersion: \"1.2.3\" }). */\n appVersion?: string;\n}\n\n/**\n * Collect every safe-to-attach environment field. Returns an empty\n * object outside an RN runtime — caller can pass appVersion via the\n * `extra` argument when running under vitest / node test fixtures.\n */\nexport function collectDeviceInfo(extra?: { appVersion?: string }): DeviceInfo {\n const info: DeviceInfo = {};\n if (extra?.appVersion) info.appVersion = extra.appVersion;\n\n // ----- Platform module (RN core) -----\n const Platform = loadPlatform();\n if (Platform) {\n info.os = Platform.OS;\n if (Platform.Version !== undefined) {\n info.osVersion = String(Platform.Version);\n }\n if (Platform.isPad) info.isPad = true;\n if (Platform.isTV) info.isTV = true;\n const c = Platform.constants;\n if (c && typeof c === \"object\") {\n if (typeof c.Model === \"string\") info.model = c.Model;\n if (typeof c.Brand === \"string\") info.brand = c.Brand;\n if (typeof c.Manufacturer === \"string\") info.manufacturer = c.Manufacturer;\n }\n }\n\n // ----- Dimensions module (RN core) -----\n const Dimensions = loadDimensions();\n if (Dimensions) {\n try {\n const screen = Dimensions.get(\"screen\");\n if (screen) {\n info.screenWidth = screen.width;\n info.screenHeight = screen.height;\n if (screen.scale !== undefined) info.scale = screen.scale;\n if (screen.fontScale !== undefined) info.fontScale = screen.fontScale;\n }\n } catch {\n /* ignore */\n }\n try {\n const win = Dimensions.get(\"window\");\n if (win) {\n info.windowWidth = win.width;\n info.windowHeight = win.height;\n }\n } catch {\n /* ignore */\n }\n }\n\n // ----- Locale + timezone via Intl (Hermes 0.74+ / modern JSC) -----\n try {\n const opts = Intl.DateTimeFormat().resolvedOptions();\n if (opts.locale) info.locale = opts.locale;\n if (opts.timeZone) info.timezone = opts.timeZone;\n } catch {\n /* runtime without Intl — leave both null */\n }\n\n return info;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction loadPlatform(): any | null {\n return safeRequire(\"react-native\", \"Platform\");\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction loadDimensions(): any | null {\n return safeRequire(\"react-native\", \"Dimensions\");\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction safeRequire(moduleId: string, named: string): any | null {\n try {\n const req = (\n globalThis as { require?: (id: string) => unknown }\n ).require;\n if (typeof req !== \"function\") return null;\n const mod = req(moduleId) as Record<string, unknown> | undefined;\n if (!mod) return null;\n return mod[named] ?? null;\n } catch {\n return null;\n }\n}\n","/**\n * Debug signal vocabulary per NorthStar §16.\n *\n * The SDK speaks a small fixed vocabulary of signals so the\n * dashboard's onboarding checklist can show \"we saw your first event\"\n * without parsing free-form console output. When debug mode is\n * enabled the signals also log to `console.info` so a developer\n * doing copy-paste integration sees actionable feedback live.\n *\n * Signal names are STABLE — adding new ones is fine, renaming is a\n * breaking change because the dashboard onboarding step keys off\n * them.\n */\n\nexport type DebugSignal =\n | \"sdk.configured\"\n | \"sdk.first_event_sent\"\n | \"sdk.invalid_key\"\n | \"sdk.no_identity\"\n | \"sdk.entitlement_cache_used\"\n | \"sdk.purchase_evidence_sent\"\n | \"sdk.environment_mismatch\"\n | \"sdk.sensitive_property_warning\"\n | \"sdk.property_coerced\"\n | \"sdk.queue_persisted\"\n | \"sdk.queue_restored\"\n | \"sdk.flush_retry_scheduled\"\n // Emitted when the queue drops a batch because the server returned\n // a permanent 4xx (key revoked, malformed batch, etc.). Always\n // loud, regardless of debug mode — see the console.error in\n // crossdeck.ts.\n | \"sdk.flush_permanent_failure\"\n | \"sdk.consent_changed\"\n | \"sdk.consent_denied\"\n | \"sdk.pii_scrubbed\";\n\nexport interface DebugContext {\n [key: string]: unknown;\n}\n\n/**\n * Names that almost always indicate PII or secret data. Used by\n * `track()` to warn the developer when a property key looks\n * dangerous. Per NorthStar §15 these are reject/warn-on-sight values;\n * we warn rather than reject because the developer might genuinely\n * want a property called e.g. \"tokens_remaining\".\n */\nconst SENSITIVE_KEY_PATTERNS: readonly RegExp[] = [\n /^email$/i,\n /^password$/i,\n /^token$/i,\n /^secret$/i,\n /^card$/i,\n /^phone$/i,\n /password/i,\n /credit_?card/i,\n];\n\nexport function findSensitivePropertyKeys(\n properties: Record<string, unknown> | undefined,\n): string[] {\n if (!properties) return [];\n const hits: string[] = [];\n for (const k of Object.keys(properties)) {\n if (SENSITIVE_KEY_PATTERNS.some((re) => re.test(k))) hits.push(k);\n }\n return hits;\n}\n\nexport interface DebugLogger {\n enabled: boolean;\n emit(signal: DebugSignal, message: string, context?: DebugContext): void;\n}\n\nexport class ConsoleDebugLogger implements DebugLogger {\n enabled = false;\n private seen = new Set<DebugSignal>();\n\n emit(signal: DebugSignal, message: string, context?: DebugContext): void {\n if (!this.enabled) return;\n // For one-shot signals (sdk.configured, sdk.first_event_sent,\n // sdk.environment_mismatch) suppress duplicates within a session\n // so a chatty app doesn't spam the console with the same message.\n if (ONCE_SIGNALS.has(signal)) {\n if (this.seen.has(signal)) return;\n this.seen.add(signal);\n }\n const ctx = context ? ` ${safeJson(context)}` : \"\";\n // eslint-disable-next-line no-console\n console.info(`[crossdeck:${signal}] ${message}${ctx}`);\n }\n}\n\nconst ONCE_SIGNALS = new Set<DebugSignal>([\n \"sdk.configured\",\n \"sdk.first_event_sent\",\n \"sdk.environment_mismatch\",\n]);\n\nfunction safeJson(obj: unknown): string {\n try {\n return JSON.stringify(obj);\n } catch {\n return \"[unserialisable context]\";\n }\n}\n","/**\n * Property validation + coercion for `track()` events.\n *\n * Why this exists: the public `EventProperties` type is\n * `Record<string, unknown>` — developers can (and will) put anything\n * in there. Without a sanitiser, JSON.stringify at flush time will\n * throw on a function, a BigInt, a circular reference, or a Map, and\n * the WHOLE BATCH gets re-buffered every flush attempt until the\n * offending event is manually purged. Stripe-grade SDKs sanitise at\n * the call site so one bad property can't poison the queue.\n *\n * Contract:\n * - Drop functions / symbols / undefined values (with a warning).\n * - Coerce Date → ISO string, BigInt → string, Error → { name, message, stack }.\n * - Truncate string values longer than `maxStringLength` (default 1024).\n * - Replace circular refs with `\"[circular]\"`. ANCESTOR-only check —\n * a legitimate DAG (`{a: shared, b: shared}`) is NOT flagged; only\n * a real cycle (parent re-encountered via a descendant) triggers.\n * - Cap total serialised size at `maxBatchPropertyBytes` (default 8192).\n *\n * Pure function — no I/O, no console calls. Caller decides how to\n * surface warnings (debug log, telemetry counter, etc.).\n */\n\nimport type { EventProperties } from \"./types\";\n\nexport interface ValidationOptions {\n maxStringLength?: number;\n maxBatchPropertyBytes?: number;\n /**\n * Hard cap on depth of object/array nesting. Anything deeper is\n * coerced to \"[depth-exceeded]\". Defaults to 5 — covers most real\n * shapes (e.g. nested API responses) without letting a circular\n * structure consume the call stack via recursion.\n */\n maxDepth?: number;\n}\n\nexport interface ValidationWarning {\n kind:\n | \"dropped_function\"\n | \"dropped_symbol\"\n | \"dropped_undefined\"\n | \"coerced_date\"\n | \"coerced_bigint\"\n | \"coerced_error\"\n | \"coerced_map\"\n | \"coerced_set\"\n | \"truncated_string\"\n | \"circular_reference\"\n | \"depth_exceeded\"\n | \"non_serialisable\"\n | \"size_cap_exceeded\";\n key: string;\n}\n\nexport interface ValidationResult {\n properties: EventProperties;\n warnings: ValidationWarning[];\n}\n\nconst DEFAULT_MAX_STRING = 1024;\nconst DEFAULT_MAX_BYTES = 8 * 1024;\nconst DEFAULT_MAX_DEPTH = 5;\n\n/**\n * Validate + coerce a property bag. Always returns a NEW object — the\n * caller's input is never mutated.\n */\nexport function validateEventProperties(\n input: EventProperties | undefined,\n options: ValidationOptions = {},\n): ValidationResult {\n const warnings: ValidationWarning[] = [];\n if (!input) return { properties: {}, warnings };\n\n const maxStringLength = options.maxStringLength ?? DEFAULT_MAX_STRING;\n const maxBatchPropertyBytes = options.maxBatchPropertyBytes ?? DEFAULT_MAX_BYTES;\n const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;\n\n // Ancestor-only circular detection: add the object/array to `seen`\n // before recursing into its children, REMOVE it after. A\n // re-encounter while a value is still in the set means it's an\n // ancestor of the current node (a real cycle). Sibling sharing —\n // two properties pointing at the same sub-object (a legitimate\n // DAG, e.g. an event with `{user: shared, owner: shared}`) — is\n // NOT a cycle and must NOT be flagged.\n const seen = new Set<object>();\n\n const visit = (\n value: unknown,\n key: string,\n depth: number,\n ): { keep: boolean; value: unknown } => {\n if (depth > maxDepth) {\n warnings.push({ kind: \"depth_exceeded\", key });\n return { keep: true, value: \"[depth-exceeded]\" };\n }\n if (value === null) return { keep: true, value: null };\n const t = typeof value;\n if (t === \"string\") {\n const s = value as string;\n if (s.length > maxStringLength) {\n warnings.push({ kind: \"truncated_string\", key });\n return { keep: true, value: s.slice(0, maxStringLength - 1) + \"…\" };\n }\n return { keep: true, value: s };\n }\n if (t === \"number\") {\n if (!Number.isFinite(value as number)) {\n warnings.push({ kind: \"non_serialisable\", key });\n return { keep: true, value: null };\n }\n return { keep: true, value };\n }\n if (t === \"boolean\") return { keep: true, value };\n if (t === \"bigint\") {\n warnings.push({ kind: \"coerced_bigint\", key });\n return { keep: true, value: (value as bigint).toString() };\n }\n if (t === \"function\") {\n warnings.push({ kind: \"dropped_function\", key });\n return { keep: false, value: undefined };\n }\n if (t === \"symbol\") {\n warnings.push({ kind: \"dropped_symbol\", key });\n return { keep: false, value: undefined };\n }\n if (t === \"undefined\") {\n warnings.push({ kind: \"dropped_undefined\", key });\n return { keep: false, value: undefined };\n }\n\n if (value instanceof Date) {\n warnings.push({ kind: \"coerced_date\", key });\n const iso = Number.isFinite(value.getTime()) ? value.toISOString() : null;\n return { keep: true, value: iso };\n }\n if (value instanceof Error) {\n warnings.push({ kind: \"coerced_error\", key });\n return {\n keep: true,\n value: {\n name: value.name,\n message: value.message,\n stack: typeof value.stack === \"string\" ? value.stack.slice(0, maxStringLength) : undefined,\n },\n };\n }\n if (value instanceof Map) {\n warnings.push({ kind: \"coerced_map\", key });\n const obj: Record<string, unknown> = {};\n for (const [k, v] of value.entries()) {\n const subKey = typeof k === \"string\" ? k : String(k);\n const result = visit(v, `${key}.${subKey}`, depth + 1);\n if (result.keep) obj[subKey] = result.value;\n }\n return { keep: true, value: obj };\n }\n if (value instanceof Set) {\n warnings.push({ kind: \"coerced_set\", key });\n const arr: unknown[] = [];\n let i = 0;\n for (const v of value.values()) {\n const result = visit(v, `${key}[${i}]`, depth + 1);\n if (result.keep) arr.push(result.value);\n i++;\n }\n return { keep: true, value: arr };\n }\n\n if (Array.isArray(value)) {\n if (seen.has(value)) {\n warnings.push({ kind: \"circular_reference\", key });\n return { keep: true, value: \"[circular]\" };\n }\n seen.add(value);\n const out: unknown[] = [];\n for (let i = 0; i < value.length; i++) {\n const result = visit(value[i], `${key}[${i}]`, depth + 1);\n if (result.keep) out.push(result.value);\n }\n // Delete on exit — the array is no longer an ancestor of any\n // sibling visit. Sibling DAG sharing is fine.\n seen.delete(value);\n return { keep: true, value: out };\n }\n\n if (t === \"object\") {\n const obj = value as Record<string, unknown>;\n if (seen.has(obj)) {\n warnings.push({ kind: \"circular_reference\", key });\n return { keep: true, value: \"[circular]\" };\n }\n seen.add(obj);\n const out: Record<string, unknown> = {};\n for (const k of Object.keys(obj)) {\n const result = visit(obj[k], `${key}.${k}`, depth + 1);\n if (result.keep) out[k] = result.value;\n }\n seen.delete(obj);\n return { keep: true, value: out };\n }\n\n warnings.push({ kind: \"non_serialisable\", key });\n try {\n return { keep: true, value: String(value) };\n } catch {\n return { keep: false, value: undefined };\n }\n };\n\n const cleaned: Record<string, unknown> = {};\n for (const k of Object.keys(input)) {\n const result = visit(input[k], k, 0);\n if (result.keep) cleaned[k] = result.value;\n }\n\n // Final pass: enforce overall byte cap. JSON.stringify the cleaned\n // bag; if too large, drop properties (largest-first) until under.\n const serialised = safeStringify(cleaned);\n if (serialised && byteLength(serialised) > maxBatchPropertyBytes) {\n warnings.push({ kind: \"size_cap_exceeded\", key: \"*\" });\n const sizes = Object.keys(cleaned)\n .map((k) => ({ k, size: byteLength(safeStringify(cleaned[k]) ?? \"\") }))\n .sort((a, b) => b.size - a.size);\n let currentSize = byteLength(serialised);\n for (const { k } of sizes) {\n if (currentSize <= maxBatchPropertyBytes) break;\n currentSize -= sizes.find((s) => s.k === k)!.size;\n delete cleaned[k];\n }\n cleaned.__truncated = true;\n }\n\n return { properties: cleaned, warnings };\n}\n\nfunction safeStringify(v: unknown): string | null {\n try {\n return JSON.stringify(v) ?? null;\n } catch {\n return null;\n }\n}\n\nfunction byteLength(s: string): number {\n if (typeof TextEncoder !== \"undefined\") {\n return new TextEncoder().encode(s).length;\n }\n return s.length * 4;\n}\n","/**\n * Super properties + group analytics — Mixpanel pattern.\n *\n * **Super properties** are key/value pairs the developer registers\n * ONCE via `Crossdeck.register({ plan: \"pro\" })` that get attached\n * to every subsequent event of that SDK instance. They're the\n * single most-used feature in Mixpanel-style analytics: \"every event\n * from this user should have `plan` and `appVersion` on it\" instead\n * of remembering to pass them on every track() call.\n *\n * **Groups** are organisational identifiers: a customer might\n * belong to an `org` (\"acme\"), a `team` (\"design\"), and a `plan`\n * (\"enterprise\"). Each event carries `$groups.{type}: id` so B2B\n * dashboards can pivot.\n *\n * Both surfaces live in this module because they share two traits:\n * - They're set once, attached to every event automatically.\n * - They persist across app launches via the same storage layer\n * the SDK uses for identity.\n *\n * The store is reset on `Crossdeck.reset()` (logout) — both super\n * properties and groups are cleared because their lifetime is tied\n * to the identified user, not the SDK instance.\n *\n * RN-specific divergence from web:\n *\n * - Storage is ASYNC. Hydration via `loadAll()` runs once during\n * `Crossdeck.init()`; subsequent reads are sync from the\n * in-memory cache, writes fan out to async storage\n * fire-and-forget. Same pattern as IdentityStore.\n */\n\nimport type { KeyValueStorage } from \"./types\";\n\nconst KEY_SUPER = \"super_props\";\nconst KEY_GROUPS = \"groups\";\n\nexport class SuperPropertyStore {\n private superProps: Record<string, unknown> = {};\n private groups: Record<string, { id: string; traits?: Record<string, unknown> }> = {};\n private loaded = false;\n\n constructor(\n private readonly storage: KeyValueStorage,\n private readonly prefix: string,\n ) {}\n\n /**\n * Hydrate from durable storage. Called by Crossdeck.init() before\n * any track() can fire so super-props are present on the very\n * first event of the session. Safe to call multiple times — second+\n * calls are no-ops.\n */\n async loadAll(): Promise<void> {\n if (this.loaded) return;\n const [supersRaw, groupsRaw] = await Promise.all([\n this.storage.getItem(this.prefix + KEY_SUPER),\n this.storage.getItem(this.prefix + KEY_GROUPS),\n ]);\n this.superProps = parseJson<Record<string, unknown>>(supersRaw) ?? {};\n this.groups = parseJson(groupsRaw) ?? {};\n this.loaded = true;\n }\n\n // ---------- super properties ----------\n\n /**\n * Merge new keys into the super-property bag. Returns a snapshot\n * of the resulting bag. Values that are `null` are deleted (the\n * explicit \"stop tracking this key\" idiom — Mixpanel semantics).\n */\n register(props: Record<string, unknown>): Record<string, unknown> {\n for (const [k, v] of Object.entries(props)) {\n if (v === null) {\n delete this.superProps[k];\n } else if (v !== undefined) {\n this.superProps[k] = v;\n }\n }\n this.writeJson(this.prefix + KEY_SUPER, this.superProps);\n return { ...this.superProps };\n }\n\n /** Remove a single super-property key. Idempotent. */\n unregister(key: string): void {\n if (key in this.superProps) {\n delete this.superProps[key];\n this.writeJson(this.prefix + KEY_SUPER, this.superProps);\n }\n }\n\n /** Snapshot of the current super-property bag. */\n getSuperProperties(): Record<string, unknown> {\n return { ...this.superProps };\n }\n\n // ---------- groups ----------\n\n /**\n * Set a group membership. Passing `id: null` clears the\n * membership for that group type — the SDK stops attaching it to\n * events.\n */\n setGroup(type: string, id: string | null, traits?: Record<string, unknown>): void {\n if (id === null) {\n delete this.groups[type];\n } else {\n this.groups[type] = traits !== undefined ? { id, traits } : { id };\n }\n this.writeJson(this.prefix + KEY_GROUPS, this.groups);\n }\n\n /**\n * Snapshot of the current groups map, keyed by group type.\n * Returned shape mirrors what the SDK attaches to every event as\n * `$groups.{type}`.\n */\n getGroups(): Record<string, { id: string; traits?: Record<string, unknown> }> {\n return JSON.parse(JSON.stringify(this.groups));\n }\n\n /**\n * The flat `{ type: id }` projection used for event-attachment.\n * Stable for fast every-event merge — we don't want to JSON-clone\n * on each track() call.\n */\n getGroupIds(): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [type, info] of Object.entries(this.groups)) {\n out[type] = info.id;\n }\n return out;\n }\n\n /** Wipe both bags. Called by Crossdeck.reset() (logout). */\n clear(): void {\n this.superProps = {};\n this.groups = {};\n void this.storage.removeItem(this.prefix + KEY_SUPER).catch(() => {});\n void this.storage.removeItem(this.prefix + KEY_GROUPS).catch(() => {});\n }\n\n private writeJson(key: string, value: unknown): void {\n let s: string;\n try {\n s = JSON.stringify(value);\n } catch {\n return;\n }\n void this.storage.setItem(key, s).catch(() => {\n // Best-effort — in-memory cache stays authoritative for this\n // session. Cross-launch persistence is lost on this one write\n // but the next register/setGroup will retry.\n });\n }\n}\n\nfunction parseJson<T>(raw: string | null): T | null {\n if (!raw) return null;\n try {\n return JSON.parse(raw) as T;\n } catch {\n return null;\n }\n}\n","/**\n * Consent gating + PII scrub.\n *\n * Three consent dimensions (GDPR / CCPA-grade kill switches), each\n * defaulting to \"granted\" but runtime-overridable via\n * `Crossdeck.consent({...})`:\n *\n * analytics — track(), identify(), auto-emissions. Off → events\n * drop silently, no network calls fire.\n * marketing — paid-traffic click IDs and referrer URL. Off → these\n * get scrubbed before they ever land in the event bag.\n * errors — error capture. Off → no error.* events emitted.\n *\n * No `respectDnt` option on React Native — there's no `navigator` or\n * Do-Not-Track header equivalent in the RN runtime. The developer is\n * responsible for wiring whatever consent mechanism their app uses\n * (App Tracking Transparency on iOS, custom UI elsewhere) into\n * `Crossdeck.consent({...})` calls.\n *\n * PII scrub: defence-in-depth regex pass over every string property\n * value. Sentinel tokens (`<email>`, `<card>`) match the backend's\n * scrub (backend/src/api/lib/scrub.ts) so the same event scrubbed at\n * SDK or backend layers carries the same dashboard-aggregation key.\n * Recursive — nested plain objects + arrays-of-objects are walked,\n * so a `{user:{email:\"x@y.com\"}}` payload ships scrubbed.\n */\n\nexport interface ConsentState {\n analytics: boolean;\n marketing: boolean;\n errors: boolean;\n}\n\nconst ALL_GRANTED: ConsentState = {\n analytics: true,\n marketing: true,\n errors: true,\n};\n\nexport class ConsentManager {\n private state: ConsentState = { ...ALL_GRANTED };\n\n /**\n * Merge new dimensions onto the current state. Returns the resulting\n * snapshot.\n */\n set(partial: Partial<ConsentState>): ConsentState {\n for (const k of Object.keys(partial) as Array<keyof ConsentState>) {\n const v = partial[k];\n if (typeof v === \"boolean\") this.state[k] = v;\n }\n return { ...this.state };\n }\n\n get(): ConsentState {\n return { ...this.state };\n }\n\n get analytics(): boolean {\n return this.state.analytics;\n }\n get marketing(): boolean {\n return this.state.marketing;\n }\n get errors(): boolean {\n return this.state.errors;\n }\n}\n\n// ============================================================\n// PII scrubbing\n// ============================================================\n\n/**\n * Email-shaped pattern. Reasonably restrictive — matches RFC 5322's\n * \"obs-local-part\" common case (the practical 99% of emails).\n */\nconst EMAIL_PATTERN =\n /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/g;\n\n/**\n * Card-number-shaped pattern. Matches sequences of 13-19 digits\n * separated by space or hyphen — the format every payment form\n * accepts. Anchored on a digit at both ends so trailing separators\n * aren't pulled into the match.\n */\nconst CARD_PATTERN = /\\b\\d(?:[ -]?\\d){12,18}\\b/g;\n\n// Sentinel tokens — aligned with backend/src/api/lib/scrub.ts which\n// uses <email>, <card>, <uuid>, <cdcust>, <crossdeck_secret_key>,\n// <aws_access_key>. Mismatched tokens between SDK and backend\n// scrubbers would split dashboard aggregation (same event arriving\n// via two paths carries two different sentinels).\nconst REPLACEMENT_EMAIL = \"<email>\";\nconst REPLACEMENT_CARD = \"<card>\";\n\n/**\n * Scrub a single string value: replace email-shaped substrings with\n * `<email>` and card-number-shaped substrings with `<card>`. Returns\n * the original string when nothing matched.\n *\n * Implementation note: `.replace()` is called unconditionally rather\n * than gating on `.test()`. The /g regexes are module-level so\n * `.test()` carries `lastIndex` state between calls — a prior match\n * leaves `lastIndex` mid-string and the next `.test()` can falsely\n * return false on a string that DOES match. `.replace(/g)` always\n * scans the full string regardless of `lastIndex`.\n */\nexport function scrubPii(value: string): string {\n if (!value) return value;\n return value\n .replace(EMAIL_PATTERN, REPLACEMENT_EMAIL)\n .replace(CARD_PATTERN, REPLACEMENT_CARD);\n}\n\n/**\n * Walk an event's properties and replace PII-shaped strings in place.\n * Returns a new object with strings scrubbed; non-string values pass\n * through unchanged.\n *\n * Defensive copy — the input is never altered. Caller can pass the\n * result straight to the queue.\n *\n * Recursive: nested plain objects + arrays are walked. Without this,\n * an event like `{user:{email:\"wes@…\"}}` would ship the email\n * unscrubbed because the top-level value is an object, not a string.\n * Every captured-error report ships nested `frames[]` / `breadcrumbs[]`\n * / `context{}` / `http{}` shapes through here — this is the SDK's\n * #1 PII protection beyond the SDK boundary.\n *\n * Date / Map / Set / Error / class instances pass through untouched\n * (those are the validateEventProperties sanitiser's job — this is\n * the PII regex pass only).\n */\nexport function scrubPiiFromProperties(\n properties: Record<string, unknown>,\n): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const k of Object.keys(properties)) {\n out[k] = scrubValue(properties[k]);\n }\n return out;\n}\n\nfunction scrubValue(v: unknown): unknown {\n if (typeof v === \"string\") return scrubPii(v);\n if (Array.isArray(v)) return v.map(scrubValue);\n if (v && typeof v === \"object\" && (v as object).constructor === Object) {\n // Plain objects only — Date, Map, Set, Error, RegExp, class\n // instances are left untouched so we don't accidentally mutate\n // an Error's `message` and confuse downstream error reporting.\n return scrubPiiFromProperties(v as Record<string, unknown>);\n }\n return v;\n}\n","/**\n * Breadcrumb ring buffer — context attached to every error report.\n *\n * Sentry / Datadog / Bugsnag all ship the same idea: keep a rolling\n * record of the last N \"things the user did\" (screen mounts, custom\n * events, network calls). When an error fires, attach the buffer so\n * the engineer reading the error can see exactly how the user got\n * into the broken state. The single most powerful debugging signal\n * in error monitoring — without breadcrumbs, errors are stack traces\n * with no story.\n *\n * Implementation: a circular buffer with a fixed cap. Old entries are\n * evicted as new ones arrive. The default cap (50) is enough to\n * cover ~5 minutes of typical user activity without ballooning the\n * error payload — Sentry uses 100 by default but the SDK is more\n * aggressive about size since we ship breadcrumbs over the wire with\n * every error, not as a separate batch.\n *\n * Privacy: breadcrumbs auto-emit from the same auto-tracking sources\n * as analytics events (when auto-track is on). Custom crumbs added\n * via Crossdeck.addBreadcrumb() pass through the same property\n * sanitiser as track() events.\n */\n\nexport type BreadcrumbCategory =\n | \"navigation\"\n | \"ui.click\"\n | \"ui.input\"\n | \"http\"\n | \"console\"\n | \"custom\"\n | \"info\";\n\nexport type BreadcrumbLevel = \"debug\" | \"info\" | \"warning\" | \"error\";\n\nexport interface Breadcrumb {\n /** epoch ms */\n timestamp: number;\n category: BreadcrumbCategory;\n level?: BreadcrumbLevel;\n /** Short human-readable description. */\n message?: string;\n /** Arbitrary key/value context for the crumb. */\n data?: Record<string, unknown>;\n}\n\nexport class BreadcrumbBuffer {\n private items: Breadcrumb[] = [];\n constructor(private readonly maxSize: number = 50) {}\n\n add(crumb: Breadcrumb): void {\n this.items.push(crumb);\n if (this.items.length > this.maxSize) {\n this.items.shift();\n }\n }\n\n /** Defensive copy — caller can read freely without mutating buffer state. */\n snapshot(): Breadcrumb[] {\n return this.items.slice();\n }\n\n clear(): void {\n this.items = [];\n }\n\n get size(): number {\n return this.items.length;\n }\n}\n","/**\n * _diagnostic-telemetry.ts (React Native SDK)\n *\n * Single-fire reliability telemetry for the SDK. Carries the\n * `crossdeck.contract_failed` event ONE WAY to the Crossdeck\n * reliability endpoint — NEVER the customer's appId, NEVER the\n * customer's track() pipeline, NEVER visible in the customer's\n * dashboard.\n *\n * Why this exists\n * ───────────────────────────────────────────────────────────────────\n * Crossdeck is an independent controller for SDK Diagnostic\n * Telemetry (Privacy Policy §6, \"Flow B\"). The legitimate-interest\n * basis depends on the payload remaining diagnostic-only: no\n * end-user identifiers, no free-form text, no stack frames. The\n * schema-lock contract at\n * `contracts/diagnostics/contract-failed-payload-schema-lock.json`\n * fixes the wire shape; this module is the call site that has to\n * honour it.\n *\n * Why bypass the existing HttpClient\n * ───────────────────────────────────────────────────────────────────\n * The HttpClient is configured for the customer's project (their\n * API key, their endpoint). Routing reliability telemetry through\n * it would (a) bill against the customer's event quota and (b)\n * show individual contract failures in their dashboard, which is\n * neither the customer's nor Crossdeck's intent. A separate one-way\n * path is the structural guarantee.\n *\n * PROVISIONING NOTE\n * ───────────────────────────────────────────────────────────────────\n * The reliability endpoint URL + publishable key below are LITERAL\n * CONSTANTS shipped in the SDK. Until the reliability project is\n * minted, the placeholder values disable telemetry — the function\n * returns early without making a request. After provisioning, swap\n * the placeholders for the real values; the same values go into the\n * backend at backend/src/api/v1-sdk-diagnostic.ts.\n */\n\nimport { SDK_NAME, SDK_VERSION } from \"./_version\";\n\n/** Reliability endpoint URL. Hardcoded — never read from config. */\nexport const DIAGNOSTIC_TELEMETRY_ENDPOINT =\n \"https://api.cross-deck.com/v1/sdk/diagnostic\";\n\n/** Reliability project's publishable key. Hardcoded constant.\n * Provisioned 2026-05-27 — Crossdeck reliability workspace\n * (app_web_92b2d6a5728a4d). Every customer SDK's contract_failed\n * events route here for Crossdeck-on-Crossdeck observability. */\nexport const DIAGNOSTIC_TELEMETRY_PUBLISHABLE_KEY =\n \"cd_pub_live_9490e7aa029c432abf\";\n\n/**\n * Whether the telemetry is enabled. Disabled while the reliability\n * project is unprovisioned (placeholder key in place).\n */\nexport function isDiagnosticTelemetryEnabled(): boolean {\n return !DIAGNOSTIC_TELEMETRY_PUBLISHABLE_KEY.startsWith(\n \"cd_pub_RELIABILITY_PLACEHOLDER\",\n );\n}\n\n/**\n * The exhaustive set of fields the payload may contain — mirrors the\n * schema-lock contract.\n */\nexport const DIAGNOSTIC_TELEMETRY_ALLOWED_KEYS: ReadonlySet<string> = new Set([\n \"contract_id\",\n \"sdk_version\",\n \"sdk_platform\",\n \"failure_reason\",\n \"run_context\",\n \"run_id\",\n \"test_file\",\n \"test_name\",\n \"device_class\",\n]);\n\n/**\n * Fire-and-forget POST to the reliability endpoint. Returns\n * immediately. Never throws — failures are silently dropped so the\n * customer's app is not affected by reliability-endpoint\n * availability.\n *\n * React Native ships fetch from a global polyfill (whatwg-fetch on\n * Android, NSURLSession bridge on iOS). We never need a different\n * code path between the platforms — the only requirement is that\n * fetch exists; if it doesn't (custom runtime, jest without polyfill)\n * we silently drop.\n *\n * @param payload key/value map of payload fields. Keys not in\n * {@link DIAGNOSTIC_TELEMETRY_ALLOWED_KEYS} are dropped before\n * serialisation.\n */\nexport function filterDiagnosticPayload(\n payload: Record<string, string>,\n): Record<string, string> {\n const filtered: Record<string, string> = {};\n for (const [k, v] of Object.entries(payload)) {\n if (DIAGNOSTIC_TELEMETRY_ALLOWED_KEYS.has(k) && typeof v === \"string\") {\n filtered[k] = v;\n }\n }\n return filtered;\n}\n\nexport function sendDiagnosticTelemetry(\n payload: Record<string, string>,\n): void {\n if (!isDiagnosticTelemetryEnabled()) return;\n const filtered = filterDiagnosticPayload(payload);\n if (Object.keys(filtered).length === 0) return;\n\n const body = JSON.stringify(filtered);\n const f = (globalThis as { fetch?: typeof fetch }).fetch;\n if (typeof f !== \"function\") return;\n\n try {\n void f(DIAGNOSTIC_TELEMETRY_ENDPOINT, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${DIAGNOSTIC_TELEMETRY_PUBLISHABLE_KEY}`,\n \"Crossdeck-Sdk-Version\": `${SDK_NAME}@${SDK_VERSION}`,\n },\n body,\n }).catch(() => {\n // Fire-and-forget; never propagate a rejection.\n });\n } catch {\n // Swallow synchronous throws.\n }\n}\n","/**\n * Stack-trace parser — normalises Hermes / JSC / V8 stack strings\n * into a common frame shape.\n *\n * Why hand-rolled, not stack-trace-js or error-stack-parser libraries:\n * those weigh 5–15 KB after minification and we'd be pulling in their\n * full feature matrix just for the parser. The patterns below cover\n * the four shapes any modern JS engine emits, totalling ~80 lines.\n *\n * The output frame shape mirrors what Sentry's `mechanism: { type:\n * 'generic' }` events ship, so future source-map symbolication on the\n * Crossdeck backend has a stable input to work against.\n *\n * Defensive: never throws. An unparseable line becomes a `raw` frame\n * with just the literal text. Engineers reading errors still get the\n * raw stack as fallback.\n */\n\nexport interface StackFrame {\n /** Function name, or \"?\" if anonymous / unparseable. */\n function: string;\n /** Source file URL the frame ran in. Empty when unknown. */\n filename: string;\n /** 1-indexed line number, or 0 when unknown. */\n lineno: number;\n /** 1-indexed column number, or 0 when unknown. */\n colno: number;\n /**\n * True when the frame is in the app's own code (best-effort:\n * detected by filename not being a known third-party path).\n * Helps the dashboard's \"your code vs library code\" view.\n */\n in_app: boolean;\n /** Raw line from the stack string for debugging when parse fails. */\n raw: string;\n}\n\n/**\n * Parse a stack string into an array of frames. Returns an empty\n * array when the input is unparseable — caller should always treat\n * the original `error.stack` as the source of truth for display.\n */\nexport function parseStack(stack: string | undefined | null): StackFrame[] {\n if (!stack || typeof stack !== \"string\") return [];\n const lines = stack.split(\"\\n\");\n const frames: StackFrame[] = [];\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n const frame = parseLine(trimmed);\n if (frame) frames.push(frame);\n }\n return frames;\n}\n\nfunction parseLine(line: string): StackFrame | null {\n // Hermes / V8 — with parens\n // Example: at handleClick (/path/to/app.bundle:42:18)\n // at Object.handleClick (.../app.bundle?platform=ios:42:18)\n let m = /^at\\s+(.+?)\\s+\\((.+?):(\\d+):(\\d+)\\)$/.exec(line);\n if (m) {\n return buildFrame({\n function: m[1]!,\n filename: m[2]!,\n lineno: parseInt(m[3]!, 10),\n colno: parseInt(m[4]!, 10),\n raw: line,\n });\n }\n\n // V8 — anonymous, no parens\n // Example: at /path/to/app.bundle:42:18\n m = /^at\\s+(.+?):(\\d+):(\\d+)$/.exec(line);\n if (m) {\n return buildFrame({\n function: \"?\",\n filename: m[1]!,\n lineno: parseInt(m[2]!, 10),\n colno: parseInt(m[3]!, 10),\n raw: line,\n });\n }\n\n // Hermes legacy / JSC — @-separator\n // Example: handleClick@/path/to/app.bundle:42:18\n m = /^(.*?)@(.+?):(\\d+):(\\d+)$/.exec(line);\n if (m) {\n return buildFrame({\n function: m[1]! || \"?\",\n filename: m[2]!,\n lineno: parseInt(m[3]!, 10),\n colno: parseInt(m[4]!, 10),\n raw: line,\n });\n }\n\n // Header line (e.g. \"TypeError: foo is not a function\") — return\n // null so caller skips it.\n if (/^\\w*Error/.test(line) || !line.includes(\":\")) {\n return null;\n }\n\n // Unparseable but plausibly a frame — keep it as raw.\n return {\n function: \"?\",\n filename: \"\",\n lineno: 0,\n colno: 0,\n in_app: true,\n raw: line,\n };\n}\n\nfunction buildFrame(input: {\n function: string;\n filename: string;\n lineno: number;\n colno: number;\n raw: string;\n}): StackFrame {\n return {\n function: input.function || \"?\",\n filename: input.filename,\n lineno: Number.isFinite(input.lineno) ? input.lineno : 0,\n colno: Number.isFinite(input.colno) ? input.colno : 0,\n in_app: isInAppFrame(input.filename),\n raw: input.raw,\n };\n}\n\n/**\n * Best-effort \"is this frame in the app's own code or a third-party\n * source we should de-emphasise in the UI\".\n *\n * Out-of-app heuristics in the RN context: the React Native runtime\n * itself (node_modules/react-native/*), the SDK's own module, and\n * any vendored polyfill (Hermes intl, etc.).\n */\nfunction isInAppFrame(filename: string): boolean {\n if (!filename) return true;\n if (/\\/node_modules\\/react-native\\//.test(filename)) return false;\n if (/\\bInitializeCore\\.js$/.test(filename)) return false;\n if (/\\b@cross-deck\\/react-native\\b/.test(filename)) return false;\n if (/\\/node_modules\\/@react-native\\//.test(filename)) return false;\n if (/\\/node_modules\\/expo\\//.test(filename)) return false;\n return true;\n}\n\n/**\n * Fingerprint an error for grouping. SHA-flavoured — we don't need\n * cryptographic strength, we need \"two errors with the same call\n * site produce the same key\". The Crossdeck backend may refine the\n * grouping further once source maps are uploaded.\n *\n * Input: the message + the first ≤3 in-app frames. When no frames\n * are available (cross-origin script error, non-Error throws,\n * unhandledrejection of a primitive), the optional `location`\n * fallback contributes filename:lineno:colno so otherwise-identical\n * \"Unknown error\" events from different call sites stay separate.\n *\n * Output: a short hex string usable as a Firestore doc id segment.\n */\nexport function fingerprintError(\n message: string,\n frames: StackFrame[],\n location?: {\n filename?: string | null;\n lineno?: number | null;\n colno?: number | null;\n errorType?: string | null;\n } | null,\n): string {\n const inAppFrames = frames.filter((f) => f.in_app).slice(0, 3);\n const parts = [\n (message || \"\").slice(0, 200),\n ...inAppFrames.map((f) => `${f.function}@${f.filename}:${f.lineno}`),\n ];\n if (inAppFrames.length === 0 && location) {\n const loc = [\n location.errorType ?? \"\",\n location.filename ?? \"\",\n location.lineno ?? \"\",\n location.colno ?? \"\",\n ].join(\":\");\n if (loc !== \":::\") parts.push(loc);\n }\n return djb2Hex(parts.join(\"|\"));\n}\n\n/**\n * djb2 — small, fast non-cryptographic string hash. 32-bit output\n * encoded as 8-char hex. Stable across runtimes; deterministic.\n */\nfunction djb2Hex(input: string): string {\n let h = 5381;\n for (let i = 0; i < input.length; i++) {\n h = ((h << 5) + h + input.charCodeAt(i)) | 0;\n }\n return (h >>> 0).toString(16).padStart(8, \"0\");\n}\n","/**\n * Error capture for React Native.\n *\n * Catches every error source the RN runtime can hand us and ships\n * them as Crossdeck events. The pipeline reuses the analytics queue:\n * - Same durable persistence (errors survive backgrounding /\n * low-memory teardown)\n * - Same exponential backoff (a flapping server doesn't flood\n * errors past the rate limit)\n * - Same Idempotency-Key (duplicate batches dedup server-side)\n * - Same consent gate (`consent.errors`)\n * - Same PII scrub on properties before they leave\n *\n * Error sources captured (each toggleable):\n * 1. `ErrorUtils.setGlobalHandler` — uncaught synchronous +\n * unhandled promise rejections (RN wires both through the\n * same global handler since RN 0.63+; older versions ship a\n * separate `HermesInternal.enablePromiseRejectionTracker`).\n * 2. `globalThis.fetch` wrap — HTTP errors the app code didn't\n * catch. 5xx + network failures fire.\n * 3. `Crossdeck.captureError(err)` — manual API for try/catch\n * blocks.\n * 4. `Crossdeck.captureMessage(msg)` — non-error events you want\n * to surface as issues.\n *\n * Defensive design rules:\n * - The error handler must NEVER throw — if our own code crashes\n * while reporting an error, we'd take down the host app's\n * error handler too. Every callback is wrapped in try/swallow.\n * - Recursion guard: a `_reporting` flag prevents the SDK from\n * reporting its own errors recursively forever.\n * - Rate limited per-fingerprint: max N reports per minute to\n * defend against runaway loops (e.g. an error in setInterval).\n * - Self-skip: requests to the configured `selfHostname` (derived\n * from `baseUrl`) never trigger captureHttp — otherwise a\n * Crossdeck-side outage would recurse (captureHttp → enqueue\n * → /events → fail → captureHttp → ∞).\n */\n\nimport {\n parseStack,\n fingerprintError,\n type StackFrame,\n} from \"./stack-parser\";\nimport type { BreadcrumbBuffer, Breadcrumb } from \"./breadcrumbs\";\nimport { isSelfRequest } from \"./http\";\n\nexport type ErrorLevel = \"error\" | \"warning\" | \"info\";\n\nexport interface CapturedError {\n /** When the error fired (epoch ms). */\n timestamp: number;\n kind:\n | \"error.unhandled\"\n | \"error.unhandledrejection\"\n | \"error.handled\"\n | \"error.message\"\n | \"error.http\";\n level: ErrorLevel;\n message: string;\n errorType: string | null;\n frames: StackFrame[];\n rawStack: string | null;\n filename: string | null;\n lineno: number | null;\n colno: number | null;\n fingerprint: string;\n breadcrumbs: Breadcrumb[];\n context: Record<string, unknown>;\n tags: Record<string, string>;\n http?: {\n url: string;\n method: string;\n status: number;\n statusText?: string;\n };\n}\n\nexport interface ErrorCaptureConfig {\n enabled: boolean;\n /** Catch ErrorUtils.setGlobalHandler. Default true. */\n globalHandler: boolean;\n /** Wrap globalThis.fetch to capture 5xx + network failures. Default true. */\n wrapFetch: boolean;\n /** Drop errors matching these substrings or regexes. */\n ignoreErrors: Array<string | RegExp>;\n /** Sample rate, 0–1. 1.0 = send every error. */\n sampleRate: number;\n /** Maximum errors per fingerprint per minute. Default 5. */\n maxPerFingerprintPerMinute: number;\n /** Total cap per session, regardless of fingerprint. Default 100. */\n maxPerSession: number;\n}\n\nexport const DEFAULT_ERROR_CAPTURE: ErrorCaptureConfig = {\n enabled: true,\n globalHandler: true,\n wrapFetch: true,\n ignoreErrors: [\n // Hermes promise-rejection wrapper boilerplate that's never\n // actionable for the app developer.\n \"Possible Unhandled Promise Rejection (id: 0)\",\n ],\n sampleRate: 1.0,\n maxPerFingerprintPerMinute: 5,\n maxPerSession: 100,\n};\n\nexport interface ErrorTrackerOptions {\n config: ErrorCaptureConfig;\n breadcrumbs: BreadcrumbBuffer;\n /** Called with each captured error. Forwards into the event queue. */\n report: (err: CapturedError) => void;\n /** Called to read the current developer-supplied context bag. */\n getContext: () => Record<string, unknown>;\n /** Called to read the current developer-supplied tag bag. */\n getTags: () => Record<string, string>;\n /**\n * Pre-send hook GETTER. The tracker invokes this on EVERY captured\n * error to resolve the current hook reference, then calls the\n * resolved function with the error (returning `null` to drop, or\n * a modified `CapturedError` to forward).\n *\n * Getter shape — not a static function — so\n * `setErrorBeforeSend()` can install or replace the hook after\n * `init()` without re-creating the tracker. Returning `null` from\n * the GETTER means \"no hook configured\" and the report goes\n * through unmodified.\n */\n beforeSend?: () => ((err: CapturedError) => CapturedError | null) | null;\n /**\n * Whether the consent dimension `errors` is currently granted.\n * Checked at capture time so a flip via Crossdeck.consent() takes\n * effect immediately.\n */\n isConsented: () => boolean;\n /**\n * The SDK's own backend hostname (derived from\n * `CrossdeckOptions.baseUrl` at construction time). Used to skip\n * captureHttp for our own requests. Null / omitted when extraction\n * fails (malformed URL) OR when the test harness doesn't supply\n * one — the tracker falls through to \"capture everything\" rather\n * than swallow.\n */\n selfHostname?: string | null;\n}\n\nexport class ErrorTracker {\n private installed = false;\n private cleanups: Array<() => void> = [];\n private _reporting = false;\n private sessionCount = 0;\n private fingerprintWindow = new Map<string, number[]>();\n\n constructor(private readonly opts: ErrorTrackerOptions) {}\n\n install(): void {\n if (this.installed) return;\n if (!this.opts.config.enabled) return;\n\n if (this.opts.config.globalHandler) this.installGlobalHandler();\n if (this.opts.config.wrapFetch) this.installFetchWrap();\n\n this.installed = true;\n }\n\n uninstall(): void {\n for (const fn of this.cleanups.splice(0)) {\n try {\n fn();\n } catch {\n // ignore\n }\n }\n this.installed = false;\n }\n\n /**\n * Manual API. Either an Error instance or any unknown value (we\n * coerce). Returns silently — never throws.\n */\n captureError(\n error: unknown,\n options?: {\n context?: Record<string, unknown>;\n tags?: Record<string, string>;\n level?: ErrorLevel;\n },\n ): void {\n if (!this.opts.isConsented()) return;\n try {\n const captured = this.buildFromUnknown(\n error,\n \"error.handled\",\n options?.level ?? \"error\",\n );\n if (options?.context)\n captured.context = { ...captured.context, ...options.context };\n if (options?.tags) captured.tags = { ...captured.tags, ...options.tags };\n this.maybeReport(captured);\n } catch {\n // self-protection — never let our own code crash the caller.\n }\n }\n\n /**\n * Capture a non-error event as an issue. For \"we hit a\n * soft-warning code path\" / \"deprecated API used\" kinds of\n * signals. Pairs with Sentry's captureMessage().\n */\n captureMessage(message: string, level: ErrorLevel = \"info\"): void {\n if (!this.opts.isConsented()) return;\n try {\n const captured: CapturedError = {\n timestamp: Date.now(),\n kind: \"error.message\",\n level,\n message,\n errorType: null,\n frames: [],\n rawStack: null,\n filename: null,\n lineno: null,\n colno: null,\n fingerprint: fingerprintError(message, []),\n breadcrumbs: this.opts.breadcrumbs.snapshot(),\n context: this.opts.getContext(),\n tags: this.opts.getTags(),\n };\n this.maybeReport(captured);\n } catch {\n // swallow\n }\n }\n\n // ============================================================\n // Listener installation\n // ============================================================\n\n private installGlobalHandler(): void {\n // RN's `ErrorUtils` is a global polyfill that wraps every JS\n // execution context. `setGlobalHandler` lets us chain in front\n // of the default handler (RN's red-box developer overlay) so\n // we get every uncaught error AND the dev experience stays\n // intact.\n const g = globalThis as unknown as {\n ErrorUtils?: {\n getGlobalHandler?: () => (error: Error, isFatal?: boolean) => void;\n setGlobalHandler?: (\n handler: (error: Error, isFatal?: boolean) => void,\n ) => void;\n };\n };\n const ErrorUtils = g.ErrorUtils;\n if (!ErrorUtils?.setGlobalHandler || !ErrorUtils?.getGlobalHandler) return;\n\n const prior = ErrorUtils.getGlobalHandler();\n const handler = (error: Error, isFatal?: boolean): void => {\n if (!this._reporting && this.opts.isConsented()) {\n try {\n this._reporting = true;\n const captured = this.buildFromUnknown(\n error,\n \"error.unhandled\",\n isFatal ? \"error\" : \"warning\",\n );\n this.maybeReport(captured);\n } catch {\n // swallow\n } finally {\n this._reporting = false;\n }\n }\n // Always defer to the prior handler so RN's red-box / OS\n // crash reporter still fires. We're additive, not\n // replacement.\n if (prior) {\n try {\n prior(error, isFatal);\n } catch {\n // swallow\n }\n }\n };\n\n ErrorUtils.setGlobalHandler(handler);\n this.cleanups.push(() => {\n // Best-effort restore — if a later library wrapped us, leave\n // their wrapper in place (matches web's fetch-wrap policy).\n if (prior) ErrorUtils.setGlobalHandler!(prior);\n });\n }\n\n /**\n * Wrap globalThis.fetch so failed HTTP requests get\n * auto-captured. We do NOT call 4xx an \"error\" (those are often\n * expected — auth required, validation failed). Only 5xx +\n * network failures fire.\n */\n private installFetchWrap(): void {\n const origFetch = globalThis.fetch;\n if (typeof origFetch !== \"function\") return;\n const tracker = this;\n const wrapped: typeof fetch = async (\n ...args: Parameters<typeof fetch>\n ): Promise<Response> => {\n const input = args[0];\n const init = args[1] ?? {};\n const url =\n typeof input === \"string\" ? input : (input as Request)?.url ?? \"\";\n const method = (init.method || \"GET\").toUpperCase();\n const start = Date.now();\n\n // Skip self-requests for breadcrumbs too — an error report's\n // crumb trail showing \"POST https://api.cross-deck.com/v1/events\"\n // entries is noise the engineer doesn't care about.\n if (!isSelfRequest(url, tracker.opts.selfHostname)) {\n tracker.opts.breadcrumbs.add({\n timestamp: start,\n category: \"http\",\n message: `${method} ${url}`,\n data: { url, method },\n });\n }\n\n try {\n const response = await origFetch(...args);\n if (response.status >= 500 && tracker.opts.isConsented()) {\n if (!isSelfRequest(url, tracker.opts.selfHostname)) {\n tracker.captureHttp({\n url,\n method,\n status: response.status,\n statusText: response.statusText,\n });\n }\n }\n return response;\n } catch (err) {\n // Genuine network failure (DNS, connection refused).\n if (\n tracker.opts.isConsented() &&\n !isSelfRequest(url, tracker.opts.selfHostname)\n ) {\n tracker.captureHttp({\n url,\n method,\n status: 0,\n statusText: err instanceof Error ? err.message : \"network error\",\n });\n }\n throw err;\n }\n };\n globalThis.fetch = wrapped;\n this.cleanups.push(() => {\n // Only restore if we're still the active wrapper. If another\n // observability tool installed AFTER us, leave their wrapper\n // in place.\n if (globalThis.fetch === wrapped) globalThis.fetch = origFetch;\n });\n }\n\n // ============================================================\n // Build + report\n // ============================================================\n\n private buildFromUnknown(\n value: unknown,\n kind: CapturedError[\"kind\"],\n level: ErrorLevel,\n ): CapturedError {\n const coerced = coerceErrorPayload(value);\n const isErrorInstance = value instanceof Error;\n const rawStack = isErrorInstance ? (value as Error).stack ?? null : null;\n const frames = parseStack(rawStack);\n const context = this.opts.getContext();\n if (coerced.extras) {\n context.__error_extras = coerced.extras;\n }\n return {\n timestamp: Date.now(),\n kind,\n level,\n message: coerced.message,\n errorType: coerced.errorType,\n frames,\n rawStack,\n filename: frames[0]?.filename ?? null,\n lineno: frames[0]?.lineno ?? null,\n colno: frames[0]?.colno ?? null,\n fingerprint: fingerprintError(coerced.message, frames, {\n errorType: coerced.errorType,\n }),\n breadcrumbs: this.opts.breadcrumbs.snapshot(),\n context,\n tags: this.opts.getTags(),\n };\n }\n\n private captureHttp(info: {\n url: string;\n method: string;\n status: number;\n statusText?: string;\n }): void {\n try {\n const message = `HTTP ${info.status} ${info.method} ${info.url}`;\n const captured: CapturedError = {\n timestamp: Date.now(),\n kind: \"error.http\",\n level: \"error\",\n message,\n errorType: \"HTTPError\",\n frames: [],\n rawStack: null,\n filename: info.url,\n lineno: null,\n colno: null,\n fingerprint: fingerprintError(\n `HTTP ${info.status} ${info.method}`,\n [],\n { filename: info.url, errorType: \"HTTPError\" },\n ),\n breadcrumbs: this.opts.breadcrumbs.snapshot(),\n context: this.opts.getContext(),\n tags: this.opts.getTags(),\n http: info,\n };\n this.maybeReport(captured);\n } catch {\n // swallow\n }\n }\n\n // ============================================================\n // Reporting pipeline — filter / sample / rate-limit / send\n // ============================================================\n\n private maybeReport(err: CapturedError): void {\n if (this.sessionCount >= this.opts.config.maxPerSession) return;\n if (this.shouldIgnore(err)) return;\n if (!this.passesSample(err)) return;\n if (!this.passesRateLimit(err)) return;\n\n // beforeSend hook — last chance to scrub or drop. Resolve the\n // current hook through the getter on every call so a hook\n // installed via `setErrorBeforeSend()` AFTER init() takes effect\n // on THIS error, not just future ones.\n let finalErr: CapturedError | null = err;\n const hook = this.opts.beforeSend?.();\n if (hook) {\n try {\n finalErr = hook(err);\n } catch {\n // A buggy beforeSend hook must NOT swallow the error\n // report. Fall back to the original.\n finalErr = err;\n }\n if (!finalErr) return;\n }\n\n this.sessionCount += 1;\n try {\n this.opts.report(finalErr);\n } catch {\n // swallow — report() failure is best-effort.\n }\n }\n\n private shouldIgnore(err: CapturedError): boolean {\n for (const pat of this.opts.config.ignoreErrors) {\n if (typeof pat === \"string\" && err.message.includes(pat)) return true;\n if (pat instanceof RegExp && pat.test(err.message)) return true;\n }\n return false;\n }\n\n private passesSample(err: CapturedError): boolean {\n if (this.opts.config.sampleRate >= 1) return true;\n if (this.opts.config.sampleRate <= 0) return false;\n // Deterministic per-fingerprint sampling — a given user always\n // either always sends a given error or never does, no flapping.\n const hashByte = parseInt(err.fingerprint.slice(0, 2), 16);\n return hashByte / 255 < this.opts.config.sampleRate;\n }\n\n private passesRateLimit(err: CapturedError): boolean {\n const windowMs = 60_000;\n const now = Date.now();\n const max = this.opts.config.maxPerFingerprintPerMinute;\n const arr = this.fingerprintWindow.get(err.fingerprint) ?? [];\n const fresh = arr.filter((t) => now - t < windowMs);\n if (fresh.length >= max) {\n this.fingerprintWindow.set(err.fingerprint, fresh);\n return false;\n }\n fresh.push(now);\n this.fingerprintWindow.set(err.fingerprint, fresh);\n return true;\n }\n}\n\n// ============================================================\n// Unknown-value coercion\n// ============================================================\n\ninterface CoercedPayload {\n message: string;\n errorType: string | null;\n extras: Record<string, unknown> | null;\n}\n\nfunction coerceErrorPayload(v: unknown): CoercedPayload {\n if (v === null) return { message: \"(thrown: null)\", errorType: null, extras: null };\n if (v === undefined) return { message: \"(thrown: undefined)\", errorType: null, extras: null };\n if (typeof v === \"string\") return { message: v, errorType: null, extras: null };\n if (typeof v === \"number\" || typeof v === \"boolean\" || typeof v === \"bigint\") {\n return { message: String(v), errorType: typeof v, extras: null };\n }\n if (typeof v === \"symbol\") {\n return { message: v.toString(), errorType: \"symbol\", extras: null };\n }\n if (typeof v === \"function\") {\n return {\n message: `(thrown function: ${v.name || \"anonymous\"})`,\n errorType: \"function\",\n extras: null,\n };\n }\n\n if (v instanceof Error) {\n const errorType = v.name || v.constructor?.name || \"Error\";\n const message =\n typeof v.message === \"string\" && v.message.length > 0\n ? v.message\n : errorType;\n const extras: Record<string, unknown> = {};\n for (const key of [\"code\", \"status\", \"statusCode\", \"errno\", \"cause\"] as const) {\n const val = (v as unknown as Record<string, unknown>)[key];\n if (val !== undefined && typeof val !== \"function\") {\n extras[key] = safeClone(val);\n }\n }\n for (const key of Object.keys(v)) {\n if (key === \"message\" || key === \"stack\" || key === \"name\" || key === \"cause\") continue;\n if (key in extras) continue;\n const val = (v as unknown as Record<string, unknown>)[key];\n if (typeof val === \"function\") continue;\n extras[key] = safeClone(val);\n }\n return {\n message,\n errorType,\n extras: Object.keys(extras).length > 0 ? extras : null,\n };\n }\n\n // Plain object — try JSON, fall back to \"[Object]\".\n try {\n const s = JSON.stringify(v);\n return {\n message: s && s.length < 200 ? s : \"[Object]\",\n errorType: (v as { constructor?: { name?: string } })?.constructor?.name ?? \"Object\",\n extras: null,\n };\n } catch {\n return { message: \"[Object]\", errorType: \"Object\", extras: null };\n }\n}\n\nfunction safeClone(v: unknown): unknown {\n if (v == null) return v;\n const t = typeof v;\n if (t === \"string\" || t === \"number\" || t === \"boolean\") return v;\n if (t === \"bigint\") return String(v);\n try {\n const s = JSON.stringify(v);\n return s === undefined ? String(v) : JSON.parse(s);\n } catch {\n return String(v);\n }\n}\n","/**\n * Public API surface for @cross-deck/react-native.\n *\n * Usage:\n *\n * import { Crossdeck } from \"@cross-deck/react-native\";\n *\n * Crossdeck.init({\n * appId: \"app_rn_xxx\",\n * publicKey: \"cd_pub_live_…\",\n * environment: \"production\",\n * });\n *\n * await Crossdeck.identify(\"user_847\");\n * const ents = await Crossdeck.getEntitlements();\n * if (Crossdeck.isEntitled(\"pro\")) {\n * showPro();\n * }\n * Crossdeck.track(\"paywall_shown\", { variant: \"v3\" });\n *\n * Lifecycle:\n *\n * - `init()` returns void but kicks off async hydration (identity,\n * super-props, entitlement cache, persisted event queue) in the\n * background. The returned `ready` promise is awaited internally\n * by every async method (`identify`, `track`, `flush`,\n * `getEntitlements`, etc.) so callers don't need to babysit it.\n * - Sync methods (`isEntitled`, `getSuperProperties`,\n * `diagnostics`) read in-memory state. Until `init()` has fired\n * they return sensible empties (false, {}, the \"not-started\"\n * diagnostics shape).\n * - `reset()` is fully sync; identity/storage wipe fans out\n * fire-and-forget to AsyncStorage.\n */\n\nimport { CrossdeckError } from \"./errors\";\nimport {\n HttpClient,\n DEFAULT_BASE_URL,\n SDK_NAME,\n SDK_VERSION,\n extractSelfHostname,\n} from \"./http\";\nimport { IdentityStore, randomChars } from \"./identity\";\nimport { EntitlementCache, type EntitlementsListener } from \"./entitlement-cache\";\nimport { deriveIdempotencyKeyForPurchase } from \"./idempotency-key\";\nimport { EventQueue, type QueuedEvent } from \"./event-queue\";\nimport { PersistentEventStore } from \"./event-storage\";\nimport { detectDefaultStorage, MemoryStorage } from \"./storage\";\nimport { collectDeviceInfo, type DeviceInfo } from \"./device-info\";\nimport { ConsoleDebugLogger, findSensitivePropertyKeys, type DebugLogger } from \"./debug\";\nimport { validateEventProperties } from \"./event-validation\";\nimport { SuperPropertyStore } from \"./super-properties\";\nimport { ConsentManager, scrubPiiFromProperties, type ConsentState } from \"./consent\";\nimport { BreadcrumbBuffer, type Breadcrumb } from \"./breadcrumbs\";\nimport type { ContractFailureInput } from \"./contracts\";\nimport { sendDiagnosticTelemetry } from \"./_diagnostic-telemetry\";\nimport {\n DEFAULT_ERROR_CAPTURE,\n ErrorTracker,\n type CapturedError,\n type ErrorCaptureConfig,\n type ErrorLevel,\n} from \"./error-capture\";\nimport type {\n AliasResult,\n CrossdeckOptions,\n Diagnostics,\n EntitlementsListResponse,\n Environment,\n EventProperties,\n GroupTraits,\n HeartbeatResponse,\n IdentifyOptions,\n PublicEntitlement,\n PurchaseResult,\n Platform,\n} from \"./types\";\n\n/**\n * Snapshot of call-time-volatile state captured at `track()` entry\n * and threaded through `trackPostHydration()`. Without this, the\n * post-hydration body would read state mutated AFTER the caller's\n * track() returned — see the comment on `track()` for the racing\n * pattern. Currently scoped to `sessionId` (the only volatile axis\n * the v1.4.0 contract tests pin); add more fields here as the\n * enrichment layer grows.\n */\ninterface TrackCallSnapshot {\n sessionId: string | null;\n}\n\ninterface InternalState {\n http: HttpClient;\n identity: IdentityStore;\n entitlements: EntitlementCache;\n events: EventQueue;\n errors: ErrorTracker | null;\n breadcrumbs: BreadcrumbBuffer;\n errorContext: Record<string, unknown>;\n errorTags: Record<string, string>;\n errorBeforeSend: ((err: CapturedError) => CapturedError | null) | null;\n superProps: SuperPropertyStore;\n consent: ConsentManager;\n scrubPii: boolean;\n deviceInfo: DeviceInfo;\n options: Required<\n Omit<\n CrossdeckOptions,\n \"storage\" | \"sdkVersion\" | \"appVersion\" | \"debug\" | \"scrubPii\" | \"errorCapture\"\n >\n > & {\n sdkVersion: string;\n appVersion: string | null;\n };\n debug: DebugLogger;\n developerUserId: string | null;\n /** v1.4.0 Phase 3.4 — currently-active session id (set by the\n * host via setSessionId(...)). Attached to every track event so\n * cross-platform funnel queries reconcile with web SDK sessions. */\n sessionId: string | null;\n lastServerTime: number | null;\n lastClientTime: number | null;\n /** Promise that resolves when async hydration completes. */\n ready: Promise<void>;\n /** True once init() has fully returned (synchronous portion done). */\n started: boolean;\n /** True once the async hydration in `ready` has completed. */\n hydrated: boolean;\n /**\n * AppState subscription handle so re-init / teardown can detach\n * the listener cleanly. RN apps that hot-reload would otherwise\n * pile up duplicate handlers each module reload.\n */\n appStateSubscription: { remove: () => void } | null;\n}\n\nexport class CrossdeckClient {\n private state: InternalState | null = null;\n\n /**\n * Boot the SDK. Returns void synchronously but kicks off async\n * hydration in the background. Callers can `await\n * Crossdeck.identify(...)` etc. directly — the SDK awaits its own\n * `ready` promise internally.\n *\n * Idempotent — calling init twice with the same options is a\n * no-op; calling with different options tears down the prior\n * tracker and replaces the configuration.\n */\n init(options: CrossdeckOptions): void {\n if (this.state) {\n // Re-init — tear down listeners (error tracker fetch wrap +\n // AppState subscription) before reconstructing. Otherwise\n // duplicate global handlers pile up on every hot-reload in\n // dev and on every test re-init.\n try {\n this.state.errors?.uninstall();\n } catch {\n /* ignore */\n }\n try {\n this.state.appStateSubscription?.remove();\n } catch {\n /* ignore */\n }\n // v1.4.0 Phase 5.5 — drain the prior EventQueue's pending\n // setTimeout BEFORE we replace this.state. Pre-fix the timer\n // would fire AFTER the state swap, firing against new\n // http/identity references with old-init events — a\n // cross-identity leak risk during HMR / config swap. flush()\n // cancels the timer (see EventQueue.cancelTimerIfSet) and\n // ships queued events out under the prior init's identity.\n //\n // CRITICAL: do NOT clear the persistent event store here.\n // The durable AsyncStorage queue belongs to the SDK lifetime,\n // not the init() lifetime — a survived crash mid-flush\n // re-hydrates on the next init.\n try {\n void this.state.events.flush();\n } catch {\n /* ignore */\n }\n }\n\n if (!options.publicKey || !options.publicKey.startsWith(\"cd_pub_\")) {\n throw new CrossdeckError({\n type: \"configuration_error\",\n code: \"invalid_public_key\",\n message: \"Crossdeck.init requires a publishable key starting with cd_pub_.\",\n });\n }\n if (!options.appId) {\n throw new CrossdeckError({\n type: \"configuration_error\",\n code: \"missing_app_id\",\n message:\n \"Crossdeck.init requires an appId. Find yours in the Crossdeck dashboard.\",\n });\n }\n if (options.environment !== \"production\" && options.environment !== \"sandbox\") {\n throw new CrossdeckError({\n type: \"configuration_error\",\n code: \"invalid_environment\",\n message: 'Crossdeck.init requires environment: \"production\" | \"sandbox\".',\n });\n }\n const keyEnv = inferEnvFromKey(options.publicKey);\n if (keyEnv && keyEnv !== options.environment) {\n throw new CrossdeckError({\n type: \"configuration_error\",\n code: \"environment_mismatch\",\n message: `Crossdeck.init: environment \"${options.environment}\" disagrees with key prefix (${keyEnv}). Reconcile your Crossdeck.init({ environment }) with the publishable key prefix.`,\n });\n }\n\n const storage = options.storage ?? detectDefaultStorage();\n const persistIdentity = options.persistIdentity ?? true;\n const opts: InternalState[\"options\"] = {\n appId: options.appId,\n publicKey: options.publicKey,\n environment: options.environment,\n baseUrl: options.baseUrl ?? DEFAULT_BASE_URL,\n persistIdentity,\n storagePrefix: options.storagePrefix ?? \"crossdeck:\",\n autoHeartbeat: options.autoHeartbeat ?? true,\n eventFlushBatchSize: options.eventFlushBatchSize ?? 20,\n // v1.4.0 Phase 3.3 — flush interval default parity at 2000ms\n // across every SDK. Per-instance override stays.\n eventFlushIntervalMs: options.eventFlushIntervalMs ?? 2000,\n sdkVersion: options.sdkVersion ?? SDK_VERSION,\n appVersion: options.appVersion ?? null,\n platform: options.platform ?? detectPlatform(),\n timeoutMs: options.timeoutMs ?? 15_000,\n // Per-platform identity claims for the bank-grade identity\n // lock. Empty string means \"not supplied\" — the HTTP layer\n // skips the header in that case and the backend will reject\n // with bundle_id_not_allowed / package_name_not_allowed at\n // first request if the project requires the lock.\n bundleId: options.bundleId ?? \"\",\n packageName: options.packageName ?? \"\",\n };\n\n const debug = new ConsoleDebugLogger();\n debug.enabled = options.debug === true;\n\n const http = new HttpClient({\n publicKey: opts.publicKey,\n baseUrl: opts.baseUrl,\n sdkVersion: opts.sdkVersion,\n timeoutMs: opts.timeoutMs,\n // Per-platform identity claims — sent as X-Crossdeck-Bundle-Id\n // / X-Crossdeck-Package-Name. Backend enforces these against\n // the app key's stored identity (bank-grade fail-closed).\n bundleId: options.bundleId,\n packageName: options.packageName,\n });\n\n // Identity continuity. When persistIdentity is off (typical\n // during a strict-consent flow before opt-in) we fall back to\n // in-memory only and write nothing to AsyncStorage.\n const effectiveStorage = persistIdentity ? storage : new MemoryStorage();\n const identity = new IdentityStore(effectiveStorage, opts.storagePrefix);\n const entitlements = new EntitlementCache(\n effectiveStorage,\n opts.storagePrefix + \"entitlements\",\n );\n const persistentEvents = persistIdentity\n ? new PersistentEventStore({ storage: effectiveStorage, prefix: opts.storagePrefix })\n : null;\n\n const events = new EventQueue({\n http,\n batchSize: opts.eventFlushBatchSize,\n intervalMs: opts.eventFlushIntervalMs,\n envelope: () => ({\n appId: opts.appId,\n environment: opts.environment,\n sdk: { name: SDK_NAME, version: opts.sdkVersion },\n }),\n persistentStore: persistentEvents ?? undefined,\n onFirstFlushSuccess: () => {\n debug.emit(\n \"sdk.first_event_sent\",\n \"First telemetry event received. View it in Live Events.\",\n { appId: opts.appId, environment: opts.environment },\n );\n },\n onRetryScheduled: (info) => {\n debug.emit(\n \"sdk.flush_retry_scheduled\",\n `Event flush failed (${info.lastError}). Retrying in ${info.delayMs}ms (attempt ${info.consecutiveFailures}).`,\n { ...info },\n );\n },\n onPermanentFailure: (info) => {\n // Bank-grade rule: a permanent 4xx that's dropping events\n // MUST be loud regardless of debug mode. Pre-fix the queue\n // retried 4xx forever silently and the customer never knew\n // their key was revoked.\n const headline = `[crossdeck] Event batch DROPPED (status ${info.status}): ${info.lastError}. ${info.droppedCount} event(s) lost — check your publishable key + app config.`;\n // eslint-disable-next-line no-console\n console.error(headline);\n debug.emit(\"sdk.flush_permanent_failure\", headline, { ...info });\n },\n });\n\n const deviceInfo: DeviceInfo = collectDeviceInfo({\n appVersion: opts.appVersion ?? undefined,\n });\n\n const superProps = new SuperPropertyStore(\n persistIdentity ? effectiveStorage : new MemoryStorage(),\n opts.storagePrefix,\n );\n\n const consent = new ConsentManager();\n const breadcrumbs = new BreadcrumbBuffer(50);\n\n this.state = {\n http,\n identity,\n entitlements,\n events,\n errors: null,\n breadcrumbs,\n errorContext: {},\n errorTags: {},\n errorBeforeSend: null,\n superProps,\n consent,\n scrubPii: options.scrubPii !== false,\n deviceInfo,\n options: opts,\n debug,\n developerUserId: null,\n sessionId: null,\n lastServerTime: null,\n lastClientTime: null,\n started: false,\n hydrated: false,\n ready: Promise.resolve(),\n appStateSubscription: null,\n };\n\n // Wire AppState observer for background persist + flush. When the\n // app moves out of `active` the SDK persists the buffer to\n // AsyncStorage immediately and triggers a best-effort flush\n // (Android gives ~tens of seconds before suspension; iOS gives a\n // few seconds — enough for a small batch). Without this, an RN\n // app that backgrounds during a buffered idle window loses every\n // buffered event when the OS later evicts the process.\n //\n // Mirrors the Web SDK's `pagehide` + `visibilitychange` wiring and\n // the Swift SDK's UIApplication.willResignActive observer.\n try {\n const RN = require(\"react-native\");\n const AppState = RN?.AppState;\n if (AppState && typeof AppState.addEventListener === \"function\") {\n const sub = AppState.addEventListener(\"change\", (next: string) => {\n if (next === \"background\" || next === \"inactive\") {\n // Both Android background + iOS inactive (e.g. app\n // switcher) get the same treatment — persist + try\n // to drain. Caller's flush() returns a Promise we\n // intentionally don't await; AppState callbacks run\n // synchronously and any unfinished flush continues in\n // the background-execution budget.\n try {\n // flush() persists the buffer to disk synchronously\n // (via the internal persistAll path) AND triggers a\n // best-effort network ship. We don't await — AppState\n // callbacks are synchronous; the ship continues in\n // whatever background-execution budget the OS allows.\n void this.state?.events.flush().catch(() => {\n /* permanent-failure callback handles error routing */\n });\n debug.emit(\"sdk.queue_persisted\", \"persisted on AppState background\");\n } catch {\n /* listener never crashes the app */\n }\n }\n });\n this.state.appStateSubscription = sub;\n }\n } catch {\n // react-native AppState unavailable — happens in JVM unit\n // tests, web-only build environments. SDK still functions;\n // just no auto-flush on background. Consumer can wire their\n // own AppState observer + call Crossdeck.flush() manually.\n }\n\n // Error capture — install BEFORE async hydration so an error\n // during boot still surfaces. consented gate keeps reports\n // gated on `consent.errors`.\n const wantErrorCapture = options.errorCapture !== false;\n if (wantErrorCapture) {\n const tracker = new ErrorTracker({\n config: { ...DEFAULT_ERROR_CAPTURE, enabled: true },\n breadcrumbs,\n report: (err) => this.reportError(err),\n getContext: () => ({ ...this.state!.errorContext }),\n getTags: () => ({ ...this.state!.errorTags }),\n beforeSend: () => this.state!.errorBeforeSend,\n isConsented: () => this.state!.consent.errors,\n selfHostname: extractSelfHostname(opts.baseUrl),\n });\n this.state.errors = tracker;\n tracker.install();\n }\n\n debug.emit(\n \"sdk.configured\",\n `Crossdeck connected to ${opts.appId} in ${opts.environment} mode.`,\n {\n appId: opts.appId,\n environment: opts.environment,\n sdkVersion: opts.sdkVersion,\n },\n );\n\n // Kick off async hydration. Every public async method awaits\n // `state.ready` before reading identity / cache / queue state\n // so the caller can `await Crossdeck.identify(...)` immediately\n // after `Crossdeck.init(...)` without manual sequencing.\n this.state.ready = (async () => {\n await Promise.all([\n identity.loadAll(),\n superProps.loadAll(),\n entitlements.hydrate(),\n events.hydrate(),\n ]);\n this.state!.hydrated = true;\n })();\n this.state.started = true;\n\n if (opts.autoHeartbeat) {\n // Fire-and-forget — heartbeat failure shouldn't block init().\n void this.state.ready.then(() => this.heartbeat()).catch(() => undefined);\n }\n }\n\n /**\n * Link the anonymous device to a developer-supplied user ID.\n * Caches the resolved Crossdeck customer for follow-up calls.\n *\n * Accepts an optional `traits` bag — profile data (name, plan,\n * signupDate, role) persisted on the Crossdeck customer record.\n */\n async identify(userId: string, options?: IdentifyOptions): Promise<AliasResult> {\n const s = this.requireStarted();\n if (!userId) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_user_id\",\n message: \"identify(userId) requires a non-empty userId.\",\n });\n }\n await s.ready;\n\n if (!s.consent.analytics) {\n s.debug.emit(\n \"sdk.consent_denied\",\n \"identify() skipped — consent denied for analytics.\",\n );\n return {\n object: \"alias_result\",\n crossdeckCustomerId: s.identity.crossdeckCustomerId ?? \"\",\n linked: [],\n mergePending: false,\n env: s.options.environment,\n };\n }\n\n const traitsValidation =\n options?.traits !== undefined\n ? validateEventProperties(options.traits)\n : null;\n const traits =\n traitsValidation && Object.keys(traitsValidation.properties).length > 0\n ? traitsValidation.properties\n : undefined;\n\n const body: Record<string, unknown> = {\n userId,\n anonymousId: s.identity.anonymousId,\n };\n if (options?.email) body.email = options.email;\n if (traits) body.traits = traits;\n\n // Bank-grade three-layer entitlement-cache isolation (v1.4.0\n // Phase 1.3). Switch the cache slot BEFORE the alias POST so a\n // mid-flight failure can't leave the cache pointing at the\n // prior user. setUserKey:\n // (a) hashes the new userId into a physically separate\n // AsyncStorage suffix — `crossdeck:entitlements:<sha256>`,\n // (b) unconditionally wipes the in-memory snapshot (no\n // conditional gating — every identify() guarantees a\n // fresh slot),\n // (c) rehydrates from the new slot so a returning user sees\n // their last-known-good immediately.\n await s.entitlements.setUserKey(userId);\n\n const result = await s.http.request<AliasResult>(\"POST\", \"/identity/alias\", {\n body,\n });\n s.identity.setCrossdeckCustomerId(result.crossdeckCustomerId);\n s.identity.setDeveloperUserId(userId);\n s.developerUserId = userId;\n return result;\n }\n\n /**\n * Register super-properties — Mixpanel pattern. Once set, every\n * subsequent event of THIS SDK instance carries these keys on its\n * properties bag automatically.\n */\n register(properties: Record<string, unknown>): Record<string, unknown> {\n const s = this.requireStarted();\n const validation = validateEventProperties(properties);\n return s.superProps.register(validation.properties);\n }\n\n /** Remove a single super-property key. Idempotent. */\n unregister(key: string): void {\n const s = this.requireStarted();\n s.superProps.unregister(key);\n }\n\n /** Snapshot of the current super-property bag. */\n getSuperProperties(): Record<string, unknown> {\n if (!this.state) return {};\n return this.state.superProps.getSuperProperties();\n }\n\n /**\n * Associate the current user with a group (org, team, account).\n * Mixpanel / Segment \"Group Analytics\" pattern.\n */\n group(type: string, id: string | null, traits?: GroupTraits): void {\n const s = this.requireStarted();\n if (!type) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_group_type\",\n message: \"group(type, id) requires a non-empty type.\",\n });\n }\n const sanitisedTraits = traits\n ? validateEventProperties(traits).properties\n : undefined;\n s.superProps.setGroup(type, id, sanitisedTraits);\n }\n\n /** Snapshot of the current groups map keyed by type. */\n getGroups(): Record<string, { id: string; traits?: Record<string, unknown> }> {\n if (!this.state) return {};\n return this.state.superProps.getGroups();\n }\n\n /** Update consent state. See `ConsentState` for the dimensions. */\n consent(state: Partial<ConsentState>): ConsentState {\n const s = this.requireStarted();\n const next = s.consent.set(state);\n s.debug.emit(\"sdk.consent_changed\", \"Consent state updated.\", { ...next });\n return next;\n }\n\n /** Snapshot of the current consent state. */\n consentStatus(): ConsentState {\n if (!this.state) {\n return { analytics: true, marketing: true, errors: true };\n }\n return this.state.consent.get();\n }\n\n // ============================================================\n // Error capture surface\n // ============================================================\n\n /** Manually capture an error from a try/catch block. */\n captureError(\n error: unknown,\n options?: {\n context?: Record<string, unknown>;\n tags?: Record<string, string>;\n level?: ErrorLevel;\n },\n ): void {\n if (!this.state?.errors) return;\n this.state.errors.captureError(error, options);\n }\n\n /** Capture a non-error event you want to surface as an issue. */\n captureMessage(message: string, level: ErrorLevel = \"info\"): void {\n if (!this.state?.errors) return;\n this.state.errors.captureMessage(message, level);\n }\n\n setTag(key: string, value: string): void {\n if (!this.state) return;\n this.state.errorTags[key] = value;\n }\n\n setTags(tags: Record<string, string>): void {\n if (!this.state) return;\n Object.assign(this.state.errorTags, tags);\n }\n\n setContext(name: string, data: Record<string, unknown>): void {\n if (!this.state) return;\n this.state.errorContext[name] = data;\n }\n\n addBreadcrumb(crumb: Breadcrumb): void {\n if (!this.state) return;\n this.state.breadcrumbs.add(crumb);\n }\n\n /**\n * Install a pre-send hook for errors. Return null to drop, or a\n * modified `CapturedError` to scrub / rewrite. Sentry's\n * beforeSend pattern — the only way to redact app-specific PII\n * (auth tokens in URLs, etc.) before the report leaves the\n * device.\n */\n setErrorBeforeSend(\n hook: ((err: CapturedError) => CapturedError | null) | null,\n ): void {\n if (!this.state) return;\n this.state.errorBeforeSend = hook;\n }\n\n private reportError(err: CapturedError): void {\n const properties: EventProperties = {\n fingerprint: err.fingerprint,\n level: err.level,\n errorType: err.errorType,\n message: err.message,\n stack: err.rawStack ?? undefined,\n frames: err.frames,\n filename: err.filename ?? undefined,\n lineno: err.lineno ?? undefined,\n colno: err.colno ?? undefined,\n tags: err.tags,\n context: err.context,\n breadcrumbs: err.breadcrumbs,\n http: err.http,\n };\n for (const k of Object.keys(properties)) {\n if (properties[k] === undefined) delete properties[k];\n }\n this.track(err.kind, properties);\n }\n\n /**\n * GDPR/CCPA right to be forgotten. Calls\n * `/v1/identity/forget` to schedule server-side deletion, then\n * wipes all local state (identity, entitlements, queue,\n * super-props, breadcrumbs).\n */\n async forget(): Promise<void> {\n const s = this.requireStarted();\n await s.ready;\n const identityQuery = this.identityQueryParams();\n try {\n await s.http.request<{ object: \"forgot\" }>(\"POST\", \"/identity/forget\", {\n body: { ...identityQuery },\n });\n } catch (err) {\n s.debug.emit(\n \"sdk.consent_denied\",\n `forget() server call failed (${err instanceof Error ? err.message : String(err)}). Local state wiped anyway.`,\n );\n }\n this.reset();\n }\n\n /**\n * Read the current customer's active entitlements from the\n * server. Updates the local cache so subsequent `isEntitled()`\n * calls answer synchronously.\n */\n async getEntitlements(): Promise<PublicEntitlement[]> {\n const s = this.requireStarted();\n await s.ready;\n const query = this.identityQueryParams();\n let result: EntitlementsListResponse;\n try {\n result = await s.http.request<EntitlementsListResponse>(\n \"GET\",\n \"/entitlements\",\n { query },\n );\n } catch (err) {\n s.entitlements.markRefreshFailed();\n throw err;\n }\n if (result.crossdeckCustomerId) {\n s.identity.setCrossdeckCustomerId(result.crossdeckCustomerId);\n }\n s.entitlements.setFromList(result.data);\n return result.data;\n }\n\n /**\n * Synchronous read from the durable local cache — answers from\n * last-known-good. The cache hydrates from device storage during\n * init() so a returning paying customer reads true even before\n * the session's first network round-trip. Returns false for a\n * genuinely new install that has never completed a\n * `getEntitlements()`, or for an entitlement past its own\n * `validUntil`.\n */\n isEntitled(key: string): boolean {\n if (!this.state) return false;\n return this.state.entitlements.isEntitled(key);\n }\n\n /** Snapshot of the local entitlement cache. */\n listEntitlements(): PublicEntitlement[] {\n if (!this.state) return [];\n return this.state.entitlements.list();\n }\n\n /**\n * Subscribe to entitlement-cache changes. Returns an idempotent\n * unsubscribe fn. The listener fires AFTER `getEntitlements()`\n * warms the cache, after `syncPurchases()` delivers fresh\n * entitlements, and on `reset()` to fire the empty-cache state\n * for logout flows.\n *\n * Listener errors are swallowed (a buggy consumer must not crash\n * the SDK or other listeners).\n */\n onEntitlementsChange(listener: EntitlementsListener): () => void {\n const s = this.requireStarted();\n return s.entitlements.subscribe(listener);\n }\n\n /**\n * Queue a telemetry event. Returns immediately — the network\n * round-trip happens in the background. Call `flush()` to force\n * an immediate send (e.g. when the app is backgrounding).\n *\n * RN-specific contract: identity hydration is async (AsyncStorage),\n * so a `track()` call fired in the same tick as `init()` reaches\n * the identity store before `loadAll()` has resolved. We defer the\n * post-validation portion via `s.ready.then(...)` in that case so\n * the event lands AFTER hydration with the right identity hint\n * stamped. Common-case `track()` after hydration runs entirely\n * synchronously.\n */\n /**\n * Emit `crossdeck.contract_failed` to the Crossdeck reliability\n * endpoint — single-fire, one-way, never visible in the customer's\n * dashboard. Goes over a dedicated HTTP path with the reliability\n * publishable key embedded at build time; the customer's track()\n * pipeline never carries `crossdeck.*` events. This is the\n * independent-controller flow described in Privacy Policy §6\n * (\"Flow B\"). The wire shape is fixed by the schema-lock contract\n * at `contracts/diagnostics/contract-failed-payload-schema-lock.json`.\n */\n reportContractFailure(input: ContractFailureInput): void {\n const payload: Record<string, string> = {\n contract_id: input.contractId,\n sdk_version: SDK_VERSION,\n sdk_platform: \"react-native\",\n failure_reason: input.failureReason,\n run_context: input.runContext,\n run_id: input.runId,\n };\n if (input.testRef) {\n payload.test_file = input.testRef.file;\n payload.test_name = input.testRef.name;\n }\n if (input.deviceClass) {\n payload.device_class = input.deviceClass;\n }\n sendDiagnosticTelemetry(payload);\n }\n\n track(name: string, properties?: EventProperties): void {\n const s = this.requireStarted();\n if (!name) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_event_name\",\n message: \"track(name) requires a non-empty name.\",\n });\n }\n // Capture call-time-volatile state BEFORE deferring through\n // `s.ready.then(...)`. Without this snapshot, two pre-hydration\n // `track()` calls separated by `setSessionId(...)` (or any other\n // mutation) would both read the LATEST value when the deferred\n // bodies fire post-hydration — silently rewriting the first\n // event with the second event's state. The Web SDK has no\n // hydration window so this race only exists on RN.\n const callTimeSnapshot: TrackCallSnapshot = {\n sessionId: s.sessionId,\n };\n if (!s.hydrated) {\n void s.ready.then(() => this.trackPostHydration(s, name, properties, callTimeSnapshot));\n return;\n }\n this.trackPostHydration(s, name, properties, callTimeSnapshot);\n }\n\n /**\n * The body of `track()` — everything after the synchronous\n * validation. Split out so the public `track()` can defer this\n * portion until async identity hydration completes (RN-specific —\n * see `track()` jsdoc).\n */\n private trackPostHydration(\n s: InternalState,\n name: string,\n properties: EventProperties | undefined,\n callTimeSnapshot: TrackCallSnapshot,\n ): void {\n // Consent gate. error.* events gate on consent.errors; everything\n // else gates on consent.analytics.\n const isError = name.startsWith(\"error.\");\n const consentGateOk = isError ? s.consent.errors : s.consent.analytics;\n if (!consentGateOk) {\n if (s.debug.enabled) {\n s.debug.emit(\n \"sdk.consent_denied\",\n `Dropped event \"${name}\" — consent denied.`,\n );\n }\n return;\n }\n\n // PII property-name warning (debug mode only).\n if (s.debug.enabled && properties) {\n const flagged = findSensitivePropertyKeys(properties);\n if (flagged.length > 0) {\n s.debug.emit(\n \"sdk.sensitive_property_warning\",\n `Event \"${name}\" has potentially sensitive property names: ${flagged.join(\", \")}. Crossdeck is privacy-first — avoid sending PII unless intentional.`,\n { eventName: name, flagged },\n );\n }\n }\n\n // §16 \"No identity\" — only emit once per session.\n if (\n s.debug.enabled &&\n !s.developerUserId &&\n !s.identity.crossdeckCustomerId\n ) {\n s.debug.emit(\n \"sdk.no_identity\",\n \"Using anonymous user until identify(userId) is called.\",\n );\n }\n\n // Validate + coerce caller-supplied properties.\n const validation = validateEventProperties(properties);\n if (s.debug.enabled && validation.warnings.length > 0) {\n for (const w of validation.warnings) {\n s.debug.emit(\n \"sdk.property_coerced\",\n `Event \"${name}\" property ${JSON.stringify(w.key)} was ${w.kind.replace(/_/g, \" \")} during validation.`,\n { eventName: name, key: w.key, kind: w.kind },\n );\n }\n }\n\n // Enrichment layer order (later wins on key conflict):\n // 1. Device info\n // 2. Super properties\n // 3. Group memberships\n // 4. SessionId (v1.4.0 Phase 3.4 — funnel parity with web)\n // 5. Caller-supplied properties (sanitised)\n const enriched: EventProperties = { ...s.deviceInfo };\n const supers = s.superProps.getSuperProperties();\n for (const k of Object.keys(supers)) {\n if (!(k in enriched)) enriched[k] = supers[k];\n }\n const groupIds = s.superProps.getGroupIds();\n if (Object.keys(groupIds).length > 0) {\n enriched.$groups = groupIds;\n }\n // v1.4.0 Phase 3.4 — attach sessionId so RN events reconcile\n // with the web SDK's session-anchored funnel queries. RN\n // doesn't own session lifecycle (the host's AppState +\n // nav library do); call setSessionId() from your AppState\n // change listener to populate this. Read the call-time\n // snapshot so two pre-hydration track() calls separated by\n // setSessionId(...) keep their respective session anchors.\n if (callTimeSnapshot.sessionId) {\n enriched.sessionId = callTimeSnapshot.sessionId;\n }\n Object.assign(enriched, validation.properties);\n\n // PII scrub — defensive regex pass before the event lands in\n // the queue.\n const finalProperties = s.scrubPii\n ? scrubPiiFromProperties(enriched)\n : enriched;\n\n const event: QueuedEvent = {\n eventId: this.mintEventId(),\n name,\n timestamp: Date.now(),\n properties: finalProperties,\n };\n Object.assign(event, this.identityHintForEvent());\n s.events.enqueue(event);\n\n // Breadcrumb emission — every analytics event becomes a\n // breadcrumb so error reports carry the context of what the\n // user was doing just before the crash. Don't emit a breadcrumb\n // for error events themselves (circular).\n if (!isError) {\n const category = name.startsWith(\"page.\") || name.startsWith(\"screen.\")\n ? \"navigation\"\n : name.startsWith(\"element.\") || name === \"session.started\"\n ? \"ui.click\"\n : \"custom\";\n s.breadcrumbs.add({\n timestamp: event.timestamp,\n category,\n message: name,\n data: properties ? { ...properties } : undefined,\n });\n }\n }\n\n /** Force-flush queued events. Useful from AppState background transitions. */\n async flush(): Promise<void> {\n const s = this.requireStarted();\n await s.ready;\n await s.events.flush();\n }\n\n /**\n * Forward purchase evidence to the backend for verification +\n * entitlement projection. RN apps typically wire this from\n * `react-native-iap` callbacks for Apple StoreKit 2 + Google\n * Billing receipts.\n */\n async syncPurchases(input: {\n rail?: \"apple\" | \"google\";\n signedTransactionInfo?: string;\n signedRenewalInfo?: string;\n purchaseToken?: string;\n appAccountToken?: string;\n }): Promise<PurchaseResult> {\n const s = this.requireStarted();\n await s.ready;\n const rail = input.rail ?? \"apple\";\n if (rail === \"apple\" && !input.signedTransactionInfo) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_signed_transaction_info\",\n message:\n \"syncPurchases (apple) requires a signedTransactionInfo string from StoreKit 2.\",\n });\n }\n if (rail === \"google\" && !input.purchaseToken) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_purchase_token\",\n message:\n \"syncPurchases (google) requires a purchaseToken string from Google Billing.\",\n });\n }\n const body = { ...input, rail };\n // Phase 2.2 bank-grade contract: deterministic Idempotency-Key\n // from the body. Same input → same key → backend short-circuits\n // with idempotent_replay: true on retry.\n const idempotencyKey = deriveIdempotencyKeyForPurchase(body);\n const result = await s.http.request<PurchaseResult>(\"POST\", \"/purchases/sync\", {\n body,\n idempotencyKey,\n });\n s.identity.setCrossdeckCustomerId(result.crossdeckCustomerId);\n s.entitlements.setFromList(result.entitlements);\n // Phase 3.5 (v1.4.0) — emit purchase.completed so RN manual\n // syncPurchases callers show up on the same funnel as the\n // Swift/Android auto-track path. Schema mirrors the native\n // auto-track shape on event name + rail/productId.\n try {\n const sourceProductId = result.entitlements[0]?.source.productId;\n const sourceSubscriptionId = result.entitlements[0]?.source.subscriptionId;\n const props: Record<string, unknown> = { rail };\n if (sourceProductId) props.productId = sourceProductId;\n if (sourceSubscriptionId) props.subscriptionId = sourceSubscriptionId;\n if (result.idempotent_replay) props.idempotent_replay = true;\n this.track(\"purchase.completed\", props);\n } catch {\n // defensive\n }\n s.debug.emit(\n \"sdk.purchase_evidence_sent\",\n `${rail === \"apple\" ? \"StoreKit\" : \"Google Billing\"} purchase evidence forwarded. Waiting for backend verification.`,\n { rail },\n );\n return result;\n }\n\n /**\n * v1.4.0 Phase 3.4 — set the active session id. RN doesn't own\n * session lifecycle (that's the host's AppState + nav library);\n * the host calls `setSessionId()` from its AppState change\n * listener so every subsequent `track()` event carries the\n * `sessionId` property — matches the web SDK's session-anchored\n * funnel queries.\n *\n * ```ts\n * import { AppState } from \"react-native\";\n *\n * let sessionId = uuid();\n * AppState.addEventListener(\"change\", (next) => {\n * if (next === \"active\") {\n * // New session if backgrounded > 30 min.\n * sessionId = uuid();\n * Crossdeck.setSessionId(sessionId);\n * } else if (next === \"background\") {\n * void Crossdeck.flush();\n * }\n * });\n * Crossdeck.setSessionId(sessionId);\n * ```\n *\n * Pass `null` to clear (between sessions, on logout, etc).\n */\n setSessionId(sessionId: string | null): void {\n const s = this.requireStarted();\n s.sessionId = sessionId ?? null;\n if (s.debug.enabled) {\n s.debug.emit(\n \"sdk.configured\",\n sessionId\n ? `Session id set to ${sessionId}; subsequent track events will carry it.`\n : \"Session id cleared; subsequent track events will omit it.\",\n );\n }\n }\n\n /** Toggle verbose diagnostic logging. */\n setDebugMode(enabled: boolean): void {\n const s = this.requireStarted();\n s.debug.enabled = enabled;\n if (enabled) {\n s.debug.emit(\n \"sdk.configured\",\n `Debug mode enabled for ${s.options.appId} in ${s.options.environment} mode.`,\n { appId: s.options.appId, environment: s.options.environment },\n );\n }\n }\n\n /**\n * Send the boot heartbeat. Called automatically by init() unless\n * `autoHeartbeat: false`. Captures clock skew between client and\n * server for diagnostics.\n */\n async heartbeat(): Promise<HeartbeatResponse> {\n const s = this.requireStarted();\n await s.ready;\n const result = await s.http.request<HeartbeatResponse>(\"GET\", \"/sdk/heartbeat\");\n if (typeof result?.serverTime === \"number\" && Number.isFinite(result.serverTime)) {\n s.lastServerTime = result.serverTime;\n s.lastClientTime = Date.now();\n }\n return result;\n }\n\n /**\n * Wipe persisted identity + entitlement cache + super-props +\n * breadcrumbs + queue. Use on logout. The next pre-login session\n * generates a fresh anonymousId and starts a new identity-graph\n * entry.\n */\n reset(): void {\n if (!this.state) return;\n if (this.state.developerUserId) {\n try {\n this.track(\"user.signed_out\", { auto: true });\n } catch {\n /* defensive — reset() must be bulletproof for logout flows */\n }\n }\n this.state.identity.reset();\n // Logout-grade wipe: removes EVERY per-user entitlement slot on\n // this device (layer (c) of the v1.4.0 isolation fix). A shared\n // device can never leave another user's entitlements readable\n // after a logout. Fire-and-forget — reset() stays synchronous\n // to preserve its existing public contract.\n void this.state.entitlements.clearAll();\n this.state.events.reset();\n this.state.superProps.clear();\n this.state.breadcrumbs.clear();\n this.state.errorContext = {};\n this.state.errorTags = {};\n this.state.developerUserId = null;\n // Null clock-skew snapshot on reset — these values belong to\n // the pre-logout session.\n this.state.lastServerTime = null;\n this.state.lastClientTime = null;\n }\n\n /**\n * Diagnostic snapshot. Stable shape regardless of whether\n * init() has been called — callers don't need to narrow on\n * `started` to access `events` or `entitlements`.\n */\n diagnostics(): Diagnostics {\n if (!this.state) {\n return {\n started: false,\n anonymousId: null,\n crossdeckCustomerId: null,\n developerUserId: null,\n sdkVersion: null,\n baseUrl: null,\n platform: null,\n clock: { lastServerTime: null, lastClientTime: null, skewMs: null },\n entitlements: { count: 0, lastUpdated: 0, stale: false, listenerErrors: 0 },\n events: {\n buffered: 0,\n dropped: 0,\n inFlight: 0,\n lastFlushAt: 0,\n lastError: null,\n consecutiveFailures: 0,\n nextRetryAt: null,\n },\n };\n }\n const s = this.state;\n const skewMs =\n s.lastServerTime !== null && s.lastClientTime !== null\n ? s.lastClientTime - s.lastServerTime\n : null;\n return {\n started: true,\n anonymousId: s.hydrated ? s.identity.anonymousId : null,\n crossdeckCustomerId: s.hydrated ? s.identity.crossdeckCustomerId : null,\n developerUserId: s.developerUserId,\n sdkVersion: s.options.sdkVersion,\n baseUrl: s.options.baseUrl,\n platform: s.options.platform,\n clock: {\n lastServerTime: s.lastServerTime,\n lastClientTime: s.lastClientTime,\n skewMs,\n },\n entitlements: {\n count: s.hydrated ? s.entitlements.list().length : 0,\n lastUpdated: s.hydrated ? s.entitlements.freshness : 0,\n stale: s.hydrated ? s.entitlements.isStale : false,\n listenerErrors: s.entitlements.listenerErrors,\n },\n events: s.events.getStats(),\n };\n }\n\n // ---------- private helpers ----------\n\n private requireStarted(): InternalState {\n if (!this.state) {\n throw new CrossdeckError({\n type: \"configuration_error\",\n code: \"not_initialized\",\n message:\n \"Call Crossdeck.init({ appId, publicKey, environment }) before any other method.\",\n });\n }\n return this.state;\n }\n\n /**\n * Build the identity query for /v1/entitlements. Priority:\n * crossdeckCustomerId > developerUserId > anonymousId\n */\n private identityQueryParams(): Record<string, string | undefined> {\n const s = this.requireStarted();\n if (s.identity.crossdeckCustomerId) {\n return { customerId: s.identity.crossdeckCustomerId };\n }\n if (s.developerUserId) return { userId: s.developerUserId };\n return { anonymousId: s.identity.anonymousId };\n }\n\n /**\n * Embed every known identity axis on the event. Send everything we\n * know; let the warehouse count by whichever axis matches the\n * question. Each field is at most 32 bytes — sending three on\n * every event costs ~80 bytes per request.\n */\n private identityHintForEvent(): Pick<\n QueuedEvent,\n \"developerUserId\" | \"anonymousId\" | \"crossdeckCustomerId\"\n > {\n const s = this.requireStarted();\n const hint: Pick<\n QueuedEvent,\n \"developerUserId\" | \"anonymousId\" | \"crossdeckCustomerId\"\n > = {\n anonymousId: s.identity.anonymousId,\n };\n if (s.developerUserId) hint.developerUserId = s.developerUserId;\n if (s.identity.crossdeckCustomerId) {\n hint.crossdeckCustomerId = s.identity.crossdeckCustomerId;\n }\n return hint;\n }\n\n private mintEventId(): string {\n const ts = Date.now().toString(36);\n return `evt_${ts}${randomChars(8)}`;\n }\n}\n\n/**\n * Default singleton — most consumers want one SDK instance per app.\n * Creating extra instances is fine; just `new CrossdeckClient()`.\n */\nexport const Crossdeck = new CrossdeckClient();\n\n// ============================================================\n// Internal helpers\n// ============================================================\n\n/**\n * Derive the env from a publishable key prefix.\n * cd_pub_test_… → \"sandbox\"\n * cd_pub_live_… → \"production\"\n * cd_pub_… → null (legacy / unprefixed — env can't be inferred)\n */\nfunction inferEnvFromKey(publicKey: string): Environment | null {\n if (publicKey.startsWith(\"cd_pub_test_\")) return \"sandbox\";\n if (publicKey.startsWith(\"cd_pub_live_\")) return \"production\";\n return null;\n}\n\n/**\n * Best-effort runtime platform detection via react-native's\n * `Platform.OS`. Returns \"web\" as the safe default in non-RN\n * runtimes (vitest under node, Storybook, etc.) so backend\n * validators don't reject.\n */\nfunction detectPlatform(): Platform {\n try {\n const req = (globalThis as { require?: (id: string) => unknown }).require;\n if (typeof req !== \"function\") return \"web\";\n const mod = req(\"react-native\") as { Platform?: { OS?: string } } | undefined;\n const os = mod?.Platform?.OS;\n if (os === \"ios\" || os === \"android\" || os === \"web\") return os;\n return \"web\";\n } catch {\n return \"web\";\n }\n}\n","// AUTO-GENERATED by scripts/emit-bundled-contracts.mjs — DO NOT EDIT.\n// Source of truth: contracts/**/*.json at the monorepo root.\n// Regenerated on every `npm run build`. The public API in\n// src/contracts.ts reads from this file.\n\nimport type { Contract } from \"./contracts\";\n\nexport const BUNDLED_IN = \"@cross-deck/react-native@1.5.3\" as const;\nexport const SDK_VERSION = \"1.5.3\" as const;\n\nexport const BUNDLED_CONTRACTS: readonly Contract[] = Object.freeze([\n {\n \"id\": \"contract-failed-payload-schema-lock\",\n \"pillar\": \"diagnostics\",\n \"status\": \"enforced\",\n \"claim\": \"The `crossdeck.contract_failed` event payload contains ONLY the named diagnostic fields and never any end-user personal data. The wire shape is fixed — adding a new field requires (1) a pull request that updates this contract's `allowedFields` set, (2) a Privacy Policy §6 amendment, and (3) the Customer Disclosure Template / SDK Data Collection Reference §B updates. Per-SDK assertion tests enforce the field set on every release. The `verification_phase` field is a categorical bucket — values are restricted to `boot` (the SDK self-test ran on Crossdeck.start) or `hot_path` (a verifier observed a real customer-triggered operation). The categorical nature is what preserves the diagnostic-only-not-personal classification. This is the structural guarantee that backs the independent-controller lawful basis in the Privacy Policy: the payload remains diagnostic-only, not personal, so the legitimate-interest analysis stays valid as the SDK evolves.\",\n \"appliesTo\": [\n \"web\",\n \"node\",\n \"swift\",\n \"android\",\n \"react-native\"\n ],\n \"allowedFields\": {\n \"required\": [\n \"contract_id\",\n \"sdk_version\",\n \"sdk_platform\",\n \"failure_reason\",\n \"run_context\",\n \"run_id\"\n ],\n \"optional\": [\n \"test_file\",\n \"test_name\",\n \"device_class\",\n \"verification_phase\"\n ],\n \"forbidden\": [\n \"anonymousId\",\n \"developerUserId\",\n \"crossdeckCustomerId\",\n \"email\",\n \"ip\",\n \"user_agent\",\n \"message\",\n \"stack\",\n \"stack_trace\",\n \"frames\",\n \"exception_message\",\n \"url\",\n \"path\",\n \"screen\",\n \"title\",\n \"label\",\n \"text\",\n \"ariaLabel\",\n \"accessibilityLabel\",\n \"contentDescription\",\n \"session_id\",\n \"sessionId\"\n ]\n },\n \"transport\": \"Telemetry is single-fire to the Crossdeck reliability endpoint only — NOT the customer's appId. The customer's track() pipeline never carries `crossdeck.*` events; the customer's dashboard never shows individual contract failures. Operational telemetry flows one-way to the Crossdeck operations team for SDK reliability purposes (legitimate interest, independent-controller flow per Privacy Policy §6). The reliability endpoint is hardcoded at SDK build time; the publishable key for the reliability project is embedded as a constant and rejects writes that don't match the schema.\",\n \"codeRef\": [\n \"sdks/web/src/crossdeck.ts\",\n \"sdks/node/src/crossdeck-server.ts\",\n \"sdks/swift/Sources/Crossdeck/Crossdeck.swift\",\n \"sdks/swift/Sources/Crossdeck/_DiagnosticTelemetry.swift\",\n \"sdks/android/crossdeck/src/main/kotlin/com/crossdeck/Crossdeck.kt\",\n \"sdks/android/crossdeck/src/main/kotlin/com/crossdeck/_DiagnosticTelemetry.kt\",\n \"sdks/react-native/src/crossdeck.ts\",\n \"backend/src/api/v1-sdk-diagnostic.ts\",\n \"sdks/web/src/_diagnostic-telemetry.ts\",\n \"sdks/node/src/_diagnostic-telemetry.ts\",\n \"sdks/react-native/src/_diagnostic-telemetry.ts\"\n ],\n \"testRef\": [\n {\n \"file\": \"sdks/web/tests/contract-failed-schema-lock.test.ts\",\n \"name\": \"reportContractFailure payload conforms to schema-lock\"\n },\n {\n \"file\": \"sdks/node/tests/contract-failed-schema-lock.test.ts\",\n \"name\": \"reportContractFailure payload conforms to schema-lock\"\n },\n {\n \"file\": \"sdks/swift/Tests/CrossdeckTests/ContractFailedSchemaLockTests.swift\",\n \"name\": \"test_reportContractFailure_payloadFieldsAreInAllowList\"\n },\n {\n \"file\": \"sdks/swift/Tests/CrossdeckTests/ContractFailedSchemaLockTests.swift\",\n \"name\": \"test_reportContractFailure_doesNotEnterCustomerTrackPipeline\"\n },\n {\n \"file\": \"sdks/android/crossdeck/src/test/kotlin/com/crossdeck/ContractFailedSchemaLockTest.kt\",\n \"name\": \"reportContractFailure payload conforms to schema-lock\"\n },\n {\n \"file\": \"sdks/android/crossdeck/src/test/kotlin/com/crossdeck/ContractFailedSchemaLockTest.kt\",\n \"name\": \"reportContractFailure does not enter customer track pipeline\"\n },\n {\n \"file\": \"sdks/react-native/tests/contract-failed-schema-lock.test.ts\",\n \"name\": \"reportContractFailure payload conforms to schema-lock\"\n },\n {\n \"file\": \"backend/tests/unit/v1-sdk-diagnostic.test.ts\",\n \"name\": \"forbidden fields are enumerated in the schema-lock contract\"\n },\n {\n \"file\": \"backend/tests/unit/v1-sdk-diagnostic.test.ts\",\n \"name\": \"required fields are enumerated in the schema-lock contract\"\n },\n {\n \"file\": \"backend/tests/unit/v1-sdk-diagnostic.test.ts\",\n \"name\": \"regression guard: never returns a raw IP\"\n },\n {\n \"file\": \"backend/tests/unit/v1-sdk-diagnostic.test.ts\",\n \"name\": \"verification_phase is in the optional field set\"\n }\n ],\n \"registeredAt\": \"2026-05-27\",\n \"firstRegisteredIn\": \"Diagnostic telemetry single-fire + schema-lock — independent-controller flow\",\n \"privacyReferences\": [\n \"legal/privacy/index.html#sdk-diagnostic\",\n \"legal/customer-disclosure/index.html#flow-b\",\n \"legal/security/index.html#diagnostic\",\n \"legal/sdk-data/index.html#b-diagnostic\"\n ],\n \"bundledIn\": \"@cross-deck/react-native@1.5.3\"\n },\n {\n \"id\": \"error-envelope-shape\",\n \"pillar\": \"errors\",\n \"status\": \"enforced\",\n \"claim\": \"Every v1 REST endpoint returns errors in a Stripe-shape envelope: `{ error: { type, code, message, request_id } }` where `type` is one of authentication_error / permission_error / invalid_request_error / rate_limit_error / internal_error (the wire vocabulary in backend/src/api/v1-errors.ts ApiErrorType). HTTP status parity: invalid_request_error → 400, authentication_error → 401, permission_error → 403, rate_limit_error → 429, internal_error → 500. SDK-side clients parse this shape via `crossdeckErrorFromResponse` (Web/Node/RN) / `crossdeckErrorFrom(response:)` (Swift) / `crossdeckErrorFromResponse` (Android) and surface the request_id verbatim so support traces are end-to-end joinable. Firebase callable endpoints (managed-keys / dashboard auth) use the Firebase HttpsError envelope instead — this contract applies to REST /v1/* only.\",\n \"appliesTo\": [\n \"web\",\n \"node\",\n \"react-native\",\n \"swift\",\n \"android\",\n \"backend\"\n ],\n \"codeRef\": [\n \"backend/src/api/v1-errors.ts\",\n \"sdks/web/src/errors.ts\",\n \"sdks/node/src/errors.ts\",\n \"sdks/react-native/src/errors.ts\",\n \"sdks/swift/Sources/Crossdeck/Errors.swift\",\n \"sdks/android/crossdeck/src/main/kotlin/com/crossdeck/Errors.kt\"\n ],\n \"testRef\": [\n {\n \"file\": \"sdks/swift/Tests/CrossdeckTests/ErrorsTests.swift\",\n \"name\": \"test_errorEnvelope_fallsBackOnGarbageBody\"\n },\n {\n \"file\": \"sdks/swift/Tests/CrossdeckTests/ErrorsTests.swift\",\n \"name\": \"test_errorEnvelope_reads_XRequestId_fallback\"\n },\n {\n \"file\": \"sdks/android/crossdeck/src/test/kotlin/com/crossdeck/ErrorTypeWireVocabTest.kt\",\n \"name\": \"backend 500 response parses to INTERNAL_ERROR\"\n }\n ],\n \"registeredAt\": \"2026-05-26\",\n \"firstRegisteredIn\": \"bank-grade reconciliation v1.4.0 — phase 8 (codifies existing contract)\",\n \"bundledIn\": \"@cross-deck/react-native@1.5.3\"\n },\n {\n \"id\": \"flush-interval-parity\",\n \"pillar\": \"analytics\",\n \"status\": \"enforced\",\n \"claim\": \"Every Crossdeck SDK defaults its event-queue flush interval to 2000ms — the Stripe-adjacent industry norm. Pre-v1.4.0 the defaults disagreed (Web/Node 1500ms; RN/Swift/Android 5000ms), so cross-platform funnels saw events landing at different cadences. Per-instance override stays — call sites can still tune it freely.\",\n \"appliesTo\": [\n \"web\",\n \"node\",\n \"react-native\",\n \"swift\",\n \"android\"\n ],\n \"codeRef\": [\n \"sdks/web/src/crossdeck.ts\",\n \"sdks/node/src/crossdeck-server.ts\",\n \"sdks/react-native/src/crossdeck.ts\",\n \"sdks/swift/Sources/Crossdeck/EventQueue.swift\",\n \"sdks/android/crossdeck/src/main/kotlin/com/crossdeck/EventQueue.kt\"\n ],\n \"testRef\": [\n {\n \"file\": \"sdks/swift/Sources/Crossdeck/EventQueue.swift\",\n \"name\": \"flushIntervalMs: Int = 2_000\"\n },\n {\n \"file\": \"sdks/android/crossdeck/src/main/kotlin/com/crossdeck/EventQueue.kt\",\n \"name\": \"flushIntervalMs: Long = 2_000L\"\n },\n {\n \"file\": \"sdks/web/src/crossdeck.ts\",\n \"name\": \"options.eventFlushIntervalMs ?? 2000\"\n },\n {\n \"file\": \"sdks/node/src/crossdeck-server.ts\",\n \"name\": \"options.eventFlushIntervalMs ?? 2000\"\n },\n {\n \"file\": \"sdks/react-native/src/crossdeck.ts\",\n \"name\": \"options.eventFlushIntervalMs ?? 2000\"\n }\n ],\n \"registeredAt\": \"2026-05-26\",\n \"firstRegisteredIn\": \"bank-grade reconciliation v1.4.0 — phase 3.3\",\n \"bundledIn\": \"@cross-deck/react-native@1.5.3\"\n },\n {\n \"id\": \"idempotency-key-deterministic\",\n \"pillar\": \"revenue\",\n \"status\": \"enforced\",\n \"claim\": \"syncPurchases() on every SDK derives a deterministic Idempotency-Key from the request body (UUID-shaped SHA-256 of `crossdeck:purchases/sync:<rail>:<jws|token>`). Same input -> same key. Backend short-circuits same-key-same-body retries by returning the cached response (status + body) with `idempotent_replay: true` flag in the body AND `Idempotent-Replayed: true` response header. Same-key-different-body returns 400 `idempotency_key_in_use`. 24-hour TTL matches Stripe. Cache only stores 2xx responses — 4xx/5xx pass through so callers can fix bugs and retry. Helper returns nil/throws on missing identifier (no silent random fallback). Cross-SDK parity is CI-pinned: deriveForPurchase('apple', 'eyJ.jws.sig') MUST equal 'a66b1640-efaf-bb4d-1261-6650033bf111' on every SDK.\",\n \"appliesTo\": [\n \"web\",\n \"node\",\n \"react-native\",\n \"swift\",\n \"android\",\n \"backend\"\n ],\n \"codeRef\": [\n \"sdks/web/src/idempotency-key.ts\",\n \"sdks/web/src/crossdeck.ts\",\n \"sdks/react-native/src/idempotency-key.ts\",\n \"sdks/react-native/src/crossdeck.ts\",\n \"sdks/node/src/idempotency-key.ts\",\n \"sdks/node/src/crossdeck-server.ts\",\n \"sdks/swift/Sources/Crossdeck/IdempotencyKey.swift\",\n \"sdks/swift/Sources/Crossdeck/Crossdeck.swift\",\n \"sdks/android/crossdeck/src/main/kotlin/com/crossdeck/IdempotencyKey.kt\",\n \"sdks/android/crossdeck/src/main/kotlin/com/crossdeck/Crossdeck.kt\",\n \"backend/src/lib/idempotency-response-cache.ts\",\n \"backend/src/api/v1-purchases.ts\"\n ],\n \"testRef\": [\n {\n \"file\": \"sdks/web/tests/idempotency-key.test.ts\",\n \"name\": \"cross-SDK oracle — apple JWS pins canonical vector\"\n },\n {\n \"file\": \"sdks/web/tests/idempotency-key.test.ts\",\n \"name\": \"is deterministic: same body twice -> identical key\"\n },\n {\n \"file\": \"sdks/web/tests/idempotency-key.test.ts\",\n \"name\": \"same identifier under different rails -> different keys\"\n },\n {\n \"file\": \"sdks/web/tests/idempotency-key.test.ts\",\n \"name\": \"never silently falls back to a random key on missing identifier\"\n },\n {\n \"file\": \"sdks/react-native/tests/idempotency-key.test.ts\",\n \"name\": \"is deterministic\"\n },\n {\n \"file\": \"sdks/react-native/tests/idempotency-key.test.ts\",\n \"name\": \"cross-SDK oracle — apple JWS pins canonical vector\"\n },\n {\n \"file\": \"sdks/node/tests/idempotency-key.test.ts\",\n \"name\": \"is deterministic\"\n },\n {\n \"file\": \"sdks/node/tests/idempotency-key.test.ts\",\n \"name\": \"rail namespacing prevents cross-rail collisions\"\n },\n {\n \"file\": \"sdks/node/tests/idempotency-key.test.ts\",\n \"name\": \"apple JWS produces the canonical pinned UUID across all 5 SDKs\"\n },\n {\n \"file\": \"backend/tests/unit/idempotency-response-cache.test.ts\",\n \"name\": \"is deterministic for the same input\"\n },\n {\n \"file\": \"backend/tests/unit/idempotency-response-cache.test.ts\",\n \"name\": \"injects idempotent_replay: true into a JSON object body\"\n },\n {\n \"file\": \"backend/tests/unit/idempotency-response-cache.test.ts\",\n \"name\": \"matches Stripe's 24-hour idempotency window\"\n },\n {\n \"file\": \"sdks/swift/Tests/CrossdeckTests/IdempotencyKeyTests.swift\",\n \"name\": \"test_crossSdkOracle_appleJWS\"\n },\n {\n \"file\": \"sdks/swift/Tests/CrossdeckTests/IdempotencyKeyTests.swift\",\n \"name\": \"test_railNamespacing_preventsCrossRailCollisions\"\n },\n {\n \"file\": \"sdks/swift/Tests/CrossdeckTests/IdempotencyKeyTests.swift\",\n \"name\": \"test_missingIdentifier_returnsNil\"\n },\n {\n \"file\": \"sdks/android/crossdeck/src/test/kotlin/com/crossdeck/IdempotencyKeyTest.kt\",\n \"name\": \"cross-SDK oracle for apple JWS\"\n },\n {\n \"file\": \"sdks/android/crossdeck/src/test/kotlin/com/crossdeck/IdempotencyKeyTest.kt\",\n \"name\": \"rail namespacing prevents cross-rail collisions\"\n },\n {\n \"file\": \"sdks/android/crossdeck/src/test/kotlin/com/crossdeck/IdempotencyKeyTest.kt\",\n \"name\": \"missing identifier returns null - never silent random fallback\"\n }\n ],\n \"registeredAt\": \"2026-05-26\",\n \"firstRegisteredIn\": \"bank-grade reconciliation v1.4.0 — phase 2.2.a + 2.2.b + 2.2.c\",\n \"bundledIn\": \"@cross-deck/react-native@1.5.3\"\n },\n {\n \"id\": \"init-reentry-drains-prior-queue\",\n \"pillar\": \"lifecycle\",\n \"status\": \"enforced\",\n \"claim\": \"Web + RN init() re-entry drains the prior EventQueue's pending setTimeout BEFORE replacing this.state. Pre-v1.4.0 the teardown handled autoTracker/webVitals/errors/unloadFlush but NOT events, so the prior queue's timer would fire AFTER the state swap — sending old-init events against new-init http + identity references (cross-identity leak during HMR / config swap / multi-tenant SDK shells). The teardown CANNOT call persistent.clear() — the durable queue belongs to the SDK lifetime, not the init() lifetime, and a survived crash mid-flush re-hydrates on the next init.\",\n \"appliesTo\": [\n \"web\",\n \"react-native\"\n ],\n \"codeRef\": [\n \"sdks/web/src/crossdeck.ts\",\n \"sdks/react-native/src/crossdeck.ts\"\n ],\n \"testRef\": [\n {\n \"file\": \"sdks/web/tests/init-reentry.test.ts\",\n \"name\": \"re-init drains the prior queue's pending timer before swapping state\"\n },\n {\n \"file\": \"sdks/web/tests/init-reentry.test.ts\",\n \"name\": \"re-init does NOT wipe the durable event store\"\n }\n ],\n \"registeredAt\": \"2026-05-26\",\n \"firstRegisteredIn\": \"bank-grade reconciliation v1.4.0 — phase 5.5\",\n \"bundledIn\": \"@cross-deck/react-native@1.5.3\"\n },\n {\n \"id\": \"per-user-cache-isolation\",\n \"pillar\": \"entitlements\",\n \"status\": \"enforced\",\n \"claim\": \"Every identify(userId) switches the entitlement cache to a physically separate per-user storage slot — `crossdeck:entitlements:<sha256(userId)>` — and unconditionally wipes the in-memory snapshot. A user-switch on a shared device CANNOT cross-read a prior user's cached entitlements, even if the in-memory clear is somehow skipped, because the storage keys are physically separate. reset() wipes every per-user slot via the persisted index.\",\n \"appliesTo\": [\n \"web\",\n \"react-native\",\n \"swift\",\n \"android\"\n ],\n \"codeRef\": [\n \"sdks/web/src/entitlement-cache.ts\",\n \"sdks/web/src/hash.ts\",\n \"sdks/web/src/crossdeck.ts\",\n \"sdks/react-native/src/entitlement-cache.ts\",\n \"sdks/react-native/src/hash.ts\",\n \"sdks/react-native/src/crossdeck.ts\",\n \"sdks/swift/Sources/Crossdeck/EntitlementCache.swift\",\n \"sdks/swift/Sources/Crossdeck/IdempotencyKey.swift\",\n \"sdks/swift/Sources/Crossdeck/Crossdeck.swift\",\n \"sdks/android/crossdeck/src/main/kotlin/com/crossdeck/EntitlementCache.kt\",\n \"sdks/android/crossdeck/src/main/kotlin/com/crossdeck/IdempotencyKey.kt\",\n \"sdks/android/crossdeck/src/main/kotlin/com/crossdeck/Crossdeck.kt\"\n ],\n \"testRef\": [\n {\n \"file\": \"sdks/web/tests/entitlement-cache-isolation.test.ts\",\n \"name\": \"identify(B) makes A's entitlements unreachable from in-memory\"\n },\n {\n \"file\": \"sdks/web/tests/entitlement-cache-isolation.test.ts\",\n \"name\": \"clearAll() removes every per-user storage key plus the index\"\n },\n {\n \"file\": \"sdks/web/tests/entitlement-cache-isolation.test.ts\",\n \"name\": \"a second cache instance reading A's storage suffix CANNOT see B's data\"\n },\n {\n \"file\": \"sdks/react-native/tests/entitlement-cache-isolation.test.ts\",\n \"name\": \"identify(B) makes A's entitlements unreachable from in-memory\"\n },\n {\n \"file\": \"sdks/react-native/tests/entitlement-cache-isolation.test.ts\",\n \"name\": \"removes every per-user storage key plus the index\"\n },\n {\n \"file\": \"sdks/swift/Tests/CrossdeckTests/EntitlementCacheIsolationTests.swift\",\n \"name\": \"test_identifyB_makesAEntitlementsUnreachable\"\n },\n {\n \"file\": \"sdks/swift/Tests/CrossdeckTests/EntitlementCacheIsolationTests.swift\",\n \"name\": \"test_identifiedWritesLandUnderPerUserSha256Key\"\n },\n {\n \"file\": \"sdks/swift/Tests/CrossdeckTests/EntitlementCacheIsolationTests.swift\",\n \"name\": \"test_clearAll_removesEveryPerUserStorageKeyPlusIndex\"\n },\n {\n \"file\": \"sdks/android/crossdeck/src/test/kotlin/com/crossdeck/EntitlementCacheIsolationTest.kt\",\n \"name\": \"identified writes land under per-user sha256 key\"\n },\n {\n \"file\": \"sdks/android/crossdeck/src/test/kotlin/com/crossdeck/EntitlementCacheIsolationTest.kt\",\n \"name\": \"identify B makes A entitlements unreachable from in-memory\"\n },\n {\n \"file\": \"sdks/android/crossdeck/src/test/kotlin/com/crossdeck/EntitlementCacheIsolationTest.kt\",\n \"name\": \"clearAll removes every per-user storage key plus the index\"\n },\n {\n \"file\": \"sdks/android/crossdeck/src/test/kotlin/com/crossdeck/EntitlementCacheIsolationTest.kt\",\n \"name\": \"a fresh cache bound to A's key CANNOT read B's blob\"\n }\n ],\n \"registeredAt\": \"2026-05-26\",\n \"firstRegisteredIn\": \"bank-grade reconciliation v1.4.0 — phase 1.3 (web/RN) + dogfood-gap fix (swift + android)\",\n \"bundledIn\": \"@cross-deck/react-native@1.5.3\"\n },\n {\n \"id\": \"rn-session-id-enrichment\",\n \"pillar\": \"analytics\",\n \"status\": \"enforced\",\n \"claim\": \"RN SDK's track() pipeline attaches a `sessionId` property to every event when the host has called `setSessionId(...)` — parity with the web SDK's session-anchored funnel queries. Pre-v1.4.0 the enrichment merged device + super + groups + caller but never carried sessionId, so cross-platform funnels on session anchors returned zero RN rows. The host owns session lifecycle (AppState + nav library); the SDK exposes setSessionId() / setSessionId(null) for the host to drive. Caller-supplied sessionId in properties still wins on conflict (matches the Phase 3.2 caller > super > device precedence chain).\",\n \"appliesTo\": [\n \"react-native\"\n ],\n \"codeRef\": [\n \"sdks/react-native/src/crossdeck.ts\"\n ],\n \"testRef\": [\n {\n \"file\": \"sdks/react-native/tests/session-id-enrichment.test.ts\",\n \"name\": \"track() events carry sessionId after setSessionId() is called\"\n },\n {\n \"file\": \"sdks/react-native/tests/session-id-enrichment.test.ts\",\n \"name\": \"track() events do NOT carry sessionId before setSessionId() is called\"\n },\n {\n \"file\": \"sdks/react-native/tests/session-id-enrichment.test.ts\",\n \"name\": \"setSessionId(null) clears the active session\"\n },\n {\n \"file\": \"sdks/react-native/tests/session-id-enrichment.test.ts\",\n \"name\": \"caller-supplied sessionId property overrides setSessionId() value (Phase 3.2 precedence)\"\n }\n ],\n \"registeredAt\": \"2026-05-26\",\n \"firstRegisteredIn\": \"bank-grade reconciliation v1.4.0 — phase 3.4\",\n \"bundledIn\": \"@cross-deck/react-native@1.5.3\"\n },\n {\n \"id\": \"sync-purchases-funnel-parity\",\n \"pillar\": \"analytics\",\n \"status\": \"enforced\",\n \"claim\": \"Manual syncPurchases() emits a `purchase.completed` analytics event on success across ALL SDKs (Web / Node / RN / Swift / Android). Pre-v1.4.0 only Swift/Android auto-track emitted it — Web/Node/RN manual calls + Swift/Android manual calls fired ZERO analytics. Schema mirrors the auto-track event name + rail/productId/subscriptionId so cross-platform funnels reconcile on every payment path. When the backend short-circuits via the v1.4.0 idempotency cache, the event also carries `idempotent_replay: true`.\",\n \"appliesTo\": [\n \"web\",\n \"node\",\n \"react-native\",\n \"swift\",\n \"android\"\n ],\n \"codeRef\": [\n \"sdks/web/src/crossdeck.ts\",\n \"sdks/node/src/crossdeck-server.ts\",\n \"sdks/react-native/src/crossdeck.ts\",\n \"sdks/swift/Sources/Crossdeck/Crossdeck.swift\",\n \"sdks/android/crossdeck/src/main/kotlin/com/crossdeck/Crossdeck.kt\"\n ],\n \"testRef\": [\n {\n \"file\": \"sdks/web/tests/sync-purchases-funnel.test.ts\",\n \"name\": \"emits purchase.completed after a successful sync\"\n },\n {\n \"file\": \"sdks/web/tests/sync-purchases-funnel.test.ts\",\n \"name\": \"carries idempotent_replay=true when backend replied from cache\"\n }\n ],\n \"registeredAt\": \"2026-05-26\",\n \"firstRegisteredIn\": \"bank-grade reconciliation v1.4.0 — phase 3.5\",\n \"bundledIn\": \"@cross-deck/react-native@1.5.3\"\n }\n]) as readonly Contract[];\n","/**\n * Public, typed accessor for the bank-grade behavioural contracts\n * this SDK ships. The full architecture — schema, distribution,\n * audit loop, pillar taxonomy — lives in `contracts/README.md`\n * at the monorepo root.\n *\n * Why a typed surface (vs. plain JSON access): contract IDs and\n * pillar names are part of Crossdeck's public commitment to\n * customers. Reading them through `CrossdeckContracts` means the\n * compiler catches drift the moment a contract is renamed or\n * retired. Tools that consume contracts at runtime (dashboards,\n * AI assistants, customer integration tests) get the exact same\n * shape every SDK ships, with no parsing layer to drift.\n *\n * --- BINARY STABILITY ---\n * `Contract` is treated as an evolving — but back-compat — wire\n * shape. Fields may be added in any minor release. Existing\n * fields will not be removed or repurposed except in a major\n * version bump, even if all known contracts stop using them.\n * Customers can rely on `id`, `pillar`, `status`, `appliesTo`,\n * `codeRef`, `testRef`, `registeredAt`, `firstRegisteredIn`,\n * and `bundledIn` being present on every contract in every\n * future minor/patch release of this SDK.\n */\n\nimport {\n BUNDLED_CONTRACTS,\n BUNDLED_IN,\n SDK_VERSION,\n} from \"./_contracts-bundled\";\n\nexport type ContractPillar =\n | \"revenue\"\n | \"entitlements\"\n | \"analytics\"\n | \"webhooks\"\n | \"errors\"\n | \"lifecycle\"\n | \"identity\";\n\nexport type ContractStatus = \"enforced\" | \"proposed\" | \"retired\";\n\nexport type ContractAppliesTo =\n | \"web\"\n | \"node\"\n | \"react-native\"\n | \"swift\"\n | \"android\"\n | \"backend\";\n\nexport interface ContractTestRef {\n readonly file: string;\n readonly name: string;\n}\n\nexport interface Contract {\n readonly id: string;\n readonly pillar: ContractPillar;\n readonly status: ContractStatus;\n readonly claim: string;\n readonly appliesTo: readonly ContractAppliesTo[];\n readonly codeRef: readonly string[];\n readonly testRef: readonly ContractTestRef[];\n readonly registeredAt: string;\n readonly firstRegisteredIn: string;\n readonly bundledIn: string;\n}\n\n/**\n * Typed entry point to the bank-grade contracts bundled with this\n * SDK release. Stable, side-effect-free, tree-shakeable.\n *\n * @example Audit at app boot\n * ```ts\n * import { CrossdeckContracts } from \"@cross-deck/react-native\";\n *\n * for (const c of CrossdeckContracts.all()) {\n * console.log(`[crossdeck] ${c.id} (${c.pillar})`);\n * }\n * ```\n */\nexport const CrossdeckContracts = {\n all(): readonly Contract[] {\n return BUNDLED_CONTRACTS.filter((c) => c.status === \"enforced\");\n },\n allIncludingHistorical(): readonly Contract[] {\n return BUNDLED_CONTRACTS;\n },\n byId(id: string): Contract | undefined {\n return BUNDLED_CONTRACTS.find((c) => c.id === id);\n },\n byPillar(pillar: ContractPillar): readonly Contract[] {\n return BUNDLED_CONTRACTS.filter(\n (c) => c.pillar === pillar && c.status === \"enforced\",\n );\n },\n withStatus(status: ContractStatus): readonly Contract[] {\n return BUNDLED_CONTRACTS.filter((c) => c.status === status);\n },\n sdkVersion: SDK_VERSION,\n bundledIn: BUNDLED_IN,\n\n /**\n * Resolve a failing test back to the contract it exercises.\n * Used by test-framework hooks to find the contract id of a\n * failed contract test so `reportContractFailure(...)` can stamp\n * the right `contract_id` on the emitted event.\n */\n findByTestName(name: string): Contract | undefined {\n return BUNDLED_CONTRACTS.find((c) =>\n c.testRef.some((ref) => ref.name === name),\n );\n },\n} as const;\n\n/**\n * Input to {@link Crossdeck.reportContractFailure}.\n *\n * SCHEMA-LOCK: this interface's field set is exhaustively named. No\n * free-form `extra: Record<string, unknown>` — the schema-lock\n * contract at\n * `contracts/diagnostics/contract-failed-payload-schema-lock.json`\n * forbids unbounded fields.\n */\nexport interface ContractFailureInput {\n contractId: string;\n /**\n * Short categorical-ish label — the SDK convention is to keep\n * this under 128 chars and stable across runs. Never an\n * end-user-supplied string.\n */\n failureReason: string;\n runContext: \"ci\" | \"dogfood\" | \"customer-app\";\n runId: string;\n testRef?: { file: string; name: string };\n /**\n * Optional coarse device class, e.g. \"ios-phone\", \"android-tablet\".\n * A categorical bucket, not a device identifier.\n */\n deviceClass?: string;\n}\n"],"mappings":"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","names":["SDK_VERSION","SDK_VERSION"]}