@cross-deck/web 0.6.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/vue.ts","../src/errors.ts","../src/http.ts","../src/identity.ts","../src/entitlement-cache.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/web-vitals.ts","../src/consent.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 | \"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\nexport class CrossdeckError extends Error {\n public readonly type: CrossdeckErrorType;\n public readonly code: string;\n public readonly requestId?: string;\n public readonly status?: number;\n public readonly retryAfterMs?: number;\n\n constructor(payload: CrossdeckErrorPayload) {\n super(payload.message);\n this.name = \"CrossdeckError\";\n this.type = payload.type;\n this.code = payload.code;\n this.requestId = payload.requestId;\n this.status = payload.status;\n this.retryAfterMs = payload.retryAfterMs;\n // Restore prototype chain — needed when 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 });\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 * 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\nexport const SDK_NAME = \"@cross-deck/web\";\nexport const SDK_VERSION = \"0.10.0\";\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\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 throw await crossdeckErrorFromResponse(response);\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 cross­deckCustomerId 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 * Local cache of active entitlements so isEntitled() can answer\n * synchronously after the first read. Cache is updated:\n * - On successful getEntitlements()\n * - On successful purchase()\n * - Manually via setFromList() (used by callers that batch updates)\n *\n * The cache holds only ACTIVE entitlements — inactive ones are excluded\n * by the backend before they hit us. isEntitled returns false for\n * anything not in the set.\n *\n * Reactive listener API\n * ---------------------\n * `subscribe(listener)` registers a callback that fires every time the\n * cache mutates (setFromList or clear). This is the foundation for the\n * `useEntitlement` React hook in `@cross-deck/web/react` and any other\n * framework binding consumers need: SwiftUI's `@Observable`, Vue's\n * `ref()`, Solid's signals, etc.\n *\n * Why we need it: isEntitled() is a sync cache read — but if a React\n * component calls it in a render path, React has no way to know when\n * the cache populates asynchronously after `getEntitlements()` lands.\n * Without a subscribe API the component shows the empty-cache result\n * forever (until something else triggers a re-render). With it, the\n * binding can re-render when the data actually arrives.\n *\n * Listener semantics:\n * - Fired AFTER the cache has been mutated (listener sees fresh state)\n * - Fire-and-forget: thrown errors in a listener don't crash the SDK\n * (they're swallowed; the next listener still runs)\n * - The unsubscribe function returned from subscribe() is idempotent\n * - Listeners are NOT fired on subscribe — caller is expected to\n * read current state synchronously from isEntitled()/list() if it\n * wants the initial render to reflect cached data\n *\n * Thread / re-entrancy safety: this is a synchronous in-memory Set with\n * no I/O. The async paths that update it are serialised through the\n * SDK's request queue — callers won't see torn reads.\n */\n\nimport type { PublicEntitlement } from \"./types\";\n\nexport type EntitlementsListener = (entitlements: PublicEntitlement[]) => void;\n\nexport class EntitlementCache {\n private active = new Set<string>();\n private all: PublicEntitlement[] = [];\n private lastUpdated = 0;\n private listeners = new Set<EntitlementsListener>();\n private listenerErrorCount = 0;\n\n /** Sync read — true iff the entitlement key is currently active. */\n isEntitled(key: string): boolean {\n return this.active.has(key);\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. 0 means \"never\". */\n get freshness(): number {\n return this.lastUpdated;\n }\n\n /**\n * Cumulative count of listener invocations that threw. Listener errors\n * are swallowed (a buggy consumer must not crash the SDK) but the\n * counter lets diagnostics() surface \"you have a broken subscriber\"\n * without putting the developer in a debug session.\n */\n get listenerErrors(): number {\n return this.listenerErrorCount;\n }\n\n /**\n * Replace the cache with a fresh server response. The backend already\n * filters to active + env-matching, so we don't re-filter — just trust\n * what we got.\n *\n * Fires listeners AFTER the mutation so each listener sees the new state.\n */\n setFromList(entitlements: PublicEntitlement[]): void {\n this.all = entitlements.slice();\n this.active = new Set(entitlements.filter((e) => e.isActive).map((e) => e.key));\n this.lastUpdated = Date.now();\n this.notify();\n }\n\n /**\n * Wipe — used on reset() (logout). The SDK forgets everything until\n * the next identify + read.\n *\n * Fires listeners so React/SwiftUI/etc bindings re-render to the\n * logged-out state immediately.\n */\n clear(): void {\n this.active.clear();\n this.all = [];\n this.lastUpdated = 0;\n this.notify();\n }\n\n /**\n * Subscribe to cache mutations. Returns an unsubscribe function.\n *\n * The listener is invoked AFTER setFromList() or clear() with the\n * current snapshot. Throwing inside a listener is non-fatal — the\n * error is swallowed and subsequent listeners still run.\n *\n * Used by `@cross-deck/web/react`'s `useEntitlement` hook to\n * trigger re-renders when entitlements change.\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 private notify(): void {\n if (this.listeners.size === 0) return;\n const snapshot = this.all.slice();\n // Iterate over 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\n // the SDK or other listeners. Counted so diagnostics() can\n // surface \"you have a broken subscriber.\"\n this.listenerErrorCount += 1;\n }\n }\n }\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.\n if (retryAfterMs !== undefined && retryAfterMs > jittered) {\n return Math.min(max, retryAfterMs);\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 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\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 private dropped = 0;\n private inFlight = 0;\n private lastFlushAt = 0;\n private lastError: string | null = null;\n private cancelTimer: (() => void) | null = null;\n private firstFlushFired = false;\n private nextRetryAt: number | null = null;\n private readonly retry: RetryPolicy;\n private readonly persistent: PersistentEventStore | null;\n\n constructor(private readonly cfg: EventQueueConfig) {\n this.retry = new RetryPolicy(cfg.retry ?? {});\n this.persistent = cfg.persistentStore ?? null;\n\n // 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 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.persistent?.save(this.buffer);\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 failure, events stay in the\n * buffer for the next scheduled retry.\n *\n * `options.keepalive` marks the underlying fetch as keepalive so the\n * browser keeps the request alive past page unload. Use this for\n * terminal flushes (pagehide / visibilitychange→hidden / beforeunload).\n */\n async flush(options: { keepalive?: boolean } = {}): Promise<IngestResponse | null> {\n if (this.buffer.length === 0) return null;\n this.cancelTimerIfSet();\n this.nextRetryAt = null;\n\n // Snapshot the buffer for THIS batch. Use a stable batch id so\n // retries of the same logical batch reuse the same Idempotency-Key.\n const batch = this.buffer.splice(0);\n const batchId = this.mintBatchId();\n this.inFlight += batch.length;\n this.persistent?.save(this.buffer);\n this.cfg.onBufferChange?.(this.buffer.length);\n\n try {\n const env = this.cfg.envelope();\n const result = await this.cfg.http.request<IngestResponse>(\"POST\", \"/events\", {\n body: {\n // NorthStar §13.1 batch envelope. The backend validates these\n // against the API-key-resolved app and rejects mismatches\n // loudly (env_mismatch).\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.retry.recordSuccess();\n // Persisted blob no longer needs these events.\n this.persistent?.save(this.buffer);\n if (!this.firstFlushFired) {\n this.firstFlushFired = true;\n this.cfg.onFirstFlushSuccess?.();\n }\n return result;\n } catch (err) {\n // Re-buffer at the FRONT so older events stay older — preserves\n // approximate ordering for the server's session reconstruction.\n this.buffer.unshift(...batch);\n this.inFlight -= batch.length;\n const message = err instanceof Error ? err.message : String(err);\n this.lastError = message;\n this.persistent?.save(this.buffer);\n this.cfg.onBufferChange?.(this.buffer.length);\n\n // Backoff: schedule a retry through the retry-policy module\n // instead of falling through to the idle timer (which would\n // fire at the same rate forever and hammer a flapping server).\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.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: 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 // ---------- 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\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\";\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}\n\nexport const DEFAULT_AUTO_TRACK: AutoTrackConfig = {\n sessions: true,\n pageViews: true,\n deviceInfo: true,\n clicks: true,\n webVitals: true,\n};\n\n/** Reopen as a new session if the tab was hidden longer than this. */\nconst SESSION_RESUME_THRESHOLD_MS = 30 * 60 * 1000;\n\ntype TrackFn = (name: string, properties?: Record<string, unknown>) => void;\n\ninterface SessionState {\n sessionId: string;\n startedAt: 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 * 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 ) {}\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 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 this.session = this.startNewSession();\n this.emitSessionStart();\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 // ---------- sessions ----------\n private installSessionTracking(): void {\n this.session = this.startNewSession();\n this.emitSessionStart();\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; do NOT emit session.ended yet.\n // pagehide / beforeunload are the canonical end signals\n // (mobile backgrounding fires pagehide reliably). If we ended\n // here, returning to the tab seconds later would always start\n // a new session — defeating the 30-min session-window intent.\n this.session.hiddenAt = Date.now();\n } else if (doc.visibilityState === \"visible\") {\n const hiddenFor = this.session.hiddenAt\n ? Date.now() - this.session.hiddenAt\n : 0;\n if (hiddenFor >= SESSION_RESUME_THRESHOLD_MS) {\n // Long idle → end the previous session, start a fresh one.\n this.emitSessionEnd();\n this.session = this.startNewSession();\n this.emitSessionStart();\n } else {\n // Quick return — same session continues.\n this.session.hiddenAt = null;\n }\n }\n };\n\n const onPageHide = (): void => this.emitSessionEnd();\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 return {\n sessionId: mintSessionId(),\n startedAt: Date.now(),\n hiddenAt: null,\n endedSent: false,\n acquisition: captureAcquisition(),\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\nfunction extractText(el: Element): string {\n // Prefer aria-label / value / text content in that order. Strips\n // newlines + collapses whitespace so \" Sign\\n up \" becomes\n // \"Sign up\".\n const aria = el.getAttribute(\"aria-label\");\n if (aria) return aria.replace(/\\s+/g, \" \").trim();\n if (el instanceof HTMLInputElement && el.value) return el.value;\n const text = (el.textContent || \"\").replace(/\\s+/g, \" \").trim();\n return text;\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 | \"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 const seen = new WeakSet<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 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 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 * 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\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, so callers can do an\n * identity-check to skip allocating a new event copy.\n */\nexport function scrubPii(value: string): string {\n if (!value) return value;\n let out = value;\n if (EMAIL_PATTERN.test(out)) {\n out = out.replace(EMAIL_PATTERN, REPLACEMENT_EMAIL);\n }\n // Reset regex lastIndex (global flag carries state between calls).\n EMAIL_PATTERN.lastIndex = 0;\n if (CARD_PATTERN.test(out)) {\n out = out.replace(CARD_PATTERN, REPLACEMENT_CARD);\n }\n CARD_PATTERN.lastIndex = 0;\n return out;\n}\n\n/**\n * Walk an event's properties and replace PII-shaped strings in place.\n * Returns the same shape with strings scrubbed; non-string values pass\n * through unchanged.\n *\n * Mutates a defensive copy — the input is never altered. Caller can\n * pass the result straight to the queue.\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 const v = properties[k];\n if (typeof v === \"string\") {\n out[k] = scrubPii(v);\n } else if (Array.isArray(v)) {\n out[k] = v.map((item) => (typeof item === \"string\" ? scrubPii(item) : item));\n } else {\n out[k] = v;\n }\n }\n return out;\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 { 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 { WebVitalsTracker } from \"./web-vitals\";\nimport { ConsentManager, scrubPii, scrubPiiFromProperties, type ConsentState } from \"./consent\";\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 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\" | \"sdkVersion\" | \"autoTrack\" | \"appVersion\" | \"debug\" | \"respectDnt\" | \"scrubPii\"\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 * 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 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 eventFlushIntervalMs: options.eventFlushIntervalMs ?? 1500,\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 });\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 const entitlements = new EntitlementCache();\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 });\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 // 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 this.state = {\n http,\n identity,\n entitlements,\n events,\n autoTracker: null,\n webVitals: 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(autoTrack, (name, properties) =>\n this.track(name, properties),\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 // 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 /**\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 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 * 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 const result = await s.http.request<EntitlementsListResponse>(\n \"GET\",\n \"/entitlements\",\n { query }\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 local cache. Returns false if the cache\n * has never been populated (call getEntitlements first to warm it).\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, and once on `reset()` to fire the\n * empty-cache state for logout flows.\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 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 // Analytics consent is the gate for general track() calls. Web\n // Vitals events (`webvitals.*`) gate on the `errors` dimension\n // since they're treated as performance / reliability data rather\n // than behavioural analytics. Both gates default-on; the developer\n // must explicitly call Crossdeck.consent({...false}) to drop them.\n const isWebVital = name.startsWith(\"webvitals.\");\n const consentGateOk = 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 // 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. Device info (browser/os/locale/screen — captured once at boot)\n // 2. Auto-tracker session + pageview + acquisition + click IDs\n // 3. Super properties (registered once via Crossdeck.register)\n // 4. Group memberships (set via Crossdeck.group)\n // 5. Caller-supplied properties (sanitised)\n // The order is intentional: developer-supplied data is most\n // authoritative, so it overrides anything the SDK auto-attached.\n const enriched: EventProperties = { ...s.deviceInfo };\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 // ----- 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: Date.now(),\n properties: finalProperties,\n };\n Object.assign(event, this.identityHintForEvent());\n s.events.enqueue(event);\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 const result = await s.http.request<PurchaseResult>(\"POST\", \"/purchases/sync\", {\n body: { rail: input.rail ?? \"apple\", ...input },\n });\n s.identity.setCrossdeckCustomerId(result.crossdeckCustomerId);\n s.entitlements.setFromList(result.entitlements);\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 this.state.entitlements.clear();\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 this.state.developerUserId = 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, 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 listenerErrors: s.entitlements.listenerErrors,\n },\n events: s.events.getStats(),\n };\n }\n\n // ---------- private helpers ----------\n\n private requireStarted(): InternalState {\n if (!this.state) {\n throw new CrossdeckError({\n type: \"configuration_error\",\n code: \"not_initialized\",\n message:\n \"Call Crossdeck.init({ appId, publicKey, environment }) before any other method.\",\n });\n }\n return this.state;\n }\n\n /**\n * Build the identity query for /v1/entitlements. Priority:\n * crossdeckCustomerId > developerUserId > anonymousId\n * — 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 if (hostname === \"::1\" || hostname === \"[::1]\") 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 };\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 };\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"]}