@layers/amba-react-native 4.3.0 → 4.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.
- package/README.md +2 -0
- package/dist/index.cjs +56 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +56 -2
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["cachedStorage: AsyncStorageStatic | null | undefined","prefix: string","timer: ReturnType<typeof setTimeout> | undefined","SESSION_HEADER_AUTH_PATHS: ReadonlySet<string>","parts: string[]","h: Record<string, string>","init: RequestInit","response: Response","text: string","parsed: unknown","code: string | undefined","envelopeDetails: unknown","details: unknown","persistence: AsyncStoragePersistence","vault: TokenVault | null","seed: PersistedIdentity","fromVault: TokenSlice | null","parsed: Partial<PersistedIdentity> | null","stored","stored: TokenSlice | null","seq","recordSeq","payload: PersistedIdentity","slice: TokenSlice","TOKEN_STORAGE_MODES: readonly TokenStorageMode[]","key: string","storage: SecureTokenStorage","cachedKeychain: SecureTokenStorage | null | undefined","full: OutboxOp","replayError: unknown","store: KvStore","storageKey: string","options: EventBatchOptions","identity: () => {\n userId: string | null;\n anonymousId: string;\n sessionToken: string | null;\n }","postBatch: (\n body: { events: ReturnType<typeof wire>[] },\n sessionToken: string,\n ) => Promise<BatchResponse>","postSingle: (\n body: ReturnType<typeof wire>,\n sessionToken: string,\n ) => Promise<unknown>","item: QueuedEvent","evicted: QueuedEvent[]","dropped: QueuedEvent[]","next: QueuedEvent[]","delay","selected: QueuedEvent[]","out: ScheduledLocalNotification[]","maxReported: number","slot: Slot","store: RemoteValuesStore","key: string","defaults: Record<string, unknown>","currentUser: () => string | null","raw: string | null","slots: Record<string, Slot>","target: string | undefined","oldest: string | null","DEVICE_OS_VALUES: readonly DeviceOs[]","ctx: RealtimeContext","name: string","conversationId: string","closeActive: (() => void) | null","settleActive: (() => void) | null","idx: number","dataLines: string[]","state: RuntimeState | null","configuring: Promise<void> | null","configureChain: Promise<void>","_pendingConfigDefaults: Record<string, unknown> | null","event: AuthStateChangeEvent","exchange","user: User","result: AuthResult","exchange: RefreshExchange","fence: CommitFence","body: Record<string, unknown>","inheritedRefresh: RefreshExchange | null","identity: IdentityStore","remoteValues: RemoteValuesCache","nextState: RuntimeState","query: Record<string, string>","query: Record<string, string> | undefined","timeoutMs: number","requestBody: string | undefined","parsed: { data?: Record<string, unknown> }","version: string | null","query: Record<string, number | undefined>"],"sources":["../src/async-storage-adapter.ts","../src/error.ts","../src/request-deadline.ts","../src/http-client.ts","../src/uuid.ts","../src/identity.ts","../src/token-storage.ts","../src/path-segment.ts","../src/resilience.ts","../src/event-batch-queue.ts","../src/push-rotation.ts","../src/local-notifications.ts","../src/experiment-reporter.ts","../src/remote-values.ts","../src/client-info.ts","../src/realtime.ts","../src/index.ts"],"sourcesContent":["/**\n * AsyncStorage-backed identity persistence for React Native.\n *\n * Bridges `@react-native-async-storage/async-storage` (peer-dep, installed\n * by the host app) to the SDK runtime. Reads/writes go through a\n * per-prefix namespace so different amba projects can coexist in the\n * same app.\n *\n * Identity records stored under `${prefix}:identity` survive app restarts.\n */\n\n// Late-imported to avoid bundling the peer-dep — host app installs it.\ntype AsyncStorageStatic = {\n getItem(key: string): Promise<string | null>;\n setItem(key: string, value: string): Promise<void>;\n removeItem(key: string): Promise<void>;\n};\n\n// Three-state cache:\n// - `undefined`: never probed.\n// - `null`: probed and unavailable (peer-dep missing OR shape check\n// failed). Future calls short-circuit to memory-only;\n// re-importing on every persistence op was a real perf\n// regression under the docs-react-native smoke harness.\n// - `<obj>`: probed and usable.\n//\n// The negative-result memoization is essential: a single Amba.* call can\n// trigger 3–5 identity reads (session token, refresh token, anonymous id,\n// etc.); without it each one re-runs `await import(...)` and the shape\n// probe.\nlet cachedStorage: AsyncStorageStatic | null | undefined = undefined;\n\n/**\n * Late-imports `@react-native-async-storage/async-storage` and returns\n * it ONLY if the resolved module exposes the three methods we use\n * (`getItem` / `setItem` / `removeItem`). Otherwise returns null and\n * the persistence layer falls back to memory-only.\n *\n * Why the shape check: pnpm with auto-install-peers (default since\n * pnpm 7) installs declared peer deps even in non-RN scratch\n * directories (e.g., this SDK's docs-smoke harness). The installed\n * package's Node entry point doesn't expose AsyncStorage's methods\n * — it's a React Native native module — so the `await import(...)`\n * succeeds (no catch fires) but the returned object has no\n * `getItem`. Calling it throws `TypeError: storage.getItem is not a\n * function` from inside `read()` (verified at PR #246 docs-react-native\n * / docs-expo run on 547a1ff4). Checking method presence here turns\n * that into the same fall-through as \"peer dep not installed at all.\"\n */\nfunction isAsyncStorageLike(mod: unknown): mod is AsyncStorageStatic {\n return (\n typeof mod === \"object\" &&\n mod !== null &&\n typeof (mod as AsyncStorageStatic).getItem === \"function\" &&\n typeof (mod as AsyncStorageStatic).setItem === \"function\" &&\n typeof (mod as AsyncStorageStatic).removeItem === \"function\"\n );\n}\n\nasync function getStorage(): Promise<AsyncStorageStatic | null> {\n if (cachedStorage !== undefined) return cachedStorage;\n try {\n const mod = await import(\"@react-native-async-storage/async-storage\");\n const candidate = (mod as { default?: unknown }).default ?? mod;\n if (!isAsyncStorageLike(candidate)) {\n // Module loaded but its shape doesn't match AsyncStorage's\n // contract. Memoize the negative result so subsequent calls\n // don't re-run the dynamic import + shape probe.\n cachedStorage = null;\n return null;\n }\n cachedStorage = candidate;\n return cachedStorage;\n } catch {\n // Host app didn't install the peer dep — fall back to in-memory.\n // amba will still work but identity won't survive app restart.\n // Memoize so future calls short-circuit.\n cachedStorage = null;\n return null;\n }\n}\n\n/**\n * Test-only reset hook. The module-level `cachedStorage` is a\n * singleton; tests that swap the mock between cases need a way to\n * invalidate it so the next call re-probes.\n */\nexport function _resetStorageCacheForTests(): void {\n cachedStorage = undefined;\n}\n\nexport class AsyncStoragePersistence {\n private memoryCache = new Map<string, string>();\n\n constructor(readonly prefix: string) {}\n\n /** Batching promises durable enqueue, so memory-only fallback is invalid. */\n async assertDurable(): Promise<void> {\n if (!(await getStorage())) {\n throw new Error(\n \"Amba event batching requires @react-native-async-storage/async-storage\",\n );\n }\n }\n\n async hydrate(): Promise<void> {\n // Future: when the SDK runtime gains a JS-side persistence bridge,\n // this will pre-load the identity record into memory so the runtime\n // can read it synchronously at init.\n }\n\n async read(key: string): Promise<string | null> {\n const fullKey = `${this.prefix}:${key}`;\n if (this.memoryCache.has(fullKey))\n return this.memoryCache.get(fullKey) ?? null;\n const storage = await getStorage();\n if (!storage) return null;\n const v = await storage.getItem(fullKey);\n if (v !== null) this.memoryCache.set(fullKey, v);\n return v;\n }\n\n /**\n * Read `key` from storage again, past the in-memory copy, and keep what\n * it holds now. For a record whose first read came back incomplete (see\n * `IdentityStore.recordStillUnreadable`).\n */\n async reread(key: string): Promise<string | null> {\n const fullKey = `${this.prefix}:${key}`;\n const storage = await getStorage();\n if (!storage) return this.memoryCache.get(fullKey) ?? null;\n const v = await storage.getItem(fullKey);\n if (v !== null) this.memoryCache.set(fullKey, v);\n else this.memoryCache.delete(fullKey);\n return v;\n }\n\n async write(key: string, value: string): Promise<void> {\n const fullKey = `${this.prefix}:${key}`;\n const storage = await getStorage();\n if (storage) await storage.setItem(fullKey, value);\n // Cache only after durable storage succeeds. A rejected AsyncStorage\n // write must not make a queued event look persisted on subsequent reads.\n this.memoryCache.set(fullKey, value);\n }\n\n async delete(key: string): Promise<void> {\n const fullKey = `${this.prefix}:${key}`;\n const storage = await getStorage();\n if (storage) await storage.removeItem(fullKey);\n this.memoryCache.delete(fullKey);\n }\n}\n","/**\n * Typed error class for the amba SDK + helper that converts arbitrary\n * thrown values (native Error, plain objects, runtime-emitted errors)\n * into a stable `AmbaApiError` instance.\n *\n * Callers can `instanceof`-narrow safely, branch on `.code`, and pull\n * extra payload from `.details` (raw HTTP body, validation field paths,\n * etc.). The wrapper methods on `Amba` wrap every async call in\n * `toAmbaApiError`, so consumers see one error shape regardless of\n * which layer (network, runtime, validation) failed.\n */\n\n/**\n * Stable error codes surfaced by the SDK. Strings rather than an enum\n * so customers can extend with custom codes by simply throwing\n * `new AmbaApiError(\"MY_CODE\", \"...\")` without coordinating with this\n * package's TypeScript types.\n */\nexport type AmbaApiErrorCode =\n | \"UNAUTHORIZED\"\n | \"FORBIDDEN\"\n | \"NOT_FOUND\"\n | \"CONFLICT\"\n | \"RATE_LIMITED\"\n | \"VALIDATION_ERROR\"\n | \"NETWORK_ERROR\"\n | \"HTTP_ERROR\"\n | \"CIRCUIT_OPEN\"\n | \"CONSENT_NOT_GRANTED\"\n | \"NOT_INITIALIZED\"\n | \"INVALID_CONFIG\"\n | \"INVALID_ARGUMENT\"\n | \"PUSH_PERMISSION_DENIED\"\n | \"PUSH_REGISTRATION_FAILED\"\n | \"LOCAL_NOTIFICATIONS_UNAVAILABLE\"\n | \"LOCAL_NOTIFICATIONS_PERMISSION_DENIED\"\n | \"NOT_SANDBOX_TIER\"\n // A session with a linked identity ended without a sign-out; the user\n // signs in again (see `AuthStateChangeReason`).\n | \"SIGN_IN_REQUIRED\"\n // A session renewal answered for a different user than the stored one.\n | \"OWNER_MISMATCH\"\n // The stored session can't be read yet (the device may be locked).\n | \"SECURE_STORE_UNAVAILABLE\"\n // The session changed while a call that would have stored one was in\n // flight; the newer session stays.\n | \"SESSION_CHANGED\"\n | \"UNKNOWN_ERROR\"\n | (string & {});\n\nexport class AmbaApiError extends Error {\n readonly code: AmbaApiErrorCode;\n readonly details?: unknown;\n constructor(\n code: AmbaApiErrorCode,\n message: string,\n details?: unknown,\n ) {\n super(message);\n this.name = \"AmbaApiError\";\n this.code = code;\n this.details = details;\n // Restore prototype chain when transpiled to ES5 targets — Error\n // subclasses lose their prototype otherwise and `instanceof` breaks.\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/**\n * Best-effort: pull a stable error code out of an `AmbaError` display\n * string. Used by `toAmbaApiError` when the underlying thrown value is a\n * generic `Error` produced by the SDK core.\n *\n * Keep in sync with the SDK core's `AmbaError` format strings — but the\n * parser falls back to UNKNOWN_ERROR for any pattern it doesn't recognize,\n * so a stale match list isn't a footgun, just a quality-of-life regression.\n */\nfunction codeFromMessage(message: string): AmbaApiErrorCode {\n // Cheapest checks first — most production errors are auth or HTTP.\n if (message.startsWith(\"Unauthorized\")) return \"UNAUTHORIZED\";\n if (message.startsWith(\"Forbidden\")) return \"FORBIDDEN\";\n if (message.startsWith(\"Not found\")) return \"NOT_FOUND\";\n if (message.startsWith(\"Conflict\")) return \"CONFLICT\";\n if (message.startsWith(\"Rate limited\")) return \"RATE_LIMITED\";\n if (message.startsWith(\"Validation error\")) return \"VALIDATION_ERROR\";\n if (message.startsWith(\"Network error\")) return \"NETWORK_ERROR\";\n if (message.startsWith(\"HTTP error\")) return \"HTTP_ERROR\";\n if (message.startsWith(\"Circuit breaker\")) return \"CIRCUIT_OPEN\";\n if (message.startsWith(\"Consent not granted\")) return \"CONSENT_NOT_GRANTED\";\n if (message.includes(\"not initialized\")) return \"NOT_INITIALIZED\";\n if (message.startsWith(\"Invalid configuration\")) return \"INVALID_CONFIG\";\n if (message.startsWith(\"Invalid argument\")) return \"INVALID_ARGUMENT\";\n return \"UNKNOWN_ERROR\";\n}\n\n/**\n * Coerce any thrown value into an `AmbaApiError`. Idempotent — passing\n * an existing `AmbaApiError` returns it unchanged so wrappers can stack\n * without re-wrapping.\n *\n * Heuristics:\n * 1. Existing `AmbaApiError` → returned as-is.\n * 2. Object with `.code` → use that code + `.message` + `.details`.\n * 3. `Error` (e.g. runtime-emitted error) → infer code from message prefix.\n * 4. Anything else → stringify and tag UNKNOWN_ERROR.\n */\nexport function toAmbaApiError(err: unknown): AmbaApiError {\n if (err instanceof AmbaApiError) return err;\n if (err && typeof err === \"object\") {\n const obj = err as { code?: unknown; message?: unknown; details?: unknown };\n if (typeof obj.code === \"string\") {\n return new AmbaApiError(\n obj.code,\n typeof obj.message === \"string\" ? obj.message : \"unknown\",\n obj.details,\n );\n }\n }\n if (err instanceof Error) {\n return new AmbaApiError(codeFromMessage(err.message), err.message);\n }\n return new AmbaApiError(\"UNKNOWN_ERROR\", String(err));\n}\n\n/**\n * Wrap a thunk so any thrown value becomes an `AmbaApiError`. The\n * wrapper methods on `Amba` use this to convert runtime-emitted errors\n * uniformly at the SDK boundary.\n */\nexport async function withAmbaError<T>(thunk: () => Promise<T>): Promise<T> {\n try {\n return await thunk();\n } catch (err) {\n throw toAmbaApiError(err);\n }\n}\n","/**\n * Request deadlines, shared by every JS SDK target (the same file ships in\n * `@layers/amba-web`, `@layers/amba-node` and `@layers/amba-react-native`).\n *\n * Every request the SDK sends runs under a deadline that covers sending it\n * and reading the whole response. When the deadline passes, the request is\n * aborted and fails with the SDK's retryable network error, so one request\n * can never hold an in-flight slot (the offline outbox flush, the lazy\n * anonymous sign-in, the session refresh, the event batch flush) for the\n * life of the process. Some runtimes never settle a fetch they gave up on:\n * Expo on Android drops a failed DNS lookup and the promise stays pending.\n * The deadline wins the race even then, because it rejects on its own timer\n * and then aborts the fetch for the runtime to clean up.\n *\n * Two bounds:\n * - `requestTimeoutMs` (default 15 s): an ordinary API request.\n * - `longRequestTimeoutMs` (default 5 min): work that legitimately runs\n * longer. AI generation (`/v1/client/ai/*`), function invocations, and\n * media uploads and downloads.\n * Both grow with the bytes a request moves (see `withTransferAllowance`), so\n * a large upload, download or event batch on a slow link has time to finish.\n * Realtime subscriptions are bounded until their stream opens; AI response\n * streams likewise until the response starts. Once open, a stream runs for\n * as long as the caller keeps it.\n */\n\n/** Default deadline for one API request, in milliseconds. */\nexport const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;\n\n/**\n * Default deadline for AI generation, function invocations and media\n * transfers, in milliseconds.\n */\nexport const DEFAULT_LONG_REQUEST_TIMEOUT_MS = 300_000;\n\n/**\n * The slowest link a deadline makes room for, in bytes per second\n * (8 KiB/s, about 64 kbit/s). Each byte a request sends or receives beyond\n * the first {@link TRANSFER_BYTES_WITHIN_BASE} adds the time it takes at this\n * rate to the deadline.\n */\nexport const MIN_TRANSFER_BYTES_PER_SECOND = 8 * 1024;\n\n/**\n * Bytes a request moves within its base deadline (64 KiB: 8 s at the\n * slowest rate). An ordinary API request stays at exactly\n * `requestTimeoutMs`; only larger bodies widen it.\n */\nexport const TRANSFER_BYTES_WITHIN_BASE = 64 * 1024;\n\n/** The largest delay `setTimeout` honors (about 24.8 days). */\nconst MAX_TIMEOUT_MS = 2_147_483_647;\n\n/** The resolved deadlines one configured SDK instance applies. */\nexport interface RequestDeadlines {\n /** Deadline for an API request, in milliseconds. */\n requestMs: number;\n /**\n * Deadline for AI generation, function invocations and media uploads and\n * downloads, in milliseconds.\n */\n longMs: number;\n}\n\n/** The configure options that set the deadlines. */\nexport interface RequestDeadlineOptions {\n requestTimeoutMs?: number;\n longRequestTimeoutMs?: number;\n}\n\n/**\n * Read the deadlines from the configure options, applying the defaults.\n * Throws an `Error` naming the option when a value is not a positive number\n * of milliseconds.\n */\nexport function resolveRequestDeadlines(\n options: RequestDeadlineOptions,\n): RequestDeadlines {\n return {\n requestMs: readTimeout(\n \"Amba.configure: requestTimeoutMs\",\n options.requestTimeoutMs,\n DEFAULT_REQUEST_TIMEOUT_MS,\n ),\n longMs: readTimeout(\n \"Amba.configure: longRequestTimeoutMs\",\n options.longRequestTimeoutMs,\n DEFAULT_LONG_REQUEST_TIMEOUT_MS,\n ),\n };\n}\n\n/**\n * Validate one timeout value; `undefined` keeps `fallback`. `label` names the\n * option in the thrown `Error` (`Amba.configure: requestTimeoutMs`).\n */\nexport function readTimeout(\n label: string,\n value: unknown,\n fallback: number,\n): number {\n if (value === undefined) return fallback;\n if (\n typeof value !== \"number\" ||\n !Number.isFinite(value) ||\n value <= 0 ||\n value > MAX_TIMEOUT_MS\n ) {\n throw new Error(\n `${label} must be a positive number of milliseconds, at most ${MAX_TIMEOUT_MS}`,\n );\n }\n return value;\n}\n\n/** The text a deadline failure carries, e.g. `request timed out after 15000 ms`. */\nexport function timeoutMessage(timeoutMs: number): string {\n return `request timed out after ${timeoutMs} ms`;\n}\n\n/**\n * `timeoutMs` plus the time the bytes beyond the first\n * {@link TRANSFER_BYTES_WITHIN_BASE} take at\n * {@link MIN_TRANSFER_BYTES_PER_SECOND}, capped at the largest delay\n * `setTimeout` honors. Unknown sizes add nothing.\n */\nexport function withTransferAllowance(\n timeoutMs: number,\n bytes: number | null | undefined,\n): number {\n const beyond =\n typeof bytes === \"number\" && Number.isFinite(bytes)\n ? bytes - TRANSFER_BYTES_WITHIN_BASE\n : 0;\n const extra =\n beyond > 0 ? Math.ceil((beyond / MIN_TRANSFER_BYTES_PER_SECOND) * 1000) : 0;\n return Math.min(MAX_TIMEOUT_MS, timeoutMs + extra);\n}\n\n/**\n * The size of a request body in bytes: a string's UTF-8 length, a buffer's\n * byte length, a `Blob`'s size. `undefined` for no body or a body of unknown\n * size (a stream).\n */\nexport function bodyByteLength(body: unknown): number | undefined {\n if (body === undefined || body === null) return undefined;\n if (typeof body === \"string\") {\n return typeof TextEncoder !== \"undefined\"\n ? new TextEncoder().encode(body).length\n : body.length;\n }\n if (typeof ArrayBuffer !== \"undefined\") {\n if (body instanceof ArrayBuffer) return body.byteLength;\n if (ArrayBuffer.isView(body)) return body.byteLength;\n }\n const size = (body as { size?: unknown }).size;\n return typeof size === \"number\" ? size : undefined;\n}\n\n/** A response's `Content-Length`, when it states one. */\nfunction contentLength(response: Response): number | undefined {\n const raw = response.headers?.get?.(\"content-length\");\n if (!raw) return undefined;\n const n = Number(raw);\n return Number.isFinite(n) && n >= 0 ? n : undefined;\n}\n\n/** The paths under the API base URL whose requests get the long deadline. */\nconst LONG_API_PATH_PREFIXES = [\"/v1/client/ai/\"];\n\n/** Whether a request to API `path` (starting `/v1/...`) gets the long deadline. */\nexport function isLongApiPath(path: string): boolean {\n return LONG_API_PATH_PREFIXES.some((prefix) => path.startsWith(prefix));\n}\n\n/**\n * The deadline for a request to absolute `url`. A URL under `apiBaseUrl`\n * gets the request deadline, or the long one for AI generation. Any other\n * host (a CDN download, an object-storage upload) is a media transfer and\n * gets the long deadline.\n */\nexport function deadlineForUrl(\n url: string,\n apiBaseUrl: string,\n deadlines: RequestDeadlines,\n): number {\n const base = apiBaseUrl.replace(/\\/+$/, \"\");\n if (url !== base && !url.startsWith(`${base}/`) && !url.startsWith(`${base}?`)) {\n return deadlines.longMs;\n }\n return isLongApiPath(url.slice(base.length))\n ? deadlines.longMs\n : deadlines.requestMs;\n}\n\n/**\n * Race `work` against a timer of `ms`. When the timer fires first the race\n * rejects with `onTimeout(ms)`, then `controller` is aborted so the runtime\n * can release the connection. The rejection does not wait for `work`, so a\n * fetch the runtime never settles still releases its caller on time. The\n * timer is cleared as soon as either side settles.\n */\nexport function raceDeadline<T>(\n work: Promise<T>,\n ms: number,\n controller: AbortController | undefined,\n onTimeout: (timeoutMs: number) => unknown,\n): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const deadline = new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n // Reject first so the race settles with the deadline error, then abort.\n reject(onTimeout(ms));\n try {\n controller?.abort();\n } catch {\n // An abort that throws changes nothing: the deadline already won.\n }\n }, ms);\n });\n return Promise.race([work, deadline]).finally(() => {\n clearTimeout(timer);\n });\n}\n\nfunction newController(): AbortController | undefined {\n return typeof AbortController === \"function\"\n ? new AbortController()\n : undefined;\n}\n\n/**\n * Run `work` under a deadline of `timeoutMs`. `work` receives an\n * `AbortSignal` to hand to `fetch` (absent when the runtime has no\n * `AbortController`) and should read everything it needs from the response\n * before resolving, so the deadline covers the body too. Past the deadline\n * the returned promise rejects with `onTimeout(timeoutMs)` and the signal is\n * aborted.\n */\nexport function withDeadline<T>(\n timeoutMs: number,\n work: (signal: AbortSignal | undefined) => Promise<T>,\n onTimeout: (timeoutMs: number) => unknown,\n): Promise<T> {\n const controller = newController();\n let running: Promise<T>;\n try {\n running = work(controller?.signal);\n } catch (err) {\n return Promise.reject(err);\n }\n return raceDeadline(running, timeoutMs, controller, onTimeout);\n}\n\n/** Sizes that widen an {@link exchange}'s deadlines. */\nexport interface ExchangeSizes {\n /** Bytes the request sends (its body). */\n requestBytes?: number;\n /**\n * Bytes the response is expected to carry, when the caller knows (a media\n * asset's size). The response's own `Content-Length` is used otherwise.\n */\n responseBytes?: number;\n /**\n * Set when `send`'s `fetch` resolves only once the whole response body has\n * arrived, as React Native's does. The expected `responseBytes` then widen\n * the send phase, where that runtime spends the download. A `fetch` that\n * resolves at the response headers (browsers, Node) leaves the send phase\n * at the request's own size, so a server that never answers fails on the\n * base deadline.\n */\n fetchBuffersBody?: boolean;\n}\n\n/**\n * Send a request and read its response under deadlines that grow with the\n * bytes involved. Sending, until `send` resolves with the response, gets\n * `timeoutMs` plus the allowance for the request body (and, with\n * `fetchBuffersBody`, for the expected response bytes). Reading,\n * `read(response)`, gets `timeoutMs` plus the allowance for the expected\n * response bytes, else the response's `Content-Length`. One `AbortSignal`\n * covers both phases, so a read past its deadline aborts the transfer too.\n * Past either deadline the promise rejects with `onTimeout(ms)`.\n */\nexport async function exchange<R>(\n timeoutMs: number,\n send: (signal: AbortSignal | undefined) => Promise<Response>,\n read: (response: Response) => Promise<R>,\n onTimeout: (timeoutMs: number) => unknown,\n sizes: ExchangeSizes = {},\n): Promise<R> {\n const controller = newController();\n const sendMs = withTransferAllowance(\n timeoutMs,\n (sizes.requestBytes ?? 0) +\n (sizes.fetchBuffersBody ? (sizes.responseBytes ?? 0) : 0),\n );\n const response = await raceDeadline(\n send(controller?.signal),\n sendMs,\n controller,\n onTimeout,\n );\n const readMs = withTransferAllowance(\n timeoutMs,\n sizes.responseBytes ?? contentLength(response),\n );\n return raceDeadline(read(response), readMs, controller, onTimeout);\n}\n\n/** `init` with `signal` set, when there is one. */\nexport function withSignal(\n init: RequestInit | undefined,\n signal: AbortSignal | undefined,\n): RequestInit | undefined {\n return signal ? { ...init, signal } : init;\n}\n\n/** Statuses whose responses carry no body (`new Response` refuses one). */\nconst NULL_BODY_STATUSES = new Set([101, 204, 205, 304]);\n\n/**\n * Wrap `baseFetch` so every call runs under the deadline `deadlineFor(url)`\n * picks, widened by the request body and the response's `Content-Length`\n * (see {@link exchange}), and resolves with a response whose body is already\n * read in full. For the SDK core, which reads the status, the headers and\n * the whole body of every response. The deadline rejects with an `Error`\n * whose message starts `Network error:`, which the core reports as\n * `NETWORK_ERROR`.\n */\nexport function deadlineFetch(\n baseFetch: typeof fetch,\n deadlineFor: (url: string) => number,\n): typeof fetch {\n return ((input: Parameters<typeof fetch>[0], init?: RequestInit) => {\n const url =\n typeof input === \"string\"\n ? input\n : input instanceof URL\n ? input.href\n : (input as { url: string }).url;\n return exchange(\n deadlineFor(url),\n (signal) => baseFetch(input, withSignal(init, signal)),\n async (response) => {\n const body = NULL_BODY_STATUSES.has(response.status)\n ? null\n : await response.arrayBuffer();\n return new Response(body, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n });\n },\n (ms) => new Error(`Network error: ${timeoutMessage(ms)}`),\n { requestBytes: bodyByteLength(init?.body) },\n );\n }) as typeof fetch;\n}\n","/**\n * Pure-JS HTTP client for the amba API.\n *\n * Built on the platform `fetch` (React Native ships it; Hermes\n * provides it; Expo polyfills it where needed). No transitive\n * runtime deps. The previous implementation tunnelled every call\n * through a compiled SDK core that did the same thing — moving the\n * logic to TS removes ~4 MB from the customer bundle and removes\n * a runtime that's foreign to Hermes.\n *\n * Responsibilities:\n * - Construct the request URL by joining `baseUrl + path` (with an\n * optional query string).\n * - Inject the standard amba auth headers — `X-Api-Key`,\n * `X-SDK-Platform`, `X-SDK-Version`, `Content-Type`,\n * `X-App-Version` / `X-Device-Region` / `X-Device-OS` when known, and\n * `Authorization: Bearer …` when a session token is present.\n * - Map non-2xx responses to typed `AmbaApiError` instances using\n * the same code set the SDK core used (UNAUTHORIZED, FORBIDDEN,\n * NOT_FOUND, CONFLICT, RATE_LIMITED, VALIDATION_ERROR, HTTP_ERROR,\n * NETWORK_ERROR).\n * - Parse JSON response bodies. 204 No Content returns `null`.\n * - Run every request under a deadline (see `request-deadline.ts`). A\n * request still unfinished at its deadline fails with `NETWORK_ERROR`,\n * so a fetch the runtime never settles releases every in-flight guard\n * that waits on it.\n */\n\nimport { AmbaApiError } from \"./error.js\";\nimport type { IdentityStore } from \"./identity.js\";\nimport {\n DEFAULT_LONG_REQUEST_TIMEOUT_MS,\n DEFAULT_REQUEST_TIMEOUT_MS,\n bodyByteLength,\n exchange,\n isLongApiPath,\n timeoutMessage,\n withSignal,\n} from \"./request-deadline.js\";\n\n/** Default amba API endpoint. */\nexport const DEFAULT_BASE_URL = \"https://api.amba.dev\";\n\n/**\n * Server response envelope. Most amba endpoints wrap their payload\n * in `{ data: ... }`. Helpers below let call sites declare whether\n * to return the envelope, the unwrapped `data` field, or the raw\n * body (e.g. `find` returns the whole envelope so consumers can\n * paginate via `next_cursor` / `has_more`).\n */\nexport interface Envelope<T> {\n data: T;\n next_cursor?: string | null;\n has_more?: boolean;\n}\n\nexport interface AmbaHttpClientOptions {\n apiKey: string;\n baseUrl: string;\n sdkPlatform: string;\n sdkVersionHeader: string;\n /** Host app version for `X-App-Version` (already normalized); omitted when unset. */\n appVersion?: string;\n /** Device region for `X-Device-Region` (already normalized); omitted when unset. */\n deviceRegion?: string;\n /** Device OS for `X-Device-OS` (already normalized); omitted when unset. */\n deviceOs?: string;\n identity: IdentityStore;\n /** Override the `fetch` implementation — tests pass a vi.fn() shim. */\n fetchImpl?: typeof fetch;\n /**\n * Deadline for one API request, from sending it to reading the whole\n * response, in milliseconds. Defaults to 15 000.\n */\n requestTimeoutMs?: number;\n /**\n * Deadline for AI generation (`/v1/client/ai/*`), and the default `send`\n * callers pass for function invocations and media transfers, in\n * milliseconds. Defaults to 300 000.\n */\n longRequestTimeoutMs?: number;\n /**\n * Optional hook invoked before a request when no session token is present.\n * Used to lazily establish an anonymous session on the first authenticated\n * call so reads (xp/collections/etc.) don't fail just because the app never\n * called `signInAnonymously()`. The callback receives the request path and\n * MUST early-return for auth/public paths to avoid recursion. It's only\n * consulted while `identity.getSessionToken()` is empty.\n */\n ensureSession?: (path: string) => Promise<void>;\n /**\n * Optional hook invoked when the server refuses the stored session token\n * on an authenticated call (`SESSION_REVOKED`: a link or an inbox proof\n * signed out every earlier session; `INVALID_SESSION_TOKEN`: the token is\n * expired or invalid). It receives the token the call sent and the user\n * it was sent for, and resolves true when a usable session for that same\n * user is stored afterwards; the call is then sent once more with that\n * session. Of the calls under `/v1/client/auth/`, only the ones that send\n * the session in the `Authorization` header reach it (`/me`,\n * `/identifiers`; see `recoversRefusedSession`). Calls that carry their\n * own session token never reach it.\n */\n onSessionRefused?: (\n usedToken: string,\n usedUserId: string | null,\n ) => Promise<boolean>;\n}\n\n/**\n * Server codes for a session token the server no longer accepts.\n * `INVALID_SESSION` is the code the auth routes that verify the session\n * themselves (`/identifiers`, `/link`, `/unlink`) answer with.\n */\nconst SESSION_REFUSED_CODES = new Set([\n \"SESSION_REVOKED\",\n \"INVALID_SESSION_TOKEN\",\n \"INVALID_SESSION\",\n]);\n\n/** True for a server error that refused the session token a call sent. */\nexport function isSessionRefusedError(err: unknown): boolean {\n return err instanceof AmbaApiError && SESSION_REFUSED_CODES.has(err.code);\n}\n\nconst AUTH_PATH_PREFIX = \"/v1/client/auth/\";\n\n/**\n * The auth routes that read the session from the `Authorization` header and\n * mint nothing: a refused session on them recovers like it does on any\n * other authenticated call. The other auth routes stay out of the\n * recovery: `/refresh` is the recovery itself, `/logout` ends the session,\n * the sign-in, sign-up, OTP and magic-link routes take credentials (a 401\n * there is a credential error), and `/link` and `/unlink` carry the session\n * in their body, so their callers recover and rebuild the body themselves.\n */\nconst SESSION_HEADER_AUTH_PATHS: ReadonlySet<string> = new Set([\n `${AUTH_PATH_PREFIX}me`,\n `${AUTH_PATH_PREFIX}identifiers`,\n]);\n\n/** Whether a refused session on `path` goes through `onSessionRefused`. */\nexport function recoversRefusedSession(path: string): boolean {\n return (\n !path.startsWith(AUTH_PATH_PREFIX) || SESSION_HEADER_AUTH_PATHS.has(path)\n );\n}\n\ntype Query = Record<string, string | number | boolean | undefined | null>;\n\nexport class AmbaHttpClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly sdkPlatform: string;\n private readonly sdkVersionHeader: string;\n private readonly appVersion?: string;\n private readonly deviceRegion?: string;\n private readonly deviceOs?: string;\n private readonly identity: IdentityStore;\n private readonly fetchImpl: typeof fetch;\n /** The `fetchImpl` option, when one was passed (`send` resolves fetch per call otherwise). */\n private readonly injectedFetch?: typeof fetch;\n /** Deadline for an API request, in milliseconds. */\n readonly requestTimeoutMs: number;\n /** Deadline for AI generation, function invocations and media transfers. */\n readonly longRequestTimeoutMs: number;\n private readonly ensureSession?: (path: string) => Promise<void>;\n private readonly onSessionRefused?: (\n usedToken: string,\n usedUserId: string | null,\n ) => Promise<boolean>;\n\n constructor(opts: AmbaHttpClientOptions) {\n this.apiKey = opts.apiKey;\n this.baseUrl = opts.baseUrl.replace(/\\/+$/, \"\");\n this.sdkPlatform = opts.sdkPlatform;\n this.sdkVersionHeader = opts.sdkVersionHeader;\n this.appVersion = opts.appVersion;\n this.deviceRegion = opts.deviceRegion;\n this.deviceOs = opts.deviceOs;\n this.identity = opts.identity;\n this.ensureSession = opts.ensureSession;\n this.onSessionRefused = opts.onSessionRefused;\n this.injectedFetch = opts.fetchImpl;\n this.fetchImpl =\n opts.fetchImpl ?? (globalThis.fetch.bind(globalThis) as typeof fetch);\n this.requestTimeoutMs = opts.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n this.longRequestTimeoutMs =\n opts.longRequestTimeoutMs ?? DEFAULT_LONG_REQUEST_TIMEOUT_MS;\n }\n\n /**\n * Send a request to an absolute URL outside the API's JSON envelope (a\n * function invocation, a CDN download, the config route that reads the\n * `ETag`) under a deadline. `read` takes what the caller needs from the\n * response (the body as text, a `Blob`) and runs inside the same\n * deadline, so a body that stops arriving fails too.\n *\n * A transport failure rejects with `NETWORK_ERROR` and the message\n * `<label>: <cause>`; the deadline rejects with `NETWORK_ERROR` and\n * `<label>: request timed out after <ms> ms`. `label` defaults to\n * `Network error` and `timeoutMs` to the request deadline; the deadline\n * widens with the request body and with `responseBytes`, the size the\n * caller expects to download (see `exchange`). An `AmbaApiError` that\n * `read` throws passes through unchanged.\n */\n async send<R>(\n url: string,\n init: RequestInit,\n read: (response: Response) => Promise<R>,\n opts: { timeoutMs?: number; label?: string; responseBytes?: number } = {},\n ): Promise<R> {\n const timeoutMs = opts.timeoutMs ?? this.requestTimeoutMs;\n const label = opts.label ?? \"Network error\";\n // Resolved per call: these direct call sites always used the global\n // fetch at call time.\n const fetchFn = this.injectedFetch ?? globalThis.fetch;\n try {\n return await exchange(\n timeoutMs,\n (signal) => fetchFn(url, withSignal(init, signal)),\n read,\n (ms) => timeoutError(label, ms),\n {\n requestBytes: bodyByteLength(init.body),\n responseBytes: opts.responseBytes,\n // React Native's fetch resolves once the whole body has arrived.\n fetchBuffersBody: true,\n },\n );\n } catch (err) {\n throw asNetworkError(err, label);\n }\n }\n\n /** Build the absolute URL for an API path, optionally with a query string. */\n url(path: string, query?: Query): string {\n const base = this.baseUrl + path;\n if (!query) return base;\n const parts: string[] = [];\n for (const k of Object.keys(query)) {\n const v = query[k];\n if (v === undefined || v === null) continue;\n parts.push(`${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`);\n }\n if (parts.length === 0) return base;\n return `${base}?${parts.join(\"&\")}`;\n }\n\n /** Build the standard request headers for the current identity state. */\n buildHeaders(extra?: Record<string, string>): Record<string, string> {\n const h: Record<string, string> = {\n \"X-Api-Key\": this.apiKey,\n \"X-SDK-Platform\": this.sdkPlatform,\n \"X-SDK-Version\": this.sdkVersionHeader,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n ...extra,\n };\n if (this.appVersion) h[\"X-App-Version\"] = this.appVersion;\n if (this.deviceRegion) h[\"X-Device-Region\"] = this.deviceRegion;\n if (this.deviceOs) h[\"X-Device-OS\"] = this.deviceOs;\n const tok = this.identity.getSessionToken();\n if (tok) h.Authorization = `Bearer ${tok}`;\n return h;\n }\n\n async get<T>(path: string, query?: Query): Promise<T> {\n return this.request<T>(\"GET\", path, undefined, query);\n }\n\n async post<T>(path: string, body?: unknown, query?: Query): Promise<T> {\n return this.request<T>(\"POST\", path, body, query);\n }\n\n /** Send a queued event with the session captured for its original user. */\n async postWithSession<T>(\n path: string,\n body: unknown,\n sessionToken: string,\n ): Promise<T> {\n return this.request<T>(\"POST\", path, body, undefined, sessionToken);\n }\n\n async patch<T>(path: string, body?: unknown): Promise<T> {\n return this.request<T>(\"PATCH\", path, body);\n }\n\n async delete<T>(path: string): Promise<T> {\n return this.request<T>(\"DELETE\", path);\n }\n\n private async request<T>(\n method: string,\n path: string,\n body?: unknown,\n query?: Query,\n sessionToken?: string,\n resent = false,\n ): Promise<T> {\n // Lazily establish an anonymous session on the first authenticated call.\n // The callback is responsible for skipping auth/public paths (so this\n // never recurses through the anonymous sign-in request itself).\n if (\n this.ensureSession &&\n !sessionToken &&\n !this.identity.getSessionToken()\n ) {\n await this.ensureSession(path);\n }\n\n const url = this.url(path, query);\n const headers = this.buildHeaders();\n if (sessionToken) headers.Authorization = `Bearer ${sessionToken}`;\n // The stored session token this call carries, when it carries one, and\n // the user it was sent for.\n const storedToken = sessionToken ? null : this.identity.getSessionToken();\n const storedUserId = storedToken ? this.identity.getAppUserId() : null;\n\n const init: RequestInit = {\n method,\n headers,\n body: body === undefined ? undefined : JSON.stringify(body ?? {}),\n };\n\n // The deadline covers sending the request and reading the whole body,\n // and widens for a large body (an event batch on a slow uplink). AI\n // generation answers when the model finishes, so it gets the long one.\n const timeoutMs = isLongApiPath(path)\n ? this.longRequestTimeoutMs\n : this.requestTimeoutMs;\n let response: Response;\n let text: string;\n try {\n ({ response, text } = await exchange(\n timeoutMs,\n (signal) => this.fetchImpl(url, withSignal(init, signal)),\n async (res) => ({\n response: res,\n // 204 No Content has no body to wait for.\n text: res.status === 204 ? \"\" : await res.text(),\n }),\n (ms) => timeoutError(\"Network error\", ms),\n { requestBytes: bodyByteLength(init.body) },\n ));\n } catch (err) {\n // Transport-level failure (DNS, refused, offline, abort, a body stream\n // that breaks) or the deadline. The platform fetch throws a TypeError\n // or AbortError; we wrap it into the SDK's stable error shape. Both\n // are the retryable NETWORK_ERROR.\n throw asNetworkError(err, \"Network error\");\n }\n\n try {\n return parseResponse<T>(response, text);\n } catch (err) {\n if (\n !resent &&\n storedToken &&\n this.onSessionRefused &&\n recoversRefusedSession(path) &&\n isSessionRefusedError(err) &&\n (await this.recoverSession(storedToken, storedUserId))\n ) {\n return this.request<T>(method, path, body, query, undefined, true);\n }\n throw err;\n }\n }\n\n /** Run the `onSessionRefused` hook; a hook that throws counts as false. */\n private async recoverSession(\n usedToken: string,\n usedUserId: string | null,\n ): Promise<boolean> {\n try {\n return (await this.onSessionRefused?.(usedToken, usedUserId)) === true;\n } catch {\n return false;\n }\n }\n}\n\n/** The `NETWORK_ERROR` a passed deadline raises. */\nfunction timeoutError(label: string, timeoutMs: number): AmbaApiError {\n return new AmbaApiError(\n \"NETWORK_ERROR\",\n `${label}: ${timeoutMessage(timeoutMs)}`,\n { timeoutMs },\n );\n}\n\n/**\n * Wrap a transport failure as `NETWORK_ERROR` (`<label>: <cause>`). An\n * `AmbaApiError` (the deadline's own, or one a `send` reader throws) passes\n * through unchanged.\n */\nfunction asNetworkError(err: unknown, label: string): AmbaApiError {\n if (err instanceof AmbaApiError) return err;\n const message =\n err instanceof Error\n ? err.message\n : typeof err === \"string\"\n ? err\n : \"fetch failed\";\n return new AmbaApiError(\"NETWORK_ERROR\", `${label}: ${message}`, err);\n}\n\nfunction parseResponse<T>(response: Response, text: string): T {\n // 204 No Content: most DELETE endpoints. Return null so generics\n // taking `unknown | null` decode cleanly.\n if (response.status === 204) {\n return null as T;\n }\n\n const ct = response.headers.get(\"content-type\") ?? \"\";\n const isJson = ct.includes(\"application/json\");\n\n if (!response.ok) {\n throw mapHttpError(response, text, isJson);\n }\n\n if (text === \"\") return null as T;\n if (isJson) {\n try {\n return JSON.parse(text) as T;\n } catch (err) {\n throw new AmbaApiError(\n \"HTTP_ERROR\",\n `Failed to parse JSON response: ${err instanceof Error ? err.message : String(err)}`,\n { body: text },\n );\n }\n }\n // Non-JSON success body (uncommon for this API). Return the raw text\n // string typed as the generic so the caller can decide.\n return text as unknown as T;\n}\n\nexport function mapHttpError(\n response: Response,\n rawBody: string,\n isJson: boolean,\n): AmbaApiError {\n let parsed: unknown = undefined;\n let message = rawBody || `HTTP ${response.status}`;\n let code: string | undefined;\n // `envelopeDetails` is the inner `error.details` field — surfaced as\n // `AmbaApiError.details` when the server emitted a typed envelope.\n // This matches the web SDK, which carries the inner\n // details (e.g. `{ streak_key: 'weekly_login' }`) verbatim — NOT the\n // whole `{error:{...}}` wrapper. Customer code like\n // `e.details.streak_key` must work identically on both SDKs.\n // (Cursor BugBot PR #156, medium — cross-SDK details-shape divergence.)\n let envelopeDetails: unknown = undefined;\n let envelopeRecognized = false;\n if (isJson && rawBody) {\n try {\n parsed = JSON.parse(rawBody);\n } catch {\n parsed = undefined;\n }\n // Standard amba envelope: `{ error: { code, message, details } }`\n // and a few legacy `{ error: \"string\" }` shapes. We recognize the\n // envelope by the presence of a string `code` alone — message is\n // optional, matching the Rust `parse_error_envelope` rule so the\n // two SDKs agree on what \"envelope\" means. (Cursor BugBot PR #156.)\n const env = parsed as {\n error?: { code?: string; message?: string; details?: unknown };\n } | null;\n const innerErr = env?.error;\n if (\n innerErr &&\n typeof innerErr === \"object\" &&\n typeof innerErr.code === \"string\"\n ) {\n code = innerErr.code;\n envelopeRecognized = true;\n envelopeDetails = innerErr.details;\n if (typeof innerErr.message === \"string\") message = innerErr.message;\n }\n }\n\n // Use the inner envelope details when the server gave us a\n // recognizable envelope; fall back to the full parsed body\n // otherwise so legacy / proxy-shaped responses still carry\n // something the customer can inspect.\n const details: unknown = envelopeRecognized ? envelopeDetails : parsed;\n\n switch (response.status) {\n case 401:\n return new AmbaApiError(code ?? \"UNAUTHORIZED\", message, details);\n case 403:\n return new AmbaApiError(code ?? \"FORBIDDEN\", message, details);\n case 404:\n return new AmbaApiError(code ?? \"NOT_FOUND\", message, details);\n case 409:\n return new AmbaApiError(code ?? \"CONFLICT\", message, details);\n case 422:\n return new AmbaApiError(code ?? \"VALIDATION_ERROR\", message, details);\n case 429: {\n const retryAfter = response.headers.get(\"retry-after\") ?? \"unspecified\";\n // Prefer the server's structured message if it gave us one; fall back\n // to a synthesized one that surfaces the Retry-After header so callers\n // can decide whether to back off vs surface to the user.\n const serverGaveMessage =\n message !== rawBody && message !== `HTTP ${response.status}`;\n return new AmbaApiError(\n code ?? \"RATE_LIMITED\",\n serverGaveMessage\n ? message\n : `Rate limited (retry after ${retryAfter})`,\n details,\n );\n }\n default:\n return new AmbaApiError(\n code ?? \"HTTP_ERROR\",\n message,\n envelopeRecognized\n ? envelopeDetails\n : (parsed ?? { status: response.status, body: rawBody }),\n );\n }\n}\n","/**\n * UUID v4 generator — pure JS, no dependencies.\n *\n * The SDK uses UUIDs for `anonymous_id`. While a user is purely anonymous,\n * that id is the whole credential for their account (presenting it to\n * `/v1/client/auth/anonymous` signs back in), so it must come from a\n * cryptographically secure generator: `globalThis.crypto.getRandomValues`.\n * React Native's Hermes engine does not ship it; apps install\n * `react-native-get-random-values` and import it first in their entry file\n * (Expo apps alike). Without it the SDK refuses to generate an id and throws\n * `SecureRandomUnavailableError` from `Amba.configure()`.\n */\n\ntype GetRandomValues = (a: Uint8Array) => Uint8Array;\n\n/** Thrown when the runtime has no `crypto.getRandomValues`. */\nexport class SecureRandomUnavailableError extends Error {\n readonly code = \"SECURE_RANDOM_UNAVAILABLE\";\n constructor() {\n super(\n \"Amba needs crypto.getRandomValues to generate identifiers, and this runtime \" +\n \"does not provide it. Install react-native-get-random-values and import it on \" +\n \"the first line of your app's entry file, before any Amba import: \" +\n \"import 'react-native-get-random-values';\",\n );\n this.name = \"SecureRandomUnavailableError\";\n }\n}\n\nfunction secureGetRandomValues(): GetRandomValues {\n const crypto = (globalThis as { crypto?: { getRandomValues?: GetRandomValues } })\n .crypto;\n const grv = crypto?.getRandomValues;\n if (!crypto || typeof grv !== \"function\") {\n throw new SecureRandomUnavailableError();\n }\n // Bind to crypto to preserve `this`.\n return grv.bind(crypto);\n}\n\n/**\n * Throws `SecureRandomUnavailableError` when the runtime cannot generate\n * secure identifiers. `IdentityStore.create` calls it so a missing polyfill\n * surfaces at `configure()`, before any identifier is needed.\n */\nexport function assertSecureRandomAvailable(): void {\n secureGetRandomValues();\n}\n\nfunction getRandomBytes(n: number): Uint8Array {\n const bytes = new Uint8Array(n);\n secureGetRandomValues()(bytes);\n return bytes;\n}\n\n/**\n * Generate a UUID v4 (random) per RFC 4122. Wire shape:\n * `xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx` where `y ∈ {8,9,a,b}`.\n */\nexport function uuidv4(): string {\n const b = getRandomBytes(16);\n // Set version (4) and variant (10xx) bits.\n b[6] = (b[6]! & 0x0f) | 0x40;\n b[8] = (b[8]! & 0x3f) | 0x80;\n const h = Array.from(b, (v) => v.toString(16).padStart(2, \"0\"));\n return (\n h.slice(0, 4).join(\"\") +\n \"-\" +\n h.slice(4, 6).join(\"\") +\n \"-\" +\n h.slice(6, 8).join(\"\") +\n \"-\" +\n h.slice(8, 10).join(\"\") +\n \"-\" +\n h.slice(10, 16).join(\"\")\n );\n}\n","/**\n * Identity store — the minimal on-device state the SDK keeps between\n * launches: `anonymous_id` (per-install UUID), authenticated user id,\n * session token, refresh token, expiry, and whether the current session\n * is purely anonymous.\n *\n * While the user is purely anonymous, `anonymous_id` is the whole\n * credential for their account: presenting it to\n * `/v1/client/auth/anonymous` signs back in. Once the session gains a real\n * identity (any sign-in other than anonymous, or a link), the server stops\n * resuming the account by anonymous id, and `clearSession` rotates the id on\n * sign-out so the next person on the device starts fresh.\n *\n * Persistence: the public part (`anonymous_id`, `app_user_id`,\n * `anonymous_session`) goes to `AsyncStoragePersistence` under\n * `<prefix>:identity_v1` (JSON-encoded).\n * The credential part (tokens + expiry) goes to the {@link TokenVault}\n * chosen by `AmbaConfig.tokenStorage` (see `token-storage.ts`); with no\n * vault (`tokenStorage: \"asyncStorage\"`) it stays in the same AsyncStorage\n * record. On `configure()` we hydrate once; subsequent reads are O(1)\n * in-process.\n *\n * Every write that installs or ends a session advances the store's\n * generation. Writes that act for a session they read earlier (a refresh, a\n * link, the automatic anonymous sign-in, an ended session's cleanup) commit\n * only while the generation is still the one they started from, so a write\n * that lost a race never replaces the newer session (see `CommitFence`).\n *\n * Why a small TS class instead of porting the per-instance-key\n * machinery: the SDK core's race rationale (cross-core write race\n * with `amba_reset`) is specific to its slot model — JS doesn't\n * have that hazard because there's one `Amba` singleton per module\n * graph. Keeping the persistence shape simple makes the AsyncStorage\n * cost predictable and the implementation auditable.\n */\n\nimport { AsyncStoragePersistence } from \"./async-storage-adapter.js\";\nimport type { TokenSlice, TokenVault } from \"./token-storage.js\";\nimport { assertSecureRandomAvailable, uuidv4 } from \"./uuid.js\";\n\n/** Storage key under the configured prefix. Bumped when shape changes. */\nconst IDENTITY_KEY = \"identity_v1\";\n\nexport interface IdentitySnapshot {\n anonymousId: string;\n appUserId: string | null;\n sessionToken: string | null;\n refreshToken: string | null;\n /** ISO-8601 expiry — null means \"no active session.\" */\n expiresAt: string | null;\n /**\n * True when the session came from an anonymous sign-in and never gained an\n * identity; false once it did; null with no session or when unknown (a\n * record written by an SDK before 4.0.14).\n */\n anonymousSession: boolean | null;\n /**\n * True after an identified session ended without a sign-out (the server\n * rejected its refresh token, or the anonymous id now needs the linked\n * sign-in). Calls that need a session fail with `SIGN_IN_REQUIRED` until\n * a sign-in, a link, an explicit anonymous sign-in or a sign-out.\n */\n signInRequired: boolean;\n}\n\n/**\n * When an identity write may commit. `ifGeneration` holds the generation\n * the write started from: the write commits only while it is unchanged.\n * `advance: false` marks a write that keeps the session it acts for (a\n * refresh, a link): it commits under the fence and leaves the generation as\n * it is, so it never cancels a sign-in or sign-out that is in flight.\n */\nexport interface CommitFence {\n ifGeneration?: number;\n advance?: boolean;\n}\n\ninterface PersistedIdentity {\n anonymous_id: string;\n app_user_id: string | null;\n session_token: string | null;\n refresh_token: string | null;\n /** ISO-8601 string; null when not set. */\n expires_at: string | null;\n /** See `IdentitySnapshot.anonymousSession`. */\n anonymous_session: boolean | null;\n /** See `IdentitySnapshot.signInRequired`. False in a record before 4.3.0. */\n sign_in_required: boolean;\n /**\n * Set (never written by this store) on a record whose credential part a\n * storage layer could not read this time: `@layers/amba-expo` keeps the\n * credentials in SecureStore, which can't be read while the device is\n * locked. The record then carries no tokens and no anonymous id, and the\n * store holds its writes back until a read succeeds.\n */\n credentials_unreadable?: boolean;\n /**\n * Set on a record written while the anonymous id is a stand-in for one\n * the unreadable credential part holds (a sign-in or sign-out while the\n * device was locked). The storage layer keeps the anonymous id it holds.\n */\n anonymous_id_unknown?: boolean;\n /**\n * Sequence number of the flush that wrote this record (see `entryBelongs`).\n * 0 for a record written before 4.0.14.\n */\n write_seq: number;\n /**\n * `write_seq` of the vault entry this record vouches for: the entry still\n * holds the session the record describes. Null once the record moved on\n * without the vault (a sign-in or sign-out whose vault write failed or was\n * held back). 0 for a record written before 4.0.14, whose entry carries no\n * sequence number.\n */\n vault_seq: number | null;\n /**\n * Random id of this install, stamped on every vault entry it writes. The\n * iOS Keychain outlives an uninstall; an entry an earlier install wrote\n * carries another id and is never this install's session. Null in a\n * record written before 4.0.14.\n */\n install_id: string | null;\n}\n\n/**\n * Whether a vault entry belongs to this install's record. An entry another\n * install wrote (`install_id`) never does. Each flush writes the vault and\n * then the record, both stamped with the flush's sequence number. An entry\n * of this install newer than the record comes from a flush that stopped\n * between the two writes, so it holds the latest session. Otherwise the\n * entry belongs only when the record vouches for it (`vault_seq`), which a\n * sign-in or sign-out that couldn't write the vault withdraws. An entry\n * written before 4.0.14 belongs while a record from then vouches for it and\n * carries the same anonymous id and user.\n */\nfunction entryBelongs(\n entry: TokenSlice,\n anonymousId: string,\n appUserId: string | null,\n installId: string | null,\n recordSeq: number,\n vaultSeq: number | null,\n): boolean {\n const entrySeq = typeof entry.write_seq === \"number\" ? entry.write_seq : 0;\n if (entrySeq === 0 || entry.install_id == null) {\n // Before 4.0.14 a sign-in whose vault write failed left the earlier\n // user's entry next to a record naming the new user.\n const sameUser =\n entry.app_user_id == null ||\n appUserId === null ||\n entry.app_user_id === appUserId;\n return vaultSeq === 0 && entry.anonymous_id === anonymousId && sameUser;\n }\n if (installId === null || entry.install_id !== installId) return false;\n if (entrySeq > recordSeq) return true;\n return entrySeq === vaultSeq;\n}\n\nfunction seqOf(value: unknown): number | null {\n return typeof value === \"number\" && Number.isInteger(value) && value >= 0\n ? value\n : null;\n}\n\nfunction isExpired(expiresAt: string | null): boolean {\n if (!expiresAt) return false;\n const t = Date.parse(expiresAt);\n if (Number.isNaN(t)) return false;\n return t <= Date.now();\n}\n\n/**\n * The SDK's identity state — held in memory after hydration, persisted\n * to AsyncStorage on every mutation. All methods are sync after\n * `hydrate()` resolves.\n */\nexport class IdentityStore {\n private anonymousId: string;\n private appUserId: string | null = null;\n private sessionToken: string | null = null;\n private refreshToken: string | null = null;\n private expiresAt: string | null = null;\n private anonymousSession: boolean | null = null;\n /**\n * Credentials an earlier SDK version left in the AsyncStorage record, with\n * the user they belong to. They stay on disk until the vault holds the\n * session, so a vault write that fails or is skipped (or a process killed\n * between the two writes) never loses the only stored copy.\n */\n private plaintext: { appUserId: string | null; slice: TokenSlice } | null;\n /**\n * Set while the vault could not be read. Writes are held back until a read\n * succeeds, so a session this launch never loaded is not overwritten.\n */\n private vaultUnreadable: boolean;\n /** Highest flush sequence number seen or used. */\n private seq: number;\n /** `write_seq` of the stored record. */\n private recordSeq: number;\n /** `vault_seq` of the stored record (see `PersistedIdentity`). */\n private vaultSeq: number | null;\n /**\n * The session the vouched vault entry holds, by refresh token and user. A\n * failed or held vault write withdraws the vouch only when the session\n * changed from it, so a link that only moves the anonymous id keeps it.\n */\n private vaultHolds: { refreshToken: string | null; appUserId: string | null };\n /** Serializes flushes, so the vault and the record stay in write order. */\n private flushChain: Promise<void> = Promise.resolve();\n /** This install's id (see `PersistedIdentity.install_id`). */\n private readonly installId: string;\n /** Whether the stored record carries `installId`. */\n private installIdStored: boolean;\n /** See `IdentitySnapshot.signInRequired`. */\n private signInRequired: boolean;\n /**\n * Advanced by every committed write that installs or ends a session (a\n * sign-in, a sign-out, an ended session). A refresh or a link keeps it.\n */\n private generation = 0;\n /** Set by `dispose`: the store commits and persists nothing after it. */\n private disposed = false;\n /**\n * Set while the record's credential part could not be read (see\n * `PersistedIdentity.credentials_unreadable`). Writes wait for a read\n * that succeeds, so a session this launch never loaded is not replaced.\n */\n private recordUnreadable: boolean;\n /**\n * Set while the anonymous id is a stand-in for one the unreadable\n * credential part holds. It is never persisted.\n */\n private temporaryAnonymousId: boolean;\n /** See `onStoredSessionLoaded`. */\n private storedSessionLoaded: (() => void) | null = null;\n /** See `onAnonymousIdKnown`. */\n private anonymousIdKnown: (() => void) | null = null;\n /** The refresh token the store held once created (see `continuesLaunchSession`). */\n private launchRefreshToken: string | null = null;\n /** Sign-ins committed since the store was created. */\n private signInsSinceLaunch = 0;\n\n private constructor(\n private readonly persistence: AsyncStoragePersistence,\n seed: PersistedIdentity,\n private readonly vault: TokenVault | null,\n plaintext: { appUserId: string | null; slice: TokenSlice } | null,\n vaultUnreadable: boolean,\n seq: number,\n recordUnreadable: boolean,\n temporaryAnonymousId: boolean,\n ) {\n this.plaintext = plaintext;\n this.vaultUnreadable = vaultUnreadable;\n this.recordUnreadable = recordUnreadable;\n this.temporaryAnonymousId = temporaryAnonymousId;\n this.signInRequired = seed.sign_in_required;\n this.seq = seq;\n this.installId = seed.install_id ?? uuidv4();\n this.installIdStored = seed.install_id !== null;\n this.recordSeq = seed.write_seq;\n this.vaultSeq = seed.vault_seq;\n this.vaultHolds = {\n refreshToken: seed.refresh_token,\n appUserId: seed.app_user_id,\n };\n this.anonymousId = seed.anonymous_id;\n this.appUserId = seed.app_user_id;\n this.sessionToken = seed.session_token;\n this.refreshToken = seed.refresh_token;\n this.expiresAt = seed.expires_at;\n this.anonymousSession = seed.anonymous_session;\n }\n\n /**\n * Build a store, hydrating any prior identity from AsyncStorage and the\n * credential vault. If no prior identity exists, a fresh `anonymous_id`\n * is minted. Expired session tokens are dropped at hydration (the refresh\n * token survives so callers can `auth.refresh()` to mint a new pair).\n *\n * With a vault, credentials an earlier SDK version left in the\n * AsyncStorage record are used for this launch and moved into the vault;\n * the AsyncStorage copy is removed once the vault write succeeds. A vault\n * holding its own credentials wins over them.\n *\n * Throws `SecureRandomUnavailableError` when the runtime has no\n * `crypto.getRandomValues`: every anonymous id the store mints must come\n * from a secure generator.\n */\n static async create(\n persistence: AsyncStoragePersistence,\n vault: TokenVault | null = null,\n ): Promise<IdentityStore> {\n assertSecureRandomAvailable();\n const raw = await persistence.read(IDENTITY_KEY);\n // The AsyncStorage record is what ties a vault entry to this install.\n // Without it (fresh install, cleared storage) a vault entry that survived\n // (the iOS Keychain outlives an uninstall) belongs to an earlier install\n // and is discarded.\n let recordPresent = false;\n let recordUnreadable = false;\n let temporaryAnonymousId = false;\n let seed: PersistedIdentity;\n if (raw) {\n try {\n const parsed = JSON.parse(raw) as Partial<PersistedIdentity>;\n seed = seedFrom(parsed);\n recordPresent = typeof parsed.anonymous_id === \"string\";\n // A storage layer that could not read the record's credential part\n // this time says so (`@layers/amba-expo` keeps it, the anonymous id\n // included, in SecureStore). The anonymous id is then a stand-in\n // until a read succeeds, and with a vault the entry waits for the\n // record that binds it.\n if (parsed.credentials_unreadable === true) {\n recordUnreadable = true;\n temporaryAnonymousId = !recordPresent;\n }\n } catch {\n // Garbage on disk — start fresh. anonymous_id rotates which is\n // acceptable for a corrupt-state recovery (rare; users on\n // upgrade keep their id because the previous SDK wrote valid JSON).\n seed = freshSeed();\n }\n } else {\n seed = freshSeed();\n }\n\n // Credentials an earlier version wrote into the AsyncStorage record.\n const plaintext =\n vault &&\n (seed.session_token !== null ||\n seed.refresh_token !== null ||\n seed.expires_at !== null)\n ? {\n appUserId: seed.app_user_id,\n slice: {\n session_token: seed.session_token,\n refresh_token: seed.refresh_token,\n expires_at: seed.expires_at,\n },\n }\n : null;\n\n // A vault that can't be read right now (a locked keystore, a native\n // error) must not be overwritten, so writes wait for a successful read.\n let vaultReadable = true;\n let seq = seed.write_seq;\n if (vault && recordUnreadable) {\n vaultReadable = false;\n } else if (vault) {\n let fromVault: TokenSlice | null = null;\n try {\n fromVault = await vault.read();\n } catch {\n vaultReadable = false;\n }\n if (fromVault) seq = Math.max(seq, seqOf(fromVault.write_seq) ?? 0);\n // Use the vault entry only when it belongs to this install's record:\n // the record exists and stands by the entry (`entryBelongs`).\n const belongs =\n fromVault !== null &&\n recordPresent &&\n entryBelongs(\n fromVault,\n seed.anonymous_id,\n seed.app_user_id,\n seed.install_id,\n seed.write_seq,\n seed.vault_seq,\n );\n if (fromVault && belongs) {\n seed.session_token = fromVault.session_token;\n seed.refresh_token = fromVault.refresh_token;\n seed.expires_at = fromVault.expires_at;\n if (fromVault.app_user_id != null) seed.app_user_id = fromVault.app_user_id;\n } else if (fromVault) {\n // An entry the record doesn't stand by (an earlier install's, or one\n // the record withdrew from). The record moves past its sequence\n // number, so the entry never counts as newer later on, even when\n // removing it below fails.\n seed.write_seq = Math.max(\n seed.write_seq,\n seqOf(fromVault.write_seq) ?? 0,\n );\n seed.vault_seq = null;\n }\n }\n // An unreadable vault or record may still hold the session, so a user id\n // without tokens stays while either can't be read (`settleSeed`).\n settleSeed(seed, vaultReadable && !recordUnreadable);\n\n const store = new IdentityStore(\n persistence,\n seed,\n vault,\n plaintext,\n !vaultReadable,\n seq,\n recordUnreadable,\n temporaryAnonymousId,\n );\n // Always persist after hydration — handles the first-run mint of\n // `anonymous_id`, clears any expired tokens from disk, and moves\n // AsyncStorage credentials into the vault.\n await store.flush({ hydrating: true });\n store.launchRefreshToken = store.refreshToken;\n return store;\n }\n\n snapshot(): IdentitySnapshot {\n return {\n anonymousId: this.anonymousId,\n appUserId: this.appUserId,\n sessionToken: this.sessionToken,\n refreshToken: this.refreshToken,\n expiresAt: this.expiresAt,\n anonymousSession: this.anonymousSession,\n signInRequired: this.signInRequired,\n };\n }\n\n /** The current generation (see `CommitFence`). */\n getGeneration(): number {\n return this.generation;\n }\n\n /**\n * Whether the store still holds the session it was created with, renewed\n * or ended since but never replaced by a sign-in: it was created holding\n * `refreshToken`, and no sign-in has committed since.\n */\n continuesLaunchSession(refreshToken: string): boolean {\n return (\n this.launchRefreshToken === refreshToken && this.signInsSinceLaunch === 0\n );\n }\n\n /** See `IdentitySnapshot.signInRequired`. */\n isSignInRequired(): boolean {\n return this.signInRequired;\n }\n\n /**\n * Detach the store (the SDK was configured again). Nothing commits or\n * persists after this; the promise settles once the writes already under\n * way have landed.\n */\n dispose(): Promise<void> {\n this.disposed = true;\n return this.flushChain;\n }\n\n /** Undo `dispose` (a `configure()` that failed before it replaced this store). */\n reattach(): void {\n this.disposed = false;\n }\n\n /** Whether a write under `fence` may commit now. */\n private mayCommit(fence: CommitFence): boolean {\n if (this.disposed) return false;\n return (\n fence.ifGeneration === undefined || fence.ifGeneration === this.generation\n );\n }\n\n /** Commit bookkeeping: advance the generation unless the fence keeps it. */\n private committed(fence: CommitFence): number {\n if (fence.advance !== false) this.generation += 1;\n return this.generation;\n }\n\n /**\n * Called when a stored session that couldn't be read at launch (a locked\n * vault or credential record) is loaded after it, so the SDK can tell its\n * subscribers. A session loaded while the store is created is not\n * reported.\n */\n onStoredSessionLoaded(listener: () => void): void {\n this.storedSessionLoaded = listener;\n }\n\n /**\n * Called when the stand-in anonymous id gives way to the device's own (see\n * `temporaryAnonymousId`): a later read found it, or a sign-in gave one.\n * A sign-out that rotates the id is not reported: it starts over.\n */\n onAnonymousIdKnown(listener: () => void): void {\n this.anonymousIdKnown = listener;\n }\n\n /** Whether the anonymous id is a stand-in (see `temporaryAnonymousId`). */\n hasStandInAnonymousId(): boolean {\n return this.temporaryAnonymousId;\n }\n\n /** The stand-in gives way to a known anonymous id (see `onAnonymousIdKnown`). */\n private anonymousIdNowKnown(): void {\n if (!this.temporaryAnonymousId) return;\n this.temporaryAnonymousId = false;\n if (!this.disposed) this.anonymousIdKnown?.();\n }\n\n /** Report a session a late read loaded (see `onStoredSessionLoaded`). */\n private reportStoredSession(): void {\n // A session loaded late is the one this store launched with, as long as\n // no sign-in came first (see `continuesLaunchSession`).\n if (this.signInsSinceLaunch === 0 && this.launchRefreshToken === null) {\n this.launchRefreshToken = this.refreshToken;\n }\n if (this.disposed) return;\n if (this.sessionToken === null && this.refreshToken === null) return;\n this.storedSessionLoaded?.();\n }\n\n /**\n * Whether the record's credential part still can't be read (see\n * `PersistedIdentity.credentials_unreadable`). When the launch read\n * failed, the record is read again first. A session it holds is loaded\n * when nothing has replaced the launch state since; the stored anonymous\n * id replaces the stand-in either way.\n */\n async recordStillUnreadable(): Promise<boolean> {\n if (!this.recordUnreadable) return false;\n let parsed: Partial<PersistedIdentity> | null = null;\n try {\n const raw = await this.persistence.reread(IDENTITY_KEY);\n parsed = raw ? (JSON.parse(raw) as Partial<PersistedIdentity>) : null;\n } catch {\n return true;\n }\n if (!this.recordUnreadable) return false;\n if (parsed?.credentials_unreadable === true) return true;\n this.recordUnreadable = false;\n const storedAnonymousId =\n parsed && typeof parsed.anonymous_id === \"string\"\n ? parsed.anonymous_id\n : null;\n const untouched =\n this.generation === 0 &&\n this.sessionToken === null &&\n this.refreshToken === null;\n if (parsed && storedAnonymousId !== null && untouched) {\n if (!this.vault) {\n const stored = seedFrom(parsed);\n settleSeed(stored, true);\n this.anonymousId = stored.anonymous_id;\n this.appUserId = stored.app_user_id;\n this.sessionToken = stored.session_token;\n this.refreshToken = stored.refresh_token;\n this.expiresAt = stored.expires_at;\n this.anonymousSession = stored.anonymous_session;\n this.signInRequired = stored.sign_in_required;\n this.seq = Math.max(this.seq, stored.write_seq);\n this.reportStoredSession();\n this.anonymousIdNowKnown();\n return false;\n }\n // With a vault, the record as read now binds the vault entry\n // (`entryBelongs`); the entry is read next, as `create` would have.\n const stored = seedFrom(parsed);\n this.anonymousId = stored.anonymous_id;\n const wasStandIn = this.temporaryAnonymousId;\n this.temporaryAnonymousId = false;\n this.appUserId = stored.app_user_id;\n this.anonymousSession = stored.anonymous_session;\n this.signInRequired = stored.sign_in_required;\n this.recordSeq = stored.write_seq;\n this.vaultSeq = stored.vault_seq;\n this.seq = Math.max(this.seq, stored.write_seq);\n await this.vaultStillUnreadable();\n if (\n this.sessionToken === null &&\n this.refreshToken === null &&\n (stored.session_token !== null || stored.refresh_token !== null)\n ) {\n // Credentials an earlier setup left in the record: used now and\n // moved into the vault by the next write, as at launch.\n this.plaintext = {\n appUserId: stored.app_user_id,\n slice: {\n session_token: stored.session_token,\n refresh_token: stored.refresh_token,\n expires_at: stored.expires_at,\n },\n };\n const expired = isExpired(stored.expires_at);\n this.sessionToken = expired ? null : stored.session_token;\n this.refreshToken = stored.refresh_token;\n this.expiresAt = expired ? null : stored.expires_at;\n this.reportStoredSession();\n }\n if (\n !this.vaultUnreadable &&\n this.sessionToken === null &&\n this.refreshToken === null\n ) {\n // No entry for this record: nobody is signed in (`settleSeed`).\n if (this.appUserId !== null && this.anonymousSession === false) {\n this.anonymousId = uuidv4();\n }\n this.appUserId = null;\n this.anonymousSession = null;\n }\n if (wasStandIn && !this.disposed) this.anonymousIdKnown?.();\n return false;\n }\n if (this.temporaryAnonymousId && storedAnonymousId !== null) {\n this.anonymousId = storedAnonymousId;\n }\n this.anonymousIdNowKnown();\n return false;\n }\n\n /**\n * Whether the stored session can't be read right now: the record's\n * credential part (see `recordStillUnreadable`) or the vault (see\n * `vaultStillUnreadable`). The record is read first: it binds the entry.\n */\n async credentialsStillUnreadable(): Promise<boolean> {\n if (await this.recordStillUnreadable()) return true;\n return this.vaultStillUnreadable();\n }\n\n /**\n * Whether the stored credentials were unreadable at the last attempt. A\n * call that needs a session reports `SECURE_STORE_UNAVAILABLE` then.\n */\n credentialsUnknown(): boolean {\n return this.recordUnreadable || this.vaultUnreadable;\n }\n\n /**\n * Whether the anonymous id is still a stand-in after the record is read\n * again (see `temporaryAnonymousId`). Anything sent under it would belong\n * to nobody.\n */\n async anonymousIdStillUnknown(): Promise<boolean> {\n if (!this.temporaryAnonymousId) return false;\n await this.recordStillUnreadable();\n return this.temporaryAnonymousId;\n }\n\n /**\n * Whether the vault still can't be read. When the launch read failed, the\n * vault is read again first, and a session it holds for this install is\n * loaded (same rule as `create`). While it can't be read, the stored\n * session is unknown: callers must not replace it with a new user.\n */\n async vaultStillUnreadable(): Promise<boolean> {\n if (!this.vault || !this.vaultUnreadable) return false;\n // The record binds the entry; it is read first.\n if (this.recordUnreadable) return true;\n let stored: TokenSlice | null;\n try {\n stored = await this.vault.read();\n } catch {\n return true;\n }\n this.vaultUnreadable = false;\n this.noteStoredSeq(stored);\n if (this.sessionToken === null && this.refreshToken === null) {\n this.adoptStored(stored);\n this.reportStoredSession();\n }\n return false;\n }\n\n /** Keep the next sequence number above an entry read from the vault. */\n private noteStoredSeq(stored: TokenSlice | null): void {\n if (stored) this.seq = Math.max(this.seq, seqOf(stored.write_seq) ?? 0);\n }\n\n /** Load a stored vault session that belongs to this install. */\n private adoptStored(stored: TokenSlice | null): void {\n if (\n !stored ||\n (stored.session_token === null && stored.refresh_token === null) ||\n // The record as it stands now: a sign-in or sign-out since launch has\n // withdrawn its vouch for an entry it never saw.\n !entryBelongs(\n stored,\n this.anonymousId,\n this.appUserId,\n this.installIdStored ? this.installId : null,\n this.recordSeq,\n this.vaultSeq,\n )\n ) {\n return;\n }\n this.sessionToken = isExpired(stored.expires_at)\n ? null\n : stored.session_token;\n this.refreshToken = stored.refresh_token;\n this.expiresAt = isExpired(stored.expires_at) ? null : stored.expires_at;\n if (stored.app_user_id != null) this.appUserId = stored.app_user_id;\n this.vaultHolds = {\n refreshToken: this.refreshToken,\n appUserId: this.appUserId,\n };\n }\n\n getAnonymousId(): string {\n return this.anonymousId;\n }\n\n getAppUserId(): string | null {\n return this.appUserId;\n }\n\n getSessionToken(): string | null {\n return this.sessionToken;\n }\n\n getRefreshToken(): string | null {\n return this.refreshToken;\n }\n\n isAuthenticated(): boolean {\n return this.sessionToken !== null;\n }\n\n /**\n * Store a session. `anonymous` records whether it is a purely anonymous\n * one (true only for `/v1/client/auth/anonymous`); omit it to keep the\n * current value (a token refresh of the same session). A stored session\n * ends a pending `signInRequired`.\n *\n * Resolves the generation the write committed at, or null when the fence\n * refused it (nothing changed).\n */\n async setSession(\n args: {\n appUserId: string;\n sessionToken: string;\n refreshToken: string;\n expiresAt: string;\n anonymous?: boolean;\n },\n fence: CommitFence = {},\n ): Promise<number | null> {\n if (!this.mayCommit(fence)) return null;\n this.appUserId = args.appUserId;\n this.sessionToken = args.sessionToken;\n this.refreshToken = args.refreshToken;\n this.expiresAt = args.expiresAt;\n if (args.anonymous !== undefined) this.anonymousSession = args.anonymous;\n this.signInRequired = false;\n if (fence.advance !== false) this.signInsSinceLaunch += 1;\n const generation = this.committed(fence);\n await this.flush();\n return generation;\n }\n\n /**\n * The current session gained an identity (a link). False when the fence\n * refused the write.\n */\n async markIdentified(fence: CommitFence = {}): Promise<boolean> {\n if (!this.mayCommit(fence)) return false;\n if (this.anonymousSession === false) return true;\n this.anonymousSession = false;\n await this.flush();\n return true;\n }\n\n /**\n * Adopt the anonymous id the server holds for this install's user: the id\n * an anonymous sign-in answered with, or the fresh id a link gave the user.\n * False when the fence refused the write.\n */\n async setAnonymousId(\n anonymousId: string,\n fence: CommitFence = {},\n ): Promise<boolean> {\n if (!this.mayCommit(fence)) return false;\n if (anonymousId !== this.anonymousId) {\n this.anonymousId = anonymousId;\n this.anonymousIdNowKnown();\n await this.flush();\n return true;\n }\n this.anonymousIdNowKnown();\n return true;\n }\n\n /**\n * End the session. The anonymous id is rotated when `rotateAnonymousId`\n * is set, and whenever the session being ended was not purely anonymous:\n * that id then identifies an account with a real sign-in, and the next\n * person on the device must start fresh. A session of unknown kind (stored\n * by an SDK before 4.0.14) counts as identified, unless\n * `keepIdWhenKindUnknown` is set (the server decides on the next\n * anonymous sign-in).\n */\n async clearSession(\n rotateAnonymousId: boolean,\n opts: {\n keepIdWhenKindUnknown?: boolean;\n /**\n * Record that the user must sign in again (an identified session\n * ended without a sign-out). Any other clear ends a pending one.\n */\n signInRequired?: boolean;\n /** Commit only while the generation is unchanged (see `CommitFence`). */\n ifGeneration?: number;\n /**\n * A sign-out that started at this generation: it clears the session\n * unless a sign-in stored a newer one since. A session that merely\n * ended meanwhile is cleared like any other.\n */\n unlessSignedInSince?: number;\n } = {},\n ): Promise<boolean> {\n if (!this.mayCommit({ ifGeneration: opts.ifGeneration })) return false;\n const hadSession = this.sessionToken !== null || this.refreshToken !== null;\n // A stored session this launch couldn't read (a locked device) may be\n // signed in; it counts as a session for the anonymous id's rotation.\n const unreadSession = this.credentialsUnknown() && this.appUserId !== null;\n if (\n opts.unlessSignedInSince !== undefined &&\n opts.unlessSignedInSince !== this.generation &&\n hadSession\n ) {\n return false;\n }\n const identified =\n this.anonymousSession === false ||\n (this.anonymousSession === null && !opts.keepIdWhenKindUnknown);\n const rotate =\n rotateAnonymousId || ((hadSession || unreadSession) && identified);\n const nextAnonymousId = rotate ? uuidv4() : this.anonymousId;\n if (rotate) this.temporaryAnonymousId = false;\n this.appUserId = null;\n this.sessionToken = null;\n this.refreshToken = null;\n this.expiresAt = null;\n this.anonymousSession = null;\n this.signInRequired = opts.signInRequired === true;\n this.anonymousId = nextAnonymousId;\n this.committed({});\n await this.flush({ signingOut: true });\n return true;\n }\n\n /**\n * Persist the identity. `hydrating` marks the write that follows `create`:\n * when the vault was unreadable and a retry now succeeds, the session it\n * holds is adopted instead of overwritten. `signingOut` marks the write\n * from `clearSession`: when the vault can't be emptied, the install moves\n * to a new anonymous id so the entry left behind no longer belongs to it.\n */\n private flush(opts?: {\n hydrating?: boolean;\n signingOut?: boolean;\n }): Promise<void> {\n // A write queued before `dispose` still lands; none is queued after it.\n if (this.disposed) return Promise.resolve();\n const run = this.flushChain.then(() => this.flushNow(opts));\n this.flushChain = run.catch(() => undefined);\n return run;\n }\n\n private async flushNow(opts?: {\n hydrating?: boolean;\n signingOut?: boolean;\n }): Promise<void> {\n // A record whose credentials couldn't be read is read again first. Until\n // it can be, the launch state (which lacks them) never overwrites it. A\n // sign-in or sign-out since launch replaces what was stored and is\n // written, its stand-in anonymous id marked so the storage layer keeps\n // the one it holds (`anonymous_id_unknown`).\n if ((await this.recordStillUnreadable()) && this.generation === 0) return;\n if (!this.vault) {\n const seq = ++this.seq;\n const recordSeq = opts?.hydrating ? this.recordSeq : seq;\n const payload: PersistedIdentity = {\n anonymous_id: this.anonymousId,\n app_user_id: this.appUserId,\n session_token: this.sessionToken,\n refresh_token: this.refreshToken,\n expires_at: this.expiresAt,\n anonymous_session: this.anonymousSession,\n sign_in_required: this.signInRequired,\n write_seq: recordSeq,\n vault_seq: null,\n install_id: this.installId,\n ...(this.temporaryAnonymousId ? { anonymous_id_unknown: true } : {}),\n };\n await this.persistence.write(IDENTITY_KEY, JSON.stringify(payload));\n this.recordSeq = recordSeq;\n this.installIdStored = true;\n return;\n }\n // The vault is written first. New credentials never reach AsyncStorage\n // while a vault is in use; the only tokens AsyncStorage may still hold\n // are the ones an earlier version wrote, and they go once the vault has\n // the session.\n if (this.vaultUnreadable) {\n // Retry the read; until it succeeds, the stored entry stays untouched.\n try {\n const stored = await this.vault.read();\n this.vaultUnreadable = false;\n this.noteStoredSeq(stored);\n // At hydration nothing has replaced the stored session yet: load it.\n // Later writes (sign-in, refresh, sign-out) are newer and win.\n // Same rule as `create` (`entryBelongs`).\n if (opts?.hydrating) this.adoptStored(stored);\n } catch {\n // Still unreadable: keep holding the write back.\n }\n }\n const seq = ++this.seq;\n // A sign-out withdraws the record's backing before it touches the vault,\n // so a removal that fails followed by a lost record write still leaves a\n // record that stands by nothing.\n if (opts?.signingOut) {\n this.vaultSeq = null;\n try {\n await this.persistence.write(\n IDENTITY_KEY,\n JSON.stringify(this.recordFor(seq, false)),\n );\n this.recordSeq = seq;\n this.installIdStored = true;\n } catch {\n // The record write below tries again.\n }\n }\n // The record learns the install id before any vault entry carries it, so\n // an entry this install wrote always names an id its record knows. Only\n // the hydrating write does this: it changes no session, so the record it\n // writes early describes the same session as the vault, and when that\n // write fails the vault write waits for a later flush.\n let holdVaultWrite = false;\n if (!this.installIdStored && opts?.hydrating && !this.vaultUnreadable) {\n try {\n await this.persistence.write(\n IDENTITY_KEY,\n JSON.stringify(this.recordFor(this.recordSeq, false)),\n );\n this.installIdStored = true;\n } catch {\n holdVaultWrite = true;\n }\n }\n // The slice is taken now: a sign-in that lands while the write is out\n // changes the live fields, and the record must describe what the vault\n // actually took.\n const slice: TokenSlice = {\n session_token: this.sessionToken,\n refresh_token: this.refreshToken,\n expires_at: this.expiresAt,\n anonymous_id: this.anonymousId,\n app_user_id: this.appUserId,\n write_seq: seq,\n install_id: this.installId,\n };\n let saved = false;\n if (!this.vaultUnreadable && !holdVaultWrite) {\n try {\n await this.vault.write(slice);\n saved = true;\n this.vaultSeq = seq;\n this.vaultHolds = {\n refreshToken: slice.refresh_token,\n appUserId: slice.app_user_id ?? null,\n };\n } catch (err) {\n // The session stays live in memory; only its survival across a\n // restart depends on the next successful write. Failing the sign-in\n // over a keystore error would be worse for the user.\n if (typeof console !== \"undefined\") {\n console.warn(\"[amba] could not save the session to the secure store\", err);\n }\n }\n }\n // The record stands by the vault entry only while that entry holds the\n // session the record describes (`entryBelongs`): a session the vault\n // didn't take (a failed or held write, or one that changed while the\n // write was out) withdraws it, and so does a sign-out that couldn't\n // empty the vault (unreadable keystore, a failed or refused removal). A\n // new anonymous id then also leaves that entry unbound, so the next\n // launch discards it. A write that only moves the anonymous id (a link)\n // keeps it.\n const vaultHasLive =\n this.vaultHolds.refreshToken === this.refreshToken &&\n this.vaultHolds.appUserId === this.appUserId;\n if ((opts?.signingOut && !saved) || !vaultHasLive) {\n this.vaultSeq = null;\n }\n if (opts?.signingOut && !saved) {\n this.anonymousId = uuidv4();\n if (typeof console !== \"undefined\") {\n console.warn(\n \"[amba] could not clear the secure store at sign-out; starting a new anonymous id so the stored session can't be restored\",\n );\n }\n }\n // A hydrating write changes no session. The record keeps its sequence\n // number unless the vault took this write, so an entry a stopped flush\n // left newer than the record stays the newest, and a record in step\n // with its entry stays in step.\n const recordSeq = opts?.hydrating && !saved ? this.recordSeq : seq;\n const record = this.recordFor(recordSeq, saved && vaultHasLive);\n await this.persistence.write(IDENTITY_KEY, JSON.stringify(record));\n this.recordSeq = recordSeq;\n this.installIdStored = true;\n if (record.refresh_token === null) this.plaintext = null;\n }\n\n /**\n * The AsyncStorage record for a vault in use. It keeps the earlier\n * version's plaintext copy only while that copy is still the live session\n * (same user, same refresh token) and the vault doesn't hold it yet\n * (`saved`). Sign-out, another user or a refresh (which retires the old\n * refresh token) drops it.\n */\n private recordFor(recordSeq: number, saved: boolean): PersistedIdentity {\n const keep =\n !saved &&\n this.plaintext !== null &&\n this.refreshToken !== null &&\n this.plaintext.appUserId === this.appUserId &&\n this.plaintext.slice.refresh_token === this.refreshToken\n ? this.plaintext.slice\n : null;\n return {\n anonymous_id: this.anonymousId,\n app_user_id: this.appUserId,\n session_token: keep?.session_token ?? null,\n refresh_token: keep?.refresh_token ?? null,\n expires_at: keep?.expires_at ?? null,\n anonymous_session: this.anonymousSession,\n sign_in_required: this.signInRequired,\n write_seq: recordSeq,\n vault_seq: this.vaultSeq,\n install_id: this.installId,\n ...(this.temporaryAnonymousId ? { anonymous_id_unknown: true } : {}),\n };\n }\n}\n\nfunction freshSeed(): PersistedIdentity {\n return {\n anonymous_id: uuidv4(),\n app_user_id: null,\n session_token: null,\n refresh_token: null,\n expires_at: null,\n anonymous_session: null,\n sign_in_required: false,\n write_seq: 0,\n vault_seq: null,\n install_id: null,\n };\n}\n\n/** The identity a parsed record describes, with defaults for missing fields. */\nfunction seedFrom(parsed: Partial<PersistedIdentity>): PersistedIdentity {\n return {\n anonymous_id: parsed.anonymous_id ?? uuidv4(),\n app_user_id: parsed.app_user_id ?? null,\n session_token: parsed.session_token ?? null,\n refresh_token: parsed.refresh_token ?? null,\n expires_at: parsed.expires_at ?? null,\n anonymous_session:\n typeof parsed.anonymous_session === \"boolean\"\n ? parsed.anonymous_session\n : null,\n sign_in_required: parsed.sign_in_required === true,\n write_seq: seqOf(parsed.write_seq) ?? 0,\n // A record from before 4.0.14 has no `vault_seq` and vouches for\n // the entry of its time (0).\n vault_seq: parsed.vault_seq === undefined ? 0 : seqOf(parsed.vault_seq),\n install_id: typeof parsed.install_id === \"string\" ? parsed.install_id : null,\n };\n}\n\n/**\n * Settle a hydrated identity. With the credentials known (`readable`), a\n * user id with no token to act on is stale (memory storage after a\n * relaunch, a vault entry that was discarded): nobody is signed in, and the\n * session kind goes with it. A session that ended this way after it gained\n * an identity rotates the anonymous id, as `clearSession` would. An expired\n * session token is dropped; its refresh token stays so the session can be\n * renewed.\n */\nfunction settleSeed(seed: PersistedIdentity, readable: boolean): void {\n if (readable && seed.session_token === null && seed.refresh_token === null) {\n if (seed.app_user_id !== null && seed.anonymous_session === false) {\n seed.anonymous_id = uuidv4();\n }\n seed.app_user_id = null;\n seed.anonymous_session = null;\n }\n if (isExpired(seed.expires_at)) {\n seed.session_token = null;\n seed.expires_at = null;\n }\n}\n","/**\n * Where the SDK keeps the session token, refresh token and expiry between\n * launches.\n *\n * The identity record has two parts. The public part (`anonymous_id`,\n * `app_user_id`) always lives in AsyncStorage. The credential part goes\n * wherever `AmbaConfig.tokenStorage` says:\n *\n * - `\"secure\"` (default): a {@link TokenVault} backed by the platform\n * keystore. The SDK uses `AmbaConfig.secureStorage` when the app supplies\n * one, otherwise `react-native-keychain` when it is installed (iOS\n * Keychain, Android Keystore). With neither available the credentials stay\n * in memory, and the SDK logs one warning.\n * - `\"memory\"`: credentials stay in memory. Every launch starts signed out.\n * - `\"asyncStorage\"`: credentials go into the AsyncStorage record next to\n * the public part, as plaintext in the app sandbox. Opt in only when a\n * lost session on restart is worse than that exposure for your app.\n */\n\n/**\n * A key/value store backed by the platform keystore. Values are small JSON\n * strings (well under 2 KB). Pass one through `AmbaConfig.secureStorage` to\n * use a secure-storage module other than `react-native-keychain`.\n */\nexport interface SecureTokenStorage {\n getItem(key: string): Promise<string | null>;\n setItem(key: string, value: string): Promise<void>;\n removeItem(key: string): Promise<void>;\n}\n\n/** `AmbaConfig.tokenStorage` values. */\nexport type TokenStorageMode = \"secure\" | \"memory\" | \"asyncStorage\";\n\nexport const TOKEN_STORAGE_MODES: readonly TokenStorageMode[] = [\n \"secure\",\n \"memory\",\n \"asyncStorage\",\n];\n\n/**\n * The credential part of the identity record. `install_id` and `write_seq`\n * (the sequence number of the flush that wrote it) tie a stored entry to\n * the install's record; an entry from before 4.0.14 binds by `anonymous_id`.\n * `app_user_id` names the user it signs in.\n */\nexport interface TokenSlice {\n session_token: string | null;\n refresh_token: string | null;\n expires_at: string | null;\n anonymous_id?: string | null;\n app_user_id?: string | null;\n write_seq?: number | null;\n install_id?: string | null;\n}\n\n/**\n * Holds the credential part of the identity record. `read()` resolves null\n * when nothing is stored; `write()` with every field null removes the entry.\n */\nexport interface TokenVault {\n readonly kind: \"secure\" | \"memory\";\n read(): Promise<TokenSlice | null>;\n write(slice: TokenSlice): Promise<void>;\n}\n\nfunction isEmpty(slice: TokenSlice): boolean {\n return (\n slice.session_token === null &&\n slice.refresh_token === null &&\n slice.expires_at === null\n );\n}\n\n/**\n * Memory-held credentials, keyed like the keystore entry. They last as long\n * as the JS runtime, so a second `Amba.configure()` (Fast Refresh, a\n * re-configure) keeps the session, and a relaunch starts signed out.\n */\nconst memorySlices = new Map<string, TokenSlice>();\n\n/** Credentials held in this JS runtime only. */\nexport class MemoryTokenVault implements TokenVault {\n readonly kind = \"memory\" as const;\n\n constructor(private readonly key: string) {}\n\n async read(): Promise<TokenSlice | null> {\n const slice = memorySlices.get(this.key);\n return slice ? { ...slice } : null;\n }\n\n async write(slice: TokenSlice): Promise<void> {\n if (isEmpty(slice)) memorySlices.delete(this.key);\n else memorySlices.set(this.key, { ...slice });\n }\n}\n\n/**\n * Keystore key for a storage prefix. Kept to `[A-Za-z0-9_.-]` because some\n * secure stores (for example `expo-secure-store`) reject other characters.\n */\nexport function secureTokenKey(prefix: string): string {\n return `${prefix.replace(/[^A-Za-z0-9_.-]/g, \"_\")}_session_tokens_v1`;\n}\n\n/** Credentials in a keystore-backed {@link SecureTokenStorage}. */\nexport class SecureTokenVault implements TokenVault {\n readonly kind = \"secure\" as const;\n\n constructor(\n private readonly storage: SecureTokenStorage,\n private readonly key: string,\n ) {}\n\n async read(): Promise<TokenSlice | null> {\n const raw = await this.storage.getItem(this.key);\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw) as Partial<TokenSlice>;\n return {\n session_token: parsed.session_token ?? null,\n refresh_token: parsed.refresh_token ?? null,\n expires_at: parsed.expires_at ?? null,\n anonymous_id: parsed.anonymous_id ?? null,\n app_user_id: parsed.app_user_id ?? null,\n write_seq: parsed.write_seq ?? null,\n install_id: parsed.install_id ?? null,\n };\n } catch {\n return null;\n }\n }\n\n async write(slice: TokenSlice): Promise<void> {\n if (isEmpty(slice)) {\n try {\n await this.storage.removeItem(this.key);\n } catch (err) {\n // A removal that fails leaves the session behind. An entry that\n // holds no tokens takes its place, so nothing signs back in from\n // it; the write fails only when that is refused too.\n try {\n await this.storage.setItem(this.key, JSON.stringify(slice));\n } catch {\n throw err;\n }\n }\n return;\n }\n await this.storage.setItem(this.key, JSON.stringify(slice));\n }\n}\n\n/** The slice of `react-native-keychain` the SDK calls. */\ninterface KeychainLike {\n setGenericPassword(\n username: string,\n password: string,\n options?: { service?: string; accessible?: unknown },\n ): Promise<unknown>;\n getGenericPassword(options?: {\n service?: string;\n }): Promise<false | { password: string }>;\n resetGenericPassword(options?: { service?: string }): Promise<unknown>;\n ACCESSIBLE?: { AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY?: unknown };\n}\n\nfunction isKeychainLike(mod: unknown): mod is KeychainLike {\n const k = mod as Partial<KeychainLike> | null;\n return (\n typeof k === \"object\" &&\n k !== null &&\n typeof k.setGenericPassword === \"function\" &&\n typeof k.getGenericPassword === \"function\" &&\n typeof k.resetGenericPassword === \"function\"\n );\n}\n\n/**\n * Adapt `react-native-keychain` to {@link SecureTokenStorage}. Items use the\n * `AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY` class on iOS: readable after the\n * first unlock (so background refreshes work) and left behind by backups\n * restored onto another device.\n */\nexport function keychainStorage(keychain: KeychainLike): SecureTokenStorage {\n const accessible = keychain.ACCESSIBLE?.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY;\n return {\n async getItem(key) {\n const entry = await keychain.getGenericPassword({ service: key });\n return entry ? entry.password : null;\n },\n async setItem(key, value) {\n const ok = await keychain.setGenericPassword(\"amba\", value, {\n service: key,\n ...(accessible !== undefined ? { accessible } : {}),\n });\n if (ok === false) {\n throw new Error(\"react-native-keychain refused to store the session\");\n }\n },\n async removeItem(key) {\n const removed = await keychain.resetGenericPassword({ service: key });\n // `false` can also mean there was nothing to remove; only a remaining\n // entry is a failure.\n if (removed === false && (await keychain.getGenericPassword({ service: key }))) {\n throw new Error(\"react-native-keychain could not remove the session\");\n }\n },\n };\n}\n\n// Memoized probe: `undefined` = not probed yet, `null` = not installed.\nlet cachedKeychain: SecureTokenStorage | null | undefined;\n\n/**\n * Load `react-native-keychain` when the app has it installed. Resolves null\n * otherwise (module missing, or a build without its native half).\n */\nexport async function loadKeychainStorage(): Promise<SecureTokenStorage | null> {\n if (cachedKeychain !== undefined) return cachedKeychain;\n try {\n // Literal specifier inside try: bundlers treat it as an optional\n // dependency, so apps without the module still build.\n const mod = (await import(\"react-native-keychain\")) as unknown as {\n default?: unknown;\n };\n const candidate = isKeychainLike(mod) ? mod : mod.default;\n cachedKeychain = isKeychainLike(candidate) ? keychainStorage(candidate) : null;\n } catch {\n cachedKeychain = null;\n }\n return cachedKeychain;\n}\n\n/** Test-only: forget the memoized keychain probe. */\nexport function _resetKeychainProbeForTests(): void {\n cachedKeychain = undefined;\n}\n\nlet memoryFallbackWarned = false;\n\n/**\n * Pick the vault for `mode`. Resolves null for `\"asyncStorage\"`, which keeps\n * the credentials inside the AsyncStorage identity record.\n */\nexport async function resolveTokenVault(\n mode: TokenStorageMode,\n prefix: string,\n secureStorage: SecureTokenStorage | undefined,\n): Promise<TokenVault | null> {\n if (mode === \"asyncStorage\") return null;\n const key = secureTokenKey(prefix);\n if (mode === \"memory\") return new MemoryTokenVault(key);\n const storage = secureStorage ?? (await loadKeychainStorage());\n if (storage) return new SecureTokenVault(storage, key);\n if (!memoryFallbackWarned && typeof console !== \"undefined\") {\n memoryFallbackWarned = true;\n console.warn(\n \"[amba] No secure token store found, so the session is kept in memory and users sign in again after every launch. \" +\n \"Install react-native-keychain (or pass `secureStorage` to Amba.configure) to keep sessions across launches in the Keychain/Keystore.\",\n );\n }\n return new MemoryTokenVault(key);\n}\n","import { AmbaApiError } from \"./error.js\";\n\n/**\n * Encode one caller-supplied id, key, slug or name as a single URL path\n * segment. `encodeURIComponent` escapes `/`, `?` and `#`, so the value stays\n * inside its segment. `.` and `..` are refused because URL resolution treats\n * them as steps to another route, and the empty string is refused because it\n * collapses onto the parent route.\n */\nexport function pathSegment(value: string): string {\n if (value === \"\" || value === \".\" || value === \"..\") {\n throw new AmbaApiError(\n \"VALIDATION_ERROR\",\n `path segment must not be ${value === \"\" ? \"empty\" : `'${value}'`}`,\n );\n }\n return encodeURIComponent(value);\n}\n","/**\n * Offline mutation queue + optimistic-update helpers — framework-agnostic\n * orchestration that sits ABOVE the collection / event write methods.\n *\n * Nothing here touches the SDK runtime directly: the queue is handed a\n * small `KvStore` (durable key/value persistence) and a `WriteExecutor`\n * (the function that actually performs a queued mutation against the API).\n * That keeps the same code identical across every JS SDK target — only the\n * adapters that supply storage + the executor differ per platform.\n *\n * Design:\n * - A write that fails because the device is offline (a transport-level\n * network error, or `navigator.onLine === false`) is appended to a\n * durable outbox instead of throwing all the way up.\n * - Each queued op carries a stable idempotency key. On replay the key is\n * forwarded to the API's idempotency support (collection inserts, and\n * the events route's keyed path for `track`) so a mutation that actually\n * reached the server before the connection dropped is never applied\n * twice.\n * - On reconnect (`online` event) the outbox is flushed in FIFO order;\n * each op is removed only after it succeeds, so a mid-flush failure\n * leaves the remainder intact for the next attempt.\n * - Each op records its owner (the user id and anonymous id live when it\n * was queued). A flush replays only the current owner's ops and holds\n * the rest until that owner signs back in; `pending()` lists only the\n * current owner's ops. The SDKs clear the whole outbox on sign-out.\n */\n\n/**\n * Minimal durable key/value contract the outbox persists through. Both the\n * browser `localStorage` adapter and the React Native AsyncStorage adapter\n * satisfy this — the queue never assumes anything else.\n */\nexport interface KvStore {\n get(key: string): Promise<string | null> | string | null;\n set(key: string, value: string): Promise<void> | void;\n delete(key: string): Promise<void> | void;\n}\n\n/**\n * The identity a queued op belongs to: the signed-in user id (null before any\n * session) and the install's anonymous id, both captured at enqueue.\n */\nexport interface OutboxOwner {\n appUserId: string | null;\n anonymousId: string | null;\n}\n\n/**\n * Whether `op` belongs to `current`. An op queued under a user id matches only\n * that user id. An op queued before any session matches the same anonymous id,\n * so a guest's offline write lands once that guest's session starts. An op\n * with no recorded owner (queued by an SDK version before owners existed)\n * matches nobody.\n */\nexport function outboxOwnerMatches(op: OutboxOp, current: OutboxOwner): boolean {\n const owner = op.owner;\n if (!owner) return false;\n if (owner.appUserId) return owner.appUserId === current.appUserId;\n return owner.anonymousId !== null && owner.anonymousId === current.anonymousId;\n}\n\n/**\n * Who started a write and the queue's purge generation at that moment,\n * captured before the live attempt (`OfflineQueue.ticket()`). A request can\n * outlive a sign-out; the ticket keeps its queued copy attributed to the user\n * who made it, and refuses it when the queue was purged meanwhile.\n */\nexport interface OutboxTicket {\n owner: OutboxOwner | undefined;\n generation: number;\n}\n\n/** The kinds of mutation the outbox can durably replay. */\nexport type OutboxOpKind =\n | \"collection.insert\"\n | \"collection.update\"\n | \"collection.delete\"\n | \"track\";\n\n/**\n * A single durably-queued mutation. `idempotencyKey` is reused verbatim on\n * every replay attempt so the server dedupes a collection insert or a\n * tracked event that landed before the connection dropped (`track` replays\n * go to the events route's keyed path, which records each key once).\n * `enqueuedAt` is an ISO timestamp for inspection.\n */\nexport interface OutboxOp {\n /** Stable per-op id (the idempotency key for collection writes). */\n idempotencyKey: string;\n kind: OutboxOpKind;\n /** Target collection name (absent for `track`). */\n collection?: string;\n /** Row id for update / delete. */\n id?: string;\n /** Insert row / update set payload. */\n payload?: Record<string, unknown>;\n /** Event name for `track`. */\n event?: string;\n /** Event properties for `track`. */\n properties?: Record<string, unknown>;\n /** Whether the tracked event was telemetry-tier. */\n telemetry?: boolean;\n /**\n * ISO timestamp the `track` originally OCCURRED, captured at enqueue. Replayed\n * as the events body `timestamp` (→ `occurred_at`) so a buffered event keeps\n * its real time instead of being re-stamped at flush. (Attribution is by the\n * session user at replay time — the events route has no body-id channel.)\n */\n occurredAt?: string;\n /**\n * Who queued the op. Stamped at enqueue when the queue has a `currentOwner`\n * source; a flush replays the op only while the same owner is signed in.\n */\n owner?: OutboxOwner;\n enqueuedAt: string;\n}\n\n/**\n * Performs one queued mutation against the live API. Implemented per-SDK so\n * the queue stays platform-agnostic. MUST forward `op.idempotencyKey` on\n * collection INSERTS (the API dedupes inserts on `(collection,\n * idempotency_key)`) and on `track` replays (the events route records each\n * `idempotency_key` once). Throws on failure — the queue decides whether to\n * retain or drop the op.\n */\nexport type WriteExecutor = (op: OutboxOp) => Promise<void>;\n\n/** Result of a {@link OfflineQueue.flush}. */\nexport interface FlushResult {\n /** Ops successfully replayed and removed from the outbox. */\n flushed: number;\n /**\n * Ops dropped because their replay failed deterministically (a coded server\n * rejection like `VALIDATION_ERROR` / `NOT_FOUND` that would fail identically\n * forever). Dropping them prevents one poisoned op from wedging the queue;\n * the drain continues past it.\n */\n dropped: number;\n /** Ops still pending after the flush (a replay failed and stopped it). */\n remaining: number;\n}\n\n/**\n * Best-effort online probe. In a browser/RN runtime `navigator.onLine`\n * exists; under Node / SSR it doesn't, so we treat \"unknown\" as online\n * (the executor's own error handling then decides).\n */\nexport function isOnline(): boolean {\n const nav =\n typeof navigator !== \"undefined\"\n ? (navigator as { onLine?: boolean })\n : undefined;\n if (nav && typeof nav.onLine === \"boolean\") return nav.onLine;\n return true;\n}\n\n/**\n * Error codes the server raised deterministically — replaying them would fail\n * identically, so they're NEVER queued even when the device reports offline.\n * (A request can fail validation/auth/etc. while `navigator.onLine` is briefly\n * false; we must not buffer-and-retry those forever.)\n */\nconst DETERMINISTIC_ERROR_CODES = new Set([\n \"VALIDATION_ERROR\",\n \"INVALID_ARGUMENT\",\n \"INVALID_INPUT\",\n \"UNAUTHORIZED\",\n \"FORBIDDEN\",\n \"NOT_FOUND\",\n \"CONFLICT\",\n \"CONSENT_NOT_GRANTED\",\n \"NOT_INITIALIZED\",\n // The events route's 400 answers for a body it refuses. A replayed\n // `track` sends the same body every time, so it would be refused forever.\n // `IDEMPOTENCY_KEY_CONFLICT` means the key already recorded an event.\n \"INVALID_JSON\",\n \"INVALID_BODY\",\n \"INVALID_EVENT\",\n \"RESERVED_EVENT_NAME\",\n \"INVALID_IDEMPOTENCY_KEY\",\n \"INVALID_TIMESTAMP\",\n \"INVALID_PROPERTIES\",\n \"INVALID_TELEMETRY\",\n \"IDEMPOTENCY_KEY_CONFLICT\",\n]);\n\n/**\n * Is this a deterministic server rejection — a coded error the server would\n * raise identically on every replay (bad input, auth, not-found, conflict)? A\n * queued op that fails this way is poison: retrying it forever would wedge the\n * queue, so the flush DROPS it. Everything else (transport failures, 5xx\n * `HTTP_ERROR`, `RATE_LIMITED`, `CIRCUIT_OPEN`, anything uncoded) is treated as\n * transient and RETAINED for a later attempt — favoring retain over data loss.\n */\nexport function isDeterministicError(err: unknown): boolean {\n const code = (err as { code?: unknown } | null | undefined)?.code;\n return typeof code === \"string\" && DETERMINISTIC_ERROR_CODES.has(code);\n}\n\n/**\n * Heuristic: does a thrown value look like a transient offline / transport\n * failure (vs a deterministic 4xx the server would reject again)? Used at\n * ENQUEUE time to decide whether a failed write is worth buffering — a\n * `VALIDATION_ERROR` will fail identically on replay, so it throws instead.\n *\n * A deterministic server error is excluded even when the device reports\n * offline: the request clearly reached the server (it got a coded response),\n * so retrying it on reconnect is pointless and would wedge the queue.\n *\n * NOTE: this is intentionally stricter than {@link isDeterministicError}'s\n * inverse — at enqueue time we only buffer GENUINE connectivity failures (a\n * non-offline 5xx/429 surfaces to the caller immediately rather than silently\n * queueing). The flush drop-decision uses {@link isDeterministicError} so a\n * transient 5xx/429 encountered DURING replay is retained, not dropped.\n */\nexport function isOfflineError(err: unknown): boolean {\n const code = (err as { code?: unknown } | null | undefined)?.code;\n if (typeof code === \"string\" && DETERMINISTIC_ERROR_CODES.has(code)) {\n return false;\n }\n if (code === \"NETWORK_ERROR\") return true;\n // `fetch` signals a transport failure (DNS / refused / offline) by throwing a\n // `TypeError` with a runtime-specific message — \"Failed to fetch\" (Chrome),\n // \"Network request failed\" (RN/Hermes), \"Load failed\" (Safari),\n // \"fetch failed\" (undici/Node). Match those messages rather than EVERY\n // TypeError, so a genuine programming `TypeError` (e.g. reading a property of\n // undefined) surfaces to the caller instead of being silently buffered —\n // especially on Node where `isOnline()` is always true.\n const msg = (err as { message?: unknown } | null | undefined)?.message;\n const looksLikeTransport =\n typeof msg === \"string\" &&\n /network (error|request failed)|failed to fetch|fetch failed|load failed/i.test(\n msg,\n );\n if (looksLikeTransport) return true;\n // No transport signal on the error itself — fall back to the runtime's\n // online state. Treat \"offline\" as queueable only for errors that DIDN'T\n // carry a deterministic server code (handled above).\n return !isOnline();\n}\n\n/**\n * Generate a stable idempotency key for a queued op: `amba_obx_` and a random\n * UUID. The server records event keys project-wide, so the UUID comes from\n * the runtime's secure random source: `crypto.randomUUID`, else\n * `crypto.getRandomValues` (Hermes has only the latter).\n */\nexport function newIdempotencyKey(): string {\n const c =\n typeof globalThis !== \"undefined\"\n ? (globalThis.crypto as\n | {\n randomUUID?: () => string;\n getRandomValues?: (array: Uint8Array) => Uint8Array;\n }\n | undefined)\n : undefined;\n if (c && typeof c.randomUUID === \"function\") return `amba_obx_${c.randomUUID()}`;\n if (c && typeof c.getRandomValues === \"function\") {\n const b = c.getRandomValues(new Uint8Array(16));\n // RFC 4122 version 4, variant 10.\n b[6] = (b[6]! & 0x0f) | 0x40;\n b[8] = (b[8]! & 0x3f) | 0x80;\n const h = Array.from(b, (x) => x.toString(16).padStart(2, \"0\")).join(\"\");\n return `amba_obx_${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;\n }\n // Last resort for a runtime with neither (the server still checks that a\n // reused key carries the same event).\n return `amba_obx_${Date.now().toString(36)}_${Math.random()\n .toString(36)\n .slice(2, 12)}`;\n}\n\n/**\n * Whether a `track` with these properties can take the events route's keyed\n * path, which accepts a plain object or no properties. A JS caller can pass\n * something else (an array); such an event stays on the unkeyed path, which\n * stores it as it always has.\n */\nexport function keyableProperties(properties: unknown): boolean {\n return (\n properties === undefined ||\n properties === null ||\n (typeof properties === \"object\" && !Array.isArray(properties))\n );\n}\n\n/**\n * Durable, ordered, idempotent offline outbox. Opt-in via `Amba.offline`.\n * Single in-flight flush is enforced so concurrent reconnect signals don't\n * double-replay.\n */\nexport class OfflineQueue {\n private readonly store: KvStore;\n private readonly executor: WriteExecutor;\n private readonly storageKey: string;\n /**\n * Notified when a queued op is dropped because its replay failed\n * deterministically (a poisoned op the server will always reject). Lets the\n * SDK surface the loss (debug log) instead of silently discarding.\n */\n private readonly onDropped?: (op: OutboxOp, err: unknown) => void;\n /**\n * The identity that is signed in right now. When set, enqueue stamps it on\n * each op and flush/pending only touch ops it owns.\n */\n private readonly currentOwner?: () => OutboxOwner;\n private flushing: Promise<FlushResult> | null = null;\n /**\n * Bumped by `purge()`. A replay that started before a purge sees the change\n * and does not re-queue its op.\n */\n private purgeGeneration = 0;\n /**\n * Serializes every read-modify-write of the persisted outbox. Each mutation\n * chains onto the previous one's promise, so concurrent `enqueue`s — and an\n * `enqueue` racing the per-op removal inside `flush` — can never read a stale\n * snapshot and clobber each other's writes (the lost-update bug). All\n * storage mutations funnel through `mutate()`; only `pendingCount`/`pending`\n * read without taking the lock (a stale-by-one read is harmless).\n */\n private tail: Promise<unknown> = Promise.resolve();\n /** Detach function for the auto-flush `online` listener, if installed. */\n private detachOnline: (() => void) | null = null;\n /**\n * Set once `dispose()` is called (on teardown / reconfigure). A disposed\n * queue refuses to mutate the persisted outbox, so an orphaned in-flight\n * flush — or any late call against the old instance — can never race the new\n * queue by reading/writing the same storage after it's been replaced.\n */\n private disposed = false;\n\n constructor(opts: {\n store: KvStore;\n executor: WriteExecutor;\n /** Persistence key for the serialized queue. */\n storageKey?: string;\n /** Called when a deterministically-failing op is dropped from the queue. */\n onDropped?: (op: OutboxOp, err: unknown) => void;\n /**\n * Returns the identity signed in right now. Binds each queued op to the\n * user who queued it: flush replays an op only for that user.\n */\n currentOwner?: () => OutboxOwner;\n }) {\n this.store = opts.store;\n this.executor = opts.executor;\n this.storageKey = opts.storageKey ?? \"offline_outbox\";\n this.onDropped = opts.onDropped;\n this.currentOwner = opts.currentOwner;\n }\n\n /**\n * The ops the current owner may see and replay. Without an owner source\n * every op qualifies.\n */\n private visible(ops: OutboxOp[]): OutboxOp[] {\n if (!this.currentOwner) return ops;\n const current = this.currentOwner();\n return ops.filter((op) => outboxOwnerMatches(op, current));\n }\n\n /**\n * Run `fn` exclusively against the persisted outbox: it chains after every\n * prior mutation and the next one waits for it. `fn` receives the current\n * ops and returns the next ops to persist plus its own result.\n */\n private mutate<R>(\n fn: (ops: OutboxOp[]) => { next: OutboxOp[]; result: R } | Promise<{ next: OutboxOp[]; result: R }>,\n opts?: { rejectIfDisposed?: boolean },\n ): Promise<R> {\n const run = this.tail.then(async () => {\n // Re-check disposal AFTER taking the lock. `save()` is already a no-op\n // when disposed, but a caller that needs to KNOW its write didn't persist\n // (enqueue) must REJECT rather than silently resolve — otherwise a\n // dispose racing between `getOfflineQueue()` and the awaited `enqueue()`\n // would hand the caller a pending-success for a write that's now lost.\n if (this.disposed && opts?.rejectIfDisposed) {\n throw new Error(\"amba offline queue was disposed (SDK reconfigured)\");\n }\n const ops = await this.load();\n const { next, result } = await fn(ops);\n await this.save(next);\n return result;\n });\n // Keep the chain alive even if this mutation rejects, so a single failure\n // doesn't permanently wedge every future mutation.\n this.tail = run.catch(() => undefined);\n return run;\n }\n\n private async load(): Promise<OutboxOp[]> {\n const raw = await this.store.get(this.storageKey);\n if (!raw) return [];\n try {\n const parsed = JSON.parse(raw) as unknown;\n return Array.isArray(parsed) ? (parsed as OutboxOp[]) : [];\n } catch {\n // Corrupt payload — start clean rather than wedge the queue forever.\n return [];\n }\n }\n\n private async save(ops: OutboxOp[]): Promise<void> {\n // A disposed queue must never write the shared storage — a new queue may\n // already own it. Drop the write silently so an orphaned in-flight flush\n // (or any late mutation) can't clobber the replacement queue's outbox.\n if (this.disposed) return;\n if (ops.length === 0) {\n await this.store.delete(this.storageKey);\n return;\n }\n await this.store.set(this.storageKey, JSON.stringify(ops));\n }\n\n /**\n * Permanently retire this queue. Detaches the auto-flush listener and marks\n * it disposed so it can no longer mutate the persisted outbox. Returns once\n * any in-flight flush + pending mutation chain has settled, so a caller (e.g.\n * `configure()` teardown) can safely create a replacement queue knowing the\n * old one will never touch storage again. Idempotent.\n */\n async dispose(): Promise<void> {\n this.disposed = true;\n if (this.detachOnline) {\n this.detachOnline();\n this.detachOnline = null;\n }\n // Await the in-flight flush + the mutation chain so all already-scheduled\n // storage work has run (its writes are now suppressed by the disposed\n // guard) before the caller installs a new queue.\n try {\n await this.flushing;\n } catch {\n // a failing flush still settles the lifecycle\n }\n await this.tail.catch(() => undefined);\n }\n\n /** Whether this queue has been disposed. */\n get isDisposed(): boolean {\n return this.disposed;\n }\n\n /**\n * Capture the current owner and purge generation. Take it before a write's\n * live attempt and pass it to {@link enqueue}.\n */\n ticket(): OutboxTicket {\n return { owner: this.currentOwner?.(), generation: this.purgeGeneration };\n }\n\n /**\n * Append an op to the durable outbox. Returns the persisted op. With a\n * `ticket`, the op belongs to the ticket's owner, and the enqueue rejects\n * (code `OFFLINE_QUEUE_PURGED`) when the queue was purged after the ticket\n * was taken, which is what a sign-out during the live attempt does.\n */\n async enqueue(\n op: Omit<OutboxOp, \"idempotencyKey\" | \"enqueuedAt\"> & {\n idempotencyKey?: string;\n },\n ticket?: OutboxTicket,\n ): Promise<OutboxOp> {\n // Fast-fail if already disposed; otherwise the re-check inside `mutate`\n // (after the lock) catches a dispose that races this call. Either way a\n // disposed queue REJECTS the enqueue rather than silently swallowing the\n // write — so the SDK's diverting write surfaces a failure instead of\n // handing the caller a pending-success for a write that never persisted.\n if (this.disposed) {\n throw new Error(\"amba offline queue was disposed (SDK reconfigured)\");\n }\n const owner = op.owner ?? (ticket ? ticket.owner : this.currentOwner?.());\n const full: OutboxOp = {\n ...op,\n ...(owner ? { owner: { ...owner } } : {}),\n idempotencyKey: op.idempotencyKey ?? newIdempotencyKey(),\n enqueuedAt: new Date().toISOString(),\n };\n return this.mutate(\n (ops) => {\n // Checked under the lock: a purge that ran while the live attempt was\n // in flight wins, and the signed-out user's write is not queued.\n if (ticket && ticket.generation !== this.purgeGeneration) {\n const err = new Error(\n \"amba offline queue was cleared by a sign-out while this write was in flight\",\n ) as Error & { code: string };\n err.code = \"OFFLINE_QUEUE_PURGED\";\n throw err;\n }\n return { next: [...ops, full], result: full };\n },\n { rejectIfDisposed: true },\n );\n }\n\n /** Number of the current owner's ops awaiting replay. */\n async pendingCount(): Promise<number> {\n return this.visible(await this.load()).length;\n }\n\n /**\n * Snapshot the current owner's pending ops (a copy — mutating it does not\n * affect the queue). Ops queued by another user stay out of the list.\n */\n async pending(): Promise<OutboxOp[]> {\n return [...this.visible(await this.load())];\n }\n\n /** Drop every queued op, whoever queued it, without replaying. */\n async clear(): Promise<void> {\n await this.mutate(() => ({ next: [], result: undefined }));\n }\n\n /**\n * Drop every queued op, including one whose replay is in flight right now:\n * that op is not re-queued when its replay fails. The SDKs call this on\n * sign-out so nothing the signed-out user queued survives on the device.\n */\n async purge(): Promise<void> {\n this.purgeGeneration += 1;\n await this.clear();\n }\n\n /**\n * Give the ops queued before any session under `anonymousId` to the\n * signed-in `appUserId`. Those ops replay under the session that holds\n * their anonymous id; the SDK calls this before that session's anonymous\n * id changes (a link, or adopting the account's id), so they keep an owner\n * that matches.\n */\n async claimAnonymousOps(anonymousId: string, appUserId: string): Promise<void> {\n await this.mutate((ops) => ({\n next: ops.map((op) =>\n op.owner &&\n op.owner.appUserId === null &&\n op.owner.anonymousId === anonymousId\n ? { ...op, owner: { appUserId, anonymousId } }\n : op,\n ),\n result: undefined,\n }));\n }\n\n /**\n * Replay the current owner's ops in FIFO order. Each op is removed only\n * after it succeeds; the first failure stops the flush and retains the\n * remainder (so order is preserved and nothing is dropped mid-batch). Ops\n * another user queued stay held for that user. Ops that can never replay\n * are dropped through `onDropped`: one with no recorded owner (code\n * `OFFLINE_OWNER_UNKNOWN`), and one queued before any session under an\n * anonymous id the install no longer has (code `OFFLINE_OWNER_GONE`).\n * Concurrent calls share one in-flight flush.\n */\n async flush(): Promise<FlushResult> {\n if (this.disposed) return { flushed: 0, dropped: 0, remaining: 0 };\n if (this.flushing) return this.flushing;\n this.flushing = this.runFlush().finally(() => {\n this.flushing = null;\n });\n return this.flushing;\n }\n\n private async runFlush(): Promise<FlushResult> {\n let flushed = 0;\n let dropped = 0;\n // Drive the flush one op at a time through `mutate()`, so a concurrent\n // `enqueue` (e.g. a new offline write mid-flush) interleaves safely\n // instead of being lost to a stale read-modify-write. Each step replays\n // the head op (outside the lock — it can be slow) then atomically removes\n // exactly that op if it's still the head.\n //\n // The drain is NOT gated on `navigator.onLine`: that's meaningless on\n // React Native (no real `navigator.onLine`), where the documented pattern\n // is to call `flush()` from a NetInfo connectivity listener — so an\n // explicit flush must always ATTEMPT the drain. If the device is in fact\n // still offline, the executor throws a transport error on the first op and\n // the loop stops below, retaining everything for the next attempt. (The\n // web auto-flush path only fires on the `online` event anyway, so it's\n // already online when it runs.)\n for (;;) {\n // Stop immediately if disposed mid-drain (a reconfigure replaced us): the\n // save guard already suppresses writes, this just avoids executing more\n // ops against a retired queue.\n if (this.disposed) break;\n const all = await this.load();\n if (this.currentOwner) {\n // Ops that can never match an owner again are dropped (reported)\n // instead of held forever: one from before owners existed, and a\n // pre-session op whose anonymous id the install no longer has\n // (rotated on sign-out, or a fresh one per page in memory mode).\n const current = this.currentOwner();\n const orphan = all.find(\n (o) =>\n !o.owner ||\n (o.owner.appUserId === null &&\n current.anonymousId !== null &&\n o.owner.anonymousId !== current.anonymousId),\n );\n if (orphan) {\n const removedOrphan = await this.removeOp(orphan);\n if (!removedOrphan) break;\n dropped += 1;\n this.reportDropped(\n orphan,\n orphan.owner\n ? {\n code: \"OFFLINE_OWNER_GONE\",\n message: \"queued before sign-in under an anonymous id this install no longer has; not replayed\",\n }\n : {\n code: \"OFFLINE_OWNER_UNKNOWN\",\n message: \"queued by an earlier SDK version without an owner; not replayed\",\n },\n );\n continue;\n }\n }\n const head = this.visible(all)[0];\n if (!head) break;\n const generation = this.purgeGeneration;\n let replayError: unknown = undefined;\n let ok = false;\n try {\n await this.executor(head);\n ok = true;\n } catch (err) {\n replayError = err;\n }\n\n if (!ok && !isDeterministicError(replayError)) {\n // A NON-deterministic replay failure — a transport blip, a 5xx\n // `HTTP_ERROR`, `RATE_LIMITED` (429), `CIRCUIT_OPEN`, or any uncoded\n // error. These may well succeed later, so STOP here and keep this op\n // plus everything after it for the next attempt (favor retain over data\n // loss). Only a deterministic server rejection (handled below) is\n // dropped.\n //\n // The replay ran OUTSIDE the lock, so a concurrent `clear()` (or a\n // drain that already removed this op) could have emptied the store\n // while it was in flight. Re-assert the un-applied op's presence under\n // the lock — re-prepend it if it's gone — so an unapplied mutation is\n // never silently lost to a clear/flush interleaving. at-least-once wins\n // over honoring a clear that raced an in-flight, un-applied write; the\n // caller can `clear()` again once the flush settles. A `purge()` (sign-\n // out) is final: the op stays gone.\n await this.mutate((ops) => {\n const present = ops.some(\n (o) => o.idempotencyKey === head.idempotencyKey,\n );\n const requeue = !present && generation === this.purgeGeneration;\n return {\n next: requeue ? [head, ...ops] : ops,\n result: undefined,\n };\n });\n break;\n }\n\n // Either the replay succeeded, OR it failed deterministically (a coded\n // server rejection that will fail identically forever). In both cases the\n // head op leaves the queue: a success drains it, a poisoned op is DROPPED\n // so it can't wedge everything behind it. Removal is by identity under\n // the lock — robust against a concurrent enqueue having shifted the\n // array.\n const removed = await this.removeOp(head);\n if (!removed) break; // shouldn't happen, but never spin\n if (ok) {\n flushed += 1;\n } else {\n dropped += 1;\n this.reportDropped(head, replayError);\n }\n }\n const remaining = this.visible(await this.load()).length;\n return { flushed, dropped, remaining };\n }\n\n /**\n * Remove exactly `op` (by idempotency key) under the lock. Robust against a\n * concurrent enqueue having shifted the array. Resolves false when the op\n * is already gone.\n */\n private removeOp(op: OutboxOp): Promise<boolean> {\n return this.mutate((ops) => {\n const idx = ops.findIndex((o) => o.idempotencyKey === op.idempotencyKey);\n if (idx === -1) return { next: ops, result: false };\n const next = ops.slice(0, idx).concat(ops.slice(idx + 1));\n return { next, result: true };\n });\n }\n\n private reportDropped(op: OutboxOp, err: unknown): void {\n try {\n this.onDropped?.(op, err);\n } catch {\n // A throwing drop-observer must not break the flush.\n }\n }\n\n /**\n * Install an auto-flush listener on the runtime's `online` event so the\n * queue drains the moment connectivity returns. Idempotent — a second\n * call is a no-op. Returns a detach function.\n */\n startAutoFlush(): () => void {\n if (this.detachOnline) return this.detachOnline;\n const target =\n typeof globalThis !== \"undefined\" &&\n typeof (globalThis as { addEventListener?: unknown }).addEventListener ===\n \"function\"\n ? (globalThis as unknown as {\n addEventListener(t: string, cb: () => void): void;\n removeEventListener(t: string, cb: () => void): void;\n })\n : null;\n if (!target) {\n // No event target (Node / SSR) — nothing to listen on. Still return a\n // detach so callers can treat the API uniformly.\n this.detachOnline = () => {};\n return this.detachOnline;\n }\n const handler = () => {\n void this.flush();\n };\n target.addEventListener(\"online\", handler);\n this.detachOnline = () => {\n target.removeEventListener(\"online\", handler);\n this.detachOnline = null;\n };\n return this.detachOnline;\n }\n}\n\n// ── Optimistic update helper ─────────────────────────────────────────────\n\n/**\n * Apply a local change immediately, fire the real mutation, and roll the\n * local change back if the mutation fails. Framework-agnostic: `apply` and\n * `rollback` operate on whatever local view you hand them (a React state\n * setter, a store, an in-memory cache).\n *\n * Resolves with the mutation's result on success. On failure it invokes\n * `rollback` (with the value `apply` returned, so you can restore a captured\n * snapshot) and rethrows the original error.\n *\n * @example\n * await withOptimistic({\n * apply: () => { const prev = todos; setTodos([...todos, draft]); return prev; },\n * mutate: () => Amba.collections.insert(\"todos\", draft),\n * rollback: (prev) => setTodos(prev),\n * });\n */\nexport async function withOptimistic<TApply, TResult>(spec: {\n /** Mutate the local view now. Return a snapshot for `rollback` to restore. */\n apply: () => TApply;\n /** The real async mutation against the API. */\n mutate: () => Promise<TResult>;\n /** Undo the local change. Receives whatever `apply` returned. */\n rollback: (applied: TApply) => void;\n}): Promise<TResult> {\n const applied = spec.apply();\n try {\n return await spec.mutate();\n } catch (err) {\n try {\n spec.rollback(applied);\n } catch {\n // A throwing rollback must not mask the original mutation error.\n }\n throw err;\n }\n}\n","import type { KvStore } from \"./resilience.js\";\nimport { newIdempotencyKey } from \"./resilience.js\";\n\nexport interface EventDrop {\n idempotencyKey: string;\n event: string;\n userId: string | null;\n reason:\n | \"telemetry_evicted\"\n | \"server_rejected\"\n | \"held_expired\"\n | \"oversized\";\n code?: string;\n}\n\nexport interface EventBatchOptions {\n batchSize: number;\n flushIntervalMs: number;\n maxQueue: number;\n onDrop?: (drop: EventDrop) => void;\n}\n\nexport interface QueuedEvent {\n event: string;\n properties?: Record<string, unknown>;\n timestamp: string;\n telemetry: boolean;\n idempotency_key: string;\n userId: string | null;\n anonymousId: string;\n heldAt?: string;\n}\n\nexport interface EventFlushResult {\n sent: number;\n rejected: number;\n remaining: number;\n}\n\ninterface BatchResponse {\n data: {\n accepted: Array<{\n idempotency_key: string;\n status: \"inserted\" | \"duplicate\";\n }>;\n rejected: Array<{ idempotency_key: string; code: string }>;\n };\n}\n\nconst MAX_BYTES = 512 * 1024;\nconst HELD_TTL_MS = 30 * 24 * 60 * 60 * 1000;\nconst COALESCE_MS = 250;\n\nfunction byteLength(s: string): number {\n if (typeof TextEncoder !== \"undefined\")\n return new TextEncoder().encode(s).length;\n return encodeURIComponent(s).replace(/%[A-Fa-f0-9]{2}|./g, \"x\").length;\n}\n\nfunction wire(e: QueuedEvent) {\n return {\n event: e.event,\n properties: e.properties,\n timestamp: e.timestamp,\n telemetry: e.telemetry,\n idempotency_key: e.idempotency_key,\n };\n}\n\nfunction safeDrop(\n cb: EventBatchOptions[\"onDrop\"],\n e: QueuedEvent,\n reason: EventDrop[\"reason\"],\n code?: string,\n): void {\n try {\n cb?.({\n idempotencyKey: e.idempotency_key,\n event: e.event,\n userId: e.userId,\n reason,\n code,\n });\n } catch {\n /* reporting cannot block delivery */\n }\n}\n\n/** Durable queue owned by one configured project and storage prefix. */\nexport class EventBatchQueue {\n private events: QueuedEvent[] = [];\n private pending: Array<{\n event: QueuedEvent;\n resolve: () => void;\n reject: (err: unknown) => void;\n }> = [];\n private writeTimer: ReturnType<typeof setTimeout> | null = null;\n private flushTimer: ReturnType<typeof setTimeout> | null = null;\n private retryTimer: ReturnType<typeof setTimeout> | null = null;\n private serialized: Promise<unknown> = Promise.resolve();\n private inFlight: Promise<EventFlushResult> | null = null;\n private disposed = false;\n private online: boolean | null = null;\n private batchingEnabled = true;\n private failures = 0;\n private clearingUsers = new Set<string>();\n\n constructor(\n private readonly store: KvStore,\n private readonly storageKey: string,\n private readonly options: EventBatchOptions,\n private readonly identity: () => {\n userId: string | null;\n anonymousId: string;\n sessionToken: string | null;\n },\n private readonly postBatch: (\n body: { events: ReturnType<typeof wire>[] },\n sessionToken: string,\n ) => Promise<BatchResponse>,\n private readonly postSingle: (\n body: ReturnType<typeof wire>,\n sessionToken: string,\n ) => Promise<unknown>,\n ) {}\n\n async start(): Promise<void> {\n const raw = await this.store.get(this.storageKey);\n if (raw) {\n try {\n const parsed = JSON.parse(raw) as unknown;\n this.events = Array.isArray(parsed) ? (parsed as QueuedEvent[]) : [];\n } catch {\n this.events = [];\n }\n }\n await this.expireHeld();\n // Unknown connectivity permits one launch attempt. The timer starts only\n // after the host supplies its first connectivity answer.\n if (this.events.length) void this.flush();\n }\n\n private exclusive<T>(work: () => Promise<T>): Promise<T> {\n const run = this.serialized.then(work);\n this.serialized = run.catch(() => undefined);\n return run;\n }\n\n private async save(): Promise<void> {\n if (this.events.length)\n await this.store.set(this.storageKey, JSON.stringify(this.events));\n else await this.store.delete(this.storageKey);\n }\n\n /**\n * Queue an event. `timestamp` is when it happened, for an event that waited\n * elsewhere before reaching the queue; it defaults to now.\n */\n enqueue(\n event: string,\n properties: Record<string, unknown> | undefined,\n telemetry: boolean,\n timestamp?: string,\n ): Promise<void> {\n if (this.disposed)\n return Promise.reject(new Error(\"Amba event queue disposed\"));\n const id = this.identity();\n const item: QueuedEvent = {\n event,\n properties,\n telemetry,\n timestamp: timestamp ?? new Date().toISOString(),\n idempotency_key: newIdempotencyKey(),\n userId: id.sessionToken ? id.userId : null,\n anonymousId: id.anonymousId,\n };\n if (!event.trim())\n return Promise.reject(new Error(\"event name must be non-empty\"));\n if (byteLength(JSON.stringify({ events: [wire(item)] })) > MAX_BYTES) {\n safeDrop(this.options.onDrop, item, \"oversized\");\n return Promise.reject(new Error(\"event exceeds 512 KB batch limit\"));\n }\n return new Promise<void>((resolve, reject) => {\n this.pending.push({ event: item, resolve, reject });\n if (!this.writeTimer)\n this.writeTimer = setTimeout(() => {\n void this.persistPending();\n }, COALESCE_MS);\n });\n }\n\n async persistPending(): Promise<void> {\n if (this.writeTimer) clearTimeout(this.writeTimer);\n this.writeTimer = null;\n const batch = this.pending.splice(0);\n if (!batch.length) return;\n try {\n let evictedKeys = new Set<string>();\n await this.exclusive(async () => {\n const before = this.events;\n this.events = [...this.events, ...batch.map((p) => p.event)];\n const evicted: QueuedEvent[] = [];\n while (this.events.length > this.options.maxQueue) {\n const idx = this.events.findIndex((e) => e.telemetry);\n if (idx < 0) break; // engagement rows are never silently discarded\n evicted.push(this.events[idx]!);\n this.events.splice(idx, 1);\n }\n try {\n await this.save();\n } catch (err) {\n this.events = before;\n throw err;\n }\n evictedKeys = new Set(evicted.map((e) => e.idempotency_key));\n for (const e of evicted)\n safeDrop(this.options.onDrop, e, \"telemetry_evicted\");\n });\n for (const p of batch) {\n if (evictedKeys.has(p.event.idempotency_key))\n p.reject(new Error(\"event evicted before durable enqueue\"));\n else p.resolve();\n }\n this.schedule();\n if (this.online !== false && this.events.length >= this.options.batchSize)\n void this.flush();\n } catch (err) {\n for (const p of batch) p.reject(err);\n }\n }\n\n setOnline(value: boolean): void {\n const wasOnline = this.online;\n this.online = value;\n if (!value) {\n this.cancelTimers();\n return;\n }\n this.schedule();\n if (wasOnline !== true) void this.flush();\n }\n\n setBatchingEnabled(enabled: boolean): void {\n this.batchingEnabled = enabled;\n if (this.online !== false) void this.flush();\n }\n\n get isBatchingEnabled(): boolean {\n return this.batchingEnabled;\n }\n\n async pendingCount(userId?: string): Promise<number> {\n await this.persistPending();\n return this.events.filter(\n (e) => userId === undefined || e.userId === userId,\n ).length;\n }\n\n async clear(userId: string): Promise<void> {\n // Await a request already in flight. It cannot be recalled after reaching\n // the server, but this prevents any further dispatch after clear resolves.\n this.clearingUsers.add(userId);\n try {\n await this.inFlight?.catch(() => undefined);\n await this.persistPending();\n await this.exclusive(async () => {\n const prior = this.events;\n const id = this.identity();\n this.events = this.events.filter(\n (e) =>\n e.userId !== userId &&\n !(\n id.userId === userId &&\n e.userId === null &&\n e.anonymousId === id.anonymousId\n ),\n );\n try {\n await this.save();\n } catch (err) {\n this.events = prior;\n throw err;\n }\n });\n } finally {\n this.clearingUsers.delete(userId);\n this.schedule();\n }\n }\n\n private async expireHeld(): Promise<void> {\n const current = this.identity();\n await this.exclusive(async () => {\n const now = Date.now();\n const dropped: QueuedEvent[] = [];\n const next: QueuedEvent[] = [];\n for (const e of this.events) {\n if (\n (e.userId && e.userId !== current.userId) ||\n (!e.userId && e.anonymousId !== current.anonymousId)\n ) {\n e.heldAt ??= new Date(now).toISOString();\n if (now - Date.parse(e.heldAt) >= HELD_TTL_MS) {\n dropped.push(e);\n continue;\n }\n } else e.heldAt = undefined;\n next.push(e);\n }\n this.events = next;\n await this.save();\n for (const e of dropped) safeDrop(this.options.onDrop, e, \"held_expired\");\n });\n }\n\n private cancelTimers(): void {\n if (this.flushTimer) clearTimeout(this.flushTimer);\n if (this.retryTimer) clearTimeout(this.retryTimer);\n this.flushTimer = this.retryTimer = null;\n }\n\n private schedule(): void {\n if (this.flushTimer) clearTimeout(this.flushTimer);\n this.flushTimer = null;\n if (\n this.disposed ||\n this.online !== true ||\n this.failures > 0 ||\n this.options.flushIntervalMs === 0\n )\n return;\n const id = this.identity();\n if (!id.sessionToken || !id.userId) return;\n const eligible = this.events.filter(\n (e) =>\n e.userId === id.userId ||\n (e.userId === null && e.anonymousId === id.anonymousId),\n );\n if (!eligible.length) return;\n const oldest = Math.min(...eligible.map((e) => Date.parse(e.timestamp)));\n const wait = Math.max(\n 0,\n oldest + this.options.flushIntervalMs - Date.now(),\n );\n this.flushTimer = setTimeout(() => {\n void this.flush();\n }, wait);\n }\n\n private retry(): void {\n this.failures += 1;\n if (this.retryTimer) clearTimeout(this.retryTimer);\n if (this.online !== true) return;\n const delay = Math.min(60_000, 1000 * 2 ** Math.min(this.failures - 1, 6));\n this.retryTimer = setTimeout(() => {\n void this.flush();\n }, delay);\n }\n\n async flush(): Promise<EventFlushResult> {\n if (this.inFlight) return this.inFlight;\n this.inFlight = this.runFlush().finally(() => {\n this.inFlight = null;\n });\n return this.inFlight;\n }\n\n private async runFlush(): Promise<EventFlushResult> {\n await this.persistPending();\n if (this.disposed || this.online === false)\n return { sent: 0, rejected: 0, remaining: this.events.length };\n await this.expireHeld();\n const id = this.identity();\n if (!id.sessionToken || !id.userId)\n return { sent: 0, rejected: 0, remaining: this.events.length };\n let sent = 0;\n let rejected = 0;\n for (;;) {\n if (\n this.disposed ||\n (this.online as boolean | null) === false ||\n this.clearingUsers.has(id.userId) ||\n this.identity().userId !== id.userId\n )\n break;\n const eligible = this.events.filter(\n (e) =>\n e.userId === id.userId ||\n (e.userId === null && e.anonymousId === id.anonymousId),\n );\n if (!eligible.length) break;\n const selected: QueuedEvent[] = [];\n for (const e of eligible) {\n if (selected.length >= this.options.batchSize) break;\n const candidate = [...selected, e];\n if (\n byteLength(JSON.stringify({ events: candidate.map(wire) })) >\n MAX_BYTES\n )\n break;\n selected.push(e);\n }\n if (!selected.length) break;\n // Bind pre-session records before request so a later account switch can\n // never send them under a different user.\n await this.exclusive(async () => {\n for (const e of selected) if (e.userId === null) e.userId = id.userId;\n await this.save();\n });\n if (this.clearingUsers.has(id.userId) || this.identity().userId !== id.userId)\n break;\n try {\n const ack = this.batchingEnabled\n ? await this.postBatch(\n { events: selected.map(wire) },\n id.sessionToken,\n )\n : {\n data: {\n accepted: [] as BatchResponse[\"data\"][\"accepted\"],\n rejected: [] as BatchResponse[\"data\"][\"rejected\"],\n },\n };\n if (!this.batchingEnabled) {\n for (const e of selected) {\n await this.postSingle(wire(e), id.sessionToken);\n ack.data.accepted.push({\n idempotency_key: e.idempotency_key,\n status: \"inserted\",\n });\n }\n }\n const accepted = new Set(\n ack.data.accepted.map((a) => a.idempotency_key),\n );\n const drops = new Map(\n ack.data.rejected.map((r) => [r.idempotency_key, r.code]),\n );\n const known = new Set(selected.map((e) => e.idempotency_key));\n if ([...accepted, ...drops.keys()].some((k) => !known.has(k)))\n throw new Error(\"event batch returned unknown key\");\n if (accepted.size + drops.size !== selected.length)\n throw new Error(\"event batch omitted acknowledgments\");\n await this.exclusive(async () => {\n const prior = this.events;\n this.events = this.events.filter(\n (e) =>\n !accepted.has(e.idempotency_key) && !drops.has(e.idempotency_key),\n );\n try {\n await this.save();\n } catch (err) {\n this.events = prior;\n throw err;\n }\n });\n sent += accepted.size;\n rejected += drops.size;\n for (const e of selected)\n if (drops.has(e.idempotency_key))\n safeDrop(\n this.options.onDrop,\n e,\n \"server_rejected\",\n drops.get(e.idempotency_key),\n );\n this.failures = 0;\n } catch {\n this.retry();\n break;\n }\n }\n this.schedule();\n return { sent, rejected, remaining: this.events.length };\n }\n\n async dispose(): Promise<void> {\n this.disposed = true;\n this.cancelTimers();\n await this.persistPending();\n await this.inFlight?.catch(() => undefined);\n await this.serialized;\n }\n}\n","/**\n * Native push-token rotation for React Native.\n *\n * The OS can rotate a device's push token at any time (app reinstall, restore,\n * FCM/APNs key roll). When it does, the old token stops delivering and the app\n * must re-register the new one. The platform surfaces this through a\n * token-refresh callback — `Notifications.addPushTokenListener` (Expo), the\n * Firebase `onTokenRefresh` callback (bare FCM), or the APNs\n * `didRegisterForRemoteNotificationsWithDeviceToken` delegate (bare iOS).\n *\n * This module is the platform-neutral glue: it takes a `subscribe` function\n * (whatever the host's notification library exposes) and a `reRegister`\n * callback (the SDK points it at its idempotent `push.register` path), wires\n * them together, and returns an unsubscribe handle. It imports nothing from\n * the notification libraries itself, so the bare RN SDK stays dependency-free\n * and the Expo wrapper supplies the concrete `addPushTokenListener`.\n */\n\n/** Re-registers a rotated token with the backend. */\nexport type ReRegister = (\n token: string,\n platform: \"apns\" | \"fcm\",\n) => Promise<unknown>;\n\n/**\n * A platform token-refresh subscription primitive. Given a handler that\n * receives the new token string, it must invoke that handler on every rotation\n * and return an unsubscribe function. `Notifications.addPushTokenListener`\n * (Expo) adapts to this directly.\n */\nexport type TokenRefreshSubscribe = (\n handler: (token: string) => void,\n) => () => void;\n\n/**\n * Wire a platform token-refresh subscription to the SDK's idempotent\n * re-register path. On every rotation the fresh token is forwarded to\n * `reRegister(token, platform)` — the same upsert the first `push.register`\n * call used, so re-registering the same device is a no-op server-side.\n *\n * `platform` defaults to the conventional pick (`\"apns\"` on iOS, `\"fcm\"`\n * elsewhere) supplied by the caller. Returns an unsubscribe function.\n *\n * @example\n * // Expo:\n * import * as Notifications from \"expo-notifications\";\n * const detach = enableNativePushAutoRotation(\n * (h) => Notifications.addPushTokenListener((t) => h(t.data)).remove,\n * reRegister,\n * \"apns\",\n * );\n */\nexport function enableNativePushAutoRotation(\n subscribe: TokenRefreshSubscribe,\n reRegister: ReRegister,\n platform: \"apns\" | \"fcm\",\n): () => void {\n const unsubscribe = subscribe((token: string) => {\n if (!token) return;\n void (async () => {\n try {\n await reRegister(token, platform);\n } catch (err) {\n if (typeof console !== \"undefined\") {\n console.error(\"[amba] push auto-rotation re-register failed\", err);\n }\n }\n })();\n });\n return typeof unsubscribe === \"function\" ? unsubscribe : () => {};\n}\n","/**\n * On-device local notifications for React Native / Expo.\n *\n * A *local* notification is one the device schedules and fires itself —\n * no server round-trip, no delivery infrastructure. Use it for reminders,\n * streak nudges, \"come back tomorrow\" prompts, and anything else the app\n * already knows it wants to surface at a future time. This is distinct\n * from *remote push* (`Amba.push.*`), where Amba's backend decides when\n * to deliver a notification and routes it through APNs/FCM.\n *\n * The scheduling itself is performed by the device's notification\n * framework via `expo-notifications` — the same library Expo and bare RN\n * apps already use for push-token capture. We require it LAZILY (a runtime\n * `import(\"expo-notifications\")`) so:\n *\n * - apps that never schedule a local notification don't pay the bundle\n * cost, and\n * - the module imports cleanly under plain Node / SSR (the SDK is a\n * pure-JS fetch client elsewhere; nothing here forces a native module\n * to evaluate at import time).\n *\n * If `expo-notifications` is not installed, every method throws a typed\n * `AmbaApiError` with a vendor-neutral, actionable message — never a\n * silent no-op.\n */\n\nimport { AmbaApiError } from \"./error.js\";\nimport type {\n LocalNotificationHandle,\n ScheduleLocalNotificationInput,\n ScheduledLocalNotification,\n} from \"./types.js\";\n\n/**\n * The narrow slice of the `expo-notifications` surface we depend on. We\n * type it structurally rather than importing the package's types so the\n * SDK builds with `expo-notifications` absent (it's an optional peer dep).\n */\ninterface ExpoNotificationsLike {\n scheduleNotificationAsync(request: {\n identifier?: string;\n content: { title: string; body: string; data?: Record<string, unknown> };\n trigger: unknown;\n }): Promise<string>;\n cancelScheduledNotificationAsync(identifier: string): Promise<void>;\n getAllScheduledNotificationsAsync(): Promise<\n Array<{\n identifier: string;\n content?: { title?: string | null; body?: string | null; data?: unknown };\n trigger?: unknown;\n }>\n >;\n getPermissionsAsync(): Promise<{ status: string; granted?: boolean }>;\n requestPermissionsAsync(): Promise<{ status: string; granted?: boolean }>;\n SchedulableTriggerInputTypes?: {\n DATE: string;\n TIME_INTERVAL: string;\n };\n}\n\n/**\n * Marker key stamped into every Amba-scheduled notification's `data`. Lets\n * `listScheduledLocal` distinguish OUR notifications from any other\n * `expo-notifications` schedules the host app created — so foreign\n * schedules never leak into the Amba list. Mirrors the web SDK's\n * `__ambaLocalId` marker.\n */\nconst AMBA_LOCAL_ID_KEY = \"__ambaLocalId\";\n\n/**\n * Generate a stable local-notification id when the caller omits one.\n */\nfunction genLocalId(): string {\n const c = (\n globalThis as { crypto?: { randomUUID?: () => string } }\n ).crypto;\n if (c?.randomUUID) return c.randomUUID();\n return `amba_local_${Date.now()}_${Math.random().toString(36).slice(2)}`;\n}\n\n/**\n * Resolve the id a schedule will be filed under. A caller may omit `id`\n * (we mint one) but must not pass a BLANK/whitespace id: `cancelLocal`\n * rejects blank ids, so returning one in the handle would make the\n * notification uncancellable. Reject blank at schedule time so every\n * returned handle id round-trips through `cancelLocal`. Mirrors the web SDK.\n */\nfunction normalizeScheduleId(rawId: string | undefined): string {\n if (rawId === undefined) return genLocalId();\n if (typeof rawId !== \"string\" || rawId.trim() === \"\") {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.notifications.scheduleLocal: id, when provided, must be a non-empty string\",\n );\n }\n return rawId;\n}\n\nasync function loadExpoNotifications(): Promise<ExpoNotificationsLike> {\n try {\n // Dynamic import: keeps `expo-notifications` out of the bundle for apps\n // that never schedule a local notification, and lets this module import\n // under plain Node where the native package isn't present.\n const mod = (await import(\"expo-notifications\")) as unknown as {\n default?: ExpoNotificationsLike;\n } & ExpoNotificationsLike;\n return (mod.default ?? mod) as ExpoNotificationsLike;\n } catch (err) {\n throw new AmbaApiError(\n \"LOCAL_NOTIFICATIONS_UNAVAILABLE\",\n \"Amba.notifications requires the `expo-notifications` peer dependency. Install it with `npx expo install expo-notifications` (works in both Expo and bare React Native apps).\",\n err,\n );\n }\n}\n\n/**\n * Translate Amba's platform-neutral trigger into the shape\n * `expo-notifications` expects. We build object triggers keyed by the\n * library's `SchedulableTriggerInputTypes` when present (SDK 0.27+),\n * falling back to the legacy `{ date }` / `{ seconds }` shorthand the\n * older API accepts — both forms remain supported by current releases.\n */\nfunction buildExpoTrigger(\n input: ScheduleLocalNotificationInput,\n Notifications: ExpoNotificationsLike,\n): unknown {\n const types = Notifications.SchedulableTriggerInputTypes;\n const trigger = input.trigger;\n\n // Guard that `trigger` is a non-null object before `in`-probing it — a\n // missing or non-object trigger must surface the documented\n // INVALID_ARGUMENT AmbaApiError, not a raw `TypeError` from `\"at\" in null`.\n if (!trigger || typeof trigger !== \"object\") {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.notifications.scheduleLocal: trigger must be { at: Date } or { inSeconds: number, repeats?: boolean }\",\n );\n }\n\n if (\"at\" in trigger) {\n const at = trigger.at instanceof Date ? trigger.at : new Date(trigger.at);\n if (Number.isNaN(at.getTime())) {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.notifications.scheduleLocal: trigger.at must be a valid Date\",\n );\n }\n return types ? { type: types.DATE, date: at } : { date: at };\n }\n\n if (\"inSeconds\" in trigger) {\n const seconds = trigger.inSeconds;\n if (!Number.isFinite(seconds) || seconds < 0) {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.notifications.scheduleLocal: trigger.inSeconds must be a non-negative number\",\n );\n }\n const repeats = \"repeats\" in trigger ? Boolean(trigger.repeats) : false;\n return types\n ? { type: types.TIME_INTERVAL, seconds, repeats }\n : { seconds, repeats };\n }\n\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.notifications.scheduleLocal: trigger must be { at: Date } or { inSeconds: number, repeats?: boolean }\",\n );\n}\n\nfunction validateContent(input: ScheduleLocalNotificationInput): void {\n if (!input || typeof input.title !== \"string\" || input.title.trim() === \"\") {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.notifications.scheduleLocal: title is required\",\n );\n }\n if (typeof input.body !== \"string\") {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.notifications.scheduleLocal: body must be a string\",\n );\n }\n}\n\n/**\n * The `notifications` namespace. Shared by `@layers/amba-react-native`\n * and (via re-export) `@layers/amba-expo`.\n */\nexport const notifications = {\n /**\n * Schedule a notification the device will fire locally — no server\n * round-trip. Requests the OS notification permission on first use.\n *\n * @returns a handle whose `id` can be passed to `cancelLocal`.\n * @throws `AmbaApiError`:\n * - `LOCAL_NOTIFICATIONS_UNAVAILABLE` — `expo-notifications` not installed.\n * - `LOCAL_NOTIFICATIONS_PERMISSION_DENIED` — user declined the prompt.\n * - `INVALID_ARGUMENT` — bad title / body / trigger.\n *\n * @example\n * const { id } = await Amba.notifications.scheduleLocal({\n * title: 'Keep your streak alive',\n * body: 'You have a lesson waiting.',\n * trigger: { inSeconds: 60 * 60 * 12 },\n * });\n */\n async scheduleLocal(\n input: ScheduleLocalNotificationInput,\n ): Promise<LocalNotificationHandle> {\n validateContent(input);\n // Resolve + validate the id up front: blank caller ids are rejected (they\n // wouldn't round-trip through `cancelLocal`), and an omitted id is minted\n // here so we can both stamp the Amba marker and return a cancellable id.\n const id = normalizeScheduleId(input.id);\n const Notifications = await loadExpoNotifications();\n const trigger = buildExpoTrigger(input, Notifications);\n\n const existing = await Notifications.getPermissionsAsync();\n let granted = existing.granted ?? existing.status === \"granted\";\n if (!granted) {\n const requested = await Notifications.requestPermissionsAsync();\n granted = requested.granted ?? requested.status === \"granted\";\n }\n if (!granted) {\n throw new AmbaApiError(\n \"LOCAL_NOTIFICATIONS_PERMISSION_DENIED\",\n \"user denied notification permission — local notifications cannot be scheduled\",\n );\n }\n\n // Pass our resolved id as the platform identifier AND stamp the Amba\n // marker into `data`, so `listScheduledLocal` can tell our notifications\n // apart from any other `expo-notifications` schedules in the host app.\n const scheduledId = await Notifications.scheduleNotificationAsync({\n identifier: id,\n content: {\n title: input.title,\n body: input.body,\n data: { ...input.data, [AMBA_LOCAL_ID_KEY]: id },\n },\n trigger,\n });\n // Prefer our resolved id (what `cancelLocal` expects); fall back to the\n // platform-returned id only if the library ignored our identifier.\n return { id: scheduledId || id };\n },\n\n /**\n * Cancel a previously scheduled local notification by id. No-throw if\n * the id is unknown (the device framework treats it as already-gone),\n * but still throws `LOCAL_NOTIFICATIONS_UNAVAILABLE` if the peer dep is\n * absent so callers learn about the missing integration.\n */\n async cancelLocal(id: string): Promise<void> {\n if (typeof id !== \"string\" || id.trim() === \"\") {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.notifications.cancelLocal: id is required\",\n );\n }\n const Notifications = await loadExpoNotifications();\n await Notifications.cancelScheduledNotificationAsync(id);\n },\n\n /**\n * List the notifications Amba has scheduled to fire on this device.\n * Returns an empty array when none are pending.\n *\n * `getAllScheduledNotificationsAsync` returns EVERY pending\n * `expo-notifications` schedule in the app — including ones the host app\n * created directly. We filter to only entries carrying the Amba marker\n * (`data.__ambaLocalId`) so foreign schedules never leak into the Amba\n * list. (Mirrors the web SDK's marker filter.) These are pending by\n * definition — `getAllScheduledNotificationsAsync` does not return\n * already-delivered notifications.\n */\n async listScheduledLocal(): Promise<ScheduledLocalNotification[]> {\n const Notifications = await loadExpoNotifications();\n const scheduled = await Notifications.getAllScheduledNotificationsAsync();\n const out: ScheduledLocalNotification[] = [];\n for (const n of scheduled) {\n const data = (n.content?.data as Record<string, unknown> | undefined) ?? {};\n const markerId = data[AMBA_LOCAL_ID_KEY] as string | undefined;\n // Only OUR notifications carry the marker; skip everything else.\n if (!markerId) continue;\n out.push({\n id: markerId,\n title: n.content?.title ?? \"\",\n body: n.content?.body ?? \"\",\n data,\n });\n }\n return out;\n },\n};\n\nexport type LocalNotificationsNamespace = typeof notifications;\n","/**\n * `onExperimentAssigned` bookkeeping.\n *\n * `experiments.getVariant(key)` hands every result to {@link\n * ExperimentAssignmentReporter.report}. The reporter calls the app's\n * listeners once per experiment per user session: the first time a user's\n * assignment for a key comes back while at least one listener is subscribed.\n * Later calls for the same user and key return without calling them again.\n * A session lasts until sign-out ({@link ExperimentAssignmentReporter.reset})\n * or until the page or app process ends; the memory is in-process.\n *\n * The reporter remembers the most recently seen `maxReported` user and\n * experiment pairs (least recently seen goes first), so a long-lived server\n * process keeps its memory bounded.\n *\n * Listener isolation: a listener runs synchronously and is never awaited. A\n * throw (or a rejected promise it returns) is caught and logged, so a broken\n * analytics forwarder can never fail or delay the SDK call that produced the\n * assignment.\n */\nimport type { ExperimentAssignment } from \"./types\";\n\nexport type ExperimentAssignedListener = (\n assignment: ExperimentAssignment,\n) => void | Promise<void>;\n\n/** Keys remembered before the oldest are forgotten (bounds memory). */\nconst DEFAULT_MAX_REPORTED = 10_000;\n\nexport class ExperimentAssignmentReporter {\n private readonly listeners = new Set<ExperimentAssignedListener>();\n // Insertion-ordered, so the first key is the oldest when trimming.\n private readonly reported = new Set<string>();\n\n constructor(private readonly maxReported: number = DEFAULT_MAX_REPORTED) {}\n\n /** Add a listener. Returns a function that removes it. */\n subscribe(listener: ExperimentAssignedListener): () => void {\n if (typeof listener !== \"function\") {\n throw new TypeError(\"onExperimentAssigned expects a function\");\n }\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /**\n * Call the listeners for `assignment` when this user has not reported this\n * experiment yet in the session. Returns `assignment` unchanged.\n */\n report(assignment: ExperimentAssignment): ExperimentAssignment {\n if (this.listeners.size === 0) return assignment;\n const id = `${assignment.bucketingId}\\u0000${assignment.experimentKey}`;\n if (this.reported.has(id)) {\n // Seen again: move it to the most recent end so the bound drops the\n // least recently seen pair first.\n this.reported.delete(id);\n this.reported.add(id);\n return assignment;\n }\n this.reported.add(id);\n if (this.reported.size > this.maxReported) {\n const oldest = this.reported.values().next().value;\n if (oldest !== undefined) this.reported.delete(oldest);\n }\n for (const listener of Array.from(this.listeners)) {\n try {\n const result = listener({ ...assignment });\n if (result && typeof (result as Promise<void>).then === \"function\") {\n (result as Promise<void>).then(undefined, logListenerError);\n }\n } catch (err) {\n logListenerError(err);\n }\n }\n return assignment;\n }\n\n /** Start a new session: every experiment reports again. */\n reset(): void {\n this.reported.clear();\n }\n}\n\nfunction logListenerError(err: unknown): void {\n console.warn(\"[amba] onExperimentAssigned listener threw\", err);\n}\n","/**\n * On-device cache of remote config and feature flags for React Native.\n *\n * The same model as the SDK core's cache (the web and native SDKs):\n *\n * - Lookup order: the value from a fetch made since launch, else the value\n * read back from AsyncStorage, else the app's `configDefaults` entry, else\n * the caller's default. A key a successful fetch omits (the server leaves\n * out a config whose kill switch is off, or a disabled flag) leaves the\n * cache with that fetch, so the lookup falls through to the defaults.\n * - One AsyncStorage record per project: the key hashes the API base URL and\n * the client key, so another project (or environment) never reads it.\n * Inside, each signed-in user has a slot keyed by a hash of the app user\n * id, and responses evaluated without a signed-in user (requests sent\n * before sign-in, or answered with each key's default value because the\n * session had expired) share the `anon` slot. Five slots at most; the\n * least recently written go first.\n * - The SDK serves the current user's own slot; config or flags it doesn't\n * hold, and every lookup without a signed-in user, come from the `anon`\n * slot. One user's slot is never served to another identity. Sign-out\n * deletes the signed-out user's slot.\n * - A fetch is cached only when the identity it was sent for is still the\n * current one and no sign-out happened while it ran.\n */\n\nimport type { ConfigBundle, FlagAssignment } from \"./types.js\";\n\n/** Prefix of the per-project AsyncStorage record key. */\nexport const CACHE_KEY_PREFIX = \"amba_rc_v1_\";\n/** Slot for values evaluated without a signed-in user. */\nexport const ANON_SLOT = \"anon\";\n/** Most user slots one project record keeps. */\nexport const MAX_SLOTS = 5;\n\nconst TWO_32 = 4294967296;\n\n/**\n * 64-bit FNV-1a over the string's UTF-16 code units (low byte, then high\n * byte), as 16 hex digits. Computed in two 32-bit halves: some React Native\n * engines (JavaScriptCore on older Android builds) have no BigInt.\n */\nexport function fnv1a64Hex(text: string): string {\n let hi = 0xcbf29ce4;\n let lo = 0x84222325;\n const step = (byte: number): void => {\n lo = (lo ^ byte) >>> 0;\n // hash *= 0x100000001b3 (mod 2^64) = hash * 0x1b3 + (hash << 40).\n const low = lo * 0x1b3;\n const carry = Math.floor(low / TWO_32);\n const high = hi * 0x1b3 + carry + ((lo << 8) >>> 0);\n lo = low % TWO_32;\n hi = high % TWO_32;\n };\n for (let i = 0; i < text.length; i++) {\n const unit = text.charCodeAt(i);\n step(unit & 0xff);\n step(unit >>> 8);\n }\n return hi.toString(16).padStart(8, \"0\") + lo.toString(16).padStart(8, \"0\");\n}\n\n/** The record key for a project: a hash of the base URL and client key. */\nexport function recordKey(baseUrl: string, clientKey: string): string {\n return `${CACHE_KEY_PREFIX}${fnv1a64Hex(`${baseUrl.replace(/\\/+$/, \"\")}\\n${clientKey}`)}`;\n}\n\n/** The slot key for an identity: `u` + a hash of the user id, or `anon`. */\nexport function slotKey(appUserId: string | null | undefined): string {\n return appUserId ? `u${fnv1a64Hex(appUserId)}` : ANON_SLOT;\n}\n\n/**\n * The user a config response was evaluated for, from its `ETag`:\n * `\"<version>:<app user id>:<segments>\"` for a signed-in caller, `\"<version>\"`\n * for every other one (no session, or an expired or revoked token, which the\n * server answers with each key's default value). `undefined` without an\n * `ETag`; `null` for an evaluation for nobody.\n */\nexport function etagEvaluatedFor(etag: string | null | undefined): string | null | undefined {\n if (!etag) return undefined;\n const parts = etag.trim().replace(/^W\\//, \"\").replace(/^\"|\"$/g, \"\").split(\":\");\n return parts.length > 1 && parts[1] ? parts[1] : null;\n}\n\ninterface Slot {\n saved_at: number;\n config?: ConfigBundle;\n flags?: FlagAssignment[];\n}\n\ninterface CacheRecord {\n v: 1;\n slots: Record<string, Slot>;\n}\n\n/** Taken when a request starts; handed back with its response. */\nexport interface FetchTicket {\n readonly generation: number;\n readonly requestedFor: string | null;\n}\n\n/** The storage the cache reads once at configure and writes behind. */\nexport interface RemoteValuesStore {\n read(key: string): Promise<string | null>;\n write(key: string, value: string): Promise<void>;\n delete(key: string): Promise<void>;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction validSlot(raw: unknown): Slot | null {\n if (!isPlainObject(raw)) return null;\n const slot: Slot = {\n saved_at: typeof raw[\"saved_at\"] === \"number\" ? raw[\"saved_at\"] : 0,\n };\n const config = raw[\"config\"];\n if (isPlainObject(config) && isPlainObject(config[\"values\"])) {\n slot.config = {\n version: typeof config[\"version\"] === \"string\" ? config[\"version\"] : null,\n values: config[\"values\"],\n };\n }\n const flags = raw[\"flags\"];\n if (Array.isArray(flags)) {\n slot.flags = flags.filter(\n (f): f is FlagAssignment =>\n isPlainObject(f) &&\n typeof f[\"name\"] === \"string\" &&\n typeof f[\"enabled\"] === \"boolean\",\n );\n }\n return slot.config || slot.flags ? slot : null;\n}\n\nfunction upsert(list: FlagAssignment[], flag: FlagAssignment): void {\n const i = list.findIndex((f) => f.name === flag.name);\n if (i >= 0) list[i] = flag;\n else list.push(flag);\n}\n\nexport class RemoteValuesCache {\n private record: CacheRecord = { v: 1, slots: {} };\n /** The slot key the held values were resolved for. */\n private viewFor: string | null = null;\n private config: ConfigBundle | null = null;\n private flags: FlagAssignment[] | null = null;\n /** Bumped by every sign-out; an older ticket's response is not cached. */\n private generation = 0;\n private writes: Promise<void> = Promise.resolve();\n\n constructor(\n private readonly store: RemoteValuesStore,\n readonly key: string,\n private readonly defaults: Record<string, unknown>,\n private readonly currentUser: () => string | null,\n ) {}\n\n /** Read the project's record from storage. `configure()` awaits it. */\n async load(): Promise<void> {\n let raw: string | null = null;\n try {\n raw = await this.store.read(this.key);\n } catch {\n return; // Storage unavailable: the cache works in memory.\n }\n if (!raw) return;\n try {\n const parsed = JSON.parse(raw) as unknown;\n const slots: Record<string, Slot> = {};\n if (isPlainObject(parsed) && isPlainObject(parsed[\"slots\"])) {\n for (const [k, v] of Object.entries(parsed[\"slots\"])) {\n const slot = validSlot(v);\n if (slot) slots[k] = slot;\n }\n }\n this.record = { v: 1, slots };\n } catch {\n this.record = { v: 1, slots: {} };\n this.persist();\n }\n }\n\n /** Take a ticket for a request about to be sent for the current user. */\n ticket(): FetchTicket {\n return { generation: this.generation, requestedFor: this.currentUser() };\n }\n\n // ── reads ──────────────────────────────────────────────────────────\n\n /** The value for config `key` (fetched, cached, then default); `undefined` when none. */\n configValue(key: string): unknown {\n this.ensureView();\n const values = this.config?.values;\n if (values && Object.prototype.hasOwnProperty.call(values, key)) {\n const v = values[key];\n if (v !== null && v !== undefined) return v;\n }\n return this.defaultFor(key);\n }\n\n /** The held assignment for flag `key`, or `null`. */\n flag(key: string): FlagAssignment | null {\n this.ensureView();\n return this.flags?.find((f) => f.name === key) ?? null;\n }\n\n /** Whether flag `key` is on: the held assignment, then a boolean default. */\n flagEnabled(key: string): boolean | undefined {\n const flag = this.flag(key);\n if (flag) return flag.enabled;\n const d = this.defaultFor(key);\n return typeof d === \"boolean\" ? d : undefined;\n }\n\n // ── writes ─────────────────────────────────────────────────────────\n\n /**\n * Hold and persist a fetched bundle; returns it. A bundle the server\n * evaluated for nobody although the request carried a user (an expired or\n * revoked session) goes to the `anon` slot and leaves the user's values\n * alone; one evaluated for another user is not cached.\n */\n storeConfig(ticket: FetchTicket, bundle: ConfigBundle, etag?: string | null): ConfigBundle {\n if (!isPlainObject(bundle.values)) return bundle;\n const evaluatedFor = etagEvaluatedFor(etag);\n let target: string | undefined;\n if (ticket.requestedFor && evaluatedFor === null) target = ANON_SLOT;\n else if (typeof evaluatedFor === \"string\" && evaluatedFor !== ticket.requestedFor) {\n return bundle;\n }\n this.write(ticket, target, (slot, held) => {\n slot.config = bundle;\n if (held) this.config = bundle;\n });\n return bundle;\n }\n\n /** Hold and persist a fetched flag list (it replaces the held one). */\n storeFlags(ticket: FetchTicket, flags: FlagAssignment[]): FlagAssignment[] {\n this.write(ticket, undefined, (slot, held) => {\n slot.flags = [...flags];\n if (held) this.flags = [...flags];\n });\n return flags;\n }\n\n /** Hold and persist one flag from a single-flag lookup; returns it. */\n upsertFlag(ticket: FetchTicket, flag: FlagAssignment): FlagAssignment {\n this.write(ticket, undefined, (slot, held) => {\n this.adoptHeldFlags(slot, held);\n slot.flags = slot.flags ?? [];\n upsert(slot.flags, flag);\n if (held) {\n this.flags = this.flags ?? [];\n upsert(this.flags, flag);\n }\n });\n return flag;\n }\n\n /** Drop flag `key` after a single-flag lookup found it off or unknown. */\n removeFlag(ticket: FetchTicket, key: string): void {\n this.write(ticket, undefined, (slot, held) => {\n this.adoptHeldFlags(slot, held);\n if (slot.flags) slot.flags = slot.flags.filter((f) => f.name !== key);\n if (held && this.flags) this.flags = this.flags.filter((f) => f.name !== key);\n });\n }\n\n /**\n * Sign-out: delete the signed-out user's slot and drop the held values;\n * later lookups serve the `anon` slot, then the defaults.\n */\n clearForSignOut(signedOutUser: string | null): void {\n this.generation += 1;\n this.config = null;\n this.flags = null;\n this.viewFor = null;\n if (!signedOutUser) return;\n const doomed = slotKey(signedOutUser);\n if (this.record.slots[doomed]) {\n delete this.record.slots[doomed];\n this.persist();\n }\n }\n\n /** Resolves when every write so far reached storage. */\n flushed(): Promise<void> {\n return this.writes;\n }\n\n // ── internals ──────────────────────────────────────────────────────\n\n /**\n * Before a single-flag change to the current user's slot: when the slot\n * holds no flag list and the served list is the signed-out one, the\n * user's slot starts from that list, so after a relaunch the user reads\n * the same flags the change was made on.\n */\n private adoptHeldFlags(slot: Slot, held: boolean): void {\n if (held && !slot.flags && this.flags) slot.flags = [...this.flags];\n }\n\n private defaultFor(key: string): unknown {\n if (!Object.prototype.hasOwnProperty.call(this.defaults, key)) return undefined;\n const v = this.defaults[key];\n return v === null ? undefined : v;\n }\n\n /**\n * `held` is `true` when the slot written is the one the current identity\n * reads, so the served values change with it.\n */\n private write(\n ticket: FetchTicket,\n targetOverride: string | undefined,\n apply: (slot: Slot, held: boolean) => void,\n ): void {\n const current = slotKey(this.currentUser());\n if (ticket.generation !== this.generation || slotKey(ticket.requestedFor) !== current) {\n return; // identity changed (or a sign-out happened) mid-flight\n }\n this.ensureView();\n const target = targetOverride ?? current;\n const existing = this.record.slots[target];\n const slot: Slot = existing\n ? {\n saved_at: existing.saved_at,\n ...(existing.config ? { config: existing.config } : {}),\n ...(existing.flags ? { flags: [...existing.flags] } : {}),\n }\n : { saved_at: 0 };\n apply(slot, target === current);\n if (!slot.config && !slot.flags) {\n // Nothing to keep for this identity: keep no empty slot.\n if (!existing) return;\n delete this.record.slots[target];\n } else {\n slot.saved_at = Date.now();\n this.record.slots[target] = slot;\n this.prune(target);\n }\n this.persist();\n // The signed-out slot changed under a signed-in user: the parts the user\n // reads from it are re-resolved on the next lookup.\n if (target !== current) this.viewFor = null;\n }\n\n /** Serve the current identity's own slot, the `anon` slot for the rest. */\n private ensureView(): void {\n const current = slotKey(this.currentUser());\n if (this.viewFor === current) return;\n const own = this.record.slots[current];\n const anon = current === ANON_SLOT ? undefined : this.record.slots[ANON_SLOT];\n const config = own?.config ?? anon?.config;\n const flags = own?.flags ?? anon?.flags;\n this.config = config ?? null;\n this.flags = flags ? [...flags] : null;\n this.viewFor = current;\n }\n\n /**\n * Keep the MAX_SLOTS most recently written user slots; `keep` and the\n * signed-out slot (read before sign-in and for every user's gaps) stay.\n */\n private prune(keep: string): void {\n const keys = Object.keys(this.record.slots).filter((k) => k !== ANON_SLOT);\n while (keys.length > MAX_SLOTS) {\n let oldest: string | null = null;\n for (const k of keys) {\n if (k === keep) continue;\n if (\n oldest === null ||\n this.record.slots[k]!.saved_at < this.record.slots[oldest]!.saved_at\n ) {\n oldest = k;\n }\n }\n if (oldest === null) break;\n delete this.record.slots[oldest];\n keys.splice(keys.indexOf(oldest), 1);\n }\n }\n\n private persist(): void {\n const empty = Object.keys(this.record.slots).length === 0;\n const text = empty ? null : JSON.stringify(this.record);\n // Writes chain so an older record never lands after a newer one.\n this.writes = this.writes\n .then(() => (text === null ? this.store.delete(this.key) : this.store.write(this.key, text)))\n .catch(() => {\n // Best effort: the held values stay in memory for this launch.\n });\n }\n}\n","/**\n * Values for the `X-App-Version`, `X-Device-Region` and `X-Device-OS`\n * request headers. Each is omitted when unknown, so every helper here\n * returns `undefined` for a value the header can't carry as-is.\n */\n\n/** Longest `X-App-Version` value sent (matches the core's limit). */\nexport const MAX_APP_VERSION_LENGTH = 64;\n\n/**\n * An app version for `X-App-Version`: the text before the first whitespace\n * (`\"2.3.1 (45)\"` sends `2.3.1`), which must start with an ASCII letter or\n * digit, hold only letters, digits and `.` `_` `+` `-`, and be at most\n * {@link MAX_APP_VERSION_LENGTH} characters: the shape the API records for\n * segment targeting. Anything else returns `undefined`.\n */\nexport function normalizeAppVersion(\n raw: string | null | undefined,\n): string | undefined {\n if (typeof raw !== \"string\") return undefined;\n const v = raw.trim().split(/\\s+/)[0] ?? \"\";\n if (v.length === 0 || v.length > MAX_APP_VERSION_LENGTH) return undefined;\n return /^[0-9A-Za-z][0-9A-Za-z._+-]*$/.test(v) ? v : undefined;\n}\n\n/**\n * The region subtag of a BCP 47 locale tag (`en-US` → `US`, `zh-Hant-TW` →\n * `TW`, POSIX `fr_FR.UTF-8` → `FR`, iOS `en_US@rg=gbzzzz` → `US`),\n * uppercased. Returns `undefined` when the tag carries no two-letter region\n * (`en`, `es-419`, `sr-Latn`).\n */\nexport function regionFromLocale(\n tag: string | null | undefined,\n): string | undefined {\n if (typeof tag !== \"string\" || tag.length === 0) return undefined;\n const parts = tag.split(/[.@]/)[0]!.split(/[-_]/);\n for (let i = 1; i < parts.length; i++) {\n const part = parts[i]!;\n // A one-letter singleton starts an extension (`-u-`, `-x-`); no region\n // subtag follows it.\n if (part.length === 1) break;\n if (/^[A-Za-z]{2}$/.test(part)) return part.toUpperCase();\n }\n return undefined;\n}\n\n/** The values `X-Device-OS` takes. */\nexport type DeviceOs =\n | \"ios\"\n | \"android\"\n | \"web\"\n | \"macos\"\n | \"windows\"\n | \"linux\"\n | \"other\";\n\nconst DEVICE_OS_VALUES: readonly DeviceOs[] = [\n \"ios\",\n \"android\",\n \"web\",\n \"macos\",\n \"windows\",\n \"linux\",\n \"other\",\n];\n\n/**\n * Map a platform OS name (React Native's `Platform.OS`, for example) to an\n * `X-Device-OS` value. Listed names pass through lowercased; any other\n * non-empty name is a known-but-unlisted OS and maps to `\"other\"`. Empty or\n * missing returns `undefined`.\n */\nexport function deviceOsFromPlatform(\n os: string | null | undefined,\n): DeviceOs | undefined {\n if (typeof os !== \"string\") return undefined;\n const v = os.trim().toLowerCase();\n if (v.length === 0) return undefined;\n return (DEVICE_OS_VALUES as readonly string[]).includes(v)\n ? (v as DeviceOs)\n : \"other\";\n}\n","/**\n * Live collection / messaging / gamification subscriptions for\n * `@layers/amba-react-native`.\n *\n * Public surface is byte-for-byte identical to `@layers/amba-web` and\n * `@layers/amba-node` so the docs are uniform across every JS SDK:\n *\n * const unsub = Amba.collection(\"messages\")\n * .where(\"circle_id\", circleId)\n * .subscribe((change) => {\n * // change.op: \"INSERT\" | \"UPDATE\" | \"DELETE\"; change.row: the row\n * });\n * unsub(); // stop listening\n *\n * const unsub2 = Amba.messaging\n * .conversation(conversationId)\n * .subscribe((change) => { ... });\n *\n * const unsub3 = Amba.gamification.subscribe((change) => { ... });\n *\n * ── Why a different transport from web/node ─────────────────────────────\n * The web/node SDKs read the streamed HTTP response body via\n * `response.body.getReader()`. React Native's `fetch` (Hermes / RN's\n * networking layer) resolves the Response only AFTER the body is fully\n * received and does NOT expose a readable `body` stream — so the web\n * transport silently never delivers a frame on a long-lived connection.\n *\n * Instead we drive the SAME server endpoint (an event stream) through\n * `XMLHttpRequest`, which RN implements with INCREMENTAL `responseText`:\n * `onreadystatechange` fires at `readyState === 3` (LOADING) as bytes\n * arrive, so we parse the newly-appended slice frame-by-frame. This is the\n * mechanism `react-native-sse` uses under the hood; we inline it to avoid a\n * peer dependency (and to set the auth header in `Authorization`, keeping\n * the session token out of the URL/query string where it would leak into\n * access logs — `EventSource` can't set headers).\n *\n * If XHR is somehow unavailable in the host runtime, we fall back to the\n * web-style `fetch` + streamed-body reader (works on RN-for-web / any\n * runtime that does expose `response.body`). Both transports speak the\n * exact same wire frames, so the public behaviour is identical.\n *\n * Reconnect/backoff and frame parsing match web/node exactly.\n */\n\nimport { pathSegment } from \"./path-segment.js\";\nimport { DEFAULT_REQUEST_TIMEOUT_MS, raceDeadline } from \"./request-deadline.js\";\n\nexport type RowChangeOp = \"INSERT\" | \"UPDATE\" | \"DELETE\";\n\nexport interface RealtimeChange<T = unknown> {\n /** Which kind of write happened. */\n op: RowChangeOp;\n /**\n * The affected row. For deletes this is a partial row (at minimum `id`).\n * If the row was too large to deliver inline, `truncated` is `true` and\n * `row` carries just `id` — re-fetch with `Amba.collections.find()` /\n * `findOne()` when you need the full body.\n */\n row: T;\n truncated?: boolean;\n}\n\nexport interface SubscribeOptions {\n /** Called once when the live connection is (re)established. */\n onOpen?: () => void;\n /** Called on a connection error before the SDK retries. */\n onError?: (err: unknown) => void;\n}\n\n/** Unsubscribe handle returned by `.subscribe(...)`. Call it to stop. */\nexport type Unsubscribe = () => void;\n\n/**\n * Auth + endpoint context for a subscription. `authHeaders` is read lazily\n * at connect time so reconnects pick up a rotated session token. Returns\n * `null` when there's no live session (subscribe throws).\n */\nexport interface RealtimeContext {\n baseUrl: string;\n authHeaders: () => Record<string, string> | null;\n /**\n * How long a connection may take to start streaming, in milliseconds\n * (the configured `requestTimeoutMs`; default 15 000). A connection that\n * hasn't started by then is aborted, reported through `onError`, and\n * retried with backoff. Once it starts, the stream stays open for as long\n * as the subscriber keeps it.\n */\n connectTimeoutMs?: number;\n}\n\nconst INITIAL_BACKOFF_MS = 500;\nconst MAX_BACKOFF_MS = 15_000;\n\nconst DEFAULT_API_URL = \"https://api.amba.dev\";\nconst DEFAULT_REALTIME_URL = \"https://realtime.amba.host\";\n\n/**\n * Resolve the base URL for live-update connections. Long-lived streams are\n * served from a dedicated realtime host (no request-duration ceiling), so\n * when the SDK points at the default production API the stream connects\n * there instead. Any custom `apiUrl` (dev, staging, self-hosted, proxy)\n * keeps live updates on that same origin — override semantics unchanged.\n */\nexport function resolveRealtimeBaseUrl(baseUrl: string): string {\n return baseUrl.replace(/\\/+$/, \"\") === DEFAULT_API_URL ? DEFAULT_REALTIME_URL : baseUrl;\n}\n\n/**\n * Builder returned by `Amba.collection(name)`. Chain `.where(...)` to\n * narrow, then `.subscribe(cb)` to start streaming.\n */\nexport class CollectionSubscription<T = unknown> {\n private readonly filters: Record<string, unknown> = {};\n\n constructor(\n private readonly ctx: RealtimeContext,\n private readonly name: string,\n ) {}\n\n /** Add an equality filter. Chainable. All filters must match (AND). */\n where(column: string, value: unknown): this {\n this.filters[column] = value;\n return this;\n }\n\n /**\n * Start listening. `cb` fires once per matching row change. Returns an\n * unsubscribe handle — call it to close the connection.\n */\n subscribe(cb: (change: RealtimeChange<T>) => void, options: SubscribeOptions = {}): Unsubscribe {\n const headers = this.ctx.authHeaders();\n if (!headers) {\n throw new Error(\n \"Amba.collection().subscribe() requires an authenticated session — call Amba.auth.signIn* first.\",\n );\n }\n const qs =\n Object.keys(this.filters).length > 0\n ? `?where=${encodeURIComponent(JSON.stringify(this.filters))}`\n : \"\";\n const url = `${resolveRealtimeBaseUrl(this.ctx.baseUrl)}/v1/client/realtime/collections/${pathSegment(\n this.name,\n )}${qs}`;\n return openStream<RealtimeChange<T>>({\n url,\n authHeaders: this.ctx.authHeaders,\n connectTimeoutMs: this.ctx.connectTimeoutMs,\n onChange: cb,\n options,\n parse: parseRowChangeFrame as (frame: string) => RealtimeChange<T> | null,\n });\n }\n}\n\n// ── Messaging: live conversation subscriptions ─────────────────────────────\n\n/**\n * A live message change in a conversation. `op` is \"INSERT\" for a new\n * message; moderation removals arrive as \"DELETE\". For a delete (or when a\n * message was too large to deliver inline) `row` may be partial — re-fetch\n * with `Amba.messaging.listMessages(conversationId)` when you need the full\n * body.\n */\nexport type MessageChange<T = unknown> = RealtimeChange<T>;\n\n/**\n * Builder returned by `Amba.messaging.conversation(id)`. Call `.subscribe(cb)`\n * to receive new messages live instead of polling.\n *\n * Delivery is scoped to conversation participants — you receive messages for\n * a conversation only if your session is a member of it. Reconnects with the\n * same exponential backoff as collection subscriptions.\n */\nexport class ConversationSubscription<T = unknown> {\n constructor(\n private readonly ctx: RealtimeContext,\n private readonly conversationId: string,\n ) {}\n\n subscribe(cb: (change: MessageChange<T>) => void, options: SubscribeOptions = {}): Unsubscribe {\n const headers = this.ctx.authHeaders();\n if (!headers) {\n throw new Error(\n \"Amba.messaging.conversation().subscribe() requires an authenticated session — call Amba.auth.signIn* first.\",\n );\n }\n const url = `${resolveRealtimeBaseUrl(this.ctx.baseUrl)}/v1/client/realtime/conversations/${pathSegment(\n this.conversationId,\n )}`;\n return openStream<MessageChange<T>>({\n url,\n authHeaders: this.ctx.authHeaders,\n connectTimeoutMs: this.ctx.connectTimeoutMs,\n onChange: cb,\n options,\n parse: parseRowChangeFrame as (frame: string) => MessageChange<T> | null,\n });\n }\n}\n\n// ── Gamification: live xp / level / achievement / streak updates ────────────\n\n/** Which slice of the signed-in user's gamification state changed. */\nexport type GamificationKind = \"xp\" | \"level\" | \"achievement\" | \"streak\";\n\n/**\n * A live change to the signed-in user's gamification state. `kind` says which\n * slice moved; `data` carries the new state inline so you can update without a\n * re-fetch:\n * - `xp` — `{ total_xp, level, delta }`\n * - `level` — `{ level, total_xp }`\n * - `achievement` — `{ achievement_id, unlocked }`\n * - `streak` — `{ streak_id, count, status, ... }`\n *\n * If a payload was too large to deliver inline, `truncated` is `true` and `data`\n * is empty — re-fetch with `Amba.xp.getBalance()` / `Amba.streaks.getAll()` etc.\n */\nexport interface GamificationChange<T = Record<string, unknown>> {\n kind: GamificationKind;\n data: T;\n truncated?: boolean;\n}\n\n/**\n * Builder returned by `Amba.gamification`. Call `.subscribe(cb)` to receive the\n * signed-in user's xp / level / achievement / streak changes live instead of\n * polling.\n *\n * Delivery is scoped to the signed-in user — you only ever receive your own\n * gamification changes, never another user's. Reconnects with the same\n * exponential backoff as collection subscriptions.\n */\nexport class GamificationSubscription {\n constructor(private readonly ctx: RealtimeContext) {}\n\n subscribe(cb: (change: GamificationChange) => void, options: SubscribeOptions = {}): Unsubscribe {\n const headers = this.ctx.authHeaders();\n if (!headers) {\n throw new Error(\n \"Amba.gamification.subscribe() requires an authenticated session — call Amba.auth.signIn* first.\",\n );\n }\n const url = `${resolveRealtimeBaseUrl(this.ctx.baseUrl)}/v1/client/realtime/gamification`;\n return openStream<GamificationChange>({\n url,\n authHeaders: this.ctx.authHeaders,\n connectTimeoutMs: this.ctx.connectTimeoutMs,\n onChange: cb,\n options,\n parse: parseGamificationFrame,\n });\n }\n}\n\n// ── Shared transport ───────────────────────────────────────────────────────\n\ninterface StreamArgs<E> {\n url: string;\n authHeaders: () => Record<string, string> | null;\n connectTimeoutMs?: number;\n onChange: (event: E) => void;\n options: SubscribeOptions;\n /** Turn one raw `\\n\\n`-delimited frame into a typed event, or null to skip. */\n parse: (frame: string) => E | null;\n}\n\n/**\n * Open a long-lived event stream and dispatch parsed events to `onChange`.\n * Reconnects with exponential backoff until the returned unsubscribe handle\n * is invoked. Chooses the XHR incremental-text transport (RN-native) when\n * available, else the streamed-`fetch`-body transport (RN-for-web / Node).\n */\nfunction openStream<E>(args: StreamArgs<E>): Unsubscribe {\n let stopped = false;\n let backoff = INITIAL_BACKOFF_MS;\n let closeActive: (() => void) | null = null;\n let settleActive: (() => void) | null = null;\n\n const run = async (): Promise<void> => {\n while (!stopped) {\n const headers = args.authHeaders();\n if (!headers) {\n // No active session (e.g. after signOut): stop reconnecting instead of\n // looping forever. A fresh subscribe() after sign-in starts a new stream.\n args.options.onError?.(new Error(\"no active session\"));\n break;\n }\n try {\n await new Promise<void>((resolve, reject) => {\n settleActive = resolve;\n // Reset backoff only once the connection proves HEALTHY (first\n // frame received), not merely on a successful connect — a server\n // that accepts then immediately drops would otherwise reset\n // backoff every loop → a tight reconnect storm.\n const onFirstFrame = (): void => {\n backoff = INITIAL_BACKOFF_MS;\n args.options.onOpen?.();\n };\n closeActive = connectOnce({\n url: args.url,\n headers,\n connectTimeoutMs:\n args.connectTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,\n onFrame: (frame) => {\n const ev = args.parse(frame);\n if (ev !== null) args.onChange(ev);\n },\n onFirstFrame,\n onDone: resolve,\n onError: reject,\n });\n });\n } catch (err) {\n if (stopped) break;\n args.options.onError?.(err);\n } finally {\n // Abort the just-ended/failed connection BEFORE reconnecting so a failed\n // attempt never leaves a live XHR/fetch open (which would duplicate\n // deliveries across retries). Abort is safe on an already-closed conn.\n closeActive?.();\n closeActive = null;\n settleActive = null;\n }\n if (stopped) break;\n await delay(backoff);\n backoff = Math.min(backoff * 2, MAX_BACKOFF_MS);\n }\n };\n\n void run();\n\n return () => {\n stopped = true;\n closeActive?.();\n // Settle the in-flight await so run() sees `stopped` and exits its loop\n // instead of hanging forever inside the connection Promise (no leaked coroutine).\n settleActive?.();\n };\n}\n\ninterface ConnectArgs {\n url: string;\n headers: Record<string, string>;\n /**\n * How long the response may take to start, in milliseconds. A connection\n * still waiting then fails through `onError` (and is retried).\n */\n connectTimeoutMs: number;\n /** One raw SSE frame (without the trailing blank line). */\n onFrame: (frame: string) => void;\n /** Fired once, the first time ANY frame is seen on this connection. */\n onFirstFrame: () => void;\n /** Stream ended cleanly (server closed). */\n onDone: () => void;\n /** Stream failed (transport error / non-2xx). */\n onError: (err: unknown) => void;\n}\n\n/**\n * Establish a single connection using the best available transport. Returns\n * an abort function. The XHR transport is preferred on React Native; the\n * fetch-stream transport covers RN-for-web and any runtime exposing a\n * readable `response.body`.\n */\nfunction connectOnce(args: ConnectArgs): () => void {\n if (typeof XMLHttpRequest !== \"undefined\") return connectXhr(args, XMLHttpRequest);\n return connectFetchStream(args);\n}\n\n/**\n * Incremental-`responseText` transport via XMLHttpRequest — the RN-native\n * path. `onreadystatechange` fires at readyState 3 (LOADING) as bytes\n * stream in; we track how much of `responseText` we've already parsed and\n * extract any newly-completed `\\n\\n`-delimited frames.\n */\nfunction connectXhr(args: ConnectArgs, XHR: typeof XMLHttpRequest): () => void {\n const xhr = new XHR();\n let consumed = 0; // chars of responseText already split into frames\n let buffer = \"\"; // partial trailing frame not yet terminated\n let sawFrame = false;\n let aborted = false;\n\n const drain = (): void => {\n // responseText is cumulative; only look at the newly-arrived slice.\n const text = xhr.responseText;\n if (text.length <= consumed) return;\n buffer += text.slice(consumed);\n consumed = text.length;\n let idx: number;\n while ((idx = buffer.indexOf(\"\\n\\n\")) !== -1) {\n const frame = buffer.slice(0, idx);\n buffer = buffer.slice(idx + 2);\n if (!sawFrame) {\n sawFrame = true;\n args.onFirstFrame();\n }\n args.onFrame(frame);\n }\n };\n\n xhr.open(\"GET\", args.url, true);\n for (const [k, v] of Object.entries(args.headers)) xhr.setRequestHeader(k, v);\n xhr.setRequestHeader(\"Accept\", \"text/event-stream\");\n\n // A connection whose response hasn't started by the deadline fails, so the\n // reconnect loop retries it. Some runtimes never report a dropped DNS\n // lookup, which would otherwise leave the subscription waiting forever.\n // Once the headers arrive the stream stays open for as long as it is kept.\n const connectTimer = setTimeout(() => {\n if (aborted || xhr.readyState >= 2) return;\n args.onError(\n new Error(`realtime connection timed out after ${args.connectTimeoutMs} ms`),\n );\n }, args.connectTimeoutMs);\n\n xhr.onreadystatechange = (): void => {\n if (aborted) return;\n // HEADERS_RECEIVED (2) and later: the response started.\n if (xhr.readyState >= 2) clearTimeout(connectTimer);\n // LOADING (3): body bytes are arriving — parse what we have so far.\n if (xhr.readyState === 3) {\n if (xhr.status >= 400) return; // error surfaced on DONE\n drain();\n }\n };\n\n xhr.onload = (): void => {\n if (aborted) return;\n if (xhr.status >= 200 && xhr.status < 300) {\n drain();\n args.onDone();\n } else {\n args.onError(new Error(`realtime connect failed: HTTP ${xhr.status}`));\n }\n };\n xhr.onerror = (): void => {\n if (aborted) return;\n args.onError(new Error(\"realtime transport error\"));\n };\n xhr.ontimeout = (): void => {\n if (aborted) return;\n args.onError(new Error(\"realtime connection timed out\"));\n };\n\n try {\n xhr.send();\n } catch (err) {\n clearTimeout(connectTimer);\n args.onError(err);\n }\n\n return () => {\n aborted = true;\n clearTimeout(connectTimer);\n try {\n xhr.abort();\n } catch {\n // best-effort\n }\n };\n}\n\n/**\n * Streamed-`fetch`-body transport — used when XHR isn't present (RN-for-web,\n * Node). Identical wire handling to web/node's `consume`.\n */\nfunction connectFetchStream(args: ConnectArgs): () => void {\n const controller = new AbortController();\n let aborted = false;\n\n void (async () => {\n try {\n // The response must start within the connect deadline; once it does,\n // the stream stays open for as long as it is kept.\n const resp = await raceDeadline(\n fetch(args.url, {\n method: \"GET\",\n headers: { ...args.headers, Accept: \"text/event-stream\" },\n signal: controller.signal,\n }),\n args.connectTimeoutMs,\n controller,\n (ms) => new Error(`realtime connection timed out after ${ms} ms`),\n );\n if (!resp.ok || !resp.body) {\n throw new Error(`realtime connect failed: HTTP ${resp.status}`);\n }\n const reader = (resp.body as ReadableStream<Uint8Array>).getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n let sawFrame = false;\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n let idx: number;\n while ((idx = buffer.indexOf(\"\\n\\n\")) !== -1) {\n const frame = buffer.slice(0, idx);\n buffer = buffer.slice(idx + 2);\n if (!sawFrame) {\n sawFrame = true;\n args.onFirstFrame();\n }\n args.onFrame(frame);\n }\n }\n if (!aborted) args.onDone();\n } catch (err) {\n if (!aborted) args.onError(err);\n }\n })();\n\n return () => {\n aborted = true;\n controller.abort();\n };\n}\n\n// ── Frame parsers (shared shapes with web/node) ─────────────────────────────\n\n/** Pull the `event:` name + joined `data:` lines out of one raw frame. */\nfunction readFrame(frame: string): { event: string; data: string } {\n let event = \"\";\n const dataLines: string[] = [];\n for (const line of frame.split(\"\\n\")) {\n if (line.startsWith(\":\")) continue; // keepalive comment\n if (line.startsWith(\"event:\")) event = line.slice(\"event:\".length).trim();\n else if (line.startsWith(\"data:\")) dataLines.push(line.slice(\"data:\".length).trim());\n }\n return { event, data: dataLines.join(\"\\n\") };\n}\n\nfunction parseRowChangeFrame<T>(frame: string): RealtimeChange<T> | null {\n const { event, data } = readFrame(frame);\n if (event !== \"change\") return null; // ignore ready / error / heartbeat\n try {\n const payload = JSON.parse(data) as {\n op: RowChangeOp;\n row: T;\n truncated?: boolean;\n };\n return { op: payload.op, row: payload.row, truncated: payload.truncated };\n } catch {\n return null; // malformed frame — skip rather than tear down the stream\n }\n}\n\nfunction parseGamificationFrame(frame: string): GamificationChange | null {\n const { event, data } = readFrame(frame);\n if (event !== \"change\") return null;\n try {\n const payload = JSON.parse(data) as {\n kind: GamificationKind;\n data: Record<string, unknown>;\n truncated?: boolean;\n };\n return { kind: payload.kind, data: payload.data, truncated: payload.truncated };\n } catch {\n return null;\n }\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","/**\n * @layers/amba-react-native — React Native SDK for amba.\n *\n * Pure-JS implementation: the SDK is now a thin client over `fetch`\n * plus AsyncStorage-backed identity. No native modules. The native\n * SDKs (Swift/Kotlin/Flutter/Unity via the shared runtime) keep\n * their core bindings; this RN SDK aligns with how comparable SDKs\n * ship to JS runtimes — pure fetch-over-HTTP.\n *\n * @example\n * ```tsx\n * import { Amba } from '@layers/amba-react-native';\n *\n * await Amba.configure({ projectId: 'proj_...', clientKey: 'amba_ck_...' });\n * await Amba.auth.signInAnonymously();\n * await Amba.events.track('app_opened');\n *\n * // Push registration with a token from expo-notifications:\n * import * as Notifications from 'expo-notifications';\n * const { data: token } = await Notifications.getDevicePushTokenAsync();\n * await Amba.push.register(token, 'apns'); // or 'fcm' on Android\n * ```\n */\n\nexport type * from \"./types\";\nimport type {\n AmbaConfig,\n AuthResult,\n User,\n FindOptions,\n FindResponse,\n Filter,\n FilterValue,\n ConfigBundle,\n FlagAssignment,\n ExperimentAssignment,\n PingResult,\n UserEntitlement,\n RestoreResult,\n OfferingsResult,\n MediaAsset,\n PresignData,\n PushPlatform,\n PushToken,\n AiMessageRequest,\n AiMessageResponse,\n Session,\n Achievement,\n AchievementProgress,\n Challenge,\n ChallengeProgress,\n CurrencyBalance,\n CurrencyTransaction,\n InventoryItem,\n InventoryPurchase,\n PurchaseRequest,\n ConsumeRequest,\n Leaderboard,\n LeaderboardEntry,\n Store,\n PurchaseOption,\n PurchaseResult,\n XpBalance,\n XpTransaction,\n Streak,\n FeedResponse,\n Friendship,\n Group,\n GroupMember,\n GroupCreate,\n GroupUpdate,\n Conversation,\n Message,\n SendMessageRequest,\n CreateConversationRequest,\n ListMessagesOptions,\n MarkReadResult,\n Report,\n ReportRequest,\n ReferralCode,\n ReferralClaim,\n Review,\n ReviewCreate,\n ReviewUpdate,\n Role,\n CatalogItem,\n ContentItem,\n DeepLink,\n DeepLinkCreate,\n OnboardingStatus,\n UserPatch,\n SessionRecord,\n SyncChange,\n SyncPushResult,\n SyncPullResult,\n LeagueMembership,\n LeagueCohort,\n LinkedIdentifier,\n AuthStateChangeEvent,\n AuthStateChangeReason,\n} from \"./types\";\n\nimport { AsyncStoragePersistence } from \"./async-storage-adapter.js\";\nimport { AmbaApiError } from \"./error.js\";\nimport {\n AmbaHttpClient,\n DEFAULT_BASE_URL,\n isSessionRefusedError,\n mapHttpError,\n} from \"./http-client.js\";\nimport type { Envelope } from \"./http-client.js\";\nimport { IdentityStore } from \"./identity.js\";\nimport type { CommitFence } from \"./identity.js\";\nimport { TOKEN_STORAGE_MODES, resolveTokenVault } from \"./token-storage.js\";\nimport { readTimeout, resolveRequestDeadlines } from \"./request-deadline.js\";\nimport { pathSegment } from \"./path-segment.js\";\nimport { EventBatchQueue } from \"./event-batch-queue.js\";\nimport type { EventFlushResult, EventDrop } from \"./event-batch-queue.js\";\nimport {\n OfflineQueue,\n isOfflineError,\n keyableProperties,\n newIdempotencyKey,\n withOptimistic,\n} from \"./resilience.js\";\nimport type { KvStore, OutboxOp, FlushResult } from \"./resilience.js\";\nimport {\n enableNativePushAutoRotation,\n type TokenRefreshSubscribe,\n} from \"./push-rotation.js\";\nimport { notifications } from \"./local-notifications.js\";\nimport { ExperimentAssignmentReporter } from \"./experiment-reporter.js\";\nimport type { ExperimentAssignedListener } from \"./experiment-reporter.js\";\nimport { RemoteValuesCache, recordKey } from \"./remote-values.js\";\nimport {\n deviceOsFromPlatform,\n normalizeAppVersion,\n regionFromLocale,\n} from \"./client-info.js\";\nimport {\n CollectionSubscription,\n ConversationSubscription,\n GamificationSubscription,\n} from \"./realtime.js\";\n\nexport { AmbaApiError } from \"./error.js\";\nexport {\n CollectionSubscription,\n ConversationSubscription,\n GamificationSubscription,\n} from \"./realtime.js\";\nexport type {\n RealtimeChange,\n MessageChange,\n GamificationChange,\n GamificationKind,\n RowChangeOp,\n SubscribeOptions,\n Unsubscribe,\n} from \"./realtime.js\";\nexport type { AmbaApiErrorCode } from \"./error.js\";\nexport { withOptimistic } from \"./resilience.js\";\nexport type {\n OutboxOp,\n OutboxOpKind,\n OutboxOwner,\n FlushResult,\n KvStore,\n} from \"./resilience.js\";\nexport type { SecureTokenStorage, TokenStorageMode } from \"./token-storage.js\";\nexport type { TokenRefreshSubscribe } from \"./push-rotation.js\";\nexport type { EventFlushResult, EventDrop } from \"./event-batch-queue.js\";\nexport type { LocalNotificationsNamespace } from \"./local-notifications.js\";\nexport type { ExperimentAssignedListener } from \"./experiment-reporter.js\";\n\n/** SDK semver — burned in at build time. Bump alongside package.json. */\nexport const SDK_VERSION = \"4.3.0\";\n\n// ── internal state ──────────────────────────────────────────────────\n\ninterface RuntimeState {\n http: AmbaHttpClient;\n identity: IdentityStore;\n debug: boolean;\n projectId: string;\n /** Client key — needed to build realtime subscribe auth headers. */\n clientKey: string;\n /** API base URL — the realtime subscribe path targets this host. */\n baseUrl: string;\n /** Optional override for `Amba.functions.invoke()` (custom domain / non-prod). */\n functionsBaseUrl: string | null;\n /** Cached server-resolved project id for the default function host. */\n resolvedFunctionsProjectId: string | null;\n /** In-flight default-host project id resolution, shared across invokes. */\n functionsProjectIdInflight: Promise<string> | null;\n /**\n * In-flight lazy anonymous sign-in, deduped per-state so concurrent reads\n * trigger exactly one sign-in. Lives on the state (not a module global) so a\n * re-`configure()` starts fresh and never awaits the previous identity's\n * sign-in. `null`/absent when idle.\n */\n anonSignInInflight?: Promise<void> | null;\n /**\n * In-flight `/refresh`, shared by every caller (the launch restore, the lazy\n * session, `auth.refresh()`, `signInAnonymously()` recovery). The server\n * accepts a refresh token once, so two concurrent refreshes would revoke\n * each other's result. `null`/absent when idle.\n */\n refreshInflight?: Promise<AuthResult> | null;\n /**\n * The `/refresh` request of `refreshInflight` and the refresh token it\n * presents, until its result is stored. A re-`configure()` hands it to the\n * next state (`inheritedRefresh`), so the token is never presented twice.\n */\n refreshExchange?: RefreshExchange | null;\n /**\n * A `/refresh` the previous state had in flight when `configure()` ran\n * again. The first refresh of the same token takes its answer instead of\n * sending the token once more.\n */\n inheritedRefresh?: RefreshExchange | null;\n /**\n * Set when a later `configure()` replaced this state. Its in-flight calls\n * may still settle, but they store nothing and tell no subscriber.\n */\n disposed?: boolean;\n /**\n * Links in flight. A link that adds an identity signs out every earlier\n * session, so a call the server refuses meanwhile waits for the pair the\n * link returns (`recoverRefusedSession`).\n */\n linksInflight?: Set<Promise<unknown>>;\n /** Durable key/value persistence (AsyncStorage-backed) for the outbox. */\n persistence: AsyncStoragePersistence;\n /** The config and flags cache (fetched, cached, configDefaults). */\n remoteValues: RemoteValuesCache;\n /** The identity (app user id, `\"\"` signed out) each first-read refresh ran for. */\n refreshedFor: { config: string | null; flags: string | null };\n /** Whether the offline outbox is diverting failed writes (opt-in). */\n offlineEnabled: boolean;\n /** Lazily-built offline outbox, bound to this state's persistence. */\n offlineQueue: OfflineQueue | null;\n eventQueue: EventBatchQueue | null;\n detachEventAppState?: () => void;\n /** Removes the foreground listener that reads a locked store again. */\n detachIdentityAppState?: () => void;\n /**\n * Events tracked while the anonymous id was a stand-in (a store locked\n * since launch), oldest first, waiting for the stored identity\n * (`holdEvent`, `releaseHeldEvents`). Memory only: before the first unlock\n * after a restart, the files AsyncStorage keeps can't be read either.\n */\n heldEvents?: HeldEvent[];\n /** Held events dropped to stay within `MAX_HELD_EVENTS`, not yet reported. */\n heldEventsDropped?: number;\n /** Set while `releaseHeldEvents` sends the held events. */\n releasingHeldEvents?: boolean;\n}\n\n/** An event `track()` held while the anonymous id was a stand-in. */\ninterface HeldEvent {\n event: string;\n properties: Record<string, unknown> | undefined;\n options: { telemetry?: boolean; immediate?: boolean } | undefined;\n /** When `track()` was called. */\n occurredAt: string;\n /** The identity generation when `track()` was called (see `releaseHeldEvents`). */\n generation: number;\n}\n\n/** The most events held at once; the oldest goes first past it. */\nconst MAX_HELD_EVENTS = 500;\n\n/**\n * Default host that serves deployed functions. `Amba.functions.invoke(name)`\n * targets `https://<projectId>.<DEFAULT_FUNCTIONS_HOST>/<name>` unless a\n * `functionsBaseUrl` override is configured.\n */\nconst DEFAULT_FUNCTIONS_HOST = \"fn.amba.host\";\nconst PROJECT_ID_PATTERN =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/** One `/refresh` request and the refresh token it presents. */\ninterface RefreshExchange {\n token: string;\n response: Promise<RefreshEnvelope>;\n}\n\nlet state: RuntimeState | null = null;\n/**\n * Monotonic token identifying the current offline activation. Bumped on every\n * `offline.enable()` AND every `configure()`, captured by each `enable()` into\n * its disable callback. A disabler only acts while the token still matches — so\n * a stale disabler (held across a re-`enable()` or a reconfigure) no-ops\n * instead of disabling the now-active outbox.\n */\nlet _offlineEnableToken = 0;\n\n/** The `configure()` running now, if any (see `liveState`). */\nlet configuring: Promise<void> | null = null;\n/** Serializes `configure()` calls, so each detaches the one before it. */\nlet configureChain: Promise<void> = Promise.resolve();\n\n/**\n * The state calls act on. While a `configure()` is running, waits for it,\n * so a sign-in, a link or a sign-out made meanwhile lands on the state it\n * installs (or on the earlier one, when it fails).\n */\nasync function liveState(): Promise<RuntimeState> {\n while (configuring) await configuring.catch(() => undefined);\n return getState();\n}\n\n/** Whether two states talk to the same project with the same stored identity. */\nfunction sameIdentityTarget(a: RuntimeState, b: RuntimeState): boolean {\n return (\n a.projectId === b.projectId &&\n a.clientKey === b.clientKey &&\n a.baseUrl === b.baseUrl &&\n a.persistence.prefix === b.persistence.prefix\n );\n}\n\nfunction getState(): RuntimeState {\n if (!state) {\n throw new AmbaApiError(\n \"NOT_INITIALIZED\",\n \"@layers/amba-react-native: SDK not configured. Call Amba.configure({ projectId, clientKey }) at app start.\",\n );\n }\n return state;\n}\n\n/**\n * Build the auth headers for a realtime subscribe connection, or `null`\n * when the SDK isn't configured or there's no live session. Read lazily at\n * connect time so reconnects pick up a rotated session token (the identity\n * store holds the canonical, auto-refreshed copy).\n */\nfunction realtimeAuthHeaders(): Record<string, string> | null {\n if (!state) return null;\n const token = state.identity.getSessionToken();\n if (!token) return null;\n return {\n \"X-Api-Key\": state.clientKey,\n Authorization: `Bearer ${token}`,\n };\n}\n\n// ── auth state pub/sub ──────────────────────────────────────────────\n\ntype AuthSubscriber = (\n session: Session | null,\n event: AuthStateChangeEvent,\n) => void;\nconst authSubscribers = new Set<AuthSubscriber>();\n\n/**\n * `Amba.onExperimentAssigned` listeners. `experiments.getVariant` reports\n * each result here; the reporter calls the listeners once per experiment per\n * user session. A sign-out (any auth change to no session) and `configure`\n * start a new session.\n */\nconst experimentReporter = new ExperimentAssignmentReporter();\n\n// ── remote config / flags: on-device reads ──────────────────────────\n\n/**\n * `configDefaults` from the latest `configure()` call, recorded before\n * configure reads the AsyncStorage cache. `config.getCached` and\n * `flags.isEnabled` serve them on a first render that runs while\n * `configure()` is still in flight.\n */\nlet _pendingConfigDefaults: Record<string, unknown> | null = null;\n\n/** Validate `AmbaConfig.configDefaults`: absent, or a plain object. */\nfunction readConfigDefaults(value: unknown): Record<string, unknown> | null {\n if (value === undefined || value === null) return null;\n if (typeof value !== \"object\" || Array.isArray(value)) {\n throw new AmbaApiError(\n \"INVALID_CONFIG\",\n \"Amba.configure: configDefaults must be an object of config key to value\",\n );\n }\n return value as Record<string, unknown>;\n}\n\n/** The pending `configDefaults` entry for `key`, ignoring inherited properties. */\nfunction pendingDefault(key: string): unknown {\n const defaults = _pendingConfigDefaults;\n if (!defaults || !Object.prototype.hasOwnProperty.call(defaults, key)) {\n return undefined;\n }\n const value = defaults[key];\n return value === null ? undefined : value;\n}\n\n/** `\"array\"` for arrays, else `typeof` — the JSON kind of a value. */\nfunction jsonKind(value: unknown): string {\n return Array.isArray(value) ? \"array\" : typeof value;\n}\n\n/**\n * `Amba.config.getCached`. A value whose JSON kind differs from a supplied\n * default's (a string where the default is a number) returns the default.\n */\nfunction configGetCached<T = unknown>(key: string): T | undefined;\nfunction configGetCached<T>(key: string, defaultValue: T): T;\nfunction configGetCached<T>(key: string, defaultValue?: T): T | undefined {\n if (state) refreshOnFirstRead(state, \"config\");\n const raw = state ? state.remoteValues.configValue(key) : pendingDefault(key);\n if (raw === undefined || raw === null) return defaultValue;\n if (\n defaultValue !== undefined &&\n defaultValue !== null &&\n jsonKind(raw) !== jsonKind(defaultValue)\n ) {\n return defaultValue;\n }\n return raw as T;\n}\n\n/** `Amba.flags.isEnabled`. */\nfunction flagsIsEnabled(key: string, defaultValue = false): boolean {\n if (state) refreshOnFirstRead(state, \"flags\");\n const held = state ? state.remoteValues.flagEnabled(key) : pendingDefault(key);\n return typeof held === \"boolean\" ? held : defaultValue;\n}\n\n/** `Amba.flags.getCached`. */\nfunction flagsGetCached(key: string): FlagAssignment | null {\n if (!state) return null;\n refreshOnFirstRead(state, \"flags\");\n return state.remoteValues.flag(key);\n}\n\n/**\n * The first on-device read of config (or flags) for each identity after\n * `configure()` starts one background fetch that replaces the cached values:\n * once at launch, and once more after a sign-in or sign-out changes who is\n * reading. Apps that never read cached values make no extra request. A\n * failure (offline) leaves the cached values in place.\n */\nfunction refreshOnFirstRead(s: RuntimeState, kind: \"config\" | \"flags\"): void {\n const identity = s.identity.getAppUserId() ?? \"\";\n if (s.refreshedFor[kind] === identity) return;\n s.refreshedFor[kind] = identity;\n void Promise.resolve()\n .then((): Promise<unknown> =>\n kind === \"config\" ? Amba.config.fetch() : Amba.flags.fetch(),\n )\n .catch(() => {});\n}\n\nfunction snapshotSession(user: User | null): Session | null {\n if (!user) return null;\n return {\n sessionToken: \"\",\n refreshToken: \"\",\n user,\n expiresAt: \"\",\n };\n}\n\n/**\n * Tell `onAuthStateChange` subscribers about an auth change of `state_`.\n * A state a later `configure()` replaced tells no one.\n */\nfunction notifyAuthSubscribers(\n state_: RuntimeState,\n session: Session | null,\n reason: AuthStateChangeReason,\n): void {\n if (state_ !== state || state_.disposed) return;\n if (session === null) experimentReporter.reset();\n const event: AuthStateChangeEvent = { reason };\n for (const cb of authSubscribers) {\n try {\n cb(session, event);\n } catch (err) {\n if (typeof console !== \"undefined\") {\n console.error(\"[amba] onAuthStateChange callback threw\", err);\n }\n }\n }\n}\n\nfunction normalizeAuthProviderNames(value: unknown): string[] {\n let normalized = value;\n if (typeof normalized === \"string\") {\n const trimmed = normalized.trim();\n if (trimmed === \"\") return [];\n try {\n normalized = JSON.parse(trimmed) as unknown;\n } catch {\n return [trimmed];\n }\n }\n\n if (!Array.isArray(normalized)) {\n if (normalized && typeof normalized === \"object\") {\n const provider = (normalized as { provider?: unknown }).provider;\n return typeof provider === \"string\" ? [provider] : [];\n }\n return [];\n }\n\n return normalized\n .map((entry) => {\n if (typeof entry === \"string\") {\n const trimmed = entry.trim();\n if (trimmed === \"\") return null;\n try {\n const parsed = JSON.parse(trimmed) as unknown;\n if (parsed && typeof parsed === \"object\") {\n const provider = (parsed as { provider?: unknown }).provider;\n return typeof provider === \"string\" ? provider : null;\n }\n } catch {\n return entry;\n }\n return entry;\n }\n if (entry && typeof entry === \"object\") {\n const provider = (entry as { provider?: unknown }).provider;\n return typeof provider === \"string\" ? provider : null;\n }\n return null;\n })\n .filter((provider): provider is string => provider !== null);\n}\n\nfunction normalizeUserAuthProviders(user: User): User {\n return {\n ...user,\n auth_providers: normalizeAuthProviderNames(\n (user as unknown as { auth_providers?: unknown }).auth_providers,\n ),\n };\n}\n\nfunction normalizeAuthResult(result: AuthResult): AuthResult {\n return {\n ...result,\n user: normalizeUserAuthProviders(result.user),\n };\n}\n\nfunction notifyFromAuthResult(\n state_: RuntimeState,\n result: AuthResult,\n reason: AuthStateChangeReason,\n): void {\n notifyAuthSubscribers(state_, snapshotSession(result.user), reason);\n}\n\n/** A call needs a session while the user must sign in again. */\nfunction signInRequiredError(): AmbaApiError {\n return new AmbaApiError(\n \"SIGN_IN_REQUIRED\",\n \"the session ended and the user must sign in again; call a signIn* method, or Amba.auth.signInAnonymously() to start a new anonymous user\",\n );\n}\n\nfunction secureStoreUnavailableError(): AmbaApiError {\n return new AmbaApiError(\n \"SECURE_STORE_UNAVAILABLE\",\n \"the stored session can't be read yet (the device may be locked); retry once it is unlocked\",\n );\n}\n\nfunction sessionChangedError(message: string): AmbaApiError {\n return new AmbaApiError(\"SESSION_CHANGED\", message);\n}\n\nfunction isSessionChanged(err: unknown): boolean {\n return err instanceof AmbaApiError && err.code === \"SESSION_CHANGED\";\n}\n\n/**\n * The stored session token, for a call that needs one. Without a session\n * the call fails with `SIGN_IN_REQUIRED` while the user must sign in again,\n * and with `UNAUTHORIZED` otherwise.\n */\nfunction requireSession(state_: RuntimeState): string {\n const token = state_.identity.getSessionToken();\n if (token) return token;\n if (state_.identity.isSignInRequired()) throw signInRequiredError();\n // A stored session this launch couldn't read (a locked device) may exist.\n if (state_.identity.credentialsUnknown()) throw secureStoreUnavailableError();\n throw new AmbaApiError(\n \"UNAUTHORIZED\",\n \"no active session — call signIn* first\",\n );\n}\n\n// ── persist-on-auth helpers ─────────────────────────────────────────\n\nfunction expiresAtFromResult(result: AuthResult): string {\n if (result.expires_at && typeof result.expires_at === \"string\")\n return result.expires_at;\n // Default 1h session lifetime — matches the engine's contract\n // when the server omits `expires_at`.\n return new Date(Date.now() + 3600 * 1000).toISOString();\n}\n\n/**\n * Store an auth result under `fence`. Resolves the generation it committed\n * at, or null when the fence refused it.\n */\nfunction persistAuthResult(\n state_: RuntimeState,\n result: AuthResult,\n anonymous: boolean | undefined,\n fence: CommitFence = {},\n): Promise<number | null> {\n return state_.identity.setSession(\n {\n appUserId: result.user.id,\n sessionToken: result.session_token,\n refreshToken: result.refresh_token,\n expiresAt: expiresAtFromResult(result),\n anonymous,\n },\n fence,\n );\n}\n\ninterface AuthEnvelope {\n data: AuthResult;\n}\n\nfunction nonEmptyString(value: unknown): string | null {\n return typeof value === \"string\" && value.length > 0 ? value : null;\n}\n\n/**\n * POST to an endpoint that answers with a session pair, and store it.\n * An anonymous sign-in records a purely anonymous session and adopts the\n * anonymous id the server answered with (the server mints one when the\n * request carried none). A link adopts the account's rotated anonymous id.\n * A refresh keeps the session's kind; every other sign-in records an\n * identified session.\n *\n * A sign-in the app asked for stores its session whatever happened\n * meanwhile: it is the newest thing the app asked for. The automatic\n * anonymous sign-in and a link pass a `fence` and fail with\n * `SESSION_CHANGED` (storing nothing) when the session changed while they\n * were in flight.\n */\nasync function postAuth(\n state_: RuntimeState,\n path: string,\n body: unknown,\n fence: CommitFence = {},\n): Promise<AuthResult> {\n const env = await state_.http.post<AuthEnvelope>(path, body);\n const result = normalizeAuthResult(env.data);\n const anonymous =\n path === P.authAnonymous ? true : path === P.authRefresh ? undefined : false;\n let target = state_;\n let committedAt = await persistAuthResult(state_, result, anonymous, fence);\n if (committedAt === null && fence.ifGeneration === undefined && state_.disposed) {\n // A sign-in the app asked for that a `configure()` overtook: the session\n // the server issued belongs on the state that replaced this one, when it\n // stores the same project's identity.\n const next = await liveState();\n if (next === state_ ? !next.disposed : sameIdentityTarget(next, state_)) {\n target = next;\n committedAt = await persistAuthResult(next, result, anonymous);\n }\n }\n if (committedAt === null) {\n throw sessionChangedError(\n \"the session changed while this sign-in was in flight; the newer session stays\",\n );\n }\n state_ = target;\n const adoptedAnonymousId =\n path === P.authAnonymous\n ? nonEmptyString(result.anonymous_id) ?? nonEmptyString(result.user?.anonymous_id)\n : path === P.authLink\n ? nonEmptyString(result.user?.anonymous_id)\n : null;\n if (adoptedAnonymousId) {\n await adoptAnonymousId(state_, adoptedAnonymousId, committedAt);\n }\n void state_.eventQueue?.flush();\n notifyFromAuthResult(state_, result, \"signed_in\");\n return result;\n}\n\n/**\n * Adopt the anonymous id the server holds for the signed-in user. Offline\n * writes queued before any session under the old id would replay under this\n * session; they are handed to the signed-in user first, so the id change\n * leaves them replayable.\n */\nasync function adoptAnonymousId(\n state_: RuntimeState,\n anonymousId: string,\n generation: number,\n): Promise<void> {\n const previous = state_.identity.getAnonymousId();\n if (anonymousId === previous) return;\n // The session this id belongs to was replaced meanwhile.\n if (state_.identity.getGeneration() !== generation) return;\n const appUserId = state_.identity.getAppUserId();\n if (appUserId && state_.offlineEnabled) {\n try {\n await getOfflineQueue(state_).claimAnonymousOps(previous, appUserId);\n } catch (err) {\n if (typeof console !== \"undefined\") {\n console.warn(\"[amba] could not reassign queued offline writes\", err);\n }\n }\n }\n await state_.identity.setAnonymousId(anonymousId, {\n ifGeneration: generation,\n });\n}\n\n// ── events held while the anonymous id is unknown ───────────────────\n\n/**\n * Hold an event `track()` got while the stored identity couldn't be read.\n * It keeps its call time and a copy of its properties; past\n * `MAX_HELD_EVENTS` the oldest is dropped.\n */\nfunction holdEvent(\n state_: RuntimeState,\n event: string,\n properties: Record<string, unknown> | undefined,\n options: { telemetry?: boolean; immediate?: boolean } | undefined,\n): void {\n const held = (state_.heldEvents ??= []);\n if (held.length >= MAX_HELD_EVENTS) {\n held.shift();\n state_.heldEventsDropped = (state_.heldEventsDropped ?? 0) + 1;\n }\n held.push({\n event,\n properties: snapshotProperties(properties),\n options,\n occurredAt: new Date().toISOString(),\n generation: state_.identity.getGeneration(),\n });\n}\n\n/** Report the held events dropped for `MAX_HELD_EVENTS`, once. */\nfunction reportHeldEventsDropped(state_: RuntimeState): void {\n const dropped = state_.heldEventsDropped ?? 0;\n state_.heldEventsDropped = 0;\n if (dropped > 0 && typeof console !== \"undefined\") {\n console.warn(\n `[amba] dropped ${dropped} event(s) tracked while the secure store was locked: more than ${MAX_HELD_EVENTS} were waiting`,\n );\n }\n}\n\n/** Drop the held events, with one warning that says why. */\nfunction dropHeldEvents(state_: RuntimeState, why: string): void {\n const held = state_.heldEvents ?? [];\n const count = held.length + (state_.heldEventsDropped ?? 0);\n state_.heldEvents = [];\n state_.heldEventsDropped = 0;\n if (count > 0 && typeof console !== \"undefined\") {\n console.warn(\n `[amba] dropped ${count} event(s) tracked while the secure store was locked: ${why}`,\n );\n }\n}\n\n/**\n * Send the held events once the stored identity is known, oldest first,\n * through the path `track()` uses, under the identity that loaded. A session\n * renewal the load started finishes first. An event that fails is reported\n * and the rest go on; events tracked meanwhile join the end of the line.\n * An event goes out only while the session it belongs to is still the\n * stored one: the session that loaded for events held before, the session\n * of the moment for events tracked during the release. When the session\n * changed since (the renewal is refused and the session ends, a sign-in or\n * sign-out lands), the event is dropped, and one warning counts them.\n */\nasync function releaseHeldEvents(state_: RuntimeState): Promise<void> {\n if (state_.releasingHeldEvents || !state_.heldEvents?.length) return;\n state_.releasingHeldEvents = true;\n let stale = 0;\n try {\n const loadedAt = state_.identity.getGeneration();\n await settleSessionInFlight(state_);\n reportHeldEventsDropped(state_);\n while (state_.heldEvents?.length && !state_.disposed) {\n const next = state_.heldEvents.shift() as HeldEvent;\n const belongsTo = Math.max(next.generation, loadedAt);\n if (state_.identity.getGeneration() !== belongsTo) {\n stale += 1;\n continue;\n }\n try {\n await sendEvent(\n state_,\n next.event,\n next.properties,\n next.options,\n next.occurredAt,\n );\n } catch (err) {\n if (typeof console !== \"undefined\") {\n console.warn(\n `[amba] could not send \"${next.event}\", tracked while the secure store was locked`,\n err,\n );\n }\n }\n }\n } finally {\n state_.releasingHeldEvents = false;\n if (stale > 0 && typeof console !== \"undefined\") {\n console.warn(\n `[amba] dropped ${stale} event(s) tracked while the secure store was locked: the session they belong to changed before they could be sent`,\n );\n }\n }\n}\n\n// ── lazy anonymous session ──────────────────────────────────────────\n//\n// `configure()` only builds local state — it mints no server session. To\n// spare every app from having to remember `await Amba.auth.signInAnonymously()`\n// before the first read (the silent-no-data trap), the HTTP client lazily\n// establishes an anonymous session on the first authenticated call. Deduped\n// via a single in-flight promise (stored ON the RuntimeState) so a burst of\n// parallel reads triggers exactly one sign-in. Keeping the promise on the state\n// — not a module global — means a re-`configure()` (new state) never awaits a\n// sign-in that targeted the previous, abandoned identity.\n\n/** Paths that must NOT trigger lazy anonymous sign-in. */\nfunction pathSkipsLazySession(path: string): boolean {\n // Auth endpoints would recurse (the sign-in request is itself a POST).\n if (path.startsWith(\"/v1/client/auth/\")) return true;\n // Event tracking is anonymous-first by design — it carries the\n // `anonymous_id` and works without a session, so don't force one (and don't\n // mint an MAU) just to fire telemetry.\n if (path === P.events || path.startsWith(`${P.events}/`)) return true;\n // Genuinely public, no-session-required surfaces — don't mint an MAU just to\n // read remote config / flags / a health ping.\n if (path === P.diagnosticsPing) return true;\n if (path === P.config || path.startsWith(`${P.config}/`)) return true;\n if (path === P.flags || path.startsWith(`${P.flags}/`)) return true;\n return false;\n}\n\n/**\n * Make sure a session is stored before an authenticated call. While the user\n * must sign in again (`sign_in_required`), the call fails with\n * `SIGN_IN_REQUIRED` and no anonymous user starts.\n */\nasync function ensureAnonymousSession(state_: RuntimeState): Promise<void> {\n if (state_.identity.getSessionToken()) return;\n if (state_.identity.isSignInRequired()) throw signInRequiredError();\n // A restore started at configure() may be in flight. When it ends without\n // a session (the refresh token was rejected, or the network failed), this\n // call starts its own sign-in below.\n if (state_.anonSignInInflight) {\n await state_.anonSignInInflight.catch(() => {});\n if (state_.identity.getSessionToken()) return;\n if (state_.identity.isSignInRequired()) throw signInRequiredError();\n }\n // A sign-out that lands while the anonymous sign-in is out leaves no\n // session: the sign-in drops its answer, and one more starts.\n for (let attempt = 0; attempt < 2; attempt += 1) {\n if (state_.identity.getSessionToken() || state_.disposed) return;\n if (state_.identity.isSignInRequired()) throw signInRequiredError();\n await anonymousSignInOnce(state_);\n }\n if (\n !state_.identity.getSessionToken() &&\n state_.identity.isSignInRequired()\n ) {\n throw signInRequiredError();\n }\n}\n\n/** One run of the lazy sign-in, shared by every caller that waits for it. */\nfunction anonymousSignInOnce(state_: RuntimeState): Promise<void> {\n if (!state_.anonSignInInflight) {\n state_.anonSignInInflight = (async () => {\n try {\n // A secure store that opened since launch loads the session it\n // holds first. A stored session that still can't be read is never\n // replaced by a new anonymous user: without its anonymous id (the\n // Expo credential record) the call fails here, and a locked vault\n // keeps it out of reach of `/anonymous` (`continueHeldSession`).\n await state_.identity.vaultStillUnreadable();\n if (await state_.identity.anonymousIdStillUnknown()) {\n throw secureStoreUnavailableError();\n }\n if (state_.identity.getSessionToken()) return;\n // A stored refresh token continues the session it belongs to, the\n // signed-in user's included. Only when there is none, or the server\n // rejects it for an anonymous session, does the device sign in\n // anonymously.\n if (await resumeWithRefreshToken(state_)) return;\n if (\n state_.identity.getSessionToken() ||\n state_.identity.isSignInRequired()\n ) {\n return;\n }\n await signInAnonymouslyAutomatic(state_);\n } finally {\n state_.anonSignInInflight = null;\n }\n })();\n }\n return state_.anonSignInInflight;\n}\n\n/**\n * Restore a stored session whose session token expired while the app was\n * closed, using its refresh token, so `isAuthenticated` and\n * `onAuthStateChange` show the signed-in user without waiting for an\n * authenticated call. Shares the in-flight slot with the lazy sign-in, so a\n * concurrent authenticated call waits for it. A rejected refresh token ends\n * the session (`endLostSession`); this never signs in anonymously.\n */\nfunction restoreStoredSession(state_: RuntimeState): Promise<void> {\n if (!state_.anonSignInInflight) {\n state_.anonSignInInflight = (async () => {\n try {\n await resumeWithRefreshToken(state_);\n } finally {\n state_.anonSignInInflight = null;\n }\n })();\n }\n return state_.anonSignInInflight;\n}\n\n/**\n * Answers that mean the refresh token's session is over: the server\n * rejected or revoked it, or it renewed a different user than the stored\n * one (`OWNER_MISMATCH`). A 401 without the server's envelope (a proxy, a\n * captive portal) maps to `UNAUTHORIZED` and leaves the session in place.\n */\nconst SESSION_ENDED_CODES = new Set([\n \"INVALID_TOKEN\",\n \"SESSION_REVOKED\",\n \"OWNER_MISMATCH\",\n]);\n\nfunction isSessionEnded(err: unknown): boolean {\n return err instanceof AmbaApiError && SESSION_ENDED_CODES.has(err.code);\n}\n\n/** Record a link in flight until it settles; returns the same promise. */\nfunction trackLink<T>(state_: RuntimeState, link: Promise<T>): Promise<T> {\n const links = (state_.linksInflight ??= new Set());\n links.add(link);\n const settle = () => {\n links.delete(link);\n };\n link.then(settle, settle);\n return link;\n}\n\n/**\n * After the server refused the stored session token on an authenticated\n * call (`SESSION_REVOKED`, `INVALID_SESSION_TOKEN`, `INVALID_SESSION`). The\n * HTTP client runs it for calls that send the session in the\n * `Authorization` header, `/v1/client/auth/me` and `/identifiers`\n * included; `withBodySession` runs it for the link and unlink calls, which\n * send the session in their body. True when a usable\n * session for the user the call was sent for is stored afterwards, so the\n * call is sent once more as that user:\n * - a link in flight on this device stores the pair it returns, which is\n * waited for first;\n * - a session another flow stored after the call left is used as is;\n * - otherwise the refresh token continues the session. A rejected refresh\n * token ends it (`endLostSession`) and the call's own error reaches the\n * app.\n * A session of any other user (a sign-in meanwhile, or the anonymous user\n * a lazy sign-in starts after the session ended) answers false, and the\n * call fails with the server's code. Offline or a server error keeps the\n * session and answers false. The\n * refresh runs in the in-flight slot the launch restore and the lazy\n * sign-in share, so calls refused together end or continue the session\n * once.\n */\nasync function recoverRefusedSession(\n state_: RuntimeState,\n usedToken: string,\n usedUserId: string | null,\n): Promise<boolean> {\n if (usedUserId === null) return false;\n const usableForCaller = (token: string | null): boolean =>\n token !== null &&\n token !== usedToken &&\n state_.identity.getAppUserId() === usedUserId;\n if (state_.linksInflight && state_.linksInflight.size > 0) {\n await Promise.allSettled(state_.linksInflight);\n }\n const current = state_.identity.getSessionToken();\n if (current !== usedToken) return usableForCaller(current);\n if (!state_.anonSignInInflight) {\n state_.anonSignInInflight = (async () => {\n try {\n await resumeWithRefreshToken(state_);\n } catch {\n // Offline or a server error: the session stays as it is.\n } finally {\n state_.anonSignInInflight = null;\n }\n })();\n }\n await state_.anonSignInInflight.catch(() => {});\n return usableForCaller(state_.identity.getSessionToken());\n}\n\n/**\n * Send an auth call that carries the stored session token in its body\n * (`/link`, `/unlink`). `send` builds the request from the token it is\n * given. When the server refuses that token, the session recovers through\n * `recoverRefusedSession` (one refresh shared with every other refused\n * call) and the call is sent once more with the recovered token; a second\n * refusal reaches the app. A link waits here outside `trackLink`: its own\n * failed attempt has already left `linksInflight` (the removal is the first\n * reaction on that promise), so recovery never waits on itself.\n */\nasync function withBodySession<T>(\n state_: RuntimeState,\n send: (sessionToken: string) => Promise<T>,\n): Promise<T> {\n await loadUnreadSession(state_);\n const usedToken = requireSession(state_);\n const usedUserId = state_.identity.getAppUserId();\n try {\n return await send(usedToken);\n } catch (err) {\n if (!isSessionRefusedError(err)) throw err;\n if (!(await recoverRefusedSession(state_, usedToken, usedUserId))) throw err;\n const recovered = state_.identity.getSessionToken();\n if (!recovered) throw err;\n return send(recovered);\n }\n}\n\n/**\n * Read a store the launch couldn't read (a locked device) again. When that\n * loads a session, wait for the renewal it starts, so the caller acts on\n * the loaded session (a sign-out revokes it, a link sends it).\n */\nasync function loadUnreadSession(state_: RuntimeState): Promise<void> {\n if (!state_.identity.credentialsUnknown()) return;\n await state_.identity.credentialsStillUnreadable();\n if (!state_.identity.credentialsUnknown()) await settleSessionInFlight(state_);\n}\n\n/**\n * Before a read of the current session (`me`, `getSession`,\n * `getCurrentIdentifiers`) while no session token is stored: load a stored\n * session the launch couldn't read yet, then wait for the launch restore or\n * sign-in in flight, so a check made at launch sees the session it\n * restores.\n */\nasync function settleSessionInFlight(state_: RuntimeState): Promise<void> {\n if (state_.identity.getSessionToken()) return;\n // A stored session that couldn't be read at launch (a locked device) is\n // loaded here once it can be.\n await state_.identity.credentialsStillUnreadable();\n if (state_.identity.getSessionToken() || !state_.anonSignInInflight) return;\n await state_.anonSignInInflight.catch(() => {});\n}\n\n/**\n * Continue the stored session with its refresh token. True when it did.\n * False when there is no refresh token, or the session is over (the server\n * rejected the refresh token, or it renewed another user): the session then\n * ends through `endLostSession`. Any other failure (offline, a 5xx) is\n * thrown, so a flaky network never ends a session or starts a new user.\n */\nasync function resumeWithRefreshToken(state_: RuntimeState): Promise<boolean> {\n const used = state_.identity.getRefreshToken();\n if (!used) return false;\n const startedAt = state_.identity.getGeneration();\n try {\n await refreshSession(state_);\n return true;\n } catch (err) {\n // Another flow replaced or ended the session meanwhile (a sign-in, a\n // sign-out, the refresh itself): keep what it stored.\n if (state_.identity.getRefreshToken() !== used) {\n return state_.identity.getSessionToken() !== null;\n }\n if (!isSessionEnded(err)) throw err;\n await endLostSession(state_, startedAt);\n return false;\n }\n}\n\n/**\n * End a session that is over without a sign-out, once, unless the session\n * changed since `startedAt`.\n *\n * - A session that gained an identity (a sign-in other than anonymous, or a\n * link) leaves the user to sign in again: its tokens are cleared, the\n * anonymous id rotates (it belongs to the identified account), and\n * subscribers hear `null` with `sign_in_required`. No new anonymous user\n * starts; calls that need a session fail with `SIGN_IN_REQUIRED`.\n * - A purely anonymous session keeps its anonymous id, so the next call that\n * needs a session resumes the same anonymous user through `/anonymous`\n * (or the server starts a new one). Subscribers hear `null` with\n * `session_ended`.\n * - A session of unknown kind (stored by an SDK before 4.0.14) is treated\n * like an anonymous one: `/anonymous` resumes a still-anonymous user, and\n * answers `IDENTITY_SIGN_IN_REQUIRED` for one who linked an identity,\n * which then leaves the user to sign in again.\n */\nasync function endLostSession(\n state_: RuntimeState,\n startedAt: number,\n): Promise<void> {\n if (state_.identity.snapshot().anonymousSession === false) {\n const ended = await state_.identity.clearSession(false, {\n signInRequired: true,\n ifGeneration: startedAt,\n });\n if (ended) notifyAuthSubscribers(state_, null, \"sign_in_required\");\n return;\n }\n const ended = await state_.identity.clearSession(false, {\n keepIdWhenKindUnknown: true,\n ifGeneration: startedAt,\n });\n if (ended) notifyAuthSubscribers(state_, null, \"session_ended\");\n}\n\n/**\n * The anonymous sign-in the SDK starts on its own before an authenticated\n * call: `/v1/client/auth/anonymous` with the device's anonymous id. It\n * stores its session only when nothing replaced the stored identity while\n * it was in flight; a sign-in that landed first wins and this answer is\n * dropped.\n *\n * When the server answers `IDENTITY_SIGN_IN_REQUIRED` (the id belongs to a\n * user who signs in with a linked identity), the stored session continues\n * when this device holds its refresh token. Otherwise the user must sign in\n * again (`sign_in_required`): no new anonymous user starts on its own.\n */\nasync function signInAnonymouslyAutomatic(state_: RuntimeState): Promise<void> {\n if (state_.disposed) return;\n const startedAt = state_.identity.getGeneration();\n try {\n await postAuth(\n state_,\n P.authAnonymous,\n { anonymous_id: state_.identity.getAnonymousId() },\n { ifGeneration: startedAt },\n );\n } catch (err) {\n if (isSessionChanged(err)) return;\n if (!(err instanceof AmbaApiError) || err.code !== IDENTITY_SIGN_IN_REQUIRED) {\n throw err;\n }\n if (await continueHeldSession(state_)) return;\n const ended = await state_.identity.clearSession(true, {\n signInRequired: true,\n ifGeneration: startedAt,\n });\n if (ended) notifyAuthSubscribers(state_, null, \"sign_in_required\");\n }\n}\n\nconst IDENTITY_SIGN_IN_REQUIRED = \"IDENTITY_SIGN_IN_REQUIRED\";\n\n/**\n * After `/anonymous` answered `IDENTITY_SIGN_IN_REQUIRED`: continue the\n * stored session when this device holds its refresh token. Null when it\n * holds none, or the server rejected it. While the stored session can't be\n * read (an iOS device not yet unlocked since a restart), it is unknown, so\n * the call fails with `SECURE_STORE_UNAVAILABLE` and replaces nothing.\n */\nasync function continueHeldSession(\n state_: RuntimeState,\n): Promise<AuthResult | null> {\n if (await state_.identity.credentialsStillUnreadable()) {\n throw secureStoreUnavailableError();\n }\n const used = state_.identity.getRefreshToken();\n if (!used) return null;\n try {\n return await refreshSession(state_);\n } catch (refreshErr) {\n if (!isSessionEnded(refreshErr)) throw refreshErr;\n // The refresh token was rejected. A launch restore sharing the same\n // refresh may already have cleared it; a different stored token means\n // another sign-in replaced the session meanwhile.\n const current = state_.identity.getRefreshToken();\n if (current !== null && current !== used) throw refreshErr;\n return null;\n }\n}\n\ninterface RefreshEnvelope {\n data: {\n session_token: string;\n refresh_token: string;\n expires_at?: string | null;\n user?: User;\n };\n}\n\n/**\n * Rotate the session pair with the stored refresh token. The session keeps\n * its user and its kind (anonymous or identified), and auth-state\n * subscribers hear the refreshed session. The server includes the user; an\n * older server that leaves it out keeps the stored user id, and the result's\n * user then carries only that id. A pair that renews a different user than\n * the stored one is never stored: the session ends (`endLostSession`) and\n * the call fails with `OWNER_MISMATCH`.\n */\nfunction refreshSession(state_: RuntimeState): Promise<AuthResult> {\n if (state_.refreshInflight) return state_.refreshInflight;\n const inflight = refreshSessionOnce(state_);\n state_.refreshInflight = inflight;\n const release = () => {\n if (state_.refreshInflight === inflight) state_.refreshInflight = null;\n };\n inflight.then(release, release);\n return inflight;\n}\n\nasync function refreshSessionOnce(state_: RuntimeState): Promise<AuthResult> {\n // A state a later configure() replaced presents no refresh token: the new\n // state owns the stored session now.\n if (state_.disposed) {\n throw sessionChangedError(\"the SDK was configured again\");\n }\n const refreshToken = state_.identity.getRefreshToken();\n if (!refreshToken && state_.identity.isSignInRequired()) {\n throw signInRequiredError();\n }\n if (!refreshToken)\n throw new AmbaApiError(\n \"UNAUTHORIZED\",\n \"no refresh token available — call signIn* first\",\n );\n const startedAt = state_.identity.getGeneration();\n const exchange = refreshExchangeFor(state_, refreshToken);\n try {\n const env = await exchange.response;\n // A sign-in or sign-out while the request was out replaced the session;\n // storing this pair would undo it.\n if (\n state_.identity.getRefreshToken() !== refreshToken ||\n state_.identity.getGeneration() !== startedAt\n ) {\n throw sessionChangedError(\n \"the session changed while it was being refreshed\",\n );\n }\n const storedUserId = state_.identity.getAppUserId();\n const renewedUserId = nonEmptyString(env.data.user?.id);\n if (\n renewedUserId !== null &&\n storedUserId !== null &&\n renewedUserId !== storedUserId\n ) {\n // The stored refresh token renewed someone else's session: the stored\n // pair is not the stored user's. Nothing of the answer is kept, and\n // the stored session ends.\n await endLostSession(state_, startedAt);\n throw new AmbaApiError(\n \"OWNER_MISMATCH\",\n \"the refreshed session belongs to a different user than the stored one; sign in again\",\n );\n }\n const appUserId = renewedUserId ?? storedUserId;\n if (!appUserId)\n throw new AmbaApiError(\n \"UNAUTHORIZED\",\n \"the refreshed session names no user — call signIn* first\",\n );\n const user: User = env.data.user\n ? normalizeUserAuthProviders(env.data.user)\n : { id: appUserId, auth_providers: [], properties: {} };\n const result: AuthResult = {\n session_token: env.data.session_token,\n refresh_token: env.data.refresh_token,\n user,\n expires_at: env.data.expires_at ?? null,\n };\n const committedAt = await persistAuthResult(state_, result, undefined, {\n ifGeneration: startedAt,\n advance: false,\n });\n if (committedAt === null) {\n throw sessionChangedError(\n \"the session changed while it was being refreshed\",\n );\n }\n // Adopt the account's current anonymous id. A link (on this device or\n // another) gives the account a new one, so for an anonymous session a\n // different id means its user gained an identity, and the session counts\n // as identified from now on. Skipped when a sign-in or sign-out replaced\n // the pair meanwhile.\n const anonymousId = nonEmptyString(user.anonymous_id);\n if (\n anonymousId &&\n anonymousId !== state_.identity.getAnonymousId() &&\n state_.identity.getRefreshToken() === result.refresh_token\n ) {\n if (state_.identity.snapshot().anonymousSession === true) {\n await state_.identity.markIdentified({ ifGeneration: committedAt });\n }\n await adoptAnonymousId(state_, anonymousId, committedAt);\n }\n void state_.eventQueue?.flush();\n notifyFromAuthResult(state_, result, \"token_refreshed\");\n return result;\n } finally {\n if (state_.refreshExchange === exchange) state_.refreshExchange = null;\n }\n}\n\n/**\n * The `/refresh` request for `refreshToken`: the one the previous state had\n * in flight for the same token when `configure()` ran again, or a new one.\n * It stays on the state until its answer is stored, so a `configure()`\n * meanwhile hands it on instead of presenting the token again.\n */\nfunction refreshExchangeFor(\n state_: RuntimeState,\n refreshToken: string,\n): RefreshExchange {\n const inherited = state_.inheritedRefresh;\n if (inherited) state_.inheritedRefresh = null;\n const exchange: RefreshExchange =\n inherited && inherited.token === refreshToken\n ? inherited\n : {\n token: refreshToken,\n response: state_.http.post<RefreshEnvelope>(P.authRefresh, {\n refresh_token: refreshToken,\n }),\n };\n state_.refreshExchange = exchange;\n return exchange;\n}\n\ninterface UserEnvelope {\n data: { user: User };\n}\n\ninterface LinkEnvelope {\n data: {\n user: User;\n session_token?: string;\n refresh_token?: string;\n expires_at?: string | null;\n };\n}\n\n/**\n * POST an OTP or email + password link. The request asks the server to\n * rotate the session (`rotate_session: true`): the link revokes every token\n * minted for this user before it, and answers with the pair that replaces\n * them, which is stored here. The user id does not change, so no auth-state\n * change fires. The session counts as identified from now on, and the\n * account's rotated anonymous id is adopted.\n */\nfunction postLink(\n state_: RuntimeState,\n body: Record<string, unknown>,\n): Promise<User> {\n return withBodySession(state_, (sessionToken) =>\n trackLink(\n state_,\n postLinkOnce(state_, { ...body, session_token: sessionToken }),\n ),\n );\n}\n\nasync function postLinkOnce(\n state_: RuntimeState,\n body: Record<string, unknown>,\n): Promise<User> {\n // The link acts for the session it was sent with: a sign-in or sign-out\n // that lands meanwhile wins, and the link's pair is not stored over it.\n const startedAt = state_.identity.getGeneration();\n const fence: CommitFence = { ifGeneration: startedAt, advance: false };\n const env = await state_.http.post<LinkEnvelope>(P.authLink, {\n ...body,\n rotate_session: true,\n });\n const user = normalizeUserAuthProviders(env.data.user);\n const sessionToken = nonEmptyString(env.data.session_token);\n const refreshToken = nonEmptyString(env.data.refresh_token);\n const stored =\n sessionToken && refreshToken\n ? (await state_.identity.setSession(\n {\n appUserId: user.id,\n sessionToken,\n refreshToken,\n expiresAt: expiresAtFromResult({\n session_token: sessionToken,\n refresh_token: refreshToken,\n user,\n expires_at: env.data.expires_at ?? null,\n }),\n anonymous: false,\n },\n fence,\n )) !== null\n : await state_.identity.markIdentified(fence);\n if (!stored) {\n throw sessionChangedError(\n \"the session changed while the link was in flight; the newer session stays\",\n );\n }\n const anonymousId = nonEmptyString(user.anonymous_id);\n if (anonymousId) {\n await adoptAnonymousId(state_, anonymousId, startedAt);\n }\n return user;\n}\n\ninterface IdentifiersEnvelope {\n data: { identifiers: LinkedIdentifier[] };\n}\n\n/**\n * POST helper for endpoints that return `{ data: { user } }` and mint no\n * tokens. Used by `unlink*`: existing session tokens stay untouched.\n */\nasync function postUserNoRotate(\n state_: RuntimeState,\n path: string,\n body: unknown,\n): Promise<User> {\n const env = await state_.http.post<UserEnvelope>(path, body);\n return normalizeUserAuthProviders(env.data.user);\n}\n\nasync function unlinkProvider(\n provider: \"sms_otp\" | \"email_otp\" | \"apple\" | \"google\",\n): Promise<User> {\n const s = await liveState();\n return withBodySession(s, (sessionToken) =>\n postUserNoRotate(s, P.authUnlink, {\n provider,\n session_token: sessionToken,\n }),\n );\n}\n\n// ── light client-side validation (mirrors engine) ────────────────\n\nfunction requireEmail(email: string): void {\n const trimmed = email.trim();\n if (trimmed === \"\")\n throw new AmbaApiError(\"VALIDATION_ERROR\", \"email must not be empty\");\n if (!trimmed.includes(\"@\") || !trimmed.includes(\".\"))\n throw new AmbaApiError(\n \"VALIDATION_ERROR\",\n `email '${email}' is not a valid email address`,\n );\n}\nfunction requirePassword(password: string): void {\n if (password.length < 8)\n throw new AmbaApiError(\n \"VALIDATION_ERROR\",\n \"password must be at least 8 characters\",\n );\n}\nfunction requireOtpCode(code: string): void {\n if (code.length !== 6 || !/^[0-9]{6}$/.test(code))\n throw new AmbaApiError(\"VALIDATION_ERROR\", \"code must be 6 digits\");\n}\n\n// Strict E.164: + followed by 1–9 then 6–14 more digits. Same shape\n// the server requires and that Telnyx wants on the wire.\nconst E164_RE = /^\\+[1-9][0-9]{6,14}$/;\nfunction requireE164Phone(phone: string): void {\n if (!E164_RE.test(phone))\n throw new AmbaApiError(\n \"VALIDATION_ERROR\",\n \"phone must be E.164 (starts with +, 8–15 digits)\",\n );\n}\n\n// ── path helpers (mirror Rust constants) ────────────────────────────\n\nconst P = {\n authAnonymous: \"/v1/client/auth/anonymous\",\n authEmailLogin: \"/v1/client/auth/email/login\",\n authEmailSignup: \"/v1/client/auth/email/signup\",\n authEmailOtpRequest: \"/v1/client/auth/email-otp/request\",\n authSmsOtpRequest: \"/v1/client/auth/sms-otp/request\",\n authSmsOtpVerify: \"/v1/client/auth/sms-otp/verify\",\n authEmailOtpVerify: \"/v1/client/auth/email-otp/verify\",\n authSocial: \"/v1/client/auth/social\",\n authMagicLinkRequest: \"/v1/client/auth/magic-link/request\",\n authMagicLinkVerify: \"/v1/client/auth/magic-link/verify\",\n authLink: \"/v1/client/auth/link\",\n authUnlink: \"/v1/client/auth/unlink\",\n authIdentifiers: \"/v1/client/auth/identifiers\",\n authRefresh: \"/v1/client/auth/refresh\",\n authLogout: \"/v1/client/auth/logout\",\n authMe: \"/v1/client/auth/me\",\n events: \"/v1/client/events\",\n users: \"/v1/client/users\",\n usersMe: \"/v1/client/users/me\",\n sessions: \"/v1/client/sessions\",\n sync: \"/v1/client/sync\",\n leagues: \"/v1/client/leagues\",\n collections: \"/v1/client/collections\",\n storage: \"/v1/client/media\",\n storagePresign: \"/v1/client/media/presign\",\n storageCommit: \"/v1/client/media/commit\",\n push: \"/v1/client/push\",\n pushRegister: \"/v1/client/push/register\",\n pushUnregister: \"/v1/client/push/unregister\",\n pushTokens: \"/v1/client/push/tokens\",\n pushSubscribe: \"/v1/client/push/subscribe\",\n pushUnsubscribe: \"/v1/client/push/unsubscribe\",\n entitlements: \"/v1/client/entitlements\",\n entitlementsRestore: \"/v1/client/entitlements/restore\",\n offerings: \"/v1/client/offerings\",\n aiAnthropic: \"/v1/client/ai/anthropic/messages\",\n config: \"/v1/client/config\",\n flags: \"/v1/client/flags\",\n experiments: \"/v1/client/experiments\",\n diagnosticsPing: \"/v1/client/diagnostics/ping\",\n achievements: \"/v1/client/achievements\",\n challenges: \"/v1/client/challenges\",\n catalog: \"/v1/client/catalog\",\n currencies: \"/v1/client/currencies\",\n inventory: \"/v1/client/inventory\",\n leaderboards: \"/v1/client/leaderboards\",\n stores: \"/v1/client/stores\",\n xp: \"/v1/client/xp\",\n streaks: \"/v1/client/streaks\",\n feeds: \"/v1/client/feeds\",\n friends: \"/v1/client/friends\",\n groups: \"/v1/client/groups\",\n messaging: \"/v1/client/messaging\",\n moderation: \"/v1/client/moderation\",\n reviews: \"/v1/client/reviews\",\n roles: \"/v1/client/roles\",\n referrals: \"/v1/client/referrals\",\n content: \"/v1/client/content\",\n deepLinks: \"/v1/client/deep-links\",\n onboarding: \"/v1/client/onboarding\",\n};\n\n/**\n * Encode one caller-supplied id, key or name as a single URL path segment.\n * `encodeURIComponent` escapes `/`, `?` and `#`; `.` and `..` are refused\n * because URL resolution treats them as steps to another route, and the\n * empty string is refused because it collapses onto the parent route.\n */\nfunction encodePathSegment(s: string): string {\n return pathSegment(s);\n}\n\n/** Options for the identity-token sign-in and link calls. */\nexport interface SocialSignInOptions {\n /**\n * The raw nonce generated for this sign-in attempt. The provider request\n * carried its SHA-256 hex digest; Amba checks the token's `nonce` claim\n * against it.\n */\n nonce?: string;\n}\n\nfunction nonceField(options: SocialSignInOptions | undefined): {\n nonce?: string;\n} {\n const nonce = options?.nonce;\n if (nonce === undefined) return {};\n if (typeof nonce !== \"string\" || nonce.trim() === \"\") {\n throw new AmbaApiError(\"VALIDATION_ERROR\", \"nonce must be a non-empty string\");\n }\n return { nonce };\n}\n\n// ── public API ──────────────────────────────────────────────────────\n\n/** Options for `Amba.functions.invoke`. */\nexport interface InvokeOptions {\n /** HTTP method. Defaults to `\"POST\"`. */\n method?: \"POST\" | \"GET\" | \"PUT\" | \"PATCH\" | \"DELETE\";\n /** Extra headers merged on top of the injected auth headers. */\n headers?: Record<string, string>;\n /**\n * How long this invocation may take, in milliseconds, from sending it to\n * reading the whole response. Defaults to the configured\n * `longRequestTimeoutMs` (5 minutes). Past it the call fails with the\n * retryable `NETWORK_ERROR`.\n */\n timeoutMs?: number;\n}\n\n/**\n * Resolve the full invocation URL for a deployed function. Resolution checks\n * `functionsBaseUrl` first, then uses a UUID-shaped configured project id\n * directly, and finally falls back to a deduped `diagnostics.ping` for the\n * authoritative server-side project id. The resolved default-host id is\n * cached for subsequent invokes.\n */\nasync function resolveFunctionUrl(\n s: RuntimeState,\n name: string,\n): Promise<string> {\n if (s.functionsBaseUrl) {\n return `${s.functionsBaseUrl.replace(/\\/$/, \"\")}/${pathSegment(name)}`;\n }\n const projectId = await resolveFunctionsProjectId(s);\n return `https://${projectId}.${DEFAULT_FUNCTIONS_HOST}/${pathSegment(name)}`;\n}\n\nasync function resolveFunctionsProjectId(s: RuntimeState): Promise<string> {\n if (s.resolvedFunctionsProjectId) {\n return s.resolvedFunctionsProjectId;\n }\n if (PROJECT_ID_PATTERN.test(s.projectId)) {\n s.resolvedFunctionsProjectId = s.projectId.toLowerCase();\n return s.resolvedFunctionsProjectId;\n }\n if (!s.functionsProjectIdInflight) {\n const resolution = (async () => {\n const ping = await Amba.diagnostics.ping();\n if (!ping.ok || !ping.server_project_id) {\n throw new AmbaApiError(\n \"FUNCTIONS_HOST_UNRESOLVED\",\n \"Could not resolve the functions host for this project. Pass `functionsBaseUrl` to Amba.configure() to invoke functions explicitly.\",\n ping,\n );\n }\n s.resolvedFunctionsProjectId = ping.server_project_id;\n return ping.server_project_id;\n })();\n s.functionsProjectIdInflight = resolution;\n void resolution.then(\n () => {\n if (s.functionsProjectIdInflight === resolution) {\n s.functionsProjectIdInflight = null;\n }\n },\n () => {\n if (s.functionsProjectIdInflight === resolution) {\n s.functionsProjectIdInflight = null;\n }\n },\n );\n }\n return s.functionsProjectIdInflight;\n}\n\n// ── offline outbox plumbing ─────────────────────────────────────────\n\n/**\n * Bridge the AsyncStorage-backed persistence to the {@link KvStore} the\n * outbox persists through. Namespaced under the configured storage prefix so\n * multiple projects in one app don't collide.\n */\nfunction offlineStore(s: RuntimeState): KvStore {\n return {\n get: (key) => s.persistence.read(key),\n set: (key, value) => s.persistence.write(key, value),\n delete: (key) => s.persistence.delete(key),\n };\n}\n\n/**\n * Whether a live `track` (outbox on) carries its idempotency key:\n * engagement events whose properties the keyed path takes. Telemetry stays\n * on the unkeyed hot path, which writes no idempotency record and answers a\n * failed telemetry insert with 200; its replays from the outbox are keyed.\n */\nfunction keyedLiveTrack(\n properties: unknown,\n telemetry: boolean | undefined,\n): boolean {\n return telemetry !== true && keyableProperties(properties);\n}\n\n/**\n * A JSON copy of `properties`: exactly what a request body serializes, and\n * immune to the app changing the object after `track()` returns.\n */\nfunction snapshotProperties(\n properties: Record<string, unknown> | undefined,\n): Record<string, unknown> | undefined {\n if (properties === undefined || properties === null) return undefined;\n const text = JSON.stringify(properties);\n return text === undefined\n ? undefined\n : (JSON.parse(text) as Record<string, unknown>);\n}\n\n/**\n * Replay one queued mutation against the live API, forwarding the op's\n * idempotency key on collection inserts and tracked events so a write that\n * landed before the connection dropped is never double-applied (API\n * migration 056 for collections, `client_event_idempotency` for events).\n */\nasync function replayOutboxOp(s: RuntimeState, op: OutboxOp): Promise<void> {\n switch (op.kind) {\n case \"collection.insert\":\n await s.http.post(\n `${P.collections}/${encodePathSegment(op.collection as string)}`,\n {\n ...(op.payload ?? {}),\n idempotency_key: op.idempotencyKey,\n },\n );\n return;\n case \"collection.update\":\n await s.http.patch(\n `${P.collections}/${encodePathSegment(op.collection as string)}/${encodePathSegment(op.id as string)}`,\n { set: op.payload ?? {} },\n );\n return;\n case \"collection.delete\":\n await s.http.delete(\n `${P.collections}/${encodePathSegment(op.collection as string)}/${encodePathSegment(op.id as string)}`,\n );\n return;\n case \"track\":\n try {\n // The op's idempotency key sends the replay down the events route's\n // keyed path, which records each key once: a replay of an event that\n // already landed (its live attempt, or an earlier replay whose\n // response was lost) answers as a duplicate and records nothing.\n // The key covers the whole payload, so every field here must match\n // the live attempt's body (see `events.track`).\n await s.http.post(P.events, {\n event: op.event,\n properties: op.properties ?? undefined,\n // Preserve the ORIGINAL event time (the events route honors a body\n // timestamp → `occurred_at`). `anonymous_id` mirrors the normal track\n // body; the server attributes engagement events to the SESSION user\n // at replay time (no body-id attribution), and the outbox replays an\n // op only while its owner is signed in. An op from an SDK version\n // that captured no event time replays its enqueue time, which stays\n // the same on every attempt.\n anonymous_id: s.identity.getAnonymousId(),\n timestamp: op.occurredAt ?? op.enqueuedAt,\n ...(op.telemetry ? { telemetry: true } : {}),\n ...(keyableProperties(op.properties)\n ? { idempotency_key: op.idempotencyKey }\n : {}),\n });\n } catch (err) {\n // A missing/expired session at replay (UNAUTHORIZED/FORBIDDEN) is\n // transient for a buffered analytics event — re-throw as NETWORK_ERROR\n // so the outbox RETAINS it (a later flush after re-auth lands it),\n // instead of the deterministic-drop the auth code would trigger.\n const code = (err as { code?: unknown })?.code;\n if (code === \"UNAUTHORIZED\" || code === \"FORBIDDEN\") {\n throw new AmbaApiError(\n \"NETWORK_ERROR\",\n \"track replay deferred: no valid session\",\n );\n }\n throw err;\n }\n return;\n }\n}\n\nfunction getOfflineQueue(s: RuntimeState): OfflineQueue {\n if (!s.offlineQueue) {\n s.offlineQueue = new OfflineQueue({\n store: offlineStore(s),\n // Scope the outbox by project so switching projects (same storage prefix)\n // can't replay a prior project's queued writes through the new project's\n // credentials. Each project's outbox persists under its own key.\n storageKey: `offline_outbox:${s.projectId}`,\n executor: (op) => replayOutboxOp(s, op),\n // Bind each queued write to the user who made it: a flush replays it\n // only while that user is signed in (signOut purges the outbox).\n // A user id counts only with a session (or refresh) token behind it.\n currentOwner: () => ({\n appUserId:\n s.identity.getSessionToken() || s.identity.getRefreshToken()\n ? s.identity.getAppUserId()\n : null,\n anonymousId: s.identity.getAnonymousId(),\n }),\n onDropped: (op, err) => {\n // A queued write the server now rejects deterministically is dropped\n // so it can't wedge the queue. Surface it rather than discard.\n if (typeof console !== \"undefined\") {\n console.warn(\n `[amba] dropped un-replayable offline ${op.kind} (${op.collection ?? op.event ?? \"?\"}):`,\n err,\n );\n }\n },\n });\n }\n return s.offlineQueue;\n}\n\n// Direct (never-queued) collection writes. The optimistic helpers use these so\n// a write either truly succeeds (resolve with the server row) or truly fails\n// (reject → rollback). Optimistic UI needs an unambiguous outcome, so it must\n// NOT silently divert to the offline outbox — a queued-pending stub is not a\n// confirmed write.\nasync function insertDirect<T>(\n s: RuntimeState,\n name: string,\n row: Record<string, unknown>,\n): Promise<T> {\n const env = await s.http.post<Envelope<T>>(\n `${P.collections}/${encodePathSegment(name)}`,\n row,\n );\n return env.data;\n}\nasync function updateDirect<T>(\n s: RuntimeState,\n name: string,\n id: string,\n set: Record<string, unknown>,\n): Promise<T> {\n const env = await s.http.patch<Envelope<T>>(\n `${P.collections}/${encodePathSegment(name)}/${encodePathSegment(id)}`,\n { set },\n );\n return env.data;\n}\nasync function deleteDirect(\n s: RuntimeState,\n name: string,\n id: string,\n): Promise<{ data: { deleted: boolean } }> {\n const result = await s.http.delete<{ data: { deleted: boolean } } | null>(\n `${P.collections}/${encodePathSegment(name)}/${encodePathSegment(id)}`,\n );\n return result ?? { data: { deleted: true } };\n}\n\n/**\n * The device OS for `X-Device-OS` from React Native's `Platform.OS`\n * (`ios`, `android`, `web` for react-native-web, `macos`, `windows`).\n * `undefined` outside React Native (Node, tests).\n */\nfunction detectDeviceOs(): string | undefined {\n try {\n // A literal lazy require is required by Metro (see configure's AppState).\n const rn = require(\"react-native\") as { Platform?: { OS?: unknown } };\n const os = rn.Platform?.OS;\n return deviceOsFromPlatform(typeof os === \"string\" ? os : undefined);\n } catch {\n return undefined;\n }\n}\n\n/**\n * The device locale's region for `X-Device-Region`: the iOS region setting\n * (`AppleLocale`) or Android's locale, falling back to the JS runtime's\n * default locale. `undefined` when none carries a two-letter region.\n */\nfunction detectDeviceRegion(): string | undefined {\n try {\n // A literal lazy require is required by Metro (see configure's AppState).\n const rn = require(\"react-native\") as {\n NativeModules?: Record<string, unknown>;\n };\n const ios = (\n rn.NativeModules?.[\"SettingsManager\"] as\n | { settings?: { AppleLocale?: unknown } }\n | undefined\n )?.settings;\n const android = rn.NativeModules?.[\"I18nManager\"] as\n | { localeIdentifier?: unknown }\n | undefined;\n const tag = ios?.AppleLocale ?? android?.localeIdentifier;\n const region = regionFromLocale(typeof tag === \"string\" ? tag : undefined);\n if (region) return region;\n } catch {\n /* Node/SSR has no native modules */\n }\n try {\n return regionFromLocale(Intl.DateTimeFormat().resolvedOptions().locale);\n } catch {\n return undefined;\n }\n}\n\n/**\n * The send path of `events.track()`: the event batch queue when batching is\n * on, otherwise the live request (with the offline outbox behind it when\n * enabled). `occurredAtOverride` is when the event happened, for one that\n * waited (see `releaseHeldEvents`).\n */\nasync function sendEvent(\n s: RuntimeState,\n event: string,\n properties: Record<string, unknown> | undefined,\n options: { telemetry?: boolean; immediate?: boolean } | undefined,\n occurredAtOverride?: string,\n): Promise<void> {\n if (s.eventQueue && !options?.immediate) {\n return s.eventQueue.enqueue(\n event,\n properties,\n options?.telemetry === true,\n occurredAtOverride,\n );\n }\n // POST /v1/client/events accepts a single event or a batch.\n // Single-shape per `packages/shared/src/types.ts:TrackEventInput`:\n // { event, properties?, timestamp?, telemetry? }\n // Pre-PR-#246 this sent `name:` instead of `event:` — the API's\n // TrackEventInput contract expects `event:`, so the field was\n // dropped, the row failed NOT-NULL, and the route returned 500\n // `TRACK_FAILED`. Web/Node SDKs route through the engine\n // which marshals the field correctly; RN's hand-rolled HTTP\n // path didn't.\n // Capture the original event TIME so a buffered event keeps its\n // `occurred_at` on replay (the events route honors a body timestamp).\n const occurredAt = occurredAtOverride ?? new Date().toISOString();\n // With the outbox on, the live body and its queued copy must carry\n // identical properties (the server's idempotency check compares\n // them), so both use one snapshot taken now.\n const props = s.offlineEnabled\n ? snapshotProperties(properties)\n : properties;\n const body: Record<string, unknown> = {\n event,\n properties: props ?? undefined,\n anonymous_id: s.identity.getAnonymousId(),\n timestamp: occurredAt,\n };\n if (options?.telemetry) {\n body[\"telemetry\"] = true;\n }\n // With the offline outbox on, the live attempt of an engagement event\n // carries the idempotency key its queued copy replays with. A request\n // that reached the server but timed out (or lost its response) before\n // the SDK heard back is then queued and replayed, and the server\n // records it once. Telemetry stays on the unkeyed hot path.\n const idempotencyKey = s.offlineEnabled ? newIdempotencyKey() : undefined;\n if (idempotencyKey && keyedLiveTrack(props, options?.telemetry)) {\n body[\"idempotency_key\"] = idempotencyKey;\n }\n // Who is writing, captured before the live attempt: a request that\n // outlives a sign-out stays attributed to this user (or is refused).\n const outboxTicket = s.offlineEnabled ? getOfflineQueue(s).ticket() : undefined;\n try {\n await s.http.post(P.events, body);\n } catch (err) {\n // With the offline outbox enabled, a transport failure buffers the\n // event for replay-on-reconnect instead of being lost. Deterministic\n // failures (validation, auth) still throw.\n if (s.offlineEnabled && isOfflineError(err)) {\n await getOfflineQueue(s).enqueue({\n kind: \"track\",\n event,\n properties: props,\n telemetry: options?.telemetry,\n occurredAt,\n idempotencyKey,\n }, outboxTicket);\n return;\n }\n throw err;\n }\n}\n\n/**\n * The local half of a sign-out on `state_`: clear the session (unless a\n * sign-in stored a newer one since `startedAt`), drop the signed-out\n * user's cached values and queued writes, and tell subscribers. False when\n * nothing was cleared.\n */\nasync function endSignedOutSession(\n state_: RuntimeState,\n rotateAnonymousId: boolean,\n startedAt: number,\n): Promise<boolean> {\n const signedOutUser = state_.identity.getAppUserId();\n const cleared = await state_.identity.clearSession(rotateAnonymousId, {\n unlessSignedInSince: startedAt,\n });\n if (!cleared) return false;\n // Events held for an identity the device couldn't read belong to the\n // session that just ended.\n dropHeldEvents(state_, \"the device signed out before its identity could be read\");\n // The signed-out user's cached config and flags go with the session (on\n // disk before signOut resolves); lookups return the signed-out values,\n // then configDefaults, until the next fetch.\n state_.remoteValues.clearForSignOut(signedOutUser);\n await state_.remoteValues.flushed();\n // Nothing the signed-out user queued offline may replay under the next\n // session, so the outbox is emptied here.\n try {\n await getOfflineQueue(state_).purge();\n } catch (err) {\n if (typeof console !== \"undefined\") {\n console.warn(\"[amba] signOut could not clear the offline outbox\", err);\n }\n }\n void state_.eventQueue?.flush();\n notifyAuthSubscribers(state_, null, \"signed_out\");\n return true;\n}\n\n/**\n * One `configure()`: build the state for `config` and install it in place\n * of the previous one (see `Amba.configure`).\n */\nasync function configureOnce(config: AmbaConfig): Promise<void> {\n if (!config.projectId || config.projectId.trim() === \"\") {\n throw new Error(\"Amba.configure: projectId is required\");\n }\n if (!config.clientKey || config.clientKey.trim() === \"\") {\n throw new Error(\"Amba.configure: clientKey is required\");\n }\n const batchSize = config.events?.batchSize ?? 100;\n const flushIntervalMs = config.events?.flushIntervalMs ?? 60_000;\n const maxQueue = config.events?.maxQueue ?? 10_000;\n if (config.events?.batching) {\n if (!Number.isInteger(batchSize) || batchSize < 1 || batchSize > 500)\n throw new Error(\"Amba.configure: events.batchSize must be 1–500\");\n if (!Number.isInteger(flushIntervalMs) || flushIntervalMs < 0)\n throw new Error(\n \"Amba.configure: events.flushIntervalMs must be a non-negative integer\",\n );\n if (!Number.isInteger(maxQueue) || maxQueue < 1)\n throw new Error(\"Amba.configure: events.maxQueue must be positive\");\n }\n const deadlines = resolveRequestDeadlines(config);\n const tokenStorage = config.tokenStorage ?? \"secure\";\n if (!TOKEN_STORAGE_MODES.includes(tokenStorage)) {\n throw new Error(\n 'Amba.configure: tokenStorage must be \"secure\", \"memory\" or \"asyncStorage\"',\n );\n }\n const secureStorage = config.secureStorage;\n if (\n secureStorage !== undefined &&\n (typeof secureStorage?.getItem !== \"function\" ||\n typeof secureStorage?.setItem !== \"function\" ||\n typeof secureStorage?.removeItem !== \"function\")\n ) {\n throw new Error(\n \"Amba.configure: secureStorage must provide getItem, setItem and removeItem\",\n );\n }\n const configDefaults = readConfigDefaults(config.configDefaults);\n // Serve the shipped defaults while configure reads storage (see\n // `_pendingConfigDefaults`).\n _pendingConfigDefaults = configDefaults;\n const storagePrefix = config.storagePrefix ?? \"amba\";\n const persistence = new AsyncStoragePersistence(storagePrefix);\n if (config.events?.batching) await persistence.assertDurable();\n await persistence.hydrate();\n const vault = await resolveTokenVault(\n tokenStorage,\n storagePrefix,\n secureStorage,\n );\n // Detach the state an earlier configure() built before the stored\n // identity is read: from here on its in-flight calls store nothing and\n // tell no subscriber, and the writes it already started land first. A\n // refresh it has in flight is handed on (`inheritedRefresh`), so its\n // refresh token is never presented twice.\n const prior = state;\n let inheritedRefresh: RefreshExchange | null = null;\n const detached = prior !== null && !prior.disposed;\n if (prior && detached) {\n prior.disposed = true;\n inheritedRefresh = prior.refreshExchange ?? prior.inheritedRefresh ?? null;\n await prior.identity.dispose();\n }\n const baseUrl = (config.apiUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n let identity: IdentityStore;\n let remoteValues: RemoteValuesCache;\n try {\n identity = await IdentityStore.create(persistence, vault);\n // The config and flags cached on the last launch, per project and user.\n const store = identity;\n remoteValues = new RemoteValuesCache(\n persistence,\n recordKey(baseUrl, config.clientKey),\n configDefaults ?? {},\n () => store.getAppUserId(),\n );\n await remoteValues.load();\n } catch (err) {\n // This configuration can't start: the earlier one stays in use.\n if (prior && detached && state === prior) {\n prior.disposed = false;\n prior.identity.reattach();\n // A renewal it had in flight may have been answered meanwhile; its\n // pair is stored now, so the spent refresh token is never sent again.\n prior.inheritedRefresh = inheritedRefresh;\n if (\n inheritedRefresh &&\n prior.identity.getRefreshToken() === inheritedRefresh.token\n ) {\n void restoreStoredSession(prior).catch(() => {});\n }\n }\n throw err;\n }\n const wrapperVersion = `amba-react-native/${SDK_VERSION}`;\n const sdkVersionHeader = `amba-sdk/${SDK_VERSION} ${wrapperVersion}`;\n const http = new AmbaHttpClient({\n apiKey: config.clientKey,\n baseUrl,\n sdkPlatform: \"react-native\",\n sdkVersionHeader,\n appVersion: normalizeAppVersion(config.appVersion),\n deviceRegion: detectDeviceRegion(),\n deviceOs: detectDeviceOs(),\n identity,\n // Every request runs under a deadline, so a fetch the network never\n // answers fails with NETWORK_ERROR and releases the outbox flush, the\n // lazy sign-in and the event batch flush that wait on it.\n requestTimeoutMs: deadlines.requestMs,\n longRequestTimeoutMs: deadlines.longMs,\n // Lazily sign in anonymously on the first authenticated call so reads\n // don't silently return no-data when the app never called\n // `signInAnonymously()`. Opt out per-config via `autoAnonymousSession:false`.\n // While the user must sign in again, such a call fails with\n // `SIGN_IN_REQUIRED` either way.\n ensureSession: async (path: string) => {\n if (pathSkipsLazySession(path)) return;\n if (state?.identity.isSignInRequired()) throw signInRequiredError();\n if (config.autoAnonymousSession === false) return;\n await ensureAnonymousSession(getState());\n },\n // A call the server refuses for its session token (a link on another\n // device signed this one out) continues the session with the refresh\n // token, or ends it so the app can ask the user to sign in again.\n onSessionRefused: (usedToken: string, usedUserId: string | null) =>\n recoverRefusedSession(getState(), usedToken, usedUserId),\n });\n // Dispose any prior offline queue (and AWAIT its in-flight flush) BEFORE\n // installing the new state, so the orphaned queue can never write the\n // outbox concurrently with the replacement. Bump the activation token so a\n // stale disable callback from the previous config no-ops.\n _offlineEnableToken += 1;\n if (prior?.offlineQueue) await prior.offlineQueue.dispose();\n prior?.detachEventAppState?.();\n prior?.detachIdentityAppState?.();\n if (prior?.eventQueue) await prior.eventQueue.dispose();\n experimentReporter.reset();\n const nextState: RuntimeState = {\n http,\n identity,\n debug: config.debug ?? false,\n projectId: config.projectId,\n clientKey: config.clientKey,\n baseUrl,\n functionsBaseUrl: config.functionsBaseUrl ?? null,\n resolvedFunctionsProjectId: null,\n functionsProjectIdInflight: null,\n persistence,\n remoteValues,\n refreshedFor: { config: null, flags: null },\n offlineEnabled: false,\n offlineQueue: null,\n eventQueue: null,\n inheritedRefresh,\n };\n // Events the replaced configuration held for a locked store move along\n // when it stores the same project's identity.\n if (prior?.heldEvents?.length || prior?.heldEventsDropped) {\n if (sameIdentityTarget(prior, nextState)) {\n // Generations count per store: carried events belong to the identity\n // this store loaded.\n const launchedAt = identity.getGeneration();\n nextState.heldEvents = (prior.heldEvents ?? []).map((held) => ({\n ...held,\n generation: launchedAt,\n }));\n nextState.heldEventsDropped = prior.heldEventsDropped;\n prior.heldEvents = [];\n prior.heldEventsDropped = 0;\n } else {\n dropHeldEvents(prior, \"the SDK was configured for another project\");\n }\n }\n state = nextState;\n // Held events go out once the stored identity is known.\n identity.onAnonymousIdKnown(() => {\n void releaseHeldEvents(nextState);\n });\n // A stored session that couldn't be read at launch (a locked device)\n // loads later. Subscribers hear it once it does; one whose session token\n // expired meanwhile is renewed first, as at launch.\n identity.onStoredSessionLoaded(() => {\n const userId = identity.getAppUserId();\n if (identity.getSessionToken() && userId) {\n notifyAuthSubscribers(\n nextState,\n snapshotSession({ id: userId, auth_providers: [], properties: {} }),\n \"session_restored\",\n );\n } else if (\n config.autoAnonymousSession !== false &&\n identity.getRefreshToken()\n ) {\n void restoreStoredSession(nextState).catch(() => {});\n }\n });\n // A store locked at launch is read again when the app comes to the\n // foreground, so `isAuthenticated`, `appUserId` and subscribers catch up\n // without waiting for a call.\n try {\n // A literal lazy require is required by Metro (see the event batching\n // AppState below).\n const { AppState } = require(\"react-native\") as typeof import(\"react-native\");\n const sub = AppState.addEventListener(\"change\", (status) => {\n if (status === \"active\" && identity.credentialsUnknown()) {\n void identity.credentialsStillUnreadable();\n }\n });\n nextState.detachIdentityAppState = () => sub.remove();\n } catch {\n /* Node/SSR has no native AppState */\n }\n if (config.events?.batching) {\n const queue = new EventBatchQueue(\n offlineStore(nextState),\n `event_outbox:${nextState.projectId}`,\n { batchSize, flushIntervalMs, maxQueue, onDrop: config.events.onDrop },\n () => ({\n userId: nextState.identity.getAppUserId(),\n anonymousId: nextState.identity.getAnonymousId(),\n sessionToken: nextState.identity.getSessionToken(),\n }),\n (body, token) =>\n nextState.http.postWithSession(\n \"/v1/client/events/batch\",\n body,\n token,\n ),\n (body, token) => nextState.http.postWithSession(P.events, body, token),\n );\n nextState.eventQueue = queue;\n await queue.start();\n // React Native may suspend JS immediately after the callback. Persist\n // the coalescing buffer first, then make a best-effort network attempt.\n try {\n // A literal lazy require is required by Metro. Dynamic import would\n // enumerate React Native's full export barrel in Expo Go.\n const { AppState } =\n require(\"react-native\") as typeof import(\"react-native\");\n const sub = AppState.addEventListener(\"change\", (status) => {\n if (status === \"background\") void queue.flush();\n });\n nextState.detachEventAppState = () => sub.remove();\n } catch {\n /* Node/SSR has no native AppState */\n }\n }\n if (\n !nextState.functionsBaseUrl &&\n !PROJECT_ID_PATTERN.test(nextState.projectId)\n ) {\n void resolveFunctionsProjectId(nextState).catch(() => {});\n }\n // Events carried over from the replaced configuration go out now when the\n // stored identity is already known (after the event queue started).\n if (nextState.heldEvents?.length && !identity.hasStandInAnonymousId()) {\n void releaseHeldEvents(nextState);\n }\n // A renewal the replaced configuration had in flight is finished now, so\n // its new pair is stored even when no call follows. Otherwise the SDK\n // manages the session unless the app opted out: a stored session whose\n // session token expired is restored in the background. A network failure\n // leaves it for the next authenticated call to retry.\n if (\n (inheritedRefresh !== null &&\n identity.getRefreshToken() === inheritedRefresh.token) ||\n (config.autoAnonymousSession !== false &&\n !identity.getSessionToken() &&\n identity.getRefreshToken())\n ) {\n void restoreStoredSession(nextState).catch(() => {});\n }\n}\n\n/**\n * Top-level SDK namespace for React Native.\n */\nexport const Amba = {\n configure: (config: AmbaConfig): Promise<void> => {\n // Runs at once when no configure() is pending, so `configDefaults` serve\n // the first frame while it reads storage; otherwise after the pending one.\n const run = configuring\n ? configureChain.then(() => configureOnce(config))\n : configureOnce(config);\n configureChain = run.catch(() => undefined);\n configuring = run;\n const settle = () => {\n if (configuring === run) configuring = null;\n };\n run.then(settle, settle);\n return run;\n },\n\n /**\n * Test-only hook to reset the singleton between tests. Not part of\n * the customer-facing surface; the production SDK uses\n * `Amba.auth.signOut(true)` to fully reset identity.\n * @internal\n */\n _reset: (): void => {\n if (state) {\n state.disposed = true;\n void state.identity.dispose();\n }\n state?.detachEventAppState?.();\n state?.detachIdentityAppState?.();\n void state?.eventQueue?.dispose();\n state = null;\n authSubscribers.clear();\n },\n\n get anonymousId(): string {\n return getState().identity.getAnonymousId();\n },\n get appUserId(): string | undefined {\n return getState().identity.getAppUserId() ?? undefined;\n },\n get isAuthenticated(): boolean {\n return getState().identity.isAuthenticated();\n },\n get projectId(): string {\n return getState().projectId;\n },\n setDebug: (enabled: boolean): void => {\n getState().debug = enabled;\n },\n\n /**\n * Live collection subscriptions. Returns a builder — chain `.where(...)`\n * to narrow, then `.subscribe(cb)` to start streaming row changes:\n *\n * const unsub = Amba.collection(\"messages\")\n * .where(\"circle_id\", circleId)\n * .subscribe((change) => {\n * // change.op: \"INSERT\" | \"UPDATE\" | \"DELETE\"; change.row: the row\n * });\n * unsub(); // stop listening\n *\n * Delivery mirrors `Amba.collections.find()` — you receive only rows your\n * session owns (plus shared/public rows), optionally narrowed by\n * `.where(...)`. Reconnects automatically with backoff if the connection\n * drops. Requires an authenticated session.\n */\n collection: <T = unknown>(name: string): CollectionSubscription<T> =>\n new CollectionSubscription<T>(\n {\n baseUrl: getState().baseUrl,\n authHeaders: realtimeAuthHeaders,\n connectTimeoutMs: getState().http.requestTimeoutMs,\n },\n name,\n ),\n\n /**\n * Live gamification updates for the signed-in user. Call `.subscribe(cb)`\n * to receive xp / level / achievement / streak changes live:\n *\n * const unsub = Amba.gamification.subscribe((change) => {\n * // change.kind: \"xp\" | \"level\" | \"achievement\" | \"streak\"\n * // change.data: the new state\n * });\n * unsub(); // stop listening\n *\n * You only ever receive your own gamification changes — never another\n * user's. A getter so `baseUrl` is read at access time. Requires a session.\n */\n get gamification(): GamificationSubscription {\n return new GamificationSubscription({\n baseUrl: getState().baseUrl,\n authHeaders: realtimeAuthHeaders,\n connectTimeoutMs: getState().http.requestTimeoutMs,\n });\n },\n\n events: {\n /**\n * Track an event.\n *\n * Default routes to the engagement meter (fan-out to segments,\n * workflows, push). Pass `{ telemetry: true }` for high-volume,\n * fire-and-forget events that DON'T need fan-out: UI button\n * clicks, scroll-depth markers, game-loop telemetry. Telemetry\n * events are billed at $0.10 per 1M overage vs $0.50 per 10K for\n * engagement events.\n *\n * @example\n * await Amba.events.track('purchase_completed', { amount: 9.99 });\n * await Amba.events.track('scroll_depth', { px: 1024 }, { telemetry: true });\n */\n track: async (\n event: string,\n properties?: Record<string, unknown>,\n options?: { telemetry?: boolean; immediate?: boolean },\n ): Promise<void> => {\n // While the stored identity can't be read (a device locked since\n // launch), the anonymous id is a stand-in and an event sent under it\n // would belong to nobody. The event waits in memory and goes out, in\n // order, once the identity loads (`releaseHeldEvents`); the call\n // resolves now, as a queued event's does.\n let s = getState();\n for (let attempt = 0; ; attempt += 1) {\n const hold =\n (s.heldEvents?.length ?? 0) > 0 ||\n (await s.identity.anonymousIdStillUnknown());\n // A `configure()` replaced the state meanwhile: the event goes to the\n // state it installs.\n if (s.disposed && attempt === 0) {\n s = await liveState();\n continue;\n }\n if (!hold) return sendEvent(s, event, properties, options);\n if (typeof event !== \"string\" || event.trim() === \"\") {\n throw new AmbaApiError(\"VALIDATION_ERROR\", \"event name must be non-empty\");\n }\n holdEvent(s, event, properties, options);\n if (!s.identity.hasStandInAnonymousId()) void releaseHeldEvents(s);\n return;\n }\n },\n flush: async (): Promise<EventFlushResult> =>\n getState().eventQueue?.flush() ?? { sent: 0, rejected: 0, remaining: 0 },\n pendingCount: async (userId?: string): Promise<number> =>\n getState().eventQueue?.pendingCount(userId) ?? 0,\n clear: async ({ userId }: { userId: string }): Promise<void> => {\n if (!userId) throw new Error(\"Amba.events.clear: userId is required\");\n await getState().eventQueue?.clear(userId);\n },\n setOnline: (online: boolean): void => {\n getState().eventQueue?.setOnline(online);\n },\n setBatchingEnabled: (enabled: boolean): void => {\n getState().eventQueue?.setBatchingEnabled(enabled);\n },\n },\n\n auth: {\n /**\n * Sign in as the device's anonymous user. Sends the SDK's anonymous_id\n * so the server either resumes the anonymous user that holds it (no MAU\n * charge) or creates one (DX-2). When that id belongs to a user who\n * signs in with a linked identity, and this device still holds that\n * user's refresh token, the call continues that user's session; with no\n * usable refresh token the device takes a new anonymous id and becomes a\n * new anonymous user. While the user must sign in again\n * (`sign_in_required`), this is how the app starts a new anonymous user\n * instead.\n */\n signInAnonymously: async (): Promise<AuthResult> => {\n const s = await liveState();\n // A secure store that opened since launch loads the session it holds\n // first, and a renewal that load starts finishes before this sign-in\n // is sent. The anonymous id of a stored session that still can't be\n // read is unknown; signing in now would start a new user in its place.\n await loadUnreadSession(s);\n if (await s.identity.anonymousIdStillUnknown()) {\n throw secureStoreUnavailableError();\n }\n const startedAt = s.identity.getGeneration();\n try {\n return await postAuth(s, P.authAnonymous, {\n anonymous_id: s.identity.getAnonymousId(),\n });\n } catch (err) {\n if (\n !(err instanceof AmbaApiError) ||\n err.code !== IDENTITY_SIGN_IN_REQUIRED\n )\n throw err;\n const held = await continueHeldSession(s);\n if (held) return held;\n // A new anonymous user, unless a sign-in stored a newer session\n // while this call was out.\n if (!(await s.identity.clearSession(true, { unlessSignedInSince: startedAt }))) {\n throw sessionChangedError(\n \"a sign-in stored a newer session while this call was in flight\",\n );\n }\n return postAuth(s, P.authAnonymous, {\n anonymous_id: s.identity.getAnonymousId(),\n });\n }\n },\n signInWithEmail: async (\n email: string,\n password: string,\n ): Promise<AuthResult> => {\n requireEmail(email);\n if (password === \"\")\n throw new AmbaApiError(\n \"VALIDATION_ERROR\",\n \"password must not be empty\",\n );\n return postAuth(await liveState(), P.authEmailLogin, { email, password });\n },\n signUpWithEmail: async (\n email: string,\n password: string,\n ): Promise<AuthResult> => {\n requireEmail(email);\n requirePassword(password);\n return postAuth(await liveState(), P.authEmailSignup, { email, password });\n },\n /**\n * Sign in with an Apple ID identity token (from\n * `ASAuthorization.identityToken` on iOS / `expo-apple-authentication`).\n * Replaces the 3.x `signInWithSocial('apple', token)` polymorphic form.\n *\n * Pass `options.nonce` when the sign-in request carried a nonce: the raw\n * value you generated for this attempt, whose SHA-256 hex digest you gave\n * Apple. Amba checks that the token's `nonce` claim is that digest, so a\n * token issued to another sign-in can't be used here.\n */\n signInWithApple: async (\n identityToken: string,\n options?: SocialSignInOptions,\n ): Promise<AuthResult> => {\n if (identityToken.trim() === \"\")\n throw new AmbaApiError(\n \"VALIDATION_ERROR\",\n \"identity_token must not be empty\",\n );\n return postAuth(await liveState(), P.authSocial, {\n provider: \"apple\",\n id_token: identityToken,\n ...nonceField(options),\n });\n },\n /**\n * Sign in with a Google ID token (from Google Sign-In SDK / Expo\n * `expo-auth-session/providers/google`). Replaces the 3.x\n * `signInWithSocial('google', token)` polymorphic form.\n *\n * Pass `options.nonce` when the sign-in request carried a nonce (see\n * `signInWithApple`).\n */\n signInWithGoogle: async (\n idToken: string,\n options?: SocialSignInOptions,\n ): Promise<AuthResult> => {\n if (idToken.trim() === \"\")\n throw new AmbaApiError(\n \"VALIDATION_ERROR\",\n \"id_token must not be empty\",\n );\n return postAuth(await liveState(), P.authSocial, {\n provider: \"google\",\n id_token: idToken,\n ...nonceField(options),\n });\n },\n /**\n * Request a one-time passcode for `email`. Server emails a 6-digit\n * code; the user types it in, then call\n * `verifyEmailOtp(email, code)` to exchange for a session.\n */\n requestEmailOtp: async (email: string): Promise<void> => {\n requireEmail(email);\n await getState().http.post(P.authEmailOtpRequest, { email });\n },\n /**\n * Exchange `(email, code)` for a session. Returns the same shape\n * as `signInWithEmail`.\n */\n verifyEmailOtp: async (\n email: string,\n code: string,\n ): Promise<AuthResult> => {\n requireEmail(email);\n requireOtpCode(code);\n return postAuth(await liveState(), P.authEmailOtpVerify, { email, code });\n },\n /**\n * Request a one-time passcode by SMS. `phone` must be E.164\n * (starts with +, 8–15 digits). Server sends a 6-digit code;\n * user types it, then call `verifySmsOtp(phone, code)` to\n * exchange for a session.\n */\n requestSmsOtp: async (phone: string): Promise<void> => {\n requireE164Phone(phone);\n await getState().http.post(P.authSmsOtpRequest, { phone });\n },\n /**\n * Exchange `(phone, code)` for a session. Same shape as\n * `signInWithEmail`.\n */\n verifySmsOtp: async (phone: string, code: string): Promise<AuthResult> => {\n requireE164Phone(phone);\n requireOtpCode(code);\n return postAuth(await liveState(), P.authSmsOtpVerify, { phone, code });\n },\n /**\n * Request a one-time magic link for `email`. The server emails a\n * deep-link URL containing a single-use token; the app's deep-link\n * handler extracts the token and calls `verifyMagicLink(token)` to\n * exchange for a session. NEW in 4.0.\n */\n requestMagicLink: async (email: string): Promise<void> => {\n requireEmail(email);\n await getState().http.post(P.authMagicLinkRequest, { email });\n },\n /**\n * Exchange a magic-link `token` for a session. Same shape as\n * `signInWithEmail`. NEW in 4.0.\n */\n verifyMagicLink: async (token: string): Promise<AuthResult> => {\n if (token.trim() === \"\")\n throw new AmbaApiError(\"VALIDATION_ERROR\", \"token must not be empty\");\n return postAuth(await liveState(), P.authMagicLinkVerify, { token });\n },\n /**\n * Link a social identity provider to the current (anonymous or\n * identified) user. Upgrades an anonymous session to an identified\n * one without minting a new app_user. NEW in 4.0.\n *\n * Mirrors `AmbaCore::link_account` in the engine — the SDK reads\n * the active session token from local identity state and posts\n * `{ provider, token, session_token }` to `/v1/client/auth/link`,\n * which is the wire shape the server expects. Callers must already\n * have a session (any flow) before linking; an `UNAUTHORIZED` error\n * is thrown if none is present.\n *\n * Email linking via this endpoint is rejected server-side — use\n * `signInWithEmail` / `signUpWithEmail` instead.\n *\n * @param provider — `\"apple\" | \"google\"`\n * @param credential — provider identity token (Apple `identityToken`,\n * Google `id_token`).\n * @param options — `nonce`: the raw nonce of this sign-in attempt, when\n * the provider request carried its SHA-256 hex digest.\n */\n linkAccount: async (\n provider: string,\n credential: string,\n options?: SocialSignInOptions,\n ): Promise<AuthResult> => {\n const providerTrimmed = provider?.trim() ?? \"\";\n if (providerTrimmed === \"\")\n throw new AmbaApiError(\n \"VALIDATION_ERROR\",\n \"provider must not be empty\",\n );\n if (typeof credential !== \"string\" || credential.trim() === \"\")\n throw new AmbaApiError(\n \"VALIDATION_ERROR\",\n \"credential must not be empty\",\n );\n const s = await liveState();\n return withBodySession(s, (sessionToken) =>\n trackLink(\n s,\n postAuth(\n s,\n P.authLink,\n {\n provider: providerTrimmed,\n token: credential,\n session_token: sessionToken,\n ...nonceField(options),\n },\n // The link acts for the session it was sent with: a sign-in or\n // sign-out that lands meanwhile wins.\n { ifGeneration: s.identity.getGeneration(), advance: false },\n ),\n ),\n );\n },\n\n /**\n * Attach a verified phone to the current session's user. Same\n * `user_id`; the SDK swaps in the fresh session pair the link returns,\n * and every other session of this user is signed out. Server returns\n * `PHONE_ALREADY_LINKED` (409) if the phone is owned by another user —\n * the SDK rethrows as `AmbaApiError`. The error names no account.\n */\n linkSmsOtp: async (phone: string, code: string): Promise<User> => {\n requireE164Phone(phone);\n requireOtpCode(code);\n return postLink(await liveState(), {\n provider: \"sms_otp\",\n phone,\n code,\n });\n },\n\n /** Symmetric counterpart to `linkSmsOtp` for email. */\n linkEmailOtp: async (email: string, code: string): Promise<User> => {\n requireEmail(email);\n requireOtpCode(code);\n return postLink(await liveState(), {\n provider: \"email_otp\",\n email,\n code,\n });\n },\n\n /**\n * Upgrade an anonymous (or any current) session by attaching an\n * email + password credential — converting the current user in place\n * to an email-identified account so its data carries over (same\n * `user_id`; the SDK swaps in the fresh session pair the link returns,\n * and every other session of this user is signed out). Server returns\n * `EMAIL_ALREADY_LINKED` (409) if the email is owned by another user —\n * rethrown as `AmbaApiError`. The error names no account.\n * After linking, the user can sign in via `signInWithEmail`.\n */\n linkEmailPassword: async (\n email: string,\n password: string,\n ): Promise<User> => {\n requireEmail(email);\n requirePassword(password);\n return postLink(await liveState(), {\n provider: \"email_password\",\n email,\n password,\n });\n },\n\n /**\n * Detach the SMS-OTP identifier from the current session's user.\n * Refused with `LAST_IDENTIFIER` if it would orphan the account.\n */\n unlinkSmsOtp: async (): Promise<User> => unlinkProvider(\"sms_otp\"),\n unlinkEmailOtp: async (): Promise<User> => unlinkProvider(\"email_otp\"),\n unlinkApple: async (): Promise<User> => unlinkProvider(\"apple\"),\n unlinkGoogle: async (): Promise<User> => unlinkProvider(\"google\"),\n\n /**\n * Snapshot of identifier providers attached to the current user.\n * Values come back **masked** (phone last-4, `a***@domain`).\n */\n getCurrentIdentifiers: async (): Promise<LinkedIdentifier[]> => {\n const s = getState();\n await settleSessionInFlight(s);\n requireSession(s);\n const env = await s.http.get<IdentifiersEnvelope>(P.authIdentifiers);\n return env.data.identifiers;\n },\n\n /**\n * Sign out: revoke the refresh token server-side (best effort) and\n * clear the local session. The anonymous id is rotated whenever the\n * session being ended was not purely anonymous, so the next person on\n * the device starts as a new anonymous user. `rotateAnonymousId: true`\n * rotates it after an anonymous session too. A pending\n * `sign_in_required` ends here as well: with `autoAnonymousSession` on,\n * the next call that needs a session starts a new anonymous user.\n *\n * Signing out ends the session the call started with. When a sign-in\n * stores a newer session while the server call is out, that session\n * stays.\n */\n signOut: async (rotateAnonymousId = false): Promise<void> => {\n const s = await liveState();\n // A stored session this launch couldn't read yet (a locked device) is\n // loaded first when it can be, so it is revoked and ended like any\n // other. One that still can't be read ends unseen (`clearSession`).\n await loadUnreadSession(s);\n const startedAt = s.identity.getGeneration();\n const revoked = s.identity.getRefreshToken();\n if (s.identity.getSessionToken()) {\n try {\n await s.http.post(\n P.authLogout,\n revoked ? { refresh_token: revoked } : {},\n );\n } catch (err) {\n // Best-effort: a server-side rejection should not trap the\n // user offline. Mirrors the engine's behaviour.\n if (typeof console !== \"undefined\") {\n console.warn(\"[amba] signOut server call failed\", err);\n }\n }\n }\n if (await endSignedOutSession(s, rotateAnonymousId, startedAt)) return;\n // A `configure()` replaced the state while the server call was out.\n // The new state ends the same session: same project, created holding\n // the refresh token this call revoked, renewed or ended since but\n // never replaced by a sign-in. A newer sign-in or another project's\n // session stays.\n if (!s.disposed || revoked === null) return;\n const next = await liveState();\n if (\n next !== s &&\n !next.disposed &&\n sameIdentityTarget(next, s) &&\n next.identity.continuesLaunchSession(revoked)\n ) {\n await endSignedOutSession(\n next,\n rotateAnonymousId,\n next.identity.getGeneration(),\n );\n }\n },\n refresh: async (): Promise<AuthResult> => refreshSession(await liveState()),\n me: async (): Promise<User> => {\n const s = getState();\n await settleSessionInFlight(s);\n requireSession(s);\n const env = await s.http.get<Envelope<User>>(P.authMe);\n return normalizeUserAuthProviders(env.data);\n },\n\n getSession: async (): Promise<Session | null> => {\n const s = getState();\n await settleSessionInFlight(s);\n if (!s.identity.isAuthenticated()) return null;\n try {\n const env = await s.http.get<Envelope<User>>(P.authMe);\n return snapshotSession(normalizeUserAuthProviders(env.data));\n } catch (err) {\n // The server refused the session and its refresh token was\n // rejected too: the session has ended (subscribers heard `null`).\n if (isSessionRefusedError(err) && !s.identity.isAuthenticated()) {\n return null;\n }\n throw err;\n }\n },\n\n /**\n * The device's anonymous id. While the stored identity can't be read\n * (a device locked since launch), it is unknown and the call fails with\n * `SECURE_STORE_UNAVAILABLE`; `Amba.anonymousId` meanwhile holds a\n * stand-in that is replaced once the store opens.\n */\n getAnonymousId: async (): Promise<string> => {\n const s = getState();\n if (await s.identity.anonymousIdStillUnknown()) {\n throw secureStoreUnavailableError();\n }\n return s.identity.getAnonymousId();\n },\n\n /**\n * Subscribe to auth changes. `event.reason` says why the callback fired\n * (see `AuthStateChangeReason`); `sign_in_required` means a session with\n * a linked identity ended and the app should show its sign-in screen.\n */\n onAuthStateChange: (\n cb: (session: Session | null, event: AuthStateChangeEvent) => void,\n ): (() => void) => {\n authSubscribers.add(cb);\n return () => {\n authSubscribers.delete(cb);\n };\n },\n },\n\n /**\n * Profile read/update for the current user. NEW in 4.0.\n *\n * `get()` — fetches the current user's full record. Short-circuits to\n * `/auth/me` so the server doesn't have to look up the caller twice.\n *\n * Both `users.get(userId)` and `users.update(userId, patch)` are\n * SDK 4.0 surfaces — `userId` omitted = current user, `userId` set\n * = the other user (server route `/v1/client/users/{id}`). The\n * server returns a public-safe profile shape for non-self reads;\n * cross-user writes require the caller to hold `users:write` in\n * their role permissions (403 otherwise).\n */\n users: {\n // <generated:users>\n get: async (userId?: string): Promise<User> => {\n const s = getState();\n await settleSessionInFlight(s);\n requireSession(s);\n const path =\n userId === undefined || userId === null || userId === \"\"\n ? P.usersMe\n : `${P.users}/${encodePathSegment(userId)}`;\n const env = await s.http.get<Envelope<User>>(path);\n return normalizeUserAuthProviders(env.data);\n },\n /**\n * Update a profile. Two overloads:\n * - `update(patch)` — update the current user (PATCH /users/me).\n * - `update(userId, patch)` — admin-style cross-user update\n * (PATCH /users/{id}); requires `users:write` role permission\n * server-side, else 403.\n */\n update: (async (\n arg1: string | UserPatch,\n arg2?: UserPatch,\n ): Promise<User> => {\n const s = getState();\n await settleSessionInFlight(s);\n requireSession(s);\n const isById = typeof arg1 === \"string\";\n const patch = isById ? (arg2 ?? {}) : (arg1 as UserPatch);\n const path = isById\n ? `${P.users}/${encodePathSegment(arg1)}`\n : `${P.users}/me`;\n const env = await s.http.patch<Envelope<User>>(path, patch);\n return normalizeUserAuthProviders(env.data);\n }) as {\n (patch: UserPatch): Promise<User>;\n (userId: string, patch: UserPatch): Promise<User>;\n },\n // </generated:users>\n },\n\n /**\n * Server-tracked customer-defined app sessions for the current user.\n * NEW in 4.0. These are the analytics `app_sessions` rows\n * (open/background events), not auth device sessions.\n *\n * - `list()` → `GET /v1/client/sessions` returns `AppSession[]` newest\n * first (capped at 100 by the server).\n * - `revoke(sessionId)` → `POST /v1/client/sessions/:id/end` ends the\n * given session row (idempotent — a second `revoke` on an already-\n * ended session is a no-op). `sessionId` is the row id (UUID)\n * returned by `list()` (the `id` field, not `session_id`). Server\n * has no DELETE on `/sessions/:id` — `end` is the canonical revoke.\n */\n sessions: {\n // <generated:sessions>\n list: async (): Promise<SessionRecord[]> => {\n const env = await getState().http.get<Envelope<SessionRecord[]>>(\n P.sessions,\n );\n return env.data;\n },\n revoke: async (sessionId: string): Promise<void> => {\n await getState().http.post(\n `${P.sessions}/${encodePathSegment(sessionId)}/end`,\n {},\n );\n },\n // </generated:sessions>\n },\n\n /**\n * Offline change replay. NEW in 4.0. Wraps `/v1/client/sync` per\n * `apps/api/src/routes/client/sync.ts` and the SDK core's\n * `SyncModule`.\n *\n * - `pushChanges(changes)` → `POST /v1/client/sync` with `{ changes }`.\n * Server returns `{ applied, conflicts, checkpoint_token }`.\n * Conflicts are server-wins; callers should re-apply\n * `server_data` from each `SyncConflict` to their local cache\n * before issuing follow-up writes.\n * - `pullChanges({ entity_type, checkpoint_token? })` →\n * `GET /v1/client/sync?entity_type=…&checkpoint_token=…`. Server\n * requires `entity_type`; `checkpoint_token` is optional on the\n * first pull. Returns `{ changes, checkpoint_token, has_more }`.\n *\n * NOTE: The wire is a single `/sync` endpoint with method = action.\n * Earlier 4.0 drafts referenced `/sync/push` and `/sync/pull` — those\n * routes do not exist on the server.\n */\n sync: {\n pushChanges: async (changes: SyncChange[]): Promise<SyncPushResult> => {\n const env = await getState().http.post<Envelope<SyncPushResult>>(P.sync, {\n changes,\n });\n return env.data;\n },\n pullChanges: async (since: {\n entity_type: string;\n checkpoint_token?: string;\n }): Promise<SyncPullResult> => {\n if (!since.entity_type || since.entity_type.trim() === \"\") {\n throw new AmbaApiError(\n \"VALIDATION_ERROR\",\n \"pullChanges requires a non-empty entity_type\",\n );\n }\n const query: Record<string, string> = { entity_type: since.entity_type };\n if (since.checkpoint_token !== undefined) {\n query[\"checkpoint_token\"] = since.checkpoint_token;\n }\n const env = await getState().http.get<Envelope<SyncPullResult>>(\n P.sync,\n query,\n );\n return env.data;\n },\n },\n\n /**\n * Automatic offline mutation queue (opt-in).\n *\n * Once enabled, a collection write (`insert` / `update` / `delete`) or a\n * `track` that fails because the device is offline is buffered to a durable\n * on-device outbox (AsyncStorage) instead of throwing — then replayed in\n * order on the next {@link flush} (e.g. when your NetInfo listener detects\n * reconnect). Replays reuse a per-op idempotency key so a write that reached\n * the server before the connection dropped is never applied twice.\n *\n * This is distinct from `Amba.sync` (manual change replay): `offline` is the\n * automatic, fire-and-forget safety net for ordinary writes.\n *\n * @example\n * import NetInfo from \"@react-native-community/netinfo\";\n * Amba.offline.enable();\n * NetInfo.addEventListener((s) => { if (s.isConnected) Amba.offline.flush(); });\n * await Amba.collections.insert(\"todos\", { title: \"Buy milk\" }); // queues if offline\n */\n offline: {\n /**\n * Turn the outbox on. Idempotent. Returns a disable function (queued ops\n * are NOT dropped — call `clear()` for that). Drive `flush()` from your own\n * connectivity listener (e.g. NetInfo) since React Native has no global\n * `online` event.\n */\n enable: (): (() => void) => {\n const s = getState();\n s.offlineEnabled = true;\n // Bump + capture the activation token so this call's disabler only acts\n // while it's still the current activation (a later enable() or a\n // configure() supersedes it).\n _offlineEnableToken += 1;\n const token = _offlineEnableToken;\n return () => {\n // No-op if a later enable() or a configure() superseded this activation\n // (token moved on) or replaced the runtime state. Otherwise a stale\n // disabler would wrongly flip the active/fresh outbox off.\n if (_offlineEnableToken !== token || state !== s) return;\n s.offlineEnabled = false;\n };\n },\n /** Whether the outbox is currently enabled. */\n isEnabled: (): boolean => getState().offlineEnabled,\n /** Count of writes awaiting replay. */\n pendingCount: async (): Promise<number> =>\n getOfflineQueue(getState()).pendingCount(),\n /** Snapshot the pending ops (a copy — safe to inspect). */\n pending: async (): Promise<OutboxOp[]> =>\n getOfflineQueue(getState()).pending(),\n /** Force a replay attempt now. Resolves with `{ flushed, dropped, remaining }` (dropped = poisoned ops the server rejects on replay, removed so they cannot wedge the queue). */\n flush: async (): Promise<FlushResult> => {\n const s = getState();\n const result = s.offlineEnabled\n ? await getOfflineQueue(s).flush()\n : {\n flushed: 0,\n dropped: 0,\n remaining: s.offlineQueue ? await s.offlineQueue.pendingCount() : 0,\n };\n await s.eventQueue?.flush();\n return result;\n },\n /** Drop every queued write without replaying. */\n clear: async (): Promise<void> => getOfflineQueue(getState()).clear(),\n },\n\n collections: {\n // <generated:collections.find>\n find: async <T = unknown>(\n name: string,\n options: FindOptions = {},\n ): Promise<FindResponse<T>> => {\n const s = getState();\n // Server returns `{ data, next_cursor, has_more }` for find —\n // the FindResponse shape matches the envelope directly.\n return s.http.post<FindResponse<T>>(\n `${P.collections}/${encodePathSegment(name)}/find`,\n options,\n );\n },\n // </generated:collections.find>\n findOne: async <T = unknown>(name: string, id: string): Promise<T> => {\n const s = getState();\n const env = await s.http.get<Envelope<T>>(\n `${P.collections}/${encodePathSegment(name)}/${encodePathSegment(id)}`,\n );\n return env.data;\n },\n /**\n * Vector similarity search. Returns the top `k` rows closest to the\n * supplied `vector` by cosine distance (default), optionally\n * further filtered by `where`. NEW in 4.0 — wires to the\n * pgvector-backed `POST /v1/client/collections/:name/find-nearest`\n * route.\n *\n * Wire body matches `NearestRequestBody` in\n * `apps/api/src/routes/client/collections.ts`: `to_vector` (or\n * `to_text` for server-side embedding) + optional `k`, `column`,\n * `distance`, `where`, `includeDeleted`.\n */\n findNearest: async <T = unknown>(\n name: string,\n vector: number[],\n k: number,\n where?: Filter,\n ): Promise<T[]> => {\n const s = getState();\n const body: Record<string, unknown> = { to_vector: vector, k };\n if (where !== undefined) body.where = where;\n const env = await s.http.post<FindResponse<T>>(\n `${P.collections}/${encodePathSegment(name)}/find-nearest`,\n body,\n );\n return env.data;\n },\n /**\n * Count rows in a collection, optionally filtered. NEW in 4.0 —\n * wires to `GET /v1/client/collections/:name/count` with an\n * optional `?query={\"where\":...}` body channel (GET requests\n * can't carry a JSON body, so the server reads the JSON from the\n * `query` param).\n *\n * The server's `/count` route only honors `where` + `includeDeleted`\n * — passing other knobs (`limit`, `cursor`, etc.) returns\n * `INVALID_QUERY_PARAM`. Mirrors the SDK core's `CollectionsModule::count`.\n */\n count: async (name: string, where?: Filter): Promise<number> => {\n const s = getState();\n const query: Record<string, string> | undefined =\n where !== undefined ? { query: JSON.stringify({ where }) } : undefined;\n const env = await s.http.get<Envelope<{ count: number }>>(\n `${P.collections}/${encodePathSegment(name)}/count`,\n query,\n );\n return env.data.count;\n },\n insert: async <T = unknown>(\n name: string,\n row: Record<string, unknown>,\n ): Promise<T> => {\n const s = getState();\n if (s.offlineEnabled) {\n // Mint the idempotency key up front and send it on the live attempt\n // too. If that attempt reached the server before the connection\n // dropped, the queued replay carries the SAME key and the API dedupes\n // it (migration 056) — no double-insert.\n const idempotencyKey =\n (typeof row[\"idempotency_key\"] === \"string\"\n ? (row[\"idempotency_key\"] as string)\n : undefined) ?? newIdempotencyKey();\n // Who is writing, captured before the live attempt: a request that\n // outlives a sign-out stays attributed to this user (or is refused).\n const outboxTicket = s.offlineEnabled ? getOfflineQueue(s).ticket() : undefined;\n try {\n const env = await s.http.post<Envelope<T>>(\n `${P.collections}/${encodePathSegment(name)}`,\n { ...row, idempotency_key: idempotencyKey },\n );\n return env.data;\n } catch (err) {\n if (isOfflineError(err)) {\n await getOfflineQueue(s).enqueue({\n kind: \"collection.insert\",\n collection: name,\n payload: row,\n idempotencyKey,\n }, outboxTicket);\n return { ...row, _amba_pending: true } as unknown as T;\n }\n throw err;\n }\n }\n const env = await s.http.post<Envelope<T>>(\n `${P.collections}/${encodePathSegment(name)}`,\n row,\n );\n return env.data;\n },\n update: async <T = unknown>(\n name: string,\n id: string,\n set: Record<string, unknown>,\n ): Promise<T> => {\n const s = getState();\n if (s.offlineEnabled) {\n // Who is writing, captured before the live attempt: a request that\n // outlives a sign-out stays attributed to this user (or is refused).\n const outboxTicket = s.offlineEnabled ? getOfflineQueue(s).ticket() : undefined;\n try {\n const env = await s.http.patch<Envelope<T>>(\n `${P.collections}/${encodePathSegment(name)}/${encodePathSegment(id)}`,\n { set },\n );\n return env.data;\n } catch (err) {\n if (isOfflineError(err)) {\n await getOfflineQueue(s).enqueue({\n kind: \"collection.update\",\n collection: name,\n id,\n payload: set,\n }, outboxTicket);\n return { id, ...set, _amba_pending: true } as unknown as T;\n }\n throw err;\n }\n }\n const env = await s.http.patch<Envelope<T>>(\n `${P.collections}/${encodePathSegment(name)}/${encodePathSegment(id)}`,\n { set },\n );\n return env.data;\n },\n delete: async (\n name: string,\n id: string,\n ): Promise<{ data: { deleted: boolean } }> => {\n const s = getState();\n if (s.offlineEnabled) {\n // Who is writing, captured before the live attempt: a request that\n // outlives a sign-out stays attributed to this user (or is refused).\n const outboxTicket = s.offlineEnabled ? getOfflineQueue(s).ticket() : undefined;\n try {\n const result = await s.http.delete<{\n data: { deleted: boolean };\n } | null>(\n `${P.collections}/${encodePathSegment(name)}/${encodePathSegment(id)}`,\n );\n return result ?? { data: { deleted: true } };\n } catch (err) {\n if (isOfflineError(err)) {\n await getOfflineQueue(s).enqueue({\n kind: \"collection.delete\",\n collection: name,\n id,\n }, outboxTicket);\n return { data: { deleted: true } };\n }\n throw err;\n }\n }\n const result = await s.http.delete<{ data: { deleted: boolean } } | null>(\n `${P.collections}/${encodePathSegment(name)}/${encodePathSegment(id)}`,\n );\n // 204 No Content path — synthesise a `{ data: { deleted: true } }`\n // envelope so consumers can branch uniformly.\n return result ?? { data: { deleted: true } };\n },\n /**\n * Optimistic insert — update a local view immediately, fire the real\n * insert, and roll the view back if it fails. Thin convenience over the\n * exported {@link withOptimistic}; for multi-step flows use that directly.\n *\n * Optimistic writes always hit the network directly — they are NOT diverted\n * to the offline outbox even when `Amba.offline` is enabled. Optimistic UI\n * needs an unambiguous outcome: this resolves only on a confirmed write and\n * rejects (running your `rollback`) on any failure including being offline.\n * To buffer-and-replay instead, use the plain `insert`/`update`/`delete`\n * with the outbox on.\n */\n optimisticInsert: async <T = unknown, TApply = unknown>(\n name: string,\n row: Record<string, unknown>,\n view: { apply: () => TApply; rollback: (applied: TApply) => void },\n ): Promise<T> =>\n withOptimistic({\n apply: view.apply,\n mutate: () => insertDirect<T>(getState(), name, row),\n rollback: view.rollback,\n }),\n /** Optimistic update — see {@link optimisticInsert}. */\n optimisticUpdate: async <T = unknown, TApply = unknown>(\n name: string,\n id: string,\n set: Record<string, unknown>,\n view: { apply: () => TApply; rollback: (applied: TApply) => void },\n ): Promise<T> =>\n withOptimistic({\n apply: view.apply,\n mutate: () => updateDirect<T>(getState(), name, id, set),\n rollback: view.rollback,\n }),\n /** Optimistic delete — see {@link optimisticInsert}. */\n optimisticDelete: async <TApply = unknown>(\n name: string,\n id: string,\n view: { apply: () => TApply; rollback: (applied: TApply) => void },\n ): Promise<{ data: { deleted: boolean } }> =>\n withOptimistic({\n apply: view.apply,\n mutate: () => deleteDirect(getState(), name, id),\n rollback: view.rollback,\n }),\n where: {\n eq: (column: string, value: FilterValue): Filter => ({\n column,\n op: \"eq\",\n value,\n }),\n ne: (column: string, value: FilterValue): Filter => ({\n column,\n op: \"ne\",\n value,\n }),\n gt: (column: string, value: FilterValue): Filter => ({\n column,\n op: \"gt\",\n value,\n }),\n gte: (column: string, value: FilterValue): Filter => ({\n column,\n op: \"gte\",\n value,\n }),\n lt: (column: string, value: FilterValue): Filter => ({\n column,\n op: \"lt\",\n value,\n }),\n lte: (column: string, value: FilterValue): Filter => ({\n column,\n op: \"lte\",\n value,\n }),\n in: (column: string, values: FilterValue[]): Filter => ({\n column,\n op: \"in\",\n value: values,\n }),\n notIn: (column: string, values: FilterValue[]): Filter => ({\n column,\n op: \"not_in\",\n value: values,\n }),\n like: (column: string, pattern: string): Filter => ({\n column,\n op: \"like\",\n value: pattern,\n }),\n ilike: (column: string, pattern: string): Filter => ({\n column,\n op: \"ilike\",\n value: pattern,\n }),\n isNull: (column: string): Filter => ({ column, op: \"is_null\" }),\n isNotNull: (column: string): Filter => ({ column, op: \"is_not_null\" }),\n and: (...filters: Filter[]): Filter => ({ and: filters }),\n or: (...filters: Filter[]): Filter => ({ or: filters }),\n not: (filter: Filter): Filter => ({ not: filter }),\n },\n },\n\n storage: {\n presign: async (params: {\n bucket: string;\n filename: string;\n mimeType: string;\n sizeBytes: number;\n retentionDays?: number;\n }): Promise<PresignData> => {\n const s = getState();\n const env = await s.http.post<Envelope<PresignData>>(P.storagePresign, {\n bucket: params.bucket,\n filename: params.filename,\n mime_type: params.mimeType,\n size_bytes: params.sizeBytes,\n retention_days: params.retentionDays,\n });\n return env.data;\n },\n commit: async (uploadId: string, assetId: string): Promise<MediaAsset> => {\n const s = getState();\n const env = await s.http.post<Envelope<MediaAsset>>(P.storageCommit, {\n upload_id: uploadId,\n asset_id: assetId,\n });\n return env.data;\n },\n // <generated:storage>\n /**\n * List media assets, optionally filtered by a key prefix. NEW in 4.0.\n *\n * Returns the full `{ data, next_cursor }` envelope so paginated\n * listings expose the cursor — aligns with the web SDK shape\n * (`MediaListResponse`) and the engine `ListResponse`.\n */\n list: async (\n prefix?: string,\n ): Promise<{ data: MediaAsset[]; next_cursor?: string | null }> => {\n const env = await getState().http.get<{\n data: MediaAsset[];\n next_cursor?: string | null;\n }>(P.storage, prefix !== undefined ? { prefix } : undefined);\n return { data: env.data ?? [], next_cursor: env.next_cursor ?? null };\n },\n /**\n * Soft-delete a media asset by its asset id (the UUID returned\n * from `upload()` / `list()` / `get()`). NEW in 4.0.\n *\n * Wires to `DELETE /v1/client/media/:assetId` per\n * `apps/api/src/routes/client/media.ts:374`. The server scopes\n * deletes by `uploaded_by = current user`. Returns 404 if the\n * asset doesn't exist or isn't owned by the caller.\n *\n * NOTE: takes the asset id (UUID), not the storage key — the\n * server has no `?key=` query channel. Use `Amba.storage.list()`\n * to get the id from a `MediaAsset` row.\n */\n delete: async (assetId: string): Promise<void> => {\n await getState().http.delete(\n `${P.storage}/${encodePathSegment(assetId)}`,\n );\n },\n // </generated:storage>\n /**\n * Download a media asset's bytes by its asset id. Two-step:\n * resolve the `MediaAsset` metadata (which carries the CDN URL),\n * then fetch the CDN URL directly with no API headers.\n *\n * Returns a `Blob` — RN's fetch surfaces blobs natively\n * (`response.blob()`). NEW in 4.0.\n *\n * Mirrors the SDK core's `StorageModule::download` — there is no\n * `/v1/client/media/download` route on the server; the asset URL\n * on the asset row is the canonical fetch target.\n */\n download: async (assetId: string): Promise<Blob> => {\n const s = getState();\n // Step 1: resolve the asset to get the CDN url.\n const assetEnv = await s.http.get<Envelope<MediaAsset>>(\n `${P.storage}/${encodePathSegment(assetId)}`,\n );\n const cdnUrl =\n (assetEnv.data as MediaAsset & { storage_url?: string }).url ??\n (assetEnv.data as MediaAsset & { storage_url?: string }).storage_url ??\n \"\";\n if (!cdnUrl) {\n throw new AmbaApiError(\"NOT_FOUND\", \"asset has no resolvable CDN url\");\n }\n // Step 2: fetch the CDN bytes. No API headers — the CDN doesn't\n // accept the project API key and signed URLs carry their own auth.\n // A media transfer gets the long deadline, widened by the asset's\n // size so a large file on a slow link has time to arrive.\n const sizeBytes = (assetEnv.data as { size_bytes?: unknown }).size_bytes;\n const { response, blob, text } = await s.http.send(\n cdnUrl,\n { method: \"GET\" },\n async (res) =>\n res.ok\n ? { response: res, blob: await res.blob(), text: \"\" }\n : { response: res, blob: null, text: await res.text() },\n {\n timeoutMs: s.http.longRequestTimeoutMs,\n responseBytes: typeof sizeBytes === \"number\" ? sizeBytes : undefined,\n },\n );\n if (!blob) {\n const ct = response.headers.get(\"content-type\") ?? \"\";\n throw mapHttpError(response, text, ct.includes(\"application/json\"));\n }\n return blob;\n },\n },\n\n push: {\n register: async (\n token: string,\n platform: PushPlatform,\n bundleId?: string,\n ): Promise<PushToken> => {\n const s = getState();\n const env = await s.http.post<Envelope<PushToken>>(P.pushRegister, {\n token,\n platform,\n bundle_id: bundleId,\n });\n return env.data;\n },\n unregister: async (token: string): Promise<void> => {\n await getState().http.post(P.pushUnregister, { token });\n },\n getTokens: async (): Promise<PushToken[]> => {\n const env = await getState().http.get<Envelope<PushToken[]>>(\n P.pushTokens,\n );\n return env.data;\n },\n subscribe: async (topic: string): Promise<void> => {\n await getState().http.post(P.pushSubscribe, { topic });\n },\n unsubscribe: async (topic: string): Promise<void> => {\n await getState().http.post(P.pushUnsubscribe, { topic });\n },\n /**\n * Auto re-register the device push token when the OS rotates it.\n *\n * The OS can roll a device's push token (reinstall, restore, key roll);\n * the new token must be re-registered or notifications stop arriving. Pass\n * your notification library's token-refresh subscription primitive — e.g.\n * `(h) => Notifications.addPushTokenListener((t) => h(t.data)).remove` on\n * Expo, or the bare-FCM `onTokenRefresh` callback — and the SDK forwards\n * each rotated token through the idempotent {@link register} path.\n *\n * Pass the token `platform` for your build — `\"apns\"` on iOS, `\"fcm\"` on\n * Android (defaults to `\"fcm\"` when omitted). The bare RN SDK never reads\n * `Platform.OS` itself (it's a pure-JS client that also loads under Node),\n * so the caller supplies it; the Expo wrapper's\n * `Amba.enablePushAutoRotation()` resolves it for you. Returns an\n * unsubscribe function.\n *\n * @example\n * import { Platform } from \"react-native\";\n * import * as Notifications from \"expo-notifications\";\n * const detach = Amba.push.enableAutoRotation(\n * (h) => Notifications.addPushTokenListener((t) => h(t.data)).remove,\n * Platform.OS === \"ios\" ? \"apns\" : \"fcm\",\n * );\n */\n enableAutoRotation: (\n subscribe: TokenRefreshSubscribe,\n platform: \"apns\" | \"fcm\" = \"fcm\",\n ): (() => void) =>\n enableNativePushAutoRotation(\n subscribe,\n (token, p) => Amba.push.register(token, p),\n platform,\n ),\n },\n\n /**\n * On-device local notifications — `scheduleLocal` / `cancelLocal` /\n * `listScheduledLocal`. These schedule notifications the device fires\n * itself (reminders, streak nudges) with no server round-trip, distinct\n * from remote push (`Amba.push.*`). Backed by `expo-notifications`\n * (an optional peer dep that works in both Expo and bare RN apps),\n * loaded lazily; methods throw a clear `LOCAL_NOTIFICATIONS_UNAVAILABLE`\n * error if it isn't installed. See `./local-notifications.ts`.\n */\n notifications,\n\n entitlements: {\n // <generated:entitlements.list>\n list: async (): Promise<UserEntitlement[]> => {\n const env = await getState().http.get<Envelope<UserEntitlement[]>>(\n P.entitlements,\n );\n return env.data;\n },\n // </generated:entitlements.list>\n // <generated:entitlements.has>\n has: async (entitlementId: string): Promise<boolean> => {\n const env = await getState().http.get<Envelope<UserEntitlement[]>>(\n P.entitlements,\n { active_only: \"true\" },\n );\n return env.data.some(\n (e) => e.entitlement_id === entitlementId && e.is_active,\n );\n },\n // </generated:entitlements.has>\n // <generated:entitlements.restore>\n /**\n * Restore purchases — re-sync the user's owned entitlements from the\n * server and return the active set. Required for App Store review (3.1.1);\n * wire it to a \"Restore Purchases\" button. Throws (access unchanged) if the\n * server can't reach the validation engine — show a retry message.\n */\n restore: async (): Promise<RestoreResult> => {\n const env = await getState().http.post<Envelope<RestoreResult>>(\n P.entitlementsRestore,\n {},\n );\n return env.data;\n },\n // </generated:entitlements.restore>\n },\n\n // <generated:offerings>\n /**\n * Subscription offerings — the packages your paywall renders, provider-\n * neutral. Pass an `offeringId` for one offering; omit it for all.\n */\n offerings: async (offeringId?: string): Promise<OfferingsResult> => {\n const env = await getState().http.get<Envelope<OfferingsResult>>(\n P.offerings,\n offeringId ? { offering_id: offeringId } : undefined,\n );\n return env.data;\n },\n // </generated:offerings>\n\n ai: {\n anthropic: {\n messages: {\n create: async (\n request: AiMessageRequest,\n ): Promise<AiMessageResponse> => {\n const env = await getState().http.post<Envelope<AiMessageResponse>>(\n P.aiAnthropic,\n request,\n );\n // Defense in depth: the server wraps the gateway's vendor body in\n // `{data:...}`. If the envelope is ever absent (the historical bug\n // that made this resolve to `undefined`), fail loud with a typed\n // error instead of silently returning nothing.\n if (!env || typeof env !== \"object\" || env.data === undefined) {\n throw new AmbaApiError(\n \"MALFORMED_RESPONSE\",\n \"AI response was not wrapped in the expected { data } envelope\",\n env,\n );\n }\n return env.data;\n },\n },\n },\n },\n\n /**\n * Invoke a deployed function by name, with the project's client key (and\n * the live session token, if any) injected automatically. Prefer this over\n * hand-rolling `fetch` against a function URL, which leaves the call\n * unauthenticated.\n *\n * The function host is selected in order from `functionsBaseUrl`, a\n * UUID-shaped configured project id, or a deduped diagnostics ping that\n * supplies the authoritative server-side project id.\n *\n * @example\n * const result = await Amba.functions.invoke<{ ok: boolean }>(\n * 'send_welcome_email',\n * { to: 'user@example.com' },\n * );\n *\n * @returns The parsed JSON response (or the raw text if not JSON).\n * @throws AmbaApiError on a network failure or a non-2xx response.\n */\n functions: {\n invoke: async <T = unknown>(\n name: string,\n body?: unknown,\n options?: InvokeOptions,\n ): Promise<T> => {\n const s = getState();\n if (!name || name.trim() === \"\") {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.functions.invoke: a function name is required.\",\n );\n }\n let timeoutMs: number;\n try {\n timeoutMs = readTimeout(\n \"Amba.functions.invoke: timeoutMs\",\n options?.timeoutMs,\n s.http.longRequestTimeoutMs,\n );\n } catch (err) {\n throw new AmbaApiError(\"INVALID_ARGUMENT\", (err as Error).message);\n }\n const url = await resolveFunctionUrl(s, name);\n const method = options?.method ?? \"POST\";\n // `buildHeaders` injects X-Api-Key + the Bearer session token (when\n // signed in) + the SDK-version headers (and a default\n // `Content-Type: application/json`). Extra headers merge on top.\n const headers = s.http.buildHeaders(options?.headers);\n let requestBody: string | undefined;\n if (body !== undefined && method !== \"GET\") {\n requestBody = JSON.stringify(body); // Content-Type already set by buildHeaders\n } else {\n // No body → drop the JSON Content-Type buildHeaders adds, so\n // body-less invokes don't advertise an (empty) JSON body that some\n // function runtimes reject. Mirrors the web SDK.\n delete headers[\"Content-Type\"];\n }\n\n // A function can run long, so the invocation gets the long deadline\n // (or the call's own `timeoutMs`), which covers reading the response.\n const { response, text } = await s.http.send(\n url,\n { method, headers, body: requestBody },\n async (res) => ({ response: res, text: await res.text() }),\n {\n timeoutMs,\n label: `Amba.functions.invoke(\"${name}\") network error`,\n },\n );\n if (!response.ok) {\n const ct = response.headers.get(\"content-type\") ?? \"\";\n throw mapHttpError(response, text, ct.includes(\"application/json\"));\n }\n if (!text) return undefined as T;\n try {\n return JSON.parse(text) as T;\n } catch {\n // Function returned non-JSON — pass it through verbatim.\n return text as unknown as T;\n }\n },\n },\n\n config: {\n // <generated:config>\n fetch: async (): Promise<ConfigBundle> => {\n const s = getState();\n // The user the request is sent for (and the sign-out generation):\n // the response is cached for them only while they are current.\n const ticket = s.remoteValues.ticket();\n // Config returns `{ data: Record<string, unknown> }` plus an\n // `ETag` header that carries the version stamp.\n const url = s.http.url(P.config);\n const headers = s.http.buildHeaders();\n // Sent under the request deadline, which covers reading the body.\n const { response, text } = await s.http.send(\n url,\n { method: \"GET\", headers },\n async (res) => ({ response: res, text: await res.text() }),\n );\n if (!response.ok) {\n const ct = response.headers.get(\"content-type\") ?? \"\";\n throw mapHttpError(response, text, ct.includes(\"application/json\"));\n }\n let parsed: { data?: Record<string, unknown> } = {};\n if (text) {\n try {\n parsed = JSON.parse(text);\n } catch {\n parsed = {};\n }\n }\n const etag = response.headers.get(\"etag\");\n // ETag format: `\"vN-...\"` — strip quotes then take the leading\n // `vN` prefix as the version stamp.\n let version: string | null = null;\n if (etag) {\n const cleaned = etag.replace(/^\"|\"$/g, \"\");\n const m = cleaned.match(/^(v\\d+)/);\n version = m ? (m[1] ?? null) : cleaned || null;\n }\n // The ETag names the user the server evaluated the bundle for.\n return s.remoteValues.storeConfig(\n ticket,\n { version, values: parsed.data ?? {} },\n etag,\n );\n },\n // </generated:config>\n /**\n * The value for `key` without a request, so a first render can read\n * config: the last fetch, else the value cached in AsyncStorage on an\n * earlier launch, else `configDefaults`, else `defaultValue`. A value of\n * another JSON kind than `defaultValue` (a string where the default is a\n * number) returns `defaultValue`. Before `configure()` finishes it reads\n * `configDefaults` only. The first call for each user (and for the\n * signed-out state) starts a background `config.fetch()` that replaces\n * the cached values.\n *\n * @example\n * const copy = Amba.config.getCached(\"welcome_copy\", \"Welcome!\");\n */\n getCached: configGetCached,\n },\n\n flags: {\n // <generated:flags>\n fetch: async (): Promise<FlagAssignment[]> => {\n const s = getState();\n const ticket = s.remoteValues.ticket();\n const env = await s.http.get<Envelope<FlagAssignment[]>>(P.flags);\n return s.remoteValues.storeFlags(ticket, env.data);\n },\n /**\n * Get a single flag assignment by name. NEW in 4.0. Wraps\n * `GET /v1/client/flags/{key}` — returns `null` when the flag is\n * unknown or disabled for this user (the server treats both as\n * 404 by design; the SDK normalizes to `null`). Other errors\n * (auth, rate-limit, network) still throw. The result updates the\n * device cache: a found flag is stored, a `null` one removed.\n */\n get: async (key: string): Promise<FlagAssignment | null> => {\n const s = getState();\n const ticket = s.remoteValues.ticket();\n try {\n const env = await s.http.get<Envelope<FlagAssignment>>(\n `${P.flags}/${encodePathSegment(key)}`,\n );\n return s.remoteValues.upsertFlag(ticket, env.data);\n } catch (err) {\n if (err instanceof AmbaApiError && err.code === \"NOT_FOUND\") {\n s.remoteValues.removeFlag(ticket, key);\n return null;\n }\n throw err;\n }\n },\n // </generated:flags>\n /**\n * The assignment the SDK holds for flag `key` without a request (the\n * last fetch, else the AsyncStorage cache), or `null`.\n */\n getCached: flagsGetCached,\n /**\n * Whether flag `key` is on without a request: the held assignment, else\n * a boolean `configDefaults` entry under the name, else `defaultValue`\n * (`false`). A flag switched off on the server is left out of the next\n * fetch, so it reads as `defaultValue` from then on. The first cached\n * flag read for each user (and for the signed-out state) starts a\n * background `flags.fetch()` that replaces the cached values.\n */\n isEnabled: flagsIsEnabled,\n },\n\n /**\n * Experiments: server-assigned, sticky A/B variants. `getVariant` assigns\n * the signed-in user (the first call creates the assignment; later calls\n * return the same variant). Call `trackExposure` when the variant is\n * actually shown.\n */\n experiments: {\n // <generated:experiments>\n /**\n * Assign the current user to experiment `key` and return the variant\n * (sticky: later calls return the same variant). Wraps\n * `GET /v1/client/experiments/{key}/assignment`. The first result for\n * a user in a session also reaches `Amba.onExperimentAssigned`.\n */\n getVariant: async (key: string): Promise<ExperimentAssignment> => {\n const s = getState();\n // The server hashes the user of the session the request carries.\n // Without a session yet, the lazy anonymous sign-in creates it.\n const userBefore = s.identity.getAppUserId();\n const env = await s.http.get<\n Envelope<{ experiment_key: string; variant: string }>\n >(`${P.experiments}/${encodePathSegment(key)}/assignment`);\n return experimentReporter.report({\n experimentKey: env.data.experiment_key,\n variant: env.data.variant,\n bucketingId: userBefore ?? s.identity.getAppUserId() ?? \"\",\n });\n },\n /**\n * Record that the current user saw their variant of `key`. Wraps\n * `POST /v1/client/experiments/{key}/exposure`. The user needs an\n * assignment first (`getVariant`).\n */\n trackExposure: async (key: string): Promise<void> => {\n await getState().http.post(\n `${P.experiments}/${encodePathSegment(key)}/exposure`,\n {},\n );\n },\n // </generated:experiments>\n },\n\n /**\n * Subscribe to experiment assignments, for forwarding to your analytics.\n * The listener runs once per experiment per user session (the first\n * `experiments.getVariant(key)` result for that user while it is\n * subscribed) with `{ experimentKey, variant, bucketingId }`. A sign-out\n * starts a new session. Errors the listener throws are caught and logged;\n * they never reach the `getVariant` caller. Returns an unsubscribe\n * function.\n */\n onExperimentAssigned: (listener: ExperimentAssignedListener): (() => void) =>\n experimentReporter.subscribe(listener),\n\n /**\n * Wire-verify primitive. Call once after `configure()` to confirm\n * the SDK is talking to the right project with the right key in\n * the right environment. See `@layers/amba-web::Amba.diagnostics.ping`\n * for the canonical usage example — the response shape is identical\n * across every Amba SDK.\n *\n * The ping response is NOT wrapped in the usual `{data: ...}`\n * envelope — the route returns the `PingResult` shape directly so\n * an operator running `curl … | jq` sees the structured fields\n * without an extra unwrap.\n */\n diagnostics: {\n ping: async (): Promise<PingResult> => {\n return await getState().http.post<PingResult>(P.diagnosticsPing, {});\n },\n },\n\n // ── Gamification ──────────────────────────────────────────────────\n\n achievements: {\n // <generated:achievements>\n getAll: async (): Promise<Achievement[]> => {\n const env = await getState().http.get<Envelope<Achievement[]>>(\n P.achievements,\n );\n return env.data;\n },\n getProgress: async (): Promise<AchievementProgress[]> => {\n const env = await getState().http.get<Envelope<AchievementProgress[]>>(\n `${P.achievements}/progress`,\n );\n return env.data;\n },\n // </generated:achievements>\n },\n\n challenges: {\n // <generated:challenges>\n getActive: async (): Promise<Challenge[]> => {\n const env = await getState().http.get<Envelope<Challenge[]>>(\n `${P.challenges}/active`,\n );\n return env.data;\n },\n get: async (id: string): Promise<Challenge> => {\n const env = await getState().http.get<Envelope<Challenge>>(\n `${P.challenges}/${encodePathSegment(id)}`,\n );\n return env.data;\n },\n getProgress: async (id: string): Promise<ChallengeProgress> => {\n const env = await getState().http.get<Envelope<ChallengeProgress>>(\n `${P.challenges}/${encodePathSegment(id)}/progress`,\n );\n return env.data;\n },\n claim: async (id: string): Promise<ChallengeProgress> => {\n const env = await getState().http.post<Envelope<ChallengeProgress>>(\n `${P.challenges}/${encodePathSegment(id)}/claim`,\n {},\n );\n return env.data;\n },\n // </generated:challenges>\n },\n\n currencies: {\n // <generated:currencies>\n getBalance: async (): Promise<CurrencyBalance[]> => {\n const env = await getState().http.get<Envelope<CurrencyBalance[]>>(\n P.currencies,\n );\n return env.data;\n },\n getTransactions: async (\n currencyKey: string,\n ): Promise<CurrencyTransaction[]> => {\n // Server route is `GET /currencies/transactions?currency_code=<key>`\n // — the key is a query param, not a path segment. DX-33 fix.\n const env = await getState().http.get<Envelope<CurrencyTransaction[]>>(\n `${P.currencies}/transactions`,\n { currency_code: currencyKey },\n );\n return env.data;\n },\n // </generated:currencies>\n },\n\n inventory: {\n // <generated:inventory>\n getItems: async (): Promise<InventoryItem[]> => {\n const env = await getState().http.get<Envelope<InventoryItem[]>>(\n P.inventory,\n );\n return env.data;\n },\n getItem: async (id: string): Promise<InventoryItem> => {\n const env = await getState().http.get<Envelope<InventoryItem>>(\n `${P.inventory}/${encodePathSegment(id)}`,\n );\n return env.data;\n },\n purchase: async (request: PurchaseRequest): Promise<InventoryPurchase> => {\n const env = await getState().http.post<Envelope<InventoryPurchase>>(\n `${P.inventory}/purchase`,\n request,\n );\n return env.data;\n },\n consume: async (request: ConsumeRequest): Promise<InventoryItem> => {\n // Server route is `POST /v1/client/inventory/:itemId/consume`. The\n // item id moves into the path; only `quantity` stays in the body.\n // DX-36 fix.\n const { item_id, ...body } = request;\n const env = await getState().http.post<Envelope<InventoryItem>>(\n `${P.inventory}/${encodePathSegment(item_id)}/consume`,\n body,\n );\n return env.data;\n },\n // </generated:inventory>\n },\n\n leaderboards: {\n // <generated:leaderboards>\n get: async (key: string): Promise<Leaderboard> => {\n const env = await getState().http.get<Envelope<Leaderboard>>(\n `${P.leaderboards}/${encodePathSegment(key)}`,\n );\n return env.data;\n },\n getEntries: async (\n key: string,\n limit?: number,\n ): Promise<LeaderboardEntry[]> => {\n const env = await getState().http.get<Envelope<LeaderboardEntry[]>>(\n `${P.leaderboards}/${encodePathSegment(key)}/entries`,\n limit !== undefined ? { limit } : undefined,\n );\n return env.data;\n },\n getMyRank: async (key: string): Promise<LeaderboardEntry> => {\n const env = await getState().http.get<Envelope<LeaderboardEntry>>(\n `${P.leaderboards}/${encodePathSegment(key)}/me`,\n );\n return env.data;\n },\n // </generated:leaderboards>\n },\n\n /**\n * Tiered weekly cohorts (Duolingo-style leagues). NEW in 4.0.\n *\n * `me()` returns the caller's current cohort/tier/score snapshot.\n * `cohort()` returns the full peer list competing in the same tier\n * this week — for rendering a league-board UI.\n */\n leagues: {\n // <generated:leagues>\n me: async (): Promise<LeagueMembership> => {\n const env = await getState().http.get<Envelope<LeagueMembership>>(\n `${P.leagues}/me`,\n );\n return env.data;\n },\n cohort: async (): Promise<LeagueCohort> => {\n // Server route is `/me/cohort` per\n // `apps/api/src/routes/client/leagues.ts:176`. The bare\n // `/cohort` path 404s.\n const env = await getState().http.get<Envelope<LeagueCohort>>(\n `${P.leagues}/me/cohort`,\n );\n return env.data;\n },\n // </generated:leagues>\n },\n\n stores: {\n // <generated:stores>\n list: async (): Promise<Store[]> => {\n const env = await getState().http.get<Envelope<Store[]>>(P.stores);\n return env.data;\n },\n getPurchaseOptions: async (\n storeKey: string,\n ): Promise<PurchaseOption[]> => {\n const env = await getState().http.get<Envelope<PurchaseOption[]>>(\n `${P.stores}/${encodePathSegment(storeKey)}/options`,\n );\n return env.data;\n },\n purchase: async (\n storeKey: string,\n purchaseOptionId: string,\n receipt: unknown,\n ): Promise<PurchaseResult> => {\n // `undefined` doesn't survive JSON.stringify (it's omitted from\n // the object, leaving the server's body as if `receipt` was\n // never sent). For symmetry with the web SDK fix, default the\n // missing-receipt case to an empty object so the server sees a\n // valid `{ purchase_option_id, receipt: {} }` body (BugBot Low\n // 2026-05-19).\n const env = await getState().http.post<Envelope<PurchaseResult>>(\n `${P.stores}/${encodePathSegment(storeKey)}/purchase`,\n { purchase_option_id: purchaseOptionId, receipt: receipt ?? {} },\n );\n return env.data;\n },\n // </generated:stores>\n },\n\n xp: {\n // <generated:xp>\n getBalance: async (): Promise<XpBalance> => {\n const env = await getState().http.get<Envelope<XpBalance>>(P.xp);\n return env.data;\n },\n getHistory: async (limit?: number): Promise<XpTransaction[]> => {\n const env = await getState().http.get<Envelope<XpTransaction[]>>(\n `${P.xp}/history`,\n limit !== undefined ? { limit } : undefined,\n );\n return env.data;\n },\n claim: async (grantKey: string): Promise<XpTransaction> => {\n const env = await getState().http.post<Envelope<XpTransaction>>(\n `${P.xp}/claim`,\n { grant_key: grantKey },\n );\n return env.data;\n },\n // </generated:xp>\n },\n\n streaks: {\n // <generated:streaks>\n getAll: async (): Promise<Streak[]> => {\n const env = await getState().http.get<Envelope<Streak[]>>(P.streaks);\n return env.data;\n },\n qualify: async (streakKey: string): Promise<Streak> => {\n const env = await getState().http.post<Envelope<Streak>>(\n `${P.streaks}/${encodePathSegment(streakKey)}/qualify`,\n {},\n );\n return env.data;\n },\n // </generated:streaks>\n },\n\n // ── Social ─────────────────────────────────────────────────────────\n\n feeds: {\n // <generated:feeds>\n getActivity: async (\n feed?: string,\n cursor?: string,\n ): Promise<FeedResponse> => {\n const f = feed ?? \"default\";\n return getState().http.get<FeedResponse>(\n `${P.feeds}/${encodePathSegment(f)}`,\n cursor ? { cursor } : undefined,\n );\n },\n // </generated:feeds>\n },\n\n friends: {\n // <generated:friends>\n getList: async (): Promise<Friendship[]> => {\n const env = await getState().http.get<Envelope<Friendship[]>>(\n P.friends,\n );\n return env.data;\n },\n getFriends: async (): Promise<Friendship[]> => {\n const env = await getState().http.get<Envelope<Friendship[]>>(\n P.friends,\n { state: \"accepted\" },\n );\n return env.data;\n },\n /**\n * Send a friend request to `userId`. NEW in 4.0 — the existing\n * SDK shipped block/unblock but had no request/accept/decline\n * surface, so customers couldn't actually build a friends graph\n * without dropping to raw HTTP.\n */\n sendRequest: async (userId: string): Promise<Friendship> => {\n // Server route is `POST /v1/client/friends/request` (singular),\n // which reads the other user's id as `addressee_id`.\n const env = await getState().http.post<Envelope<Friendship>>(\n `${P.friends}/request`,\n { addressee_id: userId },\n );\n return env.data;\n },\n /**\n * Accept an inbound friend request. NEW in 4.0.\n */\n acceptRequest: async (friendshipId: string): Promise<Friendship> => {\n const env = await getState().http.post<Envelope<Friendship>>(\n `${P.friends}/${encodePathSegment(friendshipId)}/accept`,\n {},\n );\n return env.data;\n },\n /**\n * Decline an inbound friend request. NEW in 4.0. Server endpoint\n * is `/:friendshipId/reject` (not `/decline`) — SDK keeps the\n * `declineRequest` name (intent is clear) but hits the server's\n * spelling. apps/api/src/routes/client/friends.ts:152.\n */\n declineRequest: async (friendshipId: string): Promise<void> => {\n await getState().http.post(\n `${P.friends}/${encodePathSegment(friendshipId)}/reject`,\n {},\n );\n },\n blockUser: async (userId: string): Promise<Friendship> => {\n const env = await getState().http.post<Envelope<Friendship>>(\n `${P.friends}/block`,\n { user_id: userId },\n );\n return env.data;\n },\n unblockUser: async (userId: string): Promise<void> => {\n await getState().http.post(`${P.friends}/unblock`, { user_id: userId });\n },\n removeBlock: async (friendshipId: string): Promise<void> => {\n await getState().http.delete(\n `${P.friends}/${encodePathSegment(friendshipId)}`,\n );\n },\n /**\n * `friends.removeFriend(userId)` — unfriend by the other user's id\n * without needing the friendship row id. NEW in 4.0. Wraps\n * `DELETE /v1/client/friends/by-user/{userId}`. Server preserves\n * blocked rows (use `unblockUser` to clear those); 404 → throws\n * AmbaApiError with code NOT_FOUND.\n */\n removeFriend: async (userId: string): Promise<void> => {\n await getState().http.delete(\n `${P.friends}/by-user/${encodePathSegment(userId)}`,\n );\n },\n // </generated:friends>\n },\n\n groups: {\n // <generated:groups>\n create: async (params: GroupCreate): Promise<Group> => {\n const env = await getState().http.post<Envelope<Group>>(P.groups, params);\n return env.data;\n },\n get: async (id: string): Promise<Group> => {\n const env = await getState().http.get<Envelope<Group>>(\n `${P.groups}/${encodePathSegment(id)}`,\n );\n return env.data;\n },\n update: async (id: string, patch: GroupUpdate): Promise<Group> => {\n const env = await getState().http.patch<Envelope<Group>>(\n `${P.groups}/${encodePathSegment(id)}`,\n patch,\n );\n return env.data;\n },\n delete: async (id: string): Promise<void> => {\n await getState().http.delete(`${P.groups}/${encodePathSegment(id)}`);\n },\n getMembers: async (id: string): Promise<GroupMember[]> => {\n const env = await getState().http.get<Envelope<GroupMember[]>>(\n `${P.groups}/${encodePathSegment(id)}/members`,\n );\n return env.data;\n },\n join: async (id: string): Promise<GroupMember> => {\n const env = await getState().http.post<Envelope<GroupMember>>(\n `${P.groups}/${encodePathSegment(id)}/join`,\n {},\n );\n return env.data;\n },\n leave: async (id: string): Promise<void> => {\n await getState().http.post(\n `${P.groups}/${encodePathSegment(id)}/leave`,\n {},\n );\n },\n invite: async (id: string, userId: string): Promise<GroupMember> => {\n const env = await getState().http.post<Envelope<GroupMember>>(\n `${P.groups}/${encodePathSegment(id)}/invite`,\n { user_id: userId },\n );\n return env.data;\n },\n // </generated:groups>\n },\n\n /**\n * User-to-user direct messaging. Task #62 corrected the path\n * constants — `sendMessage` previously POSTed to\n * `/v1/client/messaging/messages`, which the API doesn't serve.\n * The real route is `/v1/client/messaging/conversations/:id/messages`.\n */\n messaging: {\n /**\n * Live conversation subscription. Returns a builder — call\n * `.subscribe(cb)` to receive new messages live instead of polling:\n *\n * const unsub = Amba.messaging\n * .conversation(conversationId)\n * .subscribe((change) => {\n * // change.op === \"INSERT\"; change.row is the new message\n * });\n * unsub(); // stop listening\n *\n * Delivery is scoped to conversation participants — you receive messages\n * only if your session is a member of the conversation. Reconnects\n * automatically with backoff. Requires an authenticated session.\n */\n conversation: <T = Message>(\n conversationId: string,\n ): ConversationSubscription<T> =>\n new ConversationSubscription<T>(\n {\n baseUrl: getState().baseUrl,\n authHeaders: realtimeAuthHeaders,\n connectTimeoutMs: getState().http.requestTimeoutMs,\n },\n conversationId,\n ),\n // <generated:messaging>\n /**\n * `POST /v1/client/messaging/conversations` — create (or fetch\n * the existing direct) conversation. Idempotent for direct\n * conversations between two users.\n */\n createConversation: async (\n request: CreateConversationRequest,\n ): Promise<Conversation> => {\n const env = await getState().http.post<Envelope<Conversation>>(\n `${P.messaging}/conversations`,\n request,\n );\n return env.data;\n },\n /**\n * `GET /v1/client/messaging/conversations` — list the caller's\n * conversations. Canonical 4.0 name; the legacy `getConversations`\n * is removed in 4.0 per the spec.\n */\n conversations: async (): Promise<Conversation[]> => {\n const env = await getState().http.get<Envelope<Conversation[]>>(\n `${P.messaging}/conversations`,\n );\n return env.data;\n },\n sendMessage: async (\n conversationId: string,\n request: SendMessageRequest,\n ): Promise<Message> => {\n const env = await getState().http.post<Envelope<Message>>(\n `${P.messaging}/conversations/${encodePathSegment(conversationId)}/messages`,\n request,\n );\n return env.data;\n },\n listMessages: async (\n conversationId: string,\n opts?: ListMessagesOptions,\n ): Promise<Message[]> => {\n const query: Record<string, number | undefined> = {};\n if (opts?.limit !== undefined) query.limit = opts.limit;\n if (opts?.offset !== undefined) query.offset = opts.offset;\n const env = await getState().http.get<Envelope<Message[]>>(\n `${P.messaging}/conversations/${encodePathSegment(conversationId)}/messages`,\n Object.keys(query).length > 0 ? query : undefined,\n );\n return env.data;\n },\n markRead: async (conversationId: string): Promise<MarkReadResult> => {\n const env = await getState().http.post<Envelope<MarkReadResult>>(\n `${P.messaging}/conversations/${encodePathSegment(conversationId)}/read`,\n {},\n );\n return env.data;\n },\n /**\n * Fetch a single message by id within a conversation. NEW in 4.0.\n *\n * The server has no dedicated `GET /messages/:id` route — the\n * `/messages` list endpoint also doesn't honour a `?message_id=`\n * query filter (see `apps/api/src/routes/client/messaging.ts:358`,\n * which returns the full paginated page). We paginate forward\n * (newest-first, server page size 100) and find the matching id\n * client-side, mirroring the SDK core's strategy. Walks up to `MAX_PAGES`\n * (5,000 messages total) before giving up to avoid an unbounded\n * loop against a giant conversation; callers with longer histories\n * should keep a local index instead of relying on this helper as a\n * search primitive.\n *\n * Returns `null` when the message id isn't found within the cap\n * (or the conversation), matching the canonical contract.\n */\n getMessage: async (\n conversationId: string,\n messageId: string,\n ): Promise<Message | null> => {\n const PAGE_SIZE = 100;\n const MAX_PAGES = 50;\n const s = getState();\n const path = `${P.messaging}/conversations/${encodePathSegment(conversationId)}/messages`;\n let offset = 0;\n for (let i = 0; i < MAX_PAGES; i++) {\n const env = await s.http.get<Envelope<Message[]>>(path, {\n limit: PAGE_SIZE,\n offset,\n });\n const page = env.data;\n const hit = page.find((m) => m.id === messageId);\n if (hit) return hit;\n if (page.length < PAGE_SIZE) return null;\n offset += PAGE_SIZE;\n }\n return null;\n },\n // </generated:messaging>\n },\n\n moderation: {\n reportUser: async (request: ReportRequest): Promise<Report> => {\n const env = await getState().http.post<Envelope<Report>>(\n `${P.moderation}/reports/users`,\n request,\n );\n return env.data;\n },\n reportContent: async (request: ReportRequest): Promise<Report> => {\n const env = await getState().http.post<Envelope<Report>>(\n `${P.moderation}/reports/content`,\n request,\n );\n return env.data;\n },\n getReportStatus: async (id: string): Promise<Report> => {\n const env = await getState().http.get<Envelope<Report>>(\n `${P.moderation}/reports/${encodePathSegment(id)}`,\n );\n return env.data;\n },\n },\n\n reviews: {\n // <generated:reviews>\n list: async (\n targetType: string,\n targetId: string,\n ): Promise<Review[]> => {\n const env = await getState().http.get<Envelope<Review[]>>(P.reviews, {\n target_type: targetType,\n target_id: targetId,\n });\n return env.data;\n },\n create: async (params: ReviewCreate): Promise<Review> => {\n const env = await getState().http.post<Envelope<Review>>(P.reviews, params);\n return env.data;\n },\n update: async (id: string, patch: ReviewUpdate): Promise<Review> => {\n const env = await getState().http.patch<Envelope<Review>>(\n `${P.reviews}/${encodePathSegment(id)}`,\n patch,\n );\n return env.data;\n },\n delete: async (id: string): Promise<void> => {\n await getState().http.delete(`${P.reviews}/${encodePathSegment(id)}`);\n },\n // </generated:reviews>\n },\n\n roles: {\n // <generated:roles>\n getMyRoles: async (): Promise<Role[]> => {\n const env = await getState().http.get<Envelope<Role[]>>(\n `${P.roles}/me`,\n );\n return env.data;\n },\n hasPermission: async (permission: string): Promise<boolean> => {\n const env = await getState().http.get<Envelope<{ allowed: boolean }>>(\n `${P.roles}/check`,\n { permission },\n );\n return env.data.allowed === true;\n },\n // </generated:roles>\n },\n\n referrals: {\n // <generated:referrals>\n getReferralCode: async (): Promise<ReferralCode> => {\n // Server route is `/v1/client/referrals/my-code` (DX-32 fix).\n const env = await getState().http.get<Envelope<ReferralCode>>(\n `${P.referrals}/my-code`,\n );\n return env.data;\n },\n claimReferral: async (code: string): Promise<ReferralClaim> => {\n const env = await getState().http.post<Envelope<ReferralClaim>>(\n `${P.referrals}/claim`,\n { code },\n );\n return env.data;\n },\n create: async (\n code?: string,\n maxUses?: number,\n ): Promise<ReferralCode> => {\n const body: Record<string, unknown> = {};\n if (code !== undefined) body.code = code;\n if (maxUses !== undefined) body.max_uses = maxUses;\n const env = await getState().http.post<Envelope<ReferralCode>>(\n P.referrals,\n body,\n );\n return env.data;\n },\n // </generated:referrals>\n },\n\n // ── Lifecycle ──────────────────────────────────────────────────────\n\n catalog: {\n // <generated:catalog>\n list: async (): Promise<CatalogItem[]> => {\n const env = await getState().http.get<Envelope<CatalogItem[]>>(\n P.catalog,\n );\n return env.data;\n },\n /**\n * Get a single catalog item by id. NEW in 4.0 — wires to\n * `GET /v1/client/catalog/:id`.\n */\n get: async (id: string): Promise<CatalogItem> => {\n const env = await getState().http.get<Envelope<CatalogItem>>(\n `${P.catalog}/${encodePathSegment(id)}`,\n );\n return env.data;\n },\n // </generated:catalog>\n },\n\n content: {\n // <generated:content>\n /**\n * Canonical 4.0 names — `today` / `library` / `item`. The legacy\n * `getToday` / `getLibrary` / `getItem` are removed in 4.0 per\n * the spec (CANONICAL_API.md, content section).\n */\n today: async (\n channel: string = \"default\",\n ): Promise<ContentItem | null> => {\n const env = await getState().http.get<Envelope<ContentItem | null>>(\n `${P.content}/today/${encodePathSegment(channel)}`,\n );\n return env.data ?? null;\n },\n library: async (\n // `channel` is the library NAME (the server resolves name-or-UUID).\n channel: string = \"default\",\n options?: { limit?: number; cursor?: string },\n ): Promise<ContentItem[]> => {\n // Server route is plural `/libraries/:libraryId` (accepts name or UUID).\n const env = await getState().http.get<Envelope<ContentItem[]>>(\n `${P.content}/libraries/${encodePathSegment(channel)}`,\n {\n limit: options?.limit,\n cursor: options?.cursor,\n },\n );\n return env.data;\n },\n item: async (id: string): Promise<ContentItem> => {\n // Server route is `/items/:itemId`.\n const env = await getState().http.get<Envelope<ContentItem>>(\n `${P.content}/items/${encodePathSegment(id)}`,\n );\n return env.data;\n },\n updateItem: async (id: string, state_: unknown): Promise<ContentItem> => {\n // Server route is `PATCH /items/:itemId` (update item fields).\n const env = await getState().http.patch<Envelope<ContentItem>>(\n `${P.content}/items/${encodePathSegment(id)}`,\n state_ as Record<string, unknown>,\n );\n return env.data;\n },\n createItem: async (\n // `channel` is the library NAME (the server resolves name-or-UUID).\n channel: string,\n item: unknown,\n ): Promise<ContentItem> => {\n // Server route is `POST /libraries/:libraryId/items`.\n const env = await getState().http.post<Envelope<ContentItem>>(\n `${P.content}/libraries/${encodePathSegment(channel)}/items`,\n item as Record<string, unknown>,\n );\n return env.data;\n },\n // </generated:content>\n },\n\n deepLinks: {\n // <generated:deepLinks>\n get: async (shortCode: string): Promise<DeepLink> => {\n // Server route is `/v1/client/deep-links/resolve/:slug` — DX-42 fix.\n const env = await getState().http.get<Envelope<DeepLink>>(\n `${P.deepLinks}/resolve/${encodePathSegment(shortCode)}`,\n );\n return env.data;\n },\n // </generated:deepLinks>\n create: async (params: DeepLinkCreate): Promise<DeepLink> => {\n const env = await getState().http.post<Envelope<DeepLink>>(\n P.deepLinks,\n params,\n );\n return env.data;\n },\n },\n\n onboarding: {\n // <generated:onboarding>\n getStatus: async (): Promise<OnboardingStatus> => {\n const env = await getState().http.get<Envelope<OnboardingStatus>>(\n P.onboarding,\n );\n return env.data;\n },\n nextStep: async (payload: unknown): Promise<OnboardingStatus> => {\n const env = await getState().http.post<Envelope<OnboardingStatus>>(\n `${P.onboarding}/next`,\n payload as Record<string, unknown>,\n );\n return env.data;\n },\n skipStep: async (): Promise<OnboardingStatus> => {\n const env = await getState().http.post<Envelope<OnboardingStatus>>(\n `${P.onboarding}/skip`,\n {},\n );\n return env.data;\n },\n complete: async (): Promise<OnboardingStatus> => {\n const env = await getState().http.post<Envelope<OnboardingStatus>>(\n `${P.onboarding}/complete`,\n {},\n );\n return env.data;\n },\n // </generated:onboarding>\n },\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["cachedStorage: AsyncStorageStatic | null | undefined","prefix: string","timer: ReturnType<typeof setTimeout> | undefined","SESSION_HEADER_AUTH_PATHS: ReadonlySet<string>","parts: string[]","h: Record<string, string>","init: RequestInit","response: Response","text: string","parsed: unknown","code: string | undefined","envelopeDetails: unknown","details: unknown","persistence: AsyncStoragePersistence","vault: TokenVault | null","seed: PersistedIdentity","fromVault: TokenSlice | null","parsed: Partial<PersistedIdentity> | null","stored","stored: TokenSlice | null","seq","recordSeq","payload: PersistedIdentity","slice: TokenSlice","TOKEN_STORAGE_MODES: readonly TokenStorageMode[]","key: string","storage: SecureTokenStorage","cachedKeychain: SecureTokenStorage | null | undefined","full: OutboxOp","replayError: unknown","store: KvStore","storageKey: string","options: EventBatchOptions","identity: () => {\n userId: string | null;\n anonymousId: string;\n sessionToken: string | null;\n }","postBatch: (\n body: { events: ReturnType<typeof wire>[] },\n sessionToken: string,\n ) => Promise<BatchResponse>","postSingle: (\n body: ReturnType<typeof wire>,\n sessionToken: string,\n ) => Promise<unknown>","item: QueuedEvent","evicted: QueuedEvent[]","dropped: QueuedEvent[]","next: QueuedEvent[]","delay","selected: QueuedEvent[]","out: ScheduledLocalNotification[]","maxReported: number","slot: Slot","store: RemoteValuesStore","key: string","defaults: Record<string, unknown>","currentUser: () => string | null","raw: string | null","slots: Record<string, Slot>","target: string | undefined","oldest: string | null","DEVICE_OS_VALUES: readonly DeviceOs[]","ctx: RealtimeContext","name: string","conversationId: string","closeActive: (() => void) | null","settleActive: (() => void) | null","idx: number","dataLines: string[]","state: RuntimeState | null","configuring: Promise<void> | null","configureChain: Promise<void>","_pendingConfigDefaults: Record<string, unknown> | null","event: AuthStateChangeEvent","exchange","user: User","result: AuthResult","exchange: RefreshExchange","fence: CommitFence","body: Record<string, unknown>","inheritedRefresh: RefreshExchange | null","identity: IdentityStore","remoteValues: RemoteValuesCache","nextState: RuntimeState","query: Record<string, string>","query: Record<string, string> | undefined","timeoutMs: number","requestBody: string | undefined","parsed: { data?: Record<string, unknown> }","version: string | null","query: Record<string, number | undefined>"],"sources":["../src/async-storage-adapter.ts","../src/error.ts","../src/request-deadline.ts","../src/http-client.ts","../src/secure-random.ts","../src/uuid.ts","../src/identity.ts","../src/token-storage.ts","../src/path-segment.ts","../src/resilience.ts","../src/event-batch-queue.ts","../src/push-rotation.ts","../src/local-notifications.ts","../src/experiment-reporter.ts","../src/remote-values.ts","../src/client-info.ts","../src/realtime.ts","../src/index.ts"],"sourcesContent":["/**\n * AsyncStorage-backed identity persistence for React Native.\n *\n * Bridges `@react-native-async-storage/async-storage` (peer-dep, installed\n * by the host app) to the SDK runtime. Reads/writes go through a\n * per-prefix namespace so different amba projects can coexist in the\n * same app.\n *\n * Identity records stored under `${prefix}:identity` survive app restarts.\n */\n\n// Late-imported to avoid bundling the peer-dep — host app installs it.\ntype AsyncStorageStatic = {\n getItem(key: string): Promise<string | null>;\n setItem(key: string, value: string): Promise<void>;\n removeItem(key: string): Promise<void>;\n};\n\n// Three-state cache:\n// - `undefined`: never probed.\n// - `null`: probed and unavailable (peer-dep missing OR shape check\n// failed). Future calls short-circuit to memory-only;\n// re-importing on every persistence op was a real perf\n// regression under the docs-react-native smoke harness.\n// - `<obj>`: probed and usable.\n//\n// The negative-result memoization is essential: a single Amba.* call can\n// trigger 3–5 identity reads (session token, refresh token, anonymous id,\n// etc.); without it each one re-runs `await import(...)` and the shape\n// probe.\nlet cachedStorage: AsyncStorageStatic | null | undefined = undefined;\n\n/**\n * Late-imports `@react-native-async-storage/async-storage` and returns\n * it ONLY if the resolved module exposes the three methods we use\n * (`getItem` / `setItem` / `removeItem`). Otherwise returns null and\n * the persistence layer falls back to memory-only.\n *\n * Why the shape check: pnpm with auto-install-peers (default since\n * pnpm 7) installs declared peer deps even in non-RN scratch\n * directories (e.g., this SDK's docs-smoke harness). The installed\n * package's Node entry point doesn't expose AsyncStorage's methods\n * — it's a React Native native module — so the `await import(...)`\n * succeeds (no catch fires) but the returned object has no\n * `getItem`. Calling it throws `TypeError: storage.getItem is not a\n * function` from inside `read()` (verified at PR #246 docs-react-native\n * / docs-expo run on 547a1ff4). Checking method presence here turns\n * that into the same fall-through as \"peer dep not installed at all.\"\n */\nfunction isAsyncStorageLike(mod: unknown): mod is AsyncStorageStatic {\n return (\n typeof mod === \"object\" &&\n mod !== null &&\n typeof (mod as AsyncStorageStatic).getItem === \"function\" &&\n typeof (mod as AsyncStorageStatic).setItem === \"function\" &&\n typeof (mod as AsyncStorageStatic).removeItem === \"function\"\n );\n}\n\nasync function getStorage(): Promise<AsyncStorageStatic | null> {\n if (cachedStorage !== undefined) return cachedStorage;\n try {\n const mod = await import(\"@react-native-async-storage/async-storage\");\n const candidate = (mod as { default?: unknown }).default ?? mod;\n if (!isAsyncStorageLike(candidate)) {\n // Module loaded but its shape doesn't match AsyncStorage's\n // contract. Memoize the negative result so subsequent calls\n // don't re-run the dynamic import + shape probe.\n cachedStorage = null;\n return null;\n }\n cachedStorage = candidate;\n return cachedStorage;\n } catch {\n // Host app didn't install the peer dep — fall back to in-memory.\n // amba will still work but identity won't survive app restart.\n // Memoize so future calls short-circuit.\n cachedStorage = null;\n return null;\n }\n}\n\n/**\n * Test-only reset hook. The module-level `cachedStorage` is a\n * singleton; tests that swap the mock between cases need a way to\n * invalidate it so the next call re-probes.\n */\nexport function _resetStorageCacheForTests(): void {\n cachedStorage = undefined;\n}\n\nexport class AsyncStoragePersistence {\n private memoryCache = new Map<string, string>();\n\n constructor(readonly prefix: string) {}\n\n /** Batching promises durable enqueue, so memory-only fallback is invalid. */\n async assertDurable(): Promise<void> {\n if (!(await getStorage())) {\n throw new Error(\n \"Amba event batching requires @react-native-async-storage/async-storage\",\n );\n }\n }\n\n async hydrate(): Promise<void> {\n // Future: when the SDK runtime gains a JS-side persistence bridge,\n // this will pre-load the identity record into memory so the runtime\n // can read it synchronously at init.\n }\n\n async read(key: string): Promise<string | null> {\n const fullKey = `${this.prefix}:${key}`;\n if (this.memoryCache.has(fullKey))\n return this.memoryCache.get(fullKey) ?? null;\n const storage = await getStorage();\n if (!storage) return null;\n const v = await storage.getItem(fullKey);\n if (v !== null) this.memoryCache.set(fullKey, v);\n return v;\n }\n\n /**\n * Read `key` from storage again, past the in-memory copy, and keep what\n * it holds now. For a record whose first read came back incomplete (see\n * `IdentityStore.recordStillUnreadable`).\n */\n async reread(key: string): Promise<string | null> {\n const fullKey = `${this.prefix}:${key}`;\n const storage = await getStorage();\n if (!storage) return this.memoryCache.get(fullKey) ?? null;\n const v = await storage.getItem(fullKey);\n if (v !== null) this.memoryCache.set(fullKey, v);\n else this.memoryCache.delete(fullKey);\n return v;\n }\n\n async write(key: string, value: string): Promise<void> {\n const fullKey = `${this.prefix}:${key}`;\n const storage = await getStorage();\n if (storage) await storage.setItem(fullKey, value);\n // Cache only after durable storage succeeds. A rejected AsyncStorage\n // write must not make a queued event look persisted on subsequent reads.\n this.memoryCache.set(fullKey, value);\n }\n\n async delete(key: string): Promise<void> {\n const fullKey = `${this.prefix}:${key}`;\n const storage = await getStorage();\n if (storage) await storage.removeItem(fullKey);\n this.memoryCache.delete(fullKey);\n }\n}\n","/**\n * Typed error class for the amba SDK + helper that converts arbitrary\n * thrown values (native Error, plain objects, runtime-emitted errors)\n * into a stable `AmbaApiError` instance.\n *\n * Callers can `instanceof`-narrow safely, branch on `.code`, and pull\n * extra payload from `.details` (raw HTTP body, validation field paths,\n * etc.). The wrapper methods on `Amba` wrap every async call in\n * `toAmbaApiError`, so consumers see one error shape regardless of\n * which layer (network, runtime, validation) failed.\n */\n\n/**\n * Stable error codes surfaced by the SDK. Strings rather than an enum\n * so customers can extend with custom codes by simply throwing\n * `new AmbaApiError(\"MY_CODE\", \"...\")` without coordinating with this\n * package's TypeScript types.\n */\nexport type AmbaApiErrorCode =\n | \"UNAUTHORIZED\"\n | \"FORBIDDEN\"\n | \"NOT_FOUND\"\n | \"CONFLICT\"\n | \"RATE_LIMITED\"\n | \"VALIDATION_ERROR\"\n | \"NETWORK_ERROR\"\n | \"HTTP_ERROR\"\n | \"CIRCUIT_OPEN\"\n | \"CONSENT_NOT_GRANTED\"\n | \"NOT_INITIALIZED\"\n | \"INVALID_CONFIG\"\n | \"INVALID_ARGUMENT\"\n | \"PUSH_PERMISSION_DENIED\"\n | \"PUSH_REGISTRATION_FAILED\"\n | \"LOCAL_NOTIFICATIONS_UNAVAILABLE\"\n | \"LOCAL_NOTIFICATIONS_PERMISSION_DENIED\"\n | \"NOT_SANDBOX_TIER\"\n // A session with a linked identity ended without a sign-out; the user\n // signs in again (see `AuthStateChangeReason`).\n | \"SIGN_IN_REQUIRED\"\n // A session renewal answered for a different user than the stored one.\n | \"OWNER_MISMATCH\"\n // The stored session can't be read yet (the device may be locked).\n | \"SECURE_STORE_UNAVAILABLE\"\n // The session changed while a call that would have stored one was in\n // flight; the newer session stays.\n | \"SESSION_CHANGED\"\n | \"UNKNOWN_ERROR\"\n | (string & {});\n\nexport class AmbaApiError extends Error {\n readonly code: AmbaApiErrorCode;\n readonly details?: unknown;\n constructor(\n code: AmbaApiErrorCode,\n message: string,\n details?: unknown,\n ) {\n super(message);\n this.name = \"AmbaApiError\";\n this.code = code;\n this.details = details;\n // Restore prototype chain when transpiled to ES5 targets — Error\n // subclasses lose their prototype otherwise and `instanceof` breaks.\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/**\n * Best-effort: pull a stable error code out of an `AmbaError` display\n * string. Used by `toAmbaApiError` when the underlying thrown value is a\n * generic `Error` produced by the SDK core.\n *\n * Keep in sync with the SDK core's `AmbaError` format strings — but the\n * parser falls back to UNKNOWN_ERROR for any pattern it doesn't recognize,\n * so a stale match list isn't a footgun, just a quality-of-life regression.\n */\nfunction codeFromMessage(message: string): AmbaApiErrorCode {\n // Cheapest checks first — most production errors are auth or HTTP.\n if (message.startsWith(\"Unauthorized\")) return \"UNAUTHORIZED\";\n if (message.startsWith(\"Forbidden\")) return \"FORBIDDEN\";\n if (message.startsWith(\"Not found\")) return \"NOT_FOUND\";\n if (message.startsWith(\"Conflict\")) return \"CONFLICT\";\n if (message.startsWith(\"Rate limited\")) return \"RATE_LIMITED\";\n if (message.startsWith(\"Validation error\")) return \"VALIDATION_ERROR\";\n if (message.startsWith(\"Network error\")) return \"NETWORK_ERROR\";\n if (message.startsWith(\"HTTP error\")) return \"HTTP_ERROR\";\n if (message.startsWith(\"Circuit breaker\")) return \"CIRCUIT_OPEN\";\n if (message.startsWith(\"Consent not granted\")) return \"CONSENT_NOT_GRANTED\";\n if (message.includes(\"not initialized\")) return \"NOT_INITIALIZED\";\n if (message.startsWith(\"Invalid configuration\")) return \"INVALID_CONFIG\";\n if (message.startsWith(\"Invalid argument\")) return \"INVALID_ARGUMENT\";\n return \"UNKNOWN_ERROR\";\n}\n\n/**\n * Coerce any thrown value into an `AmbaApiError`. Idempotent — passing\n * an existing `AmbaApiError` returns it unchanged so wrappers can stack\n * without re-wrapping.\n *\n * Heuristics:\n * 1. Existing `AmbaApiError` → returned as-is.\n * 2. Object with `.code` → use that code + `.message` + `.details`.\n * 3. `Error` (e.g. runtime-emitted error) → infer code from message prefix.\n * 4. Anything else → stringify and tag UNKNOWN_ERROR.\n */\nexport function toAmbaApiError(err: unknown): AmbaApiError {\n if (err instanceof AmbaApiError) return err;\n if (err && typeof err === \"object\") {\n const obj = err as { code?: unknown; message?: unknown; details?: unknown };\n if (typeof obj.code === \"string\") {\n return new AmbaApiError(\n obj.code,\n typeof obj.message === \"string\" ? obj.message : \"unknown\",\n obj.details,\n );\n }\n }\n if (err instanceof Error) {\n return new AmbaApiError(codeFromMessage(err.message), err.message);\n }\n return new AmbaApiError(\"UNKNOWN_ERROR\", String(err));\n}\n\n/**\n * Wrap a thunk so any thrown value becomes an `AmbaApiError`. The\n * wrapper methods on `Amba` use this to convert runtime-emitted errors\n * uniformly at the SDK boundary.\n */\nexport async function withAmbaError<T>(thunk: () => Promise<T>): Promise<T> {\n try {\n return await thunk();\n } catch (err) {\n throw toAmbaApiError(err);\n }\n}\n","/**\n * Request deadlines, shared by every JS SDK target (the same file ships in\n * `@layers/amba-web`, `@layers/amba-node` and `@layers/amba-react-native`).\n *\n * Every request the SDK sends runs under a deadline that covers sending it\n * and reading the whole response. When the deadline passes, the request is\n * aborted and fails with the SDK's retryable network error, so one request\n * can never hold an in-flight slot (the offline outbox flush, the lazy\n * anonymous sign-in, the session refresh, the event batch flush) for the\n * life of the process. Some runtimes never settle a fetch they gave up on:\n * Expo on Android drops a failed DNS lookup and the promise stays pending.\n * The deadline wins the race even then, because it rejects on its own timer\n * and then aborts the fetch for the runtime to clean up.\n *\n * Two bounds:\n * - `requestTimeoutMs` (default 15 s): an ordinary API request.\n * - `longRequestTimeoutMs` (default 5 min): work that legitimately runs\n * longer. AI generation (`/v1/client/ai/*`), function invocations, and\n * media uploads and downloads.\n * Both grow with the bytes a request moves (see `withTransferAllowance`), so\n * a large upload, download or event batch on a slow link has time to finish.\n * Realtime subscriptions are bounded until their stream opens; AI response\n * streams likewise until the response starts. Once open, a stream runs for\n * as long as the caller keeps it.\n */\n\n/** Default deadline for one API request, in milliseconds. */\nexport const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;\n\n/**\n * Default deadline for AI generation, function invocations and media\n * transfers, in milliseconds.\n */\nexport const DEFAULT_LONG_REQUEST_TIMEOUT_MS = 300_000;\n\n/**\n * The slowest link a deadline makes room for, in bytes per second\n * (8 KiB/s, about 64 kbit/s). Each byte a request sends or receives beyond\n * the first {@link TRANSFER_BYTES_WITHIN_BASE} adds the time it takes at this\n * rate to the deadline.\n */\nexport const MIN_TRANSFER_BYTES_PER_SECOND = 8 * 1024;\n\n/**\n * Bytes a request moves within its base deadline (64 KiB: 8 s at the\n * slowest rate). An ordinary API request stays at exactly\n * `requestTimeoutMs`; only larger bodies widen it.\n */\nexport const TRANSFER_BYTES_WITHIN_BASE = 64 * 1024;\n\n/** The largest delay `setTimeout` honors (about 24.8 days). */\nconst MAX_TIMEOUT_MS = 2_147_483_647;\n\n/** The resolved deadlines one configured SDK instance applies. */\nexport interface RequestDeadlines {\n /** Deadline for an API request, in milliseconds. */\n requestMs: number;\n /**\n * Deadline for AI generation, function invocations and media uploads and\n * downloads, in milliseconds.\n */\n longMs: number;\n}\n\n/** The configure options that set the deadlines. */\nexport interface RequestDeadlineOptions {\n requestTimeoutMs?: number;\n longRequestTimeoutMs?: number;\n}\n\n/**\n * Read the deadlines from the configure options, applying the defaults.\n * Throws an `Error` naming the option when a value is not a positive number\n * of milliseconds.\n */\nexport function resolveRequestDeadlines(\n options: RequestDeadlineOptions,\n): RequestDeadlines {\n return {\n requestMs: readTimeout(\n \"Amba.configure: requestTimeoutMs\",\n options.requestTimeoutMs,\n DEFAULT_REQUEST_TIMEOUT_MS,\n ),\n longMs: readTimeout(\n \"Amba.configure: longRequestTimeoutMs\",\n options.longRequestTimeoutMs,\n DEFAULT_LONG_REQUEST_TIMEOUT_MS,\n ),\n };\n}\n\n/**\n * Validate one timeout value; `undefined` keeps `fallback`. `label` names the\n * option in the thrown `Error` (`Amba.configure: requestTimeoutMs`).\n */\nexport function readTimeout(\n label: string,\n value: unknown,\n fallback: number,\n): number {\n if (value === undefined) return fallback;\n if (\n typeof value !== \"number\" ||\n !Number.isFinite(value) ||\n value <= 0 ||\n value > MAX_TIMEOUT_MS\n ) {\n throw new Error(\n `${label} must be a positive number of milliseconds, at most ${MAX_TIMEOUT_MS}`,\n );\n }\n return value;\n}\n\n/** The text a deadline failure carries, e.g. `request timed out after 15000 ms`. */\nexport function timeoutMessage(timeoutMs: number): string {\n return `request timed out after ${timeoutMs} ms`;\n}\n\n/**\n * `timeoutMs` plus the time the bytes beyond the first\n * {@link TRANSFER_BYTES_WITHIN_BASE} take at\n * {@link MIN_TRANSFER_BYTES_PER_SECOND}, capped at the largest delay\n * `setTimeout` honors. Unknown sizes add nothing.\n */\nexport function withTransferAllowance(\n timeoutMs: number,\n bytes: number | null | undefined,\n): number {\n const beyond =\n typeof bytes === \"number\" && Number.isFinite(bytes)\n ? bytes - TRANSFER_BYTES_WITHIN_BASE\n : 0;\n const extra =\n beyond > 0 ? Math.ceil((beyond / MIN_TRANSFER_BYTES_PER_SECOND) * 1000) : 0;\n return Math.min(MAX_TIMEOUT_MS, timeoutMs + extra);\n}\n\n/**\n * The size of a request body in bytes: a string's UTF-8 length, a buffer's\n * byte length, a `Blob`'s size. `undefined` for no body or a body of unknown\n * size (a stream).\n */\nexport function bodyByteLength(body: unknown): number | undefined {\n if (body === undefined || body === null) return undefined;\n if (typeof body === \"string\") {\n return typeof TextEncoder !== \"undefined\"\n ? new TextEncoder().encode(body).length\n : body.length;\n }\n if (typeof ArrayBuffer !== \"undefined\") {\n if (body instanceof ArrayBuffer) return body.byteLength;\n if (ArrayBuffer.isView(body)) return body.byteLength;\n }\n const size = (body as { size?: unknown }).size;\n return typeof size === \"number\" ? size : undefined;\n}\n\n/** A response's `Content-Length`, when it states one. */\nfunction contentLength(response: Response): number | undefined {\n const raw = response.headers?.get?.(\"content-length\");\n if (!raw) return undefined;\n const n = Number(raw);\n return Number.isFinite(n) && n >= 0 ? n : undefined;\n}\n\n/** The paths under the API base URL whose requests get the long deadline. */\nconst LONG_API_PATH_PREFIXES = [\"/v1/client/ai/\"];\n\n/** Whether a request to API `path` (starting `/v1/...`) gets the long deadline. */\nexport function isLongApiPath(path: string): boolean {\n return LONG_API_PATH_PREFIXES.some((prefix) => path.startsWith(prefix));\n}\n\n/**\n * The deadline for a request to absolute `url`. A URL under `apiBaseUrl`\n * gets the request deadline, or the long one for AI generation. Any other\n * host (a CDN download, an object-storage upload) is a media transfer and\n * gets the long deadline.\n */\nexport function deadlineForUrl(\n url: string,\n apiBaseUrl: string,\n deadlines: RequestDeadlines,\n): number {\n const base = apiBaseUrl.replace(/\\/+$/, \"\");\n if (url !== base && !url.startsWith(`${base}/`) && !url.startsWith(`${base}?`)) {\n return deadlines.longMs;\n }\n return isLongApiPath(url.slice(base.length))\n ? deadlines.longMs\n : deadlines.requestMs;\n}\n\n/**\n * Race `work` against a timer of `ms`. When the timer fires first the race\n * rejects with `onTimeout(ms)`, then `controller` is aborted so the runtime\n * can release the connection. The rejection does not wait for `work`, so a\n * fetch the runtime never settles still releases its caller on time. The\n * timer is cleared as soon as either side settles.\n */\nexport function raceDeadline<T>(\n work: Promise<T>,\n ms: number,\n controller: AbortController | undefined,\n onTimeout: (timeoutMs: number) => unknown,\n): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const deadline = new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n // Reject first so the race settles with the deadline error, then abort.\n reject(onTimeout(ms));\n try {\n controller?.abort();\n } catch {\n // An abort that throws changes nothing: the deadline already won.\n }\n }, ms);\n });\n return Promise.race([work, deadline]).finally(() => {\n clearTimeout(timer);\n });\n}\n\nfunction newController(): AbortController | undefined {\n return typeof AbortController === \"function\"\n ? new AbortController()\n : undefined;\n}\n\n/**\n * Run `work` under a deadline of `timeoutMs`. `work` receives an\n * `AbortSignal` to hand to `fetch` (absent when the runtime has no\n * `AbortController`) and should read everything it needs from the response\n * before resolving, so the deadline covers the body too. Past the deadline\n * the returned promise rejects with `onTimeout(timeoutMs)` and the signal is\n * aborted.\n */\nexport function withDeadline<T>(\n timeoutMs: number,\n work: (signal: AbortSignal | undefined) => Promise<T>,\n onTimeout: (timeoutMs: number) => unknown,\n): Promise<T> {\n const controller = newController();\n let running: Promise<T>;\n try {\n running = work(controller?.signal);\n } catch (err) {\n return Promise.reject(err);\n }\n return raceDeadline(running, timeoutMs, controller, onTimeout);\n}\n\n/** Sizes that widen an {@link exchange}'s deadlines. */\nexport interface ExchangeSizes {\n /** Bytes the request sends (its body). */\n requestBytes?: number;\n /**\n * Bytes the response is expected to carry, when the caller knows (a media\n * asset's size). The response's own `Content-Length` is used otherwise.\n */\n responseBytes?: number;\n /**\n * Set when `send`'s `fetch` resolves only once the whole response body has\n * arrived, as React Native's does. The expected `responseBytes` then widen\n * the send phase, where that runtime spends the download. A `fetch` that\n * resolves at the response headers (browsers, Node) leaves the send phase\n * at the request's own size, so a server that never answers fails on the\n * base deadline.\n */\n fetchBuffersBody?: boolean;\n}\n\n/**\n * Send a request and read its response under deadlines that grow with the\n * bytes involved. Sending, until `send` resolves with the response, gets\n * `timeoutMs` plus the allowance for the request body (and, with\n * `fetchBuffersBody`, for the expected response bytes). Reading,\n * `read(response)`, gets `timeoutMs` plus the allowance for the expected\n * response bytes, else the response's `Content-Length`. One `AbortSignal`\n * covers both phases, so a read past its deadline aborts the transfer too.\n * Past either deadline the promise rejects with `onTimeout(ms)`.\n */\nexport async function exchange<R>(\n timeoutMs: number,\n send: (signal: AbortSignal | undefined) => Promise<Response>,\n read: (response: Response) => Promise<R>,\n onTimeout: (timeoutMs: number) => unknown,\n sizes: ExchangeSizes = {},\n): Promise<R> {\n const controller = newController();\n const sendMs = withTransferAllowance(\n timeoutMs,\n (sizes.requestBytes ?? 0) +\n (sizes.fetchBuffersBody ? (sizes.responseBytes ?? 0) : 0),\n );\n const response = await raceDeadline(\n send(controller?.signal),\n sendMs,\n controller,\n onTimeout,\n );\n const readMs = withTransferAllowance(\n timeoutMs,\n sizes.responseBytes ?? contentLength(response),\n );\n return raceDeadline(read(response), readMs, controller, onTimeout);\n}\n\n/** `init` with `signal` set, when there is one. */\nexport function withSignal(\n init: RequestInit | undefined,\n signal: AbortSignal | undefined,\n): RequestInit | undefined {\n return signal ? { ...init, signal } : init;\n}\n\n/** Statuses whose responses carry no body (`new Response` refuses one). */\nconst NULL_BODY_STATUSES = new Set([101, 204, 205, 304]);\n\n/**\n * Wrap `baseFetch` so every call runs under the deadline `deadlineFor(url)`\n * picks, widened by the request body and the response's `Content-Length`\n * (see {@link exchange}), and resolves with a response whose body is already\n * read in full. For the SDK core, which reads the status, the headers and\n * the whole body of every response. The deadline rejects with an `Error`\n * whose message starts `Network error:`, which the core reports as\n * `NETWORK_ERROR`.\n */\nexport function deadlineFetch(\n baseFetch: typeof fetch,\n deadlineFor: (url: string) => number,\n): typeof fetch {\n return ((input: Parameters<typeof fetch>[0], init?: RequestInit) => {\n const url =\n typeof input === \"string\"\n ? input\n : input instanceof URL\n ? input.href\n : (input as { url: string }).url;\n return exchange(\n deadlineFor(url),\n (signal) => baseFetch(input, withSignal(init, signal)),\n async (response) => {\n const body = NULL_BODY_STATUSES.has(response.status)\n ? null\n : await response.arrayBuffer();\n return new Response(body, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n });\n },\n (ms) => new Error(`Network error: ${timeoutMessage(ms)}`),\n { requestBytes: bodyByteLength(init?.body) },\n );\n }) as typeof fetch;\n}\n","/**\n * Pure-JS HTTP client for the amba API.\n *\n * Built on the platform `fetch` (React Native ships it; Hermes\n * provides it; Expo polyfills it where needed). No transitive\n * runtime deps. The previous implementation tunnelled every call\n * through a compiled SDK core that did the same thing — moving the\n * logic to TS removes ~4 MB from the customer bundle and removes\n * a runtime that's foreign to Hermes.\n *\n * Responsibilities:\n * - Construct the request URL by joining `baseUrl + path` (with an\n * optional query string).\n * - Inject the standard amba auth headers — `X-Api-Key`,\n * `X-SDK-Platform`, `X-SDK-Version`, `Content-Type`,\n * `X-App-Version` / `X-Device-Region` / `X-Device-OS` when known, and\n * `Authorization: Bearer …` when a session token is present.\n * - Map non-2xx responses to typed `AmbaApiError` instances using\n * the same code set the SDK core used (UNAUTHORIZED, FORBIDDEN,\n * NOT_FOUND, CONFLICT, RATE_LIMITED, VALIDATION_ERROR, HTTP_ERROR,\n * NETWORK_ERROR).\n * - Parse JSON response bodies. 204 No Content returns `null`.\n * - Run every request under a deadline (see `request-deadline.ts`). A\n * request still unfinished at its deadline fails with `NETWORK_ERROR`,\n * so a fetch the runtime never settles releases every in-flight guard\n * that waits on it.\n */\n\nimport { AmbaApiError } from \"./error.js\";\nimport type { IdentityStore } from \"./identity.js\";\nimport {\n DEFAULT_LONG_REQUEST_TIMEOUT_MS,\n DEFAULT_REQUEST_TIMEOUT_MS,\n bodyByteLength,\n exchange,\n isLongApiPath,\n timeoutMessage,\n withSignal,\n} from \"./request-deadline.js\";\n\n/** Default amba API endpoint. */\nexport const DEFAULT_BASE_URL = \"https://api.amba.dev\";\n\n/**\n * Server response envelope. Most amba endpoints wrap their payload\n * in `{ data: ... }`. Helpers below let call sites declare whether\n * to return the envelope, the unwrapped `data` field, or the raw\n * body (e.g. `find` returns the whole envelope so consumers can\n * paginate via `next_cursor` / `has_more`).\n */\nexport interface Envelope<T> {\n data: T;\n next_cursor?: string | null;\n has_more?: boolean;\n}\n\nexport interface AmbaHttpClientOptions {\n apiKey: string;\n baseUrl: string;\n sdkPlatform: string;\n sdkVersionHeader: string;\n /** Host app version for `X-App-Version` (already normalized); omitted when unset. */\n appVersion?: string;\n /** Device region for `X-Device-Region` (already normalized); omitted when unset. */\n deviceRegion?: string;\n /** Device OS for `X-Device-OS` (already normalized); omitted when unset. */\n deviceOs?: string;\n identity: IdentityStore;\n /** Override the `fetch` implementation — tests pass a vi.fn() shim. */\n fetchImpl?: typeof fetch;\n /**\n * Deadline for one API request, from sending it to reading the whole\n * response, in milliseconds. Defaults to 15 000.\n */\n requestTimeoutMs?: number;\n /**\n * Deadline for AI generation (`/v1/client/ai/*`), and the default `send`\n * callers pass for function invocations and media transfers, in\n * milliseconds. Defaults to 300 000.\n */\n longRequestTimeoutMs?: number;\n /**\n * Optional hook invoked before a request when no session token is present.\n * Used to lazily establish an anonymous session on the first authenticated\n * call so reads (xp/collections/etc.) don't fail just because the app never\n * called `signInAnonymously()`. The callback receives the request path and\n * MUST early-return for auth/public paths to avoid recursion. It's only\n * consulted while `identity.getSessionToken()` is empty.\n */\n ensureSession?: (path: string) => Promise<void>;\n /**\n * Optional hook invoked when the server refuses the stored session token\n * on an authenticated call (`SESSION_REVOKED`: a link or an inbox proof\n * signed out every earlier session; `INVALID_SESSION_TOKEN`: the token is\n * expired or invalid). It receives the token the call sent and the user\n * it was sent for, and resolves true when a usable session for that same\n * user is stored afterwards; the call is then sent once more with that\n * session. Of the calls under `/v1/client/auth/`, only the ones that send\n * the session in the `Authorization` header reach it (`/me`,\n * `/identifiers`; see `recoversRefusedSession`). Calls that carry their\n * own session token never reach it.\n */\n onSessionRefused?: (\n usedToken: string,\n usedUserId: string | null,\n ) => Promise<boolean>;\n}\n\n/**\n * Server codes for a session token the server no longer accepts.\n * `INVALID_SESSION` is the code the auth routes that verify the session\n * themselves (`/identifiers`, `/link`, `/unlink`) answer with.\n */\nconst SESSION_REFUSED_CODES = new Set([\n \"SESSION_REVOKED\",\n \"INVALID_SESSION_TOKEN\",\n \"INVALID_SESSION\",\n]);\n\n/** True for a server error that refused the session token a call sent. */\nexport function isSessionRefusedError(err: unknown): boolean {\n return err instanceof AmbaApiError && SESSION_REFUSED_CODES.has(err.code);\n}\n\nconst AUTH_PATH_PREFIX = \"/v1/client/auth/\";\n\n/**\n * The auth routes that read the session from the `Authorization` header and\n * mint nothing: a refused session on them recovers like it does on any\n * other authenticated call. The other auth routes stay out of the\n * recovery: `/refresh` is the recovery itself, `/logout` ends the session,\n * the sign-in, sign-up, OTP and magic-link routes take credentials (a 401\n * there is a credential error), and `/link` and `/unlink` carry the session\n * in their body, so their callers recover and rebuild the body themselves.\n */\nconst SESSION_HEADER_AUTH_PATHS: ReadonlySet<string> = new Set([\n `${AUTH_PATH_PREFIX}me`,\n `${AUTH_PATH_PREFIX}identifiers`,\n]);\n\n/** Whether a refused session on `path` goes through `onSessionRefused`. */\nexport function recoversRefusedSession(path: string): boolean {\n return (\n !path.startsWith(AUTH_PATH_PREFIX) || SESSION_HEADER_AUTH_PATHS.has(path)\n );\n}\n\ntype Query = Record<string, string | number | boolean | undefined | null>;\n\nexport class AmbaHttpClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly sdkPlatform: string;\n private readonly sdkVersionHeader: string;\n private readonly appVersion?: string;\n private readonly deviceRegion?: string;\n private readonly deviceOs?: string;\n private readonly identity: IdentityStore;\n private readonly fetchImpl: typeof fetch;\n /** The `fetchImpl` option, when one was passed (`send` resolves fetch per call otherwise). */\n private readonly injectedFetch?: typeof fetch;\n /** Deadline for an API request, in milliseconds. */\n readonly requestTimeoutMs: number;\n /** Deadline for AI generation, function invocations and media transfers. */\n readonly longRequestTimeoutMs: number;\n private readonly ensureSession?: (path: string) => Promise<void>;\n private readonly onSessionRefused?: (\n usedToken: string,\n usedUserId: string | null,\n ) => Promise<boolean>;\n\n constructor(opts: AmbaHttpClientOptions) {\n this.apiKey = opts.apiKey;\n this.baseUrl = opts.baseUrl.replace(/\\/+$/, \"\");\n this.sdkPlatform = opts.sdkPlatform;\n this.sdkVersionHeader = opts.sdkVersionHeader;\n this.appVersion = opts.appVersion;\n this.deviceRegion = opts.deviceRegion;\n this.deviceOs = opts.deviceOs;\n this.identity = opts.identity;\n this.ensureSession = opts.ensureSession;\n this.onSessionRefused = opts.onSessionRefused;\n this.injectedFetch = opts.fetchImpl;\n this.fetchImpl =\n opts.fetchImpl ?? (globalThis.fetch.bind(globalThis) as typeof fetch);\n this.requestTimeoutMs = opts.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n this.longRequestTimeoutMs =\n opts.longRequestTimeoutMs ?? DEFAULT_LONG_REQUEST_TIMEOUT_MS;\n }\n\n /**\n * Send a request to an absolute URL outside the API's JSON envelope (a\n * function invocation, a CDN download, the config route that reads the\n * `ETag`) under a deadline. `read` takes what the caller needs from the\n * response (the body as text, a `Blob`) and runs inside the same\n * deadline, so a body that stops arriving fails too.\n *\n * A transport failure rejects with `NETWORK_ERROR` and the message\n * `<label>: <cause>`; the deadline rejects with `NETWORK_ERROR` and\n * `<label>: request timed out after <ms> ms`. `label` defaults to\n * `Network error` and `timeoutMs` to the request deadline; the deadline\n * widens with the request body and with `responseBytes`, the size the\n * caller expects to download (see `exchange`). An `AmbaApiError` that\n * `read` throws passes through unchanged.\n */\n async send<R>(\n url: string,\n init: RequestInit,\n read: (response: Response) => Promise<R>,\n opts: { timeoutMs?: number; label?: string; responseBytes?: number } = {},\n ): Promise<R> {\n const timeoutMs = opts.timeoutMs ?? this.requestTimeoutMs;\n const label = opts.label ?? \"Network error\";\n // Resolved per call: these direct call sites always used the global\n // fetch at call time.\n const fetchFn = this.injectedFetch ?? globalThis.fetch;\n try {\n return await exchange(\n timeoutMs,\n (signal) => fetchFn(url, withSignal(init, signal)),\n read,\n (ms) => timeoutError(label, ms),\n {\n requestBytes: bodyByteLength(init.body),\n responseBytes: opts.responseBytes,\n // React Native's fetch resolves once the whole body has arrived.\n fetchBuffersBody: true,\n },\n );\n } catch (err) {\n throw asNetworkError(err, label);\n }\n }\n\n /** Build the absolute URL for an API path, optionally with a query string. */\n url(path: string, query?: Query): string {\n const base = this.baseUrl + path;\n if (!query) return base;\n const parts: string[] = [];\n for (const k of Object.keys(query)) {\n const v = query[k];\n if (v === undefined || v === null) continue;\n parts.push(`${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`);\n }\n if (parts.length === 0) return base;\n return `${base}?${parts.join(\"&\")}`;\n }\n\n /** Build the standard request headers for the current identity state. */\n buildHeaders(extra?: Record<string, string>): Record<string, string> {\n const h: Record<string, string> = {\n \"X-Api-Key\": this.apiKey,\n \"X-SDK-Platform\": this.sdkPlatform,\n \"X-SDK-Version\": this.sdkVersionHeader,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n ...extra,\n };\n if (this.appVersion) h[\"X-App-Version\"] = this.appVersion;\n if (this.deviceRegion) h[\"X-Device-Region\"] = this.deviceRegion;\n if (this.deviceOs) h[\"X-Device-OS\"] = this.deviceOs;\n const tok = this.identity.getSessionToken();\n if (tok) h.Authorization = `Bearer ${tok}`;\n return h;\n }\n\n async get<T>(path: string, query?: Query): Promise<T> {\n return this.request<T>(\"GET\", path, undefined, query);\n }\n\n async post<T>(path: string, body?: unknown, query?: Query): Promise<T> {\n return this.request<T>(\"POST\", path, body, query);\n }\n\n /** Send a queued event with the session captured for its original user. */\n async postWithSession<T>(\n path: string,\n body: unknown,\n sessionToken: string,\n ): Promise<T> {\n return this.request<T>(\"POST\", path, body, undefined, sessionToken);\n }\n\n async patch<T>(path: string, body?: unknown): Promise<T> {\n return this.request<T>(\"PATCH\", path, body);\n }\n\n async delete<T>(path: string): Promise<T> {\n return this.request<T>(\"DELETE\", path);\n }\n\n private async request<T>(\n method: string,\n path: string,\n body?: unknown,\n query?: Query,\n sessionToken?: string,\n resent = false,\n ): Promise<T> {\n // Lazily establish an anonymous session on the first authenticated call.\n // The callback is responsible for skipping auth/public paths (so this\n // never recurses through the anonymous sign-in request itself).\n if (\n this.ensureSession &&\n !sessionToken &&\n !this.identity.getSessionToken()\n ) {\n await this.ensureSession(path);\n }\n\n const url = this.url(path, query);\n const headers = this.buildHeaders();\n if (sessionToken) headers.Authorization = `Bearer ${sessionToken}`;\n // The stored session token this call carries, when it carries one, and\n // the user it was sent for.\n const storedToken = sessionToken ? null : this.identity.getSessionToken();\n const storedUserId = storedToken ? this.identity.getAppUserId() : null;\n\n const init: RequestInit = {\n method,\n headers,\n body: body === undefined ? undefined : JSON.stringify(body ?? {}),\n };\n\n // The deadline covers sending the request and reading the whole body,\n // and widens for a large body (an event batch on a slow uplink). AI\n // generation answers when the model finishes, so it gets the long one.\n const timeoutMs = isLongApiPath(path)\n ? this.longRequestTimeoutMs\n : this.requestTimeoutMs;\n let response: Response;\n let text: string;\n try {\n ({ response, text } = await exchange(\n timeoutMs,\n (signal) => this.fetchImpl(url, withSignal(init, signal)),\n async (res) => ({\n response: res,\n // 204 No Content has no body to wait for.\n text: res.status === 204 ? \"\" : await res.text(),\n }),\n (ms) => timeoutError(\"Network error\", ms),\n { requestBytes: bodyByteLength(init.body) },\n ));\n } catch (err) {\n // Transport-level failure (DNS, refused, offline, abort, a body stream\n // that breaks) or the deadline. The platform fetch throws a TypeError\n // or AbortError; we wrap it into the SDK's stable error shape. Both\n // are the retryable NETWORK_ERROR.\n throw asNetworkError(err, \"Network error\");\n }\n\n try {\n return parseResponse<T>(response, text);\n } catch (err) {\n if (\n !resent &&\n storedToken &&\n this.onSessionRefused &&\n recoversRefusedSession(path) &&\n isSessionRefusedError(err) &&\n (await this.recoverSession(storedToken, storedUserId))\n ) {\n return this.request<T>(method, path, body, query, undefined, true);\n }\n throw err;\n }\n }\n\n /** Run the `onSessionRefused` hook; a hook that throws counts as false. */\n private async recoverSession(\n usedToken: string,\n usedUserId: string | null,\n ): Promise<boolean> {\n try {\n return (await this.onSessionRefused?.(usedToken, usedUserId)) === true;\n } catch {\n return false;\n }\n }\n}\n\n/** The `NETWORK_ERROR` a passed deadline raises. */\nfunction timeoutError(label: string, timeoutMs: number): AmbaApiError {\n return new AmbaApiError(\n \"NETWORK_ERROR\",\n `${label}: ${timeoutMessage(timeoutMs)}`,\n { timeoutMs },\n );\n}\n\n/**\n * Wrap a transport failure as `NETWORK_ERROR` (`<label>: <cause>`). An\n * `AmbaApiError` (the deadline's own, or one a `send` reader throws) passes\n * through unchanged.\n */\nfunction asNetworkError(err: unknown, label: string): AmbaApiError {\n if (err instanceof AmbaApiError) return err;\n const message =\n err instanceof Error\n ? err.message\n : typeof err === \"string\"\n ? err\n : \"fetch failed\";\n return new AmbaApiError(\"NETWORK_ERROR\", `${label}: ${message}`, err);\n}\n\nfunction parseResponse<T>(response: Response, text: string): T {\n // 204 No Content: most DELETE endpoints. Return null so generics\n // taking `unknown | null` decode cleanly.\n if (response.status === 204) {\n return null as T;\n }\n\n const ct = response.headers.get(\"content-type\") ?? \"\";\n const isJson = ct.includes(\"application/json\");\n\n if (!response.ok) {\n throw mapHttpError(response, text, isJson);\n }\n\n if (text === \"\") return null as T;\n if (isJson) {\n try {\n return JSON.parse(text) as T;\n } catch (err) {\n throw new AmbaApiError(\n \"HTTP_ERROR\",\n `Failed to parse JSON response: ${err instanceof Error ? err.message : String(err)}`,\n { body: text },\n );\n }\n }\n // Non-JSON success body (uncommon for this API). Return the raw text\n // string typed as the generic so the caller can decide.\n return text as unknown as T;\n}\n\nexport function mapHttpError(\n response: Response,\n rawBody: string,\n isJson: boolean,\n): AmbaApiError {\n let parsed: unknown = undefined;\n let message = rawBody || `HTTP ${response.status}`;\n let code: string | undefined;\n // `envelopeDetails` is the inner `error.details` field — surfaced as\n // `AmbaApiError.details` when the server emitted a typed envelope.\n // This matches the web SDK, which carries the inner\n // details (e.g. `{ streak_key: 'weekly_login' }`) verbatim — NOT the\n // whole `{error:{...}}` wrapper. Customer code like\n // `e.details.streak_key` must work identically on both SDKs.\n // (Cursor BugBot PR #156, medium — cross-SDK details-shape divergence.)\n let envelopeDetails: unknown = undefined;\n let envelopeRecognized = false;\n if (isJson && rawBody) {\n try {\n parsed = JSON.parse(rawBody);\n } catch {\n parsed = undefined;\n }\n // Standard amba envelope: `{ error: { code, message, details } }`\n // and a few legacy `{ error: \"string\" }` shapes. We recognize the\n // envelope by the presence of a string `code` alone — message is\n // optional, matching the Rust `parse_error_envelope` rule so the\n // two SDKs agree on what \"envelope\" means. (Cursor BugBot PR #156.)\n const env = parsed as {\n error?: { code?: string; message?: string; details?: unknown };\n } | null;\n const innerErr = env?.error;\n if (\n innerErr &&\n typeof innerErr === \"object\" &&\n typeof innerErr.code === \"string\"\n ) {\n code = innerErr.code;\n envelopeRecognized = true;\n envelopeDetails = innerErr.details;\n if (typeof innerErr.message === \"string\") message = innerErr.message;\n }\n }\n\n // Use the inner envelope details when the server gave us a\n // recognizable envelope; fall back to the full parsed body\n // otherwise so legacy / proxy-shaped responses still carry\n // something the customer can inspect.\n const details: unknown = envelopeRecognized ? envelopeDetails : parsed;\n\n switch (response.status) {\n case 401:\n return new AmbaApiError(code ?? \"UNAUTHORIZED\", message, details);\n case 403:\n return new AmbaApiError(code ?? \"FORBIDDEN\", message, details);\n case 404:\n return new AmbaApiError(code ?? \"NOT_FOUND\", message, details);\n case 409:\n return new AmbaApiError(code ?? \"CONFLICT\", message, details);\n case 422:\n return new AmbaApiError(code ?? \"VALIDATION_ERROR\", message, details);\n case 429: {\n const retryAfter = response.headers.get(\"retry-after\") ?? \"unspecified\";\n // Prefer the server's structured message if it gave us one; fall back\n // to a synthesized one that surfaces the Retry-After header so callers\n // can decide whether to back off vs surface to the user.\n const serverGaveMessage =\n message !== rawBody && message !== `HTTP ${response.status}`;\n return new AmbaApiError(\n code ?? \"RATE_LIMITED\",\n serverGaveMessage\n ? message\n : `Rate limited (retry after ${retryAfter})`,\n details,\n );\n }\n default:\n return new AmbaApiError(\n code ?? \"HTTP_ERROR\",\n message,\n envelopeRecognized\n ? envelopeDetails\n : (parsed ?? { status: response.status, body: rawBody }),\n );\n }\n}\n","/**\n * Installs a secure `globalThis.crypto.getRandomValues` on React Native, so\n * apps need no polyfill import of their own.\n *\n * Hermes (React Native's default engine) ships no Web Crypto. The SDK\n * depends on `react-native-get-random-values` and loads it the first time it\n * needs random bytes (`Amba.configure()`). That polyfill draws from the\n * platform CSPRNG: `expo-crypto`'s native module when the app has it (every\n * Expo app with expo-crypto, Expo Go included), else its own\n * `RNGetRandomValues` native module (bundled in Expo Go, autolinked in bare\n * React Native and Expo builds).\n *\n * Rules this module keeps:\n *\n * - An existing `crypto.getRandomValues` (a newer Hermes, a polyfill the\n * app imported itself, a browser, Node) is used as it is. Nothing is\n * replaced.\n * - The polyfill is loaded with a LITERAL, synchronous\n * `require(\"react-native-get-random-values\")`. Metro collects a module's\n * dependencies by scanning for `require(\"<string literal>\")` at the call\n * site, so a variable or a rewritten helper would leave the module out of\n * the bundle (the 4.0.4 class of crash). A dynamic `import()` goes\n * through Expo's async-require, and a top-level static import would make\n * plain Node evaluate react-native's Flow source when it loads the SDK.\n * `scripts/pin-react-native-require.mjs` pins the literal in the ESM\n * build after tsdown runs.\n * - The installed function is probed with one byte before the SDK trusts\n * it. When the polyfill's native module is missing from the build, the\n * probe throws, and everything the polyfill added to `globalThis` is\n * removed again: a broken global would hide the missing native module\n * from the app's other libraries and from the SDK's own error message.\n * - Nothing here ever falls back to `Math.random()`.\n */\n\ntype GetRandomValues = (array: Uint8Array) => Uint8Array;\n\ninterface CryptoLike {\n getRandomValues?: GetRandomValues;\n}\n\n/** Loads the polyfill module for its side effect on `globalThis.crypto`. */\nexport type PolyfillLoader = () => void;\n\n/**\n * The production loader. The `require` call must stay a string literal; see\n * the module comment.\n */\nfunction loadGetRandomValuesPolyfill(): void {\n require(\"react-native-get-random-values\");\n}\n\nfunction globalObject(): { crypto?: CryptoLike } {\n return globalThis as unknown as { crypto?: CryptoLike };\n}\n\nfunction currentGetRandomValues(): GetRandomValues | null {\n const crypto = globalObject().crypto;\n const grv = crypto?.getRandomValues;\n return crypto && typeof grv === \"function\" ? grv : null;\n}\n\nfunction probe(grv: GetRandomValues): boolean {\n try {\n const crypto = globalObject().crypto;\n const out = grv.call(crypto, new Uint8Array(1));\n return out instanceof Uint8Array;\n } catch {\n return false;\n }\n}\n\nlet attempted = false;\n\n/**\n * Makes `globalThis.crypto.getRandomValues` available when the runtime can\n * supply a secure one. Returns whether it is available afterwards.\n *\n * The polyfill is loaded at most once per JS runtime. A later call still\n * reports true when the app installs a secure implementation itself.\n */\nexport function ensureSecureRandom(\n load: PolyfillLoader = loadGetRandomValuesPolyfill,\n): boolean {\n if (currentGetRandomValues()) return true;\n if (attempted) return false;\n attempted = true;\n\n const g = globalObject();\n const hadCrypto = Object.prototype.hasOwnProperty.call(g, \"crypto\");\n const priorCrypto = g.crypto;\n\n try {\n load();\n } catch {\n // Plain Node ESM (no `require`), a bundle without the package, or a\n // runtime where the polyfill cannot load. Restore and report below.\n }\n\n const installed = currentGetRandomValues();\n if (installed && probe(installed)) return true;\n\n // The polyfill installed a function whose native source is missing (or\n // threw half-way). Put `globalThis.crypto` back the way it was.\n try {\n if (!hadCrypto) {\n delete g.crypto;\n } else if (g.crypto === priorCrypto && priorCrypto) {\n delete priorCrypto.getRandomValues;\n } else {\n g.crypto = priorCrypto;\n }\n } catch {\n // A non-configurable global stays as the polyfill left it; this call\n // still reports false, so `Amba.configure()` fails with the setup hint.\n }\n return false;\n}\n\n/** Test seam: forget that the polyfill load was attempted. */\nexport function resetSecureRandomForTests(): void {\n attempted = false;\n}\n","/**\n * UUID v4 generator — pure JS, no dependencies.\n *\n * The SDK uses UUIDs for `anonymous_id`. While a user is purely anonymous,\n * that id is the whole credential for their account (presenting it to\n * `/v1/client/auth/anonymous` signs back in), so it must come from a\n * cryptographically secure generator: `globalThis.crypto.getRandomValues`.\n * React Native's Hermes engine does not ship it, so the SDK installs\n * `react-native-get-random-values` (one of its dependencies) on first use;\n * see `./secure-random.ts`. When no secure source can be installed the SDK\n * refuses to generate an id and throws `SecureRandomUnavailableError` from\n * `Amba.configure()`.\n */\n\nimport { ensureSecureRandom } from \"./secure-random.js\";\n\ntype GetRandomValues = (a: Uint8Array) => Uint8Array;\n\n/** Thrown when the runtime has no `crypto.getRandomValues`. */\nexport class SecureRandomUnavailableError extends Error {\n readonly code = \"SECURE_RANDOM_UNAVAILABLE\";\n constructor() {\n super(\n \"Amba needs crypto.getRandomValues to generate identifiers, and this runtime \" +\n \"does not provide it. The SDK loads react-native-get-random-values for you, \" +\n \"and its native module is missing from this build. Add the package to your \" +\n \"app's package.json so the native module is linked (Expo: `npx expo install \" +\n \"react-native-get-random-values`; bare React Native: `npm install \" +\n \"react-native-get-random-values`, then `npx pod-install`) and rebuild the \" +\n \"native app. No import is needed.\",\n );\n this.name = \"SecureRandomUnavailableError\";\n }\n}\n\nfunction secureGetRandomValues(): GetRandomValues {\n ensureSecureRandom();\n const crypto = (globalThis as { crypto?: { getRandomValues?: GetRandomValues } })\n .crypto;\n const grv = crypto?.getRandomValues;\n if (!crypto || typeof grv !== \"function\") {\n throw new SecureRandomUnavailableError();\n }\n // Bind to crypto to preserve `this`.\n return grv.bind(crypto);\n}\n\n/**\n * Throws `SecureRandomUnavailableError` when the runtime cannot generate\n * secure identifiers. `IdentityStore.create` calls it so a missing polyfill\n * surfaces at `configure()`, before any identifier is needed.\n */\nexport function assertSecureRandomAvailable(): void {\n secureGetRandomValues();\n}\n\nfunction getRandomBytes(n: number): Uint8Array {\n const bytes = new Uint8Array(n);\n secureGetRandomValues()(bytes);\n return bytes;\n}\n\n/**\n * Generate a UUID v4 (random) per RFC 4122. Wire shape:\n * `xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx` where `y ∈ {8,9,a,b}`.\n */\nexport function uuidv4(): string {\n const b = getRandomBytes(16);\n // Set version (4) and variant (10xx) bits.\n b[6] = (b[6]! & 0x0f) | 0x40;\n b[8] = (b[8]! & 0x3f) | 0x80;\n const h = Array.from(b, (v) => v.toString(16).padStart(2, \"0\"));\n return (\n h.slice(0, 4).join(\"\") +\n \"-\" +\n h.slice(4, 6).join(\"\") +\n \"-\" +\n h.slice(6, 8).join(\"\") +\n \"-\" +\n h.slice(8, 10).join(\"\") +\n \"-\" +\n h.slice(10, 16).join(\"\")\n );\n}\n","/**\n * Identity store — the minimal on-device state the SDK keeps between\n * launches: `anonymous_id` (per-install UUID), authenticated user id,\n * session token, refresh token, expiry, and whether the current session\n * is purely anonymous.\n *\n * While the user is purely anonymous, `anonymous_id` is the whole\n * credential for their account: presenting it to\n * `/v1/client/auth/anonymous` signs back in. Once the session gains a real\n * identity (any sign-in other than anonymous, or a link), the server stops\n * resuming the account by anonymous id, and `clearSession` rotates the id on\n * sign-out so the next person on the device starts fresh.\n *\n * Persistence: the public part (`anonymous_id`, `app_user_id`,\n * `anonymous_session`) goes to `AsyncStoragePersistence` under\n * `<prefix>:identity_v1` (JSON-encoded).\n * The credential part (tokens + expiry) goes to the {@link TokenVault}\n * chosen by `AmbaConfig.tokenStorage` (see `token-storage.ts`); with no\n * vault (`tokenStorage: \"asyncStorage\"`) it stays in the same AsyncStorage\n * record. On `configure()` we hydrate once; subsequent reads are O(1)\n * in-process.\n *\n * Every write that installs or ends a session advances the store's\n * generation. Writes that act for a session they read earlier (a refresh, a\n * link, the automatic anonymous sign-in, an ended session's cleanup) commit\n * only while the generation is still the one they started from, so a write\n * that lost a race never replaces the newer session (see `CommitFence`).\n *\n * Why a small TS class instead of porting the per-instance-key\n * machinery: the SDK core's race rationale (cross-core write race\n * with `amba_reset`) is specific to its slot model — JS doesn't\n * have that hazard because there's one `Amba` singleton per module\n * graph. Keeping the persistence shape simple makes the AsyncStorage\n * cost predictable and the implementation auditable.\n */\n\nimport { AsyncStoragePersistence } from \"./async-storage-adapter.js\";\nimport type { TokenSlice, TokenVault } from \"./token-storage.js\";\nimport { assertSecureRandomAvailable, uuidv4 } from \"./uuid.js\";\n\n/** Storage key under the configured prefix. Bumped when shape changes. */\nconst IDENTITY_KEY = \"identity_v1\";\n\nexport interface IdentitySnapshot {\n anonymousId: string;\n appUserId: string | null;\n sessionToken: string | null;\n refreshToken: string | null;\n /** ISO-8601 expiry — null means \"no active session.\" */\n expiresAt: string | null;\n /**\n * True when the session came from an anonymous sign-in and never gained an\n * identity; false once it did; null with no session or when unknown (a\n * record written by an SDK before 4.0.14).\n */\n anonymousSession: boolean | null;\n /**\n * True after an identified session ended without a sign-out (the server\n * rejected its refresh token, or the anonymous id now needs the linked\n * sign-in). Calls that need a session fail with `SIGN_IN_REQUIRED` until\n * a sign-in, a link, an explicit anonymous sign-in or a sign-out.\n */\n signInRequired: boolean;\n}\n\n/**\n * When an identity write may commit. `ifGeneration` holds the generation\n * the write started from: the write commits only while it is unchanged.\n * `advance: false` marks a write that keeps the session it acts for (a\n * refresh, a link): it commits under the fence and leaves the generation as\n * it is, so it never cancels a sign-in or sign-out that is in flight.\n */\nexport interface CommitFence {\n ifGeneration?: number;\n advance?: boolean;\n}\n\ninterface PersistedIdentity {\n anonymous_id: string;\n app_user_id: string | null;\n session_token: string | null;\n refresh_token: string | null;\n /** ISO-8601 string; null when not set. */\n expires_at: string | null;\n /** See `IdentitySnapshot.anonymousSession`. */\n anonymous_session: boolean | null;\n /** See `IdentitySnapshot.signInRequired`. False in a record before 4.3.0. */\n sign_in_required: boolean;\n /**\n * Set (never written by this store) on a record whose credential part a\n * storage layer could not read this time: `@layers/amba-expo` keeps the\n * credentials in SecureStore, which can't be read while the device is\n * locked. The record then carries no tokens and no anonymous id, and the\n * store holds its writes back until a read succeeds.\n */\n credentials_unreadable?: boolean;\n /**\n * Set on a record written while the anonymous id is a stand-in for one\n * the unreadable credential part holds (a sign-in or sign-out while the\n * device was locked). The storage layer keeps the anonymous id it holds.\n */\n anonymous_id_unknown?: boolean;\n /**\n * Sequence number of the flush that wrote this record (see `entryBelongs`).\n * 0 for a record written before 4.0.14.\n */\n write_seq: number;\n /**\n * `write_seq` of the vault entry this record vouches for: the entry still\n * holds the session the record describes. Null once the record moved on\n * without the vault (a sign-in or sign-out whose vault write failed or was\n * held back). 0 for a record written before 4.0.14, whose entry carries no\n * sequence number.\n */\n vault_seq: number | null;\n /**\n * Random id of this install, stamped on every vault entry it writes. The\n * iOS Keychain outlives an uninstall; an entry an earlier install wrote\n * carries another id and is never this install's session. Null in a\n * record written before 4.0.14.\n */\n install_id: string | null;\n}\n\n/**\n * Whether a vault entry belongs to this install's record. An entry another\n * install wrote (`install_id`) never does. Each flush writes the vault and\n * then the record, both stamped with the flush's sequence number. An entry\n * of this install newer than the record comes from a flush that stopped\n * between the two writes, so it holds the latest session. Otherwise the\n * entry belongs only when the record vouches for it (`vault_seq`), which a\n * sign-in or sign-out that couldn't write the vault withdraws. An entry\n * written before 4.0.14 belongs while a record from then vouches for it and\n * carries the same anonymous id and user.\n */\nfunction entryBelongs(\n entry: TokenSlice,\n anonymousId: string,\n appUserId: string | null,\n installId: string | null,\n recordSeq: number,\n vaultSeq: number | null,\n): boolean {\n const entrySeq = typeof entry.write_seq === \"number\" ? entry.write_seq : 0;\n if (entrySeq === 0 || entry.install_id == null) {\n // Before 4.0.14 a sign-in whose vault write failed left the earlier\n // user's entry next to a record naming the new user.\n const sameUser =\n entry.app_user_id == null ||\n appUserId === null ||\n entry.app_user_id === appUserId;\n return vaultSeq === 0 && entry.anonymous_id === anonymousId && sameUser;\n }\n if (installId === null || entry.install_id !== installId) return false;\n if (entrySeq > recordSeq) return true;\n return entrySeq === vaultSeq;\n}\n\nfunction seqOf(value: unknown): number | null {\n return typeof value === \"number\" && Number.isInteger(value) && value >= 0\n ? value\n : null;\n}\n\nfunction isExpired(expiresAt: string | null): boolean {\n if (!expiresAt) return false;\n const t = Date.parse(expiresAt);\n if (Number.isNaN(t)) return false;\n return t <= Date.now();\n}\n\n/**\n * The SDK's identity state — held in memory after hydration, persisted\n * to AsyncStorage on every mutation. All methods are sync after\n * `hydrate()` resolves.\n */\nexport class IdentityStore {\n private anonymousId: string;\n private appUserId: string | null = null;\n private sessionToken: string | null = null;\n private refreshToken: string | null = null;\n private expiresAt: string | null = null;\n private anonymousSession: boolean | null = null;\n /**\n * Credentials an earlier SDK version left in the AsyncStorage record, with\n * the user they belong to. They stay on disk until the vault holds the\n * session, so a vault write that fails or is skipped (or a process killed\n * between the two writes) never loses the only stored copy.\n */\n private plaintext: { appUserId: string | null; slice: TokenSlice } | null;\n /**\n * Set while the vault could not be read. Writes are held back until a read\n * succeeds, so a session this launch never loaded is not overwritten.\n */\n private vaultUnreadable: boolean;\n /** Highest flush sequence number seen or used. */\n private seq: number;\n /** `write_seq` of the stored record. */\n private recordSeq: number;\n /** `vault_seq` of the stored record (see `PersistedIdentity`). */\n private vaultSeq: number | null;\n /**\n * The session the vouched vault entry holds, by refresh token and user. A\n * failed or held vault write withdraws the vouch only when the session\n * changed from it, so a link that only moves the anonymous id keeps it.\n */\n private vaultHolds: { refreshToken: string | null; appUserId: string | null };\n /** Serializes flushes, so the vault and the record stay in write order. */\n private flushChain: Promise<void> = Promise.resolve();\n /** This install's id (see `PersistedIdentity.install_id`). */\n private readonly installId: string;\n /** Whether the stored record carries `installId`. */\n private installIdStored: boolean;\n /** See `IdentitySnapshot.signInRequired`. */\n private signInRequired: boolean;\n /**\n * Advanced by every committed write that installs or ends a session (a\n * sign-in, a sign-out, an ended session). A refresh or a link keeps it.\n */\n private generation = 0;\n /** Set by `dispose`: the store commits and persists nothing after it. */\n private disposed = false;\n /**\n * Set while the record's credential part could not be read (see\n * `PersistedIdentity.credentials_unreadable`). Writes wait for a read\n * that succeeds, so a session this launch never loaded is not replaced.\n */\n private recordUnreadable: boolean;\n /**\n * Set while the anonymous id is a stand-in for one the unreadable\n * credential part holds. It is never persisted.\n */\n private temporaryAnonymousId: boolean;\n /** See `onStoredSessionLoaded`. */\n private storedSessionLoaded: (() => void) | null = null;\n /** See `onAnonymousIdKnown`. */\n private anonymousIdKnown: (() => void) | null = null;\n /** The refresh token the store held once created (see `continuesLaunchSession`). */\n private launchRefreshToken: string | null = null;\n /** Sign-ins committed since the store was created. */\n private signInsSinceLaunch = 0;\n\n private constructor(\n private readonly persistence: AsyncStoragePersistence,\n seed: PersistedIdentity,\n private readonly vault: TokenVault | null,\n plaintext: { appUserId: string | null; slice: TokenSlice } | null,\n vaultUnreadable: boolean,\n seq: number,\n recordUnreadable: boolean,\n temporaryAnonymousId: boolean,\n ) {\n this.plaintext = plaintext;\n this.vaultUnreadable = vaultUnreadable;\n this.recordUnreadable = recordUnreadable;\n this.temporaryAnonymousId = temporaryAnonymousId;\n this.signInRequired = seed.sign_in_required;\n this.seq = seq;\n this.installId = seed.install_id ?? uuidv4();\n this.installIdStored = seed.install_id !== null;\n this.recordSeq = seed.write_seq;\n this.vaultSeq = seed.vault_seq;\n this.vaultHolds = {\n refreshToken: seed.refresh_token,\n appUserId: seed.app_user_id,\n };\n this.anonymousId = seed.anonymous_id;\n this.appUserId = seed.app_user_id;\n this.sessionToken = seed.session_token;\n this.refreshToken = seed.refresh_token;\n this.expiresAt = seed.expires_at;\n this.anonymousSession = seed.anonymous_session;\n }\n\n /**\n * Build a store, hydrating any prior identity from AsyncStorage and the\n * credential vault. If no prior identity exists, a fresh `anonymous_id`\n * is minted. Expired session tokens are dropped at hydration (the refresh\n * token survives so callers can `auth.refresh()` to mint a new pair).\n *\n * With a vault, credentials an earlier SDK version left in the\n * AsyncStorage record are used for this launch and moved into the vault;\n * the AsyncStorage copy is removed once the vault write succeeds. A vault\n * holding its own credentials wins over them.\n *\n * Throws `SecureRandomUnavailableError` when the runtime has no\n * `crypto.getRandomValues`: every anonymous id the store mints must come\n * from a secure generator.\n */\n static async create(\n persistence: AsyncStoragePersistence,\n vault: TokenVault | null = null,\n ): Promise<IdentityStore> {\n assertSecureRandomAvailable();\n const raw = await persistence.read(IDENTITY_KEY);\n // The AsyncStorage record is what ties a vault entry to this install.\n // Without it (fresh install, cleared storage) a vault entry that survived\n // (the iOS Keychain outlives an uninstall) belongs to an earlier install\n // and is discarded.\n let recordPresent = false;\n let recordUnreadable = false;\n let temporaryAnonymousId = false;\n let seed: PersistedIdentity;\n if (raw) {\n try {\n const parsed = JSON.parse(raw) as Partial<PersistedIdentity>;\n seed = seedFrom(parsed);\n recordPresent = typeof parsed.anonymous_id === \"string\";\n // A storage layer that could not read the record's credential part\n // this time says so (`@layers/amba-expo` keeps it, the anonymous id\n // included, in SecureStore). The anonymous id is then a stand-in\n // until a read succeeds, and with a vault the entry waits for the\n // record that binds it.\n if (parsed.credentials_unreadable === true) {\n recordUnreadable = true;\n temporaryAnonymousId = !recordPresent;\n }\n } catch {\n // Garbage on disk — start fresh. anonymous_id rotates which is\n // acceptable for a corrupt-state recovery (rare; users on\n // upgrade keep their id because the previous SDK wrote valid JSON).\n seed = freshSeed();\n }\n } else {\n seed = freshSeed();\n }\n\n // Credentials an earlier version wrote into the AsyncStorage record.\n const plaintext =\n vault &&\n (seed.session_token !== null ||\n seed.refresh_token !== null ||\n seed.expires_at !== null)\n ? {\n appUserId: seed.app_user_id,\n slice: {\n session_token: seed.session_token,\n refresh_token: seed.refresh_token,\n expires_at: seed.expires_at,\n },\n }\n : null;\n\n // A vault that can't be read right now (a locked keystore, a native\n // error) must not be overwritten, so writes wait for a successful read.\n let vaultReadable = true;\n let seq = seed.write_seq;\n if (vault && recordUnreadable) {\n vaultReadable = false;\n } else if (vault) {\n let fromVault: TokenSlice | null = null;\n try {\n fromVault = await vault.read();\n } catch {\n vaultReadable = false;\n }\n if (fromVault) seq = Math.max(seq, seqOf(fromVault.write_seq) ?? 0);\n // Use the vault entry only when it belongs to this install's record:\n // the record exists and stands by the entry (`entryBelongs`).\n const belongs =\n fromVault !== null &&\n recordPresent &&\n entryBelongs(\n fromVault,\n seed.anonymous_id,\n seed.app_user_id,\n seed.install_id,\n seed.write_seq,\n seed.vault_seq,\n );\n if (fromVault && belongs) {\n seed.session_token = fromVault.session_token;\n seed.refresh_token = fromVault.refresh_token;\n seed.expires_at = fromVault.expires_at;\n if (fromVault.app_user_id != null) seed.app_user_id = fromVault.app_user_id;\n } else if (fromVault) {\n // An entry the record doesn't stand by (an earlier install's, or one\n // the record withdrew from). The record moves past its sequence\n // number, so the entry never counts as newer later on, even when\n // removing it below fails.\n seed.write_seq = Math.max(\n seed.write_seq,\n seqOf(fromVault.write_seq) ?? 0,\n );\n seed.vault_seq = null;\n }\n }\n // An unreadable vault or record may still hold the session, so a user id\n // without tokens stays while either can't be read (`settleSeed`).\n settleSeed(seed, vaultReadable && !recordUnreadable);\n\n const store = new IdentityStore(\n persistence,\n seed,\n vault,\n plaintext,\n !vaultReadable,\n seq,\n recordUnreadable,\n temporaryAnonymousId,\n );\n // Always persist after hydration — handles the first-run mint of\n // `anonymous_id`, clears any expired tokens from disk, and moves\n // AsyncStorage credentials into the vault.\n await store.flush({ hydrating: true });\n store.launchRefreshToken = store.refreshToken;\n return store;\n }\n\n snapshot(): IdentitySnapshot {\n return {\n anonymousId: this.anonymousId,\n appUserId: this.appUserId,\n sessionToken: this.sessionToken,\n refreshToken: this.refreshToken,\n expiresAt: this.expiresAt,\n anonymousSession: this.anonymousSession,\n signInRequired: this.signInRequired,\n };\n }\n\n /** The current generation (see `CommitFence`). */\n getGeneration(): number {\n return this.generation;\n }\n\n /**\n * Whether the store still holds the session it was created with, renewed\n * or ended since but never replaced by a sign-in: it was created holding\n * `refreshToken`, and no sign-in has committed since.\n */\n continuesLaunchSession(refreshToken: string): boolean {\n return (\n this.launchRefreshToken === refreshToken && this.signInsSinceLaunch === 0\n );\n }\n\n /** See `IdentitySnapshot.signInRequired`. */\n isSignInRequired(): boolean {\n return this.signInRequired;\n }\n\n /**\n * Detach the store (the SDK was configured again). Nothing commits or\n * persists after this; the promise settles once the writes already under\n * way have landed.\n */\n dispose(): Promise<void> {\n this.disposed = true;\n return this.flushChain;\n }\n\n /** Undo `dispose` (a `configure()` that failed before it replaced this store). */\n reattach(): void {\n this.disposed = false;\n }\n\n /** Whether a write under `fence` may commit now. */\n private mayCommit(fence: CommitFence): boolean {\n if (this.disposed) return false;\n return (\n fence.ifGeneration === undefined || fence.ifGeneration === this.generation\n );\n }\n\n /** Commit bookkeeping: advance the generation unless the fence keeps it. */\n private committed(fence: CommitFence): number {\n if (fence.advance !== false) this.generation += 1;\n return this.generation;\n }\n\n /**\n * Called when a stored session that couldn't be read at launch (a locked\n * vault or credential record) is loaded after it, so the SDK can tell its\n * subscribers. A session loaded while the store is created is not\n * reported.\n */\n onStoredSessionLoaded(listener: () => void): void {\n this.storedSessionLoaded = listener;\n }\n\n /**\n * Called when the stand-in anonymous id gives way to the device's own (see\n * `temporaryAnonymousId`): a later read found it, or a sign-in gave one.\n * A sign-out that rotates the id is not reported: it starts over.\n */\n onAnonymousIdKnown(listener: () => void): void {\n this.anonymousIdKnown = listener;\n }\n\n /** Whether the anonymous id is a stand-in (see `temporaryAnonymousId`). */\n hasStandInAnonymousId(): boolean {\n return this.temporaryAnonymousId;\n }\n\n /** The stand-in gives way to a known anonymous id (see `onAnonymousIdKnown`). */\n private anonymousIdNowKnown(): void {\n if (!this.temporaryAnonymousId) return;\n this.temporaryAnonymousId = false;\n if (!this.disposed) this.anonymousIdKnown?.();\n }\n\n /** Report a session a late read loaded (see `onStoredSessionLoaded`). */\n private reportStoredSession(): void {\n // A session loaded late is the one this store launched with, as long as\n // no sign-in came first (see `continuesLaunchSession`).\n if (this.signInsSinceLaunch === 0 && this.launchRefreshToken === null) {\n this.launchRefreshToken = this.refreshToken;\n }\n if (this.disposed) return;\n if (this.sessionToken === null && this.refreshToken === null) return;\n this.storedSessionLoaded?.();\n }\n\n /**\n * Whether the record's credential part still can't be read (see\n * `PersistedIdentity.credentials_unreadable`). When the launch read\n * failed, the record is read again first. A session it holds is loaded\n * when nothing has replaced the launch state since; the stored anonymous\n * id replaces the stand-in either way.\n */\n async recordStillUnreadable(): Promise<boolean> {\n if (!this.recordUnreadable) return false;\n let parsed: Partial<PersistedIdentity> | null = null;\n try {\n const raw = await this.persistence.reread(IDENTITY_KEY);\n parsed = raw ? (JSON.parse(raw) as Partial<PersistedIdentity>) : null;\n } catch {\n return true;\n }\n if (!this.recordUnreadable) return false;\n if (parsed?.credentials_unreadable === true) return true;\n this.recordUnreadable = false;\n const storedAnonymousId =\n parsed && typeof parsed.anonymous_id === \"string\"\n ? parsed.anonymous_id\n : null;\n const untouched =\n this.generation === 0 &&\n this.sessionToken === null &&\n this.refreshToken === null;\n if (parsed && storedAnonymousId !== null && untouched) {\n if (!this.vault) {\n const stored = seedFrom(parsed);\n settleSeed(stored, true);\n this.anonymousId = stored.anonymous_id;\n this.appUserId = stored.app_user_id;\n this.sessionToken = stored.session_token;\n this.refreshToken = stored.refresh_token;\n this.expiresAt = stored.expires_at;\n this.anonymousSession = stored.anonymous_session;\n this.signInRequired = stored.sign_in_required;\n this.seq = Math.max(this.seq, stored.write_seq);\n this.reportStoredSession();\n this.anonymousIdNowKnown();\n return false;\n }\n // With a vault, the record as read now binds the vault entry\n // (`entryBelongs`); the entry is read next, as `create` would have.\n const stored = seedFrom(parsed);\n this.anonymousId = stored.anonymous_id;\n const wasStandIn = this.temporaryAnonymousId;\n this.temporaryAnonymousId = false;\n this.appUserId = stored.app_user_id;\n this.anonymousSession = stored.anonymous_session;\n this.signInRequired = stored.sign_in_required;\n this.recordSeq = stored.write_seq;\n this.vaultSeq = stored.vault_seq;\n this.seq = Math.max(this.seq, stored.write_seq);\n await this.vaultStillUnreadable();\n if (\n this.sessionToken === null &&\n this.refreshToken === null &&\n (stored.session_token !== null || stored.refresh_token !== null)\n ) {\n // Credentials an earlier setup left in the record: used now and\n // moved into the vault by the next write, as at launch.\n this.plaintext = {\n appUserId: stored.app_user_id,\n slice: {\n session_token: stored.session_token,\n refresh_token: stored.refresh_token,\n expires_at: stored.expires_at,\n },\n };\n const expired = isExpired(stored.expires_at);\n this.sessionToken = expired ? null : stored.session_token;\n this.refreshToken = stored.refresh_token;\n this.expiresAt = expired ? null : stored.expires_at;\n this.reportStoredSession();\n }\n if (\n !this.vaultUnreadable &&\n this.sessionToken === null &&\n this.refreshToken === null\n ) {\n // No entry for this record: nobody is signed in (`settleSeed`).\n if (this.appUserId !== null && this.anonymousSession === false) {\n this.anonymousId = uuidv4();\n }\n this.appUserId = null;\n this.anonymousSession = null;\n }\n if (wasStandIn && !this.disposed) this.anonymousIdKnown?.();\n return false;\n }\n if (this.temporaryAnonymousId && storedAnonymousId !== null) {\n this.anonymousId = storedAnonymousId;\n }\n this.anonymousIdNowKnown();\n return false;\n }\n\n /**\n * Whether the stored session can't be read right now: the record's\n * credential part (see `recordStillUnreadable`) or the vault (see\n * `vaultStillUnreadable`). The record is read first: it binds the entry.\n */\n async credentialsStillUnreadable(): Promise<boolean> {\n if (await this.recordStillUnreadable()) return true;\n return this.vaultStillUnreadable();\n }\n\n /**\n * Whether the stored credentials were unreadable at the last attempt. A\n * call that needs a session reports `SECURE_STORE_UNAVAILABLE` then.\n */\n credentialsUnknown(): boolean {\n return this.recordUnreadable || this.vaultUnreadable;\n }\n\n /**\n * Whether the anonymous id is still a stand-in after the record is read\n * again (see `temporaryAnonymousId`). Anything sent under it would belong\n * to nobody.\n */\n async anonymousIdStillUnknown(): Promise<boolean> {\n if (!this.temporaryAnonymousId) return false;\n await this.recordStillUnreadable();\n return this.temporaryAnonymousId;\n }\n\n /**\n * Whether the vault still can't be read. When the launch read failed, the\n * vault is read again first, and a session it holds for this install is\n * loaded (same rule as `create`). While it can't be read, the stored\n * session is unknown: callers must not replace it with a new user.\n */\n async vaultStillUnreadable(): Promise<boolean> {\n if (!this.vault || !this.vaultUnreadable) return false;\n // The record binds the entry; it is read first.\n if (this.recordUnreadable) return true;\n let stored: TokenSlice | null;\n try {\n stored = await this.vault.read();\n } catch {\n return true;\n }\n this.vaultUnreadable = false;\n this.noteStoredSeq(stored);\n if (this.sessionToken === null && this.refreshToken === null) {\n this.adoptStored(stored);\n this.reportStoredSession();\n }\n return false;\n }\n\n /** Keep the next sequence number above an entry read from the vault. */\n private noteStoredSeq(stored: TokenSlice | null): void {\n if (stored) this.seq = Math.max(this.seq, seqOf(stored.write_seq) ?? 0);\n }\n\n /** Load a stored vault session that belongs to this install. */\n private adoptStored(stored: TokenSlice | null): void {\n if (\n !stored ||\n (stored.session_token === null && stored.refresh_token === null) ||\n // The record as it stands now: a sign-in or sign-out since launch has\n // withdrawn its vouch for an entry it never saw.\n !entryBelongs(\n stored,\n this.anonymousId,\n this.appUserId,\n this.installIdStored ? this.installId : null,\n this.recordSeq,\n this.vaultSeq,\n )\n ) {\n return;\n }\n this.sessionToken = isExpired(stored.expires_at)\n ? null\n : stored.session_token;\n this.refreshToken = stored.refresh_token;\n this.expiresAt = isExpired(stored.expires_at) ? null : stored.expires_at;\n if (stored.app_user_id != null) this.appUserId = stored.app_user_id;\n this.vaultHolds = {\n refreshToken: this.refreshToken,\n appUserId: this.appUserId,\n };\n }\n\n getAnonymousId(): string {\n return this.anonymousId;\n }\n\n getAppUserId(): string | null {\n return this.appUserId;\n }\n\n getSessionToken(): string | null {\n return this.sessionToken;\n }\n\n getRefreshToken(): string | null {\n return this.refreshToken;\n }\n\n isAuthenticated(): boolean {\n return this.sessionToken !== null;\n }\n\n /**\n * Store a session. `anonymous` records whether it is a purely anonymous\n * one (true only for `/v1/client/auth/anonymous`); omit it to keep the\n * current value (a token refresh of the same session). A stored session\n * ends a pending `signInRequired`.\n *\n * Resolves the generation the write committed at, or null when the fence\n * refused it (nothing changed).\n */\n async setSession(\n args: {\n appUserId: string;\n sessionToken: string;\n refreshToken: string;\n expiresAt: string;\n anonymous?: boolean;\n },\n fence: CommitFence = {},\n ): Promise<number | null> {\n if (!this.mayCommit(fence)) return null;\n this.appUserId = args.appUserId;\n this.sessionToken = args.sessionToken;\n this.refreshToken = args.refreshToken;\n this.expiresAt = args.expiresAt;\n if (args.anonymous !== undefined) this.anonymousSession = args.anonymous;\n this.signInRequired = false;\n if (fence.advance !== false) this.signInsSinceLaunch += 1;\n const generation = this.committed(fence);\n await this.flush();\n return generation;\n }\n\n /**\n * The current session gained an identity (a link). False when the fence\n * refused the write.\n */\n async markIdentified(fence: CommitFence = {}): Promise<boolean> {\n if (!this.mayCommit(fence)) return false;\n if (this.anonymousSession === false) return true;\n this.anonymousSession = false;\n await this.flush();\n return true;\n }\n\n /**\n * Adopt the anonymous id the server holds for this install's user: the id\n * an anonymous sign-in answered with, or the fresh id a link gave the user.\n * False when the fence refused the write.\n */\n async setAnonymousId(\n anonymousId: string,\n fence: CommitFence = {},\n ): Promise<boolean> {\n if (!this.mayCommit(fence)) return false;\n if (anonymousId !== this.anonymousId) {\n this.anonymousId = anonymousId;\n this.anonymousIdNowKnown();\n await this.flush();\n return true;\n }\n this.anonymousIdNowKnown();\n return true;\n }\n\n /**\n * End the session. The anonymous id is rotated when `rotateAnonymousId`\n * is set, and whenever the session being ended was not purely anonymous:\n * that id then identifies an account with a real sign-in, and the next\n * person on the device must start fresh. A session of unknown kind (stored\n * by an SDK before 4.0.14) counts as identified, unless\n * `keepIdWhenKindUnknown` is set (the server decides on the next\n * anonymous sign-in).\n */\n async clearSession(\n rotateAnonymousId: boolean,\n opts: {\n keepIdWhenKindUnknown?: boolean;\n /**\n * Record that the user must sign in again (an identified session\n * ended without a sign-out). Any other clear ends a pending one.\n */\n signInRequired?: boolean;\n /** Commit only while the generation is unchanged (see `CommitFence`). */\n ifGeneration?: number;\n /**\n * A sign-out that started at this generation: it clears the session\n * unless a sign-in stored a newer one since. A session that merely\n * ended meanwhile is cleared like any other.\n */\n unlessSignedInSince?: number;\n } = {},\n ): Promise<boolean> {\n if (!this.mayCommit({ ifGeneration: opts.ifGeneration })) return false;\n const hadSession = this.sessionToken !== null || this.refreshToken !== null;\n // A stored session this launch couldn't read (a locked device) may be\n // signed in; it counts as a session for the anonymous id's rotation.\n const unreadSession = this.credentialsUnknown() && this.appUserId !== null;\n if (\n opts.unlessSignedInSince !== undefined &&\n opts.unlessSignedInSince !== this.generation &&\n hadSession\n ) {\n return false;\n }\n const identified =\n this.anonymousSession === false ||\n (this.anonymousSession === null && !opts.keepIdWhenKindUnknown);\n const rotate =\n rotateAnonymousId || ((hadSession || unreadSession) && identified);\n const nextAnonymousId = rotate ? uuidv4() : this.anonymousId;\n if (rotate) this.temporaryAnonymousId = false;\n this.appUserId = null;\n this.sessionToken = null;\n this.refreshToken = null;\n this.expiresAt = null;\n this.anonymousSession = null;\n this.signInRequired = opts.signInRequired === true;\n this.anonymousId = nextAnonymousId;\n this.committed({});\n await this.flush({ signingOut: true });\n return true;\n }\n\n /**\n * Persist the identity. `hydrating` marks the write that follows `create`:\n * when the vault was unreadable and a retry now succeeds, the session it\n * holds is adopted instead of overwritten. `signingOut` marks the write\n * from `clearSession`: when the vault can't be emptied, the install moves\n * to a new anonymous id so the entry left behind no longer belongs to it.\n */\n private flush(opts?: {\n hydrating?: boolean;\n signingOut?: boolean;\n }): Promise<void> {\n // A write queued before `dispose` still lands; none is queued after it.\n if (this.disposed) return Promise.resolve();\n const run = this.flushChain.then(() => this.flushNow(opts));\n this.flushChain = run.catch(() => undefined);\n return run;\n }\n\n private async flushNow(opts?: {\n hydrating?: boolean;\n signingOut?: boolean;\n }): Promise<void> {\n // A record whose credentials couldn't be read is read again first. Until\n // it can be, the launch state (which lacks them) never overwrites it. A\n // sign-in or sign-out since launch replaces what was stored and is\n // written, its stand-in anonymous id marked so the storage layer keeps\n // the one it holds (`anonymous_id_unknown`).\n if ((await this.recordStillUnreadable()) && this.generation === 0) return;\n if (!this.vault) {\n const seq = ++this.seq;\n const recordSeq = opts?.hydrating ? this.recordSeq : seq;\n const payload: PersistedIdentity = {\n anonymous_id: this.anonymousId,\n app_user_id: this.appUserId,\n session_token: this.sessionToken,\n refresh_token: this.refreshToken,\n expires_at: this.expiresAt,\n anonymous_session: this.anonymousSession,\n sign_in_required: this.signInRequired,\n write_seq: recordSeq,\n vault_seq: null,\n install_id: this.installId,\n ...(this.temporaryAnonymousId ? { anonymous_id_unknown: true } : {}),\n };\n await this.persistence.write(IDENTITY_KEY, JSON.stringify(payload));\n this.recordSeq = recordSeq;\n this.installIdStored = true;\n return;\n }\n // The vault is written first. New credentials never reach AsyncStorage\n // while a vault is in use; the only tokens AsyncStorage may still hold\n // are the ones an earlier version wrote, and they go once the vault has\n // the session.\n if (this.vaultUnreadable) {\n // Retry the read; until it succeeds, the stored entry stays untouched.\n try {\n const stored = await this.vault.read();\n this.vaultUnreadable = false;\n this.noteStoredSeq(stored);\n // At hydration nothing has replaced the stored session yet: load it.\n // Later writes (sign-in, refresh, sign-out) are newer and win.\n // Same rule as `create` (`entryBelongs`).\n if (opts?.hydrating) this.adoptStored(stored);\n } catch {\n // Still unreadable: keep holding the write back.\n }\n }\n const seq = ++this.seq;\n // A sign-out withdraws the record's backing before it touches the vault,\n // so a removal that fails followed by a lost record write still leaves a\n // record that stands by nothing.\n if (opts?.signingOut) {\n this.vaultSeq = null;\n try {\n await this.persistence.write(\n IDENTITY_KEY,\n JSON.stringify(this.recordFor(seq, false)),\n );\n this.recordSeq = seq;\n this.installIdStored = true;\n } catch {\n // The record write below tries again.\n }\n }\n // The record learns the install id before any vault entry carries it, so\n // an entry this install wrote always names an id its record knows. Only\n // the hydrating write does this: it changes no session, so the record it\n // writes early describes the same session as the vault, and when that\n // write fails the vault write waits for a later flush.\n let holdVaultWrite = false;\n if (!this.installIdStored && opts?.hydrating && !this.vaultUnreadable) {\n try {\n await this.persistence.write(\n IDENTITY_KEY,\n JSON.stringify(this.recordFor(this.recordSeq, false)),\n );\n this.installIdStored = true;\n } catch {\n holdVaultWrite = true;\n }\n }\n // The slice is taken now: a sign-in that lands while the write is out\n // changes the live fields, and the record must describe what the vault\n // actually took.\n const slice: TokenSlice = {\n session_token: this.sessionToken,\n refresh_token: this.refreshToken,\n expires_at: this.expiresAt,\n anonymous_id: this.anonymousId,\n app_user_id: this.appUserId,\n write_seq: seq,\n install_id: this.installId,\n };\n let saved = false;\n if (!this.vaultUnreadable && !holdVaultWrite) {\n try {\n await this.vault.write(slice);\n saved = true;\n this.vaultSeq = seq;\n this.vaultHolds = {\n refreshToken: slice.refresh_token,\n appUserId: slice.app_user_id ?? null,\n };\n } catch (err) {\n // The session stays live in memory; only its survival across a\n // restart depends on the next successful write. Failing the sign-in\n // over a keystore error would be worse for the user.\n if (typeof console !== \"undefined\") {\n console.warn(\"[amba] could not save the session to the secure store\", err);\n }\n }\n }\n // The record stands by the vault entry only while that entry holds the\n // session the record describes (`entryBelongs`): a session the vault\n // didn't take (a failed or held write, or one that changed while the\n // write was out) withdraws it, and so does a sign-out that couldn't\n // empty the vault (unreadable keystore, a failed or refused removal). A\n // new anonymous id then also leaves that entry unbound, so the next\n // launch discards it. A write that only moves the anonymous id (a link)\n // keeps it.\n const vaultHasLive =\n this.vaultHolds.refreshToken === this.refreshToken &&\n this.vaultHolds.appUserId === this.appUserId;\n if ((opts?.signingOut && !saved) || !vaultHasLive) {\n this.vaultSeq = null;\n }\n if (opts?.signingOut && !saved) {\n this.anonymousId = uuidv4();\n if (typeof console !== \"undefined\") {\n console.warn(\n \"[amba] could not clear the secure store at sign-out; starting a new anonymous id so the stored session can't be restored\",\n );\n }\n }\n // A hydrating write changes no session. The record keeps its sequence\n // number unless the vault took this write, so an entry a stopped flush\n // left newer than the record stays the newest, and a record in step\n // with its entry stays in step.\n const recordSeq = opts?.hydrating && !saved ? this.recordSeq : seq;\n const record = this.recordFor(recordSeq, saved && vaultHasLive);\n await this.persistence.write(IDENTITY_KEY, JSON.stringify(record));\n this.recordSeq = recordSeq;\n this.installIdStored = true;\n if (record.refresh_token === null) this.plaintext = null;\n }\n\n /**\n * The AsyncStorage record for a vault in use. It keeps the earlier\n * version's plaintext copy only while that copy is still the live session\n * (same user, same refresh token) and the vault doesn't hold it yet\n * (`saved`). Sign-out, another user or a refresh (which retires the old\n * refresh token) drops it.\n */\n private recordFor(recordSeq: number, saved: boolean): PersistedIdentity {\n const keep =\n !saved &&\n this.plaintext !== null &&\n this.refreshToken !== null &&\n this.plaintext.appUserId === this.appUserId &&\n this.plaintext.slice.refresh_token === this.refreshToken\n ? this.plaintext.slice\n : null;\n return {\n anonymous_id: this.anonymousId,\n app_user_id: this.appUserId,\n session_token: keep?.session_token ?? null,\n refresh_token: keep?.refresh_token ?? null,\n expires_at: keep?.expires_at ?? null,\n anonymous_session: this.anonymousSession,\n sign_in_required: this.signInRequired,\n write_seq: recordSeq,\n vault_seq: this.vaultSeq,\n install_id: this.installId,\n ...(this.temporaryAnonymousId ? { anonymous_id_unknown: true } : {}),\n };\n }\n}\n\nfunction freshSeed(): PersistedIdentity {\n return {\n anonymous_id: uuidv4(),\n app_user_id: null,\n session_token: null,\n refresh_token: null,\n expires_at: null,\n anonymous_session: null,\n sign_in_required: false,\n write_seq: 0,\n vault_seq: null,\n install_id: null,\n };\n}\n\n/** The identity a parsed record describes, with defaults for missing fields. */\nfunction seedFrom(parsed: Partial<PersistedIdentity>): PersistedIdentity {\n return {\n anonymous_id: parsed.anonymous_id ?? uuidv4(),\n app_user_id: parsed.app_user_id ?? null,\n session_token: parsed.session_token ?? null,\n refresh_token: parsed.refresh_token ?? null,\n expires_at: parsed.expires_at ?? null,\n anonymous_session:\n typeof parsed.anonymous_session === \"boolean\"\n ? parsed.anonymous_session\n : null,\n sign_in_required: parsed.sign_in_required === true,\n write_seq: seqOf(parsed.write_seq) ?? 0,\n // A record from before 4.0.14 has no `vault_seq` and vouches for\n // the entry of its time (0).\n vault_seq: parsed.vault_seq === undefined ? 0 : seqOf(parsed.vault_seq),\n install_id: typeof parsed.install_id === \"string\" ? parsed.install_id : null,\n };\n}\n\n/**\n * Settle a hydrated identity. With the credentials known (`readable`), a\n * user id with no token to act on is stale (memory storage after a\n * relaunch, a vault entry that was discarded): nobody is signed in, and the\n * session kind goes with it. A session that ended this way after it gained\n * an identity rotates the anonymous id, as `clearSession` would. An expired\n * session token is dropped; its refresh token stays so the session can be\n * renewed.\n */\nfunction settleSeed(seed: PersistedIdentity, readable: boolean): void {\n if (readable && seed.session_token === null && seed.refresh_token === null) {\n if (seed.app_user_id !== null && seed.anonymous_session === false) {\n seed.anonymous_id = uuidv4();\n }\n seed.app_user_id = null;\n seed.anonymous_session = null;\n }\n if (isExpired(seed.expires_at)) {\n seed.session_token = null;\n seed.expires_at = null;\n }\n}\n","/**\n * Where the SDK keeps the session token, refresh token and expiry between\n * launches.\n *\n * The identity record has two parts. The public part (`anonymous_id`,\n * `app_user_id`) always lives in AsyncStorage. The credential part goes\n * wherever `AmbaConfig.tokenStorage` says:\n *\n * - `\"secure\"` (default): a {@link TokenVault} backed by the platform\n * keystore. The SDK uses `AmbaConfig.secureStorage` when the app supplies\n * one, otherwise `react-native-keychain` when it is installed (iOS\n * Keychain, Android Keystore). With neither available the credentials stay\n * in memory, and the SDK logs one warning.\n * - `\"memory\"`: credentials stay in memory. Every launch starts signed out.\n * - `\"asyncStorage\"`: credentials go into the AsyncStorage record next to\n * the public part, as plaintext in the app sandbox. Opt in only when a\n * lost session on restart is worse than that exposure for your app.\n */\n\n/**\n * A key/value store backed by the platform keystore. Values are small JSON\n * strings (well under 2 KB). Pass one through `AmbaConfig.secureStorage` to\n * use a secure-storage module other than `react-native-keychain`.\n */\nexport interface SecureTokenStorage {\n getItem(key: string): Promise<string | null>;\n setItem(key: string, value: string): Promise<void>;\n removeItem(key: string): Promise<void>;\n}\n\n/** `AmbaConfig.tokenStorage` values. */\nexport type TokenStorageMode = \"secure\" | \"memory\" | \"asyncStorage\";\n\nexport const TOKEN_STORAGE_MODES: readonly TokenStorageMode[] = [\n \"secure\",\n \"memory\",\n \"asyncStorage\",\n];\n\n/**\n * The credential part of the identity record. `install_id` and `write_seq`\n * (the sequence number of the flush that wrote it) tie a stored entry to\n * the install's record; an entry from before 4.0.14 binds by `anonymous_id`.\n * `app_user_id` names the user it signs in.\n */\nexport interface TokenSlice {\n session_token: string | null;\n refresh_token: string | null;\n expires_at: string | null;\n anonymous_id?: string | null;\n app_user_id?: string | null;\n write_seq?: number | null;\n install_id?: string | null;\n}\n\n/**\n * Holds the credential part of the identity record. `read()` resolves null\n * when nothing is stored; `write()` with every field null removes the entry.\n */\nexport interface TokenVault {\n readonly kind: \"secure\" | \"memory\";\n read(): Promise<TokenSlice | null>;\n write(slice: TokenSlice): Promise<void>;\n}\n\nfunction isEmpty(slice: TokenSlice): boolean {\n return (\n slice.session_token === null &&\n slice.refresh_token === null &&\n slice.expires_at === null\n );\n}\n\n/**\n * Memory-held credentials, keyed like the keystore entry. They last as long\n * as the JS runtime, so a second `Amba.configure()` (Fast Refresh, a\n * re-configure) keeps the session, and a relaunch starts signed out.\n */\nconst memorySlices = new Map<string, TokenSlice>();\n\n/** Credentials held in this JS runtime only. */\nexport class MemoryTokenVault implements TokenVault {\n readonly kind = \"memory\" as const;\n\n constructor(private readonly key: string) {}\n\n async read(): Promise<TokenSlice | null> {\n const slice = memorySlices.get(this.key);\n return slice ? { ...slice } : null;\n }\n\n async write(slice: TokenSlice): Promise<void> {\n if (isEmpty(slice)) memorySlices.delete(this.key);\n else memorySlices.set(this.key, { ...slice });\n }\n}\n\n/**\n * Keystore key for a storage prefix. Kept to `[A-Za-z0-9_.-]` because some\n * secure stores (for example `expo-secure-store`) reject other characters.\n */\nexport function secureTokenKey(prefix: string): string {\n return `${prefix.replace(/[^A-Za-z0-9_.-]/g, \"_\")}_session_tokens_v1`;\n}\n\n/** Credentials in a keystore-backed {@link SecureTokenStorage}. */\nexport class SecureTokenVault implements TokenVault {\n readonly kind = \"secure\" as const;\n\n constructor(\n private readonly storage: SecureTokenStorage,\n private readonly key: string,\n ) {}\n\n async read(): Promise<TokenSlice | null> {\n const raw = await this.storage.getItem(this.key);\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw) as Partial<TokenSlice>;\n return {\n session_token: parsed.session_token ?? null,\n refresh_token: parsed.refresh_token ?? null,\n expires_at: parsed.expires_at ?? null,\n anonymous_id: parsed.anonymous_id ?? null,\n app_user_id: parsed.app_user_id ?? null,\n write_seq: parsed.write_seq ?? null,\n install_id: parsed.install_id ?? null,\n };\n } catch {\n return null;\n }\n }\n\n async write(slice: TokenSlice): Promise<void> {\n if (isEmpty(slice)) {\n try {\n await this.storage.removeItem(this.key);\n } catch (err) {\n // A removal that fails leaves the session behind. An entry that\n // holds no tokens takes its place, so nothing signs back in from\n // it; the write fails only when that is refused too.\n try {\n await this.storage.setItem(this.key, JSON.stringify(slice));\n } catch {\n throw err;\n }\n }\n return;\n }\n await this.storage.setItem(this.key, JSON.stringify(slice));\n }\n}\n\n/** The slice of `react-native-keychain` the SDK calls. */\ninterface KeychainLike {\n setGenericPassword(\n username: string,\n password: string,\n options?: { service?: string; accessible?: unknown },\n ): Promise<unknown>;\n getGenericPassword(options?: {\n service?: string;\n }): Promise<false | { password: string }>;\n resetGenericPassword(options?: { service?: string }): Promise<unknown>;\n ACCESSIBLE?: { AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY?: unknown };\n}\n\nfunction isKeychainLike(mod: unknown): mod is KeychainLike {\n const k = mod as Partial<KeychainLike> | null;\n return (\n typeof k === \"object\" &&\n k !== null &&\n typeof k.setGenericPassword === \"function\" &&\n typeof k.getGenericPassword === \"function\" &&\n typeof k.resetGenericPassword === \"function\"\n );\n}\n\n/**\n * Adapt `react-native-keychain` to {@link SecureTokenStorage}. Items use the\n * `AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY` class on iOS: readable after the\n * first unlock (so background refreshes work) and left behind by backups\n * restored onto another device.\n */\nexport function keychainStorage(keychain: KeychainLike): SecureTokenStorage {\n const accessible = keychain.ACCESSIBLE?.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY;\n return {\n async getItem(key) {\n const entry = await keychain.getGenericPassword({ service: key });\n return entry ? entry.password : null;\n },\n async setItem(key, value) {\n const ok = await keychain.setGenericPassword(\"amba\", value, {\n service: key,\n ...(accessible !== undefined ? { accessible } : {}),\n });\n if (ok === false) {\n throw new Error(\"react-native-keychain refused to store the session\");\n }\n },\n async removeItem(key) {\n const removed = await keychain.resetGenericPassword({ service: key });\n // `false` can also mean there was nothing to remove; only a remaining\n // entry is a failure.\n if (removed === false && (await keychain.getGenericPassword({ service: key }))) {\n throw new Error(\"react-native-keychain could not remove the session\");\n }\n },\n };\n}\n\n// Memoized probe: `undefined` = not probed yet, `null` = not installed.\nlet cachedKeychain: SecureTokenStorage | null | undefined;\n\n/**\n * Load `react-native-keychain` when the app has it installed. Resolves null\n * otherwise (module missing, or a build without its native half).\n */\nexport async function loadKeychainStorage(): Promise<SecureTokenStorage | null> {\n if (cachedKeychain !== undefined) return cachedKeychain;\n try {\n // Literal specifier inside try: bundlers treat it as an optional\n // dependency, so apps without the module still build.\n const mod = (await import(\"react-native-keychain\")) as unknown as {\n default?: unknown;\n };\n const candidate = isKeychainLike(mod) ? mod : mod.default;\n cachedKeychain = isKeychainLike(candidate) ? keychainStorage(candidate) : null;\n } catch {\n cachedKeychain = null;\n }\n return cachedKeychain;\n}\n\n/** Test-only: forget the memoized keychain probe. */\nexport function _resetKeychainProbeForTests(): void {\n cachedKeychain = undefined;\n}\n\nlet memoryFallbackWarned = false;\n\n/**\n * Pick the vault for `mode`. Resolves null for `\"asyncStorage\"`, which keeps\n * the credentials inside the AsyncStorage identity record.\n */\nexport async function resolveTokenVault(\n mode: TokenStorageMode,\n prefix: string,\n secureStorage: SecureTokenStorage | undefined,\n): Promise<TokenVault | null> {\n if (mode === \"asyncStorage\") return null;\n const key = secureTokenKey(prefix);\n if (mode === \"memory\") return new MemoryTokenVault(key);\n const storage = secureStorage ?? (await loadKeychainStorage());\n if (storage) return new SecureTokenVault(storage, key);\n if (!memoryFallbackWarned && typeof console !== \"undefined\") {\n memoryFallbackWarned = true;\n console.warn(\n \"[amba] No secure token store found, so the session is kept in memory and users sign in again after every launch. \" +\n \"Install react-native-keychain (or pass `secureStorage` to Amba.configure) to keep sessions across launches in the Keychain/Keystore.\",\n );\n }\n return new MemoryTokenVault(key);\n}\n","import { AmbaApiError } from \"./error.js\";\n\n/**\n * Encode one caller-supplied id, key, slug or name as a single URL path\n * segment. `encodeURIComponent` escapes `/`, `?` and `#`, so the value stays\n * inside its segment. `.` and `..` are refused because URL resolution treats\n * them as steps to another route, and the empty string is refused because it\n * collapses onto the parent route.\n */\nexport function pathSegment(value: string): string {\n if (value === \"\" || value === \".\" || value === \"..\") {\n throw new AmbaApiError(\n \"VALIDATION_ERROR\",\n `path segment must not be ${value === \"\" ? \"empty\" : `'${value}'`}`,\n );\n }\n return encodeURIComponent(value);\n}\n","/**\n * Offline mutation queue + optimistic-update helpers — framework-agnostic\n * orchestration that sits ABOVE the collection / event write methods.\n *\n * Nothing here touches the SDK runtime directly: the queue is handed a\n * small `KvStore` (durable key/value persistence) and a `WriteExecutor`\n * (the function that actually performs a queued mutation against the API).\n * That keeps the same code identical across every JS SDK target — only the\n * adapters that supply storage + the executor differ per platform.\n *\n * Design:\n * - A write that fails because the device is offline (a transport-level\n * network error, or `navigator.onLine === false`) is appended to a\n * durable outbox instead of throwing all the way up.\n * - Each queued op carries a stable idempotency key. On replay the key is\n * forwarded to the API's idempotency support (collection inserts, and\n * the events route's keyed path for `track`) so a mutation that actually\n * reached the server before the connection dropped is never applied\n * twice.\n * - On reconnect (`online` event) the outbox is flushed in FIFO order;\n * each op is removed only after it succeeds, so a mid-flush failure\n * leaves the remainder intact for the next attempt.\n * - Each op records its owner (the user id and anonymous id live when it\n * was queued). A flush replays only the current owner's ops and holds\n * the rest until that owner signs back in; `pending()` lists only the\n * current owner's ops. The SDKs clear the whole outbox on sign-out.\n */\n\n/**\n * Minimal durable key/value contract the outbox persists through. Both the\n * browser `localStorage` adapter and the React Native AsyncStorage adapter\n * satisfy this — the queue never assumes anything else.\n */\nexport interface KvStore {\n get(key: string): Promise<string | null> | string | null;\n set(key: string, value: string): Promise<void> | void;\n delete(key: string): Promise<void> | void;\n}\n\n/**\n * The identity a queued op belongs to: the signed-in user id (null before any\n * session) and the install's anonymous id, both captured at enqueue.\n */\nexport interface OutboxOwner {\n appUserId: string | null;\n anonymousId: string | null;\n}\n\n/**\n * Whether `op` belongs to `current`. An op queued under a user id matches only\n * that user id. An op queued before any session matches the same anonymous id,\n * so a guest's offline write lands once that guest's session starts. An op\n * with no recorded owner (queued by an SDK version before owners existed)\n * matches nobody.\n */\nexport function outboxOwnerMatches(op: OutboxOp, current: OutboxOwner): boolean {\n const owner = op.owner;\n if (!owner) return false;\n if (owner.appUserId) return owner.appUserId === current.appUserId;\n return owner.anonymousId !== null && owner.anonymousId === current.anonymousId;\n}\n\n/**\n * Who started a write and the queue's purge generation at that moment,\n * captured before the live attempt (`OfflineQueue.ticket()`). A request can\n * outlive a sign-out; the ticket keeps its queued copy attributed to the user\n * who made it, and refuses it when the queue was purged meanwhile.\n */\nexport interface OutboxTicket {\n owner: OutboxOwner | undefined;\n generation: number;\n}\n\n/** The kinds of mutation the outbox can durably replay. */\nexport type OutboxOpKind =\n | \"collection.insert\"\n | \"collection.update\"\n | \"collection.delete\"\n | \"track\";\n\n/**\n * A single durably-queued mutation. `idempotencyKey` is reused verbatim on\n * every replay attempt so the server dedupes a collection insert or a\n * tracked event that landed before the connection dropped (`track` replays\n * go to the events route's keyed path, which records each key once).\n * `enqueuedAt` is an ISO timestamp for inspection.\n */\nexport interface OutboxOp {\n /** Stable per-op id (the idempotency key for collection writes). */\n idempotencyKey: string;\n kind: OutboxOpKind;\n /** Target collection name (absent for `track`). */\n collection?: string;\n /** Row id for update / delete. */\n id?: string;\n /** Insert row / update set payload. */\n payload?: Record<string, unknown>;\n /** Event name for `track`. */\n event?: string;\n /** Event properties for `track`. */\n properties?: Record<string, unknown>;\n /** Whether the tracked event was telemetry-tier. */\n telemetry?: boolean;\n /**\n * ISO timestamp the `track` originally OCCURRED, captured at enqueue. Replayed\n * as the events body `timestamp` (→ `occurred_at`) so a buffered event keeps\n * its real time instead of being re-stamped at flush. (Attribution is by the\n * session user at replay time — the events route has no body-id channel.)\n */\n occurredAt?: string;\n /**\n * Who queued the op. Stamped at enqueue when the queue has a `currentOwner`\n * source; a flush replays the op only while the same owner is signed in.\n */\n owner?: OutboxOwner;\n enqueuedAt: string;\n}\n\n/**\n * Performs one queued mutation against the live API. Implemented per-SDK so\n * the queue stays platform-agnostic. MUST forward `op.idempotencyKey` on\n * collection INSERTS (the API dedupes inserts on `(collection,\n * idempotency_key)`) and on `track` replays (the events route records each\n * `idempotency_key` once). Throws on failure — the queue decides whether to\n * retain or drop the op.\n */\nexport type WriteExecutor = (op: OutboxOp) => Promise<void>;\n\n/** Result of a {@link OfflineQueue.flush}. */\nexport interface FlushResult {\n /** Ops successfully replayed and removed from the outbox. */\n flushed: number;\n /**\n * Ops dropped because their replay failed deterministically (a coded server\n * rejection like `VALIDATION_ERROR` / `NOT_FOUND` that would fail identically\n * forever). Dropping them prevents one poisoned op from wedging the queue;\n * the drain continues past it.\n */\n dropped: number;\n /** Ops still pending after the flush (a replay failed and stopped it). */\n remaining: number;\n}\n\n/**\n * Best-effort online probe. In a browser/RN runtime `navigator.onLine`\n * exists; under Node / SSR it doesn't, so we treat \"unknown\" as online\n * (the executor's own error handling then decides).\n */\nexport function isOnline(): boolean {\n const nav =\n typeof navigator !== \"undefined\"\n ? (navigator as { onLine?: boolean })\n : undefined;\n if (nav && typeof nav.onLine === \"boolean\") return nav.onLine;\n return true;\n}\n\n/**\n * Error codes the server raised deterministically — replaying them would fail\n * identically, so they're NEVER queued even when the device reports offline.\n * (A request can fail validation/auth/etc. while `navigator.onLine` is briefly\n * false; we must not buffer-and-retry those forever.)\n */\nconst DETERMINISTIC_ERROR_CODES = new Set([\n \"VALIDATION_ERROR\",\n \"INVALID_ARGUMENT\",\n \"INVALID_INPUT\",\n \"UNAUTHORIZED\",\n \"FORBIDDEN\",\n \"NOT_FOUND\",\n \"CONFLICT\",\n \"CONSENT_NOT_GRANTED\",\n \"NOT_INITIALIZED\",\n // The events route's 400 answers for a body it refuses. A replayed\n // `track` sends the same body every time, so it would be refused forever.\n // `IDEMPOTENCY_KEY_CONFLICT` means the key already recorded an event.\n \"INVALID_JSON\",\n \"INVALID_BODY\",\n \"INVALID_EVENT\",\n \"RESERVED_EVENT_NAME\",\n \"INVALID_IDEMPOTENCY_KEY\",\n \"INVALID_TIMESTAMP\",\n \"INVALID_PROPERTIES\",\n \"INVALID_TELEMETRY\",\n \"IDEMPOTENCY_KEY_CONFLICT\",\n]);\n\n/**\n * Is this a deterministic server rejection — a coded error the server would\n * raise identically on every replay (bad input, auth, not-found, conflict)? A\n * queued op that fails this way is poison: retrying it forever would wedge the\n * queue, so the flush DROPS it. Everything else (transport failures, 5xx\n * `HTTP_ERROR`, `RATE_LIMITED`, `CIRCUIT_OPEN`, anything uncoded) is treated as\n * transient and RETAINED for a later attempt — favoring retain over data loss.\n */\nexport function isDeterministicError(err: unknown): boolean {\n const code = (err as { code?: unknown } | null | undefined)?.code;\n return typeof code === \"string\" && DETERMINISTIC_ERROR_CODES.has(code);\n}\n\n/**\n * Heuristic: does a thrown value look like a transient offline / transport\n * failure (vs a deterministic 4xx the server would reject again)? Used at\n * ENQUEUE time to decide whether a failed write is worth buffering — a\n * `VALIDATION_ERROR` will fail identically on replay, so it throws instead.\n *\n * A deterministic server error is excluded even when the device reports\n * offline: the request clearly reached the server (it got a coded response),\n * so retrying it on reconnect is pointless and would wedge the queue.\n *\n * NOTE: this is intentionally stricter than {@link isDeterministicError}'s\n * inverse — at enqueue time we only buffer GENUINE connectivity failures (a\n * non-offline 5xx/429 surfaces to the caller immediately rather than silently\n * queueing). The flush drop-decision uses {@link isDeterministicError} so a\n * transient 5xx/429 encountered DURING replay is retained, not dropped.\n */\nexport function isOfflineError(err: unknown): boolean {\n const code = (err as { code?: unknown } | null | undefined)?.code;\n if (typeof code === \"string\" && DETERMINISTIC_ERROR_CODES.has(code)) {\n return false;\n }\n if (code === \"NETWORK_ERROR\") return true;\n // `fetch` signals a transport failure (DNS / refused / offline) by throwing a\n // `TypeError` with a runtime-specific message — \"Failed to fetch\" (Chrome),\n // \"Network request failed\" (RN/Hermes), \"Load failed\" (Safari),\n // \"fetch failed\" (undici/Node). Match those messages rather than EVERY\n // TypeError, so a genuine programming `TypeError` (e.g. reading a property of\n // undefined) surfaces to the caller instead of being silently buffered —\n // especially on Node where `isOnline()` is always true.\n const msg = (err as { message?: unknown } | null | undefined)?.message;\n const looksLikeTransport =\n typeof msg === \"string\" &&\n /network (error|request failed)|failed to fetch|fetch failed|load failed/i.test(\n msg,\n );\n if (looksLikeTransport) return true;\n // No transport signal on the error itself — fall back to the runtime's\n // online state. Treat \"offline\" as queueable only for errors that DIDN'T\n // carry a deterministic server code (handled above).\n return !isOnline();\n}\n\n/**\n * Generate a stable idempotency key for a queued op: `amba_obx_` and a random\n * UUID. The server records event keys project-wide, so the UUID comes from\n * the runtime's secure random source: `crypto.randomUUID`, else\n * `crypto.getRandomValues` (Hermes has only the latter).\n */\nexport function newIdempotencyKey(): string {\n const c =\n typeof globalThis !== \"undefined\"\n ? (globalThis.crypto as\n | {\n randomUUID?: () => string;\n getRandomValues?: (array: Uint8Array) => Uint8Array;\n }\n | undefined)\n : undefined;\n if (c && typeof c.randomUUID === \"function\") return `amba_obx_${c.randomUUID()}`;\n if (c && typeof c.getRandomValues === \"function\") {\n const b = c.getRandomValues(new Uint8Array(16));\n // RFC 4122 version 4, variant 10.\n b[6] = (b[6]! & 0x0f) | 0x40;\n b[8] = (b[8]! & 0x3f) | 0x80;\n const h = Array.from(b, (x) => x.toString(16).padStart(2, \"0\")).join(\"\");\n return `amba_obx_${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;\n }\n // Last resort for a runtime with neither (the server still checks that a\n // reused key carries the same event).\n return `amba_obx_${Date.now().toString(36)}_${Math.random()\n .toString(36)\n .slice(2, 12)}`;\n}\n\n/**\n * Whether a `track` with these properties can take the events route's keyed\n * path, which accepts a plain object or no properties. A JS caller can pass\n * something else (an array); such an event stays on the unkeyed path, which\n * stores it as it always has.\n */\nexport function keyableProperties(properties: unknown): boolean {\n return (\n properties === undefined ||\n properties === null ||\n (typeof properties === \"object\" && !Array.isArray(properties))\n );\n}\n\n/**\n * Durable, ordered, idempotent offline outbox. Opt-in via `Amba.offline`.\n * Single in-flight flush is enforced so concurrent reconnect signals don't\n * double-replay.\n */\nexport class OfflineQueue {\n private readonly store: KvStore;\n private readonly executor: WriteExecutor;\n private readonly storageKey: string;\n /**\n * Notified when a queued op is dropped because its replay failed\n * deterministically (a poisoned op the server will always reject). Lets the\n * SDK surface the loss (debug log) instead of silently discarding.\n */\n private readonly onDropped?: (op: OutboxOp, err: unknown) => void;\n /**\n * The identity that is signed in right now. When set, enqueue stamps it on\n * each op and flush/pending only touch ops it owns.\n */\n private readonly currentOwner?: () => OutboxOwner;\n private flushing: Promise<FlushResult> | null = null;\n /**\n * Bumped by `purge()`. A replay that started before a purge sees the change\n * and does not re-queue its op.\n */\n private purgeGeneration = 0;\n /**\n * Serializes every read-modify-write of the persisted outbox. Each mutation\n * chains onto the previous one's promise, so concurrent `enqueue`s — and an\n * `enqueue` racing the per-op removal inside `flush` — can never read a stale\n * snapshot and clobber each other's writes (the lost-update bug). All\n * storage mutations funnel through `mutate()`; only `pendingCount`/`pending`\n * read without taking the lock (a stale-by-one read is harmless).\n */\n private tail: Promise<unknown> = Promise.resolve();\n /** Detach function for the auto-flush `online` listener, if installed. */\n private detachOnline: (() => void) | null = null;\n /**\n * Set once `dispose()` is called (on teardown / reconfigure). A disposed\n * queue refuses to mutate the persisted outbox, so an orphaned in-flight\n * flush — or any late call against the old instance — can never race the new\n * queue by reading/writing the same storage after it's been replaced.\n */\n private disposed = false;\n\n constructor(opts: {\n store: KvStore;\n executor: WriteExecutor;\n /** Persistence key for the serialized queue. */\n storageKey?: string;\n /** Called when a deterministically-failing op is dropped from the queue. */\n onDropped?: (op: OutboxOp, err: unknown) => void;\n /**\n * Returns the identity signed in right now. Binds each queued op to the\n * user who queued it: flush replays an op only for that user.\n */\n currentOwner?: () => OutboxOwner;\n }) {\n this.store = opts.store;\n this.executor = opts.executor;\n this.storageKey = opts.storageKey ?? \"offline_outbox\";\n this.onDropped = opts.onDropped;\n this.currentOwner = opts.currentOwner;\n }\n\n /**\n * The ops the current owner may see and replay. Without an owner source\n * every op qualifies.\n */\n private visible(ops: OutboxOp[]): OutboxOp[] {\n if (!this.currentOwner) return ops;\n const current = this.currentOwner();\n return ops.filter((op) => outboxOwnerMatches(op, current));\n }\n\n /**\n * Run `fn` exclusively against the persisted outbox: it chains after every\n * prior mutation and the next one waits for it. `fn` receives the current\n * ops and returns the next ops to persist plus its own result.\n */\n private mutate<R>(\n fn: (ops: OutboxOp[]) => { next: OutboxOp[]; result: R } | Promise<{ next: OutboxOp[]; result: R }>,\n opts?: { rejectIfDisposed?: boolean },\n ): Promise<R> {\n const run = this.tail.then(async () => {\n // Re-check disposal AFTER taking the lock. `save()` is already a no-op\n // when disposed, but a caller that needs to KNOW its write didn't persist\n // (enqueue) must REJECT rather than silently resolve — otherwise a\n // dispose racing between `getOfflineQueue()` and the awaited `enqueue()`\n // would hand the caller a pending-success for a write that's now lost.\n if (this.disposed && opts?.rejectIfDisposed) {\n throw new Error(\"amba offline queue was disposed (SDK reconfigured)\");\n }\n const ops = await this.load();\n const { next, result } = await fn(ops);\n await this.save(next);\n return result;\n });\n // Keep the chain alive even if this mutation rejects, so a single failure\n // doesn't permanently wedge every future mutation.\n this.tail = run.catch(() => undefined);\n return run;\n }\n\n private async load(): Promise<OutboxOp[]> {\n const raw = await this.store.get(this.storageKey);\n if (!raw) return [];\n try {\n const parsed = JSON.parse(raw) as unknown;\n return Array.isArray(parsed) ? (parsed as OutboxOp[]) : [];\n } catch {\n // Corrupt payload — start clean rather than wedge the queue forever.\n return [];\n }\n }\n\n private async save(ops: OutboxOp[]): Promise<void> {\n // A disposed queue must never write the shared storage — a new queue may\n // already own it. Drop the write silently so an orphaned in-flight flush\n // (or any late mutation) can't clobber the replacement queue's outbox.\n if (this.disposed) return;\n if (ops.length === 0) {\n await this.store.delete(this.storageKey);\n return;\n }\n await this.store.set(this.storageKey, JSON.stringify(ops));\n }\n\n /**\n * Permanently retire this queue. Detaches the auto-flush listener and marks\n * it disposed so it can no longer mutate the persisted outbox. Returns once\n * any in-flight flush + pending mutation chain has settled, so a caller (e.g.\n * `configure()` teardown) can safely create a replacement queue knowing the\n * old one will never touch storage again. Idempotent.\n */\n async dispose(): Promise<void> {\n this.disposed = true;\n if (this.detachOnline) {\n this.detachOnline();\n this.detachOnline = null;\n }\n // Await the in-flight flush + the mutation chain so all already-scheduled\n // storage work has run (its writes are now suppressed by the disposed\n // guard) before the caller installs a new queue.\n try {\n await this.flushing;\n } catch {\n // a failing flush still settles the lifecycle\n }\n await this.tail.catch(() => undefined);\n }\n\n /** Whether this queue has been disposed. */\n get isDisposed(): boolean {\n return this.disposed;\n }\n\n /**\n * Capture the current owner and purge generation. Take it before a write's\n * live attempt and pass it to {@link enqueue}.\n */\n ticket(): OutboxTicket {\n return { owner: this.currentOwner?.(), generation: this.purgeGeneration };\n }\n\n /**\n * Append an op to the durable outbox. Returns the persisted op. With a\n * `ticket`, the op belongs to the ticket's owner, and the enqueue rejects\n * (code `OFFLINE_QUEUE_PURGED`) when the queue was purged after the ticket\n * was taken, which is what a sign-out during the live attempt does.\n */\n async enqueue(\n op: Omit<OutboxOp, \"idempotencyKey\" | \"enqueuedAt\"> & {\n idempotencyKey?: string;\n },\n ticket?: OutboxTicket,\n ): Promise<OutboxOp> {\n // Fast-fail if already disposed; otherwise the re-check inside `mutate`\n // (after the lock) catches a dispose that races this call. Either way a\n // disposed queue REJECTS the enqueue rather than silently swallowing the\n // write — so the SDK's diverting write surfaces a failure instead of\n // handing the caller a pending-success for a write that never persisted.\n if (this.disposed) {\n throw new Error(\"amba offline queue was disposed (SDK reconfigured)\");\n }\n const owner = op.owner ?? (ticket ? ticket.owner : this.currentOwner?.());\n const full: OutboxOp = {\n ...op,\n ...(owner ? { owner: { ...owner } } : {}),\n idempotencyKey: op.idempotencyKey ?? newIdempotencyKey(),\n enqueuedAt: new Date().toISOString(),\n };\n return this.mutate(\n (ops) => {\n // Checked under the lock: a purge that ran while the live attempt was\n // in flight wins, and the signed-out user's write is not queued.\n if (ticket && ticket.generation !== this.purgeGeneration) {\n const err = new Error(\n \"amba offline queue was cleared by a sign-out while this write was in flight\",\n ) as Error & { code: string };\n err.code = \"OFFLINE_QUEUE_PURGED\";\n throw err;\n }\n return { next: [...ops, full], result: full };\n },\n { rejectIfDisposed: true },\n );\n }\n\n /** Number of the current owner's ops awaiting replay. */\n async pendingCount(): Promise<number> {\n return this.visible(await this.load()).length;\n }\n\n /**\n * Snapshot the current owner's pending ops (a copy — mutating it does not\n * affect the queue). Ops queued by another user stay out of the list.\n */\n async pending(): Promise<OutboxOp[]> {\n return [...this.visible(await this.load())];\n }\n\n /** Drop every queued op, whoever queued it, without replaying. */\n async clear(): Promise<void> {\n await this.mutate(() => ({ next: [], result: undefined }));\n }\n\n /**\n * Drop every queued op, including one whose replay is in flight right now:\n * that op is not re-queued when its replay fails. The SDKs call this on\n * sign-out so nothing the signed-out user queued survives on the device.\n */\n async purge(): Promise<void> {\n this.purgeGeneration += 1;\n await this.clear();\n }\n\n /**\n * Give the ops queued before any session under `anonymousId` to the\n * signed-in `appUserId`. Those ops replay under the session that holds\n * their anonymous id; the SDK calls this before that session's anonymous\n * id changes (a link, or adopting the account's id), so they keep an owner\n * that matches.\n */\n async claimAnonymousOps(anonymousId: string, appUserId: string): Promise<void> {\n await this.mutate((ops) => ({\n next: ops.map((op) =>\n op.owner &&\n op.owner.appUserId === null &&\n op.owner.anonymousId === anonymousId\n ? { ...op, owner: { appUserId, anonymousId } }\n : op,\n ),\n result: undefined,\n }));\n }\n\n /**\n * Replay the current owner's ops in FIFO order. Each op is removed only\n * after it succeeds; the first failure stops the flush and retains the\n * remainder (so order is preserved and nothing is dropped mid-batch). Ops\n * another user queued stay held for that user. Ops that can never replay\n * are dropped through `onDropped`: one with no recorded owner (code\n * `OFFLINE_OWNER_UNKNOWN`), and one queued before any session under an\n * anonymous id the install no longer has (code `OFFLINE_OWNER_GONE`).\n * Concurrent calls share one in-flight flush.\n */\n async flush(): Promise<FlushResult> {\n if (this.disposed) return { flushed: 0, dropped: 0, remaining: 0 };\n if (this.flushing) return this.flushing;\n this.flushing = this.runFlush().finally(() => {\n this.flushing = null;\n });\n return this.flushing;\n }\n\n private async runFlush(): Promise<FlushResult> {\n let flushed = 0;\n let dropped = 0;\n // Drive the flush one op at a time through `mutate()`, so a concurrent\n // `enqueue` (e.g. a new offline write mid-flush) interleaves safely\n // instead of being lost to a stale read-modify-write. Each step replays\n // the head op (outside the lock — it can be slow) then atomically removes\n // exactly that op if it's still the head.\n //\n // The drain is NOT gated on `navigator.onLine`: that's meaningless on\n // React Native (no real `navigator.onLine`), where the documented pattern\n // is to call `flush()` from a NetInfo connectivity listener — so an\n // explicit flush must always ATTEMPT the drain. If the device is in fact\n // still offline, the executor throws a transport error on the first op and\n // the loop stops below, retaining everything for the next attempt. (The\n // web auto-flush path only fires on the `online` event anyway, so it's\n // already online when it runs.)\n for (;;) {\n // Stop immediately if disposed mid-drain (a reconfigure replaced us): the\n // save guard already suppresses writes, this just avoids executing more\n // ops against a retired queue.\n if (this.disposed) break;\n const all = await this.load();\n if (this.currentOwner) {\n // Ops that can never match an owner again are dropped (reported)\n // instead of held forever: one from before owners existed, and a\n // pre-session op whose anonymous id the install no longer has\n // (rotated on sign-out, or a fresh one per page in memory mode).\n const current = this.currentOwner();\n const orphan = all.find(\n (o) =>\n !o.owner ||\n (o.owner.appUserId === null &&\n current.anonymousId !== null &&\n o.owner.anonymousId !== current.anonymousId),\n );\n if (orphan) {\n const removedOrphan = await this.removeOp(orphan);\n if (!removedOrphan) break;\n dropped += 1;\n this.reportDropped(\n orphan,\n orphan.owner\n ? {\n code: \"OFFLINE_OWNER_GONE\",\n message: \"queued before sign-in under an anonymous id this install no longer has; not replayed\",\n }\n : {\n code: \"OFFLINE_OWNER_UNKNOWN\",\n message: \"queued by an earlier SDK version without an owner; not replayed\",\n },\n );\n continue;\n }\n }\n const head = this.visible(all)[0];\n if (!head) break;\n const generation = this.purgeGeneration;\n let replayError: unknown = undefined;\n let ok = false;\n try {\n await this.executor(head);\n ok = true;\n } catch (err) {\n replayError = err;\n }\n\n if (!ok && !isDeterministicError(replayError)) {\n // A NON-deterministic replay failure — a transport blip, a 5xx\n // `HTTP_ERROR`, `RATE_LIMITED` (429), `CIRCUIT_OPEN`, or any uncoded\n // error. These may well succeed later, so STOP here and keep this op\n // plus everything after it for the next attempt (favor retain over data\n // loss). Only a deterministic server rejection (handled below) is\n // dropped.\n //\n // The replay ran OUTSIDE the lock, so a concurrent `clear()` (or a\n // drain that already removed this op) could have emptied the store\n // while it was in flight. Re-assert the un-applied op's presence under\n // the lock — re-prepend it if it's gone — so an unapplied mutation is\n // never silently lost to a clear/flush interleaving. at-least-once wins\n // over honoring a clear that raced an in-flight, un-applied write; the\n // caller can `clear()` again once the flush settles. A `purge()` (sign-\n // out) is final: the op stays gone.\n await this.mutate((ops) => {\n const present = ops.some(\n (o) => o.idempotencyKey === head.idempotencyKey,\n );\n const requeue = !present && generation === this.purgeGeneration;\n return {\n next: requeue ? [head, ...ops] : ops,\n result: undefined,\n };\n });\n break;\n }\n\n // Either the replay succeeded, OR it failed deterministically (a coded\n // server rejection that will fail identically forever). In both cases the\n // head op leaves the queue: a success drains it, a poisoned op is DROPPED\n // so it can't wedge everything behind it. Removal is by identity under\n // the lock — robust against a concurrent enqueue having shifted the\n // array.\n const removed = await this.removeOp(head);\n if (!removed) break; // shouldn't happen, but never spin\n if (ok) {\n flushed += 1;\n } else {\n dropped += 1;\n this.reportDropped(head, replayError);\n }\n }\n const remaining = this.visible(await this.load()).length;\n return { flushed, dropped, remaining };\n }\n\n /**\n * Remove exactly `op` (by idempotency key) under the lock. Robust against a\n * concurrent enqueue having shifted the array. Resolves false when the op\n * is already gone.\n */\n private removeOp(op: OutboxOp): Promise<boolean> {\n return this.mutate((ops) => {\n const idx = ops.findIndex((o) => o.idempotencyKey === op.idempotencyKey);\n if (idx === -1) return { next: ops, result: false };\n const next = ops.slice(0, idx).concat(ops.slice(idx + 1));\n return { next, result: true };\n });\n }\n\n private reportDropped(op: OutboxOp, err: unknown): void {\n try {\n this.onDropped?.(op, err);\n } catch {\n // A throwing drop-observer must not break the flush.\n }\n }\n\n /**\n * Install an auto-flush listener on the runtime's `online` event so the\n * queue drains the moment connectivity returns. Idempotent — a second\n * call is a no-op. Returns a detach function.\n */\n startAutoFlush(): () => void {\n if (this.detachOnline) return this.detachOnline;\n const target =\n typeof globalThis !== \"undefined\" &&\n typeof (globalThis as { addEventListener?: unknown }).addEventListener ===\n \"function\"\n ? (globalThis as unknown as {\n addEventListener(t: string, cb: () => void): void;\n removeEventListener(t: string, cb: () => void): void;\n })\n : null;\n if (!target) {\n // No event target (Node / SSR) — nothing to listen on. Still return a\n // detach so callers can treat the API uniformly.\n this.detachOnline = () => {};\n return this.detachOnline;\n }\n const handler = () => {\n void this.flush();\n };\n target.addEventListener(\"online\", handler);\n this.detachOnline = () => {\n target.removeEventListener(\"online\", handler);\n this.detachOnline = null;\n };\n return this.detachOnline;\n }\n}\n\n// ── Optimistic update helper ─────────────────────────────────────────────\n\n/**\n * Apply a local change immediately, fire the real mutation, and roll the\n * local change back if the mutation fails. Framework-agnostic: `apply` and\n * `rollback` operate on whatever local view you hand them (a React state\n * setter, a store, an in-memory cache).\n *\n * Resolves with the mutation's result on success. On failure it invokes\n * `rollback` (with the value `apply` returned, so you can restore a captured\n * snapshot) and rethrows the original error.\n *\n * @example\n * await withOptimistic({\n * apply: () => { const prev = todos; setTodos([...todos, draft]); return prev; },\n * mutate: () => Amba.collections.insert(\"todos\", draft),\n * rollback: (prev) => setTodos(prev),\n * });\n */\nexport async function withOptimistic<TApply, TResult>(spec: {\n /** Mutate the local view now. Return a snapshot for `rollback` to restore. */\n apply: () => TApply;\n /** The real async mutation against the API. */\n mutate: () => Promise<TResult>;\n /** Undo the local change. Receives whatever `apply` returned. */\n rollback: (applied: TApply) => void;\n}): Promise<TResult> {\n const applied = spec.apply();\n try {\n return await spec.mutate();\n } catch (err) {\n try {\n spec.rollback(applied);\n } catch {\n // A throwing rollback must not mask the original mutation error.\n }\n throw err;\n }\n}\n","import type { KvStore } from \"./resilience.js\";\nimport { newIdempotencyKey } from \"./resilience.js\";\n\nexport interface EventDrop {\n idempotencyKey: string;\n event: string;\n userId: string | null;\n reason:\n | \"telemetry_evicted\"\n | \"server_rejected\"\n | \"held_expired\"\n | \"oversized\";\n code?: string;\n}\n\nexport interface EventBatchOptions {\n batchSize: number;\n flushIntervalMs: number;\n maxQueue: number;\n onDrop?: (drop: EventDrop) => void;\n}\n\nexport interface QueuedEvent {\n event: string;\n properties?: Record<string, unknown>;\n timestamp: string;\n telemetry: boolean;\n idempotency_key: string;\n userId: string | null;\n anonymousId: string;\n heldAt?: string;\n}\n\nexport interface EventFlushResult {\n sent: number;\n rejected: number;\n remaining: number;\n}\n\ninterface BatchResponse {\n data: {\n accepted: Array<{\n idempotency_key: string;\n status: \"inserted\" | \"duplicate\";\n }>;\n rejected: Array<{ idempotency_key: string; code: string }>;\n };\n}\n\nconst MAX_BYTES = 512 * 1024;\nconst HELD_TTL_MS = 30 * 24 * 60 * 60 * 1000;\nconst COALESCE_MS = 250;\n\nfunction byteLength(s: string): number {\n if (typeof TextEncoder !== \"undefined\")\n return new TextEncoder().encode(s).length;\n return encodeURIComponent(s).replace(/%[A-Fa-f0-9]{2}|./g, \"x\").length;\n}\n\nfunction wire(e: QueuedEvent) {\n return {\n event: e.event,\n properties: e.properties,\n timestamp: e.timestamp,\n telemetry: e.telemetry,\n idempotency_key: e.idempotency_key,\n };\n}\n\nfunction safeDrop(\n cb: EventBatchOptions[\"onDrop\"],\n e: QueuedEvent,\n reason: EventDrop[\"reason\"],\n code?: string,\n): void {\n try {\n cb?.({\n idempotencyKey: e.idempotency_key,\n event: e.event,\n userId: e.userId,\n reason,\n code,\n });\n } catch {\n /* reporting cannot block delivery */\n }\n}\n\n/** Durable queue owned by one configured project and storage prefix. */\nexport class EventBatchQueue {\n private events: QueuedEvent[] = [];\n private pending: Array<{\n event: QueuedEvent;\n resolve: () => void;\n reject: (err: unknown) => void;\n }> = [];\n private writeTimer: ReturnType<typeof setTimeout> | null = null;\n private flushTimer: ReturnType<typeof setTimeout> | null = null;\n private retryTimer: ReturnType<typeof setTimeout> | null = null;\n private serialized: Promise<unknown> = Promise.resolve();\n private inFlight: Promise<EventFlushResult> | null = null;\n private disposed = false;\n private online: boolean | null = null;\n private batchingEnabled = true;\n private failures = 0;\n private clearingUsers = new Set<string>();\n\n constructor(\n private readonly store: KvStore,\n private readonly storageKey: string,\n private readonly options: EventBatchOptions,\n private readonly identity: () => {\n userId: string | null;\n anonymousId: string;\n sessionToken: string | null;\n },\n private readonly postBatch: (\n body: { events: ReturnType<typeof wire>[] },\n sessionToken: string,\n ) => Promise<BatchResponse>,\n private readonly postSingle: (\n body: ReturnType<typeof wire>,\n sessionToken: string,\n ) => Promise<unknown>,\n ) {}\n\n async start(): Promise<void> {\n const raw = await this.store.get(this.storageKey);\n if (raw) {\n try {\n const parsed = JSON.parse(raw) as unknown;\n this.events = Array.isArray(parsed) ? (parsed as QueuedEvent[]) : [];\n } catch {\n this.events = [];\n }\n }\n await this.expireHeld();\n // Unknown connectivity permits one launch attempt. The timer starts only\n // after the host supplies its first connectivity answer.\n if (this.events.length) void this.flush();\n }\n\n private exclusive<T>(work: () => Promise<T>): Promise<T> {\n const run = this.serialized.then(work);\n this.serialized = run.catch(() => undefined);\n return run;\n }\n\n private async save(): Promise<void> {\n if (this.events.length)\n await this.store.set(this.storageKey, JSON.stringify(this.events));\n else await this.store.delete(this.storageKey);\n }\n\n /**\n * Queue an event. `timestamp` is when it happened, for an event that waited\n * elsewhere before reaching the queue; it defaults to now.\n */\n enqueue(\n event: string,\n properties: Record<string, unknown> | undefined,\n telemetry: boolean,\n timestamp?: string,\n ): Promise<void> {\n if (this.disposed)\n return Promise.reject(new Error(\"Amba event queue disposed\"));\n const id = this.identity();\n const item: QueuedEvent = {\n event,\n properties,\n telemetry,\n timestamp: timestamp ?? new Date().toISOString(),\n idempotency_key: newIdempotencyKey(),\n userId: id.sessionToken ? id.userId : null,\n anonymousId: id.anonymousId,\n };\n if (!event.trim())\n return Promise.reject(new Error(\"event name must be non-empty\"));\n if (byteLength(JSON.stringify({ events: [wire(item)] })) > MAX_BYTES) {\n safeDrop(this.options.onDrop, item, \"oversized\");\n return Promise.reject(new Error(\"event exceeds 512 KB batch limit\"));\n }\n return new Promise<void>((resolve, reject) => {\n this.pending.push({ event: item, resolve, reject });\n if (!this.writeTimer)\n this.writeTimer = setTimeout(() => {\n void this.persistPending();\n }, COALESCE_MS);\n });\n }\n\n async persistPending(): Promise<void> {\n if (this.writeTimer) clearTimeout(this.writeTimer);\n this.writeTimer = null;\n const batch = this.pending.splice(0);\n if (!batch.length) return;\n try {\n let evictedKeys = new Set<string>();\n await this.exclusive(async () => {\n const before = this.events;\n this.events = [...this.events, ...batch.map((p) => p.event)];\n const evicted: QueuedEvent[] = [];\n while (this.events.length > this.options.maxQueue) {\n const idx = this.events.findIndex((e) => e.telemetry);\n if (idx < 0) break; // engagement rows are never silently discarded\n evicted.push(this.events[idx]!);\n this.events.splice(idx, 1);\n }\n try {\n await this.save();\n } catch (err) {\n this.events = before;\n throw err;\n }\n evictedKeys = new Set(evicted.map((e) => e.idempotency_key));\n for (const e of evicted)\n safeDrop(this.options.onDrop, e, \"telemetry_evicted\");\n });\n for (const p of batch) {\n if (evictedKeys.has(p.event.idempotency_key))\n p.reject(new Error(\"event evicted before durable enqueue\"));\n else p.resolve();\n }\n this.schedule();\n if (this.online !== false && this.events.length >= this.options.batchSize)\n void this.flush();\n } catch (err) {\n for (const p of batch) p.reject(err);\n }\n }\n\n setOnline(value: boolean): void {\n const wasOnline = this.online;\n this.online = value;\n if (!value) {\n this.cancelTimers();\n return;\n }\n this.schedule();\n if (wasOnline !== true) void this.flush();\n }\n\n setBatchingEnabled(enabled: boolean): void {\n this.batchingEnabled = enabled;\n if (this.online !== false) void this.flush();\n }\n\n get isBatchingEnabled(): boolean {\n return this.batchingEnabled;\n }\n\n async pendingCount(userId?: string): Promise<number> {\n await this.persistPending();\n return this.events.filter(\n (e) => userId === undefined || e.userId === userId,\n ).length;\n }\n\n async clear(userId: string): Promise<void> {\n // Await a request already in flight. It cannot be recalled after reaching\n // the server, but this prevents any further dispatch after clear resolves.\n this.clearingUsers.add(userId);\n try {\n await this.inFlight?.catch(() => undefined);\n await this.persistPending();\n await this.exclusive(async () => {\n const prior = this.events;\n const id = this.identity();\n this.events = this.events.filter(\n (e) =>\n e.userId !== userId &&\n !(\n id.userId === userId &&\n e.userId === null &&\n e.anonymousId === id.anonymousId\n ),\n );\n try {\n await this.save();\n } catch (err) {\n this.events = prior;\n throw err;\n }\n });\n } finally {\n this.clearingUsers.delete(userId);\n this.schedule();\n }\n }\n\n private async expireHeld(): Promise<void> {\n const current = this.identity();\n await this.exclusive(async () => {\n const now = Date.now();\n const dropped: QueuedEvent[] = [];\n const next: QueuedEvent[] = [];\n for (const e of this.events) {\n if (\n (e.userId && e.userId !== current.userId) ||\n (!e.userId && e.anonymousId !== current.anonymousId)\n ) {\n e.heldAt ??= new Date(now).toISOString();\n if (now - Date.parse(e.heldAt) >= HELD_TTL_MS) {\n dropped.push(e);\n continue;\n }\n } else e.heldAt = undefined;\n next.push(e);\n }\n this.events = next;\n await this.save();\n for (const e of dropped) safeDrop(this.options.onDrop, e, \"held_expired\");\n });\n }\n\n private cancelTimers(): void {\n if (this.flushTimer) clearTimeout(this.flushTimer);\n if (this.retryTimer) clearTimeout(this.retryTimer);\n this.flushTimer = this.retryTimer = null;\n }\n\n private schedule(): void {\n if (this.flushTimer) clearTimeout(this.flushTimer);\n this.flushTimer = null;\n if (\n this.disposed ||\n this.online !== true ||\n this.failures > 0 ||\n this.options.flushIntervalMs === 0\n )\n return;\n const id = this.identity();\n if (!id.sessionToken || !id.userId) return;\n const eligible = this.events.filter(\n (e) =>\n e.userId === id.userId ||\n (e.userId === null && e.anonymousId === id.anonymousId),\n );\n if (!eligible.length) return;\n const oldest = Math.min(...eligible.map((e) => Date.parse(e.timestamp)));\n const wait = Math.max(\n 0,\n oldest + this.options.flushIntervalMs - Date.now(),\n );\n this.flushTimer = setTimeout(() => {\n void this.flush();\n }, wait);\n }\n\n private retry(): void {\n this.failures += 1;\n if (this.retryTimer) clearTimeout(this.retryTimer);\n if (this.online !== true) return;\n const delay = Math.min(60_000, 1000 * 2 ** Math.min(this.failures - 1, 6));\n this.retryTimer = setTimeout(() => {\n void this.flush();\n }, delay);\n }\n\n async flush(): Promise<EventFlushResult> {\n if (this.inFlight) return this.inFlight;\n this.inFlight = this.runFlush().finally(() => {\n this.inFlight = null;\n });\n return this.inFlight;\n }\n\n private async runFlush(): Promise<EventFlushResult> {\n await this.persistPending();\n if (this.disposed || this.online === false)\n return { sent: 0, rejected: 0, remaining: this.events.length };\n await this.expireHeld();\n const id = this.identity();\n if (!id.sessionToken || !id.userId)\n return { sent: 0, rejected: 0, remaining: this.events.length };\n let sent = 0;\n let rejected = 0;\n for (;;) {\n if (\n this.disposed ||\n (this.online as boolean | null) === false ||\n this.clearingUsers.has(id.userId) ||\n this.identity().userId !== id.userId\n )\n break;\n const eligible = this.events.filter(\n (e) =>\n e.userId === id.userId ||\n (e.userId === null && e.anonymousId === id.anonymousId),\n );\n if (!eligible.length) break;\n const selected: QueuedEvent[] = [];\n for (const e of eligible) {\n if (selected.length >= this.options.batchSize) break;\n const candidate = [...selected, e];\n if (\n byteLength(JSON.stringify({ events: candidate.map(wire) })) >\n MAX_BYTES\n )\n break;\n selected.push(e);\n }\n if (!selected.length) break;\n // Bind pre-session records before request so a later account switch can\n // never send them under a different user.\n await this.exclusive(async () => {\n for (const e of selected) if (e.userId === null) e.userId = id.userId;\n await this.save();\n });\n if (this.clearingUsers.has(id.userId) || this.identity().userId !== id.userId)\n break;\n try {\n const ack = this.batchingEnabled\n ? await this.postBatch(\n { events: selected.map(wire) },\n id.sessionToken,\n )\n : {\n data: {\n accepted: [] as BatchResponse[\"data\"][\"accepted\"],\n rejected: [] as BatchResponse[\"data\"][\"rejected\"],\n },\n };\n if (!this.batchingEnabled) {\n for (const e of selected) {\n await this.postSingle(wire(e), id.sessionToken);\n ack.data.accepted.push({\n idempotency_key: e.idempotency_key,\n status: \"inserted\",\n });\n }\n }\n const accepted = new Set(\n ack.data.accepted.map((a) => a.idempotency_key),\n );\n const drops = new Map(\n ack.data.rejected.map((r) => [r.idempotency_key, r.code]),\n );\n const known = new Set(selected.map((e) => e.idempotency_key));\n if ([...accepted, ...drops.keys()].some((k) => !known.has(k)))\n throw new Error(\"event batch returned unknown key\");\n if (accepted.size + drops.size !== selected.length)\n throw new Error(\"event batch omitted acknowledgments\");\n await this.exclusive(async () => {\n const prior = this.events;\n this.events = this.events.filter(\n (e) =>\n !accepted.has(e.idempotency_key) && !drops.has(e.idempotency_key),\n );\n try {\n await this.save();\n } catch (err) {\n this.events = prior;\n throw err;\n }\n });\n sent += accepted.size;\n rejected += drops.size;\n for (const e of selected)\n if (drops.has(e.idempotency_key))\n safeDrop(\n this.options.onDrop,\n e,\n \"server_rejected\",\n drops.get(e.idempotency_key),\n );\n this.failures = 0;\n } catch {\n this.retry();\n break;\n }\n }\n this.schedule();\n return { sent, rejected, remaining: this.events.length };\n }\n\n async dispose(): Promise<void> {\n this.disposed = true;\n this.cancelTimers();\n await this.persistPending();\n await this.inFlight?.catch(() => undefined);\n await this.serialized;\n }\n}\n","/**\n * Native push-token rotation for React Native.\n *\n * The OS can rotate a device's push token at any time (app reinstall, restore,\n * FCM/APNs key roll). When it does, the old token stops delivering and the app\n * must re-register the new one. The platform surfaces this through a\n * token-refresh callback — `Notifications.addPushTokenListener` (Expo), the\n * Firebase `onTokenRefresh` callback (bare FCM), or the APNs\n * `didRegisterForRemoteNotificationsWithDeviceToken` delegate (bare iOS).\n *\n * This module is the platform-neutral glue: it takes a `subscribe` function\n * (whatever the host's notification library exposes) and a `reRegister`\n * callback (the SDK points it at its idempotent `push.register` path), wires\n * them together, and returns an unsubscribe handle. It imports nothing from\n * the notification libraries itself, so the bare RN SDK stays dependency-free\n * and the Expo wrapper supplies the concrete `addPushTokenListener`.\n */\n\n/** Re-registers a rotated token with the backend. */\nexport type ReRegister = (\n token: string,\n platform: \"apns\" | \"fcm\",\n) => Promise<unknown>;\n\n/**\n * A platform token-refresh subscription primitive. Given a handler that\n * receives the new token string, it must invoke that handler on every rotation\n * and return an unsubscribe function. `Notifications.addPushTokenListener`\n * (Expo) adapts to this directly.\n */\nexport type TokenRefreshSubscribe = (\n handler: (token: string) => void,\n) => () => void;\n\n/**\n * Wire a platform token-refresh subscription to the SDK's idempotent\n * re-register path. On every rotation the fresh token is forwarded to\n * `reRegister(token, platform)` — the same upsert the first `push.register`\n * call used, so re-registering the same device is a no-op server-side.\n *\n * `platform` defaults to the conventional pick (`\"apns\"` on iOS, `\"fcm\"`\n * elsewhere) supplied by the caller. Returns an unsubscribe function.\n *\n * @example\n * // Expo:\n * import * as Notifications from \"expo-notifications\";\n * const detach = enableNativePushAutoRotation(\n * (h) => Notifications.addPushTokenListener((t) => h(t.data)).remove,\n * reRegister,\n * \"apns\",\n * );\n */\nexport function enableNativePushAutoRotation(\n subscribe: TokenRefreshSubscribe,\n reRegister: ReRegister,\n platform: \"apns\" | \"fcm\",\n): () => void {\n const unsubscribe = subscribe((token: string) => {\n if (!token) return;\n void (async () => {\n try {\n await reRegister(token, platform);\n } catch (err) {\n if (typeof console !== \"undefined\") {\n console.error(\"[amba] push auto-rotation re-register failed\", err);\n }\n }\n })();\n });\n return typeof unsubscribe === \"function\" ? unsubscribe : () => {};\n}\n","/**\n * On-device local notifications for React Native / Expo.\n *\n * A *local* notification is one the device schedules and fires itself —\n * no server round-trip, no delivery infrastructure. Use it for reminders,\n * streak nudges, \"come back tomorrow\" prompts, and anything else the app\n * already knows it wants to surface at a future time. This is distinct\n * from *remote push* (`Amba.push.*`), where Amba's backend decides when\n * to deliver a notification and routes it through APNs/FCM.\n *\n * The scheduling itself is performed by the device's notification\n * framework via `expo-notifications` — the same library Expo and bare RN\n * apps already use for push-token capture. We require it LAZILY (a runtime\n * `import(\"expo-notifications\")`) so:\n *\n * - apps that never schedule a local notification don't pay the bundle\n * cost, and\n * - the module imports cleanly under plain Node / SSR (the SDK is a\n * pure-JS fetch client elsewhere; nothing here forces a native module\n * to evaluate at import time).\n *\n * If `expo-notifications` is not installed, every method throws a typed\n * `AmbaApiError` with a vendor-neutral, actionable message — never a\n * silent no-op.\n */\n\nimport { AmbaApiError } from \"./error.js\";\nimport type {\n LocalNotificationHandle,\n ScheduleLocalNotificationInput,\n ScheduledLocalNotification,\n} from \"./types.js\";\n\n/**\n * The narrow slice of the `expo-notifications` surface we depend on. We\n * type it structurally rather than importing the package's types so the\n * SDK builds with `expo-notifications` absent (it's an optional peer dep).\n */\ninterface ExpoNotificationsLike {\n scheduleNotificationAsync(request: {\n identifier?: string;\n content: { title: string; body: string; data?: Record<string, unknown> };\n trigger: unknown;\n }): Promise<string>;\n cancelScheduledNotificationAsync(identifier: string): Promise<void>;\n getAllScheduledNotificationsAsync(): Promise<\n Array<{\n identifier: string;\n content?: { title?: string | null; body?: string | null; data?: unknown };\n trigger?: unknown;\n }>\n >;\n getPermissionsAsync(): Promise<{ status: string; granted?: boolean }>;\n requestPermissionsAsync(): Promise<{ status: string; granted?: boolean }>;\n SchedulableTriggerInputTypes?: {\n DATE: string;\n TIME_INTERVAL: string;\n };\n}\n\n/**\n * Marker key stamped into every Amba-scheduled notification's `data`. Lets\n * `listScheduledLocal` distinguish OUR notifications from any other\n * `expo-notifications` schedules the host app created — so foreign\n * schedules never leak into the Amba list. Mirrors the web SDK's\n * `__ambaLocalId` marker.\n */\nconst AMBA_LOCAL_ID_KEY = \"__ambaLocalId\";\n\n/**\n * Generate a stable local-notification id when the caller omits one.\n */\nfunction genLocalId(): string {\n const c = (\n globalThis as { crypto?: { randomUUID?: () => string } }\n ).crypto;\n if (c?.randomUUID) return c.randomUUID();\n return `amba_local_${Date.now()}_${Math.random().toString(36).slice(2)}`;\n}\n\n/**\n * Resolve the id a schedule will be filed under. A caller may omit `id`\n * (we mint one) but must not pass a BLANK/whitespace id: `cancelLocal`\n * rejects blank ids, so returning one in the handle would make the\n * notification uncancellable. Reject blank at schedule time so every\n * returned handle id round-trips through `cancelLocal`. Mirrors the web SDK.\n */\nfunction normalizeScheduleId(rawId: string | undefined): string {\n if (rawId === undefined) return genLocalId();\n if (typeof rawId !== \"string\" || rawId.trim() === \"\") {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.notifications.scheduleLocal: id, when provided, must be a non-empty string\",\n );\n }\n return rawId;\n}\n\nasync function loadExpoNotifications(): Promise<ExpoNotificationsLike> {\n try {\n // Dynamic import: keeps `expo-notifications` out of the bundle for apps\n // that never schedule a local notification, and lets this module import\n // under plain Node where the native package isn't present.\n const mod = (await import(\"expo-notifications\")) as unknown as {\n default?: ExpoNotificationsLike;\n } & ExpoNotificationsLike;\n return (mod.default ?? mod) as ExpoNotificationsLike;\n } catch (err) {\n throw new AmbaApiError(\n \"LOCAL_NOTIFICATIONS_UNAVAILABLE\",\n \"Amba.notifications requires the `expo-notifications` peer dependency. Install it with `npx expo install expo-notifications` (works in both Expo and bare React Native apps).\",\n err,\n );\n }\n}\n\n/**\n * Translate Amba's platform-neutral trigger into the shape\n * `expo-notifications` expects. We build object triggers keyed by the\n * library's `SchedulableTriggerInputTypes` when present (SDK 0.27+),\n * falling back to the legacy `{ date }` / `{ seconds }` shorthand the\n * older API accepts — both forms remain supported by current releases.\n */\nfunction buildExpoTrigger(\n input: ScheduleLocalNotificationInput,\n Notifications: ExpoNotificationsLike,\n): unknown {\n const types = Notifications.SchedulableTriggerInputTypes;\n const trigger = input.trigger;\n\n // Guard that `trigger` is a non-null object before `in`-probing it — a\n // missing or non-object trigger must surface the documented\n // INVALID_ARGUMENT AmbaApiError, not a raw `TypeError` from `\"at\" in null`.\n if (!trigger || typeof trigger !== \"object\") {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.notifications.scheduleLocal: trigger must be { at: Date } or { inSeconds: number, repeats?: boolean }\",\n );\n }\n\n if (\"at\" in trigger) {\n const at = trigger.at instanceof Date ? trigger.at : new Date(trigger.at);\n if (Number.isNaN(at.getTime())) {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.notifications.scheduleLocal: trigger.at must be a valid Date\",\n );\n }\n return types ? { type: types.DATE, date: at } : { date: at };\n }\n\n if (\"inSeconds\" in trigger) {\n const seconds = trigger.inSeconds;\n if (!Number.isFinite(seconds) || seconds < 0) {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.notifications.scheduleLocal: trigger.inSeconds must be a non-negative number\",\n );\n }\n const repeats = \"repeats\" in trigger ? Boolean(trigger.repeats) : false;\n return types\n ? { type: types.TIME_INTERVAL, seconds, repeats }\n : { seconds, repeats };\n }\n\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.notifications.scheduleLocal: trigger must be { at: Date } or { inSeconds: number, repeats?: boolean }\",\n );\n}\n\nfunction validateContent(input: ScheduleLocalNotificationInput): void {\n if (!input || typeof input.title !== \"string\" || input.title.trim() === \"\") {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.notifications.scheduleLocal: title is required\",\n );\n }\n if (typeof input.body !== \"string\") {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.notifications.scheduleLocal: body must be a string\",\n );\n }\n}\n\n/**\n * The `notifications` namespace. Shared by `@layers/amba-react-native`\n * and (via re-export) `@layers/amba-expo`.\n */\nexport const notifications = {\n /**\n * Schedule a notification the device will fire locally — no server\n * round-trip. Requests the OS notification permission on first use.\n *\n * @returns a handle whose `id` can be passed to `cancelLocal`.\n * @throws `AmbaApiError`:\n * - `LOCAL_NOTIFICATIONS_UNAVAILABLE` — `expo-notifications` not installed.\n * - `LOCAL_NOTIFICATIONS_PERMISSION_DENIED` — user declined the prompt.\n * - `INVALID_ARGUMENT` — bad title / body / trigger.\n *\n * @example\n * const { id } = await Amba.notifications.scheduleLocal({\n * title: 'Keep your streak alive',\n * body: 'You have a lesson waiting.',\n * trigger: { inSeconds: 60 * 60 * 12 },\n * });\n */\n async scheduleLocal(\n input: ScheduleLocalNotificationInput,\n ): Promise<LocalNotificationHandle> {\n validateContent(input);\n // Resolve + validate the id up front: blank caller ids are rejected (they\n // wouldn't round-trip through `cancelLocal`), and an omitted id is minted\n // here so we can both stamp the Amba marker and return a cancellable id.\n const id = normalizeScheduleId(input.id);\n const Notifications = await loadExpoNotifications();\n const trigger = buildExpoTrigger(input, Notifications);\n\n const existing = await Notifications.getPermissionsAsync();\n let granted = existing.granted ?? existing.status === \"granted\";\n if (!granted) {\n const requested = await Notifications.requestPermissionsAsync();\n granted = requested.granted ?? requested.status === \"granted\";\n }\n if (!granted) {\n throw new AmbaApiError(\n \"LOCAL_NOTIFICATIONS_PERMISSION_DENIED\",\n \"user denied notification permission — local notifications cannot be scheduled\",\n );\n }\n\n // Pass our resolved id as the platform identifier AND stamp the Amba\n // marker into `data`, so `listScheduledLocal` can tell our notifications\n // apart from any other `expo-notifications` schedules in the host app.\n const scheduledId = await Notifications.scheduleNotificationAsync({\n identifier: id,\n content: {\n title: input.title,\n body: input.body,\n data: { ...input.data, [AMBA_LOCAL_ID_KEY]: id },\n },\n trigger,\n });\n // Prefer our resolved id (what `cancelLocal` expects); fall back to the\n // platform-returned id only if the library ignored our identifier.\n return { id: scheduledId || id };\n },\n\n /**\n * Cancel a previously scheduled local notification by id. No-throw if\n * the id is unknown (the device framework treats it as already-gone),\n * but still throws `LOCAL_NOTIFICATIONS_UNAVAILABLE` if the peer dep is\n * absent so callers learn about the missing integration.\n */\n async cancelLocal(id: string): Promise<void> {\n if (typeof id !== \"string\" || id.trim() === \"\") {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.notifications.cancelLocal: id is required\",\n );\n }\n const Notifications = await loadExpoNotifications();\n await Notifications.cancelScheduledNotificationAsync(id);\n },\n\n /**\n * List the notifications Amba has scheduled to fire on this device.\n * Returns an empty array when none are pending.\n *\n * `getAllScheduledNotificationsAsync` returns EVERY pending\n * `expo-notifications` schedule in the app — including ones the host app\n * created directly. We filter to only entries carrying the Amba marker\n * (`data.__ambaLocalId`) so foreign schedules never leak into the Amba\n * list. (Mirrors the web SDK's marker filter.) These are pending by\n * definition — `getAllScheduledNotificationsAsync` does not return\n * already-delivered notifications.\n */\n async listScheduledLocal(): Promise<ScheduledLocalNotification[]> {\n const Notifications = await loadExpoNotifications();\n const scheduled = await Notifications.getAllScheduledNotificationsAsync();\n const out: ScheduledLocalNotification[] = [];\n for (const n of scheduled) {\n const data = (n.content?.data as Record<string, unknown> | undefined) ?? {};\n const markerId = data[AMBA_LOCAL_ID_KEY] as string | undefined;\n // Only OUR notifications carry the marker; skip everything else.\n if (!markerId) continue;\n out.push({\n id: markerId,\n title: n.content?.title ?? \"\",\n body: n.content?.body ?? \"\",\n data,\n });\n }\n return out;\n },\n};\n\nexport type LocalNotificationsNamespace = typeof notifications;\n","/**\n * `onExperimentAssigned` bookkeeping.\n *\n * `experiments.getVariant(key)` hands every result to {@link\n * ExperimentAssignmentReporter.report}. The reporter calls the app's\n * listeners once per experiment per user session: the first time a user's\n * assignment for a key comes back while at least one listener is subscribed.\n * Later calls for the same user and key return without calling them again.\n * A session lasts until sign-out ({@link ExperimentAssignmentReporter.reset})\n * or until the page or app process ends; the memory is in-process.\n *\n * The reporter remembers the most recently seen `maxReported` user and\n * experiment pairs (least recently seen goes first), so a long-lived server\n * process keeps its memory bounded.\n *\n * Listener isolation: a listener runs synchronously and is never awaited. A\n * throw (or a rejected promise it returns) is caught and logged, so a broken\n * analytics forwarder can never fail or delay the SDK call that produced the\n * assignment.\n */\nimport type { ExperimentAssignment } from \"./types\";\n\nexport type ExperimentAssignedListener = (\n assignment: ExperimentAssignment,\n) => void | Promise<void>;\n\n/** Keys remembered before the oldest are forgotten (bounds memory). */\nconst DEFAULT_MAX_REPORTED = 10_000;\n\nexport class ExperimentAssignmentReporter {\n private readonly listeners = new Set<ExperimentAssignedListener>();\n // Insertion-ordered, so the first key is the oldest when trimming.\n private readonly reported = new Set<string>();\n\n constructor(private readonly maxReported: number = DEFAULT_MAX_REPORTED) {}\n\n /** Add a listener. Returns a function that removes it. */\n subscribe(listener: ExperimentAssignedListener): () => void {\n if (typeof listener !== \"function\") {\n throw new TypeError(\"onExperimentAssigned expects a function\");\n }\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /**\n * Call the listeners for `assignment` when this user has not reported this\n * experiment yet in the session. Returns `assignment` unchanged.\n */\n report(assignment: ExperimentAssignment): ExperimentAssignment {\n if (this.listeners.size === 0) return assignment;\n const id = `${assignment.bucketingId}\\u0000${assignment.experimentKey}`;\n if (this.reported.has(id)) {\n // Seen again: move it to the most recent end so the bound drops the\n // least recently seen pair first.\n this.reported.delete(id);\n this.reported.add(id);\n return assignment;\n }\n this.reported.add(id);\n if (this.reported.size > this.maxReported) {\n const oldest = this.reported.values().next().value;\n if (oldest !== undefined) this.reported.delete(oldest);\n }\n for (const listener of Array.from(this.listeners)) {\n try {\n const result = listener({ ...assignment });\n if (result && typeof (result as Promise<void>).then === \"function\") {\n (result as Promise<void>).then(undefined, logListenerError);\n }\n } catch (err) {\n logListenerError(err);\n }\n }\n return assignment;\n }\n\n /** Start a new session: every experiment reports again. */\n reset(): void {\n this.reported.clear();\n }\n}\n\nfunction logListenerError(err: unknown): void {\n console.warn(\"[amba] onExperimentAssigned listener threw\", err);\n}\n","/**\n * On-device cache of remote config and feature flags for React Native.\n *\n * The same model as the SDK core's cache (the web and native SDKs):\n *\n * - Lookup order: the value from a fetch made since launch, else the value\n * read back from AsyncStorage, else the app's `configDefaults` entry, else\n * the caller's default. A key a successful fetch omits (the server leaves\n * out a config whose kill switch is off, or a disabled flag) leaves the\n * cache with that fetch, so the lookup falls through to the defaults.\n * - One AsyncStorage record per project: the key hashes the API base URL and\n * the client key, so another project (or environment) never reads it.\n * Inside, each signed-in user has a slot keyed by a hash of the app user\n * id, and responses evaluated without a signed-in user (requests sent\n * before sign-in, or answered with each key's default value because the\n * session had expired) share the `anon` slot. Five slots at most; the\n * least recently written go first.\n * - The SDK serves the current user's own slot; config or flags it doesn't\n * hold, and every lookup without a signed-in user, come from the `anon`\n * slot. One user's slot is never served to another identity. Sign-out\n * deletes the signed-out user's slot.\n * - A fetch is cached only when the identity it was sent for is still the\n * current one and no sign-out happened while it ran.\n */\n\nimport type { ConfigBundle, FlagAssignment } from \"./types.js\";\n\n/** Prefix of the per-project AsyncStorage record key. */\nexport const CACHE_KEY_PREFIX = \"amba_rc_v1_\";\n/** Slot for values evaluated without a signed-in user. */\nexport const ANON_SLOT = \"anon\";\n/** Most user slots one project record keeps. */\nexport const MAX_SLOTS = 5;\n\nconst TWO_32 = 4294967296;\n\n/**\n * 64-bit FNV-1a over the string's UTF-16 code units (low byte, then high\n * byte), as 16 hex digits. Computed in two 32-bit halves: some React Native\n * engines (JavaScriptCore on older Android builds) have no BigInt.\n */\nexport function fnv1a64Hex(text: string): string {\n let hi = 0xcbf29ce4;\n let lo = 0x84222325;\n const step = (byte: number): void => {\n lo = (lo ^ byte) >>> 0;\n // hash *= 0x100000001b3 (mod 2^64) = hash * 0x1b3 + (hash << 40).\n const low = lo * 0x1b3;\n const carry = Math.floor(low / TWO_32);\n const high = hi * 0x1b3 + carry + ((lo << 8) >>> 0);\n lo = low % TWO_32;\n hi = high % TWO_32;\n };\n for (let i = 0; i < text.length; i++) {\n const unit = text.charCodeAt(i);\n step(unit & 0xff);\n step(unit >>> 8);\n }\n return hi.toString(16).padStart(8, \"0\") + lo.toString(16).padStart(8, \"0\");\n}\n\n/** The record key for a project: a hash of the base URL and client key. */\nexport function recordKey(baseUrl: string, clientKey: string): string {\n return `${CACHE_KEY_PREFIX}${fnv1a64Hex(`${baseUrl.replace(/\\/+$/, \"\")}\\n${clientKey}`)}`;\n}\n\n/** The slot key for an identity: `u` + a hash of the user id, or `anon`. */\nexport function slotKey(appUserId: string | null | undefined): string {\n return appUserId ? `u${fnv1a64Hex(appUserId)}` : ANON_SLOT;\n}\n\n/**\n * The user a config response was evaluated for, from its `ETag`:\n * `\"<version>:<app user id>:<segments>\"` for a signed-in caller, `\"<version>\"`\n * for every other one (no session, or an expired or revoked token, which the\n * server answers with each key's default value). `undefined` without an\n * `ETag`; `null` for an evaluation for nobody.\n */\nexport function etagEvaluatedFor(etag: string | null | undefined): string | null | undefined {\n if (!etag) return undefined;\n const parts = etag.trim().replace(/^W\\//, \"\").replace(/^\"|\"$/g, \"\").split(\":\");\n return parts.length > 1 && parts[1] ? parts[1] : null;\n}\n\ninterface Slot {\n saved_at: number;\n config?: ConfigBundle;\n flags?: FlagAssignment[];\n}\n\ninterface CacheRecord {\n v: 1;\n slots: Record<string, Slot>;\n}\n\n/** Taken when a request starts; handed back with its response. */\nexport interface FetchTicket {\n readonly generation: number;\n readonly requestedFor: string | null;\n}\n\n/** The storage the cache reads once at configure and writes behind. */\nexport interface RemoteValuesStore {\n read(key: string): Promise<string | null>;\n write(key: string, value: string): Promise<void>;\n delete(key: string): Promise<void>;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction validSlot(raw: unknown): Slot | null {\n if (!isPlainObject(raw)) return null;\n const slot: Slot = {\n saved_at: typeof raw[\"saved_at\"] === \"number\" ? raw[\"saved_at\"] : 0,\n };\n const config = raw[\"config\"];\n if (isPlainObject(config) && isPlainObject(config[\"values\"])) {\n slot.config = {\n version: typeof config[\"version\"] === \"string\" ? config[\"version\"] : null,\n values: config[\"values\"],\n };\n }\n const flags = raw[\"flags\"];\n if (Array.isArray(flags)) {\n slot.flags = flags.filter(\n (f): f is FlagAssignment =>\n isPlainObject(f) &&\n typeof f[\"name\"] === \"string\" &&\n typeof f[\"enabled\"] === \"boolean\",\n );\n }\n return slot.config || slot.flags ? slot : null;\n}\n\nfunction upsert(list: FlagAssignment[], flag: FlagAssignment): void {\n const i = list.findIndex((f) => f.name === flag.name);\n if (i >= 0) list[i] = flag;\n else list.push(flag);\n}\n\nexport class RemoteValuesCache {\n private record: CacheRecord = { v: 1, slots: {} };\n /** The slot key the held values were resolved for. */\n private viewFor: string | null = null;\n private config: ConfigBundle | null = null;\n private flags: FlagAssignment[] | null = null;\n /** Bumped by every sign-out; an older ticket's response is not cached. */\n private generation = 0;\n private writes: Promise<void> = Promise.resolve();\n\n constructor(\n private readonly store: RemoteValuesStore,\n readonly key: string,\n private readonly defaults: Record<string, unknown>,\n private readonly currentUser: () => string | null,\n ) {}\n\n /** Read the project's record from storage. `configure()` awaits it. */\n async load(): Promise<void> {\n let raw: string | null = null;\n try {\n raw = await this.store.read(this.key);\n } catch {\n return; // Storage unavailable: the cache works in memory.\n }\n if (!raw) return;\n try {\n const parsed = JSON.parse(raw) as unknown;\n const slots: Record<string, Slot> = {};\n if (isPlainObject(parsed) && isPlainObject(parsed[\"slots\"])) {\n for (const [k, v] of Object.entries(parsed[\"slots\"])) {\n const slot = validSlot(v);\n if (slot) slots[k] = slot;\n }\n }\n this.record = { v: 1, slots };\n } catch {\n this.record = { v: 1, slots: {} };\n this.persist();\n }\n }\n\n /** Take a ticket for a request about to be sent for the current user. */\n ticket(): FetchTicket {\n return { generation: this.generation, requestedFor: this.currentUser() };\n }\n\n // ── reads ──────────────────────────────────────────────────────────\n\n /** The value for config `key` (fetched, cached, then default); `undefined` when none. */\n configValue(key: string): unknown {\n this.ensureView();\n const values = this.config?.values;\n if (values && Object.prototype.hasOwnProperty.call(values, key)) {\n const v = values[key];\n if (v !== null && v !== undefined) return v;\n }\n return this.defaultFor(key);\n }\n\n /** The held assignment for flag `key`, or `null`. */\n flag(key: string): FlagAssignment | null {\n this.ensureView();\n return this.flags?.find((f) => f.name === key) ?? null;\n }\n\n /** Whether flag `key` is on: the held assignment, then a boolean default. */\n flagEnabled(key: string): boolean | undefined {\n const flag = this.flag(key);\n if (flag) return flag.enabled;\n const d = this.defaultFor(key);\n return typeof d === \"boolean\" ? d : undefined;\n }\n\n // ── writes ─────────────────────────────────────────────────────────\n\n /**\n * Hold and persist a fetched bundle; returns it. A bundle the server\n * evaluated for nobody although the request carried a user (an expired or\n * revoked session) goes to the `anon` slot and leaves the user's values\n * alone; one evaluated for another user is not cached.\n */\n storeConfig(ticket: FetchTicket, bundle: ConfigBundle, etag?: string | null): ConfigBundle {\n if (!isPlainObject(bundle.values)) return bundle;\n const evaluatedFor = etagEvaluatedFor(etag);\n let target: string | undefined;\n if (ticket.requestedFor && evaluatedFor === null) target = ANON_SLOT;\n else if (typeof evaluatedFor === \"string\" && evaluatedFor !== ticket.requestedFor) {\n return bundle;\n }\n this.write(ticket, target, (slot, held) => {\n slot.config = bundle;\n if (held) this.config = bundle;\n });\n return bundle;\n }\n\n /** Hold and persist a fetched flag list (it replaces the held one). */\n storeFlags(ticket: FetchTicket, flags: FlagAssignment[]): FlagAssignment[] {\n this.write(ticket, undefined, (slot, held) => {\n slot.flags = [...flags];\n if (held) this.flags = [...flags];\n });\n return flags;\n }\n\n /** Hold and persist one flag from a single-flag lookup; returns it. */\n upsertFlag(ticket: FetchTicket, flag: FlagAssignment): FlagAssignment {\n this.write(ticket, undefined, (slot, held) => {\n this.adoptHeldFlags(slot, held);\n slot.flags = slot.flags ?? [];\n upsert(slot.flags, flag);\n if (held) {\n this.flags = this.flags ?? [];\n upsert(this.flags, flag);\n }\n });\n return flag;\n }\n\n /** Drop flag `key` after a single-flag lookup found it off or unknown. */\n removeFlag(ticket: FetchTicket, key: string): void {\n this.write(ticket, undefined, (slot, held) => {\n this.adoptHeldFlags(slot, held);\n if (slot.flags) slot.flags = slot.flags.filter((f) => f.name !== key);\n if (held && this.flags) this.flags = this.flags.filter((f) => f.name !== key);\n });\n }\n\n /**\n * Sign-out: delete the signed-out user's slot and drop the held values;\n * later lookups serve the `anon` slot, then the defaults.\n */\n clearForSignOut(signedOutUser: string | null): void {\n this.generation += 1;\n this.config = null;\n this.flags = null;\n this.viewFor = null;\n if (!signedOutUser) return;\n const doomed = slotKey(signedOutUser);\n if (this.record.slots[doomed]) {\n delete this.record.slots[doomed];\n this.persist();\n }\n }\n\n /** Resolves when every write so far reached storage. */\n flushed(): Promise<void> {\n return this.writes;\n }\n\n // ── internals ──────────────────────────────────────────────────────\n\n /**\n * Before a single-flag change to the current user's slot: when the slot\n * holds no flag list and the served list is the signed-out one, the\n * user's slot starts from that list, so after a relaunch the user reads\n * the same flags the change was made on.\n */\n private adoptHeldFlags(slot: Slot, held: boolean): void {\n if (held && !slot.flags && this.flags) slot.flags = [...this.flags];\n }\n\n private defaultFor(key: string): unknown {\n if (!Object.prototype.hasOwnProperty.call(this.defaults, key)) return undefined;\n const v = this.defaults[key];\n return v === null ? undefined : v;\n }\n\n /**\n * `held` is `true` when the slot written is the one the current identity\n * reads, so the served values change with it.\n */\n private write(\n ticket: FetchTicket,\n targetOverride: string | undefined,\n apply: (slot: Slot, held: boolean) => void,\n ): void {\n const current = slotKey(this.currentUser());\n if (ticket.generation !== this.generation || slotKey(ticket.requestedFor) !== current) {\n return; // identity changed (or a sign-out happened) mid-flight\n }\n this.ensureView();\n const target = targetOverride ?? current;\n const existing = this.record.slots[target];\n const slot: Slot = existing\n ? {\n saved_at: existing.saved_at,\n ...(existing.config ? { config: existing.config } : {}),\n ...(existing.flags ? { flags: [...existing.flags] } : {}),\n }\n : { saved_at: 0 };\n apply(slot, target === current);\n if (!slot.config && !slot.flags) {\n // Nothing to keep for this identity: keep no empty slot.\n if (!existing) return;\n delete this.record.slots[target];\n } else {\n slot.saved_at = Date.now();\n this.record.slots[target] = slot;\n this.prune(target);\n }\n this.persist();\n // The signed-out slot changed under a signed-in user: the parts the user\n // reads from it are re-resolved on the next lookup.\n if (target !== current) this.viewFor = null;\n }\n\n /** Serve the current identity's own slot, the `anon` slot for the rest. */\n private ensureView(): void {\n const current = slotKey(this.currentUser());\n if (this.viewFor === current) return;\n const own = this.record.slots[current];\n const anon = current === ANON_SLOT ? undefined : this.record.slots[ANON_SLOT];\n const config = own?.config ?? anon?.config;\n const flags = own?.flags ?? anon?.flags;\n this.config = config ?? null;\n this.flags = flags ? [...flags] : null;\n this.viewFor = current;\n }\n\n /**\n * Keep the MAX_SLOTS most recently written user slots; `keep` and the\n * signed-out slot (read before sign-in and for every user's gaps) stay.\n */\n private prune(keep: string): void {\n const keys = Object.keys(this.record.slots).filter((k) => k !== ANON_SLOT);\n while (keys.length > MAX_SLOTS) {\n let oldest: string | null = null;\n for (const k of keys) {\n if (k === keep) continue;\n if (\n oldest === null ||\n this.record.slots[k]!.saved_at < this.record.slots[oldest]!.saved_at\n ) {\n oldest = k;\n }\n }\n if (oldest === null) break;\n delete this.record.slots[oldest];\n keys.splice(keys.indexOf(oldest), 1);\n }\n }\n\n private persist(): void {\n const empty = Object.keys(this.record.slots).length === 0;\n const text = empty ? null : JSON.stringify(this.record);\n // Writes chain so an older record never lands after a newer one.\n this.writes = this.writes\n .then(() => (text === null ? this.store.delete(this.key) : this.store.write(this.key, text)))\n .catch(() => {\n // Best effort: the held values stay in memory for this launch.\n });\n }\n}\n","/**\n * Values for the `X-App-Version`, `X-Device-Region` and `X-Device-OS`\n * request headers. Each is omitted when unknown, so every helper here\n * returns `undefined` for a value the header can't carry as-is.\n */\n\n/** Longest `X-App-Version` value sent (matches the core's limit). */\nexport const MAX_APP_VERSION_LENGTH = 64;\n\n/**\n * An app version for `X-App-Version`: the text before the first whitespace\n * (`\"2.3.1 (45)\"` sends `2.3.1`), which must start with an ASCII letter or\n * digit, hold only letters, digits and `.` `_` `+` `-`, and be at most\n * {@link MAX_APP_VERSION_LENGTH} characters: the shape the API records for\n * segment targeting. Anything else returns `undefined`.\n */\nexport function normalizeAppVersion(\n raw: string | null | undefined,\n): string | undefined {\n if (typeof raw !== \"string\") return undefined;\n const v = raw.trim().split(/\\s+/)[0] ?? \"\";\n if (v.length === 0 || v.length > MAX_APP_VERSION_LENGTH) return undefined;\n return /^[0-9A-Za-z][0-9A-Za-z._+-]*$/.test(v) ? v : undefined;\n}\n\n/**\n * The region subtag of a BCP 47 locale tag (`en-US` → `US`, `zh-Hant-TW` →\n * `TW`, POSIX `fr_FR.UTF-8` → `FR`, iOS `en_US@rg=gbzzzz` → `US`),\n * uppercased. Returns `undefined` when the tag carries no two-letter region\n * (`en`, `es-419`, `sr-Latn`).\n */\nexport function regionFromLocale(\n tag: string | null | undefined,\n): string | undefined {\n if (typeof tag !== \"string\" || tag.length === 0) return undefined;\n const parts = tag.split(/[.@]/)[0]!.split(/[-_]/);\n for (let i = 1; i < parts.length; i++) {\n const part = parts[i]!;\n // A one-letter singleton starts an extension (`-u-`, `-x-`); no region\n // subtag follows it.\n if (part.length === 1) break;\n if (/^[A-Za-z]{2}$/.test(part)) return part.toUpperCase();\n }\n return undefined;\n}\n\n/** The values `X-Device-OS` takes. */\nexport type DeviceOs =\n | \"ios\"\n | \"android\"\n | \"web\"\n | \"macos\"\n | \"windows\"\n | \"linux\"\n | \"other\";\n\nconst DEVICE_OS_VALUES: readonly DeviceOs[] = [\n \"ios\",\n \"android\",\n \"web\",\n \"macos\",\n \"windows\",\n \"linux\",\n \"other\",\n];\n\n/**\n * Map a platform OS name (React Native's `Platform.OS`, for example) to an\n * `X-Device-OS` value. Listed names pass through lowercased; any other\n * non-empty name is a known-but-unlisted OS and maps to `\"other\"`. Empty or\n * missing returns `undefined`.\n */\nexport function deviceOsFromPlatform(\n os: string | null | undefined,\n): DeviceOs | undefined {\n if (typeof os !== \"string\") return undefined;\n const v = os.trim().toLowerCase();\n if (v.length === 0) return undefined;\n return (DEVICE_OS_VALUES as readonly string[]).includes(v)\n ? (v as DeviceOs)\n : \"other\";\n}\n","/**\n * Live collection / messaging / gamification subscriptions for\n * `@layers/amba-react-native`.\n *\n * Public surface is byte-for-byte identical to `@layers/amba-web` and\n * `@layers/amba-node` so the docs are uniform across every JS SDK:\n *\n * const unsub = Amba.collection(\"messages\")\n * .where(\"circle_id\", circleId)\n * .subscribe((change) => {\n * // change.op: \"INSERT\" | \"UPDATE\" | \"DELETE\"; change.row: the row\n * });\n * unsub(); // stop listening\n *\n * const unsub2 = Amba.messaging\n * .conversation(conversationId)\n * .subscribe((change) => { ... });\n *\n * const unsub3 = Amba.gamification.subscribe((change) => { ... });\n *\n * ── Why a different transport from web/node ─────────────────────────────\n * The web/node SDKs read the streamed HTTP response body via\n * `response.body.getReader()`. React Native's `fetch` (Hermes / RN's\n * networking layer) resolves the Response only AFTER the body is fully\n * received and does NOT expose a readable `body` stream — so the web\n * transport silently never delivers a frame on a long-lived connection.\n *\n * Instead we drive the SAME server endpoint (an event stream) through\n * `XMLHttpRequest`, which RN implements with INCREMENTAL `responseText`:\n * `onreadystatechange` fires at `readyState === 3` (LOADING) as bytes\n * arrive, so we parse the newly-appended slice frame-by-frame. This is the\n * mechanism `react-native-sse` uses under the hood; we inline it to avoid a\n * peer dependency (and to set the auth header in `Authorization`, keeping\n * the session token out of the URL/query string where it would leak into\n * access logs — `EventSource` can't set headers).\n *\n * If XHR is somehow unavailable in the host runtime, we fall back to the\n * web-style `fetch` + streamed-body reader (works on RN-for-web / any\n * runtime that does expose `response.body`). Both transports speak the\n * exact same wire frames, so the public behaviour is identical.\n *\n * Reconnect/backoff and frame parsing match web/node exactly.\n */\n\nimport { pathSegment } from \"./path-segment.js\";\nimport { DEFAULT_REQUEST_TIMEOUT_MS, raceDeadline } from \"./request-deadline.js\";\n\nexport type RowChangeOp = \"INSERT\" | \"UPDATE\" | \"DELETE\";\n\nexport interface RealtimeChange<T = unknown> {\n /** Which kind of write happened. */\n op: RowChangeOp;\n /**\n * The affected row. For deletes this is a partial row (at minimum `id`).\n * If the row was too large to deliver inline, `truncated` is `true` and\n * `row` carries just `id` — re-fetch with `Amba.collections.find()` /\n * `findOne()` when you need the full body.\n */\n row: T;\n truncated?: boolean;\n}\n\nexport interface SubscribeOptions {\n /** Called once when the live connection is (re)established. */\n onOpen?: () => void;\n /** Called on a connection error before the SDK retries. */\n onError?: (err: unknown) => void;\n}\n\n/** Unsubscribe handle returned by `.subscribe(...)`. Call it to stop. */\nexport type Unsubscribe = () => void;\n\n/**\n * Auth + endpoint context for a subscription. `authHeaders` is read lazily\n * at connect time so reconnects pick up a rotated session token. Returns\n * `null` when there's no live session (subscribe throws).\n */\nexport interface RealtimeContext {\n baseUrl: string;\n authHeaders: () => Record<string, string> | null;\n /**\n * How long a connection may take to start streaming, in milliseconds\n * (the configured `requestTimeoutMs`; default 15 000). A connection that\n * hasn't started by then is aborted, reported through `onError`, and\n * retried with backoff. Once it starts, the stream stays open for as long\n * as the subscriber keeps it.\n */\n connectTimeoutMs?: number;\n}\n\nconst INITIAL_BACKOFF_MS = 500;\nconst MAX_BACKOFF_MS = 15_000;\n\nconst DEFAULT_API_URL = \"https://api.amba.dev\";\nconst DEFAULT_REALTIME_URL = \"https://realtime.amba.host\";\n\n/**\n * Resolve the base URL for live-update connections. Long-lived streams are\n * served from a dedicated realtime host (no request-duration ceiling), so\n * when the SDK points at the default production API the stream connects\n * there instead. Any custom `apiUrl` (dev, staging, self-hosted, proxy)\n * keeps live updates on that same origin — override semantics unchanged.\n */\nexport function resolveRealtimeBaseUrl(baseUrl: string): string {\n return baseUrl.replace(/\\/+$/, \"\") === DEFAULT_API_URL ? DEFAULT_REALTIME_URL : baseUrl;\n}\n\n/**\n * Builder returned by `Amba.collection(name)`. Chain `.where(...)` to\n * narrow, then `.subscribe(cb)` to start streaming.\n */\nexport class CollectionSubscription<T = unknown> {\n private readonly filters: Record<string, unknown> = {};\n\n constructor(\n private readonly ctx: RealtimeContext,\n private readonly name: string,\n ) {}\n\n /** Add an equality filter. Chainable. All filters must match (AND). */\n where(column: string, value: unknown): this {\n this.filters[column] = value;\n return this;\n }\n\n /**\n * Start listening. `cb` fires once per matching row change. Returns an\n * unsubscribe handle — call it to close the connection.\n */\n subscribe(cb: (change: RealtimeChange<T>) => void, options: SubscribeOptions = {}): Unsubscribe {\n const headers = this.ctx.authHeaders();\n if (!headers) {\n throw new Error(\n \"Amba.collection().subscribe() requires an authenticated session — call Amba.auth.signIn* first.\",\n );\n }\n const qs =\n Object.keys(this.filters).length > 0\n ? `?where=${encodeURIComponent(JSON.stringify(this.filters))}`\n : \"\";\n const url = `${resolveRealtimeBaseUrl(this.ctx.baseUrl)}/v1/client/realtime/collections/${pathSegment(\n this.name,\n )}${qs}`;\n return openStream<RealtimeChange<T>>({\n url,\n authHeaders: this.ctx.authHeaders,\n connectTimeoutMs: this.ctx.connectTimeoutMs,\n onChange: cb,\n options,\n parse: parseRowChangeFrame as (frame: string) => RealtimeChange<T> | null,\n });\n }\n}\n\n// ── Messaging: live conversation subscriptions ─────────────────────────────\n\n/**\n * A live message change in a conversation. `op` is \"INSERT\" for a new\n * message; moderation removals arrive as \"DELETE\". For a delete (or when a\n * message was too large to deliver inline) `row` may be partial — re-fetch\n * with `Amba.messaging.listMessages(conversationId)` when you need the full\n * body.\n */\nexport type MessageChange<T = unknown> = RealtimeChange<T>;\n\n/**\n * Builder returned by `Amba.messaging.conversation(id)`. Call `.subscribe(cb)`\n * to receive new messages live instead of polling.\n *\n * Delivery is scoped to conversation participants — you receive messages for\n * a conversation only if your session is a member of it. Reconnects with the\n * same exponential backoff as collection subscriptions.\n */\nexport class ConversationSubscription<T = unknown> {\n constructor(\n private readonly ctx: RealtimeContext,\n private readonly conversationId: string,\n ) {}\n\n subscribe(cb: (change: MessageChange<T>) => void, options: SubscribeOptions = {}): Unsubscribe {\n const headers = this.ctx.authHeaders();\n if (!headers) {\n throw new Error(\n \"Amba.messaging.conversation().subscribe() requires an authenticated session — call Amba.auth.signIn* first.\",\n );\n }\n const url = `${resolveRealtimeBaseUrl(this.ctx.baseUrl)}/v1/client/realtime/conversations/${pathSegment(\n this.conversationId,\n )}`;\n return openStream<MessageChange<T>>({\n url,\n authHeaders: this.ctx.authHeaders,\n connectTimeoutMs: this.ctx.connectTimeoutMs,\n onChange: cb,\n options,\n parse: parseRowChangeFrame as (frame: string) => MessageChange<T> | null,\n });\n }\n}\n\n// ── Gamification: live xp / level / achievement / streak updates ────────────\n\n/** Which slice of the signed-in user's gamification state changed. */\nexport type GamificationKind = \"xp\" | \"level\" | \"achievement\" | \"streak\";\n\n/**\n * A live change to the signed-in user's gamification state. `kind` says which\n * slice moved; `data` carries the new state inline so you can update without a\n * re-fetch:\n * - `xp` — `{ total_xp, level, delta }`\n * - `level` — `{ level, total_xp }`\n * - `achievement` — `{ achievement_id, unlocked }`\n * - `streak` — `{ streak_id, count, status, ... }`\n *\n * If a payload was too large to deliver inline, `truncated` is `true` and `data`\n * is empty — re-fetch with `Amba.xp.getBalance()` / `Amba.streaks.getAll()` etc.\n */\nexport interface GamificationChange<T = Record<string, unknown>> {\n kind: GamificationKind;\n data: T;\n truncated?: boolean;\n}\n\n/**\n * Builder returned by `Amba.gamification`. Call `.subscribe(cb)` to receive the\n * signed-in user's xp / level / achievement / streak changes live instead of\n * polling.\n *\n * Delivery is scoped to the signed-in user — you only ever receive your own\n * gamification changes, never another user's. Reconnects with the same\n * exponential backoff as collection subscriptions.\n */\nexport class GamificationSubscription {\n constructor(private readonly ctx: RealtimeContext) {}\n\n subscribe(cb: (change: GamificationChange) => void, options: SubscribeOptions = {}): Unsubscribe {\n const headers = this.ctx.authHeaders();\n if (!headers) {\n throw new Error(\n \"Amba.gamification.subscribe() requires an authenticated session — call Amba.auth.signIn* first.\",\n );\n }\n const url = `${resolveRealtimeBaseUrl(this.ctx.baseUrl)}/v1/client/realtime/gamification`;\n return openStream<GamificationChange>({\n url,\n authHeaders: this.ctx.authHeaders,\n connectTimeoutMs: this.ctx.connectTimeoutMs,\n onChange: cb,\n options,\n parse: parseGamificationFrame,\n });\n }\n}\n\n// ── Shared transport ───────────────────────────────────────────────────────\n\ninterface StreamArgs<E> {\n url: string;\n authHeaders: () => Record<string, string> | null;\n connectTimeoutMs?: number;\n onChange: (event: E) => void;\n options: SubscribeOptions;\n /** Turn one raw `\\n\\n`-delimited frame into a typed event, or null to skip. */\n parse: (frame: string) => E | null;\n}\n\n/**\n * Open a long-lived event stream and dispatch parsed events to `onChange`.\n * Reconnects with exponential backoff until the returned unsubscribe handle\n * is invoked. Chooses the XHR incremental-text transport (RN-native) when\n * available, else the streamed-`fetch`-body transport (RN-for-web / Node).\n */\nfunction openStream<E>(args: StreamArgs<E>): Unsubscribe {\n let stopped = false;\n let backoff = INITIAL_BACKOFF_MS;\n let closeActive: (() => void) | null = null;\n let settleActive: (() => void) | null = null;\n\n const run = async (): Promise<void> => {\n while (!stopped) {\n const headers = args.authHeaders();\n if (!headers) {\n // No active session (e.g. after signOut): stop reconnecting instead of\n // looping forever. A fresh subscribe() after sign-in starts a new stream.\n args.options.onError?.(new Error(\"no active session\"));\n break;\n }\n try {\n await new Promise<void>((resolve, reject) => {\n settleActive = resolve;\n // Reset backoff only once the connection proves HEALTHY (first\n // frame received), not merely on a successful connect — a server\n // that accepts then immediately drops would otherwise reset\n // backoff every loop → a tight reconnect storm.\n const onFirstFrame = (): void => {\n backoff = INITIAL_BACKOFF_MS;\n args.options.onOpen?.();\n };\n closeActive = connectOnce({\n url: args.url,\n headers,\n connectTimeoutMs:\n args.connectTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,\n onFrame: (frame) => {\n const ev = args.parse(frame);\n if (ev !== null) args.onChange(ev);\n },\n onFirstFrame,\n onDone: resolve,\n onError: reject,\n });\n });\n } catch (err) {\n if (stopped) break;\n args.options.onError?.(err);\n } finally {\n // Abort the just-ended/failed connection BEFORE reconnecting so a failed\n // attempt never leaves a live XHR/fetch open (which would duplicate\n // deliveries across retries). Abort is safe on an already-closed conn.\n closeActive?.();\n closeActive = null;\n settleActive = null;\n }\n if (stopped) break;\n await delay(backoff);\n backoff = Math.min(backoff * 2, MAX_BACKOFF_MS);\n }\n };\n\n void run();\n\n return () => {\n stopped = true;\n closeActive?.();\n // Settle the in-flight await so run() sees `stopped` and exits its loop\n // instead of hanging forever inside the connection Promise (no leaked coroutine).\n settleActive?.();\n };\n}\n\ninterface ConnectArgs {\n url: string;\n headers: Record<string, string>;\n /**\n * How long the response may take to start, in milliseconds. A connection\n * still waiting then fails through `onError` (and is retried).\n */\n connectTimeoutMs: number;\n /** One raw SSE frame (without the trailing blank line). */\n onFrame: (frame: string) => void;\n /** Fired once, the first time ANY frame is seen on this connection. */\n onFirstFrame: () => void;\n /** Stream ended cleanly (server closed). */\n onDone: () => void;\n /** Stream failed (transport error / non-2xx). */\n onError: (err: unknown) => void;\n}\n\n/**\n * Establish a single connection using the best available transport. Returns\n * an abort function. The XHR transport is preferred on React Native; the\n * fetch-stream transport covers RN-for-web and any runtime exposing a\n * readable `response.body`.\n */\nfunction connectOnce(args: ConnectArgs): () => void {\n if (typeof XMLHttpRequest !== \"undefined\") return connectXhr(args, XMLHttpRequest);\n return connectFetchStream(args);\n}\n\n/**\n * Incremental-`responseText` transport via XMLHttpRequest — the RN-native\n * path. `onreadystatechange` fires at readyState 3 (LOADING) as bytes\n * stream in; we track how much of `responseText` we've already parsed and\n * extract any newly-completed `\\n\\n`-delimited frames.\n */\nfunction connectXhr(args: ConnectArgs, XHR: typeof XMLHttpRequest): () => void {\n const xhr = new XHR();\n let consumed = 0; // chars of responseText already split into frames\n let buffer = \"\"; // partial trailing frame not yet terminated\n let sawFrame = false;\n let aborted = false;\n\n const drain = (): void => {\n // responseText is cumulative; only look at the newly-arrived slice.\n const text = xhr.responseText;\n if (text.length <= consumed) return;\n buffer += text.slice(consumed);\n consumed = text.length;\n let idx: number;\n while ((idx = buffer.indexOf(\"\\n\\n\")) !== -1) {\n const frame = buffer.slice(0, idx);\n buffer = buffer.slice(idx + 2);\n if (!sawFrame) {\n sawFrame = true;\n args.onFirstFrame();\n }\n args.onFrame(frame);\n }\n };\n\n xhr.open(\"GET\", args.url, true);\n for (const [k, v] of Object.entries(args.headers)) xhr.setRequestHeader(k, v);\n xhr.setRequestHeader(\"Accept\", \"text/event-stream\");\n\n // A connection whose response hasn't started by the deadline fails, so the\n // reconnect loop retries it. Some runtimes never report a dropped DNS\n // lookup, which would otherwise leave the subscription waiting forever.\n // Once the headers arrive the stream stays open for as long as it is kept.\n const connectTimer = setTimeout(() => {\n if (aborted || xhr.readyState >= 2) return;\n args.onError(\n new Error(`realtime connection timed out after ${args.connectTimeoutMs} ms`),\n );\n }, args.connectTimeoutMs);\n\n xhr.onreadystatechange = (): void => {\n if (aborted) return;\n // HEADERS_RECEIVED (2) and later: the response started.\n if (xhr.readyState >= 2) clearTimeout(connectTimer);\n // LOADING (3): body bytes are arriving — parse what we have so far.\n if (xhr.readyState === 3) {\n if (xhr.status >= 400) return; // error surfaced on DONE\n drain();\n }\n };\n\n xhr.onload = (): void => {\n if (aborted) return;\n if (xhr.status >= 200 && xhr.status < 300) {\n drain();\n args.onDone();\n } else {\n args.onError(new Error(`realtime connect failed: HTTP ${xhr.status}`));\n }\n };\n xhr.onerror = (): void => {\n if (aborted) return;\n args.onError(new Error(\"realtime transport error\"));\n };\n xhr.ontimeout = (): void => {\n if (aborted) return;\n args.onError(new Error(\"realtime connection timed out\"));\n };\n\n try {\n xhr.send();\n } catch (err) {\n clearTimeout(connectTimer);\n args.onError(err);\n }\n\n return () => {\n aborted = true;\n clearTimeout(connectTimer);\n try {\n xhr.abort();\n } catch {\n // best-effort\n }\n };\n}\n\n/**\n * Streamed-`fetch`-body transport — used when XHR isn't present (RN-for-web,\n * Node). Identical wire handling to web/node's `consume`.\n */\nfunction connectFetchStream(args: ConnectArgs): () => void {\n const controller = new AbortController();\n let aborted = false;\n\n void (async () => {\n try {\n // The response must start within the connect deadline; once it does,\n // the stream stays open for as long as it is kept.\n const resp = await raceDeadline(\n fetch(args.url, {\n method: \"GET\",\n headers: { ...args.headers, Accept: \"text/event-stream\" },\n signal: controller.signal,\n }),\n args.connectTimeoutMs,\n controller,\n (ms) => new Error(`realtime connection timed out after ${ms} ms`),\n );\n if (!resp.ok || !resp.body) {\n throw new Error(`realtime connect failed: HTTP ${resp.status}`);\n }\n const reader = (resp.body as ReadableStream<Uint8Array>).getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n let sawFrame = false;\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n let idx: number;\n while ((idx = buffer.indexOf(\"\\n\\n\")) !== -1) {\n const frame = buffer.slice(0, idx);\n buffer = buffer.slice(idx + 2);\n if (!sawFrame) {\n sawFrame = true;\n args.onFirstFrame();\n }\n args.onFrame(frame);\n }\n }\n if (!aborted) args.onDone();\n } catch (err) {\n if (!aborted) args.onError(err);\n }\n })();\n\n return () => {\n aborted = true;\n controller.abort();\n };\n}\n\n// ── Frame parsers (shared shapes with web/node) ─────────────────────────────\n\n/** Pull the `event:` name + joined `data:` lines out of one raw frame. */\nfunction readFrame(frame: string): { event: string; data: string } {\n let event = \"\";\n const dataLines: string[] = [];\n for (const line of frame.split(\"\\n\")) {\n if (line.startsWith(\":\")) continue; // keepalive comment\n if (line.startsWith(\"event:\")) event = line.slice(\"event:\".length).trim();\n else if (line.startsWith(\"data:\")) dataLines.push(line.slice(\"data:\".length).trim());\n }\n return { event, data: dataLines.join(\"\\n\") };\n}\n\nfunction parseRowChangeFrame<T>(frame: string): RealtimeChange<T> | null {\n const { event, data } = readFrame(frame);\n if (event !== \"change\") return null; // ignore ready / error / heartbeat\n try {\n const payload = JSON.parse(data) as {\n op: RowChangeOp;\n row: T;\n truncated?: boolean;\n };\n return { op: payload.op, row: payload.row, truncated: payload.truncated };\n } catch {\n return null; // malformed frame — skip rather than tear down the stream\n }\n}\n\nfunction parseGamificationFrame(frame: string): GamificationChange | null {\n const { event, data } = readFrame(frame);\n if (event !== \"change\") return null;\n try {\n const payload = JSON.parse(data) as {\n kind: GamificationKind;\n data: Record<string, unknown>;\n truncated?: boolean;\n };\n return { kind: payload.kind, data: payload.data, truncated: payload.truncated };\n } catch {\n return null;\n }\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","/**\n * @layers/amba-react-native — React Native SDK for amba.\n *\n * Pure-JS implementation: the SDK is now a thin client over `fetch`\n * plus AsyncStorage-backed identity. No native modules. The native\n * SDKs (Swift/Kotlin/Flutter/Unity via the shared runtime) keep\n * their core bindings; this RN SDK aligns with how comparable SDKs\n * ship to JS runtimes — pure fetch-over-HTTP.\n *\n * @example\n * ```tsx\n * import { Amba } from '@layers/amba-react-native';\n *\n * await Amba.configure({ projectId: 'proj_...', clientKey: 'amba_ck_...' });\n * await Amba.auth.signInAnonymously();\n * await Amba.events.track('app_opened');\n *\n * // Push registration with a token from expo-notifications:\n * import * as Notifications from 'expo-notifications';\n * const { data: token } = await Notifications.getDevicePushTokenAsync();\n * await Amba.push.register(token, 'apns'); // or 'fcm' on Android\n * ```\n */\n\nexport type * from \"./types\";\nimport type {\n AmbaConfig,\n AuthResult,\n User,\n FindOptions,\n FindResponse,\n Filter,\n FilterValue,\n ConfigBundle,\n FlagAssignment,\n ExperimentAssignment,\n PingResult,\n UserEntitlement,\n RestoreResult,\n OfferingsResult,\n MediaAsset,\n PresignData,\n PushPlatform,\n PushToken,\n AiMessageRequest,\n AiMessageResponse,\n Session,\n Achievement,\n AchievementProgress,\n Challenge,\n ChallengeProgress,\n CurrencyBalance,\n CurrencyTransaction,\n InventoryItem,\n InventoryPurchase,\n PurchaseRequest,\n ConsumeRequest,\n Leaderboard,\n LeaderboardEntry,\n Store,\n PurchaseOption,\n PurchaseResult,\n XpBalance,\n XpTransaction,\n Streak,\n FeedResponse,\n Friendship,\n Group,\n GroupMember,\n GroupCreate,\n GroupUpdate,\n Conversation,\n Message,\n SendMessageRequest,\n CreateConversationRequest,\n ListMessagesOptions,\n MarkReadResult,\n Report,\n ReportRequest,\n ReferralCode,\n ReferralClaim,\n Review,\n ReviewCreate,\n ReviewUpdate,\n Role,\n CatalogItem,\n ContentItem,\n DeepLink,\n DeepLinkCreate,\n OnboardingStatus,\n UserPatch,\n SessionRecord,\n SyncChange,\n SyncPushResult,\n SyncPullResult,\n LeagueMembership,\n LeagueCohort,\n LinkedIdentifier,\n AuthStateChangeEvent,\n AuthStateChangeReason,\n} from \"./types\";\n\nimport { AsyncStoragePersistence } from \"./async-storage-adapter.js\";\nimport { AmbaApiError } from \"./error.js\";\nimport {\n AmbaHttpClient,\n DEFAULT_BASE_URL,\n isSessionRefusedError,\n mapHttpError,\n} from \"./http-client.js\";\nimport type { Envelope } from \"./http-client.js\";\nimport { IdentityStore } from \"./identity.js\";\nimport type { CommitFence } from \"./identity.js\";\nimport { TOKEN_STORAGE_MODES, resolveTokenVault } from \"./token-storage.js\";\nimport { readTimeout, resolveRequestDeadlines } from \"./request-deadline.js\";\nimport { pathSegment } from \"./path-segment.js\";\nimport { EventBatchQueue } from \"./event-batch-queue.js\";\nimport type { EventFlushResult, EventDrop } from \"./event-batch-queue.js\";\nimport {\n OfflineQueue,\n isOfflineError,\n keyableProperties,\n newIdempotencyKey,\n withOptimistic,\n} from \"./resilience.js\";\nimport type { KvStore, OutboxOp, FlushResult } from \"./resilience.js\";\nimport {\n enableNativePushAutoRotation,\n type TokenRefreshSubscribe,\n} from \"./push-rotation.js\";\nimport { notifications } from \"./local-notifications.js\";\nimport { ExperimentAssignmentReporter } from \"./experiment-reporter.js\";\nimport type { ExperimentAssignedListener } from \"./experiment-reporter.js\";\nimport { RemoteValuesCache, recordKey } from \"./remote-values.js\";\nimport {\n deviceOsFromPlatform,\n normalizeAppVersion,\n regionFromLocale,\n} from \"./client-info.js\";\nimport {\n CollectionSubscription,\n ConversationSubscription,\n GamificationSubscription,\n} from \"./realtime.js\";\n\nexport { AmbaApiError } from \"./error.js\";\nexport {\n CollectionSubscription,\n ConversationSubscription,\n GamificationSubscription,\n} from \"./realtime.js\";\nexport type {\n RealtimeChange,\n MessageChange,\n GamificationChange,\n GamificationKind,\n RowChangeOp,\n SubscribeOptions,\n Unsubscribe,\n} from \"./realtime.js\";\nexport type { AmbaApiErrorCode } from \"./error.js\";\nexport { withOptimistic } from \"./resilience.js\";\nexport type {\n OutboxOp,\n OutboxOpKind,\n OutboxOwner,\n FlushResult,\n KvStore,\n} from \"./resilience.js\";\nexport type { SecureTokenStorage, TokenStorageMode } from \"./token-storage.js\";\nexport type { TokenRefreshSubscribe } from \"./push-rotation.js\";\nexport type { EventFlushResult, EventDrop } from \"./event-batch-queue.js\";\nexport type { LocalNotificationsNamespace } from \"./local-notifications.js\";\nexport type { ExperimentAssignedListener } from \"./experiment-reporter.js\";\n\n/** SDK semver — burned in at build time. Bump alongside package.json. */\nexport const SDK_VERSION = \"4.3.1\";\n\n// ── internal state ──────────────────────────────────────────────────\n\ninterface RuntimeState {\n http: AmbaHttpClient;\n identity: IdentityStore;\n debug: boolean;\n projectId: string;\n /** Client key — needed to build realtime subscribe auth headers. */\n clientKey: string;\n /** API base URL — the realtime subscribe path targets this host. */\n baseUrl: string;\n /** Optional override for `Amba.functions.invoke()` (custom domain / non-prod). */\n functionsBaseUrl: string | null;\n /** Cached server-resolved project id for the default function host. */\n resolvedFunctionsProjectId: string | null;\n /** In-flight default-host project id resolution, shared across invokes. */\n functionsProjectIdInflight: Promise<string> | null;\n /**\n * In-flight lazy anonymous sign-in, deduped per-state so concurrent reads\n * trigger exactly one sign-in. Lives on the state (not a module global) so a\n * re-`configure()` starts fresh and never awaits the previous identity's\n * sign-in. `null`/absent when idle.\n */\n anonSignInInflight?: Promise<void> | null;\n /**\n * In-flight `/refresh`, shared by every caller (the launch restore, the lazy\n * session, `auth.refresh()`, `signInAnonymously()` recovery). The server\n * accepts a refresh token once, so two concurrent refreshes would revoke\n * each other's result. `null`/absent when idle.\n */\n refreshInflight?: Promise<AuthResult> | null;\n /**\n * The `/refresh` request of `refreshInflight` and the refresh token it\n * presents, until its result is stored. A re-`configure()` hands it to the\n * next state (`inheritedRefresh`), so the token is never presented twice.\n */\n refreshExchange?: RefreshExchange | null;\n /**\n * A `/refresh` the previous state had in flight when `configure()` ran\n * again. The first refresh of the same token takes its answer instead of\n * sending the token once more.\n */\n inheritedRefresh?: RefreshExchange | null;\n /**\n * Set when a later `configure()` replaced this state. Its in-flight calls\n * may still settle, but they store nothing and tell no subscriber.\n */\n disposed?: boolean;\n /**\n * Links in flight. A link that adds an identity signs out every earlier\n * session, so a call the server refuses meanwhile waits for the pair the\n * link returns (`recoverRefusedSession`).\n */\n linksInflight?: Set<Promise<unknown>>;\n /** Durable key/value persistence (AsyncStorage-backed) for the outbox. */\n persistence: AsyncStoragePersistence;\n /** The config and flags cache (fetched, cached, configDefaults). */\n remoteValues: RemoteValuesCache;\n /** The identity (app user id, `\"\"` signed out) each first-read refresh ran for. */\n refreshedFor: { config: string | null; flags: string | null };\n /** Whether the offline outbox is diverting failed writes (opt-in). */\n offlineEnabled: boolean;\n /** Lazily-built offline outbox, bound to this state's persistence. */\n offlineQueue: OfflineQueue | null;\n eventQueue: EventBatchQueue | null;\n detachEventAppState?: () => void;\n /** Removes the foreground listener that reads a locked store again. */\n detachIdentityAppState?: () => void;\n /**\n * Events tracked while the anonymous id was a stand-in (a store locked\n * since launch), oldest first, waiting for the stored identity\n * (`holdEvent`, `releaseHeldEvents`). Memory only: before the first unlock\n * after a restart, the files AsyncStorage keeps can't be read either.\n */\n heldEvents?: HeldEvent[];\n /** Held events dropped to stay within `MAX_HELD_EVENTS`, not yet reported. */\n heldEventsDropped?: number;\n /** Set while `releaseHeldEvents` sends the held events. */\n releasingHeldEvents?: boolean;\n}\n\n/** An event `track()` held while the anonymous id was a stand-in. */\ninterface HeldEvent {\n event: string;\n properties: Record<string, unknown> | undefined;\n options: { telemetry?: boolean; immediate?: boolean } | undefined;\n /** When `track()` was called. */\n occurredAt: string;\n /** The identity generation when `track()` was called (see `releaseHeldEvents`). */\n generation: number;\n}\n\n/** The most events held at once; the oldest goes first past it. */\nconst MAX_HELD_EVENTS = 500;\n\n/**\n * Default host that serves deployed functions. `Amba.functions.invoke(name)`\n * targets `https://<projectId>.<DEFAULT_FUNCTIONS_HOST>/<name>` unless a\n * `functionsBaseUrl` override is configured.\n */\nconst DEFAULT_FUNCTIONS_HOST = \"fn.amba.host\";\nconst PROJECT_ID_PATTERN =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/** One `/refresh` request and the refresh token it presents. */\ninterface RefreshExchange {\n token: string;\n response: Promise<RefreshEnvelope>;\n}\n\nlet state: RuntimeState | null = null;\n/**\n * Monotonic token identifying the current offline activation. Bumped on every\n * `offline.enable()` AND every `configure()`, captured by each `enable()` into\n * its disable callback. A disabler only acts while the token still matches — so\n * a stale disabler (held across a re-`enable()` or a reconfigure) no-ops\n * instead of disabling the now-active outbox.\n */\nlet _offlineEnableToken = 0;\n\n/** The `configure()` running now, if any (see `liveState`). */\nlet configuring: Promise<void> | null = null;\n/** Serializes `configure()` calls, so each detaches the one before it. */\nlet configureChain: Promise<void> = Promise.resolve();\n\n/**\n * The state calls act on. While a `configure()` is running, waits for it,\n * so a sign-in, a link or a sign-out made meanwhile lands on the state it\n * installs (or on the earlier one, when it fails).\n */\nasync function liveState(): Promise<RuntimeState> {\n while (configuring) await configuring.catch(() => undefined);\n return getState();\n}\n\n/** Whether two states talk to the same project with the same stored identity. */\nfunction sameIdentityTarget(a: RuntimeState, b: RuntimeState): boolean {\n return (\n a.projectId === b.projectId &&\n a.clientKey === b.clientKey &&\n a.baseUrl === b.baseUrl &&\n a.persistence.prefix === b.persistence.prefix\n );\n}\n\nfunction getState(): RuntimeState {\n if (!state) {\n throw new AmbaApiError(\n \"NOT_INITIALIZED\",\n \"@layers/amba-react-native: SDK not configured. Call Amba.configure({ projectId, clientKey }) at app start.\",\n );\n }\n return state;\n}\n\n/**\n * Build the auth headers for a realtime subscribe connection, or `null`\n * when the SDK isn't configured or there's no live session. Read lazily at\n * connect time so reconnects pick up a rotated session token (the identity\n * store holds the canonical, auto-refreshed copy).\n */\nfunction realtimeAuthHeaders(): Record<string, string> | null {\n if (!state) return null;\n const token = state.identity.getSessionToken();\n if (!token) return null;\n return {\n \"X-Api-Key\": state.clientKey,\n Authorization: `Bearer ${token}`,\n };\n}\n\n// ── auth state pub/sub ──────────────────────────────────────────────\n\ntype AuthSubscriber = (\n session: Session | null,\n event: AuthStateChangeEvent,\n) => void;\nconst authSubscribers = new Set<AuthSubscriber>();\n\n/**\n * `Amba.onExperimentAssigned` listeners. `experiments.getVariant` reports\n * each result here; the reporter calls the listeners once per experiment per\n * user session. A sign-out (any auth change to no session) and `configure`\n * start a new session.\n */\nconst experimentReporter = new ExperimentAssignmentReporter();\n\n// ── remote config / flags: on-device reads ──────────────────────────\n\n/**\n * `configDefaults` from the latest `configure()` call, recorded before\n * configure reads the AsyncStorage cache. `config.getCached` and\n * `flags.isEnabled` serve them on a first render that runs while\n * `configure()` is still in flight.\n */\nlet _pendingConfigDefaults: Record<string, unknown> | null = null;\n\n/** Validate `AmbaConfig.configDefaults`: absent, or a plain object. */\nfunction readConfigDefaults(value: unknown): Record<string, unknown> | null {\n if (value === undefined || value === null) return null;\n if (typeof value !== \"object\" || Array.isArray(value)) {\n throw new AmbaApiError(\n \"INVALID_CONFIG\",\n \"Amba.configure: configDefaults must be an object of config key to value\",\n );\n }\n return value as Record<string, unknown>;\n}\n\n/** The pending `configDefaults` entry for `key`, ignoring inherited properties. */\nfunction pendingDefault(key: string): unknown {\n const defaults = _pendingConfigDefaults;\n if (!defaults || !Object.prototype.hasOwnProperty.call(defaults, key)) {\n return undefined;\n }\n const value = defaults[key];\n return value === null ? undefined : value;\n}\n\n/** `\"array\"` for arrays, else `typeof` — the JSON kind of a value. */\nfunction jsonKind(value: unknown): string {\n return Array.isArray(value) ? \"array\" : typeof value;\n}\n\n/**\n * `Amba.config.getCached`. A value whose JSON kind differs from a supplied\n * default's (a string where the default is a number) returns the default.\n */\nfunction configGetCached<T = unknown>(key: string): T | undefined;\nfunction configGetCached<T>(key: string, defaultValue: T): T;\nfunction configGetCached<T>(key: string, defaultValue?: T): T | undefined {\n if (state) refreshOnFirstRead(state, \"config\");\n const raw = state ? state.remoteValues.configValue(key) : pendingDefault(key);\n if (raw === undefined || raw === null) return defaultValue;\n if (\n defaultValue !== undefined &&\n defaultValue !== null &&\n jsonKind(raw) !== jsonKind(defaultValue)\n ) {\n return defaultValue;\n }\n return raw as T;\n}\n\n/** `Amba.flags.isEnabled`. */\nfunction flagsIsEnabled(key: string, defaultValue = false): boolean {\n if (state) refreshOnFirstRead(state, \"flags\");\n const held = state ? state.remoteValues.flagEnabled(key) : pendingDefault(key);\n return typeof held === \"boolean\" ? held : defaultValue;\n}\n\n/** `Amba.flags.getCached`. */\nfunction flagsGetCached(key: string): FlagAssignment | null {\n if (!state) return null;\n refreshOnFirstRead(state, \"flags\");\n return state.remoteValues.flag(key);\n}\n\n/**\n * The first on-device read of config (or flags) for each identity after\n * `configure()` starts one background fetch that replaces the cached values:\n * once at launch, and once more after a sign-in or sign-out changes who is\n * reading. Apps that never read cached values make no extra request. A\n * failure (offline) leaves the cached values in place.\n */\nfunction refreshOnFirstRead(s: RuntimeState, kind: \"config\" | \"flags\"): void {\n const identity = s.identity.getAppUserId() ?? \"\";\n if (s.refreshedFor[kind] === identity) return;\n s.refreshedFor[kind] = identity;\n void Promise.resolve()\n .then((): Promise<unknown> =>\n kind === \"config\" ? Amba.config.fetch() : Amba.flags.fetch(),\n )\n .catch(() => {});\n}\n\nfunction snapshotSession(user: User | null): Session | null {\n if (!user) return null;\n return {\n sessionToken: \"\",\n refreshToken: \"\",\n user,\n expiresAt: \"\",\n };\n}\n\n/**\n * Tell `onAuthStateChange` subscribers about an auth change of `state_`.\n * A state a later `configure()` replaced tells no one.\n */\nfunction notifyAuthSubscribers(\n state_: RuntimeState,\n session: Session | null,\n reason: AuthStateChangeReason,\n): void {\n if (state_ !== state || state_.disposed) return;\n if (session === null) experimentReporter.reset();\n const event: AuthStateChangeEvent = { reason };\n for (const cb of authSubscribers) {\n try {\n cb(session, event);\n } catch (err) {\n if (typeof console !== \"undefined\") {\n console.error(\"[amba] onAuthStateChange callback threw\", err);\n }\n }\n }\n}\n\nfunction normalizeAuthProviderNames(value: unknown): string[] {\n let normalized = value;\n if (typeof normalized === \"string\") {\n const trimmed = normalized.trim();\n if (trimmed === \"\") return [];\n try {\n normalized = JSON.parse(trimmed) as unknown;\n } catch {\n return [trimmed];\n }\n }\n\n if (!Array.isArray(normalized)) {\n if (normalized && typeof normalized === \"object\") {\n const provider = (normalized as { provider?: unknown }).provider;\n return typeof provider === \"string\" ? [provider] : [];\n }\n return [];\n }\n\n return normalized\n .map((entry) => {\n if (typeof entry === \"string\") {\n const trimmed = entry.trim();\n if (trimmed === \"\") return null;\n try {\n const parsed = JSON.parse(trimmed) as unknown;\n if (parsed && typeof parsed === \"object\") {\n const provider = (parsed as { provider?: unknown }).provider;\n return typeof provider === \"string\" ? provider : null;\n }\n } catch {\n return entry;\n }\n return entry;\n }\n if (entry && typeof entry === \"object\") {\n const provider = (entry as { provider?: unknown }).provider;\n return typeof provider === \"string\" ? provider : null;\n }\n return null;\n })\n .filter((provider): provider is string => provider !== null);\n}\n\nfunction normalizeUserAuthProviders(user: User): User {\n return {\n ...user,\n auth_providers: normalizeAuthProviderNames(\n (user as unknown as { auth_providers?: unknown }).auth_providers,\n ),\n };\n}\n\nfunction normalizeAuthResult(result: AuthResult): AuthResult {\n return {\n ...result,\n user: normalizeUserAuthProviders(result.user),\n };\n}\n\nfunction notifyFromAuthResult(\n state_: RuntimeState,\n result: AuthResult,\n reason: AuthStateChangeReason,\n): void {\n notifyAuthSubscribers(state_, snapshotSession(result.user), reason);\n}\n\n/** A call needs a session while the user must sign in again. */\nfunction signInRequiredError(): AmbaApiError {\n return new AmbaApiError(\n \"SIGN_IN_REQUIRED\",\n \"the session ended and the user must sign in again; call a signIn* method, or Amba.auth.signInAnonymously() to start a new anonymous user\",\n );\n}\n\nfunction secureStoreUnavailableError(): AmbaApiError {\n return new AmbaApiError(\n \"SECURE_STORE_UNAVAILABLE\",\n \"the stored session can't be read yet (the device may be locked); retry once it is unlocked\",\n );\n}\n\nfunction sessionChangedError(message: string): AmbaApiError {\n return new AmbaApiError(\"SESSION_CHANGED\", message);\n}\n\nfunction isSessionChanged(err: unknown): boolean {\n return err instanceof AmbaApiError && err.code === \"SESSION_CHANGED\";\n}\n\n/**\n * The stored session token, for a call that needs one. Without a session\n * the call fails with `SIGN_IN_REQUIRED` while the user must sign in again,\n * and with `UNAUTHORIZED` otherwise.\n */\nfunction requireSession(state_: RuntimeState): string {\n const token = state_.identity.getSessionToken();\n if (token) return token;\n if (state_.identity.isSignInRequired()) throw signInRequiredError();\n // A stored session this launch couldn't read (a locked device) may exist.\n if (state_.identity.credentialsUnknown()) throw secureStoreUnavailableError();\n throw new AmbaApiError(\n \"UNAUTHORIZED\",\n \"no active session — call signIn* first\",\n );\n}\n\n// ── persist-on-auth helpers ─────────────────────────────────────────\n\nfunction expiresAtFromResult(result: AuthResult): string {\n if (result.expires_at && typeof result.expires_at === \"string\")\n return result.expires_at;\n // Default 1h session lifetime — matches the engine's contract\n // when the server omits `expires_at`.\n return new Date(Date.now() + 3600 * 1000).toISOString();\n}\n\n/**\n * Store an auth result under `fence`. Resolves the generation it committed\n * at, or null when the fence refused it.\n */\nfunction persistAuthResult(\n state_: RuntimeState,\n result: AuthResult,\n anonymous: boolean | undefined,\n fence: CommitFence = {},\n): Promise<number | null> {\n return state_.identity.setSession(\n {\n appUserId: result.user.id,\n sessionToken: result.session_token,\n refreshToken: result.refresh_token,\n expiresAt: expiresAtFromResult(result),\n anonymous,\n },\n fence,\n );\n}\n\ninterface AuthEnvelope {\n data: AuthResult;\n}\n\nfunction nonEmptyString(value: unknown): string | null {\n return typeof value === \"string\" && value.length > 0 ? value : null;\n}\n\n/**\n * POST to an endpoint that answers with a session pair, and store it.\n * An anonymous sign-in records a purely anonymous session and adopts the\n * anonymous id the server answered with (the server mints one when the\n * request carried none). A link adopts the account's rotated anonymous id.\n * A refresh keeps the session's kind; every other sign-in records an\n * identified session.\n *\n * A sign-in the app asked for stores its session whatever happened\n * meanwhile: it is the newest thing the app asked for. The automatic\n * anonymous sign-in and a link pass a `fence` and fail with\n * `SESSION_CHANGED` (storing nothing) when the session changed while they\n * were in flight.\n */\nasync function postAuth(\n state_: RuntimeState,\n path: string,\n body: unknown,\n fence: CommitFence = {},\n): Promise<AuthResult> {\n const env = await state_.http.post<AuthEnvelope>(path, body);\n const result = normalizeAuthResult(env.data);\n const anonymous =\n path === P.authAnonymous ? true : path === P.authRefresh ? undefined : false;\n let target = state_;\n let committedAt = await persistAuthResult(state_, result, anonymous, fence);\n if (committedAt === null && fence.ifGeneration === undefined && state_.disposed) {\n // A sign-in the app asked for that a `configure()` overtook: the session\n // the server issued belongs on the state that replaced this one, when it\n // stores the same project's identity.\n const next = await liveState();\n if (next === state_ ? !next.disposed : sameIdentityTarget(next, state_)) {\n target = next;\n committedAt = await persistAuthResult(next, result, anonymous);\n }\n }\n if (committedAt === null) {\n throw sessionChangedError(\n \"the session changed while this sign-in was in flight; the newer session stays\",\n );\n }\n state_ = target;\n const adoptedAnonymousId =\n path === P.authAnonymous\n ? nonEmptyString(result.anonymous_id) ?? nonEmptyString(result.user?.anonymous_id)\n : path === P.authLink\n ? nonEmptyString(result.user?.anonymous_id)\n : null;\n if (adoptedAnonymousId) {\n await adoptAnonymousId(state_, adoptedAnonymousId, committedAt);\n }\n void state_.eventQueue?.flush();\n notifyFromAuthResult(state_, result, \"signed_in\");\n return result;\n}\n\n/**\n * Adopt the anonymous id the server holds for the signed-in user. Offline\n * writes queued before any session under the old id would replay under this\n * session; they are handed to the signed-in user first, so the id change\n * leaves them replayable.\n */\nasync function adoptAnonymousId(\n state_: RuntimeState,\n anonymousId: string,\n generation: number,\n): Promise<void> {\n const previous = state_.identity.getAnonymousId();\n if (anonymousId === previous) return;\n // The session this id belongs to was replaced meanwhile.\n if (state_.identity.getGeneration() !== generation) return;\n const appUserId = state_.identity.getAppUserId();\n if (appUserId && state_.offlineEnabled) {\n try {\n await getOfflineQueue(state_).claimAnonymousOps(previous, appUserId);\n } catch (err) {\n if (typeof console !== \"undefined\") {\n console.warn(\"[amba] could not reassign queued offline writes\", err);\n }\n }\n }\n await state_.identity.setAnonymousId(anonymousId, {\n ifGeneration: generation,\n });\n}\n\n// ── events held while the anonymous id is unknown ───────────────────\n\n/**\n * Hold an event `track()` got while the stored identity couldn't be read.\n * It keeps its call time and a copy of its properties; past\n * `MAX_HELD_EVENTS` the oldest is dropped.\n */\nfunction holdEvent(\n state_: RuntimeState,\n event: string,\n properties: Record<string, unknown> | undefined,\n options: { telemetry?: boolean; immediate?: boolean } | undefined,\n): void {\n const held = (state_.heldEvents ??= []);\n if (held.length >= MAX_HELD_EVENTS) {\n held.shift();\n state_.heldEventsDropped = (state_.heldEventsDropped ?? 0) + 1;\n }\n held.push({\n event,\n properties: snapshotProperties(properties),\n options,\n occurredAt: new Date().toISOString(),\n generation: state_.identity.getGeneration(),\n });\n}\n\n/** Report the held events dropped for `MAX_HELD_EVENTS`, once. */\nfunction reportHeldEventsDropped(state_: RuntimeState): void {\n const dropped = state_.heldEventsDropped ?? 0;\n state_.heldEventsDropped = 0;\n if (dropped > 0 && typeof console !== \"undefined\") {\n console.warn(\n `[amba] dropped ${dropped} event(s) tracked while the secure store was locked: more than ${MAX_HELD_EVENTS} were waiting`,\n );\n }\n}\n\n/** Drop the held events, with one warning that says why. */\nfunction dropHeldEvents(state_: RuntimeState, why: string): void {\n const held = state_.heldEvents ?? [];\n const count = held.length + (state_.heldEventsDropped ?? 0);\n state_.heldEvents = [];\n state_.heldEventsDropped = 0;\n if (count > 0 && typeof console !== \"undefined\") {\n console.warn(\n `[amba] dropped ${count} event(s) tracked while the secure store was locked: ${why}`,\n );\n }\n}\n\n/**\n * Send the held events once the stored identity is known, oldest first,\n * through the path `track()` uses, under the identity that loaded. A session\n * renewal the load started finishes first. An event that fails is reported\n * and the rest go on; events tracked meanwhile join the end of the line.\n * An event goes out only while the session it belongs to is still the\n * stored one: the session that loaded for events held before, the session\n * of the moment for events tracked during the release. When the session\n * changed since (the renewal is refused and the session ends, a sign-in or\n * sign-out lands), the event is dropped, and one warning counts them.\n */\nasync function releaseHeldEvents(state_: RuntimeState): Promise<void> {\n if (state_.releasingHeldEvents || !state_.heldEvents?.length) return;\n state_.releasingHeldEvents = true;\n let stale = 0;\n try {\n const loadedAt = state_.identity.getGeneration();\n await settleSessionInFlight(state_);\n reportHeldEventsDropped(state_);\n while (state_.heldEvents?.length && !state_.disposed) {\n const next = state_.heldEvents.shift() as HeldEvent;\n const belongsTo = Math.max(next.generation, loadedAt);\n if (state_.identity.getGeneration() !== belongsTo) {\n stale += 1;\n continue;\n }\n try {\n await sendEvent(\n state_,\n next.event,\n next.properties,\n next.options,\n next.occurredAt,\n );\n } catch (err) {\n if (typeof console !== \"undefined\") {\n console.warn(\n `[amba] could not send \"${next.event}\", tracked while the secure store was locked`,\n err,\n );\n }\n }\n }\n } finally {\n state_.releasingHeldEvents = false;\n if (stale > 0 && typeof console !== \"undefined\") {\n console.warn(\n `[amba] dropped ${stale} event(s) tracked while the secure store was locked: the session they belong to changed before they could be sent`,\n );\n }\n }\n}\n\n// ── lazy anonymous session ──────────────────────────────────────────\n//\n// `configure()` only builds local state — it mints no server session. To\n// spare every app from having to remember `await Amba.auth.signInAnonymously()`\n// before the first read (the silent-no-data trap), the HTTP client lazily\n// establishes an anonymous session on the first authenticated call. Deduped\n// via a single in-flight promise (stored ON the RuntimeState) so a burst of\n// parallel reads triggers exactly one sign-in. Keeping the promise on the state\n// — not a module global — means a re-`configure()` (new state) never awaits a\n// sign-in that targeted the previous, abandoned identity.\n\n/** Paths that must NOT trigger lazy anonymous sign-in. */\nfunction pathSkipsLazySession(path: string): boolean {\n // Auth endpoints would recurse (the sign-in request is itself a POST).\n if (path.startsWith(\"/v1/client/auth/\")) return true;\n // Event tracking is anonymous-first by design — it carries the\n // `anonymous_id` and works without a session, so don't force one (and don't\n // mint an MAU) just to fire telemetry.\n if (path === P.events || path.startsWith(`${P.events}/`)) return true;\n // Genuinely public, no-session-required surfaces — don't mint an MAU just to\n // read remote config / flags / a health ping.\n if (path === P.diagnosticsPing) return true;\n if (path === P.config || path.startsWith(`${P.config}/`)) return true;\n if (path === P.flags || path.startsWith(`${P.flags}/`)) return true;\n return false;\n}\n\n/**\n * Make sure a session is stored before an authenticated call. While the user\n * must sign in again (`sign_in_required`), the call fails with\n * `SIGN_IN_REQUIRED` and no anonymous user starts.\n */\nasync function ensureAnonymousSession(state_: RuntimeState): Promise<void> {\n if (state_.identity.getSessionToken()) return;\n if (state_.identity.isSignInRequired()) throw signInRequiredError();\n // A restore started at configure() may be in flight. When it ends without\n // a session (the refresh token was rejected, or the network failed), this\n // call starts its own sign-in below.\n if (state_.anonSignInInflight) {\n await state_.anonSignInInflight.catch(() => {});\n if (state_.identity.getSessionToken()) return;\n if (state_.identity.isSignInRequired()) throw signInRequiredError();\n }\n // A sign-out that lands while the anonymous sign-in is out leaves no\n // session: the sign-in drops its answer, and one more starts.\n for (let attempt = 0; attempt < 2; attempt += 1) {\n if (state_.identity.getSessionToken() || state_.disposed) return;\n if (state_.identity.isSignInRequired()) throw signInRequiredError();\n await anonymousSignInOnce(state_);\n }\n if (\n !state_.identity.getSessionToken() &&\n state_.identity.isSignInRequired()\n ) {\n throw signInRequiredError();\n }\n}\n\n/** One run of the lazy sign-in, shared by every caller that waits for it. */\nfunction anonymousSignInOnce(state_: RuntimeState): Promise<void> {\n if (!state_.anonSignInInflight) {\n state_.anonSignInInflight = (async () => {\n try {\n // A secure store that opened since launch loads the session it\n // holds first. A stored session that still can't be read is never\n // replaced by a new anonymous user: without its anonymous id (the\n // Expo credential record) the call fails here, and a locked vault\n // keeps it out of reach of `/anonymous` (`continueHeldSession`).\n await state_.identity.vaultStillUnreadable();\n if (await state_.identity.anonymousIdStillUnknown()) {\n throw secureStoreUnavailableError();\n }\n if (state_.identity.getSessionToken()) return;\n // A stored refresh token continues the session it belongs to, the\n // signed-in user's included. Only when there is none, or the server\n // rejects it for an anonymous session, does the device sign in\n // anonymously.\n if (await resumeWithRefreshToken(state_)) return;\n if (\n state_.identity.getSessionToken() ||\n state_.identity.isSignInRequired()\n ) {\n return;\n }\n await signInAnonymouslyAutomatic(state_);\n } finally {\n state_.anonSignInInflight = null;\n }\n })();\n }\n return state_.anonSignInInflight;\n}\n\n/**\n * Restore a stored session whose session token expired while the app was\n * closed, using its refresh token, so `isAuthenticated` and\n * `onAuthStateChange` show the signed-in user without waiting for an\n * authenticated call. Shares the in-flight slot with the lazy sign-in, so a\n * concurrent authenticated call waits for it. A rejected refresh token ends\n * the session (`endLostSession`); this never signs in anonymously.\n */\nfunction restoreStoredSession(state_: RuntimeState): Promise<void> {\n if (!state_.anonSignInInflight) {\n state_.anonSignInInflight = (async () => {\n try {\n await resumeWithRefreshToken(state_);\n } finally {\n state_.anonSignInInflight = null;\n }\n })();\n }\n return state_.anonSignInInflight;\n}\n\n/**\n * Answers that mean the refresh token's session is over: the server\n * rejected or revoked it, or it renewed a different user than the stored\n * one (`OWNER_MISMATCH`). A 401 without the server's envelope (a proxy, a\n * captive portal) maps to `UNAUTHORIZED` and leaves the session in place.\n */\nconst SESSION_ENDED_CODES = new Set([\n \"INVALID_TOKEN\",\n \"SESSION_REVOKED\",\n \"OWNER_MISMATCH\",\n]);\n\nfunction isSessionEnded(err: unknown): boolean {\n return err instanceof AmbaApiError && SESSION_ENDED_CODES.has(err.code);\n}\n\n/** Record a link in flight until it settles; returns the same promise. */\nfunction trackLink<T>(state_: RuntimeState, link: Promise<T>): Promise<T> {\n const links = (state_.linksInflight ??= new Set());\n links.add(link);\n const settle = () => {\n links.delete(link);\n };\n link.then(settle, settle);\n return link;\n}\n\n/**\n * After the server refused the stored session token on an authenticated\n * call (`SESSION_REVOKED`, `INVALID_SESSION_TOKEN`, `INVALID_SESSION`). The\n * HTTP client runs it for calls that send the session in the\n * `Authorization` header, `/v1/client/auth/me` and `/identifiers`\n * included; `withBodySession` runs it for the link and unlink calls, which\n * send the session in their body. True when a usable\n * session for the user the call was sent for is stored afterwards, so the\n * call is sent once more as that user:\n * - a link in flight on this device stores the pair it returns, which is\n * waited for first;\n * - a session another flow stored after the call left is used as is;\n * - otherwise the refresh token continues the session. A rejected refresh\n * token ends it (`endLostSession`) and the call's own error reaches the\n * app.\n * A session of any other user (a sign-in meanwhile, or the anonymous user\n * a lazy sign-in starts after the session ended) answers false, and the\n * call fails with the server's code. Offline or a server error keeps the\n * session and answers false. The\n * refresh runs in the in-flight slot the launch restore and the lazy\n * sign-in share, so calls refused together end or continue the session\n * once.\n */\nasync function recoverRefusedSession(\n state_: RuntimeState,\n usedToken: string,\n usedUserId: string | null,\n): Promise<boolean> {\n if (usedUserId === null) return false;\n const usableForCaller = (token: string | null): boolean =>\n token !== null &&\n token !== usedToken &&\n state_.identity.getAppUserId() === usedUserId;\n if (state_.linksInflight && state_.linksInflight.size > 0) {\n await Promise.allSettled(state_.linksInflight);\n }\n const current = state_.identity.getSessionToken();\n if (current !== usedToken) return usableForCaller(current);\n if (!state_.anonSignInInflight) {\n state_.anonSignInInflight = (async () => {\n try {\n await resumeWithRefreshToken(state_);\n } catch {\n // Offline or a server error: the session stays as it is.\n } finally {\n state_.anonSignInInflight = null;\n }\n })();\n }\n await state_.anonSignInInflight.catch(() => {});\n return usableForCaller(state_.identity.getSessionToken());\n}\n\n/**\n * Send an auth call that carries the stored session token in its body\n * (`/link`, `/unlink`). `send` builds the request from the token it is\n * given. When the server refuses that token, the session recovers through\n * `recoverRefusedSession` (one refresh shared with every other refused\n * call) and the call is sent once more with the recovered token; a second\n * refusal reaches the app. A link waits here outside `trackLink`: its own\n * failed attempt has already left `linksInflight` (the removal is the first\n * reaction on that promise), so recovery never waits on itself.\n */\nasync function withBodySession<T>(\n state_: RuntimeState,\n send: (sessionToken: string) => Promise<T>,\n): Promise<T> {\n await loadUnreadSession(state_);\n const usedToken = requireSession(state_);\n const usedUserId = state_.identity.getAppUserId();\n try {\n return await send(usedToken);\n } catch (err) {\n if (!isSessionRefusedError(err)) throw err;\n if (!(await recoverRefusedSession(state_, usedToken, usedUserId))) throw err;\n const recovered = state_.identity.getSessionToken();\n if (!recovered) throw err;\n return send(recovered);\n }\n}\n\n/**\n * Read a store the launch couldn't read (a locked device) again. When that\n * loads a session, wait for the renewal it starts, so the caller acts on\n * the loaded session (a sign-out revokes it, a link sends it).\n */\nasync function loadUnreadSession(state_: RuntimeState): Promise<void> {\n if (!state_.identity.credentialsUnknown()) return;\n await state_.identity.credentialsStillUnreadable();\n if (!state_.identity.credentialsUnknown()) await settleSessionInFlight(state_);\n}\n\n/**\n * Before a read of the current session (`me`, `getSession`,\n * `getCurrentIdentifiers`) while no session token is stored: load a stored\n * session the launch couldn't read yet, then wait for the launch restore or\n * sign-in in flight, so a check made at launch sees the session it\n * restores.\n */\nasync function settleSessionInFlight(state_: RuntimeState): Promise<void> {\n if (state_.identity.getSessionToken()) return;\n // A stored session that couldn't be read at launch (a locked device) is\n // loaded here once it can be.\n await state_.identity.credentialsStillUnreadable();\n if (state_.identity.getSessionToken() || !state_.anonSignInInflight) return;\n await state_.anonSignInInflight.catch(() => {});\n}\n\n/**\n * Continue the stored session with its refresh token. True when it did.\n * False when there is no refresh token, or the session is over (the server\n * rejected the refresh token, or it renewed another user): the session then\n * ends through `endLostSession`. Any other failure (offline, a 5xx) is\n * thrown, so a flaky network never ends a session or starts a new user.\n */\nasync function resumeWithRefreshToken(state_: RuntimeState): Promise<boolean> {\n const used = state_.identity.getRefreshToken();\n if (!used) return false;\n const startedAt = state_.identity.getGeneration();\n try {\n await refreshSession(state_);\n return true;\n } catch (err) {\n // Another flow replaced or ended the session meanwhile (a sign-in, a\n // sign-out, the refresh itself): keep what it stored.\n if (state_.identity.getRefreshToken() !== used) {\n return state_.identity.getSessionToken() !== null;\n }\n if (!isSessionEnded(err)) throw err;\n await endLostSession(state_, startedAt);\n return false;\n }\n}\n\n/**\n * End a session that is over without a sign-out, once, unless the session\n * changed since `startedAt`.\n *\n * - A session that gained an identity (a sign-in other than anonymous, or a\n * link) leaves the user to sign in again: its tokens are cleared, the\n * anonymous id rotates (it belongs to the identified account), and\n * subscribers hear `null` with `sign_in_required`. No new anonymous user\n * starts; calls that need a session fail with `SIGN_IN_REQUIRED`.\n * - A purely anonymous session keeps its anonymous id, so the next call that\n * needs a session resumes the same anonymous user through `/anonymous`\n * (or the server starts a new one). Subscribers hear `null` with\n * `session_ended`.\n * - A session of unknown kind (stored by an SDK before 4.0.14) is treated\n * like an anonymous one: `/anonymous` resumes a still-anonymous user, and\n * answers `IDENTITY_SIGN_IN_REQUIRED` for one who linked an identity,\n * which then leaves the user to sign in again.\n */\nasync function endLostSession(\n state_: RuntimeState,\n startedAt: number,\n): Promise<void> {\n if (state_.identity.snapshot().anonymousSession === false) {\n const ended = await state_.identity.clearSession(false, {\n signInRequired: true,\n ifGeneration: startedAt,\n });\n if (ended) notifyAuthSubscribers(state_, null, \"sign_in_required\");\n return;\n }\n const ended = await state_.identity.clearSession(false, {\n keepIdWhenKindUnknown: true,\n ifGeneration: startedAt,\n });\n if (ended) notifyAuthSubscribers(state_, null, \"session_ended\");\n}\n\n/**\n * The anonymous sign-in the SDK starts on its own before an authenticated\n * call: `/v1/client/auth/anonymous` with the device's anonymous id. It\n * stores its session only when nothing replaced the stored identity while\n * it was in flight; a sign-in that landed first wins and this answer is\n * dropped.\n *\n * When the server answers `IDENTITY_SIGN_IN_REQUIRED` (the id belongs to a\n * user who signs in with a linked identity), the stored session continues\n * when this device holds its refresh token. Otherwise the user must sign in\n * again (`sign_in_required`): no new anonymous user starts on its own.\n */\nasync function signInAnonymouslyAutomatic(state_: RuntimeState): Promise<void> {\n if (state_.disposed) return;\n const startedAt = state_.identity.getGeneration();\n try {\n await postAuth(\n state_,\n P.authAnonymous,\n { anonymous_id: state_.identity.getAnonymousId() },\n { ifGeneration: startedAt },\n );\n } catch (err) {\n if (isSessionChanged(err)) return;\n if (!(err instanceof AmbaApiError) || err.code !== IDENTITY_SIGN_IN_REQUIRED) {\n throw err;\n }\n if (await continueHeldSession(state_)) return;\n const ended = await state_.identity.clearSession(true, {\n signInRequired: true,\n ifGeneration: startedAt,\n });\n if (ended) notifyAuthSubscribers(state_, null, \"sign_in_required\");\n }\n}\n\nconst IDENTITY_SIGN_IN_REQUIRED = \"IDENTITY_SIGN_IN_REQUIRED\";\n\n/**\n * After `/anonymous` answered `IDENTITY_SIGN_IN_REQUIRED`: continue the\n * stored session when this device holds its refresh token. Null when it\n * holds none, or the server rejected it. While the stored session can't be\n * read (an iOS device not yet unlocked since a restart), it is unknown, so\n * the call fails with `SECURE_STORE_UNAVAILABLE` and replaces nothing.\n */\nasync function continueHeldSession(\n state_: RuntimeState,\n): Promise<AuthResult | null> {\n if (await state_.identity.credentialsStillUnreadable()) {\n throw secureStoreUnavailableError();\n }\n const used = state_.identity.getRefreshToken();\n if (!used) return null;\n try {\n return await refreshSession(state_);\n } catch (refreshErr) {\n if (!isSessionEnded(refreshErr)) throw refreshErr;\n // The refresh token was rejected. A launch restore sharing the same\n // refresh may already have cleared it; a different stored token means\n // another sign-in replaced the session meanwhile.\n const current = state_.identity.getRefreshToken();\n if (current !== null && current !== used) throw refreshErr;\n return null;\n }\n}\n\ninterface RefreshEnvelope {\n data: {\n session_token: string;\n refresh_token: string;\n expires_at?: string | null;\n user?: User;\n };\n}\n\n/**\n * Rotate the session pair with the stored refresh token. The session keeps\n * its user and its kind (anonymous or identified), and auth-state\n * subscribers hear the refreshed session. The server includes the user; an\n * older server that leaves it out keeps the stored user id, and the result's\n * user then carries only that id. A pair that renews a different user than\n * the stored one is never stored: the session ends (`endLostSession`) and\n * the call fails with `OWNER_MISMATCH`.\n */\nfunction refreshSession(state_: RuntimeState): Promise<AuthResult> {\n if (state_.refreshInflight) return state_.refreshInflight;\n const inflight = refreshSessionOnce(state_);\n state_.refreshInflight = inflight;\n const release = () => {\n if (state_.refreshInflight === inflight) state_.refreshInflight = null;\n };\n inflight.then(release, release);\n return inflight;\n}\n\nasync function refreshSessionOnce(state_: RuntimeState): Promise<AuthResult> {\n // A state a later configure() replaced presents no refresh token: the new\n // state owns the stored session now.\n if (state_.disposed) {\n throw sessionChangedError(\"the SDK was configured again\");\n }\n const refreshToken = state_.identity.getRefreshToken();\n if (!refreshToken && state_.identity.isSignInRequired()) {\n throw signInRequiredError();\n }\n if (!refreshToken)\n throw new AmbaApiError(\n \"UNAUTHORIZED\",\n \"no refresh token available — call signIn* first\",\n );\n const startedAt = state_.identity.getGeneration();\n const exchange = refreshExchangeFor(state_, refreshToken);\n try {\n const env = await exchange.response;\n // A sign-in or sign-out while the request was out replaced the session;\n // storing this pair would undo it.\n if (\n state_.identity.getRefreshToken() !== refreshToken ||\n state_.identity.getGeneration() !== startedAt\n ) {\n throw sessionChangedError(\n \"the session changed while it was being refreshed\",\n );\n }\n const storedUserId = state_.identity.getAppUserId();\n const renewedUserId = nonEmptyString(env.data.user?.id);\n if (\n renewedUserId !== null &&\n storedUserId !== null &&\n renewedUserId !== storedUserId\n ) {\n // The stored refresh token renewed someone else's session: the stored\n // pair is not the stored user's. Nothing of the answer is kept, and\n // the stored session ends.\n await endLostSession(state_, startedAt);\n throw new AmbaApiError(\n \"OWNER_MISMATCH\",\n \"the refreshed session belongs to a different user than the stored one; sign in again\",\n );\n }\n const appUserId = renewedUserId ?? storedUserId;\n if (!appUserId)\n throw new AmbaApiError(\n \"UNAUTHORIZED\",\n \"the refreshed session names no user — call signIn* first\",\n );\n const user: User = env.data.user\n ? normalizeUserAuthProviders(env.data.user)\n : { id: appUserId, auth_providers: [], properties: {} };\n const result: AuthResult = {\n session_token: env.data.session_token,\n refresh_token: env.data.refresh_token,\n user,\n expires_at: env.data.expires_at ?? null,\n };\n const committedAt = await persistAuthResult(state_, result, undefined, {\n ifGeneration: startedAt,\n advance: false,\n });\n if (committedAt === null) {\n throw sessionChangedError(\n \"the session changed while it was being refreshed\",\n );\n }\n // Adopt the account's current anonymous id. A link (on this device or\n // another) gives the account a new one, so for an anonymous session a\n // different id means its user gained an identity, and the session counts\n // as identified from now on. Skipped when a sign-in or sign-out replaced\n // the pair meanwhile.\n const anonymousId = nonEmptyString(user.anonymous_id);\n if (\n anonymousId &&\n anonymousId !== state_.identity.getAnonymousId() &&\n state_.identity.getRefreshToken() === result.refresh_token\n ) {\n if (state_.identity.snapshot().anonymousSession === true) {\n await state_.identity.markIdentified({ ifGeneration: committedAt });\n }\n await adoptAnonymousId(state_, anonymousId, committedAt);\n }\n void state_.eventQueue?.flush();\n notifyFromAuthResult(state_, result, \"token_refreshed\");\n return result;\n } finally {\n if (state_.refreshExchange === exchange) state_.refreshExchange = null;\n }\n}\n\n/**\n * The `/refresh` request for `refreshToken`: the one the previous state had\n * in flight for the same token when `configure()` ran again, or a new one.\n * It stays on the state until its answer is stored, so a `configure()`\n * meanwhile hands it on instead of presenting the token again.\n */\nfunction refreshExchangeFor(\n state_: RuntimeState,\n refreshToken: string,\n): RefreshExchange {\n const inherited = state_.inheritedRefresh;\n if (inherited) state_.inheritedRefresh = null;\n const exchange: RefreshExchange =\n inherited && inherited.token === refreshToken\n ? inherited\n : {\n token: refreshToken,\n response: state_.http.post<RefreshEnvelope>(P.authRefresh, {\n refresh_token: refreshToken,\n }),\n };\n state_.refreshExchange = exchange;\n return exchange;\n}\n\ninterface UserEnvelope {\n data: { user: User };\n}\n\ninterface LinkEnvelope {\n data: {\n user: User;\n session_token?: string;\n refresh_token?: string;\n expires_at?: string | null;\n };\n}\n\n/**\n * POST an OTP or email + password link. The request asks the server to\n * rotate the session (`rotate_session: true`): the link revokes every token\n * minted for this user before it, and answers with the pair that replaces\n * them, which is stored here. The user id does not change, so no auth-state\n * change fires. The session counts as identified from now on, and the\n * account's rotated anonymous id is adopted.\n */\nfunction postLink(\n state_: RuntimeState,\n body: Record<string, unknown>,\n): Promise<User> {\n return withBodySession(state_, (sessionToken) =>\n trackLink(\n state_,\n postLinkOnce(state_, { ...body, session_token: sessionToken }),\n ),\n );\n}\n\nasync function postLinkOnce(\n state_: RuntimeState,\n body: Record<string, unknown>,\n): Promise<User> {\n // The link acts for the session it was sent with: a sign-in or sign-out\n // that lands meanwhile wins, and the link's pair is not stored over it.\n const startedAt = state_.identity.getGeneration();\n const fence: CommitFence = { ifGeneration: startedAt, advance: false };\n const env = await state_.http.post<LinkEnvelope>(P.authLink, {\n ...body,\n rotate_session: true,\n });\n const user = normalizeUserAuthProviders(env.data.user);\n const sessionToken = nonEmptyString(env.data.session_token);\n const refreshToken = nonEmptyString(env.data.refresh_token);\n const stored =\n sessionToken && refreshToken\n ? (await state_.identity.setSession(\n {\n appUserId: user.id,\n sessionToken,\n refreshToken,\n expiresAt: expiresAtFromResult({\n session_token: sessionToken,\n refresh_token: refreshToken,\n user,\n expires_at: env.data.expires_at ?? null,\n }),\n anonymous: false,\n },\n fence,\n )) !== null\n : await state_.identity.markIdentified(fence);\n if (!stored) {\n throw sessionChangedError(\n \"the session changed while the link was in flight; the newer session stays\",\n );\n }\n const anonymousId = nonEmptyString(user.anonymous_id);\n if (anonymousId) {\n await adoptAnonymousId(state_, anonymousId, startedAt);\n }\n return user;\n}\n\ninterface IdentifiersEnvelope {\n data: { identifiers: LinkedIdentifier[] };\n}\n\n/**\n * POST helper for endpoints that return `{ data: { user } }` and mint no\n * tokens. Used by `unlink*`: existing session tokens stay untouched.\n */\nasync function postUserNoRotate(\n state_: RuntimeState,\n path: string,\n body: unknown,\n): Promise<User> {\n const env = await state_.http.post<UserEnvelope>(path, body);\n return normalizeUserAuthProviders(env.data.user);\n}\n\nasync function unlinkProvider(\n provider: \"sms_otp\" | \"email_otp\" | \"apple\" | \"google\",\n): Promise<User> {\n const s = await liveState();\n return withBodySession(s, (sessionToken) =>\n postUserNoRotate(s, P.authUnlink, {\n provider,\n session_token: sessionToken,\n }),\n );\n}\n\n// ── light client-side validation (mirrors engine) ────────────────\n\nfunction requireEmail(email: string): void {\n const trimmed = email.trim();\n if (trimmed === \"\")\n throw new AmbaApiError(\"VALIDATION_ERROR\", \"email must not be empty\");\n if (!trimmed.includes(\"@\") || !trimmed.includes(\".\"))\n throw new AmbaApiError(\n \"VALIDATION_ERROR\",\n `email '${email}' is not a valid email address`,\n );\n}\nfunction requirePassword(password: string): void {\n if (password.length < 8)\n throw new AmbaApiError(\n \"VALIDATION_ERROR\",\n \"password must be at least 8 characters\",\n );\n}\nfunction requireOtpCode(code: string): void {\n if (code.length !== 6 || !/^[0-9]{6}$/.test(code))\n throw new AmbaApiError(\"VALIDATION_ERROR\", \"code must be 6 digits\");\n}\n\n// Strict E.164: + followed by 1–9 then 6–14 more digits. Same shape\n// the server requires and that Telnyx wants on the wire.\nconst E164_RE = /^\\+[1-9][0-9]{6,14}$/;\nfunction requireE164Phone(phone: string): void {\n if (!E164_RE.test(phone))\n throw new AmbaApiError(\n \"VALIDATION_ERROR\",\n \"phone must be E.164 (starts with +, 8–15 digits)\",\n );\n}\n\n// ── path helpers (mirror Rust constants) ────────────────────────────\n\nconst P = {\n authAnonymous: \"/v1/client/auth/anonymous\",\n authEmailLogin: \"/v1/client/auth/email/login\",\n authEmailSignup: \"/v1/client/auth/email/signup\",\n authEmailOtpRequest: \"/v1/client/auth/email-otp/request\",\n authSmsOtpRequest: \"/v1/client/auth/sms-otp/request\",\n authSmsOtpVerify: \"/v1/client/auth/sms-otp/verify\",\n authEmailOtpVerify: \"/v1/client/auth/email-otp/verify\",\n authSocial: \"/v1/client/auth/social\",\n authMagicLinkRequest: \"/v1/client/auth/magic-link/request\",\n authMagicLinkVerify: \"/v1/client/auth/magic-link/verify\",\n authLink: \"/v1/client/auth/link\",\n authUnlink: \"/v1/client/auth/unlink\",\n authIdentifiers: \"/v1/client/auth/identifiers\",\n authRefresh: \"/v1/client/auth/refresh\",\n authLogout: \"/v1/client/auth/logout\",\n authMe: \"/v1/client/auth/me\",\n events: \"/v1/client/events\",\n users: \"/v1/client/users\",\n usersMe: \"/v1/client/users/me\",\n sessions: \"/v1/client/sessions\",\n sync: \"/v1/client/sync\",\n leagues: \"/v1/client/leagues\",\n collections: \"/v1/client/collections\",\n storage: \"/v1/client/media\",\n storagePresign: \"/v1/client/media/presign\",\n storageCommit: \"/v1/client/media/commit\",\n push: \"/v1/client/push\",\n pushRegister: \"/v1/client/push/register\",\n pushUnregister: \"/v1/client/push/unregister\",\n pushTokens: \"/v1/client/push/tokens\",\n pushSubscribe: \"/v1/client/push/subscribe\",\n pushUnsubscribe: \"/v1/client/push/unsubscribe\",\n entitlements: \"/v1/client/entitlements\",\n entitlementsRestore: \"/v1/client/entitlements/restore\",\n offerings: \"/v1/client/offerings\",\n aiAnthropic: \"/v1/client/ai/anthropic/messages\",\n config: \"/v1/client/config\",\n flags: \"/v1/client/flags\",\n experiments: \"/v1/client/experiments\",\n diagnosticsPing: \"/v1/client/diagnostics/ping\",\n achievements: \"/v1/client/achievements\",\n challenges: \"/v1/client/challenges\",\n catalog: \"/v1/client/catalog\",\n currencies: \"/v1/client/currencies\",\n inventory: \"/v1/client/inventory\",\n leaderboards: \"/v1/client/leaderboards\",\n stores: \"/v1/client/stores\",\n xp: \"/v1/client/xp\",\n streaks: \"/v1/client/streaks\",\n feeds: \"/v1/client/feeds\",\n friends: \"/v1/client/friends\",\n groups: \"/v1/client/groups\",\n messaging: \"/v1/client/messaging\",\n moderation: \"/v1/client/moderation\",\n reviews: \"/v1/client/reviews\",\n roles: \"/v1/client/roles\",\n referrals: \"/v1/client/referrals\",\n content: \"/v1/client/content\",\n deepLinks: \"/v1/client/deep-links\",\n onboarding: \"/v1/client/onboarding\",\n};\n\n/**\n * Encode one caller-supplied id, key or name as a single URL path segment.\n * `encodeURIComponent` escapes `/`, `?` and `#`; `.` and `..` are refused\n * because URL resolution treats them as steps to another route, and the\n * empty string is refused because it collapses onto the parent route.\n */\nfunction encodePathSegment(s: string): string {\n return pathSegment(s);\n}\n\n/** Options for the identity-token sign-in and link calls. */\nexport interface SocialSignInOptions {\n /**\n * The raw nonce generated for this sign-in attempt. The provider request\n * carried its SHA-256 hex digest; Amba checks the token's `nonce` claim\n * against it.\n */\n nonce?: string;\n}\n\nfunction nonceField(options: SocialSignInOptions | undefined): {\n nonce?: string;\n} {\n const nonce = options?.nonce;\n if (nonce === undefined) return {};\n if (typeof nonce !== \"string\" || nonce.trim() === \"\") {\n throw new AmbaApiError(\"VALIDATION_ERROR\", \"nonce must be a non-empty string\");\n }\n return { nonce };\n}\n\n// ── public API ──────────────────────────────────────────────────────\n\n/** Options for `Amba.functions.invoke`. */\nexport interface InvokeOptions {\n /** HTTP method. Defaults to `\"POST\"`. */\n method?: \"POST\" | \"GET\" | \"PUT\" | \"PATCH\" | \"DELETE\";\n /** Extra headers merged on top of the injected auth headers. */\n headers?: Record<string, string>;\n /**\n * How long this invocation may take, in milliseconds, from sending it to\n * reading the whole response. Defaults to the configured\n * `longRequestTimeoutMs` (5 minutes). Past it the call fails with the\n * retryable `NETWORK_ERROR`.\n */\n timeoutMs?: number;\n}\n\n/**\n * Resolve the full invocation URL for a deployed function. Resolution checks\n * `functionsBaseUrl` first, then uses a UUID-shaped configured project id\n * directly, and finally falls back to a deduped `diagnostics.ping` for the\n * authoritative server-side project id. The resolved default-host id is\n * cached for subsequent invokes.\n */\nasync function resolveFunctionUrl(\n s: RuntimeState,\n name: string,\n): Promise<string> {\n if (s.functionsBaseUrl) {\n return `${s.functionsBaseUrl.replace(/\\/$/, \"\")}/${pathSegment(name)}`;\n }\n const projectId = await resolveFunctionsProjectId(s);\n return `https://${projectId}.${DEFAULT_FUNCTIONS_HOST}/${pathSegment(name)}`;\n}\n\nasync function resolveFunctionsProjectId(s: RuntimeState): Promise<string> {\n if (s.resolvedFunctionsProjectId) {\n return s.resolvedFunctionsProjectId;\n }\n if (PROJECT_ID_PATTERN.test(s.projectId)) {\n s.resolvedFunctionsProjectId = s.projectId.toLowerCase();\n return s.resolvedFunctionsProjectId;\n }\n if (!s.functionsProjectIdInflight) {\n const resolution = (async () => {\n const ping = await Amba.diagnostics.ping();\n if (!ping.ok || !ping.server_project_id) {\n throw new AmbaApiError(\n \"FUNCTIONS_HOST_UNRESOLVED\",\n \"Could not resolve the functions host for this project. Pass `functionsBaseUrl` to Amba.configure() to invoke functions explicitly.\",\n ping,\n );\n }\n s.resolvedFunctionsProjectId = ping.server_project_id;\n return ping.server_project_id;\n })();\n s.functionsProjectIdInflight = resolution;\n void resolution.then(\n () => {\n if (s.functionsProjectIdInflight === resolution) {\n s.functionsProjectIdInflight = null;\n }\n },\n () => {\n if (s.functionsProjectIdInflight === resolution) {\n s.functionsProjectIdInflight = null;\n }\n },\n );\n }\n return s.functionsProjectIdInflight;\n}\n\n// ── offline outbox plumbing ─────────────────────────────────────────\n\n/**\n * Bridge the AsyncStorage-backed persistence to the {@link KvStore} the\n * outbox persists through. Namespaced under the configured storage prefix so\n * multiple projects in one app don't collide.\n */\nfunction offlineStore(s: RuntimeState): KvStore {\n return {\n get: (key) => s.persistence.read(key),\n set: (key, value) => s.persistence.write(key, value),\n delete: (key) => s.persistence.delete(key),\n };\n}\n\n/**\n * Whether a live `track` (outbox on) carries its idempotency key:\n * engagement events whose properties the keyed path takes. Telemetry stays\n * on the unkeyed hot path, which writes no idempotency record and answers a\n * failed telemetry insert with 200; its replays from the outbox are keyed.\n */\nfunction keyedLiveTrack(\n properties: unknown,\n telemetry: boolean | undefined,\n): boolean {\n return telemetry !== true && keyableProperties(properties);\n}\n\n/**\n * A JSON copy of `properties`: exactly what a request body serializes, and\n * immune to the app changing the object after `track()` returns.\n */\nfunction snapshotProperties(\n properties: Record<string, unknown> | undefined,\n): Record<string, unknown> | undefined {\n if (properties === undefined || properties === null) return undefined;\n const text = JSON.stringify(properties);\n return text === undefined\n ? undefined\n : (JSON.parse(text) as Record<string, unknown>);\n}\n\n/**\n * Replay one queued mutation against the live API, forwarding the op's\n * idempotency key on collection inserts and tracked events so a write that\n * landed before the connection dropped is never double-applied (API\n * migration 056 for collections, `client_event_idempotency` for events).\n */\nasync function replayOutboxOp(s: RuntimeState, op: OutboxOp): Promise<void> {\n switch (op.kind) {\n case \"collection.insert\":\n await s.http.post(\n `${P.collections}/${encodePathSegment(op.collection as string)}`,\n {\n ...(op.payload ?? {}),\n idempotency_key: op.idempotencyKey,\n },\n );\n return;\n case \"collection.update\":\n await s.http.patch(\n `${P.collections}/${encodePathSegment(op.collection as string)}/${encodePathSegment(op.id as string)}`,\n { set: op.payload ?? {} },\n );\n return;\n case \"collection.delete\":\n await s.http.delete(\n `${P.collections}/${encodePathSegment(op.collection as string)}/${encodePathSegment(op.id as string)}`,\n );\n return;\n case \"track\":\n try {\n // The op's idempotency key sends the replay down the events route's\n // keyed path, which records each key once: a replay of an event that\n // already landed (its live attempt, or an earlier replay whose\n // response was lost) answers as a duplicate and records nothing.\n // The key covers the whole payload, so every field here must match\n // the live attempt's body (see `events.track`).\n await s.http.post(P.events, {\n event: op.event,\n properties: op.properties ?? undefined,\n // Preserve the ORIGINAL event time (the events route honors a body\n // timestamp → `occurred_at`). `anonymous_id` mirrors the normal track\n // body; the server attributes engagement events to the SESSION user\n // at replay time (no body-id attribution), and the outbox replays an\n // op only while its owner is signed in. An op from an SDK version\n // that captured no event time replays its enqueue time, which stays\n // the same on every attempt.\n anonymous_id: s.identity.getAnonymousId(),\n timestamp: op.occurredAt ?? op.enqueuedAt,\n ...(op.telemetry ? { telemetry: true } : {}),\n ...(keyableProperties(op.properties)\n ? { idempotency_key: op.idempotencyKey }\n : {}),\n });\n } catch (err) {\n // A missing/expired session at replay (UNAUTHORIZED/FORBIDDEN) is\n // transient for a buffered analytics event — re-throw as NETWORK_ERROR\n // so the outbox RETAINS it (a later flush after re-auth lands it),\n // instead of the deterministic-drop the auth code would trigger.\n const code = (err as { code?: unknown })?.code;\n if (code === \"UNAUTHORIZED\" || code === \"FORBIDDEN\") {\n throw new AmbaApiError(\n \"NETWORK_ERROR\",\n \"track replay deferred: no valid session\",\n );\n }\n throw err;\n }\n return;\n }\n}\n\nfunction getOfflineQueue(s: RuntimeState): OfflineQueue {\n if (!s.offlineQueue) {\n s.offlineQueue = new OfflineQueue({\n store: offlineStore(s),\n // Scope the outbox by project so switching projects (same storage prefix)\n // can't replay a prior project's queued writes through the new project's\n // credentials. Each project's outbox persists under its own key.\n storageKey: `offline_outbox:${s.projectId}`,\n executor: (op) => replayOutboxOp(s, op),\n // Bind each queued write to the user who made it: a flush replays it\n // only while that user is signed in (signOut purges the outbox).\n // A user id counts only with a session (or refresh) token behind it.\n currentOwner: () => ({\n appUserId:\n s.identity.getSessionToken() || s.identity.getRefreshToken()\n ? s.identity.getAppUserId()\n : null,\n anonymousId: s.identity.getAnonymousId(),\n }),\n onDropped: (op, err) => {\n // A queued write the server now rejects deterministically is dropped\n // so it can't wedge the queue. Surface it rather than discard.\n if (typeof console !== \"undefined\") {\n console.warn(\n `[amba] dropped un-replayable offline ${op.kind} (${op.collection ?? op.event ?? \"?\"}):`,\n err,\n );\n }\n },\n });\n }\n return s.offlineQueue;\n}\n\n// Direct (never-queued) collection writes. The optimistic helpers use these so\n// a write either truly succeeds (resolve with the server row) or truly fails\n// (reject → rollback). Optimistic UI needs an unambiguous outcome, so it must\n// NOT silently divert to the offline outbox — a queued-pending stub is not a\n// confirmed write.\nasync function insertDirect<T>(\n s: RuntimeState,\n name: string,\n row: Record<string, unknown>,\n): Promise<T> {\n const env = await s.http.post<Envelope<T>>(\n `${P.collections}/${encodePathSegment(name)}`,\n row,\n );\n return env.data;\n}\nasync function updateDirect<T>(\n s: RuntimeState,\n name: string,\n id: string,\n set: Record<string, unknown>,\n): Promise<T> {\n const env = await s.http.patch<Envelope<T>>(\n `${P.collections}/${encodePathSegment(name)}/${encodePathSegment(id)}`,\n { set },\n );\n return env.data;\n}\nasync function deleteDirect(\n s: RuntimeState,\n name: string,\n id: string,\n): Promise<{ data: { deleted: boolean } }> {\n const result = await s.http.delete<{ data: { deleted: boolean } } | null>(\n `${P.collections}/${encodePathSegment(name)}/${encodePathSegment(id)}`,\n );\n return result ?? { data: { deleted: true } };\n}\n\n/**\n * The device OS for `X-Device-OS` from React Native's `Platform.OS`\n * (`ios`, `android`, `web` for react-native-web, `macos`, `windows`).\n * `undefined` outside React Native (Node, tests).\n */\nfunction detectDeviceOs(): string | undefined {\n try {\n // A literal lazy require is required by Metro (see configure's AppState).\n const rn = require(\"react-native\") as { Platform?: { OS?: unknown } };\n const os = rn.Platform?.OS;\n return deviceOsFromPlatform(typeof os === \"string\" ? os : undefined);\n } catch {\n return undefined;\n }\n}\n\n/**\n * The device locale's region for `X-Device-Region`: the iOS region setting\n * (`AppleLocale`) or Android's locale, falling back to the JS runtime's\n * default locale. `undefined` when none carries a two-letter region.\n */\nfunction detectDeviceRegion(): string | undefined {\n try {\n // A literal lazy require is required by Metro (see configure's AppState).\n const rn = require(\"react-native\") as {\n NativeModules?: Record<string, unknown>;\n };\n const ios = (\n rn.NativeModules?.[\"SettingsManager\"] as\n | { settings?: { AppleLocale?: unknown } }\n | undefined\n )?.settings;\n const android = rn.NativeModules?.[\"I18nManager\"] as\n | { localeIdentifier?: unknown }\n | undefined;\n const tag = ios?.AppleLocale ?? android?.localeIdentifier;\n const region = regionFromLocale(typeof tag === \"string\" ? tag : undefined);\n if (region) return region;\n } catch {\n /* Node/SSR has no native modules */\n }\n try {\n return regionFromLocale(Intl.DateTimeFormat().resolvedOptions().locale);\n } catch {\n return undefined;\n }\n}\n\n/**\n * The send path of `events.track()`: the event batch queue when batching is\n * on, otherwise the live request (with the offline outbox behind it when\n * enabled). `occurredAtOverride` is when the event happened, for one that\n * waited (see `releaseHeldEvents`).\n */\nasync function sendEvent(\n s: RuntimeState,\n event: string,\n properties: Record<string, unknown> | undefined,\n options: { telemetry?: boolean; immediate?: boolean } | undefined,\n occurredAtOverride?: string,\n): Promise<void> {\n if (s.eventQueue && !options?.immediate) {\n return s.eventQueue.enqueue(\n event,\n properties,\n options?.telemetry === true,\n occurredAtOverride,\n );\n }\n // POST /v1/client/events accepts a single event or a batch.\n // Single-shape per `packages/shared/src/types.ts:TrackEventInput`:\n // { event, properties?, timestamp?, telemetry? }\n // Pre-PR-#246 this sent `name:` instead of `event:` — the API's\n // TrackEventInput contract expects `event:`, so the field was\n // dropped, the row failed NOT-NULL, and the route returned 500\n // `TRACK_FAILED`. Web/Node SDKs route through the engine\n // which marshals the field correctly; RN's hand-rolled HTTP\n // path didn't.\n // Capture the original event TIME so a buffered event keeps its\n // `occurred_at` on replay (the events route honors a body timestamp).\n const occurredAt = occurredAtOverride ?? new Date().toISOString();\n // With the outbox on, the live body and its queued copy must carry\n // identical properties (the server's idempotency check compares\n // them), so both use one snapshot taken now.\n const props = s.offlineEnabled\n ? snapshotProperties(properties)\n : properties;\n const body: Record<string, unknown> = {\n event,\n properties: props ?? undefined,\n anonymous_id: s.identity.getAnonymousId(),\n timestamp: occurredAt,\n };\n if (options?.telemetry) {\n body[\"telemetry\"] = true;\n }\n // With the offline outbox on, the live attempt of an engagement event\n // carries the idempotency key its queued copy replays with. A request\n // that reached the server but timed out (or lost its response) before\n // the SDK heard back is then queued and replayed, and the server\n // records it once. Telemetry stays on the unkeyed hot path.\n const idempotencyKey = s.offlineEnabled ? newIdempotencyKey() : undefined;\n if (idempotencyKey && keyedLiveTrack(props, options?.telemetry)) {\n body[\"idempotency_key\"] = idempotencyKey;\n }\n // Who is writing, captured before the live attempt: a request that\n // outlives a sign-out stays attributed to this user (or is refused).\n const outboxTicket = s.offlineEnabled ? getOfflineQueue(s).ticket() : undefined;\n try {\n await s.http.post(P.events, body);\n } catch (err) {\n // With the offline outbox enabled, a transport failure buffers the\n // event for replay-on-reconnect instead of being lost. Deterministic\n // failures (validation, auth) still throw.\n if (s.offlineEnabled && isOfflineError(err)) {\n await getOfflineQueue(s).enqueue({\n kind: \"track\",\n event,\n properties: props,\n telemetry: options?.telemetry,\n occurredAt,\n idempotencyKey,\n }, outboxTicket);\n return;\n }\n throw err;\n }\n}\n\n/**\n * The local half of a sign-out on `state_`: clear the session (unless a\n * sign-in stored a newer one since `startedAt`), drop the signed-out\n * user's cached values and queued writes, and tell subscribers. False when\n * nothing was cleared.\n */\nasync function endSignedOutSession(\n state_: RuntimeState,\n rotateAnonymousId: boolean,\n startedAt: number,\n): Promise<boolean> {\n const signedOutUser = state_.identity.getAppUserId();\n const cleared = await state_.identity.clearSession(rotateAnonymousId, {\n unlessSignedInSince: startedAt,\n });\n if (!cleared) return false;\n // Events held for an identity the device couldn't read belong to the\n // session that just ended.\n dropHeldEvents(state_, \"the device signed out before its identity could be read\");\n // The signed-out user's cached config and flags go with the session (on\n // disk before signOut resolves); lookups return the signed-out values,\n // then configDefaults, until the next fetch.\n state_.remoteValues.clearForSignOut(signedOutUser);\n await state_.remoteValues.flushed();\n // Nothing the signed-out user queued offline may replay under the next\n // session, so the outbox is emptied here.\n try {\n await getOfflineQueue(state_).purge();\n } catch (err) {\n if (typeof console !== \"undefined\") {\n console.warn(\"[amba] signOut could not clear the offline outbox\", err);\n }\n }\n void state_.eventQueue?.flush();\n notifyAuthSubscribers(state_, null, \"signed_out\");\n return true;\n}\n\n/**\n * One `configure()`: build the state for `config` and install it in place\n * of the previous one (see `Amba.configure`).\n */\nasync function configureOnce(config: AmbaConfig): Promise<void> {\n if (!config.projectId || config.projectId.trim() === \"\") {\n throw new Error(\"Amba.configure: projectId is required\");\n }\n if (!config.clientKey || config.clientKey.trim() === \"\") {\n throw new Error(\"Amba.configure: clientKey is required\");\n }\n const batchSize = config.events?.batchSize ?? 100;\n const flushIntervalMs = config.events?.flushIntervalMs ?? 60_000;\n const maxQueue = config.events?.maxQueue ?? 10_000;\n if (config.events?.batching) {\n if (!Number.isInteger(batchSize) || batchSize < 1 || batchSize > 500)\n throw new Error(\"Amba.configure: events.batchSize must be 1–500\");\n if (!Number.isInteger(flushIntervalMs) || flushIntervalMs < 0)\n throw new Error(\n \"Amba.configure: events.flushIntervalMs must be a non-negative integer\",\n );\n if (!Number.isInteger(maxQueue) || maxQueue < 1)\n throw new Error(\"Amba.configure: events.maxQueue must be positive\");\n }\n const deadlines = resolveRequestDeadlines(config);\n const tokenStorage = config.tokenStorage ?? \"secure\";\n if (!TOKEN_STORAGE_MODES.includes(tokenStorage)) {\n throw new Error(\n 'Amba.configure: tokenStorage must be \"secure\", \"memory\" or \"asyncStorage\"',\n );\n }\n const secureStorage = config.secureStorage;\n if (\n secureStorage !== undefined &&\n (typeof secureStorage?.getItem !== \"function\" ||\n typeof secureStorage?.setItem !== \"function\" ||\n typeof secureStorage?.removeItem !== \"function\")\n ) {\n throw new Error(\n \"Amba.configure: secureStorage must provide getItem, setItem and removeItem\",\n );\n }\n const configDefaults = readConfigDefaults(config.configDefaults);\n // Serve the shipped defaults while configure reads storage (see\n // `_pendingConfigDefaults`).\n _pendingConfigDefaults = configDefaults;\n const storagePrefix = config.storagePrefix ?? \"amba\";\n const persistence = new AsyncStoragePersistence(storagePrefix);\n if (config.events?.batching) await persistence.assertDurable();\n await persistence.hydrate();\n const vault = await resolveTokenVault(\n tokenStorage,\n storagePrefix,\n secureStorage,\n );\n // Detach the state an earlier configure() built before the stored\n // identity is read: from here on its in-flight calls store nothing and\n // tell no subscriber, and the writes it already started land first. A\n // refresh it has in flight is handed on (`inheritedRefresh`), so its\n // refresh token is never presented twice.\n const prior = state;\n let inheritedRefresh: RefreshExchange | null = null;\n const detached = prior !== null && !prior.disposed;\n if (prior && detached) {\n prior.disposed = true;\n inheritedRefresh = prior.refreshExchange ?? prior.inheritedRefresh ?? null;\n await prior.identity.dispose();\n }\n const baseUrl = (config.apiUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n let identity: IdentityStore;\n let remoteValues: RemoteValuesCache;\n try {\n identity = await IdentityStore.create(persistence, vault);\n // The config and flags cached on the last launch, per project and user.\n const store = identity;\n remoteValues = new RemoteValuesCache(\n persistence,\n recordKey(baseUrl, config.clientKey),\n configDefaults ?? {},\n () => store.getAppUserId(),\n );\n await remoteValues.load();\n } catch (err) {\n // This configuration can't start: the earlier one stays in use.\n if (prior && detached && state === prior) {\n prior.disposed = false;\n prior.identity.reattach();\n // A renewal it had in flight may have been answered meanwhile; its\n // pair is stored now, so the spent refresh token is never sent again.\n prior.inheritedRefresh = inheritedRefresh;\n if (\n inheritedRefresh &&\n prior.identity.getRefreshToken() === inheritedRefresh.token\n ) {\n void restoreStoredSession(prior).catch(() => {});\n }\n }\n throw err;\n }\n const wrapperVersion = `amba-react-native/${SDK_VERSION}`;\n const sdkVersionHeader = `amba-sdk/${SDK_VERSION} ${wrapperVersion}`;\n const http = new AmbaHttpClient({\n apiKey: config.clientKey,\n baseUrl,\n sdkPlatform: \"react-native\",\n sdkVersionHeader,\n appVersion: normalizeAppVersion(config.appVersion),\n deviceRegion: detectDeviceRegion(),\n deviceOs: detectDeviceOs(),\n identity,\n // Every request runs under a deadline, so a fetch the network never\n // answers fails with NETWORK_ERROR and releases the outbox flush, the\n // lazy sign-in and the event batch flush that wait on it.\n requestTimeoutMs: deadlines.requestMs,\n longRequestTimeoutMs: deadlines.longMs,\n // Lazily sign in anonymously on the first authenticated call so reads\n // don't silently return no-data when the app never called\n // `signInAnonymously()`. Opt out per-config via `autoAnonymousSession:false`.\n // While the user must sign in again, such a call fails with\n // `SIGN_IN_REQUIRED` either way.\n ensureSession: async (path: string) => {\n if (pathSkipsLazySession(path)) return;\n if (state?.identity.isSignInRequired()) throw signInRequiredError();\n if (config.autoAnonymousSession === false) return;\n await ensureAnonymousSession(getState());\n },\n // A call the server refuses for its session token (a link on another\n // device signed this one out) continues the session with the refresh\n // token, or ends it so the app can ask the user to sign in again.\n onSessionRefused: (usedToken: string, usedUserId: string | null) =>\n recoverRefusedSession(getState(), usedToken, usedUserId),\n });\n // Dispose any prior offline queue (and AWAIT its in-flight flush) BEFORE\n // installing the new state, so the orphaned queue can never write the\n // outbox concurrently with the replacement. Bump the activation token so a\n // stale disable callback from the previous config no-ops.\n _offlineEnableToken += 1;\n if (prior?.offlineQueue) await prior.offlineQueue.dispose();\n prior?.detachEventAppState?.();\n prior?.detachIdentityAppState?.();\n if (prior?.eventQueue) await prior.eventQueue.dispose();\n experimentReporter.reset();\n const nextState: RuntimeState = {\n http,\n identity,\n debug: config.debug ?? false,\n projectId: config.projectId,\n clientKey: config.clientKey,\n baseUrl,\n functionsBaseUrl: config.functionsBaseUrl ?? null,\n resolvedFunctionsProjectId: null,\n functionsProjectIdInflight: null,\n persistence,\n remoteValues,\n refreshedFor: { config: null, flags: null },\n offlineEnabled: false,\n offlineQueue: null,\n eventQueue: null,\n inheritedRefresh,\n };\n // Events the replaced configuration held for a locked store move along\n // when it stores the same project's identity.\n if (prior?.heldEvents?.length || prior?.heldEventsDropped) {\n if (sameIdentityTarget(prior, nextState)) {\n // Generations count per store: carried events belong to the identity\n // this store loaded.\n const launchedAt = identity.getGeneration();\n nextState.heldEvents = (prior.heldEvents ?? []).map((held) => ({\n ...held,\n generation: launchedAt,\n }));\n nextState.heldEventsDropped = prior.heldEventsDropped;\n prior.heldEvents = [];\n prior.heldEventsDropped = 0;\n } else {\n dropHeldEvents(prior, \"the SDK was configured for another project\");\n }\n }\n state = nextState;\n // Held events go out once the stored identity is known.\n identity.onAnonymousIdKnown(() => {\n void releaseHeldEvents(nextState);\n });\n // A stored session that couldn't be read at launch (a locked device)\n // loads later. Subscribers hear it once it does; one whose session token\n // expired meanwhile is renewed first, as at launch.\n identity.onStoredSessionLoaded(() => {\n const userId = identity.getAppUserId();\n if (identity.getSessionToken() && userId) {\n notifyAuthSubscribers(\n nextState,\n snapshotSession({ id: userId, auth_providers: [], properties: {} }),\n \"session_restored\",\n );\n } else if (\n config.autoAnonymousSession !== false &&\n identity.getRefreshToken()\n ) {\n void restoreStoredSession(nextState).catch(() => {});\n }\n });\n // A store locked at launch is read again when the app comes to the\n // foreground, so `isAuthenticated`, `appUserId` and subscribers catch up\n // without waiting for a call.\n try {\n // A literal lazy require is required by Metro (see the event batching\n // AppState below).\n const { AppState } = require(\"react-native\") as typeof import(\"react-native\");\n const sub = AppState.addEventListener(\"change\", (status) => {\n if (status === \"active\" && identity.credentialsUnknown()) {\n void identity.credentialsStillUnreadable();\n }\n });\n nextState.detachIdentityAppState = () => sub.remove();\n } catch {\n /* Node/SSR has no native AppState */\n }\n if (config.events?.batching) {\n const queue = new EventBatchQueue(\n offlineStore(nextState),\n `event_outbox:${nextState.projectId}`,\n { batchSize, flushIntervalMs, maxQueue, onDrop: config.events.onDrop },\n () => ({\n userId: nextState.identity.getAppUserId(),\n anonymousId: nextState.identity.getAnonymousId(),\n sessionToken: nextState.identity.getSessionToken(),\n }),\n (body, token) =>\n nextState.http.postWithSession(\n \"/v1/client/events/batch\",\n body,\n token,\n ),\n (body, token) => nextState.http.postWithSession(P.events, body, token),\n );\n nextState.eventQueue = queue;\n await queue.start();\n // React Native may suspend JS immediately after the callback. Persist\n // the coalescing buffer first, then make a best-effort network attempt.\n try {\n // A literal lazy require is required by Metro. Dynamic import would\n // enumerate React Native's full export barrel in Expo Go.\n const { AppState } =\n require(\"react-native\") as typeof import(\"react-native\");\n const sub = AppState.addEventListener(\"change\", (status) => {\n if (status === \"background\") void queue.flush();\n });\n nextState.detachEventAppState = () => sub.remove();\n } catch {\n /* Node/SSR has no native AppState */\n }\n }\n if (\n !nextState.functionsBaseUrl &&\n !PROJECT_ID_PATTERN.test(nextState.projectId)\n ) {\n void resolveFunctionsProjectId(nextState).catch(() => {});\n }\n // Events carried over from the replaced configuration go out now when the\n // stored identity is already known (after the event queue started).\n if (nextState.heldEvents?.length && !identity.hasStandInAnonymousId()) {\n void releaseHeldEvents(nextState);\n }\n // A renewal the replaced configuration had in flight is finished now, so\n // its new pair is stored even when no call follows. Otherwise the SDK\n // manages the session unless the app opted out: a stored session whose\n // session token expired is restored in the background. A network failure\n // leaves it for the next authenticated call to retry.\n if (\n (inheritedRefresh !== null &&\n identity.getRefreshToken() === inheritedRefresh.token) ||\n (config.autoAnonymousSession !== false &&\n !identity.getSessionToken() &&\n identity.getRefreshToken())\n ) {\n void restoreStoredSession(nextState).catch(() => {});\n }\n}\n\n/**\n * Top-level SDK namespace for React Native.\n */\nexport const Amba = {\n configure: (config: AmbaConfig): Promise<void> => {\n // Runs at once when no configure() is pending, so `configDefaults` serve\n // the first frame while it reads storage; otherwise after the pending one.\n const run = configuring\n ? configureChain.then(() => configureOnce(config))\n : configureOnce(config);\n configureChain = run.catch(() => undefined);\n configuring = run;\n const settle = () => {\n if (configuring === run) configuring = null;\n };\n run.then(settle, settle);\n return run;\n },\n\n /**\n * Test-only hook to reset the singleton between tests. Not part of\n * the customer-facing surface; the production SDK uses\n * `Amba.auth.signOut(true)` to fully reset identity.\n * @internal\n */\n _reset: (): void => {\n if (state) {\n state.disposed = true;\n void state.identity.dispose();\n }\n state?.detachEventAppState?.();\n state?.detachIdentityAppState?.();\n void state?.eventQueue?.dispose();\n state = null;\n authSubscribers.clear();\n },\n\n get anonymousId(): string {\n return getState().identity.getAnonymousId();\n },\n get appUserId(): string | undefined {\n return getState().identity.getAppUserId() ?? undefined;\n },\n get isAuthenticated(): boolean {\n return getState().identity.isAuthenticated();\n },\n get projectId(): string {\n return getState().projectId;\n },\n setDebug: (enabled: boolean): void => {\n getState().debug = enabled;\n },\n\n /**\n * Live collection subscriptions. Returns a builder — chain `.where(...)`\n * to narrow, then `.subscribe(cb)` to start streaming row changes:\n *\n * const unsub = Amba.collection(\"messages\")\n * .where(\"circle_id\", circleId)\n * .subscribe((change) => {\n * // change.op: \"INSERT\" | \"UPDATE\" | \"DELETE\"; change.row: the row\n * });\n * unsub(); // stop listening\n *\n * Delivery mirrors `Amba.collections.find()` — you receive only rows your\n * session owns (plus shared/public rows), optionally narrowed by\n * `.where(...)`. Reconnects automatically with backoff if the connection\n * drops. Requires an authenticated session.\n */\n collection: <T = unknown>(name: string): CollectionSubscription<T> =>\n new CollectionSubscription<T>(\n {\n baseUrl: getState().baseUrl,\n authHeaders: realtimeAuthHeaders,\n connectTimeoutMs: getState().http.requestTimeoutMs,\n },\n name,\n ),\n\n /**\n * Live gamification updates for the signed-in user. Call `.subscribe(cb)`\n * to receive xp / level / achievement / streak changes live:\n *\n * const unsub = Amba.gamification.subscribe((change) => {\n * // change.kind: \"xp\" | \"level\" | \"achievement\" | \"streak\"\n * // change.data: the new state\n * });\n * unsub(); // stop listening\n *\n * You only ever receive your own gamification changes — never another\n * user's. A getter so `baseUrl` is read at access time. Requires a session.\n */\n get gamification(): GamificationSubscription {\n return new GamificationSubscription({\n baseUrl: getState().baseUrl,\n authHeaders: realtimeAuthHeaders,\n connectTimeoutMs: getState().http.requestTimeoutMs,\n });\n },\n\n events: {\n /**\n * Track an event.\n *\n * Default routes to the engagement meter (fan-out to segments,\n * workflows, push). Pass `{ telemetry: true }` for high-volume,\n * fire-and-forget events that DON'T need fan-out: UI button\n * clicks, scroll-depth markers, game-loop telemetry. Telemetry\n * events are billed at $0.10 per 1M overage vs $0.50 per 10K for\n * engagement events.\n *\n * @example\n * await Amba.events.track('purchase_completed', { amount: 9.99 });\n * await Amba.events.track('scroll_depth', { px: 1024 }, { telemetry: true });\n */\n track: async (\n event: string,\n properties?: Record<string, unknown>,\n options?: { telemetry?: boolean; immediate?: boolean },\n ): Promise<void> => {\n // While the stored identity can't be read (a device locked since\n // launch), the anonymous id is a stand-in and an event sent under it\n // would belong to nobody. The event waits in memory and goes out, in\n // order, once the identity loads (`releaseHeldEvents`); the call\n // resolves now, as a queued event's does.\n let s = getState();\n for (let attempt = 0; ; attempt += 1) {\n const hold =\n (s.heldEvents?.length ?? 0) > 0 ||\n (await s.identity.anonymousIdStillUnknown());\n // A `configure()` replaced the state meanwhile: the event goes to the\n // state it installs.\n if (s.disposed && attempt === 0) {\n s = await liveState();\n continue;\n }\n if (!hold) return sendEvent(s, event, properties, options);\n if (typeof event !== \"string\" || event.trim() === \"\") {\n throw new AmbaApiError(\"VALIDATION_ERROR\", \"event name must be non-empty\");\n }\n holdEvent(s, event, properties, options);\n if (!s.identity.hasStandInAnonymousId()) void releaseHeldEvents(s);\n return;\n }\n },\n flush: async (): Promise<EventFlushResult> =>\n getState().eventQueue?.flush() ?? { sent: 0, rejected: 0, remaining: 0 },\n pendingCount: async (userId?: string): Promise<number> =>\n getState().eventQueue?.pendingCount(userId) ?? 0,\n clear: async ({ userId }: { userId: string }): Promise<void> => {\n if (!userId) throw new Error(\"Amba.events.clear: userId is required\");\n await getState().eventQueue?.clear(userId);\n },\n setOnline: (online: boolean): void => {\n getState().eventQueue?.setOnline(online);\n },\n setBatchingEnabled: (enabled: boolean): void => {\n getState().eventQueue?.setBatchingEnabled(enabled);\n },\n },\n\n auth: {\n /**\n * Sign in as the device's anonymous user. Sends the SDK's anonymous_id\n * so the server either resumes the anonymous user that holds it (no MAU\n * charge) or creates one (DX-2). When that id belongs to a user who\n * signs in with a linked identity, and this device still holds that\n * user's refresh token, the call continues that user's session; with no\n * usable refresh token the device takes a new anonymous id and becomes a\n * new anonymous user. While the user must sign in again\n * (`sign_in_required`), this is how the app starts a new anonymous user\n * instead.\n */\n signInAnonymously: async (): Promise<AuthResult> => {\n const s = await liveState();\n // A secure store that opened since launch loads the session it holds\n // first, and a renewal that load starts finishes before this sign-in\n // is sent. The anonymous id of a stored session that still can't be\n // read is unknown; signing in now would start a new user in its place.\n await loadUnreadSession(s);\n if (await s.identity.anonymousIdStillUnknown()) {\n throw secureStoreUnavailableError();\n }\n const startedAt = s.identity.getGeneration();\n try {\n return await postAuth(s, P.authAnonymous, {\n anonymous_id: s.identity.getAnonymousId(),\n });\n } catch (err) {\n if (\n !(err instanceof AmbaApiError) ||\n err.code !== IDENTITY_SIGN_IN_REQUIRED\n )\n throw err;\n const held = await continueHeldSession(s);\n if (held) return held;\n // A new anonymous user, unless a sign-in stored a newer session\n // while this call was out.\n if (!(await s.identity.clearSession(true, { unlessSignedInSince: startedAt }))) {\n throw sessionChangedError(\n \"a sign-in stored a newer session while this call was in flight\",\n );\n }\n return postAuth(s, P.authAnonymous, {\n anonymous_id: s.identity.getAnonymousId(),\n });\n }\n },\n signInWithEmail: async (\n email: string,\n password: string,\n ): Promise<AuthResult> => {\n requireEmail(email);\n if (password === \"\")\n throw new AmbaApiError(\n \"VALIDATION_ERROR\",\n \"password must not be empty\",\n );\n return postAuth(await liveState(), P.authEmailLogin, { email, password });\n },\n signUpWithEmail: async (\n email: string,\n password: string,\n ): Promise<AuthResult> => {\n requireEmail(email);\n requirePassword(password);\n return postAuth(await liveState(), P.authEmailSignup, { email, password });\n },\n /**\n * Sign in with an Apple ID identity token (from\n * `ASAuthorization.identityToken` on iOS / `expo-apple-authentication`).\n * Replaces the 3.x `signInWithSocial('apple', token)` polymorphic form.\n *\n * Pass `options.nonce` when the sign-in request carried a nonce: the raw\n * value you generated for this attempt, whose SHA-256 hex digest you gave\n * Apple. Amba checks that the token's `nonce` claim is that digest, so a\n * token issued to another sign-in can't be used here.\n */\n signInWithApple: async (\n identityToken: string,\n options?: SocialSignInOptions,\n ): Promise<AuthResult> => {\n if (identityToken.trim() === \"\")\n throw new AmbaApiError(\n \"VALIDATION_ERROR\",\n \"identity_token must not be empty\",\n );\n return postAuth(await liveState(), P.authSocial, {\n provider: \"apple\",\n id_token: identityToken,\n ...nonceField(options),\n });\n },\n /**\n * Sign in with a Google ID token (from Google Sign-In SDK / Expo\n * `expo-auth-session/providers/google`). Replaces the 3.x\n * `signInWithSocial('google', token)` polymorphic form.\n *\n * Pass `options.nonce` when the sign-in request carried a nonce (see\n * `signInWithApple`).\n */\n signInWithGoogle: async (\n idToken: string,\n options?: SocialSignInOptions,\n ): Promise<AuthResult> => {\n if (idToken.trim() === \"\")\n throw new AmbaApiError(\n \"VALIDATION_ERROR\",\n \"id_token must not be empty\",\n );\n return postAuth(await liveState(), P.authSocial, {\n provider: \"google\",\n id_token: idToken,\n ...nonceField(options),\n });\n },\n /**\n * Request a one-time passcode for `email`. Server emails a 6-digit\n * code; the user types it in, then call\n * `verifyEmailOtp(email, code)` to exchange for a session.\n */\n requestEmailOtp: async (email: string): Promise<void> => {\n requireEmail(email);\n await getState().http.post(P.authEmailOtpRequest, { email });\n },\n /**\n * Exchange `(email, code)` for a session. Returns the same shape\n * as `signInWithEmail`.\n */\n verifyEmailOtp: async (\n email: string,\n code: string,\n ): Promise<AuthResult> => {\n requireEmail(email);\n requireOtpCode(code);\n return postAuth(await liveState(), P.authEmailOtpVerify, { email, code });\n },\n /**\n * Request a one-time passcode by SMS. `phone` must be E.164\n * (starts with +, 8–15 digits). Server sends a 6-digit code;\n * user types it, then call `verifySmsOtp(phone, code)` to\n * exchange for a session.\n */\n requestSmsOtp: async (phone: string): Promise<void> => {\n requireE164Phone(phone);\n await getState().http.post(P.authSmsOtpRequest, { phone });\n },\n /**\n * Exchange `(phone, code)` for a session. Same shape as\n * `signInWithEmail`.\n */\n verifySmsOtp: async (phone: string, code: string): Promise<AuthResult> => {\n requireE164Phone(phone);\n requireOtpCode(code);\n return postAuth(await liveState(), P.authSmsOtpVerify, { phone, code });\n },\n /**\n * Request a one-time magic link for `email`. The server emails a\n * deep-link URL containing a single-use token; the app's deep-link\n * handler extracts the token and calls `verifyMagicLink(token)` to\n * exchange for a session. NEW in 4.0.\n */\n requestMagicLink: async (email: string): Promise<void> => {\n requireEmail(email);\n await getState().http.post(P.authMagicLinkRequest, { email });\n },\n /**\n * Exchange a magic-link `token` for a session. Same shape as\n * `signInWithEmail`. NEW in 4.0.\n */\n verifyMagicLink: async (token: string): Promise<AuthResult> => {\n if (token.trim() === \"\")\n throw new AmbaApiError(\"VALIDATION_ERROR\", \"token must not be empty\");\n return postAuth(await liveState(), P.authMagicLinkVerify, { token });\n },\n /**\n * Link a social identity provider to the current (anonymous or\n * identified) user. Upgrades an anonymous session to an identified\n * one without minting a new app_user. NEW in 4.0.\n *\n * Mirrors `AmbaCore::link_account` in the engine — the SDK reads\n * the active session token from local identity state and posts\n * `{ provider, token, session_token }` to `/v1/client/auth/link`,\n * which is the wire shape the server expects. Callers must already\n * have a session (any flow) before linking; an `UNAUTHORIZED` error\n * is thrown if none is present.\n *\n * Email linking via this endpoint is rejected server-side — use\n * `signInWithEmail` / `signUpWithEmail` instead.\n *\n * @param provider — `\"apple\" | \"google\"`\n * @param credential — provider identity token (Apple `identityToken`,\n * Google `id_token`).\n * @param options — `nonce`: the raw nonce of this sign-in attempt, when\n * the provider request carried its SHA-256 hex digest.\n */\n linkAccount: async (\n provider: string,\n credential: string,\n options?: SocialSignInOptions,\n ): Promise<AuthResult> => {\n const providerTrimmed = provider?.trim() ?? \"\";\n if (providerTrimmed === \"\")\n throw new AmbaApiError(\n \"VALIDATION_ERROR\",\n \"provider must not be empty\",\n );\n if (typeof credential !== \"string\" || credential.trim() === \"\")\n throw new AmbaApiError(\n \"VALIDATION_ERROR\",\n \"credential must not be empty\",\n );\n const s = await liveState();\n return withBodySession(s, (sessionToken) =>\n trackLink(\n s,\n postAuth(\n s,\n P.authLink,\n {\n provider: providerTrimmed,\n token: credential,\n session_token: sessionToken,\n ...nonceField(options),\n },\n // The link acts for the session it was sent with: a sign-in or\n // sign-out that lands meanwhile wins.\n { ifGeneration: s.identity.getGeneration(), advance: false },\n ),\n ),\n );\n },\n\n /**\n * Attach a verified phone to the current session's user. Same\n * `user_id`; the SDK swaps in the fresh session pair the link returns,\n * and every other session of this user is signed out. Server returns\n * `PHONE_ALREADY_LINKED` (409) if the phone is owned by another user —\n * the SDK rethrows as `AmbaApiError`. The error names no account.\n */\n linkSmsOtp: async (phone: string, code: string): Promise<User> => {\n requireE164Phone(phone);\n requireOtpCode(code);\n return postLink(await liveState(), {\n provider: \"sms_otp\",\n phone,\n code,\n });\n },\n\n /** Symmetric counterpart to `linkSmsOtp` for email. */\n linkEmailOtp: async (email: string, code: string): Promise<User> => {\n requireEmail(email);\n requireOtpCode(code);\n return postLink(await liveState(), {\n provider: \"email_otp\",\n email,\n code,\n });\n },\n\n /**\n * Upgrade an anonymous (or any current) session by attaching an\n * email + password credential — converting the current user in place\n * to an email-identified account so its data carries over (same\n * `user_id`; the SDK swaps in the fresh session pair the link returns,\n * and every other session of this user is signed out). Server returns\n * `EMAIL_ALREADY_LINKED` (409) if the email is owned by another user —\n * rethrown as `AmbaApiError`. The error names no account.\n * After linking, the user can sign in via `signInWithEmail`.\n */\n linkEmailPassword: async (\n email: string,\n password: string,\n ): Promise<User> => {\n requireEmail(email);\n requirePassword(password);\n return postLink(await liveState(), {\n provider: \"email_password\",\n email,\n password,\n });\n },\n\n /**\n * Detach the SMS-OTP identifier from the current session's user.\n * Refused with `LAST_IDENTIFIER` if it would orphan the account.\n */\n unlinkSmsOtp: async (): Promise<User> => unlinkProvider(\"sms_otp\"),\n unlinkEmailOtp: async (): Promise<User> => unlinkProvider(\"email_otp\"),\n unlinkApple: async (): Promise<User> => unlinkProvider(\"apple\"),\n unlinkGoogle: async (): Promise<User> => unlinkProvider(\"google\"),\n\n /**\n * Snapshot of identifier providers attached to the current user.\n * Values come back **masked** (phone last-4, `a***@domain`).\n */\n getCurrentIdentifiers: async (): Promise<LinkedIdentifier[]> => {\n const s = getState();\n await settleSessionInFlight(s);\n requireSession(s);\n const env = await s.http.get<IdentifiersEnvelope>(P.authIdentifiers);\n return env.data.identifiers;\n },\n\n /**\n * Sign out: revoke the refresh token server-side (best effort) and\n * clear the local session. The anonymous id is rotated whenever the\n * session being ended was not purely anonymous, so the next person on\n * the device starts as a new anonymous user. `rotateAnonymousId: true`\n * rotates it after an anonymous session too. A pending\n * `sign_in_required` ends here as well: with `autoAnonymousSession` on,\n * the next call that needs a session starts a new anonymous user.\n *\n * Signing out ends the session the call started with. When a sign-in\n * stores a newer session while the server call is out, that session\n * stays.\n */\n signOut: async (rotateAnonymousId = false): Promise<void> => {\n const s = await liveState();\n // A stored session this launch couldn't read yet (a locked device) is\n // loaded first when it can be, so it is revoked and ended like any\n // other. One that still can't be read ends unseen (`clearSession`).\n await loadUnreadSession(s);\n const startedAt = s.identity.getGeneration();\n const revoked = s.identity.getRefreshToken();\n if (s.identity.getSessionToken()) {\n try {\n await s.http.post(\n P.authLogout,\n revoked ? { refresh_token: revoked } : {},\n );\n } catch (err) {\n // Best-effort: a server-side rejection should not trap the\n // user offline. Mirrors the engine's behaviour.\n if (typeof console !== \"undefined\") {\n console.warn(\"[amba] signOut server call failed\", err);\n }\n }\n }\n if (await endSignedOutSession(s, rotateAnonymousId, startedAt)) return;\n // A `configure()` replaced the state while the server call was out.\n // The new state ends the same session: same project, created holding\n // the refresh token this call revoked, renewed or ended since but\n // never replaced by a sign-in. A newer sign-in or another project's\n // session stays.\n if (!s.disposed || revoked === null) return;\n const next = await liveState();\n if (\n next !== s &&\n !next.disposed &&\n sameIdentityTarget(next, s) &&\n next.identity.continuesLaunchSession(revoked)\n ) {\n await endSignedOutSession(\n next,\n rotateAnonymousId,\n next.identity.getGeneration(),\n );\n }\n },\n refresh: async (): Promise<AuthResult> => refreshSession(await liveState()),\n me: async (): Promise<User> => {\n const s = getState();\n await settleSessionInFlight(s);\n requireSession(s);\n const env = await s.http.get<Envelope<User>>(P.authMe);\n return normalizeUserAuthProviders(env.data);\n },\n\n getSession: async (): Promise<Session | null> => {\n const s = getState();\n await settleSessionInFlight(s);\n if (!s.identity.isAuthenticated()) return null;\n try {\n const env = await s.http.get<Envelope<User>>(P.authMe);\n return snapshotSession(normalizeUserAuthProviders(env.data));\n } catch (err) {\n // The server refused the session and its refresh token was\n // rejected too: the session has ended (subscribers heard `null`).\n if (isSessionRefusedError(err) && !s.identity.isAuthenticated()) {\n return null;\n }\n throw err;\n }\n },\n\n /**\n * The device's anonymous id. While the stored identity can't be read\n * (a device locked since launch), it is unknown and the call fails with\n * `SECURE_STORE_UNAVAILABLE`; `Amba.anonymousId` meanwhile holds a\n * stand-in that is replaced once the store opens.\n */\n getAnonymousId: async (): Promise<string> => {\n const s = getState();\n if (await s.identity.anonymousIdStillUnknown()) {\n throw secureStoreUnavailableError();\n }\n return s.identity.getAnonymousId();\n },\n\n /**\n * Subscribe to auth changes. `event.reason` says why the callback fired\n * (see `AuthStateChangeReason`); `sign_in_required` means a session with\n * a linked identity ended and the app should show its sign-in screen.\n */\n onAuthStateChange: (\n cb: (session: Session | null, event: AuthStateChangeEvent) => void,\n ): (() => void) => {\n authSubscribers.add(cb);\n return () => {\n authSubscribers.delete(cb);\n };\n },\n },\n\n /**\n * Profile read/update for the current user. NEW in 4.0.\n *\n * `get()` — fetches the current user's full record. Short-circuits to\n * `/auth/me` so the server doesn't have to look up the caller twice.\n *\n * Both `users.get(userId)` and `users.update(userId, patch)` are\n * SDK 4.0 surfaces — `userId` omitted = current user, `userId` set\n * = the other user (server route `/v1/client/users/{id}`). The\n * server returns a public-safe profile shape for non-self reads;\n * cross-user writes require the caller to hold `users:write` in\n * their role permissions (403 otherwise).\n */\n users: {\n // <generated:users>\n get: async (userId?: string): Promise<User> => {\n const s = getState();\n await settleSessionInFlight(s);\n requireSession(s);\n const path =\n userId === undefined || userId === null || userId === \"\"\n ? P.usersMe\n : `${P.users}/${encodePathSegment(userId)}`;\n const env = await s.http.get<Envelope<User>>(path);\n return normalizeUserAuthProviders(env.data);\n },\n /**\n * Update a profile. Two overloads:\n * - `update(patch)` — update the current user (PATCH /users/me).\n * - `update(userId, patch)` — admin-style cross-user update\n * (PATCH /users/{id}); requires `users:write` role permission\n * server-side, else 403.\n */\n update: (async (\n arg1: string | UserPatch,\n arg2?: UserPatch,\n ): Promise<User> => {\n const s = getState();\n await settleSessionInFlight(s);\n requireSession(s);\n const isById = typeof arg1 === \"string\";\n const patch = isById ? (arg2 ?? {}) : (arg1 as UserPatch);\n const path = isById\n ? `${P.users}/${encodePathSegment(arg1)}`\n : `${P.users}/me`;\n const env = await s.http.patch<Envelope<User>>(path, patch);\n return normalizeUserAuthProviders(env.data);\n }) as {\n (patch: UserPatch): Promise<User>;\n (userId: string, patch: UserPatch): Promise<User>;\n },\n // </generated:users>\n },\n\n /**\n * Server-tracked customer-defined app sessions for the current user.\n * NEW in 4.0. These are the analytics `app_sessions` rows\n * (open/background events), not auth device sessions.\n *\n * - `list()` → `GET /v1/client/sessions` returns `AppSession[]` newest\n * first (capped at 100 by the server).\n * - `revoke(sessionId)` → `POST /v1/client/sessions/:id/end` ends the\n * given session row (idempotent — a second `revoke` on an already-\n * ended session is a no-op). `sessionId` is the row id (UUID)\n * returned by `list()` (the `id` field, not `session_id`). Server\n * has no DELETE on `/sessions/:id` — `end` is the canonical revoke.\n */\n sessions: {\n // <generated:sessions>\n list: async (): Promise<SessionRecord[]> => {\n const env = await getState().http.get<Envelope<SessionRecord[]>>(\n P.sessions,\n );\n return env.data;\n },\n revoke: async (sessionId: string): Promise<void> => {\n await getState().http.post(\n `${P.sessions}/${encodePathSegment(sessionId)}/end`,\n {},\n );\n },\n // </generated:sessions>\n },\n\n /**\n * Offline change replay. NEW in 4.0. Wraps `/v1/client/sync` per\n * `apps/api/src/routes/client/sync.ts` and the SDK core's\n * `SyncModule`.\n *\n * - `pushChanges(changes)` → `POST /v1/client/sync` with `{ changes }`.\n * Server returns `{ applied, conflicts, checkpoint_token }`.\n * Conflicts are server-wins; callers should re-apply\n * `server_data` from each `SyncConflict` to their local cache\n * before issuing follow-up writes.\n * - `pullChanges({ entity_type, checkpoint_token? })` →\n * `GET /v1/client/sync?entity_type=…&checkpoint_token=…`. Server\n * requires `entity_type`; `checkpoint_token` is optional on the\n * first pull. Returns `{ changes, checkpoint_token, has_more }`.\n *\n * NOTE: The wire is a single `/sync` endpoint with method = action.\n * Earlier 4.0 drafts referenced `/sync/push` and `/sync/pull` — those\n * routes do not exist on the server.\n */\n sync: {\n pushChanges: async (changes: SyncChange[]): Promise<SyncPushResult> => {\n const env = await getState().http.post<Envelope<SyncPushResult>>(P.sync, {\n changes,\n });\n return env.data;\n },\n pullChanges: async (since: {\n entity_type: string;\n checkpoint_token?: string;\n }): Promise<SyncPullResult> => {\n if (!since.entity_type || since.entity_type.trim() === \"\") {\n throw new AmbaApiError(\n \"VALIDATION_ERROR\",\n \"pullChanges requires a non-empty entity_type\",\n );\n }\n const query: Record<string, string> = { entity_type: since.entity_type };\n if (since.checkpoint_token !== undefined) {\n query[\"checkpoint_token\"] = since.checkpoint_token;\n }\n const env = await getState().http.get<Envelope<SyncPullResult>>(\n P.sync,\n query,\n );\n return env.data;\n },\n },\n\n /**\n * Automatic offline mutation queue (opt-in).\n *\n * Once enabled, a collection write (`insert` / `update` / `delete`) or a\n * `track` that fails because the device is offline is buffered to a durable\n * on-device outbox (AsyncStorage) instead of throwing — then replayed in\n * order on the next {@link flush} (e.g. when your NetInfo listener detects\n * reconnect). Replays reuse a per-op idempotency key so a write that reached\n * the server before the connection dropped is never applied twice.\n *\n * This is distinct from `Amba.sync` (manual change replay): `offline` is the\n * automatic, fire-and-forget safety net for ordinary writes.\n *\n * @example\n * import NetInfo from \"@react-native-community/netinfo\";\n * Amba.offline.enable();\n * NetInfo.addEventListener((s) => { if (s.isConnected) Amba.offline.flush(); });\n * await Amba.collections.insert(\"todos\", { title: \"Buy milk\" }); // queues if offline\n */\n offline: {\n /**\n * Turn the outbox on. Idempotent. Returns a disable function (queued ops\n * are NOT dropped — call `clear()` for that). Drive `flush()` from your own\n * connectivity listener (e.g. NetInfo) since React Native has no global\n * `online` event.\n */\n enable: (): (() => void) => {\n const s = getState();\n s.offlineEnabled = true;\n // Bump + capture the activation token so this call's disabler only acts\n // while it's still the current activation (a later enable() or a\n // configure() supersedes it).\n _offlineEnableToken += 1;\n const token = _offlineEnableToken;\n return () => {\n // No-op if a later enable() or a configure() superseded this activation\n // (token moved on) or replaced the runtime state. Otherwise a stale\n // disabler would wrongly flip the active/fresh outbox off.\n if (_offlineEnableToken !== token || state !== s) return;\n s.offlineEnabled = false;\n };\n },\n /** Whether the outbox is currently enabled. */\n isEnabled: (): boolean => getState().offlineEnabled,\n /** Count of writes awaiting replay. */\n pendingCount: async (): Promise<number> =>\n getOfflineQueue(getState()).pendingCount(),\n /** Snapshot the pending ops (a copy — safe to inspect). */\n pending: async (): Promise<OutboxOp[]> =>\n getOfflineQueue(getState()).pending(),\n /** Force a replay attempt now. Resolves with `{ flushed, dropped, remaining }` (dropped = poisoned ops the server rejects on replay, removed so they cannot wedge the queue). */\n flush: async (): Promise<FlushResult> => {\n const s = getState();\n const result = s.offlineEnabled\n ? await getOfflineQueue(s).flush()\n : {\n flushed: 0,\n dropped: 0,\n remaining: s.offlineQueue ? await s.offlineQueue.pendingCount() : 0,\n };\n await s.eventQueue?.flush();\n return result;\n },\n /** Drop every queued write without replaying. */\n clear: async (): Promise<void> => getOfflineQueue(getState()).clear(),\n },\n\n collections: {\n // <generated:collections.find>\n find: async <T = unknown>(\n name: string,\n options: FindOptions = {},\n ): Promise<FindResponse<T>> => {\n const s = getState();\n // Server returns `{ data, next_cursor, has_more }` for find —\n // the FindResponse shape matches the envelope directly.\n return s.http.post<FindResponse<T>>(\n `${P.collections}/${encodePathSegment(name)}/find`,\n options,\n );\n },\n // </generated:collections.find>\n findOne: async <T = unknown>(name: string, id: string): Promise<T> => {\n const s = getState();\n const env = await s.http.get<Envelope<T>>(\n `${P.collections}/${encodePathSegment(name)}/${encodePathSegment(id)}`,\n );\n return env.data;\n },\n /**\n * Vector similarity search. Returns the top `k` rows closest to the\n * supplied `vector` by cosine distance (default), optionally\n * further filtered by `where`. NEW in 4.0 — wires to the\n * pgvector-backed `POST /v1/client/collections/:name/find-nearest`\n * route.\n *\n * Wire body matches `NearestRequestBody` in\n * `apps/api/src/routes/client/collections.ts`: `to_vector` (or\n * `to_text` for server-side embedding) + optional `k`, `column`,\n * `distance`, `where`, `includeDeleted`.\n */\n findNearest: async <T = unknown>(\n name: string,\n vector: number[],\n k: number,\n where?: Filter,\n ): Promise<T[]> => {\n const s = getState();\n const body: Record<string, unknown> = { to_vector: vector, k };\n if (where !== undefined) body.where = where;\n const env = await s.http.post<FindResponse<T>>(\n `${P.collections}/${encodePathSegment(name)}/find-nearest`,\n body,\n );\n return env.data;\n },\n /**\n * Count rows in a collection, optionally filtered. NEW in 4.0 —\n * wires to `GET /v1/client/collections/:name/count` with an\n * optional `?query={\"where\":...}` body channel (GET requests\n * can't carry a JSON body, so the server reads the JSON from the\n * `query` param).\n *\n * The server's `/count` route only honors `where` + `includeDeleted`\n * — passing other knobs (`limit`, `cursor`, etc.) returns\n * `INVALID_QUERY_PARAM`. Mirrors the SDK core's `CollectionsModule::count`.\n */\n count: async (name: string, where?: Filter): Promise<number> => {\n const s = getState();\n const query: Record<string, string> | undefined =\n where !== undefined ? { query: JSON.stringify({ where }) } : undefined;\n const env = await s.http.get<Envelope<{ count: number }>>(\n `${P.collections}/${encodePathSegment(name)}/count`,\n query,\n );\n return env.data.count;\n },\n insert: async <T = unknown>(\n name: string,\n row: Record<string, unknown>,\n ): Promise<T> => {\n const s = getState();\n if (s.offlineEnabled) {\n // Mint the idempotency key up front and send it on the live attempt\n // too. If that attempt reached the server before the connection\n // dropped, the queued replay carries the SAME key and the API dedupes\n // it (migration 056) — no double-insert.\n const idempotencyKey =\n (typeof row[\"idempotency_key\"] === \"string\"\n ? (row[\"idempotency_key\"] as string)\n : undefined) ?? newIdempotencyKey();\n // Who is writing, captured before the live attempt: a request that\n // outlives a sign-out stays attributed to this user (or is refused).\n const outboxTicket = s.offlineEnabled ? getOfflineQueue(s).ticket() : undefined;\n try {\n const env = await s.http.post<Envelope<T>>(\n `${P.collections}/${encodePathSegment(name)}`,\n { ...row, idempotency_key: idempotencyKey },\n );\n return env.data;\n } catch (err) {\n if (isOfflineError(err)) {\n await getOfflineQueue(s).enqueue({\n kind: \"collection.insert\",\n collection: name,\n payload: row,\n idempotencyKey,\n }, outboxTicket);\n return { ...row, _amba_pending: true } as unknown as T;\n }\n throw err;\n }\n }\n const env = await s.http.post<Envelope<T>>(\n `${P.collections}/${encodePathSegment(name)}`,\n row,\n );\n return env.data;\n },\n update: async <T = unknown>(\n name: string,\n id: string,\n set: Record<string, unknown>,\n ): Promise<T> => {\n const s = getState();\n if (s.offlineEnabled) {\n // Who is writing, captured before the live attempt: a request that\n // outlives a sign-out stays attributed to this user (or is refused).\n const outboxTicket = s.offlineEnabled ? getOfflineQueue(s).ticket() : undefined;\n try {\n const env = await s.http.patch<Envelope<T>>(\n `${P.collections}/${encodePathSegment(name)}/${encodePathSegment(id)}`,\n { set },\n );\n return env.data;\n } catch (err) {\n if (isOfflineError(err)) {\n await getOfflineQueue(s).enqueue({\n kind: \"collection.update\",\n collection: name,\n id,\n payload: set,\n }, outboxTicket);\n return { id, ...set, _amba_pending: true } as unknown as T;\n }\n throw err;\n }\n }\n const env = await s.http.patch<Envelope<T>>(\n `${P.collections}/${encodePathSegment(name)}/${encodePathSegment(id)}`,\n { set },\n );\n return env.data;\n },\n delete: async (\n name: string,\n id: string,\n ): Promise<{ data: { deleted: boolean } }> => {\n const s = getState();\n if (s.offlineEnabled) {\n // Who is writing, captured before the live attempt: a request that\n // outlives a sign-out stays attributed to this user (or is refused).\n const outboxTicket = s.offlineEnabled ? getOfflineQueue(s).ticket() : undefined;\n try {\n const result = await s.http.delete<{\n data: { deleted: boolean };\n } | null>(\n `${P.collections}/${encodePathSegment(name)}/${encodePathSegment(id)}`,\n );\n return result ?? { data: { deleted: true } };\n } catch (err) {\n if (isOfflineError(err)) {\n await getOfflineQueue(s).enqueue({\n kind: \"collection.delete\",\n collection: name,\n id,\n }, outboxTicket);\n return { data: { deleted: true } };\n }\n throw err;\n }\n }\n const result = await s.http.delete<{ data: { deleted: boolean } } | null>(\n `${P.collections}/${encodePathSegment(name)}/${encodePathSegment(id)}`,\n );\n // 204 No Content path — synthesise a `{ data: { deleted: true } }`\n // envelope so consumers can branch uniformly.\n return result ?? { data: { deleted: true } };\n },\n /**\n * Optimistic insert — update a local view immediately, fire the real\n * insert, and roll the view back if it fails. Thin convenience over the\n * exported {@link withOptimistic}; for multi-step flows use that directly.\n *\n * Optimistic writes always hit the network directly — they are NOT diverted\n * to the offline outbox even when `Amba.offline` is enabled. Optimistic UI\n * needs an unambiguous outcome: this resolves only on a confirmed write and\n * rejects (running your `rollback`) on any failure including being offline.\n * To buffer-and-replay instead, use the plain `insert`/`update`/`delete`\n * with the outbox on.\n */\n optimisticInsert: async <T = unknown, TApply = unknown>(\n name: string,\n row: Record<string, unknown>,\n view: { apply: () => TApply; rollback: (applied: TApply) => void },\n ): Promise<T> =>\n withOptimistic({\n apply: view.apply,\n mutate: () => insertDirect<T>(getState(), name, row),\n rollback: view.rollback,\n }),\n /** Optimistic update — see {@link optimisticInsert}. */\n optimisticUpdate: async <T = unknown, TApply = unknown>(\n name: string,\n id: string,\n set: Record<string, unknown>,\n view: { apply: () => TApply; rollback: (applied: TApply) => void },\n ): Promise<T> =>\n withOptimistic({\n apply: view.apply,\n mutate: () => updateDirect<T>(getState(), name, id, set),\n rollback: view.rollback,\n }),\n /** Optimistic delete — see {@link optimisticInsert}. */\n optimisticDelete: async <TApply = unknown>(\n name: string,\n id: string,\n view: { apply: () => TApply; rollback: (applied: TApply) => void },\n ): Promise<{ data: { deleted: boolean } }> =>\n withOptimistic({\n apply: view.apply,\n mutate: () => deleteDirect(getState(), name, id),\n rollback: view.rollback,\n }),\n where: {\n eq: (column: string, value: FilterValue): Filter => ({\n column,\n op: \"eq\",\n value,\n }),\n ne: (column: string, value: FilterValue): Filter => ({\n column,\n op: \"ne\",\n value,\n }),\n gt: (column: string, value: FilterValue): Filter => ({\n column,\n op: \"gt\",\n value,\n }),\n gte: (column: string, value: FilterValue): Filter => ({\n column,\n op: \"gte\",\n value,\n }),\n lt: (column: string, value: FilterValue): Filter => ({\n column,\n op: \"lt\",\n value,\n }),\n lte: (column: string, value: FilterValue): Filter => ({\n column,\n op: \"lte\",\n value,\n }),\n in: (column: string, values: FilterValue[]): Filter => ({\n column,\n op: \"in\",\n value: values,\n }),\n notIn: (column: string, values: FilterValue[]): Filter => ({\n column,\n op: \"not_in\",\n value: values,\n }),\n like: (column: string, pattern: string): Filter => ({\n column,\n op: \"like\",\n value: pattern,\n }),\n ilike: (column: string, pattern: string): Filter => ({\n column,\n op: \"ilike\",\n value: pattern,\n }),\n isNull: (column: string): Filter => ({ column, op: \"is_null\" }),\n isNotNull: (column: string): Filter => ({ column, op: \"is_not_null\" }),\n and: (...filters: Filter[]): Filter => ({ and: filters }),\n or: (...filters: Filter[]): Filter => ({ or: filters }),\n not: (filter: Filter): Filter => ({ not: filter }),\n },\n },\n\n storage: {\n presign: async (params: {\n bucket: string;\n filename: string;\n mimeType: string;\n sizeBytes: number;\n retentionDays?: number;\n }): Promise<PresignData> => {\n const s = getState();\n const env = await s.http.post<Envelope<PresignData>>(P.storagePresign, {\n bucket: params.bucket,\n filename: params.filename,\n mime_type: params.mimeType,\n size_bytes: params.sizeBytes,\n retention_days: params.retentionDays,\n });\n return env.data;\n },\n commit: async (uploadId: string, assetId: string): Promise<MediaAsset> => {\n const s = getState();\n const env = await s.http.post<Envelope<MediaAsset>>(P.storageCommit, {\n upload_id: uploadId,\n asset_id: assetId,\n });\n return env.data;\n },\n // <generated:storage>\n /**\n * List media assets, optionally filtered by a key prefix. NEW in 4.0.\n *\n * Returns the full `{ data, next_cursor }` envelope so paginated\n * listings expose the cursor — aligns with the web SDK shape\n * (`MediaListResponse`) and the engine `ListResponse`.\n */\n list: async (\n prefix?: string,\n ): Promise<{ data: MediaAsset[]; next_cursor?: string | null }> => {\n const env = await getState().http.get<{\n data: MediaAsset[];\n next_cursor?: string | null;\n }>(P.storage, prefix !== undefined ? { prefix } : undefined);\n return { data: env.data ?? [], next_cursor: env.next_cursor ?? null };\n },\n /**\n * Soft-delete a media asset by its asset id (the UUID returned\n * from `upload()` / `list()` / `get()`). NEW in 4.0.\n *\n * Wires to `DELETE /v1/client/media/:assetId` per\n * `apps/api/src/routes/client/media.ts:374`. The server scopes\n * deletes by `uploaded_by = current user`. Returns 404 if the\n * asset doesn't exist or isn't owned by the caller.\n *\n * NOTE: takes the asset id (UUID), not the storage key — the\n * server has no `?key=` query channel. Use `Amba.storage.list()`\n * to get the id from a `MediaAsset` row.\n */\n delete: async (assetId: string): Promise<void> => {\n await getState().http.delete(\n `${P.storage}/${encodePathSegment(assetId)}`,\n );\n },\n // </generated:storage>\n /**\n * Download a media asset's bytes by its asset id. Two-step:\n * resolve the `MediaAsset` metadata (which carries the CDN URL),\n * then fetch the CDN URL directly with no API headers.\n *\n * Returns a `Blob` — RN's fetch surfaces blobs natively\n * (`response.blob()`). NEW in 4.0.\n *\n * Mirrors the SDK core's `StorageModule::download` — there is no\n * `/v1/client/media/download` route on the server; the asset URL\n * on the asset row is the canonical fetch target.\n */\n download: async (assetId: string): Promise<Blob> => {\n const s = getState();\n // Step 1: resolve the asset to get the CDN url.\n const assetEnv = await s.http.get<Envelope<MediaAsset>>(\n `${P.storage}/${encodePathSegment(assetId)}`,\n );\n const cdnUrl =\n (assetEnv.data as MediaAsset & { storage_url?: string }).url ??\n (assetEnv.data as MediaAsset & { storage_url?: string }).storage_url ??\n \"\";\n if (!cdnUrl) {\n throw new AmbaApiError(\"NOT_FOUND\", \"asset has no resolvable CDN url\");\n }\n // Step 2: fetch the CDN bytes. No API headers — the CDN doesn't\n // accept the project API key and signed URLs carry their own auth.\n // A media transfer gets the long deadline, widened by the asset's\n // size so a large file on a slow link has time to arrive.\n const sizeBytes = (assetEnv.data as { size_bytes?: unknown }).size_bytes;\n const { response, blob, text } = await s.http.send(\n cdnUrl,\n { method: \"GET\" },\n async (res) =>\n res.ok\n ? { response: res, blob: await res.blob(), text: \"\" }\n : { response: res, blob: null, text: await res.text() },\n {\n timeoutMs: s.http.longRequestTimeoutMs,\n responseBytes: typeof sizeBytes === \"number\" ? sizeBytes : undefined,\n },\n );\n if (!blob) {\n const ct = response.headers.get(\"content-type\") ?? \"\";\n throw mapHttpError(response, text, ct.includes(\"application/json\"));\n }\n return blob;\n },\n },\n\n push: {\n register: async (\n token: string,\n platform: PushPlatform,\n bundleId?: string,\n ): Promise<PushToken> => {\n const s = getState();\n const env = await s.http.post<Envelope<PushToken>>(P.pushRegister, {\n token,\n platform,\n bundle_id: bundleId,\n });\n return env.data;\n },\n unregister: async (token: string): Promise<void> => {\n await getState().http.post(P.pushUnregister, { token });\n },\n getTokens: async (): Promise<PushToken[]> => {\n const env = await getState().http.get<Envelope<PushToken[]>>(\n P.pushTokens,\n );\n return env.data;\n },\n subscribe: async (topic: string): Promise<void> => {\n await getState().http.post(P.pushSubscribe, { topic });\n },\n unsubscribe: async (topic: string): Promise<void> => {\n await getState().http.post(P.pushUnsubscribe, { topic });\n },\n /**\n * Auto re-register the device push token when the OS rotates it.\n *\n * The OS can roll a device's push token (reinstall, restore, key roll);\n * the new token must be re-registered or notifications stop arriving. Pass\n * your notification library's token-refresh subscription primitive — e.g.\n * `(h) => Notifications.addPushTokenListener((t) => h(t.data)).remove` on\n * Expo, or the bare-FCM `onTokenRefresh` callback — and the SDK forwards\n * each rotated token through the idempotent {@link register} path.\n *\n * Pass the token `platform` for your build — `\"apns\"` on iOS, `\"fcm\"` on\n * Android (defaults to `\"fcm\"` when omitted). The bare RN SDK never reads\n * `Platform.OS` itself (it's a pure-JS client that also loads under Node),\n * so the caller supplies it; the Expo wrapper's\n * `Amba.enablePushAutoRotation()` resolves it for you. Returns an\n * unsubscribe function.\n *\n * @example\n * import { Platform } from \"react-native\";\n * import * as Notifications from \"expo-notifications\";\n * const detach = Amba.push.enableAutoRotation(\n * (h) => Notifications.addPushTokenListener((t) => h(t.data)).remove,\n * Platform.OS === \"ios\" ? \"apns\" : \"fcm\",\n * );\n */\n enableAutoRotation: (\n subscribe: TokenRefreshSubscribe,\n platform: \"apns\" | \"fcm\" = \"fcm\",\n ): (() => void) =>\n enableNativePushAutoRotation(\n subscribe,\n (token, p) => Amba.push.register(token, p),\n platform,\n ),\n },\n\n /**\n * On-device local notifications — `scheduleLocal` / `cancelLocal` /\n * `listScheduledLocal`. These schedule notifications the device fires\n * itself (reminders, streak nudges) with no server round-trip, distinct\n * from remote push (`Amba.push.*`). Backed by `expo-notifications`\n * (an optional peer dep that works in both Expo and bare RN apps),\n * loaded lazily; methods throw a clear `LOCAL_NOTIFICATIONS_UNAVAILABLE`\n * error if it isn't installed. See `./local-notifications.ts`.\n */\n notifications,\n\n entitlements: {\n // <generated:entitlements.list>\n list: async (): Promise<UserEntitlement[]> => {\n const env = await getState().http.get<Envelope<UserEntitlement[]>>(\n P.entitlements,\n );\n return env.data;\n },\n // </generated:entitlements.list>\n // <generated:entitlements.has>\n has: async (entitlementId: string): Promise<boolean> => {\n const env = await getState().http.get<Envelope<UserEntitlement[]>>(\n P.entitlements,\n { active_only: \"true\" },\n );\n return env.data.some(\n (e) => e.entitlement_id === entitlementId && e.is_active,\n );\n },\n // </generated:entitlements.has>\n // <generated:entitlements.restore>\n /**\n * Restore purchases — re-sync the user's owned entitlements from the\n * server and return the active set. Required for App Store review (3.1.1);\n * wire it to a \"Restore Purchases\" button. Throws (access unchanged) if the\n * server can't reach the validation engine — show a retry message.\n */\n restore: async (): Promise<RestoreResult> => {\n const env = await getState().http.post<Envelope<RestoreResult>>(\n P.entitlementsRestore,\n {},\n );\n return env.data;\n },\n // </generated:entitlements.restore>\n },\n\n // <generated:offerings>\n /**\n * Subscription offerings — the packages your paywall renders, provider-\n * neutral. Pass an `offeringId` for one offering; omit it for all.\n */\n offerings: async (offeringId?: string): Promise<OfferingsResult> => {\n const env = await getState().http.get<Envelope<OfferingsResult>>(\n P.offerings,\n offeringId ? { offering_id: offeringId } : undefined,\n );\n return env.data;\n },\n // </generated:offerings>\n\n ai: {\n anthropic: {\n messages: {\n create: async (\n request: AiMessageRequest,\n ): Promise<AiMessageResponse> => {\n const env = await getState().http.post<Envelope<AiMessageResponse>>(\n P.aiAnthropic,\n request,\n );\n // Defense in depth: the server wraps the gateway's vendor body in\n // `{data:...}`. If the envelope is ever absent (the historical bug\n // that made this resolve to `undefined`), fail loud with a typed\n // error instead of silently returning nothing.\n if (!env || typeof env !== \"object\" || env.data === undefined) {\n throw new AmbaApiError(\n \"MALFORMED_RESPONSE\",\n \"AI response was not wrapped in the expected { data } envelope\",\n env,\n );\n }\n return env.data;\n },\n },\n },\n },\n\n /**\n * Invoke a deployed function by name, with the project's client key (and\n * the live session token, if any) injected automatically. Prefer this over\n * hand-rolling `fetch` against a function URL, which leaves the call\n * unauthenticated.\n *\n * The function host is selected in order from `functionsBaseUrl`, a\n * UUID-shaped configured project id, or a deduped diagnostics ping that\n * supplies the authoritative server-side project id.\n *\n * @example\n * const result = await Amba.functions.invoke<{ ok: boolean }>(\n * 'send_welcome_email',\n * { to: 'user@example.com' },\n * );\n *\n * @returns The parsed JSON response (or the raw text if not JSON).\n * @throws AmbaApiError on a network failure or a non-2xx response.\n */\n functions: {\n invoke: async <T = unknown>(\n name: string,\n body?: unknown,\n options?: InvokeOptions,\n ): Promise<T> => {\n const s = getState();\n if (!name || name.trim() === \"\") {\n throw new AmbaApiError(\n \"INVALID_ARGUMENT\",\n \"Amba.functions.invoke: a function name is required.\",\n );\n }\n let timeoutMs: number;\n try {\n timeoutMs = readTimeout(\n \"Amba.functions.invoke: timeoutMs\",\n options?.timeoutMs,\n s.http.longRequestTimeoutMs,\n );\n } catch (err) {\n throw new AmbaApiError(\"INVALID_ARGUMENT\", (err as Error).message);\n }\n const url = await resolveFunctionUrl(s, name);\n const method = options?.method ?? \"POST\";\n // `buildHeaders` injects X-Api-Key + the Bearer session token (when\n // signed in) + the SDK-version headers (and a default\n // `Content-Type: application/json`). Extra headers merge on top.\n const headers = s.http.buildHeaders(options?.headers);\n let requestBody: string | undefined;\n if (body !== undefined && method !== \"GET\") {\n requestBody = JSON.stringify(body); // Content-Type already set by buildHeaders\n } else {\n // No body → drop the JSON Content-Type buildHeaders adds, so\n // body-less invokes don't advertise an (empty) JSON body that some\n // function runtimes reject. Mirrors the web SDK.\n delete headers[\"Content-Type\"];\n }\n\n // A function can run long, so the invocation gets the long deadline\n // (or the call's own `timeoutMs`), which covers reading the response.\n const { response, text } = await s.http.send(\n url,\n { method, headers, body: requestBody },\n async (res) => ({ response: res, text: await res.text() }),\n {\n timeoutMs,\n label: `Amba.functions.invoke(\"${name}\") network error`,\n },\n );\n if (!response.ok) {\n const ct = response.headers.get(\"content-type\") ?? \"\";\n throw mapHttpError(response, text, ct.includes(\"application/json\"));\n }\n if (!text) return undefined as T;\n try {\n return JSON.parse(text) as T;\n } catch {\n // Function returned non-JSON — pass it through verbatim.\n return text as unknown as T;\n }\n },\n },\n\n config: {\n // <generated:config>\n fetch: async (): Promise<ConfigBundle> => {\n const s = getState();\n // The user the request is sent for (and the sign-out generation):\n // the response is cached for them only while they are current.\n const ticket = s.remoteValues.ticket();\n // Config returns `{ data: Record<string, unknown> }` plus an\n // `ETag` header that carries the version stamp.\n const url = s.http.url(P.config);\n const headers = s.http.buildHeaders();\n // Sent under the request deadline, which covers reading the body.\n const { response, text } = await s.http.send(\n url,\n { method: \"GET\", headers },\n async (res) => ({ response: res, text: await res.text() }),\n );\n if (!response.ok) {\n const ct = response.headers.get(\"content-type\") ?? \"\";\n throw mapHttpError(response, text, ct.includes(\"application/json\"));\n }\n let parsed: { data?: Record<string, unknown> } = {};\n if (text) {\n try {\n parsed = JSON.parse(text);\n } catch {\n parsed = {};\n }\n }\n const etag = response.headers.get(\"etag\");\n // ETag format: `\"vN-...\"` — strip quotes then take the leading\n // `vN` prefix as the version stamp.\n let version: string | null = null;\n if (etag) {\n const cleaned = etag.replace(/^\"|\"$/g, \"\");\n const m = cleaned.match(/^(v\\d+)/);\n version = m ? (m[1] ?? null) : cleaned || null;\n }\n // The ETag names the user the server evaluated the bundle for.\n return s.remoteValues.storeConfig(\n ticket,\n { version, values: parsed.data ?? {} },\n etag,\n );\n },\n // </generated:config>\n /**\n * The value for `key` without a request, so a first render can read\n * config: the last fetch, else the value cached in AsyncStorage on an\n * earlier launch, else `configDefaults`, else `defaultValue`. A value of\n * another JSON kind than `defaultValue` (a string where the default is a\n * number) returns `defaultValue`. Before `configure()` finishes it reads\n * `configDefaults` only. The first call for each user (and for the\n * signed-out state) starts a background `config.fetch()` that replaces\n * the cached values.\n *\n * @example\n * const copy = Amba.config.getCached(\"welcome_copy\", \"Welcome!\");\n */\n getCached: configGetCached,\n },\n\n flags: {\n // <generated:flags>\n fetch: async (): Promise<FlagAssignment[]> => {\n const s = getState();\n const ticket = s.remoteValues.ticket();\n const env = await s.http.get<Envelope<FlagAssignment[]>>(P.flags);\n return s.remoteValues.storeFlags(ticket, env.data);\n },\n /**\n * Get a single flag assignment by name. NEW in 4.0. Wraps\n * `GET /v1/client/flags/{key}` — returns `null` when the flag is\n * unknown or disabled for this user (the server treats both as\n * 404 by design; the SDK normalizes to `null`). Other errors\n * (auth, rate-limit, network) still throw. The result updates the\n * device cache: a found flag is stored, a `null` one removed.\n */\n get: async (key: string): Promise<FlagAssignment | null> => {\n const s = getState();\n const ticket = s.remoteValues.ticket();\n try {\n const env = await s.http.get<Envelope<FlagAssignment>>(\n `${P.flags}/${encodePathSegment(key)}`,\n );\n return s.remoteValues.upsertFlag(ticket, env.data);\n } catch (err) {\n if (err instanceof AmbaApiError && err.code === \"NOT_FOUND\") {\n s.remoteValues.removeFlag(ticket, key);\n return null;\n }\n throw err;\n }\n },\n // </generated:flags>\n /**\n * The assignment the SDK holds for flag `key` without a request (the\n * last fetch, else the AsyncStorage cache), or `null`.\n */\n getCached: flagsGetCached,\n /**\n * Whether flag `key` is on without a request: the held assignment, else\n * a boolean `configDefaults` entry under the name, else `defaultValue`\n * (`false`). A flag switched off on the server is left out of the next\n * fetch, so it reads as `defaultValue` from then on. The first cached\n * flag read for each user (and for the signed-out state) starts a\n * background `flags.fetch()` that replaces the cached values.\n */\n isEnabled: flagsIsEnabled,\n },\n\n /**\n * Experiments: server-assigned, sticky A/B variants. `getVariant` assigns\n * the signed-in user (the first call creates the assignment; later calls\n * return the same variant). Call `trackExposure` when the variant is\n * actually shown.\n */\n experiments: {\n // <generated:experiments>\n /**\n * Assign the current user to experiment `key` and return the variant\n * (sticky: later calls return the same variant). Wraps\n * `GET /v1/client/experiments/{key}/assignment`. The first result for\n * a user in a session also reaches `Amba.onExperimentAssigned`.\n */\n getVariant: async (key: string): Promise<ExperimentAssignment> => {\n const s = getState();\n // The server hashes the user of the session the request carries.\n // Without a session yet, the lazy anonymous sign-in creates it.\n const userBefore = s.identity.getAppUserId();\n const env = await s.http.get<\n Envelope<{ experiment_key: string; variant: string }>\n >(`${P.experiments}/${encodePathSegment(key)}/assignment`);\n return experimentReporter.report({\n experimentKey: env.data.experiment_key,\n variant: env.data.variant,\n bucketingId: userBefore ?? s.identity.getAppUserId() ?? \"\",\n });\n },\n /**\n * Record that the current user saw their variant of `key`. Wraps\n * `POST /v1/client/experiments/{key}/exposure`. The user needs an\n * assignment first (`getVariant`).\n */\n trackExposure: async (key: string): Promise<void> => {\n await getState().http.post(\n `${P.experiments}/${encodePathSegment(key)}/exposure`,\n {},\n );\n },\n // </generated:experiments>\n },\n\n /**\n * Subscribe to experiment assignments, for forwarding to your analytics.\n * The listener runs once per experiment per user session (the first\n * `experiments.getVariant(key)` result for that user while it is\n * subscribed) with `{ experimentKey, variant, bucketingId }`. A sign-out\n * starts a new session. Errors the listener throws are caught and logged;\n * they never reach the `getVariant` caller. Returns an unsubscribe\n * function.\n */\n onExperimentAssigned: (listener: ExperimentAssignedListener): (() => void) =>\n experimentReporter.subscribe(listener),\n\n /**\n * Wire-verify primitive. Call once after `configure()` to confirm\n * the SDK is talking to the right project with the right key in\n * the right environment. See `@layers/amba-web::Amba.diagnostics.ping`\n * for the canonical usage example — the response shape is identical\n * across every Amba SDK.\n *\n * The ping response is NOT wrapped in the usual `{data: ...}`\n * envelope — the route returns the `PingResult` shape directly so\n * an operator running `curl … | jq` sees the structured fields\n * without an extra unwrap.\n */\n diagnostics: {\n ping: async (): Promise<PingResult> => {\n return await getState().http.post<PingResult>(P.diagnosticsPing, {});\n },\n },\n\n // ── Gamification ──────────────────────────────────────────────────\n\n achievements: {\n // <generated:achievements>\n getAll: async (): Promise<Achievement[]> => {\n const env = await getState().http.get<Envelope<Achievement[]>>(\n P.achievements,\n );\n return env.data;\n },\n getProgress: async (): Promise<AchievementProgress[]> => {\n const env = await getState().http.get<Envelope<AchievementProgress[]>>(\n `${P.achievements}/progress`,\n );\n return env.data;\n },\n // </generated:achievements>\n },\n\n challenges: {\n // <generated:challenges>\n getActive: async (): Promise<Challenge[]> => {\n const env = await getState().http.get<Envelope<Challenge[]>>(\n `${P.challenges}/active`,\n );\n return env.data;\n },\n get: async (id: string): Promise<Challenge> => {\n const env = await getState().http.get<Envelope<Challenge>>(\n `${P.challenges}/${encodePathSegment(id)}`,\n );\n return env.data;\n },\n getProgress: async (id: string): Promise<ChallengeProgress> => {\n const env = await getState().http.get<Envelope<ChallengeProgress>>(\n `${P.challenges}/${encodePathSegment(id)}/progress`,\n );\n return env.data;\n },\n claim: async (id: string): Promise<ChallengeProgress> => {\n const env = await getState().http.post<Envelope<ChallengeProgress>>(\n `${P.challenges}/${encodePathSegment(id)}/claim`,\n {},\n );\n return env.data;\n },\n // </generated:challenges>\n },\n\n currencies: {\n // <generated:currencies>\n getBalance: async (): Promise<CurrencyBalance[]> => {\n const env = await getState().http.get<Envelope<CurrencyBalance[]>>(\n P.currencies,\n );\n return env.data;\n },\n getTransactions: async (\n currencyKey: string,\n ): Promise<CurrencyTransaction[]> => {\n // Server route is `GET /currencies/transactions?currency_code=<key>`\n // — the key is a query param, not a path segment. DX-33 fix.\n const env = await getState().http.get<Envelope<CurrencyTransaction[]>>(\n `${P.currencies}/transactions`,\n { currency_code: currencyKey },\n );\n return env.data;\n },\n // </generated:currencies>\n },\n\n inventory: {\n // <generated:inventory>\n getItems: async (): Promise<InventoryItem[]> => {\n const env = await getState().http.get<Envelope<InventoryItem[]>>(\n P.inventory,\n );\n return env.data;\n },\n getItem: async (id: string): Promise<InventoryItem> => {\n const env = await getState().http.get<Envelope<InventoryItem>>(\n `${P.inventory}/${encodePathSegment(id)}`,\n );\n return env.data;\n },\n purchase: async (request: PurchaseRequest): Promise<InventoryPurchase> => {\n const env = await getState().http.post<Envelope<InventoryPurchase>>(\n `${P.inventory}/purchase`,\n request,\n );\n return env.data;\n },\n consume: async (request: ConsumeRequest): Promise<InventoryItem> => {\n // Server route is `POST /v1/client/inventory/:itemId/consume`. The\n // item id moves into the path; only `quantity` stays in the body.\n // DX-36 fix.\n const { item_id, ...body } = request;\n const env = await getState().http.post<Envelope<InventoryItem>>(\n `${P.inventory}/${encodePathSegment(item_id)}/consume`,\n body,\n );\n return env.data;\n },\n // </generated:inventory>\n },\n\n leaderboards: {\n // <generated:leaderboards>\n get: async (key: string): Promise<Leaderboard> => {\n const env = await getState().http.get<Envelope<Leaderboard>>(\n `${P.leaderboards}/${encodePathSegment(key)}`,\n );\n return env.data;\n },\n getEntries: async (\n key: string,\n limit?: number,\n ): Promise<LeaderboardEntry[]> => {\n const env = await getState().http.get<Envelope<LeaderboardEntry[]>>(\n `${P.leaderboards}/${encodePathSegment(key)}/entries`,\n limit !== undefined ? { limit } : undefined,\n );\n return env.data;\n },\n getMyRank: async (key: string): Promise<LeaderboardEntry> => {\n const env = await getState().http.get<Envelope<LeaderboardEntry>>(\n `${P.leaderboards}/${encodePathSegment(key)}/me`,\n );\n return env.data;\n },\n // </generated:leaderboards>\n },\n\n /**\n * Tiered weekly cohorts (Duolingo-style leagues). NEW in 4.0.\n *\n * `me()` returns the caller's current cohort/tier/score snapshot.\n * `cohort()` returns the full peer list competing in the same tier\n * this week — for rendering a league-board UI.\n */\n leagues: {\n // <generated:leagues>\n me: async (): Promise<LeagueMembership> => {\n const env = await getState().http.get<Envelope<LeagueMembership>>(\n `${P.leagues}/me`,\n );\n return env.data;\n },\n cohort: async (): Promise<LeagueCohort> => {\n // Server route is `/me/cohort` per\n // `apps/api/src/routes/client/leagues.ts:176`. The bare\n // `/cohort` path 404s.\n const env = await getState().http.get<Envelope<LeagueCohort>>(\n `${P.leagues}/me/cohort`,\n );\n return env.data;\n },\n // </generated:leagues>\n },\n\n stores: {\n // <generated:stores>\n list: async (): Promise<Store[]> => {\n const env = await getState().http.get<Envelope<Store[]>>(P.stores);\n return env.data;\n },\n getPurchaseOptions: async (\n storeKey: string,\n ): Promise<PurchaseOption[]> => {\n const env = await getState().http.get<Envelope<PurchaseOption[]>>(\n `${P.stores}/${encodePathSegment(storeKey)}/options`,\n );\n return env.data;\n },\n purchase: async (\n storeKey: string,\n purchaseOptionId: string,\n receipt: unknown,\n ): Promise<PurchaseResult> => {\n // `undefined` doesn't survive JSON.stringify (it's omitted from\n // the object, leaving the server's body as if `receipt` was\n // never sent). For symmetry with the web SDK fix, default the\n // missing-receipt case to an empty object so the server sees a\n // valid `{ purchase_option_id, receipt: {} }` body (BugBot Low\n // 2026-05-19).\n const env = await getState().http.post<Envelope<PurchaseResult>>(\n `${P.stores}/${encodePathSegment(storeKey)}/purchase`,\n { purchase_option_id: purchaseOptionId, receipt: receipt ?? {} },\n );\n return env.data;\n },\n // </generated:stores>\n },\n\n xp: {\n // <generated:xp>\n getBalance: async (): Promise<XpBalance> => {\n const env = await getState().http.get<Envelope<XpBalance>>(P.xp);\n return env.data;\n },\n getHistory: async (limit?: number): Promise<XpTransaction[]> => {\n const env = await getState().http.get<Envelope<XpTransaction[]>>(\n `${P.xp}/history`,\n limit !== undefined ? { limit } : undefined,\n );\n return env.data;\n },\n claim: async (grantKey: string): Promise<XpTransaction> => {\n const env = await getState().http.post<Envelope<XpTransaction>>(\n `${P.xp}/claim`,\n { grant_key: grantKey },\n );\n return env.data;\n },\n // </generated:xp>\n },\n\n streaks: {\n // <generated:streaks>\n getAll: async (): Promise<Streak[]> => {\n const env = await getState().http.get<Envelope<Streak[]>>(P.streaks);\n return env.data;\n },\n qualify: async (streakKey: string): Promise<Streak> => {\n const env = await getState().http.post<Envelope<Streak>>(\n `${P.streaks}/${encodePathSegment(streakKey)}/qualify`,\n {},\n );\n return env.data;\n },\n // </generated:streaks>\n },\n\n // ── Social ─────────────────────────────────────────────────────────\n\n feeds: {\n // <generated:feeds>\n getActivity: async (\n feed?: string,\n cursor?: string,\n ): Promise<FeedResponse> => {\n const f = feed ?? \"default\";\n return getState().http.get<FeedResponse>(\n `${P.feeds}/${encodePathSegment(f)}`,\n cursor ? { cursor } : undefined,\n );\n },\n // </generated:feeds>\n },\n\n friends: {\n // <generated:friends>\n getList: async (): Promise<Friendship[]> => {\n const env = await getState().http.get<Envelope<Friendship[]>>(\n P.friends,\n );\n return env.data;\n },\n getFriends: async (): Promise<Friendship[]> => {\n const env = await getState().http.get<Envelope<Friendship[]>>(\n P.friends,\n { state: \"accepted\" },\n );\n return env.data;\n },\n /**\n * Send a friend request to `userId`. NEW in 4.0 — the existing\n * SDK shipped block/unblock but had no request/accept/decline\n * surface, so customers couldn't actually build a friends graph\n * without dropping to raw HTTP.\n */\n sendRequest: async (userId: string): Promise<Friendship> => {\n // Server route is `POST /v1/client/friends/request` (singular),\n // which reads the other user's id as `addressee_id`.\n const env = await getState().http.post<Envelope<Friendship>>(\n `${P.friends}/request`,\n { addressee_id: userId },\n );\n return env.data;\n },\n /**\n * Accept an inbound friend request. NEW in 4.0.\n */\n acceptRequest: async (friendshipId: string): Promise<Friendship> => {\n const env = await getState().http.post<Envelope<Friendship>>(\n `${P.friends}/${encodePathSegment(friendshipId)}/accept`,\n {},\n );\n return env.data;\n },\n /**\n * Decline an inbound friend request. NEW in 4.0. Server endpoint\n * is `/:friendshipId/reject` (not `/decline`) — SDK keeps the\n * `declineRequest` name (intent is clear) but hits the server's\n * spelling. apps/api/src/routes/client/friends.ts:152.\n */\n declineRequest: async (friendshipId: string): Promise<void> => {\n await getState().http.post(\n `${P.friends}/${encodePathSegment(friendshipId)}/reject`,\n {},\n );\n },\n blockUser: async (userId: string): Promise<Friendship> => {\n const env = await getState().http.post<Envelope<Friendship>>(\n `${P.friends}/block`,\n { user_id: userId },\n );\n return env.data;\n },\n unblockUser: async (userId: string): Promise<void> => {\n await getState().http.post(`${P.friends}/unblock`, { user_id: userId });\n },\n removeBlock: async (friendshipId: string): Promise<void> => {\n await getState().http.delete(\n `${P.friends}/${encodePathSegment(friendshipId)}`,\n );\n },\n /**\n * `friends.removeFriend(userId)` — unfriend by the other user's id\n * without needing the friendship row id. NEW in 4.0. Wraps\n * `DELETE /v1/client/friends/by-user/{userId}`. Server preserves\n * blocked rows (use `unblockUser` to clear those); 404 → throws\n * AmbaApiError with code NOT_FOUND.\n */\n removeFriend: async (userId: string): Promise<void> => {\n await getState().http.delete(\n `${P.friends}/by-user/${encodePathSegment(userId)}`,\n );\n },\n // </generated:friends>\n },\n\n groups: {\n // <generated:groups>\n create: async (params: GroupCreate): Promise<Group> => {\n const env = await getState().http.post<Envelope<Group>>(P.groups, params);\n return env.data;\n },\n get: async (id: string): Promise<Group> => {\n const env = await getState().http.get<Envelope<Group>>(\n `${P.groups}/${encodePathSegment(id)}`,\n );\n return env.data;\n },\n update: async (id: string, patch: GroupUpdate): Promise<Group> => {\n const env = await getState().http.patch<Envelope<Group>>(\n `${P.groups}/${encodePathSegment(id)}`,\n patch,\n );\n return env.data;\n },\n delete: async (id: string): Promise<void> => {\n await getState().http.delete(`${P.groups}/${encodePathSegment(id)}`);\n },\n getMembers: async (id: string): Promise<GroupMember[]> => {\n const env = await getState().http.get<Envelope<GroupMember[]>>(\n `${P.groups}/${encodePathSegment(id)}/members`,\n );\n return env.data;\n },\n join: async (id: string): Promise<GroupMember> => {\n const env = await getState().http.post<Envelope<GroupMember>>(\n `${P.groups}/${encodePathSegment(id)}/join`,\n {},\n );\n return env.data;\n },\n leave: async (id: string): Promise<void> => {\n await getState().http.post(\n `${P.groups}/${encodePathSegment(id)}/leave`,\n {},\n );\n },\n invite: async (id: string, userId: string): Promise<GroupMember> => {\n const env = await getState().http.post<Envelope<GroupMember>>(\n `${P.groups}/${encodePathSegment(id)}/invite`,\n { user_id: userId },\n );\n return env.data;\n },\n // </generated:groups>\n },\n\n /**\n * User-to-user direct messaging. Task #62 corrected the path\n * constants — `sendMessage` previously POSTed to\n * `/v1/client/messaging/messages`, which the API doesn't serve.\n * The real route is `/v1/client/messaging/conversations/:id/messages`.\n */\n messaging: {\n /**\n * Live conversation subscription. Returns a builder — call\n * `.subscribe(cb)` to receive new messages live instead of polling:\n *\n * const unsub = Amba.messaging\n * .conversation(conversationId)\n * .subscribe((change) => {\n * // change.op === \"INSERT\"; change.row is the new message\n * });\n * unsub(); // stop listening\n *\n * Delivery is scoped to conversation participants — you receive messages\n * only if your session is a member of the conversation. Reconnects\n * automatically with backoff. Requires an authenticated session.\n */\n conversation: <T = Message>(\n conversationId: string,\n ): ConversationSubscription<T> =>\n new ConversationSubscription<T>(\n {\n baseUrl: getState().baseUrl,\n authHeaders: realtimeAuthHeaders,\n connectTimeoutMs: getState().http.requestTimeoutMs,\n },\n conversationId,\n ),\n // <generated:messaging>\n /**\n * `POST /v1/client/messaging/conversations` — create (or fetch\n * the existing direct) conversation. Idempotent for direct\n * conversations between two users.\n */\n createConversation: async (\n request: CreateConversationRequest,\n ): Promise<Conversation> => {\n const env = await getState().http.post<Envelope<Conversation>>(\n `${P.messaging}/conversations`,\n request,\n );\n return env.data;\n },\n /**\n * `GET /v1/client/messaging/conversations` — list the caller's\n * conversations. Canonical 4.0 name; the legacy `getConversations`\n * is removed in 4.0 per the spec.\n */\n conversations: async (): Promise<Conversation[]> => {\n const env = await getState().http.get<Envelope<Conversation[]>>(\n `${P.messaging}/conversations`,\n );\n return env.data;\n },\n sendMessage: async (\n conversationId: string,\n request: SendMessageRequest,\n ): Promise<Message> => {\n const env = await getState().http.post<Envelope<Message>>(\n `${P.messaging}/conversations/${encodePathSegment(conversationId)}/messages`,\n request,\n );\n return env.data;\n },\n listMessages: async (\n conversationId: string,\n opts?: ListMessagesOptions,\n ): Promise<Message[]> => {\n const query: Record<string, number | undefined> = {};\n if (opts?.limit !== undefined) query.limit = opts.limit;\n if (opts?.offset !== undefined) query.offset = opts.offset;\n const env = await getState().http.get<Envelope<Message[]>>(\n `${P.messaging}/conversations/${encodePathSegment(conversationId)}/messages`,\n Object.keys(query).length > 0 ? query : undefined,\n );\n return env.data;\n },\n markRead: async (conversationId: string): Promise<MarkReadResult> => {\n const env = await getState().http.post<Envelope<MarkReadResult>>(\n `${P.messaging}/conversations/${encodePathSegment(conversationId)}/read`,\n {},\n );\n return env.data;\n },\n /**\n * Fetch a single message by id within a conversation. NEW in 4.0.\n *\n * The server has no dedicated `GET /messages/:id` route — the\n * `/messages` list endpoint also doesn't honour a `?message_id=`\n * query filter (see `apps/api/src/routes/client/messaging.ts:358`,\n * which returns the full paginated page). We paginate forward\n * (newest-first, server page size 100) and find the matching id\n * client-side, mirroring the SDK core's strategy. Walks up to `MAX_PAGES`\n * (5,000 messages total) before giving up to avoid an unbounded\n * loop against a giant conversation; callers with longer histories\n * should keep a local index instead of relying on this helper as a\n * search primitive.\n *\n * Returns `null` when the message id isn't found within the cap\n * (or the conversation), matching the canonical contract.\n */\n getMessage: async (\n conversationId: string,\n messageId: string,\n ): Promise<Message | null> => {\n const PAGE_SIZE = 100;\n const MAX_PAGES = 50;\n const s = getState();\n const path = `${P.messaging}/conversations/${encodePathSegment(conversationId)}/messages`;\n let offset = 0;\n for (let i = 0; i < MAX_PAGES; i++) {\n const env = await s.http.get<Envelope<Message[]>>(path, {\n limit: PAGE_SIZE,\n offset,\n });\n const page = env.data;\n const hit = page.find((m) => m.id === messageId);\n if (hit) return hit;\n if (page.length < PAGE_SIZE) return null;\n offset += PAGE_SIZE;\n }\n return null;\n },\n // </generated:messaging>\n },\n\n moderation: {\n reportUser: async (request: ReportRequest): Promise<Report> => {\n const env = await getState().http.post<Envelope<Report>>(\n `${P.moderation}/reports/users`,\n request,\n );\n return env.data;\n },\n reportContent: async (request: ReportRequest): Promise<Report> => {\n const env = await getState().http.post<Envelope<Report>>(\n `${P.moderation}/reports/content`,\n request,\n );\n return env.data;\n },\n getReportStatus: async (id: string): Promise<Report> => {\n const env = await getState().http.get<Envelope<Report>>(\n `${P.moderation}/reports/${encodePathSegment(id)}`,\n );\n return env.data;\n },\n },\n\n reviews: {\n // <generated:reviews>\n list: async (\n targetType: string,\n targetId: string,\n ): Promise<Review[]> => {\n const env = await getState().http.get<Envelope<Review[]>>(P.reviews, {\n target_type: targetType,\n target_id: targetId,\n });\n return env.data;\n },\n create: async (params: ReviewCreate): Promise<Review> => {\n const env = await getState().http.post<Envelope<Review>>(P.reviews, params);\n return env.data;\n },\n update: async (id: string, patch: ReviewUpdate): Promise<Review> => {\n const env = await getState().http.patch<Envelope<Review>>(\n `${P.reviews}/${encodePathSegment(id)}`,\n patch,\n );\n return env.data;\n },\n delete: async (id: string): Promise<void> => {\n await getState().http.delete(`${P.reviews}/${encodePathSegment(id)}`);\n },\n // </generated:reviews>\n },\n\n roles: {\n // <generated:roles>\n getMyRoles: async (): Promise<Role[]> => {\n const env = await getState().http.get<Envelope<Role[]>>(\n `${P.roles}/me`,\n );\n return env.data;\n },\n hasPermission: async (permission: string): Promise<boolean> => {\n const env = await getState().http.get<Envelope<{ allowed: boolean }>>(\n `${P.roles}/check`,\n { permission },\n );\n return env.data.allowed === true;\n },\n // </generated:roles>\n },\n\n referrals: {\n // <generated:referrals>\n getReferralCode: async (): Promise<ReferralCode> => {\n // Server route is `/v1/client/referrals/my-code` (DX-32 fix).\n const env = await getState().http.get<Envelope<ReferralCode>>(\n `${P.referrals}/my-code`,\n );\n return env.data;\n },\n claimReferral: async (code: string): Promise<ReferralClaim> => {\n const env = await getState().http.post<Envelope<ReferralClaim>>(\n `${P.referrals}/claim`,\n { code },\n );\n return env.data;\n },\n create: async (\n code?: string,\n maxUses?: number,\n ): Promise<ReferralCode> => {\n const body: Record<string, unknown> = {};\n if (code !== undefined) body.code = code;\n if (maxUses !== undefined) body.max_uses = maxUses;\n const env = await getState().http.post<Envelope<ReferralCode>>(\n P.referrals,\n body,\n );\n return env.data;\n },\n // </generated:referrals>\n },\n\n // ── Lifecycle ──────────────────────────────────────────────────────\n\n catalog: {\n // <generated:catalog>\n list: async (): Promise<CatalogItem[]> => {\n const env = await getState().http.get<Envelope<CatalogItem[]>>(\n P.catalog,\n );\n return env.data;\n },\n /**\n * Get a single catalog item by id. NEW in 4.0 — wires to\n * `GET /v1/client/catalog/:id`.\n */\n get: async (id: string): Promise<CatalogItem> => {\n const env = await getState().http.get<Envelope<CatalogItem>>(\n `${P.catalog}/${encodePathSegment(id)}`,\n );\n return env.data;\n },\n // </generated:catalog>\n },\n\n content: {\n // <generated:content>\n /**\n * Canonical 4.0 names — `today` / `library` / `item`. The legacy\n * `getToday` / `getLibrary` / `getItem` are removed in 4.0 per\n * the spec (CANONICAL_API.md, content section).\n */\n today: async (\n channel: string = \"default\",\n ): Promise<ContentItem | null> => {\n const env = await getState().http.get<Envelope<ContentItem | null>>(\n `${P.content}/today/${encodePathSegment(channel)}`,\n );\n return env.data ?? null;\n },\n library: async (\n // `channel` is the library NAME (the server resolves name-or-UUID).\n channel: string = \"default\",\n options?: { limit?: number; cursor?: string },\n ): Promise<ContentItem[]> => {\n // Server route is plural `/libraries/:libraryId` (accepts name or UUID).\n const env = await getState().http.get<Envelope<ContentItem[]>>(\n `${P.content}/libraries/${encodePathSegment(channel)}`,\n {\n limit: options?.limit,\n cursor: options?.cursor,\n },\n );\n return env.data;\n },\n item: async (id: string): Promise<ContentItem> => {\n // Server route is `/items/:itemId`.\n const env = await getState().http.get<Envelope<ContentItem>>(\n `${P.content}/items/${encodePathSegment(id)}`,\n );\n return env.data;\n },\n updateItem: async (id: string, state_: unknown): Promise<ContentItem> => {\n // Server route is `PATCH /items/:itemId` (update item fields).\n const env = await getState().http.patch<Envelope<ContentItem>>(\n `${P.content}/items/${encodePathSegment(id)}`,\n state_ as Record<string, unknown>,\n );\n return env.data;\n },\n createItem: async (\n // `channel` is the library NAME (the server resolves name-or-UUID).\n channel: string,\n item: unknown,\n ): Promise<ContentItem> => {\n // Server route is `POST /libraries/:libraryId/items`.\n const env = await getState().http.post<Envelope<ContentItem>>(\n `${P.content}/libraries/${encodePathSegment(channel)}/items`,\n item as Record<string, unknown>,\n );\n return env.data;\n },\n // </generated:content>\n },\n\n deepLinks: {\n // <generated:deepLinks>\n get: async (shortCode: string): Promise<DeepLink> => {\n // Server route is `/v1/client/deep-links/resolve/:slug` — DX-42 fix.\n const env = await getState().http.get<Envelope<DeepLink>>(\n `${P.deepLinks}/resolve/${encodePathSegment(shortCode)}`,\n );\n return env.data;\n },\n // </generated:deepLinks>\n create: async (params: DeepLinkCreate): Promise<DeepLink> => {\n const env = await getState().http.post<Envelope<DeepLink>>(\n P.deepLinks,\n params,\n );\n return env.data;\n },\n },\n\n onboarding: {\n // <generated:onboarding>\n getStatus: async (): Promise<OnboardingStatus> => {\n const env = await getState().http.get<Envelope<OnboardingStatus>>(\n P.onboarding,\n );\n return env.data;\n },\n nextStep: async (payload: unknown): Promise<OnboardingStatus> => {\n const env = await getState().http.post<Envelope<OnboardingStatus>>(\n `${P.onboarding}/next`,\n payload as Record<string, unknown>,\n );\n return env.data;\n },\n skipStep: async (): Promise<OnboardingStatus> => {\n const env = await getState().http.post<Envelope<OnboardingStatus>>(\n `${P.onboarding}/skip`,\n {},\n );\n return env.data;\n },\n complete: async (): Promise<OnboardingStatus> => {\n const env = await getState().http.post<Envelope<OnboardingStatus>>(\n `${P.onboarding}/complete`,\n {},\n );\n return env.data;\n },\n // </generated:onboarding>\n },\n};\n"],"mappings":"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"}
|