@cross-deck/web 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -3
package/dist/index.d.mts
CHANGED
|
@@ -300,7 +300,7 @@ declare class MemoryStorage implements KeyValueStorage {
|
|
|
300
300
|
* fetch shim, no transitive deps.
|
|
301
301
|
*/
|
|
302
302
|
declare const SDK_NAME = "@cross-deck/web";
|
|
303
|
-
declare const SDK_VERSION = "0.1.
|
|
303
|
+
declare const SDK_VERSION = "0.1.1";
|
|
304
304
|
declare const DEFAULT_BASE_URL = "https://api.cross-deck.com/v1";
|
|
305
305
|
|
|
306
306
|
export { type AliasResult, type AuditRail, Crossdeck, CrossdeckClient, CrossdeckError, type CrossdeckErrorPayload, type CrossdeckErrorType, type CrossdeckOptions, DEFAULT_BASE_URL, type Diagnostics, type EntitlementsListResponse, type Environment, type EventProperties, type HeartbeatResponse, type IdentifyOptions, type KeyValueStorage, MemoryStorage, type Platform, type PublicEntitlement, type PurchaseResult, SDK_NAME, SDK_VERSION };
|
package/dist/index.d.ts
CHANGED
|
@@ -300,7 +300,7 @@ declare class MemoryStorage implements KeyValueStorage {
|
|
|
300
300
|
* fetch shim, no transitive deps.
|
|
301
301
|
*/
|
|
302
302
|
declare const SDK_NAME = "@cross-deck/web";
|
|
303
|
-
declare const SDK_VERSION = "0.1.
|
|
303
|
+
declare const SDK_VERSION = "0.1.1";
|
|
304
304
|
declare const DEFAULT_BASE_URL = "https://api.cross-deck.com/v1";
|
|
305
305
|
|
|
306
306
|
export { type AliasResult, type AuditRail, Crossdeck, CrossdeckClient, CrossdeckError, type CrossdeckErrorPayload, type CrossdeckErrorType, type CrossdeckOptions, DEFAULT_BASE_URL, type Diagnostics, type EntitlementsListResponse, type Environment, type EventProperties, type HeartbeatResponse, type IdentifyOptions, type KeyValueStorage, MemoryStorage, type Platform, type PublicEntitlement, type PurchaseResult, SDK_NAME, SDK_VERSION };
|
package/dist/index.js
CHANGED
|
@@ -78,7 +78,7 @@ function typeMapForStatus(status) {
|
|
|
78
78
|
|
|
79
79
|
// src/http.ts
|
|
80
80
|
var SDK_NAME = "@cross-deck/web";
|
|
81
|
-
var SDK_VERSION = "0.1.
|
|
81
|
+
var SDK_VERSION = "0.1.1";
|
|
82
82
|
var DEFAULT_BASE_URL = "https://api.cross-deck.com/v1";
|
|
83
83
|
var HttpClient = class {
|
|
84
84
|
constructor(config) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/http.ts","../src/identity.ts","../src/entitlement-cache.ts","../src/event-queue.ts","../src/storage.ts","../src/crossdeck.ts"],"sourcesContent":["/**\n * @cross-deck/web — public entry point.\n *\n * The default export is a singleton `Crossdeck` instance. Most apps want\n * exactly one client; instantiate `CrossdeckClient` directly if you need\n * isolated instances (e.g. one per tenant in a multi-tenant SaaS shell).\n */\n\nexport { Crossdeck, CrossdeckClient } from \"./crossdeck\";\nexport { CrossdeckError } from \"./errors\";\nexport { MemoryStorage } from \"./storage\";\nexport { SDK_NAME, SDK_VERSION, DEFAULT_BASE_URL } from \"./http\";\n\nexport type {\n CrossdeckOptions,\n IdentifyOptions,\n EventProperties,\n KeyValueStorage,\n PublicEntitlement,\n EntitlementsListResponse,\n AliasResult,\n PurchaseResult,\n HeartbeatResponse,\n Diagnostics,\n Environment,\n Platform,\n AuditRail,\n} from \"./types\";\nexport type { CrossdeckErrorType, CrossdeckErrorPayload } from \"./errors\";\n","/**\n * Stripe-style error wrapper for @cross-deck/web.\n *\n * Mirrors the wire shape returned by the v1 backend (see\n * backend/src/api/v1-errors.ts) so SDK consumers can `catch`\n * with consistent fields:\n *\n * try {\n * await crossdeck.identify(\"user_847\");\n * } catch (err) {\n * if (err instanceof CrossdeckError && err.code === \"invalid_api_key\") {\n * // ...\n * }\n * }\n */\n\nexport type CrossdeckErrorType =\n | \"authentication_error\"\n | \"permission_error\"\n | \"invalid_request_error\"\n | \"rate_limit_error\"\n | \"internal_error\"\n | \"network_error\"\n | \"configuration_error\";\n\nexport interface CrossdeckErrorPayload {\n type: CrossdeckErrorType;\n code: string;\n message: string;\n /** Server-issued request ID. Echoed in support tickets. */\n requestId?: string;\n /** HTTP status code if the error came from an API response. */\n status?: number;\n}\n\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.1.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 * 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 type {\n AliasResult,\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 options: Required<Omit<CrossdeckOptions, \"storage\" | \"sdkVersion\">> & {\n sdkVersion: string;\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 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 };\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 this.state = {\n http,\n identity,\n entitlements,\n events,\n options: opts,\n developerUserId: null,\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 const event: QueuedEvent = {\n eventId: this.mintEventId(),\n name,\n timestamp: Date.now(),\n properties: properties ?? {},\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 this.state.identity.reset();\n this.state.entitlements.clear();\n this.state.events.reset();\n this.state.developerUserId = null;\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"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/http.ts","../src/identity.ts","../src/entitlement-cache.ts","../src/event-queue.ts","../src/storage.ts","../src/crossdeck.ts"],"sourcesContent":["/**\n * @cross-deck/web — public entry point.\n *\n * The default export is a singleton `Crossdeck` instance. Most apps want\n * exactly one client; instantiate `CrossdeckClient` directly if you need\n * isolated instances (e.g. one per tenant in a multi-tenant SaaS shell).\n */\n\nexport { Crossdeck, CrossdeckClient } from \"./crossdeck\";\nexport { CrossdeckError } from \"./errors\";\nexport { MemoryStorage } from \"./storage\";\nexport { SDK_NAME, SDK_VERSION, DEFAULT_BASE_URL } from \"./http\";\n\nexport type {\n CrossdeckOptions,\n IdentifyOptions,\n EventProperties,\n KeyValueStorage,\n PublicEntitlement,\n EntitlementsListResponse,\n AliasResult,\n PurchaseResult,\n HeartbeatResponse,\n Diagnostics,\n Environment,\n Platform,\n AuditRail,\n} from \"./types\";\nexport type { CrossdeckErrorType, CrossdeckErrorPayload } from \"./errors\";\n","/**\n * Stripe-style error wrapper for @cross-deck/web.\n *\n * Mirrors the wire shape returned by the v1 backend (see\n * backend/src/api/v1-errors.ts) so SDK consumers can `catch`\n * with consistent fields:\n *\n * try {\n * await crossdeck.identify(\"user_847\");\n * } catch (err) {\n * if (err instanceof CrossdeckError && err.code === \"invalid_api_key\") {\n * // ...\n * }\n * }\n */\n\nexport type CrossdeckErrorType =\n | \"authentication_error\"\n | \"permission_error\"\n | \"invalid_request_error\"\n | \"rate_limit_error\"\n | \"internal_error\"\n | \"network_error\"\n | \"configuration_error\";\n\nexport interface CrossdeckErrorPayload {\n type: CrossdeckErrorType;\n code: string;\n message: string;\n /** Server-issued request ID. Echoed in support tickets. */\n requestId?: string;\n /** HTTP status code if the error came from an API response. */\n status?: number;\n}\n\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.1.1\";\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 * 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 type {\n AliasResult,\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 options: Required<Omit<CrossdeckOptions, \"storage\" | \"sdkVersion\">> & {\n sdkVersion: string;\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 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 };\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 this.state = {\n http,\n identity,\n entitlements,\n events,\n options: opts,\n developerUserId: null,\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 const event: QueuedEvent = {\n eventId: this.mintEventId(),\n name,\n timestamp: Date.now(),\n properties: properties ?? {},\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 this.state.identity.reset();\n this.state.entitlements.clear();\n this.state.events.reset();\n this.state.developerUserId = null;\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"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACmCO,IAAM,iBAAN,MAAM,wBAAuB,MAAM;AAAA,EAMxC,YAAY,SAAgC;AAC1C,UAAM,QAAQ,OAAO;AACrB,SAAK,OAAO;AACZ,SAAK,OAAO,QAAQ;AACpB,SAAK,OAAO,QAAQ;AACpB,SAAK,YAAY,QAAQ;AACzB,SAAK,SAAS,QAAQ;AAEtB,WAAO,eAAe,MAAM,gBAAe,SAAS;AAAA,EACtD;AACF;AAOA,eAAsB,2BAA2B,KAAwC;AACvF,QAAM,YAAY,IAAI,QAAQ,IAAI,cAAc,KAAK;AACrD,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,IAAI,KAAK;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACA,QAAM,WAAY,MAA+E;AACjG,MAAI,YAAY,OAAO,SAAS,SAAS,YAAY,OAAO,SAAS,SAAS,UAAU;AACtF,WAAO,IAAI,eAAe;AAAA,MACxB,MAAM,SAAS;AAAA,MACf,MAAM,SAAS;AAAA,MACf,SAAS,SAAS,WAAW,QAAQ,IAAI,MAAM;AAAA,MAC/C,WAAW,SAAS,cAAc;AAAA,MAClC,QAAQ,IAAI;AAAA,IACd,CAAC;AAAA,EACH;AACA,SAAO,IAAI,eAAe;AAAA,IACxB,MAAM,iBAAiB,IAAI,MAAM;AAAA,IACjC,MAAM,QAAQ,IAAI,MAAM;AAAA,IACxB,SAAS,QAAQ,IAAI,MAAM,IAAI,IAAI,cAAc,EAAE,GAAG,KAAK;AAAA,IAC3D;AAAA,IACA,QAAQ,IAAI;AAAA,EACd,CAAC;AACH;AAEA,SAAS,iBAAiB,QAAoC;AAC5D,MAAI,WAAW,IAAK,QAAO;AAC3B,MAAI,WAAW,IAAK,QAAO;AAC3B,MAAI,WAAW,IAAK,QAAO;AAC3B,MAAI,UAAU,OAAO,SAAS,IAAK,QAAO;AAC1C,SAAO;AACT;;;AChFO,IAAM,WAAW;AACjB,IAAM,cAAc;AACpB,IAAM,mBAAmB;AAQzB,IAAM,aAAN,MAAiB;AAAA,EACtB,YAA6B,QAA0B;AAA1B;AAAA,EAA2B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWxD,MAAM,QACJ,QACA,MACA,UAA0E,CAAC,GAC/D;AACZ,UAAM,MAAM,KAAK,SAAS,MAAM,QAAQ,KAAK;AAE7C,UAAM,UAAkC;AAAA,MACtC,eAAe,UAAU,KAAK,OAAO,SAAS;AAAA,MAC9C,yBAAyB,GAAG,QAAQ,IAAI,KAAK,OAAO,UAAU;AAAA,MAC9D,QAAQ;AAAA,IACV;AACA,QAAI;AACJ,QAAI,QAAQ,SAAS,QAAW;AAC9B,cAAQ,cAAc,IAAI;AAC1B,iBAAW,KAAK,UAAU,QAAQ,IAAI;AAAA,IACxC;AAEA,QAAI;AACJ,QAAI;AACF,iBAAW,MAAM,MAAM,KAAK;AAAA,QAC1B;AAAA,QACA;AAAA,QACA,MAAM;AAAA,MACR,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,YAAM,IAAI,eAAe;AAAA,QACvB,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS,eAAe,QAAQ,IAAI,UAAU;AAAA,MAChD,CAAC;AAAA,IACH;AAEA,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,MAAM,2BAA2B,QAAQ;AAAA,IACjD;AAIA,QAAI,SAAS,WAAW,IAAK,QAAO;AAEpC,QAAI;AACF,aAAQ,MAAM,SAAS,KAAK;AAAA,IAC9B,SAAS,KAAK;AACZ,YAAM,IAAI,eAAe;AAAA,QACvB,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,QACT,WAAW,SAAS,QAAQ,IAAI,cAAc,KAAK;AAAA,QACnD,QAAQ,SAAS;AAAA,MACnB,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEQ,SAAS,MAAc,OAAoD;AACjF,UAAM,OAAO,KAAK,OAAO,QAAQ,QAAQ,QAAQ,EAAE;AACnD,UAAM,YAAY,KAAK,WAAW,GAAG,IAAI,OAAO,IAAI,IAAI;AACxD,QAAI,MAAM,OAAO;AACjB,QAAI,OAAO;AACT,YAAM,SAAS,IAAI,gBAAgB;AACnC,iBAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,KAAK,GAAG;AAC1C,YAAI,OAAO,MAAM,YAAY,EAAE,SAAS,EAAG,QAAO,OAAO,GAAG,CAAC;AAAA,MAC/D;AACA,YAAM,KAAK,OAAO,SAAS;AAC3B,UAAI,GAAI,SAAQ,IAAI,SAAS,GAAG,IAAI,MAAM,OAAO;AAAA,IACnD;AACA,WAAO;AAAA,EACT;AACF;;;ACtFA,IAAM,WAAW;AACjB,IAAM,aAAa;AAOZ,IAAM,gBAAN,MAAoB;AAAA,EAGzB,YACmB,SACA,QACjB;AAFiB;AACA;AAEjB,UAAM,SAAS;AAAA,MACb,MAAM,QAAQ,QAAQ,SAAS,QAAQ;AAAA,MACvC,QAAQ,QAAQ,QAAQ,SAAS,UAAU;AAAA,IAC7C;AACA,SAAK,QAAQ;AAAA,MACX,aAAa,OAAO,QAAQ,KAAK,gBAAgB;AAAA,MACjD,qBAAqB,OAAO;AAAA,IAC9B;AACA,QAAI,CAAC,OAAO,MAAM;AAChB,cAAQ,QAAQ,SAAS,UAAU,KAAK,MAAM,WAAW;AAAA,IAC3D;AAAA,EACF;AAAA;AAAA,EAGA,IAAI,cAAsB;AACxB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA;AAAA,EAGA,IAAI,sBAAqC;AACvC,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA;AAAA,EAGA,uBAAuB,OAAqB;AAC1C,SAAK,MAAM,sBAAsB;AACjC,SAAK,QAAQ,QAAQ,KAAK,SAAS,YAAY,KAAK;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAc;AACZ,SAAK,QAAQ,WAAW,KAAK,SAAS,QAAQ;AAC9C,SAAK,QAAQ,WAAW,KAAK,SAAS,UAAU;AAChD,SAAK,QAAQ;AAAA,MACX,aAAa,KAAK,gBAAgB;AAAA,MAClC,qBAAqB;AAAA,IACvB;AACA,SAAK,QAAQ,QAAQ,KAAK,SAAS,UAAU,KAAK,MAAM,WAAW;AAAA,EACrE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,kBAA0B;AAChC,UAAM,KAAK,KAAK,IAAI,EAAE,SAAS,EAAE;AACjC,UAAM,OAAO,YAAY,EAAE;AAC3B,WAAO,QAAQ,EAAE,GAAG,IAAI;AAAA,EAC1B;AACF;AAYO,SAAS,YAAY,OAAuB;AACjD,QAAM,WAAW;AACjB,QAAM,MAAgB,CAAC;AACvB,QAAM,YAAa,WAAgF;AACnG,MAAI,WAAW,iBAAiB;AAC9B,UAAM,MAAM,IAAI,WAAW,KAAK;AAChC,cAAU,gBAAgB,GAAG;AAC7B,aAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC9B,UAAI,KAAK,SAAS,IAAI,CAAC,IAAK,SAAS,MAAM,KAAK,GAAG;AAAA,IACrD;AAAA,EACF,OAAO;AACL,aAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC9B,UAAI,KAAK,SAAS,KAAK,MAAM,KAAK,OAAO,IAAI,SAAS,MAAM,CAAC,KAAK,GAAG;AAAA,IACvE;AAAA,EACF;AACA,SAAO,IAAI,KAAK,EAAE;AACpB;;;AC9FO,IAAM,mBAAN,MAAuB;AAAA,EAAvB;AACL,SAAQ,SAAS,oBAAI,IAAY;AACjC,SAAQ,MAA2B,CAAC;AACpC,SAAQ,cAAc;AAAA;AAAA;AAAA,EAGtB,WAAW,KAAsB;AAC/B,WAAO,KAAK,OAAO,IAAI,GAAG;AAAA,EAC5B;AAAA;AAAA,EAGA,OAA4B;AAC1B,WAAO,KAAK,IAAI,MAAM;AAAA,EACxB;AAAA;AAAA,EAGA,IAAI,YAAoB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,YAAY,cAAyC;AACnD,SAAK,MAAM,aAAa,MAAM;AAC9B,SAAK,SAAS,IAAI,IAAI,aAAa,OAAO,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC;AAC9E,SAAK,cAAc,KAAK,IAAI;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,QAAc;AACZ,SAAK,OAAO,MAAM;AAClB,SAAK,MAAM,CAAC;AACZ,SAAK,cAAc;AAAA,EACrB;AACF;;;ACnCA,IAAM,kBAAkB;AA+BjB,IAAM,aAAN,MAAiB;AAAA,EAQtB,YAA6B,KAAuB;AAAvB;AAP7B,SAAQ,SAAwB,CAAC;AACjC,SAAQ,UAAU;AAClB,SAAQ,WAAW;AACnB,SAAQ,cAAc;AACtB,SAAQ,YAA2B;AACnC,SAAQ,cAAmC;AAAA,EAEU;AAAA,EAErD,QAAQ,OAA0B;AAChC,SAAK,OAAO,KAAK,KAAK;AACtB,QAAI,KAAK,OAAO,SAAS,iBAAiB;AACxC,YAAM,WAAW,KAAK,OAAO,SAAS;AACtC,WAAK,OAAO,OAAO,GAAG,QAAQ;AAC9B,WAAK,WAAW;AAChB,WAAK,IAAI,SAAS,QAAQ;AAAA,IAC5B;AACA,QAAI,KAAK,OAAO,UAAU,KAAK,IAAI,WAAW;AAC5C,WAAK,KAAK,MAAM;AAAA,IAClB,OAAO;AACL,WAAK,kBAAkB;AAAA,IACzB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,QAAwC;AAC5C,QAAI,KAAK,OAAO,WAAW,EAAG,QAAO;AACrC,SAAK,iBAAiB;AAItB,UAAM,QAAQ,KAAK,OAAO,OAAO,CAAC;AAClC,SAAK,YAAY,MAAM;AAEvB,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,IAAI,KAAK,QAAwB,QAAQ,WAAW;AAAA,QAC5E,MAAM,EAAE,QAAQ,MAAM;AAAA,MACxB,CAAC;AACD,WAAK,cAAc,KAAK,IAAI;AAC5B,WAAK,YAAY;AACjB,WAAK,YAAY,MAAM;AACvB,aAAO;AAAA,IACT,SAAS,KAAK;AAGZ,WAAK,OAAO,QAAQ,GAAG,KAAK;AAC5B,WAAK,YAAY,MAAM;AACvB,WAAK,YAAY,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAEhE,WAAK,kBAAkB;AACvB,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA,EAGA,QAAc;AACZ,SAAK,iBAAiB;AACtB,SAAK,SAAS,CAAC;AACf,SAAK,UAAU;AACf,SAAK,WAAW;AAChB,SAAK,YAAY;AAAA,EACnB;AAAA,EAEA,WAA4B;AAC1B,WAAO;AAAA,MACL,UAAU,KAAK,OAAO;AAAA,MACtB,SAAS,KAAK;AAAA,MACd,UAAU,KAAK;AAAA,MACf,aAAa,KAAK;AAAA,MAClB,WAAW,KAAK;AAAA,IAClB;AAAA,EACF;AAAA,EAEQ,oBAA0B;AAChC,SAAK,iBAAiB;AACtB,UAAM,QAAQ,KAAK,IAAI,aAAa;AACpC,SAAK,cAAc,MAAM,MAAM;AAC7B,WAAK,KAAK,MAAM;AAAA,IAClB,GAAG,KAAK,IAAI,UAAU;AAAA,EACxB;AAAA,EAEQ,mBAAyB;AAC/B,QAAI,KAAK,aAAa;AACpB,WAAK,YAAY;AACjB,WAAK,cAAc;AAAA,IACrB;AAAA,EACF;AACF;AAEA,SAAS,iBAAiB,IAAgB,IAAwB;AAIhE,QAAM,KAAK,WAAW,IAAI,EAAE;AAC5B,MAAI,OAAQ,GAAyC,UAAU,YAAY;AACzE,QAAI;AACF,MAAC,GAAwC,MAAM;AAAA,IACjD,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO,MAAM,aAAa,EAAE;AAC9B;;;AChJO,IAAM,gBAAN,MAA+C;AAAA,EAA/C;AACL,SAAQ,QAAQ,oBAAI,IAAoB;AAAA;AAAA,EACxC,QAAQ,KAA4B;AAClC,WAAO,KAAK,MAAM,IAAI,GAAG,KAAK;AAAA,EAChC;AAAA,EACA,QAAQ,KAAa,OAAqB;AACxC,SAAK,MAAM,IAAI,KAAK,KAAK;AAAA,EAC3B;AAAA,EACA,WAAW,KAAmB;AAC5B,SAAK,MAAM,OAAO,GAAG;AAAA,EACvB;AACF;AAYO,SAAS,uBAAwC;AACtD,MAAI;AACF,UAAM,KAAM,WAAkD;AAC9D,QAAI,IAAI;AAEN,YAAM,QAAQ;AACd,SAAG,QAAQ,OAAO,GAAG;AACrB,SAAG,WAAW,KAAK;AACnB,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO,IAAI,cAAc;AAC3B;;;ACKO,IAAM,kBAAN,MAAsB;AAAA,EAAtB;AACL,SAAQ,QAA8B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOtC,MAAM,SAAiC;AACrC,QAAI,CAAC,QAAQ,aAAa,CAAC,QAAQ,UAAU,WAAW,SAAS,GAAG;AAClE,YAAM,IAAI,eAAe;AAAA,QACvB,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AAEA,UAAM,UAAU,QAAQ,WAAW,qBAAqB;AACxD,UAAM,kBAAkB,QAAQ,mBAAmB;AACnD,UAAM,OAAiC;AAAA,MACrC,WAAW,QAAQ;AAAA,MACnB,SAAS,QAAQ,WAAW;AAAA,MAC5B;AAAA,MACA,eAAe,QAAQ,iBAAiB;AAAA,MACxC,eAAe,QAAQ,iBAAiB;AAAA,MACxC,qBAAqB,QAAQ,uBAAuB;AAAA,MACpD,sBAAsB,QAAQ,wBAAwB;AAAA,MACtD,YAAY,QAAQ,cAAc;AAAA,IACpC;AAEA,UAAM,OAAO,IAAI,WAAW;AAAA,MAC1B,WAAW,KAAK;AAAA,MAChB,SAAS,KAAK;AAAA,MACd,YAAY,KAAK;AAAA,IACnB,CAAC;AACD,UAAM,mBAAmB,kBAAkB,UAAU,IAAI,cAAc;AACvE,UAAM,WAAW,IAAI,cAAc,kBAAkB,KAAK,aAAa;AACvE,UAAM,eAAe,IAAI,iBAAiB;AAC1C,UAAM,SAAS,IAAI,WAAW;AAAA,MAC5B;AAAA,MACA,WAAW,KAAK;AAAA,MAChB,YAAY,KAAK;AAAA,IACnB,CAAC;AAED,SAAK,QAAQ;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS;AAAA,MACT,iBAAiB;AAAA,IACnB;AAEA,QAAI,KAAK,eAAe;AAEtB,WAAK,KAAK,UAAU,EAAE,MAAM,MAAM,MAAS;AAAA,IAC7C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,SAAS,QAAgB,UAAkD;AAC/E,UAAM,IAAI,KAAK,eAAe;AAC9B,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,eAAe;AAAA,QACvB,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AACA,UAAM,SAAS,MAAM,EAAE,KAAK,QAAqB,QAAQ,mBAAmB;AAAA,MAC1E,MAAM,EAAE,QAAQ,aAAa,EAAE,SAAS,YAAY;AAAA,IACtD,CAAC;AACD,MAAE,SAAS,uBAAuB,OAAO,mBAAmB;AAC5D,MAAE,kBAAkB;AACpB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,kBAAgD;AACpD,UAAM,IAAI,KAAK,eAAe;AAC9B,UAAM,QAAQ,KAAK,oBAAoB;AACvC,UAAM,SAAS,MAAM,EAAE,KAAK;AAAA,MAC1B;AAAA,MACA;AAAA,MACA,EAAE,MAAM;AAAA,IACV;AACA,QAAI,OAAO,qBAAqB;AAC9B,QAAE,SAAS,uBAAuB,OAAO,mBAAmB;AAAA,IAC9D;AACA,MAAE,aAAa,YAAY,OAAO,IAAI;AACtC,WAAO,OAAO;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,WAAW,KAAsB;AAC/B,UAAM,IAAI,KAAK,eAAe;AAC9B,WAAO,EAAE,aAAa,WAAW,GAAG;AAAA,EACtC;AAAA;AAAA,EAGA,mBAAwC;AACtC,UAAM,IAAI,KAAK,eAAe;AAC9B,WAAO,EAAE,aAAa,KAAK;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,MAAc,YAAoC;AACtD,UAAM,IAAI,KAAK,eAAe;AAC9B,QAAI,CAAC,MAAM;AACT,YAAM,IAAI,eAAe;AAAA,QACvB,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AACA,UAAM,QAAqB;AAAA,MACzB,SAAS,KAAK,YAAY;AAAA,MAC1B;AAAA,MACA,WAAW,KAAK,IAAI;AAAA,MACpB,YAAY,cAAc,CAAC;AAAA,IAC7B;AACA,WAAO,OAAO,OAAO,KAAK,qBAAqB,CAAC;AAChD,MAAE,OAAO,QAAQ,KAAK;AAAA,EACxB;AAAA;AAAA,EAGA,MAAM,cAA6B;AACjC,UAAM,IAAI,KAAK,eAAe;AAC9B,UAAM,EAAE,OAAO,MAAM;AAAA,EACvB;AAAA;AAAA,EAGA,MAAM,cAAc,OAIQ;AAC1B,UAAM,IAAI,KAAK,eAAe;AAC9B,QAAI,CAAC,MAAM,uBAAuB;AAChC,YAAM,IAAI,eAAe;AAAA,QACvB,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AACA,UAAM,SAAS,MAAM,EAAE,KAAK,QAAwB,QAAQ,cAAc;AAAA,MACxE,MAAM,EAAE,MAAM,SAAS,GAAG,MAAM;AAAA,IAClC,CAAC;AACD,MAAE,SAAS,uBAAuB,OAAO,mBAAmB;AAC5D,MAAE,aAAa,YAAY,OAAO,YAAY;AAC9C,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,YAAwC;AAC5C,UAAM,IAAI,KAAK,eAAe;AAC9B,WAAO,MAAM,EAAE,KAAK,QAA2B,OAAO,gBAAgB;AAAA,EACxE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAc;AACZ,QAAI,CAAC,KAAK,MAAO;AACjB,SAAK,MAAM,SAAS,MAAM;AAC1B,SAAK,MAAM,aAAa,MAAM;AAC9B,SAAK,MAAM,OAAO,MAAM;AACxB,SAAK,MAAM,kBAAkB;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,cAA2B;AACzB,QAAI,CAAC,KAAK,OAAO;AACf,aAAO;AAAA,QACL,SAAS;AAAA,QACT,aAAa;AAAA,QACb,qBAAqB;AAAA,QACrB,iBAAiB;AAAA,QACjB,YAAY;AAAA,QACZ,SAAS;AAAA,QACT,cAAc,EAAE,OAAO,GAAG,aAAa,EAAE;AAAA,QACzC,QAAQ;AAAA,UACN,UAAU;AAAA,UACV,SAAS;AAAA,UACT,UAAU;AAAA,UACV,aAAa;AAAA,UACb,WAAW;AAAA,QACb;AAAA,MACF;AAAA,IACF;AACA,UAAM,IAAI,KAAK;AACf,WAAO;AAAA,MACL,SAAS;AAAA,MACT,aAAa,EAAE,SAAS;AAAA,MACxB,qBAAqB,EAAE,SAAS;AAAA,MAChC,iBAAiB,EAAE;AAAA,MACnB,YAAY,EAAE,QAAQ;AAAA,MACtB,SAAS,EAAE,QAAQ;AAAA,MACnB,cAAc;AAAA,QACZ,OAAO,EAAE,aAAa,KAAK,EAAE;AAAA,QAC7B,aAAa,EAAE,aAAa;AAAA,MAC9B;AAAA,MACA,QAAQ,EAAE,OAAO,SAAS;AAAA,IAC5B;AAAA,EACF;AAAA;AAAA,EAIQ,iBAAgC;AACtC,QAAI,CAAC,KAAK,OAAO;AACf,YAAM,IAAI,eAAe;AAAA,QACvB,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AACA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,sBAA0D;AAChE,UAAM,IAAI,KAAK,eAAe;AAC9B,QAAI,EAAE,SAAS,qBAAqB;AAClC,aAAO,EAAE,YAAY,EAAE,SAAS,oBAAoB;AAAA,IACtD;AACA,QAAI,EAAE,gBAAiB,QAAO,EAAE,QAAQ,EAAE,gBAAgB;AAC1D,WAAO,EAAE,aAAa,EAAE,SAAS,YAAY;AAAA,EAC/C;AAAA;AAAA,EAGQ,uBAGN;AACA,UAAM,IAAI,KAAK,eAAe;AAC9B,QAAI,EAAE,SAAS,qBAAqB;AAClC,aAAO,EAAE,qBAAqB,EAAE,SAAS,oBAAoB;AAAA,IAC/D;AACA,QAAI,EAAE,gBAAiB,QAAO,EAAE,iBAAiB,EAAE,gBAAgB;AACnE,WAAO,EAAE,aAAa,EAAE,SAAS,YAAY;AAAA,EAC/C;AAAA,EAEQ,cAAsB;AAC5B,UAAM,KAAK,KAAK,IAAI,EAAE,SAAS,EAAE;AACjC,WAAO,OAAO,EAAE,GAAG,YAAY,CAAC,CAAC;AAAA,EACnC;AACF;AAMO,IAAM,YAAY,IAAI,gBAAgB;","names":[]}
|
package/dist/index.mjs
CHANGED
|
@@ -46,7 +46,7 @@ function typeMapForStatus(status) {
|
|
|
46
46
|
|
|
47
47
|
// src/http.ts
|
|
48
48
|
var SDK_NAME = "@cross-deck/web";
|
|
49
|
-
var SDK_VERSION = "0.1.
|
|
49
|
+
var SDK_VERSION = "0.1.1";
|
|
50
50
|
var DEFAULT_BASE_URL = "https://api.cross-deck.com/v1";
|
|
51
51
|
var HttpClient = class {
|
|
52
52
|
constructor(config) {
|
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/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.1.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 * 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 type {\n AliasResult,\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 options: Required<Omit<CrossdeckOptions, \"storage\" | \"sdkVersion\">> & {\n sdkVersion: string;\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 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 };\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 this.state = {\n http,\n identity,\n entitlements,\n events,\n options: opts,\n developerUserId: null,\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 const event: QueuedEvent = {\n eventId: this.mintEventId(),\n name,\n timestamp: Date.now(),\n properties: properties ?? {},\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 this.state.identity.reset();\n this.state.entitlements.clear();\n this.state.events.reset();\n this.state.developerUserId = null;\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"],"mappings":"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","names":[]}
|
|
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/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.1.1\";\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 * 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 type {\n AliasResult,\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 options: Required<Omit<CrossdeckOptions, \"storage\" | \"sdkVersion\">> & {\n sdkVersion: string;\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 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 };\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 this.state = {\n http,\n identity,\n entitlements,\n events,\n options: opts,\n developerUserId: null,\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 const event: QueuedEvent = {\n eventId: this.mintEventId(),\n name,\n timestamp: Date.now(),\n properties: properties ?? {},\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 this.state.identity.reset();\n this.state.entitlements.clear();\n this.state.events.reset();\n this.state.developerUserId = null;\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"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cross-deck/web",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Crossdeck SDK for browsers and Node.js — verified subscriptions, entitlements, and product telemetry in one client.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://cross-deck.com",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "https://github.com/VistaApps-za/crossdeck.git"
|
|
10
|
-
"directory": "sdks/web"
|
|
9
|
+
"url": "https://github.com/VistaApps-za/crossdeck-web.git"
|
|
11
10
|
},
|
|
12
11
|
"main": "./dist/index.cjs",
|
|
13
12
|
"module": "./dist/index.mjs",
|