@calimero-network/mero-js 19.27.0 → 19.27.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/http-client/signal-utils.ts", "../src/http-client/web-client.ts", "../src/http-client/http-factory.ts", "../src/http-client/retry.ts", "../src/auth-api/auth-client.ts", "../src/auth-api/auth-factory.ts", "../src/capabilities.ts", "../src/admin-api/admin-client.ts", "../src/admin-api/admin-types.ts", "../src/admin-api/admin-factory.ts", "../src/auth/index.ts", "../src/crypto/internal.ts", "../src/request/request-sig.ts", "../src/request/caller-proof.ts", "../src/request/proof-signer.ts", "../src/rpc/index.ts", "../src/events/group.ts", "../src/events/sse.ts", "../src/events/ws.ts", "../src/ephemeral/codec.ts", "../src/ephemeral/index.ts", "../src/device-cert/device-cert.ts", "../src/account/wordlist.ts", "../src/account/account.ts", "../src/cloud/routing-proof.ts", "../src/cloud/cloud-client.ts", "../src/mero-js.ts", "../src/token-store/index.ts", "../src/warrant/warrant.ts", "../src/relay/relay-client.ts", "../src/relay/nonce-source.ts", "../src/sealed/noise.ts", "../src/sealed/sealed.ts", "../src/cloud/connect.ts", "../src/sealed/verify.ts", "../src/namespace-op/namespace-op.ts", "../src/login/login.ts", "../src/login/session.ts", "../src/nodeDiscovery.ts"],
4
- "sourcesContent": ["// Utility for combining multiple AbortSignals\nexport function combineSignals(\n signals: Array<AbortSignal | undefined>,\n): AbortSignal | undefined {\n const list = signals.filter(Boolean) as AbortSignal[];\n if (list.length === 0) return undefined;\n\n // Prefer native any(), but fall back if unavailable or it throws\n const AbortSignalAny = AbortSignal as {\n any?: (signals: AbortSignal[]) => AbortSignal;\n };\n if (typeof AbortSignalAny.any === 'function') {\n try {\n return AbortSignalAny.any(list);\n } catch {\n // Fall through to manual implementation\n }\n }\n\n const controller = new AbortController();\n const onAbort = (evt: Event) => {\n controller.abort((evt.target as AbortSignal).reason);\n for (const s of list) s.removeEventListener('abort', onAbort);\n };\n\n for (const s of list) {\n if (s.aborted) return AbortSignal.abort(s.reason);\n s.addEventListener('abort', onAbort, { once: true });\n }\n\n return controller.signal;\n}\n\n// Helper to create a timeout signal\nexport function createTimeoutSignal(timeoutMs: number): AbortSignal {\n // Use AbortSignal.timeout if available (Node 18.17+, modern browsers)\n if (typeof AbortSignal.timeout === 'function') {\n return AbortSignal.timeout(timeoutMs);\n }\n\n // Fallback for older environments\n const controller = new AbortController();\n setTimeout(() => {\n controller.abort(new DOMException('Timeout', 'TimeoutError'));\n }, timeoutMs);\n\n return controller.signal;\n}\n", "// ErrorResponse import removed as it's not used\nimport {\n HttpClient,\n Transport,\n RequestOptions,\n ResponseParser,\n} from './http-types.js';\nimport { combineSignals, createTimeoutSignal } from './signal-utils.js';\n\n/**\n * `x-auth-error` reasons that mean the whole token family is gone.\n *\n * Refresh tokens are single-use (calimero-network/core#3083): presenting a\n * consumed refresh token is treated as token theft and the server revokes the\n * family. Refreshing or retrying afterwards is pointless \u2014 the only way out is\n * a fresh login.\n */\nconst TERMINAL_AUTH_ERRORS = new Set(['token_reuse', 'token_revoked']);\n\n/**\n * Pull the node's explanation out of an error body. Core answers every handled\n * failure with `{\"error\": \"<message>\"}` (`ApiError::into_response`); the nested\n * `{\"error\": {\"message\": ...}}` form is what proxies in front of a node emit.\n * Returns undefined for a non-JSON, empty, or envelope-less body.\n */\nfunction extractErrorMessage(bodyText?: string): string | undefined {\n if (!bodyText) return undefined;\n try {\n const body = JSON.parse(bodyText) as { error?: unknown; detail?: unknown };\n const error = body?.error;\n if (typeof error === 'string' && error.trim() !== '') return error;\n const nested = (error as { message?: unknown } | null)?.message;\n if (typeof nested === 'string' && nested.trim() !== '') return nested;\n // FastAPI's spelling, which is what every Calimero Cloud route answers\n // with. Without it a manager's carefully worded refusal \u2014 \"ownership is\n // recorded, but the account is not linked\" \u2014 reached callers as a bare\n // \"HTTP 403\", and the one thing they could do about it was invisible.\n const detail = body?.detail;\n if (typeof detail === 'string' && detail.trim() !== '') return detail;\n } catch {\n // Not JSON \u2014 the status line is all we can honestly report.\n }\n return undefined;\n}\n\n// Custom error class for HTTP errors\nexport class HTTPError extends Error {\n name = 'HTTPError';\n\n constructor(\n public status: number,\n public statusText: string,\n public url: string,\n public headers: Headers,\n public bodyText?: string, // cap at ~64KB\n ) {\n const explanation = extractErrorMessage(bodyText);\n super(\n explanation\n ? `HTTP ${status} ${statusText}: ${explanation}`\n : `HTTP ${status} ${statusText}`,\n );\n }\n\n toJSON(): {\n status: number;\n statusText: string;\n url: string;\n headers: Record<string, string>;\n bodyText?: string;\n } {\n return {\n status: this.status,\n statusText: this.statusText,\n url: this.url,\n headers: headersToRecord(this.headers),\n bodyText: this.bodyText,\n };\n }\n}\n\n/**\n * Terminal authentication error: the refresh-token family was revoked (single-use\n * refresh token replayed, or the token was explicitly revoked). Never retried and\n * never refreshed \u2014 apps should catch this and force a re-login.\n *\n * Extends {@link HTTPError} so existing `instanceof HTTPError` handling keeps working.\n */\nexport class AuthRevokedError extends HTTPError {\n name = 'AuthRevokedError';\n\n constructor(\n /** Value of the `x-auth-error` header, e.g. `token_reuse` or `token_revoked`. */\n public reason: string,\n status: number,\n statusText: string,\n url: string,\n headers: Headers,\n bodyText?: string,\n ) {\n super(status, statusText, url, headers, bodyText);\n this.message = `Authentication revoked (${reason}): HTTP ${status} ${statusText}`;\n }\n}\n\n// Helper function to convert Headers to Record\nfunction headersToRecord(headers: Headers): Record<string, string> {\n const record: Record<string, string> = {};\n headers.forEach((value, key) => {\n record[key] = value;\n });\n return record;\n}\n\n// Web Standards HTTP client implementation\n/**\n * The path a node will verify a request signature against.\n *\n * `new URL` drops the query and keeps any prefix the base URL contributed,\n * which is exactly what reaches the server. The fallback covers a `url` that is\n * already relative \u2014 it cannot be parsed, and signing the whole string with its\n * query attached would be worse than signing the part before the `?`.\n */\nfunction pathForProof(url: string): string {\n try {\n return new URL(url).pathname;\n } catch {\n const query = url.indexOf('?');\n return query === -1 ? url : url.slice(0, query);\n }\n}\n\nexport class WebHttpClient implements HttpClient {\n // Cache for concurrent refresh token calls to prevent race conditions\n private refreshTokenPromise: Promise<string> | null = null;\n // Cache for concurrent onTokenRefresh calls to prevent duplicate callbacks\n private onTokenRefreshPromise: Promise<void> | null = null;\n \n constructor(private transport: Transport) {}\n\n async get<T>(path: string, init?: RequestOptions): Promise<T> {\n return this.request<T>(path, { ...init, method: 'GET' });\n }\n\n async post<T>(\n path: string,\n body?: unknown,\n init?: RequestOptions,\n ): Promise<T> {\n return this.request<T>(path, {\n ...init,\n method: 'POST',\n body: body ? JSON.stringify(body) : undefined,\n headers: {\n 'Content-Type': 'application/json',\n ...init?.headers,\n },\n });\n }\n\n async put<T>(\n path: string,\n body?: unknown,\n init?: RequestOptions,\n ): Promise<T> {\n return this.request<T>(path, {\n ...init,\n method: 'PUT',\n body: body ? JSON.stringify(body) : undefined,\n headers: {\n 'Content-Type': 'application/json',\n ...init?.headers,\n },\n });\n }\n\n async delete<T>(path: string, init?: RequestOptions): Promise<T> {\n return this.request<T>(path, { ...init, method: 'DELETE' });\n }\n\n async patch<T>(\n path: string,\n body?: unknown,\n init?: RequestOptions,\n ): Promise<T> {\n return this.request<T>(path, {\n ...init,\n method: 'PATCH',\n body: body ? JSON.stringify(body) : undefined,\n headers: {\n 'Content-Type': 'application/json',\n ...init?.headers,\n },\n });\n }\n\n async head(\n path: string,\n init?: RequestOptions,\n ): Promise<{ headers: Record<string, string>; status: number }> {\n const response = await this.makeRequest<Response>(path, {\n ...init,\n method: 'HEAD',\n parse: 'response',\n });\n return {\n headers: headersToRecord(response.headers),\n status: response.status,\n };\n }\n\n async request<T>(path: string, init?: RequestOptions): Promise<T> {\n return this.makeRequest<T>(path, init);\n }\n\n private async makeRequest<T>(\n path: string,\n init?: RequestOptions,\n retryCount = 0,\n requestStartTime?: number,\n ): Promise<T> {\n // Maximum retry attempts to prevent infinite loops\n const MAX_RETRY_ATTEMPTS = 1;\n const url = this.buildUrl(path);\n \n // Track request start time for timeout calculation (only on first attempt)\n // Use per-request start time to avoid corruption from concurrent requests\n const startTime = requestStartTime ?? Date.now();\n // Note: Tauri proxy script now handles AbortSignal, so we can use full RequestInit\n // Removed Tauri-specific minimal path - proxy script handles AbortSignal properly\n const signal = this.createAbortSignal(init);\n const headers = await this.buildHeaders(init?.headers);\n let headersObj: Record<string, string>;\n if (headers instanceof Headers) {\n headersObj = {};\n headers.forEach((value, key) => {\n headersObj[key] = value;\n });\n } else {\n headersObj = headers;\n }\n \n const requestInit: RequestInit = {\n method: init?.method || 'GET',\n headers: headersObj,\n };\n \n // Check if body is a stream (ReadableStream) that can't be reused\n // Note: Blob is reusable, so it's not included here\n const isStreamBody = init?.body instanceof ReadableStream ||\n (typeof init?.body === 'object' && init?.body !== null && 'getReader' in init.body && !(init.body instanceof Blob));\n \n if (init?.body !== undefined && !isStreamBody) {\n requestInit.body = init.body;\n } else if (init?.body !== undefined && isStreamBody && retryCount === 0) {\n // Only include stream body on first attempt - can't retry with streams\n requestInit.body = init.body;\n }\n \n // For retries, calculate remaining timeout to prevent timeout reset\n // Track elapsed time and use remaining timeout for retry\n // Note: This is calculated before the request, so it doesn't include token refresh time\n // The actual remaining timeout check happens after token refresh completes\n let retrySignal: AbortSignal | undefined;\n if (retryCount > 0 && requestStartTime !== undefined) {\n const timeoutMs = init?.timeoutMs || this.transport.timeoutMs;\n if (timeoutMs) {\n // Calculate elapsed time (will be recalculated after token refresh if needed)\n const elapsed = Date.now() - startTime;\n const remaining = Math.max(0, timeoutMs - elapsed);\n // Create signal with remaining timeout, preserving user's signal\n retrySignal = this.createAbortSignal({ ...init, timeoutMs: remaining });\n } else {\n // No timeout, just preserve user's signal\n retrySignal = this.createAbortSignal(init);\n }\n } else {\n retrySignal = signal;\n }\n \n if (retrySignal) {\n requestInit.signal = retrySignal;\n }\n \n if (this.transport.credentials !== undefined) {\n requestInit.credentials = this.transport.credentials;\n }\n \n if (init?.mode !== undefined) {\n requestInit.mode = init.mode;\n }\n if (init?.cache !== undefined) {\n requestInit.cache = init.cache;\n }\n if (init?.redirect !== undefined) {\n requestInit.redirect = init.redirect;\n }\n if (init?.referrer !== undefined) {\n requestInit.referrer = init.referrer;\n }\n if (init?.referrerPolicy !== undefined) {\n requestInit.referrerPolicy = init.referrerPolicy;\n }\n if (init?.integrity !== undefined) {\n requestInit.integrity = init.integrity;\n }\n if (init?.keepalive !== undefined) {\n requestInit.keepalive = init.keepalive;\n }\n\n // Signed here rather than in `buildHeaders`, which sees neither the method\n // nor the body \u2014 and a proof commits to both. Signed AFTER the body is\n // settled above, so what is hashed is what is sent.\n //\n // The path comes from the built URL's `pathname`, not from the `path`\n // argument: that argument may carry a query string, and may be relative to\n // a base URL that contributes its own prefix. The node verifies against the\n // path it received, so the only safe source is the URL actually being\n // fetched. A query string is deliberately not covered \u2014 core's `RequestSig`\n // signs the path alone, and signing more here would fail every request.\n if (this.transport.getProof) {\n const proof = await this.transport.getProof({\n method: requestInit.method ?? 'GET',\n path: pathForProof(url),\n body: requestInit.body,\n });\n if (proof) {\n headersObj['X-Calimero-Proof'] = proof;\n }\n }\n\n try {\n const response = await this.transport.fetch(url, requestInit);\n\n if (!response.ok) {\n const bodyText = await this.getBodyText(response);\n const httpError = new HTTPError(\n response.status,\n response.statusText,\n url,\n response.headers,\n bodyText,\n );\n\n // Handle a revoked token family (single-use refresh token replayed, or an\n // explicitly revoked token). This is terminal: refreshing would only burn\n // another token and retrying would 401 again, so clear the tokens via the\n // onAuthRevoked hook and surface a distinguishable error.\n const authError = response.headers.get('x-auth-error');\n if (\n (response.status === 401 || response.status === 403) &&\n authError &&\n TERMINAL_AUTH_ERRORS.has(authError)\n ) {\n // Any in-flight refresh is based on a now-dead family \u2014 drop the caches.\n this.refreshTokenPromise = null;\n this.onTokenRefreshPromise = null;\n\n if (this.transport.onAuthRevoked) {\n try {\n await this.transport.onAuthRevoked();\n } catch {\n // Never mask the auth error with a cleanup failure.\n }\n }\n\n throw new AuthRevokedError(\n authError,\n response.status,\n response.statusText,\n url,\n response.headers,\n bodyText,\n );\n }\n\n // Handle 401 with token_expired - attempt automatic token refresh\n const userAborted = init?.signal?.aborted === true;\n if (\n response.status === 401 &&\n this.transport.refreshToken &&\n authError === 'token_expired' &&\n retryCount < MAX_RETRY_ATTEMPTS &&\n !isStreamBody &&\n !userAborted\n ) {\n try {\n // Use cached refresh promise if one is in progress (prevents race conditions)\n let refreshPromise = this.refreshTokenPromise;\n if (!refreshPromise) {\n refreshPromise = this.transport.refreshToken();\n this.refreshTokenPromise = refreshPromise;\n }\n \n // Attempt to refresh the token\n const newToken = await refreshPromise;\n \n // Validate token - must be non-empty\n if (!newToken || newToken.trim() === '') {\n // Clear caches on error\n this.refreshTokenPromise = null;\n this.onTokenRefreshPromise = null;\n throw new Error('Refresh token returned empty token');\n }\n \n // onTokenRefresh is required when refreshToken is provided\n // Without it, the new token cannot be stored and getAuthToken() will return the old token\n if (!this.transport.onTokenRefresh) {\n // Clear caches on error\n this.refreshTokenPromise = null;\n this.onTokenRefreshPromise = null;\n throw new Error(\n 'onTokenRefresh callback is required when refreshToken is provided. ' +\n 'The callback must update the token storage so getAuthToken() returns the new token.'\n );\n }\n \n // Use cached onTokenRefresh promise if one is in progress (prevents duplicate callbacks)\n // This ensures onTokenRefresh is only called once per token refresh, even with concurrent requests\n let onTokenRefreshPromise = this.onTokenRefreshPromise;\n if (!onTokenRefreshPromise) {\n onTokenRefreshPromise = this.transport.onTokenRefresh(newToken);\n this.onTokenRefreshPromise = onTokenRefreshPromise;\n }\n \n // Update token via callback (only called once per refresh, even with concurrent requests)\n // Errors from onTokenRefresh callback should be preserved (don't mask as 401)\n // This helps developers debug token storage issues\n await onTokenRefreshPromise;\n \n // Clear caches after both refresh and callback complete\n this.refreshTokenPromise = null;\n this.onTokenRefreshPromise = null;\n \n // Check if timeout has expired during token refresh\n // If so, throw the original 401 error instead of retrying with 0ms timeout\n // This prevents timeout/abort errors that obscure the root cause (expired token)\n const timeoutMs = init?.timeoutMs || this.transport.timeoutMs;\n if (timeoutMs && requestStartTime !== undefined) {\n const elapsed = Date.now() - startTime;\n const remaining = timeoutMs - elapsed;\n if (remaining <= 0) {\n // Timeout expired during token refresh - throw original 401 error\n // This is better than retrying with 0ms timeout which would cause confusing timeout errors\n throw httpError;\n }\n }\n \n // Retry the request with the new token (increment retry count)\n // Preserve user's abort signal and start time in retry\n return this.makeRequest<T>(path, init, retryCount + 1, startTime);\n } catch (refreshError) {\n // Clear caches on error\n this.refreshTokenPromise = null;\n this.onTokenRefreshPromise = null;\n // Configuration errors (missing onTokenRefresh) should be thrown as-is\n if (refreshError instanceof Error && refreshError.message.includes('onTokenRefresh')) {\n throw refreshError;\n }\n // Errors from onTokenRefresh callback are already thrown above, so if we get here,\n // it's either a refreshToken() failure or empty token - throw original 401\n // This matches the PR description: \"If refresh fails, throws the original 401 error\"\n throw httpError;\n }\n }\n\n throw httpError;\n }\n\n return this.parseResponse<T>(response, init?.parse);\n } catch (error) {\n if (error instanceof HTTPError) {\n throw error;\n }\n // Preserve configuration errors (like missing onTokenRefresh)\n if (error instanceof Error && error.message.includes('onTokenRefresh')) {\n throw error;\n }\n throw new HTTPError(\n 0,\n 'Network Error',\n url,\n new Headers(),\n error instanceof Error ? error.message : 'Unknown error',\n );\n }\n }\n\n private buildUrl(path: string): string {\n // Handle absolute URLs\n if (path.startsWith('http://') || path.startsWith('https://')) {\n return path;\n }\n\n // Handle baseUrl with path\n const baseUrl = this.transport.baseUrl;\n if (path.startsWith('/')) {\n // If path starts with /, combine with baseUrl\n const base = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;\n return `${base}${path}`;\n } else {\n // If path doesn't start with /, append to baseUrl\n const base = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;\n return `${base}${path}`;\n }\n }\n\n private createAbortSignal(init?: RequestOptions): AbortSignal | undefined {\n const signals: AbortSignal[] = [];\n\n if (this.transport.defaultAbortSignal) {\n signals.push(this.transport.defaultAbortSignal);\n }\n\n if (init?.signal) {\n signals.push(init.signal);\n }\n\n const timeoutMs = init?.timeoutMs || this.transport.timeoutMs;\n if (timeoutMs) {\n signals.push(createTimeoutSignal(timeoutMs));\n }\n\n return signals.length > 0 ? combineSignals(signals) : undefined;\n }\n\n private async buildHeaders(\n initHeaders?: HeadersInit,\n ): Promise<Record<string, string>> {\n const headers: Record<string, string> = {\n ...this.transport.defaultHeaders,\n };\n\n // Add auth token if available and not empty\n if (this.transport.getAuthToken) {\n try {\n const token = await this.transport.getAuthToken();\n if (token && token.trim() !== '') {\n headers.Authorization = `Bearer ${token}`;\n }\n } catch (error) {\n // Ignore auth token errors\n }\n }\n\n // Add init headers\n if (initHeaders) {\n if (initHeaders instanceof Headers) {\n initHeaders.forEach((value, key) => {\n headers[key] = value;\n });\n } else if (Array.isArray(initHeaders)) {\n initHeaders.forEach(([key, value]) => {\n headers[key] = value;\n });\n } else {\n Object.assign(headers, initHeaders);\n }\n }\n\n return headers;\n }\n\n private async parseResponse<T>(\n response: Response,\n parse?: ResponseParser,\n ): Promise<T> {\n switch (parse) {\n case 'text':\n return (await response.text()) as T;\n case 'blob':\n return (await response.blob()) as T;\n case 'arrayBuffer':\n return (await response.arrayBuffer()) as T;\n case 'response':\n return response as T;\n case 'json':\n default: {\n // Tolerate empty 2xx bodies (e.g. 204 No Content from delete/resync):\n // `response.json()` throws on '', so read text and return null when empty.\n const text = await response.text();\n if (!text) return null as T;\n try {\n return JSON.parse(text) as T;\n } catch {\n // Non-JSON body on a 2xx (e.g. an HTML error page) \u2014 surface a clear\n // error with a snippet instead of a bare SyntaxError.\n throw new Error(\n `Expected a JSON response (status ${response.status}) but body was not JSON: ${text.slice(0, 200)}`,\n );\n }\n }\n }\n }\n\n private async getBodyText(response: Response): Promise<string> {\n try {\n const text = await response.text();\n return text.length > 65536 ? text.slice(0, 65536) + '...' : text;\n } catch {\n return '';\n }\n }\n}\n", "import { WebHttpClient } from './web-client.js';\nimport { Transport, HttpClient } from './http-types.js';\n\n// Factory function to create HTTP client with sensible defaults\nexport function createHttpClient(transport: Transport): HttpClient {\n return new WebHttpClient(transport);\n}\n\n// Factory function for browser environments\nexport function createBrowserHttpClient(options: {\n baseUrl: string;\n fetch?: typeof fetch;\n getAuthToken?: () => Promise<string | undefined>;\n getProof?: Transport['getProof'];\n onTokenRefresh?: (newToken: string) => Promise<void>;\n /**\n * Callback to refresh the access token when a 401 error with 'token_expired' is detected.\n * Should return the new access token, or throw an error if refresh fails.\n * If provided, the client will automatically retry the request after a successful refresh.\n */\n refreshToken?: () => Promise<string>;\n /**\n * Callback invoked when the server reports a revoked token family\n * (`x-auth-error: token_reuse` / `token_revoked`). Terminal \u2014 the request is\n * never retried; clear stored tokens and force a re-login.\n */\n onAuthRevoked?: () => Promise<void> | void;\n defaultHeaders?: Record<string, string>;\n timeoutMs?: number;\n credentials?: RequestCredentials;\n defaultAbortSignal?: AbortSignal;\n}): HttpClient {\n const transport: Transport = {\n // globalThis.fetch must be called as a method on globalThis, not through a\n // variable, or browsers throw \"Illegal invocation\".\n fetch: (url: RequestInfo | URL, init?: RequestInit) =>\n options.fetch ? options.fetch(url, init) : globalThis.fetch(url, init),\n baseUrl: options.baseUrl,\n getAuthToken: options.getAuthToken,\n getProof: options.getProof,\n onTokenRefresh: options.onTokenRefresh,\n refreshToken: options.refreshToken,\n onAuthRevoked: options.onAuthRevoked,\n defaultHeaders: options.defaultHeaders,\n timeoutMs: options.timeoutMs,\n credentials: options.credentials, // No default credentials\n defaultAbortSignal: options.defaultAbortSignal,\n };\n\n return createHttpClient(transport);\n}\n\n// Factory function for Node.js environments\nexport function createNodeHttpClient(options: {\n baseUrl: string;\n fetch?: typeof fetch; // Allow injection of undici.fetch or other fetch implementations\n getAuthToken?: () => Promise<string | undefined>;\n getProof?: Transport['getProof'];\n onTokenRefresh?: (newToken: string) => Promise<void>;\n /**\n * Callback to refresh the access token when a 401 error with 'token_expired' is detected.\n * Should return the new access token, or throw an error if refresh fails.\n * If provided, the client will automatically retry the request after a successful refresh.\n */\n refreshToken?: () => Promise<string>;\n /**\n * Callback invoked when the server reports a revoked token family\n * (`x-auth-error: token_reuse` / `token_revoked`). Terminal \u2014 the request is\n * never retried; clear stored tokens and force a re-login.\n */\n onAuthRevoked?: () => Promise<void> | void;\n defaultHeaders?: Record<string, string>;\n timeoutMs?: number;\n credentials?: RequestCredentials;\n defaultAbortSignal?: AbortSignal;\n}): HttpClient {\n // Use provided fetch or try to use global fetch (Node 18+)\n const fetchImpl = options.fetch ?? globalThis.fetch;\n\n if (!fetchImpl) {\n throw new Error(\n 'No fetch implementation available. Please provide a fetch implementation ' +\n '(e.g., undici.fetch) or use Node.js 18+ which has native fetch support.',\n );\n }\n\n // Check if we're using the default globalThis.fetch to preserve context\n // When fetchImpl is globalThis.fetch, we must call it directly, not through a variable\n // Custom fetch implementations (like undici.fetch) can be called through the variable\n const isDefaultFetch = fetchImpl === globalThis.fetch;\n\n const transport: Transport = {\n // Wrap fetch in arrow function to prevent \"Illegal invocation\" error\n // For globalThis.fetch, call it directly to preserve 'this' context\n // For custom implementations, calling through the variable is safe\n fetch: isDefaultFetch\n ? (url: RequestInfo | URL, init?: RequestInit) => globalThis.fetch(url, init)\n : (url: RequestInfo | URL, init?: RequestInit) => fetchImpl(url, init),\n baseUrl: options.baseUrl,\n getAuthToken: options.getAuthToken,\n getProof: options.getProof,\n onTokenRefresh: options.onTokenRefresh,\n refreshToken: options.refreshToken,\n onAuthRevoked: options.onAuthRevoked,\n defaultHeaders: options.defaultHeaders,\n timeoutMs: options.timeoutMs,\n credentials: options.credentials, // Node.js doesn't have default credentials\n defaultAbortSignal: options.defaultAbortSignal,\n };\n\n return createHttpClient(transport);\n}\n\n// Universal factory that works in both environments\nexport function createUniversalHttpClient(options: {\n baseUrl: string;\n fetch?: typeof fetch;\n getAuthToken?: () => Promise<string | undefined>;\n getProof?: Transport['getProof'];\n onTokenRefresh?: (newToken: string) => Promise<void>;\n /**\n * Callback to refresh the access token when a 401 error with 'token_expired' is detected.\n * Should return the new access token, or throw an error if refresh fails.\n * If provided, the client will automatically retry the request after a successful refresh.\n */\n refreshToken?: () => Promise<string>;\n /**\n * Callback invoked when the server reports a revoked token family\n * (`x-auth-error: token_reuse` / `token_revoked`). Terminal \u2014 the request is\n * never retried; clear stored tokens and force a re-login.\n */\n onAuthRevoked?: () => Promise<void> | void;\n defaultHeaders?: Record<string, string>;\n timeoutMs?: number;\n credentials?: RequestCredentials;\n defaultAbortSignal?: AbortSignal;\n}): HttpClient {\n // Try to detect environment and use appropriate factory\n if (typeof window !== 'undefined') {\n // Browser environment\n return createBrowserHttpClient(options);\n } else {\n // Node.js environment\n return createNodeHttpClient(options);\n }\n}\n", "// Retry helper for HTTP requests\nexport interface RetryOptions {\n attempts?: number;\n}\n\n// Error types for retry logic\ninterface ErrorWithName extends Error {\n name: string;\n}\n\ninterface ErrorWithStatus extends Error {\n status: number;\n}\n\ninterface ErrorWithHeaders extends Error {\n headers?: Headers;\n}\n\n// Default retry condition - retry on network errors and 5xx status codes\nfunction defaultRetryCondition(error: Error, attempt: number): boolean {\n // Don't retry on the last attempt\n if (attempt <= 0) return false;\n\n // Distinguish timeout vs. user abort:\n // - Timeout: name === 'TimeoutError' (per spec/platforms)\n // - User abort: name === 'AbortError'\n const errorWithName = error as ErrorWithName;\n const name = errorWithName?.name;\n if (name === 'TimeoutError') return true;\n if (name === 'AbortError') return false;\n\n // HTTP 5xx and 429 (including HTTPError from web-client)\n const errorWithStatus = error as ErrorWithStatus;\n if (\n 'status' in errorWithStatus &&\n typeof errorWithStatus.status === 'number'\n ) {\n const status = errorWithStatus.status;\n return status >= 500 || status === 429;\n }\n // Network TypeError (DNS/reset) is reasonably retryable\n if (name === 'TypeError') return true;\n\n return false;\n}\n\n// Calculate delay with exponential backoff and jitter\nfunction calculateDelay(attempt: number): number {\n const baseDelayMs = 250; // Base 250ms as per spec\n const delay = baseDelayMs * Math.pow(2, attempt - 1);\n\n // Add \u00B120% jitter to reduce stampedes\n const jitter = (Math.random() - 0.5) * 0.4 * delay;\n return Math.max(0, delay + jitter);\n}\n\n// Retry helper function with new signature\nexport async function withRetry<T>(\n fn: (attempt: number) => Promise<T>,\n options: RetryOptions = {},\n): Promise<T> {\n const { attempts = 3 } = options;\n\n let lastError: Error | undefined;\n\n for (let attempt = 1; attempt <= attempts; attempt++) {\n try {\n return await fn(attempt);\n } catch (error) {\n lastError = error as Error;\n\n // Check if we should retry (this handles the last attempt check)\n if (!defaultRetryCondition(lastError, attempts - attempt)) {\n throw lastError;\n }\n\n // Calculate delay\n let delayMs = calculateDelay(attempt);\n\n // Check for Retry-After header if it's an HTTP error\n const errorWithHeaders = lastError as ErrorWithHeaders;\n const hdrs = errorWithHeaders.headers;\n const retryAfter = hdrs?.get?.('Retry-After');\n if (retryAfter) {\n // If it's a number, treat as seconds\n const seconds = parseInt(retryAfter, 10);\n if (!isNaN(seconds)) {\n delayMs = Math.max(delayMs, seconds * 1000);\n } else {\n // If it's a date, calculate the difference\n const date = new Date(retryAfter);\n if (!isNaN(date.getTime())) {\n const waitTime = Math.max(0, date.getTime() - Date.now());\n // Cap wait at 60s per attempt as per spec\n delayMs = Math.max(delayMs, Math.min(waitTime, 60000));\n }\n }\n }\n\n // Wait before retrying\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n }\n }\n\n throw lastError || new Error('Retry failed without error');\n}\n\n// Helper to create a retry-enabled HTTP client method\nexport function createRetryableMethod<T extends unknown[], R>(\n method: (...args: T) => Promise<R>,\n retryOptions: RetryOptions = {},\n) {\n return async (...args: T): Promise<R> => {\n return withRetry(() => method(...args), retryOptions);\n };\n}\n", "import { HttpClient } from '../http-client/index.js';\nimport {\n // Common types\n ApiResponse,\n // Health and Status\n HealthResponse,\n IdentityResponse,\n ProvidersResponse,\n // Authentication\n TokenRequest,\n TokenResponse,\n RefreshTokenRequest,\n // Mock Token (testing)\n MockTokenRequest,\n // Token Management\n RevokeTokenRequest,\n RevokeTokenResponse,\n // Key Management\n CreateKeyRequest,\n CreateKeyResponse,\n DeleteKeyResponse,\n RootKey,\n // Client Management\n ClientKey,\n GenerateClientKeyRequest,\n DeleteClientResponse,\n // Permissions\n PermissionResponse,\n UpdateKeyPermissionsRequest,\n} from './auth-types.js';\n\nexport class AuthApiClient {\n constructor(private httpClient: HttpClient) {}\n\n // Health and Status Endpoints\n async getHealth(): Promise<HealthResponse> {\n const response =\n await this.httpClient.get<ApiResponse<HealthResponse>>('/auth/health');\n if (!response.data) {\n throw new Error('Health response data is null');\n }\n return response.data;\n }\n\n async getIdentity(): Promise<IdentityResponse> {\n const response =\n await this.httpClient.get<ApiResponse<IdentityResponse>>(\n '/admin/identity',\n );\n if (!response.data) {\n throw new Error('Identity response data is null');\n }\n return response.data;\n }\n\n async getProviders(): Promise<ProvidersResponse> {\n const response =\n await this.httpClient.get<ApiResponse<ProvidersResponse>>(\n '/auth/providers',\n );\n if (!response.data) {\n throw new Error('Providers response data is null');\n }\n return response.data;\n }\n\n // Authentication Endpoints\n async getLoginPage(): Promise<string> {\n return this.httpClient.get<string>('/auth/login', { parse: 'text' });\n }\n\n async generateTokens(request: TokenRequest): Promise<TokenResponse> {\n return this.httpClient.post<TokenResponse>('/auth/token', request);\n }\n\n async refreshToken(request: RefreshTokenRequest): Promise<TokenResponse> {\n return this.httpClient.post<TokenResponse>('/auth/refresh', request);\n }\n\n async generateMockTokens(request: MockTokenRequest): Promise<TokenResponse> {\n return this.httpClient.post<TokenResponse>('/auth/mock-token', request);\n }\n\n async validateToken(token: string): Promise<{\n valid: boolean;\n headers: Record<string, string>;\n status: number;\n }> {\n try {\n const response = await this.validateTokenGet(token);\n return {\n valid: response.status === 200,\n headers: response.headers,\n status: response.status,\n };\n } catch (error) {\n return {\n valid: false,\n headers: {},\n status: 401,\n };\n }\n }\n\n async validateTokenGet(\n token: string,\n ): Promise<{ status: number; headers: Record<string, string> }> {\n const response = await this.httpClient.head('/auth/validate', {\n headers: { Authorization: `Bearer ${token}` },\n });\n return {\n status: response.status,\n headers: response.headers,\n };\n }\n\n // Token Management Endpoints\n // NOTE: node auth status lives on AdminApiClient.isAuthed() (/admin-api/is-authed);\n // there is no /auth/is-authed on the auth service.\n async revokeTokens(\n request: RevokeTokenRequest,\n ): Promise<RevokeTokenResponse> {\n const response = await this.httpClient.post<ApiResponse<RevokeTokenResponse>>(\n '/admin/revoke',\n request,\n );\n if (!response.data) {\n throw new Error('Revoke tokens response data is null');\n }\n return response.data;\n }\n\n // Key Management Endpoints\n async listRootKeys(): Promise<RootKey[]> {\n const response =\n await this.httpClient.get<ApiResponse<RootKey[]>>('/admin/keys');\n if (!response.data) {\n throw new Error('Root keys response data is null');\n }\n return response.data;\n }\n\n async createRootKey(request: CreateKeyRequest): Promise<CreateKeyResponse> {\n const response = await this.httpClient.post<ApiResponse<CreateKeyResponse>>(\n '/admin/keys',\n request,\n );\n if (!response.data) {\n throw new Error('Create root key response data is null');\n }\n return response.data;\n }\n\n async deleteRootKey(keyId: string): Promise<DeleteKeyResponse> {\n return this.httpClient.delete<DeleteKeyResponse>(`/admin/keys/${keyId}`);\n }\n\n // Client Management Endpoints\n async listClientKeys(): Promise<ClientKey[]> {\n const response = await this.httpClient.get<ApiResponse<ClientKey[]>>(\n '/admin/keys/clients',\n );\n if (!response.data) {\n throw new Error('Client keys response data is null');\n }\n return response.data;\n }\n\n async generateClientKey(\n request: GenerateClientKeyRequest,\n ): Promise<TokenResponse> {\n return this.httpClient.post<TokenResponse>('/admin/client-key', request);\n }\n\n async deleteClientKey(\n keyId: string,\n clientId: string,\n ): Promise<DeleteClientResponse> {\n return this.httpClient.delete<DeleteClientResponse>(\n `/admin/keys/${keyId}/clients/${clientId}`,\n );\n }\n\n // Permission Management Endpoints\n async getKeyPermissions(keyId: string): Promise<PermissionResponse> {\n return this.httpClient.get<PermissionResponse>(\n `/admin/keys/${keyId}/permissions`,\n );\n }\n\n async updateKeyPermissions(\n keyId: string,\n changes: UpdateKeyPermissionsRequest,\n ): Promise<PermissionResponse> {\n // Core expects an { add, remove } delta, not a { permissions } replacement.\n return this.httpClient.put<PermissionResponse>(\n `/admin/keys/${keyId}/permissions`,\n { add: changes.add, remove: changes.remove },\n );\n }\n}\n", "import { AuthApiClient } from './auth-client.js';\nimport { AuthApiClientConfig } from './auth-types.js';\nimport { HttpClient } from '../http-client/index.js';\n\n// Mock HTTP client for testing\nclass MockHttpClient implements HttpClient {\n async get<T>(): Promise<T> {\n throw new Error(\n 'HTTP client not implemented - use createAuthApiClientFromHttpClient with a real HTTP client',\n );\n }\n async post<T>(): Promise<T> {\n throw new Error(\n 'HTTP client not implemented - use createAuthApiClientFromHttpClient with a real HTTP client',\n );\n }\n async put<T>(): Promise<T> {\n throw new Error(\n 'HTTP client not implemented - use createAuthApiClientFromHttpClient with a real HTTP client',\n );\n }\n async delete<T>(): Promise<T> {\n throw new Error(\n 'HTTP client not implemented - use createAuthApiClientFromHttpClient with a real HTTP client',\n );\n }\n async patch<T>(): Promise<T> {\n throw new Error(\n 'HTTP client not implemented - use createAuthApiClientFromHttpClient with a real HTTP client',\n );\n }\n async head(): Promise<{ headers: Record<string, string>; status: number }> {\n throw new Error(\n 'HTTP client not implemented - use createAuthApiClientFromHttpClient with a real HTTP client',\n );\n }\n async request<T>(): Promise<T> {\n throw new Error(\n 'HTTP client not implemented - use createAuthApiClientFromHttpClient with a real HTTP client',\n );\n }\n}\n\n// Factory functions for creating Auth API clients\nexport function createBrowserAuthApiClient(\n _config: AuthApiClientConfig,\n): AuthApiClient {\n const httpClient = new MockHttpClient();\n return new AuthApiClient(httpClient);\n}\n\nexport function createNodeAuthApiClient(\n _config: AuthApiClientConfig,\n): AuthApiClient {\n const httpClient = new MockHttpClient();\n return new AuthApiClient(httpClient);\n}\n\nexport function createAuthApiClient(\n _config: AuthApiClientConfig,\n): AuthApiClient {\n const httpClient = new MockHttpClient();\n return new AuthApiClient(httpClient);\n}\n\nexport function createAuthApiClientFromHttpClient(\n httpClient: HttpClient,\n _config: AuthApiClientConfig,\n): AuthApiClient {\n return new AuthApiClient(httpClient);\n}\n", "// Member capability bitmask constants \u2014 mirrors core's `MemberCapabilities`\n// (crates/context/config/src/lib.rs). The value stored per-member is a u32\n// bitmask. Core currently assigns bits 0..=9; bits 10 and above are unassigned\n// and may be claimed by future core versions, so an application MUST NOT\n// assume any particular bit is safe for its own use unless core documents it\n// as reserved for applications.\n\n/**\n * Capability bits as defined by core's `MemberCapabilities`.\n *\n * The per-member value is a u32 bitmask. Core currently assigns bits 0..=9\n * (the entries below); bits 10 and above are unassigned \u2014 do not repurpose\n * them for application data, as a future core release may claim them.\n */\nexport const CAPABILITIES = {\n CAN_CREATE_CONTEXT: 1 << 0,\n CAN_INVITE_MEMBERS: 1 << 1,\n CAN_JOIN_OPEN_SUBGROUPS: 1 << 2,\n MANAGE_MEMBERS: 1 << 3,\n MANAGE_APPLICATION: 1 << 4,\n CAN_CREATE_SUBGROUP: 1 << 5,\n CAN_DELETE_SUBGROUP: 1 << 6,\n CAN_MANAGE_VISIBILITY: 1 << 7,\n CAN_MANAGE_METADATA: 1 << 8,\n /**\n * Publish writes attributed to ANOTHER member, under a warrant that member\n * signed \u2014 the grant delegated execution runs on.\n *\n * Implied by nothing: not by membership, not by admin, and not propagated by\n * the subgroup-admit cascade. So every group is authorship-closed until an\n * admin sets this bit, and a relay without it is refused at\n * `performIntent` before anything executes. That is why it is worth naming\n * here rather than leaving callers to write `1 << 9`: a client granting it\n * has to be able to say what it is granting.\n */\n CAN_AUTHOR_ON_BEHALF: 1 << 9,\n} as const;\n\nexport type CapabilityName = keyof typeof CAPABILITIES;\nexport type CapabilityBit = (typeof CAPABILITIES)[CapabilityName];\n\n/**\n * Returns true if `mask` has every bit of `cap` set. Both operands are\n * coerced to unsigned 32-bit (`>>> 0`) before comparing so a high bit such\n * as `1 << 31` doesn't fall foul of `&` yielding a signed result.\n */\nexport function hasCap(mask: number, cap: number): boolean {\n const capU32 = cap >>> 0;\n return ((mask & capU32) >>> 0) === capU32;\n}\n\n/** Returns `mask` with every bit of `cap` set (u32-normalized). */\nexport function withCap(mask: number, cap: number): number {\n return (mask | cap) >>> 0;\n}\n\n/** Returns `mask` with every bit of `cap` cleared (u32-normalized). */\nexport function withoutCap(mask: number, cap: number): number {\n return (mask & ~cap) >>> 0;\n}\n", "import { HttpClient, withRetry } from '../http-client/index.js';\nimport { CAPABILITIES, hasCap, withCap } from '../capabilities.js';\nimport type {\n HealthStatus,\n ReadinessStatus,\n AdminAuthStatus,\n InstallApplicationRequest,\n InstallApplicationResponseData,\n InstallDevApplicationRequest,\n UninstallApplicationResponseData,\n ListApplicationsResponseData,\n GetApplicationResponseData,\n ApplicationVersionEntry,\n GetApplicationAbiResponseData,\n GetLatestVersionResponseData,\n ListPackagesResponseData,\n ListVersionsResponseData,\n RegistryBundleManifest,\n CreateContextRequest,\n CreateContextResponseData,\n DeleteContextResponseData,\n GetContextsResponseData,\n Context,\n GenerateContextIdentityResponseData,\n GetContextIdentitiesResponseData,\n JoinContextResponseData,\n JoinSubgroupInheritanceResponseData,\n ContextGroupResponseData,\n ContextStorageResponseData,\n InviteSpecializedNodeRequest,\n InviteSpecializedNodeResponseData,\n UpdateContextApplicationRequest,\n ResyncContextRequest,\n ResyncContextResponseData,\n ContextsWithExecutorsResponseData,\n UploadBlobRequest,\n UploadBlobResponseData,\n DeleteBlobResponseData,\n ListBlobsResponseData,\n GetBlobInfoResponseData,\n BlobReadOptions,\n CreateContextAliasRequest,\n CreateApplicationAliasRequest,\n CreateDeviceAliasRequest,\n CreateAliasResponseData,\n LookupAliasResponseData,\n DeleteAliasResponseData,\n ListAliasesResponseData,\n ListNamespacesResponseData,\n CreateNamespaceRequest,\n CreateNamespaceResponseData,\n DeleteNamespaceResponseData,\n CreateNamespaceInvitationRequest,\n CreateNamespaceInvitationResponseData,\n CreateRecursiveInvitationResponseData,\n JoinNamespaceRequest,\n JoinNamespaceResponseData,\n CreateGroupInNamespaceRequest,\n CreateGroupInNamespaceResponseData,\n SubgroupEntry,\n Namespace,\n NamespaceIdentity,\n NodeIdentity,\n AccountPairInitRequest,\n AccountPairInitResponseData,\n AccountPairCompleteRequest,\n AccountPairCompleteResponseData,\n AccountSignWithRootRequest,\n AccountSignWithRootResponseData,\n RelinkDeviceRequest,\n RelinkDeviceResponseData,\n RescopeDeviceRequest,\n RescopeDeviceResponseData,\n LabelDeviceRequest,\n LabelDeviceResponseData,\n AccountDeviceEntry,\n AccountApplicationEntry,\n RevokeAccountDeviceRequest,\n RevokeAccountDeviceResponseData,\n GroupInfoResponseData,\n CreateGroupRequest,\n CreateGroupResponseData,\n DeleteGroupResponseData,\n ListGroupMembersResponseData,\n ListGroupContextsResponseData,\n AddGroupMembersRequest,\n RemoveGroupMembersRequest,\n UpdateMemberRoleRequest,\n MemberCapabilities,\n SetMemberCapabilitiesRequest,\n SetDefaultCapabilitiesRequest,\n SetMemberAutoFollowRequest,\n SetSubgroupVisibilityRequest,\n SetTeeAdmissionPolicyRequest,\n GetTeeAdmissionPolicyResponseData,\n SetTeeAuthoringPolicyRequest,\n SetGroupMetadataRequest,\n SetMemberMetadataRequest,\n SetContextMetadataRequest,\n MetadataRecord,\n GetMetadataResponseData,\n SyncGroupResponseData,\n UpgradeGroupRequest,\n UpgradeGroupResponseData,\n GroupUpgradeStatusResponseData,\n MigrationStatus,\n CascadeStatusEntry,\n RetryGroupUpgradeResponseData,\n ReparentGroupRequest,\n ReparentGroupResponseData,\n IssueOwnershipProofRequest,\n IssueNamespaceOwnershipProofRequest,\n IssueOwnershipProofResponseData,\n CreateGroupInvitationRequest,\n CreateGroupInvitationResponseData,\n CreateRecursiveGroupInvitationResponseData,\n JoinGroupRequest,\n JoinGroupResponseData,\n TeeInfoResponseData,\n TeeAttestRequest,\n TeeAttestResponseData,\n TeeVerifyQuoteRequest,\n TeeVerifyQuoteResponseData,\n PerformIntentRequest,\n PerformIntentResponseData,\n IntentRelayInfo,\n QueryContextRequest,\n QueryContextResponseData,\n ListMemberDevicesOptions,\n ListMemberDevicesResponseData,\n SealToAccountRequest,\n SealedEnvelope,\n AdmitJoinRequest,\n AdmitJoinResponseData,\n} from './admin-types.js';\n\n/**\n * Helper: server wraps most responses in `{ data: T }`.\n * This extracts `.data` so callers get the inner payload directly. Null-safe so an\n * empty 2xx body (parsed as null) yields undefined instead of throwing.\n */\nfunction unwrap<T>(response: { data: T }): T {\n return response?.data as T;\n}\n\n/**\n * Compare two version strings by SemVer 2.0 precedence, ascending: negative if\n * `a < b`, positive if `a > b`, `0` if equal. Build metadata (`+...`) is stripped\n * and ignored. The numeric `major.minor.patch` core is compared field-by-field\n * (numerically when both parse, so `1.10.0 > 1.9.0`; a missing field is `0`). A\n * version WITH a pre-release ranks below the same core without one, and\n * pre-release identifiers compare per spec: dot-separated, numeric identifiers\n * numerically, alphanumeric lexically by ASCII, numeric below alphanumeric, and a\n * shorter set of identifiers loses when all preceding fields are equal.\n */\nexport function compareSemver(a: string, b: string): number {\n const [coreA, preA] = splitVersion(a);\n const [coreB, preB] = splitVersion(b);\n\n const na = coreA.split('.');\n const nb = coreB.split('.');\n const n = Math.max(na.length, nb.length);\n for (let i = 0; i < n; i++) {\n const c = compareSemverId(na[i] ?? '0', nb[i] ?? '0');\n if (c !== 0) return c;\n }\n\n // A pre-release ranks below the same core version with no pre-release.\n if (preA === undefined && preB === undefined) return 0;\n if (preA === undefined) return 1;\n if (preB === undefined) return -1;\n\n const pa = preA.split('.');\n const pb = preB.split('.');\n const m = Math.max(pa.length, pb.length);\n for (let i = 0; i < m; i++) {\n if (i >= pa.length) return -1; // shorter set loses when all preceding equal\n if (i >= pb.length) return 1;\n const c = compareSemverId(pa[i], pb[i]);\n if (c !== 0) return c;\n }\n return 0;\n}\n\n/** Drop build metadata and split the pre-release tail: `1.2.3-rc.1+b` -> `['1.2.3', 'rc.1']`. */\nfunction splitVersion(v: string): [string, string | undefined] {\n const core = v.split('+', 1)[0];\n const dash = core.indexOf('-');\n return dash === -1 ? [core, undefined] : [core.slice(0, dash), core.slice(dash + 1)];\n}\n\n/**\n * Compare one identifier: numerically when both are all-digits, else lexically by\n * ASCII with a numeric identifier ranking below an alphanumeric one. Fallback: a\n * non-numeric core component (loose/malformed input) is treated as a string, so\n * comparison never throws.\n */\nfunction compareSemverId(a: string, b: string): number {\n const na = /^\\d+$/.test(a);\n const nb = /^\\d+$/.test(b);\n if (na && nb) {\n const d = Number(a) - Number(b);\n return d < 0 ? -1 : d > 0 ? 1 : 0;\n }\n if (na) return -1;\n if (nb) return 1;\n return a < b ? -1 : a > b ? 1 : 0;\n}\n\n/**\n * Fetch a URL from the (foreign-origin) registry with a per-attempt timeout and\n * retry on transient failures. Deliberately NOT routed through the node\n * `HttpClient`: that client attaches the node's bearer token to every request,\n * which must never leak to the registry's origin. Each attempt times out after\n * ~10s; network errors and 5xx are retried (2 retries, short backoff via the\n * shared `withRetry`), while a 4xx is a definitive answer and is not retried.\n * `kind`/`context` shape the error message so failures stay debuggable in logs.\n */\nasync function fetchRegistry(url: string, kind: string, context: string): Promise<Response> {\n return withRetry(\n async () => {\n const resp = await fetch(url, { signal: AbortSignal.timeout(10000) });\n if (!resp.ok) {\n // Carry `status` so withRetry retries 5xx but leaves 4xx to throw.\n const err = new Error(\n `registry ${kind} fetch failed (${resp.status}) for ${context}`,\n ) as Error & { status: number };\n err.status = resp.status;\n throw err;\n }\n return resp;\n },\n { attempts: 3 },\n );\n}\n\nexport class AdminApiClient {\n constructor(private httpClient: HttpClient) {}\n\n // ---- Health and Status (public, no auth) ----\n\n async healthCheck(): Promise<HealthStatus> {\n return unwrap(await this.httpClient.get<{ data: HealthStatus }>('/admin-api/health'));\n }\n\n /**\n * Whether the node has finished starting and its store answers. Public, like\n * {@link healthCheck}; unlike it, a node that is up but not ready answers\n * `503`, which this surfaces as an `HTTPError` whose body names the stage.\n */\n async readinessCheck(): Promise<ReadinessStatus> {\n return unwrap(await this.httpClient.get<{ data: ReadinessStatus }>('/admin-api/ready'));\n }\n\n async isAuthed(): Promise<AdminAuthStatus> {\n return this.httpClient.get<AdminAuthStatus>('/admin-api/is-authed');\n }\n\n // ---- Application Management ----\n\n /**\n * Install a published application by its `package@version` coordinates. The\n * node resolves and fetches them from its own configured `[registry]`, so no\n * URL is passed and none is accepted. A 502 means the node's source has\n * nothing published at those coordinates. This is the discrete \"download\"\n * step an Updates flow pairs with a subsequent `upgradeGroup`.\n */\n async installApplication(request: InstallApplicationRequest): Promise<InstallApplicationResponseData> {\n return unwrap(await this.httpClient.post<{ data: InstallApplicationResponseData }>('/admin-api/install-application', request));\n }\n\n /**\n * List a package's published versions from the registry, newest-first by\n * semver. Reads the registry's V2 bundle listing\n * (`GET {registry}/api/v2/bundles?package={package}`), taking each bundle's\n * `appVersion`. Registry-side data \u2014 distinct from the node's\n * installed-version list \u2014 and the source an Updates view compares against\n * the running `Context.applicationVersion` to detect \"a new version exists\".\n */\n async getRegistryVersions(registryUrl: string, packageName: string): Promise<string[]> {\n const url = new URL('/api/v2/bundles', new URL(registryUrl).origin);\n url.searchParams.set('package', packageName);\n const resp = await fetchRegistry(url.toString(), 'versions', packageName);\n const bundles = (await resp.json()) as RegistryBundleManifest[];\n return (Array.isArray(bundles) ? bundles : [])\n .map((b) => b.appVersion)\n .filter((v): v is string => typeof v === 'string')\n .sort((a, b) => compareSemver(b, a));\n }\n\n async installDevApplication(request: InstallDevApplicationRequest): Promise<InstallApplicationResponseData> {\n return unwrap(await this.httpClient.post<{ data: InstallApplicationResponseData }>('/admin-api/install-dev-application', request));\n }\n\n async uninstallApplication(appId: string): Promise<UninstallApplicationResponseData> {\n return unwrap(await this.httpClient.delete<{ data: UninstallApplicationResponseData }>(`/admin-api/applications/${appId}`));\n }\n\n async listApplications(): Promise<ListApplicationsResponseData> {\n return unwrap(await this.httpClient.get<{ data: ListApplicationsResponseData }>('/admin-api/applications'));\n }\n\n async getApplication(appId: string): Promise<GetApplicationResponseData> {\n return unwrap(await this.httpClient.get<{ data: GetApplicationResponseData }>(`/admin-api/applications/${appId}`));\n }\n\n /**\n * Installed-blob inventory for an application \u2014 one entry per locally installed\n * version. This is the *installed* inventory (source for a \"pick a version to\n * pin\" UI); the registry equivalent is `listPackageVersions`.\n */\n async listApplicationVersions(applicationId: string): Promise<ApplicationVersionEntry[]> {\n return unwrap(\n await this.httpClient.get<{ data: ApplicationVersionEntry[] }>(`/admin-api/applications/${applicationId}/versions`),\n );\n }\n\n /**\n * The application's embedded `wasm-abi/1` manifest. Omit `serviceName` for\n * single-service apps; core 400s if the ABI is absent or the name is ambiguous.\n */\n async getApplicationAbi(applicationId: string, serviceName?: string): Promise<GetApplicationAbiResponseData> {\n const query = serviceName ? `?service_name=${encodeURIComponent(serviceName)}` : '';\n return unwrap(\n await this.httpClient.get<{ data: GetApplicationAbiResponseData }>(\n `/admin-api/applications/${applicationId}/abi${query}`,\n ),\n );\n }\n\n // ---- Package Management ----\n\n async listPackages(): Promise<ListPackagesResponseData> {\n // Core returns this flat ({ packages: [...] }), not under `data`; tolerate both.\n const r = await this.httpClient.get<ListPackagesResponseData & { data?: ListPackagesResponseData }>(\n '/admin-api/packages',\n );\n return (r?.data ?? r) as ListPackagesResponseData;\n }\n\n async listPackageVersions(packageName: string): Promise<ListVersionsResponseData> {\n // Core returns this flat ({ versions: [...] }), not under `data`; tolerate both.\n const r = await this.httpClient.get<ListVersionsResponseData & { data?: ListVersionsResponseData }>(\n `/admin-api/packages/${encodeURIComponent(packageName)}/versions`,\n );\n return (r?.data ?? r) as ListVersionsResponseData;\n }\n\n async getLatestPackageVersion(packageName: string): Promise<GetLatestVersionResponseData> {\n return this.httpClient.get<GetLatestVersionResponseData>(\n `/admin-api/packages/${encodeURIComponent(packageName)}/latest`,\n );\n }\n\n // ---- Context Management ----\n\n async createContext(request: CreateContextRequest): Promise<CreateContextResponseData> {\n // Core requires `initializationParams` (no default); default it to an empty\n // byte array so callers that pass none don't get a 400.\n const body = { ...request, initializationParams: request.initializationParams ?? [] };\n return unwrap(await this.httpClient.post<{ data: CreateContextResponseData }>('/admin-api/contexts', body));\n }\n\n async deleteContext(contextId: string): Promise<DeleteContextResponseData> {\n return unwrap(await this.httpClient.delete<{ data: DeleteContextResponseData }>(`/admin-api/contexts/${contextId}`));\n }\n\n async getContexts(): Promise<GetContextsResponseData> {\n return unwrap(await this.httpClient.get<{ data: GetContextsResponseData }>('/admin-api/contexts'));\n }\n\n async getContext(contextId: string): Promise<Context> {\n return unwrap(await this.httpClient.get<{ data: Context }>(`/admin-api/contexts/${contextId}`));\n }\n\n async getContextsForApplication(applicationId: string): Promise<GetContextsResponseData> {\n return unwrap(await this.httpClient.get<{ data: GetContextsResponseData }>(`/admin-api/contexts/for-application/${applicationId}`));\n }\n\n // ---- Context Identity ----\n\n async generateContextIdentity(): Promise<GenerateContextIdentityResponseData> {\n return unwrap(await this.httpClient.post<{ data: GenerateContextIdentityResponseData }>('/admin-api/identity/context', {}));\n }\n\n async getContextIdentities(contextId: string): Promise<GetContextIdentitiesResponseData> {\n return unwrap(await this.httpClient.get<{ data: GetContextIdentitiesResponseData }>(`/admin-api/contexts/${contextId}/identities`));\n }\n\n async getContextIdentitiesOwned(contextId: string): Promise<GetContextIdentitiesResponseData> {\n return unwrap(await this.httpClient.get<{ data: GetContextIdentitiesResponseData }>(`/admin-api/contexts/${contextId}/identities-owned`));\n }\n\n // ---- Context join (group membership) ----\n\n async joinContext(contextId: string): Promise<JoinContextResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: JoinContextResponseData }>(`/admin-api/contexts/${contextId}/join`, {}),\n );\n }\n\n // ---- Context group / storage / sync ----\n\n async getContextGroup(contextId: string): Promise<ContextGroupResponseData> {\n return unwrap(await this.httpClient.get<{ data: ContextGroupResponseData }>(`/admin-api/contexts/${contextId}/group`));\n }\n\n async getContextStorage(contextId: string): Promise<ContextStorageResponseData> {\n return unwrap(await this.httpClient.get<{ data: ContextStorageResponseData }>(`/admin-api/contexts/${contextId}/storage`));\n }\n\n /** Sync one context, or every context when `contextId` is omitted. */\n async syncContext(contextId?: string): Promise<void> {\n // Sync-all is `/contexts/sync` with no trailing slash - core routes the two\n // spellings separately and 404s the trailing one.\n const path = contextId ? `/admin-api/contexts/sync/${contextId}` : '/admin-api/contexts/sync';\n await this.httpClient.post(path, {});\n }\n\n /**\n * Kick off a full state re-pull for a context (operator recovery for a\n * stranded context). `force` re-pulls even when the node does not flag the\n * context as stranded.\n */\n async resyncContext(\n contextId: string,\n request: ResyncContextRequest = {},\n ): Promise<ResyncContextResponseData> {\n // Core's `ResyncContextApiResponse` is a flat payload (no inner `data`\n // field), so parse the body directly \u2014 do NOT `unwrap`, or `resyncStarted`\n // reads as undefined and the resync silently appears to never start.\n const r = await this.httpClient.post<ResyncContextResponseData | null>(\n `/admin-api/contexts/${contextId}/resync`,\n request,\n );\n // An empty 2xx body means the resync was accepted; synthesize the result so\n // callers always get a typed value instead of null.\n return r ?? { contextId, resyncStarted: true };\n }\n\n async inviteSpecializedNode(request: InviteSpecializedNodeRequest): Promise<InviteSpecializedNodeResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: InviteSpecializedNodeResponseData }>(\n '/admin-api/contexts/invite-specialized-node',\n request,\n ),\n );\n }\n\n async updateContextApplication(\n contextId: string,\n request: UpdateContextApplicationRequest,\n ): Promise<void> {\n await this.httpClient.post(`/admin-api/contexts/${contextId}/application`, request);\n }\n\n async getContextsWithExecutorsForApplication(applicationId: string): Promise<ContextsWithExecutorsResponseData> {\n // Core returns this flat as { contexts: [...] } (not a bare array under `data`).\n const r = await this.httpClient.get<\n | ContextsWithExecutorsResponseData\n | { contexts: ContextsWithExecutorsResponseData }\n | { data: ContextsWithExecutorsResponseData }\n >(`/admin-api/contexts/with-executors/for-application/${applicationId}`);\n if (Array.isArray(r)) return r;\n return (\n (r as { contexts?: ContextsWithExecutorsResponseData } | null)?.contexts ??\n (r as { data?: ContextsWithExecutorsResponseData } | null)?.data ??\n []\n );\n }\n\n // ---- Blob Management ----\n\n async uploadBlob(request: UploadBlobRequest): Promise<UploadBlobResponseData> {\n // Core streams the raw request body into blob storage (no JSON) and takes\n // its params from the query string (`hash`, `context_id` \u2014 snake_case).\n const params = new URLSearchParams();\n if (request.hash) params.set('hash', request.hash);\n if (request.contextId) params.set('context_id', request.contextId);\n const query = params.toString();\n const path = query ? `/admin-api/blobs?${query}` : '/admin-api/blobs';\n // request.data (Uint8Array view | ArrayBuffer | Blob) is a valid BodyInit and\n // is streamed verbatim \u2014 no JSON, no cast. fetch honors a Uint8Array view's\n // byteOffset/byteLength, so subarrays upload the correct region.\n // Core's BlobInfo is snake_case (`blob_id`); map to camelCase like deleteBlob.\n const res = unwrap(\n await this.httpClient.request<{ data: { blob_id: string; size: number } }>(path, {\n method: 'PUT',\n body: request.data,\n headers: { 'Content-Type': 'application/octet-stream' },\n }),\n );\n return { blobId: res.blob_id, size: res.size };\n }\n\n async deleteBlob(blobId: string): Promise<DeleteBlobResponseData> {\n // Core's `BlobDeleteResponse` is a flat, snake_case payload (`{ blob_id,\n // deleted }`) \u2014 the lone admin DTO without a camelCase rename and without an\n // inner `data` field. Parse directly (no `unwrap`) and map to camelCase.\n const body = await this.httpClient.delete<{ blob_id: string; deleted: boolean }>(\n `/admin-api/blobs/${blobId}`,\n );\n return { blobId: body.blob_id, deleted: body.deleted };\n }\n\n async listBlobs(): Promise<ListBlobsResponseData> {\n // Core's BlobInfo is snake_case (`blob_id`); map to camelCase.\n const res = unwrap(\n await this.httpClient.get<{ data: { blobs: Array<{ blob_id: string; size: number }> } }>(\n '/admin-api/blobs',\n ),\n );\n return { blobs: res.blobs.map((b) => ({ blobId: b.blob_id, size: b.size })) };\n }\n\n /**\n * Blob-read query params. `context_id` is the same snake_case param\n * {@link uploadBlob} announces with; building it in one place keeps the read\n * side from drifting from the write side.\n */\n private blobPath(blobId: string, options?: BlobReadOptions): string {\n if (!options?.contextId) return `/admin-api/blobs/${blobId}`;\n const params = new URLSearchParams({ context_id: options.contextId });\n return `/admin-api/blobs/${blobId}?${params.toString()}`;\n }\n\n /**\n * Download a blob's raw bytes. `GET /admin-api/blobs/:id` streams the blob\n * content (e.g. `application/gzip`), NOT JSON \u2014 so fetch it as an ArrayBuffer.\n * Use {@link listBlobs} for `{ blobId, size }` metadata.\n *\n * Without `contextId` the read is local-only: the node answers from its own\n * blob store and returns immediately, or 404s. Passing `contextId` opts into\n * network discovery \u2014 the node probes that context's peers (availability\n * nodes first) for a holder, then transfers the bytes. Budget for it: core\n * bounds discovery by a ~30s deadline, and the transfer is on top of that, so\n * the worst case is well past what a UI should block on unhinted.\n */\n async getBlob(blobId: string, options?: BlobReadOptions): Promise<ArrayBuffer> {\n return this.httpClient.get<ArrayBuffer>(this.blobPath(blobId, options), {\n parse: 'arrayBuffer',\n });\n }\n\n /**\n * Fetch a blob's metadata without downloading it. `HEAD /admin-api/blobs/:id`\n * returns the info in response headers (size via `content-length`, plus\n * `x-blob-id`/`x-blob-hash`/`x-blob-mime-type`).\n *\n * Without `contextId` this is a local-only presence check that returns\n * immediately. Passing `contextId` opts into the same network discovery\n * {@link getBlob} uses \u2014 presence and size answered by probing the context's\n * peers, with no transfer \u2014 so it inherits core's ~30s discovery deadline as\n * its worst case. A probe only carries `found` and `size`, so a peer-sourced\n * answer has no `hash`/`mimeType`; read `source` to tell the two apart.\n */\n async getBlobInfo(\n blobId: string,\n options?: BlobReadOptions,\n ): Promise<GetBlobInfoResponseData> {\n // HEAD throws (HttpError) on a non-2xx status, so we only reach here on success\n // \u2014 the x-blob-* headers are present. Guard size against a missing/non-numeric\n // content-length anyway (defaults to 0 rather than NaN).\n const { headers } = await this.httpClient.head(this.blobPath(blobId, options));\n const size = Number(headers['content-length']);\n return {\n blobId: headers['x-blob-id'] ?? blobId,\n size: Number.isFinite(size) ? size : 0,\n hash: headers['x-blob-hash'],\n mimeType: headers['x-blob-mime-type'],\n // Nodes that predate the context-aware HEAD omit this header entirely, so\n // it stays undefined rather than being defaulted to 'local' \u2014 absent means\n // \"the node didn't say\", which is not the same claim as \"served locally\".\n source: headers['x-blob-source'] as GetBlobInfoResponseData['source'],\n };\n }\n\n // ---- Alias Management ----\n\n async createContextAlias(\n request: CreateContextAliasRequest,\n ): Promise<CreateAliasResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: CreateAliasResponseData }>(\n '/admin-api/alias/create/context',\n { alias: request.alias, contextId: request.contextId },\n ),\n );\n }\n\n async createApplicationAlias(\n request: CreateApplicationAliasRequest,\n ): Promise<CreateAliasResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: CreateAliasResponseData }>(\n '/admin-api/alias/create/application',\n { alias: request.alias, applicationId: request.applicationId },\n ),\n );\n }\n\n async lookupContextAlias(name: string): Promise<LookupAliasResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: LookupAliasResponseData }>(\n `/admin-api/alias/lookup/context/${encodeURIComponent(name)}`,\n {},\n ),\n );\n }\n\n async lookupApplicationAlias(name: string): Promise<LookupAliasResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: LookupAliasResponseData }>(\n `/admin-api/alias/lookup/application/${encodeURIComponent(name)}`,\n {},\n ),\n );\n }\n\n async deleteContextAlias(name: string): Promise<DeleteAliasResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: DeleteAliasResponseData }>(\n `/admin-api/alias/delete/context/${encodeURIComponent(name)}`,\n {},\n ),\n );\n }\n\n async deleteApplicationAlias(name: string): Promise<DeleteAliasResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: DeleteAliasResponseData }>(\n `/admin-api/alias/delete/application/${encodeURIComponent(name)}`,\n {},\n ),\n );\n }\n\n async listContextAliases(): Promise<ListAliasesResponseData> {\n return unwrap(await this.httpClient.get<{ data: ListAliasesResponseData }>('/admin-api/alias/list/context'));\n }\n\n async listApplicationAliases(): Promise<ListAliasesResponseData> {\n return unwrap(await this.httpClient.get<{ data: ListAliasesResponseData }>('/admin-api/alias/list/application'));\n }\n\n async createDeviceAlias(request: CreateDeviceAliasRequest): Promise<CreateAliasResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: CreateAliasResponseData }>(\n '/admin-api/alias/create/device',\n { alias: request.alias, deviceId: request.deviceId },\n ),\n );\n }\n\n async lookupDeviceAlias(name: string): Promise<LookupAliasResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: LookupAliasResponseData }>(\n `/admin-api/alias/lookup/device/${encodeURIComponent(name)}`,\n {},\n ),\n );\n }\n\n async deleteDeviceAlias(name: string): Promise<DeleteAliasResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: DeleteAliasResponseData }>(\n `/admin-api/alias/delete/device/${encodeURIComponent(name)}`,\n {},\n ),\n );\n }\n\n async listDeviceAliases(): Promise<ListAliasesResponseData> {\n return unwrap(await this.httpClient.get<{ data: ListAliasesResponseData }>('/admin-api/alias/list/device'));\n }\n\n // ---- Namespace Management ----\n\n async listNamespaces(): Promise<ListNamespacesResponseData> {\n return unwrap(await this.httpClient.get<{ data: ListNamespacesResponseData }>('/admin-api/namespaces'));\n }\n\n async getNamespace(namespaceId: string): Promise<Namespace> {\n return unwrap(await this.httpClient.get<{ data: Namespace }>(`/admin-api/namespaces/${namespaceId}`));\n }\n\n /**\n * Who this node is \u2014 the account it writes as, the device it is, the key it\n * signs with, and the account root a second device pairs against.\n *\n * Takes no namespace, and that is the point: one root key is one account\n * everywhere, and a node signs with one key, so none of it varies by\n * namespace.\n */\n async getNodeIdentity(): Promise<NodeIdentity> {\n return unwrap(await this.httpClient.get<{ data: NodeIdentity }>('/admin-api/identity'));\n }\n\n /**\n * @deprecated Use {@link getNodeIdentity} instead. Every field this returns is\n * node-level, so the namespace was only ever decoration \u2014 it now delegates to\n * the node-level route and reshapes the answer.\n *\n * Three consequences of that delegation, all visible to a caller who relied on\n * the old route's behaviour:\n *\n * - **No participation gate.** The namespace route answered 404 when this node\n * had never joined the namespace; this answers whatever the node's identity\n * is, regardless. If you were using it to ask \"am I in this namespace?\", ask\n * {@link listNamespaces} instead \u2014 that is the question you were really\n * asking.\n * - **`namespaceId` is echoed verbatim.** The old route resolved a subgroup id\n * to its root namespace and returned that; passing a subgroup id here gives\n * the subgroup id back.\n * - **404 means something else.** Previously \"not my namespace\", now \"this node\n * holds no account root yet\".\n */\n async getNamespaceIdentity(namespaceId: string): Promise<NamespaceIdentity> {\n const node = await this.getNodeIdentity();\n return { namespaceId, publicKey: node.publicKey, account: node.accountId };\n }\n\n /**\n * Ask this node to run one method on a member's behalf, under a warrant that\n * member signed.\n *\n * The node executes and signs the envelope with its own key; the change is\n * attributed to the **author**. Both halves travel together, so every peer\n * re-checks that the member consented instead of trusting the relay.\n *\n * Three things have to be true first, and none is implied by the others:\n *\n * - the author's **account** is a member of the group owning the context \u2014\n * their device joins nothing and is in no group's binding rows, which is\n * exactly the case a device certificate covers;\n * - this node holds `CAN_AUTHOR_ON_BEHALF` on that group, which is implied by\n * neither membership nor admin;\n * - the warrant has not been spent. It is single-use: presenting a valid one\n * twice is refused, because the signature stays valid forever and replay is\n * not forgery.\n *\n * A refusal comes back as a `403` carrying the reason \u2014 \"an admin must grant\n * CAN_AUTHOR_ON_BEHALF to \u2026\", or that the nonce is already spent \u2014 so a caller\n * can tell which precondition failed rather than only that one did.\n *\n * Mint the warrant with {@link signWarrant}; see\n * {@link PerformIntentRequest.warrant}.\n */\n async performIntent(\n contextId: string,\n request: PerformIntentRequest,\n ): Promise<PerformIntentResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: PerformIntentResponseData }>(\n `/admin-api/contexts/${contextId}/intents`,\n request,\n ),\n );\n }\n\n /**\n * Read a context as the session's ACCOUNT, without minting a warrant.\n *\n * Needs an account-authenticated session \u2014 one from {@link login}, not the\n * admin password login \u2014 and refuses otherwise with a `401`. The account must\n * be a member of the group owning the context (`403` if not), and the method\n * must be declared read-only in the app's ABI (`409` if not; a write needs\n * {@link performIntent}).\n */\n async queryContext(\n contextId: string,\n request: QueryContextRequest,\n ): Promise<QueryContextResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: QueryContextResponseData }>(\n `/admin-api/contexts/${contextId}/query`,\n request,\n ),\n );\n }\n\n /**\n * Ask what this node can do for a member in `contextId`, before they sign\n * anything.\n *\n * The two facts {@link performIntent} needs and a client cannot derive: whose\n * account goes in the warrant's `executor`, and whether this node holds\n * `CAN_AUTHOR_ON_BEHALF` on the owning group. `canAuthorOnBehalf: false` comes\n * back as an answer rather than an error \u2014 it is the default state of every\n * context \u2014 so a caller can say \"an admin of `groupId` must grant it to\n * `executorAccount`\" instead of presenting a warrant that will be refused\n * after the author has spent a nonce on it.\n *\n * `404` means the context belongs to no group, or this node holds no account\n * yet; either way it can be named as no warrant's executor.\n *\n * For a caller with no credential on the node at all \u2014 a browser tab holding\n * one signing key \u2014 use `RelayClient`, which reads this same route on a node\n * run as a relay.\n */\n async getIntentRelay(contextId: string): Promise<IntentRelayInfo> {\n return unwrap(\n await this.httpClient.get<{ data: IntentRelayInfo }>(\n `/admin-api/contexts/${contextId}/intents`,\n ),\n );\n }\n\n async listNamespacesForApplication(applicationId: string): Promise<ListNamespacesResponseData> {\n return unwrap(await this.httpClient.get<{ data: ListNamespacesResponseData }>(`/admin-api/namespaces/for-application/${applicationId}`));\n }\n\n async createNamespace(request: CreateNamespaceRequest): Promise<CreateNamespaceResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: CreateNamespaceResponseData }>('/admin-api/namespaces', request),\n );\n }\n\n async deleteNamespace(namespaceId: string): Promise<DeleteNamespaceResponseData> {\n // Core requires `Content-Type: application/json` on this DELETE even though\n // the body is always empty, so send the header and an empty JSON object.\n return unwrap(\n await this.httpClient.request<{ data: DeleteNamespaceResponseData }>(`/admin-api/namespaces/${namespaceId}`, {\n method: 'DELETE',\n body: '{}',\n headers: { 'Content-Type': 'application/json' },\n }),\n );\n }\n\n async createNamespaceInvitation(\n namespaceId: string,\n request?: CreateNamespaceInvitationRequest,\n ): Promise<CreateNamespaceInvitationResponseData | CreateRecursiveInvitationResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: CreateNamespaceInvitationResponseData | CreateRecursiveInvitationResponseData }>(\n `/admin-api/namespaces/${namespaceId}/invite`,\n request ?? {},\n ),\n );\n }\n\n /**\n * Present a join this caller signed to a node the inviter named as an\n * admitter.\n *\n * For a member with no node. `joinNamespace` publishes the membership op from\n * the node making the call; this hands an already-signed op to somebody else's\n * node to publish. The distinction matters because a keyholder has nowhere to\n * publish from.\n *\n * The op must be signed by the **device key** in the credential it carries \u2014\n * every peer checks that when applying a join. So the admitter cannot author\n * this, only carry it: a hostile one can refuse, and nothing else. It cannot\n * admit a different account, change the group, or grant a role.\n *\n * @param namespaceId the namespace being joined, 64 hex\n * @param request the invitation and the hex-encoded signed op\n */\n async admitJoin(\n namespaceId: string,\n request: AdmitJoinRequest,\n ): Promise<AdmitJoinResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: AdmitJoinResponseData }>(\n `/admin-api/namespaces/${namespaceId}/admit`,\n request,\n { timeoutMs: 65000 },\n ),\n );\n }\n\n async joinNamespace(\n namespaceId: string,\n request: JoinNamespaceRequest,\n ): Promise<JoinNamespaceResponseData> {\n const data = unwrap(\n await this.httpClient.post<{ data: JoinNamespaceResponseData }>(\n `/admin-api/namespaces/${namespaceId}/join`,\n request,\n { timeoutMs: 65000 },\n ),\n );\n\n // core 0.11.0-rc.25 renamed this response's `groupId` to `namespaceId`\n // (core#3598). Bind whichever spelling the node actually sent rather than\n // picking one: the rename lands as `undefined` on the field the client\n // reads, and nothing errors \u2014 no 4xx, no thrown parse \u2014 so a client that\n // knows only one spelling fails much later and somewhere else. Normalising\n // here means one mero-js works against nodes either side of that release.\n if (data && data.namespaceId === undefined && data.groupId !== undefined) {\n return { ...data, namespaceId: data.groupId };\n }\n return data;\n }\n\n async createGroupInNamespace(\n namespaceId: string,\n request?: CreateGroupInNamespaceRequest,\n ): Promise<CreateGroupInNamespaceResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: CreateGroupInNamespaceResponseData }>(\n `/admin-api/namespaces/${namespaceId}/groups`,\n request ?? {},\n ),\n );\n }\n\n async listNamespaceGroups(namespaceId: string): Promise<SubgroupEntry[]> {\n return unwrap(await this.httpClient.get<{ data: SubgroupEntry[] }>(`/admin-api/namespaces/${namespaceId}/groups`));\n }\n\n // ---- Account Devices & Pairing ----\n\n /**\n * Mint this node a device for an account that already lives somewhere else -\n * the first half of pairing, run on the joining node.\n *\n * Publishes nothing and needs no key: it produces the device id, the two\n * public keys and the signature that the account holder's\n * {@link completeAccountPairing} certifies, plus a code to read out to them\n * over a channel the payload does not travel on. Until that second half runs,\n * the device this mints is inert.\n *\n * Takes a set of namespaces rather than one, and takes it from the caller\n * because this node cannot discover it: a node that is a member of nothing can\n * neither read the account's namespace set off a DAG nor derive it. One device\n * covers however many are named - the certificate is over the account, not a\n * scope - so there is one id and one code to hand over regardless.\n */\n async initAccountPairing(request: AccountPairInitRequest): Promise<AccountPairInitResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: AccountPairInitResponseData }>('/admin-api/account/pair-init', request),\n );\n }\n\n /**\n * Certify a device another node minted, link it, and deliver its scope keys -\n * the second half of pairing, run on the node that holds the account root.\n *\n * Every field but `applications` is copied verbatim out of that node's\n * {@link initAccountPairing} answer. Only this side can complete it: the\n * certificate is signed by the account root, which never leaves the node that\n * holds it.\n *\n * The scope is named in applications, not namespaces, because that is the\n * question a person can answer - a namespace is an implementation unit they\n * never chose. Leave it out to grant every application.\n */\n async completeAccountPairing(\n request: AccountPairCompleteRequest,\n ): Promise<AccountPairCompleteResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: AccountPairCompleteResponseData }>('/admin-api/account/pair-complete', request),\n );\n }\n\n /**\n * Re-run the pairing fan-out for a device this account already certified,\n * against the namespaces this node takes part in now.\n *\n * Pairing is a snapshot, and this is how it is repeated. A namespace created\n * or joined afterwards holds no binding for a device paired before it, and the\n * symptom is silence - the device simply never sees that namespace. Nothing\n * but the id is needed to repair it: the certificate is already held here,\n * root-signed and naming no namespace, so a fresh endorsement and a key wrap\n * are all a later namespace is missing. The device does not have to be online.\n *\n * Passing `applications` widens the device's stored scope as well as\n * repairing. Passing none - or an empty list - repairs against the scope\n * already stored, which is deliberately NOT the \"every application\" that an\n * empty list means on {@link completeAccountPairing}: overloading it here\n * would make the accidental request the widest one.\n */\n async relinkAccountDevice(\n deviceId: string,\n request?: RelinkDeviceRequest,\n ): Promise<RelinkDeviceResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: RelinkDeviceResponseData }>(\n `/admin-api/account/devices/${deviceId}/relink`,\n request ?? {},\n ),\n );\n }\n\n /**\n * Replace a device's scope outright, in one root-signed op.\n *\n * {@link relinkAccountDevice} only ever adds applications; this is how a\n * scope is narrowed - or an `all` device is given a fixed list instead. The\n * device need not be online.\n */\n async rescopeAccountDevice(\n deviceId: string,\n request: RescopeDeviceRequest,\n ): Promise<RescopeDeviceResponseData> {\n return unwrap(\n await this.httpClient.put<{ data: RescopeDeviceResponseData }>(\n `/admin-api/account/devices/${encodeURIComponent(deviceId)}/scope`,\n request,\n ),\n );\n }\n\n /**\n * Sign a payload an outside verifier specified, with this account's root.\n *\n * The counterpart to a verifier that issued a challenge and will check the\n * signature against the account id it derives \u2014 the cloud's account link and\n * login exchanges being the ones this exists for. The root never leaves the\n * node, so this is the only way a client can produce that signature.\n *\n * `domain` is a NAME from a closed set the node knows, never the domain bytes.\n * That is the security property rather than an encoding preference: signing\n * caller-supplied bytes under a caller-supplied prefix would be an oracle over\n * the one key that can certify a device. A node refuses a name it does not\n * know, and refuses one whose bytes could be extended into a credential of its\n * own.\n *\n * What comes back proves possession of the root and nothing else. Whether the\n * payload is a challenge anyone issued, whether it is fresh, and whether the\n * holder may link this account are the verifier's to decide \u2014 it issued the\n * challenge and knows who to.\n *\n * ```ts\n * const { nonce } = await cloud.getAccountLoginChallenge();\n * const { rootPublicKey, signature } = await admin.signWithAccountRoot({\n * domain: 'mdma.account-login',\n * payload: hexEncodeUtf8(nonce),\n * });\n * await cloud.submitAccountLogin({ rootPublicKey, nonce, signature });\n * ```\n */\n async signWithAccountRoot(\n request: AccountSignWithRootRequest,\n ): Promise<AccountSignWithRootResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: AccountSignWithRootResponseData }>(\n '/admin-api/account/sign-with-root',\n request,\n ),\n );\n }\n\n /**\n * Name a device of this account, for a listing to render.\n *\n * Run on the node holding the account root to name any device; a paired\n * node is accepted only for that device's own id.\n */\n async labelAccountDevice(\n deviceId: string,\n request: LabelDeviceRequest,\n ): Promise<LabelDeviceResponseData> {\n return unwrap(\n await this.httpClient.put<{ data: LabelDeviceResponseData }>(\n `/admin-api/account/devices/${encodeURIComponent(deviceId)}/label`,\n request,\n ),\n );\n }\n\n /**\n * Every device of this account, as this node sees them - its own included.\n *\n * Joined from the node-local certificate cache and the live bindings of every\n * namespace this node takes part in, so it is this node's view rather than a\n * global one: a device certified by another holder appears with an empty\n * `applications`, because the certificate that would name its scope is not\n * cached here.\n *\n * The wire wraps this in `devices` rather than the usual `data`; the array is\n * what a caller wants, so that wrapper is not passed on.\n */\n async listAccountDevices(): Promise<AccountDeviceEntry[]> {\n const response = await this.httpClient.get<{ devices: AccountDeviceEntry[] }>('/admin-api/account/devices');\n return response.devices;\n }\n\n /**\n * Every application this account speaks in, with the namespaces targeting each.\n *\n * What a device-scoping UI lists: {@link completeAccountPairing} and\n * {@link relinkAccountDevice} both take applications, and this is where the\n * ids they take come from. Derived from the namespaces this node takes part\n * in, so an application installed with no namespace yet is absent - it has\n * nothing to scope a device to until one exists.\n *\n * Wrapped in `applications` on the wire rather than `data`, unwrapped here for\n * the same reason {@link listAccountDevices} unwraps `devices`.\n */\n async listAccountApplications(): Promise<AccountApplicationEntry[]> {\n const response = await this.httpClient.get<{ applications: AccountApplicationEntry[] }>(\n '/admin-api/account/applications',\n );\n return response.applications;\n }\n\n /**\n * Withdraw a device from this account, terminally.\n *\n * Two authorities reach the same endpoint. An admin may revoke any device and\n * rotates the scope key as it goes. The account holder may revoke its own with\n * a root-signed `proof`, which does not rotate - so read\n * {@link RevokeAccountDeviceResponseData.revokedIn} rather than assuming the\n * device lost its access: a namespace reporting `keyRotated: false` has\n * stopped the device writing but leaves it able to read until an admin\n * rotates.\n *\n * `namespaceId` names where to publish from, not the extent of the revocation:\n * a device belongs to the account, so every namespace holding a binding for it\n * is withdrawn from and reported back.\n */\n async revokeAccountDevice(\n namespaceId: string,\n request: RevokeAccountDeviceRequest,\n ): Promise<RevokeAccountDeviceResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: RevokeAccountDeviceResponseData }>(\n `/admin-api/namespaces/${namespaceId}/account/revoke`,\n request,\n ),\n );\n }\n\n // ---- Group Management ----\n\n async getGroupInfo(groupId: string): Promise<GroupInfoResponseData> {\n return unwrap(await this.httpClient.get<{ data: GroupInfoResponseData }>(`/admin-api/groups/${groupId}`));\n }\n\n /** Thin wrapper over {@link getGroupInfo}: returns the group's `defaultCapabilities` bitmask. */\n async getDefaultCapabilities(groupId: string): Promise<number> {\n return (await this.getGroupInfo(groupId)).defaultCapabilities;\n }\n\n /** Thin wrapper over {@link getGroupInfo}: returns the group's `subgroupVisibility`. */\n async getSubgroupVisibility(groupId: string): Promise<string> {\n return (await this.getGroupInfo(groupId)).subgroupVisibility;\n }\n\n async deleteGroup(groupId: string): Promise<DeleteGroupResponseData> {\n // Core requires `Content-Type: application/json` on this DELETE even though\n // the body is always empty, so send the header and an empty JSON object.\n return unwrap(\n await this.httpClient.request<{ data: DeleteGroupResponseData }>(`/admin-api/groups/${groupId}`, {\n method: 'DELETE',\n body: '{}',\n headers: { 'Content-Type': 'application/json' },\n }),\n );\n }\n\n async listGroupMembers(groupId: string): Promise<ListGroupMembersResponseData> {\n const response = await this.httpClient.get<ListGroupMembersResponseData>(\n `/admin-api/groups/${groupId}/members`,\n );\n // Validate the field we declare as non-optional in the type so a\n // contract-violating response (proxy error body, future API drift,\n // etc.) surfaces as a clear error rather than silently producing an\n // empty list. Empty groups still satisfy this \u2014 merod returns\n // `members: []`, not an omitted field.\n if (!Array.isArray(response?.members)) {\n // Sanitize before interpolation: groupId reaches us from caller code,\n // not parsed input, but defending the message keeps untrusted bytes\n // out of error logs and downstream UIs.\n const safeId = String(groupId).replace(/[\\r\\n\\t\\s]/g, '').slice(0, 64);\n throw new Error(\n `Invalid listGroupMembers response for group ${safeId}: missing or non-array \\`members\\` field`,\n );\n }\n return response;\n }\n\n /**\n * Member accounts in the group and the devices bound to each. An admin node\n * sees every account; a plain member only its own. `403` if this node is not a\n * member of the group.\n */\n async listMemberDevices(\n groupId: string,\n options: ListMemberDevicesOptions = {},\n ): Promise<ListMemberDevicesResponseData> {\n const params = new URLSearchParams();\n if (options.offset !== undefined) params.set('offset', String(options.offset));\n if (options.limit !== undefined) params.set('limit', String(options.limit));\n const query = params.toString();\n const path = `/admin-api/groups/${groupId}/member-devices${query ? `?${query}` : ''}`;\n return this.httpClient.get<ListMemberDevicesResponseData>(path);\n }\n\n /**\n * Seal a small payload to a member account's root key. This node must be a\n * member of the group, and `account` must be an account the group knows, so\n * the route reaches no further than the caller already does.\n */\n async sealToAccount(\n groupId: string,\n account: string,\n request: SealToAccountRequest,\n ): Promise<SealedEnvelope> {\n return unwrap(\n await this.httpClient.post<{ data: SealedEnvelope }>(\n `/admin-api/groups/${groupId}/accounts/${account}/seal`,\n request,\n ),\n );\n }\n\n async listGroupContexts(groupId: string): Promise<ListGroupContextsResponseData> {\n return unwrap(await this.httpClient.get<{ data: ListGroupContextsResponseData }>(`/admin-api/groups/${groupId}/contexts`));\n }\n\n async addGroupMembers(groupId: string, request: AddGroupMembersRequest): Promise<void> {\n await this.httpClient.post(`/admin-api/groups/${groupId}/members`, request);\n }\n\n async removeGroupMembers(groupId: string, request: RemoveGroupMembersRequest): Promise<void> {\n await this.httpClient.post(`/admin-api/groups/${groupId}/members/remove`, request);\n }\n\n /**\n * `identity` is the member's ACCOUNT (64 hex), as returned by\n * {@link listGroupMembers} \u2014 not the signing key that added them. Both are\n * 64 hex, so nothing but the source tells them apart.\n */\n async updateMemberRole(\n groupId: string,\n identity: string,\n request: UpdateMemberRoleRequest,\n ): Promise<void> {\n await this.httpClient.put(`/admin-api/groups/${groupId}/members/${identity}/role`, request);\n }\n\n /**\n * `identity` is the member's ACCOUNT (64 hex), as returned by\n * {@link listGroupMembers} \u2014 not the signing key that added them. Both are\n * 64 hex, so nothing but the source tells them apart.\n */\n async getMemberCapabilities(groupId: string, identity: string): Promise<MemberCapabilities> {\n return unwrap(\n await this.httpClient.get<{ data: MemberCapabilities }>(\n `/admin-api/groups/${groupId}/members/${identity}/capabilities`,\n ),\n );\n }\n\n /**\n * `identity` is the member's ACCOUNT (64 hex), as returned by\n * {@link listGroupMembers} \u2014 not the signing key that added them. Both are\n * 64 hex, so nothing but the source tells them apart.\n */\n async setMemberCapabilities(\n groupId: string,\n identity: string,\n request: SetMemberCapabilitiesRequest,\n ): Promise<void> {\n await this.httpClient.put(`/admin-api/groups/${groupId}/members/${identity}/capabilities`, request);\n }\n\n async setDefaultCapabilities(\n groupId: string,\n request: SetDefaultCapabilitiesRequest,\n ): Promise<void> {\n await this.httpClient.put(`/admin-api/groups/${groupId}/settings/default-capabilities`, request);\n }\n\n /**\n * Make every member admitted to `groupId` from now on able to relay a\n * delegated write, without a per-node governance op each time.\n *\n * `CAN_AUTHOR_ON_BEHALF` is implied by nothing \u2014 not membership, not admin \u2014\n * so every group is authorship-closed and each fleet relay otherwise needs an\n * admin to grant it individually, at the moment the relay is assigned and the\n * admin is not watching. Setting it in the group's DEFAULT mask is core's own\n * answer to that: `add_member` seeds a non-admin's capability row from the\n * default, so an attested node admitted afterwards lands open.\n *\n * Call it on the NAMESPACE (the root group). A grant resolves through the\n * membership anchor, so one at the root reaches every Open subgroup beneath\n * it, which is where contexts actually live; a `Restricted` subgroup is a\n * membership boundary and needs its own call.\n *\n * ### Two things this does not do\n *\n * **It is not retroactive.** The default is copied into a member's row when\n * that member is admitted, so nodes already admitted keep the row they got.\n * Use {@link grantAuthorship} for those \u2014 or call this at namespace creation,\n * which is the only point that needs no backfill at all.\n *\n * **It does not reach admins.** Core seeds the default only for non-admin\n * roles, and `CAN_AUTHOR_ON_BEHALF` is deliberately not implied by admin. An\n * admin's own node therefore stays closed and needs {@link grantAuthorship}.\n *\n * ### What it widens\n *\n * Every future non-admin member of the group, not only attested TEE nodes, so\n * any of them may be named as a warrant's `executor`. The bound worth knowing\n * before deciding: an executor cannot FORGE. A delegated write is authorized\n * by the author's own warrant \u2014 signed by their device key, committed to one\n * context, method and exact arguments, with its nonce checked unspent \u2014 so a\n * holder can only spend a warrant it was deliberately handed. What it does\n * gain is the arguments in cleartext and the choice of when, or whether, to\n * publish.\n *\n * Read-modify-write rather than a blind set: core seeds a new group's default\n * with `CAN_JOIN_OPEN_SUBGROUPS`, and overwriting that would quietly stop\n * every later member inheriting into Open subgroups.\n *\n * Returns the resulting mask and whether an op was published \u2014 it is a no-op\n * when the bit is already set, so it is safe to call on every startup.\n */\n async openToDelegatedExecution(\n groupId: string,\n ): Promise<{ changed: boolean; defaultCapabilities: number }> {\n const current = await this.getDefaultCapabilities(groupId);\n if (hasCap(current, CAPABILITIES.CAN_AUTHOR_ON_BEHALF)) {\n return { changed: false, defaultCapabilities: current };\n }\n const next = withCap(current, CAPABILITIES.CAN_AUTHOR_ON_BEHALF);\n await this.setDefaultCapabilities(groupId, { defaultCapabilities: next });\n return { changed: true, defaultCapabilities: next };\n }\n\n /**\n * Grant `account` the authorship bit on `groupId`, leaving its other\n * capabilities alone.\n *\n * The per-member counterpart to {@link openToDelegatedExecution}, and what\n * closes the gap that method cannot: a relay admitted BEFORE the default was\n * set, and an admin's own node, which never receives the default at all.\n *\n * `account` is the member's ACCOUNT id (64 hex) as `listGroupMembers` returns\n * it \u2014 for a fleet relay, the `account` the cloud reports for that node. Not\n * the signing key that added them; both are 64 hex, so nothing but the source\n * tells them apart.\n *\n * Read-modify-write, so it adds the bit rather than replacing the member's\n * mask. A no-op when the member already holds it.\n */\n async grantAuthorship(\n groupId: string,\n account: string,\n ): Promise<{ changed: boolean; capabilities: number }> {\n const { capabilities } = await this.getMemberCapabilities(groupId, account);\n if (hasCap(capabilities, CAPABILITIES.CAN_AUTHOR_ON_BEHALF)) {\n return { changed: false, capabilities };\n }\n const next = withCap(capabilities, CAPABILITIES.CAN_AUTHOR_ON_BEHALF);\n await this.setMemberCapabilities(groupId, account, { capabilities: next });\n return { changed: true, capabilities: next };\n }\n\n async setSubgroupVisibility(\n groupId: string,\n request: SetSubgroupVisibilityRequest,\n ): Promise<void> {\n await this.httpClient.put(`/admin-api/groups/${groupId}/settings/subgroup-visibility`, request);\n }\n\n async setTeeAdmissionPolicy(\n groupId: string,\n request: SetTeeAdmissionPolicyRequest,\n ): Promise<void> {\n await this.httpClient.put(`/admin-api/groups/${groupId}/settings/tee-admission-policy`, request);\n }\n\n async getTeeAdmissionPolicy(groupId: string): Promise<GetTeeAdmissionPolicyResponseData> {\n // Intersection (not union) so the type is unambiguous: `data` is optional, so\n // `?? response` cleanly falls back to a flat (un-enveloped) response.\n const response = await this.httpClient.get<\n GetTeeAdmissionPolicyResponseData & { data?: GetTeeAdmissionPolicyResponseData }\n >(`/admin-api/groups/${groupId}/settings/tee-admission-policy`);\n return response.data ?? response;\n }\n\n /**\n * Sets which admitted TEEs may author as the TEE authority. Pass\n * `{ allowedMrtd: [] }` to turn TEE authorship off. `groupId` must be a\n * namespace root; the node refuses a subgroup. The node exposes no read-back\n * route for this policy.\n */\n async setTeeAuthoringPolicy(\n groupId: string,\n request: SetTeeAuthoringPolicyRequest,\n ): Promise<void> {\n await this.httpClient.put(`/admin-api/groups/${groupId}/settings/tee-authoring-policy`, request);\n }\n\n /**\n * Turns TEE authorship off in the namespace: every TEE loses its authority,\n * and admitted TEEs stay members. The node applies the same op as\n * `setTeeAuthoringPolicy(groupId, { allowedMrtd: [] })`. `groupId` must be a\n * namespace root.\n */\n async disableTeeAuthoringPolicy(groupId: string): Promise<void> {\n await this.httpClient.delete(`/admin-api/groups/${groupId}/settings/tee-authoring-policy`);\n }\n\n // ---- Group / member / context metadata ----\n\n async setGroupMetadata(groupId: string, request: SetGroupMetadataRequest): Promise<void> {\n await this.httpClient.put(`/admin-api/groups/${groupId}/metadata`, request);\n }\n\n async getGroupMetadata(groupId: string): Promise<MetadataRecord | null> {\n // Core single-envelopes the record: `{ data: MetadataRecord | null }`.\n // \"No record yet\" is `{ data: null }` (or a bare null body on older nodes),\n // so optional-chain to a clean null. Returns the full record (name + data +\n // updatedAt/updatedBy), not just the data map.\n const response = await this.httpClient.get<GetMetadataResponseData | null>(\n `/admin-api/groups/${groupId}/metadata`,\n );\n return response?.data ?? null;\n }\n\n async setMemberMetadata(\n groupId: string,\n identity: string,\n request: SetMemberMetadataRequest,\n ): Promise<void> {\n await this.httpClient.put(`/admin-api/groups/${groupId}/members/${identity}/metadata`, request);\n }\n\n /**\n * `identity` is the member's ACCOUNT (64 hex), as returned by\n * {@link listGroupMembers} \u2014 not the signing key that added them. Both are\n * 64 hex, so nothing but the source tells them apart.\n */\n async getMemberMetadata(groupId: string, identity: string): Promise<MetadataRecord | null> {\n // Single-enveloped record; see getGroupMetadata.\n const response = await this.httpClient.get<GetMetadataResponseData | null>(\n `/admin-api/groups/${groupId}/members/${identity}/metadata`,\n );\n return response?.data ?? null;\n }\n\n async setContextMetadata(\n groupId: string,\n contextId: string,\n request: SetContextMetadataRequest,\n ): Promise<void> {\n await this.httpClient.put(`/admin-api/groups/${groupId}/contexts/${contextId}/metadata`, request);\n }\n\n async getContextMetadata(groupId: string, contextId: string): Promise<MetadataRecord | null> {\n // Single-enveloped record; see getGroupMetadata.\n const response = await this.httpClient.get<GetMetadataResponseData | null>(\n `/admin-api/groups/${groupId}/contexts/${contextId}/metadata`,\n );\n return response?.data ?? null;\n }\n\n async syncGroup(groupId: string): Promise<SyncGroupResponseData> {\n return unwrap(await this.httpClient.post<{ data: SyncGroupResponseData }>(`/admin-api/groups/${groupId}/sync`, {}));\n }\n\n async upgradeGroup(groupId: string, request: UpgradeGroupRequest): Promise<UpgradeGroupResponseData> {\n return unwrap(await this.httpClient.post<{ data: UpgradeGroupResponseData }>(`/admin-api/groups/${groupId}/upgrade`, request));\n }\n\n async getGroupUpgradeStatus(groupId: string): Promise<GroupUpgradeStatusResponseData> {\n return unwrap(\n await this.httpClient.get<{ data: GroupUpgradeStatusResponseData }>(`/admin-api/groups/${groupId}/upgrade/status`),\n );\n }\n\n /**\n * The operator-facing \"have all peers migrated?\" rollup for a namespace.\n * The handler serializes the payload directly, so there is no `{ data }`\n * envelope to unwrap here (unlike most admin reads).\n */\n async getMigrationStatus(namespaceId: string): Promise<MigrationStatus> {\n const id = encodeURIComponent(namespaceId);\n return this.httpClient.get<MigrationStatus>(`/admin-api/groups/${id}/migration-status`);\n }\n\n /** Per-group cascade-migration snapshots for a namespace. */\n async getCascadeStatus(namespaceId: string): Promise<CascadeStatusEntry[]> {\n const id = encodeURIComponent(namespaceId);\n return unwrap(\n await this.httpClient.get<{ data: CascadeStatusEntry[] }>(`/admin-api/groups/${id}/cascade-status`),\n );\n }\n\n async retryGroupUpgrade(groupId: string): Promise<RetryGroupUpgradeResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: RetryGroupUpgradeResponseData }>(\n `/admin-api/groups/${groupId}/upgrade/retry`,\n {},\n ),\n );\n }\n\n /** Move `childGroupId` under `request.newParentId`. */\n async reparentGroup(\n childGroupId: string,\n request: ReparentGroupRequest,\n ): Promise<ReparentGroupResponseData> {\n // Core returns this flat ({ reparented }); tolerate the { data } envelope too.\n const r = await this.httpClient.post<\n ReparentGroupResponseData & { data?: ReparentGroupResponseData }\n >(`/admin-api/groups/${childGroupId}/reparent`, request);\n return (r?.data ?? r) as ReparentGroupResponseData;\n }\n\n async listSubgroups(groupId: string): Promise<SubgroupEntry[]> {\n const response = await this.httpClient.get<{ subgroups?: SubgroupEntry[]; data?: SubgroupEntry[] }>(\n `/admin-api/groups/${groupId}/subgroups`,\n );\n return response.subgroups ?? response.data ?? [];\n }\n\n async detachContextFromGroup(groupId: string, contextId: string): Promise<void> {\n await this.httpClient.post(`/admin-api/groups/${groupId}/contexts/${contextId}/remove`, {});\n }\n\n // ---- Group Invitation & Join ----\n\n async createGroupInvitation(\n groupId: string,\n request?: CreateGroupInvitationRequest,\n ): Promise<CreateGroupInvitationResponseData | CreateRecursiveGroupInvitationResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: CreateGroupInvitationResponseData | CreateRecursiveGroupInvitationResponseData }>(\n `/admin-api/groups/${groupId}/invite`,\n request ?? {},\n ),\n );\n }\n\n async joinGroup(request: JoinGroupRequest): Promise<JoinGroupResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: JoinGroupResponseData }>('/admin-api/groups/join', request),\n );\n }\n\n async joinSubgroupInheritance(groupId: string): Promise<JoinSubgroupInheritanceResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: JoinSubgroupInheritanceResponseData }>(\n `/admin-api/groups/${groupId}/join-via-inheritance`,\n {},\n ),\n );\n }\n\n // ---- TEE ----\n\n async getTeeInfo(): Promise<TeeInfoResponseData> {\n return unwrap(await this.httpClient.get<{ data: TeeInfoResponseData }>('/admin-api/tee/info'));\n }\n\n async teeAttest(request: TeeAttestRequest): Promise<TeeAttestResponseData> {\n return unwrap(await this.httpClient.post<{ data: TeeAttestResponseData }>('/admin-api/tee/attest', request));\n }\n\n /**\n * @deprecated Nodes no longer serve `/admin-api/tee/verify-quote` (removed in\n * core#3262), so this fails. Verify quotes with `createQuoteVerifier`.\n */\n async teeVerifyQuote(request: TeeVerifyQuoteRequest): Promise<TeeVerifyQuoteResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: TeeVerifyQuoteResponseData }>('/admin-api/tee/verify-quote', request),\n );\n }\n\n // ---- Network ----\n\n async getPeersCount(): Promise<{ count: number }> {\n return this.httpClient.get<{ count: number }>('/admin-api/peers');\n }\n\n /** Node network status (GET /admin-api/network/status). */\n async getNetworkStatus(): Promise<unknown> {\n return this.httpClient.get<unknown>('/admin-api/network/status');\n }\n\n /** Node storage/usage stats (GET /admin-api/usage). */\n async getUsage(): Promise<unknown> {\n return this.httpClient.get<unknown>('/admin-api/usage');\n }\n\n /** Node TLS certificate, PEM text (GET /admin-api/certificate). */\n async getCertificate(): Promise<string> {\n return this.httpClient.get<string>('/admin-api/certificate', { parse: 'text' });\n }\n\n // ---- Group / context / namespace membership ----\n\n /** Create a standalone group (POST /admin-api/groups). */\n async createGroup(request: CreateGroupRequest): Promise<CreateGroupResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: CreateGroupResponseData }>('/admin-api/groups', request),\n );\n }\n\n /** Leave a group (POST /admin-api/groups/:group_id/leave). */\n async leaveGroup(groupId: string): Promise<void> {\n await this.httpClient.post(`/admin-api/groups/${groupId}/leave`, {});\n }\n\n /** Leave a context (POST /admin-api/contexts/:context_id/leave). */\n async leaveContext(contextId: string): Promise<void> {\n await this.httpClient.post(`/admin-api/contexts/${contextId}/leave`, {});\n }\n\n /** Leave a namespace (POST /admin-api/namespaces/:namespace_id/leave). */\n async leaveNamespace(namespaceId: string): Promise<void> {\n await this.httpClient.post(`/admin-api/namespaces/${namespaceId}/leave`, {});\n }\n\n /** Issue a group ownership proof (POST /admin-api/groups/:group_id/issue-ownership-proof). */\n async issueOwnershipProof(\n groupId: string,\n request: IssueOwnershipProofRequest,\n ): Promise<IssueOwnershipProofResponseData> {\n return this.httpClient.post<IssueOwnershipProofResponseData>(\n `/admin-api/groups/${groupId}/issue-ownership-proof`,\n request,\n );\n }\n\n /** Issue a namespace ownership proof (POST /admin-api/groups/:group_id/issue-namespace-ownership-proof). */\n async issueNamespaceOwnershipProof(\n groupId: string,\n request: IssueNamespaceOwnershipProofRequest,\n ): Promise<IssueOwnershipProofResponseData> {\n return this.httpClient.post<IssueOwnershipProofResponseData>(\n `/admin-api/groups/${groupId}/issue-namespace-ownership-proof`,\n request,\n );\n }\n\n /**\n * Set a member's auto-follow flag (PUT\n * /admin-api/groups/:group_id/members/:identity/auto-follow).\n *\n * `identity` is the member's ACCOUNT (64 hex), as returned by\n * {@link listGroupMembers} \u2014 not the signing key that added them. Both are\n * 64 hex, so nothing but the source tells them apart.\n */\n async setMemberAutoFollow(\n groupId: string,\n identity: string,\n request: SetMemberAutoFollowRequest,\n ): Promise<void> {\n await this.httpClient.put(`/admin-api/groups/${groupId}/members/${identity}/auto-follow`, request);\n }\n\n /** Abort a namespace migration (POST /admin-api/groups/:namespace_id/migration/abort). */\n async abortMigration(namespaceId: string): Promise<unknown> {\n return this.httpClient.post<unknown>(`/admin-api/groups/${namespaceId}/migration/abort`, {});\n }\n}\n", "import type { DcapCollateral } from '../sealed/verify.js';\n\n// Admin API Types \u2014 aligned with core server routes\n// All types use camelCase to match core's #[serde(rename_all = \"camelCase\")]\n\n// Re-export shared types\nexport { ApiResponse } from '../http-client/index.js';\n\n// ---- Health and Status ----\n\nexport interface HealthStatus {\n status: string;\n}\n\n/**\n * What `GET /admin-api/ready` reports: `ready` once the node has finished\n * starting up and its store answers. A node that is not ready answers `503`\n * with its lifecycle stage (or `store_unavailable`) as the status, which the\n * client surfaces as an `HTTPError`.\n */\nexport interface ReadinessStatus {\n status: string;\n}\n\nexport interface AdminAuthStatus {\n data: { status: string };\n}\n\n// ---- Applications ----\n\n/**\n * Install by coordinates. The node fetches only from its own configured\n * `[registry]`, so there is no caller-supplied URL; both halves are required.\n */\nexport interface InstallApplicationRequest {\n package: string;\n version: string;\n}\n\nexport interface InstallDevApplicationRequest {\n path: string;\n}\n\nexport interface InstallApplicationResponseData {\n applicationId: string;\n}\n\nexport interface UninstallApplicationResponseData {\n applicationId: string;\n}\n\nexport interface ApplicationBlob {\n bytecode: string;\n compiled: string;\n}\n\nexport interface Application {\n id: string;\n blob: ApplicationBlob;\n size: number;\n source: string;\n metadata: number[];\n /** Absent for a raw-wasm install (no signed bundle) and for bootstrap stubs. */\n signer_id?: string;\n /** Absent on a bootstrap stub row, written before the app's blob arrives. */\n package?: string;\n /** Absent when the stored version is empty or not valid semver. */\n version?: string;\n /** Named services of a multi-service bundle; absent for single-service apps. */\n services?: Record<string, ApplicationBlob>;\n}\n\nexport interface ListApplicationsResponseData {\n apps: Application[];\n}\n\nexport interface GetApplicationResponseData {\n application: Application | null;\n}\n\n/** One installed blob for an application (distinct from the package registry). */\nexport interface ApplicationVersionEntry {\n version: string;\n blobId: string;\n size: number;\n package: string;\n}\n\nexport interface ListApplicationVersionsResponseData {\n data: ApplicationVersionEntry[];\n}\n\n/** The application's `wasm-abi/1` manifest, returned verbatim by the node. */\nexport type GetApplicationAbiResponseData = Record<string, unknown>;\n\n// ---- Packages ----\n\nexport interface GetLatestVersionResponseData {\n applicationId: string | null;\n version: string | null;\n}\n\nexport interface ListPackagesResponseData {\n packages: string[];\n}\n\nexport interface ListVersionsResponseData {\n versions: string[];\n}\n\n// ---- Bundle migration metadata ----\n\n/**\n * Per-service migration descriptor carried in a multi-service bundle manifest,\n * emitted from the app's `#[app::migrate]` declaration. `toSchemaVersion` is the\n * ABI state version the migrate targets (from `#[app::state(version = N)]`,\n * the engine's gate) \u2014 NOT the bundle semver, which is `toVersion`, the\n * user-facing string an admin matches on; `method` is the migrate entrypoint.\n */\nexport interface BundleMigration {\n method: string;\n toSchemaVersion: number;\n toVersion?: string;\n}\n\n/**\n * The subset of a registry bundle manifest that `getRegistryVersions` consumes.\n * The registry serves these at `GET {registry}/api/v2/bundles?package={package}`;\n * installation itself needs only the coordinates, which the node resolves.\n */\nexport interface RegistryBundleManifest {\n package: string;\n appVersion: string;\n /** Present when this bundle's app declares a migration. */\n migration?: BundleMigration;\n}\n\n// ---- Contexts ----\n\nexport interface CreateContextRequest {\n applicationId: string;\n groupId: string;\n serviceName?: string;\n contextSeed?: string;\n initializationParams?: number[];\n identitySecret?: string;\n /** Optional human-readable label for the context. */\n name?: string;\n}\n\nexport interface CreateContextResponseData {\n contextId: string;\n memberPublicKey: string;\n groupId?: string;\n groupCreated?: boolean;\n}\n\nexport interface DeleteContextResponseData {\n isDeleted: boolean;\n}\n\nexport interface Context {\n id: string;\n applicationId: string;\n serviceName?: string;\n /**\n * Context state/root hash. Core's wire key is `contextStateHash` (part of the\n * cross-DAG auth three-level naming: contextStateHash / groupStateHash /\n * namespaceStateHash). Renamed from `rootHash`, which never populated.\n */\n contextStateHash: string;\n /** Absent while the context has no deltas yet (core omits an empty list). */\n dagHeads?: number[][];\n /** Bundle semver of the installed application (skew #2). Absent on older nodes. */\n applicationVersion?: string;\n}\n\nexport interface ContextWithGroup extends Context {\n groupId?: string;\n}\n\nexport interface GetContextsResponseData {\n contexts: ContextWithGroup[];\n}\n\n// ---- Context Identity ----\n\nexport interface GenerateContextIdentityResponseData {\n publicKey: string;\n}\n\nexport interface GetContextIdentitiesResponseData {\n identities: string[];\n}\n\n// ---- Context join (group membership; POST /contexts/:id/join) ----\n\nexport interface JoinContextResponseData {\n contextId: string;\n memberPublicKey: string;\n}\n\n// ---- Open subgroup join via inheritance (POST /groups/:group_id/join-via-inheritance) ----\n\nexport interface JoinSubgroupInheritanceResponseData {\n groupId: string;\n memberPublicKey: string;\n // `true` if the call had to publish a `MemberJoinedOpen` op to materialise\n // inherited membership; `false` if the caller was already a direct member\n // and the call was a no-op.\n wasInherited: boolean;\n}\n\n// ---- Context group / storage / sync ----\n\nexport type ContextGroupResponseData = string | null;\n\nexport interface ContextStorageResponseData {\n sizeInBytes: number;\n}\n\n// Sync context returns empty\nexport type SyncContextResponseData = null;\n\n// ---- Specialized Node Invite ----\n\nexport interface InviteSpecializedNodeRequest {\n contextId: string;\n inviterId?: string;\n}\n\nexport interface InviteSpecializedNodeResponseData {\n nonce: string;\n}\n\n// ---- Update Context Application ----\n\nexport interface UpdateContextApplicationRequest {\n applicationId: string;\n executorPublicKey: string;\n}\n\n// Update context application returns empty\nexport type UpdateContextApplicationResponseData = Record<string, never>;\n\n// ---- Resync Context ----\n\nexport interface ResyncContextRequest {\n /** Force a full re-pull even if the context is not detected as stranded. */\n force?: boolean;\n}\n\nexport interface ResyncContextResponseData {\n contextId: string;\n resyncStarted: boolean;\n}\n\n// ---- Contexts With Executors ----\n\nexport interface ContextWithExecutors {\n contextId: string;\n executors: string[];\n}\n\nexport type ContextsWithExecutorsResponseData = ContextWithExecutors[];\n\n// ---- Blobs ----\n\nexport interface UploadBlobRequest {\n /** Raw blob bytes; streamed verbatim as the request body (octet-stream). */\n data: Uint8Array | ArrayBuffer | Blob;\n /** Optional expected blob hash; sent as the `hash` query param for server-side verification. */\n hash?: string;\n /** Optional context to announce the blob to; sent as the `context_id` query param. */\n contextId?: string;\n}\n\nexport interface BlobInfo {\n blobId: string;\n size: number;\n}\n\nexport type UploadBlobResponseData = BlobInfo;\n\nexport interface DeleteBlobResponseData {\n blobId: string;\n deleted: boolean;\n}\n\nexport interface ListBlobsResponseData {\n blobs: BlobInfo[];\n}\n\nexport type GetBlobResponseData = BlobInfo;\n\n/**\n * Optional context for a blob read. Supplying `contextId` sends the\n * `context_id` query param (the same one {@link UploadBlobRequest} announces\n * with) and opts the read into network discovery instead of a local-only\n * lookup \u2014 see {@link AdminClient.getBlob}/{@link AdminClient.getBlobInfo} for\n * the latency this buys.\n */\nexport interface BlobReadOptions {\n /** Context whose peers to probe for the blob; sent as the `context_id` query param. */\n contextId?: string;\n}\n\nexport interface GetBlobInfoResponseData extends BlobInfo {\n /** Absent when the answer came from a peer probe, which carries only presence and size. */\n hash?: string;\n /** Absent when the answer came from a peer probe, which carries only presence and size. */\n mimeType?: string;\n /**\n * Where the node answered from, per the `x-blob-source` response header:\n * `'local'` for its own blob store, `'peer'` for a context probe. Undefined\n * on nodes that don't send the header.\n */\n source?: 'local' | 'peer';\n}\n\n// ---- Aliases ----\n\n// Core's CreateAliasRequest is `{ alias, #[serde(flatten)] value }`, so each\n// alias kind flattens a different id field at the top level of the body.\nexport interface CreateContextAliasRequest {\n alias: string;\n contextId: string;\n}\n\nexport interface CreateApplicationAliasRequest {\n alias: string;\n applicationId: string;\n}\n\nexport interface CreateDeviceAliasRequest {\n alias: string;\n deviceId: string;\n}\n\n/**\n * Core's `ListAliasesResponse` is `{ data: BTreeMap<Alias<T>, T> }`, so once the\n * `data` envelope is stripped the payload is a flat `{ alias: id }` map \u2014 not a\n * list of entries. Applies to context, application, and device aliases.\n */\nexport type ListAliasesResponseData = Record<string, string>;\n\n// Create/delete alias returns empty\nexport type CreateAliasResponseData = Record<string, never>;\nexport type DeleteAliasResponseData = Record<string, never>;\n\nexport interface LookupAliasResponseData {\n value?: string;\n}\n\n// ---- Shared invitation types ----\n\n/**\n * The signed half of an invitation. Wire keys are snake_case: this type mirrors\n * a core primitive (`calimero_context_config::types`), which carries no\n * camelCase rename \u2014 unlike the admin DTOs that wrap it.\n */\nexport interface GroupInvitationFromAdmin {\n readonly inviter_identity: number[];\n readonly group_id: number[];\n readonly expiration_timestamp: number;\n /** Per-invitation nonce; core keeps the legacy `secret_salt` wire name. */\n readonly secret_salt: number[];\n readonly invited_role: number;\n /**\n * Accounts permitted to admit a claim of this invitation, 64 hex characters\n * each \u2014 the same spelling every other account-addressing field uses.\n *\n * Empty means admission by broadcast: the joiner announces itself on the\n * namespace topic and any ready peer answers \u2014 which publishes the invitation\n * to every subscriber of that topic. Naming admitters keeps it off the topic,\n * and the joiner presents it to one of them directly.\n *\n * Inside the inviter's signature, so it cannot be redirected.\n */\n readonly admitters?: readonly string[];\n}\n\n/**\n * An invitation blob the node signed. **Opaque: pass it through unchanged.**\n *\n * `inviter_signature` covers `invitation`, and the trailing bootstrap fields\n * (`application_id`, `app_key`) are needed by the joiner even though they sit\n * outside the signature \u2014 so a value that has been rebuilt field-by-field is\n * not the value the node signed. Rebuilding is what a schema derived from these\n * declarations does: an object schema drops the keys it wasn't told about, and\n * core may add more (both trailing fields were added this way). Carry the value\n * verbatim from the call that produced it to {@link JoinGroupRequest}, and treat\n * the fields below as read-only detail for display.\n *\n * The `never`-typed brand makes that structural: an object literal or a schema\n * result cannot satisfy this type, while a value the SDK returned \u2014 or one\n * recovered with `JSON.parse` after transport \u2014 still does.\n */\nexport interface SignedGroupOpenInvitation {\n readonly invitation: GroupInvitationFromAdmin;\n readonly inviter_signature: string;\n /**\n * libp2p addresses for the accounts in\n * {@link GroupInvitationFromAdmin.admitters}, each a full multiaddr including\n * its `/p2p/<peer-id>` suffix.\n *\n * The peer id travels with the address because a joiner cannot derive it:\n * resolving an account to a peer needs governance state, which is exactly what\n * a joiner has not synced yet.\n *\n * Outside the signature, like the other bootstrap fields. A wrong address\n * costs a failed dial \u2014 libp2p authenticates the peer id on connect, and the\n * admitting node still has to be in the signed list for its admission to\n * count.\n *\n * Best-effort: absent on invitations from nodes predating the field, and empty\n * when the minting node had no address for any admitter.\n */\n readonly admitter_addrs?: readonly string[];\n /**\n * The account the inviter acts as, as 64 hex characters \u2014 not the\n * `inviter_identity` key inside the signed body is written in. Governance\n * rows name accounts, and a joiner cannot derive this one: an account is a\n * hash of a root it has never seen.\n *\n * Unsigned bootstrap field, and outside the signature deliberately \u2014 a\n * client that round-trips an invitation through its own typed model would\n * drop an unknown field and invalidate the signature with it. Absent on\n * invitations from older nodes.\n */\n readonly inviter_account?: string;\n /** Unsigned bootstrap field; absent on invitations from older nodes. */\n readonly application_id?: number[];\n /** Unsigned bootstrap field; absent on invitations from older nodes. */\n /**\n * The wire name is `app_key`; core's Rust field is `bytecode_id` behind a\n * `rename` that covers both directions, and core pins this spelling in a test.\n * `Option<[u8; 32]>` crosses JSON as an array of byte values, not as hex \u2014\n * unlike `AccountId`, which has its own hex representation.\n */\n readonly app_key?: number[];\n /** @internal Brand \u2014 never present at runtime, never write it. */\n readonly __nodeSigned: never;\n}\n\nexport interface RecursiveInvitationEntry {\n groupId: string;\n invitation: SignedGroupOpenInvitation;\n groupName?: string;\n}\n\n// ---- Namespaces ----\n\nexport interface Namespace {\n namespaceId: string;\n appKey: string;\n targetApplicationId: string;\n /**\n * Absent from nodes that have dropped the concept, always `\"LazyOnAccess\"` on\n * every released one. Optional because this SDK talks to both; it carries no\n * information either way. Delete once no supported release still sends it.\n */\n upgradePolicy?: string;\n createdAt: number;\n name?: string;\n memberCount: number;\n contextCount: number;\n subgroupCount: number;\n /**\n * Bundle version of this namespace's `appKey` blob \u2014 the per-namespace truth,\n * where the application row only says \"latest fetched\". Absent when core\n * cannot resolve it (raw-wasm app, legacy key, blob not retained locally).\n */\n appVersion?: string;\n}\n\nexport type ListNamespacesResponseData = Namespace[];\n\n/**\n * Who this node is: the account it writes as, the device it is, and the key it\n * signs with.\n *\n * Every field is node-level. One root key means one account, everywhere, and a\n * node signs with one key \u2014 so none of this varies by namespace, which is why\n * the endpoint behind it takes none.\n */\n/**\n * A member's request that a node run one method on their behalf.\n *\n * Delegated authorship exists for a holder that cannot run the application\n * itself \u2014 a device with only a signing key, which can neither compile the WASM\n * nor decrypt the state. What makes the resulting write *theirs* is the warrant:\n * a statement they signed, which travels with the change so every peer verifies\n * they consented rather than taking the node's word.\n */\nexport interface PerformIntentRequest {\n /** The method to run. */\n method: string;\n /** Its arguments, as the JSON the guest will receive. */\n argsJson: unknown;\n /**\n * The author's consent: hex-encoded borsh of a `Warrant`.\n *\n * Opaque on purpose. Its canonical form is those bytes \u2014 the signature covers\n * them \u2014 so re-describing the fields as JSON would create a second spelling\n * that could disagree with what was signed.\n *\n * Mint it with {@link signWarrant}, or out of band with `merod account\n * warrant` / `meroctl context intent`.\n *\n * Signing here needs no dependency, which is why it is available at all: the\n * hash is WebCrypto SHA-256, the signature is WebCrypto Ed25519, and every\n * field of a warrant is fixed-width so the encoding is concatenation rather\n * than borsh. The byte contract is pinned on the node's side, at\n * `crates/account/src/tests/warrant_wire_fixture.rs`, because a drift there\n * would otherwise surface here as a 403 rather than as an encoding error.\n */\n warrant: string;\n /**\n * Hex-encoded borsh of the author's `AccountProof<DeviceCert>`, proving the\n * key that signed the warrant is a device of the account it names.\n *\n * Only the author's own half. The node attaches its own credential, so a\n * caller never learns which of the node's processes runs the intent \u2014 and a\n * re-key on its side does not void a warrant already issued.\n */\n authorProof: string;\n}\n\n/** What a performed intent reports back. */\n/**\n * A read of a context, run as the caller's account. Needs no warrant: a read\n * publishes nothing, so there is no peer to convince.\n */\nexport interface QueryContextRequest {\n /**\n * The method to call. It must be declared read-only in the application's ABI\n * (a `&self` method in a Rust app); anything else is refused with a `409`,\n * because a write needs a warrant ({@link PerformIntentRequest}).\n */\n method: string;\n /** Its arguments, as the JSON the guest will receive. */\n argsJson: unknown;\n}\n\nexport interface QueryContextResponseData {\n /** The method's own return value. */\n returns: unknown | null;\n}\n\nexport interface PerformIntentResponseData {\n /**\n * The context's scope root after the run \u2014 how a caller sees that it wrote.\n *\n * Worth asserting on rather than the status alone: an accepted intent that\n * advanced no state is a real failure mode, and it is cheaper to catch here\n * than on a later read of a value that was never written.\n */\n rootHash: string;\n /** The method's own return value, if it had one. */\n returns: unknown | null;\n}\n\n/**\n * What a node says about running intents in one context \u2014 read before minting a\n * warrant, not after being refused one.\n *\n * Every input to a warrant is something the author already holds except two:\n * whose name goes in `executor`, and whether that node may act here at all. Both\n * belong to the node, so both are answered here, on the path the intent will be\n * presented to.\n *\n * Reading it first is what lets a client fail before signing. A warrant spends a\n * nonce from a monotonic per-device sequence, and one naming the wrong executor\n * is unspendable \u2014 the number is gone and the write never happened.\n */\nexport interface IntentRelayInfo {\n /**\n * The account a warrant for this node must name as its `executor`, 64 hex.\n *\n * An account, not the node's signing key: one of its processes re-keying must\n * not void warrants already issued to it.\n */\n executorAccount: string;\n /**\n * Whether this node holds `CAN_AUTHOR_ON_BEHALF` on the group owning the\n * context.\n *\n * `false` is an answer, not an error, and it is the *default* state of every\n * context \u2014 the capability is implied by neither membership nor admin, and is\n * not propagated by the subgroup cascade. A client reads it to tell \"ask an\n * admin of `groupId`\" from \"retry later\".\n */\n canAuthorOnBehalf: boolean;\n /** The group whose admin must grant that capability, 64 hex. */\n groupId: string;\n}\n\nexport interface NodeIdentity {\n /**\n * The account this node writes as, 64 hex characters. This \u2014 not\n * `publicKey` \u2014 is what member-addressing endpoints take, and what\n * `listGroupMembers` entries are keyed by, so it is how a caller locates\n * itself in a member list.\n */\n accountId: string;\n /**\n * The device this node is, 64 hex characters, or `null` before it has\n * enrolled anywhere.\n *\n * `null` is a real answer rather than a missing one: the account id above\n * exists as soon as the node has a root, and enrolment \u2014 which a join\n * performs \u2014 is what adds the device that speaks for it.\n */\n deviceId: string | null;\n /**\n * The key this node signs ops with, 64 hex.\n *\n * The device's signing key, not the account root: the root signs\n * certificates and never an op, so a signature on the wire verifies against\n * this one.\n */\n publicKey: string;\n /**\n * The epoch-0 root **public** key of this node's account, 64 hex characters.\n *\n * What a second device needs to pair into this account, and public by\n * construction \u2014 it is hashed into `accountId` and travels in every genesis.\n * The private root never leaves the node over HTTP.\n *\n * Optional because a node at or below `0.11.0-rc.22` does not send it: the\n * field landed after that release. Required in the type would make this SDK\n * claim something the node the caller chose may not report.\n */\n accountRootPublicKey?: string;\n /**\n * Whether this node holds the root key of the account it speaks for. `false`\n * means it runs on a delegate device key, so it cannot certify another device\n * into the account \u2014 which is what a pairing invite needs to know before\n * offering the option.\n *\n * Optional for the same reason as `accountRootPublicKey`: a node at or below\n * `0.11.0-rc.30` does not send it. `undefined` is therefore \"this node is too\n * old to say\", which is not the same answer as `false`, and a caller gating an\n * invite on it should not collapse the two.\n */\n holdsAccountRoot?: boolean;\n /**\n * Whether this node's device is certified into the account it speaks for.\n *\n * Pair-init mints a device but only pair-complete certifies it, and\n * `holdsAccountRoot` is `false` across both states, so this is what tells\n * them apart. Absent on a node predating the field.\n */\n deviceCertified?: boolean;\n /**\n * Hex X25519 public agreement key of this node's device, `null` before it\n * has a device row.\n *\n * The same key pairing calls `kemPublicKey`; core's name is kept here.\n */\n deviceAgreementKey?: string | null;\n /**\n * Hex id of the account namespace this node follows.\n *\n * A holder derives it before the namespace exists so an invite can carry\n * it; a device records it at pair-init. `null` on a node that neither holds\n * a root nor has paired with the id; absent on an old node.\n */\n accountNamespaceId?: string | null;\n /**\n * The account that withdrew this node's device, absent on a node no\n * revocation has reached.\n */\n revokedFrom?: RevokedFromEntry;\n}\n\n/** Which account withdrew this node's device, and which device it was. */\nexport interface RevokedFromEntry {\n /** The account the device spoke for, 64 hex characters. */\n accountId: string;\n /** The device that was withdrawn, 64 hex characters. */\n deviceId: string;\n}\n\n/**\n * @deprecated Use {@link AdminApiClient.getNodeIdentity} instead. Nothing here\n * varies by namespace, so the parameter could only ever be decoration.\n */\nexport interface NamespaceIdentity {\n /** Echoed back from the argument; it does not describe the identity. */\n namespaceId: string;\n /** The key this node signs with, 64 hex. */\n publicKey: string;\n /**\n * The account this node writes as, 64 hex characters. This \u2014 not\n * `publicKey` \u2014 is what member-addressing endpoints take, and what\n * `listGroupMembers` entries are keyed by, so it is how a caller locates\n * itself in a member list.\n */\n account: string;\n}\n\nexport interface CreateNamespaceRequest {\n applicationId: string;\n name?: string;\n /** Hex 32-byte blob id; pins the namespace to a specific installed version. */\n appKey?: string;\n}\n\nexport interface CreateNamespaceResponseData {\n namespaceId: string;\n}\n\nexport interface DeleteNamespaceResponseData {\n isDeleted: boolean;\n}\n\nexport interface CreateNamespaceInvitationRequest {\n expirationTimestamp?: number;\n recursive?: boolean;\n}\n\nexport interface CreateNamespaceInvitationResponseData {\n invitation: SignedGroupOpenInvitation;\n groupName?: string;\n}\n\nexport interface CreateRecursiveInvitationResponseData {\n invitations: RecursiveInvitationEntry[];\n}\n\nexport interface JoinNamespaceRequest {\n invitation: SignedGroupOpenInvitation;\n groupName?: string;\n}\n\nexport interface JoinNamespaceResponseData {\n /**\n * The namespace that was joined.\n *\n * core `0.11.0-rc.25` renamed this field on the wire from `groupId`\n * (core#3598). The endpoint had been sharing a response DTO with\n * `POST /admin-api/groups/join`, which leaked the internal \"a namespace is a\n * root group\" detail into the namespace API and meant a caller could not use\n * one spelling across the family.\n *\n * {@link AdminClient.joinNamespace} fills this in from whichever spelling the\n * node sent, so it is populated against nodes on either side of that release.\n */\n namespaceId: string;\n /**\n * @deprecated The pre-`0.11.0-rc.25` spelling of {@link namespaceId}. Present\n * only when the node predates that release \u2014 read `namespaceId` instead,\n * which is always set.\n */\n groupId?: string;\n /** The key the joiner signs with, 64 hex. */\n memberIdentity: string;\n /**\n * The account that key joined as, 64 hex characters. This \u2014 not\n * `memberIdentity` \u2014 is what every member-addressing endpoint takes, so it\n * is how the caller addresses the member it just became.\n */\n memberAccount: string;\n}\n\nexport interface CreateGroupInNamespaceRequest {\n groupName?: string;\n /** Subgroup visibility at birth. Absent means `'restricted'`. */\n visibility?: 'open' | 'restricted';\n}\n\nexport interface CreateGroupInNamespaceResponseData {\n groupId: string;\n}\n\nexport interface SubgroupEntry {\n groupId: string;\n name?: string;\n}\n\n// ---- Account devices & pairing ----\n\n/**\n * Adopt an account that already lives on another device, and mint this node a\n * device of its own for it.\n *\n * The first half of pairing, run on the joining node. It publishes nothing and\n * needs no key: it produces the values the account holder's\n * {@link AdminApiClient.completeAccountPairing} certifies, plus a confirmation\n * code to read out to them.\n *\n * Account-level, and that is the whole point of it: both credentials pairing\n * mints were always account-wide - the certificate is root-signed and the\n * endorsement node-level, so neither ever named a namespace - while the older\n * per-namespace route still made the caller name one, and the device then\n * listened on that one topic while its holder's link fanned out across all of\n * them.\n */\nexport interface AccountPairInitRequest {\n /**\n * The epoch-0 root **public** key of the account to join, 64 hex characters -\n * exactly what {@link NodeIdentity.accountRootPublicKey} reports on the node\n * that already holds the account.\n *\n * Named for the half it carries. An ed25519 private and public key are both 32\n * bytes and both hex, so nothing but the name tells them apart; the private\n * root never crosses this boundary at all.\n */\n accountRootPublicKey: string;\n /**\n * Ids of the namespaces to enroll into, 64 hex characters each.\n *\n * The caller has to supply these, because the joining node cannot discover\n * them - it is a member of nothing and holds no scope key, so it can neither\n * read the account's namespace set off a DAG nor derive it. The device that\n * already holds the account is the only party that knows it.\n *\n * This decides what the new device *listens* on, and it does not have to agree\n * with the applications the holder scopes `pair-complete` to: a binding\n * published where the device is not listening is picked up whenever it does\n * subscribe, and a subscription the holder never reaches costs nothing.\n *\n * Core refuses only a request that names neither this nor `accountNamespace`.\n * A caller that sends `accountNamespace` may leave this empty.\n */\n namespaces?: string[];\n /**\n * Hex id of the account namespace to follow - the value\n * {@link NodeIdentity.accountNamespaceId} reports on the holder.\n *\n * The device then learns the account's namespaces from the account namespace\n * itself, so the caller need not know them up front. A caller that does not\n * send this must name at least one namespace above.\n */\n accountNamespace?: string;\n}\n\n/**\n * What the joining device minted, for the account holder to certify.\n *\n * These are round-trip tokens: a caller copies them verbatim into\n * {@link AdminApiClient.completeAccountPairing}. The signing key is the NEW\n * device's, so it appears in no member listing yet and there is nothing here to\n * compare it against.\n *\n * One device is minted however many namespaces were named, so there is one of\n * each of these to hand over, not one per namespace.\n */\nexport interface AccountPairInitResponseData {\n /** The account this device will speak for once linked, 64 hex characters. */\n accountId: string;\n /** The device that was minted, 64 hex characters. */\n deviceId: string;\n /** X25519 agreement key a scope key must be wrapped under to reach this device, hex. */\n kemPublicKey: string;\n /**\n * The Ed25519 key this device signs its ops with, hex.\n *\n * The account holder cannot derive this - it is minted here - and the\n * certificate names it, so it has to travel with the other two.\n */\n signPublicKey: string;\n /**\n * Ed25519 signature over the account, the device id and both keys above, hex.\n *\n * Carried so the three values arrive as a statement by the device that minted\n * them rather than as assertions by whoever relayed them; `pair-complete`\n * refuses without it.\n */\n statement: string;\n /**\n * The value to read out to the account holder, who compares it against what\n * their `pair-complete` reports.\n *\n * The part a substituting attacker cannot fake: it can re-sign its own\n * statement, but it would have to make its own keys derive the code the other\n * side is already reading.\n */\n confirmationCode: string;\n}\n\n/**\n * Certify a device another node minted, link it, and deliver its scope keys.\n *\n * The second half of pairing, run on the node that holds the account root - the\n * only party that can sign the certificate that makes the device real. Every\n * field but `applications` is copied verbatim from that other node's\n * {@link AccountPairInitResponseData}.\n */\nexport interface AccountPairCompleteRequest {\n /** The `deviceId` the other node minted, 64 hex characters. */\n deviceId: string;\n /** That device's X25519 agreement key, 64 hex characters. */\n kemPublicKey: string;\n /** That device's Ed25519 signing key, 64 hex characters. */\n signPublicKey: string;\n /**\n * The signature from that node's pair-init, 128 hex characters (64 bytes).\n *\n * Not optional: without it the three values above are only claims by whoever\n * sent them, and certifying those would make attacker-supplied keys a trusted\n * device of this account.\n */\n statement: string;\n /**\n * The code the account holder read off the pairing device, e.g.\n * `7BC0-DAAC-CCB4-84A4`. Grouping and case are ignored.\n *\n * Required so the comparison cannot be skipped: this side re-derives the code\n * for the key material that actually arrived and refuses a mismatch. Its value\n * rests on the code reaching the holder independently of this payload - sent\n * beside the keys, it proves nothing. One code covers the whole pairing,\n * because one device was minted for it.\n */\n confirmationCode: string;\n /**\n * Which applications this device may speak for, 64 hex each. Omitted or empty means\n * every one of them.\n *\n * Scoped by application rather than by namespace because a person can answer\n * \"which apps may this device use\" and cannot answer \"which namespaces\" - a\n * namespace is an implementation unit they never named. The node resolves\n * these to namespaces through the same lookup\n * {@link AdminApiClient.listNamespacesForApplication} reads.\n */\n applications?: string[];\n}\n\n/** What pairing established. */\nexport interface AccountPairCompleteResponseData {\n /** The account the device now speaks for, 64 hex characters. */\n accountId: string;\n /** The device that was linked, 64 hex characters. */\n deviceId: string;\n /**\n * Whether the current scope key was wrapped and published for the device.\n *\n * `false` does not mean pairing failed - the link is what confers authority,\n * and the device's own sync pull re-requests the key. It does mean the device\n * cannot read until that lands.\n */\n keyDelivered: boolean;\n /**\n * The code for the key material this certified - the same value the request\n * carried, echoed so the operator can see what the certificate names.\n */\n confirmationCode: string;\n /**\n * The `AccountProof<DeviceCert>` this pairing minted, hex-encoded borsh.\n *\n * The certified device needs this to present itself as a device of the\n * account, and cannot read it off the DAG: doing so means being a member of a\n * group the account speaks in, which a thin client never is. Not a secret - a\n * certificate is public and proves nothing without the device key it names.\n */\n credential: string;\n}\n\n/**\n * A domain an outside verifier defined, which the account root may sign under.\n *\n * A name from a closed set, never the domain bytes. The node prepends the bytes\n * itself: \"sign these bytes with the account root\" would be a signing oracle\n * over the one key that can certify a device, which is account takeover. A node\n * refuses any name it does not know.\n *\n * Typed as a union plus `(string & {})` so a node newer than this SDK can be\n * reached without a cast, while the known names still autocomplete.\n */\nexport type ExternalSigningDomain =\n | 'mdma.account-link'\n | 'mdma.account-login'\n | 'mdma.account-recovery'\n // eslint-disable-next-line @typescript-eslint/ban-types\n | (string & {});\n\n/**\n * Ask this node's account root to sign a payload an outside verifier specified.\n *\n * The verifier owns the format; the node supplies only the domain. For the cloud\n * this is how a challenge it sealed and issued gets signed by a root that\n * deliberately never leaves the node holding it.\n */\nexport interface AccountSignWithRootRequest {\n /** Which verifier's domain to sign under. */\n domain: ExternalSigningDomain;\n /**\n * The bytes to sign after the domain, hex-encoded.\n *\n * Hex rather than text because the field is bytes. For the cloud's challenge \u2014\n * an ASCII string \u2014 that is the UTF-8 of it, hex-encoded; {@link hexEncodeUtf8}\n * does exactly that and exists so each caller does not re-derive it.\n */\n payload: string;\n}\n\n/**\n * Hex-encode a string's UTF-8 bytes, for {@link AccountSignWithRootRequest.payload}.\n *\n * Exists so each caller does not write the same loop. Every verifier this is\n * used with issues its challenge as text, and the node takes bytes \u2014 getting\n * that conversion subtly wrong (UTF-16 code units, a stray newline from a shell)\n * produces a signature over the wrong message, which fails at the verifier with\n * nothing local to point at.\n */\nexport function hexEncodeUtf8(text: string): string {\n return Array.from(new TextEncoder().encode(text), (b) =>\n b.toString(16).padStart(2, '0'),\n ).join('');\n}\n\n/** What the node signed, and the key to check it against. */\nexport interface AccountSignWithRootResponseData {\n /** The account root's public key, 64 hex characters. */\n rootPublicKey: string;\n /**\n * The signature over `domain \u2016 payload`, **base64**.\n *\n * Base64 rather than hex because that is what the consuming verifiers decode;\n * re-encoding it on the way out is a step that fails silently at the far end.\n */\n signature: string;\n /** The account the signing key belongs to, 64 hex characters. */\n accountId: string;\n}\n\n/**\n * Repair or widen the reach of a device this account already certified.\n *\n * Pairing is a snapshot: it bound the device wherever this node took part at the\n * time, so a namespace created or joined afterwards holds no binding for it and\n * the paired device silently never sees that namespace. This re-runs the fan-out\n * against the namespaces this node takes part in now. The device need not be\n * online, and nothing but its id is required - the certificate is already held\n * here, root-signed and naming no namespace, so a fresh endorsement and a key\n * wrap are all a later namespace is missing.\n */\nexport interface RelinkDeviceRequest {\n /**\n * Applications to add to the device's stored scope, 64 hex each.\n *\n * **Omitted or empty changes nothing** and repairs against the scope already\n * stored, which is the request an operator makes to heal drift. Note that this\n * is the one place an empty list does not mean \"all\": widening a narrow scope\n * to every application is deliberately not expressible here, because\n * overloading the empty list would make the accidental request the widest one.\n */\n applications?: string[];\n}\n\n/**\n * The scope to hold: every application, or only the ones named.\n *\n * A tagged shape rather than a list whose emptiness means everything - `all`\n * and `only: []` differ by a whole order of magnitude in what they grant, and\n * an empty `only` is refused rather than treated as `all`.\n */\nexport type DeviceScope = 'all' | { only: string[] };\n\n/**\n * Replace a device's scope outright, in one root-signed op.\n *\n * {@link relinkAccountDevice} only ever adds applications; this is how a\n * scope is narrowed, or an `all` device is given a fixed list instead.\n */\nexport interface RescopeDeviceRequest {\n scope: DeviceScope;\n}\n\n/** One namespace the relink published the device's link into. */\nexport interface RelinkOutcomeEntry {\n /** The namespace id, 64 hex characters. */\n namespaceId: string;\n /**\n * Whether the scope key was wrapped and published for the device here.\n *\n * `false` means the link landed and the delivery did not; the link is what\n * confers authority, and the device's own sync pull re-requests the key.\n */\n keyDelivered: boolean;\n}\n\n/** One namespace the relink published nothing into, and why. */\nexport interface RelinkSkipEntry {\n /** The namespace id, 64 hex characters. */\n namespaceId: string;\n /**\n * Why nothing was published there. Currently one of `outOfScope`,\n * `alreadyBound`, `noScopeKey`, `revoked`, `ownDevice`, `failed`; left as a\n * string because a node may report a reason newer than this SDK.\n */\n reason: string;\n}\n\n/** One namespace a scope replacement no longer reaches. */\nexport interface RescopeDescopeEntry {\n /** The namespace id, 64 hex characters. */\n namespaceId: string;\n /**\n * Whether the scope key was rotated. `false` means the device stopped writing\n * there but still holds the key it had, until an admin rotates.\n */\n keyRotated: boolean;\n}\n\n/** The scope a device now holds, and what each namespace did about it. */\nexport interface RescopeDeviceResponseData {\n /** The account the device speaks for, 64 hex characters. */\n accountId: string;\n /** The device that was rescoped, 64 hex characters. */\n deviceId: string;\n /** The scope after the request, 64 hex each. Empty means every application. */\n applications: string[];\n /** Namespaces the new scope no longer reaches. */\n descoped: RescopeDescopeEntry[];\n /** Namespaces the device was linked into by this call. */\n linkedIn: RelinkOutcomeEntry[];\n /** Namespaces nothing was published into, and why. */\n skipped: RelinkSkipEntry[];\n}\n\n/** What the relink repaired, and what it left alone. */\nexport interface RelinkDeviceResponseData {\n /** The account the device speaks for, 64 hex characters. */\n accountId: string;\n /** The device that was repaired, 64 hex characters. */\n deviceId: string;\n /**\n * The device's scope after the request, 64 hex each. Empty means every\n * application, which is what a pairing that named none asked for.\n */\n applications: string[];\n /**\n * Namespaces the link was published into by this call. Reported per namespace\n * because publication is per-DAG, so which namespaces a device actually\n * reached is a state the caller has to be able to see.\n */\n linkedIn: RelinkOutcomeEntry[];\n /** Namespaces nothing was published into. */\n skipped: RelinkSkipEntry[];\n}\n\n/**\n * One device of this account, joined from the node-local certificate cache and\n * the live bindings of every namespace this node takes part in.\n */\nexport interface AccountDeviceEntry {\n /** This device's id, 64 hex characters. */\n deviceId: string;\n /** The key this device signs its ops with, 64 hex. */\n signingKey: string;\n /** Set only on the device this node itself presents. */\n isSelf: boolean;\n revoked: boolean;\n /**\n * Applications this device may speak for, 64 hex each. **Empty means every\n * application**, the same convention the stored certificate uses - and the\n * opposite of what an empty {@link RelinkDeviceRequest.applications} asks for.\n * Empty also for a device this node holds no cached certificate for: one bound\n * before the cache existed, or certified by another holder.\n */\n applications: string[];\n /**\n * Ids of the namespaces currently holding a live binding for this device, 64\n * hex characters each. Empty for a certified device not yet bound anywhere.\n */\n namespaces: string[];\n /**\n * The replicated name the account gave this device, absent while it has\n * none. Every device of the account reads the same one.\n */\n label?: string;\n}\n\n/**\n * Name a device of this account, for a listing to render.\n *\n * Run on the node holding the account root to name any device; a paired node\n * is accepted only for that device's own id.\n */\nexport interface LabelDeviceRequest {\n /** Trimmed, non-empty, bounded and free of control characters. */\n label: string;\n}\n\n/**\n * The name that was published, and the epoch that orders it against a rename\n * another device of the account made at the same time.\n */\nexport interface LabelDeviceResponseData {\n /** The account the device speaks for, 64 hex characters. */\n accountId: string;\n /** The device that was named, 64 hex characters. */\n deviceId: string;\n label: string;\n labelEpoch: number;\n}\n\n/**\n * One application this account speaks in, derived from the namespaces this node\n * takes part in that target it.\n *\n * An application installed with no namespace yet is absent here: it has no\n * cross-device meaning until a namespace exists, so there is nothing to scope a\n * device to.\n */\nexport interface AccountApplicationEntry {\n /** The application id, 64 hex. */\n applicationId: string;\n /** Ids of the namespaces targeting it, 64 hex characters each. */\n namespaces: string[];\n}\n\n/** Withdraw a device from an account, terminally. */\nexport interface RevokeAccountDeviceRequest {\n /** The device to withdraw, 64 hex. */\n deviceId: string;\n /**\n * A hex-encoded, borsh-serialized signed revocation minted elsewhere -\n * the output of `merod account revoke-proof`.\n *\n * The lost-device path: the account root that owns the device is not on this\n * node, so the proof is signed offline and this node only publishes it. Omit\n * it and the node mints its own proof if it owns the account, and otherwise\n * revokes as an admin.\n *\n * Not a credential and not a secret - it authorises this one revocation of\n * this one device, and only alongside a stored binding naming the same\n * account.\n */\n proof?: string;\n}\n\n/** What a revocation withdrew in one namespace. */\nexport interface RevocationOutcome {\n /** The namespace, 64 hex. */\n namespaceId: string;\n /**\n * Whether the scope key rotated in the same op, in THIS namespace.\n *\n * `false` means the device stopped writing there at once but still holds the\n * key it had, so it can keep READING until an admin rotates. Only an admin\n * may rotate and an account holder revoking their own device usually is not\n * one, so this is commonly owed rather than exceptional.\n */\n keyRotated: boolean;\n}\n\nexport interface RevokeAccountDeviceResponseData {\n /** The account the device spoke for, 64 hex. */\n accountId: string;\n /** The device that was withdrawn, 64 hex. */\n deviceId: string;\n /**\n * Whether the scope key rotated in the namespace named in the request.\n *\n * {@link revokedIn} is the full picture; prefer it. This reports only the one\n * namespace the caller asked about and is retained because core keeps it for\n * a released `calimero-client-py` wheel that requires the field.\n */\n keyRotated: boolean;\n /**\n * Every namespace the revocation was published into.\n *\n * A device belongs to an account rather than to a scope, so revoking it\n * withdraws it from every namespace holding a binding for it - not only the\n * one named in the request. Per namespace because publication is per-DAG: a\n * namespace absent here did not receive the op, and a partially propagated\n * revocation is a state the caller has to be able to see.\n */\n revokedIn: RevocationOutcome[];\n}\n\n// ---- Groups ----\n\nexport interface CreateGroupRequest {\n applicationId: string;\n groupId?: string;\n appKey?: string;\n name?: string;\n parentGroupId?: string;\n}\n\nexport interface CreateGroupResponseData {\n groupId: string;\n}\n\nexport interface GroupUpgradeStatus {\n fromVersion: string;\n toVersion: string;\n initiatedAt: number;\n initiatedBy: string;\n status: string;\n /** Contexts THIS NODE enumerated for the upgrade. Node-local; fleet progress\n * is the `getMigrationStatus` rollup. */\n localContextsTotal?: number;\n localContextsSwapped?: number;\n /** Contexts whose swap failed on this node; a non-zero value is what\n * `retryGroupUpgrade` picks up. */\n localContextsFailed?: number;\n /** Unix seconds at which THIS NODE finished its own context swaps. Not fleet\n * convergence - that is `MigrationStatus.fleetCompletedAt`. */\n completedAt?: number;\n}\n\n// ---- Migration status (migration-UX core surfaces) ----\n\nexport type MemberMigrationState =\n | 'migrated'\n | 'in_progress'\n | 'unknown'\n | 'failed';\n\n/** Why a member's migration did not complete. */\nexport type MigrationFailureReason =\n | 'check_aborted'\n | 'apply_failed'\n | 'no_migration_path';\n\nexport interface MemberMigrationReport {\n /** ABI state version the member has loaded, from `#[app::state(version = N)]` -\n * the engine's migration gate, not the bundle semver and not a CRDT concept.\n * The wrong reading here is how the core rollup defect (comparing bundle-semver\n * majors instead of state versions) went unnoticed. */\n schemaVersion: number;\n residueAuto: number;\n syncedUpToHlc: number;\n reportedAt: number;\n /** Member's self-reported pending-authored count (best-effort; skew #1). */\n authoredRemaining: number;\n /**\n * Set when the member's migrate did not complete (its migration-check\n * aborted, or the apply errored). Absent otherwise.\n */\n migrationFailed?: MigrationFailureReason;\n}\n\nexport interface MemberMigrationStatusEntry {\n /** The reporting device's signing KEY, bs58. One account with two devices\n * appears as two rows: the cohort is replicas, not people. */\n peer: string;\n /** The ACCOUNT `peer` speaks for, 64 hex - join it to `listGroupMembers` to\n * name a person. Absent from nodes predating the field. */\n account?: string;\n /** Freshest reported facts. Omitted (not `null`) when the member's state is\n * `unknown`, so test falsiness rather than `=== null`. */\n report?: MemberMigrationReport | null;\n state: MemberMigrationState;\n}\n\nexport interface MigrationStatusRollup {\n migrated: number;\n inProgress: number;\n unknown: number;\n /** Members whose migrate aborted (migration-check failed or apply errored). */\n failed: number;\n total: number;\n allMigrated: boolean;\n /** Count of members with authoredRemaining > 0 (owners still to re-sign). */\n membersPendingSignature: number;\n}\n\nexport interface MigrationStatus {\n targetVersion: number;\n expectedMembers: number;\n /** Governance HLC the cohort was pinned at, as an opaque display string.\n * Absent when there is no migration record. */\n cohortPinnedAtHlc?: string;\n /** Unix seconds at which this node watched the FLEET converge, absent while it\n * has not. Distinct from `GroupUpgradeStatus.completedAt`, which is this node's\n * own local swap completion. Durable, unlike `rollup.allMigrated`, which is\n * recomputed from in-TTL heartbeats and lapses when a member goes quiet. */\n fleetCompletedAt?: number;\n rollup: MigrationStatusRollup;\n members: MemberMigrationStatusEntry[];\n}\n\n// ---- Cascade status ----\n\nexport interface CascadeStatusEntry {\n groupId: string;\n upgrade: GroupUpgradeStatus;\n cascadeHlc?: string;\n}\n\nexport interface GroupInfo {\n groupId: string;\n appKey: string;\n targetApplicationId: string;\n /**\n * Absent from nodes that have dropped the concept, always `\"LazyOnAccess\"` on\n * every released one. Optional because this SDK talks to both; it carries no\n * information either way. Delete once no supported release still sends it.\n */\n upgradePolicy?: string;\n memberCount: number;\n contextCount: number;\n activeUpgrade?: GroupUpgradeStatus;\n defaultCapabilities: number;\n subgroupVisibility: string;\n /**\n * The group's generic metadata record (replaces the old `alias` field).\n * `null` if no metadata has ever been set for this group.\n */\n metadata?: MetadataRecord | null;\n}\n\nexport type GroupInfoResponseData = GroupInfo;\n\nexport interface GroupMember {\n /**\n * The member's ACCOUNT: 64 hex characters.\n *\n * Not a signing key \u2014 a person may hold several keys\n * and governance rows name the person. Both are 32 bytes, so nothing here or\n * on the server will object if you pass the wrong one; it will simply name a\n * principal that exists nowhere. Feed this value to\n * {@link RemoveGroupMembersRequest} and {@link UpdateMemberRoleRequest}, not\n * to {@link GroupMemberInput}.\n */\n identity: string;\n role: string;\n name?: string;\n}\n\nexport interface ListGroupMembersResponseData {\n members: GroupMember[];\n /**\n * @deprecated The server response uses `members`, not `data`. This alias\n * is retained so existing callers compile during the upgrade window; it\n * is never populated by the client and will be removed in the next\n * major. Switch reads to `response.members`.\n */\n data?: GroupMember[];\n}\n\n/** One device bound to a member account. */\nexport interface MemberDevice {\n /** 64 hex; the form `revokeAccountDevice` takes. */\n deviceId: string;\n /**\n * The key this device's signatures carry, 64 hex. Joins against the\n * identities a context lists.\n */\n signingKey: string;\n}\n\n/** A member account and the devices bound to it in the namespace. */\nexport interface MemberDevicesEntry {\n /** The member's ACCOUNT, 64 hex. */\n account: string;\n devices: MemberDevice[];\n}\n\nexport interface ListMemberDevicesOptions {\n offset?: number;\n limit?: number;\n}\n\nexport interface ListMemberDevicesResponseData {\n /**\n * Every account in the group if the calling node is an admin; only its own\n * entry if it is a plain member.\n */\n members: MemberDevicesEntry[];\n}\n\nexport interface SealToAccountRequest {\n /**\n * Hex-encoded plaintext. Small by design: the route exists to hand an account\n * something like a list of namespace ids, and the node caps the size.\n */\n plaintext: string;\n}\n\n/**\n * A payload sealed to an account's root key. Only that account's root can open\n * it; the node that sealed it cannot.\n */\nexport interface SealedEnvelope {\n /**\n * The root-key epoch it was sealed under. A rotated root still opens envelopes\n * from its own epoch, so a recipient holding several needs to know which.\n */\n accountRootEpoch: number;\n /** Hex, 32 bytes: the one-shot sender key. */\n ephemeralPublicKey: string;\n /** Hex, 12 bytes: the AES-256-GCM nonce. */\n nonce: string;\n /** Hex: the ciphertext with its 16-byte tag appended. */\n ciphertext: string;\n}\n\nexport interface GroupContextEntry {\n contextId: string;\n name?: string;\n}\n\nexport type ListGroupContextsResponseData = GroupContextEntry[];\n\nexport interface DeleteGroupResponseData {\n isDeleted: boolean;\n}\n\n// ---- Group Members ----\n\nexport interface GroupMemberInput {\n /**\n * The invitee's signing KEY, 64 hex \u2014 NOT an account. It is spelled exactly\n * like one, so only the field name says which this is.\n *\n * Adding is the one member-facing call that names a key: the node binds the\n * key to an account as it admits it, so before that there is no account to\n * name. Every call that names an EXISTING member takes the account instead.\n */\n identity: string;\n role: string;\n}\n\nexport interface AddGroupMembersRequest {\n members: GroupMemberInput[];\n}\n\n// Returns empty\nexport type AddGroupMembersResponseData = Record<string, never>;\n\nexport interface RemoveGroupMembersRequest {\n /**\n * The members to remove, as ACCOUNTS (64 hex) \u2014 the same ids\n * {@link GroupMember.identity} returns, and not the keys\n * {@link GroupMemberInput.identity} took to add them.\n */\n members: string[];\n}\n\n// Returns empty\nexport type RemoveGroupMembersResponseData = Record<string, never>;\n\nexport interface UpdateMemberRoleRequest {\n role: string;\n}\n\n// Returns empty\nexport type UpdateMemberRoleResponseData = Record<string, never>;\n\n// ---- Group Capabilities & Settings ----\n\nexport interface MemberCapabilities {\n capabilities: number;\n}\n\nexport interface SetMemberCapabilitiesRequest {\n capabilities: number;\n}\n\n// Returns empty\nexport type SetMemberCapabilitiesResponseData = Record<string, never>;\n\nexport interface SetDefaultCapabilitiesRequest {\n defaultCapabilities: number;\n}\n\n// Returns empty\nexport type SetDefaultCapabilitiesResponseData = Record<string, never>;\n\nexport interface SetMemberAutoFollowRequest {\n /** Auto-join contexts registered in this group. */\n autoFollowContexts: boolean;\n /** Self-admit into subgroups nested under this group. */\n autoFollowSubgroups: boolean;\n}\n\n// Returns empty\nexport type SetMemberAutoFollowResponseData = Record<string, never>;\n\nexport interface SetSubgroupVisibilityRequest {\n subgroupVisibility: string;\n}\n\n// Returns empty\nexport type SetSubgroupVisibilityResponseData = Record<string, never>;\n\n/**\n * The signed-release form of a TEE admission policy: admit a TEE running any\n * mero-tee node release the mero-tee release workflow signed, if its quote\n * matches one of `allowedProfiles` in that release's `published-mrtds.json`.\n * Unlike the measurement lists, it does not need updating for each release.\n */\nexport interface SignedReleaseTeePolicy {\n /** Image profiles to admit, e.g. `locked-read-only`. */\n allowedProfiles: string[];\n /** The oldest release admitted (`2.3.72`); any signed release when absent. */\n minReleaseVersion?: string;\n}\n\n/** A policy that lists the measurements it admits. */\nexport interface MeasurementTeeAdmissionPolicyRequest {\n allowedMrtd: string[];\n allowedRtmr0: string[];\n allowedRtmr1: string[];\n allowedRtmr2: string[];\n allowedRtmr3: string[];\n allowedTcbStatuses: string[];\n acceptMock: boolean;\n}\n\n/** A policy that admits by signed release. The node refuses measurement lists beside it. */\nexport interface SignedReleaseTeeAdmissionPolicyRequest {\n signedRelease: SignedReleaseTeePolicy;\n allowedTcbStatuses: string[];\n acceptMock: boolean;\n}\n\nexport type SetTeeAdmissionPolicyRequest =\n | MeasurementTeeAdmissionPolicyRequest\n | SignedReleaseTeeAdmissionPolicyRequest;\n\n// Returns empty\nexport type SetTeeAdmissionPolicyResponseData = Record<string, never>;\n\nexport interface GetTeeAdmissionPolicyResponseData {\n allowedMrtd: string[];\n allowedRtmr0: string[];\n allowedRtmr1: string[];\n allowedRtmr2: string[];\n allowedRtmr3: string[];\n allowedTcbStatuses: string[];\n acceptMock: boolean;\n /** Set when the policy admits by signed release; the lists are then empty. */\n signedRelease?: SignedReleaseTeePolicy;\n}\n\n/**\n * Which admitted TEEs may author as the group's TEE authority (the writer behind\n * `TeeOnly` storage and `#[app::tee]` methods). A TEE whose attested MRTD is in\n * `allowedMrtd` may author; an empty list turns TEE authorship off.\n *\n * Namespace-scoped: set it on the namespace root. The node refuses it on a subgroup.\n */\nexport interface SetTeeAuthoringPolicyRequest {\n allowedMrtd: string[];\n}\n\n// Returns empty\nexport type SetTeeAuthoringPolicyResponseData = Record<string, never>;\n\n// ---- Group / member / context metadata ----\n\n/**\n * Generic metadata record attached to a group, group member, or\n * context-registered-in-a-group (core `calimero_primitives::metadata::MetadataRecord`).\n *\n * `data` is application-defined and opaque to core \u2014 it is stored verbatim.\n * Server-enforced size limits: `name` <= 64 bytes; at most 64 entries in\n * `data`; each key <= 64 bytes; each value <= 4096 bytes. Clients do not need\n * to enforce these \u2014 the server validates.\n */\nexport interface MetadataRecord {\n name: string | null;\n data: Record<string, string>;\n updatedAt: number;\n /** Public key (hex) of the member that last updated the record. */\n updatedBy: string;\n}\n\n/**\n * Request body for setting a metadata record. **This wholly replaces the\n * record**: `data` defaults to `{}` server-side and replaces the stored map,\n * while omitting `name` keeps the current name. To change `name` while\n * preserving existing `data`, GET the record first and pass its `data` back.\n */\nexport interface SetMetadataRequest {\n name?: string;\n data?: Record<string, string>;\n}\n\nexport type SetGroupMetadataRequest = SetMetadataRequest;\nexport type SetMemberMetadataRequest = SetMetadataRequest;\nexport type SetContextMetadataRequest = SetMetadataRequest;\n\n// Set-metadata returns empty\nexport type SetMetadataResponseData = Record<string, never>;\n\n/**\n * Inner payload of a GET metadata response. `data` is `null` if no metadata\n * has ever been set for the target group/member/context.\n */\nexport interface GetMetadataResponseData {\n data: MetadataRecord | null;\n}\n\n// ---- Group Sync, Signing & Upgrades ----\n\nexport interface SyncGroupResponseData {\n groupId: string;\n appKey: string;\n targetApplicationId: string;\n memberCount: number;\n contextCount: number;\n}\n\nexport interface UpgradeGroupRequest {\n targetApplicationId: string;\n /** Fan the upgrade out to every descendant subgroup running the same app\n * (one atomic cascade op). Without it the upgrade applies to the target\n * group only - members' subgroups never learn the migration. Server\n * default: false. */\n cascade?: boolean;\n /** Proceed code-only when the target build embeds no ABI. Core refuses such an\n * upgrade by default; setting this asserts the new code is layout-compatible\n * with the running state so the upgrade goes ahead. It never bypasses a\n * declared-migration or downgrade refusal. Wire name is camelCase passthrough;\n * older nodes ignore the unknown field. Server default: false. */\n forceCodeOnly?: boolean;\n}\n\nexport interface UpgradeGroupResponseData {\n groupId: string;\n status: string;\n localContextsTotal?: number;\n localContextsSwapped?: number;\n localContextsFailed?: number;\n}\n\nexport type GroupUpgradeStatusResponseData = GroupUpgradeStatus | null;\n\n// Retry returns same shape as upgrade\nexport type RetryGroupUpgradeResponseData = UpgradeGroupResponseData;\n\n// ---- Group Reparent & Context Attachments ----\n\nexport interface ReparentGroupRequest {\n /** 64-char id of the destination parent group. */\n newParentId: string;\n}\n\nexport interface ReparentGroupResponseData {\n reparented: boolean;\n}\n\n// Returns empty\nexport type DetachContextFromGroupResponseData = Record<string, never>;\n\n// ---- Group Ownership Proofs ----\n\nexport interface IssueOwnershipProofRequest {\n /** Who the proof is for. Non-empty, <= 256 characters. */\n audience: string;\n /** 64-hex context id the proof is scoped to. */\n contextId: string;\n /** What is being claimed. Non-empty, <= 512 characters. */\n subject: string;\n /** Caller-chosen hex nonce, 32-128 characters (even length). */\n nonce: string;\n /** Requested expiry, unix ms. Core clamps it to issue time + 5 minutes. */\n expiresAtMs: number;\n}\n\n/** Namespace-scoped sibling: identical minus `contextId`. */\nexport type IssueNamespaceOwnershipProofRequest = Omit<IssueOwnershipProofRequest, 'contextId'>;\n\nexport interface IssueOwnershipProofResponseData {\n /** Hex 32-byte ed25519 public key of the signer. */\n signerPublicKey: string;\n /**\n * Base64 opaque UTF-8 JSON bytes of the claim. Verifiers re-parse these exact\n * bytes and sign over `\"calimero.ownership-claim.v1\\0\" || signedPayload`.\n */\n signedPayload: string;\n /** Base64 64-byte ed25519 signature. */\n signature: string;\n}\n\n// ---- Group Invitation & Join ----\n\nexport interface CreateGroupInvitationRequest {\n expirationTimestamp?: number;\n recursive?: boolean;\n /**\n * Accounts permitted to admit a claim of this invitation, 64 hex each.\n *\n * Omitted or empty is filled in by the node from the group's admins and its\n * TEE nodes; it refuses to mint if that set comes back empty, because an empty\n * list on the wire authorises any node to admit. Naming admitters narrows that\n * set.\n *\n * Core signs this list into the invitation, so it cannot be redirected after\n * the fact.\n */\n admitters?: string[];\n /**\n * libp2p addresses for those admitters, each including its `/p2p/<peer-id>`\n * suffix.\n *\n * Omitted or empty asks the node to fill them in from addresses it already has\n * on file \u2014 which is usually what you want. Values supplied here are used as\n * given rather than merged, on the grounds that a caller naming an address is\n * correcting the node's view rather than extending it.\n *\n * Not signed, so a wrong address costs a failed dial and nothing more.\n */\n admitterAddrs?: string[];\n}\n\nexport interface CreateGroupInvitationResponseData {\n invitation: SignedGroupOpenInvitation;\n groupName?: string;\n}\n\nexport interface CreateRecursiveGroupInvitationResponseData {\n invitations: RecursiveInvitationEntry[];\n}\n\nexport interface JoinGroupRequest {\n invitation: SignedGroupOpenInvitation;\n groupName?: string;\n}\n\nexport interface JoinGroupResponseData {\n groupId: string;\n /** The key the joiner signs with, 64 hex. */\n memberIdentity: string;\n /**\n * The account that key joined as, 64 hex characters. This \u2014 not\n * `memberIdentity` \u2014 is what every member-addressing endpoint takes, so it\n * is how the caller addresses the member it just became.\n */\n memberAccount: string;\n}\n\n// ---- TEE ----\n\nexport interface TeeInfoResponseData {\n cloudProvider: string;\n osImage: string;\n mrtd: string;\n}\n\nexport interface TeeAttestRequest {\n nonce: string;\n applicationId?: string;\n /**\n * Bind the node's X25519 transport key into the quote; the response names it\n * as `transportPublicKey`. See `fetchAttestedTransportKey`.\n */\n bindTransportKey?: boolean;\n /**\n * Also return the Intel-signed collateral the quote is verified against, as\n * `collateral`, so the quote can be verified with nothing but the node. See\n * `createQuoteVerifier`. A node that predates it refuses the field.\n */\n includeCollateral?: boolean;\n}\n\nexport interface QuoteHeader {\n version: number;\n attestationKeyType: number;\n teeType: number;\n qeVendorId: string;\n userData: string;\n}\n\nexport interface QuoteBody {\n tdxVersion: string;\n teeTcbSvn: string;\n mrseam: string;\n mrsignerseam: string;\n seamattributes: string;\n tdattributes: string;\n xfam: string;\n mrtd: string;\n mrconfigid: string;\n mrowner: string;\n mrownerconfig: string;\n rtmr0: string;\n rtmr1: string;\n rtmr2: string;\n rtmr3: string;\n reportdata: string;\n teeTcbSvn2?: string;\n mrservicetd?: string;\n}\n\nexport interface Quote {\n header: QuoteHeader;\n body: QuoteBody;\n signature: string;\n attestationKey: string;\n certificationData: unknown;\n}\n\nexport interface TeeAttestResponseData {\n quoteB64: string;\n quote: Quote;\n /** Hex X25519 transport key, present only when the request set `bindTransportKey`. */\n transportPublicKey?: string;\n /**\n * The Intel-signed collateral the quote verifies against, present only when\n * the request set `includeCollateral` and the quote is not a mock.\n */\n collateral?: DcapCollateral;\n}\n\n/**\n * @deprecated Nodes no longer serve `/admin-api/tee/verify-quote` (removed in\n * core#3262), so a request for it fails. Verify quotes with\n * `createQuoteVerifier`.\n */\nexport interface TeeVerifyQuoteRequest {\n quoteB64: string;\n nonce: string;\n expectedApplicationHash?: string;\n}\n\n/** @deprecated See {@link TeeVerifyQuoteRequest}. */\nexport interface TeeVerifyQuoteResponseData {\n quoteVerified: boolean;\n nonceVerified: boolean;\n applicationHashVerified?: boolean;\n quote: Quote;\n}\n\n// ---- Client Configuration ----\n\nexport interface AdminApiClientConfig {\n baseUrl: string;\n getAuthToken?: () => Promise<string | undefined>;\n timeoutMs?: number;\n}\n\n/** A join signed by its subject, handed to an admitter to publish. */\nexport interface AdmitJoinRequest {\n /** The invitation being claimed. Must name the admitter in `admitters`. */\n readonly invitation: SignedGroupOpenInvitation;\n /**\n * The signed `SignedNamespaceOp`, borsh-encoded and hex.\n *\n * Signed by the joiner's device key, which is what stops an admitter\n * substituting a different member: peers check the op's signer against the\n * credential the op carries.\n */\n readonly signedOp: string;\n}\n\n/** What the admitter did with it. */\nexport interface AdmitJoinResponseData {\n /**\n * Whether the op reached the namespace topic.\n *\n * Not \"joined\" \u2014 membership lands when peers apply the op, which the admitter\n * neither performs nor waits for.\n */\n readonly published: boolean;\n}\n", "import { AdminApiClient } from './admin-client.js';\nimport { AdminApiClientConfig } from './admin-types.js';\nimport {\n HttpClient,\n createBrowserHttpClient,\n createNodeHttpClient,\n createUniversalHttpClient,\n} from '../http-client/index.js';\n\n// Factory functions for creating Admin API clients\n\n/** Admin client over a browser HTTP transport (global `fetch`). */\nexport function createBrowserAdminApiClient(\n config: AdminApiClientConfig,\n): AdminApiClient {\n return new AdminApiClient(createBrowserHttpClient(config));\n}\n\n/** Admin client over a Node HTTP transport (Node 18+ `fetch`). */\nexport function createNodeAdminApiClient(\n config: AdminApiClientConfig,\n): AdminApiClient {\n return new AdminApiClient(createNodeHttpClient(config));\n}\n\n/** Admin client over whichever transport suits the current runtime. */\nexport function createAdminApiClient(\n config: AdminApiClientConfig,\n): AdminApiClient {\n return new AdminApiClient(createUniversalHttpClient(config));\n}\n\n/**\n * Admin client over a caller-supplied HTTP client \u2014 use this to share one\n * transport (and its token handling) with the other API clients.\n */\nexport function createAdminApiClientFromHttpClient(\n httpClient: HttpClient,\n _config: AdminApiClientConfig,\n): AdminApiClient {\n return new AdminApiClient(httpClient);\n}\n", "export interface AuthCallbackResult {\n accessToken: string;\n refreshToken: string;\n applicationId: string;\n contextId: string;\n contextIdentity: string;\n nodeUrl: string;\n}\n\nexport interface AuthLoginOptions {\n callbackUrl: string;\n packageName?: string;\n mode: string;\n permissions?: string[];\n registryUrl?: string;\n packageVersion?: string;\n}\n\n/**\n * Parse an auth callback URL hash fragment.\n * Returns null if access_token is missing.\n */\nexport function parseAuthCallback(url: string): AuthCallbackResult | null {\n try {\n const hashIndex = url.indexOf('#');\n if (hashIndex === -1) return null;\n\n const hash = url.substring(hashIndex + 1);\n const params = new URLSearchParams(hash);\n\n const accessToken = params.get('access_token');\n if (!accessToken) return null;\n\n return {\n accessToken,\n refreshToken: params.get('refresh_token') ?? '',\n applicationId: params.get('application_id') ?? '',\n contextId: params.get('context_id') ?? '',\n contextIdentity: params.get('context_identity') ?? '',\n nodeUrl: params.get('node_url') ?? '',\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Build the auth login URL for redirecting users to the node's auth page.\n */\nexport function buildAuthLoginUrl(nodeUrl: string, opts: AuthLoginOptions): string {\n const params = new URLSearchParams();\n params.set('callback-url', opts.callbackUrl);\n\n if (opts.permissions && opts.permissions.length > 0) {\n params.set('permissions', opts.permissions.join(','));\n }\n\n params.set('mode', opts.mode);\n\n if (opts.packageName) {\n params.set('package-name', opts.packageName);\n if (opts.packageVersion) {\n params.set('package-version', opts.packageVersion);\n }\n if (opts.registryUrl) {\n params.set('registry-url', opts.registryUrl);\n }\n }\n\n // Trim trailing slash from nodeUrl\n const base = nodeUrl.replace(/\\/+$/, '');\n return `${base}/auth/login?${params.toString()}`;\n}\n", "/**\n * Byte-level primitives shared by everything in this package that signs.\n *\n * Extracted from `warrant/warrant.ts` when the device-cert signer arrived and\n * needed the same `domainHash`, hex handling and ed25519 import. Keeping one\n * copy is the point: these have to stay byte-identical with core, and two\n * copies drifting apart is exactly the failure the byte-parity e2e exists to\n * catch. Internal \u2014 not exported from the package root.\n */\n\nconst PKCS8_ED25519_PREFIX = new Uint8Array([\n 0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04,\n 0x22, 0x04, 0x20,\n]);\n\n/** Little-endian u32, for borsh fixed-width fields and epoch counters. */\nexport function u32le(value: number): Uint8Array {\n const out = new Uint8Array(4);\n new DataView(out.buffer).setUint32(0, value, true);\n return out;\n}\n\nexport function hex(bytes: Uint8Array): string {\n return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n}\n\nexport function fromHex(value: string, label: string, expectedBytes: number): Uint8Array {\n const clean = value.trim();\n if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length !== expectedBytes * 2) {\n throw new Error(\n `${label} must be ${expectedBytes * 2} hex characters, got ${clean.length}`,\n );\n }\n // Indexed rather than `match(/../g)!`: the length is already validated above,\n // so the assertion would only be telling the compiler what the guard proved,\n // and this needs no assertion at all.\n const bytes = new Uint8Array(expectedBytes);\n for (let i = 0; i < expectedBytes; i += 1) {\n bytes[i] = Number.parseInt(clean.slice(i * 2, i * 2 + 2), 16);\n }\n return bytes;\n}\n\n/**\n * Decode hex of an unknown length.\n *\n * The sized {@link fromHex} is the right default \u2014 every key and digest here\n * has a fixed width, and checking it catches a truncated paste at the edge\n * rather than deep inside a signature check. This one exists for the already-\n * encoded structures whose length is genuinely variable: a device certificate,\n * a login statement, a request signature. It still rejects odd-length and\n * non-hex input, so the only check it gives up is the one that cannot apply.\n */\nexport function fromHexUnsized(value: string, label: string): Uint8Array {\n const clean = value.trim();\n if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length % 2 !== 0) {\n throw new Error(`${label} must be an even number of hex characters`);\n }\n const bytes = new Uint8Array(clean.length / 2);\n for (let i = 0; i < bytes.length; i += 1) {\n bytes[i] = Number.parseInt(clean.slice(i * 2, i * 2 + 2), 16);\n }\n return bytes;\n}\n\nexport function u64le(value: number | bigint): Uint8Array {\n const out = new Uint8Array(8);\n new DataView(out.buffer).setBigUint64(0, BigInt(value), true);\n return out;\n}\n\nexport function concat(...parts: Uint8Array[]): Uint8Array {\n const out = new Uint8Array(parts.reduce((total, p) => total + p.length, 0));\n let offset = 0;\n for (const part of parts) {\n out.set(part, offset);\n offset += part.length;\n }\n return out;\n}\n\n/**\n * core's `domain_hash`: SHA-256 over the length-prefixed domain, then each\n * length-prefixed part. The lengths are what stop two different field splits from\n * hashing alike.\n */\nexport async function domainHash(\n domain: Uint8Array,\n parts: Uint8Array[],\n): Promise<Uint8Array> {\n const chunks: Uint8Array[] = [u64le(domain.length), domain];\n for (const part of parts) {\n chunks.push(u64le(part.length), part);\n }\n return new Uint8Array(\n await crypto.subtle.digest('SHA-256', concat(...chunks)),\n );\n}\n\nexport async function importSigningKey(secretHex: string): Promise<CryptoKey> {\n const seed = fromHex(secretHex, 'deviceSecret', 32);\n const pkcs8 = concat(PKCS8_ED25519_PREFIX, seed);\n try {\n return await crypto.subtle.importKey(\n 'pkcs8',\n pkcs8,\n { name: 'Ed25519' },\n false,\n ['sign'],\n );\n } catch (cause) {\n // The original message is folded in rather than passed as `cause`: this\n // package targets ES2020, where `Error` has no `cause` option.\n const detail = cause instanceof Error ? `: ${cause.message}` : '';\n throw new Error(\n 'this runtime has no WebCrypto Ed25519, so a warrant cannot be signed ' +\n 'here. Node 18.4+, Safari 17+, Firefox 129+ and Chrome 137+ have it; ' +\n 'otherwise mint the warrant with `merod account warrant`' +\n detail,\n );\n }\n}\n\n/**\n * The public half of an ed25519 seed.\n *\n * Derived rather than taken as an argument, for the reason core does the same: a\n * caller able to name a key it does not hold could produce a warrant it cannot\n * sign, and the field would stop meaning \"who authorised this\".\n */\nexport async function derivePublicKey(secretHex: string): Promise<Uint8Array> {\n const seed = fromHex(secretHex, 'deviceSecret', 32);\n const pkcs8 = concat(PKCS8_ED25519_PREFIX, seed);\n const key = await crypto.subtle.importKey(\n 'pkcs8',\n pkcs8,\n { name: 'Ed25519' },\n true,\n ['sign'],\n );\n const jwk = await crypto.subtle.exportKey('jwk', key);\n const raw = (jwk as JsonWebKey & { x: string }).x;\n return Uint8Array.from(atob(raw.replace(/-/g, '+').replace(/_/g, '/')), (c) =>\n c.charCodeAt(0),\n );\n}\n", "/**\n * Sign one HTTP request, so a caller's identity travels with it.\n *\n * This is the bottom link of the chain a delegated client presents. A session\n * has the node mint and remember something; this has the caller prove itself on\n * every call, so the node holds no session state and the same path works\n * whether or not the node runs a login provider at all.\n *\n * ```text\n * account root \u2500\u2500signs\u2500\u2500\u25B6 DeviceCert cold, once, offline\n * device key \u2500\u2500signs\u2500\u2500\u25B6 LoginStatement once per session, hours\n * session key \u2500\u2500signs\u2500\u2500\u25B6 RequestSig per call \u25C0\u2500\u2500 this module\n * ```\n *\n * **The middle link is optional.** A CLI or a server-side job signs with its\n * device key directly and presents two links; a browser keeping the device key\n * behind a hardware or extension boundary mints a session key and presents\n * three. The node's verifier handles both, so which you produce here is a\n * question about where your key lives, not about what the node accepts.\n *\n * **What it binds.** Method, path and a hash of the body. A signature for\n * `GET /a` is not a signature for `POST /a`, for `GET /b`, or for the same call\n * with different arguments.\n *\n * **The path excludes the query string**, deliberately: a proxy may legitimately\n * rewrite it \u2014 a token parameter most of all \u2014 and signing over bytes something\n * else is entitled to change means failing for reasons the caller cannot see.\n * Anything that must be bound belongs in the body.\n *\n * **The method is not normalized.** `HEAD` and `GET` are the same *permission*\n * and the node's permission layer folds them, but they are different requests.\n * Sign the method you are about to send.\n *\n * **Replay inside the window performs the identical request** and is bounded\n * rather than prevented \u2014 keep `expiresAt` short. A warrant, which authorizes a\n * change with lasting effect, carries a nonce ledger for this; a request\n * signature does not.\n *\n * **The byte contract is pinned in core**, at\n * `crates/account/src/tests/request_wire_fixture.rs`, for the same reason the\n * warrant and login ones are: the domains are `pub(crate)` there and the field\n * order is implicit in a `derive`, so nothing on that side forces anyone to\n * notice this file depends on both. This module's test asserts the same vectors.\n */\n\nimport {\n concat,\n domainHash,\n fromHex,\n hex,\n importSigningKey,\n u32le,\n u64le,\n} from \"../crypto/internal.js\";\n\nconst SIGN_DOMAIN = new TextEncoder().encode(\"calimero.auth.request.v1\");\n\n/**\n * Its own domain, distinct from the signing one.\n *\n * The two are different jobs on the same bytes: this produces the commitment,\n * the other signs over it. Sharing a domain would make the commitment a\n * truncated disclosure of bytes something signs.\n */\nconst BODY_DOMAIN = new TextEncoder().encode(\"calimero.auth.request.body.v1\");\n\n/** A borsh `String`: its own `u32` little-endian length, then the UTF-8. */\nfunction borshString(value: string): Uint8Array {\n const bytes = new TextEncoder().encode(value);\n return concat(u32le(bytes.length), bytes);\n}\n\n/**\n * Commit to a request body.\n *\n * Always call it, including for a body-less request: an empty body hashes to a\n * value like any other, and there is no \"absent body\" encoding for a verifier\n * to disagree with you about.\n */\nexport async function requestBodyHash(\n body: Uint8Array | string = new Uint8Array(0),\n): Promise<Uint8Array> {\n const bytes =\n typeof body === \"string\" ? new TextEncoder().encode(body) : body;\n return domainHash(BODY_DOMAIN, [bytes]);\n}\n\n/** What a caller is about to send. */\nexport interface RequestSigInput {\n /**\n * The HTTP method, exactly as it will appear on the wire \u2014 `GET`, `POST`,\n * `HEAD`. Not folded: see the module note.\n */\n method: string;\n /**\n * The path, **without** the query string. `/admin-api/namespaces`, not\n * `/admin-api/namespaces?token=\u2026`.\n */\n path: string;\n /**\n * The request body, or nothing. Pass exactly the bytes you will send: the\n * signature commits to their hash, so a body re-serialized between signing\n * and sending is a signature for a different request.\n */\n body?: Uint8Array | string;\n /** Unix seconds at signing. */\n issuedAt: number | bigint;\n /**\n * Unix seconds after which the node must refuse it.\n *\n * Keep it short \u2014 minutes, not hours. It is what bounds replay, and nothing\n * else does.\n */\n expiresAt: number | bigint;\n /**\n * The ed25519 signing secret, hex (32 bytes): the session key on the\n * three-link chain, the device key on the two-link one.\n *\n * Never sent anywhere. It signs locally and only the signature travels.\n */\n signerSecret: string;\n}\n\n/**\n * Sign one request and return it hex-encoded, ready to present.\n *\n * Returns the encoding rather than an object for the reason `signWarrant` and\n * `signLoginStatement` do: the signature covers exactly these bytes, and a\n * caller that rebuilt the fields from JSON would have a second spelling able to\n * disagree with what was signed.\n */\nexport async function signRequest(input: RequestSigInput): Promise<string> {\n const key = await importSigningKey(input.signerSecret);\n const bodyHash = await requestBodyHash(input.body);\n\n const issuedAt = u64le(input.issuedAt);\n const expiresAt = u64le(input.expiresAt);\n\n const encoder = new TextEncoder();\n const preimage = await domainHash(SIGN_DOMAIN, [\n encoder.encode(input.method),\n encoder.encode(input.path),\n bodyHash,\n issuedAt,\n expiresAt,\n ]);\n\n const signature = new Uint8Array(\n await crypto.subtle.sign({ name: \"Ed25519\" }, key, preimage),\n );\n\n return hex(\n concat(\n borshString(input.method),\n borshString(input.path),\n bodyHash,\n issuedAt,\n expiresAt,\n signature,\n ),\n );\n}\n\n/** Re-exported so a caller can check a hex signer key before signing with it. */\nexport { fromHex };\n", "/**\n * Assemble the credential a node reads from `X-Calimero-Proof`.\n *\n * A request-carried proof is three links, and the node needs all of them in one\n * header: the account root certified the device, the device (optionally)\n * certified a short-lived session key, and that key signed THIS request. Each\n * link is minted by its own function here \u2014 {@link signDeviceCert},\n * {@link signLoginStatement}, {@link signRequest} \u2014 and each already returns\n * the borsh encoding of the Rust type the node deserializes. Rust's\n * `CallerProof` is a plain struct of those three fields, and borsh writes a\n * struct as its fields back to back, so assembling the header is concatenation\n * rather than a fourth encoder.\n *\n * That is worth stating because it is load-bearing and it is not obvious: if it\n * were re-encoded here there would be a second spelling of a signed structure,\n * able to disagree with the one that was signed. `caller-proof.test.ts` pins the\n * concatenation against bytes produced by core itself.\n */\n\nimport { fromHexUnsized, hex, concat } from \"../crypto/internal.js\";\n\n/** The three links, each already hex-encoded by the function that signed it. */\nexport interface CallerProofInput {\n /**\n * `signDeviceCert(...)` \u2014 the account root's certificate over the device key,\n * with the genesis that names the account.\n */\n credential: string;\n /**\n * `signLoginStatement(...)`, when signing with a session key.\n *\n * Omit it on the two-link chain, where the device key signs the request\n * itself. Omitting is not the same as passing an empty string: borsh writes\n * `Option` as a tag byte, so the two produce different bytes and only one of\n * them parses.\n */\n session?: string;\n /** `signRequest(...)` \u2014 the signature over this exact method, path and body. */\n request: string;\n}\n\n/**\n * Concatenate the three links into the header value.\n *\n * Returns hex, ready to present as `X-Calimero-Proof`.\n */\nexport function callerProof(input: CallerProofInput): string {\n const credential = fromHexUnsized(input.credential, \"credential\");\n const request = fromHexUnsized(input.request, \"request signature\");\n\n // Borsh `Option<T>`: a single tag byte, then the value when present. A\n // two-link chain is `None`, not an empty `Some` \u2014 the node reads the tag\n // before anything else, so getting it backwards fails to parse rather than\n // failing to verify, which at least is loud.\n const session =\n input.session === undefined\n ? new Uint8Array([0])\n : concat(new Uint8Array([1]), fromHexUnsized(input.session, \"session statement\"));\n\n return hex(concat(credential, session, request));\n}\n", "/**\n * Turn a device key into something the HTTP client can call per request.\n *\n * A session is a bearer token: mint once, present many times. A request-carried\n * proof is the opposite \u2014 it commits to one method, one path and one body, so\n * it has to be produced at the moment of the call, by something holding the\n * signing key. This is that something, shaped like `getAuthToken` so the key\n * stays with the caller and never reaches the transport.\n */\n\nimport { callerProof } from \"./caller-proof.js\";\nimport { signRequest } from \"./request-sig.js\";\n\n/** What the transport knows about the call it is about to make. */\nexport interface ProofRequest {\n /** `GET`, `POST`, \u2026 exactly as it will be sent. */\n method: string;\n /** The URL path the node will see, **without** the query string. */\n path: string;\n /** The exact bytes that will be sent, if any. */\n body?: unknown;\n}\n\nexport interface ProofSignerOptions {\n /** `signDeviceCert(...)`, hex. */\n credential: string;\n /**\n * `signLoginStatement(...)`, hex \u2014 the three-link chain.\n *\n * Omit for the two-link chain, where `signerSecret` is the device key itself.\n */\n session?: string;\n /**\n * The ed25519 secret that signs each request, hex: the session key when\n * `session` is set, otherwise the device key.\n */\n signerSecret: string;\n /**\n * How long each signature stays valid. Seconds, default 120.\n *\n * This is what bounds replay and nothing else does, so it is deliberately\n * short and deliberately not configurable to \"never\". A clock skew allowance\n * lives on the node; this only has to outlive the flight time.\n */\n ttlSeconds?: number;\n /** Unix seconds. Injectable so a test does not depend on the wall clock. */\n now?: () => number;\n}\n\n/**\n * The bytes a body will actually be sent as.\n *\n * Refuses what it cannot hash without consuming or re-encoding. A stream can be\n * read once, and reading it here would send an empty body; `FormData` and\n * `URLSearchParams` are serialized by `fetch` in ways this cannot reproduce\n * byte for byte. Signing a guess would produce a signature over bytes that\n * never travelled, and the node would reject it with a signature error that\n * says nothing about the real cause \u2014 so this throws, naming the type.\n */\nfunction bodyBytes(body: unknown): Uint8Array | string {\n if (body === undefined || body === null) return new Uint8Array(0);\n if (typeof body === \"string\") return body;\n if (body instanceof Uint8Array) return body;\n if (body instanceof ArrayBuffer) return new Uint8Array(body);\n if (ArrayBuffer.isView(body)) {\n return new Uint8Array(body.buffer, body.byteOffset, body.byteLength);\n }\n throw new Error(\n `a request-carried proof cannot commit to a ${\n (body as { constructor?: { name?: string } })?.constructor?.name ?? typeof body\n } body: pass a string or bytes, or use a session for this call`,\n );\n}\n\n/**\n * Build the `X-Calimero-Proof` value for one request.\n *\n * Pass the result to the HTTP client's `getProof`.\n */\nexport function createProofSigner(\n options: ProofSignerOptions,\n): (request: ProofRequest) => Promise<string> {\n const ttl = options.ttlSeconds ?? 120;\n const clock = options.now ?? (() => Math.floor(Date.now() / 1000));\n\n return async (request: ProofRequest): Promise<string> => {\n const issuedAt = clock();\n return callerProof({\n credential: options.credential,\n session: options.session,\n request: await signRequest({\n method: request.method,\n path: request.path,\n body: bodyBytes(request.body),\n issuedAt,\n expiresAt: issuedAt + ttl,\n signerSecret: options.signerSecret,\n }),\n });\n };\n}\n", "import type { HttpClient } from '../http-client/index.js';\n\n/** Result of the owner-driven `migrate_my_entries` convert (counts are u32). */\nexport interface MigrateMyEntriesSummary {\n converted: number;\n remaining: number;\n}\n\nexport interface ExecuteParams {\n contextId: string;\n method: string;\n argsJson?: Record<string, unknown>;\n /** @deprecated No longer used by the server. Ignored if provided. */\n executorPublicKey?: string;\n}\n\nexport class RpcError extends Error {\n code: number;\n type?: string;\n data?: unknown;\n\n constructor(code: number, message: string, data?: unknown, type?: string) {\n super(message);\n this.name = 'RpcError';\n this.code = code;\n this.data = data;\n this.type = type;\n }\n}\n\ninterface JsonRpcResponse {\n jsonrpc: string;\n id: number;\n result?: {\n output?: unknown;\n [key: string]: unknown;\n };\n error?: {\n // Standard JSON-RPC fields\n code?: number;\n message?: string;\n // Server-specific fields\n type?: string;\n data?: unknown;\n };\n}\n\n/**\n * POST a JSON-RPC call and unwrap `result`, converting an `error` payload into\n * an `RpcError`. The node returns `{ type, data }` (not always `{ code,\n * message }`), so this is the one place that normalization lives \u2014 every\n * JSON-RPC caller in this SDK shares it rather than re-deriving a bare\n * `Error(message)` and dropping the typed detail.\n */\nexport async function jsonRpcCall<T>(\n httpClient: HttpClient,\n method: string,\n params: unknown,\n): Promise<T> {\n const response = await httpClient.post<JsonRpcResponse>('/jsonrpc', {\n jsonrpc: '2.0',\n id: 1,\n method,\n params,\n });\n\n if (response.error) {\n const err = response.error;\n throw new RpcError(err.code ?? -1, err.message ?? err.type ?? 'RPC error', err.data, err.type);\n }\n\n return response.result as T;\n}\n\nexport class RpcClient {\n private httpClient: HttpClient;\n\n constructor(opts: { httpClient: HttpClient }) {\n this.httpClient = opts.httpClient;\n }\n\n async execute<T = unknown>(params: ExecuteParams): Promise<T> {\n const result = await jsonRpcCall<JsonRpcResponse['result']>(this.httpClient, 'execute', {\n contextId: params.contextId,\n method: params.method,\n argsJson: params.argsJson ?? {},\n });\n\n if (result && 'output' in result) {\n return result.output as T;\n }\n\n return result as T;\n }\n\n /**\n * One-tap owner-driven convert: re-signs the caller's identity-gated entries\n * to the current schema. The export converts all of the caller's\n * below-target entries in a single sweep, so this issues one call and returns\n * the resulting summary \u2014 it does not loop.\n */\n async migrateMyEntries(contextId: string): Promise<MigrateMyEntriesSummary> {\n return this.execute<MigrateMyEntriesSummary>({ contextId, method: 'migrate_my_entries' });\n }\n\n /** Read-only count of the caller's entries still below the target schema. */\n async countMyPending(contextId: string): Promise<number> {\n return this.execute<number>({ contextId, method: 'count_my_pending' });\n }\n}\n", "/** Parsed group-membership event (core's untagged `NodeEvent` group variant). */\nexport interface GroupMembershipEventData {\n groupId: string;\n type: 'MemberJoined' | 'MemberAdded' | 'MemberRemoved';\n data: {\n /** The member's ACCOUNT, hex-encoded 32 bytes (64 chars) - the principal the\n * governance rows name. Not a signing key: the field this replaced\n * carried one, and both are strings, so the rename is what makes a stale\n * consumer fail loudly instead of comparing accounts against keys forever. */\n memberAccount: string;\n /** Absent on `MemberRemoved`, and on a `MemberJoined` inherited from an Open\n * subgroup. Core's set is closed. */\n role?: 'Admin' | 'Member' | 'ReadOnly' | 'ReadOnlyTee';\n };\n}\n\n/**\n * A migration was accepted and its target ABI state version resolved.\n * `toStateVersion` is the `#[app::state(version = N)]` number, not the bundle\n * semver (`toVersion`).\n */\nexport interface MigrationStartedData {\n fromVersion: string;\n toVersion: string;\n toStateVersion: number;\n /** Contexts the EMITTING NODE enumerated for the migration. Node-local, never\n * the fleet cohort size that `MigrationProgressData.total` carries. */\n localContextsTotal: number;\n}\n\n/**\n * Fleet rollup counters, recomputed as peers' heartbeat facts change. Counters\n * only: the per-member detail behind them stays in the admin-gated\n * `getMigrationStatus` read.\n */\nexport interface MigrationProgressData {\n migrated: number;\n inProgress: number;\n unknown: number;\n failed: number;\n /** The pinned FLEET COHORT size, never a node-local context count like\n * `MigrationStartedData.localContextsTotal`. */\n total: number;\n}\n\n/**\n * One subgroup's local context swaps advanced on the emitting node.\n *\n * Admin-only: it names a descendant subgroup, which a plain namespace member\n * may not enumerate, so core withholds it from non-admin subscribers. A\n * non-admin never receiving this variant is correct behaviour, not a gap.\n */\nexport interface CascadeProgressData {\n /** Hex-encoded descendant subgroup id, never the namespace root the event is\n * keyed on. */\n subgroupId: string;\n localContextsSwapped: number;\n /** Contexts the emitting node holds for THIS SUBGROUP. Node-local and\n * per-subgroup; unrelated to both sibling totals. */\n localContextsTotal: number;\n}\n\n/** Every pinned-cohort member reported the target version. */\nexport interface MigrationCompletedData {\n toVersion: string;\n /** FLEET convergence, in seconds since the epoch: the same stamp\n * `MigrationStatus.fleetCompletedAt` returns. Not `GroupUpgradeStatus.completedAt`,\n * which is this node's own local swap completion. */\n completedAt: number;\n}\n\n/**\n * Parsed group-migration event (core's `GroupMigration` `NodeEvent` variant),\n * keyed on the namespace root a client subscribes with.\n *\n * `MigrationStarted`, `MigrationProgress` and `MigrationCompleted` reach every\n * subscribed member; `CascadeProgress` reaches namespace admins only.\n */\nexport type GroupMigrationEventData =\n | { groupId: string; type: 'MigrationStarted'; data: MigrationStartedData }\n | { groupId: string; type: 'MigrationProgress'; data: MigrationProgressData }\n | { groupId: string; type: 'CascadeProgress'; data: CascadeProgressData }\n | { groupId: string; type: 'MigrationCompleted'; data: MigrationCompletedData };\n\nconst MIGRATION_EVENT_TYPES: ReadonlySet<string> = new Set([\n 'MigrationStarted',\n 'MigrationProgress',\n 'CascadeProgress',\n 'MigrationCompleted',\n]);\n\n/** Narrows a raw `'event'` payload to the group-migration family by its tag. */\nexport function isGroupMigrationEvent(\n event: { type?: string },\n): event is GroupMigrationEventData {\n return event.type !== undefined && MIGRATION_EVENT_TYPES.has(event.type);\n}\n", "import type { GroupMembershipEventData, GroupMigrationEventData } from './group.js';\nimport { isGroupMigrationEvent } from './group.js';\nimport { AuthRevokedError, HTTPError } from '../http-client/index.js';\n\n/**\n * `x-auth-error` reasons that mean the whole token family is gone, mirroring\n * the request path in `web-client.ts`. Anything else \u2014 `token_expired` above\n * all \u2014 is recoverable by refreshing and reconnecting.\n */\nconst TERMINAL_AUTH_ERRORS = new Set(['token_reuse', 'token_revoked']);\n\n/**\n * Statuses a terminal `x-auth-error` can arrive on.\n *\n * Measured against merod 0.11.0-rc.41: a revoked token family answers\n * `GET /sse` and `POST /sse/subscription` with **403**, `x-auth-error:\n * token_revoked` and `content-length: 0` \u2014 `token_expired` is the 401.\n * Keying the check on 401 alone made the branch below unreachable for the one\n * reason a stream actually sees, so a dead token family surfaced as a bare\n * `HTTPError(403)` and reconnected on a timer forever.\n */\nconst TERMINAL_AUTH_STATUSES = new Set([401, 403]);\n\n/**\n * Whether a failed response means the whole token family is gone, so the caller\n * must re-authenticate and the client must stop reconnecting.\n *\n * The body carries nothing on this path (`content-length: 0`), so the header is\n * the only evidence there is.\n */\nfunction isTerminalAuthFailure(response: { status: number; headers: Headers }): string | null {\n const authError = response.headers.get('x-auth-error');\n if (!authError) return null;\n if (!TERMINAL_AUTH_STATUSES.has(response.status)) return null;\n return TERMINAL_AUTH_ERRORS.has(authError) ? authError : null;\n}\n\nexport type { GroupMembershipEventData, GroupMigrationEventData };\n\nexport interface SseEventData {\n contextId: string;\n /** The context-event discriminator (core serializes it as a sibling of\n * `data`, e.g. `\"AppVersionChanged\"`). Undefined for events without a tag. */\n type?: string;\n data: unknown;\n}\n\n/** Parsed `AppVersionChanged` context event (bundle-version flip; skew #2). */\nexport interface AppVersionChangedEvent {\n contextId: string;\n fromVersion?: string;\n toVersion?: string;\n}\n\n/** Anything the `'event'` stream can deliver: context events plus the\n * group-keyed membership and migration families. */\ntype SseEvent = SseEventData | GroupMembershipEventData | GroupMigrationEventData;\ntype SseEventHandler = (event: SseEvent) => void;\ntype SseConnectHandler = (sessionId: string) => void;\ntype SseErrorHandler = (error: Error) => void;\n\ninterface SseListeners {\n connect: SseConnectHandler[];\n event: SseEventHandler[];\n error: SseErrorHandler[];\n}\n\nexport class SseClient {\n private baseUrl: string;\n private getAuthToken: () => Promise<string>;\n private reconnectDelayMs: number;\n private sessionId: string | null = null;\n private abortController: AbortController | null = null;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private subscribedContextIds: Set<string> = new Set();\n private subscribedGroupIds: Set<string> = new Set();\n private closed = false;\n private listeners: SseListeners = { connect: [], event: [], error: [] };\n private fetchImpl: typeof fetch;\n\n constructor(opts: {\n baseUrl: string;\n getAuthToken: () => Promise<string>;\n reconnectDelayMs?: number;\n fetch?: typeof fetch;\n }) {\n this.baseUrl = opts.baseUrl.replace(/\\/+$/, '');\n this.getAuthToken = opts.getAuthToken;\n this.reconnectDelayMs = opts.reconnectDelayMs ?? 3000;\n // globalThis.fetch must be called as a method on globalThis, not through a\n // variable, or browsers throw \"Illegal invocation\".\n this.fetchImpl = (url: RequestInfo | URL, init?: RequestInit) =>\n opts.fetch ? opts.fetch(url, init) : globalThis.fetch(url, init);\n }\n\n on(event: 'connect', handler: SseConnectHandler): void;\n on(event: 'event', handler: SseEventHandler): void;\n on(event: 'error', handler: SseErrorHandler): void;\n on(event: string, handler: SseConnectHandler | SseEventHandler | SseErrorHandler): void {\n const key = event as keyof SseListeners;\n if (key in this.listeners) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const arr = this.listeners[key] as any[];\n if (!arr.includes(handler)) arr.push(handler);\n }\n }\n\n off(event: 'connect', handler: SseConnectHandler): void;\n off(event: 'event', handler: SseEventHandler): void;\n off(event: 'error', handler: SseErrorHandler): void;\n off(event: string, handler: SseConnectHandler | SseEventHandler | SseErrorHandler): void {\n const key = event as keyof SseListeners;\n if (key in this.listeners) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const arr = this.listeners[key] as any[];\n const idx = arr.indexOf(handler);\n if (idx !== -1) arr.splice(idx, 1);\n }\n }\n\n /**\n * Typed convenience over the generic `'event'` stream: invokes `handler` only\n * for `AppVersionChanged` context events, with the payload already parsed.\n * Returns an unsubscribe closure (so callers need not retain the listener).\n */\n onAppVersionChanged(handler: (e: AppVersionChangedEvent) => void): () => void {\n const listener: SseEventHandler = (ev) => {\n // Core flattens the tagged payload: the event carries `type` as a sibling\n // of `data`, and `data` is the `{ fromVersion?, toVersion? }` payload.\n if (ev.type !== 'AppVersionChanged') return;\n const d = ev.data as { fromVersion?: string; toVersion?: string } | undefined;\n handler({ contextId: ev.contextId, fromVersion: d?.fromVersion, toVersion: d?.toVersion });\n };\n this.on('event', listener);\n return () => this.off('event', listener);\n }\n\n /**\n * Typed convenience over the generic `'event'` stream: invokes `handler` only\n * for group-migration events, narrowed on `type` so `data` is the matching\n * payload. Returns an unsubscribe closure.\n *\n * A non-admin subscriber sees `MigrationStarted`, `MigrationProgress` and\n * `MigrationCompleted` but never `CascadeProgress`, which core delivers to\n * namespace admins only.\n */\n onMigrationEvent(handler: (e: GroupMigrationEventData) => void): () => void {\n const listener: SseEventHandler = (ev) => {\n if (isGroupMigrationEvent(ev)) handler(ev);\n };\n this.on('event', listener);\n return () => this.off('event', listener);\n }\n\n private emit(event: 'connect', sessionId: string): void;\n private emit(event: 'event', data: SseEvent): void;\n private emit(event: 'error', error: Error): void;\n private emit(event: string, arg?: string | SseEvent | Error): void {\n const key = event as keyof SseListeners;\n if (key in this.listeners) {\n for (const handler of this.listeners[key]) {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (handler as any)(arg);\n } catch {\n // Swallow handler errors\n }\n }\n }\n }\n\n async connect(): Promise<void> {\n // Already connected \u2014 don't reconnect\n if (this.abortController && !this.closed) {\n return;\n }\n this.closed = false;\n this.abortController = new AbortController();\n\n try {\n const token = await this.getAuthToken();\n const url = `${this.baseUrl}/sse`;\n const response = await this.fetchImpl(url, {\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Accept': 'text/event-stream',\n },\n signal: this.abortController.signal,\n });\n\n if (!response.ok) {\n // Throw the same typed errors the request path throws. A bare\n // `Error(\"SSE connection failed: 401\")` forced callers to string-match\n // the status and guess what it meant, and the honest guess \u2014 treat any\n // 401 as fatal \u2014 logs the user out on a routine reconnect after the\n // access token ages out, discarding a refresh token that was still\n // good. The distinction is in the header; surface it.\n const terminal = isTerminalAuthFailure(response);\n const bodyText = await response.text().catch(() => undefined);\n\n if (terminal) {\n throw new AuthRevokedError(\n terminal,\n response.status,\n response.statusText,\n url,\n response.headers,\n bodyText,\n );\n }\n\n throw new HTTPError(\n response.status,\n response.statusText,\n url,\n response.headers,\n bodyText,\n );\n }\n\n if (!response.body) {\n throw new Error('SSE response has no body');\n }\n\n this.readStream(response.body).catch((err) => {\n if (this.closed) return;\n const error = err instanceof Error ? err : new Error(String(err));\n this.emit('error', error);\n this.scheduleReconnect();\n });\n } catch (err) {\n if (this.closed) return;\n const error = err instanceof Error ? err : new Error(String(err));\n this.emit('error', error);\n // Reconnecting against a revoked token family is a timer that never\n // succeeds: every attempt re-sends the same dead credential. The caller\n // has to re-authenticate, and an AuthRevokedError is how it is told.\n if (error instanceof AuthRevokedError) return;\n this.scheduleReconnect();\n }\n }\n\n private async readStream(body: ReadableStream<Uint8Array>): Promise<void> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n // Keep the last partial line in the buffer\n buffer = lines.pop() ?? '';\n\n for (const line of lines) {\n if (line.startsWith('data:')) {\n const jsonStr = line.slice(5).trim();\n if (jsonStr) {\n this.handleMessage(jsonStr);\n }\n }\n }\n }\n // Flush remaining bytes from the decoder\n buffer += decoder.decode();\n if (buffer.startsWith('data:')) {\n const jsonStr = buffer.slice(5).trim();\n if (jsonStr) {\n this.handleMessage(jsonStr);\n }\n }\n } catch (err) {\n if (this.closed) return;\n const error = err instanceof Error ? err : new Error(String(err));\n this.emit('error', error);\n }\n\n // Stream ended, reconnect\n if (!this.closed) {\n this.scheduleReconnect();\n }\n }\n\n private handleMessage(jsonStr: string): void {\n try {\n const msg = JSON.parse(jsonStr);\n\n // Connection message\n if (msg.type === 'connect' && msg.session_id) {\n this.sessionId = msg.session_id;\n this.emit('connect', msg.session_id);\n // Re-subscribe after reconnect\n if (this.subscribedContextIds.size > 0 || this.subscribedGroupIds.size > 0) {\n this.sendSubscription('subscribe', {\n contextIds: [...this.subscribedContextIds],\n groupIds: [...this.subscribedGroupIds],\n });\n }\n return;\n }\n\n if (!msg.result) return;\n\n let eventData = msg.result.data;\n // Decode byte-array data if needed\n if (Array.isArray(eventData)) {\n try {\n const bytes = new Uint8Array(eventData);\n const text = new TextDecoder().decode(bytes);\n eventData = JSON.parse(text);\n } catch {\n // Keep raw data\n }\n }\n\n // Context event message\n if (msg.result.contextId) {\n this.emit('event', {\n contextId: msg.result.contextId,\n // Forward the event tag (sibling of `data` in core's flattened\n // payload) so typed helpers like `onAppVersionChanged` can discriminate.\n type: msg.result.type,\n data: eventData,\n });\n return;\n }\n\n // Group-keyed event message (untagged NodeEvent's membership and\n // migration variants; the `type` tag tells them apart).\n if (msg.result.groupId) {\n this.emit('event', {\n groupId: msg.result.groupId,\n type: msg.result.type,\n data: eventData,\n });\n }\n } catch {\n // Invalid JSON, ignore\n }\n }\n\n async subscribe(idsOrOpts: string[] | { contextIds?: string[]; groupIds?: string[] }): Promise<void> {\n const opts = Array.isArray(idsOrOpts) ? { contextIds: idsOrOpts } : idsOrOpts;\n const newContextIds = (opts.contextIds ?? []).filter(id => !this.subscribedContextIds.has(id));\n const newGroupIds = (opts.groupIds ?? []).filter(id => !this.subscribedGroupIds.has(id));\n for (const id of opts.contextIds ?? []) {\n this.subscribedContextIds.add(id);\n }\n for (const id of opts.groupIds ?? []) {\n this.subscribedGroupIds.add(id);\n }\n if ((newContextIds.length > 0 || newGroupIds.length > 0) && this.sessionId) {\n await this.sendSubscription('subscribe', { contextIds: newContextIds, groupIds: newGroupIds });\n }\n }\n\n async unsubscribe(idsOrOpts: string[] | { contextIds?: string[]; groupIds?: string[] }): Promise<void> {\n const opts = Array.isArray(idsOrOpts) ? { contextIds: idsOrOpts } : idsOrOpts;\n const hadContextIds = (opts.contextIds ?? []).filter(id => this.subscribedContextIds.has(id));\n const hadGroupIds = (opts.groupIds ?? []).filter(id => this.subscribedGroupIds.has(id));\n for (const id of opts.contextIds ?? []) {\n this.subscribedContextIds.delete(id);\n }\n for (const id of opts.groupIds ?? []) {\n this.subscribedGroupIds.delete(id);\n }\n if ((hadContextIds.length > 0 || hadGroupIds.length > 0) && this.sessionId) {\n await this.sendSubscription('unsubscribe', { contextIds: hadContextIds, groupIds: hadGroupIds });\n }\n }\n\n private async sendSubscription(\n method: 'subscribe' | 'unsubscribe',\n ids: { contextIds?: string[]; groupIds?: string[] },\n ): Promise<void> {\n try {\n const token = await this.getAuthToken();\n const params: { contextIds?: string[]; groupIds?: string[] } = {};\n if (ids.contextIds && ids.contextIds.length > 0) params.contextIds = ids.contextIds;\n if (ids.groupIds && ids.groupIds.length > 0) params.groupIds = ids.groupIds;\n const response = await this.fetchImpl(`${this.baseUrl}/sse/subscription`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n id: this.sessionId,\n method,\n params,\n }),\n });\n if (!response.ok) {\n // The subscribe path used to report only the status. On the one failure\n // that matters it is the whole of what the caller got: a revoked family\n // answers 403 with an empty body, so a bare `SSE subscribe failed: 403`\n // discarded the only evidence of what went wrong \u2014 and a 403 here reads\n // identically to the unrelated \"not the session owner\" refusal, which\n // does carry a body.\n const terminal = isTerminalAuthFailure(response);\n const bodyText = await response.text().catch(() => undefined);\n this.emit(\n 'error',\n terminal\n ? new AuthRevokedError(\n terminal,\n response.status,\n response.statusText,\n `${this.baseUrl}/sse/subscription`,\n response.headers,\n bodyText,\n )\n : new HTTPError(\n response.status,\n response.statusText,\n `${this.baseUrl}/sse/subscription`,\n response.headers,\n bodyText,\n ),\n );\n }\n } catch (err) {\n this.emit('error', err instanceof Error ? err : new Error(`SSE ${method} failed`));\n }\n }\n\n private forceReconnect(): void {\n if (this.abortController) {\n this.abortController.abort();\n this.abortController = null;\n }\n this.sessionId = null;\n this.connect();\n }\n\n private scheduleReconnect(): void {\n if (this.closed) return;\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n }\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n this.forceReconnect();\n }, this.reconnectDelayMs);\n }\n\n close(): void {\n this.closed = true;\n if (this.abortController) {\n this.abortController.abort();\n this.abortController = null;\n }\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n this.sessionId = null;\n this.subscribedContextIds.clear();\n this.subscribedGroupIds.clear();\n }\n}\n", "import type { GroupMembershipEventData, GroupMigrationEventData } from './group.js';\n\nexport type { GroupMembershipEventData, GroupMigrationEventData };\n\nexport interface WsEventData {\n contextId: string;\n /** The context-event discriminator (core serializes it as a sibling of\n * `data`, e.g. `\"AppVersionChanged\"`). Undefined for events without a tag. */\n type?: string;\n data: unknown;\n}\n\n/** Anything the `'event'` stream can deliver: context events plus the\n * group-keyed membership and migration families. */\ntype WsEvent = WsEventData | GroupMembershipEventData | GroupMigrationEventData;\ntype WsEventHandler = (event: WsEvent) => void;\ntype WsConnectHandler = () => void;\ntype WsErrorHandler = (error: Error) => void;\n\ninterface WsListeners {\n connect: WsConnectHandler[];\n event: WsEventHandler[];\n error: WsErrorHandler[];\n}\n\n/**\n * @experimental WebSocket event client. The SSE client (`SseClient`) is the\n * recommended transport for production \u2014 use `MeroJs.events` instead of `MeroJs.ws`.\n */\nexport class WsClient {\n private baseUrl: string;\n private getAuthToken: () => Promise<string>;\n private ws: WebSocket | null = null;\n private closed = false;\n private reconnectAttempt = 0;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private subscribedContextIds: Set<string> = new Set();\n private subscribedGroupIds: Set<string> = new Set();\n private listeners: WsListeners = { connect: [], event: [], error: [] };\n\n private static readonly MAX_BACKOFF_MS = 30000;\n\n constructor(opts: {\n baseUrl: string;\n getAuthToken: () => Promise<string>;\n }) {\n this.baseUrl = opts.baseUrl.replace(/\\/+$/, '');\n this.getAuthToken = opts.getAuthToken;\n }\n\n on(event: 'connect', handler: WsConnectHandler): void;\n on(event: 'event', handler: WsEventHandler): void;\n on(event: 'error', handler: WsErrorHandler): void;\n on(event: string, handler: WsConnectHandler | WsEventHandler | WsErrorHandler): void {\n const key = event as keyof WsListeners;\n if (key in this.listeners) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const arr = this.listeners[key] as any[];\n if (!arr.includes(handler)) arr.push(handler);\n }\n }\n\n off(event: 'connect', handler: WsConnectHandler): void;\n off(event: 'event', handler: WsEventHandler): void;\n off(event: 'error', handler: WsErrorHandler): void;\n off(event: string, handler: WsConnectHandler | WsEventHandler | WsErrorHandler): void {\n const key = event as keyof WsListeners;\n if (key in this.listeners) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const arr = this.listeners[key] as any[];\n const idx = arr.indexOf(handler);\n if (idx !== -1) arr.splice(idx, 1);\n }\n }\n\n private emit(event: 'connect'): void;\n private emit(event: 'event', data: WsEvent): void;\n private emit(event: 'error', error: Error): void;\n private emit(event: string, arg?: WsEvent | Error): void {\n const key = event as keyof WsListeners;\n if (key in this.listeners) {\n for (const handler of this.listeners[key]) {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (handler as any)(arg);\n } catch {\n // Swallow handler errors\n }\n }\n }\n }\n\n async connect(): Promise<void> {\n if (this.ws && (this.ws.readyState === 0 /* CONNECTING */ || this.ws.readyState === 1 /* OPEN */)) {\n return;\n }\n this.closed = false;\n\n try {\n const token = await this.getAuthToken();\n if (!token) {\n throw new Error('No authentication token available for WebSocket connection');\n }\n // Browser WebSocket API can't set headers, use query param\n const wsUrl = this.baseUrl.replace(/^http/, 'ws');\n this.ws = new WebSocket(`${wsUrl}/ws?token=${encodeURIComponent(token)}`);\n\n this.ws.onopen = () => {\n this.reconnectAttempt = 0;\n this.emit('connect');\n this.resubscribeAfterReconnect();\n };\n\n this.ws.onmessage = (event) => {\n this.handleMessage(event.data);\n };\n\n this.ws.onerror = () => {\n this.emit('error', new Error('WebSocket error'));\n };\n\n this.ws.onclose = () => {\n if (!this.closed) {\n this.scheduleReconnect();\n }\n };\n } catch (err) {\n if (this.closed) return;\n const error = err instanceof Error ? err : new Error(String(err));\n this.emit('error', error);\n this.scheduleReconnect();\n }\n }\n\n private handleMessage(raw: string | ArrayBuffer | Blob): void {\n if (typeof raw !== 'string') return;\n\n try {\n const msg = JSON.parse(raw);\n\n if (!msg.result) return;\n\n let eventData = msg.result.data;\n if (Array.isArray(eventData)) {\n try {\n const bytes = new Uint8Array(eventData);\n const text = new TextDecoder().decode(bytes);\n eventData = JSON.parse(text);\n } catch {\n // Keep raw data\n }\n }\n\n if (msg.result.contextId) {\n this.emit('event', {\n contextId: msg.result.contextId,\n // Forward the event tag (sibling of `data` in core's flattened\n // payload) so consumers can discriminate, matching `SseClient`.\n type: msg.result.type,\n data: eventData,\n });\n return;\n }\n\n // Group-keyed event message (untagged NodeEvent's membership and\n // migration variants; the `type` tag tells them apart).\n if (msg.result.groupId) {\n this.emit('event', {\n groupId: msg.result.groupId,\n type: msg.result.type,\n data: eventData,\n });\n }\n } catch {\n // Invalid JSON, ignore\n }\n }\n\n subscribe(idsOrOpts: string[] | { contextIds?: string[]; groupIds?: string[] }): void {\n const opts = Array.isArray(idsOrOpts) ? { contextIds: idsOrOpts } : idsOrOpts;\n for (const id of opts.contextIds ?? []) {\n this.subscribedContextIds.add(id);\n }\n for (const id of opts.groupIds ?? []) {\n this.subscribedGroupIds.add(id);\n }\n\n if (this.ws && this.ws.readyState === 1 /* OPEN */) {\n this.sendMessage({\n id: null,\n method: 'subscribe',\n params: this.buildSubscriptionParams(opts.contextIds ?? [], opts.groupIds ?? []),\n });\n }\n }\n\n unsubscribe(idsOrOpts: string[] | { contextIds?: string[]; groupIds?: string[] }): void {\n const opts = Array.isArray(idsOrOpts) ? { contextIds: idsOrOpts } : idsOrOpts;\n for (const id of opts.contextIds ?? []) {\n this.subscribedContextIds.delete(id);\n }\n for (const id of opts.groupIds ?? []) {\n this.subscribedGroupIds.delete(id);\n }\n\n if (this.ws && this.ws.readyState === 1 /* OPEN */) {\n this.sendMessage({\n id: null,\n method: 'unsubscribe',\n params: this.buildSubscriptionParams(opts.contextIds ?? [], opts.groupIds ?? []),\n });\n }\n }\n\n private resubscribeAfterReconnect(): void {\n if (this.subscribedContextIds.size === 0 && this.subscribedGroupIds.size === 0) return;\n this.sendMessage({\n id: null,\n method: 'subscribe',\n params: this.buildSubscriptionParams([...this.subscribedContextIds], [...this.subscribedGroupIds]),\n });\n }\n\n private buildSubscriptionParams(\n contextIds: string[],\n groupIds: string[],\n ): { contextIds?: string[]; groupIds?: string[] } {\n const params: { contextIds?: string[]; groupIds?: string[] } = {};\n if (contextIds.length > 0) params.contextIds = contextIds;\n if (groupIds.length > 0) params.groupIds = groupIds;\n return params;\n }\n\n private sendMessage(msg: unknown): void {\n if (this.ws && this.ws.readyState === 1 /* OPEN */) {\n this.ws.send(JSON.stringify(msg));\n }\n }\n\n private scheduleReconnect(): void {\n if (this.closed) return;\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n }\n const delay = Math.min(\n 1000 * Math.pow(2, this.reconnectAttempt),\n WsClient.MAX_BACKOFF_MS,\n );\n this.reconnectAttempt++;\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n this.connect();\n }, delay);\n }\n\n close(): void {\n this.closed = true;\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n if (this.ws) {\n this.ws.onclose = null;\n this.ws.close();\n this.ws = null;\n }\n this.subscribedContextIds.clear();\n this.subscribedGroupIds.clear();\n }\n}\n", "import type { Codec } from './types.js';\n\n/** Default codec: JSON text as a byte array. The wire carries `state` as\n * `number[]`, not a string, so this encodes to bytes rather than to text. */\nexport function jsonCodec<T>(): Codec<T> {\n return {\n encode(value: T): number[] {\n return Array.from(new TextEncoder().encode(JSON.stringify(value)));\n },\n decode(bytes: number[]): T {\n return JSON.parse(new TextDecoder().decode(new Uint8Array(bytes))) as T;\n },\n };\n}\n", "import type { HttpClient } from '../http-client/index.js';\nimport type { SseClient } from '../events/index.js';\nimport { jsonRpcCall } from '../rpc/index.js';\nimport { jsonCodec } from './codec.js';\nimport type { Codec, EphemeralEntry } from './types.js';\n\nexport { jsonCodec };\nexport type { Codec, EphemeralEntry };\n\n/**\n * Ephemeral presence: transient, encrypted, never-persisted state (cursors,\n * typing, presence) that gossips between nodes without a WASM run or DAG\n * growth.\n *\n * The write/read asymmetry is the model, not an oversight. `set` takes no\n * author: you can only ever write your OWN slot, and the node resolves the\n * author server-side from its owned context identity \u2014 which is also why a\n * client cannot publish as somebody else. `subscribe` returns everyone's\n * slots, so each carries its `author`. The store is N independent\n * single-writer registers keyed by author; there is no merge across authors.\n *\n * There is a single read path: `subscribe`. On subscribing to a context, the\n * node replays that context's current presence to this connection as\n * ordinary `Ephemeral` events (carrying `ageMs`), before any live deltas\n * (which carry no `ageMs`). There is no separate snapshot RPC.\n */\nexport class EphemeralClient {\n private httpClient: HttpClient;\n private sse: SseClient;\n\n constructor(opts: { httpClient: HttpClient; sse: SseClient }) {\n this.httpClient = opts.httpClient;\n this.sse = opts.sse;\n }\n\n /**\n * Publish your own presence slice, replacing it wholesale.\n *\n * Rejects with an `RpcError` (not a bare `Error`), so a typed failure such as\n * an oversized slice keeps its `type` and `data` \u2014 e.g. `SliceTooLarge` with\n * the offending size \u2014 and is `instanceof RpcError` like every other RPC in\n * this SDK.\n */\n async set<T>(contextId: string, state: T, codec: Codec<T> = jsonCodec<T>()): Promise<void> {\n await jsonRpcCall<Record<string, never>>(this.httpClient, 'set_ephemeral', {\n contextId,\n state: codec.encode(state),\n });\n }\n\n /**\n * Observe presence changes for a context.\n *\n * This adds NO transport \u2014 it is a typed filter over the existing SSE event\n * stream, which already carries `{ contextId, type, data }`. On subscribing,\n * the node replays that context's current presence to this connection as\n * ordinary `Ephemeral` events before any live deltas; a replayed entry\n * carries `ageMs`, a live delta does not (never synthesized as `0` \u2014 absent\n * and zero mean different things). Returns an unsubscribe function.\n */\n subscribe<T>(\n contextId: string,\n handler: (entry: EphemeralEntry<T>) => void,\n codec: Codec<T> = jsonCodec<T>(),\n ): () => void {\n const listener = (event: unknown): void => {\n const e = event as { contextId?: string; type?: string; data?: unknown };\n if (e.type !== 'Ephemeral' || e.contextId !== contextId) return;\n\n const data = e.data as\n | { author?: string; state?: number[]; removed?: boolean; ageMs?: number }\n | undefined;\n if (!data?.author) return;\n\n // `removed` is omitted on an upsert, so normalize to a boolean here and\n // spare every caller the truthiness rule.\n const removed = Boolean(data.removed);\n const entry: EphemeralEntry<T> = {\n author: data.author,\n state: removed || data.state === undefined ? undefined : codec.decode(data.state),\n removed,\n };\n // `ageMs` is present only on a replayed seed entry; pass it through as-is\n // rather than defaulting a live delta to 0.\n if (data.ageMs !== undefined) entry.ageMs = data.ageMs;\n handler(entry);\n };\n\n this.sse.on('event', listener);\n // Errors surface via the SSE client's own 'error' event; nothing more to\n // do with them here.\n void this.sse.connect().catch(() => undefined);\n void this.sse.subscribe([contextId]).catch(() => undefined);\n\n return () => {\n this.sse.off('event', listener);\n };\n }\n}\n", "/**\n * Minting and certifying a device offline, with a key that never reaches a node.\n *\n * This is the second half of the keyholder story. `signWarrant` lets an account\n * with no node *write*; this lets one exist in the first place \u2014 an account root\n * certifies a device, and the resulting credential is what a joiner presents.\n *\n * As with the warrant signer, the risk is that this reproduces core's encoding\n * and has to keep doing so. The answer is the same: a golden vector here, and an\n * e2e that diffs this output against `merod account sign-cert` byte for byte.\n *\n * Everything below is derived from core:\n *\n * - `DeviceCert::signing_payload` \u2014 `crates/account/src/device.rs`\n * - `DeviceId::mint` \u2014 `crates/primitives/src/identity.rs`\n * - `AccountProof` / `AccountGenesis` borsh layout \u2014 `crates/account/src/{signed,account}.rs`\n */\nimport {\n concat,\n derivePublicKey,\n domainHash,\n fromHex,\n hex,\n importSigningKey,\n u32le,\n} from '../crypto/internal.js';\n\nconst CERT_DOMAIN = new TextEncoder().encode('calimero.device.cert.v1');\nconst ACCOUNT_ID_DOMAIN = new TextEncoder().encode('calimero.account.genesis.v1');\nconst DEVICE_ID_DOMAIN = new TextEncoder().encode('calimero.device.id.v1');\n\n/** `AccountGenesis::version`, which the credential's borsh encoding leads with. */\n/**\n * Core's `ACCOUNT_GENESIS_VERSION`. It is part of the `AccountId` preimage, so\n * this value decides which account a root key names \u2014 a mismatch does not fail\n * as a version error at the edge, it silently derives a different account and\n * core refuses the credential as unverifiable.\n *\n * `2` since the genesis dropped its per-namespace nonce. This was `1`, which\n * produced credentials current core rejects outright; the shape was already v2,\n * only the tag was stale.\n */\nconst ACCOUNT_GENESIS_VERSION = 2;\n\n/** What a root certifies about one device. */\nexport interface DeviceCertInput {\n /** The account root's signing secret, 64 hex. Signs the certificate. */\n rootSecret: string;\n /** The device being certified, 64 hex \u2014 see {@link mintDeviceId}. */\n device: string;\n /** The key that device signs with, 64 hex. */\n signPublicKey: string;\n /** The key the group key is delivered to, 64 hex (X25519). */\n kemPublicKey: string;\n /**\n * Must strictly exceed any epoch already folded for this device.\n *\n * The projection refuses a link that does not advance it, so a re-issued\n * certificate reusing an epoch is inert rather than a rollback.\n */\n deviceEpoch: number;\n}\n\n/**\n * Mint a device id: `H(DEVICE_ID_DOMAIN, account \u2016 nonce)`.\n *\n * Derived from the account and a fresh nonce rather than from the device's keys,\n * so rotating a keypair keeps the replica identity \u2014 and with it the counter\n * slots and HLC lineage \u2014 intact.\n */\nexport async function mintDeviceId(\n account: string,\n nonce: Uint8Array,\n): Promise<string> {\n if (nonce.length !== 16) {\n throw new Error(`nonce must be 16 bytes, got ${nonce.length}`);\n }\n const accountBytes = fromHex(account, 'account', 32);\n return hex(await domainHash(DEVICE_ID_DOMAIN, [accountBytes, nonce]));\n}\n\n/**\n * The 32 bytes a root signs to certify a device.\n *\n * Both keys are covered, so neither the signing key nor the delivery key can be\n * substituted into a certificate that otherwise verifies \u2014 which is what stops a\n * relay carrying someone's credential from making itself the reader.\n */\nexport async function deviceCertPayload(input: {\n account: string;\n device: string;\n signPublicKey: string;\n kemPublicKey: string;\n keyEpoch: number;\n deviceEpoch: number;\n}): Promise<Uint8Array> {\n return domainHash(CERT_DOMAIN, [\n fromHex(input.account, 'account', 32),\n fromHex(input.device, 'device', 32),\n fromHex(input.signPublicKey, 'signPublicKey', 32),\n fromHex(input.kemPublicKey, 'kemPublicKey', 32),\n u32le(input.keyEpoch),\n u32le(input.deviceEpoch),\n ]);\n}\n\n/**\n * Borsh-encode an `AccountProof<DeviceCert>` \u2014 what core calls a\n * `JoinAccountCredential`.\n *\n * Extracted so the device-cert path and the namespace-op signer encode this from\n * one implementation. Two copies of a borsh layout drift silently: the encoding\n * is only checked by a peer rejecting a signature, which points at the signature\n * rather than at whichever copy went stale.\n *\n * Layout is `AccountProof { genesis, chain, statement }`:\n * `AccountGenesis` (version u8 + root pk 32) + `chain` (empty Vec, u32 len) +\n * `DeviceCert` (account 32 + device 32 + sign pk 32 + kem pk 32 + key_epoch u32\n * + device_epoch u32 + signature 64) = 237 bytes with an empty chain.\n */\nexport function accountProofBytes(input: {\n rootPublicKey: Uint8Array;\n account: string;\n device: string;\n signPublicKey: string;\n kemPublicKey: string;\n keyEpoch: number;\n deviceEpoch: number;\n signature: Uint8Array;\n}): Uint8Array {\n return concat(\n new Uint8Array([ACCOUNT_GENESIS_VERSION]),\n input.rootPublicKey,\n u32le(0),\n fromHex(input.account, 'account', 32),\n fromHex(input.device, 'device', 32),\n fromHex(input.signPublicKey, 'signPublicKey', 32),\n fromHex(input.kemPublicKey, 'kemPublicKey', 32),\n u32le(input.keyEpoch),\n u32le(input.deviceEpoch),\n input.signature,\n );\n}\n\n/**\n * Certify a device, returning the hex credential `merod account sign-cert`\n * prints \u2014 a borsh-encoded `AccountProof<DeviceCert>`.\n *\n * The layout, field for field, is core's:\n *\n * ```\n * AccountProof { genesis: AccountGenesis, chain: Vec<RootKeyHandoff>, statement: DeviceCert }\n * AccountGenesis { version: u8, root_sign_pk: [u8; 32] }\n * chain \u2192 u32-LE count, then that many handoffs\n * DeviceCert { account, device, sign_pk, kem_pk: [u8; 32] x4,\n * key_epoch: u32, device_epoch: u32, signature: [u8; 64] }\n * ```\n *\n * Borsh for plain fixed-width data is concatenation; the only variable part is\n * the chain's length prefix. `chain` is empty here for the same reason it is in\n * `sign-cert`: a root that has never been handed off signs directly, and a\n * handoff chain is only needed once it has.\n *\n * `keyEpoch` is 0 for the same reason \u2014 it names the root epoch that signed, and\n * an un-rotated root is at 0. Re-signing after a handoff is a separate flow that\n * needs the chain, so this deliberately does not take it as an argument rather\n * than accepting a value it would then have to ignore.\n */\nexport async function signDeviceCert(input: DeviceCertInput): Promise<string> {\n const rootPublicKey = await derivePublicKey(input.rootSecret);\n const account = hex(\n await domainHash(ACCOUNT_ID_DOMAIN, [\n concat(new Uint8Array([ACCOUNT_GENESIS_VERSION]), rootPublicKey),\n ]),\n );\n\n const keyEpoch = 0;\n const payload = await deviceCertPayload({\n account,\n device: input.device,\n signPublicKey: input.signPublicKey,\n kemPublicKey: input.kemPublicKey,\n keyEpoch,\n deviceEpoch: input.deviceEpoch,\n });\n\n const key = await importSigningKey(input.rootSecret);\n const signature = new Uint8Array(\n await crypto.subtle.sign('Ed25519', key, payload),\n );\n\n const credential = accountProofBytes({\n rootPublicKey,\n account,\n device: input.device,\n signPublicKey: input.signPublicKey,\n kemPublicKey: input.kemPublicKey,\n keyEpoch,\n deviceEpoch: input.deviceEpoch,\n signature,\n });\n\n return hex(credential);\n}\n\n/** The account this root owns \u2014 the content address of its genesis. */\nexport async function accountForRoot(rootSecret: string): Promise<string> {\n const rootPublicKey = await derivePublicKey(rootSecret);\n return hex(\n await domainHash(ACCOUNT_ID_DOMAIN, [\n concat(new Uint8Array([ACCOUNT_GENESIS_VERSION]), rootPublicKey),\n ]),\n );\n}\n", "/**\n * The BIP39 English wordlist, verbatim \u2014 2048 words, index order significant.\n *\n * Vendored rather than depended on because this package ships with no runtime\n * dependencies, and a recovery phrase is useless if the list it was written\n * against is not the one everybody else implements. The list is content-checked\n * in `account.test.ts` against its canonical SHA-256\n * (`2f5eed53a4727b4bf8880d8f3f199efc90e58503646d9ff8eff3a2ed3b24dbda` over the\n * newline-joined, newline-terminated file), so a stray edit here fails the suite\n * instead of silently minting phrases no other wallet can restore.\n *\n * Its own module so bundlers can drop it: nothing outside `account/` imports it,\n * and the package is `sideEffects: false`, so a consumer who only signs never\n * pays for the ~13 KB.\n */\nexport const ENGLISH_WORDLIST: readonly string[] = [\n 'abandon', 'ability', 'able', 'about', 'above', 'absent', 'absorb', 'abstract',\n 'absurd', 'abuse', 'access', 'accident', 'account', 'accuse', 'achieve', 'acid',\n 'acoustic', 'acquire', 'across', 'act', 'action', 'actor', 'actress', 'actual',\n 'adapt', 'add', 'addict', 'address', 'adjust', 'admit', 'adult', 'advance',\n 'advice', 'aerobic', 'affair', 'afford', 'afraid', 'again', 'age', 'agent',\n 'agree', 'ahead', 'aim', 'air', 'airport', 'aisle', 'alarm', 'album',\n 'alcohol', 'alert', 'alien', 'all', 'alley', 'allow', 'almost', 'alone',\n 'alpha', 'already', 'also', 'alter', 'always', 'amateur', 'amazing', 'among',\n 'amount', 'amused', 'analyst', 'anchor', 'ancient', 'anger', 'angle', 'angry',\n 'animal', 'ankle', 'announce', 'annual', 'another', 'answer', 'antenna', 'antique',\n 'anxiety', 'any', 'apart', 'apology', 'appear', 'apple', 'approve', 'april',\n 'arch', 'arctic', 'area', 'arena', 'argue', 'arm', 'armed', 'armor',\n 'army', 'around', 'arrange', 'arrest', 'arrive', 'arrow', 'art', 'artefact',\n 'artist', 'artwork', 'ask', 'aspect', 'assault', 'asset', 'assist', 'assume',\n 'asthma', 'athlete', 'atom', 'attack', 'attend', 'attitude', 'attract', 'auction',\n 'audit', 'august', 'aunt', 'author', 'auto', 'autumn', 'average', 'avocado',\n 'avoid', 'awake', 'aware', 'away', 'awesome', 'awful', 'awkward', 'axis',\n 'baby', 'bachelor', 'bacon', 'badge', 'bag', 'balance', 'balcony', 'ball',\n 'bamboo', 'banana', 'banner', 'bar', 'barely', 'bargain', 'barrel', 'base',\n 'basic', 'basket', 'battle', 'beach', 'bean', 'beauty', 'because', 'become',\n 'beef', 'before', 'begin', 'behave', 'behind', 'believe', 'below', 'belt',\n 'bench', 'benefit', 'best', 'betray', 'better', 'between', 'beyond', 'bicycle',\n 'bid', 'bike', 'bind', 'biology', 'bird', 'birth', 'bitter', 'black',\n 'blade', 'blame', 'blanket', 'blast', 'bleak', 'bless', 'blind', 'blood',\n 'blossom', 'blouse', 'blue', 'blur', 'blush', 'board', 'boat', 'body',\n 'boil', 'bomb', 'bone', 'bonus', 'book', 'boost', 'border', 'boring',\n 'borrow', 'boss', 'bottom', 'bounce', 'box', 'boy', 'bracket', 'brain',\n 'brand', 'brass', 'brave', 'bread', 'breeze', 'brick', 'bridge', 'brief',\n 'bright', 'bring', 'brisk', 'broccoli', 'broken', 'bronze', 'broom', 'brother',\n 'brown', 'brush', 'bubble', 'buddy', 'budget', 'buffalo', 'build', 'bulb',\n 'bulk', 'bullet', 'bundle', 'bunker', 'burden', 'burger', 'burst', 'bus',\n 'business', 'busy', 'butter', 'buyer', 'buzz', 'cabbage', 'cabin', 'cable',\n 'cactus', 'cage', 'cake', 'call', 'calm', 'camera', 'camp', 'can',\n 'canal', 'cancel', 'candy', 'cannon', 'canoe', 'canvas', 'canyon', 'capable',\n 'capital', 'captain', 'car', 'carbon', 'card', 'cargo', 'carpet', 'carry',\n 'cart', 'case', 'cash', 'casino', 'castle', 'casual', 'cat', 'catalog',\n 'catch', 'category', 'cattle', 'caught', 'cause', 'caution', 'cave', 'ceiling',\n 'celery', 'cement', 'census', 'century', 'cereal', 'certain', 'chair', 'chalk',\n 'champion', 'change', 'chaos', 'chapter', 'charge', 'chase', 'chat', 'cheap',\n 'check', 'cheese', 'chef', 'cherry', 'chest', 'chicken', 'chief', 'child',\n 'chimney', 'choice', 'choose', 'chronic', 'chuckle', 'chunk', 'churn', 'cigar',\n 'cinnamon', 'circle', 'citizen', 'city', 'civil', 'claim', 'clap', 'clarify',\n 'claw', 'clay', 'clean', 'clerk', 'clever', 'click', 'client', 'cliff',\n 'climb', 'clinic', 'clip', 'clock', 'clog', 'close', 'cloth', 'cloud',\n 'clown', 'club', 'clump', 'cluster', 'clutch', 'coach', 'coast', 'coconut',\n 'code', 'coffee', 'coil', 'coin', 'collect', 'color', 'column', 'combine',\n 'come', 'comfort', 'comic', 'common', 'company', 'concert', 'conduct', 'confirm',\n 'congress', 'connect', 'consider', 'control', 'convince', 'cook', 'cool', 'copper',\n 'copy', 'coral', 'core', 'corn', 'correct', 'cost', 'cotton', 'couch',\n 'country', 'couple', 'course', 'cousin', 'cover', 'coyote', 'crack', 'cradle',\n 'craft', 'cram', 'crane', 'crash', 'crater', 'crawl', 'crazy', 'cream',\n 'credit', 'creek', 'crew', 'cricket', 'crime', 'crisp', 'critic', 'crop',\n 'cross', 'crouch', 'crowd', 'crucial', 'cruel', 'cruise', 'crumble', 'crunch',\n 'crush', 'cry', 'crystal', 'cube', 'culture', 'cup', 'cupboard', 'curious',\n 'current', 'curtain', 'curve', 'cushion', 'custom', 'cute', 'cycle', 'dad',\n 'damage', 'damp', 'dance', 'danger', 'daring', 'dash', 'daughter', 'dawn',\n 'day', 'deal', 'debate', 'debris', 'decade', 'december', 'decide', 'decline',\n 'decorate', 'decrease', 'deer', 'defense', 'define', 'defy', 'degree', 'delay',\n 'deliver', 'demand', 'demise', 'denial', 'dentist', 'deny', 'depart', 'depend',\n 'deposit', 'depth', 'deputy', 'derive', 'describe', 'desert', 'design', 'desk',\n 'despair', 'destroy', 'detail', 'detect', 'develop', 'device', 'devote', 'diagram',\n 'dial', 'diamond', 'diary', 'dice', 'diesel', 'diet', 'differ', 'digital',\n 'dignity', 'dilemma', 'dinner', 'dinosaur', 'direct', 'dirt', 'disagree', 'discover',\n 'disease', 'dish', 'dismiss', 'disorder', 'display', 'distance', 'divert', 'divide',\n 'divorce', 'dizzy', 'doctor', 'document', 'dog', 'doll', 'dolphin', 'domain',\n 'donate', 'donkey', 'donor', 'door', 'dose', 'double', 'dove', 'draft',\n 'dragon', 'drama', 'drastic', 'draw', 'dream', 'dress', 'drift', 'drill',\n 'drink', 'drip', 'drive', 'drop', 'drum', 'dry', 'duck', 'dumb',\n 'dune', 'during', 'dust', 'dutch', 'duty', 'dwarf', 'dynamic', 'eager',\n 'eagle', 'early', 'earn', 'earth', 'easily', 'east', 'easy', 'echo',\n 'ecology', 'economy', 'edge', 'edit', 'educate', 'effort', 'egg', 'eight',\n 'either', 'elbow', 'elder', 'electric', 'elegant', 'element', 'elephant', 'elevator',\n 'elite', 'else', 'embark', 'embody', 'embrace', 'emerge', 'emotion', 'employ',\n 'empower', 'empty', 'enable', 'enact', 'end', 'endless', 'endorse', 'enemy',\n 'energy', 'enforce', 'engage', 'engine', 'enhance', 'enjoy', 'enlist', 'enough',\n 'enrich', 'enroll', 'ensure', 'enter', 'entire', 'entry', 'envelope', 'episode',\n 'equal', 'equip', 'era', 'erase', 'erode', 'erosion', 'error', 'erupt',\n 'escape', 'essay', 'essence', 'estate', 'eternal', 'ethics', 'evidence', 'evil',\n 'evoke', 'evolve', 'exact', 'example', 'excess', 'exchange', 'excite', 'exclude',\n 'excuse', 'execute', 'exercise', 'exhaust', 'exhibit', 'exile', 'exist', 'exit',\n 'exotic', 'expand', 'expect', 'expire', 'explain', 'expose', 'express', 'extend',\n 'extra', 'eye', 'eyebrow', 'fabric', 'face', 'faculty', 'fade', 'faint',\n 'faith', 'fall', 'false', 'fame', 'family', 'famous', 'fan', 'fancy',\n 'fantasy', 'farm', 'fashion', 'fat', 'fatal', 'father', 'fatigue', 'fault',\n 'favorite', 'feature', 'february', 'federal', 'fee', 'feed', 'feel', 'female',\n 'fence', 'festival', 'fetch', 'fever', 'few', 'fiber', 'fiction', 'field',\n 'figure', 'file', 'film', 'filter', 'final', 'find', 'fine', 'finger',\n 'finish', 'fire', 'firm', 'first', 'fiscal', 'fish', 'fit', 'fitness',\n 'fix', 'flag', 'flame', 'flash', 'flat', 'flavor', 'flee', 'flight',\n 'flip', 'float', 'flock', 'floor', 'flower', 'fluid', 'flush', 'fly',\n 'foam', 'focus', 'fog', 'foil', 'fold', 'follow', 'food', 'foot',\n 'force', 'forest', 'forget', 'fork', 'fortune', 'forum', 'forward', 'fossil',\n 'foster', 'found', 'fox', 'fragile', 'frame', 'frequent', 'fresh', 'friend',\n 'fringe', 'frog', 'front', 'frost', 'frown', 'frozen', 'fruit', 'fuel',\n 'fun', 'funny', 'furnace', 'fury', 'future', 'gadget', 'gain', 'galaxy',\n 'gallery', 'game', 'gap', 'garage', 'garbage', 'garden', 'garlic', 'garment',\n 'gas', 'gasp', 'gate', 'gather', 'gauge', 'gaze', 'general', 'genius',\n 'genre', 'gentle', 'genuine', 'gesture', 'ghost', 'giant', 'gift', 'giggle',\n 'ginger', 'giraffe', 'girl', 'give', 'glad', 'glance', 'glare', 'glass',\n 'glide', 'glimpse', 'globe', 'gloom', 'glory', 'glove', 'glow', 'glue',\n 'goat', 'goddess', 'gold', 'good', 'goose', 'gorilla', 'gospel', 'gossip',\n 'govern', 'gown', 'grab', 'grace', 'grain', 'grant', 'grape', 'grass',\n 'gravity', 'great', 'green', 'grid', 'grief', 'grit', 'grocery', 'group',\n 'grow', 'grunt', 'guard', 'guess', 'guide', 'guilt', 'guitar', 'gun',\n 'gym', 'habit', 'hair', 'half', 'hammer', 'hamster', 'hand', 'happy',\n 'harbor', 'hard', 'harsh', 'harvest', 'hat', 'have', 'hawk', 'hazard',\n 'head', 'health', 'heart', 'heavy', 'hedgehog', 'height', 'hello', 'helmet',\n 'help', 'hen', 'hero', 'hidden', 'high', 'hill', 'hint', 'hip',\n 'hire', 'history', 'hobby', 'hockey', 'hold', 'hole', 'holiday', 'hollow',\n 'home', 'honey', 'hood', 'hope', 'horn', 'horror', 'horse', 'hospital',\n 'host', 'hotel', 'hour', 'hover', 'hub', 'huge', 'human', 'humble',\n 'humor', 'hundred', 'hungry', 'hunt', 'hurdle', 'hurry', 'hurt', 'husband',\n 'hybrid', 'ice', 'icon', 'idea', 'identify', 'idle', 'ignore', 'ill',\n 'illegal', 'illness', 'image', 'imitate', 'immense', 'immune', 'impact', 'impose',\n 'improve', 'impulse', 'inch', 'include', 'income', 'increase', 'index', 'indicate',\n 'indoor', 'industry', 'infant', 'inflict', 'inform', 'inhale', 'inherit', 'initial',\n 'inject', 'injury', 'inmate', 'inner', 'innocent', 'input', 'inquiry', 'insane',\n 'insect', 'inside', 'inspire', 'install', 'intact', 'interest', 'into', 'invest',\n 'invite', 'involve', 'iron', 'island', 'isolate', 'issue', 'item', 'ivory',\n 'jacket', 'jaguar', 'jar', 'jazz', 'jealous', 'jeans', 'jelly', 'jewel',\n 'job', 'join', 'joke', 'journey', 'joy', 'judge', 'juice', 'jump',\n 'jungle', 'junior', 'junk', 'just', 'kangaroo', 'keen', 'keep', 'ketchup',\n 'key', 'kick', 'kid', 'kidney', 'kind', 'kingdom', 'kiss', 'kit',\n 'kitchen', 'kite', 'kitten', 'kiwi', 'knee', 'knife', 'knock', 'know',\n 'lab', 'label', 'labor', 'ladder', 'lady', 'lake', 'lamp', 'language',\n 'laptop', 'large', 'later', 'latin', 'laugh', 'laundry', 'lava', 'law',\n 'lawn', 'lawsuit', 'layer', 'lazy', 'leader', 'leaf', 'learn', 'leave',\n 'lecture', 'left', 'leg', 'legal', 'legend', 'leisure', 'lemon', 'lend',\n 'length', 'lens', 'leopard', 'lesson', 'letter', 'level', 'liar', 'liberty',\n 'library', 'license', 'life', 'lift', 'light', 'like', 'limb', 'limit',\n 'link', 'lion', 'liquid', 'list', 'little', 'live', 'lizard', 'load',\n 'loan', 'lobster', 'local', 'lock', 'logic', 'lonely', 'long', 'loop',\n 'lottery', 'loud', 'lounge', 'love', 'loyal', 'lucky', 'luggage', 'lumber',\n 'lunar', 'lunch', 'luxury', 'lyrics', 'machine', 'mad', 'magic', 'magnet',\n 'maid', 'mail', 'main', 'major', 'make', 'mammal', 'man', 'manage',\n 'mandate', 'mango', 'mansion', 'manual', 'maple', 'marble', 'march', 'margin',\n 'marine', 'market', 'marriage', 'mask', 'mass', 'master', 'match', 'material',\n 'math', 'matrix', 'matter', 'maximum', 'maze', 'meadow', 'mean', 'measure',\n 'meat', 'mechanic', 'medal', 'media', 'melody', 'melt', 'member', 'memory',\n 'mention', 'menu', 'mercy', 'merge', 'merit', 'merry', 'mesh', 'message',\n 'metal', 'method', 'middle', 'midnight', 'milk', 'million', 'mimic', 'mind',\n 'minimum', 'minor', 'minute', 'miracle', 'mirror', 'misery', 'miss', 'mistake',\n 'mix', 'mixed', 'mixture', 'mobile', 'model', 'modify', 'mom', 'moment',\n 'monitor', 'monkey', 'monster', 'month', 'moon', 'moral', 'more', 'morning',\n 'mosquito', 'mother', 'motion', 'motor', 'mountain', 'mouse', 'move', 'movie',\n 'much', 'muffin', 'mule', 'multiply', 'muscle', 'museum', 'mushroom', 'music',\n 'must', 'mutual', 'myself', 'mystery', 'myth', 'naive', 'name', 'napkin',\n 'narrow', 'nasty', 'nation', 'nature', 'near', 'neck', 'need', 'negative',\n 'neglect', 'neither', 'nephew', 'nerve', 'nest', 'net', 'network', 'neutral',\n 'never', 'news', 'next', 'nice', 'night', 'noble', 'noise', 'nominee',\n 'noodle', 'normal', 'north', 'nose', 'notable', 'note', 'nothing', 'notice',\n 'novel', 'now', 'nuclear', 'number', 'nurse', 'nut', 'oak', 'obey',\n 'object', 'oblige', 'obscure', 'observe', 'obtain', 'obvious', 'occur', 'ocean',\n 'october', 'odor', 'off', 'offer', 'office', 'often', 'oil', 'okay',\n 'old', 'olive', 'olympic', 'omit', 'once', 'one', 'onion', 'online',\n 'only', 'open', 'opera', 'opinion', 'oppose', 'option', 'orange', 'orbit',\n 'orchard', 'order', 'ordinary', 'organ', 'orient', 'original', 'orphan', 'ostrich',\n 'other', 'outdoor', 'outer', 'output', 'outside', 'oval', 'oven', 'over',\n 'own', 'owner', 'oxygen', 'oyster', 'ozone', 'pact', 'paddle', 'page',\n 'pair', 'palace', 'palm', 'panda', 'panel', 'panic', 'panther', 'paper',\n 'parade', 'parent', 'park', 'parrot', 'party', 'pass', 'patch', 'path',\n 'patient', 'patrol', 'pattern', 'pause', 'pave', 'payment', 'peace', 'peanut',\n 'pear', 'peasant', 'pelican', 'pen', 'penalty', 'pencil', 'people', 'pepper',\n 'perfect', 'permit', 'person', 'pet', 'phone', 'photo', 'phrase', 'physical',\n 'piano', 'picnic', 'picture', 'piece', 'pig', 'pigeon', 'pill', 'pilot',\n 'pink', 'pioneer', 'pipe', 'pistol', 'pitch', 'pizza', 'place', 'planet',\n 'plastic', 'plate', 'play', 'please', 'pledge', 'pluck', 'plug', 'plunge',\n 'poem', 'poet', 'point', 'polar', 'pole', 'police', 'pond', 'pony',\n 'pool', 'popular', 'portion', 'position', 'possible', 'post', 'potato', 'pottery',\n 'poverty', 'powder', 'power', 'practice', 'praise', 'predict', 'prefer', 'prepare',\n 'present', 'pretty', 'prevent', 'price', 'pride', 'primary', 'print', 'priority',\n 'prison', 'private', 'prize', 'problem', 'process', 'produce', 'profit', 'program',\n 'project', 'promote', 'proof', 'property', 'prosper', 'protect', 'proud', 'provide',\n 'public', 'pudding', 'pull', 'pulp', 'pulse', 'pumpkin', 'punch', 'pupil',\n 'puppy', 'purchase', 'purity', 'purpose', 'purse', 'push', 'put', 'puzzle',\n 'pyramid', 'quality', 'quantum', 'quarter', 'question', 'quick', 'quit', 'quiz',\n 'quote', 'rabbit', 'raccoon', 'race', 'rack', 'radar', 'radio', 'rail',\n 'rain', 'raise', 'rally', 'ramp', 'ranch', 'random', 'range', 'rapid',\n 'rare', 'rate', 'rather', 'raven', 'raw', 'razor', 'ready', 'real',\n 'reason', 'rebel', 'rebuild', 'recall', 'receive', 'recipe', 'record', 'recycle',\n 'reduce', 'reflect', 'reform', 'refuse', 'region', 'regret', 'regular', 'reject',\n 'relax', 'release', 'relief', 'rely', 'remain', 'remember', 'remind', 'remove',\n 'render', 'renew', 'rent', 'reopen', 'repair', 'repeat', 'replace', 'report',\n 'require', 'rescue', 'resemble', 'resist', 'resource', 'response', 'result', 'retire',\n 'retreat', 'return', 'reunion', 'reveal', 'review', 'reward', 'rhythm', 'rib',\n 'ribbon', 'rice', 'rich', 'ride', 'ridge', 'rifle', 'right', 'rigid',\n 'ring', 'riot', 'ripple', 'risk', 'ritual', 'rival', 'river', 'road',\n 'roast', 'robot', 'robust', 'rocket', 'romance', 'roof', 'rookie', 'room',\n 'rose', 'rotate', 'rough', 'round', 'route', 'royal', 'rubber', 'rude',\n 'rug', 'rule', 'run', 'runway', 'rural', 'sad', 'saddle', 'sadness',\n 'safe', 'sail', 'salad', 'salmon', 'salon', 'salt', 'salute', 'same',\n 'sample', 'sand', 'satisfy', 'satoshi', 'sauce', 'sausage', 'save', 'say',\n 'scale', 'scan', 'scare', 'scatter', 'scene', 'scheme', 'school', 'science',\n 'scissors', 'scorpion', 'scout', 'scrap', 'screen', 'script', 'scrub', 'sea',\n 'search', 'season', 'seat', 'second', 'secret', 'section', 'security', 'seed',\n 'seek', 'segment', 'select', 'sell', 'seminar', 'senior', 'sense', 'sentence',\n 'series', 'service', 'session', 'settle', 'setup', 'seven', 'shadow', 'shaft',\n 'shallow', 'share', 'shed', 'shell', 'sheriff', 'shield', 'shift', 'shine',\n 'ship', 'shiver', 'shock', 'shoe', 'shoot', 'shop', 'short', 'shoulder',\n 'shove', 'shrimp', 'shrug', 'shuffle', 'shy', 'sibling', 'sick', 'side',\n 'siege', 'sight', 'sign', 'silent', 'silk', 'silly', 'silver', 'similar',\n 'simple', 'since', 'sing', 'siren', 'sister', 'situate', 'six', 'size',\n 'skate', 'sketch', 'ski', 'skill', 'skin', 'skirt', 'skull', 'slab',\n 'slam', 'sleep', 'slender', 'slice', 'slide', 'slight', 'slim', 'slogan',\n 'slot', 'slow', 'slush', 'small', 'smart', 'smile', 'smoke', 'smooth',\n 'snack', 'snake', 'snap', 'sniff', 'snow', 'soap', 'soccer', 'social',\n 'sock', 'soda', 'soft', 'solar', 'soldier', 'solid', 'solution', 'solve',\n 'someone', 'song', 'soon', 'sorry', 'sort', 'soul', 'sound', 'soup',\n 'source', 'south', 'space', 'spare', 'spatial', 'spawn', 'speak', 'special',\n 'speed', 'spell', 'spend', 'sphere', 'spice', 'spider', 'spike', 'spin',\n 'spirit', 'split', 'spoil', 'sponsor', 'spoon', 'sport', 'spot', 'spray',\n 'spread', 'spring', 'spy', 'square', 'squeeze', 'squirrel', 'stable', 'stadium',\n 'staff', 'stage', 'stairs', 'stamp', 'stand', 'start', 'state', 'stay',\n 'steak', 'steel', 'stem', 'step', 'stereo', 'stick', 'still', 'sting',\n 'stock', 'stomach', 'stone', 'stool', 'story', 'stove', 'strategy', 'street',\n 'strike', 'strong', 'struggle', 'student', 'stuff', 'stumble', 'style', 'subject',\n 'submit', 'subway', 'success', 'such', 'sudden', 'suffer', 'sugar', 'suggest',\n 'suit', 'summer', 'sun', 'sunny', 'sunset', 'super', 'supply', 'supreme',\n 'sure', 'surface', 'surge', 'surprise', 'surround', 'survey', 'suspect', 'sustain',\n 'swallow', 'swamp', 'swap', 'swarm', 'swear', 'sweet', 'swift', 'swim',\n 'swing', 'switch', 'sword', 'symbol', 'symptom', 'syrup', 'system', 'table',\n 'tackle', 'tag', 'tail', 'talent', 'talk', 'tank', 'tape', 'target',\n 'task', 'taste', 'tattoo', 'taxi', 'teach', 'team', 'tell', 'ten',\n 'tenant', 'tennis', 'tent', 'term', 'test', 'text', 'thank', 'that',\n 'theme', 'then', 'theory', 'there', 'they', 'thing', 'this', 'thought',\n 'three', 'thrive', 'throw', 'thumb', 'thunder', 'ticket', 'tide', 'tiger',\n 'tilt', 'timber', 'time', 'tiny', 'tip', 'tired', 'tissue', 'title',\n 'toast', 'tobacco', 'today', 'toddler', 'toe', 'together', 'toilet', 'token',\n 'tomato', 'tomorrow', 'tone', 'tongue', 'tonight', 'tool', 'tooth', 'top',\n 'topic', 'topple', 'torch', 'tornado', 'tortoise', 'toss', 'total', 'tourist',\n 'toward', 'tower', 'town', 'toy', 'track', 'trade', 'traffic', 'tragic',\n 'train', 'transfer', 'trap', 'trash', 'travel', 'tray', 'treat', 'tree',\n 'trend', 'trial', 'tribe', 'trick', 'trigger', 'trim', 'trip', 'trophy',\n 'trouble', 'truck', 'true', 'truly', 'trumpet', 'trust', 'truth', 'try',\n 'tube', 'tuition', 'tumble', 'tuna', 'tunnel', 'turkey', 'turn', 'turtle',\n 'twelve', 'twenty', 'twice', 'twin', 'twist', 'two', 'type', 'typical',\n 'ugly', 'umbrella', 'unable', 'unaware', 'uncle', 'uncover', 'under', 'undo',\n 'unfair', 'unfold', 'unhappy', 'uniform', 'unique', 'unit', 'universe', 'unknown',\n 'unlock', 'until', 'unusual', 'unveil', 'update', 'upgrade', 'uphold', 'upon',\n 'upper', 'upset', 'urban', 'urge', 'usage', 'use', 'used', 'useful',\n 'useless', 'usual', 'utility', 'vacant', 'vacuum', 'vague', 'valid', 'valley',\n 'valve', 'van', 'vanish', 'vapor', 'various', 'vast', 'vault', 'vehicle',\n 'velvet', 'vendor', 'venture', 'venue', 'verb', 'verify', 'version', 'very',\n 'vessel', 'veteran', 'viable', 'vibrant', 'vicious', 'victory', 'video', 'view',\n 'village', 'vintage', 'violin', 'virtual', 'virus', 'visa', 'visit', 'visual',\n 'vital', 'vivid', 'vocal', 'voice', 'void', 'volcano', 'volume', 'vote',\n 'voyage', 'wage', 'wagon', 'wait', 'walk', 'wall', 'walnut', 'want',\n 'warfare', 'warm', 'warrior', 'wash', 'wasp', 'waste', 'water', 'wave',\n 'way', 'wealth', 'weapon', 'wear', 'weasel', 'weather', 'web', 'wedding',\n 'weekend', 'weird', 'welcome', 'west', 'wet', 'whale', 'what', 'wheat',\n 'wheel', 'when', 'where', 'whip', 'whisper', 'wide', 'width', 'wife',\n 'wild', 'will', 'win', 'window', 'wine', 'wing', 'wink', 'winner',\n 'winter', 'wire', 'wisdom', 'wise', 'wish', 'witness', 'wolf', 'woman',\n 'wonder', 'wood', 'wool', 'word', 'work', 'world', 'worry', 'worth',\n 'wrap', 'wreck', 'wrestle', 'wrist', 'write', 'wrong', 'yard', 'year',\n 'yellow', 'you', 'young', 'youth', 'zebra', 'zero', 'zone', 'zoo',\n];\n", "/**\n * Creating an account root, and proving you hold one.\n *\n * `signDeviceCert` assumes an account root already exists and asks only what it\n * certifies. This is the step before that: where the root comes from, how a\n * person keeps it, and how they show a server they have it.\n *\n * Two things live here because they are the two halves of onboarding:\n *\n * - {@link generateAccountRoot} mints a root and hands back a 24-word recovery\n * phrase. The phrase *is* the key \u2014 see the interop note on that function \u2014\n * so a holder who writes it down can restore the account on any device.\n * - {@link signAccountLink} signs a cloud-issued challenge with that root, which\n * is how Calimero Cloud binds a billing login to a Calimero account.\n * - {@link signAccountLogin} signs a different cloud-issued challenge with the\n * same root to claim the account outright and open a session as it \u2014 the\n * password-free counterpart to signing in with Google.\n *\n * Both are offline and hold no state. Nothing here talks to a node or to the\n * cloud; `CloudClient.linkAccount` is the wrapper that does.\n *\n * Derived from:\n *\n * - `ACCOUNT_LINK_DOMAIN` / `ACCOUNT_LOGIN_DOMAIN`, and\n * `verify_account_link_proof` / `verify_login_proof` \u2014 mdma's\n * `manager/app/account_proof.py`\n * - the account derivation \u2014 `accountForRoot` in `../device-cert`, which is\n * core's `AccountId` preimage\n */\nimport {\n concat,\n derivePublicKey,\n fromHex,\n hex,\n importSigningKey,\n} from '../crypto/internal.js';\nimport { accountForRoot } from '../device-cert/index.js';\nimport { ENGLISH_WORDLIST } from './wordlist.js';\n\n/**\n * mdma's `ACCOUNT_LINK_DOMAIN`, trailing NUL included.\n *\n * Prefixed raw rather than run through `domainHash`: mdma verifies\n * `ed25519_verify(root_pk, ACCOUNT_LINK_DOMAIN \u2016 nonce_utf8)` over exactly these\n * bytes. The separator is what stops a signature gathered here being replayable\n * into any other protocol that has a root sign opaque bytes, so it is not\n * cosmetic \u2014 and the NUL is part of it.\n */\nconst ACCOUNT_LINK_DOMAIN = new TextEncoder().encode(\n 'calimero.mdma.account-link.v1\u0000',\n);\n\n/**\n * mdma's `ACCOUNT_LOGIN_DOMAIN`, trailing NUL included.\n *\n * A separate domain from {@link ACCOUNT_LINK_DOMAIN} because the two\n * exchanges are otherwise byte-identical \u2014 both are a root signature over a\n * cloud-issued nonce \u2014 and a signature gathered while linking is gathered from\n * someone already signed in and trusting the page. Without the separation it\n * would be replayable as a login by whoever collected it.\n */\nconst ACCOUNT_LOGIN_DOMAIN = new TextEncoder().encode(\n 'calimero.mdma.account-login.v1\\0',\n);\n\n/** Entropy carried by a phrase. 256 bits \u2014 32 bytes, an ed25519 seed exactly. */\nconst ENTROPY_BITS = 256;\n/** BIP39's checksum length: one bit per 32 bits of entropy. */\nconst CHECKSUM_BITS = ENTROPY_BITS / 32;\n/** 11 bits per word, so (256 + 8) / 11 = 24 words. */\nconst PHRASE_WORDS = (ENTROPY_BITS + CHECKSUM_BITS) / 11;\n\n/** An account root: the secret, the key it derives, and the account it names. */\nexport interface AccountRoot {\n /**\n * The root signing secret, 64 hex.\n *\n * The whole account. It signs device certificates and cloud links, and there\n * is no recovery path that does not go through it or its phrase \u2014 so it is\n * never sent anywhere, and in a browser it does not belong in `localStorage`.\n */\n secret: string;\n /** The root's ed25519 public key, 64 hex. Safe to publish. */\n publicKey: string;\n /** The account this root names, 64 hex \u2014 the content address of its genesis. */\n accountId: string;\n}\n\n/** An account root together with the phrase that restores it. */\nexport interface RecoverableAccountRoot extends AccountRoot {\n /**\n * A 24-word BIP39 English recovery phrase, single-space separated.\n *\n * Show it once, have the holder write it down, and do not keep it: it is the\n * secret in a form a person can copy by hand without transcription errors.\n */\n phrase: string;\n}\n\n/**\n * Mint a brand-new account root from the platform CSPRNG.\n *\n * This is the \"generate a new key\" half of cloud registration \u2014 the other half\n * being a root the holder already has, on a desktop app or a hardware key, which\n * signs without this ever running.\n *\n * **Interop note, and it matters.** The phrase encodes the root secret as BIP39\n * *entropy*, not as a BIP39 *seed*. A tool restoring it must call the equivalent\n * of `mnemonicToEntropy(phrase)` \u2014 32 bytes \u2014 and use those bytes as the ed25519\n * secret directly. `mnemonicToSeed(phrase)`, which most wallet libraries reach\n * for first, runs PBKDF2 and returns 64 different bytes; feeding those in names\n * a different account and fails as \"wrong account\", not as a decoding error.\n * There is no passphrase and no BIP32 derivation path: the 24 words are the key.\n */\nexport async function generateAccountRoot(): Promise<RecoverableAccountRoot> {\n const entropy = crypto.getRandomValues(new Uint8Array(ENTROPY_BITS / 8));\n return withPhrase(hex(entropy), await encodePhrase(entropy));\n}\n\n/**\n * Restore an account root from its recovery phrase.\n *\n * Whitespace and case are normalised, so a phrase pasted out of a text editor\n * with a trailing newline or a stray double space restores fine. A word outside\n * the list, a wrong word count, or a failed checksum throws rather than\n * returning some other account \u2014 a single mistyped word is the common case, and\n * BIP39's checksum catches all but 1 in 256 of them.\n */\nexport async function accountRootFromPhrase(\n phrase: string,\n): Promise<RecoverableAccountRoot> {\n const words = normalisePhrase(phrase);\n return withPhrase(hex(await decodePhrase(words)), words.join(' '));\n}\n\n/** The public half of a root you already hold, without re-deriving it by hand. */\nexport async function accountRootFromSecret(\n secret: string,\n): Promise<AccountRoot> {\n // Validates the length and alphabet before anything derives from it, so a\n // truncated paste fails here rather than naming an unrelated account.\n fromHex(secret, 'secret', 32);\n return {\n secret,\n publicKey: hex(await derivePublicKey(secret)),\n accountId: await accountForRoot(secret),\n };\n}\n\n/** What {@link signAccountLink} needs. */\nexport interface AccountLinkInput {\n /** The account root's signing secret, 64 hex. */\n rootSecret: string;\n /**\n * The challenge from `GET /api/cloud/me/accounts/challenge`, verbatim.\n *\n * Signed as its UTF-8 bytes, not decoded first: mdma seals it as an opaque\n * string and verifies over the same string it issued.\n */\n nonce: string;\n}\n\n/**\n * Sign a cloud link challenge with the account root. Returns base64.\n *\n * Standard base64, not base64url \u2014 mdma decodes it with `validate=True`, so the\n * `-`/`_` alphabet is rejected outright rather than silently mis-decoded.\n *\n * Root-signed rather than device-signed on purpose: this is the credential\n * someone still holds after losing a device, which is the case linking exists to\n * serve.\n */\nexport async function signAccountLink(\n input: AccountLinkInput,\n): Promise<string> {\n if (!input.nonce) throw new Error('nonce is required to sign an account link');\n const key = await importSigningKey(input.rootSecret);\n const signature = new Uint8Array(\n await crypto.subtle.sign(\n 'Ed25519',\n key,\n concat(ACCOUNT_LINK_DOMAIN, new TextEncoder().encode(input.nonce)),\n ),\n );\n return base64(signature);\n}\n\n/** What {@link signAccountLogin} needs. */\nexport interface AccountLoginInput {\n /** The account root's signing secret, 64 hex. */\n rootSecret: string;\n /** The challenge from `GET /api/auth/account/challenge`, verbatim UTF-8. */\n nonce: string;\n}\n\n/**\n * Sign a cloud login challenge with the account root. Returns base64.\n *\n * The statement this makes is *\"I hold the root of account X\"*, and it is the\n * one statement a device credential can never make. A routing read proves the\n * caller holds a device the root certified \u2014 the credential carries the root's\n * signature over that device \u2014 but a certificate is public and travels in the\n * clear inside every device-link op, so anyone who has seen one can say \"a\n * device of X is asking\". Only the root can say the account is theirs.\n *\n * Which is why this is worth doing **once**: the cloud records the claim, and\n * from then on it knows the account behind a device proof was proven by its\n * owner. The session it also mints is the ordinary MDMA session token, accepted\n * everywhere a Google-issued one is.\n *\n * Standard base64, not base64url \u2014 mdma decodes with `validate=True`.\n */\nexport async function signAccountLogin(\n input: AccountLoginInput,\n): Promise<string> {\n if (!input.nonce) throw new Error('nonce is required to sign an account login');\n const key = await importSigningKey(input.rootSecret);\n const signature = new Uint8Array(\n await crypto.subtle.sign(\n 'Ed25519',\n key,\n concat(ACCOUNT_LOGIN_DOMAIN, new TextEncoder().encode(input.nonce)),\n ),\n );\n return base64(signature);\n}\n\nasync function withPhrase(\n secret: string,\n phrase: string,\n): Promise<RecoverableAccountRoot> {\n return { ...(await accountRootFromSecret(secret)), phrase };\n}\n\nfunction normalisePhrase(phrase: string): string[] {\n // NFKD per BIP39. A no-op for the English list, which is ASCII, but the\n // normalisation is what the spec pins and costs nothing to honour.\n const words = phrase.normalize('NFKD').toLowerCase().trim().split(/\\s+/);\n const count = words[0] === '' ? 0 : words.length;\n if (count !== PHRASE_WORDS) {\n throw new Error(\n `recovery phrase must be ${PHRASE_WORDS} words, got ${count}`,\n );\n }\n return words;\n}\n\n/** Entropy then checksum, chopped into 11-bit indices. */\nasync function encodePhrase(entropy: Uint8Array): Promise<string> {\n const bits = bitsOf(entropy) + (await checksumBits(entropy));\n const words: string[] = [];\n for (let i = 0; i < PHRASE_WORDS; i += 1) {\n const index = Number.parseInt(bits.slice(i * 11, i * 11 + 11), 2);\n words.push(ENGLISH_WORDLIST[index]);\n }\n return words.join(' ');\n}\n\nasync function decodePhrase(words: string[]): Promise<Uint8Array> {\n let bits = '';\n for (const word of words) {\n const index = ENGLISH_WORDLIST.indexOf(word);\n if (index < 0) {\n throw new Error(`\"${word}\" is not a word in the recovery phrase list`);\n }\n bits += index.toString(2).padStart(11, '0');\n }\n\n const entropy = new Uint8Array(ENTROPY_BITS / 8);\n for (let i = 0; i < entropy.length; i += 1) {\n entropy[i] = Number.parseInt(bits.slice(i * 8, i * 8 + 8), 2);\n }\n if (bits.slice(ENTROPY_BITS) !== (await checksumBits(entropy))) {\n throw new Error(\n 'recovery phrase checksum failed \u2014 check for a mistyped or transposed word',\n );\n }\n return entropy;\n}\n\n/** The leading `CHECKSUM_BITS` of SHA-256 over the entropy, as a bit string. */\nasync function checksumBits(entropy: Uint8Array): Promise<string> {\n const digest = new Uint8Array(await crypto.subtle.digest('SHA-256', entropy));\n return bitsOf(digest).slice(0, CHECKSUM_BITS);\n}\n\nfunction bitsOf(bytes: Uint8Array): string {\n let bits = '';\n for (const byte of bytes) bits += byte.toString(2).padStart(8, '0');\n return bits;\n}\n\nfunction base64(bytes: Uint8Array): string {\n // Built a character at a time rather than spreading into `String.fromCharCode`:\n // the spread is fine for a 64-byte signature but blows the argument limit on\n // anything larger, and this has no such cliff to remember.\n let binary = '';\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary);\n}\n", "/**\n * Proving an account to the cloud for the one read a joiner cannot avoid.\n *\n * `GET /api/cloud/namespaces/{ns}/admitters` answers where to send a signed\n * membership op. A joiner needs it and *cannot* hold a cloud login: it is by\n * construction not the namespace owner, and not needing an account on the cloud\n * is the entire point of the path \u2014 a keyholder mints a root, certifies a\n * device and signs its own join offline, and this read is the only thing that\n * tells it where to post the result.\n *\n * So the caller proves possession of the credential it already holds. The cloud\n * mints a sealed, namespace-bound nonce; the device signs it; the read carries\n * the certificate and that signature.\n *\n * ## What this establishes, and what it does not\n *\n * It establishes that the caller holds a device key certified by some account\n * root, and *which* account that is. It does **not** establish that the account\n * was invited to the namespace, nor that it is a member \u2014 the cloud cannot\n * check either, because membership is governance state held by the nodes.\n * Anyone can mint an account root offline, so this is not a wall. It buys\n * attribution (every read names an account and can be rate-limited) and it ends\n * anonymous bulk discovery. Authorization stays where it always was: at the\n * node, on the signed op.\n *\n * ## Why the DEVICE signs, not the root\n *\n * The opposite choice from {@link signAccountLink}, deliberately. That one\n * exists for the device-loss case, so only the root will do. This one runs on\n * every routing read from a browser that discards the root immediately after\n * certifying its device \u2014 requiring the root would mean re-entering a 24-word\n * phrase to look up a URL.\n *\n * The certificate alone is not enough and the signature is not redundant with\n * it: a certificate travels in the clear inside every device-link op, so anyone\n * who has seen one can replay it. Only the nonce signature binds the presenter\n * to the device.\n */\n\nimport { concat, fromHex, importSigningKey } from '../crypto/internal.js';\n\n/**\n * The signing domain, followed immediately by the nonce as UTF-8.\n *\n * A flat prefix rather than this SDK's usual length-prefixed `domainHash`,\n * because the verifier is MDMA's `verify_routing_proof` and it checks\n * `DOMAIN \u2016 nonce` against the raw message. The trailing NUL is part of the\n * domain string on both sides; it is what stops a domain that is a prefix of\n * some future one from colliding with it.\n */\nconst ROUTING_PROOF_DOMAIN = new TextEncoder().encode(\n 'calimero.mdma.routing-read.v1\\0',\n);\n\n/**\n * The credential a routing read presents: a certificate, and the key to sign\n * the challenge with.\n *\n * `credential` is the 237-byte `AccountProof<DeviceCert>` as hex \u2014 exactly what\n * {@link signDeviceCert} returns, and the same string a login statement and a\n * warrant carry. It is public by construction.\n *\n * `deviceSecret` never leaves the process. It signs the challenge locally and\n * only the signature travels.\n */\nexport interface RoutingCredential {\n /** The `AccountProof<DeviceCert>`, hex borsh, as `signDeviceCert` returns it. */\n credential: string;\n /** The certified device's ed25519 signing secret, hex (32 bytes). */\n deviceSecret: string;\n}\n\n/** A challenge to sign, as the cloud minted it. */\nexport interface RoutingChallenge {\n namespaceId: string;\n /** Opaque and sealed \u2014 the client neither parses nor constructs this. */\n nonce: string;\n /** Epoch **milliseconds**, as MDMA reports it. Nonces live about two minutes. */\n expiresAtMs: number;\n}\n\n/**\n * A challenge bound to an ACCOUNT rather than a namespace, for asking which\n * relays serve that account.\n *\n * Separate from {@link RoutingChallenge} because the binding is the security\n * property, not a field name: MDMA seals the account into this one and the\n * namespace into that one, and refuses each at the other's route. Keeping the\n * TypeScript types distinct is what stops a caller passing the wrong one and\n * learning about it as a 403.\n */\nexport interface DiscoveryChallenge {\n accountId: string;\n /** Opaque and sealed \u2014 the client neither parses nor constructs this. */\n nonce: string;\n /** Epoch **milliseconds**, as MDMA reports it. Nonces live about two minutes. */\n expiresAtMs: number;\n}\n\n/**\n * The three header values a proven routing read carries.\n *\n * Intersected with `Record<string, string>` rather than declared as a plain\n * interface so it drops straight into a headers bag: an interface has no index\n * signature, so TypeScript refuses it where `Record<string, string>` is wanted\n * even though every field is a string.\n */\nexport type RoutingProofHeaders = Record<string, string> & {\n 'X-Calimero-Credential': string;\n 'X-Calimero-Nonce': string;\n 'X-Calimero-Signature': string;\n};\n\n/**\n * Sign a routing challenge with the device key.\n *\n * Returns base64, which is what the header carries and what MDMA's verifier\n * decodes \u2014 the one place this SDK does not spell a signature as hex, because\n * the wire format is not ours to choose.\n */\nexport async function signRoutingChallenge(\n nonce: string,\n deviceSecret: string,\n): Promise<string> {\n // Validate the secret the same way every other signing path does, so a\n // truncated or mis-cased key fails here rather than as a 403 from the cloud.\n fromHex(deviceSecret, 'deviceSecret', 32);\n const key = await importSigningKey(deviceSecret);\n\n const message = concat(\n ROUTING_PROOF_DOMAIN,\n new TextEncoder().encode(nonce),\n );\n\n const signature = new Uint8Array(\n await crypto.subtle.sign({ name: 'Ed25519' }, key, message),\n );\n\n return base64(signature);\n}\n\n/**\n * Build the headers for one routing read, from a challenge the cloud minted.\n *\n * Separated from the request so a caller that fetches its own challenge \u2014 a\n * test, or a client batching several reads against one nonce \u2014 can use the same\n * encoding rather than a second spelling of it.\n *\n * Takes either challenge kind: only the nonce is signed and both are signed\n * over the same domain, so one encoder serves both. What separates them is what\n * MDMA sealed inside, which is checked server-side.\n */\nexport async function routingProofHeaders(\n challenge: RoutingChallenge | DiscoveryChallenge,\n credential: RoutingCredential,\n): Promise<RoutingProofHeaders> {\n return {\n 'X-Calimero-Credential': credential.credential,\n 'X-Calimero-Nonce': challenge.nonce,\n 'X-Calimero-Signature': await signRoutingChallenge(\n challenge.nonce,\n credential.deviceSecret,\n ),\n };\n}\n\n/**\n * base64, the encoding the header carries.\n *\n * `btoa` is global in every browser and in Node since 16, and this package\n * requires Node 18 \u2014 so there is no runtime here that needs a `Buffer`\n * fallback, and adding one would pull a Node-only global into browser code.\n */\nfunction base64(bytes: Uint8Array): string {\n let binary = '';\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary);\n}\n", "/**\n * Calimero Cloud (MDMA) client.\n *\n * # Why the SDK knows about the cloud at all\n *\n * Delegated execution lets an account that holds one signing key write into a\n * context without running a node \u2014 but only through a *relay*, and a relay is\n * something the client has to be told about. A warrant names an executor\n * account, so a client that cannot discover which relay serves its namespace,\n * and which account that relay writes as, cannot mint a usable warrant at all.\n *\n * For a self-hosted namespace the operator hands those two facts over out of\n * band. For the hosted tier the cloud is the directory: the user signs in, the\n * cloud knows which namespaces they own and which attested TEE nodes are\n * assigned to each, and it answers with the relay's URL and executor account.\n * That is what this client is for \u2014 it is the discovery half of\n * {@link RelayClient}, not a second way to talk to a node.\n *\n * # What it deliberately is not\n *\n * It never sees a warrant, a device secret, or an intent. Discovery goes\n * through the cloud; the write goes straight from the client to the relay. A\n * cloud that proxied intents would read every method and argument its users\n * ever sent, and would be a throughput bottleneck on a path that has no reason\n * to involve it.\n */\n\nimport {\n accountRootFromSecret,\n signAccountLink,\n signAccountLogin,\n} from '../account/index.js';\nimport { HTTPError } from '../http-client/web-client.js';\nimport {\n routingProofHeaders,\n type DiscoveryChallenge,\n type RoutingChallenge,\n type RoutingCredential,\n} from './routing-proof.js';\n\n/** Where the hosted cloud lives when a caller names no other. */\nconst DEFAULT_CLOUD_BASE_URL = 'https://cloud.calimero.network';\n\nexport interface CloudClientConfig {\n cloudBaseUrl?: string;\n /**\n * An MDMA session token to start out authenticated with \u2014 from a previous\n * {@link CloudClient.signInWithGoogle}, persisted by the app.\n *\n * Sessions are 7-day JWTs with a rolling refresh, so a stored one is usually\n * still live and re-running the Google flow on every launch is avoidable.\n */\n sessionToken?: string;\n /**\n * Called whenever the session token changes \u2014 a sign-in, an explicit\n * refresh, or a rolling refresh the server performed mid-request. Persist it\n * here; errors thrown are swallowed so a storage failure cannot mask the\n * response the caller is waiting for.\n */\n onSession?: (session: CloudSession | null) => void | Promise<void>;\n /** Injected for tests and non-browser runtimes. Defaults to global `fetch`. */\n fetch?: typeof fetch;\n /** Per-request timeout. Defaults to 10s, as the node clients do. */\n timeoutMs?: number;\n /**\n * The device credential to prove an account with on the routing read.\n *\n * Nothing to do with {@link sessionToken}: a cloud session says who is signed\n * in, this says which account holds the key. A joiner has the second and\n * cannot have the first \u2014 see `routing-proof.ts` for why that is not an\n * oversight.\n *\n * Omitting it leaves {@link CloudClient.getNamespaceRouting} anonymous, which\n * the cloud still answers while `require_routing_proof` is off. Supply it and\n * the read is attributed; the cloud verifies a supplied proof either way, so\n * a wrong one surfaces immediately rather than on the day the flag flips.\n */\n routingCredential?: RoutingCredential;\n}\n\n/** Who is signed in, and until when. */\nexport interface CloudSession {\n sessionToken: string;\n /** Epoch **seconds**, as MDMA reports it \u2014 not milliseconds. */\n expiresAt: number;\n user: { email: string; name?: string; picture?: string };\n}\n\n/** One namespace the signed-in user owns. */\nexport interface CloudNamespace {\n namespaceId: string;\n /** Always equal to `namespaceId`; MDMA keeps it for wire compatibility. */\n groupId: string;\n /** `'enabled' | 'disabled' | 'none'` \u2014 `'none'` means HA was never enabled. */\n haStatus: string;\n haEnabledAt?: string | null;\n contexts: string[];\n fleetReplicas?: { current?: number; limit?: number } | unknown;\n}\n\n/**\n * One attested node the cloud has assigned to a namespace, described as a\n * relay.\n *\n * `authorshipReady` is the field that decides whether a write can happen, and\n * it is reported rather than assumed because it is not the cloud's to grant:\n * `CAN_AUTHOR_ON_BEHALF` is a governance capability, set by an admin of the\n * namespace, and the cloud only relays what the node observed. A relay that is\n * `active` with `authorshipReady: false` is a healthy node waiting for a grant.\n */\nexport interface CloudRelay {\n peerId: string;\n /** Base URL to present intents to, or `null` when the cloud knows none yet. */\n relayUrl: string | null;\n /** The account a warrant for this relay must name as its `executor`, hex. */\n executorAccount: string | null;\n /** `'assigned'` (admitted pending confirm) or `'active'`. */\n status: string;\n /** Whether this relay holds `CAN_AUTHOR_ON_BEHALF` on the namespace. */\n authorshipReady: boolean;\n lastSeenAt?: string | null;\n confirmedAt?: string | null;\n}\n\n/**\n * One node serving a namespace, as a caller with no cloud account sees it.\n *\n * The public counterpart to {@link CloudRelay}: same fleet rows, but keyed on a\n * namespace id anyone holding an invitation already has, and answering both\n * things a keyholder needs from a node rather than one of them.\n */\n/**\n * One relay that serves an account, as the bootstrap read reports it.\n *\n * `fresh` and a null `relayUrl` are reported rather than filtered, because\n * \"your relay is down\", \"we have no address for it\" and \"you have no relay\"\n * need different actions from a caller and an empty list erases the\n * distinction. A caller looking for somewhere to talk to wants\n * `fresh && relayUrl`; one diagnosing wants the rest.\n */\nexport interface CloudAccountRelay {\n peerId: string;\n relayUrl: string | null;\n fresh: boolean;\n}\n\nexport interface CloudNamespaceNode {\n peerId: string;\n /**\n * The node's own account id, hex.\n *\n * Intersect this against the invitation's signed `admitters` list before\n * using the node. That list is a MINT-TIME SNAPSHOT, so a node assigned after\n * an invitation was created is live, healthy, listed here, and still refused\n * with 403 \u2014 the cloud cannot know which invitation you hold.\n */\n account: string | null;\n /** Base URL, or `null` when the cloud knows none yet. */\n relayUrl: string | null;\n /** Ready-made admission URL, so a caller never rebuilds the path. */\n admitUrl: string | null;\n /** `'assigned'` (admitted pending confirm) or `'active'`. */\n status: string;\n /** Whether the node's heartbeat for this namespace is recent. */\n fresh: boolean;\n /**\n * Whether a signed join can be presented here now.\n *\n * Deliberately independent of {@link CloudNamespaceNode.authorshipReady}:\n * relaying a join the joiner already signed is not authoring on anyone's\n * behalf, so a node with no grant admits perfectly well.\n */\n canAdmit: boolean;\n /** Whether the node holds `CAN_AUTHOR_ON_BEHALF` on the namespace root group. */\n authorshipReady: boolean;\n /**\n * Whether a delegated write can be posted here now \u2014 a URL, an account, a\n * fresh heartbeat and the grant.\n *\n * Namespace-level. A context in a RESTRICTED SUBGROUP is governed by that\n * subgroup, so per-context routing remains the exact answer if a write is\n * refused despite this being true.\n */\n canExecute: boolean;\n}\n\n/** What {@link CloudClient.getNamespaceRouting} reports. */\nexport interface CloudNamespaceRouting {\n namespaceId: string;\n /** Usable nodes first, then a stable order, so a polling caller sees no shuffle. */\n nodes: CloudNamespaceNode[];\n /** Whether any listed node can take a join now. */\n servable: boolean;\n /** Whether any listed node can take a delegated write now. */\n writable: boolean;\n}\n\n/** One namespace of the caller's that a machine is serving. */\nexport interface CloudMachineNamespace {\n namespaceId: string;\n /** `'assigned'` (admitted pending confirm) or `'active'`. */\n status: string;\n /** Whether the machine holds `CAN_AUTHOR_ON_BEHALF` on this namespace. */\n authorshipReady: boolean;\n /**\n * Whether the machine's heartbeat for this namespace is recent.\n *\n * The same freshness rule the per-plan fleet cap uses, so a replica that\n * stopped polling reads as stale here rather than continuing to look\n * healthy \u2014 otherwise \"my writes stopped working\" has no visible cause.\n */\n fresh: boolean;\n confirmedAt?: string | null;\n lastSeenAt?: string | null;\n}\n\n/**\n * One attested machine serving this account.\n *\n * A narrow view on purpose: the zone, machine type, instance name, public IP\n * and KMS ids are operator data with no client use, and a cloud user is not an\n * operator.\n */\nexport interface CloudMachine {\n peerId: string;\n relayUrl: string | null;\n executorAccount: string | null;\n /**\n * Whether this machine can write for the caller *somewhere* \u2014 a URL, an\n * executor account, and the grant on at least one namespace.\n *\n * Folded here so a client does not re-derive the three-way precondition and\n * get a refusal it cannot explain.\n */\n canExecute: boolean;\n /** Only the caller's own namespaces, even on a machine shared with others. */\n namespaces: CloudMachineNamespace[];\n}\n\n/** A Calimero account linked to this cloud login. */\nexport interface CloudLinkedAccount {\n /** The account id, 64 hex. */\n accountId: string;\n linkedAt?: string | null;\n /**\n * Whether a recovery envelope has been stored for this account, so a UI can\n * say \"recoverable\" without fetching the blob.\n */\n hasRecoveryEnvelope: boolean;\n}\n\n/** What {@link CloudClient.getMyAccounts} reports. */\nexport interface CloudLinkedAccounts {\n accounts: CloudLinkedAccount[];\n /** How many the caller's plan allows. `null` means no limit. */\n limit: number | null;\n}\n\n/** A single-use challenge to sign with an account root. */\nexport interface AccountLinkChallenge {\n /** Sign this verbatim \u2014 it is an opaque sealed string, not a decodable id. */\n nonce: string;\n /** When it stops being accepted, epoch milliseconds. */\n expiresAtMs: number | null;\n}\n\n/** A proof assembled by whoever holds the root, for {@link CloudClient.submitAccountLink}. */\nexport interface AccountLinkProof {\n /** The account being claimed, 64 hex. */\n accountId: string;\n /** The root that names it, 64 hex. */\n rootPublicKey: string;\n /** The challenge that was signed. */\n nonce: string;\n /** `signAccountLink`'s output \u2014 standard base64. */\n signature: string;\n}\n\n/** Where to send someone so a cloud login can consent to linking an account. */\nexport interface AccountLinkHandoff {\n /** The cloud portal URL to open \u2014 a new tab, a popup, or the system browser. */\n url: string;\n /** The account the consent will be for, echoed so a caller can show it. */\n accountId: string;\n}\n\n/** What comes back on the callback: a grant, a refusal, or neither. */\nexport interface AccountLinkCallback {\n /** The grant to spend with {@link CloudClient.linkAccountWithGrant}. */\n grant?: string;\n /** The account the grant is for, as the portal echoed it. */\n accountId?: string;\n /** `'denied'` when the person cancelled. Absent on success. */\n error?: string;\n}\n\n/** A single-use challenge for an account root to sign itself in with. */\nexport interface AccountLoginChallenge {\n /** Sign this verbatim. Opaque and sealed by the cloud; nonces live ~2 minutes. */\n nonce: string;\n /** When it stops being accepted, epoch milliseconds. */\n expiresAtMs: number;\n}\n\n/** A proof assembled by whoever holds the root, for {@link CloudClient.submitAccountLogin}. */\nexport interface AccountLoginProof {\n /**\n * The root that names the account, 64 hex.\n *\n * No `accountId` field, unlike {@link AccountLinkProof}: the cloud derives\n * the account from this key rather than accepting one alongside it, so there\n * is nothing a caller could state that the signature would then contradict.\n */\n rootPublicKey: string;\n /** The challenge that was signed. */\n nonce: string;\n /** `signAccountLogin`'s output \u2014 standard base64. */\n signature: string;\n}\n\n/** The outcome of {@link CloudClient.linkAccount}. */\nexport interface CloudAccountLink {\n accountId: string;\n linkedAt?: string | null;\n /**\n * True when this account was already linked to this same login, which the\n * cloud treats as a 200 no-op rather than an error. A caller re-running\n * onboarding gets success, not a confusing failure.\n */\n alreadyLinked: boolean;\n}\n\nexport interface EnableHAOptions {\n groupId: string;\n contextId: string;\n redirectUrl?: string;\n}\n\nexport interface DisableHAOptions {\n groupId: string;\n contextId: string;\n redirectUrl?: string;\n}\n\n/** Response headers MDMA uses to hand back a rolling-refreshed session. */\nconst REFRESH_HEADER = 'x-mdma-session-refresh';\nconst REFRESH_EXPIRES_HEADER = 'x-mdma-session-expires';\n\nexport class CloudClient {\n private baseUrl: string;\n private session: CloudSession | null = null;\n private readonly onSession?: CloudClientConfig['onSession'];\n private readonly fetchImpl: typeof fetch;\n private readonly timeoutMs: number;\n private readonly routingCredential?: RoutingCredential;\n\n constructor(config: CloudClientConfig = {}) {\n this.baseUrl = (config.cloudBaseUrl || DEFAULT_CLOUD_BASE_URL).replace(/\\/+$/, '');\n this.onSession = config.onSession;\n // Bound through an arrow function: an unbound `globalThis.fetch` throws\n // \"Illegal invocation\" in a browser, the same reason the node HTTP clients\n // wrap it.\n const injected = config.fetch;\n this.fetchImpl = injected\n ? (input: RequestInfo | URL, init?: RequestInit) => injected(input, init)\n : (input: RequestInfo | URL, init?: RequestInit) => globalThis.fetch(input, init);\n this.timeoutMs = config.timeoutMs ?? 10_000;\n this.routingCredential = config.routingCredential;\n\n if (config.sessionToken) {\n // Expiry unknown until the server tells us; 0 means \"do not reason about\n // it locally\", never \"expired\" \u2014 the server is the only judge and it\n // answers 401 when it disagrees.\n this.session = {\n sessionToken: config.sessionToken,\n expiresAt: 0,\n user: { email: '' },\n };\n }\n }\n\n /** The current session, or `null` when nobody is signed in. */\n getSession(): CloudSession | null {\n return this.session;\n }\n\n /** Whether a session token is held. Says nothing about the server agreeing. */\n isSignedIn(): boolean {\n return this.session !== null;\n }\n\n /**\n * Exchange a Google OAuth2 ID token for an MDMA session.\n *\n * The app owns the Google flow (One Tap, an auth-code popup, a native\n * browser) and hands the resulting ID token here once. Everything after this\n * uses the returned session token, so the Google credential does not have to\n * be kept or re-minted per call.\n */\n async signInWithGoogle(idToken: string): Promise<CloudSession> {\n const body = await this.request<{\n session_token: string;\n expires_at: number;\n user: { email: string; name?: string; picture?: string };\n }>('POST', '/api/auth/google', { body: { id_token: idToken }, anonymous: true });\n return this.adoptSession(body);\n }\n\n /**\n * Sign in as a Calimero account, proving it with its root key.\n *\n * The password-free counterpart to {@link signInWithGoogle}: no Google, no\n * password, no server-held secret \u2014 the cloud mints a challenge, the account\n * root signs it, and the session that comes back is the same MDMA session\n * token every other cloud route already accepts.\n *\n * Doing this **once** is what makes an account's later device proofs mean\n * something. A routing read shows that the caller holds a device the root\n * certified, but certificates are public, so it can never show that anyone\n * held the root; the cloud records this claim and from then on knows the\n * account behind those reads was claimed by its owner. The claim is recorded\n * even when the session is refused.\n *\n * @param rootSecret the account root's signing secret, 64 hex. It never\n * leaves this process \u2014 only a signature over the cloud's nonce travels.\n *\n * A `403` means either the signature did not verify, or the account is not\n * linked to a cloud login: ownership alone says who you are, and the link\n * says what you are entitled to. Anyone can mint a root offline, so a session\n * on the proof alone would authenticate perfectly and authorize nothing. The\n * ownership claim is recorded either way, so linking later needs no re-proof.\n */\n async signInWithAccount(rootSecret: string): Promise<CloudSession> {\n const root = await accountRootFromSecret(rootSecret);\n const { nonce } = await this.getAccountLoginChallenge();\n return this.submitAccountLogin({\n rootPublicKey: root.publicKey,\n nonce,\n signature: await signAccountLogin({ rootSecret, nonce }),\n });\n }\n\n /**\n * Mint a challenge for an account root to sign.\n *\n * Public, and deliberately so: the caller has no session \u2014 obtaining one is\n * what this exchange does. The nonce is not a capability, it is the thing a\n * capability gets demonstrated against, and demonstrating it needs the root.\n *\n * Split out of {@link signInWithAccount} for the case that call cannot serve:\n * a root held by a hardware key, a desktop app or an air-gapped machine,\n * which signs without the secret ever reaching this process.\n */\n async getAccountLoginChallenge(): Promise<AccountLoginChallenge> {\n const body = await this.request<{ nonce: string; expires_at_ms: number }>(\n 'GET',\n '/api/auth/account/challenge',\n { anonymous: true },\n );\n return { nonce: body.nonce, expiresAtMs: body.expires_at_ms };\n }\n\n /** Post a proof assembled elsewhere. The other half of {@link getAccountLoginChallenge}. */\n async submitAccountLogin(proof: AccountLoginProof): Promise<CloudSession> {\n const body = await this.request<{\n session_token: string;\n expires_at: number;\n user: { email: string; name?: string; picture?: string };\n }>('POST', '/api/auth/account', {\n body: {\n root_public_key: proof.rootPublicKey,\n nonce: proof.nonce,\n signature: proof.signature,\n },\n anonymous: true,\n });\n return this.adoptSession(body);\n }\n\n /**\n * Refresh the session token explicitly.\n *\n * Rarely needed: MDMA rolling-refreshes on ordinary authenticated calls and\n * this client adopts the replacement automatically. Use it when an app wants\n * a fresh token before going idle rather than on the next request.\n */\n async refreshSession(): Promise<CloudSession> {\n const body = await this.request<{\n session_token: string;\n expires_at: number;\n user: { email: string; name?: string; picture?: string };\n }>('POST', '/api/auth/refresh');\n return this.adoptSession(body);\n }\n\n /**\n * Revoke this session server-side and forget it locally.\n *\n * Local state is cleared even if the server call fails: the caller asked to\n * be signed out, and keeping a token they believe is gone is the worse of the\n * two failures.\n */\n async signOut(): Promise<void> {\n try {\n if (this.session) await this.request<void>('POST', '/api/auth/logout', { parse: 'none' });\n } finally {\n this.session = null;\n void this.notifySession(null);\n }\n }\n\n /** Namespaces the signed-in user owns. */\n async getMyNamespaces(): Promise<CloudNamespace[]> {\n const rows = await this.request<Array<Record<string, unknown>>>(\n 'GET',\n '/api/cloud/me/namespaces',\n );\n return rows.map((row) => ({\n namespaceId: String(row.namespace_id ?? row.group_id ?? ''),\n groupId: String(row.group_id ?? row.namespace_id ?? ''),\n haStatus: String(row.ha_status ?? 'none'),\n haEnabledAt: (row.ha_enabled_at as string | null | undefined) ?? null,\n contexts: Array.isArray(row.contexts) ? (row.contexts as string[]) : [],\n fleetReplicas: row.fleet_replicas,\n }));\n }\n\n /**\n * The relays serving one namespace: where to present an intent, and as whom.\n *\n * Everything a client needs in order to mint a warrant it can actually\n * spend. Filter on `authorshipReady` before signing \u2014 a warrant presented to\n * a relay with no grant is refused *after* the author has burned a nonce from\n * its monotonic sequence on it.\n */\n async getNamespaceRelays(namespaceId: string): Promise<CloudRelay[]> {\n const body = await this.request<{ relays?: Array<Record<string, unknown>> }>(\n 'GET',\n `/api/cloud/me/namespaces/${encodeURIComponent(namespaceId)}/relays`,\n );\n return (body.relays ?? []).map((row) => ({\n peerId: String(row.peer_id ?? ''),\n relayUrl: (row.relay_url as string | null | undefined) ?? null,\n executorAccount: (row.executor_account as string | null | undefined) ?? null,\n status: String(row.status ?? ''),\n authorshipReady: row.authorship_ready === true,\n lastSeenAt: (row.last_seen_at as string | null | undefined) ?? null,\n confirmedAt: (row.confirmed_at as string | null | undefined) ?? null,\n }));\n }\n\n /**\n * The first relay for `namespaceId` that can actually run an intent, or\n * `null`.\n *\n * \"Can actually run one\" is three conditions, and a client that checks fewer\n * gets a refusal it cannot explain: the cloud must know a URL, the relay must\n * have reported the executor account a warrant has to name, and the grant\n * must be in place.\n */\n async findExecutingRelay(namespaceId: string): Promise<CloudRelay | null> {\n const relays = await this.getNamespaceRelays(namespaceId);\n return (\n relays.find((r) => r.authorshipReady && r.relayUrl !== null && r.executorAccount !== null) ??\n null\n );\n }\n\n /**\n * Where to reach a namespace without owning it: one read, both verbs.\n *\n * **Anonymous.** A joiner is by construction not the namespace owner and holds\n * no cloud session \u2014 needing one is exactly what this path exists to avoid.\n * Discovery is not authorization: merod refuses an `/admit` call unless the\n * relaying node's account is on the invitation's signed `admitters` list, and\n * refuses an intent unless the warrant is genuinely the member's, so naming a\n * node grants the caller nothing it did not already have.\n *\n * A keyholder resolves a namespace to a node ONCE and then admits, reads and\n * posts intents against that same node, which is why admission and writability\n * are answered together rather than across two endpoints \u2014 the per-context\n * read needs a context id a joiner does not yet have.\n *\n * An empty `nodes` list is a state, not an error: a namespace with no fleet\n * assignment has no cloud node, and the joiner must use one of the\n * invitation's other admitters \u2014 another admin's node, or a self-hosted peer.\n */\n async getNamespaceRouting(namespaceId: string): Promise<CloudNamespaceRouting> {\n const body = await this.request<{\n namespace_id?: unknown;\n admitters?: Array<Record<string, unknown>>;\n servable?: unknown;\n writable?: unknown;\n }>('GET', `/api/cloud/namespaces/${encodeURIComponent(namespaceId)}/admitters`, {\n anonymous: true,\n headers: await this.routingProof(namespaceId),\n });\n return {\n namespaceId: String(body.namespace_id ?? namespaceId),\n nodes: (body.admitters ?? []).map((row) => ({\n peerId: String(row.peer_id ?? ''),\n account: (row.account as string | null | undefined) ?? null,\n relayUrl: (row.relay_url as string | null | undefined) ?? null,\n admitUrl: (row.admit_url as string | null | undefined) ?? null,\n status: String(row.status ?? ''),\n fresh: row.fresh === true,\n canAdmit: row.can_admit === true,\n authorshipReady: row.authorship_ready === true,\n canExecute: row.can_execute === true,\n })),\n servable: body.servable === true,\n writable: body.writable === true,\n };\n }\n\n /**\n * Ask the cloud for a challenge to prove an account against.\n *\n * Public and unauthenticated on purpose, and safe to be: the nonce is sealed\n * and bound to this one namespace, so it is not a capability \u2014 it is the\n * thing a capability gets demonstrated against. A caller with no credential\n * gains nothing by holding one.\n *\n * {@link getNamespaceRouting} calls this for you when the client was\n * constructed with a `routingCredential`. Call it directly only to sign a\n * challenge somewhere this client cannot reach the key.\n */\n async getRoutingChallenge(namespaceId: string): Promise<RoutingChallenge> {\n const body = await this.request<{\n namespace_id?: unknown;\n nonce?: unknown;\n expires_at_ms?: unknown;\n }>('GET', `/api/cloud/namespaces/${encodeURIComponent(namespaceId)}/challenge`, {\n anonymous: true,\n });\n return {\n namespaceId: String(body.namespace_id ?? namespaceId),\n nonce: String(body.nonce ?? ''),\n expiresAtMs: Number(body.expires_at_ms ?? 0),\n };\n }\n\n /**\n * The proof headers for one routing read, or nothing when no credential was\n * configured.\n *\n * A challenge per read rather than a cached one: they expire in about two\n * minutes and are single-namespace, so caching would trade one cheap request\n * for a class of expiry bug that only shows up on a slow client.\n */\n private async routingProof(\n namespaceId: string,\n ): Promise<Record<string, string> | undefined> {\n if (!this.routingCredential) return undefined;\n const challenge = await this.getRoutingChallenge(namespaceId);\n return routingProofHeaders(challenge, this.routingCredential);\n }\n\n /**\n * Ask the cloud for a challenge bound to one ACCOUNT.\n *\n * Public and unauthenticated like {@link getRoutingChallenge}: an account id\n * is `H(genesis(root_pk))` and travels in the clear in every device-link op,\n * so naming one here reveals nothing and authorises nothing. MDMA re-derives\n * the account from the certificate and refuses a mismatch.\n */\n async getAccountRelaysChallenge(accountId: string): Promise<DiscoveryChallenge> {\n const body = await this.request<{\n account_id?: unknown;\n nonce?: unknown;\n expires_at_ms?: unknown;\n }>('GET', `/api/cloud/accounts/${encodeURIComponent(accountId)}/challenge`, {\n anonymous: true,\n });\n return {\n accountId: String(body.account_id ?? accountId),\n nonce: String(body.nonce ?? ''),\n expiresAtMs: Number(body.expires_at_ms ?? 0),\n };\n }\n\n /**\n * Which relays serve this account \u2014 the bootstrap, and nothing more.\n *\n * A device holding a certificate can resolve a namespace it can *name*\n * ({@link getNamespaceRouting}) and can ask a node it can *reach* what its\n * account belongs to. It starts with neither. This closes that one gap.\n *\n * **It does not return namespaces, deliberately.** The node answers that from\n * the governance DAG, scoped to the calling account: log in at one of these\n * relays with an account proof and read its namespace listing. A cloud-side\n * copy would be a second source of truth built from rows that linger for an\n * account which left a namespace.\n *\n * So the sequence is: this \u2192 log in at a relay \u2192 ask it what you are in.\n *\n * Proven with the **device certificate**, not a cloud session, so it needs a\n * `routingCredential` and throws without one rather than returning an empty\n * list \u2014 \"you configured no credential\" and \"you have no relays\" are\n * different answers and must not look alike.\n */\n async getAccountRelays(accountId: string): Promise<CloudAccountRelay[]> {\n if (!this.routingCredential) {\n throw new Error(\n 'getAccountRelays needs a routingCredential: construct the client with the device ' +\n 'certificate and its signing secret, since this read is proven by the device, not by a session.',\n );\n }\n const challenge = await this.getAccountRelaysChallenge(accountId);\n const body = await this.request<{\n account_id?: unknown;\n relays?: Array<Record<string, unknown>>;\n }>('GET', `/api/cloud/accounts/${encodeURIComponent(accountId)}/relays`, {\n anonymous: true,\n headers: await routingProofHeaders(challenge, this.routingCredential),\n });\n return (body.relays ?? []).map((row) => ({\n peerId: String(row.peer_id ?? ''),\n relayUrl: (row.relay_url as string | null | undefined) ?? null,\n fresh: row.fresh === true,\n }));\n }\n\n /**\n * The node to send a signed join to, chosen from those an invitation names.\n *\n * Pass the invitation's `admitters` and the intersection is done here, because\n * skipping it is the failure this method exists to prevent: the cloud lists\n * every node assigned to the namespace, while `admitters` is a snapshot signed\n * when the invitation was minted. A node added afterwards looks perfect in\n * every field here and answers 403.\n *\n * Omit `admitters` only for an invitation that names none \u2014 an empty list on\n * the wire authorises any node to admit.\n */\n async findAdmitter(\n namespaceId: string,\n admitters?: readonly string[],\n ): Promise<CloudNamespaceNode | null> {\n const { nodes } = await this.getNamespaceRouting(namespaceId);\n const allowed =\n admitters && admitters.length > 0 ? new Set(admitters.map((a) => a.toLowerCase())) : null;\n return (\n nodes.find(\n (n) =>\n n.canAdmit &&\n (allowed === null || (n.account !== null && allowed.has(n.account.toLowerCase()))),\n ) ?? null\n );\n }\n\n /**\n * Every attested machine currently serving something this account owns.\n *\n * The account-wide view of the same rows {@link getNamespaceRelays} returns\n * per namespace, keyed by machine \u2014 so a UI can answer \"what is running for\n * me?\" in one call. `canExecute` folds the three-way precondition, so a\n * caller does not re-derive it.\n *\n * Only the caller's own namespaces appear on each machine. A plan's\n * `usersPerMachine` is greater than one, so a fleet node routinely serves\n * several accounts at once.\n */\n async getMyMachines(): Promise<CloudMachine[]> {\n const body = await this.request<{ machines?: Array<Record<string, unknown>> }>(\n 'GET',\n '/api/cloud/me/machines',\n );\n return (body.machines ?? []).map((row) => ({\n peerId: String(row.peer_id ?? ''),\n relayUrl: (row.relay_url as string | null | undefined) ?? null,\n executorAccount: (row.executor_account as string | null | undefined) ?? null,\n canExecute: row.can_execute === true,\n namespaces: (Array.isArray(row.namespaces) ? row.namespaces : []).map((n) => {\n const ns = n as Record<string, unknown>;\n return {\n namespaceId: String(ns.namespace_id ?? ''),\n status: String(ns.status ?? ''),\n authorshipReady: ns.authorship_ready === true,\n fresh: ns.fresh === true,\n confirmedAt: (ns.confirmed_at as string | null | undefined) ?? null,\n lastSeenAt: (ns.last_seen_at as string | null | undefined) ?? null,\n };\n }),\n }));\n }\n\n /**\n * Claim a namespace under this account, proving ownership.\n *\n * The cloud has no other way to learn a namespace exists \u2014 namespaces are\n * created on a node, and there is deliberately no \"create namespace\" in the\n * cloud. `ownershipProof` comes from\n * `admin.issueNamespaceOwnershipProof(namespaceId, \u2026)` on a node that is a\n * **direct admin** of that namespace; merod refuses to issue one otherwise.\n *\n * Pass merod's response object straight through. The cloud accepts core's\n * `signerPublicKey`/`signedPayload` casing as well as snake_case, so there is\n * no re-keying step \u2014 and getting that wrong used to surface as a `422`\n * naming three missing fields.\n *\n * Idempotent for the same account. A namespace already claimed by someone\n * else is a `409`; a replayed proof nonce is a `403`.\n */\n async claimNamespace(namespaceId: string, ownershipProof: unknown): Promise<void> {\n await this.request<unknown>('POST', '/api/cloud/namespaces/claim', {\n body: { namespace_id: namespaceId, ownership_proof: ownershipProof },\n });\n }\n\n /**\n * Ask the fleet to host a claimed namespace \u2014 the step that gets a relay\n * assigned.\n *\n * Carries no proof: {@link claimNamespace} already established ownership,\n * and this is the separate \"now host it\" half. Idempotent.\n *\n * `admitterAddrs` is where the owner's own node can be dialed \u2014 take it from\n * that node's network status with {@link admitterAddrsFromNetworkStatus}.\n * The first fleet node placed on a namespace has nobody but the owner to ask\n * for admission, and without these it can only broadcast and hope the\n * owner's node \u2014 often a laptop behind a relay \u2014 hears it. They are a dial\n * hint, not a grant: the owner's node admits by its own policy. Omit them to\n * leave whatever the cloud already holds; pass them again when the node's\n * relay address changes, and they replace the stored ones.\n *\n * Not to be confused with {@link enableHA}, which opens the cloud's web flow\n * in a browser tab for a user to complete by hand. This is the API call.\n */\n async enableNamespaceHa(\n namespaceId: string,\n options: { admitterAddrs?: readonly string[] } = {},\n ): Promise<unknown> {\n const body =\n options.admitterAddrs === undefined\n ? {}\n : { admitter_addrs: [...options.admitterAddrs] };\n return this.request<unknown>(\n 'POST',\n `/api/cloud/namespaces/${encodeURIComponent(namespaceId)}/enable-ha`,\n { body },\n );\n }\n\n /**\n * Stop hosting a claimed namespace.\n *\n * The fleet's own reconcile loop does the rest: each assigned node notices\n * the namespace is no longer assigned to it and **self-leaves**, which makes\n * core evict it and purge its local data and keys. So this is not just a\n * billing flip.\n */\n async disableNamespaceHa(namespaceId: string): Promise<unknown> {\n return this.request<unknown>(\n 'POST',\n `/api/cloud/namespaces/${encodeURIComponent(namespaceId)}/disable-ha`,\n { body: {} },\n );\n }\n\n /**\n * The node assigned to the signed-in user, or `null`.\n *\n * @deprecated Use {@link getMyMachines}. This reads the cloud's retired\n * `NodeAssignment` ledger, which nothing has written since the v1\n * retirement, so it answers `null` for every namespace-native account \u2014\n * which reads as \"you have no machine\" when the truth is \"that is no longer\n * how a machine is assigned\".\n */\n async getMyNode(): Promise<Record<string, unknown> | null> {\n const body = await this.request<{ node: Record<string, unknown> | null }>(\n 'GET',\n '/api/cloud/me/node',\n );\n return body.node ?? null;\n }\n\n enableHA(options: EnableHAOptions): void {\n const params = new URLSearchParams({\n group_id: options.groupId,\n context_id: options.contextId,\n });\n if (options.redirectUrl) {\n params.set('redirect_url', options.redirectUrl);\n }\n window.open(`${this.baseUrl}/enable-ha?${params.toString()}`);\n }\n\n disableHA(options: DisableHAOptions): void {\n const params = new URLSearchParams({\n group_id: options.groupId,\n context_id: options.contextId,\n });\n if (options.redirectUrl) {\n params.set('redirect_url', options.redirectUrl);\n }\n window.open(`${this.baseUrl}/disable-ha?${params.toString()}`);\n }\n\n /** Record a freshly-issued session and tell the app to persist it. */\n /**\n * Link a Calimero account to this cloud login, proving possession of its root.\n *\n * Two hops, folded into one call because they are useless apart: fetch a\n * single-use challenge, then sign it with the account root and post the proof.\n * The secret never leaves this process \u2014 only a signature over a nonce the\n * cloud minted does.\n *\n * This is the binding the cloud bills against. A relay does not need it to run\n * an intent; the cloud needs it to know whose plan a hosted node is spending,\n * and to sign that account back in later.\n *\n * @param rootSecret the account root's signing secret, 64 hex \u2014 from\n * {@link generateAccountRoot}, from a restored recovery phrase, or from a\n * desktop app or hardware key that already holds one.\n *\n * Idempotent for the same login. An account already linked to a *different*\n * cloud login is a `409`; a replayed or expired challenge is a `403`; and\n * exceeding the plan's account limit is a `402`.\n */\n async linkAccount(rootSecret: string): Promise<CloudAccountLink> {\n const root = await accountRootFromSecret(rootSecret);\n const { nonce } = await this.getAccountLinkChallenge();\n return this.submitAccountLink({\n accountId: root.accountId,\n rootPublicKey: root.publicKey,\n nonce,\n signature: await signAccountLink({ rootSecret, nonce }),\n });\n }\n\n /**\n * Mint a single-use challenge for an account root to sign.\n *\n * The split half of {@link linkAccount}, for the case that call cannot serve:\n * a root held by a desktop app, a hardware key or an air-gapped machine, which\n * signs without the secret ever reaching this process. Fetch the challenge\n * here, have the holder sign it, then hand the proof to\n * {@link submitAccountLink}.\n *\n * Bound to the signed-in login and short-lived, so it is not a standing\n * capability if it leaks \u2014 but it IS single-use, so a challenge fetched and\n * abandoned is spent.\n */\n async getAccountLinkChallenge(): Promise<AccountLinkChallenge> {\n const body = await this.request<{\n nonce: string;\n expires_at_ms?: number | null;\n }>('GET', '/api/cloud/me/accounts/challenge');\n return { nonce: body.nonce, expiresAtMs: body.expires_at_ms ?? null };\n }\n\n /**\n * Post a proof assembled elsewhere. The other half of\n * {@link getAccountLinkChallenge}.\n *\n * `accountId` must be the account `rootPublicKey` names \u2014 the cloud refuses a\n * valid signature by a key that names a different account, or a caller could\n * prove one root and link somebody else's account. Derive both from the same\n * key rather than letting a user type either.\n */\n async submitAccountLink(proof: AccountLinkProof): Promise<CloudAccountLink> {\n const body = await this.request<Record<string, unknown>>(\n 'POST',\n '/api/cloud/me/accounts',\n {\n body: {\n account_id: proof.accountId,\n root_public_key: proof.rootPublicKey,\n nonce: proof.nonce,\n signature: proof.signature,\n },\n },\n );\n return {\n accountId: String(body.account_id ?? proof.accountId),\n linkedAt: (body.linked_at as string | null | undefined) ?? null,\n alreadyLinked: body.already_linked === true,\n };\n }\n\n /**\n * Where to send someone to authorise linking this account, and what to expect\n * back. Pairs with {@link readAccountLinkCallback} and\n * {@link linkAccountWithGrant}.\n *\n * This exists because a browser app is in a bind: it holds an account root and\n * can never hold a *first* cloud session, since only Google issues one. So the\n * person goes to the cloud, where they are signed in, and consents there. What\n * comes back is a grant \u2014 consent to link this one account, spendable only by\n * whoever can sign with its root.\n *\n * @param callbackUrl where the portal sends the person back. Must be an origin\n * the cloud allows; unlisted ones are refused when the grant is minted, not\n * here. The grant arrives in the URL **fragment**, which browsers do not\n * send to servers.\n *\n * Static, so it needs no client and no session: there is nothing to\n * authenticate as yet.\n */\n static accountLinkHandoff(options: {\n /** The cloud portal's origin, e.g. `https://cloud.calimero.network`. */\n portalUrl: string;\n /** The account to be linked, 64 hex \u2014 derive it, never let a user type it. */\n accountId: string;\n /** Where to come back to. */\n callbackUrl: string;\n }): AccountLinkHandoff {\n const portal = options.portalUrl.replace(/\\/+$/, '');\n const query = new URLSearchParams({\n 'link-account': options.accountId,\n 'callback-url': options.callbackUrl,\n });\n return { url: `${portal}/?${query.toString()}`, accountId: options.accountId };\n }\n\n /**\n * Read the portal's answer out of a callback URL's fragment.\n *\n * Returns `{}` when there is nothing to read, so it is safe to call on every\n * load \u2014 which is how a redirected app finds out it has been sent back.\n *\n * @param href defaults to the current location, when there is one.\n */\n static readAccountLinkCallback(href?: string): AccountLinkCallback {\n const source = href ?? (typeof window === 'undefined' ? '' : window.location.href);\n if (!source) return {};\n let hash: string;\n try {\n hash = new URL(source).hash;\n } catch {\n return {};\n }\n const params = new URLSearchParams(hash.replace(/^#/, ''));\n const grant = params.get('grant');\n const accountId = params.get('account');\n const error = params.get('error');\n const out: AccountLinkCallback = {};\n if (grant) out.grant = grant;\n if (accountId) out.accountId = accountId;\n if (error) out.error = error;\n return out;\n }\n\n /**\n * Spend a link grant: sign it with the account root and post the pair.\n *\n * Anonymous, and it has to be \u2014 the app doing this holds no cloud session,\n * which is the whole reason the grant exists. The grant carries the consenting\n * login sealed inside it, so nothing here names one; a caller-supplied email\n * would be a field to lie in.\n *\n * The signature is an ordinary account-link signature with the grant as the\n * nonce, so the same {@link signAccountLink} every other link path uses signs\n * this one. Both halves are required: a grant without the key links nothing,\n * which is what makes it safe to carry through a browser redirect.\n *\n * A `403` means the grant was bad, expired, already spent, or signed by a key\n * that names a different account; `409` that the account belongs to another\n * login; `402` that the plan is full.\n */\n async linkAccountWithGrant(options: {\n grant: string;\n /** The root of the account the grant names, 64 hex. */\n rootSecret: string;\n }): Promise<CloudAccountLink> {\n const root = await accountRootFromSecret(options.rootSecret);\n const body = await this.request<Record<string, unknown>>(\n 'POST',\n '/api/cloud/accounts/link',\n {\n body: {\n grant: options.grant,\n root_public_key: root.publicKey,\n signature: await signAccountLink({\n rootSecret: options.rootSecret,\n nonce: options.grant,\n }),\n },\n anonymous: true,\n },\n );\n return {\n accountId: String(body.account_id ?? root.accountId),\n linkedAt: (body.linked_at as string | null | undefined) ?? null,\n alreadyLinked: body.already_linked === true,\n };\n }\n\n /** The accounts linked to this login, and how many the plan allows. */\n async getMyAccounts(): Promise<CloudLinkedAccounts> {\n const body = await this.request<{\n accounts?: Array<Record<string, unknown>>;\n limit?: number | null;\n }>('GET', '/api/cloud/me/accounts');\n return {\n accounts: (body.accounts ?? []).map((row) => ({\n accountId: String(row.account_id ?? ''),\n linkedAt: (row.linked_at as string | null | undefined) ?? null,\n hasRecoveryEnvelope: row.has_recovery_envelope === true,\n })),\n limit: body.limit ?? null,\n };\n }\n\n private adoptSession(body: {\n session_token: string;\n expires_at: number;\n user: { email: string; name?: string; picture?: string };\n }): CloudSession {\n this.session = {\n sessionToken: body.session_token,\n expiresAt: body.expires_at,\n user: body.user,\n };\n void this.notifySession(this.session);\n return this.session;\n }\n\n private async notifySession(session: CloudSession | null): Promise<void> {\n try {\n await this.onSession?.(session);\n } catch {\n // A failing persistence callback must not mask the call's own result.\n }\n }\n\n private async request<T>(\n method: string,\n path: string,\n options: {\n body?: unknown;\n anonymous?: boolean;\n parse?: 'json' | 'none';\n /** Extra request headers. Never overrides `Authorization` \u2014 see below. */\n headers?: Record<string, string>;\n } = {},\n ): Promise<T> {\n if (!options.anonymous && !this.session) {\n throw new Error(\n 'Not signed in to Calimero Cloud: call signInWithGoogle() or construct the client with a sessionToken',\n );\n }\n\n // Caller headers go in FIRST, so the lines below win on any collision: a\n // per-call header must never be able to replace the session's\n // `Authorization` or change how the body is parsed.\n const headers: Record<string, string> = {\n ...options.headers,\n Accept: 'application/json',\n };\n if (options.body !== undefined) headers['Content-Type'] = 'application/json';\n if (!options.anonymous && this.session) {\n headers.Authorization = `Bearer ${this.session.sessionToken}`;\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n let response: Response;\n try {\n response = await this.fetchImpl(`${this.baseUrl}${path}`, {\n method,\n headers,\n body: options.body === undefined ? undefined : JSON.stringify(options.body),\n signal: controller.signal,\n });\n } catch (err) {\n // Same shape the node clients use for a transport failure: an `HTTPError`\n // with `status: 0`, so a caller has one error type to handle rather than\n // one per layer.\n throw new HTTPError(\n 0,\n err instanceof Error ? err.name : 'NetworkError',\n `${this.baseUrl}${path}`,\n new Headers(),\n err instanceof Error ? err.message : String(err),\n );\n } finally {\n clearTimeout(timer);\n }\n\n this.adoptRollingRefresh(response);\n\n if (!response.ok) {\n const bodyText = await response.text().catch(() => undefined);\n throw new HTTPError(\n response.status,\n response.statusText,\n `${this.baseUrl}${path}`,\n response.headers,\n bodyText,\n );\n }\n\n if (options.parse === 'none' || response.status === 204) return undefined as T;\n return (await response.json()) as T;\n }\n\n /**\n * Adopt a session token the server refreshed mid-request.\n *\n * MDMA rotates the token on activity and returns the replacement in a\n * response header rather than making the client ask. Ignoring it costs\n * nothing immediately and then logs the user out a week later, which is the\n * kind of bug that is impossible to reproduce in a short session.\n *\n * The user identity is carried over: the header holds only a token, and the\n * refresh is the same logical session as the sign-in that named the user.\n */\n private adoptRollingRefresh(response: Response): void {\n const token = response.headers.get(REFRESH_HEADER);\n if (!token || !this.session) return;\n const expiresRaw = response.headers.get(REFRESH_EXPIRES_HEADER);\n const expiresAt = Number(expiresRaw);\n this.session = {\n sessionToken: token,\n expiresAt: Number.isFinite(expiresAt) && expiresAt > 0 ? expiresAt : this.session.expiresAt,\n user: this.session.user,\n };\n void this.notifySession(this.session);\n }\n}\n\n/**\n * The addresses a fleet node can dial to ask this node for admission, from the\n * node's `GET /admin-api/network/status` (see `AdminApiClient`).\n *\n * Its `externalAddrs`, each made to end in `/p2p/<localPeerId>`: that is the\n * shape core dials, and an external address is usually reported without it. A\n * relay-circuit address already contains `/p2p/` \u2014 the relay's \u2014 so the test is\n * whether it ends in this node's own id, not whether it names a peer at all.\n * Anything that is not a multiaddr is dropped. Empty when the status names no\n * peer id or no external address, which is an honest \"not dialable yet\".\n */\nexport function admitterAddrsFromNetworkStatus(status: unknown): string[] {\n const record =\n status && typeof status === 'object'\n ? ((status as { data?: unknown }).data ?? status)\n : undefined;\n if (!record || typeof record !== 'object') return [];\n const peerId = (record as { localPeerId?: unknown }).localPeerId;\n const external = (record as { externalAddrs?: unknown }).externalAddrs;\n if (typeof peerId !== 'string' || !peerId || !Array.isArray(external)) return [];\n const suffix = `/p2p/${peerId}`;\n const out: string[] = [];\n for (const entry of external) {\n if (typeof entry !== 'string') continue;\n const addr = entry.trim();\n if (!addr.startsWith('/')) continue;\n const dialable = addr.endsWith(suffix) ? addr : `${addr}${suffix}`;\n if (!out.includes(dialable)) out.push(dialable);\n }\n return out;\n}\n", "import { createBrowserHttpClient } from './http-client/index.js';\nimport { createAuthApiClientFromHttpClient } from './auth-api/index.js';\nimport { createAdminApiClientFromHttpClient } from './admin-api/index.js';\nimport type { AuthApiClient } from './auth-api/index.js';\nimport type { AdminApiClient } from './admin-api/index.js';\nimport type { HttpClient } from './http-client/index.js';\nimport type { TokenStore } from './token-store/index.js';\nimport { parseAuthCallback, buildAuthLoginUrl } from './auth/index.js';\nimport { createProofSigner } from './request/index.js';\nimport type { ProofRequest, ProofSignerOptions } from './request/index.js';\nimport type { AuthCallbackResult, AuthLoginOptions } from './auth/index.js';\nimport { RpcClient } from './rpc/index.js';\nimport { SseClient } from './events/sse.js';\nimport { WsClient } from './events/ws.js';\nimport { EphemeralClient } from './ephemeral/index.js';\nimport { CloudClient } from './cloud/cloud-client.js';\nimport type { CloudClientConfig } from './cloud/cloud-client.js';\n\ntype ProofCallback = (\n request: ProofRequest,\n) => Promise<string | undefined>;\n\n/**\n * Accept either the keys or a ready signer.\n *\n * Taking a callback as well as an options object is not a convenience: it is\n * how a caller keeps the signing key out of the SDK entirely when it lives\n * somewhere the SDK cannot reach.\n */\nfunction resolveProofSigner(\n config: MeroJsConfig['requestProof'],\n): ProofCallback | undefined {\n if (!config) return undefined;\n return typeof config === 'function' ? config : createProofSigner(config);\n}\n\nexport interface MeroJsConfig {\n /** Base URL for the Calimero node */\n baseUrl: string;\n /** Initial credentials for authentication */\n credentials?: {\n username: string;\n password: string;\n };\n /** Custom HTTP client timeout in milliseconds */\n timeoutMs?: number;\n /** Request credentials mode for fetch (omit, same-origin, include) */\n requestCredentials?: RequestCredentials;\n /** Custom fetch for every HTTP request (`auth`, `admin`, `rpc`, `events`). Defaults to global `fetch`. */\n fetch?: typeof fetch;\n /** Optional token store for persistence */\n tokenStore?: TokenStore;\n /**\n * Called when the node reports that the credential family is gone\n * (`x-auth-error: token_reuse` / `token_revoked`). Terminal: the tokens have\n * already been cleared by the time this runs and nothing is retried, so use it\n * to send the user back through login. Errors thrown here are swallowed \u2014\n * never let a UI failure mask the auth error.\n */\n onAuthRevoked?: () => Promise<void> | void;\n /**\n * Calimero Cloud, reachable as `sdk.cloud`.\n *\n * A node connection and a cloud account are separate things a client can have\n * either of, both of, or neither \u2014 a desktop app with a local node and a\n * cloud subscription has both, and needs them from one object. So this is\n * configuration on the existing SDK rather than a second SDK: pass the\n * session token (or none, and sign in through `sdk.cloud`) and the node half\n * is unaffected.\n *\n * For a caller with *no* node at all, `connectCloud` is the entry point\n * instead \u2014 it returns a connection that can write without one.\n */\n cloud?: CloudClientConfig;\n /**\n * Sign every request with a device key, instead of or alongside a session.\n *\n * A session is a bearer token: obtained once, presented many times. A\n * request-carried proof commits to one method, one path and one body, so it\n * is minted per call and cannot be lifted onto another request. It is what\n * lets a client talk to a relay it has no account on \u2014 nothing was ever\n * issued to it.\n *\n * Pass the keys and let the SDK mint each proof:\n *\n * ```ts\n * new MeroJs({\n * baseUrl,\n * requestProof: { credential, session, signerSecret },\n * });\n * ```\n *\n * \u2026or pass your own signer, when the key must not reach the SDK at all \u2014 a\n * hardware token, or a worker that holds it:\n *\n * ```ts\n * new MeroJs({ baseUrl, requestProof: (req) => myWorker.sign(req) });\n * ```\n *\n * The node only honours this when it runs with `--delegated-access`; a node\n * that does not answers `403`, which is distinct from the `401` a bad proof\n * gets.\n *\n * Applies to the request/response API. `events` (SSE) and `ws` are long-lived\n * connections rather than requests, so a per-request signature does not\n * describe them \u2014 they still use the session.\n */\n requestProof?:\n | ProofSignerOptions\n | ((request: ProofRequest) => Promise<string | undefined>);\n}\n\nexport interface TokenData {\n access_token: string;\n refresh_token: string;\n expires_at: number;\n}\n\n/** Name of the Web Lock that serializes token refresh across tabs / webviews. */\nconst REFRESH_LOCK_NAME = 'mero-js:token-refresh';\n\n/** Minimal structural view of the Web Locks API (not available in every runtime). */\ninterface LockManagerLike {\n request<T>(name: string, callback: () => Promise<T>): Promise<T>;\n}\n\n/** Returns `navigator.locks` when the runtime supports the Web Locks API. */\nfunction getLockManager(): LockManagerLike | null {\n const nav = (globalThis as unknown as { navigator?: { locks?: LockManagerLike } }).navigator;\n const locks = nav?.locks;\n return locks && typeof locks.request === 'function' ? locks : null;\n}\n\n/** Try to extract `exp` (seconds) from a JWT, return ms timestamp or fallback. */\nfunction expiresAtFromJwt(token: string, fallbackMs: number): number {\n try {\n const parts = token.split('.');\n if (parts.length === 3) {\n // JWT uses base64url encoding: replace -/_ with +// and add padding\n let b64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');\n while (b64.length % 4) b64 += '=';\n const payload = JSON.parse(atob(b64));\n if (typeof payload.exp === 'number') {\n return payload.exp * 1000;\n }\n }\n } catch {\n // not a JWT or can't parse\n }\n return fallbackMs;\n}\n\n/**\n * Main MeroJs SDK class that manages all API clients and authentication\n */\nexport class MeroJs {\n private config: MeroJsConfig;\n private httpClient: HttpClient;\n private authClient: AuthApiClient;\n private adminClient: AdminApiClient;\n private tokenData: TokenData | null = null;\n private refreshPromise: Promise<TokenData> | null = null;\n private tokenStore: TokenStore | null;\n private rpcClient: RpcClient | null = null;\n private sseClient: SseClient | null = null;\n private wsClient: WsClient | null = null;\n private wsWarned = false;\n private ephemeralClient?: EphemeralClient;\n private cloudClient?: CloudClient;\n\n constructor(config: MeroJsConfig) {\n this.config = {\n timeoutMs: 10000,\n ...config,\n };\n\n this.tokenStore = config.tokenStore ?? null;\n\n // Restore tokens from store if available\n if (this.tokenStore) {\n this.tokenData = this.tokenStore.getTokens();\n }\n\n // Create HTTP client with token management\n // For Tauri, explicitly set credentials to 'omit' to avoid network errors\n const isTauri = typeof window !== 'undefined' && '__TAURI_INTERNALS__' in window;\n // One signer for the whole connection: `authClient` and `adminClient` are\n // both built FROM this transport, so wiring it here covers every request\n // either of them makes.\n const getProof = resolveProofSigner(this.config.requestProof);\n\n this.httpClient = createBrowserHttpClient({\n baseUrl: this.config.baseUrl,\n fetch: this.config.fetch,\n getProof,\n getAuthToken: async () => {\n const token = await this.getValidToken();\n return token?.access_token || '';\n },\n refreshToken: async () => {\n const refreshed = await this.performTokenRefresh();\n return refreshed.access_token;\n },\n onTokenRefresh: async (newToken: string) => {\n if (this.tokenData) {\n this.tokenData.access_token = newToken;\n this.tokenStore?.setTokens(this.tokenData);\n }\n },\n onAuthRevoked: async () => {\n // The refresh-token family is gone (a single-use refresh token was\n // replayed, or the token was revoked). Nothing left to refresh with \u2014\n // drop the bundle so the app can force a re-login, then let the app react.\n this.clearToken();\n try {\n await this.config.onAuthRevoked?.();\n } catch {\n // A failing app callback must not mask the auth error.\n }\n },\n timeoutMs: this.config.timeoutMs,\n credentials: this.config.requestCredentials ?? (isTauri ? 'omit' : undefined),\n });\n\n // Create API clients\n this.authClient = createAuthApiClientFromHttpClient(this.httpClient, {\n baseUrl: this.config.baseUrl,\n getAuthToken: async () => {\n const token = await this.getValidToken();\n return token?.access_token || '';\n },\n timeoutMs: this.config.timeoutMs,\n });\n\n this.adminClient = createAdminApiClientFromHttpClient(this.httpClient, {\n baseUrl: this.config.baseUrl,\n getAuthToken: async () => {\n const token = await this.getValidToken();\n return token?.access_token || '';\n },\n timeoutMs: this.config.timeoutMs,\n });\n }\n\n /**\n * Get the Auth API client\n */\n get auth(): AuthApiClient {\n return this.authClient;\n }\n\n /**\n * Get the Admin API client\n */\n get admin(): AdminApiClient {\n return this.adminClient;\n }\n\n /**\n * Get the RPC client (lazy initialized)\n */\n get rpc(): RpcClient {\n if (!this.rpcClient) {\n this.rpcClient = new RpcClient({ httpClient: this.httpClient });\n }\n return this.rpcClient;\n }\n\n /**\n * Get the SSE event client (lazy initialized)\n */\n get events(): SseClient {\n if (!this.sseClient) {\n this.sseClient = new SseClient({\n baseUrl: this.config.baseUrl,\n fetch: this.config.fetch,\n getAuthToken: async () => {\n const token = await this.getValidToken();\n return token?.access_token || '';\n },\n });\n }\n return this.sseClient;\n }\n\n /**\n * Get the ephemeral-presence client (lazy initialized).\n *\n * Shares this instance's SSE client, so `subscribe` reuses the one event\n * stream rather than opening a second connection.\n */\n get ephemeral(): EphemeralClient {\n if (!this.ephemeralClient) {\n this.ephemeralClient = new EphemeralClient({\n httpClient: this.httpClient,\n sse: this.events,\n });\n }\n return this.ephemeralClient;\n }\n\n /**\n * Calimero Cloud (lazy initialized).\n *\n * Available whether or not `cloud` was configured \u2014 an app that signs in\n * later gets an unauthenticated client it can call `signInWithGoogle` on,\n * which is one fewer construction path than making the getter conditional.\n * It is entirely independent of this instance's node credential: the cloud\n * knows nothing about the node's tokens and the node knows nothing about the\n * cloud session.\n */\n get cloud(): CloudClient {\n if (!this.cloudClient) {\n this.cloudClient = new CloudClient(this.config.cloud ?? {});\n }\n return this.cloudClient;\n }\n\n /**\n * Get the WebSocket event client (lazy initialized).\n * @experimental Use `events` (SSE) for production. WsClient is experimental.\n */\n get ws(): WsClient {\n if (!this.wsWarned) {\n this.wsWarned = true;\n console.warn('[mero-js] WsClient is experimental. Use mero.events (SSE) for production.');\n }\n if (!this.wsClient) {\n this.wsClient = new WsClient({\n baseUrl: this.config.baseUrl,\n getAuthToken: async () => {\n const token = await this.getValidToken();\n return token?.access_token || '';\n },\n });\n }\n return this.wsClient;\n }\n\n /**\n * Authenticate with the provided credentials\n * This will create the root key on first use\n */\n async authenticate(credentials?: {\n username: string;\n password: string;\n }): Promise<TokenData> {\n const creds = credentials || this.config.credentials;\n if (!creds) {\n throw new Error('No credentials provided for authentication');\n }\n\n try {\n const requestBody = {\n auth_method: 'user_password',\n public_key: creds.username,\n client_name: 'mero-js-sdk',\n permissions: ['admin'],\n timestamp: Math.floor(Date.now() / 1000),\n provider_data: {\n username: creds.username,\n password: creds.password,\n },\n };\n\n const response = await this.authClient.generateTokens(requestBody);\n\n const accessToken = response.data.access_token;\n this.tokenData = {\n access_token: accessToken,\n refresh_token: response.data.refresh_token,\n expires_at: expiresAtFromJwt(accessToken, Date.now() + 3600_000),\n };\n\n this.tokenStore?.setTokens(this.tokenData);\n\n return this.tokenData;\n } catch (error) {\n throw new Error(\n `Authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`,\n );\n }\n }\n\n /**\n * Get a valid token. Returns the current token as-is.\n * The server rejects refresh attempts while the access token is still valid,\n * so we never proactively refresh. Instead, the WebHttpClient handles 401\n * responses reactively via the refreshToken transport hook.\n */\n private async getValidToken(): Promise<TokenData | null> {\n return this.tokenData;\n }\n\n /**\n * Refresh the access token. Single-flight: every caller (including the HTTP\n * transport's 401 hook) goes through here.\n *\n * Refresh tokens are single-use (calimero-network/core#3083): each refresh\n * consumes the presented token and returns a new one, and replaying a consumed\n * token makes the server revoke the whole family. A refresh must therefore\n * happen exactly once per rotation, which needs three layers:\n * - an in-process promise, so concurrent 401s in this instance share one call;\n * - a Web Lock (when available), so other tabs/webviews don't refresh at the\n * same time on the same stored token;\n * - a re-read of the token store inside the lock, so a bundle that another\n * instance already rotated is adopted instead of replaying a stale token.\n */\n private async performTokenRefresh(): Promise<TokenData> {\n if (this.refreshPromise) {\n return this.refreshPromise;\n }\n\n // The access token that triggered this refresh. If the store holds a different\n // one by the time we get the lock, someone else already rotated for us.\n const triggeringAccessToken = this.tokenData?.access_token;\n\n const promise = this.withRefreshLock(() =>\n this.performTokenRefreshLocked(triggeringAccessToken),\n );\n this.refreshPromise = promise;\n\n try {\n return await promise;\n } finally {\n if (this.refreshPromise === promise) {\n this.refreshPromise = null;\n }\n }\n }\n\n /** Run `fn` under the cross-tab refresh lock, or directly if Web Locks are unavailable. */\n private withRefreshLock<T>(fn: () => Promise<T>): Promise<T> {\n const locks = getLockManager();\n return locks ? locks.request(REFRESH_LOCK_NAME, fn) : fn();\n }\n\n /**\n * Perform the actual token refresh. Runs with the refresh lock held.\n */\n private async performTokenRefreshLocked(\n triggeringAccessToken?: string,\n ): Promise<TokenData> {\n // Another instance/tab may have rotated the bundle while we were queued behind\n // the lock \u2014 the store, not our in-memory copy, is the source of truth.\n const stored = this.tokenStore?.getTokens() ?? null;\n if (stored?.refresh_token) {\n this.tokenData = stored;\n\n if (triggeringAccessToken && stored.access_token !== triggeringAccessToken) {\n // Someone else already refreshed. Reuse their bundle rather than replaying\n // our (now consumed) refresh token, which would revoke the family.\n return stored;\n }\n }\n\n if (!this.tokenData?.refresh_token) {\n throw new Error('No refresh token available');\n }\n\n try {\n const response = await this.authClient.refreshToken({\n access_token: this.tokenData.access_token,\n refresh_token: this.tokenData.refresh_token,\n });\n\n const accessToken = response.data.access_token;\n this.tokenData = {\n access_token: accessToken,\n // The refresh token is rotated on every refresh \u2014 persist the new one or\n // the next refresh replays a consumed token.\n refresh_token: response.data.refresh_token,\n expires_at: expiresAtFromJwt(accessToken, Date.now() + 3600_000),\n };\n\n this.tokenStore?.setTokens(this.tokenData);\n\n return this.tokenData;\n } catch (error) {\n // Don't clear tokens on refresh failure \u2014 the access token may still be\n // valid (server rejects refresh while access token hasn't expired yet).\n // A genuinely dead family arrives as `x-auth-error: token_reuse` and is\n // handled by the onAuthRevoked transport hook. Let the caller handle this.\n throw new Error(\n `Token refresh failed: ${error instanceof Error ? error.message : 'Unknown error'}`,\n );\n }\n }\n\n /**\n * Clear the current token\n */\n public clearToken(): void {\n this.refreshPromise = null;\n this.tokenData = null;\n this.tokenStore?.clear();\n }\n\n /**\n * Check if the SDK is authenticated\n */\n public isAuthenticated(): boolean {\n return this.tokenData !== null;\n }\n\n /**\n * Set token data directly (e.g., from auth callback).\n * If `expires_at` is missing or 0, attempts to parse the JWT exp claim,\n * falling back to 1 hour from now.\n */\n public setTokenData(data: TokenData): void {\n const expiresAt = data.expires_at || expiresAtFromJwt(data.access_token, Date.now() + 3600_000);\n this.tokenData = { ...data, expires_at: expiresAt };\n this.tokenStore?.setTokens(this.tokenData);\n }\n\n /**\n * Get the current token data (for debugging)\n */\n public getTokenData(): TokenData | null {\n return this.tokenData;\n }\n\n /**\n * Close all event connections and clean up resources\n */\n public close(): void {\n this.sseClient?.close();\n this.wsClient?.close();\n }\n\n /**\n * Parse an auth callback URL hash fragment (static utility)\n */\n static parseAuthCallback(url: string): AuthCallbackResult | null {\n return parseAuthCallback(url);\n }\n\n /**\n * Build an auth login URL (static utility)\n */\n static buildAuthLoginUrl(nodeUrl: string, opts: AuthLoginOptions): string {\n return buildAuthLoginUrl(nodeUrl, opts);\n }\n}\n\n/**\n * Create a new MeroJs SDK instance\n */\nexport function createMeroJs(config: MeroJsConfig): MeroJs {\n return new MeroJs(config);\n}\n", "import type { TokenData } from '../mero-js.js';\n\nexport interface TokenStore {\n getTokens(): TokenData | null;\n setTokens(data: TokenData): void;\n clear(): void;\n}\n\nexport class MemoryTokenStore implements TokenStore {\n private tokens: TokenData | null = null;\n\n getTokens(): TokenData | null {\n return this.tokens;\n }\n\n setTokens(data: TokenData): void {\n this.tokens = data;\n }\n\n clear(): void {\n this.tokens = null;\n }\n}\n\nconst STORAGE_KEY = 'mero-tokens';\n\nexport class LocalStorageTokenStore implements TokenStore {\n private readonly key: string;\n\n constructor(key: string = STORAGE_KEY) {\n this.key = key;\n }\n\n getTokens(): TokenData | null {\n try {\n if (typeof localStorage === 'undefined') return null;\n const raw = localStorage.getItem(this.key);\n if (!raw) return null;\n const parsed = JSON.parse(raw);\n if (parsed && parsed.access_token && parsed.refresh_token) {\n return {\n access_token: parsed.access_token,\n refresh_token: parsed.refresh_token,\n expires_at: typeof parsed.expires_at === 'number' ? parsed.expires_at : Date.now() + 3600_000,\n };\n }\n return null;\n } catch {\n return null;\n }\n }\n\n setTokens(data: TokenData): void {\n try {\n if (typeof localStorage === 'undefined') return;\n localStorage.setItem(this.key, JSON.stringify(data));\n } catch {\n // Storage unavailable\n }\n }\n\n clear(): void {\n try {\n if (typeof localStorage === 'undefined') return;\n localStorage.removeItem(this.key);\n } catch {\n // Storage unavailable\n }\n }\n}\n", "/**\n * Mint a warrant: the author's consent for one relay to run one intent, once.\n *\n * This exists because the case delegated authorship is *for* is an account that\n * holds no node \u2014 a browser tab, a Node service, a bot. Every one of those is a\n * JS runtime, and until now the only thing that could mint a warrant was `merod`,\n * so \"a member with no node\" in practice meant \"a member with a node it is not\n * allowed to use for anything else\".\n *\n * **No dependencies, and that is not incidental.** The primitives are all built\n * in: `crypto.subtle.digest('SHA-256')` for the hash, `crypto.subtle` Ed25519\n * for the signature, and `DataView` for the integers.\n *\n * Warrant v2 (core#3933) gave up the fixed-width encoding that used to make this\n * pure concatenation: `method` travels in the clear as a string and each cited\n * head list is a vector, so three borsh `u32` counts now sit inside the bytes.\n * Two widths to keep straight, and they are not the same: the counts **inside\n * the encoding** are `u32`, while the counts the **signing preimage** hashes are\n * `u64`. Same numbers, different serialization \u2014 reusing one for the other\n * produces a warrant that is well-formed and verifies nowhere.\n *\n * **The byte contract is pinned in core**, at\n * `crates/account/src/tests/warrant_wire_fixture.rs`. The domain strings below\n * are `pub(crate)` there, so nothing in that repository forces anyone to notice\n * this file depends on them \u2014 which is exactly why the fixture lives on that side\n * and this module's test asserts the same vectors. A drift would otherwise leave\n * this producing well-formed warrants whose signatures verify nowhere, surfacing\n * at a relay as a 403: an authorization refusal, nowhere near the cause.\n */\n\nimport {\n concat,\n derivePublicKey,\n domainHash,\n fromHex,\n hex,\n importSigningKey,\n u32le,\n u64le,\n} from '../crypto/internal.js';\n\nconst SIGN_DOMAIN = new TextEncoder().encode('calimero.warrant.v2');\nconst INTENT_DOMAIN = new TextEncoder().encode('calimero.warrant.intent.v1');\n\n/**\n * core's `MAX_WARRANT_CITED_HEADS`. A node refuses more than this before doing\n * any Ed25519 work, so minting past it produces a warrant nothing will spend.\n */\nconst MAX_CITED_HEADS = 64;\n\n/** 32 zero bytes \u2014 the `appVersion` default. See {@link WarrantInput.appVersion}. */\nconst UNPINNED_APP = '00'.repeat(32);\n\n/** Ed25519 PKCS#8 prefix, so a raw 32-byte seed can be imported by WebCrypto. */\n/** What a warrant authorises. */\nexport interface WarrantInput {\n /**\n * The context, hex \u2014 the same string every other method here takes.\n *\n * Hex like everything else on this interface. It was base58 while the node\n * spelled `ContextId` and `PublicKey` that way and `AccountId` and `DeviceId`\n * in hex; that split is gone, so this module no longer needs a rule per field\n * and no longer needs a base58 decoder to enforce one.\n */\n context: string;\n /** The author's account, hex \u2014 whose consent this is. */\n authorAccount: string;\n /** The relay authorised to act, hex. An account, not a key. */\n executor: string;\n /**\n * The application build this warrant is signed against, hex (32 bytes).\n *\n * Pins the code rather than a version string, so a relay cannot wait for an\n * upgrade that widens what `method` does and then spend a warrant signed\n * against the narrower one. Read it from the context\n * (`GET /admin-api/contexts/{id}` \u2192 `applicationId`); a value guessed here\n * pins the wrong build, which is worse than not pinning.\n *\n * Defaults to 32 zero bytes, matching `merod account sign-warrant`'s\n * `--app-version` default. Nothing verifies the field yet \u2014 core#3933 landed\n * the field set ahead of its enforcement so a signer is written once against\n * the final bytes \u2014 but a warrant minted with the default will be refused once\n * pinning lands, so pass the real value for anything meant to outlive it.\n */\n appVersion?: string;\n /**\n * The method the relay may run.\n *\n * Carried in the clear as of v2, so a peer can select a per-method write-set\n * without reversing a hash against the app's ABI. The *arguments* are still\n * committed to rather than carried \u2014 see {@link intentHash}.\n */\n method: string;\n /** Its arguments, as the JSON the guest will receive. */\n argsJson: unknown;\n /**\n * Monotonic per author **device**.\n *\n * Per device rather than per account because two devices of one account are\n * independent replicas: they cannot coordinate on a counter, so an\n * account-scoped sequence would have them refusing each other's warrants.\n */\n nonce: number | bigint;\n /**\n * Account-log heads this author saw when signing, each hex (32 bytes).\n *\n * Empty by default, and empty is the honest answer for a client that tracks\n * no log \u2014 a fabricated view is worse than none. At most\n * {@link MAX_CITED_HEADS}.\n */\n accountHeads?: string[];\n /**\n * Governance heads this author's view descended from, each hex (32 bytes).\n *\n * **Where these come from decides whether they mean anything.** A floor the\n * relay supplied is a floor the relay chose: a relay that withholds a\n * governance op can hand you a stale view and collect a warrant citing it.\n * Take the value from the account's own devices or a co-signing peer outside\n * the relay's operator. As it stands this detects an honest relay's staleness\n * and does not constrain a dishonest one.\n */\n governanceFloor?: string[];\n /** Unix seconds after which the relay must refuse it. */\n notAfter: number | bigint;\n /**\n * The author device's ed25519 signing secret, hex (32 bytes).\n *\n * Never sent anywhere. It signs locally and only the signature travels, which\n * is the whole reason a warrant can be minted by something holding no node.\n */\n deviceSecret: string;\n}\n\n\n\n\n\n\n\n/**\n * The commitment a warrant carries in place of the intent itself.\n *\n * Its own domain, distinct from the signing one, so a value computed for one\n * purpose cannot be presented for the other.\n */\nexport async function intentHash(\n method: string,\n argsJson: unknown,\n): Promise<Uint8Array> {\n const args = new TextEncoder().encode(JSON.stringify(argsJson));\n return domainHash(INTENT_DOMAIN, [new TextEncoder().encode(method), args]);\n}\n\n\n/**\n * Sign a warrant and return it hex-encoded, ready for `performIntent`.\n *\n * Returns the encoding rather than an object because the signature covers\n * exactly these bytes: a caller that rebuilt the fields from JSON would have a\n * second spelling able to disagree with what was signed.\n */\nexport async function signWarrant(input: WarrantInput): Promise<string> {\n const context = fromHex(input.context, 'context', 32);\n const authorAccount = fromHex(input.authorAccount, 'authorAccount', 32);\n const executor = fromHex(input.executor, 'executor', 32);\n const appVersion = fromHex(\n input.appVersion ?? UNPINNED_APP,\n 'appVersion',\n 32,\n );\n const accountHeads = citedHeads(input.accountHeads, 'accountHeads');\n const governanceFloor = citedHeads(input.governanceFloor, 'governanceFloor');\n\n const key = await importSigningKey(input.deviceSecret);\n const publicKey = await derivePublicKey(input.deviceSecret);\n\n const method = new TextEncoder().encode(input.method);\n const commitment = await intentHash(input.method, input.argsJson);\n const nonce = u64le(input.nonce);\n const notAfter = u64le(input.notAfter);\n\n // Each head list is preceded by its own count. `domainHash` length-prefixes\n // every part it is given, so the heads are individually unambiguous \u2014 but the\n // two lists are adjacent, so without the counts `accountHeads = [a, b],\n // governanceFloor = []` hashes identically to `[a], [b]`, and a relay could\n // relabel which plane a head was cited from. u64 HERE; u32 in the encoding.\n const preimage = await domainHash(SIGN_DOMAIN, [\n context,\n authorAccount,\n publicKey,\n executor,\n appVersion,\n method,\n commitment,\n u64le(accountHeads.length),\n ...accountHeads,\n u64le(governanceFloor.length),\n ...governanceFloor,\n nonce,\n notAfter,\n ]);\n\n const signature = new Uint8Array(\n await crypto.subtle.sign({ name: 'Ed25519' }, key, preimage),\n );\n\n // Borsh: a `String` is a u32 length then its UTF-8 bytes; a `Vec<[u8; 32]>` is\n // a u32 count then the elements. u32 HERE; u64 in the preimage above.\n return hex(\n concat(\n context,\n authorAccount,\n publicKey,\n executor,\n appVersion,\n u32le(method.length),\n method,\n commitment,\n u32le(accountHeads.length),\n ...accountHeads,\n u32le(governanceFloor.length),\n ...governanceFloor,\n nonce,\n notAfter,\n signature,\n ),\n );\n}\n\n/** Decode a cited-head list, refusing one longer than a node will accept. */\nfunction citedHeads(heads: string[] | undefined, label: string): Uint8Array[] {\n const list = heads ?? [];\n if (list.length > MAX_CITED_HEADS) {\n throw new Error(\n `${label} cites ${list.length} heads, over the ${MAX_CITED_HEADS} a node accepts`,\n );\n }\n return list.map((head, i) => fromHex(head, `${label}[${i}]`, 32));\n}\n\n/** A warrant's fields, as hex, decoded from the wire encoding. */\nexport interface WarrantFields {\n context: string;\n authorAccount: string;\n /** The author device key `signWarrant` derived from the secret. */\n deviceKey: string;\n executor: string;\n appVersion: string;\n method: string;\n intentHash: string;\n accountHeads: string[];\n governanceFloor: string[];\n /** u64 little-endian, as it sits on the wire. */\n nonce: string;\n /** u64 little-endian, as it sits on the wire. */\n notAfter: string;\n signature: string;\n}\n\n/**\n * Decode a hex warrant back into its fields.\n *\n * Exists because v2's offsets are no longer constants: `method` is a string and\n * the head lists are vectors, so everything after `appVersion` moves with the\n * method's length. Anything reading a warrant by fixed offset \u2014 a test, a client\n * inspecting one it was handed \u2014 silently reads the wrong field instead of\n * failing, which is how the three call sites that did so passed while comparing\n * the method's length prefix to a nonce.\n *\n * One copy of the layout, beside the encoder that produces it, so the two cannot\n * drift. Internal: not exported from the package root, because a client that\n * needs a warrant's fields has the input it signed.\n */\nexport function parseWarrant(warrant: string): WarrantFields {\n const at = (start: number, bytes: number) =>\n warrant.slice(start * 2, (start + bytes) * 2);\n // A borsh u32 is little-endian, so the hex pairs read back to front.\n const u32At = (start: number) =>\n Number.parseInt((at(start, 4).match(/../g) ?? []).reverse().join(''), 16);\n\n let o = 0;\n const take = (bytes: number) => {\n const field = at(o, bytes);\n o += bytes;\n return field;\n };\n\n const context = take(32);\n const authorAccount = take(32);\n const deviceKey = take(32);\n const executor = take(32);\n const appVersion = take(32);\n const methodLen = u32At(o);\n o += 4;\n const method = take(methodLen);\n const intentHashField = take(32);\n const accountCount = u32At(o);\n o += 4;\n const accountHeads = Array.from({ length: accountCount }, () => take(32));\n const governanceCount = u32At(o);\n o += 4;\n const governanceFloor = Array.from({ length: governanceCount }, () =>\n take(32),\n );\n const nonce = take(8);\n const notAfter = take(8);\n const signature = take(64);\n\n if (o * 2 !== warrant.length) {\n throw new Error(\n `warrant is ${warrant.length / 2} bytes but its fields account for ${o}`,\n );\n }\n\n return {\n context,\n authorAccount,\n deviceKey,\n executor,\n appVersion,\n method,\n intentHash: intentHashField,\n accountHeads,\n governanceFloor,\n nonce,\n notAfter,\n signature,\n };\n}\n", "/**\n * Write to a context through a relay, holding nothing but a signing key.\n *\n * # What this replaces\n *\n * `sdk.rpc.execute` asks a node to run a method *as itself*: it needs a node,\n * that node needs to be a member, and the caller needs a credential on it. For\n * a browser tab, a phone, or an agent, none of those three is available \u2014 the\n * runtime cannot compile WASM against materialized state, never received the\n * scope key that seals the deltas, and has no account on anybody's node.\n *\n * This client is the other path. The caller signs a warrant locally, the relay\n * runs the method as the *author's* principal, and the resulting delta is\n * attributed to the author's account and device \u2014 their replica slot, their\n * membership, their name in the app. The relay's key signs the envelope and\n * nothing else.\n *\n * # The three preconditions, and why they are checked in this order\n *\n * `describe()` answers the first two in one call, before anything is signed:\n * the relay's executor account (which the warrant must name) and whether it\n * holds `CAN_AUTHOR_ON_BEHALF` on the owning group. The third \u2014 that the\n * author's account is a member \u2014 only the group knows, and it surfaces as a\n * refusal.\n *\n * Checking before signing is not politeness. A warrant consumes a nonce from a\n * monotonic per-device sequence, and one minted against the wrong executor is\n * unspendable: the number is gone and the write never happened.\n */\n\nimport { signWarrant } from '../warrant/warrant.js';\nimport { HTTPError } from '../http-client/web-client.js';\nimport type { NonceSource } from './nonce-source.js';\n\n/** How long a freshly minted warrant stays presentable, in seconds. */\nconst DEFAULT_TTL_SECONDS = 300;\n\n/** Where and as whom to write, plus the key that consents. */\nexport interface RelayClientConfig {\n /**\n * The relay's base URL \u2014 the node's origin, not a path.\n *\n * Discover it from {@link CloudClient.getNamespaceRelays} for a hosted\n * namespace, or take it from the operator for a self-hosted one.\n */\n relayUrl: string;\n /**\n * The account the relay writes as, hex \u2014 a warrant's `executor`.\n *\n * An account rather than a key, so one of the relay's processes rotating its\n * signing key does not void warrants already issued to it. Left unset, the\n * first `execute` learns it from {@link RelayClient.describe}.\n */\n executorAccount?: string;\n /** The author's account, hex \u2014 whose consent the warrant carries. */\n authorAccount: string;\n /**\n * The author's `AccountProof<DeviceCert>`, hex-encoded borsh.\n *\n * Proves the key that signed the warrant is a device of the account it names.\n * The author sends only its own half; the relay attaches its own.\n */\n authorProof: string;\n /**\n * The author device's ed25519 signing secret, hex (32 bytes).\n *\n * Never transmitted. It signs in this process and only the signature leaves,\n * which is the whole reason a keyholder can author without a node.\n */\n deviceSecret: string;\n /** Where nonces come from. See {@link NonceSource} \u2014 a reset replays. */\n nonces: NonceSource;\n /**\n * Seconds a minted warrant stays valid. Defaults to 300.\n *\n * Checked by the relay against its own clock and deliberately never by peers,\n * so this bounds how long *this* request may sit in flight \u2014 not how long the\n * network will accept the delta.\n */\n ttlSeconds?: number;\n /** Injected for tests and non-browser runtimes. Defaults to global `fetch`. */\n fetch?: typeof fetch;\n timeoutMs?: number;\n}\n\n/** What a relay says about its ability to run intents in one context. */\nexport interface RelayDescription {\n /** The account a warrant must name as `executor`, hex. */\n executorAccount: string;\n /** Whether the relay holds `CAN_AUTHOR_ON_BEHALF` on the owning group. */\n canAuthorOnBehalf: boolean;\n /** The group whose admin grants that capability, hex. */\n groupId: string;\n}\n\n/** Where an accepted intent landed. */\nexport interface IntentResult<T = unknown> {\n /** The context's scope root after the run \u2014 did this change anything? */\n rootHash: string;\n /** The method's own return value. */\n returns: T | null;\n}\n\n/**\n * Thrown when a relay refuses an intent, carrying which precondition failed.\n *\n * The distinction is the whole reason this type exists. A `403` from this\n * endpoint means one of three unrelated things \u2014 no grant on the relay, the\n * author is not a member, or the nonce was already spent \u2014 and they send a\n * caller somewhere completely different: ask an admin, ask for an invitation,\n * or just retry. Collapsing them into \"forbidden\" makes a retryable replay look\n * like a permissions bug.\n */\nexport class IntentRefusedError extends Error {\n name = 'IntentRefusedError';\n\n constructor(\n /** The relay's own explanation, verbatim. */\n public readonly reason: string,\n /** `true` when re-presenting the same intent under a *fresh* warrant may work. */\n public readonly retryable: boolean,\n public readonly status: number,\n ) {\n super(`relay refused the intent (HTTP ${status}): ${reason}`);\n }\n}\n\nexport class RelayClient {\n private readonly baseUrl: string;\n private executorAccount: string | undefined;\n private readonly config: RelayClientConfig;\n private readonly fetchImpl: typeof fetch;\n private readonly timeoutMs: number;\n\n constructor(config: RelayClientConfig) {\n this.config = config;\n this.baseUrl = config.relayUrl.replace(/\\/+$/, '');\n this.executorAccount = config.executorAccount;\n const injected = config.fetch;\n this.fetchImpl = injected\n ? (input: RequestInfo | URL, init?: RequestInit) => injected(input, init)\n : (input: RequestInfo | URL, init?: RequestInit) => globalThis.fetch(input, init);\n this.timeoutMs = config.timeoutMs ?? 10_000;\n }\n\n /**\n * Ask the relay what it can do in `contextId`.\n *\n * Cheap and unauthenticated on a relay that serves delegated execution\n * publicly, which is what makes it usable as a precondition check rather than\n * as diagnostics after a failure.\n */\n async describe(contextId: string): Promise<RelayDescription> {\n const body = await this.json<{\n data: { executorAccount: string; canAuthorOnBehalf: boolean; groupId: string };\n }>('GET', `/admin-api/contexts/${encodeURIComponent(contextId)}/intents`);\n // Learned, not merely returned: a client that called `describe` should not\n // then have to pass the account back in to `execute`.\n this.executorAccount = body.data.executorAccount;\n return body.data;\n }\n\n /**\n * Mint a warrant for `method(args)` and present it to the relay.\n *\n * One intent, once: the warrant authorizes exactly this method and these\n * arguments in exactly this context, and its nonce is spent by the network on\n * apply. Nothing accumulates and nothing is reusable.\n */\n async execute<T = unknown>(\n contextId: string,\n method: string,\n argsJson: unknown = {},\n ): Promise<IntentResult<T>> {\n const executor = this.executorAccount ?? (await this.describe(contextId)).executorAccount;\n\n const nonce = await this.config.nonces.next();\n const ttl = this.config.ttlSeconds ?? DEFAULT_TTL_SECONDS;\n const warrant = await signWarrant({\n context: contextId,\n authorAccount: this.config.authorAccount,\n executor,\n method,\n argsJson,\n nonce,\n notAfter: BigInt(Math.floor(Date.now() / 1000)) + BigInt(ttl),\n deviceSecret: this.config.deviceSecret,\n });\n\n const body = await this.json<{ data: { rootHash: string; returns: T | null } }>(\n 'POST',\n `/admin-api/contexts/${encodeURIComponent(contextId)}/intents`,\n { method, argsJson, warrant, authorProof: this.config.authorProof },\n );\n return { rootHash: body.data.rootHash, returns: body.data.returns ?? null };\n }\n\n private async json<T>(method: string, path: string, body?: unknown): Promise<T> {\n const headers: Record<string, string> = { Accept: 'application/json' };\n if (body !== undefined) headers['Content-Type'] = 'application/json';\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n let response: Response;\n try {\n response = await this.fetchImpl(`${this.baseUrl}${path}`, {\n method,\n headers,\n body: body === undefined ? undefined : JSON.stringify(body),\n signal: controller.signal,\n });\n } catch (err) {\n throw new HTTPError(\n 0,\n err instanceof Error ? err.name : 'NetworkError',\n `${this.baseUrl}${path}`,\n new Headers(),\n err instanceof Error ? err.message : String(err),\n );\n } finally {\n clearTimeout(timer);\n }\n\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n // 400 and 403 are both caller preconditions and core separates them\n // deliberately: 400 means re-sending the same bytes cannot help, 403\n // means the bytes are genuine but authority is missing. Only the spent\n // nonce is worth retrying, and only under a fresh warrant.\n if (response.status === 400 || response.status === 403) {\n const reason = extractReason(text);\n throw new IntentRefusedError(reason, /nonce/i.test(reason), response.status);\n }\n throw new HTTPError(\n response.status,\n response.statusText,\n `${this.baseUrl}${path}`,\n response.headers,\n text,\n );\n }\n\n return (await response.json()) as T;\n }\n}\n\n/** Pull the relay's explanation out of its error body, or fall back to the raw text. */\nfunction extractReason(bodyText: string): string {\n if (!bodyText) return 'no reason given';\n try {\n const parsed = JSON.parse(bodyText) as { error?: unknown; message?: unknown };\n for (const candidate of [parsed.error, parsed.message]) {\n if (typeof candidate === 'string' && candidate) return candidate;\n }\n } catch {\n // Not JSON \u2014 the text itself is the best available explanation.\n }\n return bodyText;\n}\n", "/**\n * Where a warrant's nonce comes from.\n *\n * # Why this is a named concept and not a counter in the relay client\n *\n * A warrant's nonce is spent, once, per `(context, author device)` on every\n * node that applies the delta. So the sequence a client emits is not a local\n * detail \u2014 it is state the network remembers, and the client is the only party\n * that can keep it.\n *\n * The receiving ledger is a **sliding window** 64 wide, not a high-water mark,\n * which is what makes the two failure modes so different:\n *\n * - **Skipping** numbers is free. Gossip gives no ordering, so the window\n * exists precisely to accept whatever unseen nonce arrives.\n * - **Restarting** the sequence is fatal. A tab that reloads and resumes at `1`\n * re-presents nonces the network has already spent, and every one of those\n * writes is refused as a replay \u2014 indistinguishable, to the user, from the\n * app silently not saving.\n *\n * That asymmetry is why persistence is the default here and an in-memory source\n * is the one you have to ask for. It is also why a source may jump: reserving a\n * block and losing the tail is the correct trade.\n */\n\n/** Produces the next nonce for one author device. Must never go backwards. */\nexport interface NonceSource {\n next(): Promise<bigint>;\n}\n\n/**\n * A counter in memory, starting at `start`.\n *\n * Correct only for a process whose lifetime is the whole sequence \u2014 a script, a\n * server-side worker that owns its own device key, a test. In a browser tab it\n * resets on reload and replays; use {@link createLocalStorageNonceSource}\n * there.\n */\nexport function createMemoryNonceSource(start: number | bigint = 1): NonceSource {\n let next = BigInt(start);\n if (next < 1n) {\n throw new Error('nonce sequence starts at 1');\n }\n return {\n next(): Promise<bigint> {\n const value = next;\n next += 1n;\n return Promise.resolve(value);\n },\n };\n}\n\n/**\n * A counter persisted in `localStorage`, so a reload continues the sequence.\n *\n * Key it per author **device**, not per account: two devices of one account are\n * independent replicas with independent sequences, and sharing a counter would\n * have them refusing each other's warrants.\n *\n * **Reserve-then-hand-out.** The stored value is advanced *before* the nonce is\n * returned, so a crash between the two loses a number rather than reusing one.\n * Losing a number costs nothing (the window accepts gaps); reusing one costs a\n * write.\n *\n * Concurrent tabs are the case this cannot fully solve, because `localStorage`\n * has no atomic read-modify-write. Two tabs racing can hand out the same nonce,\n * and the loser's write is refused as a replay rather than silently\n * misattributed \u2014 a visible error, not corruption. An app that genuinely writes\n * from several tabs at once should hold a Web Lock around `next()` or give each\n * tab its own device.\n */\nexport function createLocalStorageNonceSource(\n key: string,\n storage?: { getItem(k: string): string | null; setItem(k: string, v: string): void },\n): NonceSource {\n const store =\n storage ?? (globalThis as unknown as { localStorage?: typeof storage }).localStorage;\n if (!store) {\n throw new Error(\n 'no localStorage in this runtime: pass a storage implementation, or use createMemoryNonceSource for a process that owns its whole sequence',\n );\n }\n return {\n next(): Promise<bigint> {\n const raw = store.getItem(key);\n // A missing, empty, or corrupted value means \"no sequence recorded\", and\n // the safe reading of that is not 1 \u2014 a cleared storage on a device that\n // has already written would replay. But the client cannot know, and 1 is\n // the only defensible start, so it is what a fresh key gets. This is why\n // the doc above tells apps to key per device: a device whose storage was\n // cleared should be re-paired rather than resumed.\n let current = 0n;\n if (raw !== null && raw !== '') {\n try {\n const parsed = BigInt(raw);\n if (parsed > 0n) current = parsed;\n } catch {\n current = 0n;\n }\n }\n const value = current + 1n;\n store.setItem(key, value.toString());\n return Promise.resolve(value);\n },\n };\n}\n", "/**\n * The Noise NK handshake (`Noise_NK_25519_AESGCM_SHA256`) that opens a sealed\n * session, written against WebCrypto from the Noise specification\n * (revision 34, http://www.noiseprotocol.org/noise.html). The node runs the\n * other side with `snow`; core's published vectors check the two against each\n * other.\n *\n * NK: the initiator knows the responder's static key in advance \u2014 here the\n * transport key the node's quote commits to \u2014 and sends nothing but an\n * ephemeral key before both ephemerals are mixed in, so everything after the\n * handshake has forward secrecy.\n */\n\nimport { concat } from '../crypto/internal.js';\n\nconst PROTOCOL_NAME = 'Noise_NK_25519_AESGCM_SHA256';\nconst DH_LEN = 32;\nconst TAG_LEN = 16;\n/** PKCS#8 wrapping of a raw X25519 private key (RFC 8410). */\nconst PKCS8_X25519_PREFIX = new Uint8Array([\n 0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, 0x04,\n 0x22, 0x04, 0x20,\n]);\n\n/** Noise's `SymmetricState`: the chaining key, the handshake hash and the cipher key. */\nexport class SymmetricState {\n private ck: Uint8Array;\n private h: Uint8Array;\n private k: CryptoKey | null = null;\n private n = 0;\n\n private constructor(h: Uint8Array) {\n this.ck = h;\n this.h = h;\n }\n\n static async initialize(): Promise<SymmetricState> {\n // A name of 32 bytes or fewer is used as the hash, zero-padded.\n const name = new TextEncoder().encode(PROTOCOL_NAME);\n const h = new Uint8Array(32);\n h.set(name);\n return new SymmetricState(h);\n }\n\n async mixHash(data: Uint8Array): Promise<void> {\n this.h = await sha256(concat(this.h, data));\n }\n\n async mixKey(inputKeyMaterial: Uint8Array): Promise<void> {\n const [ck, key] = await hkdf2(this.ck, inputKeyMaterial);\n this.ck = ck;\n this.k = await crypto.subtle.importKey('raw', key, 'AES-GCM', false, ['encrypt', 'decrypt']);\n this.n = 0;\n }\n\n async encryptAndHash(plaintext: Uint8Array): Promise<Uint8Array> {\n if (!this.k) throw new Error('Noise: no cipher key yet');\n const ciphertext = new Uint8Array(\n await crypto.subtle.encrypt(\n { name: 'AES-GCM', iv: noiseNonce(this.n), additionalData: this.h },\n this.k,\n plaintext,\n ),\n );\n this.n += 1;\n await this.mixHash(ciphertext);\n return ciphertext;\n }\n\n async decryptAndHash(ciphertext: Uint8Array): Promise<Uint8Array> {\n if (!this.k) throw new Error('Noise: no cipher key yet');\n const plaintext = new Uint8Array(\n await crypto.subtle.decrypt(\n { name: 'AES-GCM', iv: noiseNonce(this.n), additionalData: this.h },\n this.k,\n ciphertext,\n ),\n );\n this.n += 1;\n await this.mixHash(ciphertext);\n return plaintext;\n }\n\n /** The initiator-to-responder key, then the responder-to-initiator key. */\n split(): Promise<[Uint8Array, Uint8Array]> {\n return hkdf2(this.ck, new Uint8Array());\n }\n}\n\nexport interface X25519KeyPair {\n privateKey: CryptoKey;\n publicKey: Uint8Array;\n}\n\n/**\n * Start an NK handshake to `remoteStatic`. `fixedEphemeral` pins the\n * ephemeral secret, for the published test vectors only; leave it out.\n */\nexport async function initiate(\n remoteStatic: Uint8Array,\n prologue: Uint8Array,\n fixedEphemeral?: Uint8Array,\n): Promise<{\n message1: Uint8Array;\n finish(message2: Uint8Array): Promise<{ payload: Uint8Array; split: [Uint8Array, Uint8Array] }>;\n}> {\n const state = await SymmetricState.initialize();\n await state.mixHash(prologue);\n // <- s (known in advance)\n await state.mixHash(remoteStatic);\n // -> e, es\n const ephemeral = await x25519KeyPair(fixedEphemeral);\n await state.mixHash(ephemeral.publicKey);\n await state.mixKey(await dh(ephemeral.privateKey, remoteStatic));\n const message1 = concat(ephemeral.publicKey, await state.encryptAndHash(new Uint8Array()));\n\n return {\n message1,\n async finish(message2) {\n if (message2.length < DH_LEN + TAG_LEN) throw new Error('Noise: message 2 is too short');\n // <- e, ee\n const remoteEphemeral = message2.slice(0, DH_LEN);\n await state.mixHash(remoteEphemeral);\n await state.mixKey(await dh(ephemeral.privateKey, remoteEphemeral));\n const payload = await state.decryptAndHash(message2.slice(DH_LEN));\n return { payload, split: await state.split() };\n },\n };\n}\n\nexport async function x25519KeyPair(secret?: Uint8Array): Promise<X25519KeyPair> {\n if (!secret) {\n let pair: CryptoKeyPair;\n try {\n pair = (await crypto.subtle.generateKey({ name: 'X25519' }, true, [\n 'deriveBits',\n ])) as CryptoKeyPair;\n } catch (error) {\n throw new Error(\n 'Sealed transport needs WebCrypto with X25519, which this runtime lacks ' +\n `(${(error as Error)?.name ?? 'no crypto.subtle'}). Current browsers and Node have ` +\n 'it; in React Native, install react-native-quick-crypto as the global crypto.',\n );\n }\n const publicKey = new Uint8Array(await crypto.subtle.exportKey('raw', pair.publicKey));\n return { privateKey: pair.privateKey, publicKey };\n }\n const privateKey = await crypto.subtle.importKey(\n 'pkcs8',\n concat(PKCS8_X25519_PREFIX, secret),\n { name: 'X25519' },\n true,\n ['deriveBits'],\n );\n const jwk = await crypto.subtle.exportKey('jwk', privateKey);\n return { privateKey, publicKey: base64UrlDecode(jwk.x as string) };\n}\n\nexport async function dh(privateKey: CryptoKey, publicKey: Uint8Array): Promise<Uint8Array> {\n const peer = await crypto.subtle.importKey('raw', publicKey, { name: 'X25519' }, false, []);\n return new Uint8Array(\n await crypto.subtle.deriveBits({ name: 'X25519', public: peer } as EcdhKeyDeriveParams, privateKey, 256),\n );\n}\n\n/** Noise's AESGCM nonce: 32 zero bits, then the counter as a big-endian u64. */\nfunction noiseNonce(n: number): Uint8Array {\n const nonce = new Uint8Array(12);\n new DataView(nonce.buffer).setBigUint64(4, BigInt(n), false);\n return nonce;\n}\n\nasync function sha256(data: Uint8Array): Promise<Uint8Array> {\n return new Uint8Array(await crypto.subtle.digest('SHA-256', data));\n}\n\nasync function hmacSha256(key: Uint8Array, data: Uint8Array): Promise<Uint8Array> {\n const hmacKey = await crypto.subtle.importKey('raw', key, { name: 'HMAC', hash: 'SHA-256' }, false, [\n 'sign',\n ]);\n return new Uint8Array(await crypto.subtle.sign('HMAC', hmacKey, data));\n}\n\n/** Noise's `HKDF` with two outputs. */\nasync function hkdf2(chainingKey: Uint8Array, inputKeyMaterial: Uint8Array): Promise<[Uint8Array, Uint8Array]> {\n const temp = await hmacSha256(chainingKey, inputKeyMaterial);\n const first = await hmacSha256(temp, new Uint8Array([1]));\n const second = await hmacSha256(temp, concat(first, new Uint8Array([2])));\n return [first, second];\n}\n\nfunction base64UrlDecode(value: string): Uint8Array {\n const base64 = value.replace(/-/g, '+').replace(/_/g, '/');\n const binary = atob(base64 + '='.repeat((4 - (base64.length % 4)) % 4));\n return Uint8Array.from(binary, (char) => char.charCodeAt(0));\n}\n", "/**\n * Sealed transport: requests encrypted end to end to a TEE node's attested key.\n *\n * A node speaks plain HTTP and any TLS in front of it ends outside the enclave,\n * at whatever proxy or relay the operator runs, so everything crossing that hop\n * (the bearer token, JSON-RPC arguments, results) is readable there. Sealing\n * encrypts it so that only the attested TD can read it, and only the TD can\n * produce a response the client accepts.\n *\n * The client opens a session with a Noise NK handshake to the X25519 transport\n * key the node's quote commits to ({@link fetchAttestedTransportKey}). The\n * transport key only authenticates the node: the session keys come from both\n * sides' ephemeral keys, so once a session is gone (an hour at most) what was\n * sent in it cannot be opened, not even with the transport key. Each request\n * is sealed under the session; each response streams back in sealed frames, so\n * server-sent events are sealed too. WebSockets cannot be, and the node\n * refuses one inside the envelope with a 501.\n *\n * The node's side, and the wire format this implements byte for byte, are in\n * core's `crates/server/src/sealed.rs`:\n *\n * ```text\n * handshake Noise_NK_25519_AESGCM_SHA256, prologue \"calimero/sealed-http/v2\"\n * request POST /sealed/v2/handshake 0x02 || transport_pk || Noise message 1\n * response 0x02 || Noise message 2 (payload = session_id(16) || lifetime secs, u32 BE)\n * keys (req key, resp key) = Split()\n * exchange POST /sealed/v2\n * header 0x02 || session_id(16) || request_id (u64 BE; from 1, each used once)\n * request header || AES-256-GCM(req key, nonce = request_id || 0u32, aad = header, inner)\n * response frames, each u32 BE length || AES-256-GCM(resp key,\n * nonce = request_id || frame index (u32 BE, from 0), aad = header, kind || data)\n * kind 0 = head (JSON), 1 = body bytes, 2 = end; no end frame = cut short\n * inner u32 BE head length || head (JSON) || body\n * ```\n *\n * Use it by handing {@link createSealedFetch} to `MeroJsConfig.fetch`.\n */\n\nimport { concat, fromHex, hex } from '../crypto/internal.js';\nimport type { TeeAttestRequest, TeeAttestResponseData } from '../admin-api/admin-types.js';\nimport { initiate } from './noise.js';\nimport type { DcapCollateral } from './verify.js';\n\n/** Where a session is opened, relative to the node's base URL. */\nexport const HANDSHAKE_PATH = '/sealed/v2/handshake';\n/** Where sealed requests are posted, relative to the node's base URL. */\nexport const SEALED_PATH = '/sealed/v2';\n/** Content type of every sealed body. */\nexport const SEALED_CONTENT_TYPE = 'application/vnd.calimero.sealed';\n\nconst VERSION = 2;\nconst PROLOGUE = new TextEncoder().encode('calimero/sealed-http/v2');\nconst SESSION_ID_LEN = 16;\nconst HEADER_LEN = 1 + SESSION_ID_LEN + 8;\nconst FRAME_HEAD = 0;\nconst FRAME_DATA = 1;\nconst FRAME_END = 2;\n/**\n * The largest frame the node sends: a kind byte, at most 64 KiB of data, a tag.\n * Anything larger is refused before it is buffered, so a proxy cannot make the\n * client hold an unbounded frame it has yet to authenticate.\n */\nconst MAX_FRAME_LEN = 1 + 64 * 1024 + 16;\n/** Far more than a handshake reply (69 bytes) ever is. */\nconst MAX_HANDSHAKE_REPLY = 1024;\n/** Open a new session this long before the node would drop the current one. */\nconst RENEW_BEFORE_MS = 60_000;\nconst TRANSPORT_BINDING_DOMAIN = 'calimero.tee-attest.transport-key.v1';\nconst NULL_BODY_STATUSES = [101, 204, 205, 304];\n\nconst utf8 = new TextEncoder();\n\n/** The node no longer holds the transport key this client attested (it restarted). */\nexport class StaleTransportKeyError extends Error {\n name = 'StaleTransportKeyError';\n\n constructor() {\n super('The node no longer holds the attested transport key; attest again for the current one');\n }\n}\n\n/** The node refused the envelope itself, before anything inside it ran. */\nexport class SealedTransportError extends Error {\n name = 'SealedTransportError';\n\n constructor(\n public status: number,\n public code: string | undefined,\n message: string,\n /** How long the node asked to wait before retrying (`Retry-After`), if it did. */\n public retryAfterMs?: number,\n ) {\n super(message);\n }\n}\n\n/**\n * What the attest endpoint puts in report data bytes `32..64` when asked to\n * bind its transport key. `inner` is what would have been there otherwise: the\n * expected application hash, or 32 zero bytes when none was asked for.\n * Mirrors core's `attest_transport_binding`.\n */\nexport async function transportKeyBinding(\n inner: Uint8Array,\n transportKey: Uint8Array,\n): Promise<Uint8Array> {\n const digest = await crypto.subtle.digest(\n 'SHA-256',\n concat(utf8.encode(TRANSPORT_BINDING_DOMAIN), inner, transportKey),\n );\n return new Uint8Array(digest);\n}\n\nexport interface VerifyTransportQuote {\n /**\n * Verify `quoteB64` \u2014 never by asking the node being attested \u2014 and check\n * that its report data is `nonce || reportDataSuffix` (both hex). Resolve\n * true only when the quote is genuine, its measurements are ones you accept,\n * and the report data matches. {@link createQuoteVerifier} does all of this\n * in the page. `collateral` is what the node sent with the quote, when the\n * verifier asked for it.\n */\n (args: {\n quoteB64: string;\n nonce: string;\n reportDataSuffix: string;\n collateral?: DcapCollateral;\n }): Promise<boolean>;\n /**\n * Ask the node for the collateral its quote verifies against, and pass it\n * in as `collateral`. Off unless set, because a node that predates the field\n * refuses the request.\n */\n readonly includeCollateral?: boolean;\n}\n\n/**\n * Ask a node for its transport key and accept it only if a quote you verified\n * commits to it.\n *\n * `applicationHash` (hex), when given, is the bytecode hash you expect the\n * attested application to have; the node is asked to bind that application,\n * and the quote only verifies if it runs that exact bytecode.\n */\nexport async function fetchAttestedTransportKey(\n admin: { teeAttest(request: TeeAttestRequest): Promise<TeeAttestResponseData> },\n verify: VerifyTransportQuote,\n options: { applicationId?: string; applicationHash?: string } = {},\n): Promise<Uint8Array> {\n if ((options.applicationId === undefined) !== (options.applicationHash === undefined)) {\n throw new Error('applicationId and applicationHash are given together or not at all');\n }\n const nonce = hex(crypto.getRandomValues(new Uint8Array(32)));\n const attested = await admin.teeAttest({\n nonce,\n applicationId: options.applicationId,\n bindTransportKey: true,\n ...(verify.includeCollateral ? { includeCollateral: true } : {}),\n });\n if (!attested.transportPublicKey) {\n throw new Error('The node did not report a transport key; it predates sealed transport');\n }\n const transportKey = fromHex(attested.transportPublicKey, 'transportPublicKey', 32);\n const inner = options.applicationHash\n ? fromHex(options.applicationHash, 'applicationHash', 32)\n : new Uint8Array(32);\n const reportDataSuffix = hex(await transportKeyBinding(inner, transportKey));\n const verified = await verify({\n quoteB64: attested.quoteB64,\n nonce,\n reportDataSuffix,\n collateral: attested.collateral,\n });\n if (!verified) {\n throw new Error('The attestation did not verify, so its transport key is not trusted');\n }\n return transportKey;\n}\n\nexport interface AttestedSealedFetchOptions {\n /** The node's base URL. */\n baseUrl: string;\n /**\n * Decides whether the node's quote is one you trust \u2014 typically\n * {@link createQuoteVerifier}. Asked again whenever the node restarts.\n */\n verify: VerifyTransportQuote;\n /** Also require the node to run this application with this bytecode hash. */\n applicationId?: string;\n applicationHash?: string;\n /** Fetch that carries the attestation and the envelopes. Defaults to global `fetch`. */\n fetch?: typeof fetch;\n}\n\n/**\n * {@link createSealedFetch} for a node you know only by URL: it attests the\n * node's transport key with `verify`, over `POST /admin-api/tee/attest` (the\n * one call that needs no credential and is never sealed), and seals everything\n * else to it. A restarted node is attested again, through the same verifier.\n *\n * This is what to hand a {@link RelayClient} for a TEE relay, so the warrant\n * and the method's arguments reach only the attested TD:\n *\n * ```ts\n * const relay = new RelayClient({\n * relayUrl,\n * fetch: createAttestedSealedFetch({ baseUrl: relayUrl, verify: createQuoteVerifier(...) }),\n * ...\n * });\n * ```\n */\nexport function createAttestedSealedFetch(options: AttestedSealedFetchOptions): typeof fetch {\n const baseFetch = options.fetch ?? ((input, init) => fetch(input, init));\n const attestUrl = `${options.baseUrl.replace(/\\/+$/, '')}/admin-api/tee/attest`;\n const admin = {\n async teeAttest(request: TeeAttestRequest): Promise<TeeAttestResponseData> {\n const response = await baseFetch(attestUrl, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(request),\n });\n if (!response.ok) {\n throw new Error(`The node refused to attest: HTTP ${response.status}`);\n }\n const body = (await response.json()) as { data?: TeeAttestResponseData };\n if (!body.data) throw new Error('The attestation response is malformed');\n return body.data;\n },\n };\n return createSealedFetch({\n baseUrl: options.baseUrl,\n transportPublicKey: () =>\n fetchAttestedTransportKey(admin, options.verify, {\n applicationId: options.applicationId,\n applicationHash: options.applicationHash,\n }),\n fetch: baseFetch,\n });\n}\n\nexport interface SealedFetchOptions {\n /** The node's base URL, as given to `MeroJsConfig.baseUrl`. */\n baseUrl: string;\n /**\n * The node's attested transport key, or a function that attests and returns\n * it \u2014 typically `() => fetchAttestedTransportKey(admin, verify)`. Given a\n * function, a restarted node is handled: the key is attested again, through\n * your verifier, and the request retried once. Given bytes, a restart\n * surfaces as {@link StaleTransportKeyError}.\n */\n transportPublicKey: Uint8Array | (() => Promise<Uint8Array>);\n /** Fetch that carries the sealed envelope. Defaults to global `fetch`. */\n fetch?: typeof fetch;\n}\n\n/**\n * A `fetch` that seals every request to the node's attested transport key.\n * Pass it as `MeroJsConfig.fetch`.\n *\n * A request for any URL outside `baseUrl` is refused rather than sent in the\n * clear: falling back to plaintext would silently undo the point of sealing.\n */\nexport function createSealedFetch(options: SealedFetchOptions): typeof fetch {\n const base = new URL(options.baseUrl);\n const basePath = base.pathname.replace(/\\/+$/, '');\n const nodeUrl = `${base.origin}${basePath}`;\n const baseFetch = options.fetch ?? ((input, init) => fetch(input, init));\n const attest =\n typeof options.transportPublicKey === 'function' ? options.transportPublicKey : undefined;\n let transportKey: Promise<Uint8Array> = attest\n ? Promise.resolve().then(attest)\n : Promise.resolve(options.transportPublicKey as Uint8Array);\n // Attesting starts now, before any request awaits it. A node that fails to\n // attest must surface on the request that needed the key, not earlier as an\n // unhandled rejection; the failure is still thrown to every awaiter.\n transportKey.catch(() => undefined);\n let session: Promise<Session> | undefined;\n\n const openSession = async (): Promise<Session> => {\n try {\n return await handshake(baseFetch, nodeUrl, await checkedKey(transportKey));\n } catch (error) {\n if (!(error instanceof StaleTransportKeyError) || !attest) throw error;\n transportKey = Promise.resolve().then(attest);\n return handshake(baseFetch, nodeUrl, await checkedKey(transportKey));\n }\n };\n /** The session to use now, and the promise it came from, to name it if it turns out stale. */\n const currentSession = async (): Promise<[Session, Promise<Session>]> => {\n const pending = session ?? renew(undefined);\n const open = await pending;\n if (Date.now() < open.renewAt) return [open, pending];\n const renewed = renew(pending);\n return [await renewed, renewed];\n };\n // Replace the session only if it is still the one found stale, so concurrent\n // requests that all find it stale open one new session between them.\n const renew = (stale: Promise<Session> | undefined): Promise<Session> => {\n if (session === stale) {\n const opening = openSession();\n session = opening;\n opening.catch(() => {\n if (session === opening) session = undefined;\n });\n }\n return session as Promise<Session>;\n };\n\n return async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {\n const request = new Request(input, init);\n const url = new URL(request.url);\n if (url.origin !== base.origin || !url.pathname.startsWith(`${basePath}/`)) {\n throw new Error(`Refusing to send ${url.href} outside the sealed node at ${options.baseUrl}`);\n }\n const headers: Array<[string, string]> = [];\n request.headers.forEach((value, name) => headers.push([name, value]));\n const head = { method: request.method, path: url.pathname.slice(basePath.length) + url.search, headers };\n const body = new Uint8Array(await request.arrayBuffer());\n\n const send = async (open: Session): Promise<Response> => {\n const { requestId, header, envelope } = await sealRequest(open, head, body);\n const response = await baseFetch(`${nodeUrl}${SEALED_PATH}`, {\n method: 'POST',\n headers: { 'content-type': SEALED_CONTENT_TYPE },\n body: envelope,\n signal: request.signal,\n });\n if (response.headers.get('content-type') !== SEALED_CONTENT_TYPE) {\n throw await envelopeRefusal(response);\n }\n return openResponse(response, open.responseKey, header, requestId);\n };\n\n const [open, pending] = await currentSession();\n try {\n return await send(open);\n } catch (error) {\n // The node refused the envelope before running anything in it, so the\n // request can be sent again safely under a new session.\n if (!(error instanceof SealedTransportError) || error.code !== 'unknown_session') throw error;\n return send(await renew(pending));\n }\n };\n}\n\nasync function checkedKey(key: Promise<Uint8Array>): Promise<Uint8Array> {\n const bytes = await key;\n if (bytes.length !== 32) throw new Error('transportPublicKey must be 32 bytes');\n return bytes;\n}\n\nexport interface Session {\n requestKey: CryptoKey;\n responseKey: CryptoKey;\n nextRequestId: number;\n renewAt: number;\n header(requestId: number): Uint8Array;\n}\n\nasync function handshake(\n baseFetch: typeof fetch,\n nodeUrl: string,\n transportKey: Uint8Array,\n): Promise<Session> {\n try {\n return await handshakeOnce(baseFetch, nodeUrl, transportKey);\n } catch (error) {\n // The node limits how fast sessions open. A refused handshake opened\n // nothing, so trying once more after the pause it asks for is safe.\n if (!(error instanceof SealedTransportError) || error.code !== 'busy') throw error;\n await new Promise((resolve) => setTimeout(resolve, error.retryAfterMs ?? 1000));\n return handshakeOnce(baseFetch, nodeUrl, transportKey);\n }\n}\n\nasync function handshakeOnce(\n baseFetch: typeof fetch,\n nodeUrl: string,\n transportKey: Uint8Array,\n): Promise<Session> {\n const noise = await initiate(transportKey, PROLOGUE);\n const response = await baseFetch(`${nodeUrl}${HANDSHAKE_PATH}`, {\n method: 'POST',\n headers: { 'content-type': SEALED_CONTENT_TYPE },\n body: concat(new Uint8Array([VERSION]), transportKey, noise.message1),\n });\n if (response.headers.get('content-type') !== SEALED_CONTENT_TYPE) {\n throw await envelopeRefusal(response);\n }\n const reply = await readAtMost(response, MAX_HANDSHAKE_REPLY);\n return sessionFrom(noise, reply);\n}\n\n/** Finish the handshake with the node's reply. Exported for the published vectors. */\nexport async function sessionFrom(\n noise: Awaited<ReturnType<typeof initiate>>,\n reply: Uint8Array,\n): Promise<Session> {\n if (reply[0] !== VERSION) throw new Error('The handshake reply is malformed');\n let finished: Awaited<ReturnType<(typeof noise)['finish']>>;\n try {\n finished = await noise.finish(reply.slice(1));\n } catch {\n throw new Error('The handshake reply did not open: it did not come from the attested node');\n }\n const { payload, split } = finished;\n if (payload.length !== SESSION_ID_LEN + 4) throw new Error('The handshake reply is malformed');\n const sessionId = payload.slice(0, SESSION_ID_LEN);\n const lifetimeSecs = new DataView(payload.buffer, payload.byteOffset).getUint32(SESSION_ID_LEN, false);\n const [requestKey, responseKey] = await Promise.all([\n crypto.subtle.importKey('raw', split[0], 'AES-GCM', false, ['encrypt']),\n crypto.subtle.importKey('raw', split[1], 'AES-GCM', false, ['decrypt']),\n ]);\n split[0].fill(0);\n split[1].fill(0);\n return {\n requestKey,\n responseKey,\n nextRequestId: 1,\n renewAt: Date.now() + lifetimeSecs * 1000 - RENEW_BEFORE_MS,\n header(requestId) {\n const header = new Uint8Array(HEADER_LEN);\n header[0] = VERSION;\n header.set(sessionId, 1);\n new DataView(header.buffer).setBigUint64(1 + SESSION_ID_LEN, BigInt(requestId), false);\n return header;\n },\n };\n}\n\n/** Seal one request under the session's next request id. */\nexport async function sealRequest(\n session: Session,\n head: RequestHead,\n body: Uint8Array,\n): Promise<{ requestId: number; header: Uint8Array; envelope: Uint8Array }> {\n const requestId = session.nextRequestId;\n session.nextRequestId += 1;\n const header = session.header(requestId);\n const sealed = await crypto.subtle.encrypt(\n { name: 'AES-GCM', iv: nonce(requestId, 0), additionalData: header },\n session.requestKey,\n joinInner(head, body),\n );\n return { requestId, header, envelope: concat(header, new Uint8Array(sealed)) };\n}\n\n/** `request_id || index`, as the node numbers them. */\nfunction nonce(requestId: number, index: number): Uint8Array {\n const bytes = new Uint8Array(12);\n const view = new DataView(bytes.buffer);\n view.setBigUint64(0, BigInt(requestId), false);\n view.setUint32(8, index, false);\n return bytes;\n}\n\nexport interface RequestHead {\n method: string;\n path: string;\n headers: Array<[string, string]>;\n}\n\nexport interface ResponseHead {\n status: number;\n headers: Array<[string, string]>;\n}\n\nfunction joinInner(head: RequestHead, body: Uint8Array): Uint8Array {\n const encoded = utf8.encode(JSON.stringify(head));\n const len = new Uint8Array(4);\n new DataView(len.buffer).setUint32(0, encoded.length, false);\n return concat(len, encoded, body);\n}\n\n/**\n * Open a sealed response as it streams: wait for its head, then hand back a\n * `Response` whose body opens each frame as it arrives. A frame that does not\n * open, arrives out of order, or a stream that stops before its end frame\n * errors the body rather than passing anything unauthenticated on.\n */\nexport async function openResponse(\n response: Response,\n responseKey: CryptoKey,\n header: Uint8Array,\n requestId: number,\n): Promise<Response> {\n if (!response.body) throw new Error('The sealed response has no body');\n const frames = frameReader(response.body.getReader());\n let index = 0;\n const next = async (): Promise<{ kind: number; data: Uint8Array } | null> => {\n const sealed = await frames.next();\n if (!sealed) return null;\n let plain: Uint8Array;\n try {\n plain = new Uint8Array(\n await crypto.subtle.decrypt(\n { name: 'AES-GCM', iv: nonce(requestId, index), additionalData: header },\n responseKey,\n sealed,\n ),\n );\n } catch {\n throw new Error('A sealed response frame did not open: it was not sealed by the attested node');\n }\n index += 1;\n return { kind: plain[0], data: plain.slice(1) };\n };\n\n const first = await next();\n if (!first || first.kind !== FRAME_HEAD) throw new Error('The sealed response is malformed');\n const head = JSON.parse(new TextDecoder().decode(first.data)) as ResponseHead;\n if (NULL_BODY_STATUSES.includes(head.status)) {\n await frames.cancel();\n return new Response(null, { status: head.status, headers: head.headers });\n }\n\n const body = new ReadableStream<Uint8Array>({\n async pull(controller) {\n try {\n const frame = await next();\n if (!frame) {\n controller.error(new Error('The sealed response was cut short'));\n } else if (frame.kind === FRAME_DATA) {\n controller.enqueue(frame.data);\n } else if (frame.kind === FRAME_END) {\n controller.close();\n await frames.cancel();\n } else {\n controller.error(new Error('The sealed response is malformed'));\n }\n } catch (error) {\n controller.error(error);\n }\n },\n cancel() {\n return frames.cancel();\n },\n });\n return new Response(body, { status: head.status, headers: head.headers });\n}\n\n/** Split a byte stream into length-prefixed frames. */\nfunction frameReader(reader: ReadableStreamDefaultReader<Uint8Array>): {\n next(): Promise<Uint8Array | null>;\n cancel(): Promise<void>;\n} {\n let buffer = new Uint8Array();\n const fill = async (length: number): Promise<boolean> => {\n while (buffer.length < length) {\n const { done, value } = await reader.read();\n if (done) return false;\n buffer = concat(buffer, value);\n }\n return true;\n };\n return {\n async next() {\n if (!(await fill(4))) return null;\n const length = new DataView(buffer.buffer, buffer.byteOffset).getUint32(0, false);\n if (length > MAX_FRAME_LEN) {\n await reader.cancel();\n throw new Error('The sealed response is malformed: a frame is larger than the node sends');\n }\n if (!(await fill(4 + length))) return null;\n const frame = buffer.slice(4, 4 + length);\n buffer = buffer.slice(4 + length);\n return frame;\n },\n cancel: () => reader.cancel(),\n };\n}\n\n/** Read a body, refusing one longer than `limit` rather than buffering it. */\nasync function readAtMost(response: Response, limit: number): Promise<Uint8Array> {\n if (!response.body) return new Uint8Array();\n const reader = response.body.getReader();\n let bytes = new Uint8Array();\n for (;;) {\n const { done, value } = await reader.read();\n if (done) return bytes;\n bytes = concat(bytes, value);\n if (bytes.length > limit) {\n await reader.cancel();\n throw new Error('The handshake reply is malformed: it is too long');\n }\n }\n}\n\nasync function envelopeRefusal(response: Response): Promise<Error> {\n let code: string | undefined;\n let message = `Sealed request refused: HTTP ${response.status}`;\n try {\n const body = (await response.json()) as { error?: { code?: string; message?: string } };\n code = body?.error?.code;\n if (body?.error?.message) message = `Sealed request refused: ${body.error.message}`;\n } catch {\n // Not the node's JSON refusal \u2014 a proxy's page, most likely. The status is\n // all that can honestly be reported.\n }\n if (code === 'stale_transport_key') return new StaleTransportKeyError();\n const retryAfter = Number(response.headers.get('retry-after'));\n const retryAfterMs =\n Number.isFinite(retryAfter) && retryAfter > 0 ? Math.min(retryAfter, 5) * 1000 : undefined;\n return new SealedTransportError(response.status, code, message, retryAfterMs);\n}\n", "/**\n * The second way to log in: sign in to Calimero Cloud instead of connecting to\n * a node.\n *\n * # Two connection shapes, one SDK\n *\n * `createMeroJs({ baseUrl })` is the node connection: point at a node, hold a\n * credential on it, and drive its admin API and JSON-RPC. It assumes the caller\n * has a node \u2014 their own, or one they have an account on.\n *\n * `connectCloud(...)` is for the caller who has neither. They sign in with the\n * identity they already use (Google, through the cloud), and the cloud answers\n * with an attested TEE relay serving their namespace. Writes then go through\n * delegated execution: signed here, run there, attributed here.\n *\n * The two are not alternatives to be picked at random \u2014 they answer different\n * questions. \"Which node do I talk to?\" has an answer for a desktop app and no\n * answer at all for a web app whose user has never installed anything. This\n * function is that missing answer, which is why the login step it replaces is\n * the *node URL prompt* rather than the authentication.\n *\n * # What it does not do\n *\n * # Sealing the relay\n *\n * TLS in front of a relay ends outside its TD, at the host's ingress, where\n * the warrant and the method's arguments are readable. Pass `seal` (a quote\n * verifier) and every call to the relay is sealed to the key its attested TD\n * holds, so they are readable only inside it.\n *\n * It does not read state. A relay writes on the author's behalf; reads still\n * come from a node the client can query, or from the app's own projection of\n * the events it receives. `connectCloud` is deliberately narrow about this\n * rather than presenting a half-working `MeroJs` whose `admin` and `rpc` would\n * need a credential nobody holds.\n */\n\nimport { CloudClient } from './cloud-client.js';\nimport type { CloudClientConfig, CloudNamespace, CloudRelay } from './cloud-client.js';\nimport { RelayClient } from '../relay/relay-client.js';\nimport type { IntentResult, NonceSource } from '../relay/index.js';\nimport { createLocalStorageNonceSource, createMemoryNonceSource } from '../relay/nonce-source.js';\nimport { derivePublicKey, hex } from '../crypto/internal.js';\nimport { createAttestedSealedFetch } from '../sealed/sealed.js';\nimport type { VerifyTransportQuote } from '../sealed/sealed.js';\n\nexport interface ConnectCloudOptions {\n /** Defaults to the hosted cloud. */\n cloudBaseUrl?: string;\n /** A stored MDMA session token. Provide this or `googleIdToken`. */\n sessionToken?: string;\n /** A Google ID token to exchange for a session. Provide this or `sessionToken`. */\n googleIdToken?: string;\n /** Persist a new or rolling-refreshed session. See {@link CloudClientConfig.onSession}. */\n onSession?: CloudClientConfig['onSession'];\n\n /** The author's account, hex \u2014 whose writes these will be. */\n authorAccount: string;\n /** The author's `AccountProof<DeviceCert>`, hex-encoded borsh. */\n authorProof: string;\n /** The author device's ed25519 signing secret, hex. Never transmitted. */\n deviceSecret: string;\n\n /**\n * Which namespace to write in.\n *\n * Optional only when the user owns exactly one: guessing among several would\n * pick a tenant for them, and the failure would be a write landing in the\n * wrong place rather than an error.\n */\n namespaceId?: string;\n\n /**\n * Where nonces come from.\n *\n * Defaults to `localStorage`, keyed by the author's device public key, so a\n * reload continues the sequence instead of replaying it. Pass\n * {@link createMemoryNonceSource} explicitly for a process that owns its\n * whole sequence, or your own source to coordinate across tabs.\n */\n nonces?: NonceSource;\n /** Seconds a minted warrant stays presentable. Defaults to 300. */\n ttlSeconds?: number;\n /**\n * Seal every call to the relay to its attested TEE, verified with this \u2014 for\n * example {@link createQuoteVerifier} with the MRTDs you trust. The warrant\n * and the method's arguments then reach only the relay's TD, not whatever\n * terminates TLS in front of it, and the relay is one this client checked\n * itself rather than one the cloud vouched for. Calls to the cloud are not\n * sealed; they carry no intent.\n *\n * Without it, calls to the relay are plain HTTPS.\n */\n seal?: VerifyTransportQuote;\n fetch?: typeof fetch;\n timeoutMs?: number;\n}\n\n/** A signed-in cloud session with a relay ready to write through. */\nexport interface CloudConnection {\n /** The cloud API, for namespaces, billing, HA and sign-out. */\n cloud: CloudClient;\n /** The relay chosen for this namespace. */\n relay: RelayClient;\n /** The namespace writes will land in. */\n namespaceId: string;\n namespace: CloudNamespace;\n /** What the cloud reported about the chosen relay. */\n relayInfo: CloudRelay;\n /** Run one method in one context, on the author's behalf. */\n execute<T = unknown>(\n contextId: string,\n method: string,\n argsJson?: unknown,\n ): Promise<IntentResult<T>>;\n}\n\n/**\n * Sign in to the cloud and return a connection that can write.\n *\n * Throws rather than returning a half-usable connection, and the message says\n * which step is missing \u2014 no namespace, several to choose between, HA not\n * enabled, or no relay holding the authorship grant. Every one of those is\n * something the user has to go and do, and a client that reports them alike\n * cannot tell them what.\n */\nexport async function connectCloud(options: ConnectCloudOptions): Promise<CloudConnection> {\n if (!options.sessionToken && !options.googleIdToken) {\n throw new Error('connectCloud needs either a sessionToken or a googleIdToken');\n }\n\n const cloud = new CloudClient({\n cloudBaseUrl: options.cloudBaseUrl,\n sessionToken: options.sessionToken,\n onSession: options.onSession,\n fetch: options.fetch,\n timeoutMs: options.timeoutMs,\n });\n\n if (!options.sessionToken && options.googleIdToken) {\n await cloud.signInWithGoogle(options.googleIdToken);\n }\n\n const namespaces = await cloud.getMyNamespaces();\n const namespace = pickNamespace(namespaces, options.namespaceId);\n\n // One read, used for both the choice and the diagnostic. `findExecutingRelay`\n // would fetch again to explain itself, and a second call can disagree with the\n // first \u2014 reporting \"waiting for the grant\" about a relay that was already\n // usable a moment ago.\n const relays = await cloud.getNamespaceRelays(namespace.namespaceId);\n const relayInfo =\n relays.find((r) => r.authorshipReady && r.relayUrl !== null && r.executorAccount !== null) ??\n null;\n if (!relayInfo) {\n throw new Error(explainNoRelay(namespace, relays));\n }\n\n const relay = new RelayClient({\n // Checked by `findExecutingRelay`, which is the only thing that returns a\n // relay \u2014 narrowed here rather than re-validated, so the invariant lives in\n // one place.\n relayUrl: relayInfo.relayUrl as string,\n executorAccount: relayInfo.executorAccount as string,\n authorAccount: options.authorAccount,\n authorProof: options.authorProof,\n deviceSecret: options.deviceSecret,\n nonces: options.nonces ?? (await defaultNonceSource(options.deviceSecret)),\n ttlSeconds: options.ttlSeconds,\n // Only the relay is sealed: a sealed fetch is bound to one node and refuses\n // any other URL, and the cloud calls above carry no intent.\n fetch: options.seal\n ? createAttestedSealedFetch({\n baseUrl: relayInfo.relayUrl as string,\n verify: options.seal,\n fetch: options.fetch,\n })\n : options.fetch,\n timeoutMs: options.timeoutMs,\n });\n\n return {\n cloud,\n relay,\n namespaceId: namespace.namespaceId,\n namespace,\n relayInfo,\n execute: (contextId, method, argsJson) => relay.execute(contextId, method, argsJson),\n };\n}\n\n/** The one namespace to write in, or an error naming the actual ambiguity. */\nfunction pickNamespace(namespaces: CloudNamespace[], requested?: string): CloudNamespace {\n if (requested) {\n const found = namespaces.find((n) => n.namespaceId === requested);\n if (!found) {\n throw new Error(\n `this account owns no namespace ${requested}; it owns: ${\n namespaces.map((n) => n.namespaceId).join(', ') || '(none)'\n }`,\n );\n }\n return found;\n }\n if (namespaces.length === 0) {\n throw new Error(\n 'this account owns no namespaces in the cloud: claim one first, then enable HA on it so a relay is assigned',\n );\n }\n if (namespaces.length > 1) {\n throw new Error(\n `this account owns ${namespaces.length} namespaces \u2014 pass namespaceId to say which: ${namespaces\n .map((n) => n.namespaceId)\n .join(', ')}`,\n );\n }\n return namespaces[0];\n}\n\n/**\n * Why no relay can write here \u2014 the three states, kept apart.\n *\n * They are three different asks of the user (turn HA on, wait, or get an admin\n * to grant a capability) and one message for all of them would send them to\n * the wrong one.\n */\nfunction explainNoRelay(namespace: CloudNamespace, relays: CloudRelay[]): string {\n if (relays.length === 0) {\n return namespace.haStatus === 'enabled'\n ? `namespace ${namespace.namespaceId} has HA enabled but no relay has been assigned to it yet \u2014 retry shortly`\n : `namespace ${namespace.namespaceId} has no relays: enable HA on it in the cloud to have an attested node assigned`;\n }\n const waitingOnGrant = relays.filter((r) => !r.authorshipReady);\n if (waitingOnGrant.length === relays.length) {\n // Name the call, not just the capability. The grant is a governance op only\n // a namespace admin can publish, so this message is read by someone who has\n // to go ask for it \u2014 and \"grant CAN_AUTHOR_ON_BEHALF\" alone leaves them\n // relaying a constant to an admin who then has to work out the command.\n // `openToDelegatedExecution` is the durable half (every relay assigned from\n // then on lands open), `grantAuthorship` the one for relays already here.\n return (\n `every relay on namespace ${namespace.namespaceId} is waiting for the authorship grant: ` +\n `an admin of the namespace must grant CAN_AUTHOR_ON_BEHALF to ${\n waitingOnGrant.map((r) => r.executorAccount ?? r.peerId).join(', ') || 'the relay account'\n } \u2014 with AdminClient.grantAuthorship(namespaceId, account) for these relays, and ` +\n `AdminClient.openToDelegatedExecution(namespaceId) so relays assigned later need no further op`\n );\n }\n return `namespace ${namespace.namespaceId} has ${relays.length} relay(s), but none reported both a URL and an executor account yet \u2014 retry shortly`;\n}\n\n/**\n * A persisted nonce source keyed by the author's device public key.\n *\n * The key is the *public* key, derived locally: a per-device key is required\n * (two devices of one account are independent replicas) and the secret must\n * never end up in a storage key an extension or another script can enumerate.\n *\n * Falls back to memory where there is no `localStorage` \u2014 Node, a worker, an\n * edge runtime. Those are processes that own their whole sequence, which is\n * exactly the case a memory counter is correct for.\n */\nasync function defaultNonceSource(deviceSecret: string): Promise<NonceSource> {\n const key = `mero-js:warrant-nonce:${hex(await derivePublicKey(deviceSecret))}`;\n try {\n return createLocalStorageNonceSource(key);\n } catch {\n return createMemoryNonceSource(1);\n }\n}\n", "/**\n * Verifying a TEE node's quote in the page, before trusting the key it binds.\n *\n * A quote proves what runs in a TD only once it has been checked: its\n * signatures against Intel's root, the TCB status of the platform, the\n * measurements of the image, and that its report data is the nonce and the\n * binding this client asked for. {@link createQuoteVerifier} does all of that\n * here, with nothing but the node: the node hands over the Intel-signed\n * collateral with its quote (`includeCollateral`), and a node cannot forge\n * collateral, only choose which valid collateral to serve.\n *\n * The DCAP verification itself \u2014 parsing the quote, checking its signature\n * chain and appraising its TCB level against the collateral \u2014 comes from a\n * library you pass in, normally `verify` from `@phala/dcap-qvl`, a pure\n * JavaScript implementation that runs in browsers, Node and React Native. It\n * is passed in rather than bundled so this SDK keeps no runtime dependencies,\n * and applications that never talk to a TEE node pay nothing for it.\n */\n\nimport { hex } from '../crypto/internal.js';\nimport type { VerifyTransportQuote } from './sealed.js';\n\n/**\n * The collateral a quote is verified against \u2014 TCB info, QE identity, CRLs and\n * their chains \u2014 in dcap-qvl's form: byte fields are hex strings or byte\n * arrays. What `/admin-api/tee/attest` returns with `includeCollateral`, and\n * what `@phala/dcap-qvl`'s `getCollateral` fetches.\n */\nexport interface DcapCollateral {\n pck_crl_issuer_chain: string;\n root_ca_crl: number[] | string;\n pck_crl: number[] | string;\n tcb_info_issuer_chain: string;\n tcb_info: string;\n tcb_info_signature: number[] | string;\n qe_identity_issuer_chain: string;\n qe_identity: string;\n qe_identity_signature: number[] | string;\n}\n\n/** The fields of a TDX report this verifier reads. */\ninterface TdReport {\n mrTd: Uint8Array;\n rtMr0: Uint8Array;\n rtMr1: Uint8Array;\n rtMr2: Uint8Array;\n rtMr3: Uint8Array;\n reportData: Uint8Array;\n}\n\n/** What a DCAP verifier returns for a quote whose signature chain verified. */\nexport interface DcapVerifiedReport {\n status: string;\n advisory_ids?: string[];\n report: { type: string; data: unknown };\n}\n\n/**\n * Verify a quote's signature chain against `collateral` as of `nowSecs`, and\n * appraise its TCB level. Throws when it does not verify. `verify` from\n * `@phala/dcap-qvl` has exactly this shape.\n */\nexport type DcapVerify = (\n quote: Uint8Array,\n collateral: DcapCollateral,\n nowSecs: number,\n) => DcapVerifiedReport;\n\nexport interface QuoteVerifierOptions {\n /** DCAP verification: `verify` from `@phala/dcap-qvl`. */\n dcapVerify: DcapVerify;\n /**\n * MRTDs (hex) of the images trusted. Required: the MRTD names the image a TD\n * booted, and a quote that proves only \"some genuine TD\" proves nothing\n * about which code reads the traffic.\n */\n allowedMrtd: string[];\n /** RTMR allowlists (hex); a register is not checked when its list is left out. */\n allowedRtmr0?: string[];\n allowedRtmr1?: string[];\n allowedRtmr2?: string[];\n allowedRtmr3?: string[];\n /**\n * TCB statuses accepted. Defaults to `['UpToDate']`. `Revoked` is refused\n * even when listed.\n */\n allowedTcbStatuses?: string[];\n /**\n * Where to get collateral when the node sends none (it predates\n * `includeCollateral`): for example\n * `(quote) => getCollateral(PHALA_PCCS_URL, quote)` from `@phala/dcap-qvl`.\n * Without it, such a node's quote is refused.\n */\n fetchCollateral?: (quote: Uint8Array) => Promise<DcapCollateral>;\n /** The current time in milliseconds; defaults to `Date.now`. For tests. */\n now?: () => number;\n}\n\n/** A verifier for {@link fetchAttestedTransportKey}, which asks the node for collateral. */\nexport type QuoteVerifier = VerifyTransportQuote & { readonly includeCollateral: true };\n\nconst MEASUREMENT_HEX = /^[0-9a-f]{96}$/;\n\n/**\n * A verifier that checks a TEE node's quote here, in the page, and accepts it\n * only when every check passes:\n *\n * - its signatures verify against Intel's root, with collateral that is valid\n * now;\n * - the platform's TCB status is one allowed (`UpToDate` by default), and\n * never `Revoked`;\n * - the MRTD, and any RTMRs given, are ones allowed;\n * - its report data is the nonce followed by the binding the client computed.\n *\n * Any failure rejects with the reason; nothing is trusted from a quote that\n * does not pass. Mock quotes never pass: they carry no signature.\n *\n * ```ts\n * import { verify as dcapVerify } from '@phala/dcap-qvl';\n *\n * const verifier = createQuoteVerifier({ dcapVerify, allowedMrtd: [trustedMrtd] });\n * const fetch = createSealedFetch({\n * baseUrl,\n * transportPublicKey: () => fetchAttestedTransportKey(admin, verifier),\n * });\n * ```\n */\nexport function createQuoteVerifier(options: QuoteVerifierOptions): QuoteVerifier {\n const allowedMrtd = measurements(options.allowedMrtd, 'allowedMrtd');\n if (allowedMrtd.length === 0) {\n throw new Error('allowedMrtd is empty, so no quote would ever be accepted');\n }\n const allowedRtmr = [\n measurements(options.allowedRtmr0 ?? [], 'allowedRtmr0'),\n measurements(options.allowedRtmr1 ?? [], 'allowedRtmr1'),\n measurements(options.allowedRtmr2 ?? [], 'allowedRtmr2'),\n measurements(options.allowedRtmr3 ?? [], 'allowedRtmr3'),\n ];\n const allowedStatuses = options.allowedTcbStatuses ?? ['UpToDate'];\n const now = options.now ?? Date.now;\n\n const verifier = async ({\n quoteB64,\n nonce,\n reportDataSuffix,\n collateral,\n }: Parameters<VerifyTransportQuote>[0]): Promise<boolean> => {\n const quote = fromBase64(quoteB64);\n const against = collateral ?? (await options.fetchCollateral?.(quote));\n if (!against) {\n throw new Error(\n 'The node sent no collateral for its quote, and no fetchCollateral was given to get it',\n );\n }\n\n let verified: DcapVerifiedReport;\n try {\n verified = options.dcapVerify(quote, against, Math.floor(now() / 1000));\n } catch (error) {\n throw new Error(`The quote did not verify: ${(error as Error)?.message ?? String(error)}`);\n }\n\n if (verified.status === 'Revoked' || !allowedStatuses.includes(verified.status)) {\n throw new Error(`The platform's TCB status is ${verified.status}, which is not accepted`);\n }\n const td = tdReport(verified.report);\n if (!allowedMrtd.includes(hex(td.mrTd))) {\n throw new Error(`MRTD ${hex(td.mrTd)} is not an image this verifier trusts`);\n }\n const registers = [td.rtMr0, td.rtMr1, td.rtMr2, td.rtMr3];\n registers.forEach((register, index) => {\n const allowed = allowedRtmr[index];\n if (allowed.length > 0 && !allowed.includes(hex(register))) {\n throw new Error(`RTMR${index} ${hex(register)} is not one this verifier trusts`);\n }\n });\n if (hex(td.reportData) !== `${nonce}${reportDataSuffix}`.toLowerCase()) {\n throw new Error('The quote does not commit to this nonce and binding');\n }\n return true;\n };\n return Object.assign(verifier, { includeCollateral: true as const });\n}\n\nfunction measurements(values: string[], label: string): string[] {\n return values.map((value) => {\n const normalized = value.trim().toLowerCase();\n if (!MEASUREMENT_HEX.test(normalized)) {\n throw new Error(`${label} holds ${JSON.stringify(value)}, which is not a 48-byte hex measurement`);\n }\n return normalized;\n });\n}\n\n/** The TD report of a TDX quote (1.0 or 1.5); anything else is refused. */\nfunction tdReport(report: DcapVerifiedReport['report']): TdReport {\n const data = report.data as (TdReport & { base?: TdReport }) | undefined;\n if (report.type === 'td10' && data) return data;\n if (report.type === 'td15' && data?.base) return data.base;\n throw new Error(`The quote is a ${report.type} quote, not a TDX one`);\n}\n\nfunction fromBase64(value: string): Uint8Array {\n const binary = atob(value);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);\n return bytes;\n}\n", "/**\n * Sign a namespace governance op from the SDK \u2014 specifically the membership op a\n * keyholder needs to join without running a node.\n *\n * `admitJoin` carries an already-signed op to a node the inviter named as an\n * admitter. Producing that op is the piece a keyholder could not do before: the\n * op must be signed by the device key in the credential it carries, because every\n * peer checks `signer == credential.statement.sign_pk` when applying a join. An\n * admitter can relay it and can never author it.\n *\n * The encoding is borsh, hand-written to match core's structs. Correctness is not\n * asserted against bytes recorded here \u2014 a layout transcribed by eye is exactly\n * how a golden ends up confidently wrong. It is asserted by a node accepting the\n * op, which is what the e2e does.\n */\nimport type { SignedGroupOpenInvitation } from '../admin-api/admin-types.js';\nimport {\n concat,\n derivePublicKey,\n fromHex,\n hex,\n importSigningKey,\n u32le,\n u64le,\n} from '../crypto/internal.js';\n\n/** Core's `SIGNED_NAMESPACE_OP_SCHEMA_VERSION`. A node refuses any other value. */\nexport const SIGNED_NAMESPACE_OP_SCHEMA_VERSION = 8;\n\n/** Domain prefixed to the signable bytes; core's `NAMESPACE_GOVERNANCE_SIGN_DOMAIN`. */\nconst NAMESPACE_SIGN_DOMAIN = new TextEncoder().encode('calimero.namespace.v1');\n\n/** `NamespaceOp::Root` is the first variant. */\nconst NAMESPACE_OP_ROOT = 0;\n\n/**\n * `RootOp` discriminants, in declaration order. Only the two join variants are\n * emitted here; the rest are listed so the numbering is checkable against core\n * rather than inferred from the two in use.\n */\nconst ROOT_OP = {\n GroupCreated: 0,\n GroupReparented: 1,\n GroupDeleted: 2,\n AdminChanged: 3,\n PolicyUpdated: 4,\n MemberJoined: 5,\n KeyDelivery: 6,\n MemberJoinedOpen: 7,\n MemberJoinedAt: 8,\n NamespaceCreated: 9,\n MemberJoinedViaTeeAttestation: 10,\n} as const;\n\n/** A required `[u8; 32]`, as JSON carries it: 32 byte values. */\nfunction bytes32(value: readonly number[], label: string): Uint8Array {\n if (value.length !== 32) {\n throw new Error(`${label} must be 32 bytes, got ${value.length}`);\n }\n return new Uint8Array(value);\n}\n\n/**\n * Borsh `Option<[u8; 32]>` from the byte array JSON carries it as.\n *\n * `[u8; 32]` crosses JSON as 32 numbers, not as hex \u2014 the hex spelling belongs to\n * `AccountId`, which has its own representation. Encoding one of these from a hex\n * string would silently produce the wrong 32 bytes.\n */\nfunction optionalBytes32(\n value: readonly number[] | null | undefined,\n label: string,\n): Uint8Array {\n if (value === null || value === undefined) {\n return new Uint8Array([0]);\n }\n if (value.length !== 32) {\n throw new Error(`${label} must be 32 bytes, got ${value.length}`);\n }\n return concat(new Uint8Array([1]), new Uint8Array(value));\n}\n\n/** Borsh `String`: u32 length then UTF-8 bytes. */\nfunction borshString(value: string): Uint8Array {\n const bytes = new TextEncoder().encode(value);\n return concat(u32le(bytes.length), bytes);\n}\n\n\n/**\n * Borsh-encode a `SignedGroupOpenInvitation`.\n *\n * Re-encoded rather than passed through, because it has to go inside the op that\n * gets signed. That makes this function version-locked to core: a field added to\n * the invitation and omitted here produces an op whose inner invitation no longer\n * matches the inviter's signature over it. That exact drift, in client-py, broke\n * 93 scenarios \u2014 the failure surfaces as `invalid invitation signature` and points\n * nowhere near the encoder.\n */\nexport function encodeSignedInvitation(signed: SignedGroupOpenInvitation): Uint8Array {\n const body = signed.invitation;\n const admitters = body.admitters ?? [];\n const admitterAddrs = signed.admitter_addrs ?? [];\n\n return concat(\n // GroupInvitationFromAdmin.\n //\n // `SignerId` and `ContextGroupId` cross JSON as 32 numbers; `AccountId`\n // crosses as hex. Same width, different spelling, and reading either the\n // wrong way yields 32 bytes that encode cleanly and verify as nothing.\n bytes32(body.inviter_identity, 'inviter_identity'),\n bytes32(body.group_id, 'group_id'),\n u64le(body.expiration_timestamp),\n bytes32(body.secret_salt, 'secret_salt'),\n new Uint8Array([body.invited_role]),\n u32le(admitters.length),\n ...admitters.map((a, i) => fromHex(a, `admitters[${i}]`, 32)),\n // ...then the enclosing SignedGroupOpenInvitation, in declaration order.\n // `inviter_account` sits between the signature and the addresses; putting it\n // anywhere else still encodes 32 bytes and still verifies as nothing.\n borshString(signed.inviter_signature),\n signed.inviter_account\n ? concat(new Uint8Array([1]), fromHex(signed.inviter_account, 'inviter_account', 32))\n : new Uint8Array([0]),\n // A `Vec<String>`, so each entry is a bare borsh string. This carried an\n // enum until core dropped the URL variant, and an enum writes a\n // discriminant byte ahead of each entry \u2014 encoding one here now would shift\n // every following byte and fail as `invalid invitation signature`, pointing\n // nowhere near this line.\n u32le(admitterAddrs.length),\n ...admitterAddrs.map(borshString),\n optionalBytes32(signed.application_id, 'application_id'),\n // Borsh order follows core's field order, where this is `bytecode_id`; the\n // JSON key is `app_key`, a rename covering both directions that core pins in\n // a test. Encoding by the Rust name would read `undefined` and emit a None\n // where the inviter signed a Some.\n optionalBytes32(signed.app_key, 'app_key'),\n );\n}\n\nexport interface SignMemberJoinInput {\n /** The namespace being joined, 64 hex. */\n readonly namespaceId: string;\n /** The account joining, 64 hex \u2014 the subject of the credential. */\n readonly member: string;\n /** The invitation, as the admin API returned it. */\n readonly invitation: SignedGroupOpenInvitation;\n /**\n * The joiner's `AccountProof<DeviceCert>`, hex \u2014 from `signDeviceCert`.\n *\n * Its `sign_pk` must be the public key of `deviceSecret`; a peer checks that\n * when applying the join and refuses otherwise.\n */\n readonly credential: string;\n /** The device signing secret, 64 hex. Must match the credential's `sign_pk`. */\n readonly deviceSecret: string;\n /**\n * Heads this op is written against. Empty means \"signed against an empty head\"\n * \u2014 which is NOT the same as genesis, and a node with a non-empty head will\n * reject it as stale rather than treat it as a first op.\n */\n readonly parentOpHashes?: readonly string[];\n /** Anti-replay nonce; a node keeps a window per signer. */\n readonly nonce: number | bigint;\n /**\n * When the join happened, unix seconds. Defaults to now.\n *\n * Carried only by `MemberJoinedAt`, which is the variant used whenever the\n * invitation expires \u2014 and since core clamps every invitation to\n * `MAX_INVITATION_VALIDITY_SECS`, that is every invitation. The variant is\n * inferred from the invitation rather than selected by a flag: an expiring\n * invitation joined with plain `MemberJoined` is refused at apply with\n * \"expiration is set but joined_at is absent\", which is not a mistake worth\n * leaving available.\n */\n readonly joinedAt?: number | bigint;\n}\n\n/**\n * Sign a `MemberJoined` (or `MemberJoinedAt`) namespace op and return it\n * borsh-encoded and hex \u2014 the `signedOp` that `admitJoin` takes.\n */\nexport async function signMemberJoinOp(input: SignMemberJoinInput): Promise<string> {\n const parents = input.parentOpHashes ?? [];\n const credential = fromHex(input.credential, 'credential', input.credential.length / 2);\n\n // An invitation that expires must be claimed with `MemberJoinedAt`, which\n // carries `joined_at` between the invitation and the credential. Choosing the\n // variant from the invitation keeps the two in step: the discriminant and the\n // presence of that field are one decision, and splitting them produces bytes\n // that decode into the wrong shape rather than failing to decode.\n const expires = BigInt(input.invitation.invitation.expiration_timestamp ?? 0) !== 0n;\n const joinedAt =\n input.joinedAt ?? BigInt(Math.floor(Date.now() / 1000));\n\n const rootOp = expires\n ? concat(\n new Uint8Array([ROOT_OP.MemberJoinedAt]),\n fromHex(input.member, 'member', 32),\n encodeSignedInvitation(input.invitation),\n u64le(joinedAt),\n credential,\n )\n : concat(\n new Uint8Array([ROOT_OP.MemberJoined]),\n fromHex(input.member, 'member', 32),\n encodeSignedInvitation(input.invitation),\n credential,\n );\n\n const op = concat(new Uint8Array([NAMESPACE_OP_ROOT]), rootOp);\n\n const key = await importSigningKey(input.deviceSecret);\n const signer = await derivePublicKey(input.deviceSecret);\n\n const signable = concat(\n new Uint8Array([SIGNED_NAMESPACE_OP_SCHEMA_VERSION]),\n fromHex(input.namespaceId, 'namespaceId', 32),\n u32le(parents.length),\n ...parents.map((p, i) => fromHex(p, `parentOpHashes[${i}]`, 32)),\n signer,\n u64le(input.nonce),\n op,\n );\n\n const signature = new Uint8Array(\n await crypto.subtle.sign('Ed25519', key, concat(NAMESPACE_SIGN_DOMAIN, signable)),\n );\n\n // The signed struct repeats the signable fields, then the signature, then the\n // admitter endorsement. The domain is a signing prefix only and is never part\n // of the encoded op.\n //\n // The endorsement is written as borsh `None` \u2014 a single `0x00` \u2014 and that is\n // the correct value from here, not a placeholder. It authorises the membership\n // and can only be signed by an account the invitation named as an admitter,\n // which a keyholder is not. The admitter attaches its own as it relays the op\n // through `admitJoin`.\n //\n // It sits outside `signable`, so it is outside this signature and outside the\n // op's id. That is what makes attaching it possible: the admitter changes\n // neither what was signed here nor which op this is.\n const unendorsed = new Uint8Array([0]);\n\n return hex(concat(signable, signature, unendorsed));\n}\n", "/**\n * Sign the statement a device key presents to obtain a session on a node.\n *\n * This is the half of password-free login that has to run where the device key\n * lives, and for the case delegated execution exists for that is a browser tab.\n * Until this module, the only thing that could produce a login statement was\n * `merod account login-statement` \u2014 which is to say, a client had to hold a node\n * to ask a node for a session.\n *\n * **What this gets you, and what it does not.** A statement proves a device key\n * asked for this session. It says nothing about which account that device\n * belongs to: that is the `AccountProof` travelling beside it, which the node\n * checks separately. Signing here and presenting no proof gets a 401 that looks\n * exactly like a bad signature.\n *\n * **The session key is minted by the caller, not here.** The device key signs\n * once, over a short-lived key the caller generates and keeps; that key does the\n * talking for the rest of the session, which is what keeps the device key off\n * the wire. Passing a key you do not hold the private half of produces a session\n * you cannot speak on.\n *\n * **`node` must come from something you pinned.** Not from the challenge\n * response, and not from any field the node itself chose \u2014 an attacker who can\n * answer on the node's behalf would otherwise pick what your device signs about,\n * which is the whole reason the field exists. Read it out of band from the node\n * operator.\n *\n * **The byte contract is pinned in core**, at\n * `crates/account/src/tests/login_wire_fixture.rs`, for the same reason the\n * warrant one is: `AUTH_LOGIN_SIGN_DOMAIN` is `pub(crate)` there and the field\n * order is implicit in a `derive`, so nothing on that side forces anyone to\n * notice this file depends on both. This module's test asserts the same vectors.\n */\n\nimport {\n concat,\n derivePublicKey,\n domainHash,\n fromHex,\n hex,\n importSigningKey,\n u32le,\n u64le,\n} from '../crypto/internal.js';\n\nconst SIGN_DOMAIN = new TextEncoder().encode('calimero.auth.login.v1');\n\n/**\n * The client surface a session is bound to.\n *\n * A session minted for one surface cannot be presented from another, so a single\n * compromised surface does not yield sessions usable everywhere.\n */\nexport type Audience =\n | {\n /**\n * A browser origin, spelled exactly as the browser spells it\n * (`https://host:port`, no trailing slash).\n *\n * Compared byte for byte by the node. Do not normalize it here: two\n * spellings of one origin would mean a verifier that disagrees with the\n * browser about which one a token was for. `location.origin` is already\n * the spelling you want.\n */\n kind: 'webOrigin';\n origin: string;\n }\n | {\n /** A signed native client, named by its code-signing identity. */\n kind: 'codeSigningId';\n id: string;\n }\n | {\n /**\n * A command-line client, which has no origin and no signing identity.\n *\n * Carries no payload deliberately: a string here would be an audience that\n * binds nothing while looking like it binds something.\n */\n kind: 'cli';\n };\n\n/** The borsh enum tag for each variant, in core's declaration order. */\nconst AUDIENCE_TAG: Record<Audience['kind'], number> = {\n webOrigin: 0,\n codeSigningId: 1,\n cli: 2,\n};\n\n/** The variant's payload as UTF-8 \u2014 empty for the payload-free `cli`. */\nfunction audienceBody(audience: Audience): Uint8Array {\n const encoder = new TextEncoder();\n switch (audience.kind) {\n case 'webOrigin':\n return encoder.encode(audience.origin);\n case 'codeSigningId':\n return encoder.encode(audience.id);\n case 'cli':\n return new Uint8Array(0);\n }\n}\n\n/**\n * What the audience contributes to the **signing preimage**: tag then body, with\n * no length between them.\n *\n * `domainHash` length-prefixes every part it is handed, so a length written here\n * would be counted twice and the preimage would verify nowhere. This is the one\n * field spelled differently in the two encodings \u2014 see {@link audienceWire}.\n */\nfunction audienceSigningBytes(audience: Audience): Uint8Array {\n return concat(\n Uint8Array.from([AUDIENCE_TAG[audience.kind]]),\n audienceBody(audience),\n );\n}\n\n/**\n * What the audience contributes to the **wire encoding**: the borsh enum tag,\n * then for the two payload-carrying variants a borsh `String` \u2014 its own `u32`\n * little-endian length, then the UTF-8.\n *\n * `cli` is a bare tag. Writing a `u32` zero after it instead produces four extra\n * bytes that a node reads as the start of the challenge, so the statement fails\n * as a bad signature rather than as a malformed one.\n */\nfunction audienceWire(audience: Audience): Uint8Array {\n const tag = Uint8Array.from([AUDIENCE_TAG[audience.kind]]);\n if (audience.kind === 'cli') {\n return tag;\n }\n const body = audienceBody(audience);\n return concat(tag, u32le(body.length), body);\n}\n\n/** What a device is asking for. */\nexport interface LoginStatementInput {\n /**\n * The node this session is for, hex (32 bytes) \u2014 its identity public key, as\n * you pinned it. See the module note: this must not come from the node.\n */\n node: string;\n /** The client surface the session is bound to. */\n audience: Audience;\n /** The challenge this node issued, hex (32 bytes). Proves freshness. */\n challenge: string;\n /**\n * The public half of the ephemeral key this session will speak with, hex (32\n * bytes). Minted per session by the caller and thrown away with it.\n */\n sessionKey: string;\n /** Unix seconds at signing. */\n issuedAt: number | bigint;\n /** Unix seconds after which the node must refuse it. */\n expiresAt: number | bigint;\n /**\n * The device's ed25519 signing secret, hex (32 bytes).\n *\n * Never sent anywhere. It signs locally and only the signature travels, which\n * is what lets a statement be minted by something holding no node.\n */\n deviceSecret: string;\n}\n\n/**\n * Sign a login statement and return it hex-encoded, ready to present with an\n * account proof.\n *\n * Returns the encoding rather than an object for the reason `signWarrant` does:\n * the signature covers exactly these bytes, and a caller that rebuilt the fields\n * from JSON would have a second spelling able to disagree with what was signed.\n */\nexport async function signLoginStatement(\n input: LoginStatementInput,\n): Promise<string> {\n const node = fromHex(input.node, 'node', 32);\n const challenge = fromHex(input.challenge, 'challenge', 32);\n const sessionKey = fromHex(input.sessionKey, 'sessionKey', 32);\n\n const key = await importSigningKey(input.deviceSecret);\n const deviceKey = await derivePublicKey(input.deviceSecret);\n\n const issuedAt = u64le(input.issuedAt);\n const expiresAt = u64le(input.expiresAt);\n\n const preimage = await domainHash(SIGN_DOMAIN, [\n node,\n audienceSigningBytes(input.audience),\n challenge,\n sessionKey,\n deviceKey,\n issuedAt,\n expiresAt,\n ]);\n\n const signature = new Uint8Array(\n await crypto.subtle.sign({ name: 'Ed25519' }, key, preimage),\n );\n\n return hex(\n concat(\n node,\n audienceWire(input.audience),\n challenge,\n sessionKey,\n deviceKey,\n issuedAt,\n expiresAt,\n signature,\n ),\n );\n}\n", "/**\n * Obtain a session on a node using only a device key \u2014 the handshake around\n * {@link signLoginStatement}.\n *\n * Signing a statement was never the whole job. A client also has to fetch a\n * challenge, mint a session keypair, assemble a request body whose shape it\n * cannot see from here, and read a token out of the reply. Until now every\n * consumer wrote those two HTTP calls by hand, which is how the same three\n * mistakes kept being available to make:\n *\n * 1. **Asking for permissions.** `POST /auth/token` takes an optional\n * `permissions` list. Left unset, the node grants the account-proof\n * provider's own scopes \u2014 `context:intent`, `context:query`,\n * `context:subscribe` \u2014 which are the whole delegated surface and\n * deliberately nothing above it. This module never sends the field.\n * `authenticate()` in this same package hardcodes `['admin']` (#84); a\n * device-key session would be refused for that and should never ask.\n * 2. **Learning the node's key from the node.** {@link LoginConfig.node} is\n * required and has no default, because a value read from the challenge\n * response would let whoever answered choose what the device signs about.\n * That is the whole attack the field exists to stop, so the awkwardness of\n * having to supply it is the point rather than something to smooth away.\n * 3. **Losing the refresh token.** A browser tab outlives a short-lived\n * session, so both tokens are returned. Nothing here refreshes on a timer\n * (#87, #88 track that); this only makes it possible without a second\n * breaking change.\n *\n * **The session key never leaves.** It is minted here, the device key signs\n * over it once, and the secret half is handed back to the caller. That is what\n * keeps a device key off the wire for the life of a session: a leaked token\n * cannot be escalated into use of the key that authorised it.\n */\n\nimport { HTTPError } from '../http-client/web-client.js';\nimport { derivePublicKey, hex } from '../crypto/internal.js';\nimport { signLoginStatement, type Audience } from './login.js';\n\n/** Seconds a statement stays valid unless told otherwise. */\nconst DEFAULT_TTL_SECONDS = 300;\n\n/** A freshly minted ephemeral keypair, hex-encoded. */\nexport interface SessionKeyPair {\n /** The public half \u2014 what the statement is signed over. */\n publicKey: string;\n /** The private half. Held by the caller; never sent anywhere. */\n secret: string;\n}\n\n/**\n * Mint the ephemeral keypair a session speaks with.\n *\n * An ed25519 secret *is* 32 random bytes, so this needs no key generation\n * ceremony \u2014 `getRandomValues` and the same derivation every other signer here\n * uses. Exported because a caller that wants to persist a session across a page\n * reload needs to mint the pair itself and keep the secret.\n */\nexport async function generateSessionKey(): Promise<SessionKeyPair> {\n const secret = hex(crypto.getRandomValues(new Uint8Array(32)));\n return { publicKey: hex(await derivePublicKey(secret)), secret };\n}\n\n/** What a device needs in order to ask for a session. */\nexport interface LoginConfig {\n /** The node's base URL, e.g. `https://node.example`. */\n nodeUrl: string;\n /**\n * The node's device signing key, hex (32 bytes), **as this client pinned it**.\n *\n * Learn it out of band \u2014 from the node's operator, or a certificate you\n * already trust. Never from the node you are about to log in to: see the\n * module note.\n */\n node: string;\n /** The device's ed25519 signing secret, hex (32 bytes). Never sent. */\n deviceSecret: string;\n /**\n * The device's account proof, hex \u2014 the credential certifying that this\n * device belongs to its account.\n *\n * Travels beside the statement and is what settles *which account* is asking.\n * The statement alone proves only that a device key asked, so logging in\n * without this yields a 401 that looks exactly like a bad signature.\n */\n accountProof: string;\n /** The client surface this session is bound to. */\n audience: Audience;\n /**\n * How this client names itself to the node. Defaults to the audience's own\n * spelling, which is the honest answer: a session bound to an origin should\n * say that origin.\n */\n clientName?: string;\n /** Seconds the statement stays valid. Defaults to {@link DEFAULT_TTL_SECONDS}. */\n ttlSeconds?: number;\n /** Injectable for tests and non-browser runtimes. */\n fetch?: typeof fetch;\n /** Per-request timeout. Defaults to 10s. */\n timeoutMs?: number;\n}\n\n/** A session minted without a password. */\nexport interface DelegatedSession {\n /** Bearer token for the delegated surface. */\n accessToken: string;\n /** Presented to renew the access token. See #87/#88 \u2014 nothing here schedules it. */\n refreshToken: string;\n /** The session's public half, as the node knows it. */\n sessionKey: string;\n /** The session's private half. The caller keeps this; it was never sent. */\n sessionSecret: string;\n}\n\n/** The audience's own spelling, used as the default client name. */\nfunction audienceLabel(audience: Audience): string {\n switch (audience.kind) {\n case 'webOrigin':\n return audience.origin;\n case 'codeSigningId':\n return audience.id;\n case 'cli':\n return 'cli';\n }\n}\n\n/**\n * Log in with a device key and return the session.\n *\n * The challenge is fetched immediately before signing on purpose: it is\n * single-use and short-lived, so a statement minted against a stale one is\n * refused before any signature work is done.\n *\n * @throws {HTTPError} if the node refuses the challenge or the token request.\n */\nexport async function login(config: LoginConfig): Promise<DelegatedSession> {\n const baseUrl = config.nodeUrl.replace(/\\/+$/, '');\n const doFetch = config.fetch ?? globalThis.fetch;\n const timeoutMs = config.timeoutMs ?? 10_000;\n\n const request = async (path: string, init?: RequestInit): Promise<unknown> => {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n let response: Response;\n try {\n response = await doFetch(`${baseUrl}${path}`, {\n ...init,\n signal: controller.signal,\n });\n } catch (err) {\n throw new HTTPError(\n 0,\n err instanceof Error ? err.name : 'NetworkError',\n `${baseUrl}${path}`,\n new Headers(),\n err instanceof Error ? err.message : String(err),\n );\n } finally {\n clearTimeout(timer);\n }\n const text = await response.text().catch(() => '');\n if (!response.ok) {\n throw new HTTPError(\n response.status,\n response.statusText,\n `${baseUrl}${path}`,\n response.headers,\n text,\n );\n }\n return text ? (JSON.parse(text) as unknown) : {};\n };\n\n const challengeBody = (await request('/auth/challenge', {\n method: 'GET',\n headers: { Accept: 'application/json' },\n })) as { challenge?: string; data?: { challenge?: string } };\n // The node has spelled this both bare and wrapped in `data`; accept either\n // rather than couple a login to one envelope revision.\n const challenge = challengeBody.challenge ?? challengeBody.data?.challenge;\n if (!challenge) {\n throw new Error('the node issued no challenge');\n }\n\n const session = await generateSessionKey();\n const issuedAt = Math.floor(Date.now() / 1000);\n const statement = await signLoginStatement({\n node: config.node,\n audience: config.audience,\n challenge,\n sessionKey: session.publicKey,\n issuedAt,\n expiresAt: issuedAt + (config.ttlSeconds ?? DEFAULT_TTL_SECONDS),\n deviceSecret: config.deviceSecret,\n });\n\n // `timestamp` is required and the request refuses unknown fields, so a body\n // missing it is rejected during deserialization \u2014 before the provider runs,\n // and with an error that names the request shape rather than the login.\n //\n // `permissions` is omitted, not empty: see the module note.\n const tokenBody = (await request('/auth/token', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Accept: 'application/json' },\n body: JSON.stringify({\n auth_method: 'account_proof',\n public_key: session.publicKey,\n client_name: config.clientName ?? audienceLabel(config.audience),\n timestamp: issuedAt,\n provider_data: {\n challenge,\n login_statement: statement,\n account_proof: config.accountProof,\n },\n }),\n })) as {\n access_token?: string;\n refresh_token?: string;\n data?: { access_token?: string; refresh_token?: string };\n };\n\n const accessToken = tokenBody.access_token ?? tokenBody.data?.access_token;\n const refreshToken = tokenBody.refresh_token ?? tokenBody.data?.refresh_token;\n if (!accessToken) {\n throw new Error('the node minted no session');\n }\n\n return {\n accessToken,\n refreshToken: refreshToken ?? '',\n sessionKey: session.publicKey,\n sessionSecret: session.secret,\n };\n}\n", "/**\n * Local-node discovery.\n *\n * Calimero dev/desktop nodes expose a public, unauthenticated health endpoint\n * at `GET {nodeUrl}/admin-api/health` that returns `{ \"data\": { \"status\":\n * \"alive\" } }`. The most common local layouts run on a small, well-known set of\n * ports, so instead of forcing the user to type a URL we probe those ports and\n * offer whatever is actually running.\n */\n\n/**\n * Default ports probed when discovering local nodes. Covers the two-node dev\n * stacks used across the Calimero apps (RPC + alt ports for node1 / node2).\n */\nexport const DEFAULT_LOCAL_NODE_PORTS = [2428, 2429, 2528, 2529] as const;\n\n/** Host used to build local candidate URLs. */\nconst LOCAL_HOST = 'localhost';\n\n/** Per-probe timeout. A reachable local node answers almost instantly; a\n * closed port rejects fast, but a firewalled/black-holed one can hang, so we\n * cap each probe. */\nconst DEFAULT_PROBE_TIMEOUT_MS = 2000;\n\nexport interface DiscoverLocalNodesOptions {\n /** Ports to probe. Defaults to {@link DEFAULT_LOCAL_NODE_PORTS}. */\n ports?: readonly number[];\n /** Per-probe timeout in ms. Defaults to 2000. */\n timeoutMs?: number;\n /** Abort the whole discovery (e.g. when the modal closes). */\n signal?: AbortSignal;\n}\n\n/** Build the canonical base URL for a local node on the given port. */\nexport function localNodeUrl(port: number): string {\n return `http://${LOCAL_HOST}:${port}`;\n}\n\n/**\n * Resolve an API `path` against a node `baseUrl`. Ensures the base ends with a\n * slash first, so a base that carries a path prefix (e.g. a `NODE_PATH_PREFIX`\n * deployment like `http://host/node1`) keeps that segment instead of having it\n * stripped by relative URL resolution.\n */\nexport function nodeEndpoint(baseUrl: string, path: string): string {\n const base = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;\n return new URL(path, base).toString();\n}\n\n/**\n * Probe a single node's health endpoint. Resolves `true` only when the node\n * answers with a 2xx and (when the body is JSON) reports a non-dead status.\n * Any network error, timeout, or non-ok response resolves `false` \u2014 never\n * throws (except on an external abort, which is swallowed as `false`).\n */\nexport async function probeNodeHealth(\n baseUrl: string,\n options: { timeoutMs?: number; signal?: AbortSignal } = {},\n): Promise<boolean> {\n const { timeoutMs = DEFAULT_PROBE_TIMEOUT_MS, signal } = options;\n\n // Already cancelled before we start \u2014 don't fetch, and don't register a\n // timer or listener that would then need cleanup.\n if (signal?.aborted) return false;\n\n // Per-probe timeout, also linked to the caller's abort signal so closing the\n // modal cancels every in-flight probe. Bind the signal to a local so the\n // cleanup below stays symmetric with registration without re-narrowing.\n const controller = new AbortController();\n const onAbort = () => controller.abort();\n const abortSignal = signal;\n if (abortSignal) abortSignal.addEventListener('abort', onAbort, { once: true });\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const url = nodeEndpoint(baseUrl, 'admin-api/health');\n const res = await fetch(url, {\n method: 'GET',\n signal: controller.signal,\n });\n\n if (!res.ok) return false;\n\n // Health body is `{ data: { status: \"alive\" } }`, but tolerate plain\n // `{ status }` and non-JSON 2xx responses. A *missing* status is treated as\n // alive (the 2xx is enough); a status that is present but not \"alive\"\n // (including an explicit `null`) is treated as unhealthy.\n try {\n const body = (await res.json()) as\n | { data?: { status?: string | null }; status?: string | null }\n | undefined;\n const status =\n body?.data?.status !== undefined ? body?.data?.status : body?.status;\n if (status === undefined) return true;\n return typeof status === 'string' && status.toLowerCase() === 'alive';\n } catch {\n return true;\n }\n } catch {\n return false;\n } finally {\n clearTimeout(timer);\n if (abortSignal) abortSignal.removeEventListener('abort', onAbort);\n }\n}\n\n/**\n * Probe the configured local ports in parallel and return the base URLs that\n * responded as healthy, in the same order as the `ports` argument (which\n * defaults to ascending). Returns an empty array when nothing local is running.\n */\nexport async function discoverLocalNodes(\n options: DiscoverLocalNodesOptions = {},\n): Promise<string[]> {\n const { ports = DEFAULT_LOCAL_NODE_PORTS, timeoutMs, signal } = options;\n\n const results = await Promise.all(\n ports.map(async (port) => {\n const url = localNodeUrl(port);\n const ok = await probeNodeHealth(url, { timeoutMs, signal });\n return ok ? url : null;\n }),\n );\n\n return results.filter((url): url is string => url !== null);\n}\n"],
5
- "mappings": "AACO,SAASA,GACdC,EACyB,CACzB,IAAMC,EAAOD,EAAQ,OAAO,OAAO,EACnC,GAAIC,EAAK,SAAW,EAAG,OAGvB,IAAMC,EAAiB,YAGvB,GAAI,OAAOA,EAAe,KAAQ,WAChC,GAAI,CACF,OAAOA,EAAe,IAAID,CAAI,CAChC,MAAQ,CAER,CAGF,IAAME,EAAa,IAAI,gBACjBC,EAAWC,GAAe,CAC9BF,EAAW,MAAOE,EAAI,OAAuB,MAAM,EACnD,QAAWC,KAAKL,EAAMK,EAAE,oBAAoB,QAASF,CAAO,CAC9D,EAEA,QAAWE,KAAKL,EAAM,CACpB,GAAIK,EAAE,QAAS,OAAO,YAAY,MAAMA,EAAE,MAAM,EAChDA,EAAE,iBAAiB,QAASF,EAAS,CAAE,KAAM,EAAK,CAAC,CACrD,CAEA,OAAOD,EAAW,MACpB,CAGO,SAASI,GAAoBC,EAAgC,CAElE,GAAI,OAAO,YAAY,SAAY,WACjC,OAAO,YAAY,QAAQA,CAAS,EAItC,IAAML,EAAa,IAAI,gBACvB,kBAAW,IAAM,CACfA,EAAW,MAAM,IAAI,aAAa,UAAW,cAAc,CAAC,CAC9D,EAAGK,CAAS,EAELL,EAAW,MACpB,CC9BA,IAAMM,GAAuB,IAAI,IAAI,CAAC,cAAe,eAAe,CAAC,EAQrE,SAASC,GAAoBC,EAAuC,CAClE,GAAKA,EACL,GAAI,CACF,IAAMC,EAAO,KAAK,MAAMD,CAAQ,EAC1BE,EAAQD,GAAM,MACpB,GAAI,OAAOC,GAAU,UAAYA,EAAM,KAAK,IAAM,GAAI,OAAOA,EAC7D,IAAMC,EAAUD,GAAwC,QACxD,GAAI,OAAOC,GAAW,UAAYA,EAAO,KAAK,IAAM,GAAI,OAAOA,EAK/D,IAAMC,EAASH,GAAM,OACrB,GAAI,OAAOG,GAAW,UAAYA,EAAO,KAAK,IAAM,GAAI,OAAOA,CACjE,MAAQ,CAER,CAEF,CAGO,IAAMC,EAAN,cAAwB,KAAM,CAGnC,YACSC,EACAC,EACAC,EACAC,EACAT,EACP,CACA,IAAMU,EAAcX,GAAoBC,CAAQ,EAChD,MACEU,EACI,QAAQJ,CAAM,IAAIC,CAAU,KAAKG,CAAW,GAC5C,QAAQJ,CAAM,IAAIC,CAAU,EAClC,EAXO,YAAAD,EACA,gBAAAC,EACA,SAAAC,EACA,aAAAC,EACA,cAAAT,EAPT,UAAO,WAeP,CAEA,QAME,CACA,MAAO,CACL,OAAQ,KAAK,OACb,WAAY,KAAK,WACjB,IAAK,KAAK,IACV,QAASW,GAAgB,KAAK,OAAO,EACrC,SAAU,KAAK,QACjB,CACF,CACF,EASaC,EAAN,cAA+BP,CAAU,CAG9C,YAESQ,EACPP,EACAC,EACAC,EACAC,EACAT,EACA,CACA,MAAMM,EAAQC,EAAYC,EAAKC,EAAST,CAAQ,EAPzC,YAAAa,EAJT,UAAO,mBAYL,KAAK,QAAU,2BAA2BA,CAAM,WAAWP,CAAM,IAAIC,CAAU,EACjF,CACF,EAGA,SAASI,GAAgBF,EAA0C,CACjE,IAAMK,EAAiC,CAAC,EACxC,OAAAL,EAAQ,QAAQ,CAACM,EAAOC,IAAQ,CAC9BF,EAAOE,CAAG,EAAID,CAChB,CAAC,EACMD,CACT,CAWA,SAASG,GAAaT,EAAqB,CACzC,GAAI,CACF,OAAO,IAAI,IAAIA,CAAG,EAAE,QACtB,MAAQ,CACN,IAAMU,EAAQV,EAAI,QAAQ,GAAG,EAC7B,OAAOU,IAAU,GAAKV,EAAMA,EAAI,MAAM,EAAGU,CAAK,CAChD,CACF,CAEO,IAAMC,EAAN,KAA0C,CAM/C,YAAoBC,EAAsB,CAAtB,eAAAA,EAJpB,KAAQ,oBAA8C,KAEtD,KAAQ,sBAA8C,IAEX,CAE3C,MAAM,IAAOC,EAAcC,EAAmC,CAC5D,OAAO,KAAK,QAAWD,EAAM,CAAE,GAAGC,EAAM,OAAQ,KAAM,CAAC,CACzD,CAEA,MAAM,KACJD,EACApB,EACAqB,EACY,CACZ,OAAO,KAAK,QAAWD,EAAM,CAC3B,GAAGC,EACH,OAAQ,OACR,KAAMrB,EAAO,KAAK,UAAUA,CAAI,EAAI,OACpC,QAAS,CACP,eAAgB,mBAChB,GAAGqB,GAAM,OACX,CACF,CAAC,CACH,CAEA,MAAM,IACJD,EACApB,EACAqB,EACY,CACZ,OAAO,KAAK,QAAWD,EAAM,CAC3B,GAAGC,EACH,OAAQ,MACR,KAAMrB,EAAO,KAAK,UAAUA,CAAI,EAAI,OACpC,QAAS,CACP,eAAgB,mBAChB,GAAGqB,GAAM,OACX,CACF,CAAC,CACH,CAEA,MAAM,OAAUD,EAAcC,EAAmC,CAC/D,OAAO,KAAK,QAAWD,EAAM,CAAE,GAAGC,EAAM,OAAQ,QAAS,CAAC,CAC5D,CAEA,MAAM,MACJD,EACApB,EACAqB,EACY,CACZ,OAAO,KAAK,QAAWD,EAAM,CAC3B,GAAGC,EACH,OAAQ,QACR,KAAMrB,EAAO,KAAK,UAAUA,CAAI,EAAI,OACpC,QAAS,CACP,eAAgB,mBAChB,GAAGqB,GAAM,OACX,CACF,CAAC,CACH,CAEA,MAAM,KACJD,EACAC,EAC8D,CAC9D,IAAMC,EAAW,MAAM,KAAK,YAAsBF,EAAM,CACtD,GAAGC,EACH,OAAQ,OACR,MAAO,UACT,CAAC,EACD,MAAO,CACL,QAASX,GAAgBY,EAAS,OAAO,EACzC,OAAQA,EAAS,MACnB,CACF,CAEA,MAAM,QAAWF,EAAcC,EAAmC,CAChE,OAAO,KAAK,YAAeD,EAAMC,CAAI,CACvC,CAEA,MAAc,YACZD,EACAC,EACAE,EAAa,EACbC,EACY,CAGZ,IAAMjB,EAAM,KAAK,SAASa,CAAI,EAIxBK,EAAYD,GAAoB,KAAK,IAAI,EAGzCE,EAAS,KAAK,kBAAkBL,CAAI,EACpCb,EAAU,MAAM,KAAK,aAAaa,GAAM,OAAO,EACjDM,EACAnB,aAAmB,SACrBmB,EAAa,CAAC,EACdnB,EAAQ,QAAQ,CAACM,EAAOC,IAAQ,CAC9BY,EAAWZ,CAAG,EAAID,CACpB,CAAC,GAEDa,EAAanB,EAGf,IAAMoB,EAA2B,CAC/B,OAAQP,GAAM,QAAU,MACxB,QAASM,CACX,EAIME,EAAeR,GAAM,gBAAgB,gBACtB,OAAOA,GAAM,MAAS,UAAYA,GAAM,OAAS,MAAQ,cAAeA,EAAK,MAAQ,EAAEA,EAAK,gBAAgB,OAE7HA,GAAM,OAAS,QAAa,CAACQ,GAEtBR,GAAM,OAAS,QAAaQ,GAAgBN,IAAe,KAEpEK,EAAY,KAAOP,EAAK,MAO1B,IAAIS,EACJ,GAAIP,EAAa,GAAKC,IAAqB,OAAW,CACpD,IAAMO,EAAYV,GAAM,WAAa,KAAK,UAAU,UACpD,GAAIU,EAAW,CAEb,IAAMC,EAAU,KAAK,IAAI,EAAIP,EACvBQ,EAAY,KAAK,IAAI,EAAGF,EAAYC,CAAO,EAEjDF,EAAc,KAAK,kBAAkB,CAAE,GAAGT,EAAM,UAAWY,CAAU,CAAC,CACxE,MAEEH,EAAc,KAAK,kBAAkBT,CAAI,CAE7C,MACES,EAAcJ,EA2ChB,GAxCII,IACFF,EAAY,OAASE,GAGnB,KAAK,UAAU,cAAgB,SACjCF,EAAY,YAAc,KAAK,UAAU,aAGvCP,GAAM,OAAS,SACjBO,EAAY,KAAOP,EAAK,MAEtBA,GAAM,QAAU,SAClBO,EAAY,MAAQP,EAAK,OAEvBA,GAAM,WAAa,SACrBO,EAAY,SAAWP,EAAK,UAE1BA,GAAM,WAAa,SACrBO,EAAY,SAAWP,EAAK,UAE1BA,GAAM,iBAAmB,SAC3BO,EAAY,eAAiBP,EAAK,gBAEhCA,GAAM,YAAc,SACtBO,EAAY,UAAYP,EAAK,WAE3BA,GAAM,YAAc,SACtBO,EAAY,UAAYP,EAAK,WAa3B,KAAK,UAAU,SAAU,CAC3B,IAAMa,EAAQ,MAAM,KAAK,UAAU,SAAS,CAC1C,OAAQN,EAAY,QAAU,MAC9B,KAAMZ,GAAaT,CAAG,EACtB,KAAMqB,EAAY,IACpB,CAAC,EACGM,IACFP,EAAW,kBAAkB,EAAIO,EAErC,CAEA,GAAI,CACF,IAAMZ,EAAW,MAAM,KAAK,UAAU,MAAMf,EAAKqB,CAAW,EAE5D,GAAI,CAACN,EAAS,GAAI,CAChB,IAAMvB,EAAW,MAAM,KAAK,YAAYuB,CAAQ,EAC1Ca,EAAY,IAAI/B,EACpBkB,EAAS,OACTA,EAAS,WACTf,EACAe,EAAS,QACTvB,CACF,EAMMqC,EAAYd,EAAS,QAAQ,IAAI,cAAc,EACrD,IACGA,EAAS,SAAW,KAAOA,EAAS,SAAW,MAChDc,GACAvC,GAAqB,IAAIuC,CAAS,EAClC,CAKA,GAHA,KAAK,oBAAsB,KAC3B,KAAK,sBAAwB,KAEzB,KAAK,UAAU,cACjB,GAAI,CACF,MAAM,KAAK,UAAU,cAAc,CACrC,MAAQ,CAER,CAGF,MAAM,IAAIzB,EACRyB,EACAd,EAAS,OACTA,EAAS,WACTf,EACAe,EAAS,QACTvB,CACF,CACF,CAGA,IAAMsC,EAAchB,GAAM,QAAQ,UAAY,GAC9C,GACEC,EAAS,SAAW,KACpB,KAAK,UAAU,cACfc,IAAc,iBACdb,EAAa,GACb,CAACM,GACD,CAACQ,EAED,GAAI,CAEF,IAAIC,EAAiB,KAAK,oBACrBA,IACHA,EAAiB,KAAK,UAAU,aAAa,EAC7C,KAAK,oBAAsBA,GAI7B,IAAMC,EAAW,MAAMD,EAGvB,GAAI,CAACC,GAAYA,EAAS,KAAK,IAAM,GAEnC,WAAK,oBAAsB,KAC3B,KAAK,sBAAwB,KACvB,IAAI,MAAM,oCAAoC,EAKtD,GAAI,CAAC,KAAK,UAAU,eAElB,WAAK,oBAAsB,KAC3B,KAAK,sBAAwB,KACvB,IAAI,MACR,wJAEF,EAKF,IAAIC,EAAwB,KAAK,sBAC5BA,IACHA,EAAwB,KAAK,UAAU,eAAeD,CAAQ,EAC9D,KAAK,sBAAwBC,GAM/B,MAAMA,EAGN,KAAK,oBAAsB,KAC3B,KAAK,sBAAwB,KAK7B,IAAMT,EAAYV,GAAM,WAAa,KAAK,UAAU,UACpD,GAAIU,GAAaP,IAAqB,OAAW,CAC/C,IAAMQ,GAAU,KAAK,IAAI,EAAIP,EAE7B,GADkBM,EAAYC,IACb,EAGf,MAAMG,CAEV,CAIA,OAAO,KAAK,YAAef,EAAMC,EAAME,EAAa,EAAGE,CAAS,CAClE,OAASgB,EAAc,CAKrB,MAHA,KAAK,oBAAsB,KAC3B,KAAK,sBAAwB,KAEzBA,aAAwB,OAASA,EAAa,QAAQ,SAAS,gBAAgB,EAC3EA,EAKFN,CACR,CAGF,MAAMA,CACR,CAEA,OAAO,KAAK,cAAiBb,EAAUD,GAAM,KAAK,CACpD,OAASpB,EAAO,CAKd,MAJIA,aAAiBG,GAIjBH,aAAiB,OAASA,EAAM,QAAQ,SAAS,gBAAgB,EAC7DA,EAEF,IAAIG,EACR,EACA,gBACAG,EACA,IAAI,QACJN,aAAiB,MAAQA,EAAM,QAAU,eAC3C,CACF,CACF,CAEQ,SAASmB,EAAsB,CAErC,GAAIA,EAAK,WAAW,SAAS,GAAKA,EAAK,WAAW,UAAU,EAC1D,OAAOA,EAIT,IAAMsB,EAAU,KAAK,UAAU,QAC/B,OAAItB,EAAK,WAAW,GAAG,EAGd,GADMsB,EAAQ,SAAS,GAAG,EAAIA,EAAQ,MAAM,EAAG,EAAE,EAAIA,CAC9C,GAAGtB,CAAI,GAId,GADMsB,EAAQ,SAAS,GAAG,EAAIA,EAAU,GAAGA,CAAO,GAC3C,GAAGtB,CAAI,EAEzB,CAEQ,kBAAkBC,EAAgD,CACxE,IAAMsB,EAAyB,CAAC,EAE5B,KAAK,UAAU,oBACjBA,EAAQ,KAAK,KAAK,UAAU,kBAAkB,EAG5CtB,GAAM,QACRsB,EAAQ,KAAKtB,EAAK,MAAM,EAG1B,IAAMU,EAAYV,GAAM,WAAa,KAAK,UAAU,UACpD,OAAIU,GACFY,EAAQ,KAAKC,GAAoBb,CAAS,CAAC,EAGtCY,EAAQ,OAAS,EAAIE,GAAeF,CAAO,EAAI,MACxD,CAEA,MAAc,aACZG,EACiC,CACjC,IAAMtC,EAAkC,CACtC,GAAG,KAAK,UAAU,cACpB,EAGA,GAAI,KAAK,UAAU,aACjB,GAAI,CACF,IAAMuC,EAAQ,MAAM,KAAK,UAAU,aAAa,EAC5CA,GAASA,EAAM,KAAK,IAAM,KAC5BvC,EAAQ,cAAgB,UAAUuC,CAAK,GAE3C,MAAgB,CAEhB,CAIF,OAAID,IACEA,aAAuB,QACzBA,EAAY,QAAQ,CAAChC,EAAOC,IAAQ,CAClCP,EAAQO,CAAG,EAAID,CACjB,CAAC,EACQ,MAAM,QAAQgC,CAAW,EAClCA,EAAY,QAAQ,CAAC,CAAC/B,EAAKD,CAAK,IAAM,CACpCN,EAAQO,CAAG,EAAID,CACjB,CAAC,EAED,OAAO,OAAON,EAASsC,CAAW,GAI/BtC,CACT,CAEA,MAAc,cACZc,EACA0B,EACY,CACZ,OAAQA,EAAO,CACb,IAAK,OACH,OAAQ,MAAM1B,EAAS,KAAK,EAC9B,IAAK,OACH,OAAQ,MAAMA,EAAS,KAAK,EAC9B,IAAK,cACH,OAAQ,MAAMA,EAAS,YAAY,EACrC,IAAK,WACH,OAAOA,EAET,QAAS,CAGP,IAAM2B,EAAO,MAAM3B,EAAS,KAAK,EACjC,GAAI,CAAC2B,EAAM,OAAO,KAClB,GAAI,CACF,OAAO,KAAK,MAAMA,CAAI,CACxB,MAAQ,CAGN,MAAM,IAAI,MACR,oCAAoC3B,EAAS,MAAM,4BAA4B2B,EAAK,MAAM,EAAG,GAAG,CAAC,EACnG,CACF,CACF,CACF,CACF,CAEA,MAAc,YAAY3B,EAAqC,CAC7D,GAAI,CACF,IAAM2B,EAAO,MAAM3B,EAAS,KAAK,EACjC,OAAO2B,EAAK,OAAS,MAAQA,EAAK,MAAM,EAAG,KAAK,EAAI,MAAQA,CAC9D,MAAQ,CACN,MAAO,EACT,CACF,CACF,ECvlBO,SAASC,GAAiBC,EAAkC,CACjE,OAAO,IAAIC,EAAcD,CAAS,CACpC,CAGO,SAASE,EAAwBC,EAsBzB,CACb,IAAMH,EAAuB,CAG3B,MAAO,CAACI,EAAwBC,IAC9BF,EAAQ,MAAQA,EAAQ,MAAMC,EAAKC,CAAI,EAAI,WAAW,MAAMD,EAAKC,CAAI,EACvE,QAASF,EAAQ,QACjB,aAAcA,EAAQ,aACtB,SAAUA,EAAQ,SAClB,eAAgBA,EAAQ,eACxB,aAAcA,EAAQ,aACtB,cAAeA,EAAQ,cACvB,eAAgBA,EAAQ,eACxB,UAAWA,EAAQ,UACnB,YAAaA,EAAQ,YACrB,mBAAoBA,EAAQ,kBAC9B,EAEA,OAAOJ,GAAiBC,CAAS,CACnC,CAGO,SAASM,GAAqBH,EAsBtB,CAEb,IAAMI,EAAYJ,EAAQ,OAAS,WAAW,MAE9C,GAAI,CAACI,EACH,MAAM,IAAI,MACR,kJAEF,EAQF,IAAMP,EAAuB,CAI3B,MANqBO,IAAc,WAAW,MAO1C,CAACH,EAAwBC,IAAuB,WAAW,MAAMD,EAAKC,CAAI,EAC1E,CAACD,EAAwBC,IAAuBE,EAAUH,EAAKC,CAAI,EACvE,QAASF,EAAQ,QACjB,aAAcA,EAAQ,aACtB,SAAUA,EAAQ,SAClB,eAAgBA,EAAQ,eACxB,aAAcA,EAAQ,aACtB,cAAeA,EAAQ,cACvB,eAAgBA,EAAQ,eACxB,UAAWA,EAAQ,UACnB,YAAaA,EAAQ,YACrB,mBAAoBA,EAAQ,kBAC9B,EAEA,OAAOJ,GAAiBC,CAAS,CACnC,CAGO,SAASQ,GAA0BL,EAsB3B,CAEb,OAAI,OAAO,OAAW,IAEbD,EAAwBC,CAAO,EAG/BG,GAAqBH,CAAO,CAEvC,CC9HA,SAASM,GAAsBC,EAAcC,EAA0B,CAErE,GAAIA,GAAW,EAAG,MAAO,GAMzB,IAAMC,EADgBF,GACM,KAC5B,GAAIE,IAAS,eAAgB,MAAO,GACpC,GAAIA,IAAS,aAAc,MAAO,GAGlC,IAAMC,EAAkBH,EACxB,GACE,WAAYG,GACZ,OAAOA,EAAgB,QAAW,SAClC,CACA,IAAMC,EAASD,EAAgB,OAC/B,OAAOC,GAAU,KAAOA,IAAW,GACrC,CAEA,OAAIF,IAAS,WAGf,CAGA,SAASG,GAAeJ,EAAyB,CAE/C,IAAMK,EAAQ,IAAc,KAAK,IAAI,EAAGL,EAAU,CAAC,EAG7CM,GAAU,KAAK,OAAO,EAAI,IAAO,GAAMD,EAC7C,OAAO,KAAK,IAAI,EAAGA,EAAQC,CAAM,CACnC,CAGA,eAAsBC,GACpBC,EACAC,EAAwB,CAAC,EACb,CACZ,GAAM,CAAE,SAAAC,EAAW,CAAE,EAAID,EAErBE,EAEJ,QAASX,EAAU,EAAGA,GAAWU,EAAUV,IACzC,GAAI,CACF,OAAO,MAAMQ,EAAGR,CAAO,CACzB,OAASD,EAAO,CAId,GAHAY,EAAYZ,EAGR,CAACD,GAAsBa,EAAWD,EAAWV,CAAO,EACtD,MAAMW,EAIR,IAAIC,EAAUR,GAAeJ,CAAO,EAK9Ba,EAFmBF,EACK,SACL,MAAM,aAAa,EAC5C,GAAIE,EAAY,CAEd,IAAMC,EAAU,SAASD,EAAY,EAAE,EACvC,GAAI,CAAC,MAAMC,CAAO,EAChBF,EAAU,KAAK,IAAIA,EAASE,EAAU,GAAI,MACrC,CAEL,IAAMC,EAAO,IAAI,KAAKF,CAAU,EAChC,GAAI,CAAC,MAAME,EAAK,QAAQ,CAAC,EAAG,CAC1B,IAAMC,EAAW,KAAK,IAAI,EAAGD,EAAK,QAAQ,EAAI,KAAK,IAAI,CAAC,EAExDH,EAAU,KAAK,IAAIA,EAAS,KAAK,IAAII,EAAU,GAAK,CAAC,CACvD,CACF,CACF,CAGA,MAAM,IAAI,QAASC,GAAY,WAAWA,EAASL,CAAO,CAAC,CAC7D,CAGF,MAAMD,GAAa,IAAI,MAAM,4BAA4B,CAC3D,CAGO,SAASO,GACdC,EACAC,EAA6B,CAAC,EAC9B,CACA,MAAO,UAAUC,IACRd,GAAU,IAAMY,EAAO,GAAGE,CAAI,EAAGD,CAAY,CAExD,CCpFO,IAAME,EAAN,KAAoB,CACzB,YAAoBC,EAAwB,CAAxB,gBAAAA,CAAyB,CAG7C,MAAM,WAAqC,CACzC,IAAMC,EACJ,MAAM,KAAK,WAAW,IAAiC,cAAc,EACvE,GAAI,CAACA,EAAS,KACZ,MAAM,IAAI,MAAM,8BAA8B,EAEhD,OAAOA,EAAS,IAClB,CAEA,MAAM,aAAyC,CAC7C,IAAMA,EACJ,MAAM,KAAK,WAAW,IACpB,iBACF,EACF,GAAI,CAACA,EAAS,KACZ,MAAM,IAAI,MAAM,gCAAgC,EAElD,OAAOA,EAAS,IAClB,CAEA,MAAM,cAA2C,CAC/C,IAAMA,EACJ,MAAM,KAAK,WAAW,IACpB,iBACF,EACF,GAAI,CAACA,EAAS,KACZ,MAAM,IAAI,MAAM,iCAAiC,EAEnD,OAAOA,EAAS,IAClB,CAGA,MAAM,cAAgC,CACpC,OAAO,KAAK,WAAW,IAAY,cAAe,CAAE,MAAO,MAAO,CAAC,CACrE,CAEA,MAAM,eAAeC,EAA+C,CAClE,OAAO,KAAK,WAAW,KAAoB,cAAeA,CAAO,CACnE,CAEA,MAAM,aAAaA,EAAsD,CACvE,OAAO,KAAK,WAAW,KAAoB,gBAAiBA,CAAO,CACrE,CAEA,MAAM,mBAAmBA,EAAmD,CAC1E,OAAO,KAAK,WAAW,KAAoB,mBAAoBA,CAAO,CACxE,CAEA,MAAM,cAAcC,EAIjB,CACD,GAAI,CACF,IAAMF,EAAW,MAAM,KAAK,iBAAiBE,CAAK,EAClD,MAAO,CACL,MAAOF,EAAS,SAAW,IAC3B,QAASA,EAAS,QAClB,OAAQA,EAAS,MACnB,CACF,MAAgB,CACd,MAAO,CACL,MAAO,GACP,QAAS,CAAC,EACV,OAAQ,GACV,CACF,CACF,CAEA,MAAM,iBACJE,EAC8D,CAC9D,IAAMF,EAAW,MAAM,KAAK,WAAW,KAAK,iBAAkB,CAC5D,QAAS,CAAE,cAAe,UAAUE,CAAK,EAAG,CAC9C,CAAC,EACD,MAAO,CACL,OAAQF,EAAS,OACjB,QAASA,EAAS,OACpB,CACF,CAKA,MAAM,aACJC,EAC8B,CAC9B,IAAMD,EAAW,MAAM,KAAK,WAAW,KACrC,gBACAC,CACF,EACA,GAAI,CAACD,EAAS,KACZ,MAAM,IAAI,MAAM,qCAAqC,EAEvD,OAAOA,EAAS,IAClB,CAGA,MAAM,cAAmC,CACvC,IAAMA,EACJ,MAAM,KAAK,WAAW,IAA4B,aAAa,EACjE,GAAI,CAACA,EAAS,KACZ,MAAM,IAAI,MAAM,iCAAiC,EAEnD,OAAOA,EAAS,IAClB,CAEA,MAAM,cAAcC,EAAuD,CACzE,IAAMD,EAAW,MAAM,KAAK,WAAW,KACrC,cACAC,CACF,EACA,GAAI,CAACD,EAAS,KACZ,MAAM,IAAI,MAAM,uCAAuC,EAEzD,OAAOA,EAAS,IAClB,CAEA,MAAM,cAAcG,EAA2C,CAC7D,OAAO,KAAK,WAAW,OAA0B,eAAeA,CAAK,EAAE,CACzE,CAGA,MAAM,gBAAuC,CAC3C,IAAMH,EAAW,MAAM,KAAK,WAAW,IACrC,qBACF,EACA,GAAI,CAACA,EAAS,KACZ,MAAM,IAAI,MAAM,mCAAmC,EAErD,OAAOA,EAAS,IAClB,CAEA,MAAM,kBACJC,EACwB,CACxB,OAAO,KAAK,WAAW,KAAoB,oBAAqBA,CAAO,CACzE,CAEA,MAAM,gBACJE,EACAC,EAC+B,CAC/B,OAAO,KAAK,WAAW,OACrB,eAAeD,CAAK,YAAYC,CAAQ,EAC1C,CACF,CAGA,MAAM,kBAAkBD,EAA4C,CAClE,OAAO,KAAK,WAAW,IACrB,eAAeA,CAAK,cACtB,CACF,CAEA,MAAM,qBACJA,EACAE,EAC6B,CAE7B,OAAO,KAAK,WAAW,IACrB,eAAeF,CAAK,eACpB,CAAE,IAAKE,EAAQ,IAAK,OAAQA,EAAQ,MAAO,CAC7C,CACF,CACF,ECnMA,IAAMC,EAAN,KAA2C,CACzC,MAAM,KAAqB,CACzB,MAAM,IAAI,MACR,6FACF,CACF,CACA,MAAM,MAAsB,CAC1B,MAAM,IAAI,MACR,6FACF,CACF,CACA,MAAM,KAAqB,CACzB,MAAM,IAAI,MACR,6FACF,CACF,CACA,MAAM,QAAwB,CAC5B,MAAM,IAAI,MACR,6FACF,CACF,CACA,MAAM,OAAuB,CAC3B,MAAM,IAAI,MACR,6FACF,CACF,CACA,MAAM,MAAqE,CACzE,MAAM,IAAI,MACR,6FACF,CACF,CACA,MAAM,SAAyB,CAC7B,MAAM,IAAI,MACR,6FACF,CACF,CACF,EAGO,SAASC,GACdC,EACe,CACf,IAAMC,EAAa,IAAIH,EACvB,OAAO,IAAII,EAAcD,CAAU,CACrC,CAEO,SAASE,GACdH,EACe,CACf,IAAMC,EAAa,IAAIH,EACvB,OAAO,IAAII,EAAcD,CAAU,CACrC,CAEO,SAASG,GACdJ,EACe,CACf,IAAMC,EAAa,IAAIH,EACvB,OAAO,IAAII,EAAcD,CAAU,CACrC,CAEO,SAASI,GACdJ,EACAD,EACe,CACf,OAAO,IAAIE,EAAcD,CAAU,CACrC,CCxDO,IAAMK,EAAe,CAC1B,mBAAoB,EACpB,mBAAoB,EACpB,wBAAyB,EACzB,eAAgB,EAChB,mBAAoB,GACpB,oBAAqB,GACrB,oBAAqB,GACrB,sBAAuB,IACvB,oBAAqB,IAYrB,qBAAsB,GACxB,EAUO,SAASC,GAAOC,EAAcC,EAAsB,CACzD,IAAMC,EAASD,IAAQ,EACvB,OAASD,EAAOE,KAAY,IAAOA,CACrC,CAGO,SAASC,GAAQH,EAAcC,EAAqB,CACzD,OAAQD,EAAOC,KAAS,CAC1B,CAGO,SAASG,GAAWJ,EAAcC,EAAqB,CAC5D,OAAQD,EAAO,CAACC,KAAS,CAC3B,CCkFA,SAASI,EAAUC,EAA0B,CAC3C,OAAOA,GAAU,IACnB,CAYO,SAASC,GAAcC,EAAWC,EAAmB,CAC1D,GAAM,CAACC,EAAOC,CAAI,EAAIC,GAAaJ,CAAC,EAC9B,CAACK,EAAOC,CAAI,EAAIF,GAAaH,CAAC,EAE9BM,EAAKL,EAAM,MAAM,GAAG,EACpBM,EAAKH,EAAM,MAAM,GAAG,EACpBI,EAAI,KAAK,IAAIF,EAAG,OAAQC,EAAG,MAAM,EACvC,QAASE,EAAI,EAAGA,EAAID,EAAGC,IAAK,CAC1B,IAAMC,EAAIC,GAAgBL,EAAGG,CAAC,GAAK,IAAKF,EAAGE,CAAC,GAAK,GAAG,EACpD,GAAIC,IAAM,EAAG,OAAOA,CACtB,CAGA,GAAIR,IAAS,QAAaG,IAAS,OAAW,MAAO,GACrD,GAAIH,IAAS,OAAW,MAAO,GAC/B,GAAIG,IAAS,OAAW,MAAO,GAE/B,IAAMO,EAAKV,EAAK,MAAM,GAAG,EACnBW,EAAKR,EAAK,MAAM,GAAG,EACnBS,EAAI,KAAK,IAAIF,EAAG,OAAQC,EAAG,MAAM,EACvC,QAASJ,EAAI,EAAGA,EAAIK,EAAGL,IAAK,CAC1B,GAAIA,GAAKG,EAAG,OAAQ,MAAO,GAC3B,GAAIH,GAAKI,EAAG,OAAQ,MAAO,GAC3B,IAAMH,EAAIC,GAAgBC,EAAGH,CAAC,EAAGI,EAAGJ,CAAC,CAAC,EACtC,GAAIC,IAAM,EAAG,OAAOA,CACtB,CACA,MAAO,EACT,CAGA,SAASP,GAAaY,EAAyC,CAC7D,IAAMC,EAAOD,EAAE,MAAM,IAAK,CAAC,EAAE,CAAC,EACxBE,EAAOD,EAAK,QAAQ,GAAG,EAC7B,OAAOC,IAAS,GAAK,CAACD,EAAM,MAAS,EAAI,CAACA,EAAK,MAAM,EAAGC,CAAI,EAAGD,EAAK,MAAMC,EAAO,CAAC,CAAC,CACrF,CAQA,SAASN,GAAgBZ,EAAWC,EAAmB,CACrD,IAAMM,EAAK,QAAQ,KAAKP,CAAC,EACnBQ,EAAK,QAAQ,KAAKP,CAAC,EACzB,GAAIM,GAAMC,EAAI,CACZ,IAAMW,EAAI,OAAOnB,CAAC,EAAI,OAAOC,CAAC,EAC9B,OAAOkB,EAAI,EAAI,GAAKA,EAAI,EAAI,EAAI,CAClC,CACA,OAAIZ,EAAW,GACXC,EAAW,EACRR,EAAIC,EAAI,GAAKD,EAAIC,EAAI,EAAI,CAClC,CAWA,eAAemB,GAAcC,EAAaC,EAAcC,EAAoC,CAC1F,OAAOC,GACL,SAAY,CACV,IAAMC,EAAO,MAAM,MAAMJ,EAAK,CAAE,OAAQ,YAAY,QAAQ,GAAK,CAAE,CAAC,EACpE,GAAI,CAACI,EAAK,GAAI,CAEZ,IAAMC,EAAM,IAAI,MACd,YAAYJ,CAAI,kBAAkBG,EAAK,MAAM,SAASF,CAAO,EAC/D,EACA,MAAAG,EAAI,OAASD,EAAK,OACZC,CACR,CACA,OAAOD,CACT,EACA,CAAE,SAAU,CAAE,CAChB,CACF,CAEO,IAAME,EAAN,KAAqB,CAC1B,YAAoBC,EAAwB,CAAxB,gBAAAA,CAAyB,CAI7C,MAAM,aAAqC,CACzC,OAAO/B,EAAO,MAAM,KAAK,WAAW,IAA4B,mBAAmB,CAAC,CACtF,CAOA,MAAM,gBAA2C,CAC/C,OAAOA,EAAO,MAAM,KAAK,WAAW,IAA+B,kBAAkB,CAAC,CACxF,CAEA,MAAM,UAAqC,CACzC,OAAO,KAAK,WAAW,IAAqB,sBAAsB,CACpE,CAWA,MAAM,mBAAmBgC,EAA6E,CACpG,OAAOhC,EAAO,MAAM,KAAK,WAAW,KAA+C,iCAAkCgC,CAAO,CAAC,CAC/H,CAUA,MAAM,oBAAoBC,EAAqBC,EAAwC,CACrF,IAAMV,EAAM,IAAI,IAAI,kBAAmB,IAAI,IAAIS,CAAW,EAAE,MAAM,EAClET,EAAI,aAAa,IAAI,UAAWU,CAAW,EAE3C,IAAMC,EAAW,MADJ,MAAMZ,GAAcC,EAAI,SAAS,EAAG,WAAYU,CAAW,GAC5C,KAAK,EACjC,OAAQ,MAAM,QAAQC,CAAO,EAAIA,EAAU,CAAC,GACzC,IAAK/B,GAAMA,EAAE,UAAU,EACvB,OAAQe,GAAmB,OAAOA,GAAM,QAAQ,EAChD,KAAK,CAAChB,EAAGC,IAAMF,GAAcE,EAAGD,CAAC,CAAC,CACvC,CAEA,MAAM,sBAAsB6B,EAAgF,CAC1G,OAAOhC,EAAO,MAAM,KAAK,WAAW,KAA+C,qCAAsCgC,CAAO,CAAC,CACnI,CAEA,MAAM,qBAAqBI,EAA0D,CACnF,OAAOpC,EAAO,MAAM,KAAK,WAAW,OAAmD,2BAA2BoC,CAAK,EAAE,CAAC,CAC5H,CAEA,MAAM,kBAA0D,CAC9D,OAAOpC,EAAO,MAAM,KAAK,WAAW,IAA4C,yBAAyB,CAAC,CAC5G,CAEA,MAAM,eAAeoC,EAAoD,CACvE,OAAOpC,EAAO,MAAM,KAAK,WAAW,IAA0C,2BAA2BoC,CAAK,EAAE,CAAC,CACnH,CAOA,MAAM,wBAAwBC,EAA2D,CACvF,OAAOrC,EACL,MAAM,KAAK,WAAW,IAAyC,2BAA2BqC,CAAa,WAAW,CACpH,CACF,CAMA,MAAM,kBAAkBA,EAAuBC,EAA8D,CAC3G,IAAMC,EAAQD,EAAc,iBAAiB,mBAAmBA,CAAW,CAAC,GAAK,GACjF,OAAOtC,EACL,MAAM,KAAK,WAAW,IACpB,2BAA2BqC,CAAa,OAAOE,CAAK,EACtD,CACF,CACF,CAIA,MAAM,cAAkD,CAEtD,IAAMC,EAAI,MAAM,KAAK,WAAW,IAC9B,qBACF,EACA,OAAQA,GAAG,MAAQA,CACrB,CAEA,MAAM,oBAAoBN,EAAwD,CAEhF,IAAMM,EAAI,MAAM,KAAK,WAAW,IAC9B,uBAAuB,mBAAmBN,CAAW,CAAC,WACxD,EACA,OAAQM,GAAG,MAAQA,CACrB,CAEA,MAAM,wBAAwBN,EAA4D,CACxF,OAAO,KAAK,WAAW,IACrB,uBAAuB,mBAAmBA,CAAW,CAAC,SACxD,CACF,CAIA,MAAM,cAAcF,EAAmE,CAGrF,IAAMS,EAAO,CAAE,GAAGT,EAAS,qBAAsBA,EAAQ,sBAAwB,CAAC,CAAE,EACpF,OAAOhC,EAAO,MAAM,KAAK,WAAW,KAA0C,sBAAuByC,CAAI,CAAC,CAC5G,CAEA,MAAM,cAAcC,EAAuD,CACzE,OAAO1C,EAAO,MAAM,KAAK,WAAW,OAA4C,uBAAuB0C,CAAS,EAAE,CAAC,CACrH,CAEA,MAAM,aAAgD,CACpD,OAAO1C,EAAO,MAAM,KAAK,WAAW,IAAuC,qBAAqB,CAAC,CACnG,CAEA,MAAM,WAAW0C,EAAqC,CACpD,OAAO1C,EAAO,MAAM,KAAK,WAAW,IAAuB,uBAAuB0C,CAAS,EAAE,CAAC,CAChG,CAEA,MAAM,0BAA0BL,EAAyD,CACvF,OAAOrC,EAAO,MAAM,KAAK,WAAW,IAAuC,uCAAuCqC,CAAa,EAAE,CAAC,CACpI,CAIA,MAAM,yBAAwE,CAC5E,OAAOrC,EAAO,MAAM,KAAK,WAAW,KAAoD,8BAA+B,CAAC,CAAC,CAAC,CAC5H,CAEA,MAAM,qBAAqB0C,EAA8D,CACvF,OAAO1C,EAAO,MAAM,KAAK,WAAW,IAAgD,uBAAuB0C,CAAS,aAAa,CAAC,CACpI,CAEA,MAAM,0BAA0BA,EAA8D,CAC5F,OAAO1C,EAAO,MAAM,KAAK,WAAW,IAAgD,uBAAuB0C,CAAS,mBAAmB,CAAC,CAC1I,CAIA,MAAM,YAAYA,EAAqD,CACrE,OAAO1C,EACL,MAAM,KAAK,WAAW,KAAwC,uBAAuB0C,CAAS,QAAS,CAAC,CAAC,CAC3G,CACF,CAIA,MAAM,gBAAgBA,EAAsD,CAC1E,OAAO1C,EAAO,MAAM,KAAK,WAAW,IAAwC,uBAAuB0C,CAAS,QAAQ,CAAC,CACvH,CAEA,MAAM,kBAAkBA,EAAwD,CAC9E,OAAO1C,EAAO,MAAM,KAAK,WAAW,IAA0C,uBAAuB0C,CAAS,UAAU,CAAC,CAC3H,CAGA,MAAM,YAAYA,EAAmC,CAGnD,IAAMC,EAAOD,EAAY,4BAA4BA,CAAS,GAAK,2BACnE,MAAM,KAAK,WAAW,KAAKC,EAAM,CAAC,CAAC,CACrC,CAOA,MAAM,cACJD,EACAV,EAAgC,CAAC,EACG,CAUpC,OANU,MAAM,KAAK,WAAW,KAC9B,uBAAuBU,CAAS,UAChCV,CACF,GAGY,CAAE,UAAAU,EAAW,cAAe,EAAK,CAC/C,CAEA,MAAM,sBAAsBV,EAAmF,CAC7G,OAAOhC,EACL,MAAM,KAAK,WAAW,KACpB,8CACAgC,CACF,CACF,CACF,CAEA,MAAM,yBACJU,EACAV,EACe,CACf,MAAM,KAAK,WAAW,KAAK,uBAAuBU,CAAS,eAAgBV,CAAO,CACpF,CAEA,MAAM,uCAAuCK,EAAmE,CAE9G,IAAMG,EAAI,MAAM,KAAK,WAAW,IAI9B,sDAAsDH,CAAa,EAAE,EACvE,OAAI,MAAM,QAAQG,CAAC,EAAUA,EAE1BA,GAA+D,UAC/DA,GAA2D,MAC5D,CAAC,CAEL,CAIA,MAAM,WAAWR,EAA6D,CAG5E,IAAMY,EAAS,IAAI,gBACfZ,EAAQ,MAAMY,EAAO,IAAI,OAAQZ,EAAQ,IAAI,EAC7CA,EAAQ,WAAWY,EAAO,IAAI,aAAcZ,EAAQ,SAAS,EACjE,IAAMO,EAAQK,EAAO,SAAS,EACxBD,EAAOJ,EAAQ,oBAAoBA,CAAK,GAAK,mBAK7CM,EAAM7C,EACV,MAAM,KAAK,WAAW,QAAqD2C,EAAM,CAC/E,OAAQ,MACR,KAAMX,EAAQ,KACd,QAAS,CAAE,eAAgB,0BAA2B,CACxD,CAAC,CACH,EACA,MAAO,CAAE,OAAQa,EAAI,QAAS,KAAMA,EAAI,IAAK,CAC/C,CAEA,MAAM,WAAWC,EAAiD,CAIhE,IAAML,EAAO,MAAM,KAAK,WAAW,OACjC,oBAAoBK,CAAM,EAC5B,EACA,MAAO,CAAE,OAAQL,EAAK,QAAS,QAASA,EAAK,OAAQ,CACvD,CAEA,MAAM,WAA4C,CAOhD,MAAO,CAAE,MALGzC,EACV,MAAM,KAAK,WAAW,IACpB,kBACF,CACF,EACoB,MAAM,IAAKI,IAAO,CAAE,OAAQA,EAAE,QAAS,KAAMA,EAAE,IAAK,EAAE,CAAE,CAC9E,CAOQ,SAAS0C,EAAgBC,EAAmC,CAClE,GAAI,CAACA,GAAS,UAAW,MAAO,oBAAoBD,CAAM,GAC1D,IAAMF,EAAS,IAAI,gBAAgB,CAAE,WAAYG,EAAQ,SAAU,CAAC,EACpE,MAAO,oBAAoBD,CAAM,IAAIF,EAAO,SAAS,CAAC,EACxD,CAcA,MAAM,QAAQE,EAAgBC,EAAiD,CAC7E,OAAO,KAAK,WAAW,IAAiB,KAAK,SAASD,EAAQC,CAAO,EAAG,CACtE,MAAO,aACT,CAAC,CACH,CAcA,MAAM,YACJD,EACAC,EACkC,CAIlC,GAAM,CAAE,QAAAC,CAAQ,EAAI,MAAM,KAAK,WAAW,KAAK,KAAK,SAASF,EAAQC,CAAO,CAAC,EACvEE,EAAO,OAAOD,EAAQ,gBAAgB,CAAC,EAC7C,MAAO,CACL,OAAQA,EAAQ,WAAW,GAAKF,EAChC,KAAM,OAAO,SAASG,CAAI,EAAIA,EAAO,EACrC,KAAMD,EAAQ,aAAa,EAC3B,SAAUA,EAAQ,kBAAkB,EAIpC,OAAQA,EAAQ,eAAe,CACjC,CACF,CAIA,MAAM,mBACJhB,EACkC,CAClC,OAAOhC,EACL,MAAM,KAAK,WAAW,KACpB,kCACA,CAAE,MAAOgC,EAAQ,MAAO,UAAWA,EAAQ,SAAU,CACvD,CACF,CACF,CAEA,MAAM,uBACJA,EACkC,CAClC,OAAOhC,EACL,MAAM,KAAK,WAAW,KACpB,sCACA,CAAE,MAAOgC,EAAQ,MAAO,cAAeA,EAAQ,aAAc,CAC/D,CACF,CACF,CAEA,MAAM,mBAAmBkB,EAAgD,CACvE,OAAOlD,EACL,MAAM,KAAK,WAAW,KACpB,mCAAmC,mBAAmBkD,CAAI,CAAC,GAC3D,CAAC,CACH,CACF,CACF,CAEA,MAAM,uBAAuBA,EAAgD,CAC3E,OAAOlD,EACL,MAAM,KAAK,WAAW,KACpB,uCAAuC,mBAAmBkD,CAAI,CAAC,GAC/D,CAAC,CACH,CACF,CACF,CAEA,MAAM,mBAAmBA,EAAgD,CACvE,OAAOlD,EACL,MAAM,KAAK,WAAW,KACpB,mCAAmC,mBAAmBkD,CAAI,CAAC,GAC3D,CAAC,CACH,CACF,CACF,CAEA,MAAM,uBAAuBA,EAAgD,CAC3E,OAAOlD,EACL,MAAM,KAAK,WAAW,KACpB,uCAAuC,mBAAmBkD,CAAI,CAAC,GAC/D,CAAC,CACH,CACF,CACF,CAEA,MAAM,oBAAuD,CAC3D,OAAOlD,EAAO,MAAM,KAAK,WAAW,IAAuC,+BAA+B,CAAC,CAC7G,CAEA,MAAM,wBAA2D,CAC/D,OAAOA,EAAO,MAAM,KAAK,WAAW,IAAuC,mCAAmC,CAAC,CACjH,CAEA,MAAM,kBAAkBgC,EAAqE,CAC3F,OAAOhC,EACL,MAAM,KAAK,WAAW,KACpB,iCACA,CAAE,MAAOgC,EAAQ,MAAO,SAAUA,EAAQ,QAAS,CACrD,CACF,CACF,CAEA,MAAM,kBAAkBkB,EAAgD,CACtE,OAAOlD,EACL,MAAM,KAAK,WAAW,KACpB,kCAAkC,mBAAmBkD,CAAI,CAAC,GAC1D,CAAC,CACH,CACF,CACF,CAEA,MAAM,kBAAkBA,EAAgD,CACtE,OAAOlD,EACL,MAAM,KAAK,WAAW,KACpB,kCAAkC,mBAAmBkD,CAAI,CAAC,GAC1D,CAAC,CACH,CACF,CACF,CAEA,MAAM,mBAAsD,CAC1D,OAAOlD,EAAO,MAAM,KAAK,WAAW,IAAuC,8BAA8B,CAAC,CAC5G,CAIA,MAAM,gBAAsD,CAC1D,OAAOA,EAAO,MAAM,KAAK,WAAW,IAA0C,uBAAuB,CAAC,CACxG,CAEA,MAAM,aAAamD,EAAyC,CAC1D,OAAOnD,EAAO,MAAM,KAAK,WAAW,IAAyB,yBAAyBmD,CAAW,EAAE,CAAC,CACtG,CAUA,MAAM,iBAAyC,CAC7C,OAAOnD,EAAO,MAAM,KAAK,WAAW,IAA4B,qBAAqB,CAAC,CACxF,CAqBA,MAAM,qBAAqBmD,EAAiD,CAC1E,IAAMC,EAAO,MAAM,KAAK,gBAAgB,EACxC,MAAO,CAAE,YAAAD,EAAa,UAAWC,EAAK,UAAW,QAASA,EAAK,SAAU,CAC3E,CA4BA,MAAM,cACJV,EACAV,EACoC,CACpC,OAAOhC,EACL,MAAM,KAAK,WAAW,KACpB,uBAAuB0C,CAAS,WAChCV,CACF,CACF,CACF,CAWA,MAAM,aACJU,EACAV,EACmC,CACnC,OAAOhC,EACL,MAAM,KAAK,WAAW,KACpB,uBAAuB0C,CAAS,SAChCV,CACF,CACF,CACF,CAqBA,MAAM,eAAeU,EAA6C,CAChE,OAAO1C,EACL,MAAM,KAAK,WAAW,IACpB,uBAAuB0C,CAAS,UAClC,CACF,CACF,CAEA,MAAM,6BAA6BL,EAA4D,CAC7F,OAAOrC,EAAO,MAAM,KAAK,WAAW,IAA0C,yCAAyCqC,CAAa,EAAE,CAAC,CACzI,CAEA,MAAM,gBAAgBL,EAAuE,CAC3F,OAAOhC,EACL,MAAM,KAAK,WAAW,KAA4C,wBAAyBgC,CAAO,CACpG,CACF,CAEA,MAAM,gBAAgBmB,EAA2D,CAG/E,OAAOnD,EACL,MAAM,KAAK,WAAW,QAA+C,yBAAyBmD,CAAW,GAAI,CAC3G,OAAQ,SACR,KAAM,KACN,QAAS,CAAE,eAAgB,kBAAmB,CAChD,CAAC,CACH,CACF,CAEA,MAAM,0BACJA,EACAnB,EACwF,CACxF,OAAOhC,EACL,MAAM,KAAK,WAAW,KACpB,yBAAyBmD,CAAW,UACpCnB,GAAW,CAAC,CACd,CACF,CACF,CAmBA,MAAM,UACJmB,EACAnB,EACgC,CAChC,OAAOhC,EACL,MAAM,KAAK,WAAW,KACpB,yBAAyBmD,CAAW,SACpCnB,EACA,CAAE,UAAW,IAAM,CACrB,CACF,CACF,CAEA,MAAM,cACJmB,EACAnB,EACoC,CACpC,IAAMqB,EAAOrD,EACX,MAAM,KAAK,WAAW,KACpB,yBAAyBmD,CAAW,QACpCnB,EACA,CAAE,UAAW,IAAM,CACrB,CACF,EAQA,OAAIqB,GAAQA,EAAK,cAAgB,QAAaA,EAAK,UAAY,OACtD,CAAE,GAAGA,EAAM,YAAaA,EAAK,OAAQ,EAEvCA,CACT,CAEA,MAAM,uBACJF,EACAnB,EAC6C,CAC7C,OAAOhC,EACL,MAAM,KAAK,WAAW,KACpB,yBAAyBmD,CAAW,UACpCnB,GAAW,CAAC,CACd,CACF,CACF,CAEA,MAAM,oBAAoBmB,EAA+C,CACvE,OAAOnD,EAAO,MAAM,KAAK,WAAW,IAA+B,yBAAyBmD,CAAW,SAAS,CAAC,CACnH,CAoBA,MAAM,mBAAmBnB,EAAuE,CAC9F,OAAOhC,EACL,MAAM,KAAK,WAAW,KAA4C,+BAAgCgC,CAAO,CAC3G,CACF,CAeA,MAAM,uBACJA,EAC0C,CAC1C,OAAOhC,EACL,MAAM,KAAK,WAAW,KAAgD,mCAAoCgC,CAAO,CACnH,CACF,CAmBA,MAAM,oBACJsB,EACAtB,EACmC,CACnC,OAAOhC,EACL,MAAM,KAAK,WAAW,KACpB,8BAA8BsD,CAAQ,UACtCtB,GAAW,CAAC,CACd,CACF,CACF,CASA,MAAM,qBACJsB,EACAtB,EACoC,CACpC,OAAOhC,EACL,MAAM,KAAK,WAAW,IACpB,8BAA8B,mBAAmBsD,CAAQ,CAAC,SAC1DtB,CACF,CACF,CACF,CA+BA,MAAM,oBACJA,EAC0C,CAC1C,OAAOhC,EACL,MAAM,KAAK,WAAW,KACpB,oCACAgC,CACF,CACF,CACF,CAQA,MAAM,mBACJsB,EACAtB,EACkC,CAClC,OAAOhC,EACL,MAAM,KAAK,WAAW,IACpB,8BAA8B,mBAAmBsD,CAAQ,CAAC,SAC1DtB,CACF,CACF,CACF,CAcA,MAAM,oBAAoD,CAExD,OADiB,MAAM,KAAK,WAAW,IAAuC,4BAA4B,GAC1F,OAClB,CAcA,MAAM,yBAA8D,CAIlE,OAHiB,MAAM,KAAK,WAAW,IACrC,iCACF,GACgB,YAClB,CAiBA,MAAM,oBACJmB,EACAnB,EAC0C,CAC1C,OAAOhC,EACL,MAAM,KAAK,WAAW,KACpB,yBAAyBmD,CAAW,kBACpCnB,CACF,CACF,CACF,CAIA,MAAM,aAAauB,EAAiD,CAClE,OAAOvD,EAAO,MAAM,KAAK,WAAW,IAAqC,qBAAqBuD,CAAO,EAAE,CAAC,CAC1G,CAGA,MAAM,uBAAuBA,EAAkC,CAC7D,OAAQ,MAAM,KAAK,aAAaA,CAAO,GAAG,mBAC5C,CAGA,MAAM,sBAAsBA,EAAkC,CAC5D,OAAQ,MAAM,KAAK,aAAaA,CAAO,GAAG,kBAC5C,CAEA,MAAM,YAAYA,EAAmD,CAGnE,OAAOvD,EACL,MAAM,KAAK,WAAW,QAA2C,qBAAqBuD,CAAO,GAAI,CAC/F,OAAQ,SACR,KAAM,KACN,QAAS,CAAE,eAAgB,kBAAmB,CAChD,CAAC,CACH,CACF,CAEA,MAAM,iBAAiBA,EAAwD,CAC7E,IAAMtD,EAAW,MAAM,KAAK,WAAW,IACrC,qBAAqBsD,CAAO,UAC9B,EAMA,GAAI,CAAC,MAAM,QAAQtD,GAAU,OAAO,EAAG,CAIrC,IAAMuD,EAAS,OAAOD,CAAO,EAAE,QAAQ,cAAe,EAAE,EAAE,MAAM,EAAG,EAAE,EACrE,MAAM,IAAI,MACR,+CAA+CC,CAAM,0CACvD,CACF,CACA,OAAOvD,CACT,CAOA,MAAM,kBACJsD,EACAR,EAAoC,CAAC,EACG,CACxC,IAAMH,EAAS,IAAI,gBACfG,EAAQ,SAAW,QAAWH,EAAO,IAAI,SAAU,OAAOG,EAAQ,MAAM,CAAC,EACzEA,EAAQ,QAAU,QAAWH,EAAO,IAAI,QAAS,OAAOG,EAAQ,KAAK,CAAC,EAC1E,IAAMR,EAAQK,EAAO,SAAS,EACxBD,EAAO,qBAAqBY,CAAO,kBAAkBhB,EAAQ,IAAIA,CAAK,GAAK,EAAE,GACnF,OAAO,KAAK,WAAW,IAAmCI,CAAI,CAChE,CAOA,MAAM,cACJY,EACAE,EACAzB,EACyB,CACzB,OAAOhC,EACL,MAAM,KAAK,WAAW,KACpB,qBAAqBuD,CAAO,aAAaE,CAAO,QAChDzB,CACF,CACF,CACF,CAEA,MAAM,kBAAkBuB,EAAyD,CAC/E,OAAOvD,EAAO,MAAM,KAAK,WAAW,IAA6C,qBAAqBuD,CAAO,WAAW,CAAC,CAC3H,CAEA,MAAM,gBAAgBA,EAAiBvB,EAAgD,CACrF,MAAM,KAAK,WAAW,KAAK,qBAAqBuB,CAAO,WAAYvB,CAAO,CAC5E,CAEA,MAAM,mBAAmBuB,EAAiBvB,EAAmD,CAC3F,MAAM,KAAK,WAAW,KAAK,qBAAqBuB,CAAO,kBAAmBvB,CAAO,CACnF,CAOA,MAAM,iBACJuB,EACAG,EACA1B,EACe,CACf,MAAM,KAAK,WAAW,IAAI,qBAAqBuB,CAAO,YAAYG,CAAQ,QAAS1B,CAAO,CAC5F,CAOA,MAAM,sBAAsBuB,EAAiBG,EAA+C,CAC1F,OAAO1D,EACL,MAAM,KAAK,WAAW,IACpB,qBAAqBuD,CAAO,YAAYG,CAAQ,eAClD,CACF,CACF,CAOA,MAAM,sBACJH,EACAG,EACA1B,EACe,CACf,MAAM,KAAK,WAAW,IAAI,qBAAqBuB,CAAO,YAAYG,CAAQ,gBAAiB1B,CAAO,CACpG,CAEA,MAAM,uBACJuB,EACAvB,EACe,CACf,MAAM,KAAK,WAAW,IAAI,qBAAqBuB,CAAO,iCAAkCvB,CAAO,CACjG,CA+CA,MAAM,yBACJuB,EAC4D,CAC5D,IAAMI,EAAU,MAAM,KAAK,uBAAuBJ,CAAO,EACzD,GAAIK,GAAOD,EAASE,EAAa,oBAAoB,EACnD,MAAO,CAAE,QAAS,GAAO,oBAAqBF,CAAQ,EAExD,IAAMG,EAAOC,GAAQJ,EAASE,EAAa,oBAAoB,EAC/D,aAAM,KAAK,uBAAuBN,EAAS,CAAE,oBAAqBO,CAAK,CAAC,EACjE,CAAE,QAAS,GAAM,oBAAqBA,CAAK,CACpD,CAkBA,MAAM,gBACJP,EACAE,EACqD,CACrD,GAAM,CAAE,aAAAO,CAAa,EAAI,MAAM,KAAK,sBAAsBT,EAASE,CAAO,EAC1E,GAAIG,GAAOI,EAAcH,EAAa,oBAAoB,EACxD,MAAO,CAAE,QAAS,GAAO,aAAAG,CAAa,EAExC,IAAMF,EAAOC,GAAQC,EAAcH,EAAa,oBAAoB,EACpE,aAAM,KAAK,sBAAsBN,EAASE,EAAS,CAAE,aAAcK,CAAK,CAAC,EAClE,CAAE,QAAS,GAAM,aAAcA,CAAK,CAC7C,CAEA,MAAM,sBACJP,EACAvB,EACe,CACf,MAAM,KAAK,WAAW,IAAI,qBAAqBuB,CAAO,gCAAiCvB,CAAO,CAChG,CAEA,MAAM,sBACJuB,EACAvB,EACe,CACf,MAAM,KAAK,WAAW,IAAI,qBAAqBuB,CAAO,iCAAkCvB,CAAO,CACjG,CAEA,MAAM,sBAAsBuB,EAA6D,CAGvF,IAAMtD,EAAW,MAAM,KAAK,WAAW,IAErC,qBAAqBsD,CAAO,gCAAgC,EAC9D,OAAOtD,EAAS,MAAQA,CAC1B,CAQA,MAAM,sBACJsD,EACAvB,EACe,CACf,MAAM,KAAK,WAAW,IAAI,qBAAqBuB,CAAO,iCAAkCvB,CAAO,CACjG,CAQA,MAAM,0BAA0BuB,EAAgC,CAC9D,MAAM,KAAK,WAAW,OAAO,qBAAqBA,CAAO,gCAAgC,CAC3F,CAIA,MAAM,iBAAiBA,EAAiBvB,EAAiD,CACvF,MAAM,KAAK,WAAW,IAAI,qBAAqBuB,CAAO,YAAavB,CAAO,CAC5E,CAEA,MAAM,iBAAiBuB,EAAiD,CAQtE,OAHiB,MAAM,KAAK,WAAW,IACrC,qBAAqBA,CAAO,WAC9B,IACiB,MAAQ,IAC3B,CAEA,MAAM,kBACJA,EACAG,EACA1B,EACe,CACf,MAAM,KAAK,WAAW,IAAI,qBAAqBuB,CAAO,YAAYG,CAAQ,YAAa1B,CAAO,CAChG,CAOA,MAAM,kBAAkBuB,EAAiBG,EAAkD,CAKzF,OAHiB,MAAM,KAAK,WAAW,IACrC,qBAAqBH,CAAO,YAAYG,CAAQ,WAClD,IACiB,MAAQ,IAC3B,CAEA,MAAM,mBACJH,EACAb,EACAV,EACe,CACf,MAAM,KAAK,WAAW,IAAI,qBAAqBuB,CAAO,aAAab,CAAS,YAAaV,CAAO,CAClG,CAEA,MAAM,mBAAmBuB,EAAiBb,EAAmD,CAK3F,OAHiB,MAAM,KAAK,WAAW,IACrC,qBAAqBa,CAAO,aAAab,CAAS,WACpD,IACiB,MAAQ,IAC3B,CAEA,MAAM,UAAUa,EAAiD,CAC/D,OAAOvD,EAAO,MAAM,KAAK,WAAW,KAAsC,qBAAqBuD,CAAO,QAAS,CAAC,CAAC,CAAC,CACpH,CAEA,MAAM,aAAaA,EAAiBvB,EAAiE,CACnG,OAAOhC,EAAO,MAAM,KAAK,WAAW,KAAyC,qBAAqBuD,CAAO,WAAYvB,CAAO,CAAC,CAC/H,CAEA,MAAM,sBAAsBuB,EAA0D,CACpF,OAAOvD,EACL,MAAM,KAAK,WAAW,IAA8C,qBAAqBuD,CAAO,iBAAiB,CACnH,CACF,CAOA,MAAM,mBAAmBJ,EAA+C,CACtE,IAAMc,EAAK,mBAAmBd,CAAW,EACzC,OAAO,KAAK,WAAW,IAAqB,qBAAqBc,CAAE,mBAAmB,CACxF,CAGA,MAAM,iBAAiBd,EAAoD,CACzE,IAAMc,EAAK,mBAAmBd,CAAW,EACzC,OAAOnD,EACL,MAAM,KAAK,WAAW,IAAoC,qBAAqBiE,CAAE,iBAAiB,CACpG,CACF,CAEA,MAAM,kBAAkBV,EAAyD,CAC/E,OAAOvD,EACL,MAAM,KAAK,WAAW,KACpB,qBAAqBuD,CAAO,iBAC5B,CAAC,CACH,CACF,CACF,CAGA,MAAM,cACJW,EACAlC,EACoC,CAEpC,IAAM,EAAI,MAAM,KAAK,WAAW,KAE9B,qBAAqBkC,CAAY,YAAalC,CAAO,EACvD,OAAQ,GAAG,MAAQ,CACrB,CAEA,MAAM,cAAcuB,EAA2C,CAC7D,IAAMtD,EAAW,MAAM,KAAK,WAAW,IACrC,qBAAqBsD,CAAO,YAC9B,EACA,OAAOtD,EAAS,WAAaA,EAAS,MAAQ,CAAC,CACjD,CAEA,MAAM,uBAAuBsD,EAAiBb,EAAkC,CAC9E,MAAM,KAAK,WAAW,KAAK,qBAAqBa,CAAO,aAAab,CAAS,UAAW,CAAC,CAAC,CAC5F,CAIA,MAAM,sBACJa,EACAvB,EACyF,CACzF,OAAOhC,EACL,MAAM,KAAK,WAAW,KACpB,qBAAqBuD,CAAO,UAC5BvB,GAAW,CAAC,CACd,CACF,CACF,CAEA,MAAM,UAAUA,EAA2D,CACzE,OAAOhC,EACL,MAAM,KAAK,WAAW,KAAsC,yBAA0BgC,CAAO,CAC/F,CACF,CAEA,MAAM,wBAAwBuB,EAA+D,CAC3F,OAAOvD,EACL,MAAM,KAAK,WAAW,KACpB,qBAAqBuD,CAAO,wBAC5B,CAAC,CACH,CACF,CACF,CAIA,MAAM,YAA2C,CAC/C,OAAOvD,EAAO,MAAM,KAAK,WAAW,IAAmC,qBAAqB,CAAC,CAC/F,CAEA,MAAM,UAAUgC,EAA2D,CACzE,OAAOhC,EAAO,MAAM,KAAK,WAAW,KAAsC,wBAAyBgC,CAAO,CAAC,CAC7G,CAMA,MAAM,eAAeA,EAAqE,CACxF,OAAOhC,EACL,MAAM,KAAK,WAAW,KAA2C,8BAA+BgC,CAAO,CACzG,CACF,CAIA,MAAM,eAA4C,CAChD,OAAO,KAAK,WAAW,IAAuB,kBAAkB,CAClE,CAGA,MAAM,kBAAqC,CACzC,OAAO,KAAK,WAAW,IAAa,2BAA2B,CACjE,CAGA,MAAM,UAA6B,CACjC,OAAO,KAAK,WAAW,IAAa,kBAAkB,CACxD,CAGA,MAAM,gBAAkC,CACtC,OAAO,KAAK,WAAW,IAAY,yBAA0B,CAAE,MAAO,MAAO,CAAC,CAChF,CAKA,MAAM,YAAYA,EAA+D,CAC/E,OAAOhC,EACL,MAAM,KAAK,WAAW,KAAwC,oBAAqBgC,CAAO,CAC5F,CACF,CAGA,MAAM,WAAWuB,EAAgC,CAC/C,MAAM,KAAK,WAAW,KAAK,qBAAqBA,CAAO,SAAU,CAAC,CAAC,CACrE,CAGA,MAAM,aAAab,EAAkC,CACnD,MAAM,KAAK,WAAW,KAAK,uBAAuBA,CAAS,SAAU,CAAC,CAAC,CACzE,CAGA,MAAM,eAAeS,EAAoC,CACvD,MAAM,KAAK,WAAW,KAAK,yBAAyBA,CAAW,SAAU,CAAC,CAAC,CAC7E,CAGA,MAAM,oBACJI,EACAvB,EAC0C,CAC1C,OAAO,KAAK,WAAW,KACrB,qBAAqBuB,CAAO,yBAC5BvB,CACF,CACF,CAGA,MAAM,6BACJuB,EACAvB,EAC0C,CAC1C,OAAO,KAAK,WAAW,KACrB,qBAAqBuB,CAAO,mCAC5BvB,CACF,CACF,CAUA,MAAM,oBACJuB,EACAG,EACA1B,EACe,CACf,MAAM,KAAK,WAAW,IAAI,qBAAqBuB,CAAO,YAAYG,CAAQ,eAAgB1B,CAAO,CACnG,CAGA,MAAM,eAAemB,EAAuC,CAC1D,OAAO,KAAK,WAAW,KAAc,qBAAqBA,CAAW,mBAAoB,CAAC,CAAC,CAC7F,CACF,ECvoBO,SAASgB,GAAcC,EAAsB,CAClD,OAAO,MAAM,KAAK,IAAI,YAAY,EAAE,OAAOA,CAAI,EAAIC,GACjDA,EAAE,SAAS,EAAE,EAAE,SAAS,EAAG,GAAG,CAChC,EAAE,KAAK,EAAE,CACX,CCv+BO,SAASC,GACdC,EACgB,CAChB,OAAO,IAAIC,EAAeC,EAAwBF,CAAM,CAAC,CAC3D,CAGO,SAASG,GACdH,EACgB,CAChB,OAAO,IAAIC,EAAeG,GAAqBJ,CAAM,CAAC,CACxD,CAGO,SAASK,GACdL,EACgB,CAChB,OAAO,IAAIC,EAAeK,GAA0BN,CAAM,CAAC,CAC7D,CAMO,SAASO,GACdC,EACAC,EACgB,CAChB,OAAO,IAAIR,EAAeO,CAAU,CACtC,CCnBO,SAASE,GAAkBC,EAAwC,CACxE,GAAI,CACF,IAAMC,EAAYD,EAAI,QAAQ,GAAG,EACjC,GAAIC,IAAc,GAAI,OAAO,KAE7B,IAAMC,EAAOF,EAAI,UAAUC,EAAY,CAAC,EAClCE,EAAS,IAAI,gBAAgBD,CAAI,EAEjCE,EAAcD,EAAO,IAAI,cAAc,EAC7C,OAAKC,EAEE,CACL,YAAAA,EACA,aAAcD,EAAO,IAAI,eAAe,GAAK,GAC7C,cAAeA,EAAO,IAAI,gBAAgB,GAAK,GAC/C,UAAWA,EAAO,IAAI,YAAY,GAAK,GACvC,gBAAiBA,EAAO,IAAI,kBAAkB,GAAK,GACnD,QAASA,EAAO,IAAI,UAAU,GAAK,EACrC,EATyB,IAU3B,MAAQ,CACN,OAAO,IACT,CACF,CAKO,SAASE,GAAkBC,EAAiBC,EAAgC,CACjF,IAAMJ,EAAS,IAAI,gBACnB,OAAAA,EAAO,IAAI,eAAgBI,EAAK,WAAW,EAEvCA,EAAK,aAAeA,EAAK,YAAY,OAAS,GAChDJ,EAAO,IAAI,cAAeI,EAAK,YAAY,KAAK,GAAG,CAAC,EAGtDJ,EAAO,IAAI,OAAQI,EAAK,IAAI,EAExBA,EAAK,cACPJ,EAAO,IAAI,eAAgBI,EAAK,WAAW,EACvCA,EAAK,gBACPJ,EAAO,IAAI,kBAAmBI,EAAK,cAAc,EAE/CA,EAAK,aACPJ,EAAO,IAAI,eAAgBI,EAAK,WAAW,GAMxC,GADMD,EAAQ,QAAQ,OAAQ,EAAE,CACzB,eAAeH,EAAO,SAAS,CAAC,EAChD,CC9DA,IAAMK,GAAuB,IAAI,WAAW,CAC1C,GAAM,GAAM,EAAM,EAAM,EAAM,GAAM,EAAM,EAAM,EAAM,GAAM,IAAM,IAAM,EACxE,GAAM,EAAM,EACd,CAAC,EAGM,SAASC,EAAMC,EAA2B,CAC/C,IAAMC,EAAM,IAAI,WAAW,CAAC,EAC5B,WAAI,SAASA,EAAI,MAAM,EAAE,UAAU,EAAGD,EAAO,EAAI,EAC1CC,CACT,CAEO,SAASC,EAAIC,EAA2B,CAC7C,OAAO,MAAM,KAAKA,EAAQC,GAAMA,EAAE,SAAS,EAAE,EAAE,SAAS,EAAG,GAAG,CAAC,EAAE,KAAK,EAAE,CAC1E,CAEO,SAASC,EAAQL,EAAeM,EAAeC,EAAmC,CACvF,IAAMC,EAAQR,EAAM,KAAK,EACzB,GAAI,CAAC,iBAAiB,KAAKQ,CAAK,GAAKA,EAAM,SAAWD,EAAgB,EACpE,MAAM,IAAI,MACR,GAAGD,CAAK,YAAYC,EAAgB,CAAC,wBAAwBC,EAAM,MAAM,EAC3E,EAKF,IAAML,EAAQ,IAAI,WAAWI,CAAa,EAC1C,QAAS,EAAI,EAAG,EAAIA,EAAe,GAAK,EACtCJ,EAAM,CAAC,EAAI,OAAO,SAASK,EAAM,MAAM,EAAI,EAAG,EAAI,EAAI,CAAC,EAAG,EAAE,EAE9D,OAAOL,CACT,CAYO,SAASM,GAAeT,EAAeM,EAA2B,CACvE,IAAME,EAAQR,EAAM,KAAK,EACzB,GAAI,CAAC,iBAAiB,KAAKQ,CAAK,GAAKA,EAAM,OAAS,IAAM,EACxD,MAAM,IAAI,MAAM,GAAGF,CAAK,2CAA2C,EAErE,IAAMH,EAAQ,IAAI,WAAWK,EAAM,OAAS,CAAC,EAC7C,QAASE,EAAI,EAAGA,EAAIP,EAAM,OAAQO,GAAK,EACrCP,EAAMO,CAAC,EAAI,OAAO,SAASF,EAAM,MAAME,EAAI,EAAGA,EAAI,EAAI,CAAC,EAAG,EAAE,EAE9D,OAAOP,CACT,CAEO,SAASQ,EAAMX,EAAoC,CACxD,IAAMC,EAAM,IAAI,WAAW,CAAC,EAC5B,WAAI,SAASA,EAAI,MAAM,EAAE,aAAa,EAAG,OAAOD,CAAK,EAAG,EAAI,EACrDC,CACT,CAEO,SAASW,KAAUC,EAAiC,CACzD,IAAMZ,EAAM,IAAI,WAAWY,EAAM,OAAO,CAACC,EAAOC,IAAMD,EAAQC,EAAE,OAAQ,CAAC,CAAC,EACtEC,EAAS,EACb,QAAWC,KAAQJ,EACjBZ,EAAI,IAAIgB,EAAMD,CAAM,EACpBA,GAAUC,EAAK,OAEjB,OAAOhB,CACT,CAOA,eAAsBiB,EACpBC,EACAN,EACqB,CACrB,IAAMO,EAAuB,CAACT,EAAMQ,EAAO,MAAM,EAAGA,CAAM,EAC1D,QAAWF,KAAQJ,EACjBO,EAAO,KAAKT,EAAMM,EAAK,MAAM,EAAGA,CAAI,EAEtC,OAAO,IAAI,WACT,MAAM,OAAO,OAAO,OAAO,UAAWL,EAAO,GAAGQ,CAAM,CAAC,CACzD,CACF,CAEA,eAAsBC,EAAiBC,EAAuC,CAC5E,IAAMC,EAAOlB,EAAQiB,EAAW,eAAgB,EAAE,EAC5CE,EAAQZ,EAAOd,GAAsByB,CAAI,EAC/C,GAAI,CACF,OAAO,MAAM,OAAO,OAAO,UACzB,QACAC,EACA,CAAE,KAAM,SAAU,EAClB,GACA,CAAC,MAAM,CACT,CACF,OAASC,EAAO,CAGd,IAAMC,EAASD,aAAiB,MAAQ,KAAKA,EAAM,OAAO,GAAK,GAC/D,MAAM,IAAI,MACR,mMAGEC,CACJ,CACF,CACF,CASA,eAAsBC,EAAgBL,EAAwC,CAC5E,IAAMC,EAAOlB,EAAQiB,EAAW,eAAgB,EAAE,EAC5CE,EAAQZ,EAAOd,GAAsByB,CAAI,EACzCK,EAAM,MAAM,OAAO,OAAO,UAC9B,QACAJ,EACA,CAAE,KAAM,SAAU,EAClB,GACA,CAAC,MAAM,CACT,EAEMK,GADM,MAAM,OAAO,OAAO,UAAU,MAAOD,CAAG,GACJ,EAChD,OAAO,WAAW,KAAK,KAAKC,EAAI,QAAQ,KAAM,GAAG,EAAE,QAAQ,KAAM,GAAG,CAAC,EAAIC,GACvEA,EAAE,WAAW,CAAC,CAChB,CACF,CC1FA,IAAMC,GAAc,IAAI,YAAY,EAAE,OAAO,0BAA0B,EASjEC,GAAc,IAAI,YAAY,EAAE,OAAO,+BAA+B,EAG5E,SAASC,GAAYC,EAA2B,CAC9C,IAAMC,EAAQ,IAAI,YAAY,EAAE,OAAOD,CAAK,EAC5C,OAAOE,EAAOC,EAAMF,EAAM,MAAM,EAAGA,CAAK,CAC1C,CASA,eAAsBG,GACpBC,EAA4B,IAAI,WAAW,CAAC,EACvB,CACrB,IAAMJ,EACJ,OAAOI,GAAS,SAAW,IAAI,YAAY,EAAE,OAAOA,CAAI,EAAIA,EAC9D,OAAOC,EAAWR,GAAa,CAACG,CAAK,CAAC,CACxC,CA8CA,eAAsBM,GAAYC,EAAyC,CACzE,IAAMC,EAAM,MAAMC,EAAiBF,EAAM,YAAY,EAC/CG,EAAW,MAAMP,GAAgBI,EAAM,IAAI,EAE3CI,EAAWC,EAAML,EAAM,QAAQ,EAC/BM,EAAYD,EAAML,EAAM,SAAS,EAEjCO,EAAU,IAAI,YACdC,EAAW,MAAMV,EAAWT,GAAa,CAC7CkB,EAAQ,OAAOP,EAAM,MAAM,EAC3BO,EAAQ,OAAOP,EAAM,IAAI,EACzBG,EACAC,EACAE,CACF,CAAC,EAEKG,EAAY,IAAI,WACpB,MAAM,OAAO,OAAO,KAAK,CAAE,KAAM,SAAU,EAAGR,EAAKO,CAAQ,CAC7D,EAEA,OAAOE,EACLhB,EACEH,GAAYS,EAAM,MAAM,EACxBT,GAAYS,EAAM,IAAI,EACtBG,EACAC,EACAE,EACAG,CACF,CACF,CACF,CCnHO,SAASE,GAAYC,EAAiC,CAC3D,IAAMC,EAAaC,GAAeF,EAAM,WAAY,YAAY,EAC1DG,EAAUD,GAAeF,EAAM,QAAS,mBAAmB,EAM3DI,EACJJ,EAAM,UAAY,OACd,IAAI,WAAW,CAAC,CAAC,CAAC,EAClBK,EAAO,IAAI,WAAW,CAAC,CAAC,CAAC,EAAGH,GAAeF,EAAM,QAAS,mBAAmB,CAAC,EAEpF,OAAOM,EAAID,EAAOJ,EAAYG,EAASD,CAAO,CAAC,CACjD,CCDA,SAASI,GAAUC,EAAoC,CACrD,GAA0BA,GAAS,KAAM,OAAO,IAAI,WAAW,CAAC,EAEhE,GADI,OAAOA,GAAS,UAChBA,aAAgB,WAAY,OAAOA,EACvC,GAAIA,aAAgB,YAAa,OAAO,IAAI,WAAWA,CAAI,EAC3D,GAAI,YAAY,OAAOA,CAAI,EACzB,OAAO,IAAI,WAAWA,EAAK,OAAQA,EAAK,WAAYA,EAAK,UAAU,EAErE,MAAM,IAAI,MACR,8CACGA,GAA8C,aAAa,MAAQ,OAAOA,CAC7E,+DACF,CACF,CAOO,SAASC,GACdC,EAC4C,CAC5C,IAAMC,EAAMD,EAAQ,YAAc,IAC5BE,EAAQF,EAAQ,MAAQ,IAAM,KAAK,MAAM,KAAK,IAAI,EAAI,GAAI,GAEhE,MAAO,OAAOG,GAA2C,CACvD,IAAMC,EAAWF,EAAM,EACvB,OAAOG,GAAY,CACjB,WAAYL,EAAQ,WACpB,QAASA,EAAQ,QACjB,QAAS,MAAMM,GAAY,CACzB,OAAQH,EAAQ,OAChB,KAAMA,EAAQ,KACd,KAAMN,GAAUM,EAAQ,IAAI,EAC5B,SAAAC,EACA,UAAWA,EAAWH,EACtB,aAAcD,EAAQ,YACxB,CAAC,CACH,CAAC,CACH,CACF,CCpFO,IAAMO,GAAN,cAAuB,KAAM,CAKlC,YAAYC,EAAcC,EAAiBC,EAAgBC,EAAe,CACxE,MAAMF,CAAO,EACb,KAAK,KAAO,WACZ,KAAK,KAAOD,EACZ,KAAK,KAAOE,EACZ,KAAK,KAAOC,CACd,CACF,EA0BA,eAAsBC,GACpBC,EACAC,EACAC,EACY,CACZ,IAAMC,EAAW,MAAMH,EAAW,KAAsB,WAAY,CAClE,QAAS,MACT,GAAI,EACJ,OAAAC,EACA,OAAAC,CACF,CAAC,EAED,GAAIC,EAAS,MAAO,CAClB,IAAMC,EAAMD,EAAS,MACrB,MAAM,IAAIT,GAASU,EAAI,MAAQ,GAAIA,EAAI,SAAWA,EAAI,MAAQ,YAAaA,EAAI,KAAMA,EAAI,IAAI,CAC/F,CAEA,OAAOD,EAAS,MAClB,CAEO,IAAME,EAAN,KAAgB,CAGrB,YAAYC,EAAkC,CAC5C,KAAK,WAAaA,EAAK,UACzB,CAEA,MAAM,QAAqBJ,EAAmC,CAC5D,IAAMK,EAAS,MAAMR,GAAuC,KAAK,WAAY,UAAW,CACtF,UAAWG,EAAO,UAClB,OAAQA,EAAO,OACf,SAAUA,EAAO,UAAY,CAAC,CAChC,CAAC,EAED,OAAIK,GAAU,WAAYA,EACjBA,EAAO,OAGTA,CACT,CAQA,MAAM,iBAAiBC,EAAqD,CAC1E,OAAO,KAAK,QAAiC,CAAE,UAAAA,EAAW,OAAQ,oBAAqB,CAAC,CAC1F,CAGA,MAAM,eAAeA,EAAoC,CACvD,OAAO,KAAK,QAAgB,CAAE,UAAAA,EAAW,OAAQ,kBAAmB,CAAC,CACvE,CACF,ECzBA,IAAMC,GAA6C,IAAI,IAAI,CACzD,mBACA,oBACA,kBACA,oBACF,CAAC,EAGM,SAASC,GACdC,EACkC,CAClC,OAAOA,EAAM,OAAS,QAAaF,GAAsB,IAAIE,EAAM,IAAI,CACzE,CCvFA,IAAMC,GAAuB,IAAI,IAAI,CAAC,cAAe,eAAe,CAAC,EAY/DC,GAAyB,IAAI,IAAI,CAAC,IAAK,GAAG,CAAC,EASjD,SAASC,GAAsBC,EAA+D,CAC5F,IAAMC,EAAYD,EAAS,QAAQ,IAAI,cAAc,EAErD,MADI,CAACC,GACD,CAACH,GAAuB,IAAIE,EAAS,MAAM,EAAU,KAClDH,GAAqB,IAAII,CAAS,EAAIA,EAAY,IAC3D,CAgCO,IAAMC,EAAN,KAAgB,CAarB,YAAYC,EAKT,CAdH,KAAQ,UAA2B,KACnC,KAAQ,gBAA0C,KAClD,KAAQ,eAAuD,KAC/D,KAAQ,qBAAoC,IAAI,IAChD,KAAQ,mBAAkC,IAAI,IAC9C,KAAQ,OAAS,GACjB,KAAQ,UAA0B,CAAE,QAAS,CAAC,EAAG,MAAO,CAAC,EAAG,MAAO,CAAC,CAAE,EASpE,KAAK,QAAUA,EAAK,QAAQ,QAAQ,OAAQ,EAAE,EAC9C,KAAK,aAAeA,EAAK,aACzB,KAAK,iBAAmBA,EAAK,kBAAoB,IAGjD,KAAK,UAAY,CAACC,EAAwBC,IACxCF,EAAK,MAAQA,EAAK,MAAMC,EAAKC,CAAI,EAAI,WAAW,MAAMD,EAAKC,CAAI,CACnE,CAKA,GAAGC,EAAeC,EAAsE,CACtF,IAAMC,EAAMF,EACZ,GAAIE,KAAO,KAAK,UAAW,CAEzB,IAAMC,EAAM,KAAK,UAAUD,CAAG,EACzBC,EAAI,SAASF,CAAO,GAAGE,EAAI,KAAKF,CAAO,CAC9C,CACF,CAKA,IAAID,EAAeC,EAAsE,CACvF,IAAMC,EAAMF,EACZ,GAAIE,KAAO,KAAK,UAAW,CAEzB,IAAMC,EAAM,KAAK,UAAUD,CAAG,EACxBE,EAAMD,EAAI,QAAQF,CAAO,EAC3BG,IAAQ,IAAID,EAAI,OAAOC,EAAK,CAAC,CACnC,CACF,CAOA,oBAAoBH,EAA0D,CAC5E,IAAMI,EAA6BC,GAAO,CAGxC,GAAIA,EAAG,OAAS,oBAAqB,OACrC,IAAMC,EAAID,EAAG,KACbL,EAAQ,CAAE,UAAWK,EAAG,UAAW,YAAaC,GAAG,YAAa,UAAWA,GAAG,SAAU,CAAC,CAC3F,EACA,YAAK,GAAG,QAASF,CAAQ,EAClB,IAAM,KAAK,IAAI,QAASA,CAAQ,CACzC,CAWA,iBAAiBJ,EAA2D,CAC1E,IAAMI,EAA6BC,GAAO,CACpCE,GAAsBF,CAAE,GAAGL,EAAQK,CAAE,CAC3C,EACA,YAAK,GAAG,QAASD,CAAQ,EAClB,IAAM,KAAK,IAAI,QAASA,CAAQ,CACzC,CAKQ,KAAKL,EAAeS,EAAuC,CACjE,IAAMP,EAAMF,EACZ,GAAIE,KAAO,KAAK,UACd,QAAWD,KAAW,KAAK,UAAUC,CAAG,EACtC,GAAI,CAEDD,EAAgBQ,CAAG,CACtB,MAAQ,CAER,CAGN,CAEA,MAAM,SAAyB,CAE7B,GAAI,OAAK,iBAAmB,CAAC,KAAK,QAGlC,MAAK,OAAS,GACd,KAAK,gBAAkB,IAAI,gBAE3B,GAAI,CACF,IAAMC,EAAQ,MAAM,KAAK,aAAa,EAChCZ,EAAM,GAAG,KAAK,OAAO,OACrBJ,EAAW,MAAM,KAAK,UAAUI,EAAK,CACzC,QAAS,CACP,cAAiB,UAAUY,CAAK,GAChC,OAAU,mBACZ,EACA,OAAQ,KAAK,gBAAgB,MAC/B,CAAC,EAED,GAAI,CAAChB,EAAS,GAAI,CAOhB,IAAMiB,EAAWlB,GAAsBC,CAAQ,EACzCkB,EAAW,MAAMlB,EAAS,KAAK,EAAE,MAAM,IAAG,EAAY,EAE5D,MAAIiB,EACI,IAAIE,EACRF,EACAjB,EAAS,OACTA,EAAS,WACTI,EACAJ,EAAS,QACTkB,CACF,EAGI,IAAIE,EACRpB,EAAS,OACTA,EAAS,WACTI,EACAJ,EAAS,QACTkB,CACF,CACF,CAEA,GAAI,CAAClB,EAAS,KACZ,MAAM,IAAI,MAAM,0BAA0B,EAG5C,KAAK,WAAWA,EAAS,IAAI,EAAE,MAAOqB,GAAQ,CAC5C,GAAI,KAAK,OAAQ,OACjB,IAAMC,EAAQD,aAAe,MAAQA,EAAM,IAAI,MAAM,OAAOA,CAAG,CAAC,EAChE,KAAK,KAAK,QAASC,CAAK,EACxB,KAAK,kBAAkB,CACzB,CAAC,CACH,OAASD,EAAK,CACZ,GAAI,KAAK,OAAQ,OACjB,IAAMC,EAAQD,aAAe,MAAQA,EAAM,IAAI,MAAM,OAAOA,CAAG,CAAC,EAKhE,GAJA,KAAK,KAAK,QAASC,CAAK,EAIpBA,aAAiBH,EAAkB,OACvC,KAAK,kBAAkB,CACzB,EACF,CAEA,MAAc,WAAWI,EAAiD,CACxE,IAAMC,EAASD,EAAK,UAAU,EACxBE,EAAU,IAAI,YAChBC,EAAS,GAEb,GAAI,CACF,OAAS,CACP,GAAM,CAAE,KAAAC,EAAM,MAAAC,CAAM,EAAI,MAAMJ,EAAO,KAAK,EAC1C,GAAIG,EAAM,MAEVD,GAAUD,EAAQ,OAAOG,EAAO,CAAE,OAAQ,EAAK,CAAC,EAEhD,IAAMC,EAAQH,EAAO,MAAM;AAAA,CAAI,EAE/BA,EAASG,EAAM,IAAI,GAAK,GAExB,QAAWC,KAAQD,EACjB,GAAIC,EAAK,WAAW,OAAO,EAAG,CAC5B,IAAMC,EAAUD,EAAK,MAAM,CAAC,EAAE,KAAK,EAC/BC,GACF,KAAK,cAAcA,CAAO,CAE9B,CAEJ,CAGA,GADAL,GAAUD,EAAQ,OAAO,EACrBC,EAAO,WAAW,OAAO,EAAG,CAC9B,IAAMK,EAAUL,EAAO,MAAM,CAAC,EAAE,KAAK,EACjCK,GACF,KAAK,cAAcA,CAAO,CAE9B,CACF,OAASV,EAAK,CACZ,GAAI,KAAK,OAAQ,OACjB,IAAMC,EAAQD,aAAe,MAAQA,EAAM,IAAI,MAAM,OAAOA,CAAG,CAAC,EAChE,KAAK,KAAK,QAASC,CAAK,CAC1B,CAGK,KAAK,QACR,KAAK,kBAAkB,CAE3B,CAEQ,cAAcS,EAAuB,CAC3C,GAAI,CACF,IAAMC,EAAM,KAAK,MAAMD,CAAO,EAG9B,GAAIC,EAAI,OAAS,WAAaA,EAAI,WAAY,CAC5C,KAAK,UAAYA,EAAI,WACrB,KAAK,KAAK,UAAWA,EAAI,UAAU,GAE/B,KAAK,qBAAqB,KAAO,GAAK,KAAK,mBAAmB,KAAO,IACvE,KAAK,iBAAiB,YAAa,CACjC,WAAY,CAAC,GAAG,KAAK,oBAAoB,EACzC,SAAU,CAAC,GAAG,KAAK,kBAAkB,CACvC,CAAC,EAEH,MACF,CAEA,GAAI,CAACA,EAAI,OAAQ,OAEjB,IAAIC,EAAYD,EAAI,OAAO,KAE3B,GAAI,MAAM,QAAQC,CAAS,EACzB,GAAI,CACF,IAAMC,EAAQ,IAAI,WAAWD,CAAS,EAChCE,EAAO,IAAI,YAAY,EAAE,OAAOD,CAAK,EAC3CD,EAAY,KAAK,MAAME,CAAI,CAC7B,MAAQ,CAER,CAIF,GAAIH,EAAI,OAAO,UAAW,CACxB,KAAK,KAAK,QAAS,CACjB,UAAWA,EAAI,OAAO,UAGtB,KAAMA,EAAI,OAAO,KACjB,KAAMC,CACR,CAAC,EACD,MACF,CAIID,EAAI,OAAO,SACb,KAAK,KAAK,QAAS,CACjB,QAASA,EAAI,OAAO,QACpB,KAAMA,EAAI,OAAO,KACjB,KAAMC,CACR,CAAC,CAEL,MAAQ,CAER,CACF,CAEA,MAAM,UAAUG,EAAqF,CACnG,IAAMjC,EAAO,MAAM,QAAQiC,CAAS,EAAI,CAAE,WAAYA,CAAU,EAAIA,EAC9DC,GAAiBlC,EAAK,YAAc,CAAC,GAAG,OAAOmC,GAAM,CAAC,KAAK,qBAAqB,IAAIA,CAAE,CAAC,EACvFC,GAAepC,EAAK,UAAY,CAAC,GAAG,OAAOmC,GAAM,CAAC,KAAK,mBAAmB,IAAIA,CAAE,CAAC,EACvF,QAAWA,KAAMnC,EAAK,YAAc,CAAC,EACnC,KAAK,qBAAqB,IAAImC,CAAE,EAElC,QAAWA,KAAMnC,EAAK,UAAY,CAAC,EACjC,KAAK,mBAAmB,IAAImC,CAAE,GAE3BD,EAAc,OAAS,GAAKE,EAAY,OAAS,IAAM,KAAK,WAC/D,MAAM,KAAK,iBAAiB,YAAa,CAAE,WAAYF,EAAe,SAAUE,CAAY,CAAC,CAEjG,CAEA,MAAM,YAAYH,EAAqF,CACrG,IAAMjC,EAAO,MAAM,QAAQiC,CAAS,EAAI,CAAE,WAAYA,CAAU,EAAIA,EAC9DI,GAAiBrC,EAAK,YAAc,CAAC,GAAG,OAAOmC,GAAM,KAAK,qBAAqB,IAAIA,CAAE,CAAC,EACtFG,GAAetC,EAAK,UAAY,CAAC,GAAG,OAAOmC,GAAM,KAAK,mBAAmB,IAAIA,CAAE,CAAC,EACtF,QAAWA,KAAMnC,EAAK,YAAc,CAAC,EACnC,KAAK,qBAAqB,OAAOmC,CAAE,EAErC,QAAWA,KAAMnC,EAAK,UAAY,CAAC,EACjC,KAAK,mBAAmB,OAAOmC,CAAE,GAE9BE,EAAc,OAAS,GAAKC,EAAY,OAAS,IAAM,KAAK,WAC/D,MAAM,KAAK,iBAAiB,cAAe,CAAE,WAAYD,EAAe,SAAUC,CAAY,CAAC,CAEnG,CAEA,MAAc,iBACZC,EACAC,EACe,CACf,GAAI,CACF,IAAM3B,EAAQ,MAAM,KAAK,aAAa,EAChC4B,EAAyD,CAAC,EAC5DD,EAAI,YAAcA,EAAI,WAAW,OAAS,IAAGC,EAAO,WAAaD,EAAI,YACrEA,EAAI,UAAYA,EAAI,SAAS,OAAS,IAAGC,EAAO,SAAWD,EAAI,UACnE,IAAM3C,EAAW,MAAM,KAAK,UAAU,GAAG,KAAK,OAAO,oBAAqB,CACxE,OAAQ,OACR,QAAS,CACP,cAAiB,UAAUgB,CAAK,GAChC,eAAgB,kBAClB,EACA,KAAM,KAAK,UAAU,CACnB,GAAI,KAAK,UACT,OAAA0B,EACA,OAAAE,CACF,CAAC,CACH,CAAC,EACD,GAAI,CAAC5C,EAAS,GAAI,CAOhB,IAAMiB,EAAWlB,GAAsBC,CAAQ,EACzCkB,EAAW,MAAMlB,EAAS,KAAK,EAAE,MAAM,IAAG,EAAY,EAC5D,KAAK,KACH,QACAiB,EACI,IAAIE,EACFF,EACAjB,EAAS,OACTA,EAAS,WACT,GAAG,KAAK,OAAO,oBACfA,EAAS,QACTkB,CACF,EACA,IAAIE,EACFpB,EAAS,OACTA,EAAS,WACT,GAAG,KAAK,OAAO,oBACfA,EAAS,QACTkB,CACF,CACN,CACF,CACF,OAASG,EAAK,CACZ,KAAK,KAAK,QAASA,aAAe,MAAQA,EAAM,IAAI,MAAM,OAAOqB,CAAM,SAAS,CAAC,CACnF,CACF,CAEQ,gBAAuB,CACzB,KAAK,kBACP,KAAK,gBAAgB,MAAM,EAC3B,KAAK,gBAAkB,MAEzB,KAAK,UAAY,KACjB,KAAK,QAAQ,CACf,CAEQ,mBAA0B,CAC5B,KAAK,SACL,KAAK,gBACP,aAAa,KAAK,cAAc,EAElC,KAAK,eAAiB,WAAW,IAAM,CACrC,KAAK,eAAiB,KACtB,KAAK,eAAe,CACtB,EAAG,KAAK,gBAAgB,EAC1B,CAEA,OAAc,CACZ,KAAK,OAAS,GACV,KAAK,kBACP,KAAK,gBAAgB,MAAM,EAC3B,KAAK,gBAAkB,MAErB,KAAK,iBACP,aAAa,KAAK,cAAc,EAChC,KAAK,eAAiB,MAExB,KAAK,UAAY,KACjB,KAAK,qBAAqB,MAAM,EAChC,KAAK,mBAAmB,MAAM,CAChC,CACF,ECnbO,IAAMG,GAAN,MAAMA,EAAS,CAapB,YAAYC,EAGT,CAbH,KAAQ,GAAuB,KAC/B,KAAQ,OAAS,GACjB,KAAQ,iBAAmB,EAC3B,KAAQ,eAAuD,KAC/D,KAAQ,qBAAoC,IAAI,IAChD,KAAQ,mBAAkC,IAAI,IAC9C,KAAQ,UAAyB,CAAE,QAAS,CAAC,EAAG,MAAO,CAAC,EAAG,MAAO,CAAC,CAAE,EAQnE,KAAK,QAAUA,EAAK,QAAQ,QAAQ,OAAQ,EAAE,EAC9C,KAAK,aAAeA,EAAK,YAC3B,CAKA,GAAGC,EAAeC,EAAmE,CACnF,IAAMC,EAAMF,EACZ,GAAIE,KAAO,KAAK,UAAW,CAEzB,IAAMC,EAAM,KAAK,UAAUD,CAAG,EACzBC,EAAI,SAASF,CAAO,GAAGE,EAAI,KAAKF,CAAO,CAC9C,CACF,CAKA,IAAID,EAAeC,EAAmE,CACpF,IAAMC,EAAMF,EACZ,GAAIE,KAAO,KAAK,UAAW,CAEzB,IAAMC,EAAM,KAAK,UAAUD,CAAG,EACxBE,EAAMD,EAAI,QAAQF,CAAO,EAC3BG,IAAQ,IAAID,EAAI,OAAOC,EAAK,CAAC,CACnC,CACF,CAKQ,KAAKJ,EAAeK,EAA6B,CACvD,IAAMH,EAAMF,EACZ,GAAIE,KAAO,KAAK,UACd,QAAWD,KAAW,KAAK,UAAUC,CAAG,EACtC,GAAI,CAEDD,EAAgBI,CAAG,CACtB,MAAQ,CAER,CAGN,CAEA,MAAM,SAAyB,CAC7B,GAAI,OAAK,KAAO,KAAK,GAAG,aAAe,GAAsB,KAAK,GAAG,aAAe,IAGpF,MAAK,OAAS,GAEd,GAAI,CACF,IAAMC,EAAQ,MAAM,KAAK,aAAa,EACtC,GAAI,CAACA,EACH,MAAM,IAAI,MAAM,4DAA4D,EAG9E,IAAMC,EAAQ,KAAK,QAAQ,QAAQ,QAAS,IAAI,EAChD,KAAK,GAAK,IAAI,UAAU,GAAGA,CAAK,aAAa,mBAAmBD,CAAK,CAAC,EAAE,EAExE,KAAK,GAAG,OAAS,IAAM,CACrB,KAAK,iBAAmB,EACxB,KAAK,KAAK,SAAS,EACnB,KAAK,0BAA0B,CACjC,EAEA,KAAK,GAAG,UAAaN,GAAU,CAC7B,KAAK,cAAcA,EAAM,IAAI,CAC/B,EAEA,KAAK,GAAG,QAAU,IAAM,CACtB,KAAK,KAAK,QAAS,IAAI,MAAM,iBAAiB,CAAC,CACjD,EAEA,KAAK,GAAG,QAAU,IAAM,CACjB,KAAK,QACR,KAAK,kBAAkB,CAE3B,CACF,OAASQ,EAAK,CACZ,GAAI,KAAK,OAAQ,OACjB,IAAMC,EAAQD,aAAe,MAAQA,EAAM,IAAI,MAAM,OAAOA,CAAG,CAAC,EAChE,KAAK,KAAK,QAASC,CAAK,EACxB,KAAK,kBAAkB,CACzB,EACF,CAEQ,cAAcC,EAAwC,CAC5D,GAAI,OAAOA,GAAQ,SAEnB,GAAI,CACF,IAAMC,EAAM,KAAK,MAAMD,CAAG,EAE1B,GAAI,CAACC,EAAI,OAAQ,OAEjB,IAAIC,EAAYD,EAAI,OAAO,KAC3B,GAAI,MAAM,QAAQC,CAAS,EACzB,GAAI,CACF,IAAMC,EAAQ,IAAI,WAAWD,CAAS,EAChCE,EAAO,IAAI,YAAY,EAAE,OAAOD,CAAK,EAC3CD,EAAY,KAAK,MAAME,CAAI,CAC7B,MAAQ,CAER,CAGF,GAAIH,EAAI,OAAO,UAAW,CACxB,KAAK,KAAK,QAAS,CACjB,UAAWA,EAAI,OAAO,UAGtB,KAAMA,EAAI,OAAO,KACjB,KAAMC,CACR,CAAC,EACD,MACF,CAIID,EAAI,OAAO,SACb,KAAK,KAAK,QAAS,CACjB,QAASA,EAAI,OAAO,QACpB,KAAMA,EAAI,OAAO,KACjB,KAAMC,CACR,CAAC,CAEL,MAAQ,CAER,CACF,CAEA,UAAUG,EAA4E,CACpF,IAAMhB,EAAO,MAAM,QAAQgB,CAAS,EAAI,CAAE,WAAYA,CAAU,EAAIA,EACpE,QAAWC,KAAMjB,EAAK,YAAc,CAAC,EACnC,KAAK,qBAAqB,IAAIiB,CAAE,EAElC,QAAWA,KAAMjB,EAAK,UAAY,CAAC,EACjC,KAAK,mBAAmB,IAAIiB,CAAE,EAG5B,KAAK,IAAM,KAAK,GAAG,aAAe,GACpC,KAAK,YAAY,CACf,GAAI,KACJ,OAAQ,YACR,OAAQ,KAAK,wBAAwBjB,EAAK,YAAc,CAAC,EAAGA,EAAK,UAAY,CAAC,CAAC,CACjF,CAAC,CAEL,CAEA,YAAYgB,EAA4E,CACtF,IAAMhB,EAAO,MAAM,QAAQgB,CAAS,EAAI,CAAE,WAAYA,CAAU,EAAIA,EACpE,QAAWC,KAAMjB,EAAK,YAAc,CAAC,EACnC,KAAK,qBAAqB,OAAOiB,CAAE,EAErC,QAAWA,KAAMjB,EAAK,UAAY,CAAC,EACjC,KAAK,mBAAmB,OAAOiB,CAAE,EAG/B,KAAK,IAAM,KAAK,GAAG,aAAe,GACpC,KAAK,YAAY,CACf,GAAI,KACJ,OAAQ,cACR,OAAQ,KAAK,wBAAwBjB,EAAK,YAAc,CAAC,EAAGA,EAAK,UAAY,CAAC,CAAC,CACjF,CAAC,CAEL,CAEQ,2BAAkC,CACpC,KAAK,qBAAqB,OAAS,GAAK,KAAK,mBAAmB,OAAS,GAC7E,KAAK,YAAY,CACf,GAAI,KACJ,OAAQ,YACR,OAAQ,KAAK,wBAAwB,CAAC,GAAG,KAAK,oBAAoB,EAAG,CAAC,GAAG,KAAK,kBAAkB,CAAC,CACnG,CAAC,CACH,CAEQ,wBACNkB,EACAC,EACgD,CAChD,IAAMC,EAAyD,CAAC,EAChE,OAAIF,EAAW,OAAS,IAAGE,EAAO,WAAaF,GAC3CC,EAAS,OAAS,IAAGC,EAAO,SAAWD,GACpCC,CACT,CAEQ,YAAYR,EAAoB,CAClC,KAAK,IAAM,KAAK,GAAG,aAAe,GACpC,KAAK,GAAG,KAAK,KAAK,UAAUA,CAAG,CAAC,CAEpC,CAEQ,mBAA0B,CAChC,GAAI,KAAK,OAAQ,OACb,KAAK,gBACP,aAAa,KAAK,cAAc,EAElC,IAAMS,EAAQ,KAAK,IACjB,IAAO,KAAK,IAAI,EAAG,KAAK,gBAAgB,EACxCtB,GAAS,cACX,EACA,KAAK,mBACL,KAAK,eAAiB,WAAW,IAAM,CACrC,KAAK,eAAiB,KACtB,KAAK,QAAQ,CACf,EAAGsB,CAAK,CACV,CAEA,OAAc,CACZ,KAAK,OAAS,GACV,KAAK,iBACP,aAAa,KAAK,cAAc,EAChC,KAAK,eAAiB,MAEpB,KAAK,KACP,KAAK,GAAG,QAAU,KAClB,KAAK,GAAG,MAAM,EACd,KAAK,GAAK,MAEZ,KAAK,qBAAqB,MAAM,EAChC,KAAK,mBAAmB,MAAM,CAChC,CACF,EAhPatB,GAWa,eAAiB,IAXpC,IAAMuB,EAANvB,GCzBA,SAASwB,IAAyB,CACvC,MAAO,CACL,OAAOC,EAAoB,CACzB,OAAO,MAAM,KAAK,IAAI,YAAY,EAAE,OAAO,KAAK,UAAUA,CAAK,CAAC,CAAC,CACnE,EACA,OAAOC,EAAoB,CACzB,OAAO,KAAK,MAAM,IAAI,YAAY,EAAE,OAAO,IAAI,WAAWA,CAAK,CAAC,CAAC,CACnE,CACF,CACF,CCaO,IAAMC,EAAN,KAAsB,CAI3B,YAAYC,EAAkD,CAC5D,KAAK,WAAaA,EAAK,WACvB,KAAK,IAAMA,EAAK,GAClB,CAUA,MAAM,IAAOC,EAAmBC,EAAUC,EAAkBC,GAAa,EAAkB,CACzF,MAAMC,GAAmC,KAAK,WAAY,gBAAiB,CACzE,UAAAJ,EACA,MAAOE,EAAM,OAAOD,CAAK,CAC3B,CAAC,CACH,CAYA,UACED,EACAK,EACAH,EAAkBC,GAAa,EACnB,CACZ,IAAMG,EAAYC,GAAyB,CACzC,IAAMC,EAAID,EACV,GAAIC,EAAE,OAAS,aAAeA,EAAE,YAAcR,EAAW,OAEzD,IAAMS,EAAOD,EAAE,KAGf,GAAI,CAACC,GAAM,OAAQ,OAInB,IAAMC,EAAU,EAAQD,EAAK,QACvBE,EAA2B,CAC/B,OAAQF,EAAK,OACb,MAAOC,GAAWD,EAAK,QAAU,OAAY,OAAYP,EAAM,OAAOO,EAAK,KAAK,EAChF,QAAAC,CACF,EAGID,EAAK,QAAU,SAAWE,EAAM,MAAQF,EAAK,OACjDJ,EAAQM,CAAK,CACf,EAEA,YAAK,IAAI,GAAG,QAASL,CAAQ,EAGxB,KAAK,IAAI,QAAQ,EAAE,MAAM,IAAG,EAAY,EACxC,KAAK,IAAI,UAAU,CAACN,CAAS,CAAC,EAAE,MAAM,IAAG,EAAY,EAEnD,IAAM,CACX,KAAK,IAAI,IAAI,QAASM,CAAQ,CAChC,CACF,CACF,ECvEA,IAAMM,GAAc,IAAI,YAAY,EAAE,OAAO,yBAAyB,EAChEC,GAAoB,IAAI,YAAY,EAAE,OAAO,6BAA6B,EAC1EC,GAAmB,IAAI,YAAY,EAAE,OAAO,uBAAuB,EAanEC,GAA0B,EA4BhC,eAAsBC,GACpBC,EACAC,EACiB,CACjB,GAAIA,EAAM,SAAW,GACnB,MAAM,IAAI,MAAM,+BAA+BA,EAAM,MAAM,EAAE,EAE/D,IAAMC,EAAeC,EAAQH,EAAS,UAAW,EAAE,EACnD,OAAOI,EAAI,MAAMC,EAAWR,GAAkB,CAACK,EAAcD,CAAK,CAAC,CAAC,CACtE,CASA,eAAsBK,GAAkBC,EAOhB,CACtB,OAAOF,EAAWV,GAAa,CAC7BQ,EAAQI,EAAM,QAAS,UAAW,EAAE,EACpCJ,EAAQI,EAAM,OAAQ,SAAU,EAAE,EAClCJ,EAAQI,EAAM,cAAe,gBAAiB,EAAE,EAChDJ,EAAQI,EAAM,aAAc,eAAgB,EAAE,EAC9CC,EAAMD,EAAM,QAAQ,EACpBC,EAAMD,EAAM,WAAW,CACzB,CAAC,CACH,CAgBO,SAASE,GAAkBF,EASnB,CACb,OAAOG,EACL,IAAI,WAAW,CAACZ,EAAuB,CAAC,EACxCS,EAAM,cACNC,EAAM,CAAC,EACPL,EAAQI,EAAM,QAAS,UAAW,EAAE,EACpCJ,EAAQI,EAAM,OAAQ,SAAU,EAAE,EAClCJ,EAAQI,EAAM,cAAe,gBAAiB,EAAE,EAChDJ,EAAQI,EAAM,aAAc,eAAgB,EAAE,EAC9CC,EAAMD,EAAM,QAAQ,EACpBC,EAAMD,EAAM,WAAW,EACvBA,EAAM,SACR,CACF,CA0BA,eAAsBI,GAAeJ,EAAyC,CAC5E,IAAMK,EAAgB,MAAMC,EAAgBN,EAAM,UAAU,EACtDP,EAAUI,EACd,MAAMC,EAAWT,GAAmB,CAClCc,EAAO,IAAI,WAAW,CAACZ,EAAuB,CAAC,EAAGc,CAAa,CACjE,CAAC,CACH,EAEME,EAAW,EACXC,EAAU,MAAMT,GAAkB,CACtC,QAAAN,EACA,OAAQO,EAAM,OACd,cAAeA,EAAM,cACrB,aAAcA,EAAM,aACpB,SAAAO,EACA,YAAaP,EAAM,WACrB,CAAC,EAEKS,EAAM,MAAMC,EAAiBV,EAAM,UAAU,EAC7CW,EAAY,IAAI,WACpB,MAAM,OAAO,OAAO,KAAK,UAAWF,EAAKD,CAAO,CAClD,EAEMI,EAAaV,GAAkB,CACnC,cAAAG,EACA,QAAAZ,EACA,OAAQO,EAAM,OACd,cAAeA,EAAM,cACrB,aAAcA,EAAM,aACpB,SAAAO,EACA,YAAaP,EAAM,YACnB,UAAAW,CACF,CAAC,EAED,OAAOd,EAAIe,CAAU,CACvB,CAGA,eAAsBC,GAAeC,EAAqC,CACxE,IAAMT,EAAgB,MAAMC,EAAgBQ,CAAU,EACtD,OAAOjB,EACL,MAAMC,EAAWT,GAAmB,CAClCc,EAAO,IAAI,WAAW,CAACZ,EAAuB,CAAC,EAAGc,CAAa,CACjE,CAAC,CACH,CACF,CCtMO,IAAMU,GAAsC,CACjD,UAAW,UAAW,OAAQ,QAAS,QAAS,SAAU,SAAU,WACpE,SAAU,QAAS,SAAU,WAAY,UAAW,SAAU,UAAW,OACzE,WAAY,UAAW,SAAU,MAAO,SAAU,QAAS,UAAW,SACtE,QAAS,MAAO,SAAU,UAAW,SAAU,QAAS,QAAS,UACjE,SAAU,UAAW,SAAU,SAAU,SAAU,QAAS,MAAO,QACnE,QAAS,QAAS,MAAO,MAAO,UAAW,QAAS,QAAS,QAC7D,UAAW,QAAS,QAAS,MAAO,QAAS,QAAS,SAAU,QAChE,QAAS,UAAW,OAAQ,QAAS,SAAU,UAAW,UAAW,QACrE,SAAU,SAAU,UAAW,SAAU,UAAW,QAAS,QAAS,QACtE,SAAU,QAAS,WAAY,SAAU,UAAW,SAAU,UAAW,UACzE,UAAW,MAAO,QAAS,UAAW,SAAU,QAAS,UAAW,QACpE,OAAQ,SAAU,OAAQ,QAAS,QAAS,MAAO,QAAS,QAC5D,OAAQ,SAAU,UAAW,SAAU,SAAU,QAAS,MAAO,WACjE,SAAU,UAAW,MAAO,SAAU,UAAW,QAAS,SAAU,SACpE,SAAU,UAAW,OAAQ,SAAU,SAAU,WAAY,UAAW,UACxE,QAAS,SAAU,OAAQ,SAAU,OAAQ,SAAU,UAAW,UAClE,QAAS,QAAS,QAAS,OAAQ,UAAW,QAAS,UAAW,OAClE,OAAQ,WAAY,QAAS,QAAS,MAAO,UAAW,UAAW,OACnE,SAAU,SAAU,SAAU,MAAO,SAAU,UAAW,SAAU,OACpE,QAAS,SAAU,SAAU,QAAS,OAAQ,SAAU,UAAW,SACnE,OAAQ,SAAU,QAAS,SAAU,SAAU,UAAW,QAAS,OACnE,QAAS,UAAW,OAAQ,SAAU,SAAU,UAAW,SAAU,UACrE,MAAO,OAAQ,OAAQ,UAAW,OAAQ,QAAS,SAAU,QAC7D,QAAS,QAAS,UAAW,QAAS,QAAS,QAAS,QAAS,QACjE,UAAW,SAAU,OAAQ,OAAQ,QAAS,QAAS,OAAQ,OAC/D,OAAQ,OAAQ,OAAQ,QAAS,OAAQ,QAAS,SAAU,SAC5D,SAAU,OAAQ,SAAU,SAAU,MAAO,MAAO,UAAW,QAC/D,QAAS,QAAS,QAAS,QAAS,SAAU,QAAS,SAAU,QACjE,SAAU,QAAS,QAAS,WAAY,SAAU,SAAU,QAAS,UACrE,QAAS,QAAS,SAAU,QAAS,SAAU,UAAW,QAAS,OACnE,OAAQ,SAAU,SAAU,SAAU,SAAU,SAAU,QAAS,MACnE,WAAY,OAAQ,SAAU,QAAS,OAAQ,UAAW,QAAS,QACnE,SAAU,OAAQ,OAAQ,OAAQ,OAAQ,SAAU,OAAQ,MAC5D,QAAS,SAAU,QAAS,SAAU,QAAS,SAAU,SAAU,UACnE,UAAW,UAAW,MAAO,SAAU,OAAQ,QAAS,SAAU,QAClE,OAAQ,OAAQ,OAAQ,SAAU,SAAU,SAAU,MAAO,UAC7D,QAAS,WAAY,SAAU,SAAU,QAAS,UAAW,OAAQ,UACrE,SAAU,SAAU,SAAU,UAAW,SAAU,UAAW,QAAS,QACvE,WAAY,SAAU,QAAS,UAAW,SAAU,QAAS,OAAQ,QACrE,QAAS,SAAU,OAAQ,SAAU,QAAS,UAAW,QAAS,QAClE,UAAW,SAAU,SAAU,UAAW,UAAW,QAAS,QAAS,QACvE,WAAY,SAAU,UAAW,OAAQ,QAAS,QAAS,OAAQ,UACnE,OAAQ,OAAQ,QAAS,QAAS,SAAU,QAAS,SAAU,QAC/D,QAAS,SAAU,OAAQ,QAAS,OAAQ,QAAS,QAAS,QAC9D,QAAS,OAAQ,QAAS,UAAW,SAAU,QAAS,QAAS,UACjE,OAAQ,SAAU,OAAQ,OAAQ,UAAW,QAAS,SAAU,UAChE,OAAQ,UAAW,QAAS,SAAU,UAAW,UAAW,UAAW,UACvE,WAAY,UAAW,WAAY,UAAW,WAAY,OAAQ,OAAQ,SAC1E,OAAQ,QAAS,OAAQ,OAAQ,UAAW,OAAQ,SAAU,QAC9D,UAAW,SAAU,SAAU,SAAU,QAAS,SAAU,QAAS,SACrE,QAAS,OAAQ,QAAS,QAAS,SAAU,QAAS,QAAS,QAC/D,SAAU,QAAS,OAAQ,UAAW,QAAS,QAAS,SAAU,OAClE,QAAS,SAAU,QAAS,UAAW,QAAS,SAAU,UAAW,SACrE,QAAS,MAAO,UAAW,OAAQ,UAAW,MAAO,WAAY,UACjE,UAAW,UAAW,QAAS,UAAW,SAAU,OAAQ,QAAS,MACrE,SAAU,OAAQ,QAAS,SAAU,SAAU,OAAQ,WAAY,OACnE,MAAO,OAAQ,SAAU,SAAU,SAAU,WAAY,SAAU,UACnE,WAAY,WAAY,OAAQ,UAAW,SAAU,OAAQ,SAAU,QACvE,UAAW,SAAU,SAAU,SAAU,UAAW,OAAQ,SAAU,SACtE,UAAW,QAAS,SAAU,SAAU,WAAY,SAAU,SAAU,OACxE,UAAW,UAAW,SAAU,SAAU,UAAW,SAAU,SAAU,UACzE,OAAQ,UAAW,QAAS,OAAQ,SAAU,OAAQ,SAAU,UAChE,UAAW,UAAW,SAAU,WAAY,SAAU,OAAQ,WAAY,WAC1E,UAAW,OAAQ,UAAW,WAAY,UAAW,WAAY,SAAU,SAC3E,UAAW,QAAS,SAAU,WAAY,MAAO,OAAQ,UAAW,SACpE,SAAU,SAAU,QAAS,OAAQ,OAAQ,SAAU,OAAQ,QAC/D,SAAU,QAAS,UAAW,OAAQ,QAAS,QAAS,QAAS,QACjE,QAAS,OAAQ,QAAS,OAAQ,OAAQ,MAAO,OAAQ,OACzD,OAAQ,SAAU,OAAQ,QAAS,OAAQ,QAAS,UAAW,QAC/D,QAAS,QAAS,OAAQ,QAAS,SAAU,OAAQ,OAAQ,OAC7D,UAAW,UAAW,OAAQ,OAAQ,UAAW,SAAU,MAAO,QAClE,SAAU,QAAS,QAAS,WAAY,UAAW,UAAW,WAAY,WAC1E,QAAS,OAAQ,SAAU,SAAU,UAAW,SAAU,UAAW,SACrE,UAAW,QAAS,SAAU,QAAS,MAAO,UAAW,UAAW,QACpE,SAAU,UAAW,SAAU,SAAU,UAAW,QAAS,SAAU,SACvE,SAAU,SAAU,SAAU,QAAS,SAAU,QAAS,WAAY,UACtE,QAAS,QAAS,MAAO,QAAS,QAAS,UAAW,QAAS,QAC/D,SAAU,QAAS,UAAW,SAAU,UAAW,SAAU,WAAY,OACzE,QAAS,SAAU,QAAS,UAAW,SAAU,WAAY,SAAU,UACvE,SAAU,UAAW,WAAY,UAAW,UAAW,QAAS,QAAS,OACzE,SAAU,SAAU,SAAU,SAAU,UAAW,SAAU,UAAW,SACxE,QAAS,MAAO,UAAW,SAAU,OAAQ,UAAW,OAAQ,QAChE,QAAS,OAAQ,QAAS,OAAQ,SAAU,SAAU,MAAO,QAC7D,UAAW,OAAQ,UAAW,MAAO,QAAS,SAAU,UAAW,QACnE,WAAY,UAAW,WAAY,UAAW,MAAO,OAAQ,OAAQ,SACrE,QAAS,WAAY,QAAS,QAAS,MAAO,QAAS,UAAW,QAClE,SAAU,OAAQ,OAAQ,SAAU,QAAS,OAAQ,OAAQ,SAC7D,SAAU,OAAQ,OAAQ,QAAS,SAAU,OAAQ,MAAO,UAC5D,MAAO,OAAQ,QAAS,QAAS,OAAQ,SAAU,OAAQ,SAC3D,OAAQ,QAAS,QAAS,QAAS,SAAU,QAAS,QAAS,MAC/D,OAAQ,QAAS,MAAO,OAAQ,OAAQ,SAAU,OAAQ,OAC1D,QAAS,SAAU,SAAU,OAAQ,UAAW,QAAS,UAAW,SACpE,SAAU,QAAS,MAAO,UAAW,QAAS,WAAY,QAAS,SACnE,SAAU,OAAQ,QAAS,QAAS,QAAS,SAAU,QAAS,OAChE,MAAO,QAAS,UAAW,OAAQ,SAAU,SAAU,OAAQ,SAC/D,UAAW,OAAQ,MAAO,SAAU,UAAW,SAAU,SAAU,UACnE,MAAO,OAAQ,OAAQ,SAAU,QAAS,OAAQ,UAAW,SAC7D,QAAS,SAAU,UAAW,UAAW,QAAS,QAAS,OAAQ,SACnE,SAAU,UAAW,OAAQ,OAAQ,OAAQ,SAAU,QAAS,QAChE,QAAS,UAAW,QAAS,QAAS,QAAS,QAAS,OAAQ,OAChE,OAAQ,UAAW,OAAQ,OAAQ,QAAS,UAAW,SAAU,SACjE,SAAU,OAAQ,OAAQ,QAAS,QAAS,QAAS,QAAS,QAC9D,UAAW,QAAS,QAAS,OAAQ,QAAS,OAAQ,UAAW,QACjE,OAAQ,QAAS,QAAS,QAAS,QAAS,QAAS,SAAU,MAC/D,MAAO,QAAS,OAAQ,OAAQ,SAAU,UAAW,OAAQ,QAC7D,SAAU,OAAQ,QAAS,UAAW,MAAO,OAAQ,OAAQ,SAC7D,OAAQ,SAAU,QAAS,QAAS,WAAY,SAAU,QAAS,SACnE,OAAQ,MAAO,OAAQ,SAAU,OAAQ,OAAQ,OAAQ,MACzD,OAAQ,UAAW,QAAS,SAAU,OAAQ,OAAQ,UAAW,SACjE,OAAQ,QAAS,OAAQ,OAAQ,OAAQ,SAAU,QAAS,WAC5D,OAAQ,QAAS,OAAQ,QAAS,MAAO,OAAQ,QAAS,SAC1D,QAAS,UAAW,SAAU,OAAQ,SAAU,QAAS,OAAQ,UACjE,SAAU,MAAO,OAAQ,OAAQ,WAAY,OAAQ,SAAU,MAC/D,UAAW,UAAW,QAAS,UAAW,UAAW,SAAU,SAAU,SACzE,UAAW,UAAW,OAAQ,UAAW,SAAU,WAAY,QAAS,WACxE,SAAU,WAAY,SAAU,UAAW,SAAU,SAAU,UAAW,UAC1E,SAAU,SAAU,SAAU,QAAS,WAAY,QAAS,UAAW,SACvE,SAAU,SAAU,UAAW,UAAW,SAAU,WAAY,OAAQ,SACxE,SAAU,UAAW,OAAQ,SAAU,UAAW,QAAS,OAAQ,QACnE,SAAU,SAAU,MAAO,OAAQ,UAAW,QAAS,QAAS,QAChE,MAAO,OAAQ,OAAQ,UAAW,MAAO,QAAS,QAAS,OAC3D,SAAU,SAAU,OAAQ,OAAQ,WAAY,OAAQ,OAAQ,UAChE,MAAO,OAAQ,MAAO,SAAU,OAAQ,UAAW,OAAQ,MAC3D,UAAW,OAAQ,SAAU,OAAQ,OAAQ,QAAS,QAAS,OAC/D,MAAO,QAAS,QAAS,SAAU,OAAQ,OAAQ,OAAQ,WAC3D,SAAU,QAAS,QAAS,QAAS,QAAS,UAAW,OAAQ,MACjE,OAAQ,UAAW,QAAS,OAAQ,SAAU,OAAQ,QAAS,QAC/D,UAAW,OAAQ,MAAO,QAAS,SAAU,UAAW,QAAS,OACjE,SAAU,OAAQ,UAAW,SAAU,SAAU,QAAS,OAAQ,UAClE,UAAW,UAAW,OAAQ,OAAQ,QAAS,OAAQ,OAAQ,QAC/D,OAAQ,OAAQ,SAAU,OAAQ,SAAU,OAAQ,SAAU,OAC9D,OAAQ,UAAW,QAAS,OAAQ,QAAS,SAAU,OAAQ,OAC/D,UAAW,OAAQ,SAAU,OAAQ,QAAS,QAAS,UAAW,SAClE,QAAS,QAAS,SAAU,SAAU,UAAW,MAAO,QAAS,SACjE,OAAQ,OAAQ,OAAQ,QAAS,OAAQ,SAAU,MAAO,SAC1D,UAAW,QAAS,UAAW,SAAU,QAAS,SAAU,QAAS,SACrE,SAAU,SAAU,WAAY,OAAQ,OAAQ,SAAU,QAAS,WACnE,OAAQ,SAAU,SAAU,UAAW,OAAQ,SAAU,OAAQ,UACjE,OAAQ,WAAY,QAAS,QAAS,SAAU,OAAQ,SAAU,SAClE,UAAW,OAAQ,QAAS,QAAS,QAAS,QAAS,OAAQ,UAC/D,QAAS,SAAU,SAAU,WAAY,OAAQ,UAAW,QAAS,OACrE,UAAW,QAAS,SAAU,UAAW,SAAU,SAAU,OAAQ,UACrE,MAAO,QAAS,UAAW,SAAU,QAAS,SAAU,MAAO,SAC/D,UAAW,SAAU,UAAW,QAAS,OAAQ,QAAS,OAAQ,UAClE,WAAY,SAAU,SAAU,QAAS,WAAY,QAAS,OAAQ,QACtE,OAAQ,SAAU,OAAQ,WAAY,SAAU,SAAU,WAAY,QACtE,OAAQ,SAAU,SAAU,UAAW,OAAQ,QAAS,OAAQ,SAChE,SAAU,QAAS,SAAU,SAAU,OAAQ,OAAQ,OAAQ,WAC/D,UAAW,UAAW,SAAU,QAAS,OAAQ,MAAO,UAAW,UACnE,QAAS,OAAQ,OAAQ,OAAQ,QAAS,QAAS,QAAS,UAC5D,SAAU,SAAU,QAAS,OAAQ,UAAW,OAAQ,UAAW,SACnE,QAAS,MAAO,UAAW,SAAU,QAAS,MAAO,MAAO,OAC5D,SAAU,SAAU,UAAW,UAAW,SAAU,UAAW,QAAS,QACxE,UAAW,OAAQ,MAAO,QAAS,SAAU,QAAS,MAAO,OAC7D,MAAO,QAAS,UAAW,OAAQ,OAAQ,MAAO,QAAS,SAC3D,OAAQ,OAAQ,QAAS,UAAW,SAAU,SAAU,SAAU,QAClE,UAAW,QAAS,WAAY,QAAS,SAAU,WAAY,SAAU,UACzE,QAAS,UAAW,QAAS,SAAU,UAAW,OAAQ,OAAQ,OAClE,MAAO,QAAS,SAAU,SAAU,QAAS,OAAQ,SAAU,OAC/D,OAAQ,SAAU,OAAQ,QAAS,QAAS,QAAS,UAAW,QAChE,SAAU,SAAU,OAAQ,SAAU,QAAS,OAAQ,QAAS,OAChE,UAAW,SAAU,UAAW,QAAS,OAAQ,UAAW,QAAS,SACrE,OAAQ,UAAW,UAAW,MAAO,UAAW,SAAU,SAAU,SACpE,UAAW,SAAU,SAAU,MAAO,QAAS,QAAS,SAAU,WAClE,QAAS,SAAU,UAAW,QAAS,MAAO,SAAU,OAAQ,QAChE,OAAQ,UAAW,OAAQ,SAAU,QAAS,QAAS,QAAS,SAChE,UAAW,QAAS,OAAQ,SAAU,SAAU,QAAS,OAAQ,SACjE,OAAQ,OAAQ,QAAS,QAAS,OAAQ,SAAU,OAAQ,OAC5D,OAAQ,UAAW,UAAW,WAAY,WAAY,OAAQ,SAAU,UACxE,UAAW,SAAU,QAAS,WAAY,SAAU,UAAW,SAAU,UACzE,UAAW,SAAU,UAAW,QAAS,QAAS,UAAW,QAAS,WACtE,SAAU,UAAW,QAAS,UAAW,UAAW,UAAW,SAAU,UACzE,UAAW,UAAW,QAAS,WAAY,UAAW,UAAW,QAAS,UAC1E,SAAU,UAAW,OAAQ,OAAQ,QAAS,UAAW,QAAS,QAClE,QAAS,WAAY,SAAU,UAAW,QAAS,OAAQ,MAAO,SAClE,UAAW,UAAW,UAAW,UAAW,WAAY,QAAS,OAAQ,OACzE,QAAS,SAAU,UAAW,OAAQ,OAAQ,QAAS,QAAS,OAChE,OAAQ,QAAS,QAAS,OAAQ,QAAS,SAAU,QAAS,QAC9D,OAAQ,OAAQ,SAAU,QAAS,MAAO,QAAS,QAAS,OAC5D,SAAU,QAAS,UAAW,SAAU,UAAW,SAAU,SAAU,UACvE,SAAU,UAAW,SAAU,SAAU,SAAU,SAAU,UAAW,SACxE,QAAS,UAAW,SAAU,OAAQ,SAAU,WAAY,SAAU,SACtE,SAAU,QAAS,OAAQ,SAAU,SAAU,SAAU,UAAW,SACpE,UAAW,SAAU,WAAY,SAAU,WAAY,WAAY,SAAU,SAC7E,UAAW,SAAU,UAAW,SAAU,SAAU,SAAU,SAAU,MACxE,SAAU,OAAQ,OAAQ,OAAQ,QAAS,QAAS,QAAS,QAC7D,OAAQ,OAAQ,SAAU,OAAQ,SAAU,QAAS,QAAS,OAC9D,QAAS,QAAS,SAAU,SAAU,UAAW,OAAQ,SAAU,OACnE,OAAQ,SAAU,QAAS,QAAS,QAAS,QAAS,SAAU,OAChE,MAAO,OAAQ,MAAO,SAAU,QAAS,MAAO,SAAU,UAC1D,OAAQ,OAAQ,QAAS,SAAU,QAAS,OAAQ,SAAU,OAC9D,SAAU,OAAQ,UAAW,UAAW,QAAS,UAAW,OAAQ,MACpE,QAAS,OAAQ,QAAS,UAAW,QAAS,SAAU,SAAU,UAClE,WAAY,WAAY,QAAS,QAAS,SAAU,SAAU,QAAS,MACvE,SAAU,SAAU,OAAQ,SAAU,SAAU,UAAW,WAAY,OACvE,OAAQ,UAAW,SAAU,OAAQ,UAAW,SAAU,QAAS,WACnE,SAAU,UAAW,UAAW,SAAU,QAAS,QAAS,SAAU,QACtE,UAAW,QAAS,OAAQ,QAAS,UAAW,SAAU,QAAS,QACnE,OAAQ,SAAU,QAAS,OAAQ,QAAS,OAAQ,QAAS,WAC7D,QAAS,SAAU,QAAS,UAAW,MAAO,UAAW,OAAQ,OACjE,QAAS,QAAS,OAAQ,SAAU,OAAQ,QAAS,SAAU,UAC/D,SAAU,QAAS,OAAQ,QAAS,SAAU,UAAW,MAAO,OAChE,QAAS,SAAU,MAAO,QAAS,OAAQ,QAAS,QAAS,OAC7D,OAAQ,QAAS,UAAW,QAAS,QAAS,SAAU,OAAQ,SAChE,OAAQ,OAAQ,QAAS,QAAS,QAAS,QAAS,QAAS,SAC7D,QAAS,QAAS,OAAQ,QAAS,OAAQ,OAAQ,SAAU,SAC7D,OAAQ,OAAQ,OAAQ,QAAS,UAAW,QAAS,WAAY,QACjE,UAAW,OAAQ,OAAQ,QAAS,OAAQ,OAAQ,QAAS,OAC7D,SAAU,QAAS,QAAS,QAAS,UAAW,QAAS,QAAS,UAClE,QAAS,QAAS,QAAS,SAAU,QAAS,SAAU,QAAS,OACjE,SAAU,QAAS,QAAS,UAAW,QAAS,QAAS,OAAQ,QACjE,SAAU,SAAU,MAAO,SAAU,UAAW,WAAY,SAAU,UACtE,QAAS,QAAS,SAAU,QAAS,QAAS,QAAS,QAAS,OAChE,QAAS,QAAS,OAAQ,OAAQ,SAAU,QAAS,QAAS,QAC9D,QAAS,UAAW,QAAS,QAAS,QAAS,QAAS,WAAY,SACpE,SAAU,SAAU,WAAY,UAAW,QAAS,UAAW,QAAS,UACxE,SAAU,SAAU,UAAW,OAAQ,SAAU,SAAU,QAAS,UACpE,OAAQ,SAAU,MAAO,QAAS,SAAU,QAAS,SAAU,UAC/D,OAAQ,UAAW,QAAS,WAAY,WAAY,SAAU,UAAW,UACzE,UAAW,QAAS,OAAQ,QAAS,QAAS,QAAS,QAAS,OAChE,QAAS,SAAU,QAAS,SAAU,UAAW,QAAS,SAAU,QACpE,SAAU,MAAO,OAAQ,SAAU,OAAQ,OAAQ,OAAQ,SAC3D,OAAQ,QAAS,SAAU,OAAQ,QAAS,OAAQ,OAAQ,MAC5D,SAAU,SAAU,OAAQ,OAAQ,OAAQ,OAAQ,QAAS,OAC7D,QAAS,OAAQ,SAAU,QAAS,OAAQ,QAAS,OAAQ,UAC7D,QAAS,SAAU,QAAS,QAAS,UAAW,SAAU,OAAQ,QAClE,OAAQ,SAAU,OAAQ,OAAQ,MAAO,QAAS,SAAU,QAC5D,QAAS,UAAW,QAAS,UAAW,MAAO,WAAY,SAAU,QACrE,SAAU,WAAY,OAAQ,SAAU,UAAW,OAAQ,QAAS,MACpE,QAAS,SAAU,QAAS,UAAW,WAAY,OAAQ,QAAS,UACpE,SAAU,QAAS,OAAQ,MAAO,QAAS,QAAS,UAAW,SAC/D,QAAS,WAAY,OAAQ,QAAS,SAAU,OAAQ,QAAS,OACjE,QAAS,QAAS,QAAS,QAAS,UAAW,OAAQ,OAAQ,SAC/D,UAAW,QAAS,OAAQ,QAAS,UAAW,QAAS,QAAS,MAClE,OAAQ,UAAW,SAAU,OAAQ,SAAU,SAAU,OAAQ,SACjE,SAAU,SAAU,QAAS,OAAQ,QAAS,MAAO,OAAQ,UAC7D,OAAQ,WAAY,SAAU,UAAW,QAAS,UAAW,QAAS,OACtE,SAAU,SAAU,UAAW,UAAW,SAAU,OAAQ,WAAY,UACxE,SAAU,QAAS,UAAW,SAAU,SAAU,UAAW,SAAU,OACvE,QAAS,QAAS,QAAS,OAAQ,QAAS,MAAO,OAAQ,SAC3D,UAAW,QAAS,UAAW,SAAU,SAAU,QAAS,QAAS,SACrE,QAAS,MAAO,SAAU,QAAS,UAAW,OAAQ,QAAS,UAC/D,SAAU,SAAU,UAAW,QAAS,OAAQ,SAAU,UAAW,OACrE,SAAU,UAAW,SAAU,UAAW,UAAW,UAAW,QAAS,OACzE,UAAW,UAAW,SAAU,UAAW,QAAS,OAAQ,QAAS,SACrE,QAAS,QAAS,QAAS,QAAS,OAAQ,UAAW,SAAU,OACjE,SAAU,OAAQ,QAAS,OAAQ,OAAQ,OAAQ,SAAU,OAC7D,UAAW,OAAQ,UAAW,OAAQ,OAAQ,QAAS,QAAS,OAChE,MAAO,SAAU,SAAU,OAAQ,SAAU,UAAW,MAAO,UAC/D,UAAW,QAAS,UAAW,OAAQ,MAAO,QAAS,OAAQ,QAC/D,QAAS,OAAQ,QAAS,OAAQ,UAAW,OAAQ,QAAS,OAC9D,OAAQ,OAAQ,MAAO,SAAU,OAAQ,OAAQ,OAAQ,SACzD,SAAU,OAAQ,SAAU,OAAQ,OAAQ,UAAW,OAAQ,QAC/D,SAAU,OAAQ,OAAQ,OAAQ,OAAQ,QAAS,QAAS,QAC5D,OAAQ,QAAS,UAAW,QAAS,QAAS,QAAS,OAAQ,OAC/D,SAAU,MAAO,QAAS,QAAS,QAAS,OAAQ,OAAQ,KAC9D,EChOA,IAAMC,GAAsB,IAAI,YAAY,EAAE,OAC5C,iCACF,EAWMC,GAAuB,IAAI,YAAY,EAAE,OAC7C,kCACF,EAGMC,EAAe,IAEfC,GAAgBD,EAAe,GAE/BE,IAAgBF,EAAeC,IAAiB,GA4CtD,eAAsBE,IAAuD,CAC3E,IAAMC,EAAU,OAAO,gBAAgB,IAAI,WAAWJ,EAAe,CAAC,CAAC,EACvE,OAAOK,GAAWC,EAAIF,CAAO,EAAG,MAAMG,GAAaH,CAAO,CAAC,CAC7D,CAWA,eAAsBI,GACpBC,EACiC,CACjC,IAAMC,EAAQC,GAAgBF,CAAM,EACpC,OAAOJ,GAAWC,EAAI,MAAMM,GAAaF,CAAK,CAAC,EAAGA,EAAM,KAAK,GAAG,CAAC,CACnE,CAGA,eAAsBG,EACpBC,EACsB,CAGtB,OAAAC,EAAQD,EAAQ,SAAU,EAAE,EACrB,CACL,OAAAA,EACA,UAAWR,EAAI,MAAMU,EAAgBF,CAAM,CAAC,EAC5C,UAAW,MAAMG,GAAeH,CAAM,CACxC,CACF,CAyBA,eAAsBI,EACpBC,EACiB,CACjB,GAAI,CAACA,EAAM,MAAO,MAAM,IAAI,MAAM,2CAA2C,EAC7E,IAAMC,EAAM,MAAMC,EAAiBF,EAAM,UAAU,EAC7CG,EAAY,IAAI,WACpB,MAAM,OAAO,OAAO,KAClB,UACAF,EACAG,EAAOzB,GAAqB,IAAI,YAAY,EAAE,OAAOqB,EAAM,KAAK,CAAC,CACnE,CACF,EACA,OAAOK,GAAOF,CAAS,CACzB,CA2BA,eAAsBG,GACpBN,EACiB,CACjB,GAAI,CAACA,EAAM,MAAO,MAAM,IAAI,MAAM,4CAA4C,EAC9E,IAAMC,EAAM,MAAMC,EAAiBF,EAAM,UAAU,EAC7CG,EAAY,IAAI,WACpB,MAAM,OAAO,OAAO,KAClB,UACAF,EACAG,EAAOxB,GAAsB,IAAI,YAAY,EAAE,OAAOoB,EAAM,KAAK,CAAC,CACpE,CACF,EACA,OAAOK,GAAOF,CAAS,CACzB,CAEA,eAAejB,GACbS,EACAL,EACiC,CACjC,MAAO,CAAE,GAAI,MAAMI,EAAsBC,CAAM,EAAI,OAAAL,CAAO,CAC5D,CAEA,SAASE,GAAgBF,EAA0B,CAGjD,IAAMC,EAAQD,EAAO,UAAU,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,KAAK,EACjEiB,EAAQhB,EAAM,CAAC,IAAM,GAAK,EAAIA,EAAM,OAC1C,GAAIgB,IAAUxB,GACZ,MAAM,IAAI,MACR,2BAA2BA,EAAY,eAAewB,CAAK,EAC7D,EAEF,OAAOhB,CACT,CAGA,eAAeH,GAAaH,EAAsC,CAChE,IAAMuB,EAAOC,GAAOxB,CAAO,EAAK,MAAMyB,GAAazB,CAAO,EACpDM,EAAkB,CAAC,EACzB,QAASoB,EAAI,EAAGA,EAAI5B,GAAc4B,GAAK,EAAG,CACxC,IAAMC,EAAQ,OAAO,SAASJ,EAAK,MAAMG,EAAI,GAAIA,EAAI,GAAK,EAAE,EAAG,CAAC,EAChEpB,EAAM,KAAKsB,GAAiBD,CAAK,CAAC,CACpC,CACA,OAAOrB,EAAM,KAAK,GAAG,CACvB,CAEA,eAAeE,GAAaF,EAAsC,CAChE,IAAIiB,EAAO,GACX,QAAWM,KAAQvB,EAAO,CACxB,IAAMqB,EAAQC,GAAiB,QAAQC,CAAI,EAC3C,GAAIF,EAAQ,EACV,MAAM,IAAI,MAAM,IAAIE,CAAI,6CAA6C,EAEvEN,GAAQI,EAAM,SAAS,CAAC,EAAE,SAAS,GAAI,GAAG,CAC5C,CAEA,IAAM3B,EAAU,IAAI,WAAWJ,EAAe,CAAC,EAC/C,QAAS8B,EAAI,EAAGA,EAAI1B,EAAQ,OAAQ0B,GAAK,EACvC1B,EAAQ0B,CAAC,EAAI,OAAO,SAASH,EAAK,MAAMG,EAAI,EAAGA,EAAI,EAAI,CAAC,EAAG,CAAC,EAE9D,GAAIH,EAAK,MAAM3B,CAAY,IAAO,MAAM6B,GAAazB,CAAO,EAC1D,MAAM,IAAI,MACR,gFACF,EAEF,OAAOA,CACT,CAGA,eAAeyB,GAAazB,EAAsC,CAChE,IAAM8B,EAAS,IAAI,WAAW,MAAM,OAAO,OAAO,OAAO,UAAW9B,CAAO,CAAC,EAC5E,OAAOwB,GAAOM,CAAM,EAAE,MAAM,EAAGjC,EAAa,CAC9C,CAEA,SAAS2B,GAAOO,EAA2B,CACzC,IAAIR,EAAO,GACX,QAAWS,KAAQD,EAAOR,GAAQS,EAAK,SAAS,CAAC,EAAE,SAAS,EAAG,GAAG,EAClE,OAAOT,CACT,CAEA,SAASH,GAAOW,EAA2B,CAIzC,IAAIE,EAAS,GACb,QAAWD,KAAQD,EAAOE,GAAU,OAAO,aAAaD,CAAI,EAC5D,OAAO,KAAKC,CAAM,CACpB,CCzPA,IAAMC,GAAuB,IAAI,YAAY,EAAE,OAC7C,iCACF,EAoEA,eAAsBC,GACpBC,EACAC,EACiB,CAGjBC,EAAQD,EAAc,eAAgB,EAAE,EACxC,IAAME,EAAM,MAAMC,EAAiBH,CAAY,EAEzCI,EAAUC,EACdR,GACA,IAAI,YAAY,EAAE,OAAOE,CAAK,CAChC,EAEMO,EAAY,IAAI,WACpB,MAAM,OAAO,OAAO,KAAK,CAAE,KAAM,SAAU,EAAGJ,EAAKE,CAAO,CAC5D,EAEA,OAAOG,GAAOD,CAAS,CACzB,CAaA,eAAsBE,GACpBC,EACAC,EAC8B,CAC9B,MAAO,CACL,wBAAyBA,EAAW,WACpC,mBAAoBD,EAAU,MAC9B,uBAAwB,MAAMX,GAC5BW,EAAU,MACVC,EAAW,YACb,CACF,CACF,CASA,SAASH,GAAOI,EAA2B,CACzC,IAAIC,EAAS,GACb,QAAWC,KAAQF,EAAOC,GAAU,OAAO,aAAaC,CAAI,EAC5D,OAAO,KAAKD,CAAM,CACpB,CCxIA,IAAME,GAAyB,iCAgTzBC,GAAiB,yBACjBC,GAAyB,yBAElBC,EAAN,KAAkB,CAQvB,YAAYC,EAA4B,CAAC,EAAG,CAN5C,KAAQ,QAA+B,KAOrC,KAAK,SAAWA,EAAO,cAAgBJ,IAAwB,QAAQ,OAAQ,EAAE,EACjF,KAAK,UAAYI,EAAO,UAIxB,IAAMC,EAAWD,EAAO,MACxB,KAAK,UAAYC,EACb,CAACC,EAA0BC,IAAuBF,EAASC,EAAOC,CAAI,EACtE,CAACD,EAA0BC,IAAuB,WAAW,MAAMD,EAAOC,CAAI,EAClF,KAAK,UAAYH,EAAO,WAAa,IACrC,KAAK,kBAAoBA,EAAO,kBAE5BA,EAAO,eAIT,KAAK,QAAU,CACb,aAAcA,EAAO,aACrB,UAAW,EACX,KAAM,CAAE,MAAO,EAAG,CACpB,EAEJ,CAGA,YAAkC,CAChC,OAAO,KAAK,OACd,CAGA,YAAsB,CACpB,OAAO,KAAK,UAAY,IAC1B,CAUA,MAAM,iBAAiBI,EAAwC,CAC7D,IAAMC,EAAO,MAAM,KAAK,QAIrB,OAAQ,mBAAoB,CAAE,KAAM,CAAE,SAAUD,CAAQ,EAAG,UAAW,EAAK,CAAC,EAC/E,OAAO,KAAK,aAAaC,CAAI,CAC/B,CA0BA,MAAM,kBAAkBC,EAA2C,CACjE,IAAMC,EAAO,MAAMC,EAAsBF,CAAU,EAC7C,CAAE,MAAAG,CAAM,EAAI,MAAM,KAAK,yBAAyB,EACtD,OAAO,KAAK,mBAAmB,CAC7B,cAAeF,EAAK,UACpB,MAAAE,EACA,UAAW,MAAMC,GAAiB,CAAE,WAAAJ,EAAY,MAAAG,CAAM,CAAC,CACzD,CAAC,CACH,CAaA,MAAM,0BAA2D,CAC/D,IAAMJ,EAAO,MAAM,KAAK,QACtB,MACA,8BACA,CAAE,UAAW,EAAK,CACpB,EACA,MAAO,CAAE,MAAOA,EAAK,MAAO,YAAaA,EAAK,aAAc,CAC9D,CAGA,MAAM,mBAAmBM,EAAiD,CACxE,IAAMN,EAAO,MAAM,KAAK,QAIrB,OAAQ,oBAAqB,CAC9B,KAAM,CACJ,gBAAiBM,EAAM,cACvB,MAAOA,EAAM,MACb,UAAWA,EAAM,SACnB,EACA,UAAW,EACb,CAAC,EACD,OAAO,KAAK,aAAaN,CAAI,CAC/B,CASA,MAAM,gBAAwC,CAC5C,IAAMA,EAAO,MAAM,KAAK,QAIrB,OAAQ,mBAAmB,EAC9B,OAAO,KAAK,aAAaA,CAAI,CAC/B,CASA,MAAM,SAAyB,CAC7B,GAAI,CACE,KAAK,SAAS,MAAM,KAAK,QAAc,OAAQ,mBAAoB,CAAE,MAAO,MAAO,CAAC,CAC1F,QAAE,CACA,KAAK,QAAU,KACV,KAAK,cAAc,IAAI,CAC9B,CACF,CAGA,MAAM,iBAA6C,CAKjD,OAJa,MAAM,KAAK,QACtB,MACA,0BACF,GACY,IAAKO,IAAS,CACxB,YAAa,OAAOA,EAAI,cAAgBA,EAAI,UAAY,EAAE,EAC1D,QAAS,OAAOA,EAAI,UAAYA,EAAI,cAAgB,EAAE,EACtD,SAAU,OAAOA,EAAI,WAAa,MAAM,EACxC,YAAcA,EAAI,eAA+C,KACjE,SAAU,MAAM,QAAQA,EAAI,QAAQ,EAAKA,EAAI,SAAwB,CAAC,EACtE,cAAeA,EAAI,cACrB,EAAE,CACJ,CAUA,MAAM,mBAAmBC,EAA4C,CAKnE,QAJa,MAAM,KAAK,QACtB,MACA,4BAA4B,mBAAmBA,CAAW,CAAC,SAC7D,GACa,QAAU,CAAC,GAAG,IAAKD,IAAS,CACvC,OAAQ,OAAOA,EAAI,SAAW,EAAE,EAChC,SAAWA,EAAI,WAA2C,KAC1D,gBAAkBA,EAAI,kBAAkD,KACxE,OAAQ,OAAOA,EAAI,QAAU,EAAE,EAC/B,gBAAiBA,EAAI,mBAAqB,GAC1C,WAAaA,EAAI,cAA8C,KAC/D,YAAcA,EAAI,cAA8C,IAClE,EAAE,CACJ,CAWA,MAAM,mBAAmBC,EAAiD,CAExE,OADe,MAAM,KAAK,mBAAmBA,CAAW,GAE/C,KAAM,GAAM,EAAE,iBAAmB,EAAE,WAAa,MAAQ,EAAE,kBAAoB,IAAI,GACzF,IAEJ,CAqBA,MAAM,oBAAoBA,EAAqD,CAC7E,IAAMR,EAAO,MAAM,KAAK,QAKrB,MAAO,yBAAyB,mBAAmBQ,CAAW,CAAC,aAAc,CAC9E,UAAW,GACX,QAAS,MAAM,KAAK,aAAaA,CAAW,CAC9C,CAAC,EACD,MAAO,CACL,YAAa,OAAOR,EAAK,cAAgBQ,CAAW,EACpD,OAAQR,EAAK,WAAa,CAAC,GAAG,IAAKO,IAAS,CAC1C,OAAQ,OAAOA,EAAI,SAAW,EAAE,EAChC,QAAUA,EAAI,SAAyC,KACvD,SAAWA,EAAI,WAA2C,KAC1D,SAAWA,EAAI,WAA2C,KAC1D,OAAQ,OAAOA,EAAI,QAAU,EAAE,EAC/B,MAAOA,EAAI,QAAU,GACrB,SAAUA,EAAI,YAAc,GAC5B,gBAAiBA,EAAI,mBAAqB,GAC1C,WAAYA,EAAI,cAAgB,EAClC,EAAE,EACF,SAAUP,EAAK,WAAa,GAC5B,SAAUA,EAAK,WAAa,EAC9B,CACF,CAcA,MAAM,oBAAoBQ,EAAgD,CACxE,IAAMR,EAAO,MAAM,KAAK,QAIrB,MAAO,yBAAyB,mBAAmBQ,CAAW,CAAC,aAAc,CAC9E,UAAW,EACb,CAAC,EACD,MAAO,CACL,YAAa,OAAOR,EAAK,cAAgBQ,CAAW,EACpD,MAAO,OAAOR,EAAK,OAAS,EAAE,EAC9B,YAAa,OAAOA,EAAK,eAAiB,CAAC,CAC7C,CACF,CAUA,MAAc,aACZQ,EAC6C,CAC7C,GAAI,CAAC,KAAK,kBAAmB,OAC7B,IAAMC,EAAY,MAAM,KAAK,oBAAoBD,CAAW,EAC5D,OAAOE,GAAoBD,EAAW,KAAK,iBAAiB,CAC9D,CAUA,MAAM,0BAA0BE,EAAgD,CAC9E,IAAMX,EAAO,MAAM,KAAK,QAIrB,MAAO,uBAAuB,mBAAmBW,CAAS,CAAC,aAAc,CAC1E,UAAW,EACb,CAAC,EACD,MAAO,CACL,UAAW,OAAOX,EAAK,YAAcW,CAAS,EAC9C,MAAO,OAAOX,EAAK,OAAS,EAAE,EAC9B,YAAa,OAAOA,EAAK,eAAiB,CAAC,CAC7C,CACF,CAsBA,MAAM,iBAAiBW,EAAiD,CACtE,GAAI,CAAC,KAAK,kBACR,MAAM,IAAI,MACR,iLAEF,EAEF,IAAMF,EAAY,MAAM,KAAK,0BAA0BE,CAAS,EAQhE,QAPa,MAAM,KAAK,QAGrB,MAAO,uBAAuB,mBAAmBA,CAAS,CAAC,UAAW,CACvE,UAAW,GACX,QAAS,MAAMD,GAAoBD,EAAW,KAAK,iBAAiB,CACtE,CAAC,GACY,QAAU,CAAC,GAAG,IAAKF,IAAS,CACvC,OAAQ,OAAOA,EAAI,SAAW,EAAE,EAChC,SAAWA,EAAI,WAA2C,KAC1D,MAAOA,EAAI,QAAU,EACvB,EAAE,CACJ,CAcA,MAAM,aACJC,EACAI,EACoC,CACpC,GAAM,CAAE,MAAAC,CAAM,EAAI,MAAM,KAAK,oBAAoBL,CAAW,EACtDM,EACJF,GAAaA,EAAU,OAAS,EAAI,IAAI,IAAIA,EAAU,IAAKG,GAAMA,EAAE,YAAY,CAAC,CAAC,EAAI,KACvF,OACEF,EAAM,KACHG,GACCA,EAAE,WACDF,IAAY,MAASE,EAAE,UAAY,MAAQF,EAAQ,IAAIE,EAAE,QAAQ,YAAY,CAAC,EACnF,GAAK,IAET,CAcA,MAAM,eAAyC,CAK7C,QAJa,MAAM,KAAK,QACtB,MACA,wBACF,GACa,UAAY,CAAC,GAAG,IAAKT,IAAS,CACzC,OAAQ,OAAOA,EAAI,SAAW,EAAE,EAChC,SAAWA,EAAI,WAA2C,KAC1D,gBAAkBA,EAAI,kBAAkD,KACxE,WAAYA,EAAI,cAAgB,GAChC,YAAa,MAAM,QAAQA,EAAI,UAAU,EAAIA,EAAI,WAAa,CAAC,GAAG,IAAKS,GAAM,CAC3E,IAAMC,EAAKD,EACX,MAAO,CACL,YAAa,OAAOC,EAAG,cAAgB,EAAE,EACzC,OAAQ,OAAOA,EAAG,QAAU,EAAE,EAC9B,gBAAiBA,EAAG,mBAAqB,GACzC,MAAOA,EAAG,QAAU,GACpB,YAAcA,EAAG,cAA8C,KAC/D,WAAaA,EAAG,cAA8C,IAChE,CACF,CAAC,CACH,EAAE,CACJ,CAmBA,MAAM,eAAeT,EAAqBU,EAAwC,CAChF,MAAM,KAAK,QAAiB,OAAQ,8BAA+B,CACjE,KAAM,CAAE,aAAcV,EAAa,gBAAiBU,CAAe,CACrE,CAAC,CACH,CAqBA,MAAM,kBACJV,EACAW,EAAiD,CAAC,EAChC,CAClB,IAAMnB,EACJmB,EAAQ,gBAAkB,OACtB,CAAC,EACD,CAAE,eAAgB,CAAC,GAAGA,EAAQ,aAAa,CAAE,EACnD,OAAO,KAAK,QACV,OACA,yBAAyB,mBAAmBX,CAAW,CAAC,aACxD,CAAE,KAAAR,CAAK,CACT,CACF,CAUA,MAAM,mBAAmBQ,EAAuC,CAC9D,OAAO,KAAK,QACV,OACA,yBAAyB,mBAAmBA,CAAW,CAAC,cACxD,CAAE,KAAM,CAAC,CAAE,CACb,CACF,CAWA,MAAM,WAAqD,CAKzD,OAJa,MAAM,KAAK,QACtB,MACA,oBACF,GACY,MAAQ,IACtB,CAEA,SAASW,EAAgC,CACvC,IAAMC,EAAS,IAAI,gBAAgB,CACjC,SAAUD,EAAQ,QAClB,WAAYA,EAAQ,SACtB,CAAC,EACGA,EAAQ,aACVC,EAAO,IAAI,eAAgBD,EAAQ,WAAW,EAEhD,OAAO,KAAK,GAAG,KAAK,OAAO,cAAcC,EAAO,SAAS,CAAC,EAAE,CAC9D,CAEA,UAAUD,EAAiC,CACzC,IAAMC,EAAS,IAAI,gBAAgB,CACjC,SAAUD,EAAQ,QAClB,WAAYA,EAAQ,SACtB,CAAC,EACGA,EAAQ,aACVC,EAAO,IAAI,eAAgBD,EAAQ,WAAW,EAEhD,OAAO,KAAK,GAAG,KAAK,OAAO,eAAeC,EAAO,SAAS,CAAC,EAAE,CAC/D,CAuBA,MAAM,YAAYnB,EAA+C,CAC/D,IAAMC,EAAO,MAAMC,EAAsBF,CAAU,EAC7C,CAAE,MAAAG,CAAM,EAAI,MAAM,KAAK,wBAAwB,EACrD,OAAO,KAAK,kBAAkB,CAC5B,UAAWF,EAAK,UAChB,cAAeA,EAAK,UACpB,MAAAE,EACA,UAAW,MAAMiB,EAAgB,CAAE,WAAApB,EAAY,MAAAG,CAAM,CAAC,CACxD,CAAC,CACH,CAeA,MAAM,yBAAyD,CAC7D,IAAMJ,EAAO,MAAM,KAAK,QAGrB,MAAO,kCAAkC,EAC5C,MAAO,CAAE,MAAOA,EAAK,MAAO,YAAaA,EAAK,eAAiB,IAAK,CACtE,CAWA,MAAM,kBAAkBM,EAAoD,CAC1E,IAAMN,EAAO,MAAM,KAAK,QACtB,OACA,yBACA,CACE,KAAM,CACJ,WAAYM,EAAM,UAClB,gBAAiBA,EAAM,cACvB,MAAOA,EAAM,MACb,UAAWA,EAAM,SACnB,CACF,CACF,EACA,MAAO,CACL,UAAW,OAAON,EAAK,YAAcM,EAAM,SAAS,EACpD,SAAWN,EAAK,WAA2C,KAC3D,cAAeA,EAAK,iBAAmB,EACzC,CACF,CAqBA,OAAO,mBAAmBmB,EAOH,CACrB,IAAMG,EAASH,EAAQ,UAAU,QAAQ,OAAQ,EAAE,EAC7CI,EAAQ,IAAI,gBAAgB,CAChC,eAAgBJ,EAAQ,UACxB,eAAgBA,EAAQ,WAC1B,CAAC,EACD,MAAO,CAAE,IAAK,GAAGG,CAAM,KAAKC,EAAM,SAAS,CAAC,GAAI,UAAWJ,EAAQ,SAAU,CAC/E,CAUA,OAAO,wBAAwBK,EAAoC,CACjE,IAAMC,EAASD,IAAS,OAAO,OAAW,IAAc,GAAK,OAAO,SAAS,MAC7E,GAAI,CAACC,EAAQ,MAAO,CAAC,EACrB,IAAIC,EACJ,GAAI,CACFA,EAAO,IAAI,IAAID,CAAM,EAAE,IACzB,MAAQ,CACN,MAAO,CAAC,CACV,CACA,IAAML,EAAS,IAAI,gBAAgBM,EAAK,QAAQ,KAAM,EAAE,CAAC,EACnDC,EAAQP,EAAO,IAAI,OAAO,EAC1BT,EAAYS,EAAO,IAAI,SAAS,EAChCQ,EAAQR,EAAO,IAAI,OAAO,EAC1BS,EAA2B,CAAC,EAClC,OAAIF,IAAOE,EAAI,MAAQF,GACnBhB,IAAWkB,EAAI,UAAYlB,GAC3BiB,IAAOC,EAAI,MAAQD,GAChBC,CACT,CAmBA,MAAM,qBAAqBV,EAIG,CAC5B,IAAMjB,EAAO,MAAMC,EAAsBgB,EAAQ,UAAU,EACrDnB,EAAO,MAAM,KAAK,QACtB,OACA,2BACA,CACE,KAAM,CACJ,MAAOmB,EAAQ,MACf,gBAAiBjB,EAAK,UACtB,UAAW,MAAMmB,EAAgB,CAC/B,WAAYF,EAAQ,WACpB,MAAOA,EAAQ,KACjB,CAAC,CACH,EACA,UAAW,EACb,CACF,EACA,MAAO,CACL,UAAW,OAAOnB,EAAK,YAAcE,EAAK,SAAS,EACnD,SAAWF,EAAK,WAA2C,KAC3D,cAAeA,EAAK,iBAAmB,EACzC,CACF,CAGA,MAAM,eAA8C,CAClD,IAAMA,EAAO,MAAM,KAAK,QAGrB,MAAO,wBAAwB,EAClC,MAAO,CACL,UAAWA,EAAK,UAAY,CAAC,GAAG,IAAKO,IAAS,CAC5C,UAAW,OAAOA,EAAI,YAAc,EAAE,EACtC,SAAWA,EAAI,WAA2C,KAC1D,oBAAqBA,EAAI,wBAA0B,EACrD,EAAE,EACF,MAAOP,EAAK,OAAS,IACvB,CACF,CAEQ,aAAaA,EAIJ,CACf,YAAK,QAAU,CACb,aAAcA,EAAK,cACnB,UAAWA,EAAK,WAChB,KAAMA,EAAK,IACb,EACK,KAAK,cAAc,KAAK,OAAO,EAC7B,KAAK,OACd,CAEA,MAAc,cAAc8B,EAA6C,CACvE,GAAI,CACF,MAAM,KAAK,YAAYA,CAAO,CAChC,MAAQ,CAER,CACF,CAEA,MAAc,QACZC,EACAC,EACAb,EAMI,CAAC,EACO,CACZ,GAAI,CAACA,EAAQ,WAAa,CAAC,KAAK,QAC9B,MAAM,IAAI,MACR,sGACF,EAMF,IAAMc,EAAkC,CACtC,GAAGd,EAAQ,QACX,OAAQ,kBACV,EACIA,EAAQ,OAAS,SAAWc,EAAQ,cAAc,EAAI,oBACtD,CAACd,EAAQ,WAAa,KAAK,UAC7Bc,EAAQ,cAAgB,UAAU,KAAK,QAAQ,YAAY,IAG7D,IAAMC,EAAa,IAAI,gBACjBC,EAAQ,WAAW,IAAMD,EAAW,MAAM,EAAG,KAAK,SAAS,EAC7DE,EACJ,GAAI,CACFA,EAAW,MAAM,KAAK,UAAU,GAAG,KAAK,OAAO,GAAGJ,CAAI,GAAI,CACxD,OAAAD,EACA,QAAAE,EACA,KAAMd,EAAQ,OAAS,OAAY,OAAY,KAAK,UAAUA,EAAQ,IAAI,EAC1E,OAAQe,EAAW,MACrB,CAAC,CACH,OAASG,EAAK,CAIZ,MAAM,IAAIC,EACR,EACAD,aAAe,MAAQA,EAAI,KAAO,eAClC,GAAG,KAAK,OAAO,GAAGL,CAAI,GACtB,IAAI,QACJK,aAAe,MAAQA,EAAI,QAAU,OAAOA,CAAG,CACjD,CACF,QAAE,CACA,aAAaF,CAAK,CACpB,CAIA,GAFA,KAAK,oBAAoBC,CAAQ,EAE7B,CAACA,EAAS,GAAI,CAChB,IAAMG,EAAW,MAAMH,EAAS,KAAK,EAAE,MAAM,IAAG,EAAY,EAC5D,MAAM,IAAIE,EACRF,EAAS,OACTA,EAAS,WACT,GAAG,KAAK,OAAO,GAAGJ,CAAI,GACtBI,EAAS,QACTG,CACF,CACF,CAEA,GAAI,EAAApB,EAAQ,QAAU,QAAUiB,EAAS,SAAW,KACpD,OAAQ,MAAMA,EAAS,KAAK,CAC9B,CAaQ,oBAAoBA,EAA0B,CACpD,IAAMI,EAAQJ,EAAS,QAAQ,IAAI5C,EAAc,EACjD,GAAI,CAACgD,GAAS,CAAC,KAAK,QAAS,OAC7B,IAAMC,EAAaL,EAAS,QAAQ,IAAI3C,EAAsB,EACxDiD,EAAY,OAAOD,CAAU,EACnC,KAAK,QAAU,CACb,aAAcD,EACd,UAAW,OAAO,SAASE,CAAS,GAAKA,EAAY,EAAIA,EAAY,KAAK,QAAQ,UAClF,KAAM,KAAK,QAAQ,IACrB,EACK,KAAK,cAAc,KAAK,OAAO,CACtC,CACF,EAaO,SAASC,GAA+BC,EAA2B,CACxE,IAAMC,EACJD,GAAU,OAAOA,GAAW,SACtBA,EAA8B,MAAQA,EACxC,OACN,GAAI,CAACC,GAAU,OAAOA,GAAW,SAAU,MAAO,CAAC,EACnD,IAAMC,EAAUD,EAAqC,YAC/CE,EAAYF,EAAuC,cACzD,GAAI,OAAOC,GAAW,UAAY,CAACA,GAAU,CAAC,MAAM,QAAQC,CAAQ,EAAG,MAAO,CAAC,EAC/E,IAAMC,EAAS,QAAQF,CAAM,GACvBjB,EAAgB,CAAC,EACvB,QAAWoB,KAASF,EAAU,CAC5B,GAAI,OAAOE,GAAU,SAAU,SAC/B,IAAMC,EAAOD,EAAM,KAAK,EACxB,GAAI,CAACC,EAAK,WAAW,GAAG,EAAG,SAC3B,IAAMC,EAAWD,EAAK,SAASF,CAAM,EAAIE,EAAO,GAAGA,CAAI,GAAGF,CAAM,GAC3DnB,EAAI,SAASsB,CAAQ,GAAGtB,EAAI,KAAKsB,CAAQ,CAChD,CACA,OAAOtB,CACT,CCxsCA,SAASuB,GACPC,EAC2B,CAC3B,GAAKA,EACL,OAAO,OAAOA,GAAW,WAAaA,EAASC,GAAkBD,CAAM,CACzE,CAqFA,IAAME,GAAoB,wBAQ1B,SAASC,IAAyC,CAEhD,IAAMC,EADO,WAAsE,WAChE,MACnB,OAAOA,GAAS,OAAOA,EAAM,SAAY,WAAaA,EAAQ,IAChE,CAGA,SAASC,GAAiBC,EAAeC,EAA4B,CACnE,GAAI,CACF,IAAMC,EAAQF,EAAM,MAAM,GAAG,EAC7B,GAAIE,EAAM,SAAW,EAAG,CAEtB,IAAIC,EAAMD,EAAM,CAAC,EAAE,QAAQ,KAAM,GAAG,EAAE,QAAQ,KAAM,GAAG,EACvD,KAAOC,EAAI,OAAS,GAAGA,GAAO,IAC9B,IAAMC,EAAU,KAAK,MAAM,KAAKD,CAAG,CAAC,EACpC,GAAI,OAAOC,EAAQ,KAAQ,SACzB,OAAOA,EAAQ,IAAM,GAEzB,CACF,MAAQ,CAER,CACA,OAAOH,CACT,CAKO,IAAMI,GAAN,KAAa,CAelB,YAAYX,EAAsB,CAVlC,KAAQ,UAA8B,KACtC,KAAQ,eAA4C,KAEpD,KAAQ,UAA8B,KACtC,KAAQ,UAA8B,KACtC,KAAQ,SAA4B,KACpC,KAAQ,SAAW,GAKjB,KAAK,OAAS,CACZ,UAAW,IACX,GAAGA,CACL,EAEA,KAAK,WAAaA,EAAO,YAAc,KAGnC,KAAK,aACP,KAAK,UAAY,KAAK,WAAW,UAAU,GAK7C,IAAMY,EAAU,OAAO,OAAW,KAAe,wBAAyB,OAIpEC,EAAWd,GAAmB,KAAK,OAAO,YAAY,EAE5D,KAAK,WAAae,EAAwB,CACxC,QAAS,KAAK,OAAO,QACrB,MAAO,KAAK,OAAO,MACnB,SAAAD,EACA,aAAc,UACE,MAAM,KAAK,cAAc,IACzB,cAAgB,GAEhC,aAAc,UACM,MAAM,KAAK,oBAAoB,GAChC,aAEnB,eAAgB,MAAOE,GAAqB,CACtC,KAAK,YACP,KAAK,UAAU,aAAeA,EAC9B,KAAK,YAAY,UAAU,KAAK,SAAS,EAE7C,EACA,cAAe,SAAY,CAIzB,KAAK,WAAW,EAChB,GAAI,CACF,MAAM,KAAK,OAAO,gBAAgB,CACpC,MAAQ,CAER,CACF,EACA,UAAW,KAAK,OAAO,UACvB,YAAa,KAAK,OAAO,qBAAuBH,EAAU,OAAS,OACrE,CAAC,EAGD,KAAK,WAAaI,GAAkC,KAAK,WAAY,CACnE,QAAS,KAAK,OAAO,QACrB,aAAc,UACE,MAAM,KAAK,cAAc,IACzB,cAAgB,GAEhC,UAAW,KAAK,OAAO,SACzB,CAAC,EAED,KAAK,YAAcC,GAAmC,KAAK,WAAY,CACrE,QAAS,KAAK,OAAO,QACrB,aAAc,UACE,MAAM,KAAK,cAAc,IACzB,cAAgB,GAEhC,UAAW,KAAK,OAAO,SACzB,CAAC,CACH,CAKA,IAAI,MAAsB,CACxB,OAAO,KAAK,UACd,CAKA,IAAI,OAAwB,CAC1B,OAAO,KAAK,WACd,CAKA,IAAI,KAAiB,CACnB,OAAK,KAAK,YACR,KAAK,UAAY,IAAIC,EAAU,CAAE,WAAY,KAAK,UAAW,CAAC,GAEzD,KAAK,SACd,CAKA,IAAI,QAAoB,CACtB,OAAK,KAAK,YACR,KAAK,UAAY,IAAIC,EAAU,CAC7B,QAAS,KAAK,OAAO,QACrB,MAAO,KAAK,OAAO,MACnB,aAAc,UACE,MAAM,KAAK,cAAc,IACzB,cAAgB,EAElC,CAAC,GAEI,KAAK,SACd,CAQA,IAAI,WAA6B,CAC/B,OAAK,KAAK,kBACR,KAAK,gBAAkB,IAAIC,EAAgB,CACzC,WAAY,KAAK,WACjB,IAAK,KAAK,MACZ,CAAC,GAEI,KAAK,eACd,CAYA,IAAI,OAAqB,CACvB,OAAK,KAAK,cACR,KAAK,YAAc,IAAIC,EAAY,KAAK,OAAO,OAAS,CAAC,CAAC,GAErD,KAAK,WACd,CAMA,IAAI,IAAe,CACjB,OAAK,KAAK,WACR,KAAK,SAAW,GAChB,QAAQ,KAAK,2EAA2E,GAErF,KAAK,WACR,KAAK,SAAW,IAAIC,EAAS,CAC3B,QAAS,KAAK,OAAO,QACrB,aAAc,UACE,MAAM,KAAK,cAAc,IACzB,cAAgB,EAElC,CAAC,GAEI,KAAK,QACd,CAMA,MAAM,aAAaC,EAGI,CACrB,IAAMC,EAAQD,GAAe,KAAK,OAAO,YACzC,GAAI,CAACC,EACH,MAAM,IAAI,MAAM,4CAA4C,EAG9D,GAAI,CACF,IAAMC,EAAc,CAClB,YAAa,gBACb,WAAYD,EAAM,SAClB,YAAa,cACb,YAAa,CAAC,OAAO,EACrB,UAAW,KAAK,MAAM,KAAK,IAAI,EAAI,GAAI,EACvC,cAAe,CACb,SAAUA,EAAM,SAChB,SAAUA,EAAM,QAClB,CACF,EAEME,EAAW,MAAM,KAAK,WAAW,eAAeD,CAAW,EAE3DE,EAAcD,EAAS,KAAK,aAClC,YAAK,UAAY,CACf,aAAcC,EACd,cAAeD,EAAS,KAAK,cAC7B,WAAYrB,GAAiBsB,EAAa,KAAK,IAAI,EAAI,IAAQ,CACjE,EAEA,KAAK,YAAY,UAAU,KAAK,SAAS,EAElC,KAAK,SACd,OAASC,EAAO,CACd,MAAM,IAAI,MACR,0BAA0BA,aAAiB,MAAQA,EAAM,QAAU,eAAe,EACpF,CACF,CACF,CAQA,MAAc,eAA2C,CACvD,OAAO,KAAK,SACd,CAgBA,MAAc,qBAA0C,CACtD,GAAI,KAAK,eACP,OAAO,KAAK,eAKd,IAAMC,EAAwB,KAAK,WAAW,aAExCC,EAAU,KAAK,gBAAgB,IACnC,KAAK,0BAA0BD,CAAqB,CACtD,EACA,KAAK,eAAiBC,EAEtB,GAAI,CACF,OAAO,MAAMA,CACf,QAAE,CACI,KAAK,iBAAmBA,IAC1B,KAAK,eAAiB,KAE1B,CACF,CAGQ,gBAAmBC,EAAkC,CAC3D,IAAM3B,EAAQD,GAAe,EAC7B,OAAOC,EAAQA,EAAM,QAAQF,GAAmB6B,CAAE,EAAIA,EAAG,CAC3D,CAKA,MAAc,0BACZF,EACoB,CAGpB,IAAMG,EAAS,KAAK,YAAY,UAAU,GAAK,KAC/C,GAAIA,GAAQ,gBACV,KAAK,UAAYA,EAEbH,GAAyBG,EAAO,eAAiBH,GAGnD,OAAOG,EAIX,GAAI,CAAC,KAAK,WAAW,cACnB,MAAM,IAAI,MAAM,4BAA4B,EAG9C,GAAI,CACF,IAAMN,EAAW,MAAM,KAAK,WAAW,aAAa,CAClD,aAAc,KAAK,UAAU,aAC7B,cAAe,KAAK,UAAU,aAChC,CAAC,EAEKC,EAAcD,EAAS,KAAK,aAClC,YAAK,UAAY,CACf,aAAcC,EAGd,cAAeD,EAAS,KAAK,cAC7B,WAAYrB,GAAiBsB,EAAa,KAAK,IAAI,EAAI,IAAQ,CACjE,EAEA,KAAK,YAAY,UAAU,KAAK,SAAS,EAElC,KAAK,SACd,OAASC,EAAO,CAKd,MAAM,IAAI,MACR,yBAAyBA,aAAiB,MAAQA,EAAM,QAAU,eAAe,EACnF,CACF,CACF,CAKO,YAAmB,CACxB,KAAK,eAAiB,KACtB,KAAK,UAAY,KACjB,KAAK,YAAY,MAAM,CACzB,CAKO,iBAA2B,CAChC,OAAO,KAAK,YAAc,IAC5B,CAOO,aAAaK,EAAuB,CACzC,IAAMC,EAAYD,EAAK,YAAc5B,GAAiB4B,EAAK,aAAc,KAAK,IAAI,EAAI,IAAQ,EAC9F,KAAK,UAAY,CAAE,GAAGA,EAAM,WAAYC,CAAU,EAClD,KAAK,YAAY,UAAU,KAAK,SAAS,CAC3C,CAKO,cAAiC,CACtC,OAAO,KAAK,SACd,CAKO,OAAc,CACnB,KAAK,WAAW,MAAM,EACtB,KAAK,UAAU,MAAM,CACvB,CAKA,OAAO,kBAAkBC,EAAwC,CAC/D,OAAOC,GAAkBD,CAAG,CAC9B,CAKA,OAAO,kBAAkBE,EAAiBC,EAAgC,CACxE,OAAOC,GAAkBF,EAASC,CAAI,CACxC,CACF,EAKO,SAASE,GAAaxC,EAA8B,CACzD,OAAO,IAAIW,GAAOX,CAAM,CAC1B,CC/hBO,IAAMyC,GAAN,KAA6C,CAA7C,cACL,KAAQ,OAA2B,KAEnC,WAA8B,CAC5B,OAAO,KAAK,MACd,CAEA,UAAUC,EAAuB,CAC/B,KAAK,OAASA,CAChB,CAEA,OAAc,CACZ,KAAK,OAAS,IAChB,CACF,EAEMC,GAAc,cAEPC,GAAN,KAAmD,CAGxD,YAAYC,EAAcF,GAAa,CACrC,KAAK,IAAME,CACb,CAEA,WAA8B,CAC5B,GAAI,CACF,GAAI,OAAO,aAAiB,IAAa,OAAO,KAChD,IAAMC,EAAM,aAAa,QAAQ,KAAK,GAAG,EACzC,GAAI,CAACA,EAAK,OAAO,KACjB,IAAMC,EAAS,KAAK,MAAMD,CAAG,EAC7B,OAAIC,GAAUA,EAAO,cAAgBA,EAAO,cACnC,CACL,aAAcA,EAAO,aACrB,cAAeA,EAAO,cACtB,WAAY,OAAOA,EAAO,YAAe,SAAWA,EAAO,WAAa,KAAK,IAAI,EAAI,IACvF,EAEK,IACT,MAAQ,CACN,OAAO,IACT,CACF,CAEA,UAAUL,EAAuB,CAC/B,GAAI,CACF,GAAI,OAAO,aAAiB,IAAa,OACzC,aAAa,QAAQ,KAAK,IAAK,KAAK,UAAUA,CAAI,CAAC,CACrD,MAAQ,CAER,CACF,CAEA,OAAc,CACZ,GAAI,CACF,GAAI,OAAO,aAAiB,IAAa,OACzC,aAAa,WAAW,KAAK,GAAG,CAClC,MAAQ,CAER,CACF,CACF,EC5BA,IAAMM,GAAc,IAAI,YAAY,EAAE,OAAO,qBAAqB,EAC5DC,GAAgB,IAAI,YAAY,EAAE,OAAO,4BAA4B,EAMrEC,GAAkB,GAGlBC,GAAe,KAAK,OAAO,EAAE,EA8FnC,eAAsBC,GACpBC,EACAC,EACqB,CACrB,IAAMC,EAAO,IAAI,YAAY,EAAE,OAAO,KAAK,UAAUD,CAAQ,CAAC,EAC9D,OAAOE,EAAWP,GAAe,CAAC,IAAI,YAAY,EAAE,OAAOI,CAAM,EAAGE,CAAI,CAAC,CAC3E,CAUA,eAAsBE,GAAYC,EAAsC,CACtE,IAAMC,EAAUC,EAAQF,EAAM,QAAS,UAAW,EAAE,EAC9CG,EAAgBD,EAAQF,EAAM,cAAe,gBAAiB,EAAE,EAChEI,EAAWF,EAAQF,EAAM,SAAU,WAAY,EAAE,EACjDK,EAAaH,EACjBF,EAAM,YAAcP,GACpB,aACA,EACF,EACMa,EAAeC,GAAWP,EAAM,aAAc,cAAc,EAC5DQ,EAAkBD,GAAWP,EAAM,gBAAiB,iBAAiB,EAErES,EAAM,MAAMC,EAAiBV,EAAM,YAAY,EAC/CW,EAAY,MAAMC,EAAgBZ,EAAM,YAAY,EAEpDL,EAAS,IAAI,YAAY,EAAE,OAAOK,EAAM,MAAM,EAC9Ca,EAAa,MAAMnB,GAAWM,EAAM,OAAQA,EAAM,QAAQ,EAC1Dc,EAAQC,EAAMf,EAAM,KAAK,EACzBgB,EAAWD,EAAMf,EAAM,QAAQ,EAO/BiB,EAAW,MAAMnB,EAAWR,GAAa,CAC7CW,EACAE,EACAQ,EACAP,EACAC,EACAV,EACAkB,EACAE,EAAMT,EAAa,MAAM,EACzB,GAAGA,EACHS,EAAMP,EAAgB,MAAM,EAC5B,GAAGA,EACHM,EACAE,CACF,CAAC,EAEKE,EAAY,IAAI,WACpB,MAAM,OAAO,OAAO,KAAK,CAAE,KAAM,SAAU,EAAGT,EAAKQ,CAAQ,CAC7D,EAIA,OAAOE,EACLC,EACEnB,EACAE,EACAQ,EACAP,EACAC,EACAgB,EAAM1B,EAAO,MAAM,EACnBA,EACAkB,EACAQ,EAAMf,EAAa,MAAM,EACzB,GAAGA,EACHe,EAAMb,EAAgB,MAAM,EAC5B,GAAGA,EACHM,EACAE,EACAE,CACF,CACF,CACF,CAGA,SAASX,GAAWe,EAA6BC,EAA6B,CAC5E,IAAMC,EAAOF,GAAS,CAAC,EACvB,GAAIE,EAAK,OAAShC,GAChB,MAAM,IAAI,MACR,GAAG+B,CAAK,UAAUC,EAAK,MAAM,oBAAoBhC,EAAe,iBAClE,EAEF,OAAOgC,EAAK,IAAI,CAACC,EAAMC,IAAMxB,EAAQuB,EAAM,GAAGF,CAAK,IAAIG,CAAC,IAAK,EAAE,CAAC,CAClE,CC3MA,IAAMC,GAAsB,IA8EfC,GAAN,cAAiC,KAAM,CAG5C,YAEkBC,EAEAC,EACAC,EAChB,CACA,MAAM,kCAAkCA,CAAM,MAAMF,CAAM,EAAE,EAL5C,YAAAA,EAEA,eAAAC,EACA,YAAAC,EAPlB,UAAO,oBAUP,CACF,EAEaC,EAAN,KAAkB,CAOvB,YAAYC,EAA2B,CACrC,KAAK,OAASA,EACd,KAAK,QAAUA,EAAO,SAAS,QAAQ,OAAQ,EAAE,EACjD,KAAK,gBAAkBA,EAAO,gBAC9B,IAAMC,EAAWD,EAAO,MACxB,KAAK,UAAYC,EACb,CAACC,EAA0BC,IAAuBF,EAASC,EAAOC,CAAI,EACtE,CAACD,EAA0BC,IAAuB,WAAW,MAAMD,EAAOC,CAAI,EAClF,KAAK,UAAYH,EAAO,WAAa,GACvC,CASA,MAAM,SAASI,EAA8C,CAC3D,IAAMC,EAAO,MAAM,KAAK,KAErB,MAAO,uBAAuB,mBAAmBD,CAAS,CAAC,UAAU,EAGxE,YAAK,gBAAkBC,EAAK,KAAK,gBAC1BA,EAAK,IACd,CASA,MAAM,QACJD,EACAE,EACAC,EAAoB,CAAC,EACK,CAC1B,IAAMC,EAAW,KAAK,kBAAoB,MAAM,KAAK,SAASJ,CAAS,GAAG,gBAEpEK,EAAQ,MAAM,KAAK,OAAO,OAAO,KAAK,EACtCC,EAAM,KAAK,OAAO,YAAchB,GAChCiB,EAAU,MAAMC,GAAY,CAChC,QAASR,EACT,cAAe,KAAK,OAAO,cAC3B,SAAAI,EACA,OAAAF,EACA,SAAAC,EACA,MAAAE,EACA,SAAU,OAAO,KAAK,MAAM,KAAK,IAAI,EAAI,GAAI,CAAC,EAAI,OAAOC,CAAG,EAC5D,aAAc,KAAK,OAAO,YAC5B,CAAC,EAEKL,EAAO,MAAM,KAAK,KACtB,OACA,uBAAuB,mBAAmBD,CAAS,CAAC,WACpD,CAAE,OAAAE,EAAQ,SAAAC,EAAU,QAAAI,EAAS,YAAa,KAAK,OAAO,WAAY,CACpE,EACA,MAAO,CAAE,SAAUN,EAAK,KAAK,SAAU,QAASA,EAAK,KAAK,SAAW,IAAK,CAC5E,CAEA,MAAc,KAAQC,EAAgBO,EAAcR,EAA4B,CAC9E,IAAMS,EAAkC,CAAE,OAAQ,kBAAmB,EACjET,IAAS,SAAWS,EAAQ,cAAc,EAAI,oBAElD,IAAMC,EAAa,IAAI,gBACjBC,EAAQ,WAAW,IAAMD,EAAW,MAAM,EAAG,KAAK,SAAS,EAC7DE,EACJ,GAAI,CACFA,EAAW,MAAM,KAAK,UAAU,GAAG,KAAK,OAAO,GAAGJ,CAAI,GAAI,CACxD,OAAAP,EACA,QAAAQ,EACA,KAAMT,IAAS,OAAY,OAAY,KAAK,UAAUA,CAAI,EAC1D,OAAQU,EAAW,MACrB,CAAC,CACH,OAASG,EAAK,CACZ,MAAM,IAAIC,EACR,EACAD,aAAe,MAAQA,EAAI,KAAO,eAClC,GAAG,KAAK,OAAO,GAAGL,CAAI,GACtB,IAAI,QACJK,aAAe,MAAQA,EAAI,QAAU,OAAOA,CAAG,CACjD,CACF,QAAE,CACA,aAAaF,CAAK,CACpB,CAEA,GAAI,CAACC,EAAS,GAAI,CAChB,IAAMG,EAAO,MAAMH,EAAS,KAAK,EAAE,MAAM,IAAM,EAAE,EAKjD,GAAIA,EAAS,SAAW,KAAOA,EAAS,SAAW,IAAK,CACtD,IAAMrB,EAASyB,GAAcD,CAAI,EACjC,MAAM,IAAIzB,GAAmBC,EAAQ,SAAS,KAAKA,CAAM,EAAGqB,EAAS,MAAM,CAC7E,CACA,MAAM,IAAIE,EACRF,EAAS,OACTA,EAAS,WACT,GAAG,KAAK,OAAO,GAAGJ,CAAI,GACtBI,EAAS,QACTG,CACF,CACF,CAEA,OAAQ,MAAMH,EAAS,KAAK,CAC9B,CACF,EAGA,SAASI,GAAcC,EAA0B,CAC/C,GAAI,CAACA,EAAU,MAAO,kBACtB,GAAI,CACF,IAAMC,EAAS,KAAK,MAAMD,CAAQ,EAClC,QAAWE,IAAa,CAACD,EAAO,MAAOA,EAAO,OAAO,EACnD,GAAI,OAAOC,GAAc,UAAYA,EAAW,OAAOA,CAE3D,MAAQ,CAER,CACA,OAAOF,CACT,CC5NO,SAASG,GAAwBC,EAAyB,EAAgB,CAC/E,IAAIC,EAAO,OAAOD,CAAK,EACvB,GAAIC,EAAO,GACT,MAAM,IAAI,MAAM,4BAA4B,EAE9C,MAAO,CACL,MAAwB,CACtB,IAAMC,EAAQD,EACd,OAAAA,GAAQ,GACD,QAAQ,QAAQC,CAAK,CAC9B,CACF,CACF,CAqBO,SAASC,GACdC,EACAC,EACa,CACb,IAAMC,EACJD,GAAY,WAA4D,aAC1E,GAAI,CAACC,EACH,MAAM,IAAI,MACR,2IACF,EAEF,MAAO,CACL,MAAwB,CACtB,IAAMC,EAAMD,EAAM,QAAQF,CAAG,EAOzBI,EAAU,GACd,GAAID,IAAQ,MAAQA,IAAQ,GAC1B,GAAI,CACF,IAAME,EAAS,OAAOF,CAAG,EACrBE,EAAS,KAAID,EAAUC,EAC7B,MAAQ,CACND,EAAU,EACZ,CAEF,IAAMN,EAAQM,EAAU,GACxB,OAAAF,EAAM,QAAQF,EAAKF,EAAM,SAAS,CAAC,EAC5B,QAAQ,QAAQA,CAAK,CAC9B,CACF,CACF,CC1FA,IAAMQ,GAAgB,+BAChBC,GAAS,GACTC,GAAU,GAEVC,GAAsB,IAAI,WAAW,CACzC,GAAM,GAAM,EAAM,EAAM,EAAM,GAAM,EAAM,EAAM,EAAM,GAAM,IAAM,IAAM,EACxE,GAAM,EAAM,EACd,CAAC,EAGYC,GAAN,MAAMC,CAAe,CAMlB,YAAYC,EAAe,CAHnC,KAAQ,EAAsB,KAC9B,KAAQ,EAAI,EAGV,KAAK,GAAKA,EACV,KAAK,EAAIA,CACX,CAEA,aAAa,YAAsC,CAEjD,IAAMC,EAAO,IAAI,YAAY,EAAE,OAAOP,EAAa,EAC7CM,EAAI,IAAI,WAAW,EAAE,EAC3B,OAAAA,EAAE,IAAIC,CAAI,EACH,IAAIF,EAAeC,CAAC,CAC7B,CAEA,MAAM,QAAQE,EAAiC,CAC7C,KAAK,EAAI,MAAMC,GAAOC,EAAO,KAAK,EAAGF,CAAI,CAAC,CAC5C,CAEA,MAAM,OAAOG,EAA6C,CACxD,GAAM,CAACC,EAAIC,CAAG,EAAI,MAAMC,GAAM,KAAK,GAAIH,CAAgB,EACvD,KAAK,GAAKC,EACV,KAAK,EAAI,MAAM,OAAO,OAAO,UAAU,MAAOC,EAAK,UAAW,GAAO,CAAC,UAAW,SAAS,CAAC,EAC3F,KAAK,EAAI,CACX,CAEA,MAAM,eAAeE,EAA4C,CAC/D,GAAI,CAAC,KAAK,EAAG,MAAM,IAAI,MAAM,0BAA0B,EACvD,IAAMC,EAAa,IAAI,WACrB,MAAM,OAAO,OAAO,QAClB,CAAE,KAAM,UAAW,GAAIC,GAAW,KAAK,CAAC,EAAG,eAAgB,KAAK,CAAE,EAClE,KAAK,EACLF,CACF,CACF,EACA,YAAK,GAAK,EACV,MAAM,KAAK,QAAQC,CAAU,EACtBA,CACT,CAEA,MAAM,eAAeA,EAA6C,CAChE,GAAI,CAAC,KAAK,EAAG,MAAM,IAAI,MAAM,0BAA0B,EACvD,IAAMD,EAAY,IAAI,WACpB,MAAM,OAAO,OAAO,QAClB,CAAE,KAAM,UAAW,GAAIE,GAAW,KAAK,CAAC,EAAG,eAAgB,KAAK,CAAE,EAClE,KAAK,EACLD,CACF,CACF,EACA,YAAK,GAAK,EACV,MAAM,KAAK,QAAQA,CAAU,EACtBD,CACT,CAGA,OAA2C,CACzC,OAAOD,GAAM,KAAK,GAAI,IAAI,UAAY,CACxC,CACF,EAWA,eAAsBI,GACpBC,EACAC,EACAC,EAIC,CACD,IAAMC,EAAQ,MAAMlB,GAAe,WAAW,EAC9C,MAAMkB,EAAM,QAAQF,CAAQ,EAE5B,MAAME,EAAM,QAAQH,CAAY,EAEhC,IAAMI,EAAY,MAAMC,GAAcH,CAAc,EACpD,aAAMC,EAAM,QAAQC,EAAU,SAAS,EACvC,MAAMD,EAAM,OAAO,MAAMG,GAAGF,EAAU,WAAYJ,CAAY,CAAC,EAGxD,CACL,SAHeT,EAAOa,EAAU,UAAW,MAAMD,EAAM,eAAe,IAAI,UAAY,CAAC,EAIvF,MAAM,OAAOI,EAAU,CACrB,GAAIA,EAAS,OAASzB,GAASC,GAAS,MAAM,IAAI,MAAM,+BAA+B,EAEvF,IAAMyB,EAAkBD,EAAS,MAAM,EAAGzB,EAAM,EAChD,aAAMqB,EAAM,QAAQK,CAAe,EACnC,MAAML,EAAM,OAAO,MAAMG,GAAGF,EAAU,WAAYI,CAAe,CAAC,EAE3D,CAAE,QADO,MAAML,EAAM,eAAeI,EAAS,MAAMzB,EAAM,CAAC,EAC/C,MAAO,MAAMqB,EAAM,MAAM,CAAE,CAC/C,CACF,CACF,CAEA,eAAsBE,GAAcI,EAA6C,CAC/E,GAAI,CAACA,EAAQ,CACX,IAAIC,EACJ,GAAI,CACFA,EAAQ,MAAM,OAAO,OAAO,YAAY,CAAE,KAAM,QAAS,EAAG,GAAM,CAChE,YACF,CAAC,CACH,OAASC,EAAO,CACd,MAAM,IAAI,MACR,2EACOA,GAAiB,MAAQ,kBAAkB,gHAEpD,CACF,CACA,IAAMC,EAAY,IAAI,WAAW,MAAM,OAAO,OAAO,UAAU,MAAOF,EAAK,SAAS,CAAC,EACrF,MAAO,CAAE,WAAYA,EAAK,WAAY,UAAAE,CAAU,CAClD,CACA,IAAMC,EAAa,MAAM,OAAO,OAAO,UACrC,QACAtB,EAAOP,GAAqByB,CAAM,EAClC,CAAE,KAAM,QAAS,EACjB,GACA,CAAC,YAAY,CACf,EACMK,EAAM,MAAM,OAAO,OAAO,UAAU,MAAOD,CAAU,EAC3D,MAAO,CAAE,WAAAA,EAAY,UAAWE,GAAgBD,EAAI,CAAW,CAAE,CACnE,CAEA,eAAsBR,GAAGO,EAAuBD,EAA4C,CAC1F,IAAMI,EAAO,MAAM,OAAO,OAAO,UAAU,MAAOJ,EAAW,CAAE,KAAM,QAAS,EAAG,GAAO,CAAC,CAAC,EAC1F,OAAO,IAAI,WACT,MAAM,OAAO,OAAO,WAAW,CAAE,KAAM,SAAU,OAAQI,CAAK,EAA0BH,EAAY,GAAG,CACzG,CACF,CAGA,SAASf,GAAW,EAAuB,CACzC,IAAMmB,EAAQ,IAAI,WAAW,EAAE,EAC/B,WAAI,SAASA,EAAM,MAAM,EAAE,aAAa,EAAG,OAAO,CAAC,EAAG,EAAK,EACpDA,CACT,CAEA,eAAe3B,GAAOD,EAAuC,CAC3D,OAAO,IAAI,WAAW,MAAM,OAAO,OAAO,OAAO,UAAWA,CAAI,CAAC,CACnE,CAEA,eAAe6B,GAAWxB,EAAiBL,EAAuC,CAChF,IAAM8B,EAAU,MAAM,OAAO,OAAO,UAAU,MAAOzB,EAAK,CAAE,KAAM,OAAQ,KAAM,SAAU,EAAG,GAAO,CAClG,MACF,CAAC,EACD,OAAO,IAAI,WAAW,MAAM,OAAO,OAAO,KAAK,OAAQyB,EAAS9B,CAAI,CAAC,CACvE,CAGA,eAAeM,GAAMyB,EAAyB5B,EAAiE,CAC7G,IAAM6B,EAAO,MAAMH,GAAWE,EAAa5B,CAAgB,EACrD8B,EAAQ,MAAMJ,GAAWG,EAAM,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC,EAClDE,EAAS,MAAML,GAAWG,EAAM9B,EAAO+B,EAAO,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,EACxE,MAAO,CAACA,EAAOC,CAAM,CACvB,CAEA,SAASR,GAAgBS,EAA2B,CAClD,IAAMC,EAASD,EAAM,QAAQ,KAAM,GAAG,EAAE,QAAQ,KAAM,GAAG,EACnDE,EAAS,KAAKD,EAAS,IAAI,QAAQ,EAAKA,EAAO,OAAS,GAAM,CAAC,CAAC,EACtE,OAAO,WAAW,KAAKC,EAASC,GAASA,EAAK,WAAW,CAAC,CAAC,CAC7D,CCvJO,IAAMC,GAAiB,uBAEjBC,GAAc,aAEdC,EAAsB,kCAE7BC,GAAU,EACVC,GAAW,IAAI,YAAY,EAAE,OAAO,yBAAyB,EAC7DC,EAAiB,GACjBC,GAAa,EAAID,EAAiB,EAClCE,GAAa,EACbC,GAAa,EACbC,GAAY,EAMZC,GAAgB,EAAI,GAAK,KAAO,GAEhCC,GAAsB,KAEtBC,GAAkB,IAClBC,GAA2B,uCAC3BC,GAAqB,CAAC,IAAK,IAAK,IAAK,GAAG,EAExCC,GAAO,IAAI,YAGJC,EAAN,cAAqC,KAAM,CAGhD,aAAc,CACZ,MAAM,uFAAuF,EAH/F,UAAO,wBAIP,CACF,EAGaC,EAAN,cAAmC,KAAM,CAG9C,YACSC,EACAC,EACPC,EAEOC,EACP,CACA,MAAMD,CAAO,EANN,YAAAF,EACA,UAAAC,EAGA,kBAAAE,EAPT,UAAO,sBAUP,CACF,EAQA,eAAsBC,GACpBC,EACAC,EACqB,CACrB,IAAMC,EAAS,MAAM,OAAO,OAAO,OACjC,UACAC,EAAOX,GAAK,OAAOF,EAAwB,EAAGU,EAAOC,CAAY,CACnE,EACA,OAAO,IAAI,WAAWC,CAAM,CAC9B,CAiCA,eAAsBE,GACpBC,EACAC,EACAC,EAAgE,CAAC,EAC5C,CACrB,GAAKA,EAAQ,gBAAkB,SAAgBA,EAAQ,kBAAoB,QACzE,MAAM,IAAI,MAAM,oEAAoE,EAEtF,IAAMC,EAAQC,EAAI,OAAO,gBAAgB,IAAI,WAAW,EAAE,CAAC,CAAC,EACtDC,EAAW,MAAML,EAAM,UAAU,CACrC,MAAAG,EACA,cAAeD,EAAQ,cACvB,iBAAkB,GAClB,GAAID,EAAO,kBAAoB,CAAE,kBAAmB,EAAK,EAAI,CAAC,CAChE,CAAC,EACD,GAAI,CAACI,EAAS,mBACZ,MAAM,IAAI,MAAM,uEAAuE,EAEzF,IAAMT,EAAeU,EAAQD,EAAS,mBAAoB,qBAAsB,EAAE,EAC5EV,EAAQO,EAAQ,gBAClBI,EAAQJ,EAAQ,gBAAiB,kBAAmB,EAAE,EACtD,IAAI,WAAW,EAAE,EACfK,EAAmBH,EAAI,MAAMV,GAAoBC,EAAOC,CAAY,CAAC,EAO3E,GAAI,CANa,MAAMK,EAAO,CAC5B,SAAUI,EAAS,SACnB,MAAAF,EACA,iBAAAI,EACA,WAAYF,EAAS,UACvB,CAAC,EAEC,MAAM,IAAI,MAAM,qEAAqE,EAEvF,OAAOT,CACT,CAkCO,SAASY,GAA0BN,EAAmD,CAC3F,IAAMO,EAAYP,EAAQ,QAAU,CAACQ,EAAOC,IAAS,MAAMD,EAAOC,CAAI,GAChEC,EAAY,GAAGV,EAAQ,QAAQ,QAAQ,OAAQ,EAAE,CAAC,wBAClDF,EAAQ,CACZ,MAAM,UAAUa,EAA2D,CACzE,IAAMC,EAAW,MAAML,EAAUG,EAAW,CAC1C,OAAQ,OACR,QAAS,CAAE,eAAgB,kBAAmB,EAC9C,KAAM,KAAK,UAAUC,CAAO,CAC9B,CAAC,EACD,GAAI,CAACC,EAAS,GACZ,MAAM,IAAI,MAAM,oCAAoCA,EAAS,MAAM,EAAE,EAEvE,IAAMC,EAAQ,MAAMD,EAAS,KAAK,EAClC,GAAI,CAACC,EAAK,KAAM,MAAM,IAAI,MAAM,uCAAuC,EACvE,OAAOA,EAAK,IACd,CACF,EACA,OAAOC,GAAkB,CACvB,QAASd,EAAQ,QACjB,mBAAoB,IAClBH,GAA0BC,EAAOE,EAAQ,OAAQ,CAC/C,cAAeA,EAAQ,cACvB,gBAAiBA,EAAQ,eAC3B,CAAC,EACH,MAAOO,CACT,CAAC,CACH,CAwBO,SAASO,GAAkBd,EAA2C,CAC3E,IAAMe,EAAO,IAAI,IAAIf,EAAQ,OAAO,EAC9BgB,EAAWD,EAAK,SAAS,QAAQ,OAAQ,EAAE,EAC3CE,EAAU,GAAGF,EAAK,MAAM,GAAGC,CAAQ,GACnCT,EAAYP,EAAQ,QAAU,CAACQ,EAAOC,IAAS,MAAMD,EAAOC,CAAI,GAChES,EACJ,OAAOlB,EAAQ,oBAAuB,WAAaA,EAAQ,mBAAqB,OAC9EN,EAAoCwB,EACpC,QAAQ,QAAQ,EAAE,KAAKA,CAAM,EAC7B,QAAQ,QAAQlB,EAAQ,kBAAgC,EAI5DN,EAAa,MAAM,IAAG,EAAY,EAClC,IAAIyB,EAEEC,EAAc,SAA8B,CAChD,GAAI,CACF,OAAO,MAAMC,GAAUd,EAAWU,EAAS,MAAMK,GAAW5B,CAAY,CAAC,CAC3E,OAAS6B,EAAO,CACd,GAAI,EAAEA,aAAiBrC,IAA2B,CAACgC,EAAQ,MAAMK,EACjE,OAAA7B,EAAe,QAAQ,QAAQ,EAAE,KAAKwB,CAAM,EACrCG,GAAUd,EAAWU,EAAS,MAAMK,GAAW5B,CAAY,CAAC,CACrE,CACF,EAEM8B,EAAiB,SAAkD,CACvE,IAAMC,EAAUN,GAAWO,EAAM,MAAS,EACpCC,EAAO,MAAMF,EACnB,GAAI,KAAK,IAAI,EAAIE,EAAK,QAAS,MAAO,CAACA,EAAMF,CAAO,EACpD,IAAMG,EAAUF,EAAMD,CAAO,EAC7B,MAAO,CAAC,MAAMG,EAASA,CAAO,CAChC,EAGMF,EAASG,GAA0D,CACvE,GAAIV,IAAYU,EAAO,CACrB,IAAMC,EAAUV,EAAY,EAC5BD,EAAUW,EACVA,EAAQ,MAAM,IAAM,CACdX,IAAYW,IAASX,EAAU,OACrC,CAAC,CACH,CACA,OAAOA,CACT,EAEA,MAAO,OAAOX,EAA0BC,IAA0C,CAChF,IAAME,EAAU,IAAI,QAAQH,EAAOC,CAAI,EACjCsB,EAAM,IAAI,IAAIpB,EAAQ,GAAG,EAC/B,GAAIoB,EAAI,SAAWhB,EAAK,QAAU,CAACgB,EAAI,SAAS,WAAW,GAAGf,CAAQ,GAAG,EACvE,MAAM,IAAI,MAAM,oBAAoBe,EAAI,IAAI,+BAA+B/B,EAAQ,OAAO,EAAE,EAE9F,IAAMgC,EAAmC,CAAC,EAC1CrB,EAAQ,QAAQ,QAAQ,CAACsB,EAAOC,IAASF,EAAQ,KAAK,CAACE,EAAMD,CAAK,CAAC,CAAC,EACpE,IAAME,EAAO,CAAE,OAAQxB,EAAQ,OAAQ,KAAMoB,EAAI,SAAS,MAAMf,EAAS,MAAM,EAAIe,EAAI,OAAQ,QAAAC,CAAQ,EACjGnB,EAAO,IAAI,WAAW,MAAMF,EAAQ,YAAY,CAAC,EAEjDyB,EAAO,MAAOT,GAAqC,CACvD,GAAM,CAAE,UAAAU,EAAW,OAAAC,GAAQ,SAAAC,EAAS,EAAI,MAAMC,GAAYb,EAAMQ,EAAMtB,CAAI,EACpED,GAAW,MAAML,EAAU,GAAGU,CAAO,GAAG9C,EAAW,GAAI,CAC3D,OAAQ,OACR,QAAS,CAAE,eAAgBC,CAAoB,EAC/C,KAAMmE,GACN,OAAQ5B,EAAQ,MAClB,CAAC,EACD,GAAIC,GAAS,QAAQ,IAAI,cAAc,IAAMxC,EAC3C,MAAM,MAAMqE,GAAgB7B,EAAQ,EAEtC,OAAO8B,GAAa9B,GAAUe,EAAK,YAAaW,GAAQD,CAAS,CACnE,EAEM,CAACV,EAAMF,CAAO,EAAI,MAAMD,EAAe,EAC7C,GAAI,CACF,OAAO,MAAMY,EAAKT,CAAI,CACxB,OAASJ,EAAO,CAGd,GAAI,EAAEA,aAAiBpC,IAAyBoC,EAAM,OAAS,kBAAmB,MAAMA,EACxF,OAAOa,EAAK,MAAMV,EAAMD,CAAO,CAAC,CAClC,CACF,CACF,CAEA,eAAeH,GAAWqB,EAA+C,CACvE,IAAMC,EAAQ,MAAMD,EACpB,GAAIC,EAAM,SAAW,GAAI,MAAM,IAAI,MAAM,qCAAqC,EAC9E,OAAOA,CACT,CAUA,eAAevB,GACbd,EACAU,EACAvB,EACkB,CAClB,GAAI,CACF,OAAO,MAAMmD,GAActC,EAAWU,EAASvB,CAAY,CAC7D,OAAS6B,EAAO,CAGd,GAAI,EAAEA,aAAiBpC,IAAyBoC,EAAM,OAAS,OAAQ,MAAMA,EAC7E,aAAM,IAAI,QAASuB,GAAY,WAAWA,EAASvB,EAAM,cAAgB,GAAI,CAAC,EACvEsB,GAActC,EAAWU,EAASvB,CAAY,CACvD,CACF,CAEA,eAAemD,GACbtC,EACAU,EACAvB,EACkB,CAClB,IAAMqD,EAAQ,MAAMC,GAAStD,EAAcpB,EAAQ,EAC7CsC,EAAW,MAAML,EAAU,GAAGU,CAAO,GAAG/C,EAAc,GAAI,CAC9D,OAAQ,OACR,QAAS,CAAE,eAAgBE,CAAoB,EAC/C,KAAMwB,EAAO,IAAI,WAAW,CAACvB,EAAO,CAAC,EAAGqB,EAAcqD,EAAM,QAAQ,CACtE,CAAC,EACD,GAAInC,EAAS,QAAQ,IAAI,cAAc,IAAMxC,EAC3C,MAAM,MAAMqE,GAAgB7B,CAAQ,EAEtC,IAAMqC,EAAQ,MAAMC,GAAWtC,EAAU/B,EAAmB,EAC5D,OAAOsE,GAAYJ,EAAOE,CAAK,CACjC,CAGA,eAAsBE,GACpBJ,EACAE,EACkB,CAClB,GAAIA,EAAM,CAAC,IAAM5E,GAAS,MAAM,IAAI,MAAM,kCAAkC,EAC5E,IAAI+E,EACJ,GAAI,CACFA,EAAW,MAAML,EAAM,OAAOE,EAAM,MAAM,CAAC,CAAC,CAC9C,MAAQ,CACN,MAAM,IAAI,MAAM,0EAA0E,CAC5F,CACA,GAAM,CAAE,QAAAI,EAAS,MAAAC,CAAM,EAAIF,EAC3B,GAAIC,EAAQ,SAAW9E,EAAiB,EAAG,MAAM,IAAI,MAAM,kCAAkC,EAC7F,IAAMgF,EAAYF,EAAQ,MAAM,EAAG9E,CAAc,EAC3CiF,EAAe,IAAI,SAASH,EAAQ,OAAQA,EAAQ,UAAU,EAAE,UAAU9E,EAAgB,EAAK,EAC/F,CAACkF,EAAYC,CAAW,EAAI,MAAM,QAAQ,IAAI,CAClD,OAAO,OAAO,UAAU,MAAOJ,EAAM,CAAC,EAAG,UAAW,GAAO,CAAC,SAAS,CAAC,EACtE,OAAO,OAAO,UAAU,MAAOA,EAAM,CAAC,EAAG,UAAW,GAAO,CAAC,SAAS,CAAC,CACxE,CAAC,EACD,OAAAA,EAAM,CAAC,EAAE,KAAK,CAAC,EACfA,EAAM,CAAC,EAAE,KAAK,CAAC,EACR,CACL,WAAAG,EACA,YAAAC,EACA,cAAe,EACf,QAAS,KAAK,IAAI,EAAIF,EAAe,IAAO1E,GAC5C,OAAOuD,EAAW,CAChB,IAAMC,EAAS,IAAI,WAAW9D,EAAU,EACxC,OAAA8D,EAAO,CAAC,EAAIjE,GACZiE,EAAO,IAAIiB,EAAW,CAAC,EACvB,IAAI,SAASjB,EAAO,MAAM,EAAE,aAAa,EAAI/D,EAAgB,OAAO8D,CAAS,EAAG,EAAK,EAC9EC,CACT,CACF,CACF,CAGA,eAAsBE,GACpBrB,EACAgB,EACAtB,EAC0E,CAC1E,IAAMwB,EAAYlB,EAAQ,cAC1BA,EAAQ,eAAiB,EACzB,IAAMmB,EAASnB,EAAQ,OAAOkB,CAAS,EACjCsB,EAAS,MAAM,OAAO,OAAO,QACjC,CAAE,KAAM,UAAW,GAAI1D,GAAMoC,EAAW,CAAC,EAAG,eAAgBC,CAAO,EACnEnB,EAAQ,WACRyC,GAAUzB,EAAMtB,CAAI,CACtB,EACA,MAAO,CAAE,UAAAwB,EAAW,OAAAC,EAAQ,SAAU1C,EAAO0C,EAAQ,IAAI,WAAWqB,CAAM,CAAC,CAAE,CAC/E,CAGA,SAAS1D,GAAMoC,EAAmBwB,EAA2B,CAC3D,IAAMjB,EAAQ,IAAI,WAAW,EAAE,EACzBkB,EAAO,IAAI,SAASlB,EAAM,MAAM,EACtC,OAAAkB,EAAK,aAAa,EAAG,OAAOzB,CAAS,EAAG,EAAK,EAC7CyB,EAAK,UAAU,EAAGD,EAAO,EAAK,EACvBjB,CACT,CAaA,SAASgB,GAAUzB,EAAmBtB,EAA8B,CAClE,IAAMkD,EAAU9E,GAAK,OAAO,KAAK,UAAUkD,CAAI,CAAC,EAC1C6B,EAAM,IAAI,WAAW,CAAC,EAC5B,WAAI,SAASA,EAAI,MAAM,EAAE,UAAU,EAAGD,EAAQ,OAAQ,EAAK,EACpDnE,EAAOoE,EAAKD,EAASlD,CAAI,CAClC,CAQA,eAAsB6B,GACpB9B,EACA8C,EACApB,EACAD,EACmB,CACnB,GAAI,CAACzB,EAAS,KAAM,MAAM,IAAI,MAAM,iCAAiC,EACrE,IAAMqD,EAASC,GAAYtD,EAAS,KAAK,UAAU,CAAC,EAChDiD,EAAQ,EACNM,EAAO,SAAgE,CAC3E,IAAMR,EAAS,MAAMM,EAAO,KAAK,EACjC,GAAI,CAACN,EAAQ,OAAO,KACpB,IAAIS,EACJ,GAAI,CACFA,EAAQ,IAAI,WACV,MAAM,OAAO,OAAO,QAClB,CAAE,KAAM,UAAW,GAAInE,GAAMoC,EAAWwB,CAAK,EAAG,eAAgBvB,CAAO,EACvEoB,EACAC,CACF,CACF,CACF,MAAQ,CACN,MAAM,IAAI,MAAM,8EAA8E,CAChG,CACA,OAAAE,GAAS,EACF,CAAE,KAAMO,EAAM,CAAC,EAAG,KAAMA,EAAM,MAAM,CAAC,CAAE,CAChD,EAEMC,EAAQ,MAAMF,EAAK,EACzB,GAAI,CAACE,GAASA,EAAM,OAAS5F,GAAY,MAAM,IAAI,MAAM,kCAAkC,EAC3F,IAAM0D,EAAO,KAAK,MAAM,IAAI,YAAY,EAAE,OAAOkC,EAAM,IAAI,CAAC,EAC5D,GAAIrF,GAAmB,SAASmD,EAAK,MAAM,EACzC,aAAM8B,EAAO,OAAO,EACb,IAAI,SAAS,KAAM,CAAE,OAAQ9B,EAAK,OAAQ,QAASA,EAAK,OAAQ,CAAC,EAG1E,IAAMtB,EAAO,IAAI,eAA2B,CAC1C,MAAM,KAAKyD,EAAY,CACrB,GAAI,CACF,IAAMC,EAAQ,MAAMJ,EAAK,EACpBI,EAEMA,EAAM,OAAS7F,GACxB4F,EAAW,QAAQC,EAAM,IAAI,EACpBA,EAAM,OAAS5F,IACxB2F,EAAW,MAAM,EACjB,MAAML,EAAO,OAAO,GAEpBK,EAAW,MAAM,IAAI,MAAM,kCAAkC,CAAC,EAP9DA,EAAW,MAAM,IAAI,MAAM,mCAAmC,CAAC,CASnE,OAAS/C,EAAO,CACd+C,EAAW,MAAM/C,CAAK,CACxB,CACF,EACA,QAAS,CACP,OAAO0C,EAAO,OAAO,CACvB,CACF,CAAC,EACD,OAAO,IAAI,SAASpD,EAAM,CAAE,OAAQsB,EAAK,OAAQ,QAASA,EAAK,OAAQ,CAAC,CAC1E,CAGA,SAAS+B,GAAYM,EAGnB,CACA,IAAIC,EAAS,IAAI,WACXC,EAAO,MAAOC,GAAqC,CACvD,KAAOF,EAAO,OAASE,GAAQ,CAC7B,GAAM,CAAE,KAAAC,EAAM,MAAA3C,CAAM,EAAI,MAAMuC,EAAO,KAAK,EAC1C,GAAII,EAAM,MAAO,GACjBH,EAAS7E,EAAO6E,EAAQxC,CAAK,CAC/B,CACA,MAAO,EACT,EACA,MAAO,CACL,MAAM,MAAO,CACX,GAAI,CAAE,MAAMyC,EAAK,CAAC,EAAI,OAAO,KAC7B,IAAMC,EAAS,IAAI,SAASF,EAAO,OAAQA,EAAO,UAAU,EAAE,UAAU,EAAG,EAAK,EAChF,GAAIE,EAAS/F,GACX,YAAM4F,EAAO,OAAO,EACd,IAAI,MAAM,yEAAyE,EAE3F,GAAI,CAAE,MAAME,EAAK,EAAIC,CAAM,EAAI,OAAO,KACtC,IAAMJ,EAAQE,EAAO,MAAM,EAAG,EAAIE,CAAM,EACxC,OAAAF,EAASA,EAAO,MAAM,EAAIE,CAAM,EACzBJ,CACT,EACA,OAAQ,IAAMC,EAAO,OAAO,CAC9B,CACF,CAGA,eAAetB,GAAWtC,EAAoBiE,EAAoC,CAChF,GAAI,CAACjE,EAAS,KAAM,OAAO,IAAI,WAC/B,IAAM4D,EAAS5D,EAAS,KAAK,UAAU,EACnCgC,EAAQ,IAAI,WAChB,OAAS,CACP,GAAM,CAAE,KAAAgC,EAAM,MAAA3C,CAAM,EAAI,MAAMuC,EAAO,KAAK,EAC1C,GAAII,EAAM,OAAOhC,EAEjB,GADAA,EAAQhD,EAAOgD,EAAOX,CAAK,EACvBW,EAAM,OAASiC,EACjB,YAAML,EAAO,OAAO,EACd,IAAI,MAAM,kDAAkD,CAEtE,CACF,CAEA,eAAe/B,GAAgB7B,EAAoC,CACjE,IAAIvB,EACAC,EAAU,gCAAgCsB,EAAS,MAAM,GAC7D,GAAI,CACF,IAAMC,EAAQ,MAAMD,EAAS,KAAK,EAClCvB,EAAOwB,GAAM,OAAO,KAChBA,GAAM,OAAO,UAASvB,EAAU,2BAA2BuB,EAAK,MAAM,OAAO,GACnF,MAAQ,CAGR,CACA,GAAIxB,IAAS,sBAAuB,OAAO,IAAIH,EAC/C,IAAM4F,EAAa,OAAOlE,EAAS,QAAQ,IAAI,aAAa,CAAC,EACvDrB,EACJ,OAAO,SAASuF,CAAU,GAAKA,EAAa,EAAI,KAAK,IAAIA,EAAY,CAAC,EAAI,IAAO,OACnF,OAAO,IAAI3F,EAAqByB,EAAS,OAAQvB,EAAMC,EAASC,CAAY,CAC9E,CC9dA,eAAsBwF,GAAaC,EAAwD,CACzF,GAAI,CAACA,EAAQ,cAAgB,CAACA,EAAQ,cACpC,MAAM,IAAI,MAAM,6DAA6D,EAG/E,IAAMC,EAAQ,IAAIC,EAAY,CAC5B,aAAcF,EAAQ,aACtB,aAAcA,EAAQ,aACtB,UAAWA,EAAQ,UACnB,MAAOA,EAAQ,MACf,UAAWA,EAAQ,SACrB,CAAC,EAEG,CAACA,EAAQ,cAAgBA,EAAQ,eACnC,MAAMC,EAAM,iBAAiBD,EAAQ,aAAa,EAGpD,IAAMG,EAAa,MAAMF,EAAM,gBAAgB,EACzCG,EAAYC,GAAcF,EAAYH,EAAQ,WAAW,EAMzDM,EAAS,MAAML,EAAM,mBAAmBG,EAAU,WAAW,EAC7DG,EACJD,EAAO,KAAME,GAAMA,EAAE,iBAAmBA,EAAE,WAAa,MAAQA,EAAE,kBAAoB,IAAI,GACzF,KACF,GAAI,CAACD,EACH,MAAM,IAAI,MAAME,GAAeL,EAAWE,CAAM,CAAC,EAGnD,IAAMI,EAAQ,IAAIC,EAAY,CAI5B,SAAUJ,EAAU,SACpB,gBAAiBA,EAAU,gBAC3B,cAAeP,EAAQ,cACvB,YAAaA,EAAQ,YACrB,aAAcA,EAAQ,aACtB,OAAQA,EAAQ,QAAW,MAAMY,GAAmBZ,EAAQ,YAAY,EACxE,WAAYA,EAAQ,WAGpB,MAAOA,EAAQ,KACXa,GAA0B,CACxB,QAASN,EAAU,SACnB,OAAQP,EAAQ,KAChB,MAAOA,EAAQ,KACjB,CAAC,EACDA,EAAQ,MACZ,UAAWA,EAAQ,SACrB,CAAC,EAED,MAAO,CACL,MAAAC,EACA,MAAAS,EACA,YAAaN,EAAU,YACvB,UAAAA,EACA,UAAAG,EACA,QAAS,CAACO,EAAWC,EAAQC,IAAaN,EAAM,QAAQI,EAAWC,EAAQC,CAAQ,CACrF,CACF,CAGA,SAASX,GAAcF,EAA8Bc,EAAoC,CACvF,GAAIA,EAAW,CACb,IAAMC,EAAQf,EAAW,KAAMgB,GAAMA,EAAE,cAAgBF,CAAS,EAChE,GAAI,CAACC,EACH,MAAM,IAAI,MACR,kCAAkCD,CAAS,cACzCd,EAAW,IAAKgB,GAAMA,EAAE,WAAW,EAAE,KAAK,IAAI,GAAK,QACrD,EACF,EAEF,OAAOD,CACT,CACA,GAAIf,EAAW,SAAW,EACxB,MAAM,IAAI,MACR,4GACF,EAEF,GAAIA,EAAW,OAAS,EACtB,MAAM,IAAI,MACR,qBAAqBA,EAAW,MAAM,qDAAgDA,EACnF,IAAKgB,GAAMA,EAAE,WAAW,EACxB,KAAK,IAAI,CAAC,EACf,EAEF,OAAOhB,EAAW,CAAC,CACrB,CASA,SAASM,GAAeL,EAA2BE,EAA8B,CAC/E,GAAIA,EAAO,SAAW,EACpB,OAAOF,EAAU,WAAa,UAC1B,aAAaA,EAAU,WAAW,gFAClC,aAAaA,EAAU,WAAW,iFAExC,IAAMgB,EAAiBd,EAAO,OAAQ,GAAM,CAAC,EAAE,eAAe,EAC9D,OAAIc,EAAe,SAAWd,EAAO,OAQjC,4BAA4BF,EAAU,WAAW,sGAE/CgB,EAAe,IAAK,GAAM,EAAE,iBAAmB,EAAE,MAAM,EAAE,KAAK,IAAI,GAAK,mBACzE,qLAIG,aAAahB,EAAU,WAAW,QAAQE,EAAO,MAAM,0FAChE,CAaA,eAAeM,GAAmBS,EAA4C,CAC5E,IAAMC,EAAM,yBAAyBC,EAAI,MAAMC,EAAgBH,CAAY,CAAC,CAAC,GAC7E,GAAI,CACF,OAAOI,GAA8BH,CAAG,CAC1C,MAAQ,CACN,OAAOI,GAAwB,CAAC,CAClC,CACF,CCxKA,IAAMC,GAAkB,iBA0BjB,SAASC,GAAoBC,EAA8C,CAChF,IAAMC,EAAcC,EAAaF,EAAQ,YAAa,aAAa,EACnE,GAAIC,EAAY,SAAW,EACzB,MAAM,IAAI,MAAM,0DAA0D,EAE5E,IAAME,EAAc,CAClBD,EAAaF,EAAQ,cAAgB,CAAC,EAAG,cAAc,EACvDE,EAAaF,EAAQ,cAAgB,CAAC,EAAG,cAAc,EACvDE,EAAaF,EAAQ,cAAgB,CAAC,EAAG,cAAc,EACvDE,EAAaF,EAAQ,cAAgB,CAAC,EAAG,cAAc,CACzD,EACMI,EAAkBJ,EAAQ,oBAAsB,CAAC,UAAU,EAC3DK,EAAML,EAAQ,KAAO,KAAK,IA0ChC,OAAO,OAAO,OAxCG,MAAO,CACtB,SAAAM,EACA,MAAAC,EACA,iBAAAC,EACA,WAAAC,CACF,IAA6D,CAC3D,IAAMC,EAAQC,GAAWL,CAAQ,EAC3BM,EAAUH,GAAe,MAAMT,EAAQ,kBAAkBU,CAAK,EACpE,GAAI,CAACE,EACH,MAAM,IAAI,MACR,uFACF,EAGF,IAAIC,EACJ,GAAI,CACFA,EAAWb,EAAQ,WAAWU,EAAOE,EAAS,KAAK,MAAMP,EAAI,EAAI,GAAI,CAAC,CACxE,OAASS,EAAO,CACd,MAAM,IAAI,MAAM,6BAA8BA,GAAiB,SAAW,OAAOA,CAAK,CAAC,EAAE,CAC3F,CAEA,GAAID,EAAS,SAAW,WAAa,CAACT,EAAgB,SAASS,EAAS,MAAM,EAC5E,MAAM,IAAI,MAAM,gCAAgCA,EAAS,MAAM,yBAAyB,EAE1F,IAAME,EAAKC,GAASH,EAAS,MAAM,EACnC,GAAI,CAACZ,EAAY,SAASgB,EAAIF,EAAG,IAAI,CAAC,EACpC,MAAM,IAAI,MAAM,QAAQE,EAAIF,EAAG,IAAI,CAAC,uCAAuC,EAS7E,GAPkB,CAACA,EAAG,MAAOA,EAAG,MAAOA,EAAG,MAAOA,EAAG,KAAK,EAC/C,QAAQ,CAACG,EAAUC,IAAU,CACrC,IAAMC,EAAUjB,EAAYgB,CAAK,EACjC,GAAIC,EAAQ,OAAS,GAAK,CAACA,EAAQ,SAASH,EAAIC,CAAQ,CAAC,EACvD,MAAM,IAAI,MAAM,OAAOC,CAAK,IAAIF,EAAIC,CAAQ,CAAC,kCAAkC,CAEnF,CAAC,EACGD,EAAIF,EAAG,UAAU,IAAM,GAAGR,CAAK,GAAGC,CAAgB,GAAG,YAAY,EACnE,MAAM,IAAI,MAAM,qDAAqD,EAEvE,MAAO,EACT,EAC+B,CAAE,kBAAmB,EAAc,CAAC,CACrE,CAEA,SAASN,EAAamB,EAAkBC,EAAyB,CAC/D,OAAOD,EAAO,IAAKE,GAAU,CAC3B,IAAMC,EAAaD,EAAM,KAAK,EAAE,YAAY,EAC5C,GAAI,CAACzB,GAAgB,KAAK0B,CAAU,EAClC,MAAM,IAAI,MAAM,GAAGF,CAAK,UAAU,KAAK,UAAUC,CAAK,CAAC,0CAA0C,EAEnG,OAAOC,CACT,CAAC,CACH,CAGA,SAASR,GAASS,EAAgD,CAChE,IAAMC,EAAOD,EAAO,KACpB,GAAIA,EAAO,OAAS,QAAUC,EAAM,OAAOA,EAC3C,GAAID,EAAO,OAAS,QAAUC,GAAM,KAAM,OAAOA,EAAK,KACtD,MAAM,IAAI,MAAM,kBAAkBD,EAAO,IAAI,uBAAuB,CACtE,CAEA,SAASd,GAAWY,EAA2B,CAC7C,IAAMI,EAAS,KAAKJ,CAAK,EACnBK,EAAQ,IAAI,WAAWD,EAAO,MAAM,EAC1C,QAASE,EAAI,EAAGA,EAAIF,EAAO,OAAQE,GAAK,EAAGD,EAAMC,CAAC,EAAIF,EAAO,WAAWE,CAAC,EACzE,OAAOD,CACT,CCpLO,IAAME,GAAqC,EAG5CC,GAAwB,IAAI,YAAY,EAAE,OAAO,uBAAuB,EAGxEC,GAAoB,EAOpBC,GAAU,CACd,aAAc,EACd,gBAAiB,EACjB,aAAc,EACd,aAAc,EACd,cAAe,EACf,aAAc,EACd,YAAa,EACb,iBAAkB,EAClB,eAAgB,EAChB,iBAAkB,EAClB,8BAA+B,EACjC,EAGA,SAASC,GAAQC,EAA0BC,EAA2B,CACpE,GAAID,EAAM,SAAW,GACnB,MAAM,IAAI,MAAM,GAAGC,CAAK,0BAA0BD,EAAM,MAAM,EAAE,EAElE,OAAO,IAAI,WAAWA,CAAK,CAC7B,CASA,SAASE,GACPF,EACAC,EACY,CACZ,GAAID,GAAU,KACZ,OAAO,IAAI,WAAW,CAAC,CAAC,CAAC,EAE3B,GAAIA,EAAM,SAAW,GACnB,MAAM,IAAI,MAAM,GAAGC,CAAK,0BAA0BD,EAAM,MAAM,EAAE,EAElE,OAAOG,EAAO,IAAI,WAAW,CAAC,CAAC,CAAC,EAAG,IAAI,WAAWH,CAAK,CAAC,CAC1D,CAGA,SAASI,GAAYJ,EAA2B,CAC9C,IAAMK,EAAQ,IAAI,YAAY,EAAE,OAAOL,CAAK,EAC5C,OAAOG,EAAOG,EAAMD,EAAM,MAAM,EAAGA,CAAK,CAC1C,CAaO,SAASE,GAAuBC,EAA+C,CACpF,IAAMC,EAAOD,EAAO,WACdE,EAAYD,EAAK,WAAa,CAAC,EAC/BE,EAAgBH,EAAO,gBAAkB,CAAC,EAEhD,OAAOL,EAMLJ,GAAQU,EAAK,iBAAkB,kBAAkB,EACjDV,GAAQU,EAAK,SAAU,UAAU,EACjCG,EAAMH,EAAK,oBAAoB,EAC/BV,GAAQU,EAAK,YAAa,aAAa,EACvC,IAAI,WAAW,CAACA,EAAK,YAAY,CAAC,EAClCH,EAAMI,EAAU,MAAM,EACtB,GAAGA,EAAU,IAAI,CAACG,EAAG,IAAMC,EAAQD,EAAG,aAAa,CAAC,IAAK,EAAE,CAAC,EAI5DT,GAAYI,EAAO,iBAAiB,EACpCA,EAAO,gBACHL,EAAO,IAAI,WAAW,CAAC,CAAC,CAAC,EAAGW,EAAQN,EAAO,gBAAiB,kBAAmB,EAAE,CAAC,EAClF,IAAI,WAAW,CAAC,CAAC,CAAC,EAMtBF,EAAMK,EAAc,MAAM,EAC1B,GAAGA,EAAc,IAAIP,EAAW,EAChCF,GAAgBM,EAAO,eAAgB,gBAAgB,EAKvDN,GAAgBM,EAAO,QAAS,SAAS,CAC3C,CACF,CA4CA,eAAsBO,GAAiBC,EAA6C,CAClF,IAAMC,EAAUD,EAAM,gBAAkB,CAAC,EACnCE,EAAaJ,EAAQE,EAAM,WAAY,aAAcA,EAAM,WAAW,OAAS,CAAC,EAOhFG,EAAU,OAAOH,EAAM,WAAW,WAAW,sBAAwB,CAAC,IAAM,GAC5EI,EACJJ,EAAM,UAAY,OAAO,KAAK,MAAM,KAAK,IAAI,EAAI,GAAI,CAAC,EAElDK,EAASF,EACXhB,EACE,IAAI,WAAW,CAACL,GAAQ,cAAc,CAAC,EACvCgB,EAAQE,EAAM,OAAQ,SAAU,EAAE,EAClCT,GAAuBS,EAAM,UAAU,EACvCJ,EAAMQ,CAAQ,EACdF,CACF,EACAf,EACE,IAAI,WAAW,CAACL,GAAQ,YAAY,CAAC,EACrCgB,EAAQE,EAAM,OAAQ,SAAU,EAAE,EAClCT,GAAuBS,EAAM,UAAU,EACvCE,CACF,EAEEI,EAAKnB,EAAO,IAAI,WAAW,CAACN,EAAiB,CAAC,EAAGwB,CAAM,EAEvDE,EAAM,MAAMC,EAAiBR,EAAM,YAAY,EAC/CS,EAAS,MAAMC,EAAgBV,EAAM,YAAY,EAEjDW,EAAWxB,EACf,IAAI,WAAW,CAACR,EAAkC,CAAC,EACnDmB,EAAQE,EAAM,YAAa,cAAe,EAAE,EAC5CV,EAAMW,EAAQ,MAAM,EACpB,GAAGA,EAAQ,IAAI,CAACW,EAAGC,IAAMf,EAAQc,EAAG,kBAAkBC,CAAC,IAAK,EAAE,CAAC,EAC/DJ,EACAb,EAAMI,EAAM,KAAK,EACjBM,CACF,EAEMQ,EAAY,IAAI,WACpB,MAAM,OAAO,OAAO,KAAK,UAAWP,EAAKpB,EAAOP,GAAuB+B,CAAQ,CAAC,CAClF,EAeMI,EAAa,IAAI,WAAW,CAAC,CAAC,CAAC,EAErC,OAAOC,EAAI7B,EAAOwB,EAAUG,EAAWC,CAAU,CAAC,CACpD,CCxMA,IAAME,GAAc,IAAI,YAAY,EAAE,OAAO,wBAAwB,EAsC/DC,GAAiD,CACrD,UAAW,EACX,cAAe,EACf,IAAK,CACP,EAGA,SAASC,GAAaC,EAAgC,CACpD,IAAMC,EAAU,IAAI,YACpB,OAAQD,EAAS,KAAM,CACrB,IAAK,YACH,OAAOC,EAAQ,OAAOD,EAAS,MAAM,EACvC,IAAK,gBACH,OAAOC,EAAQ,OAAOD,EAAS,EAAE,EACnC,IAAK,MACH,OAAO,IAAI,WAAW,CAAC,CAC3B,CACF,CAUA,SAASE,GAAqBF,EAAgC,CAC5D,OAAOG,EACL,WAAW,KAAK,CAACL,GAAaE,EAAS,IAAI,CAAC,CAAC,EAC7CD,GAAaC,CAAQ,CACvB,CACF,CAWA,SAASI,GAAaJ,EAAgC,CACpD,IAAMK,EAAM,WAAW,KAAK,CAACP,GAAaE,EAAS,IAAI,CAAC,CAAC,EACzD,GAAIA,EAAS,OAAS,MACpB,OAAOK,EAET,IAAMC,EAAOP,GAAaC,CAAQ,EAClC,OAAOG,EAAOE,EAAKE,EAAMD,EAAK,MAAM,EAAGA,CAAI,CAC7C,CAuCA,eAAsBE,GACpBC,EACiB,CACjB,IAAMC,EAAOC,EAAQF,EAAM,KAAM,OAAQ,EAAE,EACrCG,EAAYD,EAAQF,EAAM,UAAW,YAAa,EAAE,EACpDI,EAAaF,EAAQF,EAAM,WAAY,aAAc,EAAE,EAEvDK,EAAM,MAAMC,EAAiBN,EAAM,YAAY,EAC/CO,EAAY,MAAMC,EAAgBR,EAAM,YAAY,EAEpDS,EAAWC,EAAMV,EAAM,QAAQ,EAC/BW,EAAYD,EAAMV,EAAM,SAAS,EAEjCY,EAAW,MAAMC,EAAWzB,GAAa,CAC7Ca,EACAR,GAAqBO,EAAM,QAAQ,EACnCG,EACAC,EACAG,EACAE,EACAE,CACF,CAAC,EAEKG,EAAY,IAAI,WACpB,MAAM,OAAO,OAAO,KAAK,CAAE,KAAM,SAAU,EAAGT,EAAKO,CAAQ,CAC7D,EAEA,OAAOG,EACLrB,EACEO,EACAN,GAAaK,EAAM,QAAQ,EAC3BG,EACAC,EACAG,EACAE,EACAE,EACAG,CACF,CACF,CACF,CC7KA,IAAME,GAAsB,IAkB5B,eAAsBC,IAA8C,CAClE,IAAMC,EAASC,EAAI,OAAO,gBAAgB,IAAI,WAAW,EAAE,CAAC,CAAC,EAC7D,MAAO,CAAE,UAAWA,EAAI,MAAMC,EAAgBF,CAAM,CAAC,EAAG,OAAAA,CAAO,CACjE,CAsDA,SAASG,GAAcC,EAA4B,CACjD,OAAQA,EAAS,KAAM,CACrB,IAAK,YACH,OAAOA,EAAS,OAClB,IAAK,gBACH,OAAOA,EAAS,GAClB,IAAK,MACH,MAAO,KACX,CACF,CAWA,eAAsBC,GAAMC,EAAgD,CAC1E,IAAMC,EAAUD,EAAO,QAAQ,QAAQ,OAAQ,EAAE,EAC3CE,EAAUF,EAAO,OAAS,WAAW,MACrCG,EAAYH,EAAO,WAAa,IAEhCI,EAAU,MAAOC,EAAcC,IAAyC,CAC5E,IAAMC,EAAa,IAAI,gBACjBC,EAAQ,WAAW,IAAMD,EAAW,MAAM,EAAGJ,CAAS,EACxDM,EACJ,GAAI,CACFA,EAAW,MAAMP,EAAQ,GAAGD,CAAO,GAAGI,CAAI,GAAI,CAC5C,GAAGC,EACH,OAAQC,EAAW,MACrB,CAAC,CACH,OAASG,EAAK,CACZ,MAAM,IAAIC,EACR,EACAD,aAAe,MAAQA,EAAI,KAAO,eAClC,GAAGT,CAAO,GAAGI,CAAI,GACjB,IAAI,QACJK,aAAe,MAAQA,EAAI,QAAU,OAAOA,CAAG,CACjD,CACF,QAAE,CACA,aAAaF,CAAK,CACpB,CACA,IAAMI,EAAO,MAAMH,EAAS,KAAK,EAAE,MAAM,IAAM,EAAE,EACjD,GAAI,CAACA,EAAS,GACZ,MAAM,IAAIE,EACRF,EAAS,OACTA,EAAS,WACT,GAAGR,CAAO,GAAGI,CAAI,GACjBI,EAAS,QACTG,CACF,EAEF,OAAOA,EAAQ,KAAK,MAAMA,CAAI,EAAgB,CAAC,CACjD,EAEMC,EAAiB,MAAMT,EAAQ,kBAAmB,CACtD,OAAQ,MACR,QAAS,CAAE,OAAQ,kBAAmB,CACxC,CAAC,EAGKU,EAAYD,EAAc,WAAaA,EAAc,MAAM,UACjE,GAAI,CAACC,EACH,MAAM,IAAI,MAAM,8BAA8B,EAGhD,IAAMC,EAAU,MAAMtB,GAAmB,EACnCuB,EAAW,KAAK,MAAM,KAAK,IAAI,EAAI,GAAI,EACvCC,EAAY,MAAMC,GAAmB,CACzC,KAAMlB,EAAO,KACb,SAAUA,EAAO,SACjB,UAAAc,EACA,WAAYC,EAAQ,UACpB,SAAAC,EACA,UAAWA,GAAYhB,EAAO,YAAcR,IAC5C,aAAcQ,EAAO,YACvB,CAAC,EAOKmB,EAAa,MAAMf,EAAQ,cAAe,CAC9C,OAAQ,OACR,QAAS,CAAE,eAAgB,mBAAoB,OAAQ,kBAAmB,EAC1E,KAAM,KAAK,UAAU,CACnB,YAAa,gBACb,WAAYW,EAAQ,UACpB,YAAaf,EAAO,YAAcH,GAAcG,EAAO,QAAQ,EAC/D,UAAWgB,EACX,cAAe,CACb,UAAAF,EACA,gBAAiBG,EACjB,cAAejB,EAAO,YACxB,CACF,CAAC,CACH,CAAC,EAMKoB,EAAcD,EAAU,cAAgBA,EAAU,MAAM,aACxDE,EAAeF,EAAU,eAAiBA,EAAU,MAAM,cAChE,GAAI,CAACC,EACH,MAAM,IAAI,MAAM,4BAA4B,EAG9C,MAAO,CACL,YAAAA,EACA,aAAcC,GAAgB,GAC9B,WAAYN,EAAQ,UACpB,cAAeA,EAAQ,MACzB,CACF,CCzNO,IAAMO,GAA2B,CAAC,KAAM,KAAM,KAAM,IAAI,EAGzDC,GAAa,YAKbC,GAA2B,IAY1B,SAASC,GAAaC,EAAsB,CACjD,MAAO,UAAUH,EAAU,IAAIG,CAAI,EACrC,CAQO,SAASC,GAAaC,EAAiBC,EAAsB,CAClE,IAAMC,EAAOF,EAAQ,SAAS,GAAG,EAAIA,EAAU,GAAGA,CAAO,IACzD,OAAO,IAAI,IAAIC,EAAMC,CAAI,EAAE,SAAS,CACtC,CAQA,eAAsBC,GACpBH,EACAI,EAAwD,CAAC,EACvC,CAClB,GAAM,CAAE,UAAAC,EAAYT,GAA0B,OAAAU,CAAO,EAAIF,EAIzD,GAAIE,GAAQ,QAAS,MAAO,GAK5B,IAAMC,EAAa,IAAI,gBACjBC,EAAU,IAAMD,EAAW,MAAM,EACjCE,EAAcH,EAChBG,GAAaA,EAAY,iBAAiB,QAASD,EAAS,CAAE,KAAM,EAAK,CAAC,EAC9E,IAAME,EAAQ,WAAW,IAAMH,EAAW,MAAM,EAAGF,CAAS,EAE5D,GAAI,CACF,IAAMM,EAAMZ,GAAaC,EAAS,kBAAkB,EAC9CY,EAAM,MAAM,MAAMD,EAAK,CAC3B,OAAQ,MACR,OAAQJ,EAAW,MACrB,CAAC,EAED,GAAI,CAACK,EAAI,GAAI,MAAO,GAMpB,GAAI,CACF,IAAMC,EAAQ,MAAMD,EAAI,KAAK,EAGvBE,EACJD,GAAM,MAAM,SAAW,OAAYA,GAAM,MAAM,OAASA,GAAM,OAChE,OAAIC,IAAW,OAAkB,GAC1B,OAAOA,GAAW,UAAYA,EAAO,YAAY,IAAM,OAChE,MAAQ,CACN,MAAO,EACT,CACF,MAAQ,CACN,MAAO,EACT,QAAE,CACA,aAAaJ,CAAK,EACdD,GAAaA,EAAY,oBAAoB,QAASD,CAAO,CACnE,CACF,CAOA,eAAsBO,GACpBX,EAAqC,CAAC,EACnB,CACnB,GAAM,CAAE,MAAAY,EAAQtB,GAA0B,UAAAW,EAAW,OAAAC,CAAO,EAAIF,EAUhE,OARgB,MAAM,QAAQ,IAC5BY,EAAM,IAAI,MAAOlB,GAAS,CACxB,IAAMa,EAAMd,GAAaC,CAAI,EAE7B,OADW,MAAMK,GAAgBQ,EAAK,CAAE,UAAAN,EAAW,OAAAC,CAAO,CAAC,EAC/CK,EAAM,IACpB,CAAC,CACH,GAEe,OAAQA,GAAuBA,IAAQ,IAAI,CAC5D",
6
- "names": ["combineSignals", "signals", "list", "AbortSignalAny", "controller", "onAbort", "evt", "s", "createTimeoutSignal", "timeoutMs", "TERMINAL_AUTH_ERRORS", "extractErrorMessage", "bodyText", "body", "error", "nested", "detail", "HTTPError", "status", "statusText", "url", "headers", "explanation", "headersToRecord", "AuthRevokedError", "reason", "record", "value", "key", "pathForProof", "query", "WebHttpClient", "transport", "path", "init", "response", "retryCount", "requestStartTime", "startTime", "signal", "headersObj", "requestInit", "isStreamBody", "retrySignal", "timeoutMs", "elapsed", "remaining", "proof", "httpError", "authError", "userAborted", "refreshPromise", "newToken", "onTokenRefreshPromise", "refreshError", "baseUrl", "signals", "createTimeoutSignal", "combineSignals", "initHeaders", "token", "parse", "text", "createHttpClient", "transport", "WebHttpClient", "createBrowserHttpClient", "options", "url", "init", "createNodeHttpClient", "fetchImpl", "createUniversalHttpClient", "defaultRetryCondition", "error", "attempt", "name", "errorWithStatus", "status", "calculateDelay", "delay", "jitter", "withRetry", "fn", "options", "attempts", "lastError", "delayMs", "retryAfter", "seconds", "date", "waitTime", "resolve", "createRetryableMethod", "method", "retryOptions", "args", "AuthApiClient", "httpClient", "response", "request", "token", "keyId", "clientId", "changes", "MockHttpClient", "createBrowserAuthApiClient", "_config", "httpClient", "AuthApiClient", "createNodeAuthApiClient", "createAuthApiClient", "createAuthApiClientFromHttpClient", "CAPABILITIES", "hasCap", "mask", "cap", "capU32", "withCap", "withoutCap", "unwrap", "response", "compareSemver", "a", "b", "coreA", "preA", "splitVersion", "coreB", "preB", "na", "nb", "n", "i", "c", "compareSemverId", "pa", "pb", "m", "v", "core", "dash", "d", "fetchRegistry", "url", "kind", "context", "withRetry", "resp", "err", "AdminApiClient", "httpClient", "request", "registryUrl", "packageName", "bundles", "appId", "applicationId", "serviceName", "query", "r", "body", "contextId", "path", "params", "res", "blobId", "options", "headers", "size", "name", "namespaceId", "node", "data", "deviceId", "groupId", "safeId", "account", "identity", "current", "hasCap", "CAPABILITIES", "next", "withCap", "capabilities", "id", "childGroupId", "hexEncodeUtf8", "text", "b", "createBrowserAdminApiClient", "config", "AdminApiClient", "createBrowserHttpClient", "createNodeAdminApiClient", "createNodeHttpClient", "createAdminApiClient", "createUniversalHttpClient", "createAdminApiClientFromHttpClient", "httpClient", "_config", "parseAuthCallback", "url", "hashIndex", "hash", "params", "accessToken", "buildAuthLoginUrl", "nodeUrl", "opts", "PKCS8_ED25519_PREFIX", "u32le", "value", "out", "hex", "bytes", "b", "fromHex", "label", "expectedBytes", "clean", "fromHexUnsized", "i", "u64le", "concat", "parts", "total", "p", "offset", "part", "domainHash", "domain", "chunks", "importSigningKey", "secretHex", "seed", "pkcs8", "cause", "detail", "derivePublicKey", "key", "raw", "c", "SIGN_DOMAIN", "BODY_DOMAIN", "borshString", "value", "bytes", "concat", "u32le", "requestBodyHash", "body", "domainHash", "signRequest", "input", "key", "importSigningKey", "bodyHash", "issuedAt", "u64le", "expiresAt", "encoder", "preimage", "signature", "hex", "callerProof", "input", "credential", "fromHexUnsized", "request", "session", "concat", "hex", "bodyBytes", "body", "createProofSigner", "options", "ttl", "clock", "request", "issuedAt", "callerProof", "signRequest", "RpcError", "code", "message", "data", "type", "jsonRpcCall", "httpClient", "method", "params", "response", "err", "RpcClient", "opts", "result", "contextId", "MIGRATION_EVENT_TYPES", "isGroupMigrationEvent", "event", "TERMINAL_AUTH_ERRORS", "TERMINAL_AUTH_STATUSES", "isTerminalAuthFailure", "response", "authError", "SseClient", "opts", "url", "init", "event", "handler", "key", "arr", "idx", "listener", "ev", "d", "isGroupMigrationEvent", "arg", "token", "terminal", "bodyText", "AuthRevokedError", "HTTPError", "err", "error", "body", "reader", "decoder", "buffer", "done", "value", "lines", "line", "jsonStr", "msg", "eventData", "bytes", "text", "idsOrOpts", "newContextIds", "id", "newGroupIds", "hadContextIds", "hadGroupIds", "method", "ids", "params", "_WsClient", "opts", "event", "handler", "key", "arr", "idx", "arg", "token", "wsUrl", "err", "error", "raw", "msg", "eventData", "bytes", "text", "idsOrOpts", "id", "contextIds", "groupIds", "params", "delay", "WsClient", "jsonCodec", "value", "bytes", "EphemeralClient", "opts", "contextId", "state", "codec", "jsonCodec", "jsonRpcCall", "handler", "listener", "event", "e", "data", "removed", "entry", "CERT_DOMAIN", "ACCOUNT_ID_DOMAIN", "DEVICE_ID_DOMAIN", "ACCOUNT_GENESIS_VERSION", "mintDeviceId", "account", "nonce", "accountBytes", "fromHex", "hex", "domainHash", "deviceCertPayload", "input", "u32le", "accountProofBytes", "concat", "signDeviceCert", "rootPublicKey", "derivePublicKey", "keyEpoch", "payload", "key", "importSigningKey", "signature", "credential", "accountForRoot", "rootSecret", "ENGLISH_WORDLIST", "ACCOUNT_LINK_DOMAIN", "ACCOUNT_LOGIN_DOMAIN", "ENTROPY_BITS", "CHECKSUM_BITS", "PHRASE_WORDS", "generateAccountRoot", "entropy", "withPhrase", "hex", "encodePhrase", "accountRootFromPhrase", "phrase", "words", "normalisePhrase", "decodePhrase", "accountRootFromSecret", "secret", "fromHex", "derivePublicKey", "accountForRoot", "signAccountLink", "input", "key", "importSigningKey", "signature", "concat", "base64", "signAccountLogin", "count", "bits", "bitsOf", "checksumBits", "i", "index", "ENGLISH_WORDLIST", "word", "digest", "bytes", "byte", "binary", "ROUTING_PROOF_DOMAIN", "signRoutingChallenge", "nonce", "deviceSecret", "fromHex", "key", "importSigningKey", "message", "concat", "signature", "base64", "routingProofHeaders", "challenge", "credential", "bytes", "binary", "byte", "DEFAULT_CLOUD_BASE_URL", "REFRESH_HEADER", "REFRESH_EXPIRES_HEADER", "CloudClient", "config", "injected", "input", "init", "idToken", "body", "rootSecret", "root", "accountRootFromSecret", "nonce", "signAccountLogin", "proof", "row", "namespaceId", "challenge", "routingProofHeaders", "accountId", "admitters", "nodes", "allowed", "a", "n", "ns", "ownershipProof", "options", "params", "signAccountLink", "portal", "query", "href", "source", "hash", "grant", "error", "out", "session", "method", "path", "headers", "controller", "timer", "response", "err", "HTTPError", "bodyText", "token", "expiresRaw", "expiresAt", "admitterAddrsFromNetworkStatus", "status", "record", "peerId", "external", "suffix", "entry", "addr", "dialable", "resolveProofSigner", "config", "createProofSigner", "REFRESH_LOCK_NAME", "getLockManager", "locks", "expiresAtFromJwt", "token", "fallbackMs", "parts", "b64", "payload", "MeroJs", "isTauri", "getProof", "createBrowserHttpClient", "newToken", "createAuthApiClientFromHttpClient", "createAdminApiClientFromHttpClient", "RpcClient", "SseClient", "EphemeralClient", "CloudClient", "WsClient", "credentials", "creds", "requestBody", "response", "accessToken", "error", "triggeringAccessToken", "promise", "fn", "stored", "data", "expiresAt", "url", "parseAuthCallback", "nodeUrl", "opts", "buildAuthLoginUrl", "createMeroJs", "MemoryTokenStore", "data", "STORAGE_KEY", "LocalStorageTokenStore", "key", "raw", "parsed", "SIGN_DOMAIN", "INTENT_DOMAIN", "MAX_CITED_HEADS", "UNPINNED_APP", "intentHash", "method", "argsJson", "args", "domainHash", "signWarrant", "input", "context", "fromHex", "authorAccount", "executor", "appVersion", "accountHeads", "citedHeads", "governanceFloor", "key", "importSigningKey", "publicKey", "derivePublicKey", "commitment", "nonce", "u64le", "notAfter", "preimage", "signature", "hex", "concat", "u32le", "heads", "label", "list", "head", "i", "DEFAULT_TTL_SECONDS", "IntentRefusedError", "reason", "retryable", "status", "RelayClient", "config", "injected", "input", "init", "contextId", "body", "method", "argsJson", "executor", "nonce", "ttl", "warrant", "signWarrant", "path", "headers", "controller", "timer", "response", "err", "HTTPError", "text", "extractReason", "bodyText", "parsed", "candidate", "createMemoryNonceSource", "start", "next", "value", "createLocalStorageNonceSource", "key", "storage", "store", "raw", "current", "parsed", "PROTOCOL_NAME", "DH_LEN", "TAG_LEN", "PKCS8_X25519_PREFIX", "SymmetricState", "_SymmetricState", "h", "name", "data", "sha256", "concat", "inputKeyMaterial", "ck", "key", "hkdf2", "plaintext", "ciphertext", "noiseNonce", "initiate", "remoteStatic", "prologue", "fixedEphemeral", "state", "ephemeral", "x25519KeyPair", "dh", "message2", "remoteEphemeral", "secret", "pair", "error", "publicKey", "privateKey", "jwk", "base64UrlDecode", "peer", "nonce", "hmacSha256", "hmacKey", "chainingKey", "temp", "first", "second", "value", "base64", "binary", "char", "HANDSHAKE_PATH", "SEALED_PATH", "SEALED_CONTENT_TYPE", "VERSION", "PROLOGUE", "SESSION_ID_LEN", "HEADER_LEN", "FRAME_HEAD", "FRAME_DATA", "FRAME_END", "MAX_FRAME_LEN", "MAX_HANDSHAKE_REPLY", "RENEW_BEFORE_MS", "TRANSPORT_BINDING_DOMAIN", "NULL_BODY_STATUSES", "utf8", "StaleTransportKeyError", "SealedTransportError", "status", "code", "message", "retryAfterMs", "transportKeyBinding", "inner", "transportKey", "digest", "concat", "fetchAttestedTransportKey", "admin", "verify", "options", "nonce", "hex", "attested", "fromHex", "reportDataSuffix", "createAttestedSealedFetch", "baseFetch", "input", "init", "attestUrl", "request", "response", "body", "createSealedFetch", "base", "basePath", "nodeUrl", "attest", "session", "openSession", "handshake", "checkedKey", "error", "currentSession", "pending", "renew", "open", "renewed", "stale", "opening", "url", "headers", "value", "name", "head", "send", "requestId", "header", "envelope", "sealRequest", "envelopeRefusal", "openResponse", "key", "bytes", "handshakeOnce", "resolve", "noise", "initiate", "reply", "readAtMost", "sessionFrom", "finished", "payload", "split", "sessionId", "lifetimeSecs", "requestKey", "responseKey", "sealed", "joinInner", "index", "view", "encoded", "len", "frames", "frameReader", "next", "plain", "first", "controller", "frame", "reader", "buffer", "fill", "length", "done", "limit", "retryAfter", "connectCloud", "options", "cloud", "CloudClient", "namespaces", "namespace", "pickNamespace", "relays", "relayInfo", "r", "explainNoRelay", "relay", "RelayClient", "defaultNonceSource", "createAttestedSealedFetch", "contextId", "method", "argsJson", "requested", "found", "n", "waitingOnGrant", "deviceSecret", "key", "hex", "derivePublicKey", "createLocalStorageNonceSource", "createMemoryNonceSource", "MEASUREMENT_HEX", "createQuoteVerifier", "options", "allowedMrtd", "measurements", "allowedRtmr", "allowedStatuses", "now", "quoteB64", "nonce", "reportDataSuffix", "collateral", "quote", "fromBase64", "against", "verified", "error", "td", "tdReport", "hex", "register", "index", "allowed", "values", "label", "value", "normalized", "report", "data", "binary", "bytes", "i", "SIGNED_NAMESPACE_OP_SCHEMA_VERSION", "NAMESPACE_SIGN_DOMAIN", "NAMESPACE_OP_ROOT", "ROOT_OP", "bytes32", "value", "label", "optionalBytes32", "concat", "borshString", "bytes", "u32le", "encodeSignedInvitation", "signed", "body", "admitters", "admitterAddrs", "u64le", "a", "fromHex", "signMemberJoinOp", "input", "parents", "credential", "expires", "joinedAt", "rootOp", "op", "key", "importSigningKey", "signer", "derivePublicKey", "signable", "p", "i", "signature", "unendorsed", "hex", "SIGN_DOMAIN", "AUDIENCE_TAG", "audienceBody", "audience", "encoder", "audienceSigningBytes", "concat", "audienceWire", "tag", "body", "u32le", "signLoginStatement", "input", "node", "fromHex", "challenge", "sessionKey", "key", "importSigningKey", "deviceKey", "derivePublicKey", "issuedAt", "u64le", "expiresAt", "preimage", "domainHash", "signature", "hex", "DEFAULT_TTL_SECONDS", "generateSessionKey", "secret", "hex", "derivePublicKey", "audienceLabel", "audience", "login", "config", "baseUrl", "doFetch", "timeoutMs", "request", "path", "init", "controller", "timer", "response", "err", "HTTPError", "text", "challengeBody", "challenge", "session", "issuedAt", "statement", "signLoginStatement", "tokenBody", "accessToken", "refreshToken", "DEFAULT_LOCAL_NODE_PORTS", "LOCAL_HOST", "DEFAULT_PROBE_TIMEOUT_MS", "localNodeUrl", "port", "nodeEndpoint", "baseUrl", "path", "base", "probeNodeHealth", "options", "timeoutMs", "signal", "controller", "onAbort", "abortSignal", "timer", "url", "res", "body", "status", "discoverLocalNodes", "ports"]
4
+ "sourcesContent": ["// Utility for combining multiple AbortSignals\nexport function combineSignals(\n signals: Array<AbortSignal | undefined>,\n): AbortSignal | undefined {\n const list = signals.filter(Boolean) as AbortSignal[];\n if (list.length === 0) return undefined;\n\n // Prefer native any(), but fall back if unavailable or it throws\n const AbortSignalAny = AbortSignal as {\n any?: (signals: AbortSignal[]) => AbortSignal;\n };\n if (typeof AbortSignalAny.any === 'function') {\n try {\n return AbortSignalAny.any(list);\n } catch {\n // Fall through to manual implementation\n }\n }\n\n const controller = new AbortController();\n const onAbort = (evt: Event) => {\n controller.abort((evt.target as AbortSignal).reason);\n for (const s of list) s.removeEventListener('abort', onAbort);\n };\n\n for (const s of list) {\n if (s.aborted) return AbortSignal.abort(s.reason);\n s.addEventListener('abort', onAbort, { once: true });\n }\n\n return controller.signal;\n}\n\n// Helper to create a timeout signal\nexport function createTimeoutSignal(timeoutMs: number): AbortSignal {\n // Use AbortSignal.timeout if available (Node 18.17+, modern browsers)\n if (typeof AbortSignal.timeout === 'function') {\n return AbortSignal.timeout(timeoutMs);\n }\n\n // Fallback for older environments\n const controller = new AbortController();\n setTimeout(() => {\n controller.abort(new DOMException('Timeout', 'TimeoutError'));\n }, timeoutMs);\n\n return controller.signal;\n}\n", "// ErrorResponse import removed as it's not used\nimport {\n HttpClient,\n Transport,\n RequestOptions,\n ResponseParser,\n} from './http-types.js';\nimport { combineSignals, createTimeoutSignal } from './signal-utils.js';\n\n/**\n * `x-auth-error` reasons that mean the whole token family is gone.\n *\n * Refresh tokens are single-use (calimero-network/core#3083): presenting a\n * consumed refresh token is treated as token theft and the server revokes the\n * family. Refreshing or retrying afterwards is pointless \u2014 the only way out is\n * a fresh login.\n */\nconst TERMINAL_AUTH_ERRORS = new Set(['token_reuse', 'token_revoked']);\n\n/**\n * Pull the node's explanation out of an error body. Core answers every handled\n * failure with `{\"error\": \"<message>\"}` (`ApiError::into_response`); the nested\n * `{\"error\": {\"message\": ...}}` form is what proxies in front of a node emit.\n * Returns undefined for a non-JSON, empty, or envelope-less body.\n */\nfunction extractErrorMessage(bodyText?: string): string | undefined {\n if (!bodyText) return undefined;\n try {\n const body = JSON.parse(bodyText) as { error?: unknown; detail?: unknown };\n const error = body?.error;\n if (typeof error === 'string' && error.trim() !== '') return error;\n const nested = (error as { message?: unknown } | null)?.message;\n if (typeof nested === 'string' && nested.trim() !== '') return nested;\n // FastAPI's spelling, which is what every Calimero Cloud route answers\n // with. Without it a manager's carefully worded refusal \u2014 \"ownership is\n // recorded, but the account is not linked\" \u2014 reached callers as a bare\n // \"HTTP 403\", and the one thing they could do about it was invisible.\n const detail = body?.detail;\n if (typeof detail === 'string' && detail.trim() !== '') return detail;\n } catch {\n // Not JSON \u2014 the status line is all we can honestly report.\n }\n return undefined;\n}\n\n// Custom error class for HTTP errors\nexport class HTTPError extends Error {\n name = 'HTTPError';\n\n constructor(\n public status: number,\n public statusText: string,\n public url: string,\n public headers: Headers,\n public bodyText?: string, // cap at ~64KB\n ) {\n const explanation = extractErrorMessage(bodyText);\n super(\n explanation\n ? `HTTP ${status} ${statusText}: ${explanation}`\n : `HTTP ${status} ${statusText}`,\n );\n }\n\n toJSON(): {\n status: number;\n statusText: string;\n url: string;\n headers: Record<string, string>;\n bodyText?: string;\n } {\n return {\n status: this.status,\n statusText: this.statusText,\n url: this.url,\n headers: headersToRecord(this.headers),\n bodyText: this.bodyText,\n };\n }\n}\n\n/**\n * Terminal authentication error: the refresh-token family was revoked (single-use\n * refresh token replayed, or the token was explicitly revoked). Never retried and\n * never refreshed \u2014 apps should catch this and force a re-login.\n *\n * Extends {@link HTTPError} so existing `instanceof HTTPError` handling keeps working.\n */\nexport class AuthRevokedError extends HTTPError {\n name = 'AuthRevokedError';\n\n constructor(\n /** Value of the `x-auth-error` header, e.g. `token_reuse` or `token_revoked`. */\n public reason: string,\n status: number,\n statusText: string,\n url: string,\n headers: Headers,\n bodyText?: string,\n ) {\n super(status, statusText, url, headers, bodyText);\n this.message = `Authentication revoked (${reason}): HTTP ${status} ${statusText}`;\n }\n}\n\n// Helper function to convert Headers to Record\nfunction headersToRecord(headers: Headers): Record<string, string> {\n const record: Record<string, string> = {};\n headers.forEach((value, key) => {\n record[key] = value;\n });\n return record;\n}\n\n// Web Standards HTTP client implementation\n/**\n * The path a node will verify a request signature against.\n *\n * `new URL` drops the query and keeps any prefix the base URL contributed,\n * which is exactly what reaches the server. The fallback covers a `url` that is\n * already relative \u2014 it cannot be parsed, and signing the whole string with its\n * query attached would be worse than signing the part before the `?`.\n */\nfunction pathForProof(url: string): string {\n try {\n return new URL(url).pathname;\n } catch {\n const query = url.indexOf('?');\n return query === -1 ? url : url.slice(0, query);\n }\n}\n\nexport class WebHttpClient implements HttpClient {\n // Cache for concurrent refresh token calls to prevent race conditions\n private refreshTokenPromise: Promise<string> | null = null;\n // Cache for concurrent onTokenRefresh calls to prevent duplicate callbacks\n private onTokenRefreshPromise: Promise<void> | null = null;\n \n constructor(private transport: Transport) {}\n\n async get<T>(path: string, init?: RequestOptions): Promise<T> {\n return this.request<T>(path, { ...init, method: 'GET' });\n }\n\n async post<T>(\n path: string,\n body?: unknown,\n init?: RequestOptions,\n ): Promise<T> {\n return this.request<T>(path, {\n ...init,\n method: 'POST',\n body: body ? JSON.stringify(body) : undefined,\n headers: {\n 'Content-Type': 'application/json',\n ...init?.headers,\n },\n });\n }\n\n async put<T>(\n path: string,\n body?: unknown,\n init?: RequestOptions,\n ): Promise<T> {\n return this.request<T>(path, {\n ...init,\n method: 'PUT',\n body: body ? JSON.stringify(body) : undefined,\n headers: {\n 'Content-Type': 'application/json',\n ...init?.headers,\n },\n });\n }\n\n async delete<T>(path: string, init?: RequestOptions): Promise<T> {\n return this.request<T>(path, { ...init, method: 'DELETE' });\n }\n\n async patch<T>(\n path: string,\n body?: unknown,\n init?: RequestOptions,\n ): Promise<T> {\n return this.request<T>(path, {\n ...init,\n method: 'PATCH',\n body: body ? JSON.stringify(body) : undefined,\n headers: {\n 'Content-Type': 'application/json',\n ...init?.headers,\n },\n });\n }\n\n async head(\n path: string,\n init?: RequestOptions,\n ): Promise<{ headers: Record<string, string>; status: number }> {\n const response = await this.makeRequest<Response>(path, {\n ...init,\n method: 'HEAD',\n parse: 'response',\n });\n return {\n headers: headersToRecord(response.headers),\n status: response.status,\n };\n }\n\n async request<T>(path: string, init?: RequestOptions): Promise<T> {\n return this.makeRequest<T>(path, init);\n }\n\n private async makeRequest<T>(\n path: string,\n init?: RequestOptions,\n retryCount = 0,\n requestStartTime?: number,\n ): Promise<T> {\n // Maximum retry attempts to prevent infinite loops\n const MAX_RETRY_ATTEMPTS = 1;\n const url = this.buildUrl(path);\n \n // Track request start time for timeout calculation (only on first attempt)\n // Use per-request start time to avoid corruption from concurrent requests\n const startTime = requestStartTime ?? Date.now();\n // Note: Tauri proxy script now handles AbortSignal, so we can use full RequestInit\n // Removed Tauri-specific minimal path - proxy script handles AbortSignal properly\n const signal = this.createAbortSignal(init);\n const headers = await this.buildHeaders(init?.headers);\n let headersObj: Record<string, string>;\n if (headers instanceof Headers) {\n headersObj = {};\n headers.forEach((value, key) => {\n headersObj[key] = value;\n });\n } else {\n headersObj = headers;\n }\n \n const requestInit: RequestInit = {\n method: init?.method || 'GET',\n headers: headersObj,\n };\n \n // Check if body is a stream (ReadableStream) that can't be reused\n // Note: Blob is reusable, so it's not included here\n const isStreamBody = init?.body instanceof ReadableStream ||\n (typeof init?.body === 'object' && init?.body !== null && 'getReader' in init.body && !(init.body instanceof Blob));\n \n if (init?.body !== undefined && !isStreamBody) {\n requestInit.body = init.body;\n } else if (init?.body !== undefined && isStreamBody && retryCount === 0) {\n // Only include stream body on first attempt - can't retry with streams\n requestInit.body = init.body;\n }\n \n // For retries, calculate remaining timeout to prevent timeout reset\n // Track elapsed time and use remaining timeout for retry\n // Note: This is calculated before the request, so it doesn't include token refresh time\n // The actual remaining timeout check happens after token refresh completes\n let retrySignal: AbortSignal | undefined;\n if (retryCount > 0 && requestStartTime !== undefined) {\n const timeoutMs = init?.timeoutMs || this.transport.timeoutMs;\n if (timeoutMs) {\n // Calculate elapsed time (will be recalculated after token refresh if needed)\n const elapsed = Date.now() - startTime;\n const remaining = Math.max(0, timeoutMs - elapsed);\n // Create signal with remaining timeout, preserving user's signal\n retrySignal = this.createAbortSignal({ ...init, timeoutMs: remaining });\n } else {\n // No timeout, just preserve user's signal\n retrySignal = this.createAbortSignal(init);\n }\n } else {\n retrySignal = signal;\n }\n \n if (retrySignal) {\n requestInit.signal = retrySignal;\n }\n \n if (this.transport.credentials !== undefined) {\n requestInit.credentials = this.transport.credentials;\n }\n \n if (init?.mode !== undefined) {\n requestInit.mode = init.mode;\n }\n if (init?.cache !== undefined) {\n requestInit.cache = init.cache;\n }\n if (init?.redirect !== undefined) {\n requestInit.redirect = init.redirect;\n }\n if (init?.referrer !== undefined) {\n requestInit.referrer = init.referrer;\n }\n if (init?.referrerPolicy !== undefined) {\n requestInit.referrerPolicy = init.referrerPolicy;\n }\n if (init?.integrity !== undefined) {\n requestInit.integrity = init.integrity;\n }\n if (init?.keepalive !== undefined) {\n requestInit.keepalive = init.keepalive;\n }\n\n // Signed here rather than in `buildHeaders`, which sees neither the method\n // nor the body \u2014 and a proof commits to both. Signed AFTER the body is\n // settled above, so what is hashed is what is sent.\n //\n // The path comes from the built URL's `pathname`, not from the `path`\n // argument: that argument may carry a query string, and may be relative to\n // a base URL that contributes its own prefix. The node verifies against the\n // path it received, so the only safe source is the URL actually being\n // fetched. A query string is deliberately not covered \u2014 core's `RequestSig`\n // signs the path alone, and signing more here would fail every request.\n if (this.transport.getProof) {\n const proof = await this.transport.getProof({\n method: requestInit.method ?? 'GET',\n path: pathForProof(url),\n body: requestInit.body,\n });\n if (proof) {\n headersObj['X-Calimero-Proof'] = proof;\n }\n }\n\n try {\n const response = await this.transport.fetch(url, requestInit);\n\n if (!response.ok) {\n const bodyText = await this.getBodyText(response);\n const httpError = new HTTPError(\n response.status,\n response.statusText,\n url,\n response.headers,\n bodyText,\n );\n\n // Handle a revoked token family (single-use refresh token replayed, or an\n // explicitly revoked token). This is terminal: refreshing would only burn\n // another token and retrying would 401 again, so clear the tokens via the\n // onAuthRevoked hook and surface a distinguishable error.\n const authError = response.headers.get('x-auth-error');\n if (\n (response.status === 401 || response.status === 403) &&\n authError &&\n TERMINAL_AUTH_ERRORS.has(authError)\n ) {\n // Any in-flight refresh is based on a now-dead family \u2014 drop the caches.\n this.refreshTokenPromise = null;\n this.onTokenRefreshPromise = null;\n\n if (this.transport.onAuthRevoked) {\n try {\n await this.transport.onAuthRevoked();\n } catch {\n // Never mask the auth error with a cleanup failure.\n }\n }\n\n throw new AuthRevokedError(\n authError,\n response.status,\n response.statusText,\n url,\n response.headers,\n bodyText,\n );\n }\n\n // Handle 401 with token_expired - attempt automatic token refresh\n const userAborted = init?.signal?.aborted === true;\n if (\n response.status === 401 &&\n this.transport.refreshToken &&\n authError === 'token_expired' &&\n retryCount < MAX_RETRY_ATTEMPTS &&\n !isStreamBody &&\n !userAborted\n ) {\n try {\n // Use cached refresh promise if one is in progress (prevents race conditions)\n let refreshPromise = this.refreshTokenPromise;\n if (!refreshPromise) {\n refreshPromise = this.transport.refreshToken();\n this.refreshTokenPromise = refreshPromise;\n }\n \n // Attempt to refresh the token\n const newToken = await refreshPromise;\n \n // Validate token - must be non-empty\n if (!newToken || newToken.trim() === '') {\n // Clear caches on error\n this.refreshTokenPromise = null;\n this.onTokenRefreshPromise = null;\n throw new Error('Refresh token returned empty token');\n }\n \n // onTokenRefresh is required when refreshToken is provided\n // Without it, the new token cannot be stored and getAuthToken() will return the old token\n if (!this.transport.onTokenRefresh) {\n // Clear caches on error\n this.refreshTokenPromise = null;\n this.onTokenRefreshPromise = null;\n throw new Error(\n 'onTokenRefresh callback is required when refreshToken is provided. ' +\n 'The callback must update the token storage so getAuthToken() returns the new token.'\n );\n }\n \n // Use cached onTokenRefresh promise if one is in progress (prevents duplicate callbacks)\n // This ensures onTokenRefresh is only called once per token refresh, even with concurrent requests\n let onTokenRefreshPromise = this.onTokenRefreshPromise;\n if (!onTokenRefreshPromise) {\n onTokenRefreshPromise = this.transport.onTokenRefresh(newToken);\n this.onTokenRefreshPromise = onTokenRefreshPromise;\n }\n \n // Update token via callback (only called once per refresh, even with concurrent requests)\n // Errors from onTokenRefresh callback should be preserved (don't mask as 401)\n // This helps developers debug token storage issues\n await onTokenRefreshPromise;\n \n // Clear caches after both refresh and callback complete\n this.refreshTokenPromise = null;\n this.onTokenRefreshPromise = null;\n \n // Check if timeout has expired during token refresh\n // If so, throw the original 401 error instead of retrying with 0ms timeout\n // This prevents timeout/abort errors that obscure the root cause (expired token)\n const timeoutMs = init?.timeoutMs || this.transport.timeoutMs;\n if (timeoutMs && requestStartTime !== undefined) {\n const elapsed = Date.now() - startTime;\n const remaining = timeoutMs - elapsed;\n if (remaining <= 0) {\n // Timeout expired during token refresh - throw original 401 error\n // This is better than retrying with 0ms timeout which would cause confusing timeout errors\n throw httpError;\n }\n }\n \n // Retry the request with the new token (increment retry count)\n // Preserve user's abort signal and start time in retry\n return this.makeRequest<T>(path, init, retryCount + 1, startTime);\n } catch (refreshError) {\n // Clear caches on error\n this.refreshTokenPromise = null;\n this.onTokenRefreshPromise = null;\n // Configuration errors (missing onTokenRefresh) should be thrown as-is\n if (refreshError instanceof Error && refreshError.message.includes('onTokenRefresh')) {\n throw refreshError;\n }\n // Errors from onTokenRefresh callback are already thrown above, so if we get here,\n // it's either a refreshToken() failure or empty token - throw original 401\n // This matches the PR description: \"If refresh fails, throws the original 401 error\"\n throw httpError;\n }\n }\n\n throw httpError;\n }\n\n return this.parseResponse<T>(response, init?.parse);\n } catch (error) {\n if (error instanceof HTTPError) {\n throw error;\n }\n // Preserve configuration errors (like missing onTokenRefresh)\n if (error instanceof Error && error.message.includes('onTokenRefresh')) {\n throw error;\n }\n throw new HTTPError(\n 0,\n 'Network Error',\n url,\n new Headers(),\n error instanceof Error ? error.message : 'Unknown error',\n );\n }\n }\n\n private buildUrl(path: string): string {\n // Handle absolute URLs\n if (path.startsWith('http://') || path.startsWith('https://')) {\n return path;\n }\n\n // Handle baseUrl with path\n const baseUrl = this.transport.baseUrl;\n if (path.startsWith('/')) {\n // If path starts with /, combine with baseUrl\n const base = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;\n return `${base}${path}`;\n } else {\n // If path doesn't start with /, append to baseUrl\n const base = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;\n return `${base}${path}`;\n }\n }\n\n private createAbortSignal(init?: RequestOptions): AbortSignal | undefined {\n const signals: AbortSignal[] = [];\n\n if (this.transport.defaultAbortSignal) {\n signals.push(this.transport.defaultAbortSignal);\n }\n\n if (init?.signal) {\n signals.push(init.signal);\n }\n\n const timeoutMs = init?.timeoutMs || this.transport.timeoutMs;\n if (timeoutMs) {\n signals.push(createTimeoutSignal(timeoutMs));\n }\n\n return signals.length > 0 ? combineSignals(signals) : undefined;\n }\n\n private async buildHeaders(\n initHeaders?: HeadersInit,\n ): Promise<Record<string, string>> {\n const headers: Record<string, string> = {\n ...this.transport.defaultHeaders,\n };\n\n // Add auth token if available and not empty\n if (this.transport.getAuthToken) {\n try {\n const token = await this.transport.getAuthToken();\n if (token && token.trim() !== '') {\n headers.Authorization = `Bearer ${token}`;\n }\n } catch (error) {\n // Ignore auth token errors\n }\n }\n\n // Add init headers\n if (initHeaders) {\n if (initHeaders instanceof Headers) {\n initHeaders.forEach((value, key) => {\n headers[key] = value;\n });\n } else if (Array.isArray(initHeaders)) {\n initHeaders.forEach(([key, value]) => {\n headers[key] = value;\n });\n } else {\n Object.assign(headers, initHeaders);\n }\n }\n\n return headers;\n }\n\n private async parseResponse<T>(\n response: Response,\n parse?: ResponseParser,\n ): Promise<T> {\n switch (parse) {\n case 'text':\n return (await response.text()) as T;\n case 'blob':\n return (await response.blob()) as T;\n case 'arrayBuffer':\n return (await response.arrayBuffer()) as T;\n case 'response':\n return response as T;\n case 'json':\n default: {\n // Tolerate empty 2xx bodies (e.g. 204 No Content from delete/resync):\n // `response.json()` throws on '', so read text and return null when empty.\n const text = await response.text();\n if (!text) return null as T;\n try {\n return JSON.parse(text) as T;\n } catch {\n // Non-JSON body on a 2xx (e.g. an HTML error page) \u2014 surface a clear\n // error with a snippet instead of a bare SyntaxError.\n throw new Error(\n `Expected a JSON response (status ${response.status}) but body was not JSON: ${text.slice(0, 200)}`,\n );\n }\n }\n }\n }\n\n private async getBodyText(response: Response): Promise<string> {\n try {\n const text = await response.text();\n return text.length > 65536 ? text.slice(0, 65536) + '...' : text;\n } catch {\n return '';\n }\n }\n}\n", "import { WebHttpClient } from './web-client.js';\nimport { Transport, HttpClient } from './http-types.js';\n\n// Factory function to create HTTP client with sensible defaults\nexport function createHttpClient(transport: Transport): HttpClient {\n return new WebHttpClient(transport);\n}\n\n// Factory function for browser environments\nexport function createBrowserHttpClient(options: {\n baseUrl: string;\n fetch?: typeof fetch;\n getAuthToken?: () => Promise<string | undefined>;\n getProof?: Transport['getProof'];\n onTokenRefresh?: (newToken: string) => Promise<void>;\n /**\n * Callback to refresh the access token when a 401 error with 'token_expired' is detected.\n * Should return the new access token, or throw an error if refresh fails.\n * If provided, the client will automatically retry the request after a successful refresh.\n */\n refreshToken?: () => Promise<string>;\n /**\n * Callback invoked when the server reports a revoked token family\n * (`x-auth-error: token_reuse` / `token_revoked`). Terminal \u2014 the request is\n * never retried; clear stored tokens and force a re-login.\n */\n onAuthRevoked?: () => Promise<void> | void;\n defaultHeaders?: Record<string, string>;\n timeoutMs?: number;\n credentials?: RequestCredentials;\n defaultAbortSignal?: AbortSignal;\n}): HttpClient {\n const transport: Transport = {\n // globalThis.fetch must be called as a method on globalThis, not through a\n // variable, or browsers throw \"Illegal invocation\".\n fetch: (url: RequestInfo | URL, init?: RequestInit) =>\n options.fetch ? options.fetch(url, init) : globalThis.fetch(url, init),\n baseUrl: options.baseUrl,\n getAuthToken: options.getAuthToken,\n getProof: options.getProof,\n onTokenRefresh: options.onTokenRefresh,\n refreshToken: options.refreshToken,\n onAuthRevoked: options.onAuthRevoked,\n defaultHeaders: options.defaultHeaders,\n timeoutMs: options.timeoutMs,\n credentials: options.credentials, // No default credentials\n defaultAbortSignal: options.defaultAbortSignal,\n };\n\n return createHttpClient(transport);\n}\n\n// Factory function for Node.js environments\nexport function createNodeHttpClient(options: {\n baseUrl: string;\n fetch?: typeof fetch; // Allow injection of undici.fetch or other fetch implementations\n getAuthToken?: () => Promise<string | undefined>;\n getProof?: Transport['getProof'];\n onTokenRefresh?: (newToken: string) => Promise<void>;\n /**\n * Callback to refresh the access token when a 401 error with 'token_expired' is detected.\n * Should return the new access token, or throw an error if refresh fails.\n * If provided, the client will automatically retry the request after a successful refresh.\n */\n refreshToken?: () => Promise<string>;\n /**\n * Callback invoked when the server reports a revoked token family\n * (`x-auth-error: token_reuse` / `token_revoked`). Terminal \u2014 the request is\n * never retried; clear stored tokens and force a re-login.\n */\n onAuthRevoked?: () => Promise<void> | void;\n defaultHeaders?: Record<string, string>;\n timeoutMs?: number;\n credentials?: RequestCredentials;\n defaultAbortSignal?: AbortSignal;\n}): HttpClient {\n // Use provided fetch or try to use global fetch (Node 18+)\n const fetchImpl = options.fetch ?? globalThis.fetch;\n\n if (!fetchImpl) {\n throw new Error(\n 'No fetch implementation available. Please provide a fetch implementation ' +\n '(e.g., undici.fetch) or use Node.js 18+ which has native fetch support.',\n );\n }\n\n // Check if we're using the default globalThis.fetch to preserve context\n // When fetchImpl is globalThis.fetch, we must call it directly, not through a variable\n // Custom fetch implementations (like undici.fetch) can be called through the variable\n const isDefaultFetch = fetchImpl === globalThis.fetch;\n\n const transport: Transport = {\n // Wrap fetch in arrow function to prevent \"Illegal invocation\" error\n // For globalThis.fetch, call it directly to preserve 'this' context\n // For custom implementations, calling through the variable is safe\n fetch: isDefaultFetch\n ? (url: RequestInfo | URL, init?: RequestInit) => globalThis.fetch(url, init)\n : (url: RequestInfo | URL, init?: RequestInit) => fetchImpl(url, init),\n baseUrl: options.baseUrl,\n getAuthToken: options.getAuthToken,\n getProof: options.getProof,\n onTokenRefresh: options.onTokenRefresh,\n refreshToken: options.refreshToken,\n onAuthRevoked: options.onAuthRevoked,\n defaultHeaders: options.defaultHeaders,\n timeoutMs: options.timeoutMs,\n credentials: options.credentials, // Node.js doesn't have default credentials\n defaultAbortSignal: options.defaultAbortSignal,\n };\n\n return createHttpClient(transport);\n}\n\n// Universal factory that works in both environments\nexport function createUniversalHttpClient(options: {\n baseUrl: string;\n fetch?: typeof fetch;\n getAuthToken?: () => Promise<string | undefined>;\n getProof?: Transport['getProof'];\n onTokenRefresh?: (newToken: string) => Promise<void>;\n /**\n * Callback to refresh the access token when a 401 error with 'token_expired' is detected.\n * Should return the new access token, or throw an error if refresh fails.\n * If provided, the client will automatically retry the request after a successful refresh.\n */\n refreshToken?: () => Promise<string>;\n /**\n * Callback invoked when the server reports a revoked token family\n * (`x-auth-error: token_reuse` / `token_revoked`). Terminal \u2014 the request is\n * never retried; clear stored tokens and force a re-login.\n */\n onAuthRevoked?: () => Promise<void> | void;\n defaultHeaders?: Record<string, string>;\n timeoutMs?: number;\n credentials?: RequestCredentials;\n defaultAbortSignal?: AbortSignal;\n}): HttpClient {\n // Try to detect environment and use appropriate factory\n if (typeof window !== 'undefined') {\n // Browser environment\n return createBrowserHttpClient(options);\n } else {\n // Node.js environment\n return createNodeHttpClient(options);\n }\n}\n", "// Retry helper for HTTP requests\nexport interface RetryOptions {\n attempts?: number;\n}\n\n// Error types for retry logic\ninterface ErrorWithName extends Error {\n name: string;\n}\n\ninterface ErrorWithStatus extends Error {\n status: number;\n}\n\ninterface ErrorWithHeaders extends Error {\n headers?: Headers;\n}\n\n// Default retry condition - retry on network errors and 5xx status codes\nfunction defaultRetryCondition(error: Error, attempt: number): boolean {\n // Don't retry on the last attempt\n if (attempt <= 0) return false;\n\n // Distinguish timeout vs. user abort:\n // - Timeout: name === 'TimeoutError' (per spec/platforms)\n // - User abort: name === 'AbortError'\n const errorWithName = error as ErrorWithName;\n const name = errorWithName?.name;\n if (name === 'TimeoutError') return true;\n if (name === 'AbortError') return false;\n\n // HTTP 5xx and 429 (including HTTPError from web-client)\n const errorWithStatus = error as ErrorWithStatus;\n if (\n 'status' in errorWithStatus &&\n typeof errorWithStatus.status === 'number'\n ) {\n const status = errorWithStatus.status;\n return status >= 500 || status === 429;\n }\n // Network TypeError (DNS/reset) is reasonably retryable\n if (name === 'TypeError') return true;\n\n return false;\n}\n\n// Calculate delay with exponential backoff and jitter\nfunction calculateDelay(attempt: number): number {\n const baseDelayMs = 250; // Base 250ms as per spec\n const delay = baseDelayMs * Math.pow(2, attempt - 1);\n\n // Add \u00B120% jitter to reduce stampedes\n const jitter = (Math.random() - 0.5) * 0.4 * delay;\n return Math.max(0, delay + jitter);\n}\n\n// Retry helper function with new signature\nexport async function withRetry<T>(\n fn: (attempt: number) => Promise<T>,\n options: RetryOptions = {},\n): Promise<T> {\n const { attempts = 3 } = options;\n\n let lastError: Error | undefined;\n\n for (let attempt = 1; attempt <= attempts; attempt++) {\n try {\n return await fn(attempt);\n } catch (error) {\n lastError = error as Error;\n\n // Check if we should retry (this handles the last attempt check)\n if (!defaultRetryCondition(lastError, attempts - attempt)) {\n throw lastError;\n }\n\n // Calculate delay\n let delayMs = calculateDelay(attempt);\n\n // Check for Retry-After header if it's an HTTP error\n const errorWithHeaders = lastError as ErrorWithHeaders;\n const hdrs = errorWithHeaders.headers;\n const retryAfter = hdrs?.get?.('Retry-After');\n if (retryAfter) {\n // If it's a number, treat as seconds\n const seconds = parseInt(retryAfter, 10);\n if (!isNaN(seconds)) {\n delayMs = Math.max(delayMs, seconds * 1000);\n } else {\n // If it's a date, calculate the difference\n const date = new Date(retryAfter);\n if (!isNaN(date.getTime())) {\n const waitTime = Math.max(0, date.getTime() - Date.now());\n // Cap wait at 60s per attempt as per spec\n delayMs = Math.max(delayMs, Math.min(waitTime, 60000));\n }\n }\n }\n\n // Wait before retrying\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n }\n }\n\n throw lastError || new Error('Retry failed without error');\n}\n\n// Helper to create a retry-enabled HTTP client method\nexport function createRetryableMethod<T extends unknown[], R>(\n method: (...args: T) => Promise<R>,\n retryOptions: RetryOptions = {},\n) {\n return async (...args: T): Promise<R> => {\n return withRetry(() => method(...args), retryOptions);\n };\n}\n", "import { HttpClient } from '../http-client/index.js';\nimport {\n // Common types\n ApiResponse,\n // Health and Status\n HealthResponse,\n IdentityResponse,\n ProvidersResponse,\n // Authentication\n TokenRequest,\n TokenResponse,\n RefreshTokenRequest,\n // Mock Token (testing)\n MockTokenRequest,\n // Token Management\n RevokeTokenRequest,\n RevokeTokenResponse,\n // Key Management\n CreateKeyRequest,\n CreateKeyResponse,\n DeleteKeyResponse,\n RootKey,\n // Client Management\n ClientKey,\n GenerateClientKeyRequest,\n DeleteClientResponse,\n // Permissions\n PermissionResponse,\n UpdateKeyPermissionsRequest,\n} from './auth-types.js';\n\nexport class AuthApiClient {\n constructor(private httpClient: HttpClient) {}\n\n // Health and Status Endpoints\n async getHealth(): Promise<HealthResponse> {\n const response =\n await this.httpClient.get<ApiResponse<HealthResponse>>('/auth/health');\n if (!response.data) {\n throw new Error('Health response data is null');\n }\n return response.data;\n }\n\n async getIdentity(): Promise<IdentityResponse> {\n const response =\n await this.httpClient.get<ApiResponse<IdentityResponse>>(\n '/admin/identity',\n );\n if (!response.data) {\n throw new Error('Identity response data is null');\n }\n return response.data;\n }\n\n async getProviders(): Promise<ProvidersResponse> {\n const response =\n await this.httpClient.get<ApiResponse<ProvidersResponse>>(\n '/auth/providers',\n );\n if (!response.data) {\n throw new Error('Providers response data is null');\n }\n return response.data;\n }\n\n // Authentication Endpoints\n async getLoginPage(): Promise<string> {\n return this.httpClient.get<string>('/auth/login', { parse: 'text' });\n }\n\n async generateTokens(request: TokenRequest): Promise<TokenResponse> {\n return this.httpClient.post<TokenResponse>('/auth/token', request);\n }\n\n async refreshToken(request: RefreshTokenRequest): Promise<TokenResponse> {\n return this.httpClient.post<TokenResponse>('/auth/refresh', request);\n }\n\n async generateMockTokens(request: MockTokenRequest): Promise<TokenResponse> {\n return this.httpClient.post<TokenResponse>('/auth/mock-token', request);\n }\n\n async validateToken(token: string): Promise<{\n valid: boolean;\n headers: Record<string, string>;\n status: number;\n }> {\n try {\n const response = await this.validateTokenGet(token);\n return {\n valid: response.status === 200,\n headers: response.headers,\n status: response.status,\n };\n } catch (error) {\n return {\n valid: false,\n headers: {},\n status: 401,\n };\n }\n }\n\n async validateTokenGet(\n token: string,\n ): Promise<{ status: number; headers: Record<string, string> }> {\n const response = await this.httpClient.head('/auth/validate', {\n headers: { Authorization: `Bearer ${token}` },\n });\n return {\n status: response.status,\n headers: response.headers,\n };\n }\n\n // Token Management Endpoints\n // NOTE: node auth status lives on AdminApiClient.isAuthed() (/admin-api/is-authed);\n // there is no /auth/is-authed on the auth service.\n async revokeTokens(\n request: RevokeTokenRequest,\n ): Promise<RevokeTokenResponse> {\n const response = await this.httpClient.post<ApiResponse<RevokeTokenResponse>>(\n '/admin/revoke',\n request,\n );\n if (!response.data) {\n throw new Error('Revoke tokens response data is null');\n }\n return response.data;\n }\n\n // Key Management Endpoints\n async listRootKeys(): Promise<RootKey[]> {\n const response =\n await this.httpClient.get<ApiResponse<RootKey[]>>('/admin/keys');\n if (!response.data) {\n throw new Error('Root keys response data is null');\n }\n return response.data;\n }\n\n async createRootKey(request: CreateKeyRequest): Promise<CreateKeyResponse> {\n const response = await this.httpClient.post<ApiResponse<CreateKeyResponse>>(\n '/admin/keys',\n request,\n );\n if (!response.data) {\n throw new Error('Create root key response data is null');\n }\n return response.data;\n }\n\n async deleteRootKey(keyId: string): Promise<DeleteKeyResponse> {\n return this.httpClient.delete<DeleteKeyResponse>(`/admin/keys/${keyId}`);\n }\n\n // Client Management Endpoints\n async listClientKeys(): Promise<ClientKey[]> {\n const response = await this.httpClient.get<ApiResponse<ClientKey[]>>(\n '/admin/keys/clients',\n );\n if (!response.data) {\n throw new Error('Client keys response data is null');\n }\n return response.data;\n }\n\n async generateClientKey(\n request: GenerateClientKeyRequest,\n ): Promise<TokenResponse> {\n return this.httpClient.post<TokenResponse>('/admin/client-key', request);\n }\n\n async deleteClientKey(\n keyId: string,\n clientId: string,\n ): Promise<DeleteClientResponse> {\n return this.httpClient.delete<DeleteClientResponse>(\n `/admin/keys/${keyId}/clients/${clientId}`,\n );\n }\n\n // Permission Management Endpoints\n async getKeyPermissions(keyId: string): Promise<PermissionResponse> {\n return this.httpClient.get<PermissionResponse>(\n `/admin/keys/${keyId}/permissions`,\n );\n }\n\n async updateKeyPermissions(\n keyId: string,\n changes: UpdateKeyPermissionsRequest,\n ): Promise<PermissionResponse> {\n // Core expects an { add, remove } delta, not a { permissions } replacement.\n return this.httpClient.put<PermissionResponse>(\n `/admin/keys/${keyId}/permissions`,\n { add: changes.add, remove: changes.remove },\n );\n }\n}\n", "import { AuthApiClient } from './auth-client.js';\nimport { AuthApiClientConfig } from './auth-types.js';\nimport { HttpClient } from '../http-client/index.js';\n\n// Mock HTTP client for testing\nclass MockHttpClient implements HttpClient {\n async get<T>(): Promise<T> {\n throw new Error(\n 'HTTP client not implemented - use createAuthApiClientFromHttpClient with a real HTTP client',\n );\n }\n async post<T>(): Promise<T> {\n throw new Error(\n 'HTTP client not implemented - use createAuthApiClientFromHttpClient with a real HTTP client',\n );\n }\n async put<T>(): Promise<T> {\n throw new Error(\n 'HTTP client not implemented - use createAuthApiClientFromHttpClient with a real HTTP client',\n );\n }\n async delete<T>(): Promise<T> {\n throw new Error(\n 'HTTP client not implemented - use createAuthApiClientFromHttpClient with a real HTTP client',\n );\n }\n async patch<T>(): Promise<T> {\n throw new Error(\n 'HTTP client not implemented - use createAuthApiClientFromHttpClient with a real HTTP client',\n );\n }\n async head(): Promise<{ headers: Record<string, string>; status: number }> {\n throw new Error(\n 'HTTP client not implemented - use createAuthApiClientFromHttpClient with a real HTTP client',\n );\n }\n async request<T>(): Promise<T> {\n throw new Error(\n 'HTTP client not implemented - use createAuthApiClientFromHttpClient with a real HTTP client',\n );\n }\n}\n\n// Factory functions for creating Auth API clients\nexport function createBrowserAuthApiClient(\n _config: AuthApiClientConfig,\n): AuthApiClient {\n const httpClient = new MockHttpClient();\n return new AuthApiClient(httpClient);\n}\n\nexport function createNodeAuthApiClient(\n _config: AuthApiClientConfig,\n): AuthApiClient {\n const httpClient = new MockHttpClient();\n return new AuthApiClient(httpClient);\n}\n\nexport function createAuthApiClient(\n _config: AuthApiClientConfig,\n): AuthApiClient {\n const httpClient = new MockHttpClient();\n return new AuthApiClient(httpClient);\n}\n\nexport function createAuthApiClientFromHttpClient(\n httpClient: HttpClient,\n _config: AuthApiClientConfig,\n): AuthApiClient {\n return new AuthApiClient(httpClient);\n}\n", "// Member capability bitmask constants \u2014 mirrors core's `MemberCapabilities`\n// (crates/context/config/src/lib.rs). The value stored per-member is a u32\n// bitmask. Core currently assigns bits 0..=9; bits 10 and above are unassigned\n// and may be claimed by future core versions, so an application MUST NOT\n// assume any particular bit is safe for its own use unless core documents it\n// as reserved for applications.\n\n/**\n * Capability bits as defined by core's `MemberCapabilities`.\n *\n * The per-member value is a u32 bitmask. Core currently assigns bits 0..=9\n * (the entries below); bits 10 and above are unassigned \u2014 do not repurpose\n * them for application data, as a future core release may claim them.\n */\nexport const CAPABILITIES = {\n CAN_CREATE_CONTEXT: 1 << 0,\n CAN_INVITE_MEMBERS: 1 << 1,\n CAN_JOIN_OPEN_SUBGROUPS: 1 << 2,\n MANAGE_MEMBERS: 1 << 3,\n MANAGE_APPLICATION: 1 << 4,\n CAN_CREATE_SUBGROUP: 1 << 5,\n CAN_DELETE_SUBGROUP: 1 << 6,\n CAN_MANAGE_VISIBILITY: 1 << 7,\n CAN_MANAGE_METADATA: 1 << 8,\n /**\n * Publish writes attributed to ANOTHER member, under a warrant that member\n * signed \u2014 the grant delegated execution runs on.\n *\n * Implied by nothing: not by membership, not by admin, and not propagated by\n * the subgroup-admit cascade. So every group is authorship-closed until an\n * admin sets this bit, and a relay without it is refused at\n * `performIntent` before anything executes. That is why it is worth naming\n * here rather than leaving callers to write `1 << 9`: a client granting it\n * has to be able to say what it is granting.\n */\n CAN_AUTHOR_ON_BEHALF: 1 << 9,\n} as const;\n\nexport type CapabilityName = keyof typeof CAPABILITIES;\nexport type CapabilityBit = (typeof CAPABILITIES)[CapabilityName];\n\n/**\n * Returns true if `mask` has every bit of `cap` set. Both operands are\n * coerced to unsigned 32-bit (`>>> 0`) before comparing so a high bit such\n * as `1 << 31` doesn't fall foul of `&` yielding a signed result.\n */\nexport function hasCap(mask: number, cap: number): boolean {\n const capU32 = cap >>> 0;\n return ((mask & capU32) >>> 0) === capU32;\n}\n\n/** Returns `mask` with every bit of `cap` set (u32-normalized). */\nexport function withCap(mask: number, cap: number): number {\n return (mask | cap) >>> 0;\n}\n\n/** Returns `mask` with every bit of `cap` cleared (u32-normalized). */\nexport function withoutCap(mask: number, cap: number): number {\n return (mask & ~cap) >>> 0;\n}\n", "import { HttpClient, withRetry } from '../http-client/index.js';\nimport { CAPABILITIES, hasCap, withCap } from '../capabilities.js';\nimport type {\n HealthStatus,\n ReadinessStatus,\n AdminAuthStatus,\n InstallApplicationRequest,\n InstallApplicationResponseData,\n InstallDevApplicationRequest,\n UninstallApplicationResponseData,\n ListApplicationsResponseData,\n GetApplicationResponseData,\n ApplicationVersionEntry,\n GetApplicationAbiResponseData,\n GetLatestVersionResponseData,\n ListPackagesResponseData,\n ListVersionsResponseData,\n RegistryBundleManifest,\n CreateContextRequest,\n CreateContextResponseData,\n DeleteContextResponseData,\n GetContextsResponseData,\n Context,\n GenerateContextIdentityResponseData,\n GetContextIdentitiesResponseData,\n JoinContextResponseData,\n JoinSubgroupInheritanceResponseData,\n ContextGroupResponseData,\n ContextStorageResponseData,\n InviteSpecializedNodeRequest,\n InviteSpecializedNodeResponseData,\n UpdateContextApplicationRequest,\n ResyncContextRequest,\n ResyncContextResponseData,\n ContextsWithExecutorsResponseData,\n UploadBlobRequest,\n UploadBlobResponseData,\n DeleteBlobResponseData,\n ListBlobsResponseData,\n GetBlobInfoResponseData,\n BlobReadOptions,\n CreateContextAliasRequest,\n CreateApplicationAliasRequest,\n CreateDeviceAliasRequest,\n CreateAliasResponseData,\n LookupAliasResponseData,\n DeleteAliasResponseData,\n ListAliasesResponseData,\n ListNamespacesResponseData,\n CreateNamespaceRequest,\n CreateNamespaceResponseData,\n DeleteNamespaceResponseData,\n CreateNamespaceInvitationRequest,\n CreateNamespaceInvitationResponseData,\n CreateRecursiveInvitationResponseData,\n JoinNamespaceRequest,\n JoinNamespaceResponseData,\n CreateGroupInNamespaceRequest,\n CreateGroupInNamespaceResponseData,\n SubgroupEntry,\n Namespace,\n NamespaceIdentity,\n NodeIdentity,\n AccountPairInitRequest,\n AccountPairInitResponseData,\n AccountPairCompleteRequest,\n AccountPairCompleteResponseData,\n AccountSignWithRootRequest,\n AccountSignWithRootResponseData,\n RelinkDeviceRequest,\n RelinkDeviceResponseData,\n RescopeDeviceRequest,\n RescopeDeviceResponseData,\n LabelDeviceRequest,\n LabelDeviceResponseData,\n AccountDeviceEntry,\n AccountApplicationEntry,\n RevokeAccountDeviceRequest,\n RevokeAccountDeviceResponseData,\n GroupInfoResponseData,\n CreateGroupRequest,\n CreateGroupResponseData,\n DeleteGroupResponseData,\n ListGroupMembersResponseData,\n ListGroupContextsResponseData,\n AddGroupMembersRequest,\n RemoveGroupMembersRequest,\n UpdateMemberRoleRequest,\n MemberCapabilities,\n SetMemberCapabilitiesRequest,\n SetDefaultCapabilitiesRequest,\n SetMemberAutoFollowRequest,\n SetSubgroupVisibilityRequest,\n SetTeeAdmissionPolicyRequest,\n GetTeeAdmissionPolicyResponseData,\n SetTeeAuthoringPolicyRequest,\n SetGroupMetadataRequest,\n SetMemberMetadataRequest,\n SetContextMetadataRequest,\n MetadataRecord,\n GetMetadataResponseData,\n SyncGroupResponseData,\n UpgradeGroupRequest,\n UpgradeGroupResponseData,\n GroupUpgradeStatusResponseData,\n MigrationStatus,\n CascadeStatusEntry,\n RetryGroupUpgradeResponseData,\n ReparentGroupRequest,\n ReparentGroupResponseData,\n IssueOwnershipProofRequest,\n IssueNamespaceOwnershipProofRequest,\n IssueOwnershipProofResponseData,\n CreateGroupInvitationRequest,\n CreateGroupInvitationResponseData,\n CreateRecursiveGroupInvitationResponseData,\n JoinGroupRequest,\n JoinGroupResponseData,\n TeeInfoResponseData,\n TeeAttestRequest,\n TeeAttestResponseData,\n TeeVerifyQuoteRequest,\n TeeVerifyQuoteResponseData,\n PerformIntentRequest,\n PerformIntentResponseData,\n IntentRelayInfo,\n QueryContextRequest,\n QueryContextResponseData,\n ListMemberDevicesOptions,\n ListMemberDevicesResponseData,\n SealToAccountRequest,\n SealedEnvelope,\n AdmitJoinRequest,\n AdmitJoinResponseData,\n} from './admin-types.js';\n\n/**\n * Helper: server wraps most responses in `{ data: T }`.\n * This extracts `.data` so callers get the inner payload directly. Null-safe so an\n * empty 2xx body (parsed as null) yields undefined instead of throwing.\n */\nfunction unwrap<T>(response: { data: T }): T {\n return response?.data as T;\n}\n\n/**\n * Compare two version strings by SemVer 2.0 precedence, ascending: negative if\n * `a < b`, positive if `a > b`, `0` if equal. Build metadata (`+...`) is stripped\n * and ignored. The numeric `major.minor.patch` core is compared field-by-field\n * (numerically when both parse, so `1.10.0 > 1.9.0`; a missing field is `0`). A\n * version WITH a pre-release ranks below the same core without one, and\n * pre-release identifiers compare per spec: dot-separated, numeric identifiers\n * numerically, alphanumeric lexically by ASCII, numeric below alphanumeric, and a\n * shorter set of identifiers loses when all preceding fields are equal.\n */\nexport function compareSemver(a: string, b: string): number {\n const [coreA, preA] = splitVersion(a);\n const [coreB, preB] = splitVersion(b);\n\n const na = coreA.split('.');\n const nb = coreB.split('.');\n const n = Math.max(na.length, nb.length);\n for (let i = 0; i < n; i++) {\n const c = compareSemverId(na[i] ?? '0', nb[i] ?? '0');\n if (c !== 0) return c;\n }\n\n // A pre-release ranks below the same core version with no pre-release.\n if (preA === undefined && preB === undefined) return 0;\n if (preA === undefined) return 1;\n if (preB === undefined) return -1;\n\n const pa = preA.split('.');\n const pb = preB.split('.');\n const m = Math.max(pa.length, pb.length);\n for (let i = 0; i < m; i++) {\n if (i >= pa.length) return -1; // shorter set loses when all preceding equal\n if (i >= pb.length) return 1;\n const c = compareSemverId(pa[i], pb[i]);\n if (c !== 0) return c;\n }\n return 0;\n}\n\n/** Drop build metadata and split the pre-release tail: `1.2.3-rc.1+b` -> `['1.2.3', 'rc.1']`. */\nfunction splitVersion(v: string): [string, string | undefined] {\n const core = v.split('+', 1)[0];\n const dash = core.indexOf('-');\n return dash === -1 ? [core, undefined] : [core.slice(0, dash), core.slice(dash + 1)];\n}\n\n/**\n * Compare one identifier: numerically when both are all-digits, else lexically by\n * ASCII with a numeric identifier ranking below an alphanumeric one. Fallback: a\n * non-numeric core component (loose/malformed input) is treated as a string, so\n * comparison never throws.\n */\nfunction compareSemverId(a: string, b: string): number {\n const na = /^\\d+$/.test(a);\n const nb = /^\\d+$/.test(b);\n if (na && nb) {\n const d = Number(a) - Number(b);\n return d < 0 ? -1 : d > 0 ? 1 : 0;\n }\n if (na) return -1;\n if (nb) return 1;\n return a < b ? -1 : a > b ? 1 : 0;\n}\n\n/**\n * Fetch a URL from the (foreign-origin) registry with a per-attempt timeout and\n * retry on transient failures. Deliberately NOT routed through the node\n * `HttpClient`: that client attaches the node's bearer token to every request,\n * which must never leak to the registry's origin. Each attempt times out after\n * ~10s; network errors and 5xx are retried (2 retries, short backoff via the\n * shared `withRetry`), while a 4xx is a definitive answer and is not retried.\n * `kind`/`context` shape the error message so failures stay debuggable in logs.\n */\nasync function fetchRegistry(url: string, kind: string, context: string): Promise<Response> {\n return withRetry(\n async () => {\n const resp = await fetch(url, { signal: AbortSignal.timeout(10000) });\n if (!resp.ok) {\n // Carry `status` so withRetry retries 5xx but leaves 4xx to throw.\n const err = new Error(\n `registry ${kind} fetch failed (${resp.status}) for ${context}`,\n ) as Error & { status: number };\n err.status = resp.status;\n throw err;\n }\n return resp;\n },\n { attempts: 3 },\n );\n}\n\nexport class AdminApiClient {\n constructor(private httpClient: HttpClient) {}\n\n // ---- Health and Status (public, no auth) ----\n\n async healthCheck(): Promise<HealthStatus> {\n return unwrap(await this.httpClient.get<{ data: HealthStatus }>('/admin-api/health'));\n }\n\n /**\n * Whether the node has finished starting and its store answers. Public, like\n * {@link healthCheck}; unlike it, a node that is up but not ready answers\n * `503`, which this surfaces as an `HTTPError` whose body names the stage.\n */\n async readinessCheck(): Promise<ReadinessStatus> {\n return unwrap(await this.httpClient.get<{ data: ReadinessStatus }>('/admin-api/ready'));\n }\n\n async isAuthed(): Promise<AdminAuthStatus> {\n return this.httpClient.get<AdminAuthStatus>('/admin-api/is-authed');\n }\n\n // ---- Application Management ----\n\n /**\n * Install a published application by its `package@version` coordinates. The\n * node resolves and fetches them from its own configured `[registry]`, so no\n * URL is passed and none is accepted. A 502 means the node's source has\n * nothing published at those coordinates. This is the discrete \"download\"\n * step an Updates flow pairs with a subsequent `upgradeGroup`.\n */\n async installApplication(request: InstallApplicationRequest): Promise<InstallApplicationResponseData> {\n return unwrap(await this.httpClient.post<{ data: InstallApplicationResponseData }>('/admin-api/install-application', request));\n }\n\n /**\n * List a package's published versions from the registry, newest-first by\n * semver. Reads the registry's V2 bundle listing\n * (`GET {registry}/api/v2/bundles?package={package}`), taking each bundle's\n * `appVersion`. Registry-side data \u2014 distinct from the node's\n * installed-version list \u2014 and the source an Updates view compares against\n * the running `Context.applicationVersion` to detect \"a new version exists\".\n */\n async getRegistryVersions(registryUrl: string, packageName: string): Promise<string[]> {\n const url = new URL('/api/v2/bundles', new URL(registryUrl).origin);\n url.searchParams.set('package', packageName);\n const resp = await fetchRegistry(url.toString(), 'versions', packageName);\n const bundles = (await resp.json()) as RegistryBundleManifest[];\n return (Array.isArray(bundles) ? bundles : [])\n .map((b) => b.appVersion)\n .filter((v): v is string => typeof v === 'string')\n .sort((a, b) => compareSemver(b, a));\n }\n\n async installDevApplication(request: InstallDevApplicationRequest): Promise<InstallApplicationResponseData> {\n return unwrap(await this.httpClient.post<{ data: InstallApplicationResponseData }>('/admin-api/install-dev-application', request));\n }\n\n async uninstallApplication(appId: string): Promise<UninstallApplicationResponseData> {\n return unwrap(await this.httpClient.delete<{ data: UninstallApplicationResponseData }>(`/admin-api/applications/${appId}`));\n }\n\n async listApplications(): Promise<ListApplicationsResponseData> {\n return unwrap(await this.httpClient.get<{ data: ListApplicationsResponseData }>('/admin-api/applications'));\n }\n\n async getApplication(appId: string): Promise<GetApplicationResponseData> {\n return unwrap(await this.httpClient.get<{ data: GetApplicationResponseData }>(`/admin-api/applications/${appId}`));\n }\n\n /**\n * Installed-blob inventory for an application \u2014 one entry per locally installed\n * version. This is the *installed* inventory (source for a \"pick a version to\n * pin\" UI); the registry equivalent is `listPackageVersions`.\n */\n async listApplicationVersions(applicationId: string): Promise<ApplicationVersionEntry[]> {\n return unwrap(\n await this.httpClient.get<{ data: ApplicationVersionEntry[] }>(`/admin-api/applications/${applicationId}/versions`),\n );\n }\n\n /**\n * The application's embedded `wasm-abi/1` manifest. Omit `serviceName` for\n * single-service apps; core 400s if the ABI is absent or the name is ambiguous.\n */\n async getApplicationAbi(applicationId: string, serviceName?: string): Promise<GetApplicationAbiResponseData> {\n const query = serviceName ? `?service_name=${encodeURIComponent(serviceName)}` : '';\n return unwrap(\n await this.httpClient.get<{ data: GetApplicationAbiResponseData }>(\n `/admin-api/applications/${applicationId}/abi${query}`,\n ),\n );\n }\n\n // ---- Package Management ----\n\n async listPackages(): Promise<ListPackagesResponseData> {\n // Core returns this flat ({ packages: [...] }), not under `data`; tolerate both.\n const r = await this.httpClient.get<ListPackagesResponseData & { data?: ListPackagesResponseData }>(\n '/admin-api/packages',\n );\n return (r?.data ?? r) as ListPackagesResponseData;\n }\n\n async listPackageVersions(packageName: string): Promise<ListVersionsResponseData> {\n // Core returns this flat ({ versions: [...] }), not under `data`; tolerate both.\n const r = await this.httpClient.get<ListVersionsResponseData & { data?: ListVersionsResponseData }>(\n `/admin-api/packages/${encodeURIComponent(packageName)}/versions`,\n );\n return (r?.data ?? r) as ListVersionsResponseData;\n }\n\n async getLatestPackageVersion(packageName: string): Promise<GetLatestVersionResponseData> {\n return this.httpClient.get<GetLatestVersionResponseData>(\n `/admin-api/packages/${encodeURIComponent(packageName)}/latest`,\n );\n }\n\n // ---- Context Management ----\n\n async createContext(request: CreateContextRequest): Promise<CreateContextResponseData> {\n // Core requires `initializationParams` (no default); default it to an empty\n // byte array so callers that pass none don't get a 400.\n const body = { ...request, initializationParams: request.initializationParams ?? [] };\n return unwrap(await this.httpClient.post<{ data: CreateContextResponseData }>('/admin-api/contexts', body));\n }\n\n async deleteContext(contextId: string): Promise<DeleteContextResponseData> {\n return unwrap(await this.httpClient.delete<{ data: DeleteContextResponseData }>(`/admin-api/contexts/${contextId}`));\n }\n\n async getContexts(): Promise<GetContextsResponseData> {\n return unwrap(await this.httpClient.get<{ data: GetContextsResponseData }>('/admin-api/contexts'));\n }\n\n async getContext(contextId: string): Promise<Context> {\n return unwrap(await this.httpClient.get<{ data: Context }>(`/admin-api/contexts/${contextId}`));\n }\n\n async getContextsForApplication(applicationId: string): Promise<GetContextsResponseData> {\n return unwrap(await this.httpClient.get<{ data: GetContextsResponseData }>(`/admin-api/contexts/for-application/${applicationId}`));\n }\n\n // ---- Context Identity ----\n\n async generateContextIdentity(): Promise<GenerateContextIdentityResponseData> {\n return unwrap(await this.httpClient.post<{ data: GenerateContextIdentityResponseData }>('/admin-api/identity/context', {}));\n }\n\n async getContextIdentities(contextId: string): Promise<GetContextIdentitiesResponseData> {\n return unwrap(await this.httpClient.get<{ data: GetContextIdentitiesResponseData }>(`/admin-api/contexts/${contextId}/identities`));\n }\n\n async getContextIdentitiesOwned(contextId: string): Promise<GetContextIdentitiesResponseData> {\n return unwrap(await this.httpClient.get<{ data: GetContextIdentitiesResponseData }>(`/admin-api/contexts/${contextId}/identities-owned`));\n }\n\n // ---- Context join (group membership) ----\n\n async joinContext(contextId: string): Promise<JoinContextResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: JoinContextResponseData }>(`/admin-api/contexts/${contextId}/join`, {}),\n );\n }\n\n // ---- Context group / storage / sync ----\n\n async getContextGroup(contextId: string): Promise<ContextGroupResponseData> {\n return unwrap(await this.httpClient.get<{ data: ContextGroupResponseData }>(`/admin-api/contexts/${contextId}/group`));\n }\n\n async getContextStorage(contextId: string): Promise<ContextStorageResponseData> {\n return unwrap(await this.httpClient.get<{ data: ContextStorageResponseData }>(`/admin-api/contexts/${contextId}/storage`));\n }\n\n /** Sync one context, or every context when `contextId` is omitted. */\n async syncContext(contextId?: string): Promise<void> {\n // Sync-all is `/contexts/sync` with no trailing slash - core routes the two\n // spellings separately and 404s the trailing one.\n const path = contextId ? `/admin-api/contexts/sync/${contextId}` : '/admin-api/contexts/sync';\n await this.httpClient.post(path, {});\n }\n\n /**\n * Kick off a full state re-pull for a context (operator recovery for a\n * stranded context). `force` re-pulls even when the node does not flag the\n * context as stranded.\n */\n async resyncContext(\n contextId: string,\n request: ResyncContextRequest = {},\n ): Promise<ResyncContextResponseData> {\n // Core's `ResyncContextApiResponse` is a flat payload (no inner `data`\n // field), so parse the body directly \u2014 do NOT `unwrap`, or `resyncStarted`\n // reads as undefined and the resync silently appears to never start.\n const r = await this.httpClient.post<ResyncContextResponseData | null>(\n `/admin-api/contexts/${contextId}/resync`,\n request,\n );\n // An empty 2xx body means the resync was accepted; synthesize the result so\n // callers always get a typed value instead of null.\n return r ?? { contextId, resyncStarted: true };\n }\n\n async inviteSpecializedNode(request: InviteSpecializedNodeRequest): Promise<InviteSpecializedNodeResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: InviteSpecializedNodeResponseData }>(\n '/admin-api/contexts/invite-specialized-node',\n request,\n ),\n );\n }\n\n async updateContextApplication(\n contextId: string,\n request: UpdateContextApplicationRequest,\n ): Promise<void> {\n await this.httpClient.post(`/admin-api/contexts/${contextId}/application`, request);\n }\n\n async getContextsWithExecutorsForApplication(applicationId: string): Promise<ContextsWithExecutorsResponseData> {\n // Core returns this flat as { contexts: [...] } (not a bare array under `data`).\n const r = await this.httpClient.get<\n | ContextsWithExecutorsResponseData\n | { contexts: ContextsWithExecutorsResponseData }\n | { data: ContextsWithExecutorsResponseData }\n >(`/admin-api/contexts/with-executors/for-application/${applicationId}`);\n if (Array.isArray(r)) return r;\n return (\n (r as { contexts?: ContextsWithExecutorsResponseData } | null)?.contexts ??\n (r as { data?: ContextsWithExecutorsResponseData } | null)?.data ??\n []\n );\n }\n\n // ---- Blob Management ----\n\n async uploadBlob(request: UploadBlobRequest): Promise<UploadBlobResponseData> {\n // Core streams the raw request body into blob storage (no JSON) and takes\n // its params from the query string (`hash`, `context_id` \u2014 snake_case).\n const params = new URLSearchParams();\n if (request.hash) params.set('hash', request.hash);\n if (request.contextId) params.set('context_id', request.contextId);\n const query = params.toString();\n const path = query ? `/admin-api/blobs?${query}` : '/admin-api/blobs';\n // request.data (Uint8Array view | ArrayBuffer | Blob) is a valid BodyInit and\n // is streamed verbatim \u2014 no JSON, no cast. fetch honors a Uint8Array view's\n // byteOffset/byteLength, so subarrays upload the correct region.\n // Core's BlobInfo is snake_case (`blob_id`); map to camelCase like deleteBlob.\n const res = unwrap(\n await this.httpClient.request<{ data: { blob_id: string; size: number } }>(path, {\n method: 'PUT',\n body: request.data,\n headers: { 'Content-Type': 'application/octet-stream' },\n }),\n );\n return { blobId: res.blob_id, size: res.size };\n }\n\n async deleteBlob(blobId: string): Promise<DeleteBlobResponseData> {\n // Core's `BlobDeleteResponse` is a flat, snake_case payload (`{ blob_id,\n // deleted }`) \u2014 the lone admin DTO without a camelCase rename and without an\n // inner `data` field. Parse directly (no `unwrap`) and map to camelCase.\n const body = await this.httpClient.delete<{ blob_id: string; deleted: boolean }>(\n `/admin-api/blobs/${blobId}`,\n );\n return { blobId: body.blob_id, deleted: body.deleted };\n }\n\n async listBlobs(): Promise<ListBlobsResponseData> {\n // Core's BlobInfo is snake_case (`blob_id`); map to camelCase.\n const res = unwrap(\n await this.httpClient.get<{ data: { blobs: Array<{ blob_id: string; size: number }> } }>(\n '/admin-api/blobs',\n ),\n );\n return { blobs: res.blobs.map((b) => ({ blobId: b.blob_id, size: b.size })) };\n }\n\n /**\n * Blob-read query params. `context_id` is the same snake_case param\n * {@link uploadBlob} announces with; building it in one place keeps the read\n * side from drifting from the write side.\n */\n private blobPath(blobId: string, options?: BlobReadOptions): string {\n if (!options?.contextId) return `/admin-api/blobs/${blobId}`;\n const params = new URLSearchParams({ context_id: options.contextId });\n return `/admin-api/blobs/${blobId}?${params.toString()}`;\n }\n\n /**\n * Download a blob's raw bytes. `GET /admin-api/blobs/:id` streams the blob\n * content (e.g. `application/gzip`), NOT JSON \u2014 so fetch it as an ArrayBuffer.\n * Use {@link listBlobs} for `{ blobId, size }` metadata.\n *\n * Without `contextId` the read is local-only: the node answers from its own\n * blob store and returns immediately, or 404s. Passing `contextId` opts into\n * network discovery \u2014 the node probes that context's peers (availability\n * nodes first) for a holder, then transfers the bytes. Budget for it: core\n * bounds discovery by a ~30s deadline, and the transfer is on top of that, so\n * the worst case is well past what a UI should block on unhinted.\n */\n async getBlob(blobId: string, options?: BlobReadOptions): Promise<ArrayBuffer> {\n return this.httpClient.get<ArrayBuffer>(this.blobPath(blobId, options), {\n parse: 'arrayBuffer',\n });\n }\n\n /**\n * Fetch a blob's metadata without downloading it. `HEAD /admin-api/blobs/:id`\n * returns the info in response headers (size via `content-length`, plus\n * `x-blob-id`/`x-blob-hash`/`x-blob-mime-type`).\n *\n * Without `contextId` this is a local-only presence check that returns\n * immediately. Passing `contextId` opts into the same network discovery\n * {@link getBlob} uses \u2014 presence and size answered by probing the context's\n * peers, with no transfer \u2014 so it inherits core's ~30s discovery deadline as\n * its worst case. A probe only carries `found` and `size`, so a peer-sourced\n * answer has no `hash`/`mimeType`; read `source` to tell the two apart.\n */\n async getBlobInfo(\n blobId: string,\n options?: BlobReadOptions,\n ): Promise<GetBlobInfoResponseData> {\n // HEAD throws (HttpError) on a non-2xx status, so we only reach here on success\n // \u2014 the x-blob-* headers are present. Guard size against a missing/non-numeric\n // content-length anyway (defaults to 0 rather than NaN).\n const { headers } = await this.httpClient.head(this.blobPath(blobId, options));\n const size = Number(headers['content-length']);\n return {\n blobId: headers['x-blob-id'] ?? blobId,\n size: Number.isFinite(size) ? size : 0,\n hash: headers['x-blob-hash'],\n mimeType: headers['x-blob-mime-type'],\n // Nodes that predate the context-aware HEAD omit this header entirely, so\n // it stays undefined rather than being defaulted to 'local' \u2014 absent means\n // \"the node didn't say\", which is not the same claim as \"served locally\".\n source: headers['x-blob-source'] as GetBlobInfoResponseData['source'],\n };\n }\n\n // ---- Alias Management ----\n\n async createContextAlias(\n request: CreateContextAliasRequest,\n ): Promise<CreateAliasResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: CreateAliasResponseData }>(\n '/admin-api/alias/create/context',\n { alias: request.alias, contextId: request.contextId },\n ),\n );\n }\n\n async createApplicationAlias(\n request: CreateApplicationAliasRequest,\n ): Promise<CreateAliasResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: CreateAliasResponseData }>(\n '/admin-api/alias/create/application',\n { alias: request.alias, applicationId: request.applicationId },\n ),\n );\n }\n\n async lookupContextAlias(name: string): Promise<LookupAliasResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: LookupAliasResponseData }>(\n `/admin-api/alias/lookup/context/${encodeURIComponent(name)}`,\n {},\n ),\n );\n }\n\n async lookupApplicationAlias(name: string): Promise<LookupAliasResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: LookupAliasResponseData }>(\n `/admin-api/alias/lookup/application/${encodeURIComponent(name)}`,\n {},\n ),\n );\n }\n\n async deleteContextAlias(name: string): Promise<DeleteAliasResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: DeleteAliasResponseData }>(\n `/admin-api/alias/delete/context/${encodeURIComponent(name)}`,\n {},\n ),\n );\n }\n\n async deleteApplicationAlias(name: string): Promise<DeleteAliasResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: DeleteAliasResponseData }>(\n `/admin-api/alias/delete/application/${encodeURIComponent(name)}`,\n {},\n ),\n );\n }\n\n async listContextAliases(): Promise<ListAliasesResponseData> {\n return unwrap(await this.httpClient.get<{ data: ListAliasesResponseData }>('/admin-api/alias/list/context'));\n }\n\n async listApplicationAliases(): Promise<ListAliasesResponseData> {\n return unwrap(await this.httpClient.get<{ data: ListAliasesResponseData }>('/admin-api/alias/list/application'));\n }\n\n async createDeviceAlias(request: CreateDeviceAliasRequest): Promise<CreateAliasResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: CreateAliasResponseData }>(\n '/admin-api/alias/create/device',\n { alias: request.alias, deviceId: request.deviceId },\n ),\n );\n }\n\n async lookupDeviceAlias(name: string): Promise<LookupAliasResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: LookupAliasResponseData }>(\n `/admin-api/alias/lookup/device/${encodeURIComponent(name)}`,\n {},\n ),\n );\n }\n\n async deleteDeviceAlias(name: string): Promise<DeleteAliasResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: DeleteAliasResponseData }>(\n `/admin-api/alias/delete/device/${encodeURIComponent(name)}`,\n {},\n ),\n );\n }\n\n async listDeviceAliases(): Promise<ListAliasesResponseData> {\n return unwrap(await this.httpClient.get<{ data: ListAliasesResponseData }>('/admin-api/alias/list/device'));\n }\n\n // ---- Namespace Management ----\n\n async listNamespaces(): Promise<ListNamespacesResponseData> {\n return unwrap(await this.httpClient.get<{ data: ListNamespacesResponseData }>('/admin-api/namespaces'));\n }\n\n async getNamespace(namespaceId: string): Promise<Namespace> {\n return unwrap(await this.httpClient.get<{ data: Namespace }>(`/admin-api/namespaces/${namespaceId}`));\n }\n\n /**\n * Who this node is \u2014 the account it writes as, the device it is, the key it\n * signs with, and the account root a second device pairs against.\n *\n * Takes no namespace, and that is the point: one root key is one account\n * everywhere, and a node signs with one key, so none of it varies by\n * namespace.\n */\n async getNodeIdentity(): Promise<NodeIdentity> {\n return unwrap(await this.httpClient.get<{ data: NodeIdentity }>('/admin-api/identity'));\n }\n\n /**\n * @deprecated Use {@link getNodeIdentity} instead. Every field this returns is\n * node-level, so the namespace was only ever decoration \u2014 it now delegates to\n * the node-level route and reshapes the answer.\n *\n * Three consequences of that delegation, all visible to a caller who relied on\n * the old route's behaviour:\n *\n * - **No participation gate.** The namespace route answered 404 when this node\n * had never joined the namespace; this answers whatever the node's identity\n * is, regardless. If you were using it to ask \"am I in this namespace?\", ask\n * {@link listNamespaces} instead \u2014 that is the question you were really\n * asking.\n * - **`namespaceId` is echoed verbatim.** The old route resolved a subgroup id\n * to its root namespace and returned that; passing a subgroup id here gives\n * the subgroup id back.\n * - **404 means something else.** Previously \"not my namespace\", now \"this node\n * holds no account root yet\".\n */\n async getNamespaceIdentity(namespaceId: string): Promise<NamespaceIdentity> {\n const node = await this.getNodeIdentity();\n return { namespaceId, publicKey: node.publicKey, account: node.accountId };\n }\n\n /**\n * Ask this node to run one method on a member's behalf, under a warrant that\n * member signed.\n *\n * The node executes and signs the envelope with its own key; the change is\n * attributed to the **author**. Both halves travel together, so every peer\n * re-checks that the member consented instead of trusting the relay.\n *\n * Three things have to be true first, and none is implied by the others:\n *\n * - the author's **account** is a member of the group owning the context \u2014\n * their device joins nothing and is in no group's binding rows, which is\n * exactly the case a device certificate covers;\n * - this node holds `CAN_AUTHOR_ON_BEHALF` on that group, which is implied by\n * neither membership nor admin;\n * - the warrant has not been spent. It is single-use: presenting a valid one\n * twice is refused, because the signature stays valid forever and replay is\n * not forgery.\n *\n * A refusal comes back as a `403` carrying the reason \u2014 \"an admin must grant\n * CAN_AUTHOR_ON_BEHALF to \u2026\", or that the nonce is already spent \u2014 so a caller\n * can tell which precondition failed rather than only that one did.\n *\n * Mint the warrant with {@link signWarrant}; see\n * {@link PerformIntentRequest.warrant}.\n */\n async performIntent(\n contextId: string,\n request: PerformIntentRequest,\n ): Promise<PerformIntentResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: PerformIntentResponseData }>(\n `/admin-api/contexts/${contextId}/intents`,\n request,\n ),\n );\n }\n\n /**\n * Read a context as the session's ACCOUNT, without minting a warrant.\n *\n * Needs an account-authenticated session \u2014 one from {@link login}, not the\n * admin password login \u2014 and refuses otherwise with a `401`. The account must\n * be a member of the group owning the context (`403` if not), and the method\n * must be declared read-only in the app's ABI (`409` if not; a write needs\n * {@link performIntent}).\n */\n async queryContext(\n contextId: string,\n request: QueryContextRequest,\n ): Promise<QueryContextResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: QueryContextResponseData }>(\n `/admin-api/contexts/${contextId}/query`,\n request,\n ),\n );\n }\n\n /**\n * Ask what this node can do for a member in `contextId`, before they sign\n * anything.\n *\n * The two facts {@link performIntent} needs and a client cannot derive: whose\n * account goes in the warrant's `executor`, and whether this node holds\n * `CAN_AUTHOR_ON_BEHALF` on the owning group. `canAuthorOnBehalf: false` comes\n * back as an answer rather than an error \u2014 it is the default state of every\n * context \u2014 so a caller can say \"an admin of `groupId` must grant it to\n * `executorAccount`\" instead of presenting a warrant that will be refused\n * after the author has spent a nonce on it.\n *\n * `404` means the context belongs to no group, or this node holds no account\n * yet; either way it can be named as no warrant's executor.\n *\n * For a caller with no credential on the node at all \u2014 a browser tab holding\n * one signing key \u2014 use `RelayClient`, which reads this same route on a node\n * run as a relay.\n */\n async getIntentRelay(contextId: string): Promise<IntentRelayInfo> {\n return unwrap(\n await this.httpClient.get<{ data: IntentRelayInfo }>(\n `/admin-api/contexts/${contextId}/intents`,\n ),\n );\n }\n\n async listNamespacesForApplication(applicationId: string): Promise<ListNamespacesResponseData> {\n return unwrap(await this.httpClient.get<{ data: ListNamespacesResponseData }>(`/admin-api/namespaces/for-application/${applicationId}`));\n }\n\n async createNamespace(request: CreateNamespaceRequest): Promise<CreateNamespaceResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: CreateNamespaceResponseData }>('/admin-api/namespaces', request),\n );\n }\n\n async deleteNamespace(namespaceId: string): Promise<DeleteNamespaceResponseData> {\n // Core requires `Content-Type: application/json` on this DELETE even though\n // the body is always empty, so send the header and an empty JSON object.\n return unwrap(\n await this.httpClient.request<{ data: DeleteNamespaceResponseData }>(`/admin-api/namespaces/${namespaceId}`, {\n method: 'DELETE',\n body: '{}',\n headers: { 'Content-Type': 'application/json' },\n }),\n );\n }\n\n async createNamespaceInvitation(\n namespaceId: string,\n request?: CreateNamespaceInvitationRequest,\n ): Promise<CreateNamespaceInvitationResponseData | CreateRecursiveInvitationResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: CreateNamespaceInvitationResponseData | CreateRecursiveInvitationResponseData }>(\n `/admin-api/namespaces/${namespaceId}/invite`,\n request ?? {},\n ),\n );\n }\n\n /**\n * Present a join this caller signed to a node the inviter named as an\n * admitter.\n *\n * For a member with no node. `joinNamespace` publishes the membership op from\n * the node making the call; this hands an already-signed op to somebody else's\n * node to publish. The distinction matters because a keyholder has nowhere to\n * publish from.\n *\n * The op must be signed by the **device key** in the credential it carries \u2014\n * every peer checks that when applying a join. So the admitter cannot author\n * this, only carry it: a hostile one can refuse, and nothing else. It cannot\n * admit a different account, change the group, or grant a role.\n *\n * @param namespaceId the namespace being joined, 64 hex\n * @param request the invitation and the hex-encoded signed op\n */\n async admitJoin(\n namespaceId: string,\n request: AdmitJoinRequest,\n ): Promise<AdmitJoinResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: AdmitJoinResponseData }>(\n `/admin-api/namespaces/${namespaceId}/admit`,\n request,\n { timeoutMs: 65000 },\n ),\n );\n }\n\n async joinNamespace(\n namespaceId: string,\n request: JoinNamespaceRequest,\n ): Promise<JoinNamespaceResponseData> {\n const data = unwrap(\n await this.httpClient.post<{ data: JoinNamespaceResponseData }>(\n `/admin-api/namespaces/${namespaceId}/join`,\n request,\n { timeoutMs: 65000 },\n ),\n );\n\n // core 0.11.0-rc.25 renamed this response's `groupId` to `namespaceId`\n // (core#3598). Bind whichever spelling the node actually sent rather than\n // picking one: the rename lands as `undefined` on the field the client\n // reads, and nothing errors \u2014 no 4xx, no thrown parse \u2014 so a client that\n // knows only one spelling fails much later and somewhere else. Normalising\n // here means one mero-js works against nodes either side of that release.\n if (data && data.namespaceId === undefined && data.groupId !== undefined) {\n return { ...data, namespaceId: data.groupId };\n }\n return data;\n }\n\n async createGroupInNamespace(\n namespaceId: string,\n request?: CreateGroupInNamespaceRequest,\n ): Promise<CreateGroupInNamespaceResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: CreateGroupInNamespaceResponseData }>(\n `/admin-api/namespaces/${namespaceId}/groups`,\n request ?? {},\n ),\n );\n }\n\n async listNamespaceGroups(namespaceId: string): Promise<SubgroupEntry[]> {\n return unwrap(await this.httpClient.get<{ data: SubgroupEntry[] }>(`/admin-api/namespaces/${namespaceId}/groups`));\n }\n\n // ---- Account Devices & Pairing ----\n\n /**\n * Mint this node a device for an account that already lives somewhere else -\n * the first half of pairing, run on the joining node.\n *\n * Publishes nothing and needs no key: it produces the device id, the two\n * public keys and the signature that the account holder's\n * {@link completeAccountPairing} certifies, plus a code to read out to them\n * over a channel the payload does not travel on. Until that second half runs,\n * the device this mints is inert.\n *\n * Takes a set of namespaces rather than one, and takes it from the caller\n * because this node cannot discover it: a node that is a member of nothing can\n * neither read the account's namespace set off a DAG nor derive it. One device\n * covers however many are named - the certificate is over the account, not a\n * scope - so there is one id and one code to hand over regardless.\n */\n async initAccountPairing(request: AccountPairInitRequest): Promise<AccountPairInitResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: AccountPairInitResponseData }>('/admin-api/account/pair-init', request),\n );\n }\n\n /**\n * Certify a device another node minted, link it, and deliver its scope keys -\n * the second half of pairing, run on the node that holds the account root.\n *\n * Every field but `applications` is copied verbatim out of that node's\n * {@link initAccountPairing} answer. Only this side can complete it: the\n * certificate is signed by the account root, which never leaves the node that\n * holds it.\n *\n * The scope is named in applications, not namespaces, because that is the\n * question a person can answer - a namespace is an implementation unit they\n * never chose. Leave it out to grant every application.\n */\n async completeAccountPairing(\n request: AccountPairCompleteRequest,\n ): Promise<AccountPairCompleteResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: AccountPairCompleteResponseData }>('/admin-api/account/pair-complete', request),\n );\n }\n\n /**\n * Re-run the pairing fan-out for a device this account already certified,\n * against the namespaces this node takes part in now.\n *\n * Pairing is a snapshot, and this is how it is repeated. A namespace created\n * or joined afterwards holds no binding for a device paired before it, and the\n * symptom is silence - the device simply never sees that namespace. Nothing\n * but the id is needed to repair it: the certificate is already held here,\n * root-signed and naming no namespace, so a fresh endorsement and a key wrap\n * are all a later namespace is missing. The device does not have to be online.\n *\n * Passing `applications` widens the device's stored scope as well as\n * repairing. Passing none - or an empty list - repairs against the scope\n * already stored, which is deliberately NOT the \"every application\" that an\n * empty list means on {@link completeAccountPairing}: overloading it here\n * would make the accidental request the widest one.\n */\n async relinkAccountDevice(\n deviceId: string,\n request?: RelinkDeviceRequest,\n ): Promise<RelinkDeviceResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: RelinkDeviceResponseData }>(\n `/admin-api/account/devices/${deviceId}/relink`,\n request ?? {},\n ),\n );\n }\n\n /**\n * Replace a device's scope outright, in one root-signed op.\n *\n * {@link relinkAccountDevice} only ever adds applications; this is how a\n * scope is narrowed - or an `all` device is given a fixed list instead. The\n * device need not be online.\n */\n async rescopeAccountDevice(\n deviceId: string,\n request: RescopeDeviceRequest,\n ): Promise<RescopeDeviceResponseData> {\n return unwrap(\n await this.httpClient.put<{ data: RescopeDeviceResponseData }>(\n `/admin-api/account/devices/${encodeURIComponent(deviceId)}/scope`,\n request,\n ),\n );\n }\n\n /**\n * Sign a payload an outside verifier specified, with this account's root.\n *\n * The counterpart to a verifier that issued a challenge and will check the\n * signature against the account id it derives \u2014 the cloud's account link and\n * login exchanges being the ones this exists for. The root never leaves the\n * node, so this is the only way a client can produce that signature.\n *\n * `domain` is a NAME from a closed set the node knows, never the domain bytes.\n * That is the security property rather than an encoding preference: signing\n * caller-supplied bytes under a caller-supplied prefix would be an oracle over\n * the one key that can certify a device. A node refuses a name it does not\n * know, and refuses one whose bytes could be extended into a credential of its\n * own.\n *\n * What comes back proves possession of the root and nothing else. Whether the\n * payload is a challenge anyone issued, whether it is fresh, and whether the\n * holder may link this account are the verifier's to decide \u2014 it issued the\n * challenge and knows who to.\n *\n * ```ts\n * const { nonce } = await cloud.getAccountLoginChallenge();\n * const { rootPublicKey, signature } = await admin.signWithAccountRoot({\n * domain: 'mdma.account-login',\n * payload: hexEncodeUtf8(nonce),\n * });\n * await cloud.submitAccountLogin({ rootPublicKey, nonce, signature });\n * ```\n */\n async signWithAccountRoot(\n request: AccountSignWithRootRequest,\n ): Promise<AccountSignWithRootResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: AccountSignWithRootResponseData }>(\n '/admin-api/account/sign-with-root',\n request,\n ),\n );\n }\n\n /**\n * Name a device of this account, for a listing to render.\n *\n * Run on the node holding the account root to name any device; a paired\n * node is accepted only for that device's own id.\n */\n async labelAccountDevice(\n deviceId: string,\n request: LabelDeviceRequest,\n ): Promise<LabelDeviceResponseData> {\n return unwrap(\n await this.httpClient.put<{ data: LabelDeviceResponseData }>(\n `/admin-api/account/devices/${encodeURIComponent(deviceId)}/label`,\n request,\n ),\n );\n }\n\n /**\n * Every device of this account, as this node sees them - its own included.\n *\n * Joined from the node-local certificate cache and the live bindings of every\n * namespace this node takes part in, so it is this node's view rather than a\n * global one: a device certified by another holder appears with an empty\n * `applications`, because the certificate that would name its scope is not\n * cached here.\n *\n * The wire wraps this in `devices` rather than the usual `data`; the array is\n * what a caller wants, so that wrapper is not passed on.\n */\n async listAccountDevices(): Promise<AccountDeviceEntry[]> {\n const response = await this.httpClient.get<{ devices: AccountDeviceEntry[] }>('/admin-api/account/devices');\n return response.devices;\n }\n\n /**\n * Every application this account speaks in, with the namespaces targeting each.\n *\n * What a device-scoping UI lists: {@link completeAccountPairing} and\n * {@link relinkAccountDevice} both take applications, and this is where the\n * ids they take come from. Derived from the namespaces this node takes part\n * in, so an application installed with no namespace yet is absent - it has\n * nothing to scope a device to until one exists.\n *\n * Wrapped in `applications` on the wire rather than `data`, unwrapped here for\n * the same reason {@link listAccountDevices} unwraps `devices`.\n */\n async listAccountApplications(): Promise<AccountApplicationEntry[]> {\n const response = await this.httpClient.get<{ applications: AccountApplicationEntry[] }>(\n '/admin-api/account/applications',\n );\n return response.applications;\n }\n\n /**\n * Withdraw a device from this account, terminally.\n *\n * Two authorities reach the same endpoint. An admin may revoke any device and\n * rotates the scope key as it goes. The account holder may revoke its own with\n * a root-signed `proof`, which does not rotate - so read\n * {@link RevokeAccountDeviceResponseData.revokedIn} rather than assuming the\n * device lost its access: a namespace reporting `keyRotated: false` has\n * stopped the device writing but leaves it able to read until an admin\n * rotates.\n *\n * `namespaceId` names where to publish from, not the extent of the revocation:\n * a device belongs to the account, so every namespace holding a binding for it\n * is withdrawn from and reported back.\n */\n async revokeAccountDevice(\n namespaceId: string,\n request: RevokeAccountDeviceRequest,\n ): Promise<RevokeAccountDeviceResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: RevokeAccountDeviceResponseData }>(\n `/admin-api/namespaces/${namespaceId}/account/revoke`,\n request,\n ),\n );\n }\n\n // ---- Group Management ----\n\n async getGroupInfo(groupId: string): Promise<GroupInfoResponseData> {\n return unwrap(await this.httpClient.get<{ data: GroupInfoResponseData }>(`/admin-api/groups/${groupId}`));\n }\n\n /** Thin wrapper over {@link getGroupInfo}: returns the group's `defaultCapabilities` bitmask. */\n async getDefaultCapabilities(groupId: string): Promise<number> {\n return (await this.getGroupInfo(groupId)).defaultCapabilities;\n }\n\n /** Thin wrapper over {@link getGroupInfo}: returns the group's `subgroupVisibility`. */\n async getSubgroupVisibility(groupId: string): Promise<string> {\n return (await this.getGroupInfo(groupId)).subgroupVisibility;\n }\n\n async deleteGroup(groupId: string): Promise<DeleteGroupResponseData> {\n // Core requires `Content-Type: application/json` on this DELETE even though\n // the body is always empty, so send the header and an empty JSON object.\n return unwrap(\n await this.httpClient.request<{ data: DeleteGroupResponseData }>(`/admin-api/groups/${groupId}`, {\n method: 'DELETE',\n body: '{}',\n headers: { 'Content-Type': 'application/json' },\n }),\n );\n }\n\n async listGroupMembers(groupId: string): Promise<ListGroupMembersResponseData> {\n const response = await this.httpClient.get<ListGroupMembersResponseData>(\n `/admin-api/groups/${groupId}/members`,\n );\n // Validate the field we declare as non-optional in the type so a\n // contract-violating response (proxy error body, future API drift,\n // etc.) surfaces as a clear error rather than silently producing an\n // empty list. Empty groups still satisfy this \u2014 merod returns\n // `members: []`, not an omitted field.\n if (!Array.isArray(response?.members)) {\n // Sanitize before interpolation: groupId reaches us from caller code,\n // not parsed input, but defending the message keeps untrusted bytes\n // out of error logs and downstream UIs.\n const safeId = String(groupId).replace(/[\\r\\n\\t\\s]/g, '').slice(0, 64);\n throw new Error(\n `Invalid listGroupMembers response for group ${safeId}: missing or non-array \\`members\\` field`,\n );\n }\n return response;\n }\n\n /**\n * Member accounts in the group and the devices bound to each. An admin node\n * sees every account; a plain member only its own. `403` if this node is not a\n * member of the group.\n */\n async listMemberDevices(\n groupId: string,\n options: ListMemberDevicesOptions = {},\n ): Promise<ListMemberDevicesResponseData> {\n const params = new URLSearchParams();\n if (options.offset !== undefined) params.set('offset', String(options.offset));\n if (options.limit !== undefined) params.set('limit', String(options.limit));\n const query = params.toString();\n const path = `/admin-api/groups/${groupId}/member-devices${query ? `?${query}` : ''}`;\n return this.httpClient.get<ListMemberDevicesResponseData>(path);\n }\n\n /**\n * Seal a small payload to a member account's root key. This node must be a\n * member of the group, and `account` must be an account the group knows, so\n * the route reaches no further than the caller already does.\n */\n async sealToAccount(\n groupId: string,\n account: string,\n request: SealToAccountRequest,\n ): Promise<SealedEnvelope> {\n return unwrap(\n await this.httpClient.post<{ data: SealedEnvelope }>(\n `/admin-api/groups/${groupId}/accounts/${account}/seal`,\n request,\n ),\n );\n }\n\n async listGroupContexts(groupId: string): Promise<ListGroupContextsResponseData> {\n return unwrap(await this.httpClient.get<{ data: ListGroupContextsResponseData }>(`/admin-api/groups/${groupId}/contexts`));\n }\n\n async addGroupMembers(groupId: string, request: AddGroupMembersRequest): Promise<void> {\n await this.httpClient.post(`/admin-api/groups/${groupId}/members`, request);\n }\n\n async removeGroupMembers(groupId: string, request: RemoveGroupMembersRequest): Promise<void> {\n await this.httpClient.post(`/admin-api/groups/${groupId}/members/remove`, request);\n }\n\n /**\n * `identity` is the member's ACCOUNT (64 hex), as returned by\n * {@link listGroupMembers} \u2014 not the signing key that added them. Both are\n * 64 hex, so nothing but the source tells them apart.\n */\n async updateMemberRole(\n groupId: string,\n identity: string,\n request: UpdateMemberRoleRequest,\n ): Promise<void> {\n await this.httpClient.put(`/admin-api/groups/${groupId}/members/${identity}/role`, request);\n }\n\n /**\n * `identity` is the member's ACCOUNT (64 hex), as returned by\n * {@link listGroupMembers} \u2014 not the signing key that added them. Both are\n * 64 hex, so nothing but the source tells them apart.\n */\n async getMemberCapabilities(groupId: string, identity: string): Promise<MemberCapabilities> {\n return unwrap(\n await this.httpClient.get<{ data: MemberCapabilities }>(\n `/admin-api/groups/${groupId}/members/${identity}/capabilities`,\n ),\n );\n }\n\n /**\n * `identity` is the member's ACCOUNT (64 hex), as returned by\n * {@link listGroupMembers} \u2014 not the signing key that added them. Both are\n * 64 hex, so nothing but the source tells them apart.\n */\n async setMemberCapabilities(\n groupId: string,\n identity: string,\n request: SetMemberCapabilitiesRequest,\n ): Promise<void> {\n await this.httpClient.put(`/admin-api/groups/${groupId}/members/${identity}/capabilities`, request);\n }\n\n async setDefaultCapabilities(\n groupId: string,\n request: SetDefaultCapabilitiesRequest,\n ): Promise<void> {\n await this.httpClient.put(`/admin-api/groups/${groupId}/settings/default-capabilities`, request);\n }\n\n /**\n * Make every member admitted to `groupId` from now on able to relay a\n * delegated write, without a per-node governance op each time.\n *\n * `CAN_AUTHOR_ON_BEHALF` is implied by nothing \u2014 not membership, not admin \u2014\n * so every group is authorship-closed and each fleet relay otherwise needs an\n * admin to grant it individually, at the moment the relay is assigned and the\n * admin is not watching. Setting it in the group's DEFAULT mask is core's own\n * answer to that: `add_member` seeds a non-admin's capability row from the\n * default, so an attested node admitted afterwards lands open.\n *\n * Call it on the NAMESPACE (the root group). A grant resolves through the\n * membership anchor, so one at the root reaches every Open subgroup beneath\n * it, which is where contexts actually live; a `Restricted` subgroup is a\n * membership boundary and needs its own call.\n *\n * ### Two things this does not do\n *\n * **It is not retroactive.** The default is copied into a member's row when\n * that member is admitted, so nodes already admitted keep the row they got.\n * Use {@link grantAuthorship} for those \u2014 or call this at namespace creation,\n * which is the only point that needs no backfill at all.\n *\n * **It does not reach admins.** Core seeds the default only for non-admin\n * roles, and `CAN_AUTHOR_ON_BEHALF` is deliberately not implied by admin. An\n * admin's own node therefore stays closed and needs {@link grantAuthorship}.\n *\n * ### What it widens\n *\n * Every future non-admin member of the group, not only attested TEE nodes, so\n * any of them may be named as a warrant's `executor`. The bound worth knowing\n * before deciding: an executor cannot FORGE. A delegated write is authorized\n * by the author's own warrant \u2014 signed by their device key, committed to one\n * context, method and exact arguments, with its nonce checked unspent \u2014 so a\n * holder can only spend a warrant it was deliberately handed. What it does\n * gain is the arguments in cleartext and the choice of when, or whether, to\n * publish.\n *\n * Read-modify-write rather than a blind set: core seeds a new group's default\n * with `CAN_JOIN_OPEN_SUBGROUPS`, and overwriting that would quietly stop\n * every later member inheriting into Open subgroups.\n *\n * Returns the resulting mask and whether an op was published \u2014 it is a no-op\n * when the bit is already set, so it is safe to call on every startup.\n */\n async openToDelegatedExecution(\n groupId: string,\n ): Promise<{ changed: boolean; defaultCapabilities: number }> {\n const current = await this.getDefaultCapabilities(groupId);\n if (hasCap(current, CAPABILITIES.CAN_AUTHOR_ON_BEHALF)) {\n return { changed: false, defaultCapabilities: current };\n }\n const next = withCap(current, CAPABILITIES.CAN_AUTHOR_ON_BEHALF);\n await this.setDefaultCapabilities(groupId, { defaultCapabilities: next });\n return { changed: true, defaultCapabilities: next };\n }\n\n /**\n * Grant `account` the authorship bit on `groupId`, leaving its other\n * capabilities alone.\n *\n * The per-member counterpart to {@link openToDelegatedExecution}, and what\n * closes the gap that method cannot: a relay admitted BEFORE the default was\n * set, and an admin's own node, which never receives the default at all.\n *\n * `account` is the member's ACCOUNT id (64 hex) as `listGroupMembers` returns\n * it \u2014 for a fleet relay, the `account` the cloud reports for that node. Not\n * the signing key that added them; both are 64 hex, so nothing but the source\n * tells them apart.\n *\n * Read-modify-write, so it adds the bit rather than replacing the member's\n * mask. A no-op when the member already holds it.\n */\n async grantAuthorship(\n groupId: string,\n account: string,\n ): Promise<{ changed: boolean; capabilities: number }> {\n const { capabilities } = await this.getMemberCapabilities(groupId, account);\n if (hasCap(capabilities, CAPABILITIES.CAN_AUTHOR_ON_BEHALF)) {\n return { changed: false, capabilities };\n }\n const next = withCap(capabilities, CAPABILITIES.CAN_AUTHOR_ON_BEHALF);\n await this.setMemberCapabilities(groupId, account, { capabilities: next });\n return { changed: true, capabilities: next };\n }\n\n async setSubgroupVisibility(\n groupId: string,\n request: SetSubgroupVisibilityRequest,\n ): Promise<void> {\n await this.httpClient.put(`/admin-api/groups/${groupId}/settings/subgroup-visibility`, request);\n }\n\n async setTeeAdmissionPolicy(\n groupId: string,\n request: SetTeeAdmissionPolicyRequest,\n ): Promise<void> {\n await this.httpClient.put(`/admin-api/groups/${groupId}/settings/tee-admission-policy`, request);\n }\n\n async getTeeAdmissionPolicy(groupId: string): Promise<GetTeeAdmissionPolicyResponseData> {\n // Intersection (not union) so the type is unambiguous: `data` is optional, so\n // `?? response` cleanly falls back to a flat (un-enveloped) response.\n const response = await this.httpClient.get<\n GetTeeAdmissionPolicyResponseData & { data?: GetTeeAdmissionPolicyResponseData }\n >(`/admin-api/groups/${groupId}/settings/tee-admission-policy`);\n return response.data ?? response;\n }\n\n /**\n * Sets which admitted TEEs may author as the TEE authority. Pass\n * `{ allowedMrtd: [] }` to turn TEE authorship off. `groupId` must be a\n * namespace root; the node refuses a subgroup. The node exposes no read-back\n * route for this policy.\n */\n async setTeeAuthoringPolicy(\n groupId: string,\n request: SetTeeAuthoringPolicyRequest,\n ): Promise<void> {\n await this.httpClient.put(`/admin-api/groups/${groupId}/settings/tee-authoring-policy`, request);\n }\n\n /**\n * Turns TEE authorship off in the namespace: every TEE loses its authority,\n * and admitted TEEs stay members. The node applies the same op as\n * `setTeeAuthoringPolicy(groupId, { allowedMrtd: [] })`. `groupId` must be a\n * namespace root.\n */\n async disableTeeAuthoringPolicy(groupId: string): Promise<void> {\n await this.httpClient.delete(`/admin-api/groups/${groupId}/settings/tee-authoring-policy`);\n }\n\n // ---- Group / member / context metadata ----\n\n async setGroupMetadata(groupId: string, request: SetGroupMetadataRequest): Promise<void> {\n await this.httpClient.put(`/admin-api/groups/${groupId}/metadata`, request);\n }\n\n async getGroupMetadata(groupId: string): Promise<MetadataRecord | null> {\n // Core single-envelopes the record: `{ data: MetadataRecord | null }`.\n // \"No record yet\" is `{ data: null }` (or a bare null body on older nodes),\n // so optional-chain to a clean null. Returns the full record (name + data +\n // updatedAt/updatedBy), not just the data map.\n const response = await this.httpClient.get<GetMetadataResponseData | null>(\n `/admin-api/groups/${groupId}/metadata`,\n );\n return response?.data ?? null;\n }\n\n async setMemberMetadata(\n groupId: string,\n identity: string,\n request: SetMemberMetadataRequest,\n ): Promise<void> {\n await this.httpClient.put(`/admin-api/groups/${groupId}/members/${identity}/metadata`, request);\n }\n\n /**\n * `identity` is the member's ACCOUNT (64 hex), as returned by\n * {@link listGroupMembers} \u2014 not the signing key that added them. Both are\n * 64 hex, so nothing but the source tells them apart.\n */\n async getMemberMetadata(groupId: string, identity: string): Promise<MetadataRecord | null> {\n // Single-enveloped record; see getGroupMetadata.\n const response = await this.httpClient.get<GetMetadataResponseData | null>(\n `/admin-api/groups/${groupId}/members/${identity}/metadata`,\n );\n return response?.data ?? null;\n }\n\n async setContextMetadata(\n groupId: string,\n contextId: string,\n request: SetContextMetadataRequest,\n ): Promise<void> {\n await this.httpClient.put(`/admin-api/groups/${groupId}/contexts/${contextId}/metadata`, request);\n }\n\n async getContextMetadata(groupId: string, contextId: string): Promise<MetadataRecord | null> {\n // Single-enveloped record; see getGroupMetadata.\n const response = await this.httpClient.get<GetMetadataResponseData | null>(\n `/admin-api/groups/${groupId}/contexts/${contextId}/metadata`,\n );\n return response?.data ?? null;\n }\n\n async syncGroup(groupId: string): Promise<SyncGroupResponseData> {\n return unwrap(await this.httpClient.post<{ data: SyncGroupResponseData }>(`/admin-api/groups/${groupId}/sync`, {}));\n }\n\n async upgradeGroup(groupId: string, request: UpgradeGroupRequest): Promise<UpgradeGroupResponseData> {\n return unwrap(await this.httpClient.post<{ data: UpgradeGroupResponseData }>(`/admin-api/groups/${groupId}/upgrade`, request));\n }\n\n async getGroupUpgradeStatus(groupId: string): Promise<GroupUpgradeStatusResponseData> {\n return unwrap(\n await this.httpClient.get<{ data: GroupUpgradeStatusResponseData }>(`/admin-api/groups/${groupId}/upgrade/status`),\n );\n }\n\n /**\n * The operator-facing \"have all peers migrated?\" rollup for a namespace.\n * The handler serializes the payload directly, so there is no `{ data }`\n * envelope to unwrap here (unlike most admin reads).\n */\n async getMigrationStatus(namespaceId: string): Promise<MigrationStatus> {\n const id = encodeURIComponent(namespaceId);\n return this.httpClient.get<MigrationStatus>(`/admin-api/groups/${id}/migration-status`);\n }\n\n /** Per-group cascade-migration snapshots for a namespace. */\n async getCascadeStatus(namespaceId: string): Promise<CascadeStatusEntry[]> {\n const id = encodeURIComponent(namespaceId);\n return unwrap(\n await this.httpClient.get<{ data: CascadeStatusEntry[] }>(`/admin-api/groups/${id}/cascade-status`),\n );\n }\n\n async retryGroupUpgrade(groupId: string): Promise<RetryGroupUpgradeResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: RetryGroupUpgradeResponseData }>(\n `/admin-api/groups/${groupId}/upgrade/retry`,\n {},\n ),\n );\n }\n\n /** Move `childGroupId` under `request.newParentId`. */\n async reparentGroup(\n childGroupId: string,\n request: ReparentGroupRequest,\n ): Promise<ReparentGroupResponseData> {\n // Core returns this flat ({ reparented }); tolerate the { data } envelope too.\n const r = await this.httpClient.post<\n ReparentGroupResponseData & { data?: ReparentGroupResponseData }\n >(`/admin-api/groups/${childGroupId}/reparent`, request);\n return (r?.data ?? r) as ReparentGroupResponseData;\n }\n\n async listSubgroups(groupId: string): Promise<SubgroupEntry[]> {\n const response = await this.httpClient.get<{ subgroups?: SubgroupEntry[]; data?: SubgroupEntry[] }>(\n `/admin-api/groups/${groupId}/subgroups`,\n );\n return response.subgroups ?? response.data ?? [];\n }\n\n async detachContextFromGroup(groupId: string, contextId: string): Promise<void> {\n await this.httpClient.post(`/admin-api/groups/${groupId}/contexts/${contextId}/remove`, {});\n }\n\n // ---- Group Invitation & Join ----\n\n async createGroupInvitation(\n groupId: string,\n request?: CreateGroupInvitationRequest,\n ): Promise<CreateGroupInvitationResponseData | CreateRecursiveGroupInvitationResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: CreateGroupInvitationResponseData | CreateRecursiveGroupInvitationResponseData }>(\n `/admin-api/groups/${groupId}/invite`,\n request ?? {},\n ),\n );\n }\n\n async joinGroup(request: JoinGroupRequest): Promise<JoinGroupResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: JoinGroupResponseData }>('/admin-api/groups/join', request),\n );\n }\n\n async joinSubgroupInheritance(groupId: string): Promise<JoinSubgroupInheritanceResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: JoinSubgroupInheritanceResponseData }>(\n `/admin-api/groups/${groupId}/join-via-inheritance`,\n {},\n ),\n );\n }\n\n // ---- TEE ----\n\n async getTeeInfo(): Promise<TeeInfoResponseData> {\n return unwrap(await this.httpClient.get<{ data: TeeInfoResponseData }>('/admin-api/tee/info'));\n }\n\n async teeAttest(request: TeeAttestRequest): Promise<TeeAttestResponseData> {\n return unwrap(await this.httpClient.post<{ data: TeeAttestResponseData }>('/admin-api/tee/attest', request));\n }\n\n /**\n * @deprecated Nodes no longer serve `/admin-api/tee/verify-quote` (removed in\n * core#3262), so this fails. Verify quotes with `createQuoteVerifier`.\n */\n async teeVerifyQuote(request: TeeVerifyQuoteRequest): Promise<TeeVerifyQuoteResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: TeeVerifyQuoteResponseData }>('/admin-api/tee/verify-quote', request),\n );\n }\n\n // ---- Network ----\n\n async getPeersCount(): Promise<{ count: number }> {\n return this.httpClient.get<{ count: number }>('/admin-api/peers');\n }\n\n /** Node network status (GET /admin-api/network/status). */\n async getNetworkStatus(): Promise<unknown> {\n return this.httpClient.get<unknown>('/admin-api/network/status');\n }\n\n /** Node storage/usage stats (GET /admin-api/usage). */\n async getUsage(): Promise<unknown> {\n return this.httpClient.get<unknown>('/admin-api/usage');\n }\n\n /** Node TLS certificate, PEM text (GET /admin-api/certificate). */\n async getCertificate(): Promise<string> {\n return this.httpClient.get<string>('/admin-api/certificate', { parse: 'text' });\n }\n\n // ---- Group / context / namespace membership ----\n\n /** Create a standalone group (POST /admin-api/groups). */\n async createGroup(request: CreateGroupRequest): Promise<CreateGroupResponseData> {\n return unwrap(\n await this.httpClient.post<{ data: CreateGroupResponseData }>('/admin-api/groups', request),\n );\n }\n\n /** Leave a group (POST /admin-api/groups/:group_id/leave). */\n async leaveGroup(groupId: string): Promise<void> {\n await this.httpClient.post(`/admin-api/groups/${groupId}/leave`, {});\n }\n\n /** Leave a context (POST /admin-api/contexts/:context_id/leave). */\n async leaveContext(contextId: string): Promise<void> {\n await this.httpClient.post(`/admin-api/contexts/${contextId}/leave`, {});\n }\n\n /** Leave a namespace (POST /admin-api/namespaces/:namespace_id/leave). */\n async leaveNamespace(namespaceId: string): Promise<void> {\n await this.httpClient.post(`/admin-api/namespaces/${namespaceId}/leave`, {});\n }\n\n /** Issue a group ownership proof (POST /admin-api/groups/:group_id/issue-ownership-proof). */\n async issueOwnershipProof(\n groupId: string,\n request: IssueOwnershipProofRequest,\n ): Promise<IssueOwnershipProofResponseData> {\n return this.httpClient.post<IssueOwnershipProofResponseData>(\n `/admin-api/groups/${groupId}/issue-ownership-proof`,\n request,\n );\n }\n\n /** Issue a namespace ownership proof (POST /admin-api/groups/:group_id/issue-namespace-ownership-proof). */\n async issueNamespaceOwnershipProof(\n groupId: string,\n request: IssueNamespaceOwnershipProofRequest,\n ): Promise<IssueOwnershipProofResponseData> {\n return this.httpClient.post<IssueOwnershipProofResponseData>(\n `/admin-api/groups/${groupId}/issue-namespace-ownership-proof`,\n request,\n );\n }\n\n /**\n * Set a member's auto-follow flag (PUT\n * /admin-api/groups/:group_id/members/:identity/auto-follow).\n *\n * `identity` is the member's ACCOUNT (64 hex), as returned by\n * {@link listGroupMembers} \u2014 not the signing key that added them. Both are\n * 64 hex, so nothing but the source tells them apart.\n */\n async setMemberAutoFollow(\n groupId: string,\n identity: string,\n request: SetMemberAutoFollowRequest,\n ): Promise<void> {\n await this.httpClient.put(`/admin-api/groups/${groupId}/members/${identity}/auto-follow`, request);\n }\n\n /** Abort a namespace migration (POST /admin-api/groups/:namespace_id/migration/abort). */\n async abortMigration(namespaceId: string): Promise<unknown> {\n return this.httpClient.post<unknown>(`/admin-api/groups/${namespaceId}/migration/abort`, {});\n }\n}\n", "import type { DcapCollateral } from '../sealed/verify.js';\n\n// Admin API Types \u2014 aligned with core server routes\n// All types use camelCase to match core's #[serde(rename_all = \"camelCase\")]\n\n// Re-export shared types\nexport { ApiResponse } from '../http-client/index.js';\n\n// ---- Health and Status ----\n\nexport interface HealthStatus {\n status: string;\n}\n\n/**\n * What `GET /admin-api/ready` reports: `ready` once the node has finished\n * starting up and its store answers. A node that is not ready answers `503`\n * with its lifecycle stage (or `store_unavailable`) as the status, which the\n * client surfaces as an `HTTPError`.\n */\nexport interface ReadinessStatus {\n status: string;\n}\n\nexport interface AdminAuthStatus {\n data: { status: string };\n}\n\n// ---- Applications ----\n\n/**\n * Install by coordinates. The node fetches only from its own configured\n * `[registry]`, so there is no caller-supplied URL; both halves are required.\n */\nexport interface InstallApplicationRequest {\n package: string;\n version: string;\n}\n\nexport interface InstallDevApplicationRequest {\n path: string;\n}\n\nexport interface InstallApplicationResponseData {\n applicationId: string;\n}\n\nexport interface UninstallApplicationResponseData {\n applicationId: string;\n}\n\nexport interface ApplicationBlob {\n bytecode: string;\n compiled: string;\n}\n\nexport interface Application {\n id: string;\n blob: ApplicationBlob;\n size: number;\n source: string;\n metadata: number[];\n /** Absent for a raw-wasm install (no signed bundle) and for bootstrap stubs. */\n signer_id?: string;\n /** Absent on a bootstrap stub row, written before the app's blob arrives. */\n package?: string;\n /** Absent when the stored version is empty or not valid semver. */\n version?: string;\n /** Named services of a multi-service bundle; absent for single-service apps. */\n services?: Record<string, ApplicationBlob>;\n}\n\nexport interface ListApplicationsResponseData {\n apps: Application[];\n}\n\nexport interface GetApplicationResponseData {\n application: Application | null;\n}\n\n/** One installed blob for an application (distinct from the package registry). */\nexport interface ApplicationVersionEntry {\n version: string;\n blobId: string;\n size: number;\n package: string;\n}\n\nexport interface ListApplicationVersionsResponseData {\n data: ApplicationVersionEntry[];\n}\n\n/** The application's `wasm-abi/1` manifest, returned verbatim by the node. */\nexport type GetApplicationAbiResponseData = Record<string, unknown>;\n\n// ---- Packages ----\n\nexport interface GetLatestVersionResponseData {\n applicationId: string | null;\n version: string | null;\n}\n\nexport interface ListPackagesResponseData {\n packages: string[];\n}\n\nexport interface ListVersionsResponseData {\n versions: string[];\n}\n\n// ---- Bundle migration metadata ----\n\n/**\n * Per-service migration descriptor carried in a multi-service bundle manifest,\n * emitted from the app's `#[app::migrate]` declaration. `toSchemaVersion` is the\n * ABI state version the migrate targets (from `#[app::state(version = N)]`,\n * the engine's gate) \u2014 NOT the bundle semver, which is `toVersion`, the\n * user-facing string an admin matches on; `method` is the migrate entrypoint.\n */\nexport interface BundleMigration {\n method: string;\n toSchemaVersion: number;\n toVersion?: string;\n}\n\n/**\n * The subset of a registry bundle manifest that `getRegistryVersions` consumes.\n * The registry serves these at `GET {registry}/api/v2/bundles?package={package}`;\n * installation itself needs only the coordinates, which the node resolves.\n */\nexport interface RegistryBundleManifest {\n package: string;\n appVersion: string;\n /** Present when this bundle's app declares a migration. */\n migration?: BundleMigration;\n}\n\n// ---- Contexts ----\n\nexport interface CreateContextRequest {\n applicationId: string;\n groupId: string;\n serviceName?: string;\n contextSeed?: string;\n initializationParams?: number[];\n identitySecret?: string;\n /** Optional human-readable label for the context. */\n name?: string;\n}\n\nexport interface CreateContextResponseData {\n contextId: string;\n memberPublicKey: string;\n groupId?: string;\n groupCreated?: boolean;\n}\n\nexport interface DeleteContextResponseData {\n isDeleted: boolean;\n}\n\nexport interface Context {\n id: string;\n applicationId: string;\n serviceName?: string;\n /**\n * Context state/root hash. Core's wire key is `contextStateHash` (part of the\n * cross-DAG auth three-level naming: contextStateHash / groupStateHash /\n * namespaceStateHash). Renamed from `rootHash`, which never populated.\n */\n contextStateHash: string;\n /** Absent while the context has no deltas yet (core omits an empty list). */\n dagHeads?: number[][];\n /** Bundle semver of the installed application (skew #2). Absent on older nodes. */\n applicationVersion?: string;\n}\n\nexport interface ContextWithGroup extends Context {\n groupId?: string;\n}\n\nexport interface GetContextsResponseData {\n contexts: ContextWithGroup[];\n}\n\n// ---- Context Identity ----\n\nexport interface GenerateContextIdentityResponseData {\n publicKey: string;\n}\n\nexport interface GetContextIdentitiesResponseData {\n identities: string[];\n}\n\n// ---- Context join (group membership; POST /contexts/:id/join) ----\n\nexport interface JoinContextResponseData {\n contextId: string;\n memberPublicKey: string;\n}\n\n// ---- Open subgroup join via inheritance (POST /groups/:group_id/join-via-inheritance) ----\n\nexport interface JoinSubgroupInheritanceResponseData {\n groupId: string;\n memberPublicKey: string;\n // `true` if the call had to publish a `MemberJoinedOpen` op to materialise\n // inherited membership; `false` if the caller was already a direct member\n // and the call was a no-op.\n wasInherited: boolean;\n}\n\n// ---- Context group / storage / sync ----\n\nexport type ContextGroupResponseData = string | null;\n\nexport interface ContextStorageResponseData {\n sizeInBytes: number;\n}\n\n// Sync context returns empty\nexport type SyncContextResponseData = null;\n\n// ---- Specialized Node Invite ----\n\nexport interface InviteSpecializedNodeRequest {\n contextId: string;\n inviterId?: string;\n}\n\nexport interface InviteSpecializedNodeResponseData {\n nonce: string;\n}\n\n// ---- Update Context Application ----\n\nexport interface UpdateContextApplicationRequest {\n applicationId: string;\n executorPublicKey: string;\n}\n\n// Update context application returns empty\nexport type UpdateContextApplicationResponseData = Record<string, never>;\n\n// ---- Resync Context ----\n\nexport interface ResyncContextRequest {\n /** Force a full re-pull even if the context is not detected as stranded. */\n force?: boolean;\n}\n\nexport interface ResyncContextResponseData {\n contextId: string;\n resyncStarted: boolean;\n}\n\n// ---- Contexts With Executors ----\n\nexport interface ContextWithExecutors {\n contextId: string;\n executors: string[];\n}\n\nexport type ContextsWithExecutorsResponseData = ContextWithExecutors[];\n\n// ---- Blobs ----\n\nexport interface UploadBlobRequest {\n /** Raw blob bytes; streamed verbatim as the request body (octet-stream). */\n data: Uint8Array | ArrayBuffer | Blob;\n /** Optional expected blob hash; sent as the `hash` query param for server-side verification. */\n hash?: string;\n /** Optional context to announce the blob to; sent as the `context_id` query param. */\n contextId?: string;\n}\n\nexport interface BlobInfo {\n blobId: string;\n size: number;\n}\n\nexport type UploadBlobResponseData = BlobInfo;\n\nexport interface DeleteBlobResponseData {\n blobId: string;\n deleted: boolean;\n}\n\nexport interface ListBlobsResponseData {\n blobs: BlobInfo[];\n}\n\nexport type GetBlobResponseData = BlobInfo;\n\n/**\n * Optional context for a blob read. Supplying `contextId` sends the\n * `context_id` query param (the same one {@link UploadBlobRequest} announces\n * with) and opts the read into network discovery instead of a local-only\n * lookup \u2014 see {@link AdminClient.getBlob}/{@link AdminClient.getBlobInfo} for\n * the latency this buys.\n */\nexport interface BlobReadOptions {\n /** Context whose peers to probe for the blob; sent as the `context_id` query param. */\n contextId?: string;\n}\n\nexport interface GetBlobInfoResponseData extends BlobInfo {\n /** Absent when the answer came from a peer probe, which carries only presence and size. */\n hash?: string;\n /** Absent when the answer came from a peer probe, which carries only presence and size. */\n mimeType?: string;\n /**\n * Where the node answered from, per the `x-blob-source` response header:\n * `'local'` for its own blob store, `'peer'` for a context probe. Undefined\n * on nodes that don't send the header.\n */\n source?: 'local' | 'peer';\n}\n\n// ---- Aliases ----\n\n// Core's CreateAliasRequest is `{ alias, #[serde(flatten)] value }`, so each\n// alias kind flattens a different id field at the top level of the body.\nexport interface CreateContextAliasRequest {\n alias: string;\n contextId: string;\n}\n\nexport interface CreateApplicationAliasRequest {\n alias: string;\n applicationId: string;\n}\n\nexport interface CreateDeviceAliasRequest {\n alias: string;\n deviceId: string;\n}\n\n/**\n * Core's `ListAliasesResponse` is `{ data: BTreeMap<Alias<T>, T> }`, so once the\n * `data` envelope is stripped the payload is a flat `{ alias: id }` map \u2014 not a\n * list of entries. Applies to context, application, and device aliases.\n */\nexport type ListAliasesResponseData = Record<string, string>;\n\n// Create/delete alias returns empty\nexport type CreateAliasResponseData = Record<string, never>;\nexport type DeleteAliasResponseData = Record<string, never>;\n\nexport interface LookupAliasResponseData {\n value?: string;\n}\n\n// ---- Shared invitation types ----\n\n/**\n * The signed half of an invitation. Wire keys are snake_case: this type mirrors\n * a core primitive (`calimero_context_config::types`), which carries no\n * camelCase rename \u2014 unlike the admin DTOs that wrap it.\n */\nexport interface GroupInvitationFromAdmin {\n readonly inviter_identity: number[];\n readonly group_id: number[];\n readonly expiration_timestamp: number;\n /** Per-invitation nonce; core keeps the legacy `secret_salt` wire name. */\n readonly secret_salt: number[];\n readonly invited_role: number;\n /**\n * Accounts permitted to admit a claim of this invitation, 64 hex characters\n * each \u2014 the same spelling every other account-addressing field uses.\n *\n * Empty means admission by broadcast: the joiner announces itself on the\n * namespace topic and any ready peer answers \u2014 which publishes the invitation\n * to every subscriber of that topic. Naming admitters keeps it off the topic,\n * and the joiner presents it to one of them directly.\n *\n * Inside the inviter's signature, so it cannot be redirected.\n */\n readonly admitters?: readonly string[];\n}\n\n/**\n * An invitation blob the node signed. **Opaque: pass it through unchanged.**\n *\n * `inviter_signature` covers `invitation`, and the trailing bootstrap fields\n * (`application_id`, `app_key`) are needed by the joiner even though they sit\n * outside the signature \u2014 so a value that has been rebuilt field-by-field is\n * not the value the node signed. Rebuilding is what a schema derived from these\n * declarations does: an object schema drops the keys it wasn't told about, and\n * core may add more (both trailing fields were added this way). Carry the value\n * verbatim from the call that produced it to {@link JoinGroupRequest}, and treat\n * the fields below as read-only detail for display.\n *\n * The `never`-typed brand makes that structural: an object literal or a schema\n * result cannot satisfy this type, while a value the SDK returned \u2014 or one\n * recovered with `JSON.parse` after transport \u2014 still does.\n */\nexport interface SignedGroupOpenInvitation {\n readonly invitation: GroupInvitationFromAdmin;\n readonly inviter_signature: string;\n /**\n * libp2p addresses for the accounts in\n * {@link GroupInvitationFromAdmin.admitters}, each a full multiaddr including\n * its `/p2p/<peer-id>` suffix.\n *\n * The peer id travels with the address because a joiner cannot derive it:\n * resolving an account to a peer needs governance state, which is exactly what\n * a joiner has not synced yet.\n *\n * Outside the signature, like the other bootstrap fields. A wrong address\n * costs a failed dial \u2014 libp2p authenticates the peer id on connect, and the\n * admitting node still has to be in the signed list for its admission to\n * count.\n *\n * Best-effort: absent on invitations from nodes predating the field, and empty\n * when the minting node had no address for any admitter.\n */\n readonly admitter_addrs?: readonly string[];\n /**\n * The account the inviter acts as, as 64 hex characters \u2014 not the\n * `inviter_identity` key inside the signed body is written in. Governance\n * rows name accounts, and a joiner cannot derive this one: an account is a\n * hash of a root it has never seen.\n *\n * Unsigned bootstrap field, and outside the signature deliberately \u2014 a\n * client that round-trips an invitation through its own typed model would\n * drop an unknown field and invalidate the signature with it. Absent on\n * invitations from older nodes.\n */\n readonly inviter_account?: string;\n /** Unsigned bootstrap field; absent on invitations from older nodes. */\n readonly application_id?: number[];\n /** Unsigned bootstrap field; absent on invitations from older nodes. */\n /**\n * The wire name is `app_key`; core's Rust field is `bytecode_id` behind a\n * `rename` that covers both directions, and core pins this spelling in a test.\n * `Option<[u8; 32]>` crosses JSON as an array of byte values, not as hex \u2014\n * unlike `AccountId`, which has its own hex representation.\n */\n readonly app_key?: number[];\n /** @internal Brand \u2014 never present at runtime, never write it. */\n readonly __nodeSigned: never;\n}\n\nexport interface RecursiveInvitationEntry {\n groupId: string;\n invitation: SignedGroupOpenInvitation;\n groupName?: string;\n}\n\n// ---- Namespaces ----\n\nexport interface Namespace {\n namespaceId: string;\n appKey: string;\n targetApplicationId: string;\n /**\n * Absent from nodes that have dropped the concept, always `\"LazyOnAccess\"` on\n * every released one. Optional because this SDK talks to both; it carries no\n * information either way. Delete once no supported release still sends it.\n */\n upgradePolicy?: string;\n createdAt: number;\n name?: string;\n memberCount: number;\n contextCount: number;\n subgroupCount: number;\n /**\n * Bundle version of this namespace's `appKey` blob \u2014 the per-namespace truth,\n * where the application row only says \"latest fetched\". Absent when core\n * cannot resolve it (raw-wasm app, legacy key, blob not retained locally).\n */\n appVersion?: string;\n}\n\nexport type ListNamespacesResponseData = Namespace[];\n\n/**\n * Who this node is: the account it writes as, the device it is, and the key it\n * signs with.\n *\n * Every field is node-level. One root key means one account, everywhere, and a\n * node signs with one key \u2014 so none of this varies by namespace, which is why\n * the endpoint behind it takes none.\n */\n/**\n * A member's request that a node run one method on their behalf.\n *\n * Delegated authorship exists for a holder that cannot run the application\n * itself \u2014 a device with only a signing key, which can neither compile the WASM\n * nor decrypt the state. What makes the resulting write *theirs* is the warrant:\n * a statement they signed, which travels with the change so every peer verifies\n * they consented rather than taking the node's word.\n */\nexport interface PerformIntentRequest {\n /** The method to run. */\n method: string;\n /** Its arguments, as the JSON the guest will receive. */\n argsJson: unknown;\n /**\n * The author's consent: hex-encoded borsh of a `Warrant`.\n *\n * Opaque on purpose. Its canonical form is those bytes \u2014 the signature covers\n * them \u2014 so re-describing the fields as JSON would create a second spelling\n * that could disagree with what was signed.\n *\n * Mint it with {@link signWarrant}, or out of band with `merod account\n * warrant` / `meroctl context intent`.\n *\n * Signing here needs no dependency, which is why it is available at all: the\n * hash is WebCrypto SHA-256, the signature is WebCrypto Ed25519, and every\n * field of a warrant is fixed-width so the encoding is concatenation rather\n * than borsh. The byte contract is pinned on the node's side, at\n * `crates/account/src/tests/warrant_wire_fixture.rs`, because a drift there\n * would otherwise surface here as a 403 rather than as an encoding error.\n */\n warrant: string;\n /**\n * Hex-encoded borsh of the author's `AccountProof<DeviceCert>`, proving the\n * key that signed the warrant is a device of the account it names.\n *\n * Only the author's own half. The node attaches its own credential, so a\n * caller never learns which of the node's processes runs the intent \u2014 and a\n * re-key on its side does not void a warrant already issued.\n */\n authorProof: string;\n}\n\n/** What a performed intent reports back. */\n/**\n * A read of a context, run as the caller's account. Needs no warrant: a read\n * publishes nothing, so there is no peer to convince.\n */\nexport interface QueryContextRequest {\n /**\n * The method to call. It must be declared read-only in the application's ABI\n * (a `&self` method in a Rust app); anything else is refused with a `409`,\n * because a write needs a warrant ({@link PerformIntentRequest}).\n */\n method: string;\n /** Its arguments, as the JSON the guest will receive. */\n argsJson: unknown;\n}\n\nexport interface QueryContextResponseData {\n /** The method's own return value. */\n returns: unknown | null;\n}\n\nexport interface PerformIntentResponseData {\n /**\n * The context's scope root after the run \u2014 how a caller sees that it wrote.\n *\n * Worth asserting on rather than the status alone: an accepted intent that\n * advanced no state is a real failure mode, and it is cheaper to catch here\n * than on a later read of a value that was never written.\n */\n rootHash: string;\n /** The method's own return value, if it had one. */\n returns: unknown | null;\n}\n\n/**\n * What a node says about running intents in one context \u2014 read before minting a\n * warrant, not after being refused one.\n *\n * Every input to a warrant is something the author already holds except two:\n * whose name goes in `executor`, and whether that node may act here at all. Both\n * belong to the node, so both are answered here, on the path the intent will be\n * presented to.\n *\n * Reading it first is what lets a client fail before signing. A warrant spends a\n * nonce from a monotonic per-device sequence, and one naming the wrong executor\n * is unspendable \u2014 the number is gone and the write never happened.\n */\nexport interface IntentRelayInfo {\n /**\n * The account a warrant for this node must name as its `executor`, 64 hex.\n *\n * An account, not the node's signing key: one of its processes re-keying must\n * not void warrants already issued to it.\n */\n executorAccount: string;\n /**\n * Whether this node holds `CAN_AUTHOR_ON_BEHALF` on the group owning the\n * context.\n *\n * `false` is an answer, not an error, and it is the *default* state of every\n * context \u2014 the capability is implied by neither membership nor admin, and is\n * not propagated by the subgroup cascade. A client reads it to tell \"ask an\n * admin of `groupId`\" from \"retry later\".\n */\n canAuthorOnBehalf: boolean;\n /** The group whose admin must grant that capability, 64 hex. */\n groupId: string;\n}\n\nexport interface NodeIdentity {\n /**\n * The account this node writes as, 64 hex characters. This \u2014 not\n * `publicKey` \u2014 is what member-addressing endpoints take, and what\n * `listGroupMembers` entries are keyed by, so it is how a caller locates\n * itself in a member list.\n */\n accountId: string;\n /**\n * The device this node is, 64 hex characters, or `null` before it has\n * enrolled anywhere.\n *\n * `null` is a real answer rather than a missing one: the account id above\n * exists as soon as the node has a root, and enrolment \u2014 which a join\n * performs \u2014 is what adds the device that speaks for it.\n */\n deviceId: string | null;\n /**\n * The key this node signs ops with, 64 hex.\n *\n * The device's signing key, not the account root: the root signs\n * certificates and never an op, so a signature on the wire verifies against\n * this one.\n */\n publicKey: string;\n /**\n * The epoch-0 root **public** key of this node's account, 64 hex characters.\n *\n * What a second device needs to pair into this account, and public by\n * construction \u2014 it is hashed into `accountId` and travels in every genesis.\n * The private root never leaves the node over HTTP.\n *\n * Optional because a node at or below `0.11.0-rc.22` does not send it: the\n * field landed after that release. Required in the type would make this SDK\n * claim something the node the caller chose may not report.\n */\n accountRootPublicKey?: string;\n /**\n * Whether this node holds the root key of the account it speaks for. `false`\n * means it runs on a delegate device key, so it cannot certify another device\n * into the account \u2014 which is what a pairing invite needs to know before\n * offering the option.\n *\n * Optional for the same reason as `accountRootPublicKey`: a node at or below\n * `0.11.0-rc.30` does not send it. `undefined` is therefore \"this node is too\n * old to say\", which is not the same answer as `false`, and a caller gating an\n * invite on it should not collapse the two.\n */\n holdsAccountRoot?: boolean;\n /**\n * Whether this node's device is certified into the account it speaks for.\n *\n * Pair-init mints a device but only pair-complete certifies it, and\n * `holdsAccountRoot` is `false` across both states, so this is what tells\n * them apart. Absent on a node predating the field.\n */\n deviceCertified?: boolean;\n /**\n * Hex X25519 public agreement key of this node's device, `null` before it\n * has a device row.\n *\n * The same key pairing calls `kemPublicKey`; core's name is kept here.\n */\n deviceAgreementKey?: string | null;\n /**\n * Hex id of the account namespace this node follows.\n *\n * A holder derives it before the namespace exists so an invite can carry\n * it; a device records it at pair-init. `null` on a node that neither holds\n * a root nor has paired with the id; absent on an old node.\n */\n accountNamespaceId?: string | null;\n /**\n * The account that withdrew this node's device, absent on a node no\n * revocation has reached.\n */\n revokedFrom?: RevokedFromEntry;\n}\n\n/** Which account withdrew this node's device, and which device it was. */\nexport interface RevokedFromEntry {\n /** The account the device spoke for, 64 hex characters. */\n accountId: string;\n /** The device that was withdrawn, 64 hex characters. */\n deviceId: string;\n}\n\n/**\n * @deprecated Use {@link AdminApiClient.getNodeIdentity} instead. Nothing here\n * varies by namespace, so the parameter could only ever be decoration.\n */\nexport interface NamespaceIdentity {\n /** Echoed back from the argument; it does not describe the identity. */\n namespaceId: string;\n /** The key this node signs with, 64 hex. */\n publicKey: string;\n /**\n * The account this node writes as, 64 hex characters. This \u2014 not\n * `publicKey` \u2014 is what member-addressing endpoints take, and what\n * `listGroupMembers` entries are keyed by, so it is how a caller locates\n * itself in a member list.\n */\n account: string;\n}\n\nexport interface CreateNamespaceRequest {\n applicationId: string;\n name?: string;\n /** Hex 32-byte blob id; pins the namespace to a specific installed version. */\n appKey?: string;\n}\n\nexport interface CreateNamespaceResponseData {\n namespaceId: string;\n}\n\nexport interface DeleteNamespaceResponseData {\n isDeleted: boolean;\n}\n\nexport interface CreateNamespaceInvitationRequest {\n expirationTimestamp?: number;\n recursive?: boolean;\n}\n\nexport interface CreateNamespaceInvitationResponseData {\n invitation: SignedGroupOpenInvitation;\n groupName?: string;\n}\n\nexport interface CreateRecursiveInvitationResponseData {\n invitations: RecursiveInvitationEntry[];\n}\n\nexport interface JoinNamespaceRequest {\n invitation: SignedGroupOpenInvitation;\n groupName?: string;\n}\n\nexport interface JoinNamespaceResponseData {\n /**\n * The namespace that was joined.\n *\n * core `0.11.0-rc.25` renamed this field on the wire from `groupId`\n * (core#3598). The endpoint had been sharing a response DTO with\n * `POST /admin-api/groups/join`, which leaked the internal \"a namespace is a\n * root group\" detail into the namespace API and meant a caller could not use\n * one spelling across the family.\n *\n * {@link AdminClient.joinNamespace} fills this in from whichever spelling the\n * node sent, so it is populated against nodes on either side of that release.\n */\n namespaceId: string;\n /**\n * @deprecated The pre-`0.11.0-rc.25` spelling of {@link namespaceId}. Present\n * only when the node predates that release \u2014 read `namespaceId` instead,\n * which is always set.\n */\n groupId?: string;\n /** The key the joiner signs with, 64 hex. */\n memberIdentity: string;\n /**\n * The account that key joined as, 64 hex characters. This \u2014 not\n * `memberIdentity` \u2014 is what every member-addressing endpoint takes, so it\n * is how the caller addresses the member it just became.\n */\n memberAccount: string;\n}\n\nexport interface CreateGroupInNamespaceRequest {\n groupName?: string;\n /** Subgroup visibility at birth. Absent means `'restricted'`. */\n visibility?: 'open' | 'restricted';\n}\n\nexport interface CreateGroupInNamespaceResponseData {\n groupId: string;\n}\n\nexport interface SubgroupEntry {\n groupId: string;\n name?: string;\n}\n\n// ---- Account devices & pairing ----\n\n/**\n * Adopt an account that already lives on another device, and mint this node a\n * device of its own for it.\n *\n * The first half of pairing, run on the joining node. It publishes nothing and\n * needs no key: it produces the values the account holder's\n * {@link AdminApiClient.completeAccountPairing} certifies, plus a confirmation\n * code to read out to them.\n *\n * Account-level, and that is the whole point of it: both credentials pairing\n * mints were always account-wide - the certificate is root-signed and the\n * endorsement node-level, so neither ever named a namespace - while the older\n * per-namespace route still made the caller name one, and the device then\n * listened on that one topic while its holder's link fanned out across all of\n * them.\n */\nexport interface AccountPairInitRequest {\n /**\n * The epoch-0 root **public** key of the account to join, 64 hex characters -\n * exactly what {@link NodeIdentity.accountRootPublicKey} reports on the node\n * that already holds the account.\n *\n * Named for the half it carries. An ed25519 private and public key are both 32\n * bytes and both hex, so nothing but the name tells them apart; the private\n * root never crosses this boundary at all.\n */\n accountRootPublicKey: string;\n /**\n * Ids of the namespaces to enroll into, 64 hex characters each.\n *\n * The caller has to supply these, because the joining node cannot discover\n * them - it is a member of nothing and holds no scope key, so it can neither\n * read the account's namespace set off a DAG nor derive it. The device that\n * already holds the account is the only party that knows it.\n *\n * This decides what the new device *listens* on, and it does not have to agree\n * with the applications the holder scopes `pair-complete` to: a binding\n * published where the device is not listening is picked up whenever it does\n * subscribe, and a subscription the holder never reaches costs nothing.\n *\n * Core refuses only a request that names neither this nor `accountNamespace`.\n * A caller that sends `accountNamespace` may leave this empty.\n */\n namespaces?: string[];\n /**\n * Hex id of the account namespace to follow - the value\n * {@link NodeIdentity.accountNamespaceId} reports on the holder.\n *\n * The device then learns the account's namespaces from the account namespace\n * itself, so the caller need not know them up front. A caller that does not\n * send this must name at least one namespace above.\n */\n accountNamespace?: string;\n}\n\n/**\n * What the joining device minted, for the account holder to certify.\n *\n * These are round-trip tokens: a caller copies them verbatim into\n * {@link AdminApiClient.completeAccountPairing}. The signing key is the NEW\n * device's, so it appears in no member listing yet and there is nothing here to\n * compare it against.\n *\n * One device is minted however many namespaces were named, so there is one of\n * each of these to hand over, not one per namespace.\n */\nexport interface AccountPairInitResponseData {\n /** The account this device will speak for once linked, 64 hex characters. */\n accountId: string;\n /** The device that was minted, 64 hex characters. */\n deviceId: string;\n /** X25519 agreement key a scope key must be wrapped under to reach this device, hex. */\n kemPublicKey: string;\n /**\n * The Ed25519 key this device signs its ops with, hex.\n *\n * The account holder cannot derive this - it is minted here - and the\n * certificate names it, so it has to travel with the other two.\n */\n signPublicKey: string;\n /**\n * Ed25519 signature over the account, the device id and both keys above, hex.\n *\n * Carried so the three values arrive as a statement by the device that minted\n * them rather than as assertions by whoever relayed them; `pair-complete`\n * refuses without it.\n */\n statement: string;\n /**\n * The value to read out to the account holder, who compares it against what\n * their `pair-complete` reports.\n *\n * The part a substituting attacker cannot fake: it can re-sign its own\n * statement, but it would have to make its own keys derive the code the other\n * side is already reading.\n */\n confirmationCode: string;\n}\n\n/**\n * Certify a device another node minted, link it, and deliver its scope keys.\n *\n * The second half of pairing, run on the node that holds the account root - the\n * only party that can sign the certificate that makes the device real. Every\n * field but `applications` is copied verbatim from that other node's\n * {@link AccountPairInitResponseData}.\n */\nexport interface AccountPairCompleteRequest {\n /** The `deviceId` the other node minted, 64 hex characters. */\n deviceId: string;\n /** That device's X25519 agreement key, 64 hex characters. */\n kemPublicKey: string;\n /** That device's Ed25519 signing key, 64 hex characters. */\n signPublicKey: string;\n /**\n * The signature from that node's pair-init, 128 hex characters (64 bytes).\n *\n * Not optional: without it the three values above are only claims by whoever\n * sent them, and certifying those would make attacker-supplied keys a trusted\n * device of this account.\n */\n statement: string;\n /**\n * The code the account holder read off the pairing device, e.g.\n * `7BC0-DAAC-CCB4-84A4`. Grouping and case are ignored.\n *\n * Required so the comparison cannot be skipped: this side re-derives the code\n * for the key material that actually arrived and refuses a mismatch. Its value\n * rests on the code reaching the holder independently of this payload - sent\n * beside the keys, it proves nothing. One code covers the whole pairing,\n * because one device was minted for it.\n */\n confirmationCode: string;\n /**\n * Which applications this device may speak for, 64 hex each. Omitted or empty means\n * every one of them.\n *\n * Scoped by application rather than by namespace because a person can answer\n * \"which apps may this device use\" and cannot answer \"which namespaces\" - a\n * namespace is an implementation unit they never named. The node resolves\n * these to namespaces through the same lookup\n * {@link AdminApiClient.listNamespacesForApplication} reads.\n */\n applications?: string[];\n}\n\n/** What pairing established. */\nexport interface AccountPairCompleteResponseData {\n /** The account the device now speaks for, 64 hex characters. */\n accountId: string;\n /** The device that was linked, 64 hex characters. */\n deviceId: string;\n /**\n * Whether the current scope key was wrapped and published for the device.\n *\n * `false` does not mean pairing failed - the link is what confers authority,\n * and the device's own sync pull re-requests the key. It does mean the device\n * cannot read until that lands.\n */\n keyDelivered: boolean;\n /**\n * The code for the key material this certified - the same value the request\n * carried, echoed so the operator can see what the certificate names.\n */\n confirmationCode: string;\n /**\n * The `AccountProof<DeviceCert>` this pairing minted, hex-encoded borsh.\n *\n * The certified device needs this to present itself as a device of the\n * account, and cannot read it off the DAG: doing so means being a member of a\n * group the account speaks in, which a thin client never is. Not a secret - a\n * certificate is public and proves nothing without the device key it names.\n */\n credential: string;\n}\n\n/**\n * A domain an outside verifier defined, which the account root may sign under.\n *\n * A name from a closed set, never the domain bytes. The node prepends the bytes\n * itself: \"sign these bytes with the account root\" would be a signing oracle\n * over the one key that can certify a device, which is account takeover. A node\n * refuses any name it does not know.\n *\n * Typed as a union plus `(string & {})` so a node newer than this SDK can be\n * reached without a cast, while the known names still autocomplete.\n */\nexport type ExternalSigningDomain =\n | 'mdma.account-link'\n | 'mdma.account-login'\n | 'mdma.account-recovery'\n // eslint-disable-next-line @typescript-eslint/ban-types\n | (string & {});\n\n/**\n * Ask this node's account root to sign a payload an outside verifier specified.\n *\n * The verifier owns the format; the node supplies only the domain. For the cloud\n * this is how a challenge it sealed and issued gets signed by a root that\n * deliberately never leaves the node holding it.\n */\nexport interface AccountSignWithRootRequest {\n /** Which verifier's domain to sign under. */\n domain: ExternalSigningDomain;\n /**\n * The bytes to sign after the domain, hex-encoded.\n *\n * Hex rather than text because the field is bytes. For the cloud's challenge \u2014\n * an ASCII string \u2014 that is the UTF-8 of it, hex-encoded; {@link hexEncodeUtf8}\n * does exactly that and exists so each caller does not re-derive it.\n */\n payload: string;\n}\n\n/**\n * Hex-encode a string's UTF-8 bytes, for {@link AccountSignWithRootRequest.payload}.\n *\n * Exists so each caller does not write the same loop. Every verifier this is\n * used with issues its challenge as text, and the node takes bytes \u2014 getting\n * that conversion subtly wrong (UTF-16 code units, a stray newline from a shell)\n * produces a signature over the wrong message, which fails at the verifier with\n * nothing local to point at.\n */\nexport function hexEncodeUtf8(text: string): string {\n return Array.from(new TextEncoder().encode(text), (b) =>\n b.toString(16).padStart(2, '0'),\n ).join('');\n}\n\n/** What the node signed, and the key to check it against. */\nexport interface AccountSignWithRootResponseData {\n /** The account root's public key, 64 hex characters. */\n rootPublicKey: string;\n /**\n * The signature over `domain \u2016 payload`, **base64**.\n *\n * Base64 rather than hex because that is what the consuming verifiers decode;\n * re-encoding it on the way out is a step that fails silently at the far end.\n */\n signature: string;\n /** The account the signing key belongs to, 64 hex characters. */\n accountId: string;\n}\n\n/**\n * Repair or widen the reach of a device this account already certified.\n *\n * Pairing is a snapshot: it bound the device wherever this node took part at the\n * time, so a namespace created or joined afterwards holds no binding for it and\n * the paired device silently never sees that namespace. This re-runs the fan-out\n * against the namespaces this node takes part in now. The device need not be\n * online, and nothing but its id is required - the certificate is already held\n * here, root-signed and naming no namespace, so a fresh endorsement and a key\n * wrap are all a later namespace is missing.\n */\nexport interface RelinkDeviceRequest {\n /**\n * Applications to add to the device's stored scope, 64 hex each.\n *\n * **Omitted or empty changes nothing** and repairs against the scope already\n * stored, which is the request an operator makes to heal drift. Note that this\n * is the one place an empty list does not mean \"all\": widening a narrow scope\n * to every application is deliberately not expressible here, because\n * overloading the empty list would make the accidental request the widest one.\n */\n applications?: string[];\n}\n\n/**\n * The scope to hold: every application, or only the ones named.\n *\n * A tagged shape rather than a list whose emptiness means everything - `all`\n * and `only: []` differ by a whole order of magnitude in what they grant, and\n * an empty `only` is refused rather than treated as `all`.\n */\nexport type DeviceScope = 'all' | { only: string[] };\n\n/**\n * Replace a device's scope outright, in one root-signed op.\n *\n * {@link relinkAccountDevice} only ever adds applications; this is how a\n * scope is narrowed, or an `all` device is given a fixed list instead.\n */\nexport interface RescopeDeviceRequest {\n scope: DeviceScope;\n}\n\n/** One namespace the relink published the device's link into. */\nexport interface RelinkOutcomeEntry {\n /** The namespace id, 64 hex characters. */\n namespaceId: string;\n /**\n * Whether the scope key was wrapped and published for the device here.\n *\n * `false` means the link landed and the delivery did not; the link is what\n * confers authority, and the device's own sync pull re-requests the key.\n */\n keyDelivered: boolean;\n}\n\n/** One namespace the relink published nothing into, and why. */\nexport interface RelinkSkipEntry {\n /** The namespace id, 64 hex characters. */\n namespaceId: string;\n /**\n * Why nothing was published there. Currently one of `outOfScope`,\n * `alreadyBound`, `noScopeKey`, `revoked`, `ownDevice`, `failed`; left as a\n * string because a node may report a reason newer than this SDK.\n */\n reason: string;\n}\n\n/** One namespace a scope replacement no longer reaches. */\nexport interface RescopeDescopeEntry {\n /** The namespace id, 64 hex characters. */\n namespaceId: string;\n /**\n * Whether the scope key was rotated. `false` means the device stopped writing\n * there but still holds the key it had, until an admin rotates.\n */\n keyRotated: boolean;\n}\n\n/** The scope a device now holds, and what each namespace did about it. */\nexport interface RescopeDeviceResponseData {\n /** The account the device speaks for, 64 hex characters. */\n accountId: string;\n /** The device that was rescoped, 64 hex characters. */\n deviceId: string;\n /** The scope after the request, 64 hex each. Empty means every application. */\n applications: string[];\n /** Namespaces the new scope no longer reaches. */\n descoped: RescopeDescopeEntry[];\n /** Namespaces the device was linked into by this call. */\n linkedIn: RelinkOutcomeEntry[];\n /** Namespaces nothing was published into, and why. */\n skipped: RelinkSkipEntry[];\n}\n\n/** What the relink repaired, and what it left alone. */\nexport interface RelinkDeviceResponseData {\n /** The account the device speaks for, 64 hex characters. */\n accountId: string;\n /** The device that was repaired, 64 hex characters. */\n deviceId: string;\n /**\n * The device's scope after the request, 64 hex each. Empty means every\n * application, which is what a pairing that named none asked for.\n */\n applications: string[];\n /**\n * Namespaces the link was published into by this call. Reported per namespace\n * because publication is per-DAG, so which namespaces a device actually\n * reached is a state the caller has to be able to see.\n */\n linkedIn: RelinkOutcomeEntry[];\n /** Namespaces nothing was published into. */\n skipped: RelinkSkipEntry[];\n}\n\n/**\n * One device of this account, joined from the node-local certificate cache and\n * the live bindings of every namespace this node takes part in.\n */\nexport interface AccountDeviceEntry {\n /** This device's id, 64 hex characters. */\n deviceId: string;\n /** The key this device signs its ops with, 64 hex. */\n signingKey: string;\n /** Set only on the device this node itself presents. */\n isSelf: boolean;\n revoked: boolean;\n /**\n * Applications this device may speak for, 64 hex each. **Empty means every\n * application**, the same convention the stored certificate uses - and the\n * opposite of what an empty {@link RelinkDeviceRequest.applications} asks for.\n * Empty also for a device this node holds no cached certificate for: one bound\n * before the cache existed, or certified by another holder.\n */\n applications: string[];\n /**\n * Ids of the namespaces currently holding a live binding for this device, 64\n * hex characters each. Empty for a certified device not yet bound anywhere.\n */\n namespaces: string[];\n /**\n * The replicated name the account gave this device, absent while it has\n * none. Every device of the account reads the same one.\n */\n label?: string;\n}\n\n/**\n * Name a device of this account, for a listing to render.\n *\n * Run on the node holding the account root to name any device; a paired node\n * is accepted only for that device's own id.\n */\nexport interface LabelDeviceRequest {\n /** Trimmed, non-empty, bounded and free of control characters. */\n label: string;\n}\n\n/**\n * The name that was published, and the epoch that orders it against a rename\n * another device of the account made at the same time.\n */\nexport interface LabelDeviceResponseData {\n /** The account the device speaks for, 64 hex characters. */\n accountId: string;\n /** The device that was named, 64 hex characters. */\n deviceId: string;\n label: string;\n labelEpoch: number;\n}\n\n/**\n * One application this account speaks in, derived from the namespaces this node\n * takes part in that target it.\n *\n * An application installed with no namespace yet is absent here: it has no\n * cross-device meaning until a namespace exists, so there is nothing to scope a\n * device to.\n */\nexport interface AccountApplicationEntry {\n /** The application id, 64 hex. */\n applicationId: string;\n /** Ids of the namespaces targeting it, 64 hex characters each. */\n namespaces: string[];\n}\n\n/** Withdraw a device from an account, terminally. */\nexport interface RevokeAccountDeviceRequest {\n /** The device to withdraw, 64 hex. */\n deviceId: string;\n /**\n * A hex-encoded, borsh-serialized signed revocation minted elsewhere -\n * the output of `merod account revoke-proof`.\n *\n * The lost-device path: the account root that owns the device is not on this\n * node, so the proof is signed offline and this node only publishes it. Omit\n * it and the node mints its own proof if it owns the account, and otherwise\n * revokes as an admin.\n *\n * Not a credential and not a secret - it authorises this one revocation of\n * this one device, and only alongside a stored binding naming the same\n * account.\n */\n proof?: string;\n}\n\n/** What a revocation withdrew in one namespace. */\nexport interface RevocationOutcome {\n /** The namespace, 64 hex. */\n namespaceId: string;\n /**\n * Whether the scope key rotated in the same op, in THIS namespace.\n *\n * `false` means the device stopped writing there at once but still holds the\n * key it had, so it can keep READING until an admin rotates. Only an admin\n * may rotate and an account holder revoking their own device usually is not\n * one, so this is commonly owed rather than exceptional.\n */\n keyRotated: boolean;\n}\n\nexport interface RevokeAccountDeviceResponseData {\n /** The account the device spoke for, 64 hex. */\n accountId: string;\n /** The device that was withdrawn, 64 hex. */\n deviceId: string;\n /**\n * Whether the scope key rotated in the namespace named in the request.\n *\n * {@link revokedIn} is the full picture; prefer it. This reports only the one\n * namespace the caller asked about and is retained because core keeps it for\n * a released `calimero-client-py` wheel that requires the field.\n */\n keyRotated: boolean;\n /**\n * Every namespace the revocation was published into.\n *\n * A device belongs to an account rather than to a scope, so revoking it\n * withdraws it from every namespace holding a binding for it - not only the\n * one named in the request. Per namespace because publication is per-DAG: a\n * namespace absent here did not receive the op, and a partially propagated\n * revocation is a state the caller has to be able to see.\n */\n revokedIn: RevocationOutcome[];\n}\n\n// ---- Groups ----\n\nexport interface CreateGroupRequest {\n applicationId: string;\n groupId?: string;\n appKey?: string;\n name?: string;\n parentGroupId?: string;\n}\n\nexport interface CreateGroupResponseData {\n groupId: string;\n}\n\nexport interface GroupUpgradeStatus {\n fromVersion: string;\n toVersion: string;\n initiatedAt: number;\n initiatedBy: string;\n status: string;\n /** Contexts THIS NODE enumerated for the upgrade. Node-local; fleet progress\n * is the `getMigrationStatus` rollup. */\n localContextsTotal?: number;\n localContextsSwapped?: number;\n /** Contexts whose swap failed on this node; a non-zero value is what\n * `retryGroupUpgrade` picks up. */\n localContextsFailed?: number;\n /** Unix seconds at which THIS NODE finished its own context swaps. Not fleet\n * convergence - that is `MigrationStatus.fleetCompletedAt`. */\n completedAt?: number;\n}\n\n// ---- Migration status (migration-UX core surfaces) ----\n\nexport type MemberMigrationState =\n | 'migrated'\n | 'in_progress'\n | 'unknown'\n | 'failed';\n\n/** Why a member's migration did not complete. */\nexport type MigrationFailureReason =\n | 'check_aborted'\n | 'apply_failed'\n | 'no_migration_path';\n\nexport interface MemberMigrationReport {\n /** ABI state version the member has loaded, from `#[app::state(version = N)]` -\n * the engine's migration gate, not the bundle semver and not a CRDT concept.\n * The wrong reading here is how the core rollup defect (comparing bundle-semver\n * majors instead of state versions) went unnoticed. */\n schemaVersion: number;\n residueAuto: number;\n syncedUpToHlc: number;\n reportedAt: number;\n /** Member's self-reported pending-authored count (best-effort; skew #1). */\n authoredRemaining: number;\n /**\n * Set when the member's migrate did not complete (its migration-check\n * aborted, or the apply errored). Absent otherwise.\n */\n migrationFailed?: MigrationFailureReason;\n}\n\nexport interface MemberMigrationStatusEntry {\n /** The reporting device's signing KEY, bs58. One account with two devices\n * appears as two rows: the cohort is replicas, not people. */\n peer: string;\n /** The ACCOUNT `peer` speaks for, 64 hex - join it to `listGroupMembers` to\n * name a person. Absent from nodes predating the field. */\n account?: string;\n /** Freshest reported facts. Omitted (not `null`) when the member's state is\n * `unknown`, so test falsiness rather than `=== null`. */\n report?: MemberMigrationReport | null;\n state: MemberMigrationState;\n}\n\nexport interface MigrationStatusRollup {\n migrated: number;\n inProgress: number;\n unknown: number;\n /** Members whose migrate aborted (migration-check failed or apply errored). */\n failed: number;\n total: number;\n allMigrated: boolean;\n /** Count of members with authoredRemaining > 0 (owners still to re-sign). */\n membersPendingSignature: number;\n}\n\nexport interface MigrationStatus {\n targetVersion: number;\n expectedMembers: number;\n /** Governance HLC the cohort was pinned at, as an opaque display string.\n * Absent when there is no migration record. */\n cohortPinnedAtHlc?: string;\n /** Unix seconds at which this node watched the FLEET converge, absent while it\n * has not. Distinct from `GroupUpgradeStatus.completedAt`, which is this node's\n * own local swap completion. Durable, unlike `rollup.allMigrated`, which is\n * recomputed from in-TTL heartbeats and lapses when a member goes quiet. */\n fleetCompletedAt?: number;\n rollup: MigrationStatusRollup;\n members: MemberMigrationStatusEntry[];\n}\n\n// ---- Cascade status ----\n\nexport interface CascadeStatusEntry {\n groupId: string;\n upgrade: GroupUpgradeStatus;\n cascadeHlc?: string;\n}\n\nexport interface GroupInfo {\n groupId: string;\n appKey: string;\n targetApplicationId: string;\n /**\n * Absent from nodes that have dropped the concept, always `\"LazyOnAccess\"` on\n * every released one. Optional because this SDK talks to both; it carries no\n * information either way. Delete once no supported release still sends it.\n */\n upgradePolicy?: string;\n memberCount: number;\n contextCount: number;\n activeUpgrade?: GroupUpgradeStatus;\n defaultCapabilities: number;\n subgroupVisibility: string;\n /**\n * The group's generic metadata record (replaces the old `alias` field).\n * `null` if no metadata has ever been set for this group.\n */\n metadata?: MetadataRecord | null;\n}\n\nexport type GroupInfoResponseData = GroupInfo;\n\nexport interface GroupMember {\n /**\n * The member's ACCOUNT: 64 hex characters.\n *\n * Not a signing key \u2014 a person may hold several keys\n * and governance rows name the person. Both are 32 bytes, so nothing here or\n * on the server will object if you pass the wrong one; it will simply name a\n * principal that exists nowhere. Feed this value to\n * {@link RemoveGroupMembersRequest} and {@link UpdateMemberRoleRequest}, not\n * to {@link GroupMemberInput}.\n */\n identity: string;\n role: string;\n name?: string;\n}\n\nexport interface ListGroupMembersResponseData {\n members: GroupMember[];\n /**\n * @deprecated The server response uses `members`, not `data`. This alias\n * is retained so existing callers compile during the upgrade window; it\n * is never populated by the client and will be removed in the next\n * major. Switch reads to `response.members`.\n */\n data?: GroupMember[];\n}\n\n/** One device bound to a member account. */\nexport interface MemberDevice {\n /** 64 hex; the form `revokeAccountDevice` takes. */\n deviceId: string;\n /**\n * The key this device's signatures carry, 64 hex. Joins against the\n * identities a context lists.\n */\n signingKey: string;\n}\n\n/** A member account and the devices bound to it in the namespace. */\nexport interface MemberDevicesEntry {\n /** The member's ACCOUNT, 64 hex. */\n account: string;\n devices: MemberDevice[];\n}\n\nexport interface ListMemberDevicesOptions {\n offset?: number;\n limit?: number;\n}\n\nexport interface ListMemberDevicesResponseData {\n /**\n * Every account in the group if the calling node is an admin; only its own\n * entry if it is a plain member.\n */\n members: MemberDevicesEntry[];\n}\n\nexport interface SealToAccountRequest {\n /**\n * Hex-encoded plaintext. Small by design: the route exists to hand an account\n * something like a list of namespace ids, and the node caps the size.\n */\n plaintext: string;\n}\n\n/**\n * A payload sealed to an account's root key. Only that account's root can open\n * it; the node that sealed it cannot.\n */\nexport interface SealedEnvelope {\n /**\n * The root-key epoch it was sealed under. A rotated root still opens envelopes\n * from its own epoch, so a recipient holding several needs to know which.\n */\n accountRootEpoch: number;\n /** Hex, 32 bytes: the one-shot sender key. */\n ephemeralPublicKey: string;\n /** Hex, 12 bytes: the AES-256-GCM nonce. */\n nonce: string;\n /** Hex: the ciphertext with its 16-byte tag appended. */\n ciphertext: string;\n}\n\nexport interface GroupContextEntry {\n contextId: string;\n name?: string;\n}\n\nexport type ListGroupContextsResponseData = GroupContextEntry[];\n\nexport interface DeleteGroupResponseData {\n isDeleted: boolean;\n}\n\n// ---- Group Members ----\n\nexport interface GroupMemberInput {\n /**\n * The invitee's signing KEY, 64 hex \u2014 NOT an account. It is spelled exactly\n * like one, so only the field name says which this is.\n *\n * Adding is the one member-facing call that names a key: the node binds the\n * key to an account as it admits it, so before that there is no account to\n * name. Every call that names an EXISTING member takes the account instead.\n */\n identity: string;\n role: string;\n}\n\nexport interface AddGroupMembersRequest {\n members: GroupMemberInput[];\n}\n\n// Returns empty\nexport type AddGroupMembersResponseData = Record<string, never>;\n\nexport interface RemoveGroupMembersRequest {\n /**\n * The members to remove, as ACCOUNTS (64 hex) \u2014 the same ids\n * {@link GroupMember.identity} returns, and not the keys\n * {@link GroupMemberInput.identity} took to add them.\n */\n members: string[];\n}\n\n// Returns empty\nexport type RemoveGroupMembersResponseData = Record<string, never>;\n\nexport interface UpdateMemberRoleRequest {\n role: string;\n}\n\n// Returns empty\nexport type UpdateMemberRoleResponseData = Record<string, never>;\n\n// ---- Group Capabilities & Settings ----\n\nexport interface MemberCapabilities {\n capabilities: number;\n}\n\nexport interface SetMemberCapabilitiesRequest {\n capabilities: number;\n}\n\n// Returns empty\nexport type SetMemberCapabilitiesResponseData = Record<string, never>;\n\nexport interface SetDefaultCapabilitiesRequest {\n defaultCapabilities: number;\n}\n\n// Returns empty\nexport type SetDefaultCapabilitiesResponseData = Record<string, never>;\n\nexport interface SetMemberAutoFollowRequest {\n /** Auto-join contexts registered in this group. */\n autoFollowContexts: boolean;\n /** Self-admit into subgroups nested under this group. */\n autoFollowSubgroups: boolean;\n}\n\n// Returns empty\nexport type SetMemberAutoFollowResponseData = Record<string, never>;\n\nexport interface SetSubgroupVisibilityRequest {\n subgroupVisibility: string;\n}\n\n// Returns empty\nexport type SetSubgroupVisibilityResponseData = Record<string, never>;\n\n/**\n * The signed-release form of a TEE admission policy: admit a TEE running any\n * mero-tee node release the mero-tee release workflow signed, if its quote\n * matches one of `allowedProfiles` in that release's `published-mrtds.json`.\n * Unlike the measurement lists, it does not need updating for each release.\n */\nexport interface SignedReleaseTeePolicy {\n /** Image profiles to admit, e.g. `locked-read-only`. */\n allowedProfiles: string[];\n /** The oldest release admitted (`2.3.72`); any signed release when absent. */\n minReleaseVersion?: string;\n}\n\n/** A policy that lists the measurements it admits. */\nexport interface MeasurementTeeAdmissionPolicyRequest {\n allowedMrtd: string[];\n allowedRtmr0: string[];\n allowedRtmr1: string[];\n allowedRtmr2: string[];\n allowedRtmr3: string[];\n allowedTcbStatuses: string[];\n acceptMock: boolean;\n}\n\n/** A policy that admits by signed release. The node refuses measurement lists beside it. */\nexport interface SignedReleaseTeeAdmissionPolicyRequest {\n signedRelease: SignedReleaseTeePolicy;\n allowedTcbStatuses: string[];\n acceptMock: boolean;\n}\n\nexport type SetTeeAdmissionPolicyRequest =\n | MeasurementTeeAdmissionPolicyRequest\n | SignedReleaseTeeAdmissionPolicyRequest;\n\n// Returns empty\nexport type SetTeeAdmissionPolicyResponseData = Record<string, never>;\n\nexport interface GetTeeAdmissionPolicyResponseData {\n allowedMrtd: string[];\n allowedRtmr0: string[];\n allowedRtmr1: string[];\n allowedRtmr2: string[];\n allowedRtmr3: string[];\n allowedTcbStatuses: string[];\n acceptMock: boolean;\n /** Set when the policy admits by signed release; the lists are then empty. */\n signedRelease?: SignedReleaseTeePolicy;\n}\n\n/**\n * Which admitted TEEs may author as the group's TEE authority (the writer behind\n * `TeeOnly` storage and `#[app::tee]` methods). A TEE whose attested MRTD is in\n * `allowedMrtd` may author; an empty list turns TEE authorship off.\n *\n * Namespace-scoped: set it on the namespace root. The node refuses it on a subgroup.\n */\nexport interface SetTeeAuthoringPolicyRequest {\n allowedMrtd: string[];\n}\n\n// Returns empty\nexport type SetTeeAuthoringPolicyResponseData = Record<string, never>;\n\n// ---- Group / member / context metadata ----\n\n/**\n * Generic metadata record attached to a group, group member, or\n * context-registered-in-a-group (core `calimero_primitives::metadata::MetadataRecord`).\n *\n * `data` is application-defined and opaque to core \u2014 it is stored verbatim.\n * Server-enforced size limits: `name` <= 64 bytes; at most 64 entries in\n * `data`; each key <= 64 bytes; each value <= 4096 bytes. Clients do not need\n * to enforce these \u2014 the server validates.\n */\nexport interface MetadataRecord {\n name: string | null;\n data: Record<string, string>;\n updatedAt: number;\n /** Public key (hex) of the member that last updated the record. */\n updatedBy: string;\n}\n\n/**\n * Request body for setting a metadata record. **This wholly replaces the\n * record**: `data` defaults to `{}` server-side and replaces the stored map,\n * while omitting `name` keeps the current name. To change `name` while\n * preserving existing `data`, GET the record first and pass its `data` back.\n */\nexport interface SetMetadataRequest {\n name?: string;\n data?: Record<string, string>;\n}\n\nexport type SetGroupMetadataRequest = SetMetadataRequest;\nexport type SetMemberMetadataRequest = SetMetadataRequest;\nexport type SetContextMetadataRequest = SetMetadataRequest;\n\n// Set-metadata returns empty\nexport type SetMetadataResponseData = Record<string, never>;\n\n/**\n * Inner payload of a GET metadata response. `data` is `null` if no metadata\n * has ever been set for the target group/member/context.\n */\nexport interface GetMetadataResponseData {\n data: MetadataRecord | null;\n}\n\n// ---- Group Sync, Signing & Upgrades ----\n\nexport interface SyncGroupResponseData {\n groupId: string;\n appKey: string;\n targetApplicationId: string;\n memberCount: number;\n contextCount: number;\n}\n\nexport interface UpgradeGroupRequest {\n targetApplicationId: string;\n /** Fan the upgrade out to every descendant subgroup running the same app\n * (one atomic cascade op). Without it the upgrade applies to the target\n * group only - members' subgroups never learn the migration. Server\n * default: false. */\n cascade?: boolean;\n /** Proceed code-only when the target build embeds no ABI. Core refuses such an\n * upgrade by default; setting this asserts the new code is layout-compatible\n * with the running state so the upgrade goes ahead. It never bypasses a\n * declared-migration or downgrade refusal. Wire name is camelCase passthrough;\n * older nodes ignore the unknown field. Server default: false. */\n forceCodeOnly?: boolean;\n}\n\nexport interface UpgradeGroupResponseData {\n groupId: string;\n status: string;\n localContextsTotal?: number;\n localContextsSwapped?: number;\n localContextsFailed?: number;\n}\n\nexport type GroupUpgradeStatusResponseData = GroupUpgradeStatus | null;\n\n// Retry returns same shape as upgrade\nexport type RetryGroupUpgradeResponseData = UpgradeGroupResponseData;\n\n// ---- Group Reparent & Context Attachments ----\n\nexport interface ReparentGroupRequest {\n /** 64-char id of the destination parent group. */\n newParentId: string;\n}\n\nexport interface ReparentGroupResponseData {\n reparented: boolean;\n}\n\n// Returns empty\nexport type DetachContextFromGroupResponseData = Record<string, never>;\n\n// ---- Group Ownership Proofs ----\n\nexport interface IssueOwnershipProofRequest {\n /** Who the proof is for. Non-empty, <= 256 characters. */\n audience: string;\n /** 64-hex context id the proof is scoped to. */\n contextId: string;\n /** What is being claimed. Non-empty, <= 512 characters. */\n subject: string;\n /** Caller-chosen hex nonce, 32-128 characters (even length). */\n nonce: string;\n /** Requested expiry, unix ms. Core clamps it to issue time + 5 minutes. */\n expiresAtMs: number;\n}\n\n/** Namespace-scoped sibling: identical minus `contextId`. */\nexport type IssueNamespaceOwnershipProofRequest = Omit<IssueOwnershipProofRequest, 'contextId'>;\n\nexport interface IssueOwnershipProofResponseData {\n /** Hex 32-byte ed25519 public key of the signer. */\n signerPublicKey: string;\n /**\n * Base64 opaque UTF-8 JSON bytes of the claim. Verifiers re-parse these exact\n * bytes and sign over `\"calimero.ownership-claim.v1\\0\" || signedPayload`.\n */\n signedPayload: string;\n /** Base64 64-byte ed25519 signature. */\n signature: string;\n}\n\n// ---- Group Invitation & Join ----\n\nexport interface CreateGroupInvitationRequest {\n expirationTimestamp?: number;\n recursive?: boolean;\n /**\n * Accounts permitted to admit a claim of this invitation, 64 hex each.\n *\n * Omitted or empty is filled in by the node from the group's admins and its\n * TEE nodes; it refuses to mint if that set comes back empty, because an empty\n * list on the wire authorises any node to admit. Naming admitters narrows that\n * set.\n *\n * Core signs this list into the invitation, so it cannot be redirected after\n * the fact.\n */\n admitters?: string[];\n /**\n * libp2p addresses for those admitters, each including its `/p2p/<peer-id>`\n * suffix.\n *\n * Omitted or empty asks the node to fill them in from addresses it already has\n * on file \u2014 which is usually what you want. Values supplied here are used as\n * given rather than merged, on the grounds that a caller naming an address is\n * correcting the node's view rather than extending it.\n *\n * Not signed, so a wrong address costs a failed dial and nothing more.\n */\n admitterAddrs?: string[];\n}\n\nexport interface CreateGroupInvitationResponseData {\n invitation: SignedGroupOpenInvitation;\n groupName?: string;\n}\n\nexport interface CreateRecursiveGroupInvitationResponseData {\n invitations: RecursiveInvitationEntry[];\n}\n\nexport interface JoinGroupRequest {\n invitation: SignedGroupOpenInvitation;\n groupName?: string;\n}\n\nexport interface JoinGroupResponseData {\n groupId: string;\n /** The key the joiner signs with, 64 hex. */\n memberIdentity: string;\n /**\n * The account that key joined as, 64 hex characters. This \u2014 not\n * `memberIdentity` \u2014 is what every member-addressing endpoint takes, so it\n * is how the caller addresses the member it just became.\n */\n memberAccount: string;\n}\n\n// ---- TEE ----\n\nexport interface TeeInfoResponseData {\n cloudProvider: string;\n osImage: string;\n mrtd: string;\n}\n\nexport interface TeeAttestRequest {\n nonce: string;\n applicationId?: string;\n /**\n * Bind the node's X25519 transport key into the quote; the response names it\n * as `transportPublicKey`. See `fetchAttestedTransportKey`.\n */\n bindTransportKey?: boolean;\n /**\n * Also return the Intel-signed collateral the quote is verified against, as\n * `collateral`, so the quote can be verified with nothing but the node. See\n * `createQuoteVerifier`. A node that predates it refuses the field.\n */\n includeCollateral?: boolean;\n}\n\nexport interface QuoteHeader {\n version: number;\n attestationKeyType: number;\n teeType: number;\n qeVendorId: string;\n userData: string;\n}\n\nexport interface QuoteBody {\n tdxVersion: string;\n teeTcbSvn: string;\n mrseam: string;\n mrsignerseam: string;\n seamattributes: string;\n tdattributes: string;\n xfam: string;\n mrtd: string;\n mrconfigid: string;\n mrowner: string;\n mrownerconfig: string;\n rtmr0: string;\n rtmr1: string;\n rtmr2: string;\n rtmr3: string;\n reportdata: string;\n teeTcbSvn2?: string;\n mrservicetd?: string;\n}\n\nexport interface Quote {\n header: QuoteHeader;\n body: QuoteBody;\n signature: string;\n attestationKey: string;\n certificationData: unknown;\n}\n\nexport interface TeeAttestResponseData {\n quoteB64: string;\n quote: Quote;\n /** Hex X25519 transport key, present only when the request set `bindTransportKey`. */\n transportPublicKey?: string;\n /**\n * The Intel-signed collateral the quote verifies against, present only when\n * the request set `includeCollateral` and the quote is not a mock.\n */\n collateral?: DcapCollateral;\n}\n\n/**\n * @deprecated Nodes no longer serve `/admin-api/tee/verify-quote` (removed in\n * core#3262), so a request for it fails. Verify quotes with\n * `createQuoteVerifier`.\n */\nexport interface TeeVerifyQuoteRequest {\n quoteB64: string;\n nonce: string;\n expectedApplicationHash?: string;\n}\n\n/** @deprecated See {@link TeeVerifyQuoteRequest}. */\nexport interface TeeVerifyQuoteResponseData {\n quoteVerified: boolean;\n nonceVerified: boolean;\n applicationHashVerified?: boolean;\n quote: Quote;\n}\n\n// ---- Client Configuration ----\n\nexport interface AdminApiClientConfig {\n baseUrl: string;\n getAuthToken?: () => Promise<string | undefined>;\n timeoutMs?: number;\n}\n\n/** A join signed by its subject, handed to an admitter to publish. */\nexport interface AdmitJoinRequest {\n /** The invitation being claimed. Must name the admitter in `admitters`. */\n readonly invitation: SignedGroupOpenInvitation;\n /**\n * The signed `SignedNamespaceOp`, borsh-encoded and hex.\n *\n * Signed by the joiner's device key, which is what stops an admitter\n * substituting a different member: peers check the op's signer against the\n * credential the op carries.\n */\n readonly signedOp: string;\n}\n\n/** What the admitter did with it. */\nexport interface AdmitJoinResponseData {\n /**\n * Whether the op reached the namespace topic.\n *\n * Not \"joined\" \u2014 membership lands when peers apply the op, which the admitter\n * neither performs nor waits for.\n */\n readonly published: boolean;\n}\n", "import { AdminApiClient } from './admin-client.js';\nimport { AdminApiClientConfig } from './admin-types.js';\nimport {\n HttpClient,\n createBrowserHttpClient,\n createNodeHttpClient,\n createUniversalHttpClient,\n} from '../http-client/index.js';\n\n// Factory functions for creating Admin API clients\n\n/** Admin client over a browser HTTP transport (global `fetch`). */\nexport function createBrowserAdminApiClient(\n config: AdminApiClientConfig,\n): AdminApiClient {\n return new AdminApiClient(createBrowserHttpClient(config));\n}\n\n/** Admin client over a Node HTTP transport (Node 18+ `fetch`). */\nexport function createNodeAdminApiClient(\n config: AdminApiClientConfig,\n): AdminApiClient {\n return new AdminApiClient(createNodeHttpClient(config));\n}\n\n/** Admin client over whichever transport suits the current runtime. */\nexport function createAdminApiClient(\n config: AdminApiClientConfig,\n): AdminApiClient {\n return new AdminApiClient(createUniversalHttpClient(config));\n}\n\n/**\n * Admin client over a caller-supplied HTTP client \u2014 use this to share one\n * transport (and its token handling) with the other API clients.\n */\nexport function createAdminApiClientFromHttpClient(\n httpClient: HttpClient,\n _config: AdminApiClientConfig,\n): AdminApiClient {\n return new AdminApiClient(httpClient);\n}\n", "export interface AuthCallbackResult {\n accessToken: string;\n refreshToken: string;\n applicationId: string;\n contextId: string;\n contextIdentity: string;\n nodeUrl: string;\n}\n\nexport interface AuthLoginOptions {\n callbackUrl: string;\n packageName?: string;\n mode: string;\n permissions?: string[];\n registryUrl?: string;\n packageVersion?: string;\n}\n\n/**\n * Parse an auth callback URL hash fragment.\n * Returns null if access_token is missing.\n */\nexport function parseAuthCallback(url: string): AuthCallbackResult | null {\n try {\n const hashIndex = url.indexOf('#');\n if (hashIndex === -1) return null;\n\n const hash = url.substring(hashIndex + 1);\n const params = new URLSearchParams(hash);\n\n const accessToken = params.get('access_token');\n if (!accessToken) return null;\n\n return {\n accessToken,\n refreshToken: params.get('refresh_token') ?? '',\n applicationId: params.get('application_id') ?? '',\n contextId: params.get('context_id') ?? '',\n contextIdentity: params.get('context_identity') ?? '',\n nodeUrl: params.get('node_url') ?? '',\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Build the auth login URL for redirecting users to the node's auth page.\n */\nexport function buildAuthLoginUrl(nodeUrl: string, opts: AuthLoginOptions): string {\n const params = new URLSearchParams();\n params.set('callback-url', opts.callbackUrl);\n\n if (opts.permissions && opts.permissions.length > 0) {\n params.set('permissions', opts.permissions.join(','));\n }\n\n params.set('mode', opts.mode);\n\n if (opts.packageName) {\n params.set('package-name', opts.packageName);\n if (opts.packageVersion) {\n params.set('package-version', opts.packageVersion);\n }\n if (opts.registryUrl) {\n params.set('registry-url', opts.registryUrl);\n }\n }\n\n // Trim trailing slash from nodeUrl\n const base = nodeUrl.replace(/\\/+$/, '');\n return `${base}/auth/login?${params.toString()}`;\n}\n", "/**\n * Byte-level primitives shared by everything in this package that signs.\n *\n * Extracted from `warrant/warrant.ts` when the device-cert signer arrived and\n * needed the same `domainHash`, hex handling and ed25519 import. Keeping one\n * copy is the point: these have to stay byte-identical with core, and two\n * copies drifting apart is exactly the failure the byte-parity e2e exists to\n * catch. Internal \u2014 not exported from the package root.\n */\n\nconst PKCS8_ED25519_PREFIX = new Uint8Array([\n 0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04,\n 0x22, 0x04, 0x20,\n]);\n\n/** Little-endian u32, for borsh fixed-width fields and epoch counters. */\nexport function u32le(value: number): Uint8Array {\n const out = new Uint8Array(4);\n new DataView(out.buffer).setUint32(0, value, true);\n return out;\n}\n\nexport function hex(bytes: Uint8Array): string {\n return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n}\n\nexport function fromHex(value: string, label: string, expectedBytes: number): Uint8Array {\n const clean = value.trim();\n if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length !== expectedBytes * 2) {\n throw new Error(\n `${label} must be ${expectedBytes * 2} hex characters, got ${clean.length}`,\n );\n }\n // Indexed rather than `match(/../g)!`: the length is already validated above,\n // so the assertion would only be telling the compiler what the guard proved,\n // and this needs no assertion at all.\n const bytes = new Uint8Array(expectedBytes);\n for (let i = 0; i < expectedBytes; i += 1) {\n bytes[i] = Number.parseInt(clean.slice(i * 2, i * 2 + 2), 16);\n }\n return bytes;\n}\n\n/**\n * Decode hex of an unknown length.\n *\n * The sized {@link fromHex} is the right default \u2014 every key and digest here\n * has a fixed width, and checking it catches a truncated paste at the edge\n * rather than deep inside a signature check. This one exists for the already-\n * encoded structures whose length is genuinely variable: a device certificate,\n * a login statement, a request signature. It still rejects odd-length and\n * non-hex input, so the only check it gives up is the one that cannot apply.\n */\nexport function fromHexUnsized(value: string, label: string): Uint8Array {\n const clean = value.trim();\n if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length % 2 !== 0) {\n throw new Error(`${label} must be an even number of hex characters`);\n }\n const bytes = new Uint8Array(clean.length / 2);\n for (let i = 0; i < bytes.length; i += 1) {\n bytes[i] = Number.parseInt(clean.slice(i * 2, i * 2 + 2), 16);\n }\n return bytes;\n}\n\nexport function u64le(value: number | bigint): Uint8Array {\n const out = new Uint8Array(8);\n new DataView(out.buffer).setBigUint64(0, BigInt(value), true);\n return out;\n}\n\nexport function concat(...parts: Uint8Array[]): Uint8Array {\n const out = new Uint8Array(parts.reduce((total, p) => total + p.length, 0));\n let offset = 0;\n for (const part of parts) {\n out.set(part, offset);\n offset += part.length;\n }\n return out;\n}\n\n/**\n * core's `domain_hash`: SHA-256 over the length-prefixed domain, then each\n * length-prefixed part. The lengths are what stop two different field splits from\n * hashing alike.\n */\nexport async function domainHash(\n domain: Uint8Array,\n parts: Uint8Array[],\n): Promise<Uint8Array> {\n const chunks: Uint8Array[] = [u64le(domain.length), domain];\n for (const part of parts) {\n chunks.push(u64le(part.length), part);\n }\n return new Uint8Array(\n await crypto.subtle.digest('SHA-256', concat(...chunks)),\n );\n}\n\nexport async function importSigningKey(secretHex: string): Promise<CryptoKey> {\n const seed = fromHex(secretHex, 'deviceSecret', 32);\n const pkcs8 = concat(PKCS8_ED25519_PREFIX, seed);\n try {\n return await crypto.subtle.importKey(\n 'pkcs8',\n pkcs8,\n { name: 'Ed25519' },\n false,\n ['sign'],\n );\n } catch (cause) {\n // The original message is folded in rather than passed as `cause`: this\n // package targets ES2020, where `Error` has no `cause` option.\n const detail = cause instanceof Error ? `: ${cause.message}` : '';\n throw new Error(\n 'this runtime has no WebCrypto Ed25519, so a warrant cannot be signed ' +\n 'here. Node 18.4+, Safari 17+, Firefox 129+ and Chrome 137+ have it; ' +\n 'otherwise mint the warrant with `merod account warrant`' +\n detail,\n );\n }\n}\n\n/**\n * The public half of an ed25519 seed.\n *\n * Derived rather than taken as an argument, for the reason core does the same: a\n * caller able to name a key it does not hold could produce a warrant it cannot\n * sign, and the field would stop meaning \"who authorised this\".\n */\nexport async function derivePublicKey(secretHex: string): Promise<Uint8Array> {\n const seed = fromHex(secretHex, 'deviceSecret', 32);\n const pkcs8 = concat(PKCS8_ED25519_PREFIX, seed);\n const key = await crypto.subtle.importKey(\n 'pkcs8',\n pkcs8,\n { name: 'Ed25519' },\n true,\n ['sign'],\n );\n const jwk = await crypto.subtle.exportKey('jwk', key);\n const raw = (jwk as JsonWebKey & { x: string }).x;\n return Uint8Array.from(atob(raw.replace(/-/g, '+').replace(/_/g, '/')), (c) =>\n c.charCodeAt(0),\n );\n}\n", "/**\n * Sign one HTTP request, so a caller's identity travels with it.\n *\n * This is the bottom link of the chain a delegated client presents. A session\n * has the node mint and remember something; this has the caller prove itself on\n * every call, so the node holds no session state and the same path works\n * whether or not the node runs a login provider at all.\n *\n * ```text\n * account root \u2500\u2500signs\u2500\u2500\u25B6 DeviceCert cold, once, offline\n * device key \u2500\u2500signs\u2500\u2500\u25B6 LoginStatement once per session, hours\n * session key \u2500\u2500signs\u2500\u2500\u25B6 RequestSig per call \u25C0\u2500\u2500 this module\n * ```\n *\n * **The middle link is optional.** A CLI or a server-side job signs with its\n * device key directly and presents two links; a browser keeping the device key\n * behind a hardware or extension boundary mints a session key and presents\n * three. The node's verifier handles both, so which you produce here is a\n * question about where your key lives, not about what the node accepts.\n *\n * **What it binds.** Method, path and a hash of the body. A signature for\n * `GET /a` is not a signature for `POST /a`, for `GET /b`, or for the same call\n * with different arguments.\n *\n * **The path excludes the query string**, deliberately: a proxy may legitimately\n * rewrite it \u2014 a token parameter most of all \u2014 and signing over bytes something\n * else is entitled to change means failing for reasons the caller cannot see.\n * Anything that must be bound belongs in the body.\n *\n * **The method is not normalized.** `HEAD` and `GET` are the same *permission*\n * and the node's permission layer folds them, but they are different requests.\n * Sign the method you are about to send.\n *\n * **Replay inside the window performs the identical request** and is bounded\n * rather than prevented \u2014 keep `expiresAt` short. A warrant, which authorizes a\n * change with lasting effect, carries a nonce ledger for this; a request\n * signature does not.\n *\n * **The byte contract is pinned in core**, at\n * `crates/account/src/tests/request_wire_fixture.rs`, for the same reason the\n * warrant and login ones are: the domains are `pub(crate)` there and the field\n * order is implicit in a `derive`, so nothing on that side forces anyone to\n * notice this file depends on both. This module's test asserts the same vectors.\n */\n\nimport {\n concat,\n domainHash,\n fromHex,\n hex,\n importSigningKey,\n u32le,\n u64le,\n} from \"../crypto/internal.js\";\n\nconst SIGN_DOMAIN = new TextEncoder().encode(\"calimero.auth.request.v1\");\n\n/**\n * Its own domain, distinct from the signing one.\n *\n * The two are different jobs on the same bytes: this produces the commitment,\n * the other signs over it. Sharing a domain would make the commitment a\n * truncated disclosure of bytes something signs.\n */\nconst BODY_DOMAIN = new TextEncoder().encode(\"calimero.auth.request.body.v1\");\n\n/** A borsh `String`: its own `u32` little-endian length, then the UTF-8. */\nfunction borshString(value: string): Uint8Array {\n const bytes = new TextEncoder().encode(value);\n return concat(u32le(bytes.length), bytes);\n}\n\n/**\n * Commit to a request body.\n *\n * Always call it, including for a body-less request: an empty body hashes to a\n * value like any other, and there is no \"absent body\" encoding for a verifier\n * to disagree with you about.\n */\nexport async function requestBodyHash(\n body: Uint8Array | string = new Uint8Array(0),\n): Promise<Uint8Array> {\n const bytes =\n typeof body === \"string\" ? new TextEncoder().encode(body) : body;\n return domainHash(BODY_DOMAIN, [bytes]);\n}\n\n/** What a caller is about to send. */\nexport interface RequestSigInput {\n /**\n * The HTTP method, exactly as it will appear on the wire \u2014 `GET`, `POST`,\n * `HEAD`. Not folded: see the module note.\n */\n method: string;\n /**\n * The path, **without** the query string. `/admin-api/namespaces`, not\n * `/admin-api/namespaces?token=\u2026`.\n */\n path: string;\n /**\n * The request body, or nothing. Pass exactly the bytes you will send: the\n * signature commits to their hash, so a body re-serialized between signing\n * and sending is a signature for a different request.\n */\n body?: Uint8Array | string;\n /** Unix seconds at signing. */\n issuedAt: number | bigint;\n /**\n * Unix seconds after which the node must refuse it.\n *\n * Keep it short \u2014 minutes, not hours. It is what bounds replay, and nothing\n * else does.\n */\n expiresAt: number | bigint;\n /**\n * The ed25519 signing secret, hex (32 bytes): the session key on the\n * three-link chain, the device key on the two-link one.\n *\n * Never sent anywhere. It signs locally and only the signature travels.\n */\n signerSecret: string;\n}\n\n/**\n * Sign one request and return it hex-encoded, ready to present.\n *\n * Returns the encoding rather than an object for the reason `signWarrant` and\n * `signLoginStatement` do: the signature covers exactly these bytes, and a\n * caller that rebuilt the fields from JSON would have a second spelling able to\n * disagree with what was signed.\n */\nexport async function signRequest(input: RequestSigInput): Promise<string> {\n const key = await importSigningKey(input.signerSecret);\n const bodyHash = await requestBodyHash(input.body);\n\n const issuedAt = u64le(input.issuedAt);\n const expiresAt = u64le(input.expiresAt);\n\n const encoder = new TextEncoder();\n const preimage = await domainHash(SIGN_DOMAIN, [\n encoder.encode(input.method),\n encoder.encode(input.path),\n bodyHash,\n issuedAt,\n expiresAt,\n ]);\n\n const signature = new Uint8Array(\n await crypto.subtle.sign({ name: \"Ed25519\" }, key, preimage),\n );\n\n return hex(\n concat(\n borshString(input.method),\n borshString(input.path),\n bodyHash,\n issuedAt,\n expiresAt,\n signature,\n ),\n );\n}\n\n/** Re-exported so a caller can check a hex signer key before signing with it. */\nexport { fromHex };\n", "/**\n * Assemble the credential a node reads from `X-Calimero-Proof`.\n *\n * A request-carried proof is three links, and the node needs all of them in one\n * header: the account root certified the device, the device (optionally)\n * certified a short-lived session key, and that key signed THIS request. Each\n * link is minted by its own function here \u2014 {@link signDeviceCert},\n * {@link signLoginStatement}, {@link signRequest} \u2014 and each already returns\n * the borsh encoding of the Rust type the node deserializes. Rust's\n * `CallerProof` is a plain struct of those three fields, and borsh writes a\n * struct as its fields back to back, so assembling the header is concatenation\n * rather than a fourth encoder.\n *\n * That is worth stating because it is load-bearing and it is not obvious: if it\n * were re-encoded here there would be a second spelling of a signed structure,\n * able to disagree with the one that was signed. `caller-proof.test.ts` pins the\n * concatenation against bytes produced by core itself.\n */\n\nimport { fromHexUnsized, hex, concat } from \"../crypto/internal.js\";\n\n/** The three links, each already hex-encoded by the function that signed it. */\nexport interface CallerProofInput {\n /**\n * `signDeviceCert(...)` \u2014 the account root's certificate over the device key,\n * with the genesis that names the account.\n */\n credential: string;\n /**\n * `signLoginStatement(...)`, when signing with a session key.\n *\n * Omit it on the two-link chain, where the device key signs the request\n * itself. Omitting is not the same as passing an empty string: borsh writes\n * `Option` as a tag byte, so the two produce different bytes and only one of\n * them parses.\n */\n session?: string;\n /** `signRequest(...)` \u2014 the signature over this exact method, path and body. */\n request: string;\n}\n\n/**\n * Concatenate the three links into the header value.\n *\n * Returns hex, ready to present as `X-Calimero-Proof`.\n */\nexport function callerProof(input: CallerProofInput): string {\n const credential = fromHexUnsized(input.credential, \"credential\");\n const request = fromHexUnsized(input.request, \"request signature\");\n\n // Borsh `Option<T>`: a single tag byte, then the value when present. A\n // two-link chain is `None`, not an empty `Some` \u2014 the node reads the tag\n // before anything else, so getting it backwards fails to parse rather than\n // failing to verify, which at least is loud.\n const session =\n input.session === undefined\n ? new Uint8Array([0])\n : concat(new Uint8Array([1]), fromHexUnsized(input.session, \"session statement\"));\n\n return hex(concat(credential, session, request));\n}\n", "/**\n * Turn a device key into something the HTTP client can call per request.\n *\n * A session is a bearer token: mint once, present many times. A request-carried\n * proof is the opposite \u2014 it commits to one method, one path and one body, so\n * it has to be produced at the moment of the call, by something holding the\n * signing key. This is that something, shaped like `getAuthToken` so the key\n * stays with the caller and never reaches the transport.\n */\n\nimport { callerProof } from \"./caller-proof.js\";\nimport { signRequest } from \"./request-sig.js\";\n\n/** What the transport knows about the call it is about to make. */\nexport interface ProofRequest {\n /** `GET`, `POST`, \u2026 exactly as it will be sent. */\n method: string;\n /** The URL path the node will see, **without** the query string. */\n path: string;\n /** The exact bytes that will be sent, if any. */\n body?: unknown;\n}\n\nexport interface ProofSignerOptions {\n /** `signDeviceCert(...)`, hex. */\n credential: string;\n /**\n * `signLoginStatement(...)`, hex \u2014 the three-link chain.\n *\n * Omit for the two-link chain, where `signerSecret` is the device key itself.\n */\n session?: string;\n /**\n * The ed25519 secret that signs each request, hex: the session key when\n * `session` is set, otherwise the device key.\n */\n signerSecret: string;\n /**\n * How long each signature stays valid. Seconds, default 120.\n *\n * This is what bounds replay and nothing else does, so it is deliberately\n * short and deliberately not configurable to \"never\". A clock skew allowance\n * lives on the node; this only has to outlive the flight time.\n */\n ttlSeconds?: number;\n /** Unix seconds. Injectable so a test does not depend on the wall clock. */\n now?: () => number;\n}\n\n/**\n * The bytes a body will actually be sent as.\n *\n * Refuses what it cannot hash without consuming or re-encoding. A stream can be\n * read once, and reading it here would send an empty body; `FormData` and\n * `URLSearchParams` are serialized by `fetch` in ways this cannot reproduce\n * byte for byte. Signing a guess would produce a signature over bytes that\n * never travelled, and the node would reject it with a signature error that\n * says nothing about the real cause \u2014 so this throws, naming the type.\n */\nfunction bodyBytes(body: unknown): Uint8Array | string {\n if (body === undefined || body === null) return new Uint8Array(0);\n if (typeof body === \"string\") return body;\n if (body instanceof Uint8Array) return body;\n if (body instanceof ArrayBuffer) return new Uint8Array(body);\n if (ArrayBuffer.isView(body)) {\n return new Uint8Array(body.buffer, body.byteOffset, body.byteLength);\n }\n throw new Error(\n `a request-carried proof cannot commit to a ${\n (body as { constructor?: { name?: string } })?.constructor?.name ?? typeof body\n } body: pass a string or bytes, or use a session for this call`,\n );\n}\n\n/**\n * Build the `X-Calimero-Proof` value for one request.\n *\n * Pass the result to the HTTP client's `getProof`.\n */\nexport function createProofSigner(\n options: ProofSignerOptions,\n): (request: ProofRequest) => Promise<string> {\n const ttl = options.ttlSeconds ?? 120;\n const clock = options.now ?? (() => Math.floor(Date.now() / 1000));\n\n return async (request: ProofRequest): Promise<string> => {\n const issuedAt = clock();\n return callerProof({\n credential: options.credential,\n session: options.session,\n request: await signRequest({\n method: request.method,\n path: request.path,\n body: bodyBytes(request.body),\n issuedAt,\n expiresAt: issuedAt + ttl,\n signerSecret: options.signerSecret,\n }),\n });\n };\n}\n", "import type { HttpClient } from '../http-client/index.js';\n\n/** Result of the owner-driven `migrate_my_entries` convert (counts are u32). */\nexport interface MigrateMyEntriesSummary {\n converted: number;\n remaining: number;\n}\n\nexport interface ExecuteParams {\n contextId: string;\n method: string;\n argsJson?: Record<string, unknown>;\n /** @deprecated No longer used by the server. Ignored if provided. */\n executorPublicKey?: string;\n}\n\nexport class RpcError extends Error {\n code: number;\n type?: string;\n data?: unknown;\n\n constructor(code: number, message: string, data?: unknown, type?: string) {\n super(message);\n this.name = 'RpcError';\n this.code = code;\n this.data = data;\n this.type = type;\n }\n}\n\ninterface JsonRpcResponse {\n jsonrpc: string;\n id: number;\n result?: {\n output?: unknown;\n [key: string]: unknown;\n };\n error?: {\n // Standard JSON-RPC fields\n code?: number;\n message?: string;\n // Server-specific fields\n type?: string;\n data?: unknown;\n };\n}\n\n/**\n * POST a JSON-RPC call and unwrap `result`, converting an `error` payload into\n * an `RpcError`. The node returns `{ type, data }` (not always `{ code,\n * message }`), so this is the one place that normalization lives \u2014 every\n * JSON-RPC caller in this SDK shares it rather than re-deriving a bare\n * `Error(message)` and dropping the typed detail.\n */\nexport async function jsonRpcCall<T>(\n httpClient: HttpClient,\n method: string,\n params: unknown,\n): Promise<T> {\n const response = await httpClient.post<JsonRpcResponse>('/jsonrpc', {\n jsonrpc: '2.0',\n id: 1,\n method,\n params,\n });\n\n if (response.error) {\n const err = response.error;\n throw new RpcError(err.code ?? -1, err.message ?? err.type ?? 'RPC error', err.data, err.type);\n }\n\n return response.result as T;\n}\n\nexport class RpcClient {\n private httpClient: HttpClient;\n\n constructor(opts: { httpClient: HttpClient }) {\n this.httpClient = opts.httpClient;\n }\n\n async execute<T = unknown>(params: ExecuteParams): Promise<T> {\n const result = await jsonRpcCall<JsonRpcResponse['result']>(this.httpClient, 'execute', {\n contextId: params.contextId,\n method: params.method,\n argsJson: params.argsJson ?? {},\n });\n\n if (result && 'output' in result) {\n return result.output as T;\n }\n\n return result as T;\n }\n\n /**\n * One-tap owner-driven convert: re-signs the caller's identity-gated entries\n * to the current schema. The export converts all of the caller's\n * below-target entries in a single sweep, so this issues one call and returns\n * the resulting summary \u2014 it does not loop.\n */\n async migrateMyEntries(contextId: string): Promise<MigrateMyEntriesSummary> {\n return this.execute<MigrateMyEntriesSummary>({ contextId, method: 'migrate_my_entries' });\n }\n\n /** Read-only count of the caller's entries still below the target schema. */\n async countMyPending(contextId: string): Promise<number> {\n return this.execute<number>({ contextId, method: 'count_my_pending' });\n }\n}\n", "/** Parsed group-membership event (core's untagged `NodeEvent` group variant). */\nexport interface GroupMembershipEventData {\n groupId: string;\n type: 'MemberJoined' | 'MemberAdded' | 'MemberRemoved';\n data: {\n /** The member's ACCOUNT, hex-encoded 32 bytes (64 chars) - the principal the\n * governance rows name. Not a signing key: the field this replaced\n * carried one, and both are strings, so the rename is what makes a stale\n * consumer fail loudly instead of comparing accounts against keys forever. */\n memberAccount: string;\n /** Absent on `MemberRemoved`, and on a `MemberJoined` inherited from an Open\n * subgroup. Core's set is closed. */\n role?: 'Admin' | 'Member' | 'ReadOnly' | 'ReadOnlyTee';\n };\n}\n\n/**\n * A migration was accepted and its target ABI state version resolved.\n * `toStateVersion` is the `#[app::state(version = N)]` number, not the bundle\n * semver (`toVersion`).\n */\nexport interface MigrationStartedData {\n fromVersion: string;\n toVersion: string;\n toStateVersion: number;\n /** Contexts the EMITTING NODE enumerated for the migration. Node-local, never\n * the fleet cohort size that `MigrationProgressData.total` carries. */\n localContextsTotal: number;\n}\n\n/**\n * Fleet rollup counters, recomputed as peers' heartbeat facts change. Counters\n * only: the per-member detail behind them stays in the admin-gated\n * `getMigrationStatus` read.\n */\nexport interface MigrationProgressData {\n migrated: number;\n inProgress: number;\n unknown: number;\n failed: number;\n /** The pinned FLEET COHORT size, never a node-local context count like\n * `MigrationStartedData.localContextsTotal`. */\n total: number;\n}\n\n/**\n * One subgroup's local context swaps advanced on the emitting node.\n *\n * Admin-only: it names a descendant subgroup, which a plain namespace member\n * may not enumerate, so core withholds it from non-admin subscribers. A\n * non-admin never receiving this variant is correct behaviour, not a gap.\n */\nexport interface CascadeProgressData {\n /** Hex-encoded descendant subgroup id, never the namespace root the event is\n * keyed on. */\n subgroupId: string;\n localContextsSwapped: number;\n /** Contexts the emitting node holds for THIS SUBGROUP. Node-local and\n * per-subgroup; unrelated to both sibling totals. */\n localContextsTotal: number;\n}\n\n/** Every pinned-cohort member reported the target version. */\nexport interface MigrationCompletedData {\n toVersion: string;\n /** FLEET convergence, in seconds since the epoch: the same stamp\n * `MigrationStatus.fleetCompletedAt` returns. Not `GroupUpgradeStatus.completedAt`,\n * which is this node's own local swap completion. */\n completedAt: number;\n}\n\n/**\n * Parsed group-migration event (core's `GroupMigration` `NodeEvent` variant),\n * keyed on the namespace root a client subscribes with.\n *\n * `MigrationStarted`, `MigrationProgress` and `MigrationCompleted` reach every\n * subscribed member; `CascadeProgress` reaches namespace admins only.\n */\nexport type GroupMigrationEventData =\n | { groupId: string; type: 'MigrationStarted'; data: MigrationStartedData }\n | { groupId: string; type: 'MigrationProgress'; data: MigrationProgressData }\n | { groupId: string; type: 'CascadeProgress'; data: CascadeProgressData }\n | { groupId: string; type: 'MigrationCompleted'; data: MigrationCompletedData };\n\nconst MIGRATION_EVENT_TYPES: ReadonlySet<string> = new Set([\n 'MigrationStarted',\n 'MigrationProgress',\n 'CascadeProgress',\n 'MigrationCompleted',\n]);\n\n/** Narrows a raw `'event'` payload to the group-migration family by its tag. */\nexport function isGroupMigrationEvent(\n event: { type?: string },\n): event is GroupMigrationEventData {\n return event.type !== undefined && MIGRATION_EVENT_TYPES.has(event.type);\n}\n", "import type { GroupMembershipEventData, GroupMigrationEventData } from './group.js';\nimport { isGroupMigrationEvent } from './group.js';\nimport { AuthRevokedError, HTTPError } from '../http-client/index.js';\n\n/**\n * `x-auth-error` reasons that mean the whole token family is gone, mirroring\n * the request path in `web-client.ts`. Anything else \u2014 `token_expired` above\n * all \u2014 is recoverable by refreshing and reconnecting.\n */\nconst TERMINAL_AUTH_ERRORS = new Set(['token_reuse', 'token_revoked']);\n\n/**\n * Statuses a terminal `x-auth-error` can arrive on.\n *\n * Measured against merod 0.11.0-rc.41: a revoked token family answers\n * `GET /sse` and `POST /sse/subscription` with **403**, `x-auth-error:\n * token_revoked` and `content-length: 0` \u2014 `token_expired` is the 401.\n * Keying the check on 401 alone made the branch below unreachable for the one\n * reason a stream actually sees, so a dead token family surfaced as a bare\n * `HTTPError(403)` and reconnected on a timer forever.\n */\nconst TERMINAL_AUTH_STATUSES = new Set([401, 403]);\n\n/**\n * Whether a failed response means the whole token family is gone, so the caller\n * must re-authenticate and the client must stop reconnecting.\n *\n * The body carries nothing on this path (`content-length: 0`), so the header is\n * the only evidence there is.\n */\nfunction isTerminalAuthFailure(response: { status: number; headers: Headers }): string | null {\n const authError = response.headers.get('x-auth-error');\n if (!authError) return null;\n if (!TERMINAL_AUTH_STATUSES.has(response.status)) return null;\n return TERMINAL_AUTH_ERRORS.has(authError) ? authError : null;\n}\n\nexport type { GroupMembershipEventData, GroupMigrationEventData };\n\nexport interface SseEventData {\n contextId: string;\n /** The context-event discriminator (core serializes it as a sibling of\n * `data`, e.g. `\"AppVersionChanged\"`). Undefined for events without a tag. */\n type?: string;\n data: unknown;\n}\n\n/** Parsed `AppVersionChanged` context event (bundle-version flip; skew #2). */\nexport interface AppVersionChangedEvent {\n contextId: string;\n fromVersion?: string;\n toVersion?: string;\n}\n\n/** Anything the `'event'` stream can deliver: context events plus the\n * group-keyed membership and migration families. */\ntype SseEvent = SseEventData | GroupMembershipEventData | GroupMigrationEventData;\ntype SseEventHandler = (event: SseEvent) => void;\ntype SseConnectHandler = (sessionId: string) => void;\ntype SseErrorHandler = (error: Error) => void;\n\ninterface SseListeners {\n connect: SseConnectHandler[];\n event: SseEventHandler[];\n error: SseErrorHandler[];\n}\n\nexport class SseClient {\n private baseUrl: string;\n private getAuthToken: () => Promise<string>;\n private reconnectDelayMs: number;\n private sessionId: string | null = null;\n private abortController: AbortController | null = null;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private subscribedContextIds: Set<string> = new Set();\n private subscribedGroupIds: Set<string> = new Set();\n private closed = false;\n private listeners: SseListeners = { connect: [], event: [], error: [] };\n private fetchImpl: typeof fetch;\n\n constructor(opts: {\n baseUrl: string;\n getAuthToken: () => Promise<string>;\n reconnectDelayMs?: number;\n fetch?: typeof fetch;\n }) {\n this.baseUrl = opts.baseUrl.replace(/\\/+$/, '');\n this.getAuthToken = opts.getAuthToken;\n this.reconnectDelayMs = opts.reconnectDelayMs ?? 3000;\n // globalThis.fetch must be called as a method on globalThis, not through a\n // variable, or browsers throw \"Illegal invocation\".\n this.fetchImpl = (url: RequestInfo | URL, init?: RequestInit) =>\n opts.fetch ? opts.fetch(url, init) : globalThis.fetch(url, init);\n }\n\n on(event: 'connect', handler: SseConnectHandler): void;\n on(event: 'event', handler: SseEventHandler): void;\n on(event: 'error', handler: SseErrorHandler): void;\n on(event: string, handler: SseConnectHandler | SseEventHandler | SseErrorHandler): void {\n const key = event as keyof SseListeners;\n if (key in this.listeners) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const arr = this.listeners[key] as any[];\n if (!arr.includes(handler)) arr.push(handler);\n }\n }\n\n off(event: 'connect', handler: SseConnectHandler): void;\n off(event: 'event', handler: SseEventHandler): void;\n off(event: 'error', handler: SseErrorHandler): void;\n off(event: string, handler: SseConnectHandler | SseEventHandler | SseErrorHandler): void {\n const key = event as keyof SseListeners;\n if (key in this.listeners) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const arr = this.listeners[key] as any[];\n const idx = arr.indexOf(handler);\n if (idx !== -1) arr.splice(idx, 1);\n }\n }\n\n /**\n * Typed convenience over the generic `'event'` stream: invokes `handler` only\n * for `AppVersionChanged` context events, with the payload already parsed.\n * Returns an unsubscribe closure (so callers need not retain the listener).\n */\n onAppVersionChanged(handler: (e: AppVersionChangedEvent) => void): () => void {\n const listener: SseEventHandler = (ev) => {\n // Core flattens the tagged payload: the event carries `type` as a sibling\n // of `data`, and `data` is the `{ fromVersion?, toVersion? }` payload.\n if (ev.type !== 'AppVersionChanged') return;\n const d = ev.data as { fromVersion?: string; toVersion?: string } | undefined;\n handler({ contextId: ev.contextId, fromVersion: d?.fromVersion, toVersion: d?.toVersion });\n };\n this.on('event', listener);\n return () => this.off('event', listener);\n }\n\n /**\n * Typed convenience over the generic `'event'` stream: invokes `handler` only\n * for group-migration events, narrowed on `type` so `data` is the matching\n * payload. Returns an unsubscribe closure.\n *\n * A non-admin subscriber sees `MigrationStarted`, `MigrationProgress` and\n * `MigrationCompleted` but never `CascadeProgress`, which core delivers to\n * namespace admins only.\n */\n onMigrationEvent(handler: (e: GroupMigrationEventData) => void): () => void {\n const listener: SseEventHandler = (ev) => {\n if (isGroupMigrationEvent(ev)) handler(ev);\n };\n this.on('event', listener);\n return () => this.off('event', listener);\n }\n\n private emit(event: 'connect', sessionId: string): void;\n private emit(event: 'event', data: SseEvent): void;\n private emit(event: 'error', error: Error): void;\n private emit(event: string, arg?: string | SseEvent | Error): void {\n const key = event as keyof SseListeners;\n if (key in this.listeners) {\n for (const handler of this.listeners[key]) {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (handler as any)(arg);\n } catch {\n // Swallow handler errors\n }\n }\n }\n }\n\n async connect(): Promise<void> {\n // Already connected \u2014 don't reconnect\n if (this.abortController && !this.closed) {\n return;\n }\n this.closed = false;\n this.abortController = new AbortController();\n\n try {\n const token = await this.getAuthToken();\n const url = `${this.baseUrl}/sse`;\n const response = await this.fetchImpl(url, {\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Accept': 'text/event-stream',\n },\n signal: this.abortController.signal,\n });\n\n if (!response.ok) {\n // Throw the same typed errors the request path throws. A bare\n // `Error(\"SSE connection failed: 401\")` forced callers to string-match\n // the status and guess what it meant, and the honest guess \u2014 treat any\n // 401 as fatal \u2014 logs the user out on a routine reconnect after the\n // access token ages out, discarding a refresh token that was still\n // good. The distinction is in the header; surface it.\n const terminal = isTerminalAuthFailure(response);\n const bodyText = await response.text().catch(() => undefined);\n\n if (terminal) {\n throw new AuthRevokedError(\n terminal,\n response.status,\n response.statusText,\n url,\n response.headers,\n bodyText,\n );\n }\n\n throw new HTTPError(\n response.status,\n response.statusText,\n url,\n response.headers,\n bodyText,\n );\n }\n\n if (!response.body) {\n throw new Error('SSE response has no body');\n }\n\n this.readStream(response.body).catch((err) => {\n if (this.closed) return;\n const error = err instanceof Error ? err : new Error(String(err));\n this.emit('error', error);\n this.scheduleReconnect();\n });\n } catch (err) {\n if (this.closed) return;\n const error = err instanceof Error ? err : new Error(String(err));\n this.emit('error', error);\n // Reconnecting against a revoked token family is a timer that never\n // succeeds: every attempt re-sends the same dead credential. The caller\n // has to re-authenticate, and an AuthRevokedError is how it is told.\n if (error instanceof AuthRevokedError) return;\n this.scheduleReconnect();\n }\n }\n\n private async readStream(body: ReadableStream<Uint8Array>): Promise<void> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n // Keep the last partial line in the buffer\n buffer = lines.pop() ?? '';\n\n for (const line of lines) {\n if (line.startsWith('data:')) {\n const jsonStr = line.slice(5).trim();\n if (jsonStr) {\n this.handleMessage(jsonStr);\n }\n }\n }\n }\n // Flush remaining bytes from the decoder\n buffer += decoder.decode();\n if (buffer.startsWith('data:')) {\n const jsonStr = buffer.slice(5).trim();\n if (jsonStr) {\n this.handleMessage(jsonStr);\n }\n }\n } catch (err) {\n if (this.closed) return;\n const error = err instanceof Error ? err : new Error(String(err));\n this.emit('error', error);\n }\n\n // Stream ended, reconnect\n if (!this.closed) {\n this.scheduleReconnect();\n }\n }\n\n private handleMessage(jsonStr: string): void {\n try {\n const msg = JSON.parse(jsonStr);\n\n // Connection message\n if (msg.type === 'connect' && msg.session_id) {\n this.sessionId = msg.session_id;\n this.emit('connect', msg.session_id);\n // Re-subscribe after reconnect\n if (this.subscribedContextIds.size > 0 || this.subscribedGroupIds.size > 0) {\n this.sendSubscription('subscribe', {\n contextIds: [...this.subscribedContextIds],\n groupIds: [...this.subscribedGroupIds],\n });\n }\n return;\n }\n\n if (!msg.result) return;\n\n let eventData = msg.result.data;\n // Decode byte-array data if needed\n if (Array.isArray(eventData)) {\n try {\n const bytes = new Uint8Array(eventData);\n const text = new TextDecoder().decode(bytes);\n eventData = JSON.parse(text);\n } catch {\n // Keep raw data\n }\n }\n\n // Context event message\n if (msg.result.contextId) {\n this.emit('event', {\n contextId: msg.result.contextId,\n // Forward the event tag (sibling of `data` in core's flattened\n // payload) so typed helpers like `onAppVersionChanged` can discriminate.\n type: msg.result.type,\n data: eventData,\n });\n return;\n }\n\n // Group-keyed event message (untagged NodeEvent's membership and\n // migration variants; the `type` tag tells them apart).\n if (msg.result.groupId) {\n this.emit('event', {\n groupId: msg.result.groupId,\n type: msg.result.type,\n data: eventData,\n });\n }\n } catch {\n // Invalid JSON, ignore\n }\n }\n\n async subscribe(idsOrOpts: string[] | { contextIds?: string[]; groupIds?: string[] }): Promise<void> {\n const opts = Array.isArray(idsOrOpts) ? { contextIds: idsOrOpts } : idsOrOpts;\n const newContextIds = (opts.contextIds ?? []).filter(id => !this.subscribedContextIds.has(id));\n const newGroupIds = (opts.groupIds ?? []).filter(id => !this.subscribedGroupIds.has(id));\n for (const id of opts.contextIds ?? []) {\n this.subscribedContextIds.add(id);\n }\n for (const id of opts.groupIds ?? []) {\n this.subscribedGroupIds.add(id);\n }\n if ((newContextIds.length > 0 || newGroupIds.length > 0) && this.sessionId) {\n await this.sendSubscription('subscribe', { contextIds: newContextIds, groupIds: newGroupIds });\n }\n }\n\n async unsubscribe(idsOrOpts: string[] | { contextIds?: string[]; groupIds?: string[] }): Promise<void> {\n const opts = Array.isArray(idsOrOpts) ? { contextIds: idsOrOpts } : idsOrOpts;\n const hadContextIds = (opts.contextIds ?? []).filter(id => this.subscribedContextIds.has(id));\n const hadGroupIds = (opts.groupIds ?? []).filter(id => this.subscribedGroupIds.has(id));\n for (const id of opts.contextIds ?? []) {\n this.subscribedContextIds.delete(id);\n }\n for (const id of opts.groupIds ?? []) {\n this.subscribedGroupIds.delete(id);\n }\n if ((hadContextIds.length > 0 || hadGroupIds.length > 0) && this.sessionId) {\n await this.sendSubscription('unsubscribe', { contextIds: hadContextIds, groupIds: hadGroupIds });\n }\n }\n\n private async sendSubscription(\n method: 'subscribe' | 'unsubscribe',\n ids: { contextIds?: string[]; groupIds?: string[] },\n ): Promise<void> {\n try {\n const token = await this.getAuthToken();\n const params: { contextIds?: string[]; groupIds?: string[] } = {};\n if (ids.contextIds && ids.contextIds.length > 0) params.contextIds = ids.contextIds;\n if (ids.groupIds && ids.groupIds.length > 0) params.groupIds = ids.groupIds;\n const response = await this.fetchImpl(`${this.baseUrl}/sse/subscription`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n id: this.sessionId,\n method,\n params,\n }),\n });\n if (!response.ok) {\n // The subscribe path used to report only the status. On the one failure\n // that matters it is the whole of what the caller got: a revoked family\n // answers 403 with an empty body, so a bare `SSE subscribe failed: 403`\n // discarded the only evidence of what went wrong \u2014 and a 403 here reads\n // identically to the unrelated \"not the session owner\" refusal, which\n // does carry a body.\n const terminal = isTerminalAuthFailure(response);\n const bodyText = await response.text().catch(() => undefined);\n this.emit(\n 'error',\n terminal\n ? new AuthRevokedError(\n terminal,\n response.status,\n response.statusText,\n `${this.baseUrl}/sse/subscription`,\n response.headers,\n bodyText,\n )\n : new HTTPError(\n response.status,\n response.statusText,\n `${this.baseUrl}/sse/subscription`,\n response.headers,\n bodyText,\n ),\n );\n }\n } catch (err) {\n this.emit('error', err instanceof Error ? err : new Error(`SSE ${method} failed`));\n }\n }\n\n private forceReconnect(): void {\n if (this.abortController) {\n this.abortController.abort();\n this.abortController = null;\n }\n this.sessionId = null;\n this.connect();\n }\n\n private scheduleReconnect(): void {\n if (this.closed) return;\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n }\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n this.forceReconnect();\n }, this.reconnectDelayMs);\n }\n\n close(): void {\n this.closed = true;\n if (this.abortController) {\n this.abortController.abort();\n this.abortController = null;\n }\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n this.sessionId = null;\n this.subscribedContextIds.clear();\n this.subscribedGroupIds.clear();\n }\n}\n", "import type { GroupMembershipEventData, GroupMigrationEventData } from './group.js';\n\nexport type { GroupMembershipEventData, GroupMigrationEventData };\n\nexport interface WsEventData {\n contextId: string;\n /** The context-event discriminator (core serializes it as a sibling of\n * `data`, e.g. `\"AppVersionChanged\"`). Undefined for events without a tag. */\n type?: string;\n data: unknown;\n}\n\n/** Anything the `'event'` stream can deliver: context events plus the\n * group-keyed membership and migration families. */\ntype WsEvent = WsEventData | GroupMembershipEventData | GroupMigrationEventData;\ntype WsEventHandler = (event: WsEvent) => void;\ntype WsConnectHandler = () => void;\ntype WsErrorHandler = (error: Error) => void;\n\ninterface WsListeners {\n connect: WsConnectHandler[];\n event: WsEventHandler[];\n error: WsErrorHandler[];\n}\n\n/**\n * @experimental WebSocket event client. The SSE client (`SseClient`) is the\n * recommended transport for production \u2014 use `MeroJs.events` instead of `MeroJs.ws`.\n */\nexport class WsClient {\n private baseUrl: string;\n private getAuthToken: () => Promise<string>;\n private ws: WebSocket | null = null;\n private closed = false;\n private reconnectAttempt = 0;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private subscribedContextIds: Set<string> = new Set();\n private subscribedGroupIds: Set<string> = new Set();\n private listeners: WsListeners = { connect: [], event: [], error: [] };\n\n private static readonly MAX_BACKOFF_MS = 30000;\n\n constructor(opts: {\n baseUrl: string;\n getAuthToken: () => Promise<string>;\n }) {\n this.baseUrl = opts.baseUrl.replace(/\\/+$/, '');\n this.getAuthToken = opts.getAuthToken;\n }\n\n on(event: 'connect', handler: WsConnectHandler): void;\n on(event: 'event', handler: WsEventHandler): void;\n on(event: 'error', handler: WsErrorHandler): void;\n on(event: string, handler: WsConnectHandler | WsEventHandler | WsErrorHandler): void {\n const key = event as keyof WsListeners;\n if (key in this.listeners) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const arr = this.listeners[key] as any[];\n if (!arr.includes(handler)) arr.push(handler);\n }\n }\n\n off(event: 'connect', handler: WsConnectHandler): void;\n off(event: 'event', handler: WsEventHandler): void;\n off(event: 'error', handler: WsErrorHandler): void;\n off(event: string, handler: WsConnectHandler | WsEventHandler | WsErrorHandler): void {\n const key = event as keyof WsListeners;\n if (key in this.listeners) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const arr = this.listeners[key] as any[];\n const idx = arr.indexOf(handler);\n if (idx !== -1) arr.splice(idx, 1);\n }\n }\n\n private emit(event: 'connect'): void;\n private emit(event: 'event', data: WsEvent): void;\n private emit(event: 'error', error: Error): void;\n private emit(event: string, arg?: WsEvent | Error): void {\n const key = event as keyof WsListeners;\n if (key in this.listeners) {\n for (const handler of this.listeners[key]) {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (handler as any)(arg);\n } catch {\n // Swallow handler errors\n }\n }\n }\n }\n\n async connect(): Promise<void> {\n if (this.ws && (this.ws.readyState === 0 /* CONNECTING */ || this.ws.readyState === 1 /* OPEN */)) {\n return;\n }\n this.closed = false;\n\n try {\n const token = await this.getAuthToken();\n if (!token) {\n throw new Error('No authentication token available for WebSocket connection');\n }\n // Browser WebSocket API can't set headers, use query param\n const wsUrl = this.baseUrl.replace(/^http/, 'ws');\n this.ws = new WebSocket(`${wsUrl}/ws?token=${encodeURIComponent(token)}`);\n\n this.ws.onopen = () => {\n this.reconnectAttempt = 0;\n this.emit('connect');\n this.resubscribeAfterReconnect();\n };\n\n this.ws.onmessage = (event) => {\n this.handleMessage(event.data);\n };\n\n this.ws.onerror = () => {\n this.emit('error', new Error('WebSocket error'));\n };\n\n this.ws.onclose = () => {\n if (!this.closed) {\n this.scheduleReconnect();\n }\n };\n } catch (err) {\n if (this.closed) return;\n const error = err instanceof Error ? err : new Error(String(err));\n this.emit('error', error);\n this.scheduleReconnect();\n }\n }\n\n private handleMessage(raw: string | ArrayBuffer | Blob): void {\n if (typeof raw !== 'string') return;\n\n try {\n const msg = JSON.parse(raw);\n\n if (!msg.result) return;\n\n let eventData = msg.result.data;\n if (Array.isArray(eventData)) {\n try {\n const bytes = new Uint8Array(eventData);\n const text = new TextDecoder().decode(bytes);\n eventData = JSON.parse(text);\n } catch {\n // Keep raw data\n }\n }\n\n if (msg.result.contextId) {\n this.emit('event', {\n contextId: msg.result.contextId,\n // Forward the event tag (sibling of `data` in core's flattened\n // payload) so consumers can discriminate, matching `SseClient`.\n type: msg.result.type,\n data: eventData,\n });\n return;\n }\n\n // Group-keyed event message (untagged NodeEvent's membership and\n // migration variants; the `type` tag tells them apart).\n if (msg.result.groupId) {\n this.emit('event', {\n groupId: msg.result.groupId,\n type: msg.result.type,\n data: eventData,\n });\n }\n } catch {\n // Invalid JSON, ignore\n }\n }\n\n subscribe(idsOrOpts: string[] | { contextIds?: string[]; groupIds?: string[] }): void {\n const opts = Array.isArray(idsOrOpts) ? { contextIds: idsOrOpts } : idsOrOpts;\n for (const id of opts.contextIds ?? []) {\n this.subscribedContextIds.add(id);\n }\n for (const id of opts.groupIds ?? []) {\n this.subscribedGroupIds.add(id);\n }\n\n if (this.ws && this.ws.readyState === 1 /* OPEN */) {\n this.sendMessage({\n id: null,\n method: 'subscribe',\n params: this.buildSubscriptionParams(opts.contextIds ?? [], opts.groupIds ?? []),\n });\n }\n }\n\n unsubscribe(idsOrOpts: string[] | { contextIds?: string[]; groupIds?: string[] }): void {\n const opts = Array.isArray(idsOrOpts) ? { contextIds: idsOrOpts } : idsOrOpts;\n for (const id of opts.contextIds ?? []) {\n this.subscribedContextIds.delete(id);\n }\n for (const id of opts.groupIds ?? []) {\n this.subscribedGroupIds.delete(id);\n }\n\n if (this.ws && this.ws.readyState === 1 /* OPEN */) {\n this.sendMessage({\n id: null,\n method: 'unsubscribe',\n params: this.buildSubscriptionParams(opts.contextIds ?? [], opts.groupIds ?? []),\n });\n }\n }\n\n private resubscribeAfterReconnect(): void {\n if (this.subscribedContextIds.size === 0 && this.subscribedGroupIds.size === 0) return;\n this.sendMessage({\n id: null,\n method: 'subscribe',\n params: this.buildSubscriptionParams([...this.subscribedContextIds], [...this.subscribedGroupIds]),\n });\n }\n\n private buildSubscriptionParams(\n contextIds: string[],\n groupIds: string[],\n ): { contextIds?: string[]; groupIds?: string[] } {\n const params: { contextIds?: string[]; groupIds?: string[] } = {};\n if (contextIds.length > 0) params.contextIds = contextIds;\n if (groupIds.length > 0) params.groupIds = groupIds;\n return params;\n }\n\n private sendMessage(msg: unknown): void {\n if (this.ws && this.ws.readyState === 1 /* OPEN */) {\n this.ws.send(JSON.stringify(msg));\n }\n }\n\n private scheduleReconnect(): void {\n if (this.closed) return;\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n }\n const delay = Math.min(\n 1000 * Math.pow(2, this.reconnectAttempt),\n WsClient.MAX_BACKOFF_MS,\n );\n this.reconnectAttempt++;\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n this.connect();\n }, delay);\n }\n\n close(): void {\n this.closed = true;\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n if (this.ws) {\n this.ws.onclose = null;\n this.ws.close();\n this.ws = null;\n }\n this.subscribedContextIds.clear();\n this.subscribedGroupIds.clear();\n }\n}\n", "import type { Codec } from './types.js';\n\n/** Default codec: JSON text as a byte array. The wire carries `state` as\n * `number[]`, not a string, so this encodes to bytes rather than to text. */\nexport function jsonCodec<T>(): Codec<T> {\n return {\n encode(value: T): number[] {\n return Array.from(new TextEncoder().encode(JSON.stringify(value)));\n },\n decode(bytes: number[]): T {\n return JSON.parse(new TextDecoder().decode(new Uint8Array(bytes))) as T;\n },\n };\n}\n", "import type { HttpClient } from '../http-client/index.js';\nimport type { SseClient } from '../events/index.js';\nimport { jsonRpcCall } from '../rpc/index.js';\nimport { jsonCodec } from './codec.js';\nimport type { Codec, EphemeralEntry } from './types.js';\n\nexport { jsonCodec };\nexport type { Codec, EphemeralEntry };\n\n/**\n * Ephemeral presence: transient, encrypted, never-persisted state (cursors,\n * typing, presence) that gossips between nodes without a WASM run or DAG\n * growth.\n *\n * The write/read asymmetry is the model, not an oversight. `set` takes no\n * author: you can only ever write your OWN slot, and the node resolves the\n * author server-side from its owned context identity \u2014 which is also why a\n * client cannot publish as somebody else. `subscribe` returns everyone's\n * slots, so each carries its `author`. The store is N independent\n * single-writer registers keyed by author; there is no merge across authors.\n *\n * There is a single read path: `subscribe`. On subscribing to a context, the\n * node replays that context's current presence to this connection as\n * ordinary `Ephemeral` events (carrying `ageMs`), before any live deltas\n * (which carry no `ageMs`). There is no separate snapshot RPC.\n */\nexport class EphemeralClient {\n private httpClient: HttpClient;\n private sse: SseClient;\n\n constructor(opts: { httpClient: HttpClient; sse: SseClient }) {\n this.httpClient = opts.httpClient;\n this.sse = opts.sse;\n }\n\n /**\n * Publish your own presence slice, replacing it wholesale.\n *\n * Rejects with an `RpcError` (not a bare `Error`), so a typed failure such as\n * an oversized slice keeps its `type` and `data` \u2014 e.g. `SliceTooLarge` with\n * the offending size \u2014 and is `instanceof RpcError` like every other RPC in\n * this SDK.\n */\n async set<T>(contextId: string, state: T, codec: Codec<T> = jsonCodec<T>()): Promise<void> {\n await jsonRpcCall<Record<string, never>>(this.httpClient, 'set_ephemeral', {\n contextId,\n state: codec.encode(state),\n });\n }\n\n /**\n * Observe presence changes for a context.\n *\n * This adds NO transport \u2014 it is a typed filter over the existing SSE event\n * stream, which already carries `{ contextId, type, data }`. On subscribing,\n * the node replays that context's current presence to this connection as\n * ordinary `Ephemeral` events before any live deltas; a replayed entry\n * carries `ageMs`, a live delta does not (never synthesized as `0` \u2014 absent\n * and zero mean different things). Returns an unsubscribe function.\n */\n subscribe<T>(\n contextId: string,\n handler: (entry: EphemeralEntry<T>) => void,\n codec: Codec<T> = jsonCodec<T>(),\n ): () => void {\n const listener = (event: unknown): void => {\n const e = event as { contextId?: string; type?: string; data?: unknown };\n if (e.type !== 'Ephemeral' || e.contextId !== contextId) return;\n\n const data = e.data as\n | { author?: string; state?: number[]; removed?: boolean; ageMs?: number }\n | undefined;\n if (!data?.author) return;\n\n // `removed` is omitted on an upsert, so normalize to a boolean here and\n // spare every caller the truthiness rule.\n const removed = Boolean(data.removed);\n const entry: EphemeralEntry<T> = {\n author: data.author,\n state: removed || data.state === undefined ? undefined : codec.decode(data.state),\n removed,\n };\n // `ageMs` is present only on a replayed seed entry; pass it through as-is\n // rather than defaulting a live delta to 0.\n if (data.ageMs !== undefined) entry.ageMs = data.ageMs;\n handler(entry);\n };\n\n this.sse.on('event', listener);\n // Errors surface via the SSE client's own 'error' event; nothing more to\n // do with them here.\n void this.sse.connect().catch(() => undefined);\n void this.sse.subscribe([contextId]).catch(() => undefined);\n\n return () => {\n this.sse.off('event', listener);\n };\n }\n}\n", "/**\n * Minting and certifying a device offline, with a key that never reaches a node.\n *\n * This is the second half of the keyholder story. `signWarrant` lets an account\n * with no node *write*; this lets one exist in the first place \u2014 an account root\n * certifies a device, and the resulting credential is what a joiner presents.\n *\n * As with the warrant signer, the risk is that this reproduces core's encoding\n * and has to keep doing so. The answer is the same: a golden vector here, and an\n * e2e that diffs this output against `merod account sign-cert` byte for byte.\n *\n * Everything below is derived from core:\n *\n * - `DeviceCert::signing_payload` \u2014 `crates/account/src/device.rs`\n * - `DeviceId::mint` \u2014 `crates/primitives/src/identity.rs`\n * - `AccountProof` / `AccountGenesis` borsh layout \u2014 `crates/account/src/{signed,account}.rs`\n */\nimport {\n concat,\n derivePublicKey,\n domainHash,\n fromHex,\n hex,\n importSigningKey,\n u32le,\n} from '../crypto/internal.js';\n\nconst CERT_DOMAIN = new TextEncoder().encode('calimero.device.cert.v1');\nconst ACCOUNT_ID_DOMAIN = new TextEncoder().encode('calimero.account.genesis.v1');\nconst DEVICE_ID_DOMAIN = new TextEncoder().encode('calimero.device.id.v1');\n\n/** `AccountGenesis::version`, which the credential's borsh encoding leads with. */\n/**\n * Core's `ACCOUNT_GENESIS_VERSION`. It is part of the `AccountId` preimage, so\n * this value decides which account a root key names \u2014 a mismatch does not fail\n * as a version error at the edge, it silently derives a different account and\n * core refuses the credential as unverifiable.\n *\n * `2` since the genesis dropped its per-namespace nonce. This was `1`, which\n * produced credentials current core rejects outright; the shape was already v2,\n * only the tag was stale.\n */\nconst ACCOUNT_GENESIS_VERSION = 2;\n\n/** What a root certifies about one device. */\nexport interface DeviceCertInput {\n /** The account root's signing secret, 64 hex. Signs the certificate. */\n rootSecret: string;\n /** The device being certified, 64 hex \u2014 see {@link mintDeviceId}. */\n device: string;\n /** The key that device signs with, 64 hex. */\n signPublicKey: string;\n /** The key the group key is delivered to, 64 hex (X25519). */\n kemPublicKey: string;\n /**\n * Must strictly exceed any epoch already folded for this device.\n *\n * The projection refuses a link that does not advance it, so a re-issued\n * certificate reusing an epoch is inert rather than a rollback.\n */\n deviceEpoch: number;\n}\n\n/**\n * Mint a device id: `H(DEVICE_ID_DOMAIN, account \u2016 nonce)`.\n *\n * Derived from the account and a fresh nonce rather than from the device's keys,\n * so rotating a keypair keeps the replica identity \u2014 and with it the counter\n * slots and HLC lineage \u2014 intact.\n */\nexport async function mintDeviceId(\n account: string,\n nonce: Uint8Array,\n): Promise<string> {\n if (nonce.length !== 16) {\n throw new Error(`nonce must be 16 bytes, got ${nonce.length}`);\n }\n const accountBytes = fromHex(account, 'account', 32);\n return hex(await domainHash(DEVICE_ID_DOMAIN, [accountBytes, nonce]));\n}\n\n/**\n * The 32 bytes a root signs to certify a device.\n *\n * Both keys are covered, so neither the signing key nor the delivery key can be\n * substituted into a certificate that otherwise verifies \u2014 which is what stops a\n * relay carrying someone's credential from making itself the reader.\n */\nexport async function deviceCertPayload(input: {\n account: string;\n device: string;\n signPublicKey: string;\n kemPublicKey: string;\n keyEpoch: number;\n deviceEpoch: number;\n}): Promise<Uint8Array> {\n return domainHash(CERT_DOMAIN, [\n fromHex(input.account, 'account', 32),\n fromHex(input.device, 'device', 32),\n fromHex(input.signPublicKey, 'signPublicKey', 32),\n fromHex(input.kemPublicKey, 'kemPublicKey', 32),\n u32le(input.keyEpoch),\n u32le(input.deviceEpoch),\n ]);\n}\n\n/**\n * Borsh-encode an `AccountProof<DeviceCert>` \u2014 what core calls a\n * `JoinAccountCredential`.\n *\n * Extracted so the device-cert path and the namespace-op signer encode this from\n * one implementation. Two copies of a borsh layout drift silently: the encoding\n * is only checked by a peer rejecting a signature, which points at the signature\n * rather than at whichever copy went stale.\n *\n * Layout is `AccountProof { genesis, chain, statement }`:\n * `AccountGenesis` (version u8 + root pk 32) + `chain` (empty Vec, u32 len) +\n * `DeviceCert` (account 32 + device 32 + sign pk 32 + kem pk 32 + key_epoch u32\n * + device_epoch u32 + signature 64) = 237 bytes with an empty chain.\n */\nexport function accountProofBytes(input: {\n rootPublicKey: Uint8Array;\n account: string;\n device: string;\n signPublicKey: string;\n kemPublicKey: string;\n keyEpoch: number;\n deviceEpoch: number;\n signature: Uint8Array;\n}): Uint8Array {\n return concat(\n new Uint8Array([ACCOUNT_GENESIS_VERSION]),\n input.rootPublicKey,\n u32le(0),\n fromHex(input.account, 'account', 32),\n fromHex(input.device, 'device', 32),\n fromHex(input.signPublicKey, 'signPublicKey', 32),\n fromHex(input.kemPublicKey, 'kemPublicKey', 32),\n u32le(input.keyEpoch),\n u32le(input.deviceEpoch),\n input.signature,\n );\n}\n\n/**\n * Certify a device, returning the hex credential `merod account sign-cert`\n * prints \u2014 a borsh-encoded `AccountProof<DeviceCert>`.\n *\n * The layout, field for field, is core's:\n *\n * ```\n * AccountProof { genesis: AccountGenesis, chain: Vec<RootKeyHandoff>, statement: DeviceCert }\n * AccountGenesis { version: u8, root_sign_pk: [u8; 32] }\n * chain \u2192 u32-LE count, then that many handoffs\n * DeviceCert { account, device, sign_pk, kem_pk: [u8; 32] x4,\n * key_epoch: u32, device_epoch: u32, signature: [u8; 64] }\n * ```\n *\n * Borsh for plain fixed-width data is concatenation; the only variable part is\n * the chain's length prefix. `chain` is empty here for the same reason it is in\n * `sign-cert`: a root that has never been handed off signs directly, and a\n * handoff chain is only needed once it has.\n *\n * `keyEpoch` is 0 for the same reason \u2014 it names the root epoch that signed, and\n * an un-rotated root is at 0. Re-signing after a handoff is a separate flow that\n * needs the chain, so this deliberately does not take it as an argument rather\n * than accepting a value it would then have to ignore.\n */\nexport async function signDeviceCert(input: DeviceCertInput): Promise<string> {\n const rootPublicKey = await derivePublicKey(input.rootSecret);\n const account = hex(\n await domainHash(ACCOUNT_ID_DOMAIN, [\n concat(new Uint8Array([ACCOUNT_GENESIS_VERSION]), rootPublicKey),\n ]),\n );\n\n const keyEpoch = 0;\n const payload = await deviceCertPayload({\n account,\n device: input.device,\n signPublicKey: input.signPublicKey,\n kemPublicKey: input.kemPublicKey,\n keyEpoch,\n deviceEpoch: input.deviceEpoch,\n });\n\n const key = await importSigningKey(input.rootSecret);\n const signature = new Uint8Array(\n await crypto.subtle.sign('Ed25519', key, payload),\n );\n\n const credential = accountProofBytes({\n rootPublicKey,\n account,\n device: input.device,\n signPublicKey: input.signPublicKey,\n kemPublicKey: input.kemPublicKey,\n keyEpoch,\n deviceEpoch: input.deviceEpoch,\n signature,\n });\n\n return hex(credential);\n}\n\n/** The account this root owns \u2014 the content address of its genesis. */\nexport async function accountForRoot(rootSecret: string): Promise<string> {\n const rootPublicKey = await derivePublicKey(rootSecret);\n return hex(\n await domainHash(ACCOUNT_ID_DOMAIN, [\n concat(new Uint8Array([ACCOUNT_GENESIS_VERSION]), rootPublicKey),\n ]),\n );\n}\n", "/**\n * The BIP39 English wordlist, verbatim \u2014 2048 words, index order significant.\n *\n * Vendored rather than depended on because this package ships with no runtime\n * dependencies, and a recovery phrase is useless if the list it was written\n * against is not the one everybody else implements. The list is content-checked\n * in `account.test.ts` against its canonical SHA-256\n * (`2f5eed53a4727b4bf8880d8f3f199efc90e58503646d9ff8eff3a2ed3b24dbda` over the\n * newline-joined, newline-terminated file), so a stray edit here fails the suite\n * instead of silently minting phrases no other wallet can restore.\n *\n * Its own module so bundlers can drop it: nothing outside `account/` imports it,\n * and the package is `sideEffects: false`, so a consumer who only signs never\n * pays for the ~13 KB.\n */\nexport const ENGLISH_WORDLIST: readonly string[] = [\n 'abandon', 'ability', 'able', 'about', 'above', 'absent', 'absorb', 'abstract',\n 'absurd', 'abuse', 'access', 'accident', 'account', 'accuse', 'achieve', 'acid',\n 'acoustic', 'acquire', 'across', 'act', 'action', 'actor', 'actress', 'actual',\n 'adapt', 'add', 'addict', 'address', 'adjust', 'admit', 'adult', 'advance',\n 'advice', 'aerobic', 'affair', 'afford', 'afraid', 'again', 'age', 'agent',\n 'agree', 'ahead', 'aim', 'air', 'airport', 'aisle', 'alarm', 'album',\n 'alcohol', 'alert', 'alien', 'all', 'alley', 'allow', 'almost', 'alone',\n 'alpha', 'already', 'also', 'alter', 'always', 'amateur', 'amazing', 'among',\n 'amount', 'amused', 'analyst', 'anchor', 'ancient', 'anger', 'angle', 'angry',\n 'animal', 'ankle', 'announce', 'annual', 'another', 'answer', 'antenna', 'antique',\n 'anxiety', 'any', 'apart', 'apology', 'appear', 'apple', 'approve', 'april',\n 'arch', 'arctic', 'area', 'arena', 'argue', 'arm', 'armed', 'armor',\n 'army', 'around', 'arrange', 'arrest', 'arrive', 'arrow', 'art', 'artefact',\n 'artist', 'artwork', 'ask', 'aspect', 'assault', 'asset', 'assist', 'assume',\n 'asthma', 'athlete', 'atom', 'attack', 'attend', 'attitude', 'attract', 'auction',\n 'audit', 'august', 'aunt', 'author', 'auto', 'autumn', 'average', 'avocado',\n 'avoid', 'awake', 'aware', 'away', 'awesome', 'awful', 'awkward', 'axis',\n 'baby', 'bachelor', 'bacon', 'badge', 'bag', 'balance', 'balcony', 'ball',\n 'bamboo', 'banana', 'banner', 'bar', 'barely', 'bargain', 'barrel', 'base',\n 'basic', 'basket', 'battle', 'beach', 'bean', 'beauty', 'because', 'become',\n 'beef', 'before', 'begin', 'behave', 'behind', 'believe', 'below', 'belt',\n 'bench', 'benefit', 'best', 'betray', 'better', 'between', 'beyond', 'bicycle',\n 'bid', 'bike', 'bind', 'biology', 'bird', 'birth', 'bitter', 'black',\n 'blade', 'blame', 'blanket', 'blast', 'bleak', 'bless', 'blind', 'blood',\n 'blossom', 'blouse', 'blue', 'blur', 'blush', 'board', 'boat', 'body',\n 'boil', 'bomb', 'bone', 'bonus', 'book', 'boost', 'border', 'boring',\n 'borrow', 'boss', 'bottom', 'bounce', 'box', 'boy', 'bracket', 'brain',\n 'brand', 'brass', 'brave', 'bread', 'breeze', 'brick', 'bridge', 'brief',\n 'bright', 'bring', 'brisk', 'broccoli', 'broken', 'bronze', 'broom', 'brother',\n 'brown', 'brush', 'bubble', 'buddy', 'budget', 'buffalo', 'build', 'bulb',\n 'bulk', 'bullet', 'bundle', 'bunker', 'burden', 'burger', 'burst', 'bus',\n 'business', 'busy', 'butter', 'buyer', 'buzz', 'cabbage', 'cabin', 'cable',\n 'cactus', 'cage', 'cake', 'call', 'calm', 'camera', 'camp', 'can',\n 'canal', 'cancel', 'candy', 'cannon', 'canoe', 'canvas', 'canyon', 'capable',\n 'capital', 'captain', 'car', 'carbon', 'card', 'cargo', 'carpet', 'carry',\n 'cart', 'case', 'cash', 'casino', 'castle', 'casual', 'cat', 'catalog',\n 'catch', 'category', 'cattle', 'caught', 'cause', 'caution', 'cave', 'ceiling',\n 'celery', 'cement', 'census', 'century', 'cereal', 'certain', 'chair', 'chalk',\n 'champion', 'change', 'chaos', 'chapter', 'charge', 'chase', 'chat', 'cheap',\n 'check', 'cheese', 'chef', 'cherry', 'chest', 'chicken', 'chief', 'child',\n 'chimney', 'choice', 'choose', 'chronic', 'chuckle', 'chunk', 'churn', 'cigar',\n 'cinnamon', 'circle', 'citizen', 'city', 'civil', 'claim', 'clap', 'clarify',\n 'claw', 'clay', 'clean', 'clerk', 'clever', 'click', 'client', 'cliff',\n 'climb', 'clinic', 'clip', 'clock', 'clog', 'close', 'cloth', 'cloud',\n 'clown', 'club', 'clump', 'cluster', 'clutch', 'coach', 'coast', 'coconut',\n 'code', 'coffee', 'coil', 'coin', 'collect', 'color', 'column', 'combine',\n 'come', 'comfort', 'comic', 'common', 'company', 'concert', 'conduct', 'confirm',\n 'congress', 'connect', 'consider', 'control', 'convince', 'cook', 'cool', 'copper',\n 'copy', 'coral', 'core', 'corn', 'correct', 'cost', 'cotton', 'couch',\n 'country', 'couple', 'course', 'cousin', 'cover', 'coyote', 'crack', 'cradle',\n 'craft', 'cram', 'crane', 'crash', 'crater', 'crawl', 'crazy', 'cream',\n 'credit', 'creek', 'crew', 'cricket', 'crime', 'crisp', 'critic', 'crop',\n 'cross', 'crouch', 'crowd', 'crucial', 'cruel', 'cruise', 'crumble', 'crunch',\n 'crush', 'cry', 'crystal', 'cube', 'culture', 'cup', 'cupboard', 'curious',\n 'current', 'curtain', 'curve', 'cushion', 'custom', 'cute', 'cycle', 'dad',\n 'damage', 'damp', 'dance', 'danger', 'daring', 'dash', 'daughter', 'dawn',\n 'day', 'deal', 'debate', 'debris', 'decade', 'december', 'decide', 'decline',\n 'decorate', 'decrease', 'deer', 'defense', 'define', 'defy', 'degree', 'delay',\n 'deliver', 'demand', 'demise', 'denial', 'dentist', 'deny', 'depart', 'depend',\n 'deposit', 'depth', 'deputy', 'derive', 'describe', 'desert', 'design', 'desk',\n 'despair', 'destroy', 'detail', 'detect', 'develop', 'device', 'devote', 'diagram',\n 'dial', 'diamond', 'diary', 'dice', 'diesel', 'diet', 'differ', 'digital',\n 'dignity', 'dilemma', 'dinner', 'dinosaur', 'direct', 'dirt', 'disagree', 'discover',\n 'disease', 'dish', 'dismiss', 'disorder', 'display', 'distance', 'divert', 'divide',\n 'divorce', 'dizzy', 'doctor', 'document', 'dog', 'doll', 'dolphin', 'domain',\n 'donate', 'donkey', 'donor', 'door', 'dose', 'double', 'dove', 'draft',\n 'dragon', 'drama', 'drastic', 'draw', 'dream', 'dress', 'drift', 'drill',\n 'drink', 'drip', 'drive', 'drop', 'drum', 'dry', 'duck', 'dumb',\n 'dune', 'during', 'dust', 'dutch', 'duty', 'dwarf', 'dynamic', 'eager',\n 'eagle', 'early', 'earn', 'earth', 'easily', 'east', 'easy', 'echo',\n 'ecology', 'economy', 'edge', 'edit', 'educate', 'effort', 'egg', 'eight',\n 'either', 'elbow', 'elder', 'electric', 'elegant', 'element', 'elephant', 'elevator',\n 'elite', 'else', 'embark', 'embody', 'embrace', 'emerge', 'emotion', 'employ',\n 'empower', 'empty', 'enable', 'enact', 'end', 'endless', 'endorse', 'enemy',\n 'energy', 'enforce', 'engage', 'engine', 'enhance', 'enjoy', 'enlist', 'enough',\n 'enrich', 'enroll', 'ensure', 'enter', 'entire', 'entry', 'envelope', 'episode',\n 'equal', 'equip', 'era', 'erase', 'erode', 'erosion', 'error', 'erupt',\n 'escape', 'essay', 'essence', 'estate', 'eternal', 'ethics', 'evidence', 'evil',\n 'evoke', 'evolve', 'exact', 'example', 'excess', 'exchange', 'excite', 'exclude',\n 'excuse', 'execute', 'exercise', 'exhaust', 'exhibit', 'exile', 'exist', 'exit',\n 'exotic', 'expand', 'expect', 'expire', 'explain', 'expose', 'express', 'extend',\n 'extra', 'eye', 'eyebrow', 'fabric', 'face', 'faculty', 'fade', 'faint',\n 'faith', 'fall', 'false', 'fame', 'family', 'famous', 'fan', 'fancy',\n 'fantasy', 'farm', 'fashion', 'fat', 'fatal', 'father', 'fatigue', 'fault',\n 'favorite', 'feature', 'february', 'federal', 'fee', 'feed', 'feel', 'female',\n 'fence', 'festival', 'fetch', 'fever', 'few', 'fiber', 'fiction', 'field',\n 'figure', 'file', 'film', 'filter', 'final', 'find', 'fine', 'finger',\n 'finish', 'fire', 'firm', 'first', 'fiscal', 'fish', 'fit', 'fitness',\n 'fix', 'flag', 'flame', 'flash', 'flat', 'flavor', 'flee', 'flight',\n 'flip', 'float', 'flock', 'floor', 'flower', 'fluid', 'flush', 'fly',\n 'foam', 'focus', 'fog', 'foil', 'fold', 'follow', 'food', 'foot',\n 'force', 'forest', 'forget', 'fork', 'fortune', 'forum', 'forward', 'fossil',\n 'foster', 'found', 'fox', 'fragile', 'frame', 'frequent', 'fresh', 'friend',\n 'fringe', 'frog', 'front', 'frost', 'frown', 'frozen', 'fruit', 'fuel',\n 'fun', 'funny', 'furnace', 'fury', 'future', 'gadget', 'gain', 'galaxy',\n 'gallery', 'game', 'gap', 'garage', 'garbage', 'garden', 'garlic', 'garment',\n 'gas', 'gasp', 'gate', 'gather', 'gauge', 'gaze', 'general', 'genius',\n 'genre', 'gentle', 'genuine', 'gesture', 'ghost', 'giant', 'gift', 'giggle',\n 'ginger', 'giraffe', 'girl', 'give', 'glad', 'glance', 'glare', 'glass',\n 'glide', 'glimpse', 'globe', 'gloom', 'glory', 'glove', 'glow', 'glue',\n 'goat', 'goddess', 'gold', 'good', 'goose', 'gorilla', 'gospel', 'gossip',\n 'govern', 'gown', 'grab', 'grace', 'grain', 'grant', 'grape', 'grass',\n 'gravity', 'great', 'green', 'grid', 'grief', 'grit', 'grocery', 'group',\n 'grow', 'grunt', 'guard', 'guess', 'guide', 'guilt', 'guitar', 'gun',\n 'gym', 'habit', 'hair', 'half', 'hammer', 'hamster', 'hand', 'happy',\n 'harbor', 'hard', 'harsh', 'harvest', 'hat', 'have', 'hawk', 'hazard',\n 'head', 'health', 'heart', 'heavy', 'hedgehog', 'height', 'hello', 'helmet',\n 'help', 'hen', 'hero', 'hidden', 'high', 'hill', 'hint', 'hip',\n 'hire', 'history', 'hobby', 'hockey', 'hold', 'hole', 'holiday', 'hollow',\n 'home', 'honey', 'hood', 'hope', 'horn', 'horror', 'horse', 'hospital',\n 'host', 'hotel', 'hour', 'hover', 'hub', 'huge', 'human', 'humble',\n 'humor', 'hundred', 'hungry', 'hunt', 'hurdle', 'hurry', 'hurt', 'husband',\n 'hybrid', 'ice', 'icon', 'idea', 'identify', 'idle', 'ignore', 'ill',\n 'illegal', 'illness', 'image', 'imitate', 'immense', 'immune', 'impact', 'impose',\n 'improve', 'impulse', 'inch', 'include', 'income', 'increase', 'index', 'indicate',\n 'indoor', 'industry', 'infant', 'inflict', 'inform', 'inhale', 'inherit', 'initial',\n 'inject', 'injury', 'inmate', 'inner', 'innocent', 'input', 'inquiry', 'insane',\n 'insect', 'inside', 'inspire', 'install', 'intact', 'interest', 'into', 'invest',\n 'invite', 'involve', 'iron', 'island', 'isolate', 'issue', 'item', 'ivory',\n 'jacket', 'jaguar', 'jar', 'jazz', 'jealous', 'jeans', 'jelly', 'jewel',\n 'job', 'join', 'joke', 'journey', 'joy', 'judge', 'juice', 'jump',\n 'jungle', 'junior', 'junk', 'just', 'kangaroo', 'keen', 'keep', 'ketchup',\n 'key', 'kick', 'kid', 'kidney', 'kind', 'kingdom', 'kiss', 'kit',\n 'kitchen', 'kite', 'kitten', 'kiwi', 'knee', 'knife', 'knock', 'know',\n 'lab', 'label', 'labor', 'ladder', 'lady', 'lake', 'lamp', 'language',\n 'laptop', 'large', 'later', 'latin', 'laugh', 'laundry', 'lava', 'law',\n 'lawn', 'lawsuit', 'layer', 'lazy', 'leader', 'leaf', 'learn', 'leave',\n 'lecture', 'left', 'leg', 'legal', 'legend', 'leisure', 'lemon', 'lend',\n 'length', 'lens', 'leopard', 'lesson', 'letter', 'level', 'liar', 'liberty',\n 'library', 'license', 'life', 'lift', 'light', 'like', 'limb', 'limit',\n 'link', 'lion', 'liquid', 'list', 'little', 'live', 'lizard', 'load',\n 'loan', 'lobster', 'local', 'lock', 'logic', 'lonely', 'long', 'loop',\n 'lottery', 'loud', 'lounge', 'love', 'loyal', 'lucky', 'luggage', 'lumber',\n 'lunar', 'lunch', 'luxury', 'lyrics', 'machine', 'mad', 'magic', 'magnet',\n 'maid', 'mail', 'main', 'major', 'make', 'mammal', 'man', 'manage',\n 'mandate', 'mango', 'mansion', 'manual', 'maple', 'marble', 'march', 'margin',\n 'marine', 'market', 'marriage', 'mask', 'mass', 'master', 'match', 'material',\n 'math', 'matrix', 'matter', 'maximum', 'maze', 'meadow', 'mean', 'measure',\n 'meat', 'mechanic', 'medal', 'media', 'melody', 'melt', 'member', 'memory',\n 'mention', 'menu', 'mercy', 'merge', 'merit', 'merry', 'mesh', 'message',\n 'metal', 'method', 'middle', 'midnight', 'milk', 'million', 'mimic', 'mind',\n 'minimum', 'minor', 'minute', 'miracle', 'mirror', 'misery', 'miss', 'mistake',\n 'mix', 'mixed', 'mixture', 'mobile', 'model', 'modify', 'mom', 'moment',\n 'monitor', 'monkey', 'monster', 'month', 'moon', 'moral', 'more', 'morning',\n 'mosquito', 'mother', 'motion', 'motor', 'mountain', 'mouse', 'move', 'movie',\n 'much', 'muffin', 'mule', 'multiply', 'muscle', 'museum', 'mushroom', 'music',\n 'must', 'mutual', 'myself', 'mystery', 'myth', 'naive', 'name', 'napkin',\n 'narrow', 'nasty', 'nation', 'nature', 'near', 'neck', 'need', 'negative',\n 'neglect', 'neither', 'nephew', 'nerve', 'nest', 'net', 'network', 'neutral',\n 'never', 'news', 'next', 'nice', 'night', 'noble', 'noise', 'nominee',\n 'noodle', 'normal', 'north', 'nose', 'notable', 'note', 'nothing', 'notice',\n 'novel', 'now', 'nuclear', 'number', 'nurse', 'nut', 'oak', 'obey',\n 'object', 'oblige', 'obscure', 'observe', 'obtain', 'obvious', 'occur', 'ocean',\n 'october', 'odor', 'off', 'offer', 'office', 'often', 'oil', 'okay',\n 'old', 'olive', 'olympic', 'omit', 'once', 'one', 'onion', 'online',\n 'only', 'open', 'opera', 'opinion', 'oppose', 'option', 'orange', 'orbit',\n 'orchard', 'order', 'ordinary', 'organ', 'orient', 'original', 'orphan', 'ostrich',\n 'other', 'outdoor', 'outer', 'output', 'outside', 'oval', 'oven', 'over',\n 'own', 'owner', 'oxygen', 'oyster', 'ozone', 'pact', 'paddle', 'page',\n 'pair', 'palace', 'palm', 'panda', 'panel', 'panic', 'panther', 'paper',\n 'parade', 'parent', 'park', 'parrot', 'party', 'pass', 'patch', 'path',\n 'patient', 'patrol', 'pattern', 'pause', 'pave', 'payment', 'peace', 'peanut',\n 'pear', 'peasant', 'pelican', 'pen', 'penalty', 'pencil', 'people', 'pepper',\n 'perfect', 'permit', 'person', 'pet', 'phone', 'photo', 'phrase', 'physical',\n 'piano', 'picnic', 'picture', 'piece', 'pig', 'pigeon', 'pill', 'pilot',\n 'pink', 'pioneer', 'pipe', 'pistol', 'pitch', 'pizza', 'place', 'planet',\n 'plastic', 'plate', 'play', 'please', 'pledge', 'pluck', 'plug', 'plunge',\n 'poem', 'poet', 'point', 'polar', 'pole', 'police', 'pond', 'pony',\n 'pool', 'popular', 'portion', 'position', 'possible', 'post', 'potato', 'pottery',\n 'poverty', 'powder', 'power', 'practice', 'praise', 'predict', 'prefer', 'prepare',\n 'present', 'pretty', 'prevent', 'price', 'pride', 'primary', 'print', 'priority',\n 'prison', 'private', 'prize', 'problem', 'process', 'produce', 'profit', 'program',\n 'project', 'promote', 'proof', 'property', 'prosper', 'protect', 'proud', 'provide',\n 'public', 'pudding', 'pull', 'pulp', 'pulse', 'pumpkin', 'punch', 'pupil',\n 'puppy', 'purchase', 'purity', 'purpose', 'purse', 'push', 'put', 'puzzle',\n 'pyramid', 'quality', 'quantum', 'quarter', 'question', 'quick', 'quit', 'quiz',\n 'quote', 'rabbit', 'raccoon', 'race', 'rack', 'radar', 'radio', 'rail',\n 'rain', 'raise', 'rally', 'ramp', 'ranch', 'random', 'range', 'rapid',\n 'rare', 'rate', 'rather', 'raven', 'raw', 'razor', 'ready', 'real',\n 'reason', 'rebel', 'rebuild', 'recall', 'receive', 'recipe', 'record', 'recycle',\n 'reduce', 'reflect', 'reform', 'refuse', 'region', 'regret', 'regular', 'reject',\n 'relax', 'release', 'relief', 'rely', 'remain', 'remember', 'remind', 'remove',\n 'render', 'renew', 'rent', 'reopen', 'repair', 'repeat', 'replace', 'report',\n 'require', 'rescue', 'resemble', 'resist', 'resource', 'response', 'result', 'retire',\n 'retreat', 'return', 'reunion', 'reveal', 'review', 'reward', 'rhythm', 'rib',\n 'ribbon', 'rice', 'rich', 'ride', 'ridge', 'rifle', 'right', 'rigid',\n 'ring', 'riot', 'ripple', 'risk', 'ritual', 'rival', 'river', 'road',\n 'roast', 'robot', 'robust', 'rocket', 'romance', 'roof', 'rookie', 'room',\n 'rose', 'rotate', 'rough', 'round', 'route', 'royal', 'rubber', 'rude',\n 'rug', 'rule', 'run', 'runway', 'rural', 'sad', 'saddle', 'sadness',\n 'safe', 'sail', 'salad', 'salmon', 'salon', 'salt', 'salute', 'same',\n 'sample', 'sand', 'satisfy', 'satoshi', 'sauce', 'sausage', 'save', 'say',\n 'scale', 'scan', 'scare', 'scatter', 'scene', 'scheme', 'school', 'science',\n 'scissors', 'scorpion', 'scout', 'scrap', 'screen', 'script', 'scrub', 'sea',\n 'search', 'season', 'seat', 'second', 'secret', 'section', 'security', 'seed',\n 'seek', 'segment', 'select', 'sell', 'seminar', 'senior', 'sense', 'sentence',\n 'series', 'service', 'session', 'settle', 'setup', 'seven', 'shadow', 'shaft',\n 'shallow', 'share', 'shed', 'shell', 'sheriff', 'shield', 'shift', 'shine',\n 'ship', 'shiver', 'shock', 'shoe', 'shoot', 'shop', 'short', 'shoulder',\n 'shove', 'shrimp', 'shrug', 'shuffle', 'shy', 'sibling', 'sick', 'side',\n 'siege', 'sight', 'sign', 'silent', 'silk', 'silly', 'silver', 'similar',\n 'simple', 'since', 'sing', 'siren', 'sister', 'situate', 'six', 'size',\n 'skate', 'sketch', 'ski', 'skill', 'skin', 'skirt', 'skull', 'slab',\n 'slam', 'sleep', 'slender', 'slice', 'slide', 'slight', 'slim', 'slogan',\n 'slot', 'slow', 'slush', 'small', 'smart', 'smile', 'smoke', 'smooth',\n 'snack', 'snake', 'snap', 'sniff', 'snow', 'soap', 'soccer', 'social',\n 'sock', 'soda', 'soft', 'solar', 'soldier', 'solid', 'solution', 'solve',\n 'someone', 'song', 'soon', 'sorry', 'sort', 'soul', 'sound', 'soup',\n 'source', 'south', 'space', 'spare', 'spatial', 'spawn', 'speak', 'special',\n 'speed', 'spell', 'spend', 'sphere', 'spice', 'spider', 'spike', 'spin',\n 'spirit', 'split', 'spoil', 'sponsor', 'spoon', 'sport', 'spot', 'spray',\n 'spread', 'spring', 'spy', 'square', 'squeeze', 'squirrel', 'stable', 'stadium',\n 'staff', 'stage', 'stairs', 'stamp', 'stand', 'start', 'state', 'stay',\n 'steak', 'steel', 'stem', 'step', 'stereo', 'stick', 'still', 'sting',\n 'stock', 'stomach', 'stone', 'stool', 'story', 'stove', 'strategy', 'street',\n 'strike', 'strong', 'struggle', 'student', 'stuff', 'stumble', 'style', 'subject',\n 'submit', 'subway', 'success', 'such', 'sudden', 'suffer', 'sugar', 'suggest',\n 'suit', 'summer', 'sun', 'sunny', 'sunset', 'super', 'supply', 'supreme',\n 'sure', 'surface', 'surge', 'surprise', 'surround', 'survey', 'suspect', 'sustain',\n 'swallow', 'swamp', 'swap', 'swarm', 'swear', 'sweet', 'swift', 'swim',\n 'swing', 'switch', 'sword', 'symbol', 'symptom', 'syrup', 'system', 'table',\n 'tackle', 'tag', 'tail', 'talent', 'talk', 'tank', 'tape', 'target',\n 'task', 'taste', 'tattoo', 'taxi', 'teach', 'team', 'tell', 'ten',\n 'tenant', 'tennis', 'tent', 'term', 'test', 'text', 'thank', 'that',\n 'theme', 'then', 'theory', 'there', 'they', 'thing', 'this', 'thought',\n 'three', 'thrive', 'throw', 'thumb', 'thunder', 'ticket', 'tide', 'tiger',\n 'tilt', 'timber', 'time', 'tiny', 'tip', 'tired', 'tissue', 'title',\n 'toast', 'tobacco', 'today', 'toddler', 'toe', 'together', 'toilet', 'token',\n 'tomato', 'tomorrow', 'tone', 'tongue', 'tonight', 'tool', 'tooth', 'top',\n 'topic', 'topple', 'torch', 'tornado', 'tortoise', 'toss', 'total', 'tourist',\n 'toward', 'tower', 'town', 'toy', 'track', 'trade', 'traffic', 'tragic',\n 'train', 'transfer', 'trap', 'trash', 'travel', 'tray', 'treat', 'tree',\n 'trend', 'trial', 'tribe', 'trick', 'trigger', 'trim', 'trip', 'trophy',\n 'trouble', 'truck', 'true', 'truly', 'trumpet', 'trust', 'truth', 'try',\n 'tube', 'tuition', 'tumble', 'tuna', 'tunnel', 'turkey', 'turn', 'turtle',\n 'twelve', 'twenty', 'twice', 'twin', 'twist', 'two', 'type', 'typical',\n 'ugly', 'umbrella', 'unable', 'unaware', 'uncle', 'uncover', 'under', 'undo',\n 'unfair', 'unfold', 'unhappy', 'uniform', 'unique', 'unit', 'universe', 'unknown',\n 'unlock', 'until', 'unusual', 'unveil', 'update', 'upgrade', 'uphold', 'upon',\n 'upper', 'upset', 'urban', 'urge', 'usage', 'use', 'used', 'useful',\n 'useless', 'usual', 'utility', 'vacant', 'vacuum', 'vague', 'valid', 'valley',\n 'valve', 'van', 'vanish', 'vapor', 'various', 'vast', 'vault', 'vehicle',\n 'velvet', 'vendor', 'venture', 'venue', 'verb', 'verify', 'version', 'very',\n 'vessel', 'veteran', 'viable', 'vibrant', 'vicious', 'victory', 'video', 'view',\n 'village', 'vintage', 'violin', 'virtual', 'virus', 'visa', 'visit', 'visual',\n 'vital', 'vivid', 'vocal', 'voice', 'void', 'volcano', 'volume', 'vote',\n 'voyage', 'wage', 'wagon', 'wait', 'walk', 'wall', 'walnut', 'want',\n 'warfare', 'warm', 'warrior', 'wash', 'wasp', 'waste', 'water', 'wave',\n 'way', 'wealth', 'weapon', 'wear', 'weasel', 'weather', 'web', 'wedding',\n 'weekend', 'weird', 'welcome', 'west', 'wet', 'whale', 'what', 'wheat',\n 'wheel', 'when', 'where', 'whip', 'whisper', 'wide', 'width', 'wife',\n 'wild', 'will', 'win', 'window', 'wine', 'wing', 'wink', 'winner',\n 'winter', 'wire', 'wisdom', 'wise', 'wish', 'witness', 'wolf', 'woman',\n 'wonder', 'wood', 'wool', 'word', 'work', 'world', 'worry', 'worth',\n 'wrap', 'wreck', 'wrestle', 'wrist', 'write', 'wrong', 'yard', 'year',\n 'yellow', 'you', 'young', 'youth', 'zebra', 'zero', 'zone', 'zoo',\n];\n", "/**\n * Creating an account root, and proving you hold one.\n *\n * `signDeviceCert` assumes an account root already exists and asks only what it\n * certifies. This is the step before that: where the root comes from, how a\n * person keeps it, and how they show a server they have it.\n *\n * Two things live here because they are the two halves of onboarding:\n *\n * - {@link generateAccountRoot} mints a root and hands back a 24-word recovery\n * phrase. The phrase *is* the key \u2014 see the interop note on that function \u2014\n * so a holder who writes it down can restore the account on any device.\n * - {@link signAccountLink} signs a cloud-issued challenge with that root, which\n * is how Calimero Cloud binds a billing login to a Calimero account.\n * - {@link signAccountLogin} signs a different cloud-issued challenge with the\n * same root to claim the account outright and open a session as it \u2014 the\n * password-free counterpart to signing in with Google.\n *\n * Both are offline and hold no state. Nothing here talks to a node or to the\n * cloud; `CloudClient.linkAccount` is the wrapper that does.\n *\n * Derived from:\n *\n * - `ACCOUNT_LINK_DOMAIN` / `ACCOUNT_LOGIN_DOMAIN`, and\n * `verify_account_link_proof` / `verify_login_proof` \u2014 mdma's\n * `manager/app/account_proof.py`\n * - the account derivation \u2014 `accountForRoot` in `../device-cert`, which is\n * core's `AccountId` preimage\n */\nimport {\n concat,\n derivePublicKey,\n fromHex,\n hex,\n importSigningKey,\n} from '../crypto/internal.js';\nimport { accountForRoot } from '../device-cert/index.js';\nimport { ENGLISH_WORDLIST } from './wordlist.js';\n\n/**\n * mdma's `ACCOUNT_LINK_DOMAIN`, trailing NUL included.\n *\n * Prefixed raw rather than run through `domainHash`: mdma verifies\n * `ed25519_verify(root_pk, ACCOUNT_LINK_DOMAIN \u2016 nonce_utf8)` over exactly these\n * bytes. The separator is what stops a signature gathered here being replayable\n * into any other protocol that has a root sign opaque bytes, so it is not\n * cosmetic \u2014 and the NUL is part of it.\n */\nconst ACCOUNT_LINK_DOMAIN = new TextEncoder().encode(\n 'calimero.mdma.account-link.v1\u0000',\n);\n\n/**\n * mdma's `ACCOUNT_LOGIN_DOMAIN`, trailing NUL included.\n *\n * A separate domain from {@link ACCOUNT_LINK_DOMAIN} because the two\n * exchanges are otherwise byte-identical \u2014 both are a root signature over a\n * cloud-issued nonce \u2014 and a signature gathered while linking is gathered from\n * someone already signed in and trusting the page. Without the separation it\n * would be replayable as a login by whoever collected it.\n */\nconst ACCOUNT_LOGIN_DOMAIN = new TextEncoder().encode(\n 'calimero.mdma.account-login.v1\\0',\n);\n\n/** Entropy carried by a phrase. 256 bits \u2014 32 bytes, an ed25519 seed exactly. */\nconst ENTROPY_BITS = 256;\n/** BIP39's checksum length: one bit per 32 bits of entropy. */\nconst CHECKSUM_BITS = ENTROPY_BITS / 32;\n/** 11 bits per word, so (256 + 8) / 11 = 24 words. */\nconst PHRASE_WORDS = (ENTROPY_BITS + CHECKSUM_BITS) / 11;\n\n/** An account root: the secret, the key it derives, and the account it names. */\nexport interface AccountRoot {\n /**\n * The root signing secret, 64 hex.\n *\n * The whole account. It signs device certificates and cloud links, and there\n * is no recovery path that does not go through it or its phrase \u2014 so it is\n * never sent anywhere, and in a browser it does not belong in `localStorage`.\n */\n secret: string;\n /** The root's ed25519 public key, 64 hex. Safe to publish. */\n publicKey: string;\n /** The account this root names, 64 hex \u2014 the content address of its genesis. */\n accountId: string;\n}\n\n/** An account root together with the phrase that restores it. */\nexport interface RecoverableAccountRoot extends AccountRoot {\n /**\n * A 24-word BIP39 English recovery phrase, single-space separated.\n *\n * Show it once, have the holder write it down, and do not keep it: it is the\n * secret in a form a person can copy by hand without transcription errors.\n */\n phrase: string;\n}\n\n/**\n * Mint a brand-new account root from the platform CSPRNG.\n *\n * This is the \"generate a new key\" half of cloud registration \u2014 the other half\n * being a root the holder already has, on a desktop app or a hardware key, which\n * signs without this ever running.\n *\n * **Interop note, and it matters.** The phrase encodes the root secret as BIP39\n * *entropy*, not as a BIP39 *seed*. A tool restoring it must call the equivalent\n * of `mnemonicToEntropy(phrase)` \u2014 32 bytes \u2014 and use those bytes as the ed25519\n * secret directly. `mnemonicToSeed(phrase)`, which most wallet libraries reach\n * for first, runs PBKDF2 and returns 64 different bytes; feeding those in names\n * a different account and fails as \"wrong account\", not as a decoding error.\n * There is no passphrase and no BIP32 derivation path: the 24 words are the key.\n */\nexport async function generateAccountRoot(): Promise<RecoverableAccountRoot> {\n const entropy = crypto.getRandomValues(new Uint8Array(ENTROPY_BITS / 8));\n return withPhrase(hex(entropy), await encodePhrase(entropy));\n}\n\n/**\n * Restore an account root from its recovery phrase.\n *\n * Whitespace and case are normalised, so a phrase pasted out of a text editor\n * with a trailing newline or a stray double space restores fine. A word outside\n * the list, a wrong word count, or a failed checksum throws rather than\n * returning some other account \u2014 a single mistyped word is the common case, and\n * BIP39's checksum catches all but 1 in 256 of them.\n */\nexport async function accountRootFromPhrase(\n phrase: string,\n): Promise<RecoverableAccountRoot> {\n const words = normalisePhrase(phrase);\n return withPhrase(hex(await decodePhrase(words)), words.join(' '));\n}\n\n/** The public half of a root you already hold, without re-deriving it by hand. */\nexport async function accountRootFromSecret(\n secret: string,\n): Promise<AccountRoot> {\n // Validates the length and alphabet before anything derives from it, so a\n // truncated paste fails here rather than naming an unrelated account.\n fromHex(secret, 'secret', 32);\n return {\n secret,\n publicKey: hex(await derivePublicKey(secret)),\n accountId: await accountForRoot(secret),\n };\n}\n\n/** What {@link signAccountLink} needs. */\nexport interface AccountLinkInput {\n /** The account root's signing secret, 64 hex. */\n rootSecret: string;\n /**\n * The challenge from `GET /api/cloud/me/accounts/challenge`, verbatim.\n *\n * Signed as its UTF-8 bytes, not decoded first: mdma seals it as an opaque\n * string and verifies over the same string it issued.\n */\n nonce: string;\n}\n\n/**\n * Sign a cloud link challenge with the account root. Returns base64.\n *\n * Standard base64, not base64url \u2014 mdma decodes it with `validate=True`, so the\n * `-`/`_` alphabet is rejected outright rather than silently mis-decoded.\n *\n * Root-signed rather than device-signed on purpose: this is the credential\n * someone still holds after losing a device, which is the case linking exists to\n * serve.\n */\nexport async function signAccountLink(\n input: AccountLinkInput,\n): Promise<string> {\n if (!input.nonce) throw new Error('nonce is required to sign an account link');\n const key = await importSigningKey(input.rootSecret);\n const signature = new Uint8Array(\n await crypto.subtle.sign(\n 'Ed25519',\n key,\n concat(ACCOUNT_LINK_DOMAIN, new TextEncoder().encode(input.nonce)),\n ),\n );\n return base64(signature);\n}\n\n/** What {@link signAccountLogin} needs. */\nexport interface AccountLoginInput {\n /** The account root's signing secret, 64 hex. */\n rootSecret: string;\n /** The challenge from `GET /api/auth/account/challenge`, verbatim UTF-8. */\n nonce: string;\n}\n\n/**\n * Sign a cloud login challenge with the account root. Returns base64.\n *\n * The statement this makes is *\"I hold the root of account X\"*, and it is the\n * one statement a device credential can never make. A routing read proves the\n * caller holds a device the root certified \u2014 the credential carries the root's\n * signature over that device \u2014 but a certificate is public and travels in the\n * clear inside every device-link op, so anyone who has seen one can say \"a\n * device of X is asking\". Only the root can say the account is theirs.\n *\n * Which is why this is worth doing **once**: the cloud records the claim, and\n * from then on it knows the account behind a device proof was proven by its\n * owner. The session it also mints is the ordinary MDMA session token, accepted\n * everywhere a Google-issued one is.\n *\n * Standard base64, not base64url \u2014 mdma decodes with `validate=True`.\n */\nexport async function signAccountLogin(\n input: AccountLoginInput,\n): Promise<string> {\n if (!input.nonce) throw new Error('nonce is required to sign an account login');\n const key = await importSigningKey(input.rootSecret);\n const signature = new Uint8Array(\n await crypto.subtle.sign(\n 'Ed25519',\n key,\n concat(ACCOUNT_LOGIN_DOMAIN, new TextEncoder().encode(input.nonce)),\n ),\n );\n return base64(signature);\n}\n\nasync function withPhrase(\n secret: string,\n phrase: string,\n): Promise<RecoverableAccountRoot> {\n return { ...(await accountRootFromSecret(secret)), phrase };\n}\n\nfunction normalisePhrase(phrase: string): string[] {\n // NFKD per BIP39. A no-op for the English list, which is ASCII, but the\n // normalisation is what the spec pins and costs nothing to honour.\n const words = phrase.normalize('NFKD').toLowerCase().trim().split(/\\s+/);\n const count = words[0] === '' ? 0 : words.length;\n if (count !== PHRASE_WORDS) {\n throw new Error(\n `recovery phrase must be ${PHRASE_WORDS} words, got ${count}`,\n );\n }\n return words;\n}\n\n/** Entropy then checksum, chopped into 11-bit indices. */\nasync function encodePhrase(entropy: Uint8Array): Promise<string> {\n const bits = bitsOf(entropy) + (await checksumBits(entropy));\n const words: string[] = [];\n for (let i = 0; i < PHRASE_WORDS; i += 1) {\n const index = Number.parseInt(bits.slice(i * 11, i * 11 + 11), 2);\n words.push(ENGLISH_WORDLIST[index]);\n }\n return words.join(' ');\n}\n\nasync function decodePhrase(words: string[]): Promise<Uint8Array> {\n let bits = '';\n for (const word of words) {\n const index = ENGLISH_WORDLIST.indexOf(word);\n if (index < 0) {\n throw new Error(`\"${word}\" is not a word in the recovery phrase list`);\n }\n bits += index.toString(2).padStart(11, '0');\n }\n\n const entropy = new Uint8Array(ENTROPY_BITS / 8);\n for (let i = 0; i < entropy.length; i += 1) {\n entropy[i] = Number.parseInt(bits.slice(i * 8, i * 8 + 8), 2);\n }\n if (bits.slice(ENTROPY_BITS) !== (await checksumBits(entropy))) {\n throw new Error(\n 'recovery phrase checksum failed \u2014 check for a mistyped or transposed word',\n );\n }\n return entropy;\n}\n\n/** The leading `CHECKSUM_BITS` of SHA-256 over the entropy, as a bit string. */\nasync function checksumBits(entropy: Uint8Array): Promise<string> {\n const digest = new Uint8Array(await crypto.subtle.digest('SHA-256', entropy));\n return bitsOf(digest).slice(0, CHECKSUM_BITS);\n}\n\nfunction bitsOf(bytes: Uint8Array): string {\n let bits = '';\n for (const byte of bytes) bits += byte.toString(2).padStart(8, '0');\n return bits;\n}\n\nfunction base64(bytes: Uint8Array): string {\n // Built a character at a time rather than spreading into `String.fromCharCode`:\n // the spread is fine for a 64-byte signature but blows the argument limit on\n // anything larger, and this has no such cliff to remember.\n let binary = '';\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary);\n}\n", "/**\n * Proving an account to the cloud for the one read a joiner cannot avoid.\n *\n * `GET /api/cloud/namespaces/{ns}/admitters` answers where to send a signed\n * membership op. A joiner needs it and *cannot* hold a cloud login: it is by\n * construction not the namespace owner, and not needing an account on the cloud\n * is the entire point of the path \u2014 a keyholder mints a root, certifies a\n * device and signs its own join offline, and this read is the only thing that\n * tells it where to post the result.\n *\n * So the caller proves possession of the credential it already holds. The cloud\n * mints a sealed, namespace-bound nonce; the device signs it; the read carries\n * the certificate and that signature.\n *\n * ## What this establishes, and what it does not\n *\n * It establishes that the caller holds a device key certified by some account\n * root, and *which* account that is. It does **not** establish that the account\n * was invited to the namespace, nor that it is a member \u2014 the cloud cannot\n * check either, because membership is governance state held by the nodes.\n * Anyone can mint an account root offline, so this is not a wall. It buys\n * attribution (every read names an account and can be rate-limited) and it ends\n * anonymous bulk discovery. Authorization stays where it always was: at the\n * node, on the signed op.\n *\n * ## Why the DEVICE signs, not the root\n *\n * The opposite choice from {@link signAccountLink}, deliberately. That one\n * exists for the device-loss case, so only the root will do. This one runs on\n * every routing read from a browser that discards the root immediately after\n * certifying its device \u2014 requiring the root would mean re-entering a 24-word\n * phrase to look up a URL.\n *\n * The certificate alone is not enough and the signature is not redundant with\n * it: a certificate travels in the clear inside every device-link op, so anyone\n * who has seen one can replay it. Only the nonce signature binds the presenter\n * to the device.\n */\n\nimport { concat, fromHex, importSigningKey } from '../crypto/internal.js';\n\n/**\n * The signing domain, followed immediately by the nonce as UTF-8.\n *\n * A flat prefix rather than this SDK's usual length-prefixed `domainHash`,\n * because the verifier is MDMA's `verify_routing_proof` and it checks\n * `DOMAIN \u2016 nonce` against the raw message. The trailing NUL is part of the\n * domain string on both sides; it is what stops a domain that is a prefix of\n * some future one from colliding with it.\n */\nconst ROUTING_PROOF_DOMAIN = new TextEncoder().encode(\n 'calimero.mdma.routing-read.v1\\0',\n);\n\n/**\n * The credential a routing read presents: a certificate, and the key to sign\n * the challenge with.\n *\n * `credential` is the 237-byte `AccountProof<DeviceCert>` as hex \u2014 exactly what\n * {@link signDeviceCert} returns, and the same string a login statement and a\n * warrant carry. It is public by construction.\n *\n * `deviceSecret` never leaves the process. It signs the challenge locally and\n * only the signature travels.\n */\nexport interface RoutingCredential {\n /** The `AccountProof<DeviceCert>`, hex borsh, as `signDeviceCert` returns it. */\n credential: string;\n /** The certified device's ed25519 signing secret, hex (32 bytes). */\n deviceSecret: string;\n}\n\n/** A challenge to sign, as the cloud minted it. */\nexport interface RoutingChallenge {\n namespaceId: string;\n /** Opaque and sealed \u2014 the client neither parses nor constructs this. */\n nonce: string;\n /** Epoch **milliseconds**, as MDMA reports it. Nonces live about two minutes. */\n expiresAtMs: number;\n}\n\n/**\n * A challenge bound to an ACCOUNT rather than a namespace, for asking which\n * relays serve that account.\n *\n * Separate from {@link RoutingChallenge} because the binding is the security\n * property, not a field name: MDMA seals the account into this one and the\n * namespace into that one, and refuses each at the other's route. Keeping the\n * TypeScript types distinct is what stops a caller passing the wrong one and\n * learning about it as a 403.\n */\nexport interface DiscoveryChallenge {\n accountId: string;\n /** Opaque and sealed \u2014 the client neither parses nor constructs this. */\n nonce: string;\n /** Epoch **milliseconds**, as MDMA reports it. Nonces live about two minutes. */\n expiresAtMs: number;\n}\n\n/**\n * The three header values a proven routing read carries.\n *\n * Intersected with `Record<string, string>` rather than declared as a plain\n * interface so it drops straight into a headers bag: an interface has no index\n * signature, so TypeScript refuses it where `Record<string, string>` is wanted\n * even though every field is a string.\n */\nexport type RoutingProofHeaders = Record<string, string> & {\n 'X-Calimero-Credential': string;\n 'X-Calimero-Nonce': string;\n 'X-Calimero-Signature': string;\n};\n\n/**\n * Sign a routing challenge with the device key.\n *\n * Returns base64, which is what the header carries and what MDMA's verifier\n * decodes \u2014 the one place this SDK does not spell a signature as hex, because\n * the wire format is not ours to choose.\n */\nexport async function signRoutingChallenge(\n nonce: string,\n deviceSecret: string,\n): Promise<string> {\n // Validate the secret the same way every other signing path does, so a\n // truncated or mis-cased key fails here rather than as a 403 from the cloud.\n fromHex(deviceSecret, 'deviceSecret', 32);\n const key = await importSigningKey(deviceSecret);\n\n const message = concat(\n ROUTING_PROOF_DOMAIN,\n new TextEncoder().encode(nonce),\n );\n\n const signature = new Uint8Array(\n await crypto.subtle.sign({ name: 'Ed25519' }, key, message),\n );\n\n return base64(signature);\n}\n\n/**\n * Build the headers for one routing read, from a challenge the cloud minted.\n *\n * Separated from the request so a caller that fetches its own challenge \u2014 a\n * test, or a client batching several reads against one nonce \u2014 can use the same\n * encoding rather than a second spelling of it.\n *\n * Takes either challenge kind: only the nonce is signed and both are signed\n * over the same domain, so one encoder serves both. What separates them is what\n * MDMA sealed inside, which is checked server-side.\n */\nexport async function routingProofHeaders(\n challenge: RoutingChallenge | DiscoveryChallenge,\n credential: RoutingCredential,\n): Promise<RoutingProofHeaders> {\n return {\n 'X-Calimero-Credential': credential.credential,\n 'X-Calimero-Nonce': challenge.nonce,\n 'X-Calimero-Signature': await signRoutingChallenge(\n challenge.nonce,\n credential.deviceSecret,\n ),\n };\n}\n\n/**\n * base64, the encoding the header carries.\n *\n * `btoa` is global in every browser and in Node since 16, and this package\n * requires Node 18 \u2014 so there is no runtime here that needs a `Buffer`\n * fallback, and adding one would pull a Node-only global into browser code.\n */\nfunction base64(bytes: Uint8Array): string {\n let binary = '';\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary);\n}\n", "/**\n * Calimero Cloud (MDMA) client.\n *\n * # Why the SDK knows about the cloud at all\n *\n * Delegated execution lets an account that holds one signing key write into a\n * context without running a node \u2014 but only through a *relay*, and a relay is\n * something the client has to be told about. A warrant names an executor\n * account, so a client that cannot discover which relay serves its namespace,\n * and which account that relay writes as, cannot mint a usable warrant at all.\n *\n * For a self-hosted namespace the operator hands those two facts over out of\n * band. For the hosted tier the cloud is the directory: the user signs in, the\n * cloud knows which namespaces they own and which attested TEE nodes are\n * assigned to each, and it answers with the relay's URL and executor account.\n * That is what this client is for \u2014 it is the discovery half of\n * {@link RelayClient}, not a second way to talk to a node.\n *\n * # What it deliberately is not\n *\n * It never sees a warrant, a device secret, or an intent. Discovery goes\n * through the cloud; the write goes straight from the client to the relay. A\n * cloud that proxied intents would read every method and argument its users\n * ever sent, and would be a throughput bottleneck on a path that has no reason\n * to involve it.\n */\n\nimport {\n accountRootFromSecret,\n signAccountLink,\n signAccountLogin,\n} from '../account/index.js';\nimport { HTTPError } from '../http-client/web-client.js';\nimport {\n routingProofHeaders,\n type DiscoveryChallenge,\n type RoutingChallenge,\n type RoutingCredential,\n} from './routing-proof.js';\n\n/** Where the hosted cloud lives when a caller names no other. */\nconst DEFAULT_CLOUD_BASE_URL = 'https://cloud.calimero.network';\n\nexport interface CloudClientConfig {\n cloudBaseUrl?: string;\n /**\n * An MDMA session token to start out authenticated with \u2014 from a previous\n * {@link CloudClient.signInWithGoogle}, persisted by the app.\n *\n * Sessions are 7-day JWTs with a rolling refresh, so a stored one is usually\n * still live and re-running the Google flow on every launch is avoidable.\n */\n sessionToken?: string;\n /**\n * Called whenever the session token changes \u2014 a sign-in, an explicit\n * refresh, or a rolling refresh the server performed mid-request. Persist it\n * here; errors thrown are swallowed so a storage failure cannot mask the\n * response the caller is waiting for.\n */\n onSession?: (session: CloudSession | null) => void | Promise<void>;\n /** Injected for tests and non-browser runtimes. Defaults to global `fetch`. */\n fetch?: typeof fetch;\n /** Per-request timeout. Defaults to 10s, as the node clients do. */\n timeoutMs?: number;\n /**\n * The device credential to prove an account with on the routing read.\n *\n * Nothing to do with {@link sessionToken}: a cloud session says who is signed\n * in, this says which account holds the key. A joiner has the second and\n * cannot have the first \u2014 see `routing-proof.ts` for why that is not an\n * oversight.\n *\n * Omitting it leaves {@link CloudClient.getNamespaceRouting} anonymous, which\n * the cloud still answers while `require_routing_proof` is off. Supply it and\n * the read is attributed; the cloud verifies a supplied proof either way, so\n * a wrong one surfaces immediately rather than on the day the flag flips.\n */\n routingCredential?: RoutingCredential;\n}\n\n/** Who is signed in, and until when. */\nexport interface CloudSession {\n sessionToken: string;\n /** Epoch **seconds**, as MDMA reports it \u2014 not milliseconds. */\n expiresAt: number;\n user: { email: string; name?: string; picture?: string };\n}\n\n/** One namespace the signed-in user owns. */\nexport interface CloudNamespace {\n namespaceId: string;\n /** Always equal to `namespaceId`; MDMA keeps it for wire compatibility. */\n groupId: string;\n /** `'enabled' | 'disabled' | 'none'` \u2014 `'none'` means HA was never enabled. */\n haStatus: string;\n haEnabledAt?: string | null;\n contexts: string[];\n fleetReplicas?: { current?: number; limit?: number } | unknown;\n}\n\n/**\n * One attested node the cloud has assigned to a namespace, described as a\n * relay.\n *\n * `authorshipReady` is the field that decides whether a write can happen, and\n * it is reported rather than assumed because it is not the cloud's to grant:\n * `CAN_AUTHOR_ON_BEHALF` is a governance capability, set by an admin of the\n * namespace, and the cloud only relays what the node observed. A relay that is\n * `active` with `authorshipReady: false` is a healthy node waiting for a grant.\n */\nexport interface CloudRelay {\n peerId: string;\n /** Base URL to present intents to, or `null` when the cloud knows none yet. */\n relayUrl: string | null;\n /** The account a warrant for this relay must name as its `executor`, hex. */\n executorAccount: string | null;\n /** `'assigned'` (admitted pending confirm) or `'active'`. */\n status: string;\n /** Whether this relay holds `CAN_AUTHOR_ON_BEHALF` on the namespace. */\n authorshipReady: boolean;\n lastSeenAt?: string | null;\n confirmedAt?: string | null;\n}\n\n/**\n * One node serving a namespace, as a caller with no cloud account sees it.\n *\n * The public counterpart to {@link CloudRelay}: same fleet rows, but keyed on a\n * namespace id anyone holding an invitation already has, and answering both\n * things a keyholder needs from a node rather than one of them.\n */\n/**\n * One relay that serves an account, as the bootstrap read reports it.\n *\n * `fresh` and a null `relayUrl` are reported rather than filtered, because\n * \"your relay is down\", \"we have no address for it\" and \"you have no relay\"\n * need different actions from a caller and an empty list erases the\n * distinction. A caller looking for somewhere to talk to wants\n * `fresh && relayUrl`; one diagnosing wants the rest.\n */\nexport interface CloudAccountRelay {\n peerId: string;\n relayUrl: string | null;\n fresh: boolean;\n}\n\nexport interface CloudNamespaceNode {\n peerId: string;\n /**\n * The node's own account id, hex.\n *\n * Intersect this against the invitation's signed `admitters` list before\n * using the node. That list is a MINT-TIME SNAPSHOT, so a node assigned after\n * an invitation was created is live, healthy, listed here, and still refused\n * with 403 \u2014 the cloud cannot know which invitation you hold.\n */\n account: string | null;\n /** Base URL, or `null` when the cloud knows none yet. */\n relayUrl: string | null;\n /** Ready-made admission URL, so a caller never rebuilds the path. */\n admitUrl: string | null;\n /** `'assigned'` (admitted pending confirm) or `'active'`. */\n status: string;\n /** Whether the node's heartbeat for this namespace is recent. */\n fresh: boolean;\n /**\n * Whether a signed join can be presented here now.\n *\n * Deliberately independent of {@link CloudNamespaceNode.authorshipReady}:\n * relaying a join the joiner already signed is not authoring on anyone's\n * behalf, so a node with no grant admits perfectly well.\n */\n canAdmit: boolean;\n /** Whether the node holds `CAN_AUTHOR_ON_BEHALF` on the namespace root group. */\n authorshipReady: boolean;\n /**\n * Whether a delegated write can be posted here now \u2014 a URL, an account, a\n * fresh heartbeat and the grant.\n *\n * Namespace-level. A context in a RESTRICTED SUBGROUP is governed by that\n * subgroup, so per-context routing remains the exact answer if a write is\n * refused despite this being true.\n */\n canExecute: boolean;\n}\n\n/** What {@link CloudClient.getNamespaceRouting} reports. */\nexport interface CloudNamespaceRouting {\n namespaceId: string;\n /** Usable nodes first, then a stable order, so a polling caller sees no shuffle. */\n nodes: CloudNamespaceNode[];\n /** Whether any listed node can take a join now. */\n servable: boolean;\n /** Whether any listed node can take a delegated write now. */\n writable: boolean;\n}\n\n/** One namespace of the caller's that a machine is serving. */\nexport interface CloudMachineNamespace {\n namespaceId: string;\n /** `'assigned'` (admitted pending confirm) or `'active'`. */\n status: string;\n /** Whether the machine holds `CAN_AUTHOR_ON_BEHALF` on this namespace. */\n authorshipReady: boolean;\n /**\n * Whether the machine's heartbeat for this namespace is recent.\n *\n * The same freshness rule the per-plan fleet cap uses, so a replica that\n * stopped polling reads as stale here rather than continuing to look\n * healthy \u2014 otherwise \"my writes stopped working\" has no visible cause.\n */\n fresh: boolean;\n confirmedAt?: string | null;\n lastSeenAt?: string | null;\n}\n\n/**\n * One attested machine serving this account.\n *\n * A narrow view on purpose: the zone, machine type, instance name, public IP\n * and KMS ids are operator data with no client use, and a cloud user is not an\n * operator.\n */\nexport interface CloudMachine {\n peerId: string;\n relayUrl: string | null;\n executorAccount: string | null;\n /**\n * Whether this machine can write for the caller *somewhere* \u2014 a URL, an\n * executor account, and the grant on at least one namespace.\n *\n * Folded here so a client does not re-derive the three-way precondition and\n * get a refusal it cannot explain.\n */\n canExecute: boolean;\n /** Only the caller's own namespaces, even on a machine shared with others. */\n namespaces: CloudMachineNamespace[];\n}\n\n/** A Calimero account linked to this cloud login. */\nexport interface CloudLinkedAccount {\n /** The account id, 64 hex. */\n accountId: string;\n linkedAt?: string | null;\n /**\n * Whether a recovery envelope has been stored for this account, so a UI can\n * say \"recoverable\" without fetching the blob.\n */\n hasRecoveryEnvelope: boolean;\n}\n\n/** What {@link CloudClient.getMyAccounts} reports. */\nexport interface CloudLinkedAccounts {\n accounts: CloudLinkedAccount[];\n /** How many the caller's plan allows. `null` means no limit. */\n limit: number | null;\n}\n\n/** A single-use challenge to sign with an account root. */\nexport interface AccountLinkChallenge {\n /** Sign this verbatim \u2014 it is an opaque sealed string, not a decodable id. */\n nonce: string;\n /** When it stops being accepted, epoch milliseconds. */\n expiresAtMs: number | null;\n}\n\n/** A proof assembled by whoever holds the root, for {@link CloudClient.submitAccountLink}. */\nexport interface AccountLinkProof {\n /** The account being claimed, 64 hex. */\n accountId: string;\n /** The root that names it, 64 hex. */\n rootPublicKey: string;\n /** The challenge that was signed. */\n nonce: string;\n /** `signAccountLink`'s output \u2014 standard base64. */\n signature: string;\n}\n\n/** Where to send someone so a cloud login can consent to linking an account. */\nexport interface AccountLinkHandoff {\n /** The cloud portal URL to open \u2014 a new tab, a popup, or the system browser. */\n url: string;\n /** The account the consent will be for, echoed so a caller can show it. */\n accountId: string;\n}\n\n/** What comes back on the callback: a grant, a refusal, or neither. */\nexport interface AccountLinkCallback {\n /** The grant to spend with {@link CloudClient.linkAccountWithGrant}. */\n grant?: string;\n /** The account the grant is for, as the portal echoed it. */\n accountId?: string;\n /** `'denied'` when the person cancelled. Absent on success. */\n error?: string;\n}\n\n/** A single-use challenge for an account root to sign itself in with. */\nexport interface AccountLoginChallenge {\n /** Sign this verbatim. Opaque and sealed by the cloud; nonces live ~2 minutes. */\n nonce: string;\n /** When it stops being accepted, epoch milliseconds. */\n expiresAtMs: number;\n}\n\n/** A proof assembled by whoever holds the root, for {@link CloudClient.submitAccountLogin}. */\nexport interface AccountLoginProof {\n /**\n * The root that names the account, 64 hex.\n *\n * No `accountId` field, unlike {@link AccountLinkProof}: the cloud derives\n * the account from this key rather than accepting one alongside it, so there\n * is nothing a caller could state that the signature would then contradict.\n */\n rootPublicKey: string;\n /** The challenge that was signed. */\n nonce: string;\n /** `signAccountLogin`'s output \u2014 standard base64. */\n signature: string;\n}\n\n/** The outcome of {@link CloudClient.linkAccount}. */\nexport interface CloudAccountLink {\n accountId: string;\n linkedAt?: string | null;\n /**\n * True when this account was already linked to this same login, which the\n * cloud treats as a 200 no-op rather than an error. A caller re-running\n * onboarding gets success, not a confusing failure.\n */\n alreadyLinked: boolean;\n}\n\nexport interface EnableHAOptions {\n groupId: string;\n contextId: string;\n redirectUrl?: string;\n}\n\nexport interface DisableHAOptions {\n groupId: string;\n contextId: string;\n redirectUrl?: string;\n}\n\n/** Response headers MDMA uses to hand back a rolling-refreshed session. */\nconst REFRESH_HEADER = 'x-mdma-session-refresh';\nconst REFRESH_EXPIRES_HEADER = 'x-mdma-session-expires';\n\nexport class CloudClient {\n private baseUrl: string;\n private session: CloudSession | null = null;\n private readonly onSession?: CloudClientConfig['onSession'];\n private readonly fetchImpl: typeof fetch;\n private readonly timeoutMs: number;\n private readonly routingCredential?: RoutingCredential;\n\n constructor(config: CloudClientConfig = {}) {\n this.baseUrl = (config.cloudBaseUrl || DEFAULT_CLOUD_BASE_URL).replace(/\\/+$/, '');\n this.onSession = config.onSession;\n // Bound through an arrow function: an unbound `globalThis.fetch` throws\n // \"Illegal invocation\" in a browser, the same reason the node HTTP clients\n // wrap it.\n const injected = config.fetch;\n this.fetchImpl = injected\n ? (input: RequestInfo | URL, init?: RequestInit) => injected(input, init)\n : (input: RequestInfo | URL, init?: RequestInit) => globalThis.fetch(input, init);\n this.timeoutMs = config.timeoutMs ?? 10_000;\n this.routingCredential = config.routingCredential;\n\n if (config.sessionToken) {\n // Expiry unknown until the server tells us; 0 means \"do not reason about\n // it locally\", never \"expired\" \u2014 the server is the only judge and it\n // answers 401 when it disagrees.\n this.session = {\n sessionToken: config.sessionToken,\n expiresAt: 0,\n user: { email: '' },\n };\n }\n }\n\n /** The current session, or `null` when nobody is signed in. */\n getSession(): CloudSession | null {\n return this.session;\n }\n\n /** Whether a session token is held. Says nothing about the server agreeing. */\n isSignedIn(): boolean {\n return this.session !== null;\n }\n\n /**\n * Exchange a Google OAuth2 ID token for an MDMA session.\n *\n * The app owns the Google flow (One Tap, an auth-code popup, a native\n * browser) and hands the resulting ID token here once. Everything after this\n * uses the returned session token, so the Google credential does not have to\n * be kept or re-minted per call.\n */\n async signInWithGoogle(idToken: string): Promise<CloudSession> {\n const body = await this.request<{\n session_token: string;\n expires_at: number;\n user: { email: string; name?: string; picture?: string };\n }>('POST', '/api/auth/google', { body: { id_token: idToken }, anonymous: true });\n return this.adoptSession(body);\n }\n\n /**\n * Sign in as a Calimero account, proving it with its root key.\n *\n * The password-free counterpart to {@link signInWithGoogle}: no Google, no\n * password, no server-held secret \u2014 the cloud mints a challenge, the account\n * root signs it, and the session that comes back is the same MDMA session\n * token every other cloud route already accepts.\n *\n * Doing this **once** is what makes an account's later device proofs mean\n * something. A routing read shows that the caller holds a device the root\n * certified, but certificates are public, so it can never show that anyone\n * held the root; the cloud records this claim and from then on knows the\n * account behind those reads was claimed by its owner. The claim is recorded\n * even when the session is refused.\n *\n * @param rootSecret the account root's signing secret, 64 hex. It never\n * leaves this process \u2014 only a signature over the cloud's nonce travels.\n *\n * A `403` means either the signature did not verify, or the account is not\n * linked to a cloud login: ownership alone says who you are, and the link\n * says what you are entitled to. Anyone can mint a root offline, so a session\n * on the proof alone would authenticate perfectly and authorize nothing. The\n * ownership claim is recorded either way, so linking later needs no re-proof.\n */\n async signInWithAccount(rootSecret: string): Promise<CloudSession> {\n const root = await accountRootFromSecret(rootSecret);\n const { nonce } = await this.getAccountLoginChallenge();\n return this.submitAccountLogin({\n rootPublicKey: root.publicKey,\n nonce,\n signature: await signAccountLogin({ rootSecret, nonce }),\n });\n }\n\n /**\n * Mint a challenge for an account root to sign.\n *\n * Public, and deliberately so: the caller has no session \u2014 obtaining one is\n * what this exchange does. The nonce is not a capability, it is the thing a\n * capability gets demonstrated against, and demonstrating it needs the root.\n *\n * Split out of {@link signInWithAccount} for the case that call cannot serve:\n * a root held by a hardware key, a desktop app or an air-gapped machine,\n * which signs without the secret ever reaching this process.\n */\n async getAccountLoginChallenge(): Promise<AccountLoginChallenge> {\n const body = await this.request<{ nonce: string; expires_at_ms: number }>(\n 'GET',\n '/api/auth/account/challenge',\n { anonymous: true },\n );\n return { nonce: body.nonce, expiresAtMs: body.expires_at_ms };\n }\n\n /** Post a proof assembled elsewhere. The other half of {@link getAccountLoginChallenge}. */\n async submitAccountLogin(proof: AccountLoginProof): Promise<CloudSession> {\n const body = await this.request<{\n session_token: string;\n expires_at: number;\n user: { email: string; name?: string; picture?: string };\n }>('POST', '/api/auth/account', {\n body: {\n root_public_key: proof.rootPublicKey,\n nonce: proof.nonce,\n signature: proof.signature,\n },\n anonymous: true,\n });\n return this.adoptSession(body);\n }\n\n /**\n * Refresh the session token explicitly.\n *\n * Rarely needed: MDMA rolling-refreshes on ordinary authenticated calls and\n * this client adopts the replacement automatically. Use it when an app wants\n * a fresh token before going idle rather than on the next request.\n */\n async refreshSession(): Promise<CloudSession> {\n const body = await this.request<{\n session_token: string;\n expires_at: number;\n user: { email: string; name?: string; picture?: string };\n }>('POST', '/api/auth/refresh');\n return this.adoptSession(body);\n }\n\n /**\n * Revoke this session server-side and forget it locally.\n *\n * Local state is cleared even if the server call fails: the caller asked to\n * be signed out, and keeping a token they believe is gone is the worse of the\n * two failures.\n */\n async signOut(): Promise<void> {\n try {\n if (this.session) await this.request<void>('POST', '/api/auth/logout', { parse: 'none' });\n } finally {\n this.session = null;\n void this.notifySession(null);\n }\n }\n\n /** Namespaces the signed-in user owns. */\n async getMyNamespaces(): Promise<CloudNamespace[]> {\n const rows = await this.request<Array<Record<string, unknown>>>(\n 'GET',\n '/api/cloud/me/namespaces',\n );\n return rows.map((row) => ({\n namespaceId: String(row.namespace_id ?? row.group_id ?? ''),\n groupId: String(row.group_id ?? row.namespace_id ?? ''),\n haStatus: String(row.ha_status ?? 'none'),\n haEnabledAt: (row.ha_enabled_at as string | null | undefined) ?? null,\n contexts: Array.isArray(row.contexts) ? (row.contexts as string[]) : [],\n fleetReplicas: row.fleet_replicas,\n }));\n }\n\n /**\n * The relays serving one namespace: where to present an intent, and as whom.\n *\n * Everything a client needs in order to mint a warrant it can actually\n * spend. Filter on `authorshipReady` before signing \u2014 a warrant presented to\n * a relay with no grant is refused *after* the author has burned a nonce from\n * its monotonic sequence on it.\n */\n async getNamespaceRelays(namespaceId: string): Promise<CloudRelay[]> {\n const body = await this.request<{ relays?: Array<Record<string, unknown>> }>(\n 'GET',\n `/api/cloud/me/namespaces/${encodeURIComponent(namespaceId)}/relays`,\n );\n return (body.relays ?? []).map((row) => ({\n peerId: String(row.peer_id ?? ''),\n relayUrl: (row.relay_url as string | null | undefined) ?? null,\n executorAccount: (row.executor_account as string | null | undefined) ?? null,\n status: String(row.status ?? ''),\n authorshipReady: row.authorship_ready === true,\n lastSeenAt: (row.last_seen_at as string | null | undefined) ?? null,\n confirmedAt: (row.confirmed_at as string | null | undefined) ?? null,\n }));\n }\n\n /**\n * The first relay for `namespaceId` that can actually run an intent, or\n * `null`.\n *\n * \"Can actually run one\" is three conditions, and a client that checks fewer\n * gets a refusal it cannot explain: the cloud must know a URL, the relay must\n * have reported the executor account a warrant has to name, and the grant\n * must be in place.\n */\n async findExecutingRelay(namespaceId: string): Promise<CloudRelay | null> {\n const relays = await this.getNamespaceRelays(namespaceId);\n return (\n relays.find((r) => r.authorshipReady && r.relayUrl !== null && r.executorAccount !== null) ??\n null\n );\n }\n\n /**\n * Where to reach a namespace without owning it: one read, both verbs.\n *\n * **Anonymous.** A joiner is by construction not the namespace owner and holds\n * no cloud session \u2014 needing one is exactly what this path exists to avoid.\n * Discovery is not authorization: merod refuses an `/admit` call unless the\n * relaying node's account is on the invitation's signed `admitters` list, and\n * refuses an intent unless the warrant is genuinely the member's, so naming a\n * node grants the caller nothing it did not already have.\n *\n * A keyholder resolves a namespace to a node ONCE and then admits, reads and\n * posts intents against that same node, which is why admission and writability\n * are answered together rather than across two endpoints \u2014 the per-context\n * read needs a context id a joiner does not yet have.\n *\n * An empty `nodes` list is a state, not an error: a namespace with no fleet\n * assignment has no cloud node, and the joiner must use one of the\n * invitation's other admitters \u2014 another admin's node, or a self-hosted peer.\n */\n async getNamespaceRouting(namespaceId: string): Promise<CloudNamespaceRouting> {\n const body = await this.request<{\n namespace_id?: unknown;\n admitters?: Array<Record<string, unknown>>;\n servable?: unknown;\n writable?: unknown;\n }>('GET', `/api/cloud/namespaces/${encodeURIComponent(namespaceId)}/admitters`, {\n anonymous: true,\n headers: await this.routingProof(namespaceId),\n });\n return {\n namespaceId: String(body.namespace_id ?? namespaceId),\n nodes: (body.admitters ?? []).map((row) => ({\n peerId: String(row.peer_id ?? ''),\n account: (row.account as string | null | undefined) ?? null,\n relayUrl: (row.relay_url as string | null | undefined) ?? null,\n admitUrl: (row.admit_url as string | null | undefined) ?? null,\n status: String(row.status ?? ''),\n fresh: row.fresh === true,\n canAdmit: row.can_admit === true,\n authorshipReady: row.authorship_ready === true,\n canExecute: row.can_execute === true,\n })),\n servable: body.servable === true,\n writable: body.writable === true,\n };\n }\n\n /**\n * Ask the cloud for a challenge to prove an account against.\n *\n * Public and unauthenticated on purpose, and safe to be: the nonce is sealed\n * and bound to this one namespace, so it is not a capability \u2014 it is the\n * thing a capability gets demonstrated against. A caller with no credential\n * gains nothing by holding one.\n *\n * {@link getNamespaceRouting} calls this for you when the client was\n * constructed with a `routingCredential`. Call it directly only to sign a\n * challenge somewhere this client cannot reach the key.\n */\n async getRoutingChallenge(namespaceId: string): Promise<RoutingChallenge> {\n const body = await this.request<{\n namespace_id?: unknown;\n nonce?: unknown;\n expires_at_ms?: unknown;\n }>('GET', `/api/cloud/namespaces/${encodeURIComponent(namespaceId)}/challenge`, {\n anonymous: true,\n });\n return {\n namespaceId: String(body.namespace_id ?? namespaceId),\n nonce: String(body.nonce ?? ''),\n expiresAtMs: Number(body.expires_at_ms ?? 0),\n };\n }\n\n /**\n * The proof headers for one routing read, or nothing when no credential was\n * configured.\n *\n * A challenge per read rather than a cached one: they expire in about two\n * minutes and are single-namespace, so caching would trade one cheap request\n * for a class of expiry bug that only shows up on a slow client.\n */\n private async routingProof(\n namespaceId: string,\n ): Promise<Record<string, string> | undefined> {\n if (!this.routingCredential) return undefined;\n const challenge = await this.getRoutingChallenge(namespaceId);\n return routingProofHeaders(challenge, this.routingCredential);\n }\n\n /**\n * Ask the cloud for a challenge bound to one ACCOUNT.\n *\n * Public and unauthenticated like {@link getRoutingChallenge}: an account id\n * is `H(genesis(root_pk))` and travels in the clear in every device-link op,\n * so naming one here reveals nothing and authorises nothing. MDMA re-derives\n * the account from the certificate and refuses a mismatch.\n */\n async getAccountRelaysChallenge(accountId: string): Promise<DiscoveryChallenge> {\n const body = await this.request<{\n account_id?: unknown;\n nonce?: unknown;\n expires_at_ms?: unknown;\n }>('GET', `/api/cloud/accounts/${encodeURIComponent(accountId)}/challenge`, {\n anonymous: true,\n });\n return {\n accountId: String(body.account_id ?? accountId),\n nonce: String(body.nonce ?? ''),\n expiresAtMs: Number(body.expires_at_ms ?? 0),\n };\n }\n\n /**\n * Which relays serve this account \u2014 the bootstrap, and nothing more.\n *\n * A device holding a certificate can resolve a namespace it can *name*\n * ({@link getNamespaceRouting}) and can ask a node it can *reach* what its\n * account belongs to. It starts with neither. This closes that one gap.\n *\n * **It does not return namespaces, deliberately.** The node answers that from\n * the governance DAG, scoped to the calling account: log in at one of these\n * relays with an account proof and read its namespace listing. A cloud-side\n * copy would be a second source of truth built from rows that linger for an\n * account which left a namespace.\n *\n * So the sequence is: this \u2192 log in at a relay \u2192 ask it what you are in.\n *\n * Proven with the **device certificate**, not a cloud session, so it needs a\n * `routingCredential` and throws without one rather than returning an empty\n * list \u2014 \"you configured no credential\" and \"you have no relays\" are\n * different answers and must not look alike.\n */\n async getAccountRelays(accountId: string): Promise<CloudAccountRelay[]> {\n if (!this.routingCredential) {\n throw new Error(\n 'getAccountRelays needs a routingCredential: construct the client with the device ' +\n 'certificate and its signing secret, since this read is proven by the device, not by a session.',\n );\n }\n const challenge = await this.getAccountRelaysChallenge(accountId);\n const body = await this.request<{\n account_id?: unknown;\n relays?: Array<Record<string, unknown>>;\n }>('GET', `/api/cloud/accounts/${encodeURIComponent(accountId)}/relays`, {\n anonymous: true,\n headers: await routingProofHeaders(challenge, this.routingCredential),\n });\n return (body.relays ?? []).map((row) => ({\n peerId: String(row.peer_id ?? ''),\n relayUrl: (row.relay_url as string | null | undefined) ?? null,\n fresh: row.fresh === true,\n }));\n }\n\n /**\n * The node to send a signed join to, chosen from those an invitation names.\n *\n * Pass the invitation's `admitters` and the intersection is done here, because\n * skipping it is the failure this method exists to prevent: the cloud lists\n * every node assigned to the namespace, while `admitters` is a snapshot signed\n * when the invitation was minted. A node added afterwards looks perfect in\n * every field here and answers 403.\n *\n * Omit `admitters` only for an invitation that names none \u2014 an empty list on\n * the wire authorises any node to admit.\n */\n async findAdmitter(\n namespaceId: string,\n admitters?: readonly string[],\n ): Promise<CloudNamespaceNode | null> {\n const { nodes } = await this.getNamespaceRouting(namespaceId);\n const allowed =\n admitters && admitters.length > 0 ? new Set(admitters.map((a) => a.toLowerCase())) : null;\n return (\n nodes.find(\n (n) =>\n n.canAdmit &&\n (allowed === null || (n.account !== null && allowed.has(n.account.toLowerCase()))),\n ) ?? null\n );\n }\n\n /**\n * Every attested machine currently serving something this account owns.\n *\n * The account-wide view of the same rows {@link getNamespaceRelays} returns\n * per namespace, keyed by machine \u2014 so a UI can answer \"what is running for\n * me?\" in one call. `canExecute` folds the three-way precondition, so a\n * caller does not re-derive it.\n *\n * Only the caller's own namespaces appear on each machine. A plan's\n * `usersPerMachine` is greater than one, so a fleet node routinely serves\n * several accounts at once.\n */\n async getMyMachines(): Promise<CloudMachine[]> {\n const body = await this.request<{ machines?: Array<Record<string, unknown>> }>(\n 'GET',\n '/api/cloud/me/machines',\n );\n return (body.machines ?? []).map((row) => ({\n peerId: String(row.peer_id ?? ''),\n relayUrl: (row.relay_url as string | null | undefined) ?? null,\n executorAccount: (row.executor_account as string | null | undefined) ?? null,\n canExecute: row.can_execute === true,\n namespaces: (Array.isArray(row.namespaces) ? row.namespaces : []).map((n) => {\n const ns = n as Record<string, unknown>;\n return {\n namespaceId: String(ns.namespace_id ?? ''),\n status: String(ns.status ?? ''),\n authorshipReady: ns.authorship_ready === true,\n fresh: ns.fresh === true,\n confirmedAt: (ns.confirmed_at as string | null | undefined) ?? null,\n lastSeenAt: (ns.last_seen_at as string | null | undefined) ?? null,\n };\n }),\n }));\n }\n\n /**\n * Claim a namespace under this account, proving ownership.\n *\n * The cloud has no other way to learn a namespace exists \u2014 namespaces are\n * created on a node, and there is deliberately no \"create namespace\" in the\n * cloud. `ownershipProof` comes from\n * `admin.issueNamespaceOwnershipProof(namespaceId, \u2026)` on a node that is a\n * **direct admin** of that namespace; merod refuses to issue one otherwise.\n *\n * Pass merod's response object straight through. The cloud accepts core's\n * `signerPublicKey`/`signedPayload` casing as well as snake_case, so there is\n * no re-keying step \u2014 and getting that wrong used to surface as a `422`\n * naming three missing fields.\n *\n * Idempotent for the same account. A namespace already claimed by someone\n * else is a `409`; a replayed proof nonce is a `403`.\n */\n async claimNamespace(namespaceId: string, ownershipProof: unknown): Promise<void> {\n await this.request<unknown>('POST', '/api/cloud/namespaces/claim', {\n body: { namespace_id: namespaceId, ownership_proof: ownershipProof },\n });\n }\n\n /**\n * Ask the fleet to host a claimed namespace \u2014 the step that gets a relay\n * assigned.\n *\n * Carries no proof: {@link claimNamespace} already established ownership,\n * and this is the separate \"now host it\" half. Idempotent.\n *\n * `admitterAddrs` is where the owner's own node can be dialed \u2014 take it from\n * that node's network status with {@link admitterAddrsFromNetworkStatus}.\n * The first fleet node placed on a namespace has nobody but the owner to ask\n * for admission, and without these it can only broadcast and hope the\n * owner's node \u2014 often a laptop behind a relay \u2014 hears it. They are a dial\n * hint, not a grant: the owner's node admits by its own policy. Omit them to\n * leave whatever the cloud already holds; pass them again when the node's\n * relay address changes, and they replace the stored ones.\n *\n * Not to be confused with {@link enableHA}, which opens the cloud's web flow\n * in a browser tab for a user to complete by hand. This is the API call.\n */\n async enableNamespaceHa(\n namespaceId: string,\n options: { admitterAddrs?: readonly string[] } = {},\n ): Promise<unknown> {\n const body =\n options.admitterAddrs === undefined\n ? {}\n : { admitter_addrs: [...options.admitterAddrs] };\n return this.request<unknown>(\n 'POST',\n `/api/cloud/namespaces/${encodeURIComponent(namespaceId)}/enable-ha`,\n { body },\n );\n }\n\n /**\n * Stop hosting a claimed namespace.\n *\n * The fleet's own reconcile loop does the rest: each assigned node notices\n * the namespace is no longer assigned to it and **self-leaves**, which makes\n * core evict it and purge its local data and keys. So this is not just a\n * billing flip.\n */\n async disableNamespaceHa(namespaceId: string): Promise<unknown> {\n return this.request<unknown>(\n 'POST',\n `/api/cloud/namespaces/${encodeURIComponent(namespaceId)}/disable-ha`,\n { body: {} },\n );\n }\n\n /**\n * The node assigned to the signed-in user, or `null`.\n *\n * @deprecated Use {@link getMyMachines}. This reads the cloud's retired\n * `NodeAssignment` ledger, which nothing has written since the v1\n * retirement, so it answers `null` for every namespace-native account \u2014\n * which reads as \"you have no machine\" when the truth is \"that is no longer\n * how a machine is assigned\".\n */\n async getMyNode(): Promise<Record<string, unknown> | null> {\n const body = await this.request<{ node: Record<string, unknown> | null }>(\n 'GET',\n '/api/cloud/me/node',\n );\n return body.node ?? null;\n }\n\n enableHA(options: EnableHAOptions): void {\n const params = new URLSearchParams({\n group_id: options.groupId,\n context_id: options.contextId,\n });\n if (options.redirectUrl) {\n params.set('redirect_url', options.redirectUrl);\n }\n window.open(`${this.baseUrl}/enable-ha?${params.toString()}`);\n }\n\n disableHA(options: DisableHAOptions): void {\n const params = new URLSearchParams({\n group_id: options.groupId,\n context_id: options.contextId,\n });\n if (options.redirectUrl) {\n params.set('redirect_url', options.redirectUrl);\n }\n window.open(`${this.baseUrl}/disable-ha?${params.toString()}`);\n }\n\n /** Record a freshly-issued session and tell the app to persist it. */\n /**\n * Link a Calimero account to this cloud login, proving possession of its root.\n *\n * Two hops, folded into one call because they are useless apart: fetch a\n * single-use challenge, then sign it with the account root and post the proof.\n * The secret never leaves this process \u2014 only a signature over a nonce the\n * cloud minted does.\n *\n * This is the binding the cloud bills against. A relay does not need it to run\n * an intent; the cloud needs it to know whose plan a hosted node is spending,\n * and to sign that account back in later.\n *\n * @param rootSecret the account root's signing secret, 64 hex \u2014 from\n * {@link generateAccountRoot}, from a restored recovery phrase, or from a\n * desktop app or hardware key that already holds one.\n *\n * Idempotent for the same login. An account already linked to a *different*\n * cloud login is a `409`; a replayed or expired challenge is a `403`; and\n * exceeding the plan's account limit is a `402`.\n */\n async linkAccount(rootSecret: string): Promise<CloudAccountLink> {\n const root = await accountRootFromSecret(rootSecret);\n const { nonce } = await this.getAccountLinkChallenge();\n return this.submitAccountLink({\n accountId: root.accountId,\n rootPublicKey: root.publicKey,\n nonce,\n signature: await signAccountLink({ rootSecret, nonce }),\n });\n }\n\n /**\n * Mint a single-use challenge for an account root to sign.\n *\n * The split half of {@link linkAccount}, for the case that call cannot serve:\n * a root held by a desktop app, a hardware key or an air-gapped machine, which\n * signs without the secret ever reaching this process. Fetch the challenge\n * here, have the holder sign it, then hand the proof to\n * {@link submitAccountLink}.\n *\n * Bound to the signed-in login and short-lived, so it is not a standing\n * capability if it leaks \u2014 but it IS single-use, so a challenge fetched and\n * abandoned is spent.\n */\n async getAccountLinkChallenge(): Promise<AccountLinkChallenge> {\n const body = await this.request<{\n nonce: string;\n expires_at_ms?: number | null;\n }>('GET', '/api/cloud/me/accounts/challenge');\n return { nonce: body.nonce, expiresAtMs: body.expires_at_ms ?? null };\n }\n\n /**\n * Post a proof assembled elsewhere. The other half of\n * {@link getAccountLinkChallenge}.\n *\n * `accountId` must be the account `rootPublicKey` names \u2014 the cloud refuses a\n * valid signature by a key that names a different account, or a caller could\n * prove one root and link somebody else's account. Derive both from the same\n * key rather than letting a user type either.\n */\n async submitAccountLink(proof: AccountLinkProof): Promise<CloudAccountLink> {\n const body = await this.request<Record<string, unknown>>(\n 'POST',\n '/api/cloud/me/accounts',\n {\n body: {\n account_id: proof.accountId,\n root_public_key: proof.rootPublicKey,\n nonce: proof.nonce,\n signature: proof.signature,\n },\n },\n );\n return {\n accountId: String(body.account_id ?? proof.accountId),\n linkedAt: (body.linked_at as string | null | undefined) ?? null,\n alreadyLinked: body.already_linked === true,\n };\n }\n\n /**\n * Where to send someone to authorise linking this account, and what to expect\n * back. Pairs with {@link readAccountLinkCallback} and\n * {@link linkAccountWithGrant}.\n *\n * This exists because a browser app is in a bind: it holds an account root and\n * can never hold a *first* cloud session, since only Google issues one. So the\n * person goes to the cloud, where they are signed in, and consents there. What\n * comes back is a grant \u2014 consent to link this one account, spendable only by\n * whoever can sign with its root.\n *\n * @param callbackUrl where the portal sends the person back. Must be an origin\n * the cloud allows; unlisted ones are refused when the grant is minted, not\n * here. The grant arrives in the URL **fragment**, which browsers do not\n * send to servers.\n *\n * Static, so it needs no client and no session: there is nothing to\n * authenticate as yet.\n */\n static accountLinkHandoff(options: {\n /** The cloud portal's origin, e.g. `https://cloud.calimero.network`. */\n portalUrl: string;\n /** The account to be linked, 64 hex \u2014 derive it, never let a user type it. */\n accountId: string;\n /** Where to come back to. */\n callbackUrl: string;\n }): AccountLinkHandoff {\n const portal = options.portalUrl.replace(/\\/+$/, '');\n const query = new URLSearchParams({\n 'link-account': options.accountId,\n 'callback-url': options.callbackUrl,\n });\n return { url: `${portal}/?${query.toString()}`, accountId: options.accountId };\n }\n\n /**\n * Read the portal's answer out of a callback URL's fragment.\n *\n * Returns `{}` when there is nothing to read, so it is safe to call on every\n * load \u2014 which is how a redirected app finds out it has been sent back.\n *\n * @param href defaults to the current location, when there is one.\n */\n static readAccountLinkCallback(href?: string): AccountLinkCallback {\n const source = href ?? (typeof window === 'undefined' ? '' : window.location.href);\n if (!source) return {};\n let hash: string;\n try {\n hash = new URL(source).hash;\n } catch {\n return {};\n }\n const params = new URLSearchParams(hash.replace(/^#/, ''));\n const grant = params.get('grant');\n const accountId = params.get('account');\n const error = params.get('error');\n const out: AccountLinkCallback = {};\n if (grant) out.grant = grant;\n if (accountId) out.accountId = accountId;\n if (error) out.error = error;\n return out;\n }\n\n /**\n * Spend a link grant: sign it with the account root and post the pair.\n *\n * Anonymous, and it has to be \u2014 the app doing this holds no cloud session,\n * which is the whole reason the grant exists. The grant carries the consenting\n * login sealed inside it, so nothing here names one; a caller-supplied email\n * would be a field to lie in.\n *\n * The signature is an ordinary account-link signature with the grant as the\n * nonce, so the same {@link signAccountLink} every other link path uses signs\n * this one. Both halves are required: a grant without the key links nothing,\n * which is what makes it safe to carry through a browser redirect.\n *\n * A `403` means the grant was bad, expired, already spent, or signed by a key\n * that names a different account; `409` that the account belongs to another\n * login; `402` that the plan is full.\n */\n async linkAccountWithGrant(options: {\n grant: string;\n /** The root of the account the grant names, 64 hex. */\n rootSecret: string;\n }): Promise<CloudAccountLink> {\n const root = await accountRootFromSecret(options.rootSecret);\n const body = await this.request<Record<string, unknown>>(\n 'POST',\n '/api/cloud/accounts/link',\n {\n body: {\n grant: options.grant,\n root_public_key: root.publicKey,\n signature: await signAccountLink({\n rootSecret: options.rootSecret,\n nonce: options.grant,\n }),\n },\n anonymous: true,\n },\n );\n return {\n accountId: String(body.account_id ?? root.accountId),\n linkedAt: (body.linked_at as string | null | undefined) ?? null,\n alreadyLinked: body.already_linked === true,\n };\n }\n\n /** The accounts linked to this login, and how many the plan allows. */\n async getMyAccounts(): Promise<CloudLinkedAccounts> {\n const body = await this.request<{\n accounts?: Array<Record<string, unknown>>;\n limit?: number | null;\n }>('GET', '/api/cloud/me/accounts');\n return {\n accounts: (body.accounts ?? []).map((row) => ({\n accountId: String(row.account_id ?? ''),\n linkedAt: (row.linked_at as string | null | undefined) ?? null,\n hasRecoveryEnvelope: row.has_recovery_envelope === true,\n })),\n limit: body.limit ?? null,\n };\n }\n\n private adoptSession(body: {\n session_token: string;\n expires_at: number;\n user: { email: string; name?: string; picture?: string };\n }): CloudSession {\n this.session = {\n sessionToken: body.session_token,\n expiresAt: body.expires_at,\n user: body.user,\n };\n void this.notifySession(this.session);\n return this.session;\n }\n\n private async notifySession(session: CloudSession | null): Promise<void> {\n try {\n await this.onSession?.(session);\n } catch {\n // A failing persistence callback must not mask the call's own result.\n }\n }\n\n private async request<T>(\n method: string,\n path: string,\n options: {\n body?: unknown;\n anonymous?: boolean;\n parse?: 'json' | 'none';\n /** Extra request headers. Never overrides `Authorization` \u2014 see below. */\n headers?: Record<string, string>;\n } = {},\n ): Promise<T> {\n if (!options.anonymous && !this.session) {\n throw new Error(\n 'Not signed in to Calimero Cloud: call signInWithGoogle() or construct the client with a sessionToken',\n );\n }\n\n // Caller headers go in FIRST, so the lines below win on any collision: a\n // per-call header must never be able to replace the session's\n // `Authorization` or change how the body is parsed.\n const headers: Record<string, string> = {\n ...options.headers,\n Accept: 'application/json',\n };\n if (options.body !== undefined) headers['Content-Type'] = 'application/json';\n if (!options.anonymous && this.session) {\n headers.Authorization = `Bearer ${this.session.sessionToken}`;\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n let response: Response;\n try {\n response = await this.fetchImpl(`${this.baseUrl}${path}`, {\n method,\n headers,\n body: options.body === undefined ? undefined : JSON.stringify(options.body),\n signal: controller.signal,\n });\n } catch (err) {\n // Same shape the node clients use for a transport failure: an `HTTPError`\n // with `status: 0`, so a caller has one error type to handle rather than\n // one per layer.\n throw new HTTPError(\n 0,\n err instanceof Error ? err.name : 'NetworkError',\n `${this.baseUrl}${path}`,\n new Headers(),\n err instanceof Error ? err.message : String(err),\n );\n } finally {\n clearTimeout(timer);\n }\n\n this.adoptRollingRefresh(response);\n\n if (!response.ok) {\n const bodyText = await response.text().catch(() => undefined);\n throw new HTTPError(\n response.status,\n response.statusText,\n `${this.baseUrl}${path}`,\n response.headers,\n bodyText,\n );\n }\n\n if (options.parse === 'none' || response.status === 204) return undefined as T;\n return (await response.json()) as T;\n }\n\n /**\n * Adopt a session token the server refreshed mid-request.\n *\n * MDMA rotates the token on activity and returns the replacement in a\n * response header rather than making the client ask. Ignoring it costs\n * nothing immediately and then logs the user out a week later, which is the\n * kind of bug that is impossible to reproduce in a short session.\n *\n * The user identity is carried over: the header holds only a token, and the\n * refresh is the same logical session as the sign-in that named the user.\n */\n private adoptRollingRefresh(response: Response): void {\n const token = response.headers.get(REFRESH_HEADER);\n if (!token || !this.session) return;\n const expiresRaw = response.headers.get(REFRESH_EXPIRES_HEADER);\n const expiresAt = Number(expiresRaw);\n this.session = {\n sessionToken: token,\n expiresAt: Number.isFinite(expiresAt) && expiresAt > 0 ? expiresAt : this.session.expiresAt,\n user: this.session.user,\n };\n void this.notifySession(this.session);\n }\n}\n\n/**\n * The addresses a fleet node can dial to ask this node for admission, from the\n * node's `GET /admin-api/network/status` (see `AdminApiClient`).\n *\n * Its `externalAddrs`, each made to end in `/p2p/<localPeerId>`: that is the\n * shape core dials, and an external address is usually reported without it. A\n * relay-circuit address already contains `/p2p/` \u2014 the relay's \u2014 so the test is\n * whether it ends in this node's own id, not whether it names a peer at all.\n * Anything that is not a multiaddr is dropped. Empty when the status names no\n * peer id or no external address, which is an honest \"not dialable yet\".\n */\nexport function admitterAddrsFromNetworkStatus(status: unknown): string[] {\n const record =\n status && typeof status === 'object'\n ? ((status as { data?: unknown }).data ?? status)\n : undefined;\n if (!record || typeof record !== 'object') return [];\n const peerId = (record as { localPeerId?: unknown }).localPeerId;\n const external = (record as { externalAddrs?: unknown }).externalAddrs;\n if (typeof peerId !== 'string' || !peerId || !Array.isArray(external)) return [];\n const suffix = `/p2p/${peerId}`;\n const out: string[] = [];\n for (const entry of external) {\n if (typeof entry !== 'string') continue;\n const addr = entry.trim();\n if (!addr.startsWith('/')) continue;\n const dialable = addr.endsWith(suffix) ? addr : `${addr}${suffix}`;\n if (!out.includes(dialable)) out.push(dialable);\n }\n return out;\n}\n", "import { createBrowserHttpClient } from './http-client/index.js';\nimport { createAuthApiClientFromHttpClient } from './auth-api/index.js';\nimport { createAdminApiClientFromHttpClient } from './admin-api/index.js';\nimport type { AuthApiClient } from './auth-api/index.js';\nimport type { AdminApiClient } from './admin-api/index.js';\nimport type { HttpClient } from './http-client/index.js';\nimport type { TokenStore } from './token-store/index.js';\nimport { parseAuthCallback, buildAuthLoginUrl } from './auth/index.js';\nimport { createProofSigner } from './request/index.js';\nimport type { ProofRequest, ProofSignerOptions } from './request/index.js';\nimport type { AuthCallbackResult, AuthLoginOptions } from './auth/index.js';\nimport { RpcClient } from './rpc/index.js';\nimport { SseClient } from './events/sse.js';\nimport { WsClient } from './events/ws.js';\nimport { EphemeralClient } from './ephemeral/index.js';\nimport { CloudClient } from './cloud/cloud-client.js';\nimport type { CloudClientConfig } from './cloud/cloud-client.js';\n\ntype ProofCallback = (\n request: ProofRequest,\n) => Promise<string | undefined>;\n\n/**\n * Accept either the keys or a ready signer.\n *\n * Taking a callback as well as an options object is not a convenience: it is\n * how a caller keeps the signing key out of the SDK entirely when it lives\n * somewhere the SDK cannot reach.\n */\nfunction resolveProofSigner(\n config: MeroJsConfig['requestProof'],\n): ProofCallback | undefined {\n if (!config) return undefined;\n return typeof config === 'function' ? config : createProofSigner(config);\n}\n\nexport interface MeroJsConfig {\n /** Base URL for the Calimero node */\n baseUrl: string;\n /** Initial credentials for authentication */\n credentials?: {\n username: string;\n password: string;\n };\n /** Custom HTTP client timeout in milliseconds */\n timeoutMs?: number;\n /** Request credentials mode for fetch (omit, same-origin, include) */\n requestCredentials?: RequestCredentials;\n /** Custom fetch for every HTTP request (`auth`, `admin`, `rpc`, `events`). Defaults to global `fetch`. */\n fetch?: typeof fetch;\n /** Optional token store for persistence */\n tokenStore?: TokenStore;\n /**\n * Called when the node reports that the credential family is gone\n * (`x-auth-error: token_reuse` / `token_revoked`). Terminal: the tokens have\n * already been cleared by the time this runs and nothing is retried, so use it\n * to send the user back through login. Errors thrown here are swallowed \u2014\n * never let a UI failure mask the auth error.\n */\n onAuthRevoked?: () => Promise<void> | void;\n /**\n * Calimero Cloud, reachable as `sdk.cloud`.\n *\n * A node connection and a cloud account are separate things a client can have\n * either of, both of, or neither \u2014 a desktop app with a local node and a\n * cloud subscription has both, and needs them from one object. So this is\n * configuration on the existing SDK rather than a second SDK: pass the\n * session token (or none, and sign in through `sdk.cloud`) and the node half\n * is unaffected.\n *\n * For a caller with *no* node at all, `connectCloud` is the entry point\n * instead \u2014 it returns a connection that can write without one.\n */\n cloud?: CloudClientConfig;\n /**\n * Sign every request with a device key, instead of or alongside a session.\n *\n * A session is a bearer token: obtained once, presented many times. A\n * request-carried proof commits to one method, one path and one body, so it\n * is minted per call and cannot be lifted onto another request. It is what\n * lets a client talk to a relay it has no account on \u2014 nothing was ever\n * issued to it.\n *\n * Pass the keys and let the SDK mint each proof:\n *\n * ```ts\n * new MeroJs({\n * baseUrl,\n * requestProof: { credential, session, signerSecret },\n * });\n * ```\n *\n * \u2026or pass your own signer, when the key must not reach the SDK at all \u2014 a\n * hardware token, or a worker that holds it:\n *\n * ```ts\n * new MeroJs({ baseUrl, requestProof: (req) => myWorker.sign(req) });\n * ```\n *\n * The node only honours this when it runs with `--delegated-access`; a node\n * that does not answers `403`, which is distinct from the `401` a bad proof\n * gets.\n *\n * Applies to the request/response API. `events` (SSE) and `ws` are long-lived\n * connections rather than requests, so a per-request signature does not\n * describe them \u2014 they still use the session.\n */\n requestProof?:\n | ProofSignerOptions\n | ((request: ProofRequest) => Promise<string | undefined>);\n}\n\nexport interface TokenData {\n access_token: string;\n refresh_token: string;\n expires_at: number;\n}\n\n/** Name of the Web Lock that serializes token refresh across tabs / webviews. */\nconst REFRESH_LOCK_NAME = 'mero-js:token-refresh';\n\n/** Minimal structural view of the Web Locks API (not available in every runtime). */\ninterface LockManagerLike {\n request<T>(name: string, callback: () => Promise<T>): Promise<T>;\n}\n\n/** Returns `navigator.locks` when the runtime supports the Web Locks API. */\nfunction getLockManager(): LockManagerLike | null {\n const nav = (globalThis as unknown as { navigator?: { locks?: LockManagerLike } }).navigator;\n const locks = nav?.locks;\n return locks && typeof locks.request === 'function' ? locks : null;\n}\n\n/** Try to extract `exp` (seconds) from a JWT, return ms timestamp or fallback. */\nfunction expiresAtFromJwt(token: string, fallbackMs: number): number {\n try {\n const parts = token.split('.');\n if (parts.length === 3) {\n // JWT uses base64url encoding: replace -/_ with +// and add padding\n let b64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');\n while (b64.length % 4) b64 += '=';\n const payload = JSON.parse(atob(b64));\n if (typeof payload.exp === 'number') {\n return payload.exp * 1000;\n }\n }\n } catch {\n // not a JWT or can't parse\n }\n return fallbackMs;\n}\n\n/**\n * Main MeroJs SDK class that manages all API clients and authentication\n */\nexport class MeroJs {\n private config: MeroJsConfig;\n private httpClient: HttpClient;\n private authClient: AuthApiClient;\n private adminClient: AdminApiClient;\n private tokenData: TokenData | null = null;\n private refreshPromise: Promise<TokenData> | null = null;\n private tokenStore: TokenStore | null;\n private rpcClient: RpcClient | null = null;\n private sseClient: SseClient | null = null;\n private wsClient: WsClient | null = null;\n private wsWarned = false;\n private ephemeralClient?: EphemeralClient;\n private cloudClient?: CloudClient;\n\n constructor(config: MeroJsConfig) {\n this.config = {\n timeoutMs: 10000,\n ...config,\n };\n\n this.tokenStore = config.tokenStore ?? null;\n\n // Restore tokens from store if available\n if (this.tokenStore) {\n this.tokenData = this.tokenStore.getTokens();\n }\n\n // Create HTTP client with token management\n // For Tauri, explicitly set credentials to 'omit' to avoid network errors\n const isTauri = typeof window !== 'undefined' && '__TAURI_INTERNALS__' in window;\n // One signer for the whole connection: `authClient` and `adminClient` are\n // both built FROM this transport, so wiring it here covers every request\n // either of them makes.\n const getProof = resolveProofSigner(this.config.requestProof);\n\n this.httpClient = createBrowserHttpClient({\n baseUrl: this.config.baseUrl,\n fetch: this.config.fetch,\n getProof,\n getAuthToken: async () => {\n const token = await this.getValidToken();\n return token?.access_token || '';\n },\n refreshToken: async () => {\n const refreshed = await this.performTokenRefresh();\n return refreshed.access_token;\n },\n onTokenRefresh: async (newToken: string) => {\n if (this.tokenData) {\n this.tokenData.access_token = newToken;\n this.tokenStore?.setTokens(this.tokenData);\n }\n },\n onAuthRevoked: async () => {\n // The refresh-token family is gone (a single-use refresh token was\n // replayed, or the token was revoked). Nothing left to refresh with \u2014\n // drop the bundle so the app can force a re-login, then let the app react.\n this.clearToken();\n try {\n await this.config.onAuthRevoked?.();\n } catch {\n // A failing app callback must not mask the auth error.\n }\n },\n timeoutMs: this.config.timeoutMs,\n credentials: this.config.requestCredentials ?? (isTauri ? 'omit' : undefined),\n });\n\n // Create API clients\n this.authClient = createAuthApiClientFromHttpClient(this.httpClient, {\n baseUrl: this.config.baseUrl,\n getAuthToken: async () => {\n const token = await this.getValidToken();\n return token?.access_token || '';\n },\n timeoutMs: this.config.timeoutMs,\n });\n\n this.adminClient = createAdminApiClientFromHttpClient(this.httpClient, {\n baseUrl: this.config.baseUrl,\n getAuthToken: async () => {\n const token = await this.getValidToken();\n return token?.access_token || '';\n },\n timeoutMs: this.config.timeoutMs,\n });\n }\n\n /**\n * Get the Auth API client\n */\n get auth(): AuthApiClient {\n return this.authClient;\n }\n\n /**\n * Get the Admin API client\n */\n get admin(): AdminApiClient {\n return this.adminClient;\n }\n\n /**\n * Get the RPC client (lazy initialized)\n */\n get rpc(): RpcClient {\n if (!this.rpcClient) {\n this.rpcClient = new RpcClient({ httpClient: this.httpClient });\n }\n return this.rpcClient;\n }\n\n /**\n * Get the SSE event client (lazy initialized)\n */\n get events(): SseClient {\n if (!this.sseClient) {\n this.sseClient = new SseClient({\n baseUrl: this.config.baseUrl,\n fetch: this.config.fetch,\n getAuthToken: async () => {\n const token = await this.getValidToken();\n return token?.access_token || '';\n },\n });\n }\n return this.sseClient;\n }\n\n /**\n * Get the ephemeral-presence client (lazy initialized).\n *\n * Shares this instance's SSE client, so `subscribe` reuses the one event\n * stream rather than opening a second connection.\n */\n get ephemeral(): EphemeralClient {\n if (!this.ephemeralClient) {\n this.ephemeralClient = new EphemeralClient({\n httpClient: this.httpClient,\n sse: this.events,\n });\n }\n return this.ephemeralClient;\n }\n\n /**\n * Calimero Cloud (lazy initialized).\n *\n * Available whether or not `cloud` was configured \u2014 an app that signs in\n * later gets an unauthenticated client it can call `signInWithGoogle` on,\n * which is one fewer construction path than making the getter conditional.\n * It is entirely independent of this instance's node credential: the cloud\n * knows nothing about the node's tokens and the node knows nothing about the\n * cloud session.\n */\n get cloud(): CloudClient {\n if (!this.cloudClient) {\n this.cloudClient = new CloudClient(this.config.cloud ?? {});\n }\n return this.cloudClient;\n }\n\n /**\n * Get the WebSocket event client (lazy initialized).\n * @experimental Use `events` (SSE) for production. WsClient is experimental.\n */\n get ws(): WsClient {\n if (!this.wsWarned) {\n this.wsWarned = true;\n console.warn('[mero-js] WsClient is experimental. Use mero.events (SSE) for production.');\n }\n if (!this.wsClient) {\n this.wsClient = new WsClient({\n baseUrl: this.config.baseUrl,\n getAuthToken: async () => {\n const token = await this.getValidToken();\n return token?.access_token || '';\n },\n });\n }\n return this.wsClient;\n }\n\n /**\n * Authenticate with the provided credentials\n * This will create the root key on first use\n */\n async authenticate(credentials?: {\n username: string;\n password: string;\n }): Promise<TokenData> {\n const creds = credentials || this.config.credentials;\n if (!creds) {\n throw new Error('No credentials provided for authentication');\n }\n\n try {\n const requestBody = {\n auth_method: 'user_password',\n public_key: creds.username,\n client_name: 'mero-js-sdk',\n permissions: ['admin'],\n timestamp: Math.floor(Date.now() / 1000),\n provider_data: {\n username: creds.username,\n password: creds.password,\n },\n };\n\n const response = await this.authClient.generateTokens(requestBody);\n\n const accessToken = response.data.access_token;\n this.tokenData = {\n access_token: accessToken,\n refresh_token: response.data.refresh_token,\n expires_at: expiresAtFromJwt(accessToken, Date.now() + 3600_000),\n };\n\n this.tokenStore?.setTokens(this.tokenData);\n\n return this.tokenData;\n } catch (error) {\n throw new Error(\n `Authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`,\n );\n }\n }\n\n /**\n * Get a valid token. Returns the current token as-is.\n * The server rejects refresh attempts while the access token is still valid,\n * so we never proactively refresh. Instead, the WebHttpClient handles 401\n * responses reactively via the refreshToken transport hook.\n */\n private async getValidToken(): Promise<TokenData | null> {\n return this.tokenData;\n }\n\n /**\n * Refresh the access token. Single-flight: every caller (including the HTTP\n * transport's 401 hook) goes through here.\n *\n * Refresh tokens are single-use (calimero-network/core#3083): each refresh\n * consumes the presented token and returns a new one, and replaying a consumed\n * token makes the server revoke the whole family. A refresh must therefore\n * happen exactly once per rotation, which needs three layers:\n * - an in-process promise, so concurrent 401s in this instance share one call;\n * - a Web Lock (when available), so other tabs/webviews don't refresh at the\n * same time on the same stored token;\n * - a re-read of the token store inside the lock, so a bundle that another\n * instance already rotated is adopted instead of replaying a stale token.\n */\n private async performTokenRefresh(): Promise<TokenData> {\n if (this.refreshPromise) {\n return this.refreshPromise;\n }\n\n // The access token that triggered this refresh. If the store holds a different\n // one by the time we get the lock, someone else already rotated for us.\n const triggeringAccessToken = this.tokenData?.access_token;\n\n const promise = this.withRefreshLock(() =>\n this.performTokenRefreshLocked(triggeringAccessToken),\n );\n this.refreshPromise = promise;\n\n try {\n return await promise;\n } finally {\n if (this.refreshPromise === promise) {\n this.refreshPromise = null;\n }\n }\n }\n\n /** Run `fn` under the cross-tab refresh lock, or directly if Web Locks are unavailable. */\n private withRefreshLock<T>(fn: () => Promise<T>): Promise<T> {\n const locks = getLockManager();\n return locks ? locks.request(REFRESH_LOCK_NAME, fn) : fn();\n }\n\n /**\n * Perform the actual token refresh. Runs with the refresh lock held.\n */\n private async performTokenRefreshLocked(\n triggeringAccessToken?: string,\n ): Promise<TokenData> {\n // Another instance/tab may have rotated the bundle while we were queued behind\n // the lock \u2014 the store, not our in-memory copy, is the source of truth.\n const stored = this.tokenStore?.getTokens() ?? null;\n if (stored?.refresh_token) {\n this.tokenData = stored;\n\n if (triggeringAccessToken && stored.access_token !== triggeringAccessToken) {\n // Someone else already refreshed. Reuse their bundle rather than replaying\n // our (now consumed) refresh token, which would revoke the family.\n return stored;\n }\n }\n\n if (!this.tokenData?.refresh_token) {\n throw new Error('No refresh token available');\n }\n\n try {\n const response = await this.authClient.refreshToken({\n access_token: this.tokenData.access_token,\n refresh_token: this.tokenData.refresh_token,\n });\n\n const accessToken = response.data.access_token;\n this.tokenData = {\n access_token: accessToken,\n // The refresh token is rotated on every refresh \u2014 persist the new one or\n // the next refresh replays a consumed token.\n refresh_token: response.data.refresh_token,\n expires_at: expiresAtFromJwt(accessToken, Date.now() + 3600_000),\n };\n\n this.tokenStore?.setTokens(this.tokenData);\n\n return this.tokenData;\n } catch (error) {\n // Don't clear tokens on refresh failure \u2014 the access token may still be\n // valid (server rejects refresh while access token hasn't expired yet).\n // A genuinely dead family arrives as `x-auth-error: token_reuse` and is\n // handled by the onAuthRevoked transport hook. Let the caller handle this.\n throw new Error(\n `Token refresh failed: ${error instanceof Error ? error.message : 'Unknown error'}`,\n );\n }\n }\n\n /**\n * Clear the current token\n */\n public clearToken(): void {\n this.refreshPromise = null;\n this.tokenData = null;\n this.tokenStore?.clear();\n }\n\n /**\n * Check if the SDK is authenticated\n */\n public isAuthenticated(): boolean {\n return this.tokenData !== null;\n }\n\n /**\n * Set token data directly (e.g., from auth callback).\n * If `expires_at` is missing or 0, attempts to parse the JWT exp claim,\n * falling back to 1 hour from now.\n */\n public setTokenData(data: TokenData): void {\n const expiresAt = data.expires_at || expiresAtFromJwt(data.access_token, Date.now() + 3600_000);\n this.tokenData = { ...data, expires_at: expiresAt };\n this.tokenStore?.setTokens(this.tokenData);\n }\n\n /**\n * Get the current token data (for debugging)\n */\n public getTokenData(): TokenData | null {\n return this.tokenData;\n }\n\n /**\n * Close all event connections and clean up resources\n */\n public close(): void {\n this.sseClient?.close();\n this.wsClient?.close();\n }\n\n /**\n * Parse an auth callback URL hash fragment (static utility)\n */\n static parseAuthCallback(url: string): AuthCallbackResult | null {\n return parseAuthCallback(url);\n }\n\n /**\n * Build an auth login URL (static utility)\n */\n static buildAuthLoginUrl(nodeUrl: string, opts: AuthLoginOptions): string {\n return buildAuthLoginUrl(nodeUrl, opts);\n }\n}\n\n/**\n * Create a new MeroJs SDK instance\n */\nexport function createMeroJs(config: MeroJsConfig): MeroJs {\n return new MeroJs(config);\n}\n", "import type { TokenData } from '../mero-js.js';\n\nexport interface TokenStore {\n getTokens(): TokenData | null;\n setTokens(data: TokenData): void;\n clear(): void;\n}\n\nexport class MemoryTokenStore implements TokenStore {\n private tokens: TokenData | null = null;\n\n getTokens(): TokenData | null {\n return this.tokens;\n }\n\n setTokens(data: TokenData): void {\n this.tokens = data;\n }\n\n clear(): void {\n this.tokens = null;\n }\n}\n\nconst STORAGE_KEY = 'mero-tokens';\n\nexport class LocalStorageTokenStore implements TokenStore {\n private readonly key: string;\n\n constructor(key: string = STORAGE_KEY) {\n this.key = key;\n }\n\n getTokens(): TokenData | null {\n try {\n if (typeof localStorage === 'undefined') return null;\n const raw = localStorage.getItem(this.key);\n if (!raw) return null;\n const parsed = JSON.parse(raw);\n if (parsed && parsed.access_token && parsed.refresh_token) {\n return {\n access_token: parsed.access_token,\n refresh_token: parsed.refresh_token,\n expires_at: typeof parsed.expires_at === 'number' ? parsed.expires_at : Date.now() + 3600_000,\n };\n }\n return null;\n } catch {\n return null;\n }\n }\n\n setTokens(data: TokenData): void {\n try {\n if (typeof localStorage === 'undefined') return;\n localStorage.setItem(this.key, JSON.stringify(data));\n } catch {\n // Storage unavailable\n }\n }\n\n clear(): void {\n try {\n if (typeof localStorage === 'undefined') return;\n localStorage.removeItem(this.key);\n } catch {\n // Storage unavailable\n }\n }\n}\n", "/**\n * Mint a warrant: the author's consent for one relay to run one intent, once.\n *\n * This exists because the case delegated authorship is *for* is an account that\n * holds no node \u2014 a browser tab, a Node service, a bot. Every one of those is a\n * JS runtime, and until now the only thing that could mint a warrant was `merod`,\n * so \"a member with no node\" in practice meant \"a member with a node it is not\n * allowed to use for anything else\".\n *\n * **No dependencies, and that is not incidental.** The primitives are all built\n * in: `crypto.subtle.digest('SHA-256')` for the hash, `crypto.subtle` Ed25519\n * for the signature, and `DataView` for the integers.\n *\n * Warrant v2 (core#3933) gave up the fixed-width encoding that used to make this\n * pure concatenation: `method` travels in the clear as a string and each cited\n * head list is a vector, so three borsh `u32` counts now sit inside the bytes.\n * Two widths to keep straight, and they are not the same: the counts **inside\n * the encoding** are `u32`, while the counts the **signing preimage** hashes are\n * `u64`. Same numbers, different serialization \u2014 reusing one for the other\n * produces a warrant that is well-formed and verifies nowhere.\n *\n * **The byte contract is pinned in core**, at\n * `crates/account/src/tests/warrant_wire_fixture.rs`. The domain strings below\n * are `pub(crate)` there, so nothing in that repository forces anyone to notice\n * this file depends on them \u2014 which is exactly why the fixture lives on that side\n * and this module's test asserts the same vectors. A drift would otherwise leave\n * this producing well-formed warrants whose signatures verify nowhere, surfacing\n * at a relay as a 403: an authorization refusal, nowhere near the cause.\n */\n\nimport {\n concat,\n derivePublicKey,\n domainHash,\n fromHex,\n hex,\n importSigningKey,\n u32le,\n u64le,\n} from '../crypto/internal.js';\n\nconst SIGN_DOMAIN = new TextEncoder().encode('calimero.warrant.v2');\nconst INTENT_DOMAIN = new TextEncoder().encode('calimero.warrant.intent.v1');\n\n/**\n * core's `MAX_WARRANT_CITED_HEADS`. A node refuses more than this before doing\n * any Ed25519 work, so minting past it produces a warrant nothing will spend.\n */\nconst MAX_CITED_HEADS = 64;\n\n/** 32 zero bytes \u2014 the `appVersion` default. See {@link WarrantInput.appVersion}. */\nconst UNPINNED_APP = '00'.repeat(32);\n\n/** Ed25519 PKCS#8 prefix, so a raw 32-byte seed can be imported by WebCrypto. */\n/** What a warrant authorises. */\nexport interface WarrantInput {\n /**\n * The context, hex \u2014 the same string every other method here takes.\n *\n * Hex like everything else on this interface. It was base58 while the node\n * spelled `ContextId` and `PublicKey` that way and `AccountId` and `DeviceId`\n * in hex; that split is gone, so this module no longer needs a rule per field\n * and no longer needs a base58 decoder to enforce one.\n */\n context: string;\n /** The author's account, hex \u2014 whose consent this is. */\n authorAccount: string;\n /** The relay authorised to act, hex. An account, not a key. */\n executor: string;\n /**\n * The application build this warrant is signed against, hex (32 bytes).\n *\n * Pins the code rather than a version string, so a relay cannot wait for an\n * upgrade that widens what `method` does and then spend a warrant signed\n * against the narrower one. Read it from the context\n * (`GET /admin-api/contexts/{id}` \u2192 `applicationId`); a value guessed here\n * pins the wrong build, which is worse than not pinning.\n *\n * Defaults to 32 zero bytes, matching `merod account sign-warrant`'s\n * `--app-version` default. Nothing verifies the field yet \u2014 core#3933 landed\n * the field set ahead of its enforcement so a signer is written once against\n * the final bytes \u2014 but a warrant minted with the default will be refused once\n * pinning lands, so pass the real value for anything meant to outlive it.\n */\n appVersion?: string;\n /**\n * The method the relay may run.\n *\n * Carried in the clear as of v2, so a peer can select a per-method write-set\n * without reversing a hash against the app's ABI. The *arguments* are still\n * committed to rather than carried \u2014 see {@link intentHash}.\n */\n method: string;\n /** Its arguments, as the JSON the guest will receive. */\n argsJson: unknown;\n /**\n * Monotonic per author **device**.\n *\n * Per device rather than per account because two devices of one account are\n * independent replicas: they cannot coordinate on a counter, so an\n * account-scoped sequence would have them refusing each other's warrants.\n */\n nonce: number | bigint;\n /**\n * Account-log heads this author saw when signing, each hex (32 bytes).\n *\n * Empty by default, and empty is the honest answer for a client that tracks\n * no log \u2014 a fabricated view is worse than none. At most\n * {@link MAX_CITED_HEADS}.\n */\n accountHeads?: string[];\n /**\n * Governance heads this author's view descended from, each hex (32 bytes).\n *\n * **Where these come from decides whether they mean anything.** A floor the\n * relay supplied is a floor the relay chose: a relay that withholds a\n * governance op can hand you a stale view and collect a warrant citing it.\n * Take the value from the account's own devices or a co-signing peer outside\n * the relay's operator. As it stands this detects an honest relay's staleness\n * and does not constrain a dishonest one.\n */\n governanceFloor?: string[];\n /** Unix seconds after which the relay must refuse it. */\n notAfter: number | bigint;\n /**\n * The author device's ed25519 signing secret, hex (32 bytes).\n *\n * Never sent anywhere. It signs locally and only the signature travels, which\n * is the whole reason a warrant can be minted by something holding no node.\n */\n deviceSecret: string;\n}\n\n\n\n\n\n\n\n/**\n * The commitment a warrant carries in place of the intent itself.\n *\n * Its own domain, distinct from the signing one, so a value computed for one\n * purpose cannot be presented for the other.\n */\nexport async function intentHash(\n method: string,\n argsJson: unknown,\n): Promise<Uint8Array> {\n const args = new TextEncoder().encode(JSON.stringify(argsJson));\n return domainHash(INTENT_DOMAIN, [new TextEncoder().encode(method), args]);\n}\n\n\n/**\n * Sign a warrant and return it hex-encoded, ready for `performIntent`.\n *\n * Returns the encoding rather than an object because the signature covers\n * exactly these bytes: a caller that rebuilt the fields from JSON would have a\n * second spelling able to disagree with what was signed.\n */\nexport async function signWarrant(input: WarrantInput): Promise<string> {\n const context = fromHex(input.context, 'context', 32);\n const authorAccount = fromHex(input.authorAccount, 'authorAccount', 32);\n const executor = fromHex(input.executor, 'executor', 32);\n const appVersion = fromHex(\n input.appVersion ?? UNPINNED_APP,\n 'appVersion',\n 32,\n );\n const accountHeads = citedHeads(input.accountHeads, 'accountHeads');\n const governanceFloor = citedHeads(input.governanceFloor, 'governanceFloor');\n\n const key = await importSigningKey(input.deviceSecret);\n const publicKey = await derivePublicKey(input.deviceSecret);\n\n const method = new TextEncoder().encode(input.method);\n const commitment = await intentHash(input.method, input.argsJson);\n const nonce = u64le(input.nonce);\n const notAfter = u64le(input.notAfter);\n\n // Each head list is preceded by its own count. `domainHash` length-prefixes\n // every part it is given, so the heads are individually unambiguous \u2014 but the\n // two lists are adjacent, so without the counts `accountHeads = [a, b],\n // governanceFloor = []` hashes identically to `[a], [b]`, and a relay could\n // relabel which plane a head was cited from. u64 HERE; u32 in the encoding.\n const preimage = await domainHash(SIGN_DOMAIN, [\n context,\n authorAccount,\n publicKey,\n executor,\n appVersion,\n method,\n commitment,\n u64le(accountHeads.length),\n ...accountHeads,\n u64le(governanceFloor.length),\n ...governanceFloor,\n nonce,\n notAfter,\n ]);\n\n const signature = new Uint8Array(\n await crypto.subtle.sign({ name: 'Ed25519' }, key, preimage),\n );\n\n // Borsh: a `String` is a u32 length then its UTF-8 bytes; a `Vec<[u8; 32]>` is\n // a u32 count then the elements. u32 HERE; u64 in the preimage above.\n return hex(\n concat(\n context,\n authorAccount,\n publicKey,\n executor,\n appVersion,\n u32le(method.length),\n method,\n commitment,\n u32le(accountHeads.length),\n ...accountHeads,\n u32le(governanceFloor.length),\n ...governanceFloor,\n nonce,\n notAfter,\n signature,\n ),\n );\n}\n\n/** Decode a cited-head list, refusing one longer than a node will accept. */\nfunction citedHeads(heads: string[] | undefined, label: string): Uint8Array[] {\n const list = heads ?? [];\n if (list.length > MAX_CITED_HEADS) {\n throw new Error(\n `${label} cites ${list.length} heads, over the ${MAX_CITED_HEADS} a node accepts`,\n );\n }\n return list.map((head, i) => fromHex(head, `${label}[${i}]`, 32));\n}\n\n/** A warrant's fields, as hex, decoded from the wire encoding. */\nexport interface WarrantFields {\n context: string;\n authorAccount: string;\n /** The author device key `signWarrant` derived from the secret. */\n deviceKey: string;\n executor: string;\n appVersion: string;\n method: string;\n intentHash: string;\n accountHeads: string[];\n governanceFloor: string[];\n /** u64 little-endian, as it sits on the wire. */\n nonce: string;\n /** u64 little-endian, as it sits on the wire. */\n notAfter: string;\n signature: string;\n}\n\n/**\n * Decode a hex warrant back into its fields.\n *\n * Exists because v2's offsets are no longer constants: `method` is a string and\n * the head lists are vectors, so everything after `appVersion` moves with the\n * method's length. Anything reading a warrant by fixed offset \u2014 a test, a client\n * inspecting one it was handed \u2014 silently reads the wrong field instead of\n * failing, which is how the three call sites that did so passed while comparing\n * the method's length prefix to a nonce.\n *\n * One copy of the layout, beside the encoder that produces it, so the two cannot\n * drift. Internal: not exported from the package root, because a client that\n * needs a warrant's fields has the input it signed.\n */\nexport function parseWarrant(warrant: string): WarrantFields {\n const at = (start: number, bytes: number) =>\n warrant.slice(start * 2, (start + bytes) * 2);\n // A borsh u32 is little-endian, so the hex pairs read back to front.\n const u32At = (start: number) =>\n Number.parseInt((at(start, 4).match(/../g) ?? []).reverse().join(''), 16);\n\n let o = 0;\n const take = (bytes: number) => {\n const field = at(o, bytes);\n o += bytes;\n return field;\n };\n\n const context = take(32);\n const authorAccount = take(32);\n const deviceKey = take(32);\n const executor = take(32);\n const appVersion = take(32);\n const methodLen = u32At(o);\n o += 4;\n const method = take(methodLen);\n const intentHashField = take(32);\n const accountCount = u32At(o);\n o += 4;\n const accountHeads = Array.from({ length: accountCount }, () => take(32));\n const governanceCount = u32At(o);\n o += 4;\n const governanceFloor = Array.from({ length: governanceCount }, () =>\n take(32),\n );\n const nonce = take(8);\n const notAfter = take(8);\n const signature = take(64);\n\n if (o * 2 !== warrant.length) {\n throw new Error(\n `warrant is ${warrant.length / 2} bytes but its fields account for ${o}`,\n );\n }\n\n return {\n context,\n authorAccount,\n deviceKey,\n executor,\n appVersion,\n method,\n intentHash: intentHashField,\n accountHeads,\n governanceFloor,\n nonce,\n notAfter,\n signature,\n };\n}\n", "/**\n * Write to a context through a relay, holding nothing but a signing key.\n *\n * # What this replaces\n *\n * `sdk.rpc.execute` asks a node to run a method *as itself*: it needs a node,\n * that node needs to be a member, and the caller needs a credential on it. For\n * a browser tab, a phone, or an agent, none of those three is available \u2014 the\n * runtime cannot compile WASM against materialized state, never received the\n * scope key that seals the deltas, and has no account on anybody's node.\n *\n * This client is the other path. The caller signs a warrant locally, the relay\n * runs the method as the *author's* principal, and the resulting delta is\n * attributed to the author's account and device \u2014 their replica slot, their\n * membership, their name in the app. The relay's key signs the envelope and\n * nothing else.\n *\n * # The three preconditions, and why they are checked in this order\n *\n * `describe()` answers the first two in one call, before anything is signed:\n * the relay's executor account (which the warrant must name) and whether it\n * holds `CAN_AUTHOR_ON_BEHALF` on the owning group. The third \u2014 that the\n * author's account is a member \u2014 only the group knows, and it surfaces as a\n * refusal.\n *\n * Checking before signing is not politeness. A warrant consumes a nonce from a\n * monotonic per-device sequence, and one minted against the wrong executor is\n * unspendable: the number is gone and the write never happened.\n */\n\nimport { signWarrant } from '../warrant/warrant.js';\nimport { HTTPError } from '../http-client/web-client.js';\nimport type { NonceSource } from './nonce-source.js';\n\n/** How long a freshly minted warrant stays presentable, in seconds. */\nconst DEFAULT_TTL_SECONDS = 300;\n\n/** Where and as whom to write, plus the key that consents. */\nexport interface RelayClientConfig {\n /**\n * The relay's base URL \u2014 the node's origin, not a path.\n *\n * Discover it from {@link CloudClient.getNamespaceRelays} for a hosted\n * namespace, or take it from the operator for a self-hosted one.\n */\n relayUrl: string;\n /**\n * The account the relay writes as, hex \u2014 a warrant's `executor`.\n *\n * An account rather than a key, so one of the relay's processes rotating its\n * signing key does not void warrants already issued to it. Left unset, the\n * first `execute` learns it from {@link RelayClient.describe}.\n */\n executorAccount?: string;\n /** The author's account, hex \u2014 whose consent the warrant carries. */\n authorAccount: string;\n /**\n * The author's `AccountProof<DeviceCert>`, hex-encoded borsh.\n *\n * Proves the key that signed the warrant is a device of the account it names.\n * The author sends only its own half; the relay attaches its own.\n */\n authorProof: string;\n /**\n * The author device's ed25519 signing secret, hex (32 bytes).\n *\n * Never transmitted. It signs in this process and only the signature leaves,\n * which is the whole reason a keyholder can author without a node.\n */\n deviceSecret: string;\n /** Where nonces come from. See {@link NonceSource} \u2014 a reset replays. */\n nonces: NonceSource;\n /**\n * Seconds a minted warrant stays valid. Defaults to 300.\n *\n * Checked by the relay against its own clock and deliberately never by peers,\n * so this bounds how long *this* request may sit in flight \u2014 not how long the\n * network will accept the delta.\n */\n ttlSeconds?: number;\n /** Injected for tests and non-browser runtimes. Defaults to global `fetch`. */\n fetch?: typeof fetch;\n timeoutMs?: number;\n}\n\n/** What a relay says about its ability to run intents in one context. */\nexport interface RelayDescription {\n /** The account a warrant must name as `executor`, hex. */\n executorAccount: string;\n /** Whether the relay holds `CAN_AUTHOR_ON_BEHALF` on the owning group. */\n canAuthorOnBehalf: boolean;\n /** The group whose admin grants that capability, hex. */\n groupId: string;\n}\n\n/** Where an accepted intent landed. */\nexport interface IntentResult<T = unknown> {\n /** The context's scope root after the run \u2014 did this change anything? */\n rootHash: string;\n /** The method's own return value. */\n returns: T | null;\n}\n\n/**\n * Thrown when a relay refuses an intent, carrying which precondition failed.\n *\n * The distinction is the whole reason this type exists. A `403` from this\n * endpoint means one of three unrelated things \u2014 no grant on the relay, the\n * author is not a member, or the nonce was already spent \u2014 and they send a\n * caller somewhere completely different: ask an admin, ask for an invitation,\n * or just retry. Collapsing them into \"forbidden\" makes a retryable replay look\n * like a permissions bug.\n */\nexport class IntentRefusedError extends Error {\n name = 'IntentRefusedError';\n\n constructor(\n /** The relay's own explanation, verbatim. */\n public readonly reason: string,\n /** `true` when re-presenting the same intent under a *fresh* warrant may work. */\n public readonly retryable: boolean,\n public readonly status: number,\n ) {\n super(`relay refused the intent (HTTP ${status}): ${reason}`);\n }\n}\n\nexport class RelayClient {\n private readonly baseUrl: string;\n private executorAccount: string | undefined;\n private readonly config: RelayClientConfig;\n private readonly fetchImpl: typeof fetch;\n private readonly timeoutMs: number;\n\n constructor(config: RelayClientConfig) {\n this.config = config;\n this.baseUrl = config.relayUrl.replace(/\\/+$/, '');\n this.executorAccount = config.executorAccount;\n const injected = config.fetch;\n this.fetchImpl = injected\n ? (input: RequestInfo | URL, init?: RequestInit) => injected(input, init)\n : (input: RequestInfo | URL, init?: RequestInit) => globalThis.fetch(input, init);\n this.timeoutMs = config.timeoutMs ?? 10_000;\n }\n\n /**\n * Ask the relay what it can do in `contextId`.\n *\n * Cheap and unauthenticated on a relay that serves delegated execution\n * publicly, which is what makes it usable as a precondition check rather than\n * as diagnostics after a failure.\n */\n async describe(contextId: string): Promise<RelayDescription> {\n const body = await this.json<{\n data: { executorAccount: string; canAuthorOnBehalf: boolean; groupId: string };\n }>('GET', `/admin-api/contexts/${encodeURIComponent(contextId)}/intents`);\n // Learned, not merely returned: a client that called `describe` should not\n // then have to pass the account back in to `execute`.\n this.executorAccount = body.data.executorAccount;\n return body.data;\n }\n\n /**\n * Mint a warrant for `method(args)` and present it to the relay.\n *\n * One intent, once: the warrant authorizes exactly this method and these\n * arguments in exactly this context, and its nonce is spent by the network on\n * apply. Nothing accumulates and nothing is reusable.\n */\n async execute<T = unknown>(\n contextId: string,\n method: string,\n argsJson: unknown = {},\n ): Promise<IntentResult<T>> {\n const executor = this.executorAccount ?? (await this.describe(contextId)).executorAccount;\n\n const nonce = await this.config.nonces.next();\n const ttl = this.config.ttlSeconds ?? DEFAULT_TTL_SECONDS;\n const warrant = await signWarrant({\n context: contextId,\n authorAccount: this.config.authorAccount,\n executor,\n method,\n argsJson,\n nonce,\n notAfter: BigInt(Math.floor(Date.now() / 1000)) + BigInt(ttl),\n deviceSecret: this.config.deviceSecret,\n });\n\n const body = await this.json<{ data: { rootHash: string; returns: T | null } }>(\n 'POST',\n `/admin-api/contexts/${encodeURIComponent(contextId)}/intents`,\n { method, argsJson, warrant, authorProof: this.config.authorProof },\n );\n return { rootHash: body.data.rootHash, returns: body.data.returns ?? null };\n }\n\n private async json<T>(method: string, path: string, body?: unknown): Promise<T> {\n const headers: Record<string, string> = { Accept: 'application/json' };\n if (body !== undefined) headers['Content-Type'] = 'application/json';\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n let response: Response;\n try {\n response = await this.fetchImpl(`${this.baseUrl}${path}`, {\n method,\n headers,\n body: body === undefined ? undefined : JSON.stringify(body),\n signal: controller.signal,\n });\n } catch (err) {\n throw new HTTPError(\n 0,\n err instanceof Error ? err.name : 'NetworkError',\n `${this.baseUrl}${path}`,\n new Headers(),\n err instanceof Error ? err.message : String(err),\n );\n } finally {\n clearTimeout(timer);\n }\n\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n // 400 and 403 are both caller preconditions and core separates them\n // deliberately: 400 means re-sending the same bytes cannot help, 403\n // means the bytes are genuine but authority is missing. Only the spent\n // nonce is worth retrying, and only under a fresh warrant.\n if (response.status === 400 || response.status === 403) {\n const reason = extractReason(text);\n throw new IntentRefusedError(reason, /nonce/i.test(reason), response.status);\n }\n throw new HTTPError(\n response.status,\n response.statusText,\n `${this.baseUrl}${path}`,\n response.headers,\n text,\n );\n }\n\n return (await response.json()) as T;\n }\n}\n\n/** Pull the relay's explanation out of its error body, or fall back to the raw text. */\nfunction extractReason(bodyText: string): string {\n if (!bodyText) return 'no reason given';\n try {\n const parsed = JSON.parse(bodyText) as { error?: unknown; message?: unknown };\n for (const candidate of [parsed.error, parsed.message]) {\n if (typeof candidate === 'string' && candidate) return candidate;\n }\n } catch {\n // Not JSON \u2014 the text itself is the best available explanation.\n }\n return bodyText;\n}\n", "/**\n * Where a warrant's nonce comes from.\n *\n * # Why this is a named concept and not a counter in the relay client\n *\n * A warrant's nonce is spent, once, per `(context, author device)` on every\n * node that applies the delta. So the sequence a client emits is not a local\n * detail \u2014 it is state the network remembers, and the client is the only party\n * that can keep it.\n *\n * The receiving ledger is a **sliding window** 64 wide, not a high-water mark,\n * which is what makes the two failure modes so different:\n *\n * - **Skipping** numbers is free. Gossip gives no ordering, so the window\n * exists precisely to accept whatever unseen nonce arrives.\n * - **Restarting** the sequence is fatal. A tab that reloads and resumes at `1`\n * re-presents nonces the network has already spent, and every one of those\n * writes is refused as a replay \u2014 indistinguishable, to the user, from the\n * app silently not saving.\n *\n * That asymmetry is why persistence is the default here and an in-memory source\n * is the one you have to ask for. It is also why a source may jump: reserving a\n * block and losing the tail is the correct trade.\n */\n\n/** Produces the next nonce for one author device. Must never go backwards. */\nexport interface NonceSource {\n next(): Promise<bigint>;\n}\n\n/**\n * A counter in memory, starting at `start`.\n *\n * Correct only for a process whose lifetime is the whole sequence \u2014 a script, a\n * server-side worker that owns its own device key, a test. In a browser tab it\n * resets on reload and replays; use {@link createLocalStorageNonceSource}\n * there.\n */\nexport function createMemoryNonceSource(start: number | bigint = 1): NonceSource {\n let next = BigInt(start);\n if (next < 1n) {\n throw new Error('nonce sequence starts at 1');\n }\n return {\n next(): Promise<bigint> {\n const value = next;\n next += 1n;\n return Promise.resolve(value);\n },\n };\n}\n\n/**\n * A counter persisted in `localStorage`, so a reload continues the sequence.\n *\n * Key it per author **device**, not per account: two devices of one account are\n * independent replicas with independent sequences, and sharing a counter would\n * have them refusing each other's warrants.\n *\n * **Reserve-then-hand-out.** The stored value is advanced *before* the nonce is\n * returned, so a crash between the two loses a number rather than reusing one.\n * Losing a number costs nothing (the window accepts gaps); reusing one costs a\n * write.\n *\n * Concurrent tabs are the case this cannot fully solve, because `localStorage`\n * has no atomic read-modify-write. Two tabs racing can hand out the same nonce,\n * and the loser's write is refused as a replay rather than silently\n * misattributed \u2014 a visible error, not corruption. An app that genuinely writes\n * from several tabs at once should hold a Web Lock around `next()` or give each\n * tab its own device.\n */\nexport function createLocalStorageNonceSource(\n key: string,\n storage?: { getItem(k: string): string | null; setItem(k: string, v: string): void },\n): NonceSource {\n const store =\n storage ?? (globalThis as unknown as { localStorage?: typeof storage }).localStorage;\n if (!store) {\n throw new Error(\n 'no localStorage in this runtime: pass a storage implementation, or use createMemoryNonceSource for a process that owns its whole sequence',\n );\n }\n return {\n next(): Promise<bigint> {\n const raw = store.getItem(key);\n // A missing, empty, or corrupted value means \"no sequence recorded\", and\n // the safe reading of that is not 1 \u2014 a cleared storage on a device that\n // has already written would replay. But the client cannot know, and 1 is\n // the only defensible start, so it is what a fresh key gets. This is why\n // the doc above tells apps to key per device: a device whose storage was\n // cleared should be re-paired rather than resumed.\n let current = 0n;\n if (raw !== null && raw !== '') {\n try {\n const parsed = BigInt(raw);\n if (parsed > 0n) current = parsed;\n } catch {\n current = 0n;\n }\n }\n const value = current + 1n;\n store.setItem(key, value.toString());\n return Promise.resolve(value);\n },\n };\n}\n", "/**\n * The Noise NK handshake (`Noise_NK_25519_AESGCM_SHA256`) that opens a sealed\n * session, written against WebCrypto from the Noise specification\n * (revision 34, http://www.noiseprotocol.org/noise.html). The node runs the\n * other side with `snow`; core's published vectors check the two against each\n * other.\n *\n * NK: the initiator knows the responder's static key in advance \u2014 here the\n * transport key the node's quote commits to \u2014 and sends nothing but an\n * ephemeral key before both ephemerals are mixed in, so everything after the\n * handshake has forward secrecy.\n */\n\nimport { concat } from '../crypto/internal.js';\n\nconst PROTOCOL_NAME = 'Noise_NK_25519_AESGCM_SHA256';\nconst DH_LEN = 32;\nconst TAG_LEN = 16;\n/** PKCS#8 wrapping of a raw X25519 private key (RFC 8410). */\nconst PKCS8_X25519_PREFIX = new Uint8Array([\n 0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, 0x04,\n 0x22, 0x04, 0x20,\n]);\n\n/** Noise's `SymmetricState`: the chaining key, the handshake hash and the cipher key. */\nexport class SymmetricState {\n private ck: Uint8Array;\n private h: Uint8Array;\n private k: CryptoKey | null = null;\n private n = 0;\n\n private constructor(h: Uint8Array) {\n this.ck = h;\n this.h = h;\n }\n\n static async initialize(): Promise<SymmetricState> {\n // A name of 32 bytes or fewer is used as the hash, zero-padded.\n const name = new TextEncoder().encode(PROTOCOL_NAME);\n const h = new Uint8Array(32);\n h.set(name);\n return new SymmetricState(h);\n }\n\n async mixHash(data: Uint8Array): Promise<void> {\n this.h = await sha256(concat(this.h, data));\n }\n\n async mixKey(inputKeyMaterial: Uint8Array): Promise<void> {\n const [ck, key] = await hkdf2(this.ck, inputKeyMaterial);\n this.ck = ck;\n this.k = await crypto.subtle.importKey('raw', key, 'AES-GCM', false, ['encrypt', 'decrypt']);\n this.n = 0;\n }\n\n async encryptAndHash(plaintext: Uint8Array): Promise<Uint8Array> {\n if (!this.k) throw new Error('Noise: no cipher key yet');\n const ciphertext = new Uint8Array(\n await crypto.subtle.encrypt(\n { name: 'AES-GCM', iv: noiseNonce(this.n), additionalData: this.h },\n this.k,\n plaintext,\n ),\n );\n this.n += 1;\n await this.mixHash(ciphertext);\n return ciphertext;\n }\n\n async decryptAndHash(ciphertext: Uint8Array): Promise<Uint8Array> {\n if (!this.k) throw new Error('Noise: no cipher key yet');\n const plaintext = new Uint8Array(\n await crypto.subtle.decrypt(\n { name: 'AES-GCM', iv: noiseNonce(this.n), additionalData: this.h },\n this.k,\n ciphertext,\n ),\n );\n this.n += 1;\n await this.mixHash(ciphertext);\n return plaintext;\n }\n\n /** The initiator-to-responder key, then the responder-to-initiator key. */\n split(): Promise<[Uint8Array, Uint8Array]> {\n return hkdf2(this.ck, new Uint8Array());\n }\n}\n\nexport interface X25519KeyPair {\n privateKey: CryptoKey;\n publicKey: Uint8Array;\n}\n\n/**\n * Start an NK handshake to `remoteStatic`. `fixedEphemeral` pins the\n * ephemeral secret, for the published test vectors only; leave it out.\n */\nexport async function initiate(\n remoteStatic: Uint8Array,\n prologue: Uint8Array,\n fixedEphemeral?: Uint8Array,\n): Promise<{\n message1: Uint8Array;\n finish(message2: Uint8Array): Promise<{ payload: Uint8Array; split: [Uint8Array, Uint8Array] }>;\n}> {\n const state = await SymmetricState.initialize();\n await state.mixHash(prologue);\n // <- s (known in advance)\n await state.mixHash(remoteStatic);\n // -> e, es\n const ephemeral = await x25519KeyPair(fixedEphemeral);\n await state.mixHash(ephemeral.publicKey);\n await state.mixKey(await dh(ephemeral.privateKey, remoteStatic));\n const message1 = concat(ephemeral.publicKey, await state.encryptAndHash(new Uint8Array()));\n\n return {\n message1,\n async finish(message2) {\n if (message2.length < DH_LEN + TAG_LEN) throw new Error('Noise: message 2 is too short');\n // <- e, ee\n const remoteEphemeral = message2.slice(0, DH_LEN);\n await state.mixHash(remoteEphemeral);\n await state.mixKey(await dh(ephemeral.privateKey, remoteEphemeral));\n const payload = await state.decryptAndHash(message2.slice(DH_LEN));\n return { payload, split: await state.split() };\n },\n };\n}\n\nexport async function x25519KeyPair(secret?: Uint8Array): Promise<X25519KeyPair> {\n if (!secret) {\n let pair: CryptoKeyPair;\n try {\n pair = (await crypto.subtle.generateKey({ name: 'X25519' }, true, [\n 'deriveBits',\n ])) as CryptoKeyPair;\n } catch (error) {\n throw new Error(\n 'Sealed transport needs WebCrypto with X25519, which this runtime lacks ' +\n `(${(error as Error)?.name ?? 'no crypto.subtle'}). Current browsers and Node have ` +\n 'it; in React Native, install react-native-quick-crypto as the global crypto.',\n );\n }\n const publicKey = new Uint8Array(await crypto.subtle.exportKey('raw', pair.publicKey));\n return { privateKey: pair.privateKey, publicKey };\n }\n const privateKey = await crypto.subtle.importKey(\n 'pkcs8',\n concat(PKCS8_X25519_PREFIX, secret),\n { name: 'X25519' },\n true,\n ['deriveBits'],\n );\n const jwk = await crypto.subtle.exportKey('jwk', privateKey);\n return { privateKey, publicKey: base64UrlDecode(jwk.x as string) };\n}\n\nexport async function dh(privateKey: CryptoKey, publicKey: Uint8Array): Promise<Uint8Array> {\n const peer = await crypto.subtle.importKey('raw', publicKey, { name: 'X25519' }, false, []);\n return new Uint8Array(\n await crypto.subtle.deriveBits({ name: 'X25519', public: peer } as EcdhKeyDeriveParams, privateKey, 256),\n );\n}\n\n/** Noise's AESGCM nonce: 32 zero bits, then the counter as a big-endian u64. */\nfunction noiseNonce(n: number): Uint8Array {\n const nonce = new Uint8Array(12);\n new DataView(nonce.buffer).setBigUint64(4, BigInt(n), false);\n return nonce;\n}\n\nasync function sha256(data: Uint8Array): Promise<Uint8Array> {\n return new Uint8Array(await crypto.subtle.digest('SHA-256', data));\n}\n\nasync function hmacSha256(key: Uint8Array, data: Uint8Array): Promise<Uint8Array> {\n const hmacKey = await crypto.subtle.importKey('raw', key, { name: 'HMAC', hash: 'SHA-256' }, false, [\n 'sign',\n ]);\n return new Uint8Array(await crypto.subtle.sign('HMAC', hmacKey, data));\n}\n\n/** Noise's `HKDF` with two outputs. */\nasync function hkdf2(chainingKey: Uint8Array, inputKeyMaterial: Uint8Array): Promise<[Uint8Array, Uint8Array]> {\n const temp = await hmacSha256(chainingKey, inputKeyMaterial);\n const first = await hmacSha256(temp, new Uint8Array([1]));\n const second = await hmacSha256(temp, concat(first, new Uint8Array([2])));\n return [first, second];\n}\n\nfunction base64UrlDecode(value: string): Uint8Array {\n const base64 = value.replace(/-/g, '+').replace(/_/g, '/');\n const binary = atob(base64 + '='.repeat((4 - (base64.length % 4)) % 4));\n return Uint8Array.from(binary, (char) => char.charCodeAt(0));\n}\n", "/**\n * Sealed transport: requests encrypted end to end to a TEE node's attested key.\n *\n * A node speaks plain HTTP and any TLS in front of it ends outside the enclave,\n * at whatever proxy or relay the operator runs, so everything crossing that hop\n * (the bearer token, JSON-RPC arguments, results) is readable there. Sealing\n * encrypts it so that only the attested TD can read it, and only the TD can\n * produce a response the client accepts.\n *\n * The client opens a session with a Noise NK handshake to the X25519 transport\n * key the node's quote commits to ({@link fetchAttestedTransportKey}). The\n * transport key only authenticates the node: the session keys come from both\n * sides' ephemeral keys, so once a session is gone (an hour at most) what was\n * sent in it cannot be opened, not even with the transport key. Each request\n * is sealed under the session; each response streams back in sealed frames, so\n * server-sent events are sealed too. WebSockets cannot be, and the node\n * refuses one inside the envelope with a 501.\n *\n * The node's side, and the wire format this implements byte for byte, are in\n * core's `crates/server/src/sealed.rs`:\n *\n * ```text\n * handshake Noise_NK_25519_AESGCM_SHA256, prologue \"calimero/sealed-http/v2\"\n * request POST /sealed/v2/handshake 0x02 || transport_pk || Noise message 1\n * response 0x02 || Noise message 2 (payload = session_id(16) || lifetime secs, u32 BE)\n * keys (req key, resp key) = Split()\n * exchange POST /sealed/v2\n * header 0x02 || session_id(16) || request_id (u64 BE; from 1, each used once)\n * request header || AES-256-GCM(req key, nonce = request_id || 0u32, aad = header, inner)\n * response frames, each u32 BE length || AES-256-GCM(resp key,\n * nonce = request_id || frame index (u32 BE, from 0), aad = header, kind || data)\n * kind 0 = head (JSON), 1 = body bytes, 2 = end; no end frame = cut short\n * inner u32 BE head length || head (JSON) || body\n * ```\n *\n * Use it by handing {@link createSealedFetch} to `MeroJsConfig.fetch`.\n */\n\nimport { concat, fromHex, hex } from '../crypto/internal.js';\nimport type { TeeAttestRequest, TeeAttestResponseData } from '../admin-api/admin-types.js';\nimport { initiate } from './noise.js';\nimport type { DcapCollateral } from './verify.js';\n\n/** Where a session is opened, relative to the node's base URL. */\nexport const HANDSHAKE_PATH = '/sealed/v2/handshake';\n/** Where sealed requests are posted, relative to the node's base URL. */\nexport const SEALED_PATH = '/sealed/v2';\n/** Content type of every sealed body. */\nexport const SEALED_CONTENT_TYPE = 'application/vnd.calimero.sealed';\n\nconst VERSION = 2;\nconst PROLOGUE = new TextEncoder().encode('calimero/sealed-http/v2');\nconst SESSION_ID_LEN = 16;\nconst HEADER_LEN = 1 + SESSION_ID_LEN + 8;\nconst FRAME_HEAD = 0;\nconst FRAME_DATA = 1;\nconst FRAME_END = 2;\n/**\n * The largest frame the node sends: a kind byte, at most 64 KiB of data, a tag.\n * Anything larger is refused before it is buffered, so a proxy cannot make the\n * client hold an unbounded frame it has yet to authenticate.\n */\nconst MAX_FRAME_LEN = 1 + 64 * 1024 + 16;\n/** Far more than a handshake reply (69 bytes) ever is. */\nconst MAX_HANDSHAKE_REPLY = 1024;\n/** Open a new session this long before the node would drop the current one. */\nconst RENEW_BEFORE_MS = 60_000;\nconst TRANSPORT_BINDING_DOMAIN = 'calimero.tee-attest.transport-key.v1';\nconst NULL_BODY_STATUSES = [101, 204, 205, 304];\n\nconst utf8 = new TextEncoder();\n\n/** The node no longer holds the transport key this client attested (it restarted). */\nexport class StaleTransportKeyError extends Error {\n name = 'StaleTransportKeyError';\n\n constructor() {\n super('The node no longer holds the attested transport key; attest again for the current one');\n }\n}\n\n/** The node refused the envelope itself, before anything inside it ran. */\nexport class SealedTransportError extends Error {\n name = 'SealedTransportError';\n\n constructor(\n public status: number,\n public code: string | undefined,\n message: string,\n /** How long the node asked to wait before retrying (`Retry-After`), if it did. */\n public retryAfterMs?: number,\n ) {\n super(message);\n }\n}\n\n/**\n * What the attest endpoint puts in report data bytes `32..64` when asked to\n * bind its transport key. `inner` is what would have been there otherwise: the\n * expected application hash, or 32 zero bytes when none was asked for.\n * Mirrors core's `attest_transport_binding`.\n */\nexport async function transportKeyBinding(\n inner: Uint8Array,\n transportKey: Uint8Array,\n): Promise<Uint8Array> {\n const digest = await crypto.subtle.digest(\n 'SHA-256',\n concat(utf8.encode(TRANSPORT_BINDING_DOMAIN), inner, transportKey),\n );\n return new Uint8Array(digest);\n}\n\nexport interface VerifyTransportQuote {\n /**\n * Verify `quoteB64` \u2014 never by asking the node being attested \u2014 and check\n * that its report data is `nonce || reportDataSuffix` (both hex). Resolve\n * true only when the quote is genuine, its measurements are ones you accept,\n * and the report data matches. {@link createQuoteVerifier} does all of this\n * in the page. `collateral` is what the node sent with the quote, when the\n * verifier asked for it.\n */\n (args: {\n quoteB64: string;\n nonce: string;\n reportDataSuffix: string;\n collateral?: DcapCollateral;\n }): Promise<boolean>;\n /**\n * Ask the node for the collateral its quote verifies against, and pass it\n * in as `collateral`. Off unless set, because a node that predates the field\n * refuses the request.\n */\n readonly includeCollateral?: boolean;\n}\n\n/**\n * Ask a node for its transport key and accept it only if a quote you verified\n * commits to it.\n *\n * `applicationHash` (hex), when given, is the bytecode hash you expect the\n * attested application to have; the node is asked to bind that application,\n * and the quote only verifies if it runs that exact bytecode.\n */\nexport async function fetchAttestedTransportKey(\n admin: { teeAttest(request: TeeAttestRequest): Promise<TeeAttestResponseData> },\n verify: VerifyTransportQuote,\n options: { applicationId?: string; applicationHash?: string } = {},\n): Promise<Uint8Array> {\n if ((options.applicationId === undefined) !== (options.applicationHash === undefined)) {\n throw new Error('applicationId and applicationHash are given together or not at all');\n }\n const nonce = hex(crypto.getRandomValues(new Uint8Array(32)));\n const attested = await admin.teeAttest({\n nonce,\n applicationId: options.applicationId,\n bindTransportKey: true,\n ...(verify.includeCollateral ? { includeCollateral: true } : {}),\n });\n if (!attested.transportPublicKey) {\n throw new Error('The node did not report a transport key; it predates sealed transport');\n }\n const transportKey = fromHex(attested.transportPublicKey, 'transportPublicKey', 32);\n const inner = options.applicationHash\n ? fromHex(options.applicationHash, 'applicationHash', 32)\n : new Uint8Array(32);\n const reportDataSuffix = hex(await transportKeyBinding(inner, transportKey));\n const verified = await verify({\n quoteB64: attested.quoteB64,\n nonce,\n reportDataSuffix,\n collateral: attested.collateral,\n });\n if (!verified) {\n throw new Error('The attestation did not verify, so its transport key is not trusted');\n }\n return transportKey;\n}\n\nexport interface AttestedSealedFetchOptions {\n /** The node's base URL. */\n baseUrl: string;\n /**\n * Decides whether the node's quote is one you trust \u2014 typically\n * {@link createQuoteVerifier}. Asked again whenever the node restarts.\n */\n verify: VerifyTransportQuote;\n /** Also require the node to run this application with this bytecode hash. */\n applicationId?: string;\n applicationHash?: string;\n /** Fetch that carries the attestation and the envelopes. Defaults to global `fetch`. */\n fetch?: typeof fetch;\n}\n\n/**\n * {@link createSealedFetch} for a node you know only by URL: it attests the\n * node's transport key with `verify`, over `POST /admin-api/tee/attest` (the\n * one call that needs no credential and is never sealed), and seals everything\n * else to it. A restarted node is attested again, through the same verifier.\n *\n * This is what to hand a {@link RelayClient} for a TEE relay, so the warrant\n * and the method's arguments reach only the attested TD:\n *\n * ```ts\n * const relay = new RelayClient({\n * relayUrl,\n * fetch: createAttestedSealedFetch({ baseUrl: relayUrl, verify: createQuoteVerifier(...) }),\n * ...\n * });\n * ```\n */\nexport function createAttestedSealedFetch(options: AttestedSealedFetchOptions): typeof fetch {\n const baseFetch = options.fetch ?? ((input, init) => fetch(input, init));\n const attestUrl = `${options.baseUrl.replace(/\\/+$/, '')}/admin-api/tee/attest`;\n const admin = {\n async teeAttest(request: TeeAttestRequest): Promise<TeeAttestResponseData> {\n const response = await baseFetch(attestUrl, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(request),\n });\n if (!response.ok) {\n throw new Error(`The node refused to attest: HTTP ${response.status}`);\n }\n const body = (await response.json()) as { data?: TeeAttestResponseData };\n if (!body.data) throw new Error('The attestation response is malformed');\n return body.data;\n },\n };\n return createSealedFetch({\n baseUrl: options.baseUrl,\n transportPublicKey: () =>\n fetchAttestedTransportKey(admin, options.verify, {\n applicationId: options.applicationId,\n applicationHash: options.applicationHash,\n }),\n fetch: baseFetch,\n });\n}\n\nexport interface SealedFetchOptions {\n /** The node's base URL, as given to `MeroJsConfig.baseUrl`. */\n baseUrl: string;\n /**\n * The node's attested transport key, or a function that attests and returns\n * it \u2014 typically `() => fetchAttestedTransportKey(admin, verify)`. Given a\n * function, a restarted node is handled: the key is attested again, through\n * your verifier, and the request retried once. Given bytes, a restart\n * surfaces as {@link StaleTransportKeyError}.\n */\n transportPublicKey: Uint8Array | (() => Promise<Uint8Array>);\n /** Fetch that carries the sealed envelope. Defaults to global `fetch`. */\n fetch?: typeof fetch;\n}\n\n/**\n * A `fetch` that seals every request to the node's attested transport key.\n * Pass it as `MeroJsConfig.fetch`.\n *\n * A request for any URL outside `baseUrl` is refused rather than sent in the\n * clear: falling back to plaintext would silently undo the point of sealing.\n */\nexport function createSealedFetch(options: SealedFetchOptions): typeof fetch {\n const base = new URL(options.baseUrl);\n const basePath = base.pathname.replace(/\\/+$/, '');\n const nodeUrl = `${base.origin}${basePath}`;\n const baseFetch = options.fetch ?? ((input, init) => fetch(input, init));\n const attest =\n typeof options.transportPublicKey === 'function' ? options.transportPublicKey : undefined;\n let transportKey: Promise<Uint8Array> = attest\n ? Promise.resolve().then(attest)\n : Promise.resolve(options.transportPublicKey as Uint8Array);\n // Attesting starts now, before any request awaits it. A node that fails to\n // attest must surface on the request that needed the key, not earlier as an\n // unhandled rejection; the failure is still thrown to every awaiter.\n transportKey.catch(() => undefined);\n let session: Promise<Session> | undefined;\n\n const openSession = async (): Promise<Session> => {\n try {\n return await handshake(baseFetch, nodeUrl, await checkedKey(transportKey));\n } catch (error) {\n if (!(error instanceof StaleTransportKeyError) || !attest) throw error;\n transportKey = Promise.resolve().then(attest);\n return handshake(baseFetch, nodeUrl, await checkedKey(transportKey));\n }\n };\n /** The session to use now, and the promise it came from, to name it if it turns out stale. */\n const currentSession = async (): Promise<[Session, Promise<Session>]> => {\n const pending = session ?? renew(undefined);\n const open = await pending;\n if (Date.now() < open.renewAt) return [open, pending];\n const renewed = renew(pending);\n return [await renewed, renewed];\n };\n // Replace the session only if it is still the one found stale, so concurrent\n // requests that all find it stale open one new session between them.\n const renew = (stale: Promise<Session> | undefined): Promise<Session> => {\n if (session === stale) {\n const opening = openSession();\n session = opening;\n opening.catch(() => {\n if (session === opening) session = undefined;\n });\n }\n return session as Promise<Session>;\n };\n\n return async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {\n const request = new Request(input, init);\n const url = new URL(request.url);\n if (url.origin !== base.origin || !url.pathname.startsWith(`${basePath}/`)) {\n throw new Error(`Refusing to send ${url.href} outside the sealed node at ${options.baseUrl}`);\n }\n const headers: Array<[string, string]> = [];\n request.headers.forEach((value, name) => headers.push([name, value]));\n const head = { method: request.method, path: url.pathname.slice(basePath.length) + url.search, headers };\n const body = new Uint8Array(await request.arrayBuffer());\n\n const send = async (open: Session): Promise<Response> => {\n const { requestId, header, envelope } = await sealRequest(open, head, body);\n const response = await baseFetch(`${nodeUrl}${SEALED_PATH}`, {\n method: 'POST',\n headers: { 'content-type': SEALED_CONTENT_TYPE },\n body: envelope,\n signal: request.signal,\n });\n if (response.headers.get('content-type') !== SEALED_CONTENT_TYPE) {\n throw await envelopeRefusal(response);\n }\n return openResponse(response, open.responseKey, header, requestId);\n };\n\n const [open, pending] = await currentSession();\n try {\n return await send(open);\n } catch (error) {\n // The node refused the envelope before running anything in it, so the\n // request can be sent again safely under a new session.\n if (!(error instanceof SealedTransportError) || error.code !== 'unknown_session') throw error;\n return send(await renew(pending));\n }\n };\n}\n\nasync function checkedKey(key: Promise<Uint8Array>): Promise<Uint8Array> {\n const bytes = await key;\n if (bytes.length !== 32) throw new Error('transportPublicKey must be 32 bytes');\n return bytes;\n}\n\nexport interface Session {\n requestKey: CryptoKey;\n responseKey: CryptoKey;\n nextRequestId: number;\n renewAt: number;\n header(requestId: number): Uint8Array;\n}\n\nasync function handshake(\n baseFetch: typeof fetch,\n nodeUrl: string,\n transportKey: Uint8Array,\n): Promise<Session> {\n try {\n return await handshakeOnce(baseFetch, nodeUrl, transportKey);\n } catch (error) {\n // The node limits how fast sessions open. A refused handshake opened\n // nothing, so trying once more after the pause it asks for is safe.\n if (!(error instanceof SealedTransportError) || error.code !== 'busy') throw error;\n await new Promise((resolve) => setTimeout(resolve, error.retryAfterMs ?? 1000));\n return handshakeOnce(baseFetch, nodeUrl, transportKey);\n }\n}\n\nasync function handshakeOnce(\n baseFetch: typeof fetch,\n nodeUrl: string,\n transportKey: Uint8Array,\n): Promise<Session> {\n const noise = await initiate(transportKey, PROLOGUE);\n const response = await baseFetch(`${nodeUrl}${HANDSHAKE_PATH}`, {\n method: 'POST',\n headers: { 'content-type': SEALED_CONTENT_TYPE },\n body: concat(new Uint8Array([VERSION]), transportKey, noise.message1),\n });\n if (response.headers.get('content-type') !== SEALED_CONTENT_TYPE) {\n throw await envelopeRefusal(response);\n }\n const reply = await readAtMost(response, MAX_HANDSHAKE_REPLY);\n return sessionFrom(noise, reply);\n}\n\n/** Finish the handshake with the node's reply. Exported for the published vectors. */\nexport async function sessionFrom(\n noise: Awaited<ReturnType<typeof initiate>>,\n reply: Uint8Array,\n): Promise<Session> {\n if (reply[0] !== VERSION) throw new Error('The handshake reply is malformed');\n let finished: Awaited<ReturnType<(typeof noise)['finish']>>;\n try {\n finished = await noise.finish(reply.slice(1));\n } catch {\n throw new Error('The handshake reply did not open: it did not come from the attested node');\n }\n const { payload, split } = finished;\n if (payload.length !== SESSION_ID_LEN + 4) throw new Error('The handshake reply is malformed');\n const sessionId = payload.slice(0, SESSION_ID_LEN);\n const lifetimeSecs = new DataView(payload.buffer, payload.byteOffset).getUint32(SESSION_ID_LEN, false);\n const [requestKey, responseKey] = await Promise.all([\n crypto.subtle.importKey('raw', split[0], 'AES-GCM', false, ['encrypt']),\n crypto.subtle.importKey('raw', split[1], 'AES-GCM', false, ['decrypt']),\n ]);\n split[0].fill(0);\n split[1].fill(0);\n return {\n requestKey,\n responseKey,\n nextRequestId: 1,\n renewAt: Date.now() + lifetimeSecs * 1000 - RENEW_BEFORE_MS,\n header(requestId) {\n const header = new Uint8Array(HEADER_LEN);\n header[0] = VERSION;\n header.set(sessionId, 1);\n new DataView(header.buffer).setBigUint64(1 + SESSION_ID_LEN, BigInt(requestId), false);\n return header;\n },\n };\n}\n\n/** Seal one request under the session's next request id. */\nexport async function sealRequest(\n session: Session,\n head: RequestHead,\n body: Uint8Array,\n): Promise<{ requestId: number; header: Uint8Array; envelope: Uint8Array }> {\n const requestId = session.nextRequestId;\n session.nextRequestId += 1;\n const header = session.header(requestId);\n const sealed = await crypto.subtle.encrypt(\n { name: 'AES-GCM', iv: nonce(requestId, 0), additionalData: header },\n session.requestKey,\n joinInner(head, body),\n );\n return { requestId, header, envelope: concat(header, new Uint8Array(sealed)) };\n}\n\n/** `request_id || index`, as the node numbers them. */\nfunction nonce(requestId: number, index: number): Uint8Array {\n const bytes = new Uint8Array(12);\n const view = new DataView(bytes.buffer);\n view.setBigUint64(0, BigInt(requestId), false);\n view.setUint32(8, index, false);\n return bytes;\n}\n\nexport interface RequestHead {\n method: string;\n path: string;\n headers: Array<[string, string]>;\n}\n\nexport interface ResponseHead {\n status: number;\n headers: Array<[string, string]>;\n}\n\nfunction joinInner(head: RequestHead, body: Uint8Array): Uint8Array {\n const encoded = utf8.encode(JSON.stringify(head));\n const len = new Uint8Array(4);\n new DataView(len.buffer).setUint32(0, encoded.length, false);\n return concat(len, encoded, body);\n}\n\n/**\n * Open a sealed response as it streams: wait for its head, then hand back a\n * `Response` whose body opens each frame as it arrives. A frame that does not\n * open, arrives out of order, or a stream that stops before its end frame\n * errors the body rather than passing anything unauthenticated on.\n */\nexport async function openResponse(\n response: Response,\n responseKey: CryptoKey,\n header: Uint8Array,\n requestId: number,\n): Promise<Response> {\n if (!response.body) throw new Error('The sealed response has no body');\n const frames = frameReader(response.body.getReader());\n let index = 0;\n const next = async (): Promise<{ kind: number; data: Uint8Array } | null> => {\n const sealed = await frames.next();\n if (!sealed) return null;\n let plain: Uint8Array;\n try {\n plain = new Uint8Array(\n await crypto.subtle.decrypt(\n { name: 'AES-GCM', iv: nonce(requestId, index), additionalData: header },\n responseKey,\n sealed,\n ),\n );\n } catch {\n throw new Error('A sealed response frame did not open: it was not sealed by the attested node');\n }\n index += 1;\n return { kind: plain[0], data: plain.slice(1) };\n };\n\n const first = await next();\n if (!first || first.kind !== FRAME_HEAD) throw new Error('The sealed response is malformed');\n const head = JSON.parse(new TextDecoder().decode(first.data)) as ResponseHead;\n if (NULL_BODY_STATUSES.includes(head.status)) {\n await frames.cancel();\n return new Response(null, { status: head.status, headers: head.headers });\n }\n\n const body = new ReadableStream<Uint8Array>({\n async pull(controller) {\n try {\n const frame = await next();\n if (!frame) {\n controller.error(new Error('The sealed response was cut short'));\n } else if (frame.kind === FRAME_DATA) {\n controller.enqueue(frame.data);\n } else if (frame.kind === FRAME_END) {\n controller.close();\n await frames.cancel();\n } else {\n controller.error(new Error('The sealed response is malformed'));\n }\n } catch (error) {\n controller.error(error);\n }\n },\n cancel() {\n return frames.cancel();\n },\n });\n return new Response(body, { status: head.status, headers: head.headers });\n}\n\n/** Split a byte stream into length-prefixed frames. */\nfunction frameReader(reader: ReadableStreamDefaultReader<Uint8Array>): {\n next(): Promise<Uint8Array | null>;\n cancel(): Promise<void>;\n} {\n let buffer = new Uint8Array();\n const fill = async (length: number): Promise<boolean> => {\n while (buffer.length < length) {\n const { done, value } = await reader.read();\n if (done) return false;\n buffer = concat(buffer, value);\n }\n return true;\n };\n return {\n async next() {\n if (!(await fill(4))) return null;\n const length = new DataView(buffer.buffer, buffer.byteOffset).getUint32(0, false);\n if (length > MAX_FRAME_LEN) {\n await reader.cancel();\n throw new Error('The sealed response is malformed: a frame is larger than the node sends');\n }\n if (!(await fill(4 + length))) return null;\n const frame = buffer.slice(4, 4 + length);\n buffer = buffer.slice(4 + length);\n return frame;\n },\n cancel: () => reader.cancel(),\n };\n}\n\n/** Read a body, refusing one longer than `limit` rather than buffering it. */\nasync function readAtMost(response: Response, limit: number): Promise<Uint8Array> {\n if (!response.body) return new Uint8Array();\n const reader = response.body.getReader();\n let bytes = new Uint8Array();\n for (;;) {\n const { done, value } = await reader.read();\n if (done) return bytes;\n bytes = concat(bytes, value);\n if (bytes.length > limit) {\n await reader.cancel();\n throw new Error('The handshake reply is malformed: it is too long');\n }\n }\n}\n\nasync function envelopeRefusal(response: Response): Promise<Error> {\n let code: string | undefined;\n let message = `Sealed request refused: HTTP ${response.status}`;\n try {\n const body = (await response.json()) as { error?: { code?: string; message?: string } };\n code = body?.error?.code;\n if (body?.error?.message) message = `Sealed request refused: ${body.error.message}`;\n } catch {\n // Not the node's JSON refusal \u2014 a proxy's page, most likely. The status is\n // all that can honestly be reported.\n }\n if (code === 'stale_transport_key') return new StaleTransportKeyError();\n const retryAfter = Number(response.headers.get('retry-after'));\n const retryAfterMs =\n Number.isFinite(retryAfter) && retryAfter > 0 ? Math.min(retryAfter, 5) * 1000 : undefined;\n return new SealedTransportError(response.status, code, message, retryAfterMs);\n}\n", "/**\n * The second way to log in: sign in to Calimero Cloud instead of connecting to\n * a node.\n *\n * # Two connection shapes, one SDK\n *\n * `createMeroJs({ baseUrl })` is the node connection: point at a node, hold a\n * credential on it, and drive its admin API and JSON-RPC. It assumes the caller\n * has a node \u2014 their own, or one they have an account on.\n *\n * `connectCloud(...)` is for the caller who has neither. They sign in with the\n * identity they already use (Google, through the cloud), and the cloud answers\n * with an attested TEE relay serving their namespace. Writes then go through\n * delegated execution: signed here, run there, attributed here.\n *\n * The two are not alternatives to be picked at random \u2014 they answer different\n * questions. \"Which node do I talk to?\" has an answer for a desktop app and no\n * answer at all for a web app whose user has never installed anything. This\n * function is that missing answer, which is why the login step it replaces is\n * the *node URL prompt* rather than the authentication.\n *\n * # What it does not do\n *\n * # Sealing the relay\n *\n * TLS in front of a relay ends outside its TD, at the host's ingress, where\n * the warrant and the method's arguments are readable. Pass `seal` (a quote\n * verifier) and every call to the relay is sealed to the key its attested TD\n * holds, so they are readable only inside it.\n *\n * It does not read state. A relay writes on the author's behalf; reads still\n * come from a node the client can query, or from the app's own projection of\n * the events it receives. `connectCloud` is deliberately narrow about this\n * rather than presenting a half-working `MeroJs` whose `admin` and `rpc` would\n * need a credential nobody holds.\n */\n\nimport { CloudClient } from './cloud-client.js';\nimport type { CloudClientConfig, CloudNamespace, CloudRelay } from './cloud-client.js';\nimport { RelayClient } from '../relay/relay-client.js';\nimport type { IntentResult, NonceSource } from '../relay/index.js';\nimport { createLocalStorageNonceSource, createMemoryNonceSource } from '../relay/nonce-source.js';\nimport { derivePublicKey, hex } from '../crypto/internal.js';\nimport { createAttestedSealedFetch } from '../sealed/sealed.js';\nimport type { VerifyTransportQuote } from '../sealed/sealed.js';\n\nexport interface ConnectCloudOptions {\n /** Defaults to the hosted cloud. */\n cloudBaseUrl?: string;\n /** A stored MDMA session token. Provide this or `googleIdToken`. */\n sessionToken?: string;\n /** A Google ID token to exchange for a session. Provide this or `sessionToken`. */\n googleIdToken?: string;\n /** Persist a new or rolling-refreshed session. See {@link CloudClientConfig.onSession}. */\n onSession?: CloudClientConfig['onSession'];\n\n /** The author's account, hex \u2014 whose writes these will be. */\n authorAccount: string;\n /** The author's `AccountProof<DeviceCert>`, hex-encoded borsh. */\n authorProof: string;\n /** The author device's ed25519 signing secret, hex. Never transmitted. */\n deviceSecret: string;\n\n /**\n * Which namespace to write in.\n *\n * Optional only when the user owns exactly one: guessing among several would\n * pick a tenant for them, and the failure would be a write landing in the\n * wrong place rather than an error.\n */\n namespaceId?: string;\n\n /**\n * Where nonces come from.\n *\n * Defaults to `localStorage`, keyed by the author's device public key, so a\n * reload continues the sequence instead of replaying it. Pass\n * {@link createMemoryNonceSource} explicitly for a process that owns its\n * whole sequence, or your own source to coordinate across tabs.\n */\n nonces?: NonceSource;\n /** Seconds a minted warrant stays presentable. Defaults to 300. */\n ttlSeconds?: number;\n /**\n * Seal every call to the relay to its attested TEE, verified with this \u2014 for\n * example {@link createQuoteVerifier} with the MRTDs you trust. The warrant\n * and the method's arguments then reach only the relay's TD, not whatever\n * terminates TLS in front of it, and the relay is one this client checked\n * itself rather than one the cloud vouched for. Calls to the cloud are not\n * sealed; they carry no intent.\n *\n * Without it, calls to the relay are plain HTTPS.\n */\n seal?: VerifyTransportQuote;\n fetch?: typeof fetch;\n timeoutMs?: number;\n}\n\n/** A signed-in cloud session with a relay ready to write through. */\nexport interface CloudConnection {\n /** The cloud API, for namespaces, billing, HA and sign-out. */\n cloud: CloudClient;\n /** The relay chosen for this namespace. */\n relay: RelayClient;\n /** The namespace writes will land in. */\n namespaceId: string;\n namespace: CloudNamespace;\n /** What the cloud reported about the chosen relay. */\n relayInfo: CloudRelay;\n /** Run one method in one context, on the author's behalf. */\n execute<T = unknown>(\n contextId: string,\n method: string,\n argsJson?: unknown,\n ): Promise<IntentResult<T>>;\n}\n\n/**\n * Sign in to the cloud and return a connection that can write.\n *\n * Throws rather than returning a half-usable connection, and the message says\n * which step is missing \u2014 no namespace, several to choose between, HA not\n * enabled, or no relay holding the authorship grant. Every one of those is\n * something the user has to go and do, and a client that reports them alike\n * cannot tell them what.\n */\nexport async function connectCloud(options: ConnectCloudOptions): Promise<CloudConnection> {\n if (!options.sessionToken && !options.googleIdToken) {\n throw new Error('connectCloud needs either a sessionToken or a googleIdToken');\n }\n\n const cloud = new CloudClient({\n cloudBaseUrl: options.cloudBaseUrl,\n sessionToken: options.sessionToken,\n onSession: options.onSession,\n fetch: options.fetch,\n timeoutMs: options.timeoutMs,\n });\n\n if (!options.sessionToken && options.googleIdToken) {\n await cloud.signInWithGoogle(options.googleIdToken);\n }\n\n const namespaces = await cloud.getMyNamespaces();\n const namespace = pickNamespace(namespaces, options.namespaceId);\n\n // One read, used for both the choice and the diagnostic. `findExecutingRelay`\n // would fetch again to explain itself, and a second call can disagree with the\n // first \u2014 reporting \"waiting for the grant\" about a relay that was already\n // usable a moment ago.\n const relays = await cloud.getNamespaceRelays(namespace.namespaceId);\n const relayInfo =\n relays.find((r) => r.authorshipReady && r.relayUrl !== null && r.executorAccount !== null) ??\n null;\n if (!relayInfo) {\n throw new Error(explainNoRelay(namespace, relays));\n }\n\n const relay = new RelayClient({\n // Checked by `findExecutingRelay`, which is the only thing that returns a\n // relay \u2014 narrowed here rather than re-validated, so the invariant lives in\n // one place.\n relayUrl: relayInfo.relayUrl as string,\n executorAccount: relayInfo.executorAccount as string,\n authorAccount: options.authorAccount,\n authorProof: options.authorProof,\n deviceSecret: options.deviceSecret,\n nonces: options.nonces ?? (await defaultNonceSource(options.deviceSecret)),\n ttlSeconds: options.ttlSeconds,\n // Only the relay is sealed: a sealed fetch is bound to one node and refuses\n // any other URL, and the cloud calls above carry no intent.\n fetch: options.seal\n ? createAttestedSealedFetch({\n baseUrl: relayInfo.relayUrl as string,\n verify: options.seal,\n fetch: options.fetch,\n })\n : options.fetch,\n timeoutMs: options.timeoutMs,\n });\n\n return {\n cloud,\n relay,\n namespaceId: namespace.namespaceId,\n namespace,\n relayInfo,\n execute: (contextId, method, argsJson) => relay.execute(contextId, method, argsJson),\n };\n}\n\n/** The one namespace to write in, or an error naming the actual ambiguity. */\nfunction pickNamespace(namespaces: CloudNamespace[], requested?: string): CloudNamespace {\n if (requested) {\n const found = namespaces.find((n) => n.namespaceId === requested);\n if (!found) {\n throw new Error(\n `this account owns no namespace ${requested}; it owns: ${\n namespaces.map((n) => n.namespaceId).join(', ') || '(none)'\n }`,\n );\n }\n return found;\n }\n if (namespaces.length === 0) {\n throw new Error(\n 'this account owns no namespaces in the cloud: claim one first, then enable HA on it so a relay is assigned',\n );\n }\n if (namespaces.length > 1) {\n throw new Error(\n `this account owns ${namespaces.length} namespaces \u2014 pass namespaceId to say which: ${namespaces\n .map((n) => n.namespaceId)\n .join(', ')}`,\n );\n }\n return namespaces[0];\n}\n\n/**\n * Why no relay can write here \u2014 the three states, kept apart.\n *\n * They are three different asks of the user (turn HA on, wait, or get an admin\n * to grant a capability) and one message for all of them would send them to\n * the wrong one.\n */\nfunction explainNoRelay(namespace: CloudNamespace, relays: CloudRelay[]): string {\n if (relays.length === 0) {\n return namespace.haStatus === 'enabled'\n ? `namespace ${namespace.namespaceId} has HA enabled but no relay has been assigned to it yet \u2014 retry shortly`\n : `namespace ${namespace.namespaceId} has no relays: enable HA on it in the cloud to have an attested node assigned`;\n }\n const waitingOnGrant = relays.filter((r) => !r.authorshipReady);\n if (waitingOnGrant.length === relays.length) {\n // Name the call, not just the capability. The grant is a governance op only\n // a namespace admin can publish, so this message is read by someone who has\n // to go ask for it \u2014 and \"grant CAN_AUTHOR_ON_BEHALF\" alone leaves them\n // relaying a constant to an admin who then has to work out the command.\n // `openToDelegatedExecution` is the durable half (every relay assigned from\n // then on lands open), `grantAuthorship` the one for relays already here.\n return (\n `every relay on namespace ${namespace.namespaceId} is waiting for the authorship grant: ` +\n `an admin of the namespace must grant CAN_AUTHOR_ON_BEHALF to ${\n waitingOnGrant.map((r) => r.executorAccount ?? r.peerId).join(', ') || 'the relay account'\n } \u2014 with AdminClient.grantAuthorship(namespaceId, account) for these relays, and ` +\n `AdminClient.openToDelegatedExecution(namespaceId) so relays assigned later need no further op`\n );\n }\n return `namespace ${namespace.namespaceId} has ${relays.length} relay(s), but none reported both a URL and an executor account yet \u2014 retry shortly`;\n}\n\n/**\n * A persisted nonce source keyed by the author's device public key.\n *\n * The key is the *public* key, derived locally: a per-device key is required\n * (two devices of one account are independent replicas) and the secret must\n * never end up in a storage key an extension or another script can enumerate.\n *\n * Falls back to memory where there is no `localStorage` \u2014 Node, a worker, an\n * edge runtime. Those are processes that own their whole sequence, which is\n * exactly the case a memory counter is correct for.\n */\nasync function defaultNonceSource(deviceSecret: string): Promise<NonceSource> {\n const key = `mero-js:warrant-nonce:${hex(await derivePublicKey(deviceSecret))}`;\n try {\n return createLocalStorageNonceSource(key);\n } catch {\n return createMemoryNonceSource(1);\n }\n}\n", "/**\n * Verifying a TEE node's quote in the page, before trusting the key it binds.\n *\n * A quote proves what runs in a TD only once it has been checked: its\n * signatures against Intel's root, the TCB status of the platform, the\n * measurements of the image, and that its report data is the nonce and the\n * binding this client asked for. {@link createQuoteVerifier} does all of that\n * here, with nothing but the node: the node hands over the Intel-signed\n * collateral with its quote (`includeCollateral`), and a node cannot forge\n * collateral, only choose which valid collateral to serve.\n *\n * The DCAP verification itself \u2014 parsing the quote, checking its signature\n * chain and appraising its TCB level against the collateral \u2014 comes from a\n * library you pass in, normally `verify` from `@phala/dcap-qvl`, a pure\n * JavaScript implementation that runs in browsers, Node and React Native. It\n * is passed in rather than bundled so this SDK keeps no runtime dependencies,\n * and applications that never talk to a TEE node pay nothing for it.\n */\n\nimport { hex } from '../crypto/internal.js';\nimport type { VerifyTransportQuote } from './sealed.js';\n\n/**\n * The collateral a quote is verified against \u2014 TCB info, QE identity, CRLs and\n * their chains \u2014 in dcap-qvl's form: byte fields are hex strings or byte\n * arrays. What `/admin-api/tee/attest` returns with `includeCollateral`, and\n * what `@phala/dcap-qvl`'s `getCollateral` fetches.\n */\nexport interface DcapCollateral {\n pck_crl_issuer_chain: string;\n root_ca_crl: number[] | string;\n pck_crl: number[] | string;\n tcb_info_issuer_chain: string;\n tcb_info: string;\n tcb_info_signature: number[] | string;\n qe_identity_issuer_chain: string;\n qe_identity: string;\n qe_identity_signature: number[] | string;\n}\n\n/** The fields of a TDX report this verifier reads. */\ninterface TdReport {\n mrTd: Uint8Array;\n rtMr0: Uint8Array;\n rtMr1: Uint8Array;\n rtMr2: Uint8Array;\n rtMr3: Uint8Array;\n reportData: Uint8Array;\n}\n\n/** What a DCAP verifier returns for a quote whose signature chain verified. */\nexport interface DcapVerifiedReport {\n status: string;\n advisory_ids?: string[];\n report: { type: string; data: unknown };\n}\n\n/**\n * Verify a quote's signature chain against `collateral` as of `nowSecs`, and\n * appraise its TCB level. Throws when it does not verify. `verify` from\n * `@phala/dcap-qvl` has exactly this shape.\n */\nexport type DcapVerify = (\n quote: Uint8Array,\n collateral: DcapCollateral,\n nowSecs: number,\n) => DcapVerifiedReport;\n\nexport interface QuoteVerifierOptions {\n /** DCAP verification: `verify` from `@phala/dcap-qvl`. */\n dcapVerify: DcapVerify;\n /**\n * MRTDs (hex) of the images trusted. Required: the MRTD names the image a TD\n * booted, and a quote that proves only \"some genuine TD\" proves nothing\n * about which code reads the traffic.\n */\n allowedMrtd: string[];\n /** RTMR allowlists (hex); a register is not checked when its list is left out. */\n allowedRtmr0?: string[];\n allowedRtmr1?: string[];\n allowedRtmr2?: string[];\n allowedRtmr3?: string[];\n /**\n * TCB statuses accepted. Defaults to `['UpToDate']`. `Revoked` is refused\n * even when listed.\n */\n allowedTcbStatuses?: string[];\n /**\n * Where to get collateral when the node sends none (it predates\n * `includeCollateral`): for example\n * `(quote) => getCollateral(PHALA_PCCS_URL, quote)` from `@phala/dcap-qvl`.\n * Without it, such a node's quote is refused.\n */\n fetchCollateral?: (quote: Uint8Array) => Promise<DcapCollateral>;\n /** The current time in milliseconds; defaults to `Date.now`. For tests. */\n now?: () => number;\n}\n\n/** A verifier for {@link fetchAttestedTransportKey}, which asks the node for collateral. */\nexport type QuoteVerifier = VerifyTransportQuote & { readonly includeCollateral: true };\n\nconst MEASUREMENT_HEX = /^[0-9a-f]{96}$/;\n\n/**\n * A verifier that checks a TEE node's quote here, in the page, and accepts it\n * only when every check passes:\n *\n * - its signatures verify against Intel's root, with collateral that is valid\n * now;\n * - the platform's TCB status is one allowed (`UpToDate` by default), and\n * never `Revoked`;\n * - the MRTD, and any RTMRs given, are ones allowed;\n * - its report data is the nonce followed by the binding the client computed.\n *\n * Any failure rejects with the reason; nothing is trusted from a quote that\n * does not pass. Mock quotes never pass: they carry no signature.\n *\n * ```ts\n * import { verify as dcapVerify } from '@phala/dcap-qvl';\n *\n * const verifier = createQuoteVerifier({ dcapVerify, allowedMrtd: [trustedMrtd] });\n * const fetch = createSealedFetch({\n * baseUrl,\n * transportPublicKey: () => fetchAttestedTransportKey(admin, verifier),\n * });\n * ```\n */\nexport function createQuoteVerifier(options: QuoteVerifierOptions): QuoteVerifier {\n const allowedMrtd = measurements(options.allowedMrtd, 'allowedMrtd');\n if (allowedMrtd.length === 0) {\n throw new Error('allowedMrtd is empty, so no quote would ever be accepted');\n }\n const allowedRtmr = [\n measurements(options.allowedRtmr0 ?? [], 'allowedRtmr0'),\n measurements(options.allowedRtmr1 ?? [], 'allowedRtmr1'),\n measurements(options.allowedRtmr2 ?? [], 'allowedRtmr2'),\n measurements(options.allowedRtmr3 ?? [], 'allowedRtmr3'),\n ];\n const allowedStatuses = options.allowedTcbStatuses ?? ['UpToDate'];\n const now = options.now ?? Date.now;\n\n const verifier = async ({\n quoteB64,\n nonce,\n reportDataSuffix,\n collateral,\n }: Parameters<VerifyTransportQuote>[0]): Promise<boolean> => {\n const quote = fromBase64(quoteB64);\n const against = collateral ?? (await options.fetchCollateral?.(quote));\n if (!against) {\n throw new Error(\n 'The node sent no collateral for its quote, and no fetchCollateral was given to get it',\n );\n }\n\n let verified: DcapVerifiedReport;\n try {\n verified = options.dcapVerify(quote, against, Math.floor(now() / 1000));\n } catch (error) {\n throw new Error(`The quote did not verify: ${(error as Error)?.message ?? String(error)}`);\n }\n\n if (verified.status === 'Revoked' || !allowedStatuses.includes(verified.status)) {\n throw new Error(`The platform's TCB status is ${verified.status}, which is not accepted`);\n }\n const td = tdReport(verified.report);\n if (!allowedMrtd.includes(hex(td.mrTd))) {\n throw new Error(`MRTD ${hex(td.mrTd)} is not an image this verifier trusts`);\n }\n const registers = [td.rtMr0, td.rtMr1, td.rtMr2, td.rtMr3];\n registers.forEach((register, index) => {\n const allowed = allowedRtmr[index];\n if (allowed.length > 0 && !allowed.includes(hex(register))) {\n throw new Error(`RTMR${index} ${hex(register)} is not one this verifier trusts`);\n }\n });\n if (hex(td.reportData) !== `${nonce}${reportDataSuffix}`.toLowerCase()) {\n throw new Error('The quote does not commit to this nonce and binding');\n }\n return true;\n };\n return Object.assign(verifier, { includeCollateral: true as const });\n}\n\nfunction measurements(values: string[], label: string): string[] {\n return values.map((value) => {\n const normalized = value.trim().toLowerCase();\n if (!MEASUREMENT_HEX.test(normalized)) {\n throw new Error(`${label} holds ${JSON.stringify(value)}, which is not a 48-byte hex measurement`);\n }\n return normalized;\n });\n}\n\n/** The TD report of a TDX quote (1.0 or 1.5); anything else is refused. */\nfunction tdReport(report: DcapVerifiedReport['report']): TdReport {\n const data = report.data as (TdReport & { base?: TdReport }) | undefined;\n if (report.type === 'td10' && data) return data;\n if (report.type === 'td15' && data?.base) return data.base;\n throw new Error(`The quote is a ${report.type} quote, not a TDX one`);\n}\n\nfunction fromBase64(value: string): Uint8Array {\n const binary = atob(value);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);\n return bytes;\n}\n", "/**\n * Sign a namespace governance op from the SDK \u2014 specifically the membership op a\n * keyholder needs to join without running a node.\n *\n * `admitJoin` carries an already-signed op to a node the inviter named as an\n * admitter. Producing that op is the piece a keyholder could not do before: the\n * op must be signed by the device key in the credential it carries, because every\n * peer checks `signer == credential.statement.sign_pk` when applying a join. An\n * admitter can relay it and can never author it.\n *\n * The encoding is borsh, hand-written to match core's structs. Correctness is not\n * asserted against bytes recorded here \u2014 a layout transcribed by eye is exactly\n * how a golden ends up confidently wrong. It is asserted by a node accepting the\n * op, which is what the e2e does.\n */\nimport type { SignedGroupOpenInvitation } from '../admin-api/admin-types.js';\nimport {\n concat,\n derivePublicKey,\n fromHex,\n hex,\n importSigningKey,\n u32le,\n u64le,\n} from '../crypto/internal.js';\n\n/** Core's `SIGNED_NAMESPACE_OP_SCHEMA_VERSION`. A node refuses any other value. */\nexport const SIGNED_NAMESPACE_OP_SCHEMA_VERSION = 8;\n\n/** Domain prefixed to the signable bytes; core's `NAMESPACE_GOVERNANCE_SIGN_DOMAIN`. */\nconst NAMESPACE_SIGN_DOMAIN = new TextEncoder().encode('calimero.namespace.v1');\n\n/** `NamespaceOp::Root` is the first variant. */\nconst NAMESPACE_OP_ROOT = 0;\n\n/**\n * `RootOp` discriminants, in declaration order. Only the two join variants are\n * emitted here; the rest are listed so the numbering is checkable against core\n * rather than inferred from the two in use.\n */\nconst ROOT_OP = {\n GroupCreated: 0,\n GroupReparented: 1,\n GroupDeleted: 2,\n AdminChanged: 3,\n PolicyUpdated: 4,\n MemberJoined: 5,\n KeyDelivery: 6,\n MemberJoinedOpen: 7,\n MemberJoinedAt: 8,\n NamespaceCreated: 9,\n MemberJoinedViaTeeAttestation: 10,\n} as const;\n\n/** A required `[u8; 32]`, as JSON carries it: 32 byte values. */\nfunction bytes32(value: readonly number[], label: string): Uint8Array {\n if (value.length !== 32) {\n throw new Error(`${label} must be 32 bytes, got ${value.length}`);\n }\n return new Uint8Array(value);\n}\n\n/**\n * Borsh `Option<[u8; 32]>` from the byte array JSON carries it as.\n *\n * `[u8; 32]` crosses JSON as 32 numbers, not as hex \u2014 the hex spelling belongs to\n * `AccountId`, which has its own representation. Encoding one of these from a hex\n * string would silently produce the wrong 32 bytes.\n */\nfunction optionalBytes32(\n value: readonly number[] | null | undefined,\n label: string,\n): Uint8Array {\n if (value === null || value === undefined) {\n return new Uint8Array([0]);\n }\n if (value.length !== 32) {\n throw new Error(`${label} must be 32 bytes, got ${value.length}`);\n }\n return concat(new Uint8Array([1]), new Uint8Array(value));\n}\n\n/** Borsh `String`: u32 length then UTF-8 bytes. */\nfunction borshString(value: string): Uint8Array {\n const bytes = new TextEncoder().encode(value);\n return concat(u32le(bytes.length), bytes);\n}\n\n\n/**\n * Borsh-encode a `SignedGroupOpenInvitation`.\n *\n * Re-encoded rather than passed through, because it has to go inside the op that\n * gets signed. That makes this function version-locked to core: a field added to\n * the invitation and omitted here produces an op whose inner invitation no longer\n * matches the inviter's signature over it. That exact drift, in client-py, broke\n * 93 scenarios \u2014 the failure surfaces as `invalid invitation signature` and points\n * nowhere near the encoder.\n */\nexport function encodeSignedInvitation(signed: SignedGroupOpenInvitation): Uint8Array {\n const body = signed.invitation;\n const admitters = body.admitters ?? [];\n const admitterAddrs = signed.admitter_addrs ?? [];\n\n return concat(\n // GroupInvitationFromAdmin.\n //\n // `SignerId` and `ContextGroupId` cross JSON as 32 numbers; `AccountId`\n // crosses as hex. Same width, different spelling, and reading either the\n // wrong way yields 32 bytes that encode cleanly and verify as nothing.\n bytes32(body.inviter_identity, 'inviter_identity'),\n bytes32(body.group_id, 'group_id'),\n u64le(body.expiration_timestamp),\n bytes32(body.secret_salt, 'secret_salt'),\n new Uint8Array([body.invited_role]),\n u32le(admitters.length),\n ...admitters.map((a, i) => fromHex(a, `admitters[${i}]`, 32)),\n // ...then the enclosing SignedGroupOpenInvitation, in declaration order.\n // `inviter_account` sits between the signature and the addresses; putting it\n // anywhere else still encodes 32 bytes and still verifies as nothing.\n borshString(signed.inviter_signature),\n signed.inviter_account\n ? concat(new Uint8Array([1]), fromHex(signed.inviter_account, 'inviter_account', 32))\n : new Uint8Array([0]),\n // A `Vec<String>`, so each entry is a bare borsh string. This carried an\n // enum until core dropped the URL variant, and an enum writes a\n // discriminant byte ahead of each entry \u2014 encoding one here now would shift\n // every following byte and fail as `invalid invitation signature`, pointing\n // nowhere near this line.\n u32le(admitterAddrs.length),\n ...admitterAddrs.map(borshString),\n optionalBytes32(signed.application_id, 'application_id'),\n // Borsh order follows core's field order, where this is `bytecode_id`; the\n // JSON key is `app_key`, a rename covering both directions that core pins in\n // a test. Encoding by the Rust name would read `undefined` and emit a None\n // where the inviter signed a Some.\n optionalBytes32(signed.app_key, 'app_key'),\n );\n}\n\nexport interface SignMemberJoinInput {\n /** The namespace being joined, 64 hex. */\n readonly namespaceId: string;\n /** The account joining, 64 hex \u2014 the subject of the credential. */\n readonly member: string;\n /** The invitation, as the admin API returned it. */\n readonly invitation: SignedGroupOpenInvitation;\n /**\n * The joiner's `AccountProof<DeviceCert>`, hex \u2014 from `signDeviceCert`.\n *\n * Its `sign_pk` must be the public key of `deviceSecret`; a peer checks that\n * when applying the join and refuses otherwise.\n */\n readonly credential: string;\n /** The device signing secret, 64 hex. Must match the credential's `sign_pk`. */\n readonly deviceSecret: string;\n /**\n * Heads this op is written against. Empty means \"signed against an empty head\"\n * \u2014 which is NOT the same as genesis, and a node with a non-empty head will\n * reject it as stale rather than treat it as a first op.\n */\n readonly parentOpHashes?: readonly string[];\n /** Anti-replay nonce; a node keeps a window per signer. */\n readonly nonce: number | bigint;\n /**\n * When the join happened, unix seconds. Defaults to now.\n *\n * Carried only by `MemberJoinedAt`, which is the variant used whenever the\n * invitation expires \u2014 and since core clamps every invitation to\n * `MAX_INVITATION_VALIDITY_SECS`, that is every invitation. The variant is\n * inferred from the invitation rather than selected by a flag: an expiring\n * invitation joined with plain `MemberJoined` is refused at apply with\n * \"expiration is set but joined_at is absent\", which is not a mistake worth\n * leaving available.\n */\n readonly joinedAt?: number | bigint;\n}\n\n/**\n * Sign a `MemberJoined` (or `MemberJoinedAt`) namespace op and return it\n * borsh-encoded and hex \u2014 the `signedOp` that `admitJoin` takes.\n */\nexport async function signMemberJoinOp(input: SignMemberJoinInput): Promise<string> {\n const parents = input.parentOpHashes ?? [];\n const credential = fromHex(input.credential, 'credential', input.credential.length / 2);\n\n // An invitation that expires must be claimed with `MemberJoinedAt`, which\n // carries `joined_at` between the invitation and the credential. Choosing the\n // variant from the invitation keeps the two in step: the discriminant and the\n // presence of that field are one decision, and splitting them produces bytes\n // that decode into the wrong shape rather than failing to decode.\n const expires = BigInt(input.invitation.invitation.expiration_timestamp ?? 0) !== 0n;\n const joinedAt =\n input.joinedAt ?? BigInt(Math.floor(Date.now() / 1000));\n\n const rootOp = expires\n ? concat(\n new Uint8Array([ROOT_OP.MemberJoinedAt]),\n fromHex(input.member, 'member', 32),\n encodeSignedInvitation(input.invitation),\n u64le(joinedAt),\n credential,\n )\n : concat(\n new Uint8Array([ROOT_OP.MemberJoined]),\n fromHex(input.member, 'member', 32),\n encodeSignedInvitation(input.invitation),\n credential,\n );\n\n const op = concat(new Uint8Array([NAMESPACE_OP_ROOT]), rootOp);\n\n const key = await importSigningKey(input.deviceSecret);\n const signer = await derivePublicKey(input.deviceSecret);\n\n const signable = concat(\n new Uint8Array([SIGNED_NAMESPACE_OP_SCHEMA_VERSION]),\n fromHex(input.namespaceId, 'namespaceId', 32),\n u32le(parents.length),\n ...parents.map((p, i) => fromHex(p, `parentOpHashes[${i}]`, 32)),\n signer,\n u64le(input.nonce),\n op,\n );\n\n const signature = new Uint8Array(\n await crypto.subtle.sign('Ed25519', key, concat(NAMESPACE_SIGN_DOMAIN, signable)),\n );\n\n // The signed struct repeats the signable fields, then the signature, then the\n // admitter endorsement. The domain is a signing prefix only and is never part\n // of the encoded op.\n //\n // The endorsement is written as borsh `None` \u2014 a single `0x00` \u2014 and that is\n // the correct value from here, not a placeholder. It authorises the membership\n // and can only be signed by an account the invitation named as an admitter,\n // which a keyholder is not. The admitter attaches its own as it relays the op\n // through `admitJoin`.\n //\n // It sits outside `signable`, so it is outside this signature and outside the\n // op's id. That is what makes attaching it possible: the admitter changes\n // neither what was signed here nor which op this is.\n const unendorsed = new Uint8Array([0]);\n\n return hex(concat(signable, signature, unendorsed));\n}\n", "/**\n * Sign the statement a device key presents to obtain a session on a node.\n *\n * This is the half of password-free login that has to run where the device key\n * lives, and for the case delegated execution exists for that is a browser tab.\n * Until this module, the only thing that could produce a login statement was\n * `merod account login-statement` \u2014 which is to say, a client had to hold a node\n * to ask a node for a session.\n *\n * **What this gets you, and what it does not.** A statement proves a device key\n * asked for this session. It says nothing about which account that device\n * belongs to: that is the `AccountProof` travelling beside it, which the node\n * checks separately. Signing here and presenting no proof gets a 401 that looks\n * exactly like a bad signature.\n *\n * **The session key is minted by the caller, not here.** The device key signs\n * once, over a short-lived key the caller generates and keeps; that key does the\n * talking for the rest of the session, which is what keeps the device key off\n * the wire. Passing a key you do not hold the private half of produces a session\n * you cannot speak on.\n *\n * **`node` must come from something you pinned.** Not from the challenge\n * response, and not from any field the node itself chose \u2014 an attacker who can\n * answer on the node's behalf would otherwise pick what your device signs about,\n * which is the whole reason the field exists. Read it out of band from the node\n * operator.\n *\n * **The byte contract is pinned in core**, at\n * `crates/account/src/tests/login_wire_fixture.rs`, for the same reason the\n * warrant one is: `AUTH_LOGIN_SIGN_DOMAIN` is `pub(crate)` there and the field\n * order is implicit in a `derive`, so nothing on that side forces anyone to\n * notice this file depends on both. This module's test asserts the same vectors.\n */\n\nimport {\n concat,\n derivePublicKey,\n domainHash,\n fromHex,\n hex,\n importSigningKey,\n u32le,\n u64le,\n} from '../crypto/internal.js';\n\nconst SIGN_DOMAIN = new TextEncoder().encode('calimero.auth.login.v1');\n\n/**\n * The client surface a session is bound to.\n *\n * A session minted for one surface cannot be presented from another, so a single\n * compromised surface does not yield sessions usable everywhere.\n */\nexport type Audience =\n | {\n /**\n * A browser origin, spelled exactly as the browser spells it\n * (`https://host:port`, no trailing slash).\n *\n * Compared byte for byte by the node. Do not normalize it here: two\n * spellings of one origin would mean a verifier that disagrees with the\n * browser about which one a token was for. `location.origin` is already\n * the spelling you want.\n */\n kind: 'webOrigin';\n origin: string;\n }\n | {\n /** A signed native client, named by its code-signing identity. */\n kind: 'codeSigningId';\n id: string;\n }\n | {\n /**\n * A command-line client, which has no origin and no signing identity.\n *\n * Carries no payload deliberately: a string here would be an audience that\n * binds nothing while looking like it binds something.\n */\n kind: 'cli';\n };\n\n/** The borsh enum tag for each variant, in core's declaration order. */\nconst AUDIENCE_TAG: Record<Audience['kind'], number> = {\n webOrigin: 0,\n codeSigningId: 1,\n cli: 2,\n};\n\n/** The variant's payload as UTF-8 \u2014 empty for the payload-free `cli`. */\nfunction audienceBody(audience: Audience): Uint8Array {\n const encoder = new TextEncoder();\n switch (audience.kind) {\n case 'webOrigin':\n return encoder.encode(audience.origin);\n case 'codeSigningId':\n return encoder.encode(audience.id);\n case 'cli':\n return new Uint8Array(0);\n }\n}\n\n/**\n * What the audience contributes to the **signing preimage**: tag then body, with\n * no length between them.\n *\n * `domainHash` length-prefixes every part it is handed, so a length written here\n * would be counted twice and the preimage would verify nowhere. This is the one\n * field spelled differently in the two encodings \u2014 see {@link audienceWire}.\n */\nfunction audienceSigningBytes(audience: Audience): Uint8Array {\n return concat(\n Uint8Array.from([AUDIENCE_TAG[audience.kind]]),\n audienceBody(audience),\n );\n}\n\n/**\n * What the audience contributes to the **wire encoding**: the borsh enum tag,\n * then for the two payload-carrying variants a borsh `String` \u2014 its own `u32`\n * little-endian length, then the UTF-8.\n *\n * `cli` is a bare tag. Writing a `u32` zero after it instead produces four extra\n * bytes that a node reads as the start of the challenge, so the statement fails\n * as a bad signature rather than as a malformed one.\n */\nfunction audienceWire(audience: Audience): Uint8Array {\n const tag = Uint8Array.from([AUDIENCE_TAG[audience.kind]]);\n if (audience.kind === 'cli') {\n return tag;\n }\n const body = audienceBody(audience);\n return concat(tag, u32le(body.length), body);\n}\n\n/** What a device is asking for. */\nexport interface LoginStatementInput {\n /**\n * The node this session is for, hex (32 bytes) \u2014 its identity public key, as\n * you pinned it. See the module note: this must not come from the node.\n */\n node: string;\n /** The client surface the session is bound to. */\n audience: Audience;\n /** The challenge this node issued, hex (32 bytes). Proves freshness. */\n challenge: string;\n /**\n * The public half of the ephemeral key this session will speak with, hex (32\n * bytes). Minted per session by the caller and thrown away with it.\n */\n sessionKey: string;\n /** Unix seconds at signing. */\n issuedAt: number | bigint;\n /** Unix seconds after which the node must refuse it. */\n expiresAt: number | bigint;\n /**\n * The device's ed25519 signing secret, hex (32 bytes).\n *\n * Never sent anywhere. It signs locally and only the signature travels, which\n * is what lets a statement be minted by something holding no node.\n */\n deviceSecret: string;\n}\n\n/**\n * Sign a login statement and return it hex-encoded, ready to present with an\n * account proof.\n *\n * Returns the encoding rather than an object for the reason `signWarrant` does:\n * the signature covers exactly these bytes, and a caller that rebuilt the fields\n * from JSON would have a second spelling able to disagree with what was signed.\n */\nexport async function signLoginStatement(\n input: LoginStatementInput,\n): Promise<string> {\n const node = fromHex(input.node, 'node', 32);\n const challenge = fromHex(input.challenge, 'challenge', 32);\n const sessionKey = fromHex(input.sessionKey, 'sessionKey', 32);\n\n const key = await importSigningKey(input.deviceSecret);\n const deviceKey = await derivePublicKey(input.deviceSecret);\n\n const issuedAt = u64le(input.issuedAt);\n const expiresAt = u64le(input.expiresAt);\n\n const preimage = await domainHash(SIGN_DOMAIN, [\n node,\n audienceSigningBytes(input.audience),\n challenge,\n sessionKey,\n deviceKey,\n issuedAt,\n expiresAt,\n ]);\n\n const signature = new Uint8Array(\n await crypto.subtle.sign({ name: 'Ed25519' }, key, preimage),\n );\n\n return hex(\n concat(\n node,\n audienceWire(input.audience),\n challenge,\n sessionKey,\n deviceKey,\n issuedAt,\n expiresAt,\n signature,\n ),\n );\n}\n", "/**\n * Obtain a session on a node using only a device key \u2014 the handshake around\n * {@link signLoginStatement}.\n *\n * Signing a statement was never the whole job. A client also has to fetch a\n * challenge, mint a session keypair, assemble a request body whose shape it\n * cannot see from here, and read a token out of the reply. Until now every\n * consumer wrote those two HTTP calls by hand, which is how the same three\n * mistakes kept being available to make:\n *\n * 1. **Asking for permissions.** `POST /auth/token` takes an optional\n * `permissions` list. Left unset, the node grants the account-proof\n * provider's own scopes \u2014 `context:intent`, `context:query`,\n * `context:subscribe` \u2014 which are the whole delegated surface and\n * deliberately nothing above it. This module never sends the field.\n * `authenticate()` in this same package hardcodes `['admin']` (#84); a\n * device-key session would be refused for that and should never ask.\n * 2. **Learning the node's key from the node.** {@link LoginConfig.node} is\n * required and has no default, because a value read from the challenge\n * response would let whoever answered choose what the device signs about.\n * That is the whole attack the field exists to stop, so the awkwardness of\n * having to supply it is the point rather than something to smooth away.\n * 3. **Losing the refresh token.** A browser tab outlives a short-lived\n * session, so both tokens are returned. Nothing here refreshes on a timer\n * (#87, #88 track that); this only makes it possible without a second\n * breaking change.\n *\n * **The session key never leaves.** It is minted here, the device key signs\n * over it once, and the secret half is handed back to the caller. That is what\n * keeps a device key off the wire for the life of a session: a leaked token\n * cannot be escalated into use of the key that authorised it.\n */\n\nimport { HTTPError } from '../http-client/web-client.js';\nimport { derivePublicKey, hex } from '../crypto/internal.js';\nimport { signLoginStatement, type Audience } from './login.js';\n\n/** Seconds a statement stays valid unless told otherwise. */\nconst DEFAULT_TTL_SECONDS = 300;\n\n/** A freshly minted ephemeral keypair, hex-encoded. */\nexport interface SessionKeyPair {\n /** The public half \u2014 what the statement is signed over. */\n publicKey: string;\n /** The private half. Held by the caller; never sent anywhere. */\n secret: string;\n}\n\n/**\n * Mint the ephemeral keypair a session speaks with.\n *\n * An ed25519 secret *is* 32 random bytes, so this needs no key generation\n * ceremony \u2014 `getRandomValues` and the same derivation every other signer here\n * uses. Exported because a caller that wants to persist a session across a page\n * reload needs to mint the pair itself and keep the secret.\n */\nexport async function generateSessionKey(): Promise<SessionKeyPair> {\n const secret = hex(crypto.getRandomValues(new Uint8Array(32)));\n return { publicKey: hex(await derivePublicKey(secret)), secret };\n}\n\n/** What a device needs in order to ask for a session. */\nexport interface LoginConfig {\n /** The node's base URL, e.g. `https://node.example`. */\n nodeUrl: string;\n /**\n * The node's device signing key, hex (32 bytes), **as this client pinned it**.\n *\n * Learn it out of band \u2014 from the node's operator, or a certificate you\n * already trust. Never from the node you are about to log in to: see the\n * module note.\n */\n node: string;\n /** The device's ed25519 signing secret, hex (32 bytes). Never sent. */\n deviceSecret: string;\n /**\n * The device's account proof, hex \u2014 the credential certifying that this\n * device belongs to its account.\n *\n * Travels beside the statement and is what settles *which account* is asking.\n * The statement alone proves only that a device key asked, so logging in\n * without this yields a 401 that looks exactly like a bad signature.\n */\n accountProof: string;\n /** The client surface this session is bound to. */\n audience: Audience;\n /**\n * How this client names itself to the node. Defaults to the audience's own\n * spelling, which is the honest answer: a session bound to an origin should\n * say that origin.\n */\n clientName?: string;\n /** Seconds the statement stays valid. Defaults to {@link DEFAULT_TTL_SECONDS}. */\n ttlSeconds?: number;\n /** Injectable for tests and non-browser runtimes. */\n fetch?: typeof fetch;\n /** Per-request timeout. Defaults to 10s. */\n timeoutMs?: number;\n}\n\n/** A session minted without a password. */\nexport interface DelegatedSession {\n /** Bearer token for the delegated surface. */\n accessToken: string;\n /** Presented to renew the access token. See #87/#88 \u2014 nothing here schedules it. */\n refreshToken: string;\n /** The session's public half, as the node knows it. */\n sessionKey: string;\n /** The session's private half. The caller keeps this; it was never sent. */\n sessionSecret: string;\n}\n\n/** The audience's own spelling, used as the default client name. */\nfunction audienceLabel(audience: Audience): string {\n switch (audience.kind) {\n case 'webOrigin':\n return audience.origin;\n case 'codeSigningId':\n return audience.id;\n case 'cli':\n return 'cli';\n }\n}\n\n/**\n * Log in with a device key and return the session.\n *\n * The challenge is fetched immediately before signing on purpose: it is\n * single-use and short-lived, so a statement minted against a stale one is\n * refused before any signature work is done.\n *\n * @throws {HTTPError} if the node refuses the challenge or the token request.\n */\nexport async function login(config: LoginConfig): Promise<DelegatedSession> {\n const baseUrl = config.nodeUrl.replace(/\\/+$/, '');\n const timeoutMs = config.timeoutMs ?? 10_000;\n\n const request = async (path: string, init?: RequestInit): Promise<unknown> => {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n let response: Response;\n try {\n const requestInit = { ...init, signal: controller.signal };\n // globalThis.fetch must be called as a method on globalThis, not through a\n // variable, or browsers throw \"Illegal invocation\".\n response = config.fetch\n ? await config.fetch(`${baseUrl}${path}`, requestInit)\n : await globalThis.fetch(`${baseUrl}${path}`, requestInit);\n } catch (err) {\n throw new HTTPError(\n 0,\n err instanceof Error ? err.name : 'NetworkError',\n `${baseUrl}${path}`,\n new Headers(),\n err instanceof Error ? err.message : String(err),\n );\n } finally {\n clearTimeout(timer);\n }\n const text = await response.text().catch(() => '');\n if (!response.ok) {\n throw new HTTPError(\n response.status,\n response.statusText,\n `${baseUrl}${path}`,\n response.headers,\n text,\n );\n }\n return text ? (JSON.parse(text) as unknown) : {};\n };\n\n const challengeBody = (await request('/auth/challenge', {\n method: 'GET',\n headers: { Accept: 'application/json' },\n })) as { challenge?: string; data?: { challenge?: string } };\n // The node has spelled this both bare and wrapped in `data`; accept either\n // rather than couple a login to one envelope revision.\n const challenge = challengeBody.challenge ?? challengeBody.data?.challenge;\n if (!challenge) {\n throw new Error('the node issued no challenge');\n }\n\n const session = await generateSessionKey();\n const issuedAt = Math.floor(Date.now() / 1000);\n const statement = await signLoginStatement({\n node: config.node,\n audience: config.audience,\n challenge,\n sessionKey: session.publicKey,\n issuedAt,\n expiresAt: issuedAt + (config.ttlSeconds ?? DEFAULT_TTL_SECONDS),\n deviceSecret: config.deviceSecret,\n });\n\n // `timestamp` is required and the request refuses unknown fields, so a body\n // missing it is rejected during deserialization \u2014 before the provider runs,\n // and with an error that names the request shape rather than the login.\n //\n // `permissions` is omitted, not empty: see the module note.\n const tokenBody = (await request('/auth/token', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Accept: 'application/json' },\n body: JSON.stringify({\n auth_method: 'account_proof',\n public_key: session.publicKey,\n client_name: config.clientName ?? audienceLabel(config.audience),\n timestamp: issuedAt,\n provider_data: {\n challenge,\n login_statement: statement,\n account_proof: config.accountProof,\n },\n }),\n })) as {\n access_token?: string;\n refresh_token?: string;\n data?: { access_token?: string; refresh_token?: string };\n };\n\n const accessToken = tokenBody.access_token ?? tokenBody.data?.access_token;\n const refreshToken = tokenBody.refresh_token ?? tokenBody.data?.refresh_token;\n if (!accessToken) {\n throw new Error('the node minted no session');\n }\n\n return {\n accessToken,\n refreshToken: refreshToken ?? '',\n sessionKey: session.publicKey,\n sessionSecret: session.secret,\n };\n}\n", "/**\n * Local-node discovery.\n *\n * Calimero dev/desktop nodes expose a public, unauthenticated health endpoint\n * at `GET {nodeUrl}/admin-api/health` that returns `{ \"data\": { \"status\":\n * \"alive\" } }`. The most common local layouts run on a small, well-known set of\n * ports, so instead of forcing the user to type a URL we probe those ports and\n * offer whatever is actually running.\n */\n\n/**\n * Default ports probed when discovering local nodes. Covers the two-node dev\n * stacks used across the Calimero apps (RPC + alt ports for node1 / node2).\n */\nexport const DEFAULT_LOCAL_NODE_PORTS = [2428, 2429, 2528, 2529] as const;\n\n/** Host used to build local candidate URLs. */\nconst LOCAL_HOST = 'localhost';\n\n/** Per-probe timeout. A reachable local node answers almost instantly; a\n * closed port rejects fast, but a firewalled/black-holed one can hang, so we\n * cap each probe. */\nconst DEFAULT_PROBE_TIMEOUT_MS = 2000;\n\nexport interface DiscoverLocalNodesOptions {\n /** Ports to probe. Defaults to {@link DEFAULT_LOCAL_NODE_PORTS}. */\n ports?: readonly number[];\n /** Per-probe timeout in ms. Defaults to 2000. */\n timeoutMs?: number;\n /** Abort the whole discovery (e.g. when the modal closes). */\n signal?: AbortSignal;\n}\n\n/** Build the canonical base URL for a local node on the given port. */\nexport function localNodeUrl(port: number): string {\n return `http://${LOCAL_HOST}:${port}`;\n}\n\n/**\n * Resolve an API `path` against a node `baseUrl`. Ensures the base ends with a\n * slash first, so a base that carries a path prefix (e.g. a `NODE_PATH_PREFIX`\n * deployment like `http://host/node1`) keeps that segment instead of having it\n * stripped by relative URL resolution.\n */\nexport function nodeEndpoint(baseUrl: string, path: string): string {\n const base = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;\n return new URL(path, base).toString();\n}\n\n/**\n * Probe a single node's health endpoint. Resolves `true` only when the node\n * answers with a 2xx and (when the body is JSON) reports a non-dead status.\n * Any network error, timeout, or non-ok response resolves `false` \u2014 never\n * throws (except on an external abort, which is swallowed as `false`).\n */\nexport async function probeNodeHealth(\n baseUrl: string,\n options: { timeoutMs?: number; signal?: AbortSignal } = {},\n): Promise<boolean> {\n const { timeoutMs = DEFAULT_PROBE_TIMEOUT_MS, signal } = options;\n\n // Already cancelled before we start \u2014 don't fetch, and don't register a\n // timer or listener that would then need cleanup.\n if (signal?.aborted) return false;\n\n // Per-probe timeout, also linked to the caller's abort signal so closing the\n // modal cancels every in-flight probe. Bind the signal to a local so the\n // cleanup below stays symmetric with registration without re-narrowing.\n const controller = new AbortController();\n const onAbort = () => controller.abort();\n const abortSignal = signal;\n if (abortSignal) abortSignal.addEventListener('abort', onAbort, { once: true });\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const url = nodeEndpoint(baseUrl, 'admin-api/health');\n const res = await fetch(url, {\n method: 'GET',\n signal: controller.signal,\n });\n\n if (!res.ok) return false;\n\n // Health body is `{ data: { status: \"alive\" } }`, but tolerate plain\n // `{ status }` and non-JSON 2xx responses. A *missing* status is treated as\n // alive (the 2xx is enough); a status that is present but not \"alive\"\n // (including an explicit `null`) is treated as unhealthy.\n try {\n const body = (await res.json()) as\n | { data?: { status?: string | null }; status?: string | null }\n | undefined;\n const status =\n body?.data?.status !== undefined ? body?.data?.status : body?.status;\n if (status === undefined) return true;\n return typeof status === 'string' && status.toLowerCase() === 'alive';\n } catch {\n return true;\n }\n } catch {\n return false;\n } finally {\n clearTimeout(timer);\n if (abortSignal) abortSignal.removeEventListener('abort', onAbort);\n }\n}\n\n/**\n * Probe the configured local ports in parallel and return the base URLs that\n * responded as healthy, in the same order as the `ports` argument (which\n * defaults to ascending). Returns an empty array when nothing local is running.\n */\nexport async function discoverLocalNodes(\n options: DiscoverLocalNodesOptions = {},\n): Promise<string[]> {\n const { ports = DEFAULT_LOCAL_NODE_PORTS, timeoutMs, signal } = options;\n\n const results = await Promise.all(\n ports.map(async (port) => {\n const url = localNodeUrl(port);\n const ok = await probeNodeHealth(url, { timeoutMs, signal });\n return ok ? url : null;\n }),\n );\n\n return results.filter((url): url is string => url !== null);\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["combineSignals", "signals", "list", "AbortSignalAny", "controller", "onAbort", "evt", "s", "createTimeoutSignal", "timeoutMs", "TERMINAL_AUTH_ERRORS", "extractErrorMessage", "bodyText", "body", "error", "nested", "detail", "HTTPError", "status", "statusText", "url", "headers", "explanation", "headersToRecord", "AuthRevokedError", "reason", "record", "value", "key", "pathForProof", "query", "WebHttpClient", "transport", "path", "init", "response", "retryCount", "requestStartTime", "startTime", "signal", "headersObj", "requestInit", "isStreamBody", "retrySignal", "timeoutMs", "elapsed", "remaining", "proof", "httpError", "authError", "userAborted", "refreshPromise", "newToken", "onTokenRefreshPromise", "refreshError", "baseUrl", "signals", "createTimeoutSignal", "combineSignals", "initHeaders", "token", "parse", "text", "createHttpClient", "transport", "WebHttpClient", "createBrowserHttpClient", "options", "url", "init", "createNodeHttpClient", "fetchImpl", "createUniversalHttpClient", "defaultRetryCondition", "error", "attempt", "name", "errorWithStatus", "status", "calculateDelay", "delay", "jitter", "withRetry", "fn", "options", "attempts", "lastError", "delayMs", "retryAfter", "seconds", "date", "waitTime", "resolve", "createRetryableMethod", "method", "retryOptions", "args", "AuthApiClient", "httpClient", "response", "request", "token", "keyId", "clientId", "changes", "MockHttpClient", "createBrowserAuthApiClient", "_config", "httpClient", "AuthApiClient", "createNodeAuthApiClient", "createAuthApiClient", "createAuthApiClientFromHttpClient", "CAPABILITIES", "hasCap", "mask", "cap", "capU32", "withCap", "withoutCap", "unwrap", "response", "compareSemver", "a", "b", "coreA", "preA", "splitVersion", "coreB", "preB", "na", "nb", "n", "i", "c", "compareSemverId", "pa", "pb", "m", "v", "core", "dash", "d", "fetchRegistry", "url", "kind", "context", "withRetry", "resp", "err", "AdminApiClient", "httpClient", "request", "registryUrl", "packageName", "bundles", "appId", "applicationId", "serviceName", "query", "r", "body", "contextId", "path", "params", "res", "blobId", "options", "headers", "size", "name", "namespaceId", "node", "data", "deviceId", "groupId", "safeId", "account", "identity", "current", "hasCap", "CAPABILITIES", "next", "withCap", "capabilities", "id", "childGroupId", "hexEncodeUtf8", "text", "b", "createBrowserAdminApiClient", "config", "AdminApiClient", "createBrowserHttpClient", "createNodeAdminApiClient", "createNodeHttpClient", "createAdminApiClient", "createUniversalHttpClient", "createAdminApiClientFromHttpClient", "httpClient", "_config", "parseAuthCallback", "url", "hashIndex", "hash", "params", "accessToken", "buildAuthLoginUrl", "nodeUrl", "opts", "PKCS8_ED25519_PREFIX", "u32le", "value", "out", "hex", "bytes", "b", "fromHex", "label", "expectedBytes", "clean", "fromHexUnsized", "i", "u64le", "concat", "parts", "total", "p", "offset", "part", "domainHash", "domain", "chunks", "importSigningKey", "secretHex", "seed", "pkcs8", "cause", "detail", "derivePublicKey", "key", "raw", "c", "SIGN_DOMAIN", "BODY_DOMAIN", "borshString", "value", "bytes", "concat", "u32le", "requestBodyHash", "body", "domainHash", "signRequest", "input", "key", "importSigningKey", "bodyHash", "issuedAt", "u64le", "expiresAt", "encoder", "preimage", "signature", "hex", "callerProof", "input", "credential", "fromHexUnsized", "request", "session", "concat", "hex", "bodyBytes", "body", "createProofSigner", "options", "ttl", "clock", "request", "issuedAt", "callerProof", "signRequest", "RpcError", "code", "message", "data", "type", "jsonRpcCall", "httpClient", "method", "params", "response", "err", "RpcClient", "opts", "result", "contextId", "MIGRATION_EVENT_TYPES", "isGroupMigrationEvent", "event", "TERMINAL_AUTH_ERRORS", "TERMINAL_AUTH_STATUSES", "isTerminalAuthFailure", "response", "authError", "SseClient", "opts", "url", "init", "event", "handler", "key", "arr", "idx", "listener", "ev", "d", "isGroupMigrationEvent", "arg", "token", "terminal", "bodyText", "AuthRevokedError", "HTTPError", "err", "error", "body", "reader", "decoder", "buffer", "done", "value", "lines", "line", "jsonStr", "msg", "eventData", "bytes", "text", "idsOrOpts", "newContextIds", "id", "newGroupIds", "hadContextIds", "hadGroupIds", "method", "ids", "params", "_WsClient", "opts", "event", "handler", "key", "arr", "idx", "arg", "token", "wsUrl", "err", "error", "raw", "msg", "eventData", "bytes", "text", "idsOrOpts", "id", "contextIds", "groupIds", "params", "delay", "WsClient", "jsonCodec", "value", "bytes", "EphemeralClient", "opts", "contextId", "state", "codec", "jsonCodec", "jsonRpcCall", "handler", "listener", "event", "e", "data", "removed", "entry", "CERT_DOMAIN", "ACCOUNT_ID_DOMAIN", "DEVICE_ID_DOMAIN", "ACCOUNT_GENESIS_VERSION", "mintDeviceId", "account", "nonce", "accountBytes", "fromHex", "hex", "domainHash", "deviceCertPayload", "input", "u32le", "accountProofBytes", "concat", "signDeviceCert", "rootPublicKey", "derivePublicKey", "keyEpoch", "payload", "key", "importSigningKey", "signature", "credential", "accountForRoot", "rootSecret", "ENGLISH_WORDLIST", "ACCOUNT_LINK_DOMAIN", "ACCOUNT_LOGIN_DOMAIN", "ENTROPY_BITS", "CHECKSUM_BITS", "PHRASE_WORDS", "generateAccountRoot", "entropy", "withPhrase", "hex", "encodePhrase", "accountRootFromPhrase", "phrase", "words", "normalisePhrase", "decodePhrase", "accountRootFromSecret", "secret", "fromHex", "derivePublicKey", "accountForRoot", "signAccountLink", "input", "key", "importSigningKey", "signature", "concat", "base64", "signAccountLogin", "count", "bits", "bitsOf", "checksumBits", "i", "index", "ENGLISH_WORDLIST", "word", "digest", "bytes", "byte", "binary", "ROUTING_PROOF_DOMAIN", "signRoutingChallenge", "nonce", "deviceSecret", "fromHex", "key", "importSigningKey", "message", "concat", "signature", "base64", "routingProofHeaders", "challenge", "credential", "bytes", "binary", "byte", "DEFAULT_CLOUD_BASE_URL", "REFRESH_HEADER", "REFRESH_EXPIRES_HEADER", "CloudClient", "config", "injected", "input", "init", "idToken", "body", "rootSecret", "root", "accountRootFromSecret", "nonce", "signAccountLogin", "proof", "row", "namespaceId", "challenge", "routingProofHeaders", "accountId", "admitters", "nodes", "allowed", "a", "n", "ns", "ownershipProof", "options", "params", "signAccountLink", "portal", "query", "href", "source", "hash", "grant", "error", "out", "session", "method", "path", "headers", "controller", "timer", "response", "err", "HTTPError", "bodyText", "token", "expiresRaw", "expiresAt", "admitterAddrsFromNetworkStatus", "status", "record", "peerId", "external", "suffix", "entry", "addr", "dialable", "resolveProofSigner", "config", "createProofSigner", "REFRESH_LOCK_NAME", "getLockManager", "locks", "expiresAtFromJwt", "token", "fallbackMs", "parts", "b64", "payload", "MeroJs", "isTauri", "getProof", "createBrowserHttpClient", "newToken", "createAuthApiClientFromHttpClient", "createAdminApiClientFromHttpClient", "RpcClient", "SseClient", "EphemeralClient", "CloudClient", "WsClient", "credentials", "creds", "requestBody", "response", "accessToken", "error", "triggeringAccessToken", "promise", "fn", "stored", "data", "expiresAt", "url", "parseAuthCallback", "nodeUrl", "opts", "buildAuthLoginUrl", "createMeroJs", "MemoryTokenStore", "data", "STORAGE_KEY", "LocalStorageTokenStore", "key", "raw", "parsed", "SIGN_DOMAIN", "INTENT_DOMAIN", "MAX_CITED_HEADS", "UNPINNED_APP", "intentHash", "method", "argsJson", "args", "domainHash", "signWarrant", "input", "context", "fromHex", "authorAccount", "executor", "appVersion", "accountHeads", "citedHeads", "governanceFloor", "key", "importSigningKey", "publicKey", "derivePublicKey", "commitment", "nonce", "u64le", "notAfter", "preimage", "signature", "hex", "concat", "u32le", "heads", "label", "list", "head", "i", "DEFAULT_TTL_SECONDS", "IntentRefusedError", "reason", "retryable", "status", "RelayClient", "config", "injected", "input", "init", "contextId", "body", "method", "argsJson", "executor", "nonce", "ttl", "warrant", "signWarrant", "path", "headers", "controller", "timer", "response", "err", "HTTPError", "text", "extractReason", "bodyText", "parsed", "candidate", "createMemoryNonceSource", "start", "next", "value", "createLocalStorageNonceSource", "key", "storage", "store", "raw", "current", "parsed", "PROTOCOL_NAME", "DH_LEN", "TAG_LEN", "PKCS8_X25519_PREFIX", "SymmetricState", "_SymmetricState", "h", "name", "data", "sha256", "concat", "inputKeyMaterial", "ck", "key", "hkdf2", "plaintext", "ciphertext", "noiseNonce", "initiate", "remoteStatic", "prologue", "fixedEphemeral", "state", "ephemeral", "x25519KeyPair", "dh", "message2", "remoteEphemeral", "secret", "pair", "error", "publicKey", "privateKey", "jwk", "base64UrlDecode", "peer", "nonce", "hmacSha256", "hmacKey", "chainingKey", "temp", "first", "second", "value", "base64", "binary", "char", "HANDSHAKE_PATH", "SEALED_PATH", "SEALED_CONTENT_TYPE", "VERSION", "PROLOGUE", "SESSION_ID_LEN", "HEADER_LEN", "FRAME_HEAD", "FRAME_DATA", "FRAME_END", "MAX_FRAME_LEN", "MAX_HANDSHAKE_REPLY", "RENEW_BEFORE_MS", "TRANSPORT_BINDING_DOMAIN", "NULL_BODY_STATUSES", "utf8", "StaleTransportKeyError", "SealedTransportError", "status", "code", "message", "retryAfterMs", "transportKeyBinding", "inner", "transportKey", "digest", "concat", "fetchAttestedTransportKey", "admin", "verify", "options", "nonce", "hex", "attested", "fromHex", "reportDataSuffix", "createAttestedSealedFetch", "baseFetch", "input", "init", "attestUrl", "request", "response", "body", "createSealedFetch", "base", "basePath", "nodeUrl", "attest", "session", "openSession", "handshake", "checkedKey", "error", "currentSession", "pending", "renew", "open", "renewed", "stale", "opening", "url", "headers", "value", "name", "head", "send", "requestId", "header", "envelope", "sealRequest", "envelopeRefusal", "openResponse", "key", "bytes", "handshakeOnce", "resolve", "noise", "initiate", "reply", "readAtMost", "sessionFrom", "finished", "payload", "split", "sessionId", "lifetimeSecs", "requestKey", "responseKey", "sealed", "joinInner", "index", "view", "encoded", "len", "frames", "frameReader", "next", "plain", "first", "controller", "frame", "reader", "buffer", "fill", "length", "done", "limit", "retryAfter", "connectCloud", "options", "cloud", "CloudClient", "namespaces", "namespace", "pickNamespace", "relays", "relayInfo", "r", "explainNoRelay", "relay", "RelayClient", "defaultNonceSource", "createAttestedSealedFetch", "contextId", "method", "argsJson", "requested", "found", "n", "waitingOnGrant", "deviceSecret", "key", "hex", "derivePublicKey", "createLocalStorageNonceSource", "createMemoryNonceSource", "MEASUREMENT_HEX", "createQuoteVerifier", "options", "allowedMrtd", "measurements", "allowedRtmr", "allowedStatuses", "now", "quoteB64", "nonce", "reportDataSuffix", "collateral", "quote", "fromBase64", "against", "verified", "error", "td", "tdReport", "hex", "register", "index", "allowed", "values", "label", "value", "normalized", "report", "data", "binary", "bytes", "i", "SIGNED_NAMESPACE_OP_SCHEMA_VERSION", "NAMESPACE_SIGN_DOMAIN", "NAMESPACE_OP_ROOT", "ROOT_OP", "bytes32", "value", "label", "optionalBytes32", "concat", "borshString", "bytes", "u32le", "encodeSignedInvitation", "signed", "body", "admitters", "admitterAddrs", "u64le", "a", "fromHex", "signMemberJoinOp", "input", "parents", "credential", "expires", "joinedAt", "rootOp", "op", "key", "importSigningKey", "signer", "derivePublicKey", "signable", "p", "i", "signature", "unendorsed", "hex", "SIGN_DOMAIN", "AUDIENCE_TAG", "audienceBody", "audience", "encoder", "audienceSigningBytes", "concat", "audienceWire", "tag", "body", "u32le", "signLoginStatement", "input", "node", "fromHex", "challenge", "sessionKey", "key", "importSigningKey", "deviceKey", "derivePublicKey", "issuedAt", "u64le", "expiresAt", "preimage", "domainHash", "signature", "hex", "DEFAULT_TTL_SECONDS", "generateSessionKey", "secret", "hex", "derivePublicKey", "audienceLabel", "audience", "login", "config", "baseUrl", "timeoutMs", "request", "path", "init", "controller", "timer", "response", "requestInit", "err", "HTTPError", "text", "challengeBody", "challenge", "session", "issuedAt", "statement", "signLoginStatement", "tokenBody", "accessToken", "refreshToken", "DEFAULT_LOCAL_NODE_PORTS", "LOCAL_HOST", "DEFAULT_PROBE_TIMEOUT_MS", "localNodeUrl", "port", "nodeEndpoint", "baseUrl", "path", "base", "probeNodeHealth", "options", "timeoutMs", "signal", "controller", "onAbort", "abortSignal", "timer", "url", "res", "body", "status", "discoverLocalNodes", "ports"]
7
7
  }