@cross-deck/web 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -0
- package/README.md +82 -27
- package/dist/{index.js → index.cjs} +269 -17
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +152 -11
- package/dist/index.d.ts +152 -11
- package/dist/index.mjs +268 -16
- package/dist/index.mjs.map +1 -1
- package/dist/react.cjs +1226 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.mts +68 -0
- package/dist/react.d.ts +68 -0
- package/dist/react.mjs +1200 -0
- package/dist/react.mjs.map +1 -0
- package/package.json +17 -1
- package/dist/index.js.map +0 -1
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/identity.ts","../src/entitlement-cache.ts","../src/event-queue.ts","../src/storage.ts","../src/device-info.ts","../src/auto-track.ts","../src/crossdeck.ts"],"sourcesContent":["/**\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\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\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 // 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 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 });\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 });\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.2.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\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: { body?: unknown; query?: Record<string, string | undefined> } = {}\n ): Promise<T> {\n const url = this.buildUrl(path, options.query);\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.config.publicKey}`,\n \"Crossdeck-Sdk-Version\": `${SDK_NAME}@${this.config.sdkVersion}`,\n Accept: \"application/json\",\n };\n let bodyInit: BodyInit | undefined;\n if (options.body !== undefined) {\n headers[\"Content-Type\"] = \"application/json\";\n bodyInit = JSON.stringify(options.body);\n }\n\n let response: Response;\n try {\n response = await fetch(url, {\n method,\n headers,\n body: bodyInit,\n });\n } catch (err) {\n throw new CrossdeckError({\n type: \"network_error\",\n code: \"fetch_failed\",\n message: err instanceof Error ? err.message : \"fetch failed\",\n });\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 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 * 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\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 constructor(\n private readonly storage: KeyValueStorage,\n private readonly prefix: string\n ) {\n const stored = {\n anon: storage.getItem(prefix + KEY_ANON),\n cdcust: storage.getItem(prefix + KEY_CDCUST),\n };\n this.state = {\n anonymousId: stored.anon ?? this.mintAnonymousId(),\n crossdeckCustomerId: stored.cdcust,\n };\n if (!stored.anon) {\n storage.setItem(prefix + KEY_ANON, this.state.anonymousId);\n }\n }\n\n /** Return the persisted anonymous device ID (always set). */\n get anonymousId(): string {\n return this.state.anonymousId;\n }\n\n /** Return the resolved crossdeckCustomerId once we have one, else null. */\n get crossdeckCustomerId(): string | null {\n return this.state.crossdeckCustomerId;\n }\n\n /** Persist a newly-resolved Crossdeck customer ID. */\n setCrossdeckCustomerId(value: string): void {\n this.state.crossdeckCustomerId = value;\n this.storage.setItem(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.storage.removeItem(this.prefix + KEY_ANON);\n this.storage.removeItem(this.prefix + KEY_CDCUST);\n this.state = {\n anonymousId: this.mintAnonymousId(),\n crossdeckCustomerId: null,\n };\n this.storage.setItem(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\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 * 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 class EntitlementCache {\n private active = new Set<string>();\n private all: PublicEntitlement[] = [];\n private lastUpdated = 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 * 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 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 }\n\n /**\n * Wipe — used on reset() (logout). The SDK forgets everything until\n * the next identify + read.\n */\n clear(): void {\n this.active.clear();\n this.all = [];\n this.lastUpdated = 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 5000) → flush idle batch\n * - flush() called explicitly (e.g. before page unload)\n *\n * On network failure, the events stay in the buffer for the next flush\n * — bounded retry that doesn't drop events when the network blips.\n *\n * The cap on buffer size (1000 events) protects against runaway memory\n * if the network is permanently down — beyond that we drop the oldest\n * event and increment a dropped counter (exposed via getStats()).\n */\n\nimport type { HttpClient } from \"./http\";\nimport type { EventProperties, IngestResponse } from \"./types\";\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 — exactly one will be set\n developerUserId?: string;\n anonymousId?: string;\n crossdeckCustomerId?: string;\n}\n\nexport interface EventQueueConfig {\n http: HttpClient;\n batchSize: number;\n intervalMs: number;\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}\n\nexport interface EventQueueStats {\n buffered: number;\n dropped: number;\n inFlight: number;\n lastFlushAt: number;\n lastError: string | 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\n constructor(private readonly cfg: EventQueueConfig) {}\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 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 flush attempt.\n */\n async flush(): Promise<IngestResponse | null> {\n if (this.buffer.length === 0) return null;\n this.cancelTimerIfSet();\n\n // Capture the current buffer; replace with a new array so concurrent\n // enqueue() calls during the in-flight request don't get lost.\n const batch = this.buffer.splice(0);\n this.inFlight += batch.length;\n\n try {\n const result = await this.cfg.http.request<IngestResponse>(\"POST\", \"/events\", {\n body: { events: batch },\n });\n this.lastFlushAt = Date.now();\n this.lastError = null;\n this.inFlight -= batch.length;\n return result;\n } catch (err) {\n // Re-buffer at the front of the queue. Order matters less than\n // not losing events — the backend will dedupe on eventId.\n this.buffer.unshift(...batch);\n this.inFlight -= batch.length;\n this.lastError = err instanceof Error ? err.message : String(err);\n // Schedule another idle flush so a transient outage recovers.\n this.scheduleIdleFlush();\n return null;\n }\n }\n\n /** Cancel any pending timer and clear in-memory state. */\n reset(): void {\n this.cancelTimerIfSet();\n this.buffer = [];\n this.dropped = 0;\n this.inFlight = 0;\n this.lastError = null;\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 };\n }\n\n private scheduleIdleFlush(): void {\n this.cancelTimerIfSet();\n const sched = this.cfg.scheduler ?? defaultScheduler;\n this.cancelTimer = sched(() => {\n void this.flush();\n }, this.cfg.intervalMs);\n }\n\n private cancelTimerIfSet(): void {\n if (this.cancelTimer) {\n this.cancelTimer();\n this.cancelTimer = null;\n }\n }\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 * Storage adapters for SDK-persisted state.\n *\n * Two flavours:\n * - browser localStorage (default in browsers)\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\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 * 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 * 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\nexport const DEFAULT_AUTO_TRACK: AutoTrackConfig = {\n sessions: true,\n pageViews: true,\n deviceInfo: 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\nexport class AutoTracker {\n private session: SessionState | null = null;\n private cleanups: Array<() => void> = [];\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 }\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 // ---------- 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 };\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 const fire = (): void => {\n const loc = w.location;\n this.track(\"page.viewed\", {\n path: loc.pathname,\n url: loc.href,\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 const onPopState = (): void => fire();\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\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 * Public API surface for @cross-deck/web.\n *\n * Usage (browser):\n *\n * import { Crossdeck } from \"@cross-deck/web\";\n *\n * Crossdeck.start({ publicKey: \"cd_pub_live_…\" });\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 } from \"@cross-deck/web\";\n * import { MemoryStorage } from \"@cross-deck/web\";\n *\n * Crossdeck.start({\n * publicKey: \"cd_pub_test_…\",\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_VERSION, DEFAULT_BASE_URL } from \"./http\";\nimport { IdentityStore } from \"./identity\";\nimport { EntitlementCache } from \"./entitlement-cache\";\nimport { EventQueue, type QueuedEvent } from \"./event-queue\";\nimport { 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 type {\n AliasResult,\n AutoTrackOptions,\n CrossdeckOptions,\n Diagnostics,\n EntitlementsListResponse,\n EventProperties,\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 /** Cached enrichment payload merged into every event's properties. */\n deviceInfo: DeviceInfo;\n options: Required<\n Omit<CrossdeckOptions, \"storage\" | \"sdkVersion\" | \"autoTrack\" | \"appVersion\">\n > & {\n sdkVersion: string;\n autoTrack: AutoTrackConfig;\n appVersion: string | null;\n };\n developerUserId: string | null;\n}\n\nexport class CrossdeckClient {\n private state: InternalState | null = null;\n\n /**\n * Boot the SDK. Idempotent — calling start twice with the same options\n * is a no-op; calling with different options replaces the previous\n * configuration.\n */\n start(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.start requires a publishable key starting with cd_pub_.\",\n });\n }\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 publicKey: options.publicKey,\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 eventFlushIntervalMs: options.eventFlushIntervalMs ?? 5000,\n sdkVersion: options.sdkVersion ?? SDK_VERSION,\n autoTrack,\n appVersion: options.appVersion ?? null,\n };\n\n const http = new HttpClient({\n publicKey: opts.publicKey,\n baseUrl: opts.baseUrl,\n sdkVersion: opts.sdkVersion,\n });\n const effectiveStorage = persistIdentity ? storage : new MemoryStorage();\n const identity = new IdentityStore(effectiveStorage, opts.storagePrefix);\n const entitlements = new EntitlementCache();\n const events = new EventQueue({\n http,\n batchSize: opts.eventFlushBatchSize,\n intervalMs: opts.eventFlushIntervalMs,\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 this.state = {\n http,\n identity,\n entitlements,\n events,\n autoTracker: null,\n deviceInfo,\n options: opts,\n developerUserId: null,\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\n if (opts.autoHeartbeat) {\n // Fire-and-forget — heartbeat failure shouldn't block start().\n void this.heartbeat().catch(() => undefined);\n }\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 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 const result = await s.http.request<AliasResult>(\"POST\", \"/identity/alias\", {\n body: { userId, anonymousId: s.identity.anonymousId },\n });\n s.identity.setCrossdeckCustomerId(result.crossdeckCustomerId);\n s.developerUserId = userId;\n return result;\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 * Queue a telemetry event. Returns immediately — the network round-\n * trip happens in the background. To flush before the page unloads,\n * call flushEvents().\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 // Enrichment policy: device info first, then auto-tracker context (e.g.\n // sessionId on session events), then caller-supplied properties last so\n // a developer can override anything the SDK auto-attached.\n const enriched: EventProperties = { ...s.deviceInfo };\n const sessionId = s.autoTracker?.currentSessionId;\n if (sessionId) enriched.sessionId = sessionId;\n if (properties) Object.assign(enriched, properties);\n\n const event: QueuedEvent = {\n eventId: this.mintEventId(),\n name,\n timestamp: Date.now(),\n properties: enriched,\n };\n Object.assign(event, this.identityHintForEvent());\n s.events.enqueue(event);\n }\n\n /** Force-flush queued events. Useful to call from page-unload handlers. */\n async flushEvents(): Promise<void> {\n const s = this.requireStarted();\n await s.events.flush();\n }\n\n /** Forward an Apple StoreKit 2 transaction for verification + projection. */\n async purchaseApple(input: {\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: \"purchaseApple requires a signedTransactionInfo string from StoreKit 2.\",\n });\n }\n const result = await s.http.request<PurchaseResult>(\"POST\", \"/purchases\", {\n body: { rail: \"apple\", ...input },\n });\n s.identity.setCrossdeckCustomerId(result.crossdeckCustomerId);\n s.entitlements.setFromList(result.entitlements);\n return result;\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 return await s.http.request<HeartbeatResponse>(\"GET\", \"/sdk/heartbeat\");\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 // Tear down + reinstall the auto-tracker so the new session belongs\n // to the new identity, not the old one.\n this.state.autoTracker?.uninstall();\n this.state.identity.reset();\n this.state.entitlements.clear();\n this.state.events.reset();\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 entitlements: { count: 0, lastUpdated: 0 },\n events: {\n buffered: 0,\n dropped: 0,\n inFlight: 0,\n lastFlushAt: 0,\n lastError: null,\n },\n };\n }\n const s = this.state;\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 entitlements: {\n count: s.entitlements.list().length,\n lastUpdated: s.entitlements.freshness,\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_started\",\n message: \"Call Crossdeck.start({ publicKey }) 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 /** Pick the right identity hint to embed on a queued event. */\n private identityHintForEvent(): Pick<\n QueuedEvent,\n \"developerUserId\" | \"anonymousId\" | \"crossdeckCustomerId\"\n > {\n const s = this.requireStarted();\n if (s.identity.crossdeckCustomerId) {\n return { crossdeckCustomerId: s.identity.crossdeckCustomerId };\n }\n if (s.developerUserId) return { developerUserId: s.developerUserId };\n return { anonymousId: s.identity.anonymousId };\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 */\nfunction resolveAutoTrack(\n input: CrossdeckOptions[\"autoTrack\"],\n): AutoTrackConfig {\n if (input === false) {\n return { sessions: false, pageViews: false, deviceInfo: false };\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 };\n}\n"],"mappings":"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","names":["doc"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/identity.ts","../src/entitlement-cache.ts","../src/event-queue.ts","../src/storage.ts","../src/device-info.ts","../src/auto-track.ts","../src/debug.ts","../src/crossdeck.ts"],"sourcesContent":["/**\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\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\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 // 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 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 });\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 });\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.3.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\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: { body?: unknown; query?: Record<string, string | undefined> } = {}\n ): Promise<T> {\n const url = this.buildUrl(path, options.query);\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.config.publicKey}`,\n \"Crossdeck-Sdk-Version\": `${SDK_NAME}@${this.config.sdkVersion}`,\n Accept: \"application/json\",\n };\n let bodyInit: BodyInit | undefined;\n if (options.body !== undefined) {\n headers[\"Content-Type\"] = \"application/json\";\n bodyInit = JSON.stringify(options.body);\n }\n\n let response: Response;\n try {\n response = await fetch(url, {\n method,\n headers,\n body: bodyInit,\n });\n } catch (err) {\n throw new CrossdeckError({\n type: \"network_error\",\n code: \"fetch_failed\",\n message: err instanceof Error ? err.message : \"fetch failed\",\n });\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 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 * 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\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 constructor(\n private readonly storage: KeyValueStorage,\n private readonly prefix: string\n ) {\n const stored = {\n anon: storage.getItem(prefix + KEY_ANON),\n cdcust: storage.getItem(prefix + KEY_CDCUST),\n };\n this.state = {\n anonymousId: stored.anon ?? this.mintAnonymousId(),\n crossdeckCustomerId: stored.cdcust,\n };\n if (!stored.anon) {\n storage.setItem(prefix + KEY_ANON, this.state.anonymousId);\n }\n }\n\n /** Return the persisted anonymous device ID (always set). */\n get anonymousId(): string {\n return this.state.anonymousId;\n }\n\n /** Return the resolved crossdeckCustomerId once we have one, else null. */\n get crossdeckCustomerId(): string | null {\n return this.state.crossdeckCustomerId;\n }\n\n /** Persist a newly-resolved Crossdeck customer ID. */\n setCrossdeckCustomerId(value: string): void {\n this.state.crossdeckCustomerId = value;\n this.storage.setItem(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.storage.removeItem(this.prefix + KEY_ANON);\n this.storage.removeItem(this.prefix + KEY_CDCUST);\n this.state = {\n anonymousId: this.mintAnonymousId(),\n crossdeckCustomerId: null,\n };\n this.storage.setItem(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\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\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 * 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.\n }\n }\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 5000) → flush idle batch\n * - flush() called explicitly (e.g. before page unload)\n *\n * On network failure, the events stay in the buffer for the next flush\n * — bounded retry that doesn't drop events when the network blips.\n *\n * The cap on buffer size (1000 events) protects against runaway memory\n * if the network is permanently down — beyond that we drop the oldest\n * event and increment a dropped counter (exposed via getStats()).\n */\n\nimport type { HttpClient } from \"./http\";\nimport type { EventProperties, IngestResponse } from \"./types\";\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 — exactly one will be 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\nexport interface EventQueueStats {\n buffered: number;\n dropped: number;\n inFlight: number;\n lastFlushAt: number;\n lastError: string | 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\n constructor(private readonly cfg: EventQueueConfig) {}\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 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 flush attempt.\n */\n async flush(): Promise<IngestResponse | null> {\n if (this.buffer.length === 0) return null;\n this.cancelTimerIfSet();\n\n // Capture the current buffer; replace with a new array so concurrent\n // enqueue() calls during the in-flight request don't get lost.\n const batch = this.buffer.splice(0);\n this.inFlight += batch.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 loudly\n // (env_mismatch).\n appId: env.appId,\n environment: env.environment,\n sdk: env.sdk,\n events: batch,\n },\n });\n this.lastFlushAt = Date.now();\n this.lastError = null;\n this.inFlight -= batch.length;\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 of the queue. Order matters less than\n // not losing events — the backend will dedupe on eventId.\n this.buffer.unshift(...batch);\n this.inFlight -= batch.length;\n this.lastError = err instanceof Error ? err.message : String(err);\n // Schedule another idle flush so a transient outage recovers.\n this.scheduleIdleFlush();\n return null;\n }\n }\n\n /** Cancel any pending timer and clear in-memory state. */\n reset(): void {\n this.cancelTimerIfSet();\n this.buffer = [];\n this.dropped = 0;\n this.inFlight = 0;\n this.lastError = null;\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 };\n }\n\n private scheduleIdleFlush(): void {\n this.cancelTimerIfSet();\n const sched = this.cfg.scheduler ?? defaultScheduler;\n this.cancelTimer = sched(() => {\n void this.flush();\n }, this.cfg.intervalMs);\n }\n\n private cancelTimerIfSet(): void {\n if (this.cancelTimer) {\n this.cancelTimer();\n this.cancelTimer = null;\n }\n }\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 * Storage adapters for SDK-persisted state.\n *\n * Two flavours:\n * - browser localStorage (default in browsers)\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\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 * 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 * 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\nexport const DEFAULT_AUTO_TRACK: AutoTrackConfig = {\n sessions: true,\n pageViews: true,\n deviceInfo: 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\nexport class AutoTracker {\n private session: SessionState | null = null;\n private cleanups: Array<() => void> = [];\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 }\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 // ---------- 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 };\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 const fire = (): void => {\n const loc = w.location;\n this.track(\"page.viewed\", {\n path: loc.pathname,\n url: loc.href,\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 const onPopState = (): void => fire();\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\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 * 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\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 * 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 { 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 type {\n AliasResult,\n AutoTrackOptions,\n CrossdeckOptions,\n Diagnostics,\n EntitlementsListResponse,\n Environment,\n EventProperties,\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 /** 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\"\n >\n > & {\n sdkVersion: string;\n autoTrack: AutoTrackConfig;\n appVersion: string | null;\n };\n debug: DebugLogger;\n developerUserId: string | 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 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 eventFlushIntervalMs: options.eventFlushIntervalMs ?? 5000,\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 });\n const effectiveStorage = persistIdentity ? storage : new MemoryStorage();\n const identity = new IdentityStore(effectiveStorage, opts.storagePrefix);\n const entitlements = new EntitlementCache();\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 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 });\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 this.state = {\n http,\n identity,\n entitlements,\n events,\n autoTracker: null,\n deviceInfo,\n options: opts,\n debug,\n developerUserId: 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\n if (opts.autoHeartbeat) {\n // Fire-and-forget — heartbeat failure shouldn't block init().\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 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 const result = await s.http.request<AliasResult>(\"POST\", \"/identity/alias\", {\n body: { userId, anonymousId: s.identity.anonymousId },\n });\n s.identity.setCrossdeckCustomerId(result.crossdeckCustomerId);\n s.developerUserId = userId;\n return result;\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 // 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 // Enrichment policy: device info first, then auto-tracker context (e.g.\n // sessionId on session events), then caller-supplied properties last so\n // a developer can override anything the SDK auto-attached.\n const enriched: EventProperties = { ...s.deviceInfo };\n const sessionId = s.autoTracker?.currentSessionId;\n if (sessionId) enriched.sessionId = sessionId;\n if (properties) Object.assign(enriched, properties);\n\n const event: QueuedEvent = {\n eventId: this.mintEventId(),\n name,\n timestamp: Date.now(),\n properties: enriched,\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 * NorthStar §4: standard method name across all Crossdeck SDKs.\n */\n async flush(): Promise<void> {\n const s = this.requireStarted();\n await s.events.flush();\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 return await s.http.request<HeartbeatResponse>(\"GET\", \"/sdk/heartbeat\");\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 // Tear down + reinstall the auto-tracker so the new session belongs\n // to the new identity, not the old one.\n this.state.autoTracker?.uninstall();\n this.state.identity.reset();\n this.state.entitlements.clear();\n this.state.events.reset();\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 entitlements: { count: 0, lastUpdated: 0 },\n events: {\n buffered: 0,\n dropped: 0,\n inFlight: 0,\n lastFlushAt: 0,\n lastError: null,\n },\n };\n }\n const s = this.state;\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 entitlements: {\n count: s.entitlements.list().length,\n lastUpdated: s.entitlements.freshness,\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 /** Pick the right identity hint to embed on a queued event. */\n private identityHintForEvent(): Pick<\n QueuedEvent,\n \"developerUserId\" | \"anonymousId\" | \"crossdeckCustomerId\"\n > {\n const s = this.requireStarted();\n if (s.identity.crossdeckCustomerId) {\n return { crossdeckCustomerId: s.identity.crossdeckCustomerId };\n }\n if (s.developerUserId) return { developerUserId: s.developerUserId };\n return { anonymousId: s.identity.anonymousId };\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\nfunction resolveAutoTrack(\n input: CrossdeckOptions[\"autoTrack\"],\n): AutoTrackConfig {\n if (input === false) {\n return { sessions: false, pageViews: false, deviceInfo: false };\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 };\n}\n"],"mappings":"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","names":["doc"]}
|