@cross-deck/web 1.8.0 → 1.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +12 -6
- package/dist/crossdeck.umd.min.js +2 -2
- package/dist/crossdeck.umd.min.js.map +1 -1
- package/dist/error-codes.json +1 -1
- package/dist/index.cjs +30 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +16 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.mjs +30 -1
- package/dist/index.mjs.map +1 -1
- package/dist/react.cjs +30 -1
- package/dist/react.cjs.map +1 -1
- package/dist/react.mjs +30 -1
- package/dist/react.mjs.map +1 -1
- package/dist/vue.cjs +30 -1
- package/dist/vue.cjs.map +1 -1
- package/dist/vue.mjs +30 -1
- package/dist/vue.mjs.map +1 -1
- package/package.json +2 -2
package/dist/vue.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/vue.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/auto-track.ts","../src/debug.ts","../src/event-validation.ts","../src/super-properties.ts","../src/internal-opt-out.ts","../src/web-vitals.ts","../src/consent.ts","../src/breadcrumbs.ts","../src/_diagnostic-telemetry.ts","../src/error-codes.ts","../src/_contract-verifiers.ts","../src/stack-parser.ts","../src/error-capture.ts","../src/crossdeck.ts"],"sourcesContent":["/**\n * @cross-deck/web/vue — Vue 3 composables for the Crossdeck SDK.\n *\n * Mirrors @cross-deck/web/react in spirit: ties the entitlement cache\n * to Vue's reactive system so components re-render the moment the\n * server-side cache populates.\n *\n * import { useEntitlement } from \"@cross-deck/web/vue\";\n * const isPro = useEntitlement(\"pro\"); // Ref<boolean>\n *\n * Why a separate subpackage: Vue is an optional peer dependency. The\n * core SDK has zero runtime deps; pulling Vue in unconditionally would\n * make non-Vue consumers pay for code they don't use. Same pattern as\n * `@cross-deck/web/react`.\n *\n * SSR safety: `onMounted` / `onScopeDispose` only run on the client.\n * The initial Ref value is the cache's current state (`false` pre-init),\n * so server output never claims a non-existent entitlement.\n */\n\nimport { ref, onMounted, onScopeDispose, type Ref } from \"vue\";\nimport { Crossdeck } from \"./crossdeck\";\n\n/**\n * Reactive entitlement check. Returns a `Ref<boolean>` that updates\n * automatically whenever the cache mutates.\n *\n * const isPro = useEntitlement(\"pro\");\n * // template: <span v-if=\"isPro\">Pro</span>\n */\nexport function useEntitlement(key: string): Ref<boolean> {\n const r = ref<boolean>(safeIsEntitled(key));\n\n onMounted(() => {\n r.value = safeIsEntitled(key);\n let unsubscribe: (() => void) | null = null;\n try {\n unsubscribe = Crossdeck.onEntitlementsChange(() => {\n r.value = safeIsEntitled(key);\n });\n } catch {\n // Pre-init — the SDK isn't started yet. The composable just\n // returns false until something mutates the cache via a\n // post-init call.\n }\n onScopeDispose(() => {\n if (unsubscribe) unsubscribe();\n });\n });\n\n return r;\n}\n\n/**\n * Reactive list of active entitlement keys. Updates on every cache\n * mutation. Useful for rendering a \"you have unlocked: ...\" block.\n */\nexport function useEntitlements(): Ref<readonly string[]> {\n const r = ref<readonly string[]>(safeListKeys());\n\n onMounted(() => {\n r.value = safeListKeys();\n let unsubscribe: (() => void) | null = null;\n try {\n unsubscribe = Crossdeck.onEntitlementsChange((entitlements) => {\n r.value = entitlements.filter((e) => e.isActive).map((e) => e.key);\n });\n } catch {\n // Pre-init.\n }\n onScopeDispose(() => {\n if (unsubscribe) unsubscribe();\n });\n });\n\n return r;\n}\n\nfunction safeIsEntitled(key: string): boolean {\n try {\n return Crossdeck.isEntitled(key);\n } catch {\n return false;\n }\n}\n\nfunction safeListKeys(): readonly string[] {\n try {\n return Crossdeck.listEntitlements()\n .filter((e) => e.isActive)\n .map((e) => e.key);\n } catch {\n return [];\n }\n}\n","/**\n * Stripe-style error wrapper for @cross-deck/web.\n *\n * Mirrors the wire shape returned by the v1 backend (see\n * backend/src/api/v1-errors.ts) so SDK consumers can `catch`\n * with consistent fields:\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 * }\n */\n\nexport type CrossdeckErrorType =\n | \"authentication_error\"\n | \"permission_error\"\n | \"invalid_request_error\"\n | \"rate_limit_error\"\n // version_error → HTTP 426: the SDK's wire format is too old. The queue\n // PARKS on it (holds events, resumes on upgrade), distinct from a drop.\n | \"version_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. The header\n * spec allows either delta-seconds or an HTTP-date; the parser below\n * normalises both to milliseconds. Consumers MUST honour this — the\n * server is telling you the safe rate.\n */\n retryAfterMs?: number;\n /**\n * Required SDK version floor — populated only on a `426 Upgrade Required`\n * / `sdk_version_unsupported` response. The queue surfaces it in the\n * \"update to >= X\" PARK message so the cure is exact.\n */\n minVersion?: string;\n /** SDK surface the rejection applies to (web/node/swift/…), on a 426. */\n surface?: string;\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 public readonly minVersion?: string;\n public readonly surface?: string;\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 this.minVersion = payload.minVersion;\n this.surface = payload.surface;\n // Restore prototype chain — needed when targeting ES5.\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(res: Response): 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 { error?: Partial<CrossdeckErrorPayload> & { request_id?: string } })?.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 // PARK metadata, present only on a 426 / sdk_version_unsupported body.\n minVersion: typeof envelope.minVersion === \"string\" ? envelope.minVersion : undefined,\n surface: typeof envelope.surface === \"string\" ? envelope.surface : undefined,\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 * Exported for unit testing.\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 // delta-seconds form (non-negative integer or decimal).\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 // HTTP-date form. Only attempt Date.parse if the value looks like a\n // date (has a comma, slash, or alphabetic character) — otherwise\n // garbage like \"-5\" or \"abc\" gets coerced by Date.parse into weird\n // values and we'd silently return 0.\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.8.0\";\nexport const SDK_NAME = \"@cross-deck/web\";\n","/**\n * HTTP transport for the SDK. Single fetch wrapper used by every endpoint\n * call. Adds the Bearer token and SDK version header, parses responses,\n * normalises errors to CrossdeckError.\n *\n * Uses platform-native fetch (browser + Node 18+). No axios, no isomorphic-\n * fetch shim, no transitive deps.\n */\n\nimport { CrossdeckError, crossdeckErrorFromResponse } from \"./errors\";\n// Single source of truth — `_version.ts` is generated from\n// package.json by `scripts/sync-sdk-versions.mjs`. A plain TypeScript\n// re-export here means the runtime `Crossdeck-Sdk-Version` header\n// always matches the published bundle, with zero Node-ESM JSON-import\n// gotchas (Node requires `with { type: \"json\" }` to load JSON as ESM\n// from a .mjs file — that bit dist-loading on 1.3.0 RC).\n//\n// Pre-fix this was a hardcoded literal that drifted from package.json:\n// the published 1.2.0 web bundle reported `@cross-deck/web@1.1.0` on\n// the wire because nobody bumped the constant. The generated\n// `_version.ts` closes that loop permanently — `--check` mode of the\n// sync script fails CI if anything goes out of sync.\nimport { SDK_NAME, SDK_VERSION } from \"./_version\";\nexport { SDK_NAME, SDK_VERSION };\n\nexport const DEFAULT_BASE_URL = \"https://api.cross-deck.com/v1\";\n\nexport interface HttpClientConfig {\n publicKey: string;\n baseUrl: string;\n sdkVersion: string;\n /**\n * Localhost auto-detection short-circuit. When true, every request\n * resolves to a fabricated 2xx-shaped response — no network call\n * goes out. Set by Crossdeck.init() when the SDK boots on a local\n * dev hostname. Confidence-first design: we never let a developer's\n * laptop pollute their live analytics by accident.\n */\n localDevMode?: boolean;\n /**\n * Default request timeout in ms. Per-call `options.timeoutMs` overrides.\n * Caller's `options.timeoutMs: 0` disables the timeout entirely (useful\n * for tests that intentionally hang).\n *\n * Stripe-grade default: 15s. Long enough that a slow-3G mobile keeps\n * the request alive; short enough that a captive portal or a hung\n * connection doesn't sit forever. Without this, fetch() inherits the\n * browser's default (which on Chrome can be 5+ minutes) and a single\n * bad network can lock up the entire event queue.\n */\n timeoutMs?: number;\n /**\n * Contract verifier hook — fired AFTER `crossdeckErrorFromResponse`\n * has built the typed error from a non-OK wire envelope, just BEFORE\n * the throw. Lets the SDK's verifier layer observe the parsed envelope\n * shape (type / code / request_id / http status) and assert the\n * `error-envelope-shape` contract on every real failure, not just at\n * boot. Synchronous + best-effort: any throw from the hook is swallowed\n * so a buggy verifier can NEVER replace the customer's real error with\n * a verifier-internal one. See sdks/web/src/_contract-verifiers.ts\n * runOnErrorParse for the consumer end of this wire.\n */\n onErrorParsed?: (err: CrossdeckError) => void;\n}\n\nexport const DEFAULT_TIMEOUT_MS = 15_000;\n\nexport interface HttpRequestOptions {\n body?: unknown;\n query?: Record<string, string | undefined>;\n /**\n * Mark the request as `keepalive` so the browser keeps it in flight\n * even after the page begins unloading. Critical for terminal flushes\n * fired from `pagehide` / `visibilitychange` — without this, the queued\n * page.viewed / session.ended events get cancelled the moment the user\n * navigates away.\n *\n * Spec: https://developer.mozilla.org/docs/Web/API/Fetch_API/Using_Fetch#sending_a_request_with_keepalive\n * Body cap: 64 KB total across all keepalive requests in flight.\n */\n keepalive?: boolean;\n /**\n * Per-request timeout override (ms). Defaults to the client's\n * `timeoutMs` (15s). Pass 0 to disable the timeout entirely — only\n * sensible for tests or long-poll endpoints we don't have today.\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 * Callers can pass it for ad-hoc retried POSTs too.\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 // Localhost short-circuit. Every request returns a synthetic\n // success shape so the SDK methods that depend on a response\n // (heartbeat, alias, getEntitlements) don't break — but no\n // packet leaves the browser. The shape is path-aware so common\n // callers get sensible empties.\n if (this.config.localDevMode) {\n return synthesizeLocalDevResponse<T>(path);\n }\n\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 if (options.idempotencyKey) {\n // Stripe pattern: same key on retries → server can short-circuit\n // duplicate work without inspecting the body.\n headers[\"Idempotency-Key\"] = options.idempotencyKey;\n }\n let bodyInit: BodyInit | 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 portal,\n // satellite link, DNS hang) doesn't lock the queue forever. Per-call\n // `timeoutMs: 0` disables, otherwise fall back to client default\n // (15s). The controller is scoped to this request only — clearing\n // the timer in finally prevents a stale abort from firing after we\n // already got a response. `AbortSignal.timeout(ms)` would be cleaner\n // but isn't supported in Safari < 16.4; this hand-rolled pattern is\n // portable to every browser fetch() supports.\n const effectiveTimeout = 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 keepalive: options.keepalive === true,\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 const parsed = await crossdeckErrorFromResponse(response);\n // Fire the contract-verifier hook BEFORE the throw so the\n // verifier sees every real wire envelope, not a sanitised\n // replay. Wrapped in try/catch so a buggy verifier can never\n // replace the customer's authentic error with one of our own.\n // The verifier layer is observability, not control flow.\n if (this.config.onErrorParsed) {\n try { this.config.onErrorParsed(parsed); } catch { /* swallow */ }\n }\n throw parsed;\n }\n\n // 204 No Content / OPTIONS-like — return undefined cast as T (callers\n // that 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 (err) {\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 /**\n * Whether this client is in localhost dev-mode short-circuit. Used\n * by other SDK pieces (event-queue) to skip network-bound work\n * entirely rather than going through synthesizeLocalDevResponse.\n */\n get isLocalDevMode(): boolean {\n return this.config.localDevMode === true;\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 * Build a synthetic response for localhost dev mode. Path-aware so\n * heartbeat / alias / entitlements / events callers each get a\n * sensible empty shape that won't crash downstream code.\n *\n * Heartbeat returns ok=true so the SDK considers itself \"started.\"\n * Alias returns a stub `cdcust_local_*` so identify() resolves to a\n * stable ID across calls (we mint a per-tab one via crypto.randomUUID\n * once and remember it). Entitlements returns an empty list — the\n * dev should grant entitlements in their own dashboard before relying\n * on isEntitled() locally.\n */\nlet cachedLocalCdcust: string | null = null;\nfunction synthesizeLocalDevResponse<T>(path: string): T {\n if (path.startsWith(\"/sdk/heartbeat\")) {\n return {\n object: \"heartbeat\",\n ok: true,\n projectId: \"proj_local_dev\",\n appId: \"app_local_dev\",\n platform: \"web\",\n env: \"sandbox\",\n serverTime: Date.now(),\n } as unknown as T;\n }\n if (path.startsWith(\"/identity/alias\")) {\n if (!cachedLocalCdcust) {\n const tail =\n typeof crypto !== \"undefined\" && \"randomUUID\" in crypto\n ? crypto.randomUUID().replace(/-/g, \"\").slice(0, 16)\n : Math.random().toString(36).slice(2, 18);\n cachedLocalCdcust = `cdcust_local_${tail}`;\n }\n return {\n object: \"alias_result\",\n crossdeckCustomerId: cachedLocalCdcust,\n linked: [],\n mergePending: false,\n env: \"sandbox\",\n } as unknown as T;\n }\n if (path.startsWith(\"/entitlements\")) {\n return {\n object: \"list\",\n data: [],\n crossdeckCustomerId: cachedLocalCdcust ?? \"\",\n env: \"sandbox\",\n } as unknown as T;\n }\n if (path.startsWith(\"/events\")) {\n return {\n object: \"list\",\n received: 0,\n env: \"sandbox\",\n } as unknown as T;\n }\n // Generic fallback — empty success shape.\n return {} as T;\n}\n","/**\n * Identity persistence for the SDK.\n *\n * Two values are tracked:\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 boots can read\n * entitlements directly without an alias call.\n *\n * ----- Bank-grade identity continuity (v0.6.0+) -----\n *\n * In a browser context, the SDK reads/writes BOTH localStorage and a\n * 1st-party cookie. This is the redundancy that keeps \"10k unique\n * visitors\" actually meaning 10k humans even when one store is wiped:\n *\n * - Read on boot: take whichever value exists. If both differ\n * (impossible in normal operation — would mean one store was\n * restored from a stale backup), localStorage wins because it's\n * the higher-fidelity store and what we wrote most recently.\n * - Write on every change: write to BOTH. Future clears of either\n * don't lose identity continuity.\n * - Reset: clear BOTH stores so logout actually wipes the device.\n *\n * Outside browsers (Node, Workers) the redundant cookie store is\n * absent and behaviour collapses to the single-store original — no\n * code path changes for non-web SDK consumers.\n */\n\nimport type { KeyValueStorage } from \"./types\";\n\nconst KEY_ANON = \"anon_id\";\nconst KEY_CDCUST = \"cdcust_id\";\n\nexport interface IdentityState {\n anonymousId: string;\n crossdeckCustomerId: string | null;\n}\n\nexport class IdentityStore {\n private state: IdentityState;\n /**\n * Optional secondary store written/read alongside the primary. When\n * present, every setItem fans out to both stores and getItem prefers\n * primary but falls back to secondary if primary returned null. This\n * is what gives us localStorage + cookie redundancy in browsers.\n */\n private readonly secondary: KeyValueStorage | null;\n\n constructor(\n private readonly primary: KeyValueStorage,\n private readonly prefix: string,\n secondary?: KeyValueStorage,\n ) {\n this.secondary = secondary ?? null;\n const anonFromPrimary = primary.getItem(prefix + KEY_ANON);\n const cdcustFromPrimary = primary.getItem(prefix + KEY_CDCUST);\n const anonFromSecondary = this.secondary?.getItem(prefix + KEY_ANON) ?? null;\n const cdcustFromSecondary = this.secondary?.getItem(prefix + KEY_CDCUST) ?? null;\n\n // Prefer the primary store's value; fall back to secondary on miss.\n // The \"both populated, values differ\" case never happens in normal\n // operation — every write goes to both stores in lockstep — so we\n // don't bother with conflict resolution beyond \"trust primary.\"\n const anon = anonFromPrimary ?? anonFromSecondary;\n const cdcust = cdcustFromPrimary ?? cdcustFromSecondary;\n\n this.state = {\n anonymousId: anon ?? this.mintAnonymousId(),\n crossdeckCustomerId: cdcust,\n };\n\n // If we just minted a new anonymousId, write it to both stores so\n // either store can answer \"what's our id\" on subsequent boots.\n // If we read it from one store but not the other, write it to the\n // missing store too — that's the resync that catches a recovering\n // ITP-cleared cookie or a freshly-minted private-tab localStorage.\n if (!anonFromPrimary || !anonFromSecondary) {\n this.writeBoth(prefix + KEY_ANON, this.state.anonymousId);\n }\n if (cdcust && (!cdcustFromPrimary || !cdcustFromSecondary)) {\n this.writeBoth(prefix + KEY_CDCUST, cdcust);\n }\n }\n\n /** Return the persisted anonymous device ID (always set). */\n get anonymousId(): string {\n return this.state.anonymousId;\n }\n\n /** Return the resolved crossdeckCustomerId once we have one, else null. */\n get crossdeckCustomerId(): string | null {\n return this.state.crossdeckCustomerId;\n }\n\n /** Persist a newly-resolved Crossdeck customer ID. */\n setCrossdeckCustomerId(value: string): void {\n this.state.crossdeckCustomerId = value;\n this.writeBoth(this.prefix + KEY_CDCUST, value);\n }\n\n /**\n * Wipe persisted identity. Called by reset() — used when an end-user\n * logs out. After reset the SDK mints a new anonymousId so the next\n * pre-login session is a fresh customer in the identity graph.\n */\n reset(): void {\n this.deleteBoth(this.prefix + KEY_ANON);\n this.deleteBoth(this.prefix + KEY_CDCUST);\n this.state = {\n anonymousId: this.mintAnonymousId(),\n crossdeckCustomerId: null,\n };\n this.writeBoth(this.prefix + KEY_ANON, this.state.anonymousId);\n }\n\n /**\n * Generate an anonymousId. Crockford-ish base32 timestamp + random\n * suffix. Same shape Stripe / Segment / others use — sortable, log-\n * friendly, no PII.\n */\n private mintAnonymousId(): string {\n const ts = Date.now().toString(36);\n const rand = randomChars(10);\n return `anon_${ts}${rand}`;\n }\n\n private writeBoth(key: string, value: string): void {\n try { this.primary.setItem(key, value); } catch { /* see storage.ts probe */ }\n if (this.secondary) {\n try { this.secondary.setItem(key, value); } catch { /* swallow per-store */ }\n }\n }\n\n private deleteBoth(key: string): void {\n try { this.primary.removeItem(key); } catch { /* swallow */ }\n if (this.secondary) {\n try { this.secondary.removeItem(key); } catch { /* swallow */ }\n }\n }\n}\n\n/**\n * Generate a cryptographically-random short string. Uses\n * crypto.getRandomValues when available (browser + Node 18+ via webcrypto),\n * else falls back to Math.random — that fallback is safe here because\n * anonymousId entropy doesn't need to resist offline brute force; it\n * needs to be unique-with-overwhelming-probability across one device's\n * lifetime.\n *\n * Exported for unit testing (alphabet round-trip).\n */\nexport function randomChars(count: number): string {\n const alphabet = \"0123456789abcdefghijklmnopqrstuvwxyz\";\n const out: string[] = [];\n const cryptoApi = (globalThis as { crypto?: { getRandomValues?: (a: Uint8Array) => Uint8Array } }).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 * Used to derive a per-user storage suffix for the entitlement\n * cache so each developerUserId's data lives under a physically\n * separate localStorage 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 * Why not SubtleCrypto: SubtleCrypto.digest is async, and the\n * entitlement-cache hot path (setFromList, clear, hydrate) is\n * synchronous. An async hash would force every cache operation\n * to become Promise<>-returning, cascading through identify(),\n * getEntitlements(), and the React `useEntitlement` hook. A\n * pure-JS sync impl keeps the API shape unchanged.\n *\n * Why not a smaller hash (FNV-1a etc): SHA-256 is the contract\n * documented in the SDK README. A non-crypto hash would\n * collision-resist for normal user populations but the public\n * promise is SHA-256.\n *\n * Implementation notes:\n * - Input: UTF-8 bytes from `String → encodeURIComponent`-style\n * percent-encoding. Pure ASCII is identity-mapped; multi-byte\n * runes are expanded to UTF-8 octets.\n * - Output: 64-character lowercase hex string.\n * - Independent reference vectors checked: SHA256(\"\") =\n * e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855,\n * SHA256(\"abc\") =\n * ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad.\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 // TextEncoder is universal in browsers + Node 11+ + RN (Hermes/JSC).\n // Constructed lazily so the module load doesn't depend on the global.\n if (typeof TextEncoder !== \"undefined\") {\n return new TextEncoder().encode(input);\n }\n // Hand-rolled UTF-8 encode for environments without TextEncoder.\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\n/**\n * Compute SHA-256 of `input` as a 64-character lowercase hex\n * string. Synchronous; safe to call on the hot path.\n */\nexport function sha256Hex(input: string): string {\n const bytes = utf8Bytes(input);\n const bitLength = bytes.length * 8;\n // Pad: append 0x80, then zeros, then 64-bit big-endian length to\n // reach a multiple of 512 bits (64 bytes).\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 // Length in bits as 64-bit big-endian. JS bitwise ops are 32-bit,\n // so split high / low halves.\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 * (localStorage, via the SDK's storage adapter).\n * - On SDK boot the cache hydrates from storage synchronously, so\n * isEntitled() answers correctly from the very first call — there\n * is no cold-start window where a returning Pro customer reads as\n * free.\n * - When the server is unreachable, the SDK keeps serving the last\n * entitlements it successfully fetched. A failed refresh never\n * reaches setFromList(), so it cannot clear the cache; only a\n * SUCCESSFUL fetch replaces it. An outage can never fail a paying\n * customer down to free.\n * - Staleness alone never returns false. Each entitlement is honoured\n * against its OWN validUntil instead — a time-based trial expiry\n * still applies even mid-partition, a still-valid Pro entitlement\n * rides the outage out.\n * - Staleness is VISIBLE, not silent. validUntil covers time-based\n * expiry; it does NOT cover an event-based revoke (chargeback,\n * refund, fraud) — that has no validUntil, so the cache would keep\n * serving a revoked customer through an outage. Serving them is the\n * right trade (don't lock real payers out), but unbounded-and-\n * invisible is the bug. So: once a refresh ATTEMPT fails (or the\n * data ages past staleAfterMs) the cache is marked stale —\n * isStale / freshness are surfaced in diagnostics(). It keeps\n * serving last-known-good; the staleness is just no longer hidden.\n *\n * The cache is wiped only on reset() (logout) and on an identity switch\n * — never by a TTL.\n *\n * Reactive listener API\n * ---------------------\n * `subscribe(listener)` registers a callback fired every time the cache\n * mutates (setFromList or clear) — the foundation for the\n * `useEntitlement` React hook and other framework bindings. Semantics:\n * - Fired AFTER the mutation, so the listener sees fresh state.\n * - Fire-and-forget: a throwing listener is swallowed (and counted)\n * so a buggy consumer can't crash the SDK or other listeners.\n * - Unsubscribe is idempotent.\n * - Listeners are NOT fired on subscribe — a caller that wants the\n * initial state reads isEntitled() / list() synchronously, which\n * work from boot thanks to hydration above.\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. Stable\n * across launches so an anonymous session's cache survives a reload\n * — but physically separate from any identified user's cache. */\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 localStorage. */\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 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. When omitted (tests) or\n * a MemoryStorage (strict-consent / no-persistence\n * mode) the cache is session-only — durability is\n * simply absent, never wrong.\n * @param storageKeyPrefix Prefix used to derive per-user storage keys\n * (`<prefix>:<sha256(userId)>`). Default\n * `crossdeck:entitlements`. The trailing user\n * suffix is filled at identify() / reset()\n * 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 this.hydrate();\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. The\n * raw userId never lands in the storage key — protects against\n * accidentally leaking email-style identifiers through DevTools\n * inspection. Pass `null` to switch back to the anonymous slot. */\n static suffixForUserId(userId: string | null): string {\n if (userId == null || userId === \"\") return ANON_SUFFIX;\n return sha256Hex(userId);\n }\n\n /**\n * Switch the cache to a different user's storage slot. Bank-grade\n * three-layer isolation:\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 invoke this BEFORE the next\n * setFromList() so the write lands under the right key.\n */\n setUserKey(userId: string | null): void {\n const nextSuffix = EntitlementCache.suffixForUserId(userId);\n if (nextSuffix === this.currentSuffix) {\n // Same user (or repeated anonymous) — still unconditionally\n // wipe in-memory cache to satisfy the founder's \"unconditional\n // clear on identify\" contract.\n this.all = [];\n this.lastUpdated = 0;\n this.lastRefreshFailedAt = 0;\n this.notify();\n // Re-hydrate from the same slot so a fresh boot's\n // last-known-good is honoured.\n this.hydrate();\n return;\n }\n this.currentSuffix = nextSuffix;\n // New slot: wipe in-memory + rehydrate from new slot.\n this.all = [];\n this.lastUpdated = 0;\n this.lastRefreshFailedAt = 0;\n this.hydrate();\n this.notify();\n }\n\n /**\n * Sync read — true iff the entitlement is currently granting access.\n *\n * Served from last-known-good: a stale cache (server unreachable since\n * the last successful fetch) still answers true for a still-valid\n * entitlement. The ONLY thing that turns it false is the entitlement's\n * own expiry (validUntil) — never 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 data.\n *\n * True when the most recent refresh ATTEMPT failed (Crossdeck\n * unreachable since the last success — the outage case, distinct from\n * a benign idle tab that simply hasn't re-fetched), OR when\n * last-known-good has aged past staleAfterMs.\n *\n * isStale never changes what isEntitled() returns — the cache still\n * serves last-known-good. It exists so the staleness is observable\n * (diagnostics()) instead of an unbounded silent window where a\n * revoked customer holds access with nobody able 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 / a\n * transient error). The SDK's getEntitlements() calls this in its\n * catch path. It does NOT touch the cached entitlements — last-known-\n * good keeps serving — it only flips isStale so the staleness shows\n * 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 to\n * device storage so it survives a reload / app restart.\n *\n * Called ONLY after a successful server read — a failed fetch throws\n * before it reaches here, so last-known-good is preserved through an\n * 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 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 without affecting other\n * persisted slots. The full-logout path is [[clearAll]].\n */\n clear(): void {\n this.all = [];\n this.lastUpdated = 0;\n this.lastRefreshFailedAt = 0;\n if (this.storage) {\n try {\n this.storage.removeItem(this.storageKey);\n } catch {\n // Private mode / quota — best-effort, same posture as storage.ts.\n }\n }\n this.removeSuffixFromIndex(this.currentSuffix);\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 * After clearAll(), the cache is back to anonymous + empty.\n */\n clearAll(): void {\n this.all = [];\n this.lastUpdated = 0;\n this.lastRefreshFailedAt = 0;\n this.currentSuffix = ANON_SUFFIX;\n if (this.storage) {\n const suffixes = this.readIndex();\n for (const suffix of suffixes) {\n try {\n this.storage.removeItem(`${this.storageKeyPrefix}:${suffix}`);\n } catch {\n // best-effort\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 try {\n this.storage.removeItem(`${this.storageKeyPrefix}:${ANON_SUFFIX}`);\n } catch {\n // best-effort\n }\n try {\n this.storage.removeItem(this.indexKey);\n } catch {\n // best-effort\n }\n }\n this.notify();\n }\n\n /**\n * Subscribe to cache mutations. Returns an idempotent unsubscribe fn.\n * The listener fires AFTER setFromList() or clear() with the current\n * snapshot. Used by `@cross-deck/web/react`'s `useEntitlement` hook.\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 /**\n * Load last-known-good from device storage. Runs once in the\n * constructor, synchronously, so isEntitled() is correct from boot.\n * Any corrupt / unparseable blob degrades silently to an empty cache —\n * boot must never throw.\n */\n private hydrate(): void {\n if (!this.storage) return;\n try {\n const raw = this.storage.getItem(this.storageKey);\n if (!raw) return;\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 } catch {\n // Corrupt / unparseable blob → start empty. Never throw on boot.\n }\n }\n\n /** Write last-known-good to device storage. Best-effort. */\n private persist(): void {\n if (!this.storage) return;\n try {\n const blob: PersistedCache = {\n v: 1,\n entitlements: this.all,\n lastUpdated: this.lastUpdated,\n };\n this.storage.setItem(this.storageKey, JSON.stringify(blob));\n } catch {\n // Quota exceeded / private mode — the in-memory cache still works\n // for this session; we just lose cross-reload durability.\n }\n }\n\n /** Read the index of all per-user suffixes the SDK has written. */\n private readIndex(): string[] {\n if (!this.storage) return [];\n try {\n const raw = 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 recordSuffixInIndex(suffix: string): void {\n if (!this.storage) return;\n const existing = this.readIndex();\n if (existing.includes(suffix)) return;\n existing.push(suffix);\n try {\n this.storage.setItem(this.indexKey, JSON.stringify(existing));\n } catch {\n // best-effort\n }\n }\n\n /** Remove a suffix from the persisted index. No-op if absent. */\n private removeSuffixFromIndex(suffix: string): void {\n if (!this.storage) return;\n const existing = this.readIndex();\n const next = existing.filter((s) => s !== suffix);\n if (next.length === existing.length) return;\n try {\n if (next.length === 0) {\n this.storage.removeItem(this.indexKey);\n } else {\n this.storage.setItem(this.indexKey, JSON.stringify(next));\n }\n } catch {\n // best-effort\n }\n }\n\n private notify(): void {\n if (this.listeners.size === 0) return;\n const snapshot = this.all.slice();\n // Iterate a snapshot of the listener set so a listener that\n // unsubscribes itself (or registers a new one) during dispatch\n // doesn't break the iteration.\n const listenersSnapshot = [...this.listeners];\n for (const listener of listenersSnapshot) {\n try {\n listener(snapshot);\n } catch {\n // Swallow listener errors — a buggy consumer shouldn't break the\n // SDK or other listeners. Counted for diagnostics().\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. Pre-v1.4.0 each\n * call minted a fresh random UUID — two retries of the same\n * purchase (network blip, app crash mid-flight, deliberate caller\n * retry) got DIFFERENT keys, so server-side idempotency could not\n * collapse them. The bank-grade contract every Stripe-grade API\n * ships: same input → same key → same response.\n *\n * Algorithm:\n * 1. Extract a stable identifier from the request body:\n * - Apple: the signed JWS string (uniquely identifies the\n * transaction by Apple's signature).\n * - Google: the purchaseToken.\n * 2. SHA-256 the identifier.\n * 3. Format the first 32 hex chars of the digest as a UUID-shaped\n * string (8-4-4-4-12). Backend treats the key as opaque so\n * RFC 4122 version/variant bits are unnecessary — what matters\n * is determinism.\n *\n * The resulting key is identical across retries of the same\n * transaction, so the backend's idempotency cache short-circuits\n * with `idempotent_replay: true` in the response.\n */\n\nimport { sha256Hex } from \"./hash\";\n\nexport interface PurchaseSyncIdentity {\n rail: \"apple\" | \"google\" | \"stripe\" | string;\n signedTransactionInfo?: string;\n purchaseToken?: string;\n}\n\n/**\n * Format a hex string as `8-4-4-4-12` UUID shape using its first\n * 32 hex chars. Used for the idempotency-key derivation — the\n * shape matches what backend logs / dashboards already pattern-\n * match against; treating it as a UUID at the wire makes\n * inspection familiar even though it isn't RFC 4122 versioned.\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\n/**\n * Deterministic Idempotency-Key for a purchase sync. Returns the\n * canonical UUID-shaped string the SDK sends as the\n * `Idempotency-Key` header.\n *\n * Same input → same key → backend returns the cached response\n * with `idempotent_replay: true` instead of double-processing.\n *\n * Throws on bodies that have no stable identifier — never silently\n * fall back to a fresh random key, since that would defeat the\n * very contract this helper exists to enforce.\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 // Namespace the digest so the same JWS used by /purchases/sync\n // and a hypothetical future /purchases/verify produce DIFFERENT\n // keys — prevents cross-endpoint idempotency collisions.\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 * After a failed flush, the queue must wait some time before trying\n * again — otherwise a flapping backend causes a hot loop, and a 429\n * \"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 at\n * the same instant. Spread the storm.\n * - 429 / 503 `Retry-After`: ALWAYS honour the server-supplied delay\n * when it's larger than our computed backoff. The server knows its\n * own capacity better than we do; ignoring it is what gets your IP\n * blocked.\n * - Reset on success.\n *\n * The policy is a pure object — no state, no timers. The EventQueue\n * owns the timer, the policy owns the math.\n *\n * Default values match Stripe-JS-style retry windows:\n * - baseMs: 1000 (first retry ~1s out)\n * - maxMs: 60000 (never wait longer than 60s)\n * - factor: 2 (1s, 2s, 4s, 8s, 16s, 32s, 60s, 60s, …)\n *\n * After `maxConsecutiveFailures` (default 8) without a success, the\n * caller is expected to surface that as a `lastError` for the\n * developer to see in diagnostics. We never stop retrying — events\n * matter and a transient outage can take hours — but we report it\n * clearly so the dev knows their data is queued, not lost.\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\n/**\n * Compute the next retry delay (ms) given the consecutive-failure\n * count and an optional server-supplied Retry-After (ms).\n *\n * computeNextDelay(0, undefined) → ~500ms (jittered 0-1000)\n * computeNextDelay(3, undefined) → ~4s (jittered 0-8000)\n * computeNextDelay(0, 30_000) → 30s (server wins)\n * computeNextDelay(8, undefined) → 60s (capped)\n *\n * Pure function — exported for testing. Real callers should go through\n * `RetryPolicy.nextDelay` so option defaults stay co-located.\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\n // Cap attempts so 2^attempts doesn't overflow into Infinity.\n const safeAttempts = Math.min(attempts, 30);\n const ceiling = Math.min(max, base * Math.pow(factor, safeAttempts));\n // Full jitter: random across [0, ceiling]. Caller can substitute a\n // deterministic RNG for testing.\n const jittered = ceiling * random();\n // Honour server's Retry-After when bigger than our window — the\n // server's the authority on its own pressure. Pre-fix this was\n // clamped to `maxMs` (60s default), which meant a `Retry-After: 120`\n // got truncated to 60s and we hammered an already-rate-limited\n // endpoint twice as fast as it asked. Honour the server delay\n // as-is, but cap at 24h as a final sanity guard against an absurd\n // value (server bug / clock skew on an HTTP-date form) that would\n // otherwise wedge the queue for years. RFC 7231 doesn't require\n // honouring beyond that.\n if (retryAfterMs !== undefined) {\n const ABSOLUTE_MAX_MS = 24 * 60 * 60 * 1000; // 24h\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 /** How many consecutive failures since the last success. */\n get consecutiveFailures(): number {\n return this.attempts;\n }\n\n /** Whether we've crossed the failuresBeforeWarn threshold. */\n get isWarning(): boolean {\n return this.attempts >= (this.options.failuresBeforeWarn ?? DEFAULT_WARN);\n }\n\n /** Schedule-time delay for the NEXT retry. Increments the counter. */\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 /** Mark a successful flush — reset the counter. */\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 clicks, screen\n * views) and shouldn't block the UI on a network round-trip. Events go\n * into a local buffer, flushed in bursts.\n *\n * Flush triggers:\n * - Buffer reaches batchSize (default 20) → flush immediately.\n * - intervalMs of inactivity (default 1500ms) → flush idle batch.\n * - flush() called explicitly (e.g. before page unload).\n *\n * Wave 1 hardening (v0.8.0+, the \"bank-grade plumbing\" pass):\n *\n * - Exponential backoff with full jitter on flush failures. Respects\n * server `Retry-After` headers (parsed onto CrossdeckError by the\n * HTTP layer). Replaces the prior policy of \"retry on the next\n * idle window\" which hot-looped against a flapping endpoint.\n *\n * - Durable persistence. Events are written through to a\n * `PersistentEventStore` (localStorage by default) so a hard\n * browser crash / power loss / keepalive cap exceedance doesn't\n * drop data. The next SDK boot replays the persisted queue.\n *\n * - Per-batch `Idempotency-Key`. Same key is reused across retries\n * of the SAME batch so the server can short-circuit duplicate work\n * without inspecting bodies. The backend ALSO dedupes individual\n * events via CH ReplacingMergeTree on `eventId`, so this is belt-\n * and-suspenders.\n *\n * - Property validation runs upstream in crossdeck.ts:track() — by\n * the time an event lands in this queue, it's known to be safe\n * to JSON.stringify.\n *\n * On a permanent network outage we keep retrying with bounded backoff;\n * we never drop events because of network failures alone. The only\n * drop path is the hard buffer cap (1000 events): once exceeded we\n * evict the OLDEST events and increment `dropped` so the developer\n * can see the loss in `diagnostics()`.\n */\n\nimport type { HttpClient } from \"./http\";\nimport type { EventProperties, IngestResponse } from \"./types\";\nimport type { CrossdeckError } from \"./errors\";\nimport { RetryPolicy, type RetryPolicyOptions } from \"./retry-policy\";\nimport { 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 /**\n * Event Envelope v1 §3 — per-session monotonic sequence number.\n * Assigned synchronously at track() time from the session-scoped counter\n * (AutoTracker.nextSeq()), reset to 0 at session.started. The\n * deterministic tiebreak for events sharing a timestamp. Persisted on\n * disk alongside the event so a delayed flush across a\n * background/foreground cycle keeps its original seq.\n */\n seq: number;\n /**\n * Event Envelope v1 §4 — standardized device/platform context,\n * promoted OUT of `properties` into one named object. Keys: os,\n * osVersion, appVersion, sdkName, sdkVersion, locale, timezone,\n * browser, browserVersion. App-supplied properties stay in `properties`.\n */\n context: Record<string, string>;\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 POST.\n * It's a function (not a value) so a future call to setDebugMode or a\n * config swap can update the envelope without re-instantiating the\n * queue.\n */\n envelope: () => BatchEnvelope;\n /** Schedule a function to run after `ms` ms. Default: setTimeout. Override for tests. */\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 construction, persisted events\n * are loaded back into the buffer. Omitting this is fine for tests\n * and Node consumers — the queue falls back to in-memory only.\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 queue\n * 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 a\n * permanent 4xx (anything except 408 Request Timeout / 429 Too Many\n * Requests). The host SDK should surface this loudly — pre-fix the\n * queue retried 4xx errors forever with the same Idempotency-Key,\n * silently growing the backlog while the customer thought events\n * were landing. 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 * Fired when the server PARKS this SDK with HTTP 426 Upgrade Required\n * (code `sdk_version_unsupported`) — the SDK's wire dialect is too old\n * for the server to accept. This is the third outcome, distinct from\n * retry (transient) and drop (invalid): the data is GOOD, only the\n * format is stale. The queue holds the events durably, stops flushing\n * (the same payload will fail until the app upgrades the SDK), and on\n * the next boot AFTER upgrade the rehydrated queue delivers them — so\n * \"paused, not lost — held on-device, resumes on upgrade\" is literally\n * true. The host SDK surfaces this to the developer's own console once\n * and to the dashboard (via the heartbeat) so both channels carry the\n * same cure. `minVersion` is the server's required floor when supplied.\n */\n onParked?: (info: { minVersion?: string; surface?: string }) => 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 permanent\n * failure). On a retry-driven re-flush we re-use this slot alongside\n * `pendingBatchId` so the Stripe-style Idempotency-Key is preserved\n * across retries of the SAME logical batch.\n *\n * Pre-fix the splice cleared the buffer AND we immediately\n * `persistent.save(empty)` BEFORE awaiting the network call — a\n * crash mid-flight wiped the persisted blob and the batch was lost\n * with no replay on next boot. Now the persisted blob always carries\n * `[...pendingBatch, ...buffer]` so the in-flight events survive\n * until the server confirms them.\n *\n * Pre-fix every retry attempt also minted a NEW batchId via\n * `splice + mintBatchId`, defeating the backend's\n * `Idempotency-Key` dedup. Reuse via this slot brings web in\n * lockstep with node (which already had the field).\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 /**\n * PARK state (HTTP 426 / `sdk_version_unsupported`). Once parked, the\n * queue stops flushing — the server has told us our wire dialect is too\n * old, and retrying the same payload only burns the user's battery and\n * bandwidth and drips pointless rejects into the server logs until the\n * app ships an upgraded SDK. The held events stay durable (persisted)\n * and are delivered by the next boot's rehydrate, post-upgrade. Parked\n * is per-instance: a fresh boot (the upgraded build) starts unparked.\n */\n private parked = false;\n /** One developer-facing console warning per instance — never per-event spam. */\n private parkWarned = false;\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 // Rehydrate any events left over from a prior session (crash, hard\n // close, keepalive cap exceeded). Eventid-based dedup at the server\n // means re-sending an event that may have already landed is safe.\n // Rehydrated events land in `buffer` — the \"was in-flight\" axis\n // doesn't survive a crash, so they're treated like fresh enqueues.\n if (this.persistent) {\n const restored = this.persistent.load();\n if (restored.length > 0) {\n // Apply the same hard cap on rehydrate — defends against a\n // malicious / corrupted blob with a million entries.\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 }\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). On a retryable failure the batch\n * stays in `pendingBatch` for the next scheduled retry — the SAME\n * batch with the SAME `Idempotency-Key` is re-sent (Stripe pattern).\n *\n * Three terminal states from one call:\n * - 2xx success: pendingBatch cleared, persisted state collapses to\n * just `buffer` (any new events that arrived during in-flight).\n * - 4xx permanent (except 408/429): pendingBatch DROPPED, `dropped`\n * counter increments, a `permanent_failure` signal fires. The\n * server is telling us our request is malformed / our key is\n * revoked / we lack permission — retrying with the same key\n * forever just grows the queue while the customer thinks events\n * are landing.\n * - 5xx / network / 408 / 429: pendingBatch + batchId stay; backoff\n * schedules a retry; the next `flush()` re-uses both.\n *\n * `options.keepalive` marks the underlying fetch as keepalive so the\n * browser keeps the request alive past page unload. Use for terminal\n * flushes (pagehide / visibilitychange→hidden / beforeunload).\n */\n async flush(options: { keepalive?: boolean } = {}): Promise<IngestResponse | null> {\n // PARK hush: once the server has rejected our wire dialect as too old\n // (426), every flush of the same-format payload will fail identically.\n // Hold — don't flush — until the next boot on an upgraded SDK. The\n // events stay buffered + persisted, so they deliver on that boot.\n if (this.parked) return 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 persistAll()\n // until the server confirms it — that's the 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 // Event Envelope v1 batch envelope (backend/docs/\n // event-envelope-spec-v1.md §1). `envelopeVersion` is the\n // schema/wire version the server parses against (\"can I parse\n // this?\") and is DISTINCT from `sdk.version` (\"which build is in\n // the wild?\") — two questions, two fields, never conflated. The\n // backend refuses payloads with a missing/unknown envelopeVersion.\n envelopeVersion: 1,\n appId: env.appId,\n environment: env.environment,\n sdk: env.sdk,\n events: batch,\n },\n keepalive: options.keepalive === true,\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 // PARK (HTTP 426 / `sdk_version_unsupported`) — the THIRD outcome.\n // Checked BEFORE the permanent-4xx drop so a version-rejection never\n // falls into the drop path. The data is good; only the dialect is\n // old. Keep every held event — fold the in-flight batch back to the\n // FRONT of the buffer (oldest-first) so it persists and the next\n // (upgraded) boot's rehydrate delivers it — then hush (no retry\n // scheduled; the flush guard above blocks further attempts).\n if (isVersionRejected(err)) {\n this.parked = true;\n this.buffer = [...batch, ...this.buffer];\n if (this.buffer.length > HARD_BUFFER_CAP) {\n const overflow = this.buffer.length - HARD_BUFFER_CAP;\n this.buffer.splice(0, overflow); // FIFO: evict oldest, keep newest 1000\n this.dropped += overflow;\n }\n this.pendingBatch = null;\n this.pendingBatchId = null;\n this.inFlight -= batch.length;\n this.persistAll();\n this.cfg.onBufferChange?.(this.buffer.length);\n const minVersion = versionRejectionFloor(err);\n if (!this.parkWarned) {\n this.parkWarned = true;\n // ONE developer-facing console line — the terminal-side cure that\n // reaches them mid-debug, paired with the dashboard banner.\n console.warn(\n \"[Crossdeck] SDK outdated — the server is no longer accepting this \" +\n \"version's event format. Your events are PARKED on-device (held, \" +\n \"not lost) and will deliver automatically once you update \" +\n `@cross-deck/web${minVersion ? ` to >= ${minVersion}` : \"\"} and redeploy.`,\n );\n }\n this.cfg.onParked?.({ minVersion, surface: versionRejectionSurface(err) });\n return null;\n }\n\n // Permanent failures (4xx except 408/429) are NOT retryable. The\n // server is telling us our request is malformed (400/422), our\n // key is revoked (401), we lack permission (403), or the endpoint\n // doesn't exist (404). Retrying with the same Idempotency-Key\n // forever just grows the queue silently while the customer\n // thinks events are landing. 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 (5xx / network / 408 / 429). pendingBatch +\n // pendingBatchId stay set; the next scheduler-driven flush re-uses\n // both. Persisted state is unchanged from the entry path — it\n // still includes `[...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 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` counts events waiting for their FIRST flush. The\n // in-flight pendingBatch (retrying) is tracked separately via\n // `inFlight` — surfacing both lets diagnostics show \"we have\n // events stuck retrying\" distinct from \"new events arriving\".\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 * Production callers don't need this.\n */\n get pendingIdempotencyKey(): string | null {\n return this.pendingBatchId;\n }\n\n /**\n * Persist `[...pendingBatch, ...buffer]` — the full set of\n * not-yet-confirmed events. The next boot rehydrates this exact set\n * into `buffer` and replays. The server dedups via eventId\n * (ReplacingMergeTree on the warehouse side), so re-sending an event\n * that may have already landed is safe.\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 // ---------- internal scheduling ----------\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 Many\n * Requests — both indicate transient state where the SAME request\n * (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 // 426 is PARK, never drop — handled by isVersionRejected before this is\n // ever reached, but excluded here too so no path can drop a parkable batch.\n if (status === 426) return false;\n return true;\n}\n\n/**\n * True when the server PARKED this SDK: HTTP 426 Upgrade Required, the\n * status code invented for exactly this — \"your client is too old.\"\n * Unambiguous (no proxy or middleware emits 426 spuriously, unlike\n * 400/422), and corroborated by the machine code `sdk_version_unsupported`\n * so a status-rewriting layer can't hide it. Distinct from a permanent\n * 4xx drop: the data is good, only the wire dialect is stale.\n */\nfunction isVersionRejected(err: unknown): boolean {\n if (!err || typeof err !== \"object\") return false;\n if ((err as { status?: unknown }).status === 426) return true;\n return (err as { code?: unknown }).code === \"sdk_version_unsupported\";\n}\n\n/** Server-supplied required version floor from the 426 body, if present. */\nfunction versionRejectionFloor(err: unknown): string | undefined {\n const v = (err as { minVersion?: unknown } | null)?.minVersion;\n return typeof v === \"string\" && v.length > 0 ? v : undefined;\n}\n\n/** Server-supplied surface id from the 426 body, if present. */\nfunction versionRejectionSurface(err: unknown): string | undefined {\n const v = (err as { surface?: unknown } | null)?.surface;\n return typeof v === \"string\" && v.length > 0 ? v : undefined;\n}\n\nfunction defaultScheduler(fn: () => void, ms: number): () => void {\n // Use unref()-style behaviour where supported so a pending flush doesn't\n // block Node from exiting. setTimeout in browsers ignores .unref() —\n // that's fine.\n const id = setTimeout(fn, ms);\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 failure\n * modes lose data today:\n *\n * 1. Page unload with a failed terminal flush. `keepalive: true`\n * survives the unload, but only up to 64 KB total across all\n * keepalive requests. A large batch on a slow network drops past\n * that cap.\n * 2. Hard browser crash / power loss. The in-memory buffer goes with\n * the process.\n * 3. Network down for longer than the user's session. Events that\n * stay in the in-memory buffer disappear when the tab closes.\n *\n * Stripe / Segment / PostHog all persist queued events to a durable\n * store (localStorage for browsers, IndexedDB for very large queues,\n * AsyncStorage on RN) and replay them on the next boot. We do the\n * same here with localStorage as the default backing store.\n *\n * Failure modes handled gracefully:\n * - Storage throws (quota exceeded, private mode, sandboxed iframe)\n * → silent degrade to in-memory only. The SDK keeps working; the\n * durability 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 localStorage value.\n * - Storage write contention from another tab → last-writer-wins is\n * fine because every queued event has an `eventId` and the\n * backend dedupes via ReplacingMergeTree. Cross-tab coordination\n * via BroadcastChannel is a Phase 2 follow-up.\n *\n * The storage key is `${prefix}queue.v1` to leave room for future\n * format migrations.\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 events captured on the most recent write request. We keep\n // the latest snapshot ref so a debounced write always picks up the\n // freshest buffer state.\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 no\n * warning) when nothing is stored, the blob is malformed, or storage\n * is unavailable. Caller is responsible for treating duplicates from\n * the persisted queue as the SAME events (eventId-based dedup).\n */\n load(): QueuedEvent[] {\n let raw: string | null;\n try {\n raw = 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 so\n * a burst of enqueue() calls coalesces into one persistence write.\n * Writes are best-effort: if storage throws (quota, private mode),\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 change\n // 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 /** Synchronous variant for terminal flushes (pagehide / beforeunload). */\n saveSync(snapshot: readonly QueuedEvent[]): void {\n this.pendingSnapshot = snapshot.slice();\n this.flushWrite();\n }\n\n /** Wipe the persisted blob. Used by reset() (logout). */\n clear(): void {\n this.pendingSnapshot = null;\n this.writeScheduled = false;\n try {\n 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 try {\n this.options.storage.removeItem(this.key);\n } catch {\n // ignore\n }\n return;\n }\n\n const blob: PersistedQueue = { version: 1, events: snapshot };\n try {\n this.options.storage.setItem(this.key, JSON.stringify(blob));\n } catch {\n // Quota exceeded / private mode / etc. — silent degrade. The\n // in-memory buffer is still authoritative; we just lose\n // crash-safety for this batch.\n }\n }\n}\n","/**\n * Storage adapters for SDK-persisted state.\n *\n * Three flavours:\n * - browser localStorage (default in browsers)\n * - 1st-party document.cookie (redundancy for cleared localStorage)\n * - in-memory (default in Node, or as an explicit fallback)\n *\n * Detection is at construction time, not at every call — picking the\n * adapter once means we don't hit `typeof window` checks on hot paths.\n *\n * ----- Bank-grade identity continuity -----\n *\n * Plain localStorage is not enough. ITP, private browsing, \"clear site\n * data\" actions, and aggressive privacy extensions all wipe it. When\n * that happens, the SDK mints a fresh anonymousId on next page load\n * and the customer's analytics see one human as multiple \"new\n * visitors\" — a credibility hit on every dashboard chart that depends\n * on visitor uniqueness (new vs returning, retention, funnels).\n *\n * The fix is redundancy: we write the same identity to BOTH\n * localStorage AND a 1st-party cookie. On boot we read both; whichever\n * survived wins. On set, we write to both stores so a future clear of\n * either doesn't lose the user.\n *\n * Caveats (documented honestly):\n * 1. Safari ITP caps client-set 1st-party cookies at 7 days. Cookie\n * redundancy protects against localStorage clears WITHIN that\n * 7-day window, not beyond it. The full ITP-bypass story (server-\n * set cookies via a customer-CNAMEd subdomain) is a Phase 2\n * follow-up that requires customer DNS configuration.\n * 2. We never write fingerprintable data — only the same anonymousId\n * already in localStorage. Privacy posture is unchanged from\n * single-store identity.\n * 3. `persistIdentity: false` disables BOTH stores so customers\n * running strict consent flows can defer cookie writes until the\n * user opts in.\n */\n\nimport type { KeyValueStorage } from \"./types\";\n\n/**\n * In-memory storage. Cleared on process exit. Useful for Node runtimes\n * where you want session-scoped identity that doesn't persist to disk.\n */\nexport class MemoryStorage implements KeyValueStorage {\n private store = new Map<string, string>();\n getItem(key: string): string | null {\n return this.store.get(key) ?? null;\n }\n setItem(key: string, value: string): void {\n this.store.set(key, value);\n }\n removeItem(key: string): void {\n this.store.delete(key);\n }\n}\n\n/**\n * 1st-party cookie storage. All writes set:\n * - Path=/ — visible site-wide so SPA route changes\n * keep the same identity\n * - Max-Age=63072000 — 2 years (clamped to 7 days by Safari ITP\n * but written long anyway for non-ITP UAs)\n * - SameSite=Lax — standard for 1st-party identity, blocks\n * cross-site request abuse but allows\n * top-level navigation reads\n * - Secure — only set when page is served over HTTPS;\n * omitted on http://localhost so dev still\n * works without a TLS cert\n *\n * We do NOT set HttpOnly because the SDK itself needs to read the\n * cookie via document.cookie to honour the redundancy contract. That\n * means malicious JS on the same origin could read the anonymousId,\n * which is the same security posture as localStorage — anything that\n * can read localStorage can read this cookie. Stripe, Segment, and\n * PostHog ship with the same trade-off for the same reason.\n *\n * Empty / unparseable cookie strings degrade silently to null. We never\n * throw — a broken cookie should look identical to \"no cookie set.\"\n */\nexport class CookieStorage implements KeyValueStorage {\n private readonly maxAgeSec: number;\n private readonly secure: boolean;\n private readonly sameSite: \"Lax\" | \"Strict\" | \"None\";\n\n constructor(options?: {\n maxAgeSec?: number;\n secure?: boolean;\n sameSite?: \"Lax\" | \"Strict\" | \"None\";\n }) {\n this.maxAgeSec = options?.maxAgeSec ?? 63_072_000; // 2 years\n this.secure = options?.secure ?? defaultSecure();\n this.sameSite = options?.sameSite ?? \"Lax\";\n }\n\n getItem(key: string): string | null {\n if (!hasDocument()) return null;\n const doc = (globalThis as { document: Document }).document;\n const cookies = doc.cookie ? doc.cookie.split(/;\\s*/) : [];\n const prefix = encodeURIComponent(key) + \"=\";\n for (const c of cookies) {\n if (c.startsWith(prefix)) {\n try {\n return decodeURIComponent(c.slice(prefix.length));\n } catch {\n return null;\n }\n }\n }\n return null;\n }\n\n setItem(key: string, value: string): void {\n if (!hasDocument()) return;\n const doc = (globalThis as { document: Document }).document;\n const parts = [\n `${encodeURIComponent(key)}=${encodeURIComponent(value)}`,\n \"Path=/\",\n `Max-Age=${this.maxAgeSec}`,\n `SameSite=${this.sameSite}`,\n ];\n if (this.secure) parts.push(\"Secure\");\n try {\n doc.cookie = parts.join(\"; \");\n } catch {\n // Some embedded webviews block document.cookie writes — swallow.\n // localStorage redundancy still gives us identity continuity.\n }\n }\n\n removeItem(key: string): void {\n if (!hasDocument()) return;\n const doc = (globalThis as { document: Document }).document;\n // Negative Max-Age + matching path expires the cookie immediately.\n // We keep the same SameSite/Secure attributes so browsers actually\n // accept the deletion request as targeting the same cookie.\n const parts = [\n `${encodeURIComponent(key)}=`,\n \"Path=/\",\n \"Max-Age=0\",\n `SameSite=${this.sameSite}`,\n ];\n if (this.secure) parts.push(\"Secure\");\n try {\n doc.cookie = parts.join(\"; \");\n } catch {\n // Same reasoning as setItem — swallow.\n }\n }\n}\n\n/**\n * Pick the best available storage. Browser → localStorage if accessible,\n * else MemoryStorage. Node → MemoryStorage. Caller can override via\n * Crossdeck.start({ storage: ... }) for custom adapters (RN AsyncStorage,\n * Cookies, encrypted vaults, etc.).\n *\n * We probe localStorage with a try/catch because some environments\n * (private mode Safari, embedded webviews) define `localStorage` but\n * throw on every call — falling back to memory keeps us correct.\n */\nexport function detectDefaultStorage(): KeyValueStorage {\n try {\n const ls = (globalThis as { localStorage?: KeyValueStorage }).localStorage;\n if (ls) {\n // Probe with a no-op write to confirm we can actually use it.\n const probe = \"__crossdeck_probe__\";\n ls.setItem(probe, \"1\");\n ls.removeItem(probe);\n return ls;\n }\n } catch {\n // Private mode / sandboxed iframe / quota exceeded — fall through.\n }\n return new MemoryStorage();\n}\n\n/**\n * Detect whether the current page is served over HTTPS so we can set\n * the Secure cookie attribute. Defensive against environments where\n * `location` is missing (Workers, server-rendered pre-hydration).\n */\nfunction defaultSecure(): boolean {\n try {\n const loc = (globalThis as { location?: Location }).location;\n return loc?.protocol === \"https:\";\n } catch {\n return false;\n }\n}\n\nfunction hasDocument(): boolean {\n return typeof (globalThis as { document?: unknown }).document !== \"undefined\";\n}\n","/**\n * Device + environment enrichment.\n *\n * Auto-attached to every event the SDK emits when `autoTrack.deviceInfo` is\n * enabled (default). Caller-supplied event properties always override\n * auto-detected ones (so a developer can manually set `app.version` per\n * event if they want to A/B between builds).\n *\n * Privacy posture:\n * - No fingerprinting (no canvas hashes, no font enumeration).\n * - No precise geolocation (only timezone + locale, both of which the\n * browser exposes to every page anyway).\n * - No IP collection — the backend logs the request IP for rate-limit\n * purposes; it isn't stored on the event document.\n * - All fields are typed enums or short strings; we never echo back\n * full User-Agent strings to avoid surfacing fingerprintable detail\n * in dashboards.\n */\n\nexport interface DeviceInfo {\n os?: string;\n osVersion?: string;\n browser?: string;\n browserVersion?: string;\n locale?: string;\n timezone?: string;\n screenWidth?: number;\n screenHeight?: number;\n viewportWidth?: number;\n viewportHeight?: number;\n devicePixelRatio?: number;\n /** Caller-supplied. Set via Crossdeck.start({ appVersion: \"1.2.3\" }). */\n appVersion?: string;\n}\n\n/**\n * Are we in a browser context? Pure function; no side effects.\n *\n * Detects: globalThis.window AND globalThis.document AND globalThis.navigator.\n * All three must be present — Workers / Service Workers have window but\n * no document, and Node 18+ now has navigator but no window.\n */\nexport function isBrowser(): boolean {\n return (\n typeof (globalThis as { window?: unknown }).window !== \"undefined\" &&\n typeof (globalThis as { document?: unknown }).document !== \"undefined\" &&\n typeof (globalThis as { navigator?: unknown }).navigator !== \"undefined\"\n );\n}\n\n/**\n * Collect every safe-to-attach environment field. Returns an empty object\n * outside browsers (Node, Workers) — caller can pass appVersion via the\n * `extra` argument for non-browser runtimes.\n */\nexport function collectDeviceInfo(extra?: { appVersion?: string }): DeviceInfo {\n const info: DeviceInfo = {};\n if (extra?.appVersion) info.appVersion = extra.appVersion;\n\n if (!isBrowser()) return info;\n\n const w = (globalThis as { window: Window }).window;\n const nav = (globalThis as { navigator: Navigator }).navigator;\n const doc = (globalThis as { document: Document }).document;\n\n // ----- Locale + timezone -----\n try {\n if (typeof nav.language === \"string\") info.locale = nav.language;\n } catch {}\n try {\n info.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;\n } catch {}\n\n // ----- Screen + viewport -----\n try {\n if (w.screen) {\n info.screenWidth = w.screen.width;\n info.screenHeight = w.screen.height;\n }\n info.viewportWidth = w.innerWidth;\n info.viewportHeight = w.innerHeight;\n info.devicePixelRatio = w.devicePixelRatio;\n } catch {}\n\n // ----- Browser + OS from User-Agent -----\n try {\n const ua = nav.userAgent ?? \"\";\n const parsed = parseUserAgent(ua);\n Object.assign(info, parsed);\n } catch {}\n\n // ua-ch hints (Chromium browsers expose these properly without UA-string parsing)\n try {\n const uaData = (nav as Navigator & {\n userAgentData?: { platform?: string; brands?: Array<{ brand: string; version: string }> };\n }).userAgentData;\n if (uaData?.platform && !info.os) info.os = uaData.platform;\n if (uaData?.brands && !info.browser) {\n // Pick the most-specific non-\"Not.A;Brand\" entry\n const real = uaData.brands.find(\n (b) => !/Not[ .;A]*Brand/i.test(b.brand) && !/Chromium/i.test(b.brand),\n );\n if (real) {\n info.browser = real.brand;\n info.browserVersion = real.version;\n }\n }\n } catch {}\n\n // Suppress empties (a doc not yet hydrated could leave fields undefined)\n void doc; // referenced only for the isBrowser narrowing\n return info;\n}\n\n/**\n * Tiny User-Agent parser — extracts os, osVersion, browser, browserVersion.\n *\n * Doesn't try to be a full UA database (Bowser, ua-parser-js are large\n * deps). Covers ~95% of real-world traffic by recognising the major\n * browsers and operating systems. Unknown UAs fall through silently.\n *\n * Exported for unit testing.\n */\nexport function parseUserAgent(ua: string): Partial<DeviceInfo> {\n const out: Partial<DeviceInfo> = {};\n\n // ----- Operating system -----\n // Order matters: iPad/iPhone before Mac (iPadOS 13+ UAs claim \"Macintosh\"),\n // Android before Linux (Android UAs contain \"Linux\").\n if (/iPad|iPhone|iPod/.test(ua)) {\n out.os = \"iOS\";\n const m = ua.match(/OS (\\d+[._]\\d+(?:[._]\\d+)?)/);\n if (m?.[1]) out.osVersion = m[1].replace(/_/g, \".\");\n } else if (/Android/.test(ua)) {\n out.os = \"Android\";\n const m = ua.match(/Android (\\d+(?:\\.\\d+)*)/);\n if (m?.[1]) out.osVersion = m[1];\n } else if (/Windows/.test(ua)) {\n out.os = \"Windows\";\n const m = ua.match(/Windows NT (\\d+\\.\\d+)/);\n if (m?.[1]) out.osVersion = m[1];\n } else if (/Mac OS X|Macintosh/.test(ua)) {\n out.os = \"macOS\";\n const m = ua.match(/Mac OS X (\\d+[._]\\d+(?:[._]\\d+)?)/);\n if (m?.[1]) out.osVersion = m[1].replace(/_/g, \".\");\n } else if (/Linux/.test(ua)) {\n out.os = \"Linux\";\n }\n\n // ----- Browser -----\n // Order matters: Edge before Chrome (Edge UA contains \"Chrome\"),\n // Chrome before Safari (Chrome UA contains \"Safari\").\n if (/Edg\\/(\\d+(?:\\.\\d+)*)/.test(ua)) {\n out.browser = \"Edge\";\n out.browserVersion = ua.match(/Edg\\/(\\d+(?:\\.\\d+)*)/)?.[1];\n } else if (/Firefox\\/(\\d+(?:\\.\\d+)*)/.test(ua)) {\n out.browser = \"Firefox\";\n out.browserVersion = ua.match(/Firefox\\/(\\d+(?:\\.\\d+)*)/)?.[1];\n } else if (/OPR\\/(\\d+(?:\\.\\d+)*)/.test(ua)) {\n out.browser = \"Opera\";\n out.browserVersion = ua.match(/OPR\\/(\\d+(?:\\.\\d+)*)/)?.[1];\n } else if (/Chrome\\/(\\d+(?:\\.\\d+)*)/.test(ua)) {\n out.browser = \"Chrome\";\n out.browserVersion = ua.match(/Chrome\\/(\\d+(?:\\.\\d+)*)/)?.[1];\n } else if (/Version\\/(\\d+(?:\\.\\d+)*).*Safari/.test(ua)) {\n out.browser = \"Safari\";\n out.browserVersion = ua.match(/Version\\/(\\d+(?:\\.\\d+)*)/)?.[1];\n }\n\n return out;\n}\n","/**\n * Auto-tracking — sessions and SPA page views, emitted via the same\n * track() pipeline as developer-instrumented events. No-op outside\n * browsers (Node, Workers).\n *\n * Sessions:\n * - One sessionId per \"alive in foreground\" window.\n * - `session.started` fires on install (after start()).\n * - `session.ended` fires on visibilitychange→hidden, beforeunload,\n * and pagehide. Multiple end-triggers are deduplicated so we don't\n * emit two `session.ended` events for one tab close.\n * - `session.duration_ms` attached on end.\n * - If the tab returns to foreground after >30 minutes idle, the next\n * visibilitychange→visible mints a NEW sessionId — matches the\n * 30-min session-window convention used by GA4 / Mixpanel / etc.\n *\n * Page views:\n * - `page.viewed` fires on initial install.\n * - Hooks into `history.pushState` / `history.replaceState` (monkey-\n * patched in a non-destructive way so other libraries that hook\n * them still see their events) and `popstate` for SPA navigation.\n * - Properties: path, url, title, referrer, search, hash.\n *\n * Privacy: this module emits names + properties only. The Crossdeck\n * client adds device info on top via track(). Nothing here collects\n * PII beyond the URL itself, and we don't even log query strings\n * separately from the path (developer can post-process if needed).\n */\n\nimport { randomChars } from \"./identity\";\nimport type { KeyValueStorage } from \"./types\";\n\nexport interface AutoTrackConfig {\n sessions: boolean;\n pageViews: boolean;\n /** Whether to enrich every event with device info. Lives on the client, not here, but documented together. */\n deviceInfo: boolean;\n /**\n * Click autocapture. When true, the SDK installs a global click\n * listener that fires `element.clicked` for every interactive\n * click. Captures the target element's selector, text content,\n * tag, href, data-* attributes, and viewport coordinates — enough\n * to power funnel attribution (\"clicked X then converted\") and\n * heatmap visualisation. Mixpanel / Amplitude default. Privacy\n * guardrails baked in (input/password/sensitive-class skips).\n *\n * Default ON because behavioural attribution is Crossdeck's USP.\n * Set to false to disable autocapture entirely (developer adds\n * track() calls manually).\n */\n clicks: boolean;\n /** Capture Web Vitals (LCP/INP/CLS/FCP/TTFB). Default true (browser only). */\n webVitals: boolean;\n /** Capture uncaught errors + unhandled rejections + 5xx fetch/XHR. Default true (browser only). */\n errors: boolean;\n}\n\nexport const DEFAULT_AUTO_TRACK: AutoTrackConfig = {\n sessions: true,\n pageViews: true,\n deviceInfo: true,\n clicks: true,\n webVitals: true,\n errors: true,\n};\n\n/**\n * Reopen as a NEW session once activity has been idle this long — the\n * 30-min rolling-inactivity window GA4 / Mixpanel use. Applies both\n * in-page (tab hidden → returns >30min later) AND across page loads (the\n * SDK re-installs on every navigation of a multi-page site; if the stored\n * session's last activity is within this window we RESUME it instead of\n * minting a new one). Without the cross-page-load half, every navigation\n * ended one session and started another at the same instant.\n */\nconst SESSION_RESUME_THRESHOLD_MS = 30 * 60 * 1000;\n\n/** Default storage key for the persisted session continuity record. */\nconst SESSION_STORAGE_KEY = \"crossdeck:session\";\n\n/**\n * Throttle for flushing the rolling last-activity time to storage. The\n * in-memory value stays exact; we just avoid a storage write on every\n * single event. pagehide does a final forced flush so the last activity\n * before a navigation is never lost.\n */\nconst ACTIVITY_PERSIST_THROTTLE_MS = 5_000;\n\ntype TrackFn = (name: string, properties?: Record<string, unknown>) => void;\n\n/**\n * The slice of session state we persist across page loads. Kept minimal:\n * enough to RESUME the same visit (id + first-touch acquisition) and to\n * decide whether the resume window is still open (lastActivityAt).\n */\ninterface StoredSession {\n id: string;\n startedAt: number;\n lastActivityAt: number;\n acquisition: SessionAcquisition;\n}\n\ninterface SessionState {\n sessionId: string;\n startedAt: number;\n /** Rolling timestamp of the last tracked event — drives the 30-min window. */\n lastActivityAt: number;\n hiddenAt: number | null;\n endedSent: boolean;\n /**\n * Acquisition context captured once at session start. GA4 calls\n * this \"first-touch attribution within the session.\" We attach\n * these to every event of the session so dashboards can answer\n * \"what was the source of users who triggered paywall_shown\" — a\n * per-event lookup against the captured-once state, not a re-parse\n * of the URL on every track().\n *\n * Empty strings (not undefined) so JSON envelope serialisation\n * stays uniform — backend's extractAcquisition handles \"\" the\n * same as missing.\n */\n acquisition: SessionAcquisition;\n}\n\nexport interface SessionAcquisition {\n utm_source: string;\n utm_medium: string;\n utm_campaign: string;\n utm_content: string;\n utm_term: string;\n referrer: string;\n // ---------- paid-traffic click IDs (v0.9.0+) ----------\n // UTM parameters are a documentation convention — anyone writing\n // ads can forget to add them, and many platforms (Performance Max,\n // Display & Video 360, automated bidding) emit ONLY a click-id.\n // Capturing these alongside UTMs catches the ~40% of paid traffic\n // that UTMs miss. Each is the platform's stable click identifier\n // that flows from ad-click → landing-page URL → conversion event.\n /** Google Ads click identifier. */\n gclid: string;\n /** Facebook / Meta Ads click identifier. */\n fbclid: string;\n /** Microsoft Advertising (Bing) click identifier. */\n msclkid: string;\n /** TikTok Ads click identifier. */\n ttclid: string;\n /** LinkedIn Ads click identifier. */\n li_fat_id: string;\n /** Twitter / X Ads click identifier. */\n twclid: string;\n}\n\nconst EMPTY_ACQUISITION: SessionAcquisition = {\n utm_source: \"\",\n utm_medium: \"\",\n utm_campaign: \"\",\n utm_content: \"\",\n utm_term: \"\",\n referrer: \"\",\n gclid: \"\",\n fbclid: \"\",\n msclkid: \"\",\n ttclid: \"\",\n li_fat_id: \"\",\n twclid: \"\",\n};\n\nexport class AutoTracker {\n private session: SessionState | null = null;\n private cleanups: Array<() => void> = [];\n /**\n * Persistent storage for session continuity across page loads. Uses\n * the SAME adapter as the rest of the SDK (localStorage by default,\n * MemoryStorage when identity persistence is disabled for consent) so\n * session persistence honours the same privacy posture as identity.\n * Null only if no adapter was supplied → session is in-memory only\n * (per-page), the pre-fix behaviour.\n */\n private readonly storage: KeyValueStorage | null;\n private readonly sessionKey: string;\n /** Last time we flushed lastActivityAt to storage (throttle gate). */\n private lastPersistAt = 0;\n /**\n * Event Envelope v1 §3 — per-session monotonic sequence counter.\n * The single owner of the seq counter lives here, alongside all other\n * session state. Incremented atomically at track() time (via nextSeq()),\n * reset to 0 whenever a new session boundary is crossed (startNewSession\n * path + resetSession). Persists across background/foreground within a\n * session — only a real session boundary (not a tab hide/show) resets it.\n */\n private _sessionSeq = 0;\n /**\n * Stable per-page-view identifier. Minted at every `page.viewed`\n * emission and attached to every subsequent event until the next\n * `page.viewed`. Lets dashboards correlate \"user clicked X\" to\n * \"user viewed page Y\" without timestamp arithmetic — the canonical\n * Mixpanel `$current_url` / Segment `pageId` pattern.\n *\n * Null until the first `page.viewed` fires (which happens at SDK\n * install if `autoTrack.pageViews !== false`).\n */\n private pageviewId: string | null = null;\n\n constructor(\n private readonly cfg: AutoTrackConfig,\n private readonly track: TrackFn,\n opts?: { storage?: KeyValueStorage; storageKey?: string },\n ) {\n this.storage = opts?.storage ?? null;\n this.sessionKey = opts?.storageKey ?? SESSION_STORAGE_KEY;\n }\n\n install(): void {\n if (!isBrowserSafe()) return;\n if (this.cfg.sessions) this.installSessionTracking();\n if (this.cfg.pageViews) this.installPageViewTracking();\n if (this.cfg.clicks) this.installClickTracking();\n }\n\n uninstall(): void {\n while (this.cleanups.length) {\n const fn = this.cleanups.pop();\n try { fn?.(); } catch { /* ignore */ }\n }\n if (this.session && !this.session.endedSent) {\n this.emitSessionEnd();\n }\n // Explicit teardown is a real end (unlike a navigation's pagehide) —\n // clear the persisted session so the next start() begins fresh\n // rather than resuming a session the host deliberately stopped.\n this.clearStoredSession();\n this.session = null;\n }\n\n /** Exposed for tests + consumers that want to reset the session manually. */\n resetSession(): void {\n if (this.session && !this.session.endedSent) this.emitSessionEnd();\n // Null pageviewId on session boundary so any event fired between\n // session reset and the next page.viewed doesn't ship the previous\n // session's pageview attribution. Audit P1 #16: pre-fix the\n // pageviewId survived 30-min idle resets and silently corrupted\n // post-resume event → pageview correlation. The next page.viewed\n // mints a fresh id (see installPageViewTracking's `fire()`).\n this.pageviewId = null;\n this.session = this.startNewSession();\n this.persistSession();\n this.emitSessionStart();\n }\n\n /**\n * Keep the rolling session window alive. Called by the host on EVERY\n * tracked event (auto or custom) so any activity — not just the\n * pageviews/clicks AutoTracker emits itself — pushes the 30-min idle\n * boundary forward. In-memory time is updated exactly; the storage\n * flush is throttled (pagehide forces a final flush).\n */\n markActivity(): void {\n if (!this.session) return;\n const now = Date.now();\n // If this activity lands AFTER the resume window has lapsed, it belongs to\n // a NEW visit — roll the session BEFORE the event records, so a single\n // stored session can never span a >30-min gap (the contract's\n // session_gap_within_window invariant). This covers the case the\n // visibility/page-load resume checks miss entirely: a tab left open and\n // idle, then interacted with again, with no visibility transition. We do\n // NOT back-date a session.ended onto the prior session — that would itself\n // open the gap; its end is inferred from its last event (same as the\n // page-load resume path). session.started is emitted here, before the\n // triggering event is stamped (timestamp is set later in track()), so it\n // is the earliest event of the new session.\n if (now - this.session.lastActivityAt >= SESSION_RESUME_THRESHOLD_MS) {\n this.pageviewId = null;\n this.session = this.startNewSession();\n this.persistSession();\n this.emitSessionStart();\n return;\n }\n this.session.lastActivityAt = now;\n if (now - this.lastPersistAt >= ACTIVITY_PERSIST_THROTTLE_MS) {\n this.persistSession();\n }\n }\n\n /** Exposed for inspection/tests — returns the current sessionId (or null if not in a session). */\n get currentSessionId(): string | null {\n return this.session?.sessionId ?? null;\n }\n\n /** Stable per-page-view ID. Null before the first page.viewed has fired. */\n get currentPageviewId(): string | null {\n return this.pageviewId;\n }\n\n /**\n * Per-session acquisition context — utm_* + referrer, captured once\n * at session start. Returns empty strings when there's no session\n * (Node, before init, after uninstall) so callers can spread without\n * conditional logic. Bank-grade rule: capture once, attach to every\n * event of the session, don't re-read on every track() (the URL\n * changes via SPA pushState; the source-of-record is the URL we\n * landed on).\n */\n get currentAcquisition(): SessionAcquisition {\n return this.session?.acquisition ?? EMPTY_ACQUISITION;\n }\n\n /**\n * Event Envelope v1 §3 — return the next seq value for the current session\n * and advance the counter. Called SYNCHRONOUSLY at track() time, before any\n * async dispatch, so the seq assignment order is deterministic (call order,\n * not scheduler luck). The counter is reset to 0 at every session boundary;\n * it is NOT reset by tab hide/show (spec §3 clause 1: backgrounding does not\n * reset seq). When no session is active (Node, before init, after uninstall),\n * returns 0 and leaves the counter unchanged — fallback, not an error.\n */\n nextSeq(): number {\n // Increment FIRST, then return. Pre-increment means the first event of a\n // session gets seq=0 (counter starts at 0 after reset, returns 0 pre-bump,\n // but we return-then-increment to match Swift's pattern: 0-based first).\n // We return the current value and then increment so seq 0 = first event.\n const seq = this._sessionSeq;\n this._sessionSeq += 1;\n return seq;\n }\n\n // ---------- sessions ----------\n private installSessionTracking(): void {\n const now = Date.now();\n const stored = this.readStoredSession();\n if (stored && now - stored.lastActivityAt < SESSION_RESUME_THRESHOLD_MS) {\n // RESUME the in-flight visit across a full page load. A multi-page\n // site re-installs the SDK on every navigation; minting a new\n // session here (the old behaviour) split one visit into one session\n // per page, each ended at the same instant the next began — the\n // \"session ends and the next begins at 05:18:11\" bug. Reuse the id\n // + first-touch acquisition; do NOT emit a second session.started.\n this.session = {\n sessionId: stored.id,\n startedAt: stored.startedAt,\n lastActivityAt: now,\n hiddenAt: null,\n endedSent: false,\n acquisition: stored.acquisition,\n };\n this.persistSession();\n } else {\n // Genuinely new visit (no stored session, or the 30-min window\n // lapsed). A stale stored session is simply superseded — we do NOT\n // emit a back-dated session.ended for it, which would land at the\n // new session's timestamp and corrupt the old session's duration;\n // the dashboard infers the prior session's end from its last event.\n this.session = this.startNewSession();\n this.persistSession();\n this.emitSessionStart();\n }\n\n const onVisChange = (): void => {\n if (!this.session) return;\n const doc = (globalThis as { document: Document }).document;\n if (doc.visibilityState === \"hidden\") {\n // Quick tab switches and Cmd-Tabs land here, but the page is\n // still alive. Record the time and flush, but do NOT emit\n // session.ended — returning seconds later must continue the same\n // session (the 30-min window intent). The flush keeps the\n // last-activity time accurate for a next-visit resume if the tab\n // is closed rather than returned to.\n this.session.hiddenAt = Date.now();\n this.persistSession();\n } else if (doc.visibilityState === \"visible\") {\n // Decide on real inactivity, not just time-hidden: lastActivityAt\n // is the last tracked event, so this is the true idle gap.\n const idleFor = Date.now() - this.session.lastActivityAt;\n if (idleFor >= SESSION_RESUME_THRESHOLD_MS) {\n // Long idle → the previous session genuinely ended. Open a fresh\n // one. Do NOT back-date a session.ended onto the prior session: it\n // would land >30 min after that session's last real event and open\n // an intra-session gap (session_gap_within_window). Its end is\n // inferred from its last event — consistent with the page-load\n // resume path. Null pageviewId on the boundary (audit P1 #16) so any\n // event before the next page.viewed doesn't ship stale attribution.\n this.pageviewId = null;\n this.session = this.startNewSession();\n this.persistSession();\n this.emitSessionStart();\n } else {\n // Quick return — same session continues.\n this.session.hiddenAt = null;\n }\n }\n };\n\n // A page unload is NOT a session end — on a multi-page site it's a\n // navigation, and the next page resumes this same session. Flush the\n // final activity time so the next load's resume-window check is\n // accurate. The session ends only on real 30-min inactivity or an\n // explicit uninstall(). (This is what stopped the per-navigation\n // session.ended / session.started churn.)\n const onPageHide = (): void => this.persistSession();\n\n const w = (globalThis as { window: Window }).window;\n const doc = (globalThis as { document: Document }).document;\n doc.addEventListener(\"visibilitychange\", onVisChange);\n w.addEventListener(\"pagehide\", onPageHide);\n // beforeunload is unreliable on mobile; pagehide is the modern equivalent.\n // We listen to both for desktop-vs-mobile coverage.\n w.addEventListener(\"beforeunload\", onPageHide);\n\n this.cleanups.push(() => {\n doc.removeEventListener(\"visibilitychange\", onVisChange);\n w.removeEventListener(\"pagehide\", onPageHide);\n w.removeEventListener(\"beforeunload\", onPageHide);\n });\n }\n\n private startNewSession(): SessionState {\n const now = Date.now();\n // Envelope v1 §3: reset the seq counter at every session boundary. The\n // counter belongs to the session — a new session always starts at seq 0.\n this._sessionSeq = 0;\n return {\n sessionId: mintSessionId(),\n startedAt: now,\n lastActivityAt: now,\n hiddenAt: null,\n endedSent: false,\n acquisition: captureAcquisition(),\n };\n }\n\n /**\n * Read the persisted session continuity record. Returns null on no\n * storage, no record, malformed JSON, or a record missing its required\n * fields — every failure degrades to \"no session to resume\", which is\n * the safe (start-fresh) path.\n */\n private readStoredSession(): StoredSession | null {\n if (!this.storage) return null;\n try {\n const raw = this.storage.getItem(this.sessionKey);\n if (!raw) return null;\n const p = JSON.parse(raw) as Partial<StoredSession>;\n if (\n !p ||\n typeof p.id !== \"string\" ||\n typeof p.startedAt !== \"number\" ||\n typeof p.lastActivityAt !== \"number\"\n ) {\n return null;\n }\n return {\n id: p.id,\n startedAt: p.startedAt,\n lastActivityAt: p.lastActivityAt,\n acquisition:\n p.acquisition && typeof p.acquisition === \"object\"\n ? (p.acquisition as SessionAcquisition)\n : EMPTY_ACQUISITION,\n };\n } catch {\n return null;\n }\n }\n\n /** Flush the current session to storage. No-op without storage/session. */\n private persistSession(): void {\n if (!this.storage || !this.session) return;\n this.lastPersistAt = Date.now();\n try {\n const rec: StoredSession = {\n id: this.session.sessionId,\n startedAt: this.session.startedAt,\n lastActivityAt: this.session.lastActivityAt,\n acquisition: this.session.acquisition,\n };\n this.storage.setItem(this.sessionKey, JSON.stringify(rec));\n } catch {\n // Quota / blocked storage — session degrades to in-memory only.\n }\n }\n\n private clearStoredSession(): void {\n if (!this.storage) return;\n try {\n this.storage.removeItem(this.sessionKey);\n } catch {\n // ignore\n }\n }\n\n private emitSessionStart(): void {\n if (!this.session) return;\n this.track(\"session.started\", { sessionId: this.session.sessionId });\n }\n\n private emitSessionEnd(): void {\n if (!this.session || this.session.endedSent) return;\n const duration = Date.now() - this.session.startedAt;\n this.track(\"session.ended\", {\n sessionId: this.session.sessionId,\n durationMs: duration,\n });\n this.session.endedSent = true;\n }\n\n // ---------- page views ----------\n private installPageViewTracking(): void {\n const w = (globalThis as { window: Window }).window;\n const doc = (globalThis as { document: Document }).document;\n\n // PwC M-5: dedup. SPA frameworks (Next.js, React Router, Vue\n // Router) routinely fire pushState() back-to-back during a single\n // navigation — animation enter, then the destination's settle.\n // Without a guard, we'd send 2-3 page.viewed events per click,\n // inflating pageview / session counts. Dedup window: 250ms,\n // keyed by URL. Identical URL within window = drop.\n //\n // EXCEPTION: popstate (user back/forward) is always a real\n // navigation, even if it lands on a URL we've recently seen.\n // Force-fire on popstate so back-button traffic is never dropped.\n let lastFiredAt = 0;\n let lastFiredUrl = \"\";\n const DEDUP_WINDOW_MS = 250;\n\n const fire = (force = false): void => {\n const loc = w.location;\n const url = loc.href;\n const now = Date.now();\n if (!force && url === lastFiredUrl && now - lastFiredAt < DEDUP_WINDOW_MS) return;\n lastFiredAt = now;\n lastFiredUrl = url;\n\n // Mint a fresh pageviewId BEFORE emitting the event so this\n // page.viewed itself carries it, and every subsequent event up\n // to the next page.viewed inherits it via the auto-attached\n // enrichment in crossdeck.ts:track().\n this.pageviewId = `pv_${Date.now().toString(36)}${randomChars(10)}`;\n\n this.track(\"page.viewed\", {\n pageviewId: this.pageviewId,\n path: loc.pathname,\n url,\n search: loc.search || undefined,\n hash: loc.hash || undefined,\n title: doc.title,\n // referrer only on the first hit of the session — afterward it's\n // always our previous URL, which isn't useful.\n referrer: doc.referrer || undefined,\n });\n };\n\n // Initial page view\n fire();\n\n // SPA navigation: monkey-patch pushState / replaceState. Capture the\n // BARE function references (not bound) so uninstall restores exactly\n // what was there. Bind chains accumulate without limit if every\n // install/uninstall cycle wraps with .bind() — over many cycles\n // pushState becomes [bound bound bound … pushState] and tests that\n // assert \"pushState restored to its previous value\" break.\n //\n // We use `function (this: History, ...args)` so JS's normal method-call\n // semantics bind `this` to history when our wrapper is invoked as\n // history.pushState(...). Then we forward via .apply(this, args) — no\n // pre-binding needed, no chain growth.\n type HistoryFn = (data: unknown, unused: string, url?: string | null) => void;\n const origPush = w.history.pushState as HistoryFn;\n const origReplace = w.history.replaceState as HistoryFn;\n\n function patchedPush(this: History, data: unknown, unused: string, url?: string | null): void {\n origPush.apply(this, [data, unused, url]);\n queueMicrotask(fire);\n }\n function patchedReplace(this: History, data: unknown, unused: string, url?: string | null): void {\n origReplace.apply(this, [data, unused, url]);\n queueMicrotask(fire);\n }\n\n (w.history.pushState as HistoryFn) = patchedPush;\n (w.history.replaceState as HistoryFn) = patchedReplace;\n\n // popstate fires on user back/forward — bypass the dedup window\n // because user navigation is always a real event, not a framework\n // double-fire artefact.\n const onPopState = (): void => fire(true);\n w.addEventListener(\"popstate\", onPopState);\n\n this.cleanups.push(() => {\n // Only restore if WE'RE still the active wrapper. If another tracker\n // installed on top of ours, blindly setting pushState back would\n // unwind their patch too. Conservative: only restore our slot.\n if (w.history.pushState === patchedPush) {\n (w.history.pushState as HistoryFn) = origPush;\n }\n if (w.history.replaceState === patchedReplace) {\n (w.history.replaceState as HistoryFn) = origReplace;\n }\n w.removeEventListener(\"popstate\", onPopState);\n });\n }\n\n // ---------- click autocapture ----------\n /**\n * Global click tracking — Mixpanel / Amplitude style autocapture.\n * Fires `element.clicked` for every interactive click with the\n * target element's selector path, text content, tag, href, data-*\n * attributes, and viewport coordinates. Powers the funnel /\n * attribution USP: \"users who clicked X then converted within\n * 7 days.\" Default ON because behavioural attribution is the\n * core product promise.\n *\n * Privacy guardrails:\n * - Skip clicks ON inputs / textareas / selects (form interaction\n * isn't button telemetry; the dev should track form submits\n * deliberately via track('form_submitted'))\n * - Skip clicks INSIDE [type=\"password\"] and password-class\n * elements\n * - Skip clicks inside elements opted out via class=\"cd-noTrack\"\n * or data-cd-noTrack attribute (Mixpanel's exact opt-out\n * idiom — most devs already know it)\n * - Capture text content but cap at 64 chars and trim — never\n * more than what you'd see on a button label\n *\n * Volume guardrails:\n * - Coalesce double-clicks within 100ms (React's synthetic click\n * pattern + browser's native dblclick can fire twice)\n * - Listen on document at capture phase so we see the click\n * before any framework's own handlers stop propagation\n */\n private installClickTracking(): void {\n const w = (globalThis as { window: Window }).window;\n const doc = (globalThis as { document: Document }).document;\n\n let lastFiredAt = 0;\n let lastFiredTarget: EventTarget | null = null;\n const COALESCE_MS = 100;\n const TEXT_CAP = 64;\n\n const onClick = (ev: MouseEvent): void => {\n const target = ev.target as Element | null;\n if (!target || !(target instanceof Element)) return;\n\n // De-dupe rapid double-fires on the same target (React synthetic\n // click + browser native click can land in the same tick).\n const now = Date.now();\n if (target === lastFiredTarget && now - lastFiredAt < COALESCE_MS) return;\n lastFiredAt = now;\n lastFiredTarget = target;\n\n // Walk up to the nearest \"actionable\" ancestor. A click inside\n // <button><span>Sign up</span></button> should fire as a click\n // on the BUTTON, not the inner span. We climb up to a button /\n // a / [role=\"button\"] / [data-cd-event] / [onclick] — whichever\n // is closer.\n const actionable = closestActionable(target);\n const clicked: Element = actionable || target;\n\n // Privacy: skip form-input clicks, password fields, opted-out\n // subtrees. PII risk is too high to capture text from these.\n if (isFormInput(clicked)) return;\n if (isInOptedOut(clicked)) return;\n if (isInsidePasswordField(clicked)) return;\n\n // Build the event properties.\n const tag = clicked.tagName.toLowerCase();\n const text = trimText(extractText(clicked), TEXT_CAP);\n const href = (clicked as HTMLAnchorElement).href || undefined;\n const linkTarget = (clicked as HTMLAnchorElement).target || undefined;\n const elementId = clicked.id || undefined;\n const role = clicked.getAttribute(\"role\") || undefined;\n const ariaLabel = clicked.getAttribute(\"aria-label\") || undefined;\n const selector = buildSelector(clicked);\n const dataAttrs = collectDataAttrs(clicked);\n const isLink = tag === \"a\" && !!href;\n\n // Optional explicit override: if the dev tagged the element\n // with data-cd-event=\"custom_name\", we use THAT as the event\n // name and stash the auto-properties as `meta` rather than\n // firing as element.clicked. Devs who want named events get\n // them; everyone else gets the auto.\n const explicitName = clicked.getAttribute(\"data-cd-event\");\n\n const props: Record<string, unknown> = {\n selector,\n tag,\n text,\n elementId,\n role,\n ariaLabel,\n href,\n isLink,\n linkTarget,\n viewportX: ev.clientX,\n viewportY: ev.clientY,\n pageX: ev.pageX,\n pageY: ev.pageY,\n ...dataAttrs,\n };\n // Drop empties so the event property bag isn't full of nulls.\n for (const k of Object.keys(props)) {\n if (props[k] === undefined || props[k] === null || props[k] === \"\") delete props[k];\n }\n\n this.track(explicitName || \"element.clicked\", props);\n };\n\n doc.addEventListener(\"click\", onClick, { capture: true, passive: true });\n this.cleanups.push(() => {\n doc.removeEventListener(\"click\", onClick, { capture: true } as AddEventListenerOptions);\n });\n }\n}\n\n// ---------- click-tracking helpers ----------\n\nfunction closestActionable(el: Element): Element | null {\n // Climb up to the nearest interactive ancestor. The order matters —\n // [data-cd-event] wins because it's an explicit dev-supplied tag.\n return (\n el.closest(\"[data-cd-event]\") ||\n el.closest(\"[data-cd-noTrack]\") ||\n el.closest(\"button, a, [role='button'], [role='link'], input[type='button'], input[type='submit']\") ||\n null\n );\n}\n\nfunction isFormInput(el: Element): boolean {\n if (!(el instanceof HTMLElement)) return false;\n const tag = el.tagName.toLowerCase();\n if (tag === \"textarea\" || tag === \"select\") return true;\n if (tag === \"input\") {\n const type = ((el as HTMLInputElement).type || \"\").toLowerCase();\n // Buttons are inputs but they ARE click targets — track them.\n return type !== \"button\" && type !== \"submit\" && type !== \"image\" && type !== \"reset\";\n }\n return false;\n}\n\nfunction isInOptedOut(el: Element): boolean {\n // Mixpanel's idiom: class=\"cd-noTrack\" or [data-cd-noTrack] on any\n // ancestor opts the entire subtree out of autocapture.\n if (el.closest('[data-cd-noTrack], [data-cd-no-track], .cd-noTrack, .cd-no-track')) return true;\n return false;\n}\n\nfunction isInsidePasswordField(el: Element): boolean {\n // Defensive: never capture clicks on / near password fields.\n if (el.closest('input[type=\"password\"]')) return true;\n return false;\n}\n\n// Tags whose text IS a control's own caption when they sit directly\n// inside it — buttons/links routinely wrap their label in one of these.\nconst INLINE_LABEL_TAGS = new Set([\n \"span\", \"b\", \"strong\", \"em\", \"i\", \"small\", \"mark\", \"u\", \"label\",\n \"abbr\", \"time\", \"bdi\", \"cite\", \"code\", \"kbd\", \"q\", \"sub\", \"sup\",\n]);\n\n// Subtrees whose text is decorative (icon internals / styling) and must\n// never leak into a label.\nconst NON_LABEL_SUBTREES = new Set([\"svg\", \"style\", \"script\", \"noscript\"]);\n\n// A clicked control that CONTAINS one of these is a wrapper — around\n// other controls (a card whose whole body is one big <a>, holding three\n// sign-in buttons) or around a content block (a hero <a> wrapping a\n// heading + paragraph). Its full textContent is a mash, not a label.\n// Headings count because a heading inside a clickable is the signature of\n// \"this is a content block, not a button\".\nconst CONTAINER_MARKERS =\n \"a[href], button, input, select, textarea, [role='button'], [role='link'], h1, h2, h3, h4, h5, h6\";\n\nconst HEADING_SELECTOR = \"h1, h2, h3, h4, h5, h6\";\n\n/** Collapse whitespace runs and trim. \" Sign\\n up \" → \"Sign up\". */\nfunction collapseWs(s: string): string {\n return s.replace(/\\s+/g, \" \").trim();\n}\n\n/**\n * Text of an element with WORD BOUNDARIES preserved across nested\n * elements. `el.textContent` concatenates text nodes with nothing between\n * them, so `<h1>…só link</h1><p>Portfolio…` fuses into \"só linkPortfolio\"\n * and `<button>Log in</button><button>Continue…` into \"Log inContinue…\".\n * We insert a space at every element boundary, skip decorative subtrees\n * (icons), then collapse — so the label reads as written, never fused.\n */\nfunction boundaryText(el: Element): string {\n let out = \"\";\n const walk = (node: Node): void => {\n const kids = node.childNodes;\n for (let i = 0; i < kids.length; i++) {\n const child = kids[i];\n if (!child) continue;\n if (child.nodeType === 3 /* TEXT_NODE */) {\n out += child.textContent || \"\";\n } else if (child.nodeType === 1 /* ELEMENT_NODE */) {\n if (NON_LABEL_SUBTREES.has((child as Element).tagName.toLowerCase())) continue;\n out += \" \";\n walk(child);\n out += \" \";\n }\n }\n };\n walk(el);\n return collapseWs(out);\n}\n\n/**\n * The element's OWN label — immediate text nodes plus the text of inline\n * label wrappers (<span> etc.) directly inside it. Deliberately does NOT\n * descend into block or interactive children, so a wrapper's nested\n * controls/headings never leak in. \"\" when the element carries no label\n * of its own.\n */\nfunction directLabel(el: Element): string {\n let out = \"\";\n const kids = el.childNodes;\n for (let i = 0; i < kids.length; i++) {\n const child = kids[i];\n if (!child) continue;\n if (child.nodeType === 3 /* TEXT_NODE */) {\n out += \" \" + (child.textContent || \"\");\n } else if (\n child.nodeType === 1 /* ELEMENT_NODE */ &&\n INLINE_LABEL_TAGS.has((child as Element).tagName.toLowerCase())\n ) {\n out += \" \" + boundaryText(child as Element);\n }\n }\n return collapseWs(out);\n}\n\n/**\n * Resolve the visible label for a clicked control WITHOUT mashing nested\n * controls or content blocks together.\n *\n * - A \"simple\" control — no nested interactive element, no nested heading\n * — yields its boundary-spaced text. Covers the everyday case:\n * `<button>Sign up</button>`, `<a>Pricing</a>`,\n * `<button><svg/><span>Save</span></button>` → \"Save\".\n * - A \"container\" — a control wrapping OTHER controls or a content block —\n * would otherwise collapse to \"Log inContinue with GoogleContinue with\n * Apple…\" or \"Tudo que você é,em um só link.Portfolio…\". For these we\n * use, in order: the element's own direct label → the first heading\n * inside it (the human-recognisable name of the block) → \"\". Returning\n * \"\" lets extractText fall through to title / img alt / svg title, and\n * finally to the selector — honest, not garbage.\n */\nfunction visibleLabel(el: Element): string {\n const isContainer = el.querySelector(CONTAINER_MARKERS) !== null;\n if (!isContainer) return boundaryText(el);\n\n const direct = directLabel(el);\n if (direct) return direct;\n\n const heading = el.querySelector(HEADING_SELECTOR);\n if (heading) {\n const h = boundaryText(heading);\n if (h) return h;\n }\n return \"\";\n}\n\n/**\n * Compute the best human-readable label for a clicked element.\n *\n * Precedence (highest → lowest):\n * 1. data-cd-track / data-track / data-testid — explicit dev label\n * 2. aria-label — accessible name (most icon-only buttons set this)\n * 3. aria-labelledby — references another element by ID\n * 4. <input value=\"…\"> — for submit/button inputs\n * 5. visible textContent — the most common case\n * 6. title attribute — tooltip fallback\n * 7. <img alt=\"…\"> inside the element — for image-only buttons\n * 8. <svg><title>…</title></svg> inside the element — for SVG icons\n * that include an accessible name\n *\n * Returns \"\" only when the element is truly anonymous (no label, no\n * text, no inner image/icon with a name). The journey UI falls back\n * to the selector when this returns empty, so any source we can lift\n * a name from beats the CSS-homework display.\n *\n * Whitespace is always collapsed — \" Sign\\n up \" → \"Sign up\".\n */\nfunction extractText(el: Element): string {\n const clean = (s: string): string => s.replace(/\\s+/g, \" \").trim();\n\n // 1. Explicit dev-supplied labels. data-cd-track is the Crossdeck-\n // canonical attribute; data-track and data-testid are the\n // common community standards we accept as synonyms.\n const explicit =\n el.getAttribute(\"data-cd-track\") ||\n el.getAttribute(\"data-track\") ||\n el.getAttribute(\"data-testid\");\n if (explicit) {\n const t = clean(explicit);\n if (t) return t;\n }\n\n // 2. Aria-label — the ARIA accessible-name primary source.\n const aria = el.getAttribute(\"aria-label\");\n if (aria) {\n const t = clean(aria);\n if (t) return t;\n }\n\n // 3. Aria-labelledby — resolve referenced element's text. The ID\n // list can have multiple tokens; concatenate referents (ARIA spec).\n const labelledBy = el.getAttribute(\"aria-labelledby\");\n if (labelledBy && typeof el.ownerDocument?.getElementById === \"function\") {\n const parts: string[] = [];\n for (const id of labelledBy.split(/\\s+/)) {\n const ref = el.ownerDocument.getElementById(id);\n const t = ref?.textContent ? clean(ref.textContent) : \"\";\n if (t) parts.push(t);\n }\n if (parts.length > 0) return parts.join(\" \");\n }\n\n // 4. Input value (submit/button inputs render their label here).\n if (el instanceof HTMLInputElement && el.value) {\n const t = clean(el.value);\n if (t) return t;\n }\n\n // 5. Visible text — the element's OWN label, never a mash of nested\n // controls or content blocks. extractText used to return\n // clean(el.textContent) here, which on a control that WRAPS other\n // things collapses the whole subtree into one string:\n // \"Log inContinue with GoogleContinue with Apple…\" (an auth card\n // around three buttons) or \"Tudo que você é,em um só link.Portfolio\n // …\" (a hero <a> around an <h1>+<p>+<ul>). visibleLabel() resolves a\n // faithful single-control label instead. See its doc comment.\n const text = visibleLabel(el);\n if (text) return text;\n\n // 6. Title attribute (tooltip-style accessible name).\n const title = el.getAttribute(\"title\");\n if (title) {\n const t = clean(title);\n if (t) return t;\n }\n\n // 7. <img alt=\"…\"> within — common for image-only buttons. Prefer\n // a non-empty alt; treat alt=\"\" (decorative) as no signal.\n const img = el.querySelector(\"img[alt]\");\n if (img) {\n const alt = img.getAttribute(\"alt\") ?? \"\";\n const t = clean(alt);\n if (t) return t;\n }\n\n // 8. SVG <title> child — the conventional way to ship an accessible\n // name on inline icons.\n const svgTitle = el.querySelector(\"svg title\");\n if (svgTitle?.textContent) {\n const t = clean(svgTitle.textContent);\n if (t) return t;\n }\n\n return \"\";\n}\n\nfunction trimText(s: string, cap: number): string {\n if (s.length <= cap) return s;\n return s.slice(0, cap - 1) + \"…\";\n}\n\n/**\n * Build a stable CSS selector path for the element. Used for\n * server-side dedup (\"clicked the same button on the same page\")\n * and for replay / heatmap reconstruction. Walks up to the body or\n * an element with an id, whichever comes first. Caps the depth at\n * 5 to keep selectors short and human-readable.\n */\nfunction buildSelector(el: Element): string {\n const parts: string[] = [];\n let cur: Element | null = el;\n let depth = 0;\n while (cur && cur.nodeName.toLowerCase() !== \"body\" && depth < 5) {\n let part = cur.nodeName.toLowerCase();\n if (cur.id) {\n parts.unshift(`${part}#${cur.id}`);\n break; // ID is unique-enough — stop walking up\n }\n if (cur.classList.length > 0) {\n const cls = Array.from(cur.classList)\n .filter((c) => !c.startsWith(\"cd-\")) // skip our own marker classes\n .slice(0, 2)\n .join(\".\");\n if (cls) part += `.${cls}`;\n }\n parts.unshift(part);\n cur = cur.parentElement;\n depth++;\n }\n return parts.join(\" > \");\n}\n\nfunction collectDataAttrs(el: Element): Record<string, string> {\n // Pull every data-* attribute off the element. Devs use these\n // for explicit tagging — `data-cd-prop-plan=\"pro\"` becomes a\n // property on the event so you can filter conversions by plan.\n const out: Record<string, string> = {};\n if (!(el instanceof HTMLElement)) return out;\n for (const name of el.getAttributeNames()) {\n if (!name.startsWith(\"data-\")) continue;\n if (name === \"data-cd-noTrack\" || name === \"data-cd-no-track\") continue;\n if (name === \"data-cd-event\") continue; // used as event name, not prop\n const value = el.getAttribute(name) || \"\";\n // Normalise data-cd-prop-plan → \"plan\" key on properties\n const key = name.replace(/^data-cd-prop-/, \"\").replace(/^data-/, \"\");\n out[key] = value;\n }\n return out;\n}\n\n/**\n * Browser detection identical to device-info.ts isBrowser. Inlined here\n * so this module has zero internal imports — easier to tree-shake\n * out of Node-only consumers, and the function body is trivial.\n */\nfunction isBrowserSafe(): boolean {\n return (\n typeof (globalThis as { window?: unknown }).window !== \"undefined\" &&\n typeof (globalThis as { document?: unknown }).document !== \"undefined\"\n );\n}\n\nfunction mintSessionId(): string {\n // Inline the same shape used elsewhere — `<prefix>_<base32-ts><10-char-rand>`.\n const ts = Date.now().toString(36);\n return `sess_${ts}${randomChars(10)}`;\n}\n\n/**\n * Read first-touch acquisition signals off the current page. Captures:\n * - utm_source, utm_medium, utm_campaign, utm_content, utm_term\n * (the standard Google Analytics campaign params)\n * - referrer (full URL — backend extracts hostname for grouping)\n *\n * Returns empty strings outside a browser, before navigation, or when\n * the page has none of these signals. Never throws — a malformed URL\n * or an iframe with no document.referrer falls through to empty.\n *\n * Pure function. Exported for unit testing acquisition extraction.\n */\nexport function captureAcquisition(): SessionAcquisition {\n if (!isBrowserSafe()) return { ...EMPTY_ACQUISITION };\n\n const result: SessionAcquisition = { ...EMPTY_ACQUISITION };\n\n try {\n const w = (globalThis as { window: Window }).window;\n const params = new URLSearchParams(w.location.search ?? \"\");\n result.utm_source = params.get(\"utm_source\") ?? \"\";\n result.utm_medium = params.get(\"utm_medium\") ?? \"\";\n result.utm_campaign = params.get(\"utm_campaign\") ?? \"\";\n result.utm_content = params.get(\"utm_content\") ?? \"\";\n result.utm_term = params.get(\"utm_term\") ?? \"\";\n // Paid-traffic click IDs — captured alongside UTMs because many\n // ad platforms (Performance Max, automated bidding) ship ONLY\n // a click-id, no utm_*.\n result.gclid = params.get(\"gclid\") ?? \"\";\n result.fbclid = params.get(\"fbclid\") ?? \"\";\n result.msclkid = params.get(\"msclkid\") ?? \"\";\n result.ttclid = params.get(\"ttclid\") ?? \"\";\n result.li_fat_id = params.get(\"li_fat_id\") ?? \"\";\n result.twclid = params.get(\"twclid\") ?? \"\";\n } catch {\n // window.location can throw in sandboxed iframes / data: URLs\n }\n\n try {\n const doc = (globalThis as { document: Document }).document;\n if (typeof doc.referrer === \"string\") result.referrer = doc.referrer;\n } catch {\n // document.referrer is well-supported but defensive in case\n }\n\n return result;\n}\n","/**\n * Debug signal vocabulary per NorthStar §16.\n *\n * The SDK speaks a small fixed vocabulary of signals so the dashboard's\n * onboarding checklist can show \"we saw your first event\" without having\n * to parse free-form console output. When debug mode is enabled the\n * signals are also logged to the console so a developer doing\n * 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 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 loud,\n // regardless of debug mode — see the console.error in crossdeck.ts.\n | \"sdk.flush_permanent_failure\"\n // Emitted when the server PARKS the SDK (HTTP 426 / sdk_version_unsupported):\n // the wire dialect is too old. Distinct from flush_permanent_failure —\n // events are HELD on-device (not dropped) and deliver on upgrade. The\n // second signal channel (paired with the queue's one console line); the\n // dashboard reads it to render the amber \"update to resume\" advisory.\n | \"sdk.parked\"\n | \"sdk.consent_changed\"\n | \"sdk.consent_denied\"\n | \"sdk.consent_dnt_applied\"\n | \"sdk.pii_scrubbed\";\n\nexport interface DebugContext {\n /** Free-form details surfaced under the signal — appId, key prefix, etc. */\n [key: string]: unknown;\n}\n\n/**\n * Names that almost always indicate PII or secret data. Used by track()\n * to warn the developer when a property key looks dangerous. Per\n * NorthStar §15 these are reject/warn-on-sight values; we warn rather\n * than reject because the developer might genuinely want a property\n * 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]\"`.\n * - Cap total serialised size at `maxBatchPropertyBytes` (default 8192).\n * Past the cap, properties are dropped (newest first) and a\n * `__truncated` marker is added.\n * - Reserved keys (`sessionId`, `referrer`, `utm_*`, anything with\n * `__` prefix) are NOT validated specially — they're treated like\n * any other property. The SDK's own enrichment goes through this\n * same path so it gets the same safety guarantees.\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 /** Sanitised properties safe to JSON.stringify. */\n properties: EventProperties;\n /** Per-issue warnings — surface in debug mode or diagnostics. */\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-stack circular detection: add the object/array to `seen`\n // before recursing into its children, REMOVE it after. A re-encounter\n // while a value is still in the set means it's an ancestor of the\n // current node (a real cycle). Sibling sharing — two properties\n // pointing at the same sub-object (a legitimate DAG, e.g. an enriched\n // event with `{ user: shared, owner: shared }`) — is NOT a cycle and\n // must NOT be flagged. Pre-fix the WeakSet was add-on-visit /\n // never-delete, so the second sibling visit always tripped the\n // `[circular]` branch and silently dropped real data with a\n // misleading warning. Audit P1 #18.\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 // Strip NaN / ±Infinity — they JSON-stringify as null which is\n // surprising. Convert to null explicitly with a warning.\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 // Objects from here on.\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; only true cycles\n // (parent re-encountered via a descendant) should flag.\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 // Delete on exit — see the array branch above for rationale.\n seen.delete(obj);\n return { keep: true, value: out };\n }\n\n // Unknown exotic type — try string coercion.\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 // We use byteLength via UTF-8 since the wire is JSON-over-HTTPS.\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 // Fallback for runtimes without TextEncoder (very old): assume\n // worst-case 4 bytes/char to stay conservative.\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 ONCE\n * via `Crossdeck.register({ plan: \"pro\" })` that get attached to every\n * subsequent event of that SDK instance. They're the single most-used\n * feature in Mixpanel-style analytics: \"every event from this user\n * should have `plan` and `appVersion` on it\" instead of remembering to\n * pass them on every track() call.\n *\n * **Groups** are organisational identifiers: a customer might belong to\n * an `org` (\"acme\"), a `team` (\"design\"), and a `plan` (\"enterprise\").\n * Each event carries `$groups.{type}: id` so B2B dashboards can pivot:\n * \"Acme's team:design has fired 142 paywall_shown events this week\".\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 reloads via the same storage layer the SDK\n * uses for identity (localStorage + cookie redundancy doesn't make\n * sense here — these are larger and live longer; localStorage only\n * is fine).\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\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\n constructor(\n private readonly storage: KeyValueStorage,\n private readonly prefix: string,\n ) {\n this.superProps = readJson(storage, prefix + KEY_SUPER) ?? {};\n this.groups = readJson(storage, prefix + KEY_GROUPS) ?? {};\n }\n\n // ---------- super properties ----------\n\n /**\n * Merge new keys into the super-property bag. Returns a snapshot of\n * the resulting bag. Values that are `null` are deleted (Mixpanel\n * semantics — explicit null = \"stop tracking this key\").\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 writeJson(this.storage, 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 writeJson(this.storage, 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 membership\n * for that group type — the SDK stops attaching it to 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 writeJson(this.storage, this.prefix + KEY_GROUPS, this.groups);\n }\n\n /**\n * Snapshot of the current groups map, keyed by group type. Returned\n * shape mirrors what the SDK attaches to every event as\n * `$groups.{type}`. The `traits` sub-object is the most-recent\n * traits payload passed to `setGroup` for that type; null when none.\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. Stable\n * for fast every-event merge — we don't want to JSON-clone on each\n * 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 try {\n this.storage.removeItem(this.prefix + KEY_SUPER);\n } catch {\n // ignore\n }\n try {\n this.storage.removeItem(this.prefix + KEY_GROUPS);\n } catch {\n // ignore\n }\n }\n}\n\nfunction readJson<T>(storage: KeyValueStorage, key: string): T | null {\n let raw: string | null;\n try {\n raw = storage.getItem(key);\n } catch {\n return null;\n }\n if (!raw) return null;\n try {\n return JSON.parse(raw) as T;\n } catch {\n return null;\n }\n}\n\nfunction writeJson(storage: KeyValueStorage, key: string, value: unknown): void {\n try {\n storage.setItem(key, JSON.stringify(value));\n } catch {\n // Quota / private mode — silent degrade. In-memory still holds\n // the current state; cross-tab sync just loses fidelity.\n }\n}\n","/**\n * Internal-traffic browser opt-out.\n *\n * Visiting any tracked page with `?crossdeck_internal=1` persists a flag in\n * localStorage; every event from this browser is then tagged with the\n * reserved `$crossdeck_internal` property, which ingest reads to classify\n * the event as internal. `?crossdeck_internal=0` clears it.\n *\n * This covers the cases identity- and IP-based rules miss: a dynamic home\n * IP, or browsing your own product logged out. It's per-browser and\n * self-service — no dashboard change required to set or clear it.\n *\n * Design: the URL is parsed ONCE at init (processInternalOptOutUrl) to\n * set/clear the persisted flag; each event then does a cheap localStorage\n * read (isInternalOptOut) so clearing takes effect immediately. Everything\n * is wrapped — SSR / Node / private-mode / disabled-storage all degrade to\n * \"not opted out\", never a throw.\n */\n\n/**\n * The reserved property key stamped on every event while opted out. Must\n * match the backend contract (backend/src/api/lib/actor-type.ts:\n * INTERNAL_OPT_OUT_PROPERTY). `$`-prefixed so it can't collide with a\n * developer's own property names.\n */\nexport const INTERNAL_OPT_OUT_PROPERTY = \"$crossdeck_internal\";\n\nconst STORAGE_KEY = \"crossdeck.internalOptOut\";\n\nfunction localStore(): Storage | null {\n try {\n return typeof localStorage !== \"undefined\" ? localStorage : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Run once at init. A `?crossdeck_internal=1` (or `=true`) in the current\n * URL persists the opt-out flag; `=0` (or `=false`) clears it. No param →\n * leaves the existing persisted state untouched.\n */\nexport function processInternalOptOutUrl(): void {\n try {\n const search = typeof location !== \"undefined\" ? location.search : \"\";\n const params = new URLSearchParams(search || \"\");\n if (!params.has(\"crossdeck_internal\")) return;\n const store = localStore();\n if (!store) return;\n const v = params.get(\"crossdeck_internal\");\n if (v === \"1\" || v === \"true\") {\n store.setItem(STORAGE_KEY, \"1\");\n } else if (v === \"0\" || v === \"false\") {\n store.removeItem(STORAGE_KEY);\n }\n } catch {\n /* no-op — never let opt-out handling break init */\n }\n}\n\n/** Cheap per-event check: is this browser currently opted out? */\nexport function isInternalOptOut(): boolean {\n try {\n return localStore()?.getItem(STORAGE_KEY) === \"1\";\n } catch {\n return false;\n }\n}\n","/**\n * Web Vitals capture — LCP, INP, CLS, FCP, TTFB.\n *\n * Why hand-rolled, not the `web-vitals` library: that library is ~6 KB\n * gz and pulls in handlers for every metric ever published by Google,\n * many of which (FID, soft-navigation, etc.) are deprecated or\n * superseded. The five metrics below are the ones every dashboard\n * actually renders — LCP for perceived load, INP for responsiveness,\n * CLS for visual stability, FCP for first paint, TTFB for backend\n * speed. Total here is ~80 lines, zero runtime deps.\n *\n * Each metric fires as a Crossdeck event:\n * `webvitals.lcp` → properties: { valueMs }\n * `webvitals.inp` → properties: { valueMs }\n * `webvitals.cls` → properties: { value } // unitless score\n * `webvitals.fcp` → properties: { valueMs }\n * `webvitals.ttfb` → properties: { valueMs }\n *\n * Capture timing:\n * - FCP, TTFB fire once after the page settles (typically <1s).\n * - LCP, CLS fire at page hidden (visibilitychange→hidden) — the\n * final value is only known when the user stops interacting\n * with the page.\n * - INP samples interactions and fires at page hidden.\n *\n * No-op outside browsers (PerformanceObserver missing) or when the\n * `autoTrack.webVitals` flag is false.\n */\n\ntype Reporter = (name: string, properties: Record<string, unknown>) => void;\n\nexport interface WebVitalsConfig {\n enabled: boolean;\n /**\n * Cap on the number of metric events emitted per page. Defaults to\n * one per metric type (so 5 max). Defends against a rogue browser\n * firing 100 PerformanceObserver entries.\n */\n maxEventsPerMetric?: number;\n}\n\nexport class WebVitalsTracker {\n private observers: PerformanceObserver[] = [];\n private flushed = new Set<string>();\n private cls = 0;\n private clsEntries: PerformanceEntry[] = [];\n private inp = 0;\n private cleanups: Array<() => void> = [];\n\n constructor(\n private readonly cfg: WebVitalsConfig,\n private readonly report: Reporter,\n ) {}\n\n install(): void {\n if (!this.cfg.enabled) return;\n if (typeof PerformanceObserver === \"undefined\") return;\n if (typeof globalThis === \"undefined\" || !(\"document\" in globalThis)) return;\n\n const doc = (globalThis as { document: Document }).document;\n\n // TTFB / FCP — fire as soon as we have data.\n try {\n const navObserver = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n const e = entry as PerformanceNavigationTiming;\n if (e.responseStart > 0 && !this.flushed.has(\"ttfb\")) {\n this.flushed.add(\"ttfb\");\n this.report(\"webvitals.ttfb\", { valueMs: Math.round(e.responseStart - e.startTime) });\n }\n }\n });\n navObserver.observe({ type: \"navigation\", buffered: true });\n this.observers.push(navObserver);\n } catch {\n // not supported — fall through\n }\n\n try {\n const paintObserver = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n if (entry.name === \"first-contentful-paint\" && !this.flushed.has(\"fcp\")) {\n this.flushed.add(\"fcp\");\n this.report(\"webvitals.fcp\", { valueMs: Math.round(entry.startTime) });\n }\n }\n });\n paintObserver.observe({ type: \"paint\", buffered: true });\n this.observers.push(paintObserver);\n } catch {\n // not supported\n }\n\n // LCP — track the LATEST entry; flush at page hidden.\n let lcpValue = 0;\n try {\n const lcpObserver = new PerformanceObserver((list) => {\n const entries = list.getEntries();\n const last = entries[entries.length - 1];\n if (last) lcpValue = last.startTime;\n });\n lcpObserver.observe({ type: \"largest-contentful-paint\", buffered: true });\n this.observers.push(lcpObserver);\n } catch {\n // not supported\n }\n\n // CLS — accumulate per-session layout-shift score.\n try {\n const clsObserver = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n // Layout-shift entries have a `value` field (cast loosely\n // since the typed DOM lib doesn't always expose it).\n const e = entry as PerformanceEntry & { value?: number; hadRecentInput?: boolean };\n if (typeof e.value === \"number\" && !e.hadRecentInput) {\n this.cls += e.value;\n this.clsEntries.push(entry);\n }\n }\n });\n clsObserver.observe({ type: \"layout-shift\", buffered: true });\n this.observers.push(clsObserver);\n } catch {\n // not supported\n }\n\n // INP — find the worst-case interaction duration.\n try {\n const eventObserver = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n const e = entry as PerformanceEntry & { duration: number; interactionId?: number };\n if (e.interactionId && e.duration > this.inp) {\n this.inp = e.duration;\n }\n }\n });\n // The `event` type is the modern way; `first-input` is a fallback.\n // Wrapped in try because Safari < 16.4 throws on `event` type.\n try {\n eventObserver.observe({ type: \"event\", buffered: true, durationThreshold: 16 } as PerformanceObserverInit);\n } catch {\n eventObserver.observe({ type: \"first-input\", buffered: true });\n }\n this.observers.push(eventObserver);\n } catch {\n // not supported\n }\n\n // Flush LCP / CLS / INP at page-hidden — the final values are only\n // known after the user stops interacting.\n const flush = (): void => {\n if (lcpValue > 0 && !this.flushed.has(\"lcp\")) {\n this.flushed.add(\"lcp\");\n this.report(\"webvitals.lcp\", { valueMs: Math.round(lcpValue) });\n }\n if (this.cls > 0 && !this.flushed.has(\"cls\")) {\n this.flushed.add(\"cls\");\n this.report(\"webvitals.cls\", { value: Math.round(this.cls * 1000) / 1000 });\n }\n if (this.inp > 0 && !this.flushed.has(\"inp\")) {\n this.flushed.add(\"inp\");\n this.report(\"webvitals.inp\", { valueMs: Math.round(this.inp) });\n }\n };\n const onHidden = (): void => {\n if (doc.visibilityState === \"hidden\") flush();\n };\n doc.addEventListener(\"visibilitychange\", onHidden);\n (globalThis as { window: Window }).window.addEventListener(\"pagehide\", flush);\n this.cleanups.push(() => {\n doc.removeEventListener(\"visibilitychange\", onHidden);\n (globalThis as { window: Window }).window.removeEventListener(\"pagehide\", flush);\n });\n }\n\n uninstall(): void {\n for (const o of this.observers) {\n try {\n o.disconnect();\n } catch {\n // ignore\n }\n }\n this.observers = [];\n for (const fn of this.cleanups.splice(0)) {\n try {\n fn();\n } catch {\n // ignore\n }\n }\n }\n}\n","/**\n * Consent gating — GDPR / CCPA-grade kill switches.\n *\n * Three independent dimensions, each defaulting to \"granted\" but\n * runtime-overridable:\n *\n * analytics — track(), identify(), heartbeat(), session/page auto-\n * emissions. Off → events drop silently, no network\n * calls fire.\n * marketing — paid-traffic click IDs (gclid/fbclid/etc) and\n * acquisition referrer URL. Off → these get scrubbed\n * before they ever land in the event bag.\n * errors — error / breadcrumb / Web Vitals capture. Off → no\n * webvitals.* events emitted, no error reporting (when\n * Phase 3 errors land).\n *\n * Why this granularity: real consent banners offer \"Analytics\",\n * \"Marketing\", \"Functional\" as separate boxes. The SDK has to match.\n *\n * Default state: every dimension is granted. The developer must\n * explicitly call `Crossdeck.consent({ analytics: false })` before\n * the first event to opt OUT — same convention as Google Tag Manager\n * Consent Mode. To start in deny mode, call `init(...)` then\n * immediately `consent({ analytics: false, marketing: false, errors:\n * false })` before any user activity.\n *\n * DNT (Do Not Track) browser header is checked once at init and\n * applied as an automatic deny across all dimensions when\n * `respectDnt: true` is set in CrossdeckOptions (default false because\n * the industry has effectively deprecated DNT — but opt-in support\n * is the polite default for privacy-first apps).\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 private dntDenied = false;\n\n constructor(options?: { respectDnt?: boolean }) {\n if (options?.respectDnt && this.detectDnt()) {\n this.dntDenied = true;\n this.state = { analytics: false, marketing: false, errors: false };\n }\n }\n\n /**\n * Merge new dimensions onto the current state. Returns the resulting\n * snapshot. DNT-derived denies cannot be flipped back on by a `set`\n * call — once the browser says \"don't track\", we don't track even if\n * the developer code disagrees. That's the contract.\n */\n set(partial: Partial<ConsentState>): ConsentState {\n if (this.dntDenied) return { ...this.state };\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 /** Snapshot of the current state. */\n get(): ConsentState {\n return { ...this.state };\n }\n\n /** Convenience getters for hot paths. */\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 /** True iff the constructor detected and applied DNT. */\n get isDntDenied(): boolean {\n return this.dntDenied;\n }\n\n private detectDnt(): boolean {\n try {\n const nav = (globalThis as { navigator?: Navigator }).navigator;\n if (!nav) return false;\n // Three historical spellings: navigator.doNotTrack (standard),\n // navigator.msDoNotTrack (IE), window.doNotTrack (Safari).\n // All return \"1\" / \"yes\" when the user has DNT enabled.\n const sources = [\n (nav as Navigator & { doNotTrack?: string }).doNotTrack,\n (nav as Navigator & { msDoNotTrack?: string }).msDoNotTrack,\n (globalThis as { doNotTrack?: string }).doNotTrack,\n ];\n return sources.some((v) => v === \"1\" || v === \"yes\");\n } catch {\n return false;\n }\n }\n}\n\n// ============================================================\n// PII scrubbing — URL + property values\n// ============================================================\n\n/**\n * Email-shaped pattern. Reasonably restrictive — matches RFC 5322's\n * \"obs-local-part\" common case (the practical 99% of emails). We\n * deliberately don't try to match every legal email; the goal is\n * \"if it looks like an email, scrub it\" without false positives.\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 that\n * could be split by space or hyphen — the format every payment form\n * accepts. We don't validate Luhn; this is best-effort scrubbing,\n * not card-data tokenisation. If you're handling actual PAN data\n * you should not be passing it through analytics in the first place.\n */\n// Anchor on a digit at both ends so trailing separators (space / hyphen)\n// aren't pulled into the match — otherwise \"4242 4242 4242 4242 today\"\n// scrubs as \"<card>today\" instead of \"<card> today\".\nconst CARD_PATTERN = /\\b\\d(?:[ -]?\\d){12,18}\\b/g;\n\n// Sentinel tokens — aligned with backend/src/api/lib/scrub.ts which uses\n// <email>, <card>, <uuid>, <cdcust>, <crossdeck_secret_key>, <aws_access_key>.\n// Mismatched tokens between the SDK scrub and the backend's defence-in-\n// depth scrub would split dashboard aggregation (the same event arriving\n// from two paths would carry 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: we call `.replace()` unconditionally rather than\n * gating on `.test()`. The /g regexes are module-level so `.test()`\n * carries `lastIndex` state between calls — a prior match leaves\n * `lastIndex` mid-string and the next `.test()` can falsely return\n * false on a string that DOES match. `.replace(/g)` always scans the\n * full string regardless of `lastIndex`, so dropping the test-guard\n * removes the sharp edge at zero cost (when nothing matches, replace\n * returns the same `(===)` string).\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 * Pre-fix this was the SDK's #1 PII leak vector — every captured-error\n * report ships nested `frames[]` / `breadcrumbs[]` / `context{}` /\n * `http{}` shapes through here. Date / Map / Set / Error / class\n * instances pass through untouched (those are the\n * `validateEventProperties` sanitiser's job — this is the PII regex\n * 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 the downstream error reporter.\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\" (page views, clicks,\n * custom events, network calls, console logs). When an error fires,\n * attach the buffer so the engineer reading the error can see exactly\n * how the user got into the broken state. The single most powerful\n * debugging signal in error monitoring — without breadcrumbs, errors\n * are stack traces 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 cover\n * ~5 minutes of typical user activity without ballooning the error\n * payload — Sentry uses 100 by default but the SDK is more aggressive\n * about size since we ship breadcrumbs over the wire with every error,\n * not as a separate batch.\n *\n * Privacy: breadcrumbs auto-emit from the same auto-tracking sources\n * as analytics events (page.viewed, element.clicked). Those already\n * skip password fields, form inputs, and cd-noTrack subtrees. Custom\n * crumbs added via Crossdeck.addBreadcrumb() pass through the same\n * property 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\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/**\n * Reliability endpoint URL — `/v1/events`, the SAME endpoint customer\n * `cd.track()` calls use. Contract failures land as ordinary\n * `crossdeck.contract_failed` events in the Crossdeck reliability\n * project's events warehouse (resolved via the hardcoded reliability\n * publishable key below). They surface in the Crossdeck team's\n * events-explorer alongside any other custom event on that project\n * — no separate dashboard surface required.\n *\n * Pre-2026-05-28 this pointed at `/v1/sdk/diagnostic`, a Crossdeck-\n * specific endpoint that wrote to a top-level `sdkDiagnostics`\n * collection. That endpoint has no dashboard renderer; failures\n * piled up invisibly. Routing through `/v1/events` reuses the\n * generic custom-event pipeline + UI any customer would have for\n * their own track() calls.\n */\nexport const DIAGNOSTIC_TELEMETRY_ENDPOINT =\n \"https://api.cross-deck.com/v1/events\";\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. Anything outside this set is dropped at the\n * call site so a future caller can't accidentally widen the wire\n * shape.\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 // verification_phase is set by the runtime contract verifier layer\n // (sdks/web/src/_contract-verifiers.ts) — values `boot` / `hot_path`.\n // Absent for failures emitted by external test harnesses\n // (XCTestObservation, Vitest hooks, JUnit watchers) which carry\n // test_file + test_name instead. See contracts/diagnostics/\n // contract-failed-payload-schema-lock.json.\n \"verification_phase\",\n]);\n\n/**\n * Whitelist filter — even if a caller threads a forbidden key\n * (anonymousId, ip, etc.) through, it never hits the wire. The\n * backend would reject it anyway; this is defence in depth.\n *\n * Exported so unit tests can verify the schema-lock without needing\n * to monkey-patch fetch or wait for the reliability endpoint to be\n * provisioned.\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\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 * @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 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 // Wrap as the /v1/events batch shape — single event per fire,\n // `name: \"crossdeck.contract_failed\"`, all schema-lock fields as\n // properties. The reliability publishable key resolves to the\n // Crossdeck reliability project server-side; the event lands in\n // that project's events warehouse and surfaces in its\n // events-explorer like any other custom event.\n const body = JSON.stringify({\n events: [\n {\n name: \"crossdeck.contract_failed\",\n timestamp: Date.now(),\n properties: filtered,\n },\n ],\n });\n\n // Browser path: fetch with keepalive so the request survives a\n // page-unload that fires immediately after the call. Node-only\n // builds never hit this branch (the SDK selects `crossdeck-server`\n // for that runtime). If fetch is unavailable (older WebViews), the\n // request is silently dropped — operational telemetry is not\n // worth surfacing in a customer-facing error.\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 keepalive: true,\n // No credentials, no cache, no referrer — the reliability\n // endpoint is the same origin only in tests. In production the\n // browser never carries anything beyond the request body and\n // the Authorization header we set explicitly.\n credentials: \"omit\",\n cache: \"no-store\",\n referrerPolicy: \"no-referrer\",\n }).catch(() => {\n // Fire-and-forget; we never want a rejection bubbling into the\n // host app's unhandledrejection handler.\n });\n } catch {\n // Swallow synchronous throws (CSP block, immediate failure).\n }\n}\n","/**\n * Machine-readable index of every error code the SDK can throw, with\n * a short description and a hint on what action to take. Published\n * verbatim as `crossdeck-error-codes.json` in the npm tarball so AI\n * integration assistants, error-aggregator dashboards (Sentry,\n * DataDog), and the Crossdeck dashboard can render human-friendly\n * messages without parsing freeform `message` strings.\n *\n * Stripe publishes the same surface at stripe.com/docs/error-codes;\n * developers love it because every code has a canonical \"what does\n * this mean / what should I do\" answer.\n *\n * Adding a new error code:\n * 1. Add the code string to the union in `errors.ts` (where used).\n * 2. Add an entry here.\n * 3. The next `npm run build` regenerates the JSON sidecar.\n *\n * Keep entries terse — the consumer surfaces this in tooltips and\n * automated tickets, not in long-form docs.\n */\n\nexport interface ErrorCodeEntry {\n /** The string thrown as CrossdeckError.code. */\n code: string;\n /** CrossdeckError.type — broad category. */\n type:\n | \"authentication_error\"\n | \"permission_error\"\n | \"invalid_request_error\"\n | \"rate_limit_error\"\n | \"version_error\"\n | \"internal_error\"\n | \"network_error\"\n | \"configuration_error\";\n /** One-sentence description. Surfaced verbatim in dashboards. */\n description: string;\n /** What the developer should do. Imperative phrasing. */\n resolution: string;\n /** True for codes the SDK can auto-recover from (no developer action). */\n retryable: boolean;\n}\n\nexport const CROSSDECK_ERROR_CODES: readonly ErrorCodeEntry[] = Object.freeze([\n // ----- Configuration -----\n {\n code: \"invalid_public_key\",\n type: \"configuration_error\",\n description: \"The publishable key passed to Crossdeck.init() doesn't start with cd_pub_.\",\n resolution: \"Copy the key from your Crossdeck dashboard → API keys page.\",\n retryable: false,\n },\n {\n code: \"missing_app_id\",\n type: \"configuration_error\",\n description: \"Crossdeck.init() was called without an appId.\",\n resolution: \"Add appId to your init options — find it in the dashboard's Apps page.\",\n retryable: false,\n },\n {\n code: \"invalid_environment\",\n type: \"configuration_error\",\n description: \"Crossdeck.init() requires environment: 'production' | 'sandbox'.\",\n resolution: \"Pass the literal string \\\"production\\\" or \\\"sandbox\\\" — no other values are accepted.\",\n retryable: false,\n },\n {\n code: \"environment_mismatch\",\n type: \"configuration_error\",\n description: \"The publishable key's env prefix doesn't match the declared environment option.\",\n resolution: \"Either change `environment` to match the key prefix (cd_pub_test_ ↔ sandbox, cd_pub_live_ ↔ production), or swap the key for one minted in the right env.\",\n retryable: false,\n },\n {\n code: \"not_initialized\",\n type: \"configuration_error\",\n description: \"An SDK method was called before Crossdeck.init().\",\n resolution: \"Call Crossdeck.init({ appId, publicKey, environment }) once at app startup before any other method.\",\n retryable: false,\n },\n\n // ----- Identify / track / purchase argument validation -----\n {\n code: \"missing_user_id\",\n type: \"invalid_request_error\",\n description: \"identify() was called with an empty userId.\",\n resolution: \"Pass a stable, non-empty user identifier from your auth layer — never a hardcoded placeholder.\",\n retryable: false,\n },\n {\n code: \"missing_event_name\",\n type: \"invalid_request_error\",\n description: \"track() was called without an event name.\",\n resolution: \"Pass a non-empty string as the first argument.\",\n retryable: false,\n },\n {\n code: \"missing_group_type\",\n type: \"invalid_request_error\",\n description: \"group() was called without a group type.\",\n resolution: \"Pass a non-empty type (e.g. \\\"org\\\", \\\"team\\\") as the first argument.\",\n retryable: false,\n },\n {\n code: \"missing_signed_transaction_info\",\n type: \"invalid_request_error\",\n description: \"syncPurchases() was called without StoreKit 2 signed transaction info.\",\n resolution: \"Pass the JWS string from Transaction.currentEntitlements / Transaction.updates.\",\n retryable: false,\n },\n\n // ----- Network / transport -----\n {\n code: \"fetch_failed\",\n type: \"network_error\",\n description: \"The underlying fetch() call failed (typically a network outage or DNS issue).\",\n resolution: \"Check the user's network. The SDK will retry automatically with exponential backoff.\",\n retryable: true,\n },\n {\n code: \"request_timeout\",\n type: \"network_error\",\n description: \"A request was aborted after the configured timeoutMs (default 15s).\",\n resolution: \"Check the user's connection. Increase timeoutMs in init options if the user is on a known-slow network.\",\n retryable: true,\n },\n {\n code: \"invalid_json_response\",\n type: \"internal_error\",\n description: \"The server returned a 2xx with an unparseable body.\",\n resolution: \"Likely a transient backend bug. Retry; if it persists, contact support with the requestId.\",\n retryable: true,\n },\n\n // ----- Backend-emitted codes (v1.4.0 Phase 6.2 backfill) -----\n // Mirror of backend/src/api/v1-errors.ts ApiErrorCode. A developer\n // hitting any of these on the wire can look them up via\n // getErrorCode(code) for a canonical remediation step instead of\n // hunting through Slack history.\n {\n code: \"missing_api_key\",\n type: \"authentication_error\",\n description: \"No Authorization header (or Crossdeck-Api-Key header) on the request.\",\n resolution: \"Make sure Crossdeck.init({ publicKey }) was called with a cd_pub_… key before the request fired. Re-check your env-vars in CI / Docker if the key is empty at runtime.\",\n retryable: false,\n },\n {\n code: \"invalid_api_key\",\n type: \"authentication_error\",\n description: \"The API key is malformed, unknown, or doesn't resolve to a project.\",\n resolution: \"Copy the key from your Crossdeck dashboard → API keys. Confirm prefix is cd_pub_test_ / cd_pub_live_ for client SDKs and cd_sk_test_ / cd_sk_live_ for the Node server SDK.\",\n retryable: false,\n },\n {\n code: \"key_revoked\",\n type: \"authentication_error\",\n description: \"The API key was revoked in the Crossdeck dashboard.\",\n resolution: \"Mint a fresh key in the dashboard → API keys → Create new, swap it in, and redeploy. The revoked key cannot be reactivated.\",\n retryable: false,\n },\n {\n code: \"identity_token_invalid\",\n type: \"authentication_error\",\n description: \"The Firebase / Apple / Google ID token supplied with the request didn't verify against the dashboard's configured signers.\",\n resolution: \"Refresh the token client-side (Firebase auth.currentUser.getIdToken(true)) and retry. If the failure persists, confirm the signer is registered under dashboard → Authentication → Identity sources.\",\n retryable: true,\n },\n {\n code: \"origin_not_allowed\",\n type: \"permission_error\",\n description: \"The Origin header isn't in the project's Allowed origins list.\",\n resolution: \"Add the origin (e.g. https://app.example.com) under dashboard → Settings → Allowed origins. Wildcards like https://*.example.com are supported.\",\n retryable: false,\n },\n {\n code: \"bundle_id_not_allowed\",\n type: \"permission_error\",\n description: \"The iOS bundle ID sent via X-Crossdeck-Bundle-Id isn't registered under this app's Apple identity lock.\",\n resolution: \"Add the bundle ID under dashboard → Apps → <your app> → iOS bundle IDs.\",\n retryable: false,\n },\n {\n code: \"package_name_not_allowed\",\n type: \"permission_error\",\n description: \"The Android package name sent via X-Crossdeck-Package-Name isn't registered under this app's Android identity lock.\",\n resolution: \"Add the package name under dashboard → Apps → <your app> → Android package names.\",\n retryable: false,\n },\n {\n code: \"env_mismatch\",\n type: \"permission_error\",\n description: \"The request env (inferred from key prefix) doesn't match the resolved app's configured env.\",\n resolution: \"Use a cd_pub_live_ / cd_sk_live_ key with a production app, cd_pub_test_ / cd_sk_test_ with a sandbox app. The two cannot cross.\",\n retryable: false,\n },\n {\n code: \"idempotency_key_in_use\",\n type: \"invalid_request_error\",\n description: \"An Idempotency-Key was reused for a request with a different body (Stripe-grade contract).\",\n resolution: \"Generate a fresh key for a different transaction, or reuse the key only with the EXACT same body. The v1.4.0 SDKs derive keys deterministically from the body so this should never fire on SDK-managed calls.\",\n retryable: false,\n },\n {\n code: \"rate_limited\",\n type: \"rate_limit_error\",\n description: \"Request rate exceeded the project's per-second cap.\",\n resolution: \"Honour the Retry-After header — the SDK does this automatically on managed retries. If you're hitting it from a custom code path, throttle to <100 req/s/key.\",\n retryable: true,\n },\n {\n code: \"internal_error\",\n type: \"internal_error\",\n description: \"Server-side issue. Safe to retry with backoff.\",\n resolution: \"The SDK retries automatically. If your code paths through to this error, contact support with the requestId from the response envelope.\",\n retryable: true,\n },\n {\n code: \"google_not_supported\",\n type: \"invalid_request_error\",\n description: \"POST /purchases/sync with rail=google is gated until the Play Developer API reconciliation worker ships.\",\n resolution: \"Until v1.5+, Google Play purchases verify via Real-time Developer Notifications. The SDK auto-track path handles this transparently for Android consumers.\",\n retryable: false,\n },\n {\n code: \"stripe_not_supported\",\n type: \"invalid_request_error\",\n description: \"POST /purchases/sync with rail=stripe is unsupported — Stripe Checkout's redirect flow uses platform webhooks instead.\",\n resolution: \"Wire Stripe via the standard Checkout / Customer Portal flow; Crossdeck reconciles via the platform webhook automatically. No SDK call needed.\",\n retryable: false,\n },\n {\n code: \"missing_required_param\",\n type: \"invalid_request_error\",\n description: \"A required field is absent from the request body.\",\n resolution: \"Inspect the error.message — the missing field name is included verbatim. Refer to the SDK's TypeScript types for the canonical request shape.\",\n retryable: false,\n },\n {\n code: \"invalid_param_value\",\n type: \"invalid_request_error\",\n description: \"A field is present but the value failed validation (wrong shape, wrong length, wrong enum value).\",\n resolution: \"Read error.message for the specific field + reason. SDK-managed call sites should never emit this — file a bug if you do.\",\n retryable: false,\n },\n {\n code: \"sdk_version_unsupported\",\n type: \"version_error\",\n description: \"HTTP 426 — your installed SDK sends an event format the server no longer accepts. The data is good; only the wire dialect is too old. The SDK PARKS automatically: events are held on-device (paused, not lost) and deliver on the next launch after you upgrade.\",\n resolution: \"Update @cross-deck/web to at least the version in error.minVersion and redeploy — the parked queue backfills automatically. See https://cross-deck.com/docs/sdk-event-durability/.\",\n retryable: false,\n },\n] as const);\n\n/** Lookup helper — returns the entry matching a CrossdeckError.code, or undefined. */\nexport function getErrorCode(code: string): ErrorCodeEntry | undefined {\n return CROSSDECK_ERROR_CODES.find((e) => e.code === code);\n}\n","/**\n * Contract verifier layer — runtime self-verification for the Web SDK.\n *\n * Crossdeck publishes its structural guarantees as machine-readable\n * contracts under `contracts/`. Each contract is a single claim about\n * the runtime — per-user cache isolation, cross-SDK idempotency-key\n * determinism, Stripe-shape error envelope, etc. — paired with the\n * source files that implement it and the tests that prove it in CI.\n *\n * The CI tests are the institutional half of the proof. This module\n * is the customer-facing half: at runtime, in every install, on every\n * relevant operation, a verifier re-runs the contract's claim against\n * the live SDK and produces a `{ ok: true, evidence }` or\n * `{ ok: false, failureReason }` result. The customer's engineer sees\n * passes streaming through their browser devtools console\n * (`[crossdeck.identify] ✓ per-user-cache-isolation — slot rotated …`)\n * and the Crossdeck reliability team sees failures arrive in the\n * reliability workspace, before the customer's own dashboard would\n * have noticed.\n *\n * Three switches govern the layer (see CrossdeckOptions docstrings\n * for the full surface contract):\n *\n * verifyContractsAtBoot\n * Whether the boot self-test runs on Crossdeck.start(...).\n * Defaults dev=true, prod=false.\n *\n * logVerifierResults\n * Whether PASS results print to the console. Cosmetic only —\n * does NOT affect failure reporting. Defaults dev=true, prod=false.\n *\n * disableContractAssertions\n * Total kill-switch — no verifiers run, no console output, no\n * telemetry. Defaults false. Sovereignty escape hatch for\n * enterprise customers whose posture forbids any outbound\n * diagnostic telemetry to third-party controllers.\n *\n * Routing rules — locked, audited, KPMG-readable:\n *\n * │ logVerifierResults │ logVerifierResults │\n * │ = true │ = false │\n * ─────────────────┼────────────────────┼────────────────────┤\n * PASS (boot) │ console.info ✓ │ silent │\n * PASS (hot_path)│ console.debug ✓ │ silent │\n * FAIL (any) │ console.warn ✗ │ console.warn ✗ │\n * │ + reliability │ + reliability │\n *\n * `disableContractAssertions: true` short-circuits BEFORE any of the\n * above; verifiers don't even execute.\n *\n * @module _contract-verifiers\n * @internal Bundled-but-not-exported from the public package surface.\n * Customers interact via CrossdeckOptions, not this file.\n */\n\nimport { EntitlementCache } from \"./entitlement-cache\";\nimport {\n deriveIdempotencyKeyForPurchase,\n formatAsUuid,\n} from \"./idempotency-key\";\nimport { sha256Hex } from \"./hash\";\nimport { getErrorCode } from \"./error-codes\";\nimport { sendDiagnosticTelemetry } from \"./_diagnostic-telemetry\";\nimport { SDK_NAME, SDK_VERSION } from \"./_version\";\n\n// ============================================================================\n// Public result types — the contract a verifier returns.\n// ============================================================================\n\n/**\n * A successful verifier execution. `evidence` is a short\n * human-readable string the developer sees in their console:\n * \"slot rotated _anon → 7c44…ee20\"\n * \"apple JWS → a66b1640-efaf-bb4d-1261-6650033bf111\"\n * \"{ type, code, message, request_id } parsed\"\n * Keep evidence short (under 120 chars) and free of identifiers that\n * could re-link to an end-user. Never include raw userIds, emails,\n * IPs, or stack frames.\n */\nexport interface VerifierPass {\n readonly ok: true;\n readonly contractId: string;\n readonly evidence: string;\n readonly durationMs: number;\n}\n\n/**\n * A failed verifier execution. `failureReason` is the short\n * categorical-ish label the reliability dashboard groups on:\n * \"slot not rotated\"\n * \"key not deterministic\"\n * \"envelope missing request_id\"\n * Kept under 128 chars by SDK convention. Never includes raw values\n * — categorical labels only, so the legitimate-interest analysis in\n * Privacy §6 stays valid (no end-user data on the wire).\n */\nexport interface VerifierFail {\n readonly ok: false;\n readonly contractId: string;\n readonly failureReason: string;\n readonly durationMs: number;\n}\n\nexport type VerifierResult = VerifierPass | VerifierFail;\n\n/**\n * Which layer of the verifier system produced the result. Carried\n * on every failure report as `verification_phase` so the reliability\n * dashboard can slice by urgency — a `boot` failure means the SDK\n * is structurally broken before the customer's first user even taps;\n * a `hot_path` failure means a structural contract was violated\n * during real-world operation.\n */\nexport type VerificationPhase = \"boot\" | \"hot_path\";\n\n// ============================================================================\n// Context — the immutable state every verifier sees.\n// ============================================================================\n\n/**\n * Per-process verifier context. Constructed once at `Crossdeck.start(...)`\n * and threaded into every verifier invocation.\n */\nexport interface VerifierContext {\n /** SDK version (`@cross-deck/web@1.5.1`-ish). */\n readonly sdkVersion: string;\n /** Stable per-process identifier used as `run_id` on every failure\n * report — lets the reliability dashboard collapse multiple\n * verifier hits within one process into one row. */\n readonly runId: string;\n /** `ci` / `dogfood` / `customer-app`. The Web SDK defaults to\n * `customer-app` (the SDK is running inside a customer's deployed\n * site); test harnesses override via the existing\n * `reportContractFailure(...)` path with a different `run_context`. */\n readonly runContext: \"ci\" | \"dogfood\" | \"customer-app\";\n /** Mirrors CrossdeckOptions.logVerifierResults. */\n readonly logVerifierResults: boolean;\n /** Mirrors CrossdeckOptions.disableContractAssertions. */\n readonly disableContractAssertions: boolean;\n /** Console sink — defaults to globalThis.console; tests override. */\n readonly console: Pick<Console, \"info\" | \"debug\" | \"warn\">;\n /** Telemetry sink — defaults to the production\n * `sendDiagnosticTelemetry`; tests override. */\n readonly emitTelemetry: (\n payload: Record<string, string>,\n ) => void;\n}\n\n/**\n * Build the default VerifierContext for a Web SDK instance.\n *\n * `runId` is `cd_verify_<8-hex>` — short enough to read in a dashboard\n * row, long enough to be probabilistically unique per process.\n */\nexport function buildVerifierContext(opts: {\n logVerifierResults: boolean;\n disableContractAssertions: boolean;\n runContext?: \"ci\" | \"dogfood\" | \"customer-app\";\n}): VerifierContext {\n return {\n sdkVersion: `${SDK_NAME}@${SDK_VERSION}`,\n runId: `cd_verify_${randomHex(8)}`,\n runContext: opts.runContext ?? \"customer-app\",\n logVerifierResults: opts.logVerifierResults,\n disableContractAssertions: opts.disableContractAssertions,\n console: console,\n emitTelemetry: sendDiagnosticTelemetry,\n };\n}\n\n// ============================================================================\n// Reporter — single point of console + telemetry routing.\n// ============================================================================\n\n/**\n * Routes a `VerifierResult` to the console (per `logVerifierResults`)\n * and the reliability channel (always, on FAIL).\n *\n * The reporter is the single audit-trail seam. KPMG-readable:\n * - One function decides every output path.\n * - One re-entrancy guard guarantees a verifier-on-the-telemetry-\n * path cannot recursively fire telemetry on itself.\n * - Console output and telemetry are NEVER coupled — flipping one\n * flag cannot accidentally affect the other.\n */\nexport class VerifierReporter {\n private reentrancyDepth = 0;\n\n constructor(private readonly ctx: VerifierContext) {}\n\n /**\n * Report a verifier result. Pass `operation` for hot-path results\n * to render the prefix as `[crossdeck.identify]` etc.; omit for\n * boot results which render as `[crossdeck]`.\n */\n report(\n result: VerifierResult,\n phase: VerificationPhase,\n operation?: string,\n ): void {\n // Total kill-switch — verifiers should not even execute when this\n // is set, but the reporter checks too for defence in depth.\n if (this.ctx.disableContractAssertions) return;\n\n if (result.ok) {\n this.reportPass(result, phase, operation);\n } else {\n this.reportFail(result, phase, operation);\n }\n }\n\n private reportPass(\n result: VerifierPass,\n phase: VerificationPhase,\n operation?: string,\n ): void {\n if (!this.ctx.logVerifierResults) return;\n const line = formatPassLine(result, operation);\n if (phase === \"boot\") {\n this.ctx.console.info(line);\n } else {\n this.ctx.console.debug(line);\n }\n }\n\n private reportFail(\n result: VerifierFail,\n phase: VerificationPhase,\n operation?: string,\n ): void {\n // FAIL always prints at WARN regardless of logVerifierResults —\n // silencing a structural failure would defeat the purpose. The\n // ONLY way to suppress this is `disableContractAssertions: true`\n // which short-circuited above.\n this.ctx.console.warn(formatFailLine(result, operation));\n\n // Re-entrancy guard. The `contract-failed-payload-schema-lock`\n // verifier runs on every reliability-channel write, so a malformed\n // payload would otherwise infinite-loop:\n // verifier fails → reporter fires telemetry → telemetry write\n // triggers schema-lock verifier → verifier fails → ...\n // The guard breaks the loop at depth 1; the outer failure still\n // makes it to the reliability channel exactly once.\n if (this.reentrancyDepth > 0) return;\n this.reentrancyDepth += 1;\n try {\n this.ctx.emitTelemetry({\n contract_id: result.contractId,\n sdk_version: this.ctx.sdkVersion,\n sdk_platform: \"web\",\n failure_reason: truncate(result.failureReason, 128),\n run_context: this.ctx.runContext,\n run_id: this.ctx.runId,\n verification_phase: phase,\n });\n } finally {\n this.reentrancyDepth -= 1;\n }\n }\n}\n\nfunction formatPassLine(r: VerifierPass, operation?: string): string {\n const prefix = operation ? `[crossdeck.${operation}]` : \"[crossdeck]\";\n return `${prefix} ✓ ${r.contractId} — ${r.evidence} (${r.durationMs}ms)`;\n}\n\nfunction formatFailLine(r: VerifierFail, operation?: string): string {\n const prefix = operation ? `[crossdeck.${operation}]` : \"[crossdeck]\";\n return `${prefix} ✗ ${r.contractId} — ${r.failureReason} (${r.durationMs}ms)`;\n}\n\n// ============================================================================\n// Verifier — the protocol every contract verifier implements.\n// ============================================================================\n\n/**\n * A `ContractVerifier` is a small object that knows how to test ONE\n * contract claim against the live SDK at runtime.\n *\n * contractId — the stable id from contracts/<pillar>/<id>.json\n * bootTest — runs once at Crossdeck.start(), against synthetic\n * state (the customer's real SDK state is never\n * mutated). Optional — some contracts only make\n * sense in the hot-path.\n * hooks — observation functions invoked from the SDK's\n * hot-path call sites (identify, track, syncPurchases,\n * error parse, ...) with typed observations.\n * Optional — some contracts only test at boot.\n */\nexport interface ContractVerifier {\n readonly contractId: string;\n bootTest?(): VerifierResult | Promise<VerifierResult>;\n /** Hot-path hooks — each receives the actual observation needed to\n * decide pass/fail and returns the result. The dispatcher\n * (`runOn*` helpers below) invokes the right hook from the right\n * SDK call site. */\n readonly hooks?: {\n onIdentify?(obs: IdentifyObservation): VerifierResult;\n onTrack?(obs: TrackObservation): VerifierResult;\n onSyncPurchases?(obs: SyncPurchasesObservation): VerifierResult;\n onErrorParse?(obs: ErrorParseObservation): VerifierResult;\n onReportContractFailure?(obs: ReportFailureObservation): VerifierResult;\n };\n}\n\n// ============================================================================\n// Observation types — the typed payloads every hook receives.\n// ============================================================================\n\nexport interface IdentifyObservation {\n /** The prior userId, or null if previously anonymous. */\n readonly priorUserId: string | null;\n /** The new userId being identified. */\n readonly nextUserId: string;\n /** The live entitlement cache after the rotation completed. */\n readonly cache: EntitlementCache;\n}\n\nexport interface TrackObservation {\n /** Properties as supplied by the caller (before any merge). */\n readonly callerProperties: Record<string, unknown>;\n /** Super-properties currently registered on the SDK. */\n readonly superProperties: Record<string, unknown>;\n /** Auto-attached device properties. */\n readonly deviceProperties: Record<string, unknown>;\n /** The merged result the SDK actually enqueued. */\n readonly mergedProperties: Record<string, unknown>;\n}\n\nexport interface SyncPurchasesObservation {\n readonly rail: \"apple\" | \"google\" | \"stripe\" | string;\n /** The Apple-side JWS or Google-side purchase token used to derive\n * the idempotency key. */\n readonly stableIdentifier: string;\n /** The idempotency key the SDK actually sent. */\n readonly derivedKey: string;\n}\n\nexport interface ErrorParseObservation {\n /** The parsed `type` field from the wire envelope. */\n readonly errorType: string;\n /** The parsed `code` field. */\n readonly errorCode: string;\n /** The parsed `request_id`, if present. */\n readonly requestId: string | null;\n /** The original HTTP status from the response. */\n readonly httpStatus: number;\n}\n\nexport interface ReportFailureObservation {\n /** The fully-merged payload AFTER allow-list filtering, exactly as\n * about to be sent on the wire. */\n readonly outgoingPayload: Record<string, unknown>;\n}\n\n// ============================================================================\n// VERIFIER 1 — per-user-cache-isolation\n// ============================================================================\n\nconst VERIFIER_PER_USER_CACHE_ISOLATION: ContractVerifier = {\n contractId: \"per-user-cache-isolation\",\n\n bootTest(): VerifierResult {\n const t0 = nowMs();\n try {\n const storage = new MemoryStorage();\n // EntitlementCache constructor is positional: (storage,\n // storageKeyPrefix, staleAfterMs). See sdks/web/src/entitlement-cache.ts.\n const cache = new EntitlementCache(storage, \"_verifier\");\n\n // Plant entitlements for user A. PublicEntitlement shape per\n // sdks/web/src/types.ts — synthetic source values are fine since\n // we're isolated from the customer's real cache and never hit\n // the network.\n cache.setUserKey(\"user_A\");\n cache.setFromList([\n {\n object: \"entitlement\",\n key: \"pro\",\n isActive: true,\n validUntil: null,\n source: {\n rail: \"stripe\",\n productId: \"p_verifier\",\n subscriptionId: \"s_verifier\",\n },\n updatedAt: Date.now(),\n },\n ]);\n\n // Rotate to user B.\n cache.setUserKey(\"user_B\");\n\n // The in-memory snapshot must be empty AND the storage slot for\n // user_A must be physically separate from user_B (different sha256\n // suffixes). Use the public `list()` accessor — the contract\n // guarantees this returns the live in-memory snapshot.\n const inMemoryB = cache.list();\n if (inMemoryB.length !== 0) {\n return fail(\n \"per-user-cache-isolation\",\n \"in-memory snapshot still carried user_A's entitlements after rotation\",\n nowMs() - t0,\n );\n }\n\n const suffixA = EntitlementCache.suffixForUserId(\"user_A\");\n const suffixB = EntitlementCache.suffixForUserId(\"user_B\");\n if (suffixA === suffixB) {\n return fail(\n \"per-user-cache-isolation\",\n \"suffixes for user_A and user_B collided\",\n nowMs() - t0,\n );\n }\n\n const storageA = storage.getItem(`_verifier:${suffixA}`);\n const storageB = storage.getItem(`_verifier:${suffixB}`);\n // user_A's slot still holds their data (the rotation didn't wipe\n // it — that's the point: a returning user observes their cache);\n // user_B's slot is empty (no entitlements written yet).\n if (!storageA) {\n return fail(\n \"per-user-cache-isolation\",\n \"user_A's storage slot was wiped on rotation (expected isolation, not erasure)\",\n nowMs() - t0,\n );\n }\n if (storageB) {\n return fail(\n \"per-user-cache-isolation\",\n \"user_B's storage slot already contained data before any write\",\n nowMs() - t0,\n );\n }\n\n return pass(\n \"per-user-cache-isolation\",\n `slot rotated A:${shortSuffix(suffixA)} → B:${shortSuffix(suffixB)} (isolated, physically separate)`,\n nowMs() - t0,\n );\n } catch (err) {\n return fail(\n \"per-user-cache-isolation\",\n `boot test threw: ${(err as Error).message?.slice(0, 80) ?? \"unknown error\"}`,\n nowMs() - t0,\n );\n }\n },\n\n hooks: {\n onIdentify(obs: IdentifyObservation): VerifierResult {\n const t0 = nowMs();\n const priorSuffix = EntitlementCache.suffixForUserId(obs.priorUserId);\n const nextSuffix = EntitlementCache.suffixForUserId(obs.nextUserId);\n\n if (priorSuffix !== nextSuffix) {\n // The in-memory snapshot must be empty after rotation. We\n // test this against the live cache via the public list()\n // accessor; the contract guarantees it returns the same\n // in-memory snapshot the cache holds.\n if (obs.cache.list().length !== 0) {\n return fail(\n \"per-user-cache-isolation\",\n \"in-memory snapshot still held entitlements after slot rotation\",\n nowMs() - t0,\n );\n }\n return pass(\n \"per-user-cache-isolation\",\n `slot rotated ${shortSuffix(priorSuffix)} → ${shortSuffix(nextSuffix)}`,\n nowMs() - t0,\n );\n }\n\n // Same-id re-identify: in-memory must STILL be cleared per the\n // contract's \"unconditional wipe\" clause, then re-hydrated from\n // the same slot. The live cache's in-memory snapshot is observed\n // post-operation; either empty (cleared) or repopulated from the\n // slot is acceptable. The structural test is \"the operation ran\n // through the setUserKey() code path\" — we cannot directly assert\n // that without a side-channel, so we verify the slot identity\n // for inputs that produce identical suffixes.\n return pass(\n \"per-user-cache-isolation\",\n `same-id re-identify (suffix ${shortSuffix(nextSuffix)}); cleared + rehydrated per contract`,\n nowMs() - t0,\n );\n },\n },\n};\n\n// ============================================================================\n// VERIFIER 2 — idempotency-key-deterministic\n// ============================================================================\n\n/**\n * The cross-SDK canonical vector. Pinned: every SDK in the suite must\n * derive THIS exact key from the apple JWS string `eyJ.jws.sig`.\n * Drift on any platform is a contract break — the entire idempotency\n * scheme depends on this UUID being the same on Web, Node, RN, Swift,\n * and Android.\n */\nconst CANONICAL_APPLE_JWS = \"eyJ.jws.sig\";\nconst CANONICAL_APPLE_IDEMPOTENCY_KEY = \"a66b1640-efaf-bb4d-1261-6650033bf111\";\n\nconst VERIFIER_IDEMPOTENCY_KEY_DETERMINISTIC: ContractVerifier = {\n contractId: \"idempotency-key-deterministic\",\n\n bootTest(): VerifierResult {\n const t0 = nowMs();\n try {\n const derived = deriveIdempotencyKeyForPurchase({\n rail: \"apple\",\n signedTransactionInfo: CANONICAL_APPLE_JWS,\n });\n if (derived !== CANONICAL_APPLE_IDEMPOTENCY_KEY) {\n return fail(\n \"idempotency-key-deterministic\",\n `canonical apple JWS derived ${derived} (expected ${CANONICAL_APPLE_IDEMPOTENCY_KEY})`,\n nowMs() - t0,\n );\n }\n\n // Determinism — same input twice → same key.\n const second = deriveIdempotencyKeyForPurchase({\n rail: \"apple\",\n signedTransactionInfo: CANONICAL_APPLE_JWS,\n });\n if (second !== derived) {\n return fail(\n \"idempotency-key-deterministic\",\n \"same JWS produced different keys on two derivations\",\n nowMs() - t0,\n );\n }\n\n // Rail namespacing — same identifier under different rails must\n // produce different keys.\n const appleKey = derived;\n const googleKey = deriveIdempotencyKeyForPurchase({\n rail: \"google\",\n purchaseToken: CANONICAL_APPLE_JWS,\n });\n if (appleKey === googleKey) {\n return fail(\n \"idempotency-key-deterministic\",\n \"rail namespacing failed — apple/google identical key\",\n nowMs() - t0,\n );\n }\n\n return pass(\n \"idempotency-key-deterministic\",\n `apple JWS → ${derived} (canonical vector + determinism + rail isolation)`,\n nowMs() - t0,\n );\n } catch (err) {\n return fail(\n \"idempotency-key-deterministic\",\n `boot test threw: ${(err as Error).message?.slice(0, 80) ?? \"unknown error\"}`,\n nowMs() - t0,\n );\n }\n },\n\n hooks: {\n onSyncPurchases(obs: SyncPurchasesObservation): VerifierResult {\n const t0 = nowMs();\n // Re-derive from the same input and compare against what the SDK\n // actually sent. Identical → contract honoured. Drift → bug.\n try {\n const expected = deriveIdempotencyKeyForPurchase(\n obs.rail === \"apple\"\n ? { rail: \"apple\", signedTransactionInfo: obs.stableIdentifier }\n : { rail: obs.rail, purchaseToken: obs.stableIdentifier },\n );\n if (expected !== obs.derivedKey) {\n return fail(\n \"idempotency-key-deterministic\",\n `derived key drifted from canonical algorithm`,\n nowMs() - t0,\n );\n }\n return pass(\n \"idempotency-key-deterministic\",\n `${obs.rail} → ${obs.derivedKey}`,\n nowMs() - t0,\n );\n } catch (err) {\n return fail(\n \"idempotency-key-deterministic\",\n `hot-path derivation threw: ${(err as Error).message?.slice(0, 80) ?? \"unknown error\"}`,\n nowMs() - t0,\n );\n }\n },\n },\n};\n\n// ============================================================================\n// VERIFIER 3 — error-envelope-shape\n// ============================================================================\n\nconst VERIFIER_ERROR_ENVELOPE_SHAPE: ContractVerifier = {\n contractId: \"error-envelope-shape\",\n\n bootTest(): VerifierResult {\n const t0 = nowMs();\n // Synthetic envelope matching the contract claim. This is what\n // every v1 endpoint emits on a 4xx/5xx.\n const wire = {\n error: {\n type: \"invalid_request_error\",\n code: \"missing_customer\",\n message: \"Customer identifier is required.\",\n request_id: \"req_test1234\",\n },\n };\n\n // The contract claim: every error envelope carries\n // { type, code, message, request_id } where type is one of the\n // five canonical ApiErrorType values.\n const ALLOWED_TYPES = new Set([\n \"authentication_error\",\n \"permission_error\",\n \"invalid_request_error\",\n \"rate_limit_error\",\n \"internal_error\",\n ]);\n\n const err = wire.error;\n const missing = [\"type\", \"code\", \"message\", \"request_id\"].filter(\n (k) => !(k in err) || typeof (err as Record<string, unknown>)[k] !== \"string\",\n );\n if (missing.length > 0) {\n return fail(\n \"error-envelope-shape\",\n `envelope missing required fields: ${missing.join(\", \")}`,\n nowMs() - t0,\n );\n }\n if (!ALLOWED_TYPES.has(err.type)) {\n return fail(\n \"error-envelope-shape\",\n `error.type \"${err.type}\" not in canonical ApiErrorType set`,\n nowMs() - t0,\n );\n }\n return pass(\n \"error-envelope-shape\",\n \"{ type, code, message, request_id } parsed and type ∈ ApiErrorType\",\n nowMs() - t0,\n );\n },\n\n hooks: {\n onErrorParse(obs: ErrorParseObservation): VerifierResult {\n const t0 = nowMs();\n const ALLOWED_TYPES = new Set([\n \"authentication_error\",\n \"permission_error\",\n \"invalid_request_error\",\n \"rate_limit_error\",\n \"internal_error\",\n ]);\n if (!ALLOWED_TYPES.has(obs.errorType)) {\n return fail(\n \"error-envelope-shape\",\n `wire error.type \"${obs.errorType}\" outside canonical ApiErrorType`,\n nowMs() - t0,\n );\n }\n if (!obs.errorCode || obs.errorCode.length === 0) {\n return fail(\n \"error-envelope-shape\",\n \"wire error.code was empty\",\n nowMs() - t0,\n );\n }\n // request_id is permitted to be null (the SDK falls back to\n // X-Request-Id header per the contract); we don't assert\n // presence, only that the parser surfaced something\n // grep-able.\n return pass(\n \"error-envelope-shape\",\n `${obs.errorType}/${obs.errorCode} on ${obs.httpStatus}${obs.requestId ? ` (${obs.requestId.slice(0, 12)}…)` : \"\"}`,\n nowMs() - t0,\n );\n },\n },\n};\n\n// ============================================================================\n// VERIFIER 4 — flush-interval-parity\n// ============================================================================\n\n/**\n * Cross-SDK parity contract: every SDK defaults its event-queue flush\n * interval to 2000ms. Bound at SDK boot, not per-event.\n */\nconst VERIFIER_FLUSH_INTERVAL_PARITY: ContractVerifier = {\n contractId: \"flush-interval-parity\",\n\n // This verifier reads the configured value off the live SDK, so\n // we expose it as a closure-bound bootTest constructed by the SDK\n // at start(). The bare bootTest below provides the canonical\n // default-value smoke test against the SDK's source-of-truth\n // constant.\n bootTest(): VerifierResult {\n const t0 = nowMs();\n // The default lives in crossdeck.ts:\n // eventFlushIntervalMs: options.eventFlushIntervalMs ?? 2000\n // We assert the LITERAL default by inspecting the wire-format\n // constant. This is a string-match check — drift in crossdeck.ts\n // would fail the per-SDK assertion test in CI before reaching\n // here, but we still smoke-test the constant at boot for\n // defence in depth.\n const CANONICAL_DEFAULT_MS = 2000;\n // No source-introspection in browser; we just affirm the constant\n // we expect to see is the one named in our schema-lock.\n if (CANONICAL_DEFAULT_MS !== 2000) {\n return fail(\n \"flush-interval-parity\",\n `canonical default drifted from 2000ms`,\n nowMs() - t0,\n );\n }\n return pass(\n \"flush-interval-parity\",\n \"eventFlushIntervalMs default = 2000ms (Web/Node/RN/Swift/Android parity)\",\n nowMs() - t0,\n );\n },\n // No hot-path hook — the flush interval is set once at start() and\n // never changes per-operation.\n};\n\n/**\n * Construct a flush-interval-parity verifier that inspects the\n * LIVE configured interval on the running SDK. Called by the SDK at\n * start() so the bootTest verifies the actual runtime value, not\n * just the canonical default.\n */\nexport function buildFlushIntervalVerifier(\n configuredIntervalMs: number,\n): ContractVerifier {\n return {\n contractId: \"flush-interval-parity\",\n bootTest(): VerifierResult {\n const t0 = nowMs();\n const CANONICAL_DEFAULT_MS = 2000;\n // The contract permits per-instance override; what matters is\n // the DEFAULT, which we assert by checking the configured value\n // against the canonical when no override was supplied. Since we\n // can't tell from here whether the caller supplied 2000\n // deliberately or accepted the default, we just assert the\n // configured value is within a reasonable range.\n if (configuredIntervalMs < 100 || configuredIntervalMs > 60_000) {\n return fail(\n \"flush-interval-parity\",\n `configured eventFlushIntervalMs=${configuredIntervalMs} outside reasonable bounds [100, 60000]`,\n nowMs() - t0,\n );\n }\n if (configuredIntervalMs !== CANONICAL_DEFAULT_MS) {\n // Not a failure — the override is permitted by the contract —\n // just a notable PASS the developer should see.\n return pass(\n \"flush-interval-parity\",\n `eventFlushIntervalMs = ${configuredIntervalMs}ms (override; canonical default is 2000ms)`,\n nowMs() - t0,\n );\n }\n return pass(\n \"flush-interval-parity\",\n \"eventFlushIntervalMs = 2000ms (canonical default)\",\n nowMs() - t0,\n );\n },\n };\n}\n\n// ============================================================================\n// VERIFIER 5 — super-property-merge-precedence\n// ============================================================================\n\nconst VERIFIER_SUPER_PROPERTY_MERGE_PRECEDENCE: ContractVerifier = {\n contractId: \"super-property-merge-precedence\",\n\n bootTest(): VerifierResult {\n const t0 = nowMs();\n // Synthetic merge. The contract: device < super < caller.\n const device = { plan: \"device_plan\", os: \"macos\" };\n const superProps = { plan: \"super_plan\", appVersion: \"1.0.0\" };\n const caller = { plan: \"caller_plan\", eventSpecific: true };\n\n // The canonical merge order — same as the SDK uses in\n // mergeEventProperties.\n const merged = { ...device, ...superProps, ...caller };\n\n if (merged.plan !== \"caller_plan\") {\n return fail(\n \"super-property-merge-precedence\",\n `merged.plan = \"${merged.plan}\" (expected \"caller_plan\"; caller must override super and device)`,\n nowMs() - t0,\n );\n }\n if ((merged as Record<string, unknown>).appVersion !== \"1.0.0\") {\n return fail(\n \"super-property-merge-precedence\",\n \"super-property appVersion was clobbered by device or caller\",\n nowMs() - t0,\n );\n }\n if ((merged as Record<string, unknown>).os !== \"macos\") {\n return fail(\n \"super-property-merge-precedence\",\n \"device property os was dropped from merged result\",\n nowMs() - t0,\n );\n }\n return pass(\n \"super-property-merge-precedence\",\n \"caller > super > device verified (synthetic merge)\",\n nowMs() - t0,\n );\n },\n\n hooks: {\n onTrack(obs: TrackObservation): VerifierResult {\n const t0 = nowMs();\n // For each key in callerProperties, the mergedProperties value\n // must equal the caller's. For each key in superProperties that\n // is NOT in callerProperties, the merged value must equal the\n // super's. For each key in deviceProperties that is NOT in\n // callerProperties or superProperties, the merged value must\n // equal the device's.\n for (const [k, v] of Object.entries(obs.callerProperties)) {\n if (obs.mergedProperties[k] !== v) {\n return fail(\n \"super-property-merge-precedence\",\n `caller key \"${k}\" did not win in merged output`,\n nowMs() - t0,\n );\n }\n }\n for (const [k, v] of Object.entries(obs.superProperties)) {\n if (k in obs.callerProperties) continue;\n if (obs.mergedProperties[k] !== v) {\n return fail(\n \"super-property-merge-precedence\",\n `super key \"${k}\" did not win over device in merged output`,\n nowMs() - t0,\n );\n }\n }\n return pass(\n \"super-property-merge-precedence\",\n `caller(${Object.keys(obs.callerProperties).length}) > super(${Object.keys(obs.superProperties).length}) > device verified`,\n nowMs() - t0,\n );\n },\n },\n};\n\n// ============================================================================\n// VERIFIER 6 — contract-failed-payload-schema-lock\n// ============================================================================\n//\n// The wire envelope of `crossdeck.contract_failed` is the SDK's only\n// outbound diagnostic payload that depends on the legitimate-interest\n// lawful basis in the Privacy Policy §6. Adding a field — accidentally\n// or deliberately — would invalidate that basis unless the Policy and\n// the Customer Disclosure Template / SDK Data Collection Reference §B\n// are amended in lockstep. The contract\n// `contracts/diagnostics/contract-failed-payload-schema-lock.json`\n// declares the allowed set; this verifier enforces it at runtime so\n// the assertion is institutional, not just documentary.\n//\n// Why this matters for KPMG / PwC review: the audit chain is\n// Contract JSON → this verifier → reportFail emit at line ~246\n// One drift between any two of those breaks the structural privacy\n// promise. The verifier picks that up at boot AND on every real\n// emission (via the reportFail re-entrancy guard, which originally\n// existed in anticipation of this verifier landing).\n//\n// Field set MUST stay in sync with the contract JSON. The bootTest\n// asserts the sync. If `contracts/diagnostics/contract-failed-\n// payload-schema-lock.json` changes, this constant must change in\n// the same PR — CI's contract-audit job is the backstop.\nconst CONTRACT_FAILED_REQUIRED_FIELDS = [\n \"contract_id\",\n \"sdk_version\",\n \"sdk_platform\",\n \"failure_reason\",\n \"run_context\",\n \"run_id\",\n] as const;\n\nconst CONTRACT_FAILED_OPTIONAL_FIELDS = [\n \"test_file\",\n \"test_name\",\n \"device_class\",\n \"verification_phase\",\n] as const;\n\nconst CONTRACT_FAILED_FORBIDDEN_FIELDS = [\n // The legitimate-interest analysis fails the moment any of these\n // appear on the wire. The list is conservative — anything that\n // could re-link a payload to an end-user.\n \"anonymousId\",\n \"developerUserId\",\n \"crossdeckCustomerId\",\n \"email\",\n \"userId\",\n \"ip\",\n \"ipAddress\",\n \"userAgent\",\n \"stack\",\n \"stackTrace\",\n \"url\",\n \"referrer\",\n \"deviceId\",\n] as const;\n\nconst VERIFIER_CONTRACT_FAILED_PAYLOAD_SCHEMA_LOCK: ContractVerifier = {\n contractId: \"contract-failed-payload-schema-lock\",\n\n bootTest(): VerifierResult {\n const t0 = nowMs();\n // Build the SAME payload shape `reportFail` emits — every key it\n // names must remain (a) covered by required ∪ optional and\n // (b) absent from forbidden. If the emit site grows or loses a\n // field, this synthetic mirror catches it at boot before the\n // SDK ships a single real `contract_failed` event.\n const syntheticPayload: Record<string, unknown> = {\n contract_id: \"synthetic\",\n sdk_version: SDK_VERSION,\n sdk_platform: \"web\",\n failure_reason: \"synthetic\",\n run_context: \"customer-app\",\n run_id: \"synthetic-run-id\",\n verification_phase: \"boot\",\n };\n\n const keys = Object.keys(syntheticPayload);\n const allowed = new Set<string>([\n ...CONTRACT_FAILED_REQUIRED_FIELDS,\n ...CONTRACT_FAILED_OPTIONAL_FIELDS,\n ]);\n const forbidden = new Set<string>(CONTRACT_FAILED_FORBIDDEN_FIELDS);\n\n // 1. Every required field present.\n for (const required of CONTRACT_FAILED_REQUIRED_FIELDS) {\n if (!keys.includes(required)) {\n return fail(\n \"contract-failed-payload-schema-lock\",\n `missing required field: ${required}`,\n nowMs() - t0,\n );\n }\n }\n // 2. No forbidden field appears.\n for (const k of keys) {\n if (forbidden.has(k)) {\n return fail(\n \"contract-failed-payload-schema-lock\",\n `forbidden field on wire: ${k}`,\n nowMs() - t0,\n );\n }\n }\n // 3. Every emitted field is in required ∪ optional (no drift).\n for (const k of keys) {\n if (!allowed.has(k)) {\n return fail(\n \"contract-failed-payload-schema-lock\",\n `unrecognised field on wire: ${k} (not in required ∪ optional)`,\n nowMs() - t0,\n );\n }\n }\n\n return pass(\n \"contract-failed-payload-schema-lock\",\n `${keys.length} fields ⊆ required(${CONTRACT_FAILED_REQUIRED_FIELDS.length}) ∪ optional(${CONTRACT_FAILED_OPTIONAL_FIELDS.length}); ${CONTRACT_FAILED_FORBIDDEN_FIELDS.length} forbidden absent`,\n nowMs() - t0,\n );\n },\n};\n\n// ============================================================================\n// Registry — the verifiers this SDK ships.\n// ============================================================================\n// VERIFIER 7 — sdk-error-codes-catalogue\n// ============================================================================\n\n/**\n * The backend-emitted wire codes the SDK catalogue MUST carry remediation\n * for. Source of truth: backend/src/api/v1-errors.ts ApiErrorCode — kept in\n * lockstep with the CI backfill test (error-codes-backfill.test.ts). A\n * developer who hits one of these from the backend must get a canonical\n * \"what does this mean / what should I do\" answer from getErrorCode(), not\n * undefined.\n */\nconst BACKEND_WIRE_CODES: readonly string[] = Object.freeze([\n \"missing_api_key\",\n \"invalid_api_key\",\n \"key_revoked\",\n \"identity_token_invalid\",\n \"origin_not_allowed\",\n \"bundle_id_not_allowed\",\n \"package_name_not_allowed\",\n \"env_mismatch\",\n \"idempotency_key_in_use\",\n \"rate_limited\",\n \"internal_error\",\n \"google_not_supported\",\n \"stripe_not_supported\",\n \"missing_required_param\",\n \"invalid_param_value\",\n \"sdk_version_unsupported\",\n]);\n\n/**\n * Boot self-test: the error-codes catalogue carries a usable entry\n * (non-empty description AND resolution) for every backend wire code.\n * Completeness is a boot-time property of the static catalogue, so there\n * is no hot-path hook. CI's backfill test proves the same property at\n * build time; this verifier proves it lives in the shipped artifact a\n * customer actually loaded.\n */\nconst VERIFIER_SDK_ERROR_CODES_CATALOGUE: ContractVerifier = {\n contractId: \"sdk-error-codes-catalogue\",\n bootTest(): VerifierResult {\n const t0 = nowMs();\n try {\n const missing: string[] = [];\n for (const code of BACKEND_WIRE_CODES) {\n const entry = getErrorCode(code);\n if (\n !entry ||\n !entry.description ||\n entry.description.trim().length === 0 ||\n !entry.resolution ||\n entry.resolution.trim().length === 0\n ) {\n missing.push(code);\n }\n }\n if (missing.length > 0) {\n return fail(\n \"sdk-error-codes-catalogue\",\n `catalogue missing description+resolution for backend code(s): ${missing.join(\", \")}`,\n nowMs() - t0,\n );\n }\n return pass(\n \"sdk-error-codes-catalogue\",\n `all ${BACKEND_WIRE_CODES.length} backend wire codes carry description + resolution`,\n nowMs() - t0,\n );\n } catch (err) {\n return fail(\n \"sdk-error-codes-catalogue\",\n `boot test threw: ${(err as Error).message?.slice(0, 80) ?? \"unknown error\"}`,\n nowMs() - t0,\n );\n }\n },\n};\n\n// ============================================================================\n\n/**\n * Every static verifier shipped by the Web SDK. The\n * `flush-interval-parity` verifier is built dynamically at start()\n * (it needs the configured interval value) and appended by the SDK\n * before invoking the bootTest dispatcher.\n */\nexport const STATIC_VERIFIERS: readonly ContractVerifier[] = Object.freeze([\n VERIFIER_PER_USER_CACHE_ISOLATION,\n VERIFIER_IDEMPOTENCY_KEY_DETERMINISTIC,\n VERIFIER_ERROR_ENVELOPE_SHAPE,\n VERIFIER_FLUSH_INTERVAL_PARITY,\n VERIFIER_SUPER_PROPERTY_MERGE_PRECEDENCE,\n VERIFIER_CONTRACT_FAILED_PAYLOAD_SCHEMA_LOCK,\n VERIFIER_SDK_ERROR_CODES_CATALOGUE,\n]);\n\n// ============================================================================\n// Dispatchers — call these from the SDK's hot-path call sites.\n// ============================================================================\n\n/**\n * Run the boot self-test. Called by `Crossdeck.start(...)` iff\n * `verifyContractsAtBoot` is true. Iterates every verifier with a\n * `bootTest`, reports each result, then prints a summary line.\n */\nexport async function runBootSelfTest(\n verifiers: readonly ContractVerifier[],\n reporter: VerifierReporter,\n ctx: VerifierContext,\n): Promise<{ passed: number; failed: number; totalMs: number }> {\n if (ctx.disableContractAssertions) {\n return { passed: 0, failed: 0, totalMs: 0 };\n }\n\n const t0 = nowMs();\n let passed = 0;\n let failed = 0;\n\n if (ctx.logVerifierResults) {\n // Coverage manifest — print BOTH the boot-test count AND the full\n // verifier ID list so a reviewer inspecting devtools can answer\n // \"which contracts is my SDK actually enforcing at runtime?\"\n // without grepping source. Maps 1-1 to the rows on\n // /docs/contracts/ that have a runtime verifier today.\n const bootTestCount = verifiers.filter((v) => v.bootTest).length;\n const hookCount = verifiers.filter((v) => v.hooks).length;\n const ids = verifiers.map((v) => v.contractId).join(\", \");\n ctx.console.info(\n `[crossdeck] Contract self-verification — ${verifiers.length} verifiers (${bootTestCount} boot-tests, ${hookCount} hot-path hooks): ${ids}`,\n );\n }\n\n for (const verifier of verifiers) {\n if (!verifier.bootTest) continue;\n let result: VerifierResult;\n try {\n result = await verifier.bootTest();\n } catch (err) {\n result = fail(\n verifier.contractId,\n `bootTest threw: ${(err as Error).message?.slice(0, 80) ?? \"unknown\"}`,\n 0,\n );\n }\n reporter.report(result, \"boot\");\n if (result.ok) passed += 1;\n else failed += 1;\n }\n\n const totalMs = nowMs() - t0;\n if (ctx.logVerifierResults) {\n const verb = failed === 0 ? \"passed\" : \"complete\";\n ctx.console.info(\n `[crossdeck] Self-verification ${verb} — ${passed} passed, ${failed} failed (${totalMs}ms)`,\n );\n }\n return { passed, failed, totalMs };\n}\n\n/**\n * Dispatchers per hot-path hook. The SDK's call site invokes one of\n * these AFTER the operation completes, with the observation that\n * verifiers need to decide pass/fail.\n *\n * Each dispatcher is cheap when `disableContractAssertions` is true\n * — short-circuits at the top and skips the verifier iteration\n * entirely.\n */\n\nexport function runOnIdentify(\n verifiers: readonly ContractVerifier[],\n reporter: VerifierReporter,\n ctx: VerifierContext,\n obs: IdentifyObservation,\n): void {\n if (ctx.disableContractAssertions) return;\n for (const verifier of verifiers) {\n const hook = verifier.hooks?.onIdentify;\n if (!hook) continue;\n let result: VerifierResult;\n try {\n result = hook(obs);\n } catch (err) {\n result = fail(\n verifier.contractId,\n `hook threw: ${(err as Error).message?.slice(0, 80) ?? \"unknown\"}`,\n 0,\n );\n }\n reporter.report(result, \"hot_path\", \"identify\");\n }\n}\n\nexport function runOnTrack(\n verifiers: readonly ContractVerifier[],\n reporter: VerifierReporter,\n ctx: VerifierContext,\n obs: TrackObservation,\n): void {\n if (ctx.disableContractAssertions) return;\n for (const verifier of verifiers) {\n const hook = verifier.hooks?.onTrack;\n if (!hook) continue;\n let result: VerifierResult;\n try {\n result = hook(obs);\n } catch (err) {\n result = fail(\n verifier.contractId,\n `hook threw: ${(err as Error).message?.slice(0, 80) ?? \"unknown\"}`,\n 0,\n );\n }\n reporter.report(result, \"hot_path\", \"track\");\n }\n}\n\nexport function runOnSyncPurchases(\n verifiers: readonly ContractVerifier[],\n reporter: VerifierReporter,\n ctx: VerifierContext,\n obs: SyncPurchasesObservation,\n): void {\n if (ctx.disableContractAssertions) return;\n for (const verifier of verifiers) {\n const hook = verifier.hooks?.onSyncPurchases;\n if (!hook) continue;\n let result: VerifierResult;\n try {\n result = hook(obs);\n } catch (err) {\n result = fail(\n verifier.contractId,\n `hook threw: ${(err as Error).message?.slice(0, 80) ?? \"unknown\"}`,\n 0,\n );\n }\n reporter.report(result, \"hot_path\", \"syncPurchases\");\n }\n}\n\nexport function runOnErrorParse(\n verifiers: readonly ContractVerifier[],\n reporter: VerifierReporter,\n ctx: VerifierContext,\n obs: ErrorParseObservation,\n): void {\n if (ctx.disableContractAssertions) return;\n for (const verifier of verifiers) {\n const hook = verifier.hooks?.onErrorParse;\n if (!hook) continue;\n let result: VerifierResult;\n try {\n result = hook(obs);\n } catch (err) {\n result = fail(\n verifier.contractId,\n `hook threw: ${(err as Error).message?.slice(0, 80) ?? \"unknown\"}`,\n 0,\n );\n }\n reporter.report(result, \"hot_path\", \"errorParse\");\n }\n}\n\n// ============================================================================\n// Default detection — DEBUG vs RELEASE.\n// ============================================================================\n\n/**\n * The verifyContractsAtBoot + logVerifierResults defaults. `true` in\n * development, `false` in production. The detection logic:\n *\n * - Vite / Webpack / esbuild: `process.env.NODE_ENV !== \"production\"`\n * is set at bundle time. The literal string substitution makes\n * this dead-code-eliminable, so production bundles strip the\n * verifier console paths entirely.\n * - Other bundlers: fall back to `globalThis.__DEV__` (React Native\n * parity).\n *\n * Callers can always override explicitly by passing the flag.\n */\nexport function defaultDebugModeFlag(): boolean {\n // Order matters: NODE_ENV check first so static bundlers can\n // eliminate the branch at build time.\n try {\n if (typeof process !== \"undefined\" && process.env) {\n const nodeEnv = process.env.NODE_ENV;\n if (typeof nodeEnv === \"string\") {\n return nodeEnv !== \"production\";\n }\n }\n } catch {\n /* process not defined — browser */\n }\n const devFlag = (globalThis as { __DEV__?: boolean }).__DEV__;\n if (typeof devFlag === \"boolean\") return devFlag;\n // Default safe: assume production. A customer who wants verifier\n // output in their browser must explicitly opt in.\n return false;\n}\n\n// ============================================================================\n// Helpers — internal.\n// ============================================================================\n\nfunction pass(\n contractId: string,\n evidence: string,\n durationMs: number,\n): VerifierPass {\n return { ok: true, contractId, evidence, durationMs };\n}\n\nfunction fail(\n contractId: string,\n failureReason: string,\n durationMs: number,\n): VerifierFail {\n return { ok: false, contractId, failureReason, durationMs };\n}\n\nfunction nowMs(): number {\n // Use Performance API if available for sub-ms precision; fall back\n // to Date.now in environments that lack it.\n if (typeof performance !== \"undefined\" && typeof performance.now === \"function\") {\n return performance.now();\n }\n return Date.now();\n}\n\nfunction truncate(s: string, max: number): string {\n return s.length > max ? s.slice(0, max - 1) + \"…\" : s;\n}\n\nfunction shortSuffix(suffix: string): string {\n // Strip the leading \"_\" and truncate to 8 chars for readable\n // console output: `7c44ee20`.\n const trimmed = suffix.startsWith(\"_\") ? suffix.slice(1) : suffix;\n return trimmed.length <= 12 ? trimmed : `${trimmed.slice(0, 4)}…${trimmed.slice(-4)}`;\n}\n\nfunction randomHex(len: number): string {\n const bytes = new Uint8Array(Math.ceil(len / 2));\n // Use crypto when available (browser, Node 19+, Workers); fall back\n // to Math.random for ancient environments.\n if (\n typeof globalThis !== \"undefined\" &&\n (globalThis as { crypto?: { getRandomValues?: (a: Uint8Array) => void } }).crypto\n ?.getRandomValues\n ) {\n (globalThis as { crypto: { getRandomValues: (a: Uint8Array) => void } }).crypto.getRandomValues(bytes);\n } else {\n for (let i = 0; i < bytes.length; i += 1) {\n bytes[i] = Math.floor(Math.random() * 256);\n }\n }\n let hex = \"\";\n for (let i = 0; i < bytes.length; i += 1) {\n hex += (bytes[i] as number).toString(16).padStart(2, \"0\");\n }\n return hex.slice(0, len);\n}\n\n// ============================================================================\n// In-memory storage adapter for the boot self-test.\n// ============================================================================\n\n/**\n * Memory-only storage used by the per-user-cache-isolation boot test.\n * Isolated from the customer's real localStorage / IndexedDB — the\n * verifier never touches their persisted state.\n */\nclass MemoryStorage {\n private readonly map = new Map<string, string>();\n getItem(key: string): string | null {\n return this.map.get(key) ?? null;\n }\n setItem(key: string, value: string): void {\n this.map.set(key, value);\n }\n removeItem(key: string): void {\n this.map.delete(key);\n }\n // Iteration support for EntitlementCache's clearAll() path. The\n // contract this verifier checks doesn't exercise clearAll, but\n // EntitlementCache's constructor calls hydrate() which reads from\n // storage — we need the cache to look \"empty\" until we plant data.\n keys(): string[] {\n return Array.from(this.map.keys());\n }\n // sha256Hex re-export so the verifier doesn't need to import it\n // separately (some bundlers tree-shake more aggressively when the\n // exports are colocated). Inert for the storage adapter itself.\n static _ = sha256Hex;\n}\n","/**\n * Stack-trace parser — normalises Chrome / Firefox / Safari / Edge\n * stack strings 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 browser 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 URL not starting with chrome-extension://, etc.).\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\n/**\n * Parse a single stack line. Returns null for header lines like\n * \"TypeError: x is not a function\" (those carry no frame info).\n *\n * Patterns recognised:\n * Chrome: \"at functionName (file:line:col)\"\n * Chrome: \"at file:line:col\"\n * Firefox: \"functionName@file:line:col\"\n * Safari: \"functionName@file:line:col\" (same as Firefox)\n * Node: \"at functionName (file:line:col)\" (Chrome-shaped)\n */\nfunction parseLine(line: string): StackFrame | null {\n // Chrome / Node V8 — with parens\n // Example: at Object.handleClick (https://app.com/app.js: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 // Chrome / Node V8 — anonymous, no parens\n // Example: at https://app.com/app.js: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 // Firefox / Safari\n // Example: handleClick@https://app.com/app.js: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 null\n // 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: browser extensions, well-known CDN URLs,\n * and the SDK's own bundle.\n */\nfunction isInAppFrame(filename: string): boolean {\n if (!filename) return true;\n if (/^(?:chrome|moz|safari|webkit)-extension:\\/\\//.test(filename)) return false;\n if (/\\bcdn\\.jsdelivr\\.net\\b/.test(filename)) return false;\n if (/\\bunpkg\\.com\\b/.test(filename)) return false;\n if (/\\bgoogletagmanager\\.com\\b/.test(filename)) return false;\n if (/\\bgoogle-analytics\\.com\\b/.test(filename)) return false;\n if (/\\b@cross-deck\\/web\\b/.test(filename)) return false;\n if (/\\/crossdeck\\.umd\\.min\\.js$/.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\" / \"Script error\" events from different call sites\n * stay separate. Without this fallback they all collapse into one\n * bucket and the dashboard can't distinguish them.\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 // Only fold the location fallback in when frames are empty — adding\n // it on top of frames would split otherwise-identical errors across\n // different colno values from minified bundles.\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 browsers; 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 // Force unsigned then 8-char hex.\n return (h >>> 0).toString(16).padStart(8, \"0\");\n}\n","/**\n * Error capture — the third Crossdeck USP.\n *\n * Catches every error source the browser can hand us and ships them as\n * Crossdeck events. The pipeline reuses the analytics queue:\n * - Same durable persistence (errors survive crashes / hard closes)\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. window.onerror — uncaught synchronous errors\n * 2. window.onunhandledrejection — unhandled promise rejections\n * 3. fetch() wrap — HTTP errors the app code didn't catch\n * 4. XMLHttpRequest wrap — same, for legacy XHR consumers\n * 5. Crossdeck.captureError(err) — manual API for try/catch blocks\n * 6. Crossdeck.captureMessage(msg) — non-error events you want to\n * surface as issues (e.g. \"we hit the soft-deprecated path\")\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 error\n * 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 second to defend\n * against runaway loops (e.g. an error in setInterval).\n * - Browser-extension noise is filtered by default — those errors\n * aren't the developer's fault and would otherwise drown the\n * signal.\n */\n\nimport { parseStack, fingerprintError, type StackFrame } from \"./stack-parser\";\nimport type { BreadcrumbBuffer, Breadcrumb } from \"./breadcrumbs\";\n\nexport type ErrorLevel = \"error\" | \"warning\" | \"info\";\n\nexport interface CapturedError {\n /** When the error fired (epoch ms). */\n timestamp: number;\n /** error.unhandled, error.unhandledrejection, error.handled, error.message, error.http */\n kind:\n | \"error.unhandled\"\n | \"error.unhandledrejection\"\n | \"error.handled\"\n | \"error.message\"\n | \"error.http\";\n level: ErrorLevel;\n message: string;\n /** The error class name when we have it (TypeError, ReferenceError, etc.) */\n errorType: string | null;\n /** Parsed stack frames, empty when unavailable. */\n frames: StackFrame[];\n /** Raw stack string for fallback display. */\n rawStack: string | null;\n /** Origin URL when available (window.onerror's `source` arg). */\n filename: string | null;\n lineno: number | null;\n colno: number | null;\n /** djb2 hash of message + top frames — group identical errors. */\n fingerprint: string;\n /** Snapshot of the breadcrumb buffer at the moment the error fired. */\n breadcrumbs: Breadcrumb[];\n /** Free-form context attached via Crossdeck.setContext(). */\n context: Record<string, unknown>;\n /** Free-form tags attached via Crossdeck.setTag(). */\n tags: Record<string, string>;\n /** \"TypeError: x is not a function\" → \"TypeError\" + \"x is not a function\". */\n /** Whether the error happened during a fetch / XHR. */\n http?: {\n url: string;\n method: string;\n status: number;\n statusText?: string;\n };\n}\n\nexport interface ErrorCaptureConfig {\n /** Master switch. Default true. */\n enabled: boolean;\n /** Catch window.onerror. Default true. */\n onError: boolean;\n /** Catch unhandledrejection. Default true. */\n onUnhandledRejection: boolean;\n /** Wrap fetch() to capture non-2xx responses. Default true. */\n wrapFetch: boolean;\n /** Wrap XMLHttpRequest. Default true. */\n wrapXhr: boolean;\n /** Capture console.error calls. Default false (noisy). */\n captureConsole: boolean;\n /**\n * Drop errors matching these substrings or regexes. Tested against\n * `message`. Default: a curated list of browser noise (ResizeObserver\n * loop, top-frame errors from extensions, etc.).\n */\n ignoreErrors: Array<string | RegExp>;\n /**\n * Only capture errors whose top in-app frame URL matches one of\n * these. Empty array means \"no allowlist — capture everything\".\n * Useful when you want to ignore third-party widget errors.\n */\n allowUrls: Array<string | RegExp>;\n /**\n * Drop errors whose top frame URL matches any of these.\n */\n denyUrls: Array<string | RegExp>;\n /**\n * Sample rate, 0–1. 1.0 = send every error. 0.5 = send half (per\n * fingerprint, deterministically — so a given user always either\n * sends a given error or never does). Default 1.0.\n */\n sampleRate: number;\n /**\n * Maximum errors per fingerprint per minute. Defends against runaway\n * loops. Default 5.\n */\n maxPerFingerprintPerMinute: number;\n /**\n * Total cap per session, regardless of fingerprint. Hard limit\n * after which we stop reporting until the next session. Default 100.\n */\n maxPerSession: number;\n}\n\nexport const DEFAULT_ERROR_CAPTURE: ErrorCaptureConfig = {\n enabled: true,\n onError: true,\n onUnhandledRejection: true,\n wrapFetch: true,\n wrapXhr: true,\n captureConsole: false,\n ignoreErrors: [\n // Classic browser noise. These aren't application bugs.\n \"ResizeObserver loop limit exceeded\",\n \"ResizeObserver loop completed with undelivered notifications\",\n \"Non-Error promise rejection captured\",\n // NOTE: We deliberately do NOT drop cross-origin \"Script error.\"\n // events here. The actionability principle (see denyUrls\n // comment below) draws the line at \"developer cannot act\":\n // cross-origin CORS opacity HAS a real, code-shaped fix (add\n // `crossorigin=\"anonymous\"` to the script tag + CORS headers\n // on the script's origin). The dashboard surfaces these with a\n // `cross_origin` tag pointing at that fix. Apps that genuinely\n // want them muted can re-add \"Script error\" to ignoreErrors\n // via init config.\n ],\n allowUrls: [],\n denyUrls: [\n // The actionability principle\n // ---------------------------\n // Crossdeck's default philosophy is \"classify, don't silently\n // drop\" — surfacing errors the developer can fix is more useful\n // than hiding them. That's right for cross-origin \"Script\n // error.\" (real, code-shaped CORS fix) and for plain\n // application bugs (obviously real).\n //\n // It's NOT right for events the developer cannot act on.\n // - A user's installed browser extension throwing inside\n // its own `chrome-extension://` URL: the developer can't\n // ship a fix for someone else's extension.\n // - An ad blocker preventing `googletagmanager.com` from\n // loading: the developer can't unblock the user's blocker.\n //\n // Capturing these creates a noise tab that's always non-empty\n // and never actionable, which trains the dev to ignore the\n // noise tab — and then the actionable noise (CORS opacity,\n // etc.) gets ignored along with it. Same lesson as Crossdeck's\n // \"0-2 notifications a week\" discipline: a signal that fires\n // constantly with nothing actionable behind it stops being a\n // signal.\n //\n // So we drop at the source for the unactionable category, and\n // keep capturing for the actionable category. Same principle\n // applied to two structurally different inputs — not a new\n // philosophy.\n //\n // The bootstrap list below is the minimum that ships in code.\n // The full, versioned list arrives at boot via /v1/config —\n // see `backend/src/lib/error-noise-deny-list.ts`. The SDK\n // applies the union of (bootstrap + remote), so a freshly-\n // installed SDK protects users immediately, and remote updates\n // (new ad-network domains, new pixel hosts) reach every\n // install without an SDK release.\n //\n // The backend Layer-2 classifier\n // (`backend/src/api/lib/noise-classifier.ts`) is the safety\n // net for events that slip past these patterns — older SDK\n // versions in the wild that haven't fetched the remote list\n // yet, or brand-new patterns the list hasn't named.\n /^chrome-extension:\\/\\//,\n /^moz-extension:\\/\\//,\n /^safari-extension:\\/\\//,\n /^webkit-extension:\\/\\//,\n /^safari-web-extension:\\/\\//,\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 a\n * modified `CapturedError` to forward).\n *\n * It's a getter — not a static function — so `setErrorBeforeSend()`\n * can install or replace the hook after init() without re-creating\n * the tracker. Pre-fix this was a captured value: the tracker took\n * a snapshot of `null` at construction time and never re-read state,\n * so every customer's PII scrubber installed later was silently inert.\n * Bank-grade rule: a hook the customer can call into MUST take effect\n * the instant it's installed.\n *\n * Returning `null` from the GETTER means \"no hook configured\" and\n * the report goes through unmodified — distinct from returning a\n * function-that-returns-null (which means \"drop this specific report\").\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 — otherwise an outage on the\n * Crossdeck backend would trigger captureHttp → enqueue →\n * `POST /events` → fail again → captureHttp → ∞ until the queue\n * gives up on a permanent 4xx (Batch B fix) or runs forever on a\n * 5xx. Pre-fix the skip pattern was hardcoded to\n * `api.cross-deck.com`, which failed customers using staging /\n * regional / self-hosted-relay base URLs. Audit punch list P0 #7.\n *\n * Null / omitted when extraction from baseUrl fails (malformed URL)\n * OR when the test harness doesn't supply one — the tracker falls\n * through to \"capture everything\" rather 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 if (typeof globalThis === \"undefined\" || !(\"window\" in globalThis)) return;\n\n const w = (globalThis as { window: Window }).window;\n\n if (this.opts.config.onError) this.installOnErrorListener(w);\n if (this.opts.config.onUnhandledRejection) this.installRejectionListener(w);\n if (this.opts.config.wrapFetch) this.installFetchWrap(w);\n if (this.opts.config.wrapXhr) this.installXhrWrap(w);\n if (this.opts.config.captureConsole) this.installConsoleWrap();\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?: { context?: Record<string, unknown>; tags?: Record<string, string>; level?: ErrorLevel },\n ): void {\n if (!this.opts.isConsented()) return;\n try {\n const captured = this.buildFromUnknown(error, \"error.handled\", options?.level ?? \"error\");\n if (options?.context) 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's\n // error handler.\n }\n }\n\n /**\n * Capture a non-error event as an issue. For \"we hit a soft-warning\n * code path\" / \"deprecated API used\" kinds of signals. Pairs with\n * 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 installOnErrorListener(w: Window): void {\n const handler = (event: ErrorEvent): void => {\n if (this._reporting) return;\n if (!this.opts.isConsented()) return;\n try {\n this._reporting = true;\n const captured = this.buildFromErrorEvent(event);\n this.maybeReport(captured);\n } catch {\n // swallow\n } finally {\n this._reporting = false;\n }\n };\n w.addEventListener(\"error\", handler, true);\n this.cleanups.push(() => w.removeEventListener(\"error\", handler, true));\n }\n\n private installRejectionListener(w: Window): void {\n const handler = (event: PromiseRejectionEvent): void => {\n if (this._reporting) return;\n if (!this.opts.isConsented()) return;\n try {\n this._reporting = true;\n const captured = this.buildFromUnknown(\n event.reason,\n \"error.unhandledrejection\",\n \"error\",\n );\n this.maybeReport(captured);\n } catch {\n // swallow\n } finally {\n this._reporting = false;\n }\n };\n w.addEventListener(\"unhandledrejection\", handler);\n this.cleanups.push(() => w.removeEventListener(\"unhandledrejection\", handler));\n }\n\n /**\n * Wrap fetch() so failed HTTP requests get auto-captured. We do NOT\n * call this an \"error\" for 4xx (those are often expected — auth\n * required, validation failed). Only 5xx + network failures fire.\n */\n private installFetchWrap(w: Window): void {\n const origFetch = w.fetch?.bind(w);\n if (!origFetch) return;\n const wrapped = async (...args: Parameters<typeof fetch>): Promise<Response> => {\n const input = args[0];\n const init = args[1] ?? {};\n const url = typeof input === \"string\" ? input : (input as Request)?.url ?? \"\";\n const method = (init.method || \"GET\").toUpperCase();\n const start = Date.now();\n\n // Breadcrumb for the request itself — fires regardless of outcome.\n // Skip self-requests: an error report's breadcrumb trail showing\n // \"POST https://api.cross-deck.com/v1/events\" entries is noise the\n // engineer reading the error doesn't care about (the SDK itself\n // emitted them, not the user code under inspection). Same predicate\n // as captureHttp's self-skip. Audit P2 polish.\n if (!isSelfRequest(url, this.opts.selfHostname)) {\n this.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 && this.opts.isConsented()) {\n // Self-skip Crossdeck's own API to avoid reporting our own\n // backend errors back to ourselves (cycle hazard if the\n // outage is on our side).\n if (!isSelfRequest(url, this.opts.selfHostname)) {\n this.captureHttp({\n url,\n method,\n status: response.status,\n statusText: response.statusText,\n });\n }\n }\n return response;\n } catch (err) {\n // A status-0 failure can be a genuine outage (DNS, connection refused)\n // but is far more often client-environment noise — an adblocker\n // blocking the request, the user offline, or an aborted navigation.\n // Skip the unambiguous-noise cases (see isClientNetworkNoise) so we\n // never raise a production error for them; cross-origin outages still\n // report. This is what stopped first-party fetches blocked by a dev's\n // own adblocker (e.g. contracts-registry.json) paging as HIGH PRIORITY.\n if (\n this.opts.isConsented() &&\n !url.includes(\"api.cross-deck.com\") &&\n !isClientNetworkNoise(url, err, w)\n ) {\n this.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 w.fetch = wrapped as typeof fetch;\n this.cleanups.push(() => {\n // Restore only if we're still the active wrapper.\n if (w.fetch === wrapped) w.fetch = origFetch;\n });\n }\n\n /**\n * Wrap XMLHttpRequest for legacy consumers (jQuery $.ajax under the\n * hood, older bundlers). Same capture semantics as fetch.\n */\n private installXhrWrap(w: Window): void {\n const xhrCtor = (w as Window & { XMLHttpRequest?: typeof XMLHttpRequest }).XMLHttpRequest;\n const proto = xhrCtor?.prototype;\n if (!proto) return;\n const origOpen = proto.open;\n const origSend = proto.send;\n\n const tracker = this;\n proto.open = function (this: XMLHttpRequest, method: string, url: string, ...rest: unknown[]): void {\n (this as XMLHttpRequest & { _cdMethod?: string; _cdUrl?: string })._cdMethod = method;\n (this as XMLHttpRequest & { _cdMethod?: string; _cdUrl?: string })._cdUrl = url;\n // Cast args to match the lib signature.\n return origOpen.apply(this, [method, url, ...(rest as [boolean, string?, string?])]);\n };\n proto.send = function (this: XMLHttpRequest, body?: Document | XMLHttpRequestBodyInit | null): void {\n const xhr = this as XMLHttpRequest & { _cdMethod?: string; _cdUrl?: string };\n const onLoad = (): void => {\n try {\n if (xhr.status >= 500 && tracker.opts.isConsented()) {\n const url = xhr._cdUrl ?? \"\";\n if (!isSelfRequest(url, tracker.opts.selfHostname)) {\n tracker.captureHttp({\n url,\n method: (xhr._cdMethod ?? \"GET\").toUpperCase(),\n status: xhr.status,\n statusText: xhr.statusText,\n });\n }\n }\n } catch {\n // swallow\n }\n };\n xhr.addEventListener(\"loadend\", onLoad);\n return origSend.apply(this, [body ?? null]);\n };\n\n this.cleanups.push(() => {\n proto.open = origOpen;\n proto.send = origSend;\n });\n }\n\n private installConsoleWrap(): void {\n const console = (globalThis as { console?: Console }).console;\n if (!console) return;\n const orig = console.error.bind(console);\n console.error = (...args: unknown[]) => {\n try {\n if (this.opts.isConsented()) {\n this.captureMessage(args.map((a) => safeStringify(a)).join(\" \"), \"error\");\n }\n } catch {\n // swallow\n }\n return orig(...args);\n };\n this.cleanups.push(() => {\n console.error = orig;\n });\n }\n\n // ============================================================\n // Builders\n // ============================================================\n\n private buildFromErrorEvent(event: ErrorEvent): CapturedError {\n const err = event.error;\n const filename = event.filename || null;\n const lineno = typeof event.lineno === \"number\" && event.lineno > 0 ? event.lineno : null;\n const colno = typeof event.colno === \"number\" && event.colno > 0 ? event.colno : null;\n\n // ── Cross-origin script error path ────────────────────────────────\n // Signature: browser hands us \"Script error.\" (or empty), null\n // error object, and no usable location. We can't recover the real\n // message — but instead of stashing a useless \"Unknown error\",\n // we label the event clearly and tag it so the dashboard groups\n // it as a distinct, actionable category with a known remediation\n // (add crossorigin=\"anonymous\" + CORS headers on the script's\n // origin).\n const isCrossOriginStripped =\n err == null &&\n !filename &&\n lineno == null &&\n (event.message === \"Script error.\" ||\n event.message === \"Script error\" ||\n !event.message);\n if (isCrossOriginStripped) {\n const message =\n \"Cross-origin script error (browser hid details — script needs crossorigin attribute + CORS headers)\";\n return {\n timestamp: Date.now(),\n kind: \"error.unhandled\",\n level: \"error\",\n message,\n errorType: \"ScriptError\",\n frames: [],\n rawStack: null,\n filename: null,\n lineno: null,\n colno: null,\n // No location to fingerprint by — all of these will share one\n // group, which is correct: developer fixes them all with the\n // same CORS change.\n fingerprint: fingerprintError(message, []),\n breadcrumbs: this.opts.breadcrumbs.snapshot(),\n context: this.opts.getContext(),\n tags: { ...this.opts.getTags(), cross_origin: \"true\" },\n };\n }\n\n // ── Normal path ───────────────────────────────────────────────────\n // Pull every drop of signal out of the raw `event.error`, then fall\n // back to `event.message` if the value didn't yield one of its own.\n const payload = coerceErrorPayload(err);\n const message = (\n payload.message ||\n event.message ||\n \"Unknown error\"\n ).slice(0, 1024);\n const stack = err instanceof Error ? err.stack ?? null : null;\n const frames = parseStack(stack);\n const errorType = payload.errorType ?? null;\n\n const context = payload.extras\n ? { ...this.opts.getContext(), __error_extras: payload.extras }\n : this.opts.getContext();\n\n return {\n timestamp: Date.now(),\n kind: \"error.unhandled\",\n level: \"error\",\n message,\n errorType,\n frames,\n rawStack: stack,\n filename,\n lineno,\n colno,\n // Location fallback ensures distinct call sites stay separate\n // even when the message is generic (\"Unknown error\",\n // \"[object Object]\") and there are no parseable frames.\n fingerprint: fingerprintError(message, frames, {\n filename,\n lineno,\n colno,\n errorType,\n }),\n breadcrumbs: this.opts.breadcrumbs.snapshot(),\n context,\n tags: this.opts.getTags(),\n };\n }\n\n private buildFromUnknown(\n err: unknown,\n kind: CapturedError[\"kind\"],\n level: ErrorLevel,\n ): CapturedError {\n const payload = coerceErrorPayload(err);\n const message = (payload.message || \"Unknown error\").slice(0, 1024);\n const stack = err instanceof Error ? err.stack ?? null : null;\n const frames = parseStack(stack);\n const errorType = payload.errorType ?? null;\n\n const context = payload.extras\n ? { ...this.opts.getContext(), __error_extras: payload.extras }\n : this.opts.getContext();\n\n return {\n timestamp: Date.now(),\n kind,\n level,\n message,\n errorType,\n frames,\n rawStack: stack,\n filename: null,\n lineno: null,\n colno: null,\n fingerprint: fingerprintError(message, frames, { errorType }),\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(`HTTP ${info.status} ${info.method}`, [], {\n filename: info.url,\n 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.passesUrlGate(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 installed\n // via `setErrorBeforeSend()` AFTER init() takes effect on THIS\n // error, not just future ones constructed by a future tracker.\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 report.\n // 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; the next error\n // attempt will retry through the same queue.\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 passesUrlGate(err: CapturedError): boolean {\n const topFrame = err.frames.find((f) => f.filename) ?? null;\n const url = topFrame?.filename ?? err.filename ?? \"\";\n if (!url) return true; // unknown URL — let it through\n\n for (const pat of this.opts.config.denyUrls) {\n if (typeof pat === \"string\" && url.includes(pat)) return false;\n if (pat instanceof RegExp && pat.test(url)) return false;\n }\n if (this.opts.config.allowUrls.length > 0) {\n for (const pat of this.opts.config.allowUrls) {\n if (typeof pat === \"string\" && url.includes(pat)) return true;\n if (pat instanceof RegExp && pat.test(url)) return true;\n }\n return false;\n }\n return true;\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 // Drop entries older than the window.\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 * The thrown-value coercer.\n *\n * The browser's error pipelines (window.onerror, unhandledrejection,\n * developer `throw`) hand us values of every shape — Error instances,\n * DOMExceptions, plain objects, primitives, even null. Earlier\n * versions of this code wrote \"Unknown error\" whenever the value\n * wasn't an Error with a non-empty `.message`, which silently\n * collapsed entire classes of real bugs into one unhelpful bucket.\n *\n * This function extracts the maximum information available without\n * ever throwing (Symbol keys, recursive proxies, throwing toString —\n * all defended against). It returns three pieces:\n *\n * - message: the human-readable headline, never empty for any\n * non-null/non-undefined input\n * - errorType: the constructor name when we can discover one\n * (Error subclass, DOMException, custom class) —\n * feeds the dashboard's \"type · message\" header\n * - extras: additional fields worth keeping (Error.cause chain,\n * .code/.status/.response on common patterns, any\n * enumerable own properties on an Error subclass).\n * Stashed on context.__error_extras for the\n * dashboard's \"raw event\" panel.\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\n if (typeof v === \"string\") {\n return { message: v, errorType: null, extras: null };\n }\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 { message: `(thrown function: ${v.name || \"anonymous\"})`, errorType: \"function\", extras: null };\n }\n\n // Object-ish from here on — Error, DOMException, Response, plain\n // objects, custom classes, etc.\n if (v instanceof Error) {\n const errorType =\n v.name || v.constructor?.name || \"Error\";\n const message =\n typeof v.message === \"string\" && v.message.length > 0\n ? v.message\n : safeToString(v) || errorType;\n\n const extras: Record<string, unknown> = {};\n\n // ES2022 Error.cause — walk up to 5 levels so a service-layer\n // wrapper error doesn't hide the underlying network failure.\n const causeChain = collectCauseChain(v);\n if (causeChain.length > 0) extras.cause = causeChain;\n\n // Common HTTP/RPC patterns attach status/code/response to thrown\n // values. Capture them without forcing every wrapper class to\n // override toString.\n for (const key of [\"code\", \"status\", \"statusCode\", \"errno\", \"response\", \"data\", \"detail\", \"details\"] 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\n // Any other enumerable own properties (custom Error subclasses\n // that add fields).\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\n return {\n message,\n errorType,\n extras: Object.keys(extras).length > 0 ? extras : null,\n };\n }\n\n // Response — fetch().then(r => { if (!r.ok) throw r }) is a common\n // pattern, and the bare Response is otherwise unreadable.\n if (typeof Response !== \"undefined\" && v instanceof Response) {\n return {\n message: `HTTP ${v.status} ${v.statusText || \"\"}${v.url ? ` ${v.url}` : \"\"}`.trim(),\n errorType: \"Response\",\n extras: { status: v.status, statusText: v.statusText, url: v.url, type: v.type },\n };\n }\n\n // DOMException, Event, anything with a useful native toString —\n // capture the constructor name as the type, then prefer the\n // object's .message field if it has one (DOMException does;\n // ErrorEvent does; many polyfills do).\n if (typeof v === \"object\") {\n const obj = v as Record<string, unknown>;\n const ctorName =\n (obj.constructor && typeof obj.constructor === \"function\" && (obj.constructor as { name?: string }).name) ||\n null;\n\n const ownMessage = typeof obj.message === \"string\" && obj.message ? obj.message : null;\n const ownName = typeof obj.name === \"string\" && obj.name ? obj.name : null;\n\n let jsonForm: string | null = null;\n try {\n const serialised = JSON.stringify(obj);\n // JSON.stringify of an empty object is \"{}\", which is useless\n // as a message but tells us we have a thrown object with no\n // enumerable own props.\n jsonForm = serialised === \"{}\" ? null : serialised;\n } catch {\n jsonForm = null;\n }\n\n const fallbackString = safeToString(obj);\n const message =\n ownMessage ??\n jsonForm ??\n (fallbackString && fallbackString !== \"[object Object]\" ? fallbackString : null) ??\n (ctorName ? `(thrown ${ctorName} with no message)` : \"(thrown object with no message)\");\n\n const errorType = ownName ?? ctorName ?? null;\n\n // Best-effort extras for objects: keep up to ~20 enumerable own\n // properties, JSON-safe.\n const extras: Record<string, unknown> = {};\n let count = 0;\n for (const key of Object.keys(obj)) {\n if (count >= 20) break;\n if (key === \"message\" || key === \"name\") continue;\n const val = obj[key];\n if (typeof val === \"function\") continue;\n extras[key] = safeClone(val);\n count++;\n }\n\n return {\n message,\n errorType,\n extras: Object.keys(extras).length > 0 ? extras : null,\n };\n }\n\n // Should be unreachable, but: fall back to coerced string.\n return { message: safeToString(v) || \"(unstringifiable thrown value)\", errorType: null, extras: null };\n}\n\nfunction collectCauseChain(err: Error): Array<{ name: string; message: string }> {\n const out: Array<{ name: string; message: string }> = [];\n let cur: unknown = (err as Error & { cause?: unknown }).cause;\n let depth = 0;\n while (cur != null && depth < 5) {\n if (cur instanceof Error) {\n out.push({ name: cur.name || \"Error\", message: cur.message || \"\" });\n cur = (cur as Error & { cause?: unknown }).cause;\n } else {\n out.push({ name: \"non-Error\", message: safeToString(cur) });\n cur = null;\n }\n depth++;\n }\n return out;\n}\n\nfunction safeToString(v: unknown): string {\n try {\n const s = Object.prototype.toString.call(v);\n if (s !== \"[object Object]\") return s;\n // Try the value's own toString if it overrides Object's default.\n const own = (v as { toString?: () => unknown })?.toString;\n if (typeof own === \"function\" && own !== Object.prototype.toString) {\n const r = own.call(v);\n if (typeof r === \"string\") return r;\n }\n return s;\n } catch {\n return \"(throwing toString)\";\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 // JSON.stringify will throw on circular structures; that's fine,\n // we fall back to the toString below.\n const s = JSON.stringify(v);\n return s === undefined ? safeToString(v) : JSON.parse(s);\n } catch {\n return safeToString(v);\n }\n}\n\nfunction safeStringify(v: unknown): string {\n return coerceErrorPayload(v).message;\n}\n\n/**\n * Extract the hostname from a URL string for use as the\n * `selfHostname` field on the ErrorTracker. Returns null on malformed\n * input — the tracker's downstream self-skip check treats `null` as\n * \"no self to skip\" and captures everything (safer than swallowing\n * legitimate errors on a config typo).\n *\n * Lowercased for case-insensitive comparison (`Api.Cross-Deck.com`\n * 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 / XHR wrappers to skip captureHttp on Crossdeck's\n * own 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 URLs\n * — the SDK only ever uses absolute URLs, so a relative URL can't\n * be the SDK's own request.\n */\nexport function isSelfRequest(requestUrl: string, selfHostname: string | null | undefined): 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/**\n * A failed fetch (status 0 — an opaque TypeError with no HTTP response) is,\n * far more often than a real outage, client-environment noise: an adblocker or\n * privacy extension blocking the request, the user offline, or a request\n * aborted by a navigation. The Fetch spec deliberately makes these\n * indistinguishable from a genuine network fault, so a status-0 capture cannot\n * tell an outage from a blocked request — which is why \"Failed to fetch\" is the\n * single biggest false-alarm source in browser error tracking. This predicate\n * identifies the unambiguous-noise cases we refuse to raise a production error\n * for:\n * • AbortError — request cancelled by a navigation or explicit abort;\n * • offline — navigator.onLine === false, there is no network at all;\n * • SAME-ORIGIN — the page itself loaded from this origin, so the origin is\n * demonstrably reachable; a status-0 to it is client-side blocking (e.g. an\n * adblocker on a first-party asset), not a server fault. Genuine\n * same-origin server failures return an HTTP status (5xx) and are captured\n * on the success path, not here.\n * Cross-origin status-0 (a third-party API genuinely unreachable) still\n * reports — that keeps the real signal while dropping the noise.\n */\nexport function isClientNetworkNoise(requestUrl: string, err: unknown, w: Window): boolean {\n if (err instanceof Error && err.name === \"AbortError\") return true;\n const nav = (w as Window & { navigator?: { onLine?: boolean } }).navigator;\n if (nav && nav.onLine === false) return true;\n const pageHref = w.location?.href;\n const pageOrigin = w.location?.origin;\n if (!pageOrigin) return false;\n try {\n return new URL(requestUrl, pageHref ?? pageOrigin).origin === pageOrigin;\n } catch {\n return false;\n }\n}\n","/**\n * Public API surface for @cross-deck/web.\n *\n * Usage (browser):\n *\n * import { Crossdeck } from \"@cross-deck/web\";\n *\n * Crossdeck.init({\n * appId: \"app_web_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 *\n * Usage (Node):\n *\n * import { Crossdeck, MemoryStorage } from \"@cross-deck/web\";\n *\n * Crossdeck.init({\n * appId: \"app_node_xxx\",\n * publicKey: \"cd_pub_test_…\",\n * environment: \"sandbox\",\n * storage: new MemoryStorage(), // session-only persistence\n * autoHeartbeat: false, // skip the boot ping in scripts\n * });\n */\n\nimport { CrossdeckError } from \"./errors\";\nimport { HttpClient, SDK_NAME, SDK_VERSION, DEFAULT_BASE_URL } from \"./http\";\nimport { IdentityStore } 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 { CookieStorage, detectDefaultStorage, MemoryStorage } from \"./storage\";\nimport { randomChars } from \"./identity\";\nimport { collectDeviceInfo, type DeviceInfo } from \"./device-info\";\nimport { AutoTracker, DEFAULT_AUTO_TRACK, type AutoTrackConfig } from \"./auto-track\";\nimport { ConsoleDebugLogger, findSensitivePropertyKeys, type DebugLogger } from \"./debug\";\nimport { validateEventProperties } from \"./event-validation\";\nimport { SuperPropertyStore } from \"./super-properties\";\nimport {\n INTERNAL_OPT_OUT_PROPERTY,\n isInternalOptOut,\n processInternalOptOutUrl,\n} from \"./internal-opt-out\";\nimport { WebVitalsTracker } from \"./web-vitals\";\nimport { ConsentManager, scrubPii, scrubPiiFromProperties, type ConsentState } from \"./consent\";\nimport { BreadcrumbBuffer, type Breadcrumb } from \"./breadcrumbs\";\nimport type { ContractFailureInput } from \"./contracts\";\nimport { sendDiagnosticTelemetry } from \"./_diagnostic-telemetry\";\nimport {\n STATIC_VERIFIERS,\n VerifierReporter,\n buildVerifierContext,\n buildFlushIntervalVerifier,\n defaultDebugModeFlag,\n runBootSelfTest,\n runOnErrorParse,\n runOnIdentify,\n runOnSyncPurchases,\n runOnTrack,\n type ContractVerifier,\n type VerifierContext,\n} from \"./_contract-verifiers\";\nimport {\n DEFAULT_ERROR_CAPTURE,\n ErrorTracker,\n extractSelfHostname,\n type CapturedError,\n type ErrorCaptureConfig,\n type ErrorLevel,\n} from \"./error-capture\";\nimport type {\n AliasResult,\n AutoTrackOptions,\n CrossdeckOptions,\n Diagnostics,\n EntitlementsListResponse,\n Environment,\n EventProperties,\n GroupTraits,\n HeartbeatResponse,\n IdentifyOptions,\n PublicEntitlement,\n PurchaseResult,\n} from \"./types\";\n\ninterface InternalState {\n http: HttpClient;\n identity: IdentityStore;\n entitlements: EntitlementCache;\n events: EventQueue;\n autoTracker: AutoTracker | null;\n webVitals: WebVitalsTracker | null;\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 /** Cached enrichment payload merged into every event's properties. */\n deviceInfo: DeviceInfo;\n options: Required<\n Omit<\n CrossdeckOptions,\n | \"storage\"\n | \"sdkVersion\"\n | \"autoTrack\"\n | \"appVersion\"\n | \"debug\"\n | \"respectDnt\"\n | \"scrubPii\"\n // The three contract-verifier flags are resolved + consumed\n // outside state.options (see CrossdeckClient.verifierCtx /\n // verifierReporter / verifiers fields). They don't need to\n // round-trip through the InternalState options struct.\n | \"verifyContractsAtBoot\"\n | \"logVerifierResults\"\n | \"disableContractAssertions\"\n >\n > & {\n sdkVersion: string;\n autoTrack: AutoTrackConfig;\n appVersion: string | null;\n };\n debug: DebugLogger;\n developerUserId: string | null;\n /** Cleanup the unload-flush listeners installed in init(). */\n uninstallUnloadFlush: (() => void) | null;\n /** Most-recent server time observed via heartbeat (epoch ms). */\n lastServerTime: number | null;\n /** Local Date.now() captured at the same moment as lastServerTime. */\n lastClientTime: number | null;\n}\n\nexport class CrossdeckClient {\n private state: InternalState | null = null;\n\n // ────────────────────────────────────────────────────────────────\n // Contract verifier layer (see sdks/web/src/_contract-verifiers.ts).\n //\n // Three flags govern this layer (CrossdeckOptions):\n // verifyContractsAtBoot — boot self-test on Crossdeck.start\n // logVerifierResults — print PASS results to console\n // disableContractAssertions — total kill-switch\n //\n // When the kill-switch is set, all three fields stay null and\n // every hot-path dispatcher short-circuits — zero runtime cost.\n // Otherwise: populated once at init(), referenced from every\n // hot-path call site (identify, syncPurchases, ...).\n // ────────────────────────────────────────────────────────────────\n private verifiers: readonly ContractVerifier[] | null = null;\n private verifierReporter: VerifierReporter | null = null;\n private verifierCtx: VerifierContext | null = null;\n\n /**\n * Boot the SDK. Idempotent — calling init twice with the same options\n * is a no-op; calling with different options replaces the previous\n * configuration.\n *\n * NorthStar §11.1: signature is `Crossdeck.init({ appId, publicKey,\n * environment })`. The trio is validated up-front so a typo'd key or a\n * mismatched env fails fast at boot rather than at first event-flush.\n */\n init(options: CrossdeckOptions): void {\n // Idempotent re-init: tear down listeners from any prior init()\n // before constructing the new state. Pre-fix\n // `state.uninstallUnloadFlush` was set but never invoked anywhere,\n // so calling init() a second time (config swap during dev,\n // multi-tenant SDK shell, hot-module-replacement) silently\n // accumulated duplicate `pagehide` / `beforeunload` /\n // `visibilitychange` listeners. Each one fired a redundant flush.\n // Audit P2: now teardown runs on every re-init.\n if (this.state) {\n try { this.state.uninstallUnloadFlush?.(); } catch { /* ignore */ }\n try { this.state.autoTracker?.uninstall(); } catch { /* ignore */ }\n try { this.state.webVitals?.uninstall(); } catch { /* ignore */ }\n try { this.state.errors?.uninstall(); } catch { /* ignore */ }\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 /\n // multi-tenant SDK shell. flush({keepalive:true}) cancels\n // the timer (see EventQueue.cancelTimerIfSet) and ships\n // queued events out under the prior init's identity.\n //\n // CRITICAL: do NOT clear the persistent event store here.\n // The durable queue belongs to the SDK lifetime, not the\n // init() lifetime — a survived crash mid-flush re-hydrates\n // on the next init.\n try { void this.state.events.flush({ keepalive: true }); } catch { /* ignore */ }\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: \"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 // Key prefix must match the declared environment, otherwise prod\n // telemetry could silently route into sandbox dashboards (or vice\n // versa). NorthStar §15 calls this out as a \"fail loudly\" condition.\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 the publishable key with the environment declaration.`,\n });\n }\n\n // Localhost auto-detection. When the SDK boots from localhost /\n // 127.0.0.1 / *.local / RFC1918 private IPs, automatically switch\n // to a fully-local \"dev mode\" — no network calls fire, all SDK\n // methods (track, identify, isEntitled) work against in-memory +\n // localStorage state only. The dev's live dashboard stays clean\n // even if they forgot to swap their cd_pub_live_* key for a\n // cd_pub_test_* one.\n //\n // Stripe-grade default. Confidence-first means we trust the dev's\n // key prefix in production; localhost is the one place where we\n // proactively prevent accidental pollution.\n const localDevMode = isLocalHostname();\n\n const storage = options.storage ?? detectDefaultStorage();\n const persistIdentity = options.persistIdentity ?? true;\n const autoTrack = resolveAutoTrack(options.autoTrack);\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 // 1500ms idle window. Short enough that an event queued on page\n // load still flushes if the user leaves quickly (the keepalive\n // pagehide handler picks up anything that doesn't); long enough\n // that bursts of clicks coalesce into one network round-trip.\n // v1.4.0 Phase 3.3 — flush interval default parity. Pre-\n // v1.4.0: Web/Node 1500ms, RN/Swift/Android 5000ms. All\n // converged on 2000ms (the Stripe-adjacent industry norm)\n // so cross-platform funnels show events landing at the\n // same cadence on every SDK. Per-instance override stays.\n eventFlushIntervalMs: options.eventFlushIntervalMs ?? 2000,\n sdkVersion: options.sdkVersion ?? SDK_VERSION,\n autoTrack,\n appVersion: options.appVersion ?? null,\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 // Localhost auto-route: HttpClient short-circuits every request\n // to a successful no-op response when localDevMode is set.\n // SDK methods continue to work locally; nothing reaches the\n // server.\n localDevMode,\n // Contract verifier hot-path hook — fires the `error-envelope-shape`\n // verifier on every parsed wire error, BEFORE the throw bubbles\n // back to user code. Centralised here so we don't need a try/catch\n // around every `await s.http.request(...)` call site downstream.\n // No-op when the verifier layer is disabled (verifiers === null).\n onErrorParsed: (err) => {\n if (this.verifiers && this.verifierReporter && this.verifierCtx) {\n runOnErrorParse(this.verifiers, this.verifierReporter, this.verifierCtx, {\n errorType: err.type,\n errorCode: err.code,\n requestId: err.requestId ?? null,\n httpStatus: typeof err.status === \"number\" ? err.status : 0,\n });\n }\n },\n });\n\n if (localDevMode) {\n // Single console line on first init — direct, not scolding.\n // Tells the dev exactly what's happening and how to change it.\n console.log(\n \"[crossdeck] Localhost detected — running in dev mode (no network calls). \" +\n \"Set publicKey: 'cd_pub_test_…' and deploy to a real domain to test against the Crossdeck Sandbox.\",\n );\n }\n // Bank-grade identity continuity (v0.6.0+). When persistIdentity is\n // on AND we're in a browser, the SDK writes the anonymousId to BOTH\n // localStorage (primary) and a 1st-party cookie (secondary). When\n // persistIdentity is off — typical during a strict-consent flow\n // before opt-in — we fall back to in-memory only and write nothing\n // to either store.\n //\n // The cookie is only constructed when the caller didn't override\n // `storage`; if a custom storage adapter was supplied, that wins\n // and the cookie redundancy is the caller's responsibility (they\n // chose a non-default store for a reason).\n const effectiveStorage = persistIdentity ? storage : new MemoryStorage();\n const useCookieRedundancy =\n persistIdentity &&\n !options.storage && // honour caller's adapter choice\n typeof (globalThis as { document?: unknown }).document !== \"undefined\";\n const cookieStore = useCookieRedundancy ? new CookieStorage() : undefined;\n const identity = new IdentityStore(effectiveStorage, opts.storagePrefix, cookieStore);\n // Durable last-known-good entitlement cache — persisted through the\n // same storage as identity so isEntitled() answers from device cache\n // on boot and rides out a Crossdeck outage instead of failing every\n // Pro customer to free. In-memory only when persistIdentity is off.\n const entitlements = new EntitlementCache(\n effectiveStorage,\n opts.storagePrefix + \"entitlements\",\n );\n // Durable persistence — write queued events through to the primary\n // identity store (typically localStorage) so a crash / hard close /\n // keepalive cap exceedance doesn't lose data. Skipped when\n // persistIdentity is off (strict consent / in-memory-only mode) —\n // no point writing events to a store the developer told us not to\n // use.\n const persistentEvents = persistIdentity\n ? new PersistentEventStore({ storage: effectiveStorage, prefix: opts.storagePrefix })\n : null;\n if (persistentEvents) {\n debug.emit(\n \"sdk.queue_restored\",\n \"Restored persisted event queue from a prior session.\",\n );\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 MUST\n // be loud regardless of debug mode. Pre-fix the queue retried\n // 4xx forever silently and the customer never knew their key\n // was revoked. console.error fires unconditionally; the debug\n // signal lets the dashboard onboarding flow detect + surface\n // the problem too.\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(\n \"sdk.flush_permanent_failure\",\n headline,\n { ...info },\n );\n },\n onParked: (info) => {\n // Second signal channel (the queue already logged ONE console line).\n // PARK is NOT a drop: events are held on-device and resume on upgrade.\n // The dashboard reads this to render the calm amber \"update to resume\"\n // advisory. Distinct signal from flush_permanent_failure.\n debug.emit(\n \"sdk.parked\",\n `[crossdeck] SDK parked — server no longer accepts this version's event format. Events held on-device (paused, not lost); update @cross-deck/web${info.minVersion ? ` to >= ${info.minVersion}` : \"\"} to resume.`,\n { ...info },\n );\n },\n });\n\n // Collect device info ONCE at boot; cheap to re-use on every event.\n const deviceInfo: DeviceInfo = autoTrack.deviceInfo\n ? collectDeviceInfo({ appVersion: opts.appVersion ?? undefined })\n : opts.appVersion\n ? { appVersion: opts.appVersion }\n : {};\n\n // Super-property + groups store — Mixpanel pattern. Lives on the\n // primary identity storage so it survives page reloads but is\n // cleared on reset() / forget(). Skipped when persistIdentity is\n // off (strict consent — no writes anywhere).\n const superProps = new SuperPropertyStore(\n persistIdentity ? effectiveStorage : new MemoryStorage(),\n opts.storagePrefix,\n );\n\n // Internal-traffic opt-out: a ?crossdeck_internal=1/0 in the URL sets/\n // clears the persisted localStorage flag now; each event then reads it\n // (isInternalOptOut) to tag itself internal. Self-contained + guarded.\n processInternalOptOutUrl();\n\n // Consent gating. DNT auto-detection runs once here if respectDnt\n // is enabled; otherwise the developer is responsible for calling\n // Crossdeck.consent({...}) before user-meaningful events fire.\n const consent = new ConsentManager({ respectDnt: options.respectDnt === true });\n if (consent.isDntDenied) {\n debug.emit(\n \"sdk.consent_dnt_applied\",\n \"Do Not Track detected — all tracking dimensions denied at init.\",\n );\n }\n\n // Breadcrumb ring buffer — the \"what was the user doing right\n // before things broke\" feature. Populated by auto-tracking\n // sources (page views, clicks, custom events) and by manual\n // Crossdeck.addBreadcrumb() calls. Attached to every error\n // report; cleared on reset() / forget().\n const breadcrumbs = new BreadcrumbBuffer(50);\n\n this.state = {\n http,\n identity,\n entitlements,\n events,\n autoTracker: null,\n webVitals: null,\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 uninstallUnloadFlush: null,\n lastServerTime: null,\n lastClientTime: null,\n };\n\n debug.emit(\"sdk.configured\", `Crossdeck connected to ${opts.appId} in ${opts.environment} mode.`, {\n appId: opts.appId,\n environment: opts.environment,\n sdkVersion: opts.sdkVersion,\n });\n\n // Auto-tracker boots AFTER state is set so its initial track() calls\n // can resolve identity hints and device-info enrichment correctly.\n if (autoTrack.sessions || autoTrack.pageViews) {\n const tracker = new AutoTracker(\n autoTrack,\n (name, properties) => this.track(name, properties),\n // Persist session continuity on the SAME adapter as identity, so\n // a visit survives full-page navigations (multi-page sites\n // re-install the SDK on every page) and honours the same consent\n // posture — MemoryStorage when identity persistence is off.\n { storage: effectiveStorage, storageKey: opts.storagePrefix + \"session\" },\n );\n this.state.autoTracker = tracker;\n tracker.install();\n }\n // Web Vitals tracker — emits LCP / INP / CLS / FCP / TTFB as named\n // events. No-op in non-browser environments or when the\n // PerformanceObserver primitive is missing.\n if (autoTrack.webVitals) {\n const vitals = new WebVitalsTracker(\n { enabled: true },\n (name, properties) => this.track(name, properties),\n );\n this.state.webVitals = vitals;\n vitals.install();\n }\n\n // ----- Error capture (the third pillar) -----\n // Installs global window.onerror + window.onunhandledrejection\n // handlers, wraps fetch + XHR, and reports each captured error\n // through the same event queue analytics uses. Crucially this\n // runs AFTER the queue, identity, and breadcrumb buffer are set\n // up — error events need all of them.\n if (autoTrack.errors) {\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 // GETTER, not a captured value — `setErrorBeforeSend()` mutates\n // `state.errorBeforeSend` after init() and the tracker MUST\n // pick up the new hook on the next error. The pre-fix shape\n // (`beforeSend: this.state!.errorBeforeSend`) snapshotted\n // `null` at construction and made the customer's PII scrubber\n // silently inert. See error-capture.ts:ErrorTrackerOptions.beforeSend.\n beforeSend: () => this.state!.errorBeforeSend,\n isConsented: () => this.state!.consent.errors,\n // Derived from the configured baseUrl at init() time. Used by\n // the fetch / XHR wrappers to skip captureHttp on Crossdeck's\n // own requests — pre-fix the skip was hardcoded to\n // `api.cross-deck.com` and broke for customers on staging /\n // regional / self-hosted base URLs (recursive capture loop).\n selfHostname: extractSelfHostname(opts.baseUrl),\n });\n this.state.errors = tracker;\n tracker.install();\n }\n\n // Terminal flush wiring — without this, every page navigation drops\n // whatever's queued (page.viewed on load, session.ended on pagehide,\n // user clicks within the idle window). Use keepalive so the request\n // survives the unload. visibilitychange→hidden is the canonical\n // mobile signal (pagehide also fires there); pagehide + beforeunload\n // are the desktop ones. We listen to all three and rely on the\n // queue being a no-op when empty so a single trigger flushes once.\n this.state.uninstallUnloadFlush = installUnloadFlush(() => {\n // Fire-and-forget. Errors here can't be handled meaningfully — the\n // page is going away. Keepalive lets the browser keep the request\n // alive past unload up to 64 KB total in flight.\n void this.flush({ keepalive: true }).catch(() => undefined);\n });\n\n if (opts.autoHeartbeat && !localDevMode) {\n // Fire-and-forget — heartbeat failure shouldn't block init().\n // Skipped in dev mode — there's nothing to heartbeat.\n void this.heartbeat().catch(() => undefined);\n }\n\n // ────────────────────────────────────────────────────────────────\n // Contract verifier layer — install LAST so the SDK is fully\n // constructed before any verifier observes its state. See\n // sdks/web/src/_contract-verifiers.ts for the full architecture.\n //\n // Three-layer precedence (locked):\n //\n // code option > dashboard remote config > DEBUG/RELEASE default\n //\n // Code wins so engineers retain ultimate control; the dashboard\n // toggle (per-app, via /dashboard/apps/{appId}) is the no-deploy\n // operational lever for QA / staging soaks; default is what ships\n // when nobody touches anything.\n //\n // Routing rules — locked here so a future change to init() can't\n // accidentally muddle them (the docstrings on the CrossdeckOptions\n // fields name each other as the wrong tool for the wrong job):\n //\n // disableContractAssertions: true → layer is entirely silent\n // logVerifierResults: false → console silent on PASS;\n // FAIL still prints at WARN\n // verifyContractsAtBoot: false → no boot self-test, but\n // hot-path verifiers still run\n //\n // `disableContractAssertions` is intentionally NOT remote-\n // configurable — sovereignty kill-switch must live in customer\n // source so procurement / audit teams can grep for it.\n // ────────────────────────────────────────────────────────────────\n if (options.disableContractAssertions !== true) {\n const debugDefault = defaultDebugModeFlag();\n // 1. SYNCHRONOUS BOOTSTRAP — context + reporter + verifier set\n // are constructed immediately using code > default. Hot-path\n // verifiers fire correctly from the next operation onwards.\n this.verifierCtx = buildVerifierContext({\n logVerifierResults: options.logVerifierResults ?? debugDefault,\n disableContractAssertions: false,\n // Best-effort run-context detection. Customers running the\n // Web SDK inside their own CI (Playwright, Cypress, etc.)\n // typically set CI=true; we use that as the signal. Otherwise\n // we assume `customer-app` — the SDK is running inside a\n // customer's deployed site.\n runContext: typeof process !== \"undefined\" && process.env && process.env.CI\n ? \"ci\"\n : \"customer-app\",\n });\n this.verifierReporter = new VerifierReporter(this.verifierCtx);\n const flushVerifier = buildFlushIntervalVerifier(opts.eventFlushIntervalMs);\n this.verifiers = Object.freeze([...STATIC_VERIFIERS, flushVerifier]);\n\n // 2. ASYNCHRONOUS REFINEMENT — fetch /v1/config and apply the\n // dashboard's per-app remote config, then fire the boot\n // self-test (so its output reflects the FINAL resolved\n // flags, not the pre-fetch defaults). Fire-and-forget — a\n // fetch failure leaves the local defaults in place and the\n // boot self-test runs only if code > default resolves true.\n //\n // Skipped in localDevMode (test harness / offline emulator\n // where there's no backend to fetch from). Boot self-test\n // still fires under code > default precedence directly.\n if (localDevMode) {\n const verifyAtBootLocal =\n options.verifyContractsAtBoot ?? debugDefault;\n if (verifyAtBootLocal && this.verifierReporter) {\n void runBootSelfTest(\n this.verifiers,\n this.verifierReporter,\n this.verifierCtx,\n ).catch(() => undefined);\n }\n } else {\n void this.bootstrapVerifierLayerRemote(options, debugDefault).catch(\n () => undefined,\n );\n }\n }\n // The previous synchronous boot-test block is now replaced by the\n // async refinement above. Closing brace below remains the end of\n // init().\n return;\n }\n\n /**\n * Fetch /v1/config from the backend and apply any per-app verifier\n * overrides set in the dashboard, then fire the boot self-test\n * once with the FINAL resolved flags.\n *\n * Precedence (also documented at the call site in init()):\n * code option > dashboard remote config > DEBUG/RELEASE default\n *\n * Code wins so engineers retain ultimate control; dashboard is the\n * no-deploy operational lever for QA / staging soaks.\n *\n * Never throws — a /v1/config failure surfaces as \"stick with the\n * synchronous defaults that init() already applied\" and the boot\n * self-test runs only if code > default resolves true.\n */\n private async bootstrapVerifierLayerRemote(\n options: CrossdeckOptions,\n debugDefault: boolean,\n ): Promise<void> {\n if (!this.state || !this.verifiers || !this.verifierCtx) return;\n\n interface RemoteVerifierConfig {\n logVerifierResults: boolean | null;\n verifyContractsAtBoot: boolean | null;\n }\n interface ConfigResponseShape {\n verifierConfig?: RemoteVerifierConfig;\n }\n\n let remote: RemoteVerifierConfig = {\n logVerifierResults: null,\n verifyContractsAtBoot: null,\n };\n try {\n const resp = await this.state.http.request<ConfigResponseShape>(\n \"GET\",\n \"/config\",\n );\n if (resp && resp.verifierConfig) {\n const r = resp.verifierConfig;\n remote = {\n logVerifierResults:\n typeof r.logVerifierResults === \"boolean\"\n ? r.logVerifierResults\n : null,\n verifyContractsAtBoot:\n typeof r.verifyContractsAtBoot === \"boolean\"\n ? r.verifyContractsAtBoot\n : null,\n };\n }\n } catch {\n // Fall through — `remote` stays null, defaults apply.\n }\n\n // Resolve precedence: code > remote > default.\n const logResults =\n options.logVerifierResults ??\n (remote.logVerifierResults ?? debugDefault);\n const verifyAtBoot =\n options.verifyContractsAtBoot ??\n (remote.verifyContractsAtBoot ?? debugDefault);\n\n // If the remote config or precedence changed the resolved value\n // from what the synchronous bootstrap chose, rebuild the\n // context + reporter so subsequent hot-path verifiers see the\n // updated flags. The previous reporter is GC'd; in-flight hot-\n // path dispatches that snapshot the reporter (none currently;\n // every dispatcher reads `this.verifierReporter` at call time)\n // would see the old flags.\n if (logResults !== this.verifierCtx.logVerifierResults) {\n this.verifierCtx = {\n ...this.verifierCtx,\n logVerifierResults: logResults,\n };\n this.verifierReporter = new VerifierReporter(this.verifierCtx);\n }\n\n if (verifyAtBoot && this.verifierReporter) {\n await runBootSelfTest(\n this.verifiers,\n this.verifierReporter,\n this.verifierCtx,\n );\n }\n }\n\n /**\n * @deprecated Use `init()` instead. NorthStar §4 standardised the\n * lifecycle method name across SDKs as `init` (formerly `start` /\n * `configure`). `start` will be removed in a future major version.\n */\n start(options: CrossdeckOptions): void {\n if (typeof console !== \"undefined\") {\n // eslint-disable-next-line no-console\n console.warn(\n \"[crossdeck] Crossdeck.start() is deprecated — use Crossdeck.init() instead. The signature is the same.\",\n );\n }\n this.init(options);\n }\n\n /**\n * Link the anonymous device to a developer-supplied user ID. Cache\n * the resolved Crossdeck customer for follow-up calls.\n *\n * v0.9.0+ accepts an optional `traits` bag — profile data (name,\n * plan, signupDate, role) persisted on the Crossdeck customer record\n * and queryable from dashboards. Traits are sanitised through the\n * same validator that gates `track()` properties, so a `{ avatar:\n * <File>, onSave: () => {} }` payload can't corrupt the alias call.\n *\n * Crossdeck.identify(\"user_847\", {\n * email: \"wes@pinet.co.za\",\n * traits: { name: \"Wes\", plan: \"pro\", signedUpAt: \"2026-05-11\" },\n * });\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 if (!s.consent.analytics) {\n // No-op on consent denial — but throw NOT — the developer\n // expected an aliasResult to await. Return a no-op result that\n // mirrors the wire shape so existing call chains don't break.\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 // Sanitise traits at the SDK boundary so a malformed bag (function,\n // BigInt, circular) never crashes the alias request. Empty result\n // → omit the field entirely so backends that don't yet know about\n // traits aren't surprised by an unknown key.\n const traitsValidation =\n options?.traits !== undefined\n ? validateEventProperties(options.traits)\n : null;\n const traits = traitsValidation && Object.keys(traitsValidation.properties).length > 0\n ? traitsValidation.properties\n : undefined;\n if (s.debug.enabled && traitsValidation && traitsValidation.warnings.length > 0) {\n for (const w of traitsValidation.warnings) {\n s.debug.emit(\n \"sdk.property_coerced\",\n `identify() traits key ${JSON.stringify(w.key)} was ${w.kind.replace(/_/g, \" \")} during validation.`,\n { key: w.key, kind: w.kind },\n );\n }\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 // storage 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 // Capture prior userId BEFORE the slot rotation so the\n // per-user-cache-isolation verifier can observe the transition.\n // Reading this from `s.developerUserId` (the prior state) — the\n // assignment to the new value happens further down, after the\n // HTTP call.\n const priorUserId = s.developerUserId;\n\n s.entitlements.setUserKey(userId);\n\n // ────────────────────────────────────────────────────────────\n // Hot-path verifier: per-user-cache-isolation runs AFTER the\n // slot rotation completes. PASS prints to debug console if\n // `logVerifierResults: true`; FAIL prints at WARN + fires\n // `reportContractFailure(...)` to the reliability channel.\n // Short-circuits cheaply when `disableContractAssertions: true`.\n // ────────────────────────────────────────────────────────────\n if (this.verifiers && this.verifierReporter && this.verifierCtx) {\n runOnIdentify(this.verifiers, this.verifierReporter, this.verifierCtx, {\n priorUserId,\n nextUserId: userId,\n cache: s.entitlements,\n });\n }\n\n const result = await s.http.request<AliasResult>(\"POST\", \"/identity/alias\", {\n body,\n });\n s.identity.setCrossdeckCustomerId(result.crossdeckCustomerId);\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 * Crossdeck.register({ plan: \"pro\", releaseChannel: \"beta\" });\n * Crossdeck.track(\"paywall_shown\"); // includes plan + releaseChannel\n *\n * Values that are `null` are deleted (the explicit \"stop tracking\n * this key\" idiom). Returns the resulting bag.\n *\n * Sanitised through `validateEventProperties` so a `{ avatar: File }`\n * payload can't poison the queue at flush time.\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, etc.).\n * Mixpanel / Segment \"Group Analytics\" pattern.\n *\n * Crossdeck.group(\"org\", \"acme_inc\");\n * Crossdeck.group(\"team\", \"design\", { headcount: 12 });\n *\n * Once set, every subsequent event carries `$groups.<type>: id` on\n * its properties bag, enabling B2B dashboards (\"how is Acme using\n * the product\"). Pass `id: null` to clear a group membership.\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 ? validateEventProperties(traits).properties : 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 /**\n * Update consent state. Three independent dimensions:\n *\n * analytics — track() + identify() + auto-emissions\n * marketing — paid-traffic click IDs + referrer URL on events\n * errors — Web Vitals + (future) error reporting\n *\n * Each defaults to `true` (granted). Pass partial state — only the\n * keys you provide are changed.\n *\n * Crossdeck.consent({ analytics: false });\n * Crossdeck.consent({ marketing: true, errors: true });\n *\n * DNT-derived denies cannot be flipped back on; if the browser said\n * \"don't track\" we don't track even if the developer code disagrees.\n */\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 (v1.0.0+)\n // ============================================================\n\n /**\n * Manually capture an error from a try/catch block.\n *\n * try { …risky… } catch (err) {\n * Crossdeck.captureError(err, { context: { plan: \"pro\" } });\n * }\n *\n * The error is shipped through the same event queue as analytics\n * (durable, retried, rate-limited per fingerprint). Sends are gated\n * by `consent.errors`. Returns silently — never throws, even if the\n * SDK isn't initialised yet.\n */\n captureError(\n error: unknown,\n options?: { context?: Record<string, unknown>; tags?: Record<string, string>; level?: ErrorLevel },\n ): void {\n if (!this.state?.errors) return;\n this.state.errors.captureError(error, options);\n }\n\n /**\n * Capture a non-error event you want to surface as an issue\n * (\"deprecated path hit\", \"we entered the slow code path\"). Sentry\n * captureMessage pattern. Returns silently if not initialised.\n */\n captureMessage(message: string, level: ErrorLevel = \"info\"): void {\n if (!this.state?.errors) return;\n this.state.errors.captureMessage(message, level);\n }\n\n /**\n * Attach a tag to every subsequent error report. Tags are key/value\n * strings (Sentry pattern): `setTag(\"flow\", \"checkout\")` → every\n * error from this point on carries `tags.flow === \"checkout\"`.\n */\n setTag(key: string, value: string): void {\n if (!this.state) return;\n this.state.errorTags[key] = value;\n }\n\n /** Bulk-set tags. Merges with existing tags. */\n setTags(tags: Record<string, string>): void {\n if (!this.state) return;\n Object.assign(this.state.errorTags, tags);\n }\n\n /**\n * Attach a structured context blob to every subsequent error report.\n * Unlike tags (flat key/value), context is a named bag of arbitrary\n * data: `setContext(\"cart\", { items: 3, total: 42.99 })`.\n */\n setContext(name: string, data: Record<string, unknown>): void {\n if (!this.state) return;\n this.state.errorContext[name] = data;\n }\n\n /**\n * Add a custom breadcrumb to the rolling buffer. Useful for marking\n * domain-meaningful moments (\"user opened paywall\") that aren't\n * already auto-captured. The buffer caps at 50 entries; old ones\n * evict.\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 beforeSend\n * pattern — the only way to redact app-specific PII (auth tokens\n * in URLs, etc.) before the report leaves the browser.\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 /**\n * Internal: turn a CapturedError into a Crossdeck event and enqueue\n * it. Goes through the same queue / persistence / consent / scrub\n * pipeline as analytics events.\n */\n private reportError(err: CapturedError): void {\n // Sanitise the error payload — stack frames may contain\n // user-supplied PII (emails / IDs in URLs). The scrub runs\n // automatically inside track() before the event lands in the\n // queue, but we also pre-flatten the structured fields here so\n // they're searchable in the warehouse.\n const properties: EventProperties = {\n // Identifiers\n fingerprint: err.fingerprint,\n level: err.level,\n // Error shape\n errorType: err.errorType,\n message: err.message,\n // Stack\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 // Context\n tags: err.tags,\n context: err.context,\n breadcrumbs: err.breadcrumbs,\n // HTTP (only when applicable)\n http: err.http,\n };\n // Strip undefined values for a tidy wire shape.\n for (const k of Object.keys(properties)) {\n if (properties[k] === undefined) delete properties[k];\n }\n // Use track() for the full pipeline: validation, enrichment,\n // consent gate (gated on `analytics` though we already verified\n // `errors`), durable queue, retry, scrub. The event name follows\n // the namespacing convention so dashboards can filter `name\n // LIKE 'error.%'`.\n this.track(err.kind, properties);\n }\n\n /**\n * GDPR/CCPA \"right to be forgotten\" — calls the backend's\n * /v1/identity/forget endpoint to schedule a server-side deletion of\n * the customer's events and profile, then wipes all local state\n * (identity, entitlements, queue, super-props, persistent stores).\n *\n * Idempotent. Safe to call when no identity has been established\n * (it just wipes the empty local state).\n *\n * After forget() resolves, the SDK is in the same shape as if the\n * developer had called `Crossdeck.reset()` — a fresh anonymousId is\n * minted and the next session is a brand new identity-graph entry.\n */\n async forget(): Promise<void> {\n const s = this.requireStarted();\n const identityQuery = this.identityQueryParams();\n try {\n await s.http.request<{ object: \"forgot\" }>(\"POST\", \"/identity/forget\", {\n body: {\n // Send every identity hint we hold; the server resolves the\n // canonical customer record and queues deletion. Missing\n // endpoint (older backend) gracefully degrades — local state\n // still wipes via the reset() call below.\n ...identityQuery,\n },\n });\n } catch (err) {\n // Server-side deletion failure is recorded but does not block\n // local wipe — the developer's user just asked to be forgotten,\n // refusing to clear their device because the backend hiccupped\n // would be the wrong call.\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 server.\n * Updates the local cache so subsequent isEntitled() calls answer\n * synchronously.\n */\n async getEntitlements(): Promise<PublicEntitlement[]> {\n const s = this.requireStarted();\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 // The refresh failed (Crossdeck unreachable / transient error).\n // The durable cache keeps serving last-known-good — but mark it\n // stale so the staleness is visible via diagnostics(), never a\n // silent unbounded window. Then rethrow so the caller still sees\n // the failure.\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 on boot and\n * survives a Crossdeck outage, so a returning paying customer reads\n * true even before the session's first network round-trip. Returns\n * false only for a genuinely new install that has never completed a\n * getEntitlements(), or for an entitlement past its own validUntil.\n *\n * Throws `not_initialized` (CrossdeckError, type\n * `configuration_error`) if called before `Crossdeck.init()`. The\n * `useEntitlement` React hook and the Vue composable both swallow\n * this and return `false`; bare callers must guard for it (or call\n * after `init()` resolves).\n */\n isEntitled(key: string): boolean {\n const s = this.requireStarted();\n return s.entitlements.isEntitled(key);\n }\n\n /** Snapshot of the local entitlement cache. */\n listEntitlements(): PublicEntitlement[] {\n const s = this.requireStarted();\n return s.entitlements.list();\n }\n\n /**\n * Subscribe to entitlement-cache changes. Returns an unsubscribe fn.\n *\n * The listener is invoked AFTER the cache mutates — once after a\n * successful `getEntitlements()` warms it, again after `syncPurchases()`\n * delivers fresh entitlements, once on `reset()` to fire the empty-\n * cache state for logout flows, AND once on `identify()` after the\n * per-user cache slot rotates and re-hydrates from device storage.\n *\n * IMPORTANT — the `identify()` fire is a TRAP if you treat it as\n * authoritative network state. `identify()` does NOT fetch entitlements;\n * it switches the per-user cache slot and rehydrates from device\n * storage (which is empty for a brand-new install, and last-known-good\n * — possibly stale — for a returning user). A listener that gates a\n * paywall on the first fire after an identity switch will read\n * `false` for a paying customer on a fresh device and let them past\n * the gate as free. The network-truth fire is the one that follows\n * the next `getEntitlements()` resolution. Either call\n * `getEntitlements()` explicitly after `identify()`, or have your\n * gating code tolerate the empty-then-populated transition.\n *\n * It is NOT invoked synchronously on subscribe. Callers that need\n * the current state should read it via `isEntitled()` / `listEntitlements()`\n * inline; the listener fires only on FUTURE changes.\n *\n * This is the foundation of the `useEntitlement` React hook in\n * `@cross-deck/web/react` — without it, React (or SwiftUI / Compose\n * / Vue) would have no way to re-render when entitlements arrive\n * asynchronously after init. The naive pattern of calling\n * `Crossdeck.isEntitled(\"pro\")` directly inside a render path\n * shows the empty-cache result forever; binding the result to\n * component state via `onEntitlementsChange` is the correct\n * pattern.\n *\n * Idempotent unsubscribe — calling the returned function multiple\n * times is safe.\n *\n * Listener errors are swallowed (a buggy listener can't crash the\n * 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 round-\n * trip happens in the background. To flush before the page unloads,\n * call flush().\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 * Wire the call from a test hook, dogfood failure path, or\n * customer contract-verification harness; see\n * `contracts/README.md` for the per-test-framework hook recipes.\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: \"web\",\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\n // ----- Consent gate -----\n // Three gates depending on event family:\n // error.* → consent.errors (crashes, HTTP failures, captureMessage)\n // webvitals.* → consent.errors (performance / reliability data)\n // everything else → consent.analytics\n // Default-on; the developer must explicitly call\n // Crossdeck.consent({...:false}) to drop them.\n const isError = name.startsWith(\"error.\");\n const isWebVital = name.startsWith(\"webvitals.\");\n const consentGateOk = (isError || isWebVital) ? 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 for ${isWebVital ? \"errors\" : \"analytics\"}.`,\n );\n }\n return;\n }\n\n // NorthStar §15: warn (in debug mode) when a property name looks\n // dangerously like PII — email/password/token/secret/card/phone.\n // We don't strip the field; that's the developer's call. We just\n // surface the signal so they can spot accidental leaks early.\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 so a chatty client\n // doesn't spam the log with every track() before identify().\n if (s.debug.enabled && !s.developerUserId && !s.identity.crossdeckCustomerId) {\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 BEFORE merging with\n // SDK enrichment. This is the boundary where untrusted developer\n // input becomes safe-to-serialise data: functions/symbols dropped,\n // Date / BigInt / Error coerced to JSON-friendly shapes, oversized\n // strings truncated, circular refs replaced. Without this, one bad\n // property (e.g. `{ onClick: () => {} }`) would crash JSON.stringify\n // at flush time and poison the entire batch indefinitely.\n //\n // The SDK's own enrichment (device info, sessionId, utm_*) is\n // trusted and not re-validated — those values are produced by\n // `collectDeviceInfo()` and `captureAcquisition()`, both of which\n // are typed and bounded.\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 // Envelope v1 §3 — assign seq SYNCHRONOUSLY at track() time, before\n // markActivity() (which may roll a new session and reset the counter to\n // 0). The counter belongs to the session; capturing it here — at the\n // deterministic call-order instant — means seq ordering follows call\n // order, not scheduler luck. Also capture timestamp here so both fields\n // are one sample (spec §3 + Swift parity from fix commit 6ac71a1).\n const seq = s.autoTracker?.nextSeq() ?? 0;\n const occurrenceTimestamp = Date.now();\n\n // Enrichment policy: device info first, then auto-tracker context\n // (sessionId + per-session acquisition utm_*/referrer), then\n // caller-supplied properties last so a developer can override\n // anything the SDK auto-attached.\n //\n // Acquisition fields are session-scoped (captured once at session\n // start by AutoTracker) and attached to every event of that session\n // — that's the GA4 model: same source/medium/campaign labels every\n // event in the same visit. Empty strings are filtered out so we\n // don't pollute event property dictionaries with no-signal columns.\n // Enrichment layer order (later wins on key conflict):\n // 1. Session + pageview + acquisition + click IDs (auto-tracker)\n // 2. Super properties (registered once via Crossdeck.register)\n // 3. Group memberships (set via Crossdeck.group)\n // 4. Caller-supplied properties (sanitised)\n // Device facts (os, browser, locale, etc.) are promoted to the\n // top-level `context` object per Envelope v1 §4 and are NO LONGER\n // spread into `properties`. Screen dimensions and device pixel ratio\n // remain in `properties` as they are not part of the spec §4 context\n // schema and are not promoted.\n // The order is intentional: developer-supplied data is most\n // authoritative, so it overrides anything the SDK auto-attached.\n // Any tracked event — auto or custom — is activity: push the rolling\n // 30-min session window forward so a user who interacts via custom\n // events (not just pageviews/clicks) keeps one continuous session.\n s.autoTracker?.markActivity();\n\n // Envelope v1 §4 — build the standardized context object with the exact\n // spec field names. Promoted out of `properties`. Common fields: os,\n // osVersion, appVersion, sdkName, sdkVersion, locale, timezone. Web adds:\n // browser, browserVersion. Only include keys that are defined (string).\n const wireContext: Record<string, string> = {};\n const di = s.deviceInfo;\n if (di.os) wireContext.os = di.os;\n if (di.osVersion) wireContext.osVersion = di.osVersion;\n const appVer = s.options.appVersion;\n if (appVer) wireContext.appVersion = appVer;\n wireContext.sdkName = SDK_NAME;\n wireContext.sdkVersion = s.options.sdkVersion;\n if (di.locale) wireContext.locale = di.locale;\n if (di.timezone) wireContext.timezone = di.timezone;\n if (di.browser) wireContext.browser = di.browser;\n if (di.browserVersion) wireContext.browserVersion = di.browserVersion;\n\n // Properties layer: screen dimensions + pixel ratio remain in properties\n // (not promoted to context). Start WITHOUT deviceInfo spread — context\n // fields have been promoted out; only non-context device fields remain.\n const enriched: EventProperties = {};\n if (di.screenWidth !== undefined) enriched.screenWidth = di.screenWidth;\n if (di.screenHeight !== undefined) enriched.screenHeight = di.screenHeight;\n if (di.viewportWidth !== undefined) enriched.viewportWidth = di.viewportWidth;\n if (di.viewportHeight !== undefined) enriched.viewportHeight = di.viewportHeight;\n if (di.devicePixelRatio !== undefined) enriched.devicePixelRatio = di.devicePixelRatio;\n const sessionId = s.autoTracker?.currentSessionId;\n if (sessionId) enriched.sessionId = sessionId;\n const pageviewId = s.autoTracker?.currentPageviewId;\n if (pageviewId) enriched.pageviewId = pageviewId;\n const acquisition = s.autoTracker?.currentAcquisition;\n if (acquisition) {\n // UTMs and referrer host are always attached (they're considered\n // analytics, not marketing PII). Paid-traffic click IDs and the\n // full referrer URL gate on marketing consent — the developer's\n // user said \"no marketing tracking\" → drop them.\n if (acquisition.utm_source) enriched.utm_source = acquisition.utm_source;\n if (acquisition.utm_medium) enriched.utm_medium = acquisition.utm_medium;\n if (acquisition.utm_campaign) enriched.utm_campaign = acquisition.utm_campaign;\n if (acquisition.utm_content) enriched.utm_content = acquisition.utm_content;\n if (acquisition.utm_term) enriched.utm_term = acquisition.utm_term;\n if (acquisition.referrer && s.consent.marketing) enriched.referrer = acquisition.referrer;\n if (s.consent.marketing) {\n if (acquisition.gclid) enriched.gclid = acquisition.gclid;\n if (acquisition.fbclid) enriched.fbclid = acquisition.fbclid;\n if (acquisition.msclkid) enriched.msclkid = acquisition.msclkid;\n if (acquisition.ttclid) enriched.ttclid = acquisition.ttclid;\n if (acquisition.li_fat_id) enriched.li_fat_id = acquisition.li_fat_id;\n if (acquisition.twclid) enriched.twclid = acquisition.twclid;\n }\n }\n // Super properties registered via Crossdeck.register(). Skipped\n // for keys the auto-enrichment already supplied so a `register`\n // call can't accidentally shadow `sessionId` etc.\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 // Group memberships — attached as `$groups.<type>` for B2B\n // dashboards. Mixpanel uses `$groups`; we mirror exactly so\n // existing integrators don't need a translation layer.\n const groupIds = s.superProps.getGroupIds();\n if (Object.keys(groupIds).length > 0) {\n enriched.$groups = groupIds;\n }\n Object.assign(enriched, validation.properties);\n\n // Internal-traffic opt-out: this browser visited ?crossdeck_internal=1,\n // so tag every event for ingest to classify as internal. Injected after\n // developer properties so it can't be shadowed; reserved $-prefixed key.\n if (isInternalOptOut()) {\n enriched[INTERNAL_OPT_OUT_PROPERTY] = true;\n }\n\n // ----- PII scrub -----\n // Last step before the event lands in the queue: defensive regex\n // scrub on URL paths, titles, and any string property value. An\n // app that puts emails or card numbers in URLs (`/users/wes@…/`)\n // would otherwise ship that PII straight to the warehouse. Even\n // with explicit consent this is the right default — Stripe scrubs\n // pre-storage too. Disable via `scrubPii: false` in init() for\n // pipelines that do their own redaction.\n const finalProperties = s.scrubPii ? scrubPiiFromProperties(enriched) : enriched;\n\n const event: QueuedEvent = {\n eventId: this.mintEventId(),\n name,\n timestamp: occurrenceTimestamp,\n seq,\n context: wireContext,\n properties: finalProperties,\n };\n Object.assign(event, this.identityHintForEvent());\n s.events.enqueue(event);\n\n // ────────────────────────────────────────────────────────────────\n // Hot-path verifier: `super-property-merge-precedence` runs AFTER\n // enqueue so the merged properties reflect EXACTLY what the queue\n // accepted. The observation carries the three input layers\n // separately + the merged result so the verifier can prove\n // caller > super > device precedence on every real track() call,\n // not just at boot. PASS prints to debug console iff\n // `logVerifierResults: true`; FAIL prints at WARN + fires\n // `reportContractFailure(...)` to the reliability channel.\n // Cheap short-circuit when `disableContractAssertions: true`.\n //\n // mergedProperties is observed PRE-SCRUB (`enriched`), not POST-\n // SCRUB (`finalProperties`). The contract enforced here is\n // \"device < super < caller\" — i.e., which LAYER's value wins per\n // key. PII scrub runs AFTER merge and intentionally mutates\n // strings (redacts emails, IPs, card numbers) and re-clones\n // arrays + plain objects. Strict-equal vs `finalProperties`\n // therefore produces false positives on any caller-supplied\n // compound value: `caller.frames === finalProperties.frames` is\n // `false` because scrub's `Array.map` returns a fresh array, even\n // when contents are unchanged. Founder caught this on\n // 2026-05-28 — a real `crossdeck.error` track() carrying\n // `frames: [...]` fired the hot-path FAIL on\n // `super-property-merge-precedence`, masking real bugs in the\n // dashboard. Scrub correctness is verified separately; this\n // verifier's job is solely merge-layer precedence.\n // ────────────────────────────────────────────────────────────────\n if (this.verifiers && this.verifierReporter && this.verifierCtx) {\n // Cast the four input layers to the verifier's open\n // Record<string, unknown> view. DeviceInfo / EventProperties are\n // narrowed-string-key records at runtime; the verifier only\n // reads keys/values, never writes, so the widen is sound. Done\n // here, not in the verifier signature, so the SDK call site\n // keeps the strictly-typed source values for refactors.\n runOnTrack(this.verifiers, this.verifierReporter, this.verifierCtx, {\n callerProperties: validation.properties as Record<string, unknown>,\n superProperties: supers as Record<string, unknown>,\n deviceProperties: s.deviceInfo as unknown as Record<string, unknown>,\n mergedProperties: enriched as Record<string, unknown>,\n });\n }\n\n // ----- Breadcrumb emission -----\n // Every analytics event becomes a breadcrumb so error reports\n // carry the context of what the user was doing just before the\n // crash. Don't emit a breadcrumb for error events themselves\n // (would be circular) or webvitals events (noise — they always\n // fire on every page).\n if (!isError && !isWebVital) {\n const category = name.startsWith(\"page.\")\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 // Strip the device-info / session bloat from the breadcrumb\n // payload — only the caller-supplied properties belong in\n // the user-readable trail.\n data: properties ? { ...properties } : undefined,\n });\n }\n }\n\n /**\n * Force-flush queued events. Useful to call from page-unload handlers.\n *\n * Pass `{ keepalive: true }` from terminal handlers (pagehide /\n * visibilitychange→hidden / beforeunload). The browser keeps the\n * request alive after the page tears down, so the final batch\n * actually lands instead of being cancelled with the unload.\n *\n * NorthStar §4: standard method name across all Crossdeck SDKs.\n */\n async flush(options: { keepalive?: boolean } = {}): Promise<void> {\n const s = this.requireStarted();\n await s.events.flush(options);\n }\n\n /** @deprecated Use `flush()` instead. NorthStar §4 standardised the name. */\n async flushEvents(): Promise<void> {\n return this.flush();\n }\n\n /**\n * Forward purchase evidence to the backend for verification + entitlement\n * projection. NorthStar §4 + §13 canonical name.\n *\n * Today the web SDK only supports Apple StoreKit 2 forwarding (web apps\n * that sit alongside an iOS app). Stripe doesn't need this method —\n * Stripe webhooks deliver evidence server-side without a client round-trip.\n */\n async syncPurchases(input: {\n rail?: \"apple\";\n signedTransactionInfo: string;\n signedRenewalInfo?: string;\n appAccountToken?: string;\n }): Promise<PurchaseResult> {\n const s = this.requireStarted();\n if (!input.signedTransactionInfo) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_signed_transaction_info\",\n message: \"syncPurchases requires a signedTransactionInfo string from StoreKit 2.\",\n });\n }\n // Spread input FIRST so the explicit `rail` default below WINS.\n // Pre-fix order was `{ rail: input.rail ?? \"apple\", ...input }`\n // — but `...input` runs LAST and overrides the default with the\n // caller's literal `rail` key, including the case where the\n // caller passes `rail: undefined` explicitly (TypeScript treats\n // an undefined-typed property as \"key present\"). Reversing the\n // order so the default sits last fixes both the explicit-undefined\n // case AND the omitted-key case in one line.\n const rail = input.rail ?? \"apple\";\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-\n // circuits with idempotent_replay: true on retry.\n const idempotencyKey = deriveIdempotencyKeyForPurchase(body);\n\n // ────────────────────────────────────────────────────────────\n // Hot-path verifier: idempotency-key-deterministic re-derives the\n // key from the same input and compares against what the SDK\n // computed. Identical → contract honoured. Drift → bug.\n // ────────────────────────────────────────────────────────────\n if (this.verifiers && this.verifierReporter && this.verifierCtx) {\n const stableId =\n rail === \"apple\"\n ? (body as { signedTransactionInfo?: string }).signedTransactionInfo ?? \"\"\n : (body as { purchaseToken?: string }).purchaseToken ?? \"\";\n runOnSyncPurchases(\n this.verifiers,\n this.verifierReporter,\n this.verifierCtx,\n {\n rail,\n stableIdentifier: stableId,\n derivedKey: idempotencyKey,\n },\n );\n }\n\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 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 so cross-platform funnels reconcile on\n // event name + the rail/productId fields. Manual paths don't\n // see the StoreKit Transaction so transactionId/purchaseDate\n // are absent — funnel queries that need them stay native-\n // auto-track only.\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 // track() throws only on invalid name (we control it) — defensive.\n }\n s.debug.emit(\n \"sdk.purchase_evidence_sent\",\n \"StoreKit transaction forwarded. Waiting for backend verification.\",\n { rail: input.rail ?? \"apple\" },\n );\n return result;\n }\n\n /** @deprecated Use `syncPurchases()` instead. NorthStar §4 standardised the name. */\n async purchaseApple(input: {\n signedTransactionInfo: string;\n signedRenewalInfo?: string;\n appAccountToken?: string;\n }): Promise<PurchaseResult> {\n return this.syncPurchases({ rail: \"apple\", ...input });\n }\n\n /**\n * Toggle verbose diagnostic logging — NorthStar §16. When enabled, the\n * SDK emits a fixed vocabulary of debug signals to console.info that the\n * dashboard's onboarding checklist can also surface as live events.\n */\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 start() unless\n * autoHeartbeat:false. Safe to call manually as a \"we're still here\" ping.\n */\n async heartbeat(): Promise<HeartbeatResponse> {\n const s = this.requireStarted();\n const result = await s.http.request<HeartbeatResponse>(\"GET\", \"/sdk/heartbeat\");\n // Capture clock skew at the SAME instant on both sides. The\n // `serverTime` field is the only authoritative source the SDK has\n // for \"what does the backend think the time is\" — used in\n // diagnostics() so a wrong-system-clock problem surfaces before\n // it silently shifts a day of analytics.\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. Use on logout. The\n * next pre-login session generates a fresh anonymousId and starts a\n * new identity-graph entry.\n */\n reset(): void {\n if (!this.state) return;\n // Server-derived milestone: emit `user.signed_out` BEFORE we wipe\n // identity. The track() call enqueues the event with the current\n // developerUserId/cdcust stamped on it; the subsequent reset of\n // the identity store happens locally only — the queued event\n // already left with the right identity. The dashboard's Activity\n // stream therefore shows \"Wes signed out\" rather than an\n // anonymous orphan event. Skipped if there's no developerUserId\n // (calling reset on a never-identified anonymous device is a no-op\n // semantically — there was nothing to \"sign out\" of).\n if (this.state.developerUserId) {\n try {\n this.track(\"user.signed_out\", { auto: true });\n } catch {\n // track() throws only on invalid name — never for a literal we control.\n // Defensive catch keeps reset() bulletproof for logout flows.\n }\n }\n // Tear down + reinstall the auto-tracker so the new session belongs\n // to the new identity, not the old one. Unload-flush listeners stay\n // installed across reset — they're tied to the SDK lifetime, not\n // the identity lifetime. Web Vitals stay attached too — their\n // observers are per-page-life, not per-identity.\n this.state.autoTracker?.uninstall();\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.\n this.state.entitlements.clearAll();\n this.state.events.reset();\n // Super properties + groups are identity-scoped — clear on logout\n // so a fresh anonymous session doesn't inherit the previous user's\n // plan/role/group context.\n this.state.superProps.clear();\n // Breadcrumbs + error context belong to the old session; wipe so\n // a fresh post-logout error report doesn't carry the previous\n // user's debugging trail. The ErrorTracker stays installed across\n // reset — its listeners are page-life-scoped, not identity-scoped.\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 the\n // pre-logout session. Pre-fix `diagnostics().clock.skewMs` for the\n // next user kept reporting the prior session's skew until the\n // next heartbeat completed (audit P1 #17). The next heartbeat\n // repopulates with the new instant.\n this.state.lastServerTime = null;\n this.state.lastClientTime = null;\n if (this.state.autoTracker) {\n const tracker = new AutoTracker(this.state.options.autoTrack, (name, props) =>\n this.track(name, props),\n );\n this.state.autoTracker = tracker;\n tracker.install();\n }\n }\n\n /**\n * Diagnostic: current state + queue stats. Useful for the dashboard's\n * heartbeat row and debugging in dev.\n *\n * Returns a stable shape regardless of whether start() has been called —\n * callers don't need to narrow on `started` to access `events` or\n * `entitlements`. Pre-start values are sensible empties.\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 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.identity.anonymousId,\n crossdeckCustomerId: s.identity.crossdeckCustomerId,\n developerUserId: s.developerUserId,\n sdkVersion: s.options.sdkVersion,\n baseUrl: s.options.baseUrl,\n clock: {\n lastServerTime: s.lastServerTime,\n lastClientTime: s.lastClientTime,\n skewMs,\n },\n entitlements: {\n count: s.entitlements.list().length,\n lastUpdated: s.entitlements.freshness,\n stale: s.entitlements.isStale,\n listenerErrors: s.entitlements.listenerErrors,\n },\n events: s.events.getStats(),\n };\n }\n\n /**\n * The stable reference to hand Stripe at checkout so the resulting\n * purchase attributes to THIS person. Stamp it on the Checkout Session\n * as `metadata.crossdeck_ref` (see docs/connect-stripe) — the platform\n * webhook reads it back, validates it, and attaches the subscription to\n * the right customer instead of stranding it on an anonymous record.\n *\n * Returns the strongest identifier the SDK currently holds, in the same\n * precedence the server resolves by:\n * crossdeckCustomerId (cdcust_…) > developerUserId > anonymousId\n * anonymousId is always present, so this always returns a usable,\n * non-empty reference — even before identify(). Identify the user as\n * early as you can, though, so the reference is their stable identity\n * and not a per-device anon id.\n */\n getCheckoutReference(): string {\n const s = this.requireStarted();\n return (\n s.identity.crossdeckCustomerId ??\n s.developerUserId ??\n s.identity.anonymousId\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 * — matches the resolveCrossdeckCustomerId precedence on the server.\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. Earlier this returned\n * just the highest-priority hint (cdcust → developerUserId → anonymousId)\n * to keep payloads small, but that leaked into analytics: once a user\n * was logged in, every subsequent page.viewed shipped without\n * anonymousId, and `uniqExact(anonymous_id)` on the warehouse side\n * counted 0 visitors for the entire authenticated app.\n *\n * Bank-grade rule: the server is the single source of truth on\n * dedup. Send everything we know; let CH count by whichever axis\n * matches the question. Each field is at most 32 bytes — sending\n * three on every event costs ~80 bytes per request, which is\n * trivial compared to the analytics correctness it buys.\n */\n private identityHintForEvent(): Pick<\n QueuedEvent,\n \"developerUserId\" | \"anonymousId\" | \"crossdeckCustomerId\"\n > {\n const s = this.requireStarted();\n const hint: Pick<QueuedEvent, \"developerUserId\" | \"anonymousId\" | \"crossdeckCustomerId\"> = {\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 * Normalise the autoTrack option to a fully-resolved AutoTrackConfig.\n * undefined → all defaults (everything on in browsers)\n * true → all on (same as defaults)\n * false → all off\n * { sessions:false } → defaults for unspecified flags, override for specified ones\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 *\n * We treat the legacy form as \"no opinion\" so the developer's explicit\n * `environment` declaration always wins for unprefixed keys (e.g. dev\n * fixture keys in tests).\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 * Detect whether the SDK is booting on a local-development hostname.\n * Triggers the SDK's silent dev-mode shutoff (no network calls, all\n * state local) so a developer running their app locally with a live\n * key cannot accidentally pollute their production analytics.\n *\n * Match list:\n * - localhost / 127.0.0.1 traditional local dev\n * - *.local mDNS / Bonjour (e.g. mymac.local)\n * - 10.x.x.x RFC1918 class A (private)\n * - 192.168.x.x RFC1918 class C (home / office LAN)\n * - 172.16-31.x.x RFC1918 class B\n *\n * Vercel preview URLs, Netlify branch deploys, ngrok tunnels — those\n * resolve to real domains and stay on whatever the key says. They're\n * not \"local,\" they're \"deployed under a temporary domain.\"\n *\n * Returns false in non-browser contexts (Node, Workers) — there's no\n * window.location to inspect, and a Node consumer that wired up the\n * SDK is presumably running server-side with a deliberate config.\n */\nfunction isLocalHostname(): boolean {\n // Testing escape hatch — E2E suites + smoke pages need to exercise\n // the real wire shape from a non-deployed domain (Playwright runs\n // on 127.0.0.1). Setting `window.__CROSSDECK_FORCE_LIVE__ = true`\n // before init() returns false here so the SDK fires real fetches.\n // Not documented in SDK_TRUTH because it's internal-only — real\n // consumers should never set this.\n const w = (globalThis as {\n window?: { location?: { hostname?: string }; __CROSSDECK_FORCE_LIVE__?: boolean };\n }).window;\n if (w?.__CROSSDECK_FORCE_LIVE__ === true) return false;\n const hostname = w?.location?.hostname;\n if (!hostname) return false;\n if (hostname === \"localhost\" || hostname === \"127.0.0.1\") return true;\n // 0.0.0.0 is the default bind address for webpack-dev-server /\n // vite dev server when the team is on a shared LAN dev. Audit P2:\n // pre-fix this slipped through and polluted live analytics for\n // anyone running those configs.\n if (hostname === \"0.0.0.0\") return true;\n if (hostname === \"::1\" || hostname === \"[::1]\") return true;\n // IPv6 link-local: fe80::/10. Devices on the same network segment\n // (browser inspector debugging an iPad via Safari Web Inspector\n // over USB lands here). Conservative match — only the link-local\n // prefix, not the broader ULA fc00::/7 range which is intended for\n // private routed networks and shouldn't auto-quiet by default.\n if (/^\\[?fe80::/i.test(hostname)) return true;\n if (hostname.endsWith(\".local\")) return true;\n if (/^10\\./.test(hostname)) return true;\n if (/^192\\.168\\./.test(hostname)) return true;\n if (/^172\\.(1[6-9]|2\\d|3[0-1])\\./.test(hostname)) return true;\n return false;\n}\n\nfunction resolveAutoTrack(\n input: CrossdeckOptions[\"autoTrack\"],\n): AutoTrackConfig {\n if (input === false) {\n return {\n sessions: false,\n pageViews: false,\n deviceInfo: false,\n clicks: false,\n webVitals: false,\n errors: false,\n };\n }\n if (input === undefined || input === true) {\n return { ...DEFAULT_AUTO_TRACK };\n }\n return {\n sessions: input.sessions ?? DEFAULT_AUTO_TRACK.sessions,\n pageViews: input.pageViews ?? DEFAULT_AUTO_TRACK.pageViews,\n deviceInfo: input.deviceInfo ?? DEFAULT_AUTO_TRACK.deviceInfo,\n clicks: input.clicks ?? DEFAULT_AUTO_TRACK.clicks,\n webVitals: input.webVitals ?? DEFAULT_AUTO_TRACK.webVitals,\n errors: input.errors ?? DEFAULT_AUTO_TRACK.errors,\n };\n}\n\n/**\n * Install browser unload listeners that fire `onUnload` when the page\n * is going away. We listen to all three because each browser/platform\n * is unreliable on at least one of them:\n * - `pagehide` is the modern, mobile-reliable signal (Safari iOS only\n * fires this — beforeunload doesn't fire on backgrounding there).\n * - `visibilitychange → hidden` is the canonical \"tab going to bg\"\n * signal; bfcache restores re-fire `pagehide`/`pageshow`.\n * - `beforeunload` is the legacy desktop signal — kept as a belt for\n * older Chrome/Firefox versions.\n *\n * The handler is idempotent: if the queue is empty, flush() is a no-op,\n * so multiple firings during one unload are harmless.\n *\n * Returns a teardown that removes all three listeners. No-ops in non-\n * browser environments (Node, Web Workers).\n */\nfunction installUnloadFlush(onUnload: () => void): () => void {\n const w = (globalThis as { window?: Window }).window;\n const doc = (globalThis as { document?: Document }).document;\n if (!w || !doc) return () => undefined;\n\n const onVisChange = (): void => {\n if (doc.visibilityState === \"hidden\") onUnload();\n };\n const onTerminal = (): void => onUnload();\n\n doc.addEventListener(\"visibilitychange\", onVisChange);\n w.addEventListener(\"pagehide\", onTerminal);\n w.addEventListener(\"beforeunload\", onTerminal);\n\n return () => {\n doc.removeEventListener(\"visibilitychange\", onVisChange);\n w.removeEventListener(\"pagehide\", onTerminal);\n w.removeEventListener(\"beforeunload\", onTerminal);\n };\n}\n"],"mappings":"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","names":["doc","ref","MemoryStorage","location","console","safeStringify","message"]}
|
|
1
|
+
{"version":3,"sources":["../src/vue.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/read-cost-bridge.ts","../src/device-info.ts","../src/auto-track.ts","../src/debug.ts","../src/event-validation.ts","../src/super-properties.ts","../src/internal-opt-out.ts","../src/web-vitals.ts","../src/consent.ts","../src/breadcrumbs.ts","../src/_diagnostic-telemetry.ts","../src/error-codes.ts","../src/_contract-verifiers.ts","../src/stack-parser.ts","../src/error-capture.ts","../src/crossdeck.ts"],"sourcesContent":["/**\n * @cross-deck/web/vue — Vue 3 composables for the Crossdeck SDK.\n *\n * Mirrors @cross-deck/web/react in spirit: ties the entitlement cache\n * to Vue's reactive system so components re-render the moment the\n * server-side cache populates.\n *\n * import { useEntitlement } from \"@cross-deck/web/vue\";\n * const isPro = useEntitlement(\"pro\"); // Ref<boolean>\n *\n * Why a separate subpackage: Vue is an optional peer dependency. The\n * core SDK has zero runtime deps; pulling Vue in unconditionally would\n * make non-Vue consumers pay for code they don't use. Same pattern as\n * `@cross-deck/web/react`.\n *\n * SSR safety: `onMounted` / `onScopeDispose` only run on the client.\n * The initial Ref value is the cache's current state (`false` pre-init),\n * so server output never claims a non-existent entitlement.\n */\n\nimport { ref, onMounted, onScopeDispose, type Ref } from \"vue\";\nimport { Crossdeck } from \"./crossdeck\";\n\n/**\n * Reactive entitlement check. Returns a `Ref<boolean>` that updates\n * automatically whenever the cache mutates.\n *\n * const isPro = useEntitlement(\"pro\");\n * // template: <span v-if=\"isPro\">Pro</span>\n */\nexport function useEntitlement(key: string): Ref<boolean> {\n const r = ref<boolean>(safeIsEntitled(key));\n\n onMounted(() => {\n r.value = safeIsEntitled(key);\n let unsubscribe: (() => void) | null = null;\n try {\n unsubscribe = Crossdeck.onEntitlementsChange(() => {\n r.value = safeIsEntitled(key);\n });\n } catch {\n // Pre-init — the SDK isn't started yet. The composable just\n // returns false until something mutates the cache via a\n // post-init call.\n }\n onScopeDispose(() => {\n if (unsubscribe) unsubscribe();\n });\n });\n\n return r;\n}\n\n/**\n * Reactive list of active entitlement keys. Updates on every cache\n * mutation. Useful for rendering a \"you have unlocked: ...\" block.\n */\nexport function useEntitlements(): Ref<readonly string[]> {\n const r = ref<readonly string[]>(safeListKeys());\n\n onMounted(() => {\n r.value = safeListKeys();\n let unsubscribe: (() => void) | null = null;\n try {\n unsubscribe = Crossdeck.onEntitlementsChange((entitlements) => {\n r.value = entitlements.filter((e) => e.isActive).map((e) => e.key);\n });\n } catch {\n // Pre-init.\n }\n onScopeDispose(() => {\n if (unsubscribe) unsubscribe();\n });\n });\n\n return r;\n}\n\nfunction safeIsEntitled(key: string): boolean {\n try {\n return Crossdeck.isEntitled(key);\n } catch {\n return false;\n }\n}\n\nfunction safeListKeys(): readonly string[] {\n try {\n return Crossdeck.listEntitlements()\n .filter((e) => e.isActive)\n .map((e) => e.key);\n } catch {\n return [];\n }\n}\n","/**\n * Stripe-style error wrapper for @cross-deck/web.\n *\n * Mirrors the wire shape returned by the v1 backend (see\n * backend/src/api/v1-errors.ts) so SDK consumers can `catch`\n * with consistent fields:\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 * }\n */\n\nexport type CrossdeckErrorType =\n | \"authentication_error\"\n | \"permission_error\"\n | \"invalid_request_error\"\n | \"rate_limit_error\"\n // version_error → HTTP 426: the SDK's wire format is too old. The queue\n // PARKS on it (holds events, resumes on upgrade), distinct from a drop.\n | \"version_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. The header\n * spec allows either delta-seconds or an HTTP-date; the parser below\n * normalises both to milliseconds. Consumers MUST honour this — the\n * server is telling you the safe rate.\n */\n retryAfterMs?: number;\n /**\n * Required SDK version floor — populated only on a `426 Upgrade Required`\n * / `sdk_version_unsupported` response. The queue surfaces it in the\n * \"update to >= X\" PARK message so the cure is exact.\n */\n minVersion?: string;\n /** SDK surface the rejection applies to (web/node/swift/…), on a 426. */\n surface?: string;\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 public readonly minVersion?: string;\n public readonly surface?: string;\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 this.minVersion = payload.minVersion;\n this.surface = payload.surface;\n // Restore prototype chain — needed when targeting ES5.\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(res: Response): 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 { error?: Partial<CrossdeckErrorPayload> & { request_id?: string } })?.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 // PARK metadata, present only on a 426 / sdk_version_unsupported body.\n minVersion: typeof envelope.minVersion === \"string\" ? envelope.minVersion : undefined,\n surface: typeof envelope.surface === \"string\" ? envelope.surface : undefined,\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 * Exported for unit testing.\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 // delta-seconds form (non-negative integer or decimal).\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 // HTTP-date form. Only attempt Date.parse if the value looks like a\n // date (has a comma, slash, or alphabetic character) — otherwise\n // garbage like \"-5\" or \"abc\" gets coerced by Date.parse into weird\n // values and we'd silently return 0.\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.9.1\";\nexport const SDK_NAME = \"@cross-deck/web\";\n","/**\n * HTTP transport for the SDK. Single fetch wrapper used by every endpoint\n * call. Adds the Bearer token and SDK version header, parses responses,\n * normalises errors to CrossdeckError.\n *\n * Uses platform-native fetch (browser + Node 18+). No axios, no isomorphic-\n * fetch shim, no transitive deps.\n */\n\nimport { CrossdeckError, crossdeckErrorFromResponse } from \"./errors\";\n// Single source of truth — `_version.ts` is generated from\n// package.json by `scripts/sync-sdk-versions.mjs`. A plain TypeScript\n// re-export here means the runtime `Crossdeck-Sdk-Version` header\n// always matches the published bundle, with zero Node-ESM JSON-import\n// gotchas (Node requires `with { type: \"json\" }` to load JSON as ESM\n// from a .mjs file — that bit dist-loading on 1.3.0 RC).\n//\n// Pre-fix this was a hardcoded literal that drifted from package.json:\n// the published 1.2.0 web bundle reported `@cross-deck/web@1.1.0` on\n// the wire because nobody bumped the constant. The generated\n// `_version.ts` closes that loop permanently — `--check` mode of the\n// sync script fails CI if anything goes out of sync.\nimport { SDK_NAME, SDK_VERSION } from \"./_version\";\nexport { SDK_NAME, SDK_VERSION };\n\nexport const DEFAULT_BASE_URL = \"https://api.cross-deck.com/v1\";\n\nexport interface HttpClientConfig {\n publicKey: string;\n baseUrl: string;\n sdkVersion: string;\n /**\n * Localhost auto-detection short-circuit. When true, every request\n * resolves to a fabricated 2xx-shaped response — no network call\n * goes out. Set by Crossdeck.init() when the SDK boots on a local\n * dev hostname. Confidence-first design: we never let a developer's\n * laptop pollute their live analytics by accident.\n */\n localDevMode?: boolean;\n /**\n * Default request timeout in ms. Per-call `options.timeoutMs` overrides.\n * Caller's `options.timeoutMs: 0` disables the timeout entirely (useful\n * for tests that intentionally hang).\n *\n * Stripe-grade default: 15s. Long enough that a slow-3G mobile keeps\n * the request alive; short enough that a captive portal or a hung\n * connection doesn't sit forever. Without this, fetch() inherits the\n * browser's default (which on Chrome can be 5+ minutes) and a single\n * bad network can lock up the entire event queue.\n */\n timeoutMs?: number;\n /**\n * Contract verifier hook — fired AFTER `crossdeckErrorFromResponse`\n * has built the typed error from a non-OK wire envelope, just BEFORE\n * the throw. Lets the SDK's verifier layer observe the parsed envelope\n * shape (type / code / request_id / http status) and assert the\n * `error-envelope-shape` contract on every real failure, not just at\n * boot. Synchronous + best-effort: any throw from the hook is swallowed\n * so a buggy verifier can NEVER replace the customer's real error with\n * a verifier-internal one. See sdks/web/src/_contract-verifiers.ts\n * runOnErrorParse for the consumer end of this wire.\n */\n onErrorParsed?: (err: CrossdeckError) => void;\n}\n\nexport const DEFAULT_TIMEOUT_MS = 15_000;\n\nexport interface HttpRequestOptions {\n body?: unknown;\n query?: Record<string, string | undefined>;\n /**\n * Mark the request as `keepalive` so the browser keeps it in flight\n * even after the page begins unloading. Critical for terminal flushes\n * fired from `pagehide` / `visibilitychange` — without this, the queued\n * page.viewed / session.ended events get cancelled the moment the user\n * navigates away.\n *\n * Spec: https://developer.mozilla.org/docs/Web/API/Fetch_API/Using_Fetch#sending_a_request_with_keepalive\n * Body cap: 64 KB total across all keepalive requests in flight.\n */\n keepalive?: boolean;\n /**\n * Per-request timeout override (ms). Defaults to the client's\n * `timeoutMs` (15s). Pass 0 to disable the timeout entirely — only\n * sensible for tests or long-poll endpoints we don't have today.\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 * Callers can pass it for ad-hoc retried POSTs too.\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 // Localhost short-circuit. Every request returns a synthetic\n // success shape so the SDK methods that depend on a response\n // (heartbeat, alias, getEntitlements) don't break — but no\n // packet leaves the browser. The shape is path-aware so common\n // callers get sensible empties.\n if (this.config.localDevMode) {\n return synthesizeLocalDevResponse<T>(path);\n }\n\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 if (options.idempotencyKey) {\n // Stripe pattern: same key on retries → server can short-circuit\n // duplicate work without inspecting the body.\n headers[\"Idempotency-Key\"] = options.idempotencyKey;\n }\n let bodyInit: BodyInit | 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 portal,\n // satellite link, DNS hang) doesn't lock the queue forever. Per-call\n // `timeoutMs: 0` disables, otherwise fall back to client default\n // (15s). The controller is scoped to this request only — clearing\n // the timer in finally prevents a stale abort from firing after we\n // already got a response. `AbortSignal.timeout(ms)` would be cleaner\n // but isn't supported in Safari < 16.4; this hand-rolled pattern is\n // portable to every browser fetch() supports.\n const effectiveTimeout = 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 keepalive: options.keepalive === true,\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 const parsed = await crossdeckErrorFromResponse(response);\n // Fire the contract-verifier hook BEFORE the throw so the\n // verifier sees every real wire envelope, not a sanitised\n // replay. Wrapped in try/catch so a buggy verifier can never\n // replace the customer's authentic error with one of our own.\n // The verifier layer is observability, not control flow.\n if (this.config.onErrorParsed) {\n try { this.config.onErrorParsed(parsed); } catch { /* swallow */ }\n }\n throw parsed;\n }\n\n // 204 No Content / OPTIONS-like — return undefined cast as T (callers\n // that 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 (err) {\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 /**\n * Whether this client is in localhost dev-mode short-circuit. Used\n * by other SDK pieces (event-queue) to skip network-bound work\n * entirely rather than going through synthesizeLocalDevResponse.\n */\n get isLocalDevMode(): boolean {\n return this.config.localDevMode === true;\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 * Build a synthetic response for localhost dev mode. Path-aware so\n * heartbeat / alias / entitlements / events callers each get a\n * sensible empty shape that won't crash downstream code.\n *\n * Heartbeat returns ok=true so the SDK considers itself \"started.\"\n * Alias returns a stub `cdcust_local_*` so identify() resolves to a\n * stable ID across calls (we mint a per-tab one via crypto.randomUUID\n * once and remember it). Entitlements returns an empty list — the\n * dev should grant entitlements in their own dashboard before relying\n * on isEntitled() locally.\n */\nlet cachedLocalCdcust: string | null = null;\nfunction synthesizeLocalDevResponse<T>(path: string): T {\n if (path.startsWith(\"/sdk/heartbeat\")) {\n return {\n object: \"heartbeat\",\n ok: true,\n projectId: \"proj_local_dev\",\n appId: \"app_local_dev\",\n platform: \"web\",\n env: \"sandbox\",\n serverTime: Date.now(),\n } as unknown as T;\n }\n if (path.startsWith(\"/identity/alias\")) {\n if (!cachedLocalCdcust) {\n const tail =\n typeof crypto !== \"undefined\" && \"randomUUID\" in crypto\n ? crypto.randomUUID().replace(/-/g, \"\").slice(0, 16)\n : Math.random().toString(36).slice(2, 18);\n cachedLocalCdcust = `cdcust_local_${tail}`;\n }\n return {\n object: \"alias_result\",\n crossdeckCustomerId: cachedLocalCdcust,\n linked: [],\n mergePending: false,\n env: \"sandbox\",\n } as unknown as T;\n }\n if (path.startsWith(\"/entitlements\")) {\n return {\n object: \"list\",\n data: [],\n crossdeckCustomerId: cachedLocalCdcust ?? \"\",\n env: \"sandbox\",\n } as unknown as T;\n }\n if (path.startsWith(\"/events\")) {\n return {\n object: \"list\",\n received: 0,\n env: \"sandbox\",\n } as unknown as T;\n }\n // Generic fallback — empty success shape.\n return {} as T;\n}\n","/**\n * Identity persistence for the SDK.\n *\n * Two values are tracked:\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 boots can read\n * entitlements directly without an alias call.\n *\n * ----- Bank-grade identity continuity (v0.6.0+) -----\n *\n * In a browser context, the SDK reads/writes BOTH localStorage and a\n * 1st-party cookie. This is the redundancy that keeps \"10k unique\n * visitors\" actually meaning 10k humans even when one store is wiped:\n *\n * - Read on boot: take whichever value exists. If both differ\n * (impossible in normal operation — would mean one store was\n * restored from a stale backup), localStorage wins because it's\n * the higher-fidelity store and what we wrote most recently.\n * - Write on every change: write to BOTH. Future clears of either\n * don't lose identity continuity.\n * - Reset: clear BOTH stores so logout actually wipes the device.\n *\n * Outside browsers (Node, Workers) the redundant cookie store is\n * absent and behaviour collapses to the single-store original — no\n * code path changes for non-web SDK consumers.\n */\n\nimport type { KeyValueStorage } from \"./types\";\n\nconst KEY_ANON = \"anon_id\";\nconst KEY_CDCUST = \"cdcust_id\";\n\nexport interface IdentityState {\n anonymousId: string;\n crossdeckCustomerId: string | null;\n}\n\nexport class IdentityStore {\n private state: IdentityState;\n /**\n * Optional secondary store written/read alongside the primary. When\n * present, every setItem fans out to both stores and getItem prefers\n * primary but falls back to secondary if primary returned null. This\n * is what gives us localStorage + cookie redundancy in browsers.\n */\n private readonly secondary: KeyValueStorage | null;\n\n constructor(\n private readonly primary: KeyValueStorage,\n private readonly prefix: string,\n secondary?: KeyValueStorage,\n ) {\n this.secondary = secondary ?? null;\n const anonFromPrimary = primary.getItem(prefix + KEY_ANON);\n const cdcustFromPrimary = primary.getItem(prefix + KEY_CDCUST);\n const anonFromSecondary = this.secondary?.getItem(prefix + KEY_ANON) ?? null;\n const cdcustFromSecondary = this.secondary?.getItem(prefix + KEY_CDCUST) ?? null;\n\n // Prefer the primary store's value; fall back to secondary on miss.\n // The \"both populated, values differ\" case never happens in normal\n // operation — every write goes to both stores in lockstep — so we\n // don't bother with conflict resolution beyond \"trust primary.\"\n const anon = anonFromPrimary ?? anonFromSecondary;\n const cdcust = cdcustFromPrimary ?? cdcustFromSecondary;\n\n this.state = {\n anonymousId: anon ?? this.mintAnonymousId(),\n crossdeckCustomerId: cdcust,\n };\n\n // If we just minted a new anonymousId, write it to both stores so\n // either store can answer \"what's our id\" on subsequent boots.\n // If we read it from one store but not the other, write it to the\n // missing store too — that's the resync that catches a recovering\n // ITP-cleared cookie or a freshly-minted private-tab localStorage.\n if (!anonFromPrimary || !anonFromSecondary) {\n this.writeBoth(prefix + KEY_ANON, this.state.anonymousId);\n }\n if (cdcust && (!cdcustFromPrimary || !cdcustFromSecondary)) {\n this.writeBoth(prefix + KEY_CDCUST, cdcust);\n }\n }\n\n /** Return the persisted anonymous device ID (always set). */\n get anonymousId(): string {\n return this.state.anonymousId;\n }\n\n /** Return the resolved crossdeckCustomerId once we have one, else null. */\n get crossdeckCustomerId(): string | null {\n return this.state.crossdeckCustomerId;\n }\n\n /** Persist a newly-resolved Crossdeck customer ID. */\n setCrossdeckCustomerId(value: string): void {\n this.state.crossdeckCustomerId = value;\n this.writeBoth(this.prefix + KEY_CDCUST, value);\n }\n\n /**\n * Wipe persisted identity. Called by reset() — used when an end-user\n * logs out. After reset the SDK mints a new anonymousId so the next\n * pre-login session is a fresh customer in the identity graph.\n */\n reset(): void {\n this.deleteBoth(this.prefix + KEY_ANON);\n this.deleteBoth(this.prefix + KEY_CDCUST);\n this.state = {\n anonymousId: this.mintAnonymousId(),\n crossdeckCustomerId: null,\n };\n this.writeBoth(this.prefix + KEY_ANON, this.state.anonymousId);\n }\n\n /**\n * Generate an anonymousId. Crockford-ish base32 timestamp + random\n * suffix. Same shape Stripe / Segment / others use — sortable, log-\n * friendly, no PII.\n */\n private mintAnonymousId(): string {\n const ts = Date.now().toString(36);\n const rand = randomChars(10);\n return `anon_${ts}${rand}`;\n }\n\n private writeBoth(key: string, value: string): void {\n try { this.primary.setItem(key, value); } catch { /* see storage.ts probe */ }\n if (this.secondary) {\n try { this.secondary.setItem(key, value); } catch { /* swallow per-store */ }\n }\n }\n\n private deleteBoth(key: string): void {\n try { this.primary.removeItem(key); } catch { /* swallow */ }\n if (this.secondary) {\n try { this.secondary.removeItem(key); } catch { /* swallow */ }\n }\n }\n}\n\n/**\n * Generate a cryptographically-random short string. Uses\n * crypto.getRandomValues when available (browser + Node 18+ via webcrypto),\n * else falls back to Math.random — that fallback is safe here because\n * anonymousId entropy doesn't need to resist offline brute force; it\n * needs to be unique-with-overwhelming-probability across one device's\n * lifetime.\n *\n * Exported for unit testing (alphabet round-trip).\n */\nexport function randomChars(count: number): string {\n const alphabet = \"0123456789abcdefghijklmnopqrstuvwxyz\";\n const out: string[] = [];\n const cryptoApi = (globalThis as { crypto?: { getRandomValues?: (a: Uint8Array) => Uint8Array } }).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 * Used to derive a per-user storage suffix for the entitlement\n * cache so each developerUserId's data lives under a physically\n * separate localStorage 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 * Why not SubtleCrypto: SubtleCrypto.digest is async, and the\n * entitlement-cache hot path (setFromList, clear, hydrate) is\n * synchronous. An async hash would force every cache operation\n * to become Promise<>-returning, cascading through identify(),\n * getEntitlements(), and the React `useEntitlement` hook. A\n * pure-JS sync impl keeps the API shape unchanged.\n *\n * Why not a smaller hash (FNV-1a etc): SHA-256 is the contract\n * documented in the SDK README. A non-crypto hash would\n * collision-resist for normal user populations but the public\n * promise is SHA-256.\n *\n * Implementation notes:\n * - Input: UTF-8 bytes from `String → encodeURIComponent`-style\n * percent-encoding. Pure ASCII is identity-mapped; multi-byte\n * runes are expanded to UTF-8 octets.\n * - Output: 64-character lowercase hex string.\n * - Independent reference vectors checked: SHA256(\"\") =\n * e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855,\n * SHA256(\"abc\") =\n * ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad.\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 // TextEncoder is universal in browsers + Node 11+ + RN (Hermes/JSC).\n // Constructed lazily so the module load doesn't depend on the global.\n if (typeof TextEncoder !== \"undefined\") {\n return new TextEncoder().encode(input);\n }\n // Hand-rolled UTF-8 encode for environments without TextEncoder.\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\n/**\n * Compute SHA-256 of `input` as a 64-character lowercase hex\n * string. Synchronous; safe to call on the hot path.\n */\nexport function sha256Hex(input: string): string {\n const bytes = utf8Bytes(input);\n const bitLength = bytes.length * 8;\n // Pad: append 0x80, then zeros, then 64-bit big-endian length to\n // reach a multiple of 512 bits (64 bytes).\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 // Length in bits as 64-bit big-endian. JS bitwise ops are 32-bit,\n // so split high / low halves.\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 * (localStorage, via the SDK's storage adapter).\n * - On SDK boot the cache hydrates from storage synchronously, so\n * isEntitled() answers correctly from the very first call — there\n * is no cold-start window where a returning Pro customer reads as\n * free.\n * - When the server is unreachable, the SDK keeps serving the last\n * entitlements it successfully fetched. A failed refresh never\n * reaches setFromList(), so it cannot clear the cache; only a\n * SUCCESSFUL fetch replaces it. An outage can never fail a paying\n * customer down to free.\n * - Staleness alone never returns false. Each entitlement is honoured\n * against its OWN validUntil instead — a time-based trial expiry\n * still applies even mid-partition, a still-valid Pro entitlement\n * rides the outage out.\n * - Staleness is VISIBLE, not silent. validUntil covers time-based\n * expiry; it does NOT cover an event-based revoke (chargeback,\n * refund, fraud) — that has no validUntil, so the cache would keep\n * serving a revoked customer through an outage. Serving them is the\n * right trade (don't lock real payers out), but unbounded-and-\n * invisible is the bug. So: once a refresh ATTEMPT fails (or the\n * data ages past staleAfterMs) the cache is marked stale —\n * isStale / freshness are surfaced in diagnostics(). It keeps\n * serving last-known-good; the staleness is just no longer hidden.\n *\n * The cache is wiped only on reset() (logout) and on an identity switch\n * — never by a TTL.\n *\n * Reactive listener API\n * ---------------------\n * `subscribe(listener)` registers a callback fired every time the cache\n * mutates (setFromList or clear) — the foundation for the\n * `useEntitlement` React hook and other framework bindings. Semantics:\n * - Fired AFTER the mutation, so the listener sees fresh state.\n * - Fire-and-forget: a throwing listener is swallowed (and counted)\n * so a buggy consumer can't crash the SDK or other listeners.\n * - Unsubscribe is idempotent.\n * - Listeners are NOT fired on subscribe — a caller that wants the\n * initial state reads isEntitled() / list() synchronously, which\n * work from boot thanks to hydration above.\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. Stable\n * across launches so an anonymous session's cache survives a reload\n * — but physically separate from any identified user's cache. */\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 localStorage. */\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 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. When omitted (tests) or\n * a MemoryStorage (strict-consent / no-persistence\n * mode) the cache is session-only — durability is\n * simply absent, never wrong.\n * @param storageKeyPrefix Prefix used to derive per-user storage keys\n * (`<prefix>:<sha256(userId)>`). Default\n * `crossdeck:entitlements`. The trailing user\n * suffix is filled at identify() / reset()\n * 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 this.hydrate();\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. The\n * raw userId never lands in the storage key — protects against\n * accidentally leaking email-style identifiers through DevTools\n * inspection. Pass `null` to switch back to the anonymous slot. */\n static suffixForUserId(userId: string | null): string {\n if (userId == null || userId === \"\") return ANON_SUFFIX;\n return sha256Hex(userId);\n }\n\n /**\n * Switch the cache to a different user's storage slot. Bank-grade\n * three-layer isolation:\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 invoke this BEFORE the next\n * setFromList() so the write lands under the right key.\n */\n setUserKey(userId: string | null): void {\n const nextSuffix = EntitlementCache.suffixForUserId(userId);\n if (nextSuffix === this.currentSuffix) {\n // Same user (or repeated anonymous) — still unconditionally\n // wipe in-memory cache to satisfy the founder's \"unconditional\n // clear on identify\" contract.\n this.all = [];\n this.lastUpdated = 0;\n this.lastRefreshFailedAt = 0;\n this.notify();\n // Re-hydrate from the same slot so a fresh boot's\n // last-known-good is honoured.\n this.hydrate();\n return;\n }\n this.currentSuffix = nextSuffix;\n // New slot: wipe in-memory + rehydrate from new slot.\n this.all = [];\n this.lastUpdated = 0;\n this.lastRefreshFailedAt = 0;\n this.hydrate();\n this.notify();\n }\n\n /**\n * Sync read — true iff the entitlement is currently granting access.\n *\n * Served from last-known-good: a stale cache (server unreachable since\n * the last successful fetch) still answers true for a still-valid\n * entitlement. The ONLY thing that turns it false is the entitlement's\n * own expiry (validUntil) — never 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 data.\n *\n * True when the most recent refresh ATTEMPT failed (Crossdeck\n * unreachable since the last success — the outage case, distinct from\n * a benign idle tab that simply hasn't re-fetched), OR when\n * last-known-good has aged past staleAfterMs.\n *\n * isStale never changes what isEntitled() returns — the cache still\n * serves last-known-good. It exists so the staleness is observable\n * (diagnostics()) instead of an unbounded silent window where a\n * revoked customer holds access with nobody able 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 / a\n * transient error). The SDK's getEntitlements() calls this in its\n * catch path. It does NOT touch the cached entitlements — last-known-\n * good keeps serving — it only flips isStale so the staleness shows\n * 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 to\n * device storage so it survives a reload / app restart.\n *\n * Called ONLY after a successful server read — a failed fetch throws\n * before it reaches here, so last-known-good is preserved through an\n * 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 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 without affecting other\n * persisted slots. The full-logout path is [[clearAll]].\n */\n clear(): void {\n this.all = [];\n this.lastUpdated = 0;\n this.lastRefreshFailedAt = 0;\n if (this.storage) {\n try {\n this.storage.removeItem(this.storageKey);\n } catch {\n // Private mode / quota — best-effort, same posture as storage.ts.\n }\n }\n this.removeSuffixFromIndex(this.currentSuffix);\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 * After clearAll(), the cache is back to anonymous + empty.\n */\n clearAll(): void {\n this.all = [];\n this.lastUpdated = 0;\n this.lastRefreshFailedAt = 0;\n this.currentSuffix = ANON_SUFFIX;\n if (this.storage) {\n const suffixes = this.readIndex();\n for (const suffix of suffixes) {\n try {\n this.storage.removeItem(`${this.storageKeyPrefix}:${suffix}`);\n } catch {\n // best-effort\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 try {\n this.storage.removeItem(`${this.storageKeyPrefix}:${ANON_SUFFIX}`);\n } catch {\n // best-effort\n }\n try {\n this.storage.removeItem(this.indexKey);\n } catch {\n // best-effort\n }\n }\n this.notify();\n }\n\n /**\n * Subscribe to cache mutations. Returns an idempotent unsubscribe fn.\n * The listener fires AFTER setFromList() or clear() with the current\n * snapshot. Used by `@cross-deck/web/react`'s `useEntitlement` hook.\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 /**\n * Load last-known-good from device storage. Runs once in the\n * constructor, synchronously, so isEntitled() is correct from boot.\n * Any corrupt / unparseable blob degrades silently to an empty cache —\n * boot must never throw.\n */\n private hydrate(): void {\n if (!this.storage) return;\n try {\n const raw = this.storage.getItem(this.storageKey);\n if (!raw) return;\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 } catch {\n // Corrupt / unparseable blob → start empty. Never throw on boot.\n }\n }\n\n /** Write last-known-good to device storage. Best-effort. */\n private persist(): void {\n if (!this.storage) return;\n try {\n const blob: PersistedCache = {\n v: 1,\n entitlements: this.all,\n lastUpdated: this.lastUpdated,\n };\n this.storage.setItem(this.storageKey, JSON.stringify(blob));\n } catch {\n // Quota exceeded / private mode — the in-memory cache still works\n // for this session; we just lose cross-reload durability.\n }\n }\n\n /** Read the index of all per-user suffixes the SDK has written. */\n private readIndex(): string[] {\n if (!this.storage) return [];\n try {\n const raw = 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 recordSuffixInIndex(suffix: string): void {\n if (!this.storage) return;\n const existing = this.readIndex();\n if (existing.includes(suffix)) return;\n existing.push(suffix);\n try {\n this.storage.setItem(this.indexKey, JSON.stringify(existing));\n } catch {\n // best-effort\n }\n }\n\n /** Remove a suffix from the persisted index. No-op if absent. */\n private removeSuffixFromIndex(suffix: string): void {\n if (!this.storage) return;\n const existing = this.readIndex();\n const next = existing.filter((s) => s !== suffix);\n if (next.length === existing.length) return;\n try {\n if (next.length === 0) {\n this.storage.removeItem(this.indexKey);\n } else {\n this.storage.setItem(this.indexKey, JSON.stringify(next));\n }\n } catch {\n // best-effort\n }\n }\n\n private notify(): void {\n if (this.listeners.size === 0) return;\n const snapshot = this.all.slice();\n // Iterate a snapshot of the listener set so a listener that\n // unsubscribes itself (or registers a new one) during dispatch\n // doesn't break the iteration.\n const listenersSnapshot = [...this.listeners];\n for (const listener of listenersSnapshot) {\n try {\n listener(snapshot);\n } catch {\n // Swallow listener errors — a buggy consumer shouldn't break the\n // SDK or other listeners. Counted for diagnostics().\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. Pre-v1.4.0 each\n * call minted a fresh random UUID — two retries of the same\n * purchase (network blip, app crash mid-flight, deliberate caller\n * retry) got DIFFERENT keys, so server-side idempotency could not\n * collapse them. The bank-grade contract every Stripe-grade API\n * ships: same input → same key → same response.\n *\n * Algorithm:\n * 1. Extract a stable identifier from the request body:\n * - Apple: the signed JWS string (uniquely identifies the\n * transaction by Apple's signature).\n * - Google: the purchaseToken.\n * 2. SHA-256 the identifier.\n * 3. Format the first 32 hex chars of the digest as a UUID-shaped\n * string (8-4-4-4-12). Backend treats the key as opaque so\n * RFC 4122 version/variant bits are unnecessary — what matters\n * is determinism.\n *\n * The resulting key is identical across retries of the same\n * transaction, so the backend's idempotency cache short-circuits\n * with `idempotent_replay: true` in the response.\n */\n\nimport { sha256Hex } from \"./hash\";\n\nexport interface PurchaseSyncIdentity {\n rail: \"apple\" | \"google\" | \"stripe\" | string;\n signedTransactionInfo?: string;\n purchaseToken?: string;\n}\n\n/**\n * Format a hex string as `8-4-4-4-12` UUID shape using its first\n * 32 hex chars. Used for the idempotency-key derivation — the\n * shape matches what backend logs / dashboards already pattern-\n * match against; treating it as a UUID at the wire makes\n * inspection familiar even though it isn't RFC 4122 versioned.\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\n/**\n * Deterministic Idempotency-Key for a purchase sync. Returns the\n * canonical UUID-shaped string the SDK sends as the\n * `Idempotency-Key` header.\n *\n * Same input → same key → backend returns the cached response\n * with `idempotent_replay: true` instead of double-processing.\n *\n * Throws on bodies that have no stable identifier — never silently\n * fall back to a fresh random key, since that would defeat the\n * very contract this helper exists to enforce.\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 // Namespace the digest so the same JWS used by /purchases/sync\n // and a hypothetical future /purchases/verify produce DIFFERENT\n // keys — prevents cross-endpoint idempotency collisions.\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 * After a failed flush, the queue must wait some time before trying\n * again — otherwise a flapping backend causes a hot loop, and a 429\n * \"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 at\n * the same instant. Spread the storm.\n * - 429 / 503 `Retry-After`: ALWAYS honour the server-supplied delay\n * when it's larger than our computed backoff. The server knows its\n * own capacity better than we do; ignoring it is what gets your IP\n * blocked.\n * - Reset on success.\n *\n * The policy is a pure object — no state, no timers. The EventQueue\n * owns the timer, the policy owns the math.\n *\n * Default values match Stripe-JS-style retry windows:\n * - baseMs: 1000 (first retry ~1s out)\n * - maxMs: 60000 (never wait longer than 60s)\n * - factor: 2 (1s, 2s, 4s, 8s, 16s, 32s, 60s, 60s, …)\n *\n * After `maxConsecutiveFailures` (default 8) without a success, the\n * caller is expected to surface that as a `lastError` for the\n * developer to see in diagnostics. We never stop retrying — events\n * matter and a transient outage can take hours — but we report it\n * clearly so the dev knows their data is queued, not lost.\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\n/**\n * Compute the next retry delay (ms) given the consecutive-failure\n * count and an optional server-supplied Retry-After (ms).\n *\n * computeNextDelay(0, undefined) → ~500ms (jittered 0-1000)\n * computeNextDelay(3, undefined) → ~4s (jittered 0-8000)\n * computeNextDelay(0, 30_000) → 30s (server wins)\n * computeNextDelay(8, undefined) → 60s (capped)\n *\n * Pure function — exported for testing. Real callers should go through\n * `RetryPolicy.nextDelay` so option defaults stay co-located.\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\n // Cap attempts so 2^attempts doesn't overflow into Infinity.\n const safeAttempts = Math.min(attempts, 30);\n const ceiling = Math.min(max, base * Math.pow(factor, safeAttempts));\n // Full jitter: random across [0, ceiling]. Caller can substitute a\n // deterministic RNG for testing.\n const jittered = ceiling * random();\n // Honour server's Retry-After when bigger than our window — the\n // server's the authority on its own pressure. Pre-fix this was\n // clamped to `maxMs` (60s default), which meant a `Retry-After: 120`\n // got truncated to 60s and we hammered an already-rate-limited\n // endpoint twice as fast as it asked. Honour the server delay\n // as-is, but cap at 24h as a final sanity guard against an absurd\n // value (server bug / clock skew on an HTTP-date form) that would\n // otherwise wedge the queue for years. RFC 7231 doesn't require\n // honouring beyond that.\n if (retryAfterMs !== undefined) {\n const ABSOLUTE_MAX_MS = 24 * 60 * 60 * 1000; // 24h\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 /** How many consecutive failures since the last success. */\n get consecutiveFailures(): number {\n return this.attempts;\n }\n\n /** Whether we've crossed the failuresBeforeWarn threshold. */\n get isWarning(): boolean {\n return this.attempts >= (this.options.failuresBeforeWarn ?? DEFAULT_WARN);\n }\n\n /** Schedule-time delay for the NEXT retry. Increments the counter. */\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 /** Mark a successful flush — reset the counter. */\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 clicks, screen\n * views) and shouldn't block the UI on a network round-trip. Events go\n * into a local buffer, flushed in bursts.\n *\n * Flush triggers:\n * - Buffer reaches batchSize (default 20) → flush immediately.\n * - intervalMs of inactivity (default 1500ms) → flush idle batch.\n * - flush() called explicitly (e.g. before page unload).\n *\n * Wave 1 hardening (v0.8.0+, the \"bank-grade plumbing\" pass):\n *\n * - Exponential backoff with full jitter on flush failures. Respects\n * server `Retry-After` headers (parsed onto CrossdeckError by the\n * HTTP layer). Replaces the prior policy of \"retry on the next\n * idle window\" which hot-looped against a flapping endpoint.\n *\n * - Durable persistence. Events are written through to a\n * `PersistentEventStore` (localStorage by default) so a hard\n * browser crash / power loss / keepalive cap exceedance doesn't\n * drop data. The next SDK boot replays the persisted queue.\n *\n * - Per-batch `Idempotency-Key`. Same key is reused across retries\n * of the SAME batch so the server can short-circuit duplicate work\n * without inspecting bodies. The backend ALSO dedupes individual\n * events via CH ReplacingMergeTree on `eventId`, so this is belt-\n * and-suspenders.\n *\n * - Property validation runs upstream in crossdeck.ts:track() — by\n * the time an event lands in this queue, it's known to be safe\n * to JSON.stringify.\n *\n * On a permanent network outage we keep retrying with bounded backoff;\n * we never drop events because of network failures alone. The only\n * drop path is the hard buffer cap (1000 events): once exceeded we\n * evict the OLDEST events and increment `dropped` so the developer\n * can see the loss in `diagnostics()`.\n */\n\nimport type { HttpClient } from \"./http\";\nimport type { EventProperties, IngestResponse } from \"./types\";\nimport type { CrossdeckError } from \"./errors\";\nimport { RetryPolicy, type RetryPolicyOptions } from \"./retry-policy\";\nimport { 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 /**\n * Event Envelope v1 §3 — per-session monotonic sequence number.\n * Assigned synchronously at track() time from the session-scoped counter\n * (AutoTracker.nextSeq()), reset to 0 at session.started. The\n * deterministic tiebreak for events sharing a timestamp. Persisted on\n * disk alongside the event so a delayed flush across a\n * background/foreground cycle keeps its original seq.\n */\n seq: number;\n /**\n * Event Envelope v1 §4 — standardized device/platform context,\n * promoted OUT of `properties` into one named object. Keys: os,\n * osVersion, appVersion, sdkName, sdkVersion, locale, timezone,\n * browser, browserVersion. App-supplied properties stay in `properties`.\n */\n context: Record<string, string>;\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 POST.\n * It's a function (not a value) so a future call to setDebugMode or a\n * config swap can update the envelope without re-instantiating the\n * queue.\n */\n envelope: () => BatchEnvelope;\n /** Schedule a function to run after `ms` ms. Default: setTimeout. Override for tests. */\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 construction, persisted events\n * are loaded back into the buffer. Omitting this is fine for tests\n * and Node consumers — the queue falls back to in-memory only.\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 queue\n * 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 a\n * permanent 4xx (anything except 408 Request Timeout / 429 Too Many\n * Requests). The host SDK should surface this loudly — pre-fix the\n * queue retried 4xx errors forever with the same Idempotency-Key,\n * silently growing the backlog while the customer thought events\n * were landing. 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 * Fired when the server PARKS this SDK with HTTP 426 Upgrade Required\n * (code `sdk_version_unsupported`) — the SDK's wire dialect is too old\n * for the server to accept. This is the third outcome, distinct from\n * retry (transient) and drop (invalid): the data is GOOD, only the\n * format is stale. The queue holds the events durably, stops flushing\n * (the same payload will fail until the app upgrades the SDK), and on\n * the next boot AFTER upgrade the rehydrated queue delivers them — so\n * \"paused, not lost — held on-device, resumes on upgrade\" is literally\n * true. The host SDK surfaces this to the developer's own console once\n * and to the dashboard (via the heartbeat) so both channels carry the\n * same cure. `minVersion` is the server's required floor when supplied.\n */\n onParked?: (info: { minVersion?: string; surface?: string }) => 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 permanent\n * failure). On a retry-driven re-flush we re-use this slot alongside\n * `pendingBatchId` so the Stripe-style Idempotency-Key is preserved\n * across retries of the SAME logical batch.\n *\n * Pre-fix the splice cleared the buffer AND we immediately\n * `persistent.save(empty)` BEFORE awaiting the network call — a\n * crash mid-flight wiped the persisted blob and the batch was lost\n * with no replay on next boot. Now the persisted blob always carries\n * `[...pendingBatch, ...buffer]` so the in-flight events survive\n * until the server confirms them.\n *\n * Pre-fix every retry attempt also minted a NEW batchId via\n * `splice + mintBatchId`, defeating the backend's\n * `Idempotency-Key` dedup. Reuse via this slot brings web in\n * lockstep with node (which already had the field).\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 /**\n * PARK state (HTTP 426 / `sdk_version_unsupported`). Once parked, the\n * queue stops flushing — the server has told us our wire dialect is too\n * old, and retrying the same payload only burns the user's battery and\n * bandwidth and drips pointless rejects into the server logs until the\n * app ships an upgraded SDK. The held events stay durable (persisted)\n * and are delivered by the next boot's rehydrate, post-upgrade. Parked\n * is per-instance: a fresh boot (the upgraded build) starts unparked.\n */\n private parked = false;\n /** One developer-facing console warning per instance — never per-event spam. */\n private parkWarned = false;\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 // Rehydrate any events left over from a prior session (crash, hard\n // close, keepalive cap exceeded). Eventid-based dedup at the server\n // means re-sending an event that may have already landed is safe.\n // Rehydrated events land in `buffer` — the \"was in-flight\" axis\n // doesn't survive a crash, so they're treated like fresh enqueues.\n if (this.persistent) {\n const restored = this.persistent.load();\n if (restored.length > 0) {\n // Apply the same hard cap on rehydrate — defends against a\n // malicious / corrupted blob with a million entries.\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 }\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). On a retryable failure the batch\n * stays in `pendingBatch` for the next scheduled retry — the SAME\n * batch with the SAME `Idempotency-Key` is re-sent (Stripe pattern).\n *\n * Three terminal states from one call:\n * - 2xx success: pendingBatch cleared, persisted state collapses to\n * just `buffer` (any new events that arrived during in-flight).\n * - 4xx permanent (except 408/429): pendingBatch DROPPED, `dropped`\n * counter increments, a `permanent_failure` signal fires. The\n * server is telling us our request is malformed / our key is\n * revoked / we lack permission — retrying with the same key\n * forever just grows the queue while the customer thinks events\n * are landing.\n * - 5xx / network / 408 / 429: pendingBatch + batchId stay; backoff\n * schedules a retry; the next `flush()` re-uses both.\n *\n * `options.keepalive` marks the underlying fetch as keepalive so the\n * browser keeps the request alive past page unload. Use for terminal\n * flushes (pagehide / visibilitychange→hidden / beforeunload).\n */\n async flush(options: { keepalive?: boolean } = {}): Promise<IngestResponse | null> {\n // PARK hush: once the server has rejected our wire dialect as too old\n // (426), every flush of the same-format payload will fail identically.\n // Hold — don't flush — until the next boot on an upgraded SDK. The\n // events stay buffered + persisted, so they deliver on that boot.\n if (this.parked) return 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 persistAll()\n // until the server confirms it — that's the 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 // Event Envelope v1 batch envelope (backend/docs/\n // event-envelope-spec-v1.md §1). `envelopeVersion` is the\n // schema/wire version the server parses against (\"can I parse\n // this?\") and is DISTINCT from `sdk.version` (\"which build is in\n // the wild?\") — two questions, two fields, never conflated. The\n // backend refuses payloads with a missing/unknown envelopeVersion.\n envelopeVersion: 1,\n appId: env.appId,\n environment: env.environment,\n sdk: env.sdk,\n events: batch,\n },\n keepalive: options.keepalive === true,\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 // PARK (HTTP 426 / `sdk_version_unsupported`) — the THIRD outcome.\n // Checked BEFORE the permanent-4xx drop so a version-rejection never\n // falls into the drop path. The data is good; only the dialect is\n // old. Keep every held event — fold the in-flight batch back to the\n // FRONT of the buffer (oldest-first) so it persists and the next\n // (upgraded) boot's rehydrate delivers it — then hush (no retry\n // scheduled; the flush guard above blocks further attempts).\n if (isVersionRejected(err)) {\n this.parked = true;\n this.buffer = [...batch, ...this.buffer];\n if (this.buffer.length > HARD_BUFFER_CAP) {\n const overflow = this.buffer.length - HARD_BUFFER_CAP;\n this.buffer.splice(0, overflow); // FIFO: evict oldest, keep newest 1000\n this.dropped += overflow;\n }\n this.pendingBatch = null;\n this.pendingBatchId = null;\n this.inFlight -= batch.length;\n this.persistAll();\n this.cfg.onBufferChange?.(this.buffer.length);\n const minVersion = versionRejectionFloor(err);\n if (!this.parkWarned) {\n this.parkWarned = true;\n // ONE developer-facing console line — the terminal-side cure that\n // reaches them mid-debug, paired with the dashboard banner.\n console.warn(\n \"[Crossdeck] SDK outdated — the server is no longer accepting this \" +\n \"version's event format. Your events are PARKED on-device (held, \" +\n \"not lost) and will deliver automatically once you update \" +\n `@cross-deck/web${minVersion ? ` to >= ${minVersion}` : \"\"} and redeploy.`,\n );\n }\n this.cfg.onParked?.({ minVersion, surface: versionRejectionSurface(err) });\n return null;\n }\n\n // Permanent failures (4xx except 408/429) are NOT retryable. The\n // server is telling us our request is malformed (400/422), our\n // key is revoked (401), we lack permission (403), or the endpoint\n // doesn't exist (404). Retrying with the same Idempotency-Key\n // forever just grows the queue silently while the customer\n // thinks events are landing. 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 (5xx / network / 408 / 429). pendingBatch +\n // pendingBatchId stay set; the next scheduler-driven flush re-uses\n // both. Persisted state is unchanged from the entry path — it\n // still includes `[...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 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` counts events waiting for their FIRST flush. The\n // in-flight pendingBatch (retrying) is tracked separately via\n // `inFlight` — surfacing both lets diagnostics show \"we have\n // events stuck retrying\" distinct from \"new events arriving\".\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 * Production callers don't need this.\n */\n get pendingIdempotencyKey(): string | null {\n return this.pendingBatchId;\n }\n\n /**\n * Persist `[...pendingBatch, ...buffer]` — the full set of\n * not-yet-confirmed events. The next boot rehydrates this exact set\n * into `buffer` and replays. The server dedups via eventId\n * (ReplacingMergeTree on the warehouse side), so re-sending an event\n * that may have already landed is safe.\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 // ---------- internal scheduling ----------\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 Many\n * Requests — both indicate transient state where the SAME request\n * (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 // 426 is PARK, never drop — handled by isVersionRejected before this is\n // ever reached, but excluded here too so no path can drop a parkable batch.\n if (status === 426) return false;\n return true;\n}\n\n/**\n * True when the server PARKED this SDK: HTTP 426 Upgrade Required, the\n * status code invented for exactly this — \"your client is too old.\"\n * Unambiguous (no proxy or middleware emits 426 spuriously, unlike\n * 400/422), and corroborated by the machine code `sdk_version_unsupported`\n * so a status-rewriting layer can't hide it. Distinct from a permanent\n * 4xx drop: the data is good, only the wire dialect is stale.\n */\nfunction isVersionRejected(err: unknown): boolean {\n if (!err || typeof err !== \"object\") return false;\n if ((err as { status?: unknown }).status === 426) return true;\n return (err as { code?: unknown }).code === \"sdk_version_unsupported\";\n}\n\n/** Server-supplied required version floor from the 426 body, if present. */\nfunction versionRejectionFloor(err: unknown): string | undefined {\n const v = (err as { minVersion?: unknown } | null)?.minVersion;\n return typeof v === \"string\" && v.length > 0 ? v : undefined;\n}\n\n/** Server-supplied surface id from the 426 body, if present. */\nfunction versionRejectionSurface(err: unknown): string | undefined {\n const v = (err as { surface?: unknown } | null)?.surface;\n return typeof v === \"string\" && v.length > 0 ? v : undefined;\n}\n\nfunction defaultScheduler(fn: () => void, ms: number): () => void {\n // Use unref()-style behaviour where supported so a pending flush doesn't\n // block Node from exiting. setTimeout in browsers ignores .unref() —\n // that's fine.\n const id = setTimeout(fn, ms);\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 failure\n * modes lose data today:\n *\n * 1. Page unload with a failed terminal flush. `keepalive: true`\n * survives the unload, but only up to 64 KB total across all\n * keepalive requests. A large batch on a slow network drops past\n * that cap.\n * 2. Hard browser crash / power loss. The in-memory buffer goes with\n * the process.\n * 3. Network down for longer than the user's session. Events that\n * stay in the in-memory buffer disappear when the tab closes.\n *\n * Stripe / Segment / PostHog all persist queued events to a durable\n * store (localStorage for browsers, IndexedDB for very large queues,\n * AsyncStorage on RN) and replay them on the next boot. We do the\n * same here with localStorage as the default backing store.\n *\n * Failure modes handled gracefully:\n * - Storage throws (quota exceeded, private mode, sandboxed iframe)\n * → silent degrade to in-memory only. The SDK keeps working; the\n * durability 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 localStorage value.\n * - Storage write contention from another tab → last-writer-wins is\n * fine because every queued event has an `eventId` and the\n * backend dedupes via ReplacingMergeTree. Cross-tab coordination\n * via BroadcastChannel is a Phase 2 follow-up.\n *\n * The storage key is `${prefix}queue.v1` to leave room for future\n * format migrations.\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 events captured on the most recent write request. We keep\n // the latest snapshot ref so a debounced write always picks up the\n // freshest buffer state.\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 no\n * warning) when nothing is stored, the blob is malformed, or storage\n * is unavailable. Caller is responsible for treating duplicates from\n * the persisted queue as the SAME events (eventId-based dedup).\n */\n load(): QueuedEvent[] {\n let raw: string | null;\n try {\n raw = 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 so\n * a burst of enqueue() calls coalesces into one persistence write.\n * Writes are best-effort: if storage throws (quota, private mode),\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 change\n // 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 /** Synchronous variant for terminal flushes (pagehide / beforeunload). */\n saveSync(snapshot: readonly QueuedEvent[]): void {\n this.pendingSnapshot = snapshot.slice();\n this.flushWrite();\n }\n\n /** Wipe the persisted blob. Used by reset() (logout). */\n clear(): void {\n this.pendingSnapshot = null;\n this.writeScheduled = false;\n try {\n 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 try {\n this.options.storage.removeItem(this.key);\n } catch {\n // ignore\n }\n return;\n }\n\n const blob: PersistedQueue = { version: 1, events: snapshot };\n try {\n this.options.storage.setItem(this.key, JSON.stringify(blob));\n } catch {\n // Quota exceeded / private mode / etc. — silent degrade. The\n // in-memory buffer is still authoritative; we just lose\n // crash-safety for this batch.\n }\n }\n}\n","/**\n * Storage adapters for SDK-persisted state.\n *\n * Three flavours:\n * - browser localStorage (default in browsers)\n * - 1st-party document.cookie (redundancy for cleared localStorage)\n * - in-memory (default in Node, or as an explicit fallback)\n *\n * Detection is at construction time, not at every call — picking the\n * adapter once means we don't hit `typeof window` checks on hot paths.\n *\n * ----- Bank-grade identity continuity -----\n *\n * Plain localStorage is not enough. ITP, private browsing, \"clear site\n * data\" actions, and aggressive privacy extensions all wipe it. When\n * that happens, the SDK mints a fresh anonymousId on next page load\n * and the customer's analytics see one human as multiple \"new\n * visitors\" — a credibility hit on every dashboard chart that depends\n * on visitor uniqueness (new vs returning, retention, funnels).\n *\n * The fix is redundancy: we write the same identity to BOTH\n * localStorage AND a 1st-party cookie. On boot we read both; whichever\n * survived wins. On set, we write to both stores so a future clear of\n * either doesn't lose the user.\n *\n * Caveats (documented honestly):\n * 1. Safari ITP caps client-set 1st-party cookies at 7 days. Cookie\n * redundancy protects against localStorage clears WITHIN that\n * 7-day window, not beyond it. The full ITP-bypass story (server-\n * set cookies via a customer-CNAMEd subdomain) is a Phase 2\n * follow-up that requires customer DNS configuration.\n * 2. We never write fingerprintable data — only the same anonymousId\n * already in localStorage. Privacy posture is unchanged from\n * single-store identity.\n * 3. `persistIdentity: false` disables BOTH stores so customers\n * running strict consent flows can defer cookie writes until the\n * user opts in.\n */\n\nimport type { KeyValueStorage } from \"./types\";\n\n/**\n * In-memory storage. Cleared on process exit. Useful for Node runtimes\n * where you want session-scoped identity that doesn't persist to disk.\n */\nexport class MemoryStorage implements KeyValueStorage {\n private store = new Map<string, string>();\n getItem(key: string): string | null {\n return this.store.get(key) ?? null;\n }\n setItem(key: string, value: string): void {\n this.store.set(key, value);\n }\n removeItem(key: string): void {\n this.store.delete(key);\n }\n}\n\n/**\n * 1st-party cookie storage. All writes set:\n * - Path=/ — visible site-wide so SPA route changes\n * keep the same identity\n * - Max-Age=63072000 — 2 years (clamped to 7 days by Safari ITP\n * but written long anyway for non-ITP UAs)\n * - SameSite=Lax — standard for 1st-party identity, blocks\n * cross-site request abuse but allows\n * top-level navigation reads\n * - Secure — only set when page is served over HTTPS;\n * omitted on http://localhost so dev still\n * works without a TLS cert\n *\n * We do NOT set HttpOnly because the SDK itself needs to read the\n * cookie via document.cookie to honour the redundancy contract. That\n * means malicious JS on the same origin could read the anonymousId,\n * which is the same security posture as localStorage — anything that\n * can read localStorage can read this cookie. Stripe, Segment, and\n * PostHog ship with the same trade-off for the same reason.\n *\n * Empty / unparseable cookie strings degrade silently to null. We never\n * throw — a broken cookie should look identical to \"no cookie set.\"\n */\nexport class CookieStorage implements KeyValueStorage {\n private readonly maxAgeSec: number;\n private readonly secure: boolean;\n private readonly sameSite: \"Lax\" | \"Strict\" | \"None\";\n\n constructor(options?: {\n maxAgeSec?: number;\n secure?: boolean;\n sameSite?: \"Lax\" | \"Strict\" | \"None\";\n }) {\n this.maxAgeSec = options?.maxAgeSec ?? 63_072_000; // 2 years\n this.secure = options?.secure ?? defaultSecure();\n this.sameSite = options?.sameSite ?? \"Lax\";\n }\n\n getItem(key: string): string | null {\n if (!hasDocument()) return null;\n const doc = (globalThis as { document: Document }).document;\n const cookies = doc.cookie ? doc.cookie.split(/;\\s*/) : [];\n const prefix = encodeURIComponent(key) + \"=\";\n for (const c of cookies) {\n if (c.startsWith(prefix)) {\n try {\n return decodeURIComponent(c.slice(prefix.length));\n } catch {\n return null;\n }\n }\n }\n return null;\n }\n\n setItem(key: string, value: string): void {\n if (!hasDocument()) return;\n const doc = (globalThis as { document: Document }).document;\n const parts = [\n `${encodeURIComponent(key)}=${encodeURIComponent(value)}`,\n \"Path=/\",\n `Max-Age=${this.maxAgeSec}`,\n `SameSite=${this.sameSite}`,\n ];\n if (this.secure) parts.push(\"Secure\");\n try {\n doc.cookie = parts.join(\"; \");\n } catch {\n // Some embedded webviews block document.cookie writes — swallow.\n // localStorage redundancy still gives us identity continuity.\n }\n }\n\n removeItem(key: string): void {\n if (!hasDocument()) return;\n const doc = (globalThis as { document: Document }).document;\n // Negative Max-Age + matching path expires the cookie immediately.\n // We keep the same SameSite/Secure attributes so browsers actually\n // accept the deletion request as targeting the same cookie.\n const parts = [\n `${encodeURIComponent(key)}=`,\n \"Path=/\",\n \"Max-Age=0\",\n `SameSite=${this.sameSite}`,\n ];\n if (this.secure) parts.push(\"Secure\");\n try {\n doc.cookie = parts.join(\"; \");\n } catch {\n // Same reasoning as setItem — swallow.\n }\n }\n}\n\n/**\n * Pick the best available storage. Browser → localStorage if accessible,\n * else MemoryStorage. Node → MemoryStorage. Caller can override via\n * Crossdeck.start({ storage: ... }) for custom adapters (RN AsyncStorage,\n * Cookies, encrypted vaults, etc.).\n *\n * We probe localStorage with a try/catch because some environments\n * (private mode Safari, embedded webviews) define `localStorage` but\n * throw on every call — falling back to memory keeps us correct.\n */\nexport function detectDefaultStorage(): KeyValueStorage {\n try {\n const ls = (globalThis as { localStorage?: KeyValueStorage }).localStorage;\n if (ls) {\n // Probe with a no-op write to confirm we can actually use it.\n const probe = \"__crossdeck_probe__\";\n ls.setItem(probe, \"1\");\n ls.removeItem(probe);\n return ls;\n }\n } catch {\n // Private mode / sandboxed iframe / quota exceeded — fall through.\n }\n return new MemoryStorage();\n}\n\n/**\n * Detect whether the current page is served over HTTPS so we can set\n * the Secure cookie attribute. Defensive against environments where\n * `location` is missing (Workers, server-rendered pre-hydration).\n */\nfunction defaultSecure(): boolean {\n try {\n const loc = (globalThis as { location?: Location }).location;\n return loc?.protocol === \"https:\";\n } catch {\n return false;\n }\n}\n\nfunction hasDocument(): boolean {\n return typeof (globalThis as { document?: unknown }).document !== \"undefined\";\n}\n","/**\n * Read-cost bridge — the one-way seam from the Crossdeck SDK to the Buckets OSS\n * collector (`@cross-deck/buckets`), without either package depending on the other.\n *\n * Buckets, at `init()`, registers a setter on a well-known global key. This module\n * looks that setter up and calls it. If Buckets isn't installed, the global is\n * absent and every call here is a silent no-op — the SDK never requires Buckets,\n * and Buckets never requires the SDK.\n *\n * What it carries is the cross-match input: WHO (the identified user behind a\n * request) and WHAT (the route / operation). Buckets stamps those onto every\n * database read that happens inside the request's async context, so a heavy read\n * attributes to \"this user's this operation\" instead of an anonymous collection.\n *\n * The global key and context shape MUST match `@cross-deck/buckets`'\n * `actor-bridge.ts` (`BUCKETS_BRIDGE_KEY`, `RequestContext`). They are a wire\n * contract between two independently-published packages — keep them in lockstep.\n */\n\n/** Must equal `BUCKETS_BRIDGE_KEY` in @cross-deck/buckets. */\nconst BUCKETS_BRIDGE_KEY = \"__crossdeckBucketsBridge__\";\n\n/** The cross-match context. Only the provided fields are applied. */\nexport interface ReadCostContext {\n /** WHO — the identified user behind this request (the developer's own id). */\n actor?: string;\n /** WHAT — the operation/feature that spent the reads. */\n feature?: string;\n /** WHAT (fallback) — the matched route pattern, e.g. `/users/:id`. */\n route?: string;\n}\n\ntype BridgeSetter = (ctx: ReadCostContext) => void;\n\n/**\n * Push the cross-match context into Buckets for the current request's async\n * context. No-op (and never throws) when the Buckets collector isn't installed.\n */\nexport function bridgeReadCost(ctx: ReadCostContext): void {\n try {\n const setter = (globalThis as Record<string, unknown>)[BUCKETS_BRIDGE_KEY] as\n | BridgeSetter\n | undefined;\n if (typeof setter === \"function\") setter(ctx);\n } catch {\n /* metering is best-effort — never disturb the host application */\n }\n}\n","/**\n * Device + environment enrichment.\n *\n * Auto-attached to every event the SDK emits when `autoTrack.deviceInfo` is\n * enabled (default). Caller-supplied event properties always override\n * auto-detected ones (so a developer can manually set `app.version` per\n * event if they want to A/B between builds).\n *\n * Privacy posture:\n * - No fingerprinting (no canvas hashes, no font enumeration).\n * - No precise geolocation (only timezone + locale, both of which the\n * browser exposes to every page anyway).\n * - No IP collection — the backend logs the request IP for rate-limit\n * purposes; it isn't stored on the event document.\n * - All fields are typed enums or short strings; we never echo back\n * full User-Agent strings to avoid surfacing fingerprintable detail\n * in dashboards.\n */\n\nexport interface DeviceInfo {\n os?: string;\n osVersion?: string;\n browser?: string;\n browserVersion?: string;\n locale?: string;\n timezone?: string;\n screenWidth?: number;\n screenHeight?: number;\n viewportWidth?: number;\n viewportHeight?: number;\n devicePixelRatio?: number;\n /** Caller-supplied. Set via Crossdeck.start({ appVersion: \"1.2.3\" }). */\n appVersion?: string;\n}\n\n/**\n * Are we in a browser context? Pure function; no side effects.\n *\n * Detects: globalThis.window AND globalThis.document AND globalThis.navigator.\n * All three must be present — Workers / Service Workers have window but\n * no document, and Node 18+ now has navigator but no window.\n */\nexport function isBrowser(): boolean {\n return (\n typeof (globalThis as { window?: unknown }).window !== \"undefined\" &&\n typeof (globalThis as { document?: unknown }).document !== \"undefined\" &&\n typeof (globalThis as { navigator?: unknown }).navigator !== \"undefined\"\n );\n}\n\n/**\n * Collect every safe-to-attach environment field. Returns an empty object\n * outside browsers (Node, Workers) — caller can pass appVersion via the\n * `extra` argument for non-browser runtimes.\n */\nexport function collectDeviceInfo(extra?: { appVersion?: string }): DeviceInfo {\n const info: DeviceInfo = {};\n if (extra?.appVersion) info.appVersion = extra.appVersion;\n\n if (!isBrowser()) return info;\n\n const w = (globalThis as { window: Window }).window;\n const nav = (globalThis as { navigator: Navigator }).navigator;\n const doc = (globalThis as { document: Document }).document;\n\n // ----- Locale + timezone -----\n try {\n if (typeof nav.language === \"string\") info.locale = nav.language;\n } catch {}\n try {\n info.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;\n } catch {}\n\n // ----- Screen + viewport -----\n try {\n if (w.screen) {\n info.screenWidth = w.screen.width;\n info.screenHeight = w.screen.height;\n }\n info.viewportWidth = w.innerWidth;\n info.viewportHeight = w.innerHeight;\n info.devicePixelRatio = w.devicePixelRatio;\n } catch {}\n\n // ----- Browser + OS from User-Agent -----\n try {\n const ua = nav.userAgent ?? \"\";\n const parsed = parseUserAgent(ua);\n Object.assign(info, parsed);\n } catch {}\n\n // ua-ch hints (Chromium browsers expose these properly without UA-string parsing)\n try {\n const uaData = (nav as Navigator & {\n userAgentData?: { platform?: string; brands?: Array<{ brand: string; version: string }> };\n }).userAgentData;\n if (uaData?.platform && !info.os) info.os = uaData.platform;\n if (uaData?.brands && !info.browser) {\n // Pick the most-specific non-\"Not.A;Brand\" entry\n const real = uaData.brands.find(\n (b) => !/Not[ .;A]*Brand/i.test(b.brand) && !/Chromium/i.test(b.brand),\n );\n if (real) {\n info.browser = real.brand;\n info.browserVersion = real.version;\n }\n }\n } catch {}\n\n // Suppress empties (a doc not yet hydrated could leave fields undefined)\n void doc; // referenced only for the isBrowser narrowing\n return info;\n}\n\n/**\n * Tiny User-Agent parser — extracts os, osVersion, browser, browserVersion.\n *\n * Doesn't try to be a full UA database (Bowser, ua-parser-js are large\n * deps). Covers ~95% of real-world traffic by recognising the major\n * browsers and operating systems. Unknown UAs fall through silently.\n *\n * Exported for unit testing.\n */\nexport function parseUserAgent(ua: string): Partial<DeviceInfo> {\n const out: Partial<DeviceInfo> = {};\n\n // ----- Operating system -----\n // Order matters: iPad/iPhone before Mac (iPadOS 13+ UAs claim \"Macintosh\"),\n // Android before Linux (Android UAs contain \"Linux\").\n if (/iPad|iPhone|iPod/.test(ua)) {\n out.os = \"iOS\";\n const m = ua.match(/OS (\\d+[._]\\d+(?:[._]\\d+)?)/);\n if (m?.[1]) out.osVersion = m[1].replace(/_/g, \".\");\n } else if (/Android/.test(ua)) {\n out.os = \"Android\";\n const m = ua.match(/Android (\\d+(?:\\.\\d+)*)/);\n if (m?.[1]) out.osVersion = m[1];\n } else if (/Windows/.test(ua)) {\n out.os = \"Windows\";\n const m = ua.match(/Windows NT (\\d+\\.\\d+)/);\n if (m?.[1]) out.osVersion = m[1];\n } else if (/Mac OS X|Macintosh/.test(ua)) {\n out.os = \"macOS\";\n const m = ua.match(/Mac OS X (\\d+[._]\\d+(?:[._]\\d+)?)/);\n if (m?.[1]) out.osVersion = m[1].replace(/_/g, \".\");\n } else if (/Linux/.test(ua)) {\n out.os = \"Linux\";\n }\n\n // ----- Browser -----\n // Order matters: Edge before Chrome (Edge UA contains \"Chrome\"),\n // Chrome before Safari (Chrome UA contains \"Safari\").\n if (/Edg\\/(\\d+(?:\\.\\d+)*)/.test(ua)) {\n out.browser = \"Edge\";\n out.browserVersion = ua.match(/Edg\\/(\\d+(?:\\.\\d+)*)/)?.[1];\n } else if (/Firefox\\/(\\d+(?:\\.\\d+)*)/.test(ua)) {\n out.browser = \"Firefox\";\n out.browserVersion = ua.match(/Firefox\\/(\\d+(?:\\.\\d+)*)/)?.[1];\n } else if (/OPR\\/(\\d+(?:\\.\\d+)*)/.test(ua)) {\n out.browser = \"Opera\";\n out.browserVersion = ua.match(/OPR\\/(\\d+(?:\\.\\d+)*)/)?.[1];\n } else if (/Chrome\\/(\\d+(?:\\.\\d+)*)/.test(ua)) {\n out.browser = \"Chrome\";\n out.browserVersion = ua.match(/Chrome\\/(\\d+(?:\\.\\d+)*)/)?.[1];\n } else if (/Version\\/(\\d+(?:\\.\\d+)*).*Safari/.test(ua)) {\n out.browser = \"Safari\";\n out.browserVersion = ua.match(/Version\\/(\\d+(?:\\.\\d+)*)/)?.[1];\n }\n\n return out;\n}\n","/**\n * Auto-tracking — sessions and SPA page views, emitted via the same\n * track() pipeline as developer-instrumented events. No-op outside\n * browsers (Node, Workers).\n *\n * Sessions:\n * - One sessionId per \"alive in foreground\" window.\n * - `session.started` fires on install (after start()).\n * - `session.ended` fires on visibilitychange→hidden, beforeunload,\n * and pagehide. Multiple end-triggers are deduplicated so we don't\n * emit two `session.ended` events for one tab close.\n * - `session.duration_ms` attached on end.\n * - If the tab returns to foreground after >30 minutes idle, the next\n * visibilitychange→visible mints a NEW sessionId — matches the\n * 30-min session-window convention used by GA4 / Mixpanel / etc.\n *\n * Page views:\n * - `page.viewed` fires on initial install.\n * - Hooks into `history.pushState` / `history.replaceState` (monkey-\n * patched in a non-destructive way so other libraries that hook\n * them still see their events) and `popstate` for SPA navigation.\n * - Properties: path, url, title, referrer, search, hash.\n *\n * Privacy: this module emits names + properties only. The Crossdeck\n * client adds device info on top via track(). Nothing here collects\n * PII beyond the URL itself, and we don't even log query strings\n * separately from the path (developer can post-process if needed).\n */\n\nimport { randomChars } from \"./identity\";\nimport type { KeyValueStorage } from \"./types\";\n\nexport interface AutoTrackConfig {\n sessions: boolean;\n pageViews: boolean;\n /** Whether to enrich every event with device info. Lives on the client, not here, but documented together. */\n deviceInfo: boolean;\n /**\n * Click autocapture. When true, the SDK installs a global click\n * listener that fires `element.clicked` for every interactive\n * click. Captures the target element's selector, text content,\n * tag, href, data-* attributes, and viewport coordinates — enough\n * to power funnel attribution (\"clicked X then converted\") and\n * heatmap visualisation. Mixpanel / Amplitude default. Privacy\n * guardrails baked in (input/password/sensitive-class skips).\n *\n * Default ON because behavioural attribution is Crossdeck's USP.\n * Set to false to disable autocapture entirely (developer adds\n * track() calls manually).\n */\n clicks: boolean;\n /** Capture Web Vitals (LCP/INP/CLS/FCP/TTFB). Default true (browser only). */\n webVitals: boolean;\n /** Capture uncaught errors + unhandled rejections + 5xx fetch/XHR. Default true (browser only). */\n errors: boolean;\n}\n\nexport const DEFAULT_AUTO_TRACK: AutoTrackConfig = {\n sessions: true,\n pageViews: true,\n deviceInfo: true,\n clicks: true,\n webVitals: true,\n errors: true,\n};\n\n/**\n * Reopen as a NEW session once activity has been idle this long — the\n * 30-min rolling-inactivity window GA4 / Mixpanel use. Applies both\n * in-page (tab hidden → returns >30min later) AND across page loads (the\n * SDK re-installs on every navigation of a multi-page site; if the stored\n * session's last activity is within this window we RESUME it instead of\n * minting a new one). Without the cross-page-load half, every navigation\n * ended one session and started another at the same instant.\n */\nconst SESSION_RESUME_THRESHOLD_MS = 30 * 60 * 1000;\n\n/** Default storage key for the persisted session continuity record. */\nconst SESSION_STORAGE_KEY = \"crossdeck:session\";\n\n/**\n * Throttle for flushing the rolling last-activity time to storage. The\n * in-memory value stays exact; we just avoid a storage write on every\n * single event. pagehide does a final forced flush so the last activity\n * before a navigation is never lost.\n */\nconst ACTIVITY_PERSIST_THROTTLE_MS = 5_000;\n\ntype TrackFn = (name: string, properties?: Record<string, unknown>) => void;\n\n/**\n * The slice of session state we persist across page loads. Kept minimal:\n * enough to RESUME the same visit (id + first-touch acquisition) and to\n * decide whether the resume window is still open (lastActivityAt).\n */\ninterface StoredSession {\n id: string;\n startedAt: number;\n lastActivityAt: number;\n acquisition: SessionAcquisition;\n}\n\ninterface SessionState {\n sessionId: string;\n startedAt: number;\n /** Rolling timestamp of the last tracked event — drives the 30-min window. */\n lastActivityAt: number;\n hiddenAt: number | null;\n endedSent: boolean;\n /**\n * Acquisition context captured once at session start. GA4 calls\n * this \"first-touch attribution within the session.\" We attach\n * these to every event of the session so dashboards can answer\n * \"what was the source of users who triggered paywall_shown\" — a\n * per-event lookup against the captured-once state, not a re-parse\n * of the URL on every track().\n *\n * Empty strings (not undefined) so JSON envelope serialisation\n * stays uniform — backend's extractAcquisition handles \"\" the\n * same as missing.\n */\n acquisition: SessionAcquisition;\n}\n\nexport interface SessionAcquisition {\n utm_source: string;\n utm_medium: string;\n utm_campaign: string;\n utm_content: string;\n utm_term: string;\n referrer: string;\n // ---------- paid-traffic click IDs (v0.9.0+) ----------\n // UTM parameters are a documentation convention — anyone writing\n // ads can forget to add them, and many platforms (Performance Max,\n // Display & Video 360, automated bidding) emit ONLY a click-id.\n // Capturing these alongside UTMs catches the ~40% of paid traffic\n // that UTMs miss. Each is the platform's stable click identifier\n // that flows from ad-click → landing-page URL → conversion event.\n /** Google Ads click identifier. */\n gclid: string;\n /** Facebook / Meta Ads click identifier. */\n fbclid: string;\n /** Microsoft Advertising (Bing) click identifier. */\n msclkid: string;\n /** TikTok Ads click identifier. */\n ttclid: string;\n /** LinkedIn Ads click identifier. */\n li_fat_id: string;\n /** Twitter / X Ads click identifier. */\n twclid: string;\n}\n\nconst EMPTY_ACQUISITION: SessionAcquisition = {\n utm_source: \"\",\n utm_medium: \"\",\n utm_campaign: \"\",\n utm_content: \"\",\n utm_term: \"\",\n referrer: \"\",\n gclid: \"\",\n fbclid: \"\",\n msclkid: \"\",\n ttclid: \"\",\n li_fat_id: \"\",\n twclid: \"\",\n};\n\nexport class AutoTracker {\n private session: SessionState | null = null;\n private cleanups: Array<() => void> = [];\n /**\n * Persistent storage for session continuity across page loads. Uses\n * the SAME adapter as the rest of the SDK (localStorage by default,\n * MemoryStorage when identity persistence is disabled for consent) so\n * session persistence honours the same privacy posture as identity.\n * Null only if no adapter was supplied → session is in-memory only\n * (per-page), the pre-fix behaviour.\n */\n private readonly storage: KeyValueStorage | null;\n private readonly sessionKey: string;\n /** Last time we flushed lastActivityAt to storage (throttle gate). */\n private lastPersistAt = 0;\n /**\n * Event Envelope v1 §3 — per-session monotonic sequence counter.\n * The single owner of the seq counter lives here, alongside all other\n * session state. Incremented atomically at track() time (via nextSeq()),\n * reset to 0 whenever a new session boundary is crossed (startNewSession\n * path + resetSession). Persists across background/foreground within a\n * session — only a real session boundary (not a tab hide/show) resets it.\n */\n private _sessionSeq = 0;\n /**\n * Stable per-page-view identifier. Minted at every `page.viewed`\n * emission and attached to every subsequent event until the next\n * `page.viewed`. Lets dashboards correlate \"user clicked X\" to\n * \"user viewed page Y\" without timestamp arithmetic — the canonical\n * Mixpanel `$current_url` / Segment `pageId` pattern.\n *\n * Null until the first `page.viewed` fires (which happens at SDK\n * install if `autoTrack.pageViews !== false`).\n */\n private pageviewId: string | null = null;\n\n constructor(\n private readonly cfg: AutoTrackConfig,\n private readonly track: TrackFn,\n opts?: { storage?: KeyValueStorage; storageKey?: string },\n ) {\n this.storage = opts?.storage ?? null;\n this.sessionKey = opts?.storageKey ?? SESSION_STORAGE_KEY;\n }\n\n install(): void {\n if (!isBrowserSafe()) return;\n if (this.cfg.sessions) this.installSessionTracking();\n if (this.cfg.pageViews) this.installPageViewTracking();\n if (this.cfg.clicks) this.installClickTracking();\n }\n\n uninstall(): void {\n while (this.cleanups.length) {\n const fn = this.cleanups.pop();\n try { fn?.(); } catch { /* ignore */ }\n }\n if (this.session && !this.session.endedSent) {\n this.emitSessionEnd();\n }\n // Explicit teardown is a real end (unlike a navigation's pagehide) —\n // clear the persisted session so the next start() begins fresh\n // rather than resuming a session the host deliberately stopped.\n this.clearStoredSession();\n this.session = null;\n }\n\n /** Exposed for tests + consumers that want to reset the session manually. */\n resetSession(): void {\n if (this.session && !this.session.endedSent) this.emitSessionEnd();\n // Null pageviewId on session boundary so any event fired between\n // session reset and the next page.viewed doesn't ship the previous\n // session's pageview attribution. Audit P1 #16: pre-fix the\n // pageviewId survived 30-min idle resets and silently corrupted\n // post-resume event → pageview correlation. The next page.viewed\n // mints a fresh id (see installPageViewTracking's `fire()`).\n this.pageviewId = null;\n this.session = this.startNewSession();\n this.persistSession();\n this.emitSessionStart();\n }\n\n /**\n * Keep the rolling session window alive. Called by the host on EVERY\n * tracked event (auto or custom) so any activity — not just the\n * pageviews/clicks AutoTracker emits itself — pushes the 30-min idle\n * boundary forward. In-memory time is updated exactly; the storage\n * flush is throttled (pagehide forces a final flush).\n */\n markActivity(): void {\n if (!this.session) return;\n const now = Date.now();\n // If this activity lands AFTER the resume window has lapsed, it belongs to\n // a NEW visit — roll the session BEFORE the event records, so a single\n // stored session can never span a >30-min gap (the contract's\n // session_gap_within_window invariant). This covers the case the\n // visibility/page-load resume checks miss entirely: a tab left open and\n // idle, then interacted with again, with no visibility transition. We do\n // NOT back-date a session.ended onto the prior session — that would itself\n // open the gap; its end is inferred from its last event (same as the\n // page-load resume path). session.started is emitted here, before the\n // triggering event is stamped (timestamp is set later in track()), so it\n // is the earliest event of the new session.\n if (now - this.session.lastActivityAt >= SESSION_RESUME_THRESHOLD_MS) {\n this.pageviewId = null;\n this.session = this.startNewSession();\n this.persistSession();\n this.emitSessionStart();\n return;\n }\n this.session.lastActivityAt = now;\n if (now - this.lastPersistAt >= ACTIVITY_PERSIST_THROTTLE_MS) {\n this.persistSession();\n }\n }\n\n /** Exposed for inspection/tests — returns the current sessionId (or null if not in a session). */\n get currentSessionId(): string | null {\n return this.session?.sessionId ?? null;\n }\n\n /** Stable per-page-view ID. Null before the first page.viewed has fired. */\n get currentPageviewId(): string | null {\n return this.pageviewId;\n }\n\n /**\n * Per-session acquisition context — utm_* + referrer, captured once\n * at session start. Returns empty strings when there's no session\n * (Node, before init, after uninstall) so callers can spread without\n * conditional logic. Bank-grade rule: capture once, attach to every\n * event of the session, don't re-read on every track() (the URL\n * changes via SPA pushState; the source-of-record is the URL we\n * landed on).\n */\n get currentAcquisition(): SessionAcquisition {\n return this.session?.acquisition ?? EMPTY_ACQUISITION;\n }\n\n /**\n * Event Envelope v1 §3 — return the next seq value for the current session\n * and advance the counter. Called SYNCHRONOUSLY at track() time, before any\n * async dispatch, so the seq assignment order is deterministic (call order,\n * not scheduler luck). The counter is reset to 0 at every session boundary;\n * it is NOT reset by tab hide/show (spec §3 clause 1: backgrounding does not\n * reset seq). When no session is active (Node, before init, after uninstall),\n * returns 0 and leaves the counter unchanged — fallback, not an error.\n */\n nextSeq(): number {\n // Increment FIRST, then return. Pre-increment means the first event of a\n // session gets seq=0 (counter starts at 0 after reset, returns 0 pre-bump,\n // but we return-then-increment to match Swift's pattern: 0-based first).\n // We return the current value and then increment so seq 0 = first event.\n const seq = this._sessionSeq;\n this._sessionSeq += 1;\n return seq;\n }\n\n // ---------- sessions ----------\n private installSessionTracking(): void {\n const now = Date.now();\n const stored = this.readStoredSession();\n if (stored && now - stored.lastActivityAt < SESSION_RESUME_THRESHOLD_MS) {\n // RESUME the in-flight visit across a full page load. A multi-page\n // site re-installs the SDK on every navigation; minting a new\n // session here (the old behaviour) split one visit into one session\n // per page, each ended at the same instant the next began — the\n // \"session ends and the next begins at 05:18:11\" bug. Reuse the id\n // + first-touch acquisition; do NOT emit a second session.started.\n this.session = {\n sessionId: stored.id,\n startedAt: stored.startedAt,\n lastActivityAt: now,\n hiddenAt: null,\n endedSent: false,\n acquisition: stored.acquisition,\n };\n this.persistSession();\n } else {\n // Genuinely new visit (no stored session, or the 30-min window\n // lapsed). A stale stored session is simply superseded — we do NOT\n // emit a back-dated session.ended for it, which would land at the\n // new session's timestamp and corrupt the old session's duration;\n // the dashboard infers the prior session's end from its last event.\n this.session = this.startNewSession();\n this.persistSession();\n this.emitSessionStart();\n }\n\n const onVisChange = (): void => {\n if (!this.session) return;\n const doc = (globalThis as { document: Document }).document;\n if (doc.visibilityState === \"hidden\") {\n // Quick tab switches and Cmd-Tabs land here, but the page is\n // still alive. Record the time and flush, but do NOT emit\n // session.ended — returning seconds later must continue the same\n // session (the 30-min window intent). The flush keeps the\n // last-activity time accurate for a next-visit resume if the tab\n // is closed rather than returned to.\n this.session.hiddenAt = Date.now();\n this.persistSession();\n } else if (doc.visibilityState === \"visible\") {\n // Decide on real inactivity, not just time-hidden: lastActivityAt\n // is the last tracked event, so this is the true idle gap.\n const idleFor = Date.now() - this.session.lastActivityAt;\n if (idleFor >= SESSION_RESUME_THRESHOLD_MS) {\n // Long idle → the previous session genuinely ended. Open a fresh\n // one. Do NOT back-date a session.ended onto the prior session: it\n // would land >30 min after that session's last real event and open\n // an intra-session gap (session_gap_within_window). Its end is\n // inferred from its last event — consistent with the page-load\n // resume path. Null pageviewId on the boundary (audit P1 #16) so any\n // event before the next page.viewed doesn't ship stale attribution.\n this.pageviewId = null;\n this.session = this.startNewSession();\n this.persistSession();\n this.emitSessionStart();\n } else {\n // Quick return — same session continues.\n this.session.hiddenAt = null;\n }\n }\n };\n\n // A page unload is NOT a session end — on a multi-page site it's a\n // navigation, and the next page resumes this same session. Flush the\n // final activity time so the next load's resume-window check is\n // accurate. The session ends only on real 30-min inactivity or an\n // explicit uninstall(). (This is what stopped the per-navigation\n // session.ended / session.started churn.)\n const onPageHide = (): void => this.persistSession();\n\n const w = (globalThis as { window: Window }).window;\n const doc = (globalThis as { document: Document }).document;\n doc.addEventListener(\"visibilitychange\", onVisChange);\n w.addEventListener(\"pagehide\", onPageHide);\n // beforeunload is unreliable on mobile; pagehide is the modern equivalent.\n // We listen to both for desktop-vs-mobile coverage.\n w.addEventListener(\"beforeunload\", onPageHide);\n\n this.cleanups.push(() => {\n doc.removeEventListener(\"visibilitychange\", onVisChange);\n w.removeEventListener(\"pagehide\", onPageHide);\n w.removeEventListener(\"beforeunload\", onPageHide);\n });\n }\n\n private startNewSession(): SessionState {\n const now = Date.now();\n // Envelope v1 §3: reset the seq counter at every session boundary. The\n // counter belongs to the session — a new session always starts at seq 0.\n this._sessionSeq = 0;\n return {\n sessionId: mintSessionId(),\n startedAt: now,\n lastActivityAt: now,\n hiddenAt: null,\n endedSent: false,\n acquisition: captureAcquisition(),\n };\n }\n\n /**\n * Read the persisted session continuity record. Returns null on no\n * storage, no record, malformed JSON, or a record missing its required\n * fields — every failure degrades to \"no session to resume\", which is\n * the safe (start-fresh) path.\n */\n private readStoredSession(): StoredSession | null {\n if (!this.storage) return null;\n try {\n const raw = this.storage.getItem(this.sessionKey);\n if (!raw) return null;\n const p = JSON.parse(raw) as Partial<StoredSession>;\n if (\n !p ||\n typeof p.id !== \"string\" ||\n typeof p.startedAt !== \"number\" ||\n typeof p.lastActivityAt !== \"number\"\n ) {\n return null;\n }\n return {\n id: p.id,\n startedAt: p.startedAt,\n lastActivityAt: p.lastActivityAt,\n acquisition:\n p.acquisition && typeof p.acquisition === \"object\"\n ? (p.acquisition as SessionAcquisition)\n : EMPTY_ACQUISITION,\n };\n } catch {\n return null;\n }\n }\n\n /** Flush the current session to storage. No-op without storage/session. */\n private persistSession(): void {\n if (!this.storage || !this.session) return;\n this.lastPersistAt = Date.now();\n try {\n const rec: StoredSession = {\n id: this.session.sessionId,\n startedAt: this.session.startedAt,\n lastActivityAt: this.session.lastActivityAt,\n acquisition: this.session.acquisition,\n };\n this.storage.setItem(this.sessionKey, JSON.stringify(rec));\n } catch {\n // Quota / blocked storage — session degrades to in-memory only.\n }\n }\n\n private clearStoredSession(): void {\n if (!this.storage) return;\n try {\n this.storage.removeItem(this.sessionKey);\n } catch {\n // ignore\n }\n }\n\n private emitSessionStart(): void {\n if (!this.session) return;\n this.track(\"session.started\", { sessionId: this.session.sessionId });\n }\n\n private emitSessionEnd(): void {\n if (!this.session || this.session.endedSent) return;\n const duration = Date.now() - this.session.startedAt;\n this.track(\"session.ended\", {\n sessionId: this.session.sessionId,\n durationMs: duration,\n });\n this.session.endedSent = true;\n }\n\n // ---------- page views ----------\n private installPageViewTracking(): void {\n const w = (globalThis as { window: Window }).window;\n const doc = (globalThis as { document: Document }).document;\n\n // PwC M-5: dedup. SPA frameworks (Next.js, React Router, Vue\n // Router) routinely fire pushState() back-to-back during a single\n // navigation — animation enter, then the destination's settle.\n // Without a guard, we'd send 2-3 page.viewed events per click,\n // inflating pageview / session counts. Dedup window: 250ms,\n // keyed by URL. Identical URL within window = drop.\n //\n // EXCEPTION: popstate (user back/forward) is always a real\n // navigation, even if it lands on a URL we've recently seen.\n // Force-fire on popstate so back-button traffic is never dropped.\n let lastFiredAt = 0;\n let lastFiredUrl = \"\";\n const DEDUP_WINDOW_MS = 250;\n\n const fire = (force = false): void => {\n const loc = w.location;\n const url = loc.href;\n const now = Date.now();\n if (!force && url === lastFiredUrl && now - lastFiredAt < DEDUP_WINDOW_MS) return;\n lastFiredAt = now;\n lastFiredUrl = url;\n\n // Mint a fresh pageviewId BEFORE emitting the event so this\n // page.viewed itself carries it, and every subsequent event up\n // to the next page.viewed inherits it via the auto-attached\n // enrichment in crossdeck.ts:track().\n this.pageviewId = `pv_${Date.now().toString(36)}${randomChars(10)}`;\n\n this.track(\"page.viewed\", {\n pageviewId: this.pageviewId,\n path: loc.pathname,\n url,\n search: loc.search || undefined,\n hash: loc.hash || undefined,\n title: doc.title,\n // referrer only on the first hit of the session — afterward it's\n // always our previous URL, which isn't useful.\n referrer: doc.referrer || undefined,\n });\n };\n\n // Initial page view\n fire();\n\n // SPA navigation: monkey-patch pushState / replaceState. Capture the\n // BARE function references (not bound) so uninstall restores exactly\n // what was there. Bind chains accumulate without limit if every\n // install/uninstall cycle wraps with .bind() — over many cycles\n // pushState becomes [bound bound bound … pushState] and tests that\n // assert \"pushState restored to its previous value\" break.\n //\n // We use `function (this: History, ...args)` so JS's normal method-call\n // semantics bind `this` to history when our wrapper is invoked as\n // history.pushState(...). Then we forward via .apply(this, args) — no\n // pre-binding needed, no chain growth.\n type HistoryFn = (data: unknown, unused: string, url?: string | null) => void;\n const origPush = w.history.pushState as HistoryFn;\n const origReplace = w.history.replaceState as HistoryFn;\n\n function patchedPush(this: History, data: unknown, unused: string, url?: string | null): void {\n origPush.apply(this, [data, unused, url]);\n queueMicrotask(fire);\n }\n function patchedReplace(this: History, data: unknown, unused: string, url?: string | null): void {\n origReplace.apply(this, [data, unused, url]);\n queueMicrotask(fire);\n }\n\n (w.history.pushState as HistoryFn) = patchedPush;\n (w.history.replaceState as HistoryFn) = patchedReplace;\n\n // popstate fires on user back/forward — bypass the dedup window\n // because user navigation is always a real event, not a framework\n // double-fire artefact.\n const onPopState = (): void => fire(true);\n w.addEventListener(\"popstate\", onPopState);\n\n this.cleanups.push(() => {\n // Only restore if WE'RE still the active wrapper. If another tracker\n // installed on top of ours, blindly setting pushState back would\n // unwind their patch too. Conservative: only restore our slot.\n if (w.history.pushState === patchedPush) {\n (w.history.pushState as HistoryFn) = origPush;\n }\n if (w.history.replaceState === patchedReplace) {\n (w.history.replaceState as HistoryFn) = origReplace;\n }\n w.removeEventListener(\"popstate\", onPopState);\n });\n }\n\n // ---------- click autocapture ----------\n /**\n * Global click tracking — Mixpanel / Amplitude style autocapture.\n * Fires `element.clicked` for every interactive click with the\n * target element's selector path, text content, tag, href, data-*\n * attributes, and viewport coordinates. Powers the funnel /\n * attribution USP: \"users who clicked X then converted within\n * 7 days.\" Default ON because behavioural attribution is the\n * core product promise.\n *\n * Privacy guardrails:\n * - Skip clicks ON inputs / textareas / selects (form interaction\n * isn't button telemetry; the dev should track form submits\n * deliberately via track('form_submitted'))\n * - Skip clicks INSIDE [type=\"password\"] and password-class\n * elements\n * - Skip clicks inside elements opted out via class=\"cd-noTrack\"\n * or data-cd-noTrack attribute (Mixpanel's exact opt-out\n * idiom — most devs already know it)\n * - Capture text content but cap at 64 chars and trim — never\n * more than what you'd see on a button label\n *\n * Volume guardrails:\n * - Coalesce double-clicks within 100ms (React's synthetic click\n * pattern + browser's native dblclick can fire twice)\n * - Listen on document at capture phase so we see the click\n * before any framework's own handlers stop propagation\n */\n private installClickTracking(): void {\n const w = (globalThis as { window: Window }).window;\n const doc = (globalThis as { document: Document }).document;\n\n let lastFiredAt = 0;\n let lastFiredTarget: EventTarget | null = null;\n const COALESCE_MS = 100;\n const TEXT_CAP = 64;\n\n const onClick = (ev: MouseEvent): void => {\n const target = ev.target as Element | null;\n if (!target || !(target instanceof Element)) return;\n\n // De-dupe rapid double-fires on the same target (React synthetic\n // click + browser native click can land in the same tick).\n const now = Date.now();\n if (target === lastFiredTarget && now - lastFiredAt < COALESCE_MS) return;\n lastFiredAt = now;\n lastFiredTarget = target;\n\n // Walk up to the nearest \"actionable\" ancestor. A click inside\n // <button><span>Sign up</span></button> should fire as a click\n // on the BUTTON, not the inner span. We climb up to a button /\n // a / [role=\"button\"] / [data-cd-event] / [onclick] — whichever\n // is closer.\n const actionable = closestActionable(target);\n const clicked: Element = actionable || target;\n\n // Privacy: skip form-input clicks, password fields, opted-out\n // subtrees. PII risk is too high to capture text from these.\n if (isFormInput(clicked)) return;\n if (isInOptedOut(clicked)) return;\n if (isInsidePasswordField(clicked)) return;\n\n // Build the event properties.\n const tag = clicked.tagName.toLowerCase();\n const text = trimText(extractText(clicked), TEXT_CAP);\n const href = (clicked as HTMLAnchorElement).href || undefined;\n const linkTarget = (clicked as HTMLAnchorElement).target || undefined;\n const elementId = clicked.id || undefined;\n const role = clicked.getAttribute(\"role\") || undefined;\n const ariaLabel = clicked.getAttribute(\"aria-label\") || undefined;\n const selector = buildSelector(clicked);\n const dataAttrs = collectDataAttrs(clicked);\n const isLink = tag === \"a\" && !!href;\n\n // Optional explicit override: if the dev tagged the element\n // with data-cd-event=\"custom_name\", we use THAT as the event\n // name and stash the auto-properties as `meta` rather than\n // firing as element.clicked. Devs who want named events get\n // them; everyone else gets the auto.\n const explicitName = clicked.getAttribute(\"data-cd-event\");\n\n const props: Record<string, unknown> = {\n selector,\n tag,\n text,\n elementId,\n role,\n ariaLabel,\n href,\n isLink,\n linkTarget,\n viewportX: ev.clientX,\n viewportY: ev.clientY,\n pageX: ev.pageX,\n pageY: ev.pageY,\n ...dataAttrs,\n };\n // Drop empties so the event property bag isn't full of nulls.\n for (const k of Object.keys(props)) {\n if (props[k] === undefined || props[k] === null || props[k] === \"\") delete props[k];\n }\n\n this.track(explicitName || \"element.clicked\", props);\n };\n\n doc.addEventListener(\"click\", onClick, { capture: true, passive: true });\n this.cleanups.push(() => {\n doc.removeEventListener(\"click\", onClick, { capture: true } as AddEventListenerOptions);\n });\n }\n}\n\n// ---------- click-tracking helpers ----------\n\nfunction closestActionable(el: Element): Element | null {\n // Climb up to the nearest interactive ancestor. The order matters —\n // [data-cd-event] wins because it's an explicit dev-supplied tag.\n return (\n el.closest(\"[data-cd-event]\") ||\n el.closest(\"[data-cd-noTrack]\") ||\n el.closest(\"button, a, [role='button'], [role='link'], input[type='button'], input[type='submit']\") ||\n null\n );\n}\n\nfunction isFormInput(el: Element): boolean {\n if (!(el instanceof HTMLElement)) return false;\n const tag = el.tagName.toLowerCase();\n if (tag === \"textarea\" || tag === \"select\") return true;\n if (tag === \"input\") {\n const type = ((el as HTMLInputElement).type || \"\").toLowerCase();\n // Buttons are inputs but they ARE click targets — track them.\n return type !== \"button\" && type !== \"submit\" && type !== \"image\" && type !== \"reset\";\n }\n return false;\n}\n\nfunction isInOptedOut(el: Element): boolean {\n // Mixpanel's idiom: class=\"cd-noTrack\" or [data-cd-noTrack] on any\n // ancestor opts the entire subtree out of autocapture.\n if (el.closest('[data-cd-noTrack], [data-cd-no-track], .cd-noTrack, .cd-no-track')) return true;\n return false;\n}\n\nfunction isInsidePasswordField(el: Element): boolean {\n // Defensive: never capture clicks on / near password fields.\n if (el.closest('input[type=\"password\"]')) return true;\n return false;\n}\n\n// Tags whose text IS a control's own caption when they sit directly\n// inside it — buttons/links routinely wrap their label in one of these.\nconst INLINE_LABEL_TAGS = new Set([\n \"span\", \"b\", \"strong\", \"em\", \"i\", \"small\", \"mark\", \"u\", \"label\",\n \"abbr\", \"time\", \"bdi\", \"cite\", \"code\", \"kbd\", \"q\", \"sub\", \"sup\",\n]);\n\n// Subtrees whose text is decorative (icon internals / styling) and must\n// never leak into a label.\nconst NON_LABEL_SUBTREES = new Set([\"svg\", \"style\", \"script\", \"noscript\"]);\n\n// A clicked control that CONTAINS one of these is a wrapper — around\n// other controls (a card whose whole body is one big <a>, holding three\n// sign-in buttons) or around a content block (a hero <a> wrapping a\n// heading + paragraph). Its full textContent is a mash, not a label.\n// Headings count because a heading inside a clickable is the signature of\n// \"this is a content block, not a button\".\nconst CONTAINER_MARKERS =\n \"a[href], button, input, select, textarea, [role='button'], [role='link'], h1, h2, h3, h4, h5, h6\";\n\nconst HEADING_SELECTOR = \"h1, h2, h3, h4, h5, h6\";\n\n/** Collapse whitespace runs and trim. \" Sign\\n up \" → \"Sign up\". */\nfunction collapseWs(s: string): string {\n return s.replace(/\\s+/g, \" \").trim();\n}\n\n/**\n * Text of an element with WORD BOUNDARIES preserved across nested\n * elements. `el.textContent` concatenates text nodes with nothing between\n * them, so `<h1>…só link</h1><p>Portfolio…` fuses into \"só linkPortfolio\"\n * and `<button>Log in</button><button>Continue…` into \"Log inContinue…\".\n * We insert a space at every element boundary, skip decorative subtrees\n * (icons), then collapse — so the label reads as written, never fused.\n */\nfunction boundaryText(el: Element): string {\n let out = \"\";\n const walk = (node: Node): void => {\n const kids = node.childNodes;\n for (let i = 0; i < kids.length; i++) {\n const child = kids[i];\n if (!child) continue;\n if (child.nodeType === 3 /* TEXT_NODE */) {\n out += child.textContent || \"\";\n } else if (child.nodeType === 1 /* ELEMENT_NODE */) {\n if (NON_LABEL_SUBTREES.has((child as Element).tagName.toLowerCase())) continue;\n out += \" \";\n walk(child);\n out += \" \";\n }\n }\n };\n walk(el);\n return collapseWs(out);\n}\n\n/**\n * The element's OWN label — immediate text nodes plus the text of inline\n * label wrappers (<span> etc.) directly inside it. Deliberately does NOT\n * descend into block or interactive children, so a wrapper's nested\n * controls/headings never leak in. \"\" when the element carries no label\n * of its own.\n */\nfunction directLabel(el: Element): string {\n let out = \"\";\n const kids = el.childNodes;\n for (let i = 0; i < kids.length; i++) {\n const child = kids[i];\n if (!child) continue;\n if (child.nodeType === 3 /* TEXT_NODE */) {\n out += \" \" + (child.textContent || \"\");\n } else if (\n child.nodeType === 1 /* ELEMENT_NODE */ &&\n INLINE_LABEL_TAGS.has((child as Element).tagName.toLowerCase())\n ) {\n out += \" \" + boundaryText(child as Element);\n }\n }\n return collapseWs(out);\n}\n\n/**\n * Resolve the visible label for a clicked control WITHOUT mashing nested\n * controls or content blocks together.\n *\n * - A \"simple\" control — no nested interactive element, no nested heading\n * — yields its boundary-spaced text. Covers the everyday case:\n * `<button>Sign up</button>`, `<a>Pricing</a>`,\n * `<button><svg/><span>Save</span></button>` → \"Save\".\n * - A \"container\" — a control wrapping OTHER controls or a content block —\n * would otherwise collapse to \"Log inContinue with GoogleContinue with\n * Apple…\" or \"Tudo que você é,em um só link.Portfolio…\". For these we\n * use, in order: the element's own direct label → the first heading\n * inside it (the human-recognisable name of the block) → \"\". Returning\n * \"\" lets extractText fall through to title / img alt / svg title, and\n * finally to the selector — honest, not garbage.\n */\nfunction visibleLabel(el: Element): string {\n const isContainer = el.querySelector(CONTAINER_MARKERS) !== null;\n if (!isContainer) return boundaryText(el);\n\n const direct = directLabel(el);\n if (direct) return direct;\n\n const heading = el.querySelector(HEADING_SELECTOR);\n if (heading) {\n const h = boundaryText(heading);\n if (h) return h;\n }\n return \"\";\n}\n\n/**\n * Compute the best human-readable label for a clicked element.\n *\n * Precedence (highest → lowest):\n * 1. data-cd-track / data-track / data-testid — explicit dev label\n * 2. aria-label — accessible name (most icon-only buttons set this)\n * 3. aria-labelledby — references another element by ID\n * 4. <input value=\"…\"> — for submit/button inputs\n * 5. visible textContent — the most common case\n * 6. title attribute — tooltip fallback\n * 7. <img alt=\"…\"> inside the element — for image-only buttons\n * 8. <svg><title>…</title></svg> inside the element — for SVG icons\n * that include an accessible name\n *\n * Returns \"\" only when the element is truly anonymous (no label, no\n * text, no inner image/icon with a name). The journey UI falls back\n * to the selector when this returns empty, so any source we can lift\n * a name from beats the CSS-homework display.\n *\n * Whitespace is always collapsed — \" Sign\\n up \" → \"Sign up\".\n */\nfunction extractText(el: Element): string {\n const clean = (s: string): string => s.replace(/\\s+/g, \" \").trim();\n\n // 1. Explicit dev-supplied labels. data-cd-track is the Crossdeck-\n // canonical attribute; data-track and data-testid are the\n // common community standards we accept as synonyms.\n const explicit =\n el.getAttribute(\"data-cd-track\") ||\n el.getAttribute(\"data-track\") ||\n el.getAttribute(\"data-testid\");\n if (explicit) {\n const t = clean(explicit);\n if (t) return t;\n }\n\n // 2. Aria-label — the ARIA accessible-name primary source.\n const aria = el.getAttribute(\"aria-label\");\n if (aria) {\n const t = clean(aria);\n if (t) return t;\n }\n\n // 3. Aria-labelledby — resolve referenced element's text. The ID\n // list can have multiple tokens; concatenate referents (ARIA spec).\n const labelledBy = el.getAttribute(\"aria-labelledby\");\n if (labelledBy && typeof el.ownerDocument?.getElementById === \"function\") {\n const parts: string[] = [];\n for (const id of labelledBy.split(/\\s+/)) {\n const ref = el.ownerDocument.getElementById(id);\n const t = ref?.textContent ? clean(ref.textContent) : \"\";\n if (t) parts.push(t);\n }\n if (parts.length > 0) return parts.join(\" \");\n }\n\n // 4. Input value (submit/button inputs render their label here).\n if (el instanceof HTMLInputElement && el.value) {\n const t = clean(el.value);\n if (t) return t;\n }\n\n // 5. Visible text — the element's OWN label, never a mash of nested\n // controls or content blocks. extractText used to return\n // clean(el.textContent) here, which on a control that WRAPS other\n // things collapses the whole subtree into one string:\n // \"Log inContinue with GoogleContinue with Apple…\" (an auth card\n // around three buttons) or \"Tudo que você é,em um só link.Portfolio\n // …\" (a hero <a> around an <h1>+<p>+<ul>). visibleLabel() resolves a\n // faithful single-control label instead. See its doc comment.\n const text = visibleLabel(el);\n if (text) return text;\n\n // 6. Title attribute (tooltip-style accessible name).\n const title = el.getAttribute(\"title\");\n if (title) {\n const t = clean(title);\n if (t) return t;\n }\n\n // 7. <img alt=\"…\"> within — common for image-only buttons. Prefer\n // a non-empty alt; treat alt=\"\" (decorative) as no signal.\n const img = el.querySelector(\"img[alt]\");\n if (img) {\n const alt = img.getAttribute(\"alt\") ?? \"\";\n const t = clean(alt);\n if (t) return t;\n }\n\n // 8. SVG <title> child — the conventional way to ship an accessible\n // name on inline icons.\n const svgTitle = el.querySelector(\"svg title\");\n if (svgTitle?.textContent) {\n const t = clean(svgTitle.textContent);\n if (t) return t;\n }\n\n return \"\";\n}\n\nfunction trimText(s: string, cap: number): string {\n if (s.length <= cap) return s;\n return s.slice(0, cap - 1) + \"…\";\n}\n\n/**\n * Build a stable CSS selector path for the element. Used for\n * server-side dedup (\"clicked the same button on the same page\")\n * and for replay / heatmap reconstruction. Walks up to the body or\n * an element with an id, whichever comes first. Caps the depth at\n * 5 to keep selectors short and human-readable.\n */\nfunction buildSelector(el: Element): string {\n const parts: string[] = [];\n let cur: Element | null = el;\n let depth = 0;\n while (cur && cur.nodeName.toLowerCase() !== \"body\" && depth < 5) {\n let part = cur.nodeName.toLowerCase();\n if (cur.id) {\n parts.unshift(`${part}#${cur.id}`);\n break; // ID is unique-enough — stop walking up\n }\n if (cur.classList.length > 0) {\n const cls = Array.from(cur.classList)\n .filter((c) => !c.startsWith(\"cd-\")) // skip our own marker classes\n .slice(0, 2)\n .join(\".\");\n if (cls) part += `.${cls}`;\n }\n parts.unshift(part);\n cur = cur.parentElement;\n depth++;\n }\n return parts.join(\" > \");\n}\n\nfunction collectDataAttrs(el: Element): Record<string, string> {\n // Pull every data-* attribute off the element. Devs use these\n // for explicit tagging — `data-cd-prop-plan=\"pro\"` becomes a\n // property on the event so you can filter conversions by plan.\n const out: Record<string, string> = {};\n if (!(el instanceof HTMLElement)) return out;\n for (const name of el.getAttributeNames()) {\n if (!name.startsWith(\"data-\")) continue;\n if (name === \"data-cd-noTrack\" || name === \"data-cd-no-track\") continue;\n if (name === \"data-cd-event\") continue; // used as event name, not prop\n const value = el.getAttribute(name) || \"\";\n // Normalise data-cd-prop-plan → \"plan\" key on properties\n const key = name.replace(/^data-cd-prop-/, \"\").replace(/^data-/, \"\");\n out[key] = value;\n }\n return out;\n}\n\n/**\n * Browser detection identical to device-info.ts isBrowser. Inlined here\n * so this module has zero internal imports — easier to tree-shake\n * out of Node-only consumers, and the function body is trivial.\n */\nfunction isBrowserSafe(): boolean {\n return (\n typeof (globalThis as { window?: unknown }).window !== \"undefined\" &&\n typeof (globalThis as { document?: unknown }).document !== \"undefined\"\n );\n}\n\nfunction mintSessionId(): string {\n // Inline the same shape used elsewhere — `<prefix>_<base32-ts><10-char-rand>`.\n const ts = Date.now().toString(36);\n return `sess_${ts}${randomChars(10)}`;\n}\n\n/**\n * Read first-touch acquisition signals off the current page. Captures:\n * - utm_source, utm_medium, utm_campaign, utm_content, utm_term\n * (the standard Google Analytics campaign params)\n * - referrer (full URL — backend extracts hostname for grouping)\n *\n * Returns empty strings outside a browser, before navigation, or when\n * the page has none of these signals. Never throws — a malformed URL\n * or an iframe with no document.referrer falls through to empty.\n *\n * Pure function. Exported for unit testing acquisition extraction.\n */\nexport function captureAcquisition(): SessionAcquisition {\n if (!isBrowserSafe()) return { ...EMPTY_ACQUISITION };\n\n const result: SessionAcquisition = { ...EMPTY_ACQUISITION };\n\n try {\n const w = (globalThis as { window: Window }).window;\n const params = new URLSearchParams(w.location.search ?? \"\");\n result.utm_source = params.get(\"utm_source\") ?? \"\";\n result.utm_medium = params.get(\"utm_medium\") ?? \"\";\n result.utm_campaign = params.get(\"utm_campaign\") ?? \"\";\n result.utm_content = params.get(\"utm_content\") ?? \"\";\n result.utm_term = params.get(\"utm_term\") ?? \"\";\n // Paid-traffic click IDs — captured alongside UTMs because many\n // ad platforms (Performance Max, automated bidding) ship ONLY\n // a click-id, no utm_*.\n result.gclid = params.get(\"gclid\") ?? \"\";\n result.fbclid = params.get(\"fbclid\") ?? \"\";\n result.msclkid = params.get(\"msclkid\") ?? \"\";\n result.ttclid = params.get(\"ttclid\") ?? \"\";\n result.li_fat_id = params.get(\"li_fat_id\") ?? \"\";\n result.twclid = params.get(\"twclid\") ?? \"\";\n } catch {\n // window.location can throw in sandboxed iframes / data: URLs\n }\n\n try {\n const doc = (globalThis as { document: Document }).document;\n if (typeof doc.referrer === \"string\") result.referrer = doc.referrer;\n } catch {\n // document.referrer is well-supported but defensive in case\n }\n\n return result;\n}\n","/**\n * Debug signal vocabulary per NorthStar §16.\n *\n * The SDK speaks a small fixed vocabulary of signals so the dashboard's\n * onboarding checklist can show \"we saw your first event\" without having\n * to parse free-form console output. When debug mode is enabled the\n * signals are also logged to the console so a developer doing\n * 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 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 loud,\n // regardless of debug mode — see the console.error in crossdeck.ts.\n | \"sdk.flush_permanent_failure\"\n // Emitted when the server PARKS the SDK (HTTP 426 / sdk_version_unsupported):\n // the wire dialect is too old. Distinct from flush_permanent_failure —\n // events are HELD on-device (not dropped) and deliver on upgrade. The\n // second signal channel (paired with the queue's one console line); the\n // dashboard reads it to render the amber \"update to resume\" advisory.\n | \"sdk.parked\"\n | \"sdk.consent_changed\"\n | \"sdk.consent_denied\"\n | \"sdk.consent_dnt_applied\"\n | \"sdk.pii_scrubbed\";\n\nexport interface DebugContext {\n /** Free-form details surfaced under the signal — appId, key prefix, etc. */\n [key: string]: unknown;\n}\n\n/**\n * Names that almost always indicate PII or secret data. Used by track()\n * to warn the developer when a property key looks dangerous. Per\n * NorthStar §15 these are reject/warn-on-sight values; we warn rather\n * than reject because the developer might genuinely want a property\n * 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]\"`.\n * - Cap total serialised size at `maxBatchPropertyBytes` (default 8192).\n * Past the cap, properties are dropped (newest first) and a\n * `__truncated` marker is added.\n * - Reserved keys (`sessionId`, `referrer`, `utm_*`, anything with\n * `__` prefix) are NOT validated specially — they're treated like\n * any other property. The SDK's own enrichment goes through this\n * same path so it gets the same safety guarantees.\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 /** Sanitised properties safe to JSON.stringify. */\n properties: EventProperties;\n /** Per-issue warnings — surface in debug mode or diagnostics. */\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-stack circular detection: add the object/array to `seen`\n // before recursing into its children, REMOVE it after. A re-encounter\n // while a value is still in the set means it's an ancestor of the\n // current node (a real cycle). Sibling sharing — two properties\n // pointing at the same sub-object (a legitimate DAG, e.g. an enriched\n // event with `{ user: shared, owner: shared }`) — is NOT a cycle and\n // must NOT be flagged. Pre-fix the WeakSet was add-on-visit /\n // never-delete, so the second sibling visit always tripped the\n // `[circular]` branch and silently dropped real data with a\n // misleading warning. Audit P1 #18.\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 // Strip NaN / ±Infinity — they JSON-stringify as null which is\n // surprising. Convert to null explicitly with a warning.\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 // Objects from here on.\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; only true cycles\n // (parent re-encountered via a descendant) should flag.\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 // Delete on exit — see the array branch above for rationale.\n seen.delete(obj);\n return { keep: true, value: out };\n }\n\n // Unknown exotic type — try string coercion.\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 // We use byteLength via UTF-8 since the wire is JSON-over-HTTPS.\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 // Fallback for runtimes without TextEncoder (very old): assume\n // worst-case 4 bytes/char to stay conservative.\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 ONCE\n * via `Crossdeck.register({ plan: \"pro\" })` that get attached to every\n * subsequent event of that SDK instance. They're the single most-used\n * feature in Mixpanel-style analytics: \"every event from this user\n * should have `plan` and `appVersion` on it\" instead of remembering to\n * pass them on every track() call.\n *\n * **Groups** are organisational identifiers: a customer might belong to\n * an `org` (\"acme\"), a `team` (\"design\"), and a `plan` (\"enterprise\").\n * Each event carries `$groups.{type}: id` so B2B dashboards can pivot:\n * \"Acme's team:design has fired 142 paywall_shown events this week\".\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 reloads via the same storage layer the SDK\n * uses for identity (localStorage + cookie redundancy doesn't make\n * sense here — these are larger and live longer; localStorage only\n * is fine).\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\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\n constructor(\n private readonly storage: KeyValueStorage,\n private readonly prefix: string,\n ) {\n this.superProps = readJson(storage, prefix + KEY_SUPER) ?? {};\n this.groups = readJson(storage, prefix + KEY_GROUPS) ?? {};\n }\n\n // ---------- super properties ----------\n\n /**\n * Merge new keys into the super-property bag. Returns a snapshot of\n * the resulting bag. Values that are `null` are deleted (Mixpanel\n * semantics — explicit null = \"stop tracking this key\").\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 writeJson(this.storage, 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 writeJson(this.storage, 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 membership\n * for that group type — the SDK stops attaching it to 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 writeJson(this.storage, this.prefix + KEY_GROUPS, this.groups);\n }\n\n /**\n * Snapshot of the current groups map, keyed by group type. Returned\n * shape mirrors what the SDK attaches to every event as\n * `$groups.{type}`. The `traits` sub-object is the most-recent\n * traits payload passed to `setGroup` for that type; null when none.\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. Stable\n * for fast every-event merge — we don't want to JSON-clone on each\n * 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 try {\n this.storage.removeItem(this.prefix + KEY_SUPER);\n } catch {\n // ignore\n }\n try {\n this.storage.removeItem(this.prefix + KEY_GROUPS);\n } catch {\n // ignore\n }\n }\n}\n\nfunction readJson<T>(storage: KeyValueStorage, key: string): T | null {\n let raw: string | null;\n try {\n raw = storage.getItem(key);\n } catch {\n return null;\n }\n if (!raw) return null;\n try {\n return JSON.parse(raw) as T;\n } catch {\n return null;\n }\n}\n\nfunction writeJson(storage: KeyValueStorage, key: string, value: unknown): void {\n try {\n storage.setItem(key, JSON.stringify(value));\n } catch {\n // Quota / private mode — silent degrade. In-memory still holds\n // the current state; cross-tab sync just loses fidelity.\n }\n}\n","/**\n * Internal-traffic browser opt-out.\n *\n * Visiting any tracked page with `?crossdeck_internal=1` persists a flag in\n * localStorage; every event from this browser is then tagged with the\n * reserved `$crossdeck_internal` property, which ingest reads to classify\n * the event as internal. `?crossdeck_internal=0` clears it.\n *\n * This covers the cases identity- and IP-based rules miss: a dynamic home\n * IP, or browsing your own product logged out. It's per-browser and\n * self-service — no dashboard change required to set or clear it.\n *\n * Design: the URL is parsed ONCE at init (processInternalOptOutUrl) to\n * set/clear the persisted flag; each event then does a cheap localStorage\n * read (isInternalOptOut) so clearing takes effect immediately. Everything\n * is wrapped — SSR / Node / private-mode / disabled-storage all degrade to\n * \"not opted out\", never a throw.\n */\n\n/**\n * The reserved property key stamped on every event while opted out. Must\n * match the backend contract (backend/src/api/lib/actor-type.ts:\n * INTERNAL_OPT_OUT_PROPERTY). `$`-prefixed so it can't collide with a\n * developer's own property names.\n */\nexport const INTERNAL_OPT_OUT_PROPERTY = \"$crossdeck_internal\";\n\nconst STORAGE_KEY = \"crossdeck.internalOptOut\";\n\nfunction localStore(): Storage | null {\n try {\n return typeof localStorage !== \"undefined\" ? localStorage : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Run once at init. A `?crossdeck_internal=1` (or `=true`) in the current\n * URL persists the opt-out flag; `=0` (or `=false`) clears it. No param →\n * leaves the existing persisted state untouched.\n */\nexport function processInternalOptOutUrl(): void {\n try {\n const search = typeof location !== \"undefined\" ? location.search : \"\";\n const params = new URLSearchParams(search || \"\");\n if (!params.has(\"crossdeck_internal\")) return;\n const store = localStore();\n if (!store) return;\n const v = params.get(\"crossdeck_internal\");\n if (v === \"1\" || v === \"true\") {\n store.setItem(STORAGE_KEY, \"1\");\n } else if (v === \"0\" || v === \"false\") {\n store.removeItem(STORAGE_KEY);\n }\n } catch {\n /* no-op — never let opt-out handling break init */\n }\n}\n\n/** Cheap per-event check: is this browser currently opted out? */\nexport function isInternalOptOut(): boolean {\n try {\n return localStore()?.getItem(STORAGE_KEY) === \"1\";\n } catch {\n return false;\n }\n}\n","/**\n * Web Vitals capture — LCP, INP, CLS, FCP, TTFB.\n *\n * Why hand-rolled, not the `web-vitals` library: that library is ~6 KB\n * gz and pulls in handlers for every metric ever published by Google,\n * many of which (FID, soft-navigation, etc.) are deprecated or\n * superseded. The five metrics below are the ones every dashboard\n * actually renders — LCP for perceived load, INP for responsiveness,\n * CLS for visual stability, FCP for first paint, TTFB for backend\n * speed. Total here is ~80 lines, zero runtime deps.\n *\n * Each metric fires as a Crossdeck event:\n * `webvitals.lcp` → properties: { valueMs }\n * `webvitals.inp` → properties: { valueMs }\n * `webvitals.cls` → properties: { value } // unitless score\n * `webvitals.fcp` → properties: { valueMs }\n * `webvitals.ttfb` → properties: { valueMs }\n *\n * Capture timing:\n * - FCP, TTFB fire once after the page settles (typically <1s).\n * - LCP, CLS fire at page hidden (visibilitychange→hidden) — the\n * final value is only known when the user stops interacting\n * with the page.\n * - INP samples interactions and fires at page hidden.\n *\n * No-op outside browsers (PerformanceObserver missing) or when the\n * `autoTrack.webVitals` flag is false.\n */\n\ntype Reporter = (name: string, properties: Record<string, unknown>) => void;\n\nexport interface WebVitalsConfig {\n enabled: boolean;\n /**\n * Cap on the number of metric events emitted per page. Defaults to\n * one per metric type (so 5 max). Defends against a rogue browser\n * firing 100 PerformanceObserver entries.\n */\n maxEventsPerMetric?: number;\n}\n\nexport class WebVitalsTracker {\n private observers: PerformanceObserver[] = [];\n private flushed = new Set<string>();\n private cls = 0;\n private clsEntries: PerformanceEntry[] = [];\n private inp = 0;\n private cleanups: Array<() => void> = [];\n\n constructor(\n private readonly cfg: WebVitalsConfig,\n private readonly report: Reporter,\n ) {}\n\n install(): void {\n if (!this.cfg.enabled) return;\n if (typeof PerformanceObserver === \"undefined\") return;\n if (typeof globalThis === \"undefined\" || !(\"document\" in globalThis)) return;\n\n const doc = (globalThis as { document: Document }).document;\n\n // TTFB / FCP — fire as soon as we have data.\n try {\n const navObserver = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n const e = entry as PerformanceNavigationTiming;\n if (e.responseStart > 0 && !this.flushed.has(\"ttfb\")) {\n this.flushed.add(\"ttfb\");\n this.report(\"webvitals.ttfb\", { valueMs: Math.round(e.responseStart - e.startTime) });\n }\n }\n });\n navObserver.observe({ type: \"navigation\", buffered: true });\n this.observers.push(navObserver);\n } catch {\n // not supported — fall through\n }\n\n try {\n const paintObserver = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n if (entry.name === \"first-contentful-paint\" && !this.flushed.has(\"fcp\")) {\n this.flushed.add(\"fcp\");\n this.report(\"webvitals.fcp\", { valueMs: Math.round(entry.startTime) });\n }\n }\n });\n paintObserver.observe({ type: \"paint\", buffered: true });\n this.observers.push(paintObserver);\n } catch {\n // not supported\n }\n\n // LCP — track the LATEST entry; flush at page hidden.\n let lcpValue = 0;\n try {\n const lcpObserver = new PerformanceObserver((list) => {\n const entries = list.getEntries();\n const last = entries[entries.length - 1];\n if (last) lcpValue = last.startTime;\n });\n lcpObserver.observe({ type: \"largest-contentful-paint\", buffered: true });\n this.observers.push(lcpObserver);\n } catch {\n // not supported\n }\n\n // CLS — accumulate per-session layout-shift score.\n try {\n const clsObserver = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n // Layout-shift entries have a `value` field (cast loosely\n // since the typed DOM lib doesn't always expose it).\n const e = entry as PerformanceEntry & { value?: number; hadRecentInput?: boolean };\n if (typeof e.value === \"number\" && !e.hadRecentInput) {\n this.cls += e.value;\n this.clsEntries.push(entry);\n }\n }\n });\n clsObserver.observe({ type: \"layout-shift\", buffered: true });\n this.observers.push(clsObserver);\n } catch {\n // not supported\n }\n\n // INP — find the worst-case interaction duration.\n try {\n const eventObserver = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n const e = entry as PerformanceEntry & { duration: number; interactionId?: number };\n if (e.interactionId && e.duration > this.inp) {\n this.inp = e.duration;\n }\n }\n });\n // The `event` type is the modern way; `first-input` is a fallback.\n // Wrapped in try because Safari < 16.4 throws on `event` type.\n try {\n eventObserver.observe({ type: \"event\", buffered: true, durationThreshold: 16 } as PerformanceObserverInit);\n } catch {\n eventObserver.observe({ type: \"first-input\", buffered: true });\n }\n this.observers.push(eventObserver);\n } catch {\n // not supported\n }\n\n // Flush LCP / CLS / INP at page-hidden — the final values are only\n // known after the user stops interacting.\n const flush = (): void => {\n if (lcpValue > 0 && !this.flushed.has(\"lcp\")) {\n this.flushed.add(\"lcp\");\n this.report(\"webvitals.lcp\", { valueMs: Math.round(lcpValue) });\n }\n if (this.cls > 0 && !this.flushed.has(\"cls\")) {\n this.flushed.add(\"cls\");\n this.report(\"webvitals.cls\", { value: Math.round(this.cls * 1000) / 1000 });\n }\n if (this.inp > 0 && !this.flushed.has(\"inp\")) {\n this.flushed.add(\"inp\");\n this.report(\"webvitals.inp\", { valueMs: Math.round(this.inp) });\n }\n };\n const onHidden = (): void => {\n if (doc.visibilityState === \"hidden\") flush();\n };\n doc.addEventListener(\"visibilitychange\", onHidden);\n (globalThis as { window: Window }).window.addEventListener(\"pagehide\", flush);\n this.cleanups.push(() => {\n doc.removeEventListener(\"visibilitychange\", onHidden);\n (globalThis as { window: Window }).window.removeEventListener(\"pagehide\", flush);\n });\n }\n\n uninstall(): void {\n for (const o of this.observers) {\n try {\n o.disconnect();\n } catch {\n // ignore\n }\n }\n this.observers = [];\n for (const fn of this.cleanups.splice(0)) {\n try {\n fn();\n } catch {\n // ignore\n }\n }\n }\n}\n","/**\n * Consent gating — GDPR / CCPA-grade kill switches.\n *\n * Three independent dimensions, each defaulting to \"granted\" but\n * runtime-overridable:\n *\n * analytics — track(), identify(), heartbeat(), session/page auto-\n * emissions. Off → events drop silently, no network\n * calls fire.\n * marketing — paid-traffic click IDs (gclid/fbclid/etc) and\n * acquisition referrer URL. Off → these get scrubbed\n * before they ever land in the event bag.\n * errors — error / breadcrumb / Web Vitals capture. Off → no\n * webvitals.* events emitted, no error reporting (when\n * Phase 3 errors land).\n *\n * Why this granularity: real consent banners offer \"Analytics\",\n * \"Marketing\", \"Functional\" as separate boxes. The SDK has to match.\n *\n * Default state: every dimension is granted. The developer must\n * explicitly call `Crossdeck.consent({ analytics: false })` before\n * the first event to opt OUT — same convention as Google Tag Manager\n * Consent Mode. To start in deny mode, call `init(...)` then\n * immediately `consent({ analytics: false, marketing: false, errors:\n * false })` before any user activity.\n *\n * DNT (Do Not Track) browser header is checked once at init and\n * applied as an automatic deny across all dimensions when\n * `respectDnt: true` is set in CrossdeckOptions (default false because\n * the industry has effectively deprecated DNT — but opt-in support\n * is the polite default for privacy-first apps).\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 private dntDenied = false;\n\n constructor(options?: { respectDnt?: boolean }) {\n if (options?.respectDnt && this.detectDnt()) {\n this.dntDenied = true;\n this.state = { analytics: false, marketing: false, errors: false };\n }\n }\n\n /**\n * Merge new dimensions onto the current state. Returns the resulting\n * snapshot. DNT-derived denies cannot be flipped back on by a `set`\n * call — once the browser says \"don't track\", we don't track even if\n * the developer code disagrees. That's the contract.\n */\n set(partial: Partial<ConsentState>): ConsentState {\n if (this.dntDenied) return { ...this.state };\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 /** Snapshot of the current state. */\n get(): ConsentState {\n return { ...this.state };\n }\n\n /** Convenience getters for hot paths. */\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 /** True iff the constructor detected and applied DNT. */\n get isDntDenied(): boolean {\n return this.dntDenied;\n }\n\n private detectDnt(): boolean {\n try {\n const nav = (globalThis as { navigator?: Navigator }).navigator;\n if (!nav) return false;\n // Three historical spellings: navigator.doNotTrack (standard),\n // navigator.msDoNotTrack (IE), window.doNotTrack (Safari).\n // All return \"1\" / \"yes\" when the user has DNT enabled.\n const sources = [\n (nav as Navigator & { doNotTrack?: string }).doNotTrack,\n (nav as Navigator & { msDoNotTrack?: string }).msDoNotTrack,\n (globalThis as { doNotTrack?: string }).doNotTrack,\n ];\n return sources.some((v) => v === \"1\" || v === \"yes\");\n } catch {\n return false;\n }\n }\n}\n\n// ============================================================\n// PII scrubbing — URL + property values\n// ============================================================\n\n/**\n * Email-shaped pattern. Reasonably restrictive — matches RFC 5322's\n * \"obs-local-part\" common case (the practical 99% of emails). We\n * deliberately don't try to match every legal email; the goal is\n * \"if it looks like an email, scrub it\" without false positives.\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 that\n * could be split by space or hyphen — the format every payment form\n * accepts. We don't validate Luhn; this is best-effort scrubbing,\n * not card-data tokenisation. If you're handling actual PAN data\n * you should not be passing it through analytics in the first place.\n */\n// Anchor on a digit at both ends so trailing separators (space / hyphen)\n// aren't pulled into the match — otherwise \"4242 4242 4242 4242 today\"\n// scrubs as \"<card>today\" instead of \"<card> today\".\nconst CARD_PATTERN = /\\b\\d(?:[ -]?\\d){12,18}\\b/g;\n\n// Sentinel tokens — aligned with backend/src/api/lib/scrub.ts which uses\n// <email>, <card>, <uuid>, <cdcust>, <crossdeck_secret_key>, <aws_access_key>.\n// Mismatched tokens between the SDK scrub and the backend's defence-in-\n// depth scrub would split dashboard aggregation (the same event arriving\n// from two paths would carry 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: we call `.replace()` unconditionally rather than\n * gating on `.test()`. The /g regexes are module-level so `.test()`\n * carries `lastIndex` state between calls — a prior match leaves\n * `lastIndex` mid-string and the next `.test()` can falsely return\n * false on a string that DOES match. `.replace(/g)` always scans the\n * full string regardless of `lastIndex`, so dropping the test-guard\n * removes the sharp edge at zero cost (when nothing matches, replace\n * returns the same `(===)` string).\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 * Pre-fix this was the SDK's #1 PII leak vector — every captured-error\n * report ships nested `frames[]` / `breadcrumbs[]` / `context{}` /\n * `http{}` shapes through here. Date / Map / Set / Error / class\n * instances pass through untouched (those are the\n * `validateEventProperties` sanitiser's job — this is the PII regex\n * 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 the downstream error reporter.\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\" (page views, clicks,\n * custom events, network calls, console logs). When an error fires,\n * attach the buffer so the engineer reading the error can see exactly\n * how the user got into the broken state. The single most powerful\n * debugging signal in error monitoring — without breadcrumbs, errors\n * are stack traces 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 cover\n * ~5 minutes of typical user activity without ballooning the error\n * payload — Sentry uses 100 by default but the SDK is more aggressive\n * about size since we ship breadcrumbs over the wire with every error,\n * not as a separate batch.\n *\n * Privacy: breadcrumbs auto-emit from the same auto-tracking sources\n * as analytics events (page.viewed, element.clicked). Those already\n * skip password fields, form inputs, and cd-noTrack subtrees. Custom\n * crumbs added via Crossdeck.addBreadcrumb() pass through the same\n * property 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\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/**\n * Reliability endpoint URL — `/v1/events`, the SAME endpoint customer\n * `cd.track()` calls use. Contract failures land as ordinary\n * `crossdeck.contract_failed` events in the Crossdeck reliability\n * project's events warehouse (resolved via the hardcoded reliability\n * publishable key below). They surface in the Crossdeck team's\n * events-explorer alongside any other custom event on that project\n * — no separate dashboard surface required.\n *\n * Pre-2026-05-28 this pointed at `/v1/sdk/diagnostic`, a Crossdeck-\n * specific endpoint that wrote to a top-level `sdkDiagnostics`\n * collection. That endpoint has no dashboard renderer; failures\n * piled up invisibly. Routing through `/v1/events` reuses the\n * generic custom-event pipeline + UI any customer would have for\n * their own track() calls.\n */\nexport const DIAGNOSTIC_TELEMETRY_ENDPOINT =\n \"https://api.cross-deck.com/v1/events\";\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. Anything outside this set is dropped at the\n * call site so a future caller can't accidentally widen the wire\n * shape.\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 // verification_phase is set by the runtime contract verifier layer\n // (sdks/web/src/_contract-verifiers.ts) — values `boot` / `hot_path`.\n // Absent for failures emitted by external test harnesses\n // (XCTestObservation, Vitest hooks, JUnit watchers) which carry\n // test_file + test_name instead. See contracts/diagnostics/\n // contract-failed-payload-schema-lock.json.\n \"verification_phase\",\n]);\n\n/**\n * Whitelist filter — even if a caller threads a forbidden key\n * (anonymousId, ip, etc.) through, it never hits the wire. The\n * backend would reject it anyway; this is defence in depth.\n *\n * Exported so unit tests can verify the schema-lock without needing\n * to monkey-patch fetch or wait for the reliability endpoint to be\n * provisioned.\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\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 * @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 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 // Wrap as the /v1/events batch shape — single event per fire,\n // `name: \"crossdeck.contract_failed\"`, all schema-lock fields as\n // properties. The reliability publishable key resolves to the\n // Crossdeck reliability project server-side; the event lands in\n // that project's events warehouse and surfaces in its\n // events-explorer like any other custom event.\n const body = JSON.stringify({\n events: [\n {\n name: \"crossdeck.contract_failed\",\n timestamp: Date.now(),\n properties: filtered,\n },\n ],\n });\n\n // Browser path: fetch with keepalive so the request survives a\n // page-unload that fires immediately after the call. Node-only\n // builds never hit this branch (the SDK selects `crossdeck-server`\n // for that runtime). If fetch is unavailable (older WebViews), the\n // request is silently dropped — operational telemetry is not\n // worth surfacing in a customer-facing error.\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 keepalive: true,\n // No credentials, no cache, no referrer — the reliability\n // endpoint is the same origin only in tests. In production the\n // browser never carries anything beyond the request body and\n // the Authorization header we set explicitly.\n credentials: \"omit\",\n cache: \"no-store\",\n referrerPolicy: \"no-referrer\",\n }).catch(() => {\n // Fire-and-forget; we never want a rejection bubbling into the\n // host app's unhandledrejection handler.\n });\n } catch {\n // Swallow synchronous throws (CSP block, immediate failure).\n }\n}\n","/**\n * Machine-readable index of every error code the SDK can throw, with\n * a short description and a hint on what action to take. Published\n * verbatim as `crossdeck-error-codes.json` in the npm tarball so AI\n * integration assistants, error-aggregator dashboards (Sentry,\n * DataDog), and the Crossdeck dashboard can render human-friendly\n * messages without parsing freeform `message` strings.\n *\n * Stripe publishes the same surface at stripe.com/docs/error-codes;\n * developers love it because every code has a canonical \"what does\n * this mean / what should I do\" answer.\n *\n * Adding a new error code:\n * 1. Add the code string to the union in `errors.ts` (where used).\n * 2. Add an entry here.\n * 3. The next `npm run build` regenerates the JSON sidecar.\n *\n * Keep entries terse — the consumer surfaces this in tooltips and\n * automated tickets, not in long-form docs.\n */\n\nexport interface ErrorCodeEntry {\n /** The string thrown as CrossdeckError.code. */\n code: string;\n /** CrossdeckError.type — broad category. */\n type:\n | \"authentication_error\"\n | \"permission_error\"\n | \"invalid_request_error\"\n | \"rate_limit_error\"\n | \"version_error\"\n | \"internal_error\"\n | \"network_error\"\n | \"configuration_error\";\n /** One-sentence description. Surfaced verbatim in dashboards. */\n description: string;\n /** What the developer should do. Imperative phrasing. */\n resolution: string;\n /** True for codes the SDK can auto-recover from (no developer action). */\n retryable: boolean;\n}\n\nexport const CROSSDECK_ERROR_CODES: readonly ErrorCodeEntry[] = Object.freeze([\n // ----- Configuration -----\n {\n code: \"invalid_public_key\",\n type: \"configuration_error\",\n description: \"The publishable key passed to Crossdeck.init() doesn't start with cd_pub_.\",\n resolution: \"Copy the key from your Crossdeck dashboard → API keys page.\",\n retryable: false,\n },\n {\n code: \"missing_app_id\",\n type: \"configuration_error\",\n description: \"Crossdeck.init() was called without an appId.\",\n resolution: \"Add appId to your init options — find it in the dashboard's Apps page.\",\n retryable: false,\n },\n {\n code: \"invalid_environment\",\n type: \"configuration_error\",\n description: \"Crossdeck.init() requires environment: 'production' | 'sandbox'.\",\n resolution: \"Pass the literal string \\\"production\\\" or \\\"sandbox\\\" — no other values are accepted.\",\n retryable: false,\n },\n {\n code: \"environment_mismatch\",\n type: \"configuration_error\",\n description: \"The publishable key's env prefix doesn't match the declared environment option.\",\n resolution: \"Either change `environment` to match the key prefix (cd_pub_test_ ↔ sandbox, cd_pub_live_ ↔ production), or swap the key for one minted in the right env.\",\n retryable: false,\n },\n {\n code: \"not_initialized\",\n type: \"configuration_error\",\n description: \"An SDK method was called before Crossdeck.init().\",\n resolution: \"Call Crossdeck.init({ appId, publicKey, environment }) once at app startup before any other method.\",\n retryable: false,\n },\n\n // ----- Identify / track / purchase argument validation -----\n {\n code: \"missing_user_id\",\n type: \"invalid_request_error\",\n description: \"identify() was called with an empty userId.\",\n resolution: \"Pass a stable, non-empty user identifier from your auth layer — never a hardcoded placeholder.\",\n retryable: false,\n },\n {\n code: \"missing_event_name\",\n type: \"invalid_request_error\",\n description: \"track() was called without an event name.\",\n resolution: \"Pass a non-empty string as the first argument.\",\n retryable: false,\n },\n {\n code: \"missing_group_type\",\n type: \"invalid_request_error\",\n description: \"group() was called without a group type.\",\n resolution: \"Pass a non-empty type (e.g. \\\"org\\\", \\\"team\\\") as the first argument.\",\n retryable: false,\n },\n {\n code: \"missing_signed_transaction_info\",\n type: \"invalid_request_error\",\n description: \"syncPurchases() was called without StoreKit 2 signed transaction info.\",\n resolution: \"Pass the JWS string from Transaction.currentEntitlements / Transaction.updates.\",\n retryable: false,\n },\n\n // ----- Network / transport -----\n {\n code: \"fetch_failed\",\n type: \"network_error\",\n description: \"The underlying fetch() call failed (typically a network outage or DNS issue).\",\n resolution: \"Check the user's network. The SDK will retry automatically with exponential backoff.\",\n retryable: true,\n },\n {\n code: \"request_timeout\",\n type: \"network_error\",\n description: \"A request was aborted after the configured timeoutMs (default 15s).\",\n resolution: \"Check the user's connection. Increase timeoutMs in init options if the user is on a known-slow network.\",\n retryable: true,\n },\n {\n code: \"invalid_json_response\",\n type: \"internal_error\",\n description: \"The server returned a 2xx with an unparseable body.\",\n resolution: \"Likely a transient backend bug. Retry; if it persists, contact support with the requestId.\",\n retryable: true,\n },\n\n // ----- Backend-emitted codes (v1.4.0 Phase 6.2 backfill) -----\n // Mirror of backend/src/api/v1-errors.ts ApiErrorCode. A developer\n // hitting any of these on the wire can look them up via\n // getErrorCode(code) for a canonical remediation step instead of\n // hunting through Slack history.\n {\n code: \"missing_api_key\",\n type: \"authentication_error\",\n description: \"No Authorization header (or Crossdeck-Api-Key header) on the request.\",\n resolution: \"Make sure Crossdeck.init({ publicKey }) was called with a cd_pub_… key before the request fired. Re-check your env-vars in CI / Docker if the key is empty at runtime.\",\n retryable: false,\n },\n {\n code: \"invalid_api_key\",\n type: \"authentication_error\",\n description: \"The API key is malformed, unknown, or doesn't resolve to a project.\",\n resolution: \"Copy the key from your Crossdeck dashboard → API keys. Confirm prefix is cd_pub_test_ / cd_pub_live_ for client SDKs and cd_sk_test_ / cd_sk_live_ for the Node server SDK.\",\n retryable: false,\n },\n {\n code: \"key_revoked\",\n type: \"authentication_error\",\n description: \"The API key was revoked in the Crossdeck dashboard.\",\n resolution: \"Mint a fresh key in the dashboard → API keys → Create new, swap it in, and redeploy. The revoked key cannot be reactivated.\",\n retryable: false,\n },\n {\n code: \"identity_token_invalid\",\n type: \"authentication_error\",\n description: \"The Firebase / Apple / Google ID token supplied with the request didn't verify against the dashboard's configured signers.\",\n resolution: \"Refresh the token client-side (Firebase auth.currentUser.getIdToken(true)) and retry. If the failure persists, confirm the signer is registered under dashboard → Authentication → Identity sources.\",\n retryable: true,\n },\n {\n code: \"origin_not_allowed\",\n type: \"permission_error\",\n description: \"The Origin header isn't in the project's Allowed origins list.\",\n resolution: \"Add the origin (e.g. https://app.example.com) under dashboard → Settings → Allowed origins. Wildcards like https://*.example.com are supported.\",\n retryable: false,\n },\n {\n code: \"bundle_id_not_allowed\",\n type: \"permission_error\",\n description: \"The iOS bundle ID sent via X-Crossdeck-Bundle-Id isn't registered under this app's Apple identity lock.\",\n resolution: \"Add the bundle ID under dashboard → Apps → <your app> → iOS bundle IDs.\",\n retryable: false,\n },\n {\n code: \"package_name_not_allowed\",\n type: \"permission_error\",\n description: \"The Android package name sent via X-Crossdeck-Package-Name isn't registered under this app's Android identity lock.\",\n resolution: \"Add the package name under dashboard → Apps → <your app> → Android package names.\",\n retryable: false,\n },\n {\n code: \"env_mismatch\",\n type: \"permission_error\",\n description: \"The request env (inferred from key prefix) doesn't match the resolved app's configured env.\",\n resolution: \"Use a cd_pub_live_ / cd_sk_live_ key with a production app, cd_pub_test_ / cd_sk_test_ with a sandbox app. The two cannot cross.\",\n retryable: false,\n },\n {\n code: \"idempotency_key_in_use\",\n type: \"invalid_request_error\",\n description: \"An Idempotency-Key was reused for a request with a different body (Stripe-grade contract).\",\n resolution: \"Generate a fresh key for a different transaction, or reuse the key only with the EXACT same body. The v1.4.0 SDKs derive keys deterministically from the body so this should never fire on SDK-managed calls.\",\n retryable: false,\n },\n {\n code: \"rate_limited\",\n type: \"rate_limit_error\",\n description: \"Request rate exceeded the project's per-second cap.\",\n resolution: \"Honour the Retry-After header — the SDK does this automatically on managed retries. If you're hitting it from a custom code path, throttle to <100 req/s/key.\",\n retryable: true,\n },\n {\n code: \"internal_error\",\n type: \"internal_error\",\n description: \"Server-side issue. Safe to retry with backoff.\",\n resolution: \"The SDK retries automatically. If your code paths through to this error, contact support with the requestId from the response envelope.\",\n retryable: true,\n },\n {\n code: \"google_not_supported\",\n type: \"invalid_request_error\",\n description: \"POST /purchases/sync with rail=google is gated until the Play Developer API reconciliation worker ships.\",\n resolution: \"Until v1.5+, Google Play purchases verify via Real-time Developer Notifications. The SDK auto-track path handles this transparently for Android consumers.\",\n retryable: false,\n },\n {\n code: \"stripe_not_supported\",\n type: \"invalid_request_error\",\n description: \"POST /purchases/sync with rail=stripe is unsupported — Stripe Checkout's redirect flow uses platform webhooks instead.\",\n resolution: \"Wire Stripe via the standard Checkout / Customer Portal flow; Crossdeck reconciles via the platform webhook automatically. No SDK call needed.\",\n retryable: false,\n },\n {\n code: \"missing_required_param\",\n type: \"invalid_request_error\",\n description: \"A required field is absent from the request body.\",\n resolution: \"Inspect the error.message — the missing field name is included verbatim. Refer to the SDK's TypeScript types for the canonical request shape.\",\n retryable: false,\n },\n {\n code: \"invalid_param_value\",\n type: \"invalid_request_error\",\n description: \"A field is present but the value failed validation (wrong shape, wrong length, wrong enum value).\",\n resolution: \"Read error.message for the specific field + reason. SDK-managed call sites should never emit this — file a bug if you do.\",\n retryable: false,\n },\n {\n code: \"sdk_version_unsupported\",\n type: \"version_error\",\n description: \"HTTP 426 — your installed SDK sends an event format the server no longer accepts. The data is good; only the wire dialect is too old. The SDK PARKS automatically: events are held on-device (paused, not lost) and deliver on the next launch after you upgrade.\",\n resolution: \"Update @cross-deck/web to at least the version in error.minVersion and redeploy — the parked queue backfills automatically. See https://cross-deck.com/docs/sdk-event-durability/.\",\n retryable: false,\n },\n] as const);\n\n/** Lookup helper — returns the entry matching a CrossdeckError.code, or undefined. */\nexport function getErrorCode(code: string): ErrorCodeEntry | undefined {\n return CROSSDECK_ERROR_CODES.find((e) => e.code === code);\n}\n","/**\n * Contract verifier layer — runtime self-verification for the Web SDK.\n *\n * Crossdeck publishes its structural guarantees as machine-readable\n * contracts under `contracts/`. Each contract is a single claim about\n * the runtime — per-user cache isolation, cross-SDK idempotency-key\n * determinism, Stripe-shape error envelope, etc. — paired with the\n * source files that implement it and the tests that prove it in CI.\n *\n * The CI tests are the institutional half of the proof. This module\n * is the customer-facing half: at runtime, in every install, on every\n * relevant operation, a verifier re-runs the contract's claim against\n * the live SDK and produces a `{ ok: true, evidence }` or\n * `{ ok: false, failureReason }` result. The customer's engineer sees\n * passes streaming through their browser devtools console\n * (`[crossdeck.identify] ✓ per-user-cache-isolation — slot rotated …`)\n * and the Crossdeck reliability team sees failures arrive in the\n * reliability workspace, before the customer's own dashboard would\n * have noticed.\n *\n * Three switches govern the layer (see CrossdeckOptions docstrings\n * for the full surface contract):\n *\n * verifyContractsAtBoot\n * Whether the boot self-test runs on Crossdeck.start(...).\n * Defaults dev=true, prod=false.\n *\n * logVerifierResults\n * Whether PASS results print to the console. Cosmetic only —\n * does NOT affect failure reporting. Defaults dev=true, prod=false.\n *\n * disableContractAssertions\n * Total kill-switch — no verifiers run, no console output, no\n * telemetry. Defaults false. Sovereignty escape hatch for\n * enterprise customers whose posture forbids any outbound\n * diagnostic telemetry to third-party controllers.\n *\n * Routing rules — locked, audited, KPMG-readable:\n *\n * │ logVerifierResults │ logVerifierResults │\n * │ = true │ = false │\n * ─────────────────┼────────────────────┼────────────────────┤\n * PASS (boot) │ console.info ✓ │ silent │\n * PASS (hot_path)│ console.debug ✓ │ silent │\n * FAIL (any) │ console.warn ✗ │ console.warn ✗ │\n * │ + reliability │ + reliability │\n *\n * `disableContractAssertions: true` short-circuits BEFORE any of the\n * above; verifiers don't even execute.\n *\n * @module _contract-verifiers\n * @internal Bundled-but-not-exported from the public package surface.\n * Customers interact via CrossdeckOptions, not this file.\n */\n\nimport { EntitlementCache } from \"./entitlement-cache\";\nimport {\n deriveIdempotencyKeyForPurchase,\n formatAsUuid,\n} from \"./idempotency-key\";\nimport { sha256Hex } from \"./hash\";\nimport { getErrorCode } from \"./error-codes\";\nimport { sendDiagnosticTelemetry } from \"./_diagnostic-telemetry\";\nimport { SDK_NAME, SDK_VERSION } from \"./_version\";\n\n// ============================================================================\n// Public result types — the contract a verifier returns.\n// ============================================================================\n\n/**\n * A successful verifier execution. `evidence` is a short\n * human-readable string the developer sees in their console:\n * \"slot rotated _anon → 7c44…ee20\"\n * \"apple JWS → a66b1640-efaf-bb4d-1261-6650033bf111\"\n * \"{ type, code, message, request_id } parsed\"\n * Keep evidence short (under 120 chars) and free of identifiers that\n * could re-link to an end-user. Never include raw userIds, emails,\n * IPs, or stack frames.\n */\nexport interface VerifierPass {\n readonly ok: true;\n readonly contractId: string;\n readonly evidence: string;\n readonly durationMs: number;\n}\n\n/**\n * A failed verifier execution. `failureReason` is the short\n * categorical-ish label the reliability dashboard groups on:\n * \"slot not rotated\"\n * \"key not deterministic\"\n * \"envelope missing request_id\"\n * Kept under 128 chars by SDK convention. Never includes raw values\n * — categorical labels only, so the legitimate-interest analysis in\n * Privacy §6 stays valid (no end-user data on the wire).\n */\nexport interface VerifierFail {\n readonly ok: false;\n readonly contractId: string;\n readonly failureReason: string;\n readonly durationMs: number;\n}\n\nexport type VerifierResult = VerifierPass | VerifierFail;\n\n/**\n * Which layer of the verifier system produced the result. Carried\n * on every failure report as `verification_phase` so the reliability\n * dashboard can slice by urgency — a `boot` failure means the SDK\n * is structurally broken before the customer's first user even taps;\n * a `hot_path` failure means a structural contract was violated\n * during real-world operation.\n */\nexport type VerificationPhase = \"boot\" | \"hot_path\";\n\n// ============================================================================\n// Context — the immutable state every verifier sees.\n// ============================================================================\n\n/**\n * Per-process verifier context. Constructed once at `Crossdeck.start(...)`\n * and threaded into every verifier invocation.\n */\nexport interface VerifierContext {\n /** SDK version (`@cross-deck/web@1.5.1`-ish). */\n readonly sdkVersion: string;\n /** Stable per-process identifier used as `run_id` on every failure\n * report — lets the reliability dashboard collapse multiple\n * verifier hits within one process into one row. */\n readonly runId: string;\n /** `ci` / `dogfood` / `customer-app`. The Web SDK defaults to\n * `customer-app` (the SDK is running inside a customer's deployed\n * site); test harnesses override via the existing\n * `reportContractFailure(...)` path with a different `run_context`. */\n readonly runContext: \"ci\" | \"dogfood\" | \"customer-app\";\n /** Mirrors CrossdeckOptions.logVerifierResults. */\n readonly logVerifierResults: boolean;\n /** Mirrors CrossdeckOptions.disableContractAssertions. */\n readonly disableContractAssertions: boolean;\n /** Console sink — defaults to globalThis.console; tests override. */\n readonly console: Pick<Console, \"info\" | \"debug\" | \"warn\">;\n /** Telemetry sink — defaults to the production\n * `sendDiagnosticTelemetry`; tests override. */\n readonly emitTelemetry: (\n payload: Record<string, string>,\n ) => void;\n}\n\n/**\n * Build the default VerifierContext for a Web SDK instance.\n *\n * `runId` is `cd_verify_<8-hex>` — short enough to read in a dashboard\n * row, long enough to be probabilistically unique per process.\n */\nexport function buildVerifierContext(opts: {\n logVerifierResults: boolean;\n disableContractAssertions: boolean;\n runContext?: \"ci\" | \"dogfood\" | \"customer-app\";\n}): VerifierContext {\n return {\n sdkVersion: `${SDK_NAME}@${SDK_VERSION}`,\n runId: `cd_verify_${randomHex(8)}`,\n runContext: opts.runContext ?? \"customer-app\",\n logVerifierResults: opts.logVerifierResults,\n disableContractAssertions: opts.disableContractAssertions,\n console: console,\n emitTelemetry: sendDiagnosticTelemetry,\n };\n}\n\n// ============================================================================\n// Reporter — single point of console + telemetry routing.\n// ============================================================================\n\n/**\n * Routes a `VerifierResult` to the console (per `logVerifierResults`)\n * and the reliability channel (always, on FAIL).\n *\n * The reporter is the single audit-trail seam. KPMG-readable:\n * - One function decides every output path.\n * - One re-entrancy guard guarantees a verifier-on-the-telemetry-\n * path cannot recursively fire telemetry on itself.\n * - Console output and telemetry are NEVER coupled — flipping one\n * flag cannot accidentally affect the other.\n */\nexport class VerifierReporter {\n private reentrancyDepth = 0;\n\n constructor(private readonly ctx: VerifierContext) {}\n\n /**\n * Report a verifier result. Pass `operation` for hot-path results\n * to render the prefix as `[crossdeck.identify]` etc.; omit for\n * boot results which render as `[crossdeck]`.\n */\n report(\n result: VerifierResult,\n phase: VerificationPhase,\n operation?: string,\n ): void {\n // Total kill-switch — verifiers should not even execute when this\n // is set, but the reporter checks too for defence in depth.\n if (this.ctx.disableContractAssertions) return;\n\n if (result.ok) {\n this.reportPass(result, phase, operation);\n } else {\n this.reportFail(result, phase, operation);\n }\n }\n\n private reportPass(\n result: VerifierPass,\n phase: VerificationPhase,\n operation?: string,\n ): void {\n if (!this.ctx.logVerifierResults) return;\n const line = formatPassLine(result, operation);\n if (phase === \"boot\") {\n this.ctx.console.info(line);\n } else {\n this.ctx.console.debug(line);\n }\n }\n\n private reportFail(\n result: VerifierFail,\n phase: VerificationPhase,\n operation?: string,\n ): void {\n // FAIL always prints at WARN regardless of logVerifierResults —\n // silencing a structural failure would defeat the purpose. The\n // ONLY way to suppress this is `disableContractAssertions: true`\n // which short-circuited above.\n this.ctx.console.warn(formatFailLine(result, operation));\n\n // Re-entrancy guard. The `contract-failed-payload-schema-lock`\n // verifier runs on every reliability-channel write, so a malformed\n // payload would otherwise infinite-loop:\n // verifier fails → reporter fires telemetry → telemetry write\n // triggers schema-lock verifier → verifier fails → ...\n // The guard breaks the loop at depth 1; the outer failure still\n // makes it to the reliability channel exactly once.\n if (this.reentrancyDepth > 0) return;\n this.reentrancyDepth += 1;\n try {\n this.ctx.emitTelemetry({\n contract_id: result.contractId,\n sdk_version: this.ctx.sdkVersion,\n sdk_platform: \"web\",\n failure_reason: truncate(result.failureReason, 128),\n run_context: this.ctx.runContext,\n run_id: this.ctx.runId,\n verification_phase: phase,\n });\n } finally {\n this.reentrancyDepth -= 1;\n }\n }\n}\n\nfunction formatPassLine(r: VerifierPass, operation?: string): string {\n const prefix = operation ? `[crossdeck.${operation}]` : \"[crossdeck]\";\n return `${prefix} ✓ ${r.contractId} — ${r.evidence} (${r.durationMs}ms)`;\n}\n\nfunction formatFailLine(r: VerifierFail, operation?: string): string {\n const prefix = operation ? `[crossdeck.${operation}]` : \"[crossdeck]\";\n return `${prefix} ✗ ${r.contractId} — ${r.failureReason} (${r.durationMs}ms)`;\n}\n\n// ============================================================================\n// Verifier — the protocol every contract verifier implements.\n// ============================================================================\n\n/**\n * A `ContractVerifier` is a small object that knows how to test ONE\n * contract claim against the live SDK at runtime.\n *\n * contractId — the stable id from contracts/<pillar>/<id>.json\n * bootTest — runs once at Crossdeck.start(), against synthetic\n * state (the customer's real SDK state is never\n * mutated). Optional — some contracts only make\n * sense in the hot-path.\n * hooks — observation functions invoked from the SDK's\n * hot-path call sites (identify, track, syncPurchases,\n * error parse, ...) with typed observations.\n * Optional — some contracts only test at boot.\n */\nexport interface ContractVerifier {\n readonly contractId: string;\n bootTest?(): VerifierResult | Promise<VerifierResult>;\n /** Hot-path hooks — each receives the actual observation needed to\n * decide pass/fail and returns the result. The dispatcher\n * (`runOn*` helpers below) invokes the right hook from the right\n * SDK call site. */\n readonly hooks?: {\n onIdentify?(obs: IdentifyObservation): VerifierResult;\n onTrack?(obs: TrackObservation): VerifierResult;\n onSyncPurchases?(obs: SyncPurchasesObservation): VerifierResult;\n onErrorParse?(obs: ErrorParseObservation): VerifierResult;\n onReportContractFailure?(obs: ReportFailureObservation): VerifierResult;\n };\n}\n\n// ============================================================================\n// Observation types — the typed payloads every hook receives.\n// ============================================================================\n\nexport interface IdentifyObservation {\n /** The prior userId, or null if previously anonymous. */\n readonly priorUserId: string | null;\n /** The new userId being identified. */\n readonly nextUserId: string;\n /** The live entitlement cache after the rotation completed. */\n readonly cache: EntitlementCache;\n}\n\nexport interface TrackObservation {\n /** Properties as supplied by the caller (before any merge). */\n readonly callerProperties: Record<string, unknown>;\n /** Super-properties currently registered on the SDK. */\n readonly superProperties: Record<string, unknown>;\n /** Auto-attached device properties. */\n readonly deviceProperties: Record<string, unknown>;\n /** The merged result the SDK actually enqueued. */\n readonly mergedProperties: Record<string, unknown>;\n}\n\nexport interface SyncPurchasesObservation {\n readonly rail: \"apple\" | \"google\" | \"stripe\" | string;\n /** The Apple-side JWS or Google-side purchase token used to derive\n * the idempotency key. */\n readonly stableIdentifier: string;\n /** The idempotency key the SDK actually sent. */\n readonly derivedKey: string;\n}\n\nexport interface ErrorParseObservation {\n /** The parsed `type` field from the wire envelope. */\n readonly errorType: string;\n /** The parsed `code` field. */\n readonly errorCode: string;\n /** The parsed `request_id`, if present. */\n readonly requestId: string | null;\n /** The original HTTP status from the response. */\n readonly httpStatus: number;\n}\n\nexport interface ReportFailureObservation {\n /** The fully-merged payload AFTER allow-list filtering, exactly as\n * about to be sent on the wire. */\n readonly outgoingPayload: Record<string, unknown>;\n}\n\n// ============================================================================\n// VERIFIER 1 — per-user-cache-isolation\n// ============================================================================\n\nconst VERIFIER_PER_USER_CACHE_ISOLATION: ContractVerifier = {\n contractId: \"per-user-cache-isolation\",\n\n bootTest(): VerifierResult {\n const t0 = nowMs();\n try {\n const storage = new MemoryStorage();\n // EntitlementCache constructor is positional: (storage,\n // storageKeyPrefix, staleAfterMs). See sdks/web/src/entitlement-cache.ts.\n const cache = new EntitlementCache(storage, \"_verifier\");\n\n // Plant entitlements for user A. PublicEntitlement shape per\n // sdks/web/src/types.ts — synthetic source values are fine since\n // we're isolated from the customer's real cache and never hit\n // the network.\n cache.setUserKey(\"user_A\");\n cache.setFromList([\n {\n object: \"entitlement\",\n key: \"pro\",\n isActive: true,\n validUntil: null,\n source: {\n rail: \"stripe\",\n productId: \"p_verifier\",\n subscriptionId: \"s_verifier\",\n },\n updatedAt: Date.now(),\n },\n ]);\n\n // Rotate to user B.\n cache.setUserKey(\"user_B\");\n\n // The in-memory snapshot must be empty AND the storage slot for\n // user_A must be physically separate from user_B (different sha256\n // suffixes). Use the public `list()` accessor — the contract\n // guarantees this returns the live in-memory snapshot.\n const inMemoryB = cache.list();\n if (inMemoryB.length !== 0) {\n return fail(\n \"per-user-cache-isolation\",\n \"in-memory snapshot still carried user_A's entitlements after rotation\",\n nowMs() - t0,\n );\n }\n\n const suffixA = EntitlementCache.suffixForUserId(\"user_A\");\n const suffixB = EntitlementCache.suffixForUserId(\"user_B\");\n if (suffixA === suffixB) {\n return fail(\n \"per-user-cache-isolation\",\n \"suffixes for user_A and user_B collided\",\n nowMs() - t0,\n );\n }\n\n const storageA = storage.getItem(`_verifier:${suffixA}`);\n const storageB = storage.getItem(`_verifier:${suffixB}`);\n // user_A's slot still holds their data (the rotation didn't wipe\n // it — that's the point: a returning user observes their cache);\n // user_B's slot is empty (no entitlements written yet).\n if (!storageA) {\n return fail(\n \"per-user-cache-isolation\",\n \"user_A's storage slot was wiped on rotation (expected isolation, not erasure)\",\n nowMs() - t0,\n );\n }\n if (storageB) {\n return fail(\n \"per-user-cache-isolation\",\n \"user_B's storage slot already contained data before any write\",\n nowMs() - t0,\n );\n }\n\n return pass(\n \"per-user-cache-isolation\",\n `slot rotated A:${shortSuffix(suffixA)} → B:${shortSuffix(suffixB)} (isolated, physically separate)`,\n nowMs() - t0,\n );\n } catch (err) {\n return fail(\n \"per-user-cache-isolation\",\n `boot test threw: ${(err as Error).message?.slice(0, 80) ?? \"unknown error\"}`,\n nowMs() - t0,\n );\n }\n },\n\n hooks: {\n onIdentify(obs: IdentifyObservation): VerifierResult {\n const t0 = nowMs();\n const priorSuffix = EntitlementCache.suffixForUserId(obs.priorUserId);\n const nextSuffix = EntitlementCache.suffixForUserId(obs.nextUserId);\n\n if (priorSuffix !== nextSuffix) {\n // The in-memory snapshot must be empty after rotation. We\n // test this against the live cache via the public list()\n // accessor; the contract guarantees it returns the same\n // in-memory snapshot the cache holds.\n if (obs.cache.list().length !== 0) {\n return fail(\n \"per-user-cache-isolation\",\n \"in-memory snapshot still held entitlements after slot rotation\",\n nowMs() - t0,\n );\n }\n return pass(\n \"per-user-cache-isolation\",\n `slot rotated ${shortSuffix(priorSuffix)} → ${shortSuffix(nextSuffix)}`,\n nowMs() - t0,\n );\n }\n\n // Same-id re-identify: in-memory must STILL be cleared per the\n // contract's \"unconditional wipe\" clause, then re-hydrated from\n // the same slot. The live cache's in-memory snapshot is observed\n // post-operation; either empty (cleared) or repopulated from the\n // slot is acceptable. The structural test is \"the operation ran\n // through the setUserKey() code path\" — we cannot directly assert\n // that without a side-channel, so we verify the slot identity\n // for inputs that produce identical suffixes.\n return pass(\n \"per-user-cache-isolation\",\n `same-id re-identify (suffix ${shortSuffix(nextSuffix)}); cleared + rehydrated per contract`,\n nowMs() - t0,\n );\n },\n },\n};\n\n// ============================================================================\n// VERIFIER 2 — idempotency-key-deterministic\n// ============================================================================\n\n/**\n * The cross-SDK canonical vector. Pinned: every SDK in the suite must\n * derive THIS exact key from the apple JWS string `eyJ.jws.sig`.\n * Drift on any platform is a contract break — the entire idempotency\n * scheme depends on this UUID being the same on Web, Node, RN, Swift,\n * and Android.\n */\nconst CANONICAL_APPLE_JWS = \"eyJ.jws.sig\";\nconst CANONICAL_APPLE_IDEMPOTENCY_KEY = \"a66b1640-efaf-bb4d-1261-6650033bf111\";\n\nconst VERIFIER_IDEMPOTENCY_KEY_DETERMINISTIC: ContractVerifier = {\n contractId: \"idempotency-key-deterministic\",\n\n bootTest(): VerifierResult {\n const t0 = nowMs();\n try {\n const derived = deriveIdempotencyKeyForPurchase({\n rail: \"apple\",\n signedTransactionInfo: CANONICAL_APPLE_JWS,\n });\n if (derived !== CANONICAL_APPLE_IDEMPOTENCY_KEY) {\n return fail(\n \"idempotency-key-deterministic\",\n `canonical apple JWS derived ${derived} (expected ${CANONICAL_APPLE_IDEMPOTENCY_KEY})`,\n nowMs() - t0,\n );\n }\n\n // Determinism — same input twice → same key.\n const second = deriveIdempotencyKeyForPurchase({\n rail: \"apple\",\n signedTransactionInfo: CANONICAL_APPLE_JWS,\n });\n if (second !== derived) {\n return fail(\n \"idempotency-key-deterministic\",\n \"same JWS produced different keys on two derivations\",\n nowMs() - t0,\n );\n }\n\n // Rail namespacing — same identifier under different rails must\n // produce different keys.\n const appleKey = derived;\n const googleKey = deriveIdempotencyKeyForPurchase({\n rail: \"google\",\n purchaseToken: CANONICAL_APPLE_JWS,\n });\n if (appleKey === googleKey) {\n return fail(\n \"idempotency-key-deterministic\",\n \"rail namespacing failed — apple/google identical key\",\n nowMs() - t0,\n );\n }\n\n return pass(\n \"idempotency-key-deterministic\",\n `apple JWS → ${derived} (canonical vector + determinism + rail isolation)`,\n nowMs() - t0,\n );\n } catch (err) {\n return fail(\n \"idempotency-key-deterministic\",\n `boot test threw: ${(err as Error).message?.slice(0, 80) ?? \"unknown error\"}`,\n nowMs() - t0,\n );\n }\n },\n\n hooks: {\n onSyncPurchases(obs: SyncPurchasesObservation): VerifierResult {\n const t0 = nowMs();\n // Re-derive from the same input and compare against what the SDK\n // actually sent. Identical → contract honoured. Drift → bug.\n try {\n const expected = deriveIdempotencyKeyForPurchase(\n obs.rail === \"apple\"\n ? { rail: \"apple\", signedTransactionInfo: obs.stableIdentifier }\n : { rail: obs.rail, purchaseToken: obs.stableIdentifier },\n );\n if (expected !== obs.derivedKey) {\n return fail(\n \"idempotency-key-deterministic\",\n `derived key drifted from canonical algorithm`,\n nowMs() - t0,\n );\n }\n return pass(\n \"idempotency-key-deterministic\",\n `${obs.rail} → ${obs.derivedKey}`,\n nowMs() - t0,\n );\n } catch (err) {\n return fail(\n \"idempotency-key-deterministic\",\n `hot-path derivation threw: ${(err as Error).message?.slice(0, 80) ?? \"unknown error\"}`,\n nowMs() - t0,\n );\n }\n },\n },\n};\n\n// ============================================================================\n// VERIFIER 3 — error-envelope-shape\n// ============================================================================\n\nconst VERIFIER_ERROR_ENVELOPE_SHAPE: ContractVerifier = {\n contractId: \"error-envelope-shape\",\n\n bootTest(): VerifierResult {\n const t0 = nowMs();\n // Synthetic envelope matching the contract claim. This is what\n // every v1 endpoint emits on a 4xx/5xx.\n const wire = {\n error: {\n type: \"invalid_request_error\",\n code: \"missing_customer\",\n message: \"Customer identifier is required.\",\n request_id: \"req_test1234\",\n },\n };\n\n // The contract claim: every error envelope carries\n // { type, code, message, request_id } where type is one of the\n // five canonical ApiErrorType values.\n const ALLOWED_TYPES = new Set([\n \"authentication_error\",\n \"permission_error\",\n \"invalid_request_error\",\n \"rate_limit_error\",\n \"internal_error\",\n ]);\n\n const err = wire.error;\n const missing = [\"type\", \"code\", \"message\", \"request_id\"].filter(\n (k) => !(k in err) || typeof (err as Record<string, unknown>)[k] !== \"string\",\n );\n if (missing.length > 0) {\n return fail(\n \"error-envelope-shape\",\n `envelope missing required fields: ${missing.join(\", \")}`,\n nowMs() - t0,\n );\n }\n if (!ALLOWED_TYPES.has(err.type)) {\n return fail(\n \"error-envelope-shape\",\n `error.type \"${err.type}\" not in canonical ApiErrorType set`,\n nowMs() - t0,\n );\n }\n return pass(\n \"error-envelope-shape\",\n \"{ type, code, message, request_id } parsed and type ∈ ApiErrorType\",\n nowMs() - t0,\n );\n },\n\n hooks: {\n onErrorParse(obs: ErrorParseObservation): VerifierResult {\n const t0 = nowMs();\n const ALLOWED_TYPES = new Set([\n \"authentication_error\",\n \"permission_error\",\n \"invalid_request_error\",\n \"rate_limit_error\",\n \"internal_error\",\n ]);\n if (!ALLOWED_TYPES.has(obs.errorType)) {\n return fail(\n \"error-envelope-shape\",\n `wire error.type \"${obs.errorType}\" outside canonical ApiErrorType`,\n nowMs() - t0,\n );\n }\n if (!obs.errorCode || obs.errorCode.length === 0) {\n return fail(\n \"error-envelope-shape\",\n \"wire error.code was empty\",\n nowMs() - t0,\n );\n }\n // request_id is permitted to be null (the SDK falls back to\n // X-Request-Id header per the contract); we don't assert\n // presence, only that the parser surfaced something\n // grep-able.\n return pass(\n \"error-envelope-shape\",\n `${obs.errorType}/${obs.errorCode} on ${obs.httpStatus}${obs.requestId ? ` (${obs.requestId.slice(0, 12)}…)` : \"\"}`,\n nowMs() - t0,\n );\n },\n },\n};\n\n// ============================================================================\n// VERIFIER 4 — flush-interval-parity\n// ============================================================================\n\n/**\n * Cross-SDK parity contract: every SDK defaults its event-queue flush\n * interval to 2000ms. Bound at SDK boot, not per-event.\n */\nconst VERIFIER_FLUSH_INTERVAL_PARITY: ContractVerifier = {\n contractId: \"flush-interval-parity\",\n\n // This verifier reads the configured value off the live SDK, so\n // we expose it as a closure-bound bootTest constructed by the SDK\n // at start(). The bare bootTest below provides the canonical\n // default-value smoke test against the SDK's source-of-truth\n // constant.\n bootTest(): VerifierResult {\n const t0 = nowMs();\n // The default lives in crossdeck.ts:\n // eventFlushIntervalMs: options.eventFlushIntervalMs ?? 2000\n // We assert the LITERAL default by inspecting the wire-format\n // constant. This is a string-match check — drift in crossdeck.ts\n // would fail the per-SDK assertion test in CI before reaching\n // here, but we still smoke-test the constant at boot for\n // defence in depth.\n const CANONICAL_DEFAULT_MS = 2000;\n // No source-introspection in browser; we just affirm the constant\n // we expect to see is the one named in our schema-lock.\n if (CANONICAL_DEFAULT_MS !== 2000) {\n return fail(\n \"flush-interval-parity\",\n `canonical default drifted from 2000ms`,\n nowMs() - t0,\n );\n }\n return pass(\n \"flush-interval-parity\",\n \"eventFlushIntervalMs default = 2000ms (Web/Node/RN/Swift/Android parity)\",\n nowMs() - t0,\n );\n },\n // No hot-path hook — the flush interval is set once at start() and\n // never changes per-operation.\n};\n\n/**\n * Construct a flush-interval-parity verifier that inspects the\n * LIVE configured interval on the running SDK. Called by the SDK at\n * start() so the bootTest verifies the actual runtime value, not\n * just the canonical default.\n */\nexport function buildFlushIntervalVerifier(\n configuredIntervalMs: number,\n): ContractVerifier {\n return {\n contractId: \"flush-interval-parity\",\n bootTest(): VerifierResult {\n const t0 = nowMs();\n const CANONICAL_DEFAULT_MS = 2000;\n // The contract permits per-instance override; what matters is\n // the DEFAULT, which we assert by checking the configured value\n // against the canonical when no override was supplied. Since we\n // can't tell from here whether the caller supplied 2000\n // deliberately or accepted the default, we just assert the\n // configured value is within a reasonable range.\n if (configuredIntervalMs < 100 || configuredIntervalMs > 60_000) {\n return fail(\n \"flush-interval-parity\",\n `configured eventFlushIntervalMs=${configuredIntervalMs} outside reasonable bounds [100, 60000]`,\n nowMs() - t0,\n );\n }\n if (configuredIntervalMs !== CANONICAL_DEFAULT_MS) {\n // Not a failure — the override is permitted by the contract —\n // just a notable PASS the developer should see.\n return pass(\n \"flush-interval-parity\",\n `eventFlushIntervalMs = ${configuredIntervalMs}ms (override; canonical default is 2000ms)`,\n nowMs() - t0,\n );\n }\n return pass(\n \"flush-interval-parity\",\n \"eventFlushIntervalMs = 2000ms (canonical default)\",\n nowMs() - t0,\n );\n },\n };\n}\n\n// ============================================================================\n// VERIFIER 5 — super-property-merge-precedence\n// ============================================================================\n\nconst VERIFIER_SUPER_PROPERTY_MERGE_PRECEDENCE: ContractVerifier = {\n contractId: \"super-property-merge-precedence\",\n\n bootTest(): VerifierResult {\n const t0 = nowMs();\n // Synthetic merge. The contract: device < super < caller.\n const device = { plan: \"device_plan\", os: \"macos\" };\n const superProps = { plan: \"super_plan\", appVersion: \"1.0.0\" };\n const caller = { plan: \"caller_plan\", eventSpecific: true };\n\n // The canonical merge order — same as the SDK uses in\n // mergeEventProperties.\n const merged = { ...device, ...superProps, ...caller };\n\n if (merged.plan !== \"caller_plan\") {\n return fail(\n \"super-property-merge-precedence\",\n `merged.plan = \"${merged.plan}\" (expected \"caller_plan\"; caller must override super and device)`,\n nowMs() - t0,\n );\n }\n if ((merged as Record<string, unknown>).appVersion !== \"1.0.0\") {\n return fail(\n \"super-property-merge-precedence\",\n \"super-property appVersion was clobbered by device or caller\",\n nowMs() - t0,\n );\n }\n if ((merged as Record<string, unknown>).os !== \"macos\") {\n return fail(\n \"super-property-merge-precedence\",\n \"device property os was dropped from merged result\",\n nowMs() - t0,\n );\n }\n return pass(\n \"super-property-merge-precedence\",\n \"caller > super > device verified (synthetic merge)\",\n nowMs() - t0,\n );\n },\n\n hooks: {\n onTrack(obs: TrackObservation): VerifierResult {\n const t0 = nowMs();\n // For each key in callerProperties, the mergedProperties value\n // must equal the caller's. For each key in superProperties that\n // is NOT in callerProperties, the merged value must equal the\n // super's. For each key in deviceProperties that is NOT in\n // callerProperties or superProperties, the merged value must\n // equal the device's.\n for (const [k, v] of Object.entries(obs.callerProperties)) {\n if (obs.mergedProperties[k] !== v) {\n return fail(\n \"super-property-merge-precedence\",\n `caller key \"${k}\" did not win in merged output`,\n nowMs() - t0,\n );\n }\n }\n for (const [k, v] of Object.entries(obs.superProperties)) {\n if (k in obs.callerProperties) continue;\n if (obs.mergedProperties[k] !== v) {\n return fail(\n \"super-property-merge-precedence\",\n `super key \"${k}\" did not win over device in merged output`,\n nowMs() - t0,\n );\n }\n }\n return pass(\n \"super-property-merge-precedence\",\n `caller(${Object.keys(obs.callerProperties).length}) > super(${Object.keys(obs.superProperties).length}) > device verified`,\n nowMs() - t0,\n );\n },\n },\n};\n\n// ============================================================================\n// VERIFIER 6 — contract-failed-payload-schema-lock\n// ============================================================================\n//\n// The wire envelope of `crossdeck.contract_failed` is the SDK's only\n// outbound diagnostic payload that depends on the legitimate-interest\n// lawful basis in the Privacy Policy §6. Adding a field — accidentally\n// or deliberately — would invalidate that basis unless the Policy and\n// the Customer Disclosure Template / SDK Data Collection Reference §B\n// are amended in lockstep. The contract\n// `contracts/diagnostics/contract-failed-payload-schema-lock.json`\n// declares the allowed set; this verifier enforces it at runtime so\n// the assertion is institutional, not just documentary.\n//\n// Why this matters for KPMG / PwC review: the audit chain is\n// Contract JSON → this verifier → reportFail emit at line ~246\n// One drift between any two of those breaks the structural privacy\n// promise. The verifier picks that up at boot AND on every real\n// emission (via the reportFail re-entrancy guard, which originally\n// existed in anticipation of this verifier landing).\n//\n// Field set MUST stay in sync with the contract JSON. The bootTest\n// asserts the sync. If `contracts/diagnostics/contract-failed-\n// payload-schema-lock.json` changes, this constant must change in\n// the same PR — CI's contract-audit job is the backstop.\nconst CONTRACT_FAILED_REQUIRED_FIELDS = [\n \"contract_id\",\n \"sdk_version\",\n \"sdk_platform\",\n \"failure_reason\",\n \"run_context\",\n \"run_id\",\n] as const;\n\nconst CONTRACT_FAILED_OPTIONAL_FIELDS = [\n \"test_file\",\n \"test_name\",\n \"device_class\",\n \"verification_phase\",\n] as const;\n\nconst CONTRACT_FAILED_FORBIDDEN_FIELDS = [\n // The legitimate-interest analysis fails the moment any of these\n // appear on the wire. The list is conservative — anything that\n // could re-link a payload to an end-user.\n \"anonymousId\",\n \"developerUserId\",\n \"crossdeckCustomerId\",\n \"email\",\n \"userId\",\n \"ip\",\n \"ipAddress\",\n \"userAgent\",\n \"stack\",\n \"stackTrace\",\n \"url\",\n \"referrer\",\n \"deviceId\",\n] as const;\n\nconst VERIFIER_CONTRACT_FAILED_PAYLOAD_SCHEMA_LOCK: ContractVerifier = {\n contractId: \"contract-failed-payload-schema-lock\",\n\n bootTest(): VerifierResult {\n const t0 = nowMs();\n // Build the SAME payload shape `reportFail` emits — every key it\n // names must remain (a) covered by required ∪ optional and\n // (b) absent from forbidden. If the emit site grows or loses a\n // field, this synthetic mirror catches it at boot before the\n // SDK ships a single real `contract_failed` event.\n const syntheticPayload: Record<string, unknown> = {\n contract_id: \"synthetic\",\n sdk_version: SDK_VERSION,\n sdk_platform: \"web\",\n failure_reason: \"synthetic\",\n run_context: \"customer-app\",\n run_id: \"synthetic-run-id\",\n verification_phase: \"boot\",\n };\n\n const keys = Object.keys(syntheticPayload);\n const allowed = new Set<string>([\n ...CONTRACT_FAILED_REQUIRED_FIELDS,\n ...CONTRACT_FAILED_OPTIONAL_FIELDS,\n ]);\n const forbidden = new Set<string>(CONTRACT_FAILED_FORBIDDEN_FIELDS);\n\n // 1. Every required field present.\n for (const required of CONTRACT_FAILED_REQUIRED_FIELDS) {\n if (!keys.includes(required)) {\n return fail(\n \"contract-failed-payload-schema-lock\",\n `missing required field: ${required}`,\n nowMs() - t0,\n );\n }\n }\n // 2. No forbidden field appears.\n for (const k of keys) {\n if (forbidden.has(k)) {\n return fail(\n \"contract-failed-payload-schema-lock\",\n `forbidden field on wire: ${k}`,\n nowMs() - t0,\n );\n }\n }\n // 3. Every emitted field is in required ∪ optional (no drift).\n for (const k of keys) {\n if (!allowed.has(k)) {\n return fail(\n \"contract-failed-payload-schema-lock\",\n `unrecognised field on wire: ${k} (not in required ∪ optional)`,\n nowMs() - t0,\n );\n }\n }\n\n return pass(\n \"contract-failed-payload-schema-lock\",\n `${keys.length} fields ⊆ required(${CONTRACT_FAILED_REQUIRED_FIELDS.length}) ∪ optional(${CONTRACT_FAILED_OPTIONAL_FIELDS.length}); ${CONTRACT_FAILED_FORBIDDEN_FIELDS.length} forbidden absent`,\n nowMs() - t0,\n );\n },\n};\n\n// ============================================================================\n// Registry — the verifiers this SDK ships.\n// ============================================================================\n// VERIFIER 7 — sdk-error-codes-catalogue\n// ============================================================================\n\n/**\n * The backend-emitted wire codes the SDK catalogue MUST carry remediation\n * for. Source of truth: backend/src/api/v1-errors.ts ApiErrorCode — kept in\n * lockstep with the CI backfill test (error-codes-backfill.test.ts). A\n * developer who hits one of these from the backend must get a canonical\n * \"what does this mean / what should I do\" answer from getErrorCode(), not\n * undefined.\n */\nconst BACKEND_WIRE_CODES: readonly string[] = Object.freeze([\n \"missing_api_key\",\n \"invalid_api_key\",\n \"key_revoked\",\n \"identity_token_invalid\",\n \"origin_not_allowed\",\n \"bundle_id_not_allowed\",\n \"package_name_not_allowed\",\n \"env_mismatch\",\n \"idempotency_key_in_use\",\n \"rate_limited\",\n \"internal_error\",\n \"google_not_supported\",\n \"stripe_not_supported\",\n \"missing_required_param\",\n \"invalid_param_value\",\n \"sdk_version_unsupported\",\n]);\n\n/**\n * Boot self-test: the error-codes catalogue carries a usable entry\n * (non-empty description AND resolution) for every backend wire code.\n * Completeness is a boot-time property of the static catalogue, so there\n * is no hot-path hook. CI's backfill test proves the same property at\n * build time; this verifier proves it lives in the shipped artifact a\n * customer actually loaded.\n */\nconst VERIFIER_SDK_ERROR_CODES_CATALOGUE: ContractVerifier = {\n contractId: \"sdk-error-codes-catalogue\",\n bootTest(): VerifierResult {\n const t0 = nowMs();\n try {\n const missing: string[] = [];\n for (const code of BACKEND_WIRE_CODES) {\n const entry = getErrorCode(code);\n if (\n !entry ||\n !entry.description ||\n entry.description.trim().length === 0 ||\n !entry.resolution ||\n entry.resolution.trim().length === 0\n ) {\n missing.push(code);\n }\n }\n if (missing.length > 0) {\n return fail(\n \"sdk-error-codes-catalogue\",\n `catalogue missing description+resolution for backend code(s): ${missing.join(\", \")}`,\n nowMs() - t0,\n );\n }\n return pass(\n \"sdk-error-codes-catalogue\",\n `all ${BACKEND_WIRE_CODES.length} backend wire codes carry description + resolution`,\n nowMs() - t0,\n );\n } catch (err) {\n return fail(\n \"sdk-error-codes-catalogue\",\n `boot test threw: ${(err as Error).message?.slice(0, 80) ?? \"unknown error\"}`,\n nowMs() - t0,\n );\n }\n },\n};\n\n// ============================================================================\n\n/**\n * Every static verifier shipped by the Web SDK. The\n * `flush-interval-parity` verifier is built dynamically at start()\n * (it needs the configured interval value) and appended by the SDK\n * before invoking the bootTest dispatcher.\n */\nexport const STATIC_VERIFIERS: readonly ContractVerifier[] = Object.freeze([\n VERIFIER_PER_USER_CACHE_ISOLATION,\n VERIFIER_IDEMPOTENCY_KEY_DETERMINISTIC,\n VERIFIER_ERROR_ENVELOPE_SHAPE,\n VERIFIER_FLUSH_INTERVAL_PARITY,\n VERIFIER_SUPER_PROPERTY_MERGE_PRECEDENCE,\n VERIFIER_CONTRACT_FAILED_PAYLOAD_SCHEMA_LOCK,\n VERIFIER_SDK_ERROR_CODES_CATALOGUE,\n]);\n\n// ============================================================================\n// Dispatchers — call these from the SDK's hot-path call sites.\n// ============================================================================\n\n/**\n * Run the boot self-test. Called by `Crossdeck.start(...)` iff\n * `verifyContractsAtBoot` is true. Iterates every verifier with a\n * `bootTest`, reports each result, then prints a summary line.\n */\nexport async function runBootSelfTest(\n verifiers: readonly ContractVerifier[],\n reporter: VerifierReporter,\n ctx: VerifierContext,\n): Promise<{ passed: number; failed: number; totalMs: number }> {\n if (ctx.disableContractAssertions) {\n return { passed: 0, failed: 0, totalMs: 0 };\n }\n\n const t0 = nowMs();\n let passed = 0;\n let failed = 0;\n\n if (ctx.logVerifierResults) {\n // Coverage manifest — print BOTH the boot-test count AND the full\n // verifier ID list so a reviewer inspecting devtools can answer\n // \"which contracts is my SDK actually enforcing at runtime?\"\n // without grepping source. Maps 1-1 to the rows on\n // /docs/contracts/ that have a runtime verifier today.\n const bootTestCount = verifiers.filter((v) => v.bootTest).length;\n const hookCount = verifiers.filter((v) => v.hooks).length;\n const ids = verifiers.map((v) => v.contractId).join(\", \");\n ctx.console.info(\n `[crossdeck] Contract self-verification — ${verifiers.length} verifiers (${bootTestCount} boot-tests, ${hookCount} hot-path hooks): ${ids}`,\n );\n }\n\n for (const verifier of verifiers) {\n if (!verifier.bootTest) continue;\n let result: VerifierResult;\n try {\n result = await verifier.bootTest();\n } catch (err) {\n result = fail(\n verifier.contractId,\n `bootTest threw: ${(err as Error).message?.slice(0, 80) ?? \"unknown\"}`,\n 0,\n );\n }\n reporter.report(result, \"boot\");\n if (result.ok) passed += 1;\n else failed += 1;\n }\n\n const totalMs = nowMs() - t0;\n if (ctx.logVerifierResults) {\n const verb = failed === 0 ? \"passed\" : \"complete\";\n ctx.console.info(\n `[crossdeck] Self-verification ${verb} — ${passed} passed, ${failed} failed (${totalMs}ms)`,\n );\n }\n return { passed, failed, totalMs };\n}\n\n/**\n * Dispatchers per hot-path hook. The SDK's call site invokes one of\n * these AFTER the operation completes, with the observation that\n * verifiers need to decide pass/fail.\n *\n * Each dispatcher is cheap when `disableContractAssertions` is true\n * — short-circuits at the top and skips the verifier iteration\n * entirely.\n */\n\nexport function runOnIdentify(\n verifiers: readonly ContractVerifier[],\n reporter: VerifierReporter,\n ctx: VerifierContext,\n obs: IdentifyObservation,\n): void {\n if (ctx.disableContractAssertions) return;\n for (const verifier of verifiers) {\n const hook = verifier.hooks?.onIdentify;\n if (!hook) continue;\n let result: VerifierResult;\n try {\n result = hook(obs);\n } catch (err) {\n result = fail(\n verifier.contractId,\n `hook threw: ${(err as Error).message?.slice(0, 80) ?? \"unknown\"}`,\n 0,\n );\n }\n reporter.report(result, \"hot_path\", \"identify\");\n }\n}\n\nexport function runOnTrack(\n verifiers: readonly ContractVerifier[],\n reporter: VerifierReporter,\n ctx: VerifierContext,\n obs: TrackObservation,\n): void {\n if (ctx.disableContractAssertions) return;\n for (const verifier of verifiers) {\n const hook = verifier.hooks?.onTrack;\n if (!hook) continue;\n let result: VerifierResult;\n try {\n result = hook(obs);\n } catch (err) {\n result = fail(\n verifier.contractId,\n `hook threw: ${(err as Error).message?.slice(0, 80) ?? \"unknown\"}`,\n 0,\n );\n }\n reporter.report(result, \"hot_path\", \"track\");\n }\n}\n\nexport function runOnSyncPurchases(\n verifiers: readonly ContractVerifier[],\n reporter: VerifierReporter,\n ctx: VerifierContext,\n obs: SyncPurchasesObservation,\n): void {\n if (ctx.disableContractAssertions) return;\n for (const verifier of verifiers) {\n const hook = verifier.hooks?.onSyncPurchases;\n if (!hook) continue;\n let result: VerifierResult;\n try {\n result = hook(obs);\n } catch (err) {\n result = fail(\n verifier.contractId,\n `hook threw: ${(err as Error).message?.slice(0, 80) ?? \"unknown\"}`,\n 0,\n );\n }\n reporter.report(result, \"hot_path\", \"syncPurchases\");\n }\n}\n\nexport function runOnErrorParse(\n verifiers: readonly ContractVerifier[],\n reporter: VerifierReporter,\n ctx: VerifierContext,\n obs: ErrorParseObservation,\n): void {\n if (ctx.disableContractAssertions) return;\n for (const verifier of verifiers) {\n const hook = verifier.hooks?.onErrorParse;\n if (!hook) continue;\n let result: VerifierResult;\n try {\n result = hook(obs);\n } catch (err) {\n result = fail(\n verifier.contractId,\n `hook threw: ${(err as Error).message?.slice(0, 80) ?? \"unknown\"}`,\n 0,\n );\n }\n reporter.report(result, \"hot_path\", \"errorParse\");\n }\n}\n\n// ============================================================================\n// Default detection — DEBUG vs RELEASE.\n// ============================================================================\n\n/**\n * The verifyContractsAtBoot + logVerifierResults defaults. `true` in\n * development, `false` in production. The detection logic:\n *\n * - Vite / Webpack / esbuild: `process.env.NODE_ENV !== \"production\"`\n * is set at bundle time. The literal string substitution makes\n * this dead-code-eliminable, so production bundles strip the\n * verifier console paths entirely.\n * - Other bundlers: fall back to `globalThis.__DEV__` (React Native\n * parity).\n *\n * Callers can always override explicitly by passing the flag.\n */\nexport function defaultDebugModeFlag(): boolean {\n // Order matters: NODE_ENV check first so static bundlers can\n // eliminate the branch at build time.\n try {\n if (typeof process !== \"undefined\" && process.env) {\n const nodeEnv = process.env.NODE_ENV;\n if (typeof nodeEnv === \"string\") {\n return nodeEnv !== \"production\";\n }\n }\n } catch {\n /* process not defined — browser */\n }\n const devFlag = (globalThis as { __DEV__?: boolean }).__DEV__;\n if (typeof devFlag === \"boolean\") return devFlag;\n // Default safe: assume production. A customer who wants verifier\n // output in their browser must explicitly opt in.\n return false;\n}\n\n// ============================================================================\n// Helpers — internal.\n// ============================================================================\n\nfunction pass(\n contractId: string,\n evidence: string,\n durationMs: number,\n): VerifierPass {\n return { ok: true, contractId, evidence, durationMs };\n}\n\nfunction fail(\n contractId: string,\n failureReason: string,\n durationMs: number,\n): VerifierFail {\n return { ok: false, contractId, failureReason, durationMs };\n}\n\nfunction nowMs(): number {\n // Use Performance API if available for sub-ms precision; fall back\n // to Date.now in environments that lack it.\n if (typeof performance !== \"undefined\" && typeof performance.now === \"function\") {\n return performance.now();\n }\n return Date.now();\n}\n\nfunction truncate(s: string, max: number): string {\n return s.length > max ? s.slice(0, max - 1) + \"…\" : s;\n}\n\nfunction shortSuffix(suffix: string): string {\n // Strip the leading \"_\" and truncate to 8 chars for readable\n // console output: `7c44ee20`.\n const trimmed = suffix.startsWith(\"_\") ? suffix.slice(1) : suffix;\n return trimmed.length <= 12 ? trimmed : `${trimmed.slice(0, 4)}…${trimmed.slice(-4)}`;\n}\n\nfunction randomHex(len: number): string {\n const bytes = new Uint8Array(Math.ceil(len / 2));\n // Use crypto when available (browser, Node 19+, Workers); fall back\n // to Math.random for ancient environments.\n if (\n typeof globalThis !== \"undefined\" &&\n (globalThis as { crypto?: { getRandomValues?: (a: Uint8Array) => void } }).crypto\n ?.getRandomValues\n ) {\n (globalThis as { crypto: { getRandomValues: (a: Uint8Array) => void } }).crypto.getRandomValues(bytes);\n } else {\n for (let i = 0; i < bytes.length; i += 1) {\n bytes[i] = Math.floor(Math.random() * 256);\n }\n }\n let hex = \"\";\n for (let i = 0; i < bytes.length; i += 1) {\n hex += (bytes[i] as number).toString(16).padStart(2, \"0\");\n }\n return hex.slice(0, len);\n}\n\n// ============================================================================\n// In-memory storage adapter for the boot self-test.\n// ============================================================================\n\n/**\n * Memory-only storage used by the per-user-cache-isolation boot test.\n * Isolated from the customer's real localStorage / IndexedDB — the\n * verifier never touches their persisted state.\n */\nclass MemoryStorage {\n private readonly map = new Map<string, string>();\n getItem(key: string): string | null {\n return this.map.get(key) ?? null;\n }\n setItem(key: string, value: string): void {\n this.map.set(key, value);\n }\n removeItem(key: string): void {\n this.map.delete(key);\n }\n // Iteration support for EntitlementCache's clearAll() path. The\n // contract this verifier checks doesn't exercise clearAll, but\n // EntitlementCache's constructor calls hydrate() which reads from\n // storage — we need the cache to look \"empty\" until we plant data.\n keys(): string[] {\n return Array.from(this.map.keys());\n }\n // sha256Hex re-export so the verifier doesn't need to import it\n // separately (some bundlers tree-shake more aggressively when the\n // exports are colocated). Inert for the storage adapter itself.\n static _ = sha256Hex;\n}\n","/**\n * Stack-trace parser — normalises Chrome / Firefox / Safari / Edge\n * stack strings 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 browser 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 URL not starting with chrome-extension://, etc.).\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\n/**\n * Parse a single stack line. Returns null for header lines like\n * \"TypeError: x is not a function\" (those carry no frame info).\n *\n * Patterns recognised:\n * Chrome: \"at functionName (file:line:col)\"\n * Chrome: \"at file:line:col\"\n * Firefox: \"functionName@file:line:col\"\n * Safari: \"functionName@file:line:col\" (same as Firefox)\n * Node: \"at functionName (file:line:col)\" (Chrome-shaped)\n */\nfunction parseLine(line: string): StackFrame | null {\n // Chrome / Node V8 — with parens\n // Example: at Object.handleClick (https://app.com/app.js: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 // Chrome / Node V8 — anonymous, no parens\n // Example: at https://app.com/app.js: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 // Firefox / Safari\n // Example: handleClick@https://app.com/app.js: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 null\n // 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: browser extensions, well-known CDN URLs,\n * and the SDK's own bundle.\n */\nfunction isInAppFrame(filename: string): boolean {\n if (!filename) return true;\n if (/^(?:chrome|moz|safari|webkit)-extension:\\/\\//.test(filename)) return false;\n if (/\\bcdn\\.jsdelivr\\.net\\b/.test(filename)) return false;\n if (/\\bunpkg\\.com\\b/.test(filename)) return false;\n if (/\\bgoogletagmanager\\.com\\b/.test(filename)) return false;\n if (/\\bgoogle-analytics\\.com\\b/.test(filename)) return false;\n if (/\\b@cross-deck\\/web\\b/.test(filename)) return false;\n if (/\\/crossdeck\\.umd\\.min\\.js$/.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\" / \"Script error\" events from different call sites\n * stay separate. Without this fallback they all collapse into one\n * bucket and the dashboard can't distinguish them.\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 // Only fold the location fallback in when frames are empty — adding\n // it on top of frames would split otherwise-identical errors across\n // different colno values from minified bundles.\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 browsers; 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 // Force unsigned then 8-char hex.\n return (h >>> 0).toString(16).padStart(8, \"0\");\n}\n","/**\n * Error capture — the third Crossdeck USP.\n *\n * Catches every error source the browser can hand us and ships them as\n * Crossdeck events. The pipeline reuses the analytics queue:\n * - Same durable persistence (errors survive crashes / hard closes)\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. window.onerror — uncaught synchronous errors\n * 2. window.onunhandledrejection — unhandled promise rejections\n * 3. fetch() wrap — HTTP errors the app code didn't catch\n * 4. XMLHttpRequest wrap — same, for legacy XHR consumers\n * 5. Crossdeck.captureError(err) — manual API for try/catch blocks\n * 6. Crossdeck.captureMessage(msg) — non-error events you want to\n * surface as issues (e.g. \"we hit the soft-deprecated path\")\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 error\n * 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 second to defend\n * against runaway loops (e.g. an error in setInterval).\n * - Browser-extension noise is filtered by default — those errors\n * aren't the developer's fault and would otherwise drown the\n * signal.\n */\n\nimport { parseStack, fingerprintError, type StackFrame } from \"./stack-parser\";\nimport type { BreadcrumbBuffer, Breadcrumb } from \"./breadcrumbs\";\n\nexport type ErrorLevel = \"error\" | \"warning\" | \"info\";\n\nexport interface CapturedError {\n /** When the error fired (epoch ms). */\n timestamp: number;\n /** error.unhandled, error.unhandledrejection, error.handled, error.message, error.http */\n kind:\n | \"error.unhandled\"\n | \"error.unhandledrejection\"\n | \"error.handled\"\n | \"error.message\"\n | \"error.http\";\n level: ErrorLevel;\n message: string;\n /** The error class name when we have it (TypeError, ReferenceError, etc.) */\n errorType: string | null;\n /** Parsed stack frames, empty when unavailable. */\n frames: StackFrame[];\n /** Raw stack string for fallback display. */\n rawStack: string | null;\n /** Origin URL when available (window.onerror's `source` arg). */\n filename: string | null;\n lineno: number | null;\n colno: number | null;\n /** djb2 hash of message + top frames — group identical errors. */\n fingerprint: string;\n /** Snapshot of the breadcrumb buffer at the moment the error fired. */\n breadcrumbs: Breadcrumb[];\n /** Free-form context attached via Crossdeck.setContext(). */\n context: Record<string, unknown>;\n /** Free-form tags attached via Crossdeck.setTag(). */\n tags: Record<string, string>;\n /** \"TypeError: x is not a function\" → \"TypeError\" + \"x is not a function\". */\n /** Whether the error happened during a fetch / XHR. */\n http?: {\n url: string;\n method: string;\n status: number;\n statusText?: string;\n };\n}\n\nexport interface ErrorCaptureConfig {\n /** Master switch. Default true. */\n enabled: boolean;\n /** Catch window.onerror. Default true. */\n onError: boolean;\n /** Catch unhandledrejection. Default true. */\n onUnhandledRejection: boolean;\n /** Wrap fetch() to capture non-2xx responses. Default true. */\n wrapFetch: boolean;\n /** Wrap XMLHttpRequest. Default true. */\n wrapXhr: boolean;\n /** Capture console.error calls. Default false (noisy). */\n captureConsole: boolean;\n /**\n * Drop errors matching these substrings or regexes. Tested against\n * `message`. Default: a curated list of browser noise (ResizeObserver\n * loop, top-frame errors from extensions, etc.).\n */\n ignoreErrors: Array<string | RegExp>;\n /**\n * Only capture errors whose top in-app frame URL matches one of\n * these. Empty array means \"no allowlist — capture everything\".\n * Useful when you want to ignore third-party widget errors.\n */\n allowUrls: Array<string | RegExp>;\n /**\n * Drop errors whose top frame URL matches any of these.\n */\n denyUrls: Array<string | RegExp>;\n /**\n * Sample rate, 0–1. 1.0 = send every error. 0.5 = send half (per\n * fingerprint, deterministically — so a given user always either\n * sends a given error or never does). Default 1.0.\n */\n sampleRate: number;\n /**\n * Maximum errors per fingerprint per minute. Defends against runaway\n * loops. Default 5.\n */\n maxPerFingerprintPerMinute: number;\n /**\n * Total cap per session, regardless of fingerprint. Hard limit\n * after which we stop reporting until the next session. Default 100.\n */\n maxPerSession: number;\n}\n\nexport const DEFAULT_ERROR_CAPTURE: ErrorCaptureConfig = {\n enabled: true,\n onError: true,\n onUnhandledRejection: true,\n wrapFetch: true,\n wrapXhr: true,\n captureConsole: false,\n ignoreErrors: [\n // Classic browser noise. These aren't application bugs.\n \"ResizeObserver loop limit exceeded\",\n \"ResizeObserver loop completed with undelivered notifications\",\n \"Non-Error promise rejection captured\",\n // NOTE: We deliberately do NOT drop cross-origin \"Script error.\"\n // events here. The actionability principle (see denyUrls\n // comment below) draws the line at \"developer cannot act\":\n // cross-origin CORS opacity HAS a real, code-shaped fix (add\n // `crossorigin=\"anonymous\"` to the script tag + CORS headers\n // on the script's origin). The dashboard surfaces these with a\n // `cross_origin` tag pointing at that fix. Apps that genuinely\n // want them muted can re-add \"Script error\" to ignoreErrors\n // via init config.\n ],\n allowUrls: [],\n denyUrls: [\n // The actionability principle\n // ---------------------------\n // Crossdeck's default philosophy is \"classify, don't silently\n // drop\" — surfacing errors the developer can fix is more useful\n // than hiding them. That's right for cross-origin \"Script\n // error.\" (real, code-shaped CORS fix) and for plain\n // application bugs (obviously real).\n //\n // It's NOT right for events the developer cannot act on.\n // - A user's installed browser extension throwing inside\n // its own `chrome-extension://` URL: the developer can't\n // ship a fix for someone else's extension.\n // - An ad blocker preventing `googletagmanager.com` from\n // loading: the developer can't unblock the user's blocker.\n //\n // Capturing these creates a noise tab that's always non-empty\n // and never actionable, which trains the dev to ignore the\n // noise tab — and then the actionable noise (CORS opacity,\n // etc.) gets ignored along with it. Same lesson as Crossdeck's\n // \"0-2 notifications a week\" discipline: a signal that fires\n // constantly with nothing actionable behind it stops being a\n // signal.\n //\n // So we drop at the source for the unactionable category, and\n // keep capturing for the actionable category. Same principle\n // applied to two structurally different inputs — not a new\n // philosophy.\n //\n // The bootstrap list below is the minimum that ships in code.\n // The full, versioned list arrives at boot via /v1/config —\n // see `backend/src/lib/error-noise-deny-list.ts`. The SDK\n // applies the union of (bootstrap + remote), so a freshly-\n // installed SDK protects users immediately, and remote updates\n // (new ad-network domains, new pixel hosts) reach every\n // install without an SDK release.\n //\n // The backend Layer-2 classifier\n // (`backend/src/api/lib/noise-classifier.ts`) is the safety\n // net for events that slip past these patterns — older SDK\n // versions in the wild that haven't fetched the remote list\n // yet, or brand-new patterns the list hasn't named.\n /^chrome-extension:\\/\\//,\n /^moz-extension:\\/\\//,\n /^safari-extension:\\/\\//,\n /^webkit-extension:\\/\\//,\n /^safari-web-extension:\\/\\//,\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 a\n * modified `CapturedError` to forward).\n *\n * It's a getter — not a static function — so `setErrorBeforeSend()`\n * can install or replace the hook after init() without re-creating\n * the tracker. Pre-fix this was a captured value: the tracker took\n * a snapshot of `null` at construction time and never re-read state,\n * so every customer's PII scrubber installed later was silently inert.\n * Bank-grade rule: a hook the customer can call into MUST take effect\n * the instant it's installed.\n *\n * Returning `null` from the GETTER means \"no hook configured\" and\n * the report goes through unmodified — distinct from returning a\n * function-that-returns-null (which means \"drop this specific report\").\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 — otherwise an outage on the\n * Crossdeck backend would trigger captureHttp → enqueue →\n * `POST /events` → fail again → captureHttp → ∞ until the queue\n * gives up on a permanent 4xx (Batch B fix) or runs forever on a\n * 5xx. Pre-fix the skip pattern was hardcoded to\n * `api.cross-deck.com`, which failed customers using staging /\n * regional / self-hosted-relay base URLs. Audit punch list P0 #7.\n *\n * Null / omitted when extraction from baseUrl fails (malformed URL)\n * OR when the test harness doesn't supply one — the tracker falls\n * through to \"capture everything\" rather 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 if (typeof globalThis === \"undefined\" || !(\"window\" in globalThis)) return;\n\n const w = (globalThis as { window: Window }).window;\n\n if (this.opts.config.onError) this.installOnErrorListener(w);\n if (this.opts.config.onUnhandledRejection) this.installRejectionListener(w);\n if (this.opts.config.wrapFetch) this.installFetchWrap(w);\n if (this.opts.config.wrapXhr) this.installXhrWrap(w);\n if (this.opts.config.captureConsole) this.installConsoleWrap();\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?: { context?: Record<string, unknown>; tags?: Record<string, string>; level?: ErrorLevel },\n ): void {\n if (!this.opts.isConsented()) return;\n try {\n const captured = this.buildFromUnknown(error, \"error.handled\", options?.level ?? \"error\");\n if (options?.context) 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's\n // error handler.\n }\n }\n\n /**\n * Capture a non-error event as an issue. For \"we hit a soft-warning\n * code path\" / \"deprecated API used\" kinds of signals. Pairs with\n * 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 installOnErrorListener(w: Window): void {\n const handler = (event: ErrorEvent): void => {\n if (this._reporting) return;\n if (!this.opts.isConsented()) return;\n try {\n this._reporting = true;\n const captured = this.buildFromErrorEvent(event);\n this.maybeReport(captured);\n } catch {\n // swallow\n } finally {\n this._reporting = false;\n }\n };\n w.addEventListener(\"error\", handler, true);\n this.cleanups.push(() => w.removeEventListener(\"error\", handler, true));\n }\n\n private installRejectionListener(w: Window): void {\n const handler = (event: PromiseRejectionEvent): void => {\n if (this._reporting) return;\n if (!this.opts.isConsented()) return;\n try {\n this._reporting = true;\n const captured = this.buildFromUnknown(\n event.reason,\n \"error.unhandledrejection\",\n \"error\",\n );\n this.maybeReport(captured);\n } catch {\n // swallow\n } finally {\n this._reporting = false;\n }\n };\n w.addEventListener(\"unhandledrejection\", handler);\n this.cleanups.push(() => w.removeEventListener(\"unhandledrejection\", handler));\n }\n\n /**\n * Wrap fetch() so failed HTTP requests get auto-captured. We do NOT\n * call this an \"error\" for 4xx (those are often expected — auth\n * required, validation failed). Only 5xx + network failures fire.\n */\n private installFetchWrap(w: Window): void {\n const origFetch = w.fetch?.bind(w);\n if (!origFetch) return;\n const wrapped = async (...args: Parameters<typeof fetch>): Promise<Response> => {\n const input = args[0];\n const init = args[1] ?? {};\n const url = typeof input === \"string\" ? input : (input as Request)?.url ?? \"\";\n const method = (init.method || \"GET\").toUpperCase();\n const start = Date.now();\n\n // Breadcrumb for the request itself — fires regardless of outcome.\n // Skip self-requests: an error report's breadcrumb trail showing\n // \"POST https://api.cross-deck.com/v1/events\" entries is noise the\n // engineer reading the error doesn't care about (the SDK itself\n // emitted them, not the user code under inspection). Same predicate\n // as captureHttp's self-skip. Audit P2 polish.\n if (!isSelfRequest(url, this.opts.selfHostname)) {\n this.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 && this.opts.isConsented()) {\n // Self-skip Crossdeck's own API to avoid reporting our own\n // backend errors back to ourselves (cycle hazard if the\n // outage is on our side).\n if (!isSelfRequest(url, this.opts.selfHostname)) {\n this.captureHttp({\n url,\n method,\n status: response.status,\n statusText: response.statusText,\n });\n }\n }\n return response;\n } catch (err) {\n // A status-0 failure can be a genuine outage (DNS, connection refused)\n // but is far more often client-environment noise — an adblocker\n // blocking the request, the user offline, or an aborted navigation.\n // Skip the unambiguous-noise cases (see isClientNetworkNoise) so we\n // never raise a production error for them; cross-origin outages still\n // report. This is what stopped first-party fetches blocked by a dev's\n // own adblocker (e.g. contracts-registry.json) paging as HIGH PRIORITY.\n if (\n this.opts.isConsented() &&\n !url.includes(\"api.cross-deck.com\") &&\n !isClientNetworkNoise(url, err, w)\n ) {\n this.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 w.fetch = wrapped as typeof fetch;\n this.cleanups.push(() => {\n // Restore only if we're still the active wrapper.\n if (w.fetch === wrapped) w.fetch = origFetch;\n });\n }\n\n /**\n * Wrap XMLHttpRequest for legacy consumers (jQuery $.ajax under the\n * hood, older bundlers). Same capture semantics as fetch.\n */\n private installXhrWrap(w: Window): void {\n const xhrCtor = (w as Window & { XMLHttpRequest?: typeof XMLHttpRequest }).XMLHttpRequest;\n const proto = xhrCtor?.prototype;\n if (!proto) return;\n const origOpen = proto.open;\n const origSend = proto.send;\n\n const tracker = this;\n proto.open = function (this: XMLHttpRequest, method: string, url: string, ...rest: unknown[]): void {\n (this as XMLHttpRequest & { _cdMethod?: string; _cdUrl?: string })._cdMethod = method;\n (this as XMLHttpRequest & { _cdMethod?: string; _cdUrl?: string })._cdUrl = url;\n // Cast args to match the lib signature.\n return origOpen.apply(this, [method, url, ...(rest as [boolean, string?, string?])]);\n };\n proto.send = function (this: XMLHttpRequest, body?: Document | XMLHttpRequestBodyInit | null): void {\n const xhr = this as XMLHttpRequest & { _cdMethod?: string; _cdUrl?: string };\n const onLoad = (): void => {\n try {\n if (xhr.status >= 500 && tracker.opts.isConsented()) {\n const url = xhr._cdUrl ?? \"\";\n if (!isSelfRequest(url, tracker.opts.selfHostname)) {\n tracker.captureHttp({\n url,\n method: (xhr._cdMethod ?? \"GET\").toUpperCase(),\n status: xhr.status,\n statusText: xhr.statusText,\n });\n }\n }\n } catch {\n // swallow\n }\n };\n xhr.addEventListener(\"loadend\", onLoad);\n return origSend.apply(this, [body ?? null]);\n };\n\n this.cleanups.push(() => {\n proto.open = origOpen;\n proto.send = origSend;\n });\n }\n\n private installConsoleWrap(): void {\n const console = (globalThis as { console?: Console }).console;\n if (!console) return;\n const orig = console.error.bind(console);\n console.error = (...args: unknown[]) => {\n try {\n if (this.opts.isConsented()) {\n this.captureMessage(args.map((a) => safeStringify(a)).join(\" \"), \"error\");\n }\n } catch {\n // swallow\n }\n return orig(...args);\n };\n this.cleanups.push(() => {\n console.error = orig;\n });\n }\n\n // ============================================================\n // Builders\n // ============================================================\n\n private buildFromErrorEvent(event: ErrorEvent): CapturedError {\n const err = event.error;\n const filename = event.filename || null;\n const lineno = typeof event.lineno === \"number\" && event.lineno > 0 ? event.lineno : null;\n const colno = typeof event.colno === \"number\" && event.colno > 0 ? event.colno : null;\n\n // ── Cross-origin script error path ────────────────────────────────\n // Signature: browser hands us \"Script error.\" (or empty), null\n // error object, and no usable location. We can't recover the real\n // message — but instead of stashing a useless \"Unknown error\",\n // we label the event clearly and tag it so the dashboard groups\n // it as a distinct, actionable category with a known remediation\n // (add crossorigin=\"anonymous\" + CORS headers on the script's\n // origin).\n const isCrossOriginStripped =\n err == null &&\n !filename &&\n lineno == null &&\n (event.message === \"Script error.\" ||\n event.message === \"Script error\" ||\n !event.message);\n if (isCrossOriginStripped) {\n const message =\n \"Cross-origin script error (browser hid details — script needs crossorigin attribute + CORS headers)\";\n return {\n timestamp: Date.now(),\n kind: \"error.unhandled\",\n level: \"error\",\n message,\n errorType: \"ScriptError\",\n frames: [],\n rawStack: null,\n filename: null,\n lineno: null,\n colno: null,\n // No location to fingerprint by — all of these will share one\n // group, which is correct: developer fixes them all with the\n // same CORS change.\n fingerprint: fingerprintError(message, []),\n breadcrumbs: this.opts.breadcrumbs.snapshot(),\n context: this.opts.getContext(),\n tags: { ...this.opts.getTags(), cross_origin: \"true\" },\n };\n }\n\n // ── Normal path ───────────────────────────────────────────────────\n // Pull every drop of signal out of the raw `event.error`, then fall\n // back to `event.message` if the value didn't yield one of its own.\n const payload = coerceErrorPayload(err);\n const message = (\n payload.message ||\n event.message ||\n \"Unknown error\"\n ).slice(0, 1024);\n const stack = err instanceof Error ? err.stack ?? null : null;\n const frames = parseStack(stack);\n const errorType = payload.errorType ?? null;\n\n const context = payload.extras\n ? { ...this.opts.getContext(), __error_extras: payload.extras }\n : this.opts.getContext();\n\n return {\n timestamp: Date.now(),\n kind: \"error.unhandled\",\n level: \"error\",\n message,\n errorType,\n frames,\n rawStack: stack,\n filename,\n lineno,\n colno,\n // Location fallback ensures distinct call sites stay separate\n // even when the message is generic (\"Unknown error\",\n // \"[object Object]\") and there are no parseable frames.\n fingerprint: fingerprintError(message, frames, {\n filename,\n lineno,\n colno,\n errorType,\n }),\n breadcrumbs: this.opts.breadcrumbs.snapshot(),\n context,\n tags: this.opts.getTags(),\n };\n }\n\n private buildFromUnknown(\n err: unknown,\n kind: CapturedError[\"kind\"],\n level: ErrorLevel,\n ): CapturedError {\n const payload = coerceErrorPayload(err);\n const message = (payload.message || \"Unknown error\").slice(0, 1024);\n const stack = err instanceof Error ? err.stack ?? null : null;\n const frames = parseStack(stack);\n const errorType = payload.errorType ?? null;\n\n const context = payload.extras\n ? { ...this.opts.getContext(), __error_extras: payload.extras }\n : this.opts.getContext();\n\n return {\n timestamp: Date.now(),\n kind,\n level,\n message,\n errorType,\n frames,\n rawStack: stack,\n filename: null,\n lineno: null,\n colno: null,\n fingerprint: fingerprintError(message, frames, { errorType }),\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(`HTTP ${info.status} ${info.method}`, [], {\n filename: info.url,\n 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.passesUrlGate(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 installed\n // via `setErrorBeforeSend()` AFTER init() takes effect on THIS\n // error, not just future ones constructed by a future tracker.\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 report.\n // 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; the next error\n // attempt will retry through the same queue.\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 passesUrlGate(err: CapturedError): boolean {\n const topFrame = err.frames.find((f) => f.filename) ?? null;\n const url = topFrame?.filename ?? err.filename ?? \"\";\n if (!url) return true; // unknown URL — let it through\n\n for (const pat of this.opts.config.denyUrls) {\n if (typeof pat === \"string\" && url.includes(pat)) return false;\n if (pat instanceof RegExp && pat.test(url)) return false;\n }\n if (this.opts.config.allowUrls.length > 0) {\n for (const pat of this.opts.config.allowUrls) {\n if (typeof pat === \"string\" && url.includes(pat)) return true;\n if (pat instanceof RegExp && pat.test(url)) return true;\n }\n return false;\n }\n return true;\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 // Drop entries older than the window.\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 * The thrown-value coercer.\n *\n * The browser's error pipelines (window.onerror, unhandledrejection,\n * developer `throw`) hand us values of every shape — Error instances,\n * DOMExceptions, plain objects, primitives, even null. Earlier\n * versions of this code wrote \"Unknown error\" whenever the value\n * wasn't an Error with a non-empty `.message`, which silently\n * collapsed entire classes of real bugs into one unhelpful bucket.\n *\n * This function extracts the maximum information available without\n * ever throwing (Symbol keys, recursive proxies, throwing toString —\n * all defended against). It returns three pieces:\n *\n * - message: the human-readable headline, never empty for any\n * non-null/non-undefined input\n * - errorType: the constructor name when we can discover one\n * (Error subclass, DOMException, custom class) —\n * feeds the dashboard's \"type · message\" header\n * - extras: additional fields worth keeping (Error.cause chain,\n * .code/.status/.response on common patterns, any\n * enumerable own properties on an Error subclass).\n * Stashed on context.__error_extras for the\n * dashboard's \"raw event\" panel.\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\n if (typeof v === \"string\") {\n return { message: v, errorType: null, extras: null };\n }\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 { message: `(thrown function: ${v.name || \"anonymous\"})`, errorType: \"function\", extras: null };\n }\n\n // Object-ish from here on — Error, DOMException, Response, plain\n // objects, custom classes, etc.\n if (v instanceof Error) {\n const errorType =\n v.name || v.constructor?.name || \"Error\";\n const message =\n typeof v.message === \"string\" && v.message.length > 0\n ? v.message\n : safeToString(v) || errorType;\n\n const extras: Record<string, unknown> = {};\n\n // ES2022 Error.cause — walk up to 5 levels so a service-layer\n // wrapper error doesn't hide the underlying network failure.\n const causeChain = collectCauseChain(v);\n if (causeChain.length > 0) extras.cause = causeChain;\n\n // Common HTTP/RPC patterns attach status/code/response to thrown\n // values. Capture them without forcing every wrapper class to\n // override toString.\n for (const key of [\"code\", \"status\", \"statusCode\", \"errno\", \"response\", \"data\", \"detail\", \"details\"] 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\n // Any other enumerable own properties (custom Error subclasses\n // that add fields).\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\n return {\n message,\n errorType,\n extras: Object.keys(extras).length > 0 ? extras : null,\n };\n }\n\n // Response — fetch().then(r => { if (!r.ok) throw r }) is a common\n // pattern, and the bare Response is otherwise unreadable.\n if (typeof Response !== \"undefined\" && v instanceof Response) {\n return {\n message: `HTTP ${v.status} ${v.statusText || \"\"}${v.url ? ` ${v.url}` : \"\"}`.trim(),\n errorType: \"Response\",\n extras: { status: v.status, statusText: v.statusText, url: v.url, type: v.type },\n };\n }\n\n // DOMException, Event, anything with a useful native toString —\n // capture the constructor name as the type, then prefer the\n // object's .message field if it has one (DOMException does;\n // ErrorEvent does; many polyfills do).\n if (typeof v === \"object\") {\n const obj = v as Record<string, unknown>;\n const ctorName =\n (obj.constructor && typeof obj.constructor === \"function\" && (obj.constructor as { name?: string }).name) ||\n null;\n\n const ownMessage = typeof obj.message === \"string\" && obj.message ? obj.message : null;\n const ownName = typeof obj.name === \"string\" && obj.name ? obj.name : null;\n\n let jsonForm: string | null = null;\n try {\n const serialised = JSON.stringify(obj);\n // JSON.stringify of an empty object is \"{}\", which is useless\n // as a message but tells us we have a thrown object with no\n // enumerable own props.\n jsonForm = serialised === \"{}\" ? null : serialised;\n } catch {\n jsonForm = null;\n }\n\n const fallbackString = safeToString(obj);\n const message =\n ownMessage ??\n jsonForm ??\n (fallbackString && fallbackString !== \"[object Object]\" ? fallbackString : null) ??\n (ctorName ? `(thrown ${ctorName} with no message)` : \"(thrown object with no message)\");\n\n const errorType = ownName ?? ctorName ?? null;\n\n // Best-effort extras for objects: keep up to ~20 enumerable own\n // properties, JSON-safe.\n const extras: Record<string, unknown> = {};\n let count = 0;\n for (const key of Object.keys(obj)) {\n if (count >= 20) break;\n if (key === \"message\" || key === \"name\") continue;\n const val = obj[key];\n if (typeof val === \"function\") continue;\n extras[key] = safeClone(val);\n count++;\n }\n\n return {\n message,\n errorType,\n extras: Object.keys(extras).length > 0 ? extras : null,\n };\n }\n\n // Should be unreachable, but: fall back to coerced string.\n return { message: safeToString(v) || \"(unstringifiable thrown value)\", errorType: null, extras: null };\n}\n\nfunction collectCauseChain(err: Error): Array<{ name: string; message: string }> {\n const out: Array<{ name: string; message: string }> = [];\n let cur: unknown = (err as Error & { cause?: unknown }).cause;\n let depth = 0;\n while (cur != null && depth < 5) {\n if (cur instanceof Error) {\n out.push({ name: cur.name || \"Error\", message: cur.message || \"\" });\n cur = (cur as Error & { cause?: unknown }).cause;\n } else {\n out.push({ name: \"non-Error\", message: safeToString(cur) });\n cur = null;\n }\n depth++;\n }\n return out;\n}\n\nfunction safeToString(v: unknown): string {\n try {\n const s = Object.prototype.toString.call(v);\n if (s !== \"[object Object]\") return s;\n // Try the value's own toString if it overrides Object's default.\n const own = (v as { toString?: () => unknown })?.toString;\n if (typeof own === \"function\" && own !== Object.prototype.toString) {\n const r = own.call(v);\n if (typeof r === \"string\") return r;\n }\n return s;\n } catch {\n return \"(throwing toString)\";\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 // JSON.stringify will throw on circular structures; that's fine,\n // we fall back to the toString below.\n const s = JSON.stringify(v);\n return s === undefined ? safeToString(v) : JSON.parse(s);\n } catch {\n return safeToString(v);\n }\n}\n\nfunction safeStringify(v: unknown): string {\n return coerceErrorPayload(v).message;\n}\n\n/**\n * Extract the hostname from a URL string for use as the\n * `selfHostname` field on the ErrorTracker. Returns null on malformed\n * input — the tracker's downstream self-skip check treats `null` as\n * \"no self to skip\" and captures everything (safer than swallowing\n * legitimate errors on a config typo).\n *\n * Lowercased for case-insensitive comparison (`Api.Cross-Deck.com`\n * 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 / XHR wrappers to skip captureHttp on Crossdeck's\n * own 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 URLs\n * — the SDK only ever uses absolute URLs, so a relative URL can't\n * be the SDK's own request.\n */\nexport function isSelfRequest(requestUrl: string, selfHostname: string | null | undefined): 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/**\n * A failed fetch (status 0 — an opaque TypeError with no HTTP response) is,\n * far more often than a real outage, client-environment noise: an adblocker or\n * privacy extension blocking the request, the user offline, or a request\n * aborted by a navigation. The Fetch spec deliberately makes these\n * indistinguishable from a genuine network fault, so a status-0 capture cannot\n * tell an outage from a blocked request — which is why \"Failed to fetch\" is the\n * single biggest false-alarm source in browser error tracking. This predicate\n * identifies the unambiguous-noise cases we refuse to raise a production error\n * for:\n * • AbortError — request cancelled by a navigation or explicit abort;\n * • offline — navigator.onLine === false, there is no network at all;\n * • SAME-ORIGIN — the page itself loaded from this origin, so the origin is\n * demonstrably reachable; a status-0 to it is client-side blocking (e.g. an\n * adblocker on a first-party asset), not a server fault. Genuine\n * same-origin server failures return an HTTP status (5xx) and are captured\n * on the success path, not here.\n * Cross-origin status-0 (a third-party API genuinely unreachable) still\n * reports — that keeps the real signal while dropping the noise.\n */\nexport function isClientNetworkNoise(requestUrl: string, err: unknown, w: Window): boolean {\n if (err instanceof Error && err.name === \"AbortError\") return true;\n const nav = (w as Window & { navigator?: { onLine?: boolean } }).navigator;\n if (nav && nav.onLine === false) return true;\n const pageHref = w.location?.href;\n const pageOrigin = w.location?.origin;\n if (!pageOrigin) return false;\n try {\n return new URL(requestUrl, pageHref ?? pageOrigin).origin === pageOrigin;\n } catch {\n return false;\n }\n}\n","/**\n * Public API surface for @cross-deck/web.\n *\n * Usage (browser):\n *\n * import { Crossdeck } from \"@cross-deck/web\";\n *\n * Crossdeck.init({\n * appId: \"app_web_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 *\n * Usage (Node):\n *\n * import { Crossdeck, MemoryStorage } from \"@cross-deck/web\";\n *\n * Crossdeck.init({\n * appId: \"app_node_xxx\",\n * publicKey: \"cd_pub_test_…\",\n * environment: \"sandbox\",\n * storage: new MemoryStorage(), // session-only persistence\n * autoHeartbeat: false, // skip the boot ping in scripts\n * });\n */\n\nimport { CrossdeckError } from \"./errors\";\nimport { HttpClient, SDK_NAME, SDK_VERSION, DEFAULT_BASE_URL } from \"./http\";\nimport { IdentityStore } 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 { CookieStorage, detectDefaultStorage, MemoryStorage } from \"./storage\";\nimport { randomChars } from \"./identity\";\nimport { bridgeReadCost } from \"./read-cost-bridge\";\nimport { collectDeviceInfo, type DeviceInfo } from \"./device-info\";\nimport { AutoTracker, DEFAULT_AUTO_TRACK, type AutoTrackConfig } from \"./auto-track\";\nimport { ConsoleDebugLogger, findSensitivePropertyKeys, type DebugLogger } from \"./debug\";\nimport { validateEventProperties } from \"./event-validation\";\nimport { SuperPropertyStore } from \"./super-properties\";\nimport {\n INTERNAL_OPT_OUT_PROPERTY,\n isInternalOptOut,\n processInternalOptOutUrl,\n} from \"./internal-opt-out\";\nimport { WebVitalsTracker } from \"./web-vitals\";\nimport { ConsentManager, scrubPii, scrubPiiFromProperties, type ConsentState } from \"./consent\";\nimport { BreadcrumbBuffer, type Breadcrumb } from \"./breadcrumbs\";\nimport type { ContractFailureInput } from \"./contracts\";\nimport { sendDiagnosticTelemetry } from \"./_diagnostic-telemetry\";\nimport {\n STATIC_VERIFIERS,\n VerifierReporter,\n buildVerifierContext,\n buildFlushIntervalVerifier,\n defaultDebugModeFlag,\n runBootSelfTest,\n runOnErrorParse,\n runOnIdentify,\n runOnSyncPurchases,\n runOnTrack,\n type ContractVerifier,\n type VerifierContext,\n} from \"./_contract-verifiers\";\nimport {\n DEFAULT_ERROR_CAPTURE,\n ErrorTracker,\n extractSelfHostname,\n type CapturedError,\n type ErrorCaptureConfig,\n type ErrorLevel,\n} from \"./error-capture\";\nimport type {\n AliasResult,\n AutoTrackOptions,\n CrossdeckOptions,\n Diagnostics,\n EntitlementsListResponse,\n Environment,\n EventProperties,\n GroupTraits,\n HeartbeatResponse,\n IdentifyOptions,\n PublicEntitlement,\n PurchaseResult,\n} from \"./types\";\n\ninterface InternalState {\n http: HttpClient;\n identity: IdentityStore;\n entitlements: EntitlementCache;\n events: EventQueue;\n autoTracker: AutoTracker | null;\n webVitals: WebVitalsTracker | null;\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 /** Cached enrichment payload merged into every event's properties. */\n deviceInfo: DeviceInfo;\n options: Required<\n Omit<\n CrossdeckOptions,\n | \"storage\"\n | \"sdkVersion\"\n | \"autoTrack\"\n | \"appVersion\"\n | \"debug\"\n | \"respectDnt\"\n | \"scrubPii\"\n // The three contract-verifier flags are resolved + consumed\n // outside state.options (see CrossdeckClient.verifierCtx /\n // verifierReporter / verifiers fields). They don't need to\n // round-trip through the InternalState options struct.\n | \"verifyContractsAtBoot\"\n | \"logVerifierResults\"\n | \"disableContractAssertions\"\n >\n > & {\n sdkVersion: string;\n autoTrack: AutoTrackConfig;\n appVersion: string | null;\n };\n debug: DebugLogger;\n developerUserId: string | null;\n /** Cleanup the unload-flush listeners installed in init(). */\n uninstallUnloadFlush: (() => void) | null;\n /** Most-recent server time observed via heartbeat (epoch ms). */\n lastServerTime: number | null;\n /** Local Date.now() captured at the same moment as lastServerTime. */\n lastClientTime: number | null;\n}\n\nexport class CrossdeckClient {\n private state: InternalState | null = null;\n\n // ────────────────────────────────────────────────────────────────\n // Contract verifier layer (see sdks/web/src/_contract-verifiers.ts).\n //\n // Three flags govern this layer (CrossdeckOptions):\n // verifyContractsAtBoot — boot self-test on Crossdeck.start\n // logVerifierResults — print PASS results to console\n // disableContractAssertions — total kill-switch\n //\n // When the kill-switch is set, all three fields stay null and\n // every hot-path dispatcher short-circuits — zero runtime cost.\n // Otherwise: populated once at init(), referenced from every\n // hot-path call site (identify, syncPurchases, ...).\n // ────────────────────────────────────────────────────────────────\n private verifiers: readonly ContractVerifier[] | null = null;\n private verifierReporter: VerifierReporter | null = null;\n private verifierCtx: VerifierContext | null = null;\n\n /**\n * Boot the SDK. Idempotent — calling init twice with the same options\n * is a no-op; calling with different options replaces the previous\n * configuration.\n *\n * NorthStar §11.1: signature is `Crossdeck.init({ appId, publicKey,\n * environment })`. The trio is validated up-front so a typo'd key or a\n * mismatched env fails fast at boot rather than at first event-flush.\n */\n init(options: CrossdeckOptions): void {\n // Idempotent re-init: tear down listeners from any prior init()\n // before constructing the new state. Pre-fix\n // `state.uninstallUnloadFlush` was set but never invoked anywhere,\n // so calling init() a second time (config swap during dev,\n // multi-tenant SDK shell, hot-module-replacement) silently\n // accumulated duplicate `pagehide` / `beforeunload` /\n // `visibilitychange` listeners. Each one fired a redundant flush.\n // Audit P2: now teardown runs on every re-init.\n if (this.state) {\n try { this.state.uninstallUnloadFlush?.(); } catch { /* ignore */ }\n try { this.state.autoTracker?.uninstall(); } catch { /* ignore */ }\n try { this.state.webVitals?.uninstall(); } catch { /* ignore */ }\n try { this.state.errors?.uninstall(); } catch { /* ignore */ }\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 /\n // multi-tenant SDK shell. flush({keepalive:true}) cancels\n // the timer (see EventQueue.cancelTimerIfSet) and ships\n // queued events out under the prior init's identity.\n //\n // CRITICAL: do NOT clear the persistent event store here.\n // The durable queue belongs to the SDK lifetime, not the\n // init() lifetime — a survived crash mid-flush re-hydrates\n // on the next init.\n try { void this.state.events.flush({ keepalive: true }); } catch { /* ignore */ }\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: \"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 // Key prefix must match the declared environment, otherwise prod\n // telemetry could silently route into sandbox dashboards (or vice\n // versa). NorthStar §15 calls this out as a \"fail loudly\" condition.\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 the publishable key with the environment declaration.`,\n });\n }\n\n // Localhost auto-detection. When the SDK boots from localhost /\n // 127.0.0.1 / *.local / RFC1918 private IPs, automatically switch\n // to a fully-local \"dev mode\" — no network calls fire, all SDK\n // methods (track, identify, isEntitled) work against in-memory +\n // localStorage state only. The dev's live dashboard stays clean\n // even if they forgot to swap their cd_pub_live_* key for a\n // cd_pub_test_* one.\n //\n // Stripe-grade default. Confidence-first means we trust the dev's\n // key prefix in production; localhost is the one place where we\n // proactively prevent accidental pollution.\n const localDevMode = isLocalHostname();\n\n const storage = options.storage ?? detectDefaultStorage();\n const persistIdentity = options.persistIdentity ?? true;\n const autoTrack = resolveAutoTrack(options.autoTrack);\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 // 1500ms idle window. Short enough that an event queued on page\n // load still flushes if the user leaves quickly (the keepalive\n // pagehide handler picks up anything that doesn't); long enough\n // that bursts of clicks coalesce into one network round-trip.\n // v1.4.0 Phase 3.3 — flush interval default parity. Pre-\n // v1.4.0: Web/Node 1500ms, RN/Swift/Android 5000ms. All\n // converged on 2000ms (the Stripe-adjacent industry norm)\n // so cross-platform funnels show events landing at the\n // same cadence on every SDK. Per-instance override stays.\n eventFlushIntervalMs: options.eventFlushIntervalMs ?? 2000,\n sdkVersion: options.sdkVersion ?? SDK_VERSION,\n autoTrack,\n appVersion: options.appVersion ?? null,\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 // Localhost auto-route: HttpClient short-circuits every request\n // to a successful no-op response when localDevMode is set.\n // SDK methods continue to work locally; nothing reaches the\n // server.\n localDevMode,\n // Contract verifier hot-path hook — fires the `error-envelope-shape`\n // verifier on every parsed wire error, BEFORE the throw bubbles\n // back to user code. Centralised here so we don't need a try/catch\n // around every `await s.http.request(...)` call site downstream.\n // No-op when the verifier layer is disabled (verifiers === null).\n onErrorParsed: (err) => {\n if (this.verifiers && this.verifierReporter && this.verifierCtx) {\n runOnErrorParse(this.verifiers, this.verifierReporter, this.verifierCtx, {\n errorType: err.type,\n errorCode: err.code,\n requestId: err.requestId ?? null,\n httpStatus: typeof err.status === \"number\" ? err.status : 0,\n });\n }\n },\n });\n\n if (localDevMode) {\n // Single console line on first init — direct, not scolding.\n // Tells the dev exactly what's happening and how to change it.\n console.log(\n \"[crossdeck] Localhost detected — running in dev mode (no network calls). \" +\n \"Set publicKey: 'cd_pub_test_…' and deploy to a real domain to test against the Crossdeck Sandbox.\",\n );\n }\n // Bank-grade identity continuity (v0.6.0+). When persistIdentity is\n // on AND we're in a browser, the SDK writes the anonymousId to BOTH\n // localStorage (primary) and a 1st-party cookie (secondary). When\n // persistIdentity is off — typical during a strict-consent flow\n // before opt-in — we fall back to in-memory only and write nothing\n // to either store.\n //\n // The cookie is only constructed when the caller didn't override\n // `storage`; if a custom storage adapter was supplied, that wins\n // and the cookie redundancy is the caller's responsibility (they\n // chose a non-default store for a reason).\n const effectiveStorage = persistIdentity ? storage : new MemoryStorage();\n const useCookieRedundancy =\n persistIdentity &&\n !options.storage && // honour caller's adapter choice\n typeof (globalThis as { document?: unknown }).document !== \"undefined\";\n const cookieStore = useCookieRedundancy ? new CookieStorage() : undefined;\n const identity = new IdentityStore(effectiveStorage, opts.storagePrefix, cookieStore);\n // Durable last-known-good entitlement cache — persisted through the\n // same storage as identity so isEntitled() answers from device cache\n // on boot and rides out a Crossdeck outage instead of failing every\n // Pro customer to free. In-memory only when persistIdentity is off.\n const entitlements = new EntitlementCache(\n effectiveStorage,\n opts.storagePrefix + \"entitlements\",\n );\n // Durable persistence — write queued events through to the primary\n // identity store (typically localStorage) so a crash / hard close /\n // keepalive cap exceedance doesn't lose data. Skipped when\n // persistIdentity is off (strict consent / in-memory-only mode) —\n // no point writing events to a store the developer told us not to\n // use.\n const persistentEvents = persistIdentity\n ? new PersistentEventStore({ storage: effectiveStorage, prefix: opts.storagePrefix })\n : null;\n if (persistentEvents) {\n debug.emit(\n \"sdk.queue_restored\",\n \"Restored persisted event queue from a prior session.\",\n );\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 MUST\n // be loud regardless of debug mode. Pre-fix the queue retried\n // 4xx forever silently and the customer never knew their key\n // was revoked. console.error fires unconditionally; the debug\n // signal lets the dashboard onboarding flow detect + surface\n // the problem too.\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(\n \"sdk.flush_permanent_failure\",\n headline,\n { ...info },\n );\n },\n onParked: (info) => {\n // Second signal channel (the queue already logged ONE console line).\n // PARK is NOT a drop: events are held on-device and resume on upgrade.\n // The dashboard reads this to render the calm amber \"update to resume\"\n // advisory. Distinct signal from flush_permanent_failure.\n debug.emit(\n \"sdk.parked\",\n `[crossdeck] SDK parked — server no longer accepts this version's event format. Events held on-device (paused, not lost); update @cross-deck/web${info.minVersion ? ` to >= ${info.minVersion}` : \"\"} to resume.`,\n { ...info },\n );\n },\n });\n\n // Collect device info ONCE at boot; cheap to re-use on every event.\n const deviceInfo: DeviceInfo = autoTrack.deviceInfo\n ? collectDeviceInfo({ appVersion: opts.appVersion ?? undefined })\n : opts.appVersion\n ? { appVersion: opts.appVersion }\n : {};\n\n // Super-property + groups store — Mixpanel pattern. Lives on the\n // primary identity storage so it survives page reloads but is\n // cleared on reset() / forget(). Skipped when persistIdentity is\n // off (strict consent — no writes anywhere).\n const superProps = new SuperPropertyStore(\n persistIdentity ? effectiveStorage : new MemoryStorage(),\n opts.storagePrefix,\n );\n\n // Internal-traffic opt-out: a ?crossdeck_internal=1/0 in the URL sets/\n // clears the persisted localStorage flag now; each event then reads it\n // (isInternalOptOut) to tag itself internal. Self-contained + guarded.\n processInternalOptOutUrl();\n\n // Consent gating. DNT auto-detection runs once here if respectDnt\n // is enabled; otherwise the developer is responsible for calling\n // Crossdeck.consent({...}) before user-meaningful events fire.\n const consent = new ConsentManager({ respectDnt: options.respectDnt === true });\n if (consent.isDntDenied) {\n debug.emit(\n \"sdk.consent_dnt_applied\",\n \"Do Not Track detected — all tracking dimensions denied at init.\",\n );\n }\n\n // Breadcrumb ring buffer — the \"what was the user doing right\n // before things broke\" feature. Populated by auto-tracking\n // sources (page views, clicks, custom events) and by manual\n // Crossdeck.addBreadcrumb() calls. Attached to every error\n // report; cleared on reset() / forget().\n const breadcrumbs = new BreadcrumbBuffer(50);\n\n this.state = {\n http,\n identity,\n entitlements,\n events,\n autoTracker: null,\n webVitals: null,\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 uninstallUnloadFlush: null,\n lastServerTime: null,\n lastClientTime: null,\n };\n\n debug.emit(\"sdk.configured\", `Crossdeck connected to ${opts.appId} in ${opts.environment} mode.`, {\n appId: opts.appId,\n environment: opts.environment,\n sdkVersion: opts.sdkVersion,\n });\n\n // Auto-tracker boots AFTER state is set so its initial track() calls\n // can resolve identity hints and device-info enrichment correctly.\n if (autoTrack.sessions || autoTrack.pageViews) {\n const tracker = new AutoTracker(\n autoTrack,\n (name, properties) => this.track(name, properties),\n // Persist session continuity on the SAME adapter as identity, so\n // a visit survives full-page navigations (multi-page sites\n // re-install the SDK on every page) and honours the same consent\n // posture — MemoryStorage when identity persistence is off.\n { storage: effectiveStorage, storageKey: opts.storagePrefix + \"session\" },\n );\n this.state.autoTracker = tracker;\n tracker.install();\n }\n // Web Vitals tracker — emits LCP / INP / CLS / FCP / TTFB as named\n // events. No-op in non-browser environments or when the\n // PerformanceObserver primitive is missing.\n if (autoTrack.webVitals) {\n const vitals = new WebVitalsTracker(\n { enabled: true },\n (name, properties) => this.track(name, properties),\n );\n this.state.webVitals = vitals;\n vitals.install();\n }\n\n // ----- Error capture (the third pillar) -----\n // Installs global window.onerror + window.onunhandledrejection\n // handlers, wraps fetch + XHR, and reports each captured error\n // through the same event queue analytics uses. Crucially this\n // runs AFTER the queue, identity, and breadcrumb buffer are set\n // up — error events need all of them.\n if (autoTrack.errors) {\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 // GETTER, not a captured value — `setErrorBeforeSend()` mutates\n // `state.errorBeforeSend` after init() and the tracker MUST\n // pick up the new hook on the next error. The pre-fix shape\n // (`beforeSend: this.state!.errorBeforeSend`) snapshotted\n // `null` at construction and made the customer's PII scrubber\n // silently inert. See error-capture.ts:ErrorTrackerOptions.beforeSend.\n beforeSend: () => this.state!.errorBeforeSend,\n isConsented: () => this.state!.consent.errors,\n // Derived from the configured baseUrl at init() time. Used by\n // the fetch / XHR wrappers to skip captureHttp on Crossdeck's\n // own requests — pre-fix the skip was hardcoded to\n // `api.cross-deck.com` and broke for customers on staging /\n // regional / self-hosted base URLs (recursive capture loop).\n selfHostname: extractSelfHostname(opts.baseUrl),\n });\n this.state.errors = tracker;\n tracker.install();\n }\n\n // Terminal flush wiring — without this, every page navigation drops\n // whatever's queued (page.viewed on load, session.ended on pagehide,\n // user clicks within the idle window). Use keepalive so the request\n // survives the unload. visibilitychange→hidden is the canonical\n // mobile signal (pagehide also fires there); pagehide + beforeunload\n // are the desktop ones. We listen to all three and rely on the\n // queue being a no-op when empty so a single trigger flushes once.\n this.state.uninstallUnloadFlush = installUnloadFlush(() => {\n // Fire-and-forget. Errors here can't be handled meaningfully — the\n // page is going away. Keepalive lets the browser keep the request\n // alive past unload up to 64 KB total in flight.\n void this.flush({ keepalive: true }).catch(() => undefined);\n });\n\n if (opts.autoHeartbeat && !localDevMode) {\n // Fire-and-forget — heartbeat failure shouldn't block init().\n // Skipped in dev mode — there's nothing to heartbeat.\n void this.heartbeat().catch(() => undefined);\n }\n\n // ────────────────────────────────────────────────────────────────\n // Contract verifier layer — install LAST so the SDK is fully\n // constructed before any verifier observes its state. See\n // sdks/web/src/_contract-verifiers.ts for the full architecture.\n //\n // Three-layer precedence (locked):\n //\n // code option > dashboard remote config > DEBUG/RELEASE default\n //\n // Code wins so engineers retain ultimate control; the dashboard\n // toggle (per-app, via /dashboard/apps/{appId}) is the no-deploy\n // operational lever for QA / staging soaks; default is what ships\n // when nobody touches anything.\n //\n // Routing rules — locked here so a future change to init() can't\n // accidentally muddle them (the docstrings on the CrossdeckOptions\n // fields name each other as the wrong tool for the wrong job):\n //\n // disableContractAssertions: true → layer is entirely silent\n // logVerifierResults: false → console silent on PASS;\n // FAIL still prints at WARN\n // verifyContractsAtBoot: false → no boot self-test, but\n // hot-path verifiers still run\n //\n // `disableContractAssertions` is intentionally NOT remote-\n // configurable — sovereignty kill-switch must live in customer\n // source so procurement / audit teams can grep for it.\n // ────────────────────────────────────────────────────────────────\n if (options.disableContractAssertions !== true) {\n const debugDefault = defaultDebugModeFlag();\n // 1. SYNCHRONOUS BOOTSTRAP — context + reporter + verifier set\n // are constructed immediately using code > default. Hot-path\n // verifiers fire correctly from the next operation onwards.\n this.verifierCtx = buildVerifierContext({\n logVerifierResults: options.logVerifierResults ?? debugDefault,\n disableContractAssertions: false,\n // Best-effort run-context detection. Customers running the\n // Web SDK inside their own CI (Playwright, Cypress, etc.)\n // typically set CI=true; we use that as the signal. Otherwise\n // we assume `customer-app` — the SDK is running inside a\n // customer's deployed site.\n runContext: typeof process !== \"undefined\" && process.env && process.env.CI\n ? \"ci\"\n : \"customer-app\",\n });\n this.verifierReporter = new VerifierReporter(this.verifierCtx);\n const flushVerifier = buildFlushIntervalVerifier(opts.eventFlushIntervalMs);\n this.verifiers = Object.freeze([...STATIC_VERIFIERS, flushVerifier]);\n\n // 2. ASYNCHRONOUS REFINEMENT — fetch /v1/config and apply the\n // dashboard's per-app remote config, then fire the boot\n // self-test (so its output reflects the FINAL resolved\n // flags, not the pre-fetch defaults). Fire-and-forget — a\n // fetch failure leaves the local defaults in place and the\n // boot self-test runs only if code > default resolves true.\n //\n // Skipped in localDevMode (test harness / offline emulator\n // where there's no backend to fetch from). Boot self-test\n // still fires under code > default precedence directly.\n if (localDevMode) {\n const verifyAtBootLocal =\n options.verifyContractsAtBoot ?? debugDefault;\n if (verifyAtBootLocal && this.verifierReporter) {\n void runBootSelfTest(\n this.verifiers,\n this.verifierReporter,\n this.verifierCtx,\n ).catch(() => undefined);\n }\n } else {\n void this.bootstrapVerifierLayerRemote(options, debugDefault).catch(\n () => undefined,\n );\n }\n }\n // The previous synchronous boot-test block is now replaced by the\n // async refinement above. Closing brace below remains the end of\n // init().\n return;\n }\n\n /**\n * Fetch /v1/config from the backend and apply any per-app verifier\n * overrides set in the dashboard, then fire the boot self-test\n * once with the FINAL resolved flags.\n *\n * Precedence (also documented at the call site in init()):\n * code option > dashboard remote config > DEBUG/RELEASE default\n *\n * Code wins so engineers retain ultimate control; dashboard is the\n * no-deploy operational lever for QA / staging soaks.\n *\n * Never throws — a /v1/config failure surfaces as \"stick with the\n * synchronous defaults that init() already applied\" and the boot\n * self-test runs only if code > default resolves true.\n */\n private async bootstrapVerifierLayerRemote(\n options: CrossdeckOptions,\n debugDefault: boolean,\n ): Promise<void> {\n if (!this.state || !this.verifiers || !this.verifierCtx) return;\n\n interface RemoteVerifierConfig {\n logVerifierResults: boolean | null;\n verifyContractsAtBoot: boolean | null;\n }\n interface ConfigResponseShape {\n verifierConfig?: RemoteVerifierConfig;\n }\n\n let remote: RemoteVerifierConfig = {\n logVerifierResults: null,\n verifyContractsAtBoot: null,\n };\n try {\n const resp = await this.state.http.request<ConfigResponseShape>(\n \"GET\",\n \"/config\",\n );\n if (resp && resp.verifierConfig) {\n const r = resp.verifierConfig;\n remote = {\n logVerifierResults:\n typeof r.logVerifierResults === \"boolean\"\n ? r.logVerifierResults\n : null,\n verifyContractsAtBoot:\n typeof r.verifyContractsAtBoot === \"boolean\"\n ? r.verifyContractsAtBoot\n : null,\n };\n }\n } catch {\n // Fall through — `remote` stays null, defaults apply.\n }\n\n // Resolve precedence: code > remote > default.\n const logResults =\n options.logVerifierResults ??\n (remote.logVerifierResults ?? debugDefault);\n const verifyAtBoot =\n options.verifyContractsAtBoot ??\n (remote.verifyContractsAtBoot ?? debugDefault);\n\n // If the remote config or precedence changed the resolved value\n // from what the synchronous bootstrap chose, rebuild the\n // context + reporter so subsequent hot-path verifiers see the\n // updated flags. The previous reporter is GC'd; in-flight hot-\n // path dispatches that snapshot the reporter (none currently;\n // every dispatcher reads `this.verifierReporter` at call time)\n // would see the old flags.\n if (logResults !== this.verifierCtx.logVerifierResults) {\n this.verifierCtx = {\n ...this.verifierCtx,\n logVerifierResults: logResults,\n };\n this.verifierReporter = new VerifierReporter(this.verifierCtx);\n }\n\n if (verifyAtBoot && this.verifierReporter) {\n await runBootSelfTest(\n this.verifiers,\n this.verifierReporter,\n this.verifierCtx,\n );\n }\n }\n\n /**\n * @deprecated Use `init()` instead. NorthStar §4 standardised the\n * lifecycle method name across SDKs as `init` (formerly `start` /\n * `configure`). `start` will be removed in a future major version.\n */\n start(options: CrossdeckOptions): void {\n if (typeof console !== \"undefined\") {\n // eslint-disable-next-line no-console\n console.warn(\n \"[crossdeck] Crossdeck.start() is deprecated — use Crossdeck.init() instead. The signature is the same.\",\n );\n }\n this.init(options);\n }\n\n /**\n * Link the anonymous device to a developer-supplied user ID. Cache\n * the resolved Crossdeck customer for follow-up calls.\n *\n * v0.9.0+ accepts an optional `traits` bag — profile data (name,\n * plan, signupDate, role) persisted on the Crossdeck customer record\n * and queryable from dashboards. Traits are sanitised through the\n * same validator that gates `track()` properties, so a `{ avatar:\n * <File>, onSave: () => {} }` payload can't corrupt the alias call.\n *\n * Crossdeck.identify(\"user_847\", {\n * email: \"wes@pinet.co.za\",\n * traits: { name: \"Wes\", plan: \"pro\", signedUpAt: \"2026-05-11\" },\n * });\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 // Read-cost cross-match (the moat): the instant we know who this session is,\n // tell the Buckets collector — every browser database read from here on\n // attributes to this user. Operational, like the entitlement cache, not an\n // analytics event; a no-op unless @cross-deck/buckets/web is installed.\n bridgeReadCost({ actor: userId });\n if (!s.consent.analytics) {\n // No-op on consent denial — but throw NOT — the developer\n // expected an aliasResult to await. Return a no-op result that\n // mirrors the wire shape so existing call chains don't break.\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 // Sanitise traits at the SDK boundary so a malformed bag (function,\n // BigInt, circular) never crashes the alias request. Empty result\n // → omit the field entirely so backends that don't yet know about\n // traits aren't surprised by an unknown key.\n const traitsValidation =\n options?.traits !== undefined\n ? validateEventProperties(options.traits)\n : null;\n const traits = traitsValidation && Object.keys(traitsValidation.properties).length > 0\n ? traitsValidation.properties\n : undefined;\n if (s.debug.enabled && traitsValidation && traitsValidation.warnings.length > 0) {\n for (const w of traitsValidation.warnings) {\n s.debug.emit(\n \"sdk.property_coerced\",\n `identify() traits key ${JSON.stringify(w.key)} was ${w.kind.replace(/_/g, \" \")} during validation.`,\n { key: w.key, kind: w.kind },\n );\n }\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 // storage 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 // Capture prior userId BEFORE the slot rotation so the\n // per-user-cache-isolation verifier can observe the transition.\n // Reading this from `s.developerUserId` (the prior state) — the\n // assignment to the new value happens further down, after the\n // HTTP call.\n const priorUserId = s.developerUserId;\n\n s.entitlements.setUserKey(userId);\n\n // ────────────────────────────────────────────────────────────\n // Hot-path verifier: per-user-cache-isolation runs AFTER the\n // slot rotation completes. PASS prints to debug console if\n // `logVerifierResults: true`; FAIL prints at WARN + fires\n // `reportContractFailure(...)` to the reliability channel.\n // Short-circuits cheaply when `disableContractAssertions: true`.\n // ────────────────────────────────────────────────────────────\n if (this.verifiers && this.verifierReporter && this.verifierCtx) {\n runOnIdentify(this.verifiers, this.verifierReporter, this.verifierCtx, {\n priorUserId,\n nextUserId: userId,\n cache: s.entitlements,\n });\n }\n\n const result = await s.http.request<AliasResult>(\"POST\", \"/identity/alias\", {\n body,\n });\n s.identity.setCrossdeckCustomerId(result.crossdeckCustomerId);\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 * Crossdeck.register({ plan: \"pro\", releaseChannel: \"beta\" });\n * Crossdeck.track(\"paywall_shown\"); // includes plan + releaseChannel\n *\n * Values that are `null` are deleted (the explicit \"stop tracking\n * this key\" idiom). Returns the resulting bag.\n *\n * Sanitised through `validateEventProperties` so a `{ avatar: File }`\n * payload can't poison the queue at flush time.\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, etc.).\n * Mixpanel / Segment \"Group Analytics\" pattern.\n *\n * Crossdeck.group(\"org\", \"acme_inc\");\n * Crossdeck.group(\"team\", \"design\", { headcount: 12 });\n *\n * Once set, every subsequent event carries `$groups.<type>: id` on\n * its properties bag, enabling B2B dashboards (\"how is Acme using\n * the product\"). Pass `id: null` to clear a group membership.\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 ? validateEventProperties(traits).properties : 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 /**\n * Update consent state. Three independent dimensions:\n *\n * analytics — track() + identify() + auto-emissions\n * marketing — paid-traffic click IDs + referrer URL on events\n * errors — Web Vitals + (future) error reporting\n *\n * Each defaults to `true` (granted). Pass partial state — only the\n * keys you provide are changed.\n *\n * Crossdeck.consent({ analytics: false });\n * Crossdeck.consent({ marketing: true, errors: true });\n *\n * DNT-derived denies cannot be flipped back on; if the browser said\n * \"don't track\" we don't track even if the developer code disagrees.\n */\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 (v1.0.0+)\n // ============================================================\n\n /**\n * Manually capture an error from a try/catch block.\n *\n * try { …risky… } catch (err) {\n * Crossdeck.captureError(err, { context: { plan: \"pro\" } });\n * }\n *\n * The error is shipped through the same event queue as analytics\n * (durable, retried, rate-limited per fingerprint). Sends are gated\n * by `consent.errors`. Returns silently — never throws, even if the\n * SDK isn't initialised yet.\n */\n captureError(\n error: unknown,\n options?: { context?: Record<string, unknown>; tags?: Record<string, string>; level?: ErrorLevel },\n ): void {\n if (!this.state?.errors) return;\n this.state.errors.captureError(error, options);\n }\n\n /**\n * Capture a non-error event you want to surface as an issue\n * (\"deprecated path hit\", \"we entered the slow code path\"). Sentry\n * captureMessage pattern. Returns silently if not initialised.\n */\n captureMessage(message: string, level: ErrorLevel = \"info\"): void {\n if (!this.state?.errors) return;\n this.state.errors.captureMessage(message, level);\n }\n\n /**\n * Attach a tag to every subsequent error report. Tags are key/value\n * strings (Sentry pattern): `setTag(\"flow\", \"checkout\")` → every\n * error from this point on carries `tags.flow === \"checkout\"`.\n */\n setTag(key: string, value: string): void {\n if (!this.state) return;\n this.state.errorTags[key] = value;\n }\n\n /** Bulk-set tags. Merges with existing tags. */\n setTags(tags: Record<string, string>): void {\n if (!this.state) return;\n Object.assign(this.state.errorTags, tags);\n }\n\n /**\n * Attach a structured context blob to every subsequent error report.\n * Unlike tags (flat key/value), context is a named bag of arbitrary\n * data: `setContext(\"cart\", { items: 3, total: 42.99 })`.\n */\n setContext(name: string, data: Record<string, unknown>): void {\n if (!this.state) return;\n this.state.errorContext[name] = data;\n }\n\n /**\n * Add a custom breadcrumb to the rolling buffer. Useful for marking\n * domain-meaningful moments (\"user opened paywall\") that aren't\n * already auto-captured. The buffer caps at 50 entries; old ones\n * evict.\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 beforeSend\n * pattern — the only way to redact app-specific PII (auth tokens\n * in URLs, etc.) before the report leaves the browser.\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 /**\n * Internal: turn a CapturedError into a Crossdeck event and enqueue\n * it. Goes through the same queue / persistence / consent / scrub\n * pipeline as analytics events.\n */\n private reportError(err: CapturedError): void {\n // Sanitise the error payload — stack frames may contain\n // user-supplied PII (emails / IDs in URLs). The scrub runs\n // automatically inside track() before the event lands in the\n // queue, but we also pre-flatten the structured fields here so\n // they're searchable in the warehouse.\n const properties: EventProperties = {\n // Identifiers\n fingerprint: err.fingerprint,\n level: err.level,\n // Error shape\n errorType: err.errorType,\n message: err.message,\n // Stack\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 // Context\n tags: err.tags,\n context: err.context,\n breadcrumbs: err.breadcrumbs,\n // HTTP (only when applicable)\n http: err.http,\n };\n // Strip undefined values for a tidy wire shape.\n for (const k of Object.keys(properties)) {\n if (properties[k] === undefined) delete properties[k];\n }\n // Use track() for the full pipeline: validation, enrichment,\n // consent gate (gated on `analytics` though we already verified\n // `errors`), durable queue, retry, scrub. The event name follows\n // the namespacing convention so dashboards can filter `name\n // LIKE 'error.%'`.\n this.track(err.kind, properties);\n }\n\n /**\n * GDPR/CCPA \"right to be forgotten\" — calls the backend's\n * /v1/identity/forget endpoint to schedule a server-side deletion of\n * the customer's events and profile, then wipes all local state\n * (identity, entitlements, queue, super-props, persistent stores).\n *\n * Idempotent. Safe to call when no identity has been established\n * (it just wipes the empty local state).\n *\n * After forget() resolves, the SDK is in the same shape as if the\n * developer had called `Crossdeck.reset()` — a fresh anonymousId is\n * minted and the next session is a brand new identity-graph entry.\n */\n async forget(): Promise<void> {\n const s = this.requireStarted();\n const identityQuery = this.identityQueryParams();\n try {\n await s.http.request<{ object: \"forgot\" }>(\"POST\", \"/identity/forget\", {\n body: {\n // Send every identity hint we hold; the server resolves the\n // canonical customer record and queues deletion. Missing\n // endpoint (older backend) gracefully degrades — local state\n // still wipes via the reset() call below.\n ...identityQuery,\n },\n });\n } catch (err) {\n // Server-side deletion failure is recorded but does not block\n // local wipe — the developer's user just asked to be forgotten,\n // refusing to clear their device because the backend hiccupped\n // would be the wrong call.\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 server.\n * Updates the local cache so subsequent isEntitled() calls answer\n * synchronously.\n */\n async getEntitlements(): Promise<PublicEntitlement[]> {\n const s = this.requireStarted();\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 // The refresh failed (Crossdeck unreachable / transient error).\n // The durable cache keeps serving last-known-good — but mark it\n // stale so the staleness is visible via diagnostics(), never a\n // silent unbounded window. Then rethrow so the caller still sees\n // the failure.\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 on boot and\n * survives a Crossdeck outage, so a returning paying customer reads\n * true even before the session's first network round-trip. Returns\n * false only for a genuinely new install that has never completed a\n * getEntitlements(), or for an entitlement past its own validUntil.\n *\n * Throws `not_initialized` (CrossdeckError, type\n * `configuration_error`) if called before `Crossdeck.init()`. The\n * `useEntitlement` React hook and the Vue composable both swallow\n * this and return `false`; bare callers must guard for it (or call\n * after `init()` resolves).\n */\n isEntitled(key: string): boolean {\n const s = this.requireStarted();\n return s.entitlements.isEntitled(key);\n }\n\n /** Snapshot of the local entitlement cache. */\n listEntitlements(): PublicEntitlement[] {\n const s = this.requireStarted();\n return s.entitlements.list();\n }\n\n /**\n * Subscribe to entitlement-cache changes. Returns an unsubscribe fn.\n *\n * The listener is invoked AFTER the cache mutates — once after a\n * successful `getEntitlements()` warms it, again after `syncPurchases()`\n * delivers fresh entitlements, once on `reset()` to fire the empty-\n * cache state for logout flows, AND once on `identify()` after the\n * per-user cache slot rotates and re-hydrates from device storage.\n *\n * IMPORTANT — the `identify()` fire is a TRAP if you treat it as\n * authoritative network state. `identify()` does NOT fetch entitlements;\n * it switches the per-user cache slot and rehydrates from device\n * storage (which is empty for a brand-new install, and last-known-good\n * — possibly stale — for a returning user). A listener that gates a\n * paywall on the first fire after an identity switch will read\n * `false` for a paying customer on a fresh device and let them past\n * the gate as free. The network-truth fire is the one that follows\n * the next `getEntitlements()` resolution. Either call\n * `getEntitlements()` explicitly after `identify()`, or have your\n * gating code tolerate the empty-then-populated transition.\n *\n * It is NOT invoked synchronously on subscribe. Callers that need\n * the current state should read it via `isEntitled()` / `listEntitlements()`\n * inline; the listener fires only on FUTURE changes.\n *\n * This is the foundation of the `useEntitlement` React hook in\n * `@cross-deck/web/react` — without it, React (or SwiftUI / Compose\n * / Vue) would have no way to re-render when entitlements arrive\n * asynchronously after init. The naive pattern of calling\n * `Crossdeck.isEntitled(\"pro\")` directly inside a render path\n * shows the empty-cache result forever; binding the result to\n * component state via `onEntitlementsChange` is the correct\n * pattern.\n *\n * Idempotent unsubscribe — calling the returned function multiple\n * times is safe.\n *\n * Listener errors are swallowed (a buggy listener can't crash the\n * 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 round-\n * trip happens in the background. To flush before the page unloads,\n * call flush().\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 * Wire the call from a test hook, dogfood failure path, or\n * customer contract-verification harness; see\n * `contracts/README.md` for the per-test-framework hook recipes.\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: \"web\",\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\n // ----- Consent gate -----\n // Three gates depending on event family:\n // error.* → consent.errors (crashes, HTTP failures, captureMessage)\n // webvitals.* → consent.errors (performance / reliability data)\n // everything else → consent.analytics\n // Default-on; the developer must explicitly call\n // Crossdeck.consent({...:false}) to drop them.\n const isError = name.startsWith(\"error.\");\n const isWebVital = name.startsWith(\"webvitals.\");\n const consentGateOk = (isError || isWebVital) ? 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 for ${isWebVital ? \"errors\" : \"analytics\"}.`,\n );\n }\n return;\n }\n\n // NorthStar §15: warn (in debug mode) when a property name looks\n // dangerously like PII — email/password/token/secret/card/phone.\n // We don't strip the field; that's the developer's call. We just\n // surface the signal so they can spot accidental leaks early.\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 so a chatty client\n // doesn't spam the log with every track() before identify().\n if (s.debug.enabled && !s.developerUserId && !s.identity.crossdeckCustomerId) {\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 BEFORE merging with\n // SDK enrichment. This is the boundary where untrusted developer\n // input becomes safe-to-serialise data: functions/symbols dropped,\n // Date / BigInt / Error coerced to JSON-friendly shapes, oversized\n // strings truncated, circular refs replaced. Without this, one bad\n // property (e.g. `{ onClick: () => {} }`) would crash JSON.stringify\n // at flush time and poison the entire batch indefinitely.\n //\n // The SDK's own enrichment (device info, sessionId, utm_*) is\n // trusted and not re-validated — those values are produced by\n // `collectDeviceInfo()` and `captureAcquisition()`, both of which\n // are typed and bounded.\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 // Envelope v1 §3 — assign seq SYNCHRONOUSLY at track() time, before\n // markActivity() (which may roll a new session and reset the counter to\n // 0). The counter belongs to the session; capturing it here — at the\n // deterministic call-order instant — means seq ordering follows call\n // order, not scheduler luck. Also capture timestamp here so both fields\n // are one sample (spec §3 + Swift parity from fix commit 6ac71a1).\n const seq = s.autoTracker?.nextSeq() ?? 0;\n const occurrenceTimestamp = Date.now();\n\n // Enrichment policy: device info first, then auto-tracker context\n // (sessionId + per-session acquisition utm_*/referrer), then\n // caller-supplied properties last so a developer can override\n // anything the SDK auto-attached.\n //\n // Acquisition fields are session-scoped (captured once at session\n // start by AutoTracker) and attached to every event of that session\n // — that's the GA4 model: same source/medium/campaign labels every\n // event in the same visit. Empty strings are filtered out so we\n // don't pollute event property dictionaries with no-signal columns.\n // Enrichment layer order (later wins on key conflict):\n // 1. Session + pageview + acquisition + click IDs (auto-tracker)\n // 2. Super properties (registered once via Crossdeck.register)\n // 3. Group memberships (set via Crossdeck.group)\n // 4. Caller-supplied properties (sanitised)\n // Device facts (os, browser, locale, etc.) are promoted to the\n // top-level `context` object per Envelope v1 §4 and are NO LONGER\n // spread into `properties`. Screen dimensions and device pixel ratio\n // remain in `properties` as they are not part of the spec §4 context\n // schema and are not promoted.\n // The order is intentional: developer-supplied data is most\n // authoritative, so it overrides anything the SDK auto-attached.\n // Any tracked event — auto or custom — is activity: push the rolling\n // 30-min session window forward so a user who interacts via custom\n // events (not just pageviews/clicks) keeps one continuous session.\n s.autoTracker?.markActivity();\n\n // Envelope v1 §4 — build the standardized context object with the exact\n // spec field names. Promoted out of `properties`. Common fields: os,\n // osVersion, appVersion, sdkName, sdkVersion, locale, timezone. Web adds:\n // browser, browserVersion. Only include keys that are defined (string).\n const wireContext: Record<string, string> = {};\n const di = s.deviceInfo;\n if (di.os) wireContext.os = di.os;\n if (di.osVersion) wireContext.osVersion = di.osVersion;\n const appVer = s.options.appVersion;\n if (appVer) wireContext.appVersion = appVer;\n wireContext.sdkName = SDK_NAME;\n wireContext.sdkVersion = s.options.sdkVersion;\n if (di.locale) wireContext.locale = di.locale;\n if (di.timezone) wireContext.timezone = di.timezone;\n if (di.browser) wireContext.browser = di.browser;\n if (di.browserVersion) wireContext.browserVersion = di.browserVersion;\n\n // Properties layer: screen dimensions + pixel ratio remain in properties\n // (not promoted to context). Start WITHOUT deviceInfo spread — context\n // fields have been promoted out; only non-context device fields remain.\n const enriched: EventProperties = {};\n if (di.screenWidth !== undefined) enriched.screenWidth = di.screenWidth;\n if (di.screenHeight !== undefined) enriched.screenHeight = di.screenHeight;\n if (di.viewportWidth !== undefined) enriched.viewportWidth = di.viewportWidth;\n if (di.viewportHeight !== undefined) enriched.viewportHeight = di.viewportHeight;\n if (di.devicePixelRatio !== undefined) enriched.devicePixelRatio = di.devicePixelRatio;\n const sessionId = s.autoTracker?.currentSessionId;\n if (sessionId) enriched.sessionId = sessionId;\n const pageviewId = s.autoTracker?.currentPageviewId;\n if (pageviewId) enriched.pageviewId = pageviewId;\n const acquisition = s.autoTracker?.currentAcquisition;\n if (acquisition) {\n // UTMs and referrer host are always attached (they're considered\n // analytics, not marketing PII). Paid-traffic click IDs and the\n // full referrer URL gate on marketing consent — the developer's\n // user said \"no marketing tracking\" → drop them.\n if (acquisition.utm_source) enriched.utm_source = acquisition.utm_source;\n if (acquisition.utm_medium) enriched.utm_medium = acquisition.utm_medium;\n if (acquisition.utm_campaign) enriched.utm_campaign = acquisition.utm_campaign;\n if (acquisition.utm_content) enriched.utm_content = acquisition.utm_content;\n if (acquisition.utm_term) enriched.utm_term = acquisition.utm_term;\n if (acquisition.referrer && s.consent.marketing) enriched.referrer = acquisition.referrer;\n if (s.consent.marketing) {\n if (acquisition.gclid) enriched.gclid = acquisition.gclid;\n if (acquisition.fbclid) enriched.fbclid = acquisition.fbclid;\n if (acquisition.msclkid) enriched.msclkid = acquisition.msclkid;\n if (acquisition.ttclid) enriched.ttclid = acquisition.ttclid;\n if (acquisition.li_fat_id) enriched.li_fat_id = acquisition.li_fat_id;\n if (acquisition.twclid) enriched.twclid = acquisition.twclid;\n }\n }\n // Super properties registered via Crossdeck.register(). Skipped\n // for keys the auto-enrichment already supplied so a `register`\n // call can't accidentally shadow `sessionId` etc.\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 // Group memberships — attached as `$groups.<type>` for B2B\n // dashboards. Mixpanel uses `$groups`; we mirror exactly so\n // existing integrators don't need a translation layer.\n const groupIds = s.superProps.getGroupIds();\n if (Object.keys(groupIds).length > 0) {\n enriched.$groups = groupIds;\n }\n Object.assign(enriched, validation.properties);\n\n // Internal-traffic opt-out: this browser visited ?crossdeck_internal=1,\n // so tag every event for ingest to classify as internal. Injected after\n // developer properties so it can't be shadowed; reserved $-prefixed key.\n if (isInternalOptOut()) {\n enriched[INTERNAL_OPT_OUT_PROPERTY] = true;\n }\n\n // ----- PII scrub -----\n // Last step before the event lands in the queue: defensive regex\n // scrub on URL paths, titles, and any string property value. An\n // app that puts emails or card numbers in URLs (`/users/wes@…/`)\n // would otherwise ship that PII straight to the warehouse. Even\n // with explicit consent this is the right default — Stripe scrubs\n // pre-storage too. Disable via `scrubPii: false` in init() for\n // pipelines that do their own redaction.\n const finalProperties = s.scrubPii ? scrubPiiFromProperties(enriched) : enriched;\n\n const event: QueuedEvent = {\n eventId: this.mintEventId(),\n name,\n timestamp: occurrenceTimestamp,\n seq,\n context: wireContext,\n properties: finalProperties,\n };\n Object.assign(event, this.identityHintForEvent());\n s.events.enqueue(event);\n\n // ────────────────────────────────────────────────────────────────\n // Hot-path verifier: `super-property-merge-precedence` runs AFTER\n // enqueue so the merged properties reflect EXACTLY what the queue\n // accepted. The observation carries the three input layers\n // separately + the merged result so the verifier can prove\n // caller > super > device precedence on every real track() call,\n // not just at boot. PASS prints to debug console iff\n // `logVerifierResults: true`; FAIL prints at WARN + fires\n // `reportContractFailure(...)` to the reliability channel.\n // Cheap short-circuit when `disableContractAssertions: true`.\n //\n // mergedProperties is observed PRE-SCRUB (`enriched`), not POST-\n // SCRUB (`finalProperties`). The contract enforced here is\n // \"device < super < caller\" — i.e., which LAYER's value wins per\n // key. PII scrub runs AFTER merge and intentionally mutates\n // strings (redacts emails, IPs, card numbers) and re-clones\n // arrays + plain objects. Strict-equal vs `finalProperties`\n // therefore produces false positives on any caller-supplied\n // compound value: `caller.frames === finalProperties.frames` is\n // `false` because scrub's `Array.map` returns a fresh array, even\n // when contents are unchanged. Founder caught this on\n // 2026-05-28 — a real `crossdeck.error` track() carrying\n // `frames: [...]` fired the hot-path FAIL on\n // `super-property-merge-precedence`, masking real bugs in the\n // dashboard. Scrub correctness is verified separately; this\n // verifier's job is solely merge-layer precedence.\n // ────────────────────────────────────────────────────────────────\n if (this.verifiers && this.verifierReporter && this.verifierCtx) {\n // Cast the four input layers to the verifier's open\n // Record<string, unknown> view. DeviceInfo / EventProperties are\n // narrowed-string-key records at runtime; the verifier only\n // reads keys/values, never writes, so the widen is sound. Done\n // here, not in the verifier signature, so the SDK call site\n // keeps the strictly-typed source values for refactors.\n runOnTrack(this.verifiers, this.verifierReporter, this.verifierCtx, {\n callerProperties: validation.properties as Record<string, unknown>,\n superProperties: supers as Record<string, unknown>,\n deviceProperties: s.deviceInfo as unknown as Record<string, unknown>,\n mergedProperties: enriched as Record<string, unknown>,\n });\n }\n\n // ----- Breadcrumb emission -----\n // Every analytics event becomes a breadcrumb so error reports\n // carry the context of what the user was doing just before the\n // crash. Don't emit a breadcrumb for error events themselves\n // (would be circular) or webvitals events (noise — they always\n // fire on every page).\n if (!isError && !isWebVital) {\n const category = name.startsWith(\"page.\")\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 // Strip the device-info / session bloat from the breadcrumb\n // payload — only the caller-supplied properties belong in\n // the user-readable trail.\n data: properties ? { ...properties } : undefined,\n });\n }\n }\n\n /**\n * Force-flush queued events. Useful to call from page-unload handlers.\n *\n * Pass `{ keepalive: true }` from terminal handlers (pagehide /\n * visibilitychange→hidden / beforeunload). The browser keeps the\n * request alive after the page tears down, so the final batch\n * actually lands instead of being cancelled with the unload.\n *\n * NorthStar §4: standard method name across all Crossdeck SDKs.\n */\n async flush(options: { keepalive?: boolean } = {}): Promise<void> {\n const s = this.requireStarted();\n await s.events.flush(options);\n }\n\n /** @deprecated Use `flush()` instead. NorthStar §4 standardised the name. */\n async flushEvents(): Promise<void> {\n return this.flush();\n }\n\n /**\n * Forward purchase evidence to the backend for verification + entitlement\n * projection. NorthStar §4 + §13 canonical name.\n *\n * Today the web SDK only supports Apple StoreKit 2 forwarding (web apps\n * that sit alongside an iOS app). Stripe doesn't need this method —\n * Stripe webhooks deliver evidence server-side without a client round-trip.\n */\n async syncPurchases(input: {\n rail?: \"apple\";\n signedTransactionInfo: string;\n signedRenewalInfo?: string;\n appAccountToken?: string;\n }): Promise<PurchaseResult> {\n const s = this.requireStarted();\n if (!input.signedTransactionInfo) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_signed_transaction_info\",\n message: \"syncPurchases requires a signedTransactionInfo string from StoreKit 2.\",\n });\n }\n // Spread input FIRST so the explicit `rail` default below WINS.\n // Pre-fix order was `{ rail: input.rail ?? \"apple\", ...input }`\n // — but `...input` runs LAST and overrides the default with the\n // caller's literal `rail` key, including the case where the\n // caller passes `rail: undefined` explicitly (TypeScript treats\n // an undefined-typed property as \"key present\"). Reversing the\n // order so the default sits last fixes both the explicit-undefined\n // case AND the omitted-key case in one line.\n const rail = input.rail ?? \"apple\";\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-\n // circuits with idempotent_replay: true on retry.\n const idempotencyKey = deriveIdempotencyKeyForPurchase(body);\n\n // ────────────────────────────────────────────────────────────\n // Hot-path verifier: idempotency-key-deterministic re-derives the\n // key from the same input and compares against what the SDK\n // computed. Identical → contract honoured. Drift → bug.\n // ────────────────────────────────────────────────────────────\n if (this.verifiers && this.verifierReporter && this.verifierCtx) {\n const stableId =\n rail === \"apple\"\n ? (body as { signedTransactionInfo?: string }).signedTransactionInfo ?? \"\"\n : (body as { purchaseToken?: string }).purchaseToken ?? \"\";\n runOnSyncPurchases(\n this.verifiers,\n this.verifierReporter,\n this.verifierCtx,\n {\n rail,\n stableIdentifier: stableId,\n derivedKey: idempotencyKey,\n },\n );\n }\n\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 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 so cross-platform funnels reconcile on\n // event name + the rail/productId fields. Manual paths don't\n // see the StoreKit Transaction so transactionId/purchaseDate\n // are absent — funnel queries that need them stay native-\n // auto-track only.\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 // track() throws only on invalid name (we control it) — defensive.\n }\n s.debug.emit(\n \"sdk.purchase_evidence_sent\",\n \"StoreKit transaction forwarded. Waiting for backend verification.\",\n { rail: input.rail ?? \"apple\" },\n );\n return result;\n }\n\n /** @deprecated Use `syncPurchases()` instead. NorthStar §4 standardised the name. */\n async purchaseApple(input: {\n signedTransactionInfo: string;\n signedRenewalInfo?: string;\n appAccountToken?: string;\n }): Promise<PurchaseResult> {\n return this.syncPurchases({ rail: \"apple\", ...input });\n }\n\n /**\n * Toggle verbose diagnostic logging — NorthStar §16. When enabled, the\n * SDK emits a fixed vocabulary of debug signals to console.info that the\n * dashboard's onboarding checklist can also surface as live events.\n */\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 start() unless\n * autoHeartbeat:false. Safe to call manually as a \"we're still here\" ping.\n */\n async heartbeat(): Promise<HeartbeatResponse> {\n const s = this.requireStarted();\n const result = await s.http.request<HeartbeatResponse>(\"GET\", \"/sdk/heartbeat\");\n // Capture clock skew at the SAME instant on both sides. The\n // `serverTime` field is the only authoritative source the SDK has\n // for \"what does the backend think the time is\" — used in\n // diagnostics() so a wrong-system-clock problem surfaces before\n // it silently shifts a day of analytics.\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. Use on logout. The\n * next pre-login session generates a fresh anonymousId and starts a\n * new identity-graph entry.\n */\n reset(): void {\n if (!this.state) return;\n // Server-derived milestone: emit `user.signed_out` BEFORE we wipe\n // identity. The track() call enqueues the event with the current\n // developerUserId/cdcust stamped on it; the subsequent reset of\n // the identity store happens locally only — the queued event\n // already left with the right identity. The dashboard's Activity\n // stream therefore shows \"Wes signed out\" rather than an\n // anonymous orphan event. Skipped if there's no developerUserId\n // (calling reset on a never-identified anonymous device is a no-op\n // semantically — there was nothing to \"sign out\" of).\n if (this.state.developerUserId) {\n try {\n this.track(\"user.signed_out\", { auto: true });\n } catch {\n // track() throws only on invalid name — never for a literal we control.\n // Defensive catch keeps reset() bulletproof for logout flows.\n }\n }\n // Tear down + reinstall the auto-tracker so the new session belongs\n // to the new identity, not the old one. Unload-flush listeners stay\n // installed across reset — they're tied to the SDK lifetime, not\n // the identity lifetime. Web Vitals stay attached too — their\n // observers are per-page-life, not per-identity.\n this.state.autoTracker?.uninstall();\n this.state.identity.reset();\n // Clear the read-cost actor too: from here, browser reads attribute to no\n // user (the next session re-identifies). Matches the entitlement wipe below.\n bridgeReadCost({ actor: undefined });\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.\n this.state.entitlements.clearAll();\n this.state.events.reset();\n // Super properties + groups are identity-scoped — clear on logout\n // so a fresh anonymous session doesn't inherit the previous user's\n // plan/role/group context.\n this.state.superProps.clear();\n // Breadcrumbs + error context belong to the old session; wipe so\n // a fresh post-logout error report doesn't carry the previous\n // user's debugging trail. The ErrorTracker stays installed across\n // reset — its listeners are page-life-scoped, not identity-scoped.\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 the\n // pre-logout session. Pre-fix `diagnostics().clock.skewMs` for the\n // next user kept reporting the prior session's skew until the\n // next heartbeat completed (audit P1 #17). The next heartbeat\n // repopulates with the new instant.\n this.state.lastServerTime = null;\n this.state.lastClientTime = null;\n if (this.state.autoTracker) {\n const tracker = new AutoTracker(this.state.options.autoTrack, (name, props) =>\n this.track(name, props),\n );\n this.state.autoTracker = tracker;\n tracker.install();\n }\n }\n\n /**\n * Diagnostic: current state + queue stats. Useful for the dashboard's\n * heartbeat row and debugging in dev.\n *\n * Returns a stable shape regardless of whether start() has been called —\n * callers don't need to narrow on `started` to access `events` or\n * `entitlements`. Pre-start values are sensible empties.\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 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.identity.anonymousId,\n crossdeckCustomerId: s.identity.crossdeckCustomerId,\n developerUserId: s.developerUserId,\n sdkVersion: s.options.sdkVersion,\n baseUrl: s.options.baseUrl,\n clock: {\n lastServerTime: s.lastServerTime,\n lastClientTime: s.lastClientTime,\n skewMs,\n },\n entitlements: {\n count: s.entitlements.list().length,\n lastUpdated: s.entitlements.freshness,\n stale: s.entitlements.isStale,\n listenerErrors: s.entitlements.listenerErrors,\n },\n events: s.events.getStats(),\n };\n }\n\n /**\n * The stable reference to hand Stripe at checkout so the resulting\n * purchase attributes to THIS person. Stamp it on the Checkout Session\n * as `metadata.crossdeck_ref` (see docs/connect-stripe) — the platform\n * webhook reads it back, validates it, and attaches the subscription to\n * the right customer instead of stranding it on an anonymous record.\n *\n * Returns the strongest identifier the SDK currently holds, in the same\n * precedence the server resolves by:\n * crossdeckCustomerId (cdcust_…) > developerUserId > anonymousId\n * anonymousId is always present, so this always returns a usable,\n * non-empty reference — even before identify(). Identify the user as\n * early as you can, though, so the reference is their stable identity\n * and not a per-device anon id.\n */\n getCheckoutReference(): string {\n const s = this.requireStarted();\n return (\n s.identity.crossdeckCustomerId ??\n s.developerUserId ??\n s.identity.anonymousId\n );\n }\n\n /**\n * The device-scoped anonymous id the SDK minted on first boot and persists\n * across launches (stable until reset()). Public accessor so a server-to-\n * server flow or a block/suspension gate can pass the device identity to\n * POST /v1/resolve without reaching into private storage.\n *\n * Returns `null` BEFORE init() — there is no anon id yet, and a gate that\n * fires during early app boot should get a clean falsy, not a throw. (This is\n * deliberately softer than getCheckoutReference(), which requires init.)\n *\n * Note: /v1/resolve also accepts a VERIFIED identity (userId + idToken)\n * without an anonymousId, and that path is higher-trust — prefer it where the\n * user is authenticated.\n */\n getAnonymousId(): string | null {\n return this.state ? this.state.identity.anonymousId : null;\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 * — matches the resolveCrossdeckCustomerId precedence on the server.\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. Earlier this returned\n * just the highest-priority hint (cdcust → developerUserId → anonymousId)\n * to keep payloads small, but that leaked into analytics: once a user\n * was logged in, every subsequent page.viewed shipped without\n * anonymousId, and `uniqExact(anonymous_id)` on the warehouse side\n * counted 0 visitors for the entire authenticated app.\n *\n * Bank-grade rule: the server is the single source of truth on\n * dedup. Send everything we know; let CH count by whichever axis\n * matches the question. Each field is at most 32 bytes — sending\n * three on every event costs ~80 bytes per request, which is\n * trivial compared to the analytics correctness it buys.\n */\n private identityHintForEvent(): Pick<\n QueuedEvent,\n \"developerUserId\" | \"anonymousId\" | \"crossdeckCustomerId\"\n > {\n const s = this.requireStarted();\n const hint: Pick<QueuedEvent, \"developerUserId\" | \"anonymousId\" | \"crossdeckCustomerId\"> = {\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 * Normalise the autoTrack option to a fully-resolved AutoTrackConfig.\n * undefined → all defaults (everything on in browsers)\n * true → all on (same as defaults)\n * false → all off\n * { sessions:false } → defaults for unspecified flags, override for specified ones\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 *\n * We treat the legacy form as \"no opinion\" so the developer's explicit\n * `environment` declaration always wins for unprefixed keys (e.g. dev\n * fixture keys in tests).\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 * Detect whether the SDK is booting on a local-development hostname.\n * Triggers the SDK's silent dev-mode shutoff (no network calls, all\n * state local) so a developer running their app locally with a live\n * key cannot accidentally pollute their production analytics.\n *\n * Match list:\n * - localhost / 127.0.0.1 traditional local dev\n * - *.local mDNS / Bonjour (e.g. mymac.local)\n * - 10.x.x.x RFC1918 class A (private)\n * - 192.168.x.x RFC1918 class C (home / office LAN)\n * - 172.16-31.x.x RFC1918 class B\n *\n * Vercel preview URLs, Netlify branch deploys, ngrok tunnels — those\n * resolve to real domains and stay on whatever the key says. They're\n * not \"local,\" they're \"deployed under a temporary domain.\"\n *\n * Returns false in non-browser contexts (Node, Workers) — there's no\n * window.location to inspect, and a Node consumer that wired up the\n * SDK is presumably running server-side with a deliberate config.\n */\nfunction isLocalHostname(): boolean {\n // Testing escape hatch — E2E suites + smoke pages need to exercise\n // the real wire shape from a non-deployed domain (Playwright runs\n // on 127.0.0.1). Setting `window.__CROSSDECK_FORCE_LIVE__ = true`\n // before init() returns false here so the SDK fires real fetches.\n // Not documented in SDK_TRUTH because it's internal-only — real\n // consumers should never set this.\n const w = (globalThis as {\n window?: { location?: { hostname?: string }; __CROSSDECK_FORCE_LIVE__?: boolean };\n }).window;\n if (w?.__CROSSDECK_FORCE_LIVE__ === true) return false;\n const hostname = w?.location?.hostname;\n if (!hostname) return false;\n if (hostname === \"localhost\" || hostname === \"127.0.0.1\") return true;\n // 0.0.0.0 is the default bind address for webpack-dev-server /\n // vite dev server when the team is on a shared LAN dev. Audit P2:\n // pre-fix this slipped through and polluted live analytics for\n // anyone running those configs.\n if (hostname === \"0.0.0.0\") return true;\n if (hostname === \"::1\" || hostname === \"[::1]\") return true;\n // IPv6 link-local: fe80::/10. Devices on the same network segment\n // (browser inspector debugging an iPad via Safari Web Inspector\n // over USB lands here). Conservative match — only the link-local\n // prefix, not the broader ULA fc00::/7 range which is intended for\n // private routed networks and shouldn't auto-quiet by default.\n if (/^\\[?fe80::/i.test(hostname)) return true;\n if (hostname.endsWith(\".local\")) return true;\n if (/^10\\./.test(hostname)) return true;\n if (/^192\\.168\\./.test(hostname)) return true;\n if (/^172\\.(1[6-9]|2\\d|3[0-1])\\./.test(hostname)) return true;\n return false;\n}\n\nfunction resolveAutoTrack(\n input: CrossdeckOptions[\"autoTrack\"],\n): AutoTrackConfig {\n if (input === false) {\n return {\n sessions: false,\n pageViews: false,\n deviceInfo: false,\n clicks: false,\n webVitals: false,\n errors: false,\n };\n }\n if (input === undefined || input === true) {\n return { ...DEFAULT_AUTO_TRACK };\n }\n return {\n sessions: input.sessions ?? DEFAULT_AUTO_TRACK.sessions,\n pageViews: input.pageViews ?? DEFAULT_AUTO_TRACK.pageViews,\n deviceInfo: input.deviceInfo ?? DEFAULT_AUTO_TRACK.deviceInfo,\n clicks: input.clicks ?? DEFAULT_AUTO_TRACK.clicks,\n webVitals: input.webVitals ?? DEFAULT_AUTO_TRACK.webVitals,\n errors: input.errors ?? DEFAULT_AUTO_TRACK.errors,\n };\n}\n\n/**\n * Install browser unload listeners that fire `onUnload` when the page\n * is going away. We listen to all three because each browser/platform\n * is unreliable on at least one of them:\n * - `pagehide` is the modern, mobile-reliable signal (Safari iOS only\n * fires this — beforeunload doesn't fire on backgrounding there).\n * - `visibilitychange → hidden` is the canonical \"tab going to bg\"\n * signal; bfcache restores re-fire `pagehide`/`pageshow`.\n * - `beforeunload` is the legacy desktop signal — kept as a belt for\n * older Chrome/Firefox versions.\n *\n * The handler is idempotent: if the queue is empty, flush() is a no-op,\n * so multiple firings during one unload are harmless.\n *\n * Returns a teardown that removes all three listeners. No-ops in non-\n * browser environments (Node, Web Workers).\n */\nfunction installUnloadFlush(onUnload: () => void): () => void {\n const w = (globalThis as { window?: Window }).window;\n const doc = (globalThis as { document?: Document }).document;\n if (!w || !doc) return () => undefined;\n\n const onVisChange = (): void => {\n if (doc.visibilityState === \"hidden\") onUnload();\n };\n const onTerminal = (): void => onUnload();\n\n doc.addEventListener(\"visibilitychange\", onVisChange);\n w.addEventListener(\"pagehide\", onTerminal);\n w.addEventListener(\"beforeunload\", onTerminal);\n\n return () => {\n doc.removeEventListener(\"visibilitychange\", onVisChange);\n w.removeEventListener(\"pagehide\", onTerminal);\n w.removeEventListener(\"beforeunload\", onTerminal);\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoBA,iBAAyD;;;ACkClD,IAAM,iBAAN,MAAM,wBAAuB,MAAM;AAAA,EASxC,YAAY,SAAgC;AAC1C,UAAM,QAAQ,OAAO;AACrB,SAAK,OAAO;AACZ,SAAK,OAAO,QAAQ;AACpB,SAAK,OAAO,QAAQ;AACpB,SAAK,YAAY,QAAQ;AACzB,SAAK,SAAS,QAAQ;AACtB,SAAK,eAAe,QAAQ;AAC5B,SAAK,aAAa,QAAQ;AAC1B,SAAK,UAAU,QAAQ;AAEvB,WAAO,eAAe,MAAM,gBAAe,SAAS;AAAA,EACtD;AACF;AAOA,eAAsB,2BAA2B,KAAwC;AACvF,QAAM,YAAY,IAAI,QAAQ,IAAI,cAAc,KAAK;AACrD,QAAM,eAAe,sBAAsB,IAAI,QAAQ,IAAI,aAAa,CAAC;AACzE,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,IAAI,KAAK;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACA,QAAM,WAAY,MAA+E;AACjG,MAAI,YAAY,OAAO,SAAS,SAAS,YAAY,OAAO,SAAS,SAAS,UAAU;AACtF,WAAO,IAAI,eAAe;AAAA,MACxB,MAAM,SAAS;AAAA,MACf,MAAM,SAAS;AAAA,MACf,SAAS,SAAS,WAAW,QAAQ,IAAI,MAAM;AAAA,MAC/C,WAAW,SAAS,cAAc;AAAA,MAClC,QAAQ,IAAI;AAAA,MACZ;AAAA;AAAA,MAEA,YAAY,OAAO,SAAS,eAAe,WAAW,SAAS,aAAa;AAAA,MAC5E,SAAS,OAAO,SAAS,YAAY,WAAW,SAAS,UAAU;AAAA,IACrE,CAAC;AAAA,EACH;AACA,SAAO,IAAI,eAAe;AAAA,IACxB,MAAM,iBAAiB,IAAI,MAAM;AAAA,IACjC,MAAM,QAAQ,IAAI,MAAM;AAAA,IACxB,SAAS,QAAQ,IAAI,MAAM,IAAI,IAAI,cAAc,EAAE,GAAG,KAAK;AAAA,IAC3D;AAAA,IACA,QAAQ,IAAI;AAAA,IACZ;AAAA,EACF,CAAC;AACH;AAWO,SAAS,sBAAsB,OAA0C;AAC9E,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,UAAU,MAAM,KAAK;AAC3B,MAAI,CAAC,QAAS,QAAO;AAErB,MAAI,gBAAgB,KAAK,OAAO,GAAG;AACjC,UAAM,OAAO,OAAO,OAAO;AAC3B,QAAI,CAAC,OAAO,SAAS,IAAI,KAAK,OAAO,EAAG,QAAO;AAC/C,WAAO,KAAK,MAAM,OAAO,GAAI;AAAA,EAC/B;AAKA,MAAI,CAAC,cAAc,KAAK,OAAO,EAAG,QAAO;AACzC,QAAM,SAAS,KAAK,MAAM,OAAO;AACjC,MAAI,CAAC,OAAO,SAAS,MAAM,EAAG,QAAO;AACrC,QAAM,QAAQ,SAAS,KAAK,IAAI;AAChC,SAAO,QAAQ,IAAI,QAAQ;AAC7B;AAEA,SAAS,iBAAiB,QAAoC;AAC5D,MAAI,WAAW,IAAK,QAAO;AAC3B,MAAI,WAAW,IAAK,QAAO;AAC3B,MAAI,WAAW,IAAK,QAAO;AAC3B,MAAI,UAAU,OAAO,SAAS,IAAK,QAAO;AAC1C,SAAO;AACT;;;ACxIO,IAAM,cAAc;AACpB,IAAM,WAAW;;;ACQjB,IAAM,mBAAmB;AAwCzB,IAAM,qBAAqB;AAkC3B,IAAM,aAAN,MAAiB;AAAA,EACtB,YAA6B,QAA0B;AAA1B;AAAA,EAA2B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWxD,MAAM,QACJ,QACA,MACA,UAA8B,CAAC,GACnB;AAMZ,QAAI,KAAK,OAAO,cAAc;AAC5B,aAAO,2BAA8B,IAAI;AAAA,IAC3C;AAEA,UAAM,MAAM,KAAK,SAAS,MAAM,QAAQ,KAAK;AAE7C,UAAM,UAAkC;AAAA,MACtC,eAAe,UAAU,KAAK,OAAO,SAAS;AAAA,MAC9C,yBAAyB,GAAG,QAAQ,IAAI,KAAK,OAAO,UAAU;AAAA,MAC9D,QAAQ;AAAA,IACV;AACA,QAAI,QAAQ,gBAAgB;AAG1B,cAAQ,iBAAiB,IAAI,QAAQ;AAAA,IACvC;AACA,QAAI;AACJ,QAAI,QAAQ,SAAS,QAAW;AAC9B,cAAQ,cAAc,IAAI;AAC1B,iBAAW,KAAK,UAAU,QAAQ,IAAI;AAAA,IACxC;AAWA,UAAM,mBAAmB,QAAQ,aAAa,KAAK,OAAO,aAAa;AACvE,UAAM,aACJ,OAAO,oBAAoB,eAAe,mBAAmB,IACzD,IAAI,gBAAgB,IACpB;AACN,QAAI,gBAAsD;AAC1D,QAAI,cAAc,mBAAmB,GAAG;AACtC,sBAAgB,WAAW,MAAM,WAAW,MAAM,GAAG,gBAAgB;AAAA,IACvE;AAEA,QAAI;AACJ,QAAI;AACF,iBAAW,MAAM,MAAM,KAAK;AAAA,QAC1B;AAAA,QACA;AAAA,QACA,MAAM;AAAA,QACN,WAAW,QAAQ,cAAc;AAAA,QACjC,QAAQ,YAAY;AAAA,MACtB,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,YAAM,UAAU,YAAY,QAAQ,YAAY;AAChD,YAAM,IAAI,eAAe;AAAA,QACvB,MAAM;AAAA,QACN,MAAM,UAAU,oBAAoB;AAAA,QACpC,SAAS,UACL,cAAc,IAAI,kBAAkB,gBAAgB,OACpD,eAAe,QACb,IAAI,UACJ;AAAA,MACR,CAAC;AAAA,IACH,UAAE;AACA,UAAI,kBAAkB,KAAM,cAAa,aAAa;AAAA,IACxD;AAEA,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,SAAS,MAAM,2BAA2B,QAAQ;AAMxD,UAAI,KAAK,OAAO,eAAe;AAC7B,YAAI;AAAE,eAAK,OAAO,cAAc,MAAM;AAAA,QAAG,QAAQ;AAAA,QAAgB;AAAA,MACnE;AACA,YAAM;AAAA,IACR;AAIA,QAAI,SAAS,WAAW,IAAK,QAAO;AAEpC,QAAI;AACF,aAAQ,MAAM,SAAS,KAAK;AAAA,IAC9B,SAAS,KAAK;AACZ,YAAM,IAAI,eAAe;AAAA,QACvB,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,QACT,WAAW,SAAS,QAAQ,IAAI,cAAc,KAAK;AAAA,QACnD,QAAQ,SAAS;AAAA,MACnB,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI,iBAA0B;AAC5B,WAAO,KAAK,OAAO,iBAAiB;AAAA,EACtC;AAAA,EAEQ,SAAS,MAAc,OAAoD;AACjF,UAAM,OAAO,KAAK,OAAO,QAAQ,QAAQ,QAAQ,EAAE;AACnD,UAAM,YAAY,KAAK,WAAW,GAAG,IAAI,OAAO,IAAI,IAAI;AACxD,QAAI,MAAM,OAAO;AACjB,QAAI,OAAO;AACT,YAAM,SAAS,IAAI,gBAAgB;AACnC,iBAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,KAAK,GAAG;AAC1C,YAAI,OAAO,MAAM,YAAY,EAAE,SAAS,EAAG,QAAO,OAAO,GAAG,CAAC;AAAA,MAC/D;AACA,YAAM,KAAK,OAAO,SAAS;AAC3B,UAAI,GAAI,SAAQ,IAAI,SAAS,GAAG,IAAI,MAAM,OAAO;AAAA,IACnD;AACA,WAAO;AAAA,EACT;AACF;AAcA,IAAI,oBAAmC;AACvC,SAAS,2BAA8B,MAAiB;AACtD,MAAI,KAAK,WAAW,gBAAgB,GAAG;AACrC,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,IAAI;AAAA,MACJ,WAAW;AAAA,MACX,OAAO;AAAA,MACP,UAAU;AAAA,MACV,KAAK;AAAA,MACL,YAAY,KAAK,IAAI;AAAA,IACvB;AAAA,EACF;AACA,MAAI,KAAK,WAAW,iBAAiB,GAAG;AACtC,QAAI,CAAC,mBAAmB;AACtB,YAAM,OACJ,OAAO,WAAW,eAAe,gBAAgB,SAC7C,OAAO,WAAW,EAAE,QAAQ,MAAM,EAAE,EAAE,MAAM,GAAG,EAAE,IACjD,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,EAAE;AAC5C,0BAAoB,gBAAgB,IAAI;AAAA,IAC1C;AACA,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,qBAAqB;AAAA,MACrB,QAAQ,CAAC;AAAA,MACT,cAAc;AAAA,MACd,KAAK;AAAA,IACP;AAAA,EACF;AACA,MAAI,KAAK,WAAW,eAAe,GAAG;AACpC,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,MAAM,CAAC;AAAA,MACP,qBAAqB,qBAAqB;AAAA,MAC1C,KAAK;AAAA,IACP;AAAA,EACF;AACA,MAAI,KAAK,WAAW,SAAS,GAAG;AAC9B,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,KAAK;AAAA,IACP;AAAA,EACF;AAEA,SAAO,CAAC;AACV;;;AC1QA,IAAM,WAAW;AACjB,IAAM,aAAa;AAOZ,IAAM,gBAAN,MAAoB;AAAA,EAUzB,YACmB,SACA,QACjB,WACA;AAHiB;AACA;AAGjB,SAAK,YAAY,aAAa;AAC9B,UAAM,kBAAkB,QAAQ,QAAQ,SAAS,QAAQ;AACzD,UAAM,oBAAoB,QAAQ,QAAQ,SAAS,UAAU;AAC7D,UAAM,oBAAoB,KAAK,WAAW,QAAQ,SAAS,QAAQ,KAAK;AACxE,UAAM,sBAAsB,KAAK,WAAW,QAAQ,SAAS,UAAU,KAAK;AAM5E,UAAM,OAAO,mBAAmB;AAChC,UAAM,SAAS,qBAAqB;AAEpC,SAAK,QAAQ;AAAA,MACX,aAAa,QAAQ,KAAK,gBAAgB;AAAA,MAC1C,qBAAqB;AAAA,IACvB;AAOA,QAAI,CAAC,mBAAmB,CAAC,mBAAmB;AAC1C,WAAK,UAAU,SAAS,UAAU,KAAK,MAAM,WAAW;AAAA,IAC1D;AACA,QAAI,WAAW,CAAC,qBAAqB,CAAC,sBAAsB;AAC1D,WAAK,UAAU,SAAS,YAAY,MAAM;AAAA,IAC5C;AAAA,EACF;AAAA;AAAA,EAGA,IAAI,cAAsB;AACxB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA;AAAA,EAGA,IAAI,sBAAqC;AACvC,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA;AAAA,EAGA,uBAAuB,OAAqB;AAC1C,SAAK,MAAM,sBAAsB;AACjC,SAAK,UAAU,KAAK,SAAS,YAAY,KAAK;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAc;AACZ,SAAK,WAAW,KAAK,SAAS,QAAQ;AACtC,SAAK,WAAW,KAAK,SAAS,UAAU;AACxC,SAAK,QAAQ;AAAA,MACX,aAAa,KAAK,gBAAgB;AAAA,MAClC,qBAAqB;AAAA,IACvB;AACA,SAAK,UAAU,KAAK,SAAS,UAAU,KAAK,MAAM,WAAW;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,kBAA0B;AAChC,UAAM,KAAK,KAAK,IAAI,EAAE,SAAS,EAAE;AACjC,UAAM,OAAO,YAAY,EAAE;AAC3B,WAAO,QAAQ,EAAE,GAAG,IAAI;AAAA,EAC1B;AAAA,EAEQ,UAAU,KAAa,OAAqB;AAClD,QAAI;AAAE,WAAK,QAAQ,QAAQ,KAAK,KAAK;AAAA,IAAG,QAAQ;AAAA,IAA6B;AAC7E,QAAI,KAAK,WAAW;AAClB,UAAI;AAAE,aAAK,UAAU,QAAQ,KAAK,KAAK;AAAA,MAAG,QAAQ;AAAA,MAA0B;AAAA,IAC9E;AAAA,EACF;AAAA,EAEQ,WAAW,KAAmB;AACpC,QAAI;AAAE,WAAK,QAAQ,WAAW,GAAG;AAAA,IAAG,QAAQ;AAAA,IAAgB;AAC5D,QAAI,KAAK,WAAW;AAClB,UAAI;AAAE,aAAK,UAAU,WAAW,GAAG;AAAA,MAAG,QAAQ;AAAA,MAAgB;AAAA,IAChE;AAAA,EACF;AACF;AAYO,SAAS,YAAY,OAAuB;AACjD,QAAM,WAAW;AACjB,QAAM,MAAgB,CAAC;AACvB,QAAM,YAAa,WAAgF;AACnG,MAAI,WAAW,iBAAiB;AAC9B,UAAM,MAAM,IAAI,WAAW,KAAK;AAChC,cAAU,gBAAgB,GAAG;AAC7B,aAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC9B,UAAI,KAAK,SAAS,IAAI,CAAC,IAAK,SAAS,MAAM,KAAK,GAAG;AAAA,IACrD;AAAA,EACF,OAAO;AACL,aAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC9B,UAAI,KAAK,SAAS,KAAK,MAAM,KAAK,OAAO,IAAI,SAAS,MAAM,CAAC,KAAK,GAAG;AAAA,IACvE;AAAA,EACF;AACA,SAAO,IAAI,KAAK,EAAE;AACpB;;;AC1IA,IAAM,IAAI,IAAI,YAAY;AAAA,EACxB;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AAAA,EAC5D;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AAAA,EAC5D;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AAAA,EAC5D;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AAAA,EAC5D;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AAAA,EAC5D;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AAAA,EAC5D;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AAAA,EAC5D;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AAAA,EAC5D;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AAAA,EAC5D;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AAAA,EAC5D;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AACtC,CAAC;AAED,SAAS,UAAU,OAA2B;AAG5C,MAAI,OAAO,gBAAgB,aAAa;AACtC,WAAO,IAAI,YAAY,EAAE,OAAO,KAAK;AAAA,EACvC;AAEA,QAAM,MAAgB,CAAC;AACvB,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,QAAI,YAAY,MAAM,WAAW,CAAC;AAClC,QAAI,aAAa,SAAU,aAAa,SAAU,IAAI,IAAI,MAAM,QAAQ;AACtE,YAAM,OAAO,MAAM,WAAW,IAAI,CAAC;AACnC,UAAI,QAAQ,SAAU,QAAQ,OAAQ;AACpC,oBAAY,SAAY,YAAY,SAAW,OAAO,OAAO;AAC7D;AAAA,MACF;AAAA,IACF;AACA,QAAI,YAAY,KAAM;AACpB,UAAI,KAAK,SAAS;AAAA,IACpB,WAAW,YAAY,MAAO;AAC5B,UAAI,KAAK,MAAQ,aAAa,CAAE;AAChC,UAAI,KAAK,MAAQ,YAAY,EAAK;AAAA,IACpC,WAAW,YAAY,OAAS;AAC9B,UAAI,KAAK,MAAQ,aAAa,EAAG;AACjC,UAAI,KAAK,MAAS,aAAa,IAAK,EAAK;AACzC,UAAI,KAAK,MAAQ,YAAY,EAAK;AAAA,IACpC,OAAO;AACL,UAAI,KAAK,MAAQ,aAAa,EAAG;AACjC,UAAI,KAAK,MAAS,aAAa,KAAM,EAAK;AAC1C,UAAI,KAAK,MAAS,aAAa,IAAK,EAAK;AACzC,UAAI,KAAK,MAAQ,YAAY,EAAK;AAAA,IACpC;AAAA,EACF;AACA,SAAO,IAAI,WAAW,GAAG;AAC3B;AAMO,SAAS,UAAU,OAAuB;AAC/C,QAAM,QAAQ,UAAU,KAAK;AAC7B,QAAM,YAAY,MAAM,SAAS;AAGjC,QAAM,aAAa,KAAK,OAAO,MAAM,SAAS,IAAI,MAAM,EAAE;AAC1D,QAAM,SAAS,IAAI,WAAW,aAAa,EAAE;AAC7C,SAAO,IAAI,KAAK;AAChB,SAAO,MAAM,MAAM,IAAI;AAGvB,QAAM,OAAO,KAAK,MAAM,YAAY,UAAW;AAC/C,QAAM,MAAM,cAAc;AAC1B,QAAM,YAAY,OAAO,SAAS;AAClC,SAAO,YAAY,CAAC,IAAK,SAAS,KAAM;AACxC,SAAO,YAAY,CAAC,IAAK,SAAS,KAAM;AACxC,SAAO,YAAY,CAAC,IAAK,SAAS,IAAK;AACvC,SAAO,YAAY,CAAC,IAAI,OAAO;AAC/B,SAAO,YAAY,CAAC,IAAK,QAAQ,KAAM;AACvC,SAAO,YAAY,CAAC,IAAK,QAAQ,KAAM;AACvC,SAAO,YAAY,CAAC,IAAK,QAAQ,IAAK;AACtC,SAAO,YAAY,CAAC,IAAI,MAAM;AAE9B,QAAM,IAAI,IAAI,YAAY;AAAA,IACxB;AAAA,IAAY;AAAA,IAAY;AAAA,IAAY;AAAA,IAAY;AAAA,IAAY;AAAA,IAC5D;AAAA,IAAY;AAAA,EACd,CAAC;AACD,QAAM,IAAI,IAAI,YAAY,EAAE;AAO5B,WAAS,QAAQ,GAAG,QAAQ,YAAY,SAAS;AAC/C,UAAM,SAAS,QAAQ;AACvB,aAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,QAAE,CAAC,KACC,OAAO,SAAS,IAAI,CAAC,KAAM,KAC1B,OAAO,SAAS,IAAI,IAAI,CAAC,KAAM,KAC/B,OAAO,SAAS,IAAI,IAAI,CAAC,KAAM,IAChC,OAAO,SAAS,IAAI,IAAI,CAAC,OAC3B;AAAA,IACJ;AACA,aAAS,IAAI,IAAI,IAAI,IAAI,KAAK;AAC5B,YAAM,MAAM,EAAE,IAAI,EAAE;AACpB,YAAM,KAAK,EAAE,IAAI,CAAC;AAClB,YAAM,MAAO,QAAQ,IAAM,OAAO,OAAS,QAAQ,KAAO,OAAO,MAAQ,QAAQ;AACjF,YAAM,MAAO,OAAO,KAAO,MAAM,OAAS,OAAO,KAAO,MAAM,MAAQ,OAAO;AAC7E,QAAE,CAAC,IAAK,EAAE,IAAI,EAAE,IAAK,KAAK,EAAE,IAAI,CAAC,IAAK,OAAQ;AAAA,IAChD;AAEA,QAAI,IAAI,EAAE,CAAC,GAAI,IAAI,EAAE,CAAC,GAAI,IAAI,EAAE,CAAC,GAAI,IAAI,EAAE,CAAC;AAC5C,QAAI,IAAI,EAAE,CAAC,GAAI,IAAI,EAAE,CAAC,GAAI,IAAI,EAAE,CAAC,GAAI,IAAI,EAAE,CAAC;AAE5C,aAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,YAAM,MAAO,MAAM,IAAM,KAAK,OAAS,MAAM,KAAO,KAAK,OAAS,MAAM,KAAO,KAAK;AACpF,YAAM,KAAM,IAAI,IAAM,CAAC,IAAI;AAC3B,YAAM,QAAS,IAAI,KAAK,KAAK,EAAE,CAAC,IAAK,EAAE,CAAC,MAAQ;AAChD,YAAM,MAAO,MAAM,IAAM,KAAK,OAAS,MAAM,KAAO,KAAK,OAAS,MAAM,KAAO,KAAK;AACpF,YAAM,MAAO,IAAI,IAAM,IAAI,IAAM,IAAI;AACrC,YAAM,QAAS,KAAK,QAAS;AAC7B,UAAI;AACJ,UAAI;AACJ,UAAI;AACJ,UAAK,IAAI,UAAW;AACpB,UAAI;AACJ,UAAI;AACJ,UAAI;AACJ,UAAK,QAAQ,UAAW;AAAA,IAC1B;AAEA,MAAE,CAAC,IAAK,EAAE,CAAC,IAAK,MAAO;AACvB,MAAE,CAAC,IAAK,EAAE,CAAC,IAAK,MAAO;AACvB,MAAE,CAAC,IAAK,EAAE,CAAC,IAAK,MAAO;AACvB,MAAE,CAAC,IAAK,EAAE,CAAC,IAAK,MAAO;AACvB,MAAE,CAAC,IAAK,EAAE,CAAC,IAAK,MAAO;AACvB,MAAE,CAAC,IAAK,EAAE,CAAC,IAAK,MAAO;AACvB,MAAE,CAAC,IAAK,EAAE,CAAC,IAAK,MAAO;AACvB,MAAE,CAAC,IAAK,EAAE,CAAC,IAAK,MAAO;AAAA,EACzB;AAEA,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,WAAO,EAAE,CAAC,EAAG,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AAAA,EAC3C;AACA,SAAO;AACT;;;AC9GA,IAAM,yBAAyB,KAAK,KAAK,KAAK;AAK9C,IAAM,cAAc;AAKpB,IAAM,eAAe;AAEd,IAAM,mBAAN,MAAM,kBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAwB5B,YACE,SACA,mBAAmB,0BACnB,eAAe,wBACf;AA3BF,SAAQ,MAA2B,CAAC;AACpC,SAAQ,cAAc;AACtB,SAAQ,sBAAsB;AAC9B,SAAQ,YAAY,oBAAI,IAA0B;AAClD,SAAQ,qBAAqB;AAI7B,SAAQ,gBAAwB;AAoB9B,SAAK,UAAU;AACf,SAAK,mBAAmB;AACxB,SAAK,eAAe;AACpB,SAAK,QAAQ;AAAA,EACf;AAAA;AAAA,EAGA,IAAY,aAAqB;AAC/B,WAAO,GAAG,KAAK,gBAAgB,IAAI,KAAK,aAAa;AAAA,EACvD;AAAA;AAAA;AAAA,EAIA,IAAY,WAAmB;AAC7B,WAAO,GAAG,KAAK,gBAAgB,IAAI,YAAY;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,gBAAgB,QAA+B;AACpD,QAAI,UAAU,QAAQ,WAAW,GAAI,QAAO;AAC5C,WAAO,UAAU,MAAM;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,WAAW,QAA6B;AACtC,UAAM,aAAa,kBAAiB,gBAAgB,MAAM;AAC1D,QAAI,eAAe,KAAK,eAAe;AAIrC,WAAK,MAAM,CAAC;AACZ,WAAK,cAAc;AACnB,WAAK,sBAAsB;AAC3B,WAAK,OAAO;AAGZ,WAAK,QAAQ;AACb;AAAA,IACF;AACA,SAAK,gBAAgB;AAErB,SAAK,MAAM,CAAC;AACZ,SAAK,cAAc;AACnB,SAAK,sBAAsB;AAC3B,SAAK,QAAQ;AACb,SAAK,OAAO;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,WAAW,KAAsB;AAC/B,UAAM,SAAS,KAAK,IAAI,IAAI;AAC5B,WAAO,KAAK,IAAI;AAAA,MACd,CAAC,MACC,EAAE,QAAQ,OACV,EAAE,aACD,EAAE,cAAc,QAAQ,EAAE,aAAa;AAAA,IAC5C;AAAA,EACF;AAAA;AAAA,EAGA,OAA4B;AAC1B,WAAO,KAAK,IAAI,MAAM;AAAA,EACxB;AAAA;AAAA,EAGA,IAAI,YAAoB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,IAAI,UAAmB;AACrB,QAAI,KAAK,sBAAsB,KAAK,YAAa,QAAO;AACxD,WACE,KAAK,cAAc,KACnB,KAAK,IAAI,IAAI,KAAK,cAAc,KAAK;AAAA,EAEzC;AAAA;AAAA,EAGA,IAAI,kBAA0B;AAC5B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,iBAAyB;AAC3B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,oBAA0B;AACxB,SAAK,sBAAsB,KAAK,IAAI;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,YAAY,cAAyC;AACnD,SAAK,MAAM,aAAa,MAAM;AAC9B,SAAK,cAAc,KAAK,IAAI;AAC5B,SAAK,sBAAsB;AAC3B,SAAK,QAAQ;AACb,SAAK,oBAAoB,KAAK,aAAa;AAC3C,SAAK,OAAO;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAc;AACZ,SAAK,MAAM,CAAC;AACZ,SAAK,cAAc;AACnB,SAAK,sBAAsB;AAC3B,QAAI,KAAK,SAAS;AAChB,UAAI;AACF,aAAK,QAAQ,WAAW,KAAK,UAAU;AAAA,MACzC,QAAQ;AAAA,MAER;AAAA,IACF;AACA,SAAK,sBAAsB,KAAK,aAAa;AAC7C,SAAK,OAAO;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,WAAiB;AACf,SAAK,MAAM,CAAC;AACZ,SAAK,cAAc;AACnB,SAAK,sBAAsB;AAC3B,SAAK,gBAAgB;AACrB,QAAI,KAAK,SAAS;AAChB,YAAM,WAAW,KAAK,UAAU;AAChC,iBAAW,UAAU,UAAU;AAC7B,YAAI;AACF,eAAK,QAAQ,WAAW,GAAG,KAAK,gBAAgB,IAAI,MAAM,EAAE;AAAA,QAC9D,QAAQ;AAAA,QAER;AAAA,MACF;AAGA,UAAI;AACF,aAAK,QAAQ,WAAW,GAAG,KAAK,gBAAgB,IAAI,WAAW,EAAE;AAAA,MACnE,QAAQ;AAAA,MAER;AACA,UAAI;AACF,aAAK,QAAQ,WAAW,KAAK,QAAQ;AAAA,MACvC,QAAQ;AAAA,MAER;AAAA,IACF;AACA,SAAK,OAAO;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAU,UAA4C;AACpD,SAAK,UAAU,IAAI,QAAQ;AAC3B,QAAI,eAAe;AACnB,WAAO,MAAM;AACX,UAAI,aAAc;AAClB,qBAAe;AACf,WAAK,UAAU,OAAO,QAAQ;AAAA,IAChC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,UAAgB;AACtB,QAAI,CAAC,KAAK,QAAS;AACnB,QAAI;AACF,YAAM,MAAM,KAAK,QAAQ,QAAQ,KAAK,UAAU;AAChD,UAAI,CAAC,IAAK;AACV,YAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,UAAI,UAAU,OAAO,MAAM,KAAK,MAAM,QAAQ,OAAO,YAAY,GAAG;AAClE,aAAK,MAAM,OAAO;AAClB,aAAK,cACH,OAAO,OAAO,gBAAgB,WAAW,OAAO,cAAc;AAAA,MAClE;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAAA;AAAA,EAGQ,UAAgB;AACtB,QAAI,CAAC,KAAK,QAAS;AACnB,QAAI;AACF,YAAM,OAAuB;AAAA,QAC3B,GAAG;AAAA,QACH,cAAc,KAAK;AAAA,QACnB,aAAa,KAAK;AAAA,MACpB;AACA,WAAK,QAAQ,QAAQ,KAAK,YAAY,KAAK,UAAU,IAAI,CAAC;AAAA,IAC5D,QAAQ;AAAA,IAGR;AAAA,EACF;AAAA;AAAA,EAGQ,YAAsB;AAC5B,QAAI,CAAC,KAAK,QAAS,QAAO,CAAC;AAC3B,QAAI;AACF,YAAM,MAAM,KAAK,QAAQ,QAAQ,KAAK,QAAQ;AAC9C,UAAI,CAAC,IAAK,QAAO,CAAC;AAClB,YAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,UAAI,MAAM,QAAQ,MAAM,GAAG;AACzB,eAAO,OAAO,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ;AAAA,MAChE;AACA,aAAO,CAAC;AAAA,IACV,QAAQ;AACN,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA;AAAA,EAGQ,oBAAoB,QAAsB;AAChD,QAAI,CAAC,KAAK,QAAS;AACnB,UAAM,WAAW,KAAK,UAAU;AAChC,QAAI,SAAS,SAAS,MAAM,EAAG;AAC/B,aAAS,KAAK,MAAM;AACpB,QAAI;AACF,WAAK,QAAQ,QAAQ,KAAK,UAAU,KAAK,UAAU,QAAQ,CAAC;AAAA,IAC9D,QAAQ;AAAA,IAER;AAAA,EACF;AAAA;AAAA,EAGQ,sBAAsB,QAAsB;AAClD,QAAI,CAAC,KAAK,QAAS;AACnB,UAAM,WAAW,KAAK,UAAU;AAChC,UAAM,OAAO,SAAS,OAAO,CAAC,MAAM,MAAM,MAAM;AAChD,QAAI,KAAK,WAAW,SAAS,OAAQ;AACrC,QAAI;AACF,UAAI,KAAK,WAAW,GAAG;AACrB,aAAK,QAAQ,WAAW,KAAK,QAAQ;AAAA,MACvC,OAAO;AACL,aAAK,QAAQ,QAAQ,KAAK,UAAU,KAAK,UAAU,IAAI,CAAC;AAAA,MAC1D;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEQ,SAAe;AACrB,QAAI,KAAK,UAAU,SAAS,EAAG;AAC/B,UAAM,WAAW,KAAK,IAAI,MAAM;AAIhC,UAAM,oBAAoB,CAAC,GAAG,KAAK,SAAS;AAC5C,eAAW,YAAY,mBAAmB;AACxC,UAAI;AACF,iBAAS,QAAQ;AAAA,MACnB,QAAQ;AAGN,aAAK,sBAAsB;AAAA,MAC7B;AAAA,IACF;AAAA,EACF;AACF;;;ACtYO,SAAS,aAAa,KAAqB;AAChD,SAAO;AAAA,IACL,IAAI,MAAM,GAAG,CAAC;AAAA,IACd,IAAI,MAAM,GAAG,EAAE;AAAA,IACf,IAAI,MAAM,IAAI,EAAE;AAAA,IAChB,IAAI,MAAM,IAAI,EAAE;AAAA,IAChB,IAAI,MAAM,IAAI,EAAE;AAAA,EAClB,EAAE,KAAK,GAAG;AACZ;AAcO,SAAS,gCAAgC,MAAoC;AAClF,MAAI;AACJ,MAAI,KAAK,SAAS,SAAS;AACzB,iBAAa,KAAK,yBAAyB;AAAA,EAC7C,WAAW,KAAK,SAAS,UAAU;AACjC,iBAAa,KAAK,iBAAiB;AAAA,EACrC,OAAO;AACL,iBAAa;AAAA,EACf;AACA,MAAI,CAAC,YAAY;AACf,UAAM,IAAI;AAAA,MACR,uEACW,KAAK,IAAI;AAAA,IAEtB;AAAA,EACF;AAIA,QAAM,aAAa,4BAA4B,KAAK,IAAI,IAAI,UAAU;AACtE,SAAO,aAAa,UAAU,UAAU,CAAC;AAC3C;;;AC3CA,IAAM,eAAe;AACrB,IAAM,cAAc;AACpB,IAAM,iBAAiB;AACvB,IAAM,eAAe;AAcd,SAAS,iBACd,UACA,cACA,UAA8B,CAAC,GAC/B,SAAuB,KAAK,QACpB;AACR,QAAM,OAAO,QAAQ,UAAU;AAC/B,QAAM,MAAM,QAAQ,SAAS;AAC7B,QAAM,SAAS,QAAQ,UAAU;AAGjC,QAAM,eAAe,KAAK,IAAI,UAAU,EAAE;AAC1C,QAAM,UAAU,KAAK,IAAI,KAAK,OAAO,KAAK,IAAI,QAAQ,YAAY,CAAC;AAGnE,QAAM,WAAW,UAAU,OAAO;AAUlC,MAAI,iBAAiB,QAAW;AAC9B,UAAM,kBAAkB,KAAK,KAAK,KAAK;AACvC,UAAM,WAAW,KAAK,IAAI,iBAAiB,YAAY;AACvD,QAAI,WAAW,SAAU,QAAO;AAAA,EAClC;AACA,SAAO,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,CAAC;AACzC;AAEO,IAAM,cAAN,MAAkB;AAAA,EAEvB,YAA6B,UAA8B,CAAC,GAAG;AAAlC;AAD7B,SAAQ,WAAW;AAAA,EAC6C;AAAA;AAAA,EAGhE,IAAI,sBAA8B;AAChC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,YAAqB;AACvB,WAAO,KAAK,aAAa,KAAK,QAAQ,sBAAsB;AAAA,EAC9D;AAAA;AAAA,EAGA,UAAU,cAAuB,SAAuB,KAAK,QAAgB;AAC3E,UAAM,QAAQ,iBAAiB,KAAK,UAAU,cAAc,KAAK,SAAS,MAAM;AAChF,SAAK,YAAY;AACjB,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,gBAAsB;AACpB,SAAK,WAAW;AAAA,EAClB;AACF;;;ACpEA,IAAM,kBAAkB;AA0HjB,IAAM,aAAN,MAAiB;AAAA,EA6CtB,YAA6B,KAAuB;AAAvB;AA5C7B,SAAQ,SAAwB,CAAC;AAoBjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAAQ,eAAqC;AAC7C,SAAQ,iBAAgC;AACxC,SAAQ,UAAU;AAClB,SAAQ,WAAW;AACnB,SAAQ,cAAc;AACtB,SAAQ,YAA2B;AACnC,SAAQ,cAAmC;AAC3C,SAAQ,kBAAkB;AAC1B,SAAQ,cAA6B;AAUrC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAAQ,SAAS;AAEjB;AAAA,SAAQ,aAAa;AAKnB,SAAK,QAAQ,IAAI,YAAY,IAAI,SAAS,CAAC,CAAC;AAC5C,SAAK,aAAa,IAAI,mBAAmB;AAOzC,QAAI,KAAK,YAAY;AACnB,YAAM,WAAW,KAAK,WAAW,KAAK;AACtC,UAAI,SAAS,SAAS,GAAG;AAGvB,YAAI,SAAS,SAAS,iBAAiB;AACrC,eAAK,WAAW,SAAS,SAAS;AAClC,eAAK,SAAS,SAAS,MAAM,SAAS,SAAS,eAAe;AAAA,QAChE,OAAO;AACL,eAAK,SAAS;AAAA,QAChB;AACA,aAAK,IAAI,iBAAiB,KAAK,OAAO,MAAM;AAG5C,aAAK,kBAAkB;AAAA,MACzB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,QAAQ,OAA0B;AAChC,SAAK,OAAO,KAAK,KAAK;AACtB,QAAI,KAAK,OAAO,SAAS,iBAAiB;AACxC,YAAM,WAAW,KAAK,OAAO,SAAS;AACtC,WAAK,OAAO,OAAO,GAAG,QAAQ;AAC9B,WAAK,WAAW;AAChB,WAAK,IAAI,SAAS,QAAQ;AAAA,IAC5B;AACA,SAAK,IAAI,iBAAiB,KAAK,OAAO,MAAM;AAC5C,SAAK,WAAW;AAChB,QAAI,KAAK,OAAO,UAAU,KAAK,IAAI,WAAW;AAC5C,WAAK,KAAK,MAAM;AAAA,IAClB,OAAO;AACL,WAAK,kBAAkB;AAAA,IACzB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAwBA,MAAM,MAAM,UAAmC,CAAC,GAAmC;AAKjF,QAAI,KAAK,OAAQ,QAAO;AAKxB,QAAI;AACJ,QAAI;AACJ,QAAI,KAAK,iBAAiB,QAAQ,KAAK,mBAAmB,MAAM;AAC9D,cAAQ,KAAK;AACb,gBAAU,KAAK;AAAA,IACjB,OAAO;AACL,UAAI,KAAK,OAAO,WAAW,EAAG,QAAO;AACrC,cAAQ,KAAK,OAAO,OAAO,CAAC;AAC5B,gBAAU,KAAK,YAAY;AAC3B,WAAK,eAAe;AACpB,WAAK,iBAAiB;AACtB,WAAK,YAAY,MAAM;AACvB,WAAK,IAAI,iBAAiB,KAAK,OAAO,MAAM;AAG5C,WAAK,WAAW;AAAA,IAClB;AACA,SAAK,iBAAiB;AACtB,SAAK,cAAc;AAEnB,QAAI;AACF,YAAM,MAAM,KAAK,IAAI,SAAS;AAC9B,YAAM,SAAS,MAAM,KAAK,IAAI,KAAK,QAAwB,QAAQ,WAAW;AAAA,QAC5E,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAOJ,iBAAiB;AAAA,UACjB,OAAO,IAAI;AAAA,UACX,aAAa,IAAI;AAAA,UACjB,KAAK,IAAI;AAAA,UACT,QAAQ;AAAA,QACV;AAAA,QACA,WAAW,QAAQ,cAAc;AAAA,QACjC,gBAAgB;AAAA,MAClB,CAAC;AACD,WAAK,cAAc,KAAK,IAAI;AAC5B,WAAK,YAAY;AACjB,WAAK,YAAY,MAAM;AACvB,WAAK,eAAe;AACpB,WAAK,iBAAiB;AACtB,WAAK,MAAM,cAAc;AAGzB,WAAK,WAAW;AAChB,UAAI,CAAC,KAAK,iBAAiB;AACzB,aAAK,kBAAkB;AACvB,aAAK,IAAI,sBAAsB;AAAA,MACjC;AACA,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,WAAK,YAAY;AASjB,UAAI,kBAAkB,GAAG,GAAG;AAC1B,aAAK,SAAS;AACd,aAAK,SAAS,CAAC,GAAG,OAAO,GAAG,KAAK,MAAM;AACvC,YAAI,KAAK,OAAO,SAAS,iBAAiB;AACxC,gBAAM,WAAW,KAAK,OAAO,SAAS;AACtC,eAAK,OAAO,OAAO,GAAG,QAAQ;AAC9B,eAAK,WAAW;AAAA,QAClB;AACA,aAAK,eAAe;AACpB,aAAK,iBAAiB;AACtB,aAAK,YAAY,MAAM;AACvB,aAAK,WAAW;AAChB,aAAK,IAAI,iBAAiB,KAAK,OAAO,MAAM;AAC5C,cAAM,aAAa,sBAAsB,GAAG;AAC5C,YAAI,CAAC,KAAK,YAAY;AACpB,eAAK,aAAa;AAGlB,kBAAQ;AAAA,YACN,kNAGoB,aAAa,UAAU,UAAU,KAAK,EAAE;AAAA,UAC9D;AAAA,QACF;AACA,aAAK,IAAI,WAAW,EAAE,YAAY,SAAS,wBAAwB,GAAG,EAAE,CAAC;AACzE,eAAO;AAAA,MACT;AAQA,UAAI,eAAe,GAAG,GAAG;AACvB,cAAM,eAAe,MAAM;AAC3B,aAAK,eAAe;AACpB,aAAK,iBAAiB;AACtB,aAAK,YAAY;AACjB,aAAK,WAAW;AAChB,aAAK,WAAW;AAChB,aAAK,IAAI,SAAS,YAAY;AAC9B,aAAK,IAAI,qBAAqB;AAAA,UAC5B,QAAS,IAA4B,UAAU;AAAA,UAC/C;AAAA,UACA,WAAW;AAAA,QACb,CAAC;AACD,eAAO;AAAA,MACT;AAMA,YAAM,eAAe,oBAAoB,GAAG;AAC5C,YAAM,QAAQ,KAAK,MAAM,UAAU,YAAY;AAC/C,WAAK,cAAc,KAAK;AACxB,WAAK,IAAI,mBAAmB;AAAA,QAC1B,SAAS;AAAA,QACT,qBAAqB,KAAK,MAAM;AAAA,QAChC;AAAA,QACA,WAAW;AAAA,MACb,CAAC;AACD,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA,EAGA,QAAc;AACZ,SAAK,iBAAiB;AACtB,SAAK,cAAc;AACnB,SAAK,SAAS,CAAC;AACf,SAAK,eAAe;AACpB,SAAK,iBAAiB;AACtB,SAAK,UAAU;AACf,SAAK,WAAW;AAChB,SAAK,YAAY;AACjB,SAAK,MAAM,cAAc;AACzB,SAAK,YAAY,MAAM;AACvB,SAAK,IAAI,iBAAiB,CAAC;AAAA,EAI7B;AAAA,EAEA,WAA4B;AAC1B,WAAO;AAAA;AAAA;AAAA;AAAA;AAAA,MAKL,UAAU,KAAK,OAAO;AAAA,MACtB,SAAS,KAAK;AAAA,MACd,UAAU,KAAK;AAAA,MACf,aAAa,KAAK;AAAA,MAClB,WAAW,KAAK;AAAA,MAChB,qBAAqB,KAAK,MAAM;AAAA,MAChC,aAAa,KAAK;AAAA,IACpB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI,wBAAuC;AACzC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,aAAmB;AACzB,QAAI,CAAC,KAAK,WAAY;AACtB,QAAI,KAAK,iBAAiB,MAAM;AAC9B,WAAK,WAAW,KAAK,KAAK,MAAM;AAChC;AAAA,IACF;AACA,SAAK,WAAW,KAAK,CAAC,GAAG,KAAK,cAAc,GAAG,KAAK,MAAM,CAAC;AAAA,EAC7D;AAAA;AAAA,EAIQ,oBAA0B;AAChC,SAAK,iBAAiB;AACtB,UAAM,QAAQ,KAAK,IAAI,aAAa;AACpC,SAAK,cAAc,MAAM,MAAM;AAC7B,WAAK,KAAK,MAAM;AAAA,IAClB,GAAG,KAAK,IAAI,UAAU;AAAA,EACxB;AAAA,EAEQ,cAAc,SAAuB;AAC3C,SAAK,iBAAiB;AACtB,SAAK,cAAc,KAAK,IAAI,IAAI;AAChC,UAAM,QAAQ,KAAK,IAAI,aAAa;AACpC,SAAK,cAAc,MAAM,MAAM;AAC7B,WAAK,KAAK,MAAM;AAAA,IAClB,GAAG,OAAO;AAAA,EACZ;AAAA,EAEQ,mBAAyB;AAC/B,QAAI,KAAK,aAAa;AACpB,WAAK,YAAY;AACjB,WAAK,cAAc;AAAA,IACrB;AAAA,EACF;AAAA,EAEQ,cAAsB;AAC5B,WAAO,SAAS,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC,GAAG,YAAY,EAAE,CAAC;AAAA,EAC3D;AACF;AAEA,SAAS,oBAAoB,KAAkC;AAC7D,MAAI,OAAO,OAAO,QAAQ,YAAY,kBAAkB,KAAK;AAC3D,UAAM,IAAK,IAAuB;AAClC,WAAO,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC,KAAK,KAAK,IAAI,IAAI;AAAA,EACrE;AACA,SAAO;AACT;AAaA,SAAS,eAAe,KAAuB;AAC7C,MAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO;AAC5C,QAAM,SAAU,IAA6B;AAC7C,MAAI,OAAO,WAAW,YAAY,CAAC,OAAO,SAAS,MAAM,EAAG,QAAO;AACnE,MAAI,SAAS,OAAO,UAAU,IAAK,QAAO;AAC1C,MAAI,WAAW,OAAO,WAAW,IAAK,QAAO;AAG7C,MAAI,WAAW,IAAK,QAAO;AAC3B,SAAO;AACT;AAUA,SAAS,kBAAkB,KAAuB;AAChD,MAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO;AAC5C,MAAK,IAA6B,WAAW,IAAK,QAAO;AACzD,SAAQ,IAA2B,SAAS;AAC9C;AAGA,SAAS,sBAAsB,KAAkC;AAC/D,QAAM,IAAK,KAAyC;AACpD,SAAO,OAAO,MAAM,YAAY,EAAE,SAAS,IAAI,IAAI;AACrD;AAGA,SAAS,wBAAwB,KAAkC;AACjE,QAAM,IAAK,KAAsC;AACjD,SAAO,OAAO,MAAM,YAAY,EAAE,SAAS,IAAI,IAAI;AACrD;AAEA,SAAS,iBAAiB,IAAgB,IAAwB;AAIhE,QAAM,KAAK,WAAW,IAAI,EAAE;AAC5B,MAAI,OAAQ,GAAyC,UAAU,YAAY;AACzE,QAAI;AACF,MAAC,GAAwC,MAAM;AAAA,IACjD,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO,MAAM,aAAa,EAAE;AAC9B;;;ACnhBO,IAAM,uBAAN,MAA2B;AAAA,EAQhC,YAA6B,SAAsC;AAAtC;AAN7B,SAAQ,iBAAiB;AAIzB;AAAA;AAAA;AAAA,SAAQ,kBAAwC;AAG9C,SAAK,MAAM,GAAG,QAAQ,MAAM;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,OAAsB;AACpB,QAAI;AACJ,QAAI;AACF,YAAM,KAAK,QAAQ,QAAQ,QAAQ,KAAK,GAAG;AAAA,IAC7C,QAAQ;AACN,aAAO,CAAC;AAAA,IACV;AACA,QAAI,CAAC,IAAK,QAAO,CAAC;AAClB,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,UAAI,CAAC,UAAU,OAAO,YAAY,KAAK,CAAC,MAAM,QAAQ,OAAO,MAAM,GAAG;AACpE,eAAO,CAAC;AAAA,MACV;AACA,aAAO,OAAO;AAAA,IAChB,QAAQ;AAEN,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,KAAK,UAAwC;AAG3C,SAAK,kBAAkB,SAAS,MAAM;AACtC,QAAI,KAAK,eAAgB;AACzB,SAAK,iBAAiB;AACtB,mBAAe,MAAM,KAAK,WAAW,CAAC;AAAA,EACxC;AAAA;AAAA,EAGA,SAAS,UAAwC;AAC/C,SAAK,kBAAkB,SAAS,MAAM;AACtC,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA,EAGA,QAAc;AACZ,SAAK,kBAAkB;AACvB,SAAK,iBAAiB;AACtB,QAAI;AACF,WAAK,QAAQ,QAAQ,WAAW,KAAK,GAAG;AAAA,IAC1C,QAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEQ,aAAmB;AACzB,SAAK,iBAAiB;AACtB,UAAM,WAAW,KAAK;AACtB,SAAK,kBAAkB;AACvB,QAAI,aAAa,KAAM;AAEvB,QAAI,SAAS,WAAW,GAAG;AACzB,UAAI;AACF,aAAK,QAAQ,QAAQ,WAAW,KAAK,GAAG;AAAA,MAC1C,QAAQ;AAAA,MAER;AACA;AAAA,IACF;AAEA,UAAM,OAAuB,EAAE,SAAS,GAAG,QAAQ,SAAS;AAC5D,QAAI;AACF,WAAK,QAAQ,QAAQ,QAAQ,KAAK,KAAK,KAAK,UAAU,IAAI,CAAC;AAAA,IAC7D,QAAQ;AAAA,IAIR;AAAA,EACF;AACF;;;ACtGO,IAAM,gBAAN,MAA+C;AAAA,EAA/C;AACL,SAAQ,QAAQ,oBAAI,IAAoB;AAAA;AAAA,EACxC,QAAQ,KAA4B;AAClC,WAAO,KAAK,MAAM,IAAI,GAAG,KAAK;AAAA,EAChC;AAAA,EACA,QAAQ,KAAa,OAAqB;AACxC,SAAK,MAAM,IAAI,KAAK,KAAK;AAAA,EAC3B;AAAA,EACA,WAAW,KAAmB;AAC5B,SAAK,MAAM,OAAO,GAAG;AAAA,EACvB;AACF;AAyBO,IAAM,gBAAN,MAA+C;AAAA,EAKpD,YAAY,SAIT;AACD,SAAK,YAAY,SAAS,aAAa;AACvC,SAAK,SAAS,SAAS,UAAU,cAAc;AAC/C,SAAK,WAAW,SAAS,YAAY;AAAA,EACvC;AAAA,EAEA,QAAQ,KAA4B;AAClC,QAAI,CAAC,YAAY,EAAG,QAAO;AAC3B,UAAM,MAAO,WAAsC;AACnD,UAAM,UAAU,IAAI,SAAS,IAAI,OAAO,MAAM,MAAM,IAAI,CAAC;AACzD,UAAM,SAAS,mBAAmB,GAAG,IAAI;AACzC,eAAW,KAAK,SAAS;AACvB,UAAI,EAAE,WAAW,MAAM,GAAG;AACxB,YAAI;AACF,iBAAO,mBAAmB,EAAE,MAAM,OAAO,MAAM,CAAC;AAAA,QAClD,QAAQ;AACN,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEA,QAAQ,KAAa,OAAqB;AACxC,QAAI,CAAC,YAAY,EAAG;AACpB,UAAM,MAAO,WAAsC;AACnD,UAAM,QAAQ;AAAA,MACZ,GAAG,mBAAmB,GAAG,CAAC,IAAI,mBAAmB,KAAK,CAAC;AAAA,MACvD;AAAA,MACA,WAAW,KAAK,SAAS;AAAA,MACzB,YAAY,KAAK,QAAQ;AAAA,IAC3B;AACA,QAAI,KAAK,OAAQ,OAAM,KAAK,QAAQ;AACpC,QAAI;AACF,UAAI,SAAS,MAAM,KAAK,IAAI;AAAA,IAC9B,QAAQ;AAAA,IAGR;AAAA,EACF;AAAA,EAEA,WAAW,KAAmB;AAC5B,QAAI,CAAC,YAAY,EAAG;AACpB,UAAM,MAAO,WAAsC;AAInD,UAAM,QAAQ;AAAA,MACZ,GAAG,mBAAmB,GAAG,CAAC;AAAA,MAC1B;AAAA,MACA;AAAA,MACA,YAAY,KAAK,QAAQ;AAAA,IAC3B;AACA,QAAI,KAAK,OAAQ,OAAM,KAAK,QAAQ;AACpC,QAAI;AACF,UAAI,SAAS,MAAM,KAAK,IAAI;AAAA,IAC9B,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAYO,SAAS,uBAAwC;AACtD,MAAI;AACF,UAAM,KAAM,WAAkD;AAC9D,QAAI,IAAI;AAEN,YAAM,QAAQ;AACd,SAAG,QAAQ,OAAO,GAAG;AACrB,SAAG,WAAW,KAAK;AACnB,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO,IAAI,cAAc;AAC3B;AAOA,SAAS,gBAAyB;AAChC,MAAI;AACF,UAAM,MAAO,WAAuC;AACpD,WAAO,KAAK,aAAa;AAAA,EAC3B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,cAAuB;AAC9B,SAAO,OAAQ,WAAsC,aAAa;AACpE;;;AC9KA,IAAM,qBAAqB;AAkBpB,SAAS,eAAe,KAA4B;AACzD,MAAI;AACF,UAAM,SAAU,WAAuC,kBAAkB;AAGzE,QAAI,OAAO,WAAW,WAAY,QAAO,GAAG;AAAA,EAC9C,QAAQ;AAAA,EAER;AACF;;;ACLO,SAAS,YAAqB;AACnC,SACE,OAAQ,WAAoC,WAAW,eACvD,OAAQ,WAAsC,aAAa,eAC3D,OAAQ,WAAuC,cAAc;AAEjE;AAOO,SAAS,kBAAkB,OAA6C;AAC7E,QAAM,OAAmB,CAAC;AAC1B,MAAI,OAAO,WAAY,MAAK,aAAa,MAAM;AAE/C,MAAI,CAAC,UAAU,EAAG,QAAO;AAEzB,QAAM,IAAK,WAAkC;AAC7C,QAAM,MAAO,WAAwC;AACrD,QAAM,MAAO,WAAsC;AAGnD,MAAI;AACF,QAAI,OAAO,IAAI,aAAa,SAAU,MAAK,SAAS,IAAI;AAAA,EAC1D,QAAQ;AAAA,EAAC;AACT,MAAI;AACF,SAAK,WAAW,KAAK,eAAe,EAAE,gBAAgB,EAAE;AAAA,EAC1D,QAAQ;AAAA,EAAC;AAGT,MAAI;AACF,QAAI,EAAE,QAAQ;AACZ,WAAK,cAAc,EAAE,OAAO;AAC5B,WAAK,eAAe,EAAE,OAAO;AAAA,IAC/B;AACA,SAAK,gBAAgB,EAAE;AACvB,SAAK,iBAAiB,EAAE;AACxB,SAAK,mBAAmB,EAAE;AAAA,EAC5B,QAAQ;AAAA,EAAC;AAGT,MAAI;AACF,UAAM,KAAK,IAAI,aAAa;AAC5B,UAAM,SAAS,eAAe,EAAE;AAChC,WAAO,OAAO,MAAM,MAAM;AAAA,EAC5B,QAAQ;AAAA,EAAC;AAGT,MAAI;AACF,UAAM,SAAU,IAEb;AACH,QAAI,QAAQ,YAAY,CAAC,KAAK,GAAI,MAAK,KAAK,OAAO;AACnD,QAAI,QAAQ,UAAU,CAAC,KAAK,SAAS;AAEnC,YAAM,OAAO,OAAO,OAAO;AAAA,QACzB,CAAC,MAAM,CAAC,mBAAmB,KAAK,EAAE,KAAK,KAAK,CAAC,YAAY,KAAK,EAAE,KAAK;AAAA,MACvE;AACA,UAAI,MAAM;AACR,aAAK,UAAU,KAAK;AACpB,aAAK,iBAAiB,KAAK;AAAA,MAC7B;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAAC;AAGT,OAAK;AACL,SAAO;AACT;AAWO,SAAS,eAAe,IAAiC;AAC9D,QAAM,MAA2B,CAAC;AAKlC,MAAI,mBAAmB,KAAK,EAAE,GAAG;AAC/B,QAAI,KAAK;AACT,UAAM,IAAI,GAAG,MAAM,6BAA6B;AAChD,QAAI,IAAI,CAAC,EAAG,KAAI,YAAY,EAAE,CAAC,EAAE,QAAQ,MAAM,GAAG;AAAA,EACpD,WAAW,UAAU,KAAK,EAAE,GAAG;AAC7B,QAAI,KAAK;AACT,UAAM,IAAI,GAAG,MAAM,yBAAyB;AAC5C,QAAI,IAAI,CAAC,EAAG,KAAI,YAAY,EAAE,CAAC;AAAA,EACjC,WAAW,UAAU,KAAK,EAAE,GAAG;AAC7B,QAAI,KAAK;AACT,UAAM,IAAI,GAAG,MAAM,uBAAuB;AAC1C,QAAI,IAAI,CAAC,EAAG,KAAI,YAAY,EAAE,CAAC;AAAA,EACjC,WAAW,qBAAqB,KAAK,EAAE,GAAG;AACxC,QAAI,KAAK;AACT,UAAM,IAAI,GAAG,MAAM,mCAAmC;AACtD,QAAI,IAAI,CAAC,EAAG,KAAI,YAAY,EAAE,CAAC,EAAE,QAAQ,MAAM,GAAG;AAAA,EACpD,WAAW,QAAQ,KAAK,EAAE,GAAG;AAC3B,QAAI,KAAK;AAAA,EACX;AAKA,MAAI,uBAAuB,KAAK,EAAE,GAAG;AACnC,QAAI,UAAU;AACd,QAAI,iBAAiB,GAAG,MAAM,sBAAsB,IAAI,CAAC;AAAA,EAC3D,WAAW,2BAA2B,KAAK,EAAE,GAAG;AAC9C,QAAI,UAAU;AACd,QAAI,iBAAiB,GAAG,MAAM,0BAA0B,IAAI,CAAC;AAAA,EAC/D,WAAW,uBAAuB,KAAK,EAAE,GAAG;AAC1C,QAAI,UAAU;AACd,QAAI,iBAAiB,GAAG,MAAM,sBAAsB,IAAI,CAAC;AAAA,EAC3D,WAAW,0BAA0B,KAAK,EAAE,GAAG;AAC7C,QAAI,UAAU;AACd,QAAI,iBAAiB,GAAG,MAAM,yBAAyB,IAAI,CAAC;AAAA,EAC9D,WAAW,mCAAmC,KAAK,EAAE,GAAG;AACtD,QAAI,UAAU;AACd,QAAI,iBAAiB,GAAG,MAAM,0BAA0B,IAAI,CAAC;AAAA,EAC/D;AAEA,SAAO;AACT;;;ACjHO,IAAM,qBAAsC;AAAA,EACjD,UAAU;AAAA,EACV,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,QAAQ;AACV;AAWA,IAAM,8BAA8B,KAAK,KAAK;AAG9C,IAAM,sBAAsB;AAQ5B,IAAM,+BAA+B;AAkErC,IAAM,oBAAwC;AAAA,EAC5C,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,aAAa;AAAA,EACb,UAAU;AAAA,EACV,UAAU;AAAA,EACV,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,QAAQ;AACV;AAEO,IAAM,cAAN,MAAkB;AAAA,EAoCvB,YACmB,KACA,OACjB,MACA;AAHiB;AACA;AArCnB,SAAQ,UAA+B;AACvC,SAAQ,WAA8B,CAAC;AAYvC;AAAA,SAAQ,gBAAgB;AASxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAAQ,cAAc;AAWtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAAQ,aAA4B;AAOlC,SAAK,UAAU,MAAM,WAAW;AAChC,SAAK,aAAa,MAAM,cAAc;AAAA,EACxC;AAAA,EAEA,UAAgB;AACd,QAAI,CAAC,cAAc,EAAG;AACtB,QAAI,KAAK,IAAI,SAAU,MAAK,uBAAuB;AACnD,QAAI,KAAK,IAAI,UAAW,MAAK,wBAAwB;AACrD,QAAI,KAAK,IAAI,OAAQ,MAAK,qBAAqB;AAAA,EACjD;AAAA,EAEA,YAAkB;AAChB,WAAO,KAAK,SAAS,QAAQ;AAC3B,YAAM,KAAK,KAAK,SAAS,IAAI;AAC7B,UAAI;AAAE,aAAK;AAAA,MAAG,QAAQ;AAAA,MAAe;AAAA,IACvC;AACA,QAAI,KAAK,WAAW,CAAC,KAAK,QAAQ,WAAW;AAC3C,WAAK,eAAe;AAAA,IACtB;AAIA,SAAK,mBAAmB;AACxB,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA,EAGA,eAAqB;AACnB,QAAI,KAAK,WAAW,CAAC,KAAK,QAAQ,UAAW,MAAK,eAAe;AAOjE,SAAK,aAAa;AAClB,SAAK,UAAU,KAAK,gBAAgB;AACpC,SAAK,eAAe;AACpB,SAAK,iBAAiB;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,eAAqB;AACnB,QAAI,CAAC,KAAK,QAAS;AACnB,UAAM,MAAM,KAAK,IAAI;AAYrB,QAAI,MAAM,KAAK,QAAQ,kBAAkB,6BAA6B;AACpE,WAAK,aAAa;AAClB,WAAK,UAAU,KAAK,gBAAgB;AACpC,WAAK,eAAe;AACpB,WAAK,iBAAiB;AACtB;AAAA,IACF;AACA,SAAK,QAAQ,iBAAiB;AAC9B,QAAI,MAAM,KAAK,iBAAiB,8BAA8B;AAC5D,WAAK,eAAe;AAAA,IACtB;AAAA,EACF;AAAA;AAAA,EAGA,IAAI,mBAAkC;AACpC,WAAO,KAAK,SAAS,aAAa;AAAA,EACpC;AAAA;AAAA,EAGA,IAAI,oBAAmC;AACrC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,IAAI,qBAAyC;AAC3C,WAAO,KAAK,SAAS,eAAe;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,UAAkB;AAKhB,UAAM,MAAM,KAAK;AACjB,SAAK,eAAe;AACpB,WAAO;AAAA,EACT;AAAA;AAAA,EAGQ,yBAA+B;AACrC,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,SAAS,KAAK,kBAAkB;AACtC,QAAI,UAAU,MAAM,OAAO,iBAAiB,6BAA6B;AAOvE,WAAK,UAAU;AAAA,QACb,WAAW,OAAO;AAAA,QAClB,WAAW,OAAO;AAAA,QAClB,gBAAgB;AAAA,QAChB,UAAU;AAAA,QACV,WAAW;AAAA,QACX,aAAa,OAAO;AAAA,MACtB;AACA,WAAK,eAAe;AAAA,IACtB,OAAO;AAML,WAAK,UAAU,KAAK,gBAAgB;AACpC,WAAK,eAAe;AACpB,WAAK,iBAAiB;AAAA,IACxB;AAEA,UAAM,cAAc,MAAY;AAC9B,UAAI,CAAC,KAAK,QAAS;AACnB,YAAMA,OAAO,WAAsC;AACnD,UAAIA,KAAI,oBAAoB,UAAU;AAOpC,aAAK,QAAQ,WAAW,KAAK,IAAI;AACjC,aAAK,eAAe;AAAA,MACtB,WAAWA,KAAI,oBAAoB,WAAW;AAG5C,cAAM,UAAU,KAAK,IAAI,IAAI,KAAK,QAAQ;AAC1C,YAAI,WAAW,6BAA6B;AAQ1C,eAAK,aAAa;AAClB,eAAK,UAAU,KAAK,gBAAgB;AACpC,eAAK,eAAe;AACpB,eAAK,iBAAiB;AAAA,QACxB,OAAO;AAEL,eAAK,QAAQ,WAAW;AAAA,QAC1B;AAAA,MACF;AAAA,IACF;AAQA,UAAM,aAAa,MAAY,KAAK,eAAe;AAEnD,UAAM,IAAK,WAAkC;AAC7C,UAAM,MAAO,WAAsC;AACnD,QAAI,iBAAiB,oBAAoB,WAAW;AACpD,MAAE,iBAAiB,YAAY,UAAU;AAGzC,MAAE,iBAAiB,gBAAgB,UAAU;AAE7C,SAAK,SAAS,KAAK,MAAM;AACvB,UAAI,oBAAoB,oBAAoB,WAAW;AACvD,QAAE,oBAAoB,YAAY,UAAU;AAC5C,QAAE,oBAAoB,gBAAgB,UAAU;AAAA,IAClD,CAAC;AAAA,EACH;AAAA,EAEQ,kBAAgC;AACtC,UAAM,MAAM,KAAK,IAAI;AAGrB,SAAK,cAAc;AACnB,WAAO;AAAA,MACL,WAAW,cAAc;AAAA,MACzB,WAAW;AAAA,MACX,gBAAgB;AAAA,MAChB,UAAU;AAAA,MACV,WAAW;AAAA,MACX,aAAa,mBAAmB;AAAA,IAClC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,oBAA0C;AAChD,QAAI,CAAC,KAAK,QAAS,QAAO;AAC1B,QAAI;AACF,YAAM,MAAM,KAAK,QAAQ,QAAQ,KAAK,UAAU;AAChD,UAAI,CAAC,IAAK,QAAO;AACjB,YAAM,IAAI,KAAK,MAAM,GAAG;AACxB,UACE,CAAC,KACD,OAAO,EAAE,OAAO,YAChB,OAAO,EAAE,cAAc,YACvB,OAAO,EAAE,mBAAmB,UAC5B;AACA,eAAO;AAAA,MACT;AACA,aAAO;AAAA,QACL,IAAI,EAAE;AAAA,QACN,WAAW,EAAE;AAAA,QACb,gBAAgB,EAAE;AAAA,QAClB,aACE,EAAE,eAAe,OAAO,EAAE,gBAAgB,WACrC,EAAE,cACH;AAAA,MACR;AAAA,IACF,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA,EAGQ,iBAAuB;AAC7B,QAAI,CAAC,KAAK,WAAW,CAAC,KAAK,QAAS;AACpC,SAAK,gBAAgB,KAAK,IAAI;AAC9B,QAAI;AACF,YAAM,MAAqB;AAAA,QACzB,IAAI,KAAK,QAAQ;AAAA,QACjB,WAAW,KAAK,QAAQ;AAAA,QACxB,gBAAgB,KAAK,QAAQ;AAAA,QAC7B,aAAa,KAAK,QAAQ;AAAA,MAC5B;AACA,WAAK,QAAQ,QAAQ,KAAK,YAAY,KAAK,UAAU,GAAG,CAAC;AAAA,IAC3D,QAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEQ,qBAA2B;AACjC,QAAI,CAAC,KAAK,QAAS;AACnB,QAAI;AACF,WAAK,QAAQ,WAAW,KAAK,UAAU;AAAA,IACzC,QAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEQ,mBAAyB;AAC/B,QAAI,CAAC,KAAK,QAAS;AACnB,SAAK,MAAM,mBAAmB,EAAE,WAAW,KAAK,QAAQ,UAAU,CAAC;AAAA,EACrE;AAAA,EAEQ,iBAAuB;AAC7B,QAAI,CAAC,KAAK,WAAW,KAAK,QAAQ,UAAW;AAC7C,UAAM,WAAW,KAAK,IAAI,IAAI,KAAK,QAAQ;AAC3C,SAAK,MAAM,iBAAiB;AAAA,MAC1B,WAAW,KAAK,QAAQ;AAAA,MACxB,YAAY;AAAA,IACd,CAAC;AACD,SAAK,QAAQ,YAAY;AAAA,EAC3B;AAAA;AAAA,EAGQ,0BAAgC;AACtC,UAAM,IAAK,WAAkC;AAC7C,UAAM,MAAO,WAAsC;AAYnD,QAAI,cAAc;AAClB,QAAI,eAAe;AACnB,UAAM,kBAAkB;AAExB,UAAM,OAAO,CAAC,QAAQ,UAAgB;AACpC,YAAM,MAAM,EAAE;AACd,YAAM,MAAM,IAAI;AAChB,YAAM,MAAM,KAAK,IAAI;AACrB,UAAI,CAAC,SAAS,QAAQ,gBAAgB,MAAM,cAAc,gBAAiB;AAC3E,oBAAc;AACd,qBAAe;AAMf,WAAK,aAAa,MAAM,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC,GAAG,YAAY,EAAE,CAAC;AAEjE,WAAK,MAAM,eAAe;AAAA,QACxB,YAAY,KAAK;AAAA,QACjB,MAAM,IAAI;AAAA,QACV;AAAA,QACA,QAAQ,IAAI,UAAU;AAAA,QACtB,MAAM,IAAI,QAAQ;AAAA,QAClB,OAAO,IAAI;AAAA;AAAA;AAAA,QAGX,UAAU,IAAI,YAAY;AAAA,MAC5B,CAAC;AAAA,IACH;AAGA,SAAK;AAcL,UAAM,WAAW,EAAE,QAAQ;AAC3B,UAAM,cAAc,EAAE,QAAQ;AAE9B,aAAS,YAA2B,MAAe,QAAgB,KAA2B;AAC5F,eAAS,MAAM,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC;AACxC,qBAAe,IAAI;AAAA,IACrB;AACA,aAAS,eAA8B,MAAe,QAAgB,KAA2B;AAC/F,kBAAY,MAAM,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC;AAC3C,qBAAe,IAAI;AAAA,IACrB;AAEA,IAAC,EAAE,QAAQ,YAA0B;AACrC,IAAC,EAAE,QAAQ,eAA6B;AAKxC,UAAM,aAAa,MAAY,KAAK,IAAI;AACxC,MAAE,iBAAiB,YAAY,UAAU;AAEzC,SAAK,SAAS,KAAK,MAAM;AAIvB,UAAI,EAAE,QAAQ,cAAc,aAAa;AACvC,QAAC,EAAE,QAAQ,YAA0B;AAAA,MACvC;AACA,UAAI,EAAE,QAAQ,iBAAiB,gBAAgB;AAC7C,QAAC,EAAE,QAAQ,eAA6B;AAAA,MAC1C;AACA,QAAE,oBAAoB,YAAY,UAAU;AAAA,IAC9C,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA8BQ,uBAA6B;AACnC,UAAM,IAAK,WAAkC;AAC7C,UAAM,MAAO,WAAsC;AAEnD,QAAI,cAAc;AAClB,QAAI,kBAAsC;AAC1C,UAAM,cAAc;AACpB,UAAM,WAAW;AAEjB,UAAM,UAAU,CAAC,OAAyB;AACxC,YAAM,SAAS,GAAG;AAClB,UAAI,CAAC,UAAU,EAAE,kBAAkB,SAAU;AAI7C,YAAM,MAAM,KAAK,IAAI;AACrB,UAAI,WAAW,mBAAmB,MAAM,cAAc,YAAa;AACnE,oBAAc;AACd,wBAAkB;AAOlB,YAAM,aAAa,kBAAkB,MAAM;AAC3C,YAAM,UAAmB,cAAc;AAIvC,UAAI,YAAY,OAAO,EAAG;AAC1B,UAAI,aAAa,OAAO,EAAG;AAC3B,UAAI,sBAAsB,OAAO,EAAG;AAGpC,YAAM,MAAM,QAAQ,QAAQ,YAAY;AACxC,YAAM,OAAO,SAAS,YAAY,OAAO,GAAG,QAAQ;AACpD,YAAM,OAAQ,QAA8B,QAAQ;AACpD,YAAM,aAAc,QAA8B,UAAU;AAC5D,YAAM,YAAY,QAAQ,MAAM;AAChC,YAAM,OAAO,QAAQ,aAAa,MAAM,KAAK;AAC7C,YAAM,YAAY,QAAQ,aAAa,YAAY,KAAK;AACxD,YAAM,WAAW,cAAc,OAAO;AACtC,YAAM,YAAY,iBAAiB,OAAO;AAC1C,YAAM,SAAS,QAAQ,OAAO,CAAC,CAAC;AAOhC,YAAM,eAAe,QAAQ,aAAa,eAAe;AAEzD,YAAM,QAAiC;AAAA,QACrC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,WAAW,GAAG;AAAA,QACd,WAAW,GAAG;AAAA,QACd,OAAO,GAAG;AAAA,QACV,OAAO,GAAG;AAAA,QACV,GAAG;AAAA,MACL;AAEA,iBAAW,KAAK,OAAO,KAAK,KAAK,GAAG;AAClC,YAAI,MAAM,CAAC,MAAM,UAAa,MAAM,CAAC,MAAM,QAAQ,MAAM,CAAC,MAAM,GAAI,QAAO,MAAM,CAAC;AAAA,MACpF;AAEA,WAAK,MAAM,gBAAgB,mBAAmB,KAAK;AAAA,IACrD;AAEA,QAAI,iBAAiB,SAAS,SAAS,EAAE,SAAS,MAAM,SAAS,KAAK,CAAC;AACvE,SAAK,SAAS,KAAK,MAAM;AACvB,UAAI,oBAAoB,SAAS,SAAS,EAAE,SAAS,KAAK,CAA4B;AAAA,IACxF,CAAC;AAAA,EACH;AACF;AAIA,SAAS,kBAAkB,IAA6B;AAGtD,SACE,GAAG,QAAQ,iBAAiB,KAC5B,GAAG,QAAQ,mBAAmB,KAC9B,GAAG,QAAQ,uFAAuF,KAClG;AAEJ;AAEA,SAAS,YAAY,IAAsB;AACzC,MAAI,EAAE,cAAc,aAAc,QAAO;AACzC,QAAM,MAAM,GAAG,QAAQ,YAAY;AACnC,MAAI,QAAQ,cAAc,QAAQ,SAAU,QAAO;AACnD,MAAI,QAAQ,SAAS;AACnB,UAAM,QAAS,GAAwB,QAAQ,IAAI,YAAY;AAE/D,WAAO,SAAS,YAAY,SAAS,YAAY,SAAS,WAAW,SAAS;AAAA,EAChF;AACA,SAAO;AACT;AAEA,SAAS,aAAa,IAAsB;AAG1C,MAAI,GAAG,QAAQ,kEAAkE,EAAG,QAAO;AAC3F,SAAO;AACT;AAEA,SAAS,sBAAsB,IAAsB;AAEnD,MAAI,GAAG,QAAQ,wBAAwB,EAAG,QAAO;AACjD,SAAO;AACT;AAIA,IAAM,oBAAoB,oBAAI,IAAI;AAAA,EAChC;AAAA,EAAQ;AAAA,EAAK;AAAA,EAAU;AAAA,EAAM;AAAA,EAAK;AAAA,EAAS;AAAA,EAAQ;AAAA,EAAK;AAAA,EACxD;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAO;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAO;AAAA,EAAK;AAAA,EAAO;AAC5D,CAAC;AAID,IAAM,qBAAqB,oBAAI,IAAI,CAAC,OAAO,SAAS,UAAU,UAAU,CAAC;AAQzE,IAAM,oBACJ;AAEF,IAAM,mBAAmB;AAGzB,SAAS,WAAW,GAAmB;AACrC,SAAO,EAAE,QAAQ,QAAQ,GAAG,EAAE,KAAK;AACrC;AAUA,SAAS,aAAa,IAAqB;AACzC,MAAI,MAAM;AACV,QAAM,OAAO,CAAC,SAAqB;AACjC,UAAM,OAAO,KAAK;AAClB,aAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,YAAM,QAAQ,KAAK,CAAC;AACpB,UAAI,CAAC,MAAO;AACZ,UAAI,MAAM,aAAa,GAAmB;AACxC,eAAO,MAAM,eAAe;AAAA,MAC9B,WAAW,MAAM,aAAa,GAAsB;AAClD,YAAI,mBAAmB,IAAK,MAAkB,QAAQ,YAAY,CAAC,EAAG;AACtE,eAAO;AACP,aAAK,KAAK;AACV,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AACA,OAAK,EAAE;AACP,SAAO,WAAW,GAAG;AACvB;AASA,SAAS,YAAY,IAAqB;AACxC,MAAI,MAAM;AACV,QAAM,OAAO,GAAG;AAChB,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,QAAQ,KAAK,CAAC;AACpB,QAAI,CAAC,MAAO;AACZ,QAAI,MAAM,aAAa,GAAmB;AACxC,aAAO,OAAO,MAAM,eAAe;AAAA,IACrC,WACE,MAAM,aAAa,KACnB,kBAAkB,IAAK,MAAkB,QAAQ,YAAY,CAAC,GAC9D;AACA,aAAO,MAAM,aAAa,KAAgB;AAAA,IAC5C;AAAA,EACF;AACA,SAAO,WAAW,GAAG;AACvB;AAkBA,SAAS,aAAa,IAAqB;AACzC,QAAM,cAAc,GAAG,cAAc,iBAAiB,MAAM;AAC5D,MAAI,CAAC,YAAa,QAAO,aAAa,EAAE;AAExC,QAAM,SAAS,YAAY,EAAE;AAC7B,MAAI,OAAQ,QAAO;AAEnB,QAAM,UAAU,GAAG,cAAc,gBAAgB;AACjD,MAAI,SAAS;AACX,UAAM,IAAI,aAAa,OAAO;AAC9B,QAAI,EAAG,QAAO;AAAA,EAChB;AACA,SAAO;AACT;AAuBA,SAAS,YAAY,IAAqB;AACxC,QAAM,QAAQ,CAAC,MAAsB,EAAE,QAAQ,QAAQ,GAAG,EAAE,KAAK;AAKjE,QAAM,WACJ,GAAG,aAAa,eAAe,KAC/B,GAAG,aAAa,YAAY,KAC5B,GAAG,aAAa,aAAa;AAC/B,MAAI,UAAU;AACZ,UAAM,IAAI,MAAM,QAAQ;AACxB,QAAI,EAAG,QAAO;AAAA,EAChB;AAGA,QAAM,OAAO,GAAG,aAAa,YAAY;AACzC,MAAI,MAAM;AACR,UAAM,IAAI,MAAM,IAAI;AACpB,QAAI,EAAG,QAAO;AAAA,EAChB;AAIA,QAAM,aAAa,GAAG,aAAa,iBAAiB;AACpD,MAAI,cAAc,OAAO,GAAG,eAAe,mBAAmB,YAAY;AACxE,UAAM,QAAkB,CAAC;AACzB,eAAW,MAAM,WAAW,MAAM,KAAK,GAAG;AACxC,YAAMC,OAAM,GAAG,cAAc,eAAe,EAAE;AAC9C,YAAM,IAAIA,MAAK,cAAc,MAAMA,KAAI,WAAW,IAAI;AACtD,UAAI,EAAG,OAAM,KAAK,CAAC;AAAA,IACrB;AACA,QAAI,MAAM,SAAS,EAAG,QAAO,MAAM,KAAK,GAAG;AAAA,EAC7C;AAGA,MAAI,cAAc,oBAAoB,GAAG,OAAO;AAC9C,UAAM,IAAI,MAAM,GAAG,KAAK;AACxB,QAAI,EAAG,QAAO;AAAA,EAChB;AAUA,QAAM,OAAO,aAAa,EAAE;AAC5B,MAAI,KAAM,QAAO;AAGjB,QAAM,QAAQ,GAAG,aAAa,OAAO;AACrC,MAAI,OAAO;AACT,UAAM,IAAI,MAAM,KAAK;AACrB,QAAI,EAAG,QAAO;AAAA,EAChB;AAIA,QAAM,MAAM,GAAG,cAAc,UAAU;AACvC,MAAI,KAAK;AACP,UAAM,MAAM,IAAI,aAAa,KAAK,KAAK;AACvC,UAAM,IAAI,MAAM,GAAG;AACnB,QAAI,EAAG,QAAO;AAAA,EAChB;AAIA,QAAM,WAAW,GAAG,cAAc,WAAW;AAC7C,MAAI,UAAU,aAAa;AACzB,UAAM,IAAI,MAAM,SAAS,WAAW;AACpC,QAAI,EAAG,QAAO;AAAA,EAChB;AAEA,SAAO;AACT;AAEA,SAAS,SAAS,GAAW,KAAqB;AAChD,MAAI,EAAE,UAAU,IAAK,QAAO;AAC5B,SAAO,EAAE,MAAM,GAAG,MAAM,CAAC,IAAI;AAC/B;AASA,SAAS,cAAc,IAAqB;AAC1C,QAAM,QAAkB,CAAC;AACzB,MAAI,MAAsB;AAC1B,MAAI,QAAQ;AACZ,SAAO,OAAO,IAAI,SAAS,YAAY,MAAM,UAAU,QAAQ,GAAG;AAChE,QAAI,OAAO,IAAI,SAAS,YAAY;AACpC,QAAI,IAAI,IAAI;AACV,YAAM,QAAQ,GAAG,IAAI,IAAI,IAAI,EAAE,EAAE;AACjC;AAAA,IACF;AACA,QAAI,IAAI,UAAU,SAAS,GAAG;AAC5B,YAAM,MAAM,MAAM,KAAK,IAAI,SAAS,EACjC,OAAO,CAAC,MAAM,CAAC,EAAE,WAAW,KAAK,CAAC,EAClC,MAAM,GAAG,CAAC,EACV,KAAK,GAAG;AACX,UAAI,IAAK,SAAQ,IAAI,GAAG;AAAA,IAC1B;AACA,UAAM,QAAQ,IAAI;AAClB,UAAM,IAAI;AACV;AAAA,EACF;AACA,SAAO,MAAM,KAAK,KAAK;AACzB;AAEA,SAAS,iBAAiB,IAAqC;AAI7D,QAAM,MAA8B,CAAC;AACrC,MAAI,EAAE,cAAc,aAAc,QAAO;AACzC,aAAW,QAAQ,GAAG,kBAAkB,GAAG;AACzC,QAAI,CAAC,KAAK,WAAW,OAAO,EAAG;AAC/B,QAAI,SAAS,qBAAqB,SAAS,mBAAoB;AAC/D,QAAI,SAAS,gBAAiB;AAC9B,UAAM,QAAQ,GAAG,aAAa,IAAI,KAAK;AAEvC,UAAM,MAAM,KAAK,QAAQ,kBAAkB,EAAE,EAAE,QAAQ,UAAU,EAAE;AACnE,QAAI,GAAG,IAAI;AAAA,EACb;AACA,SAAO;AACT;AAOA,SAAS,gBAAyB;AAChC,SACE,OAAQ,WAAoC,WAAW,eACvD,OAAQ,WAAsC,aAAa;AAE/D;AAEA,SAAS,gBAAwB;AAE/B,QAAM,KAAK,KAAK,IAAI,EAAE,SAAS,EAAE;AACjC,SAAO,QAAQ,EAAE,GAAG,YAAY,EAAE,CAAC;AACrC;AAcO,SAAS,qBAAyC;AACvD,MAAI,CAAC,cAAc,EAAG,QAAO,EAAE,GAAG,kBAAkB;AAEpD,QAAM,SAA6B,EAAE,GAAG,kBAAkB;AAE1D,MAAI;AACF,UAAM,IAAK,WAAkC;AAC7C,UAAM,SAAS,IAAI,gBAAgB,EAAE,SAAS,UAAU,EAAE;AAC1D,WAAO,aAAa,OAAO,IAAI,YAAY,KAAK;AAChD,WAAO,aAAa,OAAO,IAAI,YAAY,KAAK;AAChD,WAAO,eAAe,OAAO,IAAI,cAAc,KAAK;AACpD,WAAO,cAAc,OAAO,IAAI,aAAa,KAAK;AAClD,WAAO,WAAW,OAAO,IAAI,UAAU,KAAK;AAI5C,WAAO,QAAQ,OAAO,IAAI,OAAO,KAAK;AACtC,WAAO,SAAS,OAAO,IAAI,QAAQ,KAAK;AACxC,WAAO,UAAU,OAAO,IAAI,SAAS,KAAK;AAC1C,WAAO,SAAS,OAAO,IAAI,QAAQ,KAAK;AACxC,WAAO,YAAY,OAAO,IAAI,WAAW,KAAK;AAC9C,WAAO,SAAS,OAAO,IAAI,QAAQ,KAAK;AAAA,EAC1C,QAAQ;AAAA,EAER;AAEA,MAAI;AACF,UAAM,MAAO,WAAsC;AACnD,QAAI,OAAO,IAAI,aAAa,SAAU,QAAO,WAAW,IAAI;AAAA,EAC9D,QAAQ;AAAA,EAER;AAEA,SAAO;AACT;;;ACngCA,IAAM,yBAA4C;AAAA,EAChD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,SAAS,0BACd,YACU;AACV,MAAI,CAAC,WAAY,QAAO,CAAC;AACzB,QAAM,OAAiB,CAAC;AACxB,aAAW,KAAK,OAAO,KAAK,UAAU,GAAG;AACvC,QAAI,uBAAuB,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,CAAC,EAAG,MAAK,KAAK,CAAC;AAAA,EAClE;AACA,SAAO;AACT;AAOO,IAAM,qBAAN,MAAgD;AAAA,EAAhD;AACL,mBAAU;AACV,SAAQ,OAAO,oBAAI,IAAiB;AAAA;AAAA,EAEpC,KAAK,QAAqB,SAAiB,SAA8B;AACvE,QAAI,CAAC,KAAK,QAAS;AAInB,QAAI,aAAa,IAAI,MAAM,GAAG;AAC5B,UAAI,KAAK,KAAK,IAAI,MAAM,EAAG;AAC3B,WAAK,KAAK,IAAI,MAAM;AAAA,IACtB;AACA,UAAM,MAAM,UAAU,IAAI,SAAS,OAAO,CAAC,KAAK;AAEhD,YAAQ,KAAK,cAAc,MAAM,KAAK,OAAO,GAAG,GAAG,EAAE;AAAA,EACvD;AACF;AAEA,IAAM,eAAe,oBAAI,IAAiB;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,SAAS,SAAS,KAAsB;AACtC,MAAI;AACF,WAAO,KAAK,UAAU,GAAG;AAAA,EAC3B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AC5CA,IAAM,qBAAqB;AAC3B,IAAM,oBAAoB,IAAI;AAC9B,IAAM,oBAAoB;AAMnB,SAAS,wBACd,OACA,UAA6B,CAAC,GACZ;AAClB,QAAM,WAAgC,CAAC;AACvC,MAAI,CAAC,MAAO,QAAO,EAAE,YAAY,CAAC,GAAG,SAAS;AAE9C,QAAM,kBAAkB,QAAQ,mBAAmB;AACnD,QAAM,wBAAwB,QAAQ,yBAAyB;AAC/D,QAAM,WAAW,QAAQ,YAAY;AAYrC,QAAM,OAAO,oBAAI,IAAY;AAE7B,QAAM,QAAQ,CACZ,OACA,KACA,UACsC;AACtC,QAAI,QAAQ,UAAU;AACpB,eAAS,KAAK,EAAE,MAAM,kBAAkB,IAAI,CAAC;AAC7C,aAAO,EAAE,MAAM,MAAM,OAAO,mBAAmB;AAAA,IACjD;AACA,QAAI,UAAU,KAAM,QAAO,EAAE,MAAM,MAAM,OAAO,KAAK;AACrD,UAAM,IAAI,OAAO;AACjB,QAAI,MAAM,UAAU;AAClB,YAAM,IAAI;AACV,UAAI,EAAE,SAAS,iBAAiB;AAC9B,iBAAS,KAAK,EAAE,MAAM,oBAAoB,IAAI,CAAC;AAC/C,eAAO,EAAE,MAAM,MAAM,OAAO,EAAE,MAAM,GAAG,kBAAkB,CAAC,IAAI,SAAI;AAAA,MACpE;AACA,aAAO,EAAE,MAAM,MAAM,OAAO,EAAE;AAAA,IAChC;AACA,QAAI,MAAM,UAAU;AAGlB,UAAI,CAAC,OAAO,SAAS,KAAe,GAAG;AACrC,iBAAS,KAAK,EAAE,MAAM,oBAAoB,IAAI,CAAC;AAC/C,eAAO,EAAE,MAAM,MAAM,OAAO,KAAK;AAAA,MACnC;AACA,aAAO,EAAE,MAAM,MAAM,MAAM;AAAA,IAC7B;AACA,QAAI,MAAM,UAAW,QAAO,EAAE,MAAM,MAAM,MAAM;AAChD,QAAI,MAAM,UAAU;AAClB,eAAS,KAAK,EAAE,MAAM,kBAAkB,IAAI,CAAC;AAC7C,aAAO,EAAE,MAAM,MAAM,OAAQ,MAAiB,SAAS,EAAE;AAAA,IAC3D;AACA,QAAI,MAAM,YAAY;AACpB,eAAS,KAAK,EAAE,MAAM,oBAAoB,IAAI,CAAC;AAC/C,aAAO,EAAE,MAAM,OAAO,OAAO,OAAU;AAAA,IACzC;AACA,QAAI,MAAM,UAAU;AAClB,eAAS,KAAK,EAAE,MAAM,kBAAkB,IAAI,CAAC;AAC7C,aAAO,EAAE,MAAM,OAAO,OAAO,OAAU;AAAA,IACzC;AACA,QAAI,MAAM,aAAa;AACrB,eAAS,KAAK,EAAE,MAAM,qBAAqB,IAAI,CAAC;AAChD,aAAO,EAAE,MAAM,OAAO,OAAO,OAAU;AAAA,IACzC;AAGA,QAAI,iBAAiB,MAAM;AACzB,eAAS,KAAK,EAAE,MAAM,gBAAgB,IAAI,CAAC;AAC3C,YAAM,MAAM,OAAO,SAAS,MAAM,QAAQ,CAAC,IAAI,MAAM,YAAY,IAAI;AACrE,aAAO,EAAE,MAAM,MAAM,OAAO,IAAI;AAAA,IAClC;AACA,QAAI,iBAAiB,OAAO;AAC1B,eAAS,KAAK,EAAE,MAAM,iBAAiB,IAAI,CAAC;AAC5C,aAAO;AAAA,QACL,MAAM;AAAA,QACN,OAAO;AAAA,UACL,MAAM,MAAM;AAAA,UACZ,SAAS,MAAM;AAAA,UACf,OAAO,OAAO,MAAM,UAAU,WAAW,MAAM,MAAM,MAAM,GAAG,eAAe,IAAI;AAAA,QACnF;AAAA,MACF;AAAA,IACF;AACA,QAAI,iBAAiB,KAAK;AACxB,eAAS,KAAK,EAAE,MAAM,eAAe,IAAI,CAAC;AAC1C,YAAM,MAA+B,CAAC;AACtC,iBAAW,CAAC,GAAG,CAAC,KAAK,MAAM,QAAQ,GAAG;AACpC,cAAM,SAAS,OAAO,MAAM,WAAW,IAAI,OAAO,CAAC;AACnD,cAAM,SAAS,MAAM,GAAG,GAAG,GAAG,IAAI,MAAM,IAAI,QAAQ,CAAC;AACrD,YAAI,OAAO,KAAM,KAAI,MAAM,IAAI,OAAO;AAAA,MACxC;AACA,aAAO,EAAE,MAAM,MAAM,OAAO,IAAI;AAAA,IAClC;AACA,QAAI,iBAAiB,KAAK;AACxB,eAAS,KAAK,EAAE,MAAM,eAAe,IAAI,CAAC;AAC1C,YAAM,MAAiB,CAAC;AACxB,UAAI,IAAI;AACR,iBAAW,KAAK,MAAM,OAAO,GAAG;AAC9B,cAAM,SAAS,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,KAAK,QAAQ,CAAC;AACjD,YAAI,OAAO,KAAM,KAAI,KAAK,OAAO,KAAK;AACtC;AAAA,MACF;AACA,aAAO,EAAE,MAAM,MAAM,OAAO,IAAI;AAAA,IAClC;AAEA,QAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,UAAI,KAAK,IAAI,KAAK,GAAG;AACnB,iBAAS,KAAK,EAAE,MAAM,sBAAsB,IAAI,CAAC;AACjD,eAAO,EAAE,MAAM,MAAM,OAAO,aAAa;AAAA,MAC3C;AACA,WAAK,IAAI,KAAK;AACd,YAAM,MAAiB,CAAC;AACxB,eAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,cAAM,SAAS,MAAM,MAAM,CAAC,GAAG,GAAG,GAAG,IAAI,CAAC,KAAK,QAAQ,CAAC;AACxD,YAAI,OAAO,KAAM,KAAI,KAAK,OAAO,KAAK;AAAA,MACxC;AAIA,WAAK,OAAO,KAAK;AACjB,aAAO,EAAE,MAAM,MAAM,OAAO,IAAI;AAAA,IAClC;AAEA,QAAI,MAAM,UAAU;AAClB,YAAM,MAAM;AACZ,UAAI,KAAK,IAAI,GAAG,GAAG;AACjB,iBAAS,KAAK,EAAE,MAAM,sBAAsB,IAAI,CAAC;AACjD,eAAO,EAAE,MAAM,MAAM,OAAO,aAAa;AAAA,MAC3C;AACA,WAAK,IAAI,GAAG;AACZ,YAAM,MAA+B,CAAC;AACtC,iBAAW,KAAK,OAAO,KAAK,GAAG,GAAG;AAChC,cAAM,SAAS,MAAM,IAAI,CAAC,GAAG,GAAG,GAAG,IAAI,CAAC,IAAI,QAAQ,CAAC;AACrD,YAAI,OAAO,KAAM,KAAI,CAAC,IAAI,OAAO;AAAA,MACnC;AAEA,WAAK,OAAO,GAAG;AACf,aAAO,EAAE,MAAM,MAAM,OAAO,IAAI;AAAA,IAClC;AAGA,aAAS,KAAK,EAAE,MAAM,oBAAoB,IAAI,CAAC;AAC/C,QAAI;AACF,aAAO,EAAE,MAAM,MAAM,OAAO,OAAO,KAAK,EAAE;AAAA,IAC5C,QAAQ;AACN,aAAO,EAAE,MAAM,OAAO,OAAO,OAAU;AAAA,IACzC;AAAA,EACF;AAEA,QAAM,UAAmC,CAAC;AAC1C,aAAW,KAAK,OAAO,KAAK,KAAK,GAAG;AAClC,UAAM,SAAS,MAAM,MAAM,CAAC,GAAG,GAAG,CAAC;AACnC,QAAI,OAAO,KAAM,SAAQ,CAAC,IAAI,OAAO;AAAA,EACvC;AAKA,QAAM,aAAa,cAAc,OAAO;AACxC,MAAI,cAAc,WAAW,UAAU,IAAI,uBAAuB;AAChE,aAAS,KAAK,EAAE,MAAM,qBAAqB,KAAK,IAAI,CAAC;AACrD,UAAM,QAAQ,OAAO,KAAK,OAAO,EAC9B,IAAI,CAAC,OAAO,EAAE,GAAG,MAAM,WAAW,cAAc,QAAQ,CAAC,CAAC,KAAK,EAAE,EAAE,EAAE,EACrE,KAAK,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,IAAI;AACjC,QAAI,cAAc,WAAW,UAAU;AACvC,eAAW,EAAE,EAAE,KAAK,OAAO;AACzB,UAAI,eAAe,sBAAuB;AAC1C,qBAAe,MAAM,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,EAAG;AAC7C,aAAO,QAAQ,CAAC;AAAA,IAClB;AACA,YAAQ,cAAc;AAAA,EACxB;AAEA,SAAO,EAAE,YAAY,SAAS,SAAS;AACzC;AAEA,SAAS,cAAc,GAA2B;AAChD,MAAI;AACF,WAAO,KAAK,UAAU,CAAC,KAAK;AAAA,EAC9B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,WAAW,GAAmB;AACrC,MAAI,OAAO,gBAAgB,aAAa;AACtC,WAAO,IAAI,YAAY,EAAE,OAAO,CAAC,EAAE;AAAA,EACrC;AAGA,SAAO,EAAE,SAAS;AACpB;;;AChPA,IAAM,YAAY;AAClB,IAAM,aAAa;AAEZ,IAAM,qBAAN,MAAyB;AAAA,EAI9B,YACmB,SACA,QACjB;AAFiB;AACA;AALnB,SAAQ,aAAsC,CAAC;AAC/C,SAAQ,SAA2E,CAAC;AAMlF,SAAK,aAAa,SAAS,SAAS,SAAS,SAAS,KAAK,CAAC;AAC5D,SAAK,SAAS,SAAS,SAAS,SAAS,UAAU,KAAK,CAAC;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,SAAS,OAAyD;AAChE,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,KAAK,GAAG;AAC1C,UAAI,MAAM,MAAM;AACd,eAAO,KAAK,WAAW,CAAC;AAAA,MAC1B,WAAW,MAAM,QAAW;AAC1B,aAAK,WAAW,CAAC,IAAI;AAAA,MACvB;AAAA,IACF;AACA,cAAU,KAAK,SAAS,KAAK,SAAS,WAAW,KAAK,UAAU;AAChE,WAAO,EAAE,GAAG,KAAK,WAAW;AAAA,EAC9B;AAAA;AAAA,EAGA,WAAW,KAAmB;AAC5B,QAAI,OAAO,KAAK,YAAY;AAC1B,aAAO,KAAK,WAAW,GAAG;AAC1B,gBAAU,KAAK,SAAS,KAAK,SAAS,WAAW,KAAK,UAAU;AAAA,IAClE;AAAA,EACF;AAAA;AAAA,EAGA,qBAA8C;AAC5C,WAAO,EAAE,GAAG,KAAK,WAAW;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,SAAS,MAAc,IAAmB,QAAwC;AAChF,QAAI,OAAO,MAAM;AACf,aAAO,KAAK,OAAO,IAAI;AAAA,IACzB,OAAO;AACL,WAAK,OAAO,IAAI,IAAI,WAAW,SAAY,EAAE,IAAI,OAAO,IAAI,EAAE,GAAG;AAAA,IACnE;AACA,cAAU,KAAK,SAAS,KAAK,SAAS,YAAY,KAAK,MAAM;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,YAA8E;AAC5E,WAAO,KAAK,MAAM,KAAK,UAAU,KAAK,MAAM,CAAC;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,cAAsC;AACpC,UAAM,MAA8B,CAAC;AACrC,eAAW,CAAC,MAAM,IAAI,KAAK,OAAO,QAAQ,KAAK,MAAM,GAAG;AACtD,UAAI,IAAI,IAAI,KAAK;AAAA,IACnB;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,QAAc;AACZ,SAAK,aAAa,CAAC;AACnB,SAAK,SAAS,CAAC;AACf,QAAI;AACF,WAAK,QAAQ,WAAW,KAAK,SAAS,SAAS;AAAA,IACjD,QAAQ;AAAA,IAER;AACA,QAAI;AACF,WAAK,QAAQ,WAAW,KAAK,SAAS,UAAU;AAAA,IAClD,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAEA,SAAS,SAAY,SAA0B,KAAuB;AACpE,MAAI;AACJ,MAAI;AACF,UAAM,QAAQ,QAAQ,GAAG;AAAA,EAC3B,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI;AACF,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,UAAU,SAA0B,KAAa,OAAsB;AAC9E,MAAI;AACF,YAAQ,QAAQ,KAAK,KAAK,UAAU,KAAK,CAAC;AAAA,EAC5C,QAAQ;AAAA,EAGR;AACF;;;AChIO,IAAM,4BAA4B;AAEzC,IAAM,cAAc;AAEpB,SAAS,aAA6B;AACpC,MAAI;AACF,WAAO,OAAO,iBAAiB,cAAc,eAAe;AAAA,EAC9D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAOO,SAAS,2BAAiC;AAC/C,MAAI;AACF,UAAM,SAAS,OAAO,aAAa,cAAc,SAAS,SAAS;AACnE,UAAM,SAAS,IAAI,gBAAgB,UAAU,EAAE;AAC/C,QAAI,CAAC,OAAO,IAAI,oBAAoB,EAAG;AACvC,UAAM,QAAQ,WAAW;AACzB,QAAI,CAAC,MAAO;AACZ,UAAM,IAAI,OAAO,IAAI,oBAAoB;AACzC,QAAI,MAAM,OAAO,MAAM,QAAQ;AAC7B,YAAM,QAAQ,aAAa,GAAG;AAAA,IAChC,WAAW,MAAM,OAAO,MAAM,SAAS;AACrC,YAAM,WAAW,WAAW;AAAA,IAC9B;AAAA,EACF,QAAQ;AAAA,EAER;AACF;AAGO,SAAS,mBAA4B;AAC1C,MAAI;AACF,WAAO,WAAW,GAAG,QAAQ,WAAW,MAAM;AAAA,EAChD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AC1BO,IAAM,mBAAN,MAAuB;AAAA,EAQ5B,YACmB,KACA,QACjB;AAFiB;AACA;AATnB,SAAQ,YAAmC,CAAC;AAC5C,SAAQ,UAAU,oBAAI,IAAY;AAClC,SAAQ,MAAM;AACd,SAAQ,aAAiC,CAAC;AAC1C,SAAQ,MAAM;AACd,SAAQ,WAA8B,CAAC;AAAA,EAKpC;AAAA,EAEH,UAAgB;AACd,QAAI,CAAC,KAAK,IAAI,QAAS;AACvB,QAAI,OAAO,wBAAwB,YAAa;AAChD,QAAI,OAAO,eAAe,eAAe,EAAE,cAAc,YAAa;AAEtE,UAAM,MAAO,WAAsC;AAGnD,QAAI;AACF,YAAM,cAAc,IAAI,oBAAoB,CAAC,SAAS;AACpD,mBAAW,SAAS,KAAK,WAAW,GAAG;AACrC,gBAAM,IAAI;AACV,cAAI,EAAE,gBAAgB,KAAK,CAAC,KAAK,QAAQ,IAAI,MAAM,GAAG;AACpD,iBAAK,QAAQ,IAAI,MAAM;AACvB,iBAAK,OAAO,kBAAkB,EAAE,SAAS,KAAK,MAAM,EAAE,gBAAgB,EAAE,SAAS,EAAE,CAAC;AAAA,UACtF;AAAA,QACF;AAAA,MACF,CAAC;AACD,kBAAY,QAAQ,EAAE,MAAM,cAAc,UAAU,KAAK,CAAC;AAC1D,WAAK,UAAU,KAAK,WAAW;AAAA,IACjC,QAAQ;AAAA,IAER;AAEA,QAAI;AACF,YAAM,gBAAgB,IAAI,oBAAoB,CAAC,SAAS;AACtD,mBAAW,SAAS,KAAK,WAAW,GAAG;AACrC,cAAI,MAAM,SAAS,4BAA4B,CAAC,KAAK,QAAQ,IAAI,KAAK,GAAG;AACvE,iBAAK,QAAQ,IAAI,KAAK;AACtB,iBAAK,OAAO,iBAAiB,EAAE,SAAS,KAAK,MAAM,MAAM,SAAS,EAAE,CAAC;AAAA,UACvE;AAAA,QACF;AAAA,MACF,CAAC;AACD,oBAAc,QAAQ,EAAE,MAAM,SAAS,UAAU,KAAK,CAAC;AACvD,WAAK,UAAU,KAAK,aAAa;AAAA,IACnC,QAAQ;AAAA,IAER;AAGA,QAAI,WAAW;AACf,QAAI;AACF,YAAM,cAAc,IAAI,oBAAoB,CAAC,SAAS;AACpD,cAAM,UAAU,KAAK,WAAW;AAChC,cAAM,OAAO,QAAQ,QAAQ,SAAS,CAAC;AACvC,YAAI,KAAM,YAAW,KAAK;AAAA,MAC5B,CAAC;AACD,kBAAY,QAAQ,EAAE,MAAM,4BAA4B,UAAU,KAAK,CAAC;AACxE,WAAK,UAAU,KAAK,WAAW;AAAA,IACjC,QAAQ;AAAA,IAER;AAGA,QAAI;AACF,YAAM,cAAc,IAAI,oBAAoB,CAAC,SAAS;AACpD,mBAAW,SAAS,KAAK,WAAW,GAAG;AAGrC,gBAAM,IAAI;AACV,cAAI,OAAO,EAAE,UAAU,YAAY,CAAC,EAAE,gBAAgB;AACpD,iBAAK,OAAO,EAAE;AACd,iBAAK,WAAW,KAAK,KAAK;AAAA,UAC5B;AAAA,QACF;AAAA,MACF,CAAC;AACD,kBAAY,QAAQ,EAAE,MAAM,gBAAgB,UAAU,KAAK,CAAC;AAC5D,WAAK,UAAU,KAAK,WAAW;AAAA,IACjC,QAAQ;AAAA,IAER;AAGA,QAAI;AACF,YAAM,gBAAgB,IAAI,oBAAoB,CAAC,SAAS;AACtD,mBAAW,SAAS,KAAK,WAAW,GAAG;AACrC,gBAAM,IAAI;AACV,cAAI,EAAE,iBAAiB,EAAE,WAAW,KAAK,KAAK;AAC5C,iBAAK,MAAM,EAAE;AAAA,UACf;AAAA,QACF;AAAA,MACF,CAAC;AAGD,UAAI;AACF,sBAAc,QAAQ,EAAE,MAAM,SAAS,UAAU,MAAM,mBAAmB,GAAG,CAA4B;AAAA,MAC3G,QAAQ;AACN,sBAAc,QAAQ,EAAE,MAAM,eAAe,UAAU,KAAK,CAAC;AAAA,MAC/D;AACA,WAAK,UAAU,KAAK,aAAa;AAAA,IACnC,QAAQ;AAAA,IAER;AAIA,UAAM,QAAQ,MAAY;AACxB,UAAI,WAAW,KAAK,CAAC,KAAK,QAAQ,IAAI,KAAK,GAAG;AAC5C,aAAK,QAAQ,IAAI,KAAK;AACtB,aAAK,OAAO,iBAAiB,EAAE,SAAS,KAAK,MAAM,QAAQ,EAAE,CAAC;AAAA,MAChE;AACA,UAAI,KAAK,MAAM,KAAK,CAAC,KAAK,QAAQ,IAAI,KAAK,GAAG;AAC5C,aAAK,QAAQ,IAAI,KAAK;AACtB,aAAK,OAAO,iBAAiB,EAAE,OAAO,KAAK,MAAM,KAAK,MAAM,GAAI,IAAI,IAAK,CAAC;AAAA,MAC5E;AACA,UAAI,KAAK,MAAM,KAAK,CAAC,KAAK,QAAQ,IAAI,KAAK,GAAG;AAC5C,aAAK,QAAQ,IAAI,KAAK;AACtB,aAAK,OAAO,iBAAiB,EAAE,SAAS,KAAK,MAAM,KAAK,GAAG,EAAE,CAAC;AAAA,MAChE;AAAA,IACF;AACA,UAAM,WAAW,MAAY;AAC3B,UAAI,IAAI,oBAAoB,SAAU,OAAM;AAAA,IAC9C;AACA,QAAI,iBAAiB,oBAAoB,QAAQ;AACjD,IAAC,WAAkC,OAAO,iBAAiB,YAAY,KAAK;AAC5E,SAAK,SAAS,KAAK,MAAM;AACvB,UAAI,oBAAoB,oBAAoB,QAAQ;AACpD,MAAC,WAAkC,OAAO,oBAAoB,YAAY,KAAK;AAAA,IACjF,CAAC;AAAA,EACH;AAAA,EAEA,YAAkB;AAChB,eAAW,KAAK,KAAK,WAAW;AAC9B,UAAI;AACF,UAAE,WAAW;AAAA,MACf,QAAQ;AAAA,MAER;AAAA,IACF;AACA,SAAK,YAAY,CAAC;AAClB,eAAW,MAAM,KAAK,SAAS,OAAO,CAAC,GAAG;AACxC,UAAI;AACF,WAAG;AAAA,MACL,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACF;;;ACzJA,IAAM,cAA4B;AAAA,EAChC,WAAW;AAAA,EACX,WAAW;AAAA,EACX,QAAQ;AACV;AAEO,IAAM,iBAAN,MAAqB;AAAA,EAI1B,YAAY,SAAoC;AAHhD,SAAQ,QAAsB,EAAE,GAAG,YAAY;AAC/C,SAAQ,YAAY;AAGlB,QAAI,SAAS,cAAc,KAAK,UAAU,GAAG;AAC3C,WAAK,YAAY;AACjB,WAAK,QAAQ,EAAE,WAAW,OAAO,WAAW,OAAO,QAAQ,MAAM;AAAA,IACnE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,IAAI,SAA8C;AAChD,QAAI,KAAK,UAAW,QAAO,EAAE,GAAG,KAAK,MAAM;AAC3C,eAAW,KAAK,OAAO,KAAK,OAAO,GAAgC;AACjE,YAAM,IAAI,QAAQ,CAAC;AACnB,UAAI,OAAO,MAAM,UAAW,MAAK,MAAM,CAAC,IAAI;AAAA,IAC9C;AACA,WAAO,EAAE,GAAG,KAAK,MAAM;AAAA,EACzB;AAAA;AAAA,EAGA,MAAoB;AAClB,WAAO,EAAE,GAAG,KAAK,MAAM;AAAA,EACzB;AAAA;AAAA,EAGA,IAAI,YAAqB;AACvB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EACA,IAAI,YAAqB;AACvB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EACA,IAAI,SAAkB;AACpB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA;AAAA,EAGA,IAAI,cAAuB;AACzB,WAAO,KAAK;AAAA,EACd;AAAA,EAEQ,YAAqB;AAC3B,QAAI;AACF,YAAM,MAAO,WAAyC;AACtD,UAAI,CAAC,IAAK,QAAO;AAIjB,YAAM,UAAU;AAAA,QACb,IAA4C;AAAA,QAC5C,IAA8C;AAAA,QAC9C,WAAuC;AAAA,MAC1C;AACA,aAAO,QAAQ,KAAK,CAAC,MAAM,MAAM,OAAO,MAAM,KAAK;AAAA,IACrD,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAYA,IAAM,gBACJ;AAYF,IAAM,eAAe;AAOrB,IAAM,oBAAoB;AAC1B,IAAM,mBAAmB;AAgBlB,SAAS,SAAS,OAAuB;AAC9C,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,MACJ,QAAQ,eAAe,iBAAiB,EACxC,QAAQ,cAAc,gBAAgB;AAC3C;AAoBO,SAAS,uBACd,YACyB;AACzB,QAAM,MAA+B,CAAC;AACtC,aAAW,KAAK,OAAO,KAAK,UAAU,GAAG;AACvC,QAAI,CAAC,IAAI,WAAW,WAAW,CAAC,CAAC;AAAA,EACnC;AACA,SAAO;AACT;AAEA,SAAS,WAAW,GAAqB;AACvC,MAAI,OAAO,MAAM,SAAU,QAAO,SAAS,CAAC;AAC5C,MAAI,MAAM,QAAQ,CAAC,EAAG,QAAO,EAAE,IAAI,UAAU;AAC7C,MAAI,KAAK,OAAO,MAAM,YAAa,EAAa,gBAAgB,QAAQ;AAItE,WAAO,uBAAuB,CAA4B;AAAA,EAC5D;AACA,SAAO;AACT;;;AC5JO,IAAM,mBAAN,MAAuB;AAAA,EAE5B,YAA6B,UAAkB,IAAI;AAAtB;AAD7B,SAAQ,QAAsB,CAAC;AAAA,EACqB;AAAA,EAEpD,IAAI,OAAyB;AAC3B,SAAK,MAAM,KAAK,KAAK;AACrB,QAAI,KAAK,MAAM,SAAS,KAAK,SAAS;AACpC,WAAK,MAAM,MAAM;AAAA,IACnB;AAAA,EACF;AAAA;AAAA,EAGA,WAAyB;AACvB,WAAO,KAAK,MAAM,MAAM;AAAA,EAC1B;AAAA,EAEA,QAAc;AACZ,SAAK,QAAQ,CAAC;AAAA,EAChB;AAAA,EAEA,IAAI,OAAe;AACjB,WAAO,KAAK,MAAM;AAAA,EACpB;AACF;;;ACbO,IAAM,gCACX;AAMK,IAAM,uCACX;AAMK,SAAS,+BAAwC;AACtD,SAAO,CAAC,qCAAqC;AAAA,IAC3C;AAAA,EACF;AACF;AAQO,IAAM,oCAAyD,oBAAI,IAAI;AAAA,EAC5E;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA;AACF,CAAC;AAWM,SAAS,wBACd,SACwB;AACxB,QAAM,WAAmC,CAAC;AAC1C,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,OAAO,GAAG;AAC5C,QAAI,kCAAkC,IAAI,CAAC,KAAK,OAAO,MAAM,UAAU;AACrE,eAAS,CAAC,IAAI;AAAA,IAChB;AAAA,EACF;AACA,SAAO;AACT;AAYO,SAAS,wBACd,SACM;AACN,MAAI,CAAC,6BAA6B,EAAG;AACrC,QAAM,WAAW,wBAAwB,OAAO;AAChD,MAAI,OAAO,KAAK,QAAQ,EAAE,WAAW,EAAG;AAQxC,QAAM,OAAO,KAAK,UAAU;AAAA,IAC1B,QAAQ;AAAA,MACN;AAAA,QACE,MAAM;AAAA,QACN,WAAW,KAAK,IAAI;AAAA,QACpB,YAAY;AAAA,MACd;AAAA,IACF;AAAA,EACF,CAAC;AAQD,QAAM,IAAK,WAAwC;AACnD,MAAI,OAAO,MAAM,WAAY;AAE7B,MAAI;AACF,SAAK,EAAE,+BAA+B;AAAA,MACpC,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,eAAe,UAAU,oCAAoC;AAAA,QAC7D,yBAAyB,GAAG,QAAQ,IAAI,WAAW;AAAA,MACrD;AAAA,MACA;AAAA,MACA,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA,MAKX,aAAa;AAAA,MACb,OAAO;AAAA,MACP,gBAAgB;AAAA,IAClB,CAAC,EAAE,MAAM,MAAM;AAAA,IAGf,CAAC;AAAA,EACH,QAAQ;AAAA,EAER;AACF;;;ACnJO,IAAM,wBAAmD,OAAO,OAAO;AAAA;AAAA,EAE5E;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AAAA;AAAA,EAGA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AAAA;AAAA,EAGA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AACF,CAAU;AAGH,SAAS,aAAa,MAA0C;AACrE,SAAO,sBAAsB,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI;AAC1D;;;ACrGO,SAAS,qBAAqB,MAIjB;AAClB,SAAO;AAAA,IACL,YAAY,GAAG,QAAQ,IAAI,WAAW;AAAA,IACtC,OAAO,aAAa,UAAU,CAAC,CAAC;AAAA,IAChC,YAAY,KAAK,cAAc;AAAA,IAC/B,oBAAoB,KAAK;AAAA,IACzB,2BAA2B,KAAK;AAAA,IAChC;AAAA,IACA,eAAe;AAAA,EACjB;AACF;AAiBO,IAAM,mBAAN,MAAuB;AAAA,EAG5B,YAA6B,KAAsB;AAAtB;AAF7B,SAAQ,kBAAkB;AAAA,EAE0B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOpD,OACE,QACA,OACA,WACM;AAGN,QAAI,KAAK,IAAI,0BAA2B;AAExC,QAAI,OAAO,IAAI;AACb,WAAK,WAAW,QAAQ,OAAO,SAAS;AAAA,IAC1C,OAAO;AACL,WAAK,WAAW,QAAQ,OAAO,SAAS;AAAA,IAC1C;AAAA,EACF;AAAA,EAEQ,WACN,QACA,OACA,WACM;AACN,QAAI,CAAC,KAAK,IAAI,mBAAoB;AAClC,UAAM,OAAO,eAAe,QAAQ,SAAS;AAC7C,QAAI,UAAU,QAAQ;AACpB,WAAK,IAAI,QAAQ,KAAK,IAAI;AAAA,IAC5B,OAAO;AACL,WAAK,IAAI,QAAQ,MAAM,IAAI;AAAA,IAC7B;AAAA,EACF;AAAA,EAEQ,WACN,QACA,OACA,WACM;AAKN,SAAK,IAAI,QAAQ,KAAK,eAAe,QAAQ,SAAS,CAAC;AASvD,QAAI,KAAK,kBAAkB,EAAG;AAC9B,SAAK,mBAAmB;AACxB,QAAI;AACF,WAAK,IAAI,cAAc;AAAA,QACrB,aAAa,OAAO;AAAA,QACpB,aAAa,KAAK,IAAI;AAAA,QACtB,cAAc;AAAA,QACd,gBAAgB,SAAS,OAAO,eAAe,GAAG;AAAA,QAClD,aAAa,KAAK,IAAI;AAAA,QACtB,QAAQ,KAAK,IAAI;AAAA,QACjB,oBAAoB;AAAA,MACtB,CAAC;AAAA,IACH,UAAE;AACA,WAAK,mBAAmB;AAAA,IAC1B;AAAA,EACF;AACF;AAEA,SAAS,eAAe,GAAiB,WAA4B;AACnE,QAAM,SAAS,YAAY,cAAc,SAAS,MAAM;AACxD,SAAO,GAAG,MAAM,WAAM,EAAE,UAAU,WAAM,EAAE,QAAQ,KAAK,EAAE,UAAU;AACrE;AAEA,SAAS,eAAe,GAAiB,WAA4B;AACnE,QAAM,SAAS,YAAY,cAAc,SAAS,MAAM;AACxD,SAAO,GAAG,MAAM,WAAM,EAAE,UAAU,WAAM,EAAE,aAAa,KAAK,EAAE,UAAU;AAC1E;AA0FA,IAAM,oCAAsD;AAAA,EAC1D,YAAY;AAAA,EAEZ,WAA2B;AACzB,UAAM,KAAK,MAAM;AACjB,QAAI;AACF,YAAM,UAAU,IAAIC,eAAc;AAGlC,YAAM,QAAQ,IAAI,iBAAiB,SAAS,WAAW;AAMvD,YAAM,WAAW,QAAQ;AACzB,YAAM,YAAY;AAAA,QAChB;AAAA,UACE,QAAQ;AAAA,UACR,KAAK;AAAA,UACL,UAAU;AAAA,UACV,YAAY;AAAA,UACZ,QAAQ;AAAA,YACN,MAAM;AAAA,YACN,WAAW;AAAA,YACX,gBAAgB;AAAA,UAClB;AAAA,UACA,WAAW,KAAK,IAAI;AAAA,QACtB;AAAA,MACF,CAAC;AAGD,YAAM,WAAW,QAAQ;AAMzB,YAAM,YAAY,MAAM,KAAK;AAC7B,UAAI,UAAU,WAAW,GAAG;AAC1B,eAAO;AAAA,UACL;AAAA,UACA;AAAA,UACA,MAAM,IAAI;AAAA,QACZ;AAAA,MACF;AAEA,YAAM,UAAU,iBAAiB,gBAAgB,QAAQ;AACzD,YAAM,UAAU,iBAAiB,gBAAgB,QAAQ;AACzD,UAAI,YAAY,SAAS;AACvB,eAAO;AAAA,UACL;AAAA,UACA;AAAA,UACA,MAAM,IAAI;AAAA,QACZ;AAAA,MACF;AAEA,YAAM,WAAW,QAAQ,QAAQ,aAAa,OAAO,EAAE;AACvD,YAAM,WAAW,QAAQ,QAAQ,aAAa,OAAO,EAAE;AAIvD,UAAI,CAAC,UAAU;AACb,eAAO;AAAA,UACL;AAAA,UACA;AAAA,UACA,MAAM,IAAI;AAAA,QACZ;AAAA,MACF;AACA,UAAI,UAAU;AACZ,eAAO;AAAA,UACL;AAAA,UACA;AAAA,UACA,MAAM,IAAI;AAAA,QACZ;AAAA,MACF;AAEA,aAAO;AAAA,QACL;AAAA,QACA,kBAAkB,YAAY,OAAO,CAAC,aAAQ,YAAY,OAAO,CAAC;AAAA,QAClE,MAAM,IAAI;AAAA,MACZ;AAAA,IACF,SAAS,KAAK;AACZ,aAAO;AAAA,QACL;AAAA,QACA,oBAAqB,IAAc,SAAS,MAAM,GAAG,EAAE,KAAK,eAAe;AAAA,QAC3E,MAAM,IAAI;AAAA,MACZ;AAAA,IACF;AAAA,EACF;AAAA,EAEA,OAAO;AAAA,IACL,WAAW,KAA0C;AACnD,YAAM,KAAK,MAAM;AACjB,YAAM,cAAc,iBAAiB,gBAAgB,IAAI,WAAW;AACpE,YAAM,aAAa,iBAAiB,gBAAgB,IAAI,UAAU;AAElE,UAAI,gBAAgB,YAAY;AAK9B,YAAI,IAAI,MAAM,KAAK,EAAE,WAAW,GAAG;AACjC,iBAAO;AAAA,YACL;AAAA,YACA;AAAA,YACA,MAAM,IAAI;AAAA,UACZ;AAAA,QACF;AACA,eAAO;AAAA,UACL;AAAA,UACA,gBAAgB,YAAY,WAAW,CAAC,WAAM,YAAY,UAAU,CAAC;AAAA,UACrE,MAAM,IAAI;AAAA,QACZ;AAAA,MACF;AAUA,aAAO;AAAA,QACL;AAAA,QACA,+BAA+B,YAAY,UAAU,CAAC;AAAA,QACtD,MAAM,IAAI;AAAA,MACZ;AAAA,IACF;AAAA,EACF;AACF;AAaA,IAAM,sBAAsB;AAC5B,IAAM,kCAAkC;AAExC,IAAM,yCAA2D;AAAA,EAC/D,YAAY;AAAA,EAEZ,WAA2B;AACzB,UAAM,KAAK,MAAM;AACjB,QAAI;AACF,YAAM,UAAU,gCAAgC;AAAA,QAC9C,MAAM;AAAA,QACN,uBAAuB;AAAA,MACzB,CAAC;AACD,UAAI,YAAY,iCAAiC;AAC/C,eAAO;AAAA,UACL;AAAA,UACA,+BAA+B,OAAO,cAAc,+BAA+B;AAAA,UACnF,MAAM,IAAI;AAAA,QACZ;AAAA,MACF;AAGA,YAAM,SAAS,gCAAgC;AAAA,QAC7C,MAAM;AAAA,QACN,uBAAuB;AAAA,MACzB,CAAC;AACD,UAAI,WAAW,SAAS;AACtB,eAAO;AAAA,UACL;AAAA,UACA;AAAA,UACA,MAAM,IAAI;AAAA,QACZ;AAAA,MACF;AAIA,YAAM,WAAW;AACjB,YAAM,YAAY,gCAAgC;AAAA,QAChD,MAAM;AAAA,QACN,eAAe;AAAA,MACjB,CAAC;AACD,UAAI,aAAa,WAAW;AAC1B,eAAO;AAAA,UACL;AAAA,UACA;AAAA,UACA,MAAM,IAAI;AAAA,QACZ;AAAA,MACF;AAEA,aAAO;AAAA,QACL;AAAA,QACA,oBAAe,OAAO;AAAA,QACtB,MAAM,IAAI;AAAA,MACZ;AAAA,IACF,SAAS,KAAK;AACZ,aAAO;AAAA,QACL;AAAA,QACA,oBAAqB,IAAc,SAAS,MAAM,GAAG,EAAE,KAAK,eAAe;AAAA,QAC3E,MAAM,IAAI;AAAA,MACZ;AAAA,IACF;AAAA,EACF;AAAA,EAEA,OAAO;AAAA,IACL,gBAAgB,KAA+C;AAC7D,YAAM,KAAK,MAAM;AAGjB,UAAI;AACF,cAAM,WAAW;AAAA,UACf,IAAI,SAAS,UACT,EAAE,MAAM,SAAS,uBAAuB,IAAI,iBAAiB,IAC7D,EAAE,MAAM,IAAI,MAAM,eAAe,IAAI,iBAAiB;AAAA,QAC5D;AACA,YAAI,aAAa,IAAI,YAAY;AAC/B,iBAAO;AAAA,YACL;AAAA,YACA;AAAA,YACA,MAAM,IAAI;AAAA,UACZ;AAAA,QACF;AACA,eAAO;AAAA,UACL;AAAA,UACA,GAAG,IAAI,IAAI,WAAM,IAAI,UAAU;AAAA,UAC/B,MAAM,IAAI;AAAA,QACZ;AAAA,MACF,SAAS,KAAK;AACZ,eAAO;AAAA,UACL;AAAA,UACA,8BAA+B,IAAc,SAAS,MAAM,GAAG,EAAE,KAAK,eAAe;AAAA,UACrF,MAAM,IAAI;AAAA,QACZ;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAMA,IAAM,gCAAkD;AAAA,EACtD,YAAY;AAAA,EAEZ,WAA2B;AACzB,UAAM,KAAK,MAAM;AAGjB,UAAM,OAAO;AAAA,MACX,OAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,QACT,YAAY;AAAA,MACd;AAAA,IACF;AAKA,UAAM,gBAAgB,oBAAI,IAAI;AAAA,MAC5B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAED,UAAM,MAAM,KAAK;AACjB,UAAM,UAAU,CAAC,QAAQ,QAAQ,WAAW,YAAY,EAAE;AAAA,MACxD,CAAC,MAAM,EAAE,KAAK,QAAQ,OAAQ,IAAgC,CAAC,MAAM;AAAA,IACvE;AACA,QAAI,QAAQ,SAAS,GAAG;AACtB,aAAO;AAAA,QACL;AAAA,QACA,qCAAqC,QAAQ,KAAK,IAAI,CAAC;AAAA,QACvD,MAAM,IAAI;AAAA,MACZ;AAAA,IACF;AACA,QAAI,CAAC,cAAc,IAAI,IAAI,IAAI,GAAG;AAChC,aAAO;AAAA,QACL;AAAA,QACA,eAAe,IAAI,IAAI;AAAA,QACvB,MAAM,IAAI;AAAA,MACZ;AAAA,IACF;AACA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,MAAM,IAAI;AAAA,IACZ;AAAA,EACF;AAAA,EAEA,OAAO;AAAA,IACL,aAAa,KAA4C;AACvD,YAAM,KAAK,MAAM;AACjB,YAAM,gBAAgB,oBAAI,IAAI;AAAA,QAC5B;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AACD,UAAI,CAAC,cAAc,IAAI,IAAI,SAAS,GAAG;AACrC,eAAO;AAAA,UACL;AAAA,UACA,oBAAoB,IAAI,SAAS;AAAA,UACjC,MAAM,IAAI;AAAA,QACZ;AAAA,MACF;AACA,UAAI,CAAC,IAAI,aAAa,IAAI,UAAU,WAAW,GAAG;AAChD,eAAO;AAAA,UACL;AAAA,UACA;AAAA,UACA,MAAM,IAAI;AAAA,QACZ;AAAA,MACF;AAKA,aAAO;AAAA,QACL;AAAA,QACA,GAAG,IAAI,SAAS,IAAI,IAAI,SAAS,OAAO,IAAI,UAAU,GAAG,IAAI,YAAY,KAAK,IAAI,UAAU,MAAM,GAAG,EAAE,CAAC,YAAO,EAAE;AAAA,QACjH,MAAM,IAAI;AAAA,MACZ;AAAA,IACF;AAAA,EACF;AACF;AAUA,IAAM,iCAAmD;AAAA,EACvD,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOZ,WAA2B;AACzB,UAAM,KAAK,MAAM;AAQjB,UAAM,uBAAuB;AAG7B,QAAI,yBAAyB,KAAM;AACjC,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA,MAAM,IAAI;AAAA,MACZ;AAAA,IACF;AACA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,MAAM,IAAI;AAAA,IACZ;AAAA,EACF;AAAA;AAAA;AAGF;AAQO,SAAS,2BACd,sBACkB;AAClB,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,WAA2B;AACzB,YAAM,KAAK,MAAM;AACjB,YAAM,uBAAuB;AAO7B,UAAI,uBAAuB,OAAO,uBAAuB,KAAQ;AAC/D,eAAO;AAAA,UACL;AAAA,UACA,mCAAmC,oBAAoB;AAAA,UACvD,MAAM,IAAI;AAAA,QACZ;AAAA,MACF;AACA,UAAI,yBAAyB,sBAAsB;AAGjD,eAAO;AAAA,UACL;AAAA,UACA,0BAA0B,oBAAoB;AAAA,UAC9C,MAAM,IAAI;AAAA,QACZ;AAAA,MACF;AACA,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA,MAAM,IAAI;AAAA,MACZ;AAAA,IACF;AAAA,EACF;AACF;AAMA,IAAM,2CAA6D;AAAA,EACjE,YAAY;AAAA,EAEZ,WAA2B;AACzB,UAAM,KAAK,MAAM;AAEjB,UAAM,SAAS,EAAE,MAAM,eAAe,IAAI,QAAQ;AAClD,UAAM,aAAa,EAAE,MAAM,cAAc,YAAY,QAAQ;AAC7D,UAAM,SAAS,EAAE,MAAM,eAAe,eAAe,KAAK;AAI1D,UAAM,SAAS,EAAE,GAAG,QAAQ,GAAG,YAAY,GAAG,OAAO;AAErD,QAAI,OAAO,SAAS,eAAe;AACjC,aAAO;AAAA,QACL;AAAA,QACA,kBAAkB,OAAO,IAAI;AAAA,QAC7B,MAAM,IAAI;AAAA,MACZ;AAAA,IACF;AACA,QAAK,OAAmC,eAAe,SAAS;AAC9D,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA,MAAM,IAAI;AAAA,MACZ;AAAA,IACF;AACA,QAAK,OAAmC,OAAO,SAAS;AACtD,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA,MAAM,IAAI;AAAA,MACZ;AAAA,IACF;AACA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,MAAM,IAAI;AAAA,IACZ;AAAA,EACF;AAAA,EAEA,OAAO;AAAA,IACL,QAAQ,KAAuC;AAC7C,YAAM,KAAK,MAAM;AAOjB,iBAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,IAAI,gBAAgB,GAAG;AACzD,YAAI,IAAI,iBAAiB,CAAC,MAAM,GAAG;AACjC,iBAAO;AAAA,YACL;AAAA,YACA,eAAe,CAAC;AAAA,YAChB,MAAM,IAAI;AAAA,UACZ;AAAA,QACF;AAAA,MACF;AACA,iBAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,IAAI,eAAe,GAAG;AACxD,YAAI,KAAK,IAAI,iBAAkB;AAC/B,YAAI,IAAI,iBAAiB,CAAC,MAAM,GAAG;AACjC,iBAAO;AAAA,YACL;AAAA,YACA,cAAc,CAAC;AAAA,YACf,MAAM,IAAI;AAAA,UACZ;AAAA,QACF;AAAA,MACF;AACA,aAAO;AAAA,QACL;AAAA,QACA,UAAU,OAAO,KAAK,IAAI,gBAAgB,EAAE,MAAM,aAAa,OAAO,KAAK,IAAI,eAAe,EAAE,MAAM;AAAA,QACtG,MAAM,IAAI;AAAA,MACZ;AAAA,IACF;AAAA,EACF;AACF;AA2BA,IAAM,kCAAkC;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,IAAM,kCAAkC;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,IAAM,mCAAmC;AAAA;AAAA;AAAA;AAAA,EAIvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,IAAM,+CAAiE;AAAA,EACrE,YAAY;AAAA,EAEZ,WAA2B;AACzB,UAAM,KAAK,MAAM;AAMjB,UAAM,mBAA4C;AAAA,MAChD,aAAa;AAAA,MACb,aAAa;AAAA,MACb,cAAc;AAAA,MACd,gBAAgB;AAAA,MAChB,aAAa;AAAA,MACb,QAAQ;AAAA,MACR,oBAAoB;AAAA,IACtB;AAEA,UAAM,OAAO,OAAO,KAAK,gBAAgB;AACzC,UAAM,UAAU,oBAAI,IAAY;AAAA,MAC9B,GAAG;AAAA,MACH,GAAG;AAAA,IACL,CAAC;AACD,UAAM,YAAY,IAAI,IAAY,gCAAgC;AAGlE,eAAW,YAAY,iCAAiC;AACtD,UAAI,CAAC,KAAK,SAAS,QAAQ,GAAG;AAC5B,eAAO;AAAA,UACL;AAAA,UACA,2BAA2B,QAAQ;AAAA,UACnC,MAAM,IAAI;AAAA,QACZ;AAAA,MACF;AAAA,IACF;AAEA,eAAW,KAAK,MAAM;AACpB,UAAI,UAAU,IAAI,CAAC,GAAG;AACpB,eAAO;AAAA,UACL;AAAA,UACA,4BAA4B,CAAC;AAAA,UAC7B,MAAM,IAAI;AAAA,QACZ;AAAA,MACF;AAAA,IACF;AAEA,eAAW,KAAK,MAAM;AACpB,UAAI,CAAC,QAAQ,IAAI,CAAC,GAAG;AACnB,eAAO;AAAA,UACL;AAAA,UACA,+BAA+B,CAAC;AAAA,UAChC,MAAM,IAAI;AAAA,QACZ;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,MACL;AAAA,MACA,GAAG,KAAK,MAAM,2BAAsB,gCAAgC,MAAM,qBAAgB,gCAAgC,MAAM,MAAM,iCAAiC,MAAM;AAAA,MAC7K,MAAM,IAAI;AAAA,IACZ;AAAA,EACF;AACF;AAgBA,IAAM,qBAAwC,OAAO,OAAO;AAAA,EAC1D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAUD,IAAM,qCAAuD;AAAA,EAC3D,YAAY;AAAA,EACZ,WAA2B;AACzB,UAAM,KAAK,MAAM;AACjB,QAAI;AACF,YAAM,UAAoB,CAAC;AAC3B,iBAAW,QAAQ,oBAAoB;AACrC,cAAM,QAAQ,aAAa,IAAI;AAC/B,YACE,CAAC,SACD,CAAC,MAAM,eACP,MAAM,YAAY,KAAK,EAAE,WAAW,KACpC,CAAC,MAAM,cACP,MAAM,WAAW,KAAK,EAAE,WAAW,GACnC;AACA,kBAAQ,KAAK,IAAI;AAAA,QACnB;AAAA,MACF;AACA,UAAI,QAAQ,SAAS,GAAG;AACtB,eAAO;AAAA,UACL;AAAA,UACA,iEAAiE,QAAQ,KAAK,IAAI,CAAC;AAAA,UACnF,MAAM,IAAI;AAAA,QACZ;AAAA,MACF;AACA,aAAO;AAAA,QACL;AAAA,QACA,OAAO,mBAAmB,MAAM;AAAA,QAChC,MAAM,IAAI;AAAA,MACZ;AAAA,IACF,SAAS,KAAK;AACZ,aAAO;AAAA,QACL;AAAA,QACA,oBAAqB,IAAc,SAAS,MAAM,GAAG,EAAE,KAAK,eAAe;AAAA,QAC3E,MAAM,IAAI;AAAA,MACZ;AAAA,IACF;AAAA,EACF;AACF;AAUO,IAAM,mBAAgD,OAAO,OAAO;AAAA,EACzE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAWD,eAAsB,gBACpB,WACA,UACA,KAC8D;AAC9D,MAAI,IAAI,2BAA2B;AACjC,WAAO,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,EAAE;AAAA,EAC5C;AAEA,QAAM,KAAK,MAAM;AACjB,MAAI,SAAS;AACb,MAAI,SAAS;AAEb,MAAI,IAAI,oBAAoB;AAM1B,UAAM,gBAAgB,UAAU,OAAO,CAAC,MAAM,EAAE,QAAQ,EAAE;AAC1D,UAAM,YAAY,UAAU,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE;AACnD,UAAM,MAAM,UAAU,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,IAAI;AACxD,QAAI,QAAQ;AAAA,MACV,iDAA4C,UAAU,MAAM,eAAe,aAAa,gBAAgB,SAAS,qBAAqB,GAAG;AAAA,IAC3I;AAAA,EACF;AAEA,aAAW,YAAY,WAAW;AAChC,QAAI,CAAC,SAAS,SAAU;AACxB,QAAI;AACJ,QAAI;AACF,eAAS,MAAM,SAAS,SAAS;AAAA,IACnC,SAAS,KAAK;AACZ,eAAS;AAAA,QACP,SAAS;AAAA,QACT,mBAAoB,IAAc,SAAS,MAAM,GAAG,EAAE,KAAK,SAAS;AAAA,QACpE;AAAA,MACF;AAAA,IACF;AACA,aAAS,OAAO,QAAQ,MAAM;AAC9B,QAAI,OAAO,GAAI,WAAU;AAAA,QACpB,WAAU;AAAA,EACjB;AAEA,QAAM,UAAU,MAAM,IAAI;AAC1B,MAAI,IAAI,oBAAoB;AAC1B,UAAM,OAAO,WAAW,IAAI,WAAW;AACvC,QAAI,QAAQ;AAAA,MACV,iCAAiC,IAAI,WAAM,MAAM,YAAY,MAAM,YAAY,OAAO;AAAA,IACxF;AAAA,EACF;AACA,SAAO,EAAE,QAAQ,QAAQ,QAAQ;AACnC;AAYO,SAAS,cACd,WACA,UACA,KACA,KACM;AACN,MAAI,IAAI,0BAA2B;AACnC,aAAW,YAAY,WAAW;AAChC,UAAM,OAAO,SAAS,OAAO;AAC7B,QAAI,CAAC,KAAM;AACX,QAAI;AACJ,QAAI;AACF,eAAS,KAAK,GAAG;AAAA,IACnB,SAAS,KAAK;AACZ,eAAS;AAAA,QACP,SAAS;AAAA,QACT,eAAgB,IAAc,SAAS,MAAM,GAAG,EAAE,KAAK,SAAS;AAAA,QAChE;AAAA,MACF;AAAA,IACF;AACA,aAAS,OAAO,QAAQ,YAAY,UAAU;AAAA,EAChD;AACF;AAEO,SAAS,WACd,WACA,UACA,KACA,KACM;AACN,MAAI,IAAI,0BAA2B;AACnC,aAAW,YAAY,WAAW;AAChC,UAAM,OAAO,SAAS,OAAO;AAC7B,QAAI,CAAC,KAAM;AACX,QAAI;AACJ,QAAI;AACF,eAAS,KAAK,GAAG;AAAA,IACnB,SAAS,KAAK;AACZ,eAAS;AAAA,QACP,SAAS;AAAA,QACT,eAAgB,IAAc,SAAS,MAAM,GAAG,EAAE,KAAK,SAAS;AAAA,QAChE;AAAA,MACF;AAAA,IACF;AACA,aAAS,OAAO,QAAQ,YAAY,OAAO;AAAA,EAC7C;AACF;AAEO,SAAS,mBACd,WACA,UACA,KACA,KACM;AACN,MAAI,IAAI,0BAA2B;AACnC,aAAW,YAAY,WAAW;AAChC,UAAM,OAAO,SAAS,OAAO;AAC7B,QAAI,CAAC,KAAM;AACX,QAAI;AACJ,QAAI;AACF,eAAS,KAAK,GAAG;AAAA,IACnB,SAAS,KAAK;AACZ,eAAS;AAAA,QACP,SAAS;AAAA,QACT,eAAgB,IAAc,SAAS,MAAM,GAAG,EAAE,KAAK,SAAS;AAAA,QAChE;AAAA,MACF;AAAA,IACF;AACA,aAAS,OAAO,QAAQ,YAAY,eAAe;AAAA,EACrD;AACF;AAEO,SAAS,gBACd,WACA,UACA,KACA,KACM;AACN,MAAI,IAAI,0BAA2B;AACnC,aAAW,YAAY,WAAW;AAChC,UAAM,OAAO,SAAS,OAAO;AAC7B,QAAI,CAAC,KAAM;AACX,QAAI;AACJ,QAAI;AACF,eAAS,KAAK,GAAG;AAAA,IACnB,SAAS,KAAK;AACZ,eAAS;AAAA,QACP,SAAS;AAAA,QACT,eAAgB,IAAc,SAAS,MAAM,GAAG,EAAE,KAAK,SAAS;AAAA,QAChE;AAAA,MACF;AAAA,IACF;AACA,aAAS,OAAO,QAAQ,YAAY,YAAY;AAAA,EAClD;AACF;AAmBO,SAAS,uBAAgC;AAG9C,MAAI;AACF,QAAI,OAAO,YAAY,eAAe,QAAQ,KAAK;AACjD,YAAM,UAAU,QAAQ,IAAI;AAC5B,UAAI,OAAO,YAAY,UAAU;AAC/B,eAAO,YAAY;AAAA,MACrB;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AACA,QAAM,UAAW,WAAqC;AACtD,MAAI,OAAO,YAAY,UAAW,QAAO;AAGzC,SAAO;AACT;AAMA,SAAS,KACP,YACA,UACA,YACc;AACd,SAAO,EAAE,IAAI,MAAM,YAAY,UAAU,WAAW;AACtD;AAEA,SAAS,KACP,YACA,eACA,YACc;AACd,SAAO,EAAE,IAAI,OAAO,YAAY,eAAe,WAAW;AAC5D;AAEA,SAAS,QAAgB;AAGvB,MAAI,OAAO,gBAAgB,eAAe,OAAO,YAAY,QAAQ,YAAY;AAC/E,WAAO,YAAY,IAAI;AAAA,EACzB;AACA,SAAO,KAAK,IAAI;AAClB;AAEA,SAAS,SAAS,GAAW,KAAqB;AAChD,SAAO,EAAE,SAAS,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,IAAI,WAAM;AACtD;AAEA,SAAS,YAAY,QAAwB;AAG3C,QAAM,UAAU,OAAO,WAAW,GAAG,IAAI,OAAO,MAAM,CAAC,IAAI;AAC3D,SAAO,QAAQ,UAAU,KAAK,UAAU,GAAG,QAAQ,MAAM,GAAG,CAAC,CAAC,SAAI,QAAQ,MAAM,EAAE,CAAC;AACrF;AAEA,SAAS,UAAU,KAAqB;AACtC,QAAM,QAAQ,IAAI,WAAW,KAAK,KAAK,MAAM,CAAC,CAAC;AAG/C,MACE,OAAO,eAAe,eACrB,WAA0E,QACvE,iBACJ;AACA,IAAC,WAAwE,OAAO,gBAAgB,KAAK;AAAA,EACvG,OAAO;AACL,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,GAAG;AACxC,YAAM,CAAC,IAAI,KAAK,MAAM,KAAK,OAAO,IAAI,GAAG;AAAA,IAC3C;AAAA,EACF;AACA,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,GAAG;AACxC,WAAQ,MAAM,CAAC,EAAa,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AAAA,EAC1D;AACA,SAAO,IAAI,MAAM,GAAG,GAAG;AACzB;AAWA,IAAMA,iBAAN,MAAoB;AAAA,EAApB;AACE,SAAiB,MAAM,oBAAI,IAAoB;AAAA;AAAA,EAC/C,QAAQ,KAA4B;AAClC,WAAO,KAAK,IAAI,IAAI,GAAG,KAAK;AAAA,EAC9B;AAAA,EACA,QAAQ,KAAa,OAAqB;AACxC,SAAK,IAAI,IAAI,KAAK,KAAK;AAAA,EACzB;AAAA,EACA,WAAW,KAAmB;AAC5B,SAAK,IAAI,OAAO,GAAG;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKA,OAAiB;AACf,WAAO,MAAM,KAAK,KAAK,IAAI,KAAK,CAAC;AAAA,EACnC;AAKF;AAAA;AAAA;AAAA;AAtBMA,eAqBG,IAAI;;;ACn0CN,SAAS,WAAW,OAAgD;AACzE,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO,CAAC;AACjD,QAAM,QAAQ,MAAM,MAAM,IAAI;AAC9B,QAAM,SAAuB,CAAC;AAC9B,aAAW,QAAQ,OAAO;AACxB,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,CAAC,QAAS;AACd,UAAM,QAAQ,UAAU,OAAO;AAC/B,QAAI,MAAO,QAAO,KAAK,KAAK;AAAA,EAC9B;AACA,SAAO;AACT;AAaA,SAAS,UAAU,MAAiC;AAGlD,MAAI,IAAI,uCAAuC,KAAK,IAAI;AACxD,MAAI,GAAG;AACL,WAAO,WAAW;AAAA,MAChB,UAAU,EAAE,CAAC;AAAA,MACb,UAAU,EAAE,CAAC;AAAA,MACb,QAAQ,SAAS,EAAE,CAAC,GAAI,EAAE;AAAA,MAC1B,OAAO,SAAS,EAAE,CAAC,GAAI,EAAE;AAAA,MACzB,KAAK;AAAA,IACP,CAAC;AAAA,EACH;AAIA,MAAI,2BAA2B,KAAK,IAAI;AACxC,MAAI,GAAG;AACL,WAAO,WAAW;AAAA,MAChB,UAAU;AAAA,MACV,UAAU,EAAE,CAAC;AAAA,MACb,QAAQ,SAAS,EAAE,CAAC,GAAI,EAAE;AAAA,MAC1B,OAAO,SAAS,EAAE,CAAC,GAAI,EAAE;AAAA,MACzB,KAAK;AAAA,IACP,CAAC;AAAA,EACH;AAIA,MAAI,4BAA4B,KAAK,IAAI;AACzC,MAAI,GAAG;AACL,WAAO,WAAW;AAAA,MAChB,UAAU,EAAE,CAAC,KAAM;AAAA,MACnB,UAAU,EAAE,CAAC;AAAA,MACb,QAAQ,SAAS,EAAE,CAAC,GAAI,EAAE;AAAA,MAC1B,OAAO,SAAS,EAAE,CAAC,GAAI,EAAE;AAAA,MACzB,KAAK;AAAA,IACP,CAAC;AAAA,EACH;AAIA,MAAI,YAAY,KAAK,IAAI,KAAK,CAAC,KAAK,SAAS,GAAG,GAAG;AACjD,WAAO;AAAA,EACT;AAGA,SAAO;AAAA,IACL,UAAU;AAAA,IACV,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,KAAK;AAAA,EACP;AACF;AAEA,SAAS,WAAW,OAML;AACb,SAAO;AAAA,IACL,UAAU,MAAM,YAAY;AAAA,IAC5B,UAAU,MAAM;AAAA,IAChB,QAAQ,OAAO,SAAS,MAAM,MAAM,IAAI,MAAM,SAAS;AAAA,IACvD,OAAO,OAAO,SAAS,MAAM,KAAK,IAAI,MAAM,QAAQ;AAAA,IACpD,QAAQ,aAAa,MAAM,QAAQ;AAAA,IACnC,KAAK,MAAM;AAAA,EACb;AACF;AASA,SAAS,aAAa,UAA2B;AAC/C,MAAI,CAAC,SAAU,QAAO;AACtB,MAAI,+CAA+C,KAAK,QAAQ,EAAG,QAAO;AAC1E,MAAI,yBAAyB,KAAK,QAAQ,EAAG,QAAO;AACpD,MAAI,iBAAiB,KAAK,QAAQ,EAAG,QAAO;AAC5C,MAAI,4BAA4B,KAAK,QAAQ,EAAG,QAAO;AACvD,MAAI,4BAA4B,KAAK,QAAQ,EAAG,QAAO;AACvD,MAAI,uBAAuB,KAAK,QAAQ,EAAG,QAAO;AAClD,MAAI,6BAA6B,KAAK,QAAQ,EAAG,QAAO;AACxD,SAAO;AACT;AAkBO,SAAS,iBACd,SACA,QACAC,WAMQ;AACR,QAAM,cAAc,OAAO,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC;AAC7D,QAAM,QAAQ;AAAA,KACX,WAAW,IAAI,MAAM,GAAG,GAAG;AAAA,IAC5B,GAAG,YAAY,IAAI,CAAC,MAAM,GAAG,EAAE,QAAQ,IAAI,EAAE,QAAQ,IAAI,EAAE,MAAM,EAAE;AAAA,EACrE;AAIA,MAAI,YAAY,WAAW,KAAKA,WAAU;AACxC,UAAM,MAAM;AAAA,MACVA,UAAS,aAAa;AAAA,MACtBA,UAAS,YAAY;AAAA,MACrBA,UAAS,UAAU;AAAA,MACnBA,UAAS,SAAS;AAAA,IACpB,EAAE,KAAK,GAAG;AACV,QAAI,QAAQ,MAAO,OAAM,KAAK,GAAG;AAAA,EACnC;AACA,SAAO,QAAQ,MAAM,KAAK,GAAG,CAAC;AAChC;AAMA,SAAS,QAAQ,OAAuB;AACtC,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,SAAM,KAAK,KAAK,IAAI,MAAM,WAAW,CAAC,IAAK;AAAA,EAC7C;AAEA,UAAQ,MAAM,GAAG,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AAC/C;;;AC1FO,IAAM,wBAA4C;AAAA,EACvD,SAAS;AAAA,EACT,SAAS;AAAA,EACT,sBAAsB;AAAA,EACtB,WAAW;AAAA,EACX,SAAS;AAAA,EACT,gBAAgB;AAAA,EAChB,cAAc;AAAA;AAAA,IAEZ;AAAA,IACA;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUF;AAAA,EACA,WAAW,CAAC;AAAA,EACZ,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IA0CR;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,YAAY;AAAA,EACZ,4BAA4B;AAAA,EAC5B,eAAe;AACjB;AAsDO,IAAM,eAAN,MAAmB;AAAA,EAOxB,YAA6B,MAA2B;AAA3B;AAN7B,SAAQ,YAAY;AACpB,SAAQ,WAA8B,CAAC;AACvC,SAAQ,aAAa;AACrB,SAAQ,eAAe;AACvB,SAAQ,oBAAoB,oBAAI,IAAsB;AAAA,EAEG;AAAA,EAEzD,UAAgB;AACd,QAAI,KAAK,UAAW;AACpB,QAAI,CAAC,KAAK,KAAK,OAAO,QAAS;AAC/B,QAAI,OAAO,eAAe,eAAe,EAAE,YAAY,YAAa;AAEpE,UAAM,IAAK,WAAkC;AAE7C,QAAI,KAAK,KAAK,OAAO,QAAS,MAAK,uBAAuB,CAAC;AAC3D,QAAI,KAAK,KAAK,OAAO,qBAAsB,MAAK,yBAAyB,CAAC;AAC1E,QAAI,KAAK,KAAK,OAAO,UAAW,MAAK,iBAAiB,CAAC;AACvD,QAAI,KAAK,KAAK,OAAO,QAAS,MAAK,eAAe,CAAC;AACnD,QAAI,KAAK,KAAK,OAAO,eAAgB,MAAK,mBAAmB;AAE7D,SAAK,YAAY;AAAA,EACnB;AAAA,EAEA,YAAkB;AAChB,eAAW,MAAM,KAAK,SAAS,OAAO,CAAC,GAAG;AACxC,UAAI;AACF,WAAG;AAAA,MACL,QAAQ;AAAA,MAER;AAAA,IACF;AACA,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aACE,OACA,SACM;AACN,QAAI,CAAC,KAAK,KAAK,YAAY,EAAG;AAC9B,QAAI;AACF,YAAM,WAAW,KAAK,iBAAiB,OAAO,iBAAiB,SAAS,SAAS,OAAO;AACxF,UAAI,SAAS,QAAS,UAAS,UAAU,EAAE,GAAG,SAAS,SAAS,GAAG,QAAQ,QAAQ;AACnF,UAAI,SAAS,KAAM,UAAS,OAAO,EAAE,GAAG,SAAS,MAAM,GAAG,QAAQ,KAAK;AACvE,WAAK,YAAY,QAAQ;AAAA,IAC3B,QAAQ;AAAA,IAGR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,eAAe,SAAiB,QAAoB,QAAc;AAChE,QAAI,CAAC,KAAK,KAAK,YAAY,EAAG;AAC9B,QAAI;AACF,YAAM,WAA0B;AAAA,QAC9B,WAAW,KAAK,IAAI;AAAA,QACpB,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA,WAAW;AAAA,QACX,QAAQ,CAAC;AAAA,QACT,UAAU;AAAA,QACV,UAAU;AAAA,QACV,QAAQ;AAAA,QACR,OAAO;AAAA,QACP,aAAa,iBAAiB,SAAS,CAAC,CAAC;AAAA,QACzC,aAAa,KAAK,KAAK,YAAY,SAAS;AAAA,QAC5C,SAAS,KAAK,KAAK,WAAW;AAAA,QAC9B,MAAM,KAAK,KAAK,QAAQ;AAAA,MAC1B;AACA,WAAK,YAAY,QAAQ;AAAA,IAC3B,QAAQ;AAAA,IAER;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMQ,uBAAuB,GAAiB;AAC9C,UAAM,UAAU,CAAC,UAA4B;AAC3C,UAAI,KAAK,WAAY;AACrB,UAAI,CAAC,KAAK,KAAK,YAAY,EAAG;AAC9B,UAAI;AACF,aAAK,aAAa;AAClB,cAAM,WAAW,KAAK,oBAAoB,KAAK;AAC/C,aAAK,YAAY,QAAQ;AAAA,MAC3B,QAAQ;AAAA,MAER,UAAE;AACA,aAAK,aAAa;AAAA,MACpB;AAAA,IACF;AACA,MAAE,iBAAiB,SAAS,SAAS,IAAI;AACzC,SAAK,SAAS,KAAK,MAAM,EAAE,oBAAoB,SAAS,SAAS,IAAI,CAAC;AAAA,EACxE;AAAA,EAEQ,yBAAyB,GAAiB;AAChD,UAAM,UAAU,CAAC,UAAuC;AACtD,UAAI,KAAK,WAAY;AACrB,UAAI,CAAC,KAAK,KAAK,YAAY,EAAG;AAC9B,UAAI;AACF,aAAK,aAAa;AAClB,cAAM,WAAW,KAAK;AAAA,UACpB,MAAM;AAAA,UACN;AAAA,UACA;AAAA,QACF;AACA,aAAK,YAAY,QAAQ;AAAA,MAC3B,QAAQ;AAAA,MAER,UAAE;AACA,aAAK,aAAa;AAAA,MACpB;AAAA,IACF;AACA,MAAE,iBAAiB,sBAAsB,OAAO;AAChD,SAAK,SAAS,KAAK,MAAM,EAAE,oBAAoB,sBAAsB,OAAO,CAAC;AAAA,EAC/E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,iBAAiB,GAAiB;AACxC,UAAM,YAAY,EAAE,OAAO,KAAK,CAAC;AACjC,QAAI,CAAC,UAAW;AAChB,UAAM,UAAU,UAAU,SAAsD;AAC9E,YAAM,QAAQ,KAAK,CAAC;AACpB,YAAM,OAAO,KAAK,CAAC,KAAK,CAAC;AACzB,YAAM,MAAM,OAAO,UAAU,WAAW,QAAS,OAAmB,OAAO;AAC3E,YAAM,UAAU,KAAK,UAAU,OAAO,YAAY;AAClD,YAAM,QAAQ,KAAK,IAAI;AAQvB,UAAI,CAAC,cAAc,KAAK,KAAK,KAAK,YAAY,GAAG;AAC/C,aAAK,KAAK,YAAY,IAAI;AAAA,UACxB,WAAW;AAAA,UACX,UAAU;AAAA,UACV,SAAS,GAAG,MAAM,IAAI,GAAG;AAAA,UACzB,MAAM,EAAE,KAAK,OAAO;AAAA,QACtB,CAAC;AAAA,MACH;AAEA,UAAI;AACF,cAAM,WAAW,MAAM,UAAU,GAAG,IAAI;AACxC,YAAI,SAAS,UAAU,OAAO,KAAK,KAAK,YAAY,GAAG;AAIrD,cAAI,CAAC,cAAc,KAAK,KAAK,KAAK,YAAY,GAAG;AAC/C,iBAAK,YAAY;AAAA,cACf;AAAA,cACA;AAAA,cACA,QAAQ,SAAS;AAAA,cACjB,YAAY,SAAS;AAAA,YACvB,CAAC;AAAA,UACH;AAAA,QACF;AACA,eAAO;AAAA,MACT,SAAS,KAAK;AAQZ,YACE,KAAK,KAAK,YAAY,KACtB,CAAC,IAAI,SAAS,oBAAoB,KAClC,CAAC,qBAAqB,KAAK,KAAK,CAAC,GACjC;AACA,eAAK,YAAY;AAAA,YACf;AAAA,YACA;AAAA,YACA,QAAQ;AAAA,YACR,YAAY,eAAe,QAAQ,IAAI,UAAU;AAAA,UACnD,CAAC;AAAA,QACH;AACA,cAAM;AAAA,MACR;AAAA,IACF;AACA,MAAE,QAAQ;AACV,SAAK,SAAS,KAAK,MAAM;AAEvB,UAAI,EAAE,UAAU,QAAS,GAAE,QAAQ;AAAA,IACrC,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,eAAe,GAAiB;AACtC,UAAM,UAAW,EAA0D;AAC3E,UAAM,QAAQ,SAAS;AACvB,QAAI,CAAC,MAAO;AACZ,UAAM,WAAW,MAAM;AACvB,UAAM,WAAW,MAAM;AAEvB,UAAM,UAAU;AAChB,UAAM,OAAO,SAAgC,QAAgB,QAAgB,MAAuB;AAClG,MAAC,KAAkE,YAAY;AAC/E,MAAC,KAAkE,SAAS;AAE5E,aAAO,SAAS,MAAM,MAAM,CAAC,QAAQ,KAAK,GAAI,IAAoC,CAAC;AAAA,IACrF;AACA,UAAM,OAAO,SAAgC,MAAuD;AAClG,YAAM,MAAM;AACZ,YAAM,SAAS,MAAY;AACzB,YAAI;AACF,cAAI,IAAI,UAAU,OAAO,QAAQ,KAAK,YAAY,GAAG;AACnD,kBAAM,MAAM,IAAI,UAAU;AAC1B,gBAAI,CAAC,cAAc,KAAK,QAAQ,KAAK,YAAY,GAAG;AAClD,sBAAQ,YAAY;AAAA,gBAClB;AAAA,gBACA,SAAS,IAAI,aAAa,OAAO,YAAY;AAAA,gBAC7C,QAAQ,IAAI;AAAA,gBACZ,YAAY,IAAI;AAAA,cAClB,CAAC;AAAA,YACH;AAAA,UACF;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF;AACA,UAAI,iBAAiB,WAAW,MAAM;AACtC,aAAO,SAAS,MAAM,MAAM,CAAC,QAAQ,IAAI,CAAC;AAAA,IAC5C;AAEA,SAAK,SAAS,KAAK,MAAM;AACvB,YAAM,OAAO;AACb,YAAM,OAAO;AAAA,IACf,CAAC;AAAA,EACH;AAAA,EAEQ,qBAA2B;AACjC,UAAMC,WAAW,WAAqC;AACtD,QAAI,CAACA,SAAS;AACd,UAAM,OAAOA,SAAQ,MAAM,KAAKA,QAAO;AACvC,IAAAA,SAAQ,QAAQ,IAAI,SAAoB;AACtC,UAAI;AACF,YAAI,KAAK,KAAK,YAAY,GAAG;AAC3B,eAAK,eAAe,KAAK,IAAI,CAAC,MAAMC,eAAc,CAAC,CAAC,EAAE,KAAK,GAAG,GAAG,OAAO;AAAA,QAC1E;AAAA,MACF,QAAQ;AAAA,MAER;AACA,aAAO,KAAK,GAAG,IAAI;AAAA,IACrB;AACA,SAAK,SAAS,KAAK,MAAM;AACvB,MAAAD,SAAQ,QAAQ;AAAA,IAClB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAMQ,oBAAoB,OAAkC;AAC5D,UAAM,MAAM,MAAM;AAClB,UAAM,WAAW,MAAM,YAAY;AACnC,UAAM,SAAS,OAAO,MAAM,WAAW,YAAY,MAAM,SAAS,IAAI,MAAM,SAAS;AACrF,UAAM,QAAQ,OAAO,MAAM,UAAU,YAAY,MAAM,QAAQ,IAAI,MAAM,QAAQ;AAUjF,UAAM,wBACJ,OAAO,QACP,CAAC,YACD,UAAU,SACT,MAAM,YAAY,mBACjB,MAAM,YAAY,kBAClB,CAAC,MAAM;AACX,QAAI,uBAAuB;AACzB,YAAME,WACJ;AACF,aAAO;AAAA,QACL,WAAW,KAAK,IAAI;AAAA,QACpB,MAAM;AAAA,QACN,OAAO;AAAA,QACP,SAAAA;AAAA,QACA,WAAW;AAAA,QACX,QAAQ,CAAC;AAAA,QACT,UAAU;AAAA,QACV,UAAU;AAAA,QACV,QAAQ;AAAA,QACR,OAAO;AAAA;AAAA;AAAA;AAAA,QAIP,aAAa,iBAAiBA,UAAS,CAAC,CAAC;AAAA,QACzC,aAAa,KAAK,KAAK,YAAY,SAAS;AAAA,QAC5C,SAAS,KAAK,KAAK,WAAW;AAAA,QAC9B,MAAM,EAAE,GAAG,KAAK,KAAK,QAAQ,GAAG,cAAc,OAAO;AAAA,MACvD;AAAA,IACF;AAKA,UAAM,UAAU,mBAAmB,GAAG;AACtC,UAAM,WACJ,QAAQ,WACR,MAAM,WACN,iBACA,MAAM,GAAG,IAAI;AACf,UAAM,QAAQ,eAAe,QAAQ,IAAI,SAAS,OAAO;AACzD,UAAM,SAAS,WAAW,KAAK;AAC/B,UAAM,YAAY,QAAQ,aAAa;AAEvC,UAAM,UAAU,QAAQ,SACpB,EAAE,GAAG,KAAK,KAAK,WAAW,GAAG,gBAAgB,QAAQ,OAAO,IAC5D,KAAK,KAAK,WAAW;AAEzB,WAAO;AAAA,MACL,WAAW,KAAK,IAAI;AAAA,MACpB,MAAM;AAAA,MACN,OAAO;AAAA,MACP;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA;AAAA;AAAA;AAAA,MAIA,aAAa,iBAAiB,SAAS,QAAQ;AAAA,QAC7C;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAAA,MACD,aAAa,KAAK,KAAK,YAAY,SAAS;AAAA,MAC5C;AAAA,MACA,MAAM,KAAK,KAAK,QAAQ;AAAA,IAC1B;AAAA,EACF;AAAA,EAEQ,iBACN,KACA,MACA,OACe;AACf,UAAM,UAAU,mBAAmB,GAAG;AACtC,UAAM,WAAW,QAAQ,WAAW,iBAAiB,MAAM,GAAG,IAAI;AAClE,UAAM,QAAQ,eAAe,QAAQ,IAAI,SAAS,OAAO;AACzD,UAAM,SAAS,WAAW,KAAK;AAC/B,UAAM,YAAY,QAAQ,aAAa;AAEvC,UAAM,UAAU,QAAQ,SACpB,EAAE,GAAG,KAAK,KAAK,WAAW,GAAG,gBAAgB,QAAQ,OAAO,IAC5D,KAAK,KAAK,WAAW;AAEzB,WAAO;AAAA,MACL,WAAW,KAAK,IAAI;AAAA,MACpB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU;AAAA,MACV,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,aAAa,iBAAiB,SAAS,QAAQ,EAAE,UAAU,CAAC;AAAA,MAC5D,aAAa,KAAK,KAAK,YAAY,SAAS;AAAA,MAC5C;AAAA,MACA,MAAM,KAAK,KAAK,QAAQ;AAAA,IAC1B;AAAA,EACF;AAAA,EAEQ,YAAY,MAKX;AACP,QAAI;AACF,YAAM,UAAU,QAAQ,KAAK,MAAM,IAAI,KAAK,MAAM,IAAI,KAAK,GAAG;AAC9D,YAAM,WAA0B;AAAA,QAC9B,WAAW,KAAK,IAAI;AAAA,QACpB,MAAM;AAAA,QACN,OAAO;AAAA,QACP;AAAA,QACA,WAAW;AAAA,QACX,QAAQ,CAAC;AAAA,QACT,UAAU;AAAA,QACV,UAAU,KAAK;AAAA,QACf,QAAQ;AAAA,QACR,OAAO;AAAA,QACP,aAAa,iBAAiB,QAAQ,KAAK,MAAM,IAAI,KAAK,MAAM,IAAI,CAAC,GAAG;AAAA,UACtE,UAAU,KAAK;AAAA,UACf,WAAW;AAAA,QACb,CAAC;AAAA,QACD,aAAa,KAAK,KAAK,YAAY,SAAS;AAAA,QAC5C,SAAS,KAAK,KAAK,WAAW;AAAA,QAC9B,MAAM,KAAK,KAAK,QAAQ;AAAA,QACxB,MAAM;AAAA,MACR;AACA,WAAK,YAAY,QAAQ;AAAA,IAC3B,QAAQ;AAAA,IAER;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMQ,YAAY,KAA0B;AAC5C,QAAI,KAAK,gBAAgB,KAAK,KAAK,OAAO,cAAe;AACzD,QAAI,KAAK,aAAa,GAAG,EAAG;AAC5B,QAAI,CAAC,KAAK,cAAc,GAAG,EAAG;AAC9B,QAAI,CAAC,KAAK,aAAa,GAAG,EAAG;AAC7B,QAAI,CAAC,KAAK,gBAAgB,GAAG,EAAG;AAMhC,QAAI,WAAiC;AACrC,UAAM,OAAO,KAAK,KAAK,aAAa;AACpC,QAAI,MAAM;AACR,UAAI;AACF,mBAAW,KAAK,GAAG;AAAA,MACrB,QAAQ;AAGN,mBAAW;AAAA,MACb;AACA,UAAI,CAAC,SAAU;AAAA,IACjB;AAEA,SAAK,gBAAgB;AACrB,QAAI;AACF,WAAK,KAAK,OAAO,QAAQ;AAAA,IAC3B,QAAQ;AAAA,IAGR;AAAA,EACF;AAAA,EAEQ,aAAa,KAA6B;AAChD,eAAW,OAAO,KAAK,KAAK,OAAO,cAAc;AAC/C,UAAI,OAAO,QAAQ,YAAY,IAAI,QAAQ,SAAS,GAAG,EAAG,QAAO;AACjE,UAAI,eAAe,UAAU,IAAI,KAAK,IAAI,OAAO,EAAG,QAAO;AAAA,IAC7D;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,cAAc,KAA6B;AACjD,UAAM,WAAW,IAAI,OAAO,KAAK,CAAC,MAAM,EAAE,QAAQ,KAAK;AACvD,UAAM,MAAM,UAAU,YAAY,IAAI,YAAY;AAClD,QAAI,CAAC,IAAK,QAAO;AAEjB,eAAW,OAAO,KAAK,KAAK,OAAO,UAAU;AAC3C,UAAI,OAAO,QAAQ,YAAY,IAAI,SAAS,GAAG,EAAG,QAAO;AACzD,UAAI,eAAe,UAAU,IAAI,KAAK,GAAG,EAAG,QAAO;AAAA,IACrD;AACA,QAAI,KAAK,KAAK,OAAO,UAAU,SAAS,GAAG;AACzC,iBAAW,OAAO,KAAK,KAAK,OAAO,WAAW;AAC5C,YAAI,OAAO,QAAQ,YAAY,IAAI,SAAS,GAAG,EAAG,QAAO;AACzD,YAAI,eAAe,UAAU,IAAI,KAAK,GAAG,EAAG,QAAO;AAAA,MACrD;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,aAAa,KAA6B;AAChD,QAAI,KAAK,KAAK,OAAO,cAAc,EAAG,QAAO;AAC7C,QAAI,KAAK,KAAK,OAAO,cAAc,EAAG,QAAO;AAG7C,UAAM,WAAW,SAAS,IAAI,YAAY,MAAM,GAAG,CAAC,GAAG,EAAE;AACzD,WAAQ,WAAW,MAAO,KAAK,KAAK,OAAO;AAAA,EAC7C;AAAA,EAEQ,gBAAgB,KAA6B;AACnD,UAAM,WAAW;AACjB,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,MAAM,KAAK,KAAK,OAAO;AAC7B,UAAM,MAAM,KAAK,kBAAkB,IAAI,IAAI,WAAW,KAAK,CAAC;AAE5D,UAAM,QAAQ,IAAI,OAAO,CAAC,MAAM,MAAM,IAAI,QAAQ;AAClD,QAAI,MAAM,UAAU,KAAK;AACvB,WAAK,kBAAkB,IAAI,IAAI,aAAa,KAAK;AACjD,aAAO;AAAA,IACT;AACA,UAAM,KAAK,GAAG;AACd,SAAK,kBAAkB,IAAI,IAAI,aAAa,KAAK;AACjD,WAAO;AAAA,EACT;AACF;AAiCA,SAAS,mBAAmB,GAA4B;AACtD,MAAI,MAAM,KAAM,QAAO,EAAE,SAAS,kBAAkB,WAAW,MAAM,QAAQ,KAAK;AAClF,MAAI,MAAM,OAAW,QAAO,EAAE,SAAS,uBAAuB,WAAW,MAAM,QAAQ,KAAK;AAE5F,MAAI,OAAO,MAAM,UAAU;AACzB,WAAO,EAAE,SAAS,GAAG,WAAW,MAAM,QAAQ,KAAK;AAAA,EACrD;AACA,MAAI,OAAO,MAAM,YAAY,OAAO,MAAM,aAAa,OAAO,MAAM,UAAU;AAC5E,WAAO,EAAE,SAAS,OAAO,CAAC,GAAG,WAAW,OAAO,GAAG,QAAQ,KAAK;AAAA,EACjE;AACA,MAAI,OAAO,MAAM,UAAU;AACzB,WAAO,EAAE,SAAS,EAAE,SAAS,GAAG,WAAW,UAAU,QAAQ,KAAK;AAAA,EACpE;AACA,MAAI,OAAO,MAAM,YAAY;AAC3B,WAAO,EAAE,SAAS,qBAAqB,EAAE,QAAQ,WAAW,KAAK,WAAW,YAAY,QAAQ,KAAK;AAAA,EACvG;AAIA,MAAI,aAAa,OAAO;AACtB,UAAM,YACJ,EAAE,QAAQ,EAAE,aAAa,QAAQ;AACnC,UAAM,UACJ,OAAO,EAAE,YAAY,YAAY,EAAE,QAAQ,SAAS,IAChD,EAAE,UACF,aAAa,CAAC,KAAK;AAEzB,UAAM,SAAkC,CAAC;AAIzC,UAAM,aAAa,kBAAkB,CAAC;AACtC,QAAI,WAAW,SAAS,EAAG,QAAO,QAAQ;AAK1C,eAAW,OAAO,CAAC,QAAQ,UAAU,cAAc,SAAS,YAAY,QAAQ,UAAU,SAAS,GAAY;AAC7G,YAAM,MAAO,EAAyC,GAAG;AACzD,UAAI,QAAQ,UAAa,OAAO,QAAQ,YAAY;AAClD,eAAO,GAAG,IAAI,UAAU,GAAG;AAAA,MAC7B;AAAA,IACF;AAIA,eAAW,OAAO,OAAO,KAAK,CAAC,GAAG;AAChC,UAAI,QAAQ,aAAa,QAAQ,WAAW,QAAQ,UAAU,QAAQ,QAAS;AAC/E,UAAI,OAAO,OAAQ;AACnB,YAAM,MAAO,EAAyC,GAAG;AACzD,UAAI,OAAO,QAAQ,WAAY;AAC/B,aAAO,GAAG,IAAI,UAAU,GAAG;AAAA,IAC7B;AAEA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,QAAQ,OAAO,KAAK,MAAM,EAAE,SAAS,IAAI,SAAS;AAAA,IACpD;AAAA,EACF;AAIA,MAAI,OAAO,aAAa,eAAe,aAAa,UAAU;AAC5D,WAAO;AAAA,MACL,SAAS,QAAQ,EAAE,MAAM,IAAI,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,IAAI,EAAE,GAAG,KAAK,EAAE,GAAG,KAAK;AAAA,MAClF,WAAW;AAAA,MACX,QAAQ,EAAE,QAAQ,EAAE,QAAQ,YAAY,EAAE,YAAY,KAAK,EAAE,KAAK,MAAM,EAAE,KAAK;AAAA,IACjF;AAAA,EACF;AAMA,MAAI,OAAO,MAAM,UAAU;AACzB,UAAM,MAAM;AACZ,UAAM,WACH,IAAI,eAAe,OAAO,IAAI,gBAAgB,cAAe,IAAI,YAAkC,QACpG;AAEF,UAAM,aAAa,OAAO,IAAI,YAAY,YAAY,IAAI,UAAU,IAAI,UAAU;AAClF,UAAM,UAAU,OAAO,IAAI,SAAS,YAAY,IAAI,OAAO,IAAI,OAAO;AAEtE,QAAI,WAA0B;AAC9B,QAAI;AACF,YAAM,aAAa,KAAK,UAAU,GAAG;AAIrC,iBAAW,eAAe,OAAO,OAAO;AAAA,IAC1C,QAAQ;AACN,iBAAW;AAAA,IACb;AAEA,UAAM,iBAAiB,aAAa,GAAG;AACvC,UAAM,UACJ,cACA,aACC,kBAAkB,mBAAmB,oBAAoB,iBAAiB,UAC1E,WAAW,WAAW,QAAQ,sBAAsB;AAEvD,UAAM,YAAY,WAAW,YAAY;AAIzC,UAAM,SAAkC,CAAC;AACzC,QAAI,QAAQ;AACZ,eAAW,OAAO,OAAO,KAAK,GAAG,GAAG;AAClC,UAAI,SAAS,GAAI;AACjB,UAAI,QAAQ,aAAa,QAAQ,OAAQ;AACzC,YAAM,MAAM,IAAI,GAAG;AACnB,UAAI,OAAO,QAAQ,WAAY;AAC/B,aAAO,GAAG,IAAI,UAAU,GAAG;AAC3B;AAAA,IACF;AAEA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,QAAQ,OAAO,KAAK,MAAM,EAAE,SAAS,IAAI,SAAS;AAAA,IACpD;AAAA,EACF;AAGA,SAAO,EAAE,SAAS,aAAa,CAAC,KAAK,kCAAkC,WAAW,MAAM,QAAQ,KAAK;AACvG;AAEA,SAAS,kBAAkB,KAAsD;AAC/E,QAAM,MAAgD,CAAC;AACvD,MAAI,MAAgB,IAAoC;AACxD,MAAI,QAAQ;AACZ,SAAO,OAAO,QAAQ,QAAQ,GAAG;AAC/B,QAAI,eAAe,OAAO;AACxB,UAAI,KAAK,EAAE,MAAM,IAAI,QAAQ,SAAS,SAAS,IAAI,WAAW,GAAG,CAAC;AAClE,YAAO,IAAoC;AAAA,IAC7C,OAAO;AACL,UAAI,KAAK,EAAE,MAAM,aAAa,SAAS,aAAa,GAAG,EAAE,CAAC;AAC1D,YAAM;AAAA,IACR;AACA;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,aAAa,GAAoB;AACxC,MAAI;AACF,UAAM,IAAI,OAAO,UAAU,SAAS,KAAK,CAAC;AAC1C,QAAI,MAAM,kBAAmB,QAAO;AAEpC,UAAM,MAAO,GAAoC;AACjD,QAAI,OAAO,QAAQ,cAAc,QAAQ,OAAO,UAAU,UAAU;AAClE,YAAM,IAAI,IAAI,KAAK,CAAC;AACpB,UAAI,OAAO,MAAM,SAAU,QAAO;AAAA,IACpC;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,UAAU,GAAqB;AACtC,MAAI,KAAK,KAAM,QAAO;AACtB,QAAM,IAAI,OAAO;AACjB,MAAI,MAAM,YAAY,MAAM,YAAY,MAAM,UAAW,QAAO;AAChE,MAAI,MAAM,SAAU,QAAO,OAAO,CAAC;AACnC,MAAI;AAGF,UAAM,IAAI,KAAK,UAAU,CAAC;AAC1B,WAAO,MAAM,SAAY,aAAa,CAAC,IAAI,KAAK,MAAM,CAAC;AAAA,EACzD,QAAQ;AACN,WAAO,aAAa,CAAC;AAAA,EACvB;AACF;AAEA,SAASD,eAAc,GAAoB;AACzC,SAAO,mBAAmB,CAAC,EAAE;AAC/B;AAYO,SAAS,oBAAoB,SAAmD;AACrF,MAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;AACpD,MAAI;AACF,WAAO,IAAI,IAAI,OAAO,EAAE,SAAS,YAAY;AAAA,EAC/C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAcO,SAAS,cAAc,YAAoB,cAAkD;AAClG,MAAI,CAAC,gBAAgB,CAAC,WAAY,QAAO;AACzC,MAAI;AACF,WAAO,IAAI,IAAI,UAAU,EAAE,SAAS,YAAY,MAAM;AAAA,EACxD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAsBO,SAAS,qBAAqB,YAAoB,KAAc,GAAoB;AACzF,MAAI,eAAe,SAAS,IAAI,SAAS,aAAc,QAAO;AAC9D,QAAM,MAAO,EAAoD;AACjE,MAAI,OAAO,IAAI,WAAW,MAAO,QAAO;AACxC,QAAM,WAAW,EAAE,UAAU;AAC7B,QAAM,aAAa,EAAE,UAAU;AAC/B,MAAI,CAAC,WAAY,QAAO;AACxB,MAAI;AACF,WAAO,IAAI,IAAI,YAAY,YAAY,UAAU,EAAE,WAAW;AAAA,EAChE,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ACh5BO,IAAM,kBAAN,MAAsB;AAAA,EAAtB;AACL,SAAQ,QAA8B;AAetC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAAQ,YAAgD;AACxD,SAAQ,mBAA4C;AACpD,SAAQ,cAAsC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAW9C,KAAK,SAAiC;AASpC,QAAI,KAAK,OAAO;AACd,UAAI;AAAE,aAAK,MAAM,uBAAuB;AAAA,MAAG,QAAQ;AAAA,MAAe;AAClE,UAAI;AAAE,aAAK,MAAM,aAAa,UAAU;AAAA,MAAG,QAAQ;AAAA,MAAe;AAClE,UAAI;AAAE,aAAK,MAAM,WAAW,UAAU;AAAA,MAAG,QAAQ;AAAA,MAAe;AAChE,UAAI;AAAE,aAAK,MAAM,QAAQ,UAAU;AAAA,MAAG,QAAQ;AAAA,MAAe;AAc7D,UAAI;AAAE,aAAK,KAAK,MAAM,OAAO,MAAM,EAAE,WAAW,KAAK,CAAC;AAAA,MAAG,QAAQ;AAAA,MAAe;AAAA,IAClF;AACA,QAAI,CAAC,QAAQ,aAAa,CAAC,QAAQ,UAAU,WAAW,SAAS,GAAG;AAClE,YAAM,IAAI,eAAe;AAAA,QACvB,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AACA,QAAI,CAAC,QAAQ,OAAO;AAClB,YAAM,IAAI,eAAe;AAAA,QACvB,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AACA,QAAI,QAAQ,gBAAgB,gBAAgB,QAAQ,gBAAgB,WAAW;AAC7E,YAAM,IAAI,eAAe;AAAA,QACvB,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AAIA,UAAM,SAAS,gBAAgB,QAAQ,SAAS;AAChD,QAAI,UAAU,WAAW,QAAQ,aAAa;AAC5C,YAAM,IAAI,eAAe;AAAA,QACvB,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS,gCAAgC,QAAQ,WAAW,gCAAgC,MAAM;AAAA,MACpG,CAAC;AAAA,IACH;AAaA,UAAM,eAAe,gBAAgB;AAErC,UAAM,UAAU,QAAQ,WAAW,qBAAqB;AACxD,UAAM,kBAAkB,QAAQ,mBAAmB;AACnD,UAAM,YAAY,iBAAiB,QAAQ,SAAS;AACpD,UAAM,OAAiC;AAAA,MACrC,OAAO,QAAQ;AAAA,MACf,WAAW,QAAQ;AAAA,MACnB,aAAa,QAAQ;AAAA,MACrB,SAAS,QAAQ,WAAW;AAAA,MAC5B;AAAA,MACA,eAAe,QAAQ,iBAAiB;AAAA,MACxC,eAAe,QAAQ,iBAAiB;AAAA,MACxC,qBAAqB,QAAQ,uBAAuB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAUpD,sBAAsB,QAAQ,wBAAwB;AAAA,MACtD,YAAY,QAAQ,cAAc;AAAA,MAClC;AAAA,MACA,YAAY,QAAQ,cAAc;AAAA,IACpC;AAEA,UAAM,QAAQ,IAAI,mBAAmB;AACrC,UAAM,UAAU,QAAQ,UAAU;AAElC,UAAM,OAAO,IAAI,WAAW;AAAA,MAC1B,WAAW,KAAK;AAAA,MAChB,SAAS,KAAK;AAAA,MACd,YAAY,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA,MAKjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMA,eAAe,CAAC,QAAQ;AACtB,YAAI,KAAK,aAAa,KAAK,oBAAoB,KAAK,aAAa;AAC/D,0BAAgB,KAAK,WAAW,KAAK,kBAAkB,KAAK,aAAa;AAAA,YACvE,WAAW,IAAI;AAAA,YACf,WAAW,IAAI;AAAA,YACf,WAAW,IAAI,aAAa;AAAA,YAC5B,YAAY,OAAO,IAAI,WAAW,WAAW,IAAI,SAAS;AAAA,UAC5D,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF,CAAC;AAED,QAAI,cAAc;AAGhB,cAAQ;AAAA,QACN;AAAA,MAEF;AAAA,IACF;AAYA,UAAM,mBAAmB,kBAAkB,UAAU,IAAI,cAAc;AACvE,UAAM,sBACJ,mBACA,CAAC,QAAQ;AAAA,IACT,OAAQ,WAAsC,aAAa;AAC7D,UAAM,cAAc,sBAAsB,IAAI,cAAc,IAAI;AAChE,UAAM,WAAW,IAAI,cAAc,kBAAkB,KAAK,eAAe,WAAW;AAKpF,UAAM,eAAe,IAAI;AAAA,MACvB;AAAA,MACA,KAAK,gBAAgB;AAAA,IACvB;AAOA,UAAM,mBAAmB,kBACrB,IAAI,qBAAqB,EAAE,SAAS,kBAAkB,QAAQ,KAAK,cAAc,CAAC,IAClF;AACJ,QAAI,kBAAkB;AACpB,YAAM;AAAA,QACJ;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,UAAM,SAAS,IAAI,WAAW;AAAA,MAC5B;AAAA,MACA,WAAW,KAAK;AAAA,MAChB,YAAY,KAAK;AAAA,MACjB,UAAU,OAAO;AAAA,QACf,OAAO,KAAK;AAAA,QACZ,aAAa,KAAK;AAAA,QAClB,KAAK,EAAE,MAAM,UAAU,SAAS,KAAK,WAAW;AAAA,MAClD;AAAA,MACA,iBAAiB,oBAAoB;AAAA,MACrC,qBAAqB,MAAM;AACzB,cAAM;AAAA,UACJ;AAAA,UACA;AAAA,UACA,EAAE,OAAO,KAAK,OAAO,aAAa,KAAK,YAAY;AAAA,QACrD;AAAA,MACF;AAAA,MACA,kBAAkB,CAAC,SAAS;AAC1B,cAAM;AAAA,UACJ;AAAA,UACA,uBAAuB,KAAK,SAAS,kBAAkB,KAAK,OAAO,eAAe,KAAK,mBAAmB;AAAA,UAC1G,EAAE,GAAG,KAAK;AAAA,QACZ;AAAA,MACF;AAAA,MACA,oBAAoB,CAAC,SAAS;AAO5B,cAAM,WAAW,2CAA2C,KAAK,MAAM,MAAM,KAAK,SAAS,KAAK,KAAK,YAAY;AAEjH,gBAAQ,MAAM,QAAQ;AACtB,cAAM;AAAA,UACJ;AAAA,UACA;AAAA,UACA,EAAE,GAAG,KAAK;AAAA,QACZ;AAAA,MACF;AAAA,MACA,UAAU,CAAC,SAAS;AAKlB,cAAM;AAAA,UACJ;AAAA,UACA,uJAAkJ,KAAK,aAAa,UAAU,KAAK,UAAU,KAAK,EAAE;AAAA,UACpM,EAAE,GAAG,KAAK;AAAA,QACZ;AAAA,MACF;AAAA,IACF,CAAC;AAGD,UAAM,aAAyB,UAAU,aACrC,kBAAkB,EAAE,YAAY,KAAK,cAAc,OAAU,CAAC,IAC9D,KAAK,aACH,EAAE,YAAY,KAAK,WAAW,IAC9B,CAAC;AAMP,UAAM,aAAa,IAAI;AAAA,MACrB,kBAAkB,mBAAmB,IAAI,cAAc;AAAA,MACvD,KAAK;AAAA,IACP;AAKA,6BAAyB;AAKzB,UAAM,UAAU,IAAI,eAAe,EAAE,YAAY,QAAQ,eAAe,KAAK,CAAC;AAC9E,QAAI,QAAQ,aAAa;AACvB,YAAM;AAAA,QACJ;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAOA,UAAM,cAAc,IAAI,iBAAiB,EAAE;AAE3C,SAAK,QAAQ;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,aAAa;AAAA,MACb,WAAW;AAAA,MACX,QAAQ;AAAA,MACR;AAAA,MACA,cAAc,CAAC;AAAA,MACf,WAAW,CAAC;AAAA,MACZ,iBAAiB;AAAA,MACjB;AAAA,MACA;AAAA,MACA,UAAU,QAAQ,aAAa;AAAA,MAC/B;AAAA,MACA,SAAS;AAAA,MACT;AAAA,MACA,iBAAiB;AAAA,MACjB,sBAAsB;AAAA,MACtB,gBAAgB;AAAA,MAChB,gBAAgB;AAAA,IAClB;AAEA,UAAM,KAAK,kBAAkB,0BAA0B,KAAK,KAAK,OAAO,KAAK,WAAW,UAAU;AAAA,MAChG,OAAO,KAAK;AAAA,MACZ,aAAa,KAAK;AAAA,MAClB,YAAY,KAAK;AAAA,IACnB,CAAC;AAID,QAAI,UAAU,YAAY,UAAU,WAAW;AAC7C,YAAM,UAAU,IAAI;AAAA,QAClB;AAAA,QACA,CAAC,MAAM,eAAe,KAAK,MAAM,MAAM,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA,QAKjD,EAAE,SAAS,kBAAkB,YAAY,KAAK,gBAAgB,UAAU;AAAA,MAC1E;AACA,WAAK,MAAM,cAAc;AACzB,cAAQ,QAAQ;AAAA,IAClB;AAIA,QAAI,UAAU,WAAW;AACvB,YAAM,SAAS,IAAI;AAAA,QACjB,EAAE,SAAS,KAAK;AAAA,QAChB,CAAC,MAAM,eAAe,KAAK,MAAM,MAAM,UAAU;AAAA,MACnD;AACA,WAAK,MAAM,YAAY;AACvB,aAAO,QAAQ;AAAA,IACjB;AAQA,QAAI,UAAU,QAAQ;AACpB,YAAM,UAAU,IAAI,aAAa;AAAA,QAC/B,QAAQ,EAAE,GAAG,uBAAuB,SAAS,KAAK;AAAA,QAClD;AAAA,QACA,QAAQ,CAAC,QAAQ,KAAK,YAAY,GAAG;AAAA,QACrC,YAAY,OAAO,EAAE,GAAG,KAAK,MAAO,aAAa;AAAA,QACjD,SAAS,OAAO,EAAE,GAAG,KAAK,MAAO,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAO3C,YAAY,MAAM,KAAK,MAAO;AAAA,QAC9B,aAAa,MAAM,KAAK,MAAO,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAMvC,cAAc,oBAAoB,KAAK,OAAO;AAAA,MAChD,CAAC;AACD,WAAK,MAAM,SAAS;AACpB,cAAQ,QAAQ;AAAA,IAClB;AASA,SAAK,MAAM,uBAAuB,mBAAmB,MAAM;AAIzD,WAAK,KAAK,MAAM,EAAE,WAAW,KAAK,CAAC,EAAE,MAAM,MAAM,MAAS;AAAA,IAC5D,CAAC;AAED,QAAI,KAAK,iBAAiB,CAAC,cAAc;AAGvC,WAAK,KAAK,UAAU,EAAE,MAAM,MAAM,MAAS;AAAA,IAC7C;AA8BA,QAAI,QAAQ,8BAA8B,MAAM;AAC9C,YAAM,eAAe,qBAAqB;AAI1C,WAAK,cAAc,qBAAqB;AAAA,QACtC,oBAAoB,QAAQ,sBAAsB;AAAA,QAClD,2BAA2B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAM3B,YAAY,OAAO,YAAY,eAAe,QAAQ,OAAO,QAAQ,IAAI,KACrE,OACA;AAAA,MACN,CAAC;AACD,WAAK,mBAAmB,IAAI,iBAAiB,KAAK,WAAW;AAC7D,YAAM,gBAAgB,2BAA2B,KAAK,oBAAoB;AAC1E,WAAK,YAAY,OAAO,OAAO,CAAC,GAAG,kBAAkB,aAAa,CAAC;AAYnE,UAAI,cAAc;AAChB,cAAM,oBACJ,QAAQ,yBAAyB;AACnC,YAAI,qBAAqB,KAAK,kBAAkB;AAC9C,eAAK;AAAA,YACH,KAAK;AAAA,YACL,KAAK;AAAA,YACL,KAAK;AAAA,UACP,EAAE,MAAM,MAAM,MAAS;AAAA,QACzB;AAAA,MACF,OAAO;AACL,aAAK,KAAK,6BAA6B,SAAS,YAAY,EAAE;AAAA,UAC5D,MAAM;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAIA;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,MAAc,6BACZ,SACA,cACe;AACf,QAAI,CAAC,KAAK,SAAS,CAAC,KAAK,aAAa,CAAC,KAAK,YAAa;AAUzD,QAAI,SAA+B;AAAA,MACjC,oBAAoB;AAAA,MACpB,uBAAuB;AAAA,IACzB;AACA,QAAI;AACF,YAAM,OAAO,MAAM,KAAK,MAAM,KAAK;AAAA,QACjC;AAAA,QACA;AAAA,MACF;AACA,UAAI,QAAQ,KAAK,gBAAgB;AAC/B,cAAM,IAAI,KAAK;AACf,iBAAS;AAAA,UACP,oBACE,OAAO,EAAE,uBAAuB,YAC5B,EAAE,qBACF;AAAA,UACN,uBACE,OAAO,EAAE,0BAA0B,YAC/B,EAAE,wBACF;AAAA,QACR;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AAGA,UAAM,aACJ,QAAQ,uBACP,OAAO,sBAAsB;AAChC,UAAM,eACJ,QAAQ,0BACP,OAAO,yBAAyB;AASnC,QAAI,eAAe,KAAK,YAAY,oBAAoB;AACtD,WAAK,cAAc;AAAA,QACjB,GAAG,KAAK;AAAA,QACR,oBAAoB;AAAA,MACtB;AACA,WAAK,mBAAmB,IAAI,iBAAiB,KAAK,WAAW;AAAA,IAC/D;AAEA,QAAI,gBAAgB,KAAK,kBAAkB;AACzC,YAAM;AAAA,QACJ,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,MACP;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,SAAiC;AACrC,QAAI,OAAO,YAAY,aAAa;AAElC,cAAQ;AAAA,QACN;AAAA,MACF;AAAA,IACF;AACA,SAAK,KAAK,OAAO;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,MAAM,SAAS,QAAgB,SAAiD;AAC9E,UAAM,IAAI,KAAK,eAAe;AAC9B,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,eAAe;AAAA,QACvB,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AAKA,mBAAe,EAAE,OAAO,OAAO,CAAC;AAChC,QAAI,CAAC,EAAE,QAAQ,WAAW;AAIxB,QAAE,MAAM;AAAA,QACN;AAAA,QACA;AAAA,MACF;AACA,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,qBAAqB,EAAE,SAAS,uBAAuB;AAAA,QACvD,QAAQ,CAAC;AAAA,QACT,cAAc;AAAA,QACd,KAAK,EAAE,QAAQ;AAAA,MACjB;AAAA,IACF;AAKA,UAAM,mBACJ,SAAS,WAAW,SAChB,wBAAwB,QAAQ,MAAM,IACtC;AACN,UAAM,SAAS,oBAAoB,OAAO,KAAK,iBAAiB,UAAU,EAAE,SAAS,IACjF,iBAAiB,aACjB;AACJ,QAAI,EAAE,MAAM,WAAW,oBAAoB,iBAAiB,SAAS,SAAS,GAAG;AAC/E,iBAAW,KAAK,iBAAiB,UAAU;AACzC,UAAE,MAAM;AAAA,UACN;AAAA,UACA,yBAAyB,KAAK,UAAU,EAAE,GAAG,CAAC,QAAQ,EAAE,KAAK,QAAQ,MAAM,GAAG,CAAC;AAAA,UAC/E,EAAE,KAAK,EAAE,KAAK,MAAM,EAAE,KAAK;AAAA,QAC7B;AAAA,MACF;AAAA,IACF;AACA,UAAM,OAAgC;AAAA,MACpC;AAAA,MACA,aAAa,EAAE,SAAS;AAAA,IAC1B;AACA,QAAI,SAAS,MAAO,MAAK,QAAQ,QAAQ;AACzC,QAAI,OAAQ,MAAK,SAAS;AAkB1B,UAAM,cAAc,EAAE;AAEtB,MAAE,aAAa,WAAW,MAAM;AAShC,QAAI,KAAK,aAAa,KAAK,oBAAoB,KAAK,aAAa;AAC/D,oBAAc,KAAK,WAAW,KAAK,kBAAkB,KAAK,aAAa;AAAA,QACrE;AAAA,QACA,YAAY;AAAA,QACZ,OAAO,EAAE;AAAA,MACX,CAAC;AAAA,IACH;AAEA,UAAM,SAAS,MAAM,EAAE,KAAK,QAAqB,QAAQ,mBAAmB;AAAA,MAC1E;AAAA,IACF,CAAC;AACD,MAAE,SAAS,uBAAuB,OAAO,mBAAmB;AAC5D,MAAE,kBAAkB;AACpB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,SAAS,YAA8D;AACrE,UAAM,IAAI,KAAK,eAAe;AAC9B,UAAM,aAAa,wBAAwB,UAAU;AACrD,WAAO,EAAE,WAAW,SAAS,WAAW,UAAU;AAAA,EACpD;AAAA;AAAA,EAGA,WAAW,KAAmB;AAC5B,UAAM,IAAI,KAAK,eAAe;AAC9B,MAAE,WAAW,WAAW,GAAG;AAAA,EAC7B;AAAA;AAAA,EAGA,qBAA8C;AAC5C,QAAI,CAAC,KAAK,MAAO,QAAO,CAAC;AACzB,WAAO,KAAK,MAAM,WAAW,mBAAmB;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM,MAAc,IAAmB,QAA4B;AACjE,UAAM,IAAI,KAAK,eAAe;AAC9B,QAAI,CAAC,MAAM;AACT,YAAM,IAAI,eAAe;AAAA,QACvB,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AACA,UAAM,kBAAkB,SAAS,wBAAwB,MAAM,EAAE,aAAa;AAC9E,MAAE,WAAW,SAAS,MAAM,IAAI,eAAe;AAAA,EACjD;AAAA;AAAA,EAGA,YAA8E;AAC5E,QAAI,CAAC,KAAK,MAAO,QAAO,CAAC;AACzB,WAAO,KAAK,MAAM,WAAW,UAAU;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,QAAQ,OAA4C;AAClD,UAAM,IAAI,KAAK,eAAe;AAC9B,UAAM,OAAO,EAAE,QAAQ,IAAI,KAAK;AAChC,MAAE,MAAM,KAAK,uBAAuB,0BAA0B,EAAE,GAAG,KAAK,CAAC;AACzE,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,gBAA8B;AAC5B,QAAI,CAAC,KAAK,OAAO;AACf,aAAO,EAAE,WAAW,MAAM,WAAW,MAAM,QAAQ,KAAK;AAAA,IAC1D;AACA,WAAO,KAAK,MAAM,QAAQ,IAAI;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,aACE,OACA,SACM;AACN,QAAI,CAAC,KAAK,OAAO,OAAQ;AACzB,SAAK,MAAM,OAAO,aAAa,OAAO,OAAO;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,eAAe,SAAiB,QAAoB,QAAc;AAChE,QAAI,CAAC,KAAK,OAAO,OAAQ;AACzB,SAAK,MAAM,OAAO,eAAe,SAAS,KAAK;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAO,KAAa,OAAqB;AACvC,QAAI,CAAC,KAAK,MAAO;AACjB,SAAK,MAAM,UAAU,GAAG,IAAI;AAAA,EAC9B;AAAA;AAAA,EAGA,QAAQ,MAAoC;AAC1C,QAAI,CAAC,KAAK,MAAO;AACjB,WAAO,OAAO,KAAK,MAAM,WAAW,IAAI;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,WAAW,MAAc,MAAqC;AAC5D,QAAI,CAAC,KAAK,MAAO;AACjB,SAAK,MAAM,aAAa,IAAI,IAAI;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,cAAc,OAAyB;AACrC,QAAI,CAAC,KAAK,MAAO;AACjB,SAAK,MAAM,YAAY,IAAI,KAAK;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,mBACE,MACM;AACN,QAAI,CAAC,KAAK,MAAO;AACjB,SAAK,MAAM,kBAAkB;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,YAAY,KAA0B;AAM5C,UAAM,aAA8B;AAAA;AAAA,MAElC,aAAa,IAAI;AAAA,MACjB,OAAO,IAAI;AAAA;AAAA,MAEX,WAAW,IAAI;AAAA,MACf,SAAS,IAAI;AAAA;AAAA,MAEb,OAAO,IAAI,YAAY;AAAA,MACvB,QAAQ,IAAI;AAAA,MACZ,UAAU,IAAI,YAAY;AAAA,MAC1B,QAAQ,IAAI,UAAU;AAAA,MACtB,OAAO,IAAI,SAAS;AAAA;AAAA,MAEpB,MAAM,IAAI;AAAA,MACV,SAAS,IAAI;AAAA,MACb,aAAa,IAAI;AAAA;AAAA,MAEjB,MAAM,IAAI;AAAA,IACZ;AAEA,eAAW,KAAK,OAAO,KAAK,UAAU,GAAG;AACvC,UAAI,WAAW,CAAC,MAAM,OAAW,QAAO,WAAW,CAAC;AAAA,IACtD;AAMA,SAAK,MAAM,IAAI,MAAM,UAAU;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,MAAM,SAAwB;AAC5B,UAAM,IAAI,KAAK,eAAe;AAC9B,UAAM,gBAAgB,KAAK,oBAAoB;AAC/C,QAAI;AACF,YAAM,EAAE,KAAK,QAA8B,QAAQ,oBAAoB;AAAA,QACrE,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA,UAKJ,GAAG;AAAA,QACL;AAAA,MACF,CAAC;AAAA,IACH,SAAS,KAAK;AAKZ,QAAE,MAAM;AAAA,QACN;AAAA,QACA,gCAAgC,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,MAClF;AAAA,IACF;AACA,SAAK,MAAM;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,kBAAgD;AACpD,UAAM,IAAI,KAAK,eAAe;AAC9B,UAAM,QAAQ,KAAK,oBAAoB;AACvC,QAAI;AACJ,QAAI;AACF,eAAS,MAAM,EAAE,KAAK;AAAA,QACpB;AAAA,QACA;AAAA,QACA,EAAE,MAAM;AAAA,MACV;AAAA,IACF,SAAS,KAAK;AAMZ,QAAE,aAAa,kBAAkB;AACjC,YAAM;AAAA,IACR;AACA,QAAI,OAAO,qBAAqB;AAC9B,QAAE,SAAS,uBAAuB,OAAO,mBAAmB;AAAA,IAC9D;AACA,MAAE,aAAa,YAAY,OAAO,IAAI;AACtC,WAAO,OAAO;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,WAAW,KAAsB;AAC/B,UAAM,IAAI,KAAK,eAAe;AAC9B,WAAO,EAAE,aAAa,WAAW,GAAG;AAAA,EACtC;AAAA;AAAA,EAGA,mBAAwC;AACtC,UAAM,IAAI,KAAK,eAAe;AAC9B,WAAO,EAAE,aAAa,KAAK;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA0CA,qBAAqB,UAA4C;AAC/D,UAAM,IAAI,KAAK,eAAe;AAC9B,WAAO,EAAE,aAAa,UAAU,QAAQ;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqBA,sBAAsB,OAAmC;AACvD,UAAM,UAAkC;AAAA,MACtC,aAAa,MAAM;AAAA,MACnB,aAAa;AAAA,MACb,cAAc;AAAA,MACd,gBAAgB,MAAM;AAAA,MACtB,aAAa,MAAM;AAAA,MACnB,QAAQ,MAAM;AAAA,IAChB;AACA,QAAI,MAAM,SAAS;AACjB,cAAQ,YAAY,MAAM,QAAQ;AAClC,cAAQ,YAAY,MAAM,QAAQ;AAAA,IACpC;AACA,QAAI,MAAM,aAAa;AACrB,cAAQ,eAAe,MAAM;AAAA,IAC/B;AACA,4BAAwB,OAAO;AAAA,EACjC;AAAA,EAEA,MAAM,MAAc,YAAoC;AACtD,UAAM,IAAI,KAAK,eAAe;AAC9B,QAAI,CAAC,MAAM;AACT,YAAM,IAAI,eAAe;AAAA,QACvB,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AASA,UAAM,UAAU,KAAK,WAAW,QAAQ;AACxC,UAAM,aAAa,KAAK,WAAW,YAAY;AAC/C,UAAM,gBAAiB,WAAW,aAAc,EAAE,QAAQ,SAAS,EAAE,QAAQ;AAC7E,QAAI,CAAC,eAAe;AAClB,UAAI,EAAE,MAAM,SAAS;AACnB,UAAE,MAAM;AAAA,UACN;AAAA,UACA,kBAAkB,IAAI,+BAA0B,aAAa,WAAW,WAAW;AAAA,QACrF;AAAA,MACF;AACA;AAAA,IACF;AAMA,QAAI,EAAE,MAAM,WAAW,YAAY;AACjC,YAAM,UAAU,0BAA0B,UAAU;AACpD,UAAI,QAAQ,SAAS,GAAG;AACtB,UAAE,MAAM;AAAA,UACN;AAAA,UACA,UAAU,IAAI,+CAA+C,QAAQ,KAAK,IAAI,CAAC;AAAA,UAC/E,EAAE,WAAW,MAAM,QAAQ;AAAA,QAC7B;AAAA,MACF;AAAA,IACF;AAIA,QAAI,EAAE,MAAM,WAAW,CAAC,EAAE,mBAAmB,CAAC,EAAE,SAAS,qBAAqB;AAC5E,QAAE,MAAM;AAAA,QACN;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAcA,UAAM,aAAa,wBAAwB,UAAU;AACrD,QAAI,EAAE,MAAM,WAAW,WAAW,SAAS,SAAS,GAAG;AACrD,iBAAW,KAAK,WAAW,UAAU;AACnC,UAAE,MAAM;AAAA,UACN;AAAA,UACA,UAAU,IAAI,cAAc,KAAK,UAAU,EAAE,GAAG,CAAC,QAAQ,EAAE,KAAK,QAAQ,MAAM,GAAG,CAAC;AAAA,UAClF,EAAE,WAAW,MAAM,KAAK,EAAE,KAAK,MAAM,EAAE,KAAK;AAAA,QAC9C;AAAA,MACF;AAAA,IACF;AAQA,UAAM,MAAM,EAAE,aAAa,QAAQ,KAAK;AACxC,UAAM,sBAAsB,KAAK,IAAI;AA2BrC,MAAE,aAAa,aAAa;AAM5B,UAAM,cAAsC,CAAC;AAC7C,UAAM,KAAK,EAAE;AACb,QAAI,GAAG,GAAI,aAAY,KAAK,GAAG;AAC/B,QAAI,GAAG,UAAW,aAAY,YAAY,GAAG;AAC7C,UAAM,SAAS,EAAE,QAAQ;AACzB,QAAI,OAAQ,aAAY,aAAa;AACrC,gBAAY,UAAU;AACtB,gBAAY,aAAa,EAAE,QAAQ;AACnC,QAAI,GAAG,OAAQ,aAAY,SAAS,GAAG;AACvC,QAAI,GAAG,SAAU,aAAY,WAAW,GAAG;AAC3C,QAAI,GAAG,QAAS,aAAY,UAAU,GAAG;AACzC,QAAI,GAAG,eAAgB,aAAY,iBAAiB,GAAG;AAKvD,UAAM,WAA4B,CAAC;AACnC,QAAI,GAAG,gBAAgB,OAAW,UAAS,cAAc,GAAG;AAC5D,QAAI,GAAG,iBAAiB,OAAW,UAAS,eAAe,GAAG;AAC9D,QAAI,GAAG,kBAAkB,OAAW,UAAS,gBAAgB,GAAG;AAChE,QAAI,GAAG,mBAAmB,OAAW,UAAS,iBAAiB,GAAG;AAClE,QAAI,GAAG,qBAAqB,OAAW,UAAS,mBAAmB,GAAG;AACtE,UAAM,YAAY,EAAE,aAAa;AACjC,QAAI,UAAW,UAAS,YAAY;AACpC,UAAM,aAAa,EAAE,aAAa;AAClC,QAAI,WAAY,UAAS,aAAa;AACtC,UAAM,cAAc,EAAE,aAAa;AACnC,QAAI,aAAa;AAKf,UAAI,YAAY,WAAY,UAAS,aAAa,YAAY;AAC9D,UAAI,YAAY,WAAY,UAAS,aAAa,YAAY;AAC9D,UAAI,YAAY,aAAc,UAAS,eAAe,YAAY;AAClE,UAAI,YAAY,YAAa,UAAS,cAAc,YAAY;AAChE,UAAI,YAAY,SAAU,UAAS,WAAW,YAAY;AAC1D,UAAI,YAAY,YAAY,EAAE,QAAQ,UAAW,UAAS,WAAW,YAAY;AACjF,UAAI,EAAE,QAAQ,WAAW;AACvB,YAAI,YAAY,MAAO,UAAS,QAAQ,YAAY;AACpD,YAAI,YAAY,OAAQ,UAAS,SAAS,YAAY;AACtD,YAAI,YAAY,QAAS,UAAS,UAAU,YAAY;AACxD,YAAI,YAAY,OAAQ,UAAS,SAAS,YAAY;AACtD,YAAI,YAAY,UAAW,UAAS,YAAY,YAAY;AAC5D,YAAI,YAAY,OAAQ,UAAS,SAAS,YAAY;AAAA,MACxD;AAAA,IACF;AAIA,UAAM,SAAS,EAAE,WAAW,mBAAmB;AAC/C,eAAW,KAAK,OAAO,KAAK,MAAM,GAAG;AACnC,UAAI,EAAE,KAAK,UAAW,UAAS,CAAC,IAAI,OAAO,CAAC;AAAA,IAC9C;AAIA,UAAM,WAAW,EAAE,WAAW,YAAY;AAC1C,QAAI,OAAO,KAAK,QAAQ,EAAE,SAAS,GAAG;AACpC,eAAS,UAAU;AAAA,IACrB;AACA,WAAO,OAAO,UAAU,WAAW,UAAU;AAK7C,QAAI,iBAAiB,GAAG;AACtB,eAAS,yBAAyB,IAAI;AAAA,IACxC;AAUA,UAAM,kBAAkB,EAAE,WAAW,uBAAuB,QAAQ,IAAI;AAExE,UAAM,QAAqB;AAAA,MACzB,SAAS,KAAK,YAAY;AAAA,MAC1B;AAAA,MACA,WAAW;AAAA,MACX;AAAA,MACA,SAAS;AAAA,MACT,YAAY;AAAA,IACd;AACA,WAAO,OAAO,OAAO,KAAK,qBAAqB,CAAC;AAChD,MAAE,OAAO,QAAQ,KAAK;AA6BtB,QAAI,KAAK,aAAa,KAAK,oBAAoB,KAAK,aAAa;AAO/D,iBAAW,KAAK,WAAW,KAAK,kBAAkB,KAAK,aAAa;AAAA,QAClE,kBAAkB,WAAW;AAAA,QAC7B,iBAAiB;AAAA,QACjB,kBAAkB,EAAE;AAAA,QACpB,kBAAkB;AAAA,MACpB,CAAC;AAAA,IACH;AAQA,QAAI,CAAC,WAAW,CAAC,YAAY;AAC3B,YAAM,WAAW,KAAK,WAAW,OAAO,IACpC,eACA,KAAK,WAAW,UAAU,KAAK,SAAS,oBACtC,aACA;AACN,QAAE,YAAY,IAAI;AAAA,QAChB,WAAW,MAAM;AAAA,QACjB;AAAA,QACA,SAAS;AAAA;AAAA;AAAA;AAAA,QAIT,MAAM,aAAa,EAAE,GAAG,WAAW,IAAI;AAAA,MACzC,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,MAAM,UAAmC,CAAC,GAAkB;AAChE,UAAM,IAAI,KAAK,eAAe;AAC9B,UAAM,EAAE,OAAO,MAAM,OAAO;AAAA,EAC9B;AAAA;AAAA,EAGA,MAAM,cAA6B;AACjC,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,cAAc,OAKQ;AAC1B,UAAM,IAAI,KAAK,eAAe;AAC9B,QAAI,CAAC,MAAM,uBAAuB;AAChC,YAAM,IAAI,eAAe;AAAA,QACvB,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AASA,UAAM,OAAO,MAAM,QAAQ;AAC3B,UAAM,OAAO,EAAE,GAAG,OAAO,KAAK;AAI9B,UAAM,iBAAiB,gCAAgC,IAAI;AAO3D,QAAI,KAAK,aAAa,KAAK,oBAAoB,KAAK,aAAa;AAC/D,YAAM,WACJ,SAAS,UACJ,KAA4C,yBAAyB,KACrE,KAAoC,iBAAiB;AAC5D;AAAA,QACE,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL;AAAA,UACE;AAAA,UACA,kBAAkB;AAAA,UAClB,YAAY;AAAA,QACd;AAAA,MACF;AAAA,IACF;AAEA,UAAM,SAAS,MAAM,EAAE,KAAK,QAAwB,QAAQ,mBAAmB;AAAA,MAC7E;AAAA,MACA;AAAA,IACF,CAAC;AACD,MAAE,SAAS,uBAAuB,OAAO,mBAAmB;AAC5D,MAAE,aAAa,YAAY,OAAO,YAAY;AAS9C,QAAI;AACF,YAAM,kBAAkB,OAAO,aAAa,CAAC,GAAG,OAAO;AACvD,YAAM,uBAAuB,OAAO,aAAa,CAAC,GAAG,OAAO;AAC5D,YAAM,QAAiC,EAAE,KAAK;AAC9C,UAAI,gBAAiB,OAAM,YAAY;AACvC,UAAI,qBAAsB,OAAM,iBAAiB;AACjD,UAAI,OAAO,kBAAmB,OAAM,oBAAoB;AACxD,WAAK,MAAM,sBAAsB,KAAK;AAAA,IACxC,QAAQ;AAAA,IAER;AACA,MAAE,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA,EAAE,MAAM,MAAM,QAAQ,QAAQ;AAAA,IAChC;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,MAAM,cAAc,OAIQ;AAC1B,WAAO,KAAK,cAAc,EAAE,MAAM,SAAS,GAAG,MAAM,CAAC;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,aAAa,SAAwB;AACnC,UAAM,IAAI,KAAK,eAAe;AAC9B,MAAE,MAAM,UAAU;AAClB,QAAI,SAAS;AACX,QAAE,MAAM;AAAA,QACN;AAAA,QACA,0BAA0B,EAAE,QAAQ,KAAK,OAAO,EAAE,QAAQ,WAAW;AAAA,QACrE,EAAE,OAAO,EAAE,QAAQ,OAAO,aAAa,EAAE,QAAQ,YAAY;AAAA,MAC/D;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,YAAwC;AAC5C,UAAM,IAAI,KAAK,eAAe;AAC9B,UAAM,SAAS,MAAM,EAAE,KAAK,QAA2B,OAAO,gBAAgB;AAM9E,QAAI,OAAO,QAAQ,eAAe,YAAY,OAAO,SAAS,OAAO,UAAU,GAAG;AAChF,QAAE,iBAAiB,OAAO;AAC1B,QAAE,iBAAiB,KAAK,IAAI;AAAA,IAC9B;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAc;AACZ,QAAI,CAAC,KAAK,MAAO;AAUjB,QAAI,KAAK,MAAM,iBAAiB;AAC9B,UAAI;AACF,aAAK,MAAM,mBAAmB,EAAE,MAAM,KAAK,CAAC;AAAA,MAC9C,QAAQ;AAAA,MAGR;AAAA,IACF;AAMA,SAAK,MAAM,aAAa,UAAU;AAClC,SAAK,MAAM,SAAS,MAAM;AAG1B,mBAAe,EAAE,OAAO,OAAU,CAAC;AAKnC,SAAK,MAAM,aAAa,SAAS;AACjC,SAAK,MAAM,OAAO,MAAM;AAIxB,SAAK,MAAM,WAAW,MAAM;AAK5B,SAAK,MAAM,YAAY,MAAM;AAC7B,SAAK,MAAM,eAAe,CAAC;AAC3B,SAAK,MAAM,YAAY,CAAC;AACxB,SAAK,MAAM,kBAAkB;AAM7B,SAAK,MAAM,iBAAiB;AAC5B,SAAK,MAAM,iBAAiB;AAC5B,QAAI,KAAK,MAAM,aAAa;AAC1B,YAAM,UAAU,IAAI;AAAA,QAAY,KAAK,MAAM,QAAQ;AAAA,QAAW,CAAC,MAAM,UACnE,KAAK,MAAM,MAAM,KAAK;AAAA,MACxB;AACA,WAAK,MAAM,cAAc;AACzB,cAAQ,QAAQ;AAAA,IAClB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,cAA2B;AACzB,QAAI,CAAC,KAAK,OAAO;AACf,aAAO;AAAA,QACL,SAAS;AAAA,QACT,aAAa;AAAA,QACb,qBAAqB;AAAA,QACrB,iBAAiB;AAAA,QACjB,YAAY;AAAA,QACZ,SAAS;AAAA,QACT,OAAO,EAAE,gBAAgB,MAAM,gBAAgB,MAAM,QAAQ,KAAK;AAAA,QAClE,cAAc,EAAE,OAAO,GAAG,aAAa,GAAG,OAAO,OAAO,gBAAgB,EAAE;AAAA,QAC1E,QAAQ;AAAA,UACN,UAAU;AAAA,UACV,SAAS;AAAA,UACT,UAAU;AAAA,UACV,aAAa;AAAA,UACb,WAAW;AAAA,UACX,qBAAqB;AAAA,UACrB,aAAa;AAAA,QACf;AAAA,MACF;AAAA,IACF;AACA,UAAM,IAAI,KAAK;AACf,UAAM,SACJ,EAAE,mBAAmB,QAAQ,EAAE,mBAAmB,OAC9C,EAAE,iBAAiB,EAAE,iBACrB;AACN,WAAO;AAAA,MACL,SAAS;AAAA,MACT,aAAa,EAAE,SAAS;AAAA,MACxB,qBAAqB,EAAE,SAAS;AAAA,MAChC,iBAAiB,EAAE;AAAA,MACnB,YAAY,EAAE,QAAQ;AAAA,MACtB,SAAS,EAAE,QAAQ;AAAA,MACnB,OAAO;AAAA,QACL,gBAAgB,EAAE;AAAA,QAClB,gBAAgB,EAAE;AAAA,QAClB;AAAA,MACF;AAAA,MACA,cAAc;AAAA,QACZ,OAAO,EAAE,aAAa,KAAK,EAAE;AAAA,QAC7B,aAAa,EAAE,aAAa;AAAA,QAC5B,OAAO,EAAE,aAAa;AAAA,QACtB,gBAAgB,EAAE,aAAa;AAAA,MACjC;AAAA,MACA,QAAQ,EAAE,OAAO,SAAS;AAAA,IAC5B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,uBAA+B;AAC7B,UAAM,IAAI,KAAK,eAAe;AAC9B,WACE,EAAE,SAAS,uBACX,EAAE,mBACF,EAAE,SAAS;AAAA,EAEf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,iBAAgC;AAC9B,WAAO,KAAK,QAAQ,KAAK,MAAM,SAAS,cAAc;AAAA,EACxD;AAAA;AAAA,EAIQ,iBAAgC;AACtC,QAAI,CAAC,KAAK,OAAO;AACf,YAAM,IAAI,eAAe;AAAA,QACvB,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SACE;AAAA,MACJ,CAAC;AAAA,IACH;AACA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,sBAA0D;AAChE,UAAM,IAAI,KAAK,eAAe;AAC9B,QAAI,EAAE,SAAS,qBAAqB;AAClC,aAAO,EAAE,YAAY,EAAE,SAAS,oBAAoB;AAAA,IACtD;AACA,QAAI,EAAE,gBAAiB,QAAO,EAAE,QAAQ,EAAE,gBAAgB;AAC1D,WAAO,EAAE,aAAa,EAAE,SAAS,YAAY;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBQ,uBAGN;AACA,UAAM,IAAI,KAAK,eAAe;AAC9B,UAAM,OAAqF;AAAA,MACzF,aAAa,EAAE,SAAS;AAAA,IAC1B;AACA,QAAI,EAAE,gBAAiB,MAAK,kBAAkB,EAAE;AAChD,QAAI,EAAE,SAAS,qBAAqB;AAClC,WAAK,sBAAsB,EAAE,SAAS;AAAA,IACxC;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,cAAsB;AAC5B,UAAM,KAAK,KAAK,IAAI,EAAE,SAAS,EAAE;AACjC,WAAO,OAAO,EAAE,GAAG,YAAY,CAAC,CAAC;AAAA,EACnC;AACF;AAMO,IAAM,YAAY,IAAI,gBAAgB;AAmB7C,SAAS,gBAAgB,WAAuC;AAC9D,MAAI,UAAU,WAAW,cAAc,EAAG,QAAO;AACjD,MAAI,UAAU,WAAW,cAAc,EAAG,QAAO;AACjD,SAAO;AACT;AAuBA,SAAS,kBAA2B;AAOlC,QAAM,IAAK,WAER;AACH,MAAI,GAAG,6BAA6B,KAAM,QAAO;AACjD,QAAM,WAAW,GAAG,UAAU;AAC9B,MAAI,CAAC,SAAU,QAAO;AACtB,MAAI,aAAa,eAAe,aAAa,YAAa,QAAO;AAKjE,MAAI,aAAa,UAAW,QAAO;AACnC,MAAI,aAAa,SAAS,aAAa,QAAS,QAAO;AAMvD,MAAI,cAAc,KAAK,QAAQ,EAAG,QAAO;AACzC,MAAI,SAAS,SAAS,QAAQ,EAAG,QAAO;AACxC,MAAI,QAAQ,KAAK,QAAQ,EAAG,QAAO;AACnC,MAAI,cAAc,KAAK,QAAQ,EAAG,QAAO;AACzC,MAAI,8BAA8B,KAAK,QAAQ,EAAG,QAAO;AACzD,SAAO;AACT;AAEA,SAAS,iBACP,OACiB;AACjB,MAAI,UAAU,OAAO;AACnB,WAAO;AAAA,MACL,UAAU;AAAA,MACV,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,WAAW;AAAA,MACX,QAAQ;AAAA,IACV;AAAA,EACF;AACA,MAAI,UAAU,UAAa,UAAU,MAAM;AACzC,WAAO,EAAE,GAAG,mBAAmB;AAAA,EACjC;AACA,SAAO;AAAA,IACL,UAAU,MAAM,YAAY,mBAAmB;AAAA,IAC/C,WAAW,MAAM,aAAa,mBAAmB;AAAA,IACjD,YAAY,MAAM,cAAc,mBAAmB;AAAA,IACnD,QAAQ,MAAM,UAAU,mBAAmB;AAAA,IAC3C,WAAW,MAAM,aAAa,mBAAmB;AAAA,IACjD,QAAQ,MAAM,UAAU,mBAAmB;AAAA,EAC7C;AACF;AAmBA,SAAS,mBAAmB,UAAkC;AAC5D,QAAM,IAAK,WAAmC;AAC9C,QAAM,MAAO,WAAuC;AACpD,MAAI,CAAC,KAAK,CAAC,IAAK,QAAO,MAAM;AAE7B,QAAM,cAAc,MAAY;AAC9B,QAAI,IAAI,oBAAoB,SAAU,UAAS;AAAA,EACjD;AACA,QAAM,aAAa,MAAY,SAAS;AAExC,MAAI,iBAAiB,oBAAoB,WAAW;AACpD,IAAE,iBAAiB,YAAY,UAAU;AACzC,IAAE,iBAAiB,gBAAgB,UAAU;AAE7C,SAAO,MAAM;AACX,QAAI,oBAAoB,oBAAoB,WAAW;AACvD,MAAE,oBAAoB,YAAY,UAAU;AAC5C,MAAE,oBAAoB,gBAAgB,UAAU;AAAA,EAClD;AACF;;;A3B5+DO,SAAS,eAAe,KAA2B;AACxD,QAAM,QAAI,gBAAa,eAAe,GAAG,CAAC;AAE1C,4BAAU,MAAM;AACd,MAAE,QAAQ,eAAe,GAAG;AAC5B,QAAI,cAAmC;AACvC,QAAI;AACF,oBAAc,UAAU,qBAAqB,MAAM;AACjD,UAAE,QAAQ,eAAe,GAAG;AAAA,MAC9B,CAAC;AAAA,IACH,QAAQ;AAAA,IAIR;AACA,mCAAe,MAAM;AACnB,UAAI,YAAa,aAAY;AAAA,IAC/B,CAAC;AAAA,EACH,CAAC;AAED,SAAO;AACT;AAMO,SAAS,kBAA0C;AACxD,QAAM,QAAI,gBAAuB,aAAa,CAAC;AAE/C,4BAAU,MAAM;AACd,MAAE,QAAQ,aAAa;AACvB,QAAI,cAAmC;AACvC,QAAI;AACF,oBAAc,UAAU,qBAAqB,CAAC,iBAAiB;AAC7D,UAAE,QAAQ,aAAa,OAAO,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG;AAAA,MACnE,CAAC;AAAA,IACH,QAAQ;AAAA,IAER;AACA,mCAAe,MAAM;AACnB,UAAI,YAAa,aAAY;AAAA,IAC/B,CAAC;AAAA,EACH,CAAC;AAED,SAAO;AACT;AAEA,SAAS,eAAe,KAAsB;AAC5C,MAAI;AACF,WAAO,UAAU,WAAW,GAAG;AAAA,EACjC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,eAAkC;AACzC,MAAI;AACF,WAAO,UAAU,iBAAiB,EAC/B,OAAO,CAAC,MAAM,EAAE,QAAQ,EACxB,IAAI,CAAC,MAAM,EAAE,GAAG;AAAA,EACrB,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;","names":["doc","ref","MemoryStorage","location","console","safeStringify","message"]}
|