@cross-deck/node 1.2.0 → 1.3.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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/crossdeck-server.ts","../src/errors.ts","../src/event-validation.ts","../src/http.ts","../src/retry-policy.ts","../src/_rand.ts","../src/event-queue.ts","../src/breadcrumbs.ts","../src/stack-parser.ts","../src/error-capture.ts","../src/runtime-info.ts","../src/flush-on-exit.ts","../src/super-properties.ts","../src/entitlement-cache.ts","../src/debug.ts","../src/error-codes.ts","../src/webhooks.ts","../src/consent.ts"],"sourcesContent":["/**\n * @cross-deck/node — public entry point.\n *\n * Three USPs land on the server in v1.0.0:\n *\n * 1. Errors — `captureError` / `captureMessage` / `setTag` /\n * `setContext` / `addBreadcrumb` / `setErrorBeforeSend` +\n * auto-wired `uncaughtException` + `unhandledRejection` +\n * `globalThis.fetch` wrap for 5xx + network failures.\n *\n * 2. Analytics — `track()` / `ingest()` / `flush()`, durable queue\n * with retry + `Idempotency-Key`, flush-on-exit drain.\n * Super-properties (`register` / `unregister`) and group\n * analytics (`group`) for Mixpanel-style enrichment. Framework\n * adapters (Express / Lambda / Firebase) via the\n * `@cross-deck/node/auto-events` subpath.\n *\n * 3. Entitlements — `getEntitlements()` / `getCustomerEntitlements()`\n * with a per-customer TTL cache so `isEntitled()` is a memory\n * read after first warm. Webhook signature verification via\n * `verifyWebhookSignature()`.\n *\n * Cross-cutting: runtime enrichment (Node version, OS, region,\n * function name, instance ID) auto-attached to every event + error.\n * Lifecycle: `flush-on-exit` drains the queue on `beforeExit` +\n * SIGTERM + SIGINT so Cloud Functions cold-fires don't lose events.\n */\n\nexport { CrossdeckServer } from \"./crossdeck-server\";\nexport {\n CrossdeckError,\n CrossdeckAuthenticationError,\n CrossdeckPermissionError,\n CrossdeckValidationError,\n CrossdeckRateLimitError,\n CrossdeckNetworkError,\n CrossdeckInternalError,\n CrossdeckConfigurationError,\n makeCrossdeckError,\n} from \"./errors\";\nexport {\n DEFAULT_BASE_URL,\n DEFAULT_TIMEOUT_MS,\n SDK_NAME,\n SDK_VERSION,\n CROSSDECK_API_VERSION,\n} from \"./http\";\nexport { CROSSDECK_ERROR_CODES, getErrorCode, isCrossdeckErrorCode } from \"./error-codes\";\nexport { verifyWebhookSignature, signWebhookPayload } from \"./webhooks\";\nexport { scrubPii, scrubPiiFromProperties } from \"./consent\";\n\nexport type {\n AliasIdentityInput,\n AliasResult,\n AuditDecision,\n AuditEntry,\n CrossdeckServerOptions,\n Diagnostics,\n EntitlementMutationResult,\n EntitlementsListResponse,\n EntitlementStore,\n Environment,\n EventProperties,\n ForgetResult,\n GrantDuration,\n GrantEntitlementInput,\n HeartbeatResponse,\n IdentityHints,\n IdentifyOptions,\n IngestOptions,\n IngestResponse,\n PublicEntitlement,\n PurchaseResult,\n RequestOptions,\n RevokeEntitlementInput,\n ServerEvent,\n StoredEntitlements,\n SyncPurchaseInput,\n ErrorLevel,\n} from \"./types\";\nexport type { HttpRequestInfo, HttpResponseInfo, HttpRetriesConfig } from \"./http\";\nexport type { CrossdeckErrorPayload, CrossdeckErrorType } from \"./errors\";\nexport type { ErrorCodeEntry, CrossdeckErrorCode } from \"./error-codes\";\nexport type { Breadcrumb, BreadcrumbCategory, BreadcrumbLevel } from \"./breadcrumbs\";\nexport type { CapturedError, ErrorCaptureConfig } from \"./error-capture\";\nexport type { StackFrame } from \"./stack-parser\";\nexport type { RuntimeHost, RuntimeInfo } from \"./runtime-info\";\nexport type { GroupMembership } from \"./super-properties\";\nexport type { EntitlementsListener, EntitlementCacheOptions } from \"./entitlement-cache\";\nexport type { DebugSignal, DebugLogger, DebugContext } from \"./debug\";\nexport type { VerifyWebhookOptions } from \"./webhooks\";\n","/**\n * @cross-deck/node — `CrossdeckServer`, the orchestrator.\n *\n * v1.0.0 expands beyond the v0.1.0 thin HTTP client to ship the three\n * Crossdeck USPs on the server:\n *\n * 1. Errors — `captureError`, `captureMessage`, auto-wired\n * `process.on('uncaughtException')` + `process.on('unhandledRejection')`,\n * `globalThis.fetch` wrap, stack-frame parsing, breadcrumb\n * attachment, fingerprint dedup, rate-limit per fingerprint.\n * [USP 1 — landed v1.0.0]\n *\n * 2. Analytics — `track()` switches from sync-HTTP-per-event to\n * enqueue-and-batch via `EventQueue` (durable, retried, idempotent\n * per batch). `flush-on-exit` drains before Cloud Function / Lambda\n * teardown so events don't vanish silently.\n * [USP 1 ships queue + flush-on-exit; super-props + auto-events\n * arrive in USP 2]\n *\n * 3. Entitlements — TTL-cached `isEntitled()` so hot-path gates are\n * memory reads after first warm.\n * [USP 3 — pending]\n *\n * Cross-cutting: every event + error carries `runtime.*` enrichment\n * (Node version, OS, host, region, function name, instance ID) auto-\n * attached via `collectRuntimeInfo()`.\n *\n * The non-event endpoints (identify / aliasIdentity / forget /\n * getEntitlements / getCustomerEntitlements / syncPurchases /\n * grantEntitlement / revokeEntitlement / getAuditEntry) stay as direct\n * HTTP — they're transactional, not telemetry. Only `track()` changed\n * to queue-based.\n */\n\nimport { EventEmitter } from \"node:events\";\n\nimport { CrossdeckError } from \"./errors\";\nimport { validateEventProperties } from \"./event-validation\";\nimport {\n DEFAULT_BASE_URL,\n DEFAULT_TIMEOUT_MS,\n HttpClient,\n SDK_NAME,\n SDK_VERSION,\n} from \"./http\";\nimport { EventQueue, type BatchEnvelope, type QueuedEvent } from \"./event-queue\";\nimport { BreadcrumbBuffer, type Breadcrumb, type BreadcrumbCategory } from \"./breadcrumbs\";\nimport {\n DEFAULT_ERROR_CAPTURE,\n ErrorTracker,\n type CapturedError,\n type ErrorCaptureConfig,\n} from \"./error-capture\";\nimport { collectRuntimeInfo, runtimeInfoToProperties, type RuntimeInfo } from \"./runtime-info\";\nimport { FlushOnExit } from \"./flush-on-exit\";\nimport { SuperPropertyStore, type GroupMembership } from \"./super-properties\";\nimport { EntitlementCache, type EntitlementsListener } from \"./entitlement-cache\";\nimport { ConsoleDebugLogger, NullDebugLogger, findSensitivePropertyKeys, type DebugLogger } from \"./debug\";\nimport { mintId } from \"./_rand\";\nimport type {\n AliasIdentityInput,\n AliasResult,\n AuditEntry,\n AuditEntryResponse,\n CrossdeckServerOptions,\n Diagnostics,\n EntitlementMutationResult,\n EntitlementsListResponse,\n EntitlementStore,\n Environment,\n ErrorLevel,\n EventProperties,\n ForgetResult,\n GrantEntitlementInput,\n HeartbeatResponse,\n IdentityHints,\n IdentifyOptions,\n IngestOptions,\n IngestResponse,\n PublicEntitlement,\n PurchaseResult,\n RequestOptions,\n RevokeEntitlementInput,\n ServerEvent,\n StoredEntitlements,\n SyncPurchaseInput,\n} from \"./types\";\n\n/**\n * Typed event names + payloads emitted by `CrossdeckServer`. Caller\n * subscribes via the standard EventEmitter API:\n *\n * server.on(\"queue.flush_failed\", ({ error, attempt }) => { ... });\n * server.once(\"sdk.shutdown\", () => { ... });\n *\n * The typed `on` / `off` / `emit` overloads narrow the listener\n * arguments to the right shape. Untyped event names still work for\n * forward compat with any backend-side additions.\n */\nexport interface CrossdeckServerEvents {\n /** Fired once per batch on successful flush. */\n \"queue.flush_succeeded\": [info: { batchSize: number; durationMs: number }];\n /** Fired on every failed flush attempt. */\n \"queue.flush_failed\": [info: { error: CrossdeckError | string; attempt: number; nextRetryMs: number }];\n /** Fired when the queue drops oldest events due to HARD_BUFFER_CAP. */\n \"queue.dropped\": [info: { count: number }];\n /** Fired when the buffer changes size — used by callers wanting backpressure-aware tracking. */\n \"queue.buffer_changed\": [info: { size: number }];\n /** Fired when an error is captured (manual or auto). */\n \"error.captured\": [info: { fingerprint: string; kind: string; message: string }];\n /** Fired once after `getEntitlements()` warms the cache for a customer. */\n \"entitlements.warmed\": [info: { customerId: string; count: number }];\n /** Fired on `shutdown()` / `[Symbol.dispose]` / `[Symbol.asyncDispose]`. */\n \"sdk.shutdown\": [info: { reason: \"shutdown\" | \"dispose\" | \"asyncDispose\" }];\n}\n\nexport class CrossdeckServer extends EventEmitter {\n private readonly http: HttpClient;\n private readonly sdkVersion: string;\n private readonly baseUrl: string;\n private readonly appId: string | undefined;\n private readonly env: Environment;\n private readonly secretKeyPrefix: string;\n\n /**\n * Process-stable pseudo-anonymous ID. Used as the default identity\n * for `track()` / `captureError()` calls where the caller doesn't\n * supply one (e.g. an `uncaughtException` handler has no per-request\n * context). Stable for the SDK instance's lifetime so events from\n * the same process correlate.\n */\n private readonly processAnonymousId: string;\n\n private readonly runtime: RuntimeInfo;\n private readonly runtimeProperties: Record<string, unknown>;\n private readonly breadcrumbs: BreadcrumbBuffer;\n private readonly eventQueue: EventQueue;\n private readonly errorTracker: ErrorTracker | null;\n private readonly flushOnExit: FlushOnExit | null;\n private readonly superProps: SuperPropertyStore;\n private readonly entitlementCache: EntitlementCache;\n /**\n * Optional developer-supplied durable store for last-known-good\n * entitlements (Redis / their DB / a KV). `undefined` when not\n * configured — the SDK then has no cold-start durability on\n * serverless, which it states explicitly at boot.\n *\n * Touched ONLY from the async `getEntitlements()` — never from the\n * synchronous `isEntitled()`.\n */\n private readonly entitlementStore: EntitlementStore | null;\n private readonly debug: DebugLogger;\n\n /**\n * Alias map — `developerUserId` / `anonymousId` → canonical\n * `crossdeckCustomerId`. Populated by `getEntitlements()` so a\n * subsequent `isEntitled({ userId }, \"pro\")` resolves to the same\n * cache entry the prior `getEntitlements({ userId })` populated.\n *\n * Bounded by `MAX_CUSTOMER_ID_ALIASES` (matches the entitlement\n * cache's default max-customers for symmetry — if the underlying\n * cache entry was evicted, a stale alias is dead weight anyway).\n * Long-running multi-tenant servers handling a long tail of customers\n * are the failure mode this bound defends against.\n */\n private customerIdAliases = new Map<string, string>();\n\n /** Mutable error-state — modified by setTag / setContext / setErrorBeforeSend. */\n private errorContext: Record<string, unknown> = {};\n private errorTags: Record<string, string> = {};\n private errorBeforeSend: ((err: CapturedError) => CapturedError | null) | null = null;\n\n constructor(options: CrossdeckServerOptions) {\n super();\n if (!options.secretKey || !options.secretKey.startsWith(\"cd_sk_\")) {\n throw new CrossdeckError({\n type: \"configuration_error\",\n code: \"invalid_secret_key\",\n message: \"CrossdeckServer requires a secret key starting with cd_sk_.\",\n });\n }\n\n this.sdkVersion = options.sdkVersion ?? SDK_VERSION;\n this.appId = options.appId;\n this.baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n this.env = inferEnvFromKey(options.secretKey);\n this.secretKeyPrefix = maskSecretKey(options.secretKey);\n\n this.http = new HttpClient({\n secretKey: options.secretKey,\n baseUrl: this.baseUrl,\n sdkVersion: this.sdkVersion,\n timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n testMode: options.testMode,\n onRequest: options.onRequest,\n onResponse: options.onResponse,\n httpRetries: options.httpRetries,\n runtimeToken: options.runtimeToken,\n });\n\n this.processAnonymousId = mintId(\"anon_node\");\n\n this.runtime = collectRuntimeInfo({\n serviceName: options.serviceName,\n serviceVersion: options.serviceVersion,\n appVersion: options.appVersion,\n });\n this.runtimeProperties = runtimeInfoToProperties(this.runtime);\n\n this.breadcrumbs = new BreadcrumbBuffer(options.breadcrumbsMaxSize ?? 50);\n this.superProps = new SuperPropertyStore();\n this.entitlementCache = new EntitlementCache({\n ttlMs: options.entitlementCacheTtlMs ?? 60_000,\n staleAfterMs: options.entitlementStaleAfterMs,\n });\n this.entitlementStore = options.entitlementStore ?? null;\n this.debug = options.debug === true ? new ConsoleDebugLogger() : new NullDebugLogger();\n if (options.debug === true) this.debug.enabled = true;\n\n this.debug.emit(\n \"sdk.configured\",\n `Crossdeck server SDK connected. env=${this.env}, host=${this.runtime?.host ?? \"node\"}`,\n {\n env: this.env,\n sdkVersion: this.sdkVersion,\n secretKeyPrefix: this.secretKeyPrefix,\n },\n );\n\n this.eventQueue = new EventQueue({\n http: this.http,\n batchSize: options.eventFlushBatchSize ?? 20,\n intervalMs: options.eventFlushIntervalMs ?? 1500,\n envelope: (): BatchEnvelope => ({\n appId: this.appId,\n sdk: { name: SDK_NAME, version: this.sdkVersion },\n }),\n onDrop: (count) => {\n this.emit(\"queue.dropped\", { count });\n },\n onBufferChange: (size) => {\n this.emit(\"queue.buffer_changed\", { size });\n },\n onRetryScheduled: (info) => {\n this.emit(\"queue.flush_failed\", {\n error: info.lastError,\n attempt: info.consecutiveFailures,\n nextRetryMs: info.delayMs,\n });\n },\n onFirstFlushSuccess: () => {\n this.debug.emit(\"sdk.first_event_sent\", \"First batch landed.\");\n },\n });\n\n // Error capture. Default: enabled. Caller can opt out with `false`,\n // or override individual knobs with a partial object.\n if (options.errorCapture === false) {\n this.errorTracker = null;\n } else {\n const config: ErrorCaptureConfig =\n options.errorCapture && typeof options.errorCapture === \"object\"\n ? { ...DEFAULT_ERROR_CAPTURE, ...options.errorCapture }\n : { ...DEFAULT_ERROR_CAPTURE };\n this.errorTracker = new ErrorTracker({\n config,\n breadcrumbs: this.breadcrumbs,\n report: (err) => this.reportCapturedError(err),\n getContext: () => ({ ...this.errorContext }),\n getTags: () => ({ ...this.errorTags }),\n beforeSend: null, // wired via setErrorBeforeSend; ErrorTracker reads it through the live ref below\n isConsented: () => true,\n });\n // Indirect through `this.errorBeforeSend` so `setErrorBeforeSend`\n // takes effect on subsequent reports without re-installing the\n // tracker.\n const trackerOpts = (this.errorTracker as unknown as { opts: { beforeSend: ((e: CapturedError) => CapturedError | null) | null } }).opts;\n Object.defineProperty(trackerOpts, \"beforeSend\", {\n get: () => this.errorBeforeSend,\n configurable: true,\n });\n this.errorTracker.install();\n }\n\n // Flush-on-exit. Default: enabled. Critical for serverless — without\n // this, Cloud Functions / Lambda exit before HTTP completes and events\n // vanish silently.\n if (options.flushOnExit === false) {\n this.flushOnExit = null;\n } else {\n this.flushOnExit = new FlushOnExit({\n drain: () => this.eventQueue.flush().then(() => undefined),\n timeoutMs: options.flushOnExitTimeoutMs,\n });\n this.flushOnExit.install();\n }\n\n // Boot heartbeat. Fire-and-forget. Solves the cold-start\n // verification problem: the moment the customer's process boots\n // and constructs the SDK, we phone home — the dashboard row flips\n // LIVE within ~200ms without the caller having to add an explicit\n // `.heartbeat()` call. Serverless functions cold-start, construct\n // the SDK, fire the boot heartbeat, and the verification surface\n // can confirm install end-to-end on the very first inbound request.\n //\n // Side benefit: the secret key is validated at process boot rather\n // than at first event flush, so misconfigurations surface in logs\n // immediately rather than minutes later when the queue first drains.\n //\n // Opt-out via testMode (unit tests don't want network) or by\n // setting bootHeartbeat=false explicitly. Errors are swallowed so\n // a broken backend / bad key / firewall never crashes the caller's\n // process — heartbeat is diagnostic-grade, not load-bearing.\n if (options.testMode !== true && options.bootHeartbeat !== false) {\n // setImmediate lets the constructor return first so the caller's\n // code reaches the next statement before we kick off the network\n // call. Mirrors how Sentry's `Sentry.init()` schedules its boot\n // session.\n setImmediate(() => {\n void this.heartbeat().catch((err) => {\n this.debug.emit(\n \"sdk.boot_heartbeat_failed\",\n \"Boot heartbeat failed (non-fatal — events will still flush).\",\n { message: err instanceof Error ? err.message : String(err) },\n );\n });\n\n // Durability posture — boot telemetry + the honest serverless\n // warning. Scheduled alongside the boot heartbeat (same\n // `setImmediate`, same `testMode` / `bootHeartbeat` opt-out)\n // so the constructor returns before any phone-home runs and a\n // caller reading `diagnostics()` synchronously after `new\n // CrossdeckServer(...)` sees a clean buffer.\n this.emitBootTelemetry();\n });\n }\n }\n\n /**\n * Emit the one-time `sdk.boot` telemetry event and, when the runtime\n * is serverless with no `entitlementStore`, the honest \"no cold-start\n * durability\" warning.\n *\n * Why a `track()` event and not the heartbeat: `GET /v1/sdk/heartbeat`\n * carries no request body, so it cannot transport a structured\n * `durability` fact. The event pipeline can — every `track()` event\n * lands as an aggregatable document the backend can query, so\n * Crossdeck can compute fleet-wide \"% serverless-with-no-durable-\n * store\" from `sdk.boot` events (denominator = all `sdk.boot`,\n * numerator = those with `durability.coldStartDurable === false`).\n * The event rides the existing batched + retried + idempotent queue\n * and is drained by flush-on-exit, so it survives a serverless\n * teardown — it is NOT a local-only debug log.\n *\n * `isServerless` AND no store is the gap: a cold start begins with an\n * empty in-memory cache and a brief Crossdeck outage in that window\n * would read a paying customer as un-entitled. That gap is\n * unavoidable without a store — so the SDK STATES it (a\n * `sdk.no_durable_store` debug warning) rather than hiding it.\n *\n * Called once, from the deferred boot block — so it inherits the\n * `testMode` / `bootHeartbeat:false` opt-outs and never fires before\n * the constructor returns.\n */\n private emitBootTelemetry(): void {\n const isServerless = this.runtime.isServerless;\n const hasStore = this.entitlementStore !== null;\n // coldStartDurable: a long-lived host keeps the process (hence the\n // in-memory cache) warm between requests, so it is durable across a\n // brief outage without a store. A serverless host is durable across\n // a cold start ONLY with a store wired.\n const coldStartDurable = hasStore || !isServerless;\n\n // The honest warning — only in the actual gap case.\n if (isServerless && !hasStore) {\n this.debug.emit(\n \"sdk.no_durable_store\",\n `Running on a serverless host (${this.runtime.host}) with no entitlementStore. ` +\n \"The entitlement cache is in-memory only, so a cold start begins empty: \" +\n \"if Crossdeck is briefly unreachable during that window, isEntitled() can \" +\n \"read a paying customer as un-entitled. Wire `entitlementStore` (Redis / \" +\n \"your DB / a KV) to close this gap.\",\n { host: this.runtime.host, isServerless, durableStore: false },\n );\n }\n\n // One-time boot telemetry event. Fire-and-forget through track() —\n // best-effort, never throws into the constructor. `durability.*`\n // is the aggregatable fact the backend pivots on.\n try {\n this.track({\n name: \"sdk.boot\",\n anonymousId: this.processAnonymousId,\n properties: {\n \"durability.entitlementStore\": hasStore,\n \"durability.coldStartDurable\": coldStartDurable,\n \"durability.runtimeIsServerless\": isServerless,\n \"durability.runtimeHost\": this.runtime.host,\n \"durability.entitlementCacheTtlMs\": this.entitlementCache.ttl,\n },\n });\n } catch {\n // track() only throws on a missing event name — which cannot\n // happen here. Defensive: boot telemetry must never crash boot.\n }\n }\n\n // ============================================================\n // Identity — direct HTTP (transactional, not telemetry)\n // ============================================================\n\n async identify(\n userId: string,\n anonymousId: string,\n options?: IdentifyOptions & RequestOptions,\n ): Promise<AliasResult> {\n const { signal, timeoutMs, ...identifyOpts } = options ?? {};\n return this.aliasIdentity(\n { userId, anonymousId, ...identifyOpts },\n { signal, timeoutMs },\n );\n }\n\n async aliasIdentity(\n input: AliasIdentityInput,\n options?: RequestOptions,\n ): Promise<AliasResult> {\n if (!input.userId) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_user_id\",\n message: \"aliasIdentity requires a non-empty userId.\",\n });\n }\n if (!input.anonymousId) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_anonymous_id\",\n message: \"aliasIdentity requires a non-empty anonymousId.\",\n });\n }\n\n const traits = sanitizePropertyBag(input.traits, \"traits\");\n const body: Record<string, unknown> = {\n userId: input.userId,\n anonymousId: input.anonymousId,\n };\n if (input.email) body.email = input.email;\n if (traits && Object.keys(traits).length > 0) body.traits = traits;\n\n return this.http.request<AliasResult>(\"POST\", \"/identity/alias\", {\n body,\n signal: options?.signal,\n timeoutMs: options?.timeoutMs,\n });\n }\n\n async forget(hints: IdentityHints, options?: RequestOptions): Promise<ForgetResult> {\n const body = this.identityPayload(hints);\n return this.http.request<ForgetResult>(\"POST\", \"/identity/forget\", {\n body,\n signal: options?.signal,\n timeoutMs: options?.timeoutMs,\n });\n }\n\n // ============================================================\n // Entitlements — direct HTTP + TTL cache (v1.0.0+)\n //\n // `getEntitlements()` POSTs over the wire and populates the cache\n // under the response's canonical `crossdeckCustomerId`. Any\n // `userId` / `anonymousId` supplied as a hint is recorded as an\n // alias so a subsequent `isEntitled({ userId }, \"pro\")` resolves\n // to the same cache entry.\n // ============================================================\n\n /**\n * Fetch a customer's entitlements from Crossdeck and warm the cache.\n *\n * Durability — this is where last-known-good lives, NOT in the\n * synchronous `isEntitled()`:\n * - On a SUCCESSFUL fetch: the entitlement cache is populated and,\n * if an `entitlementStore` is configured, the result is persisted\n * to it (`await store.save(...)`). The cache + store now hold\n * server-confirmed truth.\n * - On a network FAILURE: the cache is marked refresh-failed for the\n * customer (so `diagnostics()` shows the staleness), then — if a\n * store is configured — last-known-good is loaded back from it\n * (`await store.load(...)`). If the store yields a snapshot, the\n * cache is populated from it and that snapshot is RETURNED as a\n * normal `EntitlementsListResponse` — a cold-start / outage no\n * longer fails a paying customer. If there is no store, or the\n * store is empty, the network error is rethrown unchanged so the\n * caller still sees the failure.\n *\n * The store is touched only here, inside the `await` that already\n * existed. `isEntitled()` remains a pure synchronous `Map` read.\n */\n async getEntitlements(\n hints: IdentityHints,\n options?: RequestOptions,\n ): Promise<EntitlementsListResponse> {\n let response: EntitlementsListResponse;\n try {\n response = await this.http.request<EntitlementsListResponse>(\"GET\", \"/entitlements\", {\n query: this.identityPayload(hints),\n signal: options?.signal,\n timeoutMs: options?.timeoutMs,\n });\n } catch (err) {\n // The refresh failed (Crossdeck unreachable / transient error).\n // Mark the customer stale so the staleness is visible via\n // diagnostics(), never a silent unbounded window.\n const failedCustomerId = this.resolveFailedRefreshCustomerId(hints);\n if (failedCustomerId) {\n this.entitlementCache.markRefreshFailed(failedCustomerId);\n }\n // Cold-start / outage fallback: try the durable store for last-\n // known-good. A hit repopulates the cache and is returned as a\n // normal response — the paying customer keeps access.\n const recovered = await this.loadEntitlementsFromStore(hints);\n if (recovered) {\n const recoveredResponse: EntitlementsListResponse = {\n object: \"list\",\n data: recovered.entitlements,\n crossdeckCustomerId: recovered.crossdeckCustomerId,\n env: recovered.env,\n };\n this.populateEntitlementCache(hints, recoveredResponse);\n // populateEntitlementCache → setForCustomer CLEARS the stale\n // flag (it treats a populate as a successful refresh). But this\n // was an OUTAGE fallback, not a fresh server read — Crossdeck is\n // still down. Re-mark the customer stale so diagnostics() keeps\n // showing the outage; the next genuinely-successful\n // getEntitlements() clears it for real.\n this.entitlementCache.markRefreshFailed(recovered.crossdeckCustomerId);\n this.debug.emit(\n \"sdk.entitlement_store_recovered\",\n `Crossdeck unreachable — served ${recovered.crossdeckCustomerId} from the durable store ` +\n `(${recovered.entitlements.length} entitlement(s), last refreshed ` +\n `${new Date(recovered.savedAt).toISOString()}).`,\n {\n customerId: recovered.crossdeckCustomerId,\n savedAt: recovered.savedAt,\n error: err instanceof Error ? err.message : String(err),\n },\n );\n return recoveredResponse;\n }\n // No store, or the store had nothing — the caller still sees the\n // failure. If the cache was previously warm for this customer it\n // keeps serving its own last-known-good via the synchronous\n // isEntitled(); the customer is now flagged stale (visible in\n // diagnostics()), which this signal makes explicit too.\n if (failedCustomerId && this.entitlementCache.isStale(failedCustomerId)) {\n this.debug.emit(\n \"sdk.entitlement_cache_stale\",\n `Crossdeck unreachable — entitlement cache for ${failedCustomerId} is now stale. ` +\n (this.entitlementStore\n ? \"No durable snapshot was available to recover from.\"\n : \"No entitlementStore is configured, so there is no durable fallback.\") +\n \" isEntitled() keeps serving last-known-good; staleness is visible in diagnostics().\",\n {\n customerId: failedCustomerId,\n durableStore: this.entitlementStore !== null,\n error: err instanceof Error ? err.message : String(err),\n },\n );\n }\n throw err;\n }\n this.populateEntitlementCache(hints, response);\n // Persist the fresh result so a future cold start / outage can\n // recover it. Best-effort — a store write failure must not fail an\n // otherwise-successful fetch.\n await this.saveEntitlementsToStore(hints, response);\n return response;\n }\n\n async getCustomerEntitlements(\n customerId: string,\n options?: RequestOptions,\n ): Promise<EntitlementsListResponse> {\n if (!customerId) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_customer_id\",\n message: \"getCustomerEntitlements requires a customerId.\",\n });\n }\n const response = await this.http.request<EntitlementsListResponse>(\n \"GET\",\n `/server/customers/${encodeURIComponent(customerId)}/entitlements`,\n {\n signal: options?.signal,\n timeoutMs: options?.timeoutMs,\n },\n );\n this.populateEntitlementCache({ customerId }, response);\n return response;\n }\n\n /**\n * Synchronous entitlement check. Returns `true` iff the customer\n * has the entitlement AND the cache entry is fresh (within\n * `entitlementCacheTtlMs`, default 60s). Returns `false` when the\n * cache is cold or expired.\n *\n * The hint can be any combination of `customerId` / `userId` /\n * `anonymousId`. After `getEntitlements({ userId })` populates the\n * cache, subsequent `isEntitled({ userId }, \"pro\")` calls within\n * TTL are memory reads (no HTTP). The \"warm cache\" pattern that\n * makes hot-path entitlement gates cheap.\n *\n * await server.getEntitlements({ userId }); // warm\n * if (server.isEntitled({ userId }, \"pro\")) { // synchronous\n * // ...\n * }\n *\n * Caller is responsible for re-warming after TTL elapses. The cache\n * does NOT auto-refresh on read (would block the hot path).\n */\n isEntitled(hint: IdentityHints | string, key: string): boolean {\n const customerId = this.resolveCacheCustomerId(hint);\n if (!customerId) return false;\n const result = this.entitlementCache.isEntitled(customerId, key);\n if (result) {\n this.debug.emit(\"sdk.entitlement_cache_used\", `Cache hit for ${customerId}/${key}.`);\n }\n return result;\n }\n\n /**\n * Snapshot of the customer's cached entitlements. Returns `[]` when\n * the cache is cold or expired. Same hint resolution as\n * `isEntitled()`.\n */\n listEntitlements(hint: IdentityHints | string): PublicEntitlement[] {\n const customerId = this.resolveCacheCustomerId(hint);\n if (!customerId) return [];\n return this.entitlementCache.list(customerId);\n }\n\n /**\n * Subscribe to entitlement-cache mutations. Listener fires after\n * `getEntitlements()` populates the cache or `shutdown()` clears\n * it. Returns an idempotent unsubscribe function.\n *\n * Used by callers that want to react to entitlement changes (e.g.\n * a websocket layer notifying connected clients of plan upgrades).\n * Listener errors are swallowed — surfaced via\n * `diagnostics().entitlements.listenerErrors`.\n */\n onEntitlementsChange(listener: EntitlementsListener): () => void {\n return this.entitlementCache.subscribe(listener);\n }\n\n // ============================================================\n // Events — queue-based track(); immediate ingest() for bulk imports\n // ============================================================\n\n /**\n * Queue an event for batched delivery. Returns synchronously — the\n * HTTP round-trip happens in the background.\n *\n * Behaviour parity with `@cross-deck/web`'s `track()`:\n * - Synchronous return, void.\n * - Throws sync on `missing_event_name`.\n * - Property bag sanitised through `validateEventProperties`.\n * - Runtime info (`runtime.*`) auto-merged into every event's\n * properties. Caller-supplied properties win on key collision.\n * - Breadcrumb auto-emitted (unless the name starts with `error.`,\n * which would cause a cycle).\n *\n * Differences from `@cross-deck/web`:\n * - Single-argument signature `track(event)` instead of\n * `track(name, properties)` — the Node wire shape needs the full\n * `ServerEvent` (identity hint, optional level + tags + categoryTags).\n * - Auto-fills `anonymousId` with `this.processAnonymousId` when no\n * identity hint is supplied. A captureError from\n * `uncaughtException` has no per-request context; without the\n * auto-fill, the event would be rejected at queue enqueue.\n */\n track(event: ServerEvent): void {\n if (!event.name) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_event_name\",\n message: \"track(event) requires a non-empty event.name.\",\n });\n }\n\n const sanitized = sanitizePropertyBag(event.properties, \"event properties\") ?? {};\n\n if (this.debug.enabled) {\n const flagged = findSensitivePropertyKeys(sanitized);\n if (flagged.length > 0) {\n this.debug.emit(\n \"sdk.sensitive_property_warning\",\n `Event \"${event.name}\" has potentially sensitive property names: ${flagged.join(\", \")}. Crossdeck is privacy-first — avoid sending PII unless intentional.`,\n { eventName: event.name, flagged },\n );\n }\n }\n\n // Enrichment order (parity with web SDK):\n // 1. Runtime info (auto-detected)\n // 2. Super-properties (registered via server.register(...))\n // 3. Group memberships → `$groups.<type>: id` (server.group(...))\n // 4. Caller-supplied properties (sanitised — most authoritative)\n //\n // Caller wins on key collision so a developer-set value overrides\n // anything the SDK auto-attached.\n const properties: EventProperties = {\n ...this.runtimeProperties,\n ...this.superProps.getSuperProperties(),\n ...sanitized,\n };\n const groupIds = this.superProps.getGroupIds();\n if (Object.keys(groupIds).length > 0 && properties.$groups === undefined) {\n properties.$groups = groupIds;\n }\n\n const identity = this.resolveIdentity(event);\n\n const queued: QueuedEvent = {\n eventId: event.eventId ?? mintId(\"evt\", 8),\n name: event.name,\n timestamp: event.timestamp ?? Date.now(),\n properties,\n ...identity,\n };\n if (event.level !== undefined) queued.level = event.level;\n if (event.tags !== undefined) queued.tags = event.tags;\n if (event.categoryTags !== undefined) queued.categoryTags = event.categoryTags;\n\n this.eventQueue.enqueue(queued);\n\n if (!event.name.startsWith(\"error.\")) {\n this.breadcrumbs.add({\n timestamp: queued.timestamp,\n category: categoryFor(event.name),\n message: event.name,\n data: sanitized,\n });\n }\n }\n\n /**\n * Immediate POST of one or more events. For bulk imports / replay\n * scenarios where the caller wants synchronous confirmation. Bypasses\n * the queue — no batching, no auto-fill of identity, no\n * runtime-enrichment.\n *\n * Use `track()` for the standard fire-and-forget telemetry path.\n * Use `ingest()` when you need:\n * - The IngestResponse synchronously.\n * - Strict per-event identity validation (no auto-fill).\n * - Caller-controlled idempotency key.\n */\n async ingest(events: ServerEvent[], options: IngestOptions = {}): Promise<IngestResponse> {\n if (!Array.isArray(events) || events.length === 0) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_events\",\n message: \"ingest requires at least one event.\",\n });\n }\n\n const normalized = events.map((event) => this.normalizeIngestEvent(event));\n const body: Record<string, unknown> = {\n events: normalized,\n sdk: { name: SDK_NAME, version: this.sdkVersion },\n };\n if (this.appId) body.appId = this.appId;\n\n return this.http.request<IngestResponse>(\"POST\", \"/events\", {\n body,\n idempotencyKey: options.idempotencyKey ?? mintId(\"batch\"),\n signal: options.signal,\n timeoutMs: options.timeoutMs,\n });\n }\n\n /**\n * Validate the secret key against the Crossdeck API and return the\n * resolved project + app metadata. Useful at boot to fail fast on a\n * misconfigured deployment — without this, a wrong secret key only\n * surfaces on the first event flush attempt, which may be minutes\n * after process start.\n *\n * const { projectId, appId, env, serverTime } = await server.heartbeat();\n *\n * Throws `CrossdeckError` on:\n * - `authentication_error` — secret key invalid / revoked\n * - `network_error` — couldn't reach the backend\n * - `request_timeout` — backend slow / unreachable\n *\n * Side effect: success records `(serverTime, clientTime)` for clock-\n * skew detection in `diagnostics().clock` (Phase 2 — not yet exposed\n * in this SDK release but the data is captured).\n *\n * Not auto-called. Caller decides whether the trade-off (one extra\n * boot request + ~50ms p50 latency) is worth the early-failure\n * signal. For serverless cold-starts, it usually is — cheap\n * compared to the cost of a silent broken secret in production.\n */\n async heartbeat(options?: RequestOptions): Promise<HeartbeatResponse> {\n const result = await this.http.request<HeartbeatResponse>(\"GET\", \"/sdk/heartbeat\", {\n signal: options?.signal,\n timeoutMs: options?.timeoutMs,\n });\n return result;\n }\n\n /**\n * Drain the event queue. Resolves when the in-flight batch completes\n * (success or failure). On failure, events stay queued for the next\n * scheduled retry — the resolved promise does NOT throw.\n *\n * Typical callers:\n * - End of a Lambda handler: `await server.flush()` before return\n * so events land before the platform freezes the process.\n * - Express server shutdown: `await server.flush()` inside the\n * SIGTERM handler.\n * - Tests: drain between assertions.\n *\n * Idempotent — flush on an empty queue is a no-op.\n */\n async flush(): Promise<void> {\n await this.eventQueue.flush();\n }\n\n async syncPurchases(\n input: SyncPurchaseInput,\n options?: RequestOptions,\n ): Promise<PurchaseResult> {\n if (!input.signedTransactionInfo) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_signed_transaction_info\",\n message: \"syncPurchases requires a signedTransactionInfo string.\",\n });\n }\n return this.http.request<PurchaseResult>(\"POST\", \"/purchases/sync\", {\n body: { rail: input.rail ?? \"apple\", ...input },\n signal: options?.signal,\n timeoutMs: options?.timeoutMs,\n });\n }\n\n // ============================================================\n // Manual entitlement controls + audit — direct HTTP\n // ============================================================\n\n async grantEntitlement(\n input: GrantEntitlementInput,\n options?: RequestOptions,\n ): Promise<EntitlementMutationResult> {\n if (!input.customerId) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_customer_id\",\n message: \"grantEntitlement requires a customerId.\",\n });\n }\n\n return this.http.request<EntitlementMutationResult>(\n \"POST\",\n `/server/customers/${encodeURIComponent(input.customerId)}/grant`,\n {\n body: {\n entitlementKey: input.entitlementKey,\n duration: input.duration,\n reason: input.reason,\n },\n signal: options?.signal,\n timeoutMs: options?.timeoutMs,\n },\n );\n }\n\n /**\n * Grant multiple entitlements in one logical call. Backend lacks a\n * bulk endpoint today, so this is a client-side fan-out — each\n * grant fires a separate request. Results return as a\n * settled-promise array so partial failures don't drop the rest:\n * the caller decides how to handle each `{ ok, value }` /\n * `{ ok: false, error }` entry.\n *\n * Use for ops sweeps (e.g. \"grant the entire `pro` tier a one-time\n * `pro_q1_bonus` entitlement\"). The bounded concurrency (default\n * `maxConcurrency: 5`) avoids hammering the backend; the rate-\n * limit policy on the server still kicks in if needed.\n */\n async bulkGrantEntitlement(\n grants: GrantEntitlementInput[],\n options?: RequestOptions & { maxConcurrency?: number },\n ): Promise<Array<{ input: GrantEntitlementInput; ok: true; value: EntitlementMutationResult } | { input: GrantEntitlementInput; ok: false; error: CrossdeckError }>> {\n return runBulkOperation(grants, options?.maxConcurrency ?? 5, (input) =>\n this.grantEntitlement(input, { signal: options?.signal, timeoutMs: options?.timeoutMs }),\n );\n }\n\n async revokeEntitlement(\n input: RevokeEntitlementInput,\n options?: RequestOptions,\n ): Promise<EntitlementMutationResult> {\n if (!input.customerId) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_customer_id\",\n message: \"revokeEntitlement requires a customerId.\",\n });\n }\n\n return this.http.request<EntitlementMutationResult>(\n \"POST\",\n `/server/customers/${encodeURIComponent(input.customerId)}/revoke`,\n {\n body: {\n entitlementKey: input.entitlementKey,\n reason: input.reason,\n },\n signal: options?.signal,\n timeoutMs: options?.timeoutMs,\n },\n );\n }\n\n /**\n * Revoke multiple entitlements in one logical call. Same\n * settled-array contract as `bulkGrantEntitlement` — see that\n * doc for behaviour notes.\n */\n async bulkRevokeEntitlement(\n revokes: RevokeEntitlementInput[],\n options?: RequestOptions & { maxConcurrency?: number },\n ): Promise<Array<{ input: RevokeEntitlementInput; ok: true; value: EntitlementMutationResult } | { input: RevokeEntitlementInput; ok: false; error: CrossdeckError }>> {\n return runBulkOperation(revokes, options?.maxConcurrency ?? 5, (input) =>\n this.revokeEntitlement(input, { signal: options?.signal, timeoutMs: options?.timeoutMs }),\n );\n }\n\n async getAuditEntry(eventId: string, options?: RequestOptions): Promise<AuditEntry> {\n if (!eventId) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_event_id\",\n message: \"getAuditEntry requires an eventId.\",\n });\n }\n\n const result = await this.http.request<AuditEntryResponse>(\n \"GET\",\n `/server/audit/${encodeURIComponent(eventId)}`,\n {\n signal: options?.signal,\n timeoutMs: options?.timeoutMs,\n },\n );\n return result.data;\n }\n\n // ============================================================\n // USP 1 — Error capture public surface\n // ============================================================\n\n /**\n * Manually capture an error from a try/catch block.\n *\n * try { … } catch (err) {\n * server.captureError(err, { context: { jobId }, tags: { flow: \"checkout\" } });\n * }\n *\n * The error ships through the same event queue analytics rides on\n * (retried, idempotent, runtime-enriched). Returns silently — never\n * throws, even if error capture is disabled.\n */\n captureError(\n error: unknown,\n options?: { context?: Record<string, unknown>; tags?: Record<string, string>; level?: ErrorLevel },\n ): void {\n if (!this.errorTracker) return;\n this.errorTracker.captureError(error, options);\n }\n\n /**\n * Capture a non-error signal as an issue. Sentry's `captureMessage`\n * pattern — for \"we hit the deprecated code path\" / \"soft-warning\n * triggered\" signals where there's no Error to throw.\n */\n captureMessage(message: string, level: ErrorLevel = \"info\"): void {\n if (!this.errorTracker) return;\n this.errorTracker.captureMessage(message, level);\n }\n\n /**\n * Attach a tag to every subsequent error report. Sentry pattern.\n * Tags are flat string key/value (queryable in the dashboard);\n * use `setContext()` for structured blobs.\n */\n setTag(key: string, value: string): void {\n this.errorTags[key] = value;\n }\n\n /** Bulk-set tags. Merges with existing tags. */\n setTags(tags: Record<string, string>): void {\n Object.assign(this.errorTags, tags);\n }\n\n /**\n * Attach a structured context blob to every subsequent error report.\n * Unlike tags (flat key/value), context is a named bag of arbitrary\n * JSON-serialisable data.\n *\n * server.setContext(\"cart\", { items: 3, total: 42.99 });\n */\n setContext(name: string, data: Record<string, unknown>): void {\n this.errorContext[name] = data;\n }\n\n /**\n * Add a custom breadcrumb to the rolling buffer. The last 50\n * breadcrumbs are attached to every subsequent error report —\n * \"what was the request doing right before things broke.\"\n */\n addBreadcrumb(crumb: Breadcrumb): void {\n this.breadcrumbs.add(crumb);\n }\n\n /**\n * Install a pre-send hook for errors. Return null to drop the report,\n * or a modified `CapturedError` to scrub fields. Sentry's\n * `beforeSend` pattern — the only place to add app-specific PII\n * redaction (auth tokens in URLs, etc.) before the report leaves the\n * process.\n *\n * The hook is called LAST, after rate-limit + sampling + path gates\n * already passed. A throwing hook falls back to the original error.\n */\n setErrorBeforeSend(hook: ((err: CapturedError) => CapturedError | null) | null): void {\n this.errorBeforeSend = hook;\n }\n\n // ============================================================\n // USP 2 — Super-properties + groups (Mixpanel pattern)\n // ============================================================\n\n /**\n * Register super-properties — every subsequent event carries these\n * keys on its `properties` bag automatically. Mixpanel pattern.\n *\n * server.register({ tenant: \"acme\", plan: \"pro\" });\n * server.track({ name: \"paywall_shown\", developerUserId: userId });\n * // ^ event carries `tenant` + `plan` in properties\n *\n * Values that are `null` are deleted (the explicit \"stop tracking\n * this key\" idiom). Sanitised through `validateEventProperties` so\n * a `{ avatar: <Buffer> }` payload can't poison the queue.\n *\n * Returns a defensive snapshot of the resulting bag.\n *\n * **Multi-tenant servers — read carefully.** Super-properties are\n * PROCESS-SCOPED. In a single Node process handling requests for\n * many tenants (the common multi-tenant SaaS shape), calling\n * `server.register({ tenant: \"acme\" })` taints EVERY subsequent\n * event from that process — including ones serving tenant \"beta\".\n * That's almost never what you want.\n *\n * The right pattern for per-request properties is to pass them on\n * the `track()` call itself:\n *\n * server.track({\n * name: \"paywall_shown\",\n * developerUserId: req.user.id,\n * properties: { tenant: req.tenantId, plan: req.user.plan },\n * });\n *\n * Reserve `register()` for properties that genuinely apply to every\n * event from this process — e.g. service version, region, build\n * commit. For those, `runtime-info` already provides\n * `runtime.serviceVersion` etc. automatically.\n */\n register(properties: Record<string, unknown>): Record<string, unknown> {\n const validation = validateEventProperties(properties);\n const result = this.superProps.register(validation.properties);\n this.debug.emit(\n \"sdk.super_property_registered\",\n `Super-properties updated. ${Object.keys(validation.properties).length} key(s) merged.`,\n );\n return result;\n }\n\n /** Remove a single super-property key. Idempotent. */\n unregister(key: string): void {\n this.superProps.unregister(key);\n }\n\n /** Snapshot of the current super-property bag. */\n getSuperProperties(): Record<string, unknown> {\n return this.superProps.getSuperProperties();\n }\n\n /**\n * Associate the current SDK instance with a group (org, team,\n * account, plan). Mixpanel / Segment Group Analytics pattern.\n *\n * server.group(\"org\", \"acme_inc\");\n * server.group(\"team\", \"design\", { headcount: 12 });\n *\n * Once set, every subsequent event carries `$groups.<type>: id` on\n * its `properties` bag, enabling B2B dashboard pivots. Pass\n * `id: null` to clear a group membership.\n *\n * Group traits are sanitised through `validateEventProperties`.\n */\n group(type: string, id: string | null, traits?: Record<string, unknown>): void {\n if (!type) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_group_type\",\n message: \"group(type, id) requires a non-empty type.\",\n });\n }\n const sanitisedTraits = traits ? validateEventProperties(traits).properties : undefined;\n this.superProps.setGroup(type, id, sanitisedTraits);\n }\n\n /** Snapshot of current group memberships keyed by type. */\n getGroups(): Record<string, GroupMembership> {\n return this.superProps.getGroups();\n }\n\n // ============================================================\n // Diagnostics — for debugging + the dashboard's heartbeat row\n // ============================================================\n\n diagnostics(): Diagnostics {\n return {\n sdkVersion: this.sdkVersion,\n baseUrl: this.baseUrl,\n secretKeyPrefix: this.secretKeyPrefix,\n env: this.env,\n runtime: {\n nodeVersion: this.runtime.nodeVersion,\n platform: this.runtime.platform,\n hostname: this.runtime.hostname,\n host: this.runtime.host,\n region: this.runtime.region,\n serviceName: this.runtime.serviceName,\n serviceVersion: this.runtime.serviceVersion,\n instanceId: this.runtime.instanceId,\n },\n entitlements: {\n count: this.entitlementCache.customerCount,\n lastUpdated: this.entitlementCache.lastUpdated,\n ttlMs: this.entitlementCache.ttl,\n listenerErrors: this.entitlementCache.listenerErrors,\n staleCustomers: this.entitlementCache.staleCustomerCount,\n isStale: this.entitlementCache.isAnyStale,\n lastRefreshFailedAt: this.entitlementCache.lastRefreshFailedAt,\n durableStore: this.entitlementStore !== null,\n // Cold-start durable iff a store is wired, OR the host is\n // long-lived (the process, hence the in-memory cache, survives).\n coldStartDurable:\n this.entitlementStore !== null || !this.runtime.isServerless,\n },\n events: this.eventQueue.getStats(),\n errors: {\n sessionCount: this.errorTracker?.reportedCount ?? 0,\n fingerprintsTracked: this.errorTracker?.fingerprintsTracked ?? 0,\n handlersInstalled: this.errorTracker?.handlersInstalled ?? false,\n },\n };\n }\n\n /**\n * Tear down handlers and clear in-memory state. Tests + custom\n * lifecycle callers only. Production code should rely on\n * `flush-on-exit` instead.\n */\n shutdown(reason: \"shutdown\" | \"dispose\" | \"asyncDispose\" = \"shutdown\"): void {\n this.emit(\"sdk.shutdown\", { reason });\n this.errorTracker?.uninstall();\n this.flushOnExit?.uninstall();\n this.eventQueue.reset();\n this.breadcrumbs.clear();\n this.superProps.clear();\n this.entitlementCache.clear();\n this.customerIdAliases.clear();\n this.errorContext = {};\n this.errorTags = {};\n this.errorBeforeSend = null;\n // Drop all event listeners last — caller's `sdk.shutdown` listeners\n // had a chance to run above.\n this.removeAllListeners();\n }\n\n // ============================================================\n // Internals\n // ============================================================\n\n /**\n * Convert a `CapturedError` into a `ServerEvent` and push through\n * `track()`. Goes through the same queue / enrichment / breadcrumb\n * pipeline analytics events do.\n */\n // ============================================================\n // Typed EventEmitter overloads — narrowing for the common methods\n // ============================================================\n\n override on<K extends keyof CrossdeckServerEvents>(\n event: K,\n listener: (...args: CrossdeckServerEvents[K]) => void,\n ): this;\n override on(event: string | symbol, listener: (...args: unknown[]) => void): this;\n override on(event: string | symbol, listener: (...args: unknown[]) => void): this {\n return super.on(event, listener);\n }\n\n override once<K extends keyof CrossdeckServerEvents>(\n event: K,\n listener: (...args: CrossdeckServerEvents[K]) => void,\n ): this;\n override once(event: string | symbol, listener: (...args: unknown[]) => void): this;\n override once(event: string | symbol, listener: (...args: unknown[]) => void): this {\n return super.once(event, listener);\n }\n\n override off<K extends keyof CrossdeckServerEvents>(\n event: K,\n listener: (...args: CrossdeckServerEvents[K]) => void,\n ): this;\n override off(event: string | symbol, listener: (...args: unknown[]) => void): this;\n override off(event: string | symbol, listener: (...args: unknown[]) => void): this {\n return super.off(event, listener);\n }\n\n override emit<K extends keyof CrossdeckServerEvents>(\n event: K,\n ...args: CrossdeckServerEvents[K]\n ): boolean;\n override emit(event: string | symbol, ...args: unknown[]): boolean;\n override emit(event: string | symbol, ...args: unknown[]): boolean {\n return super.emit(event, ...args);\n }\n\n // ============================================================\n // Health + readiness + lifecycle\n // ============================================================\n\n /**\n * Synchronous readiness check — \"is the SDK in a state where it\n * should accept new traffic?\". Used by Kubernetes readiness probes\n * and backpressure-aware callers.\n *\n * Returns `false` if:\n * - The event queue is in a sustained retry storm\n * (`consecutiveFailures >= 5`).\n * - The event queue's buffered count is at >= 80% of HARD_BUFFER_CAP.\n *\n * Otherwise `true`. The default isn't \"perfectly healthy\" — the\n * SDK is happy to enqueue events even during transient flush\n * failures because the queue's retry path handles them. Only\n * sustained failure flips this to `false`.\n */\n isReady(): boolean {\n const stats = this.eventQueue.getStats();\n if (stats.consecutiveFailures >= 5) return false;\n if (stats.buffered >= 800) return false; // 80% of HARD_BUFFER_CAP\n return true;\n }\n\n /**\n * Async wait until `isReady()` returns true OR the timeout elapses.\n * Resolves `true` on ready, `false` on timeout. Polls every 50ms by\n * default — backpressure for callers writing high-volume servers.\n *\n * if (!(await server.awaitReady(2000))) {\n * // shed load — SDK is in a retry storm, don't queue more\n * }\n */\n async awaitReady(timeoutMs = 5000, pollIntervalMs = 50): Promise<boolean> {\n if (this.isReady()) return true;\n const start = Date.now();\n return new Promise<boolean>((resolve) => {\n const tick = (): void => {\n if (this.isReady()) {\n resolve(true);\n return;\n }\n if (Date.now() - start >= timeoutMs) {\n resolve(false);\n return;\n }\n const t = setTimeout(tick, pollIntervalMs);\n if (typeof t.unref === \"function\") {\n try {\n t.unref();\n } catch {\n // ignore\n }\n }\n };\n tick();\n });\n }\n\n /**\n * Snapshot for Kubernetes liveness + readiness probes. `healthy`\n * stays true unless the SDK is in a catastrophic state (which\n * currently can't happen without crashing the process). `ready`\n * matches `isReady()`.\n *\n * app.get(\"/healthz\", (_req, res) => {\n * const h = server.getHealth();\n * res.status(h.healthy ? 200 : 503).json(h);\n * });\n */\n getHealth(): {\n ready: boolean;\n healthy: boolean;\n bufferedEvents: number;\n inFlight: number;\n consecutiveFailures: number;\n lastFlushAt: number;\n lastError: string | null;\n errorHandlersInstalled: boolean;\n } {\n const stats = this.eventQueue.getStats();\n return {\n ready: this.isReady(),\n healthy: true,\n bufferedEvents: stats.buffered,\n inFlight: stats.inFlight,\n consecutiveFailures: stats.consecutiveFailures,\n lastFlushAt: stats.lastFlushAt,\n lastError: stats.lastError,\n errorHandlersInstalled: this.errorTracker?.handlersInstalled ?? false,\n };\n }\n\n /**\n * Sync disposal hook — runs when a `using` declaration exits scope\n * (TC39 explicit-resource-management, Node 20+ / TS 5.2+).\n *\n * using server = new CrossdeckServer({ ... });\n * // ... use server ...\n * // at end of block, server[Symbol.dispose]() runs automatically\n *\n * `Symbol.dispose` is synchronous so we can't await `flush()` here\n * — for that, use `await using` + `[Symbol.asyncDispose]()`. This\n * sync variant just calls `shutdown()` (handler cleanup +\n * in-memory state wipe).\n */\n [Symbol.dispose](): void {\n this.shutdown(\"dispose\");\n }\n\n /**\n * Async disposal hook — runs when an `await using` declaration\n * exits scope. Awaits `flush()` THEN runs `shutdown()`. Use this\n * variant when the caller needs the queue drained before exit\n * (the common case for serverless handlers).\n *\n * await using server = new CrossdeckServer({ ... });\n */\n async [Symbol.asyncDispose](): Promise<void> {\n try {\n await this.flush();\n } catch {\n // shutdown is best-effort\n }\n this.shutdown(\"asyncDispose\");\n }\n\n // ============================================================\n\n private reportCapturedError(captured: CapturedError): void {\n try {\n this.emit(\"error.captured\", {\n fingerprint: captured.fingerprint,\n kind: captured.kind,\n message: captured.message,\n });\n } catch {\n // listener errors don't break the report\n }\n const properties: EventProperties = {\n fingerprint: captured.fingerprint,\n level: captured.level,\n errorType: captured.errorType,\n message: captured.message,\n stack: captured.rawStack ?? undefined,\n frames: captured.frames,\n tags: captured.tags,\n context: captured.context,\n breadcrumbs: captured.breadcrumbs,\n http: captured.http,\n };\n for (const k of Object.keys(properties)) {\n if (properties[k] === undefined) delete properties[k];\n }\n this.track({\n name: captured.kind,\n timestamp: captured.timestamp,\n properties,\n level: captured.level,\n tags: captured.tags,\n });\n }\n\n /**\n * Populate the entitlement cache from a fresh server response.\n * Records aliases so `userId` / `anonymousId` hints supplied to\n * `getEntitlements()` resolve to the same cache entry on subsequent\n * `isEntitled({ userId }, ...)` calls.\n *\n * Bounds the alias map at MAX_CUSTOMER_ID_ALIASES — once full, the\n * oldest aliases (by insertion order) are evicted FIFO. Symmetric\n * with the entitlement cache's max-customers cap.\n */\n private populateEntitlementCache(\n hints: IdentityHints,\n response: EntitlementsListResponse,\n ): void {\n const customerId = response.crossdeckCustomerId;\n if (!customerId) return;\n this.entitlementCache.setForCustomer(customerId, response.data);\n if (hints.userId) this.touchAlias(hints.userId, customerId);\n if (hints.anonymousId) this.touchAlias(hints.anonymousId, customerId);\n this.debug.emit(\n \"sdk.entitlement_cache_warm\",\n `Entitlement cache warmed for ${customerId} (${response.data.length} entitlement(s)).`,\n );\n try {\n this.emit(\"entitlements.warmed\", {\n customerId,\n count: response.data.length,\n });\n } catch {\n // listener errors don't break the response path\n }\n }\n\n /**\n * Persist a successful entitlements fetch to the durable store, if\n * one is configured. No-op when there is no store.\n *\n * Saved under EVERY identity the caller might later look up by — the\n * canonical `crossdeckCustomerId` plus any `userId` / `anonymousId`\n * hint. The Node cache resolves a hint to a canonical ID via an\n * in-memory alias map; on a cold start that map is empty, so a\n * failure-path `load()` must be able to hit the store with the raw\n * hint the caller passed. Saving under all keys makes that work.\n *\n * Best-effort: a store `save()` that throws is swallowed (logged in\n * debug) — it weakens durability for that customer but must never\n * fail an otherwise-successful `getEntitlements()`.\n */\n private async saveEntitlementsToStore(\n hints: IdentityHints,\n response: EntitlementsListResponse,\n ): Promise<void> {\n if (!this.entitlementStore) return;\n const customerId = response.crossdeckCustomerId;\n if (!customerId) return;\n const snapshot: StoredEntitlements = {\n v: 1,\n crossdeckCustomerId: customerId,\n entitlements: response.data,\n env: response.env,\n savedAt: Date.now(),\n };\n // Distinct keys only — dedupe so we don't write the same blob twice\n // when e.g. the caller passed customerId === crossdeckCustomerId.\n const keys = new Set<string>([customerId]);\n if (hints.customerId) keys.add(hints.customerId);\n if (hints.userId) keys.add(hints.userId);\n if (hints.anonymousId) keys.add(hints.anonymousId);\n for (const key of keys) {\n try {\n await this.entitlementStore.save(key, snapshot);\n } catch (err) {\n this.debug.emit(\n \"sdk.entitlement_store_recovered\",\n `entitlementStore.save failed for key ${key} — durability weakened for this customer.`,\n { key, error: err instanceof Error ? err.message : String(err) },\n );\n }\n }\n }\n\n /**\n * Load last-known-good entitlements from the durable store on a\n * network-failure path. Returns the first snapshot found across the\n * caller's identity keys, or `null` if there is no store / no stored\n * snapshot / every read failed.\n *\n * Tries the canonical `customerId` hint first, then `userId`, then\n * `anonymousId` — the order callers most commonly key by. A corrupt\n * or wrong-shaped blob is treated as a miss (the store is developer-\n * supplied; the SDK validates rather than trusts).\n */\n private async loadEntitlementsFromStore(\n hints: IdentityHints,\n ): Promise<StoredEntitlements | null> {\n if (!this.entitlementStore) return null;\n const keys: string[] = [];\n if (hints.customerId) keys.push(hints.customerId);\n if (hints.userId) keys.push(hints.userId);\n if (hints.anonymousId) keys.push(hints.anonymousId);\n for (const key of keys) {\n let loaded: StoredEntitlements | null = null;\n try {\n loaded = await this.entitlementStore.load(key);\n } catch {\n // A throwing load() degrades to \"no durable copy for this key\".\n continue;\n }\n if (isValidStoredEntitlements(loaded)) return loaded;\n }\n return null;\n }\n\n /**\n * Resolve the customer ID to stamp a failed-refresh marker against.\n *\n * Prefers a canonical ID the cache already knows (so the marker lands\n * on the existing warm entry), then falls back to whatever raw hint\n * the caller supplied — on a true cold-start failure there is no\n * cache entry yet, and marking under the hint still makes \"we tried\n * for this customer and Crossdeck was down\" observable.\n */\n private resolveFailedRefreshCustomerId(hints: IdentityHints): string | null {\n const known = this.resolveCacheCustomerId(hints);\n if (known) return known;\n return hints.customerId ?? hints.userId ?? hints.anonymousId ?? null;\n }\n\n private touchAlias(alias: string, customerId: string): void {\n // Delete-and-reinsert so the alias moves to the end of insertion\n // order (LRU \"touch\").\n this.customerIdAliases.delete(alias);\n this.customerIdAliases.set(alias, customerId);\n while (this.customerIdAliases.size > MAX_CUSTOMER_ID_ALIASES) {\n const oldest = this.customerIdAliases.keys().next().value;\n if (oldest === undefined) break;\n this.customerIdAliases.delete(oldest);\n }\n }\n\n /**\n * Resolve any hint shape (canonical customerId / userId hint /\n * anonymousId hint / raw string) to a `crossdeckCustomerId` if we\n * have a cache entry for it.\n */\n private resolveCacheCustomerId(hint: IdentityHints | string): string | null {\n if (typeof hint === \"string\") {\n // String input is treated as a canonical customerId first, then\n // as an alias (a customer's developerUserId / anonymousId).\n if (this.entitlementCache.isFresh(hint)) return hint;\n return this.customerIdAliases.get(hint) ?? null;\n }\n if (hint.customerId) return hint.customerId;\n if (hint.userId) return this.customerIdAliases.get(hint.userId) ?? null;\n if (hint.anonymousId) return this.customerIdAliases.get(hint.anonymousId) ?? null;\n return null;\n }\n\n private identityPayload(hints: IdentityHints): Record<string, string> {\n const payload: Record<string, string> = {};\n if (typeof hints.customerId === \"string\" && hints.customerId) {\n payload.customerId = hints.customerId;\n }\n if (typeof hints.userId === \"string\" && hints.userId) {\n payload.userId = hints.userId;\n }\n if (typeof hints.anonymousId === \"string\" && hints.anonymousId) {\n payload.anonymousId = hints.anonymousId;\n }\n if (Object.keys(payload).length === 0) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_identity\",\n message: \"Provide at least one of customerId, userId, or anonymousId.\",\n });\n }\n return payload;\n }\n\n /**\n * Resolve event identity. Caller-supplied wins; falls back to\n * `processAnonymousId` so events from `captureError` /\n * uncaughtException always have at least one identity hint.\n */\n private resolveIdentity(event: ServerEvent): {\n developerUserId?: string;\n anonymousId?: string;\n crossdeckCustomerId?: string;\n } {\n const out: { developerUserId?: string; anonymousId?: string; crossdeckCustomerId?: string } = {};\n if (event.developerUserId) out.developerUserId = event.developerUserId;\n if (event.anonymousId) out.anonymousId = event.anonymousId;\n if (event.crossdeckCustomerId) out.crossdeckCustomerId = event.crossdeckCustomerId;\n if (!out.developerUserId && !out.anonymousId && !out.crossdeckCustomerId) {\n out.anonymousId = this.processAnonymousId;\n }\n return out;\n }\n\n /**\n * Strict normalisation for `ingest()` — no auto-fill of identity,\n * caller must supply at least one hint per event. Matches v0.1.0\n * behaviour for backward compatibility with bulk-import callers.\n */\n private normalizeIngestEvent(event: ServerEvent): ServerEvent {\n if (!event.name) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_event_name\",\n message: \"Each event requires a non-empty name.\",\n });\n }\n const hasIdentity =\n Boolean(event.developerUserId) ||\n Boolean(event.anonymousId) ||\n Boolean(event.crossdeckCustomerId);\n if (!hasIdentity) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_identity\",\n message:\n \"Each event requires at least one of developerUserId, anonymousId, or crossdeckCustomerId.\",\n });\n }\n const properties = sanitizePropertyBag(event.properties, \"event properties\");\n return {\n ...event,\n properties,\n eventId: event.eventId ?? mintId(\"evt\", 8),\n timestamp: event.timestamp ?? Date.now(),\n };\n }\n}\n\nfunction sanitizePropertyBag(\n input: Record<string, unknown> | undefined,\n fieldName: string,\n): Record<string, unknown> | undefined {\n if (input === undefined) return undefined;\n try {\n return validateEventProperties(input).properties;\n } catch {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"serialization_failed\",\n message: `${fieldName} could not be serialized.`,\n });\n }\n}\n\n/**\n * Validate a value read back from a developer-supplied\n * `EntitlementStore`. The store is external (Redis / a DB / a KV) and\n * may return a corrupt, partial, stale-schema or attacker-influenced\n * blob — the SDK validates the shape rather than trusting it. Anything\n * that fails is treated as a cache miss (the SDK then rethrows the\n * original network error), never a crash.\n *\n * Narrows to `StoredEntitlements`: a versioned blob with a non-empty\n * `crossdeckCustomerId`, an `entitlements` array, and a known `env`.\n * Individual entitlement objects are not deep-validated here — the\n * cache + `isEntitled()` already tolerate odd entries (a missing `key`\n * simply never matches), and `validUntil` is honoured at read time.\n */\nfunction isValidStoredEntitlements(value: unknown): value is StoredEntitlements {\n if (typeof value !== \"object\" || value === null) return false;\n const v = value as Record<string, unknown>;\n return (\n v.v === 1 &&\n typeof v.crossdeckCustomerId === \"string\" &&\n v.crossdeckCustomerId.length > 0 &&\n Array.isArray(v.entitlements) &&\n (v.env === \"production\" || v.env === \"sandbox\") &&\n typeof v.savedAt === \"number\"\n );\n}\n\n/**\n * Map an event name to a breadcrumb category. Mirrors the web SDK's\n * mapping so breadcrumb timelines in error reports look the same\n * regardless of which SDK emitted them.\n */\nfunction categoryFor(name: string): BreadcrumbCategory {\n if (name.startsWith(\"page.\") || name.startsWith(\"navigation.\")) return \"navigation\";\n if (name.startsWith(\"element.\") || name.startsWith(\"ui.click\")) return \"ui.click\";\n if (name.startsWith(\"http.\") || name === \"request.handled\") return \"http\";\n return \"custom\";\n}\n\n/**\n * Maximum number of `userId` / `anonymousId` → `crossdeckCustomerId`\n * aliases we track. Matches the entitlement cache's default\n * max-customers for symmetry.\n */\nconst MAX_CUSTOMER_ID_ALIASES = 10_000;\n\n/**\n * Infer environment from the secret-key prefix. Stripe pattern —\n * `cd_sk_live_*` means production, anything else is treated as sandbox\n * (fixture / test keys typically use `cd_sk_test_*` but bare\n * `cd_sk_*` from test fixtures also falls here).\n */\nfunction inferEnvFromKey(secretKey: string): Environment {\n return secretKey.startsWith(\"cd_sk_live_\") ? \"production\" : \"sandbox\";\n}\n\n/**\n * Bounded-concurrency promise-settle helper for bulk operations.\n * Returns one entry per input, in input order. Each entry is either\n * `{ ok: true, value }` or `{ ok: false, error }`. Never rejects —\n * partial failures don't drop the rest of the batch.\n */\nasync function runBulkOperation<TInput, TResult>(\n inputs: TInput[],\n maxConcurrency: number,\n op: (input: TInput) => Promise<TResult>,\n): Promise<Array<{ input: TInput; ok: true; value: TResult } | { input: TInput; ok: false; error: CrossdeckError }>> {\n const results: Array<\n | { input: TInput; ok: true; value: TResult }\n | { input: TInput; ok: false; error: CrossdeckError }\n > = new Array(inputs.length);\n let cursor = 0;\n const workers = new Array(Math.min(maxConcurrency, Math.max(1, inputs.length)))\n .fill(0)\n .map(async () => {\n while (true) {\n const index = cursor++;\n if (index >= inputs.length) return;\n const input = inputs[index]!;\n try {\n const value = await op(input);\n results[index] = { input, ok: true, value };\n } catch (err) {\n results[index] = {\n input,\n ok: false,\n error:\n err instanceof CrossdeckError\n ? err\n : new CrossdeckError({\n type: \"internal_error\",\n code: \"bulk_operation_failed\",\n message: err instanceof Error ? err.message : String(err),\n }),\n };\n }\n }\n });\n await Promise.all(workers);\n return results;\n}\n\n/**\n * Mask the secret key for safe display in diagnostics + logs. Stripe\n * pattern: keep the env-revealing prefix (`cd_sk_test_` / `cd_sk_live_`),\n * replace the middle with `****`, append the last 4 chars when the key\n * is long enough that those 4 chars don't overlap the prefix. Test\n * fixtures with short keys degrade gracefully to `prefix + ****` with\n * no tail.\n */\nfunction maskSecretKey(secretKey: string): string {\n const m = secretKey.match(/^cd_sk_(test|live)_/);\n const prefix = m ? m[0] : secretKey.slice(0, 11);\n const tail = secretKey.length > prefix.length + 4 ? secretKey.slice(-4) : \"\";\n return `${prefix}****${tail}`;\n}\n","export 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 /**\n * Error code. The canonical set is the `CrossdeckErrorCode` literal\n * union exported from `./error-codes` (derived from\n * `CROSSDECK_ERROR_CODES`). Typed as `string` here so the SDK can\n * still surface server-returned codes that aren't (yet) in the\n * catalogue without a wholesale recompile of every consumer.\n *\n * For type-safe code comparisons in caller code, use:\n * `import { CrossdeckErrorCode, isCrossdeckErrorCode } from \"@cross-deck/node\"`\n * `if (isCrossdeckErrorCode(err.code) && err.code === \"webhook_invalid_signature\") {}`\n */\n code: string;\n message: string;\n requestId?: string;\n status?: number;\n retryAfterMs?: number;\n}\n\nexport class CrossdeckError extends Error {\n public readonly type: CrossdeckErrorType;\n public readonly code: string;\n public readonly requestId?: string;\n public readonly status?: number;\n public readonly retryAfterMs?: number;\n\n constructor(payload: CrossdeckErrorPayload) {\n super(payload.message);\n this.name = \"CrossdeckError\";\n this.type = payload.type;\n this.code = payload.code;\n this.requestId = payload.requestId;\n this.status = payload.status;\n this.retryAfterMs = payload.retryAfterMs;\n Object.setPrototypeOf(this, CrossdeckError.prototype);\n }\n\n /**\n * JSON representation suitable for structured loggers. Without this,\n * `console.log(err)` and most log frameworks (Pino, Winston) emit\n * only `name` + `message` + `stack` — losing `type`, `code`,\n * `requestId`, `status`, `retryAfterMs`. With `toJSON`, calling\n * `JSON.stringify(err)` or passing the error to a logger that\n * serialises via JSON includes the full diagnostic surface.\n *\n * Stripe pattern. Critical for production observability.\n */\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n message: this.message,\n type: this.type,\n code: this.code,\n requestId: this.requestId,\n status: this.status,\n retryAfterMs: this.retryAfterMs,\n stack: this.stack,\n };\n }\n}\n\n/**\n * Authentication failure — the secret key is missing, invalid, or\n * revoked. Maps to `type: \"authentication_error\"`. Includes codes:\n * `invalid_secret_key`, `webhook_invalid_signature`,\n * `webhook_replay_window_exceeded`, and any 401 from the backend.\n *\n * if (err instanceof CrossdeckAuthenticationError) { ... }\n *\n * Stripe pattern — typed subclasses make caller error-handling\n * clean and let TypeScript narrow on `instanceof`.\n */\nexport class CrossdeckAuthenticationError extends CrossdeckError {\n constructor(payload: CrossdeckErrorPayload) {\n super(payload);\n this.name = \"CrossdeckAuthenticationError\";\n Object.setPrototypeOf(this, CrossdeckAuthenticationError.prototype);\n }\n}\n\n/**\n * Caller is authenticated but doesn't have permission for the\n * requested resource. Maps to `type: \"permission_error\"`.\n */\nexport class CrossdeckPermissionError extends CrossdeckError {\n constructor(payload: CrossdeckErrorPayload) {\n super(payload);\n this.name = \"CrossdeckPermissionError\";\n Object.setPrototypeOf(this, CrossdeckPermissionError.prototype);\n }\n}\n\n/**\n * Request is malformed or violates a validation rule. Maps to\n * `type: \"invalid_request_error\"`. Includes codes like\n * `missing_user_id`, `missing_event_name`, `serialization_failed`,\n * and any 4xx (other than 401/403/429) from the backend.\n */\nexport class CrossdeckValidationError extends CrossdeckError {\n constructor(payload: CrossdeckErrorPayload) {\n super(payload);\n this.name = \"CrossdeckValidationError\";\n Object.setPrototypeOf(this, CrossdeckValidationError.prototype);\n }\n}\n\n/**\n * Rate limit exceeded. Maps to `type: \"rate_limit_error\"`. Carries\n * `retryAfterMs` from the server's `Retry-After` header — caller\n * should back off and retry only after that delay.\n */\nexport class CrossdeckRateLimitError extends CrossdeckError {\n constructor(payload: CrossdeckErrorPayload) {\n super(payload);\n this.name = \"CrossdeckRateLimitError\";\n Object.setPrototypeOf(this, CrossdeckRateLimitError.prototype);\n }\n}\n\n/**\n * Network-layer failure — `fetch` threw, the request timed out, or\n * the response body was unparseable. Maps to `type: \"network_error\"`\n * with codes `fetch_failed`, `request_timeout`, or `internal_error`\n * (`invalid_json_response`). Almost always transient; the SDK auto-\n * retries event-queue flushes.\n */\nexport class CrossdeckNetworkError extends CrossdeckError {\n constructor(payload: CrossdeckErrorPayload) {\n super(payload);\n this.name = \"CrossdeckNetworkError\";\n Object.setPrototypeOf(this, CrossdeckNetworkError.prototype);\n }\n}\n\n/**\n * Backend returned a 5xx or the SDK detected an unexpected\n * internal state. Maps to `type: \"internal_error\"`.\n */\nexport class CrossdeckInternalError extends CrossdeckError {\n constructor(payload: CrossdeckErrorPayload) {\n super(payload);\n this.name = \"CrossdeckInternalError\";\n Object.setPrototypeOf(this, CrossdeckInternalError.prototype);\n }\n}\n\n/**\n * Misconfigured SDK options at construction time. Maps to\n * `type: \"configuration_error\"`. Includes codes like\n * `invalid_secret_key`, `webhook_missing_secret`. Never retryable —\n * always a developer fix.\n */\nexport class CrossdeckConfigurationError extends CrossdeckError {\n constructor(payload: CrossdeckErrorPayload) {\n super(payload);\n this.name = \"CrossdeckConfigurationError\";\n Object.setPrototypeOf(this, CrossdeckConfigurationError.prototype);\n }\n}\n\n/**\n * Construct the right `CrossdeckError` subclass for a given payload's\n * `type`. Used by `crossdeckErrorFromResponse` + by any internal call\n * site that throws — gives every thrown error its semantic subclass\n * without forcing every call site to know the mapping.\n */\nexport function makeCrossdeckError(payload: CrossdeckErrorPayload): CrossdeckError {\n switch (payload.type) {\n case \"authentication_error\":\n return new CrossdeckAuthenticationError(payload);\n case \"permission_error\":\n return new CrossdeckPermissionError(payload);\n case \"invalid_request_error\":\n return new CrossdeckValidationError(payload);\n case \"rate_limit_error\":\n return new CrossdeckRateLimitError(payload);\n case \"network_error\":\n return new CrossdeckNetworkError(payload);\n case \"internal_error\":\n return new CrossdeckInternalError(payload);\n case \"configuration_error\":\n return new CrossdeckConfigurationError(payload);\n default:\n // Exhaustiveness fallback — also covers the edge case where the\n // backend introduces a new error type before the SDK is updated.\n return new CrossdeckError(payload);\n }\n}\n\nexport async function crossdeckErrorFromResponse(res: Response): Promise<CrossdeckError> {\n const requestId = res.headers.get(\"x-request-id\") ?? undefined;\n const retryAfterMs = parseRetryAfterHeader(res.headers.get(\"retry-after\"));\n let body: unknown;\n try {\n body = await res.json();\n } catch {\n body = null;\n }\n const envelope = (body as { error?: Partial<CrossdeckErrorPayload> & { request_id?: string } })?.error;\n if (envelope && typeof envelope.type === \"string\" && typeof envelope.code === \"string\") {\n return makeCrossdeckError({\n type: envelope.type as CrossdeckErrorType,\n code: envelope.code,\n message: envelope.message ?? `HTTP ${res.status}`,\n requestId: envelope.request_id ?? requestId,\n status: res.status,\n retryAfterMs,\n });\n }\n return makeCrossdeckError({\n type: typeMapForStatus(res.status),\n code: `http_${res.status}`,\n message: `HTTP ${res.status} ${res.statusText || \"\"}`.trim(),\n requestId,\n status: res.status,\n retryAfterMs,\n });\n}\n\nexport function parseRetryAfterHeader(value: string | null): number | undefined {\n if (!value) return undefined;\n const trimmed = value.trim();\n if (!trimmed) return undefined;\n if (/^\\d+(\\.\\d+)?$/.test(trimmed)) {\n const secs = Number(trimmed);\n if (!Number.isFinite(secs) || secs < 0) return undefined;\n return Math.round(secs * 1000);\n }\n if (!/[a-zA-Z,/:]/.test(trimmed)) return undefined;\n const target = Date.parse(trimmed);\n if (!Number.isFinite(target)) return undefined;\n const delta = target - Date.now();\n return delta > 0 ? delta : 0;\n}\n\nfunction typeMapForStatus(status: number): CrossdeckErrorType {\n if (status === 401) return \"authentication_error\";\n if (status === 403) return \"permission_error\";\n if (status === 429) return \"rate_limit_error\";\n if (status >= 400 && status < 500) return \"invalid_request_error\";\n return \"internal_error\";\n}\n","/**\n * Property validation + coercion for Node SDK payloads.\n *\n * Ported directly from `@cross-deck/web` so web + node share the same\n * sanitisation contract for traits and event property bags.\n */\n\ntype EventProperties = Record<string, unknown>;\n\nexport interface ValidationOptions {\n maxStringLength?: number;\n maxBatchPropertyBytes?: number;\n maxDepth?: number;\n}\n\nexport interface ValidationWarning {\n kind:\n | \"dropped_function\"\n | \"dropped_symbol\"\n | \"dropped_undefined\"\n | \"coerced_date\"\n | \"coerced_bigint\"\n | \"coerced_error\"\n | \"coerced_map\"\n | \"coerced_set\"\n | \"truncated_string\"\n | \"circular_reference\"\n | \"depth_exceeded\"\n | \"non_serialisable\"\n | \"size_cap_exceeded\";\n key: string;\n}\n\nexport interface ValidationResult {\n properties: EventProperties;\n warnings: ValidationWarning[];\n}\n\nconst DEFAULT_MAX_STRING = 1024;\nconst DEFAULT_MAX_BYTES = 8 * 1024;\nconst DEFAULT_MAX_DEPTH = 5;\n\nexport function validateEventProperties(\n input: EventProperties | undefined,\n options: ValidationOptions = {},\n): ValidationResult {\n const warnings: ValidationWarning[] = [];\n if (!input) return { properties: {}, warnings };\n\n const maxStringLength = options.maxStringLength ?? DEFAULT_MAX_STRING;\n const maxBatchPropertyBytes = options.maxBatchPropertyBytes ?? DEFAULT_MAX_BYTES;\n const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;\n\n const seen = new WeakSet<object>();\n\n const visit = (\n value: unknown,\n key: string,\n depth: number,\n ): { keep: boolean; value: unknown } => {\n if (depth > maxDepth) {\n warnings.push({ kind: \"depth_exceeded\", key });\n return { keep: true, value: \"[depth-exceeded]\" };\n }\n if (value === null) return { keep: true, value: null };\n const t = typeof value;\n if (t === \"string\") {\n const s = value as string;\n if (s.length > maxStringLength) {\n warnings.push({ kind: \"truncated_string\", key });\n return { keep: true, value: s.slice(0, maxStringLength - 1) + \"…\" };\n }\n return { keep: true, value: s };\n }\n if (t === \"number\") {\n if (!Number.isFinite(value as number)) {\n warnings.push({ kind: \"non_serialisable\", key });\n return { keep: true, value: null };\n }\n return { keep: true, value };\n }\n if (t === \"boolean\") return { keep: true, value };\n if (t === \"bigint\") {\n warnings.push({ kind: \"coerced_bigint\", key });\n return { keep: true, value: (value as bigint).toString() };\n }\n if (t === \"function\") {\n warnings.push({ kind: \"dropped_function\", key });\n return { keep: false, value: undefined };\n }\n if (t === \"symbol\") {\n warnings.push({ kind: \"dropped_symbol\", key });\n return { keep: false, value: undefined };\n }\n if (t === \"undefined\") {\n warnings.push({ kind: \"dropped_undefined\", key });\n return { keep: false, value: undefined };\n }\n\n if (value instanceof Date) {\n warnings.push({ kind: \"coerced_date\", key });\n const iso = Number.isFinite(value.getTime()) ? value.toISOString() : null;\n return { keep: true, value: iso };\n }\n if (value instanceof Error) {\n warnings.push({ kind: \"coerced_error\", key });\n return {\n keep: true,\n value: {\n name: value.name,\n message: value.message,\n stack:\n typeof value.stack === \"string\"\n ? value.stack.slice(0, maxStringLength)\n : undefined,\n },\n };\n }\n if (value instanceof Map) {\n warnings.push({ kind: \"coerced_map\", key });\n const obj: Record<string, unknown> = {};\n for (const [k, v] of value.entries()) {\n const subKey = typeof k === \"string\" ? k : String(k);\n const result = visit(v, `${key}.${subKey}`, depth + 1);\n if (result.keep) obj[subKey] = result.value;\n }\n return { keep: true, value: obj };\n }\n if (value instanceof Set) {\n warnings.push({ kind: \"coerced_set\", key });\n const arr: unknown[] = [];\n let i = 0;\n for (const v of value.values()) {\n const result = visit(v, `${key}[${i}]`, depth + 1);\n if (result.keep) arr.push(result.value);\n i++;\n }\n return { keep: true, value: arr };\n }\n\n if (Array.isArray(value)) {\n if (seen.has(value)) {\n warnings.push({ kind: \"circular_reference\", key });\n return { keep: true, value: \"[circular]\" };\n }\n seen.add(value);\n const out: unknown[] = [];\n for (let i = 0; i < value.length; i++) {\n const result = visit(value[i], `${key}[${i}]`, depth + 1);\n if (result.keep) out.push(result.value);\n }\n return { keep: true, value: out };\n }\n\n if (t === \"object\") {\n const obj = value as Record<string, unknown>;\n if (seen.has(obj)) {\n warnings.push({ kind: \"circular_reference\", key });\n return { keep: true, value: \"[circular]\" };\n }\n seen.add(obj);\n const out: Record<string, unknown> = {};\n for (const k of Object.keys(obj)) {\n const result = visit(obj[k], `${key}.${k}`, depth + 1);\n if (result.keep) out[k] = result.value;\n }\n return { keep: true, value: out };\n }\n\n warnings.push({ kind: \"non_serialisable\", key });\n try {\n return { keep: true, value: String(value) };\n } catch {\n return { keep: false, value: undefined };\n }\n };\n\n const cleaned: Record<string, unknown> = {};\n for (const k of Object.keys(input)) {\n const result = visit(input[k], k, 0);\n if (result.keep) cleaned[k] = result.value;\n }\n\n const serialised = safeStringify(cleaned);\n if (serialised && byteLength(serialised) > maxBatchPropertyBytes) {\n warnings.push({ kind: \"size_cap_exceeded\", key: \"*\" });\n const sizes = Object.keys(cleaned)\n .map((k) => ({ k, size: byteLength(safeStringify(cleaned[k]) ?? \"\") }))\n .sort((a, b) => b.size - a.size);\n let currentSize = byteLength(serialised);\n for (const { k } of sizes) {\n if (currentSize <= maxBatchPropertyBytes) break;\n currentSize -= sizes.find((s) => s.k === k)!.size;\n delete cleaned[k];\n }\n cleaned.__truncated = true;\n }\n\n return { properties: cleaned, warnings };\n}\n\nfunction safeStringify(v: unknown): string | null {\n try {\n return JSON.stringify(v) ?? null;\n } catch {\n return null;\n }\n}\n\nfunction byteLength(s: string): number {\n if (typeof TextEncoder !== \"undefined\") {\n return new TextEncoder().encode(s).length;\n }\n return s.length * 4;\n}\n","import { CrossdeckError, makeCrossdeckError } from \"./errors\";\nimport { crossdeckErrorFromResponse } from \"./errors\";\nimport { validateEventProperties } from \"./event-validation\";\n\nexport const SDK_NAME = \"@cross-deck/node\";\nexport const SDK_VERSION = \"1.2.0\";\nexport const DEFAULT_BASE_URL = \"https://api.cross-deck.com/v1\";\nexport const DEFAULT_TIMEOUT_MS = 15_000;\n\n/**\n * Pinned Crossdeck API version sent on every request as\n * `Crossdeck-Api-Version`. Forward-compat with backend evolution —\n * server-side breaking changes ship under a new version date; pinning\n * means the SDK keeps speaking the version it was built against until\n * the SDK explicitly bumps. Stripe pattern (`Stripe-Version`).\n *\n * Bump this in lockstep with backend version releases. Document the\n * deprecation policy in CHANGELOG.\n */\nexport const CROSSDECK_API_VERSION = \"2025-01-01\";\n\n/** Default GET retry attempts. Configurable via `httpRetries.maxAttempts`. */\nconst DEFAULT_GET_RETRY_ATTEMPTS = 3;\n\n/** Default statuses considered retryable on GET. 408 + 5xx (except 501 Not Implemented). */\nconst DEFAULT_RETRYABLE_STATUSES = new Set([408, 500, 502, 503, 504]);\n\nexport interface HttpRetriesConfig {\n /** Max attempts INCLUSIVE of the first call. Default 3 (1 initial + 2 retries). 1 disables retries. */\n maxAttempts?: number;\n /** Statuses considered retryable. Default: 408, 500, 502, 503, 504. */\n retryableStatuses?: number[];\n}\n\nexport interface HttpClientConfig {\n secretKey: string;\n baseUrl: string;\n sdkVersion: string;\n timeoutMs?: number;\n /**\n * Override the runtime token in the `User-Agent` header. Default\n * detects `node/<process.versions.node>` automatically.\n */\n runtimeToken?: string;\n /**\n * Retry config for idempotent GET requests. Default: 3 attempts\n * with exponential backoff + full jitter, retrying on 408 + 5xx\n * (except 501) and on network failures. Set `maxAttempts: 1` to\n * disable retries.\n */\n httpRetries?: HttpRetriesConfig;\n /**\n * `testMode: true` short-circuits every request to a synthetic\n * success response with a benign shape. No network goes out. For\n * caller test suites that don't want to mock `globalThis.fetch`.\n * Forwarded from `CrossdeckServerOptions.testMode`.\n */\n testMode?: boolean;\n /**\n * Optional inspection hooks. Fire BEFORE/AFTER every request — used\n * for debugging, audit logging, and custom telemetry. Both must\n * return synchronously and must NOT throw (errors are swallowed,\n * the request continues).\n */\n onRequest?: (info: HttpRequestInfo) => void;\n onResponse?: (info: HttpResponseInfo) => void;\n}\n\nexport interface HttpRequestInfo {\n method: \"GET\" | \"POST\";\n url: string;\n headers: Record<string, string>;\n /** The serialised body string, when one was set. */\n bodyPreview?: string;\n /** Attempt number, starting at 1. Useful for distinguishing retries. */\n attempt: number;\n}\n\nexport interface HttpResponseInfo {\n method: \"GET\" | \"POST\";\n url: string;\n status: number;\n durationMs: number;\n attempt: number;\n /** True if the request was a synthetic test-mode response. */\n testMode: boolean;\n}\n\nexport interface HttpRequestOptions {\n body?: unknown;\n query?: Record<string, string | undefined>;\n timeoutMs?: number;\n idempotencyKey?: string;\n /**\n * Caller-supplied AbortSignal. When aborted, the in-flight `fetch`\n * is cancelled and the call throws `CrossdeckError({ code:\n * \"request_aborted\" })`. Compose with the per-request timeout\n * (whichever fires first wins).\n */\n signal?: AbortSignal;\n /** Override the HTTP-retry policy for this single call. */\n retries?: HttpRetriesConfig;\n}\n\nexport class HttpClient {\n private readonly userAgent: string;\n\n constructor(private readonly config: HttpClientConfig) {\n this.userAgent = buildUserAgent(config.sdkVersion, config.runtimeToken);\n }\n\n async request<T>(\n method: \"GET\" | \"POST\",\n path: string,\n options: HttpRequestOptions = {},\n ): Promise<T> {\n const url = this.buildUrl(path, options.query);\n\n // testMode short-circuits ALL requests with a synthetic response —\n // caller test suites get a valid-shaped object back without\n // mocking `globalThis.fetch`. Honours `onRequest` / `onResponse`\n // hooks so audit pipelines see the synthetic traffic.\n if (this.config.testMode === true) {\n return this.synthesizeTestModeResponse<T>(method, path, url, options);\n }\n\n const headers = this.buildHeaders(options);\n const bodyInit = this.buildBody(headers, options.body);\n\n // Retry policy applies only to idempotent GET requests. POST\n // retries are handled by the EventQueue with batch-level\n // Idempotency-Key reuse.\n const retryCfg = options.retries ?? this.config.httpRetries ?? {};\n const maxAttempts =\n method === \"GET\" ? (retryCfg.maxAttempts ?? DEFAULT_GET_RETRY_ATTEMPTS) : 1;\n const retryableStatuses = retryCfg.retryableStatuses\n ? new Set(retryCfg.retryableStatuses)\n : DEFAULT_RETRYABLE_STATUSES;\n\n let lastError: CrossdeckError | null = null;\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n const reqInfo: HttpRequestInfo = {\n method,\n url,\n headers,\n bodyPreview: typeof bodyInit === \"string\" ? bodyInit : undefined,\n attempt,\n };\n try {\n this.config.onRequest?.(reqInfo);\n } catch {\n // hooks must never break the request pipeline\n }\n\n const start = Date.now();\n let response: Response | null = null;\n let networkError: unknown = null;\n try {\n response = await this.dispatch(url, method, headers, bodyInit, options);\n } catch (err) {\n networkError = err;\n }\n\n const durationMs = Date.now() - start;\n\n if (response) {\n try {\n this.config.onResponse?.({\n method,\n url,\n status: response.status,\n durationMs,\n attempt,\n testMode: false,\n });\n } catch {\n // hooks must never break the response pipeline\n }\n }\n\n // Network error path — retry on GET if attempts remain.\n if (networkError !== null) {\n lastError = this.translateNetworkError(networkError, path, options);\n if (method === \"GET\" && attempt < maxAttempts) {\n await sleepWithJitter(attempt);\n continue;\n }\n throw lastError;\n }\n\n // Non-OK response — retry on GET for the retryable statuses,\n // else throw the typed subclass.\n if (response && !response.ok) {\n const err = await crossdeckErrorFromResponse(response);\n if (\n method === \"GET\" &&\n retryableStatuses.has(response.status) &&\n attempt < maxAttempts\n ) {\n lastError = err;\n await sleepForRetry(err, attempt);\n continue;\n }\n throw err;\n }\n\n // 2xx — return the body.\n if (response!.status === 204) return undefined as T;\n try {\n return (await response!.json()) as T;\n } catch {\n throw makeCrossdeckError({\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 // Unreachable in practice — the loop either returns or throws —\n // but TypeScript needs an exit. Rethrow the last seen error.\n throw lastError ?? makeCrossdeckError({\n type: \"internal_error\",\n code: \"retry_exhausted\",\n message: `GET ${path} exhausted ${maxAttempts} attempts.`,\n });\n }\n\n /**\n * Issue a single fetch invocation. Composes the per-request timeout\n * with the caller-supplied AbortSignal — whichever fires first wins.\n */\n private async dispatch(\n url: string,\n method: \"GET\" | \"POST\",\n headers: Record<string, string>,\n bodyInit: RequestInit[\"body\"] | undefined,\n options: HttpRequestOptions,\n ): Promise<Response> {\n const effectiveTimeout = options.timeoutMs ?? this.config.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const supportsAbort = typeof AbortController !== \"undefined\";\n const controller = supportsAbort && effectiveTimeout > 0 ? new AbortController() : null;\n\n // Chain the caller's AbortSignal into ours so an external abort\n // propagates to the in-flight fetch.\n let externalAbortHandler: (() => void) | null = null;\n if (controller && options.signal) {\n if (options.signal.aborted) {\n controller.abort();\n } else {\n externalAbortHandler = (): void => controller.abort();\n options.signal.addEventListener(\"abort\", externalAbortHandler, { once: true });\n }\n }\n\n let timeoutHandle: ReturnType<typeof setTimeout> | null = null;\n if (controller && effectiveTimeout > 0) {\n timeoutHandle = setTimeout(() => controller.abort(), effectiveTimeout);\n }\n\n try {\n return await fetch(url, {\n method,\n headers,\n body: bodyInit,\n signal: controller?.signal ?? options.signal,\n });\n } finally {\n if (timeoutHandle !== null) clearTimeout(timeoutHandle);\n if (externalAbortHandler && options.signal) {\n try {\n options.signal.removeEventListener(\"abort\", externalAbortHandler);\n } catch {\n // ignore — signal may not support removeEventListener\n }\n }\n }\n }\n\n /** Build the request headers. Same across attempts so caches can dedupe. */\n private buildHeaders(options: HttpRequestOptions): Record<string, string> {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.config.secretKey}`,\n \"Crossdeck-Sdk-Version\": `${SDK_NAME}@${this.config.sdkVersion}`,\n \"Crossdeck-Api-Version\": CROSSDECK_API_VERSION,\n \"User-Agent\": this.userAgent,\n Accept: \"application/json\",\n };\n if (options.idempotencyKey) headers[\"Idempotency-Key\"] = options.idempotencyKey;\n if (options.body !== undefined) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n return headers;\n }\n\n private buildBody(\n headers: Record<string, string>,\n body: unknown,\n ): RequestInit[\"body\"] | undefined {\n if (body === undefined) return undefined;\n void headers; // headers already include Content-Type from buildHeaders\n return serializeRequestBody(body);\n }\n\n /** Translate a thrown fetch error or abort into a typed `CrossdeckError`. */\n private translateNetworkError(\n err: unknown,\n path: string,\n options: HttpRequestOptions,\n ): CrossdeckError {\n const callerAborted =\n options.signal?.aborted === true ||\n (err instanceof Error &&\n (err.name === \"AbortError\" || /aborted/i.test(err.message)));\n const callerInitiated = options.signal?.aborted === true;\n return makeCrossdeckError({\n type: \"network_error\",\n code: callerAborted\n ? callerInitiated\n ? \"request_aborted\"\n : \"request_timeout\"\n : \"fetch_failed\",\n message: callerAborted\n ? callerInitiated\n ? `Request to ${path} aborted by caller AbortSignal.`\n : `Request to ${path} aborted after ${options.timeoutMs ?? this.config.timeoutMs ?? DEFAULT_TIMEOUT_MS}ms`\n : err instanceof Error\n ? err.message\n : \"fetch failed\",\n });\n }\n\n /** Synthesise a benign success-shaped response for `testMode: true`. */\n private synthesizeTestModeResponse<T>(\n method: \"GET\" | \"POST\",\n path: string,\n url: string,\n options: HttpRequestOptions,\n ): T {\n try {\n this.config.onRequest?.({\n method,\n url,\n headers: this.buildHeaders(options),\n bodyPreview: options.body !== undefined ? safeStringify(options.body) : undefined,\n attempt: 1,\n });\n } catch {\n // ignore\n }\n const synth = synthForPath<T>(path);\n try {\n this.config.onResponse?.({\n method,\n url,\n status: 200,\n durationMs: 0,\n attempt: 1,\n testMode: true,\n });\n } catch {\n // ignore\n }\n return synth;\n }\n\n private buildUrl(path: string, query?: Record<string, string | undefined>): string {\n const base = this.config.baseUrl.replace(/\\/+$/, \"\");\n const cleanPath = path.startsWith(\"/\") ? path : `/${path}`;\n let url = base + cleanPath;\n if (query) {\n const params = new URLSearchParams();\n for (const [k, v] of Object.entries(query)) {\n if (typeof v === \"string\" && v.length > 0) params.append(k, v);\n }\n const qs = params.toString();\n if (qs) url += (url.includes(\"?\") ? \"&\" : \"?\") + qs;\n }\n return url;\n }\n}\n\n/**\n * Build the `User-Agent` header. HTTP best practice — surfaces the\n * SDK + runtime + OS to the backend's request logs without parsing\n * the bespoke `Crossdeck-Sdk-Version` (which only carries the SDK\n * name/version).\n *\n * @cross-deck/node/1.2.0 node/20.10.0 darwin\n */\nfunction buildUserAgent(sdkVersion: string, override?: string): string {\n if (override) return `${SDK_NAME}/${sdkVersion} ${override}`;\n const nodeVersion = typeof process !== \"undefined\" && process.versions ? process.versions.node : \"unknown\";\n const osPlatform = typeof process !== \"undefined\" && process.platform ? process.platform : \"unknown\";\n return `${SDK_NAME}/${sdkVersion} node/${nodeVersion} ${osPlatform}`;\n}\n\n/** Exponential backoff with full jitter for the GET retry path. */\nasync function sleepWithJitter(attempt: number): Promise<void> {\n // 50ms * 2^(attempt-1), capped at 2s, jittered to [0, ceiling].\n const ceiling = Math.min(2000, 50 * Math.pow(2, attempt - 1));\n const delay = Math.round(ceiling * Math.random());\n await new Promise<void>((resolve) => {\n const t = setTimeout(resolve, delay);\n if (typeof t.unref === \"function\") {\n try {\n t.unref();\n } catch {\n // ignore\n }\n }\n });\n}\n\n/**\n * Sleep for the retry delay implied by a `CrossdeckError` — honours\n * server `Retry-After` when present, else falls back to jittered\n * exponential backoff.\n */\nasync function sleepForRetry(err: CrossdeckError, attempt: number): Promise<void> {\n if (err.retryAfterMs !== undefined && err.retryAfterMs > 0) {\n await new Promise<void>((resolve) => {\n const t = setTimeout(resolve, err.retryAfterMs);\n if (typeof t.unref === \"function\") {\n try {\n t.unref();\n } catch {\n // ignore\n }\n }\n });\n return;\n }\n await sleepWithJitter(attempt);\n}\n\n/**\n * Synth response for `testMode: true`. Path-aware so each public\n * method's expected shape comes back. Keep this small + benign —\n * the goal is \"caller's code path runs without surprises\", not \"the\n * SDK behaves identically to production.\"\n */\nfunction synthForPath<T>(path: string): T {\n if (path.startsWith(\"/sdk/heartbeat\")) {\n return {\n object: \"heartbeat\",\n ok: true,\n projectId: \"proj_test_mode\",\n appId: \"app_test_mode\",\n platform: \"node\",\n env: \"sandbox\",\n serverTime: Date.now(),\n } as unknown as T;\n }\n if (path.startsWith(\"/identity/alias\")) {\n return {\n object: \"alias_result\",\n crossdeckCustomerId: \"cdcust_test_mode\",\n linked: [],\n mergePending: false,\n env: \"sandbox\",\n } as unknown as T;\n }\n if (path.startsWith(\"/identity/forget\")) {\n return {\n object: \"forgot\",\n crossdeckCustomerId: null,\n queuedAt: Date.now(),\n env: \"sandbox\",\n } as unknown as T;\n }\n if (path.includes(\"/entitlements\")) {\n return {\n object: \"list\",\n data: [],\n crossdeckCustomerId: \"cdcust_test_mode\",\n env: \"sandbox\",\n } as unknown as T;\n }\n if (path.startsWith(\"/events\")) {\n return {\n object: \"list\",\n received: 0,\n env: \"sandbox\",\n } as unknown as T;\n }\n if (path.includes(\"/purchases/sync\")) {\n return {\n object: \"purchase_result\",\n crossdeckCustomerId: \"cdcust_test_mode\",\n env: \"sandbox\",\n entitlements: [],\n } as unknown as T;\n }\n if (path.includes(\"/grant\") || path.includes(\"/revoke\")) {\n return {\n object: \"entitlement_mutation\",\n action: path.includes(\"/grant\") ? \"grant\" : \"revoke\",\n crossdeckCustomerId: \"cdcust_test_mode\",\n entitlement: {\n object: \"entitlement\",\n key: \"pro\",\n isActive: path.includes(\"/grant\"),\n validUntil: null,\n source: { rail: \"manual\", productId: \"manual\", subscriptionId: \"manual:test_mode\" },\n updatedAt: Date.now(),\n },\n env: \"sandbox\",\n } as unknown as T;\n }\n if (path.startsWith(\"/server/audit/\")) {\n return {\n object: \"audit_entry\",\n data: {\n eventId: \"audit_test_mode\",\n rail: \"manual\",\n env: \"sandbox\",\n eventType: \"test_mode\",\n projectId: \"proj_test_mode\",\n decision: \"applied\",\n signatureVerified: true,\n reconciledWithProvider: false,\n rawEventReceivedAt: Date.now(),\n processedAt: Date.now(),\n },\n } as unknown as T;\n }\n return {} as T;\n}\n\nfunction safeStringify(v: unknown): string {\n try {\n return JSON.stringify(v) ?? \"\";\n } catch {\n return \"[unserialisable]\";\n }\n}\n\nfunction serializeRequestBody(body: unknown): string {\n try {\n const direct = JSON.stringify(body);\n if (typeof direct === \"string\") return direct;\n } catch {\n // Fall through to the sanitising backstop.\n }\n\n try {\n const wrapped = validateEventProperties(\n { __body: body },\n {\n maxStringLength: 1_000_000,\n maxBatchPropertyBytes: 10 * 1024 * 1024,\n maxDepth: 20,\n },\n ).properties.__body;\n const serialized = JSON.stringify(wrapped);\n if (typeof serialized === \"string\") return serialized;\n } catch {\n // Surface a stable SDK error instead of leaking the runtime's raw\n // JSON.stringify message.\n }\n\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"serialization_failed\",\n message: \"Request body could not be serialized.\",\n });\n}\n","/**\n * Retry policy for the event-queue flush.\n *\n * After a failed flush, the queue must wait some time before trying\n * again — otherwise a flapping backend causes a hot loop, and a 429\n * \"slow down\" goes ignored.\n *\n * Policy (parity with `@cross-deck/web/src/retry-policy.ts`):\n * - Exponential backoff: `base * 2^attempts`, capped at `maxMs`.\n * - Full jitter: result is multiplied by `Math.random()` so 100 SDK\n * instances retrying the same downed endpoint don't all hammer at\n * the same instant. Spread the storm.\n * - 429 / 503 `Retry-After`: ALWAYS honour the server-supplied delay\n * when it's larger than our computed backoff. The server knows its\n * own capacity better than we do; ignoring it is what gets your IP\n * blocked.\n * - Reset on success.\n *\n * The policy is a pure object — no state mutation outside `attempts`,\n * no timers. The EventQueue owns the timer; the policy owns the math.\n *\n * Default values match Stripe-style retry windows:\n * - baseMs: 1000 (first retry ~1s out, jittered to 0-1s)\n * - maxMs: 60000 (never wait longer than 60s)\n * - factor: 2 (1s, 2s, 4s, 8s, 16s, 32s, 60s, 60s, …)\n *\n * After `failuresBeforeWarn` (default 8) without a success, the caller\n * is expected to surface that as a `lastError` for the developer to see\n * in diagnostics. We never stop retrying — events matter and a transient\n * outage can take hours — but we report it clearly so the dev knows\n * their data is queued, not lost.\n */\n\nexport interface RetryPolicyOptions {\n baseMs?: number;\n maxMs?: number;\n factor?: number;\n /** Number of consecutive failures before flagging diagnostics. Default 8. */\n failuresBeforeWarn?: number;\n}\n\nconst DEFAULT_BASE = 1000;\nconst DEFAULT_MAX = 60_000;\nconst DEFAULT_FACTOR = 2;\nconst DEFAULT_WARN = 8;\n\n/**\n * Compute the next retry delay (ms) given the consecutive-failure count\n * and an optional server-supplied `Retry-After` (ms).\n *\n * computeNextDelay(0, undefined) → ~500ms (jittered 0-1000)\n * computeNextDelay(3, undefined) → ~4s (jittered 0-8000)\n * computeNextDelay(0, 30_000) → 30s (server wins)\n * computeNextDelay(8, undefined) → 60s (capped)\n *\n * Pure function — exported for unit testing. Real callers go through\n * `RetryPolicy.nextDelay` so option defaults stay co-located.\n */\nexport function computeNextDelay(\n attempts: number,\n retryAfterMs: number | undefined,\n options: RetryPolicyOptions = {},\n random: () => number = Math.random,\n): number {\n const base = options.baseMs ?? DEFAULT_BASE;\n const max = options.maxMs ?? DEFAULT_MAX;\n const factor = options.factor ?? DEFAULT_FACTOR;\n\n // Cap attempts so 2^attempts doesn't overflow into Infinity.\n const safeAttempts = Math.min(attempts, 30);\n const ceiling = Math.min(max, base * Math.pow(factor, safeAttempts));\n // Full jitter: random across [0, ceiling]. Caller can substitute a\n // deterministic RNG for testing.\n const jittered = ceiling * random();\n // Honour server's Retry-After when bigger than our window — the\n // server's the authority on its own pressure.\n if (retryAfterMs !== undefined && retryAfterMs > jittered) {\n return Math.min(max, retryAfterMs);\n }\n return Math.max(0, Math.round(jittered));\n}\n\nexport class RetryPolicy {\n private attempts = 0;\n constructor(private readonly options: RetryPolicyOptions = {}) {}\n\n /** How many consecutive failures since the last success. */\n get consecutiveFailures(): number {\n return this.attempts;\n }\n\n /** Whether we've crossed the failuresBeforeWarn threshold. */\n get isWarning(): boolean {\n return this.attempts >= (this.options.failuresBeforeWarn ?? DEFAULT_WARN);\n }\n\n /** Schedule-time delay for the NEXT retry. Increments the counter. */\n nextDelay(retryAfterMs?: number, random: () => number = Math.random): number {\n const delay = computeNextDelay(this.attempts, retryAfterMs, this.options, random);\n this.attempts += 1;\n return delay;\n }\n\n /** Mark a successful flush — reset the counter. */\n recordSuccess(): void {\n this.attempts = 0;\n }\n}\n","/**\n * Cryptographically-random short IDs used across the SDK — event IDs,\n * batch IDs, internal correlation. Same alphabet (`0-9a-z`) and shape\n * (`<prefix>_<base32-ts><rand>`) as `@cross-deck/web`'s\n * `identity.ts:randomChars` so events emitted by the Node SDK look\n * identical to web SDK events in the warehouse.\n *\n * Node 18+ exposes `globalThis.crypto.getRandomValues` natively. If the\n * runtime is older (which the package.json `engines` field forbids,\n * but a sloppy host might run anyway), we fall back to `Math.random`.\n * The fallback is safe here because ID entropy doesn't need to resist\n * offline brute force — it needs to be unique-with-overwhelming-probability\n * across one process lifetime.\n */\n\nconst ALPHABET = \"0123456789abcdefghijklmnopqrstuvwxyz\";\n\nexport function randomChars(count: number): string {\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/**\n * Mint a prefixed ID like `evt_<base32-ts><rand>` / `batch_<base32-ts><rand>`.\n * Sortable (timestamp-prefixed) and log-friendly. Stripe / Segment use the\n * same shape.\n */\nexport function mintId(prefix: string, randLen = 10): string {\n return `${prefix}_${Date.now().toString(36)}${randomChars(randLen)}`;\n}\n","/**\n * Local event queue + batched flush.\n *\n * Why a queue: `track()` is called from hot paths (request handlers,\n * Lambda invocations) and shouldn't block the caller on a network\n * round-trip. Events go into a local buffer, flushed in bursts.\n *\n * Flush triggers:\n * - Buffer reaches `batchSize` (default 20) → flush immediately.\n * - `intervalMs` of inactivity (default 1500ms) → flush idle batch.\n * - `flush()` called explicitly (e.g. from `flush-on-exit.ts` before\n * a Cloud Function exits, or before a Lambda handler returns).\n *\n * Bank-grade hardening (parity with `@cross-deck/web/src/event-queue.ts`):\n * - Exponential backoff with full jitter on flush failures. Honours\n * server `Retry-After` (parsed onto `CrossdeckError` by the HTTP\n * layer). Replaces the prior policy of \"retry on the next idle\n * window\" which hot-looped against a flapping endpoint.\n * - Per-batch `Idempotency-Key`. The SAME key is reused across\n * retries of the SAME batch so the server can short-circuit\n * duplicate work without inspecting bodies. The backend ALSO\n * dedupes individual events via ClickHouse ReplacingMergeTree on\n * `eventId`, so this is belt-and-suspenders.\n *\n * Node differences from web:\n * - No `keepalive` option (Node has no page unload concept).\n * - No `persistentStore` (no localStorage; Node deployments are\n * stateless — Lambda freezes between invocations, Cloud Functions\n * tear down containers). On-exit drainage is handled by the\n * separate `flush-on-exit.ts` module that calls `flush()` from\n * `process.on('beforeExit')` + SIGTERM + SIGINT handlers.\n * - Default scheduler uses `setTimeout(...).unref()` — already\n * Node-friendly, so a pending flush doesn't block the process\n * from exiting between invocations.\n *\n * On a permanent network outage we keep retrying with bounded backoff;\n * we never drop events because of network failures alone. The only\n * drop path is the hard buffer cap (1000 events): once exceeded we\n * evict the OLDEST events and increment `dropped` so the developer\n * can see the loss in `diagnostics()`.\n */\n\nimport type { HttpClient } from \"./http\";\nimport type { EventProperties, IngestResponse } from \"./types\";\nimport type { CrossdeckError } from \"./errors\";\nimport { RetryPolicy, type RetryPolicyOptions } from \"./retry-policy\";\nimport { mintId } from \"./_rand\";\n\nconst HARD_BUFFER_CAP = 1000;\n\nexport interface QueuedEvent {\n eventId: string;\n name: string;\n timestamp: number;\n properties: EventProperties;\n // identity hint — at least one of these is always set per-event for\n // Node (the caller supplies them; the SDK doesn't mint anonymousId).\n developerUserId?: string;\n anonymousId?: string;\n crossdeckCustomerId?: string;\n /** Optional Sentry-style level for error.* events. */\n level?: \"error\" | \"warning\" | \"info\";\n /** Optional Sentry-style tag bag for error.* events. */\n tags?: Record<string, string>;\n /** Optional categoryTags column projection on the backend. */\n categoryTags?: string[];\n}\n\nexport interface BatchEnvelope {\n /** Optional appId. The server is authoritative via the API key; this is metadata only. */\n appId?: string;\n sdk: { name: string; version: string };\n}\n\nexport interface EventQueueConfig {\n http: HttpClient;\n batchSize: number;\n intervalMs: number;\n /**\n * Returns the batch envelope to attach to each POST. Function (not\n * a value) so a future config swap can update it without\n * re-instantiating the queue.\n */\n envelope: () => BatchEnvelope;\n /** Schedule a function to run after `ms` ms. Default: setTimeout with .unref(). Override for tests. */\n scheduler?: (fn: () => void, ms: number) => () => void;\n /** Called when the SDK drops events because the buffer is full. */\n onDrop?: (dropped: number) => void;\n /** Called once after the first successful flush — drives the §16 \"First event sent\" debug signal. */\n onFirstFlushSuccess?: () => void;\n /** Retry policy overrides for failed flushes. */\n retry?: RetryPolicyOptions;\n /**\n * Called whenever an item is added to the buffer or removed by a\n * successful flush. Exposed so the host SDK can surface live queue\n * stats via `diagnostics()` without polling.\n */\n onBufferChange?: (size: number) => void;\n /**\n * Fired (async, never throws) whenever the retry policy schedules\n * the next flush attempt. Used by the SDK debug logger to surface\n * \"flush failed, retrying in Xms\" signals.\n */\n onRetryScheduled?: (info: {\n delayMs: number;\n consecutiveFailures: number;\n retryAfterMs?: number;\n lastError: string;\n }) => void;\n}\n\nexport interface EventQueueStats {\n buffered: number;\n dropped: number;\n inFlight: number;\n lastFlushAt: number;\n lastError: string | null;\n /** Consecutive flush failures since the last success. */\n consecutiveFailures: number;\n /** Set when the next flush is scheduled by the retry policy. */\n nextRetryAt: number | null;\n}\n\nexport class EventQueue {\n private buffer: QueuedEvent[] = [];\n private dropped = 0;\n private inFlight = 0;\n private lastFlushAt = 0;\n private lastError: string | null = null;\n private cancelTimer: (() => void) | null = null;\n private firstFlushFired = false;\n private nextRetryAt: number | null = null;\n private readonly retry: RetryPolicy;\n /**\n * Stable Idempotency-Key for the current in-flight batch. Minted\n * lazily inside `flush()` when no key is pending. Reused across\n * retries of the same logical batch so the backend's idempotency\n * layer can short-circuit duplicates (Stripe pattern). Reset to\n * `null` after a successful flush.\n */\n private pendingBatchId: string | null = null;\n /**\n * In-flight events that have been spliced from the buffer for the\n * current batch but haven't yet been confirmed (success or final\n * failure). On a retry-driven flush, we re-use this batch alongside\n * `pendingBatchId` instead of re-splicing. New events that arrive\n * during in-flight are buffered separately and join the next batch\n * AFTER this one settles.\n */\n private pendingBatch: QueuedEvent[] | null = null;\n\n constructor(private readonly cfg: EventQueueConfig) {\n this.retry = new RetryPolicy(cfg.retry ?? {});\n }\n\n enqueue(event: QueuedEvent): void {\n this.buffer.push(event);\n if (this.buffer.length > HARD_BUFFER_CAP) {\n const overflow = this.buffer.length - HARD_BUFFER_CAP;\n this.buffer.splice(0, overflow);\n this.dropped += overflow;\n this.cfg.onDrop?.(overflow);\n }\n this.cfg.onBufferChange?.(this.buffer.length);\n 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 * `pendingBatch` slot for the next scheduled retry — the SAME batch\n * with the SAME `Idempotency-Key` is re-sent (Stripe pattern).\n *\n * The `pendingBatch` slot guarantees retry semantics:\n * - First call: splices buffer → pendingBatch + mints batchId.\n * - On 5xx / network failure: pendingBatch stays; scheduler fires\n * `flush()` again later, which re-uses pendingBatch + the same\n * batchId.\n * - On success: pendingBatch + batchId cleared; subsequent calls\n * splice the buffer again with a fresh batchId.\n *\n * New events that arrive during an in-flight batch land in `buffer`\n * (separate from `pendingBatch`) and ship on the next batch after\n * this one settles. Strict ordering preserved.\n */\n async flush(): Promise<IngestResponse | null> {\n // Resume an in-flight batch retry path: if we already have a\n // pending batch (a prior flush failed and we're being re-invoked\n // by the retry timer or the caller), re-attempt with the SAME\n // batchId. This is the Idempotency-Key reuse contract.\n let batch: QueuedEvent[];\n let batchId: string;\n if (this.pendingBatch !== null && this.pendingBatchId !== null) {\n batch = this.pendingBatch;\n batchId = this.pendingBatchId;\n } else {\n if (this.buffer.length === 0) return null;\n batch = this.buffer.splice(0);\n batchId = mintId(\"batch\");\n this.pendingBatch = batch;\n this.pendingBatchId = batchId;\n this.inFlight += batch.length;\n this.cfg.onBufferChange?.(this.buffer.length);\n }\n this.cancelTimerIfSet();\n this.nextRetryAt = null;\n\n try {\n const env = this.cfg.envelope();\n const body: Record<string, unknown> = {\n events: batch,\n sdk: env.sdk,\n };\n if (env.appId) body.appId = env.appId;\n const result = await this.cfg.http.request<IngestResponse>(\"POST\", \"/events\", {\n body,\n idempotencyKey: batchId,\n });\n this.lastFlushAt = Date.now();\n this.lastError = null;\n this.inFlight -= batch.length;\n this.pendingBatch = null;\n this.pendingBatchId = null;\n this.retry.recordSuccess();\n if (!this.firstFlushFired) {\n this.firstFlushFired = true;\n this.cfg.onFirstFlushSuccess?.();\n }\n return result;\n } catch (err) {\n // Keep `pendingBatch` + `pendingBatchId` set — the next\n // scheduler-driven (or caller-driven) flush will retry with the\n // SAME key. This is the Idempotency-Key reuse contract.\n const message = err instanceof Error ? err.message : String(err);\n this.lastError = message;\n\n const retryAfterMs = extractRetryAfterMs(err);\n const delay = this.retry.nextDelay(retryAfterMs);\n this.scheduleRetry(delay);\n this.cfg.onRetryScheduled?.({\n delayMs: delay,\n consecutiveFailures: this.retry.consecutiveFailures,\n retryAfterMs,\n lastError: message,\n });\n return null;\n }\n }\n\n /** Cancel any pending timer and clear in-memory state. */\n reset(): void {\n this.cancelTimerIfSet();\n this.nextRetryAt = null;\n this.buffer = [];\n this.pendingBatch = null;\n this.pendingBatchId = null;\n this.dropped = 0;\n this.inFlight = 0;\n this.lastError = null;\n this.retry.recordSuccess();\n this.cfg.onBufferChange?.(0);\n // Note: we deliberately do NOT reset firstFlushFired — the\n // \"First event sent\" signal is a one-time per-process lifetime\n // moment, not per-identity.\n }\n\n getStats(): EventQueueStats {\n return {\n // `buffered` counts events waiting for their FIRST flush. The\n // in-flight pendingBatch (retrying) is tracked separately via\n // `inFlight` — surfacing both lets diagnostics show \"we have\n // events stuck retrying\" distinct from \"new events arriving\".\n buffered: this.buffer.length,\n dropped: this.dropped,\n inFlight: this.inFlight,\n lastFlushAt: this.lastFlushAt,\n lastError: this.lastError,\n consecutiveFailures: this.retry.consecutiveFailures,\n nextRetryAt: this.nextRetryAt,\n };\n }\n\n /**\n * The Idempotency-Key of the in-flight pending batch (if any).\n * Exposed for testing the Stripe-style reuse contract. Production\n * callers don't need this.\n */\n get pendingIdempotencyKey(): string | null {\n return this.pendingBatchId;\n }\n\n // ---------- internal scheduling ----------\n\n private scheduleIdleFlush(): void {\n this.cancelTimerIfSet();\n const sched = this.cfg.scheduler ?? defaultScheduler;\n this.cancelTimer = sched(() => {\n void this.flush();\n }, this.cfg.intervalMs);\n }\n\n private scheduleRetry(delayMs: number): void {\n this.cancelTimerIfSet();\n this.nextRetryAt = Date.now() + delayMs;\n const sched = this.cfg.scheduler ?? defaultScheduler;\n this.cancelTimer = sched(() => {\n void this.flush();\n }, delayMs);\n }\n\n private cancelTimerIfSet(): void {\n if (this.cancelTimer) {\n this.cancelTimer();\n this.cancelTimer = null;\n }\n }\n}\n\nfunction extractRetryAfterMs(err: unknown): number | undefined {\n if (err && typeof err === \"object\" && \"retryAfterMs\" in err) {\n const v = (err as CrossdeckError).retryAfterMs;\n return typeof v === \"number\" && Number.isFinite(v) && v >= 0 ? v : undefined;\n }\n return undefined;\n}\n\n/**\n * Default scheduler — `setTimeout` with `.unref()` so a pending flush\n * does NOT keep the Node process alive. Critical for short-lived\n * runtimes (Lambda, Cloud Functions) — without `.unref()`, an\n * outstanding retry timer would prevent `process.exit` from firing\n * naturally and the function would hang until the platform's SIGKILL.\n */\nfunction defaultScheduler(fn: () => void, ms: number): () => void {\n const id = setTimeout(fn, ms);\n if (typeof id.unref === \"function\") {\n try {\n id.unref();\n } catch {\n // ignore — unref is best-effort\n }\n }\n return () => clearTimeout(id);\n}\n","/**\n * Breadcrumb ring buffer — context attached to every error report.\n *\n * Sentry / Datadog / Bugsnag all ship the same idea: keep a rolling\n * record of the last N \"things the process did\" (HTTP calls, queued\n * events, custom log lines, function invocations). When an error fires,\n * attach the buffer so the engineer reading the error can see exactly\n * how the process got into the broken state. The single most powerful\n * debugging signal in error monitoring — without breadcrumbs, errors\n * are stack traces with no story.\n *\n * Implementation: a circular buffer with a fixed cap. Old entries are\n * evicted as new ones arrive. The default cap (50) is enough to cover\n * ~5 minutes of typical request activity without ballooning the error\n * payload. Sentry uses 100 by default but the SDK is more aggressive\n * about size since we ship breadcrumbs over the wire with every error,\n * not as a separate batch.\n *\n * Verbatim port of `@cross-deck/web/src/breadcrumbs.ts`. The data\n * structure has zero browser dependencies; same code works in Node.\n *\n * Privacy: breadcrumbs from `track()` calls auto-flow through the same\n * property sanitiser (`event-validation.ts`) before reaching this\n * buffer, so a function/symbol/Error-shape in a tracked property won't\n * crash subsequent error reports.\n */\n\nexport type BreadcrumbCategory =\n | \"navigation\"\n | \"ui.click\"\n | \"ui.input\"\n | \"http\"\n | \"console\"\n | \"custom\"\n | \"info\";\n\nexport type BreadcrumbLevel = \"debug\" | \"info\" | \"warning\" | \"error\";\n\nexport interface Breadcrumb {\n /** epoch ms */\n timestamp: number;\n category: BreadcrumbCategory;\n level?: BreadcrumbLevel;\n /** Short human-readable description. */\n message?: string;\n /** Arbitrary key/value context for the crumb. */\n data?: Record<string, unknown>;\n}\n\nexport class BreadcrumbBuffer {\n private items: Breadcrumb[] = [];\n constructor(private readonly maxSize: number = 50) {}\n\n add(crumb: Breadcrumb): void {\n this.items.push(crumb);\n if (this.items.length > this.maxSize) {\n this.items.shift();\n }\n }\n\n /** Defensive copy — caller can read freely without mutating buffer state. */\n snapshot(): Breadcrumb[] {\n return this.items.slice();\n }\n\n clear(): void {\n this.items = [];\n }\n\n get size(): number {\n return this.items.length;\n }\n}\n","/**\n * Stack-trace parser — normalises V8 / Firefox / Safari stack strings\n * into a common frame shape.\n *\n * Why hand-rolled, not `stack-trace-js` / `error-stack-parser`: those\n * weigh 5–15 KB after minification and we'd be pulling in their full\n * feature matrix just for the parser. The patterns below cover the\n * three shapes any modern runtime emits, totalling ~80 lines.\n *\n * Port of `@cross-deck/web/src/stack-parser.ts`. Two differences:\n * 1. `isInAppFrame` heuristics are Node-aware (`node_modules/`,\n * `node:` core URLs, `internal/` Node internals,\n * `@cross-deck/node` self-skip) instead of browser-aware\n * (extension URLs, CDN hostnames).\n * 2. Path separator handling accepts both `/` (Unix / V8 standard)\n * and `\\` (Windows native paths sometimes leak into `error.stack`\n * on Node-for-Windows deployments).\n *\n * Defensive: never throws. An unparseable line becomes a `raw` frame\n * with just the literal text. Engineers reading errors still get the\n * raw stack as fallback.\n */\n\nexport interface StackFrame {\n /** Function name, or \"?\" if anonymous / unparseable. */\n function: string;\n /** Source file URL the frame ran in. Empty when unknown. */\n filename: string;\n /** 1-indexed line number, or 0 when unknown. */\n lineno: number;\n /** 1-indexed column number, or 0 when unknown. */\n colno: number;\n /**\n * True when the frame is in the app's own code (best-effort:\n * detected by URL not in node_modules/, not a node: core URL, etc.).\n * Powers the dashboard's \"your code vs library code\" view.\n */\n in_app: boolean;\n /** Raw line from the stack string for debugging when parse fails. */\n raw: string;\n}\n\n/**\n * Parse a stack string into an array of frames. Returns an empty\n * array when the input is unparseable — caller should always treat\n * the original `error.stack` as the source of truth for display.\n */\nexport function parseStack(stack: string | undefined | null): StackFrame[] {\n if (!stack || typeof stack !== \"string\") return [];\n const lines = stack.split(\"\\n\");\n const frames: StackFrame[] = [];\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n const frame = parseLine(trimmed);\n if (frame) frames.push(frame);\n }\n return frames;\n}\n\n/**\n * Parse a single stack line. Returns null for header lines like\n * \"TypeError: x is not a function\" (those carry no frame info).\n *\n * Patterns recognised:\n * Chrome / Node V8: \"at functionName (file:line:col)\"\n * Chrome / Node V8: \"at file:line:col\" (anonymous)\n * Firefox / Safari: \"functionName@file:line:col\"\n */\nfunction parseLine(line: string): StackFrame | null {\n // Chrome / Node V8 — with parens\n // Example: at Object.handleRequest (file:///app/server.js:42:18)\n // Example: at Object.handleRequest (/app/server.js:42:18)\n let m = /^at\\s+(.+?)\\s+\\((.+?):(\\d+):(\\d+)\\)$/.exec(line);\n if (m) {\n return buildFrame({\n function: m[1]!,\n filename: m[2]!,\n lineno: parseInt(m[3]!, 10),\n colno: parseInt(m[4]!, 10),\n raw: line,\n });\n }\n\n // Chrome / Node V8 — anonymous, no parens\n // Example: at /app/server.js:42:18\n m = /^at\\s+(.+?):(\\d+):(\\d+)$/.exec(line);\n if (m) {\n return buildFrame({\n function: \"?\",\n filename: m[1]!,\n lineno: parseInt(m[2]!, 10),\n colno: parseInt(m[3]!, 10),\n raw: line,\n });\n }\n\n // Firefox / Safari — also emitted by some Node test runners\n // Example: handleRequest@/app/server.js:42:18\n m = /^(.*?)@(.+?):(\\d+):(\\d+)$/.exec(line);\n if (m) {\n return buildFrame({\n function: m[1]! || \"?\",\n filename: m[2]!,\n lineno: parseInt(m[3]!, 10),\n colno: parseInt(m[4]!, 10),\n raw: line,\n });\n }\n\n // Header line (\"TypeError: foo is not a function\") — return null\n // so caller skips it. Catches anything starting with a *Error word\n // OR any line that has no colon at all (genuine frames always do).\n if (/^\\w*Error/.test(line) || !line.includes(\":\")) {\n return null;\n }\n\n // Unparseable but plausibly a frame — keep it as raw.\n return {\n function: \"?\",\n filename: \"\",\n lineno: 0,\n colno: 0,\n in_app: true,\n raw: line,\n };\n}\n\nfunction buildFrame(input: {\n function: string;\n filename: string;\n lineno: number;\n colno: number;\n raw: string;\n}): StackFrame {\n return {\n function: input.function || \"?\",\n filename: input.filename,\n lineno: Number.isFinite(input.lineno) ? input.lineno : 0,\n colno: Number.isFinite(input.colno) ? input.colno : 0,\n in_app: isInAppFrame(input.filename),\n raw: input.raw,\n };\n}\n\n/**\n * \"Is this frame in the app's own code or a third-party source we\n * should de-emphasise in the UI.\"\n *\n * Node-aware heuristics:\n * 1. SDK self-skip — any frame containing `@cross-deck/node`. Defends\n * against the cycle where the SDK reports on itself.\n * 2. node_modules — library code, almost never the app's bug.\n * 3. `node:` core modules (Node 16+) — `node:fs`, `node:http`, etc.\n * 4. `internal/` — Node-internal V8 frames like\n * `internal/process/task_queues.js`.\n * 5. Empty filename — anonymous frame, let it through as in_app.\n *\n * Path separator handling: V8 always emits `/` on all platforms, but\n * Windows-native paths can leak into `error.stack` via re-thrown\n * errors in Node-for-Windows deployments. We match both.\n */\nfunction isInAppFrame(filename: string): boolean {\n if (!filename) return true;\n // SDK self-skip — must come before node_modules check because the\n // package's own frames live under node_modules/@cross-deck/node\n // when installed, but ALSO want to skip when running from sdks/node/src\n // during dogfood/dev.\n if (/@cross-deck[\\\\/]node/.test(filename)) return false;\n // Library code from any node_modules tree (root or nested).\n if (/[\\\\/]node_modules[\\\\/]/.test(filename)) return false;\n // Node 16+ core module URLs.\n if (/^node:/.test(filename)) return false;\n // Older Node / V8-internal frames.\n if (/^internal[\\\\/]/.test(filename)) return false;\n return true;\n}\n\n/**\n * Fingerprint an error for grouping. SHA-flavoured — we don't need\n * cryptographic strength, we need \"two errors with the same call site\n * produce the same key\". The Crossdeck backend may refine the grouping\n * further once source maps are uploaded.\n *\n * Input: the message + the first ≤3 in-app frames. When no frames\n * are available (non-Error throws of primitives, unhandledRejection\n * of a value with no stack), the optional `location` fallback\n * contributes filename/lineno/errorType so otherwise-identical\n * generic messages from different call sites stay separate. Without\n * the fallback they all collapse into one bucket and the dashboard\n * can't distinguish them.\n *\n * Output: an 8-char hex string usable as a doc id segment.\n */\nexport function fingerprintError(\n message: string,\n frames: StackFrame[],\n location?: {\n filename?: string | null;\n lineno?: number | null;\n errorType?: string | null;\n } | null,\n): string {\n const inAppFrames = frames.filter((f) => f.in_app).slice(0, 3);\n const parts = [\n (message || \"\").slice(0, 200),\n ...inAppFrames.map((f) => `${f.function}@${f.filename}:${f.lineno}`),\n ];\n if (inAppFrames.length === 0 && location) {\n const loc = [\n location.errorType ?? \"\",\n location.filename ?? \"\",\n location.lineno ?? \"\",\n ].join(\":\");\n if (loc !== \"::\") parts.push(loc);\n }\n return djb2Hex(parts.join(\"|\"));\n}\n\n/**\n * djb2 — small, fast non-cryptographic string hash. 32-bit output\n * encoded as 8-char hex. Stable across runtimes; deterministic.\n */\nfunction djb2Hex(input: string): string {\n let h = 5381;\n for (let i = 0; i < input.length; i++) {\n h = ((h << 5) + h + input.charCodeAt(i)) | 0;\n }\n // Force unsigned then 8-char hex.\n return (h >>> 0).toString(16).padStart(8, \"0\");\n}\n","/**\n * Error capture — the third Crossdeck USP, the headline reason backend\n * developers install observability SDKs.\n *\n * Catches every error source a Node process can hand us and ships them\n * as Crossdeck events. The pipeline reuses the analytics queue:\n * - Same retry-with-backoff + Idempotency-Key (duplicate batches\n * dedup server-side)\n * - Same property sanitisation (one bad context blob can't poison\n * the batch)\n * - Same on-the-wire enrichment via runtime-info\n *\n * Error sources captured (each toggleable):\n * 1. `process.on('uncaughtException')` — uncaught synchronous errors\n * 2. `process.on('unhandledRejection')` — unhandled promise rejections\n * 3. `globalThis.fetch` wrap — 5xx + network failures\n * 4. `console.error` wrap (default OFF) — noisy, opt-in\n * 5. `server.captureError(err)` — manual try/catch API\n * 6. `server.captureMessage(msg)` — non-error signals\n *\n * Adapted from `@cross-deck/web/src/error-capture.ts`. Three runtime\n * differences:\n * - `window.onerror` → `process.on('uncaughtException')`. Node's\n * uncaught-exception handler receives an `Error` directly, not an\n * `ErrorEvent` wrapper — `buildFromUnknown` handles both.\n * - `window.onunhandledrejection` → `process.on('unhandledRejection')`.\n * Same shape (the rejection's `reason`); same handler logic.\n * - `XMLHttpRequest` wrap → dropped (no XHR in Node).\n *\n * Defensive design rules (parity with web):\n * - The error handler must NEVER throw — if our own code crashed\n * while reporting an error, we'd take down the host's last-resort\n * error path. Every callback wrapped in try/swallow.\n * - Recursion guard: a `_reporting` flag prevents reporting our own\n * errors recursively forever.\n * - Rate limited per-fingerprint: max N reports per minute to defend\n * against runaway loops (e.g. an error in a per-request middleware).\n * - Session cap (per process lifetime): hard limit after which we\n * stop reporting. The dashboard sees \"1 unique error\" instead of\n * a million events.\n * - Self-skip for `api.cross-deck.com` requests so a Crossdeck\n * outage doesn't self-amplify back into the queue.\n */\n\nimport { parseStack, fingerprintError, type StackFrame } from \"./stack-parser\";\nimport type { BreadcrumbBuffer, Breadcrumb } from \"./breadcrumbs\";\nimport type { ErrorLevel } from \"./types\";\n\nexport type { ErrorLevel };\n\nexport interface CapturedError {\n /** When the error fired (epoch ms). */\n timestamp: number;\n /** error.unhandled | error.unhandledrejection | error.handled | error.message | error.http */\n kind:\n | \"error.unhandled\"\n | \"error.unhandledrejection\"\n | \"error.handled\"\n | \"error.message\"\n | \"error.http\";\n level: ErrorLevel;\n message: string;\n /** The error class name when we have it (TypeError, ReferenceError, etc.) */\n errorType: string | null;\n /** Parsed stack frames, empty when unavailable. */\n frames: StackFrame[];\n /** Raw stack string for fallback display. */\n rawStack: string | null;\n /** djb2 hash of message + top frames — groups identical errors. */\n fingerprint: string;\n /** Snapshot of the breadcrumb buffer at the moment the error fired. */\n breadcrumbs: Breadcrumb[];\n /** Free-form context attached via `server.setContext()`. */\n context: Record<string, unknown>;\n /** Free-form tags attached via `server.setTag()`. */\n tags: Record<string, string>;\n /** Set only on `error.http` — the request that failed. */\n http?: {\n url: string;\n method: string;\n status: number;\n statusText?: string;\n };\n}\n\nexport interface ErrorCaptureConfig {\n /** Master switch. Default true. */\n enabled: boolean;\n /** Hook `process.on('uncaughtException')`. Default true. */\n onUncaughtException: boolean;\n /** Hook `process.on('unhandledRejection')`. Default true. */\n onUnhandledRejection: boolean;\n /** Wrap `globalThis.fetch` to capture 5xx + network failures. Default true. */\n wrapFetch: boolean;\n /** Wrap `console.error`. Default false (noisy). */\n captureConsole: boolean;\n /**\n * Drop errors matching these substrings / regexes. Tested against\n * `message`. Default: empty (Node has no equivalent of the\n * browser's `ResizeObserver` / `Script error.` noise).\n */\n ignoreErrors: Array<string | RegExp>;\n /**\n * Only capture errors whose top in-app frame filename matches one\n * of these. Empty array means \"no allowlist — capture everything\".\n */\n allowPaths: Array<string | RegExp>;\n /**\n * Drop errors whose top frame filename matches any of these.\n * Default: SDK self-skip pattern (`@cross-deck/node`).\n */\n denyPaths: Array<string | RegExp>;\n /**\n * Sample rate, 0–1. 1.0 = send every error. 0.5 = send half (per\n * fingerprint, deterministically — so a given fingerprint always\n * either always sends or never does, no flapping). Default 1.0.\n */\n sampleRate: number;\n /**\n * Maximum errors per fingerprint per minute. Defends against\n * runaway loops. Default 5.\n */\n maxPerFingerprintPerMinute: number;\n /**\n * Total cap per process lifetime, regardless of fingerprint. Hard\n * limit after which we stop reporting. Default 100.\n */\n maxPerSession: number;\n}\n\nexport const DEFAULT_ERROR_CAPTURE: ErrorCaptureConfig = {\n enabled: true,\n onUncaughtException: true,\n onUnhandledRejection: true,\n wrapFetch: true,\n captureConsole: false,\n ignoreErrors: [],\n allowPaths: [],\n denyPaths: [\n // SDK self-skip — caught by stack-parser's `isInAppFrame` too,\n // but defensive here in case a future change to the heuristic\n // misses one of these paths.\n /[\\\\/]node_modules[\\\\/]@cross-deck[\\\\/]node[\\\\/]/,\n ],\n sampleRate: 1.0,\n maxPerFingerprintPerMinute: 5,\n maxPerSession: 100,\n};\n\nexport interface ErrorTrackerOptions {\n config: ErrorCaptureConfig;\n breadcrumbs: BreadcrumbBuffer;\n /** Called with each captured error. Forwards into the event queue. */\n report: (err: CapturedError) => void;\n /** Called to read the current developer-supplied context bag. */\n getContext: () => Record<string, unknown>;\n /** Called to read the current developer-supplied tag bag. */\n getTags: () => Record<string, string>;\n /**\n * Pre-send hook. Return null to drop, or a modified version to\n * scrub fields. Called LAST, after rate limit + sampling have\n * already passed.\n */\n beforeSend?: ((err: CapturedError) => CapturedError | null) | null;\n /**\n * Whether the consent dimension `errors` is currently granted. The\n * Node SDK doesn't ship a ConsentManager (server-side trust model\n * — the caller decides), so this is typically `() => true`. We keep\n * the hook so callers who DO want a kill switch (e.g. a config\n * flag to disable error reporting in CI) have a place to wire it.\n */\n isConsented: () => boolean;\n}\n\n/**\n * Cap on the size of the per-fingerprint rate-limit window Map. A\n * long-running process firing many unique fingerprints would\n * otherwise leak Map entries forever. When the Map exceeds this\n * size, dead entries (empty windows) are pruned; if pruning doesn't\n * release enough space, the oldest entries are evicted FIFO. 4096\n * unique error fingerprints is well above realistic per-minute\n * cardinality on production servers.\n */\nconst MAX_FINGERPRINTS_TRACKED = 4096;\nconst FINGERPRINT_WINDOW_MS = 60_000;\n\nexport class ErrorTracker {\n private installed = false;\n private cleanups: Array<() => void> = [];\n private _reporting = false;\n private sessionCount = 0;\n private fingerprintWindow = new Map<string, number[]>();\n\n constructor(private readonly opts: ErrorTrackerOptions) {}\n\n install(): void {\n if (this.installed) return;\n if (!this.opts.config.enabled) return;\n\n if (this.opts.config.onUncaughtException) this.installUncaughtExceptionHandler();\n if (this.opts.config.onUnhandledRejection) this.installUnhandledRejectionHandler();\n if (this.opts.config.wrapFetch) this.installFetchWrap();\n if (this.opts.config.captureConsole) this.installConsoleWrap();\n\n this.installed = true;\n }\n\n uninstall(): void {\n for (const fn of this.cleanups.splice(0)) {\n try {\n fn();\n } catch {\n // ignore\n }\n }\n this.installed = false;\n }\n\n /**\n * Manual API. Either an Error instance or any unknown value (we\n * coerce). Returns silently — never throws, even if the SDK isn't\n * initialised.\n */\n captureError(\n error: unknown,\n options?: { context?: Record<string, unknown>; tags?: Record<string, string>; level?: ErrorLevel },\n ): void {\n if (!this.opts.isConsented()) return;\n try {\n const captured = this.buildFromUnknown(error, \"error.handled\", options?.level ?? \"error\");\n if (options?.context) captured.context = { ...captured.context, ...options.context };\n if (options?.tags) captured.tags = { ...captured.tags, ...options.tags };\n this.maybeReport(captured);\n } catch {\n // self-protection — never let our own code crash the caller's\n // error handler.\n }\n }\n\n /**\n * Capture a non-error event as an issue. For \"we hit a soft-warning\n * code path\" / \"deprecated API used\" kinds of signals. Pairs with\n * Sentry's captureMessage().\n */\n captureMessage(message: string, level: ErrorLevel = \"info\"): void {\n if (!this.opts.isConsented()) return;\n try {\n const captured: CapturedError = {\n timestamp: Date.now(),\n kind: \"error.message\",\n level,\n message,\n errorType: null,\n frames: [],\n rawStack: null,\n fingerprint: fingerprintError(message, []),\n breadcrumbs: this.opts.breadcrumbs.snapshot(),\n context: this.opts.getContext(),\n tags: this.opts.getTags(),\n };\n this.maybeReport(captured);\n } catch {\n // swallow\n }\n }\n\n /** Inspection hook — total reports captured this process lifetime. */\n get reportedCount(): number {\n return this.sessionCount;\n }\n\n /** Inspection hook — number of distinct fingerprints inside the rate-limit window. */\n get fingerprintsTracked(): number {\n return this.fingerprintWindow.size;\n }\n\n /** Inspection hook — whether global handlers are installed. */\n get handlersInstalled(): boolean {\n return this.installed;\n }\n\n // ============================================================\n // Listener installation — Node hooks\n // ============================================================\n\n private installUncaughtExceptionHandler(): void {\n const handler = (err: Error): void => {\n if (this._reporting) return;\n if (!this.opts.isConsented()) return;\n try {\n this._reporting = true;\n const captured = this.buildFromUnknown(err, \"error.unhandled\", \"error\");\n this.maybeReport(captured);\n } catch {\n // swallow\n } finally {\n this._reporting = false;\n }\n };\n process.on(\"uncaughtException\", handler);\n this.cleanups.push(() => process.off(\"uncaughtException\", handler));\n }\n\n private installUnhandledRejectionHandler(): void {\n // Node's `unhandledRejection` handler receives `(reason, promise)`.\n // The reason is whatever was passed to `reject()` — typically an\n // Error, but can be any value. `buildFromUnknown` handles both.\n const handler = (reason: unknown): void => {\n if (this._reporting) return;\n if (!this.opts.isConsented()) return;\n try {\n this._reporting = true;\n const captured = this.buildFromUnknown(reason, \"error.unhandledrejection\", \"error\");\n this.maybeReport(captured);\n } catch {\n // swallow\n } finally {\n this._reporting = false;\n }\n };\n process.on(\"unhandledRejection\", handler);\n this.cleanups.push(() => process.off(\"unhandledRejection\", handler));\n }\n\n /**\n * Wrap `globalThis.fetch` so failed HTTP requests get auto-captured.\n * We do NOT call 4xx an \"error\" (those are often expected — auth\n * required, validation failed). Only 5xx + network failures fire.\n *\n * Node 18+ exposes `fetch` natively on `globalThis`. We tolerate\n * its absence (some sandboxed runtimes / patched globals) by\n * skipping the wrap rather than throwing.\n */\n private installFetchWrap(): void {\n const origFetch = globalThis.fetch;\n if (typeof origFetch !== \"function\") return;\n const tracker = this;\n const wrapped: typeof fetch = async (...args: Parameters<typeof fetch>): Promise<Response> => {\n const input = args[0];\n const init = args[1] ?? {};\n const url = typeof input === \"string\" ? input : (input as Request)?.url ?? \"\";\n const method = (init.method || \"GET\").toUpperCase();\n const start = Date.now();\n\n tracker.opts.breadcrumbs.add({\n timestamp: start,\n category: \"http\",\n message: `${method} ${url}`,\n data: { url, method },\n });\n\n try {\n const response = await origFetch(...args);\n if (response.status >= 500 && tracker.opts.isConsented()) {\n // Self-skip Crossdeck's own API to avoid the cycle where a\n // Crossdeck outage reports the outage to Crossdeck.\n if (!url.includes(\"api.cross-deck.com\")) {\n tracker.captureHttp({\n url,\n method,\n status: response.status,\n statusText: response.statusText,\n });\n }\n }\n return response;\n } catch (err) {\n // Genuine network failure (DNS, connection refused, ECONNRESET).\n if (tracker.opts.isConsented() && !url.includes(\"api.cross-deck.com\")) {\n tracker.captureHttp({\n url,\n method,\n status: 0,\n statusText: err instanceof Error ? err.message : \"network error\",\n });\n }\n throw err;\n }\n };\n globalThis.fetch = wrapped;\n this.cleanups.push(() => {\n // Restore only if we're still the active wrapper. Another\n // observability tool installed AFTER us would have replaced\n // `globalThis.fetch`; we don't want to unwind their patch.\n if (globalThis.fetch === wrapped) globalThis.fetch = origFetch;\n });\n }\n\n private installConsoleWrap(): void {\n const orig = console.error.bind(console);\n const tracker = this;\n console.error = (...args: unknown[]): void => {\n try {\n if (tracker.opts.isConsented()) {\n tracker.captureMessage(args.map((a) => safeStringify(a)).join(\" \"), \"error\");\n }\n } catch {\n // swallow\n }\n return orig(...args);\n };\n this.cleanups.push(() => {\n console.error = orig;\n });\n }\n\n // ============================================================\n // Builders\n // ============================================================\n\n /**\n * Build a `CapturedError` from any value. Handles:\n * - Error instances (the common case) — parses `err.stack` into\n * frames, fingerprints over message + top in-app frames.\n * - Non-Error rejections (promise rejected with a string / number\n * / plain object) — coerces via `safeStringify`, no frames.\n *\n * Verbatim port of web's `buildFromUnknown` — the logic is\n * runtime-agnostic.\n */\n private buildFromUnknown(\n err: unknown,\n kind: CapturedError[\"kind\"],\n level: ErrorLevel,\n ): CapturedError {\n const payload = coerceErrorPayload(err);\n const message = (payload.message || \"Unknown error\").slice(0, 1024);\n const stack = err instanceof Error ? err.stack ?? null : null;\n const frames = parseStack(stack);\n const errorType = payload.errorType ?? null;\n\n const context = payload.extras\n ? { ...this.opts.getContext(), __error_extras: payload.extras }\n : this.opts.getContext();\n\n return {\n timestamp: Date.now(),\n kind,\n level,\n message,\n errorType,\n frames,\n rawStack: stack,\n // Location fallback ensures distinct call sites stay separate\n // even when the message is generic and there are no parseable\n // frames (e.g. `throw \"boom\"` from a middleware).\n fingerprint: fingerprintError(message, frames, {\n filename: frames[0]?.filename ?? null,\n lineno: frames[0]?.lineno ?? null,\n errorType,\n }),\n breadcrumbs: this.opts.breadcrumbs.snapshot(),\n context,\n tags: this.opts.getTags(),\n };\n }\n\n private captureHttp(info: {\n url: string;\n method: string;\n status: number;\n statusText?: string;\n }): void {\n try {\n const message = `HTTP ${info.status} ${info.method} ${info.url}`;\n const captured: CapturedError = {\n timestamp: Date.now(),\n kind: \"error.http\",\n level: \"error\",\n message,\n errorType: \"HTTPError\",\n frames: [],\n rawStack: null,\n fingerprint: fingerprintError(`HTTP ${info.status} ${info.method}`, [], {\n filename: info.url,\n errorType: \"HTTPError\",\n }),\n breadcrumbs: this.opts.breadcrumbs.snapshot(),\n context: this.opts.getContext(),\n tags: this.opts.getTags(),\n http: info,\n };\n this.maybeReport(captured);\n } catch {\n // swallow\n }\n }\n\n // ============================================================\n // Reporting pipeline — filter / sample / rate-limit / send\n // ============================================================\n\n private maybeReport(err: CapturedError): void {\n if (this.sessionCount >= this.opts.config.maxPerSession) return;\n if (this.shouldIgnore(err)) return;\n if (!this.passesPathGate(err)) return;\n if (!this.passesSample(err)) return;\n if (!this.passesRateLimit(err)) return;\n\n // beforeSend hook — last chance to scrub or drop.\n let finalErr: CapturedError | null = err;\n if (this.opts.beforeSend) {\n try {\n finalErr = this.opts.beforeSend(err);\n } catch {\n // A buggy beforeSend hook must NOT swallow the error report.\n // Fall back to the original.\n finalErr = err;\n }\n if (!finalErr) return;\n }\n\n this.sessionCount += 1;\n try {\n this.opts.report(finalErr);\n } catch {\n // swallow — report() failure is best-effort; the next error\n // attempt will retry through the same queue.\n }\n }\n\n private shouldIgnore(err: CapturedError): boolean {\n for (const pat of this.opts.config.ignoreErrors) {\n if (typeof pat === \"string\" && err.message.includes(pat)) return true;\n if (pat instanceof RegExp && pat.test(err.message)) return true;\n }\n return false;\n }\n\n private passesPathGate(err: CapturedError): boolean {\n // Check the top frame's filename (best-effort — many error.http\n // events have no frames). When the URL is unknown, let it\n // through.\n const topFrame = err.frames.find((f) => f.filename) ?? null;\n const path = topFrame?.filename ?? \"\";\n if (!path) return true;\n\n for (const pat of this.opts.config.denyPaths) {\n if (typeof pat === \"string\" && path.includes(pat)) return false;\n if (pat instanceof RegExp && pat.test(path)) return false;\n }\n if (this.opts.config.allowPaths.length > 0) {\n for (const pat of this.opts.config.allowPaths) {\n if (typeof pat === \"string\" && path.includes(pat)) return true;\n if (pat instanceof RegExp && pat.test(path)) return true;\n }\n return false;\n }\n return true;\n }\n\n private passesSample(err: CapturedError): boolean {\n if (this.opts.config.sampleRate >= 1) return true;\n if (this.opts.config.sampleRate <= 0) return false;\n // Deterministic per-fingerprint sampling — a given fingerprint\n // always either always sends or never does, no flapping.\n const hashByte = parseInt(err.fingerprint.slice(0, 2), 16);\n return hashByte / 255 < this.opts.config.sampleRate;\n }\n\n private passesRateLimit(err: CapturedError): boolean {\n const now = Date.now();\n const max = this.opts.config.maxPerFingerprintPerMinute;\n const arr = this.fingerprintWindow.get(err.fingerprint) ?? [];\n const fresh = arr.filter((t) => now - t < FINGERPRINT_WINDOW_MS);\n if (fresh.length >= max) {\n this.fingerprintWindow.set(err.fingerprint, fresh);\n return false;\n }\n fresh.push(now);\n this.fingerprintWindow.set(err.fingerprint, fresh);\n this.maybePruneFingerprintWindow(now);\n return true;\n }\n\n /**\n * Bound the fingerprint Map's memory footprint. Runs opportunistically\n * — only when the Map exceeds `MAX_FINGERPRINTS_TRACKED`. First pass:\n * delete entries whose ENTIRE window is stale (no live timestamps\n * inside the 60s window). Second pass (if still over): FIFO-evict\n * the oldest entries by Map insertion order until we're under the\n * cap. Defends against a long-running process with high-cardinality\n * fingerprints leaking memory forever.\n */\n private maybePruneFingerprintWindow(now: number): void {\n if (this.fingerprintWindow.size <= MAX_FINGERPRINTS_TRACKED) return;\n // Pass 1 — drop entries whose entire window is stale.\n for (const [fp, timestamps] of this.fingerprintWindow) {\n const hasLive = timestamps.some((t) => now - t < FINGERPRINT_WINDOW_MS);\n if (!hasLive) this.fingerprintWindow.delete(fp);\n }\n if (this.fingerprintWindow.size <= MAX_FINGERPRINTS_TRACKED) return;\n // Pass 2 — FIFO evict the oldest until under cap.\n const overflow = this.fingerprintWindow.size - MAX_FINGERPRINTS_TRACKED;\n let dropped = 0;\n for (const fp of this.fingerprintWindow.keys()) {\n if (dropped >= overflow) break;\n this.fingerprintWindow.delete(fp);\n dropped += 1;\n }\n }\n}\n\n/**\n * The thrown-value coercer.\n *\n * Node's error pipelines (process.on('uncaughtException'),\n * process.on('unhandledRejection'), developer `throw`) hand us values\n * of every shape — Error instances, AggregateError, plain objects,\n * primitives, even null. Earlier versions of this code wrote \"Unknown\n * error\" whenever the value wasn't an Error with a non-empty\n * `.message`, which silently collapsed entire classes of real bugs\n * into one unhelpful bucket.\n *\n * Returns three pieces (never throws):\n *\n * - message: human-readable headline, never empty for any\n * non-null/non-undefined input\n * - errorType: constructor name when discoverable (Error subclass,\n * AggregateError, custom class)\n * - extras: additional fields worth keeping (Error.cause chain,\n * .code/.errno/.statusCode/.response on common Node\n * patterns, AggregateError.errors[], any enumerable\n * own properties on an Error subclass). Stashed on\n * context.__error_extras for the dashboard's \"raw\n * event\" panel.\n */\ninterface CoercedPayload {\n message: string;\n errorType: string | null;\n extras: Record<string, unknown> | null;\n}\n\nfunction coerceErrorPayload(v: unknown): CoercedPayload {\n if (v === null) return { message: \"(thrown: null)\", errorType: null, extras: null };\n if (v === undefined) return { message: \"(thrown: undefined)\", errorType: null, extras: null };\n\n if (typeof v === \"string\") {\n return { message: v, errorType: null, extras: null };\n }\n if (typeof v === \"number\" || typeof v === \"boolean\" || typeof v === \"bigint\") {\n return { message: String(v), errorType: typeof v, extras: null };\n }\n if (typeof v === \"symbol\") {\n return { message: v.toString(), errorType: \"symbol\", extras: null };\n }\n if (typeof v === \"function\") {\n return { message: `(thrown function: ${v.name || \"anonymous\"})`, errorType: \"function\", extras: null };\n }\n\n // Error instances — including AggregateError (Node 16+, thrown by\n // Promise.any when all inputs reject).\n if (v instanceof Error) {\n const errorType = v.name || v.constructor?.name || \"Error\";\n const message =\n typeof v.message === \"string\" && v.message.length > 0\n ? v.message\n : safeToString(v) || errorType;\n\n const extras: Record<string, unknown> = {};\n\n // ES2022 Error.cause — walk up to 5 levels so a service-layer\n // wrapper error doesn't hide the underlying network failure.\n const causeChain = collectCauseChain(v);\n if (causeChain.length > 0) extras.cause = causeChain;\n\n // AggregateError carries an `errors` array of the underlying\n // rejections. Without surfacing this, the user just sees\n // \"AggregateError: All promises were rejected\" with no clue\n // which one failed.\n const aggErrors = (v as unknown as { errors?: unknown }).errors;\n if (Array.isArray(aggErrors)) {\n extras.aggregatedErrors = aggErrors.slice(0, 10).map((inner) => {\n if (inner instanceof Error) {\n return { name: inner.name || \"Error\", message: inner.message || \"\" };\n }\n return { name: \"non-Error\", message: safeToString(inner) };\n });\n }\n\n // Common Node error patterns attach code / errno / syscall /\n // statusCode / response to thrown values. Capture them without\n // forcing every wrapper class to override toString.\n for (const key of [\n \"code\", \"errno\", \"syscall\", \"path\",\n \"status\", \"statusCode\", \"response\", \"data\", \"detail\", \"details\",\n ] as const) {\n const val = (v as unknown as Record<string, unknown>)[key];\n if (val !== undefined && typeof val !== \"function\") {\n extras[key] = safeClone(val);\n }\n }\n\n // Any other enumerable own properties (custom Error subclasses\n // that add fields).\n for (const key of Object.keys(v)) {\n if (key === \"message\" || key === \"stack\" || key === \"name\" || key === \"cause\" || key === \"errors\") continue;\n if (key in extras) continue;\n const val = (v as unknown as Record<string, unknown>)[key];\n if (typeof val === \"function\") continue;\n extras[key] = safeClone(val);\n }\n\n return {\n message,\n errorType,\n extras: Object.keys(extras).length > 0 ? extras : null,\n };\n }\n\n // Response — fetch().then(r => { if (!r.ok) throw r }) is a common\n // Node 18+ pattern (built-in fetch), and the bare Response is\n // otherwise unreadable.\n if (typeof Response !== \"undefined\" && v instanceof Response) {\n return {\n message: `HTTP ${v.status} ${v.statusText || \"\"}${v.url ? ` ${v.url}` : \"\"}`.trim(),\n errorType: \"Response\",\n extras: { status: v.status, statusText: v.statusText, url: v.url, type: v.type },\n };\n }\n\n // Plain objects / custom classes that don't extend Error.\n if (typeof v === \"object\") {\n const obj = v as Record<string, unknown>;\n const ctorName =\n (obj.constructor && typeof obj.constructor === \"function\" && (obj.constructor as { name?: string }).name) ||\n null;\n\n const ownMessage = typeof obj.message === \"string\" && obj.message ? obj.message : null;\n const ownName = typeof obj.name === \"string\" && obj.name ? obj.name : null;\n\n let jsonForm: string | null = null;\n try {\n const serialised = JSON.stringify(obj);\n jsonForm = serialised === \"{}\" ? null : serialised;\n } catch {\n jsonForm = null;\n }\n\n const fallbackString = safeToString(obj);\n const message =\n ownMessage ??\n jsonForm ??\n (fallbackString && fallbackString !== \"[object Object]\" ? fallbackString : null) ??\n (ctorName ? `(thrown ${ctorName} with no message)` : \"(thrown object with no message)\");\n\n const errorType = ownName ?? ctorName ?? null;\n\n const extras: Record<string, unknown> = {};\n let count = 0;\n for (const key of Object.keys(obj)) {\n if (count >= 20) break;\n if (key === \"message\" || key === \"name\") continue;\n const val = obj[key];\n if (typeof val === \"function\") continue;\n extras[key] = safeClone(val);\n count++;\n }\n\n return {\n message,\n errorType,\n extras: Object.keys(extras).length > 0 ? extras : null,\n };\n }\n\n return { message: safeToString(v) || \"(unstringifiable thrown value)\", errorType: null, extras: null };\n}\n\nfunction collectCauseChain(err: Error): Array<{ name: string; message: string }> {\n const out: Array<{ name: string; message: string }> = [];\n let cur: unknown = (err as Error & { cause?: unknown }).cause;\n let depth = 0;\n while (cur != null && depth < 5) {\n if (cur instanceof Error) {\n out.push({ name: cur.name || \"Error\", message: cur.message || \"\" });\n cur = (cur as Error & { cause?: unknown }).cause;\n } else {\n out.push({ name: \"non-Error\", message: safeToString(cur) });\n cur = null;\n }\n depth++;\n }\n return out;\n}\n\nfunction safeToString(v: unknown): string {\n try {\n const s = Object.prototype.toString.call(v);\n if (s !== \"[object Object]\") return s;\n const own = (v as { toString?: () => unknown })?.toString;\n if (typeof own === \"function\" && own !== Object.prototype.toString) {\n const r = own.call(v);\n if (typeof r === \"string\") return r;\n }\n return s;\n } catch {\n return \"(throwing toString)\";\n }\n}\n\nfunction safeClone(v: unknown): unknown {\n if (v == null) return v;\n const t = typeof v;\n if (t === \"string\" || t === \"number\" || t === \"boolean\") return v;\n if (t === \"bigint\") return String(v);\n try {\n const s = JSON.stringify(v);\n return s === undefined ? safeToString(v) : JSON.parse(s);\n } catch {\n return safeToString(v);\n }\n}\n\nfunction safeStringify(v: unknown): string {\n return coerceErrorPayload(v).message;\n}\n","/**\n * Runtime info enrichment — the Node SDK equivalent of\n * `@cross-deck/web/src/device-info.ts`.\n *\n * Detects the host platform (Lambda / Firebase Functions v1 / v2 /\n * Cloud Run / Vercel / plain Node), region, service name + version,\n * and instance ID. Auto-merged into every event's `properties` and\n * every captured-error's `runtime` block.\n *\n * Privacy posture (parity with web's device-info.ts):\n * - No fingerprinting / hardware identifiers.\n * - No precise geolocation (region only — the platform's own metadata).\n * - No IP collection (backend logs the request IP for rate-limit\n * purposes; not stored on the event document).\n *\n * Detection runs ONCE per process — the returned `RuntimeInfo` is a\n * frozen reference cached at module level. Zero per-event overhead.\n * Caller-supplied overrides (serviceName / serviceVersion / appVersion\n * via `CrossdeckServer` options) win over env-derived values on the\n * first call — that's the SDK constructor.\n */\n\nimport { hostname as osHostname, platform as osPlatform, release as osRelease } from \"node:os\";\n\nexport type RuntimeHost =\n | \"aws-lambda\"\n | \"azure-functions\"\n | \"google-app-engine\"\n | \"firebase-functions-v1\"\n | \"firebase-functions-v2\"\n | \"cloud-run\"\n | \"vercel\"\n | \"netlify\"\n | \"heroku\"\n | \"render\"\n | \"railway\"\n | \"fly\"\n | \"kubernetes\"\n | \"node\";\n\n/**\n * Hosts where a cold start begins with empty process memory — FaaS and\n * serverless-container platforms that scale to zero and spin up a fresh\n * instance per burst of traffic.\n *\n * Drives `RuntimeInfo.isServerless`. The entitlement-cache durability\n * layer treats a host in this set with no `entitlementStore` as having\n * no cold-start durability. Long-lived process hosts (Heroku, Render,\n * Railway, Fly, Kubernetes, plain Node) are deliberately ABSENT — their\n * process, and so the in-memory cache, survives between requests.\n */\nconst SERVERLESS_HOSTS: ReadonlySet<RuntimeHost> = new Set<RuntimeHost>([\n \"aws-lambda\",\n \"azure-functions\",\n \"google-app-engine\",\n \"firebase-functions-v1\",\n \"firebase-functions-v2\",\n \"cloud-run\",\n \"vercel\",\n \"netlify\",\n]);\n\nexport interface RuntimeInfo {\n nodeVersion: string;\n /** `os.platform()` — \"darwin\" | \"linux\" | \"win32\" | … */\n platform: string;\n /** `os.release()` — kernel release string, e.g. \"5.15.0-1071-aws\". */\n platformRelease: string;\n hostname: string;\n host: RuntimeHost;\n /**\n * Whether the host is a scale-to-zero / per-request-instance platform\n * where a cold start begins with empty process memory.\n *\n * `true` for FaaS + serverless-container platforms (Lambda, Cloud\n * Run, Firebase Functions v1/v2, Vercel, Netlify, Azure Functions,\n * App Engine). `false` for long-lived process hosts (Heroku, Render,\n * Railway, Fly, Kubernetes, plain Node) where the process — and thus\n * the in-memory entitlement cache — persists across requests.\n *\n * The entitlement-cache durability layer reads this: a serverless\n * host with no `entitlementStore` has no cold-start durability, and\n * the SDK surfaces that explicitly (debug warning + a `durability`\n * fact on the boot telemetry event).\n */\n isServerless: boolean;\n region: string | null;\n serviceName: string | null;\n serviceVersion: string | null;\n /**\n * Process-stable ID. Lambda log stream name when on Lambda; revision +\n * pid on Cloud Run / Firebase v2; pid as string otherwise. Used by the\n * dashboard to distinguish events from different instances of the same\n * function name + version.\n */\n instanceId: string | null;\n /** Caller-supplied app version. Attached as `appVersion` on every event. */\n appVersion: string | null;\n}\n\nexport interface RuntimeInfoOptions {\n serviceName?: string;\n serviceVersion?: string;\n appVersion?: string;\n}\n\nlet cached: RuntimeInfo | null = null;\n\n/**\n * Collect runtime info. Detection runs once per process; subsequent\n * calls return the same cached frozen object. Caller-supplied options\n * take effect on the FIRST call — that's by design, the SDK constructor\n * passes them once and downstream call sites read.\n */\nexport function collectRuntimeInfo(options: RuntimeInfoOptions = {}): RuntimeInfo {\n if (cached) return cached;\n cached = detect(options);\n return cached;\n}\n\n/**\n * Re-detect. Tests + multi-instance scenarios only. Not called from the\n * SDK boot path — runtime info is logically immutable for a process's\n * lifetime.\n */\nexport function resetRuntimeInfoCache(): void {\n cached = null;\n}\n\nfunction detect(options: RuntimeInfoOptions): RuntimeInfo {\n // Defensive: edge runtimes (Workers, Bun-in-some-configs, sandboxed\n // hosts) may have a partial or absent `process` object. Fall back\n // to an empty env so detection just returns the \"node\" host.\n const env: NodeJS.ProcessEnv =\n typeof process !== \"undefined\" && process.env ? process.env : ({} as NodeJS.ProcessEnv);\n const detected = detectHost(env);\n return Object.freeze({\n nodeVersion: typeof process !== \"undefined\" && process.versions ? process.versions.node : \"unknown\",\n platform: safePlatform(),\n platformRelease: safeRelease(),\n hostname: safeHostname(),\n host: detected.host,\n isServerless: SERVERLESS_HOSTS.has(detected.host),\n region: detected.region,\n serviceName: options.serviceName ?? detected.serviceName,\n serviceVersion: options.serviceVersion ?? detected.serviceVersion,\n instanceId: detected.instanceId,\n appVersion: options.appVersion ?? null,\n });\n}\n\ninterface HostDetection {\n host: RuntimeHost;\n region: string | null;\n serviceName: string | null;\n serviceVersion: string | null;\n instanceId: string | null;\n}\n\n/**\n * Host detection — order matters. More-specific signals first:\n * - AWS Lambda: `AWS_LAMBDA_FUNCTION_NAME` (unique to Lambda).\n * - Azure Functions: `FUNCTIONS_WORKER_RUNTIME` + `WEBSITE_INSTANCE_ID`.\n * - Google App Engine: `GAE_APPLICATION` (gen-1 + gen-2 standard) —\n * checked before K_SERVICE because GAE gen-2 sets BOTH and we\n * want the more specific label.\n * - Firebase v2 / Cloud Run: share `K_SERVICE` + `K_REVISION`. The\n * difference is `FIREBASE_CONFIG` / `GCLOUD_PROJECT` (set by the\n * Firebase deploy chain, not by plain Cloud Run).\n * - Firebase v1: `FUNCTION_NAME` + `FUNCTION_REGION` (the legacy\n * pair, still used by gen-1 deployments).\n * - Vercel: `VERCEL === \"1\"`.\n * - Netlify Functions: `NETLIFY === \"true\"` or `AWS_LAMBDA_FUNCTION_NAME`\n * prefixed (Netlify runs on Lambda under the hood but we already\n * captured Lambda first; the `NETLIFY=true` check identifies\n * Netlify Functions specifically when Lambda env vars aren't set).\n * - Heroku: `DYNO` (the dyno identifier; uniquely Heroku).\n * - Render: `RENDER === \"true\"` + `RENDER_INSTANCE_ID`.\n * - Railway: `RAILWAY_ENVIRONMENT`.\n * - Fly.io: `FLY_APP_NAME` + `FLY_REGION`.\n * - Kubernetes: `KUBERNETES_SERVICE_HOST` — generic fallback for\n * containerised Node not on a more-specific platform.\n * - Else: plain Node — long-lived servers, dev machines, test runs.\n */\nfunction detectHost(env: NodeJS.ProcessEnv): HostDetection {\n const pid = safePid();\n\n if (env.AWS_LAMBDA_FUNCTION_NAME) {\n return {\n host: \"aws-lambda\",\n region: env.AWS_REGION ?? null,\n serviceName: env.AWS_LAMBDA_FUNCTION_NAME,\n serviceVersion: env.AWS_LAMBDA_FUNCTION_VERSION ?? null,\n instanceId: env.AWS_LAMBDA_LOG_STREAM_NAME ?? pid,\n };\n }\n\n if (env.FUNCTIONS_WORKER_RUNTIME && env.WEBSITE_INSTANCE_ID) {\n return {\n host: \"azure-functions\",\n region: env.REGION_NAME ?? env.WEBSITE_LOCATION ?? null,\n serviceName: env.WEBSITE_SITE_NAME ?? null,\n serviceVersion: env.WEBSITE_BUILD_ID ?? null,\n instanceId: env.WEBSITE_INSTANCE_ID,\n };\n }\n\n if (env.GAE_APPLICATION) {\n return {\n host: \"google-app-engine\",\n region: env.GAE_REGION ?? env.GOOGLE_CLOUD_REGION ?? null,\n serviceName: env.GAE_SERVICE ?? null,\n serviceVersion: env.GAE_VERSION ?? null,\n instanceId: env.GAE_INSTANCE ?? pid,\n };\n }\n\n if (env.K_SERVICE && env.K_REVISION) {\n const isFirebase = Boolean(env.FIREBASE_CONFIG || env.GCLOUD_PROJECT);\n return {\n host: isFirebase ? \"firebase-functions-v2\" : \"cloud-run\",\n region: env.FUNCTION_REGION ?? env.GOOGLE_CLOUD_REGION ?? null,\n serviceName: env.K_SERVICE,\n serviceVersion: env.K_REVISION,\n instanceId: `${env.K_REVISION}:${pid}`,\n };\n }\n\n if (env.FUNCTION_NAME && env.FUNCTION_REGION) {\n return {\n host: \"firebase-functions-v1\",\n region: env.FUNCTION_REGION,\n serviceName: env.FUNCTION_NAME,\n serviceVersion: env.X_GOOGLE_FUNCTION_VERSION ?? null,\n instanceId: pid,\n };\n }\n\n if (env.VERCEL === \"1\") {\n return {\n host: \"vercel\",\n region: env.VERCEL_REGION ?? null,\n serviceName: env.VERCEL_URL ?? null,\n serviceVersion: env.VERCEL_GIT_COMMIT_SHA?.slice(0, 7) ?? null,\n instanceId: pid,\n };\n }\n\n if (env.NETLIFY === \"true\" || env.NETLIFY_BUILD_BASE) {\n return {\n host: \"netlify\",\n region: env.AWS_REGION ?? null,\n serviceName: env.SITE_NAME ?? env.SITE_ID ?? null,\n serviceVersion: env.COMMIT_REF?.slice(0, 7) ?? null,\n instanceId: pid,\n };\n }\n\n if (env.DYNO) {\n return {\n host: \"heroku\",\n region: null,\n serviceName: env.HEROKU_APP_NAME ?? null,\n serviceVersion: env.HEROKU_RELEASE_VERSION ?? env.HEROKU_SLUG_COMMIT?.slice(0, 7) ?? null,\n instanceId: env.DYNO,\n };\n }\n\n if (env.RENDER === \"true\" || env.RENDER_INSTANCE_ID) {\n return {\n host: \"render\",\n region: env.RENDER_SERVICE_REGION ?? null,\n serviceName: env.RENDER_SERVICE_NAME ?? null,\n serviceVersion: env.RENDER_GIT_COMMIT?.slice(0, 7) ?? null,\n instanceId: env.RENDER_INSTANCE_ID ?? pid,\n };\n }\n\n if (env.RAILWAY_ENVIRONMENT) {\n return {\n host: \"railway\",\n region: env.RAILWAY_REGION ?? null,\n serviceName: env.RAILWAY_SERVICE_NAME ?? null,\n serviceVersion: env.RAILWAY_GIT_COMMIT_SHA?.slice(0, 7) ?? null,\n instanceId: env.RAILWAY_REPLICA_ID ?? pid,\n };\n }\n\n if (env.FLY_APP_NAME) {\n return {\n host: \"fly\",\n region: env.FLY_REGION ?? null,\n serviceName: env.FLY_APP_NAME,\n serviceVersion: env.FLY_IMAGE_REF?.slice(-7) ?? null,\n instanceId: env.FLY_ALLOC_ID ?? pid,\n };\n }\n\n if (env.KUBERNETES_SERVICE_HOST) {\n return {\n host: \"kubernetes\",\n region: null,\n serviceName: env.POD_NAME ?? env.HOSTNAME ?? null,\n serviceVersion: null,\n instanceId: env.POD_NAME ?? env.HOSTNAME ?? pid,\n };\n }\n\n return {\n host: \"node\",\n region: null,\n serviceName: null,\n serviceVersion: null,\n instanceId: pid,\n };\n}\n\nfunction safeHostname(): string {\n try {\n return osHostname();\n } catch {\n return \"unknown\";\n }\n}\n\nfunction safePlatform(): string {\n try {\n return osPlatform();\n } catch {\n return \"unknown\";\n }\n}\n\nfunction safeRelease(): string {\n try {\n return osRelease();\n } catch {\n return \"unknown\";\n }\n}\n\nfunction safePid(): string {\n try {\n return typeof process !== \"undefined\" && process.pid ? String(process.pid) : \"0\";\n } catch {\n return \"0\";\n }\n}\n\n/**\n * Flatten `RuntimeInfo` into a property bag suitable for merging onto\n * every event. Keys are namespaced under `runtime.*` to keep top-level\n * event properties clean and to match the dashboard's runtime column\n * group.\n *\n * Null fields are omitted so downstream property bags don't fill with\n * empty columns.\n */\nexport function runtimeInfoToProperties(info: RuntimeInfo): Record<string, unknown> {\n const out: Record<string, unknown> = {\n \"runtime.nodeVersion\": info.nodeVersion,\n \"runtime.platform\": info.platform,\n \"runtime.platformRelease\": info.platformRelease,\n \"runtime.hostname\": info.hostname,\n \"runtime.host\": info.host,\n };\n if (info.region) out[\"runtime.region\"] = info.region;\n if (info.serviceName) out[\"runtime.serviceName\"] = info.serviceName;\n if (info.serviceVersion) out[\"runtime.serviceVersion\"] = info.serviceVersion;\n if (info.instanceId) out[\"runtime.instanceId\"] = info.instanceId;\n if (info.appVersion) out.appVersion = info.appVersion;\n return out;\n}\n","/**\n * Process-exit drain hook.\n *\n * Defends against the silent-loss failure mode that motivated the\n * entire v1.0.0 port: a Cloud Function cold-starts, fires 3 events\n * synchronously, and the process exits before the HTTP POSTs complete.\n * Without this module, those events vanish without trace.\n *\n * With it, we install handlers for `beforeExit` + `SIGTERM` + `SIGINT`\n * that synchronously await `queue.flush()` before the process is\n * allowed to terminate. Bounded by `timeoutMs` (default 2000ms) so a\n * misbehaving server can't keep the function alive past the platform's\n * SIGKILL window (typically 5-10s after SIGTERM).\n *\n * Idempotency:\n * - `install()` returns immediately if already installed.\n * - Multiple signals (e.g. SIGTERM then beforeExit) only drain once.\n * - `uninstall()` removes the listeners.\n *\n * Why not just rely on `process.on('exit')`: by the time 'exit' fires,\n * the event loop is dead. No async work can run. `beforeExit` is the\n * last point at which the loop is still alive and we can `await\n * flush()` properly. SIGTERM + SIGINT are the platform's \"you're being\n * terminated\" signals — Cloud Run fires SIGTERM on container stop;\n * Lambda fires SIGTERM on idle termination; SIGINT is the user's\n * Ctrl-C in dev.\n */\n\nexport interface FlushOnExitOptions {\n /**\n * Async drain function — typically `() => server.flush()`. May reject\n * or hang; we bound it with `timeoutMs`.\n */\n drain: () => Promise<unknown>;\n /**\n * Bounded timeout for the drain (ms). Default 2000.\n *\n * Two seconds is enough to flush a handful of events over a healthy\n * network without holding up function teardown past the platform's\n * SIGKILL window.\n */\n timeoutMs?: number;\n /**\n * Optional callback fired when the drain starts. Wired by the SDK\n * debug logger to emit `sdk.flush_on_exit_started`.\n */\n onStart?: () => void;\n /**\n * Optional callback fired when the drain completes (success or\n * timeout). Wired by the SDK debug logger to emit\n * `sdk.flush_on_exit_completed`. Receives `{ durationMs, timedOut }`.\n */\n onComplete?: (info: { durationMs: number; timedOut: boolean }) => void;\n /**\n * Optional callback fired when the drain throws. Receives the error.\n * The handler always allows the process to exit — drain failure is\n * observation, not a block.\n */\n onError?: (err: unknown) => void;\n}\n\ntype Signal = \"SIGTERM\" | \"SIGINT\";\nconst SIGNALS: readonly Signal[] = [\"SIGTERM\", \"SIGINT\"];\n\nconst DEFAULT_TIMEOUT_MS = 2000;\n\nexport class FlushOnExit {\n private installed = false;\n private draining = false;\n private drained = false;\n private beforeExitHandler: (() => void) | null = null;\n private signalHandlers: Partial<Record<Signal, () => void>> = {};\n\n constructor(private readonly options: FlushOnExitOptions) {}\n\n /**\n * Install handlers for `beforeExit` + `SIGTERM` + `SIGINT`. Idempotent —\n * calling twice does NOT register duplicate handlers.\n */\n install(): void {\n if (this.installed) return;\n this.installed = true;\n\n this.beforeExitHandler = () => {\n // `beforeExit` fires when the event loop has no more work AND\n // there's no async pending. We're allowed to start more async\n // here — the loop will re-tick to drain it. After we return\n // from awaiting drain, `beforeExit` will NOT re-fire (Node\n // guards against re-entry).\n void this.runDrain(\"beforeExit\");\n };\n process.on(\"beforeExit\", this.beforeExitHandler);\n\n for (const sig of SIGNALS) {\n const handler = (): void => {\n void this.runDrainAndExit(sig);\n };\n this.signalHandlers[sig] = handler;\n process.on(sig, handler);\n }\n }\n\n /**\n * Remove all handlers. Tests + custom-lifecycle callers only.\n */\n uninstall(): void {\n if (!this.installed) return;\n this.installed = false;\n if (this.beforeExitHandler) {\n process.off(\"beforeExit\", this.beforeExitHandler);\n this.beforeExitHandler = null;\n }\n for (const sig of SIGNALS) {\n const handler = this.signalHandlers[sig];\n if (handler) {\n process.off(sig, handler);\n delete this.signalHandlers[sig];\n }\n }\n }\n\n /**\n * Force-drain immediately (without waiting for an exit signal).\n * Used by `wrapLambdaHandler` / `wrapFunction` — Lambda freezes the\n * process between invocations, so we drain at the END of each\n * invocation rather than waiting for SIGTERM.\n */\n async drainNow(): Promise<void> {\n return this.runDrain(\"manual\");\n }\n\n /** True if the drain has already completed (one-shot lifecycle). */\n get hasDrained(): boolean {\n return this.drained;\n }\n\n /** True if a drain is in flight. */\n get isDraining(): boolean {\n return this.draining;\n }\n\n // ---------- internals ----------\n\n private async runDrain(_reason: \"beforeExit\" | \"SIGTERM\" | \"SIGINT\" | \"manual\"): Promise<void> {\n // One-shot — multiple signals during the same process must not\n // double-drain. The queue has been spliced empty after the first\n // call; subsequent flushes would be no-ops, but we also have to\n // worry about a race where two signals fire in quick succession.\n if (this.drained || this.draining) return;\n this.draining = true;\n this.options.onStart?.();\n\n const start = Date.now();\n const timeoutMs = this.options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n let timedOut = false;\n let drainError: unknown = null;\n\n // Race the drain against the timeout. The drain's rejection is\n // surfaced via `drainError` (not by rethrowing) so we can ALWAYS\n // fire `onComplete` and ALWAYS fire `onError` when the drain\n // threw — and never leave the process hanging on a swallowed\n // rejection.\n await new Promise<void>((resolve) => {\n let settled = false;\n const timer = setTimeout(() => {\n if (settled) return;\n settled = true;\n timedOut = true;\n resolve();\n }, timeoutMs);\n // `.unref()` so the timer itself doesn't keep the loop alive\n // past the drain — otherwise a fast drain would still wait the\n // full timeout before letting `beforeExit` complete.\n if (typeof timer.unref === \"function\") {\n try {\n timer.unref();\n } catch {\n // ignore\n }\n }\n\n let drainPromise: Promise<unknown>;\n try {\n drainPromise = this.options.drain();\n } catch (syncErr) {\n // The drain function threw synchronously before returning a\n // promise. Treat the same as an async rejection.\n if (settled) return;\n settled = true;\n drainError = syncErr;\n clearTimeout(timer);\n resolve();\n return;\n }\n\n drainPromise.then(\n () => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n resolve();\n },\n (err) => {\n if (settled) return;\n settled = true;\n drainError = err;\n clearTimeout(timer);\n resolve();\n },\n );\n });\n\n if (drainError !== null) {\n try {\n this.options.onError?.(drainError);\n } catch {\n // A buggy onError must NOT block process exit.\n }\n }\n\n this.draining = false;\n this.drained = true;\n this.options.onComplete?.({\n durationMs: Date.now() - start,\n timedOut,\n });\n }\n\n /**\n * Drain in response to a termination signal. After the drain\n * completes, we re-raise the signal so the process exits with the\n * correct exit code — `kill -TERM <pid>` should still terminate\n * the process even though we installed a handler that \"consumed\" it.\n *\n * Detail: Node attaches a default SIGTERM handler that exits the\n * process. The MOMENT we register our own handler with `process.on`,\n * the default is removed. So we have to re-raise to mimic the\n * default behaviour after our drain completes.\n */\n private async runDrainAndExit(sig: Signal): Promise<void> {\n await this.runDrain(sig);\n // Remove our handler so the re-raised signal hits the default.\n const handler = this.signalHandlers[sig];\n if (handler) {\n process.off(sig, handler);\n delete this.signalHandlers[sig];\n }\n // Re-raise. Exit code convention: 128 + signal number (SIGTERM=15\n // → 143; SIGINT=2 → 130). Node's process.kill() does this for us.\n try {\n process.kill(process.pid, sig);\n } catch {\n // Defensive: process.kill can fail if pid is somehow invalid.\n // Fall back to a hard exit with the conventional code.\n const code = sig === \"SIGTERM\" ? 143 : 130;\n process.exit(code);\n }\n }\n}\n\n","/**\n * Super-properties + group analytics — Mixpanel pattern.\n *\n * Super properties are key/value pairs the developer registers ONCE\n * via `server.register({ tenant: \"acme\" })` that get attached to\n * every subsequent event of THIS SDK instance. They're the single\n * most-used feature in Mixpanel-style analytics: \"every event from\n * this process should have `tenant` and `serviceName` on it\" instead\n * of remembering to pass them on every `track()` call.\n *\n * Groups are organisational identifiers: a customer might belong to\n * an `org` (\"acme\"), a `team` (\"design\"), and a `plan` (\"enterprise\").\n * Each event carries `$groups.{type}: id` so B2B dashboards can pivot:\n * \"Acme's team:design fired 142 paywall_shown events this week\".\n *\n * Node port differences from `@cross-deck/web/src/super-properties.ts`:\n * - No `KeyValueStorage` backing. In-memory only. Node processes are\n * short-lived (Lambda freezes between invocations, Cloud Functions\n * tear down containers); super-properties typically belong to the\n * SDK instance lifetime, not persistence-across-process.\n * - The Store reset clears both bags (parity with web's clear()).\n *\n * The store is reset on `server.shutdown()` — both super properties\n * and groups are cleared because their lifetime is tied to the SDK\n * instance, not to the process.\n */\n\nexport interface GroupMembership {\n id: string;\n traits?: Record<string, unknown>;\n}\n\nexport class SuperPropertyStore {\n private superProps: Record<string, unknown> = {};\n private groups: Record<string, GroupMembership> = {};\n\n /**\n * Merge new keys into the super-property bag. Returns a snapshot\n * of the resulting bag. Values that are `null` are deleted\n * (Mixpanel's explicit \"stop tracking this key\" idiom).\n */\n register(props: Record<string, unknown>): Record<string, unknown> {\n for (const [k, v] of Object.entries(props)) {\n if (v === null) {\n delete this.superProps[k];\n } else if (v !== undefined) {\n this.superProps[k] = v;\n }\n }\n return { ...this.superProps };\n }\n\n /** Remove a single super-property key. Idempotent. */\n unregister(key: string): void {\n if (key in this.superProps) {\n delete this.superProps[key];\n }\n }\n\n /** Defensive snapshot of the current super-property bag. */\n getSuperProperties(): Record<string, unknown> {\n return { ...this.superProps };\n }\n\n /**\n * Set a group membership. Passing `id: null` clears the membership\n * for that type — the SDK stops attaching it to events.\n */\n setGroup(type: string, id: string | null, traits?: Record<string, unknown>): void {\n if (id === null) {\n delete this.groups[type];\n } else {\n this.groups[type] = traits !== undefined ? { id, traits } : { id };\n }\n }\n\n /**\n * Defensive snapshot of the current groups map, keyed by group type.\n * The `traits` sub-object is the most-recent traits payload passed\n * to `setGroup` for that type.\n */\n getGroups(): Record<string, GroupMembership> {\n // Defensive deep copy — caller mutations to traits don't bleed\n // into the store.\n const out: Record<string, GroupMembership> = {};\n for (const [type, membership] of Object.entries(this.groups)) {\n out[type] = {\n id: membership.id,\n ...(membership.traits ? { traits: { ...membership.traits } } : {}),\n };\n }\n return out;\n }\n\n /**\n * Flat `{ type: id }` projection used for event-attachment. Stable\n * for fast every-event merge — we don't JSON-clone on each\n * `track()` call (hot path).\n */\n getGroupIds(): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [type, info] of Object.entries(this.groups)) {\n out[type] = info.id;\n }\n return out;\n }\n\n /** Wipe both bags. Called by `server.shutdown()`. */\n clear(): void {\n this.superProps = {};\n this.groups = {};\n }\n}\n","/**\n * Per-customer durable last-known-good entitlement cache — the third\n * Crossdeck USP on the server.\n *\n * Why this exists: server-side gating code looks like\n *\n * if (server.isEntitled(customerId, \"pro\")) { … }\n *\n * inside a request handler. Without a cache, every request makes an\n * HTTP round-trip to `GET /v1/entitlements?customerId=…` — 50-200ms\n * per request, every request, for every customer. The cache makes\n * `isEntitled()` a `Map.get()` after the first warm.\n *\n * Durability contract (mirrors `@cross-deck/web/src/entitlement-cache.ts`,\n * adapted for a multi-tenant server):\n * - This cache is NOT a second source of truth. Crossdeck remains the\n * only source; this is the SDK's local copy of what the server last\n * told us — a cache that does not forget during a network partition.\n * - Only a SUCCESSFUL fetch replaces a customer's entry (via\n * `setForCustomer`). A failed refresh never reaches it, so an outage\n * can never fail a paying customer down to free.\n * - **The TTL is a REFRESH HINT, not an invalidation.** `isEntitled()`\n * and `list()` keep serving last-known-good after `ttlMs` elapses —\n * they do NOT return `false` / `[]` because the entry aged. The TTL\n * only drives `needsRefresh()` (\"a re-fetch is due\") and, with no\n * failed refresh, the stale flag. This is the central fix: on\n * serverless a paying customer must not be locked out 60s after a\n * warm just because Crossdeck was briefly unreachable.\n * - Staleness alone never returns false. Each entitlement is honoured\n * against its OWN `validUntil` instead — a time-based trial expiry\n * still applies even mid-partition; a still-valid Pro entitlement\n * rides the outage out.\n * - Staleness is VISIBLE, not silent. `validUntil` covers time-based\n * expiry; it does NOT cover an event-based revoke (chargeback,\n * refund, fraud) — that has no `validUntil`, so the cache would keep\n * serving a revoked customer through an outage. Serving them is the\n * right trade (don't lock real payers out), but unbounded-and-\n * invisible is the bug. So once a refresh ATTEMPT fails\n * (`markRefreshFailed`) or the data ages past `staleAfterMs`, the\n * customer is flagged stale — `isStale()` / `staleCustomerCount` are\n * surfaced in `diagnostics()`. It keeps serving last-known-good; the\n * staleness is just no longer hidden.\n *\n * Cold-start durability lives one layer up: `getEntitlements()` in\n * `crossdeck-server.ts` persists every successful fetch to an optional\n * `EntitlementStore` and, on a network failure, loads last-known-good\n * back from it and into this cache. This cache stays a pure in-memory\n * structure with NO I/O — `isEntitled()` is and remains synchronous.\n *\n * Differences from the web SDK's cache:\n * - **Per-customer**, not singleton. Web SDK has one user per browser\n * tab; Node SDK has many users hitting one server. The cache is\n * keyed by `crossdeckCustomerId`. Staleness, freshness and the\n * failed-refresh marker are therefore all per-customer too.\n * - **No synchronous storage hydration** in the constructor. The web\n * SDK hydrates from `localStorage` on boot; the Node durable store\n * is async, so hydration happens lazily inside `getEntitlements()`.\n * - **LRU-bounded** by `maxCustomers` — a long-running multi-tenant\n * server would otherwise leak Map entries forever.\n * - **Subscriber API unchanged** — `subscribe(listener)` fires after\n * any mutation (set / clear). Passive LRU eviction and a TTL\n * elapsing are NOT mutations, by design.\n */\n\nimport type { PublicEntitlement } from \"./types\";\n\nexport type EntitlementsListener = (\n customerId: string,\n entitlements: PublicEntitlement[],\n) => void;\n\ninterface CacheEntry {\n /**\n * Full entitlement objects (active + inactive). `isEntitled()`\n * iterates these and checks `isActive` + each entitlement's own\n * `validUntil` inline — mirroring the web SDK — so no separate\n * active-key set is kept; that set could not carry `validUntil`.\n */\n all: PublicEntitlement[];\n /**\n * epoch ms after which a re-fetch is DUE. This is a refresh hint, NOT\n * an expiry: past this point `isEntitled()` / `list()` still serve\n * last-known-good. It only drives `needsRefresh()` and the stale flag.\n */\n refreshDueAt: number;\n /** epoch ms the entry was last populated by a successful fetch. */\n populatedAt: number;\n /**\n * epoch ms of the most recent FAILED refresh attempt for this\n * customer, or 0 if the last attempt succeeded. Set by\n * `markRefreshFailed`, cleared by `setForCustomer`. When this is more\n * recent than `populatedAt` the customer is knowingly stale.\n */\n refreshFailedAt: number;\n}\n\nexport interface EntitlementCacheOptions {\n /**\n * Refresh-hint TTL in ms. Default 60_000 (60s).\n *\n * After `ttlMs` a customer's entry is \"refresh due\" — `needsRefresh()`\n * returns true and the caller should re-fetch. It is NOT an expiry:\n * `isEntitled()` keeps serving last-known-good past it. `0` makes\n * every entry immediately refresh-due (useful for tests) but STILL\n * does not invalidate — last-known-good is served regardless.\n */\n ttlMs?: number;\n /**\n * Maximum number of customers cached at once. Long-running multi-tenant\n * servers handling a long tail of customers would otherwise leak Map\n * entries forever. Default 10_000 — enough for any realistic deployment.\n * When the cap is reached, the OLDEST entry (by insertion / refresh\n * order) is evicted to make room. Eviction does NOT fire listeners\n * (passive eviction is not a mutation by design).\n */\n maxCustomers?: number;\n /**\n * Age (ms) past which a customer's last-known-good data is flagged\n * STALE even with no failed refresh. Default 24h.\n *\n * Staleness never changes what `isEntitled()` returns; it only makes a\n * long un-refreshed window observable via `isStale()` / diagnostics —\n * so an event-based revoke (no `validUntil`) riding out an outage is\n * visible instead of silent.\n */\n staleAfterMs?: number;\n}\n\nconst DEFAULT_MAX_CUSTOMERS = 10_000;\n/** Default staleness window — data older than this is flagged even with no failed refresh. */\nconst DEFAULT_STALE_AFTER_MS = 24 * 60 * 60 * 1000; // 24h\n\nexport class EntitlementCache {\n private readonly ttlMs: number;\n private readonly maxCustomers: number;\n private readonly staleAfterMs: number;\n private byCustomer = new Map<string, CacheEntry>();\n private listeners = new Set<EntitlementsListener>();\n private listenerErrorCount = 0;\n private evicted = 0;\n\n constructor(options: EntitlementCacheOptions = {}) {\n this.ttlMs = options.ttlMs ?? 60_000;\n this.maxCustomers = options.maxCustomers ?? DEFAULT_MAX_CUSTOMERS;\n this.staleAfterMs = options.staleAfterMs ?? DEFAULT_STALE_AFTER_MS;\n }\n\n /**\n * Synchronous lookup — true iff the customer currently has the\n * entitlement granting access. Pure in-memory `Map` read, ZERO I/O.\n *\n * Served from last-known-good: a stale entry (Crossdeck unreachable\n * since the last successful fetch, or past `ttlMs`) STILL answers true\n * for a still-valid entitlement. Cache staleness alone never makes\n * this `false` — the central durability fix. The only things that\n * turn it false:\n * - the customer has no cached entry at all (genuine cold miss)\n * - no matching `key` in the customer's entitlement set\n * - the matching entitlement is `isActive: false`\n * - the matching entitlement is past its OWN `validUntil` — a\n * time-based trial expiry still applies mid-outage (mirrors the\n * web SDK's `validUntil` check exactly).\n *\n * An entry being past `ttlMs`, or marked refresh-failed, does NOT\n * affect the answer — `getEntitlements()` re-fetches on the TTL hint,\n * but until it succeeds the customer keeps their access.\n */\n isEntitled(customerId: string, key: string): boolean {\n const entry = this.byCustomer.get(customerId);\n if (!entry) return false;\n const nowSec = Date.now() / 1000;\n return entry.all.some(\n (e) =>\n e.key === key &&\n e.isActive &&\n (e.validUntil == null || e.validUntil > nowSec),\n );\n }\n\n /**\n * Full snapshot for callers that need source / validUntil details.\n * Returns `[]` ONLY when the customer has no cached entry — a stale\n * or past-TTL entry still returns its last-known-good entitlements\n * (same durability posture as `isEntitled()`; per-entitlement\n * `validUntil` is the caller's to honour from the returned objects).\n */\n list(customerId: string): PublicEntitlement[] {\n const entry = this.byCustomer.get(customerId);\n if (!entry) return [];\n return entry.all.slice();\n }\n\n /**\n * Whether the customer's entry is still within `ttlMs` — i.e. a\n * re-fetch is NOT yet due. Useful for deciding whether to warm before\n * a hot path. A `false` result does NOT mean the cache is empty or\n * that `isEntitled()` will return false — it only means the data is\n * past its refresh hint. See `needsRefresh()` for the inverse.\n */\n isFresh(customerId: string): boolean {\n const entry = this.byCustomer.get(customerId);\n return Boolean(entry && Date.now() <= entry.refreshDueAt);\n }\n\n /**\n * Whether the customer should be re-fetched: either there is no\n * cached entry, or the entry is past its `ttlMs` refresh hint, or the\n * most recent refresh attempt for them failed (retry it).\n *\n * This is purely advisory — `getEntitlements()` decides when to act\n * on it. It NEVER gates `isEntitled()`, which serves last-known-good\n * regardless.\n */\n needsRefresh(customerId: string): boolean {\n const entry = this.byCustomer.get(customerId);\n if (!entry) return true;\n // A non-zero failure stamp means a refresh failed since the last\n // success (setForCustomer zeroes it) — retry it.\n if (entry.refreshFailedAt > 0) return true;\n return Date.now() > entry.refreshDueAt;\n }\n\n /**\n * Replace (or insert) the cache entry for a customer with a fresh\n * server response. Sets `refreshDueAt` to `now + ttlMs` and CLEARS\n * any failed-refresh marker — a success ends the stale state.\n *\n * Called ONLY after a SUCCESSFUL server read — a failed fetch is\n * routed to `markRefreshFailed` instead and never reaches here, so\n * last-known-good is preserved through an outage.\n *\n * Re-inserting an existing customerId \"touches\" it — the entry moves\n * to the end of insertion order (Map semantics) so it's treated as\n * most-recently-used for LRU eviction. Fires listeners.\n */\n setForCustomer(customerId: string, entitlements: PublicEntitlement[]): void {\n const now = Date.now();\n // Delete-and-reinsert so the entry's insertion-order position\n // moves to the end (LRU \"touch\"). JS Maps iterate in insertion\n // order, so this is what gives us LRU eviction semantics.\n this.byCustomer.delete(customerId);\n this.byCustomer.set(customerId, {\n all: entitlements.slice(),\n refreshDueAt: now + this.ttlMs,\n populatedAt: now,\n refreshFailedAt: 0,\n });\n // Enforce max-customers cap by evicting the oldest entries (head\n // of insertion order = least recently used).\n while (this.byCustomer.size > this.maxCustomers) {\n const oldestKey = this.byCustomer.keys().next().value;\n if (oldestKey === undefined) break;\n this.byCustomer.delete(oldestKey);\n this.evicted += 1;\n }\n this.notify(customerId, entitlements);\n }\n\n /**\n * Record that a refresh attempt for a customer FAILED (Crossdeck\n * unreachable / transient error). `getEntitlements()` calls this in\n * its catch path.\n *\n * It does NOT touch the customer's cached entitlements — last-known-\n * good keeps serving — it only stamps `refreshFailedAt` so the\n * customer shows up as stale in `diagnostics()` rather than the\n * staleness being a silent unbounded window.\n *\n * If the customer has no entry yet (a genuine cold miss whose first\n * fetch failed) a stub entry with no entitlements is created purely\n * to carry the failed-refresh marker — so \"we tried and Crossdeck was\n * down\" is observable even before any successful warm. The stub holds\n * an empty entitlement set, so `isEntitled()` still correctly returns\n * false for it; there is genuinely nothing to serve.\n */\n markRefreshFailed(customerId: string): void {\n const now = Date.now();\n const entry = this.byCustomer.get(customerId);\n if (entry) {\n entry.refreshFailedAt = now;\n return;\n }\n // Cold miss whose first refresh failed — create a marker-only stub.\n // Not an LRU \"touch\" of real data, but it IS an entry, so it counts\n // toward the cap; eviction order treats it like any other.\n this.byCustomer.set(customerId, {\n all: [],\n refreshDueAt: now + this.ttlMs,\n populatedAt: 0,\n refreshFailedAt: now,\n });\n while (this.byCustomer.size > this.maxCustomers) {\n const oldestKey = this.byCustomer.keys().next().value;\n if (oldestKey === undefined) break;\n this.byCustomer.delete(oldestKey);\n this.evicted += 1;\n }\n }\n\n /**\n * Whether a customer is knowingly serving older-than-trustworthy\n * data. True when the most recent refresh ATTEMPT for them failed\n * (Crossdeck unreachable since the last success — the outage case,\n * distinct from a benign idle customer simply past `ttlMs`), OR when\n * their last-known-good has aged past `staleAfterMs`.\n *\n * `isStale` NEVER changes what `isEntitled()` returns — the cache\n * still serves last-known-good. It exists so the staleness is\n * observable via `diagnostics()` instead of an unbounded silent\n * window where a revoked customer (event-based revoke, no\n * `validUntil`) holds access with nobody able to see it.\n *\n * Returns false for an unknown customer — nothing cached, nothing\n * stale.\n */\n isStale(customerId: string): boolean {\n const entry = this.byCustomer.get(customerId);\n if (!entry) return false;\n return this.entryIsStale(entry);\n }\n\n /** Epoch ms of a customer's last failed refresh, or 0 if none / unknown. */\n refreshFailedAt(customerId: string): number {\n return this.byCustomer.get(customerId)?.refreshFailedAt ?? 0;\n }\n\n /**\n * Drop a single customer's entry. Fires listeners with an empty\n * list so subscribers know that customer's cache is gone.\n */\n clearCustomer(customerId: string): void {\n if (!this.byCustomer.delete(customerId)) return;\n this.notify(customerId, []);\n }\n\n /** Wipe the whole cache. Fires listeners for each customer that had a cached entry. */\n clear(): void {\n const customers = [...this.byCustomer.keys()];\n this.byCustomer.clear();\n for (const id of customers) this.notify(id, []);\n }\n\n /**\n * Subscribe to cache mutations. Returns an unsubscribe function.\n * Listener is invoked AFTER each `setForCustomer` / `clearCustomer`\n * / `clear` call with the affected customer ID + fresh entitlements\n * snapshot. NOT fired on TTL expiry (passive eviction is not a\n * mutation by design).\n *\n * Listener errors are swallowed — a buggy consumer must not crash\n * the SDK or other listeners. The error count is surfaced via\n * `listenerErrors`.\n *\n * Returned unsubscribe is idempotent.\n */\n subscribe(listener: EntitlementsListener): () => void {\n this.listeners.add(listener);\n let unsubscribed = false;\n return () => {\n if (unsubscribed) return;\n unsubscribed = true;\n this.listeners.delete(listener);\n };\n }\n\n // ---------- diagnostics ----------\n\n /** Total customers currently cached (counts expired entries too — eviction is lazy). */\n get customerCount(): number {\n return this.byCustomer.size;\n }\n\n /** Most-recent populatedAt across all entries, or 0 if cache empty. */\n get lastUpdated(): number {\n let max = 0;\n for (const entry of this.byCustomer.values()) {\n if (entry.populatedAt > max) max = entry.populatedAt;\n }\n return max;\n }\n\n /** Configured TTL in ms. */\n get ttl(): number {\n return this.ttlMs;\n }\n\n /** Cumulative count of listener invocations that threw. Surfaced in `diagnostics()`. */\n get listenerErrors(): number {\n return this.listenerErrorCount;\n }\n\n /** Cumulative count of entries evicted by the max-customers cap. */\n get evictedCount(): number {\n return this.evicted;\n }\n\n /** Configured max-customers cap. */\n get maxSize(): number {\n return this.maxCustomers;\n }\n\n /** Configured staleness window in ms. */\n get staleWindowMs(): number {\n return this.staleAfterMs;\n }\n\n /**\n * Count of cached customers currently flagged stale — most recent\n * refresh failed, or data aged past `staleAfterMs`. The cache keeps\n * serving last-known-good for them; this is the observability number\n * `diagnostics()` surfaces.\n */\n get staleCustomerCount(): number {\n let count = 0;\n for (const entry of this.byCustomer.values()) {\n if (this.entryIsStale(entry)) count += 1;\n }\n return count;\n }\n\n /** Whether ANY cached customer is currently stale. */\n get isAnyStale(): boolean {\n for (const entry of this.byCustomer.values()) {\n if (this.entryIsStale(entry)) return true;\n }\n return false;\n }\n\n /**\n * Most recent failed-refresh timestamp across all customers (epoch\n * ms), or 0 if every cached customer's last refresh succeeded.\n */\n get lastRefreshFailedAt(): number {\n let max = 0;\n for (const entry of this.byCustomer.values()) {\n if (entry.refreshFailedAt > max) max = entry.refreshFailedAt;\n }\n return max;\n }\n\n // ---------- internals ----------\n\n /**\n * Stale iff the entry's most recent refresh attempt failed, OR its\n * last-known-good has aged past `staleAfterMs`.\n *\n * `refreshFailedAt` is non-zero ONLY between a failed refresh and the\n * next successful one (`setForCustomer` zeroes it), so `> 0` alone\n * means \"a failure occurred since the last success\" — no need to\n * compare against `populatedAt`, which would mis-fire when a failure\n * and a populate land in the same millisecond. A marker-only stub\n * (populatedAt 0, failure stamped) is stale via this first clause.\n */\n private entryIsStale(entry: CacheEntry): boolean {\n if (entry.refreshFailedAt > 0) return true;\n return (\n entry.populatedAt > 0 &&\n Date.now() - entry.populatedAt > this.staleAfterMs\n );\n }\n\n private notify(customerId: string, snapshot: PublicEntitlement[]): void {\n if (this.listeners.size === 0) return;\n const snap = snapshot.slice();\n // Iterate over a snapshot of the listener set so a listener that\n // unsubscribes itself during dispatch doesn't break iteration.\n const listenersSnapshot = [...this.listeners];\n for (const listener of listenersSnapshot) {\n try {\n listener(customerId, snap);\n } catch {\n this.listenerErrorCount += 1;\n }\n }\n }\n}\n","/**\n * Debug signal vocabulary — NorthStar §16, Node port of\n * `@cross-deck/web/src/debug.ts`.\n *\n * The SDK speaks a small fixed vocabulary of signals so the dashboard's\n * onboarding checklist and the developer's console output both speak\n * the same words. When `debug: true` is set in `CrossdeckServerOptions`,\n * the signals are also logged to `console.info` so a developer doing\n * copy-paste integration sees actionable feedback live.\n *\n * Signal names are STABLE — adding new ones is fine, renaming is a\n * breaking change because the dashboard onboarding step keys off them.\n *\n * Node-specific additions beyond web's vocabulary:\n * - `sdk.flush_on_exit_started` / `sdk.flush_on_exit_completed`\n * - `sdk.webhook_verified`\n * - `sdk.runtime_detected`\n * - `sdk.entitlement_cache_warm`\n * - `sdk.entitlement_cache_stale`\n * - `sdk.entitlement_store_recovered`\n * - `sdk.no_durable_store`\n * - `sdk.super_property_registered`\n */\n\nexport type DebugSignal =\n | \"sdk.configured\"\n | \"sdk.first_event_sent\"\n | \"sdk.invalid_key\"\n | \"sdk.no_identity\"\n | \"sdk.entitlement_cache_used\"\n | \"sdk.entitlement_cache_warm\"\n | \"sdk.entitlement_cache_stale\"\n | \"sdk.entitlement_store_recovered\"\n | \"sdk.no_durable_store\"\n | \"sdk.purchase_evidence_sent\"\n | \"sdk.environment_mismatch\"\n | \"sdk.sensitive_property_warning\"\n | \"sdk.property_coerced\"\n | \"sdk.flush_retry_scheduled\"\n | \"sdk.flush_on_exit_started\"\n | \"sdk.flush_on_exit_completed\"\n | \"sdk.webhook_verified\"\n | \"sdk.runtime_detected\"\n | \"sdk.super_property_registered\"\n | \"sdk.boot_heartbeat_failed\";\n\nexport interface DebugContext {\n [key: string]: unknown;\n}\n\n/**\n * Names that almost always indicate PII or secret data. Used by\n * `track()` to warn the developer when a property key looks dangerous.\n * Per NorthStar §15 these are reject/warn-on-sight values; we warn\n * rather than reject because the developer might genuinely want a\n * property called e.g. \"tokens_remaining\".\n */\nconst SENSITIVE_KEY_PATTERNS: readonly RegExp[] = [\n /^email$/i,\n /^password$/i,\n /^token$/i,\n /^secret$/i,\n /^card$/i,\n /^phone$/i,\n /password/i,\n /credit_?card/i,\n];\n\nexport function findSensitivePropertyKeys(\n properties: Record<string, unknown> | undefined,\n): string[] {\n if (!properties) return [];\n const hits: string[] = [];\n for (const k of Object.keys(properties)) {\n if (SENSITIVE_KEY_PATTERNS.some((re) => re.test(k))) hits.push(k);\n }\n return hits;\n}\n\nexport interface DebugLogger {\n enabled: boolean;\n emit(signal: DebugSignal, message: string, context?: DebugContext): void;\n}\n\nconst ONCE_SIGNALS = new Set<DebugSignal>([\n \"sdk.configured\",\n \"sdk.first_event_sent\",\n \"sdk.environment_mismatch\",\n \"sdk.runtime_detected\",\n]);\n\n/**\n * Default debug logger. Writes to `console.info` with a\n * `[crossdeck:<signal>]` prefix so a developer grepping their logs can\n * find SDK signals quickly. Inactive when `enabled === false` — the\n * SDK constructs the logger regardless so a runtime `setDebugMode(true)`\n * doesn't require re-wiring.\n *\n * One-shot signals (sdk.configured, sdk.first_event_sent,\n * sdk.environment_mismatch, sdk.runtime_detected) deduplicate within\n * a process lifetime so a chatty app doesn't spam the console with\n * the same message.\n */\nexport class ConsoleDebugLogger implements DebugLogger {\n enabled = false;\n private seen = new Set<DebugSignal>();\n\n emit(signal: DebugSignal, message: string, context?: DebugContext): void {\n if (!this.enabled) return;\n if (ONCE_SIGNALS.has(signal)) {\n if (this.seen.has(signal)) return;\n this.seen.add(signal);\n }\n const ctx = context ? ` ${safeJson(context)}` : \"\";\n // eslint-disable-next-line no-console\n console.info(`[crossdeck:${signal}] ${message}${ctx}`);\n }\n}\n\n/**\n * No-op logger for tests + callers that want the SDK to be 100% silent.\n * Constructed when `debug` is unset and no custom logger is supplied;\n * the SDK calls `emit()` on every signal regardless of the runtime\n * `enabled` state, so a permanently-off logger keeps the hot path\n * branch-free.\n */\nexport class NullDebugLogger implements DebugLogger {\n enabled = false;\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n emit(_signal: DebugSignal, _message: string, _context?: DebugContext): void {\n /* no-op */\n }\n}\n\nfunction safeJson(obj: unknown): string {\n try {\n return JSON.stringify(obj);\n } catch {\n return \"[unserialisable context]\";\n }\n}\n","/**\n * Machine-readable index of every error code `@cross-deck/node` can\n * throw, with a short description and a hint on what action to take.\n * Mirrors `@cross-deck/web/src/error-codes.ts`.\n *\n * Stripe publishes the same surface at stripe.com/docs/error-codes;\n * developers love it because every code has a canonical \"what does\n * this mean / what should I do\" answer.\n *\n * Differences from web:\n * - Drops browser-only config codes (`invalid_public_key`,\n * `missing_app_id`, `invalid_environment`, `environment_mismatch`,\n * `not_initialized`).\n * - Adds `invalid_secret_key` (Node SDK takes a secret key, not a\n * publishable key + env declaration).\n * - Adds Node-lifecycle codes (`flush_on_exit_failed`,\n * `webhook_invalid_signature`, `webhook_replay_window_exceeded`,\n * `webhook_missing_secret`).\n *\n * Adding a new error code:\n * 1. Throw it as `CrossdeckError.code` in the call site.\n * 2. Add an entry here so the dashboard + AI assistants can render\n * the canonical fix.\n *\n * Keep entries terse — consumers surface this in tooltips and\n * automated tickets, not in long-form docs.\n */\n\nexport interface ErrorCodeEntry {\n /** The string thrown as `CrossdeckError.code`. */\n code: string;\n /** Broad category — `CrossdeckError.type`. */\n type:\n | \"authentication_error\"\n | \"permission_error\"\n | \"invalid_request_error\"\n | \"rate_limit_error\"\n | \"internal_error\"\n | \"network_error\"\n | \"configuration_error\";\n /** One-sentence description. Surfaced verbatim in dashboards. */\n description: string;\n /** What the developer should do. Imperative phrasing. */\n resolution: string;\n /** True for codes the SDK can auto-recover from (no developer action). */\n retryable: boolean;\n}\n\n/**\n * Internal source-of-truth with literal types preserved via `as const`.\n * The exported `CROSSDECK_ERROR_CODES` widens to `readonly\n * ErrorCodeEntry[]` for the public surface (callers iterating it\n * shouldn't depend on literal positions), while `CrossdeckErrorCode`\n * below derives the literal union from this constant for type-safe\n * code narrowing.\n */\nconst _CROSSDECK_ERROR_CODES = Object.freeze([\n // ----- Configuration -----\n {\n code: \"invalid_secret_key\",\n type: \"configuration_error\",\n description: \"The secret key passed to new CrossdeckServer({ secretKey }) doesn't start with cd_sk_.\",\n resolution: \"Copy the key from your Crossdeck dashboard → API keys page. Server SDKs use cd_sk_test_… (sandbox) or cd_sk_live_… (production). Never ship this key to a browser.\",\n retryable: false,\n },\n\n // ----- Argument validation -----\n {\n code: \"missing_user_id\",\n type: \"invalid_request_error\",\n description: \"identify() / aliasIdentity() called with an empty userId.\",\n resolution: \"Pass a stable, non-empty user identifier from your auth layer — never a hardcoded placeholder.\",\n retryable: false,\n },\n {\n code: \"missing_anonymous_id\",\n type: \"invalid_request_error\",\n description: \"aliasIdentity() called with an empty anonymousId.\",\n resolution: \"Pass the anonymousId originally minted by the web SDK on this user's device.\",\n retryable: false,\n },\n {\n code: \"missing_customer_id\",\n type: \"invalid_request_error\",\n description: \"An operation that requires a Crossdeck customer ID was called with an empty value.\",\n resolution: \"Pass the customerId returned from a prior identify() / getEntitlements() call.\",\n retryable: false,\n },\n {\n code: \"missing_identity\",\n type: \"invalid_request_error\",\n description: \"An ingest / forget / entitlements call received no identity hints.\",\n resolution: \"Pass at least one of customerId, userId, or anonymousId on the call (or per-event for ingest).\",\n retryable: false,\n },\n {\n code: \"missing_event_name\",\n type: \"invalid_request_error\",\n description: \"track() / ingest() received an event without a name.\",\n resolution: \"Pass a non-empty string as the event name. The wire shape is { name, properties? }.\",\n retryable: false,\n },\n {\n code: \"missing_events\",\n type: \"invalid_request_error\",\n description: \"ingest() received an empty array.\",\n resolution: \"Pass at least one event. Use server.track(event) to send a single event.\",\n retryable: false,\n },\n {\n code: \"missing_event_id\",\n type: \"invalid_request_error\",\n description: \"getAuditEntry() called with an empty eventId.\",\n resolution: \"Pass the eventId from the audit row you want to inspect.\",\n retryable: false,\n },\n {\n code: \"missing_signed_transaction_info\",\n type: \"invalid_request_error\",\n description: \"syncPurchases() called without StoreKit 2 signed transaction info.\",\n resolution: \"Pass the JWS string from Transaction.currentEntitlements / Transaction.updates.\",\n retryable: false,\n },\n {\n code: \"missing_group_type\",\n type: \"invalid_request_error\",\n description: \"group(type, id) called with an empty type.\",\n resolution: \"Pass a non-empty group type (e.g. \\\"org\\\", \\\"team\\\", \\\"plan\\\") as the first argument.\",\n retryable: false,\n },\n {\n code: \"serialization_failed\",\n type: \"invalid_request_error\",\n description: \"An event payload or trait bag could not be JSON-serialised even after sanitisation.\",\n resolution: \"Inspect the payload for non-JSON-friendly values (functions, symbols, deeply circular refs). The SDK's validator drops these by default, so this usually means a bug — file an issue with the payload shape.\",\n retryable: false,\n },\n\n // ----- Network / transport -----\n {\n code: \"fetch_failed\",\n type: \"network_error\",\n description: \"The underlying fetch() call failed (typically a network outage, DNS, or refused connection).\",\n resolution: \"Check the host's outbound network. The SDK retries automatically with exponential backoff + jitter for queued events.\",\n retryable: true,\n },\n {\n code: \"request_timeout\",\n type: \"network_error\",\n description: \"A request was aborted after the configured timeoutMs (default 15s).\",\n resolution: \"Check the host's network. Increase timeoutMs in CrossdeckServer options if you're on a known-slow link.\",\n retryable: true,\n },\n {\n code: \"invalid_json_response\",\n type: \"internal_error\",\n description: \"The server returned a 2xx with an unparseable body.\",\n resolution: \"Likely a transient backend bug. Retry; if it persists, contact support with the requestId.\",\n retryable: true,\n },\n\n // ----- Lifecycle (Node-specific) -----\n {\n code: \"flush_on_exit_failed\",\n type: \"internal_error\",\n description: \"The on-exit drain (beforeExit / SIGTERM / SIGINT) did not complete before flushOnExitTimeoutMs.\",\n resolution: \"Increase flushOnExitTimeoutMs in CrossdeckServer options. Default is 2000ms; serverless runtimes typically allow 5-10s before SIGKILL. If events are dropping silently in production, raise this.\",\n retryable: false,\n },\n\n // ----- Webhook verification (Node-specific) -----\n {\n code: \"webhook_invalid_signature\",\n type: \"authentication_error\",\n description: \"The webhook signature header did not verify against the supplied secret.\",\n resolution: \"Confirm the secret matches the one in your Crossdeck dashboard → Webhooks page. If the request is genuinely from Crossdeck, the secret is wrong, stale, or recently rotated.\",\n retryable: false,\n },\n {\n code: \"webhook_replay_window_exceeded\",\n type: \"authentication_error\",\n description: \"The webhook timestamp is older than the replay-tolerance window (default 5 minutes).\",\n resolution: \"The webhook is either replayed or your receiving clock is wildly skewed. Verify NTP on the receiving host. Increase replayToleranceMs only if you accept the replay-attack risk.\",\n retryable: false,\n },\n {\n code: \"webhook_missing_secret\",\n type: \"configuration_error\",\n description: \"verifyWebhookSignature() was called without a signing secret.\",\n resolution: \"Pass the secret from your Crossdeck dashboard → Webhooks page. Never hardcode in source — read from an env var.\",\n retryable: false,\n },\n] as const);\n\n/**\n * Literal union of every code documented in `CROSSDECK_ERROR_CODES`.\n * Exported so callers can do type-safe code comparisons:\n *\n * if (err.code === \"webhook_invalid_signature\") { ... } // typed\n * if (err.code === \"webook_invalid_signature\") { ... } // TS error\n *\n * Mis-spelling a code at compile time fails to compile — the gap that\n * silently broke v0.1.0 callers checking for non-existent codes.\n */\nexport type CrossdeckErrorCode = (typeof _CROSSDECK_ERROR_CODES)[number][\"code\"];\n\n/** Type guard: narrows a string to the documented literal union. */\nexport function isCrossdeckErrorCode(code: string): code is CrossdeckErrorCode {\n return _CROSSDECK_ERROR_CODES.some((e) => e.code === code);\n}\n\n/**\n * Public catalogue, widened to `readonly ErrorCodeEntry[]` for iteration\n * stability. Callers needing literal types should reach for the\n * `CrossdeckErrorCode` union above instead of indexing into this array.\n */\nexport const CROSSDECK_ERROR_CODES: readonly ErrorCodeEntry[] = _CROSSDECK_ERROR_CODES;\n\n/** Lookup helper — returns the entry matching a CrossdeckError.code, or undefined. */\nexport function getErrorCode(code: string): ErrorCodeEntry | undefined {\n return CROSSDECK_ERROR_CODES.find((e) => e.code === code);\n}\n","/**\n * Webhook signature verification — Stripe pattern.\n *\n * Lets customers verify the events Crossdeck sends to THEM. Table-stakes\n * for any backend SDK (Stripe ships `Stripe.webhooks.constructEvent()`\n * from day one, Svix ships `Webhook.verify()` from day one).\n *\n * Wire format:\n * Header `Crossdeck-Signature: t=<unix-seconds>,v1=<hex>`\n * Where `v1` is HMAC-SHA256(secret, `${t}.${payload}`) — Stripe-compatible.\n *\n * Customers receive a signing secret from the Crossdeck dashboard\n * (one-time reveal at mint time; rotated as needed). Each webhook\n * carries the signature header above. The customer's handler:\n *\n * import { verifyWebhookSignature } from \"@cross-deck/node\";\n *\n * app.post(\"/crossdeck-webhook\", express.raw({ type: \"application/json\" }), (req, res) => {\n * try {\n * const event = verifyWebhookSignature(\n * req.body.toString(\"utf8\"),\n * req.headers[\"crossdeck-signature\"],\n * process.env.CROSSDECK_WEBHOOK_SECRET,\n * );\n * // event is the parsed JSON payload\n * handleCrossdeckEvent(event);\n * res.sendStatus(200);\n * } catch (err) {\n * res.sendStatus(401);\n * }\n * });\n *\n * The signing scheme is constant-time via `crypto.timingSafeEqual` so\n * a malicious caller can't extract the signature by measuring response\n * timing. Replay defence: timestamps older than `replayToleranceMs`\n * (default 5 min) are rejected — required because HMAC-SHA256 is\n * stateless and would otherwise allow an attacker to replay an old\n * webhook indefinitely.\n *\n * Supports multiple secrets for rotation: pass an array; the helper\n * tries each, accepts on the first match. Lets customers rotate the\n * dashboard secret without dropping in-flight webhooks.\n */\n\nimport { createHmac, timingSafeEqual } from \"node:crypto\";\n\nimport { CrossdeckError } from \"./errors\";\n\nexport interface VerifyWebhookOptions {\n /**\n * Maximum age of the webhook timestamp in milliseconds. Default\n * 5 minutes (5 * 60 * 1000). Anything older than this is rejected\n * as a replay. Pass 0 to disable the replay window (NOT recommended\n * — accept the trade-off only if you have a separate replay defence).\n */\n replayToleranceMs?: number;\n /**\n * Override the current time. Tests use this to verify timestamp\n * handling deterministically. Defaults to `Date.now()`.\n */\n now?: () => number;\n}\n\nconst DEFAULT_REPLAY_TOLERANCE_MS = 5 * 60 * 1000;\n\n/**\n * Verify a Crossdeck-signed webhook. Returns the parsed JSON payload\n * on success. Throws `CrossdeckError` on:\n * - missing / malformed signature header (`webhook_invalid_signature`)\n * - missing secret (`webhook_missing_secret`)\n * - timestamp outside replay window (`webhook_replay_window_exceeded`)\n * - HMAC mismatch (`webhook_invalid_signature`)\n * - non-JSON payload (`webhook_invalid_signature` — same code because\n * a tampered payload that breaks JSON parses as invalid)\n *\n * `secret` accepts a single string or an array of strings (for\n * rotation). Any one match is sufficient.\n */\nexport function verifyWebhookSignature(\n payload: string,\n signatureHeader: string | string[] | undefined,\n secret: string | string[] | undefined,\n options: VerifyWebhookOptions = {},\n): unknown {\n const secrets = normaliseSecrets(secret);\n if (secrets.length === 0) {\n throw new CrossdeckError({\n type: \"configuration_error\",\n code: \"webhook_missing_secret\",\n message:\n \"verifyWebhookSignature requires a non-empty secret. Read it from process.env.CROSSDECK_WEBHOOK_SECRET — never hardcode in source.\",\n });\n }\n\n const header = normaliseHeader(signatureHeader);\n const parsed = parseSignatureHeader(header);\n if (!parsed) {\n throw new CrossdeckError({\n type: \"authentication_error\",\n code: \"webhook_invalid_signature\",\n message:\n \"Webhook signature header is missing or malformed. Expected 'Crossdeck-Signature: t=<unix>,v1=<hex>'.\",\n });\n }\n\n const tolerance = options.replayToleranceMs ?? DEFAULT_REPLAY_TOLERANCE_MS;\n if (tolerance > 0) {\n const now = (options.now ?? Date.now)();\n const timestampMs = parsed.timestampSec * 1000;\n const drift = Math.abs(now - timestampMs);\n if (drift > tolerance) {\n throw new CrossdeckError({\n type: \"authentication_error\",\n code: \"webhook_replay_window_exceeded\",\n message: `Webhook timestamp is ${drift}ms outside the ${tolerance}ms replay-tolerance window. Either the request is replayed or the receiving clock is skewed — verify NTP on the host.`,\n });\n }\n }\n\n const signedPayload = `${parsed.timestampSec}.${payload}`;\n const expectedBuf = Buffer.from(parsed.signature, \"hex\");\n if (expectedBuf.length === 0) {\n throw new CrossdeckError({\n type: \"authentication_error\",\n code: \"webhook_invalid_signature\",\n message: \"Webhook signature is not a valid hex string.\",\n });\n }\n\n const anyMatch = secrets.some((s) => {\n const computed = createHmac(\"sha256\", s).update(signedPayload).digest();\n return computed.length === expectedBuf.length && timingSafeEqual(computed, expectedBuf);\n });\n if (!anyMatch) {\n throw new CrossdeckError({\n type: \"authentication_error\",\n code: \"webhook_invalid_signature\",\n message:\n \"Webhook signature did not verify. Confirm the secret matches your Crossdeck dashboard → Webhooks page (and that you're not on a stale rotation).\",\n });\n }\n\n try {\n return JSON.parse(payload);\n } catch {\n throw new CrossdeckError({\n type: \"authentication_error\",\n code: \"webhook_invalid_signature\",\n message:\n \"Webhook signature verified but the payload is not valid JSON. Either the payload was tampered with after signing, or the webhook source is misconfigured.\",\n });\n }\n}\n\n/**\n * Pure-function signing — mirror of what the Crossdeck backend does\n * when sending a webhook. Exported so customers building their own\n * test fixtures (a service that sends Crossdeck-signed webhooks to\n * their own test harness) can re-use the canonical signing scheme\n * instead of re-implementing it.\n *\n * const ts = Math.floor(Date.now() / 1000);\n * const sig = signWebhookPayload(payload, secret, ts);\n * const header = `t=${ts},v1=${sig}`;\n *\n * NOT marked as a security primitive for general HMAC — use\n * `node:crypto` directly for that. This is only the\n * Crossdeck-signature shape.\n */\nexport function signWebhookPayload(payload: string, secret: string, timestampSec: number): string {\n return createHmac(\"sha256\", secret)\n .update(`${timestampSec}.${payload}`)\n .digest(\"hex\");\n}\n\ninterface ParsedSignature {\n timestampSec: number;\n signature: string;\n}\n\nfunction parseSignatureHeader(header: string | null): ParsedSignature | null {\n if (!header) return null;\n // Format: \"t=<unix>,v1=<hex>\". Order-independent, ignore unknown keys.\n let timestampSec: number | null = null;\n let signature: string | null = null;\n for (const part of header.split(\",\")) {\n const eqIdx = part.indexOf(\"=\");\n if (eqIdx <= 0) continue;\n const key = part.slice(0, eqIdx).trim();\n const value = part.slice(eqIdx + 1).trim();\n if (key === \"t\") {\n const n = Number(value);\n if (Number.isFinite(n) && n > 0) timestampSec = Math.floor(n);\n } else if (key === \"v1\") {\n if (/^[0-9a-fA-F]+$/.test(value)) signature = value.toLowerCase();\n }\n }\n if (timestampSec === null || signature === null) return null;\n return { timestampSec, signature };\n}\n\nfunction normaliseHeader(input: string | string[] | undefined): string | null {\n if (input === undefined) return null;\n if (Array.isArray(input)) return input[0] ?? null;\n return input;\n}\n\nfunction normaliseSecrets(input: string | string[] | undefined): string[] {\n if (input === undefined || input === null) return [];\n const arr = Array.isArray(input) ? input : [input];\n return arr.filter((s) => typeof s === \"string\" && s.length > 0);\n}\n","/**\n * PII scrub utilities — Node port of `@cross-deck/web/src/consent.ts`'s\n * regex-based defence layer.\n *\n * **What's NOT here (intentionally)**: the `ConsentManager` class. That's\n * a browser/end-user UX surface (DNT detection, per-dimension consent\n * gating). On the server side, the customer's user already accepted\n * or declined consent in their client; the API caller decides what\n * to forward. Shipping `ConsentManager` here would imply server-side\n * gating that doesn't match the trust model.\n *\n * **What IS here**: opt-in utilities customers can use to scrub\n * email-shaped and card-number-shaped substrings out of event\n * properties before forwarding to Crossdeck. Stripe-grade defence in\n * depth, applied at the caller's discretion:\n *\n * import { scrubPiiFromProperties } from \"@cross-deck/node\";\n *\n * server.track({\n * name: \"checkout.started\",\n * developerUserId: userId,\n * properties: scrubPiiFromProperties({\n * url: req.url, // might contain \"/users/wes@…/\" — gets [email]\n * lastError: e.message, // might contain card numbers\n * }),\n * });\n */\n\n/**\n * Email-shaped pattern. Restrictive enough to match the practical\n * 99% of emails (RFC 5322's \"obs-local-part\" common case) without\n * false positives. We deliberately don't try to match every legal\n * email; the goal is \"if it looks like an email, scrub it.\"\n */\nconst EMAIL_PATTERN =\n /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/g;\n\n/**\n * Card-number-shaped pattern. Matches sequences of 13-19 digits that\n * could be split by space or hyphen — the format every payment form\n * accepts. We don't validate Luhn; this is best-effort scrubbing,\n * not card-data tokenisation. If you're handling actual PAN data you\n * should not be passing it through analytics in the first place.\n *\n * Anchored on a digit at both ends so trailing separators (space /\n * hyphen) aren't pulled into the match — otherwise\n * \"4242 4242 4242 4242 today\" would scrub as \"[card]today\" instead of\n * \"[card] today\".\n */\nconst CARD_PATTERN = /\\b\\d(?:[ -]?\\d){12,18}\\b/g;\n\nconst REPLACEMENT_EMAIL = \"[email]\";\nconst REPLACEMENT_CARD = \"[card]\";\n\n/**\n * Scrub a single string value: replace email-shaped substrings with\n * `[email]` and card-number-shaped substrings with `[card]`. Returns\n * the original string (===) when nothing matched, so callers can do\n * an identity-check to skip allocating a new event copy.\n */\nexport function scrubPii(value: string): string {\n if (!value) return value;\n let out = value;\n if (EMAIL_PATTERN.test(out)) {\n out = out.replace(EMAIL_PATTERN, REPLACEMENT_EMAIL);\n }\n // Reset regex lastIndex (global flag carries state between calls).\n EMAIL_PATTERN.lastIndex = 0;\n if (CARD_PATTERN.test(out)) {\n out = out.replace(CARD_PATTERN, REPLACEMENT_CARD);\n }\n CARD_PATTERN.lastIndex = 0;\n return out;\n}\n\n/**\n * Walk a property bag and replace PII-shaped strings in place. Returns\n * a new object with strings scrubbed; non-string values pass through\n * unchanged.\n *\n * Defensive copy — the input is never altered. Caller can pass the\n * result straight to `server.track()`.\n *\n * Recursive: nested objects + arrays are walked. Functions, symbols,\n * Dates, etc. pass through untouched (those are the\n * `validateEventProperties` sanitiser's job — this is just the\n * PII regex pass).\n */\nexport function scrubPiiFromProperties(\n properties: Record<string, unknown>,\n): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const k of Object.keys(properties)) {\n out[k] = scrubValue(properties[k]);\n }\n return out;\n}\n\nfunction scrubValue(v: unknown): unknown {\n if (typeof v === \"string\") return scrubPii(v);\n if (Array.isArray(v)) return v.map(scrubValue);\n if (v && typeof v === \"object\" && v.constructor === Object) {\n // Plain objects only — Date, Map, Set, Error are left untouched\n // (the property validator already handles those). This avoids\n // accidentally mutating an Error's `message` and confusing the\n // downstream error reporting layer.\n return scrubPiiFromProperties(v as Record<string, unknown>);\n }\n return v;\n}\n"],"mappings":"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","names":["safeStringify","osPlatform","safeStringify","osHostname","osPlatform","osRelease","DEFAULT_TIMEOUT_MS"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/crossdeck-server.ts","../src/errors.ts","../src/event-validation.ts","../src/_version.ts","../src/http.ts","../src/retry-policy.ts","../src/_rand.ts","../src/event-queue.ts","../src/breadcrumbs.ts","../src/stack-parser.ts","../src/error-capture.ts","../src/runtime-info.ts","../src/flush-on-exit.ts","../src/super-properties.ts","../src/entitlement-cache.ts","../src/debug.ts","../src/error-codes.ts","../src/webhooks.ts","../src/consent.ts"],"sourcesContent":["/**\n * @cross-deck/node — public entry point.\n *\n * Three USPs land on the server in v1.0.0:\n *\n * 1. Errors — `captureError` / `captureMessage` / `setTag` /\n * `setContext` / `addBreadcrumb` / `setErrorBeforeSend` +\n * auto-wired `uncaughtException` + `unhandledRejection` +\n * `globalThis.fetch` wrap for 5xx + network failures.\n *\n * 2. Analytics — `track()` / `ingest()` / `flush()`, durable queue\n * with retry + `Idempotency-Key`, flush-on-exit drain.\n * Super-properties (`register` / `unregister`) and group\n * analytics (`group`) for Mixpanel-style enrichment. Framework\n * adapters (Express / Lambda / Firebase) via the\n * `@cross-deck/node/auto-events` subpath.\n *\n * 3. Entitlements — `getEntitlements()` / `getCustomerEntitlements()`\n * with a per-customer TTL cache so `isEntitled()` is a memory\n * read after first warm. Webhook signature verification via\n * `verifyWebhookSignature()`.\n *\n * Cross-cutting: runtime enrichment (Node version, OS, region,\n * function name, instance ID) auto-attached to every event + error.\n * Lifecycle: `flush-on-exit` drains the queue on `beforeExit` +\n * SIGTERM + SIGINT so Cloud Functions cold-fires don't lose events.\n */\n\nexport { CrossdeckServer } from \"./crossdeck-server\";\nexport {\n CrossdeckError,\n CrossdeckAuthenticationError,\n CrossdeckPermissionError,\n CrossdeckValidationError,\n CrossdeckRateLimitError,\n CrossdeckNetworkError,\n CrossdeckInternalError,\n CrossdeckConfigurationError,\n makeCrossdeckError,\n} from \"./errors\";\nexport {\n DEFAULT_BASE_URL,\n DEFAULT_TIMEOUT_MS,\n SDK_NAME,\n SDK_VERSION,\n CROSSDECK_API_VERSION,\n} from \"./http\";\nexport { CROSSDECK_ERROR_CODES, getErrorCode, isCrossdeckErrorCode } from \"./error-codes\";\nexport { verifyWebhookSignature, signWebhookPayload } from \"./webhooks\";\nexport { scrubPii, scrubPiiFromProperties } from \"./consent\";\n\nexport type {\n AliasIdentityInput,\n AliasResult,\n AuditDecision,\n AuditEntry,\n CrossdeckServerOptions,\n Diagnostics,\n EntitlementMutationResult,\n EntitlementsListResponse,\n EntitlementStore,\n Environment,\n EventProperties,\n ForgetResult,\n GrantDuration,\n GrantEntitlementInput,\n HeartbeatResponse,\n IdentityHints,\n IdentifyOptions,\n IngestOptions,\n IngestResponse,\n PublicEntitlement,\n PurchaseResult,\n RequestOptions,\n RevokeEntitlementInput,\n ServerEvent,\n StoredEntitlements,\n SyncPurchaseInput,\n ErrorLevel,\n} from \"./types\";\nexport type { HttpRequestInfo, HttpResponseInfo, HttpRetriesConfig } from \"./http\";\nexport type { CrossdeckErrorPayload, CrossdeckErrorType } from \"./errors\";\nexport type { ErrorCodeEntry, CrossdeckErrorCode } from \"./error-codes\";\nexport type { Breadcrumb, BreadcrumbCategory, BreadcrumbLevel } from \"./breadcrumbs\";\nexport type { CapturedError, ErrorCaptureConfig } from \"./error-capture\";\nexport type { StackFrame } from \"./stack-parser\";\nexport type { RuntimeHost, RuntimeInfo } from \"./runtime-info\";\nexport type { GroupMembership } from \"./super-properties\";\nexport type { EntitlementsListener, EntitlementCacheOptions } from \"./entitlement-cache\";\nexport type { DebugSignal, DebugLogger, DebugContext } from \"./debug\";\nexport type { VerifyWebhookOptions } from \"./webhooks\";\n","/**\n * @cross-deck/node — `CrossdeckServer`, the orchestrator.\n *\n * v1.0.0 expands beyond the v0.1.0 thin HTTP client to ship the three\n * Crossdeck USPs on the server:\n *\n * 1. Errors — `captureError`, `captureMessage`, auto-wired\n * `process.on('uncaughtException')` + `process.on('unhandledRejection')`,\n * `globalThis.fetch` wrap, stack-frame parsing, breadcrumb\n * attachment, fingerprint dedup, rate-limit per fingerprint.\n * [USP 1 — landed v1.0.0]\n *\n * 2. Analytics — `track()` switches from sync-HTTP-per-event to\n * enqueue-and-batch via `EventQueue` (durable, retried, idempotent\n * per batch). `flush-on-exit` drains before Cloud Function / Lambda\n * teardown so events don't vanish silently.\n * [USP 1 ships queue + flush-on-exit; super-props + auto-events\n * arrive in USP 2]\n *\n * 3. Entitlements — TTL-cached `isEntitled()` so hot-path gates are\n * memory reads after first warm.\n * [USP 3 — pending]\n *\n * Cross-cutting: every event + error carries `runtime.*` enrichment\n * (Node version, OS, host, region, function name, instance ID) auto-\n * attached via `collectRuntimeInfo()`.\n *\n * The non-event endpoints (identify / aliasIdentity / forget /\n * getEntitlements / getCustomerEntitlements / syncPurchases /\n * grantEntitlement / revokeEntitlement / getAuditEntry) stay as direct\n * HTTP — they're transactional, not telemetry. Only `track()` changed\n * to queue-based.\n */\n\nimport { EventEmitter } from \"node:events\";\n\nimport { CrossdeckError } from \"./errors\";\nimport { validateEventProperties } from \"./event-validation\";\nimport {\n DEFAULT_BASE_URL,\n DEFAULT_TIMEOUT_MS,\n HttpClient,\n SDK_NAME,\n SDK_VERSION,\n} from \"./http\";\nimport { EventQueue, type BatchEnvelope, type QueuedEvent } from \"./event-queue\";\nimport { BreadcrumbBuffer, type Breadcrumb, type BreadcrumbCategory } from \"./breadcrumbs\";\nimport {\n DEFAULT_ERROR_CAPTURE,\n ErrorTracker,\n extractSelfHostname,\n type CapturedError,\n type ErrorCaptureConfig,\n} from \"./error-capture\";\nimport { collectRuntimeInfo, runtimeInfoToProperties, type RuntimeInfo } from \"./runtime-info\";\nimport { FlushOnExit } from \"./flush-on-exit\";\nimport { SuperPropertyStore, type GroupMembership } from \"./super-properties\";\nimport { EntitlementCache, type EntitlementsListener } from \"./entitlement-cache\";\nimport { ConsoleDebugLogger, NullDebugLogger, findSensitivePropertyKeys, type DebugLogger } from \"./debug\";\nimport { mintId } from \"./_rand\";\nimport type {\n AliasIdentityInput,\n AliasResult,\n AuditEntry,\n AuditEntryResponse,\n CrossdeckServerOptions,\n Diagnostics,\n EntitlementMutationResult,\n EntitlementsListResponse,\n EntitlementStore,\n Environment,\n ErrorLevel,\n EventProperties,\n ForgetResult,\n GrantEntitlementInput,\n HeartbeatResponse,\n IdentityHints,\n IdentifyOptions,\n IngestOptions,\n IngestResponse,\n PublicEntitlement,\n PurchaseResult,\n RequestOptions,\n RevokeEntitlementInput,\n ServerEvent,\n StoredEntitlements,\n SyncPurchaseInput,\n} from \"./types\";\n\n/**\n * Typed event names + payloads emitted by `CrossdeckServer`. Caller\n * subscribes via the standard EventEmitter API:\n *\n * server.on(\"queue.flush_failed\", ({ error, attempt }) => { ... });\n * server.once(\"sdk.shutdown\", () => { ... });\n *\n * The typed `on` / `off` / `emit` overloads narrow the listener\n * arguments to the right shape. Untyped event names still work for\n * forward compat with any backend-side additions.\n */\nexport interface CrossdeckServerEvents {\n /** Fired once per batch on successful flush. */\n \"queue.flush_succeeded\": [info: { batchSize: number; durationMs: number }];\n /** Fired on every failed flush attempt. */\n \"queue.flush_failed\": [info: { error: CrossdeckError | string; attempt: number; nextRetryMs: number }];\n /** Fired when the queue drops oldest events due to HARD_BUFFER_CAP. */\n \"queue.dropped\": [info: { count: number }];\n /** Fired when the buffer changes size — used by callers wanting backpressure-aware tracking. */\n \"queue.buffer_changed\": [info: { size: number }];\n /** Fired when an error is captured (manual or auto). */\n \"error.captured\": [info: { fingerprint: string; kind: string; message: string }];\n /** Fired once after `getEntitlements()` warms the cache for a customer. */\n \"entitlements.warmed\": [info: { customerId: string; count: number }];\n /** Fired on `shutdown()` / `[Symbol.dispose]` / `[Symbol.asyncDispose]`. */\n \"sdk.shutdown\": [info: { reason: \"shutdown\" | \"dispose\" | \"asyncDispose\" }];\n}\n\nexport class CrossdeckServer extends EventEmitter {\n private readonly http: HttpClient;\n private readonly sdkVersion: string;\n private readonly baseUrl: string;\n private readonly appId: string | undefined;\n private readonly env: Environment;\n private readonly secretKeyPrefix: string;\n\n /**\n * Process-stable pseudo-anonymous ID. Used as the default identity\n * for `track()` / `captureError()` calls where the caller doesn't\n * supply one (e.g. an `uncaughtException` handler has no per-request\n * context). Stable for the SDK instance's lifetime so events from\n * the same process correlate.\n */\n private readonly processAnonymousId: string;\n\n private readonly runtime: RuntimeInfo;\n private readonly runtimeProperties: Record<string, unknown>;\n private readonly breadcrumbs: BreadcrumbBuffer;\n private readonly eventQueue: EventQueue;\n private readonly errorTracker: ErrorTracker | null;\n private readonly flushOnExit: FlushOnExit | null;\n private readonly superProps: SuperPropertyStore;\n private readonly entitlementCache: EntitlementCache;\n /**\n * Optional developer-supplied durable store for last-known-good\n * entitlements (Redis / their DB / a KV). `undefined` when not\n * configured — the SDK then has no cold-start durability on\n * serverless, which it states explicitly at boot.\n *\n * Touched ONLY from the async `getEntitlements()` — never from the\n * synchronous `isEntitled()`.\n */\n private readonly entitlementStore: EntitlementStore | null;\n private readonly debug: DebugLogger;\n\n /**\n * Alias map — `developerUserId` / `anonymousId` → canonical\n * `crossdeckCustomerId`. Populated by `getEntitlements()` so a\n * subsequent `isEntitled({ userId }, \"pro\")` resolves to the same\n * cache entry the prior `getEntitlements({ userId })` populated.\n *\n * Bounded by `MAX_CUSTOMER_ID_ALIASES` (matches the entitlement\n * cache's default max-customers for symmetry — if the underlying\n * cache entry was evicted, a stale alias is dead weight anyway).\n * Long-running multi-tenant servers handling a long tail of customers\n * are the failure mode this bound defends against.\n */\n private customerIdAliases = new Map<string, string>();\n\n /** Mutable error-state — modified by setTag / setContext / setErrorBeforeSend. */\n private errorContext: Record<string, unknown> = {};\n private errorTags: Record<string, string> = {};\n private errorBeforeSend: ((err: CapturedError) => CapturedError | null) | null = null;\n\n constructor(options: CrossdeckServerOptions) {\n super();\n if (!options.secretKey || !options.secretKey.startsWith(\"cd_sk_\")) {\n throw new CrossdeckError({\n type: \"configuration_error\",\n code: \"invalid_secret_key\",\n message: \"CrossdeckServer requires a secret key starting with cd_sk_.\",\n });\n }\n\n this.sdkVersion = options.sdkVersion ?? SDK_VERSION;\n this.appId = options.appId;\n this.baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n this.env = inferEnvFromKey(options.secretKey);\n this.secretKeyPrefix = maskSecretKey(options.secretKey);\n\n this.http = new HttpClient({\n secretKey: options.secretKey,\n baseUrl: this.baseUrl,\n sdkVersion: this.sdkVersion,\n timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n testMode: options.testMode,\n onRequest: options.onRequest,\n onResponse: options.onResponse,\n httpRetries: options.httpRetries,\n runtimeToken: options.runtimeToken,\n });\n\n this.processAnonymousId = mintId(\"anon_node\");\n\n this.runtime = collectRuntimeInfo({\n serviceName: options.serviceName,\n serviceVersion: options.serviceVersion,\n appVersion: options.appVersion,\n });\n this.runtimeProperties = runtimeInfoToProperties(this.runtime);\n\n this.breadcrumbs = new BreadcrumbBuffer(options.breadcrumbsMaxSize ?? 50);\n this.superProps = new SuperPropertyStore();\n this.entitlementCache = new EntitlementCache({\n ttlMs: options.entitlementCacheTtlMs ?? 60_000,\n staleAfterMs: options.entitlementStaleAfterMs,\n });\n this.entitlementStore = options.entitlementStore ?? null;\n this.debug = options.debug === true ? new ConsoleDebugLogger() : new NullDebugLogger();\n if (options.debug === true) this.debug.enabled = true;\n\n this.debug.emit(\n \"sdk.configured\",\n `Crossdeck server SDK connected. env=${this.env}, host=${this.runtime?.host ?? \"node\"}`,\n {\n env: this.env,\n sdkVersion: this.sdkVersion,\n secretKeyPrefix: this.secretKeyPrefix,\n },\n );\n\n this.eventQueue = new EventQueue({\n http: this.http,\n batchSize: options.eventFlushBatchSize ?? 20,\n intervalMs: options.eventFlushIntervalMs ?? 1500,\n envelope: (): BatchEnvelope => ({\n appId: this.appId,\n // Ship env on every batch so the backend can cross-check\n // against the API-key-derived env and reject mismatches\n // loudly (env_mismatch). Web has always done this; node now\n // matches so defence-in-depth is symmetric across SDKs.\n environment: this.env,\n sdk: { name: SDK_NAME, version: this.sdkVersion },\n }),\n onDrop: (count) => {\n this.emit(\"queue.dropped\", { count });\n },\n onBufferChange: (size) => {\n this.emit(\"queue.buffer_changed\", { size });\n },\n onRetryScheduled: (info) => {\n this.emit(\"queue.flush_failed\", {\n error: info.lastError,\n attempt: info.consecutiveFailures,\n nextRetryMs: info.delayMs,\n });\n },\n onPermanentFailure: (info) => {\n // Bank-grade rule: a permanent 4xx that's dropping events MUST\n // be loud regardless of debug mode. Pre-fix the queue retried\n // 4xx forever silently and the customer never knew their key\n // was revoked. console.error fires unconditionally; the debug\n // signal lets ops dashboards detect + surface the problem too;\n // the event-emitter signal lets host-app code listen and page.\n const headline = `[crossdeck] Event batch DROPPED (status ${info.status}): ${info.lastError}. ${info.droppedCount} event(s) lost — check your secret key + app config.`;\n // eslint-disable-next-line no-console\n console.error(headline);\n this.debug.emit(\n \"sdk.flush_permanent_failure\",\n headline,\n { ...info },\n );\n this.emit(\"queue.permanent_failure\", {\n status: info.status,\n droppedCount: info.droppedCount,\n error: info.lastError,\n });\n },\n onFirstFlushSuccess: () => {\n this.debug.emit(\"sdk.first_event_sent\", \"First batch landed.\");\n },\n });\n\n // Error capture. Default: enabled. Caller can opt out with `false`,\n // or override individual knobs with a partial object.\n if (options.errorCapture === false) {\n this.errorTracker = null;\n } else {\n const config: ErrorCaptureConfig =\n options.errorCapture && typeof options.errorCapture === \"object\"\n ? { ...DEFAULT_ERROR_CAPTURE, ...options.errorCapture }\n : { ...DEFAULT_ERROR_CAPTURE };\n this.errorTracker = new ErrorTracker({\n config,\n breadcrumbs: this.breadcrumbs,\n report: (err) => this.reportCapturedError(err),\n getContext: () => ({ ...this.errorContext }),\n getTags: () => ({ ...this.errorTags }),\n // GETTER, not a captured value — `setErrorBeforeSend()` mutates\n // `this.errorBeforeSend` after init() and the tracker MUST pick\n // up the new hook on the next error. Pre-fix we worked around\n // a captured-by-value field with `Object.defineProperty` on the\n // tracker's private opts; the contract is now a real getter so\n // we just hand it the closure and the hack is gone.\n beforeSend: () => this.errorBeforeSend,\n isConsented: () => true,\n // Derived from the configured baseUrl at construction time.\n // Used by the fetch wrapper to skip captureHttp on Crossdeck's\n // own requests — pre-fix the skip was hardcoded to\n // `api.cross-deck.com` and broke for customers on staging /\n // regional / self-hosted base URLs (recursive capture loop).\n selfHostname: extractSelfHostname(this.baseUrl),\n });\n this.errorTracker.install();\n }\n\n // Flush-on-exit. Default: enabled. Critical for serverless — without\n // this, Cloud Functions / Lambda exit before HTTP completes and events\n // vanish silently.\n if (options.flushOnExit === false) {\n this.flushOnExit = null;\n } else {\n this.flushOnExit = new FlushOnExit({\n drain: () => this.eventQueue.flush().then(() => undefined),\n timeoutMs: options.flushOnExitTimeoutMs,\n });\n this.flushOnExit.install();\n }\n\n // Boot heartbeat. Fire-and-forget. Solves the cold-start\n // verification problem: the moment the customer's process boots\n // and constructs the SDK, we phone home — the dashboard row flips\n // LIVE within ~200ms without the caller having to add an explicit\n // `.heartbeat()` call. Serverless functions cold-start, construct\n // the SDK, fire the boot heartbeat, and the verification surface\n // can confirm install end-to-end on the very first inbound request.\n //\n // Side benefit: the secret key is validated at process boot rather\n // than at first event flush, so misconfigurations surface in logs\n // immediately rather than minutes later when the queue first drains.\n //\n // Opt-out via testMode (unit tests don't want network) or by\n // setting bootHeartbeat=false explicitly. Errors are swallowed so\n // a broken backend / bad key / firewall never crashes the caller's\n // process — heartbeat is diagnostic-grade, not load-bearing.\n // Durability warning fires UNCONDITIONALLY (regardless of\n // bootHeartbeat / testMode opt-outs) because it is a local-only\n // debug signal — no network call, no phone-home. Pre-fix it sat\n // inside `emitBootTelemetry()` which sat inside the bootHeartbeat\n // gate, so a developer who set `bootHeartbeat: false` (common in\n // serverless-test setups, CI scripts, and customers who don't want\n // the boot phone-home) silently disabled the entire reason\n // `entitlementStore` exists. Audit P1 #9: warning must surface\n // independently of the heartbeat opt-out.\n this.emitDurabilityWarning();\n\n if (options.testMode !== true && options.bootHeartbeat !== false) {\n // setImmediate lets the constructor return first so the caller's\n // code reaches the next statement before we kick off the network\n // call. Mirrors how Sentry's `Sentry.init()` schedules its boot\n // session.\n setImmediate(() => {\n void this.heartbeat().catch((err) => {\n this.debug.emit(\n \"sdk.boot_heartbeat_failed\",\n \"Boot heartbeat failed (non-fatal — events will still flush).\",\n { message: err instanceof Error ? err.message : String(err) },\n );\n });\n\n // Boot telemetry phone-home — the aggregatable `sdk.boot`\n // event. Stays inside the bootHeartbeat gate (the durability\n // WARNING above is local; this is a track() that hits the\n // wire). Same `testMode` / `bootHeartbeat` opt-out as the\n // heartbeat itself.\n this.emitBootTelemetryEvent();\n });\n }\n }\n\n /**\n * Emit the honest \"no cold-start durability\" warning when the runtime\n * is serverless AND no `entitlementStore` is wired. Local-only debug\n * signal — no network call, no phone-home. Safe to fire from the\n * constructor before `setImmediate` because there is no I/O on this\n * path.\n *\n * `isServerless` AND no store is the gap: a cold start begins with an\n * empty in-memory cache and a brief Crossdeck outage in that window\n * would read a paying customer as un-entitled. That gap is\n * unavoidable without a store — so the SDK STATES it (a\n * `sdk.no_durable_store` debug warning) rather than hiding it.\n *\n * Audit P1 #9: this used to live INSIDE `emitBootTelemetry()` which\n * itself sat inside the `bootHeartbeat` gate, so any developer who\n * set `bootHeartbeat: false` silently disabled the entire reason\n * `entitlementStore` exists. Now split: warning fires\n * unconditionally; the boot phone-home stays gated.\n */\n private emitDurabilityWarning(): void {\n const isServerless = this.runtime.isServerless;\n const hasStore = this.entitlementStore !== null;\n if (isServerless && !hasStore) {\n this.debug.emit(\n \"sdk.no_durable_store\",\n `Running on a serverless host (${this.runtime.host}) with no entitlementStore. ` +\n \"The entitlement cache is in-memory only, so a cold start begins empty: \" +\n \"if Crossdeck is briefly unreachable during that window, isEntitled() can \" +\n \"read a paying customer as un-entitled. Wire `entitlementStore` (Redis / \" +\n \"your DB / a KV) to close this gap.\",\n { host: this.runtime.host, isServerless, durableStore: false },\n );\n }\n }\n\n /**\n * Emit the one-time `sdk.boot` telemetry event — the aggregatable\n * fact the backend pivots on (compute fleet-wide\n * \"% serverless-with-no-durable-store\"). Rides the batched + retried\n * + idempotent queue and is drained by flush-on-exit, so it survives\n * a serverless teardown.\n *\n * Why a `track()` event and not the heartbeat: `GET /v1/sdk/heartbeat`\n * carries no request body, so it cannot transport a structured\n * `durability` fact.\n *\n * Gated by `bootHeartbeat` (and `testMode`) because it IS a phone-\n * home — the unconditional surface is `emitDurabilityWarning()`,\n * which has no network call.\n */\n private emitBootTelemetryEvent(): void {\n const isServerless = this.runtime.isServerless;\n const hasStore = this.entitlementStore !== null;\n // coldStartDurable: a long-lived host keeps the process (hence the\n // in-memory cache) warm between requests, so it is durable across a\n // brief outage without a store. A serverless host is durable across\n // a cold start ONLY with a store wired.\n const coldStartDurable = hasStore || !isServerless;\n\n // One-time boot telemetry event. Fire-and-forget through track() —\n // best-effort, never throws into the constructor.\n try {\n this.track({\n name: \"sdk.boot\",\n anonymousId: this.processAnonymousId,\n properties: {\n \"durability.entitlementStore\": hasStore,\n \"durability.coldStartDurable\": coldStartDurable,\n \"durability.runtimeIsServerless\": isServerless,\n \"durability.runtimeHost\": this.runtime.host,\n \"durability.entitlementCacheTtlMs\": this.entitlementCache.ttl,\n },\n });\n } catch {\n // track() only throws on a missing event name — which cannot\n // happen here. Defensive: boot telemetry must never crash boot.\n }\n }\n\n // ============================================================\n // Identity — direct HTTP (transactional, not telemetry)\n // ============================================================\n\n async identify(\n userId: string,\n anonymousId: string,\n options?: IdentifyOptions & RequestOptions,\n ): Promise<AliasResult> {\n const { signal, timeoutMs, ...identifyOpts } = options ?? {};\n return this.aliasIdentity(\n { userId, anonymousId, ...identifyOpts },\n { signal, timeoutMs },\n );\n }\n\n async aliasIdentity(\n input: AliasIdentityInput,\n options?: RequestOptions,\n ): Promise<AliasResult> {\n if (!input.userId) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_user_id\",\n message: \"aliasIdentity requires a non-empty userId.\",\n });\n }\n if (!input.anonymousId) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_anonymous_id\",\n message: \"aliasIdentity requires a non-empty anonymousId.\",\n });\n }\n\n const traits = sanitizePropertyBag(input.traits, \"traits\");\n const body: Record<string, unknown> = {\n userId: input.userId,\n anonymousId: input.anonymousId,\n };\n if (input.email) body.email = input.email;\n if (traits && Object.keys(traits).length > 0) body.traits = traits;\n\n return this.http.request<AliasResult>(\"POST\", \"/identity/alias\", {\n body,\n signal: options?.signal,\n timeoutMs: options?.timeoutMs,\n });\n }\n\n async forget(hints: IdentityHints, options?: RequestOptions): Promise<ForgetResult> {\n const body = this.identityPayload(hints);\n return this.http.request<ForgetResult>(\"POST\", \"/identity/forget\", {\n body,\n signal: options?.signal,\n timeoutMs: options?.timeoutMs,\n });\n }\n\n // ============================================================\n // Entitlements — direct HTTP + TTL cache (v1.0.0+)\n //\n // `getEntitlements()` POSTs over the wire and populates the cache\n // under the response's canonical `crossdeckCustomerId`. Any\n // `userId` / `anonymousId` supplied as a hint is recorded as an\n // alias so a subsequent `isEntitled({ userId }, \"pro\")` resolves\n // to the same cache entry.\n // ============================================================\n\n /**\n * Fetch a customer's entitlements from Crossdeck and warm the cache.\n *\n * Durability — this is where last-known-good lives, NOT in the\n * synchronous `isEntitled()`:\n * - On a SUCCESSFUL fetch: the entitlement cache is populated and,\n * if an `entitlementStore` is configured, the result is persisted\n * to it (`await store.save(...)`). The cache + store now hold\n * server-confirmed truth.\n * - On a network FAILURE: the cache is marked refresh-failed for the\n * customer (so `diagnostics()` shows the staleness), then — if a\n * store is configured — last-known-good is loaded back from it\n * (`await store.load(...)`). If the store yields a snapshot, the\n * cache is populated from it and that snapshot is RETURNED as a\n * normal `EntitlementsListResponse` — a cold-start / outage no\n * longer fails a paying customer. If there is no store, or the\n * store is empty, the network error is rethrown unchanged so the\n * caller still sees the failure.\n *\n * The store is touched only here, inside the `await` that already\n * existed. `isEntitled()` remains a pure synchronous `Map` read.\n */\n async getEntitlements(\n hints: IdentityHints,\n options?: RequestOptions,\n ): Promise<EntitlementsListResponse> {\n let response: EntitlementsListResponse;\n try {\n response = await this.http.request<EntitlementsListResponse>(\"GET\", \"/entitlements\", {\n query: this.identityPayload(hints),\n signal: options?.signal,\n timeoutMs: options?.timeoutMs,\n });\n } catch (err) {\n // The refresh failed (Crossdeck unreachable / transient error).\n // Mark the customer stale so the staleness is visible via\n // diagnostics(), never a silent unbounded window.\n const failedCustomerId = this.resolveFailedRefreshCustomerId(hints);\n if (failedCustomerId) {\n this.entitlementCache.markRefreshFailed(failedCustomerId);\n }\n // Cold-start / outage fallback: try the durable store for last-\n // known-good. A hit repopulates the cache and is returned as a\n // normal response — the paying customer keeps access.\n const recovered = await this.loadEntitlementsFromStore(hints);\n if (recovered) {\n const recoveredResponse: EntitlementsListResponse = {\n object: \"list\",\n data: recovered.entitlements,\n crossdeckCustomerId: recovered.crossdeckCustomerId,\n env: recovered.env,\n };\n this.populateEntitlementCache(hints, recoveredResponse);\n // populateEntitlementCache → setForCustomer CLEARS the stale\n // flag (it treats a populate as a successful refresh). But this\n // was an OUTAGE fallback, not a fresh server read — Crossdeck is\n // still down. Re-mark the customer stale so diagnostics() keeps\n // showing the outage; the next genuinely-successful\n // getEntitlements() clears it for real.\n this.entitlementCache.markRefreshFailed(recovered.crossdeckCustomerId);\n this.debug.emit(\n \"sdk.entitlement_store_recovered\",\n `Crossdeck unreachable — served ${recovered.crossdeckCustomerId} from the durable store ` +\n `(${recovered.entitlements.length} entitlement(s), last refreshed ` +\n `${new Date(recovered.savedAt).toISOString()}).`,\n {\n customerId: recovered.crossdeckCustomerId,\n savedAt: recovered.savedAt,\n error: err instanceof Error ? err.message : String(err),\n },\n );\n return recoveredResponse;\n }\n // No store, or the store had nothing — the caller still sees the\n // failure. If the cache was previously warm for this customer it\n // keeps serving its own last-known-good via the synchronous\n // isEntitled(); the customer is now flagged stale (visible in\n // diagnostics()), which this signal makes explicit too.\n if (failedCustomerId && this.entitlementCache.isStale(failedCustomerId)) {\n this.debug.emit(\n \"sdk.entitlement_cache_stale\",\n `Crossdeck unreachable — entitlement cache for ${failedCustomerId} is now stale. ` +\n (this.entitlementStore\n ? \"No durable snapshot was available to recover from.\"\n : \"No entitlementStore is configured, so there is no durable fallback.\") +\n \" isEntitled() keeps serving last-known-good; staleness is visible in diagnostics().\",\n {\n customerId: failedCustomerId,\n durableStore: this.entitlementStore !== null,\n error: err instanceof Error ? err.message : String(err),\n },\n );\n }\n throw err;\n }\n this.populateEntitlementCache(hints, response);\n // Persist the fresh result so a future cold start / outage can\n // recover it. Best-effort — a store write failure must not fail an\n // otherwise-successful fetch.\n await this.saveEntitlementsToStore(hints, response);\n return response;\n }\n\n async getCustomerEntitlements(\n customerId: string,\n options?: RequestOptions,\n ): Promise<EntitlementsListResponse> {\n if (!customerId) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_customer_id\",\n message: \"getCustomerEntitlements requires a customerId.\",\n });\n }\n const response = await this.http.request<EntitlementsListResponse>(\n \"GET\",\n `/server/customers/${encodeURIComponent(customerId)}/entitlements`,\n {\n signal: options?.signal,\n timeoutMs: options?.timeoutMs,\n },\n );\n this.populateEntitlementCache({ customerId }, response);\n return response;\n }\n\n /**\n * Synchronous entitlement check. Returns `true` iff the customer\n * has the entitlement AND the cache entry is fresh (within\n * `entitlementCacheTtlMs`, default 60s). Returns `false` when the\n * cache is cold or expired.\n *\n * The hint can be any combination of `customerId` / `userId` /\n * `anonymousId`. After `getEntitlements({ userId })` populates the\n * cache, subsequent `isEntitled({ userId }, \"pro\")` calls within\n * TTL are memory reads (no HTTP). The \"warm cache\" pattern that\n * makes hot-path entitlement gates cheap.\n *\n * await server.getEntitlements({ userId }); // warm\n * if (server.isEntitled({ userId }, \"pro\")) { // synchronous\n * // ...\n * }\n *\n * Caller is responsible for re-warming after TTL elapses. The cache\n * does NOT auto-refresh on read (would block the hot path).\n */\n isEntitled(hint: IdentityHints | string, key: string): boolean {\n const customerId = this.resolveCacheCustomerId(hint);\n if (!customerId) return false;\n const result = this.entitlementCache.isEntitled(customerId, key);\n if (result) {\n this.debug.emit(\"sdk.entitlement_cache_used\", `Cache hit for ${customerId}/${key}.`);\n }\n return result;\n }\n\n /**\n * Snapshot of the customer's cached entitlements. Returns `[]` when\n * the cache is cold or expired. Same hint resolution as\n * `isEntitled()`.\n */\n listEntitlements(hint: IdentityHints | string): PublicEntitlement[] {\n const customerId = this.resolveCacheCustomerId(hint);\n if (!customerId) return [];\n return this.entitlementCache.list(customerId);\n }\n\n /**\n * Subscribe to entitlement-cache mutations. Listener fires after\n * `getEntitlements()` populates the cache or `shutdown()` clears\n * it. Returns an idempotent unsubscribe function.\n *\n * Used by callers that want to react to entitlement changes (e.g.\n * a websocket layer notifying connected clients of plan upgrades).\n * Listener errors are swallowed — surfaced via\n * `diagnostics().entitlements.listenerErrors`.\n */\n onEntitlementsChange(listener: EntitlementsListener): () => void {\n return this.entitlementCache.subscribe(listener);\n }\n\n // ============================================================\n // Events — queue-based track(); immediate ingest() for bulk imports\n // ============================================================\n\n /**\n * Queue an event for batched delivery. Returns synchronously — the\n * HTTP round-trip happens in the background.\n *\n * Behaviour parity with `@cross-deck/web`'s `track()`:\n * - Synchronous return, void.\n * - Throws sync on `missing_event_name`.\n * - Property bag sanitised through `validateEventProperties`.\n * - Runtime info (`runtime.*`) auto-merged into every event's\n * properties. Caller-supplied properties win on key collision.\n * - Breadcrumb auto-emitted (unless the name starts with `error.`,\n * which would cause a cycle).\n *\n * Differences from `@cross-deck/web`:\n * - Single-argument signature `track(event)` instead of\n * `track(name, properties)` — the Node wire shape needs the full\n * `ServerEvent` (identity hint, optional level + tags + categoryTags).\n * - Auto-fills `anonymousId` with `this.processAnonymousId` when no\n * identity hint is supplied. A captureError from\n * `uncaughtException` has no per-request context; without the\n * auto-fill, the event would be rejected at queue enqueue.\n */\n track(event: ServerEvent): void {\n if (!event.name) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_event_name\",\n message: \"track(event) requires a non-empty event.name.\",\n });\n }\n\n const sanitized = sanitizePropertyBag(event.properties, \"event properties\") ?? {};\n\n if (this.debug.enabled) {\n const flagged = findSensitivePropertyKeys(sanitized);\n if (flagged.length > 0) {\n this.debug.emit(\n \"sdk.sensitive_property_warning\",\n `Event \"${event.name}\" has potentially sensitive property names: ${flagged.join(\", \")}. Crossdeck is privacy-first — avoid sending PII unless intentional.`,\n { eventName: event.name, flagged },\n );\n }\n }\n\n // Enrichment order (parity with web SDK):\n // 1. Runtime info (auto-detected)\n // 2. Super-properties (registered via server.register(...))\n // 3. Group memberships → `$groups.<type>: id` (server.group(...))\n // 4. Caller-supplied properties (sanitised — most authoritative)\n //\n // Caller wins on key collision so a developer-set value overrides\n // anything the SDK auto-attached.\n const properties: EventProperties = {\n ...this.runtimeProperties,\n ...this.superProps.getSuperProperties(),\n ...sanitized,\n };\n const groupIds = this.superProps.getGroupIds();\n if (Object.keys(groupIds).length > 0 && properties.$groups === undefined) {\n properties.$groups = groupIds;\n }\n\n const identity = this.resolveIdentity(event);\n\n const queued: QueuedEvent = {\n eventId: event.eventId ?? mintId(\"evt\", 8),\n name: event.name,\n timestamp: event.timestamp ?? Date.now(),\n properties,\n ...identity,\n };\n if (event.level !== undefined) queued.level = event.level;\n if (event.tags !== undefined) queued.tags = event.tags;\n if (event.categoryTags !== undefined) queued.categoryTags = event.categoryTags;\n\n this.eventQueue.enqueue(queued);\n\n if (!event.name.startsWith(\"error.\")) {\n this.breadcrumbs.add({\n timestamp: queued.timestamp,\n category: categoryFor(event.name),\n message: event.name,\n data: sanitized,\n });\n }\n }\n\n /**\n * Immediate POST of one or more events. For bulk imports / replay\n * scenarios where the caller wants synchronous confirmation. Bypasses\n * the queue — no batching, no auto-fill of identity, no\n * runtime-enrichment.\n *\n * Use `track()` for the standard fire-and-forget telemetry path.\n * Use `ingest()` when you need:\n * - The IngestResponse synchronously.\n * - Strict per-event identity validation (no auto-fill).\n * - Caller-controlled idempotency key.\n */\n async ingest(events: ServerEvent[], options: IngestOptions = {}): Promise<IngestResponse> {\n if (!Array.isArray(events) || events.length === 0) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_events\",\n message: \"ingest requires at least one event.\",\n });\n }\n\n const normalized = events.map((event) => this.normalizeIngestEvent(event));\n const body: Record<string, unknown> = {\n events: normalized,\n sdk: { name: SDK_NAME, version: this.sdkVersion },\n // Match the queue's batch envelope (see event-queue.ts) — backend\n // cross-checks `environment` against the API-key-derived env and\n // rejects mismatches loudly (env_mismatch). Pre-fix this direct\n // ingest path skipped env, so a \"live key, env: sandbox\"\n // misconfig fell through silently for the bulk-import path.\n environment: this.env,\n };\n if (this.appId) body.appId = this.appId;\n\n return this.http.request<IngestResponse>(\"POST\", \"/events\", {\n body,\n idempotencyKey: options.idempotencyKey ?? mintId(\"batch\"),\n signal: options.signal,\n timeoutMs: options.timeoutMs,\n });\n }\n\n /**\n * Validate the secret key against the Crossdeck API and return the\n * resolved project + app metadata. Useful at boot to fail fast on a\n * misconfigured deployment — without this, a wrong secret key only\n * surfaces on the first event flush attempt, which may be minutes\n * after process start.\n *\n * const { projectId, appId, env, serverTime } = await server.heartbeat();\n *\n * Throws `CrossdeckError` on:\n * - `authentication_error` — secret key invalid / revoked\n * - `network_error` — couldn't reach the backend\n * - `request_timeout` — backend slow / unreachable\n *\n * Side effect: success records `(serverTime, clientTime)` for clock-\n * skew detection in `diagnostics().clock` (Phase 2 — not yet exposed\n * in this SDK release but the data is captured).\n *\n * Not auto-called. Caller decides whether the trade-off (one extra\n * boot request + ~50ms p50 latency) is worth the early-failure\n * signal. For serverless cold-starts, it usually is — cheap\n * compared to the cost of a silent broken secret in production.\n */\n async heartbeat(options?: RequestOptions): Promise<HeartbeatResponse> {\n const result = await this.http.request<HeartbeatResponse>(\"GET\", \"/sdk/heartbeat\", {\n signal: options?.signal,\n timeoutMs: options?.timeoutMs,\n });\n return result;\n }\n\n /**\n * Drain the event queue. Resolves when the in-flight batch completes\n * (success or failure). On failure, events stay queued for the next\n * scheduled retry — the resolved promise does NOT throw.\n *\n * Typical callers:\n * - End of a Lambda handler: `await server.flush()` before return\n * so events land before the platform freezes the process.\n * - Express server shutdown: `await server.flush()` inside the\n * SIGTERM handler.\n * - Tests: drain between assertions.\n *\n * Idempotent — flush on an empty queue is a no-op.\n */\n async flush(): Promise<void> {\n await this.eventQueue.flush();\n }\n\n async syncPurchases(\n input: SyncPurchaseInput,\n options?: RequestOptions,\n ): Promise<PurchaseResult> {\n if (!input.signedTransactionInfo) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_signed_transaction_info\",\n message: \"syncPurchases requires a signedTransactionInfo string.\",\n });\n }\n // Spread input FIRST so the explicit `rail` default below WINS.\n // Pre-fix order was `{ rail: input.rail ?? \"apple\", ...input }`\n // — but `...input` runs LAST and overrides the default with the\n // caller's literal `rail` key, including the case where the\n // caller passes `rail: undefined` explicitly (TypeScript treats\n // an undefined-typed property as \"key present\"). Reversing the\n // order so the default sits last fixes both the explicit-undefined\n // case AND the omitted-key case in one line.\n const rail = input.rail ?? \"apple\";\n return this.http.request<PurchaseResult>(\"POST\", \"/purchases/sync\", {\n body: { ...input, rail },\n signal: options?.signal,\n timeoutMs: options?.timeoutMs,\n });\n }\n\n // ============================================================\n // Manual entitlement controls + audit — direct HTTP\n // ============================================================\n\n async grantEntitlement(\n input: GrantEntitlementInput,\n options?: RequestOptions,\n ): Promise<EntitlementMutationResult> {\n if (!input.customerId) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_customer_id\",\n message: \"grantEntitlement requires a customerId.\",\n });\n }\n\n return this.http.request<EntitlementMutationResult>(\n \"POST\",\n `/server/customers/${encodeURIComponent(input.customerId)}/grant`,\n {\n body: {\n entitlementKey: input.entitlementKey,\n duration: input.duration,\n reason: input.reason,\n },\n signal: options?.signal,\n timeoutMs: options?.timeoutMs,\n },\n );\n }\n\n /**\n * Grant multiple entitlements in one logical call. Backend lacks a\n * bulk endpoint today, so this is a client-side fan-out — each\n * grant fires a separate request. Results return as a\n * settled-promise array so partial failures don't drop the rest:\n * the caller decides how to handle each `{ ok, value }` /\n * `{ ok: false, error }` entry.\n *\n * Use for ops sweeps (e.g. \"grant the entire `pro` tier a one-time\n * `pro_q1_bonus` entitlement\"). The bounded concurrency (default\n * `maxConcurrency: 5`) avoids hammering the backend; the rate-\n * limit policy on the server still kicks in if needed.\n */\n async bulkGrantEntitlement(\n grants: GrantEntitlementInput[],\n options?: RequestOptions & { maxConcurrency?: number },\n ): Promise<Array<{ input: GrantEntitlementInput; ok: true; value: EntitlementMutationResult } | { input: GrantEntitlementInput; ok: false; error: CrossdeckError }>> {\n return runBulkOperation(grants, options?.maxConcurrency ?? 5, (input) =>\n this.grantEntitlement(input, { signal: options?.signal, timeoutMs: options?.timeoutMs }),\n );\n }\n\n async revokeEntitlement(\n input: RevokeEntitlementInput,\n options?: RequestOptions,\n ): Promise<EntitlementMutationResult> {\n if (!input.customerId) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_customer_id\",\n message: \"revokeEntitlement requires a customerId.\",\n });\n }\n\n return this.http.request<EntitlementMutationResult>(\n \"POST\",\n `/server/customers/${encodeURIComponent(input.customerId)}/revoke`,\n {\n body: {\n entitlementKey: input.entitlementKey,\n reason: input.reason,\n },\n signal: options?.signal,\n timeoutMs: options?.timeoutMs,\n },\n );\n }\n\n /**\n * Revoke multiple entitlements in one logical call. Same\n * settled-array contract as `bulkGrantEntitlement` — see that\n * doc for behaviour notes.\n */\n async bulkRevokeEntitlement(\n revokes: RevokeEntitlementInput[],\n options?: RequestOptions & { maxConcurrency?: number },\n ): Promise<Array<{ input: RevokeEntitlementInput; ok: true; value: EntitlementMutationResult } | { input: RevokeEntitlementInput; ok: false; error: CrossdeckError }>> {\n return runBulkOperation(revokes, options?.maxConcurrency ?? 5, (input) =>\n this.revokeEntitlement(input, { signal: options?.signal, timeoutMs: options?.timeoutMs }),\n );\n }\n\n async getAuditEntry(eventId: string, options?: RequestOptions): Promise<AuditEntry> {\n if (!eventId) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_event_id\",\n message: \"getAuditEntry requires an eventId.\",\n });\n }\n\n const result = await this.http.request<AuditEntryResponse>(\n \"GET\",\n `/server/audit/${encodeURIComponent(eventId)}`,\n {\n signal: options?.signal,\n timeoutMs: options?.timeoutMs,\n },\n );\n return result.data;\n }\n\n // ============================================================\n // USP 1 — Error capture public surface\n // ============================================================\n\n /**\n * Manually capture an error from a try/catch block.\n *\n * try { … } catch (err) {\n * server.captureError(err, { context: { jobId }, tags: { flow: \"checkout\" } });\n * }\n *\n * The error ships through the same event queue analytics rides on\n * (retried, idempotent, runtime-enriched). Returns silently — never\n * throws, even if error capture is disabled.\n */\n captureError(\n error: unknown,\n options?: { context?: Record<string, unknown>; tags?: Record<string, string>; level?: ErrorLevel },\n ): void {\n if (!this.errorTracker) return;\n this.errorTracker.captureError(error, options);\n }\n\n /**\n * Capture a non-error signal as an issue. Sentry's `captureMessage`\n * pattern — for \"we hit the deprecated code path\" / \"soft-warning\n * triggered\" signals where there's no Error to throw.\n */\n captureMessage(message: string, level: ErrorLevel = \"info\"): void {\n if (!this.errorTracker) return;\n this.errorTracker.captureMessage(message, level);\n }\n\n /**\n * Attach a tag to every subsequent error report. Sentry pattern.\n * Tags are flat string key/value (queryable in the dashboard);\n * use `setContext()` for structured blobs.\n */\n setTag(key: string, value: string): void {\n this.errorTags[key] = value;\n }\n\n /** Bulk-set tags. Merges with existing tags. */\n setTags(tags: Record<string, string>): void {\n Object.assign(this.errorTags, tags);\n }\n\n /**\n * Attach a structured context blob to every subsequent error report.\n * Unlike tags (flat key/value), context is a named bag of arbitrary\n * JSON-serialisable data.\n *\n * server.setContext(\"cart\", { items: 3, total: 42.99 });\n */\n setContext(name: string, data: Record<string, unknown>): void {\n this.errorContext[name] = data;\n }\n\n /**\n * Add a custom breadcrumb to the rolling buffer. The last 50\n * breadcrumbs are attached to every subsequent error report —\n * \"what was the request doing right before things broke.\"\n */\n addBreadcrumb(crumb: Breadcrumb): void {\n this.breadcrumbs.add(crumb);\n }\n\n /**\n * Install a pre-send hook for errors. Return null to drop the report,\n * or a modified `CapturedError` to scrub fields. Sentry's\n * `beforeSend` pattern — the only place to add app-specific PII\n * redaction (auth tokens in URLs, etc.) before the report leaves the\n * process.\n *\n * The hook is called LAST, after rate-limit + sampling + path gates\n * already passed. A throwing hook falls back to the original error.\n */\n setErrorBeforeSend(hook: ((err: CapturedError) => CapturedError | null) | null): void {\n this.errorBeforeSend = hook;\n }\n\n // ============================================================\n // USP 2 — Super-properties + groups (Mixpanel pattern)\n // ============================================================\n\n /**\n * Register super-properties — every subsequent event carries these\n * keys on its `properties` bag automatically. Mixpanel pattern.\n *\n * server.register({ tenant: \"acme\", plan: \"pro\" });\n * server.track({ name: \"paywall_shown\", developerUserId: userId });\n * // ^ event carries `tenant` + `plan` in properties\n *\n * Values that are `null` are deleted (the explicit \"stop tracking\n * this key\" idiom). Sanitised through `validateEventProperties` so\n * a `{ avatar: <Buffer> }` payload can't poison the queue.\n *\n * Returns a defensive snapshot of the resulting bag.\n *\n * **Multi-tenant servers — read carefully.** Super-properties are\n * PROCESS-SCOPED. In a single Node process handling requests for\n * many tenants (the common multi-tenant SaaS shape), calling\n * `server.register({ tenant: \"acme\" })` taints EVERY subsequent\n * event from that process — including ones serving tenant \"beta\".\n * That's almost never what you want.\n *\n * The right pattern for per-request properties is to pass them on\n * the `track()` call itself:\n *\n * server.track({\n * name: \"paywall_shown\",\n * developerUserId: req.user.id,\n * properties: { tenant: req.tenantId, plan: req.user.plan },\n * });\n *\n * Reserve `register()` for properties that genuinely apply to every\n * event from this process — e.g. service version, region, build\n * commit. For those, `runtime-info` already provides\n * `runtime.serviceVersion` etc. automatically.\n */\n register(properties: Record<string, unknown>): Record<string, unknown> {\n const validation = validateEventProperties(properties);\n const result = this.superProps.register(validation.properties);\n this.debug.emit(\n \"sdk.super_property_registered\",\n `Super-properties updated. ${Object.keys(validation.properties).length} key(s) merged.`,\n );\n return result;\n }\n\n /** Remove a single super-property key. Idempotent. */\n unregister(key: string): void {\n this.superProps.unregister(key);\n }\n\n /** Snapshot of the current super-property bag. */\n getSuperProperties(): Record<string, unknown> {\n return this.superProps.getSuperProperties();\n }\n\n /**\n * Associate the current SDK instance with a group (org, team,\n * account, plan). Mixpanel / Segment Group Analytics pattern.\n *\n * server.group(\"org\", \"acme_inc\");\n * server.group(\"team\", \"design\", { headcount: 12 });\n *\n * Once set, every subsequent event carries `$groups.<type>: id` on\n * its `properties` bag, enabling B2B dashboard pivots. Pass\n * `id: null` to clear a group membership.\n *\n * Group traits are sanitised through `validateEventProperties`.\n */\n group(type: string, id: string | null, traits?: Record<string, unknown>): void {\n if (!type) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_group_type\",\n message: \"group(type, id) requires a non-empty type.\",\n });\n }\n const sanitisedTraits = traits ? validateEventProperties(traits).properties : undefined;\n this.superProps.setGroup(type, id, sanitisedTraits);\n }\n\n /** Snapshot of current group memberships keyed by type. */\n getGroups(): Record<string, GroupMembership> {\n return this.superProps.getGroups();\n }\n\n // ============================================================\n // Diagnostics — for debugging + the dashboard's heartbeat row\n // ============================================================\n\n diagnostics(): Diagnostics {\n return {\n sdkVersion: this.sdkVersion,\n baseUrl: this.baseUrl,\n secretKeyPrefix: this.secretKeyPrefix,\n env: this.env,\n runtime: {\n nodeVersion: this.runtime.nodeVersion,\n platform: this.runtime.platform,\n hostname: this.runtime.hostname,\n host: this.runtime.host,\n region: this.runtime.region,\n serviceName: this.runtime.serviceName,\n serviceVersion: this.runtime.serviceVersion,\n instanceId: this.runtime.instanceId,\n },\n entitlements: {\n count: this.entitlementCache.customerCount,\n lastUpdated: this.entitlementCache.lastUpdated,\n ttlMs: this.entitlementCache.ttl,\n listenerErrors: this.entitlementCache.listenerErrors,\n staleCustomers: this.entitlementCache.staleCustomerCount,\n isStale: this.entitlementCache.isAnyStale,\n lastRefreshFailedAt: this.entitlementCache.lastRefreshFailedAt,\n durableStore: this.entitlementStore !== null,\n // Cold-start durable iff a store is wired, OR the host is\n // long-lived (the process, hence the in-memory cache, survives).\n coldStartDurable:\n this.entitlementStore !== null || !this.runtime.isServerless,\n },\n events: this.eventQueue.getStats(),\n errors: {\n sessionCount: this.errorTracker?.reportedCount ?? 0,\n fingerprintsTracked: this.errorTracker?.fingerprintsTracked ?? 0,\n handlersInstalled: this.errorTracker?.handlersInstalled ?? false,\n },\n };\n }\n\n /**\n * Tear down handlers and clear in-memory state. Tests + custom\n * lifecycle callers only. Production code should rely on\n * `flush-on-exit` instead.\n */\n shutdown(reason: \"shutdown\" | \"dispose\" | \"asyncDispose\" = \"shutdown\"): void {\n this.emit(\"sdk.shutdown\", { reason });\n this.errorTracker?.uninstall();\n this.flushOnExit?.uninstall();\n this.eventQueue.reset();\n this.breadcrumbs.clear();\n this.superProps.clear();\n this.entitlementCache.clear();\n this.customerIdAliases.clear();\n this.errorContext = {};\n this.errorTags = {};\n this.errorBeforeSend = null;\n // Drop all event listeners last — caller's `sdk.shutdown` listeners\n // had a chance to run above.\n this.removeAllListeners();\n }\n\n // ============================================================\n // Internals\n // ============================================================\n\n /**\n * Convert a `CapturedError` into a `ServerEvent` and push through\n * `track()`. Goes through the same queue / enrichment / breadcrumb\n * pipeline analytics events do.\n */\n // ============================================================\n // Typed EventEmitter overloads — narrowing for the common methods\n // ============================================================\n\n override on<K extends keyof CrossdeckServerEvents>(\n event: K,\n listener: (...args: CrossdeckServerEvents[K]) => void,\n ): this;\n override on(event: string | symbol, listener: (...args: unknown[]) => void): this;\n override on(event: string | symbol, listener: (...args: unknown[]) => void): this {\n return super.on(event, listener);\n }\n\n override once<K extends keyof CrossdeckServerEvents>(\n event: K,\n listener: (...args: CrossdeckServerEvents[K]) => void,\n ): this;\n override once(event: string | symbol, listener: (...args: unknown[]) => void): this;\n override once(event: string | symbol, listener: (...args: unknown[]) => void): this {\n return super.once(event, listener);\n }\n\n override off<K extends keyof CrossdeckServerEvents>(\n event: K,\n listener: (...args: CrossdeckServerEvents[K]) => void,\n ): this;\n override off(event: string | symbol, listener: (...args: unknown[]) => void): this;\n override off(event: string | symbol, listener: (...args: unknown[]) => void): this {\n return super.off(event, listener);\n }\n\n override emit<K extends keyof CrossdeckServerEvents>(\n event: K,\n ...args: CrossdeckServerEvents[K]\n ): boolean;\n override emit(event: string | symbol, ...args: unknown[]): boolean;\n override emit(event: string | symbol, ...args: unknown[]): boolean {\n return super.emit(event, ...args);\n }\n\n // ============================================================\n // Health + readiness + lifecycle\n // ============================================================\n\n /**\n * Synchronous readiness check — \"is the SDK in a state where it\n * should accept new traffic?\". Used by Kubernetes readiness probes\n * and backpressure-aware callers.\n *\n * Returns `false` if:\n * - The event queue is in a sustained retry storm\n * (`consecutiveFailures >= 5`).\n * - The event queue's buffered count is at >= 80% of HARD_BUFFER_CAP.\n *\n * Otherwise `true`. The default isn't \"perfectly healthy\" — the\n * SDK is happy to enqueue events even during transient flush\n * failures because the queue's retry path handles them. Only\n * sustained failure flips this to `false`.\n */\n isReady(): boolean {\n const stats = this.eventQueue.getStats();\n if (stats.consecutiveFailures >= 5) return false;\n if (stats.buffered >= 800) return false; // 80% of HARD_BUFFER_CAP\n return true;\n }\n\n /**\n * Async wait until `isReady()` returns true OR the timeout elapses.\n * Resolves `true` on ready, `false` on timeout. Polls every 50ms by\n * default — backpressure for callers writing high-volume servers.\n *\n * if (!(await server.awaitReady(2000))) {\n * // shed load — SDK is in a retry storm, don't queue more\n * }\n */\n async awaitReady(timeoutMs = 5000, pollIntervalMs = 50): Promise<boolean> {\n if (this.isReady()) return true;\n const start = Date.now();\n return new Promise<boolean>((resolve) => {\n const tick = (): void => {\n if (this.isReady()) {\n resolve(true);\n return;\n }\n if (Date.now() - start >= timeoutMs) {\n resolve(false);\n return;\n }\n const t = setTimeout(tick, pollIntervalMs);\n if (typeof t.unref === \"function\") {\n try {\n t.unref();\n } catch {\n // ignore\n }\n }\n };\n tick();\n });\n }\n\n /**\n * Snapshot for Kubernetes liveness + readiness probes. `healthy`\n * stays true unless the SDK is in a catastrophic state (which\n * currently can't happen without crashing the process). `ready`\n * matches `isReady()`.\n *\n * app.get(\"/healthz\", (_req, res) => {\n * const h = server.getHealth();\n * res.status(h.healthy ? 200 : 503).json(h);\n * });\n */\n getHealth(): {\n ready: boolean;\n healthy: boolean;\n bufferedEvents: number;\n inFlight: number;\n consecutiveFailures: number;\n lastFlushAt: number;\n lastError: string | null;\n errorHandlersInstalled: boolean;\n } {\n const stats = this.eventQueue.getStats();\n return {\n ready: this.isReady(),\n healthy: true,\n bufferedEvents: stats.buffered,\n inFlight: stats.inFlight,\n consecutiveFailures: stats.consecutiveFailures,\n lastFlushAt: stats.lastFlushAt,\n lastError: stats.lastError,\n errorHandlersInstalled: this.errorTracker?.handlersInstalled ?? false,\n };\n }\n\n /**\n * Sync disposal hook — runs when a `using` declaration exits scope\n * (TC39 explicit-resource-management, Node 20+ / TS 5.2+).\n *\n * using server = new CrossdeckServer({ ... });\n * // ... use server ...\n * // at end of block, server[Symbol.dispose]() runs automatically\n *\n * `Symbol.dispose` is synchronous so we can't await `flush()` here\n * — for that, use `await using` + `[Symbol.asyncDispose]()`. This\n * sync variant just calls `shutdown()` (handler cleanup +\n * in-memory state wipe).\n */\n [Symbol.dispose](): void {\n this.shutdown(\"dispose\");\n }\n\n /**\n * Async disposal hook — runs when an `await using` declaration\n * exits scope. Awaits `flush()` THEN runs `shutdown()`. Use this\n * variant when the caller needs the queue drained before exit\n * (the common case for serverless handlers).\n *\n * await using server = new CrossdeckServer({ ... });\n */\n async [Symbol.asyncDispose](): Promise<void> {\n try {\n await this.flush();\n } catch {\n // shutdown is best-effort\n }\n this.shutdown(\"asyncDispose\");\n }\n\n // ============================================================\n\n private reportCapturedError(captured: CapturedError): void {\n try {\n this.emit(\"error.captured\", {\n fingerprint: captured.fingerprint,\n kind: captured.kind,\n message: captured.message,\n });\n } catch {\n // listener errors don't break the report\n }\n const properties: EventProperties = {\n fingerprint: captured.fingerprint,\n level: captured.level,\n errorType: captured.errorType,\n message: captured.message,\n stack: captured.rawStack ?? undefined,\n frames: captured.frames,\n tags: captured.tags,\n context: captured.context,\n breadcrumbs: captured.breadcrumbs,\n http: captured.http,\n };\n for (const k of Object.keys(properties)) {\n if (properties[k] === undefined) delete properties[k];\n }\n this.track({\n name: captured.kind,\n timestamp: captured.timestamp,\n properties,\n level: captured.level,\n tags: captured.tags,\n });\n }\n\n /**\n * Populate the entitlement cache from a fresh server response.\n * Records aliases so `userId` / `anonymousId` hints supplied to\n * `getEntitlements()` resolve to the same cache entry on subsequent\n * `isEntitled({ userId }, ...)` calls.\n *\n * Bounds the alias map at MAX_CUSTOMER_ID_ALIASES — once full, the\n * oldest aliases (by insertion order) are evicted FIFO. Symmetric\n * with the entitlement cache's max-customers cap.\n */\n private populateEntitlementCache(\n hints: IdentityHints,\n response: EntitlementsListResponse,\n ): void {\n const customerId = response.crossdeckCustomerId;\n if (!customerId) return;\n this.entitlementCache.setForCustomer(customerId, response.data);\n if (hints.userId) this.touchAlias(hints.userId, customerId);\n if (hints.anonymousId) this.touchAlias(hints.anonymousId, customerId);\n this.debug.emit(\n \"sdk.entitlement_cache_warm\",\n `Entitlement cache warmed for ${customerId} (${response.data.length} entitlement(s)).`,\n );\n try {\n this.emit(\"entitlements.warmed\", {\n customerId,\n count: response.data.length,\n });\n } catch {\n // listener errors don't break the response path\n }\n }\n\n /**\n * Persist a successful entitlements fetch to the durable store, if\n * one is configured. No-op when there is no store.\n *\n * Saved under EVERY identity the caller might later look up by — the\n * canonical `crossdeckCustomerId` plus any `userId` / `anonymousId`\n * hint. The Node cache resolves a hint to a canonical ID via an\n * in-memory alias map; on a cold start that map is empty, so a\n * failure-path `load()` must be able to hit the store with the raw\n * hint the caller passed. Saving under all keys makes that work.\n *\n * Best-effort: a store `save()` that throws is swallowed (logged in\n * debug) — it weakens durability for that customer but must never\n * fail an otherwise-successful `getEntitlements()`.\n */\n private async saveEntitlementsToStore(\n hints: IdentityHints,\n response: EntitlementsListResponse,\n ): Promise<void> {\n if (!this.entitlementStore) return;\n const customerId = response.crossdeckCustomerId;\n if (!customerId) return;\n const snapshot: StoredEntitlements = {\n v: 1,\n crossdeckCustomerId: customerId,\n entitlements: response.data,\n env: response.env,\n savedAt: Date.now(),\n };\n // Distinct keys only — dedupe so we don't write the same blob twice\n // when e.g. the caller passed customerId === crossdeckCustomerId.\n const keys = new Set<string>([customerId]);\n if (hints.customerId) keys.add(hints.customerId);\n if (hints.userId) keys.add(hints.userId);\n if (hints.anonymousId) keys.add(hints.anonymousId);\n for (const key of keys) {\n try {\n await this.entitlementStore.save(key, snapshot);\n } catch (err) {\n this.debug.emit(\n \"sdk.entitlement_store_recovered\",\n `entitlementStore.save failed for key ${key} — durability weakened for this customer.`,\n { key, error: err instanceof Error ? err.message : String(err) },\n );\n }\n }\n }\n\n /**\n * Load last-known-good entitlements from the durable store on a\n * network-failure path. Returns the first snapshot found across the\n * caller's identity keys, or `null` if there is no store / no stored\n * snapshot / every read failed.\n *\n * Tries the canonical `customerId` hint first, then `userId`, then\n * `anonymousId` — the order callers most commonly key by. A corrupt\n * or wrong-shaped blob is treated as a miss (the store is developer-\n * supplied; the SDK validates rather than trusts).\n */\n private async loadEntitlementsFromStore(\n hints: IdentityHints,\n ): Promise<StoredEntitlements | null> {\n if (!this.entitlementStore) return null;\n const keys: string[] = [];\n if (hints.customerId) keys.push(hints.customerId);\n if (hints.userId) keys.push(hints.userId);\n if (hints.anonymousId) keys.push(hints.anonymousId);\n for (const key of keys) {\n let loaded: StoredEntitlements | null = null;\n try {\n loaded = await this.entitlementStore.load(key);\n } catch {\n // A throwing load() degrades to \"no durable copy for this key\".\n continue;\n }\n if (isValidStoredEntitlements(loaded)) return loaded;\n }\n return null;\n }\n\n /**\n * Resolve the customer ID to stamp a failed-refresh marker against.\n *\n * Prefers a canonical ID the cache already knows (so the marker lands\n * on the existing warm entry), then falls back to whatever raw hint\n * the caller supplied — on a true cold-start failure there is no\n * cache entry yet, and marking under the hint still makes \"we tried\n * for this customer and Crossdeck was down\" observable.\n */\n private resolveFailedRefreshCustomerId(hints: IdentityHints): string | null {\n const known = this.resolveCacheCustomerId(hints);\n if (known) return known;\n return hints.customerId ?? hints.userId ?? hints.anonymousId ?? null;\n }\n\n private touchAlias(alias: string, customerId: string): void {\n // Delete-and-reinsert so the alias moves to the end of insertion\n // order (LRU \"touch\").\n this.customerIdAliases.delete(alias);\n this.customerIdAliases.set(alias, customerId);\n while (this.customerIdAliases.size > MAX_CUSTOMER_ID_ALIASES) {\n const oldest = this.customerIdAliases.keys().next().value;\n if (oldest === undefined) break;\n this.customerIdAliases.delete(oldest);\n }\n }\n\n /**\n * Resolve any hint shape (canonical customerId / userId hint /\n * anonymousId hint / raw string) to a `crossdeckCustomerId` if we\n * have a cache entry for it.\n *\n * String overload is STRICT on the canonical-id shape. Pre-fix\n * `isFresh(raw)` treated any string with a cache entry as a valid\n * canonical id — if tenant A's userId happened to collide with\n * tenant B's crossdeckCustomerId, A's call would resolve to B's\n * cached entitlements. Bounded by the `cdcust_` prefix convention\n * (which both SDKs and the backend mint, see\n * backend/src/lib/customers.ts) — anything else is treated purely\n * as an alias lookup, never as a canonical id. Audit P1 #19.\n */\n private resolveCacheCustomerId(hint: IdentityHints | string): string | null {\n if (typeof hint === \"string\") {\n // Canonical-shape check FIRST: only `cdcust_…`-prefixed strings\n // are eligible to be returned as-is. Non-prefixed strings drop\n // straight to the alias map — no cross-tenant primitive even if\n // a stray cache entry exists for the same string under a\n // different prefix family.\n if (hint.startsWith(\"cdcust_\") && this.entitlementCache.isFresh(hint)) {\n return hint;\n }\n return this.customerIdAliases.get(hint) ?? null;\n }\n if (hint.customerId) return hint.customerId;\n if (hint.userId) return this.customerIdAliases.get(hint.userId) ?? null;\n if (hint.anonymousId) return this.customerIdAliases.get(hint.anonymousId) ?? null;\n return null;\n }\n\n private identityPayload(hints: IdentityHints): Record<string, string> {\n const payload: Record<string, string> = {};\n if (typeof hints.customerId === \"string\" && hints.customerId) {\n payload.customerId = hints.customerId;\n }\n if (typeof hints.userId === \"string\" && hints.userId) {\n payload.userId = hints.userId;\n }\n if (typeof hints.anonymousId === \"string\" && hints.anonymousId) {\n payload.anonymousId = hints.anonymousId;\n }\n if (Object.keys(payload).length === 0) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_identity\",\n message: \"Provide at least one of customerId, userId, or anonymousId.\",\n });\n }\n return payload;\n }\n\n /**\n * Resolve event identity. Caller-supplied wins; falls back to\n * `processAnonymousId` so events from `captureError` /\n * uncaughtException always have at least one identity hint.\n */\n private resolveIdentity(event: ServerEvent): {\n developerUserId?: string;\n anonymousId?: string;\n crossdeckCustomerId?: string;\n } {\n const out: { developerUserId?: string; anonymousId?: string; crossdeckCustomerId?: string } = {};\n if (event.developerUserId) out.developerUserId = event.developerUserId;\n if (event.anonymousId) out.anonymousId = event.anonymousId;\n if (event.crossdeckCustomerId) out.crossdeckCustomerId = event.crossdeckCustomerId;\n if (!out.developerUserId && !out.anonymousId && !out.crossdeckCustomerId) {\n out.anonymousId = this.processAnonymousId;\n }\n return out;\n }\n\n /**\n * Strict normalisation for `ingest()` — no auto-fill of identity,\n * caller must supply at least one hint per event. Matches v0.1.0\n * behaviour for backward compatibility with bulk-import callers.\n */\n private normalizeIngestEvent(event: ServerEvent): ServerEvent {\n if (!event.name) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_event_name\",\n message: \"Each event requires a non-empty name.\",\n });\n }\n const hasIdentity =\n Boolean(event.developerUserId) ||\n Boolean(event.anonymousId) ||\n Boolean(event.crossdeckCustomerId);\n if (!hasIdentity) {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"missing_identity\",\n message:\n \"Each event requires at least one of developerUserId, anonymousId, or crossdeckCustomerId.\",\n });\n }\n const properties = sanitizePropertyBag(event.properties, \"event properties\");\n return {\n ...event,\n properties,\n eventId: event.eventId ?? mintId(\"evt\", 8),\n timestamp: event.timestamp ?? Date.now(),\n };\n }\n}\n\nfunction sanitizePropertyBag(\n input: Record<string, unknown> | undefined,\n fieldName: string,\n): Record<string, unknown> | undefined {\n if (input === undefined) return undefined;\n try {\n return validateEventProperties(input).properties;\n } catch {\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"serialization_failed\",\n message: `${fieldName} could not be serialized.`,\n });\n }\n}\n\n/**\n * Validate a value read back from a developer-supplied\n * `EntitlementStore`. The store is external (Redis / a DB / a KV) and\n * may return a corrupt, partial, stale-schema or attacker-influenced\n * blob — the SDK validates the shape rather than trusting it. Anything\n * that fails is treated as a cache miss (the SDK then rethrows the\n * original network error), never a crash.\n *\n * Narrows to `StoredEntitlements`: a versioned blob with a non-empty\n * `crossdeckCustomerId`, an `entitlements` array, and a known `env`.\n * Individual entitlement objects are not deep-validated here — the\n * cache + `isEntitled()` already tolerate odd entries (a missing `key`\n * simply never matches), and `validUntil` is honoured at read time.\n */\nfunction isValidStoredEntitlements(value: unknown): value is StoredEntitlements {\n if (typeof value !== \"object\" || value === null) return false;\n const v = value as Record<string, unknown>;\n return (\n v.v === 1 &&\n typeof v.crossdeckCustomerId === \"string\" &&\n v.crossdeckCustomerId.length > 0 &&\n Array.isArray(v.entitlements) &&\n (v.env === \"production\" || v.env === \"sandbox\") &&\n typeof v.savedAt === \"number\"\n );\n}\n\n/**\n * Map an event name to a breadcrumb category. Mirrors the web SDK's\n * mapping so breadcrumb timelines in error reports look the same\n * regardless of which SDK emitted them.\n */\nfunction categoryFor(name: string): BreadcrumbCategory {\n if (name.startsWith(\"page.\") || name.startsWith(\"navigation.\")) return \"navigation\";\n if (name.startsWith(\"element.\") || name.startsWith(\"ui.click\")) return \"ui.click\";\n if (name.startsWith(\"http.\") || name === \"request.handled\") return \"http\";\n return \"custom\";\n}\n\n/**\n * Maximum number of `userId` / `anonymousId` → `crossdeckCustomerId`\n * aliases we track. Matches the entitlement cache's default\n * max-customers for symmetry.\n */\nconst MAX_CUSTOMER_ID_ALIASES = 10_000;\n\n/**\n * Infer environment from the secret-key prefix. Stripe pattern —\n * `cd_sk_live_*` means production, anything else is treated as sandbox\n * (fixture / test keys typically use `cd_sk_test_*` but bare\n * `cd_sk_*` from test fixtures also falls here).\n */\nfunction inferEnvFromKey(secretKey: string): Environment {\n return secretKey.startsWith(\"cd_sk_live_\") ? \"production\" : \"sandbox\";\n}\n\n/**\n * Bounded-concurrency promise-settle helper for bulk operations.\n * Returns one entry per input, in input order. Each entry is either\n * `{ ok: true, value }` or `{ ok: false, error }`. Never rejects —\n * partial failures don't drop the rest of the batch.\n */\nasync function runBulkOperation<TInput, TResult>(\n inputs: TInput[],\n maxConcurrency: number,\n op: (input: TInput) => Promise<TResult>,\n): Promise<Array<{ input: TInput; ok: true; value: TResult } | { input: TInput; ok: false; error: CrossdeckError }>> {\n const results: Array<\n | { input: TInput; ok: true; value: TResult }\n | { input: TInput; ok: false; error: CrossdeckError }\n > = new Array(inputs.length);\n let cursor = 0;\n const workers = new Array(Math.min(maxConcurrency, Math.max(1, inputs.length)))\n .fill(0)\n .map(async () => {\n while (true) {\n const index = cursor++;\n if (index >= inputs.length) return;\n const input = inputs[index]!;\n try {\n const value = await op(input);\n results[index] = { input, ok: true, value };\n } catch (err) {\n results[index] = {\n input,\n ok: false,\n error:\n err instanceof CrossdeckError\n ? err\n : new CrossdeckError({\n type: \"internal_error\",\n code: \"bulk_operation_failed\",\n message: err instanceof Error ? err.message : String(err),\n }),\n };\n }\n }\n });\n await Promise.all(workers);\n return results;\n}\n\n/**\n * Mask the secret key for safe display in diagnostics + logs. Stripe\n * pattern: keep the env-revealing prefix (`cd_sk_test_` / `cd_sk_live_`),\n * replace the middle with `****`, append the last 4 chars when the key\n * is long enough that those 4 chars don't overlap the prefix. Test\n * fixtures with short keys degrade gracefully to `prefix + ****` with\n * no tail.\n */\nfunction maskSecretKey(secretKey: string): string {\n const m = secretKey.match(/^cd_sk_(test|live)_/);\n const prefix = m ? m[0] : secretKey.slice(0, 11);\n const tail = secretKey.length > prefix.length + 4 ? secretKey.slice(-4) : \"\";\n return `${prefix}****${tail}`;\n}\n","export 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 /**\n * Error code. The canonical set is the `CrossdeckErrorCode` literal\n * union exported from `./error-codes` (derived from\n * `CROSSDECK_ERROR_CODES`). Typed as `string` here so the SDK can\n * still surface server-returned codes that aren't (yet) in the\n * catalogue without a wholesale recompile of every consumer.\n *\n * For type-safe code comparisons in caller code, use:\n * `import { CrossdeckErrorCode, isCrossdeckErrorCode } from \"@cross-deck/node\"`\n * `if (isCrossdeckErrorCode(err.code) && err.code === \"webhook_invalid_signature\") {}`\n */\n code: string;\n message: string;\n requestId?: string;\n status?: number;\n retryAfterMs?: number;\n}\n\nexport class CrossdeckError extends Error {\n public readonly type: CrossdeckErrorType;\n public readonly code: string;\n public readonly requestId?: string;\n public readonly status?: number;\n public readonly retryAfterMs?: number;\n\n constructor(payload: CrossdeckErrorPayload) {\n super(payload.message);\n this.name = \"CrossdeckError\";\n this.type = payload.type;\n this.code = payload.code;\n this.requestId = payload.requestId;\n this.status = payload.status;\n this.retryAfterMs = payload.retryAfterMs;\n Object.setPrototypeOf(this, CrossdeckError.prototype);\n }\n\n /**\n * JSON representation suitable for structured loggers. Without this,\n * `console.log(err)` and most log frameworks (Pino, Winston) emit\n * only `name` + `message` + `stack` — losing `type`, `code`,\n * `requestId`, `status`, `retryAfterMs`. With `toJSON`, calling\n * `JSON.stringify(err)` or passing the error to a logger that\n * serialises via JSON includes the full diagnostic surface.\n *\n * Stripe pattern. Critical for production observability.\n */\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n message: this.message,\n type: this.type,\n code: this.code,\n requestId: this.requestId,\n status: this.status,\n retryAfterMs: this.retryAfterMs,\n stack: this.stack,\n };\n }\n}\n\n/**\n * Authentication failure — the secret key is missing, invalid, or\n * revoked. Maps to `type: \"authentication_error\"`. Includes codes:\n * `invalid_secret_key`, `webhook_invalid_signature`,\n * `webhook_replay_window_exceeded`, and any 401 from the backend.\n *\n * if (err instanceof CrossdeckAuthenticationError) { ... }\n *\n * Stripe pattern — typed subclasses make caller error-handling\n * clean and let TypeScript narrow on `instanceof`.\n */\nexport class CrossdeckAuthenticationError extends CrossdeckError {\n constructor(payload: CrossdeckErrorPayload) {\n super(payload);\n this.name = \"CrossdeckAuthenticationError\";\n Object.setPrototypeOf(this, CrossdeckAuthenticationError.prototype);\n }\n}\n\n/**\n * Caller is authenticated but doesn't have permission for the\n * requested resource. Maps to `type: \"permission_error\"`.\n */\nexport class CrossdeckPermissionError extends CrossdeckError {\n constructor(payload: CrossdeckErrorPayload) {\n super(payload);\n this.name = \"CrossdeckPermissionError\";\n Object.setPrototypeOf(this, CrossdeckPermissionError.prototype);\n }\n}\n\n/**\n * Request is malformed or violates a validation rule. Maps to\n * `type: \"invalid_request_error\"`. Includes codes like\n * `missing_user_id`, `missing_event_name`, `serialization_failed`,\n * and any 4xx (other than 401/403/429) from the backend.\n */\nexport class CrossdeckValidationError extends CrossdeckError {\n constructor(payload: CrossdeckErrorPayload) {\n super(payload);\n this.name = \"CrossdeckValidationError\";\n Object.setPrototypeOf(this, CrossdeckValidationError.prototype);\n }\n}\n\n/**\n * Rate limit exceeded. Maps to `type: \"rate_limit_error\"`. Carries\n * `retryAfterMs` from the server's `Retry-After` header — caller\n * should back off and retry only after that delay.\n */\nexport class CrossdeckRateLimitError extends CrossdeckError {\n constructor(payload: CrossdeckErrorPayload) {\n super(payload);\n this.name = \"CrossdeckRateLimitError\";\n Object.setPrototypeOf(this, CrossdeckRateLimitError.prototype);\n }\n}\n\n/**\n * Network-layer failure — `fetch` threw, the request timed out, or\n * the response body was unparseable. Maps to `type: \"network_error\"`\n * with codes `fetch_failed`, `request_timeout`, or `internal_error`\n * (`invalid_json_response`). Almost always transient; the SDK auto-\n * retries event-queue flushes.\n */\nexport class CrossdeckNetworkError extends CrossdeckError {\n constructor(payload: CrossdeckErrorPayload) {\n super(payload);\n this.name = \"CrossdeckNetworkError\";\n Object.setPrototypeOf(this, CrossdeckNetworkError.prototype);\n }\n}\n\n/**\n * Backend returned a 5xx or the SDK detected an unexpected\n * internal state. Maps to `type: \"internal_error\"`.\n */\nexport class CrossdeckInternalError extends CrossdeckError {\n constructor(payload: CrossdeckErrorPayload) {\n super(payload);\n this.name = \"CrossdeckInternalError\";\n Object.setPrototypeOf(this, CrossdeckInternalError.prototype);\n }\n}\n\n/**\n * Misconfigured SDK options at construction time. Maps to\n * `type: \"configuration_error\"`. Includes codes like\n * `invalid_secret_key`, `webhook_missing_secret`. Never retryable —\n * always a developer fix.\n */\nexport class CrossdeckConfigurationError extends CrossdeckError {\n constructor(payload: CrossdeckErrorPayload) {\n super(payload);\n this.name = \"CrossdeckConfigurationError\";\n Object.setPrototypeOf(this, CrossdeckConfigurationError.prototype);\n }\n}\n\n/**\n * Construct the right `CrossdeckError` subclass for a given payload's\n * `type`. Used by `crossdeckErrorFromResponse` + by any internal call\n * site that throws — gives every thrown error its semantic subclass\n * without forcing every call site to know the mapping.\n */\nexport function makeCrossdeckError(payload: CrossdeckErrorPayload): CrossdeckError {\n switch (payload.type) {\n case \"authentication_error\":\n return new CrossdeckAuthenticationError(payload);\n case \"permission_error\":\n return new CrossdeckPermissionError(payload);\n case \"invalid_request_error\":\n return new CrossdeckValidationError(payload);\n case \"rate_limit_error\":\n return new CrossdeckRateLimitError(payload);\n case \"network_error\":\n return new CrossdeckNetworkError(payload);\n case \"internal_error\":\n return new CrossdeckInternalError(payload);\n case \"configuration_error\":\n return new CrossdeckConfigurationError(payload);\n default:\n // Exhaustiveness fallback — also covers the edge case where the\n // backend introduces a new error type before the SDK is updated.\n return new CrossdeckError(payload);\n }\n}\n\nexport async function crossdeckErrorFromResponse(res: Response): Promise<CrossdeckError> {\n const requestId = res.headers.get(\"x-request-id\") ?? undefined;\n const retryAfterMs = parseRetryAfterHeader(res.headers.get(\"retry-after\"));\n let body: unknown;\n try {\n body = await res.json();\n } catch {\n body = null;\n }\n const envelope = (body as { error?: Partial<CrossdeckErrorPayload> & { request_id?: string } })?.error;\n if (envelope && typeof envelope.type === \"string\" && typeof envelope.code === \"string\") {\n return makeCrossdeckError({\n type: envelope.type as CrossdeckErrorType,\n code: envelope.code,\n message: envelope.message ?? `HTTP ${res.status}`,\n requestId: envelope.request_id ?? requestId,\n status: res.status,\n retryAfterMs,\n });\n }\n return makeCrossdeckError({\n type: typeMapForStatus(res.status),\n code: `http_${res.status}`,\n message: `HTTP ${res.status} ${res.statusText || \"\"}`.trim(),\n requestId,\n status: res.status,\n retryAfterMs,\n });\n}\n\nexport function parseRetryAfterHeader(value: string | null): number | undefined {\n if (!value) return undefined;\n const trimmed = value.trim();\n if (!trimmed) return undefined;\n if (/^\\d+(\\.\\d+)?$/.test(trimmed)) {\n const secs = Number(trimmed);\n if (!Number.isFinite(secs) || secs < 0) return undefined;\n return Math.round(secs * 1000);\n }\n if (!/[a-zA-Z,/:]/.test(trimmed)) return undefined;\n const target = Date.parse(trimmed);\n if (!Number.isFinite(target)) return undefined;\n const delta = target - Date.now();\n return delta > 0 ? delta : 0;\n}\n\nfunction typeMapForStatus(status: number): CrossdeckErrorType {\n if (status === 401) return \"authentication_error\";\n if (status === 403) return \"permission_error\";\n if (status === 429) return \"rate_limit_error\";\n if (status >= 400 && status < 500) return \"invalid_request_error\";\n return \"internal_error\";\n}\n","/**\n * Property validation + coercion for Node SDK payloads.\n *\n * Ported directly from `@cross-deck/web` so web + node share the same\n * sanitisation contract for traits and event property bags.\n */\n\ntype EventProperties = Record<string, unknown>;\n\nexport interface ValidationOptions {\n maxStringLength?: number;\n maxBatchPropertyBytes?: number;\n maxDepth?: number;\n}\n\nexport interface ValidationWarning {\n kind:\n | \"dropped_function\"\n | \"dropped_symbol\"\n | \"dropped_undefined\"\n | \"coerced_date\"\n | \"coerced_bigint\"\n | \"coerced_error\"\n | \"coerced_map\"\n | \"coerced_set\"\n | \"truncated_string\"\n | \"circular_reference\"\n | \"depth_exceeded\"\n | \"non_serialisable\"\n | \"size_cap_exceeded\";\n key: string;\n}\n\nexport interface ValidationResult {\n properties: EventProperties;\n warnings: ValidationWarning[];\n}\n\nconst DEFAULT_MAX_STRING = 1024;\nconst DEFAULT_MAX_BYTES = 8 * 1024;\nconst DEFAULT_MAX_DEPTH = 5;\n\nexport function validateEventProperties(\n input: EventProperties | undefined,\n options: ValidationOptions = {},\n): ValidationResult {\n const warnings: ValidationWarning[] = [];\n if (!input) return { properties: {}, warnings };\n\n const maxStringLength = options.maxStringLength ?? DEFAULT_MAX_STRING;\n const maxBatchPropertyBytes = options.maxBatchPropertyBytes ?? DEFAULT_MAX_BYTES;\n const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;\n\n const seen = new WeakSet<object>();\n\n const visit = (\n value: unknown,\n key: string,\n depth: number,\n ): { keep: boolean; value: unknown } => {\n if (depth > maxDepth) {\n warnings.push({ kind: \"depth_exceeded\", key });\n return { keep: true, value: \"[depth-exceeded]\" };\n }\n if (value === null) return { keep: true, value: null };\n const t = typeof value;\n if (t === \"string\") {\n const s = value as string;\n if (s.length > maxStringLength) {\n warnings.push({ kind: \"truncated_string\", key });\n return { keep: true, value: s.slice(0, maxStringLength - 1) + \"…\" };\n }\n return { keep: true, value: s };\n }\n if (t === \"number\") {\n if (!Number.isFinite(value as number)) {\n warnings.push({ kind: \"non_serialisable\", key });\n return { keep: true, value: null };\n }\n return { keep: true, value };\n }\n if (t === \"boolean\") return { keep: true, value };\n if (t === \"bigint\") {\n warnings.push({ kind: \"coerced_bigint\", key });\n return { keep: true, value: (value as bigint).toString() };\n }\n if (t === \"function\") {\n warnings.push({ kind: \"dropped_function\", key });\n return { keep: false, value: undefined };\n }\n if (t === \"symbol\") {\n warnings.push({ kind: \"dropped_symbol\", key });\n return { keep: false, value: undefined };\n }\n if (t === \"undefined\") {\n warnings.push({ kind: \"dropped_undefined\", key });\n return { keep: false, value: undefined };\n }\n\n if (value instanceof Date) {\n warnings.push({ kind: \"coerced_date\", key });\n const iso = Number.isFinite(value.getTime()) ? value.toISOString() : null;\n return { keep: true, value: iso };\n }\n if (value instanceof Error) {\n warnings.push({ kind: \"coerced_error\", key });\n return {\n keep: true,\n value: {\n name: value.name,\n message: value.message,\n stack:\n typeof value.stack === \"string\"\n ? value.stack.slice(0, maxStringLength)\n : undefined,\n },\n };\n }\n if (value instanceof Map) {\n warnings.push({ kind: \"coerced_map\", key });\n const obj: Record<string, unknown> = {};\n for (const [k, v] of value.entries()) {\n const subKey = typeof k === \"string\" ? k : String(k);\n const result = visit(v, `${key}.${subKey}`, depth + 1);\n if (result.keep) obj[subKey] = result.value;\n }\n return { keep: true, value: obj };\n }\n if (value instanceof Set) {\n warnings.push({ kind: \"coerced_set\", key });\n const arr: unknown[] = [];\n let i = 0;\n for (const v of value.values()) {\n const result = visit(v, `${key}[${i}]`, depth + 1);\n if (result.keep) arr.push(result.value);\n i++;\n }\n return { keep: true, value: arr };\n }\n\n if (Array.isArray(value)) {\n if (seen.has(value)) {\n warnings.push({ kind: \"circular_reference\", key });\n return { keep: true, value: \"[circular]\" };\n }\n seen.add(value);\n const out: unknown[] = [];\n for (let i = 0; i < value.length; i++) {\n const result = visit(value[i], `${key}[${i}]`, depth + 1);\n if (result.keep) out.push(result.value);\n }\n return { keep: true, value: out };\n }\n\n if (t === \"object\") {\n const obj = value as Record<string, unknown>;\n if (seen.has(obj)) {\n warnings.push({ kind: \"circular_reference\", key });\n return { keep: true, value: \"[circular]\" };\n }\n seen.add(obj);\n const out: Record<string, unknown> = {};\n for (const k of Object.keys(obj)) {\n const result = visit(obj[k], `${key}.${k}`, depth + 1);\n if (result.keep) out[k] = result.value;\n }\n return { keep: true, value: out };\n }\n\n warnings.push({ kind: \"non_serialisable\", key });\n try {\n return { keep: true, value: String(value) };\n } catch {\n return { keep: false, value: undefined };\n }\n };\n\n const cleaned: Record<string, unknown> = {};\n for (const k of Object.keys(input)) {\n const result = visit(input[k], k, 0);\n if (result.keep) cleaned[k] = result.value;\n }\n\n const serialised = safeStringify(cleaned);\n if (serialised && byteLength(serialised) > maxBatchPropertyBytes) {\n warnings.push({ kind: \"size_cap_exceeded\", key: \"*\" });\n const sizes = Object.keys(cleaned)\n .map((k) => ({ k, size: byteLength(safeStringify(cleaned[k]) ?? \"\") }))\n .sort((a, b) => b.size - a.size);\n let currentSize = byteLength(serialised);\n for (const { k } of sizes) {\n if (currentSize <= maxBatchPropertyBytes) break;\n currentSize -= sizes.find((s) => s.k === k)!.size;\n delete cleaned[k];\n }\n cleaned.__truncated = true;\n }\n\n return { properties: cleaned, warnings };\n}\n\nfunction safeStringify(v: unknown): string | null {\n try {\n return JSON.stringify(v) ?? null;\n } catch {\n return null;\n }\n}\n\nfunction byteLength(s: string): number {\n if (typeof TextEncoder !== \"undefined\") {\n return new TextEncoder().encode(s).length;\n }\n return s.length * 4;\n}\n","/**\n * SDK version constant — generated by `scripts/sync-sdk-versions.mjs`.\n *\n * Single source of truth: the `version` field in this package's\n * package.json. The sync script writes this file so that\n * `SDK_VERSION` is a plain TypeScript literal at runtime — no\n * runtime JSON-import gotcha (Node ESM requires\n * `with { type: \"json\" }` to import JSON as ESM, and the published\n * dist file would otherwise fail to load).\n *\n * Drift protection: `node scripts/sync-sdk-versions.mjs --check` (the\n * CI gate) flags this file when it falls out of sync with package.json.\n * Bumping `package.json` without re-running the sync script fails CI.\n *\n * Do NOT edit by hand — `node scripts/sync-sdk-versions.mjs`.\n */\nexport const SDK_VERSION = \"1.3.1\";\nexport const SDK_NAME = \"@cross-deck/node\";\n","import { CrossdeckError, makeCrossdeckError } from \"./errors\";\nimport { crossdeckErrorFromResponse } from \"./errors\";\nimport { validateEventProperties } from \"./event-validation\";\n// Single source of truth — `_version.ts` is generated from\n// package.json by `scripts/sync-sdk-versions.mjs`. A plain TypeScript\n// re-export here means the runtime `Crossdeck-Sdk-Version` header\n// always matches the published bundle, with zero Node-ESM JSON-import\n// gotchas. Pre-fix this was a hardcoded literal that drifted from\n// package.json. `--check` mode of the sync script fails CI on drift.\nimport { SDK_NAME, SDK_VERSION } from \"./_version\";\nexport { SDK_NAME, SDK_VERSION };\n\nexport const DEFAULT_BASE_URL = \"https://api.cross-deck.com/v1\";\nexport const DEFAULT_TIMEOUT_MS = 15_000;\n\n/**\n * Pinned Crossdeck API version sent on every request as\n * `Crossdeck-Api-Version`. Forward-compat with backend evolution —\n * server-side breaking changes ship under a new version date; pinning\n * means the SDK keeps speaking the version it was built against until\n * the SDK explicitly bumps. Stripe pattern (`Stripe-Version`).\n *\n * Bump this in lockstep with backend version releases. Document the\n * deprecation policy in CHANGELOG.\n */\nexport const CROSSDECK_API_VERSION = \"2025-01-01\";\n\n/** Default GET retry attempts. Configurable via `httpRetries.maxAttempts`. */\nconst DEFAULT_GET_RETRY_ATTEMPTS = 3;\n\n/** Default statuses considered retryable on GET. 408 + 5xx (except 501 Not Implemented). */\nconst DEFAULT_RETRYABLE_STATUSES = new Set([408, 500, 502, 503, 504]);\n\nexport interface HttpRetriesConfig {\n /** Max attempts INCLUSIVE of the first call. Default 3 (1 initial + 2 retries). 1 disables retries. */\n maxAttempts?: number;\n /** Statuses considered retryable. Default: 408, 500, 502, 503, 504. */\n retryableStatuses?: number[];\n}\n\nexport interface HttpClientConfig {\n secretKey: string;\n baseUrl: string;\n sdkVersion: string;\n timeoutMs?: number;\n /**\n * Override the runtime token in the `User-Agent` header. Default\n * detects `node/<process.versions.node>` automatically.\n */\n runtimeToken?: string;\n /**\n * Retry config for idempotent GET requests. Default: 3 attempts\n * with exponential backoff + full jitter, retrying on 408 + 5xx\n * (except 501) and on network failures. Set `maxAttempts: 1` to\n * disable retries.\n */\n httpRetries?: HttpRetriesConfig;\n /**\n * `testMode: true` short-circuits every request to a synthetic\n * success response with a benign shape. No network goes out. For\n * caller test suites that don't want to mock `globalThis.fetch`.\n * Forwarded from `CrossdeckServerOptions.testMode`.\n */\n testMode?: boolean;\n /**\n * Optional inspection hooks. Fire BEFORE/AFTER every request — used\n * for debugging, audit logging, and custom telemetry. Both must\n * return synchronously and must NOT throw (errors are swallowed,\n * the request continues).\n */\n onRequest?: (info: HttpRequestInfo) => void;\n onResponse?: (info: HttpResponseInfo) => void;\n}\n\nexport interface HttpRequestInfo {\n method: \"GET\" | \"POST\";\n url: string;\n headers: Record<string, string>;\n /** The serialised body string, when one was set. */\n bodyPreview?: string;\n /** Attempt number, starting at 1. Useful for distinguishing retries. */\n attempt: number;\n}\n\nexport interface HttpResponseInfo {\n method: \"GET\" | \"POST\";\n url: string;\n status: number;\n durationMs: number;\n attempt: number;\n /** True if the request was a synthetic test-mode response. */\n testMode: boolean;\n}\n\nexport interface HttpRequestOptions {\n body?: unknown;\n query?: Record<string, string | undefined>;\n timeoutMs?: number;\n idempotencyKey?: string;\n /**\n * Caller-supplied AbortSignal. When aborted, the in-flight `fetch`\n * is cancelled and the call throws `CrossdeckError({ code:\n * \"request_aborted\" })`. Compose with the per-request timeout\n * (whichever fires first wins).\n */\n signal?: AbortSignal;\n /** Override the HTTP-retry policy for this single call. */\n retries?: HttpRetriesConfig;\n}\n\nexport class HttpClient {\n private readonly userAgent: string;\n\n constructor(private readonly config: HttpClientConfig) {\n this.userAgent = buildUserAgent(config.sdkVersion, config.runtimeToken);\n }\n\n async request<T>(\n method: \"GET\" | \"POST\",\n path: string,\n options: HttpRequestOptions = {},\n ): Promise<T> {\n const url = this.buildUrl(path, options.query);\n\n // testMode short-circuits ALL requests with a synthetic response —\n // caller test suites get a valid-shaped object back without\n // mocking `globalThis.fetch`. Honours `onRequest` / `onResponse`\n // hooks so audit pipelines see the synthetic traffic.\n if (this.config.testMode === true) {\n return this.synthesizeTestModeResponse<T>(method, path, url, options);\n }\n\n const headers = this.buildHeaders(options);\n const bodyInit = this.buildBody(headers, options.body);\n\n // Retry policy applies only to idempotent GET requests. POST\n // retries are handled by the EventQueue with batch-level\n // Idempotency-Key reuse.\n const retryCfg = options.retries ?? this.config.httpRetries ?? {};\n const maxAttempts =\n method === \"GET\" ? (retryCfg.maxAttempts ?? DEFAULT_GET_RETRY_ATTEMPTS) : 1;\n const retryableStatuses = retryCfg.retryableStatuses\n ? new Set(retryCfg.retryableStatuses)\n : DEFAULT_RETRYABLE_STATUSES;\n\n let lastError: CrossdeckError | null = null;\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n const reqInfo: HttpRequestInfo = {\n method,\n url,\n headers,\n bodyPreview: typeof bodyInit === \"string\" ? bodyInit : undefined,\n attempt,\n };\n try {\n this.config.onRequest?.(reqInfo);\n } catch {\n // hooks must never break the request pipeline\n }\n\n const start = Date.now();\n let response: Response | null = null;\n let networkError: unknown = null;\n try {\n response = await this.dispatch(url, method, headers, bodyInit, options);\n } catch (err) {\n networkError = err;\n }\n\n const durationMs = Date.now() - start;\n\n if (response) {\n try {\n this.config.onResponse?.({\n method,\n url,\n status: response.status,\n durationMs,\n attempt,\n testMode: false,\n });\n } catch {\n // hooks must never break the response pipeline\n }\n }\n\n // Network error path — retry on GET if attempts remain.\n if (networkError !== null) {\n lastError = this.translateNetworkError(networkError, path, options);\n if (method === \"GET\" && attempt < maxAttempts) {\n await sleepWithJitter(attempt);\n continue;\n }\n throw lastError;\n }\n\n // Non-OK response — retry on GET for the retryable statuses,\n // else throw the typed subclass.\n if (response && !response.ok) {\n const err = await crossdeckErrorFromResponse(response);\n if (\n method === \"GET\" &&\n retryableStatuses.has(response.status) &&\n attempt < maxAttempts\n ) {\n lastError = err;\n await sleepForRetry(err, attempt);\n continue;\n }\n throw err;\n }\n\n // 2xx — return the body.\n if (response!.status === 204) return undefined as T;\n try {\n return (await response!.json()) as T;\n } catch {\n throw makeCrossdeckError({\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 // Unreachable in practice — the loop either returns or throws —\n // but TypeScript needs an exit. Rethrow the last seen error.\n throw lastError ?? makeCrossdeckError({\n type: \"internal_error\",\n code: \"retry_exhausted\",\n message: `GET ${path} exhausted ${maxAttempts} attempts.`,\n });\n }\n\n /**\n * Issue a single fetch invocation. Composes the per-request timeout\n * with the caller-supplied AbortSignal — whichever fires first wins.\n */\n private async dispatch(\n url: string,\n method: \"GET\" | \"POST\",\n headers: Record<string, string>,\n bodyInit: RequestInit[\"body\"] | undefined,\n options: HttpRequestOptions,\n ): Promise<Response> {\n const effectiveTimeout = options.timeoutMs ?? this.config.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const supportsAbort = typeof AbortController !== \"undefined\";\n const controller = supportsAbort && effectiveTimeout > 0 ? new AbortController() : null;\n\n // Chain the caller's AbortSignal into ours so an external abort\n // propagates to the in-flight fetch.\n let externalAbortHandler: (() => void) | null = null;\n if (controller && options.signal) {\n if (options.signal.aborted) {\n controller.abort();\n } else {\n externalAbortHandler = (): void => controller.abort();\n options.signal.addEventListener(\"abort\", externalAbortHandler, { once: true });\n }\n }\n\n let timeoutHandle: ReturnType<typeof setTimeout> | null = null;\n if (controller && effectiveTimeout > 0) {\n timeoutHandle = setTimeout(() => controller.abort(), effectiveTimeout);\n }\n\n try {\n return await fetch(url, {\n method,\n headers,\n body: bodyInit,\n signal: controller?.signal ?? options.signal,\n });\n } finally {\n if (timeoutHandle !== null) clearTimeout(timeoutHandle);\n if (externalAbortHandler && options.signal) {\n try {\n options.signal.removeEventListener(\"abort\", externalAbortHandler);\n } catch {\n // ignore — signal may not support removeEventListener\n }\n }\n }\n }\n\n /** Build the request headers. Same across attempts so caches can dedupe. */\n private buildHeaders(options: HttpRequestOptions): Record<string, string> {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.config.secretKey}`,\n \"Crossdeck-Sdk-Version\": `${SDK_NAME}@${this.config.sdkVersion}`,\n \"Crossdeck-Api-Version\": CROSSDECK_API_VERSION,\n \"User-Agent\": this.userAgent,\n Accept: \"application/json\",\n };\n if (options.idempotencyKey) headers[\"Idempotency-Key\"] = options.idempotencyKey;\n if (options.body !== undefined) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n return headers;\n }\n\n private buildBody(\n headers: Record<string, string>,\n body: unknown,\n ): RequestInit[\"body\"] | undefined {\n if (body === undefined) return undefined;\n void headers; // headers already include Content-Type from buildHeaders\n return serializeRequestBody(body);\n }\n\n /** Translate a thrown fetch error or abort into a typed `CrossdeckError`. */\n private translateNetworkError(\n err: unknown,\n path: string,\n options: HttpRequestOptions,\n ): CrossdeckError {\n const callerAborted =\n options.signal?.aborted === true ||\n (err instanceof Error &&\n (err.name === \"AbortError\" || /aborted/i.test(err.message)));\n const callerInitiated = options.signal?.aborted === true;\n return makeCrossdeckError({\n type: \"network_error\",\n code: callerAborted\n ? callerInitiated\n ? \"request_aborted\"\n : \"request_timeout\"\n : \"fetch_failed\",\n message: callerAborted\n ? callerInitiated\n ? `Request to ${path} aborted by caller AbortSignal.`\n : `Request to ${path} aborted after ${options.timeoutMs ?? this.config.timeoutMs ?? DEFAULT_TIMEOUT_MS}ms`\n : err instanceof Error\n ? err.message\n : \"fetch failed\",\n });\n }\n\n /** Synthesise a benign success-shaped response for `testMode: true`. */\n private synthesizeTestModeResponse<T>(\n method: \"GET\" | \"POST\",\n path: string,\n url: string,\n options: HttpRequestOptions,\n ): T {\n try {\n this.config.onRequest?.({\n method,\n url,\n headers: this.buildHeaders(options),\n bodyPreview: options.body !== undefined ? safeStringify(options.body) : undefined,\n attempt: 1,\n });\n } catch {\n // ignore\n }\n const synth = synthForPath<T>(path);\n try {\n this.config.onResponse?.({\n method,\n url,\n status: 200,\n durationMs: 0,\n attempt: 1,\n testMode: true,\n });\n } catch {\n // ignore\n }\n return synth;\n }\n\n private buildUrl(path: string, query?: Record<string, string | undefined>): string {\n const base = this.config.baseUrl.replace(/\\/+$/, \"\");\n const cleanPath = path.startsWith(\"/\") ? path : `/${path}`;\n let url = base + cleanPath;\n if (query) {\n const params = new URLSearchParams();\n for (const [k, v] of Object.entries(query)) {\n if (typeof v === \"string\" && v.length > 0) params.append(k, v);\n }\n const qs = params.toString();\n if (qs) url += (url.includes(\"?\") ? \"&\" : \"?\") + qs;\n }\n return url;\n }\n}\n\n/**\n * Build the `User-Agent` header. HTTP best practice — surfaces the\n * SDK + runtime + OS to the backend's request logs without parsing\n * the bespoke `Crossdeck-Sdk-Version` (which only carries the SDK\n * name/version).\n *\n * @cross-deck/node/1.2.0 node/20.10.0 darwin\n */\nfunction buildUserAgent(sdkVersion: string, override?: string): string {\n if (override) return `${SDK_NAME}/${sdkVersion} ${override}`;\n const nodeVersion = typeof process !== \"undefined\" && process.versions ? process.versions.node : \"unknown\";\n const osPlatform = typeof process !== \"undefined\" && process.platform ? process.platform : \"unknown\";\n return `${SDK_NAME}/${sdkVersion} node/${nodeVersion} ${osPlatform}`;\n}\n\n/** Exponential backoff with full jitter for the GET retry path. */\nasync function sleepWithJitter(attempt: number): Promise<void> {\n // 50ms * 2^(attempt-1), capped at 2s, jittered to [0, ceiling].\n const ceiling = Math.min(2000, 50 * Math.pow(2, attempt - 1));\n const delay = Math.round(ceiling * Math.random());\n await new Promise<void>((resolve) => {\n const t = setTimeout(resolve, delay);\n if (typeof t.unref === \"function\") {\n try {\n t.unref();\n } catch {\n // ignore\n }\n }\n });\n}\n\n/**\n * Sleep for the retry delay implied by a `CrossdeckError` — honours\n * server `Retry-After` when present, else falls back to jittered\n * exponential backoff.\n */\nasync function sleepForRetry(err: CrossdeckError, attempt: number): Promise<void> {\n if (err.retryAfterMs !== undefined && err.retryAfterMs > 0) {\n await new Promise<void>((resolve) => {\n const t = setTimeout(resolve, err.retryAfterMs);\n if (typeof t.unref === \"function\") {\n try {\n t.unref();\n } catch {\n // ignore\n }\n }\n });\n return;\n }\n await sleepWithJitter(attempt);\n}\n\n/**\n * Synth response for `testMode: true`. Path-aware so each public\n * method's expected shape comes back. Keep this small + benign —\n * the goal is \"caller's code path runs without surprises\", not \"the\n * SDK behaves identically to production.\"\n */\nfunction synthForPath<T>(path: string): T {\n if (path.startsWith(\"/sdk/heartbeat\")) {\n return {\n object: \"heartbeat\",\n ok: true,\n projectId: \"proj_test_mode\",\n appId: \"app_test_mode\",\n platform: \"node\",\n env: \"sandbox\",\n serverTime: Date.now(),\n } as unknown as T;\n }\n if (path.startsWith(\"/identity/alias\")) {\n return {\n object: \"alias_result\",\n crossdeckCustomerId: \"cdcust_test_mode\",\n linked: [],\n mergePending: false,\n env: \"sandbox\",\n } as unknown as T;\n }\n if (path.startsWith(\"/identity/forget\")) {\n return {\n object: \"forgot\",\n crossdeckCustomerId: null,\n queuedAt: Date.now(),\n env: \"sandbox\",\n } as unknown as T;\n }\n if (path.includes(\"/entitlements\")) {\n return {\n object: \"list\",\n data: [],\n crossdeckCustomerId: \"cdcust_test_mode\",\n env: \"sandbox\",\n } as unknown as T;\n }\n if (path.startsWith(\"/events\")) {\n return {\n object: \"list\",\n received: 0,\n env: \"sandbox\",\n } as unknown as T;\n }\n if (path.includes(\"/purchases/sync\")) {\n return {\n object: \"purchase_result\",\n crossdeckCustomerId: \"cdcust_test_mode\",\n env: \"sandbox\",\n entitlements: [],\n } as unknown as T;\n }\n if (path.includes(\"/grant\") || path.includes(\"/revoke\")) {\n return {\n object: \"entitlement_mutation\",\n action: path.includes(\"/grant\") ? \"grant\" : \"revoke\",\n crossdeckCustomerId: \"cdcust_test_mode\",\n entitlement: {\n object: \"entitlement\",\n key: \"pro\",\n isActive: path.includes(\"/grant\"),\n validUntil: null,\n source: { rail: \"manual\", productId: \"manual\", subscriptionId: \"manual:test_mode\" },\n updatedAt: Date.now(),\n },\n env: \"sandbox\",\n } as unknown as T;\n }\n if (path.startsWith(\"/server/audit/\")) {\n return {\n object: \"audit_entry\",\n data: {\n eventId: \"audit_test_mode\",\n rail: \"manual\",\n env: \"sandbox\",\n eventType: \"test_mode\",\n projectId: \"proj_test_mode\",\n decision: \"applied\",\n signatureVerified: true,\n reconciledWithProvider: false,\n rawEventReceivedAt: Date.now(),\n processedAt: Date.now(),\n },\n } as unknown as T;\n }\n return {} as T;\n}\n\nfunction safeStringify(v: unknown): string {\n try {\n return JSON.stringify(v) ?? \"\";\n } catch {\n return \"[unserialisable]\";\n }\n}\n\nfunction serializeRequestBody(body: unknown): string {\n try {\n const direct = JSON.stringify(body);\n if (typeof direct === \"string\") return direct;\n } catch {\n // Fall through to the sanitising backstop.\n }\n\n try {\n const wrapped = validateEventProperties(\n { __body: body },\n {\n maxStringLength: 1_000_000,\n maxBatchPropertyBytes: 10 * 1024 * 1024,\n maxDepth: 20,\n },\n ).properties.__body;\n const serialized = JSON.stringify(wrapped);\n if (typeof serialized === \"string\") return serialized;\n } catch {\n // Surface a stable SDK error instead of leaking the runtime's raw\n // JSON.stringify message.\n }\n\n throw new CrossdeckError({\n type: \"invalid_request_error\",\n code: \"serialization_failed\",\n message: \"Request body could not be serialized.\",\n });\n}\n","/**\n * Retry policy for the event-queue flush.\n *\n * After a failed flush, the queue must wait some time before trying\n * again — otherwise a flapping backend causes a hot loop, and a 429\n * \"slow down\" goes ignored.\n *\n * Policy (parity with `@cross-deck/web/src/retry-policy.ts`):\n * - Exponential backoff: `base * 2^attempts`, capped at `maxMs`.\n * - Full jitter: result is multiplied by `Math.random()` so 100 SDK\n * instances retrying the same downed endpoint don't all hammer at\n * the same instant. Spread the storm.\n * - 429 / 503 `Retry-After`: ALWAYS honour the server-supplied delay\n * when it's larger than our computed backoff. The server knows its\n * own capacity better than we do; ignoring it is what gets your IP\n * blocked.\n * - Reset on success.\n *\n * The policy is a pure object — no state mutation outside `attempts`,\n * no timers. The EventQueue owns the timer; the policy owns the math.\n *\n * Default values match Stripe-style retry windows:\n * - baseMs: 1000 (first retry ~1s out, jittered to 0-1s)\n * - maxMs: 60000 (never wait longer than 60s)\n * - factor: 2 (1s, 2s, 4s, 8s, 16s, 32s, 60s, 60s, …)\n *\n * After `failuresBeforeWarn` (default 8) without a success, the caller\n * is expected to surface that as a `lastError` for the developer to see\n * in diagnostics. We never stop retrying — events matter and a transient\n * outage can take hours — but we report it clearly so the dev knows\n * their data is queued, not lost.\n */\n\nexport interface RetryPolicyOptions {\n baseMs?: number;\n maxMs?: number;\n factor?: number;\n /** Number of consecutive failures before flagging diagnostics. Default 8. */\n failuresBeforeWarn?: number;\n}\n\nconst DEFAULT_BASE = 1000;\nconst DEFAULT_MAX = 60_000;\nconst DEFAULT_FACTOR = 2;\nconst DEFAULT_WARN = 8;\n\n/**\n * Compute the next retry delay (ms) given the consecutive-failure count\n * and an optional server-supplied `Retry-After` (ms).\n *\n * computeNextDelay(0, undefined) → ~500ms (jittered 0-1000)\n * computeNextDelay(3, undefined) → ~4s (jittered 0-8000)\n * computeNextDelay(0, 30_000) → 30s (server wins)\n * computeNextDelay(8, undefined) → 60s (capped)\n *\n * Pure function — exported for unit testing. Real callers go through\n * `RetryPolicy.nextDelay` so option defaults stay co-located.\n */\nexport function computeNextDelay(\n attempts: number,\n retryAfterMs: number | undefined,\n options: RetryPolicyOptions = {},\n random: () => number = Math.random,\n): number {\n const base = options.baseMs ?? DEFAULT_BASE;\n const max = options.maxMs ?? DEFAULT_MAX;\n const factor = options.factor ?? DEFAULT_FACTOR;\n\n // Cap attempts so 2^attempts doesn't overflow into Infinity.\n const safeAttempts = Math.min(attempts, 30);\n const ceiling = Math.min(max, base * Math.pow(factor, safeAttempts));\n // Full jitter: random across [0, ceiling]. Caller can substitute a\n // deterministic RNG for testing.\n const jittered = ceiling * random();\n // Honour server's Retry-After when bigger than our window — the\n // server's the authority on its own pressure.\n if (retryAfterMs !== undefined && retryAfterMs > jittered) {\n return Math.min(max, retryAfterMs);\n }\n return Math.max(0, Math.round(jittered));\n}\n\nexport class RetryPolicy {\n private attempts = 0;\n constructor(private readonly options: RetryPolicyOptions = {}) {}\n\n /** How many consecutive failures since the last success. */\n get consecutiveFailures(): number {\n return this.attempts;\n }\n\n /** Whether we've crossed the failuresBeforeWarn threshold. */\n get isWarning(): boolean {\n return this.attempts >= (this.options.failuresBeforeWarn ?? DEFAULT_WARN);\n }\n\n /** Schedule-time delay for the NEXT retry. Increments the counter. */\n nextDelay(retryAfterMs?: number, random: () => number = Math.random): number {\n const delay = computeNextDelay(this.attempts, retryAfterMs, this.options, random);\n this.attempts += 1;\n return delay;\n }\n\n /** Mark a successful flush — reset the counter. */\n recordSuccess(): void {\n this.attempts = 0;\n }\n}\n","/**\n * Cryptographically-random short IDs used across the SDK — event IDs,\n * batch IDs, internal correlation. Same alphabet (`0-9a-z`) and shape\n * (`<prefix>_<base32-ts><rand>`) as `@cross-deck/web`'s\n * `identity.ts:randomChars` so events emitted by the Node SDK look\n * identical to web SDK events in the warehouse.\n *\n * Node 18+ exposes `globalThis.crypto.getRandomValues` natively. If the\n * runtime is older (which the package.json `engines` field forbids,\n * but a sloppy host might run anyway), we fall back to `Math.random`.\n * The fallback is safe here because ID entropy doesn't need to resist\n * offline brute force — it needs to be unique-with-overwhelming-probability\n * across one process lifetime.\n */\n\nconst ALPHABET = \"0123456789abcdefghijklmnopqrstuvwxyz\";\n\nexport function randomChars(count: number): string {\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/**\n * Mint a prefixed ID like `evt_<base32-ts><rand>` / `batch_<base32-ts><rand>`.\n * Sortable (timestamp-prefixed) and log-friendly. Stripe / Segment use the\n * same shape.\n */\nexport function mintId(prefix: string, randLen = 10): string {\n return `${prefix}_${Date.now().toString(36)}${randomChars(randLen)}`;\n}\n","/**\n * Local event queue + batched flush.\n *\n * Why a queue: `track()` is called from hot paths (request handlers,\n * Lambda invocations) and shouldn't block the caller on a network\n * round-trip. Events go into a local buffer, flushed in bursts.\n *\n * Flush triggers:\n * - Buffer reaches `batchSize` (default 20) → flush immediately.\n * - `intervalMs` of inactivity (default 1500ms) → flush idle batch.\n * - `flush()` called explicitly (e.g. from `flush-on-exit.ts` before\n * a Cloud Function exits, or before a Lambda handler returns).\n *\n * Bank-grade hardening (parity with `@cross-deck/web/src/event-queue.ts`):\n * - Exponential backoff with full jitter on flush failures. Honours\n * server `Retry-After` (parsed onto `CrossdeckError` by the HTTP\n * layer). Replaces the prior policy of \"retry on the next idle\n * window\" which hot-looped against a flapping endpoint.\n * - Per-batch `Idempotency-Key`. The SAME key is reused across\n * retries of the SAME batch so the server can short-circuit\n * duplicate work without inspecting bodies. The backend ALSO\n * dedupes individual events via ClickHouse ReplacingMergeTree on\n * `eventId`, so this is belt-and-suspenders.\n *\n * Node differences from web:\n * - No `keepalive` option (Node has no page unload concept).\n * - No `persistentStore` (no localStorage; Node deployments are\n * stateless — Lambda freezes between invocations, Cloud Functions\n * tear down containers). On-exit drainage is handled by the\n * separate `flush-on-exit.ts` module that calls `flush()` from\n * `process.on('beforeExit')` + SIGTERM + SIGINT handlers.\n * - Default scheduler uses `setTimeout(...).unref()` — already\n * Node-friendly, so a pending flush doesn't block the process\n * from exiting between invocations.\n *\n * On a permanent network outage we keep retrying with bounded backoff;\n * we never drop events because of network failures alone. The only\n * drop path is the hard buffer cap (1000 events): once exceeded we\n * evict the OLDEST events and increment `dropped` so the developer\n * can see the loss in `diagnostics()`.\n */\n\nimport type { HttpClient } from \"./http\";\nimport type { EventProperties, IngestResponse } from \"./types\";\nimport type { CrossdeckError } from \"./errors\";\nimport { RetryPolicy, type RetryPolicyOptions } from \"./retry-policy\";\nimport { mintId } from \"./_rand\";\n\nconst HARD_BUFFER_CAP = 1000;\n\nexport interface QueuedEvent {\n eventId: string;\n name: string;\n timestamp: number;\n properties: EventProperties;\n // identity hint — at least one of these is always set per-event for\n // Node (the caller supplies them; the SDK doesn't mint anonymousId).\n developerUserId?: string;\n anonymousId?: string;\n crossdeckCustomerId?: string;\n /** Optional Sentry-style level for error.* events. */\n level?: \"error\" | \"warning\" | \"info\";\n /** Optional Sentry-style tag bag for error.* events. */\n tags?: Record<string, string>;\n /** Optional categoryTags column projection on the backend. */\n categoryTags?: string[];\n}\n\nexport interface BatchEnvelope {\n /** Optional appId. The server is authoritative via the API key; this is metadata only. */\n appId?: string;\n /**\n * Declared environment (\"production\" / \"sandbox\"). The backend\n * cross-checks this against the API-key-derived env and rejects\n * mismatches loudly (env_mismatch) — catches the \"live key, env:\n * sandbox in caller code\" config drift before it pollutes the\n * wrong dashboard. Web has always sent this; node now matches so\n * defence-in-depth is symmetric across SDKs (cross-SDK parity P1\n * audit finding).\n */\n environment?: \"production\" | \"sandbox\";\n sdk: { name: string; version: string };\n}\n\nexport interface EventQueueConfig {\n http: HttpClient;\n batchSize: number;\n intervalMs: number;\n /**\n * Returns the batch envelope to attach to each POST. Function (not\n * a value) so a future config swap can update it without\n * re-instantiating the queue.\n */\n envelope: () => BatchEnvelope;\n /** Schedule a function to run after `ms` ms. Default: setTimeout with .unref(). Override for tests. */\n scheduler?: (fn: () => void, ms: number) => () => void;\n /** Called when the SDK drops events because the buffer is full. */\n onDrop?: (dropped: number) => void;\n /** Called once after the first successful flush — drives the §16 \"First event sent\" debug signal. */\n onFirstFlushSuccess?: () => void;\n /** Retry policy overrides for failed flushes. */\n retry?: RetryPolicyOptions;\n /**\n * Called whenever an item is added to the buffer or removed by a\n * successful flush. Exposed so the host SDK can surface live queue\n * stats via `diagnostics()` without polling.\n */\n onBufferChange?: (size: number) => void;\n /**\n * Fired (async, never throws) whenever the retry policy schedules\n * the next flush attempt. Used by the SDK debug logger to surface\n * \"flush failed, retrying in Xms\" signals.\n */\n onRetryScheduled?: (info: {\n delayMs: number;\n consecutiveFailures: number;\n retryAfterMs?: number;\n lastError: string;\n }) => void;\n /**\n * Fired when the queue DROPS a batch because the server returned a\n * permanent 4xx (anything except 408 Request Timeout / 429 Too Many\n * Requests). The host SDK should surface this loudly — pre-fix the\n * queue retried 4xx errors forever with the same Idempotency-Key,\n * silently growing the backlog while the customer thought events\n * were landing. Common causes:\n * - 401: secret key revoked / rotated\n * - 403: lacking permission for the project\n * - 400/422: malformed batch (schema mismatch, oversized event)\n * - 404: endpoint doesn't exist (typo'd baseUrl)\n */\n onPermanentFailure?: (info: {\n status: number;\n droppedCount: number;\n lastError: string;\n }) => void;\n}\n\nexport interface EventQueueStats {\n buffered: number;\n dropped: number;\n inFlight: number;\n lastFlushAt: number;\n lastError: string | null;\n /** Consecutive flush failures since the last success. */\n consecutiveFailures: number;\n /** Set when the next flush is scheduled by the retry policy. */\n nextRetryAt: number | null;\n}\n\nexport class EventQueue {\n private buffer: QueuedEvent[] = [];\n private dropped = 0;\n private inFlight = 0;\n private lastFlushAt = 0;\n private lastError: string | null = null;\n private cancelTimer: (() => void) | null = null;\n private firstFlushFired = false;\n private nextRetryAt: number | null = null;\n private readonly retry: RetryPolicy;\n /**\n * Stable Idempotency-Key for the current in-flight batch. Minted\n * lazily inside `flush()` when no key is pending. Reused across\n * retries of the same logical batch so the backend's idempotency\n * layer can short-circuit duplicates (Stripe pattern). Reset to\n * `null` after a successful flush.\n */\n private pendingBatchId: string | null = null;\n /**\n * In-flight events that have been spliced from the buffer for the\n * current batch but haven't yet been confirmed (success or final\n * failure). On a retry-driven flush, we re-use this batch alongside\n * `pendingBatchId` instead of re-splicing. New events that arrive\n * during in-flight are buffered separately and join the next batch\n * AFTER this one settles.\n */\n private pendingBatch: QueuedEvent[] | null = null;\n\n constructor(private readonly cfg: EventQueueConfig) {\n this.retry = new RetryPolicy(cfg.retry ?? {});\n }\n\n enqueue(event: QueuedEvent): void {\n this.buffer.push(event);\n if (this.buffer.length > HARD_BUFFER_CAP) {\n const overflow = this.buffer.length - HARD_BUFFER_CAP;\n this.buffer.splice(0, overflow);\n this.dropped += overflow;\n this.cfg.onDrop?.(overflow);\n }\n this.cfg.onBufferChange?.(this.buffer.length);\n 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 * `pendingBatch` slot for the next scheduled retry — the SAME batch\n * with the SAME `Idempotency-Key` is re-sent (Stripe pattern).\n *\n * The `pendingBatch` slot guarantees retry semantics:\n * - First call: splices buffer → pendingBatch + mints batchId.\n * - On 5xx / network failure: pendingBatch stays; scheduler fires\n * `flush()` again later, which re-uses pendingBatch + the same\n * batchId.\n * - On success: pendingBatch + batchId cleared; subsequent calls\n * splice the buffer again with a fresh batchId.\n *\n * New events that arrive during an in-flight batch land in `buffer`\n * (separate from `pendingBatch`) and ship on the next batch after\n * this one settles. Strict ordering preserved.\n */\n async flush(): Promise<IngestResponse | null> {\n // Resume an in-flight batch retry path: if we already have a\n // pending batch (a prior flush failed and we're being re-invoked\n // by the retry timer or the caller), re-attempt with the SAME\n // batchId. This is the Idempotency-Key reuse contract.\n let batch: QueuedEvent[];\n let batchId: string;\n if (this.pendingBatch !== null && this.pendingBatchId !== null) {\n batch = this.pendingBatch;\n batchId = this.pendingBatchId;\n } else {\n if (this.buffer.length === 0) return null;\n batch = this.buffer.splice(0);\n batchId = mintId(\"batch\");\n this.pendingBatch = batch;\n this.pendingBatchId = batchId;\n this.inFlight += batch.length;\n this.cfg.onBufferChange?.(this.buffer.length);\n }\n this.cancelTimerIfSet();\n this.nextRetryAt = null;\n\n try {\n const env = this.cfg.envelope();\n const body: Record<string, unknown> = {\n events: batch,\n sdk: env.sdk,\n };\n if (env.appId) body.appId = env.appId;\n // environment ships when the host SDK supplied one (server SDKs\n // know their env from init() options). Backend cross-checks it\n // against the API-key-derived env and rejects mismatches loudly\n // (env_mismatch) — defence-in-depth so a \"live key, env: sandbox\"\n // misconfig doesn't pollute the wrong dashboard. Parity with web.\n if (env.environment) body.environment = env.environment;\n const result = await this.cfg.http.request<IngestResponse>(\"POST\", \"/events\", {\n body,\n idempotencyKey: batchId,\n });\n this.lastFlushAt = Date.now();\n this.lastError = null;\n this.inFlight -= batch.length;\n this.pendingBatch = null;\n this.pendingBatchId = null;\n this.retry.recordSuccess();\n if (!this.firstFlushFired) {\n this.firstFlushFired = true;\n this.cfg.onFirstFlushSuccess?.();\n }\n return result;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n this.lastError = message;\n\n // Permanent failures (4xx except 408/429) are NOT retryable. The\n // server is telling us our request is malformed (400/422), our\n // key is revoked (401), we lack permission (403), or the endpoint\n // doesn't exist (404). Retrying with the same Idempotency-Key\n // forever just grows the queue silently while the customer thinks\n // events are landing. Drop the batch loudly.\n if (isPermanent4xx(err)) {\n const droppedCount = batch.length;\n this.pendingBatch = null;\n this.pendingBatchId = null;\n this.inFlight -= droppedCount;\n this.dropped += droppedCount;\n this.cfg.onDrop?.(droppedCount);\n this.cfg.onPermanentFailure?.({\n status: (err as { status?: number }).status ?? 0,\n droppedCount,\n lastError: message,\n });\n return null;\n }\n\n // Retryable failure (5xx / network / 408 / 429). Keep\n // `pendingBatch` + `pendingBatchId` set — the next\n // scheduler-driven (or caller-driven) flush will retry with the\n // SAME key. This is the Idempotency-Key reuse contract.\n const retryAfterMs = extractRetryAfterMs(err);\n const delay = this.retry.nextDelay(retryAfterMs);\n this.scheduleRetry(delay);\n this.cfg.onRetryScheduled?.({\n delayMs: delay,\n consecutiveFailures: this.retry.consecutiveFailures,\n retryAfterMs,\n lastError: message,\n });\n return null;\n }\n }\n\n /** Cancel any pending timer and clear in-memory state. */\n reset(): void {\n this.cancelTimerIfSet();\n this.nextRetryAt = null;\n this.buffer = [];\n this.pendingBatch = null;\n this.pendingBatchId = null;\n this.dropped = 0;\n this.inFlight = 0;\n this.lastError = null;\n this.retry.recordSuccess();\n this.cfg.onBufferChange?.(0);\n // Note: we deliberately do NOT reset firstFlushFired — the\n // \"First event sent\" signal is a one-time per-process lifetime\n // moment, not per-identity.\n }\n\n getStats(): EventQueueStats {\n return {\n // `buffered` counts events waiting for their FIRST flush. The\n // in-flight pendingBatch (retrying) is tracked separately via\n // `inFlight` — surfacing both lets diagnostics show \"we have\n // events stuck retrying\" distinct from \"new events arriving\".\n buffered: this.buffer.length,\n dropped: this.dropped,\n inFlight: this.inFlight,\n lastFlushAt: this.lastFlushAt,\n lastError: this.lastError,\n consecutiveFailures: this.retry.consecutiveFailures,\n nextRetryAt: this.nextRetryAt,\n };\n }\n\n /**\n * The Idempotency-Key of the in-flight pending batch (if any).\n * Exposed for testing the Stripe-style reuse contract. Production\n * callers don't need this.\n */\n get pendingIdempotencyKey(): string | null {\n return this.pendingBatchId;\n }\n\n // ---------- internal scheduling ----------\n\n private scheduleIdleFlush(): void {\n this.cancelTimerIfSet();\n const sched = this.cfg.scheduler ?? defaultScheduler;\n this.cancelTimer = sched(() => {\n void this.flush();\n }, this.cfg.intervalMs);\n }\n\n private scheduleRetry(delayMs: number): void {\n this.cancelTimerIfSet();\n this.nextRetryAt = Date.now() + delayMs;\n const sched = this.cfg.scheduler ?? defaultScheduler;\n this.cancelTimer = sched(() => {\n void this.flush();\n }, delayMs);\n }\n\n private cancelTimerIfSet(): void {\n if (this.cancelTimer) {\n this.cancelTimer();\n this.cancelTimer = null;\n }\n }\n}\n\nfunction extractRetryAfterMs(err: unknown): number | undefined {\n if (err && typeof err === \"object\" && \"retryAfterMs\" in err) {\n const v = (err as CrossdeckError).retryAfterMs;\n return typeof v === \"number\" && Number.isFinite(v) && v >= 0 ? v : undefined;\n }\n return undefined;\n}\n\n/**\n * True when the error represents a permanent 4xx response that\n * SHOULDN'T be retried. Excludes 408 Request Timeout and 429 Too Many\n * Requests — both indicate transient state where the SAME request\n * (with the SAME Idempotency-Key) can succeed on a retry.\n *\n * Anything that isn't a CrossdeckError-shaped object with a numeric\n * status field returns false (network errors / fetch failures fall\n * here — those ARE retryable). Conservative default: only flag as\n * permanent when we have strong evidence from the server.\n */\nfunction isPermanent4xx(err: unknown): boolean {\n if (!err || typeof err !== \"object\") return false;\n const status = (err as { status?: unknown }).status;\n if (typeof status !== \"number\" || !Number.isFinite(status)) return false;\n if (status < 400 || status >= 500) return false;\n if (status === 408 || status === 429) return false;\n return true;\n}\n\n/**\n * Default scheduler — `setTimeout` with `.unref()` so a pending flush\n * does NOT keep the Node process alive. Critical for short-lived\n * runtimes (Lambda, Cloud Functions) — without `.unref()`, an\n * outstanding retry timer would prevent `process.exit` from firing\n * naturally and the function would hang until the platform's SIGKILL.\n */\nfunction defaultScheduler(fn: () => void, ms: number): () => void {\n const id = setTimeout(fn, ms);\n if (typeof id.unref === \"function\") {\n try {\n id.unref();\n } catch {\n // ignore — unref is best-effort\n }\n }\n return () => clearTimeout(id);\n}\n","/**\n * Breadcrumb ring buffer — context attached to every error report.\n *\n * Sentry / Datadog / Bugsnag all ship the same idea: keep a rolling\n * record of the last N \"things the process did\" (HTTP calls, queued\n * events, custom log lines, function invocations). When an error fires,\n * attach the buffer so the engineer reading the error can see exactly\n * how the process got into the broken state. The single most powerful\n * debugging signal in error monitoring — without breadcrumbs, errors\n * are stack traces with no story.\n *\n * Implementation: a circular buffer with a fixed cap. Old entries are\n * evicted as new ones arrive. The default cap (50) is enough to cover\n * ~5 minutes of typical request activity without ballooning the error\n * payload. Sentry uses 100 by default but the SDK is more aggressive\n * about size since we ship breadcrumbs over the wire with every error,\n * not as a separate batch.\n *\n * Verbatim port of `@cross-deck/web/src/breadcrumbs.ts`. The data\n * structure has zero browser dependencies; same code works in Node.\n *\n * Privacy: breadcrumbs from `track()` calls auto-flow through the same\n * property sanitiser (`event-validation.ts`) before reaching this\n * buffer, so a function/symbol/Error-shape in a tracked property won't\n * crash subsequent error reports.\n */\n\nexport type BreadcrumbCategory =\n | \"navigation\"\n | \"ui.click\"\n | \"ui.input\"\n | \"http\"\n | \"console\"\n | \"custom\"\n | \"info\";\n\nexport type BreadcrumbLevel = \"debug\" | \"info\" | \"warning\" | \"error\";\n\nexport interface Breadcrumb {\n /** epoch ms */\n timestamp: number;\n category: BreadcrumbCategory;\n level?: BreadcrumbLevel;\n /** Short human-readable description. */\n message?: string;\n /** Arbitrary key/value context for the crumb. */\n data?: Record<string, unknown>;\n}\n\nexport class BreadcrumbBuffer {\n private items: Breadcrumb[] = [];\n constructor(private readonly maxSize: number = 50) {}\n\n add(crumb: Breadcrumb): void {\n this.items.push(crumb);\n if (this.items.length > this.maxSize) {\n this.items.shift();\n }\n }\n\n /** Defensive copy — caller can read freely without mutating buffer state. */\n snapshot(): Breadcrumb[] {\n return this.items.slice();\n }\n\n clear(): void {\n this.items = [];\n }\n\n get size(): number {\n return this.items.length;\n }\n}\n","/**\n * Stack-trace parser — normalises V8 / Firefox / Safari stack strings\n * into a common frame shape.\n *\n * Why hand-rolled, not `stack-trace-js` / `error-stack-parser`: those\n * weigh 5–15 KB after minification and we'd be pulling in their full\n * feature matrix just for the parser. The patterns below cover the\n * three shapes any modern runtime emits, totalling ~80 lines.\n *\n * Port of `@cross-deck/web/src/stack-parser.ts`. Two differences:\n * 1. `isInAppFrame` heuristics are Node-aware (`node_modules/`,\n * `node:` core URLs, `internal/` Node internals,\n * `@cross-deck/node` self-skip) instead of browser-aware\n * (extension URLs, CDN hostnames).\n * 2. Path separator handling accepts both `/` (Unix / V8 standard)\n * and `\\` (Windows native paths sometimes leak into `error.stack`\n * on Node-for-Windows deployments).\n *\n * Defensive: never throws. An unparseable line becomes a `raw` frame\n * with just the literal text. Engineers reading errors still get the\n * raw stack as fallback.\n */\n\nexport interface StackFrame {\n /** Function name, or \"?\" if anonymous / unparseable. */\n function: string;\n /** Source file URL the frame ran in. Empty when unknown. */\n filename: string;\n /** 1-indexed line number, or 0 when unknown. */\n lineno: number;\n /** 1-indexed column number, or 0 when unknown. */\n colno: number;\n /**\n * True when the frame is in the app's own code (best-effort:\n * detected by URL not in node_modules/, not a node: core URL, etc.).\n * Powers the dashboard's \"your code vs library code\" view.\n */\n in_app: boolean;\n /** Raw line from the stack string for debugging when parse fails. */\n raw: string;\n}\n\n/**\n * Parse a stack string into an array of frames. Returns an empty\n * array when the input is unparseable — caller should always treat\n * the original `error.stack` as the source of truth for display.\n */\nexport function parseStack(stack: string | undefined | null): StackFrame[] {\n if (!stack || typeof stack !== \"string\") return [];\n const lines = stack.split(\"\\n\");\n const frames: StackFrame[] = [];\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n const frame = parseLine(trimmed);\n if (frame) frames.push(frame);\n }\n return frames;\n}\n\n/**\n * Parse a single stack line. Returns null for header lines like\n * \"TypeError: x is not a function\" (those carry no frame info).\n *\n * Patterns recognised:\n * Chrome / Node V8: \"at functionName (file:line:col)\"\n * Chrome / Node V8: \"at file:line:col\" (anonymous)\n * Firefox / Safari: \"functionName@file:line:col\"\n */\nfunction parseLine(line: string): StackFrame | null {\n // Chrome / Node V8 — with parens\n // Example: at Object.handleRequest (file:///app/server.js:42:18)\n // Example: at Object.handleRequest (/app/server.js:42:18)\n let m = /^at\\s+(.+?)\\s+\\((.+?):(\\d+):(\\d+)\\)$/.exec(line);\n if (m) {\n return buildFrame({\n function: m[1]!,\n filename: m[2]!,\n lineno: parseInt(m[3]!, 10),\n colno: parseInt(m[4]!, 10),\n raw: line,\n });\n }\n\n // Chrome / Node V8 — anonymous, no parens\n // Example: at /app/server.js:42:18\n m = /^at\\s+(.+?):(\\d+):(\\d+)$/.exec(line);\n if (m) {\n return buildFrame({\n function: \"?\",\n filename: m[1]!,\n lineno: parseInt(m[2]!, 10),\n colno: parseInt(m[3]!, 10),\n raw: line,\n });\n }\n\n // Firefox / Safari — also emitted by some Node test runners\n // Example: handleRequest@/app/server.js:42:18\n m = /^(.*?)@(.+?):(\\d+):(\\d+)$/.exec(line);\n if (m) {\n return buildFrame({\n function: m[1]! || \"?\",\n filename: m[2]!,\n lineno: parseInt(m[3]!, 10),\n colno: parseInt(m[4]!, 10),\n raw: line,\n });\n }\n\n // Header line (\"TypeError: foo is not a function\") — return null\n // so caller skips it. Catches anything starting with a *Error word\n // OR any line that has no colon at all (genuine frames always do).\n if (/^\\w*Error/.test(line) || !line.includes(\":\")) {\n return null;\n }\n\n // Unparseable but plausibly a frame — keep it as raw.\n return {\n function: \"?\",\n filename: \"\",\n lineno: 0,\n colno: 0,\n in_app: true,\n raw: line,\n };\n}\n\nfunction buildFrame(input: {\n function: string;\n filename: string;\n lineno: number;\n colno: number;\n raw: string;\n}): StackFrame {\n return {\n function: input.function || \"?\",\n filename: input.filename,\n lineno: Number.isFinite(input.lineno) ? input.lineno : 0,\n colno: Number.isFinite(input.colno) ? input.colno : 0,\n in_app: isInAppFrame(input.filename),\n raw: input.raw,\n };\n}\n\n/**\n * \"Is this frame in the app's own code or a third-party source we\n * should de-emphasise in the UI.\"\n *\n * Node-aware heuristics:\n * 1. SDK self-skip — any frame containing `@cross-deck/node`. Defends\n * against the cycle where the SDK reports on itself.\n * 2. node_modules — library code, almost never the app's bug.\n * 3. `node:` core modules (Node 16+) — `node:fs`, `node:http`, etc.\n * 4. `internal/` — Node-internal V8 frames like\n * `internal/process/task_queues.js`.\n * 5. Empty filename — anonymous frame, let it through as in_app.\n *\n * Path separator handling: V8 always emits `/` on all platforms, but\n * Windows-native paths can leak into `error.stack` via re-thrown\n * errors in Node-for-Windows deployments. We match both.\n */\nfunction isInAppFrame(filename: string): boolean {\n if (!filename) return true;\n // SDK self-skip — must come before node_modules check because the\n // package's own frames live under node_modules/@cross-deck/node\n // when installed, but ALSO want to skip when running from sdks/node/src\n // during dogfood/dev.\n if (/@cross-deck[\\\\/]node/.test(filename)) return false;\n // Library code from any node_modules tree (root or nested).\n if (/[\\\\/]node_modules[\\\\/]/.test(filename)) return false;\n // Node 16+ core module URLs.\n if (/^node:/.test(filename)) return false;\n // Older Node / V8-internal frames.\n if (/^internal[\\\\/]/.test(filename)) return false;\n return true;\n}\n\n/**\n * Fingerprint an error for grouping. SHA-flavoured — we don't need\n * cryptographic strength, we need \"two errors with the same call site\n * produce the same key\". The Crossdeck backend may refine the grouping\n * further once source maps are uploaded.\n *\n * Input: the message + the first ≤3 in-app frames. When no frames\n * are available (non-Error throws of primitives, unhandledRejection\n * of a value with no stack), the optional `location` fallback\n * contributes filename/lineno/errorType so otherwise-identical\n * generic messages from different call sites stay separate. Without\n * the fallback they all collapse into one bucket and the dashboard\n * can't distinguish them.\n *\n * Output: an 8-char hex string usable as a doc id segment.\n */\nexport function fingerprintError(\n message: string,\n frames: StackFrame[],\n location?: {\n filename?: string | null;\n lineno?: number | null;\n errorType?: string | null;\n } | null,\n): string {\n const inAppFrames = frames.filter((f) => f.in_app).slice(0, 3);\n const parts = [\n (message || \"\").slice(0, 200),\n ...inAppFrames.map((f) => `${f.function}@${f.filename}:${f.lineno}`),\n ];\n if (inAppFrames.length === 0 && location) {\n const loc = [\n location.errorType ?? \"\",\n location.filename ?? \"\",\n location.lineno ?? \"\",\n ].join(\":\");\n if (loc !== \"::\") parts.push(loc);\n }\n return djb2Hex(parts.join(\"|\"));\n}\n\n/**\n * djb2 — small, fast non-cryptographic string hash. 32-bit output\n * encoded as 8-char hex. Stable across runtimes; deterministic.\n */\nfunction djb2Hex(input: string): string {\n let h = 5381;\n for (let i = 0; i < input.length; i++) {\n h = ((h << 5) + h + input.charCodeAt(i)) | 0;\n }\n // Force unsigned then 8-char hex.\n return (h >>> 0).toString(16).padStart(8, \"0\");\n}\n","/**\n * Error capture — the third Crossdeck USP, the headline reason backend\n * developers install observability SDKs.\n *\n * Catches every error source a Node process can hand us and ships them\n * as Crossdeck events. The pipeline reuses the analytics queue:\n * - Same retry-with-backoff + Idempotency-Key (duplicate batches\n * dedup server-side)\n * - Same property sanitisation (one bad context blob can't poison\n * the batch)\n * - Same on-the-wire enrichment via runtime-info\n *\n * Error sources captured (each toggleable):\n * 1. `process.on('uncaughtException')` — uncaught synchronous errors\n * 2. `process.on('unhandledRejection')` — unhandled promise rejections\n * 3. `globalThis.fetch` wrap — 5xx + network failures\n * 4. `console.error` wrap (default OFF) — noisy, opt-in\n * 5. `server.captureError(err)` — manual try/catch API\n * 6. `server.captureMessage(msg)` — non-error signals\n *\n * Adapted from `@cross-deck/web/src/error-capture.ts`. Three runtime\n * differences:\n * - `window.onerror` → `process.on('uncaughtException')`. Node's\n * uncaught-exception handler receives an `Error` directly, not an\n * `ErrorEvent` wrapper — `buildFromUnknown` handles both.\n * - `window.onunhandledrejection` → `process.on('unhandledRejection')`.\n * Same shape (the rejection's `reason`); same handler logic.\n * - `XMLHttpRequest` wrap → dropped (no XHR in Node).\n *\n * Defensive design rules (parity with web):\n * - The error handler must NEVER throw — if our own code crashed\n * while reporting an error, we'd take down the host's last-resort\n * error path. Every callback wrapped in try/swallow.\n * - Recursion guard: a `_reporting` flag prevents reporting our own\n * errors recursively forever.\n * - Rate limited per-fingerprint: max N reports per minute to defend\n * against runaway loops (e.g. an error in a per-request middleware).\n * - Session cap (per process lifetime): hard limit after which we\n * stop reporting. The dashboard sees \"1 unique error\" instead of\n * a million events.\n * - Self-skip for `api.cross-deck.com` requests so a Crossdeck\n * outage doesn't self-amplify back into the queue.\n */\n\nimport { parseStack, fingerprintError, type StackFrame } from \"./stack-parser\";\nimport type { BreadcrumbBuffer, Breadcrumb } from \"./breadcrumbs\";\nimport type { ErrorLevel } from \"./types\";\n\nexport type { ErrorLevel };\n\nexport interface CapturedError {\n /** When the error fired (epoch ms). */\n timestamp: number;\n /** error.unhandled | error.unhandledrejection | error.handled | error.message | error.http */\n kind:\n | \"error.unhandled\"\n | \"error.unhandledrejection\"\n | \"error.handled\"\n | \"error.message\"\n | \"error.http\";\n level: ErrorLevel;\n message: string;\n /** The error class name when we have it (TypeError, ReferenceError, etc.) */\n errorType: string | null;\n /** Parsed stack frames, empty when unavailable. */\n frames: StackFrame[];\n /** Raw stack string for fallback display. */\n rawStack: string | null;\n /** djb2 hash of message + top frames — groups identical errors. */\n fingerprint: string;\n /** Snapshot of the breadcrumb buffer at the moment the error fired. */\n breadcrumbs: Breadcrumb[];\n /** Free-form context attached via `server.setContext()`. */\n context: Record<string, unknown>;\n /** Free-form tags attached via `server.setTag()`. */\n tags: Record<string, string>;\n /** Set only on `error.http` — the request that failed. */\n http?: {\n url: string;\n method: string;\n status: number;\n statusText?: string;\n };\n}\n\nexport interface ErrorCaptureConfig {\n /** Master switch. Default true. */\n enabled: boolean;\n /** Hook `process.on('uncaughtException')`. Default true. */\n onUncaughtException: boolean;\n /** Hook `process.on('unhandledRejection')`. Default true. */\n onUnhandledRejection: boolean;\n /** Wrap `globalThis.fetch` to capture 5xx + network failures. Default true. */\n wrapFetch: boolean;\n /** Wrap `console.error`. Default false (noisy). */\n captureConsole: boolean;\n /**\n * Drop errors matching these substrings / regexes. Tested against\n * `message`. Default: empty (Node has no equivalent of the\n * browser's `ResizeObserver` / `Script error.` noise).\n */\n ignoreErrors: Array<string | RegExp>;\n /**\n * Only capture errors whose top in-app frame filename matches one\n * of these. Empty array means \"no allowlist — capture everything\".\n */\n allowPaths: Array<string | RegExp>;\n /**\n * Drop errors whose top frame filename matches any of these.\n * Default: SDK self-skip pattern (`@cross-deck/node`).\n */\n denyPaths: Array<string | RegExp>;\n /**\n * Sample rate, 0–1. 1.0 = send every error. 0.5 = send half (per\n * fingerprint, deterministically — so a given fingerprint always\n * either always sends or never does, no flapping). Default 1.0.\n */\n sampleRate: number;\n /**\n * Maximum errors per fingerprint per minute. Defends against\n * runaway loops. Default 5.\n */\n maxPerFingerprintPerMinute: number;\n /**\n * Total cap per process lifetime, regardless of fingerprint. Hard\n * limit after which we stop reporting. Default 100.\n */\n maxPerSession: number;\n}\n\nexport const DEFAULT_ERROR_CAPTURE: ErrorCaptureConfig = {\n enabled: true,\n onUncaughtException: true,\n onUnhandledRejection: true,\n wrapFetch: true,\n captureConsole: false,\n ignoreErrors: [],\n allowPaths: [],\n denyPaths: [\n // SDK self-skip — caught by stack-parser's `isInAppFrame` too,\n // but defensive here in case a future change to the heuristic\n // misses one of these paths.\n /[\\\\/]node_modules[\\\\/]@cross-deck[\\\\/]node[\\\\/]/,\n ],\n sampleRate: 1.0,\n maxPerFingerprintPerMinute: 5,\n maxPerSession: 100,\n};\n\nexport interface ErrorTrackerOptions {\n config: ErrorCaptureConfig;\n breadcrumbs: BreadcrumbBuffer;\n /** Called with each captured error. Forwards into the event queue. */\n report: (err: CapturedError) => void;\n /** Called to read the current developer-supplied context bag. */\n getContext: () => Record<string, unknown>;\n /** Called to read the current developer-supplied tag bag. */\n getTags: () => Record<string, string>;\n /**\n * Pre-send hook GETTER. The tracker invokes this on EVERY captured\n * error to resolve the current hook reference, then calls the\n * resolved function with the error (returning `null` to drop, or a\n * modified `CapturedError` to forward).\n *\n * Getter shape — not a static function — so `setErrorBeforeSend()`\n * can install or replace the hook after init() without re-creating\n * the tracker. Pre-fix the field was a captured value: the tracker\n * took a snapshot at construction and never re-read it, so customer\n * PII scrubbers installed later were silently inert. Node worked\n * around this with an `Object.defineProperty` getter trick;\n * normalising the contract to a getter removes the hack and brings\n * web + node into lockstep.\n *\n * Returning `null` from the GETTER means \"no hook configured\" and\n * the report goes through unmodified — distinct from a hook that\n * itself returns null (which means \"drop this specific report\").\n */\n beforeSend?: () => ((err: CapturedError) => CapturedError | null) | null;\n /**\n * Whether the consent dimension `errors` is currently granted. The\n * Node SDK doesn't ship a ConsentManager (server-side trust model\n * — the caller decides), so this is typically `() => true`. We keep\n * the hook so callers who DO want a kill switch (e.g. a config\n * flag to disable error reporting in CI) have a place to wire it.\n */\n isConsented: () => boolean;\n /**\n * The SDK's own backend hostname (derived from\n * `CrossdeckServerOptions.baseUrl` at construction time). Used to\n * skip captureHttp for our own requests — otherwise a Crossdeck-\n * side outage would recurse: captureHttp → enqueue → POST /events\n * → fail again → captureHttp → ∞ until the queue's permanent-4xx\n * hard-stop (Batch B) or runs forever on 5xx. Pre-fix the skip\n * pattern was hardcoded to `api.cross-deck.com`, which failed any\n * customer pointing the SDK at staging / regional / self-hosted\n * relay base URLs. Audit punch list P0 #7.\n *\n * Null / omitted when extraction from baseUrl fails (malformed URL)\n * OR when the test harness doesn't supply one — the tracker falls\n * through to \"capture everything\" rather than swallow.\n */\n selfHostname?: string | null;\n}\n\n/**\n * Cap on the size of the per-fingerprint rate-limit window Map. A\n * long-running process firing many unique fingerprints would\n * otherwise leak Map entries forever. When the Map exceeds this\n * size, dead entries (empty windows) are pruned; if pruning doesn't\n * release enough space, the oldest entries are evicted FIFO. 4096\n * unique error fingerprints is well above realistic per-minute\n * cardinality on production servers.\n */\nconst MAX_FINGERPRINTS_TRACKED = 4096;\nconst FINGERPRINT_WINDOW_MS = 60_000;\n\nexport class ErrorTracker {\n private installed = false;\n private cleanups: Array<() => void> = [];\n private _reporting = false;\n private sessionCount = 0;\n private fingerprintWindow = new Map<string, number[]>();\n\n constructor(private readonly opts: ErrorTrackerOptions) {}\n\n install(): void {\n if (this.installed) return;\n if (!this.opts.config.enabled) return;\n\n if (this.opts.config.onUncaughtException) this.installUncaughtExceptionHandler();\n if (this.opts.config.onUnhandledRejection) this.installUnhandledRejectionHandler();\n if (this.opts.config.wrapFetch) this.installFetchWrap();\n if (this.opts.config.captureConsole) this.installConsoleWrap();\n\n this.installed = true;\n }\n\n uninstall(): void {\n for (const fn of this.cleanups.splice(0)) {\n try {\n fn();\n } catch {\n // ignore\n }\n }\n this.installed = false;\n }\n\n /**\n * Manual API. Either an Error instance or any unknown value (we\n * coerce). Returns silently — never throws, even if the SDK isn't\n * initialised.\n */\n captureError(\n error: unknown,\n options?: { context?: Record<string, unknown>; tags?: Record<string, string>; level?: ErrorLevel },\n ): void {\n if (!this.opts.isConsented()) return;\n try {\n const captured = this.buildFromUnknown(error, \"error.handled\", options?.level ?? \"error\");\n if (options?.context) captured.context = { ...captured.context, ...options.context };\n if (options?.tags) captured.tags = { ...captured.tags, ...options.tags };\n this.maybeReport(captured);\n } catch {\n // self-protection — never let our own code crash the caller's\n // error handler.\n }\n }\n\n /**\n * Capture a non-error event as an issue. For \"we hit a soft-warning\n * code path\" / \"deprecated API used\" kinds of signals. Pairs with\n * Sentry's captureMessage().\n */\n captureMessage(message: string, level: ErrorLevel = \"info\"): void {\n if (!this.opts.isConsented()) return;\n try {\n const captured: CapturedError = {\n timestamp: Date.now(),\n kind: \"error.message\",\n level,\n message,\n errorType: null,\n frames: [],\n rawStack: null,\n fingerprint: fingerprintError(message, []),\n breadcrumbs: this.opts.breadcrumbs.snapshot(),\n context: this.opts.getContext(),\n tags: this.opts.getTags(),\n };\n this.maybeReport(captured);\n } catch {\n // swallow\n }\n }\n\n /** Inspection hook — total reports captured this process lifetime. */\n get reportedCount(): number {\n return this.sessionCount;\n }\n\n /** Inspection hook — number of distinct fingerprints inside the rate-limit window. */\n get fingerprintsTracked(): number {\n return this.fingerprintWindow.size;\n }\n\n /** Inspection hook — whether global handlers are installed. */\n get handlersInstalled(): boolean {\n return this.installed;\n }\n\n // ============================================================\n // Listener installation — Node hooks\n // ============================================================\n\n private installUncaughtExceptionHandler(): void {\n const handler = (err: Error): void => {\n if (this._reporting) return;\n if (!this.opts.isConsented()) return;\n try {\n this._reporting = true;\n const captured = this.buildFromUnknown(err, \"error.unhandled\", \"error\");\n this.maybeReport(captured);\n } catch {\n // swallow\n } finally {\n this._reporting = false;\n }\n };\n process.on(\"uncaughtException\", handler);\n this.cleanups.push(() => process.off(\"uncaughtException\", handler));\n }\n\n private installUnhandledRejectionHandler(): void {\n // Node's `unhandledRejection` handler receives `(reason, promise)`.\n // The reason is whatever was passed to `reject()` — typically an\n // Error, but can be any value. `buildFromUnknown` handles both.\n const handler = (reason: unknown): void => {\n if (this._reporting) return;\n if (!this.opts.isConsented()) return;\n try {\n this._reporting = true;\n const captured = this.buildFromUnknown(reason, \"error.unhandledrejection\", \"error\");\n this.maybeReport(captured);\n } catch {\n // swallow\n } finally {\n this._reporting = false;\n }\n };\n process.on(\"unhandledRejection\", handler);\n this.cleanups.push(() => process.off(\"unhandledRejection\", handler));\n }\n\n /**\n * Wrap `globalThis.fetch` so failed HTTP requests get auto-captured.\n * We do NOT call 4xx an \"error\" (those are often expected — auth\n * required, validation failed). Only 5xx + network failures fire.\n *\n * Node 18+ exposes `fetch` natively on `globalThis`. We tolerate\n * its absence (some sandboxed runtimes / patched globals) by\n * skipping the wrap rather than throwing.\n */\n private installFetchWrap(): void {\n const origFetch = globalThis.fetch;\n if (typeof origFetch !== \"function\") return;\n const tracker = this;\n const wrapped: typeof fetch = async (...args: Parameters<typeof fetch>): Promise<Response> => {\n const input = args[0];\n const init = args[1] ?? {};\n const url = typeof input === \"string\" ? input : (input as Request)?.url ?? \"\";\n const method = (init.method || \"GET\").toUpperCase();\n const start = Date.now();\n\n // Skip self-requests for breadcrumbs too — an error report's\n // crumb trail showing \"POST https://api.cross-deck.com/v1/events\"\n // entries is noise the engineer doesn't care about. Same\n // predicate as captureHttp's self-skip. Audit P2 polish.\n if (!isSelfRequest(url, tracker.opts.selfHostname)) {\n tracker.opts.breadcrumbs.add({\n timestamp: start,\n category: \"http\",\n message: `${method} ${url}`,\n data: { url, method },\n });\n }\n\n try {\n const response = await origFetch(...args);\n if (response.status >= 500 && tracker.opts.isConsented()) {\n // Self-skip Crossdeck's own API to avoid the cycle where a\n // Crossdeck outage reports the outage to Crossdeck.\n if (!isSelfRequest(url, tracker.opts.selfHostname)) {\n tracker.captureHttp({\n url,\n method,\n status: response.status,\n statusText: response.statusText,\n });\n }\n }\n return response;\n } catch (err) {\n // Genuine network failure (DNS, connection refused, ECONNRESET).\n if (tracker.opts.isConsented() && !url.includes(\"api.cross-deck.com\")) {\n tracker.captureHttp({\n url,\n method,\n status: 0,\n statusText: err instanceof Error ? err.message : \"network error\",\n });\n }\n throw err;\n }\n };\n globalThis.fetch = wrapped;\n this.cleanups.push(() => {\n // Restore only if we're still the active wrapper. Another\n // observability tool installed AFTER us would have replaced\n // `globalThis.fetch`; we don't want to unwind their patch.\n if (globalThis.fetch === wrapped) globalThis.fetch = origFetch;\n });\n }\n\n private installConsoleWrap(): void {\n const orig = console.error.bind(console);\n const tracker = this;\n console.error = (...args: unknown[]): void => {\n try {\n if (tracker.opts.isConsented()) {\n tracker.captureMessage(args.map((a) => safeStringify(a)).join(\" \"), \"error\");\n }\n } catch {\n // swallow\n }\n return orig(...args);\n };\n this.cleanups.push(() => {\n console.error = orig;\n });\n }\n\n // ============================================================\n // Builders\n // ============================================================\n\n /**\n * Build a `CapturedError` from any value. Handles:\n * - Error instances (the common case) — parses `err.stack` into\n * frames, fingerprints over message + top in-app frames.\n * - Non-Error rejections (promise rejected with a string / number\n * / plain object) — coerces via `safeStringify`, no frames.\n *\n * Verbatim port of web's `buildFromUnknown` — the logic is\n * runtime-agnostic.\n */\n private buildFromUnknown(\n err: unknown,\n kind: CapturedError[\"kind\"],\n level: ErrorLevel,\n ): CapturedError {\n const payload = coerceErrorPayload(err);\n const message = (payload.message || \"Unknown error\").slice(0, 1024);\n const stack = err instanceof Error ? err.stack ?? null : null;\n const frames = parseStack(stack);\n const errorType = payload.errorType ?? null;\n\n const context = payload.extras\n ? { ...this.opts.getContext(), __error_extras: payload.extras }\n : this.opts.getContext();\n\n return {\n timestamp: Date.now(),\n kind,\n level,\n message,\n errorType,\n frames,\n rawStack: stack,\n // Location fallback ensures distinct call sites stay separate\n // even when the message is generic and there are no parseable\n // frames (e.g. `throw \"boom\"` from a middleware).\n fingerprint: fingerprintError(message, frames, {\n filename: frames[0]?.filename ?? null,\n lineno: frames[0]?.lineno ?? null,\n errorType,\n }),\n breadcrumbs: this.opts.breadcrumbs.snapshot(),\n context,\n tags: this.opts.getTags(),\n };\n }\n\n private captureHttp(info: {\n url: string;\n method: string;\n status: number;\n statusText?: string;\n }): void {\n try {\n const message = `HTTP ${info.status} ${info.method} ${info.url}`;\n const captured: CapturedError = {\n timestamp: Date.now(),\n kind: \"error.http\",\n level: \"error\",\n message,\n errorType: \"HTTPError\",\n frames: [],\n rawStack: null,\n fingerprint: fingerprintError(`HTTP ${info.status} ${info.method}`, [], {\n filename: info.url,\n errorType: \"HTTPError\",\n }),\n breadcrumbs: this.opts.breadcrumbs.snapshot(),\n context: this.opts.getContext(),\n tags: this.opts.getTags(),\n http: info,\n };\n this.maybeReport(captured);\n } catch {\n // swallow\n }\n }\n\n // ============================================================\n // Reporting pipeline — filter / sample / rate-limit / send\n // ============================================================\n\n private maybeReport(err: CapturedError): void {\n if (this.sessionCount >= this.opts.config.maxPerSession) return;\n if (this.shouldIgnore(err)) return;\n if (!this.passesPathGate(err)) return;\n if (!this.passesSample(err)) return;\n if (!this.passesRateLimit(err)) return;\n\n // beforeSend hook — last chance to scrub or drop. Resolve the\n // current hook through the getter on every call so a hook installed\n // via `setErrorBeforeSend()` AFTER init() takes effect on THIS\n // error, not just future ones constructed by a future tracker.\n let finalErr: CapturedError | null = err;\n const hook = this.opts.beforeSend?.();\n if (hook) {\n try {\n finalErr = hook(err);\n } catch {\n // A buggy beforeSend hook must NOT swallow the error report.\n // Fall back to the original.\n finalErr = err;\n }\n if (!finalErr) return;\n }\n\n this.sessionCount += 1;\n try {\n this.opts.report(finalErr);\n } catch {\n // swallow — report() failure is best-effort; the next error\n // attempt will retry through the same queue.\n }\n }\n\n private shouldIgnore(err: CapturedError): boolean {\n for (const pat of this.opts.config.ignoreErrors) {\n if (typeof pat === \"string\" && err.message.includes(pat)) return true;\n if (pat instanceof RegExp && pat.test(err.message)) return true;\n }\n return false;\n }\n\n private passesPathGate(err: CapturedError): boolean {\n // Check the top frame's filename (best-effort — many error.http\n // events have no frames). When the URL is unknown, let it\n // through.\n const topFrame = err.frames.find((f) => f.filename) ?? null;\n const path = topFrame?.filename ?? \"\";\n if (!path) return true;\n\n for (const pat of this.opts.config.denyPaths) {\n if (typeof pat === \"string\" && path.includes(pat)) return false;\n if (pat instanceof RegExp && pat.test(path)) return false;\n }\n if (this.opts.config.allowPaths.length > 0) {\n for (const pat of this.opts.config.allowPaths) {\n if (typeof pat === \"string\" && path.includes(pat)) return true;\n if (pat instanceof RegExp && pat.test(path)) return true;\n }\n return false;\n }\n return true;\n }\n\n private passesSample(err: CapturedError): boolean {\n if (this.opts.config.sampleRate >= 1) return true;\n if (this.opts.config.sampleRate <= 0) return false;\n // Deterministic per-fingerprint sampling — a given fingerprint\n // always either always sends or never does, no flapping.\n const hashByte = parseInt(err.fingerprint.slice(0, 2), 16);\n return hashByte / 255 < this.opts.config.sampleRate;\n }\n\n private passesRateLimit(err: CapturedError): boolean {\n const now = Date.now();\n const max = this.opts.config.maxPerFingerprintPerMinute;\n const arr = this.fingerprintWindow.get(err.fingerprint) ?? [];\n const fresh = arr.filter((t) => now - t < FINGERPRINT_WINDOW_MS);\n if (fresh.length >= max) {\n this.fingerprintWindow.set(err.fingerprint, fresh);\n return false;\n }\n fresh.push(now);\n this.fingerprintWindow.set(err.fingerprint, fresh);\n this.maybePruneFingerprintWindow(now);\n return true;\n }\n\n /**\n * Bound the fingerprint Map's memory footprint. Runs opportunistically\n * — only when the Map exceeds `MAX_FINGERPRINTS_TRACKED`. First pass:\n * delete entries whose ENTIRE window is stale (no live timestamps\n * inside the 60s window). Second pass (if still over): FIFO-evict\n * the oldest entries by Map insertion order until we're under the\n * cap. Defends against a long-running process with high-cardinality\n * fingerprints leaking memory forever.\n */\n private maybePruneFingerprintWindow(now: number): void {\n if (this.fingerprintWindow.size <= MAX_FINGERPRINTS_TRACKED) return;\n // Pass 1 — drop entries whose entire window is stale.\n for (const [fp, timestamps] of this.fingerprintWindow) {\n const hasLive = timestamps.some((t) => now - t < FINGERPRINT_WINDOW_MS);\n if (!hasLive) this.fingerprintWindow.delete(fp);\n }\n if (this.fingerprintWindow.size <= MAX_FINGERPRINTS_TRACKED) return;\n // Pass 2 — FIFO evict the oldest until under cap.\n const overflow = this.fingerprintWindow.size - MAX_FINGERPRINTS_TRACKED;\n let dropped = 0;\n for (const fp of this.fingerprintWindow.keys()) {\n if (dropped >= overflow) break;\n this.fingerprintWindow.delete(fp);\n dropped += 1;\n }\n }\n}\n\n/**\n * The thrown-value coercer.\n *\n * Node's error pipelines (process.on('uncaughtException'),\n * process.on('unhandledRejection'), developer `throw`) hand us values\n * of every shape — Error instances, AggregateError, plain objects,\n * primitives, even null. Earlier versions of this code wrote \"Unknown\n * error\" whenever the value wasn't an Error with a non-empty\n * `.message`, which silently collapsed entire classes of real bugs\n * into one unhelpful bucket.\n *\n * Returns three pieces (never throws):\n *\n * - message: human-readable headline, never empty for any\n * non-null/non-undefined input\n * - errorType: constructor name when discoverable (Error subclass,\n * AggregateError, custom class)\n * - extras: additional fields worth keeping (Error.cause chain,\n * .code/.errno/.statusCode/.response on common Node\n * patterns, AggregateError.errors[], any enumerable\n * own properties on an Error subclass). Stashed on\n * context.__error_extras for the dashboard's \"raw\n * event\" panel.\n */\ninterface CoercedPayload {\n message: string;\n errorType: string | null;\n extras: Record<string, unknown> | null;\n}\n\nfunction coerceErrorPayload(v: unknown): CoercedPayload {\n if (v === null) return { message: \"(thrown: null)\", errorType: null, extras: null };\n if (v === undefined) return { message: \"(thrown: undefined)\", errorType: null, extras: null };\n\n if (typeof v === \"string\") {\n return { message: v, errorType: null, extras: null };\n }\n if (typeof v === \"number\" || typeof v === \"boolean\" || typeof v === \"bigint\") {\n return { message: String(v), errorType: typeof v, extras: null };\n }\n if (typeof v === \"symbol\") {\n return { message: v.toString(), errorType: \"symbol\", extras: null };\n }\n if (typeof v === \"function\") {\n return { message: `(thrown function: ${v.name || \"anonymous\"})`, errorType: \"function\", extras: null };\n }\n\n // Error instances — including AggregateError (Node 16+, thrown by\n // Promise.any when all inputs reject).\n if (v instanceof Error) {\n const errorType = v.name || v.constructor?.name || \"Error\";\n const message =\n typeof v.message === \"string\" && v.message.length > 0\n ? v.message\n : safeToString(v) || errorType;\n\n const extras: Record<string, unknown> = {};\n\n // ES2022 Error.cause — walk up to 5 levels so a service-layer\n // wrapper error doesn't hide the underlying network failure.\n const causeChain = collectCauseChain(v);\n if (causeChain.length > 0) extras.cause = causeChain;\n\n // AggregateError carries an `errors` array of the underlying\n // rejections. Without surfacing this, the user just sees\n // \"AggregateError: All promises were rejected\" with no clue\n // which one failed.\n const aggErrors = (v as unknown as { errors?: unknown }).errors;\n if (Array.isArray(aggErrors)) {\n extras.aggregatedErrors = aggErrors.slice(0, 10).map((inner) => {\n if (inner instanceof Error) {\n return { name: inner.name || \"Error\", message: inner.message || \"\" };\n }\n return { name: \"non-Error\", message: safeToString(inner) };\n });\n }\n\n // Common Node error patterns attach code / errno / syscall /\n // statusCode / response to thrown values. Capture them without\n // forcing every wrapper class to override toString.\n for (const key of [\n \"code\", \"errno\", \"syscall\", \"path\",\n \"status\", \"statusCode\", \"response\", \"data\", \"detail\", \"details\",\n ] as const) {\n const val = (v as unknown as Record<string, unknown>)[key];\n if (val !== undefined && typeof val !== \"function\") {\n extras[key] = safeClone(val);\n }\n }\n\n // Any other enumerable own properties (custom Error subclasses\n // that add fields).\n for (const key of Object.keys(v)) {\n if (key === \"message\" || key === \"stack\" || key === \"name\" || key === \"cause\" || key === \"errors\") continue;\n if (key in extras) continue;\n const val = (v as unknown as Record<string, unknown>)[key];\n if (typeof val === \"function\") continue;\n extras[key] = safeClone(val);\n }\n\n return {\n message,\n errorType,\n extras: Object.keys(extras).length > 0 ? extras : null,\n };\n }\n\n // Response — fetch().then(r => { if (!r.ok) throw r }) is a common\n // Node 18+ pattern (built-in fetch), and the bare Response is\n // otherwise unreadable.\n if (typeof Response !== \"undefined\" && v instanceof Response) {\n return {\n message: `HTTP ${v.status} ${v.statusText || \"\"}${v.url ? ` ${v.url}` : \"\"}`.trim(),\n errorType: \"Response\",\n extras: { status: v.status, statusText: v.statusText, url: v.url, type: v.type },\n };\n }\n\n // Plain objects / custom classes that don't extend Error.\n if (typeof v === \"object\") {\n const obj = v as Record<string, unknown>;\n const ctorName =\n (obj.constructor && typeof obj.constructor === \"function\" && (obj.constructor as { name?: string }).name) ||\n null;\n\n const ownMessage = typeof obj.message === \"string\" && obj.message ? obj.message : null;\n const ownName = typeof obj.name === \"string\" && obj.name ? obj.name : null;\n\n let jsonForm: string | null = null;\n try {\n const serialised = JSON.stringify(obj);\n jsonForm = serialised === \"{}\" ? null : serialised;\n } catch {\n jsonForm = null;\n }\n\n const fallbackString = safeToString(obj);\n const message =\n ownMessage ??\n jsonForm ??\n (fallbackString && fallbackString !== \"[object Object]\" ? fallbackString : null) ??\n (ctorName ? `(thrown ${ctorName} with no message)` : \"(thrown object with no message)\");\n\n const errorType = ownName ?? ctorName ?? null;\n\n const extras: Record<string, unknown> = {};\n let count = 0;\n for (const key of Object.keys(obj)) {\n if (count >= 20) break;\n if (key === \"message\" || key === \"name\") continue;\n const val = obj[key];\n if (typeof val === \"function\") continue;\n extras[key] = safeClone(val);\n count++;\n }\n\n return {\n message,\n errorType,\n extras: Object.keys(extras).length > 0 ? extras : null,\n };\n }\n\n return { message: safeToString(v) || \"(unstringifiable thrown value)\", errorType: null, extras: null };\n}\n\nfunction collectCauseChain(err: Error): Array<{ name: string; message: string }> {\n const out: Array<{ name: string; message: string }> = [];\n let cur: unknown = (err as Error & { cause?: unknown }).cause;\n let depth = 0;\n while (cur != null && depth < 5) {\n if (cur instanceof Error) {\n out.push({ name: cur.name || \"Error\", message: cur.message || \"\" });\n cur = (cur as Error & { cause?: unknown }).cause;\n } else {\n out.push({ name: \"non-Error\", message: safeToString(cur) });\n cur = null;\n }\n depth++;\n }\n return out;\n}\n\nfunction safeToString(v: unknown): string {\n try {\n const s = Object.prototype.toString.call(v);\n if (s !== \"[object Object]\") return s;\n const own = (v as { toString?: () => unknown })?.toString;\n if (typeof own === \"function\" && own !== Object.prototype.toString) {\n const r = own.call(v);\n if (typeof r === \"string\") return r;\n }\n return s;\n } catch {\n return \"(throwing toString)\";\n }\n}\n\nfunction safeClone(v: unknown): unknown {\n if (v == null) return v;\n const t = typeof v;\n if (t === \"string\" || t === \"number\" || t === \"boolean\") return v;\n if (t === \"bigint\") return String(v);\n try {\n const s = JSON.stringify(v);\n return s === undefined ? safeToString(v) : JSON.parse(s);\n } catch {\n return safeToString(v);\n }\n}\n\nfunction safeStringify(v: unknown): string {\n return coerceErrorPayload(v).message;\n}\n\n/**\n * Extract the hostname from a URL string for use as the\n * `selfHostname` field on the ErrorTracker. Returns null on malformed\n * input — the tracker's downstream self-skip check treats `null` as\n * \"no self to skip\" and captures everything (safer than swallowing\n * legitimate errors on a config typo).\n *\n * Lowercased for case-insensitive comparison.\n */\nexport function extractSelfHostname(baseUrl: string | undefined | null): string | null {\n if (!baseUrl || typeof baseUrl !== \"string\") return null;\n try {\n return new URL(baseUrl).hostname.toLowerCase();\n } catch {\n return null;\n }\n}\n\n/**\n * True when the request URL targets the SDK's own backend hostname.\n * Used by the fetch wrapper to skip captureHttp on Crossdeck's own\n * requests — otherwise a Crossdeck-side outage would recurse\n * (captureHttp → enqueue → /events → fail → captureHttp → …).\n *\n * Strict hostname compare (not substring) so\n * `https://api.cross-deck.com.attacker.example/...` doesn't falsely\n * match `api.cross-deck.com`. Falls back to `false` on malformed URLs\n * — the SDK only ever uses absolute URLs, so a relative URL can't\n * be the SDK's own request.\n */\nexport function isSelfRequest(requestUrl: string, selfHostname: string | null | undefined): boolean {\n if (!selfHostname || !requestUrl) return false;\n try {\n return new URL(requestUrl).hostname.toLowerCase() === selfHostname;\n } catch {\n return false;\n }\n}\n","/**\n * Runtime info enrichment — the Node SDK equivalent of\n * `@cross-deck/web/src/device-info.ts`.\n *\n * Detects the host platform (Lambda / Firebase Functions v1 / v2 /\n * Cloud Run / Vercel / plain Node), region, service name + version,\n * and instance ID. Auto-merged into every event's `properties` and\n * every captured-error's `runtime` block.\n *\n * Privacy posture (parity with web's device-info.ts):\n * - No fingerprinting / hardware identifiers.\n * - No precise geolocation (region only — the platform's own metadata).\n * - No IP collection (backend logs the request IP for rate-limit\n * purposes; not stored on the event document).\n *\n * Detection runs ONCE per process — the returned `RuntimeInfo` is a\n * frozen reference cached at module level. Zero per-event overhead.\n * Caller-supplied overrides (serviceName / serviceVersion / appVersion\n * via `CrossdeckServer` options) win over env-derived values on the\n * first call — that's the SDK constructor.\n */\n\nimport { hostname as osHostname, platform as osPlatform, release as osRelease } from \"node:os\";\n\nexport type RuntimeHost =\n | \"aws-lambda\"\n | \"azure-functions\"\n | \"google-app-engine\"\n | \"firebase-functions-v1\"\n | \"firebase-functions-v2\"\n | \"cloud-run\"\n | \"vercel\"\n | \"netlify\"\n | \"heroku\"\n | \"render\"\n | \"railway\"\n | \"fly\"\n | \"kubernetes\"\n | \"node\";\n\n/**\n * Hosts where a cold start begins with empty process memory — FaaS and\n * serverless-container platforms that scale to zero and spin up a fresh\n * instance per burst of traffic.\n *\n * Drives `RuntimeInfo.isServerless`. The entitlement-cache durability\n * layer treats a host in this set with no `entitlementStore` as having\n * no cold-start durability. Long-lived process hosts (Heroku, Render,\n * Railway, Fly, Kubernetes, plain Node) are deliberately ABSENT — their\n * process, and so the in-memory cache, survives between requests.\n */\nconst SERVERLESS_HOSTS: ReadonlySet<RuntimeHost> = new Set<RuntimeHost>([\n \"aws-lambda\",\n \"azure-functions\",\n \"google-app-engine\",\n \"firebase-functions-v1\",\n \"firebase-functions-v2\",\n \"cloud-run\",\n \"vercel\",\n \"netlify\",\n]);\n\nexport interface RuntimeInfo {\n nodeVersion: string;\n /** `os.platform()` — \"darwin\" | \"linux\" | \"win32\" | … */\n platform: string;\n /** `os.release()` — kernel release string, e.g. \"5.15.0-1071-aws\". */\n platformRelease: string;\n hostname: string;\n host: RuntimeHost;\n /**\n * Whether the host is a scale-to-zero / per-request-instance platform\n * where a cold start begins with empty process memory.\n *\n * `true` for FaaS + serverless-container platforms (Lambda, Cloud\n * Run, Firebase Functions v1/v2, Vercel, Netlify, Azure Functions,\n * App Engine). `false` for long-lived process hosts (Heroku, Render,\n * Railway, Fly, Kubernetes, plain Node) where the process — and thus\n * the in-memory entitlement cache — persists across requests.\n *\n * The entitlement-cache durability layer reads this: a serverless\n * host with no `entitlementStore` has no cold-start durability, and\n * the SDK surfaces that explicitly (debug warning + a `durability`\n * fact on the boot telemetry event).\n */\n isServerless: boolean;\n region: string | null;\n serviceName: string | null;\n serviceVersion: string | null;\n /**\n * Process-stable ID. Lambda log stream name when on Lambda; revision +\n * pid on Cloud Run / Firebase v2; pid as string otherwise. Used by the\n * dashboard to distinguish events from different instances of the same\n * function name + version.\n */\n instanceId: string | null;\n /** Caller-supplied app version. Attached as `appVersion` on every event. */\n appVersion: string | null;\n}\n\nexport interface RuntimeInfoOptions {\n serviceName?: string;\n serviceVersion?: string;\n appVersion?: string;\n}\n\nlet cached: RuntimeInfo | null = null;\n\n/**\n * Collect runtime info. Detection runs once per process; subsequent\n * calls return the same cached frozen object. Caller-supplied options\n * take effect on the FIRST call — that's by design, the SDK constructor\n * passes them once and downstream call sites read.\n */\nexport function collectRuntimeInfo(options: RuntimeInfoOptions = {}): RuntimeInfo {\n if (cached) return cached;\n cached = detect(options);\n return cached;\n}\n\n/**\n * Re-detect. Tests + multi-instance scenarios only. Not called from the\n * SDK boot path — runtime info is logically immutable for a process's\n * lifetime.\n */\nexport function resetRuntimeInfoCache(): void {\n cached = null;\n}\n\nfunction detect(options: RuntimeInfoOptions): RuntimeInfo {\n // Defensive: edge runtimes (Workers, Bun-in-some-configs, sandboxed\n // hosts) may have a partial or absent `process` object. Fall back\n // to an empty env so detection just returns the \"node\" host.\n const env: NodeJS.ProcessEnv =\n typeof process !== \"undefined\" && process.env ? process.env : ({} as NodeJS.ProcessEnv);\n const detected = detectHost(env);\n return Object.freeze({\n nodeVersion: typeof process !== \"undefined\" && process.versions ? process.versions.node : \"unknown\",\n platform: safePlatform(),\n platformRelease: safeRelease(),\n hostname: safeHostname(),\n host: detected.host,\n isServerless: SERVERLESS_HOSTS.has(detected.host),\n region: detected.region,\n serviceName: options.serviceName ?? detected.serviceName,\n serviceVersion: options.serviceVersion ?? detected.serviceVersion,\n instanceId: detected.instanceId,\n appVersion: options.appVersion ?? null,\n });\n}\n\ninterface HostDetection {\n host: RuntimeHost;\n region: string | null;\n serviceName: string | null;\n serviceVersion: string | null;\n instanceId: string | null;\n}\n\n/**\n * Host detection — order matters. More-specific signals first:\n * - AWS Lambda: `AWS_LAMBDA_FUNCTION_NAME` (unique to Lambda).\n * - Azure Functions: `FUNCTIONS_WORKER_RUNTIME` + `WEBSITE_INSTANCE_ID`.\n * - Google App Engine: `GAE_APPLICATION` (gen-1 + gen-2 standard) —\n * checked before K_SERVICE because GAE gen-2 sets BOTH and we\n * want the more specific label.\n * - Firebase v2 / Cloud Run: share `K_SERVICE` + `K_REVISION`. The\n * difference is `FIREBASE_CONFIG` / `GCLOUD_PROJECT` (set by the\n * Firebase deploy chain, not by plain Cloud Run).\n * - Firebase v1: `FUNCTION_NAME` + `FUNCTION_REGION` (the legacy\n * pair, still used by gen-1 deployments).\n * - Vercel: `VERCEL === \"1\"`.\n * - Netlify Functions: `NETLIFY === \"true\"` or `AWS_LAMBDA_FUNCTION_NAME`\n * prefixed (Netlify runs on Lambda under the hood but we already\n * captured Lambda first; the `NETLIFY=true` check identifies\n * Netlify Functions specifically when Lambda env vars aren't set).\n * - Heroku: `DYNO` (the dyno identifier; uniquely Heroku).\n * - Render: `RENDER === \"true\"` + `RENDER_INSTANCE_ID`.\n * - Railway: `RAILWAY_ENVIRONMENT`.\n * - Fly.io: `FLY_APP_NAME` + `FLY_REGION`.\n * - Kubernetes: `KUBERNETES_SERVICE_HOST` — generic fallback for\n * containerised Node not on a more-specific platform.\n * - Else: plain Node — long-lived servers, dev machines, test runs.\n */\nfunction detectHost(env: NodeJS.ProcessEnv): HostDetection {\n const pid = safePid();\n\n if (env.AWS_LAMBDA_FUNCTION_NAME) {\n return {\n host: \"aws-lambda\",\n region: env.AWS_REGION ?? null,\n serviceName: env.AWS_LAMBDA_FUNCTION_NAME,\n serviceVersion: env.AWS_LAMBDA_FUNCTION_VERSION ?? null,\n instanceId: env.AWS_LAMBDA_LOG_STREAM_NAME ?? pid,\n };\n }\n\n if (env.FUNCTIONS_WORKER_RUNTIME && env.WEBSITE_INSTANCE_ID) {\n return {\n host: \"azure-functions\",\n region: env.REGION_NAME ?? env.WEBSITE_LOCATION ?? null,\n serviceName: env.WEBSITE_SITE_NAME ?? null,\n serviceVersion: env.WEBSITE_BUILD_ID ?? null,\n instanceId: env.WEBSITE_INSTANCE_ID,\n };\n }\n\n if (env.GAE_APPLICATION) {\n return {\n host: \"google-app-engine\",\n region: env.GAE_REGION ?? env.GOOGLE_CLOUD_REGION ?? null,\n serviceName: env.GAE_SERVICE ?? null,\n serviceVersion: env.GAE_VERSION ?? null,\n instanceId: env.GAE_INSTANCE ?? pid,\n };\n }\n\n if (env.K_SERVICE && env.K_REVISION) {\n const isFirebase = Boolean(env.FIREBASE_CONFIG || env.GCLOUD_PROJECT);\n return {\n host: isFirebase ? \"firebase-functions-v2\" : \"cloud-run\",\n region: env.FUNCTION_REGION ?? env.GOOGLE_CLOUD_REGION ?? null,\n serviceName: env.K_SERVICE,\n serviceVersion: env.K_REVISION,\n instanceId: `${env.K_REVISION}:${pid}`,\n };\n }\n\n if (env.FUNCTION_NAME && env.FUNCTION_REGION) {\n return {\n host: \"firebase-functions-v1\",\n region: env.FUNCTION_REGION,\n serviceName: env.FUNCTION_NAME,\n serviceVersion: env.X_GOOGLE_FUNCTION_VERSION ?? null,\n instanceId: pid,\n };\n }\n\n if (env.VERCEL === \"1\") {\n return {\n host: \"vercel\",\n region: env.VERCEL_REGION ?? null,\n serviceName: env.VERCEL_URL ?? null,\n serviceVersion: env.VERCEL_GIT_COMMIT_SHA?.slice(0, 7) ?? null,\n instanceId: pid,\n };\n }\n\n if (env.NETLIFY === \"true\" || env.NETLIFY_BUILD_BASE) {\n return {\n host: \"netlify\",\n region: env.AWS_REGION ?? null,\n serviceName: env.SITE_NAME ?? env.SITE_ID ?? null,\n serviceVersion: env.COMMIT_REF?.slice(0, 7) ?? null,\n instanceId: pid,\n };\n }\n\n if (env.DYNO) {\n return {\n host: \"heroku\",\n region: null,\n serviceName: env.HEROKU_APP_NAME ?? null,\n serviceVersion: env.HEROKU_RELEASE_VERSION ?? env.HEROKU_SLUG_COMMIT?.slice(0, 7) ?? null,\n instanceId: env.DYNO,\n };\n }\n\n if (env.RENDER === \"true\" || env.RENDER_INSTANCE_ID) {\n return {\n host: \"render\",\n region: env.RENDER_SERVICE_REGION ?? null,\n serviceName: env.RENDER_SERVICE_NAME ?? null,\n serviceVersion: env.RENDER_GIT_COMMIT?.slice(0, 7) ?? null,\n instanceId: env.RENDER_INSTANCE_ID ?? pid,\n };\n }\n\n if (env.RAILWAY_ENVIRONMENT) {\n return {\n host: \"railway\",\n region: env.RAILWAY_REGION ?? null,\n serviceName: env.RAILWAY_SERVICE_NAME ?? null,\n serviceVersion: env.RAILWAY_GIT_COMMIT_SHA?.slice(0, 7) ?? null,\n instanceId: env.RAILWAY_REPLICA_ID ?? pid,\n };\n }\n\n if (env.FLY_APP_NAME) {\n return {\n host: \"fly\",\n region: env.FLY_REGION ?? null,\n serviceName: env.FLY_APP_NAME,\n serviceVersion: env.FLY_IMAGE_REF?.slice(-7) ?? null,\n instanceId: env.FLY_ALLOC_ID ?? pid,\n };\n }\n\n if (env.KUBERNETES_SERVICE_HOST) {\n return {\n host: \"kubernetes\",\n region: null,\n serviceName: env.POD_NAME ?? env.HOSTNAME ?? null,\n serviceVersion: null,\n instanceId: env.POD_NAME ?? env.HOSTNAME ?? pid,\n };\n }\n\n return {\n host: \"node\",\n region: null,\n serviceName: null,\n serviceVersion: null,\n instanceId: pid,\n };\n}\n\nfunction safeHostname(): string {\n try {\n return osHostname();\n } catch {\n return \"unknown\";\n }\n}\n\nfunction safePlatform(): string {\n try {\n return osPlatform();\n } catch {\n return \"unknown\";\n }\n}\n\nfunction safeRelease(): string {\n try {\n return osRelease();\n } catch {\n return \"unknown\";\n }\n}\n\nfunction safePid(): string {\n try {\n return typeof process !== \"undefined\" && process.pid ? String(process.pid) : \"0\";\n } catch {\n return \"0\";\n }\n}\n\n/**\n * Flatten `RuntimeInfo` into a property bag suitable for merging onto\n * every event. Keys are namespaced under `runtime.*` to keep top-level\n * event properties clean and to match the dashboard's runtime column\n * group.\n *\n * Null fields are omitted so downstream property bags don't fill with\n * empty columns.\n */\nexport function runtimeInfoToProperties(info: RuntimeInfo): Record<string, unknown> {\n const out: Record<string, unknown> = {\n \"runtime.nodeVersion\": info.nodeVersion,\n \"runtime.platform\": info.platform,\n \"runtime.platformRelease\": info.platformRelease,\n \"runtime.hostname\": info.hostname,\n \"runtime.host\": info.host,\n };\n if (info.region) out[\"runtime.region\"] = info.region;\n if (info.serviceName) out[\"runtime.serviceName\"] = info.serviceName;\n if (info.serviceVersion) out[\"runtime.serviceVersion\"] = info.serviceVersion;\n if (info.instanceId) out[\"runtime.instanceId\"] = info.instanceId;\n if (info.appVersion) out.appVersion = info.appVersion;\n return out;\n}\n","/**\n * Process-exit drain hook.\n *\n * Defends against the silent-loss failure mode that motivated the\n * entire v1.0.0 port: a Cloud Function cold-starts, fires 3 events\n * synchronously, and the process exits before the HTTP POSTs complete.\n * Without this module, those events vanish without trace.\n *\n * With it, we install handlers for `beforeExit` + `SIGTERM` + `SIGINT`\n * that synchronously await `queue.flush()` before the process is\n * allowed to terminate. Bounded by `timeoutMs` (default 2000ms) so a\n * misbehaving server can't keep the function alive past the platform's\n * SIGKILL window (typically 5-10s after SIGTERM).\n *\n * Idempotency:\n * - `install()` returns immediately if already installed.\n * - Multiple signals (e.g. SIGTERM then beforeExit) only drain once.\n * - `uninstall()` removes the listeners.\n *\n * Why not just rely on `process.on('exit')`: by the time 'exit' fires,\n * the event loop is dead. No async work can run. `beforeExit` is the\n * last point at which the loop is still alive and we can `await\n * flush()` properly. SIGTERM + SIGINT are the platform's \"you're being\n * terminated\" signals — Cloud Run fires SIGTERM on container stop;\n * Lambda fires SIGTERM on idle termination; SIGINT is the user's\n * Ctrl-C in dev.\n */\n\nexport interface FlushOnExitOptions {\n /**\n * Async drain function — typically `() => server.flush()`. May reject\n * or hang; we bound it with `timeoutMs`.\n */\n drain: () => Promise<unknown>;\n /**\n * Bounded timeout for the drain (ms). Default 2000.\n *\n * Two seconds is enough to flush a handful of events over a healthy\n * network without holding up function teardown past the platform's\n * SIGKILL window.\n */\n timeoutMs?: number;\n /**\n * Optional callback fired when the drain starts. Wired by the SDK\n * debug logger to emit `sdk.flush_on_exit_started`.\n */\n onStart?: () => void;\n /**\n * Optional callback fired when the drain completes (success or\n * timeout). Wired by the SDK debug logger to emit\n * `sdk.flush_on_exit_completed`. Receives `{ durationMs, timedOut }`.\n */\n onComplete?: (info: { durationMs: number; timedOut: boolean }) => void;\n /**\n * Optional callback fired when the drain throws. Receives the error.\n * The handler always allows the process to exit — drain failure is\n * observation, not a block.\n */\n onError?: (err: unknown) => void;\n}\n\ntype Signal = \"SIGTERM\" | \"SIGINT\";\nconst SIGNALS: readonly Signal[] = [\"SIGTERM\", \"SIGINT\"];\n\nconst DEFAULT_TIMEOUT_MS = 2000;\n\nexport class FlushOnExit {\n private installed = false;\n private draining = false;\n private drained = false;\n private beforeExitHandler: (() => void) | null = null;\n private signalHandlers: Partial<Record<Signal, () => void>> = {};\n\n constructor(private readonly options: FlushOnExitOptions) {}\n\n /**\n * Install handlers for `beforeExit` + `SIGTERM` + `SIGINT`. Idempotent —\n * calling twice does NOT register duplicate handlers.\n */\n install(): void {\n if (this.installed) return;\n this.installed = true;\n\n this.beforeExitHandler = () => {\n // `beforeExit` fires when the event loop has no more work AND\n // there's no async pending. We're allowed to start more async\n // here — the loop will re-tick to drain it. After we return\n // from awaiting drain, `beforeExit` will NOT re-fire (Node\n // guards against re-entry).\n void this.runDrain(\"beforeExit\");\n };\n process.on(\"beforeExit\", this.beforeExitHandler);\n\n for (const sig of SIGNALS) {\n const handler = (): void => {\n void this.runDrainAndExit(sig);\n };\n this.signalHandlers[sig] = handler;\n process.on(sig, handler);\n }\n }\n\n /**\n * Remove all handlers. Tests + custom-lifecycle callers only.\n */\n uninstall(): void {\n if (!this.installed) return;\n this.installed = false;\n if (this.beforeExitHandler) {\n process.off(\"beforeExit\", this.beforeExitHandler);\n this.beforeExitHandler = null;\n }\n for (const sig of SIGNALS) {\n const handler = this.signalHandlers[sig];\n if (handler) {\n process.off(sig, handler);\n delete this.signalHandlers[sig];\n }\n }\n }\n\n /**\n * Force-drain immediately (without waiting for an exit signal).\n * Used by `wrapLambdaHandler` / `wrapFunction` — Lambda freezes the\n * process between invocations, so we drain at the END of each\n * invocation rather than waiting for SIGTERM.\n */\n async drainNow(): Promise<void> {\n return this.runDrain(\"manual\");\n }\n\n /** True if the drain has already completed (one-shot lifecycle). */\n get hasDrained(): boolean {\n return this.drained;\n }\n\n /** True if a drain is in flight. */\n get isDraining(): boolean {\n return this.draining;\n }\n\n // ---------- internals ----------\n\n private async runDrain(_reason: \"beforeExit\" | \"SIGTERM\" | \"SIGINT\" | \"manual\"): Promise<void> {\n // One-shot — multiple signals during the same process must not\n // double-drain. The queue has been spliced empty after the first\n // call; subsequent flushes would be no-ops, but we also have to\n // worry about a race where two signals fire in quick succession.\n if (this.drained || this.draining) return;\n this.draining = true;\n this.options.onStart?.();\n\n const start = Date.now();\n const timeoutMs = this.options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n let timedOut = false;\n let drainError: unknown = null;\n\n // Race the drain against the timeout. The drain's rejection is\n // surfaced via `drainError` (not by rethrowing) so we can ALWAYS\n // fire `onComplete` and ALWAYS fire `onError` when the drain\n // threw — and never leave the process hanging on a swallowed\n // rejection.\n await new Promise<void>((resolve) => {\n let settled = false;\n const timer = setTimeout(() => {\n if (settled) return;\n settled = true;\n timedOut = true;\n resolve();\n }, timeoutMs);\n // `.unref()` so the timer itself doesn't keep the loop alive\n // past the drain — otherwise a fast drain would still wait the\n // full timeout before letting `beforeExit` complete.\n if (typeof timer.unref === \"function\") {\n try {\n timer.unref();\n } catch {\n // ignore\n }\n }\n\n let drainPromise: Promise<unknown>;\n try {\n drainPromise = this.options.drain();\n } catch (syncErr) {\n // The drain function threw synchronously before returning a\n // promise. Treat the same as an async rejection.\n if (settled) return;\n settled = true;\n drainError = syncErr;\n clearTimeout(timer);\n resolve();\n return;\n }\n\n drainPromise.then(\n () => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n resolve();\n },\n (err) => {\n if (settled) return;\n settled = true;\n drainError = err;\n clearTimeout(timer);\n resolve();\n },\n );\n });\n\n if (drainError !== null) {\n try {\n this.options.onError?.(drainError);\n } catch {\n // A buggy onError must NOT block process exit.\n }\n }\n\n this.draining = false;\n this.drained = true;\n this.options.onComplete?.({\n durationMs: Date.now() - start,\n timedOut,\n });\n }\n\n /**\n * Drain in response to a termination signal. After the drain\n * completes, we re-raise the signal so the process exits with the\n * correct exit code — `kill -TERM <pid>` should still terminate\n * the process even though we installed a handler that \"consumed\" it.\n *\n * Detail: Node attaches a default SIGTERM handler that exits the\n * process. The MOMENT we register our own handler with `process.on`,\n * the default is removed. So we have to re-raise to mimic the\n * default behaviour after our drain completes.\n */\n private async runDrainAndExit(sig: Signal): Promise<void> {\n await this.runDrain(sig);\n // Remove our handler so the re-raised signal hits the default.\n const handler = this.signalHandlers[sig];\n if (handler) {\n process.off(sig, handler);\n delete this.signalHandlers[sig];\n }\n // Re-raise. Exit code convention: 128 + signal number (SIGTERM=15\n // → 143; SIGINT=2 → 130). Node's process.kill() does this for us.\n try {\n process.kill(process.pid, sig);\n } catch {\n // Defensive: process.kill can fail if pid is somehow invalid.\n // Fall back to a hard exit with the conventional code.\n const code = sig === \"SIGTERM\" ? 143 : 130;\n process.exit(code);\n }\n }\n}\n\n","/**\n * Super-properties + group analytics — Mixpanel pattern.\n *\n * Super properties are key/value pairs the developer registers ONCE\n * via `server.register({ tenant: \"acme\" })` that get attached to\n * every subsequent event of THIS SDK instance. They're the single\n * most-used feature in Mixpanel-style analytics: \"every event from\n * this process should have `tenant` and `serviceName` on it\" instead\n * of remembering to pass them on every `track()` call.\n *\n * Groups are organisational identifiers: a customer might belong to\n * an `org` (\"acme\"), a `team` (\"design\"), and a `plan` (\"enterprise\").\n * Each event carries `$groups.{type}: id` so B2B dashboards can pivot:\n * \"Acme's team:design fired 142 paywall_shown events this week\".\n *\n * Node port differences from `@cross-deck/web/src/super-properties.ts`:\n * - No `KeyValueStorage` backing. In-memory only. Node processes are\n * short-lived (Lambda freezes between invocations, Cloud Functions\n * tear down containers); super-properties typically belong to the\n * SDK instance lifetime, not persistence-across-process.\n * - The Store reset clears both bags (parity with web's clear()).\n *\n * The store is reset on `server.shutdown()` — both super properties\n * and groups are cleared because their lifetime is tied to the SDK\n * instance, not to the process.\n */\n\nexport interface GroupMembership {\n id: string;\n traits?: Record<string, unknown>;\n}\n\nexport class SuperPropertyStore {\n private superProps: Record<string, unknown> = {};\n private groups: Record<string, GroupMembership> = {};\n\n /**\n * Merge new keys into the super-property bag. Returns a snapshot\n * of the resulting bag. Values that are `null` are deleted\n * (Mixpanel's explicit \"stop tracking this key\" idiom).\n */\n register(props: Record<string, unknown>): Record<string, unknown> {\n for (const [k, v] of Object.entries(props)) {\n if (v === null) {\n delete this.superProps[k];\n } else if (v !== undefined) {\n this.superProps[k] = v;\n }\n }\n return { ...this.superProps };\n }\n\n /** Remove a single super-property key. Idempotent. */\n unregister(key: string): void {\n if (key in this.superProps) {\n delete this.superProps[key];\n }\n }\n\n /** Defensive snapshot of the current super-property bag. */\n getSuperProperties(): Record<string, unknown> {\n return { ...this.superProps };\n }\n\n /**\n * Set a group membership. Passing `id: null` clears the membership\n * for that type — the SDK stops attaching it to events.\n */\n setGroup(type: string, id: string | null, traits?: Record<string, unknown>): void {\n if (id === null) {\n delete this.groups[type];\n } else {\n this.groups[type] = traits !== undefined ? { id, traits } : { id };\n }\n }\n\n /**\n * Defensive snapshot of the current groups map, keyed by group type.\n * The `traits` sub-object is the most-recent traits payload passed\n * to `setGroup` for that type.\n */\n getGroups(): Record<string, GroupMembership> {\n // Defensive deep copy — caller mutations to traits don't bleed\n // into the store.\n const out: Record<string, GroupMembership> = {};\n for (const [type, membership] of Object.entries(this.groups)) {\n out[type] = {\n id: membership.id,\n ...(membership.traits ? { traits: { ...membership.traits } } : {}),\n };\n }\n return out;\n }\n\n /**\n * Flat `{ type: id }` projection used for event-attachment. Stable\n * for fast every-event merge — we don't JSON-clone on each\n * `track()` call (hot path).\n */\n getGroupIds(): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [type, info] of Object.entries(this.groups)) {\n out[type] = info.id;\n }\n return out;\n }\n\n /** Wipe both bags. Called by `server.shutdown()`. */\n clear(): void {\n this.superProps = {};\n this.groups = {};\n }\n}\n","/**\n * Per-customer durable last-known-good entitlement cache — the third\n * Crossdeck USP on the server.\n *\n * Why this exists: server-side gating code looks like\n *\n * if (server.isEntitled(customerId, \"pro\")) { … }\n *\n * inside a request handler. Without a cache, every request makes an\n * HTTP round-trip to `GET /v1/entitlements?customerId=…` — 50-200ms\n * per request, every request, for every customer. The cache makes\n * `isEntitled()` a `Map.get()` after the first warm.\n *\n * Durability contract (mirrors `@cross-deck/web/src/entitlement-cache.ts`,\n * adapted for a multi-tenant server):\n * - This cache is NOT a second source of truth. Crossdeck remains the\n * only source; this is the SDK's local copy of what the server last\n * told us — a cache that does not forget during a network partition.\n * - Only a SUCCESSFUL fetch replaces a customer's entry (via\n * `setForCustomer`). A failed refresh never reaches it, so an outage\n * can never fail a paying customer down to free.\n * - **The TTL is a REFRESH HINT, not an invalidation.** `isEntitled()`\n * and `list()` keep serving last-known-good after `ttlMs` elapses —\n * they do NOT return `false` / `[]` because the entry aged. The TTL\n * only drives `needsRefresh()` (\"a re-fetch is due\") and, with no\n * failed refresh, the stale flag. This is the central fix: on\n * serverless a paying customer must not be locked out 60s after a\n * warm just because Crossdeck was briefly unreachable.\n * - Staleness alone never returns false. Each entitlement is honoured\n * against its OWN `validUntil` instead — a time-based trial expiry\n * still applies even mid-partition; a still-valid Pro entitlement\n * rides the outage out.\n * - Staleness is VISIBLE, not silent. `validUntil` covers time-based\n * expiry; it does NOT cover an event-based revoke (chargeback,\n * refund, fraud) — that has no `validUntil`, so the cache would keep\n * serving a revoked customer through an outage. Serving them is the\n * right trade (don't lock real payers out), but unbounded-and-\n * invisible is the bug. So once a refresh ATTEMPT fails\n * (`markRefreshFailed`) or the data ages past `staleAfterMs`, the\n * customer is flagged stale — `isStale()` / `staleCustomerCount` are\n * surfaced in `diagnostics()`. It keeps serving last-known-good; the\n * staleness is just no longer hidden.\n *\n * Cold-start durability lives one layer up: `getEntitlements()` in\n * `crossdeck-server.ts` persists every successful fetch to an optional\n * `EntitlementStore` and, on a network failure, loads last-known-good\n * back from it and into this cache. This cache stays a pure in-memory\n * structure with NO I/O — `isEntitled()` is and remains synchronous.\n *\n * Differences from the web SDK's cache:\n * - **Per-customer**, not singleton. Web SDK has one user per browser\n * tab; Node SDK has many users hitting one server. The cache is\n * keyed by `crossdeckCustomerId`. Staleness, freshness and the\n * failed-refresh marker are therefore all per-customer too.\n * - **No synchronous storage hydration** in the constructor. The web\n * SDK hydrates from `localStorage` on boot; the Node durable store\n * is async, so hydration happens lazily inside `getEntitlements()`.\n * - **LRU-bounded** by `maxCustomers` — a long-running multi-tenant\n * server would otherwise leak Map entries forever.\n * - **Subscriber API unchanged** — `subscribe(listener)` fires after\n * any mutation (set / clear). Passive LRU eviction and a TTL\n * elapsing are NOT mutations, by design.\n */\n\nimport type { PublicEntitlement } from \"./types\";\n\nexport type EntitlementsListener = (\n customerId: string,\n entitlements: PublicEntitlement[],\n) => void;\n\ninterface CacheEntry {\n /**\n * Full entitlement objects (active + inactive). `isEntitled()`\n * iterates these and checks `isActive` + each entitlement's own\n * `validUntil` inline — mirroring the web SDK — so no separate\n * active-key set is kept; that set could not carry `validUntil`.\n */\n all: PublicEntitlement[];\n /**\n * epoch ms after which a re-fetch is DUE. This is a refresh hint, NOT\n * an expiry: past this point `isEntitled()` / `list()` still serve\n * last-known-good. It only drives `needsRefresh()` and the stale flag.\n */\n refreshDueAt: number;\n /** epoch ms the entry was last populated by a successful fetch. */\n populatedAt: number;\n /**\n * epoch ms of the most recent FAILED refresh attempt for this\n * customer, or 0 if the last attempt succeeded. Set by\n * `markRefreshFailed`, cleared by `setForCustomer`. When this is more\n * recent than `populatedAt` the customer is knowingly stale.\n */\n refreshFailedAt: number;\n}\n\nexport interface EntitlementCacheOptions {\n /**\n * Refresh-hint TTL in ms. Default 60_000 (60s).\n *\n * After `ttlMs` a customer's entry is \"refresh due\" — `needsRefresh()`\n * returns true and the caller should re-fetch. It is NOT an expiry:\n * `isEntitled()` keeps serving last-known-good past it. `0` makes\n * every entry immediately refresh-due (useful for tests) but STILL\n * does not invalidate — last-known-good is served regardless.\n */\n ttlMs?: number;\n /**\n * Maximum number of customers cached at once. Long-running multi-tenant\n * servers handling a long tail of customers would otherwise leak Map\n * entries forever. Default 10_000 — enough for any realistic deployment.\n * When the cap is reached, the OLDEST entry (by insertion / refresh\n * order) is evicted to make room. Eviction does NOT fire listeners\n * (passive eviction is not a mutation by design).\n */\n maxCustomers?: number;\n /**\n * Age (ms) past which a customer's last-known-good data is flagged\n * STALE even with no failed refresh. Default 24h.\n *\n * Staleness never changes what `isEntitled()` returns; it only makes a\n * long un-refreshed window observable via `isStale()` / diagnostics —\n * so an event-based revoke (no `validUntil`) riding out an outage is\n * visible instead of silent.\n */\n staleAfterMs?: number;\n}\n\nconst DEFAULT_MAX_CUSTOMERS = 10_000;\n/** Default staleness window — data older than this is flagged even with no failed refresh. */\nconst DEFAULT_STALE_AFTER_MS = 24 * 60 * 60 * 1000; // 24h\n\nexport class EntitlementCache {\n private readonly ttlMs: number;\n private readonly maxCustomers: number;\n private readonly staleAfterMs: number;\n private byCustomer = new Map<string, CacheEntry>();\n private listeners = new Set<EntitlementsListener>();\n private listenerErrorCount = 0;\n private evicted = 0;\n\n constructor(options: EntitlementCacheOptions = {}) {\n this.ttlMs = options.ttlMs ?? 60_000;\n this.maxCustomers = options.maxCustomers ?? DEFAULT_MAX_CUSTOMERS;\n this.staleAfterMs = options.staleAfterMs ?? DEFAULT_STALE_AFTER_MS;\n }\n\n /**\n * Synchronous lookup — true iff the customer currently has the\n * entitlement granting access. Pure in-memory `Map` read, ZERO I/O.\n *\n * Served from last-known-good: a stale entry (Crossdeck unreachable\n * since the last successful fetch, or past `ttlMs`) STILL answers true\n * for a still-valid entitlement. Cache staleness alone never makes\n * this `false` — the central durability fix. The only things that\n * turn it false:\n * - the customer has no cached entry at all (genuine cold miss)\n * - no matching `key` in the customer's entitlement set\n * - the matching entitlement is `isActive: false`\n * - the matching entitlement is past its OWN `validUntil` — a\n * time-based trial expiry still applies mid-outage (mirrors the\n * web SDK's `validUntil` check exactly).\n *\n * An entry being past `ttlMs`, or marked refresh-failed, does NOT\n * affect the answer — `getEntitlements()` re-fetches on the TTL hint,\n * but until it succeeds the customer keeps their access.\n */\n isEntitled(customerId: string, key: string): boolean {\n const entry = this.byCustomer.get(customerId);\n if (!entry) return false;\n const nowSec = Date.now() / 1000;\n return entry.all.some(\n (e) =>\n e.key === key &&\n e.isActive &&\n (e.validUntil == null || e.validUntil > nowSec),\n );\n }\n\n /**\n * Full snapshot for callers that need source / validUntil details.\n * Returns `[]` ONLY when the customer has no cached entry — a stale\n * or past-TTL entry still returns its last-known-good entitlements\n * (same durability posture as `isEntitled()`; per-entitlement\n * `validUntil` is the caller's to honour from the returned objects).\n */\n list(customerId: string): PublicEntitlement[] {\n const entry = this.byCustomer.get(customerId);\n if (!entry) return [];\n return entry.all.slice();\n }\n\n /**\n * Whether the customer's entry is still within `ttlMs` — i.e. a\n * re-fetch is NOT yet due. Useful for deciding whether to warm before\n * a hot path. A `false` result does NOT mean the cache is empty or\n * that `isEntitled()` will return false — it only means the data is\n * past its refresh hint. See `needsRefresh()` for the inverse.\n */\n isFresh(customerId: string): boolean {\n const entry = this.byCustomer.get(customerId);\n return Boolean(entry && Date.now() <= entry.refreshDueAt);\n }\n\n /**\n * Whether the customer should be re-fetched: either there is no\n * cached entry, or the entry is past its `ttlMs` refresh hint, or the\n * most recent refresh attempt for them failed (retry it).\n *\n * This is purely advisory — `getEntitlements()` decides when to act\n * on it. It NEVER gates `isEntitled()`, which serves last-known-good\n * regardless.\n */\n needsRefresh(customerId: string): boolean {\n const entry = this.byCustomer.get(customerId);\n if (!entry) return true;\n // A non-zero failure stamp means a refresh failed since the last\n // success (setForCustomer zeroes it) — retry it.\n if (entry.refreshFailedAt > 0) return true;\n return Date.now() > entry.refreshDueAt;\n }\n\n /**\n * Replace (or insert) the cache entry for a customer with a fresh\n * server response. Sets `refreshDueAt` to `now + ttlMs` and CLEARS\n * any failed-refresh marker — a success ends the stale state.\n *\n * Called ONLY after a SUCCESSFUL server read — a failed fetch is\n * routed to `markRefreshFailed` instead and never reaches here, so\n * last-known-good is preserved through an outage.\n *\n * Re-inserting an existing customerId \"touches\" it — the entry moves\n * to the end of insertion order (Map semantics) so it's treated as\n * most-recently-used for LRU eviction. Fires listeners.\n */\n setForCustomer(customerId: string, entitlements: PublicEntitlement[]): void {\n const now = Date.now();\n // Delete-and-reinsert so the entry's insertion-order position\n // moves to the end (LRU \"touch\"). JS Maps iterate in insertion\n // order, so this is what gives us LRU eviction semantics.\n this.byCustomer.delete(customerId);\n this.byCustomer.set(customerId, {\n all: entitlements.slice(),\n refreshDueAt: now + this.ttlMs,\n populatedAt: now,\n refreshFailedAt: 0,\n });\n // Enforce max-customers cap by evicting the oldest entries (head\n // of insertion order = least recently used).\n while (this.byCustomer.size > this.maxCustomers) {\n const oldestKey = this.byCustomer.keys().next().value;\n if (oldestKey === undefined) break;\n this.byCustomer.delete(oldestKey);\n this.evicted += 1;\n }\n this.notify(customerId, entitlements);\n }\n\n /**\n * Record that a refresh attempt for a customer FAILED (Crossdeck\n * unreachable / transient error). `getEntitlements()` calls this in\n * its catch path.\n *\n * It does NOT touch the customer's cached entitlements — last-known-\n * good keeps serving — it only stamps `refreshFailedAt` so the\n * customer shows up as stale in `diagnostics()` rather than the\n * staleness being a silent unbounded window.\n *\n * If the customer has no entry yet (a genuine cold miss whose first\n * fetch failed) a stub entry with no entitlements is created purely\n * to carry the failed-refresh marker — so \"we tried and Crossdeck was\n * down\" is observable even before any successful warm. The stub holds\n * an empty entitlement set, so `isEntitled()` still correctly returns\n * false for it; there is genuinely nothing to serve.\n */\n markRefreshFailed(customerId: string): void {\n const now = Date.now();\n const entry = this.byCustomer.get(customerId);\n if (entry) {\n entry.refreshFailedAt = now;\n return;\n }\n // Cold miss whose first refresh failed — create a marker-only stub.\n // Not an LRU \"touch\" of real data, but it IS an entry, so it counts\n // toward the cap; eviction order treats it like any other.\n this.byCustomer.set(customerId, {\n all: [],\n refreshDueAt: now + this.ttlMs,\n populatedAt: 0,\n refreshFailedAt: now,\n });\n while (this.byCustomer.size > this.maxCustomers) {\n const oldestKey = this.byCustomer.keys().next().value;\n if (oldestKey === undefined) break;\n this.byCustomer.delete(oldestKey);\n this.evicted += 1;\n }\n }\n\n /**\n * Whether a customer is knowingly serving older-than-trustworthy\n * data. True when the most recent refresh ATTEMPT for them failed\n * (Crossdeck unreachable since the last success — the outage case,\n * distinct from a benign idle customer simply past `ttlMs`), OR when\n * their last-known-good has aged past `staleAfterMs`.\n *\n * `isStale` NEVER changes what `isEntitled()` returns — the cache\n * still serves last-known-good. It exists so the staleness is\n * observable via `diagnostics()` instead of an unbounded silent\n * window where a revoked customer (event-based revoke, no\n * `validUntil`) holds access with nobody able to see it.\n *\n * Returns false for an unknown customer — nothing cached, nothing\n * stale.\n */\n isStale(customerId: string): boolean {\n const entry = this.byCustomer.get(customerId);\n if (!entry) return false;\n return this.entryIsStale(entry);\n }\n\n /** Epoch ms of a customer's last failed refresh, or 0 if none / unknown. */\n refreshFailedAt(customerId: string): number {\n return this.byCustomer.get(customerId)?.refreshFailedAt ?? 0;\n }\n\n /**\n * Drop a single customer's entry. Fires listeners with an empty\n * list so subscribers know that customer's cache is gone.\n */\n clearCustomer(customerId: string): void {\n if (!this.byCustomer.delete(customerId)) return;\n this.notify(customerId, []);\n }\n\n /** Wipe the whole cache. Fires listeners for each customer that had a cached entry. */\n clear(): void {\n const customers = [...this.byCustomer.keys()];\n this.byCustomer.clear();\n for (const id of customers) this.notify(id, []);\n }\n\n /**\n * Subscribe to cache mutations. Returns an unsubscribe function.\n * Listener is invoked AFTER each `setForCustomer` / `clearCustomer`\n * / `clear` call with the affected customer ID + fresh entitlements\n * snapshot. NOT fired on TTL expiry (passive eviction is not a\n * mutation by design).\n *\n * Listener errors are swallowed — a buggy consumer must not crash\n * the SDK or other listeners. The error count is surfaced via\n * `listenerErrors`.\n *\n * Returned unsubscribe is idempotent.\n */\n subscribe(listener: EntitlementsListener): () => void {\n this.listeners.add(listener);\n let unsubscribed = false;\n return () => {\n if (unsubscribed) return;\n unsubscribed = true;\n this.listeners.delete(listener);\n };\n }\n\n // ---------- diagnostics ----------\n\n /** Total customers currently cached (counts expired entries too — eviction is lazy). */\n get customerCount(): number {\n return this.byCustomer.size;\n }\n\n /** Most-recent populatedAt across all entries, or 0 if cache empty. */\n get lastUpdated(): number {\n let max = 0;\n for (const entry of this.byCustomer.values()) {\n if (entry.populatedAt > max) max = entry.populatedAt;\n }\n return max;\n }\n\n /** Configured TTL in ms. */\n get ttl(): number {\n return this.ttlMs;\n }\n\n /** Cumulative count of listener invocations that threw. Surfaced in `diagnostics()`. */\n get listenerErrors(): number {\n return this.listenerErrorCount;\n }\n\n /** Cumulative count of entries evicted by the max-customers cap. */\n get evictedCount(): number {\n return this.evicted;\n }\n\n /** Configured max-customers cap. */\n get maxSize(): number {\n return this.maxCustomers;\n }\n\n /** Configured staleness window in ms. */\n get staleWindowMs(): number {\n return this.staleAfterMs;\n }\n\n /**\n * Count of cached customers currently flagged stale — most recent\n * refresh failed, or data aged past `staleAfterMs`. The cache keeps\n * serving last-known-good for them; this is the observability number\n * `diagnostics()` surfaces.\n */\n get staleCustomerCount(): number {\n let count = 0;\n for (const entry of this.byCustomer.values()) {\n if (this.entryIsStale(entry)) count += 1;\n }\n return count;\n }\n\n /** Whether ANY cached customer is currently stale. */\n get isAnyStale(): boolean {\n for (const entry of this.byCustomer.values()) {\n if (this.entryIsStale(entry)) return true;\n }\n return false;\n }\n\n /**\n * Most recent failed-refresh timestamp across all customers (epoch\n * ms), or 0 if every cached customer's last refresh succeeded.\n */\n get lastRefreshFailedAt(): number {\n let max = 0;\n for (const entry of this.byCustomer.values()) {\n if (entry.refreshFailedAt > max) max = entry.refreshFailedAt;\n }\n return max;\n }\n\n // ---------- internals ----------\n\n /**\n * Stale iff the entry's most recent refresh attempt failed, OR its\n * last-known-good has aged past `staleAfterMs`.\n *\n * `refreshFailedAt` is non-zero ONLY between a failed refresh and the\n * next successful one (`setForCustomer` zeroes it), so `> 0` alone\n * means \"a failure occurred since the last success\" — no need to\n * compare against `populatedAt`, which would mis-fire when a failure\n * and a populate land in the same millisecond. A marker-only stub\n * (populatedAt 0, failure stamped) is stale via this first clause.\n */\n private entryIsStale(entry: CacheEntry): boolean {\n if (entry.refreshFailedAt > 0) return true;\n return (\n entry.populatedAt > 0 &&\n Date.now() - entry.populatedAt > this.staleAfterMs\n );\n }\n\n private notify(customerId: string, snapshot: PublicEntitlement[]): void {\n if (this.listeners.size === 0) return;\n const snap = snapshot.slice();\n // Iterate over a snapshot of the listener set so a listener that\n // unsubscribes itself during dispatch doesn't break iteration.\n const listenersSnapshot = [...this.listeners];\n for (const listener of listenersSnapshot) {\n try {\n listener(customerId, snap);\n } catch {\n this.listenerErrorCount += 1;\n }\n }\n }\n}\n","/**\n * Debug signal vocabulary — NorthStar §16, Node port of\n * `@cross-deck/web/src/debug.ts`.\n *\n * The SDK speaks a small fixed vocabulary of signals so the dashboard's\n * onboarding checklist and the developer's console output both speak\n * the same words. When `debug: true` is set in `CrossdeckServerOptions`,\n * the signals are also logged to `console.info` so a developer doing\n * copy-paste integration sees actionable feedback live.\n *\n * Signal names are STABLE — adding new ones is fine, renaming is a\n * breaking change because the dashboard onboarding step keys off them.\n *\n * Node-specific additions beyond web's vocabulary:\n * - `sdk.flush_on_exit_started` / `sdk.flush_on_exit_completed`\n * - `sdk.webhook_verified`\n * - `sdk.runtime_detected`\n * - `sdk.entitlement_cache_warm`\n * - `sdk.entitlement_cache_stale`\n * - `sdk.entitlement_store_recovered`\n * - `sdk.no_durable_store`\n * - `sdk.super_property_registered`\n */\n\nexport type DebugSignal =\n | \"sdk.configured\"\n | \"sdk.first_event_sent\"\n | \"sdk.invalid_key\"\n | \"sdk.no_identity\"\n | \"sdk.entitlement_cache_used\"\n | \"sdk.entitlement_cache_warm\"\n | \"sdk.entitlement_cache_stale\"\n | \"sdk.entitlement_store_recovered\"\n | \"sdk.no_durable_store\"\n | \"sdk.purchase_evidence_sent\"\n | \"sdk.environment_mismatch\"\n | \"sdk.sensitive_property_warning\"\n | \"sdk.property_coerced\"\n | \"sdk.flush_retry_scheduled\"\n // Emitted when the queue drops a batch because the server returned\n // a permanent 4xx (key revoked, malformed batch, etc.). Always loud,\n // regardless of debug mode — see the console.error in\n // crossdeck-server.ts.\n | \"sdk.flush_permanent_failure\"\n | \"sdk.flush_on_exit_started\"\n | \"sdk.flush_on_exit_completed\"\n | \"sdk.webhook_verified\"\n | \"sdk.runtime_detected\"\n | \"sdk.super_property_registered\"\n | \"sdk.boot_heartbeat_failed\";\n\nexport interface DebugContext {\n [key: string]: unknown;\n}\n\n/**\n * Names that almost always indicate PII or secret data. Used by\n * `track()` to warn the developer when a property key looks dangerous.\n * Per NorthStar §15 these are reject/warn-on-sight values; we warn\n * rather than reject because the developer might genuinely want a\n * property called e.g. \"tokens_remaining\".\n */\nconst SENSITIVE_KEY_PATTERNS: readonly RegExp[] = [\n /^email$/i,\n /^password$/i,\n /^token$/i,\n /^secret$/i,\n /^card$/i,\n /^phone$/i,\n /password/i,\n /credit_?card/i,\n];\n\nexport function findSensitivePropertyKeys(\n properties: Record<string, unknown> | undefined,\n): string[] {\n if (!properties) return [];\n const hits: string[] = [];\n for (const k of Object.keys(properties)) {\n if (SENSITIVE_KEY_PATTERNS.some((re) => re.test(k))) hits.push(k);\n }\n return hits;\n}\n\nexport interface DebugLogger {\n enabled: boolean;\n emit(signal: DebugSignal, message: string, context?: DebugContext): void;\n}\n\nconst ONCE_SIGNALS = new Set<DebugSignal>([\n \"sdk.configured\",\n \"sdk.first_event_sent\",\n \"sdk.environment_mismatch\",\n \"sdk.runtime_detected\",\n]);\n\n/**\n * Default debug logger. Writes to `console.info` with a\n * `[crossdeck:<signal>]` prefix so a developer grepping their logs can\n * find SDK signals quickly. Inactive when `enabled === false` — the\n * SDK constructs the logger regardless so a runtime `setDebugMode(true)`\n * doesn't require re-wiring.\n *\n * One-shot signals (sdk.configured, sdk.first_event_sent,\n * sdk.environment_mismatch, sdk.runtime_detected) deduplicate within\n * a process lifetime so a chatty app doesn't spam the console with\n * the same message.\n */\nexport class ConsoleDebugLogger implements DebugLogger {\n enabled = false;\n private seen = new Set<DebugSignal>();\n\n emit(signal: DebugSignal, message: string, context?: DebugContext): void {\n if (!this.enabled) return;\n if (ONCE_SIGNALS.has(signal)) {\n if (this.seen.has(signal)) return;\n this.seen.add(signal);\n }\n const ctx = context ? ` ${safeJson(context)}` : \"\";\n // eslint-disable-next-line no-console\n console.info(`[crossdeck:${signal}] ${message}${ctx}`);\n }\n}\n\n/**\n * No-op logger for tests + callers that want the SDK to be 100% silent.\n * Constructed when `debug` is unset and no custom logger is supplied;\n * the SDK calls `emit()` on every signal regardless of the runtime\n * `enabled` state, so a permanently-off logger keeps the hot path\n * branch-free.\n */\nexport class NullDebugLogger implements DebugLogger {\n enabled = false;\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n emit(_signal: DebugSignal, _message: string, _context?: DebugContext): void {\n /* no-op */\n }\n}\n\nfunction safeJson(obj: unknown): string {\n try {\n return JSON.stringify(obj);\n } catch {\n return \"[unserialisable context]\";\n }\n}\n","/**\n * Machine-readable index of every error code `@cross-deck/node` can\n * throw, with a short description and a hint on what action to take.\n * Mirrors `@cross-deck/web/src/error-codes.ts`.\n *\n * Stripe publishes the same surface at stripe.com/docs/error-codes;\n * developers love it because every code has a canonical \"what does\n * this mean / what should I do\" answer.\n *\n * Differences from web:\n * - Drops browser-only config codes (`invalid_public_key`,\n * `missing_app_id`, `invalid_environment`, `environment_mismatch`,\n * `not_initialized`).\n * - Adds `invalid_secret_key` (Node SDK takes a secret key, not a\n * publishable key + env declaration).\n * - Adds Node-lifecycle codes (`flush_on_exit_failed`,\n * `webhook_invalid_signature`, `webhook_replay_window_exceeded`,\n * `webhook_missing_secret`).\n *\n * Adding a new error code:\n * 1. Throw it as `CrossdeckError.code` in the call site.\n * 2. Add an entry here so the dashboard + AI assistants can render\n * the canonical fix.\n *\n * Keep entries terse — consumers surface this in tooltips and\n * automated tickets, not in long-form docs.\n */\n\nexport interface ErrorCodeEntry {\n /** The string thrown as `CrossdeckError.code`. */\n code: string;\n /** Broad category — `CrossdeckError.type`. */\n type:\n | \"authentication_error\"\n | \"permission_error\"\n | \"invalid_request_error\"\n | \"rate_limit_error\"\n | \"internal_error\"\n | \"network_error\"\n | \"configuration_error\";\n /** One-sentence description. Surfaced verbatim in dashboards. */\n description: string;\n /** What the developer should do. Imperative phrasing. */\n resolution: string;\n /** True for codes the SDK can auto-recover from (no developer action). */\n retryable: boolean;\n}\n\n/**\n * Internal source-of-truth with literal types preserved via `as const`.\n * The exported `CROSSDECK_ERROR_CODES` widens to `readonly\n * ErrorCodeEntry[]` for the public surface (callers iterating it\n * shouldn't depend on literal positions), while `CrossdeckErrorCode`\n * below derives the literal union from this constant for type-safe\n * code narrowing.\n */\nconst _CROSSDECK_ERROR_CODES = Object.freeze([\n // ----- Configuration -----\n {\n code: \"invalid_secret_key\",\n type: \"configuration_error\",\n description: \"The secret key passed to new CrossdeckServer({ secretKey }) doesn't start with cd_sk_.\",\n resolution: \"Copy the key from your Crossdeck dashboard → API keys page. Server SDKs use cd_sk_test_… (sandbox) or cd_sk_live_… (production). Never ship this key to a browser.\",\n retryable: false,\n },\n\n // ----- Argument validation -----\n {\n code: \"missing_user_id\",\n type: \"invalid_request_error\",\n description: \"identify() / aliasIdentity() called with an empty userId.\",\n resolution: \"Pass a stable, non-empty user identifier from your auth layer — never a hardcoded placeholder.\",\n retryable: false,\n },\n {\n code: \"missing_anonymous_id\",\n type: \"invalid_request_error\",\n description: \"aliasIdentity() called with an empty anonymousId.\",\n resolution: \"Pass the anonymousId originally minted by the web SDK on this user's device.\",\n retryable: false,\n },\n {\n code: \"missing_customer_id\",\n type: \"invalid_request_error\",\n description: \"An operation that requires a Crossdeck customer ID was called with an empty value.\",\n resolution: \"Pass the customerId returned from a prior identify() / getEntitlements() call.\",\n retryable: false,\n },\n {\n code: \"missing_identity\",\n type: \"invalid_request_error\",\n description: \"An ingest / forget / entitlements call received no identity hints.\",\n resolution: \"Pass at least one of customerId, userId, or anonymousId on the call (or per-event for ingest).\",\n retryable: false,\n },\n {\n code: \"missing_event_name\",\n type: \"invalid_request_error\",\n description: \"track() / ingest() received an event without a name.\",\n resolution: \"Pass a non-empty string as the event name. The wire shape is { name, properties? }.\",\n retryable: false,\n },\n {\n code: \"missing_events\",\n type: \"invalid_request_error\",\n description: \"ingest() received an empty array.\",\n resolution: \"Pass at least one event. Use server.track(event) to send a single event.\",\n retryable: false,\n },\n {\n code: \"missing_event_id\",\n type: \"invalid_request_error\",\n description: \"getAuditEntry() called with an empty eventId.\",\n resolution: \"Pass the eventId from the audit row you want to inspect.\",\n retryable: false,\n },\n {\n code: \"missing_signed_transaction_info\",\n type: \"invalid_request_error\",\n description: \"syncPurchases() called without StoreKit 2 signed transaction info.\",\n resolution: \"Pass the JWS string from Transaction.currentEntitlements / Transaction.updates.\",\n retryable: false,\n },\n {\n code: \"missing_group_type\",\n type: \"invalid_request_error\",\n description: \"group(type, id) called with an empty type.\",\n resolution: \"Pass a non-empty group type (e.g. \\\"org\\\", \\\"team\\\", \\\"plan\\\") as the first argument.\",\n retryable: false,\n },\n {\n code: \"serialization_failed\",\n type: \"invalid_request_error\",\n description: \"An event payload or trait bag could not be JSON-serialised even after sanitisation.\",\n resolution: \"Inspect the payload for non-JSON-friendly values (functions, symbols, deeply circular refs). The SDK's validator drops these by default, so this usually means a bug — file an issue with the payload shape.\",\n retryable: false,\n },\n\n // ----- Network / transport -----\n {\n code: \"fetch_failed\",\n type: \"network_error\",\n description: \"The underlying fetch() call failed (typically a network outage, DNS, or refused connection).\",\n resolution: \"Check the host's outbound network. The SDK retries automatically with exponential backoff + jitter for queued events.\",\n retryable: true,\n },\n {\n code: \"request_timeout\",\n type: \"network_error\",\n description: \"A request was aborted after the configured timeoutMs (default 15s).\",\n resolution: \"Check the host's network. Increase timeoutMs in CrossdeckServer options if you're on a known-slow link.\",\n retryable: true,\n },\n {\n code: \"invalid_json_response\",\n type: \"internal_error\",\n description: \"The server returned a 2xx with an unparseable body.\",\n resolution: \"Likely a transient backend bug. Retry; if it persists, contact support with the requestId.\",\n retryable: true,\n },\n\n // ----- Lifecycle (Node-specific) -----\n {\n code: \"flush_on_exit_failed\",\n type: \"internal_error\",\n description: \"The on-exit drain (beforeExit / SIGTERM / SIGINT) did not complete before flushOnExitTimeoutMs.\",\n resolution: \"Increase flushOnExitTimeoutMs in CrossdeckServer options. Default is 2000ms; serverless runtimes typically allow 5-10s before SIGKILL. If events are dropping silently in production, raise this.\",\n retryable: false,\n },\n\n // ----- Webhook verification (Node-specific) -----\n {\n code: \"webhook_invalid_signature\",\n type: \"authentication_error\",\n description: \"The webhook signature header did not verify against the supplied secret.\",\n resolution: \"Confirm the secret matches the one in your Crossdeck dashboard → Webhooks page. If the request is genuinely from Crossdeck, the secret is wrong, stale, or recently rotated.\",\n retryable: false,\n },\n {\n code: \"webhook_replay_window_exceeded\",\n type: \"authentication_error\",\n description: \"The webhook timestamp is older than the replay-tolerance window (default 5 minutes).\",\n resolution: \"The webhook is either replayed or your receiving clock is wildly skewed. Verify NTP on the receiving host. Increase replayToleranceMs only if you accept the replay-attack risk.\",\n retryable: false,\n },\n {\n code: \"webhook_missing_secret\",\n type: \"configuration_error\",\n description: \"verifyWebhookSignature() was called without a signing secret.\",\n resolution: \"Pass the secret from your Crossdeck dashboard → Webhooks page. Never hardcode in source — read from an env var.\",\n retryable: false,\n },\n] as const);\n\n/**\n * Literal union of every code documented in `CROSSDECK_ERROR_CODES`.\n * Exported so callers can do type-safe code comparisons:\n *\n * if (err.code === \"webhook_invalid_signature\") { ... } // typed\n * if (err.code === \"webook_invalid_signature\") { ... } // TS error\n *\n * Mis-spelling a code at compile time fails to compile — the gap that\n * silently broke v0.1.0 callers checking for non-existent codes.\n */\nexport type CrossdeckErrorCode = (typeof _CROSSDECK_ERROR_CODES)[number][\"code\"];\n\n/** Type guard: narrows a string to the documented literal union. */\nexport function isCrossdeckErrorCode(code: string): code is CrossdeckErrorCode {\n return _CROSSDECK_ERROR_CODES.some((e) => e.code === code);\n}\n\n/**\n * Public catalogue, widened to `readonly ErrorCodeEntry[]` for iteration\n * stability. Callers needing literal types should reach for the\n * `CrossdeckErrorCode` union above instead of indexing into this array.\n */\nexport const CROSSDECK_ERROR_CODES: readonly ErrorCodeEntry[] = _CROSSDECK_ERROR_CODES;\n\n/** Lookup helper — returns the entry matching a CrossdeckError.code, or undefined. */\nexport function getErrorCode(code: string): ErrorCodeEntry | undefined {\n return CROSSDECK_ERROR_CODES.find((e) => e.code === code);\n}\n","/**\n * Webhook signature verification — Stripe pattern.\n *\n * Lets customers verify the events Crossdeck sends to THEM. Table-stakes\n * for any backend SDK (Stripe ships `Stripe.webhooks.constructEvent()`\n * from day one, Svix ships `Webhook.verify()` from day one).\n *\n * Wire format:\n * Header `Crossdeck-Signature: t=<unix-seconds>,v1=<hex>`\n * Where `v1` is HMAC-SHA256(secret, `${t}.${payload}`) — Stripe-compatible.\n *\n * Customers receive a signing secret from the Crossdeck dashboard\n * (one-time reveal at mint time; rotated as needed). Each webhook\n * carries the signature header above. The customer's handler:\n *\n * import { verifyWebhookSignature } from \"@cross-deck/node\";\n *\n * app.post(\"/crossdeck-webhook\", express.raw({ type: \"application/json\" }), (req, res) => {\n * try {\n * const event = verifyWebhookSignature(\n * req.body.toString(\"utf8\"),\n * req.headers[\"crossdeck-signature\"],\n * process.env.CROSSDECK_WEBHOOK_SECRET,\n * );\n * // event is the parsed JSON payload\n * handleCrossdeckEvent(event);\n * res.sendStatus(200);\n * } catch (err) {\n * res.sendStatus(401);\n * }\n * });\n *\n * The signing scheme is constant-time via `crypto.timingSafeEqual` so\n * a malicious caller can't extract the signature by measuring response\n * timing. Replay defence: timestamps older than `replayToleranceMs`\n * (default 5 min) are rejected — required because HMAC-SHA256 is\n * stateless and would otherwise allow an attacker to replay an old\n * webhook indefinitely.\n *\n * Supports multiple secrets for rotation: pass an array; the helper\n * tries each, accepts on the first match. Lets customers rotate the\n * dashboard secret without dropping in-flight webhooks.\n */\n\nimport { createHmac, timingSafeEqual } from \"node:crypto\";\n\nimport { CrossdeckError } from \"./errors\";\n\nexport interface VerifyWebhookOptions {\n /**\n * Maximum age of the webhook timestamp in milliseconds. Default\n * 5 minutes (5 * 60 * 1000). Anything older than this is rejected\n * as a replay. Pass 0 to disable the replay window (NOT recommended\n * — accept the trade-off only if you have a separate replay defence).\n */\n replayToleranceMs?: number;\n /**\n * Override the current time. Tests use this to verify timestamp\n * handling deterministically. Defaults to `Date.now()`.\n */\n now?: () => number;\n}\n\nconst DEFAULT_REPLAY_TOLERANCE_MS = 5 * 60 * 1000;\n\n/**\n * Verify a Crossdeck-signed webhook. Returns the parsed JSON payload\n * on success. Throws `CrossdeckError` on:\n * - missing / malformed signature header (`webhook_invalid_signature`)\n * - missing secret (`webhook_missing_secret`)\n * - timestamp outside replay window (`webhook_replay_window_exceeded`)\n * - HMAC mismatch (`webhook_invalid_signature`)\n * - non-JSON payload (`webhook_invalid_signature` — same code because\n * a tampered payload that breaks JSON parses as invalid)\n *\n * `secret` accepts a single string or an array of strings (for\n * rotation). Any one match is sufficient.\n */\nexport function verifyWebhookSignature(\n payload: string,\n signatureHeader: string | string[] | undefined,\n secret: string | string[] | undefined,\n options: VerifyWebhookOptions = {},\n): unknown {\n const secrets = normaliseSecrets(secret);\n if (secrets.length === 0) {\n throw new CrossdeckError({\n type: \"configuration_error\",\n code: \"webhook_missing_secret\",\n message:\n \"verifyWebhookSignature requires a non-empty secret. Read it from process.env.CROSSDECK_WEBHOOK_SECRET — never hardcode in source.\",\n });\n }\n\n const header = normaliseHeader(signatureHeader);\n const parsed = parseSignatureHeader(header);\n if (!parsed) {\n throw new CrossdeckError({\n type: \"authentication_error\",\n code: \"webhook_invalid_signature\",\n message:\n \"Webhook signature header is missing or malformed. Expected 'Crossdeck-Signature: t=<unix>,v1=<hex>'.\",\n });\n }\n\n const tolerance = options.replayToleranceMs ?? DEFAULT_REPLAY_TOLERANCE_MS;\n if (tolerance > 0) {\n const now = (options.now ?? Date.now)();\n const timestampMs = parsed.timestampSec * 1000;\n const drift = Math.abs(now - timestampMs);\n if (drift > tolerance) {\n throw new CrossdeckError({\n type: \"authentication_error\",\n code: \"webhook_replay_window_exceeded\",\n message: `Webhook timestamp is ${drift}ms outside the ${tolerance}ms replay-tolerance window. Either the request is replayed or the receiving clock is skewed — verify NTP on the host.`,\n });\n }\n }\n\n const signedPayload = `${parsed.timestampSec}.${payload}`;\n const expectedBuf = Buffer.from(parsed.signature, \"hex\");\n if (expectedBuf.length === 0) {\n throw new CrossdeckError({\n type: \"authentication_error\",\n code: \"webhook_invalid_signature\",\n message: \"Webhook signature is not a valid hex string.\",\n });\n }\n\n const anyMatch = secrets.some((s) => {\n const computed = createHmac(\"sha256\", s).update(signedPayload).digest();\n return computed.length === expectedBuf.length && timingSafeEqual(computed, expectedBuf);\n });\n if (!anyMatch) {\n throw new CrossdeckError({\n type: \"authentication_error\",\n code: \"webhook_invalid_signature\",\n message:\n \"Webhook signature did not verify. Confirm the secret matches your Crossdeck dashboard → Webhooks page (and that you're not on a stale rotation).\",\n });\n }\n\n try {\n return JSON.parse(payload);\n } catch {\n throw new CrossdeckError({\n type: \"authentication_error\",\n code: \"webhook_invalid_signature\",\n message:\n \"Webhook signature verified but the payload is not valid JSON. Either the payload was tampered with after signing, or the webhook source is misconfigured.\",\n });\n }\n}\n\n/**\n * Pure-function signing — mirror of what the Crossdeck backend does\n * when sending a webhook. Exported so customers building their own\n * test fixtures (a service that sends Crossdeck-signed webhooks to\n * their own test harness) can re-use the canonical signing scheme\n * instead of re-implementing it.\n *\n * const ts = Math.floor(Date.now() / 1000);\n * const sig = signWebhookPayload(payload, secret, ts);\n * const header = `t=${ts},v1=${sig}`;\n *\n * NOT marked as a security primitive for general HMAC — use\n * `node:crypto` directly for that. This is only the\n * Crossdeck-signature shape.\n */\nexport function signWebhookPayload(payload: string, secret: string, timestampSec: number): string {\n return createHmac(\"sha256\", secret)\n .update(`${timestampSec}.${payload}`)\n .digest(\"hex\");\n}\n\ninterface ParsedSignature {\n timestampSec: number;\n signature: string;\n}\n\nfunction parseSignatureHeader(header: string | null): ParsedSignature | null {\n if (!header) return null;\n // Format: \"t=<unix>,v1=<hex>\". Order-independent, ignore unknown keys.\n let timestampSec: number | null = null;\n let signature: string | null = null;\n for (const part of header.split(\",\")) {\n const eqIdx = part.indexOf(\"=\");\n if (eqIdx <= 0) continue;\n const key = part.slice(0, eqIdx).trim();\n const value = part.slice(eqIdx + 1).trim();\n if (key === \"t\") {\n const n = Number(value);\n if (Number.isFinite(n) && n > 0) timestampSec = Math.floor(n);\n } else if (key === \"v1\") {\n if (/^[0-9a-fA-F]+$/.test(value)) signature = value.toLowerCase();\n }\n }\n if (timestampSec === null || signature === null) return null;\n return { timestampSec, signature };\n}\n\nfunction normaliseHeader(input: string | string[] | undefined): string | null {\n if (input === undefined) return null;\n if (Array.isArray(input)) return input[0] ?? null;\n return input;\n}\n\nfunction normaliseSecrets(input: string | string[] | undefined): string[] {\n if (input === undefined || input === null) return [];\n const arr = Array.isArray(input) ? input : [input];\n return arr.filter((s) => typeof s === \"string\" && s.length > 0);\n}\n","/**\n * PII scrub utilities — Node port of `@cross-deck/web/src/consent.ts`'s\n * regex-based defence layer.\n *\n * **What's NOT here (intentionally)**: the `ConsentManager` class. That's\n * a browser/end-user UX surface (DNT detection, per-dimension consent\n * gating). On the server side, the customer's user already accepted\n * or declined consent in their client; the API caller decides what\n * to forward. Shipping `ConsentManager` here would imply server-side\n * gating that doesn't match the trust model.\n *\n * **What IS here**: opt-in utilities customers can use to scrub\n * email-shaped and card-number-shaped substrings out of event\n * properties before forwarding to Crossdeck. Stripe-grade defence in\n * depth, applied at the caller's discretion:\n *\n * import { scrubPiiFromProperties } from \"@cross-deck/node\";\n *\n * server.track({\n * name: \"checkout.started\",\n * developerUserId: userId,\n * properties: scrubPiiFromProperties({\n * url: req.url, // might contain \"/users/wes@…/\" — gets <email>\n * lastError: e.message, // might contain card numbers\n * }),\n * });\n */\n\n/**\n * Email-shaped pattern. Restrictive enough to match the practical\n * 99% of emails (RFC 5322's \"obs-local-part\" common case) without\n * false positives. We deliberately don't try to match every legal\n * email; the goal is \"if it looks like an email, scrub it.\"\n */\nconst EMAIL_PATTERN =\n /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/g;\n\n/**\n * Card-number-shaped pattern. Matches sequences of 13-19 digits that\n * could be split by space or hyphen — the format every payment form\n * accepts. We don't validate Luhn; this is best-effort scrubbing,\n * not card-data tokenisation. If you're handling actual PAN data you\n * should not be passing it through analytics in the first place.\n *\n * Anchored on a digit at both ends so trailing separators (space /\n * hyphen) aren't pulled into the match — otherwise\n * \"4242 4242 4242 4242 today\" would scrub as \"<card>today\" instead of\n * \"<card> today\".\n */\nconst CARD_PATTERN = /\\b\\d(?:[ -]?\\d){12,18}\\b/g;\n\n// Sentinel tokens — aligned with backend/src/api/lib/scrub.ts which uses\n// <email>, <card>, <uuid>, <cdcust>, <crossdeck_secret_key>, <aws_access_key>.\n// Mismatched tokens between the SDK scrub and the backend's defence-in-\n// depth scrub would split dashboard aggregation (the same event arriving\n// from two paths would carry two different sentinels).\nconst REPLACEMENT_EMAIL = \"<email>\";\nconst REPLACEMENT_CARD = \"<card>\";\n\n/**\n * Scrub a single string value: replace email-shaped substrings with\n * `<email>` and card-number-shaped substrings with `<card>`. Returns\n * the original string (===) when nothing matched.\n *\n * Implementation note: we call `.replace()` unconditionally rather than\n * gating on `.test()`. The /g regexes are module-level so `.test()`\n * carries `lastIndex` state between calls — a prior match leaves\n * `lastIndex` mid-string and the next `.test()` can falsely return\n * false on a string that DOES match. `.replace(/g)` always scans the\n * full string regardless of `lastIndex`, so dropping the test-guard\n * removes the sharp edge at zero cost (when nothing matches, replace\n * returns the same `(===)` string).\n */\nexport function scrubPii(value: string): string {\n if (!value) return value;\n return value\n .replace(EMAIL_PATTERN, REPLACEMENT_EMAIL)\n .replace(CARD_PATTERN, REPLACEMENT_CARD);\n}\n\n/**\n * Walk a property bag and replace PII-shaped strings in place. Returns\n * a new object with strings scrubbed; non-string values pass through\n * unchanged.\n *\n * Defensive copy — the input is never altered. Caller can pass the\n * result straight to `server.track()`.\n *\n * Recursive: nested objects + arrays are walked. Functions, symbols,\n * Dates, etc. pass through untouched (those are the\n * `validateEventProperties` sanitiser's job — this is just the\n * PII regex pass).\n */\nexport function scrubPiiFromProperties(\n properties: Record<string, unknown>,\n): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const k of Object.keys(properties)) {\n out[k] = scrubValue(properties[k]);\n }\n return out;\n}\n\nfunction scrubValue(v: unknown): unknown {\n if (typeof v === \"string\") return scrubPii(v);\n if (Array.isArray(v)) return v.map(scrubValue);\n if (v && typeof v === \"object\" && v.constructor === Object) {\n // Plain objects only — Date, Map, Set, Error are left untouched\n // (the property validator already handles those). This avoids\n // accidentally mutating an Error's `message` and confusing the\n // downstream error reporting layer.\n return scrubPiiFromProperties(v as Record<string, unknown>);\n }\n return v;\n}\n"],"mappings":"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","names":["safeStringify","osPlatform","safeStringify","osHostname","osPlatform","osRelease","DEFAULT_TIMEOUT_MS"]}