@palbase/web 7.3.8 → 7.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/{analytics-facade-BER0EyYT.d.ts → analytics-facade-But6gu2r.d.ts} +135 -16
  2. package/dist/{analytics-facade-CQXCQSoF.d.cts → analytics-facade-f8IVujJW.d.cts} +135 -16
  3. package/dist/{chunk-6DGCUHSO.js → chunk-4WMJTUDN.js} +28 -19
  4. package/dist/chunk-4WMJTUDN.js.map +1 -0
  5. package/dist/{chunk-CFDU23TB.js → chunk-G56VGUYZ.js} +3 -3
  6. package/dist/chunk-G56VGUYZ.js.map +1 -0
  7. package/dist/{chunk-HT5SNA5H.js → chunk-VHUCJBBQ.js} +2 -2
  8. package/dist/chunk-VHUCJBBQ.js.map +1 -0
  9. package/dist/{chunk-A42CPPQ7.js → chunk-WXBOPPHU.js} +502 -38
  10. package/dist/chunk-WXBOPPHU.js.map +1 -0
  11. package/dist/gen/cli.cjs +2 -2
  12. package/dist/gen/cli.cjs.map +1 -1
  13. package/dist/gen/cli.js +2 -2
  14. package/dist/gen/cli.js.map +1 -1
  15. package/dist/index.cjs +514 -50
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +3 -3
  18. package/dist/index.d.ts +3 -3
  19. package/dist/index.js +3 -3
  20. package/dist/internal.cjs +527 -54
  21. package/dist/internal.cjs.map +1 -1
  22. package/dist/internal.d.cts +24 -4
  23. package/dist/internal.d.ts +24 -4
  24. package/dist/internal.js +3 -3
  25. package/dist/next/client.cjs +521 -51
  26. package/dist/next/client.cjs.map +1 -1
  27. package/dist/next/client.js +3 -3
  28. package/dist/next/index.cjs +528 -55
  29. package/dist/next/index.cjs.map +1 -1
  30. package/dist/next/index.d.cts +2 -2
  31. package/dist/next/index.d.ts +2 -2
  32. package/dist/next/index.js +4 -4
  33. package/dist/next/index.js.map +1 -1
  34. package/dist/next/proxy.cjs +1 -1
  35. package/dist/next/proxy.cjs.map +1 -1
  36. package/dist/next/proxy.js +3 -3
  37. package/dist/{pb-WAQvwCfM.d.cts → pb-9MMDNQ9p.d.cts} +6 -1
  38. package/dist/{pb-BRlmBIAm.d.ts → pb-DRmdWQzG.d.ts} +6 -1
  39. package/dist/react/index.cjs +22 -19
  40. package/dist/react/index.cjs.map +1 -1
  41. package/dist/react/index.d.cts +1 -1
  42. package/dist/react/index.d.ts +1 -1
  43. package/dist/react/index.js +3 -3
  44. package/package.json +1 -1
  45. package/dist/chunk-6DGCUHSO.js.map +0 -1
  46. package/dist/chunk-A42CPPQ7.js.map +0 -1
  47. package/dist/chunk-CFDU23TB.js.map +0 -1
  48. package/dist/chunk-HT5SNA5H.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/internal.ts","../../core/src/config.ts","../../core/src/errors.ts","../../core/src/pow.ts","../../core/src/platform.ts","../../core/src/http.ts","../../core/src/token.ts","../src/errors.ts","../src/perf/url-redactor.ts","../src/request.ts","../src/state.ts","../src/namespaces.ts","../../modules/auth/src/auth-client.ts","../../modules/auth/src/device-client.ts","../src/analytics-facade.ts","../src/api-key.ts","../src/auth-wire.ts","../src/passkey.ts","../src/auth-facade.ts","../src/calls/facade.ts","../../modules/flags/src/admin-client.ts","../../modules/flags/src/flags-client.ts","../../modules/flags/src/pooled-flags.ts","../src/flags-facade.ts","../src/messaging/deadline-calculator.ts","../src/messaging/delete-fold.ts","../src/messaging/edit-fold.ts","../src/messaging/util.ts","../src/messaging/wire.ts","../src/messaging/keydir.ts","../src/messaging/group-messaging.ts","../src/messaging/mention-ranges.ts","../src/messaging/reaction-fold.ts","../src/messaging/timer-fold.ts","../src/messaging/chat.ts","../src/messaging/delivery-source.ts","../src/messaging/disappearing.ts","../src/messaging/history.ts","../src/messaging/mention-elevation.ts","../src/messaging/wasm/pkg/palbe_mls_bg.js","../src/messaging/wasm/pkg/snippets/mls-rs-core-f99cdecbb456b09c/inline0.js","../src/messaging/wasm/loader.ts","../src/messaging/wasm/bridge.ts","../src/messaging/mls-engine.ts","../src/messaging/registry.ts","../src/messaging/storage.ts","../src/messaging/suppression.ts","../src/messaging/coordinator.ts","../src/messaging/facade.ts","../src/perf/app-start.ts","../src/perf/perf-config-client.ts","../src/perf/fetch-swizzle.ts","../src/perf/offline-queue.ts","../src/perf/perf-state.ts","../src/perf/perf-wire.ts","../src/perf/perf-facade.ts","../src/perf/web-vitals.ts","../src/runtime-metadata.ts","../src/realtime/anon-token.ts","../src/realtime/frames.ts","../src/realtime/connection.ts","../src/realtime/facade.ts","../src/storage.ts","../src/version.ts","../src/runtime.ts","../src/call.ts","../src/upload.ts","../src/pb.ts"],"sourcesContent":["import type { PalbeConfig } from './config.js';\nimport { BackendError } from './errors.js';\nimport { __registerNamespaces, resetNamespaces } from './namespaces.js';\nimport { buildRuntime, type PalbeRuntime } from './runtime.js';\nimport { palbeState } from './state.js';\n\n// Runtime lives in a process-global slot (see state.ts) so every build copy\n// (ESM/CJS, palbe + palbe/internal entries) converges on one singleton.\n// This is the BROWSER path (one runtime per tab). Server-side per-request\n// runtimes arrive with palbe/next (P3); do not sign in from shared server\n// code against this singleton.\n\n/**\n * Called by the generated palbe.gen.ts on import. REPLACES the runtime\n * (re-runs storage hydration) — safe to call repeatedly, e.g. watch-mode\n * regen. The namespace registry intentionally survives re-configure: the\n * gen file re-registers its static descriptors right after.\n */\nexport function __configure(config: PalbeConfig): void {\n const state = palbeState();\n // Destroy the old realtime facade if it was constructed — closes the socket\n // and clears heartbeat timers so they do not outlive the replaced runtime.\n state.runtime?.destroyRealtime();\n state.runtime = buildRuntime(config);\n // Fire after the runtime is in place so listeners that read pb.* immediately\n // (e.g. useSyncExternalStore getSnapshot) see the new runtime.\n for (const cb of state.configuredListeners) {\n try {\n cb();\n } catch {\n // consumer errors must not abort delivery to other listeners\n }\n }\n}\n\n/**\n * Register a callback that fires synchronously after every successful\n * `__configure` call (including re-configure / watch-mode regen).\n *\n * Returns an unsubscribe function. Safe to call before the first `__configure`.\n *\n * @internal — consumed by `palbe/react`; not part of the public `palbe` surface.\n */\nexport function onConfigured(cb: () => void): () => void {\n const state = palbeState();\n state.configuredListeners.add(cb);\n return () => state.configuredListeners.delete(cb);\n}\n\n/** @internal */\nexport function getRuntime(): PalbeRuntime {\n const rt = palbeState().runtime;\n if (!rt) throw BackendError.notConfigured();\n return rt;\n}\n\n/** Test-only: drop runtime + registered namespaces. */\nexport function __reset(): void {\n const state = palbeState();\n state.runtime?.destroyRealtime();\n state.runtime = null;\n state.configuredListeners.clear();\n resetNamespaces();\n}\n\nexport type { EndpointDescriptor, NamespaceTree } from './namespaces.js';\nexport type { PB } from './pb.js';\nexport { createBoundClient } from './pb.js';\nexport type { PalbeConfig, PalbeRuntime };\n// Server-adapter seams (palbe/next): per-request runtime + bound client.\nexport { __registerNamespaces, buildRuntime };\n","import type { HttpClient } from './http.js';\nimport type { ProjectConfig } from './types.js';\n\nconst CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes\n\nexport class ConfigFetcher {\n protected readonly httpClient: HttpClient;\n private cachedConfig: ProjectConfig | null = null;\n private cacheTimestamp = 0;\n\n constructor(httpClient: HttpClient) {\n this.httpClient = httpClient;\n }\n\n async getConfig(): Promise<ProjectConfig | null> {\n const now = Date.now();\n\n if (this.cachedConfig && now - this.cacheTimestamp < CACHE_TTL_MS) {\n return this.cachedConfig;\n }\n\n try {\n const response = await this.httpClient.request<ProjectConfig>('GET', '/v1/config');\n\n if (response.error || !response.data) {\n return null;\n }\n\n this.cachedConfig = response.data;\n this.cacheTimestamp = now;\n\n return this.cachedConfig;\n } catch {\n return null;\n }\n }\n}\n","export class PalbaseError extends Error {\n readonly code: string;\n readonly status: number;\n readonly details?: unknown;\n\n constructor(code: string, message: string, status: number, details?: unknown) {\n super(message);\n this.name = 'PalbaseError';\n this.code = code;\n this.status = status;\n this.details = details;\n }\n}\n","// Proof-of-work: the bot gate in front of /auth/signup and /auth/login.\n//\n// The server answers an unsolved request with 403 and a challenge in the body:\n//\n// { \"error\": \"pow_required\", \"challenge\": { \"id\", \"prefix\", \"difficulty\" } }\n//\n// A client finds any nonce whose SHA-256(prefix + nonce) begins with\n// `difficulty` zero bits, then repeats the request carrying the id and nonce as\n// headers. The work is the point: a person signing up pays it once and does not\n// notice, a script signing up ten thousand times pays it ten thousand times.\n//\n// # Why this lives in core, and not one layer up\n//\n// Until 2026-08-14 nothing shipped could solve it: the gate was written with the\n// server and its own integration harness, and every real client sent requests\n// without the headers and got 403. It was then solved in @palbase/web's own\n// request path — which covers `pb.call` and the module facades and NOT\n// `pb.auth.*`, because those go through @palbase/auth's client and from there\n// into core's HttpClient. So the fix landed everywhere except the two endpoints\n// the gate actually guards, and `npm i @palbase/web` still could not sign a\n// person in. Measured 2026-08-18 against a real stack, on the published 7.3.0.\n//\n// The lesson is where a retry belongs: at the layer that ISSUES the request.\n// Core owns fetch for every client in this repo, so core owns the challenge.\n//\n// WebCrypto rather than a hashing dependency: `crypto.subtle` is present in\n// browsers and in Node 18+, which is the same floor the rest of the SDK sets.\n// Measured at the server's default difficulty of 16: ~330ms, ~65k digests.\n\n/** The challenge a `pow_required` response carries. */\nexport interface PowChallenge {\n id: string;\n prefix: string;\n difficulty: number;\n}\n\n/** Header names the retry must carry. Mirrors the server's constants. */\nexport const POW_CHALLENGE_ID_HEADER = 'X-PoW-Challenge-ID';\nexport const POW_NONCE_HEADER = 'X-PoW-Nonce';\n\n/**\n * Reads a challenge out of an error envelope, or returns null when the envelope\n * is not a `pow_required` one.\n *\n * The whole wire envelope is stored on the error, so the challenge arrives\n * without the HTTP layer having to know about proof-of-work at all.\n */\nexport function asPowChallenge(details: unknown): PowChallenge | null {\n if (typeof details !== 'object' || details === null) return null;\n const env = details as Record<string, unknown>;\n if (env.error !== 'pow_required') return null;\n const c = env.challenge;\n if (typeof c !== 'object' || c === null) return null;\n const { id, prefix, difficulty } = c as Record<string, unknown>;\n if (typeof id !== 'string' || typeof prefix !== 'string') return null;\n if (typeof difficulty !== 'number' || !Number.isInteger(difficulty) || difficulty < 0) return null;\n return { id, prefix, difficulty };\n}\n\nconst encoder = new TextEncoder();\n\n/**\n * One SHA-256, by the fastest route this runtime offers.\n *\n * Awaiting `crypto.subtle.digest` once per nonce is what made this expensive,\n * and the cost is the await rather than the hashing. Measured on one machine,\n * 200k digests of a 40-byte input:\n *\n *\tawaited crypto.subtle.digest 105,597 digests/s\n *\tsync node:crypto createHash 1,324,503 digests/s — 12.5x\n *\n * That is the difference between difficulty 24 taking 159 seconds and taking\n * 13. Node, Bun and Deno all have the sync one; a browser has only WebCrypto,\n * and there it stays async.\n *\n * The specifier is assembled at runtime so a browser bundler does not try to\n * resolve `node:crypto` and fail the build over a branch that never runs there.\n */\ntype Hasher = (input: string) => Uint8Array | Promise<Uint8Array>;\n\nlet hasher: Hasher | null = null;\n\nasync function digester(): Promise<Hasher> {\n if (hasher) return hasher;\n // Read off globalThis with an inline shape rather than by naming `process`,\n // which needs @types/node — a dependency this package does not have and should\n // not grow for one branch. It typechecked locally only because those types\n // were hoisted into node_modules by a sibling package; the publish workflow's\n // clean checkout is what said so, which is exactly what it is for.\n const runtime = globalThis as {\n process?: { versions?: { node?: string; bun?: string } };\n };\n const nodeish =\n runtime.process?.versions?.node !== undefined ||\n runtime.process?.versions?.bun !== undefined;\n if (nodeish) {\n try {\n const mod = (await import(/* @vite-ignore */ `${'node:'}crypto`)) as {\n createHash?: (alg: string) => { update(s: string): { digest(): Uint8Array } };\n };\n if (typeof mod.createHash === 'function') {\n const createHash = mod.createHash;\n hasher = (input: string) => new Uint8Array(createHash('sha256').update(input).digest());\n return hasher;\n }\n } catch {\n // No node:crypto here. WebCrypto below is not a fallback in the apologetic\n // sense — it is the only hash a browser has, and it is correct.\n }\n }\n hasher = async (input: string) =>\n new Uint8Array(await crypto.subtle.digest('SHA-256', encoder.encode(input)));\n return hasher;\n}\n\n/** Monotonic where it exists, wall-clock where it does not. */\nconst now = (): number =>\n typeof performance !== 'undefined' && typeof performance.now === 'function'\n ? performance.now()\n : Date.now();\n\n/** Counts leading zero bits, stopping at the first byte that has a one. */\nfunction leadingZeroBits(hash: Uint8Array): number {\n let bits = 0;\n for (const byte of hash) {\n if (byte === 0) {\n bits += 8;\n continue;\n }\n // clz32 counts across 32 bits; a byte occupies the low 8, so the first 24\n // are always zero and get subtracted back off.\n return bits + Math.clz32(byte) - 24;\n }\n return bits;\n}\n\n/**\n * The hardest challenge this client will attempt.\n *\n * Not a taste: it is the server's own ceiling. palauth maps a risk score to a\n * difficulty and its worst case is 24 (`DifficultyForRisk`, bot/pow.go:156-166).\n * Anything above that cannot have come from a stack behaving as designed, and\n * the cost of humouring it falls entirely on this side — each step up DOUBLES\n * the work, so difficulty 30 is sixty-four times a legitimate worst case and, on\n * the web, sixty-four times a frozen main thread. Refused immediately, by name.\n */\nexport const MAX_POW_DIFFICULTY = 24;\n\n/**\n * Finds a nonce satisfying the challenge and returns the headers a retry needs.\n *\n * THE BUDGET SCALES WITH THE CHALLENGE, and the first version of this did not.\n * It bounded the search at a flat `1 << 24` — which is not a generous bound for\n * difficulty 24, it is the EXPECTED number of attempts. Finding a nonce is a\n * geometric process: the chance of needing more than 2^d attempts is 1/e, so a\n * flat 2^24 would have failed roughly 37% of legitimate hardest-risk challenges,\n * and failed them for precisely the users the gate exists to slow down — who\n * would have been unable to sign in at all rather than made to wait.\n *\n * Eight times expected puts that at e^-8, about three in ten thousand, while\n * leaving the common case (the server's default 16, and 12 for an unremarkable\n * caller) exactly as cheap as it was.\n *\n * `powBudget` is exported and separate so the RELATIONSHIP can be asserted\n * directly. A test that only watches a cheap challenge succeed cannot tell this\n * budget from the flat one it replaced — measured: reinstating `1 << 24` left\n * such a test green.\n */\nexport function powBudget(difficulty: number): number {\n return 8 * 2 ** difficulty;\n}\n\n/**\n * The longest a solve may be ALLOWED to take, and the difference from a\n * deadline is the whole point.\n *\n * The first version of this was a flat 120s deadline, and it was measured to be\n * worse than the flat iteration budget it was meant to backstop: at ~105k\n * digests/s, difficulty 24 EXPECTS 159 seconds, so a 120s clock killed the\n * majority of legitimate hardest-risk solves — reintroducing, larger, exactly\n * the class of defect that replacing `1 << 24` had removed. Guessing a number\n * for an unknown machine cannot work: the same difficulty is 13 seconds on a\n * runtime with a sync hasher and 159 on one without.\n *\n * So the machine is MEASURED, and the decision moves to the front. A short\n * calibration gives the rate this process actually hashes at; if the whole\n * iteration budget cannot fit in this window at that rate, the solve is refused\n * IMMEDIATELY, naming the numbers. A caller then learns in milliseconds that\n * this difficulty is unpayable here, instead of after two minutes of work\n * thrown away.\n *\n * What remains after that is a guarantee rather than a gamble: a solve that\n * starts can always finish inside its budget, so the only failure left is the\n * budget's own e^-8.\n */\nexport const POW_TIME_BUDGET_MS = 120_000;\n\n/**\n * The window the rate is measured over, and the warm-up it deliberately skips.\n *\n * All of these are REAL attempts — the search starts at nonce 0 and never\n * restarts — so calibration costs nothing but the reading. The first 1024 are\n * excluded from the timing because they include this loop's own JIT warm-up:\n * measured, timing from zero reported 747k digests/s on a machine whose steady\n * rate is 1.32M, and the decision below would have refused a difficulty this\n * machine can pay in half the allowance.\n */\nconst CALIBRATION_WARMUP = 1024;\nconst CALIBRATION_END = 9216;\n\nexport async function solvePowChallenge(\n challenge: PowChallenge,\n maxIterations = powBudget(challenge.difficulty),\n // The caller's AbortSignal, honoured INSIDE the loop rather than only around\n // the fetch it precedes — for the callers that have one. `pb.auth.signIn` does\n // NOT: it reaches the network through @palbase/auth's client, which takes\n // credentials and nothing else. So it is the extra a caller can opt into, and\n // POW_TIME_BUDGET_MS below is what actually bounds the work.\n signal?: AbortSignal,\n): Promise<Record<string, string>> {\n if (challenge.difficulty > MAX_POW_DIFFICULTY) {\n throw new Error(\n `proof-of-work: refusing difficulty ${challenge.difficulty}; this client attempts at most ${MAX_POW_DIFFICULTY}, which is the highest a Palbase stack issues`,\n );\n }\n\n const digest = await digester();\n let warmedAt = 0;\n let calibrated = false;\n // Armed by the calibration below, never before it: until the rate is known\n // there is no honest number to put here.\n let deadline = Number.POSITIVE_INFINITY;\n\n for (let nonce = 0; nonce < maxIterations; nonce++) {\n // Checked in batches: reading them is cheap but not free, and a\n // 1024-digest granularity bounds the delay at a few milliseconds.\n if ((nonce & 1023) === 0) {\n if (signal?.aborted) {\n throw new DOMException('proof-of-work solve aborted', 'AbortError');\n }\n if (now() > deadline) {\n throw new Error(\n `proof-of-work: gave up on difficulty ${challenge.difficulty} after ${POW_TIME_BUDGET_MS / 1000}s ` +\n `and ${nonce.toLocaleString()} attempts — the tail this run drew is longer than the allowance`,\n );\n }\n }\n\n // THE DECISION, TAKEN ONCE AND TAKEN EARLY.\n //\n // After CALIBRATION_DIGESTS real attempts the rate of THIS process is\n // known, so the question \"can this machine pay this difficulty\" has an\n // answer instead of an assumption. If the whole budget cannot fit in the\n // time budget, refuse here — milliseconds in, with the numbers — rather\n // than spend two minutes and throw them away. If it fits, everything after\n // this point is guaranteed to finish inside the window, so the only\n // remaining failure is the budget's own e^-8.\n if (nonce === CALIBRATION_WARMUP) {\n warmedAt = now();\n }\n if (!calibrated && nonce === CALIBRATION_END) {\n calibrated = true;\n const elapsed = Math.max(now() - warmedAt, 0.001);\n const rate = (CALIBRATION_END - CALIBRATION_WARMUP) / (elapsed / 1000);\n // EXPECTED, not worst case, and the difference is the whole judgement.\n //\n // Finding a nonce is geometric: 2^difficulty attempts on average, with a\n // long tail the 8x budget covers. Refusing because the TAIL will not fit\n // would turn away work whose expected cost is seventeen seconds — measured\n // exactly that on this machine at difficulty 24. Refusing on the EXPECTED\n // cost turns away only what is genuinely unpayable here, and what it lets\n // through is then cut by the clock with probability e^-(budget/expected):\n // at 120s against a 17s expectation that is one run in a thousand, and at\n // difficulty 20 on a browser it is one in a hundred and fifty thousand.\n const expectedMs = (2 ** challenge.difficulty / rate) * 1000;\n if (expectedMs > POW_TIME_BUDGET_MS) {\n throw new Error(\n `proof-of-work: difficulty ${challenge.difficulty} needs about ${Math.round(expectedMs / 1000)}s here ` +\n `(${Math.round(rate).toLocaleString()} digests/s) and this client allows ${POW_TIME_BUDGET_MS / 1000}s; ` +\n `refusing before spending the time rather than after`,\n );\n }\n deadline = now() + (POW_TIME_BUDGET_MS - (now() - warmedAt));\n }\n\n const hash = await digest(challenge.prefix + nonce);\n if (leadingZeroBits(hash) >= challenge.difficulty) {\n return {\n [POW_CHALLENGE_ID_HEADER]: challenge.id,\n [POW_NONCE_HEADER]: String(nonce),\n };\n }\n }\n throw new Error(\n `proof-of-work: no nonce found for difficulty ${challenge.difficulty} within ${maxIterations} attempts`,\n );\n}\n","export type Platform = 'browser' | 'node' | 'react-native' | 'deno' | 'bun';\n\ndeclare const Deno: unknown;\ndeclare const process: { versions: Record<string, string> } | undefined;\n\nexport function detectPlatform(): Platform {\n if (typeof Deno !== 'undefined') {\n return 'deno';\n }\n\n if (process?.versions) {\n if ('bun' in process.versions) {\n return 'bun';\n }\n if ('node' in process.versions) {\n return 'node';\n }\n }\n\n if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {\n return 'react-native';\n }\n\n return 'browser';\n}\n\n/**\n * The platform word this SDK puts on the wire (`X-Platform`), which the server\n * reads to target flags and to label telemetry.\n *\n * It is NOT `detectPlatform()`'s value verbatim: that reports the JS host\n * (\"browser\"), while the wire wants the platform. iOS sends \"ios\", not the name\n * of its runtime, and a condition author writes `client.platform == 'web'` —\n * the word every other flag vendor uses too. Server hosts keep their own names,\n * where the distinction is the useful part.\n */\nexport function wirePlatform(): string {\n const host = detectPlatform();\n return host === 'browser' ? 'web' : host;\n}\n","import { PalbaseError } from './errors.js';\nimport { asPowChallenge, solvePowChallenge } from './pow.js';\nimport { wirePlatform } from './platform.js';\nimport type { TokenManager } from './token.js';\nimport type { HttpClientOptions, PalbaseResponse, RequestOptions } from './types.js';\n\n/**\n * Default production host. Dev / staging / local callers override via\n * `options.url`. Apex-style routing is the only supported production path;\n * Kong resolves Environment identity from the API key.\n */\nconst PALBASE_DEFAULT_HOST = 'api.palbase.studio';\n\n/**\n * Parse the Environment ref from a Palbase API key.\n *\n * Canonical shape: `pb_{environment_ref}_c{random}`, where the Environment\n * ref is 4-24 lowercase ASCII alphanumeric characters and random is 20 base62\n * chars. See\n * docs/MODULE_HEADER_CONTRACT.md §\"API key format\" (palbase repo) for\n * the full spec.\n *\n * Returns the Environment ref on match; `null` otherwise.\n */\nconst API_KEY_RE = /^pb_([a-z0-9]{4,24})_c[A-Za-z0-9]{20}$/;\n\nfunction parseEnvironmentRef(apiKey: string): string | null {\n return API_KEY_RE.exec(apiKey)?.[1] ?? null;\n}\nconst MAX_RETRIES = 3;\nconst INITIAL_BACKOFF_MS = 200;\n/**\n * Upper bound on a single 429 retry sleep. A server may return a long\n * Retry-After (a locked account can send minutes/hours); honoring it verbatim\n * would HANG the request for that whole window. Cap each retry at 10s — after\n * MAX_RETRIES the 429 envelope surfaces to the caller (fail fast, don't sleep\n * minutes). The clamp never skips a retry; it only bounds how long each waits.\n */\nconst MAX_RETRY_DELAY_MS = 10_000;\n\n/**\n * Request interceptor. Runs before every HTTP request.\n * Can modify headers, body, or reject the request.\n */\nexport type RequestInterceptor = (request: {\n headers: Record<string, string>;\n method: string;\n path: string;\n}) => void | Promise<void>;\n\nexport class HttpClient {\n protected readonly apiKey: string;\n protected readonly options?: HttpClientOptions;\n\n tokenManager: TokenManager | null = null;\n\n /**\n * Admin JWT used for platform admin endpoints (/admin/*).\n * When set, takes precedence over tokenManager access token in the\n * Authorization header.\n */\n adminToken: string | null = null;\n\n private readonly interceptors: RequestInterceptor[] = [];\n\n constructor(apiKey: string, options?: HttpClientOptions) {\n this.apiKey = apiKey;\n this.options = options;\n }\n\n /** Set (or clear) the admin JWT used on admin endpoints. */\n setAdminToken(token: string | null): void {\n this.adminToken = token;\n }\n\n /**\n * Create a scoped HttpClient that adds the given extra headers to every\n * request. The returned client shares the admin token and token manager\n * with the parent at runtime — later changes on the parent propagate to\n * the scope and vice versa.\n *\n * Typical use: adding an Environment-routing header for an admin call.\n */\n withHeaders(extra: Record<string, string>): HttpClient {\n const mergedHeaders = { ...(this.options?.headers ?? {}), ...extra };\n\n const scoped: HttpClient = new HttpClient(this.apiKey, {\n ...this.options,\n headers: mergedHeaders,\n });\n scoped.tokenManager = this.tokenManager;\n // Delegate adminToken reads + writes to the parent so the scope always\n // sees the latest token, and setAdminToken on the scope affects the parent.\n Object.defineProperty(scoped, 'adminToken', {\n get: () => this.adminToken,\n set: (v: string | null) => {\n this.adminToken = v;\n },\n configurable: true,\n });\n return scoped;\n }\n\n /** Add a request interceptor. Runs before every request. */\n addInterceptor(interceptor: RequestInterceptor): void {\n this.interceptors.push(interceptor);\n }\n\n async request<T>(\n method: string,\n path: string,\n options?: RequestOptions,\n ): Promise<PalbaseResponse<T>> {\n // If token is expired and refresh is available, refresh before making the request\n if (\n this.tokenManager?.isExpired() &&\n this.tokenManager.getRefreshToken() &&\n this.tokenManager.refreshFunction\n ) {\n try {\n await this.tokenManager.refreshSession();\n } catch (e) {\n const status = e instanceof PalbaseError ? e.status : 0;\n if (status === 400 || status === 401 || status === 403) {\n // Terminal: the refresh token is dead (revoked/expired/forbidden).\n // Clear the session (listeners persist the sign-out) and proceed\n // unauthenticated — the endpoint will 401 into the normal error\n // envelope instead of bricking every subsequent call including\n // the recovery sign-in.\n this.tokenManager.clearSession();\n } else {\n throw e; // network/5xx: transient, stay loud\n }\n }\n }\n\n return this.executeWithRetry<T>(method, path, options, 0);\n }\n\n private getBaseUrl(): string {\n // Explicit URL always wins (local dev, staging, test rigs).\n if (this.options?.url) {\n return this.options.url;\n }\n\n // Validate the key shape up front so apex-routed callers still\n // fail loud on a malformed key instead of hitting the gateway\n // with bad credentials.\n if (this.apiKey && parseEnvironmentRef(this.apiKey) === null) {\n throw new PalbaseError(\n 'invalid_api_key',\n 'Invalid API key format. Expected pb_{environment_ref}_c{20_base62_chars}. For dev/staging pass `url: \"https://api.dev.palbase.studio\"` via options.',\n 0,\n );\n }\n\n return `https://${PALBASE_DEFAULT_HOST}`;\n }\n\n private buildHeaders(options?: RequestOptions): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n // Client identity, the web counterpart of the iOS SDK's\n // ClientInfo.augment(). The server reads these to resolve flag targeting\n // conditions and to label telemetry, so an app declares nothing and calls\n // nothing — whatever the SDK can know, it sends.\n 'X-Platform': wirePlatform(),\n };\n // The host app's own version is not knowable on the web (no bundle to read\n // it from), so it is opt-in; when given it fills the same header iOS fills\n // from CFBundleShortVersionString.\n const appVersion = this.options?.appVersion?.trim();\n if (appVersion) {\n headers['X-Palbase-Client-Version'] = appVersion;\n }\n\n // Palbase Environment keys live in the `apikey` header — never in\n // `Authorization` — because Kong's key-auth resolves them on that\n // header and the gateway's pre-function plugin stamps the downstream\n // identity.\n const effectiveKey = this.apiKey;\n if (effectiveKey) {\n headers['apikey'] = effectiveKey;\n }\n\n // User session token, if any. Kong's pre-function plugin strips\n // Authorization on /v1/* routes anyway (PostgREST has no JWT\n // secret and would crash on a Bearer it can't decode), but\n // sending it preserves the contract for /auth/* endpoints that\n // do consume the bearer (e.g. session refresh).\n const token = this.tokenManager?.getAccessToken();\n if (token) {\n headers['Authorization'] = `Bearer ${token}`;\n }\n\n // adminToken (platform admin JWT) takes precedence — used by the\n // @palbase/admin internal flows that hit /admin/* routes; those\n // routes verify the bearer themselves and aren't subject to the\n // /v1/* Authorization-strip rule.\n if (this.adminToken) {\n headers['Authorization'] = `Bearer ${this.adminToken}`;\n }\n\n // Merge global custom headers\n if (this.options?.headers) {\n Object.assign(headers, this.options.headers);\n }\n\n // Merge per-request headers\n if (options?.headers) {\n Object.assign(headers, options.headers);\n }\n\n return headers;\n }\n\n private async executeWithRetry<T>(\n method: string,\n path: string,\n options: RequestOptions | undefined,\n attempt: number,\n // Headers a PREVIOUS attempt earned and this one has to carry. Today that\n // is only the solved proof-of-work pair; it is a parameter rather than a\n // field because it belongs to one request's second try, and a field would\n // leak it onto every later call made through this client.\n earned?: Record<string, string>,\n ): Promise<PalbaseResponse<T>> {\n const url = `${this.getBaseUrl()}${path}`;\n const headers = { ...this.buildHeaders(options), ...earned };\n\n // Run interceptors\n for (const interceptor of this.interceptors) {\n await interceptor({ headers, method, path });\n }\n\n const fetchOptions: RequestInit = {\n method,\n headers,\n signal: options?.signal,\n };\n\n if (options?.body !== undefined) {\n fetchOptions.body = JSON.stringify(options.body);\n }\n\n let response: Response;\n try {\n response = await fetch(url, fetchOptions);\n } catch (error) {\n // Network error — retry with backoff\n if (attempt < MAX_RETRIES - 1) {\n const backoff = INITIAL_BACKOFF_MS * 2 ** attempt;\n await this.delay(backoff);\n // WITHOUT `earned`, and that is the whole point of this line.\n //\n // A network error means the response was lost, not that the request\n // was. If it reached the server, the challenge is already SPENT —\n // palauth's VerifyChallenge reads and deletes in one step\n // (bot/pow.go:96-104), deliberately, because a proof presented twice is\n // not proof. Replaying the nonce would then answer `pow_invalid`, and\n // the one-solve guard below would refuse to try again: a request one\n // fresh solve away from succeeding, failed. Dropping it costs nothing\n // in the other case — if the server never saw the request, a fresh\n // challenge works exactly as well as the old one.\n return this.executeWithRetry<T>(method, path, options, attempt + 1);\n }\n\n // All retries exhausted — throw PalbaseError\n throw new PalbaseError(\n 'network_error',\n error instanceof Error ? error.message : 'Network request failed',\n 0,\n );\n }\n\n // Handle 429 Too Many Requests — retry with Retry-After or backoff;\n // if retries exhausted, fall through to normal error response handling below\n if (response.status === 429) {\n if (attempt < MAX_RETRIES - 1) {\n const retryAfter = response.headers.get('Retry-After');\n const parsed = retryAfter ? Number.parseInt(retryAfter, 10) : Number.NaN;\n // Clamp the server-requested wait: a long Retry-After (locked account)\n // must not hang the request — cap each sleep, exhaust MAX_RETRIES, then\n // fall through to surface the 429 envelope below.\n const delayMs = Number.isNaN(parsed)\n ? INITIAL_BACKOFF_MS * 2 ** attempt\n : Math.min(parsed * 1000, MAX_RETRY_DELAY_MS);\n await this.delay(delayMs);\n // WITH `earned`, unlike the network path above: a 429 is a refusal the\n // server issued INSTEAD of doing the work, so the challenge was never\n // consumed. The edge's rate limiter answers before palsvc, and on the\n // auth routes palauth's own limiter runs BEFORE the proof-of-work\n // middleware (auth/internal/server/server.go: rl.LoginByIP, then powMW).\n return this.executeWithRetry<T>(method, path, options, attempt + 1, earned);\n }\n }\n\n // Parse response body\n let data: T | null = null;\n let errorBody: { error?: string; error_description?: string; status?: number } | undefined;\n\n // HEAD responses have no body by spec — skip parsing.\n const contentType = response.headers.get('Content-Type');\n if (method !== 'HEAD' && contentType?.includes('json')) {\n const body = (await response.json()) as Record<string, unknown>;\n if (response.ok) {\n data = body as T;\n } else {\n errorBody = body as typeof errorBody;\n }\n }\n\n // Proof-of-work: /auth/signup and /auth/token sit behind a bot gate that\n // answers an unsolved request with 403 and the challenge in the body. Solve\n // it and repeat the request carrying the two headers; the caller never\n // learns the gate is there.\n //\n // HERE, in core, because this is the layer that issues the request for every\n // client in the repo — @palbase/auth's sign-in, @palbase/web's facades, the\n // server SDK. The same retry lived one layer up in @palbase/web until\n // 2026-08-18 and covered everything EXCEPT `pb.auth.*`, which reaches the\n // network through this method; so the gate stayed unsatisfiable on exactly\n // the two endpoints it guards.\n //\n // ONE retry, and only when the body really carries a challenge: `earned`\n // being set already means this IS the second try. A 403 that says\n // pow_required without a challenge is a server the client cannot satisfy,\n // and looping on it would turn a broken gate into a hang.\n if (response.status === 403 && !earned) {\n const challenge = asPowChallenge(errorBody);\n if (challenge) {\n return this.executeWithRetry<T>(\n method,\n path,\n options,\n attempt,\n await solvePowChallenge(challenge, undefined, options?.signal),\n );\n }\n }\n\n if (!response.ok) {\n return {\n data: null,\n error: new PalbaseError(\n errorBody?.error ?? 'unknown_error',\n errorBody?.error_description ?? response.statusText,\n response.status,\n errorBody,\n ),\n status: response.status,\n };\n }\n\n // Parse PostgREST Content-Range for count queries (e.g. \"0-9/42\" or \"*/42\").\n const contentRange = response.headers.get('Content-Range');\n let count: number | undefined;\n if (contentRange) {\n const slash = contentRange.lastIndexOf('/');\n if (slash >= 0) {\n const totalPart = contentRange.slice(slash + 1);\n if (totalPart !== '*') {\n const parsed = Number.parseInt(totalPart, 10);\n if (!Number.isNaN(parsed)) {\n count = parsed;\n }\n }\n }\n }\n\n return {\n data,\n error: null,\n status: response.status,\n ...(count !== undefined ? { count } : {}),\n };\n }\n\n private delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n","import type { AuthStateCallback, Session, Unsubscribe } from './types.js';\n\nexport class TokenManager {\n private session: Session | null = null;\n private listeners: Set<AuthStateCallback> = new Set();\n private refreshPromise: Promise<void> | null = null;\n private refreshing = false;\n\n refreshFunction: ((refreshToken: string) => Promise<Session>) | null = null;\n\n setSession(session: Session): void {\n this.session = session;\n this.notify('SESSION_SET', session);\n }\n\n getAccessToken(): string | null {\n return this.session?.accessToken ?? null;\n }\n\n getRefreshToken(): string | null {\n return this.session?.refreshToken ?? null;\n }\n\n clearSession(): void {\n this.session = null;\n this.notify('SESSION_CLEARED', null);\n }\n\n isExpired(): boolean {\n if (!this.session) return true;\n return Date.now() >= this.session.expiresAt;\n }\n\n async refreshSession(): Promise<void> {\n if (!this.session?.refreshToken || !this.refreshFunction) {\n return;\n }\n\n // Collapse concurrent refresh calls into a single request\n if (this.refreshPromise) {\n return this.refreshPromise;\n }\n\n // Re-entrancy guard: the wired refreshFunction issues its own HTTP request\n // (POST /auth/token/refresh) through HttpClient, whose pre-flight calls\n // refreshSession() again SYNCHRONOUSLY — before `refreshPromise` below is\n // assigned (the whole chain runs before the first real await). Without\n // this flag that recursion is unbounded (stack overflow). Returning early\n // lets the refresh request itself proceed unauthenticated — it carries\n // the refresh token in its body, not the Bearer header.\n if (this.refreshing) {\n return;\n }\n\n this.refreshing = true;\n this.refreshPromise = this.executeRefresh(this.session.refreshToken);\n\n try {\n await this.refreshPromise;\n } finally {\n this.refreshPromise = null;\n this.refreshing = false;\n }\n }\n\n onAuthStateChange(callback: AuthStateCallback): Unsubscribe {\n this.listeners.add(callback);\n return () => {\n this.listeners.delete(callback);\n };\n }\n\n private async executeRefresh(refreshToken: string): Promise<void> {\n if (!this.refreshFunction) return;\n const newSession = await this.refreshFunction(refreshToken);\n this.setSession(newSession);\n }\n\n private notify(event: 'SESSION_SET' | 'SESSION_CLEARED', session: Session | null): void {\n for (const listener of this.listeners) {\n listener(event, session);\n }\n }\n}\n","import { PalbaseError, type PalbaseResponse } from '@palbase/core';\n\nexport interface FieldError {\n field: string;\n message: string;\n}\n\nexport type BackendErrorKind =\n | 'notConfigured'\n | 'validation'\n | 'unauthorized'\n | 'rateLimited'\n | 'server'\n | 'network'\n | 'decode';\n\ninterface BackendErrorParams {\n code: string;\n message: string;\n status?: number;\n requestId?: string;\n fields?: FieldError[];\n retryAfter?: number;\n data?: unknown;\n}\n\nexport class BackendError extends Error {\n readonly kind: BackendErrorKind;\n readonly code: string;\n readonly status: number;\n readonly requestId?: string;\n readonly fields?: FieldError[];\n readonly retryAfter?: number;\n readonly data?: unknown;\n\n constructor(kind: BackendErrorKind, params: BackendErrorParams) {\n super(params.message);\n this.name = 'BackendError';\n this.kind = kind;\n this.code = params.code;\n this.status = params.status ?? 0;\n this.requestId = params.requestId;\n this.fields = params.fields;\n this.retryAfter = params.retryAfter;\n this.data = params.data;\n }\n\n static notConfigured(): BackendError {\n return new BackendError('notConfigured', {\n code: 'not_configured',\n message:\n \"Palbe is not configured. Run 'palbase web link' in your project and make sure palbe.gen.ts is imported once at app startup.\",\n });\n }\n}\n\nfunction isFieldErrorArray(value: unknown): value is FieldError[] {\n return (\n Array.isArray(value) &&\n value.length > 0 &&\n value.every(\n (v) =>\n typeof v === 'object' &&\n v !== null &&\n typeof (v as Record<string, unknown>).field === 'string' &&\n typeof (v as Record<string, unknown>).message === 'string',\n )\n );\n}\n\nfunction pickField(value: unknown, key: string): unknown {\n if (typeof value === 'object' && value !== null) {\n return (value as Record<string, unknown>)[key];\n }\n return undefined;\n}\n\nfunction pickNumber(value: unknown, key: string): number | undefined {\n const n = pickField(value, key);\n return typeof n === 'number' ? n : undefined;\n}\n\nfunction pickString(value: unknown, key: string): string | undefined {\n const s = pickField(value, key);\n return typeof s === 'string' ? s : undefined;\n}\n\nexport function fromPalbaseError(err: PalbaseError): BackendError {\n // HttpClient stores the WHOLE wire envelope as err.details:\n // { error, error_description, status, request_id, retry_after?, details?, data? }\n const base: BackendErrorParams = {\n code: err.code,\n message: err.message,\n status: err.status,\n requestId: pickString(err.details, 'request_id'),\n data: pickField(err.details, 'data'),\n };\n if (err.code === 'network_error') return new BackendError('network', base);\n if (err.status === 401) return new BackendError('unauthorized', base);\n if (err.status === 429)\n return new BackendError('rateLimited', {\n ...base,\n retryAfter: pickNumber(err.details, 'retry_after'),\n });\n // Field-error array lives at the envelope's nested `details` key.\n const nested = pickField(err.details, 'details');\n if (err.status === 400 && isFieldErrorArray(nested))\n return new BackendError('validation', { ...base, fields: nested });\n // Fallthrough also covers status-0 non-network errors (e.g. auth client's\n // synthetic 'no_refresh_token' with status 0) — those intentionally map to 'server'.\n return new BackendError('server', base);\n}\n\n/** Decode a raw wire body (used by paths that bypass HttpClient, e.g. upload). */\nexport function fromEnvelope(status: number, body: unknown): BackendError {\n const code = pickString(body, 'error') ?? 'http_error';\n const message = pickString(body, 'error_description') ?? `HTTP ${status}`;\n const requestId = pickString(body, 'request_id');\n const details = pickField(body, 'details');\n const params: BackendErrorParams = {\n code,\n message,\n status,\n requestId,\n data: pickField(body, 'data'),\n };\n if (status === 401) return new BackendError('unauthorized', params);\n if (status === 429)\n return new BackendError('rateLimited', {\n ...params,\n // Real 429 wire body has TOP-LEVEL retry_after; nested details is a fallback.\n retryAfter: pickNumber(body, 'retry_after') ?? pickNumber(details, 'retry_after'),\n });\n if (status === 400 && isFieldErrorArray(details))\n return new BackendError('validation', { ...params, fields: details });\n return new BackendError('server', params);\n}\n\n/**\n * Type guard for BackendError that survives module-identity splits.\n * This package ships dual ESM + CJS builds; if both end up loaded (or the\n * package is installed twice), two distinct BackendError classes coexist and\n * `instanceof` fails for errors thrown by \"the other\" copy. Falls back to a\n * structural check on `name` + `kind`.\n */\nexport function isBackendError(e: unknown): e is BackendError {\n return (\n e instanceof BackendError ||\n (typeof e === 'object' &&\n e !== null &&\n (e as Record<string, unknown>).name === 'BackendError' &&\n typeof (e as Record<string, unknown>).kind === 'string')\n );\n}\n\n/**\n * Structural fallback after instanceof for PalbaseError — same dual ESM+CJS\n * identity-split rationale as isBackendError above: two loaded PalbaseError\n * classes break `instanceof` across copies; the name check bridges that.\n * Returns the error as PalbaseError, or null when it isn't one.\n */\nexport function asPalbaseError(e: unknown): PalbaseError | null {\n if (e instanceof PalbaseError) return e;\n if (e instanceof Error && e.name === 'PalbaseError') return e as PalbaseError;\n return null;\n}\n\n/** Convert an internal {data,error} envelope into data-or-throw. */\nexport function unwrap<T>(res: PalbaseResponse<T>): T {\n if (res.error) throw fromPalbaseError(res.error);\n return res.data as T;\n}\n","const ID_SEGMENT =\n /^(?:\\d+|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/;\n\n// An email lands in a path segment as `jane@acme.com` or percent-encoded\n// `jane%40acme.com`. Either form is PII and must never survive into a metric\n// name, so a segment carrying an `@` (encoded or raw) is masked like an id.\nconst EMAIL_SEGMENT = /(?:@|%40)/i;\n\nfunction isSensitiveSegment(seg: string): boolean {\n return ID_SEGMENT.test(seg) || EMAIL_SEGMENT.test(seg);\n}\n\n/**\n * Capture-time URL redaction (§4.1 privacy, client half). Collapses a request\n * URL to a PII-free, low-cardinality path pattern BEFORE it leaves the browser:\n * query string dropped wholesale, id-like segments masked. The ingest scrubber\n * re-applies server-side (defense in depth).\n *\n * Rules:\n * - absolute URL → keep only `pathname` (scheme/host/query/hash dropped);\n * - relative URL → drop everything from the first `?` onward;\n * - any segment that is purely numeric, a UUID, or carries an email (`@` /\n * `%40`) → `:id`;\n * - credentials/tokens live in the query (dropped wholesale), emails in a\n * path segment (masked), so neither survives into the metric name.\n */\nexport function redactUrl(rawUrl: string): string {\n let path = rawUrl;\n try {\n // Absolute URLs: take the pathname (drops scheme/host/query/hash).\n path = new URL(rawUrl).pathname;\n } catch {\n const q = path.indexOf('?');\n if (q >= 0) path = path.slice(0, q);\n }\n return path\n .split('/')\n .map((seg) => (isSensitiveSegment(seg) ? ':id' : seg))\n .join('/');\n}\n","import { asPalbaseError, fromPalbaseError, unwrap } from './errors.js';\nimport { redactUrl } from './perf/url-redactor.js';\nimport type { PalbeRuntime } from './runtime.js';\nimport { asPowChallenge, solvePowChallenge } from '@palbase/core';\n\nexport interface RequestSpec {\n body?: unknown;\n headers?: Record<string, string>;\n signal?: AbortSignal;\n}\n\nexport interface CallOptions {\n headers?: Record<string, string>;\n signal?: AbortSignal;\n}\n\nconst MUTATING = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);\n\n// The perf flush (`POST /v1/analytics/perf`) and analytics event ingest\n// (`/v1/analytics/*`) BOTH funnel through `palbeRequest`. Tracing them would\n// make every flush emit another `network` item that the next flush ships,\n// then traces again — a self-measurement loop. Excluding the whole\n// `/v1/analytics/` prefix breaks it. This is the SAME self-exclusion shape as\n// the iOS network trace (1b-3), keyed on the identical ingest prefix.\nconst PERF_EXCLUDED_PREFIX = '/v1/analytics/';\n\nfunction isSelfTraced(path: string): boolean {\n return path.startsWith(PERF_EXCLUDED_PREFIX);\n}\n\n/**\n * Codes meaning the server-side session is GONE — a token refresh cannot help\n * and the client must tear its local session down. `subject_fenced` is palauth's\n * wire-true erasure-fence code (`bridge.go`); `subject_erased` is accepted for\n * forward-compat. `session_revoked` is palauth's revoked-session 401.\n * A PLAIN 403 (ordinary authz / RLS denial) is intentionally NOT here — matching\n * all 403s would sign users out on routine permission errors.\n */\nfunction isSessionKilled(status: number, code: string): boolean {\n if (status === 401) return code === 'session_revoked';\n if (status === 403) return code === 'subject_fenced' || code === 'subject_erased';\n return false;\n}\n\n/** Monotonic millisecond clock — `performance.now()` when present, else\n * `Date.now()`. Mirrors `perf-facade.ts`'s `nowMs()` so network durations and\n * custom-trace durations share one time base. */\nfunction nowMs(): number {\n return typeof performance !== 'undefined' && typeof performance.now === 'function'\n ? performance.now()\n : Date.now();\n}\n\n/** An `AbortError` (caller-cancelled request) is NOT a completed network call,\n * so it must never produce a trace. */\nfunction isAbort(e: unknown): boolean {\n return e instanceof Error && e.name === 'AbortError';\n}\n\nexport async function palbeRequest<T>(\n rt: PalbeRuntime,\n method: string,\n path: string,\n spec: RequestSpec = {},\n): Promise<T> {\n const headers: Record<string, string> = { ...spec.headers };\n // Case-insensitive scan: a caller-supplied `idempotency-key` must suppress\n // generation, otherwise Headers merges both casings and dedup breaks.\n const callerHasKey = Object.keys(headers).some((k) => k.toLowerCase() === 'idempotency-key');\n if (MUTATING.has(method) && !callerHasKey) {\n headers['Idempotency-Key'] = crypto.randomUUID();\n }\n\n // `extra` carries the proof-of-work headers on the one retry that needs them.\n // The base `headers` are reused rather than rebuilt, so a retry keeps the same\n // Idempotency-Key and the server still sees one logical request.\n const attempt = async (extra?: Record<string, string>) => {\n try {\n return await rt.http.request<T>(method, path, {\n body: spec.body,\n headers: extra ? { ...headers, ...extra } : headers,\n signal: spec.signal,\n });\n } catch (e) {\n const pe = asPalbaseError(e);\n throw pe ? fromPalbaseError(pe) : e;\n }\n };\n\n // The single network-trace choke point: every SDK fetch funnels through\n // `palbeRequest`, so timing it here records one `network` PerfItem per call —\n // redacted URL + method + final HTTP status + wall-clock duration. The\n // `/v1/analytics/` ingest paths are self-excluded (no recursion) and aborts\n // are skipped (not a completed call).\n // `rt.perf` is guarded too: `buildRuntime` always wires it, but a hand-built\n // runtime (tests, embedders) may omit it — a missing facade must never break\n // the request path.\n const traced = !isSelfTraced(path) && rt.perf !== undefined;\n const startedAt = traced ? nowMs() : 0;\n // Exactly ONE trace per `palbeRequest`: the inner refresh-catch records with\n // the precise status and rethrows; that rethrow is then caught by the outer\n // catch, so the guard keeps the second `record()` a no-op (no double-count).\n let recorded = false;\n const record = (status: number): void => {\n if (!traced || recorded) return;\n recorded = true;\n rt.perf.recordNetwork(method, redactUrl(path), status, nowMs() - startedAt);\n };\n\n let res: Awaited<ReturnType<typeof attempt>>;\n try {\n res = await attempt();\n\n // Reactive 401: refresh once, retry once with the same Idempotency-Key.\n // A revoked session (401 session_revoked) is terminal — a refresh cannot\n // resurrect it, so skip straight to the teardown check below.\n if (\n res.error?.status === 401 &&\n res.error.code !== 'session_revoked' &&\n rt.tokenManager.getRefreshToken() &&\n rt.tokenManager.refreshFunction\n ) {\n try {\n await rt.tokenManager.refreshSession();\n } catch (refreshErr) {\n const pe = asPalbaseError(refreshErr);\n const status = pe?.status ?? 0;\n if (status === 400 || status === 401 || status === 403) {\n // Terminal: the refresh token is dead. Sign out and surface the\n // ORIGINAL 401 (mirrors HttpClient's pre-flight terminal split).\n rt.tokenManager.clearSession();\n record(res.error.status);\n throw fromPalbaseError(res.error);\n }\n // Transient (network/5xx): keep the session, surface the refresh failure.\n record(pe?.status ?? 0);\n throw pe ? fromPalbaseError(pe) : refreshErr;\n }\n res = await attempt();\n }\n\n // Reactive proof-of-work: signup and login sit behind a bot gate that\n // answers an unsolved request with 403 and the challenge in the body. Solve\n // it once and repeat the request with the headers; the caller never learns\n // the gate is there.\n //\n // Placed after the 401 branch so it sees whichever response is current — a\n // refresh-and-retry can land on the gate just as a first attempt can.\n //\n // Only ONE retry, and only when the body really carries a challenge. A 403\n // that merely says pow_required without one is a server the client cannot\n // satisfy, and looping on it would turn a broken gate into a hang.\n if (res.error?.status === 403 && res.error.code === 'pow_required') {\n const challenge = asPowChallenge(res.error.details);\n if (challenge) {\n res = await attempt(await solvePowChallenge(challenge));\n }\n }\n } catch (e) {\n // A thrown error here is a network/transport failure (the `{data,error}`\n // envelope never throws). Record it as a completed-with-error call unless\n // the caller aborted — an abort is a cancellation, not a measurement.\n if (!isAbort(e)) record(asPalbaseError(e)?.status ?? 0);\n throw e;\n }\n\n // Reactive session teardown: a server-side session kill (subject erased /\n // session revoked) means the local session is dead regardless of which facade\n // issued this call — tear it down + emit signedOut{sessionInvalid} once. Scoped\n // to the exact codes so an ordinary 403 authz denial does NOT sign the user out.\n if (res.error && isSessionKilled(res.error.status, res.error.code)) {\n rt.auth.reactiveTeardown();\n }\n\n // Response resolved (2xx or an error envelope): record the real HTTP status\n // BEFORE `unwrap` may throw, so a 4xx/5xx is measured with its true status.\n record(res.error?.status ?? 200);\n return unwrap(res);\n}\n","import type { NamespaceTree } from './namespaces.js';\nimport type { PalbeRuntime } from './runtime.js';\n\nexport interface PalbeGlobalState {\n runtime: PalbeRuntime | null;\n registry: NamespaceTree;\n /** Memoized materialized namespaces (per top-level key); reset on (re-)register. */\n nsCache: Record<string, unknown>;\n /** Listeners fired synchronously after every successful __configure call. */\n configuredListeners: Set<() => void>;\n}\n\n/**\n * Module-level state would split between the ESM and CJS bundles of the two\n * entries (esbuild only code-splits ESM) and between dual-package copies in\n * one process. A globalThis slot keyed by Symbol.for converges them all.\n */\nconst STATE_KEY: unique symbol = Symbol.for('palbe.state.v1');\n\ninterface PalbeGlobals {\n [STATE_KEY]?: PalbeGlobalState;\n}\n\nexport function palbeState(): PalbeGlobalState {\n // Single direct widening view of globalThis — typed via the unique-symbol\n // key above so reads/writes of the slot stay fully typed.\n const g = globalThis as PalbeGlobals;\n let state = g[STATE_KEY];\n if (!state) {\n state = { runtime: null, registry: {}, nsCache: {}, configuredListeners: new Set() };\n g[STATE_KEY] = state;\n }\n return state;\n}\n","import { BackendError, isBackendError } from './errors.js';\nimport { getRuntime } from './internal.js';\nimport { type CallOptions, palbeRequest } from './request.js';\nimport type { PalbeRuntime } from './runtime.js';\nimport { palbeState } from './state.js';\n\nexport interface EndpointDescriptor {\n method: string;\n path: string;\n /** `{name}` segments consumed as leading call args, in order. */\n pathParams?: string[];\n /**\n * 'body' (default for non-GET) | 'query' (serialize input into the URL) |\n * 'none' (no input arg — options come straight after path params).\n */\n input?: 'body' | 'query' | 'none';\n /** wire error code → lift fn; the materializer rethrows the lifted error. */\n errors?: Record<string, (e: BackendError) => Error>;\n}\n\nexport type NamespaceTree = { [key: string]: NamespaceTree | EndpointDescriptor };\n\n// Reserved surface names on pb — cannot be shadowed at the top level by codegen\n// namespaces. `perf` sits next to `analytics`: `pb.perf` is the PalPerf facade,\n// so a generated controller named `perf` must NOT collide with it.\nexport const RESERVED = new Set([\n 'call',\n 'upload',\n 'auth',\n 'flags',\n 'realtime',\n 'analytics',\n 'calls',\n 'messaging',\n 'perf',\n]);\n\nfunction reservedNamespaceError(key: string): BackendError {\n return new BackendError('validation', {\n code: 'reserved_namespace',\n message: `Cannot register endpoint namespace '${key}': the name is reserved on pb.`,\n });\n}\n\nfunction isDescriptor(node: NamespaceTree | EndpointDescriptor): node is EndpointDescriptor {\n return (\n typeof (node as EndpointDescriptor).path === 'string' &&\n typeof (node as EndpointDescriptor).method === 'string'\n );\n}\n\n/**\n * Validate + normalize one tree level, recursively:\n * - plain-object guard (a literal `__proto__:` in a hand-written tree would\n * swap the node's prototype — cheap proto-pollution insurance);\n * - reject 'then' at EVERY level (a nested then makes that namespace object\n * thenable → `await pb.rooms` hangs) and every Object.prototype member\n * name (`'valueOf' in {}` is true — covers toString, __proto__, …);\n * - uppercase descriptor methods (request.ts matches mutating methods\n * against an uppercase set; a codegen 'get' would otherwise send a body\n * and an Idempotency-Key).\n */\nfunction validateDescriptorExtensions(d: EndpointDescriptor, path: string): void {\n if (d.pathParams !== undefined) {\n if (!Array.isArray(d.pathParams) || !d.pathParams.every((p) => typeof p === 'string')) {\n throw new BackendError('validation', {\n code: 'invalid_namespace_tree',\n message: `Descriptor '${path}': pathParams must be an array of strings.`,\n });\n }\n // Cross-check: a pathParams entry without a matching placeholder would\n // silently consume a call arg and substitute nothing — fail at registration.\n for (const p of d.pathParams) {\n if (!d.path.includes(`{${p}}`)) {\n throw new BackendError('validation', {\n code: 'invalid_namespace_tree',\n message: `Descriptor '${path}': pathParams entry '${p}' has no '{${p}}' placeholder in the path.`,\n });\n }\n }\n }\n if (d.input !== undefined && d.input !== 'body' && d.input !== 'query' && d.input !== 'none') {\n throw new BackendError('validation', {\n code: 'invalid_namespace_tree',\n message: `Descriptor '${path}': input must be 'body', 'query' or 'none'.`,\n });\n }\n if (d.errors !== undefined) {\n for (const [code, fn] of Object.entries(d.errors)) {\n if (typeof fn !== 'function') {\n throw new BackendError('validation', {\n code: 'invalid_namespace_tree',\n message: `Descriptor '${path}': errors['${code}'] must be a function.`,\n });\n }\n }\n }\n}\n\nfunction validateTree(node: NamespaceTree): void {\n const proto = Object.getPrototypeOf(node) as unknown;\n if (proto !== Object.prototype && proto !== null) {\n throw new BackendError('validation', {\n code: 'invalid_namespace_tree',\n message: 'Namespace tree nodes must be plain objects.',\n });\n }\n for (const [key, child] of Object.entries(node)) {\n if (key === 'then' || key in {}) throw reservedNamespaceError(key);\n if (isDescriptor(child)) {\n child.method = child.method.toUpperCase();\n validateDescriptorExtensions(child, child.path);\n } else {\n validateTree(child);\n }\n }\n}\n\nexport function __registerNamespaces(tree: NamespaceTree): void {\n for (const key of Object.keys(tree)) {\n if (RESERVED.has(key)) throw reservedNamespaceError(key);\n }\n validateTree(tree);\n const state = palbeState();\n state.registry = tree;\n state.nsCache = {};\n}\n\nexport function resetNamespaces(): void {\n const state = palbeState();\n state.registry = {};\n state.nsCache = {};\n}\n\nexport function getRegistry(): NamespaceTree {\n return palbeState().registry;\n}\n\nfunction serializeQuery(input: unknown): string {\n if (input === undefined || input === null) return '';\n const entries = Object.entries(input as Record<string, unknown>)\n .filter(([, v]) => v !== undefined && v !== null)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));\n if (entries.length === 0) return '';\n const parts = entries.map(([k, v]) => {\n if (typeof v !== 'string' && typeof v !== 'number' && typeof v !== 'boolean') {\n // Objects/arrays would stringify to '[object Object]' on the wire — fail loud.\n throw new BackendError('validation', {\n code: 'invalid_query_value',\n message: `Query parameter '${k}' must be a string, number, or boolean.`,\n });\n }\n return `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`;\n });\n return `?${parts.join('&')}`;\n}\n\nasync function invokeDescriptor(\n rt: PalbeRuntime,\n d: EndpointDescriptor,\n args: unknown[],\n): Promise<unknown> {\n const params = d.pathParams ?? [];\n let path = d.path;\n for (let i = 0; i < params.length; i++) {\n const v = args[i];\n if (typeof v !== 'string' && typeof v !== 'number') {\n throw new BackendError('validation', {\n code: 'missing_path_param',\n message: `${d.path}: path parameter '${params[i]}' must be a string or number`,\n });\n }\n path = path.replace(`{${params[i]}}`, encodeURIComponent(String(v)));\n }\n // input:'none' ops have NO input slot — options come straight after the\n // path params (a generated `get(id, options?)` would otherwise drop its\n // options into the input slot: GET throws, POST sends them as the body).\n if (d.input === 'none' && args.length > params.length + 1) {\n throw new BackendError('validation', {\n code: 'unexpected_argument',\n message: `${d.path}: endpoint takes no input — pass only path params and options`,\n });\n }\n const input = d.input === 'none' ? undefined : args[params.length];\n const optionsSlot = d.input === 'none' ? params.length : params.length + 1;\n const options = args[optionsSlot] as CallOptions | undefined;\n let body: unknown;\n if (d.input === 'query') {\n path += serializeQuery(input);\n } else if (d.input !== 'none') {\n if (d.method === 'GET') {\n if (input !== undefined) {\n throw new BackendError('validation', {\n code: 'unsupported_get_input',\n message: `${d.path}: GET endpoints take no body input`,\n });\n }\n } else {\n body = input;\n }\n }\n try {\n return await palbeRequest(rt, d.method, path, {\n body,\n headers: options?.headers,\n signal: options?.signal,\n });\n } catch (e) {\n if (d.errors && isBackendError(e)) {\n const lift = d.errors[e.code];\n if (lift) throw lift(e);\n }\n throw e;\n }\n}\n\n/**\n * The runtime is resolved AT CALL TIME via `resolveRt` (late binding): the\n * global path passes `getRuntime` so a `__configure` swap is picked up by\n * already-materialized namespaces; bound clients pass `() => rt` so the\n * captured runtime never changes.\n */\nfunction materialize(\n node: NamespaceTree | EndpointDescriptor,\n resolveRt: () => PalbeRuntime,\n): unknown {\n if (isDescriptor(node)) {\n // async wrapper: resolveRt() may throw (notConfigured) — endpoint methods\n // must ALWAYS reject, never throw synchronously (callers use .catch()).\n return async (...args: unknown[]) => invokeDescriptor(resolveRt(), node, args);\n }\n const out: Record<string, unknown> = {};\n for (const [key, child] of Object.entries(node)) {\n out[key] = materialize(child, resolveRt);\n }\n return out;\n}\n\n/** Global path: resolves the global runtime slot, memoizes in the global nsCache. */\nexport function getNamespace(name: string): unknown {\n const state = palbeState();\n const cached = state.nsCache[name];\n if (cached !== undefined) return cached;\n const node = state.registry[name];\n if (node === undefined) return undefined;\n const ns = materialize(node, getRuntime);\n state.nsCache[name] = ns;\n return ns;\n}\n\n/**\n * Bound path: namespaces materialize against ONE fixed runtime, memoized in a\n * per-client closure cache (the global nsCache stays global-only — a bound\n * entry there would leak one request's runtime into every other client).\n * The cache is invalidated when the registry object is swapped\n * (__registerNamespaces replaces it wholesale, e.g. watch-mode regen).\n */\nexport function boundNamespaceAccessor(rt: PalbeRuntime): (name: string) => unknown {\n let cacheRegistry = palbeState().registry;\n let cache: Record<string, unknown> = {};\n return (name: string): unknown => {\n const registry = palbeState().registry;\n if (registry !== cacheRegistry) {\n cacheRegistry = registry;\n cache = {};\n }\n const cached = cache[name];\n if (cached !== undefined) return cached;\n const node = registry[name];\n if (node === undefined) return undefined;\n const ns = materialize(node, () => rt);\n cache[name] = ns;\n return ns;\n };\n}\n","import type { HttpClient, PalbaseResponse, Session, TokenManager } from '@palbase/core';\nimport { PalbaseError } from '@palbase/core';\nimport { DeviceClient } from './device-client.js';\nimport type {\n AuthEvent,\n AuthResult,\n AuthSession,\n AuthStateChangeCallback,\n CredentialExchangeParams,\n Identity,\n LinkIdentityParams,\n MagicLinkParams,\n MagicLinkVerifyParams,\n MFAChallengeParams,\n MFAEmailChallengeParams,\n MFAEmailVerifyParams,\n MFAEnrollParams,\n MFAEnrollResult,\n MFAFactor,\n MFARecoveryParams,\n OAuthOptions,\n PasswordChangeParams,\n PasswordResetConfirmParams,\n PasswordResetParams,\n PasswordSetParams,\n RegisterTrustedDeviceParams,\n SignInCredentials,\n SignUpCredentials,\n TokenResponse,\n TrustedDevice,\n User,\n VerifyEmailParams,\n} from './types.js';\n\n/** Reserved for future user-scope options. Admin surface is in @palbase/server. */\nexport type AuthClientOptions = Record<string, never>;\n\nconst PROVIDER_RE = /^[a-zA-Z0-9_-]+$/;\n\n/** Validates that a path parameter does not contain path traversal characters. */\nfunction validatePathParam(name: string, value: string): void {\n if (value.includes('/') || value.includes('..') || value.includes('%')) {\n throw new Error(`Invalid ${name}: must not contain path separators`);\n }\n}\n\n/** Convert AuthResult from the real API into the SDK Session shape. */\nfunction toSession(result: AuthResult): Session {\n return {\n accessToken: result.access_token,\n refreshToken: result.refresh_token,\n expiresAt: Date.now() + result.expires_in * 1000,\n };\n}\n\n/** Convert AuthResult UserInfo into the SDK User shape. */\nfunction toUser(result: AuthResult): User {\n return {\n id: result.user.id,\n email: result.user.email,\n emailVerified: result.user.email_verified,\n createdAt: result.user.created_at,\n updatedAt: result.user.created_at,\n };\n}\n\nexport class AuthClient {\n private readonly httpClient: HttpClient;\n private readonly tokenManager: TokenManager;\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private currentSession: Session | null = null;\n private hasSession = false;\n private _mfa: ReturnType<AuthClient['buildMfa']> | null = null;\n\n /** Device attestation */\n readonly device: DeviceClient;\n\n constructor(\n httpClient: HttpClient,\n tokenManager: TokenManager,\n apiKey?: string,\n baseUrl?: string,\n _options?: AuthClientOptions,\n ) {\n this.httpClient = httpClient;\n this.tokenManager = tokenManager;\n this.apiKey = apiKey ?? '';\n this.baseUrl = baseUrl ?? '';\n this.device = new DeviceClient(httpClient);\n\n this.tokenManager.onAuthStateChange((event, session) => {\n if (event === 'SESSION_SET') {\n this.currentSession = session;\n } else {\n this.currentSession = null;\n }\n });\n }\n\n // ── Core Auth ───────────────────────────────────────────\n\n async signUp(\n credentials: SignUpCredentials,\n ): Promise<PalbaseResponse<{ user: User; session: Session }>> {\n const response = await this.httpClient.request<AuthResult>('POST', '/auth/signup', {\n body: credentials,\n });\n\n if (response.data) {\n const session = toSession(response.data);\n const user = toUser(response.data);\n this.setSessionAndWireRefresh(session);\n return { data: { user, session }, error: null, status: response.status };\n }\n\n return { data: null, error: response.error, status: response.status };\n }\n\n async signIn(\n credentials: SignInCredentials,\n ): Promise<PalbaseResponse<{ user: User; session: Session }>> {\n const response = await this.httpClient.request<AuthResult>('POST', '/auth/login', {\n body: credentials,\n });\n\n if (response.data) {\n const session = toSession(response.data);\n const user = toUser(response.data);\n this.setSessionAndWireRefresh(session);\n return { data: { user, session }, error: null, status: response.status };\n }\n\n return { data: null, error: response.error, status: response.status };\n }\n\n async signOut(): Promise<PalbaseResponse<void>> {\n const response = await this.httpClient.request<void>('POST', '/auth/logout');\n this.tokenManager.clearSession();\n this.hasSession = false;\n return response;\n }\n\n // ── Email Verification ──────────────────────────────────\n\n async verifyEmail(params: VerifyEmailParams): Promise<PalbaseResponse<{ status: string }>> {\n return this.httpClient.request<{ status: string }>('POST', '/auth/verify-email', {\n body: params,\n });\n }\n\n async resendVerification(\n email: string,\n ): Promise<PalbaseResponse<{ verification_token?: string; verification_code?: string }>> {\n return this.httpClient.request<{ verification_token?: string; verification_code?: string }>(\n 'POST',\n '/auth/resend-verification',\n { body: { email } },\n );\n }\n\n // ── Password ────────────────────────────────────────────\n\n async requestPasswordReset(\n params: PasswordResetParams,\n ): Promise<PalbaseResponse<{ success: boolean }>> {\n return this.httpClient.request<{ success: boolean }>('POST', '/auth/password/reset', {\n body: params,\n });\n }\n\n async confirmPasswordReset(\n params: PasswordResetConfirmParams,\n ): Promise<PalbaseResponse<{ success: boolean }>> {\n return this.httpClient.request<{ success: boolean }>('POST', '/auth/password/reset/confirm', {\n body: params,\n });\n }\n\n async changePassword(\n params: PasswordChangeParams,\n ): Promise<PalbaseResponse<{ success: boolean }>> {\n return this.httpClient.request<{ success: boolean }>('POST', '/auth/password/change', {\n body: params,\n });\n }\n\n /**\n * Give a PASSWORDLESS account its first password.\n *\n * Separate from `changePassword` because that one cannot serve the case: it\n * sends a current password and palauth answers an account that has none with\n * 401. Requires a RECENT sign-in (403 `reauthentication_required` otherwise,\n * the same gate passkey enrollment carries); an account that already has a\n * password gets 409 `password_already_set`.\n */\n async setPassword(params: PasswordSetParams): Promise<PalbaseResponse<{ success: boolean }>> {\n return this.httpClient.request<{ success: boolean }>('POST', '/auth/password/set', {\n body: params,\n });\n }\n\n // ── Token ───────────────────────────────────────────────\n\n async refresh(): Promise<PalbaseResponse<TokenResponse>> {\n const refreshToken = this.tokenManager.getRefreshToken();\n if (!refreshToken) {\n return {\n data: null,\n error: new PalbaseError('no_refresh_token', 'No refresh token available', 0),\n status: 0,\n };\n }\n\n const response = await this.httpClient.request<TokenResponse>('POST', '/auth/token/refresh', {\n body: { refresh_token: refreshToken },\n });\n\n if (response.data) {\n const session: Session = {\n accessToken: response.data.access_token,\n refreshToken: response.data.refresh_token,\n expiresAt: Date.now() + response.data.expires_in * 1000,\n };\n this.tokenManager.setSession(session);\n }\n\n return response;\n }\n\n getAccessToken(): string | null {\n return this.tokenManager.getAccessToken();\n }\n\n onTokenChange(\n callback: (tokens: { accessToken: string | null; refreshToken: string | null }) => void,\n ): () => void {\n return this.tokenManager.onAuthStateChange((event, session) => {\n if (event === 'SESSION_SET' && session) {\n callback({ accessToken: session.accessToken, refreshToken: session.refreshToken });\n } else {\n callback({ accessToken: null, refreshToken: null });\n }\n });\n }\n\n setTokens(accessToken: string, refreshToken: string, expiresIn = 3600): void {\n const session: Session = {\n accessToken,\n refreshToken,\n // Default 1h when the caller has no TTL; corrected on next refresh anyway.\n expiresAt: Date.now() + expiresIn * 1000,\n };\n this.setSessionAndWireRefresh(session);\n }\n\n // ── Sessions ────────────────────────────────────────────\n\n async listSessions(): Promise<PalbaseResponse<AuthSession[]>> {\n const response = await this.httpClient.request<{ sessions: AuthSession[] }>(\n 'GET',\n '/auth/sessions',\n );\n\n if (response.data) {\n return { data: response.data.sessions, error: null, status: response.status };\n }\n\n return { data: null, error: response.error, status: response.status };\n }\n\n async revokeSession(sessionId: string): Promise<PalbaseResponse<void>> {\n validatePathParam('sessionId', sessionId);\n return this.httpClient.request<void>('DELETE', `/auth/sessions/${sessionId}`);\n }\n\n async revokeAllSessions(): Promise<PalbaseResponse<void>> {\n return this.httpClient.request<void>('DELETE', '/auth/sessions');\n }\n\n // ── MFA ─────────────────────────────────────────────────\n\n get mfa() {\n this._mfa ??= this.buildMfa();\n return this._mfa;\n }\n\n private buildMfa() {\n return {\n enroll: (params: MFAEnrollParams): Promise<PalbaseResponse<MFAEnrollResult>> =>\n this.httpClient.request('POST', '/auth/mfa/enroll', { body: params }),\n\n verifyEnrollment: (code: string): Promise<PalbaseResponse<{ status: string }>> =>\n this.httpClient.request('POST', '/auth/mfa/verify', { body: { code } }),\n\n challenge: (params: MFAChallengeParams): Promise<PalbaseResponse<TokenResponse>> =>\n this.httpClient.request('POST', '/auth/mfa/challenge', { body: params }),\n\n recovery: (params: MFARecoveryParams): Promise<PalbaseResponse<TokenResponse>> =>\n this.httpClient.request('POST', '/auth/mfa/recovery', { body: params }),\n\n listFactors: (): Promise<PalbaseResponse<{ factors: MFAFactor[] }>> =>\n this.httpClient.request('GET', '/auth/mfa/factors'),\n\n removeFactor: (\n factorId: string,\n currentPassword: string,\n ): Promise<PalbaseResponse<{ status: string }>> => {\n validatePathParam('factorId', factorId);\n return this.httpClient.request('DELETE', `/auth/mfa/factors/${factorId}`, {\n body: { current_password: currentPassword },\n });\n },\n\n regenerateRecoveryCodes: (): Promise<PalbaseResponse<{ recovery_codes: string[] }>> =>\n this.httpClient.request('POST', '/auth/mfa/recovery-codes/regenerate'),\n\n emailEnroll: (): Promise<PalbaseResponse<{ status: string }>> =>\n this.httpClient.request('POST', '/auth/mfa/email/enroll'),\n\n emailChallenge: (\n params: MFAEmailChallengeParams,\n ): Promise<PalbaseResponse<{ status: string }>> =>\n this.httpClient.request('POST', '/auth/mfa/email/challenge', { body: params }),\n\n emailVerify: (params: MFAEmailVerifyParams): Promise<PalbaseResponse<TokenResponse>> =>\n this.httpClient.request('POST', '/auth/mfa/email/verify', { body: params }),\n };\n }\n\n // ── OAuth / Social ──────────────────────────────────────\n\n async getOAuthURL(options: OAuthOptions): Promise<PalbaseResponse<{ url: string }>> {\n if (!PROVIDER_RE.test(options.provider)) {\n return {\n data: null,\n error: new PalbaseError('invalid_provider', 'Invalid OAuth provider name', 0),\n status: 0,\n };\n }\n\n const params = new URLSearchParams();\n if (options.redirectTo) {\n params.set('redirect_uri', options.redirectTo);\n }\n const query = params.toString();\n const path = `/auth/oauth/${options.provider}/authorize${query ? `?${query}` : ''}`;\n\n return this.httpClient.request<{ url: string }>('GET', path);\n }\n\n async signInWithCredential(\n params: CredentialExchangeParams,\n ): Promise<PalbaseResponse<{ user: User; session: Session }>> {\n const response = await this.httpClient.request<AuthResult>('POST', '/auth/oauth/credential', {\n body: params,\n });\n\n if (response.data?.access_token) {\n const session = toSession(response.data);\n const user = toUser(response.data);\n this.setSessionAndWireRefresh(session);\n return { data: { user, session }, error: null, status: response.status };\n }\n\n return { data: null, error: response.error, status: response.status };\n }\n\n // ── Identities ──────────────────────────────────────────\n\n async listIdentities(): Promise<PalbaseResponse<{ identities: Identity[] }>> {\n return this.httpClient.request<{ identities: Identity[] }>('GET', '/auth/identities');\n }\n\n async linkIdentity(params: LinkIdentityParams): Promise<PalbaseResponse<{ success: boolean }>> {\n return this.httpClient.request<{ success: boolean }>('POST', '/auth/identities/link', {\n body: params,\n });\n }\n\n async unlinkIdentity(identityId: string): Promise<PalbaseResponse<{ success: boolean }>> {\n validatePathParam('identityId', identityId);\n return this.httpClient.request<{ success: boolean }>(\n 'DELETE',\n `/auth/identities/${identityId}`,\n );\n }\n\n // ── Magic Link ──────────────────────────────────────────\n\n async requestMagicLink(params: MagicLinkParams): Promise<PalbaseResponse<{ success: boolean }>> {\n return this.httpClient.request<{ success: boolean }>('POST', '/auth/magic-link', {\n body: params,\n });\n }\n\n async verifyMagicLink(\n params: MagicLinkVerifyParams,\n ): Promise<PalbaseResponse<{ user: User; session: Session }>> {\n const response = await this.httpClient.request<AuthResult>('POST', '/auth/magic-link/verify', {\n body: params,\n });\n\n if (response.data?.access_token) {\n const session = toSession(response.data);\n const user = toUser(response.data);\n this.setSessionAndWireRefresh(session);\n return { data: { user, session }, error: null, status: response.status };\n }\n\n return { data: null, error: response.error, status: response.status };\n }\n\n // ── Trusted Devices ─────────────────────────────────────\n\n async listTrustedDevices(): Promise<PalbaseResponse<{ trusted_devices: TrustedDevice[] }>> {\n return this.httpClient.request<{ trusted_devices: TrustedDevice[] }>(\n 'GET',\n '/auth/trusted-devices',\n );\n }\n\n async registerTrustedDevice(\n params: RegisterTrustedDeviceParams,\n ): Promise<PalbaseResponse<{ trusted_device_token: string }>> {\n return this.httpClient.request<{ trusted_device_token: string }>(\n 'POST',\n '/auth/trusted-devices',\n { body: params },\n );\n }\n\n async revokeTrustedDevice(deviceId: string): Promise<PalbaseResponse<{ success: boolean }>> {\n validatePathParam('deviceId', deviceId);\n return this.httpClient.request<{ success: boolean }>(\n 'DELETE',\n `/auth/trusted-devices/${deviceId}`,\n );\n }\n\n // ── Session State ───────────────────────────────────────\n\n getSession(): { data: Session | null; error: null } {\n return { data: this.currentSession, error: null };\n }\n\n onAuthStateChange(callback: AuthStateChangeCallback): {\n data: { subscription: { unsubscribe: () => void } };\n } {\n const unsubscribe = this.tokenManager.onAuthStateChange((event, session) => {\n let authEvent: AuthEvent;\n\n if (event === 'SESSION_SET') {\n authEvent = this.hasSession ? 'TOKEN_REFRESHED' : 'SIGNED_IN';\n this.hasSession = true;\n } else {\n authEvent = 'SIGNED_OUT';\n this.hasSession = false;\n }\n\n callback(authEvent, session);\n });\n\n return { data: { subscription: { unsubscribe } } };\n }\n\n // ── Server-only ─────────────────────────────────────────\n\n /**\n * Verify a user's JWT token by calling GET /auth/user with their token.\n * Server-only — should NOT be exposed in client SDK.\n */\n async verifyUserToken(jwt: string): Promise<PalbaseResponse<User>> {\n const url = `${this.baseUrl}/auth/user`;\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: 'GET',\n headers: {\n apikey: this.apiKey,\n Authorization: `Bearer ${jwt}`,\n 'Content-Type': 'application/json',\n },\n });\n } catch (error) {\n throw new PalbaseError(\n 'network_error',\n error instanceof Error ? error.message : 'Network request failed',\n 0,\n );\n }\n\n const contentType = response.headers.get('Content-Type');\n if (response.ok && contentType?.includes('application/json')) {\n const data = (await response.json()) as User;\n return { data, error: null, status: response.status };\n }\n\n let errorBody: { error?: string; error_description?: string } | undefined;\n if (contentType?.includes('application/json')) {\n errorBody = (await response.json()) as typeof errorBody;\n }\n\n return {\n data: null,\n error: new PalbaseError(\n errorBody?.error ?? 'invalid_token',\n errorBody?.error_description ?? 'Token verification failed',\n response.status,\n errorBody,\n ),\n status: response.status,\n };\n }\n\n // ── Private ─────────────────────────────────────────────\n\n private setSessionAndWireRefresh(session: Session): void {\n this.tokenManager.setSession(session);\n this.hasSession = true;\n\n this.tokenManager.refreshFunction = async (refreshToken: string): Promise<Session> => {\n const response = await this.httpClient.request<TokenResponse>('POST', '/auth/token/refresh', {\n body: { refresh_token: refreshToken },\n });\n\n if (response.error || !response.data) {\n throw response.error ?? new Error('Failed to refresh token');\n }\n\n return {\n accessToken: response.data.access_token,\n refreshToken: response.data.refresh_token,\n expiresAt: Date.now() + response.data.expires_in * 1000,\n };\n };\n }\n}\n","import type { HttpClient, PalbaseResponse } from '@palbase/core';\nimport type {\n AttestAndroidParams,\n AttestAndroidResult,\n AttestiOSParams,\n AttestiOSResult,\n BindDeviceParams,\n DeviceInfo,\n VerifyRequestSignatureParams,\n} from './types.js';\n\n/** Validates that a path parameter does not contain path traversal characters. */\nfunction validatePathParam(name: string, value: string): void {\n if (value.includes('/') || value.includes('..') || value.includes('%')) {\n throw new Error(`Invalid ${name}: must not contain path separators`);\n }\n}\n\nconst TOKEN_BUFFER_MS = 5 * 60 * 1000; // 5 min before expiry\n\n/**\n * Device attestation client.\n * Handles device challenge/attestation/bind and App Check JWT cache.\n */\nexport class DeviceClient {\n private readonly httpClient: HttpClient;\n private cachedToken: { token: string; expiresAt: number } | null = null;\n private refreshTimer: ReturnType<typeof setTimeout> | null = null;\n\n constructor(httpClient: HttpClient) {\n this.httpClient = httpClient;\n }\n\n /** Generate a device attestation challenge. */\n async generateChallenge(): Promise<PalbaseResponse<{ challenge: string }>> {\n return this.httpClient.request<{ challenge: string }>('POST', '/auth/devices/challenge');\n }\n\n /** Attest an Android device with a Play Integrity verdict token. */\n async attestAndroid(params: AttestAndroidParams): Promise<PalbaseResponse<AttestAndroidResult>> {\n return this.httpClient.request<AttestAndroidResult>('POST', '/auth/devices/attest/android', {\n body: params,\n });\n }\n\n /** Attest an iOS device with App Attest attestation data. */\n async attestiOS(params: AttestiOSParams): Promise<PalbaseResponse<AttestiOSResult>> {\n return this.httpClient.request<AttestiOSResult>('POST', '/auth/devices/attest/ios', {\n body: params,\n });\n }\n\n /** Bind a verified device with a public key for request signing. */\n async bind(params: BindDeviceParams): Promise<PalbaseResponse<{ success: boolean }>> {\n return this.httpClient.request<{ success: boolean }>('POST', '/auth/devices/bind', {\n body: params,\n });\n }\n\n /** List all devices for the current user. */\n async list(): Promise<PalbaseResponse<{ devices: DeviceInfo[] }>> {\n return this.httpClient.request<{ devices: DeviceInfo[] }>('GET', '/auth/devices');\n }\n\n /** Delete a device by ID. */\n async delete(deviceId: string): Promise<PalbaseResponse<{ success: boolean }>> {\n validatePathParam('deviceId', deviceId);\n return this.httpClient.request<{ success: boolean }>('DELETE', `/auth/devices/${deviceId}`);\n }\n\n /**\n * Verify a request signature from a device (server-only).\n * Should NOT be exposed in client SDK.\n */\n async verifyRequestSignature(\n deviceId: string,\n params: VerifyRequestSignatureParams,\n ): Promise<PalbaseResponse<{ verified: boolean }>> {\n validatePathParam('deviceId', deviceId);\n return this.httpClient.request<{ verified: boolean }>(\n 'POST',\n `/auth/devices/${deviceId}/verify`,\n { body: params },\n );\n }\n\n /** Get the cached App Check token, or null if not available / expired. */\n getToken(): string | null {\n if (!this.cachedToken) return null;\n if (Date.now() >= this.cachedToken.expiresAt) return null;\n return this.cachedToken.token;\n }\n\n /** Whether App Check is active (token cached and not expired). */\n get isActive(): boolean {\n return this.getToken() !== null;\n }\n\n /** Set a cached App Check token manually (e.g. after attest flow). */\n setCachedToken(token: string, expiresInMs: number): void {\n this.cachedToken = {\n token,\n expiresAt: Date.now() + expiresInMs,\n };\n this.scheduleRefresh(expiresInMs);\n }\n\n /** Clean up timers and cached state. */\n dispose(): void {\n if (this.refreshTimer) {\n clearTimeout(this.refreshTimer);\n this.refreshTimer = null;\n }\n this.cachedToken = null;\n }\n\n private scheduleRefresh(ttlMs: number): void {\n if (this.refreshTimer) {\n clearTimeout(this.refreshTimer);\n }\n const refreshIn = Math.max(ttlMs - TOKEN_BUFFER_MS, 0);\n this.refreshTimer = setTimeout(() => {\n this.cachedToken = null;\n }, refreshIn);\n }\n}\n","// `pb.analytics` — the stateful analytics surface. Mirrors the iOS\n// `AnalyticsClient`/`AnalyticsStorage` pair (palbackend-ios-src\n// Sources/Palbe/Analytics/): anonymous distinct-id, in-memory buffer with\n// size/timer flushing, identify/alias/reset/optOut, and auth-lifecycle\n// bindings. Transport is direct `palbeRequest` to palanalytics' live\n// ingestion wire (verified against modules/analytics Go handlers):\n//\n// POST /v1/analytics/batch {events:[{event,distinct_id,properties?,timestamp,sent_at}]}\n// POST /v1/analytics/capture single event (server mode — no buffering)\n// POST /v1/analytics/identify {distinct_id,anonymous_id,traits?,timestamp,sent_at}\n// POST /v1/analytics/alias {from,to,timestamp,sent_at}\n//\n// Ingestion is fire-and-forget (iOS K11): nothing here ever throws into the\n// caller — failures are console.warn'ed and the events dropped. The only\n// awaited surface is `flush()`, which still never rejects.\n//\n// Wire decisions (documented deviations):\n// - screen() emits the SERVER-canonical mapping (event `$screen` +\n// `properties.screen_name` — exactly what Go's /v1/analytics/screen\n// shorthand stores), NOT iOS's event-named-after-the-screen variant, so\n// batched screens and shorthand screens collapse to one stored shape.\n// - `sent_at` is stamped at SEND time (flush), not enqueue time like iOS:\n// the server's skew correction (`timestamp + (received_at - sent_at)`)\n// assumes send time; the iOS enqueue-time stamp inflates corrected\n// timestamps by the buffering delay.\n// - optOut gates ALL ingest calls (capture/screen/identify/alias) — iOS\n// parity. reset() and the X-Distinct-Id header are NOT gated (also iOS\n// parity: the header resolver never checks opt-out).\n\nimport type { AuthChangeEvent } from './auth-facade.js';\nimport { palbeRequest } from './request.js';\nimport type { PalbeRuntime } from './runtime.js';\n\n/** Free-form event property bag (JSON object on the wire). */\nexport type AnalyticsProperties = Record<string, unknown>;\n\n/** Server event-name rule — matches modules/analytics/internal/validator/event.go\n * eventNameRegex exactly: leading letter or `$`, max 64 chars total,\n * MaxEventNameLen=64 (i.e. body up to 63 chars after the leading char). */\nconst EVENT_NAME_RE = /^[a-zA-Z$][a-zA-Z0-9_.:$-]{0,63}$/;\n\nconst FLUSH_AT = 20;\nconst FLUSH_INTERVAL_MS = 10_000;\n/** Server hard cap: /v1/analytics/batch rejects > 100 events per request. */\nconst MAX_BATCH_SIZE = 100;\n\n/** A buffered capture event — distinct_id is stamped at CAPTURE time so the\n * anon id is preserved on the wire for pre-login events (the gateway's Bearer\n * override applies to the REQUEST's X-Distinct-Id header, not the event body).\n * `sent_at` is added at flush time. */\ninterface CaptureWire {\n event: string;\n distinct_id: string;\n properties?: AnalyticsProperties;\n timestamp: number;\n}\n\nlet warnedInvalidEventName = false;\n\nfunction warnInvalidEventName(name: string): void {\n if (warnedInvalidEventName) return;\n warnedInvalidEventName = true;\n console.warn(\n `palbe analytics: dropping event \"${name}\" — names must match ^[a-zA-Z$][a-zA-Z0-9_.:$-]{0,63}$ (warned once per process)`,\n );\n}\n\n/** Minimal persisted string store: localStorage in browsers, per-runtime\n * memory elsewhere (SSR anon ids are ephemeral by design). Browser detection\n * matches the facade (`document`, not localStorage presence) — server\n * runtimes must NEVER touch storage, even when a localStorage global exists\n * (the flags server-env contract pins that). */\ninterface StringStore {\n get(key: string): string | null;\n set(key: string, value: string): void;\n remove(key: string): void;\n}\n\nfunction defaultStringStore(): StringStore {\n if (typeof document !== 'undefined' && typeof localStorage !== 'undefined') {\n return {\n get: (key) => {\n try {\n return localStorage.getItem(key);\n } catch {\n return null;\n }\n },\n set: (key, value) => {\n try {\n localStorage.setItem(key, value);\n } catch {\n // quota/private mode — the id just won't survive a reload\n }\n },\n remove: (key) => {\n try {\n localStorage.removeItem(key);\n } catch {\n // ignore\n }\n },\n };\n }\n const mem = new Map<string, string>();\n return {\n get: (key) => mem.get(key) ?? null,\n set: (key, value) => {\n mem.set(key, value);\n },\n remove: (key) => {\n mem.delete(key);\n },\n };\n}\n\n/**\n * Runtime-scoped analytics identity — constructed EAGERLY in `buildRuntime`\n * (cheap: listener wiring only, no I/O) because the X-Distinct-Id header\n * interceptor must stamp every request from the first one, even when\n * `pb.analytics` is never touched (iOS: the resolver is wired at\n * PalBackend init). The facade wraps this state lazily.\n *\n * Auth events: while no facade exists, the state maintains identity only\n * (user id for the header, anon rotation on user-initiated sign-out). Once\n * the facade attaches, it takes over wholesale and adds the ingest\n * side-effects (flush + identify / flush + reset).\n */\nexport class AnalyticsState {\n identifiedUserId: string | null = null;\n /** Set by PalbeAnalytics on construction — delegates auth events to it. */\n facadeAuthHandler: ((event: AuthChangeEvent) => void) | null = null;\n\n private anonId: string | null = null;\n private readonly store = defaultStringStore();\n private readonly idKey: string;\n private readonly optOutKey: string;\n\n constructor(rt: PalbeRuntime) {\n const ref = rt.projectRef;\n this.idKey = ref ? `palbe.analytics.id.${ref}` : 'palbe.analytics.id';\n this.optOutKey = ref ? `palbe.analytics.optout.${ref}` : 'palbe.analytics.optout';\n rt.auth.onAuthEvent((event) => {\n if (this.facadeAuthHandler) {\n this.facadeAuthHandler(event);\n return;\n }\n if (event.type === 'signedIn') {\n // Identity for the header only — no identify POST without a facade\n // (an app that never uses pb.analytics generates no analytics\n // traffic). Opt-out gates the link, mirroring the gated identify.\n if (!this.isOptedOut()) this.identifiedUserId = event.user.id;\n } else if (event.type === 'signedOut' && event.reason === 'userInitiated') {\n this.identifiedUserId = null;\n this.rotateAnonymousId();\n }\n // sessionExpired / tokenRefreshed: identity preserved (iOS K9)\n });\n }\n\n /** User id when identified, else the stable anon id. NEVER empty —\n * this is what `identify()` links, so stamping it on every request lets\n * the trace pipeline stitch pre-login activity to the user. */\n distinctId(): string {\n return this.identifiedUserId ?? this.anonymousId();\n }\n\n anonymousId(): string {\n if (this.anonId) return this.anonId;\n const stored = this.store.get(this.idKey);\n if (stored) {\n this.anonId = stored;\n return stored;\n }\n return this.rotateAnonymousId();\n }\n\n rotateAnonymousId(): string {\n const fresh = crypto.randomUUID();\n this.store.set(this.idKey, fresh);\n this.anonId = fresh;\n return fresh;\n }\n\n isOptedOut(): boolean {\n return this.store.get(this.optOutKey) === 'true';\n }\n\n setOptOut(on: boolean): void {\n if (on) this.store.set(this.optOutKey, 'true');\n else this.store.remove(this.optOutKey);\n }\n}\n\nexport class PalbeAnalytics {\n private readonly buffer: CaptureWire[] = [];\n private flushTimer: ReturnType<typeof setTimeout> | null = null;\n /** Browser → buffer + size/timer flush. Server (no document) → immediate\n * per-call POST, zero timers (nothing leaks into RSC/route handlers). */\n private readonly browser = typeof document !== 'undefined';\n\n constructor(\n private readonly rt: PalbeRuntime,\n private readonly state: AnalyticsState,\n ) {\n this.state.facadeAuthHandler = (event) => {\n this.handleAuthEvent(event);\n };\n }\n\n /** Record an event. Invalid names are dropped (one warn per process). */\n capture(event: string, properties?: AnalyticsProperties): void {\n if (this.state.isOptedOut()) return;\n if (!EVENT_NAME_RE.test(event)) {\n warnInvalidEventName(event);\n return;\n }\n this.ingest(event, properties);\n }\n\n /** Record a screen view — server-canonical `$screen` + `screen_name`.\n * Validates non-empty only (the live wire at /v1/analytics/screen requires\n * non-empty screen_name; the event-name regex does NOT apply here). */\n screen(name: string, properties?: AnalyticsProperties): void {\n if (this.state.isOptedOut()) return;\n if (name.trim() === '') {\n console.warn('palbe analytics: dropping screen() — name must be non-empty');\n return;\n }\n this.ingest('$screen', { ...properties, screen_name: name });\n }\n\n /** Link the current anon id to `userId` and adopt it as the distinct id.\n * Immediate POST (not buffered). Re-identifying a DIFFERENT user\n * auto-resets first (iOS K10) so one anon id never links two users. */\n identify(userId: string, traits?: AnalyticsProperties): void {\n if (this.state.isOptedOut()) return;\n const existing = this.state.identifiedUserId;\n if (existing && existing !== userId) {\n console.warn(\n `palbe analytics: re-identify changes the user from ${existing} to ${userId} — auto-resetting (call reset() first if this is intentional)`,\n );\n this.reset();\n }\n const ts = Date.now();\n const body: Record<string, unknown> = {\n distinct_id: userId,\n anonymous_id: this.state.anonymousId(),\n timestamp: ts,\n sent_at: ts,\n };\n if (traits) body.traits = traits;\n this.state.identifiedUserId = userId;\n void this.post('/v1/analytics/identify', body);\n }\n\n /** Merge distinct id `from` into `to` (identity stitching). Immediate POST. */\n alias(from: string, to: string): void {\n if (this.state.isOptedOut()) return;\n const ts = Date.now();\n void this.post('/v1/analytics/alias', { from, to, timestamp: ts, sent_at: ts });\n }\n\n /** Drop the buffer, clear the identified user and rotate the anon id\n * (sign-out hygiene). Callers wanting pending events delivered flush first\n * — the auth binding does. */\n reset(): void {\n this.buffer.length = 0;\n this.cancelTimer();\n this.state.identifiedUserId = null;\n this.state.rotateAnonymousId();\n }\n\n /** Persisted GDPR opt-out. Opting out drops anything pending so nothing\n * already-buffered leaks after the user said stop. */\n setOptOut(on: boolean): void {\n this.state.setOptOut(on);\n if (on) {\n this.buffer.length = 0;\n this.cancelTimer();\n }\n }\n\n /** Drain the buffer to /v1/analytics/batch (≤100 per request, sequential).\n * Resolves when delivery finished; NEVER rejects — failed slices are\n * dropped with one warn per flush. 207 partial-reject is also warned once\n * (fire-and-forget: no retry for already-delivered batches). */\n async flush(): Promise<void> {\n this.cancelTimer();\n if (this.buffer.length === 0) return;\n const sentAt = Date.now();\n let warned = false;\n while (this.buffer.length > 0) {\n const slice = this.buffer.splice(0, MAX_BATCH_SIZE);\n try {\n const result = await palbeRequest<{ accepted?: number; rejected?: unknown[] }>(\n this.rt,\n 'POST',\n '/v1/analytics/batch',\n { body: { events: slice.map((e) => ({ ...e, sent_at: sentAt })) } },\n );\n if (Array.isArray(result?.rejected) && result.rejected.length > 0) {\n console.warn(\n `palbe analytics: batch partial-reject — ${result.rejected.length} event(s) rejected by server`,\n );\n }\n } catch (err) {\n if (!warned) {\n warned = true;\n console.warn('palbe analytics: batch flush failed — events dropped', err);\n }\n }\n }\n }\n\n // ── internals ──────────────────────────────────────────\n\n private ingest(event: string, properties?: AnalyticsProperties): void {\n const wire: CaptureWire = {\n event,\n distinct_id: this.state.distinctId(), // stamped at capture time\n timestamp: Date.now(),\n };\n if (properties) wire.properties = properties;\n if (!this.browser) {\n void this.post('/v1/analytics/capture', { ...wire, sent_at: wire.timestamp });\n return;\n }\n this.buffer.push(wire);\n if (this.buffer.length >= FLUSH_AT) void this.flush();\n else this.startTimer();\n }\n\n /** iOS PalBackend auth-binding parity: signedIn → flush + identify;\n * userInitiated signedOut → flush + reset; sessionExpired → flush only. */\n private handleAuthEvent(event: AuthChangeEvent): void {\n if (event.type === 'signedIn') {\n void this.flush().then(() => {\n this.identify(event.user.id);\n });\n } else if (event.type === 'signedOut') {\n if (event.reason === 'userInitiated') {\n void this.flush().then(() => {\n this.reset();\n });\n } else {\n void this.flush();\n }\n }\n // tokenRefreshed: nothing\n }\n\n private startTimer(): void {\n if (this.flushTimer !== null) return;\n this.flushTimer = setTimeout(() => {\n this.flushTimer = null;\n void this.flush();\n }, FLUSH_INTERVAL_MS);\n }\n\n private cancelTimer(): void {\n if (this.flushTimer !== null) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n }\n\n private async post(path: string, body: unknown): Promise<void> {\n try {\n await palbeRequest(this.rt, 'POST', path, { body });\n } catch (err) {\n console.warn(`palbe analytics: POST ${path} failed`, err);\n }\n }\n}\n","/**\n * `pb_<environmentRef>_<scope><random>` → environmentRef ('' if malformed).\n * Utility for callers that only have a raw key. App-bound runtime storage and\n * palbe/next cookie naming use the explicit `PalbeConfig.environmentRef` emitted\n * by `palbe-gen`.\n *\n * The key is shape-validated (prefix `pb`, a 4-24 character lowercase ASCII\n * alphanumeric environmentRef, and a `<scope><20-base62-random>` secret segment\n * where scope ∈ {c,s}) before a ref is returned. A structurally-invalid key returns\n * '' so session storage stays\n * UNSCOPED rather than getting bound to a partially-parsed ref — a loose parse\n * (e.g. `pb_victim_cXX`) must not scope persisted sessions to `victim` and\n * hydrate a session belonging to a different, real key. The backend remains the\n * authority that rejects a bad key on the first request; this keeps the client's\n * local session-storage scoping honest. (Audit finding apikey-ref-mismatch.)\n */\nconst API_KEY_RE = /^pb_([a-z0-9]{4,24})_[cs][A-Za-z0-9]{20}$/;\nconst PUBLISHABLE_API_KEY_RE = /^pb_([a-z0-9]{4,24})_c[A-Za-z0-9]{20}$/;\n\nexport function environmentRefFromApiKey(apiKey: string): string {\n const m = API_KEY_RE.exec(apiKey);\n return m ? (m[1] ?? '') : '';\n}\n\n/** Internal app-runtime parser: browser config accepts publishable keys only. */\nexport function environmentRefFromPublishableApiKey(apiKey: string): string {\n const match = PUBLISHABLE_API_KEY_RE.exec(apiKey);\n return match ? (match[1] ?? '') : '';\n}\n","/**\n * palauth AuthResult wire shape + its structural guard — shared by the auth\n * facade (sign-in / verify / code-exchange flows) and the palbe/next OAuth\n * callback handler, so both gate on the SAME shape rules.\n */\n\nexport interface WireUser {\n id: string;\n email?: string; // omitted for phone-only users\n email_verified: boolean;\n created_at: string;\n}\n\n/**\n * GET /auth/me. Distinct from WireUser (GET /auth/user): only this endpoint\n * reports which credentials the account actually has.\n *\n * `providers` is nullable ON PURPOSE and the difference carries meaning —\n * `[]` is \"asked, none linked\", `null` is \"could not determine\" (social login\n * unconfigured on the server, or the identity lookup failed). Collapsing null\n * to [] would report a social-only account as password-only, which is exactly\n * the wrong answer for the form this field exists to choose.\n */\nexport interface WireMe {\n has_password: boolean;\n has_mfa: boolean;\n providers: string[] | null;\n}\n\nexport interface WireAuthResult {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n user: WireUser;\n}\n\n/**\n * Structural guard for the palauth AuthResult wire shape. Consumers adopt\n * the session (persisting tokens to storage / cookies) BEFORE reading user\n * fields, so the full shape must be proven before adoption — a 200 body with\n * access_token but a missing/malformed user, refresh_token or expires_in\n * would otherwise half-adopt a zombie session (tokens stored, then TypeError\n * on user.id).\n */\nexport function asWireAuthResult(raw: unknown): WireAuthResult | null {\n if (typeof raw !== 'object' || raw === null) return null;\n const obj = raw as Record<string, unknown>;\n if (typeof obj.access_token !== 'string') return null;\n if (typeof obj.refresh_token !== 'string') return null;\n if (typeof obj.expires_in !== 'number') return null;\n const user = obj.user;\n if (typeof user !== 'object' || user === null) return null;\n if (typeof (user as Record<string, unknown>).id !== 'string') return null;\n return raw as WireAuthResult;\n}\n","// Passkeys (WebAuthn platform credentials) for the browser, surfaced through\n// `pb.auth.*` with the SAME names the iOS SDK uses — signInWithPasskey,\n// signUpWithPasskey, registerPasskey — so the two surfaces are learned once.\n//\n// A passkey is DISCOVERABLE, so signing in needs no e-mail, no password and no\n// code: the browser lists the accounts stored for this relying party and the\n// user picks one.\n//\n// The three palauth calls are the same ones iOS makes:\n// POST /auth/webauthn/login/begin {} → assertion options\n// POST /auth/webauthn/login/finish <WebAuthn JSON> → session\n// POST /auth/webauthn/signup/begin {email, user_name?} → creation options + user_id\n// POST /auth/webauthn/signup/finish?user_id=… <WebAuthn JSON>\n// POST /auth/webauthn/register/begin|finish → add to a signed-in account\n//\n// This module is the ONLY place that translates between palauth's WebAuthn JSON\n// (base64URL strings) and the ArrayBuffers `navigator.credentials` wants. There\n// is deliberately no dependency added for it: @simplewebauthn is Studio's\n// internal choice, and a customer bundle should not grow one for ~60 lines of\n// base64URL.\n//\n// CROSS-DOMAIN NOTE. The relying-party id is the Environment's own host. A page\n// served from the customer's OWN domain can still use those passkeys, but only\n// through WebAuthn Related Origin Requests — the browser fetches\n// https://<rpId>/.well-known/webauthn and checks this origin against it. ROR is\n// not universal: Firefox on Android has it OFF by default, and a client without\n// it throws SecurityError rather than degrading. So `isPasskeySupported()`\n// exists and every caller needs a non-passkey fallback.\n\nimport { asWireAuthResult, type WireAuthResult } from './auth-wire.js';\nimport { palbeRequest } from './request.js';\nimport type { PalbeRuntime } from './runtime.js';\n\n/** palauth wraps both ceremonies' options as `{options: {publicKey: {…}}}`. */\ninterface OptionsEnvelope {\n options: { publicKey: Record<string, unknown> };\n}\n\ninterface SignupBeginResponse extends OptionsEnvelope {\n user_id: string;\n}\n\n/**\n * Decode WebAuthn's base64URL (RFC 4648 §5, no padding). Plain atob rejects\n * these strings, which is how a passkey integration fails at the first\n * challenge with an error that names nothing.\n */\nfunction base64UrlToBytes(value: string): Uint8Array<ArrayBuffer> {\n const padded = value.replace(/-/g, '+').replace(/_/g, '/');\n const binary = atob(padded.padEnd(padded.length + ((4 - (padded.length % 4)) % 4), '='));\n const bytes = new Uint8Array(new ArrayBuffer(binary.length));\n for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);\n return bytes;\n}\n\n/** Encode as base64URL without padding — the only form WebAuthn accepts. */\nfunction bytesToBase64Url(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = '';\n for (const b of bytes) binary += String.fromCharCode(b);\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\nfunction requireString(source: Record<string, unknown>, key: string): string {\n const value = source[key];\n if (typeof value !== 'string' || value === '') {\n throw new Error(`passkey options are missing ${key}`);\n }\n return value;\n}\n\n/**\n * Turn palauth's creation options into the shape navigator.credentials.create\n * wants. Only the fields the ceremony requires are translated; anything else\n * the server sends rides along untouched.\n */\nfunction toCreationOptions(publicKey: Record<string, unknown>): PublicKeyCredentialCreationOptions {\n const rp = publicKey.rp as { id?: string; name?: string } | undefined;\n const user = publicKey.user as { id?: string; name?: string; displayName?: string } | undefined;\n if (!user?.id) {\n throw new Error('passkey options carry no user id — the credential would be unusable');\n }\n const excludeCredentials = Array.isArray(publicKey.excludeCredentials)\n ? (publicKey.excludeCredentials as Array<{ id: string; type: string }>).map((c) => ({\n id: base64UrlToBytes(c.id),\n type: 'public-key' as const,\n }))\n : undefined;\n\n return {\n challenge: base64UrlToBytes(requireString(publicKey, 'challenge')),\n rp: { id: rp?.id, name: rp?.name ?? rp?.id ?? '' },\n user: {\n id: base64UrlToBytes(user.id),\n name: user.name ?? '',\n displayName: user.displayName ?? user.name ?? '',\n },\n pubKeyCredParams:\n (publicKey.pubKeyCredParams as PublicKeyCredentialParameters[] | undefined) ?? [],\n authenticatorSelection: publicKey.authenticatorSelection as\n | AuthenticatorSelectionCriteria\n | undefined,\n timeout: publicKey.timeout as number | undefined,\n attestation: publicKey.attestation as AttestationConveyancePreference | undefined,\n excludeCredentials,\n };\n}\n\n/** Assertion options carry `rpId` at the top level, not `rp.id`. */\nfunction toRequestOptions(publicKey: Record<string, unknown>): PublicKeyCredentialRequestOptions {\n const allowCredentials = Array.isArray(publicKey.allowCredentials)\n ? (publicKey.allowCredentials as Array<{ id: string }>).map((c) => ({\n id: base64UrlToBytes(c.id),\n type: 'public-key' as const,\n }))\n : undefined;\n\n return {\n challenge: base64UrlToBytes(requireString(publicKey, 'challenge')),\n rpId: publicKey.rpId as string | undefined,\n timeout: publicKey.timeout as number | undefined,\n userVerification: publicKey.userVerification as UserVerificationRequirement | undefined,\n allowCredentials,\n };\n}\n\nfunction attestationToJSON(credential: PublicKeyCredential): Record<string, unknown> {\n const response = credential.response as AuthenticatorAttestationResponse;\n return {\n id: credential.id,\n rawId: bytesToBase64Url(credential.rawId),\n type: credential.type,\n response: {\n clientDataJSON: bytesToBase64Url(response.clientDataJSON),\n attestationObject: bytesToBase64Url(response.attestationObject),\n },\n };\n}\n\nfunction assertionToJSON(credential: PublicKeyCredential): Record<string, unknown> {\n const response = credential.response as AuthenticatorAssertionResponse;\n return {\n id: credential.id,\n rawId: bytesToBase64Url(credential.rawId),\n type: credential.type,\n response: {\n clientDataJSON: bytesToBase64Url(response.clientDataJSON),\n authenticatorData: bytesToBase64Url(response.authenticatorData),\n signature: bytesToBase64Url(response.signature),\n // The user handle is how palauth resolves WHO signed in on a discoverable\n // login — there is no identifier in the request.\n userHandle: response.userHandle ? bytesToBase64Url(response.userHandle) : null,\n },\n };\n}\n\n/**\n * Whether this browser can run a passkey ceremony at all.\n *\n * Callers MUST branch on this rather than assuming: a client without WebAuthn —\n * or without Related Origin Requests, when the page is on a different domain\n * than the relying party — throws instead of degrading, so the UI needs a\n * password or e-mail path beside the passkey button.\n */\nexport function isPasskeySupported(): boolean {\n return (\n typeof window !== 'undefined' &&\n typeof window.PublicKeyCredential === 'function' &&\n typeof navigator !== 'undefined' &&\n navigator.credentials !== undefined\n );\n}\n\nfunction ensureSupported(): void {\n if (!isPasskeySupported()) {\n throw new Error('This browser cannot use passkeys — offer a password or e-mail sign-in');\n }\n}\n\nasync function createCredential(publicKey: Record<string, unknown>): Promise<PublicKeyCredential> {\n const credential = await navigator.credentials.create({\n publicKey: toCreationOptions(publicKey),\n });\n if (credential === null) {\n throw new Error('the browser returned no credential');\n }\n return credential as PublicKeyCredential;\n}\n\nasync function getCredential(publicKey: Record<string, unknown>): Promise<PublicKeyCredential> {\n const credential = await navigator.credentials.get({\n publicKey: toRequestOptions(publicKey),\n });\n if (credential === null) {\n throw new Error('the browser returned no credential');\n }\n return credential as PublicKeyCredential;\n}\n\n/** Sign in with a passkey already stored for this relying party. */\nexport async function passkeySignIn(rt: PalbeRuntime): Promise<WireAuthResult> {\n ensureSupported();\n // An EMPTY body is what makes this discoverable: palauth reads no user_id and\n // asks the authenticator for whatever it holds for this relying party.\n const envelope = await palbeRequest<OptionsEnvelope>(rt, 'POST', '/auth/webauthn/login/begin', {\n body: {},\n });\n const credential = await getCredential(envelope.options.publicKey);\n const raw = await palbeRequest<unknown>(rt, 'POST', '/auth/webauthn/login/finish', {\n body: assertionToJSON(credential),\n });\n const result = asWireAuthResult(raw);\n if (result === null) {\n throw new Error('passkey sign-in returned an unusable session');\n }\n return result;\n}\n\n/**\n * Create an account whose only credential is a passkey — no password ever\n * exists for it.\n */\nexport async function passkeySignUp(\n rt: PalbeRuntime,\n email: string,\n displayName?: string,\n): Promise<WireAuthResult> {\n ensureSupported();\n const begun = await palbeRequest<SignupBeginResponse>(rt, 'POST', '/auth/webauthn/signup/begin', {\n body: { email, user_name: displayName },\n });\n const credential = await createCredential(begun.options.publicKey);\n // user_id travels as a QUERY parameter: palauth keys the pending ceremony by\n // it, and the account was created by signup/begin moments ago.\n await palbeRequest<unknown>(\n rt,\n 'POST',\n `/auth/webauthn/signup/finish?user_id=${encodeURIComponent(begun.user_id)}`,\n { body: attestationToJSON(credential) },\n );\n // signup/finish returns the CREDENTIAL, not a session: the account exists but\n // nobody is signed in. Signing in immediately is what a caller means by \"sign\n // up\", and it also proves the credential just created actually works.\n return await passkeySignIn(rt);\n}\n\n/**\n * Add a passkey to the account that is already signed in.\n *\n * Requires a RECENT sign-in: palauth answers 403 `reauthentication_required`\n * when the session is older than its freshness window, because a passkey keeps\n * working after a password reset and a merely-valid session must not be able to\n * enroll one. Callers should re-authenticate and retry.\n */\nexport async function passkeyRegister(rt: PalbeRuntime, name?: string): Promise<void> {\n ensureSupported();\n const envelope = await palbeRequest<OptionsEnvelope>(\n rt,\n 'POST',\n '/auth/webauthn/register/begin',\n { body: { name } },\n );\n const credential = await createCredential(envelope.options.publicKey);\n await palbeRequest<unknown>(rt, 'POST', '/auth/webauthn/register/finish', {\n body: attestationToJSON(credential),\n });\n}\n","import type { User as AuthClientUser } from '@palbase/auth';\nimport type { Session } from '@palbase/core';\nimport { asWireAuthResult, type WireAuthResult, type WireMe, type WireUser } from './auth-wire.js';\nimport { BackendError, unwrap } from './errors.js';\nimport { isPasskeySupported, passkeyRegister, passkeySignIn, passkeySignUp } from './passkey.js';\nimport { palbeRequest } from './request.js';\nimport type { PalbeRuntime } from './runtime.js';\n\nexport type MagicLinkResult =\n | ({ status: 'signedIn' } & AuthSuccess)\n | { status: 'mfaRequired'; mfaToken: string; factors: string[] };\n\nexport type OAuthExchangeResult =\n | ({ status: 'signedIn' } & AuthSuccess)\n | { status: 'mfaRequired'; mfaToken: string; factors: string[] };\n\nexport interface AuthUser {\n id: string;\n /** null for phone-only users (the server omits email). */\n email: string | null;\n emailVerified: boolean;\n createdAt: string;\n}\n\n/** What `pb.auth.getSignInMethods()` reports about an account's credentials. */\nexport interface SignInMethods {\n /** A local password exists — offer \"change password\", not \"set password\". */\n password: boolean;\n /** An MFA factor is enrolled. */\n mfa: boolean;\n /**\n * Linked OAuth providers, e.g. `['google']`. `[]` means none are linked;\n * `null` means the server could not determine it (social login is not\n * configured, or the lookup failed) — treat null as unknown, not as none.\n */\n providers: string[] | null;\n}\n\nexport interface AuthSuccess {\n user: AuthUser;\n session: Session;\n}\n\nexport type AuthState = { status: 'signedIn'; user: AuthUser } | { status: 'signedOut' };\n\nexport type AuthChangeEvent =\n | { type: 'signedIn'; user: AuthUser }\n | {\n type: 'signedOut';\n /**\n * `userInitiated` — pb.auth.signOut(); `sessionExpired` — a refresh\n * ultimately failed; `accountDeleted` — this device ran deleteAccount();\n * `sessionInvalid` — a server-side session kill (erased/revoked subject)\n * was observed on a request and the client tore down reactively.\n */\n reason: 'userInitiated' | 'sessionExpired' | 'accountDeleted' | 'sessionInvalid';\n }\n | { type: 'tokenRefreshed' };\n\nexport type Unsubscribe = () => void;\n\n/** Map the wire bare-user shape (GET /auth/user) to AuthUser. */\nfunction mapWireUser(raw: WireUser): AuthUser {\n return {\n id: raw.id,\n email: raw.email ?? null,\n emailVerified: raw.email_verified,\n createdAt: raw.created_at,\n };\n}\n\n/** Map @palbase/auth's camelCase User to palbe's AuthUser (drop extra fields). */\nfunction mapClientUser(u: AuthClientUser): AuthUser {\n // AuthClient types email as a required string, but the wire omits it for\n // phone-only users — normalize missing/empty to null at this boundary.\n return {\n id: u.id,\n email: u.email || null,\n emailVerified: u.emailVerified,\n createdAt: u.createdAt,\n };\n}\n\n/** Key-order-proof equality on the four AuthUser fields (refreshUser change detection). */\nfunction usersEqual(a: AuthUser, b: AuthUser | null): boolean {\n return (\n b !== null &&\n a.id === b.id &&\n a.email === b.email &&\n a.emailVerified === b.emailVerified &&\n a.createdAt === b.createdAt\n );\n}\n\nexport class PalbeAuth {\n private cachedUser: AuthUser | null = null;\n private signingOut = false;\n // Explicit signedOut reason pinned for the NEXT session clear (deleteAccount /\n // reactiveTeardown set it before clearSession; the SIGNED_OUT handler consumes\n // it exactly once, then resets to null → normal signOut/expiry split resumes).\n private nextSignOutReason: 'accountDeleted' | 'sessionInvalid' | null = null;\n private signingIn = false; // suppresses AuthClient's TOKEN_REFRESHED during re-signIn\n private signedInState = false; // dedupes AuthClient's repeated SIGNED_OUT events\n private hydratingUser = false; // guards hydrateUser() to a single boot fetch\n private readonly stateListeners = new Set<(state: AuthState) => void>();\n private readonly eventListeners = new Set<(event: AuthChangeEvent) => void>();\n private readonly userListeners = new Set<(user: AuthUser) => void>();\n\n constructor(private readonly rt: PalbeRuntime) {\n // AuthClient fires SIGNED_OUT on any session clear and TOKEN_REFRESHED on\n // refresh. SIGNED_IN is emitted by the facade itself (it knows the user);\n // after hydration AuthClient never fires SIGNED_IN anyway (hasSession=true).\n rt.authClient.onAuthStateChange((event, _session) => {\n if (event === 'SIGNED_OUT') {\n if (!this.signedInState) return; // duplicate clear — already signed out\n this.signedInState = false;\n this.cachedUser = null;\n // A caller (deleteAccount / reactiveTeardown) may pin an explicit reason\n // for THIS clear; otherwise fall back to the signOut-vs-expiry split.\n const reason =\n this.nextSignOutReason ?? (this.signingOut ? 'userInitiated' : 'sessionExpired');\n this.nextSignOutReason = null;\n this.emitState({ status: 'signedOut' });\n this.emitEvent({ type: 'signedOut', reason });\n } else if (event === 'TOKEN_REFRESHED') {\n // Re-signIn while signed in makes AuthClient map the new session to\n // TOKEN_REFRESHED (hasSession=true) — that would leak a tokenRefreshed\n // with the OLD user just before signedIn. iOS emits exactly one\n // signedIn per login; suppress it here.\n if (this.signingIn) return;\n this.emitEvent({ type: 'tokenRefreshed' });\n }\n });\n // Hydrated runtime starts in a signed-in-ish state (pending refresh).\n this.signedInState = rt.tokenManager.getRefreshToken() !== null;\n }\n\n // ── state ──────────────────────────────────────────────\n\n get currentUser(): AuthUser | null {\n return this.cachedUser;\n }\n\n get isSignedIn(): boolean {\n return this.rt.tokenManager.getRefreshToken() !== null;\n }\n\n // ── core flows ─────────────────────────────────────────\n\n async signUp(params: { email: string; password: string }): Promise<AuthSuccess> {\n this.signingIn = true;\n try {\n const data = unwrap(await this.rt.authClient.signUp(params));\n return this.adopt(data);\n } finally {\n this.signingIn = false;\n }\n }\n\n async signIn(params: { email: string; password: string }): Promise<AuthSuccess> {\n this.signingIn = true;\n try {\n const data = unwrap(await this.rt.authClient.signIn(params));\n return this.adopt(data);\n } finally {\n this.signingIn = false;\n }\n }\n\n async signOut(): Promise<void> {\n this.signingOut = true;\n try {\n await this.rt.authClient.signOut();\n } catch {\n // local sign-out must win even when the server call fails (offline etc.)\n } finally {\n this.rt.tokenManager.clearSession(); // idempotent; deduped by signedInState\n this.signingOut = false; // AFTER clear → reason stays userInitiated\n this.rt.storage.clear();\n this.cachedUser = null;\n }\n }\n\n /**\n * Self-service account erasure (\"right to be forgotten\"). Calls palauth\n * `DELETE /auth/user` (session-authed + fresh re-auth): password users pass\n * `{ password }`; passwordless/OAuth users rely on a freshly stepped-up\n * session. On 202 the erasure workflow is durably queued AND every session is\n * already revoked server-side — so we tear down local state (mirroring\n * `signOut()`'s clear, minus the pointless `/logout`) and emit\n * `signedOut{reason:'accountDeleted'}`.\n *\n * On 401 `reauth_required` / 503 `not_configured` (or `erasure_unavailable`)\n * the request throws BEFORE the teardown line, so the local session is left\n * fully intact — the deletion did not happen and the user is still signed in.\n */\n async deleteAccount(params: { password?: string } = {}): Promise<{ erasureId: string }> {\n const res = await palbeRequest<{ erasure_id: string }>(this.rt, 'DELETE', '/auth/user', {\n body: params.password ? { password: params.password } : {},\n });\n // Reached only on 202 (2xx): sessions already revoked server-side.\n this.nextSignOutReason = 'accountDeleted';\n this.rt.tokenManager.clearSession(); // fires SIGNED_OUT → emits accountDeleted\n this.rt.storage.clear();\n this.cachedUser = null;\n return { erasureId: res.erasure_id };\n }\n\n /**\n * @internal — invoked by the transport choke point (`request.ts`), not app code.\n * Reactive teardown when the server reports the session is gone: a 403\n * `subject_fenced`/`subject_erased` (subject being erased) or a 401\n * `session_revoked`. Clears local state and emits\n * `signedOut{reason:'sessionInvalid'}` exactly once (deduped by\n * `signedInState`). An ordinary 403 authz denial does NOT reach here —\n * `request.ts` matches only the exact session-kill codes.\n */\n reactiveTeardown(): void {\n if (!this.signedInState) return; // already torn down — exactly-once\n this.nextSignOutReason = 'sessionInvalid';\n this.rt.tokenManager.clearSession(); // fires SIGNED_OUT → emits sessionInvalid\n this.rt.storage.clear();\n this.cachedUser = null;\n }\n\n async getUser(): Promise<AuthUser> {\n const raw = await palbeRequest<WireUser>(this.rt, 'GET', '/auth/user');\n return mapWireUser(raw);\n }\n\n /**\n * Which credentials this account actually has.\n *\n * An account reached through Google has no password, so showing it a\n * \"current password\" field gives the user something they can never fill —\n * and `getUser()` cannot tell you, because GET /auth/user carries no\n * credential information. Read this before rendering any re-auth or\n * change-password form.\n */\n async getSignInMethods(): Promise<SignInMethods> {\n const raw = await palbeRequest<WireMe>(this.rt, 'GET', '/auth/me');\n return {\n password: raw.has_password,\n mfa: raw.has_mfa,\n providers: raw.providers ?? null,\n };\n }\n\n async refreshUser(): Promise<AuthUser> {\n const user = await this.getUser();\n const changed = !usersEqual(user, this.cachedUser);\n this.cachedUser = user;\n if (changed) for (const cb of this.userListeners) this.safeInvoke(() => cb(user));\n return user;\n }\n\n /**\n * Restore the user after a session was rehydrated from storage (page reload).\n * Hydration in buildRuntime restores the TOKENS synchronously, but the access\n * token JWT does not carry emailVerified/createdAt, so the full AuthUser can't\n * be reconstructed offline — this fetches GET /auth/user once and announces\n * `signedIn`, so the app no longer has to `await pb.auth.refreshUser()` itself\n * on boot. Idempotent (runs once), browser-safe (no-op when not signed in or\n * a user is already cached), and never throws (a boot-time network failure\n * must not break app startup — isSignedIn stays true, the app can retry).\n */\n hydrateUser(): void {\n if (this.hydratingUser || this.cachedUser || !this.isSignedIn) return;\n this.hydratingUser = true;\n void this.refreshUser()\n .then((user) => {\n // Only announce if we're still signed in (a sign-out may have raced).\n if (this.isSignedIn) {\n this.signedInState = true;\n this.emitState({ status: 'signedIn', user });\n }\n })\n .catch(() => {\n // Swallow: a failed boot fetch leaves isSignedIn true; the app can\n // call refreshUser() again or the next authed request will 401→refresh.\n })\n .finally(() => {\n this.hydratingUser = false;\n });\n }\n\n // ── listeners ──────────────────────────────────────────\n\n /**\n * Subscribe to signed-in/signed-out state. Fires immediately with the\n * current snapshot (iOS parity). A restored session (page reload) hydrates\n * the user asynchronously via `hydrateUser()` (fired once at boot), so the\n * FIRST snapshot may report signedOut for a tick even when `isSignedIn` is\n * true; the listener then fires again with `signedIn` once the user lands.\n * Rely on `isSignedIn` for the session truth if you need it synchronously.\n */\n onAuthStateChange(callback: (state: AuthState) => void): Unsubscribe {\n this.stateListeners.add(callback);\n this.safeInvoke(() => callback(this.snapshotState()));\n return () => this.stateListeners.delete(callback);\n }\n\n onAuthEvent(callback: (event: AuthChangeEvent) => void): Unsubscribe {\n this.eventListeners.add(callback);\n return () => this.eventListeners.delete(callback);\n }\n\n /** Subscribe to user-profile changes. Replays the cached user on subscribe (iOS parity). */\n onUserChange(callback: (user: AuthUser) => void): Unsubscribe {\n this.userListeners.add(callback);\n const u = this.cachedUser;\n if (u) this.safeInvoke(() => callback(u));\n return () => this.userListeners.delete(callback);\n }\n\n // ── internals ──────────────────────────────────────────\n\n private snapshotState(): AuthState {\n return this.cachedUser && this.isSignedIn\n ? { status: 'signedIn', user: this.cachedUser }\n : { status: 'signedOut' };\n }\n\n /**\n * Listener exception isolation: a throwing consumer callback must never\n * break delivery to other listeners nor propagate into the emit caller\n * (tokenManager.clearSession callers, pb.call paths, signOut, ...).\n */\n private safeInvoke(fn: () => void): void {\n try {\n fn();\n } catch {\n // consumer callback errors are intentionally swallowed\n }\n }\n\n private emitState(state: AuthState): void {\n for (const cb of this.stateListeners) this.safeInvoke(() => cb(state));\n }\n\n private emitEvent(event: AuthChangeEvent): void {\n for (const cb of this.eventListeners) this.safeInvoke(() => cb(event));\n }\n\n /** Cache the user + announce sign-in. Session was already set by AuthClient. */\n private adopt(data: { user: AuthClientUser; session: Session }): AuthSuccess {\n const user = mapClientUser(data.user);\n this.cachedUser = user;\n this.signedInState = true;\n this.emitState({ status: 'signedIn', user });\n this.emitEvent({ type: 'signedIn', user });\n return { user, session: data.session };\n }\n\n /** Adopt a raw palauth AuthResult wire shape: wire tokens, cache user, announce. */\n private adoptWire(raw: WireAuthResult): AuthSuccess {\n this.rt.authClient.setTokens(raw.access_token, raw.refresh_token, raw.expires_in);\n const user = mapWireUser(raw.user);\n this.cachedUser = user;\n this.signedInState = true;\n this.emitState({ status: 'signedIn', user });\n this.emitEvent({ type: 'signedIn', user });\n const session: Session = {\n accessToken: raw.access_token,\n refreshToken: raw.refresh_token,\n expiresAt: Date.now() + raw.expires_in * 1000,\n };\n return { user, session };\n }\n\n /** Run a sign-in flow under the signingIn flag to suppress spurious tokenRefreshed. */\n private async withSigningIn<T>(fn: () => Promise<T>): Promise<T> {\n this.signingIn = true;\n try {\n return await fn();\n } finally {\n this.signingIn = false;\n }\n }\n\n /**\n * Defensively decode a 200 union body (AuthResult | mfa-required) and\n * complete the flow. The wire contract promises one of the two shapes, but\n * a literal-`null` (or otherwise malformed) JSON body must surface as\n * BackendError('decode') — never a raw TypeError from probing a non-object.\n * The mfa branch validates its fields: `mfa_token` is required; the factor\n * list (named `factors` by magic-link verify, `mfa_factors` by the OAuth\n * callback — extraction-verified against palauth) tolerates a missing or\n * malformed value as []. The signedIn branch validates the FULL AuthResult\n * shape (asWireAuthResult) — an incomplete body falls through to decode,\n * never half-adopts.\n */\n private completeAuthUnion(\n raw: unknown,\n factorsKey: 'factors' | 'mfa_factors',\n context: string,\n ): MagicLinkResult {\n if (typeof raw === 'object' && raw !== null) {\n const obj = raw as Record<string, unknown>;\n if (obj.mfa_required === true) {\n if (typeof obj.mfa_token === 'string') {\n const list = obj[factorsKey];\n const factors = Array.isArray(list)\n ? list.filter((f): f is string => typeof f === 'string')\n : [];\n return { status: 'mfaRequired', mfaToken: obj.mfa_token, factors };\n }\n // mfa_required without a usable mfa_token is unrecoverable — fall\n // through to decode (NOT the signedIn branch).\n } else {\n const wire = asWireAuthResult(raw);\n if (wire) return { status: 'signedIn', ...this.adoptWire(wire) };\n }\n }\n // 200 body matching neither shape — never fabricate a result.\n throw this.decodeError(context);\n }\n\n private decodeError(context: string): BackendError {\n return new BackendError('decode', {\n code: 'decode_error',\n message: `Unexpected ${context} response`,\n });\n }\n\n // ── additional auth flows ──────────────────────────────\n\n async signInWithOTP(params: { phone: string }): Promise<void> {\n // The server only supports the 'sms' channel (rejects anything else).\n await palbeRequest(this.rt, 'POST', '/auth/otp', {\n body: { phone: params.phone, channel: 'sms' },\n });\n }\n\n async verifyOTP(params: { phone: string; token: string }): Promise<AuthSuccess> {\n return this.withSigningIn(async () => {\n const raw = await palbeRequest<unknown>(this.rt, 'POST', '/auth/otp/verify', {\n body: params,\n });\n // The OTP-verify wire is AuthResult ONLY (palauth phoneLoginResponse —\n // no MFA union on this route): the same zombie-session guard applies,\n // and an mfa-shaped body here is a decode error, not an MFA result.\n const wire = asWireAuthResult(raw);\n if (!wire) throw this.decodeError('OTP verify');\n return this.adoptWire(wire);\n });\n }\n\n async resetPassword(email: string): Promise<void> {\n unwrap(await this.rt.authClient.requestPasswordReset({ email }));\n }\n\n async confirmPasswordReset(params: { token: string; newPassword: string }): Promise<void> {\n unwrap(\n await this.rt.authClient.confirmPasswordReset({\n token: params.token,\n new_password: params.newPassword,\n }),\n );\n }\n\n async updatePassword(params: { currentPassword: string; newPassword: string }): Promise<void> {\n unwrap(\n await this.rt.authClient.changePassword({\n current_password: params.currentPassword,\n new_password: params.newPassword,\n }),\n );\n }\n\n /**\n * Give a passwordless account its FIRST password, in-app.\n *\n * For accounts created with a passkey or a social provider: they have no\n * current password, so `updatePassword` cannot serve them — it sends one and\n * the server answers 401. Before this existed the only route was the\n * password-RESET e-mail, which asks someone already signed in to go find\n * their inbox.\n *\n * Requires a RECENT sign-in, not merely a session: a first password is a\n * credential that outlives the rest, so the server answers 403\n * `reauthentication_required` on a stale one — the same gate\n * `registerPasskey` carries. An account that already has a password gets 409\n * `password_already_set`; change it with `updatePassword` instead.\n *\n * The session stays valid — no re-login.\n */\n async setPassword(newPassword: string): Promise<void> {\n unwrap(await this.rt.authClient.setPassword({ new_password: newPassword }));\n }\n\n async verifyEmail(params: { token: string } | { code: string; email: string }): Promise<void> {\n unwrap(await this.rt.authClient.verifyEmail(params));\n }\n\n async resendVerification(email: string): Promise<void> {\n unwrap(await this.rt.authClient.resendVerification(email));\n }\n\n async signInWithMagicLink(email: string): Promise<void> {\n unwrap(await this.rt.authClient.requestMagicLink({ email }));\n }\n\n async verifyMagicLink(token: string): Promise<MagicLinkResult> {\n return this.withSigningIn(async () => {\n const raw = await palbeRequest<unknown>(this.rt, 'POST', '/auth/magic-link/verify', {\n body: { token },\n });\n return this.completeAuthUnion(raw, 'factors', 'magic-link verify');\n });\n }\n\n async signInWithCredential(params: {\n provider: string;\n credential: string;\n }): Promise<AuthSuccess> {\n return this.withSigningIn(async () => {\n const data = unwrap(await this.rt.authClient.signInWithCredential(params));\n return this.adopt(data);\n });\n }\n\n /**\n * Sign in with a passkey stored for this Environment. No identifier is typed:\n * the browser lists the accounts it holds and the user picks one.\n *\n * Throws when the browser cannot run a ceremony — check `passkeysSupported`\n * first and keep a password or e-mail path beside the button.\n */\n async signInWithPasskey(): Promise<AuthSuccess> {\n return this.withSigningIn(async () => this.adoptWire(await passkeySignIn(this.rt)));\n }\n\n /**\n * Create an account whose only credential is a passkey — the e-mail is the\n * identifier, the passkey is the credential, and no password ever exists.\n */\n async signUpWithPasskey(email: string, displayName?: string): Promise<AuthSuccess> {\n return this.withSigningIn(async () =>\n this.adoptWire(await passkeySignUp(this.rt, email, displayName)),\n );\n }\n\n /**\n * Add a passkey to the signed-in account, so the next sign-in is one tap.\n *\n * Rejects with a 403 `reauthentication_required` when the session is not\n * recent: a passkey outlives a password reset, so enrolling one takes more\n * than a valid session.\n */\n async registerPasskey(name?: string): Promise<void> {\n await passkeyRegister(this.rt, name);\n }\n\n /** Whether this browser can run a passkey ceremony at all. */\n get passkeysSupported(): boolean {\n return isPasskeySupported();\n }\n\n async signInWithOAuth(params: {\n provider: string;\n redirectTo?: string;\n /** Set false to skip the browser redirect (popup/manual flows). Default true. */\n redirect?: boolean;\n }): Promise<{ url: string }> {\n const { redirect = true, ...opts } = params;\n const { url } = unwrap(await this.rt.authClient.getOAuthURL(opts));\n if (redirect && typeof window !== 'undefined' && typeof window.location !== 'undefined') {\n window.location.assign(url);\n }\n return { url };\n }\n\n /**\n * Complete the OAuth redirect flow: trade the provider's `code` + `state`\n * (from the app's redirect_uri query) for a session. PKCE verifier + state\n * live server-side in palauth — the client only relays the two values.\n */\n async exchangeCodeForSession(params: {\n provider: string;\n code: string;\n state: string;\n }): Promise<OAuthExchangeResult> {\n return this.withSigningIn(async () => {\n const path =\n `/auth/oauth/${encodeURIComponent(params.provider)}/callback` +\n `?code=${encodeURIComponent(params.code)}&state=${encodeURIComponent(params.state)}`;\n const raw = await palbeRequest<unknown>(this.rt, 'GET', path);\n return this.completeAuthUnion(raw, 'mfa_factors', 'OAuth code-exchange');\n });\n }\n\n /** Config-as-code gate for the zero-arg provider sugar. */\n private requireProvider(name: 'google' | 'apple', label: string): void {\n if (!this.rt.config.oauth?.[name]?.enabled) {\n throw new BackendError('validation', {\n code: `${name}_not_configured`,\n message: `${label} sign-in is not configured: enable the ${label} provider for this project, then regenerate palbe.gen.ts.`,\n });\n }\n }\n\n async signInWithGoogle(opts?: { redirectTo?: string; redirect?: boolean }): Promise<{\n url: string;\n }> {\n this.requireProvider('google', 'Google');\n return this.signInWithOAuth({ provider: 'google', ...opts });\n }\n\n async signInWithApple(opts?: { redirectTo?: string; redirect?: boolean }): Promise<{\n url: string;\n }> {\n this.requireProvider('apple', 'Apple');\n return this.signInWithOAuth({ provider: 'apple', ...opts });\n }\n}\n","// `pb.calls` — SP-4 voice/video call surface for the web SDK.\n//\n// Architecture: thin wrapper around the embedded media engine (browser WebRTC) +\n// the messaging server's call REST endpoints:\n// POST /v1/messaging/groups/{gid}/calls → start a call\n// POST /v1/messaging/groups/{gid}/calls/{cid}/accept → join a call\n// POST /v1/messaging/groups/{gid}/calls/{cid}/decline → decline\n// POST /v1/messaging/groups/{gid}/calls/{cid}/mute → mute/unmute (host)\n// POST /v1/messaging/groups/{gid}/calls/{cid}/kick → kick (host)\n//\n// The {gid} is a messaging group display-id (`grp_<uuidv7>`).\n//\n// ─── E2EE NOTE ─────────────────────────────────────────────────────────────\n// The media engine's hop-by-hop transport is already DTLS-SRTP encrypted.\n// Frame-level end-to-end encryption (MLS-derived keys via the engine's external\n// key provider) is the SP-1.x web-MLS work and is OUT OF SCOPE for this surface.\n// The `setMediaKey` method is wired to that key provider as a design seam; call\n// it AFTER web-MLS delivers a shared frame key. Until then, rooms connect without\n// e2ee.\n// TODO(SP-1-web): derive the frame key from the MLS exporter and call\n// call.setMediaKey(derivedKeyArrayBuffer)\n// ───────────────────────────────────────────────────────────────────────────\n\nimport {\n ExternalE2EEKeyProvider,\n type RemoteParticipant,\n type RemoteTrack,\n type RemoteTrackPublication,\n Room,\n RoomEvent,\n Track,\n} from 'livekit-client';\nimport type { Unsubscribe } from '../auth-facade.js';\nimport { BackendError } from '../errors.js';\nimport { palbeRequest } from '../request.js';\nimport type { PalbeRuntime } from '../runtime.js';\n\n// ─── Wire types ────────────────────────────────────────────────────────────\n\ninterface StartCallResponse {\n call_id: string;\n token: string;\n edge_url: string;\n max_members: number;\n}\n\ninterface AcceptCallResponse {\n token: string;\n edge_url: string;\n}\n\n// ─── Public types ──────────────────────────────────────────────────────────\n\nexport type CallState = 'connecting' | 'active' | 'ended';\n\nexport interface CallParticipant {\n identity: string;\n isSpeaking: boolean;\n audioMuted: boolean;\n videoEnabled: boolean;\n /** The live <video> element for this participant, or null before track arrives. */\n videoElement: HTMLVideoElement | null;\n /** The live <audio> element for this participant, or null before track arrives. */\n audioElement: HTMLAudioElement | null;\n}\n\nexport type CallChangeCallback = (call: Call) => void;\n\n/**\n * A live call session. Obtain via `pb.calls.start()` or `pb.calls.accept()`.\n *\n * Lifecycle:\n * connecting → active (room joined, tracks flowing)\n * active → ended (leave() / remote disconnect)\n *\n * Subscribe to changes via `call.subscribe(cb)` which fires on state or\n * participant changes (mirrors flags `onChange` / realtime `on` pattern).\n */\nexport class Call {\n readonly id: string;\n\n private _state: CallState = 'connecting';\n private readonly _participants = new Map<string, CallParticipant>();\n private readonly _listeners = new Set<CallChangeCallback>();\n private readonly _room: Room;\n private readonly _keyProvider: ExternalE2EEKeyProvider;\n\n // PRIVATE constructor + `@internal` factory: the media-engine types (`Room`,\n // `ExternalE2EEKeyProvider`) appear ONLY on a private constructor and an\n // `@internal` static factory, both of which `stripInternal` (set in this\n // package's tsconfig) removes from the emitted public `.d.ts`. So no engine type\n // ever reaches the public API surface — the same \"no engine type in the public\n // interface\" contract the iOS SDK enforces. Consumers obtain a `Call` only\n // through `pb.calls.start()` / `.accept()`.\n private constructor(callId: string, room: Room, keyProvider: ExternalE2EEKeyProvider) {\n this.id = callId;\n this._room = room;\n this._keyProvider = keyProvider;\n this._wireMediaEvents();\n }\n\n /** @internal — facade-only factory; stripped from the public `.d.ts`. */\n static _create(callId: string, room: Room, keyProvider: ExternalE2EEKeyProvider): Call {\n return new Call(callId, room, keyProvider);\n }\n\n // ─── State ──────────────────────────────────────────────────────────────\n\n get state(): CallState {\n return this._state;\n }\n\n /** Snapshot of current remote participants (does NOT include the local participant). */\n get participants(): readonly CallParticipant[] {\n return Array.from(this._participants.values());\n }\n\n // (No `localParticipant` accessor: it would return a raw media-engine participant\n // object and leak the engine type onto the public API. Local mic/camera control is\n // exposed engine-agnostically via `mute()` / `setCamera()` below.)\n\n // ─── Subscribe ──────────────────────────────────────────────────────────\n\n /**\n * Subscribe to call changes (state + participant updates).\n * Returns an `Unsubscribe` function — matches the SDK's flags/auth pattern.\n */\n subscribe(callback: CallChangeCallback): Unsubscribe {\n this._listeners.add(callback);\n return () => {\n this._listeners.delete(callback);\n };\n }\n\n // ─── Controls ───────────────────────────────────────────────────────────\n\n /** Mute / unmute the local microphone. */\n async mute(on: boolean): Promise<void> {\n await this._room.localParticipant.setMicrophoneEnabled(!on);\n }\n\n /** Enable / disable the local camera. */\n async setCamera(on: boolean): Promise<void> {\n await this._room.localParticipant.setCameraEnabled(on);\n }\n\n /**\n * Set a raw frame encryption key (ArrayBuffer) for this call.\n * Design seam for SP-1.x web-MLS: call this with the MLS exporter secret\n * to enable frame-level E2EE via the media engine's external key provider.\n *\n * NOTE: The room must have been opened with `e2ee` options for this to take\n * effect. Currently calls connect WITHOUT e2ee — see module docstring.\n *\n * TODO(SP-1-web): wire this to MLS-derived key on session join.\n */\n async setMediaKey(key: ArrayBuffer): Promise<void> {\n await this._keyProvider.setKey(key);\n }\n\n /** Leave the call and disconnect from the media room. */\n async leave(): Promise<void> {\n await this._room.disconnect();\n this._setState('ended');\n }\n\n // ─── Internal ───────────────────────────────────────────────────────────\n\n private _setState(next: CallState): void {\n if (this._state === next) return;\n this._state = next;\n this._emit();\n }\n\n private _emit(): void {\n for (const cb of this._listeners) cb(this);\n }\n\n private _upsertParticipant(p: RemoteParticipant): void {\n const existing = this._participants.get(p.identity);\n const entry: CallParticipant = {\n identity: p.identity,\n isSpeaking: p.isSpeaking,\n audioMuted: !p.isMicrophoneEnabled,\n videoEnabled: p.isCameraEnabled,\n videoElement: existing?.videoElement ?? null,\n audioElement: existing?.audioElement ?? null,\n };\n this._participants.set(p.identity, entry);\n }\n\n private _attachTrack(track: RemoteTrack, participant: RemoteParticipant): void {\n const element = track.attach();\n const entry = this._participants.get(participant.identity);\n if (track.kind === Track.Kind.Video) {\n if (entry) {\n entry.videoElement = element as HTMLVideoElement;\n }\n } else if (track.kind === Track.Kind.Audio) {\n if (entry) {\n entry.audioElement = element as HTMLAudioElement;\n // Auto-play audio (browser policy: audio elements can autoplay when\n // created by a user gesture chain; the call starts from a user tap).\n (element as HTMLAudioElement).play().catch(() => {});\n }\n }\n this._emit();\n }\n\n private _wireMediaEvents(): void {\n this._room\n .on(RoomEvent.Connected, () => {\n this._setState('active');\n })\n .on(\n RoomEvent.TrackSubscribed,\n (track: RemoteTrack, _pub: RemoteTrackPublication, participant: RemoteParticipant) => {\n this._upsertParticipant(participant);\n this._attachTrack(track, participant);\n },\n )\n .on(\n RoomEvent.TrackUnsubscribed,\n (_track: RemoteTrack, _pub: RemoteTrackPublication, participant: RemoteParticipant) => {\n this._upsertParticipant(participant);\n this._emit();\n },\n )\n .on(RoomEvent.ParticipantConnected, (participant: RemoteParticipant) => {\n this._upsertParticipant(participant);\n this._emit();\n })\n .on(RoomEvent.ParticipantDisconnected, (participant: RemoteParticipant) => {\n this._participants.delete(participant.identity);\n this._emit();\n })\n .on(RoomEvent.ActiveSpeakersChanged, (speakers) => {\n for (const [id, entry] of this._participants) {\n entry.isSpeaking = speakers.some((s) => s.identity === id);\n }\n this._emit();\n })\n .on(RoomEvent.Disconnected, () => {\n this._setState('ended');\n });\n }\n}\n\n// ─── Facade ────────────────────────────────────────────────────────────────\n\nexport class PalbeCalls {\n private readonly rt: PalbeRuntime;\n\n constructor(rt: PalbeRuntime) {\n this.rt = rt;\n }\n\n /**\n * Start a new call in a messaging group.\n *\n * @param groupId - Messaging group display-id (`grp_<uuidv7>`)\n * @param media - Which media tracks to publish (default: audio + video)\n * @returns A `Call` in `connecting` state; transitions to `active` once the media room connects.\n *\n * @throws BackendError('network', { code: 'call_service_unavailable' }) if the SFU is down (503)\n * @throws BackendError('forbidden', { code: 'not_group_member' }) if the caller is not a member (403)\n * @throws BackendError('conflict', { code: 'call_room_full' }) if the room is full (409)\n */\n async start(\n groupId: string,\n { media = ['audio', 'video'] }: { media?: Array<'audio' | 'video'> } = {},\n ): Promise<Call> {\n if (!groupId) {\n throw new BackendError('validation', {\n code: 'invalid_group_id',\n message: 'groupId must be a non-empty string',\n });\n }\n const res = await palbeRequest<StartCallResponse>(\n this.rt,\n 'POST',\n `/v1/messaging/groups/${encodeURIComponent(groupId)}/calls`,\n { body: { media } },\n );\n return this._connect(res.call_id, res.token, res.edge_url, media);\n }\n\n /**\n * Accept an incoming call in a messaging group.\n *\n * @param groupId - Messaging group display-id\n * @param callId - Call identifier (from the push notification payload)\n * @param media - Which media tracks to publish (default: audio + video)\n */\n async accept(\n groupId: string,\n callId: string,\n { media = ['audio', 'video'] }: { media?: Array<'audio' | 'video'> } = {},\n ): Promise<Call> {\n const res = await palbeRequest<AcceptCallResponse>(\n this.rt,\n 'POST',\n `/v1/messaging/groups/${encodeURIComponent(groupId)}/calls/${encodeURIComponent(callId)}/accept`,\n { body: {} },\n );\n return this._connect(callId, res.token, res.edge_url, media);\n }\n\n /**\n * Decline an incoming call.\n *\n * @param groupId - Messaging group display-id\n * @param callId - Call identifier\n * @param reason - Optional decline reason string\n */\n async decline(groupId: string, callId: string, reason?: string): Promise<void> {\n await palbeRequest<void>(\n this.rt,\n 'POST',\n `/v1/messaging/groups/${encodeURIComponent(groupId)}/calls/${encodeURIComponent(callId)}/decline`,\n { body: { reason: reason ?? '' } },\n );\n }\n\n // ─── Internal ─────────────────────────────────────────────────────────\n\n private async _connect(\n callId: string,\n token: string,\n edgeUrl: string,\n media: Array<'audio' | 'video'>,\n ): Promise<Call> {\n // ExternalE2EEKeyProvider is the MLS seam (see module docstring).\n // Constructed but NOT passed to Room options — we connect without e2ee.\n // TODO(SP-1-web): pass { e2ee: { worker, keyProvider } } when MLS is ready.\n const keyProvider = new ExternalE2EEKeyProvider();\n\n const room = new Room({\n adaptiveStream: true,\n dynacast: true,\n });\n\n const call = Call._create(callId, room, keyProvider);\n\n // Connect first; track publishing follows after Connected fires.\n // LiveKit requires the room to be connected before publishing.\n await room.connect(edgeUrl, token);\n\n if (media.includes('audio')) {\n await room.localParticipant.setMicrophoneEnabled(true);\n }\n if (media.includes('video')) {\n await room.localParticipant.setCameraEnabled(true);\n }\n\n return call;\n }\n}\n","import type { HttpClient, PalbaseResponse } from '@palbase/core';\nimport type { FlagValue } from './types.js';\n\n/**\n * Admin client for the user-flags module — wraps the system flag CRUD\n * and per-user override endpoints exposed by `modules/user-flags`.\n *\n * Two surfaces:\n * - `.system.*` — project-wide flag defaults (platform auth)\n * - `.users.*` — per-user overrides (service-side)\n */\nexport class FlagsAdminClient {\n readonly system: SystemFlagsAdminClient;\n readonly users: UserFlagsAdminClient;\n\n constructor(httpClient: HttpClient) {\n this.system = new SystemFlagsAdminClient(httpClient);\n this.users = new UserFlagsAdminClient(httpClient);\n }\n}\n\nexport interface SystemFlag {\n key: string;\n value: FlagValue;\n /** Optional variant label when the flag is variant-typed. */\n variant?: string;\n /** Optional targeting rules (shape passes through from the backend). */\n rules?: unknown;\n updated_at?: string;\n}\n\nexport interface UserFlagOverride {\n user_id: string;\n key: string;\n value: FlagValue;\n variant?: string;\n updated_at?: string;\n}\n\nexport interface UserFlagsSnapshot {\n user_id: string;\n flags: Record<string, FlagValue>;\n}\n\nexport interface BatchFlagInput {\n /** Optional: evaluate for these user IDs. */\n user_ids?: string[];\n /** Optional: evaluate a subset of flag keys. */\n keys?: string[];\n}\n\nexport interface BatchFlagResult {\n user_id: string;\n flags: Record<string, FlagValue>;\n}\n\nexport class SystemFlagsAdminClient {\n constructor(private readonly http: HttpClient) {}\n\n list(): Promise<PalbaseResponse<SystemFlag[]>> {\n return this.http.request<SystemFlag[]>('GET', '/v1/user-flags/system');\n }\n\n get(key: string): Promise<PalbaseResponse<SystemFlag>> {\n return this.http.request<SystemFlag>('GET', `/v1/user-flags/system/${encodeURIComponent(key)}`);\n }\n\n put(\n key: string,\n input: { value: FlagValue; variant?: string; rules?: unknown },\n ): Promise<PalbaseResponse<SystemFlag>> {\n return this.http.request<SystemFlag>(\n 'PUT',\n `/v1/user-flags/system/${encodeURIComponent(key)}`,\n { body: input },\n );\n }\n\n delete(key: string): Promise<PalbaseResponse<void>> {\n return this.http.request<void>('DELETE', `/v1/user-flags/system/${encodeURIComponent(key)}`);\n }\n}\n\nexport class UserFlagsAdminClient {\n constructor(private readonly http: HttpClient) {}\n\n /** Read evaluated flags for a user (defaults + overrides merged). */\n get(userId: string): Promise<PalbaseResponse<UserFlagsSnapshot>> {\n return this.http.request<UserFlagsSnapshot>(\n 'GET',\n `/v1/user-flags/users/${encodeURIComponent(userId)}`,\n );\n }\n\n /**\n * Replace the full set of overrides for a user in one call. Any key not\n * present in `overrides` is removed.\n */\n putBulk(\n userId: string,\n overrides: Record<string, FlagValue>,\n ): Promise<PalbaseResponse<UserFlagsSnapshot>> {\n return this.http.request<UserFlagsSnapshot>(\n 'PUT',\n `/v1/user-flags/users/${encodeURIComponent(userId)}`,\n { body: { overrides } },\n );\n }\n\n putOverride(\n userId: string,\n key: string,\n input: { value: FlagValue; variant?: string },\n ): Promise<PalbaseResponse<UserFlagOverride>> {\n return this.http.request<UserFlagOverride>(\n 'PUT',\n `/v1/user-flags/users/${encodeURIComponent(userId)}/${encodeURIComponent(key)}`,\n { body: input },\n );\n }\n\n deleteOverride(userId: string, key: string): Promise<PalbaseResponse<void>> {\n return this.http.request<void>(\n 'DELETE',\n `/v1/user-flags/users/${encodeURIComponent(userId)}/${encodeURIComponent(key)}`,\n );\n }\n\n deleteAllOverrides(userId: string): Promise<PalbaseResponse<void>> {\n return this.http.request<void>('DELETE', `/v1/user-flags/users/${encodeURIComponent(userId)}`);\n }\n\n /** Evaluate flags for a batch of users / a subset of keys. */\n batch(input?: BatchFlagInput): Promise<PalbaseResponse<BatchFlagResult[]>> {\n return this.http.request<BatchFlagResult[]>('POST', '/v1/user-flags/batch', {\n body: input ?? {},\n });\n }\n}\n","import type { HttpClient, PalbaseResponse } from '@palbase/core';\nimport type {\n Flag,\n FlagContext,\n FlagSource,\n FlagsDelta,\n FlagsSnapshot,\n FlagValue,\n FlagVariant,\n} from './types.js';\n\nconst FLAG_NAME_RE = /^[a-zA-Z0-9_-]+$/;\n\n/**\n * Wire shape of the merged read endpoints `GET /v1/user-flags` (current auth\n * user) and `GET /v1/user-flags/users/{userId}` (a named user). Both return the\n * server-resolved flag values (system defaults + that user's overrides).\n */\ninterface MergedSnapshotResponse {\n values: Record<string, FlagValue> | null;\n fetched_at: string;\n}\n\n/** Coerce a resolved flag value to an enabled boolean. A boolean flag is itself;\n * any other present, non-empty, non-zero value counts as enabled; missing = off. */\nfunction flagEnabled(value: FlagValue | undefined): boolean {\n if (typeof value === 'boolean') return value;\n return value != null && value !== 0 && value !== '';\n}\n\n/** Derive a {@link FlagVariant} from a resolved flag value. A \"variant\" is the\n * value of a multivariate (string) flag, so a string flag's value IS its variant\n * name. Boolean / numeric / object flags and missing flags have no variant →\n * `null` (so a caller's `getVariant` resolves to null, per the \"no variant\"\n * contract). */\nfunction flagVariant(value: FlagValue | undefined): FlagVariant | null {\n return typeof value === 'string' ? { name: value } : null;\n}\n\n/** Re-map the `data` of a successful response; pass errors / empty bodies through\n * unchanged (typed to the new payload). */\nfunction mapResponse<A, B>(res: PalbaseResponse<A>, fn: (data: A) => B): PalbaseResponse<B> {\n if (res.error || res.data == null) {\n return { ...res, data: null } as PalbaseResponse<B>;\n }\n return { ...res, data: fn(res.data) };\n}\n\n/**\n * Result of writing a single user override (`PUT .../{userId}/{key}`) — echoes\n * the resolved key/value with `source: \"override\"`.\n */\nexport interface SetOverrideResult {\n key: string;\n value: FlagValue;\n source: FlagSource;\n}\n\n/**\n * Result of replacing all overrides for one user (`PUT .../{userId}`).\n * `values` is the resolved override set after the write.\n */\nexport interface SetOverridesResult {\n values: Record<string, FlagValue>;\n}\n\n/**\n * Result of clearing a single override (`DELETE .../{userId}/{key}`). `value`\n * is the system (default) value the user falls back to after removal, with\n * `source: \"system\"`.\n */\nexport interface ClearOverrideResult {\n key: string;\n value: FlagValue;\n source: FlagSource;\n}\n\n/** Result of clearing all overrides for a user (`DELETE .../{userId}`). */\nexport interface ClearAllOverridesResult {\n deleted: number;\n}\n\n/** One cross-user override operation for {@link FlagsServiceClient.batchSetOverrides}. */\nexport interface BatchOverrideOperation {\n userId: string;\n values: Record<string, FlagValue>;\n}\n\n/** Result of a cross-user batch write (`POST /v1/user-flags/batch`). */\nexport interface BatchSetOverridesResult {\n applied: number;\n}\n\n/**\n * Cross-user admin write surface, reached via {@link FlagsClient.asService}.\n *\n * These five methods set/clear overrides for an ARBITRARY user (named\n * explicitly), so they bypass the current-user scope that the default\n * `FlagsClient.setOverride(...)` is bound to. Mirrors the\n * `Database` / `Database.asService()` split: the privileged cross-user path is\n * moved OFF the default surface so it is greppable + intentional. Requires a\n * privileged key (service_role / managed runtime).\n */\nexport interface FlagsServiceClient {\n /**\n * Set (or replace) a single feature-flag override for one user.\n * `PUT /v1/user-flags/users/{userId}/{key}` with body `{ value }`.\n */\n setOverrideForUser(\n userId: string,\n key: string,\n value: FlagValue,\n ): Promise<PalbaseResponse<SetOverrideResult>>;\n\n /**\n * Set multiple feature-flag overrides for one user in a single call.\n * `PUT /v1/user-flags/users/{userId}` with body `{ values }`.\n */\n setOverridesForUser(\n userId: string,\n values: Record<string, FlagValue>,\n ): Promise<PalbaseResponse<SetOverridesResult>>;\n\n /**\n * Clear a single override for one user, restoring the project (system)\n * default for that key. `DELETE /v1/user-flags/users/{userId}/{key}`.\n */\n clearOverrideForUser(userId: string, key: string): Promise<PalbaseResponse<ClearOverrideResult>>;\n\n /**\n * Clear all overrides for one user, restoring project defaults for every\n * key. `DELETE /v1/user-flags/users/{userId}`.\n */\n clearAllOverridesForUser(userId: string): Promise<PalbaseResponse<ClearAllOverridesResult>>;\n\n /**\n * Apply override writes across many users in one request (max 1000 ops).\n * `POST /v1/user-flags/batch` with body `{ operations: [{ user_id, values }] }`.\n */\n batchSetOverrides(\n operations: ReadonlyArray<BatchOverrideOperation>,\n ): Promise<PalbaseResponse<BatchSetOverridesResult>>;\n}\n\n/**\n * Stateless, one-shot HTTP wrapper for the flags API. Reads (`isEnabled` /\n * `getVariant` / `getAll`) resolve against the live `GET /v1/user-flags` merged\n * endpoint and derive their result from the returned value map; it is also the\n * transport layer underneath `FlagsPool` (which adds caching, polling and\n * auth-state binding on top).\n *\n * Writes mirror the `Database` / `Database.asService()` model:\n * — {@link setOverride} (default) writes an override for the CURRENT user (the\n * `getCurrentUserId` getter supplied at construction). No userId argument; it\n * errors clearly when there is no signed-in user.\n * — {@link asService} returns the cross-user admin surface\n * ({@link FlagsServiceClient}) for writing overrides for an arbitrary user.\n */\nexport class FlagsClient {\n private readonly httpClient: HttpClient;\n private readonly getCurrentUserId?: () => string | null | undefined;\n\n /**\n * @param httpClient transport.\n * @param getCurrentUserId optional getter resolving the signed-in user's id\n * for {@link setOverride}. When omitted (or it returns no id), `setOverride`\n * errors and the caller must use {@link asService} for cross-user writes.\n */\n constructor(httpClient: HttpClient, getCurrentUserId?: () => string | null | undefined) {\n this.httpClient = httpClient;\n this.getCurrentUserId = getCurrentUserId;\n }\n\n async isEnabled(flagName: string, context?: FlagContext): Promise<PalbaseResponse<boolean>> {\n if (!FLAG_NAME_RE.test(flagName)) {\n throw new Error(\n `Invalid flag name: \"${flagName}\". Flag names must match ${FLAG_NAME_RE.source}`,\n );\n }\n\n const res = await this.httpClient.request<MergedSnapshotResponse>(\n 'GET',\n this.mergedPath(context),\n );\n return mapResponse(res, (snap) => flagEnabled(snap.values?.[flagName]));\n }\n\n async getVariant(flagName: string, context?: FlagContext): Promise<PalbaseResponse<FlagVariant>> {\n if (!FLAG_NAME_RE.test(flagName)) {\n throw new Error(\n `Invalid flag name: \"${flagName}\". Flag names must match ${FLAG_NAME_RE.source}`,\n );\n }\n\n const res = await this.httpClient.request<MergedSnapshotResponse>(\n 'GET',\n this.mergedPath(context),\n );\n // Inlined (not via mapResponse) so the `FlagVariant | null` payload stays\n // assignable to the public `PalbaseResponse<FlagVariant>` return type.\n if (res.error || res.data == null) {\n return { ...res, data: null };\n }\n return { ...res, data: flagVariant(res.data.values?.[flagName]) };\n }\n\n async getAll(context?: FlagContext): Promise<PalbaseResponse<Flag[]>> {\n const res = await this.httpClient.request<MergedSnapshotResponse>(\n 'GET',\n this.mergedPath(context),\n );\n return mapResponse(res, (snap) => {\n const values = snap.values ?? {};\n return Object.keys(values).map((name) => {\n const value = values[name];\n const flag: Flag = { name, enabled: flagEnabled(value) };\n if (typeof value === 'string') flag.variant = { name: value };\n return flag;\n });\n });\n }\n\n /**\n * Cold-start read: the full merged flag set for the current auth identity.\n * With a Bearer token the backend returns the user-merged view; without one\n * it returns project (system) defaults.\n */\n snapshot(): Promise<PalbaseResponse<FlagsSnapshot>> {\n return this.httpClient.request<FlagsSnapshot>('GET', '/v1/user-flags/snapshot');\n }\n\n /**\n * Incremental read of version-ordered ops since `sinceVersion`.\n *\n * Sends `If-None-Match: <sinceVersion>` so an unchanged server can answer\n * `304 Not Modified` cheaply. The 304 surfaces as a `PalbaseResponse` with\n * `status === 304` (the core http client maps non-2xx to an error/empty body);\n * callers treat that as \"no change\".\n */\n delta(sinceVersion: string): Promise<PalbaseResponse<FlagsDelta>> {\n return this.httpClient.request<FlagsDelta>(\n 'GET',\n `/v1/user-flags/delta?since=${encodeURIComponent(sinceVersion)}`,\n { headers: { 'If-None-Match': sinceVersion } },\n );\n }\n\n /**\n * Set (or replace) a single feature-flag override for the CURRENT USER.\n *\n * Resolves the signed-in user from the `getCurrentUserId` getter supplied at\n * construction and issues `PUT /v1/user-flags/users/{uid}/{key}` with body\n * `{ value }`. No userId argument — the override is bound to the current\n * user, so no admin power is required.\n *\n * Errors when there is no signed-in user (anonymous, or no getter supplied);\n * use {@link asService}`.setOverrideForUser(userId, key, value)` to write a\n * flag override for an arbitrary (cross-user) target.\n */\n setOverride(key: string, value: FlagValue): Promise<PalbaseResponse<SetOverrideResult>> {\n const userId = this.getCurrentUserId?.();\n if (!userId) {\n throw new Error(\n 'setOverride requires a signed-in user; use FlagsClient.asService().setOverrideForUser(userId, key, value) for cross-user writes',\n );\n }\n return this.httpClient.request<SetOverrideResult>(\n 'PUT',\n `/v1/user-flags/users/${encodeURIComponent(userId)}/${encodeURIComponent(key)}`,\n { body: { value } },\n );\n }\n\n /**\n * Return the cross-user admin write surface ({@link FlagsServiceClient}).\n *\n * Use sparingly and explicitly — the default {@link setOverride} path is\n * bound to the current user; `asService()` is how you write a flag override\n * for an ARBITRARY user. Mirrors `Database.asService()`. The returned client\n * shares this client's transport (the privileged service_role key).\n */\n asService(): FlagsServiceClient {\n const http = this.httpClient;\n return {\n setOverrideForUser(userId, key, value) {\n return http.request<SetOverrideResult>(\n 'PUT',\n `/v1/user-flags/users/${encodeURIComponent(userId)}/${encodeURIComponent(key)}`,\n { body: { value } },\n );\n },\n setOverridesForUser(userId, values) {\n return http.request<SetOverridesResult>(\n 'PUT',\n `/v1/user-flags/users/${encodeURIComponent(userId)}`,\n { body: { values } },\n );\n },\n clearOverrideForUser(userId, key) {\n return http.request<ClearOverrideResult>(\n 'DELETE',\n `/v1/user-flags/users/${encodeURIComponent(userId)}/${encodeURIComponent(key)}`,\n );\n },\n clearAllOverridesForUser(userId) {\n return http.request<ClearAllOverridesResult>(\n 'DELETE',\n `/v1/user-flags/users/${encodeURIComponent(userId)}`,\n );\n },\n batchSetOverrides(operations) {\n return http.request<BatchSetOverridesResult>('POST', '/v1/user-flags/batch', {\n body: {\n operations: operations.map((op) => ({\n user_id: op.userId,\n values: op.values,\n })),\n },\n });\n },\n };\n }\n\n /**\n * Resolve the merged-read path for a context. With `context.userId` present the\n * read targets that specific user's merged view (`/v1/user-flags/users/{id}`, a\n * privileged cross-user read); otherwise the current auth user's view\n * (`/v1/user-flags`). `context.properties` has no server-side targeting support\n * in the user-flags module and is ignored.\n */\n private mergedPath(context?: FlagContext): string {\n const uid = context?.userId;\n return uid ? `/v1/user-flags/users/${encodeURIComponent(uid)}` : '/v1/user-flags';\n }\n}\n","import type { FlagsClient } from './flags-client.js';\nimport type {\n AuthStateSource,\n FlagSource,\n FlagsChangeListener,\n FlagsSnapshot,\n FlagValue,\n Unsubscribe,\n} from './types.js';\n\n/** Options controlling pool behaviour. All have sensible defaults. */\nexport interface FlagsPoolOptions {\n /** Delta poll interval in ms. Default 30_000. Set `0` to disable polling. */\n pollIntervalMs?: number;\n /**\n * localStorage key for cold-start persistence. Default\n * `palbase.flags.snapshot`. Ignored when localStorage is unavailable (SSR).\n */\n storageKey?: string;\n /** Disable localStorage persistence even in the browser. Default false. */\n persist?: boolean;\n /**\n * Auth-state source. When provided, the pool re-snapshots on sign-in and\n * sign-out so the cache switches between user overrides and project defaults.\n */\n auth?: AuthStateSource;\n /**\n * Inject timer/visibility primitives for testing. Defaults to the global\n * `setInterval`/`clearInterval` and the browser `document`/`window`.\n */\n env?: PoolEnv;\n}\n\n/** Injectable runtime seams (browser globals) so the pool stays testable. */\nexport interface PoolEnv {\n now(): number;\n setInterval(handler: () => void, ms: number): IntervalHandle;\n clearInterval(handle: IntervalHandle): void;\n /** localStorage-like store, or `null` when unavailable (SSR). */\n storage: KeyValueStore | null;\n /** Visibility source, or `null` when there is no document (SSR). */\n visibility: VisibilitySource | null;\n}\n\nexport type IntervalHandle = ReturnType<typeof setInterval>;\n\nexport interface KeyValueStore {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\nexport interface VisibilitySource {\n isHidden(): boolean;\n onVisibilityChange(cb: () => void): Unsubscribe;\n}\n\nconst DEFAULT_POLL_MS = 30_000;\nconst DEFAULT_STORAGE_KEY = 'palbase.flags.snapshot';\n\ninterface CacheState {\n values: Record<string, FlagValue>;\n sources: Record<string, FlagSource>;\n syncVersion: string | null;\n}\n\nconst EMPTY_STATE: CacheState = {\n values: {},\n sources: {},\n syncVersion: null,\n};\n\n/**\n * Stateful, auto-pooling flag cache built on top of the stateless\n * {@link FlagsClient} transport.\n *\n * Responsibilities:\n * - Cold start from a snapshot (and from localStorage for instant boot).\n * - Poll deltas with conditional `If-None-Match` (304 → no-op).\n * - Re-snapshot on a version gap / tombstone-expiry signalled by the backend.\n * - Re-snapshot on auth sign-in / sign-out (override view ↔ defaults).\n * - Notify subscribers (drives `useFlag`/`useFlags`).\n *\n * Resolution is server-side: `get(key, default)` returns the cached value or\n * the supplied default; the pool never merges.\n */\nexport class FlagsPool {\n private readonly transport: FlagsClient;\n private readonly pollIntervalMs: number;\n private readonly storageKey: string;\n private readonly persist: boolean;\n private readonly env: PoolEnv;\n\n private state: CacheState = EMPTY_STATE;\n private readonly listeners = new Set<FlagsChangeListener>();\n\n private timer: IntervalHandle | null = null;\n private polling = false;\n private started = false;\n private destroyed = false;\n\n private readyPromise: Promise<void>;\n private resolveReady: (() => void) | null = null;\n private firstLoadDone = false;\n\n private authUnsub: Unsubscribe | null = null;\n private visibilityUnsub: Unsubscribe | null = null;\n private lastAuthHasSession: boolean | null = null;\n\n /** Cached frozen view for `getSnapshot` identity stability (React). */\n private frozenView: Readonly<Record<string, FlagValue>> = Object.freeze({});\n private viewDirty = true;\n\n constructor(transport: FlagsClient, options: FlagsPoolOptions = {}) {\n this.transport = transport;\n this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_MS;\n this.storageKey = options.storageKey ?? DEFAULT_STORAGE_KEY;\n this.persist = options.persist ?? true;\n this.env = options.env ?? defaultEnv();\n\n this.readyPromise = new Promise<void>((resolve) => {\n this.resolveReady = resolve;\n });\n\n // Hydrate synchronously from persisted snapshot for instant cold-start.\n this.hydrateFromStorage();\n\n if (options.auth) {\n this.bindAuth(options.auth);\n }\n }\n\n // ---- public surface -----------------------------------------------------\n\n /** Start cold-start fetch + polling + visibility binding. Idempotent. */\n start(): void {\n if (this.started || this.destroyed) {\n return;\n }\n this.started = true;\n\n void this.refresh();\n this.startPolling();\n this.bindVisibility();\n }\n\n /** Resolves once the first snapshot (or persisted hydrate) is available. */\n ready(): Promise<void> {\n if (this.firstLoadDone) {\n return Promise.resolve();\n }\n if (!this.started) {\n this.start();\n }\n return this.readyPromise;\n }\n\n /** Returns the cached value for `key`, or `fallback` when absent. */\n get<T extends FlagValue>(key: string, fallback: T): T {\n const value = this.state.values[key];\n return value === undefined ? fallback : (value as T);\n }\n\n /** Boolean convenience: cached value strictly equals `true`, else `fallback`. */\n isEnabled(key: string, fallback = false): boolean {\n const value = this.state.values[key];\n if (value === undefined) {\n return fallback;\n }\n return value === true;\n }\n\n /** Returns the cached value's source (`system`/`user`) when known. */\n getSource(key: string): FlagSource | undefined {\n return this.state.sources[key];\n }\n\n /** Returns a frozen snapshot of all cached values (stable identity). */\n all(): Readonly<Record<string, FlagValue>> {\n if (this.viewDirty) {\n this.frozenView = Object.freeze({ ...this.state.values });\n this.viewDirty = false;\n }\n return this.frozenView;\n }\n\n /** Current opaque sync version (null before first load). */\n get syncVersion(): string | null {\n return this.state.syncVersion;\n }\n\n /** Subscribe to any change in the cached set. Returns unsubscribe. */\n onChange(listener: FlagsChangeListener): Unsubscribe {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /**\n * `useSyncExternalStore`-compatible subscribe. Auto-starts the pool on first\n * subscription so a React component mount triggers cold-start.\n */\n subscribe = (listener: FlagsChangeListener): Unsubscribe => {\n if (!this.started) {\n this.start();\n }\n return this.onChange(listener);\n };\n\n /** Force an immediate re-snapshot (used on auth change / manual refresh). */\n async refresh(): Promise<void> {\n if (this.destroyed) {\n return;\n }\n const res = await this.transport.snapshot();\n if (res.error || res.data == null) {\n // Keep any persisted/previous state; still mark first load complete so\n // `ready()` does not hang forever on a transient network failure.\n this.markFirstLoadDone();\n return;\n }\n this.applySnapshot(res.data);\n this.markFirstLoadDone();\n }\n\n /** Stop timers and detach all auth/visibility listeners. */\n destroy(): void {\n this.destroyed = true;\n this.stopPolling();\n this.authUnsub?.();\n this.authUnsub = null;\n this.visibilityUnsub?.();\n this.visibilityUnsub = null;\n this.listeners.clear();\n }\n\n // ---- internals ----------------------------------------------------------\n\n private async poll(): Promise<void> {\n if (this.polling || this.destroyed) {\n return;\n }\n const since = this.state.syncVersion;\n if (since == null) {\n // No baseline yet — fall back to a snapshot.\n await this.refresh();\n return;\n }\n this.polling = true;\n try {\n const res = await this.transport.delta(since);\n if (res.status === 304) {\n return; // not modified — cheap no-op\n }\n if (res.error || res.data == null) {\n // A version gap / expired tombstone window is signalled by 409/410.\n if (res.status === 409 || res.status === 410) {\n await this.refresh();\n }\n return;\n }\n this.applyDelta(res.data.ops as readonly unknown[], res.data.sync_version);\n } finally {\n this.polling = false;\n }\n }\n\n private applySnapshot(snapshot: FlagsSnapshot): void {\n this.state = {\n values: { ...snapshot.values },\n sources: { ...(snapshot.sources ?? {}) },\n syncVersion: snapshot.sync_version,\n };\n this.viewDirty = true;\n this.persistState();\n this.notify();\n }\n\n private applyDelta(ops: readonly unknown[], nextVersion: string): void {\n // Each op is re-narrowed defensively via `isDeltaOp`.\n const values = { ...this.state.values };\n const sources = { ...this.state.sources };\n let changed = false;\n\n for (const raw of ops) {\n if (!isDeltaOp(raw)) {\n continue;\n }\n if (raw.op === 'delete') {\n if (raw.key in values) {\n delete values[raw.key];\n delete sources[raw.key];\n changed = true;\n }\n } else {\n values[raw.key] = raw.value;\n if (raw.source) {\n sources[raw.key] = raw.source;\n }\n changed = true;\n }\n }\n\n this.state = { values, sources, syncVersion: nextVersion };\n if (changed) {\n this.viewDirty = true;\n this.persistState();\n this.notify();\n } else {\n // version still advances even with no value change\n this.persistState();\n }\n }\n\n private startPolling(): void {\n if (this.pollIntervalMs <= 0 || this.timer != null) {\n return;\n }\n this.timer = this.env.setInterval(() => {\n void this.poll();\n }, this.pollIntervalMs);\n }\n\n private stopPolling(): void {\n if (this.timer != null) {\n this.env.clearInterval(this.timer);\n this.timer = null;\n }\n }\n\n private bindVisibility(): void {\n const vis = this.env.visibility;\n if (!vis) {\n return;\n }\n this.visibilityUnsub = vis.onVisibilityChange(() => {\n if (vis.isHidden()) {\n this.stopPolling();\n } else {\n // Came back to foreground: catch up immediately, then resume polling.\n void this.poll();\n this.startPolling();\n }\n });\n }\n\n private bindAuth(auth: AuthStateSource): void {\n const handle = auth.onAuthStateChange((_event, session) => {\n const hasSession = session != null;\n // Fires immediately with current state on subscribe; only re-snapshot\n // when the identity actually flips (sign-in ↔ sign-out), not on every\n // token refresh, to avoid redundant cold-starts.\n if (this.lastAuthHasSession === null) {\n this.lastAuthHasSession = hasSession;\n return;\n }\n if (hasSession !== this.lastAuthHasSession) {\n this.lastAuthHasSession = hasSession;\n if (this.started) {\n void this.refresh();\n }\n }\n });\n // Normalize the Supabase-style handle to a plain unsubscribe.\n this.authUnsub = () => handle.data.subscription.unsubscribe();\n }\n\n private notify(): void {\n for (const listener of this.listeners) {\n listener();\n }\n }\n\n private markFirstLoadDone(): void {\n if (!this.firstLoadDone) {\n this.firstLoadDone = true;\n this.resolveReady?.();\n this.resolveReady = null;\n }\n }\n\n private hydrateFromStorage(): void {\n if (!this.persist) {\n return;\n }\n const store = this.env.storage;\n if (!store) {\n return;\n }\n const raw = store.getItem(this.storageKey);\n if (!raw) {\n return;\n }\n const parsed = parsePersisted(raw);\n if (!parsed) {\n return;\n }\n this.state = parsed;\n this.viewDirty = true;\n // A persisted snapshot is enough to satisfy `ready()` instantly; a live\n // `refresh()` will still run on `start()` to revalidate.\n this.markFirstLoadDone();\n }\n\n private persistState(): void {\n if (!this.persist) {\n return;\n }\n const store = this.env.storage;\n if (!store || this.state.syncVersion == null) {\n return;\n }\n try {\n store.setItem(this.storageKey, JSON.stringify(this.state));\n } catch {\n // Quota / private-mode — persistence is best-effort.\n }\n }\n}\n\n// ---- guards / helpers -----------------------------------------------------\n\nfunction isDeltaOp(\n value: unknown,\n): value is\n | { op: 'put'; key: string; value: FlagValue; source?: FlagSource }\n | { op: 'delete'; key: string } {\n if (typeof value !== 'object' || value === null) {\n return false;\n }\n const rec = value as Record<string, unknown>;\n if (typeof rec.key !== 'string') {\n return false;\n }\n if (rec.op === 'delete') {\n return true;\n }\n return rec.op === 'put' && 'value' in rec;\n}\n\nfunction parsePersisted(raw: string): CacheState | null {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return null;\n }\n if (typeof parsed !== 'object' || parsed === null) {\n return null;\n }\n const rec = parsed as Record<string, unknown>;\n if (typeof rec.syncVersion !== 'string') {\n return null;\n }\n if (typeof rec.values !== 'object' || rec.values === null) {\n return null;\n }\n const sources =\n typeof rec.sources === 'object' && rec.sources !== null\n ? (rec.sources as Record<string, FlagSource>)\n : {};\n return {\n values: rec.values as Record<string, FlagValue>,\n sources,\n syncVersion: rec.syncVersion,\n };\n}\n\n/** Build the default browser/Node runtime seam, guarding all DOM globals. */\nfunction defaultEnv(): PoolEnv {\n const storage = resolveStorage();\n const visibility = resolveVisibility();\n return {\n now: () => Date.now(),\n setInterval: (handler, ms) => setInterval(handler, ms),\n clearInterval: (handle) => clearInterval(handle),\n storage,\n visibility,\n };\n}\n\nfunction resolveStorage(): KeyValueStore | null {\n try {\n if (typeof localStorage === 'undefined') {\n return null;\n }\n // Touch it to surface private-mode/security errors up front.\n const probe = '__palbase_flags_probe__';\n localStorage.setItem(probe, '1');\n localStorage.removeItem(probe);\n return localStorage;\n } catch {\n return null;\n }\n}\n\nfunction resolveVisibility(): VisibilitySource | null {\n if (typeof document === 'undefined') {\n return null;\n }\n const doc = document;\n return {\n isHidden: () => doc.visibilityState === 'hidden',\n onVisibilityChange: (cb) => {\n doc.addEventListener('visibilitychange', cb);\n return () => doc.removeEventListener('visibilitychange', cb);\n },\n };\n}\n","import type { PalbaseResponse } from '@palbase/core';\nimport {\n type AuthStateSource,\n FlagsClient,\n FlagsPool,\n type FlagsPoolOptions,\n type FlagValue,\n type FlagVariant,\n type PoolEnv,\n} from '@palbase/flags';\nimport type { PalbeAuth, Unsubscribe } from './auth-facade.js';\nimport { BackendError } from './errors.js';\nimport type { PalbeRuntime } from './runtime.js';\n\nexport type { FlagValue } from '@palbase/flags';\n\n/** Frozen view of all cached flag values (what `all()` returns / `changes()` yields). */\nexport type FlagsView = Readonly<Record<string, FlagValue>>;\n\n/**\n * Adapt PalbeAuth's `(state: AuthState) => void` listener contract to the\n * Supabase-style `AuthStateSource` FlagsPool binds to (`(event, session)`\n * callback + `{ data: { subscription: { unsubscribe } } }` handle).\n *\n * PalbeAuth fires the callback IMMEDIATELY on subscribe (iOS parity); the\n * pool's bindAuth treats that first call as the baseline (its\n * `lastAuthHasSession === null` branch) and only re-snapshots on a later\n * identity flip — exactly the behaviour we want, no suppression needed.\n */\nexport function palbeAuthAdapter(auth: PalbeAuth): AuthStateSource {\n return {\n onAuthStateChange(callback) {\n const unsubscribe = auth.onAuthStateChange(() => {\n // Token truth, not state.status: a restored session (page reload) has\n // no cachedUser yet, so snapshotState() reports signedOut while\n // isSignedIn is true. Using the token-manager directly ensures the pool\n // baseline matches the actual session presence on the first fire.\n const signedIn = auth.isSignedIn;\n callback(signedIn ? 'SIGNED_IN' : 'SIGNED_OUT', signedIn ? { signedIn } : null);\n });\n return { data: { subscription: { unsubscribe } } };\n },\n };\n}\n\n/**\n * Server-side pool seams: no storage, no visibility, and — because polling is\n * disabled via `pollIntervalMs: 0` — an interval seam that must never be\n * reached. Throwing keeps a future regression loud instead of leaking timers\n * into RSC/route-handler processes.\n */\nfunction serverPoolEnv(): PoolEnv {\n return {\n now: () => Date.now(),\n setInterval: () => {\n throw new Error('palbe flags: polling is disabled server-side');\n },\n clearInterval: () => {},\n storage: null,\n visibility: null,\n };\n}\n\n/** Object.is for primitives; objects/arrays by JSON text. Snapshots are\n * re-frozen NEW references on every re-snapshot, so reference equality would\n * report structurally-identical values as \"changed\". JSON compare is\n * key-order-sensitive — a server that reorders keys without changing values\n * may fire a spurious per-key callback (accepted tradeoff, documented). */\nfunction sameFlagValue(a: FlagValue | undefined, b: FlagValue | undefined): boolean {\n if (Object.is(a, b)) return true;\n if (typeof a === 'object' && a !== null && typeof b === 'object' && b !== null) {\n return JSON.stringify(a) === JSON.stringify(b);\n }\n return false;\n}\n\n/**\n * `pb.flags` — iOS-parity facade over `FlagsPool` (cache + delta polling +\n * auth binding) and `FlagsClient` (stateless transport).\n *\n * Start semantics: the facade is constructed lazily (first `pb.flags` touch).\n * - Browser: the pool starts right here in the constructor — cold snapshot +\n * 30s delta polling + visibility pause. \"First use starts the machinery\"\n * without per-method start checks.\n * - Server (no `document`): NEVER auto-starts. `ready()`/`refresh()` are\n * one-shot snapshot fetches with zero timers and zero storage access, so a\n * pbServer request that reads flags pays exactly one HTTP call.\n *\n * localStorage persistence is scoped per environment ref (`palbe.flags.<ref>`),\n * mirroring the session-storage key convention.\n */\nexport class PalbeFlags {\n private readonly transport: FlagsClient;\n private readonly pool: FlagsPool;\n\n constructor(rt: PalbeRuntime) {\n this.transport = new FlagsClient(rt.http);\n const browser = typeof document !== 'undefined';\n const ref = rt.projectRef;\n const options: FlagsPoolOptions = {\n auth: palbeAuthAdapter(rt.auth),\n ...(ref ? { storageKey: `palbe.flags.${ref}` } : {}),\n ...(browser ? {} : { pollIntervalMs: 0, env: serverPoolEnv() }),\n };\n this.pool = new FlagsPool(this.transport, options);\n if (browser) this.pool.start();\n }\n\n /** Resolves once the first snapshot (or persisted hydrate) is available. Auto-starts the pool. */\n ready(): Promise<void> {\n return this.pool.ready();\n }\n\n /** Force an immediate re-snapshot. */\n refresh(): Promise<void> {\n return this.pool.refresh();\n }\n\n /** Frozen snapshot of all cached values (identity-stable until a change). */\n all(): FlagsView {\n return this.pool.all();\n }\n\n /** Raw cached value for `key`, or `undefined` when not in the cache. */\n get(key: string): FlagValue | undefined {\n return this.pool.all()[key];\n }\n\n /** `true` only when the cached value is strictly `true`; `fallback` when the key is absent. */\n isEnabled(key: string, fallback = false): boolean {\n return this.pool.isEnabled(key, fallback);\n }\n\n /** Alias of {@link isEnabled} (iOS parity). */\n bool(key: string, fallback = false): boolean {\n return this.isEnabled(key, fallback);\n }\n\n /** Cached value when it is a string, else `fallback`. */\n getString(key: string, fallback: string): string {\n const value = this.pool.all()[key];\n return typeof value === 'string' ? value : fallback;\n }\n\n /** Cached value when it is an integer number, else `fallback`. */\n getInt(key: string, fallback: number): number {\n const value = this.pool.all()[key];\n return typeof value === 'number' && Number.isInteger(value) ? value : fallback;\n }\n\n /** Cached value when it is a number (integers included), else `fallback`. */\n getDouble(key: string, fallback: number): number {\n const value = this.pool.all()[key];\n return typeof value === 'number' ? value : fallback;\n }\n\n /**\n * Resolve the multivariate variant for `key` — the variant name, or `null`\n * when the flag has no variant (or the read fails).\n *\n * Wire failures (network errors, 404, etc.) resolve to `null`.\n * Invalid flag names throw `BackendError('validation', { code: 'invalid_flag_name' })`.\n *\n * DEVIATION from iOS sync-cache parity: the platform does not propagate\n * variant metadata into the user-flags snapshot/delta cache, so this is an\n * ASYNC transport read (derived from `GET /v1/user-flags`), not a cache lookup.\n */\n async getVariant(key: string): Promise<string | null> {\n let res: PalbaseResponse<FlagVariant>;\n try {\n res = await this.transport.getVariant(key);\n } catch (err) {\n // FlagsClient throws synchronously for invalid flag names; map to a\n // typed validation error so callers can distinguish \"bad name\" from\n // \"no variant\" (which resolves null).\n throw new BackendError('validation', {\n code: 'invalid_flag_name',\n message: err instanceof Error ? err.message : String(err),\n });\n }\n return res.data?.name ?? null;\n }\n\n /** Subscribe to any change in the cached flag set. */\n onChange(callback: () => void): Unsubscribe {\n return this.pool.onChange(callback);\n }\n\n /**\n * Observe ONE key: fires only when that key's value actually changes\n * (per {@link sameFlagValue} — structural compare for objects), with the\n * new value (`undefined` = deleted). P5 React-hook substrate.\n */\n subscribeKey(key: string, callback: (value: FlagValue | undefined) => void): Unsubscribe {\n let last = this.pool.all()[key];\n return this.pool.onChange(() => {\n const next = this.pool.all()[key];\n if (sameFlagValue(last, next)) return;\n last = next;\n callback(next);\n });\n }\n\n /**\n * Async iteration over flag changes: yields the new {@link all} view on\n * every pool change notification. The listener is detached when the\n * consumer `break`s/`return`s/`throw`s — including while a `next()` is\n * still pending (it resolves `{ done: true }` instead of hanging, which a\n * plain async-generator `finally` would not guarantee).\n */\n changes(): AsyncIterableIterator<FlagsView> {\n const queue: FlagsView[] = [];\n const pending: Array<(result: IteratorResult<FlagsView, undefined>) => void> = [];\n let finished = false;\n const unsubscribe = this.pool.onChange(() => {\n const snapshot = this.pool.all();\n const resolve = pending.shift();\n if (resolve) resolve({ value: snapshot, done: false });\n else queue.push(snapshot);\n });\n const finish = (): void => {\n if (finished) return;\n finished = true;\n unsubscribe();\n while (pending.length > 0) pending.shift()?.({ value: undefined, done: true });\n };\n return {\n next: (): Promise<IteratorResult<FlagsView, undefined>> => {\n if (finished) return Promise.resolve({ value: undefined, done: true });\n const head = queue.shift();\n if (head !== undefined) return Promise.resolve({ value: head, done: false });\n return new Promise((resolve) => {\n pending.push(resolve);\n });\n },\n return: (): Promise<IteratorResult<FlagsView, undefined>> => {\n finish();\n return Promise.resolve({ value: undefined, done: true });\n },\n throw: (error?: unknown): Promise<IteratorResult<FlagsView, undefined>> => {\n finish();\n return Promise.reject(error);\n },\n [Symbol.asyncIterator](): AsyncIterableIterator<FlagsView> {\n return this;\n },\n };\n }\n\n /** Stop polling and detach all listeners (auth + visibility + subscribers). */\n destroy(): void {\n this.pool.destroy();\n }\n}\n","/**\n * Pure deadline math — the web mirror of iOS DeadlineCalculator (Codex-finalized\n * restart conversion rule). Identical algorithm; web uses ms internally and\n * compares against ttlSeconds (converted). Clock readings are passed in so it is\n * fully table-testable and byte-identical with iOS.\n *\n * NOTE on the send-vs-arrival clamp: like iOS T3, this Calculator stays PURE — it\n * consumes an already-resolved anchor + the current clock readings. The\n * `min(sender_send_ts + ttl, first_arrival_monotonic + ttl)` effective-deadline\n * basis is applied at ARM time by the caller (Chat, T10), which picks the anchor's\n * monotonic baseline; the Calculator only converts that anchor into remaining/purge.\n */\nexport interface AnchorTriple {\n mAnchorMs: number; // performance.now() at capture\n wAnchorEpochMs: number; // Date.now() at capture\n bAnchorToken: string; // per-page-session token (changes across reload/reboot)\n}\n\nexport type DeadlineResult = { kind: 'remaining'; seconds: number } | { kind: 'purgeNow' };\n\nexport function remainingSeconds(args: {\n ttlSeconds: number;\n anchor: AnchorTriple;\n nowMonotonicMs: number;\n nowWallEpochMs: number;\n nowBootToken: string;\n}): DeadlineResult {\n const ttl = args.ttlSeconds;\n const wallDeltaSec = (args.nowWallEpochMs - args.anchor.wAnchorEpochMs) / 1000;\n let elapsed: number;\n if (args.nowBootToken === args.anchor.bAnchorToken) {\n const monoDeltaSec = Math.max(0, args.nowMonotonicMs - args.anchor.mAnchorMs) / 1000;\n elapsed = Math.max(monoDeltaSec, wallDeltaSec); // larger-elapsed: backward wall can't extend\n } else {\n elapsed = wallDeltaSec; // reboot: monotonic meaningless → wall-only (best-effort, bounded)\n }\n const remaining = Math.min(ttl, ttl - elapsed); // clamp: never MORE than ttl of life\n return remaining <= 0 ? { kind: 'purgeNow' } : { kind: 'remaining', seconds: remaining };\n}\n\n/** Impure clock seam. The boot token is minted once per page-session via\n * crypto.randomUUID() and persisted (sessionStorage), so it is stable within a\n * boot and changes across a reload/reboot — making the monotonic reading\n * comparable across a restart (same-boot vs reboot conversion). */\nlet cachedBootToken: string | null = null;\nexport const MonotonicClock = {\n nowMs(): number {\n return typeof performance !== 'undefined' ? performance.now() : Date.now();\n },\n nowWallEpochMs(): number {\n return Date.now();\n },\n bootToken(): string {\n if (cachedBootToken !== null) return cachedBootToken;\n try {\n const existing =\n typeof sessionStorage !== 'undefined' ? sessionStorage.getItem('pb_boot_token') : null;\n if (existing) {\n cachedBootToken = existing;\n return existing;\n }\n const fresh = crypto.randomUUID();\n if (typeof sessionStorage !== 'undefined') sessionStorage.setItem('pb_boot_token', fresh);\n cachedBootToken = fresh;\n return fresh;\n } catch {\n cachedBootToken = crypto.randomUUID();\n return cachedBootToken;\n }\n },\n};\n","/**\n * DeleteFold — pure fold of delete-for-everyone tombstones into a monotonic\n * `tombstoned` set.\n *\n * Algorithm mirrors iOS `DeleteFold` (DeleteFold.swift) byte-for-byte in\n * behaviour so the two SDKs compute the IDENTICAL tombstoned set from the same\n * events:\n *\n * - **Dedup first** on `eventClientMsgId` (a re-delivered logical tombstone\n * applies at most once). Dedup is recorded ONLY for events the fold could\n * actually evaluate (target present, or parked in pending) — an unverifiable\n * event (unresolved sender AND absent target) is held by its own id without\n * being marked seen, so it can be re-attempted when more resolves.\n * - **Author-gate (the #1 security property):** a tombstone scrubs a target\n * ONLY when its resolved authenticated sender (`actorUserId`) equals the\n * TARGET message's author (`authorOfTarget(target)`). A non-author tombstone\n * is a forgery → IGNORED. This gate is enforced IDENTICALLY on the in-order\n * path AND the deferred/pending path — there is NO code path that tombstones\n * without passing it.\n * - **ABSORBING / sticky (NOT LWW):** once a target is in `tombstoned` it is\n * NEVER removed. No later event, no higher seq, no reevaluate can\n * un-tombstone it. (Eviction within the retention window is the STORE's job,\n * not the fold's.)\n * - **Pending (dangling target):** a valid-shaped tombstone whose target isn't\n * local yet is parked in `pending` CARRYING its authenticated actor, so the\n * deferred sender-match can be enforced when the target finally arrives\n * (`reevaluatePending`). A forged early tombstone parked in pending is\n * DISCARDED on the target's arrival (sender-match fails) → the genuine\n * message renders.\n * - **Unresolved sender + absent target:** nothing can be verified yet (neither\n * side resolved). The event is parked in `held` keyed by its\n * `eventClientMsgId` and re-ingested on `reevaluatePending` — it never\n * tombstones until BOTH the actor and the target's author are known AND match.\n *\n * Pure: no I/O, fully table-testable.\n */\n\n/**\n * One delete-for-everyone tombstone event for a target message. Carries\n * everything the fold needs and nothing it must read from the ciphertext: the\n * actor is the AUTHENTICATED sender resolved upstream (`actorUserId`), NEVER an\n * actor field on the wire.\n */\nexport interface DeleteEvent {\n targetClientMsgId: string;\n /** Resolved authenticated sender userId; null = unresolved sender. */\n actorUserId: string | null;\n epoch: number;\n serverSeq: number;\n /** Dedup key — the tombstone's own client_msg_id (unique per logical event). */\n eventClientMsgId: string;\n}\n\nimport type { AuthorResolution } from './edit-fold.js';\n\n/**\n * Resolves the target message's author via {@link AuthorResolution} (the SAME\n * orphan-aware resolution the edit gate uses): `'author'` → run the author-gate;\n * `'unknown'` → defer (target absent locally → pending/held); `'purged'` → NO-OP\n * (the target was TTL-purged — a delete of an already-gone message is already\n * satisfied; never park in pending, never re-attempt).\n */\ntype AuthorOfTarget = (targetClientMsgId: string) => AuthorResolution;\n\nexport class DeleteFold {\n // targets with a confirmed valid tombstone — MONOTONIC (never removed within retention).\n private readonly tombstoned = new Set<string>();\n // target → the tombstone's authenticated actor userId, awaiting the target's arrival.\n private readonly pending = new Map<string, string>();\n // dedup of real wire events the fold could evaluate (tombstoned or parked in pending).\n private readonly seen = new Set<string>();\n // events parked because NEITHER the actor NOR the target's author was resolvable at ingest;\n // keyed by eventClientMsgId so a re-delivered unverifiable event is held exactly once.\n private held: DeleteEvent[] = [];\n\n /**\n * Ingest one tombstone. `authorOfTarget` resolves the target message's author via\n * {@link AuthorResolution}: `'author'` → run the author-gate; `'unknown'` → defer\n * (target absent locally → pending/held); `'purged'` → NO-OP, mark seen (the target\n * was TTL-purged — a delete of an already-gone message is already satisfied; never\n * park in pending, never re-attempt).\n */\n ingest(e: DeleteEvent, authorOfTarget: AuthorOfTarget): void {\n // Already absorbed → no-op (idempotent, absorbing).\n if (this.tombstoned.has(e.targetClientMsgId)) return;\n // Dedup of the same logical tombstone (only events we actually parked/evaluated are seen).\n if (this.seen.has(e.eventClientMsgId)) return;\n if (this.heldContains(e.eventClientMsgId)) return;\n\n const res = authorOfTarget(e.targetClientMsgId);\n if (res.kind === 'purged') {\n // Target already TTL-purged → already gone. No-op, mark seen (never park in pending).\n this.seen.add(e.eventClientMsgId);\n return;\n }\n if (res.kind === 'author') {\n // Target present → run the author-gate NOW.\n this.seen.add(e.eventClientMsgId);\n if (e.actorUserId === null || e.actorUserId !== res.userId) return; // forgery / unresolved actor → ignore\n this.tombstoned.add(e.targetClientMsgId);\n } else if (e.actorUserId !== null) {\n // Target absent but actor resolved → park the authenticated actor for the deferred gate.\n this.seen.add(e.eventClientMsgId);\n this.pending.set(e.targetClientMsgId, e.actorUserId);\n } else {\n // Neither actor NOR target resolved — cannot verify anything yet. Hold by id and\n // re-ingest on reevaluate; NOT marked seen so it stays re-attemptable.\n this.held.push(e);\n }\n }\n\n /** True once a valid tombstone has absorbed this target. */\n isTombstoned(targetClientMsgId: string): boolean {\n return this.tombstoned.has(targetClientMsgId);\n }\n\n /**\n * When a target message newly arrives with a resolved `author`, re-check any\n * pending tombstone for it AND re-attempt any held (unverifiable) tombstones\n * whose target is now resolvable. The deferred gate is the SAME comparison as\n * the in-order path.\n */\n reevaluatePending(target: string, author: AuthorResolution): void {\n const res = author;\n // Deferred gate for a parked pending tombstone.\n const actor = this.pending.get(target);\n if (actor !== undefined) {\n // Absorbing: only ADD on a match; a mismatch DISCARDS the (forged) pending entry so it\n // can never scrub the genuine message.\n if (res.kind === 'author') {\n if (actor === res.userId) this.tombstoned.add(target); // match → absorbed\n this.pending.delete(target); // match → absorbed; mismatch → forged tombstone loses\n } else if (res.kind === 'purged') {\n // Target was TTL-purged → already gone; nothing left to scrub or defer. Discard.\n this.pending.delete(target);\n }\n // 'unknown' → cannot decide yet, keep the pending entry.\n }\n // Re-attempt any held events whose target now has a resolvable author. Re-ingest runs the\n // full gate (dedup via seen, author-match / purged no-op), so it can neither double-apply\n // nor scrub without the match. Events that still don't resolve are re-held.\n if (this.held.length === 0) return;\n const pendingHeld = this.held;\n this.held = [];\n for (const e of pendingHeld) {\n this.ingest(e, (t): AuthorResolution => (t === target ? res : { kind: 'unknown' }));\n }\n }\n\n private heldContains(eventClientMsgId: string): boolean {\n return this.held.some((h) => h.eventClientMsgId === eventClientMsgId);\n }\n}\n","/**\n * EditFold — pure fold of edit-by-supersession events into a per-target winning\n * text + a write-once `edited` flag.\n *\n * Algorithm mirrors iOS `EditFold` (EditFold.swift) byte-for-byte in behaviour so\n * the two SDKs compute the IDENTICAL winning edit from the same events:\n *\n * - **Author-gate first, dedup after** on `eventClientMsgId`. Dedup happens only\n * AFTER the event is both resolvable (target+sender) and authorized, so a HELD\n * event is never prematurely consumed by the seen-set (matching iOS ordering).\n * - **HOLD (unresolved/dangling):** if the target's author is not yet resolvable\n * (`authorOfTarget` returns null — target unknown), OR the sender is unresolved\n * (`editorUserId == null` — roster gap), the event is parked in `held` (deduped\n * by `eventClientMsgId`) and re-attempted by `reevaluateHeld(...)` once the\n * roster/target arrives. Never folds an unresolved sender; never silently drops\n * a holdable edit.\n * - **Author-gate:** the edit's resolved sender (`editorUserId`) must equal the\n * TARGET message's author (`authorOfTarget(target)`). A non-author edit is a\n * forgery → DROPPED (not held). It is NOT marked seen — letting the gate re-drop\n * a re-delivery is harmless and idempotent (it never reaches the winner state,\n * so the winner is unaffected).\n * - **LWW per target** on `(epoch, serverSeq)` ascending. An incoming order that\n * is `< stored`, or `== stored` and loses the id backstop, is ignored.\n * - **Exact-tie rule (cross-SDK contract — MUST match iOS):** on an exact\n * `(epoch, serverSeq)` tie the edit whose `eventClientMsgId` is **lexicographically\n * GREATER (`>`) wins**. Implemented as \"incoming `<= stored.lastEventId` → loses\"\n * (identical comparison direction to iOS EditFold.swift). The server guarantees\n * `(epoch, serverSeq)` is unique per logical event, so this is a theoretical\n * backstop only — but it is fully deterministic + order-independent.\n * - **Write-once `edited`:** once ANY valid edit applies to a target it stays\n * edited forever (editing back to the original text still shows edited). Never\n * unset.\n *\n * Pure: no I/O, fully table-testable.\n */\n\nimport type { MentionRange } from './group-messaging.js';\n\n/**\n * One edit-by-supersession event for the LATEST edit's text to be folded onto a\n * target message in place. Carries everything the fold needs and nothing it must\n * read from the ciphertext: the actor is the AUTHENTICATED sender resolved\n * upstream (`editorUserId`), never an actor field on the wire.\n */\nexport interface EditEvent {\n targetClientMsgId: string;\n /** Resolved sender userId; null = unresolved (roster gap) → HOLD. */\n editorUserId: string | null;\n newText: string;\n epoch: number;\n serverSeq: number;\n /** Dedup key — the edit's own client_msg_id (unique per logical event). */\n eventClientMsgId: string;\n /**\n * The edit's REPLACEMENT mention ranges (raw, un-normalized). The fold tracks them\n * on the WINNING edit only, so the edited message's mentions re-resolve from the\n * LWW-winning edit's ranges (mentions T6). Undefined/null = the edit carried none.\n */\n bodyRanges?: MentionRange[] | null;\n}\n\ninterface TargetState {\n orderEpoch: number;\n orderSeq: number;\n lastEventId: string;\n text: string;\n /** The WINNING edit's replacement mention ranges (raw); null when it carried none. */\n bodyRanges: MentionRange[] | null;\n}\n\n/**\n * Orphan-aware author resolution (web mirror of iOS `TargetAuthor`):\n * - `{ kind: 'author'; userId }` — target present, author known → run the author-gate.\n * - `{ kind: 'purged' }` — the target was TTL-purged (its `client_msg_id` is in the\n * persistent `purgedClientMsgIds` STRING set, namespace-separate from the int\n * `server_seq` tombstone) → DROP an edit (no held leak, never resurrects).\n * - `{ kind: 'unknown' }` — target not locally resolvable yet → HOLD (re-attempt later).\n */\nexport type AuthorResolution =\n | { kind: 'author'; userId: string }\n | { kind: 'purged' }\n | { kind: 'unknown' };\n\n/**\n * Resolves the target message's AUTHOR via {@link AuthorResolution}. ALL call-sites\n * (`chat.ts` author closure, `coordinator.ts` `projectHistory`) return this union\n * directly (the legacy `string | null` shim was deleted in Task 10).\n */\ntype AuthorOfTarget = (targetClientMsgId: string) => AuthorResolution;\n\n/**\n * Lexicographic (epoch, serverSeq) comparison. Returns true if a < b\n * (the incoming order is strictly older than the stored order).\n */\nfunction orderLt(aEpoch: number, aSeq: number, bEpoch: number, bSeq: number): boolean {\n if (aEpoch !== bEpoch) return aEpoch < bEpoch;\n return aSeq < bSeq;\n}\n\n/** True if (aEpoch, aSeq) equals (bEpoch, bSeq). */\nfunction orderEq(aEpoch: number, aSeq: number, bEpoch: number, bSeq: number): boolean {\n return aEpoch === bEpoch && aSeq === bSeq;\n}\n\nexport class EditFold {\n // target → winning edit state\n private readonly states = new Map<string, TargetState>();\n // dedup of real wire events that reached (and were resolvable enough to evaluate at) the fold\n private readonly seenEvents = new Set<string>();\n // events parked because target/author or sender was unresolved at ingest time\n private held: EditEvent[] = [];\n // targets that have had ≥1 valid edit applied (write-once)\n private readonly editedTargets = new Set<string>();\n\n /**\n * Ingest one edit. `authorOfTarget` resolves the target message's author via\n * {@link AuthorResolution}: `'author'` → run the author-gate; `'unknown'` → HOLD\n * (target/author not yet known); `'purged'` → DROP (the target was TTL-purged —\n * editing a disappeared message is a no-op; mark the event seen so it never re-holds\n * and a later author \"resolution\" cannot resurrect it).\n */\n ingest(e: EditEvent, authorOfTarget: AuthorOfTarget): void {\n const res = authorOfTarget(e.targetClientMsgId);\n if (res.kind === 'unknown') {\n this.holdIfNew(e); // target/author not yet known → HOLD\n return;\n }\n if (res.kind === 'purged') {\n this.seenEvents.add(e.eventClientMsgId); // mark seen → never re-held, never resurrects\n return; // DROP: editing a disappeared message is a no-op\n }\n const author = res.userId;\n // Sender not yet resolvable (roster gap) → hold; we must never fold an unattributed edit.\n if (e.editorUserId === null) {\n this.holdIfNew(e);\n return;\n }\n // Author-gate: a non-author edit is a forgery — dropped, not held, not marked seen\n // (the gate re-drops any re-delivery; it never touches winner state, so it's idempotent).\n if (e.editorUserId !== author) return;\n\n // Resolvable + authorized: dedup, then LWW.\n if (this.seenEvents.has(e.eventClientMsgId)) return; // re-delivery of same logical edit\n this.seenEvents.add(e.eventClientMsgId);\n\n const prev = this.states.get(e.targetClientMsgId);\n if (prev !== undefined) {\n // stale / out-of-order\n if (orderLt(e.epoch, e.serverSeq, prev.orderEpoch, prev.orderSeq)) return;\n // exact (epoch, serverSeq) tie: lexicographically GREATER eventClientMsgId wins,\n // so an incoming id <= the stored one loses (identical to iOS EditFold's `<=`).\n if (\n orderEq(e.epoch, e.serverSeq, prev.orderEpoch, prev.orderSeq) &&\n e.eventClientMsgId <= prev.lastEventId\n ) {\n return;\n }\n }\n this.states.set(e.targetClientMsgId, {\n orderEpoch: e.epoch,\n orderSeq: e.serverSeq,\n lastEventId: e.eventClientMsgId,\n text: e.newText,\n bodyRanges: e.bodyRanges ?? null,\n });\n this.editedTargets.add(e.targetClientMsgId); // write-once\n }\n\n /**\n * Park an event for later re-attempt, deduping held re-deliveries by\n * eventClientMsgId so a repeatedly-delivered unresolvable edit is held exactly\n * once (and never double-applies when it finally resolves on reevaluate).\n */\n private holdIfNew(e: EditEvent): void {\n if (this.held.some((h) => h.eventClientMsgId === e.eventClientMsgId)) return;\n this.held.push(e);\n }\n\n /** The winning edit text for a target, or null if no valid edit has applied. */\n text(targetClientMsgId: string): string | null {\n return this.states.get(targetClientMsgId)?.text ?? null;\n }\n\n /** Write-once: true once any valid edit applied to the target. */\n isEdited(targetClientMsgId: string): boolean {\n return this.editedTargets.has(targetClientMsgId);\n }\n\n /**\n * The WINNING edit's replacement mention ranges for a target (raw, un-normalized),\n * or null when no valid edit applied or the winning edit carried none. The Chat\n * normalizes these against the edited text to compute the edited message's mentions\n * (mentions T6). LWW-consistent: always the same edit that `text(...)` returns.\n */\n bodyRanges(targetClientMsgId: string): MentionRange[] | null {\n return this.states.get(targetClientMsgId)?.bodyRanges ?? null;\n }\n\n /**\n * Re-run HELD edits when the roster/target newly resolves (call on member/roster\n * change and when a target message arrives). Clears `held` and re-ingests each\n * event with the fresh `authorOfTarget` — events that still don't resolve are\n * simply re-held; events that now resolve fold via the normal LWW path.\n * Idempotent: re-ingest is deduped by `seenEvents` (applied events) and by\n * `holdIfNew` (still-held events), so reevaluating repeatedly can neither\n * double-apply nor lose an edit.\n */\n reevaluateHeld(authorOfTarget: AuthorOfTarget): void {\n const pending = this.held;\n this.held = [];\n for (const e of pending) this.ingest(e, authorOfTarget);\n }\n}\n","// Byte / base64 helpers — portable across browser and Node (no Buffer dep on\n// the browser path). All MLS blobs cross the wire as base64 strings.\n\n/** Encode bytes to a standard (non-url-safe) base64 string. */\nexport function toBase64(bytes: Uint8Array): string {\n if (typeof Buffer !== 'undefined') {\n return Buffer.from(bytes).toString('base64');\n }\n let binary = '';\n const chunk = 0x8000;\n for (let i = 0; i < bytes.length; i += chunk) {\n binary += String.fromCharCode(...bytes.subarray(i, i + chunk));\n }\n return btoa(binary);\n}\n\n/** Decode a standard base64 string to bytes. */\nexport function fromBase64(b64: string): Uint8Array {\n if (typeof Buffer !== 'undefined') {\n return new Uint8Array(Buffer.from(b64, 'base64'));\n }\n const binary = atob(b64);\n const out = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);\n return out;\n}\n\n/** Lowercase hex of a byte buffer (a stable string key for any blob). */\nexport function bytesToHex(bytes: Uint8Array): string {\n let s = '';\n for (let i = 0; i < bytes.length; i++) {\n s += (bytes[i] ?? 0).toString(16).padStart(2, '0');\n }\n return s;\n}\n\nconst utf8Encoder = new TextEncoder();\nconst utf8Decoder = new TextDecoder();\n\nexport const encodeUtf8 = (s: string): Uint8Array => utf8Encoder.encode(s);\nexport const decodeUtf8 = (b: Uint8Array): string => utf8Decoder.decode(b);\n\n/** Bytes of a device id (the MLS credential identity == the `mdv_` device id). */\nexport const deviceIdBytes = (deviceId: string): Uint8Array => encodeUtf8(deviceId);\n\n/** A fresh client idempotency / id value (crypto-strong; never Math.random). */\nexport function randomId(): string {\n return crypto.randomUUID();\n}\n\n/** Mint a client-side `mdv_<uuidv7>` device id (SP-1.6 client-minted). The\n * server (messaging `ValidateDeviceID`) requires the mdv_ prefix followed by a\n * canonical UUID *version 7* — a v4 (what `crypto.randomUUID()` emits) is\n * rejected with `invalid_device_id`. So mint a real v7 here: the version must\n * match the server's id-validator, not just the prefix. A random v7 bound to\n * the caller's iJWT sub stays squat-safe (ownership = sub, uniqueness = PK). */\nexport function mintDeviceId(): string {\n return `mdv_${uuidV7()}`;\n}\n\n/**\n * Generate a canonical RFC 9562 UUID version 7 (time-ordered): 48-bit big-endian\n * Unix-ms timestamp, version nibble 7, variant bits 10, the rest CSPRNG random.\n * `crypto.randomUUID()` only emits v4, and the messaging server validates v7, so\n * the device-id minter cannot reuse it — this is the byte-shape the server's\n * `uuid.Version() == 7` check accepts.\n */\nexport function uuidV7(): string {\n const bytes = new Uint8Array(16);\n crypto.getRandomValues(bytes);\n const ts = Date.now(); // ms since epoch fits in 48 bits until year 10889\n // A DataView write avoids `noUncheckedIndexedAccess` \"possibly undefined\" on\n // raw index assignment and writes the 48-bit timestamp big-endian in one shot.\n const view = new DataView(bytes.buffer);\n view.setUint32(0, Math.floor(ts / 2 ** 16)); // high 32 bits of the 48-bit ms\n view.setUint16(4, ts & 0xffff); // low 16 bits\n view.setUint8(6, (view.getUint8(6) & 0x0f) | 0x70); // version 7\n view.setUint8(8, (view.getUint8(8) & 0x3f) | 0x80); // variant 10\n const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\n/** The canonical sorted-pair direct_key for a 1:1 chat (`dm:<a>|<b>`). */\nexport function directKey(userA: string, userB: string): string {\n const [a, b] = [userA, userB].sort();\n return `dm:${a}|${b}`;\n}\n\n/** Mint a `msg_<32 hex>` client message id (matches iOS format). */\nexport function mintClientMsgId(): string {\n const b = new Uint8Array(16);\n crypto.getRandomValues(b);\n return `msg_${Array.from(b, (x) => x.toString(16).padStart(2, '0')).join('')}`;\n}\n\n/** Truncate a string to 240 Unicode scalars (code points). Returns [truncated, wasTruncated]. */\nexport function truncateQuote(s: string): [string, boolean] {\n const scalars = [...s];\n if (scalars.length <= 240) return [s, false];\n return [scalars.slice(0, 240).join(''), true];\n}\n","// The messaging wire protocol for the web SDK — paths + request/response shapes.\n//\n// CRITICAL (differs from iOS): the web HttpClient does a RAW JSON.stringify of\n// the request body (NO camel↔snake conversion — verified @palbase/core\n// HttpClient). So every field here is snake_case to match the server tags\n// VERBATIM. The field names are the live server JSON tags, cross-checked against\n// modules/messaging/internal/handler/{group,message,keypackage,trust}.go and the\n// iOS MessagingTypes.swift wire doc (live-proven). All blobs are base64 strings.\n//\n// Paths mirror the iOS MessagingPaths (the M3 #7 contract): a {gid} segment is\n// ALWAYS a `grp_<uuidv7>` display_id (url-safe), NEVER the base64 rfc_group_id.\n\n// ─── Paths ───────────────────────────────────────────────────────────────────\n\nconst GROUPS = '/v1/messaging/groups';\nconst DEVICES = '/v1/messaging/devices';\nconst KEYDIR = '/v1/messaging/keydir';\n\n/** encodeURIComponent a single path segment (defense in depth; the real fix is\n * passing the url-safe grp_/mdv_ id, never the base64 rfc_group_id — M3 #7). */\nconst seg = (s: string): string => encodeURIComponent(s);\n\nexport const MessagingPaths = {\n groups: GROUPS,\n groupsDirect: `${GROUPS}/direct`,\n group: (displayId: string) => `${GROUPS}/${seg(displayId)}`,\n groupMetadata: (displayId: string) => `${GROUPS}/${seg(displayId)}/metadata`,\n groupMembers: (displayId: string) => `${GROUPS}/${seg(displayId)}/members`,\n groupMemberDevice: (displayId: string, deviceId: string) =>\n `${GROUPS}/${seg(displayId)}/members/${seg(deviceId)}`,\n groupMessages: (displayId: string) => `${GROUPS}/${seg(displayId)}/messages`,\n groupCommits: (displayId: string) => `${GROUPS}/${seg(displayId)}/commits`,\n groupRead: (displayId: string) => `${GROUPS}/${seg(displayId)}/read`,\n // Server-metadata cluster: the per-(user,group) notify scope (mute toggle, GET/PUT)\n // + the caller's own unread view (GET). {gid} is the grp_ display_id (M3 #7).\n groupNotify: (displayId: string) => `${GROUPS}/${seg(displayId)}/notify`,\n groupUnread: (displayId: string) => `${GROUPS}/${seg(displayId)}/unread`,\n\n deviceWelcomes: (deviceId: string) => `${DEVICES}/${seg(deviceId)}/welcomes`,\n deviceQueue: (deviceId: string) => `${DEVICES}/${seg(deviceId)}/queue`,\n deviceQueueAck: (deviceId: string) => `${DEVICES}/${seg(deviceId)}/queue/ack`,\n\n keydirDevices: `${KEYDIR}/devices`,\n keydirDeviceKeyPackages: (deviceId: string) => `${KEYDIR}/devices/${seg(deviceId)}/keypackages`,\n keydirUserDevices: (userId: string) => `${KEYDIR}/users/${seg(userId)}/devices`,\n keydirUserClaim: (userId: string) => `${KEYDIR}/users/${seg(userId)}/keypackages/claim`,\n keydirPinsVerify: `${KEYDIR}/pins/verify`,\n\n prefs: '/v1/messaging/prefs',\n} as const;\n\n/** True iff `s` is safe to place RAW in a URL path segment (M3 #7 gate). A\n * grp_/mdv_ id passes; a base64 rfc_group_id (`/`, `=`, `+`) FAILS. */\nexport function isUrlSafePathSegment(s: string): boolean {\n return s.length > 0 && /^[A-Za-z0-9\\-._~]+$/.test(s);\n}\n\n// ─── Keydir (SP-0) ───────────────────────────────────────────────────────────\n\nexport type MessagingPlatform = 'android' | 'ios' | 'web';\n\nexport interface EnrollDeviceBody {\n device_id: string;\n platform: MessagingPlatform;\n device_name?: string;\n cipher_suite: number;\n signature_key: string; // base64 raw curve public key\n credential: string; // base64 TLS-encoded MLS Credential (identity == device_id)\n capabilities: string; // base64\n}\n\nexport interface EnrollResponse {\n device_id: string;\n signature_key_fingerprint: string;\n trust_tier: string;\n one_time_count: number;\n}\n\nexport interface ReplenishBody {\n key_packages: string[]; // base64 raw KeyPackages\n}\n\nexport interface DeviceInfo {\n device_id: string;\n signature_key: string;\n credential: string;\n cipher_suite: number;\n key_epoch: number;\n trust_tier: string;\n first_seen_at: string;\n}\n\nexport interface DeviceListResponse {\n device_set_version: string;\n devices: DeviceInfo[];\n warnings?: unknown[];\n}\n\nexport interface ClaimedKeyPackage {\n device_id: string;\n cipher_suite: number;\n key_package: string; // base64\n reused: boolean;\n}\n\nexport interface ClaimResponse {\n key_packages: ClaimedKeyPackage[];\n depleted_devices?: string[];\n}\n\nexport interface VerifyBody {\n peer_user_id: string;\n verified_device_set_hash: string;\n}\n\n// ─── Group create / direct ────────────────────────────────────────────────────\n\nexport interface CreateGroupBody {\n rfc_group_id: string; // base64\n cipher_suite: number;\n creator_device_id: string;\n creator_leaf_index: number;\n metadata?: string; // base64 sealed-name blob; omitted on web (no exporter yet)\n}\n\nexport interface CreateGroupResponse {\n display_id: string;\n rfc_group_id: string;\n current_epoch: number;\n metadata?: string;\n}\n\nexport interface CreateDirectGroupBody {\n direct_key: string;\n peer_user_id: string;\n rfc_group_id: string; // base64\n cipher_suite: number;\n creator_device_id: string;\n creator_leaf_index: number;\n}\n\nexport interface CreateDirectGroupResponse {\n display_id: string;\n rfc_group_id: string;\n current_epoch: number;\n owner_user_id?: string;\n exists?: boolean;\n}\n\nexport interface GroupReadResponse {\n display_id: string;\n rfc_group_id: string;\n current_epoch: number;\n status?: string;\n metadata?: string;\n}\n\n// ─── Members ───────────────────────────────────────────────────────────────────\n\nexport type MessagingGroupRole = 'owner' | 'admin' | 'member';\n\nexport interface GroupMemberRow {\n device_id: string;\n user_id: string;\n role: MessagingGroupRole;\n leaf_index: number;\n}\n\nexport interface ListMembersResponse {\n members: GroupMemberRow[];\n}\n\nexport interface WelcomeBody {\n recipient_device_id: string;\n recipient_user_id: string;\n welcome_b64: string;\n}\n\n/** `POST /groups/{gid}/members` — declared add + opaque commit + welcomes. */\nexport interface AddMemberBody {\n op: string;\n commit_b64: string;\n target_device_ids: string[];\n leaf_indexes: number[];\n user_ids: string[];\n key_package_refs: string[];\n welcomes: WelcomeBody[];\n}\n\n/** `DELETE /groups/{gid}/members/{deviceId}` — multi-remove / self-leave. */\nexport interface RemoveMembersBody {\n op: string;\n commit_b64: string;\n target_device_ids: string[];\n user_ids: string[];\n}\n\n/** `{ epoch }` — the advanced-to epoch a membership/commit write returns. */\nexport interface CommitAccepted {\n epoch: number;\n}\n\n// ─── Messages ──────────────────────────────────────────────────────────────────\n\nexport interface SendMessageBody {\n ciphertext_b64: string;\n client_idem_key: string;\n}\n\nexport interface SentReceipt {\n server_seq: number;\n epoch: number;\n}\n\n/** One queue row from `GET /devices/{deviceId}/queue`. */\nexport interface QueuedMessage {\n id: string;\n epoch: number;\n content_type: number; // 1=app, 2=proposal, 3=commit\n wire_format: number; // 1=public, 2=private\n ciphertext_b64: string;\n server_seq: number;\n // OPTIONAL group attribution + the fanned-out declaration (the live server\n // does not fan these on queue rows yet — nil today, gate engages when present).\n rfc_group_id_b64?: string;\n sender_device_id?: string;\n declared_added_device_ids?: string[];\n declared_removed_device_ids?: string[];\n declared_op?: string;\n}\n\nexport interface QueuePage {\n messages: QueuedMessage[];\n}\n\n/** One row from `GET /devices/{deviceId}/welcomes` — a new-member bootstrap. */\nexport interface PendingWelcome {\n id: string;\n display_id: string; // the group's grp_ routing id (url-safe; NOT the base64 rfc)\n rfc_group_id_b64: string;\n welcome_b64: string;\n added_at_epoch: number;\n}\n\nexport interface WelcomePage {\n welcomes: PendingWelcome[];\n}\n\nexport interface AckBody {\n message_ids: string[];\n}\n\n/** Queue-row content-type discriminator (matches the server `content_type`). */\nexport const ContentType = {\n application: 1,\n proposal: 2,\n commit: 3,\n} as const;\n\n// ─── SP-3 comms (read receipts / prefs / presence / typing) ──────────────────\n\nexport interface MarkReadBody {\n read_seq: number;\n read_epoch: number;\n is_private: boolean;\n}\n\nexport interface MarkReadResponse {\n read_seq: number;\n advanced: boolean;\n broadcast: boolean;\n}\n\nexport interface CommsPrefsResponse {\n share_presence: boolean;\n share_receipts: boolean;\n updated_at?: string;\n}\n\nexport interface SetPrefsBody {\n share_presence?: boolean;\n share_receipts?: boolean;\n}\n\nexport interface CommsPrefs {\n sharePresence: boolean;\n shareReceipts: boolean;\n}\n","// The MLS key directory (SP-0) mechanics — INTERNAL, mirroring the iOS\n// KeyDirectoryInternal. Hidden behind the coordinator's implicit enroll (the\n// first chat op self-enrolls). Byte-identical wire calls to iOS: same\n// /v1/messaging/keydir/* routes, same iJWT bearer (palbeRequest), same\n// snake_case bodies.\n\nimport { palbeRequest } from '../request.js';\nimport type { PalbeRuntime } from '../runtime.js';\nimport type { MlsEngine } from './mls-engine.js';\nimport type { DeviceIdStore } from './storage.js';\nimport { toBase64 } from './util.js';\nimport {\n type ClaimResponse,\n type DeviceListResponse,\n type EnrollDeviceBody,\n type EnrollResponse,\n MessagingPaths,\n type ReplenishBody,\n type VerifyBody,\n} from './wire.js';\n\n/**\n * Enroll THIS device + publish its first KeyPackages from REAL engine material.\n * Idempotent: a returning user (persisted device id) skips the POST and just\n * replenishes. Returns the resolved device id.\n *\n * Flow (mirrors iOS enroll):\n * 1. engine.enrollmentMaterial() → { signatureKey, credential, capabilities }\n * 2. POST /keydir/devices (the server validates the client-minted mdv_ id +\n * credential.identity == device_id + ownership == iJWT sub)\n * 3. generate N key packages → POST /keydir/devices/{id}/keypackages\n */\nexport async function enrollDevice(\n rt: PalbeRuntime,\n engine: MlsEngine,\n deviceStore: DeviceIdStore,\n opts: { keyPackageCount?: number; deviceName?: string } = {},\n): Promise<string> {\n const keyPackageCount = opts.keyPackageCount ?? 10;\n const deviceId = engine.deviceId;\n const alreadyEnrolled = (await deviceStore.load(engine.userId)) === deviceId;\n\n if (!alreadyEnrolled) {\n const material = await engine.enrollmentMaterial();\n const body: EnrollDeviceBody = {\n device_id: deviceId,\n platform: 'web',\n device_name: opts.deviceName,\n cipher_suite: 1,\n signature_key: toBase64(material.signatureKey),\n credential: toBase64(material.credential),\n capabilities: toBase64(material.capabilities),\n };\n await palbeRequest<EnrollResponse>(rt, 'POST', MessagingPaths.keydirDevices, { body });\n await deviceStore.save(engine.userId, deviceId);\n }\n\n if (keyPackageCount > 0) {\n const raws: string[] = [];\n for (let i = 0; i < keyPackageCount; i++) {\n raws.push(toBase64(await engine.generateKeyPackage()));\n }\n await replenish(rt, deviceId, raws);\n }\n return deviceId;\n}\n\n/** Publish more one-use KeyPackages for a device. */\nexport async function replenish(\n rt: PalbeRuntime,\n deviceId: string,\n rawKeyPackagesB64: string[],\n): Promise<void> {\n const body: ReplenishBody = { key_packages: rawKeyPackagesB64 };\n await palbeRequest<void>(rt, 'POST', MessagingPaths.keydirDeviceKeyPackages(deviceId), { body });\n}\n\n/** Claim a peer's KeyPackages (one per device) to add them to a group. */\nexport async function claimKeyPackages(rt: PalbeRuntime, userId: string): Promise<ClaimResponse> {\n return palbeRequest<ClaimResponse>(rt, 'POST', MessagingPaths.keydirUserClaim(userId), {\n body: {},\n });\n}\n\n/** List a user's enrolled devices (for the safety number + roster resolution). */\nexport async function listDevices(rt: PalbeRuntime, userId: string): Promise<DeviceListResponse> {\n return palbeRequest<DeviceListResponse>(rt, 'GET', MessagingPaths.keydirUserDevices(userId));\n}\n\n/** Record a TOFU verify pin for a peer's current device set. */\nexport async function verifyPeer(\n rt: PalbeRuntime,\n peerUserId: string,\n verifiedDeviceSetHash: string,\n): Promise<void> {\n const body: VerifyBody = {\n peer_user_id: peerUserId,\n verified_device_set_hash: verifiedDeviceSetHash,\n };\n await palbeRequest<void>(rt, 'POST', MessagingPaths.keydirPinsVerify, { body });\n}\n","// The membership/send orchestrator — the web mirror of iOS GroupMessaging.\n// Two correctness invariants (design §3, identical to iOS):\n//\n// (1) ATOMIC-PERSIST AROUND A COMMIT. Stage a pending commit in the engine,\n// POST it, apply ONLY on a 200. On a 409 epoch_conflict clearPendingCommit —\n// the local epoch is NEVER ahead of the server-accepted epoch.\n// (2) THE STALE-EPOCH REBASE LOOP. On 409 drain the winning commits, rebuild at\n// the new epoch, re-POST — bounded + jittered. Exhaustion throws.\n//\n// sendMessage NEVER rebases: an application message is epoch-bound; a 422\n// stale_application surfaces to the caller (re-send after the delivery source\n// catches up). Only membership COMMITS rebase.\n//\n// WEB GAP vs iOS: there is no MLS exporter export in the WASM surface yet, so\n// the group NAME is not sealed (metadata omitted; the server stays blind and the\n// UI falls back to a friendly label). Text/membership flows are byte-identical.\n\nimport { BackendError, isBackendError } from '../errors.js';\nimport { palbeRequest } from '../request.js';\nimport type { PalbeRuntime } from '../runtime.js';\nimport type { MessageStore, StoredMessage } from './history.js';\nimport { claimKeyPackages } from './keydir.js';\nimport type { MlsEngine } from './mls-engine.js';\nimport type { ResolvedReply, SentReceipt } from './types.js';\nimport {\n decodeUtf8,\n deviceIdBytes,\n encodeUtf8,\n fromBase64,\n directKey as makeDirectKey,\n mintClientMsgId,\n randomId,\n toBase64,\n} from './util.js';\nimport {\n type AddMemberBody,\n type ClaimResponse,\n type CommitAccepted,\n ContentType,\n type CreateDirectGroupBody,\n type CreateDirectGroupResponse,\n type CreateGroupBody,\n type CreateGroupResponse,\n type ListMembersResponse,\n MessagingPaths,\n type QueuePage,\n type RemoveMembersBody,\n type SendMessageBody,\n type WelcomeBody,\n type SentReceipt as WireReceipt,\n} from './wire.js';\n\n/** A resolved group as the SDK tracks it. `name` is always null on web (no exporter). */\nexport interface MessagingGroup {\n displayId: string;\n rfcGroupId: string; // base64 routing key\n currentEpoch: number;\n ownerUserId: string | null;\n directKey: string | null;\n name: string | null;\n}\n\nexport interface QuotePreview {\n kind: string;\n author_user_id: string;\n body?: string;\n body_truncated: boolean;\n}\nexport interface ReplyRef {\n v: number;\n client_msg_id: string;\n preview?: QuotePreview;\n}\n\n/** A single mention range inside a message body.\n * Offsets are UTF-16 code units, half-open [start, start+length).\n * Wire: snake_case (mentioned_user_id, body_ranges) — web does RAW JSON, no case-convert.\n */\nexport interface MentionRange {\n start: number;\n length: number;\n mentionedUserId: string;\n}\n\n/** The typed E2E message envelope (matches the iOS wire format exactly). */\ninterface MsgEnvelope {\n v: 1;\n type: 'text';\n client_msg_id: string;\n text: string;\n reply_to?: ReplyRef;\n body_ranges?: Array<{ start: number; length: number; mentioned_user_id: string }>;\n // Per-message TTL — sender_send_ts present IFF start === 'send' (raw snake_case wire).\n expiry?: { v: number; ttl_seconds: number; start: 'send' | 'read'; sender_send_ts?: number };\n}\n\nexport interface ReactionPayload {\n targetClientMsgId: string;\n emoji: string;\n op: 'add' | 'remove';\n}\n\nexport interface EditPayload {\n targetClientMsgId: string;\n newText: string;\n}\n\nexport interface DeletePayload {\n targetClientMsgId: string;\n scope: string;\n}\n\n/** Per-message TTL spec (decoded). senderSendTs present iff start === 'send'. */\nexport interface ExpirySpec {\n v: number;\n ttlSeconds: number;\n start: 'send' | 'read';\n senderSendTs: number | null;\n}\n\n/** Encode a delete event into the wire envelope bytes. */\nexport function encodeDelete(args: { clientMsgId: string; targetClientMsgId: string }): Uint8Array {\n return encodeUtf8(\n JSON.stringify({\n v: 1,\n type: 'delete',\n client_msg_id: args.clientMsgId,\n target_client_msg_id: args.targetClientMsgId,\n scope: 'everyone',\n }),\n );\n}\n\n/** Encode an edit event into the wire envelope bytes. */\nexport function encodeEdit(args: {\n clientMsgId: string;\n targetClientMsgId: string;\n newText: string;\n bodyRanges?: MentionRange[] | null;\n}): Uint8Array {\n return encodeUtf8(\n JSON.stringify({\n v: 1,\n type: 'edit',\n client_msg_id: args.clientMsgId,\n target_client_msg_id: args.targetClientMsgId,\n new_text: args.newText,\n ...(args.bodyRanges && args.bodyRanges.length > 0\n ? {\n body_ranges: args.bodyRanges.map((r) => ({\n start: r.start,\n length: r.length,\n mentioned_user_id: r.mentionedUserId,\n })),\n }\n : {}),\n }),\n );\n}\n\n/** Encode a reaction event into the wire envelope bytes. */\nexport function encodeReaction(args: {\n clientMsgId: string;\n targetClientMsgId: string;\n emoji: string;\n op: 'add' | 'remove';\n}): Uint8Array {\n return encodeUtf8(\n JSON.stringify({\n v: 1,\n type: 'reaction',\n client_msg_id: args.clientMsgId,\n target_client_msg_id: args.targetClientMsgId,\n emoji: args.emoji,\n op: args.op,\n }),\n );\n}\n\n/** Encode a text message into the wire envelope bytes. */\nexport function encodeEnvelope(args: {\n text: string;\n clientMsgId: string;\n replyTo?: ReplyRef | null;\n bodyRanges?: MentionRange[] | null;\n expiry?: ExpirySpec | null;\n}): Uint8Array {\n const env: MsgEnvelope = {\n v: 1,\n type: 'text',\n client_msg_id: args.clientMsgId,\n text: args.text,\n ...(args.replyTo ? { reply_to: args.replyTo } : {}),\n ...(args.bodyRanges && args.bodyRanges.length > 0\n ? {\n body_ranges: args.bodyRanges.map((r) => ({\n start: r.start,\n length: r.length,\n mentioned_user_id: r.mentionedUserId,\n })),\n }\n : {}),\n ...(args.expiry\n ? {\n expiry: {\n v: args.expiry.v,\n ttl_seconds: args.expiry.ttlSeconds,\n start: args.expiry.start,\n // present IFF send (drop a stray senderSendTs on a read anchor)\n ...(args.expiry.start === 'send' && args.expiry.senderSendTs != null\n ? { sender_send_ts: args.expiry.senderSendTs }\n : {}),\n },\n }\n : {}),\n };\n return encodeUtf8(JSON.stringify(env));\n}\n\n/** Encode a per-chat default disappearing-timer control envelope. ttlSeconds === null\n * disables the chat default; it is OMITTED from the wire (absent === null === disable). */\nexport function encodeTimerSet(args: {\n clientMsgId: string;\n ttlSeconds: number | null;\n start: 'send' | 'read';\n}): Uint8Array {\n return encodeUtf8(\n JSON.stringify({\n v: 1,\n type: 'timer_set',\n client_msg_id: args.clientMsgId,\n ...(args.ttlSeconds != null ? { ttl_seconds: args.ttlSeconds } : {}),\n start: args.start,\n }),\n );\n}\n\n/** Decode an application plaintext (tolerant: bare-text or missing-v → legacy fallback). */\nexport function decodeEnvelope(bytes: Uint8Array): {\n type?: string;\n text: string | null;\n clientMsgId: string;\n replyTo: ReplyRef | null;\n reaction?: ReactionPayload;\n edit?: EditPayload;\n delete?: DeletePayload;\n bodyRanges?: MentionRange[];\n expiry?: ExpirySpec;\n timer?: { ttlSeconds: number | null; start: 'send' | 'read' };\n} {\n const s = decodeUtf8(bytes);\n try {\n const o = JSON.parse(s) as {\n type?: string;\n v?: number;\n text?: string;\n client_msg_id?: string;\n reply_to?: ReplyRef;\n target_client_msg_id?: string;\n emoji?: string;\n op?: string;\n new_text?: string;\n scope?: string;\n body_ranges?: Array<{ start: number; length: number; mentioned_user_id: string }>;\n expiry?: unknown;\n ttl_seconds?: number;\n sender_send_ts?: number;\n start?: string;\n };\n if (typeof o === 'object' && o !== null && o.type === 'timer_set') {\n // timer_set control envelope — ttl_seconds absent === null === disable.\n return {\n type: 'timer_set',\n text: null,\n clientMsgId: o.client_msg_id ?? '',\n replyTo: null,\n timer: {\n ttlSeconds: typeof o.ttl_seconds === 'number' ? o.ttl_seconds : null,\n start: o.start === 'read' ? 'read' : 'send',\n },\n };\n }\n if (typeof o === 'object' && o !== null && o.type === 'delete') {\n // Delete envelope.\n return {\n type: 'delete',\n text: null,\n clientMsgId: o.client_msg_id ?? '',\n replyTo: null,\n delete: {\n targetClientMsgId: o.target_client_msg_id ?? '',\n scope: o.scope ?? 'everyone',\n },\n };\n }\n if (typeof o === 'object' && o !== null && o.type === 'reaction') {\n // Reaction envelope.\n return {\n type: 'reaction',\n text: null,\n clientMsgId: o.client_msg_id ?? '',\n replyTo: null,\n reaction: {\n targetClientMsgId: o.target_client_msg_id ?? '',\n emoji: o.emoji ?? '',\n op: (o.op as 'add' | 'remove') ?? 'add',\n },\n };\n }\n if (typeof o === 'object' && o !== null && o.type === 'edit') {\n // Edit envelope.\n const editRanges = decodeBodyRanges(o.body_ranges);\n return {\n type: 'edit',\n text: null,\n clientMsgId: o.client_msg_id ?? '',\n replyTo: null,\n edit: {\n targetClientMsgId: o.target_client_msg_id ?? '',\n newText: o.new_text ?? '',\n },\n ...(editRanges ? { bodyRanges: editRanges } : {}),\n };\n }\n if (typeof o === 'object' && o !== null && o.type === 'text' && typeof o.v === 'number') {\n // Full v:1 envelope — extract all fields.\n const textRanges = decodeBodyRanges(o.body_ranges);\n const expiry = decodeExpiry(o.expiry);\n return {\n type: 'text',\n text: o.text ?? null,\n clientMsgId: o.client_msg_id ?? '',\n replyTo: o.reply_to ?? null,\n ...(textRanges ? { bodyRanges: textRanges } : {}),\n ...(expiry ? { expiry } : {}),\n };\n }\n if (typeof o === 'object' && o !== null && o.type === 'text') {\n // Legacy envelope (no v) — extract text, no reply context.\n return { type: 'text', text: o.text ?? null, clientMsgId: '', replyTo: null };\n }\n // A media envelope (not yet supported on web) → no text.\n if (typeof o === 'object' && o !== null && o.type === 'media')\n return { type: 'media', text: null, clientMsgId: '', replyTo: null };\n } catch {\n // legacy/bare plaintext\n }\n return { text: s, clientMsgId: '', replyTo: null };\n}\n\n/** Map a raw wire expiry object (snake_case) to ExpirySpec. Returns undefined if absent\n * or malformed (no ttl_seconds) — an old decoder with this branch stripped ignores it. */\nfunction decodeExpiry(raw: unknown): ExpirySpec | undefined {\n if (typeof raw !== 'object' || raw === null) return undefined;\n const o = raw as { v?: number; ttl_seconds?: number; start?: string; sender_send_ts?: number };\n if (typeof o.ttl_seconds !== 'number') return undefined;\n const start: 'send' | 'read' = o.start === 'read' ? 'read' : 'send';\n return {\n v: typeof o.v === 'number' ? o.v : 1,\n ttlSeconds: o.ttl_seconds,\n start,\n // tolerant: only honor sender_send_ts on a send anchor; null otherwise.\n senderSendTs:\n start === 'send' && typeof o.sender_send_ts === 'number' ? o.sender_send_ts : null,\n };\n}\n\n/** Map raw wire body_ranges (snake_case) to MentionRange[]. Returns undefined if absent or empty. */\nfunction decodeBodyRanges(\n raw: Array<{ start: number; length: number; mentioned_user_id: string }> | undefined | null,\n): MentionRange[] | undefined {\n if (!raw || raw.length === 0) return undefined;\n return raw.map((r) => ({\n start: r.start,\n length: r.length,\n mentionedUserId: r.mentioned_user_id,\n }));\n}\n\n/**\n * Resolve a reply reference against local history.\n *\n * `lookup(clientMsgId)` should return `{ text, senderUserId }` when the parent\n * is in local history, or `null` when not found.\n *\n * Spoof defense: when the parent IS found, the REAL text from local history wins\n * over the embedded `preview.body` — an attacker cannot make a reply appear to\n * quote different text than what was actually sent.\n */\nexport function resolveReply(\n ref: ReplyRef,\n lookup: (clientMsgId: string) => { text: string; senderUserId: string } | null,\n): ResolvedReply {\n const parent = lookup(ref.client_msg_id);\n if (parent !== null) {\n return {\n parentClientMsgId: ref.client_msg_id,\n state: 'resolved',\n quoteSenderUserId: parent.senderUserId,\n quoteText: parent.text,\n quoteKind: ref.preview?.kind ?? 'text',\n };\n }\n return {\n parentClientMsgId: ref.client_msg_id,\n state: 'unavailable',\n quoteSenderUserId: ref.preview?.author_user_id ?? '',\n quoteText: ref.preview?.body ?? null,\n quoteKind: ref.preview?.kind ?? 'text',\n };\n}\n\nexport interface RebaseOptions {\n maxAttempts: number;\n baseDelayMs: number;\n jitterMs: number;\n}\n\nconst DEFAULT_REBASE: RebaseOptions = { maxAttempts: 6, baseDelayMs: 80, jitterMs: 120 };\n\n/** Thrown when the rebase loop exhausts its budget (the change did NOT land). */\nexport class RebaseExhausted extends Error {\n constructor(\n readonly attempts: number,\n readonly lastKnownEpoch: number | null,\n ) {\n super(`commit rebase exhausted after ${attempts} attempts`);\n this.name = 'RebaseExhausted';\n }\n}\n\nexport class GroupMessaging {\n constructor(\n private readonly rt: PalbeRuntime,\n private readonly engine: MlsEngine,\n private readonly selfUserId: string,\n private readonly selfDeviceId: string,\n private readonly messageStore: MessageStore,\n private readonly rebase: RebaseOptions = DEFAULT_REBASE,\n ) {}\n\n // ── Create ──\n\n /** Create a group: mint the rfc_group_id, POST create, return the server group.\n * No name sealing on web (metadata omitted — server-blind, label fallback). */\n async createGroup(cipherSuite = 1): Promise<MessagingGroup> {\n const rfcGroupId = await this.engine.createGroup();\n const body: CreateGroupBody = {\n rfc_group_id: toBase64(rfcGroupId),\n cipher_suite: cipherSuite,\n creator_device_id: this.selfDeviceId,\n creator_leaf_index: 0,\n };\n const res = await palbeRequest<CreateGroupResponse>(this.rt, 'POST', MessagingPaths.groups, {\n body,\n });\n return {\n displayId: res.display_id,\n rfcGroupId: res.rfc_group_id,\n currentEpoch: res.current_epoch,\n ownerUserId: this.selfUserId,\n directKey: null,\n name: null,\n };\n }\n\n // ── Direct (1:1) get-or-create ──\n\n /** Materialize a DIRECT chat keyed by the stable direct_key (sorted user pair).\n * EXISTS → adopt (the Welcome drains via the pump); ABSENT → create + add peer. */\n async getOrCreateDirect(peerUserId: string): Promise<MessagingGroup> {\n const dk = makeDirectKey(this.selfUserId, peerUserId);\n const rfcGroupId = await this.engine.createGroup(); // reserved (used only if INSERT)\n const body: CreateDirectGroupBody = {\n direct_key: dk,\n peer_user_id: peerUserId,\n rfc_group_id: toBase64(rfcGroupId),\n cipher_suite: 1,\n creator_device_id: this.selfDeviceId,\n creator_leaf_index: 0,\n };\n const res = await palbeRequest<CreateDirectGroupResponse>(\n this.rt,\n 'POST',\n MessagingPaths.groupsDirect,\n { body },\n );\n\n if (res.exists === true) {\n // ADOPT — discard the reserved group; the founder's Welcome drains in.\n await this.engine.clearPendingCommit(rfcGroupId);\n return {\n displayId: res.display_id,\n rfcGroupId: res.rfc_group_id,\n currentEpoch: res.current_epoch,\n ownerUserId: res.owner_user_id ?? null,\n directKey: dk,\n name: null,\n };\n }\n\n // CREATE branch — the server bound the group to direct_key; add the peer.\n const group: MessagingGroup = {\n displayId: res.display_id,\n rfcGroupId: res.rfc_group_id,\n currentEpoch: res.current_epoch,\n ownerUserId: this.selfUserId,\n directKey: dk,\n name: null,\n };\n const claimed = await claimKeyPackages(this.rt, peerUserId);\n await this.addMember(group, peerUserId, claimed);\n return group;\n }\n\n // ── Members ──\n\n /** The group roster (device leaves). The coordinator dedups to users. */\n async listMembers(displayId: string): Promise<ListMembersResponse['members']> {\n const res = await palbeRequest<ListMembersResponse>(\n this.rt,\n 'GET',\n MessagingPaths.groupMembers(displayId),\n );\n return res.members;\n }\n\n /** Add a member: claim → stage the add commit → run the rebase loop. SP-1.6:\n * EVERY device of the user joins in ONE atomic commit (one shared Welcome). */\n async addMember(group: MessagingGroup, userId: string, claimed?: ClaimResponse): Promise<void> {\n const claim = claimed ?? (await claimKeyPackages(this.rt, userId));\n const adds = claim.key_packages\n .map((kp) => {\n try {\n return { deviceId: kp.device_id, kp: fromBase64(kp.key_package) };\n } catch {\n return null;\n }\n })\n .filter((x): x is { deviceId: string; kp: Uint8Array } => x !== null);\n if (adds.length === 0) {\n throw new BackendError('validation', {\n code: 'no_keypackage',\n message: 'no claimable key package for user',\n status: 422,\n });\n }\n const userIds = adds.map(() => userId);\n\n await this.commitWithRebase(group.rfcGroupId, async () => {\n const out = await this.engine.commitChanges(\n fromBase64(group.rfcGroupId),\n adds.map((a) => a.kp),\n [],\n );\n // One shared Welcome per added device (the engine returns one welcome for a\n // single-Welcome multi-add; route it to each added device).\n const welcomeB64 = out.welcome ? toBase64(out.welcome) : '';\n const welcomes: WelcomeBody[] = adds.map((a) => ({\n recipient_device_id: a.deviceId,\n recipient_user_id: userId,\n welcome_b64: welcomeB64,\n }));\n const memberBody: AddMemberBody = {\n op: 'add',\n commit_b64: toBase64(out.commit),\n target_device_ids: adds.map((a) => a.deviceId),\n leaf_indexes: out.addedLeafIndices,\n user_ids: userIds,\n key_package_refs: [],\n welcomes,\n };\n return {\n method: 'POST',\n path: MessagingPaths.groupMembers(group.displayId),\n body: memberBody,\n };\n });\n }\n\n /** Remove a user = evict ALL their device leaves in ONE commit (SP-1.6 D4). */\n async removeMember(group: MessagingGroup, userId: string): Promise<void> {\n const members = await this.listMembers(group.displayId);\n const deviceIds = members.filter((m) => m.user_id === userId).map((m) => m.device_id);\n if (deviceIds.length === 0) {\n throw new BackendError('validation', {\n code: 'not_a_member',\n message: 'user has no device in this group',\n status: 404,\n });\n }\n await this.stageMultiRemove(group, deviceIds, [userId]);\n }\n\n /** Leave = remove the CALLING device's leaf only (per-device self-leave). */\n async leaveGroup(group: MessagingGroup): Promise<void> {\n await this.stageMultiRemove(group, [this.selfDeviceId], [this.selfUserId]);\n }\n\n private async stageMultiRemove(\n group: MessagingGroup,\n deviceIds: string[],\n removedUserIds: string[],\n ): Promise<void> {\n const pathDeviceId = deviceIds[0];\n if (!pathDeviceId) throw new Error('no device to remove');\n await this.commitWithRebase(group.rfcGroupId, async () => {\n const out = await this.engine.commitChanges(\n fromBase64(group.rfcGroupId),\n [],\n deviceIds.map(deviceIdBytes),\n );\n const body: RemoveMembersBody = {\n op: 'remove',\n commit_b64: toBase64(out.commit),\n target_device_ids: deviceIds,\n user_ids: removedUserIds,\n };\n return {\n method: 'DELETE',\n path: MessagingPaths.groupMemberDevice(group.displayId, pathDeviceId),\n body,\n };\n });\n }\n\n // ── Send ──\n\n /** Send a text message. Encrypt at the current epoch, POST, return the receipt +\n * the client-minted `clientMsgId` (needed by the Chat layer for reply indexing).\n * NEVER rebases (a 422 stale_application surfaces to the caller). */\n async sendText(\n group: MessagingGroup,\n text: string,\n replyTo?: ReplyRef | null,\n bodyRanges?: MentionRange[] | null,\n expiry?: ExpirySpec | null,\n ): Promise<{ receipt: SentReceipt; clientMsgId: string }> {\n const clientMsgId = mintClientMsgId();\n const plaintext = encodeEnvelope({ text, clientMsgId, replyTo, bodyRanges, expiry });\n const ct = await this.engine.encryptApplication(fromBase64(group.rfcGroupId), plaintext);\n const body: SendMessageBody = {\n ciphertext_b64: toBase64(ct),\n client_idem_key: randomId(),\n };\n const wire = await palbeRequest<WireReceipt>(\n this.rt,\n 'POST',\n MessagingPaths.groupMessages(group.displayId),\n { body },\n );\n\n // Persist the OUTGOING copy — the ONLY durable form of an own-sent message\n // (the sender can never decrypt its own ciphertext). Best-effort: the send\n // already landed; a persist failure must not fail/re-trigger it.\n const stored: StoredMessage = {\n id: `${group.rfcGroupId}#${wire.server_seq}`,\n direction: 'outgoing',\n text,\n senderDeviceId: this.selfDeviceId,\n epoch: wire.epoch,\n serverSeq: wire.server_seq,\n at: Date.now(),\n clientMsgId,\n replyTo: replyTo\n ? {\n clientMsgId: replyTo.client_msg_id,\n previewBody: replyTo.preview?.body ?? null,\n previewAuthorUserId: replyTo.preview?.author_user_id ?? null,\n previewKind: replyTo.preview?.kind ?? 'text',\n }\n : null,\n // Persist the OUTGOING bubble's mention ranges so an own-sent mention re-resolves\n // onto its bubble after a reload (own-send reload parity — the T4-reviewer Minor).\n ...(bodyRanges && bodyRanges.length > 0 ? { bodyRanges } : {}),\n // Disappearing T10: persist the per-message TTL so the own-send re-arms its purge\n // after a cold launch (the projection derives the deadline from this row's expiry).\n ...(expiry ? { expiry } : {}),\n };\n try {\n await this.messageStore.append(group.rfcGroupId, stored);\n } catch {\n // logged at the coordinator level; never fails the send\n }\n return { receipt: { serverSeq: wire.server_seq, epoch: wire.epoch }, clientMsgId };\n }\n\n /** Send a per-chat default disappearing-timer control envelope (`timer_set`). Encrypts\n * a `type:'timer_set'` envelope at the current epoch and sends through the SAME MLS\n * application path as `sendText` (the server stays blind — it's an opaque app message,\n * NEVER a bubble). `ttlSeconds === null` disables the default (omitted from the wire).\n * Persists the outgoing `timer_set` row so the chat default re-folds on cold launch\n * (the page-local TimerFold in projectHistory). NEVER rebases (epoch-bound). */\n async sendTimerSet(\n group: MessagingGroup,\n args: { clientMsgId: string; ttlSeconds: number | null; start: 'send' | 'read' },\n ): Promise<{ receipt: SentReceipt; clientMsgId: string }> {\n const plaintext = encodeTimerSet({\n clientMsgId: args.clientMsgId,\n ttlSeconds: args.ttlSeconds,\n start: args.start,\n });\n const ct = await this.engine.encryptApplication(fromBase64(group.rfcGroupId), plaintext);\n const body: SendMessageBody = {\n ciphertext_b64: toBase64(ct),\n client_idem_key: randomId(),\n };\n const wire = await palbeRequest<WireReceipt>(\n this.rt,\n 'POST',\n MessagingPaths.groupMessages(group.displayId),\n { body },\n );\n\n // Persist the OUTGOING timer_set copy (same rationale as sendText: the sender can't\n // decrypt its own ciphertext). text is null — a timer_set is never a bubble; the\n // envelopeType + timer fields drive the chat-default re-fold on reload.\n const stored: StoredMessage = {\n id: `${group.rfcGroupId}#${wire.server_seq}`,\n direction: 'outgoing',\n text: null,\n senderDeviceId: this.selfDeviceId,\n epoch: wire.epoch,\n serverSeq: wire.server_seq,\n at: Date.now(),\n clientMsgId: args.clientMsgId,\n replyTo: null,\n envelopeType: 'timer_set',\n timer: { ttlSeconds: args.ttlSeconds, start: args.start },\n };\n try {\n await this.messageStore.append(group.rfcGroupId, stored);\n } catch {\n // best-effort, identical to sendText/sendReaction/sendEdit/sendDelete\n }\n return {\n receipt: { serverSeq: wire.server_seq, epoch: wire.epoch },\n clientMsgId: args.clientMsgId,\n };\n }\n\n /** Send a reaction (add/remove of an emoji on a target message). Encrypts a\n * `type:'reaction'` envelope at the current epoch and sends through the SAME\n * MLS application path as `sendText` (the server stays blind — a reaction is\n * just another application message). Persists the outgoing reaction row so it\n * re-folds onto its target after a reload (the own-send half of the reload\n * parity). NEVER rebases (epoch-bound like any application message). */\n async sendReaction(\n group: MessagingGroup,\n args: { clientMsgId: string; targetClientMsgId: string; emoji: string; op: 'add' | 'remove' },\n ): Promise<{ receipt: SentReceipt; clientMsgId: string }> {\n const plaintext = encodeReaction({\n clientMsgId: args.clientMsgId,\n targetClientMsgId: args.targetClientMsgId,\n emoji: args.emoji,\n op: args.op,\n });\n const ct = await this.engine.encryptApplication(fromBase64(group.rfcGroupId), plaintext);\n const body: SendMessageBody = {\n ciphertext_b64: toBase64(ct),\n client_idem_key: randomId(),\n };\n const wire = await palbeRequest<WireReceipt>(\n this.rt,\n 'POST',\n MessagingPaths.groupMessages(group.displayId),\n { body },\n );\n\n // Persist the OUTGOING reaction copy (same rationale as sendText: the sender\n // can't decrypt its own ciphertext). text is null — a reaction is never a\n // bubble; the envelopeType + reaction fields drive the re-fold on reload.\n const stored: StoredMessage = {\n id: `${group.rfcGroupId}#${wire.server_seq}`,\n direction: 'outgoing',\n text: null,\n senderDeviceId: this.selfDeviceId,\n epoch: wire.epoch,\n serverSeq: wire.server_seq,\n at: Date.now(),\n clientMsgId: args.clientMsgId,\n replyTo: null,\n envelopeType: 'reaction',\n reaction: {\n targetClientMsgId: args.targetClientMsgId,\n emoji: args.emoji,\n op: args.op,\n },\n };\n try {\n await this.messageStore.append(group.rfcGroupId, stored);\n } catch {\n // best-effort, identical to sendText\n }\n return {\n receipt: { serverSeq: wire.server_seq, epoch: wire.epoch },\n clientMsgId: args.clientMsgId,\n };\n }\n\n /** Send an edit (edit-by-supersession on a target message). Encrypts a\n * `type:'edit'` envelope at the current epoch and sends through the SAME MLS\n * application path as `sendText` (the server stays blind — an edit is just\n * another application message). Persists the outgoing edit row so it re-folds\n * onto its target's text after a reload (the own-send half of the reload\n * parity). NEVER rebases (epoch-bound like any application message). */\n async sendEdit(\n group: MessagingGroup,\n args: {\n clientMsgId: string;\n targetClientMsgId: string;\n newText: string;\n bodyRanges?: MentionRange[] | null;\n },\n ): Promise<{ receipt: SentReceipt; clientMsgId: string }> {\n const plaintext = encodeEdit({\n clientMsgId: args.clientMsgId,\n targetClientMsgId: args.targetClientMsgId,\n newText: args.newText,\n bodyRanges: args.bodyRanges,\n });\n const ct = await this.engine.encryptApplication(fromBase64(group.rfcGroupId), plaintext);\n const body: SendMessageBody = {\n ciphertext_b64: toBase64(ct),\n client_idem_key: randomId(),\n };\n const wire = await palbeRequest<WireReceipt>(\n this.rt,\n 'POST',\n MessagingPaths.groupMessages(group.displayId),\n { body },\n );\n\n // Persist the OUTGOING edit copy (same rationale as sendText: the sender can't\n // decrypt its own ciphertext). text is null — an edit is never a bubble; the\n // envelopeType + edit fields drive the re-fold onto the target on reload.\n const stored: StoredMessage = {\n id: `${group.rfcGroupId}#${wire.server_seq}`,\n direction: 'outgoing',\n text: null,\n senderDeviceId: this.selfDeviceId,\n epoch: wire.epoch,\n serverSeq: wire.server_seq,\n at: Date.now(),\n clientMsgId: args.clientMsgId,\n replyTo: null,\n envelopeType: 'edit',\n edit: {\n targetClientMsgId: args.targetClientMsgId,\n newText: args.newText,\n // Persist the edit's REPLACEMENT ranges so the edited message's mentions\n // re-resolve from this edit after a reload (own-send reload parity — T6).\n ...(args.bodyRanges && args.bodyRanges.length > 0 ? { bodyRanges: args.bodyRanges } : {}),\n },\n };\n try {\n await this.messageStore.append(group.rfcGroupId, stored);\n } catch {\n // best-effort, identical to sendText/sendReaction\n }\n return {\n receipt: { serverSeq: wire.server_seq, epoch: wire.epoch },\n clientMsgId: args.clientMsgId,\n };\n }\n\n /** Send a delete-for-everyone tombstone on a target message. Encrypts a\n * `type:'delete'` envelope at the current epoch and sends through the SAME MLS\n * application path as `sendText` (the server stays blind — a delete is just\n * another opaque application message; the original ciphertext row is NOT\n * removed). Persists the outgoing delete row so the tombstone re-folds onto its\n * target after a reload (the own-send half of the reload parity — the iOS-review\n * CRITICAL boundary; the projection's `.delete` branch re-folds it). NEVER\n * rebases (epoch-bound like any application message). */\n async sendDelete(\n group: MessagingGroup,\n args: { clientMsgId: string; targetClientMsgId: string },\n ): Promise<{ receipt: SentReceipt; clientMsgId: string }> {\n const plaintext = encodeDelete({\n clientMsgId: args.clientMsgId,\n targetClientMsgId: args.targetClientMsgId,\n });\n const ct = await this.engine.encryptApplication(fromBase64(group.rfcGroupId), plaintext);\n const body: SendMessageBody = {\n ciphertext_b64: toBase64(ct),\n client_idem_key: randomId(),\n };\n const wire = await palbeRequest<WireReceipt>(\n this.rt,\n 'POST',\n MessagingPaths.groupMessages(group.displayId),\n { body },\n );\n\n // Persist the OUTGOING delete copy (same rationale as sendText: the sender can't\n // decrypt its own ciphertext). text is null — a delete is never a bubble; the\n // envelopeType + delete fields drive the re-fold onto the target on reload.\n const stored: StoredMessage = {\n id: `${group.rfcGroupId}#${wire.server_seq}`,\n direction: 'outgoing',\n text: null,\n senderDeviceId: this.selfDeviceId,\n epoch: wire.epoch,\n serverSeq: wire.server_seq,\n at: Date.now(),\n clientMsgId: args.clientMsgId,\n replyTo: null,\n envelopeType: 'delete',\n delete: {\n targetClientMsgId: args.targetClientMsgId,\n scope: 'everyone',\n },\n };\n try {\n await this.messageStore.append(group.rfcGroupId, stored);\n } catch {\n // best-effort, identical to sendText/sendReaction/sendEdit\n }\n return {\n receipt: { serverSeq: wire.server_seq, epoch: wire.epoch },\n clientMsgId: args.clientMsgId,\n };\n }\n\n // ── The rebase loop ──\n\n private async commitWithRebase(\n rfcGroupId: string,\n build: () => Promise<{ method: string; path: string; body: unknown }>,\n ): Promise<void> {\n const gidBytes = fromBase64(rfcGroupId);\n let lastEpoch: number | null = null;\n for (let attempt = 1; attempt <= this.rebase.maxAttempts; attempt++) {\n const pending = await build();\n try {\n await palbeRequest<CommitAccepted>(this.rt, pending.method, pending.path, {\n body: pending.body,\n });\n await this.engine.applyPendingCommit(gidBytes);\n return;\n } catch (e) {\n if (isBackendError(e) && e.code === 'epoch_conflict') {\n await this.engine.clearPendingCommit(gidBytes);\n const ce = (e.data as { current_epoch?: number } | undefined)?.current_epoch;\n if (typeof ce === 'number') lastEpoch = ce;\n if (attempt === this.rebase.maxAttempts) break;\n await this.drain(rfcGroupId);\n await this.backoff(attempt);\n continue; // rebuild + re-POST\n }\n throw e;\n }\n }\n await this.engine.clearPendingCommit(gidBytes);\n throw new RebaseExhausted(this.rebase.maxAttempts, lastEpoch);\n }\n\n /** Pull the device queue, process the winning commit(s) so the engine advances\n * to the new epoch, ack the commits (the delivery source owns app messages). */\n private async drain(rfcGroupId: string): Promise<void> {\n const page = await palbeRequest<QueuePage>(\n this.rt,\n 'GET',\n MessagingPaths.deviceQueue(this.selfDeviceId),\n );\n const gidBytes = fromBase64(rfcGroupId);\n const ackIds: string[] = [];\n for (const row of page.messages) {\n if (row.content_type !== ContentType.commit) continue;\n try {\n await this.engine.processIncoming(gidBytes, fromBase64(row.ciphertext_b64));\n } catch {\n // a non-applicable commit (own / consumed) — still ack so it isn't redelivered\n }\n ackIds.push(row.id);\n }\n if (ackIds.length > 0) {\n await palbeRequest<void>(this.rt, 'POST', MessagingPaths.deviceQueueAck(this.selfDeviceId), {\n body: { message_ids: ackIds },\n });\n }\n }\n\n private async backoff(attempt: number): Promise<void> {\n const base = this.rebase.baseDelayMs * attempt;\n const jitter = this.rebase.jitterMs === 0 ? 0 : Math.random() * this.rebase.jitterMs;\n const total = base + jitter;\n if (total <= 0) return;\n await new Promise((r) => setTimeout(r, total));\n }\n}\n","import type { MentionRange } from './group-messaging.js';\n\n// Mentions (design 2026-06-20) — the PURE, deterministic decode-time normalization of\n// `body_ranges`. BOTH SDKs (iOS `Palbe` + web `@palbase/web`) apply this exact\n// algorithm bit-identically so a malformed or adversarial `body_ranges` produces the\n// SAME surviving spans on both runtimes. A cross-binding golden (T7) locks this — if\n// one SDK clamped where the other dropped, both could pass their own tests yet diverge\n// on the wire. This is a single PINNED contract, not implementer's choice.\n//\n// OFFSET UNIT = UTF-16 code units (JS string .length IS UTF-16 code units — native).\n// NEVER use [...text] (scalar/codepoint iteration) — mixing units corrupts spans over\n// emoji/surrogate pairs. Use text.length / text.charCodeAt / text.slice only.\n// The `…Utf16` suffix names the unit so an accidental scalar use is visible.\n//\n// THE EXACT ORDERED ALGORITHM (offsets are UTF-16 code units; N = text.length):\n// 1. DROP-invalid (not clamp): drop if start < 0 || length <= 0 || start + length > N.\n// 1b. DROP-surrogate-boundary: drop if `start` OR `start+length` index falls mid\n// surrogate pair (the unit BEFORE the index is a high surrogate 0xD800–0xDBFF AND\n// the unit AT the index is a low surrogate 0xDC00–0xDFFF). Splitting a pair is\n// invalid. Index 0 and index N never trigger.\n// 2. SORT: ascending by `start`; ties broken by `length` DESCENDING (wider survives an\n// equal-start collision); then by original index ascending (stable). Sort BEFORE\n// the overlap walk — mandatory; walking unsorted input changes which range survives.\n// 3. DROP-overlap (not merge) + drop-duplicate: walk left-to-right; keep a range only\n// if its `start >= previous-KEPT range's (start+length)`; otherwise DROP it (the\n// later overlapper). Exact duplicates fall out as a sub-case. NEVER merge.\n//\n// PURE: no I/O, never throws. A fully-invalid input returns []; the message text always\n// renders regardless. The self-mention-by-sender \"render-but-don't-elevate\" rule is NOT\n// applied here — normalize is purely structural; that gate lives at the elevation\n// decision (T3).\n\n/**\n * Deterministically normalize decode-time mention ranges per the pinned cross-SDK\n * contract (drop-invalid → drop-surrogate-boundary → sort[start asc, length DESC, idx\n * asc] → drop-overlap/duplicate). Offsets are UTF-16 code units into `text`. Pure.\n *\n * Mirrors iOS `normalizeMentionRangesUtf16` BIT-IDENTICALLY (T7 cross-binding golden\n * locks this). NEVER uses `[...text]` scalar spread — only `text.length` / `charCodeAt`.\n */\nexport function normalizeMentionRangesUtf16(ranges: MentionRange[], text: string): MentionRange[] {\n const n = text.length; // UTF-16 code unit count — native JS string .length\n\n // True iff splitting `text` AT `index` would cut a surrogate pair — i.e. the unit\n // just before `index` is a high surrogate and the unit at `index` is a low surrogate.\n // Boundary indices 0 and n are always safe (no unit before / no unit at).\n function splitsSurrogatePair(index: number): boolean {\n if (index <= 0 || index >= n) return false;\n const before = text.charCodeAt(index - 1);\n const at = text.charCodeAt(index);\n const beforeIsHigh = before >= 0xd800 && before <= 0xdbff;\n const atIsLow = at >= 0xdc00 && at <= 0xdfff;\n return beforeIsHigh && atIsLow;\n }\n\n // Steps 1 + 1b — DROP-invalid then DROP-surrogate-boundary. Retain the original index\n // for the stable tie-break in step 2.\n const survivors: Array<{ idx: number; range: MentionRange }> = [];\n for (let idx = 0; idx < ranges.length; idx++) {\n const r = ranges[idx];\n if (r === undefined) continue;\n // Step 1: validity (drop, do not clamp).\n if (r.start < 0 || r.length <= 0 || r.start + r.length > n) continue;\n // Step 1b: surrogate-pair boundary (drop). r.start + r.length <= n is guaranteed\n // by step 1, so both indices are in [0, n].\n if (splitsSurrogatePair(r.start) || splitsSurrogatePair(r.start + r.length)) continue;\n survivors.push({ idx, range: r });\n }\n\n // Step 2: sort ascending by start; ties by length DESCENDING (wider wins); then by\n // original index ascending (stable). Done BEFORE the overlap walk.\n survivors.sort((lhs, rhs) => {\n if (lhs.range.start !== rhs.range.start) return lhs.range.start - rhs.range.start;\n if (lhs.range.length !== rhs.range.length) return rhs.range.length - lhs.range.length; // DESC\n return lhs.idx - rhs.idx;\n });\n\n // Step 3: drop the later overlapper (and exact duplicates as a sub-case) — never merge.\n const kept: MentionRange[] = [];\n let prevEnd = Number.NEGATIVE_INFINITY; // exclusive end of the last KEPT range\n for (const s of survivors) {\n if (s.range.start >= prevEnd) {\n kept.push(s.range);\n prevEnd = s.range.start + s.range.length;\n }\n // else: overlaps (or duplicates) the previous kept range → DROP.\n }\n return kept;\n}\n","/**\n * ReactionFold — pure fold of reaction events into a per-target emoji tally.\n *\n * Algorithm mirrors iOS `ReactionFold` (ReactionFold.swift) byte-for-byte in\n * behaviour: LWW per (target, actorUserId) on (epoch, serverSeq), idempotent\n * on eventClientMsgId.\n *\n * Tie assumption (same as iOS doc-comment):\n * The server guarantees (epoch, serverSeq) is unique per logical event, so\n * exact-order ties cannot occur in practice. The `<=` stale-check (first-\n * arriving wins on an exact tie) is therefore safe. Re-delivery of the SAME\n * logical event is already handled by the seenEvents dedup and never reaches\n * this check.\n */\n\nexport interface ReactionEvent {\n targetClientMsgId: string;\n actorUserId: string;\n emoji: string;\n op: 'add' | 'remove';\n epoch: number;\n serverSeq: number;\n /** Dedup key — unique per logical event (e.g. the reaction message's clientMsgId). */\n eventClientMsgId: string;\n}\n\n/** Lexicographic (epoch, serverSeq) comparison. Returns true if a <= b. */\nfunction orderLte(aEpoch: number, aSeq: number, bEpoch: number, bSeq: number): boolean {\n if (aEpoch !== bEpoch) return aEpoch < bEpoch;\n return aSeq <= bSeq;\n}\n\ninterface UserState {\n orderEpoch: number;\n orderSeq: number;\n emoji: string | null; // null = removed\n}\n\nexport class ReactionFold {\n // Map<targetClientMsgId, Map<actorUserId, UserState>>\n private readonly states = new Map<string, Map<string, UserState>>();\n // Dedup set: eventClientMsgId values already applied.\n private readonly seenEvents = new Set<string>();\n\n ingest(e: ReactionEvent): void {\n // Idempotency: skip if this exact event was already applied.\n if (this.seenEvents.has(e.eventClientMsgId)) return;\n this.seenEvents.add(e.eventClientMsgId);\n\n let byUser = this.states.get(e.targetClientMsgId);\n if (byUser === undefined) {\n byUser = new Map<string, UserState>();\n this.states.set(e.targetClientMsgId, byUser);\n }\n\n const prev = byUser.get(e.actorUserId);\n if (prev !== undefined) {\n // Stale / out-of-order: ignore if new order <= stored order.\n if (orderLte(e.epoch, e.serverSeq, prev.orderEpoch, prev.orderSeq)) return;\n }\n\n byUser.set(e.actorUserId, {\n orderEpoch: e.epoch,\n orderSeq: e.serverSeq,\n emoji: e.op === 'add' ? e.emoji : null,\n });\n }\n\n /**\n * Returns a tally of `{ emoji → sorted userIds[] }` for the given target\n * message. Users whose last event was a `remove` (emoji === null) are\n * excluded. userId arrays are sorted for deterministic output (render\n * stability + golden tests).\n */\n tally(targetClientMsgId: string): Record<string, string[]> {\n const byUser = this.states.get(targetClientMsgId);\n if (byUser === undefined) return {};\n\n const out = new Map<string, string[]>();\n for (const [userId, st] of byUser) {\n if (st.emoji === null) continue;\n let users = out.get(st.emoji);\n if (users === undefined) {\n users = [];\n out.set(st.emoji, users);\n }\n users.push(userId);\n }\n\n // Sort userId arrays for determinism (render stability + golden tests).\n const result: Record<string, string[]> = {};\n for (const [emoji, users] of out) {\n result[emoji] = users.sort();\n }\n return result;\n }\n}\n","/**\n * TimerFold — pure per-chat fold of `timer_set` events into ONE active default\n * cell. LWW on (epoch, serverSeq); idempotent on eventClientMsgId. Mirrors iOS\n * `TimerFold` byte-for-byte in behaviour (and reactions' (epoch,seq) machinery),\n * keyed at the chat level rather than per-target. `actorUserId` is the\n * AUTHENTICATED sender resolved upstream — never a wire field.\n */\nexport interface TimerEvent {\n ttlSeconds: number | null;\n start: 'send' | 'read';\n actorUserId: string;\n epoch: number;\n serverSeq: number;\n eventClientMsgId: string;\n}\n\n/** Lexicographic (epoch, serverSeq) comparison. Returns true if a <= b. */\nfunction orderLte(aEpoch: number, aSeq: number, bEpoch: number, bSeq: number): boolean {\n if (aEpoch !== bEpoch) return aEpoch < bEpoch;\n return aSeq <= bSeq;\n}\n\ninterface Cell {\n orderEpoch: number;\n orderSeq: number;\n ttlSeconds: number | null;\n start: 'send' | 'read';\n actor: string;\n}\n\nexport class TimerFold {\n private cell: Cell | null = null;\n private readonly seenEvents = new Set<string>();\n\n ingest(e: TimerEvent): void {\n // Idempotency: skip if this exact event was already applied.\n if (this.seenEvents.has(e.eventClientMsgId)) return;\n this.seenEvents.add(e.eventClientMsgId);\n\n // Stale / out-of-order: ignore if new order <= stored order. The server\n // guarantees (epoch, serverSeq) is unique per logical event, so exact-order\n // ties cannot occur in practice; first-arriving wins on an exact tie. SAME\n // logical event re-delivery is already caught by the seenEvents dedup above.\n if (\n this.cell !== null &&\n orderLte(e.epoch, e.serverSeq, this.cell.orderEpoch, this.cell.orderSeq)\n ) {\n return;\n }\n this.cell = {\n orderEpoch: e.epoch,\n orderSeq: e.serverSeq,\n ttlSeconds: e.ttlSeconds,\n start: e.start,\n actor: e.actorUserId,\n };\n }\n\n /**\n * The active chat default, or null if no timer_set has applied.\n * `ttlSeconds === null` means DISABLED (still applied — distinct from \"never\n * set\"). `start` is meaningful only when ttlSeconds !== null.\n */\n active(): { ttlSeconds: number | null; start: 'send' | 'read' } | null {\n if (this.cell === null) return null;\n return { ttlSeconds: this.cell.ttlSeconds, start: this.cell.start };\n }\n\n /** The userId behind the winning timer_set (for the optional system line). null if unset. */\n lastActor(): string | null {\n return this.cell?.actor ?? null;\n }\n}\n","// The Chat — the one object a chat screen binds to. A DM or a group, uniformly.\n// Web idiom of the iOS `@Observable Chat`: an event-emitter exposing `onChange`\n// returning an Unsubscribe (matches flags/realtime), plus snapshot getters\n// (messages/members/typing/unreadCount/lastMessage). React hooks wrap it with\n// useSyncExternalStore.\n//\n// WhatsApp-lazy: a direct/group chat opens as a `.draft` (no network); the first\n// `send` MATERIALIZES it (claim → create/get-or-create → add → register), flips\n// to `.active`, wires live subscriptions, and hydrates durable history.\n\nimport { type AnchorTriple, MonotonicClock, remainingSeconds } from './deadline-calculator.js';\nimport { DeleteFold } from './delete-fold.js';\nimport type { IncomingMessage } from './delivery-source.js';\nimport { type AuthorResolution, EditFold } from './edit-fold.js';\nimport {\n type ExpirySpec,\n type MentionRange,\n type MessagingGroup,\n type ReplyRef,\n resolveReply,\n} from './group-messaging.js';\nimport { normalizeMentionRangesUtf16 } from './mention-ranges.js';\nimport { ReactionFold } from './reaction-fold.js';\nimport { TimerFold } from './timer-fold.js';\nimport type {\n ChatKind,\n ChatMember,\n ChatMessage,\n ChatMessageKind,\n ChatState,\n MessageDirection,\n NotifyScope,\n PresenceState,\n ResolvedMention,\n ResolvedReply,\n SentReceipt,\n UnreadView,\n Unsubscribe,\n} from './types.js';\nimport { mintClientMsgId, truncateQuote } from './util.js';\n\n/**\n * The neutral copy shown in place of a deleted-for-everyone message. Cooperative\n * erasure — it does NOT imply cryptographic erasure (the design's honest caveat).\n * The single source of truth for the descriptor, used by BOTH the live Chat render\n * precedence and the coordinator history projection so they render identically.\n */\nexport const DELETED_DESCRIPTOR = '🚫 This message was deleted';\n\n/** The intended participants of a draft chat (held until the first send). */\nexport interface ChatDraft {\n mode: { kind: 'direct'; peerUserId: string } | { kind: 'group'; members: string[] };\n /** The reserved local id a draft shows before it materializes. */\n reservedId: string;\n}\n\n/** The seam the Chat talks to for the work needing the resolved machinery. */\nexport interface ChatBackend {\n selfUserId: string;\n /** Materialize a draft → the active group (DM get-or-create / group create+add). */\n materialize(draft: ChatDraft): Promise<MessagingGroup>;\n sendText(\n group: MessagingGroup,\n text: string,\n replyTo?: ReplyRef | null,\n bodyRanges?: MentionRange[] | null,\n expiry?: ExpirySpec | null,\n ): Promise<{ receipt: SentReceipt; clientMsgId: string }>;\n /** Send a reaction (add/remove an emoji on a target message) through the same\n * MLS application path as `sendText`. Returns the receipt + the wire clientMsgId. */\n sendReaction(\n group: MessagingGroup,\n args: { clientMsgId: string; targetClientMsgId: string; emoji: string; op: 'add' | 'remove' },\n ): Promise<{ receipt: SentReceipt; clientMsgId: string }>;\n /** Send an edit (edit-by-supersession on a target message) through the same MLS\n * application path as `sendText`. Returns the receipt + the wire clientMsgId. */\n sendEdit(\n group: MessagingGroup,\n args: {\n clientMsgId: string;\n targetClientMsgId: string;\n newText: string;\n bodyRanges?: MentionRange[] | null;\n },\n ): Promise<{ receipt: SentReceipt; clientMsgId: string }>;\n /** Send a delete-for-everyone tombstone on a target message through the same MLS\n * application path as `sendText` (the server stays blind — a delete is just\n * another opaque application message). Returns the receipt + the wire clientMsgId. */\n sendDelete(\n group: MessagingGroup,\n args: { clientMsgId: string; targetClientMsgId: string },\n ): Promise<{ receipt: SentReceipt; clientMsgId: string }>;\n history(group: MessagingGroup, limit: number, before?: number): Promise<ChatMessage[]>;\n members(group: MessagingGroup): Promise<ChatMember[]>;\n addMember(group: MessagingGroup, userId: string): Promise<void>;\n removeMember(group: MessagingGroup, userId: string): Promise<void>;\n leave(group: MessagingGroup): Promise<void>;\n registerActive(group: MessagingGroup): void;\n setTyping(group: MessagingGroup, isTyping: boolean): void;\n markRead(group: MessagingGroup, upToServerSeq: number): Promise<void>;\n /** Server-metadata cluster: set/get the per-(user,group) notify scope (mute) and\n * fetch the caller's own unread view. The server stays MLS-blind (opaque metadata). */\n setNotifyScope(group: MessagingGroup, scope: NotifyScope): Promise<NotifyScope>;\n getNotifyScope(group: MessagingGroup): Promise<NotifyScope>;\n unread(group: MessagingGroup): Promise<UnreadView>;\n /** Subscribe live message + conv (presence/typing/read/delivered) for an active group. */\n subscribeLive(group: MessagingGroup, chat: Chat): Unsubscribe;\n /** Resolve a sender device id → its owning user id (for senderUserId). */\n userIdForDevice(group: MessagingGroup, deviceId: string): Promise<string | null>;\n /** Load this chat's persisted delete-for-me suppression keys (durable, no wire). */\n loadSuppressed(group: MessagingGroup): Promise<string[]>;\n /** Persist this chat's delete-for-me suppression keys (durable, no wire). */\n saveSuppressed(group: MessagingGroup, keys: string[]): Promise<void>;\n /** Load this chat's persisted self-elevation dedup keys (durable, no wire) so a\n * re-delivered mention does not re-fire `onMentionElevation` after a cold launch. */\n loadElevated(group: MessagingGroup): Promise<string[]>;\n /** Persist this chat's self-elevation dedup keys (durable, no wire). */\n saveElevated(group: MessagingGroup, keys: string[]): Promise<void>;\n\n // ── Disappearing / TTL (T10) ──\n\n /** Send a per-chat default disappearing-timer control envelope (`timer_set`). Goes\n * through the SAME MLS application path as `sendText` — server-blind, never a bubble.\n * `ttlSeconds === null` disables the default. Returns the receipt + the wire clientMsgId. */\n sendTimerSet(\n group: MessagingGroup,\n args: { clientMsgId: string; ttlSeconds: number | null; start: 'send' | 'read' },\n ): Promise<{ receipt: SentReceipt; clientMsgId: string }>;\n /** The persisted INTEGER `server_seq` tombstone set for a chat — drives transcript\n * exclusion (cold-launch) + redelivery drop (live arrival). Durable, no wire. */\n tombstonedSeqs(group: MessagingGroup): Promise<Set<number>>;\n /** The persisted STRING `client_msg_id` purge set — drives the orphan-fold resolver\n * (`authorOfTarget → 'purged'`) so a late edit DROPs / a late delete no-ops. */\n purgedClientMsgIds(group: MessagingGroup): Promise<Set<string>>;\n /** The persisted write-once anchor triple for a `clientMsgId`, or null if none. */\n anchor(group: MessagingGroup, clientMsgId: string): Promise<AnchorTriple | null>;\n /** Write-once: capture the first-arrival/first-read anchor triple for a `clientMsgId`\n * (a second call for the same id is a no-op, so the deadline never resets). */\n writeAnchorOnce(group: MessagingGroup, clientMsgId: string, a: AnchorTriple): Promise<void>;\n /** Tombstone-first purge commit point: persist the int `server_seq` + string\n * `client_msg_id` purge id in ONE durable record (idempotent). */\n tombstone(group: MessagingGroup, serverSeq: number, clientMsgId: string): Promise<void>;\n}\n\nexport class Chat {\n /** Stable, URL/log-safe id (the grp_ display id once active; a reserved local id while draft). */\n id: string;\n readonly kind: ChatKind;\n private _state: ChatState;\n\n private _group: MessagingGroup | null;\n private _draft: ChatDraft | null;\n private readonly backend: ChatBackend;\n\n private messageList: ChatMessage[] = [];\n private memberCache: ChatMember[] = [];\n private typingList: ChatMember[] = [];\n private presenceStates = new Map<string, PresenceState>();\n private readWatermark = -1;\n private titleOverride: string | null = null;\n\n private readonly seenKeys = new Set<string>();\n /** Index: clientMsgId → { text, senderUserId } for resolveReply lookups. */\n private readonly byClientMsgId = new Map<string, { text: string; senderUserId: string }>();\n /** The single authoritative reaction fold for this chat (live + own-send + history). */\n private readonly reactionFold = new ReactionFold();\n /** The single authoritative edit fold for this chat (live + own-send + history). */\n private readonly editFold = new EditFold();\n /** The single authoritative delete-for-everyone fold (live + own-send + history).\n * A tombstone scrubs its target in place (delete DOMINATES edit at render). */\n private readonly deleteFold = new DeleteFold();\n /** The per-chat default disappearing-timer fold — the latest valid `timer_set` (LWW\n * on (epoch, serverSeq), author = the resolved MLS sender). A `timer_set` is NEVER a\n * bubble; it routes here. The active default governs a subsequent bubble that carries\n * no per-message expiry (disappearing T10). */\n private readonly timerFold = new TimerFold();\n /** Advisory in-memory purge timers, keyed by serverSeq. The DURABLE truth is the\n * persisted anchor + a re-check on every load; this just drives live eviction while\n * the tab is open. Cancelled when the message purges (disappearing T10). */\n private readonly purgeTimers = new Map<number, ReturnType<typeof setTimeout>>();\n /** In-memory mirror of the durable `purgedClientMsgIds` set (the STRING namespace),\n * hydrated from `backend.purgedClientMsgIds` and grown by each live purge. Consulted\n * by `authorOfTarget` so a late edit/delete targeting a TTL-purged message resolves to\n * `'purged'` (DROP / no-op — never resurrects). Namespace-separate from the int seq\n * tombstone (disappearing T10). */\n private readonly purgedCids = new Set<string>();\n private purgedLoaded = false;\n /** delete-for-me suppression keys (clientMsgId, or `seq:<serverSeq>` for legacy)\n * — the message is OMITTED from this view. Local + persisted per chat, NO wire. */\n private readonly suppressed = new Set<string>();\n /** True once the persisted suppression set has been loaded (so the omit applies\n * even on the cold-launch hydrate path before a fresh deleteForMe). */\n private suppressedLoaded = false;\n /** Self-elevation dedup keys (`<selfUserId>|<clientMsgId or seq:n>`). Once a\n * mention of me from another sender fires `onMentionElevation`, its key lands here\n * + is persisted, so a re-delivery / cold-launch re-hydrate never re-fires. */\n private readonly elevated = new Set<string>();\n /** True once the persisted elevation set has been loaded (so a re-delivered mention\n * on the cold-launch hydrate path dedups against the persisted decision). */\n private elevatedLoaded = false;\n /** Per-target BASE (original) text, so the rendered text is `edit ?? original`.\n * Seeded once per target (write-once base) so a 2nd own edit can't corrupt it. */\n private readonly originalTextByClientMsgId = new Map<string, string | null>();\n /** Per-target AUTHOR userId — the EditFold author-gate input (filled at bubble\n * projection time from senderUserId; '' = resolved-but-unknown peer). */\n private readonly authorByClientMsgId = new Map<string, string>();\n private loadedEarliestSeq: number | null = null;\n private historyLoaded = false;\n private wired = false;\n private liveUnsub: Unsubscribe | null = null;\n\n private readonly listeners = new Set<() => void>();\n\n /**\n * Fires ONCE per `(selfUserId, clientMsgId)` when an INCOMING message mentions THIS\n * user from ANOTHER sender (not an edit). The dedup survives re-delivery + reload\n * via the persisted elevation set, so this never double-fires for one mention. The\n * app wires it to a buzz/badge (e.g. an in-app banner). Best-effort cooperative —\n * the SDK guarantees the DECISION, not the buzz. Mirrors iOS `Chat.onMentionElevation`.\n */\n onMentionElevation?: (message: ChatMessage) => void;\n\n /** @internal — obtain via pb.messaging.directChat / groupChat / chat(id). */\n constructor(\n args:\n | { group: MessagingGroup; kind: ChatKind; backend: ChatBackend }\n | { draft: ChatDraft; kind: ChatKind; backend: ChatBackend },\n ) {\n this.backend = args.backend;\n if ('group' in args) {\n this.id = args.group.displayId;\n this.kind = args.kind;\n this._state = 'active';\n this._group = args.group;\n this._draft = null;\n this.seedMembersFromGroup(args.group);\n } else {\n this.id = args.draft.reservedId;\n this.kind = args.kind;\n this._state = 'draft';\n this._group = null;\n this._draft = args.draft;\n this.seedDraftMembers(args.draft);\n }\n }\n\n // ── Observability ──\n\n /** Subscribe to any change in this chat (messages/members/typing/state). */\n onChange(cb: () => void): Unsubscribe {\n this.listeners.add(cb);\n // Lazily wire live + hydrate history on first observe of an active chat.\n this.ensureWired();\n return () => this.listeners.delete(cb);\n }\n\n private emit(): void {\n for (const cb of this.listeners) cb();\n }\n\n // ── Snapshot getters ──\n\n get state(): ChatState {\n return this._state;\n }\n get isDirect(): boolean {\n return this.kind === 'direct';\n }\n get messages(): readonly ChatMessage[] {\n return this.surfaced();\n }\n get members(): readonly ChatMember[] {\n return this.memberCache;\n }\n get typing(): readonly ChatMember[] {\n return this.typingList;\n }\n get lastMessage(): ChatMessage | null {\n return this.surfaced().at(-1) ?? null;\n }\n get unreadCount(): number {\n // A tombstoned (deleted-for-everyone) message carries no real content, so it\n // must not inflate the unread badge (mirrors the iOS T3 fix).\n return this.surfaced().filter(\n (m) => m.direction === 'incoming' && !m.isDeleted && m.serverSeq > this.readWatermark,\n ).length;\n }\n\n /**\n * The RENDER PRECEDENCE — the single composition point (live AND history project\n * through it identically). Over the raw `messageList` (which already carries the\n * folded edit text + reactions + reply):\n * (1) in the delete-for-me suppression set → OMIT the message entirely;\n * (2) else tombstoned (delete-for-everyone) → the neutral \"deleted\" descriptor\n * with reactions/reply/edit HIDDEN (delete DOMINATES edit — short-circuit);\n * (3) else the row as-is (edit overlay + reactions + reply already applied).\n * Pure over (messageList, deleteFold, suppressed) — recomputed on every read so a\n * just-folded delete / just-suppressed key takes effect without rewriting rows.\n */\n private surfaced(): ChatMessage[] {\n const out: ChatMessage[] = [];\n for (const m of this.messageList) {\n const key = this.suppressionKey(m);\n if (this.suppressed.has(key)) continue; // (1) for-me → omit\n // Tombstoned by THIS chat's live fold OR already scrubbed by the coordinator\n // history projection (the cold-launch path — its own fold pre-applied the\n // scrub; the Chat's deleteFold is empty until a live re-delivery).\n const tombstoned =\n (m.clientMsgId && this.deleteFold.isTombstoned(m.clientMsgId)) || m.isDeleted;\n if (tombstoned) {\n // (2) tombstone DOMINATES — scrub to the descriptor, hide everything else\n // (reactions, reply, edit AND mentions — delete dominates mentions too).\n out.push({\n ...m,\n text: DELETED_DESCRIPTOR,\n reactions: {},\n replyTo: null,\n edited: false,\n isDeleted: true,\n mentions: [],\n // delete DOMINATES disappearing too — a tombstoned message carries no deadline.\n expiresAt: null,\n });\n continue;\n }\n out.push(m); // (3) raw row (edit/reactions/reply already overlaid)\n }\n return out;\n }\n\n /** The delete-for-me suppression key: clientMsgId when present, else `seq:<n>`. */\n private suppressionKey(m: ChatMessage): string {\n return m.clientMsgId ? m.clientMsgId : `seq:${m.serverSeq}`;\n }\n get title(): string {\n if (this.titleOverride) return this.titleOverride;\n if (this._group?.name) return this._group.name;\n if (this._draft) {\n const mode = this._draft.mode;\n if (mode.kind === 'direct') {\n const peer = this.memberCache.find((m) => m.userId === mode.peerUserId);\n return peer?.displayName ?? 'Chat';\n }\n return 'Group';\n }\n // Active group, no name (web has no exporter yet) — friendly fallback.\n const tail = this.id.startsWith('grp_') ? this.id.slice(4) : this.id;\n const short = tail.slice(-4);\n return short ? `Group ${short}` : 'Group';\n }\n\n presence(userId: string): PresenceState | null {\n return this.presenceStates.get(userId) ?? null;\n }\n\n // ── Wiring (live + history) ──\n\n private ensureWired(): void {\n if (this.wired || this._state !== 'active' || !this._group) return;\n this.wired = true;\n this.liveUnsub = this.backend.subscribeLive(this._group, this);\n // Load the persisted delete-for-me suppression set FIRST so a for-me'd message\n // stays omitted from the moment history hydrates (the cold-launch parity). The\n // hydrate is async too; the load races it but a late load still re-emits so the\n // omit applies (surfaced() reads the set live on every render).\n void this.loadSuppressed();\n // Load the persisted self-elevation dedup set FIRST too, so a re-delivered mention\n // already-buzzed in a previous session does not re-fire on the cold-launch hydrate.\n void this.loadElevated();\n // Load the durable purge set (STRING namespace) BEFORE hydrating history, so the live\n // fold's `authorOfTarget` resolves a TTL-purged target to 'purged' from the first merge\n // (a late edit/delete against a disappeared message DROPs/no-ops). Chained so the order\n // holds; the cold-launch transcript exclusion itself comes from projectHistory.\n void this.loadPurged().then(() => this.hydrateHistory());\n void this.refreshMembers();\n }\n\n /** Hydrate the durable `purgedClientMsgIds` set (once) into the in-memory mirror so the\n * live Edit/Delete fold author-gate sees TTL-purged targets as 'purged' on cold launch\n * (disappearing T10). No re-emit: it only gates the orphan-fold resolution. */\n private async loadPurged(): Promise<void> {\n if (this.purgedLoaded || !this._group) return;\n this.purgedLoaded = true;\n try {\n const ids = await this.backend.purgedClientMsgIds(this._group);\n for (const id of ids) this.purgedCids.add(id);\n } catch {\n // best-effort — a load failure just means the mirror starts empty this session\n }\n }\n\n /** Hydrate the persisted delete-for-me suppression keys (once), then re-emit so\n * any already-surfaced suppressed message is omitted (cold-launch parity). */\n private async loadSuppressed(): Promise<void> {\n if (this.suppressedLoaded || !this._group) return;\n this.suppressedLoaded = true;\n try {\n const keys = await this.backend.loadSuppressed(this._group);\n let changed = false;\n for (const k of keys) {\n if (!this.suppressed.has(k)) {\n this.suppressed.add(k);\n changed = true;\n }\n }\n if (changed) this.emit();\n } catch {\n // best-effort — a load failure just means the set starts empty this session\n }\n }\n\n /** Hydrate the persisted self-elevation dedup keys (once). No re-emit: the set only\n * gates the elevation DECISION, it does not change what renders. */\n private async loadElevated(): Promise<void> {\n if (this.elevatedLoaded || !this._group) return;\n this.elevatedLoaded = true;\n try {\n const keys = await this.backend.loadElevated(this._group);\n for (const k of keys) this.elevated.add(k);\n } catch {\n // best-effort — a load failure just means the set starts empty this session\n }\n }\n\n private async hydrateHistory(): Promise<void> {\n if (this.historyLoaded || !this._group) return;\n this.historyLoaded = true;\n const entries = await this.backend.history(this._group, 50);\n this.mergeHistory(entries);\n }\n\n private mergeHistory(incoming: ChatMessage[]): void {\n let changed = false;\n // Pass 1: populate byClientMsgId + the edit base/author maps from all new\n // history rows (so replies + dangling edits within the same page and against\n // already-indexed live messages resolve correctly).\n for (const m of incoming) {\n if (m.serverSeq <= 0) continue;\n // A coordinator-scrubbed delete row carries the descriptor as `text`; never\n // index it as reply text nor seed it as an edit base (it has no real body).\n if (m.clientMsgId && m.text !== null && !m.isDeleted) {\n // Index ANY row with a non-empty clientMsgId + non-null text, regardless\n // of direction. For incoming rows senderUserId is null — use '' so the\n // lookup entry exists and resolveReply returns state:'resolved' (real text\n // wins over attacker-controlled preview). Spoof defense covers peer messages.\n this.byClientMsgId.set(m.clientMsgId, {\n text: m.text,\n senderUserId: m.senderUserId ?? '',\n });\n }\n // Seed the edit base/author. The coordinator's page-local fold already\n // overlaid `text`/`edited` onto this row, but the row's text IS the latest\n // edit (or original) — for the Chat's authoritative fold we want the ORIGINAL\n // base. When the fold knows nothing for this target the upstream text IS the\n // original; when it does know (cross-page/live), recomputeEdit uses fold.text.\n if (m.clientMsgId && !m.isDeleted) {\n this.seedEditBase(m.clientMsgId, m.text, m.senderUserId ?? '');\n }\n }\n this.editFold.reevaluateHeld(this.authorOfTarget);\n // Pass 2: merge into messageList (replyTo is already resolved by coordinator).\n // The coordinator already page-local-folded reactions + edits and attached each\n // surfaced row's tally/text; applyReactionTally + applyEditOverlay let the\n // Chat's authoritative folds (live/own-send/cross-page) override when they hold.\n for (const m of incoming) {\n if (m.serverSeq <= 0) continue;\n const key = this.internalKey(m.serverSeq);\n if (this.seenKeys.has(key)) continue;\n this.seenKeys.add(key);\n // A live tombstone parked in the Chat's fold re-checks its deferred gate when\n // an older target pages in (loadEarlier). A coordinator-scrubbed row already\n // carries isDeleted; surfaced() honors either path (delete DOMINATES).\n if (m.clientMsgId && !m.isDeleted) {\n this.deleteFold.reevaluatePending(m.clientMsgId, this.authorOfTarget(m.clientMsgId));\n }\n // Mentions T6: re-resolve roster names on the projection's null-name spans\n // (resolution is LIVE), then overlay the live folds (an edit replaces mentions).\n this.messageList.push(\n this.applyEditOverlay(this.applyReactionTally(this.resolveMentionNames(m))),\n );\n changed = true;\n this.loadedEarliestSeq = Math.min(this.loadedEarliestSeq ?? m.serverSeq, m.serverSeq);\n // Disappearing T10 (the cold-launch re-arm — the M1-trap-confirmed gap): re-arm the\n // purge from the projection's derived EFFECTIVE deadline (own per-message expiry ELSE\n // the chat default folded forward-only AS OF this message's order, computed in the\n // pure projectHistory). This makes a default-governed message re-arm from HISTORY on\n // a fresh tab, not only from a live timer_set (mirrors iOS mergeHistory armPurge).\n if (m.expiresAt && m.clientMsgId && !m.isDeleted) {\n void this.armFromDeadline(m.expiresAt, m.serverSeq, m.clientMsgId);\n }\n }\n if (changed) {\n this.messageList.sort((a, b) => a.serverSeq - b.serverSeq);\n this.emit();\n }\n }\n\n /** @internal — called by the backend's live subscription. */\n async ingestLive(incoming: IncomingMessage): Promise<void> {\n if (!this._group) return;\n if (incoming.kind === 'commit') {\n await this.refreshMembers();\n return;\n }\n if (incoming.serverSeq <= 0) return; // welcome/proposal — not a row\n // Disappearing T10 (redelivery-drop, the M1-trap arrival half): a message whose\n // server_seq is already TTL-tombstoned in the durable store is DROPPED on arrival and\n // NEVER re-surfaced. A purge is crash-safe because the tombstone is written FIRST; if\n // the same seq is re-delivered after a purge it must not resurface (the cold-launch\n // projectHistory enforces the SAME exclusion on reload). Checked BEFORE the seenKeys\n // dedup so a purged-then-redelivered seq is dropped, not silently consumed.\n if ((await this.backend.tombstonedSeqs(this._group)).has(incoming.serverSeq)) return;\n const key = this.internalKey(incoming.serverSeq);\n if (this.seenKeys.has(key)) return;\n this.seenKeys.add(key);\n let senderUser: string | null = null;\n if (incoming.senderDeviceId) {\n senderUser = await this.backend.userIdForDevice(this._group, incoming.senderDeviceId);\n }\n const direction: MessageDirection =\n senderUser !== null && senderUser === this.backend.selfUserId ? 'outgoing' : 'incoming';\n\n // Disappearing T10: a `timer_set` is a control envelope, NOT a bubble. Route it into\n // the chat-default `TimerFold` (actor = the AUTHENTICATED sender, resolved device→user;\n // own-send → self). An unresolvable actor DROPs it (no spoofable timer). Never appends\n // a row, never notifies, never increments unread (mirrors iOS T5).\n if (incoming.envelopeType === 'timer_set' && incoming.timer) {\n const actorUserId = direction === 'outgoing' ? this.backend.selfUserId : senderUser;\n if (actorUserId !== null) {\n this.timerFold.ingest({\n ttlSeconds: incoming.timer.ttlSeconds,\n start: incoming.timer.start,\n actorUserId,\n epoch: incoming.epoch,\n serverSeq: incoming.serverSeq,\n eventClientMsgId: incoming.clientMsgId,\n });\n }\n return; // no bubble, no unread, no push\n }\n\n // A reaction is NEVER a visible bubble — it folds into the target's tally and\n // re-emits, so it also never notifies like a message (mirrors iOS T3).\n if (incoming.envelopeType === 'reaction' && incoming.reaction) {\n // actorUserId = the resolved sender (own-send → selfUserId). An unresolvable\n // sender would yield a spoofable actor — drop it (no fold).\n const actorUserId = direction === 'outgoing' ? this.backend.selfUserId : senderUser;\n if (actorUserId !== null) {\n this.reactionFold.ingest({\n targetClientMsgId: incoming.reaction.targetClientMsgId,\n actorUserId,\n emoji: incoming.reaction.emoji,\n op: incoming.reaction.op,\n epoch: incoming.epoch,\n serverSeq: incoming.serverSeq,\n eventClientMsgId: incoming.clientMsgId,\n });\n this.recomputeReactions(incoming.reaction.targetClientMsgId);\n }\n return;\n }\n\n // An edit is NEVER a visible bubble — it folds the latest author edit's text\n // onto the target in place and re-emits, so it also never notifies like a\n // message (mirrors iOS T3). editorUserId = the resolved sender (own-send →\n // selfUserId); null → the fold HOLDs until the roster resolves.\n if (incoming.envelopeType === 'edit' && incoming.edit) {\n const editorUserId = direction === 'outgoing' ? this.backend.selfUserId : senderUser;\n this.editFold.ingest(\n {\n targetClientMsgId: incoming.edit.targetClientMsgId,\n editorUserId,\n newText: incoming.edit.newText,\n epoch: incoming.epoch,\n serverSeq: incoming.serverSeq,\n eventClientMsgId: incoming.clientMsgId,\n // Mentions T6: carry the edit's REPLACEMENT ranges so the edited message's\n // mentions reflect them (recomputed against the new text on recomputeEdit).\n bodyRanges: incoming.bodyRanges,\n },\n this.authorOfTarget,\n );\n this.recomputeEdit(incoming.edit.targetClientMsgId);\n return;\n }\n\n // A delete-for-everyone is NEVER a visible bubble — it folds a tombstone onto\n // its target (author-gated) and re-emits, so it also never notifies like a\n // message (mirrors iOS T3). actorUserId = the resolved sender (own-send →\n // selfUserId); null → the fold parks it in held until the roster resolves. The\n // authorOfTarget closure resolves the TARGET message's author for the gate.\n if (incoming.envelopeType === 'delete' && incoming.delete) {\n const actorUserId = direction === 'outgoing' ? this.backend.selfUserId : senderUser;\n this.deleteFold.ingest(\n {\n targetClientMsgId: incoming.delete.targetClientMsgId,\n actorUserId,\n epoch: incoming.epoch,\n serverSeq: incoming.serverSeq,\n eventClientMsgId: incoming.clientMsgId,\n },\n this.authorOfTarget,\n );\n // A tombstone that arrived BEFORE its target lands parked in pending; the\n // target's own insert below re-checks it. Re-emit so an in-order scrub shows.\n this.emit();\n return;\n }\n\n // clientMsgId and replyRef are decoded by the delivery source (decodeEnvelope)\n // and carried on IncomingMessage. Legacy/system messages carry empty string / null.\n const incomingClientMsgId = incoming.clientMsgId;\n const incomingReplyRef = incoming.replyRef;\n\n // Resolve reply if present.\n let resolvedReplyTo: ResolvedReply | null = null;\n if (incomingReplyRef) {\n resolvedReplyTo = resolveReply(incomingReplyRef, (id) => this.byClientMsgId.get(id) ?? null);\n }\n\n // Mentions T6: resolve the decode-time mention spans (normalize + roster name)\n // for this NORMAL text bubble. A mention is carried on the message's own text (no\n // fold) — the bubble renders normally with these spans for highlighting.\n const mentions = this.resolveMentions(incoming.text, incoming.bodyRanges);\n\n const msg: ChatMessage = {\n id: this.publicId(incoming.serverSeq),\n kind: this.kindOf(incoming),\n direction,\n senderUserId: senderUser,\n text: incoming.text,\n serverSeq: incoming.serverSeq,\n sentAt: incoming.receivedAt,\n clientMsgId: incomingClientMsgId,\n replyTo: resolvedReplyTo,\n // Attach any tally already folded for this message (a reaction that arrived\n // BEFORE its target — the dangling case — renders the moment the target lands).\n reactions: incomingClientMsgId ? this.reactionFold.tally(incomingClientMsgId) : {},\n // Default false; applyEditOverlay below folds any edit that arrived first.\n edited: false,\n // Default false; surfaced() applies the tombstone scrub if a delete folded.\n isDeleted: false,\n mentions,\n // Disappearing T10: the LOCAL deadline (own per-message expiry ELSE the chat default\n // active AS OF arrival). null when this message is non-disappearing.\n expiresAt: this.deadlineFor(this.toExpirySpec(incoming.expiry) ?? this.defaultExpiry()),\n };\n\n // Self-elevation decision (best-effort cooperative, dedup-once via the persisted\n // set). Compute on the INCOMING bubble BEFORE projection — an edit never lands here\n // (it routed to the EditFold above), satisfying the not-edit gate.\n this.elevateIfMentioned(msg, mentions, senderUser, incoming.envelopeType);\n\n // Index non-empty clientMsgId for future resolveReply lookups.\n // Index ANY incoming message (senderUser may be null for peers); use '' for\n // the senderUserId so resolveReply still returns state:'resolved' (real text\n // wins over attacker-controlled preview). Spoof defense covers peer messages.\n if (incomingClientMsgId && incoming.text !== null) {\n this.byClientMsgId.set(incomingClientMsgId, {\n text: incoming.text,\n senderUserId: senderUser ?? '',\n });\n }\n\n // Record this bubble's BASE text + AUTHOR for the edit fold BEFORE\n // reevaluateHeld, so a dangling edit whose target just arrived author-gates\n // correctly. Base text is seeded once (write-once) so a folded edit overlay\n // never overwrites the original we render `edit ?? original` against.\n if (incomingClientMsgId) {\n this.seedEditBase(incomingClientMsgId, incoming.text, senderUser);\n this.editFold.reevaluateHeld(this.authorOfTarget);\n // A pending/held tombstone for this just-arrived target re-checks its\n // deferred author-gate here — a forged early tombstone is discarded (the\n // message renders), a genuine one scrubs (mirrors iOS T3 reevaluatePending).\n this.deleteFold.reevaluatePending(\n incomingClientMsgId,\n this.authorOfTarget(incomingClientMsgId),\n );\n }\n\n this.messageList.push(this.applyEditOverlay(msg));\n this.messageList.sort((a, b) => a.serverSeq - b.serverSeq);\n this.loadedEarliestSeq = Math.min(\n this.loadedEarliestSeq ?? incoming.serverSeq,\n incoming.serverSeq,\n );\n this.emit();\n // Disappearing T10: a normal bubble carrying a per-message `expiry` arms its purge on\n // first decrypt-and-display (the durable write-once anchor + the advisory timer); a\n // bubble with NO per-message expiry falls back to the chat default (`timerFold`).\n // own-expiry WINS over the default. Fire-and-forget — best-effort; never blocks ingest.\n void this.armPurge(\n this.toExpirySpec(incoming.expiry) ?? this.defaultExpiry(),\n incoming.serverSeq,\n incomingClientMsgId,\n );\n }\n\n // ── Disappearing (TTL — T10) ──\n\n /** Normalize a decoded `IncomingMessage.expiry` / `StoredMessage.expiry` into the\n * `ExpirySpec` the arm path consumes (or null when absent). */\n private toExpirySpec(\n e:\n | { v: number; ttlSeconds: number; start: 'send' | 'read'; senderSendTs: number | null }\n | null\n | undefined,\n ): ExpirySpec | null {\n return e\n ? { v: e.v, ttlSeconds: e.ttlSeconds, start: e.start, senderSendTs: e.senderSendTs }\n : null;\n }\n\n /** The chat-default expiry derived from the active `timer_set` fold, as an `ExpirySpec`\n * so a bubble with no per-message expiry inherits it. null when no default is active or\n * the default was explicitly DISABLED (`ttlSeconds === null`). `senderSendTs` is null —\n * the default has no per-message sender clock; the arrival anchor drives the deadline\n * (mirrors iOS `defaultExpiry()`). */\n private defaultExpiry(): ExpirySpec | null {\n const active = this.timerFold.active();\n if (!active || active.ttlSeconds === null) return null;\n return { v: 1, ttlSeconds: active.ttlSeconds, start: active.start, senderSendTs: null };\n }\n\n /** The surfaced display deadline for an effective expiry (a local, monotonic-derived\n * value — the wall-clock projection of the TTL from now). null for a non-disappearing\n * message. The durable purge is driven by `armPurge`'s write-once anchor; this is the\n * UI countdown baseline. */\n private deadlineFor(expiry: ExpirySpec | null): Date | null {\n if (!expiry) return null;\n return new Date(Date.now() + expiry.ttlSeconds * 1000);\n }\n\n /** Arm a message's TTL purge on first decrypt-and-display. Captures the WRITE-ONCE\n * monotonic/wall/boot anchor (so the deadline survives a reload — a re-arm after relaunch\n * reads back the ORIGINAL capture, never a fresh one → the deadline never resets),\n * computes the remaining time via `remainingSeconds`, applies the send-anchor clamp\n * `min(sender_send_ts+ttl, first_arrival+ttl)` (read-anchor uses the write-once first-read\n * capture), then either purges immediately or schedules an advisory `setTimeout`. A null\n * expiry / empty clientMsgId is a no-op. Mirrors iOS `armPurge`. */\n private async armPurge(\n expiry: ExpirySpec | null,\n serverSeq: number,\n clientMsgId: string,\n ): Promise<void> {\n if (!expiry || !clientMsgId || !this._group) return;\n const group = this._group;\n // Write-once arrival/first-read anchor (durable so the deadline survives a reload).\n const fresh: AnchorTriple = {\n mAnchorMs: MonotonicClock.nowMs(),\n wAnchorEpochMs: MonotonicClock.nowWallEpochMs(),\n bAnchorToken: MonotonicClock.bootToken(),\n };\n await this.backend.writeAnchorOnce(group, clientMsgId, fresh);\n // Use the PERSISTED anchor (the write-once one) for the deadline math.\n const effective = (await this.backend.anchor(group, clientMsgId)) ?? fresh;\n const result = remainingSeconds({\n ttlSeconds: expiry.ttlSeconds,\n anchor: effective,\n nowMonotonicMs: MonotonicClock.nowMs(),\n nowWallEpochMs: MonotonicClock.nowWallEpochMs(),\n nowBootToken: MonotonicClock.bootToken(),\n });\n // Send-anchor clamp: the sender's intended deadline is sender_send_ts + ttl; a\n // clock-skew / late arrival can only SHORTEN, never extend, the message's life.\n let purgeInSeconds: number | null;\n if (result.kind === 'purgeNow') {\n purgeInSeconds = 0;\n } else if (expiry.start === 'send' && expiry.senderSendTs !== null) {\n const sendRemaining =\n expiry.senderSendTs + expiry.ttlSeconds - MonotonicClock.nowWallEpochMs() / 1000;\n purgeInSeconds = sendRemaining <= 0 ? 0 : Math.min(result.seconds, sendRemaining);\n } else {\n purgeInSeconds = result.seconds;\n }\n // Cancel any prior timer for this seq (idempotent re-arm) before scheduling.\n const prior = this.purgeTimers.get(serverSeq);\n if (prior) clearTimeout(prior);\n this.purgeTimers.delete(serverSeq);\n if (purgeInSeconds <= 0) {\n await this.purge(serverSeq, clientMsgId);\n return;\n }\n const handle = setTimeout(() => {\n void this.purge(serverSeq, clientMsgId);\n }, purgeInSeconds * 1000);\n this.purgeTimers.set(serverSeq, handle);\n }\n\n /** Re-arm a purge from a derived deadline (cold-launch hydrate path). The deadline is\n * the projection's monotonic-derived `expiresAt`; schedule an advisory timer for the\n * remaining time (purge immediately if the deadline has already passed). The durable\n * tombstone is written by `purge` when it fires (the crash-safe commit point). */\n private async armFromDeadline(\n deadline: Date,\n serverSeq: number,\n clientMsgId: string,\n ): Promise<void> {\n if (!this._group) return;\n const remainingMs = deadline.getTime() - Date.now();\n const prior = this.purgeTimers.get(serverSeq);\n if (prior) clearTimeout(prior);\n this.purgeTimers.delete(serverSeq);\n if (remainingMs <= 0) {\n await this.purge(serverSeq, clientMsgId);\n return;\n }\n const handle = setTimeout(() => {\n void this.purge(serverSeq, clientMsgId);\n }, remainingMs);\n this.purgeTimers.set(serverSeq, handle);\n }\n\n /** Purge message M (TTL eviction). TOMBSTONE-FIRST (the crash-safe commit point):\n * persist the `server_seq` tombstone + the `client_msg_id` purge id in ONE durable\n * record, THEN drop M's body from `messageList` + `emit()`, then re-evaluate any HELD\n * edit / PARKED delete targeting the now-purged cid so an orphan annotation DROPs/no-ops\n * (the resolver now returns `'purged'`). Idempotent. Mirrors iOS `purge`. */\n private async purge(serverSeq: number, clientMsgId: string): Promise<void> {\n if (!this._group) return;\n const prior = this.purgeTimers.get(serverSeq);\n if (prior) clearTimeout(prior);\n this.purgeTimers.delete(serverSeq);\n // Tombstone FIRST (durable commit point) — records clientMsgId in purgedClientMsgIds.\n await this.backend.tombstone(this._group, serverSeq, clientMsgId);\n if (clientMsgId) this.purgedCids.add(clientMsgId); // live-mirror the purge set\n // Drop the body from the transcript, then re-emit.\n this.messageList = this.messageList.filter((m) => m.serverSeq !== serverSeq);\n this.seenKeys.delete(this.internalKey(serverSeq));\n this.emit();\n // The purge now resolves this cid to 'purged' — re-evaluate any held edit / parked\n // delete so an orphan annotation targeting the disappeared message DROPs/no-ops.\n this.editFold.reevaluateHeld(this.authorOfTarget);\n if (clientMsgId) {\n this.deleteFold.reevaluatePending(clientMsgId, this.authorOfTarget(clientMsgId));\n }\n }\n\n /** The Edit/Delete fold author-gate input via {@link AuthorResolution} (disappearing\n * T10 — the orphan-aware resolver): `'purged'` when the target's clientMsgId is in the\n * durable purge set (a late edit DROPs / a late delete no-ops — never resurrects a\n * disappeared message); `'author'` when its author is locally known → run the\n * author-gate; `'unknown'` otherwise → HOLD. The live twin of `projectHistory`'s\n * resolver. Captured as a bound arrow so it can be passed to the pure folds. */\n private readonly authorOfTarget = (targetClientMsgId: string): AuthorResolution => {\n if (this.purgedCids.has(targetClientMsgId)) return { kind: 'purged' };\n const a = this.authorByClientMsgId.get(targetClientMsgId);\n return a !== undefined ? { kind: 'author', userId: a } : { kind: 'unknown' };\n };\n\n // ── Mentions (mentions T6) ──\n\n /** Resolve a message's decode-time mention spans: NORMALIZE the raw `body_ranges`\n * against `text` (the pinned `normalizeMentionRangesUtf16` cross-SDK contract) then\n * resolve each surviving range's `mentionedUserId` to a roster display name. An id\n * not in the roster resolves to `null` (the renderer falls back to the `text` slice).\n * Pure over (text, bodyRanges, memberCache); never throws. Mirrors iOS T3. */\n private resolveMentions(\n text: string | null,\n bodyRanges: MentionRange[] | null | undefined,\n ): ResolvedMention[] {\n if (text === null || !bodyRanges || bodyRanges.length === 0) return [];\n const normalized = normalizeMentionRangesUtf16(bodyRanges, text);\n if (normalized.length === 0) return [];\n return normalized.map((r) => ({\n start: r.start,\n length: r.length,\n mentionedUserId: r.mentionedUserId,\n displayName: this.displayNameOf(r.mentionedUserId),\n }));\n }\n\n /** Re-resolve the roster display name on already-NORMALIZED spans (the history\n * projection produces them with null names — resolution is LIVE, not snapshotted).\n * A member rename then reflects on old messages. Returns the message unchanged when\n * it has no mentions (the common case) or no name changed. Mirrors iOS T3. */\n private resolveMentionNames(m: ChatMessage): ChatMessage {\n // Tolerate a row that predates the mentions field (an older persisted projection /\n // a hand-built history row) — treat absent as `[]`, never throw.\n if (!m.mentions || m.mentions.length === 0) {\n return m.mentions ? m : { ...m, mentions: [] };\n }\n let changed = false;\n const reresolved = m.mentions.map((span) => {\n const name = this.displayNameOf(span.mentionedUserId);\n if (name === span.displayName) return span;\n changed = true;\n return { ...span, displayName: name };\n });\n if (!changed) return m;\n return { ...m, mentions: reresolved };\n }\n\n /** The WINNING edit's resolved mentions for a target (normalize its replacement\n * ranges against the new text + roster names), or `[]` if no winning edit / no\n * ranges. The edited message's mentions reflect the EDIT's ranges (mirrors iOS T3). */\n private editMentions(targetClientMsgId: string, newText: string | null): ResolvedMention[] {\n const ranges = this.editFold.bodyRanges(targetClientMsgId);\n if (!ranges) return [];\n return this.resolveMentions(newText, ranges);\n }\n\n /** Resolve a userId → its roster display name (null if not a known member). */\n private displayNameOf(userId: string): string | null {\n return this.memberCache.find((mm) => mm.userId === userId)?.displayName ?? null;\n }\n\n /** Compute the SELF-ELEVATION decision for a freshly-ingested INCOMING bubble and,\n * when it fires, record the dedup key (persisted) + invoke `onMentionElevation`.\n * Gate (mirrors iOS T3): a surviving mention targets THIS user AND the sender is not\n * me AND it's NOT an edit AND the `(selfUserId, clientMsgId|seq)` key isn't already\n * elevated. Dedup-once: the in-memory set gates the session, the persisted set\n * survives reload. An EDIT never reaches here (it folds, not a bubble) — the\n * `envelopeType !== 'edit'` guard is belt-and-braces. */\n private elevateIfMentioned(\n message: ChatMessage,\n mentions: ResolvedMention[],\n senderUserId: string | null,\n envelopeType: string | undefined,\n ): void {\n const me = this.backend.selfUserId;\n if (envelopeType === 'edit') return; // edits never elevate\n if (senderUserId === me) return; // self-mention never buzzes me\n if (!mentions.some((mm) => mm.mentionedUserId === me)) return; // not mentioned\n const idPart = message.clientMsgId ? message.clientMsgId : `seq:${message.serverSeq}`;\n const key = `${me}|${idPart}`;\n if (this.elevated.has(key)) return; // already elevated → no re-buzz\n this.elevated.add(key);\n // Persist the dedup set (fire-and-forget — the in-memory record already gates this\n // session; the write makes it survive a cold launch).\n if (this._group) {\n void this.backend.saveElevated(this._group, [...this.elevated]).catch(() => {});\n }\n this.onMentionElevation?.(message);\n }\n\n /** Seed the per-target base text + author for the edit fold. Base is write-once\n * (a later own/peer edit must not overwrite the original we render against). The\n * author is (re)recorded whenever a non-empty resolution is available. */\n private seedEditBase(clientMsgId: string, text: string | null, author: string | null): void {\n if (!this.originalTextByClientMsgId.has(clientMsgId)) {\n this.originalTextByClientMsgId.set(clientMsgId, text);\n }\n if (author !== null) this.authorByClientMsgId.set(clientMsgId, author);\n }\n\n /**\n * Rebuild the target message's `reactions` from the authoritative fold and\n * re-emit. No-op when the target isn't present yet (its tally is attached the\n * moment it lands, via the append/merge paths) or when the tally is unchanged.\n */\n private recomputeReactions(targetClientMsgId: string): void {\n if (!targetClientMsgId) return;\n const tally = this.reactionFold.tally(targetClientMsgId);\n let changed = false;\n this.messageList = this.messageList.map((m) => {\n if (m.clientMsgId !== targetClientMsgId) return m;\n if (sameReactions(m.reactions, tally)) return m;\n changed = true;\n return { ...m, reactions: tally };\n });\n if (changed) this.emit();\n }\n\n /**\n * Overlay the authoritative fold's tally onto a message as it is appended/merged.\n * The Chat fold WINS when it has a non-empty tally; otherwise the tally already\n * attached upstream (the coordinator's page-local history fold) is preserved.\n */\n private applyReactionTally(m: ChatMessage): ChatMessage {\n if (!m.clientMsgId) return m;\n const tally = this.reactionFold.tally(m.clientMsgId);\n if (Object.keys(tally).length === 0) return m; // fold has nothing — keep upstream tally\n if (sameReactions(m.reactions, tally)) return m;\n return { ...m, reactions: tally };\n }\n\n /**\n * Rebuild the target message's rendered `text` + `edited` flag from the\n * authoritative edit fold and re-emit, PRESERVING `.reactions` and `.replyTo`\n * (the reaction-polish lesson — never clobber). text = `editFold.text(cid) ??\n * base`; base is the seeded original so a forged/ignored edit leaves it intact.\n * No-op when the target isn't present yet (the fold already recorded it; the\n * overlay applies the moment the target lands) or when unchanged.\n */\n private recomputeEdit(targetClientMsgId: string): void {\n if (!targetClientMsgId) return;\n const editText = this.editFold.text(targetClientMsgId);\n const foldEdited = this.editFold.isEdited(targetClientMsgId);\n let changed = false;\n this.messageList = this.messageList.map((m) => {\n if (m.clientMsgId !== targetClientMsgId) return m;\n const base = this.originalTextByClientMsgId.has(targetClientMsgId)\n ? (this.originalTextByClientMsgId.get(targetClientMsgId) ?? null)\n : m.text;\n const text = editText ?? base;\n // Write-once edited: the Chat fold OR an already-set flag (e.g. the\n // coordinator's page-local history projection). Never downgrade true → false.\n const edited = foldEdited || m.edited;\n // Mentions T6: when an edit WINS, the edited message's mentions reflect the\n // EDIT's replacement ranges (recomputed against the new text); otherwise keep\n // the message's existing mentions.\n const mentions = editText !== null ? this.editMentions(targetClientMsgId, text) : m.mentions;\n if (m.text === text && m.edited === edited && sameMentions(m.mentions, mentions)) return m;\n changed = true;\n return { ...m, text, edited, mentions }; // PRESERVES reactions + replyTo (spread keeps them)\n });\n if (changed) this.emit();\n }\n\n /**\n * Overlay the authoritative edit fold's winning text + flag onto a message as it\n * is appended/merged. The fold WINS when it has an edit for this target;\n * otherwise the upstream `text`/`edited` (e.g. the coordinator's page-local\n * history fold) is preserved. PRESERVES reactions + replyTo.\n */\n private applyEditOverlay(m: ChatMessage): ChatMessage {\n if (!m.clientMsgId) return m;\n const editText = this.editFold.text(m.clientMsgId);\n const foldEdited = this.editFold.isEdited(m.clientMsgId);\n if (editText === null && !foldEdited) return m; // fold has nothing — keep upstream\n const text = editText ?? m.text;\n // Write-once: never downgrade an already-edited flag (the upstream projection).\n const edited = foldEdited || m.edited;\n // Mentions T6: when the LIVE fold has a winning edit for this target, the edited\n // message's mentions come from that edit's replacement ranges; otherwise keep the\n // mentions the message ALREADY carries (e.g. the coordinator's page-local overlay).\n const mentions = editText !== null ? this.editMentions(m.clientMsgId, text) : m.mentions;\n if (m.text === text && m.edited === edited && sameMentions(m.mentions, mentions)) return m;\n return { ...m, text, edited, mentions };\n }\n\n /** @internal — called by the backend's conv subscription. */\n applyConv(event: string, payload: Record<string, unknown>): void {\n const userId = typeof payload.user_id === 'string' ? payload.user_id : null;\n if (!userId || userId === this.backend.selfUserId) return;\n if (event === 'typing') {\n const isTyping = payload.typing === true;\n const member = this.memberCache.find((m) => m.userId === userId) ?? {\n id: userId,\n userId,\n displayName: null,\n role: 'member' as const,\n isSelf: false,\n };\n this.typingList = isTyping\n ? [...this.typingList.filter((m) => m.userId !== userId), member]\n : this.typingList.filter((m) => m.userId !== userId);\n this.emit();\n } else if (event === 'presence') {\n const online = payload.online === true;\n const ts = typeof payload.ts === 'number' ? payload.ts : null;\n this.presenceStates.set(userId, {\n userId,\n isOnline: online,\n lastSeenAt: ts ? new Date(ts * 1000) : null,\n });\n this.emit();\n } else if (event === 'delivered' || event === 'read') {\n // Server-metadata cluster — the double-tick fold (parity with iOS T11). A peer's\n // `delivered`/`read` broadcast carries the acking device_id + an `up_to_server_seq`\n // high-watermark. We fold it onto THIS user's OWN (outgoing) messages whose\n // serverSeq is at or below the acked seq, USER-LEVEL (any peer/device counts) and\n // MONOTONIC (`max` — a stale lower incoming tick NEVER lowers the displayed tick;\n // the same guard the iOS `foldDeliveredTick` applies). `read` STAYS gated by the\n // peer's share_receipts server-side (the broadcast simply never arrives when muted);\n // the client fold is identical (delivered mirrors read).\n const seq = typeof payload.up_to_server_seq === 'number' ? payload.up_to_server_seq : null;\n if (seq === null) return;\n let changed = false;\n this.messageList = this.messageList.map((m) => {\n if (m.direction !== 'outgoing' || m.serverSeq > seq) return m;\n const cur = event === 'delivered' ? (m.deliveredUpTo ?? -1) : (m.readUpTo ?? -1);\n if (seq <= cur) return m; // monotonic: a stale/equal tick never lowers it\n changed = true;\n return event === 'delivered' ? { ...m, deliveredUpTo: seq } : { ...m, readUpTo: seq };\n });\n if (changed) this.emit();\n }\n }\n\n private kindOf(incoming: IncomingMessage): ChatMessageKind {\n if (incoming.kind === 'commit' || incoming.kind === 'proposal' || incoming.kind === 'welcome')\n return 'system';\n return 'text';\n }\n\n private internalKey(serverSeq: number): string {\n return this._group ? `${this._group.rfcGroupId}#${serverSeq}` : `draft#${serverSeq}`;\n }\n private publicId(serverSeq: number): string {\n return `${this.id}#${serverSeq}`;\n }\n\n /** Page older messages in. Returns how many were prepended. */\n async loadEarlier(limit = 50): Promise<number> {\n if (!this._group) return 0;\n const before = this.loadedEarliestSeq ?? undefined;\n const older = await this.backend.history(this._group, limit, before);\n const before0 = this.messageList.length;\n this.mergeHistory(older);\n return this.messageList.length - before0;\n }\n\n // ── Members ──\n\n private async refreshMembers(): Promise<void> {\n if (!this._group) return;\n const m = await this.backend.members(this._group);\n if (m.length > 0) {\n this.memberCache = m;\n this.emit();\n }\n // On a roster change a previously-unresolvable editor may now resolve — re-run\n // held edits + recompute any target whose edit just landed (mirrors iOS T3's\n // refreshMembers hook).\n this.editFold.reevaluateHeld(this.authorOfTarget);\n for (const cid of this.originalTextByClientMsgId.keys()) this.recomputeEdit(cid);\n // Mentions T6: a member's display name may now be known — re-resolve mention names\n // on every message (resolution is LIVE, never snapshotted; a rename reflects here).\n this.reresolveAllMentionNames();\n }\n\n /** Re-resolve roster display names across the whole transcript (called on a roster\n * change). Re-emits only if any name actually changed. */\n private reresolveAllMentionNames(): void {\n let changed = false;\n this.messageList = this.messageList.map((m) => {\n const reresolved = this.resolveMentionNames(m);\n if (reresolved !== m) changed = true;\n return reresolved;\n });\n if (changed) this.emit();\n }\n\n private seedMembersFromGroup(group: MessagingGroup): void {\n const seed: ChatMember[] = [\n {\n id: this.backend.selfUserId,\n userId: this.backend.selfUserId,\n displayName: null,\n role: 'owner',\n isSelf: true,\n },\n ];\n if (group.ownerUserId && group.ownerUserId !== this.backend.selfUserId) {\n seed.push({\n id: group.ownerUserId,\n userId: group.ownerUserId,\n displayName: null,\n role: 'owner',\n isSelf: false,\n });\n }\n this.memberCache = seed;\n }\n\n private seedDraftMembers(draft: ChatDraft): void {\n const seed: ChatMember[] = [\n {\n id: this.backend.selfUserId,\n userId: this.backend.selfUserId,\n displayName: null,\n role: draft.mode.kind === 'group' ? 'owner' : 'member',\n isSelf: true,\n },\n ];\n if (draft.mode.kind === 'direct') {\n seed.push({\n id: draft.mode.peerUserId,\n userId: draft.mode.peerUserId,\n displayName: null,\n role: 'member',\n isSelf: false,\n });\n } else {\n for (const uid of draft.mode.members) {\n seed.push({ id: uid, userId: uid, displayName: null, role: 'member', isSelf: false });\n }\n }\n this.memberCache = seed;\n }\n\n // ── Send (with WhatsApp lazy materialize) ──\n\n async send(\n text: string,\n opts?: {\n replyTo?: ChatMessage;\n mentions?: MentionRange[];\n expiresIn?: { ttlSeconds: number; start?: 'send' | 'read' };\n },\n ): Promise<SentReceipt> {\n const group = await this.materializeIfNeeded();\n\n // Build ReplyRef from the parent ChatMessage if provided (and replyable).\n let replyRef: ReplyRef | null = null;\n let resolvedReplyTo: ResolvedReply | null = null;\n const parent = opts?.replyTo;\n if (parent?.clientMsgId && parent.kind !== 'system') {\n const [previewBody, wasTruncated] = parent.text ? truncateQuote(parent.text) : ['', false];\n replyRef = {\n v: 1,\n client_msg_id: parent.clientMsgId,\n preview: {\n kind: 'text',\n author_user_id: parent.senderUserId ?? '',\n body: previewBody || undefined,\n body_truncated: wasTruncated,\n },\n };\n // Resolve the reply against local history immediately (the parent IS local).\n resolvedReplyTo = resolveReply(replyRef, (id) => this.byClientMsgId.get(id) ?? null);\n }\n\n // Mentions T6: carry the caller's mention ranges onto the wire envelope (encoded\n // as `body_ranges`). The own-sent bubble resolves them locally for its own render.\n const bodyRanges = opts?.mentions ?? null;\n // Disappearing T10: a per-message TTL rides INSIDE the ciphertext as an `expiry` spec\n // — the server stays MLS-blind (no expires_at, no new route). `start === 'send'`\n // (default) anchors the deadline to the sender's compose-time (stamps sender_send_ts,\n // clamped on receipt by the local first-arrival); `read` anchors it to first-read.\n //\n // COMPOSE-TIME STAMPING (spec §\"Per-message vs per-chat\"): a message's effective TTL\n // is its OWN explicit `expiresIn` IF present, ELSE the chat's active default timer\n // STAMPED ONTO IT NOW. The per-message `expiry` field is the durable record — so a\n // plain send under an active default carries the (frozen) default on the wire, making\n // sender and receiver symmetric (both arm from the SAME stamped expiry, forward-only).\n const effectiveExpiry = this.composeExpiry(opts?.expiresIn);\n const { receipt, clientMsgId } = await this.backend.sendText(\n group,\n text,\n replyRef,\n bodyRanges,\n effectiveExpiry,\n );\n this.appendOwnSend(text, receipt, clientMsgId, resolvedReplyTo, bodyRanges, effectiveExpiry);\n // The own-send arms its OWN purge on the same rule (the stamped expiry is the basis).\n // FIRE-AND-FORGET ADVISORY: we return on the LOCAL emit — there is NO peer-enforcement.\n if (effectiveExpiry) void this.armPurge(effectiveExpiry, receipt.serverSeq, clientMsgId);\n return receipt; // fire-and-forget advisory: local emit only, no peer-enforcement signal\n }\n\n /** Resolve a send's effective per-message expiry at COMPOSE TIME: the caller's explicit\n * `expiresIn` if present, ELSE the chat's active default timer stamped onto the message\n * NOW (the durable record per spec §\"Compose-time stamping\"). A `send`-anchored expiry\n * (explicit or default-inherited) stamps `senderSendTs` = the sender's compose epoch\n * seconds; a `read`-anchored one carries none (the deadline is the recipient's local\n * first-read). Returns null when there is neither an explicit expiry nor an active\n * default (a plain, non-disappearing send). Mirrors the iOS compose-time stamping. */\n private composeExpiry(explicit?: {\n ttlSeconds: number;\n start?: 'send' | 'read';\n }): ExpirySpec | null {\n const base: ExpirySpec | null = explicit\n ? {\n v: 1,\n ttlSeconds: explicit.ttlSeconds,\n start: explicit.start ?? 'send',\n senderSendTs: null,\n }\n : this.defaultExpiry();\n if (!base) return null;\n return {\n ...base,\n senderSendTs: base.start === 'send' ? Math.floor(Date.now() / 1000) : null,\n };\n }\n\n /** Set (or DISABLE) this chat's DEFAULT disappearing timer. Emits a `timer_set` control\n * envelope (server-blind — an opaque application message, NEVER a bubble) and folds the\n * own-set locally so the default applies immediately to subsequent sends that carry no\n * per-message expiry. `ttlSeconds === null` DISABLES the default. FIRE-AND-FORGET\n * ADVISORY: returns on the LOCAL emit only; it exposes NO \"active for all peers\" signal.\n * Mirrors iOS `setDisappearing(ttlSeconds:start:)`. */\n async setDisappearing(opts: {\n ttlSeconds: number | null;\n start?: 'send' | 'read';\n }): Promise<void> {\n const group = await this.materializeIfNeeded();\n const clientMsgId = mintClientMsgId();\n const start = opts.start ?? 'send';\n const { receipt } = await this.backend.sendTimerSet(group, {\n clientMsgId,\n ttlSeconds: opts.ttlSeconds,\n start,\n });\n // Fold the own-set locally (actor = self) at the server-assigned order so the default\n // applies immediately. The fold dedups on the event clientMsgId, so the durable echo on\n // the next pump is a no-op. A timer_set is NOT a bubble.\n this.timerFold.ingest({\n ttlSeconds: opts.ttlSeconds,\n start,\n actorUserId: this.backend.selfUserId,\n epoch: receipt.epoch,\n serverSeq: receipt.serverSeq,\n eventClientMsgId: clientMsgId,\n });\n // fire-and-forget advisory: returns on local emit; no \"active for all peers\" signal.\n }\n\n private appendOwnSend(\n text: string,\n receipt: SentReceipt,\n clientMsgId: string,\n resolvedReplyTo: ResolvedReply | null,\n bodyRanges?: MentionRange[] | null,\n expiry?: ExpirySpec | null,\n ): void {\n if (receipt.serverSeq <= 0) return;\n const key = this.internalKey(receipt.serverSeq);\n if (this.seenKeys.has(key)) return;\n this.seenKeys.add(key);\n\n // Index own-sent clientMsgId for future resolveReply lookups (self-reply support).\n if (clientMsgId) {\n this.byClientMsgId.set(clientMsgId, { text, senderUserId: this.backend.selfUserId });\n // Seed the edit base + author (self) so a later own edit author-gates and the\n // overlay renders `edit ?? original`.\n this.seedEditBase(clientMsgId, text, this.backend.selfUserId);\n }\n\n this.messageList.push({\n id: this.publicId(receipt.serverSeq),\n kind: 'text',\n direction: 'outgoing',\n senderUserId: this.backend.selfUserId,\n text,\n serverSeq: receipt.serverSeq,\n sentAt: new Date(),\n clientMsgId,\n replyTo: resolvedReplyTo,\n // Attach any tally already folded for this own-sent message (rare, but keeps\n // the dangling-target invariant uniform across every append path).\n reactions: clientMsgId ? this.reactionFold.tally(clientMsgId) : {},\n // Own-sent edits fold via edit() after the fact; new sends start unedited.\n edited: false,\n // Own-sent deletes fold via deleteForEveryone() after the fact; start live.\n isDeleted: false,\n // Mentions T6: resolve the own-sent bubble's mentions for its own render (the\n // sender never gets a wire echo of its own message — this is the only local copy).\n mentions: this.resolveMentions(text, bodyRanges),\n // Disappearing T10: the surfaced deadline reflects the message's effective expiry\n // (explicit `expiresIn` OR the chat default stamped at compose time). null only when\n // this send is non-disappearing. armPurge re-derives the durable monotonic deadline;\n // this is the immediate UI countdown baseline (own sender and receiver are symmetric).\n expiresAt: this.deadlineFor(expiry ?? null),\n });\n this.messageList.sort((a, b) => a.serverSeq - b.serverSeq);\n this.emit();\n }\n\n private async materializeIfNeeded(): Promise<MessagingGroup> {\n if (this._group) return this._group;\n if (!this._draft) throw new Error('messaging_not_configured');\n const group = await this.backend.materialize(this._draft);\n this._group = group;\n this.id = group.displayId;\n this._state = 'active';\n this._draft = null;\n this.backend.registerActive(group);\n this.ensureWired();\n this.emit();\n return group;\n }\n\n // ── Membership (groups only) ──\n\n async addMemberUser(userId: string): Promise<void> {\n if (this.kind !== 'group') throw new Error('chat_is_direct');\n const group = await this.materializeIfNeeded();\n await this.backend.addMember(group, userId);\n await this.refreshMembers();\n }\n\n async removeMemberUser(userId: string): Promise<void> {\n if (this.kind !== 'group') throw new Error('chat_is_direct');\n if (!this._group) throw new Error('messaging_not_configured');\n await this.backend.removeMember(this._group, userId);\n await this.refreshMembers();\n }\n\n async leave(): Promise<void> {\n if (!this._group) return;\n await this.backend.leave(this._group);\n this.liveUnsub?.();\n this.liveUnsub = null;\n }\n\n // ── Imperative ──\n\n setTyping(isTyping: boolean): void {\n if (this._group) this.backend.setTyping(this._group, isTyping);\n }\n\n async markRead(message: ChatMessage): Promise<void> {\n if (!this._group) return;\n await this.backend.markRead(this._group, message.serverSeq);\n this.readWatermark = Math.max(this.readWatermark, message.serverSeq);\n this.emit();\n }\n\n // ── Server-metadata cluster: notify scope (mute) + server unread ──\n\n /** Set this chat's notify scope (the mute toggle). `'none'` mutes the push wake;\n * `'all'` unmutes (the default). Materializes a draft first (the scope is a\n * per-(user,group) server row), PUTs `/notify`, and returns the server-echoed scope.\n * Mirrors iOS `Chat.setNotifyScope`. */\n async setNotifyScope(scope: NotifyScope): Promise<NotifyScope> {\n const group = await this.materializeIfNeeded();\n return this.backend.setNotifyScope(group, scope);\n }\n\n /** Refresh + return this chat's notify scope from the server (fail-OPEN to `'all'`).\n * Returns `'all'` for a draft chat (no server row yet). */\n async getNotifyScope(): Promise<NotifyScope> {\n if (!this._group) return 'all';\n return this.backend.getNotifyScope(this._group);\n }\n\n /** Fetch the caller's authoritative SERVER unread count (opaque server metadata).\n * Returns the clamped count (`max(0, …)`); `0` for a draft chat. The local computed\n * `unreadCount` getter stays the instant, offline best-effort badge — this is the\n * canonical count on demand. Named distinctly so it does not shadow the observable\n * `unreadCount` snapshot getter. Mirrors iOS `Chat.refreshUnread`. */\n async unreadCountFromServer(): Promise<number> {\n if (!this._group) return 0;\n const v = await this.backend.unread(this._group);\n return Math.max(0, v.unreadCount);\n }\n\n // ── Reactions ──\n\n /** Add an emoji reaction to a message. No-op if the message isn't reactable\n * (empty clientMsgId — a legacy/system row). The reaction folds locally with\n * the server receipt's `(epoch, serverSeq)` so the target's tally updates\n * instantly; the durable echo on the next pump is a fold no-op (dedup on the\n * SAME wire clientMsgId). Never appends a bubble. */\n async react(message: ChatMessage, emoji: string): Promise<void> {\n await this.sendReaction(message, emoji, 'add');\n }\n\n /** Remove this user's emoji reaction from a message (op:'remove'). */\n async unreact(message: ChatMessage, emoji: string): Promise<void> {\n await this.sendReaction(message, emoji, 'remove');\n }\n\n private async sendReaction(\n message: ChatMessage,\n emoji: string,\n op: 'add' | 'remove',\n ): Promise<void> {\n if (!message.clientMsgId) return; // not reactable\n const group = await this.materializeIfNeeded();\n const clientMsgId = mintClientMsgId();\n const { receipt } = await this.backend.sendReaction(group, {\n clientMsgId,\n targetClientMsgId: message.clientMsgId,\n emoji,\n op,\n });\n // Fold the own-send locally (actor = self) using the SAME wire clientMsgId so\n // the durable echo dedups (no double-fold), ordered by the receipt's (epoch,\n // serverSeq) for LWW. Then recompute the target's tally + emit.\n this.reactionFold.ingest({\n targetClientMsgId: message.clientMsgId,\n actorUserId: this.backend.selfUserId,\n emoji,\n op,\n epoch: receipt.epoch,\n serverSeq: receipt.serverSeq,\n eventClientMsgId: clientMsgId,\n });\n this.recomputeReactions(message.clientMsgId);\n }\n\n // ── Edit ──\n\n /** Edit an own text message (edit-by-supersession). No-op if the message isn't\n * editable (empty clientMsgId, or not a `text` kind). The edit folds locally\n * with the server receipt's `(epoch, serverSeq)` so the target's text updates\n * instantly; the durable echo on the next pump is a fold no-op (dedup on the\n * SAME wire clientMsgId). Never appends a bubble; PRESERVES the target's\n * reactions + reply context. Only the original author's edits count — for an own\n * message self IS the author, so the author-gate passes. `opts.mentions` carries the\n * edit's REPLACEMENT mention ranges → the edited message's mentions reflect them. */\n async edit(\n message: ChatMessage,\n newText: string,\n opts?: { mentions?: MentionRange[] },\n ): Promise<void> {\n if (!message.clientMsgId || message.kind !== 'text') return; // not editable\n const group = await this.materializeIfNeeded();\n const clientMsgId = mintClientMsgId();\n const bodyRanges = opts?.mentions ?? null;\n const { receipt } = await this.backend.sendEdit(group, {\n clientMsgId,\n targetClientMsgId: message.clientMsgId,\n newText,\n bodyRanges,\n });\n // Seed the base text + author (self) if not already recorded — a 2nd own edit\n // must not corrupt the base (the seed is write-once). self IS the author of an\n // own message, so authorOfTarget gates the own edit.\n this.seedEditBase(message.clientMsgId, message.text, this.backend.selfUserId);\n // Fold the own-send locally (editor = self) using the SAME wire clientMsgId so\n // the durable echo dedups (no double-fold), ordered by the receipt's (epoch,\n // serverSeq) for LWW. Carry the edit's replacement ranges so the edited message's\n // mentions reflect them. Then recompute the target's text + edited in place.\n this.editFold.ingest(\n {\n targetClientMsgId: message.clientMsgId,\n editorUserId: this.backend.selfUserId,\n newText,\n epoch: receipt.epoch,\n serverSeq: receipt.serverSeq,\n eventClientMsgId: clientMsgId,\n bodyRanges,\n },\n this.authorOfTarget,\n );\n this.recomputeEdit(message.clientMsgId);\n }\n\n // ── Delete ──\n\n /** Delete a message for EVERYONE (a cooperative encrypted tombstone). Only the\n * ORIGINAL SENDER can do this — for an own message self IS the author, so the\n * author-gate passes. Legacy messages (empty clientMsgId) are DISABLED (a\n * tombstone keys on the target's clientMsgId, which they lack) — no-op. Sends a\n * `type:'delete'` envelope through the SAME MLS path as a text message (the\n * server stays blind), folds the own delete locally so the target scrubs in\n * place instantly (the durable echo dedups on the SAME wire clientMsgId), and\n * re-emits. NEVER appends a bubble. delete-for-me'ing the target becomes moot. */\n async deleteForEveryone(message: ChatMessage): Promise<void> {\n if (!message.clientMsgId) return; // legacy → for-everyone disabled\n const group = await this.materializeIfNeeded();\n const clientMsgId = mintClientMsgId();\n const { receipt } = await this.backend.sendDelete(group, {\n clientMsgId,\n targetClientMsgId: message.clientMsgId,\n });\n // Seed the author (self) so the own delete author-gates: self IS the author of\n // an own message. The base text seed is write-once + harmless here.\n this.seedEditBase(message.clientMsgId, message.text, this.backend.selfUserId);\n // Fold the own delete locally (actor = self = author). Absorbing — the durable\n // echo on the next pump dedups on the SAME wire clientMsgId.\n this.deleteFold.ingest(\n {\n targetClientMsgId: message.clientMsgId,\n actorUserId: this.backend.selfUserId,\n epoch: receipt.epoch,\n serverSeq: receipt.serverSeq,\n eventClientMsgId: clientMsgId,\n },\n this.authorOfTarget,\n );\n this.emit(); // surfaced() re-reads the fold → the target renders \"deleted\"\n }\n\n /** Delete a message for ME only — a LOCAL, per-device suppression. NO wire, NO\n * attribution, no server contact: the message is OMITTED from THIS view and the\n * suppression key persists per chat (survives reload). The key is the message's\n * clientMsgId when present, else `seq:<serverSeq>` for legacy messages. */\n async deleteForMe(message: ChatMessage): Promise<void> {\n const key = message.clientMsgId ? message.clientMsgId : `seq:${message.serverSeq}`;\n if (this.suppressed.has(key)) return; // idempotent\n this.suppressed.add(key);\n this.emit(); // surfaced() omits it immediately (no wire — nothing sent)\n // Persist the full set per chat (fire-and-forget; the omit already applied).\n if (this._group) {\n try {\n await this.backend.saveSuppressed(this._group, [...this.suppressed]);\n } catch {\n // best-effort — the in-memory omit holds this session even if persist fails\n }\n }\n }\n}\n\n/** Order-sensitive equality of two resolved-mention lists (normalize is deterministic,\n * so equal spans imply equal order). Cheap shortcut so the overlay paths can no-op. */\nfunction sameMentions(a: ResolvedMention[], b: ResolvedMention[]): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n const x = a[i];\n const y = b[i];\n if (!x || !y) return false;\n if (\n x.start !== y.start ||\n x.length !== y.length ||\n x.mentionedUserId !== y.mentionedUserId ||\n x.displayName !== y.displayName\n ) {\n return false;\n }\n }\n return true;\n}\n\n/** Shallow-equal two reaction tallies (emoji → sorted userIds[]). */\nfunction sameReactions(a: Record<string, string[]>, b: Record<string, string[]>): boolean {\n const ak = Object.keys(a);\n const bk = Object.keys(b);\n if (ak.length !== bk.length) return false;\n for (const k of ak) {\n const av = a[k];\n const bv = b[k];\n if (!bv || av === undefined || av.length !== bv.length) return false;\n for (let i = 0; i < av.length; i++) if (av[i] !== bv[i]) return false;\n }\n return true;\n}\n","// The live delivery source — the web mirror of iOS MessageDeliverySource.\n// Subscribes the device wake topic `messaging:device:<id>` over pb.realtime\n// (new_message wake → pumpOnce), and on each pump drains:\n//\n// 1. welcomes → engine.joinFromWelcome → register the group + emit `welcome`\n// 2. device queue → for each row:\n// - COMMIT: processIncoming (applies), bump epoch, emit `commit`\n// - APPLICATION: decrypt → persist plaintext → emit `message`\n// 3. ack everything consumed.\n//\n// The realtime wake is notification-only; the authoritative messages come from\n// the authenticated queue pull (never the broadcast payload), exactly as iOS.\n//\n// SERVER WIRE GAP (same as iOS): queue rows do NOT yet carry rfc_group_id or the\n// fanned declaration. When absent, route a blob to the SOLE known group (the\n// common 1:1/small-group case). The reconciliation-gate hook (describeIncoming)\n// is ready for the day the server fans the declaration — a pure server change.\n\nimport { palbeRequest } from '../request.js';\nimport type { PalbeRuntime } from '../runtime.js';\nimport {\n type DeletePayload,\n decodeEnvelope,\n type EditPayload,\n type ExpirySpec,\n GroupMessaging,\n type MentionRange,\n type MessagingGroup,\n type ReactionPayload,\n type ReplyRef,\n} from './group-messaging.js';\nimport type { MessageStore, StoredMessage } from './history.js';\nimport type { MlsEngine } from './mls-engine.js';\nimport type { GroupRegistry } from './registry.js';\nimport type { NotifyScope, UnreadView } from './types.js';\nimport { decodeUtf8, fromBase64, toBase64 } from './util.js';\nimport {\n ContentType,\n MessagingPaths,\n type QueuedMessage,\n type QueuePage,\n type WelcomePage,\n} from './wire.js';\n\n/** A decoded incoming message handed to chat listeners. */\nexport interface IncomingMessage {\n kind: 'application' | 'commit' | 'proposal' | 'welcome';\n group: MessagingGroup;\n text: string | null;\n senderDeviceId: string | null;\n epoch: number;\n serverSeq: number;\n receivedAt: Date;\n /** The client-minted idempotency id from the decoded envelope (empty string for legacy/system). */\n clientMsgId: string;\n /** The decoded reply reference, if present in the envelope. */\n replyRef: ReplyRef | null;\n /**\n * The decoded envelope discriminator (`'text'`/`'reaction'`/`'media'`/…).\n * Defaults to `'text'` at every existing call site — only the reaction branch\n * sets `'reaction'`, so the Chat can route a reaction into its fold instead of\n * appending a visible bubble. Optional for forward-compat with old emitters.\n */\n envelopeType?: string;\n /** The decoded reaction payload, present only when `envelopeType === 'reaction'`. */\n reaction?: ReactionPayload | null;\n /** The decoded edit payload, present only when `envelopeType === 'edit'`. Lets\n * the Chat route an edit into its EditFold instead of appending a visible bubble. */\n edit?: EditPayload | null;\n /** The decoded delete payload, present only when `envelopeType === 'delete'`. Lets\n * the Chat route a delete-for-everyone tombstone into its DeleteFold instead of\n * appending a visible bubble. */\n delete?: DeletePayload | null;\n /** The decoded mention ranges (raw, un-normalized). Present on a `'text'` bubble\n * with `body_ranges` OR on an `'edit'` (the edit's replacement ranges). The Chat\n * normalizes + resolves roster names → `ChatMessage.mentions` (mentions T6). */\n bodyRanges?: MentionRange[] | null;\n /** The decoded `timer_set` payload, present only when `envelopeType === 'timer_set'`\n * (disappearing T10). Lets the Chat route a chat-default timer into its TimerFold\n * instead of appending a visible bubble. */\n timer?: { ttlSeconds: number | null; start: 'send' | 'read' } | null;\n /** The decoded per-message TTL spec, present only on a `'text'` bubble that carried an\n * `expiry` (disappearing T10). Lets the Chat arm the message's purge on first display. */\n expiry?: ExpirySpec | null;\n}\n\ntype MessageListener = (m: IncomingMessage) => void;\ntype ConvEvent = { event: string; payload: Record<string, unknown> };\ntype ConvListener = (e: ConvEvent) => void;\n\n/** A tiny per-group fan-out hub (the web analogue of the iOS message/conv hubs). */\nexport class MessageHub {\n private readonly messageListeners = new Map<string, Set<MessageListener>>();\n private readonly convListeners = new Map<string, Set<ConvListener>>();\n\n onMessage(displayId: string, fn: MessageListener): () => void {\n return this.add(this.messageListeners, displayId, fn);\n }\n emitMessage(displayId: string, m: IncomingMessage): void {\n for (const fn of this.messageListeners.get(displayId) ?? []) fn(m);\n }\n onConv(displayId: string, fn: ConvListener): () => void {\n return this.add(this.convListeners, displayId, fn);\n }\n emitConv(displayId: string, e: ConvEvent): void {\n for (const fn of this.convListeners.get(displayId) ?? []) fn(e);\n }\n\n private add<T>(map: Map<string, Set<T>>, key: string, fn: T): () => void {\n let set = map.get(key);\n if (!set) {\n set = new Set();\n map.set(key, set);\n }\n set.add(fn);\n return () => {\n set?.delete(fn);\n };\n }\n}\n\n/**\n * Decode the MLS-authenticated sender identity (`received.sender`) into the\n * `mdv_…` device id. The leaf-credential identity is exactly the SP-0 device id\n * UTF-8 bytes (see MlsEngine: `idBytes = utf8.encode(deviceId)`), so a plain\n * UTF-8 decode recovers it. Returns null for an empty/garbage identity so the\n * downstream `if (senderDeviceId)` guard stays sound (no spoofable empty actor).\n */\nfunction decodeSenderDeviceId(sender: Uint8Array): string | null {\n if (sender.length === 0) return null;\n const id = decodeUtf8(sender);\n return id.length > 0 ? id : null;\n}\n\nexport class MessageDeliverySource {\n private started = false;\n private wakeUnsub: (() => void) | null = null;\n // Per-observed-group conv subscription + heartbeat (presence/typing/read).\n private readonly observed = new Map<\n string,\n { unsub: () => void; heartbeat?: ReturnType<typeof setInterval> }\n >();\n\n constructor(\n private readonly rt: PalbeRuntime,\n private readonly engine: MlsEngine,\n private readonly hub: MessageHub,\n private readonly registry: GroupRegistry,\n private readonly messageStore: MessageStore,\n private readonly deviceId: string,\n private readonly selfUserId: string,\n ) {}\n\n /** Subscribe the device wake topic + run an initial drain. Idempotent. */\n async start(): Promise<void> {\n if (this.started) return;\n this.started = true;\n try {\n const channel = this.rt.realtime.channel(`messaging:device:${this.deviceId}`);\n const sub = channel.on('new_message', () => {\n void this.pumpOnce();\n });\n // Re-pump on every (re)connect to catch wakes missed while offline.\n const offConn = this.rt.realtime.status.onChange((s) => {\n if (s.state === 'connected') void this.pumpOnce();\n });\n this.wakeUnsub = () => {\n sub.cancel();\n offConn();\n };\n } catch {\n // No WebSocket (SSR/Node) — the wake source is inert; explicit pumps still work.\n }\n await this.pumpOnce();\n }\n\n stop(): void {\n this.wakeUnsub?.();\n this.wakeUnsub = null;\n for (const [, o] of this.observed) {\n o.unsub();\n if (o.heartbeat) clearInterval(o.heartbeat);\n }\n this.observed.clear();\n this.started = false;\n }\n\n /** One full drain: welcomes → queue (decrypt + emit) → ack. Never throws. */\n async pumpOnce(): Promise<void> {\n try {\n await this.pullWelcomes();\n } catch {\n // transient — the next wake retries\n }\n try {\n await this.pullQueue();\n } catch {\n // transient — the next wake retries\n }\n }\n\n // ── Welcomes ──\n\n private async pullWelcomes(): Promise<void> {\n const page = await palbeRequest<WelcomePage>(\n this.rt,\n 'GET',\n MessagingPaths.deviceWelcomes(this.deviceId),\n );\n for (const w of page.welcomes) {\n try {\n const gidBytes = await this.engine.joinFromWelcome(fromBase64(w.welcome_b64));\n // Register under the server-minted grp_ display_id (url-safe), NEVER the\n // base64 rfc_group_id (which would 404 the members route — M3 #7).\n const group: MessagingGroup = {\n displayId: w.display_id,\n rfcGroupId: toBase64(gidBytes),\n currentEpoch: w.added_at_epoch,\n ownerUserId: null,\n directKey: null,\n name: null,\n };\n this.registry.register(group);\n this.hub.emitMessage(group.displayId, {\n kind: 'welcome',\n group,\n text: null,\n senderDeviceId: null,\n epoch: w.added_at_epoch,\n serverSeq: 0,\n receivedAt: new Date(),\n clientMsgId: '',\n replyRef: null,\n });\n } catch {\n // POISON-PILL GUARD: a re-served welcome whose one-time KeyPackage was\n // already consumed (a duplicate for a membership we hold) fails join\n // forever — skip quietly when the group is already known.\n const known = this.registry.all().some((g) => g.displayId === w.display_id);\n if (!known) {\n // genuinely unknown group — swallow (best-effort pump); next wake retries\n }\n }\n }\n }\n\n // ── Queue ──\n\n private async pullQueue(): Promise<void> {\n const page = await palbeRequest<QueuePage>(\n this.rt,\n 'GET',\n MessagingPaths.deviceQueue(this.deviceId),\n );\n const ackIds: string[] = [];\n for (const row of page.messages) {\n if (await this.process(row)) ackIds.push(row.id);\n }\n if (ackIds.length > 0) {\n await palbeRequest<void>(this.rt, 'POST', MessagingPaths.deviceQueueAck(this.deviceId), {\n body: { message_ids: ackIds },\n });\n }\n }\n\n /** Process one queue row; returns true if consumed (should ack). */\n private async process(row: QueuedMessage): Promise<boolean> {\n let blob: Uint8Array;\n try {\n blob = fromBase64(row.ciphertext_b64);\n } catch {\n return true; // junk → drop\n }\n // Resolve the target group: prefer the row's own rfc, else the sole group.\n const group =\n (row.rfc_group_id_b64 ? this.registry.group(row.rfc_group_id_b64) : undefined) ??\n this.registry.soleGroup();\n if (!group) return false; // unresolvable → leave unacked\n\n try {\n const received = await this.engine.processIncoming(fromBase64(group.rfcGroupId), blob);\n if (received.type === 'application') {\n const decoded = decodeEnvelope(received.data);\n const { text, clientMsgId, replyTo } = decoded;\n const isReaction = decoded.type === 'reaction' && decoded.reaction != null;\n const isEdit = decoded.type === 'edit' && decoded.edit != null;\n const isDelete = decoded.type === 'delete' && decoded.delete != null;\n const isTimerSet = decoded.type === 'timer_set' && decoded.timer != null;\n // SENDER ATTRIBUTION: prefer the server's `sender_device_id`, but the live\n // server does NOT yet fan that field onto queue rows (same gap as\n // `rfc_group_id` — see QueuedMessage). So fall back to the MLS-decrypted\n // sender (`received.sender`), which IS the device's leaf-credential\n // identity bytes (`mdv_…`, the SP-0 device id). This is the\n // cryptographically authenticated sender — strictly stronger than a\n // server-asserted field, and unspoofable. Without it, a peer's reaction\n // arrives with a null sender → the Chat fold can't resolve the actor and\n // SILENTLY DROPS it (text still renders, reactions vanish — the live bug).\n const senderDeviceId = row.sender_device_id ?? decodeSenderDeviceId(received.sender);\n const stored: StoredMessage = {\n id: `${group.rfcGroupId}#${row.server_seq}`,\n direction: 'incoming',\n text,\n senderDeviceId,\n epoch: row.epoch,\n serverSeq: row.server_seq,\n at: Date.now(),\n clientMsgId: clientMsgId || undefined,\n replyTo: replyTo\n ? {\n clientMsgId: replyTo.client_msg_id,\n previewBody: replyTo.preview?.body ?? null,\n previewAuthorUserId: replyTo.preview?.author_user_id ?? null,\n previewKind: replyTo.preview?.kind ?? 'text',\n }\n : null,\n // Thread the reaction discriminator + fields through the persisted row so\n // a reaction folded LIVE re-folds onto its target after a reload (the\n // reload-parity boundary — mirrors iOS T3). Omitted for non-reactions →\n // old rows hydrate as `'text'`/no-reaction (backward-compat).\n ...(isReaction && decoded.reaction\n ? {\n envelopeType: 'reaction' as const,\n reaction: {\n targetClientMsgId: decoded.reaction.targetClientMsgId,\n emoji: decoded.reaction.emoji,\n op: decoded.reaction.op,\n },\n }\n : {}),\n // Thread the edit discriminator + new text through the persisted row so an\n // edit folded LIVE re-folds onto its target after a reload (the reload-parity\n // boundary — mirrors iOS T3). Omitted for non-edits → old rows hydrate as\n // `'text'`/no-edit (backward-compat). The edit's replacement body_ranges ride\n // along so the edited message's mentions re-resolve on cold launch (T6).\n ...(isEdit && decoded.edit\n ? {\n envelopeType: 'edit' as const,\n edit: {\n targetClientMsgId: decoded.edit.targetClientMsgId,\n newText: decoded.edit.newText,\n ...(decoded.bodyRanges ? { bodyRanges: decoded.bodyRanges } : {}),\n },\n }\n : {}),\n // Thread the TEXT bubble's mention ranges (raw) through the persisted row so a\n // mention surfaced LIVE re-resolves onto its bubble after a reload (the\n // reload-parity boundary for mentions — T6, mirrors iOS T3). Only on a text\n // bubble (not a reaction/edit/delete row); omitted when absent (backward-compat).\n ...(!isReaction && !isEdit && !isDelete && decoded.bodyRanges\n ? { bodyRanges: decoded.bodyRanges }\n : {}),\n // Thread the delete discriminator + target through the persisted row so a\n // delete-for-everyone tombstone folded LIVE re-folds onto its target after\n // a reload (the reload-parity boundary — the iOS-review CRITICAL lesson;\n // the projection's `.delete` branch re-folds it so it never leaks a blank\n // bubble). Omitted for non-deletes → old rows hydrate as `'text'`/no-delete.\n ...(isDelete && decoded.delete\n ? {\n envelopeType: 'delete' as const,\n delete: {\n targetClientMsgId: decoded.delete.targetClientMsgId,\n scope: decoded.delete.scope,\n },\n }\n : {}),\n // Disappearing T10: thread the timer_set discriminator + payload through the\n // persisted row so the chat default re-folds on cold launch (the page-local\n // TimerFold in projectHistory). Omitted for non-timer_set rows (backward-compat).\n ...(isTimerSet && decoded.timer\n ? {\n envelopeType: 'timer_set' as const,\n timer: { ttlSeconds: decoded.timer.ttlSeconds, start: decoded.timer.start },\n }\n : {}),\n // Disappearing T10: thread a TEXT bubble's per-message expiry through the persisted\n // row so the message re-arms its purge on cold launch (the projection derives the\n // deadline from this). Only on a text bubble; omitted when absent (backward-compat).\n ...(!isReaction && !isEdit && !isDelete && !isTimerSet && decoded.expiry\n ? { expiry: decoded.expiry }\n : {}),\n };\n try {\n await this.messageStore.append(group.rfcGroupId, stored);\n } catch {\n return false; // persist failed → NOT acked, re-served next pump\n }\n this.hub.emitMessage(group.displayId, {\n kind: 'application',\n group,\n text,\n senderDeviceId,\n epoch: row.epoch,\n serverSeq: row.server_seq,\n receivedAt: new Date(),\n clientMsgId,\n replyRef: replyTo,\n envelopeType: decoded.type ?? 'text',\n reaction: isReaction ? decoded.reaction : null,\n edit: isEdit ? decoded.edit : null,\n delete: isDelete ? decoded.delete : null,\n // The raw mention ranges (text bubble or the edit's replacement ranges); the\n // Chat normalizes + resolves names → ChatMessage.mentions (T6).\n bodyRanges: decoded.bodyRanges ?? null,\n // Disappearing T10: the decoded timer_set payload (chat default control) + a text\n // bubble's per-message expiry. The Chat routes timer_set into its TimerFold and\n // arms a bubble's purge from the expiry (or the active default).\n timer: isTimerSet ? decoded.timer : null,\n expiry:\n !isReaction && !isEdit && !isDelete && !isTimerSet ? (decoded.expiry ?? null) : null,\n });\n return true;\n }\n if (received.type === 'commitApplied') {\n const newEpoch = Math.max(row.epoch, group.currentEpoch + 1);\n this.registry.bumpEpoch(group.rfcGroupId, newEpoch);\n const fresh = this.registry.group(group.rfcGroupId) ?? group;\n this.hub.emitMessage(group.displayId, {\n kind: 'commit',\n group: fresh,\n text: null,\n senderDeviceId: row.sender_device_id ?? null,\n epoch: newEpoch,\n serverSeq: 0,\n receivedAt: new Date(),\n clientMsgId: '',\n replyRef: null,\n });\n return true;\n }\n return true; // proposal / other — consumed\n } catch (e) {\n // Own-sent echo or consumed-key: mls-rs refuses to decrypt own ciphertext /\n // a replayed generation. ACK + skip (the durable copy is the send path's).\n if (isOwnEchoOrConsumed(e)) return true;\n return false; // decode/transient → leave unacked for retry\n }\n }\n\n // ── Conv topic (presence / typing / read) ──\n\n /** Begin observing a group's conv topic; fans events into the hub. Idempotent. */\n observeConversation(group: MessagingGroup): void {\n if (this.observed.has(group.displayId)) return;\n try {\n const channel = this.rt.realtime.channel(`messaging:conv:${group.rfcGroupId}`);\n // Server-metadata cluster: subscribe `delivered` alongside `read` (its exact 3-key\n // twin `{user_id, device_id, up_to_server_seq}`) so the sender's \"delivered\" tick\n // advances. The Chat consumes both via applyConv → the per-message user-level\n // monotonic watermark fold (mirrors iOS MessageDeliverySource's delivered case).\n const subs = ['presence', 'typing', 'read', 'delivered'].map((ev) =>\n channel.on(ev, (payload) => {\n this.hub.emitConv(group.displayId, { event: ev, payload });\n }),\n );\n // Announce online + heartbeat every 25s.\n const emitPresence = (online: boolean) =>\n channel.send('presence', {\n user_id: this.selfUserId,\n device_id: this.deviceId,\n online,\n ts: Date.now() / 1000,\n });\n emitPresence(true);\n const heartbeat = setInterval(() => emitPresence(true), 25_000);\n this.observed.set(group.displayId, {\n unsub: () => {\n for (const s of subs) s.cancel();\n },\n heartbeat,\n });\n } catch {\n // no socket (SSR) — presence/typing simply unavailable\n }\n }\n\n /** Announce typing on a group's conv topic (the app calls per keystroke). */\n setTyping(group: MessagingGroup, isTyping: boolean): void {\n this.observeConversation(group);\n try {\n this.rt.realtime.channel(`messaging:conv:${group.rfcGroupId}`).send('typing', {\n user_id: this.selfUserId,\n device_id: this.deviceId,\n typing: isTyping,\n });\n } catch {\n // no socket\n }\n }\n\n /** Advance this device's read cursor (idempotent; the server clamps). */\n async markRead(group: MessagingGroup, upToServerSeq: number): Promise<void> {\n await palbeRequest<void>(this.rt, 'POST', MessagingPaths.groupRead(group.displayId), {\n body: { read_seq: upToServerSeq, read_epoch: group.currentEpoch, is_private: false },\n });\n }\n\n // ── Server-metadata cluster: notify scope (mute) + unread (HTTP) ──\n\n /** PUT `/v1/messaging/groups/{gid}/notify` — set the caller's per-(user,group) notify\n * scope (`'all'`|`'none'`). The server stores the opaque enum verbatim and stays blind.\n * Returns the server-echoed scope (fail-OPEN to `'all'` on an unknown value). */\n async setNotifyScope(group: MessagingGroup, scope: NotifyScope): Promise<NotifyScope> {\n const res = await palbeRequest<{ notify_scope: string }>(\n this.rt,\n 'PUT',\n MessagingPaths.groupNotify(group.displayId),\n { body: { notify_scope: scope } },\n );\n return res.notify_scope === 'none' ? 'none' : 'all';\n }\n\n /** GET `/v1/messaging/groups/{gid}/notify` — the caller's notify scope. An absent\n * server row / unknown value reads as `'all'` (fail-OPEN — never silently mutes). */\n async getNotifyScope(group: MessagingGroup): Promise<NotifyScope> {\n const res = await palbeRequest<{ notify_scope: string }>(\n this.rt,\n 'GET',\n MessagingPaths.groupNotify(group.displayId),\n );\n return res.notify_scope === 'none' ? 'none' : 'all';\n }\n\n /** GET `/v1/messaging/groups/{gid}/unread` — the caller's OWN unread view (opaque\n * server metadata). Maps the snake_case wire → the camelCase `UnreadView`. */\n async unread(group: MessagingGroup): Promise<UnreadView> {\n const res = await palbeRequest<{\n group_max_seq: number;\n last_read_seq: number;\n delivered_seq: number;\n unread_count: number;\n }>(this.rt, 'GET', MessagingPaths.groupUnread(group.displayId));\n return {\n groupMaxSeq: res.group_max_seq,\n lastReadSeq: res.last_read_seq,\n deliveredSeq: res.delivered_seq,\n unreadCount: res.unread_count,\n };\n }\n}\n\n/** mls-rs own-echo (\"message from self can't be processed\") or consumed-key\n * (\"key not available, invalid generation\") — both ACK-and-skip on the receiver. */\nfunction isOwnEchoOrConsumed(e: unknown): boolean {\n const msg = e instanceof Error ? e.message : String(e);\n return msg.includes('message from self') || msg.includes('key not available, invalid generation');\n}\n\n// keep GroupMessaging/ContentType referenced for the wire-gap declaration hook\nvoid GroupMessaging;\nvoid ContentType;\n","// The disappearing/TTL durable store — the web mirror of iOS `DisappearingStore`.\n//\n// Holds the crash-safe purge state for disappearing messages, sealed at rest by the\n// same durable KV (IndexedDB + WebCrypto AES-GCM in the browser) that seals the\n// message store, so it survives a cold launch / mid-purge crash.\n//\n// TWO SEPARATE persistent purge sets, two key namespaces that CANNOT collide:\n// (a) `tombstonedSeqs` — an INTEGER `server_seq` tombstone. Written FIRST (the purge\n// commit point), it drives transcript exclusion (rebuilt-from-store skips a\n// tombstoned seq) + redelivery drop (a redelivered tombstoned seq is dropped on\n// arrival). A mid-purge crash AFTER this write resurrects the tombstone on reload.\n// (b) `purgedClientMsgIds` — a STRING `client_msg_id` set. It drives the orphan-fold\n// resolver (`authorOfTarget → 'purged'`) so a late edit DROPs and a late delete\n// no-ops against a TTL-purged target.\n// The int seq and the string id live in distinct JSON fields; one is NEVER keyed off\n// the other (per the TTL global constraint).\n//\n// Anchors are WRITE-ONCE per `(device, clientMsgId)`: the first arrival captures the\n// monotonic/wall/boot anchor triple; a later re-arm for the same clientMsgId is a\n// no-op, so the deadline never resets.\n\nimport type { AnchorTriple } from './deadline-calculator.js';\nimport type { DurableKV } from './storage.js';\nimport { decodeUtf8, encodeUtf8 } from './util.js';\n\ninterface DisappearingRecord {\n tombstonedSeqs: number[];\n purgedClientMsgIds: string[];\n anchors: Record<string, AnchorTriple>;\n}\n\n/**\n * Per-chat IndexedDB-backed store for disappearing durable state (keyed by\n * `disappear:<rfcGroupId>`). Mirrors `SuppressionStore`'s DurableKV/JSON pattern.\n */\nexport class DisappearingStore {\n constructor(private readonly kv: DurableKV) {}\n\n private key(rfc: string): string {\n return `disappear:${rfc}`;\n }\n\n private async load(rfc: string): Promise<DisappearingRecord> {\n const raw = await this.kv.get(this.key(rfc));\n if (!raw) return { tombstonedSeqs: [], purgedClientMsgIds: [], anchors: {} };\n try {\n const r = JSON.parse(decodeUtf8(raw)) as Partial<DisappearingRecord>;\n return {\n tombstonedSeqs: r.tombstonedSeqs ?? [],\n purgedClientMsgIds: r.purgedClientMsgIds ?? [],\n anchors: r.anchors ?? {},\n };\n } catch {\n return { tombstonedSeqs: [], purgedClientMsgIds: [], anchors: {} };\n }\n }\n\n private async save(rfc: string, rec: DisappearingRecord): Promise<void> {\n await this.kv.set(this.key(rfc), encodeUtf8(JSON.stringify(rec)));\n }\n\n /** The persisted INTEGER `server_seq` tombstone set for a chat (transcript exclusion + redelivery drop). */\n async tombstonedSeqs(rfc: string): Promise<Set<number>> {\n return new Set((await this.load(rfc)).tombstonedSeqs);\n }\n\n /** The persisted STRING `client_msg_id` purge set for a chat (orphan-fold → `'purged'`). */\n async purgedClientMsgIds(rfc: string): Promise<Set<string>> {\n return new Set((await this.load(rfc)).purgedClientMsgIds);\n }\n\n /**\n * Tombstone-first commit point: the INTEGER seq and the STRING client_msg_id are\n * written together in ONE durable record. Idempotent (re-tombstoning the same seq /\n * id is a no-op). This write is THE purge commit — once it lands, a transcript rebuilt\n * from the store excludes the seq and a redelivery is dropped, even across a crash.\n */\n async tombstone(rfc: string, serverSeq: number, clientMsgId: string): Promise<void> {\n const rec = await this.load(rfc);\n if (!rec.tombstonedSeqs.includes(serverSeq)) rec.tombstonedSeqs.push(serverSeq);\n if (clientMsgId && !rec.purgedClientMsgIds.includes(clientMsgId)) {\n rec.purgedClientMsgIds.push(clientMsgId);\n }\n await this.save(rfc, rec);\n }\n\n /** The persisted write-once anchor triple for a `clientMsgId`, or null if none. */\n async anchor(rfc: string, clientMsgId: string): Promise<AnchorTriple | null> {\n return (await this.load(rfc)).anchors[clientMsgId] ?? null;\n }\n\n /** Write-once: a second call for the same `clientMsgId` is a no-op (deadline never resets). */\n async writeAnchorOnce(rfc: string, clientMsgId: string, a: AnchorTriple): Promise<void> {\n const rec = await this.load(rfc);\n if (rec.anchors[clientMsgId]) return;\n rec.anchors[clientMsgId] = a;\n await this.save(rfc, rec);\n }\n}\n","// Local decrypted-message history + the group catalog — the web mirror of iOS\n// MessageStore + GroupCatalog. MLS decrypt-once means a received message's key\n// is erased on first decrypt, so you can NEVER refetch+redecrypt it from the\n// server: the decrypted plaintext MUST be persisted locally (sealed at rest by\n// the durable KV — IndexedDB+WebCrypto-AES-GCM in the browser). The own-sent\n// copy is likewise the only durable form (the sender can't decrypt its own\n// ciphertext).\n\nimport type { MentionRange } from './group-messaging.js';\nimport type { DurableKV } from './storage.js';\nimport type { MessageDirection } from './types.js';\nimport { decodeUtf8, encodeUtf8 } from './util.js';\n\n/** A durable stored message (one row in the local sealed transcript). */\nexport interface StoredMessage {\n /** Stable across reloads: `<rfcGroupId>#<serverSeq>`. */\n id: string;\n direction: MessageDirection;\n text: string | null;\n senderDeviceId: string | null;\n epoch: number;\n serverSeq: number;\n /** ms since epoch. */\n at: number;\n /**\n * Client-minted idempotency id from the decoded envelope.\n * Optional for backward-compat: old persisted rows without this key\n * are treated as '' (no id) on read.\n */\n clientMsgId?: string;\n /**\n * The decoded reply reference, if the message was a reply.\n * Optional for backward-compat: old persisted rows without this key\n * are treated as null on read.\n */\n replyTo?: {\n clientMsgId: string;\n previewBody: string | null;\n previewAuthorUserId: string | null;\n previewKind: string;\n } | null;\n /**\n * The decoded envelope discriminator (`'text'`/`'reaction'`/…).\n * Optional for backward-compat: old persisted rows without this key are treated\n * as `'text'` (a non-reaction) on read, so a pre-reactions row never re-folds.\n */\n envelopeType?: string;\n /**\n * The decoded reaction payload, present only when `envelopeType === 'reaction'`.\n * Optional + backward-compat (old rows → undefined → no re-fold). Threading this\n * through the persisted shape is the reload-parity boundary: a reaction folded\n * LIVE re-folds onto its target after a cold launch.\n */\n reaction?: {\n targetClientMsgId: string;\n emoji: string;\n op: 'add' | 'remove';\n } | null;\n /**\n * The decoded edit payload, present only when `envelopeType === 'edit'`.\n * Optional + backward-compat (old rows → undefined → no re-fold). Threading this\n * through the persisted shape is the reload-parity boundary for edits: an edit\n * folded LIVE re-folds onto its target's text after a cold launch.\n */\n edit?: {\n targetClientMsgId: string;\n newText: string;\n /**\n * The edit's REPLACEMENT mention ranges (raw, un-normalized). Optional +\n * backward-compat (old rows → undefined). Threaded so an edited message's\n * mentions re-resolve from the WINNING edit's ranges after a reload (mentions T6).\n */\n bodyRanges?: MentionRange[] | null;\n } | null;\n /**\n * The decoded mention ranges on a TEXT/media bubble (raw, un-normalized — the\n * Chat/projection normalizes + resolves names). Present only on `'text'` rows that\n * carried `body_ranges`. Optional + backward-compat (old rows → undefined → no\n * mentions). Threading this through the persisted shape is the reload-parity\n * boundary for mentions: a mention surfaced LIVE re-resolves onto its bubble after a\n * cold launch (mentions T6, mirrors iOS T3).\n */\n bodyRanges?: MentionRange[] | null;\n /**\n * The decoded delete payload, present only when `envelopeType === 'delete'`.\n * Optional + backward-compat (old rows → undefined → no re-fold). Threading this\n * through the persisted shape is the reload-parity boundary for deletes: a\n * delete-for-everyone tombstone folded LIVE re-folds onto its target after a\n * cold launch (and never leaks as a blank bubble — the iOS-review CRITICAL\n * lesson: the projection MUST have a `.delete` branch).\n */\n delete?: {\n targetClientMsgId: string;\n scope: string;\n } | null;\n /**\n * The decoded per-message TTL spec (own-sent / decoded), present only on a text/media\n * row that carried an `expiry` (disappearing T10). Optional + backward-compat (old rows\n * → undefined → no re-arm). Threaded so an own-send TTL re-arms its purge after a cold\n * launch, and so the cold-launch `projectHistory` derives the message's effective TTL.\n */\n expiry?: {\n v: number;\n ttlSeconds: number;\n start: 'send' | 'read';\n senderSendTs: number | null;\n } | null;\n /**\n * The decoded `timer_set` payload, present only when `envelopeType === 'timer_set'`\n * (disappearing T10). Optional + backward-compat (old rows → undefined → no re-fold).\n * Threaded so the chat default disappearing timer re-folds on a cold launch (the\n * page-local `TimerFold` in `projectHistory`). `ttlSeconds === null` disables the default.\n */\n timer?: {\n ttlSeconds: number | null;\n start: 'send' | 'read';\n } | null;\n}\n\n/** Persist decrypted messages per group, newest-last, deduped by id. */\nexport class MessageStore {\n constructor(private readonly kv: DurableKV) {}\n\n private key(rfcGroupId: string): string {\n return `msgs:${rfcGroupId}`;\n }\n\n /** Append (idempotent on id) and persist the group's transcript. */\n async append(rfcGroupId: string, msg: StoredMessage): Promise<void> {\n const list = await this.load(rfcGroupId);\n if (list.some((m) => m.id === msg.id)) return; // dedup by stable id\n list.push(msg);\n list.sort((a, b) => a.serverSeq - b.serverSeq);\n await this.save(rfcGroupId, list);\n }\n\n /** True if a message id is already durable (the consumed-key recovery check). */\n async contains(rfcGroupId: string, id: string): Promise<boolean> {\n const list = await this.load(rfcGroupId);\n return list.some((m) => m.id === id);\n }\n\n /**\n * Read history for a group, newest-last. `before` pages older: returns up to\n * `limit` rows with serverSeq < before. nil `before` returns the newest page.\n */\n async history(rfcGroupId: string, limit: number, before?: number): Promise<StoredMessage[]> {\n const list = await this.load(rfcGroupId);\n const filtered = before == null ? list : list.filter((m) => m.serverSeq < before);\n return filtered.slice(Math.max(0, filtered.length - limit));\n }\n\n async load(rfcGroupId: string): Promise<StoredMessage[]> {\n const raw = await this.kv.get(this.key(rfcGroupId));\n if (!raw) return [];\n try {\n return JSON.parse(decodeUtf8(raw)) as StoredMessage[];\n } catch {\n return [];\n }\n }\n\n private async save(rfcGroupId: string, list: StoredMessage[]): Promise<void> {\n await this.kv.set(this.key(rfcGroupId), encodeUtf8(JSON.stringify(list)));\n }\n\n async wipe(): Promise<void> {\n for (const k of await this.kv.keys('msgs:')) await this.kv.delete(k);\n }\n}\n\n/** A persisted group entry (the catalog row — survives reloads). */\nexport interface CatalogGroup {\n displayId: string;\n rfcGroupId: string;\n currentEpoch: number;\n ownerUserId: string | null;\n directKey: string | null;\n name: string | null;\n}\n\n/** Durable list of known groups (the web mirror of iOS GroupCatalog), so a\n * reload hydrates the chat list + lets queue rows route before the first pump. */\nexport class GroupCatalog {\n constructor(private readonly kv: DurableKV) {}\n\n async upsert(g: CatalogGroup): Promise<void> {\n await this.kv.set(`cat:${g.rfcGroupId}`, encodeUtf8(JSON.stringify(g)));\n }\n\n async remove(rfcGroupId: string): Promise<void> {\n await this.kv.delete(`cat:${rfcGroupId}`);\n }\n\n async all(): Promise<CatalogGroup[]> {\n const keys = await this.kv.keys('cat:');\n const out: CatalogGroup[] = [];\n for (const k of keys) {\n const raw = await this.kv.get(k);\n if (!raw) continue;\n try {\n out.push(JSON.parse(decodeUtf8(raw)) as CatalogGroup);\n } catch {\n // skip a corrupt row\n }\n }\n return out;\n }\n\n async wipe(): Promise<void> {\n for (const k of await this.kv.keys('cat:')) await this.kv.delete(k);\n }\n}\n","// The self-elevation dedup store — the web mirror of iOS `MentionElevationStore`.\n//\n// WHY THIS EXISTS (mentions design §\"Self-elevation idempotency\"): when an INCOMING\n// message mentions THIS device's user (and the sender isn't me, and it isn't an edit),\n// the Chat fires `onMentionElevation` exactly ONCE. Re-delivery (a reconnect re-pump),\n// a backfill, and a cold-launch relaunch must NOT re-fire. The in-memory set gates a\n// session; this persisted set makes the dedup survive reload — a re-delivered mention\n// after a cold launch finds its key already set and is a no-op.\n//\n// POSTURE — identical to `SuppressionStore`: a persisted per-chat `Set<elevationKey>`\n// (keyed by rfcGroupId) where `elevationKey` is `\"<selfUserId>|<idPart>\"` (idPart =\n// the mention message's client_msg_id, or `seq:<serverSeq>` for a legacy message). The\n// `selfUserId` scoping means a multi-account device never crosses dedup sets. Sealed at\n// rest by the same durable KV (IndexedDB + WebCrypto AES-GCM in the browser) that seals\n// the message store — so \"which messages buzzed me\" stays private (a soft mention-graph\n// signal) and the dedup survives a cold launch.\n\nimport type { DurableKV } from './storage.js';\nimport { decodeUtf8, encodeUtf8 } from './util.js';\n\n/** Persist a per-chat self-elevation dedup key set (keyed by rfcGroupId). */\nexport class MentionElevationStore {\n constructor(private readonly kv: DurableKV) {}\n\n private key(rfcGroupId: string): string {\n return `elev:${rfcGroupId}`;\n }\n\n /** Load the persisted elevation keys for a chat (empty array if none). */\n async load(rfcGroupId: string): Promise<string[]> {\n const raw = await this.kv.get(this.key(rfcGroupId));\n if (!raw) return [];\n try {\n const parsed = JSON.parse(decodeUtf8(raw));\n return Array.isArray(parsed) ? (parsed as string[]) : [];\n } catch {\n return [];\n }\n }\n\n /** Persist the full elevation key set for a chat (deterministic, deduped order). */\n async save(rfcGroupId: string, keys: string[]): Promise<void> {\n const sorted = [...new Set(keys)].sort();\n await this.kv.set(this.key(rfcGroupId), encodeUtf8(JSON.stringify(sorted)));\n }\n\n async wipe(): Promise<void> {\n for (const k of await this.kv.keys('elev:')) await this.kv.delete(k);\n }\n}\n","/**\n * Browser binding of [`PalbeMlsClient`]. Holds the same `Arc<PalbeMlsClient>`\n * the native FFI would; every method delegates to the engine.\n */\nexport class MlsClient {\n static __wrap(ptr) {\n const obj = Object.create(MlsClient.prototype);\n obj.__wbg_ptr = ptr;\n MlsClientFinalization.register(obj, obj.__wbg_ptr, obj);\n return obj;\n }\n __destroy_into_raw() {\n const ptr = this.__wbg_ptr;\n this.__wbg_ptr = 0;\n MlsClientFinalization.unregister(this);\n return ptr;\n }\n free() {\n const ptr = this.__destroy_into_raw();\n wasm.__wbg_mlsclient_free(ptr, 0);\n }\n /**\n * The MLS Capabilities wire bytes (the keydir's `capabilities`).\n * @returns {Uint8Array}\n */\n capabilities() {\n const ret = wasm.mlsclient_capabilities(this.__wbg_ptr);\n var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();\n wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);\n return v1;\n }\n /**\n * Create a new group at epoch 0. `groupId` `undefined`/`null` lets mls-rs\n * mint a globally-unique id (the rfc_group_id routing key).\n * @param {Uint8Array | null} [group_id]\n * @returns {MlsGroup}\n */\n createGroup(group_id) {\n var ptr0 = isLikeNone(group_id) ? 0 : passArray8ToWasm0(group_id, wasm.__wbindgen_malloc);\n var len0 = WASM_VECTOR_LEN;\n const ret = wasm.mlsclient_createGroup(this.__wbg_ptr, ptr0, len0);\n if (ret[2]) {\n throw takeFromExternrefTable0(ret[1]);\n }\n return MlsGroup.__wrap(ret[0]);\n }\n /**\n * The MLS Credential wire bytes (the keydir's `credential`).\n * @returns {Uint8Array}\n */\n credential() {\n const ret = wasm.mlsclient_credential(this.__wbg_ptr);\n var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();\n wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);\n return v1;\n }\n /**\n * The MLS-encoded enrollment material `{ signatureKey, credential,\n * capabilities }` (byte fields as arrays-of-numbers — wrap in Uint8Array as\n * needed). Base64 these into the SP-0 keydir enroll body.\n * @returns {any}\n */\n enrollmentMaterial() {\n const ret = wasm.mlsclient_enrollmentMaterial(this.__wbg_ptr);\n if (ret[2]) {\n throw takeFromExternrefTable0(ret[1]);\n }\n return takeFromExternrefTable0(ret[0]);\n }\n /**\n * Mint a one-use KeyPackage (RAW RFC 9420 §10 bytes) — uploaded to the\n * keydir, consumed by an adder's `addMember`.\n * @returns {Uint8Array}\n */\n generateKeyPackage() {\n const ret = wasm.mlsclient_generateKeyPackage(this.__wbg_ptr);\n if (ret[3]) {\n throw takeFromExternrefTable0(ret[2]);\n }\n var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();\n wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);\n return v1;\n }\n /**\n * Join a group from a Welcome (opaque MLSMessage bytes).\n * @param {Uint8Array} welcome\n * @returns {MlsGroup}\n */\n joinGroup(welcome) {\n const ptr0 = passArray8ToWasm0(welcome, wasm.__wbindgen_malloc);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.mlsclient_joinGroup(this.__wbg_ptr, ptr0, len0);\n if (ret[2]) {\n throw takeFromExternrefTable0(ret[1]);\n }\n return MlsGroup.__wrap(ret[0]);\n }\n /**\n * Re-hydrate a previously-persisted group by its `groupId` (restart-safe\n * reload of an already-joined group).\n * @param {Uint8Array} group_id\n * @returns {MlsGroup}\n */\n loadGroup(group_id) {\n const ptr0 = passArray8ToWasm0(group_id, wasm.__wbindgen_malloc);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.mlsclient_loadGroup(this.__wbg_ptr, ptr0, len0);\n if (ret[2]) {\n throw takeFromExternrefTable0(ret[1]);\n }\n return MlsGroup.__wrap(ret[0]);\n }\n /**\n * The serialized STABLE signature keypair (secret-bearing). Persist sealed\n * and pass back as `signatureKeypair` on restart so the device keeps the\n * SAME MLS signing identity.\n * @returns {Uint8Array}\n */\n signatureKeypair() {\n const ret = wasm.mlsclient_signatureKeypair(this.__wbg_ptr);\n var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();\n wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);\n return v1;\n }\n /**\n * The device's STABLE signature PUBLIC key bytes (the keydir's\n * `signature_key`).\n * @returns {Uint8Array}\n */\n signaturePublicKey() {\n const ret = wasm.mlsclient_signaturePublicKey(this.__wbg_ptr);\n var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();\n wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);\n return v1;\n }\n}\nif (Symbol.dispose) MlsClient.prototype[Symbol.dispose] = MlsClient.prototype.free;\n\n/**\n * Browser binding of [`PalbeMlsGroup`].\n */\nexport class MlsGroup {\n static __wrap(ptr) {\n const obj = Object.create(MlsGroup.prototype);\n obj.__wbg_ptr = ptr;\n MlsGroupFinalization.register(obj, obj.__wbg_ptr, obj);\n return obj;\n }\n __destroy_into_raw() {\n const ptr = this.__wbg_ptr;\n this.__wbg_ptr = 0;\n MlsGroupFinalization.unregister(this);\n return ptr;\n }\n free() {\n const ptr = this.__destroy_into_raw();\n wasm.__wbg_mlsgroup_free(ptr, 0);\n }\n /**\n * Stage a member add. Returns `{ commit, welcome?, addedLeafIndices }`\n * (byte fields as arrays-of-numbers). The group is PENDING after this.\n * @param {Uint8Array} key_package_msg\n * @returns {any}\n */\n addMember(key_package_msg) {\n const ptr0 = passArray8ToWasm0(key_package_msg, wasm.__wbindgen_malloc);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.mlsgroup_addMember(this.__wbg_ptr, ptr0, len0);\n if (ret[2]) {\n throw takeFromExternrefTable0(ret[1]);\n }\n return takeFromExternrefTable0(ret[0]);\n }\n /**\n * Apply the staged commit (advances the local epoch). Call ONLY after the\n * server accepted the commit.\n */\n applyPendingCommit() {\n const ret = wasm.mlsgroup_applyPendingCommit(this.__wbg_ptr);\n if (ret[1]) {\n throw takeFromExternrefTable0(ret[0]);\n }\n }\n /**\n * Discard the staged commit without advancing (the 409-rebase path).\n */\n clearPendingCommit() {\n wasm.mlsgroup_clearPendingCommit(this.__wbg_ptr);\n }\n /**\n * Stage a SINGLE atomic add+remove commit. `addKeyPackages` is an\n * `Array<Uint8Array>` of one-use key packages; `removeMemberIds` an\n * `Array<Uint8Array>` of member identity bytes. Returns the same\n * `AddResult` shape as `addMember`. PENDING after this.\n * @param {Uint8Array[]} add_key_packages\n * @param {Uint8Array[]} remove_member_ids\n * @returns {any}\n */\n commitChanges(add_key_packages, remove_member_ids) {\n const ptr0 = passArrayJsValueToWasm0(add_key_packages, wasm.__wbindgen_malloc);\n const len0 = WASM_VECTOR_LEN;\n const ptr1 = passArrayJsValueToWasm0(remove_member_ids, wasm.__wbindgen_malloc);\n const len1 = WASM_VECTOR_LEN;\n const ret = wasm.mlsgroup_commitChanges(this.__wbg_ptr, ptr0, len0, ptr1, len1);\n if (ret[2]) {\n throw takeFromExternrefTable0(ret[1]);\n }\n return takeFromExternrefTable0(ret[0]);\n }\n /**\n * The current (applied) epoch as a JS BigInt (u64 exceeds safe-integer).\n * @returns {bigint}\n */\n currentEpoch() {\n const ret = wasm.mlsgroup_currentEpoch(this.__wbg_ptr);\n return BigInt.asUintN(64, ret);\n }\n /**\n * Inspect an incoming Commit and return the membership delta it WOULD\n * effect WITHOUT applying it (the reconciliation gate's crypto-truth).\n * Returns `{ kind, addedIdentities, removedIdentities }`.\n * @param {Uint8Array} commit_msg\n * @returns {any}\n */\n describeIncomingCommit(commit_msg) {\n const ptr0 = passArray8ToWasm0(commit_msg, wasm.__wbindgen_malloc);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.mlsgroup_describeIncomingCommit(this.__wbg_ptr, ptr0, len0);\n if (ret[2]) {\n throw takeFromExternrefTable0(ret[1]);\n }\n return takeFromExternrefTable0(ret[0]);\n }\n /**\n * Encrypt an application message at the current epoch. Returns the opaque\n * MLSMessage bytes (PrivateMessage).\n * @param {Uint8Array} plaintext\n * @returns {Uint8Array}\n */\n encryptApplicationMessage(plaintext) {\n const ptr0 = passArray8ToWasm0(plaintext, wasm.__wbindgen_malloc);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.mlsgroup_encryptApplicationMessage(this.__wbg_ptr, ptr0, len0);\n if (ret[3]) {\n throw takeFromExternrefTable0(ret[2]);\n }\n var v2 = getArrayU8FromWasm0(ret[0], ret[1]).slice();\n wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);\n return v2;\n }\n /**\n * Derive the group's current-epoch exporter secret (RFC 9420 §8.5) — the SAME\n * engine call iOS uses (uniffi `exportSecret`), so a cross-platform call/group\n * derives a byte-identical key. The SP-4 web call sets this as the LiveKit\n * frame key (server + SFU stay blind); also group-name/media sealing.\n * @param {Uint8Array} label\n * @param {Uint8Array} context\n * @param {bigint} len\n * @returns {Uint8Array}\n */\n exportSecret(label, context, len) {\n const ptr0 = passArray8ToWasm0(label, wasm.__wbindgen_malloc);\n const len0 = WASM_VECTOR_LEN;\n const ptr1 = passArray8ToWasm0(context, wasm.__wbindgen_malloc);\n const len1 = WASM_VECTOR_LEN;\n const ret = wasm.mlsgroup_exportSecret(this.__wbg_ptr, ptr0, len0, ptr1, len1, len);\n if (ret[3]) {\n throw takeFromExternrefTable0(ret[2]);\n }\n var v3 = getArrayU8FromWasm0(ret[0], ret[1]).slice();\n wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);\n return v3;\n }\n /**\n * The MLS group id (the rfc_group_id routing key / storage key).\n * @returns {Uint8Array}\n */\n groupId() {\n const ret = wasm.mlsgroup_groupId(this.__wbg_ptr);\n var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();\n wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);\n return v1;\n }\n /**\n * True while a staged (unapplied) commit exists.\n * @returns {boolean}\n */\n hasPendingCommit() {\n const ret = wasm.mlsgroup_hasPendingCommit(this.__wbg_ptr);\n return ret !== 0;\n }\n /**\n * Process an incoming MLSMessage and apply its effect. Returns a\n * `ReceivedMessage` plain object (serde-tagged enum): e.g.\n * `{ Application: { sender, data } }`, `{ CommitApplied: { epoch,\n * removedSelf } }`, `\"Proposal\"`, or `\"Other\"`.\n * @param {Uint8Array} message\n * @returns {any}\n */\n processIncomingMessage(message) {\n const ptr0 = passArray8ToWasm0(message, wasm.__wbindgen_malloc);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.mlsgroup_processIncomingMessage(this.__wbg_ptr, ptr0, len0);\n if (ret[2]) {\n throw takeFromExternrefTable0(ret[1]);\n }\n return takeFromExternrefTable0(ret[0]);\n }\n /**\n * Stage a member remove (by the member's identity bytes). Returns\n * `{ commit }`. PENDING after this.\n * @param {Uint8Array} member_id\n * @returns {any}\n */\n removeMember(member_id) {\n const ptr0 = passArray8ToWasm0(member_id, wasm.__wbindgen_malloc);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.mlsgroup_removeMember(this.__wbg_ptr, ptr0, len0);\n if (ret[2]) {\n throw takeFromExternrefTable0(ret[1]);\n }\n return takeFromExternrefTable0(ret[0]);\n }\n /**\n * Persist the current group state through the JS group storage callback.\n */\n writeToStorage() {\n const ret = wasm.mlsgroup_writeToStorage(this.__wbg_ptr);\n if (ret[1]) {\n throw takeFromExternrefTable0(ret[0]);\n }\n }\n}\nif (Symbol.dispose) MlsGroup.prototype[Symbol.dispose] = MlsGroup.prototype.free;\n\n/**\n * Mint a device MLS client (browser binding of [`engine::generate_client`]).\n *\n * - `id`: the member identity bytes (the SP-0 device id `mdv_`).\n * - `group_storage`: a JS object implementing `IGroupStateStorage`.\n * - `key_package_storage`: a JS object implementing `IKeyPackageStorage`.\n * - `signature_keypair`: `undefined`/`null` on first run (a fresh keypair is\n * minted), or the previously-persisted serialized secret-key bytes on restart.\n * @param {Uint8Array} id\n * @param {IGroupStateStorage} group_storage\n * @param {IKeyPackageStorage} key_package_storage\n * @param {Uint8Array | null} [signature_keypair]\n * @returns {MlsClient}\n */\nexport function generateClient(id, group_storage, key_package_storage, signature_keypair) {\n const ptr0 = passArray8ToWasm0(id, wasm.__wbindgen_malloc);\n const len0 = WASM_VECTOR_LEN;\n var ptr1 = isLikeNone(signature_keypair) ? 0 : passArray8ToWasm0(signature_keypair, wasm.__wbindgen_malloc);\n var len1 = WASM_VECTOR_LEN;\n const ret = wasm.generateClient(ptr0, len0, group_storage, key_package_storage, ptr1, len1);\n if (ret[2]) {\n throw takeFromExternrefTable0(ret[1]);\n }\n return MlsClient.__wrap(ret[0]);\n}\n\n/**\n * Install the browser console panic hook so a Rust panic shows a readable\n * stack in the devtools console instead of an opaque `unreachable`. Idempotent;\n * the TS facade calls this once at module init.\n */\nexport function setPanicHook() {\n wasm.setPanicHook();\n}\nexport function __wbg_BigInt_231999d28f899902() { return handleError(function (arg0) {\n const ret = BigInt(arg0);\n return ret;\n}, arguments); }\nexport function __wbg_Error_fdd633d4bb5dd76a(arg0, arg1) {\n const ret = Error(getStringFromWasm0(arg0, arg1));\n return ret;\n}\nexport function __wbg_String_8564e559799eccda(arg0, arg1) {\n const ret = String(arg1);\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);\n const len1 = WASM_VECTOR_LEN;\n getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);\n getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);\n}\nexport function __wbg___wbindgen_bigint_get_as_i64_d9e915702856f831(arg0, arg1) {\n const v = arg1;\n const ret = typeof(v) === 'bigint' ? v : undefined;\n getDataViewMemory0().setBigInt64(arg0 + 8 * 1, isLikeNone(ret) ? BigInt(0) : ret, true);\n getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true);\n}\nexport function __wbg___wbindgen_debug_string_8a447059637473e2(arg0, arg1) {\n const ret = debugString(arg1);\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);\n const len1 = WASM_VECTOR_LEN;\n getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);\n getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);\n}\nexport function __wbg___wbindgen_is_function_acc5528be2b923f2(arg0) {\n const ret = typeof(arg0) === 'function';\n return ret;\n}\nexport function __wbg___wbindgen_is_null_6d937fbfb6478470(arg0) {\n const ret = arg0 === null;\n return ret;\n}\nexport function __wbg___wbindgen_is_object_0beba4a1980d3eea(arg0) {\n const val = arg0;\n const ret = typeof(val) === 'object' && val !== null;\n return ret;\n}\nexport function __wbg___wbindgen_is_string_1fca8072260dd261(arg0) {\n const ret = typeof(arg0) === 'string';\n return ret;\n}\nexport function __wbg___wbindgen_is_undefined_721f8decd50c87a3(arg0) {\n const ret = arg0 === undefined;\n return ret;\n}\nexport function __wbg___wbindgen_jsval_eq_4e8c38722cb8ff51(arg0, arg1) {\n const ret = arg0 === arg1;\n return ret;\n}\nexport function __wbg___wbindgen_memory_9751d9a3017e7c25() {\n const ret = wasm.memory;\n return ret;\n}\nexport function __wbg___wbindgen_number_get_1cc01dd708740256(arg0, arg1) {\n const obj = arg1;\n const ret = typeof(obj) === 'number' ? obj : undefined;\n getDataViewMemory0().setFloat64(arg0 + 8 * 1, isLikeNone(ret) ? 0 : ret, true);\n getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true);\n}\nexport function __wbg___wbindgen_string_get_71bb4348194e31f0(arg0, arg1) {\n const obj = arg1;\n const ret = typeof(obj) === 'string' ? obj : undefined;\n var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);\n var len1 = WASM_VECTOR_LEN;\n getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);\n getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);\n}\nexport function __wbg___wbindgen_throw_ea4887a5f8f9a9db(arg0, arg1) {\n throw new Error(getStringFromWasm0(arg0, arg1));\n}\nexport function __wbg_buffer_7447b9cc2267a9e5(arg0) {\n const ret = arg0.buffer;\n return ret;\n}\nexport function __wbg_call_67f43c91d09298f2() { return handleError(function (arg0, arg1, arg2) {\n const ret = arg0.call(arg1, arg2);\n return ret;\n}, arguments); }\nexport function __wbg_call_b51415974987aa44() { return handleError(function (arg0, arg1) {\n const ret = arg0.call(arg1);\n return ret;\n}, arguments); }\nexport function __wbg_crypto_38df2bab126b63dc(arg0) {\n const ret = arg0.crypto;\n return ret;\n}\nexport function __wbg_delete_21fafcbc7bb82c85() { return handleError(function (arg0, arg1) {\n arg0.delete(arg1);\n}, arguments); }\nexport function __wbg_epoch_15c000ffe3004541() { return handleError(function (arg0, arg1, arg2) {\n const ret = arg0.epoch(arg1, BigInt.asUintN(64, arg2));\n return ret;\n}, arguments); }\nexport function __wbg_error_a6fa202b58aa1cd3(arg0, arg1) {\n let deferred0_0;\n let deferred0_1;\n try {\n deferred0_0 = arg0;\n deferred0_1 = arg1;\n console.error(getStringFromWasm0(arg0, arg1));\n } finally {\n wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);\n }\n}\nexport function __wbg_getRandomValues_c44a50d8cfdaebeb() { return handleError(function (arg0, arg1) {\n arg0.getRandomValues(arg1);\n}, arguments); }\nexport function __wbg_get_615446055a48103f() { return handleError(function (arg0, arg1) {\n const ret = arg0.get(arg1);\n return ret;\n}, arguments); }\nexport function __wbg_globalThis_6d268067835e6709() { return handleError(function () {\n const ret = globalThis.globalThis;\n return ret;\n}, arguments); }\nexport function __wbg_global_3fe6c6c8ad6e6fb2() { return handleError(function () {\n const ret = global.global;\n return ret;\n}, arguments); }\nexport function __wbg_insert_2d611f2bf9b60fee() { return handleError(function (arg0, arg1, arg2) {\n arg0.insert(arg1, arg2);\n}, arguments); }\nexport function __wbg_instanceof_Error_38f854307ecab4ce(arg0) {\n let result;\n try {\n result = arg0 instanceof Error;\n } catch (_) {\n result = false;\n }\n const ret = result;\n return ret;\n}\nexport function __wbg_length_c552db98817b9523(arg0) {\n const ret = arg0.length;\n return ret;\n}\nexport function __wbg_maxEpochId_0b176768b2f352fb() { return handleError(function (arg0, arg1) {\n const ret = arg0.maxEpochId(arg1);\n return ret;\n}, arguments); }\nexport function __wbg_message_a05fcf872473ffc5(arg0) {\n const ret = arg0.message;\n return ret;\n}\nexport function __wbg_msCrypto_bd5a034af96bcba6(arg0) {\n const ret = arg0.msCrypto;\n return ret;\n}\nexport function __wbg_new_227d7c05414eb861() {\n const ret = new Error();\n return ret;\n}\nexport function __wbg_new_32de5cbf49ca7dcb(arg0) {\n const ret = new Uint8Array(arg0);\n return ret;\n}\nexport function __wbg_new_364c96143b8f3496() {\n const ret = new Object();\n return ret;\n}\nexport function __wbg_new_b8b71ca8104fc178(arg0, arg1) {\n const ret = new Error(getStringFromWasm0(arg0, arg1));\n return ret;\n}\nexport function __wbg_new_d9762fd75876aafe() {\n const ret = new Array();\n return ret;\n}\nexport function __wbg_new_no_args_4010ad257320fa4f(arg0, arg1) {\n const ret = new Function(getStringFromWasm0(arg0, arg1));\n return ret;\n}\nexport function __wbg_new_with_byte_offset_and_length_8b21e3b1308deb48(arg0, arg1, arg2) {\n const ret = new Uint8Array(arg0, arg1 >>> 0, arg2 >>> 0);\n return ret;\n}\nexport function __wbg_new_with_length_5fdafe029be917a5(arg0) {\n const ret = new Uint8Array(arg0 >>> 0);\n return ret;\n}\nexport function __wbg_node_84ea875411254db1(arg0) {\n const ret = arg0.node;\n return ret;\n}\nexport function __wbg_process_44c7a14e11e9f69e(arg0) {\n const ret = arg0.process;\n return ret;\n}\nexport function __wbg_push_1303ce035391aed3(arg0, arg1) {\n const ret = arg0.push(arg1);\n return ret;\n}\nexport function __wbg_randomFillSync_6c25eac9869eb53c() { return handleError(function (arg0, arg1) {\n arg0.randomFillSync(arg1);\n}, arguments); }\nexport function __wbg_require_b4edbdcf3e2a1ef0() { return handleError(function () {\n const ret = module.require;\n return ret;\n}, arguments); }\nexport function __wbg_self_1035a7cbd1b0d959() { return handleError(function () {\n const ret = self.self;\n return ret;\n}, arguments); }\nexport function __wbg_set_047d1ea37bb67c19(arg0, arg1, arg2) {\n arg0.set(arg1, arg2 >>> 0);\n}\nexport function __wbg_set_1ddc4b8cd44d0da4() { return handleError(function (arg0, arg1, arg2) {\n const ret = Reflect.set(arg0, arg1, arg2);\n return ret;\n}, arguments); }\nexport function __wbg_set_28cba565792ec75f(arg0, arg1, arg2) {\n arg0[arg1 >>> 0] = arg2;\n}\nexport function __wbg_set_6be42768c690e380(arg0, arg1, arg2) {\n arg0[arg1] = arg2;\n}\nexport function __wbg_set_name_b4c29a3a72ebbddc(arg0, arg1, arg2) {\n arg0.name = getStringFromWasm0(arg1, arg2);\n}\nexport function __wbg_stack_3b0d974bbf31e44f(arg0, arg1) {\n const ret = arg1.stack;\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);\n const len1 = WASM_VECTOR_LEN;\n getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);\n getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);\n}\nexport function __wbg_state_bd20456c0f3efbc9() { return handleError(function (arg0, arg1) {\n const ret = arg0.state(arg1);\n return ret;\n}, arguments); }\nexport function __wbg_subarray_e0162dcdea48eb3a(arg0, arg1, arg2) {\n const ret = arg0.subarray(arg1 >>> 0, arg2 >>> 0);\n return ret;\n}\nexport function __wbg_versions_276b2795b1c6a219(arg0) {\n const ret = arg0.versions;\n return ret;\n}\nexport function __wbg_window_9c17850b5e99c0ab() { return handleError(function () {\n const ret = window.window;\n return ret;\n}, arguments); }\nexport function __wbg_write_56b1cf5bb0e780d6() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {\n arg0.write(arg1, arg2, arg3, arg4);\n}, arguments); }\nexport function __wbindgen_cast_0000000000000001(arg0) {\n // Cast intrinsic for `F64 -> Externref`.\n const ret = arg0;\n return ret;\n}\nexport function __wbindgen_cast_0000000000000002(arg0, arg1) {\n // Cast intrinsic for `Ref(String) -> Externref`.\n const ret = getStringFromWasm0(arg0, arg1);\n return ret;\n}\nexport function __wbindgen_cast_0000000000000003(arg0) {\n // Cast intrinsic for `U64 -> Externref`.\n const ret = BigInt.asUintN(64, arg0);\n return ret;\n}\nexport function __wbindgen_init_externref_table() {\n const table = wasm.__wbindgen_externrefs;\n const offset = table.grow(4);\n table.set(0, undefined);\n table.set(offset + 0, undefined);\n table.set(offset + 1, null);\n table.set(offset + 2, true);\n table.set(offset + 3, false);\n}\nconst MlsClientFinalization = (typeof FinalizationRegistry === 'undefined')\n ? { register: () => {}, unregister: () => {} }\n : new FinalizationRegistry(ptr => wasm.__wbg_mlsclient_free(ptr, 1));\nconst MlsGroupFinalization = (typeof FinalizationRegistry === 'undefined')\n ? { register: () => {}, unregister: () => {} }\n : new FinalizationRegistry(ptr => wasm.__wbg_mlsgroup_free(ptr, 1));\n\nfunction addToExternrefTable0(obj) {\n const idx = wasm.__externref_table_alloc();\n wasm.__wbindgen_externrefs.set(idx, obj);\n return idx;\n}\n\nfunction debugString(val) {\n // primitive types\n const type = typeof val;\n if (type == 'number' || type == 'boolean' || val == null) {\n return `${val}`;\n }\n if (type == 'string') {\n return `\"${val}\"`;\n }\n if (type == 'symbol') {\n const description = val.description;\n if (description == null) {\n return 'Symbol';\n } else {\n return `Symbol(${description})`;\n }\n }\n if (type == 'function') {\n const name = val.name;\n if (typeof name == 'string' && name.length > 0) {\n return `Function(${name})`;\n } else {\n return 'Function';\n }\n }\n // objects\n if (Array.isArray(val)) {\n const length = val.length;\n let debug = '[';\n if (length > 0) {\n debug += debugString(val[0]);\n }\n for(let i = 1; i < length; i++) {\n debug += ', ' + debugString(val[i]);\n }\n debug += ']';\n return debug;\n }\n // Test for built-in\n const builtInMatches = /\\[object ([^\\]]+)\\]/.exec(toString.call(val));\n let className;\n if (builtInMatches && builtInMatches.length > 1) {\n className = builtInMatches[1];\n } else {\n // Failed to match the standard '[object ClassName]'\n return toString.call(val);\n }\n if (className == 'Object') {\n // we're a user defined class or Object\n // JSON.stringify avoids problems with cycles, and is generally much\n // easier than looping through ownProperties of `val`.\n try {\n return 'Object(' + JSON.stringify(val) + ')';\n } catch (_) {\n return 'Object';\n }\n }\n // errors\n if (val instanceof Error) {\n return `${val.name}: ${val.message}\\n${val.stack}`;\n }\n // TODO we could test for more things here, like `Set`s and `Map`s.\n return className;\n}\n\nfunction getArrayU8FromWasm0(ptr, len) {\n ptr = ptr >>> 0;\n return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len);\n}\n\nlet cachedDataViewMemory0 = null;\nfunction getDataViewMemory0() {\n if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {\n cachedDataViewMemory0 = new DataView(wasm.memory.buffer);\n }\n return cachedDataViewMemory0;\n}\n\nfunction getStringFromWasm0(ptr, len) {\n return decodeText(ptr >>> 0, len);\n}\n\nlet cachedUint8ArrayMemory0 = null;\nfunction getUint8ArrayMemory0() {\n if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {\n cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);\n }\n return cachedUint8ArrayMemory0;\n}\n\nfunction handleError(f, args) {\n try {\n return f.apply(this, args);\n } catch (e) {\n const idx = addToExternrefTable0(e);\n wasm.__wbindgen_exn_store(idx);\n }\n}\n\nfunction isLikeNone(x) {\n return x === undefined || x === null;\n}\n\nfunction passArray8ToWasm0(arg, malloc) {\n const ptr = malloc(arg.length * 1, 1) >>> 0;\n getUint8ArrayMemory0().set(arg, ptr / 1);\n WASM_VECTOR_LEN = arg.length;\n return ptr;\n}\n\nfunction passArrayJsValueToWasm0(array, malloc) {\n const ptr = malloc(array.length * 4, 4) >>> 0;\n for (let i = 0; i < array.length; i++) {\n const add = addToExternrefTable0(array[i]);\n getDataViewMemory0().setUint32(ptr + 4 * i, add, true);\n }\n WASM_VECTOR_LEN = array.length;\n return ptr;\n}\n\nfunction passStringToWasm0(arg, malloc, realloc) {\n if (realloc === undefined) {\n const buf = cachedTextEncoder.encode(arg);\n const ptr = malloc(buf.length, 1) >>> 0;\n getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);\n WASM_VECTOR_LEN = buf.length;\n return ptr;\n }\n\n let len = arg.length;\n let ptr = malloc(len, 1) >>> 0;\n\n const mem = getUint8ArrayMemory0();\n\n let offset = 0;\n\n for (; offset < len; offset++) {\n const code = arg.charCodeAt(offset);\n if (code > 0x7F) break;\n mem[ptr + offset] = code;\n }\n if (offset !== len) {\n if (offset !== 0) {\n arg = arg.slice(offset);\n }\n ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;\n const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);\n const ret = cachedTextEncoder.encodeInto(arg, view);\n\n offset += ret.written;\n ptr = realloc(ptr, len, offset, 1) >>> 0;\n }\n\n WASM_VECTOR_LEN = offset;\n return ptr;\n}\n\nfunction takeFromExternrefTable0(idx) {\n const value = wasm.__wbindgen_externrefs.get(idx);\n wasm.__externref_table_dealloc(idx);\n return value;\n}\n\nlet cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });\ncachedTextDecoder.decode();\nconst MAX_SAFARI_DECODE_BYTES = 2146435072;\nlet numBytesDecoded = 0;\nfunction decodeText(ptr, len) {\n numBytesDecoded += len;\n if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) {\n cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });\n cachedTextDecoder.decode();\n numBytesDecoded = len;\n }\n return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));\n}\n\nconst cachedTextEncoder = new TextEncoder();\n\nif (!('encodeInto' in cachedTextEncoder)) {\n cachedTextEncoder.encodeInto = function (arg, view) {\n const buf = cachedTextEncoder.encode(arg);\n view.set(buf);\n return {\n read: arg.length,\n written: buf.length\n };\n };\n}\n\nlet WASM_VECTOR_LEN = 0;\n\n\nlet wasm;\nexport function __wbg_set_wasm(val) {\n wasm = val;\n}\n","\nexport function date_now() {\n return Date.now();\n}","// Portable WASM loader for the palbe-mls engine — works in browser bundles and\n// Node without filesystem paths, public URLs, or runtime network requests.\n//\n// Why not just `import * as wasm from './palbe_mls_bg.wasm'` (the bundler\n// target's own entry, `palbe_mls.js`)? Because that import form only works in\n// bundlers that special-case `.wasm` (webpack/Vite with config) and NEVER in\n// Node — so the two-client crypto smoke (vitest, Node) could not run the real\n// engine. Instead we replicate what the wasm-pack `web`/`nodejs` targets do\n// internally: build the import object from the glue module's named exports +\n// the inline snippet, then `WebAssembly.instantiate` the bytes ourselves. The\n// bytes live in a private, lazily imported JavaScript package target, so a\n// consumer bundler can code-split them without needing a `.wasm` loader.\n//\n// init is idempotent + races-safe: concurrent callers share one in-flight\n// promise, and a second call after success is a no-op returning the same glue.\n\n// The glue exports every user-facing binding (MlsClient/MlsGroup/generateClient/\n// setPanicHook) AND all the `__wbg_*` import functions the `.wasm` needs, plus\n// `__wbg_set_wasm` (hand the instantiated exports back to the glue) and\n// `__wbindgen_start` (run wasm-bindgen's start section). We import it as a\n// namespace so we can both call the bindings and assemble the import object.\nimport * as glue from './pkg/palbe_mls_bg.js';\n// The single inline snippet the `.wasm` imports directly (Date.now()).\nimport * as snippet from './pkg/snippets/mls-rs-core-f99cdecbb456b09c/inline0.js';\n\n/** The subset of the glue surface the bridge calls. */\nexport interface MlsGlue {\n generateClient: typeof glue.generateClient;\n setPanicHook: typeof glue.setPanicHook;\n MlsClient: typeof glue.MlsClient;\n MlsGroup: typeof glue.MlsGroup;\n}\n\n// The two import-module specifiers the `.wasm` declares (verified via\n// WebAssembly.Module.imports — './palbe_mls_bg.js' + the inline snippet).\nconst GLUE_MODULE = './palbe_mls_bg.js';\nconst SNIPPET_MODULE = './snippets/mls-rs-core-f99cdecbb456b09c/inline0.js';\n\nlet inflight: Promise<MlsGlue> | null = null;\nlet ready: MlsGlue | null = null;\n\nfunction decodeBase64(encoded: unknown): ArrayBuffer {\n if (\n typeof encoded !== 'string' ||\n encoded.length === 0 ||\n typeof globalThis.atob !== 'function'\n ) {\n throw new Error('palbe-mls WASM asset is invalid');\n }\n\n let binary: string;\n try {\n binary = globalThis.atob(encoded);\n } catch {\n throw new Error('palbe-mls WASM asset is invalid');\n }\n\n const bytes = new Uint8Array(binary.length);\n for (let index = 0; index < binary.length; index += 1) {\n bytes[index] = binary.charCodeAt(index);\n }\n return bytes.buffer;\n}\n\nasync function loadWasmModule(): Promise<WebAssembly.Module> {\n const asset = await import('#palbase-mls-wasm');\n return WebAssembly.compile(decodeBase64(asset.default));\n}\n\n/** Assemble the import object the `.wasm` expects from the glue + snippet. */\nfunction buildImports(): WebAssembly.Imports {\n return {\n [GLUE_MODULE]: glue as unknown as WebAssembly.ModuleImports,\n [SNIPPET_MODULE]: snippet as unknown as WebAssembly.ModuleImports,\n };\n}\n\n/**\n * Initialize the MLS WASM engine. Idempotent — call it once before any\n * messaging crypto op (the facade does this on `pb.messaging.start()` and\n * before the first chat op). Returns the glue surface (the bridge uses it).\n */\nexport function initMls(): Promise<MlsGlue> {\n if (ready) return Promise.resolve(ready);\n if (inflight) return inflight;\n const attempt = (async () => {\n const module = await loadWasmModule();\n const instance = await WebAssembly.instantiate(module, buildImports());\n // Hand the instance's exports back to the glue (its module-level `wasm`)…\n const setWasm = (glue as unknown as { __wbg_set_wasm: (e: unknown) => void }).__wbg_set_wasm;\n setWasm(instance.exports);\n // …then run wasm-bindgen's start section (sets up the heap, etc).\n (instance.exports as { __wbindgen_start?: () => void }).__wbindgen_start?.();\n const g: MlsGlue = {\n generateClient: glue.generateClient,\n setPanicHook: glue.setPanicHook,\n MlsClient: glue.MlsClient,\n MlsGroup: glue.MlsGroup,\n };\n // Surface Rust panics as readable console errors in the browser.\n try {\n g.setPanicHook();\n } catch {\n // best-effort: a missing console_error_panic_hook must not fail init\n }\n ready = g;\n return g;\n })();\n inflight = attempt;\n void attempt.catch(() => {\n if (inflight === attempt) inflight = null;\n });\n return attempt;\n}\n\n/** True once `initMls()` has completed (the engine is usable synchronously). */\nexport function isMlsReady(): boolean {\n return ready !== null;\n}\n\n/** The initialized glue, or throw if `initMls()` has not completed. */\nexport function requireMls(): MlsGlue {\n if (!ready) {\n throw new Error('palbe-mls WASM not initialized — await initMls() first');\n }\n return ready;\n}\n","// Typed bridge over the raw WASM `MlsClient`/`MlsGroup`. It normalizes the two\n// byte conventions the engine uses and presents clean TS records:\n//\n// · Direct byte methods (signaturePublicKey, generateKeyPackage,\n// encryptApplicationMessage, groupId, …) already return `Uint8Array`.\n// · serde records/enums (enrollmentMaterial, addMember/commitChanges result,\n// processIncomingMessage, describeIncomingCommit) cross via\n// serde-wasm-bindgen, where `Vec<u8>` fields serialize as ARRAYS OF NUMBERS\n// (documented in src/wasm.rs §Records). We wrap those back into Uint8Array.\n//\n// Field names are camelCase (the engine derives `serde(rename_all =\n// \"camelCase\")`). The `ReceivedMessage` enum is externally tagged:\n// { Application: { sender, data } } | { CommitApplied: { epoch, removedSelf } }\n// | \"Proposal\" | \"Other\"\n//\n// Everything here is SYNC — the engine methods are synchronous (mls-rs storage\n// is sync in this build). Serialization of state happens through the JS storage\n// callbacks the host supplies (see ../storage.ts).\n\nimport { requireMls } from './loader.js';\nimport type {\n IGroupStateStorage,\n IKeyPackageStorage,\n MlsClient as RawClientType,\n MlsGroup as RawGroup,\n} from './pkg/palbe_mls.types.d.ts';\n\nexport type { IGroupStateStorage, IKeyPackageStorage };\n\n/** Coerce a serde byte field (array-of-numbers OR Uint8Array) to Uint8Array. */\nfunction toBytes(v: unknown): Uint8Array {\n if (v instanceof Uint8Array) return v;\n if (Array.isArray(v)) return Uint8Array.from(v as number[]);\n if (v == null) return new Uint8Array(0);\n // ArrayBuffer / typed-array view fallthrough.\n if (ArrayBuffer.isView(v)) return new Uint8Array((v as ArrayBufferView).buffer);\n throw new Error('expected byte field (Uint8Array | number[])');\n}\n\nfunction optBytes(v: unknown): Uint8Array | undefined {\n if (v == null) return undefined;\n return toBytes(v);\n}\n\n// ─── Record types (normalized) ───────────────────────────────────────────────\n\n/** `{ signatureKey, credential, capabilities }` — the SP-0 enroll material. */\nexport interface EnrollmentMaterial {\n signatureKey: Uint8Array;\n credential: Uint8Array;\n capabilities: Uint8Array;\n}\n\n/** Result of staging an add/commit-changes: commit + optional welcome + leaves. */\nexport interface AddResult {\n commit: Uint8Array;\n welcome?: Uint8Array;\n addedLeafIndices: number[];\n}\n\n/** Result of staging a remove: just the commit. */\nexport interface RemoveResult {\n commit: Uint8Array;\n}\n\nexport type CommitKind = 'Add' | 'Remove' | 'Update' | 'Mixed' | 'Other';\n\n/** The membership delta a commit WOULD effect (the reconciliation gate truth). */\nexport interface CommitDescription {\n kind: CommitKind;\n addedIdentities: Uint8Array[];\n removedIdentities: Uint8Array[];\n}\n\n/** What `processIncomingMessage` decoded (the normalized form of the serde enum). */\nexport type ReceivedMessage =\n | { type: 'application'; sender: Uint8Array; data: Uint8Array }\n | { type: 'commitApplied'; epoch: number; removedSelf: boolean }\n | { type: 'proposal' }\n | { type: 'other' };\n\n// ─── Group wrapper ───────────────────────────────────────────────────────────\n\n/** Typed wrapper over a raw `MlsGroup`. */\nexport class MlsGroupHandle {\n /** @internal */\n constructor(private readonly raw: RawGroup) {}\n\n /** The MLS rfc_group_id (routing key / storage key) bytes. */\n groupId(): Uint8Array {\n return this.raw.groupId();\n }\n\n /** Current applied epoch (BigInt → number; epochs stay well under 2^53). */\n currentEpoch(): number {\n return Number(this.raw.currentEpoch());\n }\n\n /** Stage a member add (PENDING after). */\n addMember(keyPackageMsg: Uint8Array): AddResult {\n return normalizeAddResult(this.raw.addMember(keyPackageMsg));\n }\n\n /** Stage a member remove by identity bytes (PENDING after). */\n removeMember(memberId: Uint8Array): RemoveResult {\n const r = this.raw.removeMember(memberId) as { commit: unknown };\n return { commit: toBytes(r.commit) };\n }\n\n /** Stage a SINGLE atomic add+remove commit (PENDING after). */\n commitChanges(addKeyPackages: Uint8Array[], removeMemberIds: Uint8Array[]): AddResult {\n return normalizeAddResult(this.raw.commitChanges(addKeyPackages, removeMemberIds));\n }\n\n /** Apply the staged commit (advance the local epoch). Only after server-accept. */\n applyPendingCommit(): void {\n this.raw.applyPendingCommit();\n }\n\n /** Discard the staged commit without advancing (the 409-rebase path). */\n clearPendingCommit(): void {\n this.raw.clearPendingCommit();\n }\n\n /** True while a staged (unapplied) commit exists. */\n hasPendingCommit(): boolean {\n return this.raw.hasPendingCommit();\n }\n\n /** Encrypt an application message at the current epoch → PrivateMessage bytes. */\n encryptApplicationMessage(plaintext: Uint8Array): Uint8Array {\n return this.raw.encryptApplicationMessage(plaintext);\n }\n\n /** Process an incoming MLSMessage and apply its effect (normalized result). */\n processIncomingMessage(message: Uint8Array): ReceivedMessage {\n return normalizeReceived(this.raw.processIncomingMessage(message));\n }\n\n /** Inspect an incoming Commit WITHOUT applying it (the gate's crypto-truth). */\n describeIncomingCommit(commitMsg: Uint8Array): CommitDescription {\n const d = this.raw.describeIncomingCommit(commitMsg) as {\n kind: CommitKind;\n addedIdentities: unknown[];\n removedIdentities: unknown[];\n };\n return {\n kind: d.kind,\n addedIdentities: d.addedIdentities.map(toBytes),\n removedIdentities: d.removedIdentities.map(toBytes),\n };\n }\n\n /** Persist the current group state through the JS group-storage callback. */\n writeToStorage(): void {\n this.raw.writeToStorage();\n }\n}\n\nfunction normalizeAddResult(v: unknown): AddResult {\n const r = v as { commit: unknown; welcome?: unknown; addedLeafIndices?: unknown[] };\n return {\n commit: toBytes(r.commit),\n welcome: optBytes(r.welcome),\n addedLeafIndices: (r.addedLeafIndices ?? []).map((n) => Number(n)),\n };\n}\n\nfunction normalizeReceived(v: unknown): ReceivedMessage {\n // Unit variants serialize as the bare string (\"Proposal\" / \"Other\").\n if (v === 'Proposal') return { type: 'proposal' };\n if (v === 'Other') return { type: 'other' };\n const obj = v as Record<string, unknown>;\n if (obj.Application) {\n const a = obj.Application as { sender: unknown; data: unknown };\n return { type: 'application', sender: toBytes(a.sender), data: toBytes(a.data) };\n }\n if (obj.CommitApplied) {\n const c = obj.CommitApplied as { epoch: unknown; removedSelf: unknown };\n return { type: 'commitApplied', epoch: Number(c.epoch), removedSelf: Boolean(c.removedSelf) };\n }\n return { type: 'other' };\n}\n\n// ─── Client wrapper ──────────────────────────────────────────────────────────\n\n/** Typed wrapper over a raw `MlsClient`. */\nexport class MlsClientHandle {\n /** @internal */\n constructor(private readonly raw: RawClientType) {}\n\n /** The SP-0 keydir enroll material (`{ signatureKey, credential, capabilities }`). */\n enrollmentMaterial(): EnrollmentMaterial {\n const m = this.raw.enrollmentMaterial() as {\n signatureKey: unknown;\n credential: unknown;\n capabilities: unknown;\n };\n return {\n signatureKey: toBytes(m.signatureKey),\n credential: toBytes(m.credential),\n capabilities: toBytes(m.capabilities),\n };\n }\n\n /** The device's STABLE signature PUBLIC key bytes (the keydir `signature_key`). */\n signaturePublicKey(): Uint8Array {\n return this.raw.signaturePublicKey();\n }\n\n /** The MLS Credential wire bytes (the keydir `credential`). */\n credential(): Uint8Array {\n return this.raw.credential();\n }\n\n /** The MLS Capabilities wire bytes (the keydir `capabilities`). */\n capabilities(): Uint8Array {\n return this.raw.capabilities();\n }\n\n /** The serialized STABLE signature keypair (secret-bearing — persist sealed). */\n signatureKeypair(): Uint8Array {\n return this.raw.signatureKeypair();\n }\n\n /** Mint a one-use KeyPackage (RAW RFC 9420 §10 bytes). */\n generateKeyPackage(): Uint8Array {\n return this.raw.generateKeyPackage();\n }\n\n /** Create a new group at epoch 0 (engine mints the rfc_group_id). */\n createGroup(groupId?: Uint8Array): MlsGroupHandle {\n return new MlsGroupHandle(this.raw.createGroup(groupId ?? undefined));\n }\n\n /** Join a group from a Welcome (opaque MLSMessage bytes). */\n joinGroup(welcome: Uint8Array): MlsGroupHandle {\n return new MlsGroupHandle(this.raw.joinGroup(welcome));\n }\n\n /** Re-hydrate an already-joined group by its rfc_group_id bytes. */\n loadGroup(groupId: Uint8Array): MlsGroupHandle {\n return new MlsGroupHandle(this.raw.loadGroup(groupId));\n }\n}\n\n/**\n * Mint a device MLS client. `initMls()` MUST have completed first\n * (the facade enforces this). `signatureKeypair` is undefined on first run, or\n * the previously-persisted sealed keypair bytes on restart (same MLS identity).\n */\nexport function generateClient(\n id: Uint8Array,\n groupStorage: IGroupStateStorage,\n keyPackageStorage: IKeyPackageStorage,\n signatureKeypair?: Uint8Array,\n): MlsClientHandle {\n const { generateClient: gen } = requireMls();\n return new MlsClientHandle(\n gen(id, groupStorage, keyPackageStorage, signatureKeypair ?? undefined),\n );\n}\n","// The web mirror of iOS `MLSEngine` — a single serialized owner of the WASM\n// MlsClient + the per-group MlsGroup handles. Every public method runs through\n// a promise-chain mutex (`enqueue`) so the synchronous engine critical section\n// is never re-entered concurrently (the engine is single-threaded; two awaited\n// ops interleaving would corrupt the storage write-through cache).\n//\n// DURABILITY BOUNDARY: a mutating engine op (create / join / commit / encrypt /\n// process / applyPending) is followed by an awaited storage flush before the\n// host treats the op as done — so an acked message / applied commit is always\n// durable (the engine erases a message key on first decrypt; the durable copy\n// is the only recoverable form, mirroring the iOS D3/D5 ordering).\n//\n// IDENTITY: the MLS credential identity is the SP-0 device id (`mdv_`), passed\n// as the `id` bytes to generateClient (SP-1.6 per-device leaf identity).\n\nimport type { GroupStateStorage, KeyPackageStorage, SignatureKeyStore } from './storage.js';\nimport { bytesToHex } from './util.js';\nimport {\n type AddResult,\n type CommitDescription,\n generateClient,\n type MlsClientHandle,\n type MlsGroupHandle,\n type ReceivedMessage,\n} from './wasm/bridge.js';\nimport { initMls } from './wasm/loader.js';\n\nconst utf8 = new TextEncoder();\n\n/** The rfc_group_id keyed by its hex form (the per-group handle cache key). */\nconst gidHex = bytesToHex;\n\nexport interface EngineEnrollmentMaterial {\n signatureKey: Uint8Array;\n credential: Uint8Array;\n capabilities: Uint8Array;\n}\n\n/**\n * The serialized MLS engine. Built once per (userId, deviceId). All crypto +\n * storage flows through here; `GroupMessaging` and `MessageDeliverySource` hold\n * the SAME instance so one MLS identity backs enroll + group ops + delivery.\n */\nexport class MlsEngine {\n private readonly groups = new Map<string, MlsGroupHandle>();\n private chain: Promise<unknown> = Promise.resolve();\n\n private constructor(\n readonly userId: string,\n readonly deviceId: string,\n private readonly client: MlsClientHandle,\n private readonly groupStore: GroupStateStorage,\n private readonly kpStore: KeyPackageStorage,\n ) {}\n\n /**\n * Build the engine: init the WASM, hydrate the durable stores into the sync\n * caches, generate (or restore) the device client. Idempotent restore: the\n * persisted signature keypair keeps the SAME MLS signing identity across\n * reloads; the group-state cache makes already-joined groups reloadable.\n */\n static async create(opts: {\n userId: string;\n deviceId: string;\n groupStore: GroupStateStorage;\n kpStore: KeyPackageStorage;\n sigStore: SignatureKeyStore;\n }): Promise<MlsEngine> {\n await initMls();\n // Hydrate the durable state into the sync caches BEFORE generateClient so a\n // restart sees its persisted groups + key-package secrets.\n await Promise.all([opts.groupStore.hydrate(), opts.kpStore.hydrate()]);\n const persistedKeypair = await opts.sigStore.load(opts.userId);\n const idBytes = utf8.encode(opts.deviceId);\n const client = generateClient(idBytes, opts.groupStore, opts.kpStore, persistedKeypair);\n // Persist the (possibly freshly minted) signature keypair so the next reload\n // restores the SAME identity.\n if (!persistedKeypair) {\n await opts.sigStore.save(opts.userId, client.signatureKeypair());\n }\n return new MlsEngine(opts.userId, opts.deviceId, client, opts.groupStore, opts.kpStore);\n }\n\n /** Serialize an engine op onto the chain so the critical section is exclusive. */\n private enqueue<T>(fn: () => T | Promise<T>): Promise<T> {\n const run = this.chain.then(fn, fn);\n // Keep the chain alive but swallow rejections so one failure doesn't poison\n // every later op (each caller still sees its own rejection via `run`).\n this.chain = run.then(\n () => undefined,\n () => undefined,\n );\n return run;\n }\n\n /** Flush both storage caches durably (the post-mutation durability boundary). */\n private async flush(): Promise<void> {\n await Promise.all([this.groupStore.flush(), this.kpStore.flush()]);\n }\n\n // ── Enroll material / key packages ──\n\n enrollmentMaterial(): Promise<EngineEnrollmentMaterial> {\n return this.enqueue(() => this.client.enrollmentMaterial());\n }\n\n signaturePublicKey(): Promise<Uint8Array> {\n return this.enqueue(() => this.client.signaturePublicKey());\n }\n\n generateKeyPackage(): Promise<Uint8Array> {\n return this.enqueue(async () => {\n const kp = this.client.generateKeyPackage();\n // A generated key package stores its private bits via the kp store — flush.\n await this.flush();\n return kp;\n });\n }\n\n // ── Group create / join / reload ──\n\n /** Create a group; returns its rfc_group_id (the routing key). */\n createGroup(): Promise<Uint8Array> {\n return this.enqueue(async () => {\n const group = this.client.createGroup();\n const gid = group.groupId();\n this.groups.set(gidHex(gid), group);\n group.writeToStorage();\n await this.flush();\n return gid;\n });\n }\n\n /** Join from a Welcome; returns the joined group's rfc_group_id. */\n joinFromWelcome(welcome: Uint8Array): Promise<Uint8Array> {\n return this.enqueue(async () => {\n const group = this.client.joinGroup(welcome);\n const gid = group.groupId();\n this.groups.set(gidHex(gid), group);\n group.writeToStorage();\n await this.flush();\n return gid;\n });\n }\n\n /** The current applied epoch for a group (0 if unknown). */\n epoch(rfcGroupId: Uint8Array): Promise<number> {\n return this.enqueue(() => {\n const g = this.handle(rfcGroupId);\n return g ? g.currentEpoch() : 0;\n });\n }\n\n // ── Membership commits (staged → applied/cleared by the rebase loop) ──\n\n /** Stage a SINGLE atomic add+remove commit. Returns the AddResult (PENDING). */\n commitChanges(\n rfcGroupId: Uint8Array,\n adds: Uint8Array[],\n removeMemberIds: Uint8Array[],\n ): Promise<AddResult> {\n return this.enqueue(() => {\n const g = this.requireHandle(rfcGroupId);\n return g.commitChanges(adds, removeMemberIds);\n });\n }\n\n /** Apply the staged commit (advance the epoch). Only after a server-accept. */\n applyPendingCommit(rfcGroupId: Uint8Array): Promise<void> {\n return this.enqueue(async () => {\n const g = this.requireHandle(rfcGroupId);\n g.applyPendingCommit();\n g.writeToStorage();\n await this.flush();\n });\n }\n\n /** Discard the staged commit (the 409-rebase path). */\n clearPendingCommit(rfcGroupId: Uint8Array): Promise<void> {\n return this.enqueue(() => {\n const g = this.handle(rfcGroupId);\n if (g?.hasPendingCommit()) g.clearPendingCommit();\n });\n }\n\n // ── Application messages ──\n\n /** Encrypt a plaintext at the current epoch → PrivateMessage bytes. */\n encryptApplication(rfcGroupId: Uint8Array, plaintext: Uint8Array): Promise<Uint8Array> {\n return this.enqueue(async () => {\n const g = this.requireHandle(rfcGroupId);\n const ct = g.encryptApplicationMessage(plaintext);\n // The sender ratchet advanced — checkpoint.\n g.writeToStorage();\n await this.flush();\n return ct;\n });\n }\n\n /**\n * Process an incoming MLSMessage (decrypt application / apply commit). Returns\n * the normalized ReceivedMessage. `describeIncomingCommit` could gate a commit\n * before applying when the server fans a declaration; today the live server\n * does not, so we process directly (parity with the iOS nil-declaration path).\n */\n processIncoming(rfcGroupId: Uint8Array, blob: Uint8Array): Promise<ReceivedMessage> {\n return this.enqueue(async () => {\n const g = this.requireHandle(rfcGroupId);\n const received = g.processIncomingMessage(blob);\n // A commit/decrypt mutated state — checkpoint before the host acks.\n g.writeToStorage();\n await this.flush();\n return received;\n });\n }\n\n /** Inspect a commit WITHOUT applying (the reconciliation gate's crypto-truth). */\n describeIncomingCommit(rfcGroupId: Uint8Array, commit: Uint8Array): Promise<CommitDescription> {\n return this.enqueue(() => this.requireHandle(rfcGroupId).describeIncomingCommit(commit));\n }\n\n /** Persist the current group state (explicit checkpoint). */\n writeToStorage(rfcGroupId: Uint8Array): Promise<void> {\n return this.enqueue(async () => {\n const g = this.handle(rfcGroupId);\n if (g) {\n g.writeToStorage();\n await this.flush();\n }\n });\n }\n\n // ── Handle resolution ──\n\n /** Resolve (or reload from storage) the handle for a group, or undefined. */\n private handle(rfcGroupId: Uint8Array): MlsGroupHandle | undefined {\n const key = gidHex(rfcGroupId);\n const cached = this.groups.get(key);\n if (cached) return cached;\n // Try to reload from the hydrated group-state cache (restart-safe).\n try {\n const g = this.client.loadGroup(rfcGroupId);\n this.groups.set(key, g);\n return g;\n } catch {\n return undefined;\n }\n }\n\n private requireHandle(rfcGroupId: Uint8Array): MlsGroupHandle {\n const g = this.handle(rfcGroupId);\n if (!g) throw new Error(`MLS group not loaded: ${gidHex(rfcGroupId)}`);\n return g;\n }\n}\n","// The group registry — maps rfc_group_id ↔ the resolved MessagingGroup and\n// drives the observable chat list. Web mirror of the iOS GroupRegistry: a SET\n// change (register-new / remove / hydrate) publishes a fresh [Chat] snapshot;\n// an in-place epoch bump does NOT (a chatty group must not invalidate the list).\n\nimport type { Chat } from './chat.js';\nimport type { MessagingGroup } from './group-messaging.js';\nimport type { GroupCatalog } from './history.js';\n\n/** A pointer-stable Chat factory: one Chat per rfc_group_id. */\nexport type ChatFactory = (group: MessagingGroup) => Chat;\n\nexport class GroupRegistry {\n private readonly byRfc = new Map<string, MessagingGroup>();\n private catalog: GroupCatalog | null = null;\n private chatFactory: ChatFactory | null = null;\n private onListChange: ((chats: Chat[]) => void) | null = null;\n\n /** Wire the observable chat-list sink + the pointer-stable Chat factory, then\n * replay the current set so a sink attached after groups loaded reflects them. */\n attachChatList(onListChange: (chats: Chat[]) => void, factory: ChatFactory): void {\n this.onListChange = onListChange;\n this.chatFactory = factory;\n this.publish();\n }\n\n /** Hydrate from the durable catalog (live entries win — fresher epoch). */\n async attachCatalog(catalog: GroupCatalog): Promise<void> {\n this.catalog = catalog;\n let changed = false;\n for (const c of await catalog.all()) {\n if (!this.byRfc.has(c.rfcGroupId)) {\n this.byRfc.set(c.rfcGroupId, {\n displayId: c.displayId,\n rfcGroupId: c.rfcGroupId,\n currentEpoch: c.currentEpoch,\n ownerUserId: c.ownerUserId,\n directKey: c.directKey,\n name: c.name,\n });\n changed = true;\n }\n }\n if (changed) this.publish();\n }\n\n register(group: MessagingGroup): void {\n const isNew = !this.byRfc.has(group.rfcGroupId);\n this.byRfc.set(group.rfcGroupId, group);\n void this.persist(group);\n if (isNew) this.publish();\n }\n\n remove(rfcGroupId: string): void {\n if (!this.byRfc.has(rfcGroupId)) return;\n this.byRfc.delete(rfcGroupId);\n if (this.catalog) void this.catalog.remove(rfcGroupId);\n this.publish();\n }\n\n group(rfcGroupId: string): MessagingGroup | undefined {\n return this.byRfc.get(rfcGroupId);\n }\n\n /** The single known group, if exactly one (the wire-gap routing fallback). */\n soleGroup(): MessagingGroup | undefined {\n return this.byRfc.size === 1 ? this.byRfc.values().next().value : undefined;\n }\n\n all(): MessagingGroup[] {\n return [...this.byRfc.values()];\n }\n\n /** Advance a cached epoch (after a processed commit). Does NOT publish (the\n * group SET is unchanged — only metadata). */\n bumpEpoch(rfcGroupId: string, epoch: number): void {\n const g = this.byRfc.get(rfcGroupId);\n if (!g) return;\n const bumped = { ...g, currentEpoch: epoch };\n this.byRfc.set(rfcGroupId, bumped);\n void this.persist(bumped);\n }\n\n private publish(): void {\n const factory = this.chatFactory;\n if (!this.onListChange || !factory) return;\n const chats = this.all().map((g) => factory(g));\n this.onListChange(chats);\n }\n\n private async persist(group: MessagingGroup): Promise<void> {\n if (!this.catalog) return;\n try {\n await this.catalog.upsert({\n displayId: group.displayId,\n rfcGroupId: group.rfcGroupId,\n currentEpoch: group.currentEpoch,\n ownerUserId: group.ownerUserId,\n directKey: group.directKey,\n name: group.name,\n });\n } catch {\n // best-effort persistence — never blocks the live path\n }\n }\n}\n","// The persistence layer for web messaging — the web mirror of the iOS\n// Keychain-sealed MLS stores (MessagingGroupStateStorage / KeyPackageStorage /\n// MessagingDeviceIDStore / MessagingSignatureKeyStore / MessageStore /\n// GroupCatalog).\n//\n// The CRITICAL contract (src/wasm.rs §Storage): mls-rs calls IGroupStateStorage\n// / IKeyPackageStorage methods SYNCHRONOUSLY inside the engine's locked\n// critical section. So those are backed by an in-memory write-through cache\n// (sync reads/writes) and flushed to a durable, encrypted-at-rest backend\n// (IndexedDB + WebCrypto AES-GCM) ASYNCHRONOUSLY, OUTSIDE the engine call. The\n// engine never awaits I/O; the host (the engine wrapper) awaits the flush after\n// each mutating op so an acked message is always durable.\n//\n// At-rest encryption: a per-device AES-GCM key, non-extractable, kept in\n// IndexedDB as a CryptoKey (browsers persist CryptoKey objects in IDB without\n// exposing raw bytes — the web analogue of the iOS Keychain seal key). In Node\n// (vitest / SSR) there is no IndexedDB; a pure in-memory durable backend stands\n// in (the crypto smoke needs durability WITHIN a process, not across reloads).\n\nimport { bytesToHex } from './util.js';\nimport type { IGroupStateStorage, IKeyPackageStorage } from './wasm/bridge.js';\n\n// ─── Durable KV backend (IndexedDB in browser, memory in Node) ──────────────\n\n/** A namespaced async key→bytes store. Values are sealed before write. */\nexport interface DurableKV {\n get(key: string): Promise<Uint8Array | undefined>;\n set(key: string, value: Uint8Array): Promise<void>;\n delete(key: string): Promise<void>;\n /** All keys under a prefix (for hydrating a store on launch). */\n keys(prefix: string): Promise<string[]>;\n}\n\nconst hasIndexedDB = (): boolean =>\n typeof indexedDB !== 'undefined' && typeof crypto !== 'undefined' && !!crypto.subtle;\n\n/** A tight, non-shared ArrayBuffer copy of a byte view (WebCrypto BufferSource). */\nfunction copyToArrayBuffer(view: Uint8Array): ArrayBuffer {\n const out = new ArrayBuffer(view.byteLength);\n new Uint8Array(out).set(view);\n return out;\n}\n\n// ── In-memory backend (Node/SSR/tests) ──────────────────────────────────────\n\nclass MemoryKV implements DurableKV {\n private readonly map = new Map<string, Uint8Array>();\n get(key: string): Promise<Uint8Array | undefined> {\n return Promise.resolve(this.map.get(key));\n }\n set(key: string, value: Uint8Array): Promise<void> {\n this.map.set(key, value.slice());\n return Promise.resolve();\n }\n delete(key: string): Promise<void> {\n this.map.delete(key);\n return Promise.resolve();\n }\n keys(prefix: string): Promise<string[]> {\n return Promise.resolve([...this.map.keys()].filter((k) => k.startsWith(prefix)));\n }\n}\n\n// ── IndexedDB backend (browser) with WebCrypto AES-GCM seal at rest ─────────\n\nconst DB_NAME = 'palbe.messaging';\nconst STORE = 'kv';\nconst KEY_STORE = 'keys';\nconst SEAL_KEY_ID = 'seal-key-v1';\n\nfunction openDb(): Promise<IDBDatabase> {\n return new Promise((resolve, reject) => {\n const req = indexedDB.open(DB_NAME, 1);\n req.onupgradeneeded = () => {\n const db = req.result;\n if (!db.objectStoreNames.contains(STORE)) db.createObjectStore(STORE);\n if (!db.objectStoreNames.contains(KEY_STORE)) db.createObjectStore(KEY_STORE);\n };\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => reject(req.error);\n });\n}\n\nfunction idbReq<T>(req: IDBRequest<T>): Promise<T> {\n return new Promise((resolve, reject) => {\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => reject(req.error);\n });\n}\n\n/** Get-or-create the non-extractable AES-GCM seal key (persisted as a CryptoKey). */\nasync function getSealKey(db: IDBDatabase): Promise<CryptoKey> {\n const existing = await idbReq(\n db.transaction(KEY_STORE, 'readonly').objectStore(KEY_STORE).get(SEAL_KEY_ID),\n );\n if (existing) return existing as CryptoKey;\n const key = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, false, [\n 'encrypt',\n 'decrypt',\n ]);\n // Storing a non-extractable CryptoKey keeps the raw bytes inside the browser's\n // key store — the page can use it to seal/open but can never read it out.\n await idbReq(db.transaction(KEY_STORE, 'readwrite').objectStore(KEY_STORE).put(key, SEAL_KEY_ID));\n return key;\n}\n\nclass IndexedDbKV implements DurableKV {\n private dbPromise: Promise<IDBDatabase> | null = null;\n private keyPromise: Promise<CryptoKey> | null = null;\n\n private db(): Promise<IDBDatabase> {\n if (!this.dbPromise) this.dbPromise = openDb();\n return this.dbPromise;\n }\n private async sealKey(): Promise<CryptoKey> {\n if (!this.keyPromise) this.keyPromise = this.db().then(getSealKey);\n return this.keyPromise;\n }\n\n private async seal(plain: Uint8Array): Promise<Uint8Array> {\n const key = await this.sealKey();\n const iv = crypto.getRandomValues(new Uint8Array(12));\n const ct = new Uint8Array(\n await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, copyToArrayBuffer(plain)),\n );\n const out = new Uint8Array(iv.length + ct.length);\n out.set(iv, 0);\n out.set(ct, iv.length);\n return out;\n }\n\n private async open(sealed: Uint8Array): Promise<Uint8Array> {\n const key = await this.sealKey();\n const iv = copyToArrayBuffer(sealed.subarray(0, 12));\n const ct = copyToArrayBuffer(sealed.subarray(12));\n const pt = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ct);\n return new Uint8Array(pt);\n }\n\n async get(k: string): Promise<Uint8Array | undefined> {\n const db = await this.db();\n const sealed = (await idbReq(db.transaction(STORE, 'readonly').objectStore(STORE).get(k))) as\n | Uint8Array\n | undefined;\n if (!sealed) return undefined;\n return this.open(sealed);\n }\n async set(k: string, v: Uint8Array): Promise<void> {\n const db = await this.db();\n const sealed = await this.seal(v);\n await idbReq(db.transaction(STORE, 'readwrite').objectStore(STORE).put(sealed, k));\n }\n async delete(k: string): Promise<void> {\n const db = await this.db();\n await idbReq(db.transaction(STORE, 'readwrite').objectStore(STORE).delete(k));\n }\n async keys(prefix: string): Promise<string[]> {\n const db = await this.db();\n const all = (await idbReq(\n db.transaction(STORE, 'readonly').objectStore(STORE).getAllKeys(),\n )) as IDBValidKey[];\n return all.map(String).filter((k) => k.startsWith(prefix));\n }\n}\n\n/** Build the durable KV for this environment (IndexedDB in browser, memory else). */\nexport function createDurableKV(): DurableKV {\n return hasIndexedDB() ? new IndexedDbKV() : new MemoryKV();\n}\n\n// ─── Byte / hex helpers ──────────────────────────────────────────────────────\n\nfunction bytesKey(prefix: string, id: Uint8Array): string {\n // Hex the id bytes so any blob can be a string key.\n return prefix + bytesToHex(id);\n}\n\n// ─── Sync write-through cache (the IGroupStateStorage / IKeyPackageStorage seam)\n\n/**\n * The synchronous group-state storage mls-rs calls inside the engine. Reads/\n * writes hit an in-memory cache (sync); a durable flush to the encrypted KV is\n * scheduled and awaited by the engine wrapper OUTSIDE the engine call.\n *\n * Layout per group (keyed by hex(groupId)):\n * state:<gid> → latest serialized group state\n * epoch:<gid>:<epochId> → a stored prior-epoch record\n * maxEpoch:<gid> → highest stored prior-epoch id (decimal string bytes)\n */\nexport class GroupStateStorage implements IGroupStateStorage {\n // In-memory authoritative cache (the engine reads/writes this synchronously).\n private readonly stateCache = new Map<string, Uint8Array>();\n private readonly epochCache = new Map<string, Uint8Array>();\n private readonly maxEpochCache = new Map<string, bigint>();\n // Keys touched since the last flush (durable write queue).\n private readonly dirty = new Set<string>();\n\n constructor(private readonly kv: DurableKV) {}\n\n // ── sync engine seam ──\n state(groupId: Uint8Array): Uint8Array | undefined {\n return this.stateCache.get(bytesKey('', groupId));\n }\n epoch(groupId: Uint8Array, epochId: bigint): Uint8Array | undefined {\n return this.epochCache.get(`${bytesKey('', groupId)}:${epochId}`);\n }\n write(\n groupId: Uint8Array,\n groupState: Uint8Array,\n epochInserts: { id: bigint; data: Uint8Array }[],\n epochUpdates: { id: bigint; data: Uint8Array }[],\n ): void {\n const gid = bytesKey('', groupId);\n this.stateCache.set(gid, groupState.slice());\n this.dirty.add(`state:${gid}`);\n let max = this.maxEpochCache.get(gid) ?? -1n;\n for (const rec of [...epochInserts, ...epochUpdates]) {\n this.epochCache.set(`${gid}:${rec.id}`, rec.data.slice());\n this.dirty.add(`epoch:${gid}:${rec.id}`);\n if (rec.id > max) max = rec.id;\n }\n if (max >= 0n) {\n this.maxEpochCache.set(gid, max);\n this.dirty.add(`maxEpoch:${gid}`);\n }\n }\n maxEpochId(groupId: Uint8Array): bigint | undefined {\n return this.maxEpochCache.get(bytesKey('', groupId));\n }\n\n // ── async durable flush (called by the engine wrapper after a mutating op) ──\n async flush(): Promise<void> {\n if (this.dirty.size === 0) return;\n const pending = [...this.dirty];\n this.dirty.clear();\n await Promise.all(\n pending.map(async (key) => {\n if (key.startsWith('state:')) {\n const gid = key.slice('state:'.length);\n const v = this.stateCache.get(gid);\n if (v) await this.kv.set(`gss:state:${gid}`, v);\n } else if (key.startsWith('epoch:')) {\n const rest = key.slice('epoch:'.length);\n const v = this.epochCache.get(rest);\n if (v) await this.kv.set(`gss:epoch:${rest}`, v);\n } else if (key.startsWith('maxEpoch:')) {\n const gid = key.slice('maxEpoch:'.length);\n const max = this.maxEpochCache.get(gid);\n if (max != null) await this.kv.set(`gss:maxEpoch:${gid}`, encodeBigint(max));\n }\n }),\n );\n }\n\n /** Hydrate the in-memory cache from the durable KV (call before generateClient). */\n async hydrate(): Promise<void> {\n const keys = await this.kv.keys('gss:');\n for (const k of keys) {\n const v = await this.kv.get(k);\n if (!v) continue;\n if (k.startsWith('gss:state:')) {\n this.stateCache.set(k.slice('gss:state:'.length), v);\n } else if (k.startsWith('gss:epoch:')) {\n this.epochCache.set(k.slice('gss:epoch:'.length), v);\n } else if (k.startsWith('gss:maxEpoch:')) {\n this.maxEpochCache.set(k.slice('gss:maxEpoch:'.length), decodeBigint(v));\n }\n }\n }\n\n async wipe(): Promise<void> {\n this.stateCache.clear();\n this.epochCache.clear();\n this.maxEpochCache.clear();\n this.dirty.clear();\n for (const k of await this.kv.keys('gss:')) await this.kv.delete(k);\n }\n}\n\n/**\n * The synchronous key-package secret storage mls-rs calls. Secret-bearing (HPKE\n * private keys) — sealed at rest by the KV. Same sync-cache + async-flush shape.\n */\nexport class KeyPackageStorage implements IKeyPackageStorage {\n private readonly cache = new Map<string, Uint8Array>();\n private readonly dirtySet = new Set<string>();\n private readonly dirtyDelete = new Set<string>();\n\n constructor(private readonly kv: DurableKV) {}\n\n // ── sync engine seam ──\n delete(id: Uint8Array): void {\n const k = bytesKey('', id);\n this.cache.delete(k);\n this.dirtyDelete.add(k);\n this.dirtySet.delete(k);\n }\n insert(id: Uint8Array, data: Uint8Array): void {\n const k = bytesKey('', id);\n this.cache.set(k, data.slice());\n this.dirtySet.add(k);\n this.dirtyDelete.delete(k);\n }\n get(id: Uint8Array): Uint8Array | undefined {\n return this.cache.get(bytesKey('', id));\n }\n\n // ── async durable flush ──\n async flush(): Promise<void> {\n if (this.dirtySet.size === 0 && this.dirtyDelete.size === 0) return;\n const sets = [...this.dirtySet];\n const dels = [...this.dirtyDelete];\n this.dirtySet.clear();\n this.dirtyDelete.clear();\n await Promise.all([\n ...sets.map((k) => {\n const v = this.cache.get(k);\n return v ? this.kv.set(`kps:${k}`, v) : Promise.resolve();\n }),\n ...dels.map((k) => this.kv.delete(`kps:${k}`)),\n ]);\n }\n\n async hydrate(): Promise<void> {\n for (const k of await this.kv.keys('kps:')) {\n const v = await this.kv.get(k);\n if (v) this.cache.set(k.slice('kps:'.length), v);\n }\n }\n\n async wipe(): Promise<void> {\n this.cache.clear();\n this.dirtySet.clear();\n this.dirtyDelete.clear();\n for (const k of await this.kv.keys('kps:')) await this.kv.delete(k);\n }\n}\n\nfunction encodeBigint(n: bigint): Uint8Array {\n return new TextEncoder().encode(n.toString());\n}\nfunction decodeBigint(b: Uint8Array): bigint {\n return BigInt(new TextDecoder().decode(b));\n}\n\n// ─── Device-id + signature-keypair stores (small singletons) ────────────────\n\nconst enc = new TextEncoder();\nconst dec = new TextDecoder();\n\n/** Persisted server-minted device id, keyed by user id (enroll idempotency). */\nexport class DeviceIdStore {\n constructor(private readonly kv: DurableKV) {}\n async load(userId: string): Promise<string | undefined> {\n const v = await this.kv.get(`devid:${userId}`);\n return v ? dec.decode(v) : undefined;\n }\n async save(userId: string, deviceId: string): Promise<void> {\n await this.kv.set(`devid:${userId}`, enc.encode(deviceId));\n }\n async clear(userId: string): Promise<void> {\n await this.kv.delete(`devid:${userId}`);\n }\n}\n\n/** Persisted STABLE signature keypair bytes, keyed by user id (same MLS identity). */\nexport class SignatureKeyStore {\n constructor(private readonly kv: DurableKV) {}\n async load(userId: string): Promise<Uint8Array | undefined> {\n return this.kv.get(`sigkey:${userId}`);\n }\n async save(userId: string, keypair: Uint8Array): Promise<void> {\n await this.kv.set(`sigkey:${userId}`, keypair);\n }\n}\n","// The delete-for-me suppression store — the web mirror of iOS `SuppressionStore`.\n//\n// delete-for-me is a PURELY LOCAL, per-device, unsynced \"hide this message\"\n// intent: NO wire, NO attribution, no server contact. It is a persisted per-chat\n// `Set<suppressionKey>` where `suppressionKey` is the message's `client_msg_id`\n// when present, else `seq:<server_seq>` for legacy messages (which lack a\n// client_msg_id but are always server-acked, so `server_seq` is stable). At\n// render time a message whose key is in the set is OMITTED from the transcript.\n//\n// Sealed at rest by the same durable KV (IndexedDB + WebCrypto AES-GCM in the\n// browser) that seals the message store — so the suppression intent survives a\n// cold launch and a for-me'd message stays hidden after reload (the reload-parity\n// boundary for delete-for-me, mirroring the message store's reaction/edit/delete\n// re-fold parity).\n\nimport type { DurableKV } from './storage.js';\nimport { decodeUtf8, encodeUtf8 } from './util.js';\n\n/** Persist a per-chat delete-for-me suppression key set (keyed by rfcGroupId). */\nexport class SuppressionStore {\n constructor(private readonly kv: DurableKV) {}\n\n private key(rfcGroupId: string): string {\n return `supp:${rfcGroupId}`;\n }\n\n /** Load the persisted suppression keys for a chat (empty array if none). */\n async load(rfcGroupId: string): Promise<string[]> {\n const raw = await this.kv.get(this.key(rfcGroupId));\n if (!raw) return [];\n try {\n const parsed = JSON.parse(decodeUtf8(raw));\n return Array.isArray(parsed) ? (parsed as string[]) : [];\n } catch {\n return [];\n }\n }\n\n /** Persist the full suppression key set for a chat (deterministic order). */\n async save(rfcGroupId: string, keys: string[]): Promise<void> {\n const sorted = [...new Set(keys)].sort();\n await this.kv.set(this.key(rfcGroupId), encodeUtf8(JSON.stringify(sorted)));\n }\n\n async wipe(): Promise<void> {\n for (const k of await this.kv.keys('supp:')) await this.kv.delete(k);\n }\n}\n","// The messaging coordinator — the web mirror of iOS MessagingCoordinator +\n// MessagingGroupState. Builds the engine/storage/delivery-source ONCE (lazily,\n// on first chat op after sign-in), implements the ChatBackend seam, mints\n// pointer-stable Chats (one per rfc id), and owns the observable chat list.\n//\n// Process-shared singleton (one runtime per browser tab), like the iOS shared\n// holder. Bound to ONE PalbeRuntime (the configured pb).\n\nimport type { PalbeRuntime } from '../runtime.js';\nimport { Chat, type ChatBackend, type ChatDraft, DELETED_DESCRIPTOR } from './chat.js';\nimport type { AnchorTriple } from './deadline-calculator.js';\nimport { DeleteFold } from './delete-fold.js';\nimport { type IncomingMessage, MessageDeliverySource, MessageHub } from './delivery-source.js';\nimport { DisappearingStore } from './disappearing.js';\nimport { type AuthorResolution, EditFold } from './edit-fold.js';\nimport {\n type ExpirySpec,\n GroupMessaging,\n type MentionRange,\n type MessagingGroup,\n type ReplyRef,\n resolveReply,\n} from './group-messaging.js';\nimport { GroupCatalog, MessageStore, type StoredMessage } from './history.js';\nimport { enrollDevice, listDevices } from './keydir.js';\nimport { MentionElevationStore } from './mention-elevation.js';\nimport { normalizeMentionRangesUtf16 } from './mention-ranges.js';\nimport { MlsEngine } from './mls-engine.js';\nimport { ReactionFold } from './reaction-fold.js';\nimport { GroupRegistry } from './registry.js';\nimport {\n createDurableKV,\n DeviceIdStore,\n type DurableKV,\n GroupStateStorage,\n KeyPackageStorage,\n SignatureKeyStore,\n} from './storage.js';\nimport { SuppressionStore } from './suppression.js';\nimport { TimerFold } from './timer-fold.js';\nimport type {\n ChatMember,\n ChatMessage,\n NotifyScope,\n ResolvedMention,\n ResolvedReply,\n SentReceipt,\n UnreadView,\n Unsubscribe,\n} from './types.js';\nimport { directKey as makeDirectKey, mintDeviceId } from './util.js';\n\ninterface Resolved {\n engine: MlsEngine;\n groups: GroupMessaging;\n source: MessageDeliverySource;\n hub: MessageHub;\n messageStore: MessageStore;\n catalog: GroupCatalog;\n registry: GroupRegistry;\n deviceId: string;\n userId: string;\n}\n\nexport class MessagingCoordinator implements ChatBackend {\n private readonly kv: DurableKV;\n private readonly deviceStore: DeviceIdStore;\n private readonly sigStore: SignatureKeyStore;\n private readonly groupStore: GroupStateStorage;\n private readonly kpStore: KeyPackageStorage;\n private readonly suppressionStore: SuppressionStore;\n private readonly elevationStore: MentionElevationStore;\n private readonly disappearingStore: DisappearingStore;\n private readonly registry = new GroupRegistry();\n\n private resolved: Resolved | null = null;\n private resolvePromise: Promise<Resolved> | null = null;\n\n // Pointer-stable Chat cache (one per rfc id) + the observable chat list.\n private readonly chatByRfc = new Map<string, Chat>();\n private chatList: Chat[] = [];\n private readonly listListeners = new Set<() => void>();\n\n constructor(private readonly rt: PalbeRuntime) {\n this.kv = createDurableKV();\n this.deviceStore = new DeviceIdStore(this.kv);\n this.sigStore = new SignatureKeyStore(this.kv);\n this.groupStore = new GroupStateStorage(this.kv);\n this.kpStore = new KeyPackageStorage(this.kv);\n // The delete-for-me suppression store is durable-only (no engine) — build it\n // eagerly so loadSuppressed/saveSuppressed work without forcing MLS resolution.\n this.suppressionStore = new SuppressionStore(this.kv);\n // The self-elevation dedup store is durable-only (no engine) — build it eagerly so\n // loadElevated/saveElevated work without forcing MLS resolution (mirrors suppression).\n this.elevationStore = new MentionElevationStore(this.kv);\n // The disappearing/TTL purge store is durable-only (no engine) — build it eagerly so\n // the tombstone/purge/anchor seam works without forcing MLS resolution (T10).\n this.disappearingStore = new DisappearingStore(this.kv);\n // Wire the registry → observable chat list with the pointer-stable factory.\n this.registry.attachChatList(\n (chats) => {\n this.chatList = chats;\n this.emitList();\n },\n (group) => this.chatFor(group),\n );\n }\n\n // ── ChatBackend identity ──\n\n get selfUserId(): string {\n const u = this.rt.auth.currentUser;\n if (!u) throw new Error('not_signed_in');\n return u.id;\n }\n\n // ── Public surface (used by the facade) ──\n\n /** Self-enroll + start the delivery source (idempotent). The first chat op\n * calls this implicitly; pb.messaging.start() is the opt-in warm-at-sign-in. */\n async ensureEnrolled(): Promise<void> {\n await this.resolve();\n }\n\n /** Open the ONE direct chat with userId. INSTANT + LOCAL (no network); the\n * group materializes on the first send. Pointer-stable per peer pair. */\n directChat(peerUserId: string): Chat {\n const selfId = this.selfUserId;\n const dk = makeDirectKey(selfId, peerUserId);\n // If an active direct chat with this key already exists, return it.\n for (const chat of this.chatByRfc.values()) {\n if (chat.isDirect && chat.state === 'active') {\n // best-effort: match the active group's direct key via the registry\n const g = this.registry.all().find((x) => x.displayId === chat.id);\n if (g?.directKey === dk) return chat;\n }\n }\n const draft: ChatDraft = {\n mode: { kind: 'direct', peerUserId },\n reservedId: `draft:${dk}`,\n };\n // Cache the draft under its reserved id so two opens converge.\n const cached = this.chatByRfc.get(draft.reservedId);\n if (cached) return cached;\n const chat = new Chat({ draft, kind: 'direct', backend: this });\n this.chatByRfc.set(draft.reservedId, chat);\n // Fire-and-forget enroll so a peer's DM/Welcome can drain in.\n void this.ensureEnrolled().catch(() => {});\n return chat;\n }\n\n /** Open a multi-party group chat (LOCAL + LAZY; created on first send). */\n groupChat(members: string[] = []): Chat {\n const draft: ChatDraft = {\n mode: { kind: 'group', members },\n reservedId: `draft:group:${crypto.randomUUID()}`,\n };\n const chat = new Chat({ draft, kind: 'group', backend: this });\n this.chatByRfc.set(draft.reservedId, chat);\n void this.ensureEnrolled().catch(() => {});\n return chat;\n }\n\n /** Look up an active chat by id (grp_ display id). */\n chatById(id: string): Chat | null {\n const g = this.registry.all().find((x) => x.displayId === id);\n return g ? this.chatFor(g) : (this.chatByRfc.get(id) ?? null);\n }\n\n /** The observable chat list (active chats only). */\n get chats(): readonly Chat[] {\n void this.ensureEnrolled().catch(() => {});\n return this.chatList;\n }\n\n onChatsChange(cb: () => void): Unsubscribe {\n this.listListeners.add(cb);\n void this.ensureEnrolled().catch(() => {});\n return () => this.listListeners.delete(cb);\n }\n\n private emitList(): void {\n for (const cb of this.listListeners) cb();\n }\n\n /** Pointer-stable Chat for a group (one per rfc id). */\n private chatFor(group: MessagingGroup): Chat {\n const existing = this.chatByRfc.get(group.rfcGroupId);\n if (existing) return existing;\n const chat = new Chat({\n group,\n kind: group.directKey ? 'direct' : 'group',\n backend: this,\n });\n this.chatByRfc.set(group.rfcGroupId, chat);\n return chat;\n }\n\n // ── Resolution (build-once) ──\n\n private resolve(): Promise<Resolved> {\n if (this.resolved) return Promise.resolve(this.resolved);\n if (this.resolvePromise) return this.resolvePromise;\n this.resolvePromise = this.buildResolved();\n return this.resolvePromise;\n }\n\n private async buildResolved(): Promise<Resolved> {\n const userId = this.selfUserId;\n // Resolve (or mint + persist) the device id BEFORE building the engine — the\n // engine's MLS credential identity IS the device id (SP-1.6).\n const deviceId = (await this.deviceStore.load(userId)) ?? mintDeviceId();\n\n const engine = await MlsEngine.create({\n userId,\n deviceId,\n groupStore: this.groupStore,\n kpStore: this.kpStore,\n sigStore: this.sigStore,\n });\n\n const messageStore = new MessageStore(this.kv);\n const catalog = new GroupCatalog(this.kv);\n const hub = new MessageHub();\n const groups = new GroupMessaging(this.rt, engine, userId, deviceId, messageStore);\n const source = new MessageDeliverySource(\n this.rt,\n engine,\n hub,\n this.registry,\n messageStore,\n deviceId,\n userId,\n );\n\n // Enroll this device (idempotent — persists the id, publishes key packages).\n await enrollDevice(this.rt, engine, this.deviceStore, {});\n\n // Hydrate the registry from the durable catalog BEFORE the first pump.\n await this.registry.attachCatalog(catalog);\n\n const resolved: Resolved = {\n engine,\n groups,\n source,\n hub,\n messageStore,\n catalog,\n registry: this.registry,\n deviceId,\n userId,\n };\n this.resolved = resolved;\n\n // Start the live delivery source (subscribe wake + initial drain).\n await source.start();\n return resolved;\n }\n\n // ── ChatBackend impl ──\n\n async materialize(draft: ChatDraft): Promise<MessagingGroup> {\n const r = await this.resolve();\n let group: MessagingGroup;\n if (draft.mode.kind === 'direct') {\n group = await r.groups.getOrCreateDirect(draft.mode.peerUserId);\n } else {\n group = await r.groups.createGroup();\n for (const userId of draft.mode.members) {\n await r.groups.addMember(group, userId);\n }\n }\n void r.catalog.upsert({\n displayId: group.displayId,\n rfcGroupId: group.rfcGroupId,\n currentEpoch: group.currentEpoch,\n ownerUserId: group.ownerUserId,\n directKey: group.directKey,\n name: group.name,\n });\n return group;\n }\n\n async sendText(\n group: MessagingGroup,\n text: string,\n replyTo?: ReplyRef | null,\n bodyRanges?: MentionRange[] | null,\n expiry?: ExpirySpec | null,\n ): Promise<{ receipt: SentReceipt; clientMsgId: string }> {\n const r = await this.resolve();\n // Disappearing T10: forward the per-message/default-stamped `expiry` to the wire.\n // Dropping it here (the facade declared only 4 params) silently stripped the\n // per-message TTL from the ciphertext, so a recipient decoded a plain text and\n // never armed — invisible on the sender's own screen (it arms from effectiveExpiry\n // directly) and missed by unit tests that drove groups.sendText directly. Caught by\n // the live 2-browser A↔B gate; locked by the coordinator-forwarding regression test.\n return r.groups.sendText(group, text, replyTo, bodyRanges, expiry);\n }\n\n async sendReaction(\n group: MessagingGroup,\n args: { clientMsgId: string; targetClientMsgId: string; emoji: string; op: 'add' | 'remove' },\n ): Promise<{ receipt: SentReceipt; clientMsgId: string }> {\n const r = await this.resolve();\n return r.groups.sendReaction(group, args);\n }\n\n async sendEdit(\n group: MessagingGroup,\n args: {\n clientMsgId: string;\n targetClientMsgId: string;\n newText: string;\n bodyRanges?: MentionRange[] | null;\n },\n ): Promise<{ receipt: SentReceipt; clientMsgId: string }> {\n const r = await this.resolve();\n return r.groups.sendEdit(group, args);\n }\n\n async sendDelete(\n group: MessagingGroup,\n args: { clientMsgId: string; targetClientMsgId: string },\n ): Promise<{ receipt: SentReceipt; clientMsgId: string }> {\n const r = await this.resolve();\n return r.groups.sendDelete(group, args);\n }\n\n async sendTimerSet(\n group: MessagingGroup,\n args: { clientMsgId: string; ttlSeconds: number | null; start: 'send' | 'read' },\n ): Promise<{ receipt: SentReceipt; clientMsgId: string }> {\n const r = await this.resolve();\n return r.groups.sendTimerSet(group, args);\n }\n\n /** Load this chat's persisted delete-for-me suppression keys (durable-only). */\n loadSuppressed(group: MessagingGroup): Promise<string[]> {\n return this.suppressionStore.load(group.rfcGroupId);\n }\n\n /** Persist this chat's delete-for-me suppression keys (durable-only, no wire). */\n saveSuppressed(group: MessagingGroup, keys: string[]): Promise<void> {\n return this.suppressionStore.save(group.rfcGroupId, keys);\n }\n\n /** Load this chat's persisted self-elevation dedup keys (durable-only). */\n loadElevated(group: MessagingGroup): Promise<string[]> {\n return this.elevationStore.load(group.rfcGroupId);\n }\n\n /** Persist this chat's self-elevation dedup keys (durable-only, no wire). */\n saveElevated(group: MessagingGroup, keys: string[]): Promise<void> {\n return this.elevationStore.save(group.rfcGroupId, keys);\n }\n\n // ── Disappearing / TTL seam (durable-only, no wire) ──\n\n tombstonedSeqs(group: MessagingGroup): Promise<Set<number>> {\n return this.disappearingStore.tombstonedSeqs(group.rfcGroupId);\n }\n\n purgedClientMsgIds(group: MessagingGroup): Promise<Set<string>> {\n return this.disappearingStore.purgedClientMsgIds(group.rfcGroupId);\n }\n\n anchor(group: MessagingGroup, clientMsgId: string): Promise<AnchorTriple | null> {\n return this.disappearingStore.anchor(group.rfcGroupId, clientMsgId);\n }\n\n writeAnchorOnce(group: MessagingGroup, clientMsgId: string, a: AnchorTriple): Promise<void> {\n return this.disappearingStore.writeAnchorOnce(group.rfcGroupId, clientMsgId, a);\n }\n\n tombstone(group: MessagingGroup, serverSeq: number, clientMsgId: string): Promise<void> {\n return this.disappearingStore.tombstone(group.rfcGroupId, serverSeq, clientMsgId);\n }\n\n async history(group: MessagingGroup, limit: number, before?: number): Promise<ChatMessage[]> {\n const r = await this.resolve();\n const rows = await r.messageStore.history(group.rfcGroupId, limit, before);\n // Disappearing T10: the REAL cold-launch path passes the durable per-chat purge state\n // so projectHistory excludes tombstoned seqs + resolves purged orphans + arms the chat\n // default — the SAME pure function the reload-parity test drives (the M1 shared helper).\n const tombstoned = await this.disappearingStore.tombstonedSeqs(group.rfcGroupId);\n const purged = await this.disappearingStore.purgedClientMsgIds(group.rfcGroupId);\n return projectHistory(group.displayId, rows, this.selfUserId, undefined, tombstoned, purged);\n }\n\n async members(group: MessagingGroup): Promise<ChatMember[]> {\n const r = await this.resolve();\n try {\n const rows = await r.groups.listMembers(group.displayId);\n // Dedup device rows → users.\n const byUser = new Map<string, ChatMember>();\n for (const row of rows) {\n if (!byUser.has(row.user_id)) {\n byUser.set(row.user_id, {\n id: row.user_id,\n userId: row.user_id,\n displayName: null,\n role: row.role,\n isSelf: row.user_id === this.selfUserId,\n });\n }\n }\n return [...byUser.values()];\n } catch {\n return [];\n }\n }\n\n async addMember(group: MessagingGroup, userId: string): Promise<void> {\n const r = await this.resolve();\n await r.groups.addMember(group, userId);\n }\n\n async removeMember(group: MessagingGroup, userId: string): Promise<void> {\n const r = await this.resolve();\n await r.groups.removeMember(group, userId);\n }\n\n async leave(group: MessagingGroup): Promise<void> {\n const r = await this.resolve();\n await r.groups.leaveGroup(group);\n r.registry.remove(group.rfcGroupId);\n }\n\n registerActive(group: MessagingGroup): void {\n this.registry.register(group);\n }\n\n setTyping(group: MessagingGroup, isTyping: boolean): void {\n void this.resolve().then((r) => r.source.setTyping(group, isTyping));\n }\n\n async markRead(group: MessagingGroup, upToServerSeq: number): Promise<void> {\n const r = await this.resolve();\n await r.source.markRead(group, upToServerSeq);\n }\n\n // ── Server-metadata cluster: notify scope (mute) + unread ──\n\n async setNotifyScope(group: MessagingGroup, scope: NotifyScope): Promise<NotifyScope> {\n const r = await this.resolve();\n return r.source.setNotifyScope(group, scope);\n }\n\n async getNotifyScope(group: MessagingGroup): Promise<NotifyScope> {\n const r = await this.resolve();\n return r.source.getNotifyScope(group);\n }\n\n async unread(group: MessagingGroup): Promise<UnreadView> {\n const r = await this.resolve();\n return r.source.unread(group);\n }\n\n subscribeLive(group: MessagingGroup, chat: Chat): Unsubscribe {\n let offMsg: (() => void) | null = null;\n let offConv: (() => void) | null = null;\n void this.resolve().then((r) => {\n offMsg = r.hub.onMessage(group.displayId, (m: IncomingMessage) => {\n void chat.ingestLive(m);\n });\n r.source.observeConversation(group);\n offConv = r.hub.onConv(group.displayId, (e) => chat.applyConv(e.event, e.payload));\n });\n return () => {\n offMsg?.();\n offConv?.();\n };\n }\n\n async userIdForDevice(group: MessagingGroup, deviceId: string): Promise<string | null> {\n const r = await this.resolve();\n try {\n const rows = await r.groups.listMembers(group.displayId);\n return rows.find((m) => m.device_id === deviceId)?.user_id ?? null;\n } catch {\n return null;\n }\n }\n\n // ── Trust (chat-scoped) ──\n\n /** List a user's devices (used by safety-number computation). */\n async listUserDevices(userId: string): Promise<string[]> {\n const res = await listDevices(this.rt, userId);\n return res.devices.map((d) => d.device_id);\n }\n}\n\n/**\n * Project a page of stored rows → surfaced `ChatMessage[]`, page-local-folding\n * reaction rows into their targets' tallies (the web mirror of the iOS\n * `MessagingCoordinator.history()` reaction-vs-bubble loop).\n *\n * A `'reaction'`/`'edit'`/`'delete'` row NEVER becomes a bubble — each folds into a\n * page-local `ReactionFold`/`EditFold`/`DeleteFold` and is skipped. Every surfaced\n * message carries its tally + the winning edit text, UNLESS a valid tombstone folded\n * onto it — then it renders the neutral \"deleted\" descriptor with reactions/reply/edit\n * HIDDEN (the render precedence: delete DOMINATES edit). Backward-compat: old rows (no\n * `envelopeType`) project as ordinary messages. Actor attribution at the store layer:\n * outgoing rows = self; incoming rows use `resolveActor(senderDeviceId)` when provided\n * (the live Chat fold, which resolves device→user, remains authoritative cross-page).\n *\n * Exported so the reaction/edit/delete reload-parity boundary is testable with a REAL\n * `MessageStore` (real persist → unseal → project → re-fold), not a bypass — and so the\n * REAL cold-launch path folds deletes (the iOS-review CRITICAL `.delete` branch lives\n * HERE, the same function `coordinator.history()` calls, so a persisted tombstone can\n * never leak as a blank bubble).\n */\nexport function projectHistory(\n displayId: string,\n rows: StoredMessage[],\n selfUserId: string,\n resolveActor?: (senderDeviceId: string | null) => string | null,\n tombstonedSeqs: Set<number> = new Set(),\n purgedClientMsgIds: Set<string> = new Set(),\n): ChatMessage[] {\n // Disappearing T10 (the M1-trap cold-launch parity): drop every row whose serverSeq is\n // in `tombstonedSeqs` BEFORE any fold sees it — a TTL-purged message is NEVER\n // re-projected (the transcript-reader half of the live `ingestLive` redelivery-drop).\n const visible = rows.filter((s) => !tombstonedSeqs.has(s.serverSeq));\n\n // Page-local fold: ingest every reaction row before projecting, so a surfaced\n // message in the same page renders its tally on cold launch.\n const fold = new ReactionFold();\n for (const s of visible) {\n if (s.envelopeType !== 'reaction' || !s.reaction) continue;\n const actor =\n s.direction === 'outgoing' ? selfUserId : (resolveActor?.(s.senderDeviceId ?? null) ?? null);\n if (actor === null) continue; // unresolvable peer actor — leave to the live fold\n fold.ingest({\n targetClientMsgId: s.reaction.targetClientMsgId,\n actorUserId: actor,\n emoji: s.reaction.emoji,\n op: s.reaction.op,\n epoch: s.epoch,\n serverSeq: s.serverSeq,\n eventClientMsgId: s.clientMsgId ?? `${s.id}`,\n });\n }\n\n // Page-local edit fold: an `'edit'` row NEVER becomes a bubble — it folds the\n // latest author edit's text onto its target in place. The author-gate resolves\n // each bubble's author from a page map (outgoing = self; incoming = resolveActor,\n // else unresolved → unknown so the edit HOLDs). A target in `purgedClientMsgIds`\n // resolves to 'purged' so a late edit DROPs (never resurrects). After the batch,\n // reevaluateHeld handles an out-of-order page (an edit row before its target).\n const editFold = new EditFold();\n // Page-local delete fold: a `'delete'` row NEVER becomes a bubble — it folds a\n // delete-for-everyone tombstone onto its target (author-gated). After the batch\n // a tombstoned target is mapped to the neutral \"deleted\" descriptor with\n // reactions/reply/edit HIDDEN (delete DOMINATES edit). This is the iOS-review\n // CRITICAL branch: the REAL projection MUST fold deletes, else a persisted\n // tombstone leaks as a blank bubble + never scrubs on cold launch. The reload\n // test drives THIS exact function (the same one coordinator.history() calls), so\n // the boundary is genuinely locked, not a test-local re-implementation.\n const deleteFold = new DeleteFold();\n // Disappearing T10 (the M1-trap cold-launch chat-default arming): a `'timer_set'` row is\n // NEVER a bubble — fold it into a page-local TimerFold so a bubble with no per-message\n // expiry inherits the chat default active AS OF its order. Because we iterate\n // oldest-first and fold each timer_set BEFORE reaching a later bubble, `active()` at a\n // bubble reflects only timer_set events at a LOWER (epoch, serverSeq) order → FORWARD-ONLY\n // (a message ordered BEFORE the governing timer_set inherits nothing).\n const pageTimerFold = new TimerFold();\n const authorByClientMsgId = new Map<string, string>();\n for (const s of visible) {\n // A non-bubble row (reaction/edit/delete/timer_set) is not itself a target — only the\n // surfaced text/media rows author-gate the folds.\n if (\n s.envelopeType === 'reaction' ||\n s.envelopeType === 'edit' ||\n s.envelopeType === 'delete' ||\n s.envelopeType === 'timer_set'\n )\n continue;\n const cid = s.clientMsgId ?? '';\n if (!cid) continue;\n const author =\n s.direction === 'outgoing' ? selfUserId : resolveActor?.(s.senderDeviceId ?? null);\n if (author != null) authorByClientMsgId.set(cid, author);\n }\n // Disappearing T10: the orphan-aware resolver — a target whose clientMsgId is in\n // `purgedClientMsgIds` resolves to 'purged' (a late edit DROPs / a late delete no-ops),\n // else 'author' from the page map, else 'unknown' (HOLD). Same shape the live\n // `Chat.authorOfTarget` uses, so the cold-launch fold and the live fold never diverge.\n const authorOfTarget = (cid: string): AuthorResolution => {\n if (purgedClientMsgIds.has(cid)) return { kind: 'purged' };\n const a = authorByClientMsgId.get(cid);\n return a !== undefined ? { kind: 'author', userId: a } : { kind: 'unknown' };\n };\n for (const s of visible) {\n if (s.envelopeType !== 'edit' || !s.edit) continue;\n const editor =\n s.direction === 'outgoing' ? selfUserId : (resolveActor?.(s.senderDeviceId ?? null) ?? null);\n editFold.ingest(\n {\n targetClientMsgId: s.edit.targetClientMsgId,\n editorUserId: editor,\n newText: s.edit.newText,\n epoch: s.epoch,\n serverSeq: s.serverSeq,\n eventClientMsgId: s.clientMsgId ?? `${s.id}`,\n // Mentions T6: the edit's replacement ranges ride the fold so the WINNING\n // edit's ranges drive the edited message's mentions on cold launch.\n bodyRanges: s.edit.bodyRanges ?? null,\n },\n authorOfTarget,\n );\n }\n editFold.reevaluateHeld(authorOfTarget);\n // Fold every delete row through the same author-gate (actor = resolved sender;\n // outgoing = self). A pending/held tombstone whose target sits later in the page\n // is re-evaluated by the post-loop sweep below.\n for (const s of visible) {\n if (s.envelopeType !== 'delete' || !s.delete) continue;\n const actor =\n s.direction === 'outgoing' ? selfUserId : (resolveActor?.(s.senderDeviceId ?? null) ?? null);\n deleteFold.ingest(\n {\n targetClientMsgId: s.delete.targetClientMsgId,\n actorUserId: actor,\n epoch: s.epoch,\n serverSeq: s.serverSeq,\n eventClientMsgId: s.clientMsgId ?? `${s.id}`,\n },\n authorOfTarget,\n );\n }\n // Sweep: re-evaluate any pending/held tombstone now that every target's author\n // is known (an out-of-order tombstone before its target in the same page).\n for (const [cid] of authorByClientMsgId) deleteFold.reevaluatePending(cid, authorOfTarget(cid));\n\n // Reply lookup over the surfaced (non-fold) rows.\n const lookup = new Map<string, { text: string; senderUserId: string }>();\n for (const s of visible) {\n if (\n s.envelopeType === 'reaction' ||\n s.envelopeType === 'edit' ||\n s.envelopeType === 'delete' ||\n s.envelopeType === 'timer_set'\n )\n continue;\n const cid = s.clientMsgId ?? '';\n if (cid && s.text !== null) {\n // selfUserId for outgoing rows; '' for incoming (peer user unresolved at\n // history time — the empty string still yields state:'resolved').\n const senderUserId = s.direction === 'outgoing' ? selfUserId : '';\n lookup.set(cid, { text: s.text, senderUserId });\n }\n }\n\n const out: ChatMessage[] = [];\n // The page-local timer fold is consumed INLINE as we iterate oldest-first (so a bubble\n // sees only the chat default at a LOWER order — forward-only). A timer_set row never\n // becomes a bubble; it folds + is skipped.\n for (const s of visible) {\n if (s.envelopeType === 'timer_set') {\n // Fold the chat default (no bubble) — actor = the resolved sender USER; an\n // unresolvable actor drops it (no spoofable timer). own-send (outgoing) → self.\n const actor =\n s.direction === 'outgoing'\n ? selfUserId\n : (resolveActor?.(s.senderDeviceId ?? null) ?? null);\n if (actor !== null && s.timer) {\n pageTimerFold.ingest({\n ttlSeconds: s.timer.ttlSeconds,\n start: s.timer.start,\n actorUserId: actor,\n epoch: s.epoch,\n serverSeq: s.serverSeq,\n eventClientMsgId: s.clientMsgId ?? `${s.id}`,\n });\n }\n continue; // never projected as a row\n }\n if (s.envelopeType === 'reaction' || s.envelopeType === 'edit' || s.envelopeType === 'delete')\n continue; // folded, never a bubble\n const clientMsgId = s.clientMsgId ?? '';\n // RENDER PRECEDENCE (history side; suppression is applied later by the Chat):\n // (1) tombstoned → the neutral \"deleted\" descriptor, HIDE reactions/reply/edit\n // (delete DOMINATES edit — short-circuit the overlay); (2) else edit overlay +\n // reactions + reply. Identical to the live Chat composition so a cold launch\n // and a live delete render the same.\n const isDeleted = clientMsgId ? deleteFold.isTombstoned(clientMsgId) : false;\n if (isDeleted) {\n out.push({\n id: `${displayId}#${s.serverSeq}`,\n kind: 'text',\n direction: s.direction,\n senderUserId: s.direction === 'outgoing' ? selfUserId : null,\n text: DELETED_DESCRIPTOR,\n serverSeq: s.serverSeq,\n sentAt: new Date(s.at),\n clientMsgId,\n replyTo: null,\n reactions: {},\n edited: false,\n isDeleted: true,\n // Mentions T6: delete DOMINATES mentions too — a tombstoned message has none.\n mentions: [],\n // delete DOMINATES disappearing too — a tombstoned message carries no deadline.\n expiresAt: null,\n });\n continue;\n }\n // Disappearing T10 (cold-launch arming): the message's EFFECTIVE TTL = its OWN\n // per-message `expiry` if present, ELSE the chat default active AS OF this message's\n // order (forward-only — `pageTimerFold.active()` reflects only timer_set events at a\n // LOWER (epoch, serverSeq) order because we fold them inline oldest-first). own-expiry\n // WINS. The deadline is derived purely from the row's send time `s.at` + the TTL — a\n // monotonic-derived local value, never a wire field; the Chat re-arms the durable\n // write-once anchor + the advisory purge timer off it on merge.\n let expiresAt: Date | null = null;\n if (s.expiry) {\n expiresAt = new Date(s.at + s.expiry.ttlSeconds * 1000);\n } else {\n const active = pageTimerFold.active();\n if (active && active.ttlSeconds !== null) {\n expiresAt = new Date(s.at + active.ttlSeconds * 1000);\n }\n }\n let replyTo: ResolvedReply | null = null;\n if (s.replyTo) {\n const ref: ReplyRef = {\n v: 1,\n client_msg_id: s.replyTo.clientMsgId,\n preview: {\n kind: s.replyTo.previewKind,\n author_user_id: s.replyTo.previewAuthorUserId ?? '',\n body: s.replyTo.previewBody ?? undefined,\n body_truncated: false,\n },\n };\n replyTo = resolveReply(ref, (id) => lookup.get(id) ?? null);\n }\n // Overlay the winning edit's text + flag onto the row in place: the ORIGINAL\n // decrypted text stays on the persisted row; the projection computes\n // `text = edit ?? original` + `edited` so a cold-launched screen renders the\n // edited text without a new bubble (mirrors iOS T3 history projection).\n const editText = clientMsgId ? editFold.text(clientMsgId) : null;\n const edited = clientMsgId ? editFold.isEdited(clientMsgId) : false;\n const text = editText ?? s.text;\n // Mentions T6: NORMALIZE the row's raw body_ranges against the SURFACED text → spans\n // with a null displayName (name resolution is LIVE — the Chat re-resolves on merge,\n // the projection has no roster). When an edit WON, the surfaced mentions come from the\n // WINNING edit's replacement ranges (against the edited text); else the bubble's own.\n const rawRanges = editText !== null ? editFold.bodyRanges(clientMsgId) : s.bodyRanges;\n const mentions = normalizeMentionsNullNames(rawRanges, text);\n out.push({\n id: `${displayId}#${s.serverSeq}`,\n kind: s.text != null ? 'text' : 'system',\n direction: s.direction,\n senderUserId: s.direction === 'outgoing' ? selfUserId : null,\n text,\n serverSeq: s.serverSeq,\n sentAt: new Date(s.at),\n clientMsgId,\n replyTo,\n reactions: clientMsgId ? fold.tally(clientMsgId) : {},\n edited,\n isDeleted: false,\n mentions,\n expiresAt,\n });\n }\n return out;\n}\n\n/** NORMALIZE raw mention ranges against `text` → `ResolvedMention[]` with `displayName:\n * null` (the history projection has no roster — names are resolved LIVE by the Chat on\n * merge). Returns `[]` when there is no text or no surviving range. */\nfunction normalizeMentionsNullNames(\n raw: MentionRange[] | null | undefined,\n text: string | null,\n): ResolvedMention[] {\n if (text === null || !raw || raw.length === 0) return [];\n return normalizeMentionRangesUtf16(raw, text).map((r) => ({\n start: r.start,\n length: r.length,\n mentionedUserId: r.mentionedUserId,\n displayName: null,\n }));\n}\n","// `pb.messaging` — the chat-centric E2E-encrypted messaging surface (Web-MLS\n// Phase 2). The web mirror of the iOS `pb.messaging`: the dev's mental model is\n// a CHAT (a DM or a group), so the top-level noun is `Chat`, bundling its own\n// messages + typing + presence + members.\n//\n// const dm = pb.messaging.directChat(peerUserId); // INSTANT, LOCAL, no network\n// await dm.send('hi'); // first send MATERIALIZES it\n// const off = pb.messaging.onChatsChange(() => render(pb.messaging.chats));\n//\n// The MLS protocol underneath (keydir enroll/claim, group commits, queue pull)\n// is INTERNAL — the first chat op self-enrolls. `messaging` is a reserved\n// namespace (no backend endpoint may be named `messaging`).\n\nimport type { PalbeRuntime } from '../runtime.js';\nimport type { Chat } from './chat.js';\nimport { MessagingCoordinator } from './coordinator.js';\nimport type { Unsubscribe } from './types.js';\n\nexport class PalbeMessaging {\n private readonly coordinator: MessagingCoordinator;\n\n constructor(rt: PalbeRuntime) {\n this.coordinator = new MessagingCoordinator(rt);\n }\n\n /**\n * Warm messaging at sign-in (optional). Enrolls this device + starts the live\n * delivery source so incoming DMs/Welcomes drain before the chat list opens.\n * Never REQUIRED — any chat op self-enrolls. Idempotent.\n */\n async start(): Promise<void> {\n await this.coordinator.ensureEnrolled();\n }\n\n /**\n * Open the ONE direct chat with `userId`. INSTANT + LOCAL — no network, nothing\n * created yet. Returns a draft `Chat` to show immediately; the server group +\n * MLS material materialize LAZILY on the first `chat.send`. Idempotent + stable:\n * the same two users always resolve to the SAME Chat.\n */\n directChat(userId: string): Chat {\n return this.coordinator.directChat(userId);\n }\n\n /**\n * Open a multi-party group chat. LOCAL + LAZY like `directChat`: pick the\n * members, get a draft Chat, the group is created on the FIRST `chat.send`.\n */\n groupChat(opts: { members?: string[] } = {}): Chat {\n return this.coordinator.groupChat(opts.members ?? []);\n }\n\n /** Look up a chat by id (e.g. from a push payload / deep link). null if unknown. */\n chat(id: string): Chat | null {\n return this.coordinator.chatById(id);\n }\n\n /** The observable chat list (DMs + groups, active only). Read it after an\n * `onChatsChange` subscription to render the inbox. */\n get chats(): readonly Chat[] {\n return this.coordinator.chats;\n }\n\n /** Subscribe to chat-list changes (a chat added/removed/hydrated). */\n onChatsChange(cb: () => void): Unsubscribe {\n return this.coordinator.onChatsChange(cb);\n }\n}\n","// Web cold-start measurement (§2.8: app-start). The web equivalent of iOS's\n// prewarm-discard (AppStartTracker.measure): iOS may PREWARM a process, so it\n// must discard those launches; the web has no prewarm, so a browser load is\n// ALWAYS a real cold start — `measureAppStart` therefore measures whenever the\n// First Contentful Paint timestamp is available, returning `null` only when FCP\n// is missing (no paint yet / unsupported) or the delta is non-positive (clock\n// skew). `value` is milliseconds from navigation start to FCP.\n//\n// PURE by design (mirrors iOS): the FCP/navigation timestamps come in as\n// parameters so the measurement is unit-testable in isolation. The\n// PerformanceObserver wiring (reading `first-contentful-paint` + navigation\n// startTime, firing once) lives in `runtime.ts`'s browser branch, which calls\n// `rt.perf.record(measureAppStart(...))`.\n//\n// The `app_start` trace_type + `cold_start` name match the iOS emitter exactly,\n// so a row from either platform tags identically server-side.\n\nimport type { PerfItem } from './perf-wire.js';\n\n/** First Contentful Paint relative to navigation start (the two `PerformanceEntry`\n * timestamps the browser branch reads). Both are `DOMHighResTimeStamp` ms. */\nexport interface AppStartTimings {\n /** Navigation start time (a `PerformanceNavigationTiming.startTime`, usually 0). */\n startTime: number;\n /** First Contentful Paint `startTime`; `0` when no paint has been recorded. */\n fcp: number;\n}\n\n/**\n * Build a `cold_start` `app_start` PerfItem from the navigation→FCP timings, or\n * `null` when FCP is unavailable (`fcp <= 0`) or the delta is non-positive.\n * `value` is `fcp - startTime` in milliseconds.\n */\nexport function measureAppStart(nav: AppStartTimings): PerfItem | null {\n if (!(nav.fcp > 0)) return null;\n const value = nav.fcp - nav.startTime;\n if (value <= 0) return null;\n return {\n row_id: crypto.randomUUID(),\n trace_type: 'app_start',\n name: 'cold_start',\n value,\n timestamp: Date.now(),\n };\n}\n","// Remote perf sampling (the SDK half). The server owns the sample rate via\n// `GET /v1/analytics/perf/config` `{ sample_pct, flush_at, flush_interval_ms }`\n// (modules/analytics, Faz-2 B1). The SDK fetches it (ETag-cached) and applies\n// `sample_pct` as a CLIENT-SIDE ceiling so it only uploads ~sample_pct% of\n// distinct rows — saving bandwidth — while the server STILL re-samples\n// authoritatively at ingest (defense-in-depth, invariant 3). The SDK never\n// raises its own rate above the fetched value.\n//\n// Cross-boundary wire contract (mutation-evident): `perfSampleBucket` below is a\n// byte-for-byte mirror of the Go `service.SampleDecision`\n// (modules/analytics/internal/service/perf_sampling.go) — FNV-1a-32 over the\n// UTF-8 bytes of `row_id`, mod 100, sampled iff the bucket is strictly below\n// `sample_pct`. Because the decision is DETERMINISTIC per row_id, the client and\n// the server pick the SAME rows: there is no client/server drift, and a row\n// redelivered after an offline-queue reconnect samples identically (idempotency\n// under ReplacingMergeTree). Changing this algorithm here without changing it\n// there breaks the contract — that is why both sides golden-test it.\n\nimport type { PalbeRuntime } from '../runtime.js';\n\n/** The config endpoint the SDK consults for the server-controlled sample rate. */\nconst PERF_CONFIG_PATH = '/v1/analytics/perf/config';\n\n/** FNV-1a 32-bit constants (RFC / Go `hash/fnv`). */\nconst FNV_OFFSET_BASIS_32 = 0x811c9dc5;\nconst FNV_PRIME_32 = 0x01000193;\n\nconst utf8 =\n typeof TextEncoder !== 'undefined'\n ? new TextEncoder()\n : { encode: (s: string): Uint8Array => Uint8Array.from(Buffer.from(s, 'utf-8')) };\n\n/**\n * Deterministic sampling bucket (0..99) for a `row_id`, identical to the Go\n * `service.SampleDecision`: FNV-1a-32 over the row_id's UTF-8 bytes, mod 100.\n * An item is sampled-in iff `perfSampleBucket(row_id) < sample_pct`.\n *\n * Implemented in 32-bit unsigned arithmetic via `Math.imul` (the multiply must\n * wrap exactly like Go's `uint32`); `>>> 0` keeps every intermediate unsigned.\n */\nexport function perfSampleBucket(rowId: string): number {\n let hash = FNV_OFFSET_BASIS_32;\n // Iterate values (not indices) so `noUncheckedIndexedAccess` keeps each byte a\n // definite `number`.\n for (const byte of utf8.encode(rowId)) {\n hash ^= byte;\n hash = Math.imul(hash, FNV_PRIME_32);\n }\n // `>>> 0` coerces to the unsigned 32-bit value before the mod, matching\n // Go's `h.Sum32() % 100` on a `uint32`.\n return (hash >>> 0) % 100;\n}\n\n/** The server config body. snake_case = wire shape (see B1's `perfConfigResponse`). */\ninterface PerfConfig {\n sample_pct: number;\n flush_at: number;\n flush_interval_ms: number;\n}\n\nfunction isPerfConfig(value: unknown): value is PerfConfig {\n if (typeof value !== 'object' || value === null) return false;\n const o = value as Record<string, unknown>;\n return typeof o.sample_pct === 'number';\n}\n\n/** The shape `rt.http.request` resolves to — `{ data, error, status, headers }`. */\ninterface RawResponse {\n data: unknown;\n error: { status: number } | null;\n status?: number;\n headers?: Record<string, string>;\n}\n\nfunction isRawResponse(value: unknown): value is RawResponse {\n return typeof value === 'object' && value !== null && 'data' in value;\n}\n\nfunction headerValue(\n headers: Record<string, string> | undefined,\n name: string,\n): string | undefined {\n if (!headers) return undefined;\n const lower = name.toLowerCase();\n for (const [k, v] of Object.entries(headers)) {\n if (k.toLowerCase() === lower) return v;\n }\n return undefined;\n}\n\n/**\n * Fetches the server-controlled perf config and applies its `sample_pct` to the\n * facade's client-side sampling. ETag-aware: it remembers the last ETag and\n * sends `If-None-Match`, so a steady SDK gets a 304 and skips re-applying an\n * unchanged config.\n *\n * The fetch goes through `rt.http.request` directly (NOT `palbeRequest`'s\n * unwrap) so a 304 — which has no body — is observable instead of throwing, and\n * because the config GET is one of the `/v1/analytics/*` paths the network-trace\n * choke point self-excludes (no self-measurement loop).\n */\nexport class PerfConfigClient {\n private etag: string | undefined;\n\n async fetchConfig(rt: PalbeRuntime, perf: { setSamplePct(pct: number): void }): Promise<void> {\n const headers: Record<string, string> = {};\n if (this.etag) headers['If-None-Match'] = this.etag;\n try {\n const res: unknown = await rt.http.request('GET', PERF_CONFIG_PATH, { headers });\n if (!isRawResponse(res)) return;\n\n // 304 Not Modified: config unchanged — keep the applied pct and the cached\n // ETag. Mutation-evident: if we re-applied here, a 304 would clobber pct.\n if (res.status === 304 || (res.error && res.error.status === 304)) return;\n if (res.error) return; // any other error: keep the current (default 100) pct\n\n const newEtag = headerValue(res.headers, 'etag');\n if (newEtag) this.etag = newEtag;\n\n if (isPerfConfig(res.data)) {\n perf.setSamplePct(res.data.sample_pct);\n }\n } catch {\n // Network/transport failure — fire-and-forget: the SDK keeps its current\n // sample rate (defaults to 100 before the first successful fetch).\n }\n }\n}\n","// Opt-in app-level network capture. By default the SDK only traces ITS OWN\n// fetches (the `request.ts` choke point). An app that wants every `fetch(...)`\n// it makes timed as a `network` perf item can opt in with `installFetchSwizzle`\n// — it wraps the global `fetch`, records a redacted trace per call, and hands\n// back an `uninstall` that restores the original `fetch`. It is OFF by default\n// (the embedder calls this explicitly) because swizzling a global is a\n// page-wide side effect.\n//\n// Self-exclusion: the wrapper skips the `/v1/analytics/*` ingest paths, exactly\n// like the `request.ts` network trace — otherwise the perf-flush POST (which an\n// app could route through the global `fetch`) would emit another `network` item\n// that the next flush ships, an item-amplifying self-measurement loop. This is\n// the SAME prefix guard the SDK uses internally (mutation-evident: drop it and\n// the self-loop test goes red).\n\nimport { redactUrl } from './url-redactor.js';\n\n/** The minimal perf surface the swizzle needs — `recordNetwork`, as on PalbePerf. */\ninterface NetworkPerfSink {\n recordNetwork(\n method: string,\n url: string,\n status: number,\n durationMs: number,\n requestId?: string,\n ): void;\n}\n\n/** The same ingest prefix `request.ts` self-excludes — keep them identical so\n * the wrapper never traces a perf/analytics POST routed through global fetch. */\nconst PERF_EXCLUDED_PREFIX = '/v1/analytics/';\n\n/** Monotonic clock — `performance.now()` when present, else `Date.now()`. */\nfunction nowMs(): number {\n return typeof performance !== 'undefined' && typeof performance.now === 'function'\n ? performance.now()\n : Date.now();\n}\n\n/** Pull the request URL + method out of the `fetch` argument tuple without a\n * cast: `fetch(input, init)` where input is a string, URL, or Request. */\nfunction describeRequest(\n input: RequestInfo | URL,\n init?: RequestInit,\n): { url: string; method: string } {\n let url: string;\n let method = 'GET';\n if (typeof input === 'string') {\n url = input;\n } else if (input instanceof URL) {\n url = input.href;\n } else {\n // Request object: it carries both url and method.\n url = input.url;\n method = input.method;\n }\n if (init?.method) method = init.method;\n return { url, method: method.toUpperCase() };\n}\n\n/** Resolve the path portion of a URL for the self-exclusion check (absolute or\n * relative). Returns the raw input on a parse failure so the guard still works\n * on a bare `/v1/analytics/...` relative path. */\nfunction pathOf(url: string): string {\n try {\n return new URL(url, 'http://_local').pathname;\n } catch {\n return url;\n }\n}\n\n/**\n * Wrap the global `fetch` to record a redacted `network` perf item per app-level\n * request. Returns an `uninstall` that restores the original `fetch`. No-op (and\n * a no-op uninstall) when there is no global `fetch` (some server runtimes).\n */\nexport function installFetchSwizzle(perf: NetworkPerfSink): () => void {\n if (typeof fetch !== 'function') return () => {};\n const original = fetch;\n\n const wrapped = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {\n const { url, method } = describeRequest(input, init);\n const traced = !pathOf(url).startsWith(PERF_EXCLUDED_PREFIX);\n const startedAt = traced ? nowMs() : 0;\n try {\n const res = await original(input, init);\n if (traced) perf.recordNetwork(method, redactUrl(url), res.status, nowMs() - startedAt);\n return res;\n } catch (err) {\n // A transport failure is still a completed-with-error call (status 0),\n // mirroring the `request.ts` trace; rethrow so the caller sees the error.\n if (traced) perf.recordNetwork(method, redactUrl(url), 0, nowMs() - startedAt);\n throw err;\n }\n };\n\n globalThis.fetch = wrapped as typeof fetch;\n return () => {\n globalThis.fetch = original;\n };\n}\n","// A bounded, FIFO-evicting durability buffer for perf items that failed to\n// flush. It mirrors the iOS offline queue (persist-on-fail, drain-on-reconnect):\n// when `flush()` cannot reach `/v1/analytics/perf` (offline / transport error),\n// the facade hands the failed slice here instead of dropping it; the next flush\n// (size/timer, or the `online` reconnect event) drains it oldest-first.\n//\n// Durability: in the browser the queue persists to `localStorage` under a fixed\n// key, so items survive a page reload while the device is offline. On the server\n// (no `localStorage`) it degrades to pure in-memory — the Faz-1 \"no leak into\n// RSC/route handlers\" rule holds (no timers, no I/O beyond the in-memory array).\n//\n// Bounded + counted: a device offline for days must not grow `localStorage`\n// unbounded. The queue caps at `maxItems` (default 500) and FIFO-evicts the\n// OLDEST items on overflow; every eviction increments `dropped` so the loss is\n// observable, never silent corruption.\n//\n// Idempotency: items are stored VERBATIM — their original `row_id` (client\n// UUIDv7) is preserved, never re-minted. A re-delivered item after reconnect\n// therefore dedups server-side (ClickHouse ReplacingMergeTree keyed on row_id).\n\nimport type { PerfItem } from './perf-wire.js';\n\n/** localStorage key for the persisted perf offline queue (one per origin). */\nexport const PERF_QUEUE_KEY = 'palbe.perf.queue';\n\n/** Default item cap — bounds memory/disk for a long-offline device. */\nconst DEFAULT_MAX_ITEMS = 500;\n\n/** True iff this is a browser with a usable `localStorage`. Gated on `document`\n * too (the SAME \"browser\" test the facade uses): a server runtime must be PURE\n * in-memory — no `localStorage` read/write — even if a `localStorage` global is\n * polyfilled/stubbed there (the \"no I/O leak into RSC/route handlers\" rule). The\n * offline queue's durability (reload survival, reconnect drain) only has meaning\n * in the browser anyway. */\nfunction canPersist(): boolean {\n return typeof document !== 'undefined' && typeof localStorage !== 'undefined';\n}\n\n/**\n * Narrow `unknown` (parsed JSON) into a `PerfItem`. Only the wire-required\n * fields are checked; this is a defensive parse of persisted bytes that another\n * tab / a corrupt write may have left — a malformed entry is skipped, never\n * cast. Keeps the queue typed end-to-end with no `any`.\n */\nfunction isPerfItem(value: unknown): value is PerfItem {\n if (typeof value !== 'object' || value === null) return false;\n const o = value as Record<string, unknown>;\n return (\n typeof o.row_id === 'string' &&\n typeof o.trace_type === 'string' &&\n typeof o.name === 'string' &&\n typeof o.value === 'number' &&\n typeof o.timestamp === 'number'\n );\n}\n\n/** Read the persisted items, dropping any malformed entries. Never throws. */\nfunction readPersisted(): PerfItem[] {\n if (!canPersist()) return [];\n try {\n const raw = localStorage.getItem(PERF_QUEUE_KEY);\n if (!raw) return [];\n const parsed: unknown = JSON.parse(raw);\n if (!Array.isArray(parsed)) return [];\n return parsed.filter(isPerfItem);\n } catch {\n // Corrupt JSON / quota read error — degrade to empty, never break the\n // request path.\n return [];\n }\n}\n\n/**\n * Bounded FIFO perf-item queue. `enqueue` appends and evicts the oldest on\n * overflow; `drainAll` returns everything (oldest-first) and clears the store;\n * `count` is the current depth; `dropped` is the cumulative eviction counter.\n *\n * When `localStorage` is present the queue is read-through / write-through to a\n * fixed key, so a fresh instance after a reload sees the persisted items. With\n * no `localStorage` it is purely in-memory.\n */\nexport class PerfOfflineQueue {\n private items: PerfItem[];\n private _dropped = 0;\n private readonly maxItems: number;\n\n constructor(maxItems: number = DEFAULT_MAX_ITEMS) {\n this.maxItems = Math.max(1, maxItems);\n // Hydrate from the persisted store (browser) so durability survives reload.\n this.items = readPersisted();\n // A persisted store larger than the cap (e.g. a smaller cap after an\n // upgrade) is trimmed on construction, counting the eviction.\n this.trim();\n }\n\n /** Append items; FIFO-evict the oldest when over `maxItems`. */\n enqueue(items: PerfItem[]): void {\n if (items.length === 0) return;\n this.items.push(...items);\n this.trim();\n this.persist();\n }\n\n /** Return all queued items (oldest-first) and clear the queue + store. */\n drainAll(): PerfItem[] {\n if (this.items.length === 0) return [];\n const out = this.items;\n this.items = [];\n this.clearStore();\n return out;\n }\n\n /** Current queue depth. */\n get count(): number {\n return this.items.length;\n }\n\n /** Cumulative count of FIFO-evicted items (a `dropped` metric, not silent). */\n get dropped(): number {\n return this._dropped;\n }\n\n /** Drop the oldest items until at most `maxItems` remain, counting each. */\n private trim(): void {\n const overflow = this.items.length - this.maxItems;\n if (overflow > 0) {\n this.items.splice(0, overflow);\n this._dropped += overflow;\n }\n }\n\n private persist(): void {\n if (!canPersist()) return;\n try {\n localStorage.setItem(PERF_QUEUE_KEY, JSON.stringify(this.items));\n } catch {\n // Quota exceeded / SSR — non-fatal: durability degrades to in-memory.\n }\n }\n\n private clearStore(): void {\n if (!canPersist()) return;\n try {\n localStorage.removeItem(PERF_QUEUE_KEY);\n } catch {\n // ignore\n }\n }\n}\n","// Runtime-scoped PalPerf buffer + test-device flag. Kept separate from the\n// facade (PalbePerf) for the same reason AnalyticsState is split out: the\n// buffer/flag identity outlives the lazy facade, and `pb.setTestDevice` /\n// `request.ts`'s network hook need to reach the flag and the buffer without\n// forcing the facade to construct. State is intentionally dumb — no I/O, no\n// timers; the facade owns flush policy.\n\nimport type { PerfItem } from './perf-wire.js';\n\n/** Server hard cap: `POST /v1/analytics/perf` rejects > 100 items per request. */\nexport const MAX_PERF_BATCH = 100;\n\n/**\n * The mutable PalPerf state shared between the facade and the network hook.\n * `testDevice` is signalled to the server via the `X-Palbase-Test-Device: 1`\n * request header (server-authoritative tagging — the SDK only flips the bit).\n */\nexport class PerfState {\n /** Pending, un-flushed perf items (FIFO). */\n readonly buffer: PerfItem[] = [];\n /** When true, every flush carries `X-Palbase-Test-Device: 1`. */\n testDevice = false;\n\n enqueue(item: PerfItem): void {\n this.buffer.push(item);\n }\n\n /** Detach up to `MAX_PERF_BATCH` items for one POST (FIFO order preserved). */\n take(limit: number = MAX_PERF_BATCH): PerfItem[] {\n return this.buffer.splice(0, limit);\n }\n\n get size(): number {\n return this.buffer.length;\n }\n}\n","// Wire model for the PalPerf ingest contract (§8.3/§8.6). The live backend\n// (`POST /v1/analytics/perf`, modules/analytics) decodes each item with\n// snake_case json tags — `trace_type`, `request_id`, `row_id`. These property\n// names ARE the wire format: a camelCase key would silently zero-value\n// server-side. distinct_id/app_id/geo and Environment identity are\n// server-derived, so the client MUST NOT send them — they are absent from\n// `PerfItem` by design.\n\n/** The Faz-1 trace kinds plus the Faz-1.5 web-vital family. */\nexport type PerfTraceType = 'network' | 'app_start' | 'custom' | 'web_vital';\n\n/**\n * One perf measurement, shaped EXACTLY as the ingest wire expects. Keys are\n * snake_case to match the Go decode tags — do not rename to camelCase.\n */\nexport interface PerfItem {\n /** Client-generated UUIDv7 — server dedup key. */\n row_id: string;\n trace_type: PerfTraceType;\n name: string;\n /** Duration in milliseconds. */\n value: number;\n counters?: Record<string, number>;\n attrs?: Record<string, string>;\n request_id?: string;\n /** Unix milliseconds. */\n timestamp: number;\n}\n\n/** The `POST /v1/analytics/perf` request body shape (≤100 / ≤512 KB per call). */\nexport interface PerfBatch {\n items: PerfItem[];\n}\n\n/** Wraps items in the ingest request-body envelope. */\nexport function encodePerfBatch(items: PerfItem[]): PerfBatch {\n return { items };\n}\n","// `pb.perf` — the stateful PalPerf surface. Mirrors `analytics-facade.ts`\n// (same buffer + size/timer flush in the browser, immediate flush on the\n// server where there is no `document`) and the iOS `pb.perf.*` API\n// (startTrace/putAttribute/incrementMetric/stop). Transport is the SAME seam\n// as analytics event send: `palbeRequest(rt, 'POST', '/v1/analytics/perf', …)`,\n// which wraps `rt.http.request` and unwraps the `{ data, error }` envelope.\n//\n// The live ingest contract (proven E2E against modules/analytics):\n//\n// POST /v1/analytics/perf {\"items\":[PerfItem…]} → 202 {accepted,dropped}\n//\n// distinct_id / app_id / geo and Environment identity are server-derived — the\n// SDK sends raw measurements only (§8.3/§8.6). Test traffic is signalled with the\n// `X-Palbase-Test-Device: 1` header, which the server reads to tag the rows;\n// the SDK only flips the bit via `setTestDevice`.\n//\n// Ingestion is fire-and-forget: `flush()` is the only awaited surface and it\n// NEVER rejects — a failed slice is dropped with one warn per flush. `record`\n// and `recordNetwork` never throw into the caller.\n\nimport { palbeRequest } from '../request.js';\nimport type { PalbeRuntime } from '../runtime.js';\nimport { installFetchSwizzle } from './fetch-swizzle.js';\nimport { PerfOfflineQueue } from './offline-queue.js';\nimport { perfSampleBucket } from './perf-config-client.js';\nimport { MAX_PERF_BATCH, PerfState } from './perf-state.js';\nimport { encodePerfBatch, type PerfItem, type PerfTraceType } from './perf-wire.js';\n\nconst FLUSH_AT = 20;\nconst FLUSH_INTERVAL_MS = 10_000;\nconst TEST_DEVICE_HEADER = 'X-Palbase-Test-Device';\n\n/** Monotonic millisecond clock — `performance.now()` when present (browser and\n * modern Node), else `Date.now()`. Used to time custom traces; the wall clock\n * is only used for the wire `timestamp`. */\nfunction nowMs(): number {\n return typeof performance !== 'undefined' && typeof performance.now === 'function'\n ? performance.now()\n : Date.now();\n}\n\n/**\n * A running custom trace. `putAttribute`/`incrementMetric` accumulate metadata;\n * `stop()` records ONE `custom` PerfItem whose `value` is the elapsed\n * milliseconds since `startTrace`. Idempotent: a second `stop()` is a no-op.\n */\nexport class PerfTrace {\n private readonly attrs: Record<string, string> = {};\n private readonly counters: Record<string, number> = {};\n private readonly startedAt = nowMs();\n private stopped = false;\n\n constructor(\n private readonly name: string,\n private readonly onStop: (item: PerfItem) => void,\n ) {}\n\n putAttribute(key: string, value: string): void {\n this.attrs[key] = value;\n }\n\n incrementMetric(name: string, by = 1): void {\n this.counters[name] = (this.counters[name] ?? 0) + by;\n }\n\n stop(): void {\n if (this.stopped) return;\n this.stopped = true;\n const item: PerfItem = {\n row_id: crypto.randomUUID(),\n trace_type: 'custom',\n name: this.name,\n value: Math.max(0, nowMs() - this.startedAt),\n timestamp: Date.now(),\n };\n if (Object.keys(this.attrs).length > 0) item.attrs = this.attrs;\n if (Object.keys(this.counters).length > 0) item.counters = this.counters;\n this.onStop(item);\n }\n}\n\nexport class PalbePerf {\n private flushTimer: ReturnType<typeof setTimeout> | null = null;\n /** Browser → buffer + size/timer flush. Server (no document) → immediate\n * per-item flush, zero timers (nothing leaks into RSC/route handlers). */\n private readonly browser = typeof document !== 'undefined';\n /** Bound `online` handler so it can be removed on `dispose()` (no leak). */\n private readonly onOnline = (): void => {\n void this.flush();\n };\n /** Server-controlled client-side sample rate (0..100). 100 until the config\n * client fetches `/v1/analytics/perf/config` — the SDK obeys this ceiling and\n * never raises its own rate (invariant 3). `record` drops an item whose\n * deterministic `row_id` bucket is >= this pct, mirroring the server's\n * `SampleDecision` so client + server keep the SAME rows. */\n private samplePct = 100;\n\n constructor(\n private readonly rt: PalbeRuntime,\n private readonly state: PerfState = new PerfState(),\n /** Durable offline buffer for failed flushes (persist-on-fail). Browser →\n * localStorage-backed; server → in-memory. */\n private readonly queue: PerfOfflineQueue = new PerfOfflineQueue(),\n ) {\n // Drain-on-reconnect: when the browser comes back online, flush — this ships\n // whatever the offline queue persisted while the network was down. Server\n // (no `window`) wires nothing (the no-timers/no-listeners rule holds).\n if (typeof window !== 'undefined' && typeof window.addEventListener === 'function') {\n window.addEventListener('online', this.onOnline);\n }\n }\n\n /** Remove the `online` listener. Called when the runtime is replaced so the\n * handler does not outlive this facade. No-op outside the browser. */\n dispose(): void {\n if (typeof window !== 'undefined' && typeof window.removeEventListener === 'function') {\n window.removeEventListener('online', this.onOnline);\n }\n }\n\n /** Mark (or unmark) this client's traffic as test — the server tags the rows\n * when the `X-Palbase-Test-Device: 1` header rides along on flush. */\n setTestDevice(on: boolean): void {\n this.state.testDevice = on;\n }\n\n /** Apply the server-resolved client-side sample rate (0..100), clamped. Called\n * by `PerfConfigClient` after fetching `/v1/analytics/perf/config`. The SDK\n * OBEYS this value — it is a ceiling, never raised locally. */\n setSamplePct(pct: number): void {\n this.samplePct = Math.max(0, Math.min(100, Math.trunc(pct)));\n }\n\n /** Pending (un-flushed) buffer depth. Test-only visibility into the buffer so\n * the remote-sampling tests can assert how many items were sampled in. */\n get bufferSizeForTest(): number {\n return this.state.size;\n }\n\n /** Opt-in: wrap the global `fetch` so every app-level request is recorded as a\n * redacted `network` perf item (the `/v1/analytics/*` ingest paths are\n * self-excluded). OFF by default — swizzling a global is a page-wide side\n * effect. Returns an `uninstall` that restores the original `fetch`. */\n enableFetchCapture(): () => void {\n return installFetchSwizzle(this);\n }\n\n /** Start a custom trace; the returned handle records a `custom` item on\n * `.stop()`. */\n startTrace(name: string): PerfTrace {\n return new PerfTrace(name, (item) => this.record(item));\n }\n\n /** Buffer one perf item. In the browser, flush on size/timer; on the server\n * flush immediately (no timers). Never throws.\n *\n * Client-side remote sampling: an item whose deterministic `row_id` bucket is\n * NOT below the server-controlled `samplePct` is dropped before buffering —\n * the SAME FNV-1a-mod-100 decision the server applies at ingest, so the two\n * keep identical rows and the SDK saves the upload (defense-in-depth: the\n * server re-samples authoritatively). */\n record(item: PerfItem): void {\n if (perfSampleBucket(item.row_id) >= this.samplePct) return;\n this.state.enqueue(item);\n if (!this.browser) {\n void this.flush();\n return;\n }\n if (this.state.size >= FLUSH_AT) void this.flush();\n else this.startTimer();\n }\n\n /** Buffer a network trace — called by `request.ts` around `rt.http.request`\n * (the analytics ingest path is excluded by the caller to avoid recursion). */\n recordNetwork(\n method: string,\n url: string,\n status: number,\n durationMs: number,\n requestId?: string,\n ): void {\n const item: PerfItem = {\n row_id: crypto.randomUUID(),\n trace_type: 'network' satisfies PerfTraceType,\n name: `${method} ${url}`,\n value: durationMs,\n attrs: { status: String(status) },\n timestamp: Date.now(),\n };\n if (requestId) item.request_id = requestId;\n this.record(item);\n }\n\n /** Drain the offline queue (oldest-first) and the live buffer to\n * `/v1/analytics/perf` (≤100 items per request, sequential). Resolves when\n * delivery finished; NEVER rejects. A failed slice is NOT dropped — it goes\n * to the offline queue (persist-on-fail) so the next flush (size/timer or the\n * `online` reconnect event) retries it. Items keep their original `row_id`,\n * so a redelivery dedups server-side (ReplacingMergeTree). */\n async flush(): Promise<void> {\n this.cancelTimer();\n // Previously-failed items drain FIRST (oldest-first), prepended ahead of the\n // current buffer so the queue cannot starve.\n const pending = this.queue.drainAll();\n if (pending.length === 0 && this.state.size === 0) return;\n const headers = this.state.testDevice ? { [TEST_DEVICE_HEADER]: '1' } : undefined;\n const remaining = [...pending];\n while (this.state.size > 0) remaining.push(...this.state.take(MAX_PERF_BATCH));\n\n for (let i = 0; i < remaining.length; i += MAX_PERF_BATCH) {\n const slice = remaining.slice(i, i + MAX_PERF_BATCH);\n try {\n await palbeRequest<{ accepted?: number; dropped?: number }>(\n this.rt,\n 'POST',\n '/v1/analytics/perf',\n { body: encodePerfBatch(slice), headers },\n );\n } catch {\n // Offline / transport error: persist this slice for a later drain\n // instead of dropping it. Re-enqueueing a drained slice is safe — the\n // bounded queue caps growth and the row_id dedups any redelivery.\n this.queue.enqueue(slice);\n }\n }\n }\n\n private startTimer(): void {\n if (this.flushTimer !== null) return;\n this.flushTimer = setTimeout(() => {\n this.flushTimer = null;\n void this.flush();\n }, FLUSH_INTERVAL_MS);\n }\n\n private cancelTimer(): void {\n if (this.flushTimer !== null) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n }\n}\n","// Web Vitals capture (PalPerf Faz 1.5). Each Core Web Vital is ONE `web_vital`\n// perf row: name = the vital (LCP/CLS/INP/FCP/TTFB), value = the metric. They\n// ride the SAME ingest + `perf_agg_daily_mv` percentile rollup as every other\n// perf family — no new wire shape, no new CH table. distinct_id/app_id/geo are\n// server-derived (§8.6); the SDK sends raw measurements only.\n//\n// Strict TS, no `any`/cast: PerformanceObserver entry types are narrowed via\n// typed predicates, never coerced. We avoid the `web-vitals` npm dep (the SDK\n// ships zero runtime deps for perf) and read the raw PerformanceObserver\n// entries directly — CLS is summed across `layout-shift` entries without recent\n// input, LCP/INP take the latest, FCP/TTFB are one-shot.\n\nimport type { PerfItem } from './perf-wire.js';\n\n/** The Core Web Vitals we capture. FID is intentionally omitted — it is\n * deprecated in favour of INP. Closed union → exhaustive, no `any`. */\nexport type WebVitalName = 'LCP' | 'CLS' | 'INP' | 'FCP' | 'TTFB';\n\n/** Build a `web_vital` PerfItem. `value` is the metric (ms for timing vitals,\n * a unitless score ×1000-free for CLS). Negative values clamp to 0. PURE. */\nexport function webVitalItem(name: WebVitalName, value: number): PerfItem {\n return {\n row_id: crypto.randomUUID(),\n trace_type: 'web_vital',\n name,\n value: Math.max(0, value),\n timestamp: Date.now(),\n };\n}\n\n// --- typed PerformanceObserver entry narrowing (no `any`) -------------------\n\ninterface LayoutShiftEntry extends PerformanceEntry {\n value: number;\n hadRecentInput: boolean;\n}\nfunction isLayoutShift(e: PerformanceEntry): e is LayoutShiftEntry {\n return e.entryType === 'layout-shift' && 'value' in e && 'hadRecentInput' in e;\n}\n\ninterface EventTimingEntry extends PerformanceEntry {\n duration: number;\n interactionId?: number;\n}\nfunction isEventTiming(e: PerformanceEntry): e is EventTimingEntry {\n return e.entryType === 'event' || e.entryType === 'first-input';\n}\n\n/** Safely create + start a PerformanceObserver for one entry type, swallowing\n * unsupported-type errors (Safari lacks `layout-shift`/`event`). Returns the\n * observer or null. */\nfunction safeObserve(\n type: string,\n cb: (entries: PerformanceEntryList) => void,\n): PerformanceObserver | null {\n if (typeof PerformanceObserver === 'undefined') return null;\n try {\n const obs = new PerformanceObserver((list) => cb(list.getEntries()));\n obs.observe({ type, buffered: true } as PerformanceObserverInit);\n return obs;\n } catch {\n return null;\n }\n}\n\n/**\n * Wire the Core Web Vitals observers, recording one `web_vital` PerfItem per\n * settled vital. Returns a disconnect fn (call on teardown). No-op outside the\n * browser. LCP/INP record the LATEST observed value on disconnect/pagehide; CLS\n * sums layout shifts without recent input; FCP/TTFB are one-shot. Best-effort —\n * a missing observer type is silently skipped, never thrown.\n */\nexport function observeWebVitals(record: (item: PerfItem) => void): () => void {\n // No-op outside a real browser. We require the exact DOM/timing methods we\n // call below — a partial/non-standard `document` (e.g. an SSR shim, or a test\n // fake that only sets `window`) must NOT crash runtime construction. This is\n // the same defensiveness `recordColdStart` applies.\n if (\n typeof document === 'undefined' ||\n typeof document.addEventListener !== 'function' ||\n typeof document.removeEventListener !== 'function' ||\n typeof performance === 'undefined' ||\n typeof performance.getEntriesByType !== 'function'\n ) {\n return () => {};\n }\n\n let cls = 0;\n let lcp = 0;\n let inp = 0;\n\n const observers: Array<PerformanceObserver | null> = [\n // LCP: keep the largest/last reported render.\n safeObserve('largest-contentful-paint', (entries) => {\n for (const e of entries) lcp = Math.max(lcp, e.startTime);\n }),\n // CLS: sum shift values that weren't caused by recent input.\n safeObserve('layout-shift', (entries) => {\n for (const e of entries) if (isLayoutShift(e) && !e.hadRecentInput) cls += e.value;\n }),\n // INP: approximate as the worst interaction duration observed.\n safeObserve('event', (entries) => {\n for (const e of entries) if (isEventTiming(e)) inp = Math.max(inp, e.duration);\n }),\n // FCP: one-shot.\n safeObserve('paint', (entries) => {\n for (const e of entries)\n if (e.name === 'first-contentful-paint') record(webVitalItem('FCP', e.startTime));\n }),\n ];\n\n // TTFB from the navigation entry (one-shot, available immediately).\n const nav = performance.getEntriesByType('navigation')[0] as\n | PerformanceNavigationTiming\n | undefined;\n if (nav && nav.responseStart > 0) record(webVitalItem('TTFB', nav.responseStart));\n\n // Flush the running aggregates (LCP/CLS/INP) once, when the page is hidden or\n // torn down — that's when the values have settled.\n let flushed = false;\n const flush = (): void => {\n if (flushed) return;\n flushed = true;\n if (lcp > 0) record(webVitalItem('LCP', lcp));\n record(webVitalItem('CLS', cls));\n if (inp > 0) record(webVitalItem('INP', inp));\n };\n const onHide = (): void => {\n if (document.visibilityState === 'hidden') flush();\n };\n document.addEventListener('visibilitychange', onHide);\n\n return () => {\n flush();\n document.removeEventListener('visibilitychange', onHide);\n for (const o of observers) o?.disconnect();\n };\n}\n","const APP_METADATA_HEADER = 'X-Palbase-Bundle';\n\n/**\n * Stamp the live browser origin metadata on an app-key request. Final headers\n * are inspected case-insensitively so an explicit caller/config value wins.\n * The authenticated app identity is the app-bound API key plus its app_id;\n * this header is diagnostic metadata, not an authorization proof. Server\n * runtimes have no browser origin and intentionally add nothing.\n */\nexport function applyBrowserOriginHeader(headers: Record<string, string>): void {\n if (Object.keys(headers).some((key) => key.toLowerCase() === 'x-palbase-bundle')) return;\n const origin =\n typeof window !== 'undefined' && typeof window.location !== 'undefined'\n ? window.location.origin\n : '';\n if (origin !== '') headers[APP_METADATA_HEADER] = origin;\n}\n","// Anonymous-token minting for the realtime WebSocket — a port of the iOS\n// `AnonTokenProvider` actor (palbackend-ios-src Sources/Palbe/Realtime/\n// AnonTokenProvider.swift).\n//\n// Why this exists: Realtime validates every connection's token against the\n// tenant's JWKS (palauth's ES256 signing key). A signed-in user already has a\n// palauth-issued Bearer that validates there. An anonymous caller only holds\n// the Environment's publishable API key (`pb_<environmentRef>_c…`) — NOT a JWT, so Realtime\n// rejects it with MalformedJWT. `POST /auth/anonymous` mints the missing\n// piece: a role=anon ES256 JWT signed by the same key Realtime trusts.\n//\n// Deliberately SEPARATE from TokenManager (which owns the user-session\n// lifecycle and auth-state events): an anonymous token has no user, no\n// refresh token, and emits no auth events. The provider memoises one token\n// and re-mints it shortly before expiry.\n\nimport { BackendError, fromEnvelope } from '../errors.js';\nimport type { PalbeRuntime } from '../runtime.js';\nimport { applyBrowserOriginHeader } from '../runtime-metadata.js';\n\ninterface AnonToken {\n accessToken: string;\n expiresAt: number; // epoch ms\n}\n\n/** Re-mint this long before real expiry so a long-lived socket never presents\n * an expired token on reconnect/join (iOS refreshSkew = 60s). */\nconst REFRESH_SKEW_MS = 60_000;\n\nexport class AnonTokenProvider {\n private readonly rt: PalbeRuntime;\n private cached: AnonToken | null = null;\n private inFlight: Promise<string> | null = null;\n\n constructor(rt: PalbeRuntime) {\n this.rt = rt;\n }\n\n /**\n * Return a valid anonymous access token, minting (or re-minting) one when\n * the cache is empty or within the skew of expiry. Concurrent callers\n * collapse onto a single in-flight mint.\n */\n token(): Promise<string> {\n if (this.cached && Date.now() < this.cached.expiresAt - REFRESH_SKEW_MS) {\n return Promise.resolve(this.cached.accessToken);\n }\n if (this.inFlight) return this.inFlight;\n const mint = this.mint().then(\n (token) => {\n this.cached = token;\n this.inFlight = null;\n return token.accessToken;\n },\n (e: unknown) => {\n this.inFlight = null; // a failed mint must not poison future attempts\n throw e;\n },\n );\n this.inFlight = mint;\n return mint;\n }\n\n /**\n * Raw fetch, deliberately NOT through HttpClient/palbeRequest: the mint\n * must carry the Environment API key and NEVER a Bearer (iOS keeps\n * `/auth/anonymous` on the unauthenticated-path list), and HttpClient's\n * pre-flight would try to refresh a stale session before an anon mint —\n * exactly the entanglement this provider exists to avoid.\n */\n private async mint(): Promise<AnonToken> {\n const base = this.rt.config.url.replace(/\\/+$/, '');\n const headers: Record<string, string> = {\n apikey: this.rt.config.apiKey,\n ...this.rt.config.headers,\n };\n applyBrowserOriginHeader(headers);\n let response: Response;\n try {\n response = await fetch(`${base}/auth/anonymous`, {\n method: 'POST',\n headers,\n });\n } catch (e) {\n throw new BackendError('network', {\n code: 'network_error',\n message: e instanceof Error ? e.message : 'Network request failed',\n });\n }\n const body: unknown = await response.json().catch(() => null);\n if (!response.ok) throw fromEnvelope(response.status, body);\n // Wire shape: { access_token, token_type: \"Bearer\", expires_in: seconds }.\n const obj = (typeof body === 'object' && body !== null ? body : {}) as Record<string, unknown>;\n const accessToken = obj.access_token;\n const expiresIn = obj.expires_in;\n if (typeof accessToken !== 'string' || accessToken === '' || typeof expiresIn !== 'number') {\n throw new BackendError('decode', {\n code: 'decode_error',\n message:\n 'POST /auth/anonymous returned an unexpected shape (expected {access_token, expires_in}).',\n });\n }\n return { accessToken, expiresAt: Date.now() + expiresIn * 1000 };\n }\n}\n\n/**\n * The realtime connection's token closure (iOS PalBackend.swift parity):\n * 1. the signed-in user Bearer when present and unexpired (user-override\n * flag topics + private channels authorize);\n * 2. else an anonymous palauth JWT (role=anon);\n * 3. else — ONLY when the mint fails — the bare Environment API key. Realtime\n * would reject it, but it keeps the closure total: the connect proceeds,\n * fails, and the backoff loop retries the mint on the next attempt.\n */\nexport function realtimeTokenProvider(\n rt: PalbeRuntime,\n anon: AnonTokenProvider,\n): () => Promise<string> {\n return async () => {\n const bearer = rt.tokenManager.getAccessToken();\n if (bearer && !rt.tokenManager.isExpired()) return bearer;\n try {\n return await anon.token();\n } catch {\n return rt.config.apiKey;\n }\n };\n}\n","// Phoenix Channels v2 frame codec — the realtime wire format.\n//\n// Every shape here is byte-locked to the iOS encoder (palbackend-ios-src\n// Sources/Palbe/Realtime/RealtimeConnection.swift), the proven-live ground\n// truth against the platform's Supabase Realtime (Elixir/Phoenix):\n//\n// join: [join_ref, ref, \"realtime:<topic>\", \"phx_join\", {access_token, config}]\n// reply: [join_ref, ref, \"realtime:<topic>\", \"phx_reply\", {status, response}]\n// heartbeat: [null, ref, \"phoenix\", \"heartbeat\", {}]\n// leave: [null, ref, \"realtime:<topic>\", \"phx_leave\", {}]\n// broadcast: [join_ref, ref, \"realtime:<topic>\", \"broadcast\", {type, event, payload}]\n//\n// Refs are stringified monotonic ints. Topics are bare sub-topics in the API\n// (\"room:42\"); the codec adds the \"realtime:\" prefix on the way out and strips\n// it on the way in. Goldens: __tests__/realtime-frames.test.ts.\n\nexport const REALTIME_PREFIX = 'realtime:';\n\n/** A decoded Phoenix v2 frame. Only topic/event/payload matter for routing. */\nexport interface PhoenixFrame {\n joinRef: unknown;\n ref: unknown;\n topic: string;\n event: string;\n payload: unknown;\n}\n\n/** An inbound broadcast routed to channel handlers: bare topic + inner event/payload. */\nexport interface RealtimeInbound {\n topic: string;\n event: string;\n payload: Record<string, unknown>;\n}\n\n/**\n * Join a topic. join_ref = ref (iOS sends the same ref in both slots). The\n * config mirrors iOS exactly: broadcast self=false, no presence key, public\n * channel, no postgres_changes.\n */\nexport function encodeJoin(ref: number, topic: string, accessToken: string): string {\n const r = String(ref);\n return JSON.stringify([\n r,\n r,\n REALTIME_PREFIX + topic,\n 'phx_join',\n {\n access_token: accessToken,\n config: {\n broadcast: { self: false },\n presence: { key: '' },\n private: false,\n postgres_changes: [],\n },\n },\n ]);\n}\n\nexport function encodeLeave(ref: number, topic: string): string {\n return JSON.stringify([null, String(ref), REALTIME_PREFIX + topic, 'phx_leave', {}]);\n}\n\nexport function encodeHeartbeat(ref: number): string {\n return JSON.stringify([null, String(ref), 'phoenix', 'heartbeat', {}]);\n}\n\n/**\n * Client-side broadcast push (web addition — iOS is receive-only). The payload\n * envelope {type:\"broadcast\", event, payload} matches what the server fans out\n * to other subscribers (Realtime's BroadcastHandler re-broadcasts it verbatim),\n * so receivers unwrap pushes from peers and from the backend identically.\n * joinRef is the topic's CURRENT join ref — Phoenix drops channel messages\n * whose join_ref does not match the live channel instance.\n */\nexport function encodeBroadcast(\n joinRef: string,\n ref: number,\n topic: string,\n event: string,\n payload: Record<string, unknown>,\n): string {\n return JSON.stringify([\n joinRef,\n String(ref),\n REALTIME_PREFIX + topic,\n 'broadcast',\n { type: 'broadcast', event, payload },\n ]);\n}\n\n/** Decode a Phoenix v2 frame; null when the text is not a valid 5-slot array. */\nexport function decodeFrame(text: string): PhoenixFrame | null {\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n return null;\n }\n if (!Array.isArray(parsed)) return null;\n const [joinRef, ref, topic, event, payload] = parsed as unknown[];\n if (typeof topic !== 'string' || typeof event !== 'string') return null;\n return { joinRef, ref, topic, event, payload };\n}\n\nfunction asObject(value: unknown): Record<string, unknown> | null {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return null;\n return value as Record<string, unknown>;\n}\n\n/**\n * Route an inbound frame: only `broadcast` events carry channel traffic\n * (replies/acks/presence are connection bookkeeping — iOS ignores them the\n * same way; reconnect re-joins every topic, so socket health drives recovery).\n * Missing inner event defaults to '' and a non-object payload to {} — exact\n * iOS decoder behavior.\n */\nexport function unwrapBroadcast(frame: PhoenixFrame): RealtimeInbound | null {\n if (frame.event !== 'broadcast') return null;\n const body = asObject(frame.payload);\n if (!body) return null;\n const event = typeof body.event === 'string' ? body.event : '';\n const payload = asObject(body.payload) ?? {};\n return { topic: stripRealtimePrefix(frame.topic), event, payload };\n}\n\nexport function stripRealtimePrefix(topic: string): string {\n return topic.startsWith(REALTIME_PREFIX) ? topic.slice(REALTIME_PREFIX.length) : topic;\n}\n","// The Phoenix-Channels WebSocket transport for Palbase Realtime — a port of\n// the proven-live iOS `RealtimeConnection` actor (palbackend-ios-src\n// Sources/Palbe/Realtime/RealtimeConnection.swift), the wire ground truth\n// against the platform's Supabase Realtime (Elixir/Phoenix):\n//\n// connect: wss://<host>/realtime/v1/websocket?apikey=<jwt>&vsn=2.0.0\n// join: [join_ref, ref, \"realtime:<topic>\", \"phx_join\", {access_token, config}]\n// heartbeat: [null, ref, \"phoenix\", \"heartbeat\", {}] (~every 25s)\n// recv: [null, null, \"realtime:<topic>\", \"broadcast\", {type, event, payload}]\n//\n// ONE socket is shared by every subscribed channel (the Supabase model —\n// multiplex all topics over a single connection). This class is a generic\n// frame pump; the facade (facade.ts) owns per-channel handlers and refcounts.\n//\n// DEVIATION from iOS (documented): iOS sends the auth token in the\n// `x-api-key` HTTP header on the WS upgrade. Browsers cannot set headers on\n// a WebSocket handshake, so the web client uses the server's query fallback —\n// Realtime's user_socket.ex reads `x-api-key` first, then `params[\"apikey\"]`.\n//\n// Resilience mirrors iOS: a dropped socket reconnects with exponential\n// backoff (1,2,4…s capped at 30s + deterministic ref jitter); on reconnect\n// every previously-joined topic is re-joined with a FRESH token and fresh\n// refs. The 25s heartbeat keeps Kong's idle WS timeout (180s) from closing\n// the connection. One behavioral adaptation: iOS promotes to `connected` on\n// the first RECEIVED frame because `URLSessionWebSocketTask.resume()` does\n// not signal handshake completion — a browser WebSocket's `open` event fires\n// only after a successful HTTP-101 handshake, so promotion happens there.\n\nimport {\n decodeFrame,\n encodeBroadcast,\n encodeHeartbeat,\n encodeJoin,\n encodeLeave,\n type RealtimeInbound,\n stripRealtimePrefix,\n unwrapBroadcast,\n} from './frames.js';\n\n/**\n * The connection state of the shared realtime WebSocket (iOS enum parity), plus\n * `'error'` — surfaced when a channel could not be recovered after a\n * token-expiry close exhausted its rejoin attempts (the socket itself may still\n * be up; the app should stop expecting events on the dead channel).\n */\nexport type RealtimeConnectionState = 'idle' | 'connected' | 'reconnecting' | 'error';\n\n/** Connection-level callbacks the owner (the realtime facade) installs once. */\nexport interface RealtimeSocketHandlers {\n /** A broadcast arrived for a topic (bare sub-topic, inner event/payload). */\n onInbound: (inbound: RealtimeInbound) => void;\n /** The socket (re)connected — fires on EVERY successful open, first included. */\n onReconnect: () => void;\n /** The socket's connection state changed (idle/connected/reconnecting). */\n onStateChange: (state: RealtimeConnectionState) => void;\n /**\n * A single channel died for good (token-expiry rejoin exhausted N attempts).\n * The socket is still up; the owner surfaces this on the channel's status so\n * the app stops expecting events on that topic. Distinct from onStateChange,\n * which is connection-global — one bad channel must not flag the whole socket.\n */\n onChannelError: (topic: string) => void;\n}\n\n/** The structural slice of the DOM WebSocket the transport needs (injectable). */\nexport interface WebSocketLike {\n readonly readyState: number;\n send(data: string): void;\n close(code?: number): void;\n onopen: (() => void) | null;\n onmessage: ((event: { data: unknown }) => void) | null;\n onclose: (() => void) | null;\n onerror: (() => void) | null;\n}\n\nexport type WebSocketCtor = new (url: string) => WebSocketLike;\n\nexport interface RealtimeSocketOptions {\n /** https base, e.g. https://ref.dev.palbase.studio (PalbeConfig.url). */\n baseUrl: string;\n /**\n * Resolves the token used as the WS `apikey` query + per-join `access_token`.\n * Bearer when signed in, else anon JWT, else the Environment API key (see\n * anon-token.ts). Re-invoked on every connect AND every join, so a token\n * rotated while offline is picked up by the next reconnect.\n */\n tokenProvider: () => Promise<string>;\n /** Injectable for non-DOM hosts; defaults to globalThis.WebSocket, resolved per connect. */\n webSocket?: WebSocketCtor;\n /** Heartbeat cadence; default 25s (under Kong's 180s idle WS cap). */\n heartbeatIntervalMs?: number;\n /** Reconnect backoff cap; default 30s. */\n maxBackoffSeconds?: number;\n}\n\nconst WS_OPEN = 1;\nconst DEFAULT_HEARTBEAT_MS = 25_000;\nconst DEFAULT_MAX_BACKOFF_SECONDS = 30;\n// How many times a single channel may be rejoined after a token-expiry close\n// before we give up on it (the rejoin-storm guard). A genuinely-bad token (not\n// just expired) would close every rejoin immediately; capping turns that into a\n// surfaced channel error instead of a tight loop. The first rejoin is attempt 0.\nconst MAX_CHANNEL_REJOIN_ATTEMPTS = 3;\n\n/**\n * Build the Phoenix WS URL: https→wss (http→ws), trailing slashes stripped,\n * Kong's `/realtime/v1/websocket` path, `apikey` + `vsn=2.0.0` query.\n * Port of iOS `websocketURL` — plus the browser apikey-query deviation\n * documented in the file header.\n */\nexport function websocketUrl(base: string, token: string): string {\n let s = base;\n if (s.startsWith('https://')) {\n s = `wss://${s.slice('https://'.length)}`;\n } else if (s.startsWith('http://')) {\n s = `ws://${s.slice('http://'.length)}`;\n }\n while (s.endsWith('/')) s = s.slice(0, -1);\n return `${s}/realtime/v1/websocket?apikey=${encodeURIComponent(token)}&vsn=2.0.0`;\n}\n\n/**\n * Exponential reconnect backoff: 2^attempt seconds capped at `cap`, plus up\n * to 1s of deterministic jitter derived from the ref counter (iOS formula —\n * nondeterministic RNG is banned, and ref-based jitter is enough to de-sync\n * reconnect storms). Pure so growth/cap are unit-testable without sleeping.\n */\nexport function backoffDelaySeconds(attempt: number, refCounter: number, cap: number): number {\n const base = Math.min(2 ** attempt, cap);\n const jitter = (refCounter % 1000) / 1000;\n return base + jitter;\n}\n\ninterface PendingBroadcast {\n topic: string;\n event: string;\n payload: Record<string, unknown>;\n}\n\nexport class RealtimeSocket {\n private readonly options: RealtimeSocketOptions;\n private handlers: RealtimeSocketHandlers | null = null;\n\n private ws: WebSocketLike | null = null;\n // Membership intent (bare sub-topics): re-joined on every (re)connect. A\n // topic stays in this set from joinTopic() until leaveTopic().\n private readonly joinedTopics = new Set<string>();\n // topic → join_ref of the LIVE join on the current socket. Phoenix drops\n // channel messages whose join_ref doesn't match, so broadcasts carry this.\n // Cleared on disconnect (the next socket gets fresh joins/refs).\n private readonly joinRefs = new Map<string, string>();\n // Broadcasts pushed before the topic's join is live — flushed right after it.\n private pendingBroadcasts: PendingBroadcast[] = [];\n // Per-topic rejoin attempt counter for the token-expiry channel-death guard.\n // Bumped on every channel rejoin; reset to 0 when the topic delivers a live\n // broadcast (proof the channel recovered). At MAX_CHANNEL_REJOIN_ATTEMPTS the\n // channel is given up on (onChannelError) instead of looping forever.\n private readonly channelRejoinAttempts = new Map<string, number>();\n // Pending per-topic rejoin backoff timers — separate from the socket-level\n // reconnectTimer so a channel backoff never clobbers socket reconnection.\n private readonly channelRejoinTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\n // Monotonic Phoenix message ref. Every outbound frame carries a unique ref.\n private refCounter = 0;\n private connected = false;\n private started = false;\n private stopped = false;\n // Reset ONLY on a genuine open, so a never-opening socket walks\n // attempt 0,1,2,… → delays 1,2,4,…,cap instead of hammering (iOS parity).\n private reconnectAttempt = 0;\n // Invalidates async continuations (token awaits) from a superseded connect.\n private connectEpoch = 0;\n\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n\n constructor(options: RealtimeSocketOptions) {\n this.options = options;\n }\n\n // MARK: lifecycle\n\n /** Install handlers and open the socket. Idempotent while started. */\n start(handlers: RealtimeSocketHandlers): void {\n this.handlers = handlers;\n if (this.started) return;\n this.started = true;\n this.stopped = false;\n void this.connect();\n }\n\n /** Tear down the socket, stop reconnecting, emit `idle`. Idempotent. */\n stop(): void {\n this.stopped = true;\n this.started = false;\n this.connected = false;\n this.connectEpoch += 1; // invalidate in-flight connect continuations\n this.stopHeartbeat();\n if (this.reconnectTimer !== null) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n this.clearAllChannelRejoins();\n const ws = this.ws;\n this.ws = null;\n this.joinRefs.clear();\n ws?.close(1000);\n this.handlers?.onStateChange('idle');\n }\n\n // MARK: topic membership\n\n /**\n * Join a topic (bare sub-topic, no \"realtime:\" prefix). Idempotent. Sent\n * immediately when the socket is up, else on the next (re)connect.\n */\n joinTopic(topic: string): void {\n if (this.joinedTopics.has(topic)) return;\n this.joinedTopics.add(topic);\n if (this.connected) void this.sendJoin(topic);\n }\n\n /**\n * Leave a topic: sends `phx_leave` when connected and always drops it from\n * the re-join set (a future reconnect won't restore it). Pending broadcasts\n * for the topic are discarded.\n */\n leaveTopic(topic: string): void {\n if (!this.joinedTopics.delete(topic)) return;\n this.joinRefs.delete(topic);\n this.pendingBroadcasts = this.pendingBroadcasts.filter((p) => p.topic !== topic);\n this.clearChannelRejoin(topic); // cancel any pending token-expiry rejoin\n if (this.isOpen()) this.send(encodeLeave(this.nextRef(), topic));\n }\n\n /**\n * Push a broadcast onto a topic (web addition — iOS is receive-only). Sent\n * synchronously when the topic's join is live on the current socket;\n * otherwise queued and flushed right after the join goes out.\n */\n sendBroadcast(topic: string, event: string, payload: Record<string, unknown>): void {\n const joinRef = this.joinRefs.get(topic);\n if (joinRef !== undefined && this.isOpen()) {\n this.send(encodeBroadcast(joinRef, this.nextRef(), topic, event, payload));\n } else {\n this.pendingBroadcasts.push({ topic, event, payload });\n }\n }\n\n // MARK: connect / reconnect\n\n private async connect(): Promise<void> {\n if (this.stopped) return;\n const epoch = ++this.connectEpoch;\n let token: string;\n try {\n token = await this.options.tokenProvider();\n } catch {\n // The provider is total by design (apikey fallback) — but a throw must\n // not kill the reconnect loop.\n this.scheduleReconnect();\n return;\n }\n if (this.stopped || epoch !== this.connectEpoch) return;\n\n // Resolved per attempt (not cached) so an environment that gains a\n // WebSocket later (test stubs, polyfill load order) recovers on retry.\n const Ctor = this.options.webSocket ?? (globalThis as { WebSocket?: WebSocketCtor }).WebSocket;\n if (!Ctor) {\n this.scheduleReconnect();\n return;\n }\n\n const ws = new Ctor(websocketUrl(this.options.baseUrl, token));\n this.ws = ws;\n // Every callback gates on `ws === this.ws` so events from a superseded\n // socket (replaced during reconnect) can't corrupt the live one's state.\n ws.onopen = () => {\n if (ws === this.ws) void this.handleOpen();\n };\n ws.onmessage = (event) => {\n if (ws === this.ws) this.handleMessage(event.data);\n };\n ws.onclose = () => {\n if (ws === this.ws) this.handleClose();\n };\n // Browsers always follow `error` with `close` — onclose drives recovery.\n ws.onerror = () => {};\n }\n\n /**\n * The browser `open` event proves the HTTP-101 handshake completed (the\n * analogue of iOS promoteToConnected): flip connected, announce, then\n * (re)join every topic with a fresh token + fresh refs. Backoff is NOT\n * reset here — it resets on the first inbound frame (iOS parity), so a\n * server that opens but immediately closes still grows the backoff.\n */\n private async handleOpen(): Promise<void> {\n if (this.stopped) return;\n this.connected = true;\n this.handlers?.onStateChange('connected');\n this.handlers?.onReconnect();\n this.startHeartbeat();\n for (const topic of this.joinedTopics) {\n await this.sendJoin(topic);\n }\n }\n\n private handleClose(): void {\n if (this.stopped) return;\n this.ws = null;\n this.joinRefs.clear();\n // The socket reconnect rejoins ALL topics fresh — drop per-channel rejoin\n // state so a pending channel backoff doesn't race the rejoin-all.\n this.clearAllChannelRejoins();\n this.stopHeartbeat();\n this.scheduleReconnect();\n }\n\n private scheduleReconnect(): void {\n if (this.stopped || !this.started) return;\n this.connected = false;\n this.handlers?.onStateChange('reconnecting');\n const attempt = this.reconnectAttempt;\n this.reconnectAttempt += 1;\n const cap = this.options.maxBackoffSeconds ?? DEFAULT_MAX_BACKOFF_SECONDS;\n const delaySeconds = backoffDelaySeconds(attempt, this.refCounter, cap);\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n void this.connect();\n }, delaySeconds * 1000);\n }\n\n // MARK: frames out\n\n /**\n * Join carries a freshly-resolved token (iOS parity — `sendJoin` awaits the\n * provider), so a Bearer rotated while offline rides the next rejoin.\n */\n private async sendJoin(topic: string): Promise<void> {\n const token = await this.options.tokenProvider();\n // Re-validate after the await: the socket may have dropped or the topic\n // may have been left while the token resolved.\n if (!this.isOpen() || !this.joinedTopics.has(topic)) return;\n const ref = this.nextRef();\n this.joinRefs.set(topic, String(ref));\n this.send(encodeJoin(ref, topic, token));\n this.flushPending(topic);\n }\n\n private flushPending(topic: string): void {\n const ready = this.pendingBroadcasts.filter((p) => p.topic === topic);\n if (ready.length === 0) return;\n this.pendingBroadcasts = this.pendingBroadcasts.filter((p) => p.topic !== topic);\n for (const p of ready) this.sendBroadcast(p.topic, p.event, p.payload);\n }\n\n private startHeartbeat(): void {\n this.stopHeartbeat();\n const interval = this.options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_MS;\n this.heartbeatTimer = setInterval(() => {\n if (this.isOpen()) this.send(encodeHeartbeat(this.nextRef()));\n }, interval);\n }\n\n private stopHeartbeat(): void {\n if (this.heartbeatTimer !== null) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n }\n\n /** Drop-if-not-open: a send racing a disconnect is recovered by the rejoin-all on reconnect. */\n private send(text: string): void {\n if (this.ws && this.ws.readyState === WS_OPEN) this.ws.send(text);\n }\n\n // MARK: frames in\n\n private handleMessage(data: unknown): void {\n if (typeof data !== 'string') return;\n const frame = decodeFrame(data);\n if (!frame) return;\n // Any valid inbound frame proves the server is actually delivering data\n // (not just completing the TCP handshake). Reset backoff here — iOS parity:\n // iOS promotes to connected on the first frame because URLSession does not\n // surface a separate handshake-complete signal. A server that opens then\n // immediately closes does NOT trigger this, so backoff keeps growing.\n this.reconnectAttempt = 0;\n // Token-expiry channel death (the cross-SDK P4 bug): at JWT TTL the server\n // kills just THIS channel (`{:stop, :normal}`) while the socket stays up\n // (heartbeats hold it past Kong's idle cap). Phoenix v2 pushes a per-topic\n // phx_close carrying the channel's join_ref ([<joinRef>,null,topic,\"phx_close\",{}];\n // phx_error on a crash). We rejoin that one topic with a FRESH token rather\n // than letting it silently die. Socket-level reconnect rejoins ALL topics —\n // this handles the case where only ONE channel was killed.\n if (frame.event === 'phx_close' || frame.event === 'phx_error') {\n this.handleChannelClose(frame);\n return;\n }\n // Only `broadcast` events carry channel traffic; replies/acks/presence\n // are bookkeeping (iOS ignores them identically).\n const inbound = unwrapBroadcast(frame);\n if (inbound) {\n // A live broadcast proves the channel recovered — reset its rejoin guard.\n this.channelRejoinAttempts.delete(inbound.topic);\n this.handlers?.onInbound(inbound);\n }\n }\n\n /**\n * Recover (or give up on) a single channel the server closed for token\n * reasons. Ignored unless the close targets a topic we still intend to be\n * joined AND its join_ref matches the LIVE join — so a stale close from a\n * superseded join, or a close arriving after our OWN phx_leave (the topic is\n * already dropped from joinedTopics/joinRefs), does NOT trigger a rejoin.\n */\n private handleChannelClose(frame: { topic: string; joinRef: unknown }): void {\n const topic = stripRealtimePrefix(frame.topic);\n // Intentional-leave / stale-close guard: leaveTopic() removes the topic\n // from joinedTopics and its joinRefs entry, so neither check passes here.\n if (!this.joinedTopics.has(topic)) return;\n if (this.joinRefs.get(topic) !== frame.joinRef) return;\n // The current join is dead; drop its ref so a queued send won't use it.\n this.joinRefs.delete(topic);\n\n const attempt = this.channelRejoinAttempts.get(topic) ?? 0;\n if (attempt >= MAX_CHANNEL_REJOIN_ATTEMPTS) {\n // A genuinely-bad token (not just expired) closes every rejoin at once —\n // give up on this channel instead of tight-looping.\n this.joinedTopics.delete(topic);\n this.channelRejoinAttempts.delete(topic);\n this.handlers?.onChannelError(topic);\n return;\n }\n this.channelRejoinAttempts.set(topic, attempt + 1);\n if (attempt === 0) {\n // First close: rejoin immediately (the common case — token simply expired).\n void this.sendJoin(topic);\n return;\n }\n // Repeated closes: back off (reuse the socket reconnect curve) so a flapping\n // channel doesn't hammer the server. Replaces any pending rejoin for the topic.\n const cap = this.options.maxBackoffSeconds ?? DEFAULT_MAX_BACKOFF_SECONDS;\n const delayMs = backoffDelaySeconds(attempt, this.refCounter, cap) * 1000;\n const existing = this.channelRejoinTimers.get(topic);\n if (existing !== undefined) clearTimeout(existing);\n const timer = setTimeout(() => {\n this.channelRejoinTimers.delete(topic);\n if (this.joinedTopics.has(topic)) void this.sendJoin(topic);\n }, delayMs);\n this.channelRejoinTimers.set(topic, timer);\n }\n\n // MARK: helpers\n\n /** Drop a single topic's token-expiry rejoin state (cancel its pending timer). */\n private clearChannelRejoin(topic: string): void {\n const timer = this.channelRejoinTimers.get(topic);\n if (timer !== undefined) {\n clearTimeout(timer);\n this.channelRejoinTimers.delete(topic);\n }\n this.channelRejoinAttempts.delete(topic);\n }\n\n /** Drop all per-channel rejoin state (socket teardown / full reconnect). */\n private clearAllChannelRejoins(): void {\n for (const timer of this.channelRejoinTimers.values()) clearTimeout(timer);\n this.channelRejoinTimers.clear();\n this.channelRejoinAttempts.clear();\n }\n\n private isOpen(): boolean {\n return this.connected && this.ws !== null && this.ws.readyState === WS_OPEN;\n }\n\n private nextRef(): number {\n this.refCounter += 1;\n return this.refCounter;\n }\n}\n","// `pb.realtime` — the public, customer-facing realtime surface. Mirrors the\n// iOS `Facade+Realtime` + `RealtimeClient` pair (palbackend-ios-src\n// Sources/Palbe/Realtime/): a channel/handler manager multiplexing every\n// subscription over ONE shared, auto-reconnecting WebSocket (connection.ts).\n//\n// const channel = pb.realtime.channel('room:42');\n// const sub = channel.on('message', (payload) => { ... });\n// channel.send('message', { text: 'hi' }); // web addition — iOS is receive-only\n// // later: sub.cancel() (last cancel on the channel → phx_leave)\n//\n// Channel names are app-defined bare sub-topics — do NOT prefix with\n// \"realtime:\" (that's internal to the transport). `realtime` is a reserved\n// namespace: a backend endpoint cannot be named `realtime` (namespaces.ts\n// RESERVED, alongside auth/flags).\n//\n// Realtime is CLIENT-ONLY: on a host with no WebSocket (SSR/RSC/route\n// handlers), `channel()` throws a guided validation error. Reading\n// `status` is always safe (it just reports `idle`).\n\nimport type { Unsubscribe } from '../auth-facade.js';\nimport { BackendError } from '../errors.js';\nimport type { PalbeRuntime } from '../runtime.js';\nimport { AnonTokenProvider, realtimeTokenProvider } from './anon-token.js';\nimport { type RealtimeConnectionState, RealtimeSocket } from './connection.js';\nimport type { RealtimeInbound } from './frames.js';\n\nexport type { RealtimeConnectionState } from './connection.js';\n\n/** An inbound broadcast payload (the inner `payload` of the Phoenix envelope). */\nexport type RealtimePayload = Record<string, unknown>;\nexport type RealtimeHandler = (payload: RealtimePayload) => void;\n\n/** A cancellable realtime subscription. `cancel()` removes the handler and,\n * when it was the channel's last, leaves the channel on the shared socket.\n * Cancelling twice is a no-op. */\nexport interface RealtimeSubscription {\n cancel(): void;\n}\n\nexport interface RealtimeStatusSnapshot {\n state: RealtimeConnectionState;\n lastEventAt: Date | null;\n}\n\n/**\n * The observable connection status (`pb.realtime.status`) — the plain-JS\n * analogue of the iOS `@Observable RealtimeStatusStore`. `state` drives a\n * live \"connected\" badge; `lastEventAt` proves a push actually arrived.\n * `onChange` fires on state transitions and on every recorded event.\n */\nexport interface RealtimeStatus {\n readonly state: RealtimeConnectionState;\n readonly lastEventAt: Date | null;\n onChange(callback: (status: RealtimeStatusSnapshot) => void): Unsubscribe;\n}\n\nclass StatusStore implements RealtimeStatus {\n private currentState: RealtimeConnectionState = 'idle';\n private lastEvent: Date | null = null;\n private readonly listeners = new Set<(status: RealtimeStatusSnapshot) => void>();\n\n get state(): RealtimeConnectionState {\n return this.currentState;\n }\n\n get lastEventAt(): Date | null {\n return this.lastEvent;\n }\n\n onChange(callback: (status: RealtimeStatusSnapshot) => void): Unsubscribe {\n this.listeners.add(callback);\n return () => {\n this.listeners.delete(callback);\n };\n }\n\n /** No-op on equal state (iOS setState guard) — listeners see transitions only. */\n setState(state: RealtimeConnectionState): void {\n if (state === this.currentState) return;\n this.currentState = state;\n this.emit();\n }\n\n recordEvent(at: Date): void {\n this.lastEvent = at;\n this.emit();\n }\n\n private emit(): void {\n const snapshot: RealtimeStatusSnapshot = {\n state: this.currentState,\n lastEventAt: this.lastEvent,\n };\n for (const listener of this.listeners) listener(snapshot);\n }\n}\n\ninterface HandlerEntry {\n topic: string;\n event: string;\n handler: RealtimeHandler;\n}\n\n/**\n * A subscription handle for one realtime channel (returned by\n * `pb.realtime.channel(name)` — the SAME instance per name). A channel is\n * joined on the shared socket when its first handler is added (or on the\n * first `send`) and left when its last handler cancels.\n */\nexport class RealtimeChannel {\n /** The app-defined channel name (bare sub-topic, no \"realtime:\" prefix). */\n readonly name: string;\n private readonly owner: PalbeRealtime;\n\n /** @internal — obtain channels via `pb.realtime.channel(name)`. */\n constructor(name: string, owner: PalbeRealtime) {\n this.name = name;\n this.owner = owner;\n }\n\n /**\n * Subscribe `handler` to `event` on this channel. Fire-and-forget: the\n * join rides the shared socket asynchronously. Hold the returned\n * subscription and `cancel()` it to stop.\n */\n on(event: string, handler: RealtimeHandler): RealtimeSubscription {\n return this.owner.subscribe(this.name, event, handler);\n }\n\n /**\n * Broadcast `payload` to the channel's other subscribers (web addition —\n * iOS is receive-only). Joins the channel if it isn't already; queued\n * until the join is live on the socket.\n */\n send(event: string, payload: RealtimePayload = {}): void {\n this.owner.send(this.name, event, payload);\n }\n}\n\nexport class PalbeRealtime {\n private readonly rt: PalbeRuntime;\n private socket: RealtimeSocket | null = null;\n private readonly channels = new Map<string, RealtimeChannel>();\n private readonly statusStore = new StatusStore();\n // Per-(topic, event) handlers — an event may have multiple handlers\n // (multiple .on calls); entry identity is the unsubscribe token.\n private readonly handlers = new Set<HandlerEntry>();\n // Joins are refcounted per topic so the socket only leaves a topic when\n // its last handler cancels (iOS RealtimeClient parity).\n private readonly topicRefcount = new Map<string, number>();\n\n constructor(rt: PalbeRuntime) {\n this.rt = rt;\n }\n\n /**\n * Get the handle for an app-defined channel (e.g. \"room:42\"). The same\n * name returns the SAME instance. Throws a guided error on server-side\n * hosts (SSR/RSC/Node) — realtime is client-only.\n */\n channel(name: string): RealtimeChannel {\n // Primary: detect server environment by the absence of the browser globals\n // `document` and `window`. Node ≥22 ships a global WebSocket, so the old\n // `typeof WebSocket === 'undefined'` check is no-op there — server code\n // would silently open real sockets.\n if (typeof document === 'undefined' && typeof window === 'undefined') {\n throw new BackendError('validation', {\n code: 'realtime_unavailable',\n message:\n 'pb.realtime requires a browser environment: document and window are not defined ' +\n '(server/SSR/RSC). Subscribe from browser code instead — e.g. a client component or useEffect.',\n });\n }\n // Secondary: browser polyfill may be missing even with document/window present.\n if (typeof WebSocket === 'undefined') {\n throw new BackendError('validation', {\n code: 'realtime_unavailable',\n message:\n 'pb.realtime is client-only: this environment has no WebSocket. ' +\n 'Subscribe from browser code instead — e.g. a client component or useEffect.',\n });\n }\n let channel = this.channels.get(name);\n if (!channel) {\n channel = new RealtimeChannel(name, this);\n this.channels.set(name, channel);\n }\n return channel;\n }\n\n /**\n * Tear down the shared socket and clear all channel state. Called by\n * `__configure` and `__reset` when the runtime is replaced, so old sockets\n * are not left open and heartbeat timers do not leak.\n */\n destroy(): void {\n this.socket?.stop();\n this.socket = null;\n this.channels.clear();\n }\n\n /** The observable connection status. Safe to read anywhere (reports `idle` until a socket exists). */\n get status(): RealtimeStatus {\n return this.statusStore;\n }\n\n /** @internal */\n subscribe(topic: string, event: string, handler: RealtimeHandler): RealtimeSubscription {\n const socket = this.ensureSocket();\n const entry: HandlerEntry = { topic, event, handler };\n this.handlers.add(entry);\n const count = (this.topicRefcount.get(topic) ?? 0) + 1;\n this.topicRefcount.set(topic, count);\n if (count === 1) socket.joinTopic(topic);\n\n let cancelled = false;\n return {\n cancel: () => {\n if (cancelled) return;\n cancelled = true;\n this.handlers.delete(entry);\n const next = (this.topicRefcount.get(topic) ?? 1) - 1;\n if (next <= 0) {\n this.topicRefcount.delete(topic);\n socket.leaveTopic(topic);\n } else {\n this.topicRefcount.set(topic, next);\n }\n },\n };\n }\n\n /** @internal */\n send(topic: string, event: string, payload: RealtimePayload): void {\n const socket = this.ensureSocket();\n // Phoenix only accepts pushes on joined topics. Idempotent when already\n // joined; a send-only channel stays joined until its handlers (if any\n // are ever added) all cancel.\n socket.joinTopic(topic);\n socket.sendBroadcast(topic, event, payload);\n }\n\n /** Lazily build + start the shared socket on first subscription/send. */\n private ensureSocket(): RealtimeSocket {\n if (!this.socket) {\n const anon = new AnonTokenProvider(this.rt);\n this.socket = new RealtimeSocket({\n baseUrl: this.rt.config.url,\n tokenProvider: realtimeTokenProvider(this.rt, anon),\n });\n this.socket.start({\n onInbound: (inbound) => this.route(inbound),\n // Rejoin-all already happens inside the socket; nothing to refresh\n // here (no managed flags-sync on web yet — pb.flags polls).\n onReconnect: () => {},\n onStateChange: (state) => this.statusStore.setState(state),\n onChannelError: (topic) => this.handleChannelError(topic),\n });\n }\n return this.socket;\n }\n\n private route(inbound: RealtimeInbound): void {\n // Any inbound broadcast counts as a \"live event\" (iOS parity) — even one\n // without a matching handler proves the socket is delivering.\n this.statusStore.recordEvent(new Date());\n for (const entry of this.handlers) {\n if (entry.topic === inbound.topic && entry.event === inbound.event) {\n entry.handler(inbound.payload);\n }\n }\n }\n\n /**\n * A channel died for good (token-expiry rejoin exhausted N attempts). Drop its\n * handlers + refcount so the app stops expecting events on it, and flip the\n * status observable to `'error'` (the React `useChannel` hook reports this).\n * A later `.on()` for the same topic re-joins from scratch (refcount 1).\n */\n private handleChannelError(topic: string): void {\n for (const entry of this.handlers) {\n if (entry.topic === topic) this.handlers.delete(entry);\n }\n this.topicRefcount.delete(topic);\n this.statusStore.setState('error');\n }\n}\n","/**\n * Persisted session. The refresh token is the durable credential; access\n * token + expiry ride along (when available) so a reload — or a server-side\n * cookie read — can reuse a still-fresh access token instead of burning a\n * refresh per hydration (rotation storm under SSR).\n */\nexport interface PersistedSession {\n refreshToken: string;\n accessToken?: string;\n /** ms epoch */\n expiresAt?: number;\n}\n\nexport interface SessionStorageAdapter {\n load(): PersistedSession | null;\n save(session: PersistedSession): void;\n clear(): void;\n}\n\nexport function memorySessionStorage(): SessionStorageAdapter {\n let current: PersistedSession | null = null;\n return {\n load: () => current,\n save: (s) => {\n current = s;\n },\n clear: () => {\n current = null;\n },\n };\n}\n\nconst DEFAULT_KEY = 'palbe.session';\n\nexport function localStorageSessionStorage(key: string = DEFAULT_KEY): SessionStorageAdapter {\n return {\n load: () => {\n try {\n const raw = localStorage.getItem(key);\n if (!raw) return null;\n const parsed: unknown = JSON.parse(raw);\n if (typeof parsed === 'object' && parsed !== null) {\n const obj = parsed as Record<string, unknown>;\n if (typeof obj.refreshToken === 'string') {\n // Optional fields are validated as a PAIR: a wrong-typed (or lone)\n // access token / expiry degrades to the legacy {refreshToken}\n // shape — never null the whole session over the optional part.\n if (typeof obj.accessToken === 'string' && typeof obj.expiresAt === 'number') {\n return {\n refreshToken: obj.refreshToken,\n accessToken: obj.accessToken,\n expiresAt: obj.expiresAt,\n };\n }\n return { refreshToken: obj.refreshToken };\n }\n }\n return null;\n } catch {\n return null;\n }\n },\n save: (s) => {\n try {\n localStorage.setItem(key, JSON.stringify(s));\n } catch {\n // quota/SSR — non-fatal: session just won't survive reload\n }\n },\n clear: () => {\n try {\n localStorage.removeItem(key);\n } catch {\n // ignore\n }\n },\n };\n}\n\n/**\n * Browser → localStorage, else memory. Pass an environment-scoped key\n * (`palbe.session.<environmentRef>`) — the bare default key would collide\n * between two Palbase environments served from the same origin.\n */\nexport function defaultSessionStorage(key?: string): SessionStorageAdapter {\n if (typeof localStorage !== 'undefined') return localStorageSessionStorage(key);\n return memorySessionStorage();\n}\n","// AUTO-GENERATED from package.json — do not edit\nexport const VERSION = '7.3.8';\n","import { AuthClient } from '@palbase/auth';\nimport { HttpClient, TokenManager } from '@palbase/core';\nimport { AnalyticsState, PalbeAnalytics } from './analytics-facade.js';\nimport { environmentRefFromPublishableApiKey } from './api-key.js';\nimport { PalbeAuth } from './auth-facade.js';\nimport { PalbeCalls } from './calls/facade.js';\nimport type { PalbeConfig } from './config.js';\nimport { PalbeFlags } from './flags-facade.js';\nimport { PalbeMessaging } from './messaging/facade.js';\nimport { measureAppStart } from './perf/app-start.js';\nimport { PerfConfigClient } from './perf/perf-config-client.js';\nimport { PalbePerf } from './perf/perf-facade.js';\nimport { observeWebVitals } from './perf/web-vitals.js';\nimport { PalbeRealtime } from './realtime/facade.js';\nimport { applyBrowserOriginHeader } from './runtime-metadata.js';\nimport { defaultSessionStorage, type SessionStorageAdapter } from './storage.js';\nimport { VERSION } from './version.js';\n\nexport interface PalbeRuntime {\n config: PalbeConfig;\n /**\n * The project this runtime talks to, read from the publishable key.\n *\n * Exposed here so every consumer takes it from ONE derivation. It used to be\n * a field on the config that had to equal the key's — see buildRuntime.\n */\n projectRef: string;\n http: HttpClient;\n tokenManager: TokenManager;\n authClient: AuthClient;\n auth: PalbeAuth;\n flags: PalbeFlags;\n realtime: PalbeRealtime;\n analytics: PalbeAnalytics;\n calls: PalbeCalls;\n messaging: PalbeMessaging;\n /**\n * PalPerf surface. EAGER (like analytics identity): `request.ts` records a\n * `network` trace around EVERY SDK fetch, so the facade must exist from the\n * first request — never lazy. The buffer/timer it owns is inert until the\n * first item is recorded.\n */\n perf: PalbePerf;\n storage: SessionStorageAdapter;\n /**\n * Destroy the realtime facade if it was already constructed (no-op otherwise).\n * Called by `__configure` / `__reset` when the runtime is replaced so the old\n * socket is closed and heartbeat timers do not leak.\n */\n destroyRealtime(): void;\n}\n\nexport function buildRuntime(config: PalbeConfig): PalbeRuntime {\n // THE project identity, and there is one source for it: the publishable key\n // carries it (`pb_<ref>_c<random>`), so anything holding the key holds the ref.\n //\n // It used to be a second field beside the key, cross-checked against it — and\n // the cross-check was the whole reason it existed, which is the tell. A copy\n // that must equal its original is not a fact, it is an opportunity to\n // disagree: measured on 2026-08-16, `palbase link` wrote one value and minted\n // a key carrying another, and this generator refused to run at all.\n const projectRef = environmentRefFromPublishableApiKey(config.apiKey);\n if (projectRef === '') {\n throw new Error('PalbeConfig.apiKey does not contain a valid publishable project identity');\n }\n try {\n // Yalnız AYRIŞTIRILABİLİRLİK sınanıyor; sonucuna ihtiyaç yok.\n new URL(config.url);\n } catch {\n throw new Error('PalbeConfig.url must be a valid URL');\n }\n // NO HOST-FROM-KEY CHECK. A publishable key does not name its own address on\n // v2: a stack's identity is a compile-time constant, so every tenant's key\n // reads `pb_project_…` while its address is the tenant's own ref. The rule\n // that used to live here refused every correct v2 config. See the same note\n // in gen/generate.ts.\n const http = new HttpClient(config.apiKey, {\n url: config.url,\n headers: { 'X-Client-Info': `palbe-web/${VERSION}`, ...config.headers },\n });\n // One transport-level default covers direct AuthClient/HttpClient calls as\n // well as palbeRequest. It runs after global + per-request header merging,\n // so an explicit caller value wins and the live origin is read per request.\n http.addInterceptor(({ headers }) => applyBrowserOriginHeader(headers));\n const tokenManager = new TokenManager();\n http.tokenManager = tokenManager;\n const authClient = new AuthClient(http, tokenManager);\n const ref = projectRef;\n const storage = config.storage ?? defaultSessionStorage(ref ? `palbe.session.${ref}` : undefined);\n\n // Hydrate. Two paths:\n //\n // 1. FULL session persisted and still fresh → adopt it as a REAL session\n // (setTokens with the remaining TTL). The first request goes out with\n // the persisted Bearer and NO pre-flight refresh — this is what keeps\n // per-request server runtimes (palbe/next) from burning a token\n // rotation per request.\n //\n // 2. Legacy {refreshToken}-only shape, or an expired/invalid full session\n // → the expired-trick: the refresh token becomes an already-expired\n // session so HttpClient's pre-flight refresh runs before the first\n // real request. setTokens wires refreshFunction but sets\n // expiresAt=now+1h — we then override expiresAt to 0 so isExpired()\n // returns true immediately.\n //\n // Two implicit consequences, relied upon downstream (BOTH paths):\n // (a) setTokens marks AuthClient.hasSession=true, so the first successful\n // refresh after restore emits TOKEN_REFRESHED (not SIGNED_IN) — the\n // auth facade relies on this distinction.\n // (b) In the expired-trick path the transient session is\n // {accessToken:'', expiresAt:0} = \"pending refresh\" — never usable\n // directly, it only exists to arm pre-flight.\n const persisted = storage.load();\n if (persisted) {\n const { accessToken, expiresAt } = persisted;\n if (accessToken && expiresAt && expiresAt > Date.now()) {\n authClient.setTokens(\n accessToken,\n persisted.refreshToken,\n Math.floor((expiresAt - Date.now()) / 1000),\n );\n } else {\n authClient.setTokens('', persisted.refreshToken); // wires refreshFunction\n tokenManager.setSession({\n accessToken: '',\n refreshToken: persisted.refreshToken,\n expiresAt: 0,\n });\n }\n }\n\n // Persist token changes — the FULL session (access+expiry ride along so the\n // next hydration can skip the refresh); clear storage on sign-out.\n // AuthClient's own session mirror is registered first (its constructor), so\n // getSession() here already reflects the change that triggered us.\n authClient.onTokenChange(({ refreshToken }) => {\n if (refreshToken) {\n const session = authClient.getSession().data;\n if (session) {\n storage.save({\n refreshToken,\n accessToken: session.accessToken,\n expiresAt: session.expiresAt,\n });\n } else {\n storage.save({ refreshToken });\n }\n } else {\n storage.clear();\n }\n });\n\n // Lazy getter resolves the rt↔PalbeAuth construction cycle without a cast:\n // PalbeAuth needs rt, and `auth` stays non-optional on the interface.\n let auth: PalbeAuth | undefined;\n let flags: PalbeFlags | undefined;\n let realtime: PalbeRealtime | undefined;\n let analytics: PalbeAnalytics | undefined;\n let calls: PalbeCalls | undefined;\n let messaging: PalbeMessaging | undefined;\n let perf: PalbePerf | undefined;\n const rt: PalbeRuntime = {\n config,\n projectRef,\n http,\n tokenManager,\n authClient,\n storage,\n get auth() {\n if (!auth) auth = new PalbeAuth(rt);\n return auth;\n },\n // Same lazy pattern as `auth`: the pool is constructed (and, in the\n // browser, started) only when `pb.flags` is actually touched — a pbServer\n // request that never reads flags pays nothing.\n get flags() {\n if (!flags) flags = new PalbeFlags(rt);\n return flags;\n },\n // Lazy like `auth`/`flags` — but constructing PalbeRealtime is itself\n // inert: the WebSocket only opens on the first channel subscription/send.\n get realtime() {\n if (!realtime) realtime = new PalbeRealtime(rt);\n return realtime;\n },\n // PalbeCalls is lazy + stateless per instance (no timer/socket to destroy).\n get calls() {\n if (!calls) calls = new PalbeCalls(rt);\n return calls;\n },\n // Lazy like the others — constructing PalbeMessaging is inert (no enroll /\n // socket / WASM load until the first chat op or pb.messaging.start()).\n get messaging() {\n if (!messaging) messaging = new PalbeMessaging(rt);\n return messaging;\n },\n destroyRealtime() {\n // Runtime teardown hook (called by __configure/__reset on replace). Tear\n // down everything that holds a global listener/socket/timer so the old\n // runtime doesn't leak across a swap.\n // Only destroy if the lazy getter was already accessed (memo is set).\n // Do NOT touch `rt.realtime` here — that would construct it just to destroy.\n realtime?.destroy();\n realtime = undefined;\n // PalPerf is eager (below), so it always holds the `online` reconnect\n // listener — remove it so a __configure swap doesn't accumulate handlers.\n perf?.dispose();\n },\n // The buffering facade is lazy; its identity state is NOT (below).\n get analytics() {\n if (!analytics) analytics = new PalbeAnalytics(rt, analyticsState);\n return analytics;\n },\n // PalPerf is constructed up front (touched below): `request.ts` records a\n // network trace on EVERY fetch, so it can't be lazy. The memo here only\n // guards against re-construction.\n get perf() {\n if (!perf) perf = new PalbePerf(rt);\n return perf;\n },\n };\n\n // Analytics identity state is EAGER (cheap: listener wiring, no I/O): the\n // X-Distinct-Id interceptor must stamp EVERY request from the first one,\n // even when pb.analytics is never touched (iOS wires the resolver at\n // PalBackend init). Subscribing to auth events here constructs PalbeAuth\n // up front — acceptable, its constructor only attaches listeners.\n const analyticsState = new AnalyticsState(rt);\n http.addInterceptor((request) => {\n // Caller-supplied X-Distinct-Id wins (iOS applyDistinctId parity).\n const hasOwn = Object.keys(request.headers).some((k) => k.toLowerCase() === 'x-distinct-id');\n if (!hasOwn) request.headers['X-Distinct-Id'] = analyticsState.distinctId();\n });\n\n // Eagerly materialize the perf facade so `request.ts` can record a network\n // trace from the very first SDK fetch (the facade buffer stays inert until\n // an item is actually recorded).\n void rt.perf;\n\n // Browser-only: fetch the server-controlled perf sample rate once at init and\n // apply it to the facade's client-side sampling (remote sampling). Server\n // (no `document`) skips this — a per-request runtime must not fire an extra\n // GET per request. Fire-and-forget: a failed fetch leaves the default 100%.\n if (typeof document !== 'undefined') {\n void new PerfConfigClient().fetchConfig(rt, rt.perf);\n // A session restored from the cookie above rehydrated the TOKENS but not the\n // user object (the access-token JWT carries no emailVerified/createdAt), so\n // fetch GET /auth/user once and announce `signedIn` — the app no longer has\n // to `await pb.auth.refreshUser()` on boot. Browser-only (SSR must not fetch;\n // it renders SIGNED_OUT and the client hydrates), idempotent, non-throwing.\n rt.auth.hydrateUser();\n }\n\n // Browser-only cold-start trace. The web has no prewarm (unlike iOS), so a\n // load is always a real cold start: read First Contentful Paint relative to\n // navigation start and record ONE `app_start` item. The measurement itself is\n // the pure `measureAppStart`; this is just the untestable observer glue.\n recordColdStart(rt);\n\n // Browser-only Core Web Vitals. Records one `web_vital` perf item per settled\n // vital (LCP/CLS/INP/FCP/TTFB) through the same buffer as every perf family.\n // Best-effort — unsupported observer types are skipped, never thrown.\n observeWebVitals((item) => rt.perf.record(item));\n\n return rt;\n}\n\n/**\n * Wire the browser cold-start measurement. Records exactly one `app_start` perf\n * item once First Contentful Paint is known. No-op outside the browser (no\n * `document`/`PerformanceObserver`) or when navigation timing is unavailable.\n * Failures are swallowed — a perf hook must never break runtime construction.\n */\nfunction recordColdStart(rt: PalbeRuntime): void {\n if (typeof document === 'undefined' || typeof performance === 'undefined') return;\n try {\n const navEntry = performance.getEntriesByType('navigation')[0] as\n | PerformanceNavigationTiming\n | undefined;\n const startTime = navEntry?.startTime ?? 0;\n\n const recordFromFcp = (fcp: number): void => {\n const item = measureAppStart({ startTime, fcp });\n if (item) rt.perf.record(item);\n };\n\n // FCP may already have happened by the time the SDK is configured — read\n // the existing paint entry first.\n const existing = performance\n .getEntriesByName('first-contentful-paint')\n .find((e) => e.startTime > 0);\n if (existing) {\n recordFromFcp(existing.startTime);\n return;\n }\n\n // Otherwise observe paint entries and record on the first FCP, then stop.\n if (typeof PerformanceObserver === 'undefined') return;\n const observer = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n if (entry.name === 'first-contentful-paint') {\n observer.disconnect();\n recordFromFcp(entry.startTime);\n return;\n }\n }\n });\n observer.observe({ type: 'paint', buffered: true });\n } catch {\n // PerformanceObserver/navigation timing not supported — cold-start trace is\n // best-effort, never fatal.\n }\n}\n","import { BackendError } from './errors.js';\nimport { type CallOptions, palbeRequest } from './request.js';\nimport type { PalbeRuntime } from './runtime.js';\n\n/**\n * Runtime resolution is deferred behind `resolveRt` (global pb passes\n * `getRuntime`, bound clients pass `() => rt`) — but name validation runs\n * FIRST so an empty name fails as `validation` even before `notConfigured`.\n */\nexport async function callEndpoint<O>(\n resolveRt: () => PalbeRuntime,\n name: string,\n input?: unknown,\n options?: CallOptions,\n): Promise<O> {\n if (!name || name === '/') {\n throw new BackendError('validation', {\n code: 'invalid_endpoint_name',\n message: 'Endpoint name must be a non-empty path like \"todos/create\"',\n });\n }\n const rt = resolveRt();\n const path = name.startsWith('/') ? name : `/${name}`;\n return palbeRequest<O>(rt, 'POST', path, {\n body: input,\n headers: options?.headers,\n signal: options?.signal,\n });\n}\n","import { asPalbaseError, BackendError, fromEnvelope, fromPalbaseError } from './errors.js';\nimport type { PalbeRuntime } from './runtime.js';\nimport { applyBrowserOriginHeader } from './runtime-metadata.js';\nimport { VERSION } from './version.js';\n\nexport interface UploadProgress {\n /** Bytes sent so far. */\n sent: number;\n /**\n * Total bytes. Always > 0 when delivered — progress events whose length is\n * not computable are skipped entirely.\n */\n total: number;\n}\n\nexport interface UploadOptions {\n file: Blob;\n filename?: string;\n contentType?: string;\n fields?: Record<string, string>;\n onProgress?: (progress: UploadProgress) => void;\n signal?: AbortSignal;\n headers?: Record<string, string>;\n /** Client-side pre-send validation (iOS parity). */\n constraints?: { maxSize?: number; allowedTypes?: string[] };\n}\n\nfunction abortError(): BackendError {\n return new BackendError('network', { code: 'aborted', message: 'Upload aborted' });\n}\n\nfunction checkConstraints(options: UploadOptions): void {\n const c = options.constraints;\n if (!c) return;\n if (c.maxSize !== undefined && options.file.size > c.maxSize) {\n const message = `File size ${options.file.size} exceeds max ${c.maxSize} bytes`;\n throw new BackendError('validation', {\n code: 'file_too_large',\n message,\n fields: [{ field: 'file', message }],\n });\n }\n // Empty allowedTypes = no type check (iOS contract). Validate the EFFECTIVE\n // type: a contentType override wins over file.type (it is what gets sent).\n const effType = options.contentType ?? options.file.type;\n if (c.allowedTypes && c.allowedTypes.length > 0 && !c.allowedTypes.includes(effType)) {\n const message = `File type '${effType}' is not allowed`;\n throw new BackendError('validation', {\n code: 'file_type_not_allowed',\n message,\n fields: [{ field: 'file', message }],\n });\n }\n}\n\nasync function buildHeaders(\n rt: PalbeRuntime,\n extra?: Record<string, string>,\n): Promise<Record<string, string>> {\n // Pre-flight refresh: mirrors the pattern in request.ts / HttpClient.\n if (\n rt.tokenManager.isExpired() &&\n rt.tokenManager.getRefreshToken() &&\n rt.tokenManager.refreshFunction\n ) {\n try {\n await rt.tokenManager.refreshSession();\n } catch (e) {\n const pe = asPalbaseError(e);\n const status = pe?.status ?? 0;\n if (status === 400 || status === 401 || status === 403) {\n rt.tokenManager.clearSession(); // terminal — proceed unauthenticated\n } else {\n throw pe ? fromPalbaseError(pe) : e;\n }\n }\n }\n\n // Uploads bypass @palbase/core to stream multipart bodies. Kong derives the\n // Environment identity from the API key, matching the iOS upload path.\n const headers: Record<string, string> = {\n apikey: rt.config.apiKey,\n 'X-Client-Info': `palbe-web/${VERSION}`,\n ...rt.config.headers,\n };\n const token = rt.tokenManager.getAccessToken();\n if (token) headers.Authorization = `Bearer ${token}`;\n // Case-insensitive scan (mirrors request.ts): a caller-supplied\n // `idempotency-key` in any casing must suppress generation.\n const callerHasKey = Object.keys(extra ?? {}).some((k) => k.toLowerCase() === 'idempotency-key');\n if (!callerHasKey) headers['Idempotency-Key'] = crypto.randomUUID();\n // extra spreads LAST so a caller-pinned Authorization wins (HttpClient/iOS precedence).\n const merged = { ...headers, ...extra };\n applyBrowserOriginHeader(merged);\n return merged;\n}\n\nfunction buildForm(options: UploadOptions): FormData {\n const form = new FormData();\n for (const [k, v] of Object.entries(options.fields ?? {})) form.append(k, v);\n const file =\n options.contentType && options.file.type !== options.contentType\n ? new Blob([options.file], { type: options.contentType })\n : options.file;\n const filename =\n options.filename ??\n (typeof File !== 'undefined' && options.file instanceof File ? options.file.name : 'file');\n form.append('file', file, filename);\n return form;\n}\n\nfunction decode<O>(status: number, text: string): O {\n let body: unknown;\n try {\n body = text === '' ? null : JSON.parse(text);\n } catch {\n if (status >= 200 && status < 300) {\n throw new BackendError('decode', {\n code: 'decode_error',\n message: 'Invalid JSON in response',\n status,\n });\n }\n body = text;\n }\n if (status < 200 || status >= 300) throw fromEnvelope(status, body);\n return body as O;\n}\n\nfunction uploadViaXHR<O>(\n url: string,\n form: FormData,\n headers: Record<string, string>,\n options: UploadOptions,\n): Promise<O> {\n return new Promise<O>((resolve, reject) => {\n // Pre-aborted signal: never send. (xhr.abort() before send() fires no\n // abort event per spec, so routing this through onabort would hang.)\n if (options.signal?.aborted) {\n reject(abortError());\n return;\n }\n const xhr = new XMLHttpRequest();\n xhr.open('POST', url);\n for (const [k, v] of Object.entries(headers)) xhr.setRequestHeader(k, v);\n xhr.upload.onprogress = (e: { lengthComputable: boolean; loaded: number; total: number }) => {\n if (!e.lengthComputable) return;\n options.onProgress?.({ sent: e.loaded, total: e.total });\n };\n const onAbort = () => xhr.abort();\n const cleanup = () => options.signal?.removeEventListener('abort', onAbort);\n xhr.onload = () => {\n cleanup();\n try {\n resolve(decode<O>(xhr.status, xhr.responseText));\n } catch (err) {\n reject(err);\n }\n };\n xhr.onerror = () => {\n cleanup();\n reject(new BackendError('network', { code: 'network_error', message: 'Upload failed' }));\n };\n xhr.onabort = () => {\n cleanup();\n reject(abortError());\n };\n options.signal?.addEventListener('abort', onAbort, { once: true });\n xhr.send(form);\n });\n}\n\nasync function uploadViaFetch<O>(\n url: string,\n form: FormData,\n headers: Record<string, string>,\n options: UploadOptions,\n): Promise<O> {\n let res: Response;\n try {\n res = await fetch(url, { method: 'POST', body: form, headers, signal: options.signal });\n } catch (e) {\n // DOMException extends Error in modern runtimes — name check covers both.\n if (e instanceof Error && e.name === 'AbortError') throw abortError();\n throw new BackendError('network', {\n code: 'network_error',\n message: e instanceof Error ? e.message : 'Upload failed',\n });\n }\n return decode<O>(res.status, await res.text());\n}\n\n/** Runtime resolution deferred behind `resolveRt` — see call.ts for the pattern. */\nexport async function uploadEndpoint<O>(\n resolveRt: () => PalbeRuntime,\n name: string,\n options: UploadOptions,\n): Promise<O> {\n const rt = resolveRt();\n checkConstraints(options); // pre-send guard — rejects before any network I/O\n const path = name.startsWith('/') ? name : `/${name}`;\n const url = `${rt.config.url}${path}`;\n const headers = await buildHeaders(rt, options.headers);\n const form = buildForm(options);\n const useXHR = options.onProgress !== undefined && typeof XMLHttpRequest !== 'undefined';\n return useXHR\n ? uploadViaXHR<O>(url, form, headers, options)\n : uploadViaFetch<O>(url, form, headers, options);\n}\n","import type { PalbeAnalytics } from './analytics-facade.js';\nimport type { PalbeAuth } from './auth-facade.js';\nimport { callEndpoint } from './call.js';\nimport type { PalbeCalls } from './calls/facade.js';\nimport type { PalbeFlags } from './flags-facade.js';\nimport { getRuntime } from './internal.js';\nimport type { PalbeMessaging } from './messaging/facade.js';\nimport { boundNamespaceAccessor, getNamespace, getRegistry } from './namespaces.js';\nimport type { PalbePerf } from './perf/perf-facade.js';\nimport type { PalbeRealtime } from './realtime/facade.js';\nimport type { CallOptions } from './request.js';\nimport type { PalbeRuntime } from './runtime.js';\nimport type { UploadOptions } from './upload.js';\nimport { uploadEndpoint } from './upload.js';\n\n/**\n * The Palbe surface. Codegen augments this interface with typed endpoint\n * namespaces: declare module '@palbase/web' { interface PB { rooms: ... } }\n */\nexport interface PB {\n call<O = unknown>(name: string, input?: unknown, options?: CallOptions): Promise<O>;\n upload<O = unknown>(name: string, options: UploadOptions): Promise<O>;\n readonly auth: PalbeAuth;\n readonly flags: PalbeFlags;\n readonly realtime: PalbeRealtime;\n readonly analytics: PalbeAnalytics;\n readonly calls: PalbeCalls;\n readonly messaging: PalbeMessaging;\n /** The PalPerf surface: custom traces (`startTrace`) + the buffered flush of\n * network/app-start/custom items. `pb.perf.record(...)` is also how the\n * runtime feeds the cold-start measurement in. */\n readonly perf: PalbePerf;\n /** Mark (or unmark) this client's traffic as test — flips the facade's flag\n * so every subsequent perf flush carries `X-Palbase-Test-Device: 1` (the\n * SAME header the iOS SDK sends; the server reads it → `is_test_traffic`). */\n setTestDevice(on: boolean): void;\n}\n\n/**\n * One proxy construction for BOTH client flavors:\n * - global `pb`: resolveRt = getRuntime (late-bound — a __configure swap is\n * visible to every subsequent call), nsAccessor = the globally-cached path;\n * - bound clients: resolveRt = () => rt (captured once, immune to global\n * swaps), nsAccessor = a per-client cache (see namespaces.ts).\n */\nfunction createClientProxy(\n resolveRt: () => PalbeRuntime,\n nsAccessor: (name: string) => unknown,\n): PB {\n const base = {\n call<O = unknown>(name: string, input?: unknown, options?: CallOptions): Promise<O> {\n return callEndpoint<O>(resolveRt, name, input, options);\n },\n upload<O = unknown>(name: string, options: UploadOptions): Promise<O> {\n return uploadEndpoint<O>(resolveRt, name, options);\n },\n get auth(): PalbeAuth {\n return resolveRt().auth;\n },\n get flags(): PalbeFlags {\n return resolveRt().flags;\n },\n get realtime(): PalbeRealtime {\n return resolveRt().realtime;\n },\n get analytics(): PalbeAnalytics {\n return resolveRt().analytics;\n },\n get calls(): PalbeCalls {\n return resolveRt().calls;\n },\n get messaging(): PalbeMessaging {\n return resolveRt().messaging;\n },\n get perf(): PalbePerf {\n return resolveRt().perf;\n },\n setTestDevice(on: boolean): void {\n resolveRt().perf.setTestDevice(on);\n },\n };\n return new Proxy(base, {\n get(target, prop, receiver) {\n if (prop in target) return Reflect.get(target, prop, receiver);\n // `then` must NEVER resolve to a namespace: a thenable pb would make\n // `await pb` (or Promise.resolve(pb)) hang on the proxy. Registration\n // also rejects it (see namespaces.ts) — this is defense in depth.\n if (prop === 'then') return undefined;\n if (typeof prop === 'string') {\n const ns = nsAccessor(prop);\n if (ns !== undefined) return ns;\n }\n return undefined;\n },\n has(target, prop) {\n if (prop in target) return true;\n return typeof prop === 'string' && prop !== 'then' && getRegistry()[prop] !== undefined;\n },\n }) as PB;\n}\n\nexport const pb: PB = createClientProxy(getRuntime, getNamespace);\n\n/**\n * A `PB` client permanently bound to ONE runtime — typed namespaces, `call`,\n * `upload` and `auth` all resolve `rt`, NEVER the global slot. This is the\n * per-request isolation primitive for server adapters (palbe/next pbServer):\n * two bound clients serving concurrent requests cannot leak each other's\n * sessions, and global `__configure` swaps don't touch them.\n */\nexport function createBoundClient(rt: PalbeRuntime): PB {\n return createClientProxy(() => rt, boundNamespaceAccessor(rt));\n}\n"],"mappings":"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","names":["seg","validatePathParam","orderLte","ptr","module","generateClient","generateClient","chat","utf8","PERF_EXCLUDED_PREFIX","nowMs","FLUSH_AT","FLUSH_INTERVAL_MS","nowMs","ready"]}
1
+ {"version":3,"sources":["../src/internal.ts","../../core/src/config.ts","../../core/src/errors.ts","../../core/src/pow.ts","../../core/src/platform.ts","../../core/src/http.ts","../../core/src/token.ts","../src/errors.ts","../src/perf/url-redactor.ts","../src/request.ts","../src/state.ts","../src/namespaces.ts","../../modules/auth/src/auth-client.ts","../../modules/auth/src/device-client.ts","../src/analytics-facade.ts","../src/api-key.ts","../src/auth-wire.ts","../src/passkey.ts","../src/auth-facade.ts","../src/calls/facade.ts","../../modules/flags/src/admin-client.ts","../../modules/flags/src/flags-client.ts","../../modules/flags/src/pooled-flags.ts","../src/flags-facade.ts","../src/messaging/deadline-calculator.ts","../src/messaging/delete-fold.ts","../src/messaging/edit-fold.ts","../src/messaging/util.ts","../src/messaging/wire.ts","../src/messaging/keydir.ts","../src/messaging/group-messaging.ts","../src/messaging/mention-ranges.ts","../src/messaging/reaction-fold.ts","../src/messaging/timer-fold.ts","../src/messaging/chat.ts","../src/messaging/delivery-source.ts","../src/messaging/disappearing.ts","../src/messaging/history.ts","../src/messaging/mention-elevation.ts","../src/messaging/wasm/pkg/palbe_mls_bg.js","../src/messaging/wasm/pkg/snippets/mls-rs-core-f99cdecbb456b09c/inline0.js","../src/messaging/wasm/loader.ts","../src/messaging/wasm/bridge.ts","../src/messaging/mls-engine.ts","../src/messaging/registry.ts","../src/messaging/storage.ts","../src/messaging/suppression.ts","../src/messaging/coordinator.ts","../src/messaging/facade.ts","../src/perf/app-start.ts","../src/perf/perf-config-client.ts","../src/perf/fetch-swizzle.ts","../src/perf/offline-queue.ts","../src/perf/perf-state.ts","../src/perf/perf-wire.ts","../src/perf/perf-facade.ts","../src/perf/web-vitals.ts","../src/runtime-metadata.ts","../src/realtime/anon-token.ts","../src/realtime/frames.ts","../src/realtime/connection.ts","../src/realtime/state.ts","../src/realtime/facade.ts","../src/storage.ts","../src/version.ts","../src/runtime.ts","../src/call.ts","../src/upload.ts","../src/pb.ts"],"sourcesContent":["import type { PalbeConfig } from './config.js';\nimport { BackendError } from './errors.js';\nimport { __registerNamespaces, resetNamespaces } from './namespaces.js';\nimport { buildRuntime, type PalbeRuntime } from './runtime.js';\nimport { palbeState } from './state.js';\n\n// Runtime lives in a process-global slot (see state.ts) so every build copy\n// (ESM/CJS, palbe + palbe/internal entries) converges on one singleton.\n// This is the BROWSER path (one runtime per tab). Server-side per-request\n// runtimes arrive with palbe/next (P3); do not sign in from shared server\n// code against this singleton.\n\n/**\n * Called by the generated palbe.gen.ts on import. REPLACES the runtime\n * (re-runs storage hydration) — safe to call repeatedly, e.g. watch-mode\n * regen. The namespace registry intentionally survives re-configure: the\n * gen file re-registers its static descriptors right after.\n */\nexport function __configure(config: PalbeConfig): void {\n const state = palbeState();\n // Destroy the old realtime facade if it was constructed — closes the socket\n // and clears heartbeat timers so they do not outlive the replaced runtime.\n state.runtime?.destroyRealtime();\n state.runtime = buildRuntime(config);\n // Fire after the runtime is in place so listeners that read pb.* immediately\n // (e.g. useSyncExternalStore getSnapshot) see the new runtime.\n for (const cb of state.configuredListeners) {\n try {\n cb();\n } catch {\n // consumer errors must not abort delivery to other listeners\n }\n }\n}\n\n/**\n * Register a callback that fires synchronously after every successful\n * `__configure` call (including re-configure / watch-mode regen).\n *\n * Returns an unsubscribe function. Safe to call before the first `__configure`.\n *\n * Consumed by `palbe/react`; not part of the public `palbe` surface — which is\n * what the separate `palbe/internal` ENTRY expresses, and the only thing that\n * may. DO NOT mark this doc comment with the TSDoc internal tag (not even in\n * prose — the compiler matches the token anywhere in the block): `stripInternal`\n * in tsconfig.json reads the whole src tree, so the tag deletes this declaration\n * from the shipped `internal.d.ts` while the JS keeps exporting it, and every\n * honest importer fails typecheck (WEB-9).\n */\nexport function onConfigured(cb: () => void): () => void {\n const state = palbeState();\n state.configuredListeners.add(cb);\n return () => state.configuredListeners.delete(cb);\n}\n\n/** The configured runtime, or a guided `notConfigured` throw. Package-internal\n * (hence the `palbe/internal` entry) but part of THAT entry's shipped surface —\n * carries no TSDoc internal tag, which `stripInternal` would honour by deleting\n * this declaration from `internal.d.ts` (see `onConfigured` above). */\nexport function getRuntime(): PalbeRuntime {\n const rt = palbeState().runtime;\n if (!rt) throw BackendError.notConfigured();\n return rt;\n}\n\n/** Test-only: drop runtime + registered namespaces. */\nexport function __reset(): void {\n const state = palbeState();\n state.runtime?.destroyRealtime();\n state.runtime = null;\n state.configuredListeners.clear();\n resetNamespaces();\n}\n\nexport type { EndpointDescriptor, NamespaceTree } from './namespaces.js';\nexport type { PB } from './pb.js';\nexport { createBoundClient } from './pb.js';\nexport type { PalbeConfig, PalbeRuntime };\n// Server-adapter seams (palbe/next): per-request runtime + bound client.\nexport { __registerNamespaces, buildRuntime };\n","import type { HttpClient } from './http.js';\nimport type { ProjectConfig } from './types.js';\n\nconst CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes\n\nexport class ConfigFetcher {\n protected readonly httpClient: HttpClient;\n private cachedConfig: ProjectConfig | null = null;\n private cacheTimestamp = 0;\n\n constructor(httpClient: HttpClient) {\n this.httpClient = httpClient;\n }\n\n async getConfig(): Promise<ProjectConfig | null> {\n const now = Date.now();\n\n if (this.cachedConfig && now - this.cacheTimestamp < CACHE_TTL_MS) {\n return this.cachedConfig;\n }\n\n try {\n const response = await this.httpClient.request<ProjectConfig>('GET', '/v1/config');\n\n if (response.error || !response.data) {\n return null;\n }\n\n this.cachedConfig = response.data;\n this.cacheTimestamp = now;\n\n return this.cachedConfig;\n } catch {\n return null;\n }\n }\n}\n","export class PalbaseError extends Error {\n readonly code: string;\n readonly status: number;\n readonly details?: unknown;\n\n constructor(code: string, message: string, status: number, details?: unknown) {\n super(message);\n this.name = 'PalbaseError';\n this.code = code;\n this.status = status;\n this.details = details;\n }\n}\n","// Proof-of-work: the bot gate in front of /auth/signup and /auth/login.\n//\n// The server answers an unsolved request with 403 and a challenge in the body:\n//\n// { \"error\": \"pow_required\", \"challenge\": { \"id\", \"prefix\", \"difficulty\" } }\n//\n// A client finds any nonce whose SHA-256(prefix + nonce) begins with\n// `difficulty` zero bits, then repeats the request carrying the id and nonce as\n// headers. The work is the point: a person signing up pays it once and does not\n// notice, a script signing up ten thousand times pays it ten thousand times.\n//\n// # Why this lives in core, and not one layer up\n//\n// Until 2026-08-14 nothing shipped could solve it: the gate was written with the\n// server and its own integration harness, and every real client sent requests\n// without the headers and got 403. It was then solved in @palbase/web's own\n// request path — which covers `pb.call` and the module facades and NOT\n// `pb.auth.*`, because those go through @palbase/auth's client and from there\n// into core's HttpClient. So the fix landed everywhere except the two endpoints\n// the gate actually guards, and `npm i @palbase/web` still could not sign a\n// person in. Measured 2026-08-18 against a real stack, on the published 7.3.0.\n//\n// The lesson is where a retry belongs: at the layer that ISSUES the request.\n// Core owns fetch for every client in this repo, so core owns the challenge.\n//\n// WebCrypto rather than a hashing dependency: `crypto.subtle` is present in\n// browsers and in Node 18+, which is the same floor the rest of the SDK sets.\n// Measured at the server's default difficulty of 16: ~330ms, ~65k digests.\n\n/** The challenge a `pow_required` response carries. */\nexport interface PowChallenge {\n id: string;\n prefix: string;\n difficulty: number;\n}\n\n/** Header names the retry must carry. Mirrors the server's constants. */\nexport const POW_CHALLENGE_ID_HEADER = 'X-PoW-Challenge-ID';\nexport const POW_NONCE_HEADER = 'X-PoW-Nonce';\n\n/**\n * Reads a challenge out of an error envelope, or returns null when the envelope\n * is not a `pow_required` one.\n *\n * The whole wire envelope is stored on the error, so the challenge arrives\n * without the HTTP layer having to know about proof-of-work at all.\n */\nexport function asPowChallenge(details: unknown): PowChallenge | null {\n if (typeof details !== 'object' || details === null) return null;\n const env = details as Record<string, unknown>;\n if (env.error !== 'pow_required') return null;\n const c = env.challenge;\n if (typeof c !== 'object' || c === null) return null;\n const { id, prefix, difficulty } = c as Record<string, unknown>;\n if (typeof id !== 'string' || typeof prefix !== 'string') return null;\n if (typeof difficulty !== 'number' || !Number.isInteger(difficulty) || difficulty < 0) return null;\n return { id, prefix, difficulty };\n}\n\nconst encoder = new TextEncoder();\n\n/**\n * One SHA-256, by the fastest route this runtime offers.\n *\n * Awaiting `crypto.subtle.digest` once per nonce is what made this expensive,\n * and the cost is the await rather than the hashing. Measured on one machine,\n * 200k digests of a 40-byte input:\n *\n *\tawaited crypto.subtle.digest 105,597 digests/s\n *\tsync node:crypto createHash 1,324,503 digests/s — 12.5x\n *\n * That is the difference between difficulty 24 taking 159 seconds and taking\n * 13. Node, Bun and Deno all have the sync one; a browser has only WebCrypto,\n * and there it stays async.\n *\n * The specifier is assembled at runtime so a browser bundler does not try to\n * resolve `node:crypto` and fail the build over a branch that never runs there.\n */\ntype Hasher = (input: string) => Uint8Array | Promise<Uint8Array>;\n\nlet hasher: Hasher | null = null;\n\nasync function digester(): Promise<Hasher> {\n if (hasher) return hasher;\n // Read off globalThis with an inline shape rather than by naming `process`,\n // which needs @types/node — a dependency this package does not have and should\n // not grow for one branch. It typechecked locally only because those types\n // were hoisted into node_modules by a sibling package; the publish workflow's\n // clean checkout is what said so, which is exactly what it is for.\n const runtime = globalThis as {\n process?: { versions?: { node?: string; bun?: string } };\n };\n const nodeish =\n runtime.process?.versions?.node !== undefined ||\n runtime.process?.versions?.bun !== undefined;\n if (nodeish) {\n try {\n const mod = (await import(/* @vite-ignore */ `${'node:'}crypto`)) as {\n createHash?: (alg: string) => { update(s: string): { digest(): Uint8Array } };\n };\n if (typeof mod.createHash === 'function') {\n const createHash = mod.createHash;\n hasher = (input: string) => new Uint8Array(createHash('sha256').update(input).digest());\n return hasher;\n }\n } catch {\n // No node:crypto here. WebCrypto below is not a fallback in the apologetic\n // sense — it is the only hash a browser has, and it is correct.\n }\n }\n hasher = async (input: string) =>\n new Uint8Array(await crypto.subtle.digest('SHA-256', encoder.encode(input)));\n return hasher;\n}\n\n/** Monotonic where it exists, wall-clock where it does not. */\nconst now = (): number =>\n typeof performance !== 'undefined' && typeof performance.now === 'function'\n ? performance.now()\n : Date.now();\n\n/** Counts leading zero bits, stopping at the first byte that has a one. */\nfunction leadingZeroBits(hash: Uint8Array): number {\n let bits = 0;\n for (const byte of hash) {\n if (byte === 0) {\n bits += 8;\n continue;\n }\n // clz32 counts across 32 bits; a byte occupies the low 8, so the first 24\n // are always zero and get subtracted back off.\n return bits + Math.clz32(byte) - 24;\n }\n return bits;\n}\n\n/**\n * The hardest challenge this client will attempt.\n *\n * Not a taste: it is the server's own ceiling. palauth maps a risk score to a\n * difficulty and its worst case is 24 (`DifficultyForRisk`, bot/pow.go:156-166).\n * Anything above that cannot have come from a stack behaving as designed, and\n * the cost of humouring it falls entirely on this side — each step up DOUBLES\n * the work, so difficulty 30 is sixty-four times a legitimate worst case and, on\n * the web, sixty-four times a frozen main thread. Refused immediately, by name.\n */\nexport const MAX_POW_DIFFICULTY = 24;\n\n/**\n * Finds a nonce satisfying the challenge and returns the headers a retry needs.\n *\n * THE BUDGET SCALES WITH THE CHALLENGE, and the first version of this did not.\n * It bounded the search at a flat `1 << 24` — which is not a generous bound for\n * difficulty 24, it is the EXPECTED number of attempts. Finding a nonce is a\n * geometric process: the chance of needing more than 2^d attempts is 1/e, so a\n * flat 2^24 would have failed roughly 37% of legitimate hardest-risk challenges,\n * and failed them for precisely the users the gate exists to slow down — who\n * would have been unable to sign in at all rather than made to wait.\n *\n * Eight times expected puts that at e^-8, about three in ten thousand, while\n * leaving the common case (the server's default 16, and 12 for an unremarkable\n * caller) exactly as cheap as it was.\n *\n * `powBudget` is exported and separate so the RELATIONSHIP can be asserted\n * directly. A test that only watches a cheap challenge succeed cannot tell this\n * budget from the flat one it replaced — measured: reinstating `1 << 24` left\n * such a test green.\n */\nexport function powBudget(difficulty: number): number {\n return 8 * 2 ** difficulty;\n}\n\n/**\n * The longest a solve may be ALLOWED to take, and the difference from a\n * deadline is the whole point.\n *\n * The first version of this was a flat 120s deadline, and it was measured to be\n * worse than the flat iteration budget it was meant to backstop: at ~105k\n * digests/s, difficulty 24 EXPECTS 159 seconds, so a 120s clock killed the\n * majority of legitimate hardest-risk solves — reintroducing, larger, exactly\n * the class of defect that replacing `1 << 24` had removed. Guessing a number\n * for an unknown machine cannot work: the same difficulty is 13 seconds on a\n * runtime with a sync hasher and 159 on one without.\n *\n * So the machine is MEASURED, and the decision moves to the front. A short\n * calibration gives the rate this process actually hashes at; if the whole\n * iteration budget cannot fit in this window at that rate, the solve is refused\n * IMMEDIATELY, naming the numbers. A caller then learns in milliseconds that\n * this difficulty is unpayable here, instead of after two minutes of work\n * thrown away.\n *\n * What remains after that is a guarantee rather than a gamble: a solve that\n * starts can always finish inside its budget, so the only failure left is the\n * budget's own e^-8.\n */\nexport const POW_TIME_BUDGET_MS = 120_000;\n\n/**\n * The window the rate is measured over, and the warm-up it deliberately skips.\n *\n * All of these are REAL attempts — the search starts at nonce 0 and never\n * restarts — so calibration costs nothing but the reading. The first 1024 are\n * excluded from the timing because they include this loop's own JIT warm-up:\n * measured, timing from zero reported 747k digests/s on a machine whose steady\n * rate is 1.32M, and the decision below would have refused a difficulty this\n * machine can pay in half the allowance.\n */\nconst CALIBRATION_WARMUP = 1024;\nconst CALIBRATION_END = 9216;\n\nexport async function solvePowChallenge(\n challenge: PowChallenge,\n maxIterations = powBudget(challenge.difficulty),\n // The caller's AbortSignal, honoured INSIDE the loop rather than only around\n // the fetch it precedes — for the callers that have one. `pb.auth.signIn` does\n // NOT: it reaches the network through @palbase/auth's client, which takes\n // credentials and nothing else. So it is the extra a caller can opt into, and\n // POW_TIME_BUDGET_MS below is what actually bounds the work.\n signal?: AbortSignal,\n): Promise<Record<string, string>> {\n if (challenge.difficulty > MAX_POW_DIFFICULTY) {\n throw new Error(\n `proof-of-work: refusing difficulty ${challenge.difficulty}; this client attempts at most ${MAX_POW_DIFFICULTY}, which is the highest a Palbase stack issues`,\n );\n }\n\n const digest = await digester();\n let warmedAt = 0;\n let calibrated = false;\n // Armed by the calibration below, never before it: until the rate is known\n // there is no honest number to put here.\n let deadline = Number.POSITIVE_INFINITY;\n\n for (let nonce = 0; nonce < maxIterations; nonce++) {\n // Checked in batches: reading them is cheap but not free, and a\n // 1024-digest granularity bounds the delay at a few milliseconds.\n if ((nonce & 1023) === 0) {\n if (signal?.aborted) {\n throw new DOMException('proof-of-work solve aborted', 'AbortError');\n }\n if (now() > deadline) {\n throw new Error(\n `proof-of-work: gave up on difficulty ${challenge.difficulty} after ${POW_TIME_BUDGET_MS / 1000}s ` +\n `and ${nonce.toLocaleString()} attempts — the tail this run drew is longer than the allowance`,\n );\n }\n }\n\n // THE DECISION, TAKEN ONCE AND TAKEN EARLY.\n //\n // After CALIBRATION_DIGESTS real attempts the rate of THIS process is\n // known, so the question \"can this machine pay this difficulty\" has an\n // answer instead of an assumption. If the whole budget cannot fit in the\n // time budget, refuse here — milliseconds in, with the numbers — rather\n // than spend two minutes and throw them away. If it fits, everything after\n // this point is guaranteed to finish inside the window, so the only\n // remaining failure is the budget's own e^-8.\n if (nonce === CALIBRATION_WARMUP) {\n warmedAt = now();\n }\n if (!calibrated && nonce === CALIBRATION_END) {\n calibrated = true;\n const elapsed = Math.max(now() - warmedAt, 0.001);\n const rate = (CALIBRATION_END - CALIBRATION_WARMUP) / (elapsed / 1000);\n // EXPECTED, not worst case, and the difference is the whole judgement.\n //\n // Finding a nonce is geometric: 2^difficulty attempts on average, with a\n // long tail the 8x budget covers. Refusing because the TAIL will not fit\n // would turn away work whose expected cost is seventeen seconds — measured\n // exactly that on this machine at difficulty 24. Refusing on the EXPECTED\n // cost turns away only what is genuinely unpayable here, and what it lets\n // through is then cut by the clock with probability e^-(budget/expected):\n // at 120s against a 17s expectation that is one run in a thousand, and at\n // difficulty 20 on a browser it is one in a hundred and fifty thousand.\n const expectedMs = (2 ** challenge.difficulty / rate) * 1000;\n if (expectedMs > POW_TIME_BUDGET_MS) {\n throw new Error(\n `proof-of-work: difficulty ${challenge.difficulty} needs about ${Math.round(expectedMs / 1000)}s here ` +\n `(${Math.round(rate).toLocaleString()} digests/s) and this client allows ${POW_TIME_BUDGET_MS / 1000}s; ` +\n `refusing before spending the time rather than after`,\n );\n }\n deadline = now() + (POW_TIME_BUDGET_MS - (now() - warmedAt));\n }\n\n const hash = await digest(challenge.prefix + nonce);\n if (leadingZeroBits(hash) >= challenge.difficulty) {\n return {\n [POW_CHALLENGE_ID_HEADER]: challenge.id,\n [POW_NONCE_HEADER]: String(nonce),\n };\n }\n }\n throw new Error(\n `proof-of-work: no nonce found for difficulty ${challenge.difficulty} within ${maxIterations} attempts`,\n );\n}\n","export type Platform = 'browser' | 'node' | 'react-native' | 'deno' | 'bun';\n\ndeclare const Deno: unknown;\ndeclare const process: { versions: Record<string, string> } | undefined;\n\nexport function detectPlatform(): Platform {\n if (typeof Deno !== 'undefined') {\n return 'deno';\n }\n\n if (process?.versions) {\n if ('bun' in process.versions) {\n return 'bun';\n }\n if ('node' in process.versions) {\n return 'node';\n }\n }\n\n if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {\n return 'react-native';\n }\n\n return 'browser';\n}\n\n/**\n * The platform word this SDK puts on the wire (`X-Platform`), which the server\n * reads to target flags and to label telemetry.\n *\n * It is NOT `detectPlatform()`'s value verbatim: that reports the JS host\n * (\"browser\"), while the wire wants the platform. iOS sends \"ios\", not the name\n * of its runtime, and a condition author writes `client.platform == 'web'` —\n * the word every other flag vendor uses too. Server hosts keep their own names,\n * where the distinction is the useful part.\n */\nexport function wirePlatform(): string {\n const host = detectPlatform();\n return host === 'browser' ? 'web' : host;\n}\n","import { PalbaseError } from './errors.js';\nimport { asPowChallenge, solvePowChallenge } from './pow.js';\nimport { wirePlatform } from './platform.js';\nimport type { TokenManager } from './token.js';\nimport type { HttpClientOptions, PalbaseResponse, RequestOptions } from './types.js';\n\n/**\n * Default production host. Dev / staging / local callers override via\n * `options.url`. Apex-style routing is the only supported production path;\n * Kong resolves Environment identity from the API key.\n */\nconst PALBASE_DEFAULT_HOST = 'api.palbase.studio';\n\n/**\n * Parse the Environment ref from a Palbase API key.\n *\n * Canonical shape: `pb_{environment_ref}_c{random}`, where the Environment ref\n * is 4-24 lowercase ASCII alphanumeric characters and random is AT LEAST 20\n * base62 chars.\n *\n * The length is a floor, not an equality. The stack's own minter writes 20\n * (v2/cmd/palsvc/initenv.go) and the cloud control plane writes 32\n * (v2-cloud/platform/server/services/keys.ts), and the door that admits the\n * request refuses to rule on the difference: *\"a shorter or longer secret is\n * not a security property this door can rule on\"*\n * (v2/internal/platform/identitymw.go: parseAPIKey). A client that is stricter\n * than the server does not add safety — it just refuses working keys, which is\n * exactly what this one did to every cloud project until 2026-08-25.\n *\n * Returns the Environment ref on match; `null` otherwise.\n */\nconst API_KEY_RE = /^pb_([a-z0-9]{4,24})_c[A-Za-z0-9]{20,}$/;\n\nfunction parseEnvironmentRef(apiKey: string): string | null {\n return API_KEY_RE.exec(apiKey)?.[1] ?? null;\n}\nconst MAX_RETRIES = 3;\nconst INITIAL_BACKOFF_MS = 200;\n/**\n * Upper bound on a single 429 retry sleep. A server may return a long\n * Retry-After (a locked account can send minutes/hours); honoring it verbatim\n * would HANG the request for that whole window. Cap each retry at 10s — after\n * MAX_RETRIES the 429 envelope surfaces to the caller (fail fast, don't sleep\n * minutes). The clamp never skips a retry; it only bounds how long each waits.\n */\nconst MAX_RETRY_DELAY_MS = 10_000;\n\n/**\n * Carry a 429's retry hint into the error envelope when only the header has it.\n *\n * A REFUSAL FROM THE EDGE CARRIES NOTHING BUT THE HEADER. Envoy and the\n * gateway limiter answer before any Palbase service is reached, so their 429\n * has no `retry_after` and no `data.retryAfter` — and every reader above this\n * layer (`@palbase/web`'s BackendError, the iOS SDK) looks in the BODY. The\n * seconds were on the wire and unreachable to all of them.\n *\n * Lifted under `retry_after`, the platform's own name for it (palauth's\n * rate-limit envelope), never overwriting a hint the service itself sent — a\n * service knows its window, the edge only knows its own.\n */\nfunction withRetryHint(\n body: Record<string, unknown> | undefined,\n response: Response,\n): Record<string, unknown> | undefined {\n if (response.status !== 429) return body;\n const data = body?.data;\n const alreadyStated =\n typeof body?.retry_after === 'number' ||\n (typeof data === 'object' && data !== null && 'retryAfter' in data);\n if (alreadyStated) return body;\n const seconds = Number.parseInt(response.headers.get('Retry-After') ?? '', 10);\n if (Number.isNaN(seconds) || seconds <= 0) return body;\n return { ...body, retry_after: seconds };\n}\n\n/**\n * Request interceptor. Runs before every HTTP request.\n * Can modify headers, body, or reject the request.\n */\nexport type RequestInterceptor = (request: {\n headers: Record<string, string>;\n method: string;\n path: string;\n}) => void | Promise<void>;\n\nexport class HttpClient {\n protected readonly apiKey: string;\n protected readonly options?: HttpClientOptions;\n\n tokenManager: TokenManager | null = null;\n\n /**\n * Admin JWT used for platform admin endpoints (/admin/*).\n * When set, takes precedence over tokenManager access token in the\n * Authorization header.\n */\n adminToken: string | null = null;\n\n private readonly interceptors: RequestInterceptor[] = [];\n\n constructor(apiKey: string, options?: HttpClientOptions) {\n this.apiKey = apiKey;\n this.options = options;\n }\n\n /** Set (or clear) the admin JWT used on admin endpoints. */\n setAdminToken(token: string | null): void {\n this.adminToken = token;\n }\n\n /**\n * Create a scoped HttpClient that adds the given extra headers to every\n * request. The returned client shares the admin token and token manager\n * with the parent at runtime — later changes on the parent propagate to\n * the scope and vice versa.\n *\n * Typical use: adding an Environment-routing header for an admin call.\n */\n withHeaders(extra: Record<string, string>): HttpClient {\n const mergedHeaders = { ...(this.options?.headers ?? {}), ...extra };\n\n const scoped: HttpClient = new HttpClient(this.apiKey, {\n ...this.options,\n headers: mergedHeaders,\n });\n scoped.tokenManager = this.tokenManager;\n // Delegate adminToken reads + writes to the parent so the scope always\n // sees the latest token, and setAdminToken on the scope affects the parent.\n Object.defineProperty(scoped, 'adminToken', {\n get: () => this.adminToken,\n set: (v: string | null) => {\n this.adminToken = v;\n },\n configurable: true,\n });\n return scoped;\n }\n\n /** Add a request interceptor. Runs before every request. */\n addInterceptor(interceptor: RequestInterceptor): void {\n this.interceptors.push(interceptor);\n }\n\n async request<T>(\n method: string,\n path: string,\n options?: RequestOptions,\n ): Promise<PalbaseResponse<T>> {\n // If token is expired and refresh is available, refresh before making the request\n if (\n this.tokenManager?.isExpired() &&\n this.tokenManager.getRefreshToken() &&\n this.tokenManager.refreshFunction\n ) {\n try {\n await this.tokenManager.refreshSession();\n } catch (e) {\n const status = e instanceof PalbaseError ? e.status : 0;\n if (status === 400 || status === 401 || status === 403) {\n // Terminal: the refresh token is dead (revoked/expired/forbidden).\n // Clear the session (listeners persist the sign-out) and proceed\n // unauthenticated — the endpoint will 401 into the normal error\n // envelope instead of bricking every subsequent call including\n // the recovery sign-in.\n this.tokenManager.clearSession();\n } else {\n throw e; // network/5xx: transient, stay loud\n }\n }\n }\n\n return this.executeWithRetry<T>(method, path, options, 0);\n }\n\n private getBaseUrl(): string {\n // Explicit URL always wins (local dev, staging, test rigs).\n if (this.options?.url) {\n return this.options.url;\n }\n\n // Validate the key shape up front so apex-routed callers still\n // fail loud on a malformed key instead of hitting the gateway\n // with bad credentials.\n if (this.apiKey && parseEnvironmentRef(this.apiKey) === null) {\n throw new PalbaseError(\n 'invalid_api_key',\n 'Invalid API key format. Expected pb_{environment_ref}_c{at least 20 base62 chars}. For dev/staging pass `url: \"https://api.dev.palbase.studio\"` via options.',\n 0,\n );\n }\n\n return `https://${PALBASE_DEFAULT_HOST}`;\n }\n\n private buildHeaders(options?: RequestOptions): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n // Client identity, the web counterpart of the iOS SDK's\n // ClientInfo.augment(). The server reads these to resolve flag targeting\n // conditions and to label telemetry, so an app declares nothing and calls\n // nothing — whatever the SDK can know, it sends.\n 'X-Platform': wirePlatform(),\n };\n // The host app's own version is not knowable on the web (no bundle to read\n // it from), so it is opt-in; when given it fills the same header iOS fills\n // from CFBundleShortVersionString.\n const appVersion = this.options?.appVersion?.trim();\n if (appVersion) {\n headers['X-Palbase-Client-Version'] = appVersion;\n }\n\n // Palbase Environment keys live in the `apikey` header — never in\n // `Authorization` — because Kong's key-auth resolves them on that\n // header and the gateway's pre-function plugin stamps the downstream\n // identity.\n const effectiveKey = this.apiKey;\n if (effectiveKey) {\n headers['apikey'] = effectiveKey;\n }\n\n // User session token, if any. Kong's pre-function plugin strips\n // Authorization on /v1/* routes anyway (PostgREST has no JWT\n // secret and would crash on a Bearer it can't decode), but\n // sending it preserves the contract for /auth/* endpoints that\n // do consume the bearer (e.g. session refresh).\n const token = this.tokenManager?.getAccessToken();\n if (token) {\n headers['Authorization'] = `Bearer ${token}`;\n }\n\n // adminToken (platform admin JWT) takes precedence — used by the\n // @palbase/admin internal flows that hit /admin/* routes; those\n // routes verify the bearer themselves and aren't subject to the\n // /v1/* Authorization-strip rule.\n if (this.adminToken) {\n headers['Authorization'] = `Bearer ${this.adminToken}`;\n }\n\n // Merge global custom headers\n if (this.options?.headers) {\n Object.assign(headers, this.options.headers);\n }\n\n // Merge per-request headers\n if (options?.headers) {\n Object.assign(headers, options.headers);\n }\n\n return headers;\n }\n\n private async executeWithRetry<T>(\n method: string,\n path: string,\n options: RequestOptions | undefined,\n attempt: number,\n // Headers a PREVIOUS attempt earned and this one has to carry. Today that\n // is only the solved proof-of-work pair; it is a parameter rather than a\n // field because it belongs to one request's second try, and a field would\n // leak it onto every later call made through this client.\n earned?: Record<string, string>,\n ): Promise<PalbaseResponse<T>> {\n const url = `${this.getBaseUrl()}${path}`;\n const headers = { ...this.buildHeaders(options), ...earned };\n\n // Run interceptors\n for (const interceptor of this.interceptors) {\n await interceptor({ headers, method, path });\n }\n\n const fetchOptions: RequestInit = {\n method,\n headers,\n signal: options?.signal,\n };\n\n if (options?.body !== undefined) {\n fetchOptions.body = JSON.stringify(options.body);\n }\n\n let response: Response;\n try {\n response = await fetch(url, fetchOptions);\n } catch (error) {\n // Network error — retry with backoff\n if (attempt < MAX_RETRIES - 1) {\n const backoff = INITIAL_BACKOFF_MS * 2 ** attempt;\n await this.delay(backoff);\n // WITHOUT `earned`, and that is the whole point of this line.\n //\n // A network error means the response was lost, not that the request\n // was. If it reached the server, the challenge is already SPENT —\n // palauth's VerifyChallenge reads and deletes in one step\n // (bot/pow.go:96-104), deliberately, because a proof presented twice is\n // not proof. Replaying the nonce would then answer `pow_invalid`, and\n // the one-solve guard below would refuse to try again: a request one\n // fresh solve away from succeeding, failed. Dropping it costs nothing\n // in the other case — if the server never saw the request, a fresh\n // challenge works exactly as well as the old one.\n return this.executeWithRetry<T>(method, path, options, attempt + 1);\n }\n\n // All retries exhausted — throw PalbaseError\n throw new PalbaseError(\n 'network_error',\n error instanceof Error ? error.message : 'Network request failed',\n 0,\n );\n }\n\n // Handle 429 Too Many Requests — retry with Retry-After or backoff;\n // if retries exhausted, fall through to normal error response handling below\n if (response.status === 429) {\n if (attempt < MAX_RETRIES - 1) {\n const retryAfter = response.headers.get('Retry-After');\n const parsed = retryAfter ? Number.parseInt(retryAfter, 10) : Number.NaN;\n // Clamp the server-requested wait: a long Retry-After (locked account)\n // must not hang the request — cap each sleep, exhaust MAX_RETRIES, then\n // fall through to surface the 429 envelope below.\n const delayMs = Number.isNaN(parsed)\n ? INITIAL_BACKOFF_MS * 2 ** attempt\n : Math.min(parsed * 1000, MAX_RETRY_DELAY_MS);\n await this.delay(delayMs);\n // WITH `earned`, unlike the network path above: a 429 is a refusal the\n // server issued INSTEAD of doing the work, so the challenge was never\n // consumed. The edge's rate limiter answers before palsvc, and on the\n // auth routes palauth's own limiter runs BEFORE the proof-of-work\n // middleware (auth/internal/server/server.go: rl.LoginByIP, then powMW).\n return this.executeWithRetry<T>(method, path, options, attempt + 1, earned);\n }\n }\n\n // Parse response body\n let data: T | null = null;\n let errorBody: { error?: string; error_description?: string; status?: number } | undefined;\n\n // HEAD responses have no body by spec — skip parsing.\n const contentType = response.headers.get('Content-Type');\n if (method !== 'HEAD' && contentType?.includes('json')) {\n const body = (await response.json()) as Record<string, unknown>;\n if (response.ok) {\n data = body as T;\n } else {\n errorBody = body as typeof errorBody;\n }\n }\n\n // Proof-of-work: /auth/signup and /auth/token sit behind a bot gate that\n // answers an unsolved request with 403 and the challenge in the body. Solve\n // it and repeat the request carrying the two headers; the caller never\n // learns the gate is there.\n //\n // HERE, in core, because this is the layer that issues the request for every\n // client in the repo — @palbase/auth's sign-in, @palbase/web's facades, the\n // server SDK. The same retry lived one layer up in @palbase/web until\n // 2026-08-18 and covered everything EXCEPT `pb.auth.*`, which reaches the\n // network through this method; so the gate stayed unsatisfiable on exactly\n // the two endpoints it guards.\n //\n // ONE retry, and only when the body really carries a challenge: `earned`\n // being set already means this IS the second try. A 403 that says\n // pow_required without a challenge is a server the client cannot satisfy,\n // and looping on it would turn a broken gate into a hang.\n if (response.status === 403 && !earned) {\n const challenge = asPowChallenge(errorBody);\n if (challenge) {\n return this.executeWithRetry<T>(\n method,\n path,\n options,\n attempt,\n await solvePowChallenge(challenge, undefined, options?.signal),\n );\n }\n }\n\n if (!response.ok) {\n return {\n data: null,\n error: new PalbaseError(\n errorBody?.error ?? 'unknown_error',\n errorBody?.error_description ?? response.statusText,\n response.status,\n withRetryHint(errorBody, response),\n ),\n status: response.status,\n };\n }\n\n // Parse PostgREST Content-Range for count queries (e.g. \"0-9/42\" or \"*/42\").\n const contentRange = response.headers.get('Content-Range');\n let count: number | undefined;\n if (contentRange) {\n const slash = contentRange.lastIndexOf('/');\n if (slash >= 0) {\n const totalPart = contentRange.slice(slash + 1);\n if (totalPart !== '*') {\n const parsed = Number.parseInt(totalPart, 10);\n if (!Number.isNaN(parsed)) {\n count = parsed;\n }\n }\n }\n }\n\n return {\n data,\n error: null,\n status: response.status,\n ...(count !== undefined ? { count } : {}),\n };\n }\n\n private delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n","import type { AuthStateCallback, Session, Unsubscribe } from './types.js';\n\nexport class TokenManager {\n private session: Session | null = null;\n private listeners: Set<AuthStateCallback> = new Set();\n private refreshPromise: Promise<void> | null = null;\n private refreshing = false;\n\n refreshFunction: ((refreshToken: string) => Promise<Session>) | null = null;\n\n setSession(session: Session): void {\n this.session = session;\n this.notify('SESSION_SET', session);\n }\n\n getAccessToken(): string | null {\n return this.session?.accessToken ?? null;\n }\n\n getRefreshToken(): string | null {\n return this.session?.refreshToken ?? null;\n }\n\n clearSession(): void {\n this.session = null;\n this.notify('SESSION_CLEARED', null);\n }\n\n isExpired(): boolean {\n if (!this.session) return true;\n return Date.now() >= this.session.expiresAt;\n }\n\n async refreshSession(): Promise<void> {\n if (!this.session?.refreshToken || !this.refreshFunction) {\n return;\n }\n\n // Collapse concurrent refresh calls into a single request\n if (this.refreshPromise) {\n return this.refreshPromise;\n }\n\n // Re-entrancy guard: the wired refreshFunction issues its own HTTP request\n // (POST /auth/token/refresh) through HttpClient, whose pre-flight calls\n // refreshSession() again SYNCHRONOUSLY — before `refreshPromise` below is\n // assigned (the whole chain runs before the first real await). Without\n // this flag that recursion is unbounded (stack overflow). Returning early\n // lets the refresh request itself proceed unauthenticated — it carries\n // the refresh token in its body, not the Bearer header.\n if (this.refreshing) {\n return;\n }\n\n this.refreshing = true;\n this.refreshPromise = this.executeRefresh(this.session.refreshToken);\n\n try {\n await this.refreshPromise;\n } finally {\n this.refreshPromise = null;\n this.refreshing = false;\n }\n }\n\n onAuthStateChange(callback: AuthStateCallback): Unsubscribe {\n this.listeners.add(callback);\n return () => {\n this.listeners.delete(callback);\n };\n }\n\n private async executeRefresh(refreshToken: string): Promise<void> {\n if (!this.refreshFunction) return;\n const newSession = await this.refreshFunction(refreshToken);\n this.setSession(newSession);\n }\n\n private notify(event: 'SESSION_SET' | 'SESSION_CLEARED', session: Session | null): void {\n for (const listener of this.listeners) {\n listener(event, session);\n }\n }\n}\n","import { PalbaseError, type PalbaseResponse } from '@palbase/core';\n\nexport interface FieldError {\n field: string;\n message: string;\n}\n\nexport type BackendErrorKind =\n | 'notConfigured'\n | 'validation'\n | 'unauthorized'\n | 'rateLimited'\n | 'server'\n | 'network'\n | 'decode';\n\ninterface BackendErrorParams {\n code: string;\n message: string;\n status?: number;\n requestId?: string;\n fields?: FieldError[];\n retryAfter?: number;\n data?: unknown;\n}\n\nexport class BackendError extends Error {\n readonly kind: BackendErrorKind;\n readonly code: string;\n readonly status: number;\n readonly requestId?: string;\n readonly fields?: FieldError[];\n readonly retryAfter?: number;\n readonly data?: unknown;\n\n constructor(kind: BackendErrorKind, params: BackendErrorParams) {\n super(params.message);\n this.name = 'BackendError';\n this.kind = kind;\n this.code = params.code;\n this.status = params.status ?? 0;\n this.requestId = params.requestId;\n this.fields = params.fields;\n this.retryAfter = params.retryAfter;\n this.data = params.data;\n }\n\n static notConfigured(): BackendError {\n return new BackendError('notConfigured', {\n code: 'not_configured',\n message:\n \"Palbe is not configured. Run 'palbase web link' in your project and make sure palbe.gen.ts is imported once at app startup.\",\n });\n }\n}\n\nfunction isFieldErrorArray(value: unknown): value is FieldError[] {\n return (\n Array.isArray(value) &&\n value.length > 0 &&\n value.every(\n (v) =>\n typeof v === 'object' &&\n v !== null &&\n typeof (v as Record<string, unknown>).field === 'string' &&\n typeof (v as Record<string, unknown>).message === 'string',\n )\n );\n}\n\n/**\n * Find the field-error array in a wire envelope.\n *\n * `data.fields` IS THE CONTRACT: the backend SDK declares\n * `bad_request: { fields: [{ field, message }] }` in its error registry, so\n * codegen types it as `error.data.fields`, `throw new BadRequest({ fields })`\n * answers in that shape, and — since 22.1.0 — so do the engine's own automatic\n * `@Body`/`@QueryParams`/`@Headers` refusals.\n *\n * The top-level `fields` is the SAME array as emitted by backend 22.0.x and\n * earlier, which is still what every tenant deployed before that release\n * answers. A browser gets the SDK long before its tenant redeploys, so this\n * reader covers both until the fleet has rolled; drop it once it has.\n *\n * A nested `details: [{ field, message }]` was read here until 8.0.0 and is NOT\n * read any more: no Palbase surface has ever emitted it. Believing in it is why\n * `kind: 'validation'` was unreachable for every real 400 the platform sends.\n */\nfunction fieldErrorsOf(envelope: unknown): FieldError[] | undefined {\n const scoped = pickField(pickField(envelope, 'data'), 'fields');\n if (isFieldErrorArray(scoped)) return scoped;\n const flat = pickField(envelope, 'fields');\n return isFieldErrorArray(flat) ? flat : undefined;\n}\n\n/**\n * Find the rate-limit hint in a wire envelope.\n *\n * Two producers, both real and both measured. palauth answers a lockout with a\n * top-level `retry_after` (`auth/internal/ratelimit/limiter.go`). Everything\n * built on the backend SDK — the engine's per-route limiter, and\n * `throw new TooManyRequests({ retryAfter })` — answers with `data.retryAfter`,\n * the name its error registry publishes and codegen types.\n */\nfunction retryAfterOf(envelope: unknown): number | undefined {\n return (\n pickNumber(envelope, 'retry_after') ?? pickNumber(pickField(envelope, 'data'), 'retryAfter')\n );\n}\n\nfunction pickField(value: unknown, key: string): unknown {\n if (typeof value === 'object' && value !== null) {\n return (value as Record<string, unknown>)[key];\n }\n return undefined;\n}\n\nfunction pickNumber(value: unknown, key: string): number | undefined {\n const n = pickField(value, key);\n return typeof n === 'number' ? n : undefined;\n}\n\nfunction pickString(value: unknown, key: string): string | undefined {\n const s = pickField(value, key);\n return typeof s === 'string' ? s : undefined;\n}\n\nexport function fromPalbaseError(err: PalbaseError): BackendError {\n // HttpClient stores the WHOLE wire envelope as err.details:\n // { error, error_description, status, request_id, retry_after?, data? }\n const base: BackendErrorParams = {\n code: err.code,\n message: err.message,\n status: err.status,\n requestId: pickString(err.details, 'request_id'),\n data: pickField(err.details, 'data'),\n };\n if (err.code === 'network_error') return new BackendError('network', base);\n if (err.status === 401) return new BackendError('unauthorized', base);\n if (err.status === 429)\n return new BackendError('rateLimited', { ...base, retryAfter: retryAfterOf(err.details) });\n const fields = fieldErrorsOf(err.details);\n if (err.status === 400 && fields) return new BackendError('validation', { ...base, fields });\n // Fallthrough also covers status-0 non-network errors (e.g. auth client's\n // synthetic 'no_refresh_token' with status 0) — those intentionally map to 'server'.\n return new BackendError('server', base);\n}\n\n/** Decode a raw wire body (used by paths that bypass HttpClient, e.g. upload). */\nexport function fromEnvelope(status: number, body: unknown): BackendError {\n const code = pickString(body, 'error') ?? 'http_error';\n const message = pickString(body, 'error_description') ?? `HTTP ${status}`;\n const requestId = pickString(body, 'request_id');\n const params: BackendErrorParams = {\n code,\n message,\n status,\n requestId,\n data: pickField(body, 'data'),\n };\n if (status === 401) return new BackendError('unauthorized', params);\n if (status === 429)\n return new BackendError('rateLimited', { ...params, retryAfter: retryAfterOf(body) });\n const fields = fieldErrorsOf(body);\n if (status === 400 && fields) return new BackendError('validation', { ...params, fields });\n return new BackendError('server', params);\n}\n\n/**\n * Type guard for BackendError that survives module-identity splits.\n * This package ships dual ESM + CJS builds; if both end up loaded (or the\n * package is installed twice), two distinct BackendError classes coexist and\n * `instanceof` fails for errors thrown by \"the other\" copy. Falls back to a\n * structural check on `name` + `kind`.\n */\nexport function isBackendError(e: unknown): e is BackendError {\n return (\n e instanceof BackendError ||\n (typeof e === 'object' &&\n e !== null &&\n (e as Record<string, unknown>).name === 'BackendError' &&\n typeof (e as Record<string, unknown>).kind === 'string')\n );\n}\n\n/**\n * Structural fallback after instanceof for PalbaseError — same dual ESM+CJS\n * identity-split rationale as isBackendError above: two loaded PalbaseError\n * classes break `instanceof` across copies; the name check bridges that.\n * Returns the error as PalbaseError, or null when it isn't one.\n */\nexport function asPalbaseError(e: unknown): PalbaseError | null {\n if (e instanceof PalbaseError) return e;\n if (e instanceof Error && e.name === 'PalbaseError') return e as PalbaseError;\n return null;\n}\n\n/** Convert an internal {data,error} envelope into data-or-throw. */\nexport function unwrap<T>(res: PalbaseResponse<T>): T {\n if (res.error) throw fromPalbaseError(res.error);\n return res.data as T;\n}\n","const ID_SEGMENT =\n /^(?:\\d+|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/;\n\n// An email lands in a path segment as `jane@acme.com` or percent-encoded\n// `jane%40acme.com`. Either form is PII and must never survive into a metric\n// name, so a segment carrying an `@` (encoded or raw) is masked like an id.\nconst EMAIL_SEGMENT = /(?:@|%40)/i;\n\nfunction isSensitiveSegment(seg: string): boolean {\n return ID_SEGMENT.test(seg) || EMAIL_SEGMENT.test(seg);\n}\n\n/**\n * Capture-time URL redaction (§4.1 privacy, client half). Collapses a request\n * URL to a PII-free, low-cardinality path pattern BEFORE it leaves the browser:\n * query string dropped wholesale, id-like segments masked. The ingest scrubber\n * re-applies server-side (defense in depth).\n *\n * Rules:\n * - absolute URL → keep only `pathname` (scheme/host/query/hash dropped);\n * - relative URL → drop everything from the first `?` onward;\n * - any segment that is purely numeric, a UUID, or carries an email (`@` /\n * `%40`) → `:id`;\n * - credentials/tokens live in the query (dropped wholesale), emails in a\n * path segment (masked), so neither survives into the metric name.\n */\nexport function redactUrl(rawUrl: string): string {\n let path = rawUrl;\n try {\n // Absolute URLs: take the pathname (drops scheme/host/query/hash).\n path = new URL(rawUrl).pathname;\n } catch {\n const q = path.indexOf('?');\n if (q >= 0) path = path.slice(0, q);\n }\n return path\n .split('/')\n .map((seg) => (isSensitiveSegment(seg) ? ':id' : seg))\n .join('/');\n}\n","import { asPalbaseError, fromPalbaseError, unwrap } from './errors.js';\nimport { redactUrl } from './perf/url-redactor.js';\nimport type { PalbeRuntime } from './runtime.js';\nimport { asPowChallenge, solvePowChallenge } from '@palbase/core';\n\nexport interface RequestSpec {\n body?: unknown;\n headers?: Record<string, string>;\n signal?: AbortSignal;\n}\n\nexport interface CallOptions {\n headers?: Record<string, string>;\n signal?: AbortSignal;\n}\n\nconst MUTATING = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);\n\n// The perf flush (`POST /v1/analytics/perf`) and analytics event ingest\n// (`/v1/analytics/*`) BOTH funnel through `palbeRequest`. Tracing them would\n// make every flush emit another `network` item that the next flush ships,\n// then traces again — a self-measurement loop. Excluding the whole\n// `/v1/analytics/` prefix breaks it. This is the SAME self-exclusion shape as\n// the iOS network trace (1b-3), keyed on the identical ingest prefix.\nconst PERF_EXCLUDED_PREFIX = '/v1/analytics/';\n\nfunction isSelfTraced(path: string): boolean {\n return path.startsWith(PERF_EXCLUDED_PREFIX);\n}\n\n/**\n * Codes meaning the server-side session is GONE — a token refresh cannot help\n * and the client must tear its local session down. `subject_fenced` is palauth's\n * wire-true erasure-fence code (`bridge.go`); `subject_erased` is accepted for\n * forward-compat. `session_revoked` is palauth's revoked-session 401.\n * A PLAIN 403 (ordinary authz / RLS denial) is intentionally NOT here — matching\n * all 403s would sign users out on routine permission errors.\n */\nfunction isSessionKilled(status: number, code: string): boolean {\n if (status === 401) return code === 'session_revoked';\n if (status === 403) return code === 'subject_fenced' || code === 'subject_erased';\n return false;\n}\n\n/** Monotonic millisecond clock — `performance.now()` when present, else\n * `Date.now()`. Mirrors `perf-facade.ts`'s `nowMs()` so network durations and\n * custom-trace durations share one time base. */\nfunction nowMs(): number {\n return typeof performance !== 'undefined' && typeof performance.now === 'function'\n ? performance.now()\n : Date.now();\n}\n\n/** An `AbortError` (caller-cancelled request) is NOT a completed network call,\n * so it must never produce a trace. */\nfunction isAbort(e: unknown): boolean {\n return e instanceof Error && e.name === 'AbortError';\n}\n\nexport async function palbeRequest<T>(\n rt: PalbeRuntime,\n method: string,\n path: string,\n spec: RequestSpec = {},\n): Promise<T> {\n const headers: Record<string, string> = { ...spec.headers };\n // Case-insensitive scan: a caller-supplied `idempotency-key` must suppress\n // generation, otherwise Headers merges both casings and dedup breaks.\n const callerHasKey = Object.keys(headers).some((k) => k.toLowerCase() === 'idempotency-key');\n if (MUTATING.has(method) && !callerHasKey) {\n headers['Idempotency-Key'] = crypto.randomUUID();\n }\n\n // `extra` carries the proof-of-work headers on the one retry that needs them.\n // The base `headers` are reused rather than rebuilt, so a retry keeps the same\n // Idempotency-Key and the server still sees one logical request.\n const attempt = async (extra?: Record<string, string>) => {\n try {\n return await rt.http.request<T>(method, path, {\n body: spec.body,\n headers: extra ? { ...headers, ...extra } : headers,\n signal: spec.signal,\n });\n } catch (e) {\n const pe = asPalbaseError(e);\n throw pe ? fromPalbaseError(pe) : e;\n }\n };\n\n // The single network-trace choke point: every SDK fetch funnels through\n // `palbeRequest`, so timing it here records one `network` PerfItem per call —\n // redacted URL + method + final HTTP status + wall-clock duration. The\n // `/v1/analytics/` ingest paths are self-excluded (no recursion) and aborts\n // are skipped (not a completed call).\n // `rt.perf` is guarded too: `buildRuntime` always wires it, but a hand-built\n // runtime (tests, embedders) may omit it — a missing facade must never break\n // the request path.\n const traced = !isSelfTraced(path) && rt.perf !== undefined;\n const startedAt = traced ? nowMs() : 0;\n // Exactly ONE trace per `palbeRequest`: the inner refresh-catch records with\n // the precise status and rethrows; that rethrow is then caught by the outer\n // catch, so the guard keeps the second `record()` a no-op (no double-count).\n let recorded = false;\n const record = (status: number): void => {\n if (!traced || recorded) return;\n recorded = true;\n rt.perf.recordNetwork(method, redactUrl(path), status, nowMs() - startedAt);\n };\n\n let res: Awaited<ReturnType<typeof attempt>>;\n try {\n res = await attempt();\n\n // Reactive 401: refresh once, retry once with the same Idempotency-Key.\n // A revoked session (401 session_revoked) is terminal — a refresh cannot\n // resurrect it, so skip straight to the teardown check below.\n if (\n res.error?.status === 401 &&\n res.error.code !== 'session_revoked' &&\n rt.tokenManager.getRefreshToken() &&\n rt.tokenManager.refreshFunction\n ) {\n try {\n await rt.tokenManager.refreshSession();\n } catch (refreshErr) {\n const pe = asPalbaseError(refreshErr);\n const status = pe?.status ?? 0;\n if (status === 400 || status === 401 || status === 403) {\n // Terminal: the refresh token is dead. Sign out and surface the\n // ORIGINAL 401 (mirrors HttpClient's pre-flight terminal split).\n rt.tokenManager.clearSession();\n record(res.error.status);\n throw fromPalbaseError(res.error);\n }\n // Transient (network/5xx): keep the session, surface the refresh failure.\n record(pe?.status ?? 0);\n throw pe ? fromPalbaseError(pe) : refreshErr;\n }\n res = await attempt();\n }\n\n // Reactive proof-of-work: signup and login sit behind a bot gate that\n // answers an unsolved request with 403 and the challenge in the body. Solve\n // it once and repeat the request with the headers; the caller never learns\n // the gate is there.\n //\n // Placed after the 401 branch so it sees whichever response is current — a\n // refresh-and-retry can land on the gate just as a first attempt can.\n //\n // Only ONE retry, and only when the body really carries a challenge. A 403\n // that merely says pow_required without one is a server the client cannot\n // satisfy, and looping on it would turn a broken gate into a hang.\n if (res.error?.status === 403 && res.error.code === 'pow_required') {\n const challenge = asPowChallenge(res.error.details);\n if (challenge) {\n res = await attempt(await solvePowChallenge(challenge));\n }\n }\n } catch (e) {\n // A thrown error here is a network/transport failure (the `{data,error}`\n // envelope never throws). Record it as a completed-with-error call unless\n // the caller aborted — an abort is a cancellation, not a measurement.\n if (!isAbort(e)) record(asPalbaseError(e)?.status ?? 0);\n throw e;\n }\n\n // Reactive session teardown: a server-side session kill (subject erased /\n // session revoked) means the local session is dead regardless of which facade\n // issued this call — tear it down + emit signedOut{sessionInvalid} once. Scoped\n // to the exact codes so an ordinary 403 authz denial does NOT sign the user out.\n if (res.error && isSessionKilled(res.error.status, res.error.code)) {\n rt.auth.reactiveTeardown();\n }\n\n // Response resolved (2xx or an error envelope): record the real HTTP status\n // BEFORE `unwrap` may throw, so a 4xx/5xx is measured with its true status.\n record(res.error?.status ?? 200);\n return unwrap(res);\n}\n","import type { NamespaceTree } from './namespaces.js';\nimport type { PalbeRuntime } from './runtime.js';\n\nexport interface PalbeGlobalState {\n runtime: PalbeRuntime | null;\n registry: NamespaceTree;\n /** Memoized materialized namespaces (per top-level key); reset on (re-)register. */\n nsCache: Record<string, unknown>;\n /** Listeners fired synchronously after every successful __configure call. */\n configuredListeners: Set<() => void>;\n}\n\n/**\n * Module-level state would split between the ESM and CJS bundles of the two\n * entries (esbuild only code-splits ESM) and between dual-package copies in\n * one process. A globalThis slot keyed by Symbol.for converges them all.\n */\nconst STATE_KEY: unique symbol = Symbol.for('palbe.state.v1');\n\ninterface PalbeGlobals {\n [STATE_KEY]?: PalbeGlobalState;\n}\n\nexport function palbeState(): PalbeGlobalState {\n // Single direct widening view of globalThis — typed via the unique-symbol\n // key above so reads/writes of the slot stay fully typed.\n const g = globalThis as PalbeGlobals;\n let state = g[STATE_KEY];\n if (!state) {\n state = { runtime: null, registry: {}, nsCache: {}, configuredListeners: new Set() };\n g[STATE_KEY] = state;\n }\n return state;\n}\n","import { BackendError, isBackendError } from './errors.js';\nimport { getRuntime } from './internal.js';\nimport { type CallOptions, palbeRequest } from './request.js';\nimport type { PalbeRuntime } from './runtime.js';\nimport { palbeState } from './state.js';\n\nexport interface EndpointDescriptor {\n method: string;\n path: string;\n /** `{name}` segments consumed as leading call args, in order. */\n pathParams?: string[];\n /**\n * 'body' (default for non-GET) | 'query' (serialize input into the URL) |\n * 'none' (no input arg — options come straight after path params).\n */\n input?: 'body' | 'query' | 'none';\n /** wire error code → lift fn; the materializer rethrows the lifted error. */\n errors?: Record<string, (e: BackendError) => Error>;\n}\n\nexport type NamespaceTree = { [key: string]: NamespaceTree | EndpointDescriptor };\n\n// Reserved surface names on pb — cannot be shadowed at the top level by codegen\n// namespaces. `perf` sits next to `analytics`: `pb.perf` is the PalPerf facade,\n// so a generated controller named `perf` must NOT collide with it.\nexport const RESERVED = new Set([\n 'call',\n 'upload',\n 'auth',\n 'flags',\n 'realtime',\n 'analytics',\n 'calls',\n 'messaging',\n 'perf',\n]);\n\nfunction reservedNamespaceError(key: string): BackendError {\n return new BackendError('validation', {\n code: 'reserved_namespace',\n message: `Cannot register endpoint namespace '${key}': the name is reserved on pb.`,\n });\n}\n\nfunction isDescriptor(node: NamespaceTree | EndpointDescriptor): node is EndpointDescriptor {\n return (\n typeof (node as EndpointDescriptor).path === 'string' &&\n typeof (node as EndpointDescriptor).method === 'string'\n );\n}\n\n/**\n * Validate + normalize one tree level, recursively:\n * - plain-object guard (a literal `__proto__:` in a hand-written tree would\n * swap the node's prototype — cheap proto-pollution insurance);\n * - reject 'then' at EVERY level (a nested then makes that namespace object\n * thenable → `await pb.rooms` hangs) and every Object.prototype member\n * name (`'valueOf' in {}` is true — covers toString, __proto__, …);\n * - uppercase descriptor methods (request.ts matches mutating methods\n * against an uppercase set; a codegen 'get' would otherwise send a body\n * and an Idempotency-Key).\n */\nfunction validateDescriptorExtensions(d: EndpointDescriptor, path: string): void {\n if (d.pathParams !== undefined) {\n if (!Array.isArray(d.pathParams) || !d.pathParams.every((p) => typeof p === 'string')) {\n throw new BackendError('validation', {\n code: 'invalid_namespace_tree',\n message: `Descriptor '${path}': pathParams must be an array of strings.`,\n });\n }\n // Cross-check: a pathParams entry without a matching placeholder would\n // silently consume a call arg and substitute nothing — fail at registration.\n for (const p of d.pathParams) {\n if (!d.path.includes(`{${p}}`)) {\n throw new BackendError('validation', {\n code: 'invalid_namespace_tree',\n message: `Descriptor '${path}': pathParams entry '${p}' has no '{${p}}' placeholder in the path.`,\n });\n }\n }\n }\n if (d.input !== undefined && d.input !== 'body' && d.input !== 'query' && d.input !== 'none') {\n throw new BackendError('validation', {\n code: 'invalid_namespace_tree',\n message: `Descriptor '${path}': input must be 'body', 'query' or 'none'.`,\n });\n }\n if (d.errors !== undefined) {\n for (const [code, fn] of Object.entries(d.errors)) {\n if (typeof fn !== 'function') {\n throw new BackendError('validation', {\n code: 'invalid_namespace_tree',\n message: `Descriptor '${path}': errors['${code}'] must be a function.`,\n });\n }\n }\n }\n}\n\nfunction validateTree(node: NamespaceTree): void {\n const proto = Object.getPrototypeOf(node) as unknown;\n if (proto !== Object.prototype && proto !== null) {\n throw new BackendError('validation', {\n code: 'invalid_namespace_tree',\n message: 'Namespace tree nodes must be plain objects.',\n });\n }\n for (const [key, child] of Object.entries(node)) {\n if (key === 'then' || key in {}) throw reservedNamespaceError(key);\n if (isDescriptor(child)) {\n child.method = child.method.toUpperCase();\n validateDescriptorExtensions(child, child.path);\n } else {\n validateTree(child);\n }\n }\n}\n\nexport function __registerNamespaces(tree: NamespaceTree): void {\n for (const key of Object.keys(tree)) {\n if (RESERVED.has(key)) throw reservedNamespaceError(key);\n }\n validateTree(tree);\n const state = palbeState();\n state.registry = tree;\n state.nsCache = {};\n}\n\nexport function resetNamespaces(): void {\n const state = palbeState();\n state.registry = {};\n state.nsCache = {};\n}\n\nexport function getRegistry(): NamespaceTree {\n return palbeState().registry;\n}\n\nfunction serializeQuery(input: unknown): string {\n if (input === undefined || input === null) return '';\n const entries = Object.entries(input as Record<string, unknown>)\n .filter(([, v]) => v !== undefined && v !== null)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));\n if (entries.length === 0) return '';\n const parts = entries.map(([k, v]) => {\n if (typeof v !== 'string' && typeof v !== 'number' && typeof v !== 'boolean') {\n // Objects/arrays would stringify to '[object Object]' on the wire — fail loud.\n throw new BackendError('validation', {\n code: 'invalid_query_value',\n message: `Query parameter '${k}' must be a string, number, or boolean.`,\n });\n }\n return `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`;\n });\n return `?${parts.join('&')}`;\n}\n\nasync function invokeDescriptor(\n rt: PalbeRuntime,\n d: EndpointDescriptor,\n args: unknown[],\n): Promise<unknown> {\n const params = d.pathParams ?? [];\n let path = d.path;\n for (let i = 0; i < params.length; i++) {\n const v = args[i];\n if (typeof v !== 'string' && typeof v !== 'number') {\n throw new BackendError('validation', {\n code: 'missing_path_param',\n message: `${d.path}: path parameter '${params[i]}' must be a string or number`,\n });\n }\n path = path.replace(`{${params[i]}}`, encodeURIComponent(String(v)));\n }\n // input:'none' ops have NO input slot — options come straight after the\n // path params (a generated `get(id, options?)` would otherwise drop its\n // options into the input slot: GET throws, POST sends them as the body).\n if (d.input === 'none' && args.length > params.length + 1) {\n throw new BackendError('validation', {\n code: 'unexpected_argument',\n message: `${d.path}: endpoint takes no input — pass only path params and options`,\n });\n }\n const input = d.input === 'none' ? undefined : args[params.length];\n const optionsSlot = d.input === 'none' ? params.length : params.length + 1;\n const options = args[optionsSlot] as CallOptions | undefined;\n let body: unknown;\n if (d.input === 'query') {\n path += serializeQuery(input);\n } else if (d.input !== 'none') {\n if (d.method === 'GET') {\n if (input !== undefined) {\n throw new BackendError('validation', {\n code: 'unsupported_get_input',\n message: `${d.path}: GET endpoints take no body input`,\n });\n }\n } else {\n body = input;\n }\n }\n try {\n return await palbeRequest(rt, d.method, path, {\n body,\n headers: options?.headers,\n signal: options?.signal,\n });\n } catch (e) {\n if (d.errors && isBackendError(e)) {\n const lift = d.errors[e.code];\n if (lift) throw lift(e);\n }\n throw e;\n }\n}\n\n/**\n * The runtime is resolved AT CALL TIME via `resolveRt` (late binding): the\n * global path passes `getRuntime` so a `__configure` swap is picked up by\n * already-materialized namespaces; bound clients pass `() => rt` so the\n * captured runtime never changes.\n */\nfunction materialize(\n node: NamespaceTree | EndpointDescriptor,\n resolveRt: () => PalbeRuntime,\n): unknown {\n if (isDescriptor(node)) {\n // async wrapper: resolveRt() may throw (notConfigured) — endpoint methods\n // must ALWAYS reject, never throw synchronously (callers use .catch()).\n return async (...args: unknown[]) => invokeDescriptor(resolveRt(), node, args);\n }\n const out: Record<string, unknown> = {};\n for (const [key, child] of Object.entries(node)) {\n out[key] = materialize(child, resolveRt);\n }\n return out;\n}\n\n/** Global path: resolves the global runtime slot, memoizes in the global nsCache. */\nexport function getNamespace(name: string): unknown {\n const state = palbeState();\n const cached = state.nsCache[name];\n if (cached !== undefined) return cached;\n const node = state.registry[name];\n if (node === undefined) return undefined;\n const ns = materialize(node, getRuntime);\n state.nsCache[name] = ns;\n return ns;\n}\n\n/**\n * Bound path: namespaces materialize against ONE fixed runtime, memoized in a\n * per-client closure cache (the global nsCache stays global-only — a bound\n * entry there would leak one request's runtime into every other client).\n * The cache is invalidated when the registry object is swapped\n * (__registerNamespaces replaces it wholesale, e.g. watch-mode regen).\n */\nexport function boundNamespaceAccessor(rt: PalbeRuntime): (name: string) => unknown {\n let cacheRegistry = palbeState().registry;\n let cache: Record<string, unknown> = {};\n return (name: string): unknown => {\n const registry = palbeState().registry;\n if (registry !== cacheRegistry) {\n cacheRegistry = registry;\n cache = {};\n }\n const cached = cache[name];\n if (cached !== undefined) return cached;\n const node = registry[name];\n if (node === undefined) return undefined;\n const ns = materialize(node, () => rt);\n cache[name] = ns;\n return ns;\n };\n}\n","import type { HttpClient, PalbaseResponse, Session, TokenManager } from '@palbase/core';\nimport { PalbaseError } from '@palbase/core';\nimport { DeviceClient } from './device-client.js';\nimport type {\n AuthEvent,\n AuthResult,\n AuthSession,\n AuthStateChangeCallback,\n CredentialExchangeParams,\n Identity,\n LinkIdentityParams,\n MagicLinkParams,\n MagicLinkVerifyParams,\n MFAChallengeParams,\n MFAEmailChallengeParams,\n MFAEmailVerifyParams,\n MFAEnrollParams,\n MFAEnrollResult,\n MFAFactor,\n MFARecoveryParams,\n OAuthOptions,\n PasswordChangeParams,\n PasswordResetConfirmParams,\n PasswordResetParams,\n PasswordSetParams,\n RegisterTrustedDeviceParams,\n SignInCredentials,\n SignUpCredentials,\n TokenResponse,\n TrustedDevice,\n User,\n VerifyEmailParams,\n} from './types.js';\n\n/** Reserved for future user-scope options. Admin surface is in @palbase/server. */\nexport type AuthClientOptions = Record<string, never>;\n\nconst PROVIDER_RE = /^[a-zA-Z0-9_-]+$/;\n\n/** Validates that a path parameter does not contain path traversal characters. */\nfunction validatePathParam(name: string, value: string): void {\n if (value.includes('/') || value.includes('..') || value.includes('%')) {\n throw new Error(`Invalid ${name}: must not contain path separators`);\n }\n}\n\n/** Convert AuthResult from the real API into the SDK Session shape. */\nfunction toSession(result: AuthResult): Session {\n return {\n accessToken: result.access_token,\n refreshToken: result.refresh_token,\n expiresAt: Date.now() + result.expires_in * 1000,\n };\n}\n\n/** Convert AuthResult UserInfo into the SDK User shape. */\nfunction toUser(result: AuthResult): User {\n return {\n id: result.user.id,\n email: result.user.email,\n emailVerified: result.user.email_verified,\n createdAt: result.user.created_at,\n updatedAt: result.user.created_at,\n };\n}\n\nexport class AuthClient {\n private readonly httpClient: HttpClient;\n private readonly tokenManager: TokenManager;\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private currentSession: Session | null = null;\n private hasSession = false;\n private _mfa: ReturnType<AuthClient['buildMfa']> | null = null;\n\n /** Device attestation */\n readonly device: DeviceClient;\n\n constructor(\n httpClient: HttpClient,\n tokenManager: TokenManager,\n apiKey?: string,\n baseUrl?: string,\n _options?: AuthClientOptions,\n ) {\n this.httpClient = httpClient;\n this.tokenManager = tokenManager;\n this.apiKey = apiKey ?? '';\n this.baseUrl = baseUrl ?? '';\n this.device = new DeviceClient(httpClient);\n\n this.tokenManager.onAuthStateChange((event, session) => {\n if (event === 'SESSION_SET') {\n this.currentSession = session;\n } else {\n this.currentSession = null;\n }\n });\n }\n\n // ── Core Auth ───────────────────────────────────────────\n\n async signUp(\n credentials: SignUpCredentials,\n ): Promise<PalbaseResponse<{ user: User; session: Session }>> {\n const response = await this.httpClient.request<AuthResult>('POST', '/auth/signup', {\n body: credentials,\n });\n\n if (response.data) {\n const session = toSession(response.data);\n const user = toUser(response.data);\n this.setSessionAndWireRefresh(session);\n return { data: { user, session }, error: null, status: response.status };\n }\n\n return { data: null, error: response.error, status: response.status };\n }\n\n async signIn(\n credentials: SignInCredentials,\n ): Promise<PalbaseResponse<{ user: User; session: Session }>> {\n const response = await this.httpClient.request<AuthResult>('POST', '/auth/login', {\n body: credentials,\n });\n\n if (response.data) {\n const session = toSession(response.data);\n const user = toUser(response.data);\n this.setSessionAndWireRefresh(session);\n return { data: { user, session }, error: null, status: response.status };\n }\n\n return { data: null, error: response.error, status: response.status };\n }\n\n async signOut(): Promise<PalbaseResponse<void>> {\n const response = await this.httpClient.request<void>('POST', '/auth/logout');\n this.tokenManager.clearSession();\n this.hasSession = false;\n return response;\n }\n\n // ── Email Verification ──────────────────────────────────\n\n async verifyEmail(params: VerifyEmailParams): Promise<PalbaseResponse<{ status: string }>> {\n return this.httpClient.request<{ status: string }>('POST', '/auth/verify-email', {\n body: params,\n });\n }\n\n async resendVerification(\n email: string,\n ): Promise<PalbaseResponse<{ verification_token?: string; verification_code?: string }>> {\n return this.httpClient.request<{ verification_token?: string; verification_code?: string }>(\n 'POST',\n '/auth/resend-verification',\n { body: { email } },\n );\n }\n\n // ── Password ────────────────────────────────────────────\n\n async requestPasswordReset(\n params: PasswordResetParams,\n ): Promise<PalbaseResponse<{ success: boolean }>> {\n return this.httpClient.request<{ success: boolean }>('POST', '/auth/password/reset', {\n body: params,\n });\n }\n\n async confirmPasswordReset(\n params: PasswordResetConfirmParams,\n ): Promise<PalbaseResponse<{ success: boolean }>> {\n return this.httpClient.request<{ success: boolean }>('POST', '/auth/password/reset/confirm', {\n body: params,\n });\n }\n\n async changePassword(\n params: PasswordChangeParams,\n ): Promise<PalbaseResponse<{ success: boolean }>> {\n return this.httpClient.request<{ success: boolean }>('POST', '/auth/password/change', {\n body: params,\n });\n }\n\n /**\n * Give a PASSWORDLESS account its first password.\n *\n * Separate from `changePassword` because that one cannot serve the case: it\n * sends a current password and palauth answers an account that has none with\n * 401. Requires a RECENT sign-in (403 `reauthentication_required` otherwise,\n * the same gate passkey enrollment carries); an account that already has a\n * password gets 409 `password_already_set`.\n */\n async setPassword(params: PasswordSetParams): Promise<PalbaseResponse<{ success: boolean }>> {\n return this.httpClient.request<{ success: boolean }>('POST', '/auth/password/set', {\n body: params,\n });\n }\n\n // ── Token ───────────────────────────────────────────────\n\n async refresh(): Promise<PalbaseResponse<TokenResponse>> {\n const refreshToken = this.tokenManager.getRefreshToken();\n if (!refreshToken) {\n return {\n data: null,\n error: new PalbaseError('no_refresh_token', 'No refresh token available', 0),\n status: 0,\n };\n }\n\n const response = await this.httpClient.request<TokenResponse>('POST', '/auth/token/refresh', {\n body: { refresh_token: refreshToken },\n });\n\n if (response.data) {\n const session: Session = {\n accessToken: response.data.access_token,\n refreshToken: response.data.refresh_token,\n expiresAt: Date.now() + response.data.expires_in * 1000,\n };\n this.tokenManager.setSession(session);\n }\n\n return response;\n }\n\n getAccessToken(): string | null {\n return this.tokenManager.getAccessToken();\n }\n\n onTokenChange(\n callback: (tokens: { accessToken: string | null; refreshToken: string | null }) => void,\n ): () => void {\n return this.tokenManager.onAuthStateChange((event, session) => {\n if (event === 'SESSION_SET' && session) {\n callback({ accessToken: session.accessToken, refreshToken: session.refreshToken });\n } else {\n callback({ accessToken: null, refreshToken: null });\n }\n });\n }\n\n setTokens(accessToken: string, refreshToken: string, expiresIn = 3600): void {\n const session: Session = {\n accessToken,\n refreshToken,\n // Default 1h when the caller has no TTL; corrected on next refresh anyway.\n expiresAt: Date.now() + expiresIn * 1000,\n };\n this.setSessionAndWireRefresh(session);\n }\n\n // ── Sessions ────────────────────────────────────────────\n\n async listSessions(): Promise<PalbaseResponse<AuthSession[]>> {\n const response = await this.httpClient.request<{ sessions: AuthSession[] }>(\n 'GET',\n '/auth/sessions',\n );\n\n if (response.data) {\n return { data: response.data.sessions, error: null, status: response.status };\n }\n\n return { data: null, error: response.error, status: response.status };\n }\n\n async revokeSession(sessionId: string): Promise<PalbaseResponse<void>> {\n validatePathParam('sessionId', sessionId);\n return this.httpClient.request<void>('DELETE', `/auth/sessions/${sessionId}`);\n }\n\n async revokeAllSessions(): Promise<PalbaseResponse<void>> {\n return this.httpClient.request<void>('DELETE', '/auth/sessions');\n }\n\n // ── MFA ─────────────────────────────────────────────────\n\n get mfa() {\n this._mfa ??= this.buildMfa();\n return this._mfa;\n }\n\n private buildMfa() {\n return {\n enroll: (params: MFAEnrollParams): Promise<PalbaseResponse<MFAEnrollResult>> =>\n this.httpClient.request('POST', '/auth/mfa/enroll', { body: params }),\n\n verifyEnrollment: (code: string): Promise<PalbaseResponse<{ status: string }>> =>\n this.httpClient.request('POST', '/auth/mfa/verify', { body: { code } }),\n\n challenge: (params: MFAChallengeParams): Promise<PalbaseResponse<TokenResponse>> =>\n this.httpClient.request('POST', '/auth/mfa/challenge', { body: params }),\n\n recovery: (params: MFARecoveryParams): Promise<PalbaseResponse<TokenResponse>> =>\n this.httpClient.request('POST', '/auth/mfa/recovery', { body: params }),\n\n listFactors: (): Promise<PalbaseResponse<{ factors: MFAFactor[] }>> =>\n this.httpClient.request('GET', '/auth/mfa/factors'),\n\n removeFactor: (\n factorId: string,\n currentPassword: string,\n ): Promise<PalbaseResponse<{ status: string }>> => {\n validatePathParam('factorId', factorId);\n return this.httpClient.request('DELETE', `/auth/mfa/factors/${factorId}`, {\n body: { current_password: currentPassword },\n });\n },\n\n regenerateRecoveryCodes: (): Promise<PalbaseResponse<{ recovery_codes: string[] }>> =>\n this.httpClient.request('POST', '/auth/mfa/recovery-codes/regenerate'),\n\n emailEnroll: (): Promise<PalbaseResponse<{ status: string }>> =>\n this.httpClient.request('POST', '/auth/mfa/email/enroll'),\n\n emailChallenge: (\n params: MFAEmailChallengeParams,\n ): Promise<PalbaseResponse<{ status: string }>> =>\n this.httpClient.request('POST', '/auth/mfa/email/challenge', { body: params }),\n\n emailVerify: (params: MFAEmailVerifyParams): Promise<PalbaseResponse<TokenResponse>> =>\n this.httpClient.request('POST', '/auth/mfa/email/verify', { body: params }),\n };\n }\n\n // ── OAuth / Social ──────────────────────────────────────\n\n async getOAuthURL(options: OAuthOptions): Promise<PalbaseResponse<{ url: string }>> {\n if (!PROVIDER_RE.test(options.provider)) {\n return {\n data: null,\n error: new PalbaseError('invalid_provider', 'Invalid OAuth provider name', 0),\n status: 0,\n };\n }\n\n const params = new URLSearchParams();\n if (options.redirectTo) {\n params.set('redirect_uri', options.redirectTo);\n }\n const query = params.toString();\n const path = `/auth/oauth/${options.provider}/authorize${query ? `?${query}` : ''}`;\n\n return this.httpClient.request<{ url: string }>('GET', path);\n }\n\n async signInWithCredential(\n params: CredentialExchangeParams,\n ): Promise<PalbaseResponse<{ user: User; session: Session }>> {\n const response = await this.httpClient.request<AuthResult>('POST', '/auth/oauth/credential', {\n body: params,\n });\n\n if (response.data?.access_token) {\n const session = toSession(response.data);\n const user = toUser(response.data);\n this.setSessionAndWireRefresh(session);\n return { data: { user, session }, error: null, status: response.status };\n }\n\n return { data: null, error: response.error, status: response.status };\n }\n\n // ── Identities ──────────────────────────────────────────\n\n async listIdentities(): Promise<PalbaseResponse<{ identities: Identity[] }>> {\n return this.httpClient.request<{ identities: Identity[] }>('GET', '/auth/identities');\n }\n\n async linkIdentity(params: LinkIdentityParams): Promise<PalbaseResponse<{ success: boolean }>> {\n return this.httpClient.request<{ success: boolean }>('POST', '/auth/identities/link', {\n body: params,\n });\n }\n\n async unlinkIdentity(identityId: string): Promise<PalbaseResponse<{ success: boolean }>> {\n validatePathParam('identityId', identityId);\n return this.httpClient.request<{ success: boolean }>(\n 'DELETE',\n `/auth/identities/${identityId}`,\n );\n }\n\n // ── Magic Link ──────────────────────────────────────────\n\n async requestMagicLink(params: MagicLinkParams): Promise<PalbaseResponse<{ success: boolean }>> {\n return this.httpClient.request<{ success: boolean }>('POST', '/auth/magic-link', {\n body: params,\n });\n }\n\n async verifyMagicLink(\n params: MagicLinkVerifyParams,\n ): Promise<PalbaseResponse<{ user: User; session: Session }>> {\n const response = await this.httpClient.request<AuthResult>('POST', '/auth/magic-link/verify', {\n body: params,\n });\n\n if (response.data?.access_token) {\n const session = toSession(response.data);\n const user = toUser(response.data);\n this.setSessionAndWireRefresh(session);\n return { data: { user, session }, error: null, status: response.status };\n }\n\n return { data: null, error: response.error, status: response.status };\n }\n\n // ── Trusted Devices ─────────────────────────────────────\n\n async listTrustedDevices(): Promise<PalbaseResponse<{ trusted_devices: TrustedDevice[] }>> {\n return this.httpClient.request<{ trusted_devices: TrustedDevice[] }>(\n 'GET',\n '/auth/trusted-devices',\n );\n }\n\n async registerTrustedDevice(\n params: RegisterTrustedDeviceParams,\n ): Promise<PalbaseResponse<{ trusted_device_token: string }>> {\n return this.httpClient.request<{ trusted_device_token: string }>(\n 'POST',\n '/auth/trusted-devices',\n { body: params },\n );\n }\n\n async revokeTrustedDevice(deviceId: string): Promise<PalbaseResponse<{ success: boolean }>> {\n validatePathParam('deviceId', deviceId);\n return this.httpClient.request<{ success: boolean }>(\n 'DELETE',\n `/auth/trusted-devices/${deviceId}`,\n );\n }\n\n // ── Session State ───────────────────────────────────────\n\n getSession(): { data: Session | null; error: null } {\n return { data: this.currentSession, error: null };\n }\n\n onAuthStateChange(callback: AuthStateChangeCallback): {\n data: { subscription: { unsubscribe: () => void } };\n } {\n const unsubscribe = this.tokenManager.onAuthStateChange((event, session) => {\n let authEvent: AuthEvent;\n\n if (event === 'SESSION_SET') {\n authEvent = this.hasSession ? 'TOKEN_REFRESHED' : 'SIGNED_IN';\n this.hasSession = true;\n } else {\n authEvent = 'SIGNED_OUT';\n this.hasSession = false;\n }\n\n callback(authEvent, session);\n });\n\n return { data: { subscription: { unsubscribe } } };\n }\n\n // ── Server-only ─────────────────────────────────────────\n\n /**\n * Verify a user's JWT token by calling GET /auth/user with their token.\n * Server-only — should NOT be exposed in client SDK.\n */\n async verifyUserToken(jwt: string): Promise<PalbaseResponse<User>> {\n const url = `${this.baseUrl}/auth/user`;\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: 'GET',\n headers: {\n apikey: this.apiKey,\n Authorization: `Bearer ${jwt}`,\n 'Content-Type': 'application/json',\n },\n });\n } catch (error) {\n throw new PalbaseError(\n 'network_error',\n error instanceof Error ? error.message : 'Network request failed',\n 0,\n );\n }\n\n const contentType = response.headers.get('Content-Type');\n if (response.ok && contentType?.includes('application/json')) {\n const data = (await response.json()) as User;\n return { data, error: null, status: response.status };\n }\n\n let errorBody: { error?: string; error_description?: string } | undefined;\n if (contentType?.includes('application/json')) {\n errorBody = (await response.json()) as typeof errorBody;\n }\n\n return {\n data: null,\n error: new PalbaseError(\n errorBody?.error ?? 'invalid_token',\n errorBody?.error_description ?? 'Token verification failed',\n response.status,\n errorBody,\n ),\n status: response.status,\n };\n }\n\n // ── Private ─────────────────────────────────────────────\n\n private setSessionAndWireRefresh(session: Session): void {\n this.tokenManager.setSession(session);\n this.hasSession = true;\n\n this.tokenManager.refreshFunction = async (refreshToken: string): Promise<Session> => {\n const response = await this.httpClient.request<TokenResponse>('POST', '/auth/token/refresh', {\n body: { refresh_token: refreshToken },\n });\n\n if (response.error || !response.data) {\n throw response.error ?? new Error('Failed to refresh token');\n }\n\n return {\n accessToken: response.data.access_token,\n refreshToken: response.data.refresh_token,\n expiresAt: Date.now() + response.data.expires_in * 1000,\n };\n };\n }\n}\n","import type { HttpClient, PalbaseResponse } from '@palbase/core';\nimport type {\n AttestAndroidParams,\n AttestAndroidResult,\n AttestiOSParams,\n AttestiOSResult,\n BindDeviceParams,\n DeviceInfo,\n VerifyRequestSignatureParams,\n} from './types.js';\n\n/** Validates that a path parameter does not contain path traversal characters. */\nfunction validatePathParam(name: string, value: string): void {\n if (value.includes('/') || value.includes('..') || value.includes('%')) {\n throw new Error(`Invalid ${name}: must not contain path separators`);\n }\n}\n\nconst TOKEN_BUFFER_MS = 5 * 60 * 1000; // 5 min before expiry\n\n/**\n * Device attestation client.\n * Handles device challenge/attestation/bind and App Check JWT cache.\n */\nexport class DeviceClient {\n private readonly httpClient: HttpClient;\n private cachedToken: { token: string; expiresAt: number } | null = null;\n private refreshTimer: ReturnType<typeof setTimeout> | null = null;\n\n constructor(httpClient: HttpClient) {\n this.httpClient = httpClient;\n }\n\n /** Generate a device attestation challenge. */\n async generateChallenge(): Promise<PalbaseResponse<{ challenge: string }>> {\n return this.httpClient.request<{ challenge: string }>('POST', '/auth/devices/challenge');\n }\n\n /** Attest an Android device with a Play Integrity verdict token. */\n async attestAndroid(params: AttestAndroidParams): Promise<PalbaseResponse<AttestAndroidResult>> {\n return this.httpClient.request<AttestAndroidResult>('POST', '/auth/devices/attest/android', {\n body: params,\n });\n }\n\n /** Attest an iOS device with App Attest attestation data. */\n async attestiOS(params: AttestiOSParams): Promise<PalbaseResponse<AttestiOSResult>> {\n return this.httpClient.request<AttestiOSResult>('POST', '/auth/devices/attest/ios', {\n body: params,\n });\n }\n\n /** Bind a verified device with a public key for request signing. */\n async bind(params: BindDeviceParams): Promise<PalbaseResponse<{ success: boolean }>> {\n return this.httpClient.request<{ success: boolean }>('POST', '/auth/devices/bind', {\n body: params,\n });\n }\n\n /** List all devices for the current user. */\n async list(): Promise<PalbaseResponse<{ devices: DeviceInfo[] }>> {\n return this.httpClient.request<{ devices: DeviceInfo[] }>('GET', '/auth/devices');\n }\n\n /** Delete a device by ID. */\n async delete(deviceId: string): Promise<PalbaseResponse<{ success: boolean }>> {\n validatePathParam('deviceId', deviceId);\n return this.httpClient.request<{ success: boolean }>('DELETE', `/auth/devices/${deviceId}`);\n }\n\n /**\n * Verify a request signature from a device (server-only).\n * Should NOT be exposed in client SDK.\n */\n async verifyRequestSignature(\n deviceId: string,\n params: VerifyRequestSignatureParams,\n ): Promise<PalbaseResponse<{ verified: boolean }>> {\n validatePathParam('deviceId', deviceId);\n return this.httpClient.request<{ verified: boolean }>(\n 'POST',\n `/auth/devices/${deviceId}/verify`,\n { body: params },\n );\n }\n\n /** Get the cached App Check token, or null if not available / expired. */\n getToken(): string | null {\n if (!this.cachedToken) return null;\n if (Date.now() >= this.cachedToken.expiresAt) return null;\n return this.cachedToken.token;\n }\n\n /** Whether App Check is active (token cached and not expired). */\n get isActive(): boolean {\n return this.getToken() !== null;\n }\n\n /** Set a cached App Check token manually (e.g. after attest flow). */\n setCachedToken(token: string, expiresInMs: number): void {\n this.cachedToken = {\n token,\n expiresAt: Date.now() + expiresInMs,\n };\n this.scheduleRefresh(expiresInMs);\n }\n\n /** Clean up timers and cached state. */\n dispose(): void {\n if (this.refreshTimer) {\n clearTimeout(this.refreshTimer);\n this.refreshTimer = null;\n }\n this.cachedToken = null;\n }\n\n private scheduleRefresh(ttlMs: number): void {\n if (this.refreshTimer) {\n clearTimeout(this.refreshTimer);\n }\n const refreshIn = Math.max(ttlMs - TOKEN_BUFFER_MS, 0);\n this.refreshTimer = setTimeout(() => {\n this.cachedToken = null;\n }, refreshIn);\n }\n}\n","// `pb.analytics` — the stateful analytics surface. Mirrors the iOS\n// `AnalyticsClient`/`AnalyticsStorage` pair (palbackend-ios-src\n// Sources/Palbe/Analytics/): anonymous distinct-id, in-memory buffer with\n// size/timer flushing, identify/alias/reset/optOut, and auth-lifecycle\n// bindings. Transport is direct `palbeRequest` to palanalytics' live\n// ingestion wire (verified against modules/analytics Go handlers):\n//\n// POST /v1/analytics/batch {events:[{event,distinct_id,properties?,timestamp,sent_at}]}\n// POST /v1/analytics/capture single event (server mode — no buffering)\n// POST /v1/analytics/identify {distinct_id,anonymous_id,traits?,timestamp,sent_at}\n// POST /v1/analytics/alias {from,to,timestamp,sent_at}\n//\n// Ingestion is fire-and-forget (iOS K11): nothing here ever throws into the\n// caller — failures are console.warn'ed and the events dropped. The only\n// awaited surface is `flush()`, which still never rejects.\n//\n// Wire decisions (documented deviations):\n// - screen() emits the SERVER-canonical mapping (event `$screen` +\n// `properties.screen_name` — exactly what Go's /v1/analytics/screen\n// shorthand stores), NOT iOS's event-named-after-the-screen variant, so\n// batched screens and shorthand screens collapse to one stored shape.\n// - `sent_at` is stamped at SEND time (flush), not enqueue time like iOS:\n// the server's skew correction (`timestamp + (received_at - sent_at)`)\n// assumes send time; the iOS enqueue-time stamp inflates corrected\n// timestamps by the buffering delay.\n// - optOut gates ALL ingest calls (capture/screen/identify/alias) — iOS\n// parity. reset() and the X-Distinct-Id header are NOT gated (also iOS\n// parity: the header resolver never checks opt-out).\n\nimport type { AuthChangeEvent } from './auth-facade.js';\nimport { palbeRequest } from './request.js';\nimport type { PalbeRuntime } from './runtime.js';\n\n/** Free-form event property bag (JSON object on the wire). */\nexport type AnalyticsProperties = Record<string, unknown>;\n\n/** Server event-name rule — matches modules/analytics/internal/validator/event.go\n * eventNameRegex exactly: leading letter or `$`, max 64 chars total,\n * MaxEventNameLen=64 (i.e. body up to 63 chars after the leading char). */\nconst EVENT_NAME_RE = /^[a-zA-Z$][a-zA-Z0-9_.:$-]{0,63}$/;\n\nconst FLUSH_AT = 20;\nconst FLUSH_INTERVAL_MS = 10_000;\n/** Server hard cap: /v1/analytics/batch rejects > 100 events per request. */\nconst MAX_BATCH_SIZE = 100;\n\n/** A buffered capture event — distinct_id is stamped at CAPTURE time so the\n * anon id is preserved on the wire for pre-login events (the gateway's Bearer\n * override applies to the REQUEST's X-Distinct-Id header, not the event body).\n * `sent_at` is added at flush time. */\ninterface CaptureWire {\n event: string;\n distinct_id: string;\n properties?: AnalyticsProperties;\n timestamp: number;\n}\n\nlet warnedInvalidEventName = false;\n\nfunction warnInvalidEventName(name: string): void {\n if (warnedInvalidEventName) return;\n warnedInvalidEventName = true;\n console.warn(\n `palbe analytics: dropping event \"${name}\" — names must match ^[a-zA-Z$][a-zA-Z0-9_.:$-]{0,63}$ (warned once per process)`,\n );\n}\n\n/** Minimal persisted string store: localStorage in browsers, per-runtime\n * memory elsewhere (SSR anon ids are ephemeral by design). Browser detection\n * matches the facade (`document`, not localStorage presence) — server\n * runtimes must NEVER touch storage, even when a localStorage global exists\n * (the flags server-env contract pins that). */\ninterface StringStore {\n get(key: string): string | null;\n set(key: string, value: string): void;\n remove(key: string): void;\n}\n\nfunction defaultStringStore(): StringStore {\n if (typeof document !== 'undefined' && typeof localStorage !== 'undefined') {\n return {\n get: (key) => {\n try {\n return localStorage.getItem(key);\n } catch {\n return null;\n }\n },\n set: (key, value) => {\n try {\n localStorage.setItem(key, value);\n } catch {\n // quota/private mode — the id just won't survive a reload\n }\n },\n remove: (key) => {\n try {\n localStorage.removeItem(key);\n } catch {\n // ignore\n }\n },\n };\n }\n const mem = new Map<string, string>();\n return {\n get: (key) => mem.get(key) ?? null,\n set: (key, value) => {\n mem.set(key, value);\n },\n remove: (key) => {\n mem.delete(key);\n },\n };\n}\n\n/**\n * Runtime-scoped analytics identity — constructed EAGERLY in `buildRuntime`\n * (cheap: listener wiring only, no I/O) because the X-Distinct-Id header\n * interceptor must stamp every request from the first one, even when\n * `pb.analytics` is never touched (iOS: the resolver is wired at\n * PalBackend init). The facade wraps this state lazily.\n *\n * Auth events: while no facade exists, the state maintains identity only\n * (user id for the header, anon rotation on user-initiated sign-out). Once\n * the facade attaches, it takes over wholesale and adds the ingest\n * side-effects (flush + identify / flush + reset).\n */\nexport class AnalyticsState {\n identifiedUserId: string | null = null;\n /** Set by PalbeAnalytics on construction — delegates auth events to it. */\n facadeAuthHandler: ((event: AuthChangeEvent) => void) | null = null;\n\n private anonId: string | null = null;\n private readonly store = defaultStringStore();\n private readonly idKey: string;\n private readonly optOutKey: string;\n\n constructor(rt: PalbeRuntime) {\n const ref = rt.projectRef;\n this.idKey = ref ? `palbe.analytics.id.${ref}` : 'palbe.analytics.id';\n this.optOutKey = ref ? `palbe.analytics.optout.${ref}` : 'palbe.analytics.optout';\n rt.auth.onAuthEvent((event) => {\n if (this.facadeAuthHandler) {\n this.facadeAuthHandler(event);\n return;\n }\n if (event.type === 'signedIn') {\n // Identity for the header only — no identify POST without a facade\n // (an app that never uses pb.analytics generates no analytics\n // traffic). Opt-out gates the link, mirroring the gated identify.\n if (!this.isOptedOut()) this.identifiedUserId = event.user.id;\n } else if (event.type === 'signedOut' && event.reason === 'userInitiated') {\n this.identifiedUserId = null;\n this.rotateAnonymousId();\n }\n // sessionExpired / tokenRefreshed: identity preserved (iOS K9)\n });\n }\n\n /** User id when identified, else the stable anon id. NEVER empty —\n * this is what `identify()` links, so stamping it on every request lets\n * the trace pipeline stitch pre-login activity to the user. */\n distinctId(): string {\n return this.identifiedUserId ?? this.anonymousId();\n }\n\n anonymousId(): string {\n if (this.anonId) return this.anonId;\n const stored = this.store.get(this.idKey);\n if (stored) {\n this.anonId = stored;\n return stored;\n }\n return this.rotateAnonymousId();\n }\n\n rotateAnonymousId(): string {\n const fresh = crypto.randomUUID();\n this.store.set(this.idKey, fresh);\n this.anonId = fresh;\n return fresh;\n }\n\n isOptedOut(): boolean {\n return this.store.get(this.optOutKey) === 'true';\n }\n\n setOptOut(on: boolean): void {\n if (on) this.store.set(this.optOutKey, 'true');\n else this.store.remove(this.optOutKey);\n }\n}\n\nexport class PalbeAnalytics {\n private readonly buffer: CaptureWire[] = [];\n private flushTimer: ReturnType<typeof setTimeout> | null = null;\n /** Browser → buffer + size/timer flush. Server (no document) → immediate\n * per-call POST, zero timers (nothing leaks into RSC/route handlers). */\n private readonly browser = typeof document !== 'undefined';\n\n constructor(\n private readonly rt: PalbeRuntime,\n private readonly state: AnalyticsState,\n ) {\n this.state.facadeAuthHandler = (event) => {\n this.handleAuthEvent(event);\n };\n }\n\n /** Record an event. Invalid names are dropped (one warn per process). */\n capture(event: string, properties?: AnalyticsProperties): void {\n if (this.state.isOptedOut()) return;\n if (!EVENT_NAME_RE.test(event)) {\n warnInvalidEventName(event);\n return;\n }\n this.ingest(event, properties);\n }\n\n /** Record a screen view — server-canonical `$screen` + `screen_name`.\n * Validates non-empty only (the live wire at /v1/analytics/screen requires\n * non-empty screen_name; the event-name regex does NOT apply here). */\n screen(name: string, properties?: AnalyticsProperties): void {\n if (this.state.isOptedOut()) return;\n if (name.trim() === '') {\n console.warn('palbe analytics: dropping screen() — name must be non-empty');\n return;\n }\n this.ingest('$screen', { ...properties, screen_name: name });\n }\n\n /** Link the current anon id to `userId` and adopt it as the distinct id.\n * Immediate POST (not buffered). Re-identifying a DIFFERENT user\n * auto-resets first (iOS K10) so one anon id never links two users. */\n identify(userId: string, traits?: AnalyticsProperties): void {\n if (this.state.isOptedOut()) return;\n const existing = this.state.identifiedUserId;\n if (existing && existing !== userId) {\n console.warn(\n `palbe analytics: re-identify changes the user from ${existing} to ${userId} — auto-resetting (call reset() first if this is intentional)`,\n );\n this.reset();\n }\n const ts = Date.now();\n const body: Record<string, unknown> = {\n distinct_id: userId,\n anonymous_id: this.state.anonymousId(),\n timestamp: ts,\n sent_at: ts,\n };\n if (traits) body.traits = traits;\n this.state.identifiedUserId = userId;\n void this.post('/v1/analytics/identify', body);\n }\n\n /** Merge distinct id `from` into `to` (identity stitching). Immediate POST. */\n alias(from: string, to: string): void {\n if (this.state.isOptedOut()) return;\n const ts = Date.now();\n void this.post('/v1/analytics/alias', { from, to, timestamp: ts, sent_at: ts });\n }\n\n /** Drop the buffer, clear the identified user and rotate the anon id\n * (sign-out hygiene). Callers wanting pending events delivered flush first\n * — the auth binding does. */\n reset(): void {\n this.buffer.length = 0;\n this.cancelTimer();\n this.state.identifiedUserId = null;\n this.state.rotateAnonymousId();\n }\n\n /** Persisted GDPR opt-out. Opting out drops anything pending so nothing\n * already-buffered leaks after the user said stop. */\n setOptOut(on: boolean): void {\n this.state.setOptOut(on);\n if (on) {\n this.buffer.length = 0;\n this.cancelTimer();\n }\n }\n\n /** Drain the buffer to /v1/analytics/batch (≤100 per request, sequential).\n * Resolves when delivery finished; NEVER rejects — failed slices are\n * dropped with one warn per flush. 207 partial-reject is also warned once\n * (fire-and-forget: no retry for already-delivered batches). */\n async flush(): Promise<void> {\n this.cancelTimer();\n if (this.buffer.length === 0) return;\n const sentAt = Date.now();\n let warned = false;\n while (this.buffer.length > 0) {\n const slice = this.buffer.splice(0, MAX_BATCH_SIZE);\n try {\n const result = await palbeRequest<{ accepted?: number; rejected?: unknown[] }>(\n this.rt,\n 'POST',\n '/v1/analytics/batch',\n { body: { events: slice.map((e) => ({ ...e, sent_at: sentAt })) } },\n );\n if (Array.isArray(result?.rejected) && result.rejected.length > 0) {\n console.warn(\n `palbe analytics: batch partial-reject — ${result.rejected.length} event(s) rejected by server`,\n );\n }\n } catch (err) {\n if (!warned) {\n warned = true;\n console.warn('palbe analytics: batch flush failed — events dropped', err);\n }\n }\n }\n }\n\n // ── internals ──────────────────────────────────────────\n\n private ingest(event: string, properties?: AnalyticsProperties): void {\n const wire: CaptureWire = {\n event,\n distinct_id: this.state.distinctId(), // stamped at capture time\n timestamp: Date.now(),\n };\n if (properties) wire.properties = properties;\n if (!this.browser) {\n void this.post('/v1/analytics/capture', { ...wire, sent_at: wire.timestamp });\n return;\n }\n this.buffer.push(wire);\n if (this.buffer.length >= FLUSH_AT) void this.flush();\n else this.startTimer();\n }\n\n /** iOS PalBackend auth-binding parity: signedIn → flush + identify;\n * userInitiated signedOut → flush + reset; sessionExpired → flush only. */\n private handleAuthEvent(event: AuthChangeEvent): void {\n if (event.type === 'signedIn') {\n void this.flush().then(() => {\n this.identify(event.user.id);\n });\n } else if (event.type === 'signedOut') {\n if (event.reason === 'userInitiated') {\n void this.flush().then(() => {\n this.reset();\n });\n } else {\n void this.flush();\n }\n }\n // tokenRefreshed: nothing\n }\n\n private startTimer(): void {\n if (this.flushTimer !== null) return;\n this.flushTimer = setTimeout(() => {\n this.flushTimer = null;\n void this.flush();\n }, FLUSH_INTERVAL_MS);\n }\n\n private cancelTimer(): void {\n if (this.flushTimer !== null) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n }\n\n private async post(path: string, body: unknown): Promise<void> {\n try {\n await palbeRequest(this.rt, 'POST', path, { body });\n } catch (err) {\n console.warn(`palbe analytics: POST ${path} failed`, err);\n }\n }\n}\n","/**\n * `pb_<environmentRef>_<scope><random>` → environmentRef ('' if malformed).\n *\n * THE KEY IS THE ONLY SOURCE. There is no `environmentRef` on `PalbeConfig` to\n * prefer over this parse: the field was a second copy of what the key already\n * carries, `palbase link` was measured writing one value while minting a key\n * carrying another, and it was removed everywhere (see `runtime.ts`'s note).\n * So app-bound runtime storage (`runtime.ts`) and palbe/next cookie naming\n * (client, server, proxy, callback) all reach the ref through the publishable\n * parser below, off `config.apiKey`.\n *\n * The key is shape-validated (prefix `pb`, a 4-24 character lowercase ASCII\n * alphanumeric environmentRef, and a `<scope><at-least-20-base62-random>` secret\n * segment where scope ∈ {c,s}) before a ref is returned.\n *\n * THE SECRET'S LENGTH IS NOT PART OF THE CONTRACT — only its floor is. Two\n * minters ship two lengths (the stack's own `initenv` writes 20 base62, the\n * cloud control plane writes 32), and the gate that actually admits a request\n * says so outright: *\"a shorter or longer secret is not a security property\n * this door can rule on, and refusing one would only turn off a stack whose key\n * works everywhere else\"* (v2/internal/platform/identitymw.go: parseAPIKey).\n * Pinning EXACTLY 20 here made this SDK refuse every key the cloud has ever\n * minted (measured 2026-08-25: `buildRuntime` threw and palbe-gen never ran).\n * The floor stays, because the short/loose body is what the finding below was\n * about; the ceiling was never anyone's rule. A structurally-invalid key returns\n * '' so session storage stays\n * UNSCOPED rather than getting bound to a partially-parsed ref — a loose parse\n * (e.g. `pb_victim_cXX`) must not scope persisted sessions to `victim` and\n * hydrate a session belonging to a different, real key. The backend remains the\n * authority that rejects a bad key on the first request; this keeps the client's\n * local session-storage scoping honest. (Audit finding apikey-ref-mismatch.)\n */\nconst API_KEY_RE = /^pb_([a-z0-9]{4,24})_[cs][A-Za-z0-9]{20,}$/;\nconst PUBLISHABLE_API_KEY_RE = /^pb_([a-z0-9]{4,24})_c[A-Za-z0-9]{20,}$/;\n\nexport function environmentRefFromApiKey(apiKey: string): string {\n const m = API_KEY_RE.exec(apiKey);\n return m ? (m[1] ?? '') : '';\n}\n\n/** Internal app-runtime parser: browser config accepts publishable keys only. */\nexport function environmentRefFromPublishableApiKey(apiKey: string): string {\n const match = PUBLISHABLE_API_KEY_RE.exec(apiKey);\n return match ? (match[1] ?? '') : '';\n}\n","/**\n * palauth AuthResult wire shape + its structural guard — shared by the auth\n * facade (sign-in / verify / code-exchange flows) and the palbe/next OAuth\n * callback handler, so both gate on the SAME shape rules.\n */\n\nexport interface WireUser {\n id: string;\n email?: string; // omitted for phone-only users\n email_verified: boolean;\n created_at: string;\n}\n\n/**\n * GET /auth/me. Distinct from WireUser (GET /auth/user): only this endpoint\n * reports which credentials the account actually has.\n *\n * `providers` is nullable ON PURPOSE and the difference carries meaning —\n * `[]` is \"asked, none linked\", `null` is \"could not determine\" (social login\n * unconfigured on the server, or the identity lookup failed). Collapsing null\n * to [] would report a social-only account as password-only, which is exactly\n * the wrong answer for the form this field exists to choose.\n */\nexport interface WireMe {\n has_password: boolean;\n has_mfa: boolean;\n providers: string[] | null;\n}\n\nexport interface WireAuthResult {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n user: WireUser;\n}\n\n/**\n * Structural guard for the palauth AuthResult wire shape. Consumers adopt\n * the session (persisting tokens to storage / cookies) BEFORE reading user\n * fields, so the full shape must be proven before adoption — a 200 body with\n * access_token but a missing/malformed user, refresh_token or expires_in\n * would otherwise half-adopt a zombie session (tokens stored, then TypeError\n * on user.id).\n */\nexport function asWireAuthResult(raw: unknown): WireAuthResult | null {\n if (typeof raw !== 'object' || raw === null) return null;\n const obj = raw as Record<string, unknown>;\n if (typeof obj.access_token !== 'string') return null;\n if (typeof obj.refresh_token !== 'string') return null;\n if (typeof obj.expires_in !== 'number') return null;\n const user = obj.user;\n if (typeof user !== 'object' || user === null) return null;\n if (typeof (user as Record<string, unknown>).id !== 'string') return null;\n return raw as WireAuthResult;\n}\n","// Passkeys (WebAuthn platform credentials) for the browser, surfaced through\n// `pb.auth.*` with the SAME names the iOS SDK uses — signInWithPasskey,\n// signUpWithPasskey, registerPasskey — so the two surfaces are learned once.\n//\n// A passkey is DISCOVERABLE, so signing in needs no e-mail, no password and no\n// code: the browser lists the accounts stored for this relying party and the\n// user picks one.\n//\n// The three palauth calls are the same ones iOS makes:\n// POST /auth/webauthn/login/begin {} → assertion options\n// POST /auth/webauthn/login/finish <WebAuthn JSON> → session\n// POST /auth/webauthn/signup/begin {email, user_name?} → creation options + user_id\n// POST /auth/webauthn/signup/finish?user_id=… <WebAuthn JSON>\n// POST /auth/webauthn/register/begin|finish → add to a signed-in account\n//\n// This module is the ONLY place that translates between palauth's WebAuthn JSON\n// (base64URL strings) and the ArrayBuffers `navigator.credentials` wants. There\n// is deliberately no dependency added for it: @simplewebauthn is Studio's\n// internal choice, and a customer bundle should not grow one for ~60 lines of\n// base64URL.\n//\n// CROSS-DOMAIN NOTE. The relying-party id is the Environment's own host. A page\n// served from the customer's OWN domain can still use those passkeys, but only\n// through WebAuthn Related Origin Requests — the browser fetches\n// https://<rpId>/.well-known/webauthn and checks this origin against it. ROR is\n// not universal: Firefox on Android has it OFF by default, and a client without\n// it throws SecurityError rather than degrading. So `isPasskeySupported()`\n// exists and every caller needs a non-passkey fallback.\n\nimport { asWireAuthResult, type WireAuthResult } from './auth-wire.js';\nimport { palbeRequest } from './request.js';\nimport type { PalbeRuntime } from './runtime.js';\n\n/** palauth wraps both ceremonies' options as `{options: {publicKey: {…}}}`. */\ninterface OptionsEnvelope {\n options: { publicKey: Record<string, unknown> };\n}\n\ninterface SignupBeginResponse extends OptionsEnvelope {\n user_id: string;\n}\n\n/**\n * Decode WebAuthn's base64URL (RFC 4648 §5, no padding). Plain atob rejects\n * these strings, which is how a passkey integration fails at the first\n * challenge with an error that names nothing.\n */\nfunction base64UrlToBytes(value: string): Uint8Array<ArrayBuffer> {\n const padded = value.replace(/-/g, '+').replace(/_/g, '/');\n const binary = atob(padded.padEnd(padded.length + ((4 - (padded.length % 4)) % 4), '='));\n const bytes = new Uint8Array(new ArrayBuffer(binary.length));\n for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);\n return bytes;\n}\n\n/** Encode as base64URL without padding — the only form WebAuthn accepts. */\nfunction bytesToBase64Url(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = '';\n for (const b of bytes) binary += String.fromCharCode(b);\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\nfunction requireString(source: Record<string, unknown>, key: string): string {\n const value = source[key];\n if (typeof value !== 'string' || value === '') {\n throw new Error(`passkey options are missing ${key}`);\n }\n return value;\n}\n\n/**\n * Turn palauth's creation options into the shape navigator.credentials.create\n * wants. Only the fields the ceremony requires are translated; anything else\n * the server sends rides along untouched.\n */\nfunction toCreationOptions(publicKey: Record<string, unknown>): PublicKeyCredentialCreationOptions {\n const rp = publicKey.rp as { id?: string; name?: string } | undefined;\n const user = publicKey.user as { id?: string; name?: string; displayName?: string } | undefined;\n if (!user?.id) {\n throw new Error('passkey options carry no user id — the credential would be unusable');\n }\n const excludeCredentials = Array.isArray(publicKey.excludeCredentials)\n ? (publicKey.excludeCredentials as Array<{ id: string; type: string }>).map((c) => ({\n id: base64UrlToBytes(c.id),\n type: 'public-key' as const,\n }))\n : undefined;\n\n return {\n challenge: base64UrlToBytes(requireString(publicKey, 'challenge')),\n rp: { id: rp?.id, name: rp?.name ?? rp?.id ?? '' },\n user: {\n id: base64UrlToBytes(user.id),\n name: user.name ?? '',\n displayName: user.displayName ?? user.name ?? '',\n },\n pubKeyCredParams:\n (publicKey.pubKeyCredParams as PublicKeyCredentialParameters[] | undefined) ?? [],\n authenticatorSelection: publicKey.authenticatorSelection as\n | AuthenticatorSelectionCriteria\n | undefined,\n timeout: publicKey.timeout as number | undefined,\n attestation: publicKey.attestation as AttestationConveyancePreference | undefined,\n excludeCredentials,\n };\n}\n\n/** Assertion options carry `rpId` at the top level, not `rp.id`. */\nfunction toRequestOptions(publicKey: Record<string, unknown>): PublicKeyCredentialRequestOptions {\n const allowCredentials = Array.isArray(publicKey.allowCredentials)\n ? (publicKey.allowCredentials as Array<{ id: string }>).map((c) => ({\n id: base64UrlToBytes(c.id),\n type: 'public-key' as const,\n }))\n : undefined;\n\n return {\n challenge: base64UrlToBytes(requireString(publicKey, 'challenge')),\n rpId: publicKey.rpId as string | undefined,\n timeout: publicKey.timeout as number | undefined,\n userVerification: publicKey.userVerification as UserVerificationRequirement | undefined,\n allowCredentials,\n };\n}\n\nfunction attestationToJSON(credential: PublicKeyCredential): Record<string, unknown> {\n const response = credential.response as AuthenticatorAttestationResponse;\n return {\n id: credential.id,\n rawId: bytesToBase64Url(credential.rawId),\n type: credential.type,\n response: {\n clientDataJSON: bytesToBase64Url(response.clientDataJSON),\n attestationObject: bytesToBase64Url(response.attestationObject),\n },\n };\n}\n\nfunction assertionToJSON(credential: PublicKeyCredential): Record<string, unknown> {\n const response = credential.response as AuthenticatorAssertionResponse;\n return {\n id: credential.id,\n rawId: bytesToBase64Url(credential.rawId),\n type: credential.type,\n response: {\n clientDataJSON: bytesToBase64Url(response.clientDataJSON),\n authenticatorData: bytesToBase64Url(response.authenticatorData),\n signature: bytesToBase64Url(response.signature),\n // The user handle is how palauth resolves WHO signed in on a discoverable\n // login — there is no identifier in the request.\n userHandle: response.userHandle ? bytesToBase64Url(response.userHandle) : null,\n },\n };\n}\n\n/**\n * Whether this browser can run a passkey ceremony at all.\n *\n * Callers MUST branch on this rather than assuming: a client without WebAuthn —\n * or without Related Origin Requests, when the page is on a different domain\n * than the relying party — throws instead of degrading, so the UI needs a\n * password or e-mail path beside the passkey button.\n */\nexport function isPasskeySupported(): boolean {\n return (\n typeof window !== 'undefined' &&\n typeof window.PublicKeyCredential === 'function' &&\n typeof navigator !== 'undefined' &&\n navigator.credentials !== undefined\n );\n}\n\nfunction ensureSupported(): void {\n if (!isPasskeySupported()) {\n throw new Error('This browser cannot use passkeys — offer a password or e-mail sign-in');\n }\n}\n\nasync function createCredential(publicKey: Record<string, unknown>): Promise<PublicKeyCredential> {\n const credential = await navigator.credentials.create({\n publicKey: toCreationOptions(publicKey),\n });\n if (credential === null) {\n throw new Error('the browser returned no credential');\n }\n return credential as PublicKeyCredential;\n}\n\nasync function getCredential(publicKey: Record<string, unknown>): Promise<PublicKeyCredential> {\n const credential = await navigator.credentials.get({\n publicKey: toRequestOptions(publicKey),\n });\n if (credential === null) {\n throw new Error('the browser returned no credential');\n }\n return credential as PublicKeyCredential;\n}\n\n/** Sign in with a passkey already stored for this relying party. */\nexport async function passkeySignIn(rt: PalbeRuntime): Promise<WireAuthResult> {\n ensureSupported();\n // An EMPTY body is what makes this discoverable: palauth reads no user_id and\n // asks the authenticator for whatever it holds for this relying party.\n const envelope = await palbeRequest<OptionsEnvelope>(rt, 'POST', '/auth/webauthn/login/begin', {\n body: {},\n });\n const credential = await getCredential(envelope.options.publicKey);\n const raw = await palbeRequest<unknown>(rt, 'POST', '/auth/webauthn/login/finish', {\n body: assertionToJSON(credential),\n });\n const result = asWireAuthResult(raw);\n if (result === null) {\n throw new Error('passkey sign-in returned an unusable session');\n }\n return result;\n}\n\n/**\n * Create an account whose only credential is a passkey — no password ever\n * exists for it.\n */\nexport async function passkeySignUp(\n rt: PalbeRuntime,\n email: string,\n displayName?: string,\n): Promise<WireAuthResult> {\n ensureSupported();\n const begun = await palbeRequest<SignupBeginResponse>(rt, 'POST', '/auth/webauthn/signup/begin', {\n body: { email, user_name: displayName },\n });\n const credential = await createCredential(begun.options.publicKey);\n // user_id travels as a QUERY parameter: palauth keys the pending ceremony by\n // it, and the account was created by signup/begin moments ago.\n await palbeRequest<unknown>(\n rt,\n 'POST',\n `/auth/webauthn/signup/finish?user_id=${encodeURIComponent(begun.user_id)}`,\n { body: attestationToJSON(credential) },\n );\n // signup/finish returns the CREDENTIAL, not a session: the account exists but\n // nobody is signed in. Signing in immediately is what a caller means by \"sign\n // up\", and it also proves the credential just created actually works.\n return await passkeySignIn(rt);\n}\n\n/**\n * Add a passkey to the account that is already signed in.\n *\n * Requires a RECENT sign-in: palauth answers 403 `reauthentication_required`\n * when the session is older than its freshness window, because a passkey keeps\n * working after a password reset and a merely-valid session must not be able to\n * enroll one. Callers should re-authenticate and retry.\n */\nexport async function passkeyRegister(rt: PalbeRuntime, name?: string): Promise<void> {\n ensureSupported();\n const envelope = await palbeRequest<OptionsEnvelope>(\n rt,\n 'POST',\n '/auth/webauthn/register/begin',\n { body: { name } },\n );\n const credential = await createCredential(envelope.options.publicKey);\n await palbeRequest<unknown>(rt, 'POST', '/auth/webauthn/register/finish', {\n body: attestationToJSON(credential),\n });\n}\n","import type { User as AuthClientUser } from '@palbase/auth';\nimport type { Session } from '@palbase/core';\nimport { asWireAuthResult, type WireAuthResult, type WireMe, type WireUser } from './auth-wire.js';\nimport { BackendError, unwrap } from './errors.js';\nimport { isPasskeySupported, passkeyRegister, passkeySignIn, passkeySignUp } from './passkey.js';\nimport { palbeRequest } from './request.js';\nimport type { PalbeRuntime } from './runtime.js';\n\nexport type MagicLinkResult =\n | ({ status: 'signedIn' } & AuthSuccess)\n | { status: 'mfaRequired'; mfaToken: string; factors: string[] };\n\nexport type OAuthExchangeResult =\n | ({ status: 'signedIn' } & AuthSuccess)\n | { status: 'mfaRequired'; mfaToken: string; factors: string[] };\n\nexport interface AuthUser {\n id: string;\n /** null for phone-only users (the server omits email). */\n email: string | null;\n emailVerified: boolean;\n createdAt: string;\n}\n\n/** What `pb.auth.getSignInMethods()` reports about an account's credentials. */\nexport interface SignInMethods {\n /** A local password exists — offer \"change password\", not \"set password\". */\n password: boolean;\n /** An MFA factor is enrolled. */\n mfa: boolean;\n /**\n * Linked OAuth providers, e.g. `['google']`. `[]` means none are linked;\n * `null` means the server could not determine it (social login is not\n * configured, or the lookup failed) — treat null as unknown, not as none.\n */\n providers: string[] | null;\n}\n\nexport interface AuthSuccess {\n user: AuthUser;\n session: Session;\n}\n\nexport type AuthState = { status: 'signedIn'; user: AuthUser } | { status: 'signedOut' };\n\nexport type AuthChangeEvent =\n | { type: 'signedIn'; user: AuthUser }\n | {\n type: 'signedOut';\n /**\n * `userInitiated` — pb.auth.signOut(); `sessionExpired` — a refresh\n * ultimately failed; `accountDeleted` — this device ran deleteAccount();\n * `sessionInvalid` — a server-side session kill (erased/revoked subject)\n * was observed on a request and the client tore down reactively.\n */\n reason: 'userInitiated' | 'sessionExpired' | 'accountDeleted' | 'sessionInvalid';\n }\n | { type: 'tokenRefreshed' };\n\nexport type Unsubscribe = () => void;\n\n/** Map the wire bare-user shape (GET /auth/user) to AuthUser. */\nfunction mapWireUser(raw: WireUser): AuthUser {\n return {\n id: raw.id,\n email: raw.email ?? null,\n emailVerified: raw.email_verified,\n createdAt: raw.created_at,\n };\n}\n\n/** Map @palbase/auth's camelCase User to palbe's AuthUser (drop extra fields). */\nfunction mapClientUser(u: AuthClientUser): AuthUser {\n // AuthClient types email as a required string, but the wire omits it for\n // phone-only users — normalize missing/empty to null at this boundary.\n return {\n id: u.id,\n email: u.email || null,\n emailVerified: u.emailVerified,\n createdAt: u.createdAt,\n };\n}\n\n/** Key-order-proof equality on the four AuthUser fields (refreshUser change detection). */\nfunction usersEqual(a: AuthUser, b: AuthUser | null): boolean {\n return (\n b !== null &&\n a.id === b.id &&\n a.email === b.email &&\n a.emailVerified === b.emailVerified &&\n a.createdAt === b.createdAt\n );\n}\n\nexport class PalbeAuth {\n private cachedUser: AuthUser | null = null;\n private signingOut = false;\n // Explicit signedOut reason pinned for the NEXT session clear (deleteAccount /\n // reactiveTeardown set it before clearSession; the SIGNED_OUT handler consumes\n // it exactly once, then resets to null → normal signOut/expiry split resumes).\n private nextSignOutReason: 'accountDeleted' | 'sessionInvalid' | null = null;\n private signingIn = false; // suppresses AuthClient's TOKEN_REFRESHED during re-signIn\n private signedInState = false; // dedupes AuthClient's repeated SIGNED_OUT events\n private hydratingUser = false; // guards hydrateUser() to a single boot fetch\n private readonly stateListeners = new Set<(state: AuthState) => void>();\n private readonly eventListeners = new Set<(event: AuthChangeEvent) => void>();\n private readonly userListeners = new Set<(user: AuthUser) => void>();\n\n constructor(private readonly rt: PalbeRuntime) {\n // AuthClient fires SIGNED_OUT on any session clear and TOKEN_REFRESHED on\n // refresh. SIGNED_IN is emitted by the facade itself (it knows the user);\n // after hydration AuthClient never fires SIGNED_IN anyway (hasSession=true).\n rt.authClient.onAuthStateChange((event, _session) => {\n if (event === 'SIGNED_OUT') {\n if (!this.signedInState) return; // duplicate clear — already signed out\n this.signedInState = false;\n this.cachedUser = null;\n // A caller (deleteAccount / reactiveTeardown) may pin an explicit reason\n // for THIS clear; otherwise fall back to the signOut-vs-expiry split.\n const reason =\n this.nextSignOutReason ?? (this.signingOut ? 'userInitiated' : 'sessionExpired');\n this.nextSignOutReason = null;\n this.emitState({ status: 'signedOut' });\n this.emitEvent({ type: 'signedOut', reason });\n } else if (event === 'TOKEN_REFRESHED') {\n // Re-signIn while signed in makes AuthClient map the new session to\n // TOKEN_REFRESHED (hasSession=true) — that would leak a tokenRefreshed\n // with the OLD user just before signedIn. iOS emits exactly one\n // signedIn per login; suppress it here.\n if (this.signingIn) return;\n this.emitEvent({ type: 'tokenRefreshed' });\n }\n });\n // Hydrated runtime starts in a signed-in-ish state (pending refresh).\n this.signedInState = rt.tokenManager.getRefreshToken() !== null;\n }\n\n // ── state ──────────────────────────────────────────────\n\n get currentUser(): AuthUser | null {\n return this.cachedUser;\n }\n\n get isSignedIn(): boolean {\n return this.rt.tokenManager.getRefreshToken() !== null;\n }\n\n // ── core flows ─────────────────────────────────────────\n\n async signUp(params: { email: string; password: string }): Promise<AuthSuccess> {\n this.signingIn = true;\n try {\n const data = unwrap(await this.rt.authClient.signUp(params));\n return this.adopt(data);\n } finally {\n this.signingIn = false;\n }\n }\n\n async signIn(params: { email: string; password: string }): Promise<AuthSuccess> {\n this.signingIn = true;\n try {\n const data = unwrap(await this.rt.authClient.signIn(params));\n return this.adopt(data);\n } finally {\n this.signingIn = false;\n }\n }\n\n async signOut(): Promise<void> {\n this.signingOut = true;\n try {\n await this.rt.authClient.signOut();\n } catch {\n // local sign-out must win even when the server call fails (offline etc.)\n } finally {\n this.rt.tokenManager.clearSession(); // idempotent; deduped by signedInState\n this.signingOut = false; // AFTER clear → reason stays userInitiated\n this.rt.storage.clear();\n this.cachedUser = null;\n }\n }\n\n /**\n * Self-service account erasure (\"right to be forgotten\"). Calls palauth\n * `DELETE /auth/user` (session-authed + fresh re-auth): password users pass\n * `{ password }`; passwordless/OAuth users rely on a freshly stepped-up\n * session. The handler answers **204 with no body** — the erasure has already\n * finished and every session is already revoked server-side — so we tear down\n * local state (mirroring `signOut()`'s clear, minus the pointless `/logout`)\n * and emit `signedOut{reason:'accountDeleted'}`.\n *\n * Returns nothing, because there is nothing to return: the handler used to\n * answer 202 with an `{erasure_id}` naming an asynchronous run, and that id\n * was removed along with the run (`gdpr_handlers.go:221-229`). Reading it\n * here threw a `TypeError` AFTER the account was already gone — a success\n * reported as a failure.\n *\n * On 401 `reauth_required` / 503 `not_configured` (or `erasure_unavailable`)\n * the request throws BEFORE the teardown line, so the local session is left\n * fully intact — the deletion did not happen and the user is still signed in.\n */\n async deleteAccount(params: { password?: string } = {}): Promise<void> {\n await palbeRequest<null>(this.rt, 'DELETE', '/auth/user', {\n body: params.password ? { password: params.password } : {},\n });\n // Reached only on 204 (2xx): sessions already revoked server-side.\n this.nextSignOutReason = 'accountDeleted';\n this.rt.tokenManager.clearSession(); // fires SIGNED_OUT → emits accountDeleted\n this.rt.storage.clear();\n this.cachedUser = null;\n }\n\n /**\n * @internal — invoked by the transport choke point (`request.ts`), not app code.\n * Reactive teardown when the server reports the session is gone: a 403\n * `subject_fenced`/`subject_erased` (subject being erased) or a 401\n * `session_revoked`. Clears local state and emits\n * `signedOut{reason:'sessionInvalid'}` exactly once (deduped by\n * `signedInState`). An ordinary 403 authz denial does NOT reach here —\n * `request.ts` matches only the exact session-kill codes.\n */\n reactiveTeardown(): void {\n if (!this.signedInState) return; // already torn down — exactly-once\n this.nextSignOutReason = 'sessionInvalid';\n this.rt.tokenManager.clearSession(); // fires SIGNED_OUT → emits sessionInvalid\n this.rt.storage.clear();\n this.cachedUser = null;\n }\n\n async getUser(): Promise<AuthUser> {\n const raw = await palbeRequest<WireUser>(this.rt, 'GET', '/auth/user');\n return mapWireUser(raw);\n }\n\n /**\n * Which credentials this account actually has.\n *\n * An account reached through Google has no password, so showing it a\n * \"current password\" field gives the user something they can never fill —\n * and `getUser()` cannot tell you, because GET /auth/user carries no\n * credential information. Read this before rendering any re-auth or\n * change-password form.\n */\n async getSignInMethods(): Promise<SignInMethods> {\n const raw = await palbeRequest<WireMe>(this.rt, 'GET', '/auth/me');\n return {\n password: raw.has_password,\n mfa: raw.has_mfa,\n providers: raw.providers ?? null,\n };\n }\n\n async refreshUser(): Promise<AuthUser> {\n const user = await this.getUser();\n const changed = !usersEqual(user, this.cachedUser);\n this.cachedUser = user;\n if (changed) for (const cb of this.userListeners) this.safeInvoke(() => cb(user));\n return user;\n }\n\n /**\n * Restore the user after a session was rehydrated from storage (page reload).\n * Hydration in buildRuntime restores the TOKENS synchronously, but the access\n * token JWT does not carry emailVerified/createdAt, so the full AuthUser can't\n * be reconstructed offline — this fetches GET /auth/user once and announces\n * `signedIn`, so the app no longer has to `await pb.auth.refreshUser()` itself\n * on boot. Idempotent (runs once), browser-safe (no-op when not signed in or\n * a user is already cached), and never throws (a boot-time network failure\n * must not break app startup — isSignedIn stays true, the app can retry).\n */\n hydrateUser(): void {\n if (this.hydratingUser || this.cachedUser || !this.isSignedIn) return;\n this.hydratingUser = true;\n void this.refreshUser()\n .then((user) => {\n // Only announce if we're still signed in (a sign-out may have raced).\n if (this.isSignedIn) {\n this.signedInState = true;\n this.emitState({ status: 'signedIn', user });\n }\n })\n .catch(() => {\n // Swallow: a failed boot fetch leaves isSignedIn true; the app can\n // call refreshUser() again or the next authed request will 401→refresh.\n })\n .finally(() => {\n this.hydratingUser = false;\n });\n }\n\n // ── listeners ──────────────────────────────────────────\n\n /**\n * Subscribe to signed-in/signed-out state. Fires immediately with the\n * current snapshot (iOS parity). A restored session (page reload) hydrates\n * the user asynchronously via `hydrateUser()` (fired once at boot), so the\n * FIRST snapshot may report signedOut for a tick even when `isSignedIn` is\n * true; the listener then fires again with `signedIn` once the user lands.\n * Rely on `isSignedIn` for the session truth if you need it synchronously.\n */\n onAuthStateChange(callback: (state: AuthState) => void): Unsubscribe {\n this.stateListeners.add(callback);\n this.safeInvoke(() => callback(this.snapshotState()));\n return () => this.stateListeners.delete(callback);\n }\n\n onAuthEvent(callback: (event: AuthChangeEvent) => void): Unsubscribe {\n this.eventListeners.add(callback);\n return () => this.eventListeners.delete(callback);\n }\n\n /** Subscribe to user-profile changes. Replays the cached user on subscribe (iOS parity). */\n onUserChange(callback: (user: AuthUser) => void): Unsubscribe {\n this.userListeners.add(callback);\n const u = this.cachedUser;\n if (u) this.safeInvoke(() => callback(u));\n return () => this.userListeners.delete(callback);\n }\n\n // ── internals ──────────────────────────────────────────\n\n private snapshotState(): AuthState {\n return this.cachedUser && this.isSignedIn\n ? { status: 'signedIn', user: this.cachedUser }\n : { status: 'signedOut' };\n }\n\n /**\n * Listener exception isolation: a throwing consumer callback must never\n * break delivery to other listeners nor propagate into the emit caller\n * (tokenManager.clearSession callers, pb.call paths, signOut, ...).\n */\n private safeInvoke(fn: () => void): void {\n try {\n fn();\n } catch {\n // consumer callback errors are intentionally swallowed\n }\n }\n\n private emitState(state: AuthState): void {\n for (const cb of this.stateListeners) this.safeInvoke(() => cb(state));\n }\n\n private emitEvent(event: AuthChangeEvent): void {\n for (const cb of this.eventListeners) this.safeInvoke(() => cb(event));\n }\n\n /** Cache the user + announce sign-in. Session was already set by AuthClient. */\n private adopt(data: { user: AuthClientUser; session: Session }): AuthSuccess {\n const user = mapClientUser(data.user);\n this.cachedUser = user;\n this.signedInState = true;\n this.emitState({ status: 'signedIn', user });\n this.emitEvent({ type: 'signedIn', user });\n return { user, session: data.session };\n }\n\n /** Adopt a raw palauth AuthResult wire shape: wire tokens, cache user, announce. */\n private adoptWire(raw: WireAuthResult): AuthSuccess {\n this.rt.authClient.setTokens(raw.access_token, raw.refresh_token, raw.expires_in);\n const user = mapWireUser(raw.user);\n this.cachedUser = user;\n this.signedInState = true;\n this.emitState({ status: 'signedIn', user });\n this.emitEvent({ type: 'signedIn', user });\n const session: Session = {\n accessToken: raw.access_token,\n refreshToken: raw.refresh_token,\n expiresAt: Date.now() + raw.expires_in * 1000,\n };\n return { user, session };\n }\n\n /** Run a sign-in flow under the signingIn flag to suppress spurious tokenRefreshed. */\n private async withSigningIn<T>(fn: () => Promise<T>): Promise<T> {\n this.signingIn = true;\n try {\n return await fn();\n } finally {\n this.signingIn = false;\n }\n }\n\n /**\n * Defensively decode a 200 union body (AuthResult | mfa-required) and\n * complete the flow. The wire contract promises one of the two shapes, but\n * a literal-`null` (or otherwise malformed) JSON body must surface as\n * BackendError('decode') — never a raw TypeError from probing a non-object.\n * The mfa branch validates its fields: `mfa_token` is required; the factor\n * list (named `factors` by magic-link verify, `mfa_factors` by the OAuth\n * callback — extraction-verified against palauth) tolerates a missing or\n * malformed value as []. The signedIn branch validates the FULL AuthResult\n * shape (asWireAuthResult) — an incomplete body falls through to decode,\n * never half-adopts.\n */\n private completeAuthUnion(\n raw: unknown,\n factorsKey: 'factors' | 'mfa_factors',\n context: string,\n ): MagicLinkResult {\n if (typeof raw === 'object' && raw !== null) {\n const obj = raw as Record<string, unknown>;\n if (obj.mfa_required === true) {\n if (typeof obj.mfa_token === 'string') {\n const list = obj[factorsKey];\n const factors = Array.isArray(list)\n ? list.filter((f): f is string => typeof f === 'string')\n : [];\n return { status: 'mfaRequired', mfaToken: obj.mfa_token, factors };\n }\n // mfa_required without a usable mfa_token is unrecoverable — fall\n // through to decode (NOT the signedIn branch).\n } else {\n const wire = asWireAuthResult(raw);\n if (wire) return { status: 'signedIn', ...this.adoptWire(wire) };\n }\n }\n // 200 body matching neither shape — never fabricate a result.\n throw this.decodeError(context);\n }\n\n private decodeError(context: string): BackendError {\n return new BackendError('decode', {\n code: 'decode_error',\n message: `Unexpected ${context} response`,\n });\n }\n\n // ── additional auth flows ──────────────────────────────\n\n async signInWithOTP(params: { phone: string }): Promise<void> {\n // The server only supports the 'sms' channel (rejects anything else).\n await palbeRequest(this.rt, 'POST', '/auth/otp', {\n body: { phone: params.phone, channel: 'sms' },\n });\n }\n\n async verifyOTP(params: { phone: string; token: string }): Promise<AuthSuccess> {\n return this.withSigningIn(async () => {\n const raw = await palbeRequest<unknown>(this.rt, 'POST', '/auth/otp/verify', {\n body: params,\n });\n // The OTP-verify wire is AuthResult ONLY (palauth phoneLoginResponse —\n // no MFA union on this route): the same zombie-session guard applies,\n // and an mfa-shaped body here is a decode error, not an MFA result.\n const wire = asWireAuthResult(raw);\n if (!wire) throw this.decodeError('OTP verify');\n return this.adoptWire(wire);\n });\n }\n\n async resetPassword(email: string): Promise<void> {\n unwrap(await this.rt.authClient.requestPasswordReset({ email }));\n }\n\n async confirmPasswordReset(params: { token: string; newPassword: string }): Promise<void> {\n unwrap(\n await this.rt.authClient.confirmPasswordReset({\n token: params.token,\n new_password: params.newPassword,\n }),\n );\n }\n\n async updatePassword(params: { currentPassword: string; newPassword: string }): Promise<void> {\n unwrap(\n await this.rt.authClient.changePassword({\n current_password: params.currentPassword,\n new_password: params.newPassword,\n }),\n );\n }\n\n /**\n * Give a passwordless account its FIRST password, in-app.\n *\n * For accounts created with a passkey or a social provider: they have no\n * current password, so `updatePassword` cannot serve them — it sends one and\n * the server answers 401. Before this existed the only route was the\n * password-RESET e-mail, which asks someone already signed in to go find\n * their inbox.\n *\n * Requires a RECENT sign-in, not merely a session: a first password is a\n * credential that outlives the rest, so the server answers 403\n * `reauthentication_required` on a stale one — the same gate\n * `registerPasskey` carries. An account that already has a password gets 409\n * `password_already_set`; change it with `updatePassword` instead.\n *\n * The session stays valid — no re-login.\n */\n async setPassword(newPassword: string): Promise<void> {\n unwrap(await this.rt.authClient.setPassword({ new_password: newPassword }));\n }\n\n async verifyEmail(params: { token: string } | { code: string; email: string }): Promise<void> {\n unwrap(await this.rt.authClient.verifyEmail(params));\n }\n\n async resendVerification(email: string): Promise<void> {\n unwrap(await this.rt.authClient.resendVerification(email));\n }\n\n async signInWithMagicLink(email: string): Promise<void> {\n unwrap(await this.rt.authClient.requestMagicLink({ email }));\n }\n\n async verifyMagicLink(token: string): Promise<MagicLinkResult> {\n return this.withSigningIn(async () => {\n const raw = await palbeRequest<unknown>(this.rt, 'POST', '/auth/magic-link/verify', {\n body: { token },\n });\n return this.completeAuthUnion(raw, 'factors', 'magic-link verify');\n });\n }\n\n async signInWithCredential(params: {\n provider: string;\n credential: string;\n }): Promise<AuthSuccess> {\n return this.withSigningIn(async () => {\n const data = unwrap(await this.rt.authClient.signInWithCredential(params));\n return this.adopt(data);\n });\n }\n\n /**\n * Sign in with a passkey stored for this Environment. No identifier is typed:\n * the browser lists the accounts it holds and the user picks one.\n *\n * Throws when the browser cannot run a ceremony — check `passkeysSupported`\n * first and keep a password or e-mail path beside the button.\n */\n async signInWithPasskey(): Promise<AuthSuccess> {\n return this.withSigningIn(async () => this.adoptWire(await passkeySignIn(this.rt)));\n }\n\n /**\n * Create an account whose only credential is a passkey — the e-mail is the\n * identifier, the passkey is the credential, and no password ever exists.\n */\n async signUpWithPasskey(email: string, displayName?: string): Promise<AuthSuccess> {\n return this.withSigningIn(async () =>\n this.adoptWire(await passkeySignUp(this.rt, email, displayName)),\n );\n }\n\n /**\n * Add a passkey to the signed-in account, so the next sign-in is one tap.\n *\n * Rejects with a 403 `reauthentication_required` when the session is not\n * recent: a passkey outlives a password reset, so enrolling one takes more\n * than a valid session.\n */\n async registerPasskey(name?: string): Promise<void> {\n await passkeyRegister(this.rt, name);\n }\n\n /** Whether this browser can run a passkey ceremony at all. */\n get passkeysSupported(): boolean {\n return isPasskeySupported();\n }\n\n async signInWithOAuth(params: {\n provider: string;\n redirectTo?: string;\n /** Set false to skip the browser redirect (popup/manual flows). Default true. */\n redirect?: boolean;\n }): Promise<{ url: string }> {\n const { redirect = true, ...opts } = params;\n const { url } = unwrap(await this.rt.authClient.getOAuthURL(opts));\n if (redirect && typeof window !== 'undefined' && typeof window.location !== 'undefined') {\n window.location.assign(url);\n }\n return { url };\n }\n\n /**\n * Complete the OAuth redirect flow: trade the provider's `code` + `state`\n * (from the app's redirect_uri query) for a session. PKCE verifier + state\n * live server-side in palauth — the client only relays the two values.\n */\n async exchangeCodeForSession(params: {\n provider: string;\n code: string;\n state: string;\n }): Promise<OAuthExchangeResult> {\n return this.withSigningIn(async () => {\n const path =\n `/auth/oauth/${encodeURIComponent(params.provider)}/callback` +\n `?code=${encodeURIComponent(params.code)}&state=${encodeURIComponent(params.state)}`;\n const raw = await palbeRequest<unknown>(this.rt, 'GET', path);\n return this.completeAuthUnion(raw, 'mfa_factors', 'OAuth code-exchange');\n });\n }\n\n /** Config-as-code gate for the zero-arg provider sugar. */\n private requireProvider(name: 'google' | 'apple', label: string): void {\n if (!this.rt.config.oauth?.[name]?.enabled) {\n throw new BackendError('validation', {\n code: `${name}_not_configured`,\n message: `${label} sign-in is not configured: enable the ${label} provider for this project, then regenerate palbe.gen.ts.`,\n });\n }\n }\n\n async signInWithGoogle(opts?: { redirectTo?: string; redirect?: boolean }): Promise<{\n url: string;\n }> {\n this.requireProvider('google', 'Google');\n return this.signInWithOAuth({ provider: 'google', ...opts });\n }\n\n async signInWithApple(opts?: { redirectTo?: string; redirect?: boolean }): Promise<{\n url: string;\n }> {\n this.requireProvider('apple', 'Apple');\n return this.signInWithOAuth({ provider: 'apple', ...opts });\n }\n}\n","// `pb.calls` — SP-4 voice/video call surface for the web SDK.\n//\n// Architecture: thin wrapper around the embedded media engine (browser WebRTC) +\n// the messaging server's call REST endpoints:\n// POST /v1/messaging/groups/{gid}/calls → start a call\n// POST /v1/messaging/groups/{gid}/calls/{cid}/accept → join a call\n// POST /v1/messaging/groups/{gid}/calls/{cid}/decline → decline\n// POST /v1/messaging/groups/{gid}/calls/{cid}/mute → mute/unmute (host)\n// POST /v1/messaging/groups/{gid}/calls/{cid}/kick → kick (host)\n//\n// The {gid} is a messaging group display-id (`grp_<uuidv7>`).\n//\n// ─── E2EE NOTE ─────────────────────────────────────────────────────────────\n// The media engine's hop-by-hop transport is already DTLS-SRTP encrypted.\n// Frame-level end-to-end encryption (MLS-derived keys via the engine's external\n// key provider) is the SP-1.x web-MLS work and is OUT OF SCOPE for this surface.\n// The `setMediaKey` method is wired to that key provider as a design seam; call\n// it AFTER web-MLS delivers a shared frame key. Until then, rooms connect without\n// e2ee.\n// TODO(SP-1-web): derive the frame key from the MLS exporter and call\n// call.setMediaKey(derivedKeyArrayBuffer)\n// ───────────────────────────────────────────────────────────────────────────\n\nimport {\n ExternalE2EEKeyProvider,\n type RemoteParticipant,\n type RemoteTrack,\n type RemoteTrackPublication,\n Room,\n RoomEvent,\n Track,\n} from 'livekit-client';\nimport type { Unsubscribe } from '../auth-facade.js';\nimport { BackendError } from '../errors.js';\nimport { palbeRequest } from '../request.js';\nimport type { PalbeRuntime } from '../runtime.js';\n\n// ─── Wire types ────────────────────────────────────────────────────────────\n\ninterface StartCallResponse {\n call_id: string;\n token: string;\n edge_url: string;\n max_members: number;\n}\n\ninterface AcceptCallResponse {\n token: string;\n edge_url: string;\n}\n\n// ─── Public types ──────────────────────────────────────────────────────────\n\nexport type CallState = 'connecting' | 'active' | 'ended';\n\nexport interface CallParticipant {\n identity: string;\n isSpeaking: boolean;\n audioMuted: boolean;\n videoEnabled: boolean;\n /** The live <video> element for this participant, or null before track arrives. */\n videoElement: HTMLVideoElement | null;\n /** The live <audio> element for this participant, or null before track arrives. */\n audioElement: HTMLAudioElement | null;\n}\n\nexport type CallChangeCallback = (call: Call) => void;\n\n/**\n * A live call session. Obtain via `pb.calls.start()` or `pb.calls.accept()`.\n *\n * Lifecycle:\n * connecting → active (room joined, tracks flowing)\n * active → ended (leave() / remote disconnect)\n *\n * Subscribe to changes via `call.subscribe(cb)` which fires on state or\n * participant changes (mirrors flags `onChange` / realtime `on` pattern).\n */\nexport class Call {\n readonly id: string;\n\n private _state: CallState = 'connecting';\n private readonly _participants = new Map<string, CallParticipant>();\n private readonly _listeners = new Set<CallChangeCallback>();\n private readonly _room: Room;\n private readonly _keyProvider: ExternalE2EEKeyProvider;\n\n // PRIVATE constructor + `@internal` factory: the media-engine types (`Room`,\n // `ExternalE2EEKeyProvider`) appear ONLY on a private constructor and an\n // `@internal` static factory, both of which `stripInternal` (set in this\n // package's tsconfig) removes from the emitted public `.d.ts`. So no engine type\n // ever reaches the public API surface — the same \"no engine type in the public\n // interface\" contract the iOS SDK enforces. Consumers obtain a `Call` only\n // through `pb.calls.start()` / `.accept()`.\n private constructor(callId: string, room: Room, keyProvider: ExternalE2EEKeyProvider) {\n this.id = callId;\n this._room = room;\n this._keyProvider = keyProvider;\n this._wireMediaEvents();\n }\n\n /** @internal — facade-only factory; stripped from the public `.d.ts`. */\n static _create(callId: string, room: Room, keyProvider: ExternalE2EEKeyProvider): Call {\n return new Call(callId, room, keyProvider);\n }\n\n // ─── State ──────────────────────────────────────────────────────────────\n\n get state(): CallState {\n return this._state;\n }\n\n /** Snapshot of current remote participants (does NOT include the local participant). */\n get participants(): readonly CallParticipant[] {\n return Array.from(this._participants.values());\n }\n\n // (No `localParticipant` accessor: it would return a raw media-engine participant\n // object and leak the engine type onto the public API. Local mic/camera control is\n // exposed engine-agnostically via `mute()` / `setCamera()` below.)\n\n // ─── Subscribe ──────────────────────────────────────────────────────────\n\n /**\n * Subscribe to call changes (state + participant updates).\n * Returns an `Unsubscribe` function — matches the SDK's flags/auth pattern.\n */\n subscribe(callback: CallChangeCallback): Unsubscribe {\n this._listeners.add(callback);\n return () => {\n this._listeners.delete(callback);\n };\n }\n\n // ─── Controls ───────────────────────────────────────────────────────────\n\n /** Mute / unmute the local microphone. */\n async mute(on: boolean): Promise<void> {\n await this._room.localParticipant.setMicrophoneEnabled(!on);\n }\n\n /** Enable / disable the local camera. */\n async setCamera(on: boolean): Promise<void> {\n await this._room.localParticipant.setCameraEnabled(on);\n }\n\n /**\n * Set a raw frame encryption key (ArrayBuffer) for this call.\n * Design seam for SP-1.x web-MLS: call this with the MLS exporter secret\n * to enable frame-level E2EE via the media engine's external key provider.\n *\n * NOTE: The room must have been opened with `e2ee` options for this to take\n * effect. Currently calls connect WITHOUT e2ee — see module docstring.\n *\n * TODO(SP-1-web): wire this to MLS-derived key on session join.\n */\n async setMediaKey(key: ArrayBuffer): Promise<void> {\n await this._keyProvider.setKey(key);\n }\n\n /** Leave the call and disconnect from the media room. */\n async leave(): Promise<void> {\n await this._room.disconnect();\n this._setState('ended');\n }\n\n // ─── Internal ───────────────────────────────────────────────────────────\n\n private _setState(next: CallState): void {\n if (this._state === next) return;\n this._state = next;\n this._emit();\n }\n\n private _emit(): void {\n for (const cb of this._listeners) cb(this);\n }\n\n private _upsertParticipant(p: RemoteParticipant): void {\n const existing = this._participants.get(p.identity);\n const entry: CallParticipant = {\n identity: p.identity,\n isSpeaking: p.isSpeaking,\n audioMuted: !p.isMicrophoneEnabled,\n videoEnabled: p.isCameraEnabled,\n videoElement: existing?.videoElement ?? null,\n audioElement: existing?.audioElement ?? null,\n };\n this._participants.set(p.identity, entry);\n }\n\n private _attachTrack(track: RemoteTrack, participant: RemoteParticipant): void {\n const element = track.attach();\n const entry = this._participants.get(participant.identity);\n if (track.kind === Track.Kind.Video) {\n if (entry) {\n entry.videoElement = element as HTMLVideoElement;\n }\n } else if (track.kind === Track.Kind.Audio) {\n if (entry) {\n entry.audioElement = element as HTMLAudioElement;\n // Auto-play audio (browser policy: audio elements can autoplay when\n // created by a user gesture chain; the call starts from a user tap).\n (element as HTMLAudioElement).play().catch(() => {});\n }\n }\n this._emit();\n }\n\n private _wireMediaEvents(): void {\n this._room\n .on(RoomEvent.Connected, () => {\n this._setState('active');\n })\n .on(\n RoomEvent.TrackSubscribed,\n (track: RemoteTrack, _pub: RemoteTrackPublication, participant: RemoteParticipant) => {\n this._upsertParticipant(participant);\n this._attachTrack(track, participant);\n },\n )\n .on(\n RoomEvent.TrackUnsubscribed,\n (_track: RemoteTrack, _pub: RemoteTrackPublication, participant: RemoteParticipant) => {\n this._upsertParticipant(participant);\n this._emit();\n },\n )\n .on(RoomEvent.ParticipantConnected, (participant: RemoteParticipant) => {\n this._upsertParticipant(participant);\n this._emit();\n })\n .on(RoomEvent.ParticipantDisconnected, (participant: RemoteParticipant) => {\n this._participants.delete(participant.identity);\n this._emit();\n })\n .on(RoomEvent.ActiveSpeakersChanged, (speakers) => {\n for (const [id, entry] of this._participants) {\n entry.isSpeaking = speakers.some((s) => s.identity === id);\n }\n this._emit();\n })\n .on(RoomEvent.Disconnected, () => {\n this._setState('ended');\n });\n }\n}\n\n// ─── Facade ────────────────────────────────────────────────────────────────\n\nexport class PalbeCalls {\n private readonly rt: PalbeRuntime;\n\n constructor(rt: PalbeRuntime) {\n this.rt = rt;\n }\n\n /**\n * Start a new call in a messaging group.\n *\n * @param groupId - Messaging group display-id (`grp_<uuidv7>`)\n * @param media - Which media tracks to publish (default: audio + video)\n * @returns A `Call` in `connecting` state; transitions to `active` once the media room connects.\n *\n * Every failure below arrives as kind `'server'` — this SDK's error kinds are\n * `notConfigured | validation | unauthorized | rateLimited | server | network\n * | decode`, and only a 401, a 429 or a 400 carrying a field-error array gets\n * its own kind. So branch on `err.code`, never on the kind.\n *\n * @throws BackendError('server', { code: 'calling_unavailable' }) no SFU configured on this stack (503)\n * @throws BackendError('server', { code: 'not_group_member' }) the caller is not a member of the group (403)\n * @throws BackendError('server', { code: 'call_room_full' }) the call is at its participant limit (409)\n * @throws BackendError('server', { code: 'call_provider_error' }) the media provider failed upstream (502)\n */\n async start(\n groupId: string,\n { media = ['audio', 'video'] }: { media?: Array<'audio' | 'video'> } = {},\n ): Promise<Call> {\n if (!groupId) {\n throw new BackendError('validation', {\n code: 'invalid_group_id',\n message: 'groupId must be a non-empty string',\n });\n }\n const res = await palbeRequest<StartCallResponse>(\n this.rt,\n 'POST',\n `/v1/messaging/groups/${encodeURIComponent(groupId)}/calls`,\n { body: { media } },\n );\n return this._connect(res.call_id, res.token, res.edge_url, media);\n }\n\n /**\n * Accept an incoming call in a messaging group.\n *\n * @param groupId - Messaging group display-id\n * @param callId - Call identifier, carried by the `call_invite` the server\n * fans onto the group's conversation topic\n * (`pb.realtime.channel('messaging:conv:<rfcGroupId>').on('call_invite', …)`).\n * This SDK ships no web push: on the web the invite arrives over realtime or\n * over whatever signalling the app already runs.\n * @param media - Which media tracks to publish (default: audio + video)\n */\n async accept(\n groupId: string,\n callId: string,\n { media = ['audio', 'video'] }: { media?: Array<'audio' | 'video'> } = {},\n ): Promise<Call> {\n const res = await palbeRequest<AcceptCallResponse>(\n this.rt,\n 'POST',\n `/v1/messaging/groups/${encodeURIComponent(groupId)}/calls/${encodeURIComponent(callId)}/accept`,\n { body: {} },\n );\n return this._connect(callId, res.token, res.edge_url, media);\n }\n\n /**\n * Decline an incoming call.\n *\n * @param groupId - Messaging group display-id\n * @param callId - Call identifier\n * @param reason - Optional decline reason string\n */\n async decline(groupId: string, callId: string, reason?: string): Promise<void> {\n await palbeRequest<void>(\n this.rt,\n 'POST',\n `/v1/messaging/groups/${encodeURIComponent(groupId)}/calls/${encodeURIComponent(callId)}/decline`,\n { body: { reason: reason ?? '' } },\n );\n }\n\n // ─── Internal ─────────────────────────────────────────────────────────\n\n private async _connect(\n callId: string,\n token: string,\n edgeUrl: string,\n media: Array<'audio' | 'video'>,\n ): Promise<Call> {\n // ExternalE2EEKeyProvider is the MLS seam (see module docstring).\n // Constructed but NOT passed to Room options — we connect without e2ee.\n // TODO(SP-1-web): pass { e2ee: { worker, keyProvider } } when MLS is ready.\n const keyProvider = new ExternalE2EEKeyProvider();\n\n const room = new Room({\n adaptiveStream: true,\n dynacast: true,\n });\n\n const call = Call._create(callId, room, keyProvider);\n\n // Connect first; track publishing follows after Connected fires.\n // LiveKit requires the room to be connected before publishing.\n await room.connect(edgeUrl, token);\n\n if (media.includes('audio')) {\n await room.localParticipant.setMicrophoneEnabled(true);\n }\n if (media.includes('video')) {\n await room.localParticipant.setCameraEnabled(true);\n }\n\n return call;\n }\n}\n","import type { HttpClient, PalbaseResponse } from '@palbase/core';\nimport type { FlagValue } from './types.js';\n\n/**\n * Admin client for the user-flags module — wraps the system flag CRUD\n * and per-user override endpoints exposed by `modules/user-flags`.\n *\n * Two surfaces:\n * - `.system.*` — project-wide flag defaults (platform auth)\n * - `.users.*` — per-user overrides (service-side)\n */\nexport class FlagsAdminClient {\n readonly system: SystemFlagsAdminClient;\n readonly users: UserFlagsAdminClient;\n\n constructor(httpClient: HttpClient) {\n this.system = new SystemFlagsAdminClient(httpClient);\n this.users = new UserFlagsAdminClient(httpClient);\n }\n}\n\nexport interface SystemFlag {\n key: string;\n value: FlagValue;\n /** Optional variant label when the flag is variant-typed. */\n variant?: string;\n /** Optional targeting rules (shape passes through from the backend). */\n rules?: unknown;\n updated_at?: string;\n}\n\nexport interface UserFlagOverride {\n user_id: string;\n key: string;\n value: FlagValue;\n variant?: string;\n updated_at?: string;\n}\n\nexport interface UserFlagsSnapshot {\n user_id: string;\n flags: Record<string, FlagValue>;\n}\n\nexport interface BatchFlagInput {\n /** Optional: evaluate for these user IDs. */\n user_ids?: string[];\n /** Optional: evaluate a subset of flag keys. */\n keys?: string[];\n}\n\nexport interface BatchFlagResult {\n user_id: string;\n flags: Record<string, FlagValue>;\n}\n\nexport class SystemFlagsAdminClient {\n constructor(private readonly http: HttpClient) {}\n\n list(): Promise<PalbaseResponse<SystemFlag[]>> {\n return this.http.request<SystemFlag[]>('GET', '/v1/user-flags/system');\n }\n\n get(key: string): Promise<PalbaseResponse<SystemFlag>> {\n return this.http.request<SystemFlag>('GET', `/v1/user-flags/system/${encodeURIComponent(key)}`);\n }\n\n put(\n key: string,\n input: { value: FlagValue; variant?: string; rules?: unknown },\n ): Promise<PalbaseResponse<SystemFlag>> {\n return this.http.request<SystemFlag>(\n 'PUT',\n `/v1/user-flags/system/${encodeURIComponent(key)}`,\n { body: input },\n );\n }\n\n delete(key: string): Promise<PalbaseResponse<void>> {\n return this.http.request<void>('DELETE', `/v1/user-flags/system/${encodeURIComponent(key)}`);\n }\n}\n\nexport class UserFlagsAdminClient {\n constructor(private readonly http: HttpClient) {}\n\n /** Read evaluated flags for a user (defaults + overrides merged). */\n get(userId: string): Promise<PalbaseResponse<UserFlagsSnapshot>> {\n return this.http.request<UserFlagsSnapshot>(\n 'GET',\n `/v1/user-flags/users/${encodeURIComponent(userId)}`,\n );\n }\n\n /**\n * Replace the full set of overrides for a user in one call. Any key not\n * present in `overrides` is removed.\n */\n putBulk(\n userId: string,\n overrides: Record<string, FlagValue>,\n ): Promise<PalbaseResponse<UserFlagsSnapshot>> {\n return this.http.request<UserFlagsSnapshot>(\n 'PUT',\n `/v1/user-flags/users/${encodeURIComponent(userId)}`,\n { body: { overrides } },\n );\n }\n\n putOverride(\n userId: string,\n key: string,\n input: { value: FlagValue; variant?: string },\n ): Promise<PalbaseResponse<UserFlagOverride>> {\n return this.http.request<UserFlagOverride>(\n 'PUT',\n `/v1/user-flags/users/${encodeURIComponent(userId)}/${encodeURIComponent(key)}`,\n { body: input },\n );\n }\n\n deleteOverride(userId: string, key: string): Promise<PalbaseResponse<void>> {\n return this.http.request<void>(\n 'DELETE',\n `/v1/user-flags/users/${encodeURIComponent(userId)}/${encodeURIComponent(key)}`,\n );\n }\n\n deleteAllOverrides(userId: string): Promise<PalbaseResponse<void>> {\n return this.http.request<void>('DELETE', `/v1/user-flags/users/${encodeURIComponent(userId)}`);\n }\n\n /** Evaluate flags for a batch of users / a subset of keys. */\n batch(input?: BatchFlagInput): Promise<PalbaseResponse<BatchFlagResult[]>> {\n return this.http.request<BatchFlagResult[]>('POST', '/v1/user-flags/batch', {\n body: input ?? {},\n });\n }\n}\n","import type { HttpClient, PalbaseResponse } from '@palbase/core';\nimport type {\n Flag,\n FlagContext,\n FlagSource,\n FlagsDelta,\n FlagsSnapshot,\n FlagValue,\n FlagVariant,\n} from './types.js';\n\nconst FLAG_NAME_RE = /^[a-zA-Z0-9_-]+$/;\n\n/**\n * Wire shape of the merged read endpoints `GET /v1/user-flags` (current auth\n * user) and `GET /v1/user-flags/users/{userId}` (a named user). Both return the\n * server-resolved flag values (system defaults + that user's overrides).\n */\ninterface MergedSnapshotResponse {\n values: Record<string, FlagValue> | null;\n fetched_at: string;\n}\n\n/** Coerce a resolved flag value to an enabled boolean. A boolean flag is itself;\n * any other present, non-empty, non-zero value counts as enabled; missing = off. */\nfunction flagEnabled(value: FlagValue | undefined): boolean {\n if (typeof value === 'boolean') return value;\n return value != null && value !== 0 && value !== '';\n}\n\n/** Derive a {@link FlagVariant} from a resolved flag value. A \"variant\" is the\n * value of a multivariate (string) flag, so a string flag's value IS its variant\n * name. Boolean / numeric / object flags and missing flags have no variant →\n * `null` (so a caller's `getVariant` resolves to null, per the \"no variant\"\n * contract). */\nfunction flagVariant(value: FlagValue | undefined): FlagVariant | null {\n return typeof value === 'string' ? { name: value } : null;\n}\n\n/** Re-map the `data` of a successful response; pass errors / empty bodies through\n * unchanged (typed to the new payload). */\nfunction mapResponse<A, B>(res: PalbaseResponse<A>, fn: (data: A) => B): PalbaseResponse<B> {\n if (res.error || res.data == null) {\n return { ...res, data: null } as PalbaseResponse<B>;\n }\n return { ...res, data: fn(res.data) };\n}\n\n/**\n * Result of writing a single user override (`PUT .../{userId}/{key}`) — echoes\n * the resolved key/value with `source: \"override\"`.\n */\nexport interface SetOverrideResult {\n key: string;\n value: FlagValue;\n source: FlagSource;\n}\n\n/**\n * Result of replacing all overrides for one user (`PUT .../{userId}`).\n * `values` is the resolved override set after the write.\n */\nexport interface SetOverridesResult {\n values: Record<string, FlagValue>;\n}\n\n/**\n * Result of clearing a single override (`DELETE .../{userId}/{key}`). `value`\n * is the system (default) value the user falls back to after removal, with\n * `source: \"system\"`.\n */\nexport interface ClearOverrideResult {\n key: string;\n value: FlagValue;\n source: FlagSource;\n}\n\n/** Result of clearing all overrides for a user (`DELETE .../{userId}`). */\nexport interface ClearAllOverridesResult {\n deleted: number;\n}\n\n/** One cross-user override operation for {@link FlagsServiceClient.batchSetOverrides}. */\nexport interface BatchOverrideOperation {\n userId: string;\n values: Record<string, FlagValue>;\n}\n\n/** Result of a cross-user batch write (`POST /v1/user-flags/batch`). */\nexport interface BatchSetOverridesResult {\n applied: number;\n}\n\n/**\n * Cross-user admin write surface, reached via {@link FlagsClient.asService}.\n *\n * These five methods set/clear overrides for an ARBITRARY user (named\n * explicitly), so they bypass the current-user scope that the default\n * `FlagsClient.setOverride(...)` is bound to. Mirrors the\n * `Database` / `Database.asService()` split: the privileged cross-user path is\n * moved OFF the default surface so it is greppable + intentional. Requires a\n * privileged key (service_role / managed runtime).\n */\nexport interface FlagsServiceClient {\n /**\n * Set (or replace) a single feature-flag override for one user.\n * `PUT /v1/user-flags/users/{userId}/{key}` with body `{ value }`.\n */\n setOverrideForUser(\n userId: string,\n key: string,\n value: FlagValue,\n ): Promise<PalbaseResponse<SetOverrideResult>>;\n\n /**\n * Set multiple feature-flag overrides for one user in a single call.\n * `PUT /v1/user-flags/users/{userId}` with body `{ values }`.\n */\n setOverridesForUser(\n userId: string,\n values: Record<string, FlagValue>,\n ): Promise<PalbaseResponse<SetOverridesResult>>;\n\n /**\n * Clear a single override for one user, restoring the project (system)\n * default for that key. `DELETE /v1/user-flags/users/{userId}/{key}`.\n */\n clearOverrideForUser(userId: string, key: string): Promise<PalbaseResponse<ClearOverrideResult>>;\n\n /**\n * Clear all overrides for one user, restoring project defaults for every\n * key. `DELETE /v1/user-flags/users/{userId}`.\n */\n clearAllOverridesForUser(userId: string): Promise<PalbaseResponse<ClearAllOverridesResult>>;\n\n /**\n * Apply override writes across many users in one request (max 1000 ops).\n * `POST /v1/user-flags/batch` with body `{ operations: [{ user_id, values }] }`.\n */\n batchSetOverrides(\n operations: ReadonlyArray<BatchOverrideOperation>,\n ): Promise<PalbaseResponse<BatchSetOverridesResult>>;\n}\n\n/**\n * Stateless, one-shot HTTP wrapper for the flags API. Reads (`isEnabled` /\n * `getVariant` / `getAll`) resolve against the live `GET /v1/user-flags` merged\n * endpoint and derive their result from the returned value map; it is also the\n * transport layer underneath `FlagsPool` (which adds caching, polling and\n * auth-state binding on top).\n *\n * Writes mirror the `Database` / `Database.asService()` model:\n * — {@link setOverride} (default) writes an override for the CURRENT user (the\n * `getCurrentUserId` getter supplied at construction). No userId argument; it\n * errors clearly when there is no signed-in user.\n * — {@link asService} returns the cross-user admin surface\n * ({@link FlagsServiceClient}) for writing overrides for an arbitrary user.\n */\nexport class FlagsClient {\n private readonly httpClient: HttpClient;\n private readonly getCurrentUserId?: () => string | null | undefined;\n\n /**\n * @param httpClient transport.\n * @param getCurrentUserId optional getter resolving the signed-in user's id\n * for {@link setOverride}. When omitted (or it returns no id), `setOverride`\n * errors and the caller must use {@link asService} for cross-user writes.\n */\n constructor(httpClient: HttpClient, getCurrentUserId?: () => string | null | undefined) {\n this.httpClient = httpClient;\n this.getCurrentUserId = getCurrentUserId;\n }\n\n async isEnabled(flagName: string, context?: FlagContext): Promise<PalbaseResponse<boolean>> {\n if (!FLAG_NAME_RE.test(flagName)) {\n throw new Error(\n `Invalid flag name: \"${flagName}\". Flag names must match ${FLAG_NAME_RE.source}`,\n );\n }\n\n const res = await this.httpClient.request<MergedSnapshotResponse>(\n 'GET',\n this.mergedPath(context),\n );\n return mapResponse(res, (snap) => flagEnabled(snap.values?.[flagName]));\n }\n\n async getVariant(flagName: string, context?: FlagContext): Promise<PalbaseResponse<FlagVariant>> {\n if (!FLAG_NAME_RE.test(flagName)) {\n throw new Error(\n `Invalid flag name: \"${flagName}\". Flag names must match ${FLAG_NAME_RE.source}`,\n );\n }\n\n const res = await this.httpClient.request<MergedSnapshotResponse>(\n 'GET',\n this.mergedPath(context),\n );\n // Inlined (not via mapResponse) so the `FlagVariant | null` payload stays\n // assignable to the public `PalbaseResponse<FlagVariant>` return type.\n if (res.error || res.data == null) {\n return { ...res, data: null };\n }\n return { ...res, data: flagVariant(res.data.values?.[flagName]) };\n }\n\n async getAll(context?: FlagContext): Promise<PalbaseResponse<Flag[]>> {\n const res = await this.httpClient.request<MergedSnapshotResponse>(\n 'GET',\n this.mergedPath(context),\n );\n return mapResponse(res, (snap) => {\n const values = snap.values ?? {};\n return Object.keys(values).map((name) => {\n const value = values[name];\n const flag: Flag = { name, enabled: flagEnabled(value) };\n if (typeof value === 'string') flag.variant = { name: value };\n return flag;\n });\n });\n }\n\n /**\n * Cold-start read: the full merged flag set for the current auth identity.\n * With a Bearer token the backend returns the user-merged view; without one\n * it returns project (system) defaults.\n */\n snapshot(): Promise<PalbaseResponse<FlagsSnapshot>> {\n return this.httpClient.request<FlagsSnapshot>('GET', '/v1/user-flags/snapshot');\n }\n\n /**\n * Incremental read of version-ordered ops since `sinceVersion`.\n *\n * Sends `If-None-Match: <sinceVersion>` so an unchanged server can answer\n * `304 Not Modified` cheaply. The 304 surfaces as a `PalbaseResponse` with\n * `status === 304` (the core http client maps non-2xx to an error/empty body);\n * callers treat that as \"no change\".\n */\n delta(sinceVersion: string): Promise<PalbaseResponse<FlagsDelta>> {\n return this.httpClient.request<FlagsDelta>(\n 'GET',\n `/v1/user-flags/delta?since=${encodeURIComponent(sinceVersion)}`,\n { headers: { 'If-None-Match': sinceVersion } },\n );\n }\n\n /**\n * Set (or replace) a single feature-flag override for the CURRENT USER.\n *\n * Resolves the signed-in user from the `getCurrentUserId` getter supplied at\n * construction and issues `PUT /v1/user-flags/users/{uid}/{key}` with body\n * `{ value }`. No userId argument — the override is bound to the current\n * user, so no admin power is required.\n *\n * Errors when there is no signed-in user (anonymous, or no getter supplied);\n * use {@link asService}`.setOverrideForUser(userId, key, value)` to write a\n * flag override for an arbitrary (cross-user) target.\n */\n setOverride(key: string, value: FlagValue): Promise<PalbaseResponse<SetOverrideResult>> {\n const userId = this.getCurrentUserId?.();\n if (!userId) {\n throw new Error(\n 'setOverride requires a signed-in user; use FlagsClient.asService().setOverrideForUser(userId, key, value) for cross-user writes',\n );\n }\n return this.httpClient.request<SetOverrideResult>(\n 'PUT',\n `/v1/user-flags/users/${encodeURIComponent(userId)}/${encodeURIComponent(key)}`,\n { body: { value } },\n );\n }\n\n /**\n * Return the cross-user admin write surface ({@link FlagsServiceClient}).\n *\n * Use sparingly and explicitly — the default {@link setOverride} path is\n * bound to the current user; `asService()` is how you write a flag override\n * for an ARBITRARY user. Mirrors `Database.asService()`. The returned client\n * shares this client's transport (the privileged service_role key).\n */\n asService(): FlagsServiceClient {\n const http = this.httpClient;\n return {\n setOverrideForUser(userId, key, value) {\n return http.request<SetOverrideResult>(\n 'PUT',\n `/v1/user-flags/users/${encodeURIComponent(userId)}/${encodeURIComponent(key)}`,\n { body: { value } },\n );\n },\n setOverridesForUser(userId, values) {\n return http.request<SetOverridesResult>(\n 'PUT',\n `/v1/user-flags/users/${encodeURIComponent(userId)}`,\n { body: { values } },\n );\n },\n clearOverrideForUser(userId, key) {\n return http.request<ClearOverrideResult>(\n 'DELETE',\n `/v1/user-flags/users/${encodeURIComponent(userId)}/${encodeURIComponent(key)}`,\n );\n },\n clearAllOverridesForUser(userId) {\n return http.request<ClearAllOverridesResult>(\n 'DELETE',\n `/v1/user-flags/users/${encodeURIComponent(userId)}`,\n );\n },\n batchSetOverrides(operations) {\n return http.request<BatchSetOverridesResult>('POST', '/v1/user-flags/batch', {\n body: {\n operations: operations.map((op) => ({\n user_id: op.userId,\n values: op.values,\n })),\n },\n });\n },\n };\n }\n\n /**\n * Resolve the merged-read path for a context. With `context.userId` present the\n * read targets that specific user's merged view (`/v1/user-flags/users/{id}`, a\n * privileged cross-user read); otherwise the current auth user's view\n * (`/v1/user-flags`). `context.properties` has no server-side targeting support\n * in the user-flags module and is ignored.\n */\n private mergedPath(context?: FlagContext): string {\n const uid = context?.userId;\n return uid ? `/v1/user-flags/users/${encodeURIComponent(uid)}` : '/v1/user-flags';\n }\n}\n","import type { FlagsClient } from './flags-client.js';\nimport type {\n AuthStateSource,\n FlagSource,\n FlagsChangeListener,\n FlagsSnapshot,\n FlagValue,\n Unsubscribe,\n} from './types.js';\n\n/** Options controlling pool behaviour. All have sensible defaults. */\nexport interface FlagsPoolOptions {\n /** Delta poll interval in ms. Default 30_000. Set `0` to disable polling. */\n pollIntervalMs?: number;\n /**\n * localStorage key for cold-start persistence. Default\n * `palbase.flags.snapshot`. Ignored when localStorage is unavailable (SSR).\n */\n storageKey?: string;\n /** Disable localStorage persistence even in the browser. Default false. */\n persist?: boolean;\n /**\n * Auth-state source. When provided, the pool re-snapshots on sign-in and\n * sign-out so the cache switches between user overrides and project defaults.\n */\n auth?: AuthStateSource;\n /**\n * Inject timer/visibility primitives for testing. Defaults to the global\n * `setInterval`/`clearInterval` and the browser `document`/`window`.\n */\n env?: PoolEnv;\n}\n\n/** Injectable runtime seams (browser globals) so the pool stays testable. */\nexport interface PoolEnv {\n now(): number;\n setInterval(handler: () => void, ms: number): IntervalHandle;\n clearInterval(handle: IntervalHandle): void;\n /** localStorage-like store, or `null` when unavailable (SSR). */\n storage: KeyValueStore | null;\n /** Visibility source, or `null` when there is no document (SSR). */\n visibility: VisibilitySource | null;\n}\n\nexport type IntervalHandle = ReturnType<typeof setInterval>;\n\nexport interface KeyValueStore {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\nexport interface VisibilitySource {\n isHidden(): boolean;\n onVisibilityChange(cb: () => void): Unsubscribe;\n}\n\nconst DEFAULT_POLL_MS = 30_000;\nconst DEFAULT_STORAGE_KEY = 'palbase.flags.snapshot';\n\ninterface CacheState {\n values: Record<string, FlagValue>;\n sources: Record<string, FlagSource>;\n syncVersion: string | null;\n}\n\nconst EMPTY_STATE: CacheState = {\n values: {},\n sources: {},\n syncVersion: null,\n};\n\n/**\n * Stateful, auto-pooling flag cache built on top of the stateless\n * {@link FlagsClient} transport.\n *\n * Responsibilities:\n * - Cold start from a snapshot (and from localStorage for instant boot).\n * - Poll deltas with conditional `If-None-Match` (304 → no-op).\n * - Re-snapshot on a version gap / tombstone-expiry signalled by the backend.\n * - Re-snapshot on auth sign-in / sign-out (override view ↔ defaults).\n * - Notify subscribers (drives `useFlag`/`useFlags`).\n *\n * Resolution is server-side: `get(key, default)` returns the cached value or\n * the supplied default; the pool never merges.\n */\nexport class FlagsPool {\n private readonly transport: FlagsClient;\n private readonly pollIntervalMs: number;\n private readonly storageKey: string;\n private readonly persist: boolean;\n private readonly env: PoolEnv;\n\n private state: CacheState = EMPTY_STATE;\n private readonly listeners = new Set<FlagsChangeListener>();\n\n private timer: IntervalHandle | null = null;\n private polling = false;\n private started = false;\n private destroyed = false;\n\n private readyPromise: Promise<void>;\n private resolveReady: (() => void) | null = null;\n private firstLoadDone = false;\n\n private authUnsub: Unsubscribe | null = null;\n private visibilityUnsub: Unsubscribe | null = null;\n private lastAuthHasSession: boolean | null = null;\n\n /** Cached frozen view for `getSnapshot` identity stability (React). */\n private frozenView: Readonly<Record<string, FlagValue>> = Object.freeze({});\n private viewDirty = true;\n\n constructor(transport: FlagsClient, options: FlagsPoolOptions = {}) {\n this.transport = transport;\n this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_MS;\n this.storageKey = options.storageKey ?? DEFAULT_STORAGE_KEY;\n this.persist = options.persist ?? true;\n this.env = options.env ?? defaultEnv();\n\n this.readyPromise = new Promise<void>((resolve) => {\n this.resolveReady = resolve;\n });\n\n // Hydrate synchronously from persisted snapshot for instant cold-start.\n this.hydrateFromStorage();\n\n if (options.auth) {\n this.bindAuth(options.auth);\n }\n }\n\n // ---- public surface -----------------------------------------------------\n\n /** Start cold-start fetch + polling + visibility binding. Idempotent. */\n start(): void {\n if (this.started || this.destroyed) {\n return;\n }\n this.started = true;\n\n void this.refresh();\n this.startPolling();\n this.bindVisibility();\n }\n\n /** Resolves once the first snapshot (or persisted hydrate) is available. */\n ready(): Promise<void> {\n if (this.firstLoadDone) {\n return Promise.resolve();\n }\n if (!this.started) {\n this.start();\n }\n return this.readyPromise;\n }\n\n /** Returns the cached value for `key`, or `fallback` when absent. */\n get<T extends FlagValue>(key: string, fallback: T): T {\n const value = this.state.values[key];\n return value === undefined ? fallback : (value as T);\n }\n\n /** Boolean convenience: cached value strictly equals `true`, else `fallback`. */\n isEnabled(key: string, fallback = false): boolean {\n const value = this.state.values[key];\n if (value === undefined) {\n return fallback;\n }\n return value === true;\n }\n\n /** Returns the cached value's source (`system`/`user`) when known. */\n getSource(key: string): FlagSource | undefined {\n return this.state.sources[key];\n }\n\n /** Returns a frozen snapshot of all cached values (stable identity). */\n all(): Readonly<Record<string, FlagValue>> {\n if (this.viewDirty) {\n this.frozenView = Object.freeze({ ...this.state.values });\n this.viewDirty = false;\n }\n return this.frozenView;\n }\n\n /** Current opaque sync version (null before first load). */\n get syncVersion(): string | null {\n return this.state.syncVersion;\n }\n\n /** Subscribe to any change in the cached set. Returns unsubscribe. */\n onChange(listener: FlagsChangeListener): Unsubscribe {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /**\n * `useSyncExternalStore`-compatible subscribe. Auto-starts the pool on first\n * subscription so a React component mount triggers cold-start.\n */\n subscribe = (listener: FlagsChangeListener): Unsubscribe => {\n if (!this.started) {\n this.start();\n }\n return this.onChange(listener);\n };\n\n /** Force an immediate re-snapshot (used on auth change / manual refresh). */\n async refresh(): Promise<void> {\n if (this.destroyed) {\n return;\n }\n const res = await this.transport.snapshot();\n if (res.error || res.data == null) {\n // Keep any persisted/previous state; still mark first load complete so\n // `ready()` does not hang forever on a transient network failure.\n this.markFirstLoadDone();\n return;\n }\n this.applySnapshot(res.data);\n this.markFirstLoadDone();\n }\n\n /** Stop timers and detach all auth/visibility listeners. */\n destroy(): void {\n this.destroyed = true;\n this.stopPolling();\n this.authUnsub?.();\n this.authUnsub = null;\n this.visibilityUnsub?.();\n this.visibilityUnsub = null;\n this.listeners.clear();\n }\n\n // ---- internals ----------------------------------------------------------\n\n private async poll(): Promise<void> {\n if (this.polling || this.destroyed) {\n return;\n }\n const since = this.state.syncVersion;\n if (since == null) {\n // No baseline yet — fall back to a snapshot.\n await this.refresh();\n return;\n }\n this.polling = true;\n try {\n const res = await this.transport.delta(since);\n if (res.status === 304) {\n return; // not modified — cheap no-op\n }\n if (res.error || res.data == null) {\n // A version gap / expired tombstone window is signalled by 409/410.\n if (res.status === 409 || res.status === 410) {\n await this.refresh();\n }\n return;\n }\n this.applyDelta(res.data.ops as readonly unknown[], res.data.sync_version);\n } finally {\n this.polling = false;\n }\n }\n\n private applySnapshot(snapshot: FlagsSnapshot): void {\n this.state = {\n values: { ...snapshot.values },\n sources: { ...(snapshot.sources ?? {}) },\n syncVersion: snapshot.sync_version,\n };\n this.viewDirty = true;\n this.persistState();\n this.notify();\n }\n\n private applyDelta(ops: readonly unknown[], nextVersion: string): void {\n // Each op is re-narrowed defensively via `isDeltaOp`.\n const values = { ...this.state.values };\n const sources = { ...this.state.sources };\n let changed = false;\n\n for (const raw of ops) {\n if (!isDeltaOp(raw)) {\n continue;\n }\n if (raw.op === 'delete') {\n if (raw.key in values) {\n delete values[raw.key];\n delete sources[raw.key];\n changed = true;\n }\n } else {\n values[raw.key] = raw.value;\n if (raw.source) {\n sources[raw.key] = raw.source;\n }\n changed = true;\n }\n }\n\n this.state = { values, sources, syncVersion: nextVersion };\n if (changed) {\n this.viewDirty = true;\n this.persistState();\n this.notify();\n } else {\n // version still advances even with no value change\n this.persistState();\n }\n }\n\n private startPolling(): void {\n if (this.pollIntervalMs <= 0 || this.timer != null) {\n return;\n }\n this.timer = this.env.setInterval(() => {\n void this.poll();\n }, this.pollIntervalMs);\n }\n\n private stopPolling(): void {\n if (this.timer != null) {\n this.env.clearInterval(this.timer);\n this.timer = null;\n }\n }\n\n private bindVisibility(): void {\n const vis = this.env.visibility;\n if (!vis) {\n return;\n }\n this.visibilityUnsub = vis.onVisibilityChange(() => {\n if (vis.isHidden()) {\n this.stopPolling();\n } else {\n // Came back to foreground: catch up immediately, then resume polling.\n void this.poll();\n this.startPolling();\n }\n });\n }\n\n private bindAuth(auth: AuthStateSource): void {\n const handle = auth.onAuthStateChange((_event, session) => {\n const hasSession = session != null;\n // Fires immediately with current state on subscribe; only re-snapshot\n // when the identity actually flips (sign-in ↔ sign-out), not on every\n // token refresh, to avoid redundant cold-starts.\n if (this.lastAuthHasSession === null) {\n this.lastAuthHasSession = hasSession;\n return;\n }\n if (hasSession !== this.lastAuthHasSession) {\n this.lastAuthHasSession = hasSession;\n if (this.started) {\n void this.refresh();\n }\n }\n });\n // Normalize the Supabase-style handle to a plain unsubscribe.\n this.authUnsub = () => handle.data.subscription.unsubscribe();\n }\n\n private notify(): void {\n for (const listener of this.listeners) {\n listener();\n }\n }\n\n private markFirstLoadDone(): void {\n if (!this.firstLoadDone) {\n this.firstLoadDone = true;\n this.resolveReady?.();\n this.resolveReady = null;\n }\n }\n\n private hydrateFromStorage(): void {\n if (!this.persist) {\n return;\n }\n const store = this.env.storage;\n if (!store) {\n return;\n }\n const raw = store.getItem(this.storageKey);\n if (!raw) {\n return;\n }\n const parsed = parsePersisted(raw);\n if (!parsed) {\n return;\n }\n this.state = parsed;\n this.viewDirty = true;\n // A persisted snapshot is enough to satisfy `ready()` instantly; a live\n // `refresh()` will still run on `start()` to revalidate.\n this.markFirstLoadDone();\n }\n\n private persistState(): void {\n if (!this.persist) {\n return;\n }\n const store = this.env.storage;\n if (!store || this.state.syncVersion == null) {\n return;\n }\n try {\n store.setItem(this.storageKey, JSON.stringify(this.state));\n } catch {\n // Quota / private-mode — persistence is best-effort.\n }\n }\n}\n\n// ---- guards / helpers -----------------------------------------------------\n\nfunction isDeltaOp(\n value: unknown,\n): value is\n | { op: 'put'; key: string; value: FlagValue; source?: FlagSource }\n | { op: 'delete'; key: string } {\n if (typeof value !== 'object' || value === null) {\n return false;\n }\n const rec = value as Record<string, unknown>;\n if (typeof rec.key !== 'string') {\n return false;\n }\n if (rec.op === 'delete') {\n return true;\n }\n return rec.op === 'put' && 'value' in rec;\n}\n\nfunction parsePersisted(raw: string): CacheState | null {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return null;\n }\n if (typeof parsed !== 'object' || parsed === null) {\n return null;\n }\n const rec = parsed as Record<string, unknown>;\n if (typeof rec.syncVersion !== 'string') {\n return null;\n }\n if (typeof rec.values !== 'object' || rec.values === null) {\n return null;\n }\n const sources =\n typeof rec.sources === 'object' && rec.sources !== null\n ? (rec.sources as Record<string, FlagSource>)\n : {};\n return {\n values: rec.values as Record<string, FlagValue>,\n sources,\n syncVersion: rec.syncVersion,\n };\n}\n\n/** Build the default browser/Node runtime seam, guarding all DOM globals. */\nfunction defaultEnv(): PoolEnv {\n const storage = resolveStorage();\n const visibility = resolveVisibility();\n return {\n now: () => Date.now(),\n setInterval: (handler, ms) => setInterval(handler, ms),\n clearInterval: (handle) => clearInterval(handle),\n storage,\n visibility,\n };\n}\n\nfunction resolveStorage(): KeyValueStore | null {\n try {\n if (typeof localStorage === 'undefined') {\n return null;\n }\n // Touch it to surface private-mode/security errors up front.\n const probe = '__palbase_flags_probe__';\n localStorage.setItem(probe, '1');\n localStorage.removeItem(probe);\n return localStorage;\n } catch {\n return null;\n }\n}\n\nfunction resolveVisibility(): VisibilitySource | null {\n if (typeof document === 'undefined') {\n return null;\n }\n const doc = document;\n return {\n isHidden: () => doc.visibilityState === 'hidden',\n onVisibilityChange: (cb) => {\n doc.addEventListener('visibilitychange', cb);\n return () => doc.removeEventListener('visibilitychange', cb);\n },\n };\n}\n","import type { PalbaseResponse } from '@palbase/core';\nimport {\n type AuthStateSource,\n FlagsClient,\n FlagsPool,\n type FlagsPoolOptions,\n type FlagValue,\n type FlagVariant,\n type PoolEnv,\n} from '@palbase/flags';\nimport type { PalbeAuth, Unsubscribe } from './auth-facade.js';\nimport { BackendError } from './errors.js';\nimport type { PalbeRuntime } from './runtime.js';\n\nexport type { FlagValue } from '@palbase/flags';\n\n/** Frozen view of all cached flag values (what `all()` returns / `changes()` yields). */\nexport type FlagsView = Readonly<Record<string, FlagValue>>;\n\n/**\n * Adapt PalbeAuth's `(state: AuthState) => void` listener contract to the\n * Supabase-style `AuthStateSource` FlagsPool binds to (`(event, session)`\n * callback + `{ data: { subscription: { unsubscribe } } }` handle).\n *\n * PalbeAuth fires the callback IMMEDIATELY on subscribe (iOS parity); the\n * pool's bindAuth treats that first call as the baseline (its\n * `lastAuthHasSession === null` branch) and only re-snapshots on a later\n * identity flip — exactly the behaviour we want, no suppression needed.\n */\nexport function palbeAuthAdapter(auth: PalbeAuth): AuthStateSource {\n return {\n onAuthStateChange(callback) {\n const unsubscribe = auth.onAuthStateChange(() => {\n // Token truth, not state.status: a restored session (page reload) has\n // no cachedUser yet, so snapshotState() reports signedOut while\n // isSignedIn is true. Using the token-manager directly ensures the pool\n // baseline matches the actual session presence on the first fire.\n const signedIn = auth.isSignedIn;\n callback(signedIn ? 'SIGNED_IN' : 'SIGNED_OUT', signedIn ? { signedIn } : null);\n });\n return { data: { subscription: { unsubscribe } } };\n },\n };\n}\n\n/**\n * Server-side pool seams: no storage, no visibility, and — because polling is\n * disabled via `pollIntervalMs: 0` — an interval seam that must never be\n * reached. Throwing keeps a future regression loud instead of leaking timers\n * into RSC/route-handler processes.\n */\nfunction serverPoolEnv(): PoolEnv {\n return {\n now: () => Date.now(),\n setInterval: () => {\n throw new Error('palbe flags: polling is disabled server-side');\n },\n clearInterval: () => {},\n storage: null,\n visibility: null,\n };\n}\n\n/** Object.is for primitives; objects/arrays by JSON text. Snapshots are\n * re-frozen NEW references on every re-snapshot, so reference equality would\n * report structurally-identical values as \"changed\". JSON compare is\n * key-order-sensitive — a server that reorders keys without changing values\n * may fire a spurious per-key callback (accepted tradeoff, documented). */\nfunction sameFlagValue(a: FlagValue | undefined, b: FlagValue | undefined): boolean {\n if (Object.is(a, b)) return true;\n if (typeof a === 'object' && a !== null && typeof b === 'object' && b !== null) {\n return JSON.stringify(a) === JSON.stringify(b);\n }\n return false;\n}\n\n/**\n * `pb.flags` — iOS-parity facade over `FlagsPool` (cache + delta polling +\n * auth binding) and `FlagsClient` (stateless transport).\n *\n * Start semantics: the facade is constructed lazily (first `pb.flags` touch).\n * - Browser: the pool starts right here in the constructor — cold snapshot +\n * 30s delta polling + visibility pause. \"First use starts the machinery\"\n * without per-method start checks.\n * - Server (no `document`): NEVER auto-starts. `ready()`/`refresh()` are\n * one-shot snapshot fetches with zero timers and zero storage access, so a\n * pbServer request that reads flags pays exactly one HTTP call.\n *\n * localStorage persistence is scoped per environment ref (`palbe.flags.<ref>`),\n * mirroring the session-storage key convention.\n */\nexport class PalbeFlags {\n private readonly transport: FlagsClient;\n private readonly pool: FlagsPool;\n\n constructor(rt: PalbeRuntime) {\n this.transport = new FlagsClient(rt.http);\n const browser = typeof document !== 'undefined';\n const ref = rt.projectRef;\n const options: FlagsPoolOptions = {\n auth: palbeAuthAdapter(rt.auth),\n ...(ref ? { storageKey: `palbe.flags.${ref}` } : {}),\n ...(browser ? {} : { pollIntervalMs: 0, env: serverPoolEnv() }),\n };\n this.pool = new FlagsPool(this.transport, options);\n if (browser) this.pool.start();\n }\n\n /** Resolves once the first snapshot (or persisted hydrate) is available. Auto-starts the pool. */\n ready(): Promise<void> {\n return this.pool.ready();\n }\n\n /** Force an immediate re-snapshot. */\n refresh(): Promise<void> {\n return this.pool.refresh();\n }\n\n /** Frozen snapshot of all cached values (identity-stable until a change). */\n all(): FlagsView {\n return this.pool.all();\n }\n\n /** Raw cached value for `key`, or `undefined` when not in the cache. */\n get(key: string): FlagValue | undefined {\n return this.pool.all()[key];\n }\n\n /** `true` only when the cached value is strictly `true`; `fallback` when the key is absent. */\n isEnabled(key: string, fallback = false): boolean {\n return this.pool.isEnabled(key, fallback);\n }\n\n /** Alias of {@link isEnabled} (iOS parity). */\n bool(key: string, fallback = false): boolean {\n return this.isEnabled(key, fallback);\n }\n\n /** Cached value when it is a string, else `fallback`. */\n getString(key: string, fallback: string): string {\n const value = this.pool.all()[key];\n return typeof value === 'string' ? value : fallback;\n }\n\n /** Cached value when it is an integer number, else `fallback`. */\n getInt(key: string, fallback: number): number {\n const value = this.pool.all()[key];\n return typeof value === 'number' && Number.isInteger(value) ? value : fallback;\n }\n\n /** Cached value when it is a number (integers included), else `fallback`. */\n getDouble(key: string, fallback: number): number {\n const value = this.pool.all()[key];\n return typeof value === 'number' ? value : fallback;\n }\n\n /**\n * Resolve the multivariate variant for `key` — the variant name, or `null`\n * when the flag has no variant (or the read fails).\n *\n * Wire failures (network errors, 404, etc.) resolve to `null`.\n * Invalid flag names throw `BackendError('validation', { code: 'invalid_flag_name' })`.\n *\n * DEVIATION from iOS sync-cache parity: the platform does not propagate\n * variant metadata into the user-flags snapshot/delta cache, so this is an\n * ASYNC transport read (derived from `GET /v1/user-flags`), not a cache lookup.\n */\n async getVariant(key: string): Promise<string | null> {\n let res: PalbaseResponse<FlagVariant>;\n try {\n res = await this.transport.getVariant(key);\n } catch (err) {\n // FlagsClient throws synchronously for invalid flag names; map to a\n // typed validation error so callers can distinguish \"bad name\" from\n // \"no variant\" (which resolves null).\n throw new BackendError('validation', {\n code: 'invalid_flag_name',\n message: err instanceof Error ? err.message : String(err),\n });\n }\n return res.data?.name ?? null;\n }\n\n /** Subscribe to any change in the cached flag set. */\n onChange(callback: () => void): Unsubscribe {\n return this.pool.onChange(callback);\n }\n\n /**\n * Observe ONE key: fires only when that key's value actually changes\n * (per {@link sameFlagValue} — structural compare for objects), with the\n * new value (`undefined` = deleted). P5 React-hook substrate.\n */\n subscribeKey(key: string, callback: (value: FlagValue | undefined) => void): Unsubscribe {\n let last = this.pool.all()[key];\n return this.pool.onChange(() => {\n const next = this.pool.all()[key];\n if (sameFlagValue(last, next)) return;\n last = next;\n callback(next);\n });\n }\n\n /**\n * Async iteration over flag changes: yields the new {@link all} view on\n * every pool change notification. The listener is detached when the\n * consumer `break`s/`return`s/`throw`s — including while a `next()` is\n * still pending (it resolves `{ done: true }` instead of hanging, which a\n * plain async-generator `finally` would not guarantee).\n */\n changes(): AsyncIterableIterator<FlagsView> {\n const queue: FlagsView[] = [];\n const pending: Array<(result: IteratorResult<FlagsView, undefined>) => void> = [];\n let finished = false;\n const unsubscribe = this.pool.onChange(() => {\n const snapshot = this.pool.all();\n const resolve = pending.shift();\n if (resolve) resolve({ value: snapshot, done: false });\n else queue.push(snapshot);\n });\n const finish = (): void => {\n if (finished) return;\n finished = true;\n unsubscribe();\n while (pending.length > 0) pending.shift()?.({ value: undefined, done: true });\n };\n return {\n next: (): Promise<IteratorResult<FlagsView, undefined>> => {\n if (finished) return Promise.resolve({ value: undefined, done: true });\n const head = queue.shift();\n if (head !== undefined) return Promise.resolve({ value: head, done: false });\n return new Promise((resolve) => {\n pending.push(resolve);\n });\n },\n return: (): Promise<IteratorResult<FlagsView, undefined>> => {\n finish();\n return Promise.resolve({ value: undefined, done: true });\n },\n throw: (error?: unknown): Promise<IteratorResult<FlagsView, undefined>> => {\n finish();\n return Promise.reject(error);\n },\n [Symbol.asyncIterator](): AsyncIterableIterator<FlagsView> {\n return this;\n },\n };\n }\n\n /** Stop polling and detach all listeners (auth + visibility + subscribers). */\n destroy(): void {\n this.pool.destroy();\n }\n}\n","/**\n * Pure deadline math — the web mirror of iOS DeadlineCalculator (Codex-finalized\n * restart conversion rule). Identical algorithm; web uses ms internally and\n * compares against ttlSeconds (converted). Clock readings are passed in so it is\n * fully table-testable and byte-identical with iOS.\n *\n * NOTE on the send-vs-arrival clamp: like iOS T3, this Calculator stays PURE — it\n * consumes an already-resolved anchor + the current clock readings. The\n * `min(sender_send_ts + ttl, first_arrival_monotonic + ttl)` effective-deadline\n * basis is applied at ARM time by the caller (Chat, T10), which picks the anchor's\n * monotonic baseline; the Calculator only converts that anchor into remaining/purge.\n */\nexport interface AnchorTriple {\n mAnchorMs: number; // performance.now() at capture\n wAnchorEpochMs: number; // Date.now() at capture\n bAnchorToken: string; // per-page-session token (changes across reload/reboot)\n}\n\nexport type DeadlineResult = { kind: 'remaining'; seconds: number } | { kind: 'purgeNow' };\n\nexport function remainingSeconds(args: {\n ttlSeconds: number;\n anchor: AnchorTriple;\n nowMonotonicMs: number;\n nowWallEpochMs: number;\n nowBootToken: string;\n}): DeadlineResult {\n const ttl = args.ttlSeconds;\n const wallDeltaSec = (args.nowWallEpochMs - args.anchor.wAnchorEpochMs) / 1000;\n let elapsed: number;\n if (args.nowBootToken === args.anchor.bAnchorToken) {\n const monoDeltaSec = Math.max(0, args.nowMonotonicMs - args.anchor.mAnchorMs) / 1000;\n elapsed = Math.max(monoDeltaSec, wallDeltaSec); // larger-elapsed: backward wall can't extend\n } else {\n elapsed = wallDeltaSec; // reboot: monotonic meaningless → wall-only (best-effort, bounded)\n }\n const remaining = Math.min(ttl, ttl - elapsed); // clamp: never MORE than ttl of life\n return remaining <= 0 ? { kind: 'purgeNow' } : { kind: 'remaining', seconds: remaining };\n}\n\n/** Impure clock seam. The boot token is minted once per page-session via\n * crypto.randomUUID() and persisted (sessionStorage), so it is stable within a\n * boot and changes across a reload/reboot — making the monotonic reading\n * comparable across a restart (same-boot vs reboot conversion). */\nlet cachedBootToken: string | null = null;\nexport const MonotonicClock = {\n nowMs(): number {\n return typeof performance !== 'undefined' ? performance.now() : Date.now();\n },\n nowWallEpochMs(): number {\n return Date.now();\n },\n bootToken(): string {\n if (cachedBootToken !== null) return cachedBootToken;\n try {\n const existing =\n typeof sessionStorage !== 'undefined' ? sessionStorage.getItem('pb_boot_token') : null;\n if (existing) {\n cachedBootToken = existing;\n return existing;\n }\n const fresh = crypto.randomUUID();\n if (typeof sessionStorage !== 'undefined') sessionStorage.setItem('pb_boot_token', fresh);\n cachedBootToken = fresh;\n return fresh;\n } catch {\n cachedBootToken = crypto.randomUUID();\n return cachedBootToken;\n }\n },\n};\n","/**\n * DeleteFold — pure fold of delete-for-everyone tombstones into a monotonic\n * `tombstoned` set.\n *\n * Algorithm mirrors iOS `DeleteFold` (DeleteFold.swift) byte-for-byte in\n * behaviour so the two SDKs compute the IDENTICAL tombstoned set from the same\n * events:\n *\n * - **Dedup first** on `eventClientMsgId` (a re-delivered logical tombstone\n * applies at most once). Dedup is recorded ONLY for events the fold could\n * actually evaluate (target present, or parked in pending) — an unverifiable\n * event (unresolved sender AND absent target) is held by its own id without\n * being marked seen, so it can be re-attempted when more resolves.\n * - **Author-gate (the #1 security property):** a tombstone scrubs a target\n * ONLY when its resolved authenticated sender (`actorUserId`) equals the\n * TARGET message's author (`authorOfTarget(target)`). A non-author tombstone\n * is a forgery → IGNORED. This gate is enforced IDENTICALLY on the in-order\n * path AND the deferred/pending path — there is NO code path that tombstones\n * without passing it.\n * - **ABSORBING / sticky (NOT LWW):** once a target is in `tombstoned` it is\n * NEVER removed. No later event, no higher seq, no reevaluate can\n * un-tombstone it. (Eviction within the retention window is the STORE's job,\n * not the fold's.)\n * - **Pending (dangling target):** a valid-shaped tombstone whose target isn't\n * local yet is parked in `pending` CARRYING its authenticated actor, so the\n * deferred sender-match can be enforced when the target finally arrives\n * (`reevaluatePending`). A forged early tombstone parked in pending is\n * DISCARDED on the target's arrival (sender-match fails) → the genuine\n * message renders.\n * - **Unresolved sender + absent target:** nothing can be verified yet (neither\n * side resolved). The event is parked in `held` keyed by its\n * `eventClientMsgId` and re-ingested on `reevaluatePending` — it never\n * tombstones until BOTH the actor and the target's author are known AND match.\n *\n * Pure: no I/O, fully table-testable.\n */\n\n/**\n * One delete-for-everyone tombstone event for a target message. Carries\n * everything the fold needs and nothing it must read from the ciphertext: the\n * actor is the AUTHENTICATED sender resolved upstream (`actorUserId`), NEVER an\n * actor field on the wire.\n */\nexport interface DeleteEvent {\n targetClientMsgId: string;\n /** Resolved authenticated sender userId; null = unresolved sender. */\n actorUserId: string | null;\n epoch: number;\n serverSeq: number;\n /** Dedup key — the tombstone's own client_msg_id (unique per logical event). */\n eventClientMsgId: string;\n}\n\nimport type { AuthorResolution } from './edit-fold.js';\n\n/**\n * Resolves the target message's author via {@link AuthorResolution} (the SAME\n * orphan-aware resolution the edit gate uses): `'author'` → run the author-gate;\n * `'unknown'` → defer (target absent locally → pending/held); `'purged'` → NO-OP\n * (the target was TTL-purged — a delete of an already-gone message is already\n * satisfied; never park in pending, never re-attempt).\n */\ntype AuthorOfTarget = (targetClientMsgId: string) => AuthorResolution;\n\nexport class DeleteFold {\n // targets with a confirmed valid tombstone — MONOTONIC (never removed within retention).\n private readonly tombstoned = new Set<string>();\n // target → the tombstone's authenticated actor userId, awaiting the target's arrival.\n private readonly pending = new Map<string, string>();\n // dedup of real wire events the fold could evaluate (tombstoned or parked in pending).\n private readonly seen = new Set<string>();\n // events parked because NEITHER the actor NOR the target's author was resolvable at ingest;\n // keyed by eventClientMsgId so a re-delivered unverifiable event is held exactly once.\n private held: DeleteEvent[] = [];\n\n /**\n * Ingest one tombstone. `authorOfTarget` resolves the target message's author via\n * {@link AuthorResolution}: `'author'` → run the author-gate; `'unknown'` → defer\n * (target absent locally → pending/held); `'purged'` → NO-OP, mark seen (the target\n * was TTL-purged — a delete of an already-gone message is already satisfied; never\n * park in pending, never re-attempt).\n */\n ingest(e: DeleteEvent, authorOfTarget: AuthorOfTarget): void {\n // Already absorbed → no-op (idempotent, absorbing).\n if (this.tombstoned.has(e.targetClientMsgId)) return;\n // Dedup of the same logical tombstone (only events we actually parked/evaluated are seen).\n if (this.seen.has(e.eventClientMsgId)) return;\n if (this.heldContains(e.eventClientMsgId)) return;\n\n const res = authorOfTarget(e.targetClientMsgId);\n if (res.kind === 'purged') {\n // Target already TTL-purged → already gone. No-op, mark seen (never park in pending).\n this.seen.add(e.eventClientMsgId);\n return;\n }\n if (res.kind === 'author') {\n // Target present → run the author-gate NOW.\n this.seen.add(e.eventClientMsgId);\n if (e.actorUserId === null || e.actorUserId !== res.userId) return; // forgery / unresolved actor → ignore\n this.tombstoned.add(e.targetClientMsgId);\n } else if (e.actorUserId !== null) {\n // Target absent but actor resolved → park the authenticated actor for the deferred gate.\n this.seen.add(e.eventClientMsgId);\n this.pending.set(e.targetClientMsgId, e.actorUserId);\n } else {\n // Neither actor NOR target resolved — cannot verify anything yet. Hold by id and\n // re-ingest on reevaluate; NOT marked seen so it stays re-attemptable.\n this.held.push(e);\n }\n }\n\n /** True once a valid tombstone has absorbed this target. */\n isTombstoned(targetClientMsgId: string): boolean {\n return this.tombstoned.has(targetClientMsgId);\n }\n\n /**\n * When a target message newly arrives with a resolved `author`, re-check any\n * pending tombstone for it AND re-attempt any held (unverifiable) tombstones\n * whose target is now resolvable. The deferred gate is the SAME comparison as\n * the in-order path.\n */\n reevaluatePending(target: string, author: AuthorResolution): void {\n const res = author;\n // Deferred gate for a parked pending tombstone.\n const actor = this.pending.get(target);\n if (actor !== undefined) {\n // Absorbing: only ADD on a match; a mismatch DISCARDS the (forged) pending entry so it\n // can never scrub the genuine message.\n if (res.kind === 'author') {\n if (actor === res.userId) this.tombstoned.add(target); // match → absorbed\n this.pending.delete(target); // match → absorbed; mismatch → forged tombstone loses\n } else if (res.kind === 'purged') {\n // Target was TTL-purged → already gone; nothing left to scrub or defer. Discard.\n this.pending.delete(target);\n }\n // 'unknown' → cannot decide yet, keep the pending entry.\n }\n // Re-attempt any held events whose target now has a resolvable author. Re-ingest runs the\n // full gate (dedup via seen, author-match / purged no-op), so it can neither double-apply\n // nor scrub without the match. Events that still don't resolve are re-held.\n if (this.held.length === 0) return;\n const pendingHeld = this.held;\n this.held = [];\n for (const e of pendingHeld) {\n this.ingest(e, (t): AuthorResolution => (t === target ? res : { kind: 'unknown' }));\n }\n }\n\n private heldContains(eventClientMsgId: string): boolean {\n return this.held.some((h) => h.eventClientMsgId === eventClientMsgId);\n }\n}\n","/**\n * EditFold — pure fold of edit-by-supersession events into a per-target winning\n * text + a write-once `edited` flag.\n *\n * Algorithm mirrors iOS `EditFold` (EditFold.swift) byte-for-byte in behaviour so\n * the two SDKs compute the IDENTICAL winning edit from the same events:\n *\n * - **Author-gate first, dedup after** on `eventClientMsgId`. Dedup happens only\n * AFTER the event is both resolvable (target+sender) and authorized, so a HELD\n * event is never prematurely consumed by the seen-set (matching iOS ordering).\n * - **HOLD (unresolved/dangling):** if the target's author is not yet resolvable\n * (`authorOfTarget` returns null — target unknown), OR the sender is unresolved\n * (`editorUserId == null` — roster gap), the event is parked in `held` (deduped\n * by `eventClientMsgId`) and re-attempted by `reevaluateHeld(...)` once the\n * roster/target arrives. Never folds an unresolved sender; never silently drops\n * a holdable edit.\n * - **Author-gate:** the edit's resolved sender (`editorUserId`) must equal the\n * TARGET message's author (`authorOfTarget(target)`). A non-author edit is a\n * forgery → DROPPED (not held). It is NOT marked seen — letting the gate re-drop\n * a re-delivery is harmless and idempotent (it never reaches the winner state,\n * so the winner is unaffected).\n * - **LWW per target** on `(epoch, serverSeq)` ascending. An incoming order that\n * is `< stored`, or `== stored` and loses the id backstop, is ignored.\n * - **Exact-tie rule (cross-SDK contract — MUST match iOS):** on an exact\n * `(epoch, serverSeq)` tie the edit whose `eventClientMsgId` is **lexicographically\n * GREATER (`>`) wins**. Implemented as \"incoming `<= stored.lastEventId` → loses\"\n * (identical comparison direction to iOS EditFold.swift). The server guarantees\n * `(epoch, serverSeq)` is unique per logical event, so this is a theoretical\n * backstop only — but it is fully deterministic + order-independent.\n * - **Write-once `edited`:** once ANY valid edit applies to a target it stays\n * edited forever (editing back to the original text still shows edited). Never\n * unset.\n *\n * Pure: no I/O, fully table-testable.\n */\n\nimport type { MentionRange } from './group-messaging.js';\n\n/**\n * One edit-by-supersession event for the LATEST edit's text to be folded onto a\n * target message in place. Carries everything the fold needs and nothing it must\n * read from the ciphertext: the actor is the AUTHENTICATED sender resolved\n * upstream (`editorUserId`), never an actor field on the wire.\n */\nexport interface EditEvent {\n targetClientMsgId: string;\n /** Resolved sender userId; null = unresolved (roster gap) → HOLD. */\n editorUserId: string | null;\n newText: string;\n epoch: number;\n serverSeq: number;\n /** Dedup key — the edit's own client_msg_id (unique per logical event). */\n eventClientMsgId: string;\n /**\n * The edit's REPLACEMENT mention ranges (raw, un-normalized). The fold tracks them\n * on the WINNING edit only, so the edited message's mentions re-resolve from the\n * LWW-winning edit's ranges (mentions T6). Undefined/null = the edit carried none.\n */\n bodyRanges?: MentionRange[] | null;\n}\n\ninterface TargetState {\n orderEpoch: number;\n orderSeq: number;\n lastEventId: string;\n text: string;\n /** The WINNING edit's replacement mention ranges (raw); null when it carried none. */\n bodyRanges: MentionRange[] | null;\n}\n\n/**\n * Orphan-aware author resolution (web mirror of iOS `TargetAuthor`):\n * - `{ kind: 'author'; userId }` — target present, author known → run the author-gate.\n * - `{ kind: 'purged' }` — the target was TTL-purged (its `client_msg_id` is in the\n * persistent `purgedClientMsgIds` STRING set, namespace-separate from the int\n * `server_seq` tombstone) → DROP an edit (no held leak, never resurrects).\n * - `{ kind: 'unknown' }` — target not locally resolvable yet → HOLD (re-attempt later).\n */\nexport type AuthorResolution =\n | { kind: 'author'; userId: string }\n | { kind: 'purged' }\n | { kind: 'unknown' };\n\n/**\n * Resolves the target message's AUTHOR via {@link AuthorResolution}. ALL call-sites\n * (`chat.ts` author closure, `coordinator.ts` `projectHistory`) return this union\n * directly (the legacy `string | null` shim was deleted in Task 10).\n */\ntype AuthorOfTarget = (targetClientMsgId: string) => AuthorResolution;\n\n/**\n * Lexicographic (epoch, serverSeq) comparison. Returns true if a < b\n * (the incoming order is strictly older than the stored order).\n */\nfunction orderLt(aEpoch: number, aSeq: number, bEpoch: number, bSeq: number): boolean {\n if (aEpoch !== bEpoch) return aEpoch < bEpoch;\n return aSeq < bSeq;\n}\n\n/** True if (aEpoch, aSeq) equals (bEpoch, bSeq). */\nfunction orderEq(aEpoch: number, aSeq: number, bEpoch: number, bSeq: number): boolean {\n return aEpoch === bEpoch && aSeq === bSeq;\n}\n\nexport class EditFold {\n // target → winning edit state\n private readonly states = new Map<string, TargetState>();\n // dedup of real wire events that reached (and were resolvable enough to evaluate at) the fold\n private readonly seenEvents = new Set<string>();\n // events parked because target/author or sender was unresolved at ingest time\n private held: EditEvent[] = [];\n // targets that have had ≥1 valid edit applied (write-once)\n private readonly editedTargets = new Set<string>();\n\n /**\n * Ingest one edit. `authorOfTarget` resolves the target message's author via\n * {@link AuthorResolution}: `'author'` → run the author-gate; `'unknown'` → HOLD\n * (target/author not yet known); `'purged'` → DROP (the target was TTL-purged —\n * editing a disappeared message is a no-op; mark the event seen so it never re-holds\n * and a later author \"resolution\" cannot resurrect it).\n */\n ingest(e: EditEvent, authorOfTarget: AuthorOfTarget): void {\n const res = authorOfTarget(e.targetClientMsgId);\n if (res.kind === 'unknown') {\n this.holdIfNew(e); // target/author not yet known → HOLD\n return;\n }\n if (res.kind === 'purged') {\n this.seenEvents.add(e.eventClientMsgId); // mark seen → never re-held, never resurrects\n return; // DROP: editing a disappeared message is a no-op\n }\n const author = res.userId;\n // Sender not yet resolvable (roster gap) → hold; we must never fold an unattributed edit.\n if (e.editorUserId === null) {\n this.holdIfNew(e);\n return;\n }\n // Author-gate: a non-author edit is a forgery — dropped, not held, not marked seen\n // (the gate re-drops any re-delivery; it never touches winner state, so it's idempotent).\n if (e.editorUserId !== author) return;\n\n // Resolvable + authorized: dedup, then LWW.\n if (this.seenEvents.has(e.eventClientMsgId)) return; // re-delivery of same logical edit\n this.seenEvents.add(e.eventClientMsgId);\n\n const prev = this.states.get(e.targetClientMsgId);\n if (prev !== undefined) {\n // stale / out-of-order\n if (orderLt(e.epoch, e.serverSeq, prev.orderEpoch, prev.orderSeq)) return;\n // exact (epoch, serverSeq) tie: lexicographically GREATER eventClientMsgId wins,\n // so an incoming id <= the stored one loses (identical to iOS EditFold's `<=`).\n if (\n orderEq(e.epoch, e.serverSeq, prev.orderEpoch, prev.orderSeq) &&\n e.eventClientMsgId <= prev.lastEventId\n ) {\n return;\n }\n }\n this.states.set(e.targetClientMsgId, {\n orderEpoch: e.epoch,\n orderSeq: e.serverSeq,\n lastEventId: e.eventClientMsgId,\n text: e.newText,\n bodyRanges: e.bodyRanges ?? null,\n });\n this.editedTargets.add(e.targetClientMsgId); // write-once\n }\n\n /**\n * Park an event for later re-attempt, deduping held re-deliveries by\n * eventClientMsgId so a repeatedly-delivered unresolvable edit is held exactly\n * once (and never double-applies when it finally resolves on reevaluate).\n */\n private holdIfNew(e: EditEvent): void {\n if (this.held.some((h) => h.eventClientMsgId === e.eventClientMsgId)) return;\n this.held.push(e);\n }\n\n /** The winning edit text for a target, or null if no valid edit has applied. */\n text(targetClientMsgId: string): string | null {\n return this.states.get(targetClientMsgId)?.text ?? null;\n }\n\n /** Write-once: true once any valid edit applied to the target. */\n isEdited(targetClientMsgId: string): boolean {\n return this.editedTargets.has(targetClientMsgId);\n }\n\n /**\n * The WINNING edit's replacement mention ranges for a target (raw, un-normalized),\n * or null when no valid edit applied or the winning edit carried none. The Chat\n * normalizes these against the edited text to compute the edited message's mentions\n * (mentions T6). LWW-consistent: always the same edit that `text(...)` returns.\n */\n bodyRanges(targetClientMsgId: string): MentionRange[] | null {\n return this.states.get(targetClientMsgId)?.bodyRanges ?? null;\n }\n\n /**\n * Re-run HELD edits when the roster/target newly resolves (call on member/roster\n * change and when a target message arrives). Clears `held` and re-ingests each\n * event with the fresh `authorOfTarget` — events that still don't resolve are\n * simply re-held; events that now resolve fold via the normal LWW path.\n * Idempotent: re-ingest is deduped by `seenEvents` (applied events) and by\n * `holdIfNew` (still-held events), so reevaluating repeatedly can neither\n * double-apply nor lose an edit.\n */\n reevaluateHeld(authorOfTarget: AuthorOfTarget): void {\n const pending = this.held;\n this.held = [];\n for (const e of pending) this.ingest(e, authorOfTarget);\n }\n}\n","// Byte / base64 helpers — portable across browser and Node (no Buffer dep on\n// the browser path). All MLS blobs cross the wire as base64 strings.\n\n/** Encode bytes to a standard (non-url-safe) base64 string. */\nexport function toBase64(bytes: Uint8Array): string {\n if (typeof Buffer !== 'undefined') {\n return Buffer.from(bytes).toString('base64');\n }\n let binary = '';\n const chunk = 0x8000;\n for (let i = 0; i < bytes.length; i += chunk) {\n binary += String.fromCharCode(...bytes.subarray(i, i + chunk));\n }\n return btoa(binary);\n}\n\n/** Decode a standard base64 string to bytes. */\nexport function fromBase64(b64: string): Uint8Array {\n if (typeof Buffer !== 'undefined') {\n return new Uint8Array(Buffer.from(b64, 'base64'));\n }\n const binary = atob(b64);\n const out = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);\n return out;\n}\n\n/** Lowercase hex of a byte buffer (a stable string key for any blob). */\nexport function bytesToHex(bytes: Uint8Array): string {\n let s = '';\n for (let i = 0; i < bytes.length; i++) {\n s += (bytes[i] ?? 0).toString(16).padStart(2, '0');\n }\n return s;\n}\n\nconst utf8Encoder = new TextEncoder();\nconst utf8Decoder = new TextDecoder();\n\nexport const encodeUtf8 = (s: string): Uint8Array => utf8Encoder.encode(s);\nexport const decodeUtf8 = (b: Uint8Array): string => utf8Decoder.decode(b);\n\n/** Bytes of a device id (the MLS credential identity == the `mdv_` device id). */\nexport const deviceIdBytes = (deviceId: string): Uint8Array => encodeUtf8(deviceId);\n\n/** A fresh client idempotency / id value (crypto-strong; never Math.random). */\nexport function randomId(): string {\n return crypto.randomUUID();\n}\n\n/** Mint a client-side `mdv_<uuidv7>` device id (SP-1.6 client-minted). The\n * server (messaging `ValidateDeviceID`) requires the mdv_ prefix followed by a\n * canonical UUID *version 7* — a v4 (what `crypto.randomUUID()` emits) is\n * rejected with `invalid_device_id`. So mint a real v7 here: the version must\n * match the server's id-validator, not just the prefix. A random v7 bound to\n * the caller's iJWT sub stays squat-safe (ownership = sub, uniqueness = PK). */\nexport function mintDeviceId(): string {\n return `mdv_${uuidV7()}`;\n}\n\n/**\n * Generate a canonical RFC 9562 UUID version 7 (time-ordered): 48-bit big-endian\n * Unix-ms timestamp, version nibble 7, variant bits 10, the rest CSPRNG random.\n * `crypto.randomUUID()` only emits v4, and the messaging server validates v7, so\n * the device-id minter cannot reuse it — this is the byte-shape the server's\n * `uuid.Version() == 7` check accepts.\n */\nexport function uuidV7(): string {\n const bytes = new Uint8Array(16);\n crypto.getRandomValues(bytes);\n const ts = Date.now(); // ms since epoch fits in 48 bits until year 10889\n // A DataView write avoids `noUncheckedIndexedAccess` \"possibly undefined\" on\n // raw index assignment and writes the 48-bit timestamp big-endian in one shot.\n const view = new DataView(bytes.buffer);\n view.setUint32(0, Math.floor(ts / 2 ** 16)); // high 32 bits of the 48-bit ms\n view.setUint16(4, ts & 0xffff); // low 16 bits\n view.setUint8(6, (view.getUint8(6) & 0x0f) | 0x70); // version 7\n view.setUint8(8, (view.getUint8(8) & 0x3f) | 0x80); // variant 10\n const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\n/** The canonical sorted-pair direct_key for a 1:1 chat (`dm:<a>|<b>`). */\nexport function directKey(userA: string, userB: string): string {\n const [a, b] = [userA, userB].sort();\n return `dm:${a}|${b}`;\n}\n\n/** Mint a `msg_<32 hex>` client message id (matches iOS format). */\nexport function mintClientMsgId(): string {\n const b = new Uint8Array(16);\n crypto.getRandomValues(b);\n return `msg_${Array.from(b, (x) => x.toString(16).padStart(2, '0')).join('')}`;\n}\n\n/** Truncate a string to 240 Unicode scalars (code points). Returns [truncated, wasTruncated]. */\nexport function truncateQuote(s: string): [string, boolean] {\n const scalars = [...s];\n if (scalars.length <= 240) return [s, false];\n return [scalars.slice(0, 240).join(''), true];\n}\n","// The messaging wire protocol for the web SDK — paths + request/response shapes.\n//\n// CRITICAL (differs from iOS): the web HttpClient does a RAW JSON.stringify of\n// the request body (NO camel↔snake conversion — verified @palbase/core\n// HttpClient). So every field here is snake_case to match the server tags\n// VERBATIM. The field names are the live server JSON tags, cross-checked against\n// modules/messaging/internal/handler/{group,message,keypackage,trust}.go and the\n// iOS MessagingTypes.swift wire doc (live-proven). All blobs are base64 strings.\n//\n// Paths mirror the iOS MessagingPaths (the M3 #7 contract): a {gid} segment is\n// ALWAYS a `grp_<uuidv7>` display_id (url-safe), NEVER the base64 rfc_group_id.\n\n// ─── Paths ───────────────────────────────────────────────────────────────────\n\nconst GROUPS = '/v1/messaging/groups';\nconst DEVICES = '/v1/messaging/devices';\nconst KEYDIR = '/v1/messaging/keydir';\n\n/** encodeURIComponent a single path segment (defense in depth; the real fix is\n * passing the url-safe grp_/mdv_ id, never the base64 rfc_group_id — M3 #7). */\nconst seg = (s: string): string => encodeURIComponent(s);\n\nexport const MessagingPaths = {\n groups: GROUPS,\n groupsDirect: `${GROUPS}/direct`,\n group: (displayId: string) => `${GROUPS}/${seg(displayId)}`,\n groupMetadata: (displayId: string) => `${GROUPS}/${seg(displayId)}/metadata`,\n groupMembers: (displayId: string) => `${GROUPS}/${seg(displayId)}/members`,\n groupMemberDevice: (displayId: string, deviceId: string) =>\n `${GROUPS}/${seg(displayId)}/members/${seg(deviceId)}`,\n groupMessages: (displayId: string) => `${GROUPS}/${seg(displayId)}/messages`,\n groupCommits: (displayId: string) => `${GROUPS}/${seg(displayId)}/commits`,\n groupRead: (displayId: string) => `${GROUPS}/${seg(displayId)}/read`,\n // Server-metadata cluster: the per-(user,group) notify scope (mute toggle, GET/PUT)\n // + the caller's own unread view (GET). {gid} is the grp_ display_id (M3 #7).\n groupNotify: (displayId: string) => `${GROUPS}/${seg(displayId)}/notify`,\n groupUnread: (displayId: string) => `${GROUPS}/${seg(displayId)}/unread`,\n\n deviceWelcomes: (deviceId: string) => `${DEVICES}/${seg(deviceId)}/welcomes`,\n deviceQueue: (deviceId: string) => `${DEVICES}/${seg(deviceId)}/queue`,\n deviceQueueAck: (deviceId: string) => `${DEVICES}/${seg(deviceId)}/queue/ack`,\n\n keydirDevices: `${KEYDIR}/devices`,\n keydirDeviceKeyPackages: (deviceId: string) => `${KEYDIR}/devices/${seg(deviceId)}/keypackages`,\n keydirUserDevices: (userId: string) => `${KEYDIR}/users/${seg(userId)}/devices`,\n keydirUserClaim: (userId: string) => `${KEYDIR}/users/${seg(userId)}/keypackages/claim`,\n keydirPinsVerify: `${KEYDIR}/pins/verify`,\n\n prefs: '/v1/messaging/prefs',\n} as const;\n\n/** True iff `s` is safe to place RAW in a URL path segment (M3 #7 gate). A\n * grp_/mdv_ id passes; a base64 rfc_group_id (`/`, `=`, `+`) FAILS. */\nexport function isUrlSafePathSegment(s: string): boolean {\n return s.length > 0 && /^[A-Za-z0-9\\-._~]+$/.test(s);\n}\n\n// ─── Keydir (SP-0) ───────────────────────────────────────────────────────────\n\nexport type MessagingPlatform = 'android' | 'ios' | 'web';\n\nexport interface EnrollDeviceBody {\n device_id: string;\n platform: MessagingPlatform;\n device_name?: string;\n cipher_suite: number;\n signature_key: string; // base64 raw curve public key\n credential: string; // base64 TLS-encoded MLS Credential (identity == device_id)\n capabilities: string; // base64\n}\n\nexport interface EnrollResponse {\n device_id: string;\n signature_key_fingerprint: string;\n trust_tier: string;\n one_time_count: number;\n}\n\nexport interface ReplenishBody {\n key_packages: string[]; // base64 raw KeyPackages\n}\n\nexport interface DeviceInfo {\n device_id: string;\n signature_key: string;\n credential: string;\n cipher_suite: number;\n key_epoch: number;\n trust_tier: string;\n first_seen_at: string;\n}\n\nexport interface DeviceListResponse {\n device_set_version: string;\n devices: DeviceInfo[];\n warnings?: unknown[];\n}\n\nexport interface ClaimedKeyPackage {\n device_id: string;\n cipher_suite: number;\n key_package: string; // base64\n reused: boolean;\n}\n\nexport interface ClaimResponse {\n key_packages: ClaimedKeyPackage[];\n depleted_devices?: string[];\n}\n\nexport interface VerifyBody {\n peer_user_id: string;\n verified_device_set_hash: string;\n}\n\n// ─── Group create / direct ────────────────────────────────────────────────────\n\nexport interface CreateGroupBody {\n rfc_group_id: string; // base64\n cipher_suite: number;\n creator_device_id: string;\n creator_leaf_index: number;\n metadata?: string; // base64 sealed-name blob; omitted on web (no exporter yet)\n}\n\nexport interface CreateGroupResponse {\n display_id: string;\n rfc_group_id: string;\n current_epoch: number;\n metadata?: string;\n}\n\nexport interface CreateDirectGroupBody {\n direct_key: string;\n peer_user_id: string;\n rfc_group_id: string; // base64\n cipher_suite: number;\n creator_device_id: string;\n creator_leaf_index: number;\n}\n\nexport interface CreateDirectGroupResponse {\n display_id: string;\n rfc_group_id: string;\n current_epoch: number;\n owner_user_id?: string;\n exists?: boolean;\n}\n\nexport interface GroupReadResponse {\n display_id: string;\n rfc_group_id: string;\n current_epoch: number;\n status?: string;\n metadata?: string;\n}\n\n// ─── Members ───────────────────────────────────────────────────────────────────\n\nexport type MessagingGroupRole = 'owner' | 'admin' | 'member';\n\nexport interface GroupMemberRow {\n device_id: string;\n user_id: string;\n role: MessagingGroupRole;\n leaf_index: number;\n}\n\nexport interface ListMembersResponse {\n members: GroupMemberRow[];\n}\n\nexport interface WelcomeBody {\n recipient_device_id: string;\n recipient_user_id: string;\n welcome_b64: string;\n}\n\n/** `POST /groups/{gid}/members` — declared add + opaque commit + welcomes. */\nexport interface AddMemberBody {\n op: string;\n commit_b64: string;\n target_device_ids: string[];\n leaf_indexes: number[];\n user_ids: string[];\n key_package_refs: string[];\n welcomes: WelcomeBody[];\n}\n\n/** `DELETE /groups/{gid}/members/{deviceId}` — multi-remove / self-leave. */\nexport interface RemoveMembersBody {\n op: string;\n commit_b64: string;\n target_device_ids: string[];\n user_ids: string[];\n}\n\n/** `{ epoch }` — the advanced-to epoch a membership/commit write returns. */\nexport interface CommitAccepted {\n epoch: number;\n}\n\n// ─── Messages ──────────────────────────────────────────────────────────────────\n\nexport interface SendMessageBody {\n ciphertext_b64: string;\n client_idem_key: string;\n}\n\nexport interface SentReceipt {\n server_seq: number;\n epoch: number;\n}\n\n/** One queue row from `GET /devices/{deviceId}/queue`. */\nexport interface QueuedMessage {\n id: string;\n epoch: number;\n content_type: number; // 1=app, 2=proposal, 3=commit\n wire_format: number; // 1=public, 2=private\n ciphertext_b64: string;\n server_seq: number;\n // OPTIONAL group attribution + the fanned-out declaration (the live server\n // does not fan these on queue rows yet — nil today, gate engages when present).\n rfc_group_id_b64?: string;\n sender_device_id?: string;\n declared_added_device_ids?: string[];\n declared_removed_device_ids?: string[];\n declared_op?: string;\n}\n\nexport interface QueuePage {\n messages: QueuedMessage[];\n}\n\n/** One row from `GET /devices/{deviceId}/welcomes` — a new-member bootstrap. */\nexport interface PendingWelcome {\n id: string;\n display_id: string; // the group's grp_ routing id (url-safe; NOT the base64 rfc)\n rfc_group_id_b64: string;\n welcome_b64: string;\n added_at_epoch: number;\n}\n\nexport interface WelcomePage {\n welcomes: PendingWelcome[];\n}\n\nexport interface AckBody {\n message_ids: string[];\n}\n\n/** Queue-row content-type discriminator (matches the server `content_type`). */\nexport const ContentType = {\n application: 1,\n proposal: 2,\n commit: 3,\n} as const;\n\n// ─── SP-3 comms (read receipts / prefs / presence / typing) ──────────────────\n\nexport interface MarkReadBody {\n read_seq: number;\n read_epoch: number;\n is_private: boolean;\n}\n\nexport interface MarkReadResponse {\n read_seq: number;\n advanced: boolean;\n broadcast: boolean;\n}\n\nexport interface CommsPrefsResponse {\n share_presence: boolean;\n share_receipts: boolean;\n updated_at?: string;\n}\n\nexport interface SetPrefsBody {\n share_presence?: boolean;\n share_receipts?: boolean;\n}\n\nexport interface CommsPrefs {\n sharePresence: boolean;\n shareReceipts: boolean;\n}\n","// The MLS key directory (SP-0) mechanics — INTERNAL, mirroring the iOS\n// KeyDirectoryInternal. Hidden behind the coordinator's implicit enroll (the\n// first chat op self-enrolls). Byte-identical wire calls to iOS: same\n// /v1/messaging/keydir/* routes, same iJWT bearer (palbeRequest), same\n// snake_case bodies.\n\nimport { palbeRequest } from '../request.js';\nimport type { PalbeRuntime } from '../runtime.js';\nimport type { MlsEngine } from './mls-engine.js';\nimport type { DeviceIdStore } from './storage.js';\nimport { toBase64 } from './util.js';\nimport {\n type ClaimResponse,\n type DeviceListResponse,\n type EnrollDeviceBody,\n type EnrollResponse,\n MessagingPaths,\n type ReplenishBody,\n type VerifyBody,\n} from './wire.js';\n\n/**\n * Enroll THIS device + publish its first KeyPackages from REAL engine material.\n * Idempotent: a returning user (persisted device id) skips the POST and just\n * replenishes. Returns the resolved device id.\n *\n * Flow (mirrors iOS enroll):\n * 1. engine.enrollmentMaterial() → { signatureKey, credential, capabilities }\n * 2. POST /keydir/devices (the server validates the client-minted mdv_ id +\n * credential.identity == device_id + ownership == iJWT sub)\n * 3. generate N key packages → POST /keydir/devices/{id}/keypackages\n */\nexport async function enrollDevice(\n rt: PalbeRuntime,\n engine: MlsEngine,\n deviceStore: DeviceIdStore,\n opts: { keyPackageCount?: number; deviceName?: string } = {},\n): Promise<string> {\n const keyPackageCount = opts.keyPackageCount ?? 10;\n const deviceId = engine.deviceId;\n const alreadyEnrolled = (await deviceStore.load(engine.userId)) === deviceId;\n\n if (!alreadyEnrolled) {\n const material = await engine.enrollmentMaterial();\n const body: EnrollDeviceBody = {\n device_id: deviceId,\n platform: 'web',\n device_name: opts.deviceName,\n cipher_suite: 1,\n signature_key: toBase64(material.signatureKey),\n credential: toBase64(material.credential),\n capabilities: toBase64(material.capabilities),\n };\n await palbeRequest<EnrollResponse>(rt, 'POST', MessagingPaths.keydirDevices, { body });\n await deviceStore.save(engine.userId, deviceId);\n }\n\n if (keyPackageCount > 0) {\n const raws: string[] = [];\n for (let i = 0; i < keyPackageCount; i++) {\n raws.push(toBase64(await engine.generateKeyPackage()));\n }\n await replenish(rt, deviceId, raws);\n }\n return deviceId;\n}\n\n/** Publish more one-use KeyPackages for a device. */\nexport async function replenish(\n rt: PalbeRuntime,\n deviceId: string,\n rawKeyPackagesB64: string[],\n): Promise<void> {\n const body: ReplenishBody = { key_packages: rawKeyPackagesB64 };\n await palbeRequest<void>(rt, 'POST', MessagingPaths.keydirDeviceKeyPackages(deviceId), { body });\n}\n\n/** Claim a peer's KeyPackages (one per device) to add them to a group. */\nexport async function claimKeyPackages(rt: PalbeRuntime, userId: string): Promise<ClaimResponse> {\n return palbeRequest<ClaimResponse>(rt, 'POST', MessagingPaths.keydirUserClaim(userId), {\n body: {},\n });\n}\n\n/** List a user's enrolled devices (for the safety number + roster resolution). */\nexport async function listDevices(rt: PalbeRuntime, userId: string): Promise<DeviceListResponse> {\n return palbeRequest<DeviceListResponse>(rt, 'GET', MessagingPaths.keydirUserDevices(userId));\n}\n\n/** Record a TOFU verify pin for a peer's current device set. */\nexport async function verifyPeer(\n rt: PalbeRuntime,\n peerUserId: string,\n verifiedDeviceSetHash: string,\n): Promise<void> {\n const body: VerifyBody = {\n peer_user_id: peerUserId,\n verified_device_set_hash: verifiedDeviceSetHash,\n };\n await palbeRequest<void>(rt, 'POST', MessagingPaths.keydirPinsVerify, { body });\n}\n","// The membership/send orchestrator — the web mirror of iOS GroupMessaging.\n// Two correctness invariants (design §3, identical to iOS):\n//\n// (1) ATOMIC-PERSIST AROUND A COMMIT. Stage a pending commit in the engine,\n// POST it, apply ONLY on a 200. On a 409 epoch_conflict clearPendingCommit —\n// the local epoch is NEVER ahead of the server-accepted epoch.\n// (2) THE STALE-EPOCH REBASE LOOP. On 409 drain the winning commits, rebuild at\n// the new epoch, re-POST — bounded + jittered. Exhaustion throws.\n//\n// sendMessage NEVER rebases: an application message is epoch-bound; a 422\n// stale_application surfaces to the caller (re-send after the delivery source\n// catches up). Only membership COMMITS rebase.\n//\n// WEB GAP vs iOS: there is no MLS exporter export in the WASM surface yet, so\n// the group NAME is not sealed (metadata omitted; the server stays blind and the\n// UI falls back to a friendly label). Text/membership flows are byte-identical.\n\nimport { BackendError, isBackendError } from '../errors.js';\nimport { palbeRequest } from '../request.js';\nimport type { PalbeRuntime } from '../runtime.js';\nimport type { MessageStore, StoredMessage } from './history.js';\nimport { claimKeyPackages } from './keydir.js';\nimport type { MlsEngine } from './mls-engine.js';\nimport type { ResolvedReply, SentReceipt } from './types.js';\nimport {\n decodeUtf8,\n deviceIdBytes,\n encodeUtf8,\n fromBase64,\n directKey as makeDirectKey,\n mintClientMsgId,\n randomId,\n toBase64,\n} from './util.js';\nimport {\n type AddMemberBody,\n type ClaimResponse,\n type CommitAccepted,\n ContentType,\n type CreateDirectGroupBody,\n type CreateDirectGroupResponse,\n type CreateGroupBody,\n type CreateGroupResponse,\n type ListMembersResponse,\n MessagingPaths,\n type QueuePage,\n type RemoveMembersBody,\n type SendMessageBody,\n type WelcomeBody,\n type SentReceipt as WireReceipt,\n} from './wire.js';\n\n/** A resolved group as the SDK tracks it. `name` is always null on web (no exporter). */\nexport interface MessagingGroup {\n displayId: string;\n rfcGroupId: string; // base64 routing key\n currentEpoch: number;\n ownerUserId: string | null;\n directKey: string | null;\n name: string | null;\n}\n\nexport interface QuotePreview {\n kind: string;\n author_user_id: string;\n body?: string;\n body_truncated: boolean;\n}\nexport interface ReplyRef {\n v: number;\n client_msg_id: string;\n preview?: QuotePreview;\n}\n\n/** A single mention range inside a message body.\n * Offsets are UTF-16 code units, half-open [start, start+length).\n * Wire: snake_case (mentioned_user_id, body_ranges) — web does RAW JSON, no case-convert.\n */\nexport interface MentionRange {\n start: number;\n length: number;\n mentionedUserId: string;\n}\n\n/** The typed E2E message envelope (matches the iOS wire format exactly). */\ninterface MsgEnvelope {\n v: 1;\n type: 'text';\n client_msg_id: string;\n text: string;\n reply_to?: ReplyRef;\n body_ranges?: Array<{ start: number; length: number; mentioned_user_id: string }>;\n // Per-message TTL — sender_send_ts present IFF start === 'send' (raw snake_case wire).\n expiry?: { v: number; ttl_seconds: number; start: 'send' | 'read'; sender_send_ts?: number };\n}\n\nexport interface ReactionPayload {\n targetClientMsgId: string;\n emoji: string;\n op: 'add' | 'remove';\n}\n\nexport interface EditPayload {\n targetClientMsgId: string;\n newText: string;\n}\n\nexport interface DeletePayload {\n targetClientMsgId: string;\n scope: string;\n}\n\n/** Per-message TTL spec (decoded). senderSendTs present iff start === 'send'. */\nexport interface ExpirySpec {\n v: number;\n ttlSeconds: number;\n start: 'send' | 'read';\n senderSendTs: number | null;\n}\n\n/** Encode a delete event into the wire envelope bytes. */\nexport function encodeDelete(args: { clientMsgId: string; targetClientMsgId: string }): Uint8Array {\n return encodeUtf8(\n JSON.stringify({\n v: 1,\n type: 'delete',\n client_msg_id: args.clientMsgId,\n target_client_msg_id: args.targetClientMsgId,\n scope: 'everyone',\n }),\n );\n}\n\n/** Encode an edit event into the wire envelope bytes. */\nexport function encodeEdit(args: {\n clientMsgId: string;\n targetClientMsgId: string;\n newText: string;\n bodyRanges?: MentionRange[] | null;\n}): Uint8Array {\n return encodeUtf8(\n JSON.stringify({\n v: 1,\n type: 'edit',\n client_msg_id: args.clientMsgId,\n target_client_msg_id: args.targetClientMsgId,\n new_text: args.newText,\n ...(args.bodyRanges && args.bodyRanges.length > 0\n ? {\n body_ranges: args.bodyRanges.map((r) => ({\n start: r.start,\n length: r.length,\n mentioned_user_id: r.mentionedUserId,\n })),\n }\n : {}),\n }),\n );\n}\n\n/** Encode a reaction event into the wire envelope bytes. */\nexport function encodeReaction(args: {\n clientMsgId: string;\n targetClientMsgId: string;\n emoji: string;\n op: 'add' | 'remove';\n}): Uint8Array {\n return encodeUtf8(\n JSON.stringify({\n v: 1,\n type: 'reaction',\n client_msg_id: args.clientMsgId,\n target_client_msg_id: args.targetClientMsgId,\n emoji: args.emoji,\n op: args.op,\n }),\n );\n}\n\n/** Encode a text message into the wire envelope bytes. */\nexport function encodeEnvelope(args: {\n text: string;\n clientMsgId: string;\n replyTo?: ReplyRef | null;\n bodyRanges?: MentionRange[] | null;\n expiry?: ExpirySpec | null;\n}): Uint8Array {\n const env: MsgEnvelope = {\n v: 1,\n type: 'text',\n client_msg_id: args.clientMsgId,\n text: args.text,\n ...(args.replyTo ? { reply_to: args.replyTo } : {}),\n ...(args.bodyRanges && args.bodyRanges.length > 0\n ? {\n body_ranges: args.bodyRanges.map((r) => ({\n start: r.start,\n length: r.length,\n mentioned_user_id: r.mentionedUserId,\n })),\n }\n : {}),\n ...(args.expiry\n ? {\n expiry: {\n v: args.expiry.v,\n ttl_seconds: args.expiry.ttlSeconds,\n start: args.expiry.start,\n // present IFF send (drop a stray senderSendTs on a read anchor)\n ...(args.expiry.start === 'send' && args.expiry.senderSendTs != null\n ? { sender_send_ts: args.expiry.senderSendTs }\n : {}),\n },\n }\n : {}),\n };\n return encodeUtf8(JSON.stringify(env));\n}\n\n/** Encode a per-chat default disappearing-timer control envelope. ttlSeconds === null\n * disables the chat default; it is OMITTED from the wire (absent === null === disable). */\nexport function encodeTimerSet(args: {\n clientMsgId: string;\n ttlSeconds: number | null;\n start: 'send' | 'read';\n}): Uint8Array {\n return encodeUtf8(\n JSON.stringify({\n v: 1,\n type: 'timer_set',\n client_msg_id: args.clientMsgId,\n ...(args.ttlSeconds != null ? { ttl_seconds: args.ttlSeconds } : {}),\n start: args.start,\n }),\n );\n}\n\n/** Decode an application plaintext (tolerant: bare-text or missing-v → legacy fallback). */\nexport function decodeEnvelope(bytes: Uint8Array): {\n type?: string;\n text: string | null;\n clientMsgId: string;\n replyTo: ReplyRef | null;\n reaction?: ReactionPayload;\n edit?: EditPayload;\n delete?: DeletePayload;\n bodyRanges?: MentionRange[];\n expiry?: ExpirySpec;\n timer?: { ttlSeconds: number | null; start: 'send' | 'read' };\n} {\n const s = decodeUtf8(bytes);\n try {\n const o = JSON.parse(s) as {\n type?: string;\n v?: number;\n text?: string;\n client_msg_id?: string;\n reply_to?: ReplyRef;\n target_client_msg_id?: string;\n emoji?: string;\n op?: string;\n new_text?: string;\n scope?: string;\n body_ranges?: Array<{ start: number; length: number; mentioned_user_id: string }>;\n expiry?: unknown;\n ttl_seconds?: number;\n sender_send_ts?: number;\n start?: string;\n };\n if (typeof o === 'object' && o !== null && o.type === 'timer_set') {\n // timer_set control envelope — ttl_seconds absent === null === disable.\n return {\n type: 'timer_set',\n text: null,\n clientMsgId: o.client_msg_id ?? '',\n replyTo: null,\n timer: {\n ttlSeconds: typeof o.ttl_seconds === 'number' ? o.ttl_seconds : null,\n start: o.start === 'read' ? 'read' : 'send',\n },\n };\n }\n if (typeof o === 'object' && o !== null && o.type === 'delete') {\n // Delete envelope.\n return {\n type: 'delete',\n text: null,\n clientMsgId: o.client_msg_id ?? '',\n replyTo: null,\n delete: {\n targetClientMsgId: o.target_client_msg_id ?? '',\n scope: o.scope ?? 'everyone',\n },\n };\n }\n if (typeof o === 'object' && o !== null && o.type === 'reaction') {\n // Reaction envelope.\n return {\n type: 'reaction',\n text: null,\n clientMsgId: o.client_msg_id ?? '',\n replyTo: null,\n reaction: {\n targetClientMsgId: o.target_client_msg_id ?? '',\n emoji: o.emoji ?? '',\n op: (o.op as 'add' | 'remove') ?? 'add',\n },\n };\n }\n if (typeof o === 'object' && o !== null && o.type === 'edit') {\n // Edit envelope.\n const editRanges = decodeBodyRanges(o.body_ranges);\n return {\n type: 'edit',\n text: null,\n clientMsgId: o.client_msg_id ?? '',\n replyTo: null,\n edit: {\n targetClientMsgId: o.target_client_msg_id ?? '',\n newText: o.new_text ?? '',\n },\n ...(editRanges ? { bodyRanges: editRanges } : {}),\n };\n }\n if (typeof o === 'object' && o !== null && o.type === 'text' && typeof o.v === 'number') {\n // Full v:1 envelope — extract all fields.\n const textRanges = decodeBodyRanges(o.body_ranges);\n const expiry = decodeExpiry(o.expiry);\n return {\n type: 'text',\n text: o.text ?? null,\n clientMsgId: o.client_msg_id ?? '',\n replyTo: o.reply_to ?? null,\n ...(textRanges ? { bodyRanges: textRanges } : {}),\n ...(expiry ? { expiry } : {}),\n };\n }\n if (typeof o === 'object' && o !== null && o.type === 'text') {\n // Legacy envelope (no v) — extract text, no reply context.\n return { type: 'text', text: o.text ?? null, clientMsgId: '', replyTo: null };\n }\n // A media envelope (not yet supported on web) → no text.\n if (typeof o === 'object' && o !== null && o.type === 'media')\n return { type: 'media', text: null, clientMsgId: '', replyTo: null };\n } catch {\n // legacy/bare plaintext\n }\n return { text: s, clientMsgId: '', replyTo: null };\n}\n\n/** Map a raw wire expiry object (snake_case) to ExpirySpec. Returns undefined if absent\n * or malformed (no ttl_seconds) — an old decoder with this branch stripped ignores it. */\nfunction decodeExpiry(raw: unknown): ExpirySpec | undefined {\n if (typeof raw !== 'object' || raw === null) return undefined;\n const o = raw as { v?: number; ttl_seconds?: number; start?: string; sender_send_ts?: number };\n if (typeof o.ttl_seconds !== 'number') return undefined;\n const start: 'send' | 'read' = o.start === 'read' ? 'read' : 'send';\n return {\n v: typeof o.v === 'number' ? o.v : 1,\n ttlSeconds: o.ttl_seconds,\n start,\n // tolerant: only honor sender_send_ts on a send anchor; null otherwise.\n senderSendTs:\n start === 'send' && typeof o.sender_send_ts === 'number' ? o.sender_send_ts : null,\n };\n}\n\n/** Map raw wire body_ranges (snake_case) to MentionRange[]. Returns undefined if absent or empty. */\nfunction decodeBodyRanges(\n raw: Array<{ start: number; length: number; mentioned_user_id: string }> | undefined | null,\n): MentionRange[] | undefined {\n if (!raw || raw.length === 0) return undefined;\n return raw.map((r) => ({\n start: r.start,\n length: r.length,\n mentionedUserId: r.mentioned_user_id,\n }));\n}\n\n/**\n * Resolve a reply reference against local history.\n *\n * `lookup(clientMsgId)` should return `{ text, senderUserId }` when the parent\n * is in local history, or `null` when not found.\n *\n * Spoof defense: when the parent IS found, the REAL text from local history wins\n * over the embedded `preview.body` — an attacker cannot make a reply appear to\n * quote different text than what was actually sent.\n */\nexport function resolveReply(\n ref: ReplyRef,\n lookup: (clientMsgId: string) => { text: string; senderUserId: string } | null,\n): ResolvedReply {\n const parent = lookup(ref.client_msg_id);\n if (parent !== null) {\n return {\n parentClientMsgId: ref.client_msg_id,\n state: 'resolved',\n quoteSenderUserId: parent.senderUserId,\n quoteText: parent.text,\n quoteKind: ref.preview?.kind ?? 'text',\n };\n }\n return {\n parentClientMsgId: ref.client_msg_id,\n state: 'unavailable',\n quoteSenderUserId: ref.preview?.author_user_id ?? '',\n quoteText: ref.preview?.body ?? null,\n quoteKind: ref.preview?.kind ?? 'text',\n };\n}\n\nexport interface RebaseOptions {\n maxAttempts: number;\n baseDelayMs: number;\n jitterMs: number;\n}\n\nconst DEFAULT_REBASE: RebaseOptions = { maxAttempts: 6, baseDelayMs: 80, jitterMs: 120 };\n\n/** Thrown when the rebase loop exhausts its budget (the change did NOT land). */\nexport class RebaseExhausted extends Error {\n constructor(\n readonly attempts: number,\n readonly lastKnownEpoch: number | null,\n ) {\n super(`commit rebase exhausted after ${attempts} attempts`);\n this.name = 'RebaseExhausted';\n }\n}\n\nexport class GroupMessaging {\n constructor(\n private readonly rt: PalbeRuntime,\n private readonly engine: MlsEngine,\n private readonly selfUserId: string,\n private readonly selfDeviceId: string,\n private readonly messageStore: MessageStore,\n private readonly rebase: RebaseOptions = DEFAULT_REBASE,\n ) {}\n\n // ── Create ──\n\n /** Create a group: mint the rfc_group_id, POST create, return the server group.\n * No name sealing on web (metadata omitted — server-blind, label fallback). */\n async createGroup(cipherSuite = 1): Promise<MessagingGroup> {\n const rfcGroupId = await this.engine.createGroup();\n const body: CreateGroupBody = {\n rfc_group_id: toBase64(rfcGroupId),\n cipher_suite: cipherSuite,\n creator_device_id: this.selfDeviceId,\n creator_leaf_index: 0,\n };\n const res = await palbeRequest<CreateGroupResponse>(this.rt, 'POST', MessagingPaths.groups, {\n body,\n });\n return {\n displayId: res.display_id,\n rfcGroupId: res.rfc_group_id,\n currentEpoch: res.current_epoch,\n ownerUserId: this.selfUserId,\n directKey: null,\n name: null,\n };\n }\n\n // ── Direct (1:1) get-or-create ──\n\n /** Materialize a DIRECT chat keyed by the stable direct_key (sorted user pair).\n * EXISTS → adopt (the Welcome drains via the pump); ABSENT → create + add peer. */\n async getOrCreateDirect(peerUserId: string): Promise<MessagingGroup> {\n const dk = makeDirectKey(this.selfUserId, peerUserId);\n const rfcGroupId = await this.engine.createGroup(); // reserved (used only if INSERT)\n const body: CreateDirectGroupBody = {\n direct_key: dk,\n peer_user_id: peerUserId,\n rfc_group_id: toBase64(rfcGroupId),\n cipher_suite: 1,\n creator_device_id: this.selfDeviceId,\n creator_leaf_index: 0,\n };\n const res = await palbeRequest<CreateDirectGroupResponse>(\n this.rt,\n 'POST',\n MessagingPaths.groupsDirect,\n { body },\n );\n\n if (res.exists === true) {\n // ADOPT — discard the reserved group; the founder's Welcome drains in.\n await this.engine.clearPendingCommit(rfcGroupId);\n return {\n displayId: res.display_id,\n rfcGroupId: res.rfc_group_id,\n currentEpoch: res.current_epoch,\n ownerUserId: res.owner_user_id ?? null,\n directKey: dk,\n name: null,\n };\n }\n\n // CREATE branch — the server bound the group to direct_key; add the peer.\n const group: MessagingGroup = {\n displayId: res.display_id,\n rfcGroupId: res.rfc_group_id,\n currentEpoch: res.current_epoch,\n ownerUserId: this.selfUserId,\n directKey: dk,\n name: null,\n };\n const claimed = await claimKeyPackages(this.rt, peerUserId);\n await this.addMember(group, peerUserId, claimed);\n return group;\n }\n\n // ── Members ──\n\n /** The group roster (device leaves). The coordinator dedups to users. */\n async listMembers(displayId: string): Promise<ListMembersResponse['members']> {\n const res = await palbeRequest<ListMembersResponse>(\n this.rt,\n 'GET',\n MessagingPaths.groupMembers(displayId),\n );\n return res.members;\n }\n\n /** Add a member: claim → stage the add commit → run the rebase loop. SP-1.6:\n * EVERY device of the user joins in ONE atomic commit (one shared Welcome). */\n async addMember(group: MessagingGroup, userId: string, claimed?: ClaimResponse): Promise<void> {\n const claim = claimed ?? (await claimKeyPackages(this.rt, userId));\n const adds = claim.key_packages\n .map((kp) => {\n try {\n return { deviceId: kp.device_id, kp: fromBase64(kp.key_package) };\n } catch {\n return null;\n }\n })\n .filter((x): x is { deviceId: string; kp: Uint8Array } => x !== null);\n if (adds.length === 0) {\n throw new BackendError('validation', {\n code: 'no_keypackage',\n message: 'no claimable key package for user',\n status: 422,\n });\n }\n const userIds = adds.map(() => userId);\n\n await this.commitWithRebase(group.rfcGroupId, async () => {\n const out = await this.engine.commitChanges(\n fromBase64(group.rfcGroupId),\n adds.map((a) => a.kp),\n [],\n );\n // One shared Welcome per added device (the engine returns one welcome for a\n // single-Welcome multi-add; route it to each added device).\n const welcomeB64 = out.welcome ? toBase64(out.welcome) : '';\n const welcomes: WelcomeBody[] = adds.map((a) => ({\n recipient_device_id: a.deviceId,\n recipient_user_id: userId,\n welcome_b64: welcomeB64,\n }));\n const memberBody: AddMemberBody = {\n op: 'add',\n commit_b64: toBase64(out.commit),\n target_device_ids: adds.map((a) => a.deviceId),\n leaf_indexes: out.addedLeafIndices,\n user_ids: userIds,\n key_package_refs: [],\n welcomes,\n };\n return {\n method: 'POST',\n path: MessagingPaths.groupMembers(group.displayId),\n body: memberBody,\n };\n });\n }\n\n /** Remove a user = evict ALL their device leaves in ONE commit (SP-1.6 D4). */\n async removeMember(group: MessagingGroup, userId: string): Promise<void> {\n const members = await this.listMembers(group.displayId);\n const deviceIds = members.filter((m) => m.user_id === userId).map((m) => m.device_id);\n if (deviceIds.length === 0) {\n throw new BackendError('validation', {\n code: 'not_a_member',\n message: 'user has no device in this group',\n status: 404,\n });\n }\n await this.stageMultiRemove(group, deviceIds, [userId]);\n }\n\n /** Leave = remove the CALLING device's leaf only (per-device self-leave). */\n async leaveGroup(group: MessagingGroup): Promise<void> {\n await this.stageMultiRemove(group, [this.selfDeviceId], [this.selfUserId]);\n }\n\n private async stageMultiRemove(\n group: MessagingGroup,\n deviceIds: string[],\n removedUserIds: string[],\n ): Promise<void> {\n const pathDeviceId = deviceIds[0];\n if (!pathDeviceId) throw new Error('no device to remove');\n await this.commitWithRebase(group.rfcGroupId, async () => {\n const out = await this.engine.commitChanges(\n fromBase64(group.rfcGroupId),\n [],\n deviceIds.map(deviceIdBytes),\n );\n const body: RemoveMembersBody = {\n op: 'remove',\n commit_b64: toBase64(out.commit),\n target_device_ids: deviceIds,\n user_ids: removedUserIds,\n };\n return {\n method: 'DELETE',\n path: MessagingPaths.groupMemberDevice(group.displayId, pathDeviceId),\n body,\n };\n });\n }\n\n // ── Send ──\n\n /** Send a text message. Encrypt at the current epoch, POST, return the receipt +\n * the client-minted `clientMsgId` (needed by the Chat layer for reply indexing).\n * NEVER rebases (a 422 stale_application surfaces to the caller). */\n async sendText(\n group: MessagingGroup,\n text: string,\n replyTo?: ReplyRef | null,\n bodyRanges?: MentionRange[] | null,\n expiry?: ExpirySpec | null,\n ): Promise<{ receipt: SentReceipt; clientMsgId: string }> {\n const clientMsgId = mintClientMsgId();\n const plaintext = encodeEnvelope({ text, clientMsgId, replyTo, bodyRanges, expiry });\n const ct = await this.engine.encryptApplication(fromBase64(group.rfcGroupId), plaintext);\n const body: SendMessageBody = {\n ciphertext_b64: toBase64(ct),\n client_idem_key: randomId(),\n };\n const wire = await palbeRequest<WireReceipt>(\n this.rt,\n 'POST',\n MessagingPaths.groupMessages(group.displayId),\n { body },\n );\n\n // Persist the OUTGOING copy — the ONLY durable form of an own-sent message\n // (the sender can never decrypt its own ciphertext). Best-effort: the send\n // already landed; a persist failure must not fail/re-trigger it.\n const stored: StoredMessage = {\n id: `${group.rfcGroupId}#${wire.server_seq}`,\n direction: 'outgoing',\n text,\n senderDeviceId: this.selfDeviceId,\n epoch: wire.epoch,\n serverSeq: wire.server_seq,\n at: Date.now(),\n clientMsgId,\n replyTo: replyTo\n ? {\n clientMsgId: replyTo.client_msg_id,\n previewBody: replyTo.preview?.body ?? null,\n previewAuthorUserId: replyTo.preview?.author_user_id ?? null,\n previewKind: replyTo.preview?.kind ?? 'text',\n }\n : null,\n // Persist the OUTGOING bubble's mention ranges so an own-sent mention re-resolves\n // onto its bubble after a reload (own-send reload parity — the T4-reviewer Minor).\n ...(bodyRanges && bodyRanges.length > 0 ? { bodyRanges } : {}),\n // Disappearing T10: persist the per-message TTL so the own-send re-arms its purge\n // after a cold launch (the projection derives the deadline from this row's expiry).\n ...(expiry ? { expiry } : {}),\n };\n try {\n await this.messageStore.append(group.rfcGroupId, stored);\n } catch {\n // logged at the coordinator level; never fails the send\n }\n return { receipt: { serverSeq: wire.server_seq, epoch: wire.epoch }, clientMsgId };\n }\n\n /** Send a per-chat default disappearing-timer control envelope (`timer_set`). Encrypts\n * a `type:'timer_set'` envelope at the current epoch and sends through the SAME MLS\n * application path as `sendText` (the server stays blind — it's an opaque app message,\n * NEVER a bubble). `ttlSeconds === null` disables the default (omitted from the wire).\n * Persists the outgoing `timer_set` row so the chat default re-folds on cold launch\n * (the page-local TimerFold in projectHistory). NEVER rebases (epoch-bound). */\n async sendTimerSet(\n group: MessagingGroup,\n args: { clientMsgId: string; ttlSeconds: number | null; start: 'send' | 'read' },\n ): Promise<{ receipt: SentReceipt; clientMsgId: string }> {\n const plaintext = encodeTimerSet({\n clientMsgId: args.clientMsgId,\n ttlSeconds: args.ttlSeconds,\n start: args.start,\n });\n const ct = await this.engine.encryptApplication(fromBase64(group.rfcGroupId), plaintext);\n const body: SendMessageBody = {\n ciphertext_b64: toBase64(ct),\n client_idem_key: randomId(),\n };\n const wire = await palbeRequest<WireReceipt>(\n this.rt,\n 'POST',\n MessagingPaths.groupMessages(group.displayId),\n { body },\n );\n\n // Persist the OUTGOING timer_set copy (same rationale as sendText: the sender can't\n // decrypt its own ciphertext). text is null — a timer_set is never a bubble; the\n // envelopeType + timer fields drive the chat-default re-fold on reload.\n const stored: StoredMessage = {\n id: `${group.rfcGroupId}#${wire.server_seq}`,\n direction: 'outgoing',\n text: null,\n senderDeviceId: this.selfDeviceId,\n epoch: wire.epoch,\n serverSeq: wire.server_seq,\n at: Date.now(),\n clientMsgId: args.clientMsgId,\n replyTo: null,\n envelopeType: 'timer_set',\n timer: { ttlSeconds: args.ttlSeconds, start: args.start },\n };\n try {\n await this.messageStore.append(group.rfcGroupId, stored);\n } catch {\n // best-effort, identical to sendText/sendReaction/sendEdit/sendDelete\n }\n return {\n receipt: { serverSeq: wire.server_seq, epoch: wire.epoch },\n clientMsgId: args.clientMsgId,\n };\n }\n\n /** Send a reaction (add/remove of an emoji on a target message). Encrypts a\n * `type:'reaction'` envelope at the current epoch and sends through the SAME\n * MLS application path as `sendText` (the server stays blind — a reaction is\n * just another application message). Persists the outgoing reaction row so it\n * re-folds onto its target after a reload (the own-send half of the reload\n * parity). NEVER rebases (epoch-bound like any application message). */\n async sendReaction(\n group: MessagingGroup,\n args: { clientMsgId: string; targetClientMsgId: string; emoji: string; op: 'add' | 'remove' },\n ): Promise<{ receipt: SentReceipt; clientMsgId: string }> {\n const plaintext = encodeReaction({\n clientMsgId: args.clientMsgId,\n targetClientMsgId: args.targetClientMsgId,\n emoji: args.emoji,\n op: args.op,\n });\n const ct = await this.engine.encryptApplication(fromBase64(group.rfcGroupId), plaintext);\n const body: SendMessageBody = {\n ciphertext_b64: toBase64(ct),\n client_idem_key: randomId(),\n };\n const wire = await palbeRequest<WireReceipt>(\n this.rt,\n 'POST',\n MessagingPaths.groupMessages(group.displayId),\n { body },\n );\n\n // Persist the OUTGOING reaction copy (same rationale as sendText: the sender\n // can't decrypt its own ciphertext). text is null — a reaction is never a\n // bubble; the envelopeType + reaction fields drive the re-fold on reload.\n const stored: StoredMessage = {\n id: `${group.rfcGroupId}#${wire.server_seq}`,\n direction: 'outgoing',\n text: null,\n senderDeviceId: this.selfDeviceId,\n epoch: wire.epoch,\n serverSeq: wire.server_seq,\n at: Date.now(),\n clientMsgId: args.clientMsgId,\n replyTo: null,\n envelopeType: 'reaction',\n reaction: {\n targetClientMsgId: args.targetClientMsgId,\n emoji: args.emoji,\n op: args.op,\n },\n };\n try {\n await this.messageStore.append(group.rfcGroupId, stored);\n } catch {\n // best-effort, identical to sendText\n }\n return {\n receipt: { serverSeq: wire.server_seq, epoch: wire.epoch },\n clientMsgId: args.clientMsgId,\n };\n }\n\n /** Send an edit (edit-by-supersession on a target message). Encrypts a\n * `type:'edit'` envelope at the current epoch and sends through the SAME MLS\n * application path as `sendText` (the server stays blind — an edit is just\n * another application message). Persists the outgoing edit row so it re-folds\n * onto its target's text after a reload (the own-send half of the reload\n * parity). NEVER rebases (epoch-bound like any application message). */\n async sendEdit(\n group: MessagingGroup,\n args: {\n clientMsgId: string;\n targetClientMsgId: string;\n newText: string;\n bodyRanges?: MentionRange[] | null;\n },\n ): Promise<{ receipt: SentReceipt; clientMsgId: string }> {\n const plaintext = encodeEdit({\n clientMsgId: args.clientMsgId,\n targetClientMsgId: args.targetClientMsgId,\n newText: args.newText,\n bodyRanges: args.bodyRanges,\n });\n const ct = await this.engine.encryptApplication(fromBase64(group.rfcGroupId), plaintext);\n const body: SendMessageBody = {\n ciphertext_b64: toBase64(ct),\n client_idem_key: randomId(),\n };\n const wire = await palbeRequest<WireReceipt>(\n this.rt,\n 'POST',\n MessagingPaths.groupMessages(group.displayId),\n { body },\n );\n\n // Persist the OUTGOING edit copy (same rationale as sendText: the sender can't\n // decrypt its own ciphertext). text is null — an edit is never a bubble; the\n // envelopeType + edit fields drive the re-fold onto the target on reload.\n const stored: StoredMessage = {\n id: `${group.rfcGroupId}#${wire.server_seq}`,\n direction: 'outgoing',\n text: null,\n senderDeviceId: this.selfDeviceId,\n epoch: wire.epoch,\n serverSeq: wire.server_seq,\n at: Date.now(),\n clientMsgId: args.clientMsgId,\n replyTo: null,\n envelopeType: 'edit',\n edit: {\n targetClientMsgId: args.targetClientMsgId,\n newText: args.newText,\n // Persist the edit's REPLACEMENT ranges so the edited message's mentions\n // re-resolve from this edit after a reload (own-send reload parity — T6).\n ...(args.bodyRanges && args.bodyRanges.length > 0 ? { bodyRanges: args.bodyRanges } : {}),\n },\n };\n try {\n await this.messageStore.append(group.rfcGroupId, stored);\n } catch {\n // best-effort, identical to sendText/sendReaction\n }\n return {\n receipt: { serverSeq: wire.server_seq, epoch: wire.epoch },\n clientMsgId: args.clientMsgId,\n };\n }\n\n /** Send a delete-for-everyone tombstone on a target message. Encrypts a\n * `type:'delete'` envelope at the current epoch and sends through the SAME MLS\n * application path as `sendText` (the server stays blind — a delete is just\n * another opaque application message; the original ciphertext row is NOT\n * removed). Persists the outgoing delete row so the tombstone re-folds onto its\n * target after a reload (the own-send half of the reload parity — the iOS-review\n * CRITICAL boundary; the projection's `.delete` branch re-folds it). NEVER\n * rebases (epoch-bound like any application message). */\n async sendDelete(\n group: MessagingGroup,\n args: { clientMsgId: string; targetClientMsgId: string },\n ): Promise<{ receipt: SentReceipt; clientMsgId: string }> {\n const plaintext = encodeDelete({\n clientMsgId: args.clientMsgId,\n targetClientMsgId: args.targetClientMsgId,\n });\n const ct = await this.engine.encryptApplication(fromBase64(group.rfcGroupId), plaintext);\n const body: SendMessageBody = {\n ciphertext_b64: toBase64(ct),\n client_idem_key: randomId(),\n };\n const wire = await palbeRequest<WireReceipt>(\n this.rt,\n 'POST',\n MessagingPaths.groupMessages(group.displayId),\n { body },\n );\n\n // Persist the OUTGOING delete copy (same rationale as sendText: the sender can't\n // decrypt its own ciphertext). text is null — a delete is never a bubble; the\n // envelopeType + delete fields drive the re-fold onto the target on reload.\n const stored: StoredMessage = {\n id: `${group.rfcGroupId}#${wire.server_seq}`,\n direction: 'outgoing',\n text: null,\n senderDeviceId: this.selfDeviceId,\n epoch: wire.epoch,\n serverSeq: wire.server_seq,\n at: Date.now(),\n clientMsgId: args.clientMsgId,\n replyTo: null,\n envelopeType: 'delete',\n delete: {\n targetClientMsgId: args.targetClientMsgId,\n scope: 'everyone',\n },\n };\n try {\n await this.messageStore.append(group.rfcGroupId, stored);\n } catch {\n // best-effort, identical to sendText/sendReaction/sendEdit\n }\n return {\n receipt: { serverSeq: wire.server_seq, epoch: wire.epoch },\n clientMsgId: args.clientMsgId,\n };\n }\n\n // ── The rebase loop ──\n\n private async commitWithRebase(\n rfcGroupId: string,\n build: () => Promise<{ method: string; path: string; body: unknown }>,\n ): Promise<void> {\n const gidBytes = fromBase64(rfcGroupId);\n let lastEpoch: number | null = null;\n for (let attempt = 1; attempt <= this.rebase.maxAttempts; attempt++) {\n const pending = await build();\n try {\n await palbeRequest<CommitAccepted>(this.rt, pending.method, pending.path, {\n body: pending.body,\n });\n await this.engine.applyPendingCommit(gidBytes);\n return;\n } catch (e) {\n if (isBackendError(e) && e.code === 'epoch_conflict') {\n await this.engine.clearPendingCommit(gidBytes);\n const ce = (e.data as { current_epoch?: number } | undefined)?.current_epoch;\n if (typeof ce === 'number') lastEpoch = ce;\n if (attempt === this.rebase.maxAttempts) break;\n await this.drain(rfcGroupId);\n await this.backoff(attempt);\n continue; // rebuild + re-POST\n }\n throw e;\n }\n }\n await this.engine.clearPendingCommit(gidBytes);\n throw new RebaseExhausted(this.rebase.maxAttempts, lastEpoch);\n }\n\n /** Pull the device queue, process the winning commit(s) so the engine advances\n * to the new epoch, ack the commits (the delivery source owns app messages). */\n private async drain(rfcGroupId: string): Promise<void> {\n const page = await palbeRequest<QueuePage>(\n this.rt,\n 'GET',\n MessagingPaths.deviceQueue(this.selfDeviceId),\n );\n const gidBytes = fromBase64(rfcGroupId);\n const ackIds: string[] = [];\n for (const row of page.messages) {\n if (row.content_type !== ContentType.commit) continue;\n try {\n await this.engine.processIncoming(gidBytes, fromBase64(row.ciphertext_b64));\n } catch {\n // a non-applicable commit (own / consumed) — still ack so it isn't redelivered\n }\n ackIds.push(row.id);\n }\n if (ackIds.length > 0) {\n await palbeRequest<void>(this.rt, 'POST', MessagingPaths.deviceQueueAck(this.selfDeviceId), {\n body: { message_ids: ackIds },\n });\n }\n }\n\n private async backoff(attempt: number): Promise<void> {\n const base = this.rebase.baseDelayMs * attempt;\n const jitter = this.rebase.jitterMs === 0 ? 0 : Math.random() * this.rebase.jitterMs;\n const total = base + jitter;\n if (total <= 0) return;\n await new Promise((r) => setTimeout(r, total));\n }\n}\n","import type { MentionRange } from './group-messaging.js';\n\n// Mentions (design 2026-06-20) — the PURE, deterministic decode-time normalization of\n// `body_ranges`. BOTH SDKs (iOS `Palbe` + web `@palbase/web`) apply this exact\n// algorithm bit-identically so a malformed or adversarial `body_ranges` produces the\n// SAME surviving spans on both runtimes. A cross-binding golden (T7) locks this — if\n// one SDK clamped where the other dropped, both could pass their own tests yet diverge\n// on the wire. This is a single PINNED contract, not implementer's choice.\n//\n// OFFSET UNIT = UTF-16 code units (JS string .length IS UTF-16 code units — native).\n// NEVER use [...text] (scalar/codepoint iteration) — mixing units corrupts spans over\n// emoji/surrogate pairs. Use text.length / text.charCodeAt / text.slice only.\n// The `…Utf16` suffix names the unit so an accidental scalar use is visible.\n//\n// THE EXACT ORDERED ALGORITHM (offsets are UTF-16 code units; N = text.length):\n// 1. DROP-invalid (not clamp): drop if start < 0 || length <= 0 || start + length > N.\n// 1b. DROP-surrogate-boundary: drop if `start` OR `start+length` index falls mid\n// surrogate pair (the unit BEFORE the index is a high surrogate 0xD800–0xDBFF AND\n// the unit AT the index is a low surrogate 0xDC00–0xDFFF). Splitting a pair is\n// invalid. Index 0 and index N never trigger.\n// 2. SORT: ascending by `start`; ties broken by `length` DESCENDING (wider survives an\n// equal-start collision); then by original index ascending (stable). Sort BEFORE\n// the overlap walk — mandatory; walking unsorted input changes which range survives.\n// 3. DROP-overlap (not merge) + drop-duplicate: walk left-to-right; keep a range only\n// if its `start >= previous-KEPT range's (start+length)`; otherwise DROP it (the\n// later overlapper). Exact duplicates fall out as a sub-case. NEVER merge.\n//\n// PURE: no I/O, never throws. A fully-invalid input returns []; the message text always\n// renders regardless. The self-mention-by-sender \"render-but-don't-elevate\" rule is NOT\n// applied here — normalize is purely structural; that gate lives at the elevation\n// decision (T3).\n\n/**\n * Deterministically normalize decode-time mention ranges per the pinned cross-SDK\n * contract (drop-invalid → drop-surrogate-boundary → sort[start asc, length DESC, idx\n * asc] → drop-overlap/duplicate). Offsets are UTF-16 code units into `text`. Pure.\n *\n * Mirrors iOS `normalizeMentionRangesUtf16` BIT-IDENTICALLY (T7 cross-binding golden\n * locks this). NEVER uses `[...text]` scalar spread — only `text.length` / `charCodeAt`.\n */\nexport function normalizeMentionRangesUtf16(ranges: MentionRange[], text: string): MentionRange[] {\n const n = text.length; // UTF-16 code unit count — native JS string .length\n\n // True iff splitting `text` AT `index` would cut a surrogate pair — i.e. the unit\n // just before `index` is a high surrogate and the unit at `index` is a low surrogate.\n // Boundary indices 0 and n are always safe (no unit before / no unit at).\n function splitsSurrogatePair(index: number): boolean {\n if (index <= 0 || index >= n) return false;\n const before = text.charCodeAt(index - 1);\n const at = text.charCodeAt(index);\n const beforeIsHigh = before >= 0xd800 && before <= 0xdbff;\n const atIsLow = at >= 0xdc00 && at <= 0xdfff;\n return beforeIsHigh && atIsLow;\n }\n\n // Steps 1 + 1b — DROP-invalid then DROP-surrogate-boundary. Retain the original index\n // for the stable tie-break in step 2.\n const survivors: Array<{ idx: number; range: MentionRange }> = [];\n for (let idx = 0; idx < ranges.length; idx++) {\n const r = ranges[idx];\n if (r === undefined) continue;\n // Step 1: validity (drop, do not clamp).\n if (r.start < 0 || r.length <= 0 || r.start + r.length > n) continue;\n // Step 1b: surrogate-pair boundary (drop). r.start + r.length <= n is guaranteed\n // by step 1, so both indices are in [0, n].\n if (splitsSurrogatePair(r.start) || splitsSurrogatePair(r.start + r.length)) continue;\n survivors.push({ idx, range: r });\n }\n\n // Step 2: sort ascending by start; ties by length DESCENDING (wider wins); then by\n // original index ascending (stable). Done BEFORE the overlap walk.\n survivors.sort((lhs, rhs) => {\n if (lhs.range.start !== rhs.range.start) return lhs.range.start - rhs.range.start;\n if (lhs.range.length !== rhs.range.length) return rhs.range.length - lhs.range.length; // DESC\n return lhs.idx - rhs.idx;\n });\n\n // Step 3: drop the later overlapper (and exact duplicates as a sub-case) — never merge.\n const kept: MentionRange[] = [];\n let prevEnd = Number.NEGATIVE_INFINITY; // exclusive end of the last KEPT range\n for (const s of survivors) {\n if (s.range.start >= prevEnd) {\n kept.push(s.range);\n prevEnd = s.range.start + s.range.length;\n }\n // else: overlaps (or duplicates) the previous kept range → DROP.\n }\n return kept;\n}\n","/**\n * ReactionFold — pure fold of reaction events into a per-target emoji tally.\n *\n * Algorithm mirrors iOS `ReactionFold` (ReactionFold.swift) byte-for-byte in\n * behaviour: LWW per (target, actorUserId) on (epoch, serverSeq), idempotent\n * on eventClientMsgId.\n *\n * Tie assumption (same as iOS doc-comment):\n * The server guarantees (epoch, serverSeq) is unique per logical event, so\n * exact-order ties cannot occur in practice. The `<=` stale-check (first-\n * arriving wins on an exact tie) is therefore safe. Re-delivery of the SAME\n * logical event is already handled by the seenEvents dedup and never reaches\n * this check.\n */\n\nexport interface ReactionEvent {\n targetClientMsgId: string;\n actorUserId: string;\n emoji: string;\n op: 'add' | 'remove';\n epoch: number;\n serverSeq: number;\n /** Dedup key — unique per logical event (e.g. the reaction message's clientMsgId). */\n eventClientMsgId: string;\n}\n\n/** Lexicographic (epoch, serverSeq) comparison. Returns true if a <= b. */\nfunction orderLte(aEpoch: number, aSeq: number, bEpoch: number, bSeq: number): boolean {\n if (aEpoch !== bEpoch) return aEpoch < bEpoch;\n return aSeq <= bSeq;\n}\n\ninterface UserState {\n orderEpoch: number;\n orderSeq: number;\n emoji: string | null; // null = removed\n}\n\nexport class ReactionFold {\n // Map<targetClientMsgId, Map<actorUserId, UserState>>\n private readonly states = new Map<string, Map<string, UserState>>();\n // Dedup set: eventClientMsgId values already applied.\n private readonly seenEvents = new Set<string>();\n\n ingest(e: ReactionEvent): void {\n // Idempotency: skip if this exact event was already applied.\n if (this.seenEvents.has(e.eventClientMsgId)) return;\n this.seenEvents.add(e.eventClientMsgId);\n\n let byUser = this.states.get(e.targetClientMsgId);\n if (byUser === undefined) {\n byUser = new Map<string, UserState>();\n this.states.set(e.targetClientMsgId, byUser);\n }\n\n const prev = byUser.get(e.actorUserId);\n if (prev !== undefined) {\n // Stale / out-of-order: ignore if new order <= stored order.\n if (orderLte(e.epoch, e.serverSeq, prev.orderEpoch, prev.orderSeq)) return;\n }\n\n byUser.set(e.actorUserId, {\n orderEpoch: e.epoch,\n orderSeq: e.serverSeq,\n emoji: e.op === 'add' ? e.emoji : null,\n });\n }\n\n /**\n * Returns a tally of `{ emoji → sorted userIds[] }` for the given target\n * message. Users whose last event was a `remove` (emoji === null) are\n * excluded. userId arrays are sorted for deterministic output (render\n * stability + golden tests).\n */\n tally(targetClientMsgId: string): Record<string, string[]> {\n const byUser = this.states.get(targetClientMsgId);\n if (byUser === undefined) return {};\n\n const out = new Map<string, string[]>();\n for (const [userId, st] of byUser) {\n if (st.emoji === null) continue;\n let users = out.get(st.emoji);\n if (users === undefined) {\n users = [];\n out.set(st.emoji, users);\n }\n users.push(userId);\n }\n\n // Sort userId arrays for determinism (render stability + golden tests).\n const result: Record<string, string[]> = {};\n for (const [emoji, users] of out) {\n result[emoji] = users.sort();\n }\n return result;\n }\n}\n","/**\n * TimerFold — pure per-chat fold of `timer_set` events into ONE active default\n * cell. LWW on (epoch, serverSeq); idempotent on eventClientMsgId. Mirrors iOS\n * `TimerFold` byte-for-byte in behaviour (and reactions' (epoch,seq) machinery),\n * keyed at the chat level rather than per-target. `actorUserId` is the\n * AUTHENTICATED sender resolved upstream — never a wire field.\n */\nexport interface TimerEvent {\n ttlSeconds: number | null;\n start: 'send' | 'read';\n actorUserId: string;\n epoch: number;\n serverSeq: number;\n eventClientMsgId: string;\n}\n\n/** Lexicographic (epoch, serverSeq) comparison. Returns true if a <= b. */\nfunction orderLte(aEpoch: number, aSeq: number, bEpoch: number, bSeq: number): boolean {\n if (aEpoch !== bEpoch) return aEpoch < bEpoch;\n return aSeq <= bSeq;\n}\n\ninterface Cell {\n orderEpoch: number;\n orderSeq: number;\n ttlSeconds: number | null;\n start: 'send' | 'read';\n actor: string;\n}\n\nexport class TimerFold {\n private cell: Cell | null = null;\n private readonly seenEvents = new Set<string>();\n\n ingest(e: TimerEvent): void {\n // Idempotency: skip if this exact event was already applied.\n if (this.seenEvents.has(e.eventClientMsgId)) return;\n this.seenEvents.add(e.eventClientMsgId);\n\n // Stale / out-of-order: ignore if new order <= stored order. The server\n // guarantees (epoch, serverSeq) is unique per logical event, so exact-order\n // ties cannot occur in practice; first-arriving wins on an exact tie. SAME\n // logical event re-delivery is already caught by the seenEvents dedup above.\n if (\n this.cell !== null &&\n orderLte(e.epoch, e.serverSeq, this.cell.orderEpoch, this.cell.orderSeq)\n ) {\n return;\n }\n this.cell = {\n orderEpoch: e.epoch,\n orderSeq: e.serverSeq,\n ttlSeconds: e.ttlSeconds,\n start: e.start,\n actor: e.actorUserId,\n };\n }\n\n /**\n * The active chat default, or null if no timer_set has applied.\n * `ttlSeconds === null` means DISABLED (still applied — distinct from \"never\n * set\"). `start` is meaningful only when ttlSeconds !== null.\n */\n active(): { ttlSeconds: number | null; start: 'send' | 'read' } | null {\n if (this.cell === null) return null;\n return { ttlSeconds: this.cell.ttlSeconds, start: this.cell.start };\n }\n\n /** The userId behind the winning timer_set (for the optional system line). null if unset. */\n lastActor(): string | null {\n return this.cell?.actor ?? null;\n }\n}\n","// The Chat — the one object a chat screen binds to. A DM or a group, uniformly.\n// Web idiom of the iOS `@Observable Chat`: an event-emitter exposing `onChange`\n// returning an Unsubscribe (matches flags/realtime), plus snapshot getters\n// (messages/members/typing/unreadCount/lastMessage). React hooks wrap it with\n// useSyncExternalStore.\n//\n// WhatsApp-lazy: a direct/group chat opens as a `.draft` (no network); the first\n// `send` MATERIALIZES it (claim → create/get-or-create → add → register), flips\n// to `.active`, wires live subscriptions, and hydrates durable history.\n\nimport { type AnchorTriple, MonotonicClock, remainingSeconds } from './deadline-calculator.js';\nimport { DeleteFold } from './delete-fold.js';\nimport type { IncomingMessage } from './delivery-source.js';\nimport { type AuthorResolution, EditFold } from './edit-fold.js';\nimport {\n type ExpirySpec,\n type MentionRange,\n type MessagingGroup,\n type ReplyRef,\n resolveReply,\n} from './group-messaging.js';\nimport { normalizeMentionRangesUtf16 } from './mention-ranges.js';\nimport { ReactionFold } from './reaction-fold.js';\nimport { TimerFold } from './timer-fold.js';\nimport type {\n ChatKind,\n ChatMember,\n ChatMessage,\n ChatMessageKind,\n ChatState,\n MessageDirection,\n NotifyScope,\n PresenceState,\n ResolvedMention,\n ResolvedReply,\n SentReceipt,\n UnreadView,\n Unsubscribe,\n} from './types.js';\nimport { mintClientMsgId, truncateQuote } from './util.js';\n\n/**\n * The neutral copy shown in place of a deleted-for-everyone message. Cooperative\n * erasure — it does NOT imply cryptographic erasure (the design's honest caveat).\n * The single source of truth for the descriptor, used by BOTH the live Chat render\n * precedence and the coordinator history projection so they render identically.\n */\nexport const DELETED_DESCRIPTOR = '🚫 This message was deleted';\n\n/** The intended participants of a draft chat (held until the first send). */\nexport interface ChatDraft {\n mode: { kind: 'direct'; peerUserId: string } | { kind: 'group'; members: string[] };\n /** The reserved local id a draft shows before it materializes. */\n reservedId: string;\n}\n\n/** The seam the Chat talks to for the work needing the resolved machinery. */\nexport interface ChatBackend {\n selfUserId: string;\n /** Materialize a draft → the active group (DM get-or-create / group create+add). */\n materialize(draft: ChatDraft): Promise<MessagingGroup>;\n sendText(\n group: MessagingGroup,\n text: string,\n replyTo?: ReplyRef | null,\n bodyRanges?: MentionRange[] | null,\n expiry?: ExpirySpec | null,\n ): Promise<{ receipt: SentReceipt; clientMsgId: string }>;\n /** Send a reaction (add/remove an emoji on a target message) through the same\n * MLS application path as `sendText`. Returns the receipt + the wire clientMsgId. */\n sendReaction(\n group: MessagingGroup,\n args: { clientMsgId: string; targetClientMsgId: string; emoji: string; op: 'add' | 'remove' },\n ): Promise<{ receipt: SentReceipt; clientMsgId: string }>;\n /** Send an edit (edit-by-supersession on a target message) through the same MLS\n * application path as `sendText`. Returns the receipt + the wire clientMsgId. */\n sendEdit(\n group: MessagingGroup,\n args: {\n clientMsgId: string;\n targetClientMsgId: string;\n newText: string;\n bodyRanges?: MentionRange[] | null;\n },\n ): Promise<{ receipt: SentReceipt; clientMsgId: string }>;\n /** Send a delete-for-everyone tombstone on a target message through the same MLS\n * application path as `sendText` (the server stays blind — a delete is just\n * another opaque application message). Returns the receipt + the wire clientMsgId. */\n sendDelete(\n group: MessagingGroup,\n args: { clientMsgId: string; targetClientMsgId: string },\n ): Promise<{ receipt: SentReceipt; clientMsgId: string }>;\n history(group: MessagingGroup, limit: number, before?: number): Promise<ChatMessage[]>;\n members(group: MessagingGroup): Promise<ChatMember[]>;\n addMember(group: MessagingGroup, userId: string): Promise<void>;\n removeMember(group: MessagingGroup, userId: string): Promise<void>;\n leave(group: MessagingGroup): Promise<void>;\n registerActive(group: MessagingGroup): void;\n setTyping(group: MessagingGroup, isTyping: boolean): void;\n markRead(group: MessagingGroup, upToServerSeq: number): Promise<void>;\n /** Server-metadata cluster: set/get the per-(user,group) notify scope (mute) and\n * fetch the caller's own unread view. The server stays MLS-blind (opaque metadata). */\n setNotifyScope(group: MessagingGroup, scope: NotifyScope): Promise<NotifyScope>;\n getNotifyScope(group: MessagingGroup): Promise<NotifyScope>;\n unread(group: MessagingGroup): Promise<UnreadView>;\n /** Subscribe live message + conv (presence/typing/read/delivered) for an active group. */\n subscribeLive(group: MessagingGroup, chat: Chat): Unsubscribe;\n /** Resolve a sender device id → its owning user id (for senderUserId). */\n userIdForDevice(group: MessagingGroup, deviceId: string): Promise<string | null>;\n /** Load this chat's persisted delete-for-me suppression keys (durable, no wire). */\n loadSuppressed(group: MessagingGroup): Promise<string[]>;\n /** Persist this chat's delete-for-me suppression keys (durable, no wire). */\n saveSuppressed(group: MessagingGroup, keys: string[]): Promise<void>;\n /** Load this chat's persisted self-elevation dedup keys (durable, no wire) so a\n * re-delivered mention does not re-fire `onMentionElevation` after a cold launch. */\n loadElevated(group: MessagingGroup): Promise<string[]>;\n /** Persist this chat's self-elevation dedup keys (durable, no wire). */\n saveElevated(group: MessagingGroup, keys: string[]): Promise<void>;\n\n // ── Disappearing / TTL (T10) ──\n\n /** Send a per-chat default disappearing-timer control envelope (`timer_set`). Goes\n * through the SAME MLS application path as `sendText` — server-blind, never a bubble.\n * `ttlSeconds === null` disables the default. Returns the receipt + the wire clientMsgId. */\n sendTimerSet(\n group: MessagingGroup,\n args: { clientMsgId: string; ttlSeconds: number | null; start: 'send' | 'read' },\n ): Promise<{ receipt: SentReceipt; clientMsgId: string }>;\n /** The persisted INTEGER `server_seq` tombstone set for a chat — drives transcript\n * exclusion (cold-launch) + redelivery drop (live arrival). Durable, no wire. */\n tombstonedSeqs(group: MessagingGroup): Promise<Set<number>>;\n /** The persisted STRING `client_msg_id` purge set — drives the orphan-fold resolver\n * (`authorOfTarget → 'purged'`) so a late edit DROPs / a late delete no-ops. */\n purgedClientMsgIds(group: MessagingGroup): Promise<Set<string>>;\n /** The persisted write-once anchor triple for a `clientMsgId`, or null if none. */\n anchor(group: MessagingGroup, clientMsgId: string): Promise<AnchorTriple | null>;\n /** Write-once: capture the first-arrival/first-read anchor triple for a `clientMsgId`\n * (a second call for the same id is a no-op, so the deadline never resets). */\n writeAnchorOnce(group: MessagingGroup, clientMsgId: string, a: AnchorTriple): Promise<void>;\n /** Tombstone-first purge commit point: persist the int `server_seq` + string\n * `client_msg_id` purge id in ONE durable record (idempotent). */\n tombstone(group: MessagingGroup, serverSeq: number, clientMsgId: string): Promise<void>;\n}\n\nexport class Chat {\n /** Stable, URL/log-safe id (the grp_ display id once active; a reserved local id while draft). */\n id: string;\n readonly kind: ChatKind;\n private _state: ChatState;\n\n private _group: MessagingGroup | null;\n private _draft: ChatDraft | null;\n private readonly backend: ChatBackend;\n\n private messageList: ChatMessage[] = [];\n private memberCache: ChatMember[] = [];\n private typingList: ChatMember[] = [];\n private presenceStates = new Map<string, PresenceState>();\n private readWatermark = -1;\n private titleOverride: string | null = null;\n\n private readonly seenKeys = new Set<string>();\n /** Index: clientMsgId → { text, senderUserId } for resolveReply lookups. */\n private readonly byClientMsgId = new Map<string, { text: string; senderUserId: string }>();\n /** The single authoritative reaction fold for this chat (live + own-send + history). */\n private readonly reactionFold = new ReactionFold();\n /** The single authoritative edit fold for this chat (live + own-send + history). */\n private readonly editFold = new EditFold();\n /** The single authoritative delete-for-everyone fold (live + own-send + history).\n * A tombstone scrubs its target in place (delete DOMINATES edit at render). */\n private readonly deleteFold = new DeleteFold();\n /** The per-chat default disappearing-timer fold — the latest valid `timer_set` (LWW\n * on (epoch, serverSeq), author = the resolved MLS sender). A `timer_set` is NEVER a\n * bubble; it routes here. The active default governs a subsequent bubble that carries\n * no per-message expiry (disappearing T10). */\n private readonly timerFold = new TimerFold();\n /** Advisory in-memory purge timers, keyed by serverSeq. The DURABLE truth is the\n * persisted anchor + a re-check on every load; this just drives live eviction while\n * the tab is open. Cancelled when the message purges (disappearing T10). */\n private readonly purgeTimers = new Map<number, ReturnType<typeof setTimeout>>();\n /** In-memory mirror of the durable `purgedClientMsgIds` set (the STRING namespace),\n * hydrated from `backend.purgedClientMsgIds` and grown by each live purge. Consulted\n * by `authorOfTarget` so a late edit/delete targeting a TTL-purged message resolves to\n * `'purged'` (DROP / no-op — never resurrects). Namespace-separate from the int seq\n * tombstone (disappearing T10). */\n private readonly purgedCids = new Set<string>();\n private purgedLoaded = false;\n /** delete-for-me suppression keys (clientMsgId, or `seq:<serverSeq>` for legacy)\n * — the message is OMITTED from this view. Local + persisted per chat, NO wire. */\n private readonly suppressed = new Set<string>();\n /** True once the persisted suppression set has been loaded (so the omit applies\n * even on the cold-launch hydrate path before a fresh deleteForMe). */\n private suppressedLoaded = false;\n /** Self-elevation dedup keys (`<selfUserId>|<clientMsgId or seq:n>`). Once a\n * mention of me from another sender fires `onMentionElevation`, its key lands here\n * + is persisted, so a re-delivery / cold-launch re-hydrate never re-fires. */\n private readonly elevated = new Set<string>();\n /** True once the persisted elevation set has been loaded (so a re-delivered mention\n * on the cold-launch hydrate path dedups against the persisted decision). */\n private elevatedLoaded = false;\n /** Per-target BASE (original) text, so the rendered text is `edit ?? original`.\n * Seeded once per target (write-once base) so a 2nd own edit can't corrupt it. */\n private readonly originalTextByClientMsgId = new Map<string, string | null>();\n /** Per-target AUTHOR userId — the EditFold author-gate input (filled at bubble\n * projection time from senderUserId; '' = resolved-but-unknown peer). */\n private readonly authorByClientMsgId = new Map<string, string>();\n private loadedEarliestSeq: number | null = null;\n private historyLoaded = false;\n private wired = false;\n private liveUnsub: Unsubscribe | null = null;\n\n private readonly listeners = new Set<() => void>();\n\n /**\n * Fires ONCE per `(selfUserId, clientMsgId)` when an INCOMING message mentions THIS\n * user from ANOTHER sender (not an edit). The dedup survives re-delivery + reload\n * via the persisted elevation set, so this never double-fires for one mention. The\n * app wires it to a buzz/badge (e.g. an in-app banner). Best-effort cooperative —\n * the SDK guarantees the DECISION, not the buzz. Mirrors iOS `Chat.onMentionElevation`.\n */\n onMentionElevation?: (message: ChatMessage) => void;\n\n /** @internal — obtain via pb.messaging.directChat / groupChat / chat(id). */\n constructor(\n args:\n | { group: MessagingGroup; kind: ChatKind; backend: ChatBackend }\n | { draft: ChatDraft; kind: ChatKind; backend: ChatBackend },\n ) {\n this.backend = args.backend;\n if ('group' in args) {\n this.id = args.group.displayId;\n this.kind = args.kind;\n this._state = 'active';\n this._group = args.group;\n this._draft = null;\n this.seedMembersFromGroup(args.group);\n } else {\n this.id = args.draft.reservedId;\n this.kind = args.kind;\n this._state = 'draft';\n this._group = null;\n this._draft = args.draft;\n this.seedDraftMembers(args.draft);\n }\n }\n\n // ── Observability ──\n\n /** Subscribe to any change in this chat (messages/members/typing/state). */\n onChange(cb: () => void): Unsubscribe {\n this.listeners.add(cb);\n // Lazily wire live + hydrate history on first observe of an active chat.\n this.ensureWired();\n return () => this.listeners.delete(cb);\n }\n\n private emit(): void {\n for (const cb of this.listeners) cb();\n }\n\n // ── Snapshot getters ──\n\n get state(): ChatState {\n return this._state;\n }\n get isDirect(): boolean {\n return this.kind === 'direct';\n }\n get messages(): readonly ChatMessage[] {\n return this.surfaced();\n }\n get members(): readonly ChatMember[] {\n return this.memberCache;\n }\n get typing(): readonly ChatMember[] {\n return this.typingList;\n }\n get lastMessage(): ChatMessage | null {\n return this.surfaced().at(-1) ?? null;\n }\n get unreadCount(): number {\n // A tombstoned (deleted-for-everyone) message carries no real content, so it\n // must not inflate the unread badge (mirrors the iOS T3 fix).\n return this.surfaced().filter(\n (m) => m.direction === 'incoming' && !m.isDeleted && m.serverSeq > this.readWatermark,\n ).length;\n }\n\n /**\n * The RENDER PRECEDENCE — the single composition point (live AND history project\n * through it identically). Over the raw `messageList` (which already carries the\n * folded edit text + reactions + reply):\n * (1) in the delete-for-me suppression set → OMIT the message entirely;\n * (2) else tombstoned (delete-for-everyone) → the neutral \"deleted\" descriptor\n * with reactions/reply/edit HIDDEN (delete DOMINATES edit — short-circuit);\n * (3) else the row as-is (edit overlay + reactions + reply already applied).\n * Pure over (messageList, deleteFold, suppressed) — recomputed on every read so a\n * just-folded delete / just-suppressed key takes effect without rewriting rows.\n */\n private surfaced(): ChatMessage[] {\n const out: ChatMessage[] = [];\n for (const m of this.messageList) {\n const key = this.suppressionKey(m);\n if (this.suppressed.has(key)) continue; // (1) for-me → omit\n // Tombstoned by THIS chat's live fold OR already scrubbed by the coordinator\n // history projection (the cold-launch path — its own fold pre-applied the\n // scrub; the Chat's deleteFold is empty until a live re-delivery).\n const tombstoned =\n (m.clientMsgId && this.deleteFold.isTombstoned(m.clientMsgId)) || m.isDeleted;\n if (tombstoned) {\n // (2) tombstone DOMINATES — scrub to the descriptor, hide everything else\n // (reactions, reply, edit AND mentions — delete dominates mentions too).\n out.push({\n ...m,\n text: DELETED_DESCRIPTOR,\n reactions: {},\n replyTo: null,\n edited: false,\n isDeleted: true,\n mentions: [],\n // delete DOMINATES disappearing too — a tombstoned message carries no deadline.\n expiresAt: null,\n });\n continue;\n }\n out.push(m); // (3) raw row (edit/reactions/reply already overlaid)\n }\n return out;\n }\n\n /** The delete-for-me suppression key: clientMsgId when present, else `seq:<n>`. */\n private suppressionKey(m: ChatMessage): string {\n return m.clientMsgId ? m.clientMsgId : `seq:${m.serverSeq}`;\n }\n get title(): string {\n if (this.titleOverride) return this.titleOverride;\n if (this._group?.name) return this._group.name;\n // A DM is named after the PERSON, in EVERY state. `kind` — not `_draft` —\n // is what makes it a DM: materializeIfNeeded clears `_draft`, so keying on\n // it renamed a live one-to-one conversation to `Group <last4>` the moment\n // its first message was sent. The roster survives that transition (the\n // draft seed, then refreshMembers), so it is the peer's home in both.\n if (this.kind === 'direct') {\n const peer = this.memberCache.find((m) => !m.isSelf);\n return peer?.displayName ?? 'Chat';\n }\n if (this._draft) return 'Group';\n // Active group, no name (web has no exporter yet) — friendly fallback.\n const tail = this.id.startsWith('grp_') ? this.id.slice(4) : this.id;\n const short = tail.slice(-4);\n return short ? `Group ${short}` : 'Group';\n }\n\n presence(userId: string): PresenceState | null {\n return this.presenceStates.get(userId) ?? null;\n }\n\n // ── Wiring (live + history) ──\n\n private ensureWired(): void {\n if (this.wired || this._state !== 'active' || !this._group) return;\n this.wired = true;\n this.liveUnsub = this.backend.subscribeLive(this._group, this);\n // Load the persisted delete-for-me suppression set FIRST so a for-me'd message\n // stays omitted from the moment history hydrates (the cold-launch parity). The\n // hydrate is async too; the load races it but a late load still re-emits so the\n // omit applies (surfaced() reads the set live on every render).\n void this.loadSuppressed();\n // Load the persisted self-elevation dedup set FIRST too, so a re-delivered mention\n // already-buzzed in a previous session does not re-fire on the cold-launch hydrate.\n void this.loadElevated();\n // Load the durable purge set (STRING namespace) BEFORE hydrating history, so the live\n // fold's `authorOfTarget` resolves a TTL-purged target to 'purged' from the first merge\n // (a late edit/delete against a disappeared message DROPs/no-ops). Chained so the order\n // holds; the cold-launch transcript exclusion itself comes from projectHistory.\n void this.loadPurged().then(() => this.hydrateHistory());\n void this.refreshMembers();\n }\n\n /** Hydrate the durable `purgedClientMsgIds` set (once) into the in-memory mirror so the\n * live Edit/Delete fold author-gate sees TTL-purged targets as 'purged' on cold launch\n * (disappearing T10). No re-emit: it only gates the orphan-fold resolution. */\n private async loadPurged(): Promise<void> {\n if (this.purgedLoaded || !this._group) return;\n this.purgedLoaded = true;\n try {\n const ids = await this.backend.purgedClientMsgIds(this._group);\n for (const id of ids) this.purgedCids.add(id);\n } catch {\n // best-effort — a load failure just means the mirror starts empty this session\n }\n }\n\n /** Hydrate the persisted delete-for-me suppression keys (once), then re-emit so\n * any already-surfaced suppressed message is omitted (cold-launch parity). */\n private async loadSuppressed(): Promise<void> {\n if (this.suppressedLoaded || !this._group) return;\n this.suppressedLoaded = true;\n try {\n const keys = await this.backend.loadSuppressed(this._group);\n let changed = false;\n for (const k of keys) {\n if (!this.suppressed.has(k)) {\n this.suppressed.add(k);\n changed = true;\n }\n }\n if (changed) this.emit();\n } catch {\n // best-effort — a load failure just means the set starts empty this session\n }\n }\n\n /** Hydrate the persisted self-elevation dedup keys (once). No re-emit: the set only\n * gates the elevation DECISION, it does not change what renders. */\n private async loadElevated(): Promise<void> {\n if (this.elevatedLoaded || !this._group) return;\n this.elevatedLoaded = true;\n try {\n const keys = await this.backend.loadElevated(this._group);\n for (const k of keys) this.elevated.add(k);\n } catch {\n // best-effort — a load failure just means the set starts empty this session\n }\n }\n\n private async hydrateHistory(): Promise<void> {\n if (this.historyLoaded || !this._group) return;\n this.historyLoaded = true;\n const entries = await this.backend.history(this._group, 50);\n this.mergeHistory(entries);\n }\n\n private mergeHistory(incoming: ChatMessage[]): void {\n let changed = false;\n // Pass 1: populate byClientMsgId + the edit base/author maps from all new\n // history rows (so replies + dangling edits within the same page and against\n // already-indexed live messages resolve correctly).\n for (const m of incoming) {\n if (m.serverSeq <= 0) continue;\n // A coordinator-scrubbed delete row carries the descriptor as `text`; never\n // index it as reply text nor seed it as an edit base (it has no real body).\n if (m.clientMsgId && m.text !== null && !m.isDeleted) {\n // Index ANY row with a non-empty clientMsgId + non-null text, regardless\n // of direction. For incoming rows senderUserId is null — use '' so the\n // lookup entry exists and resolveReply returns state:'resolved' (real text\n // wins over attacker-controlled preview). Spoof defense covers peer messages.\n this.byClientMsgId.set(m.clientMsgId, {\n text: m.text,\n senderUserId: m.senderUserId ?? '',\n });\n }\n // Seed the edit base/author. The coordinator's page-local fold already\n // overlaid `text`/`edited` onto this row, but the row's text IS the latest\n // edit (or original) — for the Chat's authoritative fold we want the ORIGINAL\n // base. When the fold knows nothing for this target the upstream text IS the\n // original; when it does know (cross-page/live), recomputeEdit uses fold.text.\n if (m.clientMsgId && !m.isDeleted) {\n this.seedEditBase(m.clientMsgId, m.text, m.senderUserId ?? '');\n }\n }\n this.editFold.reevaluateHeld(this.authorOfTarget);\n // Pass 2: merge into messageList (replyTo is already resolved by coordinator).\n // The coordinator already page-local-folded reactions + edits and attached each\n // surfaced row's tally/text; applyReactionTally + applyEditOverlay let the\n // Chat's authoritative folds (live/own-send/cross-page) override when they hold.\n for (const m of incoming) {\n if (m.serverSeq <= 0) continue;\n const key = this.internalKey(m.serverSeq);\n if (this.seenKeys.has(key)) continue;\n this.seenKeys.add(key);\n // A live tombstone parked in the Chat's fold re-checks its deferred gate when\n // an older target pages in (loadEarlier). A coordinator-scrubbed row already\n // carries isDeleted; surfaced() honors either path (delete DOMINATES).\n if (m.clientMsgId && !m.isDeleted) {\n this.deleteFold.reevaluatePending(m.clientMsgId, this.authorOfTarget(m.clientMsgId));\n }\n // Mentions T6: re-resolve roster names on the projection's null-name spans\n // (resolution is LIVE), then overlay the live folds (an edit replaces mentions).\n this.messageList.push(\n this.applyEditOverlay(this.applyReactionTally(this.resolveMentionNames(m))),\n );\n changed = true;\n this.loadedEarliestSeq = Math.min(this.loadedEarliestSeq ?? m.serverSeq, m.serverSeq);\n // Disappearing T10 (the cold-launch re-arm — the M1-trap-confirmed gap): re-arm the\n // purge from the projection's derived EFFECTIVE deadline (own per-message expiry ELSE\n // the chat default folded forward-only AS OF this message's order, computed in the\n // pure projectHistory). This makes a default-governed message re-arm from HISTORY on\n // a fresh tab, not only from a live timer_set (mirrors iOS mergeHistory armPurge).\n if (m.expiresAt && m.clientMsgId && !m.isDeleted) {\n void this.armFromDeadline(m.expiresAt, m.serverSeq, m.clientMsgId);\n }\n }\n if (changed) {\n this.messageList.sort((a, b) => a.serverSeq - b.serverSeq);\n this.emit();\n }\n }\n\n /** @internal — called by the backend's live subscription. */\n async ingestLive(incoming: IncomingMessage): Promise<void> {\n if (!this._group) return;\n if (incoming.kind === 'commit') {\n await this.refreshMembers();\n return;\n }\n if (incoming.serverSeq <= 0) return; // welcome/proposal — not a row\n // Disappearing T10 (redelivery-drop, the M1-trap arrival half): a message whose\n // server_seq is already TTL-tombstoned in the durable store is DROPPED on arrival and\n // NEVER re-surfaced. A purge is crash-safe because the tombstone is written FIRST; if\n // the same seq is re-delivered after a purge it must not resurface (the cold-launch\n // projectHistory enforces the SAME exclusion on reload). Checked BEFORE the seenKeys\n // dedup so a purged-then-redelivered seq is dropped, not silently consumed.\n if ((await this.backend.tombstonedSeqs(this._group)).has(incoming.serverSeq)) return;\n const key = this.internalKey(incoming.serverSeq);\n if (this.seenKeys.has(key)) return;\n this.seenKeys.add(key);\n let senderUser: string | null = null;\n if (incoming.senderDeviceId) {\n senderUser = await this.backend.userIdForDevice(this._group, incoming.senderDeviceId);\n }\n const direction: MessageDirection =\n senderUser !== null && senderUser === this.backend.selfUserId ? 'outgoing' : 'incoming';\n\n // Disappearing T10: a `timer_set` is a control envelope, NOT a bubble. Route it into\n // the chat-default `TimerFold` (actor = the AUTHENTICATED sender, resolved device→user;\n // own-send → self). An unresolvable actor DROPs it (no spoofable timer). Never appends\n // a row, never notifies, never increments unread (mirrors iOS T5).\n if (incoming.envelopeType === 'timer_set' && incoming.timer) {\n const actorUserId = direction === 'outgoing' ? this.backend.selfUserId : senderUser;\n if (actorUserId !== null) {\n this.timerFold.ingest({\n ttlSeconds: incoming.timer.ttlSeconds,\n start: incoming.timer.start,\n actorUserId,\n epoch: incoming.epoch,\n serverSeq: incoming.serverSeq,\n eventClientMsgId: incoming.clientMsgId,\n });\n }\n return; // no bubble, no unread, no push\n }\n\n // A reaction is NEVER a visible bubble — it folds into the target's tally and\n // re-emits, so it also never notifies like a message (mirrors iOS T3).\n if (incoming.envelopeType === 'reaction' && incoming.reaction) {\n // actorUserId = the resolved sender (own-send → selfUserId). An unresolvable\n // sender would yield a spoofable actor — drop it (no fold).\n const actorUserId = direction === 'outgoing' ? this.backend.selfUserId : senderUser;\n if (actorUserId !== null) {\n this.reactionFold.ingest({\n targetClientMsgId: incoming.reaction.targetClientMsgId,\n actorUserId,\n emoji: incoming.reaction.emoji,\n op: incoming.reaction.op,\n epoch: incoming.epoch,\n serverSeq: incoming.serverSeq,\n eventClientMsgId: incoming.clientMsgId,\n });\n this.recomputeReactions(incoming.reaction.targetClientMsgId);\n }\n return;\n }\n\n // An edit is NEVER a visible bubble — it folds the latest author edit's text\n // onto the target in place and re-emits, so it also never notifies like a\n // message (mirrors iOS T3). editorUserId = the resolved sender (own-send →\n // selfUserId); null → the fold HOLDs until the roster resolves.\n if (incoming.envelopeType === 'edit' && incoming.edit) {\n const editorUserId = direction === 'outgoing' ? this.backend.selfUserId : senderUser;\n this.editFold.ingest(\n {\n targetClientMsgId: incoming.edit.targetClientMsgId,\n editorUserId,\n newText: incoming.edit.newText,\n epoch: incoming.epoch,\n serverSeq: incoming.serverSeq,\n eventClientMsgId: incoming.clientMsgId,\n // Mentions T6: carry the edit's REPLACEMENT ranges so the edited message's\n // mentions reflect them (recomputed against the new text on recomputeEdit).\n bodyRanges: incoming.bodyRanges,\n },\n this.authorOfTarget,\n );\n this.recomputeEdit(incoming.edit.targetClientMsgId);\n return;\n }\n\n // A delete-for-everyone is NEVER a visible bubble — it folds a tombstone onto\n // its target (author-gated) and re-emits, so it also never notifies like a\n // message (mirrors iOS T3). actorUserId = the resolved sender (own-send →\n // selfUserId); null → the fold parks it in held until the roster resolves. The\n // authorOfTarget closure resolves the TARGET message's author for the gate.\n if (incoming.envelopeType === 'delete' && incoming.delete) {\n const actorUserId = direction === 'outgoing' ? this.backend.selfUserId : senderUser;\n this.deleteFold.ingest(\n {\n targetClientMsgId: incoming.delete.targetClientMsgId,\n actorUserId,\n epoch: incoming.epoch,\n serverSeq: incoming.serverSeq,\n eventClientMsgId: incoming.clientMsgId,\n },\n this.authorOfTarget,\n );\n // A tombstone that arrived BEFORE its target lands parked in pending; the\n // target's own insert below re-checks it. Re-emit so an in-order scrub shows.\n this.emit();\n return;\n }\n\n // clientMsgId and replyRef are decoded by the delivery source (decodeEnvelope)\n // and carried on IncomingMessage. Legacy/system messages carry empty string / null.\n const incomingClientMsgId = incoming.clientMsgId;\n const incomingReplyRef = incoming.replyRef;\n\n // Resolve reply if present.\n let resolvedReplyTo: ResolvedReply | null = null;\n if (incomingReplyRef) {\n resolvedReplyTo = resolveReply(incomingReplyRef, (id) => this.byClientMsgId.get(id) ?? null);\n }\n\n // Mentions T6: resolve the decode-time mention spans (normalize + roster name)\n // for this NORMAL text bubble. A mention is carried on the message's own text (no\n // fold) — the bubble renders normally with these spans for highlighting.\n const mentions = this.resolveMentions(incoming.text, incoming.bodyRanges);\n\n const msg: ChatMessage = {\n id: this.publicId(incoming.serverSeq),\n kind: this.kindOf(incoming),\n direction,\n senderUserId: senderUser,\n text: incoming.text,\n serverSeq: incoming.serverSeq,\n sentAt: incoming.receivedAt,\n clientMsgId: incomingClientMsgId,\n replyTo: resolvedReplyTo,\n // Attach any tally already folded for this message (a reaction that arrived\n // BEFORE its target — the dangling case — renders the moment the target lands).\n reactions: incomingClientMsgId ? this.reactionFold.tally(incomingClientMsgId) : {},\n // Default false; applyEditOverlay below folds any edit that arrived first.\n edited: false,\n // Default false; surfaced() applies the tombstone scrub if a delete folded.\n isDeleted: false,\n mentions,\n // Disappearing T10: the LOCAL deadline (own per-message expiry ELSE the chat default\n // active AS OF arrival). null when this message is non-disappearing.\n expiresAt: this.deadlineFor(this.toExpirySpec(incoming.expiry) ?? this.defaultExpiry()),\n };\n\n // Self-elevation decision (best-effort cooperative, dedup-once via the persisted\n // set). Compute on the INCOMING bubble BEFORE projection — an edit never lands here\n // (it routed to the EditFold above), satisfying the not-edit gate.\n this.elevateIfMentioned(msg, mentions, senderUser, incoming.envelopeType);\n\n // Index non-empty clientMsgId for future resolveReply lookups.\n // Index ANY incoming message (senderUser may be null for peers); use '' for\n // the senderUserId so resolveReply still returns state:'resolved' (real text\n // wins over attacker-controlled preview). Spoof defense covers peer messages.\n if (incomingClientMsgId && incoming.text !== null) {\n this.byClientMsgId.set(incomingClientMsgId, {\n text: incoming.text,\n senderUserId: senderUser ?? '',\n });\n }\n\n // Record this bubble's BASE text + AUTHOR for the edit fold BEFORE\n // reevaluateHeld, so a dangling edit whose target just arrived author-gates\n // correctly. Base text is seeded once (write-once) so a folded edit overlay\n // never overwrites the original we render `edit ?? original` against.\n if (incomingClientMsgId) {\n this.seedEditBase(incomingClientMsgId, incoming.text, senderUser);\n this.editFold.reevaluateHeld(this.authorOfTarget);\n // A pending/held tombstone for this just-arrived target re-checks its\n // deferred author-gate here — a forged early tombstone is discarded (the\n // message renders), a genuine one scrubs (mirrors iOS T3 reevaluatePending).\n this.deleteFold.reevaluatePending(\n incomingClientMsgId,\n this.authorOfTarget(incomingClientMsgId),\n );\n }\n\n this.messageList.push(this.applyEditOverlay(msg));\n this.messageList.sort((a, b) => a.serverSeq - b.serverSeq);\n this.loadedEarliestSeq = Math.min(\n this.loadedEarliestSeq ?? incoming.serverSeq,\n incoming.serverSeq,\n );\n this.emit();\n // Disappearing T10: a normal bubble carrying a per-message `expiry` arms its purge on\n // first decrypt-and-display (the durable write-once anchor + the advisory timer); a\n // bubble with NO per-message expiry falls back to the chat default (`timerFold`).\n // own-expiry WINS over the default. Fire-and-forget — best-effort; never blocks ingest.\n void this.armPurge(\n this.toExpirySpec(incoming.expiry) ?? this.defaultExpiry(),\n incoming.serverSeq,\n incomingClientMsgId,\n );\n }\n\n // ── Disappearing (TTL — T10) ──\n\n /** Normalize a decoded `IncomingMessage.expiry` / `StoredMessage.expiry` into the\n * `ExpirySpec` the arm path consumes (or null when absent). */\n private toExpirySpec(\n e:\n | { v: number; ttlSeconds: number; start: 'send' | 'read'; senderSendTs: number | null }\n | null\n | undefined,\n ): ExpirySpec | null {\n return e\n ? { v: e.v, ttlSeconds: e.ttlSeconds, start: e.start, senderSendTs: e.senderSendTs }\n : null;\n }\n\n /** The chat-default expiry derived from the active `timer_set` fold, as an `ExpirySpec`\n * so a bubble with no per-message expiry inherits it. null when no default is active or\n * the default was explicitly DISABLED (`ttlSeconds === null`). `senderSendTs` is null —\n * the default has no per-message sender clock; the arrival anchor drives the deadline\n * (mirrors iOS `defaultExpiry()`). */\n private defaultExpiry(): ExpirySpec | null {\n const active = this.timerFold.active();\n if (!active || active.ttlSeconds === null) return null;\n return { v: 1, ttlSeconds: active.ttlSeconds, start: active.start, senderSendTs: null };\n }\n\n /** The surfaced display deadline for an effective expiry (a local, monotonic-derived\n * value — the wall-clock projection of the TTL from now). null for a non-disappearing\n * message. The durable purge is driven by `armPurge`'s write-once anchor; this is the\n * UI countdown baseline. */\n private deadlineFor(expiry: ExpirySpec | null): Date | null {\n if (!expiry) return null;\n return new Date(Date.now() + expiry.ttlSeconds * 1000);\n }\n\n /** Arm a message's TTL purge on first decrypt-and-display. Captures the WRITE-ONCE\n * monotonic/wall/boot anchor (so the deadline survives a reload — a re-arm after relaunch\n * reads back the ORIGINAL capture, never a fresh one → the deadline never resets),\n * computes the remaining time via `remainingSeconds`, applies the send-anchor clamp\n * `min(sender_send_ts+ttl, first_arrival+ttl)` (read-anchor uses the write-once first-read\n * capture), then either purges immediately or schedules an advisory `setTimeout`. A null\n * expiry / empty clientMsgId is a no-op. Mirrors iOS `armPurge`. */\n private async armPurge(\n expiry: ExpirySpec | null,\n serverSeq: number,\n clientMsgId: string,\n ): Promise<void> {\n if (!expiry || !clientMsgId || !this._group) return;\n const group = this._group;\n // Write-once arrival/first-read anchor (durable so the deadline survives a reload).\n const fresh: AnchorTriple = {\n mAnchorMs: MonotonicClock.nowMs(),\n wAnchorEpochMs: MonotonicClock.nowWallEpochMs(),\n bAnchorToken: MonotonicClock.bootToken(),\n };\n await this.backend.writeAnchorOnce(group, clientMsgId, fresh);\n // Use the PERSISTED anchor (the write-once one) for the deadline math.\n const effective = (await this.backend.anchor(group, clientMsgId)) ?? fresh;\n const result = remainingSeconds({\n ttlSeconds: expiry.ttlSeconds,\n anchor: effective,\n nowMonotonicMs: MonotonicClock.nowMs(),\n nowWallEpochMs: MonotonicClock.nowWallEpochMs(),\n nowBootToken: MonotonicClock.bootToken(),\n });\n // Send-anchor clamp: the sender's intended deadline is sender_send_ts + ttl; a\n // clock-skew / late arrival can only SHORTEN, never extend, the message's life.\n let purgeInSeconds: number | null;\n if (result.kind === 'purgeNow') {\n purgeInSeconds = 0;\n } else if (expiry.start === 'send' && expiry.senderSendTs !== null) {\n const sendRemaining =\n expiry.senderSendTs + expiry.ttlSeconds - MonotonicClock.nowWallEpochMs() / 1000;\n purgeInSeconds = sendRemaining <= 0 ? 0 : Math.min(result.seconds, sendRemaining);\n } else {\n purgeInSeconds = result.seconds;\n }\n // Cancel any prior timer for this seq (idempotent re-arm) before scheduling.\n const prior = this.purgeTimers.get(serverSeq);\n if (prior) clearTimeout(prior);\n this.purgeTimers.delete(serverSeq);\n if (purgeInSeconds <= 0) {\n await this.purge(serverSeq, clientMsgId);\n return;\n }\n const handle = setTimeout(() => {\n void this.purge(serverSeq, clientMsgId);\n }, purgeInSeconds * 1000);\n this.purgeTimers.set(serverSeq, handle);\n }\n\n /** Re-arm a purge from a derived deadline (cold-launch hydrate path). The deadline is\n * the projection's monotonic-derived `expiresAt`; schedule an advisory timer for the\n * remaining time (purge immediately if the deadline has already passed). The durable\n * tombstone is written by `purge` when it fires (the crash-safe commit point). */\n private async armFromDeadline(\n deadline: Date,\n serverSeq: number,\n clientMsgId: string,\n ): Promise<void> {\n if (!this._group) return;\n const remainingMs = deadline.getTime() - Date.now();\n const prior = this.purgeTimers.get(serverSeq);\n if (prior) clearTimeout(prior);\n this.purgeTimers.delete(serverSeq);\n if (remainingMs <= 0) {\n await this.purge(serverSeq, clientMsgId);\n return;\n }\n const handle = setTimeout(() => {\n void this.purge(serverSeq, clientMsgId);\n }, remainingMs);\n this.purgeTimers.set(serverSeq, handle);\n }\n\n /** Purge message M (TTL eviction). TOMBSTONE-FIRST (the crash-safe commit point):\n * persist the `server_seq` tombstone + the `client_msg_id` purge id in ONE durable\n * record, THEN drop M's body from `messageList` + `emit()`, then re-evaluate any HELD\n * edit / PARKED delete targeting the now-purged cid so an orphan annotation DROPs/no-ops\n * (the resolver now returns `'purged'`). Idempotent. Mirrors iOS `purge`. */\n private async purge(serverSeq: number, clientMsgId: string): Promise<void> {\n if (!this._group) return;\n const prior = this.purgeTimers.get(serverSeq);\n if (prior) clearTimeout(prior);\n this.purgeTimers.delete(serverSeq);\n // Tombstone FIRST (durable commit point) — records clientMsgId in purgedClientMsgIds.\n await this.backend.tombstone(this._group, serverSeq, clientMsgId);\n if (clientMsgId) this.purgedCids.add(clientMsgId); // live-mirror the purge set\n // Drop the body from the transcript, then re-emit.\n this.messageList = this.messageList.filter((m) => m.serverSeq !== serverSeq);\n this.seenKeys.delete(this.internalKey(serverSeq));\n this.emit();\n // The purge now resolves this cid to 'purged' — re-evaluate any held edit / parked\n // delete so an orphan annotation targeting the disappeared message DROPs/no-ops.\n this.editFold.reevaluateHeld(this.authorOfTarget);\n if (clientMsgId) {\n this.deleteFold.reevaluatePending(clientMsgId, this.authorOfTarget(clientMsgId));\n }\n }\n\n /** The Edit/Delete fold author-gate input via {@link AuthorResolution} (disappearing\n * T10 — the orphan-aware resolver): `'purged'` when the target's clientMsgId is in the\n * durable purge set (a late edit DROPs / a late delete no-ops — never resurrects a\n * disappeared message); `'author'` when its author is locally known → run the\n * author-gate; `'unknown'` otherwise → HOLD. The live twin of `projectHistory`'s\n * resolver. Captured as a bound arrow so it can be passed to the pure folds. */\n private readonly authorOfTarget = (targetClientMsgId: string): AuthorResolution => {\n if (this.purgedCids.has(targetClientMsgId)) return { kind: 'purged' };\n const a = this.authorByClientMsgId.get(targetClientMsgId);\n return a !== undefined ? { kind: 'author', userId: a } : { kind: 'unknown' };\n };\n\n // ── Mentions (mentions T6) ──\n\n /** Resolve a message's decode-time mention spans: NORMALIZE the raw `body_ranges`\n * against `text` (the pinned `normalizeMentionRangesUtf16` cross-SDK contract) then\n * resolve each surviving range's `mentionedUserId` to a roster display name. An id\n * not in the roster resolves to `null` (the renderer falls back to the `text` slice).\n * Pure over (text, bodyRanges, memberCache); never throws. Mirrors iOS T3. */\n private resolveMentions(\n text: string | null,\n bodyRanges: MentionRange[] | null | undefined,\n ): ResolvedMention[] {\n if (text === null || !bodyRanges || bodyRanges.length === 0) return [];\n const normalized = normalizeMentionRangesUtf16(bodyRanges, text);\n if (normalized.length === 0) return [];\n return normalized.map((r) => ({\n start: r.start,\n length: r.length,\n mentionedUserId: r.mentionedUserId,\n displayName: this.displayNameOf(r.mentionedUserId),\n }));\n }\n\n /** Re-resolve the roster display name on already-NORMALIZED spans (the history\n * projection produces them with null names — resolution is LIVE, not snapshotted).\n * A member rename then reflects on old messages. Returns the message unchanged when\n * it has no mentions (the common case) or no name changed. Mirrors iOS T3. */\n private resolveMentionNames(m: ChatMessage): ChatMessage {\n // Tolerate a row that predates the mentions field (an older persisted projection /\n // a hand-built history row) — treat absent as `[]`, never throw.\n if (!m.mentions || m.mentions.length === 0) {\n return m.mentions ? m : { ...m, mentions: [] };\n }\n let changed = false;\n const reresolved = m.mentions.map((span) => {\n const name = this.displayNameOf(span.mentionedUserId);\n if (name === span.displayName) return span;\n changed = true;\n return { ...span, displayName: name };\n });\n if (!changed) return m;\n return { ...m, mentions: reresolved };\n }\n\n /** The WINNING edit's resolved mentions for a target (normalize its replacement\n * ranges against the new text + roster names), or `[]` if no winning edit / no\n * ranges. The edited message's mentions reflect the EDIT's ranges (mirrors iOS T3). */\n private editMentions(targetClientMsgId: string, newText: string | null): ResolvedMention[] {\n const ranges = this.editFold.bodyRanges(targetClientMsgId);\n if (!ranges) return [];\n return this.resolveMentions(newText, ranges);\n }\n\n /** Resolve a userId → its roster display name (null if not a known member). */\n private displayNameOf(userId: string): string | null {\n return this.memberCache.find((mm) => mm.userId === userId)?.displayName ?? null;\n }\n\n /** Compute the SELF-ELEVATION decision for a freshly-ingested INCOMING bubble and,\n * when it fires, record the dedup key (persisted) + invoke `onMentionElevation`.\n * Gate (mirrors iOS T3): a surviving mention targets THIS user AND the sender is not\n * me AND it's NOT an edit AND the `(selfUserId, clientMsgId|seq)` key isn't already\n * elevated. Dedup-once: the in-memory set gates the session, the persisted set\n * survives reload. An EDIT never reaches here (it folds, not a bubble) — the\n * `envelopeType !== 'edit'` guard is belt-and-braces. */\n private elevateIfMentioned(\n message: ChatMessage,\n mentions: ResolvedMention[],\n senderUserId: string | null,\n envelopeType: string | undefined,\n ): void {\n const me = this.backend.selfUserId;\n if (envelopeType === 'edit') return; // edits never elevate\n if (senderUserId === me) return; // self-mention never buzzes me\n if (!mentions.some((mm) => mm.mentionedUserId === me)) return; // not mentioned\n const idPart = message.clientMsgId ? message.clientMsgId : `seq:${message.serverSeq}`;\n const key = `${me}|${idPart}`;\n if (this.elevated.has(key)) return; // already elevated → no re-buzz\n this.elevated.add(key);\n // Persist the dedup set (fire-and-forget — the in-memory record already gates this\n // session; the write makes it survive a cold launch).\n if (this._group) {\n void this.backend.saveElevated(this._group, [...this.elevated]).catch(() => {});\n }\n this.onMentionElevation?.(message);\n }\n\n /** Seed the per-target base text + author for the edit fold. Base is write-once\n * (a later own/peer edit must not overwrite the original we render against). The\n * author is (re)recorded whenever a non-empty resolution is available. */\n private seedEditBase(clientMsgId: string, text: string | null, author: string | null): void {\n if (!this.originalTextByClientMsgId.has(clientMsgId)) {\n this.originalTextByClientMsgId.set(clientMsgId, text);\n }\n if (author !== null) this.authorByClientMsgId.set(clientMsgId, author);\n }\n\n /**\n * Rebuild the target message's `reactions` from the authoritative fold and\n * re-emit. No-op when the target isn't present yet (its tally is attached the\n * moment it lands, via the append/merge paths) or when the tally is unchanged.\n */\n private recomputeReactions(targetClientMsgId: string): void {\n if (!targetClientMsgId) return;\n const tally = this.reactionFold.tally(targetClientMsgId);\n let changed = false;\n this.messageList = this.messageList.map((m) => {\n if (m.clientMsgId !== targetClientMsgId) return m;\n if (sameReactions(m.reactions, tally)) return m;\n changed = true;\n return { ...m, reactions: tally };\n });\n if (changed) this.emit();\n }\n\n /**\n * Overlay the authoritative fold's tally onto a message as it is appended/merged.\n * The Chat fold WINS when it has a non-empty tally; otherwise the tally already\n * attached upstream (the coordinator's page-local history fold) is preserved.\n */\n private applyReactionTally(m: ChatMessage): ChatMessage {\n if (!m.clientMsgId) return m;\n const tally = this.reactionFold.tally(m.clientMsgId);\n if (Object.keys(tally).length === 0) return m; // fold has nothing — keep upstream tally\n if (sameReactions(m.reactions, tally)) return m;\n return { ...m, reactions: tally };\n }\n\n /**\n * Rebuild the target message's rendered `text` + `edited` flag from the\n * authoritative edit fold and re-emit, PRESERVING `.reactions` and `.replyTo`\n * (the reaction-polish lesson — never clobber). text = `editFold.text(cid) ??\n * base`; base is the seeded original so a forged/ignored edit leaves it intact.\n * No-op when the target isn't present yet (the fold already recorded it; the\n * overlay applies the moment the target lands) or when unchanged.\n */\n private recomputeEdit(targetClientMsgId: string): void {\n if (!targetClientMsgId) return;\n const editText = this.editFold.text(targetClientMsgId);\n const foldEdited = this.editFold.isEdited(targetClientMsgId);\n let changed = false;\n this.messageList = this.messageList.map((m) => {\n if (m.clientMsgId !== targetClientMsgId) return m;\n const base = this.originalTextByClientMsgId.has(targetClientMsgId)\n ? (this.originalTextByClientMsgId.get(targetClientMsgId) ?? null)\n : m.text;\n const text = editText ?? base;\n // Write-once edited: the Chat fold OR an already-set flag (e.g. the\n // coordinator's page-local history projection). Never downgrade true → false.\n const edited = foldEdited || m.edited;\n // Mentions T6: when an edit WINS, the edited message's mentions reflect the\n // EDIT's replacement ranges (recomputed against the new text); otherwise keep\n // the message's existing mentions.\n const mentions = editText !== null ? this.editMentions(targetClientMsgId, text) : m.mentions;\n if (m.text === text && m.edited === edited && sameMentions(m.mentions, mentions)) return m;\n changed = true;\n return { ...m, text, edited, mentions }; // PRESERVES reactions + replyTo (spread keeps them)\n });\n if (changed) this.emit();\n }\n\n /**\n * Overlay the authoritative edit fold's winning text + flag onto a message as it\n * is appended/merged. The fold WINS when it has an edit for this target;\n * otherwise the upstream `text`/`edited` (e.g. the coordinator's page-local\n * history fold) is preserved. PRESERVES reactions + replyTo.\n */\n private applyEditOverlay(m: ChatMessage): ChatMessage {\n if (!m.clientMsgId) return m;\n const editText = this.editFold.text(m.clientMsgId);\n const foldEdited = this.editFold.isEdited(m.clientMsgId);\n if (editText === null && !foldEdited) return m; // fold has nothing — keep upstream\n const text = editText ?? m.text;\n // Write-once: never downgrade an already-edited flag (the upstream projection).\n const edited = foldEdited || m.edited;\n // Mentions T6: when the LIVE fold has a winning edit for this target, the edited\n // message's mentions come from that edit's replacement ranges; otherwise keep the\n // mentions the message ALREADY carries (e.g. the coordinator's page-local overlay).\n const mentions = editText !== null ? this.editMentions(m.clientMsgId, text) : m.mentions;\n if (m.text === text && m.edited === edited && sameMentions(m.mentions, mentions)) return m;\n return { ...m, text, edited, mentions };\n }\n\n /** @internal — called by the backend's conv subscription. */\n applyConv(event: string, payload: Record<string, unknown>): void {\n const userId = typeof payload.user_id === 'string' ? payload.user_id : null;\n if (!userId || userId === this.backend.selfUserId) return;\n if (event === 'typing') {\n const isTyping = payload.typing === true;\n const member = this.memberCache.find((m) => m.userId === userId) ?? {\n id: userId,\n userId,\n displayName: null,\n role: 'member' as const,\n isSelf: false,\n };\n this.typingList = isTyping\n ? [...this.typingList.filter((m) => m.userId !== userId), member]\n : this.typingList.filter((m) => m.userId !== userId);\n this.emit();\n } else if (event === 'presence') {\n const online = payload.online === true;\n const ts = typeof payload.ts === 'number' ? payload.ts : null;\n this.presenceStates.set(userId, {\n userId,\n isOnline: online,\n lastSeenAt: ts ? new Date(ts * 1000) : null,\n });\n this.emit();\n } else if (event === 'delivered' || event === 'read') {\n // Server-metadata cluster — the double-tick fold (parity with iOS T11). A peer's\n // `delivered`/`read` broadcast carries the acking device_id + an `up_to_server_seq`\n // high-watermark. We fold it onto THIS user's OWN (outgoing) messages whose\n // serverSeq is at or below the acked seq, USER-LEVEL (any peer/device counts) and\n // MONOTONIC (`max` — a stale lower incoming tick NEVER lowers the displayed tick;\n // the same guard the iOS `foldDeliveredTick` applies). `read` STAYS gated by the\n // peer's share_receipts server-side (the broadcast simply never arrives when muted);\n // the client fold is identical (delivered mirrors read).\n const seq = typeof payload.up_to_server_seq === 'number' ? payload.up_to_server_seq : null;\n if (seq === null) return;\n let changed = false;\n this.messageList = this.messageList.map((m) => {\n if (m.direction !== 'outgoing' || m.serverSeq > seq) return m;\n const cur = event === 'delivered' ? (m.deliveredUpTo ?? -1) : (m.readUpTo ?? -1);\n if (seq <= cur) return m; // monotonic: a stale/equal tick never lowers it\n changed = true;\n return event === 'delivered' ? { ...m, deliveredUpTo: seq } : { ...m, readUpTo: seq };\n });\n if (changed) this.emit();\n }\n }\n\n private kindOf(incoming: IncomingMessage): ChatMessageKind {\n if (incoming.kind === 'commit' || incoming.kind === 'proposal' || incoming.kind === 'welcome')\n return 'system';\n return 'text';\n }\n\n private internalKey(serverSeq: number): string {\n return this._group ? `${this._group.rfcGroupId}#${serverSeq}` : `draft#${serverSeq}`;\n }\n private publicId(serverSeq: number): string {\n return `${this.id}#${serverSeq}`;\n }\n\n /** Page older messages in. Returns how many were prepended. */\n async loadEarlier(limit = 50): Promise<number> {\n if (!this._group) return 0;\n const before = this.loadedEarliestSeq ?? undefined;\n const older = await this.backend.history(this._group, limit, before);\n const before0 = this.messageList.length;\n this.mergeHistory(older);\n return this.messageList.length - before0;\n }\n\n // ── Members ──\n\n private async refreshMembers(): Promise<void> {\n if (!this._group) return;\n const m = await this.backend.members(this._group);\n if (m.length > 0) {\n this.memberCache = m;\n this.emit();\n }\n // On a roster change a previously-unresolvable editor may now resolve — re-run\n // held edits + recompute any target whose edit just landed (mirrors iOS T3's\n // refreshMembers hook).\n this.editFold.reevaluateHeld(this.authorOfTarget);\n for (const cid of this.originalTextByClientMsgId.keys()) this.recomputeEdit(cid);\n // Mentions T6: a member's display name may now be known — re-resolve mention names\n // on every message (resolution is LIVE, never snapshotted; a rename reflects here).\n this.reresolveAllMentionNames();\n }\n\n /** Re-resolve roster display names across the whole transcript (called on a roster\n * change). Re-emits only if any name actually changed. */\n private reresolveAllMentionNames(): void {\n let changed = false;\n this.messageList = this.messageList.map((m) => {\n const reresolved = this.resolveMentionNames(m);\n if (reresolved !== m) changed = true;\n return reresolved;\n });\n if (changed) this.emit();\n }\n\n private seedMembersFromGroup(group: MessagingGroup): void {\n const seed: ChatMember[] = [\n {\n id: this.backend.selfUserId,\n userId: this.backend.selfUserId,\n displayName: null,\n role: 'owner',\n isSelf: true,\n },\n ];\n if (group.ownerUserId && group.ownerUserId !== this.backend.selfUserId) {\n seed.push({\n id: group.ownerUserId,\n userId: group.ownerUserId,\n displayName: null,\n role: 'owner',\n isSelf: false,\n });\n }\n this.memberCache = seed;\n }\n\n private seedDraftMembers(draft: ChatDraft): void {\n const seed: ChatMember[] = [\n {\n id: this.backend.selfUserId,\n userId: this.backend.selfUserId,\n displayName: null,\n role: draft.mode.kind === 'group' ? 'owner' : 'member',\n isSelf: true,\n },\n ];\n if (draft.mode.kind === 'direct') {\n seed.push({\n id: draft.mode.peerUserId,\n userId: draft.mode.peerUserId,\n displayName: null,\n role: 'member',\n isSelf: false,\n });\n } else {\n for (const uid of draft.mode.members) {\n seed.push({ id: uid, userId: uid, displayName: null, role: 'member', isSelf: false });\n }\n }\n this.memberCache = seed;\n }\n\n // ── Send (with WhatsApp lazy materialize) ──\n\n async send(\n text: string,\n opts?: {\n replyTo?: ChatMessage;\n mentions?: MentionRange[];\n expiresIn?: { ttlSeconds: number; start?: 'send' | 'read' };\n },\n ): Promise<SentReceipt> {\n const group = await this.materializeIfNeeded();\n\n // Build ReplyRef from the parent ChatMessage if provided (and replyable).\n let replyRef: ReplyRef | null = null;\n let resolvedReplyTo: ResolvedReply | null = null;\n const parent = opts?.replyTo;\n if (parent?.clientMsgId && parent.kind !== 'system') {\n const [previewBody, wasTruncated] = parent.text ? truncateQuote(parent.text) : ['', false];\n replyRef = {\n v: 1,\n client_msg_id: parent.clientMsgId,\n preview: {\n kind: 'text',\n author_user_id: parent.senderUserId ?? '',\n body: previewBody || undefined,\n body_truncated: wasTruncated,\n },\n };\n // Resolve the reply against local history immediately (the parent IS local).\n resolvedReplyTo = resolveReply(replyRef, (id) => this.byClientMsgId.get(id) ?? null);\n }\n\n // Mentions T6: carry the caller's mention ranges onto the wire envelope (encoded\n // as `body_ranges`). The own-sent bubble resolves them locally for its own render.\n const bodyRanges = opts?.mentions ?? null;\n // Disappearing T10: a per-message TTL rides INSIDE the ciphertext as an `expiry` spec\n // — the server stays MLS-blind (no expires_at, no new route). `start === 'send'`\n // (default) anchors the deadline to the sender's compose-time (stamps sender_send_ts,\n // clamped on receipt by the local first-arrival); `read` anchors it to first-read.\n //\n // COMPOSE-TIME STAMPING (spec §\"Per-message vs per-chat\"): a message's effective TTL\n // is its OWN explicit `expiresIn` IF present, ELSE the chat's active default timer\n // STAMPED ONTO IT NOW. The per-message `expiry` field is the durable record — so a\n // plain send under an active default carries the (frozen) default on the wire, making\n // sender and receiver symmetric (both arm from the SAME stamped expiry, forward-only).\n const effectiveExpiry = this.composeExpiry(opts?.expiresIn);\n const { receipt, clientMsgId } = await this.backend.sendText(\n group,\n text,\n replyRef,\n bodyRanges,\n effectiveExpiry,\n );\n this.appendOwnSend(text, receipt, clientMsgId, resolvedReplyTo, bodyRanges, effectiveExpiry);\n // The own-send arms its OWN purge on the same rule (the stamped expiry is the basis).\n // FIRE-AND-FORGET ADVISORY: we return on the LOCAL emit — there is NO peer-enforcement.\n if (effectiveExpiry) void this.armPurge(effectiveExpiry, receipt.serverSeq, clientMsgId);\n return receipt; // fire-and-forget advisory: local emit only, no peer-enforcement signal\n }\n\n /** Resolve a send's effective per-message expiry at COMPOSE TIME: the caller's explicit\n * `expiresIn` if present, ELSE the chat's active default timer stamped onto the message\n * NOW (the durable record per spec §\"Compose-time stamping\"). A `send`-anchored expiry\n * (explicit or default-inherited) stamps `senderSendTs` = the sender's compose epoch\n * seconds; a `read`-anchored one carries none (the deadline is the recipient's local\n * first-read). Returns null when there is neither an explicit expiry nor an active\n * default (a plain, non-disappearing send). Mirrors the iOS compose-time stamping. */\n private composeExpiry(explicit?: {\n ttlSeconds: number;\n start?: 'send' | 'read';\n }): ExpirySpec | null {\n const base: ExpirySpec | null = explicit\n ? {\n v: 1,\n ttlSeconds: explicit.ttlSeconds,\n start: explicit.start ?? 'send',\n senderSendTs: null,\n }\n : this.defaultExpiry();\n if (!base) return null;\n return {\n ...base,\n senderSendTs: base.start === 'send' ? Math.floor(Date.now() / 1000) : null,\n };\n }\n\n /** Set (or DISABLE) this chat's DEFAULT disappearing timer. Emits a `timer_set` control\n * envelope (server-blind — an opaque application message, NEVER a bubble) and folds the\n * own-set locally so the default applies immediately to subsequent sends that carry no\n * per-message expiry. `ttlSeconds === null` DISABLES the default. FIRE-AND-FORGET\n * ADVISORY: returns on the LOCAL emit only; it exposes NO \"active for all peers\" signal.\n * Mirrors iOS `setDisappearing(ttlSeconds:start:)`. */\n async setDisappearing(opts: {\n ttlSeconds: number | null;\n start?: 'send' | 'read';\n }): Promise<void> {\n const group = await this.materializeIfNeeded();\n const clientMsgId = mintClientMsgId();\n const start = opts.start ?? 'send';\n const { receipt } = await this.backend.sendTimerSet(group, {\n clientMsgId,\n ttlSeconds: opts.ttlSeconds,\n start,\n });\n // Fold the own-set locally (actor = self) at the server-assigned order so the default\n // applies immediately. The fold dedups on the event clientMsgId, so the durable echo on\n // the next pump is a no-op. A timer_set is NOT a bubble.\n this.timerFold.ingest({\n ttlSeconds: opts.ttlSeconds,\n start,\n actorUserId: this.backend.selfUserId,\n epoch: receipt.epoch,\n serverSeq: receipt.serverSeq,\n eventClientMsgId: clientMsgId,\n });\n // fire-and-forget advisory: returns on local emit; no \"active for all peers\" signal.\n }\n\n private appendOwnSend(\n text: string,\n receipt: SentReceipt,\n clientMsgId: string,\n resolvedReplyTo: ResolvedReply | null,\n bodyRanges?: MentionRange[] | null,\n expiry?: ExpirySpec | null,\n ): void {\n if (receipt.serverSeq <= 0) return;\n const key = this.internalKey(receipt.serverSeq);\n if (this.seenKeys.has(key)) return;\n this.seenKeys.add(key);\n\n // Index own-sent clientMsgId for future resolveReply lookups (self-reply support).\n if (clientMsgId) {\n this.byClientMsgId.set(clientMsgId, { text, senderUserId: this.backend.selfUserId });\n // Seed the edit base + author (self) so a later own edit author-gates and the\n // overlay renders `edit ?? original`.\n this.seedEditBase(clientMsgId, text, this.backend.selfUserId);\n }\n\n this.messageList.push({\n id: this.publicId(receipt.serverSeq),\n kind: 'text',\n direction: 'outgoing',\n senderUserId: this.backend.selfUserId,\n text,\n serverSeq: receipt.serverSeq,\n sentAt: new Date(),\n clientMsgId,\n replyTo: resolvedReplyTo,\n // Attach any tally already folded for this own-sent message (rare, but keeps\n // the dangling-target invariant uniform across every append path).\n reactions: clientMsgId ? this.reactionFold.tally(clientMsgId) : {},\n // Own-sent edits fold via edit() after the fact; new sends start unedited.\n edited: false,\n // Own-sent deletes fold via deleteForEveryone() after the fact; start live.\n isDeleted: false,\n // Mentions T6: resolve the own-sent bubble's mentions for its own render (the\n // sender never gets a wire echo of its own message — this is the only local copy).\n mentions: this.resolveMentions(text, bodyRanges),\n // Disappearing T10: the surfaced deadline reflects the message's effective expiry\n // (explicit `expiresIn` OR the chat default stamped at compose time). null only when\n // this send is non-disappearing. armPurge re-derives the durable monotonic deadline;\n // this is the immediate UI countdown baseline (own sender and receiver are symmetric).\n expiresAt: this.deadlineFor(expiry ?? null),\n });\n this.messageList.sort((a, b) => a.serverSeq - b.serverSeq);\n this.emit();\n }\n\n private async materializeIfNeeded(): Promise<MessagingGroup> {\n if (this._group) return this._group;\n if (!this._draft) throw new Error('messaging_not_configured');\n const group = await this.backend.materialize(this._draft);\n this._group = group;\n this.id = group.displayId;\n this._state = 'active';\n this._draft = null;\n this.backend.registerActive(group);\n this.ensureWired();\n this.emit();\n return group;\n }\n\n // ── Membership (groups only) ──\n\n async addMemberUser(userId: string): Promise<void> {\n if (this.kind !== 'group') throw new Error('chat_is_direct');\n const group = await this.materializeIfNeeded();\n await this.backend.addMember(group, userId);\n await this.refreshMembers();\n }\n\n async removeMemberUser(userId: string): Promise<void> {\n if (this.kind !== 'group') throw new Error('chat_is_direct');\n if (!this._group) throw new Error('messaging_not_configured');\n await this.backend.removeMember(this._group, userId);\n await this.refreshMembers();\n }\n\n async leave(): Promise<void> {\n if (!this._group) return;\n await this.backend.leave(this._group);\n this.liveUnsub?.();\n this.liveUnsub = null;\n }\n\n // ── Imperative ──\n\n setTyping(isTyping: boolean): void {\n if (this._group) this.backend.setTyping(this._group, isTyping);\n }\n\n async markRead(message: ChatMessage): Promise<void> {\n if (!this._group) return;\n await this.backend.markRead(this._group, message.serverSeq);\n this.readWatermark = Math.max(this.readWatermark, message.serverSeq);\n this.emit();\n }\n\n // ── Server-metadata cluster: notify scope (mute) + server unread ──\n\n /** Set this chat's notify scope (the mute toggle). `'none'` mutes the push wake;\n * `'all'` unmutes (the default). Materializes a draft first (the scope is a\n * per-(user,group) server row), PUTs `/notify`, and returns the server-echoed scope.\n * Mirrors iOS `Chat.setNotifyScope`. */\n async setNotifyScope(scope: NotifyScope): Promise<NotifyScope> {\n const group = await this.materializeIfNeeded();\n return this.backend.setNotifyScope(group, scope);\n }\n\n /** Refresh + return this chat's notify scope from the server (fail-OPEN to `'all'`).\n * Returns `'all'` for a draft chat (no server row yet). */\n async getNotifyScope(): Promise<NotifyScope> {\n if (!this._group) return 'all';\n return this.backend.getNotifyScope(this._group);\n }\n\n /** Fetch the caller's authoritative SERVER unread count (opaque server metadata).\n * Returns the clamped count (`max(0, …)`); `0` for a draft chat. The local computed\n * `unreadCount` getter stays the instant, offline best-effort badge — this is the\n * canonical count on demand. Named distinctly so it does not shadow the observable\n * `unreadCount` snapshot getter. Mirrors iOS `Chat.refreshUnread`. */\n async unreadCountFromServer(): Promise<number> {\n if (!this._group) return 0;\n const v = await this.backend.unread(this._group);\n return Math.max(0, v.unreadCount);\n }\n\n // ── Reactions ──\n\n /** Add an emoji reaction to a message. No-op if the message isn't reactable\n * (empty clientMsgId — a legacy/system row). The reaction folds locally with\n * the server receipt's `(epoch, serverSeq)` so the target's tally updates\n * instantly; the durable echo on the next pump is a fold no-op (dedup on the\n * SAME wire clientMsgId). Never appends a bubble. */\n async react(message: ChatMessage, emoji: string): Promise<void> {\n await this.sendReaction(message, emoji, 'add');\n }\n\n /** Remove this user's emoji reaction from a message (op:'remove'). */\n async unreact(message: ChatMessage, emoji: string): Promise<void> {\n await this.sendReaction(message, emoji, 'remove');\n }\n\n private async sendReaction(\n message: ChatMessage,\n emoji: string,\n op: 'add' | 'remove',\n ): Promise<void> {\n if (!message.clientMsgId) return; // not reactable\n const group = await this.materializeIfNeeded();\n const clientMsgId = mintClientMsgId();\n const { receipt } = await this.backend.sendReaction(group, {\n clientMsgId,\n targetClientMsgId: message.clientMsgId,\n emoji,\n op,\n });\n // Fold the own-send locally (actor = self) using the SAME wire clientMsgId so\n // the durable echo dedups (no double-fold), ordered by the receipt's (epoch,\n // serverSeq) for LWW. Then recompute the target's tally + emit.\n this.reactionFold.ingest({\n targetClientMsgId: message.clientMsgId,\n actorUserId: this.backend.selfUserId,\n emoji,\n op,\n epoch: receipt.epoch,\n serverSeq: receipt.serverSeq,\n eventClientMsgId: clientMsgId,\n });\n this.recomputeReactions(message.clientMsgId);\n }\n\n // ── Edit ──\n\n /** Edit an own text message (edit-by-supersession). No-op if the message isn't\n * editable (empty clientMsgId, or not a `text` kind). The edit folds locally\n * with the server receipt's `(epoch, serverSeq)` so the target's text updates\n * instantly; the durable echo on the next pump is a fold no-op (dedup on the\n * SAME wire clientMsgId). Never appends a bubble; PRESERVES the target's\n * reactions + reply context. Only the original author's edits count — for an own\n * message self IS the author, so the author-gate passes. `opts.mentions` carries the\n * edit's REPLACEMENT mention ranges → the edited message's mentions reflect them. */\n async edit(\n message: ChatMessage,\n newText: string,\n opts?: { mentions?: MentionRange[] },\n ): Promise<void> {\n if (!message.clientMsgId || message.kind !== 'text') return; // not editable\n const group = await this.materializeIfNeeded();\n const clientMsgId = mintClientMsgId();\n const bodyRanges = opts?.mentions ?? null;\n const { receipt } = await this.backend.sendEdit(group, {\n clientMsgId,\n targetClientMsgId: message.clientMsgId,\n newText,\n bodyRanges,\n });\n // Seed the base text + author (self) if not already recorded — a 2nd own edit\n // must not corrupt the base (the seed is write-once). self IS the author of an\n // own message, so authorOfTarget gates the own edit.\n this.seedEditBase(message.clientMsgId, message.text, this.backend.selfUserId);\n // Fold the own-send locally (editor = self) using the SAME wire clientMsgId so\n // the durable echo dedups (no double-fold), ordered by the receipt's (epoch,\n // serverSeq) for LWW. Carry the edit's replacement ranges so the edited message's\n // mentions reflect them. Then recompute the target's text + edited in place.\n this.editFold.ingest(\n {\n targetClientMsgId: message.clientMsgId,\n editorUserId: this.backend.selfUserId,\n newText,\n epoch: receipt.epoch,\n serverSeq: receipt.serverSeq,\n eventClientMsgId: clientMsgId,\n bodyRanges,\n },\n this.authorOfTarget,\n );\n this.recomputeEdit(message.clientMsgId);\n }\n\n // ── Delete ──\n\n /** Delete a message for EVERYONE (a cooperative encrypted tombstone). Only the\n * ORIGINAL SENDER can do this — for an own message self IS the author, so the\n * author-gate passes. Legacy messages (empty clientMsgId) are DISABLED (a\n * tombstone keys on the target's clientMsgId, which they lack) — no-op. Sends a\n * `type:'delete'` envelope through the SAME MLS path as a text message (the\n * server stays blind), folds the own delete locally so the target scrubs in\n * place instantly (the durable echo dedups on the SAME wire clientMsgId), and\n * re-emits. NEVER appends a bubble. delete-for-me'ing the target becomes moot. */\n async deleteForEveryone(message: ChatMessage): Promise<void> {\n if (!message.clientMsgId) return; // legacy → for-everyone disabled\n const group = await this.materializeIfNeeded();\n const clientMsgId = mintClientMsgId();\n const { receipt } = await this.backend.sendDelete(group, {\n clientMsgId,\n targetClientMsgId: message.clientMsgId,\n });\n // Seed the author (self) so the own delete author-gates: self IS the author of\n // an own message. The base text seed is write-once + harmless here.\n this.seedEditBase(message.clientMsgId, message.text, this.backend.selfUserId);\n // Fold the own delete locally (actor = self = author). Absorbing — the durable\n // echo on the next pump dedups on the SAME wire clientMsgId.\n this.deleteFold.ingest(\n {\n targetClientMsgId: message.clientMsgId,\n actorUserId: this.backend.selfUserId,\n epoch: receipt.epoch,\n serverSeq: receipt.serverSeq,\n eventClientMsgId: clientMsgId,\n },\n this.authorOfTarget,\n );\n this.emit(); // surfaced() re-reads the fold → the target renders \"deleted\"\n }\n\n /** Delete a message for ME only — a LOCAL, per-device suppression. NO wire, NO\n * attribution, no server contact: the message is OMITTED from THIS view and the\n * suppression key persists per chat (survives reload). The key is the message's\n * clientMsgId when present, else `seq:<serverSeq>` for legacy messages. */\n async deleteForMe(message: ChatMessage): Promise<void> {\n const key = message.clientMsgId ? message.clientMsgId : `seq:${message.serverSeq}`;\n if (this.suppressed.has(key)) return; // idempotent\n this.suppressed.add(key);\n this.emit(); // surfaced() omits it immediately (no wire — nothing sent)\n // Persist the full set per chat (fire-and-forget; the omit already applied).\n if (this._group) {\n try {\n await this.backend.saveSuppressed(this._group, [...this.suppressed]);\n } catch {\n // best-effort — the in-memory omit holds this session even if persist fails\n }\n }\n }\n}\n\n/** Order-sensitive equality of two resolved-mention lists (normalize is deterministic,\n * so equal spans imply equal order). Cheap shortcut so the overlay paths can no-op. */\nfunction sameMentions(a: ResolvedMention[], b: ResolvedMention[]): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n const x = a[i];\n const y = b[i];\n if (!x || !y) return false;\n if (\n x.start !== y.start ||\n x.length !== y.length ||\n x.mentionedUserId !== y.mentionedUserId ||\n x.displayName !== y.displayName\n ) {\n return false;\n }\n }\n return true;\n}\n\n/** Shallow-equal two reaction tallies (emoji → sorted userIds[]). */\nfunction sameReactions(a: Record<string, string[]>, b: Record<string, string[]>): boolean {\n const ak = Object.keys(a);\n const bk = Object.keys(b);\n if (ak.length !== bk.length) return false;\n for (const k of ak) {\n const av = a[k];\n const bv = b[k];\n if (!bv || av === undefined || av.length !== bv.length) return false;\n for (let i = 0; i < av.length; i++) if (av[i] !== bv[i]) return false;\n }\n return true;\n}\n","// The live delivery source — the web mirror of iOS MessageDeliverySource.\n// Subscribes the device wake topic `messaging:device:<id>` over pb.realtime\n// (new_message wake → pumpOnce), and on each pump drains:\n//\n// 1. welcomes → engine.joinFromWelcome → register the group + emit `welcome`\n// 2. device queue → for each row:\n// - COMMIT: processIncoming (applies), bump epoch, emit `commit`\n// - APPLICATION: decrypt → persist plaintext → emit `message`\n// 3. ack everything consumed.\n//\n// The realtime wake is notification-only; the authoritative messages come from\n// the authenticated queue pull (never the broadcast payload), exactly as iOS.\n//\n// SERVER WIRE GAP (same as iOS): queue rows do NOT yet carry rfc_group_id or the\n// fanned declaration. When absent, route a blob to the SOLE known group (the\n// common 1:1/small-group case). The reconciliation-gate hook (describeIncoming)\n// is ready for the day the server fans the declaration — a pure server change.\n\nimport { palbeRequest } from '../request.js';\nimport type { PalbeRuntime } from '../runtime.js';\nimport {\n type DeletePayload,\n decodeEnvelope,\n type EditPayload,\n type ExpirySpec,\n GroupMessaging,\n type MentionRange,\n type MessagingGroup,\n type ReactionPayload,\n type ReplyRef,\n} from './group-messaging.js';\nimport type { MessageStore, StoredMessage } from './history.js';\nimport type { MlsEngine } from './mls-engine.js';\nimport type { GroupRegistry } from './registry.js';\nimport type { NotifyScope, UnreadView } from './types.js';\nimport { decodeUtf8, fromBase64, toBase64 } from './util.js';\nimport {\n ContentType,\n MessagingPaths,\n type QueuedMessage,\n type QueuePage,\n type WelcomePage,\n} from './wire.js';\n\n/** A decoded incoming message handed to chat listeners. */\nexport interface IncomingMessage {\n kind: 'application' | 'commit' | 'proposal' | 'welcome';\n group: MessagingGroup;\n text: string | null;\n senderDeviceId: string | null;\n epoch: number;\n serverSeq: number;\n receivedAt: Date;\n /** The client-minted idempotency id from the decoded envelope (empty string for legacy/system). */\n clientMsgId: string;\n /** The decoded reply reference, if present in the envelope. */\n replyRef: ReplyRef | null;\n /**\n * The decoded envelope discriminator (`'text'`/`'reaction'`/`'media'`/…).\n * Defaults to `'text'` at every existing call site — only the reaction branch\n * sets `'reaction'`, so the Chat can route a reaction into its fold instead of\n * appending a visible bubble. Optional for forward-compat with old emitters.\n */\n envelopeType?: string;\n /** The decoded reaction payload, present only when `envelopeType === 'reaction'`. */\n reaction?: ReactionPayload | null;\n /** The decoded edit payload, present only when `envelopeType === 'edit'`. Lets\n * the Chat route an edit into its EditFold instead of appending a visible bubble. */\n edit?: EditPayload | null;\n /** The decoded delete payload, present only when `envelopeType === 'delete'`. Lets\n * the Chat route a delete-for-everyone tombstone into its DeleteFold instead of\n * appending a visible bubble. */\n delete?: DeletePayload | null;\n /** The decoded mention ranges (raw, un-normalized). Present on a `'text'` bubble\n * with `body_ranges` OR on an `'edit'` (the edit's replacement ranges). The Chat\n * normalizes + resolves roster names → `ChatMessage.mentions` (mentions T6). */\n bodyRanges?: MentionRange[] | null;\n /** The decoded `timer_set` payload, present only when `envelopeType === 'timer_set'`\n * (disappearing T10). Lets the Chat route a chat-default timer into its TimerFold\n * instead of appending a visible bubble. */\n timer?: { ttlSeconds: number | null; start: 'send' | 'read' } | null;\n /** The decoded per-message TTL spec, present only on a `'text'` bubble that carried an\n * `expiry` (disappearing T10). Lets the Chat arm the message's purge on first display. */\n expiry?: ExpirySpec | null;\n}\n\ntype MessageListener = (m: IncomingMessage) => void;\ntype ConvEvent = { event: string; payload: Record<string, unknown> };\ntype ConvListener = (e: ConvEvent) => void;\n\n/** A tiny per-group fan-out hub (the web analogue of the iOS message/conv hubs). */\nexport class MessageHub {\n private readonly messageListeners = new Map<string, Set<MessageListener>>();\n private readonly convListeners = new Map<string, Set<ConvListener>>();\n\n onMessage(displayId: string, fn: MessageListener): () => void {\n return this.add(this.messageListeners, displayId, fn);\n }\n emitMessage(displayId: string, m: IncomingMessage): void {\n for (const fn of this.messageListeners.get(displayId) ?? []) fn(m);\n }\n onConv(displayId: string, fn: ConvListener): () => void {\n return this.add(this.convListeners, displayId, fn);\n }\n emitConv(displayId: string, e: ConvEvent): void {\n for (const fn of this.convListeners.get(displayId) ?? []) fn(e);\n }\n /** How many live conv listeners a group still has — the census the LAST\n * subscriber reads before tearing the conv topic (and its presence) down. */\n convListenerCount(displayId: string): number {\n return this.convListeners.get(displayId)?.size ?? 0;\n }\n\n private add<T>(map: Map<string, Set<T>>, key: string, fn: T): () => void {\n let set = map.get(key);\n if (!set) {\n set = new Set();\n map.set(key, set);\n }\n set.add(fn);\n return () => {\n set?.delete(fn);\n };\n }\n}\n\n/**\n * Decode the MLS-authenticated sender identity (`received.sender`) into the\n * `mdv_…` device id. The leaf-credential identity is exactly the SP-0 device id\n * UTF-8 bytes (see MlsEngine: `idBytes = utf8.encode(deviceId)`), so a plain\n * UTF-8 decode recovers it. Returns null for an empty/garbage identity so the\n * downstream `if (senderDeviceId)` guard stays sound (no spoofable empty actor).\n */\nfunction decodeSenderDeviceId(sender: Uint8Array): string | null {\n if (sender.length === 0) return null;\n const id = decodeUtf8(sender);\n return id.length > 0 ? id : null;\n}\n\nexport class MessageDeliverySource {\n private started = false;\n private wakeUnsub: (() => void) | null = null;\n // Per-observed-group conv subscription + heartbeat (presence/typing/read).\n // `teardown` is the ONE departure path: it stops the heartbeat, broadcasts the\n // offline frame and drops the subscriptions, so no caller can tear an\n // observation down without saying goodbye (see observeConversation).\n private readonly observed = new Map<string, { teardown: () => void }>();\n\n constructor(\n private readonly rt: PalbeRuntime,\n private readonly engine: MlsEngine,\n private readonly hub: MessageHub,\n private readonly registry: GroupRegistry,\n private readonly messageStore: MessageStore,\n private readonly deviceId: string,\n private readonly selfUserId: string,\n ) {}\n\n /** Subscribe the device wake topic + run an initial drain. Idempotent. */\n async start(): Promise<void> {\n if (this.started) return;\n this.started = true;\n try {\n const channel = this.rt.realtime.channel(`messaging:device:${this.deviceId}`);\n const sub = channel.on('new_message', () => {\n void this.pumpOnce();\n });\n // Re-pump on every (re)connect to catch wakes missed while offline.\n const offConn = this.rt.realtime.status.onChange((s) => {\n if (s.state === 'connected') void this.pumpOnce();\n });\n this.wakeUnsub = () => {\n sub.cancel();\n offConn();\n };\n } catch {\n // No WebSocket (SSR/Node) — the wake source is inert; explicit pumps still work.\n }\n await this.pumpOnce();\n }\n\n stop(): void {\n this.wakeUnsub?.();\n this.wakeUnsub = null;\n for (const [, o] of this.observed) o.teardown();\n this.observed.clear();\n this.started = false;\n }\n\n /** One full drain: welcomes → queue (decrypt + emit) → ack. Never throws. */\n async pumpOnce(): Promise<void> {\n try {\n await this.pullWelcomes();\n } catch {\n // transient — the next wake retries\n }\n try {\n await this.pullQueue();\n } catch {\n // transient — the next wake retries\n }\n }\n\n // ── Welcomes ──\n\n private async pullWelcomes(): Promise<void> {\n const page = await palbeRequest<WelcomePage>(\n this.rt,\n 'GET',\n MessagingPaths.deviceWelcomes(this.deviceId),\n );\n for (const w of page.welcomes) {\n try {\n const gidBytes = await this.engine.joinFromWelcome(fromBase64(w.welcome_b64));\n // Register under the server-minted grp_ display_id (url-safe), NEVER the\n // base64 rfc_group_id (which would 404 the members route — M3 #7).\n const group: MessagingGroup = {\n displayId: w.display_id,\n rfcGroupId: toBase64(gidBytes),\n currentEpoch: w.added_at_epoch,\n ownerUserId: null,\n directKey: null,\n name: null,\n };\n this.registry.register(group);\n this.hub.emitMessage(group.displayId, {\n kind: 'welcome',\n group,\n text: null,\n senderDeviceId: null,\n epoch: w.added_at_epoch,\n serverSeq: 0,\n receivedAt: new Date(),\n clientMsgId: '',\n replyRef: null,\n });\n } catch {\n // POISON-PILL GUARD: a re-served welcome whose one-time KeyPackage was\n // already consumed (a duplicate for a membership we hold) fails join\n // forever — skip quietly when the group is already known.\n const known = this.registry.all().some((g) => g.displayId === w.display_id);\n if (!known) {\n // genuinely unknown group — swallow (best-effort pump); next wake retries\n }\n }\n }\n }\n\n // ── Queue ──\n\n private async pullQueue(): Promise<void> {\n const page = await palbeRequest<QueuePage>(\n this.rt,\n 'GET',\n MessagingPaths.deviceQueue(this.deviceId),\n );\n const ackIds: string[] = [];\n for (const row of page.messages) {\n if (await this.process(row)) ackIds.push(row.id);\n }\n if (ackIds.length > 0) {\n await palbeRequest<void>(this.rt, 'POST', MessagingPaths.deviceQueueAck(this.deviceId), {\n body: { message_ids: ackIds },\n });\n }\n }\n\n /** Process one queue row; returns true if consumed (should ack). */\n private async process(row: QueuedMessage): Promise<boolean> {\n let blob: Uint8Array;\n try {\n blob = fromBase64(row.ciphertext_b64);\n } catch {\n return true; // junk → drop\n }\n // Resolve the target group: prefer the row's own rfc, else the sole group.\n const group =\n (row.rfc_group_id_b64 ? this.registry.group(row.rfc_group_id_b64) : undefined) ??\n this.registry.soleGroup();\n if (!group) return false; // unresolvable → leave unacked\n\n try {\n const received = await this.engine.processIncoming(fromBase64(group.rfcGroupId), blob);\n if (received.type === 'application') {\n const decoded = decodeEnvelope(received.data);\n const { text, clientMsgId, replyTo } = decoded;\n const isReaction = decoded.type === 'reaction' && decoded.reaction != null;\n const isEdit = decoded.type === 'edit' && decoded.edit != null;\n const isDelete = decoded.type === 'delete' && decoded.delete != null;\n const isTimerSet = decoded.type === 'timer_set' && decoded.timer != null;\n // SENDER ATTRIBUTION: prefer the server's `sender_device_id`, but the live\n // server does NOT yet fan that field onto queue rows (same gap as\n // `rfc_group_id` — see QueuedMessage). So fall back to the MLS-decrypted\n // sender (`received.sender`), which IS the device's leaf-credential\n // identity bytes (`mdv_…`, the SP-0 device id). This is the\n // cryptographically authenticated sender — strictly stronger than a\n // server-asserted field, and unspoofable. Without it, a peer's reaction\n // arrives with a null sender → the Chat fold can't resolve the actor and\n // SILENTLY DROPS it (text still renders, reactions vanish — the live bug).\n const senderDeviceId = row.sender_device_id ?? decodeSenderDeviceId(received.sender);\n const stored: StoredMessage = {\n id: `${group.rfcGroupId}#${row.server_seq}`,\n direction: 'incoming',\n text,\n senderDeviceId,\n epoch: row.epoch,\n serverSeq: row.server_seq,\n at: Date.now(),\n clientMsgId: clientMsgId || undefined,\n replyTo: replyTo\n ? {\n clientMsgId: replyTo.client_msg_id,\n previewBody: replyTo.preview?.body ?? null,\n previewAuthorUserId: replyTo.preview?.author_user_id ?? null,\n previewKind: replyTo.preview?.kind ?? 'text',\n }\n : null,\n // Thread the reaction discriminator + fields through the persisted row so\n // a reaction folded LIVE re-folds onto its target after a reload (the\n // reload-parity boundary — mirrors iOS T3). Omitted for non-reactions →\n // old rows hydrate as `'text'`/no-reaction (backward-compat).\n ...(isReaction && decoded.reaction\n ? {\n envelopeType: 'reaction' as const,\n reaction: {\n targetClientMsgId: decoded.reaction.targetClientMsgId,\n emoji: decoded.reaction.emoji,\n op: decoded.reaction.op,\n },\n }\n : {}),\n // Thread the edit discriminator + new text through the persisted row so an\n // edit folded LIVE re-folds onto its target after a reload (the reload-parity\n // boundary — mirrors iOS T3). Omitted for non-edits → old rows hydrate as\n // `'text'`/no-edit (backward-compat). The edit's replacement body_ranges ride\n // along so the edited message's mentions re-resolve on cold launch (T6).\n ...(isEdit && decoded.edit\n ? {\n envelopeType: 'edit' as const,\n edit: {\n targetClientMsgId: decoded.edit.targetClientMsgId,\n newText: decoded.edit.newText,\n ...(decoded.bodyRanges ? { bodyRanges: decoded.bodyRanges } : {}),\n },\n }\n : {}),\n // Thread the TEXT bubble's mention ranges (raw) through the persisted row so a\n // mention surfaced LIVE re-resolves onto its bubble after a reload (the\n // reload-parity boundary for mentions — T6, mirrors iOS T3). Only on a text\n // bubble (not a reaction/edit/delete row); omitted when absent (backward-compat).\n ...(!isReaction && !isEdit && !isDelete && decoded.bodyRanges\n ? { bodyRanges: decoded.bodyRanges }\n : {}),\n // Thread the delete discriminator + target through the persisted row so a\n // delete-for-everyone tombstone folded LIVE re-folds onto its target after\n // a reload (the reload-parity boundary — the iOS-review CRITICAL lesson;\n // the projection's `.delete` branch re-folds it so it never leaks a blank\n // bubble). Omitted for non-deletes → old rows hydrate as `'text'`/no-delete.\n ...(isDelete && decoded.delete\n ? {\n envelopeType: 'delete' as const,\n delete: {\n targetClientMsgId: decoded.delete.targetClientMsgId,\n scope: decoded.delete.scope,\n },\n }\n : {}),\n // Disappearing T10: thread the timer_set discriminator + payload through the\n // persisted row so the chat default re-folds on cold launch (the page-local\n // TimerFold in projectHistory). Omitted for non-timer_set rows (backward-compat).\n ...(isTimerSet && decoded.timer\n ? {\n envelopeType: 'timer_set' as const,\n timer: { ttlSeconds: decoded.timer.ttlSeconds, start: decoded.timer.start },\n }\n : {}),\n // Disappearing T10: thread a TEXT bubble's per-message expiry through the persisted\n // row so the message re-arms its purge on cold launch (the projection derives the\n // deadline from this). Only on a text bubble; omitted when absent (backward-compat).\n ...(!isReaction && !isEdit && !isDelete && !isTimerSet && decoded.expiry\n ? { expiry: decoded.expiry }\n : {}),\n };\n try {\n await this.messageStore.append(group.rfcGroupId, stored);\n } catch {\n return false; // persist failed → NOT acked, re-served next pump\n }\n this.hub.emitMessage(group.displayId, {\n kind: 'application',\n group,\n text,\n senderDeviceId,\n epoch: row.epoch,\n serverSeq: row.server_seq,\n receivedAt: new Date(),\n clientMsgId,\n replyRef: replyTo,\n envelopeType: decoded.type ?? 'text',\n reaction: isReaction ? decoded.reaction : null,\n edit: isEdit ? decoded.edit : null,\n delete: isDelete ? decoded.delete : null,\n // The raw mention ranges (text bubble or the edit's replacement ranges); the\n // Chat normalizes + resolves names → ChatMessage.mentions (T6).\n bodyRanges: decoded.bodyRanges ?? null,\n // Disappearing T10: the decoded timer_set payload (chat default control) + a text\n // bubble's per-message expiry. The Chat routes timer_set into its TimerFold and\n // arms a bubble's purge from the expiry (or the active default).\n timer: isTimerSet ? decoded.timer : null,\n expiry:\n !isReaction && !isEdit && !isDelete && !isTimerSet ? (decoded.expiry ?? null) : null,\n });\n return true;\n }\n if (received.type === 'commitApplied') {\n const newEpoch = Math.max(row.epoch, group.currentEpoch + 1);\n this.registry.bumpEpoch(group.rfcGroupId, newEpoch);\n const fresh = this.registry.group(group.rfcGroupId) ?? group;\n this.hub.emitMessage(group.displayId, {\n kind: 'commit',\n group: fresh,\n text: null,\n senderDeviceId: row.sender_device_id ?? null,\n epoch: newEpoch,\n serverSeq: 0,\n receivedAt: new Date(),\n clientMsgId: '',\n replyRef: null,\n });\n return true;\n }\n return true; // proposal / other — consumed\n } catch (e) {\n // Own-sent echo or consumed-key: mls-rs refuses to decrypt own ciphertext /\n // a replayed generation. ACK + skip (the durable copy is the send path's).\n if (isOwnEchoOrConsumed(e)) return true;\n return false; // decode/transient → leave unacked for retry\n }\n }\n\n // ── Conv topic (presence / typing / read) ──\n\n /** Begin observing a group's conv topic; fans events into the hub. Idempotent. */\n observeConversation(group: MessagingGroup): void {\n if (this.observed.has(group.displayId)) return;\n try {\n const channel = this.rt.realtime.channel(`messaging:conv:${group.rfcGroupId}`);\n // Server-metadata cluster: subscribe `delivered` alongside `read` (its exact 3-key\n // twin `{user_id, device_id, up_to_server_seq}`) so the sender's \"delivered\" tick\n // advances. The Chat consumes both via applyConv → the per-message user-level\n // monotonic watermark fold (mirrors iOS MessageDeliverySource's delivered case).\n const subs = ['presence', 'typing', 'read', 'delivered'].map((ev) =>\n channel.on(ev, (payload) => {\n this.hub.emitConv(group.displayId, { event: ev, payload });\n }),\n );\n // Announce online + heartbeat every 25s.\n const emitPresence = (online: boolean) =>\n channel.send('presence', {\n user_id: this.selfUserId,\n device_id: this.deviceId,\n online,\n ts: Date.now() / 1000,\n });\n emitPresence(true);\n const heartbeat = setInterval(() => emitPresence(true), 25_000);\n this.observed.set(group.displayId, {\n teardown: () => {\n // Stop the heartbeat BEFORE the goodbye so no beat can land after it.\n clearInterval(heartbeat);\n // The departure frame. Presence is broadcast-only — a peer's light is\n // lit and unlit solely by these frames, and the server keeps no\n // expiry that would time this device out — so leaving without it\n // leaves every peer showing us online for the rest of their session.\n try {\n emitPresence(false);\n } catch {\n // socket already gone — nothing left to say goodbye through\n }\n for (const s of subs) s.cancel();\n },\n });\n } catch {\n // no socket (SSR) — presence/typing simply unavailable\n }\n }\n\n /** Stop observing a group's conv topic: announce OFFLINE, stop the heartbeat\n * and drop the subscriptions. Idempotent — an unobserved group is a no-op. */\n unobserveConversation(group: MessagingGroup): void {\n const o = this.observed.get(group.displayId);\n if (!o) return;\n this.observed.delete(group.displayId);\n o.teardown();\n }\n\n /** Announce typing on a group's conv topic (the app calls per keystroke). */\n setTyping(group: MessagingGroup, isTyping: boolean): void {\n this.observeConversation(group);\n try {\n this.rt.realtime.channel(`messaging:conv:${group.rfcGroupId}`).send('typing', {\n user_id: this.selfUserId,\n device_id: this.deviceId,\n typing: isTyping,\n });\n } catch {\n // no socket\n }\n }\n\n /** Advance this device's read cursor (idempotent; the server clamps). */\n async markRead(group: MessagingGroup, upToServerSeq: number): Promise<void> {\n await palbeRequest<void>(this.rt, 'POST', MessagingPaths.groupRead(group.displayId), {\n body: { read_seq: upToServerSeq, read_epoch: group.currentEpoch, is_private: false },\n });\n }\n\n // ── Server-metadata cluster: notify scope (mute) + unread (HTTP) ──\n\n /** PUT `/v1/messaging/groups/{gid}/notify` — set the caller's per-(user,group) notify\n * scope (`'all'`|`'none'`). The server stores the opaque enum verbatim and stays blind.\n * Returns the server-echoed scope (fail-OPEN to `'all'` on an unknown value). */\n async setNotifyScope(group: MessagingGroup, scope: NotifyScope): Promise<NotifyScope> {\n const res = await palbeRequest<{ notify_scope: string }>(\n this.rt,\n 'PUT',\n MessagingPaths.groupNotify(group.displayId),\n { body: { notify_scope: scope } },\n );\n return res.notify_scope === 'none' ? 'none' : 'all';\n }\n\n /** GET `/v1/messaging/groups/{gid}/notify` — the caller's notify scope. An absent\n * server row / unknown value reads as `'all'` (fail-OPEN — never silently mutes). */\n async getNotifyScope(group: MessagingGroup): Promise<NotifyScope> {\n const res = await palbeRequest<{ notify_scope: string }>(\n this.rt,\n 'GET',\n MessagingPaths.groupNotify(group.displayId),\n );\n return res.notify_scope === 'none' ? 'none' : 'all';\n }\n\n /** GET `/v1/messaging/groups/{gid}/unread` — the caller's OWN unread view (opaque\n * server metadata). Maps the snake_case wire → the camelCase `UnreadView`. */\n async unread(group: MessagingGroup): Promise<UnreadView> {\n const res = await palbeRequest<{\n group_max_seq: number;\n last_read_seq: number;\n delivered_seq: number;\n unread_count: number;\n }>(this.rt, 'GET', MessagingPaths.groupUnread(group.displayId));\n return {\n groupMaxSeq: res.group_max_seq,\n lastReadSeq: res.last_read_seq,\n deliveredSeq: res.delivered_seq,\n unreadCount: res.unread_count,\n };\n }\n}\n\n/** mls-rs own-echo (\"message from self can't be processed\") or consumed-key\n * (\"key not available, invalid generation\") — both ACK-and-skip on the receiver. */\nfunction isOwnEchoOrConsumed(e: unknown): boolean {\n const msg = e instanceof Error ? e.message : String(e);\n return msg.includes('message from self') || msg.includes('key not available, invalid generation');\n}\n\n// keep GroupMessaging/ContentType referenced for the wire-gap declaration hook\nvoid GroupMessaging;\nvoid ContentType;\n","// The disappearing/TTL durable store — the web mirror of iOS `DisappearingStore`.\n//\n// Holds the crash-safe purge state for disappearing messages, sealed at rest by the\n// same durable KV (IndexedDB + WebCrypto AES-GCM in the browser) that seals the\n// message store, so it survives a cold launch / mid-purge crash.\n//\n// TWO SEPARATE persistent purge sets, two key namespaces that CANNOT collide:\n// (a) `tombstonedSeqs` — an INTEGER `server_seq` tombstone. Written FIRST (the purge\n// commit point), it drives transcript exclusion (rebuilt-from-store skips a\n// tombstoned seq) + redelivery drop (a redelivered tombstoned seq is dropped on\n// arrival). A mid-purge crash AFTER this write resurrects the tombstone on reload.\n// (b) `purgedClientMsgIds` — a STRING `client_msg_id` set. It drives the orphan-fold\n// resolver (`authorOfTarget → 'purged'`) so a late edit DROPs and a late delete\n// no-ops against a TTL-purged target.\n// The int seq and the string id live in distinct JSON fields; one is NEVER keyed off\n// the other (per the TTL global constraint).\n//\n// Anchors are WRITE-ONCE per `(device, clientMsgId)`: the first arrival captures the\n// monotonic/wall/boot anchor triple; a later re-arm for the same clientMsgId is a\n// no-op, so the deadline never resets.\n\nimport type { AnchorTriple } from './deadline-calculator.js';\nimport type { DurableKV } from './storage.js';\nimport { decodeUtf8, encodeUtf8 } from './util.js';\n\ninterface DisappearingRecord {\n tombstonedSeqs: number[];\n purgedClientMsgIds: string[];\n anchors: Record<string, AnchorTriple>;\n}\n\n/**\n * Per-chat IndexedDB-backed store for disappearing durable state (keyed by\n * `disappear:<rfcGroupId>`). Mirrors `SuppressionStore`'s DurableKV/JSON pattern.\n */\nexport class DisappearingStore {\n constructor(private readonly kv: DurableKV) {}\n\n private key(rfc: string): string {\n return `disappear:${rfc}`;\n }\n\n private async load(rfc: string): Promise<DisappearingRecord> {\n const raw = await this.kv.get(this.key(rfc));\n if (!raw) return { tombstonedSeqs: [], purgedClientMsgIds: [], anchors: {} };\n try {\n const r = JSON.parse(decodeUtf8(raw)) as Partial<DisappearingRecord>;\n return {\n tombstonedSeqs: r.tombstonedSeqs ?? [],\n purgedClientMsgIds: r.purgedClientMsgIds ?? [],\n anchors: r.anchors ?? {},\n };\n } catch {\n return { tombstonedSeqs: [], purgedClientMsgIds: [], anchors: {} };\n }\n }\n\n private async save(rfc: string, rec: DisappearingRecord): Promise<void> {\n await this.kv.set(this.key(rfc), encodeUtf8(JSON.stringify(rec)));\n }\n\n /** The persisted INTEGER `server_seq` tombstone set for a chat (transcript exclusion + redelivery drop). */\n async tombstonedSeqs(rfc: string): Promise<Set<number>> {\n return new Set((await this.load(rfc)).tombstonedSeqs);\n }\n\n /** The persisted STRING `client_msg_id` purge set for a chat (orphan-fold → `'purged'`). */\n async purgedClientMsgIds(rfc: string): Promise<Set<string>> {\n return new Set((await this.load(rfc)).purgedClientMsgIds);\n }\n\n /**\n * Tombstone-first commit point: the INTEGER seq and the STRING client_msg_id are\n * written together in ONE durable record. Idempotent (re-tombstoning the same seq /\n * id is a no-op). This write is THE purge commit — once it lands, a transcript rebuilt\n * from the store excludes the seq and a redelivery is dropped, even across a crash.\n */\n async tombstone(rfc: string, serverSeq: number, clientMsgId: string): Promise<void> {\n const rec = await this.load(rfc);\n if (!rec.tombstonedSeqs.includes(serverSeq)) rec.tombstonedSeqs.push(serverSeq);\n if (clientMsgId && !rec.purgedClientMsgIds.includes(clientMsgId)) {\n rec.purgedClientMsgIds.push(clientMsgId);\n }\n await this.save(rfc, rec);\n }\n\n /** The persisted write-once anchor triple for a `clientMsgId`, or null if none. */\n async anchor(rfc: string, clientMsgId: string): Promise<AnchorTriple | null> {\n return (await this.load(rfc)).anchors[clientMsgId] ?? null;\n }\n\n /** Write-once: a second call for the same `clientMsgId` is a no-op (deadline never resets). */\n async writeAnchorOnce(rfc: string, clientMsgId: string, a: AnchorTriple): Promise<void> {\n const rec = await this.load(rfc);\n if (rec.anchors[clientMsgId]) return;\n rec.anchors[clientMsgId] = a;\n await this.save(rfc, rec);\n }\n}\n","// Local decrypted-message history + the group catalog — the web mirror of iOS\n// MessageStore + GroupCatalog. MLS decrypt-once means a received message's key\n// is erased on first decrypt, so you can NEVER refetch+redecrypt it from the\n// server: the decrypted plaintext MUST be persisted locally (sealed at rest by\n// the durable KV — IndexedDB+WebCrypto-AES-GCM in the browser). The own-sent\n// copy is likewise the only durable form (the sender can't decrypt its own\n// ciphertext).\n\nimport type { MentionRange } from './group-messaging.js';\nimport type { DurableKV } from './storage.js';\nimport type { MessageDirection } from './types.js';\nimport { decodeUtf8, encodeUtf8 } from './util.js';\n\n/** A durable stored message (one row in the local sealed transcript). */\nexport interface StoredMessage {\n /** Stable across reloads: `<rfcGroupId>#<serverSeq>`. */\n id: string;\n direction: MessageDirection;\n text: string | null;\n senderDeviceId: string | null;\n epoch: number;\n serverSeq: number;\n /** ms since epoch. */\n at: number;\n /**\n * Client-minted idempotency id from the decoded envelope.\n * Optional for backward-compat: old persisted rows without this key\n * are treated as '' (no id) on read.\n */\n clientMsgId?: string;\n /**\n * The decoded reply reference, if the message was a reply.\n * Optional for backward-compat: old persisted rows without this key\n * are treated as null on read.\n */\n replyTo?: {\n clientMsgId: string;\n previewBody: string | null;\n previewAuthorUserId: string | null;\n previewKind: string;\n } | null;\n /**\n * The decoded envelope discriminator (`'text'`/`'reaction'`/…).\n * Optional for backward-compat: old persisted rows without this key are treated\n * as `'text'` (a non-reaction) on read, so a pre-reactions row never re-folds.\n */\n envelopeType?: string;\n /**\n * The decoded reaction payload, present only when `envelopeType === 'reaction'`.\n * Optional + backward-compat (old rows → undefined → no re-fold). Threading this\n * through the persisted shape is the reload-parity boundary: a reaction folded\n * LIVE re-folds onto its target after a cold launch.\n */\n reaction?: {\n targetClientMsgId: string;\n emoji: string;\n op: 'add' | 'remove';\n } | null;\n /**\n * The decoded edit payload, present only when `envelopeType === 'edit'`.\n * Optional + backward-compat (old rows → undefined → no re-fold). Threading this\n * through the persisted shape is the reload-parity boundary for edits: an edit\n * folded LIVE re-folds onto its target's text after a cold launch.\n */\n edit?: {\n targetClientMsgId: string;\n newText: string;\n /**\n * The edit's REPLACEMENT mention ranges (raw, un-normalized). Optional +\n * backward-compat (old rows → undefined). Threaded so an edited message's\n * mentions re-resolve from the WINNING edit's ranges after a reload (mentions T6).\n */\n bodyRanges?: MentionRange[] | null;\n } | null;\n /**\n * The decoded mention ranges on a TEXT/media bubble (raw, un-normalized — the\n * Chat/projection normalizes + resolves names). Present only on `'text'` rows that\n * carried `body_ranges`. Optional + backward-compat (old rows → undefined → no\n * mentions). Threading this through the persisted shape is the reload-parity\n * boundary for mentions: a mention surfaced LIVE re-resolves onto its bubble after a\n * cold launch (mentions T6, mirrors iOS T3).\n */\n bodyRanges?: MentionRange[] | null;\n /**\n * The decoded delete payload, present only when `envelopeType === 'delete'`.\n * Optional + backward-compat (old rows → undefined → no re-fold). Threading this\n * through the persisted shape is the reload-parity boundary for deletes: a\n * delete-for-everyone tombstone folded LIVE re-folds onto its target after a\n * cold launch (and never leaks as a blank bubble — the iOS-review CRITICAL\n * lesson: the projection MUST have a `.delete` branch).\n */\n delete?: {\n targetClientMsgId: string;\n scope: string;\n } | null;\n /**\n * The decoded per-message TTL spec (own-sent / decoded), present only on a text/media\n * row that carried an `expiry` (disappearing T10). Optional + backward-compat (old rows\n * → undefined → no re-arm). Threaded so an own-send TTL re-arms its purge after a cold\n * launch, and so the cold-launch `projectHistory` derives the message's effective TTL.\n */\n expiry?: {\n v: number;\n ttlSeconds: number;\n start: 'send' | 'read';\n senderSendTs: number | null;\n } | null;\n /**\n * The decoded `timer_set` payload, present only when `envelopeType === 'timer_set'`\n * (disappearing T10). Optional + backward-compat (old rows → undefined → no re-fold).\n * Threaded so the chat default disappearing timer re-folds on a cold launch (the\n * page-local `TimerFold` in `projectHistory`). `ttlSeconds === null` disables the default.\n */\n timer?: {\n ttlSeconds: number | null;\n start: 'send' | 'read';\n } | null;\n}\n\n/** Persist decrypted messages per group, newest-last, deduped by id. */\nexport class MessageStore {\n constructor(private readonly kv: DurableKV) {}\n\n private key(rfcGroupId: string): string {\n return `msgs:${rfcGroupId}`;\n }\n\n /** Append (idempotent on id) and persist the group's transcript. */\n async append(rfcGroupId: string, msg: StoredMessage): Promise<void> {\n const list = await this.load(rfcGroupId);\n if (list.some((m) => m.id === msg.id)) return; // dedup by stable id\n list.push(msg);\n list.sort((a, b) => a.serverSeq - b.serverSeq);\n await this.save(rfcGroupId, list);\n }\n\n /** True if a message id is already durable (the consumed-key recovery check). */\n async contains(rfcGroupId: string, id: string): Promise<boolean> {\n const list = await this.load(rfcGroupId);\n return list.some((m) => m.id === id);\n }\n\n /**\n * Read history for a group, newest-last. `before` pages older: returns up to\n * `limit` rows with serverSeq < before. nil `before` returns the newest page.\n */\n async history(rfcGroupId: string, limit: number, before?: number): Promise<StoredMessage[]> {\n const list = await this.load(rfcGroupId);\n const filtered = before == null ? list : list.filter((m) => m.serverSeq < before);\n return filtered.slice(Math.max(0, filtered.length - limit));\n }\n\n async load(rfcGroupId: string): Promise<StoredMessage[]> {\n const raw = await this.kv.get(this.key(rfcGroupId));\n if (!raw) return [];\n try {\n return JSON.parse(decodeUtf8(raw)) as StoredMessage[];\n } catch {\n return [];\n }\n }\n\n private async save(rfcGroupId: string, list: StoredMessage[]): Promise<void> {\n await this.kv.set(this.key(rfcGroupId), encodeUtf8(JSON.stringify(list)));\n }\n\n async wipe(): Promise<void> {\n for (const k of await this.kv.keys('msgs:')) await this.kv.delete(k);\n }\n}\n\n/** A persisted group entry (the catalog row — survives reloads). */\nexport interface CatalogGroup {\n displayId: string;\n rfcGroupId: string;\n currentEpoch: number;\n ownerUserId: string | null;\n directKey: string | null;\n name: string | null;\n}\n\n/** Durable list of known groups (the web mirror of iOS GroupCatalog), so a\n * reload hydrates the chat list + lets queue rows route before the first pump. */\nexport class GroupCatalog {\n constructor(private readonly kv: DurableKV) {}\n\n async upsert(g: CatalogGroup): Promise<void> {\n await this.kv.set(`cat:${g.rfcGroupId}`, encodeUtf8(JSON.stringify(g)));\n }\n\n async remove(rfcGroupId: string): Promise<void> {\n await this.kv.delete(`cat:${rfcGroupId}`);\n }\n\n async all(): Promise<CatalogGroup[]> {\n const keys = await this.kv.keys('cat:');\n const out: CatalogGroup[] = [];\n for (const k of keys) {\n const raw = await this.kv.get(k);\n if (!raw) continue;\n try {\n out.push(JSON.parse(decodeUtf8(raw)) as CatalogGroup);\n } catch {\n // skip a corrupt row\n }\n }\n return out;\n }\n\n async wipe(): Promise<void> {\n for (const k of await this.kv.keys('cat:')) await this.kv.delete(k);\n }\n}\n","// The self-elevation dedup store — the web mirror of iOS `MentionElevationStore`.\n//\n// WHY THIS EXISTS (mentions design §\"Self-elevation idempotency\"): when an INCOMING\n// message mentions THIS device's user (and the sender isn't me, and it isn't an edit),\n// the Chat fires `onMentionElevation` exactly ONCE. Re-delivery (a reconnect re-pump),\n// a backfill, and a cold-launch relaunch must NOT re-fire. The in-memory set gates a\n// session; this persisted set makes the dedup survive reload — a re-delivered mention\n// after a cold launch finds its key already set and is a no-op.\n//\n// POSTURE — identical to `SuppressionStore`: a persisted per-chat `Set<elevationKey>`\n// (keyed by rfcGroupId) where `elevationKey` is `\"<selfUserId>|<idPart>\"` (idPart =\n// the mention message's client_msg_id, or `seq:<serverSeq>` for a legacy message). The\n// `selfUserId` scoping means a multi-account device never crosses dedup sets. Sealed at\n// rest by the same durable KV (IndexedDB + WebCrypto AES-GCM in the browser) that seals\n// the message store — so \"which messages buzzed me\" stays private (a soft mention-graph\n// signal) and the dedup survives a cold launch.\n\nimport type { DurableKV } from './storage.js';\nimport { decodeUtf8, encodeUtf8 } from './util.js';\n\n/** Persist a per-chat self-elevation dedup key set (keyed by rfcGroupId). */\nexport class MentionElevationStore {\n constructor(private readonly kv: DurableKV) {}\n\n private key(rfcGroupId: string): string {\n return `elev:${rfcGroupId}`;\n }\n\n /** Load the persisted elevation keys for a chat (empty array if none). */\n async load(rfcGroupId: string): Promise<string[]> {\n const raw = await this.kv.get(this.key(rfcGroupId));\n if (!raw) return [];\n try {\n const parsed = JSON.parse(decodeUtf8(raw));\n return Array.isArray(parsed) ? (parsed as string[]) : [];\n } catch {\n return [];\n }\n }\n\n /** Persist the full elevation key set for a chat (deterministic, deduped order). */\n async save(rfcGroupId: string, keys: string[]): Promise<void> {\n const sorted = [...new Set(keys)].sort();\n await this.kv.set(this.key(rfcGroupId), encodeUtf8(JSON.stringify(sorted)));\n }\n\n async wipe(): Promise<void> {\n for (const k of await this.kv.keys('elev:')) await this.kv.delete(k);\n }\n}\n","/**\n * Browser binding of [`PalbeMlsClient`]. Holds the same `Arc<PalbeMlsClient>`\n * the native FFI would; every method delegates to the engine.\n */\nexport class MlsClient {\n static __wrap(ptr) {\n const obj = Object.create(MlsClient.prototype);\n obj.__wbg_ptr = ptr;\n MlsClientFinalization.register(obj, obj.__wbg_ptr, obj);\n return obj;\n }\n __destroy_into_raw() {\n const ptr = this.__wbg_ptr;\n this.__wbg_ptr = 0;\n MlsClientFinalization.unregister(this);\n return ptr;\n }\n free() {\n const ptr = this.__destroy_into_raw();\n wasm.__wbg_mlsclient_free(ptr, 0);\n }\n /**\n * The MLS Capabilities wire bytes (the keydir's `capabilities`).\n * @returns {Uint8Array}\n */\n capabilities() {\n const ret = wasm.mlsclient_capabilities(this.__wbg_ptr);\n var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();\n wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);\n return v1;\n }\n /**\n * Create a new group at epoch 0. `groupId` `undefined`/`null` lets mls-rs\n * mint a globally-unique id (the rfc_group_id routing key).\n * @param {Uint8Array | null} [group_id]\n * @returns {MlsGroup}\n */\n createGroup(group_id) {\n var ptr0 = isLikeNone(group_id) ? 0 : passArray8ToWasm0(group_id, wasm.__wbindgen_malloc);\n var len0 = WASM_VECTOR_LEN;\n const ret = wasm.mlsclient_createGroup(this.__wbg_ptr, ptr0, len0);\n if (ret[2]) {\n throw takeFromExternrefTable0(ret[1]);\n }\n return MlsGroup.__wrap(ret[0]);\n }\n /**\n * The MLS Credential wire bytes (the keydir's `credential`).\n * @returns {Uint8Array}\n */\n credential() {\n const ret = wasm.mlsclient_credential(this.__wbg_ptr);\n var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();\n wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);\n return v1;\n }\n /**\n * The MLS-encoded enrollment material `{ signatureKey, credential,\n * capabilities }` (byte fields as arrays-of-numbers — wrap in Uint8Array as\n * needed). Base64 these into the SP-0 keydir enroll body.\n * @returns {any}\n */\n enrollmentMaterial() {\n const ret = wasm.mlsclient_enrollmentMaterial(this.__wbg_ptr);\n if (ret[2]) {\n throw takeFromExternrefTable0(ret[1]);\n }\n return takeFromExternrefTable0(ret[0]);\n }\n /**\n * Mint a one-use KeyPackage (RAW RFC 9420 §10 bytes) — uploaded to the\n * keydir, consumed by an adder's `addMember`.\n * @returns {Uint8Array}\n */\n generateKeyPackage() {\n const ret = wasm.mlsclient_generateKeyPackage(this.__wbg_ptr);\n if (ret[3]) {\n throw takeFromExternrefTable0(ret[2]);\n }\n var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();\n wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);\n return v1;\n }\n /**\n * Join a group from a Welcome (opaque MLSMessage bytes).\n * @param {Uint8Array} welcome\n * @returns {MlsGroup}\n */\n joinGroup(welcome) {\n const ptr0 = passArray8ToWasm0(welcome, wasm.__wbindgen_malloc);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.mlsclient_joinGroup(this.__wbg_ptr, ptr0, len0);\n if (ret[2]) {\n throw takeFromExternrefTable0(ret[1]);\n }\n return MlsGroup.__wrap(ret[0]);\n }\n /**\n * Re-hydrate a previously-persisted group by its `groupId` (restart-safe\n * reload of an already-joined group).\n * @param {Uint8Array} group_id\n * @returns {MlsGroup}\n */\n loadGroup(group_id) {\n const ptr0 = passArray8ToWasm0(group_id, wasm.__wbindgen_malloc);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.mlsclient_loadGroup(this.__wbg_ptr, ptr0, len0);\n if (ret[2]) {\n throw takeFromExternrefTable0(ret[1]);\n }\n return MlsGroup.__wrap(ret[0]);\n }\n /**\n * The serialized STABLE signature keypair (secret-bearing). Persist sealed\n * and pass back as `signatureKeypair` on restart so the device keeps the\n * SAME MLS signing identity.\n * @returns {Uint8Array}\n */\n signatureKeypair() {\n const ret = wasm.mlsclient_signatureKeypair(this.__wbg_ptr);\n var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();\n wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);\n return v1;\n }\n /**\n * The device's STABLE signature PUBLIC key bytes (the keydir's\n * `signature_key`).\n * @returns {Uint8Array}\n */\n signaturePublicKey() {\n const ret = wasm.mlsclient_signaturePublicKey(this.__wbg_ptr);\n var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();\n wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);\n return v1;\n }\n}\nif (Symbol.dispose) MlsClient.prototype[Symbol.dispose] = MlsClient.prototype.free;\n\n/**\n * Browser binding of [`PalbeMlsGroup`].\n */\nexport class MlsGroup {\n static __wrap(ptr) {\n const obj = Object.create(MlsGroup.prototype);\n obj.__wbg_ptr = ptr;\n MlsGroupFinalization.register(obj, obj.__wbg_ptr, obj);\n return obj;\n }\n __destroy_into_raw() {\n const ptr = this.__wbg_ptr;\n this.__wbg_ptr = 0;\n MlsGroupFinalization.unregister(this);\n return ptr;\n }\n free() {\n const ptr = this.__destroy_into_raw();\n wasm.__wbg_mlsgroup_free(ptr, 0);\n }\n /**\n * Stage a member add. Returns `{ commit, welcome?, addedLeafIndices }`\n * (byte fields as arrays-of-numbers). The group is PENDING after this.\n * @param {Uint8Array} key_package_msg\n * @returns {any}\n */\n addMember(key_package_msg) {\n const ptr0 = passArray8ToWasm0(key_package_msg, wasm.__wbindgen_malloc);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.mlsgroup_addMember(this.__wbg_ptr, ptr0, len0);\n if (ret[2]) {\n throw takeFromExternrefTable0(ret[1]);\n }\n return takeFromExternrefTable0(ret[0]);\n }\n /**\n * Apply the staged commit (advances the local epoch). Call ONLY after the\n * server accepted the commit.\n */\n applyPendingCommit() {\n const ret = wasm.mlsgroup_applyPendingCommit(this.__wbg_ptr);\n if (ret[1]) {\n throw takeFromExternrefTable0(ret[0]);\n }\n }\n /**\n * Discard the staged commit without advancing (the 409-rebase path).\n */\n clearPendingCommit() {\n wasm.mlsgroup_clearPendingCommit(this.__wbg_ptr);\n }\n /**\n * Stage a SINGLE atomic add+remove commit. `addKeyPackages` is an\n * `Array<Uint8Array>` of one-use key packages; `removeMemberIds` an\n * `Array<Uint8Array>` of member identity bytes. Returns the same\n * `AddResult` shape as `addMember`. PENDING after this.\n * @param {Uint8Array[]} add_key_packages\n * @param {Uint8Array[]} remove_member_ids\n * @returns {any}\n */\n commitChanges(add_key_packages, remove_member_ids) {\n const ptr0 = passArrayJsValueToWasm0(add_key_packages, wasm.__wbindgen_malloc);\n const len0 = WASM_VECTOR_LEN;\n const ptr1 = passArrayJsValueToWasm0(remove_member_ids, wasm.__wbindgen_malloc);\n const len1 = WASM_VECTOR_LEN;\n const ret = wasm.mlsgroup_commitChanges(this.__wbg_ptr, ptr0, len0, ptr1, len1);\n if (ret[2]) {\n throw takeFromExternrefTable0(ret[1]);\n }\n return takeFromExternrefTable0(ret[0]);\n }\n /**\n * The current (applied) epoch as a JS BigInt (u64 exceeds safe-integer).\n * @returns {bigint}\n */\n currentEpoch() {\n const ret = wasm.mlsgroup_currentEpoch(this.__wbg_ptr);\n return BigInt.asUintN(64, ret);\n }\n /**\n * Inspect an incoming Commit and return the membership delta it WOULD\n * effect WITHOUT applying it (the reconciliation gate's crypto-truth).\n * Returns `{ kind, addedIdentities, removedIdentities }`.\n * @param {Uint8Array} commit_msg\n * @returns {any}\n */\n describeIncomingCommit(commit_msg) {\n const ptr0 = passArray8ToWasm0(commit_msg, wasm.__wbindgen_malloc);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.mlsgroup_describeIncomingCommit(this.__wbg_ptr, ptr0, len0);\n if (ret[2]) {\n throw takeFromExternrefTable0(ret[1]);\n }\n return takeFromExternrefTable0(ret[0]);\n }\n /**\n * Encrypt an application message at the current epoch. Returns the opaque\n * MLSMessage bytes (PrivateMessage).\n * @param {Uint8Array} plaintext\n * @returns {Uint8Array}\n */\n encryptApplicationMessage(plaintext) {\n const ptr0 = passArray8ToWasm0(plaintext, wasm.__wbindgen_malloc);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.mlsgroup_encryptApplicationMessage(this.__wbg_ptr, ptr0, len0);\n if (ret[3]) {\n throw takeFromExternrefTable0(ret[2]);\n }\n var v2 = getArrayU8FromWasm0(ret[0], ret[1]).slice();\n wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);\n return v2;\n }\n /**\n * Derive the group's current-epoch exporter secret (RFC 9420 §8.5) — the SAME\n * engine call iOS uses (uniffi `exportSecret`), so a cross-platform call/group\n * derives a byte-identical key. The SP-4 web call sets this as the LiveKit\n * frame key (server + SFU stay blind); also group-name/media sealing.\n * @param {Uint8Array} label\n * @param {Uint8Array} context\n * @param {bigint} len\n * @returns {Uint8Array}\n */\n exportSecret(label, context, len) {\n const ptr0 = passArray8ToWasm0(label, wasm.__wbindgen_malloc);\n const len0 = WASM_VECTOR_LEN;\n const ptr1 = passArray8ToWasm0(context, wasm.__wbindgen_malloc);\n const len1 = WASM_VECTOR_LEN;\n const ret = wasm.mlsgroup_exportSecret(this.__wbg_ptr, ptr0, len0, ptr1, len1, len);\n if (ret[3]) {\n throw takeFromExternrefTable0(ret[2]);\n }\n var v3 = getArrayU8FromWasm0(ret[0], ret[1]).slice();\n wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);\n return v3;\n }\n /**\n * The MLS group id (the rfc_group_id routing key / storage key).\n * @returns {Uint8Array}\n */\n groupId() {\n const ret = wasm.mlsgroup_groupId(this.__wbg_ptr);\n var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();\n wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);\n return v1;\n }\n /**\n * True while a staged (unapplied) commit exists.\n * @returns {boolean}\n */\n hasPendingCommit() {\n const ret = wasm.mlsgroup_hasPendingCommit(this.__wbg_ptr);\n return ret !== 0;\n }\n /**\n * Process an incoming MLSMessage and apply its effect. Returns a\n * `ReceivedMessage` plain object (serde-tagged enum): e.g.\n * `{ Application: { sender, data } }`, `{ CommitApplied: { epoch,\n * removedSelf } }`, `\"Proposal\"`, or `\"Other\"`.\n * @param {Uint8Array} message\n * @returns {any}\n */\n processIncomingMessage(message) {\n const ptr0 = passArray8ToWasm0(message, wasm.__wbindgen_malloc);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.mlsgroup_processIncomingMessage(this.__wbg_ptr, ptr0, len0);\n if (ret[2]) {\n throw takeFromExternrefTable0(ret[1]);\n }\n return takeFromExternrefTable0(ret[0]);\n }\n /**\n * Stage a member remove (by the member's identity bytes). Returns\n * `{ commit }`. PENDING after this.\n * @param {Uint8Array} member_id\n * @returns {any}\n */\n removeMember(member_id) {\n const ptr0 = passArray8ToWasm0(member_id, wasm.__wbindgen_malloc);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.mlsgroup_removeMember(this.__wbg_ptr, ptr0, len0);\n if (ret[2]) {\n throw takeFromExternrefTable0(ret[1]);\n }\n return takeFromExternrefTable0(ret[0]);\n }\n /**\n * Persist the current group state through the JS group storage callback.\n */\n writeToStorage() {\n const ret = wasm.mlsgroup_writeToStorage(this.__wbg_ptr);\n if (ret[1]) {\n throw takeFromExternrefTable0(ret[0]);\n }\n }\n}\nif (Symbol.dispose) MlsGroup.prototype[Symbol.dispose] = MlsGroup.prototype.free;\n\n/**\n * Mint a device MLS client (browser binding of [`engine::generate_client`]).\n *\n * - `id`: the member identity bytes (the SP-0 device id `mdv_`).\n * - `group_storage`: a JS object implementing `IGroupStateStorage`.\n * - `key_package_storage`: a JS object implementing `IKeyPackageStorage`.\n * - `signature_keypair`: `undefined`/`null` on first run (a fresh keypair is\n * minted), or the previously-persisted serialized secret-key bytes on restart.\n * @param {Uint8Array} id\n * @param {IGroupStateStorage} group_storage\n * @param {IKeyPackageStorage} key_package_storage\n * @param {Uint8Array | null} [signature_keypair]\n * @returns {MlsClient}\n */\nexport function generateClient(id, group_storage, key_package_storage, signature_keypair) {\n const ptr0 = passArray8ToWasm0(id, wasm.__wbindgen_malloc);\n const len0 = WASM_VECTOR_LEN;\n var ptr1 = isLikeNone(signature_keypair) ? 0 : passArray8ToWasm0(signature_keypair, wasm.__wbindgen_malloc);\n var len1 = WASM_VECTOR_LEN;\n const ret = wasm.generateClient(ptr0, len0, group_storage, key_package_storage, ptr1, len1);\n if (ret[2]) {\n throw takeFromExternrefTable0(ret[1]);\n }\n return MlsClient.__wrap(ret[0]);\n}\n\n/**\n * Install the browser console panic hook so a Rust panic shows a readable\n * stack in the devtools console instead of an opaque `unreachable`. Idempotent;\n * the TS facade calls this once at module init.\n */\nexport function setPanicHook() {\n wasm.setPanicHook();\n}\nexport function __wbg_BigInt_231999d28f899902() { return handleError(function (arg0) {\n const ret = BigInt(arg0);\n return ret;\n}, arguments); }\nexport function __wbg_Error_fdd633d4bb5dd76a(arg0, arg1) {\n const ret = Error(getStringFromWasm0(arg0, arg1));\n return ret;\n}\nexport function __wbg_String_8564e559799eccda(arg0, arg1) {\n const ret = String(arg1);\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);\n const len1 = WASM_VECTOR_LEN;\n getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);\n getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);\n}\nexport function __wbg___wbindgen_bigint_get_as_i64_d9e915702856f831(arg0, arg1) {\n const v = arg1;\n const ret = typeof(v) === 'bigint' ? v : undefined;\n getDataViewMemory0().setBigInt64(arg0 + 8 * 1, isLikeNone(ret) ? BigInt(0) : ret, true);\n getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true);\n}\nexport function __wbg___wbindgen_debug_string_8a447059637473e2(arg0, arg1) {\n const ret = debugString(arg1);\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);\n const len1 = WASM_VECTOR_LEN;\n getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);\n getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);\n}\nexport function __wbg___wbindgen_is_function_acc5528be2b923f2(arg0) {\n const ret = typeof(arg0) === 'function';\n return ret;\n}\nexport function __wbg___wbindgen_is_null_6d937fbfb6478470(arg0) {\n const ret = arg0 === null;\n return ret;\n}\nexport function __wbg___wbindgen_is_object_0beba4a1980d3eea(arg0) {\n const val = arg0;\n const ret = typeof(val) === 'object' && val !== null;\n return ret;\n}\nexport function __wbg___wbindgen_is_string_1fca8072260dd261(arg0) {\n const ret = typeof(arg0) === 'string';\n return ret;\n}\nexport function __wbg___wbindgen_is_undefined_721f8decd50c87a3(arg0) {\n const ret = arg0 === undefined;\n return ret;\n}\nexport function __wbg___wbindgen_jsval_eq_4e8c38722cb8ff51(arg0, arg1) {\n const ret = arg0 === arg1;\n return ret;\n}\nexport function __wbg___wbindgen_memory_9751d9a3017e7c25() {\n const ret = wasm.memory;\n return ret;\n}\nexport function __wbg___wbindgen_number_get_1cc01dd708740256(arg0, arg1) {\n const obj = arg1;\n const ret = typeof(obj) === 'number' ? obj : undefined;\n getDataViewMemory0().setFloat64(arg0 + 8 * 1, isLikeNone(ret) ? 0 : ret, true);\n getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true);\n}\nexport function __wbg___wbindgen_string_get_71bb4348194e31f0(arg0, arg1) {\n const obj = arg1;\n const ret = typeof(obj) === 'string' ? obj : undefined;\n var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);\n var len1 = WASM_VECTOR_LEN;\n getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);\n getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);\n}\nexport function __wbg___wbindgen_throw_ea4887a5f8f9a9db(arg0, arg1) {\n throw new Error(getStringFromWasm0(arg0, arg1));\n}\nexport function __wbg_buffer_7447b9cc2267a9e5(arg0) {\n const ret = arg0.buffer;\n return ret;\n}\nexport function __wbg_call_67f43c91d09298f2() { return handleError(function (arg0, arg1, arg2) {\n const ret = arg0.call(arg1, arg2);\n return ret;\n}, arguments); }\nexport function __wbg_call_b51415974987aa44() { return handleError(function (arg0, arg1) {\n const ret = arg0.call(arg1);\n return ret;\n}, arguments); }\nexport function __wbg_crypto_38df2bab126b63dc(arg0) {\n const ret = arg0.crypto;\n return ret;\n}\nexport function __wbg_delete_21fafcbc7bb82c85() { return handleError(function (arg0, arg1) {\n arg0.delete(arg1);\n}, arguments); }\nexport function __wbg_epoch_15c000ffe3004541() { return handleError(function (arg0, arg1, arg2) {\n const ret = arg0.epoch(arg1, BigInt.asUintN(64, arg2));\n return ret;\n}, arguments); }\nexport function __wbg_error_a6fa202b58aa1cd3(arg0, arg1) {\n let deferred0_0;\n let deferred0_1;\n try {\n deferred0_0 = arg0;\n deferred0_1 = arg1;\n console.error(getStringFromWasm0(arg0, arg1));\n } finally {\n wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);\n }\n}\nexport function __wbg_getRandomValues_c44a50d8cfdaebeb() { return handleError(function (arg0, arg1) {\n arg0.getRandomValues(arg1);\n}, arguments); }\nexport function __wbg_get_615446055a48103f() { return handleError(function (arg0, arg1) {\n const ret = arg0.get(arg1);\n return ret;\n}, arguments); }\nexport function __wbg_globalThis_6d268067835e6709() { return handleError(function () {\n const ret = globalThis.globalThis;\n return ret;\n}, arguments); }\nexport function __wbg_global_3fe6c6c8ad6e6fb2() { return handleError(function () {\n const ret = global.global;\n return ret;\n}, arguments); }\nexport function __wbg_insert_2d611f2bf9b60fee() { return handleError(function (arg0, arg1, arg2) {\n arg0.insert(arg1, arg2);\n}, arguments); }\nexport function __wbg_instanceof_Error_38f854307ecab4ce(arg0) {\n let result;\n try {\n result = arg0 instanceof Error;\n } catch (_) {\n result = false;\n }\n const ret = result;\n return ret;\n}\nexport function __wbg_length_c552db98817b9523(arg0) {\n const ret = arg0.length;\n return ret;\n}\nexport function __wbg_maxEpochId_0b176768b2f352fb() { return handleError(function (arg0, arg1) {\n const ret = arg0.maxEpochId(arg1);\n return ret;\n}, arguments); }\nexport function __wbg_message_a05fcf872473ffc5(arg0) {\n const ret = arg0.message;\n return ret;\n}\nexport function __wbg_msCrypto_bd5a034af96bcba6(arg0) {\n const ret = arg0.msCrypto;\n return ret;\n}\nexport function __wbg_new_227d7c05414eb861() {\n const ret = new Error();\n return ret;\n}\nexport function __wbg_new_32de5cbf49ca7dcb(arg0) {\n const ret = new Uint8Array(arg0);\n return ret;\n}\nexport function __wbg_new_364c96143b8f3496() {\n const ret = new Object();\n return ret;\n}\nexport function __wbg_new_b8b71ca8104fc178(arg0, arg1) {\n const ret = new Error(getStringFromWasm0(arg0, arg1));\n return ret;\n}\nexport function __wbg_new_d9762fd75876aafe() {\n const ret = new Array();\n return ret;\n}\nexport function __wbg_new_no_args_4010ad257320fa4f(arg0, arg1) {\n const ret = new Function(getStringFromWasm0(arg0, arg1));\n return ret;\n}\nexport function __wbg_new_with_byte_offset_and_length_8b21e3b1308deb48(arg0, arg1, arg2) {\n const ret = new Uint8Array(arg0, arg1 >>> 0, arg2 >>> 0);\n return ret;\n}\nexport function __wbg_new_with_length_5fdafe029be917a5(arg0) {\n const ret = new Uint8Array(arg0 >>> 0);\n return ret;\n}\nexport function __wbg_node_84ea875411254db1(arg0) {\n const ret = arg0.node;\n return ret;\n}\nexport function __wbg_process_44c7a14e11e9f69e(arg0) {\n const ret = arg0.process;\n return ret;\n}\nexport function __wbg_push_1303ce035391aed3(arg0, arg1) {\n const ret = arg0.push(arg1);\n return ret;\n}\nexport function __wbg_randomFillSync_6c25eac9869eb53c() { return handleError(function (arg0, arg1) {\n arg0.randomFillSync(arg1);\n}, arguments); }\nexport function __wbg_require_b4edbdcf3e2a1ef0() { return handleError(function () {\n const ret = module.require;\n return ret;\n}, arguments); }\nexport function __wbg_self_1035a7cbd1b0d959() { return handleError(function () {\n const ret = self.self;\n return ret;\n}, arguments); }\nexport function __wbg_set_047d1ea37bb67c19(arg0, arg1, arg2) {\n arg0.set(arg1, arg2 >>> 0);\n}\nexport function __wbg_set_1ddc4b8cd44d0da4() { return handleError(function (arg0, arg1, arg2) {\n const ret = Reflect.set(arg0, arg1, arg2);\n return ret;\n}, arguments); }\nexport function __wbg_set_28cba565792ec75f(arg0, arg1, arg2) {\n arg0[arg1 >>> 0] = arg2;\n}\nexport function __wbg_set_6be42768c690e380(arg0, arg1, arg2) {\n arg0[arg1] = arg2;\n}\nexport function __wbg_set_name_b4c29a3a72ebbddc(arg0, arg1, arg2) {\n arg0.name = getStringFromWasm0(arg1, arg2);\n}\nexport function __wbg_stack_3b0d974bbf31e44f(arg0, arg1) {\n const ret = arg1.stack;\n const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);\n const len1 = WASM_VECTOR_LEN;\n getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);\n getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);\n}\nexport function __wbg_state_bd20456c0f3efbc9() { return handleError(function (arg0, arg1) {\n const ret = arg0.state(arg1);\n return ret;\n}, arguments); }\nexport function __wbg_subarray_e0162dcdea48eb3a(arg0, arg1, arg2) {\n const ret = arg0.subarray(arg1 >>> 0, arg2 >>> 0);\n return ret;\n}\nexport function __wbg_versions_276b2795b1c6a219(arg0) {\n const ret = arg0.versions;\n return ret;\n}\nexport function __wbg_window_9c17850b5e99c0ab() { return handleError(function () {\n const ret = window.window;\n return ret;\n}, arguments); }\nexport function __wbg_write_56b1cf5bb0e780d6() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {\n arg0.write(arg1, arg2, arg3, arg4);\n}, arguments); }\nexport function __wbindgen_cast_0000000000000001(arg0) {\n // Cast intrinsic for `F64 -> Externref`.\n const ret = arg0;\n return ret;\n}\nexport function __wbindgen_cast_0000000000000002(arg0, arg1) {\n // Cast intrinsic for `Ref(String) -> Externref`.\n const ret = getStringFromWasm0(arg0, arg1);\n return ret;\n}\nexport function __wbindgen_cast_0000000000000003(arg0) {\n // Cast intrinsic for `U64 -> Externref`.\n const ret = BigInt.asUintN(64, arg0);\n return ret;\n}\nexport function __wbindgen_init_externref_table() {\n const table = wasm.__wbindgen_externrefs;\n const offset = table.grow(4);\n table.set(0, undefined);\n table.set(offset + 0, undefined);\n table.set(offset + 1, null);\n table.set(offset + 2, true);\n table.set(offset + 3, false);\n}\nconst MlsClientFinalization = (typeof FinalizationRegistry === 'undefined')\n ? { register: () => {}, unregister: () => {} }\n : new FinalizationRegistry(ptr => wasm.__wbg_mlsclient_free(ptr, 1));\nconst MlsGroupFinalization = (typeof FinalizationRegistry === 'undefined')\n ? { register: () => {}, unregister: () => {} }\n : new FinalizationRegistry(ptr => wasm.__wbg_mlsgroup_free(ptr, 1));\n\nfunction addToExternrefTable0(obj) {\n const idx = wasm.__externref_table_alloc();\n wasm.__wbindgen_externrefs.set(idx, obj);\n return idx;\n}\n\nfunction debugString(val) {\n // primitive types\n const type = typeof val;\n if (type == 'number' || type == 'boolean' || val == null) {\n return `${val}`;\n }\n if (type == 'string') {\n return `\"${val}\"`;\n }\n if (type == 'symbol') {\n const description = val.description;\n if (description == null) {\n return 'Symbol';\n } else {\n return `Symbol(${description})`;\n }\n }\n if (type == 'function') {\n const name = val.name;\n if (typeof name == 'string' && name.length > 0) {\n return `Function(${name})`;\n } else {\n return 'Function';\n }\n }\n // objects\n if (Array.isArray(val)) {\n const length = val.length;\n let debug = '[';\n if (length > 0) {\n debug += debugString(val[0]);\n }\n for(let i = 1; i < length; i++) {\n debug += ', ' + debugString(val[i]);\n }\n debug += ']';\n return debug;\n }\n // Test for built-in\n const builtInMatches = /\\[object ([^\\]]+)\\]/.exec(toString.call(val));\n let className;\n if (builtInMatches && builtInMatches.length > 1) {\n className = builtInMatches[1];\n } else {\n // Failed to match the standard '[object ClassName]'\n return toString.call(val);\n }\n if (className == 'Object') {\n // we're a user defined class or Object\n // JSON.stringify avoids problems with cycles, and is generally much\n // easier than looping through ownProperties of `val`.\n try {\n return 'Object(' + JSON.stringify(val) + ')';\n } catch (_) {\n return 'Object';\n }\n }\n // errors\n if (val instanceof Error) {\n return `${val.name}: ${val.message}\\n${val.stack}`;\n }\n // TODO we could test for more things here, like `Set`s and `Map`s.\n return className;\n}\n\nfunction getArrayU8FromWasm0(ptr, len) {\n ptr = ptr >>> 0;\n return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len);\n}\n\nlet cachedDataViewMemory0 = null;\nfunction getDataViewMemory0() {\n if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {\n cachedDataViewMemory0 = new DataView(wasm.memory.buffer);\n }\n return cachedDataViewMemory0;\n}\n\nfunction getStringFromWasm0(ptr, len) {\n return decodeText(ptr >>> 0, len);\n}\n\nlet cachedUint8ArrayMemory0 = null;\nfunction getUint8ArrayMemory0() {\n if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {\n cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);\n }\n return cachedUint8ArrayMemory0;\n}\n\nfunction handleError(f, args) {\n try {\n return f.apply(this, args);\n } catch (e) {\n const idx = addToExternrefTable0(e);\n wasm.__wbindgen_exn_store(idx);\n }\n}\n\nfunction isLikeNone(x) {\n return x === undefined || x === null;\n}\n\nfunction passArray8ToWasm0(arg, malloc) {\n const ptr = malloc(arg.length * 1, 1) >>> 0;\n getUint8ArrayMemory0().set(arg, ptr / 1);\n WASM_VECTOR_LEN = arg.length;\n return ptr;\n}\n\nfunction passArrayJsValueToWasm0(array, malloc) {\n const ptr = malloc(array.length * 4, 4) >>> 0;\n for (let i = 0; i < array.length; i++) {\n const add = addToExternrefTable0(array[i]);\n getDataViewMemory0().setUint32(ptr + 4 * i, add, true);\n }\n WASM_VECTOR_LEN = array.length;\n return ptr;\n}\n\nfunction passStringToWasm0(arg, malloc, realloc) {\n if (realloc === undefined) {\n const buf = cachedTextEncoder.encode(arg);\n const ptr = malloc(buf.length, 1) >>> 0;\n getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);\n WASM_VECTOR_LEN = buf.length;\n return ptr;\n }\n\n let len = arg.length;\n let ptr = malloc(len, 1) >>> 0;\n\n const mem = getUint8ArrayMemory0();\n\n let offset = 0;\n\n for (; offset < len; offset++) {\n const code = arg.charCodeAt(offset);\n if (code > 0x7F) break;\n mem[ptr + offset] = code;\n }\n if (offset !== len) {\n if (offset !== 0) {\n arg = arg.slice(offset);\n }\n ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;\n const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);\n const ret = cachedTextEncoder.encodeInto(arg, view);\n\n offset += ret.written;\n ptr = realloc(ptr, len, offset, 1) >>> 0;\n }\n\n WASM_VECTOR_LEN = offset;\n return ptr;\n}\n\nfunction takeFromExternrefTable0(idx) {\n const value = wasm.__wbindgen_externrefs.get(idx);\n wasm.__externref_table_dealloc(idx);\n return value;\n}\n\nlet cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });\ncachedTextDecoder.decode();\nconst MAX_SAFARI_DECODE_BYTES = 2146435072;\nlet numBytesDecoded = 0;\nfunction decodeText(ptr, len) {\n numBytesDecoded += len;\n if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) {\n cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });\n cachedTextDecoder.decode();\n numBytesDecoded = len;\n }\n return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));\n}\n\nconst cachedTextEncoder = new TextEncoder();\n\nif (!('encodeInto' in cachedTextEncoder)) {\n cachedTextEncoder.encodeInto = function (arg, view) {\n const buf = cachedTextEncoder.encode(arg);\n view.set(buf);\n return {\n read: arg.length,\n written: buf.length\n };\n };\n}\n\nlet WASM_VECTOR_LEN = 0;\n\n\nlet wasm;\nexport function __wbg_set_wasm(val) {\n wasm = val;\n}\n","\nexport function date_now() {\n return Date.now();\n}","// Portable WASM loader for the palbe-mls engine — works in browser bundles and\n// Node without filesystem paths, public URLs, or runtime network requests.\n//\n// Why not just `import * as wasm from './palbe_mls_bg.wasm'` (the bundler\n// target's own entry, `palbe_mls.js`)? Because that import form only works in\n// bundlers that special-case `.wasm` (webpack/Vite with config) and NEVER in\n// Node — so the two-client crypto smoke (vitest, Node) could not run the real\n// engine. Instead we replicate what the wasm-pack `web`/`nodejs` targets do\n// internally: build the import object from the glue module's named exports +\n// the inline snippet, then `WebAssembly.instantiate` the bytes ourselves. The\n// bytes live in a private, lazily imported JavaScript package target, so a\n// consumer bundler can code-split them without needing a `.wasm` loader.\n//\n// init is idempotent + races-safe: concurrent callers share one in-flight\n// promise, and a second call after success is a no-op returning the same glue.\n\n// The glue exports every user-facing binding (MlsClient/MlsGroup/generateClient/\n// setPanicHook) AND all the `__wbg_*` import functions the `.wasm` needs, plus\n// `__wbg_set_wasm` (hand the instantiated exports back to the glue) and\n// `__wbindgen_start` (run wasm-bindgen's start section). We import it as a\n// namespace so we can both call the bindings and assemble the import object.\nimport * as glue from './pkg/palbe_mls_bg.js';\n// The single inline snippet the `.wasm` imports directly (Date.now()).\nimport * as snippet from './pkg/snippets/mls-rs-core-f99cdecbb456b09c/inline0.js';\n\n/** The subset of the glue surface the bridge calls. */\nexport interface MlsGlue {\n generateClient: typeof glue.generateClient;\n setPanicHook: typeof glue.setPanicHook;\n MlsClient: typeof glue.MlsClient;\n MlsGroup: typeof glue.MlsGroup;\n}\n\n// The two import-module specifiers the `.wasm` declares (verified via\n// WebAssembly.Module.imports — './palbe_mls_bg.js' + the inline snippet).\nconst GLUE_MODULE = './palbe_mls_bg.js';\nconst SNIPPET_MODULE = './snippets/mls-rs-core-f99cdecbb456b09c/inline0.js';\n\nlet inflight: Promise<MlsGlue> | null = null;\nlet ready: MlsGlue | null = null;\n\nfunction decodeBase64(encoded: unknown): ArrayBuffer {\n if (\n typeof encoded !== 'string' ||\n encoded.length === 0 ||\n typeof globalThis.atob !== 'function'\n ) {\n throw new Error('palbe-mls WASM asset is invalid');\n }\n\n let binary: string;\n try {\n binary = globalThis.atob(encoded);\n } catch {\n throw new Error('palbe-mls WASM asset is invalid');\n }\n\n const bytes = new Uint8Array(binary.length);\n for (let index = 0; index < binary.length; index += 1) {\n bytes[index] = binary.charCodeAt(index);\n }\n return bytes.buffer;\n}\n\nasync function loadWasmModule(): Promise<WebAssembly.Module> {\n const asset = await import('#palbase-mls-wasm');\n return WebAssembly.compile(decodeBase64(asset.default));\n}\n\n/** Assemble the import object the `.wasm` expects from the glue + snippet. */\nfunction buildImports(): WebAssembly.Imports {\n return {\n [GLUE_MODULE]: glue as unknown as WebAssembly.ModuleImports,\n [SNIPPET_MODULE]: snippet as unknown as WebAssembly.ModuleImports,\n };\n}\n\n/**\n * Initialize the MLS WASM engine. Idempotent — call it once before any\n * messaging crypto op (the facade does this on `pb.messaging.start()` and\n * before the first chat op). Returns the glue surface (the bridge uses it).\n */\nexport function initMls(): Promise<MlsGlue> {\n if (ready) return Promise.resolve(ready);\n if (inflight) return inflight;\n const attempt = (async () => {\n const module = await loadWasmModule();\n const instance = await WebAssembly.instantiate(module, buildImports());\n // Hand the instance's exports back to the glue (its module-level `wasm`)…\n const setWasm = (glue as unknown as { __wbg_set_wasm: (e: unknown) => void }).__wbg_set_wasm;\n setWasm(instance.exports);\n // …then run wasm-bindgen's start section (sets up the heap, etc).\n (instance.exports as { __wbindgen_start?: () => void }).__wbindgen_start?.();\n const g: MlsGlue = {\n generateClient: glue.generateClient,\n setPanicHook: glue.setPanicHook,\n MlsClient: glue.MlsClient,\n MlsGroup: glue.MlsGroup,\n };\n // Surface Rust panics as readable console errors in the browser.\n try {\n g.setPanicHook();\n } catch {\n // best-effort: a missing console_error_panic_hook must not fail init\n }\n ready = g;\n return g;\n })();\n inflight = attempt;\n void attempt.catch(() => {\n if (inflight === attempt) inflight = null;\n });\n return attempt;\n}\n\n/** True once `initMls()` has completed (the engine is usable synchronously). */\nexport function isMlsReady(): boolean {\n return ready !== null;\n}\n\n/** The initialized glue, or throw if `initMls()` has not completed. */\nexport function requireMls(): MlsGlue {\n if (!ready) {\n throw new Error('palbe-mls WASM not initialized — await initMls() first');\n }\n return ready;\n}\n","// Typed bridge over the raw WASM `MlsClient`/`MlsGroup`. It normalizes the two\n// byte conventions the engine uses and presents clean TS records:\n//\n// · Direct byte methods (signaturePublicKey, generateKeyPackage,\n// encryptApplicationMessage, groupId, …) already return `Uint8Array`.\n// · serde records/enums (enrollmentMaterial, addMember/commitChanges result,\n// processIncomingMessage, describeIncomingCommit) cross via\n// serde-wasm-bindgen, where `Vec<u8>` fields serialize as ARRAYS OF NUMBERS\n// (documented in src/wasm.rs §Records). We wrap those back into Uint8Array.\n//\n// Field names are camelCase (the engine derives `serde(rename_all =\n// \"camelCase\")`). The `ReceivedMessage` enum is externally tagged:\n// { Application: { sender, data } } | { CommitApplied: { epoch, removedSelf } }\n// | \"Proposal\" | \"Other\"\n//\n// Everything here is SYNC — the engine methods are synchronous (mls-rs storage\n// is sync in this build). Serialization of state happens through the JS storage\n// callbacks the host supplies (see ../storage.ts).\n\nimport { requireMls } from './loader.js';\nimport type {\n IGroupStateStorage,\n IKeyPackageStorage,\n MlsClient as RawClientType,\n MlsGroup as RawGroup,\n} from './pkg/palbe_mls.types.d.ts';\n\nexport type { IGroupStateStorage, IKeyPackageStorage };\n\n/** Coerce a serde byte field (array-of-numbers OR Uint8Array) to Uint8Array. */\nfunction toBytes(v: unknown): Uint8Array {\n if (v instanceof Uint8Array) return v;\n if (Array.isArray(v)) return Uint8Array.from(v as number[]);\n if (v == null) return new Uint8Array(0);\n // ArrayBuffer / typed-array view fallthrough.\n if (ArrayBuffer.isView(v)) return new Uint8Array((v as ArrayBufferView).buffer);\n throw new Error('expected byte field (Uint8Array | number[])');\n}\n\nfunction optBytes(v: unknown): Uint8Array | undefined {\n if (v == null) return undefined;\n return toBytes(v);\n}\n\n// ─── Record types (normalized) ───────────────────────────────────────────────\n\n/** `{ signatureKey, credential, capabilities }` — the SP-0 enroll material. */\nexport interface EnrollmentMaterial {\n signatureKey: Uint8Array;\n credential: Uint8Array;\n capabilities: Uint8Array;\n}\n\n/** Result of staging an add/commit-changes: commit + optional welcome + leaves. */\nexport interface AddResult {\n commit: Uint8Array;\n welcome?: Uint8Array;\n addedLeafIndices: number[];\n}\n\n/** Result of staging a remove: just the commit. */\nexport interface RemoveResult {\n commit: Uint8Array;\n}\n\nexport type CommitKind = 'Add' | 'Remove' | 'Update' | 'Mixed' | 'Other';\n\n/** The membership delta a commit WOULD effect (the reconciliation gate truth). */\nexport interface CommitDescription {\n kind: CommitKind;\n addedIdentities: Uint8Array[];\n removedIdentities: Uint8Array[];\n}\n\n/** What `processIncomingMessage` decoded (the normalized form of the serde enum). */\nexport type ReceivedMessage =\n | { type: 'application'; sender: Uint8Array; data: Uint8Array }\n | { type: 'commitApplied'; epoch: number; removedSelf: boolean }\n | { type: 'proposal' }\n | { type: 'other' };\n\n// ─── Group wrapper ───────────────────────────────────────────────────────────\n\n/** Typed wrapper over a raw `MlsGroup`. */\nexport class MlsGroupHandle {\n /** @internal */\n constructor(private readonly raw: RawGroup) {}\n\n /** The MLS rfc_group_id (routing key / storage key) bytes. */\n groupId(): Uint8Array {\n return this.raw.groupId();\n }\n\n /** Current applied epoch (BigInt → number; epochs stay well under 2^53). */\n currentEpoch(): number {\n return Number(this.raw.currentEpoch());\n }\n\n /** Stage a member add (PENDING after). */\n addMember(keyPackageMsg: Uint8Array): AddResult {\n return normalizeAddResult(this.raw.addMember(keyPackageMsg));\n }\n\n /** Stage a member remove by identity bytes (PENDING after). */\n removeMember(memberId: Uint8Array): RemoveResult {\n const r = this.raw.removeMember(memberId) as { commit: unknown };\n return { commit: toBytes(r.commit) };\n }\n\n /** Stage a SINGLE atomic add+remove commit (PENDING after). */\n commitChanges(addKeyPackages: Uint8Array[], removeMemberIds: Uint8Array[]): AddResult {\n return normalizeAddResult(this.raw.commitChanges(addKeyPackages, removeMemberIds));\n }\n\n /** Apply the staged commit (advance the local epoch). Only after server-accept. */\n applyPendingCommit(): void {\n this.raw.applyPendingCommit();\n }\n\n /** Discard the staged commit without advancing (the 409-rebase path). */\n clearPendingCommit(): void {\n this.raw.clearPendingCommit();\n }\n\n /** True while a staged (unapplied) commit exists. */\n hasPendingCommit(): boolean {\n return this.raw.hasPendingCommit();\n }\n\n /** Encrypt an application message at the current epoch → PrivateMessage bytes. */\n encryptApplicationMessage(plaintext: Uint8Array): Uint8Array {\n return this.raw.encryptApplicationMessage(plaintext);\n }\n\n /** Process an incoming MLSMessage and apply its effect (normalized result). */\n processIncomingMessage(message: Uint8Array): ReceivedMessage {\n return normalizeReceived(this.raw.processIncomingMessage(message));\n }\n\n /** Inspect an incoming Commit WITHOUT applying it (the gate's crypto-truth). */\n describeIncomingCommit(commitMsg: Uint8Array): CommitDescription {\n const d = this.raw.describeIncomingCommit(commitMsg) as {\n kind: CommitKind;\n addedIdentities: unknown[];\n removedIdentities: unknown[];\n };\n return {\n kind: d.kind,\n addedIdentities: d.addedIdentities.map(toBytes),\n removedIdentities: d.removedIdentities.map(toBytes),\n };\n }\n\n /** Persist the current group state through the JS group-storage callback. */\n writeToStorage(): void {\n this.raw.writeToStorage();\n }\n}\n\nfunction normalizeAddResult(v: unknown): AddResult {\n const r = v as { commit: unknown; welcome?: unknown; addedLeafIndices?: unknown[] };\n return {\n commit: toBytes(r.commit),\n welcome: optBytes(r.welcome),\n addedLeafIndices: (r.addedLeafIndices ?? []).map((n) => Number(n)),\n };\n}\n\nfunction normalizeReceived(v: unknown): ReceivedMessage {\n // Unit variants serialize as the bare string (\"Proposal\" / \"Other\").\n if (v === 'Proposal') return { type: 'proposal' };\n if (v === 'Other') return { type: 'other' };\n const obj = v as Record<string, unknown>;\n if (obj.Application) {\n const a = obj.Application as { sender: unknown; data: unknown };\n return { type: 'application', sender: toBytes(a.sender), data: toBytes(a.data) };\n }\n if (obj.CommitApplied) {\n const c = obj.CommitApplied as { epoch: unknown; removedSelf: unknown };\n return { type: 'commitApplied', epoch: Number(c.epoch), removedSelf: Boolean(c.removedSelf) };\n }\n return { type: 'other' };\n}\n\n// ─── Client wrapper ──────────────────────────────────────────────────────────\n\n/** Typed wrapper over a raw `MlsClient`. */\nexport class MlsClientHandle {\n /** @internal */\n constructor(private readonly raw: RawClientType) {}\n\n /** The SP-0 keydir enroll material (`{ signatureKey, credential, capabilities }`). */\n enrollmentMaterial(): EnrollmentMaterial {\n const m = this.raw.enrollmentMaterial() as {\n signatureKey: unknown;\n credential: unknown;\n capabilities: unknown;\n };\n return {\n signatureKey: toBytes(m.signatureKey),\n credential: toBytes(m.credential),\n capabilities: toBytes(m.capabilities),\n };\n }\n\n /** The device's STABLE signature PUBLIC key bytes (the keydir `signature_key`). */\n signaturePublicKey(): Uint8Array {\n return this.raw.signaturePublicKey();\n }\n\n /** The MLS Credential wire bytes (the keydir `credential`). */\n credential(): Uint8Array {\n return this.raw.credential();\n }\n\n /** The MLS Capabilities wire bytes (the keydir `capabilities`). */\n capabilities(): Uint8Array {\n return this.raw.capabilities();\n }\n\n /** The serialized STABLE signature keypair (secret-bearing — persist sealed). */\n signatureKeypair(): Uint8Array {\n return this.raw.signatureKeypair();\n }\n\n /** Mint a one-use KeyPackage (RAW RFC 9420 §10 bytes). */\n generateKeyPackage(): Uint8Array {\n return this.raw.generateKeyPackage();\n }\n\n /** Create a new group at epoch 0 (engine mints the rfc_group_id). */\n createGroup(groupId?: Uint8Array): MlsGroupHandle {\n return new MlsGroupHandle(this.raw.createGroup(groupId ?? undefined));\n }\n\n /** Join a group from a Welcome (opaque MLSMessage bytes). */\n joinGroup(welcome: Uint8Array): MlsGroupHandle {\n return new MlsGroupHandle(this.raw.joinGroup(welcome));\n }\n\n /** Re-hydrate an already-joined group by its rfc_group_id bytes. */\n loadGroup(groupId: Uint8Array): MlsGroupHandle {\n return new MlsGroupHandle(this.raw.loadGroup(groupId));\n }\n}\n\n/**\n * Mint a device MLS client. `initMls()` MUST have completed first\n * (the facade enforces this). `signatureKeypair` is undefined on first run, or\n * the previously-persisted sealed keypair bytes on restart (same MLS identity).\n */\nexport function generateClient(\n id: Uint8Array,\n groupStorage: IGroupStateStorage,\n keyPackageStorage: IKeyPackageStorage,\n signatureKeypair?: Uint8Array,\n): MlsClientHandle {\n const { generateClient: gen } = requireMls();\n return new MlsClientHandle(\n gen(id, groupStorage, keyPackageStorage, signatureKeypair ?? undefined),\n );\n}\n","// The web mirror of iOS `MLSEngine` — a single serialized owner of the WASM\n// MlsClient + the per-group MlsGroup handles. Every public method runs through\n// a promise-chain mutex (`enqueue`) so the synchronous engine critical section\n// is never re-entered concurrently (the engine is single-threaded; two awaited\n// ops interleaving would corrupt the storage write-through cache).\n//\n// DURABILITY BOUNDARY: a mutating engine op (create / join / commit / encrypt /\n// process / applyPending) is followed by an awaited storage flush before the\n// host treats the op as done — so an acked message / applied commit is always\n// durable (the engine erases a message key on first decrypt; the durable copy\n// is the only recoverable form, mirroring the iOS D3/D5 ordering).\n//\n// IDENTITY: the MLS credential identity is the SP-0 device id (`mdv_`), passed\n// as the `id` bytes to generateClient (SP-1.6 per-device leaf identity).\n\nimport type { GroupStateStorage, KeyPackageStorage, SignatureKeyStore } from './storage.js';\nimport { bytesToHex } from './util.js';\nimport {\n type AddResult,\n type CommitDescription,\n generateClient,\n type MlsClientHandle,\n type MlsGroupHandle,\n type ReceivedMessage,\n} from './wasm/bridge.js';\nimport { initMls } from './wasm/loader.js';\n\nconst utf8 = new TextEncoder();\n\n/** The rfc_group_id keyed by its hex form (the per-group handle cache key). */\nconst gidHex = bytesToHex;\n\nexport interface EngineEnrollmentMaterial {\n signatureKey: Uint8Array;\n credential: Uint8Array;\n capabilities: Uint8Array;\n}\n\n/**\n * The serialized MLS engine. Built once per (userId, deviceId). All crypto +\n * storage flows through here; `GroupMessaging` and `MessageDeliverySource` hold\n * the SAME instance so one MLS identity backs enroll + group ops + delivery.\n */\nexport class MlsEngine {\n private readonly groups = new Map<string, MlsGroupHandle>();\n private chain: Promise<unknown> = Promise.resolve();\n\n private constructor(\n readonly userId: string,\n readonly deviceId: string,\n private readonly client: MlsClientHandle,\n private readonly groupStore: GroupStateStorage,\n private readonly kpStore: KeyPackageStorage,\n ) {}\n\n /**\n * Build the engine: init the WASM, hydrate the durable stores into the sync\n * caches, generate (or restore) the device client. Idempotent restore: the\n * persisted signature keypair keeps the SAME MLS signing identity across\n * reloads; the group-state cache makes already-joined groups reloadable.\n */\n static async create(opts: {\n userId: string;\n deviceId: string;\n groupStore: GroupStateStorage;\n kpStore: KeyPackageStorage;\n sigStore: SignatureKeyStore;\n }): Promise<MlsEngine> {\n await initMls();\n // Hydrate the durable state into the sync caches BEFORE generateClient so a\n // restart sees its persisted groups + key-package secrets.\n await Promise.all([opts.groupStore.hydrate(), opts.kpStore.hydrate()]);\n const persistedKeypair = await opts.sigStore.load(opts.userId);\n const idBytes = utf8.encode(opts.deviceId);\n const client = generateClient(idBytes, opts.groupStore, opts.kpStore, persistedKeypair);\n // Persist the (possibly freshly minted) signature keypair so the next reload\n // restores the SAME identity.\n if (!persistedKeypair) {\n await opts.sigStore.save(opts.userId, client.signatureKeypair());\n }\n return new MlsEngine(opts.userId, opts.deviceId, client, opts.groupStore, opts.kpStore);\n }\n\n /** Serialize an engine op onto the chain so the critical section is exclusive. */\n private enqueue<T>(fn: () => T | Promise<T>): Promise<T> {\n const run = this.chain.then(fn, fn);\n // Keep the chain alive but swallow rejections so one failure doesn't poison\n // every later op (each caller still sees its own rejection via `run`).\n this.chain = run.then(\n () => undefined,\n () => undefined,\n );\n return run;\n }\n\n /** Flush both storage caches durably (the post-mutation durability boundary). */\n private async flush(): Promise<void> {\n await Promise.all([this.groupStore.flush(), this.kpStore.flush()]);\n }\n\n // ── Enroll material / key packages ──\n\n enrollmentMaterial(): Promise<EngineEnrollmentMaterial> {\n return this.enqueue(() => this.client.enrollmentMaterial());\n }\n\n signaturePublicKey(): Promise<Uint8Array> {\n return this.enqueue(() => this.client.signaturePublicKey());\n }\n\n generateKeyPackage(): Promise<Uint8Array> {\n return this.enqueue(async () => {\n const kp = this.client.generateKeyPackage();\n // A generated key package stores its private bits via the kp store — flush.\n await this.flush();\n return kp;\n });\n }\n\n // ── Group create / join / reload ──\n\n /** Create a group; returns its rfc_group_id (the routing key). */\n createGroup(): Promise<Uint8Array> {\n return this.enqueue(async () => {\n const group = this.client.createGroup();\n const gid = group.groupId();\n this.groups.set(gidHex(gid), group);\n group.writeToStorage();\n await this.flush();\n return gid;\n });\n }\n\n /** Join from a Welcome; returns the joined group's rfc_group_id. */\n joinFromWelcome(welcome: Uint8Array): Promise<Uint8Array> {\n return this.enqueue(async () => {\n const group = this.client.joinGroup(welcome);\n const gid = group.groupId();\n this.groups.set(gidHex(gid), group);\n group.writeToStorage();\n await this.flush();\n return gid;\n });\n }\n\n /** The current applied epoch for a group (0 if unknown). */\n epoch(rfcGroupId: Uint8Array): Promise<number> {\n return this.enqueue(() => {\n const g = this.handle(rfcGroupId);\n return g ? g.currentEpoch() : 0;\n });\n }\n\n // ── Membership commits (staged → applied/cleared by the rebase loop) ──\n\n /** Stage a SINGLE atomic add+remove commit. Returns the AddResult (PENDING). */\n commitChanges(\n rfcGroupId: Uint8Array,\n adds: Uint8Array[],\n removeMemberIds: Uint8Array[],\n ): Promise<AddResult> {\n return this.enqueue(() => {\n const g = this.requireHandle(rfcGroupId);\n return g.commitChanges(adds, removeMemberIds);\n });\n }\n\n /** Apply the staged commit (advance the epoch). Only after a server-accept. */\n applyPendingCommit(rfcGroupId: Uint8Array): Promise<void> {\n return this.enqueue(async () => {\n const g = this.requireHandle(rfcGroupId);\n g.applyPendingCommit();\n g.writeToStorage();\n await this.flush();\n });\n }\n\n /** Discard the staged commit (the 409-rebase path). */\n clearPendingCommit(rfcGroupId: Uint8Array): Promise<void> {\n return this.enqueue(() => {\n const g = this.handle(rfcGroupId);\n if (g?.hasPendingCommit()) g.clearPendingCommit();\n });\n }\n\n // ── Application messages ──\n\n /** Encrypt a plaintext at the current epoch → PrivateMessage bytes. */\n encryptApplication(rfcGroupId: Uint8Array, plaintext: Uint8Array): Promise<Uint8Array> {\n return this.enqueue(async () => {\n const g = this.requireHandle(rfcGroupId);\n const ct = g.encryptApplicationMessage(plaintext);\n // The sender ratchet advanced — checkpoint.\n g.writeToStorage();\n await this.flush();\n return ct;\n });\n }\n\n /**\n * Process an incoming MLSMessage (decrypt application / apply commit). Returns\n * the normalized ReceivedMessage. `describeIncomingCommit` could gate a commit\n * before applying when the server fans a declaration; today the live server\n * does not, so we process directly (parity with the iOS nil-declaration path).\n */\n processIncoming(rfcGroupId: Uint8Array, blob: Uint8Array): Promise<ReceivedMessage> {\n return this.enqueue(async () => {\n const g = this.requireHandle(rfcGroupId);\n const received = g.processIncomingMessage(blob);\n // A commit/decrypt mutated state — checkpoint before the host acks.\n g.writeToStorage();\n await this.flush();\n return received;\n });\n }\n\n /** Inspect a commit WITHOUT applying (the reconciliation gate's crypto-truth). */\n describeIncomingCommit(rfcGroupId: Uint8Array, commit: Uint8Array): Promise<CommitDescription> {\n return this.enqueue(() => this.requireHandle(rfcGroupId).describeIncomingCommit(commit));\n }\n\n /** Persist the current group state (explicit checkpoint). */\n writeToStorage(rfcGroupId: Uint8Array): Promise<void> {\n return this.enqueue(async () => {\n const g = this.handle(rfcGroupId);\n if (g) {\n g.writeToStorage();\n await this.flush();\n }\n });\n }\n\n // ── Handle resolution ──\n\n /** Resolve (or reload from storage) the handle for a group, or undefined. */\n private handle(rfcGroupId: Uint8Array): MlsGroupHandle | undefined {\n const key = gidHex(rfcGroupId);\n const cached = this.groups.get(key);\n if (cached) return cached;\n // Try to reload from the hydrated group-state cache (restart-safe).\n try {\n const g = this.client.loadGroup(rfcGroupId);\n this.groups.set(key, g);\n return g;\n } catch {\n return undefined;\n }\n }\n\n private requireHandle(rfcGroupId: Uint8Array): MlsGroupHandle {\n const g = this.handle(rfcGroupId);\n if (!g) throw new Error(`MLS group not loaded: ${gidHex(rfcGroupId)}`);\n return g;\n }\n}\n","// The group registry — maps rfc_group_id ↔ the resolved MessagingGroup and\n// drives the observable chat list. Web mirror of the iOS GroupRegistry: a SET\n// change (register-new / remove / hydrate) publishes a fresh [Chat] snapshot;\n// an in-place epoch bump does NOT (a chatty group must not invalidate the list).\n\nimport type { Chat } from './chat.js';\nimport type { MessagingGroup } from './group-messaging.js';\nimport type { GroupCatalog } from './history.js';\n\n/** A pointer-stable Chat factory: one Chat per rfc_group_id. */\nexport type ChatFactory = (group: MessagingGroup) => Chat;\n\nexport class GroupRegistry {\n private readonly byRfc = new Map<string, MessagingGroup>();\n private catalog: GroupCatalog | null = null;\n private chatFactory: ChatFactory | null = null;\n private onListChange: ((chats: Chat[]) => void) | null = null;\n\n /** Wire the observable chat-list sink + the pointer-stable Chat factory, then\n * replay the current set so a sink attached after groups loaded reflects them. */\n attachChatList(onListChange: (chats: Chat[]) => void, factory: ChatFactory): void {\n this.onListChange = onListChange;\n this.chatFactory = factory;\n this.publish();\n }\n\n /** Hydrate from the durable catalog (live entries win — fresher epoch). */\n async attachCatalog(catalog: GroupCatalog): Promise<void> {\n this.catalog = catalog;\n let changed = false;\n for (const c of await catalog.all()) {\n if (!this.byRfc.has(c.rfcGroupId)) {\n this.byRfc.set(c.rfcGroupId, {\n displayId: c.displayId,\n rfcGroupId: c.rfcGroupId,\n currentEpoch: c.currentEpoch,\n ownerUserId: c.ownerUserId,\n directKey: c.directKey,\n name: c.name,\n });\n changed = true;\n }\n }\n if (changed) this.publish();\n }\n\n register(group: MessagingGroup): void {\n const isNew = !this.byRfc.has(group.rfcGroupId);\n this.byRfc.set(group.rfcGroupId, group);\n void this.persist(group);\n if (isNew) this.publish();\n }\n\n remove(rfcGroupId: string): void {\n if (!this.byRfc.has(rfcGroupId)) return;\n this.byRfc.delete(rfcGroupId);\n if (this.catalog) void this.catalog.remove(rfcGroupId);\n this.publish();\n }\n\n group(rfcGroupId: string): MessagingGroup | undefined {\n return this.byRfc.get(rfcGroupId);\n }\n\n /** The single known group, if exactly one (the wire-gap routing fallback). */\n soleGroup(): MessagingGroup | undefined {\n return this.byRfc.size === 1 ? this.byRfc.values().next().value : undefined;\n }\n\n all(): MessagingGroup[] {\n return [...this.byRfc.values()];\n }\n\n /** Advance a cached epoch (after a processed commit). Does NOT publish (the\n * group SET is unchanged — only metadata). */\n bumpEpoch(rfcGroupId: string, epoch: number): void {\n const g = this.byRfc.get(rfcGroupId);\n if (!g) return;\n const bumped = { ...g, currentEpoch: epoch };\n this.byRfc.set(rfcGroupId, bumped);\n void this.persist(bumped);\n }\n\n private publish(): void {\n const factory = this.chatFactory;\n if (!this.onListChange || !factory) return;\n const chats = this.all().map((g) => factory(g));\n this.onListChange(chats);\n }\n\n private async persist(group: MessagingGroup): Promise<void> {\n if (!this.catalog) return;\n try {\n await this.catalog.upsert({\n displayId: group.displayId,\n rfcGroupId: group.rfcGroupId,\n currentEpoch: group.currentEpoch,\n ownerUserId: group.ownerUserId,\n directKey: group.directKey,\n name: group.name,\n });\n } catch {\n // best-effort persistence — never blocks the live path\n }\n }\n}\n","// The persistence layer for web messaging — the web mirror of the iOS\n// Keychain-sealed MLS stores (MessagingGroupStateStorage / KeyPackageStorage /\n// MessagingDeviceIDStore / MessagingSignatureKeyStore / MessageStore /\n// GroupCatalog).\n//\n// The CRITICAL contract (src/wasm.rs §Storage): mls-rs calls IGroupStateStorage\n// / IKeyPackageStorage methods SYNCHRONOUSLY inside the engine's locked\n// critical section. So those are backed by an in-memory write-through cache\n// (sync reads/writes) and flushed to a durable, encrypted-at-rest backend\n// (IndexedDB + WebCrypto AES-GCM) ASYNCHRONOUSLY, OUTSIDE the engine call. The\n// engine never awaits I/O; the host (the engine wrapper) awaits the flush after\n// each mutating op so an acked message is always durable.\n//\n// At-rest encryption: a per-device AES-GCM key, non-extractable, kept in\n// IndexedDB as a CryptoKey (browsers persist CryptoKey objects in IDB without\n// exposing raw bytes — the web analogue of the iOS Keychain seal key). In Node\n// (vitest / SSR) there is no IndexedDB; a pure in-memory durable backend stands\n// in (the crypto smoke needs durability WITHIN a process, not across reloads).\n\nimport { bytesToHex } from './util.js';\nimport type { IGroupStateStorage, IKeyPackageStorage } from './wasm/bridge.js';\n\n// ─── Durable KV backend (IndexedDB in browser, memory in Node) ──────────────\n\n/** A namespaced async key→bytes store. Values are sealed before write. */\nexport interface DurableKV {\n get(key: string): Promise<Uint8Array | undefined>;\n set(key: string, value: Uint8Array): Promise<void>;\n delete(key: string): Promise<void>;\n /** All keys under a prefix (for hydrating a store on launch). */\n keys(prefix: string): Promise<string[]>;\n}\n\nconst hasIndexedDB = (): boolean =>\n typeof indexedDB !== 'undefined' && typeof crypto !== 'undefined' && !!crypto.subtle;\n\n/** A tight, non-shared ArrayBuffer copy of a byte view (WebCrypto BufferSource). */\nfunction copyToArrayBuffer(view: Uint8Array): ArrayBuffer {\n const out = new ArrayBuffer(view.byteLength);\n new Uint8Array(out).set(view);\n return out;\n}\n\n// ── In-memory backend (Node/SSR/tests) ──────────────────────────────────────\n\nclass MemoryKV implements DurableKV {\n private readonly map = new Map<string, Uint8Array>();\n get(key: string): Promise<Uint8Array | undefined> {\n return Promise.resolve(this.map.get(key));\n }\n set(key: string, value: Uint8Array): Promise<void> {\n this.map.set(key, value.slice());\n return Promise.resolve();\n }\n delete(key: string): Promise<void> {\n this.map.delete(key);\n return Promise.resolve();\n }\n keys(prefix: string): Promise<string[]> {\n return Promise.resolve([...this.map.keys()].filter((k) => k.startsWith(prefix)));\n }\n}\n\n// ── IndexedDB backend (browser) with WebCrypto AES-GCM seal at rest ─────────\n\nconst DB_NAME = 'palbe.messaging';\nconst STORE = 'kv';\nconst KEY_STORE = 'keys';\nconst SEAL_KEY_ID = 'seal-key-v1';\n\nfunction openDb(): Promise<IDBDatabase> {\n return new Promise((resolve, reject) => {\n const req = indexedDB.open(DB_NAME, 1);\n req.onupgradeneeded = () => {\n const db = req.result;\n if (!db.objectStoreNames.contains(STORE)) db.createObjectStore(STORE);\n if (!db.objectStoreNames.contains(KEY_STORE)) db.createObjectStore(KEY_STORE);\n };\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => reject(req.error);\n });\n}\n\nfunction idbReq<T>(req: IDBRequest<T>): Promise<T> {\n return new Promise((resolve, reject) => {\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => reject(req.error);\n });\n}\n\n/** Get-or-create the non-extractable AES-GCM seal key (persisted as a CryptoKey). */\nasync function getSealKey(db: IDBDatabase): Promise<CryptoKey> {\n const existing = await idbReq(\n db.transaction(KEY_STORE, 'readonly').objectStore(KEY_STORE).get(SEAL_KEY_ID),\n );\n if (existing) return existing as CryptoKey;\n const key = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, false, [\n 'encrypt',\n 'decrypt',\n ]);\n // Storing a non-extractable CryptoKey keeps the raw bytes inside the browser's\n // key store — the page can use it to seal/open but can never read it out.\n await idbReq(db.transaction(KEY_STORE, 'readwrite').objectStore(KEY_STORE).put(key, SEAL_KEY_ID));\n return key;\n}\n\nclass IndexedDbKV implements DurableKV {\n private dbPromise: Promise<IDBDatabase> | null = null;\n private keyPromise: Promise<CryptoKey> | null = null;\n\n private db(): Promise<IDBDatabase> {\n if (!this.dbPromise) this.dbPromise = openDb();\n return this.dbPromise;\n }\n private async sealKey(): Promise<CryptoKey> {\n if (!this.keyPromise) this.keyPromise = this.db().then(getSealKey);\n return this.keyPromise;\n }\n\n private async seal(plain: Uint8Array): Promise<Uint8Array> {\n const key = await this.sealKey();\n const iv = crypto.getRandomValues(new Uint8Array(12));\n const ct = new Uint8Array(\n await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, copyToArrayBuffer(plain)),\n );\n const out = new Uint8Array(iv.length + ct.length);\n out.set(iv, 0);\n out.set(ct, iv.length);\n return out;\n }\n\n private async open(sealed: Uint8Array): Promise<Uint8Array> {\n const key = await this.sealKey();\n const iv = copyToArrayBuffer(sealed.subarray(0, 12));\n const ct = copyToArrayBuffer(sealed.subarray(12));\n const pt = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ct);\n return new Uint8Array(pt);\n }\n\n async get(k: string): Promise<Uint8Array | undefined> {\n const db = await this.db();\n const sealed = (await idbReq(db.transaction(STORE, 'readonly').objectStore(STORE).get(k))) as\n | Uint8Array\n | undefined;\n if (!sealed) return undefined;\n return this.open(sealed);\n }\n async set(k: string, v: Uint8Array): Promise<void> {\n const db = await this.db();\n const sealed = await this.seal(v);\n await idbReq(db.transaction(STORE, 'readwrite').objectStore(STORE).put(sealed, k));\n }\n async delete(k: string): Promise<void> {\n const db = await this.db();\n await idbReq(db.transaction(STORE, 'readwrite').objectStore(STORE).delete(k));\n }\n async keys(prefix: string): Promise<string[]> {\n const db = await this.db();\n const all = (await idbReq(\n db.transaction(STORE, 'readonly').objectStore(STORE).getAllKeys(),\n )) as IDBValidKey[];\n return all.map(String).filter((k) => k.startsWith(prefix));\n }\n}\n\n/** Build the durable KV for this environment (IndexedDB in browser, memory else). */\nexport function createDurableKV(): DurableKV {\n return hasIndexedDB() ? new IndexedDbKV() : new MemoryKV();\n}\n\n// ─── Byte / hex helpers ──────────────────────────────────────────────────────\n\nfunction bytesKey(prefix: string, id: Uint8Array): string {\n // Hex the id bytes so any blob can be a string key.\n return prefix + bytesToHex(id);\n}\n\n// ─── Sync write-through cache (the IGroupStateStorage / IKeyPackageStorage seam)\n\n/**\n * The synchronous group-state storage mls-rs calls inside the engine. Reads/\n * writes hit an in-memory cache (sync); a durable flush to the encrypted KV is\n * scheduled and awaited by the engine wrapper OUTSIDE the engine call.\n *\n * Layout per group (keyed by hex(groupId)):\n * state:<gid> → latest serialized group state\n * epoch:<gid>:<epochId> → a stored prior-epoch record\n * maxEpoch:<gid> → highest stored prior-epoch id (decimal string bytes)\n */\nexport class GroupStateStorage implements IGroupStateStorage {\n // In-memory authoritative cache (the engine reads/writes this synchronously).\n private readonly stateCache = new Map<string, Uint8Array>();\n private readonly epochCache = new Map<string, Uint8Array>();\n private readonly maxEpochCache = new Map<string, bigint>();\n // Keys touched since the last flush (durable write queue).\n private readonly dirty = new Set<string>();\n\n constructor(private readonly kv: DurableKV) {}\n\n // ── sync engine seam ──\n state(groupId: Uint8Array): Uint8Array | undefined {\n return this.stateCache.get(bytesKey('', groupId));\n }\n epoch(groupId: Uint8Array, epochId: bigint): Uint8Array | undefined {\n return this.epochCache.get(`${bytesKey('', groupId)}:${epochId}`);\n }\n write(\n groupId: Uint8Array,\n groupState: Uint8Array,\n epochInserts: { id: bigint; data: Uint8Array }[],\n epochUpdates: { id: bigint; data: Uint8Array }[],\n ): void {\n const gid = bytesKey('', groupId);\n this.stateCache.set(gid, groupState.slice());\n this.dirty.add(`state:${gid}`);\n let max = this.maxEpochCache.get(gid) ?? -1n;\n for (const rec of [...epochInserts, ...epochUpdates]) {\n this.epochCache.set(`${gid}:${rec.id}`, rec.data.slice());\n this.dirty.add(`epoch:${gid}:${rec.id}`);\n if (rec.id > max) max = rec.id;\n }\n if (max >= 0n) {\n this.maxEpochCache.set(gid, max);\n this.dirty.add(`maxEpoch:${gid}`);\n }\n }\n maxEpochId(groupId: Uint8Array): bigint | undefined {\n return this.maxEpochCache.get(bytesKey('', groupId));\n }\n\n // ── async durable flush (called by the engine wrapper after a mutating op) ──\n async flush(): Promise<void> {\n if (this.dirty.size === 0) return;\n const pending = [...this.dirty];\n this.dirty.clear();\n await Promise.all(\n pending.map(async (key) => {\n if (key.startsWith('state:')) {\n const gid = key.slice('state:'.length);\n const v = this.stateCache.get(gid);\n if (v) await this.kv.set(`gss:state:${gid}`, v);\n } else if (key.startsWith('epoch:')) {\n const rest = key.slice('epoch:'.length);\n const v = this.epochCache.get(rest);\n if (v) await this.kv.set(`gss:epoch:${rest}`, v);\n } else if (key.startsWith('maxEpoch:')) {\n const gid = key.slice('maxEpoch:'.length);\n const max = this.maxEpochCache.get(gid);\n if (max != null) await this.kv.set(`gss:maxEpoch:${gid}`, encodeBigint(max));\n }\n }),\n );\n }\n\n /** Hydrate the in-memory cache from the durable KV (call before generateClient). */\n async hydrate(): Promise<void> {\n const keys = await this.kv.keys('gss:');\n for (const k of keys) {\n const v = await this.kv.get(k);\n if (!v) continue;\n if (k.startsWith('gss:state:')) {\n this.stateCache.set(k.slice('gss:state:'.length), v);\n } else if (k.startsWith('gss:epoch:')) {\n this.epochCache.set(k.slice('gss:epoch:'.length), v);\n } else if (k.startsWith('gss:maxEpoch:')) {\n this.maxEpochCache.set(k.slice('gss:maxEpoch:'.length), decodeBigint(v));\n }\n }\n }\n\n async wipe(): Promise<void> {\n this.stateCache.clear();\n this.epochCache.clear();\n this.maxEpochCache.clear();\n this.dirty.clear();\n for (const k of await this.kv.keys('gss:')) await this.kv.delete(k);\n }\n}\n\n/**\n * The synchronous key-package secret storage mls-rs calls. Secret-bearing (HPKE\n * private keys) — sealed at rest by the KV. Same sync-cache + async-flush shape.\n */\nexport class KeyPackageStorage implements IKeyPackageStorage {\n private readonly cache = new Map<string, Uint8Array>();\n private readonly dirtySet = new Set<string>();\n private readonly dirtyDelete = new Set<string>();\n\n constructor(private readonly kv: DurableKV) {}\n\n // ── sync engine seam ──\n delete(id: Uint8Array): void {\n const k = bytesKey('', id);\n this.cache.delete(k);\n this.dirtyDelete.add(k);\n this.dirtySet.delete(k);\n }\n insert(id: Uint8Array, data: Uint8Array): void {\n const k = bytesKey('', id);\n this.cache.set(k, data.slice());\n this.dirtySet.add(k);\n this.dirtyDelete.delete(k);\n }\n get(id: Uint8Array): Uint8Array | undefined {\n return this.cache.get(bytesKey('', id));\n }\n\n // ── async durable flush ──\n async flush(): Promise<void> {\n if (this.dirtySet.size === 0 && this.dirtyDelete.size === 0) return;\n const sets = [...this.dirtySet];\n const dels = [...this.dirtyDelete];\n this.dirtySet.clear();\n this.dirtyDelete.clear();\n await Promise.all([\n ...sets.map((k) => {\n const v = this.cache.get(k);\n return v ? this.kv.set(`kps:${k}`, v) : Promise.resolve();\n }),\n ...dels.map((k) => this.kv.delete(`kps:${k}`)),\n ]);\n }\n\n async hydrate(): Promise<void> {\n for (const k of await this.kv.keys('kps:')) {\n const v = await this.kv.get(k);\n if (v) this.cache.set(k.slice('kps:'.length), v);\n }\n }\n\n async wipe(): Promise<void> {\n this.cache.clear();\n this.dirtySet.clear();\n this.dirtyDelete.clear();\n for (const k of await this.kv.keys('kps:')) await this.kv.delete(k);\n }\n}\n\nfunction encodeBigint(n: bigint): Uint8Array {\n return new TextEncoder().encode(n.toString());\n}\nfunction decodeBigint(b: Uint8Array): bigint {\n return BigInt(new TextDecoder().decode(b));\n}\n\n// ─── Device-id + signature-keypair stores (small singletons) ────────────────\n\nconst enc = new TextEncoder();\nconst dec = new TextDecoder();\n\n/** Persisted server-minted device id, keyed by user id (enroll idempotency). */\nexport class DeviceIdStore {\n constructor(private readonly kv: DurableKV) {}\n async load(userId: string): Promise<string | undefined> {\n const v = await this.kv.get(`devid:${userId}`);\n return v ? dec.decode(v) : undefined;\n }\n async save(userId: string, deviceId: string): Promise<void> {\n await this.kv.set(`devid:${userId}`, enc.encode(deviceId));\n }\n async clear(userId: string): Promise<void> {\n await this.kv.delete(`devid:${userId}`);\n }\n}\n\n/** Persisted STABLE signature keypair bytes, keyed by user id (same MLS identity). */\nexport class SignatureKeyStore {\n constructor(private readonly kv: DurableKV) {}\n async load(userId: string): Promise<Uint8Array | undefined> {\n return this.kv.get(`sigkey:${userId}`);\n }\n async save(userId: string, keypair: Uint8Array): Promise<void> {\n await this.kv.set(`sigkey:${userId}`, keypair);\n }\n}\n","// The delete-for-me suppression store — the web mirror of iOS `SuppressionStore`.\n//\n// delete-for-me is a PURELY LOCAL, per-device, unsynced \"hide this message\"\n// intent: NO wire, NO attribution, no server contact. It is a persisted per-chat\n// `Set<suppressionKey>` where `suppressionKey` is the message's `client_msg_id`\n// when present, else `seq:<server_seq>` for legacy messages (which lack a\n// client_msg_id but are always server-acked, so `server_seq` is stable). At\n// render time a message whose key is in the set is OMITTED from the transcript.\n//\n// Sealed at rest by the same durable KV (IndexedDB + WebCrypto AES-GCM in the\n// browser) that seals the message store — so the suppression intent survives a\n// cold launch and a for-me'd message stays hidden after reload (the reload-parity\n// boundary for delete-for-me, mirroring the message store's reaction/edit/delete\n// re-fold parity).\n\nimport type { DurableKV } from './storage.js';\nimport { decodeUtf8, encodeUtf8 } from './util.js';\n\n/** Persist a per-chat delete-for-me suppression key set (keyed by rfcGroupId). */\nexport class SuppressionStore {\n constructor(private readonly kv: DurableKV) {}\n\n private key(rfcGroupId: string): string {\n return `supp:${rfcGroupId}`;\n }\n\n /** Load the persisted suppression keys for a chat (empty array if none). */\n async load(rfcGroupId: string): Promise<string[]> {\n const raw = await this.kv.get(this.key(rfcGroupId));\n if (!raw) return [];\n try {\n const parsed = JSON.parse(decodeUtf8(raw));\n return Array.isArray(parsed) ? (parsed as string[]) : [];\n } catch {\n return [];\n }\n }\n\n /** Persist the full suppression key set for a chat (deterministic order). */\n async save(rfcGroupId: string, keys: string[]): Promise<void> {\n const sorted = [...new Set(keys)].sort();\n await this.kv.set(this.key(rfcGroupId), encodeUtf8(JSON.stringify(sorted)));\n }\n\n async wipe(): Promise<void> {\n for (const k of await this.kv.keys('supp:')) await this.kv.delete(k);\n }\n}\n","// The messaging coordinator — the web mirror of iOS MessagingCoordinator +\n// MessagingGroupState. Builds the engine/storage/delivery-source ONCE (lazily,\n// on first chat op after sign-in), implements the ChatBackend seam, mints\n// pointer-stable Chats (one per rfc id), and owns the observable chat list.\n//\n// Process-shared singleton (one runtime per browser tab), like the iOS shared\n// holder. Bound to ONE PalbeRuntime (the configured pb).\n\nimport type { PalbeRuntime } from '../runtime.js';\nimport { Chat, type ChatBackend, type ChatDraft, DELETED_DESCRIPTOR } from './chat.js';\nimport type { AnchorTriple } from './deadline-calculator.js';\nimport { DeleteFold } from './delete-fold.js';\nimport { type IncomingMessage, MessageDeliverySource, MessageHub } from './delivery-source.js';\nimport { DisappearingStore } from './disappearing.js';\nimport { type AuthorResolution, EditFold } from './edit-fold.js';\nimport {\n type ExpirySpec,\n GroupMessaging,\n type MentionRange,\n type MessagingGroup,\n type ReplyRef,\n resolveReply,\n} from './group-messaging.js';\nimport { GroupCatalog, MessageStore, type StoredMessage } from './history.js';\nimport { enrollDevice, listDevices } from './keydir.js';\nimport { MentionElevationStore } from './mention-elevation.js';\nimport { normalizeMentionRangesUtf16 } from './mention-ranges.js';\nimport { MlsEngine } from './mls-engine.js';\nimport { ReactionFold } from './reaction-fold.js';\nimport { GroupRegistry } from './registry.js';\nimport {\n createDurableKV,\n DeviceIdStore,\n type DurableKV,\n GroupStateStorage,\n KeyPackageStorage,\n SignatureKeyStore,\n} from './storage.js';\nimport { SuppressionStore } from './suppression.js';\nimport { TimerFold } from './timer-fold.js';\nimport type {\n ChatMember,\n ChatMessage,\n NotifyScope,\n ResolvedMention,\n ResolvedReply,\n SentReceipt,\n UnreadView,\n Unsubscribe,\n} from './types.js';\nimport { directKey as makeDirectKey, mintDeviceId } from './util.js';\n\ninterface Resolved {\n engine: MlsEngine;\n groups: GroupMessaging;\n source: MessageDeliverySource;\n hub: MessageHub;\n messageStore: MessageStore;\n catalog: GroupCatalog;\n registry: GroupRegistry;\n deviceId: string;\n userId: string;\n}\n\nexport class MessagingCoordinator implements ChatBackend {\n private readonly kv: DurableKV;\n private readonly deviceStore: DeviceIdStore;\n private readonly sigStore: SignatureKeyStore;\n private readonly groupStore: GroupStateStorage;\n private readonly kpStore: KeyPackageStorage;\n private readonly suppressionStore: SuppressionStore;\n private readonly elevationStore: MentionElevationStore;\n private readonly disappearingStore: DisappearingStore;\n private readonly registry = new GroupRegistry();\n\n private resolved: Resolved | null = null;\n private resolvePromise: Promise<Resolved> | null = null;\n\n // Pointer-stable Chat cache (one per rfc id) + the observable chat list.\n private readonly chatByRfc = new Map<string, Chat>();\n private chatList: Chat[] = [];\n private readonly listListeners = new Set<() => void>();\n\n constructor(private readonly rt: PalbeRuntime) {\n this.kv = createDurableKV();\n this.deviceStore = new DeviceIdStore(this.kv);\n this.sigStore = new SignatureKeyStore(this.kv);\n this.groupStore = new GroupStateStorage(this.kv);\n this.kpStore = new KeyPackageStorage(this.kv);\n // The delete-for-me suppression store is durable-only (no engine) — build it\n // eagerly so loadSuppressed/saveSuppressed work without forcing MLS resolution.\n this.suppressionStore = new SuppressionStore(this.kv);\n // The self-elevation dedup store is durable-only (no engine) — build it eagerly so\n // loadElevated/saveElevated work without forcing MLS resolution (mirrors suppression).\n this.elevationStore = new MentionElevationStore(this.kv);\n // The disappearing/TTL purge store is durable-only (no engine) — build it eagerly so\n // the tombstone/purge/anchor seam works without forcing MLS resolution (T10).\n this.disappearingStore = new DisappearingStore(this.kv);\n // Wire the registry → observable chat list with the pointer-stable factory.\n this.registry.attachChatList(\n (chats) => {\n this.chatList = chats;\n this.emitList();\n },\n (group) => this.chatFor(group),\n );\n }\n\n // ── ChatBackend identity ──\n\n get selfUserId(): string {\n const u = this.rt.auth.currentUser;\n if (!u) throw new Error('not_signed_in');\n return u.id;\n }\n\n // ── Public surface (used by the facade) ──\n\n /** Self-enroll + start the delivery source (idempotent). The first chat op\n * calls this implicitly; pb.messaging.start() is the opt-in warm-at-sign-in. */\n async ensureEnrolled(): Promise<void> {\n await this.resolve();\n }\n\n /** Open the ONE direct chat with userId. INSTANT + LOCAL (no network); the\n * group materializes on the first send. Pointer-stable per peer pair. */\n directChat(peerUserId: string): Chat {\n const selfId = this.selfUserId;\n const dk = makeDirectKey(selfId, peerUserId);\n // If an active direct chat with this key already exists, return it.\n for (const chat of this.chatByRfc.values()) {\n if (chat.isDirect && chat.state === 'active') {\n // best-effort: match the active group's direct key via the registry\n const g = this.registry.all().find((x) => x.displayId === chat.id);\n if (g?.directKey === dk) return chat;\n }\n }\n const draft: ChatDraft = {\n mode: { kind: 'direct', peerUserId },\n reservedId: `draft:${dk}`,\n };\n // Cache the draft under its reserved id so two opens converge.\n const cached = this.chatByRfc.get(draft.reservedId);\n if (cached) return cached;\n const chat = new Chat({ draft, kind: 'direct', backend: this });\n this.chatByRfc.set(draft.reservedId, chat);\n // Fire-and-forget enroll so a peer's DM/Welcome can drain in.\n void this.ensureEnrolled().catch(() => {});\n return chat;\n }\n\n /** Open a multi-party group chat (LOCAL + LAZY; created on first send). */\n groupChat(members: string[] = []): Chat {\n const draft: ChatDraft = {\n mode: { kind: 'group', members },\n reservedId: `draft:group:${crypto.randomUUID()}`,\n };\n const chat = new Chat({ draft, kind: 'group', backend: this });\n this.chatByRfc.set(draft.reservedId, chat);\n void this.ensureEnrolled().catch(() => {});\n return chat;\n }\n\n /** Look up an active chat by id (grp_ display id). */\n chatById(id: string): Chat | null {\n const g = this.registry.all().find((x) => x.displayId === id);\n return g ? this.chatFor(g) : (this.chatByRfc.get(id) ?? null);\n }\n\n /** The observable chat list (active chats only). */\n get chats(): readonly Chat[] {\n void this.ensureEnrolled().catch(() => {});\n return this.chatList;\n }\n\n onChatsChange(cb: () => void): Unsubscribe {\n this.listListeners.add(cb);\n void this.ensureEnrolled().catch(() => {});\n return () => this.listListeners.delete(cb);\n }\n\n private emitList(): void {\n for (const cb of this.listListeners) cb();\n }\n\n /** Pointer-stable Chat for a group (one per rfc id). */\n private chatFor(group: MessagingGroup): Chat {\n const existing = this.chatByRfc.get(group.rfcGroupId);\n if (existing) return existing;\n const chat = new Chat({\n group,\n kind: group.directKey ? 'direct' : 'group',\n backend: this,\n });\n this.chatByRfc.set(group.rfcGroupId, chat);\n return chat;\n }\n\n // ── Resolution (build-once) ──\n\n private resolve(): Promise<Resolved> {\n if (this.resolved) return Promise.resolve(this.resolved);\n if (this.resolvePromise) return this.resolvePromise;\n this.resolvePromise = this.buildResolved();\n return this.resolvePromise;\n }\n\n private async buildResolved(): Promise<Resolved> {\n const userId = this.selfUserId;\n // Resolve (or mint + persist) the device id BEFORE building the engine — the\n // engine's MLS credential identity IS the device id (SP-1.6).\n const deviceId = (await this.deviceStore.load(userId)) ?? mintDeviceId();\n\n const engine = await MlsEngine.create({\n userId,\n deviceId,\n groupStore: this.groupStore,\n kpStore: this.kpStore,\n sigStore: this.sigStore,\n });\n\n const messageStore = new MessageStore(this.kv);\n const catalog = new GroupCatalog(this.kv);\n const hub = new MessageHub();\n const groups = new GroupMessaging(this.rt, engine, userId, deviceId, messageStore);\n const source = new MessageDeliverySource(\n this.rt,\n engine,\n hub,\n this.registry,\n messageStore,\n deviceId,\n userId,\n );\n\n // Enroll this device (idempotent — persists the id, publishes key packages).\n await enrollDevice(this.rt, engine, this.deviceStore, {});\n\n // Hydrate the registry from the durable catalog BEFORE the first pump.\n await this.registry.attachCatalog(catalog);\n\n const resolved: Resolved = {\n engine,\n groups,\n source,\n hub,\n messageStore,\n catalog,\n registry: this.registry,\n deviceId,\n userId,\n };\n this.resolved = resolved;\n\n // Start the live delivery source (subscribe wake + initial drain).\n await source.start();\n return resolved;\n }\n\n // ── ChatBackend impl ──\n\n async materialize(draft: ChatDraft): Promise<MessagingGroup> {\n const r = await this.resolve();\n let group: MessagingGroup;\n if (draft.mode.kind === 'direct') {\n group = await r.groups.getOrCreateDirect(draft.mode.peerUserId);\n } else {\n group = await r.groups.createGroup();\n for (const userId of draft.mode.members) {\n await r.groups.addMember(group, userId);\n }\n }\n void r.catalog.upsert({\n displayId: group.displayId,\n rfcGroupId: group.rfcGroupId,\n currentEpoch: group.currentEpoch,\n ownerUserId: group.ownerUserId,\n directKey: group.directKey,\n name: group.name,\n });\n return group;\n }\n\n async sendText(\n group: MessagingGroup,\n text: string,\n replyTo?: ReplyRef | null,\n bodyRanges?: MentionRange[] | null,\n expiry?: ExpirySpec | null,\n ): Promise<{ receipt: SentReceipt; clientMsgId: string }> {\n const r = await this.resolve();\n // Disappearing T10: forward the per-message/default-stamped `expiry` to the wire.\n // Dropping it here (the facade declared only 4 params) silently stripped the\n // per-message TTL from the ciphertext, so a recipient decoded a plain text and\n // never armed — invisible on the sender's own screen (it arms from effectiveExpiry\n // directly) and missed by unit tests that drove groups.sendText directly. Caught by\n // the live 2-browser A↔B gate; locked by the coordinator-forwarding regression test.\n return r.groups.sendText(group, text, replyTo, bodyRanges, expiry);\n }\n\n async sendReaction(\n group: MessagingGroup,\n args: { clientMsgId: string; targetClientMsgId: string; emoji: string; op: 'add' | 'remove' },\n ): Promise<{ receipt: SentReceipt; clientMsgId: string }> {\n const r = await this.resolve();\n return r.groups.sendReaction(group, args);\n }\n\n async sendEdit(\n group: MessagingGroup,\n args: {\n clientMsgId: string;\n targetClientMsgId: string;\n newText: string;\n bodyRanges?: MentionRange[] | null;\n },\n ): Promise<{ receipt: SentReceipt; clientMsgId: string }> {\n const r = await this.resolve();\n return r.groups.sendEdit(group, args);\n }\n\n async sendDelete(\n group: MessagingGroup,\n args: { clientMsgId: string; targetClientMsgId: string },\n ): Promise<{ receipt: SentReceipt; clientMsgId: string }> {\n const r = await this.resolve();\n return r.groups.sendDelete(group, args);\n }\n\n async sendTimerSet(\n group: MessagingGroup,\n args: { clientMsgId: string; ttlSeconds: number | null; start: 'send' | 'read' },\n ): Promise<{ receipt: SentReceipt; clientMsgId: string }> {\n const r = await this.resolve();\n return r.groups.sendTimerSet(group, args);\n }\n\n /** Load this chat's persisted delete-for-me suppression keys (durable-only). */\n loadSuppressed(group: MessagingGroup): Promise<string[]> {\n return this.suppressionStore.load(group.rfcGroupId);\n }\n\n /** Persist this chat's delete-for-me suppression keys (durable-only, no wire). */\n saveSuppressed(group: MessagingGroup, keys: string[]): Promise<void> {\n return this.suppressionStore.save(group.rfcGroupId, keys);\n }\n\n /** Load this chat's persisted self-elevation dedup keys (durable-only). */\n loadElevated(group: MessagingGroup): Promise<string[]> {\n return this.elevationStore.load(group.rfcGroupId);\n }\n\n /** Persist this chat's self-elevation dedup keys (durable-only, no wire). */\n saveElevated(group: MessagingGroup, keys: string[]): Promise<void> {\n return this.elevationStore.save(group.rfcGroupId, keys);\n }\n\n // ── Disappearing / TTL seam (durable-only, no wire) ──\n\n tombstonedSeqs(group: MessagingGroup): Promise<Set<number>> {\n return this.disappearingStore.tombstonedSeqs(group.rfcGroupId);\n }\n\n purgedClientMsgIds(group: MessagingGroup): Promise<Set<string>> {\n return this.disappearingStore.purgedClientMsgIds(group.rfcGroupId);\n }\n\n anchor(group: MessagingGroup, clientMsgId: string): Promise<AnchorTriple | null> {\n return this.disappearingStore.anchor(group.rfcGroupId, clientMsgId);\n }\n\n writeAnchorOnce(group: MessagingGroup, clientMsgId: string, a: AnchorTriple): Promise<void> {\n return this.disappearingStore.writeAnchorOnce(group.rfcGroupId, clientMsgId, a);\n }\n\n tombstone(group: MessagingGroup, serverSeq: number, clientMsgId: string): Promise<void> {\n return this.disappearingStore.tombstone(group.rfcGroupId, serverSeq, clientMsgId);\n }\n\n async history(group: MessagingGroup, limit: number, before?: number): Promise<ChatMessage[]> {\n const r = await this.resolve();\n const rows = await r.messageStore.history(group.rfcGroupId, limit, before);\n // Disappearing T10: the REAL cold-launch path passes the durable per-chat purge state\n // so projectHistory excludes tombstoned seqs + resolves purged orphans + arms the chat\n // default — the SAME pure function the reload-parity test drives (the M1 shared helper).\n const tombstoned = await this.disappearingStore.tombstonedSeqs(group.rfcGroupId);\n const purged = await this.disappearingStore.purgedClientMsgIds(group.rfcGroupId);\n return projectHistory(group.displayId, rows, this.selfUserId, undefined, tombstoned, purged);\n }\n\n async members(group: MessagingGroup): Promise<ChatMember[]> {\n const r = await this.resolve();\n try {\n const rows = await r.groups.listMembers(group.displayId);\n // Dedup device rows → users.\n const byUser = new Map<string, ChatMember>();\n for (const row of rows) {\n if (!byUser.has(row.user_id)) {\n byUser.set(row.user_id, {\n id: row.user_id,\n userId: row.user_id,\n displayName: null,\n role: row.role,\n isSelf: row.user_id === this.selfUserId,\n });\n }\n }\n return [...byUser.values()];\n } catch {\n return [];\n }\n }\n\n async addMember(group: MessagingGroup, userId: string): Promise<void> {\n const r = await this.resolve();\n await r.groups.addMember(group, userId);\n }\n\n async removeMember(group: MessagingGroup, userId: string): Promise<void> {\n const r = await this.resolve();\n await r.groups.removeMember(group, userId);\n }\n\n async leave(group: MessagingGroup): Promise<void> {\n const r = await this.resolve();\n await r.groups.leaveGroup(group);\n r.registry.remove(group.rfcGroupId);\n }\n\n registerActive(group: MessagingGroup): void {\n this.registry.register(group);\n }\n\n setTyping(group: MessagingGroup, isTyping: boolean): void {\n void this.resolve().then((r) => r.source.setTyping(group, isTyping));\n }\n\n async markRead(group: MessagingGroup, upToServerSeq: number): Promise<void> {\n const r = await this.resolve();\n await r.source.markRead(group, upToServerSeq);\n }\n\n // ── Server-metadata cluster: notify scope (mute) + unread ──\n\n async setNotifyScope(group: MessagingGroup, scope: NotifyScope): Promise<NotifyScope> {\n const r = await this.resolve();\n return r.source.setNotifyScope(group, scope);\n }\n\n async getNotifyScope(group: MessagingGroup): Promise<NotifyScope> {\n const r = await this.resolve();\n return r.source.getNotifyScope(group);\n }\n\n async unread(group: MessagingGroup): Promise<UnreadView> {\n const r = await this.resolve();\n return r.source.unread(group);\n }\n\n subscribeLive(group: MessagingGroup, chat: Chat): Unsubscribe {\n let offMsg: (() => void) | null = null;\n let offConv: (() => void) | null = null;\n let resolved: Resolved | null = null;\n // The runtime resolves on a promise, so a component that mounts and unmounts\n // inside one tick unsubscribes BEFORE this body runs. Wiring it up anyway\n // would leave listeners nobody can drop and — the whole point of the\n // departure frame — a device announcing itself on a topic no Chat is\n // holding, with no unsubscribe left to take it down.\n let cancelled = false;\n void this.resolve().then((r) => {\n if (cancelled) return;\n resolved = r;\n offMsg = r.hub.onMessage(group.displayId, (m: IncomingMessage) => {\n void chat.ingestLive(m);\n });\n r.source.observeConversation(group);\n offConv = r.hub.onConv(group.displayId, (e) => chat.applyConv(e.event, e.payload));\n });\n return () => {\n cancelled = true;\n offMsg?.();\n offConv?.();\n // The conv topic (and the presence heartbeat riding it) belongs to the\n // group, not to one Chat — so it comes down only when the LAST live\n // subscriber has left. Presence is broadcast-only with no server-side\n // expiry: without this the departing device stays lit for every peer.\n if (resolved && resolved.hub.convListenerCount(group.displayId) === 0) {\n resolved.source.unobserveConversation(group);\n }\n };\n }\n\n async userIdForDevice(group: MessagingGroup, deviceId: string): Promise<string | null> {\n const r = await this.resolve();\n try {\n const rows = await r.groups.listMembers(group.displayId);\n return rows.find((m) => m.device_id === deviceId)?.user_id ?? null;\n } catch {\n return null;\n }\n }\n\n // ── Trust (chat-scoped) ──\n\n /** List a user's devices (used by safety-number computation). */\n async listUserDevices(userId: string): Promise<string[]> {\n const res = await listDevices(this.rt, userId);\n return res.devices.map((d) => d.device_id);\n }\n}\n\n/**\n * Project a page of stored rows → surfaced `ChatMessage[]`, page-local-folding\n * reaction rows into their targets' tallies (the web mirror of the iOS\n * `MessagingCoordinator.history()` reaction-vs-bubble loop).\n *\n * A `'reaction'`/`'edit'`/`'delete'` row NEVER becomes a bubble — each folds into a\n * page-local `ReactionFold`/`EditFold`/`DeleteFold` and is skipped. Every surfaced\n * message carries its tally + the winning edit text, UNLESS a valid tombstone folded\n * onto it — then it renders the neutral \"deleted\" descriptor with reactions/reply/edit\n * HIDDEN (the render precedence: delete DOMINATES edit). Backward-compat: old rows (no\n * `envelopeType`) project as ordinary messages. Actor attribution at the store layer:\n * outgoing rows = self; incoming rows use `resolveActor(senderDeviceId)` when provided\n * (the live Chat fold, which resolves device→user, remains authoritative cross-page).\n *\n * Exported so the reaction/edit/delete reload-parity boundary is testable with a REAL\n * `MessageStore` (real persist → unseal → project → re-fold), not a bypass — and so the\n * REAL cold-launch path folds deletes (the iOS-review CRITICAL `.delete` branch lives\n * HERE, the same function `coordinator.history()` calls, so a persisted tombstone can\n * never leak as a blank bubble).\n */\nexport function projectHistory(\n displayId: string,\n rows: StoredMessage[],\n selfUserId: string,\n resolveActor?: (senderDeviceId: string | null) => string | null,\n tombstonedSeqs: Set<number> = new Set(),\n purgedClientMsgIds: Set<string> = new Set(),\n): ChatMessage[] {\n // Disappearing T10 (the M1-trap cold-launch parity): drop every row whose serverSeq is\n // in `tombstonedSeqs` BEFORE any fold sees it — a TTL-purged message is NEVER\n // re-projected (the transcript-reader half of the live `ingestLive` redelivery-drop).\n const visible = rows.filter((s) => !tombstonedSeqs.has(s.serverSeq));\n\n // Page-local fold: ingest every reaction row before projecting, so a surfaced\n // message in the same page renders its tally on cold launch.\n const fold = new ReactionFold();\n for (const s of visible) {\n if (s.envelopeType !== 'reaction' || !s.reaction) continue;\n const actor =\n s.direction === 'outgoing' ? selfUserId : (resolveActor?.(s.senderDeviceId ?? null) ?? null);\n if (actor === null) continue; // unresolvable peer actor — leave to the live fold\n fold.ingest({\n targetClientMsgId: s.reaction.targetClientMsgId,\n actorUserId: actor,\n emoji: s.reaction.emoji,\n op: s.reaction.op,\n epoch: s.epoch,\n serverSeq: s.serverSeq,\n eventClientMsgId: s.clientMsgId ?? `${s.id}`,\n });\n }\n\n // Page-local edit fold: an `'edit'` row NEVER becomes a bubble — it folds the\n // latest author edit's text onto its target in place. The author-gate resolves\n // each bubble's author from a page map (outgoing = self; incoming = resolveActor,\n // else unresolved → unknown so the edit HOLDs). A target in `purgedClientMsgIds`\n // resolves to 'purged' so a late edit DROPs (never resurrects). After the batch,\n // reevaluateHeld handles an out-of-order page (an edit row before its target).\n const editFold = new EditFold();\n // Page-local delete fold: a `'delete'` row NEVER becomes a bubble — it folds a\n // delete-for-everyone tombstone onto its target (author-gated). After the batch\n // a tombstoned target is mapped to the neutral \"deleted\" descriptor with\n // reactions/reply/edit HIDDEN (delete DOMINATES edit). This is the iOS-review\n // CRITICAL branch: the REAL projection MUST fold deletes, else a persisted\n // tombstone leaks as a blank bubble + never scrubs on cold launch. The reload\n // test drives THIS exact function (the same one coordinator.history() calls), so\n // the boundary is genuinely locked, not a test-local re-implementation.\n const deleteFold = new DeleteFold();\n // Disappearing T10 (the M1-trap cold-launch chat-default arming): a `'timer_set'` row is\n // NEVER a bubble — fold it into a page-local TimerFold so a bubble with no per-message\n // expiry inherits the chat default active AS OF its order. Because we iterate\n // oldest-first and fold each timer_set BEFORE reaching a later bubble, `active()` at a\n // bubble reflects only timer_set events at a LOWER (epoch, serverSeq) order → FORWARD-ONLY\n // (a message ordered BEFORE the governing timer_set inherits nothing).\n const pageTimerFold = new TimerFold();\n const authorByClientMsgId = new Map<string, string>();\n for (const s of visible) {\n // A non-bubble row (reaction/edit/delete/timer_set) is not itself a target — only the\n // surfaced text/media rows author-gate the folds.\n if (\n s.envelopeType === 'reaction' ||\n s.envelopeType === 'edit' ||\n s.envelopeType === 'delete' ||\n s.envelopeType === 'timer_set'\n )\n continue;\n const cid = s.clientMsgId ?? '';\n if (!cid) continue;\n const author =\n s.direction === 'outgoing' ? selfUserId : resolveActor?.(s.senderDeviceId ?? null);\n if (author != null) authorByClientMsgId.set(cid, author);\n }\n // Disappearing T10: the orphan-aware resolver — a target whose clientMsgId is in\n // `purgedClientMsgIds` resolves to 'purged' (a late edit DROPs / a late delete no-ops),\n // else 'author' from the page map, else 'unknown' (HOLD). Same shape the live\n // `Chat.authorOfTarget` uses, so the cold-launch fold and the live fold never diverge.\n const authorOfTarget = (cid: string): AuthorResolution => {\n if (purgedClientMsgIds.has(cid)) return { kind: 'purged' };\n const a = authorByClientMsgId.get(cid);\n return a !== undefined ? { kind: 'author', userId: a } : { kind: 'unknown' };\n };\n for (const s of visible) {\n if (s.envelopeType !== 'edit' || !s.edit) continue;\n const editor =\n s.direction === 'outgoing' ? selfUserId : (resolveActor?.(s.senderDeviceId ?? null) ?? null);\n editFold.ingest(\n {\n targetClientMsgId: s.edit.targetClientMsgId,\n editorUserId: editor,\n newText: s.edit.newText,\n epoch: s.epoch,\n serverSeq: s.serverSeq,\n eventClientMsgId: s.clientMsgId ?? `${s.id}`,\n // Mentions T6: the edit's replacement ranges ride the fold so the WINNING\n // edit's ranges drive the edited message's mentions on cold launch.\n bodyRanges: s.edit.bodyRanges ?? null,\n },\n authorOfTarget,\n );\n }\n editFold.reevaluateHeld(authorOfTarget);\n // Fold every delete row through the same author-gate (actor = resolved sender;\n // outgoing = self). A pending/held tombstone whose target sits later in the page\n // is re-evaluated by the post-loop sweep below.\n for (const s of visible) {\n if (s.envelopeType !== 'delete' || !s.delete) continue;\n const actor =\n s.direction === 'outgoing' ? selfUserId : (resolveActor?.(s.senderDeviceId ?? null) ?? null);\n deleteFold.ingest(\n {\n targetClientMsgId: s.delete.targetClientMsgId,\n actorUserId: actor,\n epoch: s.epoch,\n serverSeq: s.serverSeq,\n eventClientMsgId: s.clientMsgId ?? `${s.id}`,\n },\n authorOfTarget,\n );\n }\n // Sweep: re-evaluate any pending/held tombstone now that every target's author\n // is known (an out-of-order tombstone before its target in the same page).\n for (const [cid] of authorByClientMsgId) deleteFold.reevaluatePending(cid, authorOfTarget(cid));\n\n // Reply lookup over the surfaced (non-fold) rows.\n const lookup = new Map<string, { text: string; senderUserId: string }>();\n for (const s of visible) {\n if (\n s.envelopeType === 'reaction' ||\n s.envelopeType === 'edit' ||\n s.envelopeType === 'delete' ||\n s.envelopeType === 'timer_set'\n )\n continue;\n const cid = s.clientMsgId ?? '';\n if (cid && s.text !== null) {\n // selfUserId for outgoing rows; '' for incoming (peer user unresolved at\n // history time — the empty string still yields state:'resolved').\n const senderUserId = s.direction === 'outgoing' ? selfUserId : '';\n lookup.set(cid, { text: s.text, senderUserId });\n }\n }\n\n const out: ChatMessage[] = [];\n // The page-local timer fold is consumed INLINE as we iterate oldest-first (so a bubble\n // sees only the chat default at a LOWER order — forward-only). A timer_set row never\n // becomes a bubble; it folds + is skipped.\n for (const s of visible) {\n if (s.envelopeType === 'timer_set') {\n // Fold the chat default (no bubble) — actor = the resolved sender USER; an\n // unresolvable actor drops it (no spoofable timer). own-send (outgoing) → self.\n const actor =\n s.direction === 'outgoing'\n ? selfUserId\n : (resolveActor?.(s.senderDeviceId ?? null) ?? null);\n if (actor !== null && s.timer) {\n pageTimerFold.ingest({\n ttlSeconds: s.timer.ttlSeconds,\n start: s.timer.start,\n actorUserId: actor,\n epoch: s.epoch,\n serverSeq: s.serverSeq,\n eventClientMsgId: s.clientMsgId ?? `${s.id}`,\n });\n }\n continue; // never projected as a row\n }\n if (s.envelopeType === 'reaction' || s.envelopeType === 'edit' || s.envelopeType === 'delete')\n continue; // folded, never a bubble\n const clientMsgId = s.clientMsgId ?? '';\n // RENDER PRECEDENCE (history side; suppression is applied later by the Chat):\n // (1) tombstoned → the neutral \"deleted\" descriptor, HIDE reactions/reply/edit\n // (delete DOMINATES edit — short-circuit the overlay); (2) else edit overlay +\n // reactions + reply. Identical to the live Chat composition so a cold launch\n // and a live delete render the same.\n const isDeleted = clientMsgId ? deleteFold.isTombstoned(clientMsgId) : false;\n if (isDeleted) {\n out.push({\n id: `${displayId}#${s.serverSeq}`,\n kind: 'text',\n direction: s.direction,\n senderUserId: s.direction === 'outgoing' ? selfUserId : null,\n text: DELETED_DESCRIPTOR,\n serverSeq: s.serverSeq,\n sentAt: new Date(s.at),\n clientMsgId,\n replyTo: null,\n reactions: {},\n edited: false,\n isDeleted: true,\n // Mentions T6: delete DOMINATES mentions too — a tombstoned message has none.\n mentions: [],\n // delete DOMINATES disappearing too — a tombstoned message carries no deadline.\n expiresAt: null,\n });\n continue;\n }\n // Disappearing T10 (cold-launch arming): the message's EFFECTIVE TTL = its OWN\n // per-message `expiry` if present, ELSE the chat default active AS OF this message's\n // order (forward-only — `pageTimerFold.active()` reflects only timer_set events at a\n // LOWER (epoch, serverSeq) order because we fold them inline oldest-first). own-expiry\n // WINS. The deadline is derived purely from the row's send time `s.at` + the TTL — a\n // monotonic-derived local value, never a wire field; the Chat re-arms the durable\n // write-once anchor + the advisory purge timer off it on merge.\n let expiresAt: Date | null = null;\n if (s.expiry) {\n expiresAt = new Date(s.at + s.expiry.ttlSeconds * 1000);\n } else {\n const active = pageTimerFold.active();\n if (active && active.ttlSeconds !== null) {\n expiresAt = new Date(s.at + active.ttlSeconds * 1000);\n }\n }\n let replyTo: ResolvedReply | null = null;\n if (s.replyTo) {\n const ref: ReplyRef = {\n v: 1,\n client_msg_id: s.replyTo.clientMsgId,\n preview: {\n kind: s.replyTo.previewKind,\n author_user_id: s.replyTo.previewAuthorUserId ?? '',\n body: s.replyTo.previewBody ?? undefined,\n body_truncated: false,\n },\n };\n replyTo = resolveReply(ref, (id) => lookup.get(id) ?? null);\n }\n // Overlay the winning edit's text + flag onto the row in place: the ORIGINAL\n // decrypted text stays on the persisted row; the projection computes\n // `text = edit ?? original` + `edited` so a cold-launched screen renders the\n // edited text without a new bubble (mirrors iOS T3 history projection).\n const editText = clientMsgId ? editFold.text(clientMsgId) : null;\n const edited = clientMsgId ? editFold.isEdited(clientMsgId) : false;\n const text = editText ?? s.text;\n // Mentions T6: NORMALIZE the row's raw body_ranges against the SURFACED text → spans\n // with a null displayName (name resolution is LIVE — the Chat re-resolves on merge,\n // the projection has no roster). When an edit WON, the surfaced mentions come from the\n // WINNING edit's replacement ranges (against the edited text); else the bubble's own.\n const rawRanges = editText !== null ? editFold.bodyRanges(clientMsgId) : s.bodyRanges;\n const mentions = normalizeMentionsNullNames(rawRanges, text);\n out.push({\n id: `${displayId}#${s.serverSeq}`,\n kind: s.text != null ? 'text' : 'system',\n direction: s.direction,\n senderUserId: s.direction === 'outgoing' ? selfUserId : null,\n text,\n serverSeq: s.serverSeq,\n sentAt: new Date(s.at),\n clientMsgId,\n replyTo,\n reactions: clientMsgId ? fold.tally(clientMsgId) : {},\n edited,\n isDeleted: false,\n mentions,\n expiresAt,\n });\n }\n return out;\n}\n\n/** NORMALIZE raw mention ranges against `text` → `ResolvedMention[]` with `displayName:\n * null` (the history projection has no roster — names are resolved LIVE by the Chat on\n * merge). Returns `[]` when there is no text or no surviving range. */\nfunction normalizeMentionsNullNames(\n raw: MentionRange[] | null | undefined,\n text: string | null,\n): ResolvedMention[] {\n if (text === null || !raw || raw.length === 0) return [];\n return normalizeMentionRangesUtf16(raw, text).map((r) => ({\n start: r.start,\n length: r.length,\n mentionedUserId: r.mentionedUserId,\n displayName: null,\n }));\n}\n","// `pb.messaging` — the chat-centric E2E-encrypted messaging surface (Web-MLS\n// Phase 2). The web mirror of the iOS `pb.messaging`: the dev's mental model is\n// a CHAT (a DM or a group), so the top-level noun is `Chat`, bundling its own\n// messages + typing + presence + members.\n//\n// const dm = pb.messaging.directChat(peerUserId); // INSTANT, LOCAL, no network\n// await dm.send('hi'); // first send MATERIALIZES it\n// const off = pb.messaging.onChatsChange(() => render(pb.messaging.chats));\n//\n// The MLS protocol underneath (keydir enroll/claim, group commits, queue pull)\n// is INTERNAL — the first chat op self-enrolls. `messaging` is a reserved\n// namespace (no backend endpoint may be named `messaging`).\n\nimport type { PalbeRuntime } from '../runtime.js';\nimport type { Chat } from './chat.js';\nimport { MessagingCoordinator } from './coordinator.js';\nimport type { Unsubscribe } from './types.js';\n\nexport class PalbeMessaging {\n private readonly coordinator: MessagingCoordinator;\n\n constructor(rt: PalbeRuntime) {\n this.coordinator = new MessagingCoordinator(rt);\n }\n\n /**\n * Warm messaging at sign-in (optional). Enrolls this device + starts the live\n * delivery source so incoming DMs/Welcomes drain before the chat list opens.\n * Never REQUIRED — any chat op self-enrolls. Idempotent.\n */\n async start(): Promise<void> {\n await this.coordinator.ensureEnrolled();\n }\n\n /**\n * Open the ONE direct chat with `userId`. INSTANT + LOCAL — no network, nothing\n * created yet. Returns a draft `Chat` to show immediately; the server group +\n * MLS material materialize LAZILY on the first `chat.send`. Idempotent + stable:\n * the same two users always resolve to the SAME Chat.\n */\n directChat(userId: string): Chat {\n return this.coordinator.directChat(userId);\n }\n\n /**\n * Open a multi-party group chat. LOCAL + LAZY like `directChat`: pick the\n * members, get a draft Chat, the group is created on the FIRST `chat.send`.\n */\n groupChat(opts: { members?: string[] } = {}): Chat {\n return this.coordinator.groupChat(opts.members ?? []);\n }\n\n /** Look up a chat by id (e.g. from a push payload / deep link). null if unknown. */\n chat(id: string): Chat | null {\n return this.coordinator.chatById(id);\n }\n\n /** The observable chat list (DMs + groups, active only). Read it after an\n * `onChatsChange` subscription to render the inbox. */\n get chats(): readonly Chat[] {\n return this.coordinator.chats;\n }\n\n /** Subscribe to chat-list changes (a chat added/removed/hydrated). */\n onChatsChange(cb: () => void): Unsubscribe {\n return this.coordinator.onChatsChange(cb);\n }\n}\n","// Web cold-start measurement (§2.8: app-start). The web equivalent of iOS's\n// prewarm-discard (AppStartTracker.measure): iOS may PREWARM a process, so it\n// must discard those launches; the web has no prewarm, so a browser load is\n// ALWAYS a real cold start — `measureAppStart` therefore measures whenever the\n// First Contentful Paint timestamp is available, returning `null` only when FCP\n// is missing (no paint yet / unsupported) or the delta is non-positive (clock\n// skew). `value` is milliseconds from navigation start to FCP.\n//\n// PURE by design (mirrors iOS): the FCP/navigation timestamps come in as\n// parameters so the measurement is unit-testable in isolation. The\n// PerformanceObserver wiring (reading `first-contentful-paint` + navigation\n// startTime, firing once) lives in `runtime.ts`'s browser branch, which calls\n// `rt.perf.record(measureAppStart(...))`.\n//\n// The `app_start` trace_type + `cold_start` name match the iOS emitter exactly,\n// so a row from either platform tags identically server-side.\n\nimport type { PerfItem } from './perf-wire.js';\n\n/** First Contentful Paint relative to navigation start (the two `PerformanceEntry`\n * timestamps the browser branch reads). Both are `DOMHighResTimeStamp` ms. */\nexport interface AppStartTimings {\n /** Navigation start time (a `PerformanceNavigationTiming.startTime`, usually 0). */\n startTime: number;\n /** First Contentful Paint `startTime`; `0` when no paint has been recorded. */\n fcp: number;\n}\n\n/**\n * Build a `cold_start` `app_start` PerfItem from the navigation→FCP timings, or\n * `null` when FCP is unavailable (`fcp <= 0`) or the delta is non-positive.\n * `value` is `fcp - startTime` in milliseconds.\n */\nexport function measureAppStart(nav: AppStartTimings): PerfItem | null {\n if (!(nav.fcp > 0)) return null;\n const value = nav.fcp - nav.startTime;\n if (value <= 0) return null;\n return {\n row_id: crypto.randomUUID(),\n trace_type: 'app_start',\n name: 'cold_start',\n value,\n timestamp: Date.now(),\n };\n}\n","// Remote perf sampling (the SDK half). The server owns the sample rate via\n// `GET /v1/analytics/perf/config` `{ sample_pct, flush_at, flush_interval_ms }`\n// (modules/analytics, Faz-2 B1). The SDK fetches it (ETag-cached) and applies\n// `sample_pct` as a CLIENT-SIDE ceiling so it only uploads ~sample_pct% of\n// distinct rows — saving bandwidth — while the server STILL re-samples\n// authoritatively at ingest (defense-in-depth, invariant 3). The SDK never\n// raises its own rate above the fetched value.\n//\n// Cross-boundary wire contract (mutation-evident): `perfSampleBucket` below is a\n// byte-for-byte mirror of the Go `service.SampleDecision`\n// (modules/analytics/internal/service/perf_sampling.go) — FNV-1a-32 over the\n// UTF-8 bytes of `row_id`, mod 100, sampled iff the bucket is strictly below\n// `sample_pct`. Because the decision is DETERMINISTIC per row_id, the client and\n// the server pick the SAME rows: there is no client/server drift, and a row\n// redelivered after an offline-queue reconnect samples identically (idempotency\n// under ReplacingMergeTree). Changing this algorithm here without changing it\n// there breaks the contract — that is why both sides golden-test it.\n\nimport type { PalbeRuntime } from '../runtime.js';\n\n/** The config endpoint the SDK consults for the server-controlled sample rate. */\nconst PERF_CONFIG_PATH = '/v1/analytics/perf/config';\n\n/** FNV-1a 32-bit constants (RFC / Go `hash/fnv`). */\nconst FNV_OFFSET_BASIS_32 = 0x811c9dc5;\nconst FNV_PRIME_32 = 0x01000193;\n\nconst utf8 =\n typeof TextEncoder !== 'undefined'\n ? new TextEncoder()\n : { encode: (s: string): Uint8Array => Uint8Array.from(Buffer.from(s, 'utf-8')) };\n\n/**\n * Deterministic sampling bucket (0..99) for a `row_id`, identical to the Go\n * `service.SampleDecision`: FNV-1a-32 over the row_id's UTF-8 bytes, mod 100.\n * An item is sampled-in iff `perfSampleBucket(row_id) < sample_pct`.\n *\n * Implemented in 32-bit unsigned arithmetic via `Math.imul` (the multiply must\n * wrap exactly like Go's `uint32`); `>>> 0` keeps every intermediate unsigned.\n */\nexport function perfSampleBucket(rowId: string): number {\n let hash = FNV_OFFSET_BASIS_32;\n // Iterate values (not indices) so `noUncheckedIndexedAccess` keeps each byte a\n // definite `number`.\n for (const byte of utf8.encode(rowId)) {\n hash ^= byte;\n hash = Math.imul(hash, FNV_PRIME_32);\n }\n // `>>> 0` coerces to the unsigned 32-bit value before the mod, matching\n // Go's `h.Sum32() % 100` on a `uint32`.\n return (hash >>> 0) % 100;\n}\n\n/** The server config body. snake_case = wire shape (see B1's `perfConfigResponse`). */\ninterface PerfConfig {\n sample_pct: number;\n flush_at: number;\n flush_interval_ms: number;\n}\n\nfunction isPerfConfig(value: unknown): value is PerfConfig {\n if (typeof value !== 'object' || value === null) return false;\n const o = value as Record<string, unknown>;\n return typeof o.sample_pct === 'number';\n}\n\n/** The shape `rt.http.request` resolves to — `{ data, error, status, headers }`. */\ninterface RawResponse {\n data: unknown;\n error: { status: number } | null;\n status?: number;\n headers?: Record<string, string>;\n}\n\nfunction isRawResponse(value: unknown): value is RawResponse {\n return typeof value === 'object' && value !== null && 'data' in value;\n}\n\nfunction headerValue(\n headers: Record<string, string> | undefined,\n name: string,\n): string | undefined {\n if (!headers) return undefined;\n const lower = name.toLowerCase();\n for (const [k, v] of Object.entries(headers)) {\n if (k.toLowerCase() === lower) return v;\n }\n return undefined;\n}\n\n/**\n * Fetches the server-controlled perf config and applies its `sample_pct` to the\n * facade's client-side sampling. ETag-aware: it remembers the last ETag and\n * sends `If-None-Match`, so a steady SDK gets a 304 and skips re-applying an\n * unchanged config.\n *\n * The fetch goes through `rt.http.request` directly (NOT `palbeRequest`'s\n * unwrap) so a 304 — which has no body — is observable instead of throwing, and\n * because the config GET is one of the `/v1/analytics/*` paths the network-trace\n * choke point self-excludes (no self-measurement loop).\n */\nexport class PerfConfigClient {\n private etag: string | undefined;\n\n async fetchConfig(rt: PalbeRuntime, perf: { setSamplePct(pct: number): void }): Promise<void> {\n const headers: Record<string, string> = {};\n if (this.etag) headers['If-None-Match'] = this.etag;\n try {\n const res: unknown = await rt.http.request('GET', PERF_CONFIG_PATH, { headers });\n if (!isRawResponse(res)) return;\n\n // 304 Not Modified: config unchanged — keep the applied pct and the cached\n // ETag. Mutation-evident: if we re-applied here, a 304 would clobber pct.\n if (res.status === 304 || (res.error && res.error.status === 304)) return;\n if (res.error) return; // any other error: keep the current (default 100) pct\n\n const newEtag = headerValue(res.headers, 'etag');\n if (newEtag) this.etag = newEtag;\n\n if (isPerfConfig(res.data)) {\n perf.setSamplePct(res.data.sample_pct);\n }\n } catch {\n // Network/transport failure — fire-and-forget: the SDK keeps its current\n // sample rate (defaults to 100 before the first successful fetch).\n }\n }\n}\n","// Opt-in app-level network capture. By default the SDK only traces ITS OWN\n// fetches (the `request.ts` choke point). An app that wants every `fetch(...)`\n// it makes timed as a `network` perf item can opt in with `installFetchSwizzle`\n// — it wraps the global `fetch`, records a redacted trace per call, and hands\n// back an `uninstall` that restores the original `fetch`. It is OFF by default\n// (the embedder calls this explicitly) because swizzling a global is a\n// page-wide side effect.\n//\n// Self-exclusion: the wrapper skips the `/v1/analytics/*` ingest paths, exactly\n// like the `request.ts` network trace — otherwise the perf-flush POST (which an\n// app could route through the global `fetch`) would emit another `network` item\n// that the next flush ships, an item-amplifying self-measurement loop. This is\n// the SAME prefix guard the SDK uses internally (mutation-evident: drop it and\n// the self-loop test goes red).\n\nimport { redactUrl } from './url-redactor.js';\n\n/** The minimal perf surface the swizzle needs — `recordNetwork`, as on PalbePerf. */\ninterface NetworkPerfSink {\n recordNetwork(\n method: string,\n url: string,\n status: number,\n durationMs: number,\n requestId?: string,\n ): void;\n}\n\n/** The same ingest prefix `request.ts` self-excludes — keep them identical so\n * the wrapper never traces a perf/analytics POST routed through global fetch. */\nconst PERF_EXCLUDED_PREFIX = '/v1/analytics/';\n\n/** Monotonic clock — `performance.now()` when present, else `Date.now()`. */\nfunction nowMs(): number {\n return typeof performance !== 'undefined' && typeof performance.now === 'function'\n ? performance.now()\n : Date.now();\n}\n\n/** Pull the request URL + method out of the `fetch` argument tuple without a\n * cast: `fetch(input, init)` where input is a string, URL, or Request. */\nfunction describeRequest(\n input: RequestInfo | URL,\n init?: RequestInit,\n): { url: string; method: string } {\n let url: string;\n let method = 'GET';\n if (typeof input === 'string') {\n url = input;\n } else if (input instanceof URL) {\n url = input.href;\n } else {\n // Request object: it carries both url and method.\n url = input.url;\n method = input.method;\n }\n if (init?.method) method = init.method;\n return { url, method: method.toUpperCase() };\n}\n\n/** Resolve the path portion of a URL for the self-exclusion check (absolute or\n * relative). Returns the raw input on a parse failure so the guard still works\n * on a bare `/v1/analytics/...` relative path. */\nfunction pathOf(url: string): string {\n try {\n return new URL(url, 'http://_local').pathname;\n } catch {\n return url;\n }\n}\n\n/**\n * Wrap the global `fetch` to record a redacted `network` perf item per app-level\n * request. Returns an `uninstall` that restores the original `fetch`. No-op (and\n * a no-op uninstall) when there is no global `fetch` (some server runtimes).\n */\nexport function installFetchSwizzle(perf: NetworkPerfSink): () => void {\n if (typeof fetch !== 'function') return () => {};\n const original = fetch;\n\n const wrapped = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {\n const { url, method } = describeRequest(input, init);\n const traced = !pathOf(url).startsWith(PERF_EXCLUDED_PREFIX);\n const startedAt = traced ? nowMs() : 0;\n try {\n const res = await original(input, init);\n if (traced) perf.recordNetwork(method, redactUrl(url), res.status, nowMs() - startedAt);\n return res;\n } catch (err) {\n // A transport failure is still a completed-with-error call (status 0),\n // mirroring the `request.ts` trace; rethrow so the caller sees the error.\n if (traced) perf.recordNetwork(method, redactUrl(url), 0, nowMs() - startedAt);\n throw err;\n }\n };\n\n globalThis.fetch = wrapped as typeof fetch;\n return () => {\n globalThis.fetch = original;\n };\n}\n","// A bounded, FIFO-evicting durability buffer for perf items that failed to\n// flush. It mirrors the iOS offline queue (persist-on-fail, drain-on-reconnect):\n// when `flush()` cannot reach `/v1/analytics/perf` (offline / transport error),\n// the facade hands the failed slice here instead of dropping it; the next flush\n// (size/timer, or the `online` reconnect event) drains it oldest-first.\n//\n// Durability: in the browser the queue persists to `localStorage` under a fixed\n// key, so items survive a page reload while the device is offline. On the server\n// (no `localStorage`) it degrades to pure in-memory — the Faz-1 \"no leak into\n// RSC/route handlers\" rule holds (no timers, no I/O beyond the in-memory array).\n//\n// Bounded + counted: a device offline for days must not grow `localStorage`\n// unbounded. The queue caps at `maxItems` (default 500) and FIFO-evicts the\n// OLDEST items on overflow; every eviction increments `dropped` so the loss is\n// observable, never silent corruption.\n//\n// Idempotency: items are stored VERBATIM — their original `row_id` (client\n// UUIDv7) is preserved, never re-minted. A re-delivered item after reconnect\n// therefore dedups server-side (ClickHouse ReplacingMergeTree keyed on row_id).\n\nimport type { PerfItem } from './perf-wire.js';\n\n/** localStorage key for the persisted perf offline queue (one per origin). */\nexport const PERF_QUEUE_KEY = 'palbe.perf.queue';\n\n/** Default item cap — bounds memory/disk for a long-offline device. */\nconst DEFAULT_MAX_ITEMS = 500;\n\n/** True iff this is a browser with a usable `localStorage`. Gated on `document`\n * too (the SAME \"browser\" test the facade uses): a server runtime must be PURE\n * in-memory — no `localStorage` read/write — even if a `localStorage` global is\n * polyfilled/stubbed there (the \"no I/O leak into RSC/route handlers\" rule). The\n * offline queue's durability (reload survival, reconnect drain) only has meaning\n * in the browser anyway. */\nfunction canPersist(): boolean {\n return typeof document !== 'undefined' && typeof localStorage !== 'undefined';\n}\n\n/**\n * Narrow `unknown` (parsed JSON) into a `PerfItem`. Only the wire-required\n * fields are checked; this is a defensive parse of persisted bytes that another\n * tab / a corrupt write may have left — a malformed entry is skipped, never\n * cast. Keeps the queue typed end-to-end with no `any`.\n */\nfunction isPerfItem(value: unknown): value is PerfItem {\n if (typeof value !== 'object' || value === null) return false;\n const o = value as Record<string, unknown>;\n return (\n typeof o.row_id === 'string' &&\n typeof o.trace_type === 'string' &&\n typeof o.name === 'string' &&\n typeof o.value === 'number' &&\n typeof o.timestamp === 'number'\n );\n}\n\n/** Read the persisted items, dropping any malformed entries. Never throws. */\nfunction readPersisted(): PerfItem[] {\n if (!canPersist()) return [];\n try {\n const raw = localStorage.getItem(PERF_QUEUE_KEY);\n if (!raw) return [];\n const parsed: unknown = JSON.parse(raw);\n if (!Array.isArray(parsed)) return [];\n return parsed.filter(isPerfItem);\n } catch {\n // Corrupt JSON / quota read error — degrade to empty, never break the\n // request path.\n return [];\n }\n}\n\n/**\n * Bounded FIFO perf-item queue. `enqueue` appends and evicts the oldest on\n * overflow; `drainAll` returns everything (oldest-first) and clears the store;\n * `count` is the current depth; `dropped` is the cumulative eviction counter.\n *\n * When `localStorage` is present the queue is read-through / write-through to a\n * fixed key, so a fresh instance after a reload sees the persisted items. With\n * no `localStorage` it is purely in-memory.\n */\nexport class PerfOfflineQueue {\n private items: PerfItem[];\n private _dropped = 0;\n private readonly maxItems: number;\n\n constructor(maxItems: number = DEFAULT_MAX_ITEMS) {\n this.maxItems = Math.max(1, maxItems);\n // Hydrate from the persisted store (browser) so durability survives reload.\n this.items = readPersisted();\n // A persisted store larger than the cap (e.g. a smaller cap after an\n // upgrade) is trimmed on construction, counting the eviction.\n this.trim();\n }\n\n /** Append items; FIFO-evict the oldest when over `maxItems`. */\n enqueue(items: PerfItem[]): void {\n if (items.length === 0) return;\n this.items.push(...items);\n this.trim();\n this.persist();\n }\n\n /** Return all queued items (oldest-first) and clear the queue + store. */\n drainAll(): PerfItem[] {\n if (this.items.length === 0) return [];\n const out = this.items;\n this.items = [];\n this.clearStore();\n return out;\n }\n\n /** Current queue depth. */\n get count(): number {\n return this.items.length;\n }\n\n /** Cumulative count of FIFO-evicted items (a `dropped` metric, not silent). */\n get dropped(): number {\n return this._dropped;\n }\n\n /** Drop the oldest items until at most `maxItems` remain, counting each. */\n private trim(): void {\n const overflow = this.items.length - this.maxItems;\n if (overflow > 0) {\n this.items.splice(0, overflow);\n this._dropped += overflow;\n }\n }\n\n private persist(): void {\n if (!canPersist()) return;\n try {\n localStorage.setItem(PERF_QUEUE_KEY, JSON.stringify(this.items));\n } catch {\n // Quota exceeded / SSR — non-fatal: durability degrades to in-memory.\n }\n }\n\n private clearStore(): void {\n if (!canPersist()) return;\n try {\n localStorage.removeItem(PERF_QUEUE_KEY);\n } catch {\n // ignore\n }\n }\n}\n","// Runtime-scoped PalPerf buffer + test-device flag. Kept separate from the\n// facade (PalbePerf) for the same reason AnalyticsState is split out: the\n// buffer/flag identity outlives the lazy facade, and `pb.setTestDevice` /\n// `request.ts`'s network hook need to reach the flag and the buffer without\n// forcing the facade to construct. State is intentionally dumb — no I/O, no\n// timers; the facade owns flush policy.\n\nimport type { PerfItem } from './perf-wire.js';\n\n/** Server hard cap: `POST /v1/analytics/perf` rejects > 100 items per request. */\nexport const MAX_PERF_BATCH = 100;\n\n/**\n * The mutable PalPerf state shared between the facade and the network hook.\n * `testDevice` is signalled to the server via the `X-Palbase-Test-Device: 1`\n * request header (server-authoritative tagging — the SDK only flips the bit).\n */\nexport class PerfState {\n /** Pending, un-flushed perf items (FIFO). */\n readonly buffer: PerfItem[] = [];\n /** When true, every flush carries `X-Palbase-Test-Device: 1`. */\n testDevice = false;\n\n enqueue(item: PerfItem): void {\n this.buffer.push(item);\n }\n\n /** Detach up to `MAX_PERF_BATCH` items for one POST (FIFO order preserved). */\n take(limit: number = MAX_PERF_BATCH): PerfItem[] {\n return this.buffer.splice(0, limit);\n }\n\n get size(): number {\n return this.buffer.length;\n }\n}\n","// Wire model for the PalPerf ingest contract (§8.3/§8.6). The live backend\n// (`POST /v1/analytics/perf`, modules/analytics) decodes each item with\n// snake_case json tags — `trace_type`, `request_id`, `row_id`. These property\n// names ARE the wire format: a camelCase key would silently zero-value\n// server-side. distinct_id/app_id/geo and Environment identity are\n// server-derived, so the client MUST NOT send them — they are absent from\n// `PerfItem` by design.\n\n/** The Faz-1 trace kinds plus the Faz-1.5 web-vital family. */\nexport type PerfTraceType = 'network' | 'app_start' | 'custom' | 'web_vital';\n\n/**\n * One perf measurement, shaped EXACTLY as the ingest wire expects. Keys are\n * snake_case to match the Go decode tags — do not rename to camelCase.\n */\nexport interface PerfItem {\n /** Client-generated UUIDv7 — server dedup key. */\n row_id: string;\n trace_type: PerfTraceType;\n name: string;\n /** Duration in milliseconds. */\n value: number;\n counters?: Record<string, number>;\n attrs?: Record<string, string>;\n request_id?: string;\n /** Unix milliseconds. */\n timestamp: number;\n}\n\n/** The `POST /v1/analytics/perf` request body shape (≤100 / ≤512 KB per call). */\nexport interface PerfBatch {\n items: PerfItem[];\n}\n\n/** Wraps items in the ingest request-body envelope. */\nexport function encodePerfBatch(items: PerfItem[]): PerfBatch {\n return { items };\n}\n","// `pb.perf` — the stateful PalPerf surface. Mirrors `analytics-facade.ts`\n// (same buffer + size/timer flush in the browser, immediate flush on the\n// server where there is no `document`) and the iOS `pb.perf.*` API\n// (startTrace/putAttribute/incrementMetric/stop). Transport is the SAME seam\n// as analytics event send: `palbeRequest(rt, 'POST', '/v1/analytics/perf', …)`,\n// which wraps `rt.http.request` and unwraps the `{ data, error }` envelope.\n//\n// The live ingest contract (proven E2E against modules/analytics):\n//\n// POST /v1/analytics/perf {\"items\":[PerfItem…]} → 202 {accepted,dropped}\n//\n// distinct_id / app_id / geo and Environment identity are server-derived — the\n// SDK sends raw measurements only (§8.3/§8.6). Test traffic is signalled with the\n// `X-Palbase-Test-Device: 1` header, which the server reads to tag the rows;\n// the SDK only flips the bit via `setTestDevice`.\n//\n// Ingestion is fire-and-forget: `flush()` is the only awaited surface and it\n// NEVER rejects — a failed slice is dropped with one warn per flush. `record`\n// and `recordNetwork` never throw into the caller.\n\nimport { palbeRequest } from '../request.js';\nimport type { PalbeRuntime } from '../runtime.js';\nimport { installFetchSwizzle } from './fetch-swizzle.js';\nimport { PerfOfflineQueue } from './offline-queue.js';\nimport { perfSampleBucket } from './perf-config-client.js';\nimport { MAX_PERF_BATCH, PerfState } from './perf-state.js';\nimport { encodePerfBatch, type PerfItem, type PerfTraceType } from './perf-wire.js';\n\nconst FLUSH_AT = 20;\nconst FLUSH_INTERVAL_MS = 10_000;\nconst TEST_DEVICE_HEADER = 'X-Palbase-Test-Device';\n\n/** Monotonic millisecond clock — `performance.now()` when present (browser and\n * modern Node), else `Date.now()`. Used to time custom traces; the wall clock\n * is only used for the wire `timestamp`. */\nfunction nowMs(): number {\n return typeof performance !== 'undefined' && typeof performance.now === 'function'\n ? performance.now()\n : Date.now();\n}\n\n/**\n * A running custom trace. `putAttribute`/`incrementMetric` accumulate metadata;\n * `stop()` records ONE `custom` PerfItem whose `value` is the elapsed\n * milliseconds since `startTrace`. Idempotent: a second `stop()` is a no-op.\n */\nexport class PerfTrace {\n private readonly attrs: Record<string, string> = {};\n private readonly counters: Record<string, number> = {};\n private readonly startedAt = nowMs();\n private stopped = false;\n\n constructor(\n private readonly name: string,\n private readonly onStop: (item: PerfItem) => void,\n ) {}\n\n putAttribute(key: string, value: string): void {\n this.attrs[key] = value;\n }\n\n incrementMetric(name: string, by = 1): void {\n this.counters[name] = (this.counters[name] ?? 0) + by;\n }\n\n stop(): void {\n if (this.stopped) return;\n this.stopped = true;\n const item: PerfItem = {\n row_id: crypto.randomUUID(),\n trace_type: 'custom',\n name: this.name,\n value: Math.max(0, nowMs() - this.startedAt),\n timestamp: Date.now(),\n };\n if (Object.keys(this.attrs).length > 0) item.attrs = this.attrs;\n if (Object.keys(this.counters).length > 0) item.counters = this.counters;\n this.onStop(item);\n }\n}\n\nexport class PalbePerf {\n private flushTimer: ReturnType<typeof setTimeout> | null = null;\n /** Browser → buffer + size/timer flush. Server (no document) → immediate\n * per-item flush, zero timers (nothing leaks into RSC/route handlers). */\n private readonly browser = typeof document !== 'undefined';\n /** Bound `online` handler so it can be removed on `dispose()` (no leak). */\n private readonly onOnline = (): void => {\n void this.flush();\n };\n /** Server-controlled client-side sample rate (0..100). 100 until the config\n * client fetches `/v1/analytics/perf/config` — the SDK obeys this ceiling and\n * never raises its own rate (invariant 3). `record` drops an item whose\n * deterministic `row_id` bucket is >= this pct, mirroring the server's\n * `SampleDecision` so client + server keep the SAME rows. */\n private samplePct = 100;\n\n constructor(\n private readonly rt: PalbeRuntime,\n private readonly state: PerfState = new PerfState(),\n /** Durable offline buffer for failed flushes (persist-on-fail). Browser →\n * localStorage-backed; server → in-memory. */\n private readonly queue: PerfOfflineQueue = new PerfOfflineQueue(),\n ) {\n // Drain-on-reconnect: when the browser comes back online, flush — this ships\n // whatever the offline queue persisted while the network was down. Server\n // (no `window`) wires nothing (the no-timers/no-listeners rule holds).\n if (typeof window !== 'undefined' && typeof window.addEventListener === 'function') {\n window.addEventListener('online', this.onOnline);\n }\n }\n\n /** Remove the `online` listener. Called when the runtime is replaced so the\n * handler does not outlive this facade. No-op outside the browser. */\n dispose(): void {\n if (typeof window !== 'undefined' && typeof window.removeEventListener === 'function') {\n window.removeEventListener('online', this.onOnline);\n }\n }\n\n /** Mark (or unmark) this client's traffic as test — the server tags the rows\n * when the `X-Palbase-Test-Device: 1` header rides along on flush. */\n setTestDevice(on: boolean): void {\n this.state.testDevice = on;\n }\n\n /** Apply the server-resolved client-side sample rate (0..100), clamped. Called\n * by `PerfConfigClient` after fetching `/v1/analytics/perf/config`. The SDK\n * OBEYS this value — it is a ceiling, never raised locally. */\n setSamplePct(pct: number): void {\n this.samplePct = Math.max(0, Math.min(100, Math.trunc(pct)));\n }\n\n /** Pending (un-flushed) buffer depth. Test-only visibility into the buffer so\n * the remote-sampling tests can assert how many items were sampled in. */\n get bufferSizeForTest(): number {\n return this.state.size;\n }\n\n /** Opt-in: wrap the global `fetch` so every app-level request is recorded as a\n * redacted `network` perf item (the `/v1/analytics/*` ingest paths are\n * self-excluded). OFF by default — swizzling a global is a page-wide side\n * effect. Returns an `uninstall` that restores the original `fetch`. */\n enableFetchCapture(): () => void {\n return installFetchSwizzle(this);\n }\n\n /** Start a custom trace; the returned handle records a `custom` item on\n * `.stop()`. */\n startTrace(name: string): PerfTrace {\n return new PerfTrace(name, (item) => this.record(item));\n }\n\n /** Buffer one perf item. In the browser, flush on size/timer; on the server\n * flush immediately (no timers). Never throws.\n *\n * Client-side remote sampling: an item whose deterministic `row_id` bucket is\n * NOT below the server-controlled `samplePct` is dropped before buffering —\n * the SAME FNV-1a-mod-100 decision the server applies at ingest, so the two\n * keep identical rows and the SDK saves the upload (defense-in-depth: the\n * server re-samples authoritatively). */\n record(item: PerfItem): void {\n if (perfSampleBucket(item.row_id) >= this.samplePct) return;\n this.state.enqueue(item);\n if (!this.browser) {\n void this.flush();\n return;\n }\n if (this.state.size >= FLUSH_AT) void this.flush();\n else this.startTimer();\n }\n\n /** Buffer a network trace — called by `request.ts` around `rt.http.request`\n * (the analytics ingest path is excluded by the caller to avoid recursion). */\n recordNetwork(\n method: string,\n url: string,\n status: number,\n durationMs: number,\n requestId?: string,\n ): void {\n const item: PerfItem = {\n row_id: crypto.randomUUID(),\n trace_type: 'network' satisfies PerfTraceType,\n name: `${method} ${url}`,\n value: durationMs,\n attrs: { status: String(status) },\n timestamp: Date.now(),\n };\n if (requestId) item.request_id = requestId;\n this.record(item);\n }\n\n /** Drain the offline queue (oldest-first) and the live buffer to\n * `/v1/analytics/perf` (≤100 items per request, sequential). Resolves when\n * delivery finished; NEVER rejects. A failed slice is NOT dropped — it goes\n * to the offline queue (persist-on-fail) so the next flush (size/timer or the\n * `online` reconnect event) retries it. Items keep their original `row_id`,\n * so a redelivery dedups server-side (ReplacingMergeTree). */\n async flush(): Promise<void> {\n this.cancelTimer();\n // Previously-failed items drain FIRST (oldest-first), prepended ahead of the\n // current buffer so the queue cannot starve.\n const pending = this.queue.drainAll();\n if (pending.length === 0 && this.state.size === 0) return;\n const headers = this.state.testDevice ? { [TEST_DEVICE_HEADER]: '1' } : undefined;\n const remaining = [...pending];\n while (this.state.size > 0) remaining.push(...this.state.take(MAX_PERF_BATCH));\n\n for (let i = 0; i < remaining.length; i += MAX_PERF_BATCH) {\n const slice = remaining.slice(i, i + MAX_PERF_BATCH);\n try {\n await palbeRequest<{ accepted?: number; dropped?: number }>(\n this.rt,\n 'POST',\n '/v1/analytics/perf',\n { body: encodePerfBatch(slice), headers },\n );\n } catch {\n // Offline / transport error: persist this slice for a later drain\n // instead of dropping it. Re-enqueueing a drained slice is safe — the\n // bounded queue caps growth and the row_id dedups any redelivery.\n this.queue.enqueue(slice);\n }\n }\n }\n\n private startTimer(): void {\n if (this.flushTimer !== null) return;\n this.flushTimer = setTimeout(() => {\n this.flushTimer = null;\n void this.flush();\n }, FLUSH_INTERVAL_MS);\n }\n\n private cancelTimer(): void {\n if (this.flushTimer !== null) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n }\n}\n","// Web Vitals capture (PalPerf Faz 1.5). Each Core Web Vital is ONE `web_vital`\n// perf row: name = the vital (LCP/CLS/INP/FCP/TTFB), value = the metric. They\n// ride the SAME ingest + `perf_agg_daily_mv` percentile rollup as every other\n// perf family — no new wire shape, no new CH table. distinct_id/app_id/geo are\n// server-derived (§8.6); the SDK sends raw measurements only.\n//\n// Strict TS, no `any`/cast: PerformanceObserver entry types are narrowed via\n// typed predicates, never coerced. We avoid the `web-vitals` npm dep (the SDK\n// ships zero runtime deps for perf) and read the raw PerformanceObserver\n// entries directly — CLS is summed across `layout-shift` entries without recent\n// input, LCP/INP take the latest, FCP/TTFB are one-shot.\n\nimport type { PerfItem } from './perf-wire.js';\n\n/** The Core Web Vitals we capture. FID is intentionally omitted — it is\n * deprecated in favour of INP. Closed union → exhaustive, no `any`. */\nexport type WebVitalName = 'LCP' | 'CLS' | 'INP' | 'FCP' | 'TTFB';\n\n/** Build a `web_vital` PerfItem. `value` is the metric (ms for timing vitals,\n * a unitless score ×1000-free for CLS). Negative values clamp to 0. PURE. */\nexport function webVitalItem(name: WebVitalName, value: number): PerfItem {\n return {\n row_id: crypto.randomUUID(),\n trace_type: 'web_vital',\n name,\n value: Math.max(0, value),\n timestamp: Date.now(),\n };\n}\n\n// --- typed PerformanceObserver entry narrowing (no `any`) -------------------\n\ninterface LayoutShiftEntry extends PerformanceEntry {\n value: number;\n hadRecentInput: boolean;\n}\nfunction isLayoutShift(e: PerformanceEntry): e is LayoutShiftEntry {\n return e.entryType === 'layout-shift' && 'value' in e && 'hadRecentInput' in e;\n}\n\ninterface EventTimingEntry extends PerformanceEntry {\n duration: number;\n interactionId?: number;\n}\nfunction isEventTiming(e: PerformanceEntry): e is EventTimingEntry {\n return e.entryType === 'event' || e.entryType === 'first-input';\n}\n\n/** Safely create + start a PerformanceObserver for one entry type, swallowing\n * unsupported-type errors (Safari lacks `layout-shift`/`event`). Returns the\n * observer or null. */\nfunction safeObserve(\n type: string,\n cb: (entries: PerformanceEntryList) => void,\n): PerformanceObserver | null {\n if (typeof PerformanceObserver === 'undefined') return null;\n try {\n const obs = new PerformanceObserver((list) => cb(list.getEntries()));\n obs.observe({ type, buffered: true } as PerformanceObserverInit);\n return obs;\n } catch {\n return null;\n }\n}\n\n/**\n * Wire the Core Web Vitals observers, recording one `web_vital` PerfItem per\n * settled vital. Returns a disconnect fn (call on teardown). No-op outside the\n * browser. LCP/INP record the LATEST observed value on disconnect/pagehide; CLS\n * sums layout shifts without recent input; FCP/TTFB are one-shot. Best-effort —\n * a missing observer type is silently skipped, never thrown.\n */\nexport function observeWebVitals(record: (item: PerfItem) => void): () => void {\n // No-op outside a real browser. We require the exact DOM/timing methods we\n // call below — a partial/non-standard `document` (e.g. an SSR shim, or a test\n // fake that only sets `window`) must NOT crash runtime construction. This is\n // the same defensiveness `recordColdStart` applies.\n if (\n typeof document === 'undefined' ||\n typeof document.addEventListener !== 'function' ||\n typeof document.removeEventListener !== 'function' ||\n typeof performance === 'undefined' ||\n typeof performance.getEntriesByType !== 'function'\n ) {\n return () => {};\n }\n\n let cls = 0;\n let lcp = 0;\n let inp = 0;\n\n const observers: Array<PerformanceObserver | null> = [\n // LCP: keep the largest/last reported render.\n safeObserve('largest-contentful-paint', (entries) => {\n for (const e of entries) lcp = Math.max(lcp, e.startTime);\n }),\n // CLS: sum shift values that weren't caused by recent input.\n safeObserve('layout-shift', (entries) => {\n for (const e of entries) if (isLayoutShift(e) && !e.hadRecentInput) cls += e.value;\n }),\n // INP: approximate as the worst interaction duration observed.\n safeObserve('event', (entries) => {\n for (const e of entries) if (isEventTiming(e)) inp = Math.max(inp, e.duration);\n }),\n // FCP: one-shot.\n safeObserve('paint', (entries) => {\n for (const e of entries)\n if (e.name === 'first-contentful-paint') record(webVitalItem('FCP', e.startTime));\n }),\n ];\n\n // TTFB from the navigation entry (one-shot, available immediately).\n const nav = performance.getEntriesByType('navigation')[0] as\n | PerformanceNavigationTiming\n | undefined;\n if (nav && nav.responseStart > 0) record(webVitalItem('TTFB', nav.responseStart));\n\n // Flush the running aggregates (LCP/CLS/INP) once, when the page is hidden or\n // torn down — that's when the values have settled.\n let flushed = false;\n const flush = (): void => {\n if (flushed) return;\n flushed = true;\n if (lcp > 0) record(webVitalItem('LCP', lcp));\n record(webVitalItem('CLS', cls));\n if (inp > 0) record(webVitalItem('INP', inp));\n };\n const onHide = (): void => {\n if (document.visibilityState === 'hidden') flush();\n };\n document.addEventListener('visibilitychange', onHide);\n\n return () => {\n flush();\n document.removeEventListener('visibilitychange', onHide);\n for (const o of observers) o?.disconnect();\n };\n}\n","const APP_METADATA_HEADER = 'X-Palbase-Bundle';\n\n/**\n * Stamp the live browser origin metadata on an app-key request. Final headers\n * are inspected case-insensitively so an explicit caller/config value wins.\n * The authenticated app identity is the app-bound API key plus its app_id;\n * this header is diagnostic metadata, not an authorization proof. Server\n * runtimes have no browser origin and intentionally add nothing.\n */\nexport function applyBrowserOriginHeader(headers: Record<string, string>): void {\n if (Object.keys(headers).some((key) => key.toLowerCase() === 'x-palbase-bundle')) return;\n const origin =\n typeof window !== 'undefined' && typeof window.location !== 'undefined'\n ? window.location.origin\n : '';\n if (origin !== '') headers[APP_METADATA_HEADER] = origin;\n}\n","// Anonymous-token minting for the realtime WebSocket — a port of the iOS\n// `AnonTokenProvider` actor (palbackend-ios-src Sources/Palbe/Realtime/\n// AnonTokenProvider.swift).\n//\n// Why this exists: Realtime validates every connection's token against the\n// tenant's JWKS (palauth's ES256 signing key). A signed-in user already has a\n// palauth-issued Bearer that validates there. An anonymous caller only holds\n// the Environment's publishable API key (`pb_<environmentRef>_c…`) — NOT a JWT, so Realtime\n// rejects it with MalformedJWT. `POST /auth/anonymous` mints the missing\n// piece: a role=anon ES256 JWT signed by the same key Realtime trusts.\n//\n// Deliberately SEPARATE from TokenManager (which owns the user-session\n// lifecycle and auth-state events): an anonymous token has no user, no\n// refresh token, and emits no auth events. The provider memoises one token\n// and re-mints it shortly before expiry.\n\nimport { BackendError, fromEnvelope } from '../errors.js';\nimport type { PalbeRuntime } from '../runtime.js';\nimport { applyBrowserOriginHeader } from '../runtime-metadata.js';\n\ninterface AnonToken {\n accessToken: string;\n expiresAt: number; // epoch ms\n}\n\n/** Re-mint this long before real expiry so a long-lived socket never presents\n * an expired token on reconnect/join (iOS refreshSkew = 60s). */\nconst REFRESH_SKEW_MS = 60_000;\n\nexport class AnonTokenProvider {\n private readonly rt: PalbeRuntime;\n private cached: AnonToken | null = null;\n private inFlight: Promise<string> | null = null;\n\n constructor(rt: PalbeRuntime) {\n this.rt = rt;\n }\n\n /**\n * Return a valid anonymous access token, minting (or re-minting) one when\n * the cache is empty or within the skew of expiry. Concurrent callers\n * collapse onto a single in-flight mint.\n */\n token(): Promise<string> {\n if (this.cached && Date.now() < this.cached.expiresAt - REFRESH_SKEW_MS) {\n return Promise.resolve(this.cached.accessToken);\n }\n if (this.inFlight) return this.inFlight;\n const mint = this.mint().then(\n (token) => {\n this.cached = token;\n this.inFlight = null;\n return token.accessToken;\n },\n (e: unknown) => {\n this.inFlight = null; // a failed mint must not poison future attempts\n throw e;\n },\n );\n this.inFlight = mint;\n return mint;\n }\n\n /**\n * Raw fetch, deliberately NOT through HttpClient/palbeRequest: the mint\n * must carry the Environment API key and NEVER a Bearer (iOS keeps\n * `/auth/anonymous` on the unauthenticated-path list), and HttpClient's\n * pre-flight would try to refresh a stale session before an anon mint —\n * exactly the entanglement this provider exists to avoid.\n */\n private async mint(): Promise<AnonToken> {\n const base = this.rt.config.url.replace(/\\/+$/, '');\n const headers: Record<string, string> = {\n apikey: this.rt.config.apiKey,\n ...this.rt.config.headers,\n };\n applyBrowserOriginHeader(headers);\n let response: Response;\n try {\n response = await fetch(`${base}/auth/anonymous`, {\n method: 'POST',\n headers,\n });\n } catch (e) {\n throw new BackendError('network', {\n code: 'network_error',\n message: e instanceof Error ? e.message : 'Network request failed',\n });\n }\n const body: unknown = await response.json().catch(() => null);\n if (!response.ok) throw fromEnvelope(response.status, body);\n // Wire shape: { access_token, token_type: \"Bearer\", expires_in: seconds }.\n const obj = (typeof body === 'object' && body !== null ? body : {}) as Record<string, unknown>;\n const accessToken = obj.access_token;\n const expiresIn = obj.expires_in;\n if (typeof accessToken !== 'string' || accessToken === '' || typeof expiresIn !== 'number') {\n throw new BackendError('decode', {\n code: 'decode_error',\n message:\n 'POST /auth/anonymous returned an unexpected shape (expected {access_token, expires_in}).',\n });\n }\n return { accessToken, expiresAt: Date.now() + expiresIn * 1000 };\n }\n}\n\n/**\n * The realtime connection's token closure (iOS PalBackend.swift parity):\n * 1. the signed-in user Bearer when present and unexpired (user-override\n * flag topics + private channels authorize);\n * 2. else an anonymous palauth JWT (role=anon);\n * 3. else — ONLY when the mint fails — the bare Environment API key. Realtime\n * would reject it, but it keeps the closure total: the connect proceeds,\n * fails, and the backoff loop retries the mint on the next attempt.\n */\nexport function realtimeTokenProvider(\n rt: PalbeRuntime,\n anon: AnonTokenProvider,\n): () => Promise<string> {\n return async () => {\n const bearer = rt.tokenManager.getAccessToken();\n if (bearer && !rt.tokenManager.isExpired()) return bearer;\n try {\n return await anon.token();\n } catch {\n return rt.config.apiKey;\n }\n };\n}\n","// Phoenix Channels v2 frame codec — the realtime wire format.\n//\n// Every shape here is byte-locked to the iOS encoder (palbackend-ios-src\n// Sources/Palbe/Realtime/RealtimeConnection.swift), the proven-live ground\n// truth against the platform's Supabase Realtime (Elixir/Phoenix):\n//\n// join: [join_ref, ref, \"realtime:<topic>\", \"phx_join\", {access_token, config}]\n// reply: [join_ref, ref, \"realtime:<topic>\", \"phx_reply\", {status, response}]\n// heartbeat: [null, ref, \"phoenix\", \"heartbeat\", {}]\n// leave: [null, ref, \"realtime:<topic>\", \"phx_leave\", {}]\n// broadcast: [join_ref, ref, \"realtime:<topic>\", \"broadcast\", {type, event, payload}]\n//\n// Refs are stringified monotonic ints. Topics are bare sub-topics in the API\n// (\"room:42\"); the codec adds the \"realtime:\" prefix on the way out and strips\n// it on the way in. Goldens: __tests__/realtime-frames.test.ts.\n\nexport const REALTIME_PREFIX = 'realtime:';\n\n/** A decoded Phoenix v2 frame. Only topic/event/payload matter for routing. */\nexport interface PhoenixFrame {\n joinRef: unknown;\n ref: unknown;\n topic: string;\n event: string;\n payload: unknown;\n}\n\n/** An inbound broadcast routed to channel handlers: bare topic + inner event/payload. */\nexport interface RealtimeInbound {\n topic: string;\n event: string;\n payload: Record<string, unknown>;\n}\n\n/**\n * Join a topic. join_ref = ref (iOS sends the same ref in both slots). The\n * config mirrors iOS exactly: broadcast self=false, no presence key, public\n * channel, no postgres_changes.\n */\nexport function encodeJoin(ref: number, topic: string, accessToken: string): string {\n const r = String(ref);\n return JSON.stringify([\n r,\n r,\n REALTIME_PREFIX + topic,\n 'phx_join',\n {\n access_token: accessToken,\n config: {\n broadcast: { self: false },\n presence: { key: '' },\n private: false,\n postgres_changes: [],\n },\n },\n ]);\n}\n\nexport function encodeLeave(ref: number, topic: string): string {\n return JSON.stringify([null, String(ref), REALTIME_PREFIX + topic, 'phx_leave', {}]);\n}\n\nexport function encodeHeartbeat(ref: number): string {\n return JSON.stringify([null, String(ref), 'phoenix', 'heartbeat', {}]);\n}\n\n/**\n * Client-side broadcast push (web addition — iOS is receive-only). The payload\n * envelope {type:\"broadcast\", event, payload} matches what the server fans out\n * to other subscribers (Realtime's BroadcastHandler re-broadcasts it verbatim),\n * so receivers unwrap pushes from peers and from the backend identically.\n * joinRef is the topic's CURRENT join ref — Phoenix drops channel messages\n * whose join_ref does not match the live channel instance.\n */\nexport function encodeBroadcast(\n joinRef: string,\n ref: number,\n topic: string,\n event: string,\n payload: Record<string, unknown>,\n): string {\n return JSON.stringify([\n joinRef,\n String(ref),\n REALTIME_PREFIX + topic,\n 'broadcast',\n { type: 'broadcast', event, payload },\n ]);\n}\n\n/** Decode a Phoenix v2 frame; null when the text is not a valid 5-slot array. */\nexport function decodeFrame(text: string): PhoenixFrame | null {\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n return null;\n }\n if (!Array.isArray(parsed)) return null;\n const [joinRef, ref, topic, event, payload] = parsed as unknown[];\n if (typeof topic !== 'string' || typeof event !== 'string') return null;\n return { joinRef, ref, topic, event, payload };\n}\n\nfunction asObject(value: unknown): Record<string, unknown> | null {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return null;\n return value as Record<string, unknown>;\n}\n\n/**\n * Route an inbound frame: only `broadcast` events carry channel traffic\n * (replies/acks/presence are connection bookkeeping — iOS ignores them the\n * same way; reconnect re-joins every topic, so socket health drives recovery).\n * Missing inner event defaults to '' and a non-object payload to {} — exact\n * iOS decoder behavior.\n */\nexport function unwrapBroadcast(frame: PhoenixFrame): RealtimeInbound | null {\n if (frame.event !== 'broadcast') return null;\n const body = asObject(frame.payload);\n if (!body) return null;\n const event = typeof body.event === 'string' ? body.event : '';\n const payload = asObject(body.payload) ?? {};\n return { topic: stripRealtimePrefix(frame.topic), event, payload };\n}\n\nexport function stripRealtimePrefix(topic: string): string {\n return topic.startsWith(REALTIME_PREFIX) ? topic.slice(REALTIME_PREFIX.length) : topic;\n}\n\n// ── the state vocabulary ─────────────────────────────────────────────────────\n//\n// New event names on the SAME 5-slot envelope, byte-locked to the server's\n// goldens (internal/modules/rt/frames_state_test.go). A client that speaks only\n// broadcast is unaffected.\n\n/** Write one entry on a channel's shared state. */\nexport function encodeStateSet(\n joinRef: string,\n ref: number,\n topic: string,\n key: string,\n value: unknown,\n life: 'ephemeral' | 'durable',\n): string {\n return JSON.stringify([\n joinRef,\n String(ref),\n REALTIME_PREFIX + topic,\n 'state_set',\n { key, value, life },\n ]);\n}\n\n/** Remove one entry. The server refuses a key this connection does not own. */\nexport function encodeStateDel(joinRef: string, ref: number, topic: string, key: string): string {\n return JSON.stringify([joinRef, String(ref), REALTIME_PREFIX + topic, 'state_del', { key }]);\n}\n\n/** Ask for a fresh snapshot — the answer to a detected gap. */\nexport function encodeStateResync(joinRef: string, ref: number, topic: string): string {\n return JSON.stringify([joinRef, String(ref), REALTIME_PREFIX + topic, 'state_resync', {}]);\n}\n\n/** Present a fresher credential for the SAME principal without rejoining.\n * A rejoin would reset presence and force a full resync; this does not. */\nexport function encodeAccessToken(joinRef: string, ref: number, topic: string, token: string): string {\n return JSON.stringify([\n joinRef,\n String(ref),\n REALTIME_PREFIX + topic,\n 'access_token',\n { access_token: token },\n ]);\n}\n","// The Phoenix-Channels WebSocket transport for Palbase Realtime — a port of\n// the proven-live iOS `RealtimeConnection` actor (palbackend-ios-src\n// Sources/Palbe/Realtime/RealtimeConnection.swift), the wire ground truth\n// against the platform's Supabase Realtime (Elixir/Phoenix):\n//\n// connect: wss://<host>/realtime/v1/websocket?apikey=<jwt>&vsn=2.0.0\n// join: [join_ref, ref, \"realtime:<topic>\", \"phx_join\", {access_token, config}]\n// heartbeat: [null, ref, \"phoenix\", \"heartbeat\", {}] (~every 25s)\n// recv: [null, null, \"realtime:<topic>\", \"broadcast\", {type, event, payload}]\n//\n// ONE socket is shared by every subscribed channel (the Supabase model —\n// multiplex all topics over a single connection). This class is a generic\n// frame pump; the facade (facade.ts) owns per-channel handlers and refcounts.\n//\n// DEVIATION from iOS (documented): iOS sends the auth token in the\n// `x-api-key` HTTP header on the WS upgrade. Browsers cannot set headers on\n// a WebSocket handshake, so the web client uses the server's query fallback —\n// Realtime's user_socket.ex reads `x-api-key` first, then `params[\"apikey\"]`.\n//\n// Resilience mirrors iOS: a dropped socket reconnects with exponential\n// backoff (1,2,4…s capped at 30s + deterministic ref jitter); on reconnect\n// every previously-joined topic is re-joined with a FRESH token and fresh\n// refs. The 25s heartbeat keeps Kong's idle WS timeout (180s) from closing\n// the connection. One behavioral adaptation: iOS promotes to `connected` on\n// the first RECEIVED frame because `URLSessionWebSocketTask.resume()` does\n// not signal handshake completion — a browser WebSocket's `open` event fires\n// only after a successful HTTP-101 handshake, so promotion happens there.\n\nimport {\n decodeFrame,\n encodeAccessToken,\n encodeBroadcast,\n encodeHeartbeat,\n encodeJoin,\n encodeLeave,\n encodeStateDel,\n encodeStateResync,\n encodeStateSet,\n type RealtimeInbound,\n stripRealtimePrefix,\n unwrapBroadcast,\n} from './frames.js';\nimport type { DiffBody, SnapshotBody } from './state.js';\n\n/**\n * The connection state of the shared realtime WebSocket (iOS enum parity), plus\n * `'error'` — surfaced when a channel could not be recovered after a\n * token-expiry close exhausted its rejoin attempts (the socket itself may still\n * be up; the app should stop expecting events on the dead channel).\n */\nexport type RealtimeConnectionState = 'idle' | 'connected' | 'reconnecting' | 'error';\n\n/** Connection-level callbacks the owner (the realtime facade) installs once. */\nexport interface RealtimeSocketHandlers {\n /** A broadcast arrived for a topic (bare sub-topic, inner event/payload). */\n onInbound: (inbound: RealtimeInbound) => void;\n /** The socket (re)connected — fires on EVERY successful open, first included. */\n onReconnect: () => void;\n /** The socket's connection state changed (idle/connected/reconnecting). */\n onStateChange: (state: RealtimeConnectionState) => void;\n /** A channel's whole state arrived — on join, and in answer to a resync. */\n onStateSnapshot: (topic: string, body: SnapshotBody) => void;\n /** A change to a channel's state. The owner applies it and, when it reports a\n * gap, asks for a snapshot. */\n onStateDiff: (topic: string, body: DiffBody) => void;\n /** The join was acked, carrying who this connection is ON THAT CHANNEL.\n * Presence is keyed by it, so nothing can be written until it is known. */\n onJoinInfo: (topic: string, info: { conn?: string; occupancy?: number; state_seq?: number }) => void;\n /**\n * A single channel died for good (token-expiry rejoin exhausted N attempts).\n * The socket is still up; the owner surfaces this on the channel's status so\n * the app stops expecting events on that topic. Distinct from onStateChange,\n * which is connection-global — one bad channel must not flag the whole socket.\n */\n onChannelError: (topic: string) => void;\n /**\n * The server REFUSED a join, and said why.\n *\n * Distinct from onChannelError, which reports a channel that died after\n * having worked. This one never started: `unauthorized` is the authorization\n * model answering \"you may not\", `unavailable` is it answering \"I could not\n * ask\". The owner has to keep those apart, because they demand opposite\n * things of the caller — one says stop, the other says try again.\n */\n onChannelRefused: (topic: string, reason: string) => void;\n}\n\n/**\n * Refusals that asking again can still answer differently.\n *\n * `unavailable` is the project not being reachable to decide. The two ceilings\n * belong here too and did not: they are sent on the JOIN path, so they carry the\n * join's ref and pass the correlation gate as channel refusals — and being\n * treated as FINAL meant a client caught in a reconnect storm dropped the topic\n * from its rejoin set and never came back to it. The server's own ratelimit\n * comment is explicit that these two are \"slow down\" and \"you are holding too\n * much\", which are the two things a client can act on and recover from.\n */\nconst RETRYABLE_REFUSALS = new Set(['unavailable', 'rate_limited', 'too_many_channels']);\n\n/** The structural slice of the DOM WebSocket the transport needs (injectable). */\nexport interface WebSocketLike {\n readonly readyState: number;\n send(data: string): void;\n close(code?: number): void;\n onopen: (() => void) | null;\n onmessage: ((event: { data: unknown }) => void) | null;\n onclose: (() => void) | null;\n onerror: (() => void) | null;\n}\n\nexport type WebSocketCtor = new (url: string) => WebSocketLike;\n\nexport interface RealtimeSocketOptions {\n /** https base, e.g. https://ref.dev.palbase.studio (PalbeConfig.url). */\n baseUrl: string;\n /**\n * Resolves the token used as the WS `apikey` query + per-join `access_token`.\n * Bearer when signed in, else anon JWT, else the Environment API key (see\n * anon-token.ts). Re-invoked on every connect AND every join, so a token\n * rotated while offline is picked up by the next reconnect.\n */\n tokenProvider: () => Promise<string>;\n /** Injectable for non-DOM hosts; defaults to globalThis.WebSocket, resolved per connect. */\n webSocket?: WebSocketCtor;\n /** Heartbeat cadence; default 25s (under Kong's 180s idle WS cap). */\n heartbeatIntervalMs?: number;\n /** Reconnect backoff cap; default 30s. */\n maxBackoffSeconds?: number;\n}\n\nconst WS_OPEN = 1;\nconst DEFAULT_HEARTBEAT_MS = 25_000;\nconst DEFAULT_MAX_BACKOFF_SECONDS = 30;\n// How many times a single channel may be rejoined after a token-expiry close\n// before we give up on it (the rejoin-storm guard). A genuinely-bad token (not\n// just expired) would close every rejoin immediately; capping turns that into a\n// surfaced channel error instead of a tight loop. The first rejoin is attempt 0.\nconst MAX_CHANNEL_REJOIN_ATTEMPTS = 3;\n\n/**\n * Build the Phoenix WS URL: https→wss (http→ws), trailing slashes stripped,\n * Kong's `/realtime/v1/websocket` path, `apikey` + `vsn=2.0.0` query.\n * Port of iOS `websocketURL` — plus the browser apikey-query deviation\n * documented in the file header.\n */\nexport function websocketUrl(base: string, token: string): string {\n let s = base;\n if (s.startsWith('https://')) {\n s = `wss://${s.slice('https://'.length)}`;\n } else if (s.startsWith('http://')) {\n s = `ws://${s.slice('http://'.length)}`;\n }\n while (s.endsWith('/')) s = s.slice(0, -1);\n return `${s}/realtime/v1/websocket?apikey=${encodeURIComponent(token)}&vsn=2.0.0`;\n}\n\n/**\n * Exponential reconnect backoff: 2^attempt seconds capped at `cap`, plus up\n * to 1s of deterministic jitter derived from the ref counter (iOS formula —\n * nondeterministic RNG is banned, and ref-based jitter is enough to de-sync\n * reconnect storms). Pure so growth/cap are unit-testable without sleeping.\n */\nexport function backoffDelaySeconds(attempt: number, refCounter: number, cap: number): number {\n const base = Math.min(2 ** attempt, cap);\n const jitter = (refCounter % 1000) / 1000;\n return base + jitter;\n}\n\ninterface PendingBroadcast {\n topic: string;\n event: string;\n payload: Record<string, unknown>;\n}\n\nexport class RealtimeSocket {\n private readonly options: RealtimeSocketOptions;\n private handlers: RealtimeSocketHandlers | null = null;\n\n private ws: WebSocketLike | null = null;\n // Membership intent (bare sub-topics): re-joined on every (re)connect. A\n // topic stays in this set from joinTopic() until leaveTopic().\n private readonly joinedTopics = new Set<string>();\n // topic → join_ref of the LIVE join on the current socket. Phoenix drops\n // channel messages whose join_ref doesn't match, so broadcasts carry this.\n // Cleared on disconnect (the next socket gets fresh joins/refs).\n private readonly joinRefs = new Map<string, string>();\n // Broadcasts pushed before the topic's join is live — flushed right after it.\n private pendingBroadcasts: PendingBroadcast[] = [];\n // Per-topic rejoin attempt counter for the token-expiry channel-death guard.\n // Bumped on every channel rejoin; reset to 0 when the topic delivers a live\n // broadcast (proof the channel recovered). At MAX_CHANNEL_REJOIN_ATTEMPTS the\n // channel is given up on (onChannelError) instead of looping forever.\n private readonly channelRejoinAttempts = new Map<string, number>();\n // Pending per-topic rejoin backoff timers — separate from the socket-level\n // reconnectTimer so a channel backoff never clobbers socket reconnection.\n private readonly channelRejoinTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\n // Monotonic Phoenix message ref. Every outbound frame carries a unique ref.\n private refCounter = 0;\n private connected = false;\n private started = false;\n private stopped = false;\n // Reset ONLY on a genuine open, so a never-opening socket walks\n // attempt 0,1,2,… → delays 1,2,4,…,cap instead of hammering (iOS parity).\n private reconnectAttempt = 0;\n // Invalidates async continuations (token awaits) from a superseded connect.\n private connectEpoch = 0;\n\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n\n constructor(options: RealtimeSocketOptions) {\n this.options = options;\n }\n\n // MARK: lifecycle\n\n /** Install handlers and open the socket. Idempotent while started. */\n start(handlers: RealtimeSocketHandlers): void {\n this.handlers = handlers;\n if (this.started) return;\n this.started = true;\n this.stopped = false;\n void this.connect();\n }\n\n /** Tear down the socket, stop reconnecting, emit `idle`. Idempotent. */\n stop(): void {\n this.stopped = true;\n this.started = false;\n this.connected = false;\n this.connectEpoch += 1; // invalidate in-flight connect continuations\n this.stopHeartbeat();\n if (this.reconnectTimer !== null) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n this.clearAllChannelRejoins();\n const ws = this.ws;\n this.ws = null;\n this.joinRefs.clear();\n ws?.close(1000);\n this.handlers?.onStateChange('idle');\n }\n\n // MARK: topic membership\n\n /**\n * Join a topic (bare sub-topic, no \"realtime:\" prefix). Idempotent. Sent\n * immediately when the socket is up, else on the next (re)connect.\n */\n joinTopic(topic: string): void {\n if (this.joinedTopics.has(topic)) return;\n this.joinedTopics.add(topic);\n if (this.connected) void this.sendJoin(topic);\n }\n\n /**\n * Leave a topic: sends `phx_leave` when connected and always drops it from\n * the re-join set (a future reconnect won't restore it). Pending broadcasts\n * for the topic are discarded.\n */\n leaveTopic(topic: string): void {\n if (!this.joinedTopics.delete(topic)) return;\n this.joinRefs.delete(topic);\n this.pendingBroadcasts = this.pendingBroadcasts.filter((p) => p.topic !== topic);\n this.clearChannelRejoin(topic); // cancel any pending token-expiry rejoin\n if (this.isOpen()) this.send(encodeLeave(this.nextRef(), topic));\n }\n\n /**\n * Push a broadcast onto a topic (web addition — iOS is receive-only). Sent\n * synchronously when the topic's join is live on the current socket;\n * otherwise queued and flushed right after the join goes out.\n */\n sendBroadcast(topic: string, event: string, payload: Record<string, unknown>): void {\n const joinRef = this.joinRefs.get(topic);\n if (joinRef !== undefined && this.isOpen()) {\n this.send(encodeBroadcast(joinRef, this.nextRef(), topic, event, payload));\n } else {\n this.pendingBroadcasts.push({ topic, event, payload });\n }\n }\n\n // MARK: connect / reconnect\n\n private async connect(): Promise<void> {\n if (this.stopped) return;\n const epoch = ++this.connectEpoch;\n let token: string;\n try {\n token = await this.options.tokenProvider();\n } catch {\n // The provider is total by design (apikey fallback) — but a throw must\n // not kill the reconnect loop.\n this.scheduleReconnect();\n return;\n }\n if (this.stopped || epoch !== this.connectEpoch) return;\n\n // Resolved per attempt (not cached) so an environment that gains a\n // WebSocket later (test stubs, polyfill load order) recovers on retry.\n const Ctor = this.options.webSocket ?? (globalThis as { WebSocket?: WebSocketCtor }).WebSocket;\n if (!Ctor) {\n this.scheduleReconnect();\n return;\n }\n\n const ws = new Ctor(websocketUrl(this.options.baseUrl, token));\n this.ws = ws;\n // Every callback gates on `ws === this.ws` so events from a superseded\n // socket (replaced during reconnect) can't corrupt the live one's state.\n ws.onopen = () => {\n if (ws === this.ws) void this.handleOpen();\n };\n ws.onmessage = (event) => {\n if (ws === this.ws) this.handleMessage(event.data);\n };\n ws.onclose = () => {\n if (ws === this.ws) this.handleClose();\n };\n // Browsers always follow `error` with `close` — onclose drives recovery.\n ws.onerror = () => {};\n }\n\n /**\n * The browser `open` event proves the HTTP-101 handshake completed (the\n * analogue of iOS promoteToConnected): flip connected, announce, then\n * (re)join every topic with a fresh token + fresh refs. Backoff is NOT\n * reset here — it resets on the first inbound frame (iOS parity), so a\n * server that opens but immediately closes still grows the backoff.\n */\n private async handleOpen(): Promise<void> {\n if (this.stopped) return;\n this.connected = true;\n this.handlers?.onStateChange('connected');\n this.handlers?.onReconnect();\n this.startHeartbeat();\n for (const topic of this.joinedTopics) {\n await this.sendJoin(topic);\n }\n }\n\n private handleClose(): void {\n if (this.stopped) return;\n this.ws = null;\n this.joinRefs.clear();\n // The socket reconnect rejoins ALL topics fresh — drop per-channel rejoin\n // state so a pending channel backoff doesn't race the rejoin-all.\n this.clearAllChannelRejoins();\n this.stopHeartbeat();\n this.scheduleReconnect();\n }\n\n private scheduleReconnect(): void {\n if (this.stopped || !this.started) return;\n this.connected = false;\n this.handlers?.onStateChange('reconnecting');\n const attempt = this.reconnectAttempt;\n this.reconnectAttempt += 1;\n const cap = this.options.maxBackoffSeconds ?? DEFAULT_MAX_BACKOFF_SECONDS;\n const delaySeconds = backoffDelaySeconds(attempt, this.refCounter, cap);\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n void this.connect();\n }, delaySeconds * 1000);\n }\n\n // MARK: frames out\n\n /**\n * Join carries a freshly-resolved token (iOS parity — `sendJoin` awaits the\n * provider), so a Bearer rotated while offline rides the next rejoin.\n */\n private async sendJoin(topic: string): Promise<void> {\n const token = await this.options.tokenProvider();\n // Re-validate after the await: the socket may have dropped or the topic\n // may have been left while the token resolved.\n if (!this.isOpen() || !this.joinedTopics.has(topic)) return;\n const ref = this.nextRef();\n this.joinRefs.set(topic, String(ref));\n this.send(encodeJoin(ref, topic, token));\n this.flushPending(topic);\n }\n\n private flushPending(topic: string): void {\n const ready = this.pendingBroadcasts.filter((p) => p.topic === topic);\n if (ready.length === 0) return;\n this.pendingBroadcasts = this.pendingBroadcasts.filter((p) => p.topic !== topic);\n for (const p of ready) this.sendBroadcast(p.topic, p.event, p.payload);\n }\n\n private startHeartbeat(): void {\n this.stopHeartbeat();\n const interval = this.options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_MS;\n this.heartbeatTimer = setInterval(() => {\n if (this.isOpen()) this.send(encodeHeartbeat(this.nextRef()));\n }, interval);\n }\n\n private stopHeartbeat(): void {\n if (this.heartbeatTimer !== null) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n }\n\n /** Drop-if-not-open: a send racing a disconnect is recovered by the rejoin-all on reconnect. */\n private send(text: string): void {\n if (this.ws && this.ws.readyState === WS_OPEN) this.ws.send(text);\n }\n\n // MARK: frames in\n\n private handleMessage(data: unknown): void {\n if (typeof data !== 'string') return;\n const frame = decodeFrame(data);\n if (!frame) return;\n // Any valid inbound frame proves the server is actually delivering data\n // (not just completing the TCP handshake). Reset backoff here — iOS parity:\n // iOS promotes to connected on the first frame because URLSession does not\n // surface a separate handshake-complete signal. A server that opens then\n // immediately closes does NOT trigger this, so backoff keeps growing.\n this.reconnectAttempt = 0;\n // Token-expiry channel death (the cross-SDK P4 bug): at JWT TTL the server\n // kills just THIS channel (`{:stop, :normal}`) while the socket stays up\n // (heartbeats hold it past Kong's idle cap). Phoenix v2 pushes a per-topic\n // phx_close carrying the channel's join_ref ([<joinRef>,null,topic,\"phx_close\",{}];\n // phx_error on a crash). We rejoin that one topic with a FRESH token rather\n // than letting it silently die. Socket-level reconnect rejoins ALL topics —\n // this handles the case where only ONE channel was killed.\n if (frame.event === 'phx_close' || frame.event === 'phx_error') {\n this.handleChannelClose(frame);\n return;\n }\n // State frames are routed by NAME, before the broadcast unwrap: they are a\n // different vocabulary on the same envelope, and unwrapBroadcast does not\n // recognise them.\n if (frame.event === 'state_snapshot') {\n this.handlers?.onStateSnapshot(stripRealtimePrefix(frame.topic), frame.payload as SnapshotBody);\n return;\n }\n if (frame.event === 'state_diff') {\n this.handlers?.onStateDiff(stripRealtimePrefix(frame.topic), frame.payload as DiffBody);\n return;\n }\n // A join ack carries this connection's identity ON THAT CHANNEL. It is the\n // only place that identity exists, and presence cannot be written without it.\n if (frame.event === 'phx_reply') {\n const body = frame.payload as {\n status?: string;\n response?: Record<string, unknown>;\n } | null;\n const topic = stripRealtimePrefix(frame.topic);\n if (body && body.status === 'ok' && body.response && typeof body.response === 'object') {\n this.handlers?.onJoinInfo(topic, body.response);\n return;\n }\n // A refusal — but of WHAT?\n //\n // The server answers a refused PUSH with exactly the same envelope as a\n // refused join: a handler that threw, a state key you do not own, a rate\n // limit. Reading every error reply as a channel refusal tears down a\n // working channel from ordinary app code — one bid the project rejects\n // would drop every handler on `auction:7` and stop the socket ever\n // rejoining it.\n //\n // The ref separates them. A join's reply carries the join's own ref\n // (encodeJoin puts it in both slots); a push's reply carries the push's.\n // Anything that is not the live join's ref is one operation being\n // refused, and the channel is untouched.\n if (body && body.status === 'error' && String(frame.ref) === this.joinRefs.get(topic)) {\n const reason = typeof body.response?.['reason'] === 'string'\n ? (body.response['reason'] as string)\n : 'unauthorized';\n // A final refusal stops being a subscription: leaving the topic in the\n // set would rejoin it on every reconnect, forever, against a decision\n // that will not change. A transient one stays — the reconnect IS the\n // retry it asks for.\n if (!RETRYABLE_REFUSALS.has(reason)) {\n this.joinedTopics.delete(topic);\n this.joinRefs.delete(topic);\n }\n this.handlers?.onChannelRefused(topic, reason);\n }\n return;\n }\n // Only `broadcast` events carry channel traffic; replies/acks/presence\n // are bookkeeping (iOS ignores them identically).\n const inbound = unwrapBroadcast(frame);\n if (inbound) {\n // A live broadcast proves the channel recovered — reset its rejoin guard.\n this.channelRejoinAttempts.delete(inbound.topic);\n this.handlers?.onInbound(inbound);\n }\n }\n\n /**\n * Recover (or give up on) a single channel the server closed for token\n * reasons. Ignored unless the close targets a topic we still intend to be\n * joined AND its join_ref matches the LIVE join — so a stale close from a\n * superseded join, or a close arriving after our OWN phx_leave (the topic is\n * already dropped from joinedTopics/joinRefs), does NOT trigger a rejoin.\n */\n private handleChannelClose(frame: { topic: string; joinRef: unknown }): void {\n const topic = stripRealtimePrefix(frame.topic);\n // Intentional-leave / stale-close guard: leaveTopic() removes the topic\n // from joinedTopics and its joinRefs entry, so neither check passes here.\n if (!this.joinedTopics.has(topic)) return;\n if (this.joinRefs.get(topic) !== frame.joinRef) return;\n // The current join is dead; drop its ref so a queued send won't use it.\n this.joinRefs.delete(topic);\n\n const attempt = this.channelRejoinAttempts.get(topic) ?? 0;\n if (attempt >= MAX_CHANNEL_REJOIN_ATTEMPTS) {\n // A genuinely-bad token (not just expired) closes every rejoin at once —\n // give up on this channel instead of tight-looping.\n this.joinedTopics.delete(topic);\n this.channelRejoinAttempts.delete(topic);\n this.handlers?.onChannelError(topic);\n return;\n }\n this.channelRejoinAttempts.set(topic, attempt + 1);\n if (attempt === 0) {\n // First close: rejoin immediately (the common case — token simply expired).\n void this.sendJoin(topic);\n return;\n }\n // Repeated closes: back off (reuse the socket reconnect curve) so a flapping\n // channel doesn't hammer the server. Replaces any pending rejoin for the topic.\n const cap = this.options.maxBackoffSeconds ?? DEFAULT_MAX_BACKOFF_SECONDS;\n const delayMs = backoffDelaySeconds(attempt, this.refCounter, cap) * 1000;\n const existing = this.channelRejoinTimers.get(topic);\n if (existing !== undefined) clearTimeout(existing);\n const timer = setTimeout(() => {\n this.channelRejoinTimers.delete(topic);\n if (this.joinedTopics.has(topic)) void this.sendJoin(topic);\n }, delayMs);\n this.channelRejoinTimers.set(topic, timer);\n }\n\n // MARK: state\n\n /** Write one entry on a joined channel's state. Dropped when the topic has no\n * live join: the server refuses state on a channel this socket is not on, and\n * the reconnect's join brings a fresh snapshot anyway. */\n sendStateSet(topic: string, key: string, value: unknown, life: 'ephemeral' | 'durable'): void {\n const joinRef = this.joinRefs.get(topic);\n if (joinRef === undefined || !this.isOpen()) return;\n this.send(encodeStateSet(joinRef, this.nextRef(), topic, key, value, life));\n }\n\n /** Remove one entry. The server refuses a key this connection does not own. */\n sendStateDel(topic: string, key: string): void {\n const joinRef = this.joinRefs.get(topic);\n if (joinRef === undefined || !this.isOpen()) return;\n this.send(encodeStateDel(joinRef, this.nextRef(), topic, key));\n }\n\n /** Ask for a fresh snapshot — the answer to a detected gap. */\n sendStateResync(topic: string): void {\n const joinRef = this.joinRefs.get(topic);\n if (joinRef === undefined || !this.isOpen()) return;\n this.send(encodeStateResync(joinRef, this.nextRef(), topic));\n }\n\n /**\n * Present a fresher credential on every joined channel WITHOUT rejoining.\n *\n * The alternative — dropping and rejoining — reaps this connection's\n * ephemeral entries (its presence) and forces a full resync on every channel.\n * A token rotation is not a reconnection and must not look like one.\n */\n sendAccessToken(token: string): void {\n if (!this.isOpen()) return;\n for (const [topic, joinRef] of this.joinRefs) {\n this.send(encodeAccessToken(joinRef, this.nextRef(), topic, token));\n }\n }\n\n // MARK: helpers\n\n /** Drop a single topic's token-expiry rejoin state (cancel its pending timer). */\n private clearChannelRejoin(topic: string): void {\n const timer = this.channelRejoinTimers.get(topic);\n if (timer !== undefined) {\n clearTimeout(timer);\n this.channelRejoinTimers.delete(topic);\n }\n this.channelRejoinAttempts.delete(topic);\n }\n\n /** Drop all per-channel rejoin state (socket teardown / full reconnect). */\n private clearAllChannelRejoins(): void {\n for (const timer of this.channelRejoinTimers.values()) clearTimeout(timer);\n this.channelRejoinTimers.clear();\n this.channelRejoinAttempts.clear();\n }\n\n private isOpen(): boolean {\n return this.connected && this.ws !== null && this.ws.readyState === WS_OPEN;\n }\n\n private nextRef(): number {\n this.refCounter += 1;\n return this.refCounter;\n }\n}\n","// state.ts — the client half of the channel state primitive.\n//\n// The server keeps a keyed, last-writer-wins map per channel and hands it out\n// two ways: a SNAPSHOT (the whole picture, on join and on resync) and DIFFS\n// (what changed, after that). This applies both to a local map and — the part\n// that matters — DETECTS when a diff cannot be applied.\n//\n// That detection is the reason state can travel over a transport that is\n// allowed to drop frames. A diff carries the sequence it moves the channel\n// FROM as well as the one it moves it TO, so a client holding less than\n// `from_seq` knows it missed something. It says so instead of guessing, and the\n// caller answers by asking for a fresh snapshot. Without it a dropped frame\n// would leave a client quietly, permanently wrong — which looks exactly like a\n// frame that never arrived.\n\n/** One entry as it travels: the value, and whether it outlives its writer. */\nexport interface StateEntryWire {\n v: unknown;\n life: string;\n}\n\nexport interface SnapshotBody {\n seq: number;\n entries: Record<string, StateEntryWire>;\n}\n\nexport interface DiffBody {\n from_seq: number;\n seq: number;\n set: Record<string, StateEntryWire>;\n del: string[];\n}\n\n/** What applying a diff did. `gap` means the caller must resync. */\nexport type DiffResult = 'applied' | 'gap';\n\n/** The key prefix presence entries use. One per connection, and the connection\n * id is the suffix — which is how a client tells its own entry from everyone\n * else's. */\nexport const PRESENCE_PREFIX = '$p:';\n\nexport class ChannelState {\n /** The sequence this local copy is current as of. */\n seq = 0;\n /** key → value. Lifetimes are the server's business; a reader only needs the\n * values, and presence is derived from the key shape. */\n readonly entries = new Map<string, unknown>();\n\n /** Replace everything. A snapshot is the WHOLE picture, never a merge —\n * merging one would keep entries the server has since removed. */\n applySnapshot(body: SnapshotBody): void {\n this.entries.clear();\n for (const [k, e] of Object.entries(body.entries ?? {})) {\n this.entries.set(k, e.v);\n }\n this.seq = body.seq;\n }\n\n /**\n * Apply a diff, or report that it cannot be applied.\n *\n * `gap` when this copy is behind the diff's starting point: frames were\n * missed, and applying anyway would produce a state that never existed on the\n * server. The state is left untouched so the caller can resync from something\n * consistent.\n *\n * A diff at or below the current sequence is already applied — a resend after\n * a retry — and is silently accepted so duplicates are harmless.\n */\n applyDiff(body: DiffBody): DiffResult {\n if (this.seq < body.from_seq) return 'gap';\n if (body.seq <= this.seq) return 'applied';\n\n for (const [k, e] of Object.entries(body.set ?? {})) this.entries.set(k, e.v);\n for (const k of body.del ?? []) this.entries.delete(k);\n this.seq = body.seq;\n return 'applied';\n }\n\n /**\n * Everyone else's presence.\n *\n * `selfConn` is this connection's id, as the join reply reported it. Its own\n * entry is excluded because an app rendering \"who else is here\" would\n * otherwise always show one extra face — itself.\n */\n presence(selfConn: string | undefined): unknown[] {\n const own = selfConn === undefined ? undefined : PRESENCE_PREFIX + selfConn;\n const out: unknown[] = [];\n for (const [k, v] of this.entries) {\n if (!k.startsWith(PRESENCE_PREFIX) || k === own) continue;\n out.push(v);\n }\n return out;\n }\n}\n","// `pb.realtime` — the public, customer-facing realtime surface. Mirrors the\n// iOS `Facade+Realtime` + `RealtimeClient` pair (palbackend-ios-src\n// Sources/Palbe/Realtime/): a channel/handler manager multiplexing every\n// subscription over ONE shared, auto-reconnecting WebSocket (connection.ts).\n//\n// const channel = pb.realtime.channel('room:42');\n// const sub = channel.on('message', (payload) => { ... });\n// channel.send('message', { text: 'hi' }); // web addition — iOS is receive-only\n// // later: sub.cancel() (last cancel on the channel → phx_leave)\n//\n// Channel names are app-defined bare sub-topics — do NOT prefix with\n// \"realtime:\" (that's internal to the transport). `realtime` is a reserved\n// namespace: a backend endpoint cannot be named `realtime` (namespaces.ts\n// RESERVED, alongside auth/flags).\n//\n// Realtime is CLIENT-ONLY: on a host with no WebSocket (SSR/RSC/route\n// handlers), `channel()` throws a guided validation error. Reading\n// `status` is always safe (it just reports `idle`).\n\nimport type { Unsubscribe } from '../auth-facade.js';\nimport { BackendError } from '../errors.js';\nimport type { PalbeRuntime } from '../runtime.js';\nimport { AnonTokenProvider, realtimeTokenProvider } from './anon-token.js';\nimport { type RealtimeConnectionState, RealtimeSocket } from './connection.js';\nimport type { RealtimeInbound } from './frames.js';\nimport { ChannelState, type DiffBody, type SnapshotBody } from './state.js';\n\nexport type { RealtimeConnectionState } from './connection.js';\n\n/** An inbound broadcast payload (the inner `payload` of the Phoenix envelope). */\nexport type RealtimePayload = Record<string, unknown>;\nexport type RealtimeHandler = (payload: RealtimePayload) => void;\n\n/** A cancellable realtime subscription. `cancel()` removes the handler and,\n * when it was the channel's last, leaves the channel on the shared socket.\n * Cancelling twice is a no-op. */\nexport interface RealtimeSubscription {\n cancel(): void;\n}\n\nexport interface RealtimeStatusSnapshot {\n state: RealtimeConnectionState;\n lastEventAt: Date | null;\n}\n\n/**\n * The observable connection status (`pb.realtime.status`) — the plain-JS\n * analogue of the iOS `@Observable RealtimeStatusStore`. `state` drives a\n * live \"connected\" badge; `lastEventAt` proves a push actually arrived.\n * `onChange` fires on state transitions and on every recorded event.\n */\nexport interface RealtimeStatus {\n readonly state: RealtimeConnectionState;\n readonly lastEventAt: Date | null;\n onChange(callback: (status: RealtimeStatusSnapshot) => void): Unsubscribe;\n}\n\nclass StatusStore implements RealtimeStatus {\n private currentState: RealtimeConnectionState = 'idle';\n private lastEvent: Date | null = null;\n private readonly listeners = new Set<(status: RealtimeStatusSnapshot) => void>();\n\n get state(): RealtimeConnectionState {\n return this.currentState;\n }\n\n get lastEventAt(): Date | null {\n return this.lastEvent;\n }\n\n onChange(callback: (status: RealtimeStatusSnapshot) => void): Unsubscribe {\n this.listeners.add(callback);\n return () => {\n this.listeners.delete(callback);\n };\n }\n\n /** No-op on equal state (iOS setState guard) — listeners see transitions only. */\n setState(state: RealtimeConnectionState): void {\n if (state === this.currentState) return;\n this.currentState = state;\n this.emit();\n }\n\n recordEvent(at: Date): void {\n this.lastEvent = at;\n this.emit();\n }\n\n private emit(): void {\n const snapshot: RealtimeStatusSnapshot = {\n state: this.currentState,\n lastEventAt: this.lastEvent,\n };\n for (const listener of this.listeners) listener(snapshot);\n }\n}\n\ninterface HandlerEntry {\n topic: string;\n event: string;\n handler: RealtimeHandler;\n}\n\n/**\n * Refusals worth asking about again — the same set the transport uses to decide\n * whether to keep a topic in its rejoin list. `unavailable` is the project being\n * unreachable; the two ceilings are \"slow down\" and \"you are holding too much\",\n * both of which pass with time.\n */\nconst RETRYABLE_REASONS = new Set(['unavailable', 'rate_limited', 'too_many_channels']);\n\n/** Why the server refused a channel. */\nexport interface RealtimeChannelError {\n /** The channel this is about. */\n topic: string;\n /** Exactly what the server said: `\"unauthorized\"` or `\"unavailable\"`. */\n reason: string;\n /**\n * Whether asking again could succeed. `unavailable` (the project could not be\n * reached to decide) is retryable and the socket retries it on reconnect;\n * `unauthorized` is a settled answer and is not.\n */\n retryable: boolean;\n}\n\ninterface ErrorHandlerEntry {\n topic: string;\n handler: (error: RealtimeChannelError) => void;\n}\n\n/**\n * A subscription handle for one realtime channel (returned by\n * `pb.realtime.channel(name)` — the SAME instance per name). A channel is\n * joined on the shared socket when its first handler is added (or on the\n * first `send`) and left when its last handler cancels.\n */\nexport class RealtimeChannel {\n /** The app-defined channel name (bare sub-topic, no \"realtime:\" prefix). */\n readonly name: string;\n private readonly owner: PalbeRealtime;\n\n /** @internal — obtain channels via `pb.realtime.channel(name)`. */\n constructor(name: string, owner: PalbeRealtime) {\n this.name = name;\n this.owner = owner;\n }\n\n /**\n * Subscribe `handler` to `event` on this channel. Fire-and-forget: the\n * join rides the shared socket asynchronously. Hold the returned\n * subscription and `cancel()` it to stop.\n */\n on(event: string, handler: RealtimeHandler): RealtimeSubscription {\n return this.owner.subscribe(this.name, event, handler);\n }\n\n /**\n * Called when the server REFUSES this channel.\n *\n * A join is an authorization decision, and a decision the app never hears is\n * the same as no decision at all: silence looks exactly like a slow network.\n * This is where a refusal arrives, carrying the server's own distinction —\n * `retryable: false` means \"you may not\" and asking again will not help;\n * `retryable: true` means the project could not be asked, and the socket's\n * next reconnect retries on its own.\n *\n * pb.realtime.channel('room:42').onError((err) => {\n * if (!err.retryable) showJoinDenied()\n * })\n */\n onError(handler: (error: RealtimeChannelError) => void): RealtimeSubscription {\n return this.owner.subscribeError(this.name, handler);\n }\n\n /**\n * Broadcast `payload` to the channel's other subscribers (web addition —\n * iOS is receive-only). Joins the channel if it isn't already; queued\n * until the join is live on the socket.\n */\n send(event: string, payload: RealtimePayload = {}): void {\n this.owner.send(this.name, event, payload);\n }\n\n /**\n * Watch one key of the channel's shared state.\n *\n * The handler is called with the CURRENT value straight away when there is\n * one, and again on every change. That is the difference from `on()`: a\n * subscriber to an event learns nothing until the next one, while state is\n * already there when you ask.\n */\n onState(key: string, handler: (value: unknown) => void): RealtimeSubscription {\n return this.owner.subscribeState(this.name, key, handler);\n }\n\n /** Read one key without subscribing. */\n stateValue(key: string): unknown {\n return this.owner.stateOf(this.name).entries.get(key);\n }\n\n /** Write one key. `durable` outlives this connection; `ephemeral` is reaped\n * when it closes, which is what presence is built on. */\n setState(key: string, value: unknown, opts: { life?: 'ephemeral' | 'durable' } = {}): void {\n this.owner.writeState(this.name, key, value, opts.life ?? 'durable');\n }\n\n /** Remove a key this connection wrote. */\n clearState(key: string): void {\n this.owner.clearState(this.name, key);\n }\n\n /**\n * Announce this client on the channel. `meta` is whatever other participants\n * should see — a name, an avatar, a cursor.\n *\n * It is ephemeral state keyed by this connection, so it disappears when the\n * socket does. Nothing has to be un-announced on a crash.\n */\n presenceEnter(meta: RealtimePayload): void {\n this.owner.presenceWrite(this.name, meta);\n }\n\n /** Replace the announced value — a cursor moving is this, at whatever rate\n * the app likes: the server conflates writes it cannot deliver fast enough. */\n presenceUpdate(meta: RealtimePayload): void {\n this.owner.presenceWrite(this.name, meta);\n }\n\n /** Leave early. Closing the socket does this on its own. */\n presenceLeave(): void {\n this.owner.presenceClear(this.name);\n }\n\n /** Everyone else currently announced, this client excluded. */\n presenceOthers(): unknown[] {\n return this.owner.presenceOthers(this.name);\n }\n\n /** Watch the announced set. Called on every change. */\n onPresence(handler: (others: unknown[]) => void): RealtimeSubscription {\n return this.owner.subscribePresence(this.name, handler);\n }\n}\n\nexport class PalbeRealtime {\n private readonly rt: PalbeRuntime;\n private socket: RealtimeSocket | null = null;\n private readonly channels = new Map<string, RealtimeChannel>();\n private readonly statusStore = new StatusStore();\n // Per-(topic, event) handlers — an event may have multiple handlers\n // (multiple .on calls); entry identity is the unsubscribe token.\n private readonly handlers = new Set<HandlerEntry>();\n // Per-topic refusal handlers. Kept apart from `handlers` because they are not\n // refcounted: registering one must not join a channel, and cancelling the\n // last one must not leave a channel that still has event handlers on it.\n private readonly errorHandlers = new Set<ErrorHandlerEntry>();\n // Joins are refcounted per topic so the socket only leaves a topic when\n // its last handler cancels (iOS RealtimeClient parity).\n private readonly topicRefcount = new Map<string, number>();\n // Per-topic shared state, and this connection's identity on each topic (from\n // the join ack). Presence is keyed by that identity, so a write before the\n // ack has nowhere to go and is queued until it arrives.\n private readonly states = new Map<string, ChannelState>();\n private readonly connIds = new Map<string, string>();\n private readonly pendingPresence = new Map<string, RealtimePayload>();\n private readonly stateWatchers = new Set<{ topic: string; key: string; handler: (v: unknown) => void }>();\n private readonly presenceWatchers = new Set<{ topic: string; handler: (others: unknown[]) => void }>();\n\n constructor(rt: PalbeRuntime) {\n this.rt = rt;\n }\n\n /**\n * Get the handle for an app-defined channel (e.g. \"room:42\"). The same\n * name returns the SAME instance. Throws a guided error on server-side\n * hosts (SSR/RSC/Node) — realtime is client-only.\n */\n channel(name: string): RealtimeChannel {\n // Primary: detect server environment by the absence of the browser globals\n // `document` and `window`. Node ≥22 ships a global WebSocket, so the old\n // `typeof WebSocket === 'undefined'` check is no-op there — server code\n // would silently open real sockets.\n if (typeof document === 'undefined' && typeof window === 'undefined') {\n throw new BackendError('validation', {\n code: 'realtime_unavailable',\n message:\n 'pb.realtime requires a browser environment: document and window are not defined ' +\n '(server/SSR/RSC). Subscribe from browser code instead — e.g. a client component or useEffect.',\n });\n }\n // Secondary: browser polyfill may be missing even with document/window present.\n if (typeof WebSocket === 'undefined') {\n throw new BackendError('validation', {\n code: 'realtime_unavailable',\n message:\n 'pb.realtime is client-only: this environment has no WebSocket. ' +\n 'Subscribe from browser code instead — e.g. a client component or useEffect.',\n });\n }\n let channel = this.channels.get(name);\n if (!channel) {\n channel = new RealtimeChannel(name, this);\n this.channels.set(name, channel);\n }\n return channel;\n }\n\n /**\n * Tear down the shared socket and clear all channel state. Called by\n * `__configure` and `__reset` when the runtime is replaced, so old sockets\n * are not left open and heartbeat timers do not leak.\n */\n destroy(): void {\n this.socket?.stop();\n this.socket = null;\n this.channels.clear();\n }\n\n /** The observable connection status. Safe to read anywhere (reports `idle` until a socket exists). */\n get status(): RealtimeStatus {\n return this.statusStore;\n }\n\n /**\n * Hand the socket a fresher credential for the SAME user, without rejoining.\n *\n * Called when the app's token rotates. The alternative — letting the channels\n * die at expiry and rejoining — reaps this connection's ephemeral entries\n * (its presence, on every channel) and forces a full state resync. A token\n * getting older is not a disconnection and must not cost one.\n *\n * A no-op before a socket exists: the first connect resolves a fresh token\n * on its own.\n */\n refreshToken(token: string): void {\n this.socket?.sendAccessToken(token);\n }\n\n /** @internal */\n subscribe(topic: string, event: string, handler: RealtimeHandler): RealtimeSubscription {\n const socket = this.ensureSocket();\n const entry: HandlerEntry = { topic, event, handler };\n this.handlers.add(entry);\n const count = (this.topicRefcount.get(topic) ?? 0) + 1;\n this.topicRefcount.set(topic, count);\n if (count === 1) socket.joinTopic(topic);\n\n let cancelled = false;\n return {\n cancel: () => {\n if (cancelled) return;\n cancelled = true;\n this.handlers.delete(entry);\n // A count that is GONE means this topic was already torn down — by a\n // final refusal, which deletes the count while the app still holds live\n // cancel handles. Reading a missing count as 1 and decrementing it to 0\n // then leaves the channel: an app that re-subscribed after\n // re-authenticating, and only afterwards tidied up its old handle, had\n // the new subscription silently severed. iOS guards this; this did not.\n const held = this.topicRefcount.get(topic);\n if (held === undefined) return;\n const next = held - 1;\n if (next <= 0) {\n this.topicRefcount.delete(topic);\n socket.leaveTopic(topic);\n } else {\n this.topicRefcount.set(topic, next);\n }\n },\n };\n }\n\n /**\n * Register a refusal handler for one topic. Does NOT join: watching for a\n * refusal is not a subscription, and an app that registers one before its\n * first `.on()` should not be joining anything yet.\n *\n * @internal\n */\n subscribeError(\n topic: string,\n handler: (error: RealtimeChannelError) => void,\n ): RealtimeSubscription {\n const entry: ErrorHandlerEntry = { topic, handler };\n this.errorHandlers.add(entry);\n let cancelled = false;\n return {\n cancel: () => {\n if (cancelled) return;\n cancelled = true;\n this.errorHandlers.delete(entry);\n },\n };\n }\n\n /** @internal */\n send(topic: string, event: string, payload: RealtimePayload): void {\n const socket = this.ensureSocket();\n // Phoenix only accepts pushes on joined topics. Idempotent when already\n // joined; a send-only channel stays joined until its handlers (if any\n // are ever added) all cancel.\n socket.joinTopic(topic);\n socket.sendBroadcast(topic, event, payload);\n }\n\n /** Lazily build + start the shared socket on first subscription/send. */\n private ensureSocket(): RealtimeSocket {\n if (!this.socket) {\n const anon = new AnonTokenProvider(this.rt);\n this.socket = new RealtimeSocket({\n baseUrl: this.rt.config.url,\n tokenProvider: realtimeTokenProvider(this.rt, anon),\n });\n this.socket.start({\n onInbound: (inbound) => this.route(inbound),\n // Rejoin-all already happens inside the socket; nothing to refresh\n // here (no managed flags-sync on web yet — pb.flags polls).\n onReconnect: () => {},\n onStateChange: (state) => this.statusStore.setState(state),\n onChannelError: (topic) => this.handleChannelError(topic),\n onChannelRefused: (topic, reason) => this.handleChannelRefused(topic, reason),\n onStateSnapshot: (topic, body) => this.applySnapshot(topic, body),\n onStateDiff: (topic, body) => this.applyDiff(topic, body),\n onJoinInfo: (topic, info) => this.applyJoinInfo(topic, info),\n });\n }\n return this.socket;\n }\n\n private route(inbound: RealtimeInbound): void {\n // Any inbound broadcast counts as a \"live event\" (iOS parity) — even one\n // without a matching handler proves the socket is delivering.\n this.statusStore.recordEvent(new Date());\n for (const entry of this.handlers) {\n if (entry.topic === inbound.topic && entry.event === inbound.event) {\n entry.handler(inbound.payload);\n }\n }\n }\n\n /**\n /**\n * The server refused a channel. Tell whoever asked, and — when the answer is\n * final — stop pretending the app is subscribed to it.\n *\n * `unavailable` leaves everything in place: the project could not be reached\n * to decide, the socket rejoins on its next reconnect, and dropping the app's\n * handlers would make a momentary outage look like a permanent denial.\n */\n private handleChannelRefused(topic: string, reason: string): void {\n const retryable = RETRYABLE_REASONS.has(reason);\n for (const entry of this.errorHandlers) {\n if (entry.topic === topic) entry.handler({ topic, reason, retryable });\n }\n if (retryable) return;\n // A final refusal ends every kind of subscription on this channel, not just\n // the event handlers. A state or presence watcher left registered here would\n // wait forever: no snapshot or diff can arrive for a channel the server has\n // refused, so the app sits on a callback that will never fire again and has\n // nothing telling it why.\n this.forgetTopic(topic);\n }\n\n /**\n * A channel died for good — a token-expiry rejoin that exhausted its attempts.\n * The app must stop expecting anything on it, and the status observable flips\n * to `'error'` (the React `useChannel` hook reports that).\n *\n * It clears the SAME registries a final refusal clears, and used to clear only\n * the event handlers. A state or presence watcher left behind here waits\n * forever for a snapshot that cannot arrive, and fires twice if the app ever\n * re-subscribes — the identical failure its sibling three functions up\n * describes, on a channel that was given up on rather than refused.\n */\n private handleChannelError(topic: string): void {\n this.forgetTopic(topic);\n this.statusStore.setState('error');\n }\n\n /** Drop every registration this topic holds. One place, because \"which\n * registries\" is a question two callers must never answer differently. */\n private forgetTopic(topic: string): void {\n for (const entry of this.handlers) {\n if (entry.topic === topic) this.handlers.delete(entry);\n }\n for (const entry of this.stateWatchers) {\n if (entry.topic === topic) this.stateWatchers.delete(entry);\n }\n for (const entry of this.presenceWatchers) {\n if (entry.topic === topic) this.presenceWatchers.delete(entry);\n }\n this.pendingPresence.delete(topic);\n this.states.delete(topic);\n this.connIds.delete(topic);\n this.topicRefcount.delete(topic);\n }\n\n // ── state ──────────────────────────────────────────────────────────────────\n\n /** @internal */\n stateOf(topic: string): ChannelState {\n let st = this.states.get(topic);\n if (!st) {\n st = new ChannelState();\n this.states.set(topic, st);\n }\n return st;\n }\n\n /** @internal */\n subscribeState(topic: string, key: string, handler: (value: unknown) => void): RealtimeSubscription {\n const socket = this.ensureSocket();\n const entry = { topic, key, handler };\n this.stateWatchers.add(entry);\n this.retainTopic(topic, socket);\n // The current value, if there is one: state is already there when you ask,\n // and making the caller wait for a change would defeat the point.\n const known = this.stateOf(topic).entries.get(key);\n if (known !== undefined) handler(known);\n\n let cancelled = false;\n return {\n cancel: () => {\n if (cancelled) return;\n cancelled = true;\n this.stateWatchers.delete(entry);\n this.releaseTopic(topic, socket);\n },\n };\n }\n\n /** @internal */\n subscribePresence(topic: string, handler: (others: unknown[]) => void): RealtimeSubscription {\n const socket = this.ensureSocket();\n const entry = { topic, handler };\n this.presenceWatchers.add(entry);\n this.retainTopic(topic, socket);\n handler(this.presenceOthers(topic));\n\n let cancelled = false;\n return {\n cancel: () => {\n if (cancelled) return;\n cancelled = true;\n this.presenceWatchers.delete(entry);\n this.releaseTopic(topic, socket);\n },\n };\n }\n\n /** @internal */\n writeState(topic: string, key: string, value: unknown, life: 'ephemeral' | 'durable'): void {\n const socket = this.ensureSocket();\n socket.joinTopic(topic);\n socket.sendStateSet(topic, key, value, life);\n }\n\n /** @internal */\n clearState(topic: string, key: string): void {\n this.ensureSocket().sendStateDel(topic, key);\n }\n\n /** @internal */\n presenceWrite(topic: string, meta: RealtimePayload): void {\n const socket = this.ensureSocket();\n socket.joinTopic(topic);\n const conn = this.connIds.get(topic);\n if (conn === undefined) {\n // The join ack has not arrived, so this connection has no identity on the\n // channel yet and the entry would have no key. Held, and written the\n // moment the ack lands — announcing under a guessed key would leave a\n // ghost nothing ever reaps.\n this.pendingPresence.set(topic, meta);\n return;\n }\n socket.sendStateSet(topic, PRESENCE_KEY(conn), meta, 'ephemeral');\n }\n\n /** @internal */\n presenceClear(topic: string): void {\n this.pendingPresence.delete(topic);\n const conn = this.connIds.get(topic);\n if (conn === undefined) return;\n this.ensureSocket().sendStateDel(topic, PRESENCE_KEY(conn));\n }\n\n /** @internal */\n presenceOthers(topic: string): unknown[] {\n return this.stateOf(topic).presence(this.connIds.get(topic));\n }\n\n private retainTopic(topic: string, socket: RealtimeSocket): void {\n const count = (this.topicRefcount.get(topic) ?? 0) + 1;\n this.topicRefcount.set(topic, count);\n if (count === 1) socket.joinTopic(topic);\n }\n\n private releaseTopic(topic: string, socket: RealtimeSocket): void {\n const next = (this.topicRefcount.get(topic) ?? 1) - 1;\n if (next <= 0) {\n this.topicRefcount.delete(topic);\n socket.leaveTopic(topic);\n return;\n }\n this.topicRefcount.set(topic, next);\n }\n\n private applySnapshot(topic: string, body: SnapshotBody): void {\n this.stateOf(topic).applySnapshot(body);\n this.notifyState(topic);\n }\n\n private applyDiff(topic: string, body: DiffBody): void {\n if (this.stateOf(topic).applyDiff(body) === 'gap') {\n // Frames were missed. Asking for the whole picture is the only honest\n // recovery: applying the diff anyway would produce a state that never\n // existed on the server.\n this.socket?.sendStateResync(topic);\n return;\n }\n this.notifyState(topic);\n }\n\n private applyJoinInfo(topic: string, info: { conn?: string; state_seq?: number }): void {\n if (typeof info.conn !== 'string' || info.conn === '') return;\n this.connIds.set(topic, info.conn);\n\n // ADOPT THE SERVER'S SEQUENCE. The ack says which sequence the state we are\n // about to receive describes, and until now nothing read it.\n //\n // It matters because the server's sequence can go BACKWARDS between our\n // joins: a channel everybody left and that held nothing is forgotten, and\n // the next write starts again at 1. A client that kept its old local\n // sequence — say 11 — then reads the fresh `state_diff{from_seq:0, seq:1}`\n // as one it has already applied (`seq <= this.seq`) and silently discards\n // it, along with every change after it until the server passes 11. Its\n // picture is wrong and nothing ever heals it, which is the exact failure\n // the store's own eviction guard exists to prevent — reached by another\n // door.\n //\n // Behind the server means our copy is stale, not ahead: drop it and take\n // what arrives. A snapshot follows when there is anything to send, and when\n // there is not, empty IS the truth.\n const seq = info.state_seq;\n if (typeof seq === 'number' && seq < this.stateOf(topic).seq) {\n this.stateOf(topic).applySnapshot({ seq, entries: {} });\n }\n const held = this.pendingPresence.get(topic);\n if (held !== undefined) {\n this.pendingPresence.delete(topic);\n this.socket?.sendStateSet(topic, PRESENCE_KEY(info.conn), held, 'ephemeral');\n }\n this.notifyState(topic);\n }\n\n private notifyState(topic: string): void {\n this.statusStore.recordEvent(new Date());\n const st = this.stateOf(topic);\n for (const w of this.stateWatchers) {\n if (w.topic === topic) w.handler(st.entries.get(w.key));\n }\n if (this.presenceWatchers.size > 0) {\n const others = this.presenceOthers(topic);\n for (const w of this.presenceWatchers) {\n if (w.topic === topic) w.handler(others);\n }\n }\n }\n}\n\n/** Presence is state keyed by the connection that announced it. */\nconst PRESENCE_KEY = (conn: string) => `$p:${conn}`;\n","/**\n * Persisted session. The refresh token is the durable credential; access\n * token + expiry ride along (when available) so a reload — or a server-side\n * cookie read — can reuse a still-fresh access token instead of burning a\n * refresh per hydration (rotation storm under SSR).\n */\nexport interface PersistedSession {\n refreshToken: string;\n accessToken?: string;\n /** ms epoch */\n expiresAt?: number;\n}\n\nexport interface SessionStorageAdapter {\n load(): PersistedSession | null;\n save(session: PersistedSession): void;\n clear(): void;\n}\n\nexport function memorySessionStorage(): SessionStorageAdapter {\n let current: PersistedSession | null = null;\n return {\n load: () => current,\n save: (s) => {\n current = s;\n },\n clear: () => {\n current = null;\n },\n };\n}\n\nconst DEFAULT_KEY = 'palbe.session';\n\nexport function localStorageSessionStorage(key: string = DEFAULT_KEY): SessionStorageAdapter {\n return {\n load: () => {\n try {\n const raw = localStorage.getItem(key);\n if (!raw) return null;\n const parsed: unknown = JSON.parse(raw);\n if (typeof parsed === 'object' && parsed !== null) {\n const obj = parsed as Record<string, unknown>;\n if (typeof obj.refreshToken === 'string') {\n // Optional fields are validated as a PAIR: a wrong-typed (or lone)\n // access token / expiry degrades to the legacy {refreshToken}\n // shape — never null the whole session over the optional part.\n if (typeof obj.accessToken === 'string' && typeof obj.expiresAt === 'number') {\n return {\n refreshToken: obj.refreshToken,\n accessToken: obj.accessToken,\n expiresAt: obj.expiresAt,\n };\n }\n return { refreshToken: obj.refreshToken };\n }\n }\n return null;\n } catch {\n return null;\n }\n },\n save: (s) => {\n try {\n localStorage.setItem(key, JSON.stringify(s));\n } catch {\n // quota/SSR — non-fatal: session just won't survive reload\n }\n },\n clear: () => {\n try {\n localStorage.removeItem(key);\n } catch {\n // ignore\n }\n },\n };\n}\n\n/**\n * Browser → localStorage, else memory. Pass an environment-scoped key\n * (`palbe.session.<environmentRef>`) — the bare default key would collide\n * between two Palbase environments served from the same origin.\n */\nexport function defaultSessionStorage(key?: string): SessionStorageAdapter {\n if (typeof localStorage !== 'undefined') return localStorageSessionStorage(key);\n return memorySessionStorage();\n}\n","// AUTO-GENERATED from package.json — do not edit\nexport const VERSION = '7.4.0';\n","import { AuthClient } from '@palbase/auth';\nimport { HttpClient, TokenManager } from '@palbase/core';\nimport { AnalyticsState, PalbeAnalytics } from './analytics-facade.js';\nimport { environmentRefFromPublishableApiKey } from './api-key.js';\nimport { PalbeAuth } from './auth-facade.js';\nimport { PalbeCalls } from './calls/facade.js';\nimport type { PalbeConfig } from './config.js';\nimport { PalbeFlags } from './flags-facade.js';\nimport { PalbeMessaging } from './messaging/facade.js';\nimport { measureAppStart } from './perf/app-start.js';\nimport { PerfConfigClient } from './perf/perf-config-client.js';\nimport { PalbePerf } from './perf/perf-facade.js';\nimport { observeWebVitals } from './perf/web-vitals.js';\nimport { PalbeRealtime } from './realtime/facade.js';\nimport { applyBrowserOriginHeader } from './runtime-metadata.js';\nimport { defaultSessionStorage, type SessionStorageAdapter } from './storage.js';\nimport { VERSION } from './version.js';\n\nexport interface PalbeRuntime {\n config: PalbeConfig;\n /**\n * The project this runtime talks to, read from the publishable key.\n *\n * Exposed here so every consumer takes it from ONE derivation. It used to be\n * a field on the config that had to equal the key's — see buildRuntime.\n */\n projectRef: string;\n http: HttpClient;\n tokenManager: TokenManager;\n authClient: AuthClient;\n auth: PalbeAuth;\n flags: PalbeFlags;\n realtime: PalbeRealtime;\n analytics: PalbeAnalytics;\n calls: PalbeCalls;\n messaging: PalbeMessaging;\n /**\n * PalPerf surface. EAGER (like analytics identity): `request.ts` records a\n * `network` trace around EVERY SDK fetch, so the facade must exist from the\n * first request — never lazy. The buffer/timer it owns is inert until the\n * first item is recorded.\n */\n perf: PalbePerf;\n storage: SessionStorageAdapter;\n /**\n * Destroy the realtime facade if it was already constructed (no-op otherwise).\n * Called by `__configure` / `__reset` when the runtime is replaced so the old\n * socket is closed and heartbeat timers do not leak.\n */\n destroyRealtime(): void;\n}\n\nexport function buildRuntime(config: PalbeConfig): PalbeRuntime {\n // THE project identity, and there is one source for it: the publishable key\n // carries it (`pb_<ref>_c<random>`), so anything holding the key holds the ref.\n //\n // It used to be a second field beside the key, cross-checked against it — and\n // the cross-check was the whole reason it existed, which is the tell. A copy\n // that must equal its original is not a fact, it is an opportunity to\n // disagree: measured on 2026-08-16, `palbase link` wrote one value and minted\n // a key carrying another, and this generator refused to run at all.\n const projectRef = environmentRefFromPublishableApiKey(config.apiKey);\n if (projectRef === '') {\n throw new Error('PalbeConfig.apiKey does not contain a valid publishable project identity');\n }\n try {\n // Yalnız AYRIŞTIRILABİLİRLİK sınanıyor; sonucuna ihtiyaç yok.\n new URL(config.url);\n } catch {\n throw new Error('PalbeConfig.url must be a valid URL');\n }\n // NO HOST-FROM-KEY CHECK. A publishable key does not name its own address on\n // v2: a stack's identity is a compile-time constant, so every tenant's key\n // reads `pb_project_…` while its address is the tenant's own ref. The rule\n // that used to live here refused every correct v2 config. See the same note\n // in gen/generate.ts.\n const http = new HttpClient(config.apiKey, {\n url: config.url,\n headers: { 'X-Client-Info': `palbe-web/${VERSION}`, ...config.headers },\n });\n // One transport-level default covers direct AuthClient/HttpClient calls as\n // well as palbeRequest. It runs after global + per-request header merging,\n // so an explicit caller value wins and the live origin is read per request.\n http.addInterceptor(({ headers }) => applyBrowserOriginHeader(headers));\n const tokenManager = new TokenManager();\n http.tokenManager = tokenManager;\n const authClient = new AuthClient(http, tokenManager);\n const ref = projectRef;\n const storage = config.storage ?? defaultSessionStorage(ref ? `palbe.session.${ref}` : undefined);\n\n // Hydrate. Two paths:\n //\n // 1. FULL session persisted and still fresh → adopt it as a REAL session\n // (setTokens with the remaining TTL). The first request goes out with\n // the persisted Bearer and NO pre-flight refresh — this is what keeps\n // per-request server runtimes (palbe/next) from burning a token\n // rotation per request.\n //\n // 2. Legacy {refreshToken}-only shape, or an expired/invalid full session\n // → the expired-trick: the refresh token becomes an already-expired\n // session so HttpClient's pre-flight refresh runs before the first\n // real request. setTokens wires refreshFunction but sets\n // expiresAt=now+1h — we then override expiresAt to 0 so isExpired()\n // returns true immediately.\n //\n // Two implicit consequences, relied upon downstream (BOTH paths):\n // (a) setTokens marks AuthClient.hasSession=true, so the first successful\n // refresh after restore emits TOKEN_REFRESHED (not SIGNED_IN) — the\n // auth facade relies on this distinction.\n // (b) In the expired-trick path the transient session is\n // {accessToken:'', expiresAt:0} = \"pending refresh\" — never usable\n // directly, it only exists to arm pre-flight.\n const persisted = storage.load();\n if (persisted) {\n const { accessToken, expiresAt } = persisted;\n if (accessToken && expiresAt && expiresAt > Date.now()) {\n authClient.setTokens(\n accessToken,\n persisted.refreshToken,\n Math.floor((expiresAt - Date.now()) / 1000),\n );\n } else {\n authClient.setTokens('', persisted.refreshToken); // wires refreshFunction\n tokenManager.setSession({\n accessToken: '',\n refreshToken: persisted.refreshToken,\n expiresAt: 0,\n });\n }\n }\n\n // Persist token changes — the FULL session (access+expiry ride along so the\n // next hydration can skip the refresh); clear storage on sign-out.\n // AuthClient's own session mirror is registered first (its constructor), so\n // getSession() here already reflects the change that triggered us.\n authClient.onTokenChange(({ refreshToken }) => {\n if (refreshToken) {\n const session = authClient.getSession().data;\n if (session) {\n storage.save({\n refreshToken,\n accessToken: session.accessToken,\n expiresAt: session.expiresAt,\n });\n } else {\n storage.save({ refreshToken });\n }\n } else {\n storage.clear();\n }\n });\n\n // Lazy getter resolves the rt↔PalbeAuth construction cycle without a cast:\n // PalbeAuth needs rt, and `auth` stays non-optional on the interface.\n let auth: PalbeAuth | undefined;\n let flags: PalbeFlags | undefined;\n let realtime: PalbeRealtime | undefined;\n let analytics: PalbeAnalytics | undefined;\n let calls: PalbeCalls | undefined;\n let messaging: PalbeMessaging | undefined;\n let perf: PalbePerf | undefined;\n const rt: PalbeRuntime = {\n config,\n projectRef,\n http,\n tokenManager,\n authClient,\n storage,\n get auth() {\n if (!auth) auth = new PalbeAuth(rt);\n return auth;\n },\n // Same lazy pattern as `auth`: the pool is constructed (and, in the\n // browser, started) only when `pb.flags` is actually touched — a pbServer\n // request that never reads flags pays nothing.\n get flags() {\n if (!flags) flags = new PalbeFlags(rt);\n return flags;\n },\n // Lazy like `auth`/`flags` — but constructing PalbeRealtime is itself\n // inert: the WebSocket only opens on the first channel subscription/send.\n get realtime() {\n if (!realtime) realtime = new PalbeRealtime(rt);\n return realtime;\n },\n // PalbeCalls is lazy + stateless per instance (no timer/socket to destroy).\n get calls() {\n if (!calls) calls = new PalbeCalls(rt);\n return calls;\n },\n // Lazy like the others — constructing PalbeMessaging is inert (no enroll /\n // socket / WASM load until the first chat op or pb.messaging.start()).\n get messaging() {\n if (!messaging) messaging = new PalbeMessaging(rt);\n return messaging;\n },\n destroyRealtime() {\n // Runtime teardown hook (called by __configure/__reset on replace). Tear\n // down everything that holds a global listener/socket/timer so the old\n // runtime doesn't leak across a swap.\n // Only destroy if the lazy getter was already accessed (memo is set).\n // Do NOT touch `rt.realtime` here — that would construct it just to destroy.\n realtime?.destroy();\n realtime = undefined;\n // PalPerf is eager (below), so it always holds the `online` reconnect\n // listener — remove it so a __configure swap doesn't accumulate handlers.\n perf?.dispose();\n },\n // The buffering facade is lazy; its identity state is NOT (below).\n get analytics() {\n if (!analytics) analytics = new PalbeAnalytics(rt, analyticsState);\n return analytics;\n },\n // PalPerf is constructed up front (touched below): `request.ts` records a\n // network trace on EVERY fetch, so it can't be lazy. The memo here only\n // guards against re-construction.\n get perf() {\n if (!perf) perf = new PalbePerf(rt);\n return perf;\n },\n };\n\n // Analytics identity state is EAGER (cheap: listener wiring, no I/O): the\n // X-Distinct-Id interceptor must stamp EVERY request from the first one,\n // even when pb.analytics is never touched (iOS wires the resolver at\n // PalBackend init). Subscribing to auth events here constructs PalbeAuth\n // up front — acceptable, its constructor only attaches listeners.\n const analyticsState = new AnalyticsState(rt);\n http.addInterceptor((request) => {\n // Caller-supplied X-Distinct-Id wins (iOS applyDistinctId parity).\n const hasOwn = Object.keys(request.headers).some((k) => k.toLowerCase() === 'x-distinct-id');\n if (!hasOwn) request.headers['X-Distinct-Id'] = analyticsState.distinctId();\n });\n\n // Eagerly materialize the perf facade so `request.ts` can record a network\n // trace from the very first SDK fetch (the facade buffer stays inert until\n // an item is actually recorded).\n void rt.perf;\n\n // Browser-only: fetch the server-controlled perf sample rate once at init and\n // apply it to the facade's client-side sampling (remote sampling). Server\n // (no `document`) skips this — a per-request runtime must not fire an extra\n // GET per request. Fire-and-forget: a failed fetch leaves the default 100%.\n if (typeof document !== 'undefined') {\n void new PerfConfigClient().fetchConfig(rt, rt.perf);\n // A session restored from the cookie above rehydrated the TOKENS but not the\n // user object (the access-token JWT carries no emailVerified/createdAt), so\n // fetch GET /auth/user once and announce `signedIn` — the app no longer has\n // to `await pb.auth.refreshUser()` on boot. Browser-only (SSR must not fetch;\n // it renders SIGNED_OUT and the client hydrates), idempotent, non-throwing.\n rt.auth.hydrateUser();\n }\n\n // Browser-only cold-start trace. The web has no prewarm (unlike iOS), so a\n // load is always a real cold start: read First Contentful Paint relative to\n // navigation start and record ONE `app_start` item. The measurement itself is\n // the pure `measureAppStart`; this is just the untestable observer glue.\n recordColdStart(rt);\n\n // Browser-only Core Web Vitals. Records one `web_vital` perf item per settled\n // vital (LCP/CLS/INP/FCP/TTFB) through the same buffer as every perf family.\n // Best-effort — unsupported observer types are skipped, never thrown.\n observeWebVitals((item) => rt.perf.record(item));\n\n return rt;\n}\n\n/**\n * Wire the browser cold-start measurement. Records exactly one `app_start` perf\n * item once First Contentful Paint is known. No-op outside the browser (no\n * `document`/`PerformanceObserver`) or when navigation timing is unavailable.\n * Failures are swallowed — a perf hook must never break runtime construction.\n */\nfunction recordColdStart(rt: PalbeRuntime): void {\n if (typeof document === 'undefined' || typeof performance === 'undefined') return;\n try {\n const navEntry = performance.getEntriesByType('navigation')[0] as\n | PerformanceNavigationTiming\n | undefined;\n const startTime = navEntry?.startTime ?? 0;\n\n const recordFromFcp = (fcp: number): void => {\n const item = measureAppStart({ startTime, fcp });\n if (item) rt.perf.record(item);\n };\n\n // FCP may already have happened by the time the SDK is configured — read\n // the existing paint entry first.\n const existing = performance\n .getEntriesByName('first-contentful-paint')\n .find((e) => e.startTime > 0);\n if (existing) {\n recordFromFcp(existing.startTime);\n return;\n }\n\n // Otherwise observe paint entries and record on the first FCP, then stop.\n if (typeof PerformanceObserver === 'undefined') return;\n const observer = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n if (entry.name === 'first-contentful-paint') {\n observer.disconnect();\n recordFromFcp(entry.startTime);\n return;\n }\n }\n });\n observer.observe({ type: 'paint', buffered: true });\n } catch {\n // PerformanceObserver/navigation timing not supported — cold-start trace is\n // best-effort, never fatal.\n }\n}\n","import { BackendError } from './errors.js';\nimport { type CallOptions, palbeRequest } from './request.js';\nimport type { PalbeRuntime } from './runtime.js';\n\n/**\n * Runtime resolution is deferred behind `resolveRt` (global pb passes\n * `getRuntime`, bound clients pass `() => rt`) — but name validation runs\n * FIRST so an empty name fails as `validation` even before `notConfigured`.\n */\nexport async function callEndpoint<O>(\n resolveRt: () => PalbeRuntime,\n name: string,\n input?: unknown,\n options?: CallOptions,\n): Promise<O> {\n if (!name || name === '/') {\n throw new BackendError('validation', {\n code: 'invalid_endpoint_name',\n message: 'Endpoint name must be a non-empty path like \"todos/create\"',\n });\n }\n const rt = resolveRt();\n const path = name.startsWith('/') ? name : `/${name}`;\n return palbeRequest<O>(rt, 'POST', path, {\n body: input,\n headers: options?.headers,\n signal: options?.signal,\n });\n}\n","import { asPalbaseError, BackendError, fromEnvelope, fromPalbaseError } from './errors.js';\nimport type { PalbeRuntime } from './runtime.js';\nimport { applyBrowserOriginHeader } from './runtime-metadata.js';\nimport { VERSION } from './version.js';\n\nexport interface UploadProgress {\n /** Bytes sent so far. */\n sent: number;\n /**\n * Total bytes. Always > 0 when delivered — progress events whose length is\n * not computable are skipped entirely.\n */\n total: number;\n}\n\nexport interface UploadOptions {\n file: Blob;\n filename?: string;\n contentType?: string;\n /**\n * The request body sent beside the file. Folded into the single `body` part\n * storage reads and handed to the route's schema — pass a pre-encoded JSON\n * string as `body` to own the envelope yourself.\n */\n fields?: Record<string, string>;\n onProgress?: (progress: UploadProgress) => void;\n signal?: AbortSignal;\n headers?: Record<string, string>;\n /** Client-side pre-send validation (iOS parity). */\n constraints?: { maxSize?: number; allowedTypes?: string[] };\n}\n\n/** Storage's client-facing door: `POST /v1/storage/upload/{route...}`. */\nconst UPLOAD_PATH = '/v1/storage/upload';\n\nfunction abortError(): BackendError {\n return new BackendError('network', { code: 'aborted', message: 'Upload aborted' });\n}\n\nfunction checkConstraints(options: UploadOptions): void {\n const c = options.constraints;\n if (!c) return;\n if (c.maxSize !== undefined && options.file.size > c.maxSize) {\n const message = `File size ${options.file.size} exceeds max ${c.maxSize} bytes`;\n throw new BackendError('validation', {\n code: 'file_too_large',\n message,\n fields: [{ field: 'file', message }],\n });\n }\n // Empty allowedTypes = no type check (iOS contract). Validate the EFFECTIVE\n // type: a contentType override wins over file.type (it is what gets sent).\n const effType = options.contentType ?? options.file.type;\n if (c.allowedTypes && c.allowedTypes.length > 0 && !c.allowedTypes.includes(effType)) {\n const message = `File type '${effType}' is not allowed`;\n throw new BackendError('validation', {\n code: 'file_type_not_allowed',\n message,\n fields: [{ field: 'file', message }],\n });\n }\n}\n\nasync function buildHeaders(\n rt: PalbeRuntime,\n extra?: Record<string, string>,\n): Promise<Record<string, string>> {\n // Pre-flight refresh: mirrors the pattern in request.ts / HttpClient.\n if (\n rt.tokenManager.isExpired() &&\n rt.tokenManager.getRefreshToken() &&\n rt.tokenManager.refreshFunction\n ) {\n try {\n await rt.tokenManager.refreshSession();\n } catch (e) {\n const pe = asPalbaseError(e);\n const status = pe?.status ?? 0;\n if (status === 400 || status === 401 || status === 403) {\n rt.tokenManager.clearSession(); // terminal — proceed unauthenticated\n } else {\n throw pe ? fromPalbaseError(pe) : e;\n }\n }\n }\n\n // Uploads bypass @palbase/core to stream multipart bodies. Kong derives the\n // Environment identity from the API key, matching the iOS upload path.\n const headers: Record<string, string> = {\n apikey: rt.config.apiKey,\n 'X-Client-Info': `palbe-web/${VERSION}`,\n ...rt.config.headers,\n };\n const token = rt.tokenManager.getAccessToken();\n if (token) headers.Authorization = `Bearer ${token}`;\n // Case-insensitive scan (mirrors request.ts): a caller-supplied\n // `idempotency-key` in any casing must suppress generation.\n const callerHasKey = Object.keys(extra ?? {}).some((k) => k.toLowerCase() === 'idempotency-key');\n if (!callerHasKey) headers['Idempotency-Key'] = crypto.randomUUID();\n // extra spreads LAST so a caller-pinned Authorization wins (HttpClient/iOS precedence).\n const merged = { ...headers, ...extra };\n applyBrowserOriginHeader(merged);\n return merged;\n}\n\nfunction buildForm(options: UploadOptions): FormData {\n const form = new FormData();\n // The handler's own body travels as a SINGLE `body` part carrying JSON, not\n // as loose form fields: storage reads exactly `r.FormValue(\"body\")` and hands\n // that to the route's schema (`upload.go`, receiveUpload). One part per field\n // was appended here and every one of them was dropped on the other side — the\n // upload still answered 200 and the handler's `@Body` was `undefined`.\n // Same folding as the iOS SDK (BackendUpload.swift): a caller who supplies\n // `body` themselves has already encoded the envelope, so it is sent verbatim.\n const { body, ...loose } = options.fields ?? {};\n if (body !== undefined) form.append('body', body);\n else if (Object.keys(loose).length > 0) form.append('body', JSON.stringify(loose));\n const file =\n options.contentType && options.file.type !== options.contentType\n ? new Blob([options.file], { type: options.contentType })\n : options.file;\n const filename =\n options.filename ??\n (typeof File !== 'undefined' && options.file instanceof File ? options.file.name : 'file');\n form.append('file', file, filename);\n return form;\n}\n\nfunction decode<O>(status: number, text: string): O {\n let body: unknown;\n try {\n body = text === '' ? null : JSON.parse(text);\n } catch {\n if (status >= 200 && status < 300) {\n throw new BackendError('decode', {\n code: 'decode_error',\n message: 'Invalid JSON in response',\n status,\n });\n }\n body = text;\n }\n if (status < 200 || status >= 300) throw fromEnvelope(status, body);\n return body as O;\n}\n\nfunction uploadViaXHR<O>(\n url: string,\n form: FormData,\n headers: Record<string, string>,\n options: UploadOptions,\n): Promise<O> {\n return new Promise<O>((resolve, reject) => {\n // Pre-aborted signal: never send. (xhr.abort() before send() fires no\n // abort event per spec, so routing this through onabort would hang.)\n if (options.signal?.aborted) {\n reject(abortError());\n return;\n }\n const xhr = new XMLHttpRequest();\n xhr.open('POST', url);\n for (const [k, v] of Object.entries(headers)) xhr.setRequestHeader(k, v);\n xhr.upload.onprogress = (e: { lengthComputable: boolean; loaded: number; total: number }) => {\n if (!e.lengthComputable) return;\n options.onProgress?.({ sent: e.loaded, total: e.total });\n };\n const onAbort = () => xhr.abort();\n const cleanup = () => options.signal?.removeEventListener('abort', onAbort);\n xhr.onload = () => {\n cleanup();\n try {\n resolve(decode<O>(xhr.status, xhr.responseText));\n } catch (err) {\n reject(err);\n }\n };\n xhr.onerror = () => {\n cleanup();\n reject(new BackendError('network', { code: 'network_error', message: 'Upload failed' }));\n };\n xhr.onabort = () => {\n cleanup();\n reject(abortError());\n };\n options.signal?.addEventListener('abort', onAbort, { once: true });\n xhr.send(form);\n });\n}\n\nasync function uploadViaFetch<O>(\n url: string,\n form: FormData,\n headers: Record<string, string>,\n options: UploadOptions,\n): Promise<O> {\n let res: Response;\n try {\n res = await fetch(url, { method: 'POST', body: form, headers, signal: options.signal });\n } catch (e) {\n // DOMException extends Error in modern runtimes — name check covers both.\n if (e instanceof Error && e.name === 'AbortError') throw abortError();\n throw new BackendError('network', {\n code: 'network_error',\n message: e instanceof Error ? e.message : 'Upload failed',\n });\n }\n return decode<O>(res.status, await res.text());\n}\n\n/** Runtime resolution deferred behind `resolveRt` — see call.ts for the pattern. */\nexport async function uploadEndpoint<O>(\n resolveRt: () => PalbeRuntime,\n name: string,\n options: UploadOptions,\n): Promise<O> {\n const rt = resolveRt();\n checkConstraints(options); // pre-send guard — rejects before any network I/O\n // `name` is the tenant ROUTE, and the door is STORAGE's — storage authorizes\n // against the route's own `@Upload` declaration, writes the bytes, renders\n // the variants and only then calls the handler. Posting the multipart\n // straight at the route is what the runtime refuses (\"this endpoint accepts\n // uploads through storage, not directly\"), so every upload from a browser was\n // a 401 no app could act on. Mirrors the iOS path (BackendUpload.swift).\n const route = name.startsWith('/') ? name : `/${name}`;\n const url = `${rt.config.url}${UPLOAD_PATH}${route}`;\n const headers = await buildHeaders(rt, options.headers);\n const form = buildForm(options);\n const useXHR = options.onProgress !== undefined && typeof XMLHttpRequest !== 'undefined';\n return useXHR\n ? uploadViaXHR<O>(url, form, headers, options)\n : uploadViaFetch<O>(url, form, headers, options);\n}\n","import type { PalbeAnalytics } from './analytics-facade.js';\nimport type { PalbeAuth } from './auth-facade.js';\nimport { callEndpoint } from './call.js';\nimport type { PalbeCalls } from './calls/facade.js';\nimport type { PalbeFlags } from './flags-facade.js';\nimport { getRuntime } from './internal.js';\nimport type { PalbeMessaging } from './messaging/facade.js';\nimport { boundNamespaceAccessor, getNamespace, getRegistry } from './namespaces.js';\nimport type { PalbePerf } from './perf/perf-facade.js';\nimport type { PalbeRealtime } from './realtime/facade.js';\nimport type { CallOptions } from './request.js';\nimport type { PalbeRuntime } from './runtime.js';\nimport type { UploadOptions } from './upload.js';\nimport { uploadEndpoint } from './upload.js';\n\n/**\n * The Palbe surface. Codegen augments this interface with typed endpoint\n * namespaces: declare module '@palbase/web' { interface PB { rooms: ... } }\n */\nexport interface PB {\n call<O = unknown>(name: string, input?: unknown, options?: CallOptions): Promise<O>;\n upload<O = unknown>(name: string, options: UploadOptions): Promise<O>;\n readonly auth: PalbeAuth;\n readonly flags: PalbeFlags;\n readonly realtime: PalbeRealtime;\n readonly analytics: PalbeAnalytics;\n readonly calls: PalbeCalls;\n readonly messaging: PalbeMessaging;\n /** The PalPerf surface: custom traces (`startTrace`) + the buffered flush of\n * network/app-start/custom items. `pb.perf.record(...)` is also how the\n * runtime feeds the cold-start measurement in. */\n readonly perf: PalbePerf;\n /** Mark (or unmark) this client's traffic as test — flips the facade's flag\n * so every subsequent perf flush carries `X-Palbase-Test-Device: 1` (the\n * SAME header the iOS SDK sends; the server reads it → `is_test_traffic`). */\n setTestDevice(on: boolean): void;\n}\n\n/**\n * One proxy construction for BOTH client flavors:\n * - global `pb`: resolveRt = getRuntime (late-bound — a __configure swap is\n * visible to every subsequent call), nsAccessor = the globally-cached path;\n * - bound clients: resolveRt = () => rt (captured once, immune to global\n * swaps), nsAccessor = a per-client cache (see namespaces.ts).\n */\nfunction createClientProxy(\n resolveRt: () => PalbeRuntime,\n nsAccessor: (name: string) => unknown,\n): PB {\n const base = {\n call<O = unknown>(name: string, input?: unknown, options?: CallOptions): Promise<O> {\n return callEndpoint<O>(resolveRt, name, input, options);\n },\n upload<O = unknown>(name: string, options: UploadOptions): Promise<O> {\n return uploadEndpoint<O>(resolveRt, name, options);\n },\n get auth(): PalbeAuth {\n return resolveRt().auth;\n },\n get flags(): PalbeFlags {\n return resolveRt().flags;\n },\n get realtime(): PalbeRealtime {\n return resolveRt().realtime;\n },\n get analytics(): PalbeAnalytics {\n return resolveRt().analytics;\n },\n get calls(): PalbeCalls {\n return resolveRt().calls;\n },\n get messaging(): PalbeMessaging {\n return resolveRt().messaging;\n },\n get perf(): PalbePerf {\n return resolveRt().perf;\n },\n setTestDevice(on: boolean): void {\n resolveRt().perf.setTestDevice(on);\n },\n };\n return new Proxy(base, {\n get(target, prop, receiver) {\n if (prop in target) return Reflect.get(target, prop, receiver);\n // `then` must NEVER resolve to a namespace: a thenable pb would make\n // `await pb` (or Promise.resolve(pb)) hang on the proxy. Registration\n // also rejects it (see namespaces.ts) — this is defense in depth.\n if (prop === 'then') return undefined;\n if (typeof prop === 'string') {\n const ns = nsAccessor(prop);\n if (ns !== undefined) return ns;\n }\n return undefined;\n },\n has(target, prop) {\n if (prop in target) return true;\n return typeof prop === 'string' && prop !== 'then' && getRegistry()[prop] !== undefined;\n },\n }) as PB;\n}\n\nexport const pb: PB = createClientProxy(getRuntime, getNamespace);\n\n/**\n * A `PB` client permanently bound to ONE runtime — typed namespaces, `call`,\n * `upload` and `auth` all resolve `rt`, NEVER the global slot. This is the\n * per-request isolation primitive for server adapters (palbe/next pbServer):\n * two bound clients serving concurrent requests cannot leak each other's\n * sessions, and global `__configure` swaps don't touch them.\n */\nexport function createBoundClient(rt: PalbeRuntime): PB {\n return createClientProxy(() => rt, boundNamespaceAccessor(rt));\n}\n"],"mappings":"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","names":["seg","validatePathParam","orderLte","ptr","module","generateClient","generateClient","chat","utf8","PERF_EXCLUDED_PREFIX","nowMs","FLUSH_AT","FLUSH_INTERVAL_MS","nowMs","ready"]}