@layers/amba-react-native 4.0.5 → 4.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +514 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +205 -130
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +205 -130
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +521 -53
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["cachedStorage: AsyncStorageStatic | null | undefined","prefix: string","parts: string[]","h: Record<string, string>","init: RequestInit","response: Response","parsed: unknown","code: string | undefined","envelopeDetails: unknown","details: unknown","persistence: AsyncStoragePersistence","seed: PersistedIdentity","payload: PersistedIdentity","full: OutboxOp","replayError: unknown","out: ScheduledLocalNotification[]","ctx: RealtimeContext","name: string","conversationId: string","closeActive: (() => void) | null","settleActive: (() => void) | null","idx: number","dataLines: string[]","state: RuntimeState | null","body: Record<string, unknown>","query: Record<string, string>","query: Record<string, string> | undefined","response: Response","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/http-client.ts","../src/uuid.ts","../src/identity.ts","../src/resilience.ts","../src/push-rotation.ts","../src/local-notifications.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(private readonly prefix: string) {}\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 async write(key: string, value: string): Promise<void> {\n const fullKey = `${this.prefix}:${key}`;\n this.memoryCache.set(fullKey, value);\n const storage = await getStorage();\n if (storage) await storage.setItem(fullKey, value);\n }\n\n async delete(key: string): Promise<void> {\n const fullKey = `${this.prefix}:${key}`;\n this.memoryCache.delete(fullKey);\n const storage = await getStorage();\n if (storage) await storage.removeItem(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 | \"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 * 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`, 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 */\n\nimport { AmbaApiError } from \"./error.js\";\nimport type { IdentityStore } from \"./identity.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 identity: IdentityStore;\n /** Override the `fetch` implementation — tests pass a vi.fn() shim. */\n fetchImpl?: typeof fetch;\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\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 identity: IdentityStore;\n private readonly fetchImpl: typeof fetch;\n private readonly ensureSession?: (path: string) => Promise<void>;\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.identity = opts.identity;\n this.ensureSession = opts.ensureSession;\n this.fetchImpl =\n opts.fetchImpl ??\n (globalThis.fetch.bind(globalThis) as typeof fetch);\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 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 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 ): 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 (this.ensureSession && !this.identity.getSessionToken()) {\n await this.ensureSession(path);\n }\n\n const url = this.url(path, query);\n const headers = this.buildHeaders();\n\n const init: RequestInit = {\n method,\n headers,\n body: body === undefined ? undefined : JSON.stringify(body ?? {}),\n };\n\n let response: Response;\n try {\n response = await this.fetchImpl(url, init);\n } catch (err) {\n // Transport-level failure (DNS, refused, offline, abort). The\n // platform fetch throws a TypeError or AbortError; we wrap it\n // into the SDK's stable error shape.\n const message =\n err instanceof Error\n ? err.message\n : typeof err === \"string\"\n ? err\n : \"fetch failed\";\n throw new AmbaApiError(\"NETWORK_ERROR\", `Network error: ${message}`, err);\n }\n\n return parseResponse<T>(response);\n }\n}\n\nasync function parseResponse<T>(response: Response): Promise<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 text = await response.text();\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: ${\n err instanceof Error ? err.message : String(err)\n }`,\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 (innerErr && typeof innerErr === \"object\" && typeof innerErr.code === \"string\") {\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(\n code ?? \"VALIDATION_ERROR\",\n message,\n details,\n );\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 * Why pure JS rather than `globalThis.crypto.randomUUID()`:\n * - Older Hermes builds (RN <0.74) don't ship `crypto` global.\n * - `expo-crypto` exposes `randomUUID()` but is a separate dep we\n * don't want to force on every consumer.\n * - The amba SDK only uses UUIDs for `anonymous_id` — a stable\n * per-install identifier. Cryptographic randomness is not a\n * security requirement here; uniqueness across installs is.\n *\n * If `globalThis.crypto?.getRandomValues` is available (it is on\n * Hermes 0.75+ via the engine and on every Expo runtime via\n * react-native-get-random-values that Expo auto-installs), we use it\n * for higher-quality randomness. Otherwise we fall back to\n * `Math.random()` — which is sufficient for the install-id use case\n * because per-device uniqueness is what we need, not collision\n * resistance across the global population.\n */\n\ntype GetRandomValues = (a: Uint8Array) => Uint8Array;\n\nfunction getRandomBytes(n: number): Uint8Array {\n const bytes = new Uint8Array(n);\n const grv = (globalThis as { crypto?: { getRandomValues?: GetRandomValues } })\n .crypto?.getRandomValues;\n if (typeof grv === \"function\") {\n // Bind to crypto to preserve `this`.\n (globalThis as { crypto: { getRandomValues: GetRandomValues } }).crypto.getRandomValues(\n bytes,\n );\n return bytes;\n }\n // Math.random fallback — not crypto-secure but good enough for\n // install-id uniqueness.\n for (let i = 0; i < n; i++) bytes[i] = Math.floor(Math.random() * 256);\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` (stable per-install UUID), authenticated\n * user id, session token, refresh token, expiry.\n *\n * Persistence: writes go to `AsyncStoragePersistence` under\n * `<prefix>:identity_v1` (JSON-encoded). On `configure()` we hydrate\n * once; subsequent reads/writes are O(1) in-process.\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 { 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\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}\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\n private constructor(\n private readonly persistence: AsyncStoragePersistence,\n seed: PersistedIdentity,\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 }\n\n /**\n * Build a store, hydrating any prior identity from AsyncStorage.\n * If no prior identity exists, a fresh `anonymous_id` is minted.\n * Expired session tokens are dropped at hydration (the refresh token\n * survives so callers can `auth.refresh()` to mint a new pair).\n */\n static async create(\n persistence: AsyncStoragePersistence,\n ): Promise<IdentityStore> {\n const raw = await persistence.read(IDENTITY_KEY);\n let seed: PersistedIdentity;\n if (raw) {\n try {\n const parsed = JSON.parse(raw) as Partial<PersistedIdentity>;\n seed = {\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 };\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 if (isExpired(seed.expires_at)) {\n // Drop expired session_token but keep refresh_token so the caller\n // can mint a new pair.\n seed.session_token = null;\n seed.expires_at = null;\n }\n\n const store = new IdentityStore(persistence, seed);\n // Always persist after hydration — handles the first-run mint of\n // `anonymous_id` and clears any expired tokens from disk.\n await store.flush();\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 };\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 async setSession(args: {\n appUserId: string;\n sessionToken: string;\n refreshToken: string;\n expiresAt: string;\n }): Promise<void> {\n this.appUserId = args.appUserId;\n this.sessionToken = args.sessionToken;\n this.refreshToken = args.refreshToken;\n this.expiresAt = args.expiresAt;\n await this.flush();\n }\n\n async clearSession(rotateAnonymousId: boolean): Promise<void> {\n this.appUserId = null;\n this.sessionToken = null;\n this.refreshToken = null;\n this.expiresAt = null;\n if (rotateAnonymousId) {\n this.anonymousId = uuidv4();\n }\n await this.flush();\n }\n\n private async flush(): Promise<void> {\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 };\n await this.persistence.write(IDENTITY_KEY, JSON.stringify(payload));\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 };\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 collection-write idempotency support so a\n * mutation that actually reached the server before the connection\n * dropped is never applied 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 */\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/** 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 write that landed\n * before the connection dropped (the events `track` API has no idempotency\n * channel, so `track` replays are at-least-once — a rare reconnect duplicate\n * is acceptable for fire-and-forget analytics). `enqueuedAt` is an ISO\n * 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 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)`); `track` replays carry no idempotency channel and are\n * at-least-once. Throws on failure — the queue decides whether to retain or\n * 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]);\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/** Generate a stable idempotency key for a queued op. */\nexport function newIdempotencyKey(): string {\n const c =\n typeof globalThis !== \"undefined\"\n ? (globalThis.crypto as { randomUUID?: () => string } | undefined)\n : undefined;\n if (c && typeof c.randomUUID === \"function\") return `amba_obx_${c.randomUUID()}`;\n // Fallback for runtimes without crypto.randomUUID — collision-resistant\n // enough for a per-device outbox (the server still dedupes on the key).\n return `amba_obx_${Date.now().toString(36)}_${Math.random()\n .toString(36)\n .slice(2, 12)}`;\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 private flushing: Promise<FlushResult> | null = null;\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 this.store = opts.store;\n this.executor = opts.executor;\n this.storageKey = opts.storageKey ?? \"offline_outbox\";\n this.onDropped = opts.onDropped;\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 /** Append an op to the durable outbox. Returns the persisted op. */\n async enqueue(\n op: Omit<OutboxOp, \"idempotencyKey\" | \"enqueuedAt\"> & {\n idempotencyKey?: string;\n },\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 full: OutboxOp = {\n ...op,\n idempotencyKey: op.idempotencyKey ?? newIdempotencyKey(),\n enqueuedAt: new Date().toISOString(),\n };\n return this.mutate((ops) => ({ next: [...ops, full], result: full }), {\n rejectIfDisposed: true,\n });\n }\n\n /** Number of ops awaiting replay. */\n async pendingCount(): Promise<number> {\n return (await this.load()).length;\n }\n\n /** Snapshot the pending ops (a copy — mutating it does not affect the queue). */\n async pending(): Promise<OutboxOp[]> {\n return [...(await this.load())];\n }\n\n /** Drop every queued op without replaying. */\n async clear(): Promise<void> {\n await this.mutate(() => ({ next: [], result: undefined }));\n }\n\n /**\n * Replay the outbox in FIFO order. Each op is removed only after it\n * succeeds; the first failure stops the flush and retains the remainder\n * (so order is preserved and nothing is dropped mid-batch). Concurrent\n * 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 head = (await this.load())[0];\n if (!head) break;\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.\n await this.mutate((ops) => {\n const present = ops.some(\n (o) => o.idempotencyKey === head.idempotencyKey,\n );\n return {\n next: present ? ops : [head, ...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.mutate((ops) => {\n const idx = ops.findIndex(\n (o) => o.idempotencyKey === head.idempotencyKey,\n );\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 if (!removed) break; // shouldn't happen, but never spin\n if (ok) {\n flushed += 1;\n } else {\n dropped += 1;\n try {\n this.onDropped?.(head, replayError);\n } catch {\n // A throwing drop-observer must not break the flush.\n }\n }\n }\n const remaining = (await this.load()).length;\n return { flushed, dropped, remaining };\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","/**\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 * 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\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\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/${encodeURIComponent(\n this.name,\n )}${qs}`;\n return openStream<RealtimeChange<T>>({\n url,\n authHeaders: this.ctx.authHeaders,\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/${encodeURIComponent(\n this.conversationId,\n )}`;\n return openStream<MessageChange<T>>({\n url,\n authHeaders: this.ctx.authHeaders,\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 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 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 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 /** 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 xhr.onreadystatechange = (): void => {\n if (aborted) return;\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 args.onError(err);\n }\n\n return () => {\n aborted = true;\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 const resp = await fetch(args.url, {\n method: \"GET\",\n headers: { ...args.headers, Accept: \"text/event-stream\" },\n signal: controller.signal,\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 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 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} from \"./types\";\n\nimport { AsyncStoragePersistence } from \"./async-storage-adapter.js\";\nimport { AmbaApiError } from \"./error.js\";\nimport { AmbaHttpClient, DEFAULT_BASE_URL, mapHttpError } from \"./http-client.js\";\nimport type { Envelope } from \"./http-client.js\";\nimport { IdentityStore } from \"./identity.js\";\nimport {\n OfflineQueue,\n isOfflineError,\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 {\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 FlushResult,\n KvStore,\n} from \"./resilience.js\";\nexport type { TokenRefreshSubscribe } from \"./push-rotation.js\";\nexport type { LocalNotificationsNamespace } from \"./local-notifications.js\";\n\n/** SDK semver — burned in at build time. Bump alongside package.json. */\nexport const SDK_VERSION = \"4.0.5\";\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 /**\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 /** Durable key/value persistence (AsyncStorage-backed) for the outbox. */\n persistence: AsyncStoragePersistence;\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}\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\";\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\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 = (session: Session | null) => void;\nconst authSubscribers = new Set<AuthSubscriber>();\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\nfunction notifyAuthSubscribers(session: Session | null): void {\n for (const cb of authSubscribers) {\n try {\n cb(session);\n } catch (err) {\n if (typeof console !== \"undefined\") {\n console.error(\"[amba] onAuthStateChange callback threw\", err);\n }\n }\n }\n}\n\nasync function notifyFromAuthResult(result: AuthResult): Promise<void> {\n notifyAuthSubscribers(snapshotSession(result.user));\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\nasync function persistAuthResult(\n state_: RuntimeState,\n result: AuthResult,\n): Promise<void> {\n await state_.identity.setSession({\n appUserId: result.user.id,\n sessionToken: result.session_token,\n refreshToken: result.refresh_token,\n expiresAt: expiresAtFromResult(result),\n });\n}\n\ninterface AuthEnvelope {\n data: AuthResult;\n}\n\nasync function postAuth(\n state_: RuntimeState,\n path: string,\n body: unknown,\n): Promise<AuthResult> {\n const env = await state_.http.post<AuthEnvelope>(path, body);\n const result = env.data;\n await persistAuthResult(state_, result);\n await notifyFromAuthResult(result);\n return result;\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\nasync function ensureAnonymousSession(state_: RuntimeState): Promise<void> {\n if (state_.identity.getSessionToken()) return;\n if (!state_.anonSignInInflight) {\n state_.anonSignInInflight = (async () => {\n try {\n await postAuth(state_, P.authAnonymous, {\n anonymous_id: state_.identity.getAnonymousId(),\n });\n } finally {\n state_.anonSignInInflight = null;\n }\n })();\n }\n await state_.anonSignInInflight;\n}\n\ninterface UserEnvelope {\n data: { user: User };\n}\n\ninterface IdentifiersEnvelope {\n data: { identifiers: LinkedIdentifier[] };\n}\n\n/**\n * POST helper for endpoints that return `{ data: { user } }` WITHOUT\n * minting new tokens. Used by `linkSmsOtp` / `linkEmailOtp` / `unlink*`\n * — these are \"decorate the current user,\" not \"re-authenticate.\"\n * 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 env.data.user;\n}\n\nasync function unlinkProvider(\n provider: \"sms_otp\" | \"email_otp\" | \"apple\" | \"google\",\n): Promise<User> {\n const s = getState();\n const sessionToken = s.identity.getSessionToken();\n if (!sessionToken)\n throw new AmbaApiError(\n \"UNAUTHORIZED\",\n \"no active session — call signIn* first\",\n );\n return postUserNoRotate(s, P.authUnlink, {\n provider,\n session_token: sessionToken,\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(\n \"VALIDATION_ERROR\",\n \"email must not be empty\",\n );\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(\n \"VALIDATION_ERROR\",\n \"code must be 6 digits\",\n );\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 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\nfunction encodePathSegment(s: string): string {\n return encodeURIComponent(s);\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\n/**\n * Resolve the full invocation URL for a deployed function. With\n * `functionsBaseUrl` configured we POST to `<base>/<name>` verbatim;\n * otherwise we resolve the server-side project id once (via\n * `diagnostics.ping`, cached) and target\n * `https://<projectId>.fn.amba.host/<name>`.\n */\nasync function resolveFunctionUrl(s: RuntimeState, name: string): Promise<string> {\n if (s.functionsBaseUrl) {\n return `${s.functionsBaseUrl.replace(/\\/$/, \"\")}/${encodeURIComponent(name)}`;\n }\n if (!s.resolvedFunctionsProjectId) {\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 }\n return `https://${s.resolvedFunctionsProjectId}.${DEFAULT_FUNCTIONS_HOST}/${encodeURIComponent(name)}`;\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 * Replay one queued mutation against the live API, forwarding the op's\n * idempotency key on collection inserts so a write that landed before the\n * connection dropped is never double-applied (API migration 056).\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 { ...(op.payload ?? {}), idempotency_key: op.idempotencyKey },\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 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, but the server attributes engagement events to the SESSION\n // user at replay time (no body-id attribution) — so a buffered event\n // lands against whoever is signed in when it flushes.\n anonymous_id: s.identity.getAnonymousId(),\n timestamp: op.occurredAt ?? new Date().toISOString(),\n ...(op.telemetry ? { telemetry: true } : {}),\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 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 * Top-level SDK namespace for React Native.\n */\nexport const Amba = {\n configure: async (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 persistence = new AsyncStoragePersistence(\n config.storagePrefix ?? \"amba\",\n );\n await persistence.hydrate();\n const identity = await IdentityStore.create(persistence);\n\n const baseUrl = (config.apiUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\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 identity,\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 ensureSession:\n config.autoAnonymousSession === false\n ? undefined\n : async (path: string) => {\n if (pathSkipsLazySession(path)) return;\n await ensureAnonymousSession(getState());\n },\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 const prior = state;\n _offlineEnableToken += 1;\n if (prior?.offlineQueue) await prior.offlineQueue.dispose();\n state = {\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 persistence,\n offlineEnabled: false,\n offlineQueue: null,\n };\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 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 { baseUrl: getState().baseUrl, authHeaders: realtimeAuthHeaders },\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 });\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 },\n ): Promise<void> => {\n const s = getState();\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 = new Date().toISOString();\n const body: Record<string, unknown> = {\n event,\n properties: properties ?? undefined,\n anonymous_id: s.identity.getAnonymousId(),\n timestamp: occurredAt,\n };\n if (options?.telemetry) {\n body['telemetry'] = true;\n }\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,\n telemetry: options?.telemetry,\n occurredAt,\n });\n return;\n }\n throw err;\n }\n },\n },\n\n auth: {\n signInAnonymously: async (): Promise<AuthResult> => {\n const s = getState();\n // Send the SDK's current anonymous_id so the server can either\n // attach to the existing app_user (no MAU charge) or mint a new\n // one. See DX-2.\n return postAuth(s, P.authAnonymous, {\n anonymous_id: s.identity.getAnonymousId(),\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(getState(), 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(getState(), 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 signInWithApple: async (identityToken: string): Promise<AuthResult> => {\n if (identityToken.trim() === \"\")\n throw new AmbaApiError(\n \"VALIDATION_ERROR\",\n \"identity_token must not be empty\",\n );\n return postAuth(getState(), P.authSocial, {\n provider: \"apple\",\n id_token: identityToken,\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 signInWithGoogle: async (idToken: string): Promise<AuthResult> => {\n if (idToken.trim() === \"\")\n throw new AmbaApiError(\n \"VALIDATION_ERROR\",\n \"id_token must not be empty\",\n );\n return postAuth(getState(), P.authSocial, {\n provider: \"google\",\n id_token: idToken,\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(getState(), 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(getState(), 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(\n \"VALIDATION_ERROR\",\n \"token must not be empty\",\n );\n return postAuth(getState(), 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 */\n linkAccount: async (\n provider: string,\n credential: string,\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 = getState();\n const sessionToken = s.identity.getSessionToken();\n if (!sessionToken)\n throw new AmbaApiError(\n \"UNAUTHORIZED\",\n \"no active session — call signIn* first\",\n );\n return postAuth(s, P.authLink, {\n provider: providerTrimmed,\n token: credential,\n session_token: sessionToken,\n });\n },\n\n /**\n * Attach a verified phone to the current session's user without\n * minting new tokens. Same `user_id`, same session token. Server\n * returns `PHONE_ALREADY_LINKED` (409) with\n * `details.conflicting_user_id` if the phone is owned by another\n * user — the SDK rethrows as `AmbaApiError`.\n */\n linkSmsOtp: async (phone: string, code: string): Promise<User> => {\n requireE164Phone(phone);\n requireOtpCode(code);\n const s = getState();\n const sessionToken = s.identity.getSessionToken();\n if (!sessionToken)\n throw new AmbaApiError(\n \"UNAUTHORIZED\",\n \"no active session — call signIn* first\",\n );\n return postUserNoRotate(s, P.authLink, {\n provider: \"sms_otp\",\n phone,\n code,\n session_token: sessionToken,\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 const s = getState();\n const sessionToken = s.identity.getSessionToken();\n if (!sessionToken)\n throw new AmbaApiError(\n \"UNAUTHORIZED\",\n \"no active session — call signIn* first\",\n );\n return postUserNoRotate(s, P.authLink, {\n provider: \"email_otp\",\n email,\n code,\n session_token: sessionToken,\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`, same session token, no rotation). Server returns\n * `EMAIL_ALREADY_LINKED` (409) with `details.conflicting_user_id` if\n * the email is owned by another user — rethrown as `AmbaApiError`.\n * After linking, the user can sign in via `signInWithEmail`.\n */\n linkEmailPassword: async (email: string, password: string): Promise<User> => {\n requireEmail(email);\n requirePassword(password);\n const s = getState();\n const sessionToken = s.identity.getSessionToken();\n if (!sessionToken)\n throw new AmbaApiError(\n \"UNAUTHORIZED\",\n \"no active session — call signIn* first\",\n );\n return postUserNoRotate(s, P.authLink, {\n provider: \"email_password\",\n email,\n password,\n session_token: sessionToken,\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 if (!s.identity.getSessionToken())\n throw new AmbaApiError(\n \"UNAUTHORIZED\",\n \"no active session — call signIn* first\",\n );\n const env = await s.http.get<IdentifiersEnvelope>(P.authIdentifiers);\n return env.data.identifiers;\n },\n\n signOut: async (rotateAnonymousId = false): Promise<void> => {\n const s = getState();\n if (s.identity.getSessionToken()) {\n try {\n await s.http.post(P.authLogout, {});\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 await s.identity.clearSession(rotateAnonymousId);\n notifyAuthSubscribers(null);\n },\n refresh: async (): Promise<AuthResult> => {\n const s = getState();\n const refreshToken = s.identity.getRefreshToken();\n if (!refreshToken)\n throw new AmbaApiError(\n \"UNAUTHORIZED\",\n \"no refresh token available — call signIn* first\",\n );\n return postAuth(s, P.authRefresh, { refresh_token: refreshToken });\n },\n me: async (): Promise<User> => {\n const s = getState();\n if (!s.identity.getSessionToken())\n throw new AmbaApiError(\n \"UNAUTHORIZED\",\n \"no active session — call signIn* first\",\n );\n const env = await s.http.get<Envelope<User>>(P.authMe);\n return env.data;\n },\n\n getSession: async (): Promise<Session | null> => {\n const s = getState();\n if (!s.identity.isAuthenticated()) return null;\n const env = await s.http.get<Envelope<User>>(P.authMe);\n return snapshotSession(env.data);\n },\n\n getAnonymousId: async (): Promise<string> => {\n return getState().identity.getAnonymousId();\n },\n\n onAuthStateChange: (\n cb: (session: Session | null) => 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 if (!s.identity.getSessionToken())\n throw new AmbaApiError(\n \"UNAUTHORIZED\",\n \"no active session — call signIn* first\",\n );\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 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: ((\n arg1: string | UserPatch,\n arg2?: UserPatch,\n ): Promise<User> => {\n const s = getState();\n if (!s.identity.getSessionToken())\n throw new AmbaApiError(\n \"UNAUTHORIZED\",\n \"no active session — call signIn* first\",\n );\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 return s.http\n .patch<Envelope<User>>(path, patch)\n .then((env) => 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>>(\n P.sync,\n { changes },\n );\n return env.data;\n },\n pullChanges: async (\n since: { entity_type: string; 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> => getOfflineQueue(getState()).flush(),\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\n ? { query: JSON.stringify({ where }) }\n : 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 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 });\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 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 });\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 try {\n const result = await s.http.delete<\n { data: { deleted: boolean } } | null\n >(\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 });\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(\n \"NOT_FOUND\",\n \"asset has no resolvable CDN url\",\n );\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 let response: Response;\n try {\n response = await (globalThis.fetch ?? fetch)(cdnUrl, {\n method: \"GET\",\n });\n } catch (err) {\n throw new AmbaApiError(\n \"NETWORK_ERROR\",\n `Network error: ${err instanceof Error ? err.message : String(err)}`,\n err,\n );\n }\n if (!response.ok) {\n const text = await response.text();\n const ct = response.headers.get(\"content-type\") ?? \"\";\n throw mapHttpError(response, text, ct.includes(\"application/json\"));\n }\n return response.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 * @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 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 let response: Response;\n try {\n response = await (globalThis.fetch ?? fetch)(url, {\n method,\n headers,\n body: requestBody,\n });\n } catch (err) {\n throw new AmbaApiError(\n \"NETWORK_ERROR\",\n `Amba.functions.invoke(\"${name}\") network error: ${err instanceof Error ? err.message : String(err)}`,\n err,\n );\n }\n\n const text = await response.text();\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 // 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 let response: Response;\n try {\n response = await (globalThis.fetch ?? fetch)(url, {\n method: \"GET\",\n headers,\n });\n } catch (err) {\n throw new AmbaApiError(\n \"NETWORK_ERROR\",\n `Network error: ${err instanceof Error ? err.message : String(err)}`,\n err,\n );\n }\n const text = await response.text();\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 return {\n version,\n values: parsed.data ?? {},\n };\n },\n // </generated:config>\n },\n\n flags: {\n // <generated:flags>\n fetch: async (): Promise<FlagAssignment[]> => {\n const env = await getState().http.get<Envelope<FlagAssignment[]>>(P.flags);\n return 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.\n */\n get: async (key: string): Promise<FlagAssignment | null> => {\n try {\n const env = await getState().http.get<Envelope<FlagAssignment>>(\n `${P.flags}/${encodePathSegment(key)}`,\n );\n return env.data;\n } catch (err) {\n if (err instanceof AmbaApiError && err.code === \"NOT_FOUND\") return null;\n throw err;\n }\n },\n // </generated:flags>\n },\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<InventoryItem> => {\n const env = await getState().http.post<Envelope<InventoryItem>>(\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 // apps/api/src/routes/client/friends.ts:20.\n const env = await getState().http.post<Envelope<Friendship>>(\n `${P.friends}/request`,\n { user_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>(conversationId: string): ConversationSubscription<T> =>\n new ConversationSubscription<T>(\n { baseUrl: getState().baseUrl, authHeaders: realtimeAuthHeaders },\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 /**\n * Developer utilities. Only available on sandbox-tier projects.\n *\n * These methods are safe to call from client-side code — they use the\n * project's client key, not an admin credential.\n */\n dev: {\n /**\n * Wipe all users and their associated data on a sandbox-tier project.\n *\n * Lets a developer's Settings or Account tab add a \"Reset\" button\n * without embedding an admin credential on the device. Rejects with\n * an `AmbaApiError` with `code: 'NOT_SANDBOX_TIER'` if the project\n * is not on a sandbox tier.\n */\n resetSandbox: async (): Promise<{ deleted: number }> => {\n const s = getState();\n const env = await s.http.post<Envelope<{ deleted: number }>>(\n \"/v1/client/dev/reset-sandbox\",\n {},\n );\n return env.data;\n },\n },\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["cachedStorage: AsyncStorageStatic | null | undefined","prefix: string","parts: string[]","h: Record<string, string>","init: RequestInit","response: Response","parsed: unknown","code: string | undefined","envelopeDetails: unknown","details: unknown","persistence: AsyncStoragePersistence","seed: PersistedIdentity","payload: PersistedIdentity","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[]","ctx: RealtimeContext","name: string","conversationId: string","closeActive: (() => void) | null","settleActive: (() => void) | null","idx: number","dataLines: string[]","state: RuntimeState | null","nextState: RuntimeState","body: Record<string, unknown>","query: Record<string, string>","query: Record<string, string> | undefined","response: Response","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/http-client.ts","../src/uuid.ts","../src/identity.ts","../src/resilience.ts","../src/event-batch-queue.ts","../src/push-rotation.ts","../src/local-notifications.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(private 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 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 | \"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 * 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`, 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 */\n\nimport { AmbaApiError } from \"./error.js\";\nimport type { IdentityStore } from \"./identity.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 identity: IdentityStore;\n /** Override the `fetch` implementation — tests pass a vi.fn() shim. */\n fetchImpl?: typeof fetch;\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\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 identity: IdentityStore;\n private readonly fetchImpl: typeof fetch;\n private readonly ensureSession?: (path: string) => Promise<void>;\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.identity = opts.identity;\n this.ensureSession = opts.ensureSession;\n this.fetchImpl =\n opts.fetchImpl ?? (globalThis.fetch.bind(globalThis) as typeof fetch);\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 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 ): 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\n const init: RequestInit = {\n method,\n headers,\n body: body === undefined ? undefined : JSON.stringify(body ?? {}),\n };\n\n let response: Response;\n try {\n response = await this.fetchImpl(url, init);\n } catch (err) {\n // Transport-level failure (DNS, refused, offline, abort). The\n // platform fetch throws a TypeError or AbortError; we wrap it\n // into the SDK's stable error shape.\n const message =\n err instanceof Error\n ? err.message\n : typeof err === \"string\"\n ? err\n : \"fetch failed\";\n throw new AmbaApiError(\"NETWORK_ERROR\", `Network error: ${message}`, err);\n }\n\n return parseResponse<T>(response);\n }\n}\n\nasync function parseResponse<T>(response: Response): Promise<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 text = await response.text();\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 * Why pure JS rather than `globalThis.crypto.randomUUID()`:\n * - Older Hermes builds (RN <0.74) don't ship `crypto` global.\n * - `expo-crypto` exposes `randomUUID()` but is a separate dep we\n * don't want to force on every consumer.\n * - The amba SDK only uses UUIDs for `anonymous_id` — a stable\n * per-install identifier. Cryptographic randomness is not a\n * security requirement here; uniqueness across installs is.\n *\n * If `globalThis.crypto?.getRandomValues` is available (it is on\n * Hermes 0.75+ via the engine and on every Expo runtime via\n * react-native-get-random-values that Expo auto-installs), we use it\n * for higher-quality randomness. Otherwise we fall back to\n * `Math.random()` — which is sufficient for the install-id use case\n * because per-device uniqueness is what we need, not collision\n * resistance across the global population.\n */\n\ntype GetRandomValues = (a: Uint8Array) => Uint8Array;\n\nfunction getRandomBytes(n: number): Uint8Array {\n const bytes = new Uint8Array(n);\n const grv = (globalThis as { crypto?: { getRandomValues?: GetRandomValues } })\n .crypto?.getRandomValues;\n if (typeof grv === \"function\") {\n // Bind to crypto to preserve `this`.\n (globalThis as { crypto: { getRandomValues: GetRandomValues } }).crypto.getRandomValues(\n bytes,\n );\n return bytes;\n }\n // Math.random fallback — not crypto-secure but good enough for\n // install-id uniqueness.\n for (let i = 0; i < n; i++) bytes[i] = Math.floor(Math.random() * 256);\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` (stable per-install UUID), authenticated\n * user id, session token, refresh token, expiry.\n *\n * Persistence: writes go to `AsyncStoragePersistence` under\n * `<prefix>:identity_v1` (JSON-encoded). On `configure()` we hydrate\n * once; subsequent reads/writes are O(1) in-process.\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 { 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\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}\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\n private constructor(\n private readonly persistence: AsyncStoragePersistence,\n seed: PersistedIdentity,\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 }\n\n /**\n * Build a store, hydrating any prior identity from AsyncStorage.\n * If no prior identity exists, a fresh `anonymous_id` is minted.\n * Expired session tokens are dropped at hydration (the refresh token\n * survives so callers can `auth.refresh()` to mint a new pair).\n */\n static async create(\n persistence: AsyncStoragePersistence,\n ): Promise<IdentityStore> {\n const raw = await persistence.read(IDENTITY_KEY);\n let seed: PersistedIdentity;\n if (raw) {\n try {\n const parsed = JSON.parse(raw) as Partial<PersistedIdentity>;\n seed = {\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 };\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 if (isExpired(seed.expires_at)) {\n // Drop expired session_token but keep refresh_token so the caller\n // can mint a new pair.\n seed.session_token = null;\n seed.expires_at = null;\n }\n\n const store = new IdentityStore(persistence, seed);\n // Always persist after hydration — handles the first-run mint of\n // `anonymous_id` and clears any expired tokens from disk.\n await store.flush();\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 };\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 async setSession(args: {\n appUserId: string;\n sessionToken: string;\n refreshToken: string;\n expiresAt: string;\n }): Promise<void> {\n this.appUserId = args.appUserId;\n this.sessionToken = args.sessionToken;\n this.refreshToken = args.refreshToken;\n this.expiresAt = args.expiresAt;\n await this.flush();\n }\n\n async clearSession(rotateAnonymousId: boolean): Promise<void> {\n this.appUserId = null;\n this.sessionToken = null;\n this.refreshToken = null;\n this.expiresAt = null;\n if (rotateAnonymousId) {\n this.anonymousId = uuidv4();\n }\n await this.flush();\n }\n\n private async flush(): Promise<void> {\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 };\n await this.persistence.write(IDENTITY_KEY, JSON.stringify(payload));\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 };\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 collection-write idempotency support so a\n * mutation that actually reached the server before the connection\n * dropped is never applied 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 */\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/** 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 write that landed\n * before the connection dropped (the events `track` API has no idempotency\n * channel, so `track` replays are at-least-once — a rare reconnect duplicate\n * is acceptable for fire-and-forget analytics). `enqueuedAt` is an ISO\n * 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 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)`); `track` replays carry no idempotency channel and are\n * at-least-once. Throws on failure — the queue decides whether to retain or\n * 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]);\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/** Generate a stable idempotency key for a queued op. */\nexport function newIdempotencyKey(): string {\n const c =\n typeof globalThis !== \"undefined\"\n ? (globalThis.crypto as { randomUUID?: () => string } | undefined)\n : undefined;\n if (c && typeof c.randomUUID === \"function\") return `amba_obx_${c.randomUUID()}`;\n // Fallback for runtimes without crypto.randomUUID — collision-resistant\n // enough for a per-device outbox (the server still dedupes on the key).\n return `amba_obx_${Date.now().toString(36)}_${Math.random()\n .toString(36)\n .slice(2, 12)}`;\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 private flushing: Promise<FlushResult> | null = null;\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 this.store = opts.store;\n this.executor = opts.executor;\n this.storageKey = opts.storageKey ?? \"offline_outbox\";\n this.onDropped = opts.onDropped;\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 /** Append an op to the durable outbox. Returns the persisted op. */\n async enqueue(\n op: Omit<OutboxOp, \"idempotencyKey\" | \"enqueuedAt\"> & {\n idempotencyKey?: string;\n },\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 full: OutboxOp = {\n ...op,\n idempotencyKey: op.idempotencyKey ?? newIdempotencyKey(),\n enqueuedAt: new Date().toISOString(),\n };\n return this.mutate((ops) => ({ next: [...ops, full], result: full }), {\n rejectIfDisposed: true,\n });\n }\n\n /** Number of ops awaiting replay. */\n async pendingCount(): Promise<number> {\n return (await this.load()).length;\n }\n\n /** Snapshot the pending ops (a copy — mutating it does not affect the queue). */\n async pending(): Promise<OutboxOp[]> {\n return [...(await this.load())];\n }\n\n /** Drop every queued op without replaying. */\n async clear(): Promise<void> {\n await this.mutate(() => ({ next: [], result: undefined }));\n }\n\n /**\n * Replay the outbox in FIFO order. Each op is removed only after it\n * succeeds; the first failure stops the flush and retains the remainder\n * (so order is preserved and nothing is dropped mid-batch). Concurrent\n * 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 head = (await this.load())[0];\n if (!head) break;\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.\n await this.mutate((ops) => {\n const present = ops.some(\n (o) => o.idempotencyKey === head.idempotencyKey,\n );\n return {\n next: present ? ops : [head, ...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.mutate((ops) => {\n const idx = ops.findIndex(\n (o) => o.idempotencyKey === head.idempotencyKey,\n );\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 if (!removed) break; // shouldn't happen, but never spin\n if (ok) {\n flushed += 1;\n } else {\n dropped += 1;\n try {\n this.onDropped?.(head, replayError);\n } catch {\n // A throwing drop-observer must not break the flush.\n }\n }\n }\n const remaining = (await this.load()).length;\n return { flushed, dropped, remaining };\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 enqueue(\n event: string,\n properties: Record<string, unknown> | undefined,\n telemetry: boolean,\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: 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 * 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\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\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/${encodeURIComponent(\n this.name,\n )}${qs}`;\n return openStream<RealtimeChange<T>>({\n url,\n authHeaders: this.ctx.authHeaders,\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/${encodeURIComponent(\n this.conversationId,\n )}`;\n return openStream<MessageChange<T>>({\n url,\n authHeaders: this.ctx.authHeaders,\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 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 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 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 /** 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 xhr.onreadystatechange = (): void => {\n if (aborted) return;\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 args.onError(err);\n }\n\n return () => {\n aborted = true;\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 const resp = await fetch(args.url, {\n method: \"GET\",\n headers: { ...args.headers, Accept: \"text/event-stream\" },\n signal: controller.signal,\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 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 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} from \"./types\";\n\nimport { AsyncStoragePersistence } from \"./async-storage-adapter.js\";\nimport { AmbaApiError } from \"./error.js\";\nimport {\n AmbaHttpClient,\n DEFAULT_BASE_URL,\n mapHttpError,\n} from \"./http-client.js\";\nimport type { Envelope } from \"./http-client.js\";\nimport { IdentityStore } from \"./identity.js\";\nimport { EventBatchQueue } from \"./event-batch-queue.js\";\nimport type { EventFlushResult, EventDrop } from \"./event-batch-queue.js\";\nimport {\n OfflineQueue,\n isOfflineError,\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 {\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 FlushResult,\n KvStore,\n} from \"./resilience.js\";\nexport type { TokenRefreshSubscribe } from \"./push-rotation.js\";\nexport type { EventFlushResult, EventDrop } from \"./event-batch-queue.js\";\nexport type { LocalNotificationsNamespace } from \"./local-notifications.js\";\n\n/** SDK semver — burned in at build time. Bump alongside package.json. */\nexport const SDK_VERSION = \"4.0.7\";\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 /** Durable key/value persistence (AsyncStorage-backed) for the outbox. */\n persistence: AsyncStoragePersistence;\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}\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\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\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 = (session: Session | null) => void;\nconst authSubscribers = new Set<AuthSubscriber>();\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\nfunction notifyAuthSubscribers(session: Session | null): void {\n for (const cb of authSubscribers) {\n try {\n cb(session);\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\nasync function notifyFromAuthResult(result: AuthResult): Promise<void> {\n notifyAuthSubscribers(snapshotSession(result.user));\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\nasync function persistAuthResult(\n state_: RuntimeState,\n result: AuthResult,\n): Promise<void> {\n await state_.identity.setSession({\n appUserId: result.user.id,\n sessionToken: result.session_token,\n refreshToken: result.refresh_token,\n expiresAt: expiresAtFromResult(result),\n });\n}\n\ninterface AuthEnvelope {\n data: AuthResult;\n}\n\nasync function postAuth(\n state_: RuntimeState,\n path: string,\n body: unknown,\n): Promise<AuthResult> {\n const env = await state_.http.post<AuthEnvelope>(path, body);\n const result = normalizeAuthResult(env.data);\n await persistAuthResult(state_, result);\n void state_.eventQueue?.flush();\n await notifyFromAuthResult(result);\n return result;\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\nasync function ensureAnonymousSession(state_: RuntimeState): Promise<void> {\n if (state_.identity.getSessionToken()) return;\n if (!state_.anonSignInInflight) {\n state_.anonSignInInflight = (async () => {\n try {\n await postAuth(state_, P.authAnonymous, {\n anonymous_id: state_.identity.getAnonymousId(),\n });\n } finally {\n state_.anonSignInInflight = null;\n }\n })();\n }\n await state_.anonSignInInflight;\n}\n\ninterface UserEnvelope {\n data: { user: User };\n}\n\ninterface IdentifiersEnvelope {\n data: { identifiers: LinkedIdentifier[] };\n}\n\n/**\n * POST helper for endpoints that return `{ data: { user } }` WITHOUT\n * minting new tokens. Used by `linkSmsOtp` / `linkEmailOtp` / `unlink*`\n * — these are \"decorate the current user,\" not \"re-authenticate.\"\n * 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 = getState();\n const sessionToken = s.identity.getSessionToken();\n if (!sessionToken)\n throw new AmbaApiError(\n \"UNAUTHORIZED\",\n \"no active session — call signIn* first\",\n );\n return postUserNoRotate(s, P.authUnlink, {\n provider,\n session_token: sessionToken,\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 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\nfunction encodePathSegment(s: string): string {\n return encodeURIComponent(s);\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\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(/\\/$/, \"\")}/${encodeURIComponent(name)}`;\n }\n const projectId = await resolveFunctionsProjectId(s);\n return `https://${projectId}.${DEFAULT_FUNCTIONS_HOST}/${encodeURIComponent(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 * Replay one queued mutation against the live API, forwarding the op's\n * idempotency key on collection inserts so a write that landed before the\n * connection dropped is never double-applied (API migration 056).\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 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, but the server attributes engagement events to the SESSION\n // user at replay time (no body-id attribution) — so a buffered event\n // lands against whoever is signed in when it flushes.\n anonymous_id: s.identity.getAnonymousId(),\n timestamp: op.occurredAt ?? new Date().toISOString(),\n ...(op.telemetry ? { telemetry: true } : {}),\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 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 * Top-level SDK namespace for React Native.\n */\nexport const Amba = {\n configure: async (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 persistence = new AsyncStoragePersistence(\n config.storagePrefix ?? \"amba\",\n );\n if (config.events?.batching) await persistence.assertDurable();\n await persistence.hydrate();\n const identity = await IdentityStore.create(persistence);\n\n const baseUrl = (config.apiUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\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 identity,\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 ensureSession:\n config.autoAnonymousSession === false\n ? undefined\n : async (path: string) => {\n if (pathSkipsLazySession(path)) return;\n await ensureAnonymousSession(getState());\n },\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 const prior = state;\n _offlineEnableToken += 1;\n if (prior?.offlineQueue) await prior.offlineQueue.dispose();\n prior?.detachEventAppState?.();\n if (prior?.eventQueue) await prior.eventQueue.dispose();\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 offlineEnabled: false,\n offlineQueue: null,\n eventQueue: null,\n };\n state = nextState;\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 },\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 state?.detachEventAppState?.();\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 { baseUrl: getState().baseUrl, authHeaders: realtimeAuthHeaders },\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 });\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 const s = getState();\n if (s.eventQueue && !options?.immediate) {\n return s.eventQueue.enqueue(\n event,\n properties,\n options?.telemetry === true,\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 = new Date().toISOString();\n const body: Record<string, unknown> = {\n event,\n properties: properties ?? undefined,\n anonymous_id: s.identity.getAnonymousId(),\n timestamp: occurredAt,\n };\n if (options?.telemetry) {\n body[\"telemetry\"] = true;\n }\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,\n telemetry: options?.telemetry,\n occurredAt,\n });\n return;\n }\n throw err;\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 signInAnonymously: async (): Promise<AuthResult> => {\n const s = getState();\n // Send the SDK's current anonymous_id so the server can either\n // attach to the existing app_user (no MAU charge) or mint a new\n // one. See DX-2.\n return postAuth(s, P.authAnonymous, {\n anonymous_id: s.identity.getAnonymousId(),\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(getState(), 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(getState(), 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 signInWithApple: async (identityToken: string): Promise<AuthResult> => {\n if (identityToken.trim() === \"\")\n throw new AmbaApiError(\n \"VALIDATION_ERROR\",\n \"identity_token must not be empty\",\n );\n return postAuth(getState(), P.authSocial, {\n provider: \"apple\",\n id_token: identityToken,\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 signInWithGoogle: async (idToken: string): Promise<AuthResult> => {\n if (idToken.trim() === \"\")\n throw new AmbaApiError(\n \"VALIDATION_ERROR\",\n \"id_token must not be empty\",\n );\n return postAuth(getState(), P.authSocial, {\n provider: \"google\",\n id_token: idToken,\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(getState(), 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(getState(), 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(getState(), 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 */\n linkAccount: async (\n provider: string,\n credential: string,\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 = getState();\n const sessionToken = s.identity.getSessionToken();\n if (!sessionToken)\n throw new AmbaApiError(\n \"UNAUTHORIZED\",\n \"no active session — call signIn* first\",\n );\n return postAuth(s, P.authLink, {\n provider: providerTrimmed,\n token: credential,\n session_token: sessionToken,\n });\n },\n\n /**\n * Attach a verified phone to the current session's user without\n * minting new tokens. Same `user_id`, same session token. Server\n * returns `PHONE_ALREADY_LINKED` (409) with\n * `details.conflicting_user_id` if the phone is owned by another\n * user — the SDK rethrows as `AmbaApiError`.\n */\n linkSmsOtp: async (phone: string, code: string): Promise<User> => {\n requireE164Phone(phone);\n requireOtpCode(code);\n const s = getState();\n const sessionToken = s.identity.getSessionToken();\n if (!sessionToken)\n throw new AmbaApiError(\n \"UNAUTHORIZED\",\n \"no active session — call signIn* first\",\n );\n return postUserNoRotate(s, P.authLink, {\n provider: \"sms_otp\",\n phone,\n code,\n session_token: sessionToken,\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 const s = getState();\n const sessionToken = s.identity.getSessionToken();\n if (!sessionToken)\n throw new AmbaApiError(\n \"UNAUTHORIZED\",\n \"no active session — call signIn* first\",\n );\n return postUserNoRotate(s, P.authLink, {\n provider: \"email_otp\",\n email,\n code,\n session_token: sessionToken,\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`, same session token, no rotation). Server returns\n * `EMAIL_ALREADY_LINKED` (409) with `details.conflicting_user_id` if\n * the email is owned by another user — rethrown as `AmbaApiError`.\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 const s = getState();\n const sessionToken = s.identity.getSessionToken();\n if (!sessionToken)\n throw new AmbaApiError(\n \"UNAUTHORIZED\",\n \"no active session — call signIn* first\",\n );\n return postUserNoRotate(s, P.authLink, {\n provider: \"email_password\",\n email,\n password,\n session_token: sessionToken,\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 if (!s.identity.getSessionToken())\n throw new AmbaApiError(\n \"UNAUTHORIZED\",\n \"no active session — call signIn* first\",\n );\n const env = await s.http.get<IdentifiersEnvelope>(P.authIdentifiers);\n return env.data.identifiers;\n },\n\n signOut: async (rotateAnonymousId = false): Promise<void> => {\n const s = getState();\n if (s.identity.getSessionToken()) {\n try {\n await s.http.post(P.authLogout, {});\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 await s.identity.clearSession(rotateAnonymousId);\n void s.eventQueue?.flush();\n notifyAuthSubscribers(null);\n },\n refresh: async (): Promise<AuthResult> => {\n const s = getState();\n const refreshToken = s.identity.getRefreshToken();\n if (!refreshToken)\n throw new AmbaApiError(\n \"UNAUTHORIZED\",\n \"no refresh token available — call signIn* first\",\n );\n return postAuth(s, P.authRefresh, { refresh_token: refreshToken });\n },\n me: async (): Promise<User> => {\n const s = getState();\n if (!s.identity.getSessionToken())\n throw new AmbaApiError(\n \"UNAUTHORIZED\",\n \"no active session — call signIn* first\",\n );\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 if (!s.identity.isAuthenticated()) return null;\n const env = await s.http.get<Envelope<User>>(P.authMe);\n return snapshotSession(normalizeUserAuthProviders(env.data));\n },\n\n getAnonymousId: async (): Promise<string> => {\n return getState().identity.getAnonymousId();\n },\n\n onAuthStateChange: (\n cb: (session: Session | null) => 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 if (!s.identity.getSessionToken())\n throw new AmbaApiError(\n \"UNAUTHORIZED\",\n \"no active session — call signIn* first\",\n );\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: ((\n arg1: string | UserPatch,\n arg2?: UserPatch,\n ): Promise<User> => {\n const s = getState();\n if (!s.identity.getSessionToken())\n throw new AmbaApiError(\n \"UNAUTHORIZED\",\n \"no active session — call signIn* first\",\n );\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 return s.http\n .patch<Envelope<User>>(path, patch)\n .then((env) => 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 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 });\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 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 });\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 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 });\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 let response: Response;\n try {\n response = await (globalThis.fetch ?? fetch)(cdnUrl, {\n method: \"GET\",\n });\n } catch (err) {\n throw new AmbaApiError(\n \"NETWORK_ERROR\",\n `Network error: ${err instanceof Error ? err.message : String(err)}`,\n err,\n );\n }\n if (!response.ok) {\n const text = await response.text();\n const ct = response.headers.get(\"content-type\") ?? \"\";\n throw mapHttpError(response, text, ct.includes(\"application/json\"));\n }\n return response.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 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 let response: Response;\n try {\n response = await (globalThis.fetch ?? fetch)(url, {\n method,\n headers,\n body: requestBody,\n });\n } catch (err) {\n throw new AmbaApiError(\n \"NETWORK_ERROR\",\n `Amba.functions.invoke(\"${name}\") network error: ${err instanceof Error ? err.message : String(err)}`,\n err,\n );\n }\n\n const text = await response.text();\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 // 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 let response: Response;\n try {\n response = await (globalThis.fetch ?? fetch)(url, {\n method: \"GET\",\n headers,\n });\n } catch (err) {\n throw new AmbaApiError(\n \"NETWORK_ERROR\",\n `Network error: ${err instanceof Error ? err.message : String(err)}`,\n err,\n );\n }\n const text = await response.text();\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 return {\n version,\n values: parsed.data ?? {},\n };\n },\n // </generated:config>\n },\n\n flags: {\n // <generated:flags>\n fetch: async (): Promise<FlagAssignment[]> => {\n const env = await getState().http.get<Envelope<FlagAssignment[]>>(P.flags);\n return 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.\n */\n get: async (key: string): Promise<FlagAssignment | null> => {\n try {\n const env = await getState().http.get<Envelope<FlagAssignment>>(\n `${P.flags}/${encodePathSegment(key)}`,\n );\n return env.data;\n } catch (err) {\n if (err instanceof AmbaApiError && err.code === \"NOT_FOUND\") return null;\n throw err;\n }\n },\n // </generated:flags>\n },\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<InventoryItem> => {\n const env = await getState().http.post<Envelope<InventoryItem>>(\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 // apps/api/src/routes/client/friends.ts:20.\n const env = await getState().http.post<Envelope<Friendship>>(\n `${P.friends}/request`,\n { user_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 { baseUrl: getState().baseUrl, authHeaders: realtimeAuthHeaders },\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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"}
|