@korajs/auth 1.0.0-beta.11 → 1.0.0-beta.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/README.md +52 -47
  2. package/dist/{create-org-session-RsDj9cl4.d.cts → create-org-session-ChFdulEM.d.cts} +211 -17
  3. package/dist/{create-org-session-RsDj9cl4.d.ts → create-org-session-ChFdulEM.d.ts} +211 -17
  4. package/dist/index.cjs +645 -150
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.cts +27 -9
  7. package/dist/index.d.ts +27 -9
  8. package/dist/index.js +644 -150
  9. package/dist/index.js.map +1 -1
  10. package/dist/{operation-encryptor-DRmKNWpF.d.cts → operation-encryptor-DDdlb9bm.d.cts} +16 -0
  11. package/dist/{operation-encryptor-DRmKNWpF.d.ts → operation-encryptor-DDdlb9bm.d.ts} +16 -0
  12. package/dist/react.d.cts +2 -2
  13. package/dist/react.d.ts +2 -2
  14. package/dist/server.cjs +2852 -1675
  15. package/dist/server.cjs.map +1 -1
  16. package/dist/server.d.cts +779 -168
  17. package/dist/server.d.ts +779 -168
  18. package/dist/server.js +2831 -1665
  19. package/dist/server.js.map +1 -1
  20. package/dist/svelte.cjs +2 -2
  21. package/dist/svelte.cjs.map +1 -1
  22. package/dist/svelte.d.cts +2 -2
  23. package/dist/svelte.d.ts +2 -2
  24. package/dist/svelte.js +2 -2
  25. package/dist/svelte.js.map +1 -1
  26. package/dist/vue.d.cts +1 -1
  27. package/dist/vue.d.ts +1 -1
  28. package/package.json +7 -7
  29. package/src/admin/admin-api.ts +327 -0
  30. package/src/admin/audit-log.ts +324 -0
  31. package/src/admin/webhooks.ts +576 -0
  32. package/src/bindings/create-auth-session.ts +184 -0
  33. package/src/bindings/create-org-session.ts +130 -0
  34. package/src/client/auth-client.ts +1592 -0
  35. package/src/client/auth-sync.ts +213 -0
  36. package/src/client/device-session.ts +104 -0
  37. package/src/client/org-client.ts +399 -0
  38. package/src/client/quickstart.ts +108 -0
  39. package/src/client/storage.ts +94 -0
  40. package/src/device/device-identity.ts +330 -0
  41. package/src/device/device-store.ts +379 -0
  42. package/src/encryption/auto-lock.ts +170 -0
  43. package/src/encryption/database-encryption.ts +265 -0
  44. package/src/encryption/key-derivation.ts +149 -0
  45. package/src/encryption/operation-encryptor.ts +361 -0
  46. package/src/index.ts +132 -0
  47. package/src/mfa/totp.ts +826 -0
  48. package/src/org/org-routes.ts +758 -0
  49. package/src/org/org-store.ts +490 -0
  50. package/src/org/org-types.ts +230 -0
  51. package/src/passkey/passkey-client.ts +597 -0
  52. package/src/passkey/passkey-server.ts +779 -0
  53. package/src/postgres/ensure-schema.ts +65 -0
  54. package/src/provider/adapter.ts +246 -0
  55. package/src/provider/built-in/auth-routes.ts +1313 -0
  56. package/src/provider/built-in/email-verification.ts +303 -0
  57. package/src/provider/built-in/password-hash.ts +118 -0
  58. package/src/provider/built-in/password-reset.ts +416 -0
  59. package/src/provider/built-in/postgres-user-store.ts +328 -0
  60. package/src/provider/built-in/quickstart-server.ts +760 -0
  61. package/src/provider/built-in/sqlite-user-store.ts +322 -0
  62. package/src/provider/built-in/sync-scopes.ts +85 -0
  63. package/src/provider/built-in/user-store.ts +465 -0
  64. package/src/provider/external/clerk-adapter.ts +157 -0
  65. package/src/provider/external/external-jwt-provider.ts +491 -0
  66. package/src/provider/external/supabase-adapter.ts +163 -0
  67. package/src/provider/oauth/linked-identity-store.ts +108 -0
  68. package/src/provider/oauth/oauth-flow.ts +550 -0
  69. package/src/provider/oauth/oauth-types.ts +184 -0
  70. package/src/provider/oauth/postgres-oauth-store.ts +296 -0
  71. package/src/provider/oauth/sqlite-oauth-store.ts +272 -0
  72. package/src/rbac/rbac-engine.ts +323 -0
  73. package/src/rbac/rbac-types.ts +210 -0
  74. package/src/rbac/scope-resolver.ts +140 -0
  75. package/src/react/AuthProvider.tsx +97 -0
  76. package/src/react/OrgProvider.tsx +41 -0
  77. package/src/react/auth-context.ts +26 -0
  78. package/src/react/hooks.ts +110 -0
  79. package/src/react/org-hooks.ts +214 -0
  80. package/src/react.ts +26 -0
  81. package/src/server.ts +334 -0
  82. package/src/session/session.ts +401 -0
  83. package/src/svelte/auth-context.ts +50 -0
  84. package/src/svelte/org-context.ts +32 -0
  85. package/src/svelte/org-hooks.ts +201 -0
  86. package/src/svelte/use-auth.ts +115 -0
  87. package/src/svelte.ts +25 -0
  88. package/src/tokens/encrypted-token-store.ts +360 -0
  89. package/src/tokens/jwt.ts +236 -0
  90. package/src/tokens/postgres-token-revocation-store.ts +140 -0
  91. package/src/tokens/sqlite-token-revocation-store.ts +121 -0
  92. package/src/tokens/token-manager.ts +821 -0
  93. package/src/tokens/token-store.ts +192 -0
  94. package/src/types.ts +394 -0
  95. package/src/vue/auth-context.ts +10 -0
  96. package/src/vue/auth-provider-types.ts +5 -0
  97. package/src/vue/auth-provider.ts +76 -0
  98. package/src/vue/org-hooks.ts +193 -0
  99. package/src/vue/org-provider.ts +49 -0
  100. package/src/vue/use-auth.ts +139 -0
  101. package/src/vue.ts +10 -0
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client/auth-client.ts","../src/client/device-session.ts","../src/device/device-store.ts","../src/client/storage.ts","../src/client/quickstart.ts","../src/client/auth-sync.ts","../src/client/org-client.ts","../src/tokens/token-store.ts","../src/tokens/encrypted-token-store.ts","../src/encryption/key-derivation.ts","../src/encryption/auto-lock.ts"],"sourcesContent":["import { KoraError } from '@korajs/core'\nimport type { AuthDeviceIdentityProvider } from './device-session'\n\n// ---------------------------------------------------------------------------\n// Auth-specific error\n// ---------------------------------------------------------------------------\n\n/**\n * Thrown when an authentication operation fails.\n * Includes a machine-readable code and optional context for debugging.\n */\nexport class AuthError extends KoraError {\n\tconstructor(message: string, code: string, context?: Record<string, unknown>) {\n\t\tsuper(message, code, context)\n\t\tthis.name = 'AuthError'\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Possible authentication states for the client.\n * - 'loading': Initial state while restoring tokens from storage\n * - 'authenticated': User is signed in with a valid session\n * - 'unauthenticated': No valid session exists\n */\nexport type AuthState = 'loading' | 'authenticated' | 'unauthenticated'\n\n/**\n * Authenticated user information.\n */\nexport interface AuthUser {\n\t/** Unique user identifier */\n\tid: string\n\n\t/** User email address */\n\temail: string\n\n\t/** Display name (may be absent if user did not provide one) */\n\tname: string | null\n}\n\nexport interface LinkedOAuthAccount {\n\tid: string\n\tuserId: string\n\tprovider: string\n\tproviderUserId: string\n\temail: string | null\n\tlinkedAt: number\n}\n\nexport interface OAuthAuthorizationResult {\n\turl: string\n\tstate: string\n}\n\nexport interface OAuthAuthorizationOptions {\n\t/**\n\t * Redirect the current browser window to the provider after creating the URL.\n\t * Defaults to true when `window.location.assign` is available.\n\t */\n\tredirect?: boolean\n\t/**\n\t * Optional app-specific return path stored in OAuth state metadata.\n\t */\n\treturnTo?: string\n\t/**\n\t * Optional extra metadata stored in OAuth state. Use this for app handoff data.\n\t */\n\tmetadata?: Record<string, unknown>\n\tdeviceId?: string\n\tdevicePublicKey?: string\n}\n\nexport interface OAuthCallbackParams {\n\tcode: string\n\tstate: string\n\tdeviceId?: string\n\tdevicePublicKey?: string\n}\n\n/**\n * Configuration for the AuthClient.\n */\nexport interface AuthClientConfig {\n\t/** Base URL of the auth server (e.g. 'http://localhost:3001') */\n\tserverUrl: string\n\n\t/** Storage key prefix for tokens. Defaults to 'kora_auth' */\n\tstorageKey?: string\n\n\t/**\n\t * Optional token storage adapter.\n\t *\n\t * Use this for runtimes where localStorage is not the right place for\n\t * credentials, such as React Native/Expo SecureStore, iOS Keychain,\n\t * Android Keystore, or a Tauri secure storage plugin.\n\t */\n\tstorage?: AuthTokenStorage\n\n\t/**\n\t * Optional fetch implementation. Defaults to globalThis.fetch.\n\t * Useful for tests, SSR adapters, and mobile runtimes with a custom fetch.\n\t */\n\tfetch?: typeof fetch\n\n\t/**\n\t * Optional local device identity provider.\n\t *\n\t * When configured, sign-up and sign-in automatically include stable\n\t * `deviceId` and `devicePublicKey` fields unless the caller provides them.\n\t */\n\tdeviceIdentity?: AuthDeviceIdentityProvider\n}\n\ntype MaybePromise<T> = T | Promise<T>\n\n/**\n * Token pair returned by the auth server on sign-up, sign-in, and refresh.\n */\ninterface AuthTokensResponse {\n\taccessToken: string\n\trefreshToken: string\n}\n\n/**\n * Sign-up and sign-in responses include user data alongside tokens.\n */\ninterface AuthSignInResponse {\n\tuser: { id: string; email: string; name: string | null }\n\ttokens: AuthTokensResponse\n}\n\ninterface OAuthSignInResponse extends AuthSignInResponse {\n\tidentity: LinkedOAuthAccount\n}\n\n/**\n * User profile returned by the /auth/me endpoint.\n */\ninterface UserProfileResponse {\n\tid: string\n\temail: string\n\tname: string | null\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** Number of seconds before actual expiry at which we consider a token expired. */\nconst EXPIRY_BUFFER_SECONDS = 30\n\n/**\n * Decode the payload portion of a JWT without verifying the signature.\n * Client-side only -- verification is the server's responsibility.\n *\n * Returns null if the token is malformed.\n */\nfunction decodeJwtPayload(token: string): Record<string, unknown> | null {\n\tconst parts = token.split('.')\n\tif (parts.length !== 3) {\n\t\treturn null\n\t}\n\n\ttry {\n\t\t// Base64url -> standard base64\n\t\tconst base64 = (parts[1] as string).replace(/-/g, '+').replace(/_/g, '/')\n\t\tconst json = atob(base64)\n\t\treturn JSON.parse(json) as Record<string, unknown>\n\t} catch {\n\t\treturn null\n\t}\n}\n\n/**\n * Returns true if the JWT's `exp` claim is in the past (with a small buffer).\n * If the token cannot be decoded, returns true (treat as expired).\n */\nfunction isTokenExpired(token: string): boolean {\n\tconst payload = decodeJwtPayload(token)\n\tif (!payload || typeof payload.exp !== 'number') {\n\t\treturn true\n\t}\n\tconst nowSeconds = Math.floor(Date.now() / 1000)\n\treturn (payload.exp as number) <= nowSeconds + EXPIRY_BUFFER_SECONDS\n}\n\n/**\n * Extracts the `sub` (user ID) from a JWT payload.\n * Returns null if the token is malformed or missing the sub claim.\n */\nfunction getUserIdFromToken(token: string): string | null {\n\tconst payload = decodeJwtPayload(token)\n\tif (!payload || typeof payload.sub !== 'string') {\n\t\treturn null\n\t}\n\treturn payload.sub as string\n}\n\nfunction getDefaultFetch(): typeof fetch {\n\tif (typeof globalThis.fetch !== 'function') {\n\t\treturn async () => {\n\t\t\tthrow new AuthError(\n\t\t\t\t'No fetch implementation is available in this runtime. Pass `fetch` to AuthClientConfig.',\n\t\t\t\t'AUTH_FETCH_UNAVAILABLE',\n\t\t\t)\n\t\t}\n\t}\n\treturn globalThis.fetch.bind(globalThis)\n}\n\nfunction normalizeAuthUser(user: { id: string; email: string; name?: string | null }): AuthUser {\n\treturn {\n\t\tid: user.id,\n\t\temail: user.email,\n\t\tname: user.name ?? null,\n\t}\n}\n\nfunction canRedirectCurrentWindow(): boolean {\n\treturn (\n\t\ttypeof globalThis.window !== 'undefined' &&\n\t\ttypeof globalThis.window.location?.assign === 'function'\n\t)\n}\n\nfunction redirectCurrentWindow(url: string): void {\n\tif (!canRedirectCurrentWindow()) {\n\t\tthrow new AuthError(\n\t\t\t'OAuth redirect is not available in this runtime. Pass redirect: false and open the returned URL with your platform browser API.',\n\t\t\t'AUTH_OAUTH_REDIRECT_UNAVAILABLE',\n\t\t)\n\t}\n\tglobalThis.window.location.assign(url)\n}\n\n// ---------------------------------------------------------------------------\n// Simple token storage backed by localStorage (browser) or in-memory fallback\n// ---------------------------------------------------------------------------\n\nexport interface AuthTokenStorage {\n\tgetAccessToken(): MaybePromise<string | null>\n\tgetRefreshToken(): MaybePromise<string | null>\n\tsetTokens(access: string, refresh: string): MaybePromise<void>\n\tclear(): MaybePromise<void>\n}\n\nfunction createTokenStorage(prefix: string): AuthTokenStorage {\n\t// Try localStorage; fall back to in-memory if unavailable (SSR, Web Worker, etc.)\n\tlet useLocalStorage = false\n\ttry {\n\t\tif (typeof window !== 'undefined' && typeof window.localStorage !== 'undefined') {\n\t\t\t// Smoke test: ensure we can actually write\n\t\t\tconst testKey = `${prefix}_test`\n\t\t\twindow.localStorage.setItem(testKey, '1')\n\t\t\twindow.localStorage.removeItem(testKey)\n\t\t\tuseLocalStorage = true\n\t\t}\n\t} catch {\n\t\t// localStorage not available (e.g., Safari private browsing throws in some contexts)\n\t}\n\n\tif (useLocalStorage) {\n\t\tconst accessKey = `${prefix}_access_token`\n\t\tconst refreshKey = `${prefix}_refresh_token`\n\t\treturn {\n\t\t\tgetAccessToken(): string | null {\n\t\t\t\treturn window.localStorage.getItem(accessKey)\n\t\t\t},\n\t\t\tgetRefreshToken(): string | null {\n\t\t\t\treturn window.localStorage.getItem(refreshKey)\n\t\t\t},\n\t\t\tsetTokens(access: string, refresh: string): void {\n\t\t\t\twindow.localStorage.setItem(accessKey, access)\n\t\t\t\twindow.localStorage.setItem(refreshKey, refresh)\n\t\t\t},\n\t\t\tclear(): void {\n\t\t\t\twindow.localStorage.removeItem(accessKey)\n\t\t\t\twindow.localStorage.removeItem(refreshKey)\n\t\t\t},\n\t\t}\n\t}\n\n\t// In-memory fallback\n\tlet accessToken: string | null = null\n\tlet refreshToken: string | null = null\n\treturn {\n\t\tgetAccessToken(): string | null {\n\t\t\treturn accessToken\n\t\t},\n\t\tgetRefreshToken(): string | null {\n\t\t\treturn refreshToken\n\t\t},\n\t\tsetTokens(access: string, refresh: string): void {\n\t\t\taccessToken = access\n\t\t\trefreshToken = refresh\n\t\t},\n\t\tclear(): void {\n\t\t\taccessToken = null\n\t\t\trefreshToken = null\n\t\t},\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// AuthClient\n// ---------------------------------------------------------------------------\n\n/**\n * Client-side authentication manager for Kora.js.\n *\n * Manages token storage, session restoration, sign-up, sign-in, sign-out,\n * token refresh, and auth state change notifications. Framework-agnostic --\n * works in any JavaScript environment with `fetch` and optionally `localStorage`.\n *\n * @example\n * ```typescript\n * const auth = new AuthClient({ serverUrl: 'http://localhost:3001' })\n * await auth.initialize()\n *\n * if (!auth.isAuthenticated) {\n * await auth.signIn({ email: 'user@example.com', password: 'secret' })\n * }\n *\n * const unsub = auth.onAuthChange((state) => {\n * console.log('Auth state:', state)\n * })\n * ```\n */\nexport class AuthClient {\n\tprivate readonly serverUrl: string\n\tprivate readonly storage: AuthTokenStorage\n\tprivate readonly fetchFn: typeof fetch\n\tprivate readonly deviceIdentity: AuthDeviceIdentityProvider | undefined\n\tprivate readonly listeners: Set<(state: AuthState) => void> = new Set()\n\n\tprivate _state: AuthState = 'loading'\n\tprivate _user: AuthUser | null = null\n\tprivate _refreshPromise: Promise<string | null> | null = null\n\tprivate _initialized = false\n\n\t/**\n\t * Creates a new AuthClient.\n\t *\n\t * @param config - Auth client configuration\n\t */\n\tconstructor(config: AuthClientConfig) {\n\t\t// Strip trailing slash to normalize URLs\n\t\tthis.serverUrl = config.serverUrl.replace(/\\/+$/, '')\n\t\tconst prefix = config.storageKey ?? 'kora_auth'\n\t\tthis.storage = config.storage ?? createTokenStorage(prefix)\n\t\tthis.fetchFn = config.fetch ?? getDefaultFetch()\n\t\tthis.deviceIdentity = config.deviceIdentity\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Public getters\n\t// -----------------------------------------------------------------------\n\n\t/** Current authentication state. */\n\tget state(): AuthState {\n\t\treturn this._state\n\t}\n\n\t/** Current authenticated user, or null if not signed in. */\n\tget currentUser(): AuthUser | null {\n\t\treturn this._user\n\t}\n\n\t/** Whether the user is currently authenticated. */\n\tget isAuthenticated(): boolean {\n\t\treturn this._state === 'authenticated'\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Initialization\n\t// -----------------------------------------------------------------------\n\n\t/**\n\t * Initialize the auth client by restoring a session from stored tokens.\n\t *\n\t * Loads tokens from storage, validates the access token, and attempts a\n\t * refresh if the access token is expired but a refresh token is available.\n\t * Safe to call multiple times -- subsequent calls are no-ops once initialized.\n\t */\n\tasync initialize(): Promise<void> {\n\t\t// Guard against double initialization (e.g., React StrictMode double-mount)\n\t\tif (this._initialized) {\n\t\t\treturn\n\t\t}\n\t\tthis._initialized = true\n\n\t\tconst accessToken = await this.storage.getAccessToken()\n\t\tconst refreshToken = await this.storage.getRefreshToken()\n\n\t\t// No stored tokens -- stay unauthenticated\n\t\tif (!accessToken || !refreshToken) {\n\t\t\tthis.setState('unauthenticated', null)\n\t\t\treturn\n\t\t}\n\n\t\t// Access token still valid -- restore session from it\n\t\tif (!isTokenExpired(accessToken)) {\n\t\t\tawait this.restoreSession(accessToken)\n\t\t\treturn\n\t\t}\n\n\t\t// Access token expired -- try refreshing\n\t\ttry {\n\t\t\tconst newAccessToken = await this.refreshAccessToken(refreshToken)\n\t\t\tif (newAccessToken) {\n\t\t\t\tawait this.restoreSession(newAccessToken)\n\t\t\t\treturn\n\t\t\t}\n\t\t} catch {\n\t\t\t// Refresh failed (network error, token revoked, etc.)\n\t\t}\n\n\t\t// Could not restore session\n\t\tawait this.storage.clear()\n\t\tthis.setState('unauthenticated', null)\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Sign up / Sign in / Sign out\n\t// -----------------------------------------------------------------------\n\n\t/**\n\t * Register a new user account.\n\t *\n\t * @param params - Sign-up credentials\n\t * @returns The newly created AuthUser\n\t * @throws {AuthError} If the request fails or the server returns an error\n\t */\n\tasync signUp(params: {\n\t\temail: string\n\t\tpassword: string\n\t\tname?: string\n\t\tdeviceId?: string\n\t\tdevicePublicKey?: string\n\t}): Promise<AuthUser> {\n\t\tconst body = await this.withDeviceIdentity(params)\n\t\tconst response = await this.request<AuthSignInResponse | AuthTokensResponse>('/auth/signup', {\n\t\t\tmethod: 'POST',\n\t\t\tbody,\n\t\t})\n\n\t\tconst tokens = 'tokens' in response ? response.tokens : response\n\t\tawait this.storage.setTokens(tokens.accessToken, tokens.refreshToken)\n\n\t\tconst user = await this.fetchUserProfile(tokens.accessToken)\n\t\tthis.setState('authenticated', user)\n\t\treturn user\n\t}\n\n\t/**\n\t * Sign in with email and password.\n\t *\n\t * @param params - Sign-in credentials\n\t * @returns The authenticated AuthUser\n\t * @throws {AuthError} If the credentials are invalid or the request fails\n\t */\n\tasync signIn(params: {\n\t\temail: string\n\t\tpassword: string\n\t\tdeviceId?: string\n\t\tdevicePublicKey?: string\n\t}): Promise<AuthUser> {\n\t\tconst body = await this.withDeviceIdentity(params)\n\t\tconst response = await this.request<AuthSignInResponse | AuthTokensResponse>('/auth/signin', {\n\t\t\tmethod: 'POST',\n\t\t\tbody,\n\t\t})\n\n\t\tconst tokens = 'tokens' in response ? response.tokens : response\n\t\tawait this.storage.setTokens(tokens.accessToken, tokens.refreshToken)\n\n\t\tconst user = await this.fetchUserProfile(tokens.accessToken)\n\t\tthis.setState('authenticated', user)\n\t\treturn user\n\t}\n\n\t/**\n\t * Create an OAuth authorization URL and optionally redirect the current window.\n\t *\n\t * For web apps, call this from a button click and keep the default redirect behavior.\n\t * For desktop/mobile, pass `redirect: false`, open the returned URL with the runtime's\n\t * browser API, then call `completeOAuthSignIn()` after receiving the callback.\n\t */\n\tasync signInWithOAuth(\n\t\tprovider: string,\n\t\toptions: OAuthAuthorizationOptions = {},\n\t): Promise<OAuthAuthorizationResult> {\n\t\tconst result = await this.createOAuthAuthorization(provider, options)\n\t\tif (options.redirect ?? canRedirectCurrentWindow()) {\n\t\t\tredirectCurrentWindow(result.url)\n\t\t}\n\t\treturn result\n\t}\n\n\t/**\n\t * Complete an OAuth sign-in callback and store the issued Kora tokens.\n\t */\n\tasync completeOAuthSignIn(provider: string, params: OAuthCallbackParams): Promise<AuthUser> {\n\t\tconst body = await this.withDeviceIdentity(params)\n\t\tconst response = await this.request<OAuthSignInResponse>(\n\t\t\t`/auth/oauth/${encodeURIComponent(provider)}/callback`,\n\t\t\t{\n\t\t\t\tmethod: 'POST',\n\t\t\t\tbody: { ...body },\n\t\t\t},\n\t\t)\n\n\t\tawait this.storage.setTokens(response.tokens.accessToken, response.tokens.refreshToken)\n\t\tconst user = normalizeAuthUser(response.user)\n\t\tthis.setState('authenticated', user)\n\t\treturn user\n\t}\n\n\t/**\n\t * Create an OAuth authorization URL for linking another provider to the current user.\n\t */\n\tasync getOAuthAuthorizationUrl(\n\t\tprovider: string,\n\t\toptions: OAuthAuthorizationOptions = {},\n\t): Promise<OAuthAuthorizationResult> {\n\t\treturn this.createOAuthAuthorization(provider, { ...options, redirect: false })\n\t}\n\n\t/**\n\t * Link an OAuth provider to the current authenticated user.\n\t */\n\tasync linkOAuth(provider: string, params: OAuthCallbackParams): Promise<LinkedOAuthAccount> {\n\t\tconst token = await this.requireAccessToken()\n\t\treturn this.request<LinkedOAuthAccount>(`/auth/oauth/${encodeURIComponent(provider)}/link`, {\n\t\t\tmethod: 'POST',\n\t\t\tbody: {\n\t\t\t\tcode: params.code,\n\t\t\t\tstate: params.state,\n\t\t\t},\n\t\t\ttoken,\n\t\t})\n\t}\n\n\t/**\n\t * List OAuth accounts linked to the current authenticated user.\n\t */\n\tasync listLinkedAccounts(): Promise<LinkedOAuthAccount[]> {\n\t\tconst token = await this.requireAccessToken()\n\t\treturn this.request<LinkedOAuthAccount[]>('/auth/oauth/links', {\n\t\t\tmethod: 'GET',\n\t\t\ttoken,\n\t\t})\n\t}\n\n\t/**\n\t * Unlink an OAuth provider from the current authenticated user.\n\t */\n\tasync unlinkOAuth(provider: string): Promise<void> {\n\t\tconst token = await this.requireAccessToken()\n\t\tawait this.request<{ ok: true }>(`/auth/oauth/${encodeURIComponent(provider)}/link`, {\n\t\t\tmethod: 'DELETE',\n\t\t\ttoken,\n\t\t})\n\t}\n\n\t/**\n\t * Sign out the current user.\n\t *\n\t * Clears local tokens and attempts to revoke the refresh token on the server\n\t * (best-effort — succeeds even if the server is unreachable). This ensures that\n\t * stolen refresh tokens cannot be used after the user explicitly signs out.\n\t */\n\tasync signOut(): Promise<void> {\n\t\tconst accessToken = await this.storage.getAccessToken()\n\t\tconst refreshToken = await this.storage.getRefreshToken()\n\n\t\t// Clear local state immediately (don't wait for server)\n\t\tawait this.storage.clear()\n\t\tthis._refreshPromise = null\n\t\tthis.setState('unauthenticated', null)\n\n\t\t// Best-effort server-side revocation\n\t\tif (accessToken) {\n\t\t\ttry {\n\t\t\t\tawait this.request('/auth/signout', {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\tbody: { refreshToken: refreshToken ?? undefined },\n\t\t\t\t\ttoken: accessToken,\n\t\t\t\t})\n\t\t\t} catch {\n\t\t\t\t// Server may be unreachable (offline) — local sign-out still succeeds\n\t\t\t}\n\t\t}\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Token access\n\t// -----------------------------------------------------------------------\n\n\t/**\n\t * Get a valid access token, automatically refreshing if expired.\n\t *\n\t * @returns A valid access token string, or null if the user is not\n\t * authenticated and refresh is not possible\n\t */\n\tasync getAccessToken(): Promise<string | null> {\n\t\tconst accessToken = await this.storage.getAccessToken()\n\n\t\tif (accessToken && !isTokenExpired(accessToken)) {\n\t\t\treturn accessToken\n\t\t}\n\n\t\t// Attempt refresh\n\t\tconst refreshToken = await this.storage.getRefreshToken()\n\t\tif (!refreshToken) {\n\t\t\treturn null\n\t\t}\n\n\t\ttry {\n\t\t\tconst newAccessToken = await this.refreshAccessToken(refreshToken)\n\t\t\treturn newAccessToken\n\t\t} catch {\n\t\t\treturn null\n\t\t}\n\t}\n\n\t/**\n\t * Get a valid token for the sync engine handshake.\n\t * Alias for {@link getAccessToken}.\n\t *\n\t * @returns A valid access token string, or null if unavailable\n\t */\n\tasync getSyncToken(): Promise<string | null> {\n\t\treturn this.getAccessToken()\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// State change subscriptions\n\t// -----------------------------------------------------------------------\n\n\t/**\n\t * Subscribe to authentication state changes.\n\t *\n\t * The callback is invoked whenever the auth state transitions (e.g., from\n\t * 'unauthenticated' to 'authenticated' on sign-in).\n\t *\n\t * @param callback - Function called with the new AuthState on each change\n\t * @returns An unsubscribe function that removes the listener\n\t *\n\t * @example\n\t * ```typescript\n\t * const unsub = auth.onAuthChange((state) => {\n\t * console.log('Auth state changed to:', state)\n\t * })\n\t * // Later: unsub()\n\t * ```\n\t */\n\tonAuthChange(callback: (state: AuthState) => void): () => void {\n\t\tthis.listeners.add(callback)\n\t\treturn () => {\n\t\t\tthis.listeners.delete(callback)\n\t\t}\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Internal helpers\n\t// -----------------------------------------------------------------------\n\n\t/**\n\t * Update internal state and notify all listeners.\n\t */\n\tprivate setState(state: AuthState, user: AuthUser | null): void {\n\t\tconst changed = this._state !== state || this._user !== user\n\t\tthis._state = state\n\t\tthis._user = user\n\n\t\tif (changed) {\n\t\t\tfor (const listener of this.listeners) {\n\t\t\t\ttry {\n\t\t\t\t\tlistener(state)\n\t\t\t\t} catch {\n\t\t\t\t\t// Listeners should not throw, but if they do, do not let it\n\t\t\t\t\t// break the notification loop for other listeners.\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Restore a session from a valid access token by fetching the user profile.\n\t * Falls back to extracting the user ID from the token payload if the\n\t * /auth/me request fails (offline scenario).\n\t */\n\tprivate async restoreSession(accessToken: string): Promise<void> {\n\t\ttry {\n\t\t\tconst user = await this.fetchUserProfile(accessToken)\n\t\t\tthis.setState('authenticated', user)\n\t\t} catch {\n\t\t\t// Network may be unavailable -- extract minimal user info from the token\n\t\t\tconst userId = getUserIdFromToken(accessToken)\n\t\t\tif (userId) {\n\t\t\t\tthis.setState('authenticated', {\n\t\t\t\t\tid: userId,\n\t\t\t\t\temail: '',\n\t\t\t\t\tname: null,\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tawait this.storage.clear()\n\t\t\t\tthis.setState('unauthenticated', null)\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Fetch the current user profile from the server.\n\t */\n\tprivate async fetchUserProfile(accessToken: string): Promise<AuthUser> {\n\t\tconst profile = await this.request<UserProfileResponse>('/auth/me', {\n\t\t\tmethod: 'GET',\n\t\t\ttoken: accessToken,\n\t\t})\n\t\treturn normalizeAuthUser(profile)\n\t}\n\n\tprivate async createOAuthAuthorization(\n\t\tprovider: string,\n\t\toptions: OAuthAuthorizationOptions,\n\t): Promise<OAuthAuthorizationResult> {\n\t\tconst params = new URLSearchParams()\n\t\tconst withIdentity = await this.withDeviceIdentity({\n\t\t\tdeviceId: options.deviceId,\n\t\t\tdevicePublicKey: options.devicePublicKey,\n\t\t})\n\n\t\tif (options.returnTo) {\n\t\t\tparams.set('returnTo', options.returnTo)\n\t\t}\n\t\tif (withIdentity.deviceId) {\n\t\t\tparams.set('deviceId', withIdentity.deviceId)\n\t\t}\n\t\tif (withIdentity.devicePublicKey) {\n\t\t\tparams.set('devicePublicKey', withIdentity.devicePublicKey)\n\t\t}\n\t\tif (options.metadata) {\n\t\t\tfor (const [key, value] of Object.entries(options.metadata)) {\n\t\t\t\tif (value !== undefined && value !== null) {\n\t\t\t\t\tparams.set(key, String(value))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst query = params.toString()\n\t\treturn this.request<OAuthAuthorizationResult>(\n\t\t\t`/auth/oauth/${encodeURIComponent(provider)}${query ? `?${query}` : ''}`,\n\t\t\t{\n\t\t\t\tmethod: 'GET',\n\t\t\t},\n\t\t)\n\t}\n\n\tprivate async withDeviceIdentity<T extends { deviceId?: string; devicePublicKey?: string }>(\n\t\tparams: T,\n\t): Promise<T> {\n\t\tif (!this.deviceIdentity || (params.deviceId && params.devicePublicKey)) {\n\t\t\treturn params\n\t\t}\n\n\t\tconst identity = await this.deviceIdentity.getDeviceIdentity()\n\t\treturn {\n\t\t\t...params,\n\t\t\tdeviceId: params.deviceId ?? identity.deviceId,\n\t\t\tdevicePublicKey: params.devicePublicKey ?? identity.devicePublicKey,\n\t\t}\n\t}\n\n\t/**\n\t * Refresh the access token using a refresh token.\n\t * De-duplicates concurrent refresh calls so only one network request is made.\n\t */\n\tprivate async refreshAccessToken(refreshToken: string): Promise<string | null> {\n\t\t// De-duplicate: if a refresh is already in progress, return the same promise\n\t\tif (this._refreshPromise) {\n\t\t\treturn this._refreshPromise\n\t\t}\n\n\t\tthis._refreshPromise = this.performRefresh(refreshToken)\n\n\t\ttry {\n\t\t\tconst result = await this._refreshPromise\n\t\t\treturn result\n\t\t} finally {\n\t\t\tthis._refreshPromise = null\n\t\t}\n\t}\n\n\tprivate async requireAccessToken(): Promise<string> {\n\t\tconst token = await this.getAccessToken()\n\t\tif (!token) {\n\t\t\tthrow new AuthError('You must be signed in to perform this action.', 'AUTH_REQUIRED')\n\t\t}\n\t\treturn token\n\t}\n\n\t/**\n\t * Execute the token refresh network request.\n\t */\n\tprivate async performRefresh(refreshToken: string): Promise<string | null> {\n\t\ttry {\n\t\t\tconst response = await this.request<AuthTokensResponse>('/auth/refresh', {\n\t\t\t\tmethod: 'POST',\n\t\t\t\tbody: { refreshToken },\n\t\t\t})\n\n\t\t\tawait this.storage.setTokens(response.accessToken, response.refreshToken)\n\t\t\treturn response.accessToken\n\t\t} catch {\n\t\t\t// Refresh failed -- clear tokens to avoid infinite retry loops\n\t\t\tawait this.storage.clear()\n\t\t\tthis.setState('unauthenticated', null)\n\t\t\treturn null\n\t\t}\n\t}\n\n\t/**\n\t * Make an HTTP request to the auth server.\n\t *\n\t * @param path - URL path relative to serverUrl (e.g. '/auth/signin')\n\t * @param options - Request options\n\t * @returns Parsed JSON response body\n\t * @throws {AuthError} On network failure or non-2xx response\n\t */\n\tprivate async request<T>(\n\t\tpath: string,\n\t\toptions: {\n\t\t\tmethod: 'GET' | 'POST' | 'DELETE'\n\t\t\tbody?: Record<string, unknown>\n\t\t\ttoken?: string\n\t\t},\n\t): Promise<T> {\n\t\tconst url = `${this.serverUrl}${path}`\n\n\t\tconst headers: Record<string, string> = {}\n\t\tif (options.body) {\n\t\t\theaders['Content-Type'] = 'application/json'\n\t\t}\n\t\tif (options.token) {\n\t\t\theaders.Authorization = `Bearer ${options.token}`\n\t\t}\n\n\t\tlet response: Response\n\t\ttry {\n\t\t\tresponse = await this.fetchFn(url, {\n\t\t\t\tmethod: options.method,\n\t\t\t\theaders,\n\t\t\t\tbody: options.body ? JSON.stringify(options.body) : undefined,\n\t\t\t})\n\t\t} catch (cause) {\n\t\t\tthrow new AuthError(\n\t\t\t\t`Network request to ${path} failed. The auth server at ${this.serverUrl} may be unreachable. Check your network connection and serverUrl configuration.`,\n\t\t\t\t'AUTH_NETWORK_ERROR',\n\t\t\t\t{ path, cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t\t)\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tlet errorMessage = `Auth server returned HTTP ${response.status}`\n\t\t\tlet serverError: string | undefined\n\t\t\ttry {\n\t\t\t\tconst body = (await response.json()) as Record<string, unknown>\n\t\t\t\tif (typeof body.error === 'string') {\n\t\t\t\t\terrorMessage = body.error as string\n\t\t\t\t\tserverError = errorMessage\n\t\t\t\t} else if (typeof body.message === 'string') {\n\t\t\t\t\terrorMessage = body.message as string\n\t\t\t\t\tserverError = errorMessage\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Response body is not JSON -- use the status text\n\t\t\t}\n\n\t\t\tthrow new AuthError(errorMessage, 'AUTH_SERVER_ERROR', {\n\t\t\t\tpath,\n\t\t\t\tstatus: response.status,\n\t\t\t\tserverError,\n\t\t\t})\n\t\t}\n\n\t\tconst json = (await response.json()) as Record<string, unknown>\n\n\t\t// The BuiltInAuthRoutes server wraps success responses in { data: T }.\n\t\t// Unwrap the envelope so callers get the inner payload directly.\n\t\tconst data = (json.data !== undefined ? json.data : json) as T\n\t\treturn data\n\t}\n}\n","import { KoraError } from '@korajs/core'\nimport { exportPublicKeyJwk, generateDeviceKeyPair } from '../device/device-identity'\nimport { type DeviceKeyStore, createDeviceKeyStore } from '../device/device-store'\nimport type { AuthKeyValueStorage } from './storage'\n\nconst DEFAULT_DEVICE_ID_KEY = 'kora_auth_device_id'\n\nexport interface AuthDeviceIdentity {\n\t/** Stable local device ID used in token `dev` claims. */\n\tdeviceId: string\n\t/** Public proof-of-possession key serialized as a JSON Web Key string. */\n\tdevicePublicKey: string\n}\n\nexport interface AuthDeviceIdentityProvider {\n\tgetDeviceIdentity(): Promise<AuthDeviceIdentity>\n}\n\nexport interface PersistentDeviceIdentityOptions {\n\t/** Store for the stable device ID. Use a platform credential store in production. */\n\tstorage: AuthKeyValueStorage\n\t/** Store for the non-extractable device key pair. Defaults to IndexedDB when available. */\n\tkeyStore?: DeviceKeyStore\n\t/** Storage key for the device ID. Defaults to `kora_auth_device_id`. */\n\tdeviceIdKey?: string\n\t/** Optional device ID generator for tests or custom device registries. */\n\tgenerateDeviceId?: () => string\n}\n\nexport class AuthDeviceIdentityError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'AUTH_DEVICE_IDENTITY_ERROR', context)\n\t\tthis.name = 'AuthDeviceIdentityError'\n\t}\n}\n\n/**\n * Creates a persistent device identity provider for `AuthClient`.\n *\n * The provider keeps a stable device ID in the supplied key-value store and a\n * non-extractable ECDSA P-256 key pair in the supplied `DeviceKeyStore`. The\n * public key is returned during sign-up/sign-in so the server can bind tokens\n * to a real offline device instead of a transient browser session.\n */\nexport function createPersistentDeviceIdentity(\n\toptions: PersistentDeviceIdentityOptions,\n): AuthDeviceIdentityProvider {\n\tconst storage = options.storage\n\tconst keyStore = options.keyStore ?? createDefaultPersistentKeyStore()\n\tconst deviceIdKey = options.deviceIdKey ?? DEFAULT_DEVICE_ID_KEY\n\tconst generateDeviceId = options.generateDeviceId ?? defaultDeviceId\n\n\treturn {\n\t\tasync getDeviceIdentity(): Promise<AuthDeviceIdentity> {\n\t\t\tlet deviceId = await storage.getItem(deviceIdKey)\n\t\t\tif (!deviceId) {\n\t\t\t\tdeviceId = generateDeviceId()\n\t\t\t\tawait storage.setItem(deviceIdKey, deviceId)\n\t\t\t}\n\n\t\t\tlet keyPair = await keyStore.loadKeyPair(deviceId)\n\t\t\tif (!keyPair) {\n\t\t\t\tkeyPair = await generateDeviceKeyPair()\n\t\t\t\tawait keyStore.saveKeyPair(deviceId, keyPair)\n\t\t\t}\n\n\t\t\tconst publicKey = await exportPublicKeyJwk(keyPair)\n\t\t\treturn {\n\t\t\t\tdeviceId,\n\t\t\t\tdevicePublicKey: JSON.stringify(publicKey),\n\t\t\t}\n\t\t},\n\t}\n}\n\nfunction createDefaultPersistentKeyStore(): DeviceKeyStore {\n\tif (typeof globalThis.indexedDB !== 'undefined') {\n\t\treturn createDeviceKeyStore()\n\t}\n\n\tthrow new AuthDeviceIdentityError(\n\t\t'No persistent device key store is available in this runtime. Pass `keyStore` to createPersistentDeviceIdentity().',\n\t)\n}\n\nfunction defaultDeviceId(): string {\n\tif (typeof globalThis.crypto?.randomUUID === 'function') {\n\t\treturn globalThis.crypto.randomUUID()\n\t}\n\n\tconst bytes = new Uint8Array(16)\n\tif (typeof globalThis.crypto?.getRandomValues === 'function') {\n\t\tglobalThis.crypto.getRandomValues(bytes)\n\t} else {\n\t\tfor (let i = 0; i < bytes.length; i++) {\n\t\t\tbytes[i] = Math.floor(Math.random() * 256)\n\t\t}\n\t}\n\n\tbytes[6] = ((bytes[6] as number) & 0x0f) | 0x40\n\tbytes[8] = ((bytes[8] as number) & 0x3f) | 0x80\n\tconst hex = [...bytes].map((byte) => byte.toString(16).padStart(2, '0')).join('')\n\treturn `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`\n}\n","import { KoraError } from '@korajs/core'\n\n// --- Device key store errors ---\n\n/**\n * Thrown when a device key store operation fails.\n * Provides context about the operation and the underlying cause.\n */\nexport class DeviceKeyStoreError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'DEVICE_KEY_STORE_ERROR', context)\n\t\tthis.name = 'DeviceKeyStoreError'\n\t}\n}\n\n// --- Interface ---\n\n/**\n * Persistent storage interface for ECDSA P-256 device key pairs.\n *\n * Device key pairs are used for proof-of-possession authentication:\n * the private key never leaves the device, and the public key is\n * registered with the server. This store persists key pairs across\n * page reloads and app restarts.\n *\n * In the browser, CryptoKey objects are structured-cloneable, so they\n * can be stored directly in IndexedDB without serialization. In Node.js\n * or test environments, an in-memory implementation is used instead.\n */\nexport interface DeviceKeyStore {\n\t/**\n\t * Persist a key pair for the given device.\n\t *\n\t * Overwrites any previously stored key pair for the same device ID.\n\t *\n\t * @param deviceId - The unique device identifier (typically a UUID v7)\n\t * @param keyPair - The ECDSA P-256 CryptoKeyPair to store\n\t * @throws {DeviceKeyStoreError} If the storage operation fails\n\t */\n\tsaveKeyPair(deviceId: string, keyPair: CryptoKeyPair): Promise<void>\n\n\t/**\n\t * Load a previously stored key pair for the given device.\n\t *\n\t * @param deviceId - The unique device identifier\n\t * @returns The stored CryptoKeyPair, or null if no key pair exists for the device\n\t * @throws {DeviceKeyStoreError} If the storage operation fails\n\t */\n\tloadKeyPair(deviceId: string): Promise<CryptoKeyPair | null>\n\n\t/**\n\t * Delete a stored key pair for the given device.\n\t *\n\t * No-op if no key pair exists for the device ID.\n\t *\n\t * @param deviceId - The unique device identifier\n\t * @throws {DeviceKeyStoreError} If the storage operation fails\n\t */\n\tdeleteKeyPair(deviceId: string): Promise<void>\n\n\t/**\n\t * Check whether a key pair exists for the given device.\n\t *\n\t * @param deviceId - The unique device identifier\n\t * @returns True if a key pair is stored for the device, false otherwise\n\t * @throws {DeviceKeyStoreError} If the storage operation fails\n\t */\n\thasKeyPair(deviceId: string): Promise<boolean>\n}\n\n// --- IndexedDB constants ---\n\n/** IndexedDB database name for device key storage. */\nconst IDB_DATABASE_NAME = 'kora_device_keys'\n\n/** IndexedDB object store name within the database. */\nconst IDB_STORE_NAME = 'keypairs'\n\n/** Current database schema version. */\nconst IDB_VERSION = 1\n\n// --- IndexedDB implementation ---\n\n/**\n * Browser-based device key store backed by IndexedDB.\n *\n * CryptoKey objects are structured-cloneable, so they can be stored\n * directly in IndexedDB without needing to export/import them as JWK.\n * This preserves the non-extractable flag on private keys, ensuring\n * they cannot be read even from storage.\n *\n * The database uses a single object store (`keypairs`) with the device ID\n * as the key and the full CryptoKeyPair as the value.\n *\n * @example\n * ```typescript\n * const store = new IndexedDBDeviceKeyStore()\n * const keyPair = await generateDeviceKeyPair()\n * await store.saveKeyPair('device-123', keyPair)\n *\n * const loaded = await store.loadKeyPair('device-123')\n * // loaded.privateKey is still non-extractable\n * ```\n */\nexport class IndexedDBDeviceKeyStore implements DeviceKeyStore {\n\tprivate dbPromise: Promise<IDBDatabase> | null = null\n\n\t/**\n\t * Opens (or creates) the IndexedDB database.\n\t *\n\t * The database connection is lazily initialized on first use and\n\t * reused for subsequent operations. If the database does not exist,\n\t * it is created with the `keypairs` object store.\n\t */\n\tprivate openDatabase(): Promise<IDBDatabase> {\n\t\t// Reuse the database connection if already opened.\n\t\t// This avoids repeatedly opening the database on every operation.\n\t\tif (this.dbPromise !== null) {\n\t\t\treturn this.dbPromise\n\t\t}\n\n\t\tthis.dbPromise = new Promise<IDBDatabase>((resolve, reject) => {\n\t\t\tlet request: IDBOpenDBRequest\n\n\t\t\ttry {\n\t\t\t\trequest = globalThis.indexedDB.open(IDB_DATABASE_NAME, IDB_VERSION)\n\t\t\t} catch (cause) {\n\t\t\t\tthis.dbPromise = null\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t'Failed to open IndexedDB database for device key storage. ' +\n\t\t\t\t\t\t\t'IndexedDB may be unavailable or access may be denied.',\n\t\t\t\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\trequest.onupgradeneeded = () => {\n\t\t\t\tconst db = request.result\n\t\t\t\tif (!db.objectStoreNames.contains(IDB_STORE_NAME)) {\n\t\t\t\t\tdb.createObjectStore(IDB_STORE_NAME)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\trequest.onsuccess = () => {\n\t\t\t\tresolve(request.result)\n\t\t\t}\n\n\t\t\trequest.onerror = () => {\n\t\t\t\tthis.dbPromise = null\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError('Failed to open IndexedDB database for device key storage.', {\n\t\t\t\t\t\terror: request.error?.message,\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\trequest.onblocked = () => {\n\t\t\t\tthis.dbPromise = null\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t'IndexedDB database open was blocked. ' +\n\t\t\t\t\t\t\t'Another tab may have an older version of the database open. ' +\n\t\t\t\t\t\t\t'Close other tabs and try again.',\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\n\t\treturn this.dbPromise\n\t}\n\n\t/** @inheritdoc */\n\tasync saveKeyPair(deviceId: string, keyPair: CryptoKeyPair): Promise<void> {\n\t\tconst db = await this.openDatabase()\n\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\ttry {\n\t\t\t\tconst tx = db.transaction(IDB_STORE_NAME, 'readwrite')\n\t\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\n\t\t\t\t// Store the CryptoKeyPair as a structured clone with the deviceId as the key.\n\t\t\t\t// IndexedDB handles structured cloning of CryptoKey objects natively.\n\t\t\t\tstore.put(keyPair, deviceId)\n\n\t\t\t\ttx.oncomplete = () => {\n\t\t\t\t\tresolve()\n\t\t\t\t}\n\n\t\t\t\ttx.onerror = () => {\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew DeviceKeyStoreError(`Failed to save key pair for device \"${deviceId}\".`, {\n\t\t\t\t\t\t\tdeviceId,\n\t\t\t\t\t\t\terror: tx.error?.message,\n\t\t\t\t\t\t}),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t} catch (cause) {\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(`Failed to save key pair for device \"${deviceId}\".`, {\n\t\t\t\t\t\tdeviceId,\n\t\t\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n\n\t/** @inheritdoc */\n\tasync loadKeyPair(deviceId: string): Promise<CryptoKeyPair | null> {\n\t\tconst db = await this.openDatabase()\n\n\t\treturn new Promise<CryptoKeyPair | null>((resolve, reject) => {\n\t\t\ttry {\n\t\t\t\tconst tx = db.transaction(IDB_STORE_NAME, 'readonly')\n\t\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\t\t\t\tconst request = store.get(deviceId)\n\n\t\t\t\trequest.onsuccess = () => {\n\t\t\t\t\tconst result = request.result as CryptoKeyPair | undefined\n\t\t\t\t\tresolve(result ?? null)\n\t\t\t\t}\n\n\t\t\t\trequest.onerror = () => {\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew DeviceKeyStoreError(`Failed to load key pair for device \"${deviceId}\".`, {\n\t\t\t\t\t\t\tdeviceId,\n\t\t\t\t\t\t\terror: request.error?.message,\n\t\t\t\t\t\t}),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t} catch (cause) {\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(`Failed to load key pair for device \"${deviceId}\".`, {\n\t\t\t\t\t\tdeviceId,\n\t\t\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n\n\t/** @inheritdoc */\n\tasync deleteKeyPair(deviceId: string): Promise<void> {\n\t\tconst db = await this.openDatabase()\n\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\ttry {\n\t\t\t\tconst tx = db.transaction(IDB_STORE_NAME, 'readwrite')\n\t\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\t\t\t\tstore.delete(deviceId)\n\n\t\t\t\ttx.oncomplete = () => {\n\t\t\t\t\tresolve()\n\t\t\t\t}\n\n\t\t\t\ttx.onerror = () => {\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew DeviceKeyStoreError(`Failed to delete key pair for device \"${deviceId}\".`, {\n\t\t\t\t\t\t\tdeviceId,\n\t\t\t\t\t\t\terror: tx.error?.message,\n\t\t\t\t\t\t}),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t} catch (cause) {\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(`Failed to delete key pair for device \"${deviceId}\".`, {\n\t\t\t\t\t\tdeviceId,\n\t\t\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n\n\t/** @inheritdoc */\n\tasync hasKeyPair(deviceId: string): Promise<boolean> {\n\t\tconst db = await this.openDatabase()\n\n\t\treturn new Promise<boolean>((resolve, reject) => {\n\t\t\ttry {\n\t\t\t\tconst tx = db.transaction(IDB_STORE_NAME, 'readonly')\n\t\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\n\t\t\t\t// Use count() with the key to check existence without loading the value.\n\t\t\t\t// This is more efficient than get() for large CryptoKeyPair objects.\n\t\t\t\tconst request = store.count(deviceId)\n\n\t\t\t\trequest.onsuccess = () => {\n\t\t\t\t\tresolve(request.result > 0)\n\t\t\t\t}\n\n\t\t\t\trequest.onerror = () => {\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t\t`Failed to check if key pair exists for device \"${deviceId}\".`,\n\t\t\t\t\t\t\t{ deviceId, error: request.error?.message },\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t} catch (cause) {\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(`Failed to check if key pair exists for device \"${deviceId}\".`, {\n\t\t\t\t\t\tdeviceId,\n\t\t\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// --- In-memory implementation ---\n\n/**\n * In-memory device key store for Node.js and testing environments.\n *\n * Key pairs are stored in a plain Map and do not survive process restarts.\n * This is suitable for server-side rendering, Node.js scripts, and unit tests\n * where IndexedDB is not available.\n *\n * @example\n * ```typescript\n * const store = new InMemoryDeviceKeyStore()\n * const keyPair = await generateDeviceKeyPair()\n * await store.saveKeyPair('device-123', keyPair)\n *\n * const loaded = await store.loadKeyPair('device-123')\n * ```\n */\nexport class InMemoryDeviceKeyStore implements DeviceKeyStore {\n\tprivate readonly store = new Map<string, CryptoKeyPair>()\n\n\t/** @inheritdoc */\n\tasync saveKeyPair(deviceId: string, keyPair: CryptoKeyPair): Promise<void> {\n\t\tthis.store.set(deviceId, keyPair)\n\t}\n\n\t/** @inheritdoc */\n\tasync loadKeyPair(deviceId: string): Promise<CryptoKeyPair | null> {\n\t\treturn this.store.get(deviceId) ?? null\n\t}\n\n\t/** @inheritdoc */\n\tasync deleteKeyPair(deviceId: string): Promise<void> {\n\t\tthis.store.delete(deviceId)\n\t}\n\n\t/** @inheritdoc */\n\tasync hasKeyPair(deviceId: string): Promise<boolean> {\n\t\treturn this.store.has(deviceId)\n\t}\n}\n\n// --- Factory ---\n\n/**\n * Creates a DeviceKeyStore appropriate for the current environment.\n *\n * In browsers where IndexedDB is available, returns an {@link IndexedDBDeviceKeyStore}\n * that persists CryptoKeyPair objects as structured clones. In Node.js, SSR,\n * or environments without IndexedDB, returns an {@link InMemoryDeviceKeyStore}.\n *\n * @returns A DeviceKeyStore instance for the current environment\n *\n * @example\n * ```typescript\n * const store = createDeviceKeyStore()\n * const keyPair = await generateDeviceKeyPair()\n * await store.saveKeyPair(deviceId, keyPair)\n * ```\n */\nexport function createDeviceKeyStore(): DeviceKeyStore {\n\tif (typeof globalThis.indexedDB !== 'undefined') {\n\t\treturn new IndexedDBDeviceKeyStore()\n\t}\n\treturn new InMemoryDeviceKeyStore()\n}\n","import type { AuthTokenStorage } from './auth-client'\n\ntype MaybePromise<T> = T | Promise<T>\n\n/**\n * Minimal key-value credential storage interface used by Kora auth adapters.\n *\n * This intentionally matches the shape of secure stores across runtimes:\n * browser Storage, Tauri secure storage plugins, Expo SecureStore, iOS Keychain,\n * Android Keystore wrappers, and encrypted desktop stores.\n */\nexport interface AuthKeyValueStorage {\n\tgetItem(key: string): MaybePromise<string | null>\n\tsetItem(key: string, value: string): MaybePromise<void>\n\tremoveItem(key: string): MaybePromise<void>\n}\n\nexport interface AuthTokenStorageOptions {\n\t/** Backing credential store. */\n\tstore: AuthKeyValueStorage\n\t/** Storage key prefix. Defaults to `kora_auth`. */\n\tprefix?: string\n}\n\n/**\n * Creates an `AuthTokenStorage` adapter from a runtime key-value store.\n *\n * Use this with platform credential stores instead of wiring `AuthClient`\n * directly to browser localStorage in desktop and mobile apps.\n */\nexport function createAuthTokenStorage(options: AuthTokenStorageOptions): AuthTokenStorage {\n\tconst prefix = options.prefix ?? 'kora_auth'\n\tconst accessKey = `${prefix}_access_token`\n\tconst refreshKey = `${prefix}_refresh_token`\n\tconst store = options.store\n\n\treturn {\n\t\tgetAccessToken: () => store.getItem(accessKey),\n\t\tgetRefreshToken: () => store.getItem(refreshKey),\n\t\tasync setTokens(accessToken: string, refreshToken: string): Promise<void> {\n\t\t\tawait store.setItem(accessKey, accessToken)\n\t\t\tawait store.setItem(refreshKey, refreshToken)\n\t\t},\n\t\tasync clear(): Promise<void> {\n\t\t\tawait store.removeItem(accessKey)\n\t\t\tawait store.removeItem(refreshKey)\n\t\t},\n\t}\n}\n\n/**\n * Creates an in-memory token storage adapter.\n *\n * Useful for tests, demos, and SSR. Production desktop and mobile apps should\n * prefer a secure platform-backed store.\n */\nexport function createMemoryAuthTokenStorage(): AuthTokenStorage {\n\tlet accessToken: string | null = null\n\tlet refreshToken: string | null = null\n\n\treturn {\n\t\tgetAccessToken: () => accessToken,\n\t\tgetRefreshToken: () => refreshToken,\n\t\tsetTokens(access: string, refresh: string): void {\n\t\t\taccessToken = access\n\t\t\trefreshToken = refresh\n\t\t},\n\t\tclear(): void {\n\t\t\taccessToken = null\n\t\t\trefreshToken = null\n\t\t},\n\t}\n}\n\n/**\n * Adapts Web Storage-compatible APIs such as `localStorage` or `sessionStorage`.\n */\nexport function createWebStorageAuthTokenStorage(\n\tstorage: Storage,\n\tprefix?: string,\n): AuthTokenStorage {\n\treturn createAuthTokenStorage({\n\t\tprefix,\n\t\tstore: {\n\t\t\tgetItem: (key) => storage.getItem(key),\n\t\t\tsetItem: (key, value) => {\n\t\t\t\tstorage.setItem(key, value)\n\t\t\t},\n\t\t\tremoveItem: (key) => {\n\t\t\t\tstorage.removeItem(key)\n\t\t\t},\n\t\t},\n\t})\n}\n","import type { DeviceKeyStore } from '../device/device-store'\nimport { AuthClient, type AuthClientConfig, type AuthTokenStorage } from './auth-client'\nimport type { AuthDeviceIdentityProvider } from './device-session'\nimport { createPersistentDeviceIdentity } from './device-session'\nimport {\n\ttype AuthKeyValueStorage,\n\tcreateAuthTokenStorage,\n\tcreateWebStorageAuthTokenStorage,\n} from './storage'\n\nexport interface CreateKoraAuthOptions\n\textends Omit<AuthClientConfig, 'storage' | 'deviceIdentity'> {\n\t/**\n\t * Complete token storage adapter. Use this for fully custom runtimes.\n\t * If omitted, `credentialStore` is adapted automatically.\n\t */\n\tstorage?: AuthTokenStorage\n\t/**\n\t * Runtime credential store used for tokens and stable device ID.\n\t * Examples: Tauri secure storage, Expo SecureStore, iOS Keychain, Android Keystore.\n\t */\n\tcredentialStore?: AuthKeyValueStorage\n\t/**\n\t * Explicit device identity provider. Set to `false` to disable automatic\n\t * device binding during sign-up/sign-in.\n\t */\n\tdeviceIdentity?: AuthDeviceIdentityProvider | false\n\t/**\n\t * Device key store for runtimes without IndexedDB, such as React Native.\n\t */\n\tdeviceKeyStore?: DeviceKeyStore\n}\n\n/**\n * Create a production-shaped Kora auth client with minimal setup.\n *\n * Defaults:\n * - browser/Tauri WebView: localStorage for tokens, IndexedDB for device keys\n * - desktop/mobile: pass `credentialStore` and optionally `deviceKeyStore`\n * - automatic device identity is enabled when a persistent device ID store exists\n */\nexport function createKoraAuth(options: CreateKoraAuthOptions): AuthClient {\n\tconst storage =\n\t\toptions.storage ??\n\t\t(options.credentialStore\n\t\t\t? createAuthTokenStorage({\n\t\t\t\t\tstore: options.credentialStore,\n\t\t\t\t\tprefix: options.storageKey,\n\t\t\t\t})\n\t\t\t: tryCreateDefaultTokenStorage(options.storageKey))\n\n\tconst deviceIdentity =\n\t\toptions.deviceIdentity === false\n\t\t\t? undefined\n\t\t\t: (options.deviceIdentity ??\n\t\t\t\ttryCreateDefaultDeviceIdentity(options.credentialStore, options.deviceKeyStore))\n\n\treturn new AuthClient({\n\t\tserverUrl: options.serverUrl,\n\t\tstorageKey: options.storageKey,\n\t\tstorage,\n\t\tfetch: options.fetch,\n\t\tdeviceIdentity,\n\t})\n}\n\nfunction tryCreateDefaultTokenStorage(\n\tstorageKey: string | undefined,\n): AuthTokenStorage | undefined {\n\tconst storage = tryGetBrowserStorage()\n\treturn storage ? createWebStorageAuthTokenStorage(storage, storageKey) : undefined\n}\n\nfunction tryCreateDefaultDeviceIdentity(\n\tcredentialStore: AuthKeyValueStorage | undefined,\n\tdeviceKeyStore: DeviceKeyStore | undefined,\n): AuthDeviceIdentityProvider | undefined {\n\tconst storage = credentialStore ?? tryGetBrowserStorage()\n\tif (!storage) {\n\t\treturn undefined\n\t}\n\n\ttry {\n\t\treturn createPersistentDeviceIdentity({\n\t\t\tstorage,\n\t\t\tkeyStore: deviceKeyStore,\n\t\t})\n\t} catch {\n\t\treturn undefined\n\t}\n}\n\nfunction tryGetBrowserStorage(): Storage | null {\n\ttry {\n\t\tif (typeof globalThis.localStorage === 'undefined') {\n\t\t\treturn null\n\t\t}\n\t\tconst key = '__kora_auth_quickstart_test__'\n\t\tglobalThis.localStorage.setItem(key, '1')\n\t\tglobalThis.localStorage.removeItem(key)\n\t\treturn globalThis.localStorage\n\t} catch {\n\t\treturn null\n\t}\n}\n","import {\n\ttype SchemaDefinition,\n\ttype ScopeMap,\n\tbuildScopeMap,\n\textractScopeValuesFromClaims,\n} from '@korajs/core'\nimport type { AuthSyncBinding } from '@korajs/core/bindings'\nimport type { AuthState } from './auth-client'\n\n/**\n * Minimal auth client surface required for sync integration.\n * Matches {@link AuthClient} without importing implementation details.\n */\nexport interface AuthSyncClient {\n\tgetAccessToken(): Promise<string | null>\n\treadonly state?: AuthState\n\tonAuthChange?(callback: (state: AuthState) => void): () => void\n}\n\n/**\n * Sync binding returned by {@link createKoraAuthSync}.\n * Passed to `createApp({ sync: { authClient } })` in korajs.\n *\n * @deprecated Use {@link AuthSyncBinding} from `@korajs/core/bindings` or `@korajs/auth`.\n */\nexport type KoraAuthSyncBinding = AuthSyncBinding\n\n/**\n * Configuration for {@link createKoraAuthSync}.\n */\nexport interface CreateKoraAuthSyncOptions {\n\t/** Kora auth client from `createKoraAuth()`. */\n\tauthClient: AuthSyncClient\n\t/**\n\t * Application schema. When provided, scope maps are built automatically\n\t * from JWT claims and schema scope declarations.\n\t */\n\tschema?: SchemaDefinition\n\t/**\n\t * Custom claim → flat scope value mapping.\n\t * Defaults to {@link extractScopeValuesFromClaims}.\n\t */\n\tscopeFromClaims?: (claims: Record<string, unknown>) => Record<string, unknown>\n\t/** Signed-out behavior. Authenticated-only sync is suspended by default. */\n\tanonymous?: 'suspend' | 'allow'\n}\n\n/**\n * Decode JWT payload without signature verification (client-side scope hints only).\n */\nfunction decodeJwtPayload(token: string): Record<string, unknown> | null {\n\tconst parts = token.split('.')\n\tif (parts.length !== 3) {\n\t\treturn null\n\t}\n\n\tconst payloadSegment = parts[1]\n\tif (payloadSegment === undefined) {\n\t\treturn null\n\t}\n\n\ttry {\n\t\tconst base64 = payloadSegment.replace(/-/g, '+').replace(/_/g, '/')\n\t\tconst padded = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), '=')\n\t\tconst json =\n\t\t\ttypeof atob === 'function' ? atob(padded) : Buffer.from(padded, 'base64').toString('utf-8')\n\t\tconst parsed: unknown = JSON.parse(json)\n\t\tif (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n\t\t\treturn null\n\t\t}\n\t\treturn parsed as Record<string, unknown>\n\t} catch {\n\t\treturn null\n\t}\n}\n\nfunction readDeviceIdFromClaims(claims: Record<string, unknown>): string | undefined {\n\tconst dev = claims.dev\n\treturn typeof dev === 'string' && dev.length > 0 ? dev : undefined\n}\n\n/**\n * Creates a sync auth binding for `createApp({ sync: { authClient: binding } })`.\n *\n * Wires token refresh, automatic scope maps from JWT claims, and device-bound\n * sync node ids (`dev` claim) separate from the user id (`sub`).\n *\n * @example\n * ```typescript\n * import { createKoraAuth, createKoraAuthSync } from '@korajs/auth'\n * import { createApp } from 'korajs'\n *\n * const authClient = createKoraAuth({ serverUrl: 'https://api.example.com' })\n *\n * const app = createApp({\n * schema,\n * sync: {\n * url: 'wss://api.example.com/kora-sync',\n * authClient: createKoraAuthSync({ authClient, schema }),\n * },\n * })\n * ```\n */\nexport function createKoraAuthSync(options: CreateKoraAuthSyncOptions): AuthSyncBinding {\n\tconst { authClient, schema, scopeFromClaims, anonymous = 'suspend' } = options\n\n\tconst binding: AuthSyncBinding = {\n\t\tauth: async () => {\n\t\t\tconst token = await authClient.getAccessToken()\n\t\t\treturn { token: token ?? '' }\n\t\t},\n\t}\n\n\tbinding.resolveSyncState = async () => {\n\t\tif (authClient.state === 'loading') return { state: 'loading' }\n\t\tconst token = await authClient.getAccessToken()\n\t\tif (!token) {\n\t\t\treturn anonymous === 'allow'\n\t\t\t\t? { state: 'anonymous', mayConnectAnonymously: true }\n\t\t\t\t: { state: 'signed-out', mayConnectAnonymously: false }\n\t\t}\n\t\tconst claims = decodeJwtPayload(token)\n\t\tconst userId = typeof claims?.sub === 'string' ? claims.sub : ''\n\t\tif (!userId) {\n\t\t\treturn anonymous === 'allow'\n\t\t\t\t? { state: 'anonymous', mayConnectAnonymously: true }\n\t\t\t\t: { state: 'signed-out', mayConnectAnonymously: false }\n\t\t}\n\t\treturn { state: 'authenticated', userId, token }\n\t}\n\n\tif (schema) {\n\t\tbinding.resolveScopeMap = async () => {\n\t\t\tconst token = await authClient.getAccessToken()\n\t\t\tif (!token) {\n\t\t\t\treturn undefined\n\t\t\t}\n\n\t\t\tconst claims = decodeJwtPayload(token)\n\t\t\tif (!claims) {\n\t\t\t\treturn undefined\n\t\t\t}\n\n\t\t\tconst scopeValues = scopeFromClaims\n\t\t\t\t? scopeFromClaims(claims)\n\t\t\t\t: extractScopeValuesFromClaims(schema, claims)\n\n\t\t\treturn buildScopeMap(schema, scopeValues)\n\t\t}\n\t}\n\n\tbinding.resolveNodeId = async () => {\n\t\tconst token = await authClient.getAccessToken()\n\t\tif (!token) {\n\t\t\treturn undefined\n\t\t}\n\n\t\tconst claims = decodeJwtPayload(token)\n\t\tif (!claims) {\n\t\t\treturn undefined\n\t\t}\n\n\t\treturn readDeviceIdFromClaims(claims)\n\t}\n\n\tbinding.resolveUserId = async () => {\n\t\tconst token = await authClient.getAccessToken()\n\t\tif (!token) {\n\t\t\treturn undefined\n\t\t}\n\n\t\tconst claims = decodeJwtPayload(token)\n\t\tif (!claims || typeof claims.sub !== 'string' || claims.sub.length === 0) {\n\t\t\treturn undefined\n\t\t}\n\n\t\treturn claims.sub\n\t}\n\n\tif (authClient.onAuthChange) {\n\t\tbinding.subscribe = (listener) => authClient.onAuthChange?.(() => listener()) ?? (() => {})\n\t}\n\n\treturn binding\n}\n","import { KoraError } from '@korajs/core'\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Organization info returned by the server.\n */\nexport interface ClientOrganization {\n\tid: string\n\tname: string\n\tslug: string\n\townerId: string\n\tcreatedAt: number\n\tupdatedAt: number\n\tmetadata: Record<string, unknown>\n}\n\n/**\n * Membership info returned by the server.\n */\nexport interface ClientMembership {\n\tid: string\n\torgId: string\n\tuserId: string\n\trole: string\n\tinvitedBy: string | null\n\tjoinedAt: number\n}\n\n/**\n * Invitation info returned by the server.\n */\nexport interface ClientInvitation {\n\tid: string\n\torgId: string\n\temail: string\n\trole: string\n\tinvitedBy: string\n\ttoken: string\n\tcreatedAt: number\n\texpiresAt: number\n\tstatus: string\n}\n\n/**\n * Configuration for the OrgClient.\n */\nexport interface OrgClientConfig {\n\t/** Base URL of the auth/org server */\n\tserverUrl: string\n\t/** Function that returns a valid access token for authenticated requests */\n\tgetAccessToken: () => Promise<string | null>\n}\n\n/**\n * Thrown when an org operation fails.\n */\nexport class OrgClientError extends KoraError {\n\tconstructor(message: string, code: string, context?: Record<string, unknown>) {\n\t\tsuper(message, code, context)\n\t\tthis.name = 'OrgClientError'\n\t}\n}\n\n// ============================================================================\n// OrgClient\n// ============================================================================\n\n/**\n * Client-side organization manager.\n *\n * Handles org CRUD, member management, invitations, and active org switching.\n * Framework-agnostic — works in any JavaScript environment with `fetch`.\n *\n * @example\n * ```typescript\n * const orgClient = new OrgClient({\n * serverUrl: 'http://localhost:3001',\n * getAccessToken: () => authClient.getAccessToken(),\n * })\n *\n * const org = await orgClient.createOrg({ name: 'Acme Inc', slug: 'acme' })\n * orgClient.switchOrg(org.id)\n * ```\n */\nexport class OrgClient {\n\tprivate readonly serverUrl: string\n\tprivate readonly getAccessToken: () => Promise<string | null>\n\tprivate readonly listeners = new Set<(orgId: string | null) => void>()\n\n\tprivate _activeOrgId: string | null = null\n\tprivate _activeOrg: ClientOrganization | null = null\n\tprivate _activeRole: string | null = null\n\n\tconstructor(config: OrgClientConfig) {\n\t\tthis.serverUrl = config.serverUrl.replace(/\\/+$/, '')\n\t\tthis.getAccessToken = config.getAccessToken\n\t}\n\n\t// --- Getters ---\n\n\t/** Currently active organization ID */\n\tget activeOrgId(): string | null {\n\t\treturn this._activeOrgId\n\t}\n\n\t/** Currently active organization */\n\tget activeOrg(): ClientOrganization | null {\n\t\treturn this._activeOrg\n\t}\n\n\t/** Current user's role in the active organization */\n\tget activeRole(): string | null {\n\t\treturn this._activeRole\n\t}\n\n\t// --- Organization Operations ---\n\n\t/**\n\t * Create a new organization.\n\t */\n\tasync createOrg(params: {\n\t\tname: string\n\t\tslug?: string\n\t\tmetadata?: Record<string, unknown>\n\t}): Promise<ClientOrganization> {\n\t\treturn this.request<ClientOrganization>('/orgs', {\n\t\t\tmethod: 'POST',\n\t\t\tbody: params,\n\t\t})\n\t}\n\n\t/**\n\t * List all organizations the current user belongs to.\n\t */\n\tasync listOrgs(): Promise<ClientOrganization[]> {\n\t\treturn this.request<ClientOrganization[]>('/orgs', { method: 'GET' })\n\t}\n\n\t/**\n\t * Get an organization by ID.\n\t */\n\tasync getOrg(orgId: string): Promise<ClientOrganization> {\n\t\treturn this.request<ClientOrganization>(`/orgs/${orgId}`, { method: 'GET' })\n\t}\n\n\t/**\n\t * Update an organization.\n\t */\n\tasync updateOrg(\n\t\torgId: string,\n\t\tparams: { name?: string; slug?: string; metadata?: Record<string, unknown> },\n\t): Promise<ClientOrganization> {\n\t\tconst result = await this.request<ClientOrganization>(`/orgs/${orgId}`, {\n\t\t\tmethod: 'PATCH',\n\t\t\tbody: params,\n\t\t})\n\t\t// Update cached active org if this is the active one\n\t\tif (this._activeOrgId === orgId) {\n\t\t\tthis._activeOrg = result\n\t\t}\n\t\treturn result\n\t}\n\n\t/**\n\t * Delete an organization.\n\t */\n\tasync deleteOrg(orgId: string): Promise<void> {\n\t\tawait this.request(`/orgs/${orgId}`, { method: 'DELETE' })\n\t\tif (this._activeOrgId === orgId) {\n\t\t\tthis._activeOrgId = null\n\t\t\tthis._activeOrg = null\n\t\t\tthis._activeRole = null\n\t\t\tthis.notifyListeners()\n\t\t}\n\t}\n\n\t// --- Org Switching ---\n\n\t/**\n\t * Switch the active organization context.\n\t * Fetches the org details and the user's membership/role.\n\t */\n\tasync switchOrg(orgId: string): Promise<void> {\n\t\tconst org = await this.request<ClientOrganization>(`/orgs/${orgId}`, { method: 'GET' })\n\t\tconst membership = await this.request<ClientMembership>(`/orgs/${orgId}/membership`, {\n\t\t\tmethod: 'GET',\n\t\t})\n\n\t\tthis._activeOrgId = orgId\n\t\tthis._activeOrg = org\n\t\tthis._activeRole = membership.role\n\t\tthis.notifyListeners()\n\t}\n\n\t/**\n\t * Clear the active organization (no org selected).\n\t */\n\tclearActiveOrg(): void {\n\t\tthis._activeOrgId = null\n\t\tthis._activeOrg = null\n\t\tthis._activeRole = null\n\t\tthis.notifyListeners()\n\t}\n\n\t// --- Member Management ---\n\n\t/**\n\t * List members of an organization.\n\t */\n\tasync listMembers(orgId: string): Promise<ClientMembership[]> {\n\t\treturn this.request<ClientMembership[]>(`/orgs/${orgId}/members`, { method: 'GET' })\n\t}\n\n\t/**\n\t * Remove a member from an organization.\n\t */\n\tasync removeMember(orgId: string, userId: string): Promise<void> {\n\t\tawait this.request(`/orgs/${orgId}/members/${userId}`, { method: 'DELETE' })\n\t}\n\n\t/**\n\t * Update a member's role.\n\t */\n\tasync updateMemberRole(orgId: string, userId: string, role: string): Promise<ClientMembership> {\n\t\treturn this.request<ClientMembership>(`/orgs/${orgId}/members/${userId}/role`, {\n\t\t\tmethod: 'PATCH',\n\t\t\tbody: { role },\n\t\t})\n\t}\n\n\t/**\n\t * Transfer ownership to another member.\n\t */\n\tasync transferOwnership(orgId: string, newOwnerId: string): Promise<void> {\n\t\tawait this.request(`/orgs/${orgId}/transfer`, {\n\t\t\tmethod: 'POST',\n\t\t\tbody: { newOwnerId },\n\t\t})\n\t}\n\n\t/**\n\t * Leave an organization (remove yourself).\n\t */\n\tasync leaveOrg(orgId: string): Promise<void> {\n\t\tawait this.request(`/orgs/${orgId}/leave`, { method: 'POST' })\n\t\tif (this._activeOrgId === orgId) {\n\t\t\tthis._activeOrgId = null\n\t\t\tthis._activeOrg = null\n\t\t\tthis._activeRole = null\n\t\t\tthis.notifyListeners()\n\t\t}\n\t}\n\n\t// --- Invitations ---\n\n\t/**\n\t * Invite a user to an organization by email.\n\t */\n\tasync inviteMember(\n\t\torgId: string,\n\t\tparams: { email: string; role: string },\n\t): Promise<ClientInvitation> {\n\t\treturn this.request<ClientInvitation>(`/orgs/${orgId}/invitations`, {\n\t\t\tmethod: 'POST',\n\t\t\tbody: params,\n\t\t})\n\t}\n\n\t/**\n\t * Accept an invitation by token.\n\t */\n\tasync acceptInvitation(token: string): Promise<ClientMembership> {\n\t\treturn this.request<ClientMembership>('/invitations/accept', {\n\t\t\tmethod: 'POST',\n\t\t\tbody: { token },\n\t\t})\n\t}\n\n\t/**\n\t * List pending invitations for an organization.\n\t */\n\tasync listInvitations(orgId: string): Promise<ClientInvitation[]> {\n\t\treturn this.request<ClientInvitation[]>(`/orgs/${orgId}/invitations`, { method: 'GET' })\n\t}\n\n\t/**\n\t * Revoke a pending invitation.\n\t */\n\tasync revokeInvitation(orgId: string, invitationId: string): Promise<void> {\n\t\tawait this.request(`/orgs/${orgId}/invitations/${invitationId}`, { method: 'DELETE' })\n\t}\n\n\t/**\n\t * List pending invitations for the current user's email.\n\t */\n\tasync listMyInvitations(email: string): Promise<ClientInvitation[]> {\n\t\treturn this.request<ClientInvitation[]>(`/invitations?email=${encodeURIComponent(email)}`, {\n\t\t\tmethod: 'GET',\n\t\t})\n\t}\n\n\t// --- Subscriptions ---\n\n\t/**\n\t * Subscribe to active org changes.\n\t * @returns Unsubscribe function\n\t */\n\tonOrgChange(callback: (orgId: string | null) => void): () => void {\n\t\tthis.listeners.add(callback)\n\t\treturn () => {\n\t\t\tthis.listeners.delete(callback)\n\t\t}\n\t}\n\n\t// --- Internal ---\n\n\tprivate notifyListeners(): void {\n\t\tfor (const listener of this.listeners) {\n\t\t\ttry {\n\t\t\t\tlistener(this._activeOrgId)\n\t\t\t} catch {\n\t\t\t\t// Don't let listener errors break the notification loop\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async request<T = void>(\n\t\tpath: string,\n\t\toptions: {\n\t\t\tmethod: 'GET' | 'POST' | 'PATCH' | 'DELETE'\n\t\t\tbody?: Record<string, unknown>\n\t\t},\n\t): Promise<T> {\n\t\tconst token = await this.getAccessToken()\n\t\tif (!token) {\n\t\t\tthrow new OrgClientError(\n\t\t\t\t'Not authenticated. Sign in before performing organization operations.',\n\t\t\t\t'ORG_NOT_AUTHENTICATED',\n\t\t\t)\n\t\t}\n\n\t\tconst url = `${this.serverUrl}${path}`\n\t\tconst headers: Record<string, string> = {\n\t\t\tAuthorization: `Bearer ${token}`,\n\t\t}\n\t\tif (options.body) {\n\t\t\theaders['Content-Type'] = 'application/json'\n\t\t}\n\n\t\tlet response: Response\n\t\ttry {\n\t\t\tresponse = await fetch(url, {\n\t\t\t\tmethod: options.method,\n\t\t\t\theaders,\n\t\t\t\tbody: options.body ? JSON.stringify(options.body) : undefined,\n\t\t\t})\n\t\t} catch (cause) {\n\t\t\tthrow new OrgClientError(`Network request to ${path} failed.`, 'ORG_NETWORK_ERROR', {\n\t\t\t\tpath,\n\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t})\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tlet errorMessage = `Server returned HTTP ${response.status}`\n\t\t\ttry {\n\t\t\t\tconst body = (await response.json()) as Record<string, unknown>\n\t\t\t\tif (typeof body.error === 'string') {\n\t\t\t\t\terrorMessage = body.error as string\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// not JSON\n\t\t\t}\n\t\t\tthrow new OrgClientError(errorMessage, 'ORG_SERVER_ERROR', {\n\t\t\t\tpath,\n\t\t\t\tstatus: response.status,\n\t\t\t})\n\t\t}\n\n\t\t// Handle empty responses (DELETE, etc.)\n\t\tconst text = await response.text()\n\t\tif (text.length === 0) return undefined as T\n\n\t\ttry {\n\t\t\tconst body = JSON.parse(text)\n\t\t\t// Unwrap { data: ... } envelope if present\n\t\t\tif (body && typeof body === 'object' && 'data' in body) {\n\t\t\t\treturn body.data as T\n\t\t\t}\n\t\t\treturn body as T\n\t\t} catch {\n\t\t\treturn undefined as T\n\t\t}\n\t}\n}\n","import type { AuthTokens } from '../types'\n\n/** Default key used for localStorage persistence. */\nconst DEFAULT_STORAGE_KEY = 'kora_auth_tokens'\n\n/**\n * Minimal storage interface matching the subset of the Web Storage API\n * that TokenStore needs. This allows both localStorage and in-memory\n * implementations to be used interchangeably.\n */\ninterface SimpleStorage {\n\tgetItem(key: string): string | null\n\tsetItem(key: string, value: string): void\n\tremoveItem(key: string): void\n}\n\n/**\n * In-memory storage fallback used when localStorage is unavailable\n * (e.g., in Node.js, SSR environments, or when storage access is denied).\n */\nclass MemoryStorage implements SimpleStorage {\n\tprivate store = new Map<string, string>()\n\n\tgetItem(key: string): string | null {\n\t\treturn this.store.get(key) ?? null\n\t}\n\n\tsetItem(key: string, value: string): void {\n\t\tthis.store.set(key, value)\n\t}\n\n\tremoveItem(key: string): void {\n\t\tthis.store.delete(key)\n\t}\n}\n\n/**\n * Attempts to access localStorage. Returns null if unavailable.\n *\n * localStorage may be unavailable in several scenarios:\n * - Node.js / server-side rendering (no window object)\n * - Private browsing modes with restricted storage\n * - iframe sandboxing without storage access\n * - User has disabled cookies/storage in browser settings\n */\nfunction tryGetLocalStorage(): SimpleStorage | null {\n\ttry {\n\t\tif (typeof globalThis !== 'undefined' && 'localStorage' in globalThis) {\n\t\t\tconst storage = globalThis.localStorage as SimpleStorage\n\t\t\t// Probe that read/write actually works (some browsers throw on access)\n\t\t\tconst testKey = '__kora_storage_test__'\n\t\t\tstorage.setItem(testKey, '1')\n\t\t\tstorage.removeItem(testKey)\n\t\t\treturn storage\n\t\t}\n\t} catch {\n\t\t// localStorage exists but access is denied (e.g., private mode in some browsers)\n\t}\n\treturn null\n}\n\n/**\n * Client-side token storage for Kora auth tokens.\n *\n * Persists tokens to localStorage when available, falling back to in-memory\n * storage in environments where localStorage is unavailable (Node.js, SSR,\n * restricted browser contexts). Encrypted storage is planned for Phase 2.\n *\n * All operations are synchronous since both localStorage and in-memory\n * storage are synchronous.\n *\n * @example\n * ```typescript\n * const store = new TokenStore()\n *\n * // After login\n * store.saveTokens({ accessToken: '...', refreshToken: '...' })\n *\n * // Before API calls\n * const token = store.getAccessToken()\n *\n * // On logout\n * store.clearTokens()\n * ```\n */\nexport class TokenStore {\n\tprivate readonly storageKey: string\n\tprivate readonly storage: SimpleStorage\n\n\t/**\n\t * Creates a new TokenStore instance.\n\t *\n\t * @param storageKey - The key under which tokens are stored. Defaults to 'kora_auth_tokens'.\n\t * Use different keys if your app runs multiple Kora instances with separate auth.\n\t */\n\tconstructor(storageKey?: string) {\n\t\tthis.storageKey = storageKey ?? DEFAULT_STORAGE_KEY\n\t\tthis.storage = tryGetLocalStorage() ?? new MemoryStorage()\n\t}\n\n\t/**\n\t * Save tokens to persistent storage.\n\t *\n\t * Overwrites any previously stored tokens. The tokens are serialized\n\t * as JSON. Only the `accessToken`, `refreshToken`, and optional\n\t * `deviceCredential` fields are persisted.\n\t *\n\t * @param tokens - The token set to store\n\t */\n\tsaveTokens(tokens: AuthTokens): void {\n\t\tconst serialized: AuthTokens = {\n\t\t\taccessToken: tokens.accessToken,\n\t\t\trefreshToken: tokens.refreshToken,\n\t\t}\n\t\tif (tokens.deviceCredential !== undefined) {\n\t\t\tserialized.deviceCredential = tokens.deviceCredential\n\t\t}\n\t\tthis.storage.setItem(this.storageKey, JSON.stringify(serialized))\n\t}\n\n\t/**\n\t * Load tokens from storage.\n\t *\n\t * @returns The stored {@link AuthTokens}, or null if no tokens have been saved\n\t */\n\tloadTokens(): AuthTokens | null {\n\t\tconst raw = this.storage.getItem(this.storageKey)\n\t\tif (raw === null) {\n\t\t\treturn null\n\t\t}\n\n\t\ttry {\n\t\t\tconst parsed: unknown = JSON.parse(raw)\n\t\t\tif (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst record = parsed as Record<string, unknown>\n\n\t\t\t// Validate required fields are present and are strings\n\t\t\tif (typeof record.accessToken !== 'string' || typeof record.refreshToken !== 'string') {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst tokens: AuthTokens = {\n\t\t\t\taccessToken: record.accessToken,\n\t\t\t\trefreshToken: record.refreshToken,\n\t\t\t}\n\n\t\t\tif (typeof record.deviceCredential === 'string') {\n\t\t\t\ttokens.deviceCredential = record.deviceCredential\n\t\t\t}\n\n\t\t\treturn tokens\n\t\t} catch {\n\t\t\t// Corrupted data in storage; treat as empty\n\t\t\treturn null\n\t\t}\n\t}\n\n\t/**\n\t * Clear all stored tokens.\n\t *\n\t * Call this on logout to remove credentials from persistent storage.\n\t */\n\tclearTokens(): void {\n\t\tthis.storage.removeItem(this.storageKey)\n\t}\n\n\t/**\n\t * Get the current access token.\n\t *\n\t * Returns the raw token string without validating expiration.\n\t * The caller is responsible for checking whether the token is\n\t * still valid and initiating a refresh if needed.\n\t *\n\t * @returns The access token string, or null if no tokens are stored\n\t */\n\tgetAccessToken(): string | null {\n\t\tconst tokens = this.loadTokens()\n\t\treturn tokens?.accessToken ?? null\n\t}\n\n\t/**\n\t * Get the current refresh token.\n\t *\n\t * @returns The refresh token string, or null if no tokens are stored\n\t */\n\tgetRefreshToken(): string | null {\n\t\tconst tokens = this.loadTokens()\n\t\treturn tokens?.refreshToken ?? null\n\t}\n}\n","import { KoraError } from '@korajs/core'\nimport { decryptData, encryptData } from '../encryption/database-encryption'\nimport type { AuthTokens } from '../types'\n\n// --- Errors ---\n\n/**\n * Thrown when encrypted token storage operations fail.\n * Includes context about what went wrong to aid debugging without\n * exposing sensitive key or token material.\n */\nexport class EncryptedTokenStoreError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'ENCRYPTED_TOKEN_STORE_ERROR', context)\n\t\tthis.name = 'EncryptedTokenStoreError'\n\t}\n}\n\n// --- Encoding helpers ---\n\n/**\n * Encodes a Uint8Array as a base64url string (no padding).\n * Implemented locally to avoid coupling to the device-identity module.\n *\n * @param bytes - The binary data to encode\n * @returns A base64url-encoded string without padding characters\n */\nfunction toBase64Url(bytes: Uint8Array): string {\n\tlet binary = ''\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tbinary += String.fromCharCode(bytes[i] as number)\n\t}\n\treturn btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\n/**\n * Decodes a base64url string (no padding) into a Uint8Array.\n *\n * @param str - A base64url-encoded string (with or without padding)\n * @returns The decoded binary data as a Uint8Array\n */\nfunction fromBase64Url(str: string): Uint8Array {\n\tlet base64 = str.replace(/-/g, '+').replace(/_/g, '/')\n\tconst paddingNeeded = (4 - (base64.length % 4)) % 4\n\tbase64 += '='.repeat(paddingNeeded)\n\n\tconst binary = atob(base64)\n\tconst bytes = new Uint8Array(binary.length)\n\tfor (let i = 0; i < binary.length; i++) {\n\t\tbytes[i] = binary.charCodeAt(i)\n\t}\n\treturn bytes\n}\n\n// --- Storage helpers ---\n\n/**\n * Minimal storage interface matching the subset of the Web Storage API\n * that EncryptedTokenStore needs. Allows both localStorage and in-memory\n * implementations to be used interchangeably.\n */\ninterface SimpleStorage {\n\tgetItem(key: string): string | null\n\tsetItem(key: string, value: string): void\n\tremoveItem(key: string): void\n}\n\n/**\n * In-memory storage fallback used when localStorage is unavailable\n * (e.g., in Node.js, SSR environments, or when storage access is denied).\n */\nclass MemoryStorage implements SimpleStorage {\n\tprivate store = new Map<string, string>()\n\n\tgetItem(key: string): string | null {\n\t\treturn this.store.get(key) ?? null\n\t}\n\n\tsetItem(key: string, value: string): void {\n\t\tthis.store.set(key, value)\n\t}\n\n\tremoveItem(key: string): void {\n\t\tthis.store.delete(key)\n\t}\n}\n\n/**\n * Attempts to access localStorage. Returns null if unavailable.\n *\n * localStorage may be unavailable in several scenarios:\n * - Node.js / server-side rendering (no window object)\n * - Private browsing modes with restricted storage\n * - iframe sandboxing without storage access\n * - User has disabled cookies/storage in browser settings\n */\nfunction tryGetLocalStorage(): SimpleStorage | null {\n\ttry {\n\t\tif (typeof globalThis !== 'undefined' && 'localStorage' in globalThis) {\n\t\t\tconst storage = globalThis.localStorage as SimpleStorage\n\t\t\tconst testKey = '__kora_encrypted_storage_test__'\n\t\t\tstorage.setItem(testKey, '1')\n\t\t\tstorage.removeItem(testKey)\n\t\t\treturn storage\n\t\t}\n\t} catch {\n\t\t// localStorage exists but access is denied\n\t}\n\treturn null\n}\n\n// --- Types ---\n\n/** Default storage key prefix for encrypted tokens. */\nconst DEFAULT_STORAGE_KEY = 'kora_auth_encrypted'\n\n/**\n * The serialized format stored in localStorage.\n * Both `iv` and `data` are base64url-encoded strings.\n */\ninterface EncryptedPayload {\n\t/** Base64url-encoded initialization vector (12 bytes for AES-GCM) */\n\tiv: string\n\t/** Base64url-encoded AES-256-GCM ciphertext of the JSON-serialized AuthTokens */\n\tdata: string\n}\n\n/**\n * Configuration for the encrypted token store.\n */\nexport interface EncryptedTokenStoreConfig {\n\t/**\n\t * Storage key prefix. Defaults to 'kora_auth_encrypted'.\n\t * Use different keys if your app runs multiple Kora instances with separate auth.\n\t */\n\tstorageKey?: string\n\t/**\n\t * The AES-256-GCM CryptoKey used to encrypt and decrypt tokens.\n\t *\n\t * This can be:\n\t * - A key derived from a user passphrase via {@link deriveEncryptionKey}\n\t * - A device-derived key (e.g., from secure hardware or biometric unlock)\n\t * - A randomly generated key from {@link generateEncryptionKey}\n\t *\n\t * The caller is responsible for obtaining the key before constructing the store.\n\t */\n\tkey: CryptoKey\n}\n\n// --- Implementation ---\n\n/**\n * Encrypted token store that protects auth tokens at rest.\n *\n * Addresses the security vulnerability of storing tokens in plaintext localStorage,\n * which is accessible to any JavaScript running on the page (XSS attack surface).\n * Tokens are encrypted with AES-256-GCM before being written to storage.\n *\n * The stored format is a JSON string with two base64url-encoded fields:\n * - `iv`: the 12-byte initialization vector (unique per encryption)\n * - `data`: the AES-256-GCM ciphertext of the JSON-serialized tokens\n *\n * AES-GCM provides both confidentiality (tokens are unreadable without the key)\n * and integrity (tampered ciphertext is detected and rejected).\n *\n * @example\n * ```typescript\n * import { deriveEncryptionKey } from '@korajs/auth'\n *\n * // Derive a key from the user's passphrase\n * const { key } = await deriveEncryptionKey('user-passphrase', storedSalt)\n *\n * const store = new EncryptedTokenStore({ key })\n *\n * // After login: encrypt and persist tokens\n * await store.saveTokens({ accessToken: '...', refreshToken: '...' })\n *\n * // Before API calls: decrypt and retrieve\n * const accessToken = await store.getAccessToken()\n *\n * // On logout: remove encrypted data\n * store.clearTokens()\n * ```\n */\nexport class EncryptedTokenStore {\n\tprivate readonly storageKey: string\n\tprivate readonly key: CryptoKey\n\tprivate readonly storage: SimpleStorage\n\n\t/**\n\t * Creates a new EncryptedTokenStore instance.\n\t *\n\t * @param config - Configuration including the encryption key and optional storage key\n\t */\n\tconstructor(config: EncryptedTokenStoreConfig) {\n\t\tthis.storageKey = config.storageKey ?? DEFAULT_STORAGE_KEY\n\t\tthis.key = config.key\n\t\tthis.storage = tryGetLocalStorage() ?? new MemoryStorage()\n\t}\n\n\t/**\n\t * Encrypt and save tokens to persistent storage.\n\t *\n\t * Serializes the tokens as JSON, encrypts with AES-256-GCM using a fresh\n\t * random IV, then stores the result as a JSON object containing the\n\t * base64url-encoded IV and ciphertext.\n\t *\n\t * Overwrites any previously stored tokens.\n\t *\n\t * @param tokens - The token set to encrypt and store\n\t * @throws {EncryptedTokenStoreError} If encryption fails\n\t */\n\tasync saveTokens(tokens: AuthTokens): Promise<void> {\n\t\t// Build a clean token object with only the expected fields\n\t\tconst serialized: AuthTokens = {\n\t\t\taccessToken: tokens.accessToken,\n\t\t\trefreshToken: tokens.refreshToken,\n\t\t}\n\t\tif (tokens.deviceCredential !== undefined) {\n\t\t\tserialized.deviceCredential = tokens.deviceCredential\n\t\t}\n\n\t\tconst plaintext = new TextEncoder().encode(JSON.stringify(serialized))\n\n\t\ttry {\n\t\t\tconst { ciphertext, iv } = await encryptData(this.key, plaintext)\n\n\t\t\tconst payload: EncryptedPayload = {\n\t\t\t\tiv: toBase64Url(iv),\n\t\t\t\tdata: toBase64Url(ciphertext),\n\t\t\t}\n\n\t\t\tthis.storage.setItem(this.storageKey, JSON.stringify(payload))\n\t\t} catch (cause) {\n\t\t\t// Re-throw EncryptedTokenStoreError as-is\n\t\t\tif (cause instanceof EncryptedTokenStoreError) {\n\t\t\t\tthrow cause\n\t\t\t}\n\n\t\t\tthrow new EncryptedTokenStoreError(\n\t\t\t\t'Failed to encrypt and save auth tokens. ' +\n\t\t\t\t\t'Ensure the encryption key is a valid AES-256-GCM CryptoKey.',\n\t\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Load and decrypt tokens from storage.\n\t *\n\t * Reads the encrypted payload from localStorage, decodes the base64url IV\n\t * and ciphertext, decrypts with AES-256-GCM, and parses the resulting JSON.\n\t *\n\t * Returns null (without throwing) if:\n\t * - No tokens have been saved\n\t * - The stored data is corrupted or not valid JSON\n\t * - Decryption fails (wrong key, tampered ciphertext, or wrong IV)\n\t * - The decrypted data does not contain valid token fields\n\t *\n\t * This fail-silent design prevents decryption errors from crashing the\n\t * application. The caller should treat null as \"no valid tokens available\"\n\t * and initiate a re-authentication flow.\n\t *\n\t * @returns The decrypted {@link AuthTokens}, or null if unavailable or decryption fails\n\t */\n\tasync loadTokens(): Promise<AuthTokens | null> {\n\t\tconst raw = this.storage.getItem(this.storageKey)\n\t\tif (raw === null) {\n\t\t\treturn null\n\t\t}\n\n\t\ttry {\n\t\t\t// Parse the stored encrypted payload\n\t\t\tconst parsed: unknown = JSON.parse(raw)\n\t\t\tif (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst record = parsed as Record<string, unknown>\n\t\t\tif (typeof record.iv !== 'string' || typeof record.data !== 'string') {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\t// Decode the base64url-encoded IV and ciphertext\n\t\t\tconst iv = fromBase64Url(record.iv)\n\t\t\tconst ciphertext = fromBase64Url(record.data)\n\n\t\t\t// Decrypt with AES-256-GCM\n\t\t\tconst plaintextBytes = await decryptData(this.key, ciphertext, iv)\n\t\t\tconst json = new TextDecoder().decode(plaintextBytes)\n\n\t\t\t// Parse the decrypted JSON and validate token structure\n\t\t\tconst tokenData: unknown = JSON.parse(json)\n\t\t\tif (typeof tokenData !== 'object' || tokenData === null || Array.isArray(tokenData)) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst tokenRecord = tokenData as Record<string, unknown>\n\t\t\tif (\n\t\t\t\ttypeof tokenRecord.accessToken !== 'string' ||\n\t\t\t\ttypeof tokenRecord.refreshToken !== 'string'\n\t\t\t) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst tokens: AuthTokens = {\n\t\t\t\taccessToken: tokenRecord.accessToken,\n\t\t\t\trefreshToken: tokenRecord.refreshToken,\n\t\t\t}\n\n\t\t\tif (typeof tokenRecord.deviceCredential === 'string') {\n\t\t\t\ttokens.deviceCredential = tokenRecord.deviceCredential\n\t\t\t}\n\n\t\t\treturn tokens\n\t\t} catch {\n\t\t\t// Decryption failure, corrupted data, or JSON parse error.\n\t\t\t// Return null instead of throwing to allow graceful fallback\n\t\t\t// to re-authentication.\n\t\t\treturn null\n\t\t}\n\t}\n\n\t/**\n\t * Clear all stored encrypted tokens.\n\t *\n\t * Removes the encrypted payload from storage. This is a synchronous\n\t * operation since it only removes the localStorage entry.\n\t *\n\t * Call this on logout to ensure no encrypted credential material\n\t * remains in persistent storage.\n\t */\n\tclearTokens(): void {\n\t\tthis.storage.removeItem(this.storageKey)\n\t}\n\n\t/**\n\t * Get the current access token by decrypting stored tokens.\n\t *\n\t * Returns the raw token string without validating expiration.\n\t * The caller is responsible for checking whether the token is\n\t * still valid and initiating a refresh if needed.\n\t *\n\t * @returns The decrypted access token string, or null if no valid tokens are stored\n\t */\n\tasync getAccessToken(): Promise<string | null> {\n\t\tconst tokens = await this.loadTokens()\n\t\treturn tokens?.accessToken ?? null\n\t}\n\n\t/**\n\t * Get the current refresh token by decrypting stored tokens.\n\t *\n\t * @returns The decrypted refresh token string, or null if no valid tokens are stored\n\t */\n\tasync getRefreshToken(): Promise<string | null> {\n\t\tconst tokens = await this.loadTokens()\n\t\treturn tokens?.refreshToken ?? null\n\t}\n}\n","import { KoraError } from '@korajs/core'\n\n// --- Key derivation errors ---\n\n/**\n * Thrown when a key derivation operation fails.\n */\nexport class KeyDerivationError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'KEY_DERIVATION_ERROR', context)\n\t\tthis.name = 'KeyDerivationError'\n\t}\n}\n\n// --- Internal helpers ---\n\n/** Salt length in bytes. 32 bytes (256 bits) provides sufficient randomness. */\nconst SALT_LENGTH = 32\n\n/**\n * PBKDF2 iteration count. 600,000 iterations is the OWASP-recommended minimum\n * for SHA-256 as of 2024, providing strong resistance against brute-force attacks.\n */\nconst PBKDF2_ITERATIONS = 600_000\n\n/** Derived key length in bits (AES-256). */\nconst DERIVED_KEY_LENGTH = 256\n\n/**\n * Asserts that `crypto.subtle` is available, throwing a clear error if not.\n */\nfunction assertCryptoAvailable(): void {\n\tif (typeof globalThis.crypto === 'undefined' || typeof globalThis.crypto.subtle === 'undefined') {\n\t\tthrow new KeyDerivationError(\n\t\t\t'Web Crypto API (crypto.subtle) is not available in this environment. ' +\n\t\t\t\t'Key derivation requires crypto.subtle, which is available in modern browsers and Node.js 20+.',\n\t\t)\n\t}\n}\n\n// --- Public API ---\n\n/**\n * Generates a cryptographically random 32-byte salt for key derivation.\n *\n * Each call returns a unique salt. The salt should be stored alongside the\n * encrypted data so that the same passphrase can reproduce the same key later.\n *\n * @returns A random 32-byte Uint8Array\n *\n * @example\n * ```typescript\n * const salt = generateSalt()\n * // salt.length === 32\n * ```\n */\nexport function generateSalt(): Uint8Array {\n\tif (typeof globalThis.crypto === 'undefined') {\n\t\tthrow new KeyDerivationError(\n\t\t\t'Web Crypto API (crypto) is not available. Cannot generate random salt.',\n\t\t)\n\t}\n\n\treturn globalThis.crypto.getRandomValues(new Uint8Array(SALT_LENGTH))\n}\n\n/**\n * Derives an AES-256-GCM encryption key from a passphrase using PBKDF2.\n *\n * Uses PBKDF2 with SHA-256 and 600,000 iterations (OWASP-recommended minimum)\n * to derive a 256-bit key from the passphrase. The derived key can be used with\n * the database encryption functions ({@link encryptData}, {@link decryptData}).\n *\n * If no salt is provided, a random 32-byte salt is generated. The salt must be\n * persisted alongside the encrypted data so the key can be re-derived later.\n *\n * **Deterministic:** The same passphrase and salt always produce the same key.\n * This is essential for decryption: the user enters their passphrase, the stored\n * salt is used, and the identical key is re-derived to decrypt the data.\n *\n * @param passphrase - The user's passphrase (any non-empty string)\n * @param salt - Optional salt bytes. If omitted, a random 32-byte salt is generated.\n * @returns An object containing the derived CryptoKey and the salt used\n * @throws {KeyDerivationError} If the passphrase is empty, crypto is unavailable, or derivation fails\n *\n * @example\n * ```typescript\n * // First time: derive key with a new salt\n * const { key, salt } = await deriveEncryptionKey('my-secure-passphrase')\n * // Store the salt alongside the encrypted data\n *\n * // Later: re-derive the same key using the stored salt\n * const { key: sameKey } = await deriveEncryptionKey('my-secure-passphrase', salt)\n * ```\n */\nexport async function deriveEncryptionKey(\n\tpassphrase: string,\n\tsalt?: Uint8Array,\n): Promise<{ key: CryptoKey; salt: Uint8Array }> {\n\tassertCryptoAvailable()\n\n\tif (passphrase.length === 0) {\n\t\tthrow new KeyDerivationError(\n\t\t\t'Passphrase must not be empty. Provide a non-empty string for key derivation.',\n\t\t)\n\t}\n\n\tconst usedSalt = salt ?? generateSalt()\n\n\ttry {\n\t\t// Step 1: Import the passphrase as raw key material for PBKDF2\n\t\tconst passphraseBytes = new TextEncoder().encode(passphrase)\n\t\tconst baseKey = await globalThis.crypto.subtle.importKey(\n\t\t\t'raw',\n\t\t\tpassphraseBytes,\n\t\t\t'PBKDF2',\n\t\t\tfalse,\n\t\t\t['deriveBits', 'deriveKey'],\n\t\t)\n\n\t\t// Step 2: Derive an AES-256-GCM key using PBKDF2 with SHA-256\n\t\tconst derivedKey = await globalThis.crypto.subtle.deriveKey(\n\t\t\t{\n\t\t\t\tname: 'PBKDF2',\n\t\t\t\tsalt: usedSalt as unknown as ArrayBuffer,\n\t\t\t\titerations: PBKDF2_ITERATIONS,\n\t\t\t\thash: 'SHA-256',\n\t\t\t},\n\t\t\tbaseKey,\n\t\t\t{ name: 'AES-GCM', length: DERIVED_KEY_LENGTH },\n\t\t\t// extractable: true so the derived key can be exported if needed\n\t\t\ttrue,\n\t\t\t['encrypt', 'decrypt'],\n\t\t)\n\n\t\treturn { key: derivedKey, salt: usedSalt }\n\t} catch (cause) {\n\t\t// Re-throw KeyDerivationError as-is (e.g., from assertCryptoAvailable)\n\t\tif (cause instanceof KeyDerivationError) {\n\t\t\tthrow cause\n\t\t}\n\n\t\tthrow new KeyDerivationError(\n\t\t\t'Failed to derive encryption key from passphrase using PBKDF2. ' +\n\t\t\t\t'Ensure the runtime supports PBKDF2 with SHA-256.',\n\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t)\n\t}\n}\n","/**\n * Configuration for the AutoLockManager.\n */\nexport interface AutoLockConfig {\n\t/** Inactivity timeout in milliseconds before auto-locking. */\n\ttimeout: number\n\t/** Callback invoked when the lock engages (either by timeout or manual lock). */\n\tonLock: () => void\n}\n\n/**\n * Manages inactivity-based auto-locking for the encrypted local store.\n *\n * The AutoLockManager starts an inactivity timer when {@link start} is called.\n * If no user activity is reported via {@link reportActivity} within the configured\n * timeout, the manager transitions to the locked state and invokes the `onLock`\n * callback.\n *\n * This class has no DOM dependencies. It uses `setTimeout` and `clearTimeout` for\n * timing and accepts an `onLock` callback for side effects. The consuming code is\n * responsible for wiring DOM events (e.g., visibility changes, user interactions)\n * to {@link reportActivity}.\n *\n * @example\n * ```typescript\n * const manager = new AutoLockManager({\n * timeout: 15 * 60 * 1000, // 15 minutes\n * onLock: () => {\n * // Clear decrypted data from memory\n * // Show lock screen\n * }\n * })\n *\n * manager.start()\n *\n * // Call on user interactions to reset the timer\n * document.addEventListener('click', () => manager.reportActivity())\n * document.addEventListener('keydown', () => manager.reportActivity())\n *\n * // Check lock state\n * if (manager.isLocked) {\n * // Prompt for passphrase\n * }\n * ```\n */\nexport class AutoLockManager {\n\tprivate readonly _timeout: number\n\tprivate readonly _onLock: () => void\n\tprivate _timerId: ReturnType<typeof setTimeout> | null = null\n\tprivate _isLocked = false\n\tprivate _isRunning = false\n\n\tconstructor(config: AutoLockConfig) {\n\t\tif (config.timeout <= 0) {\n\t\t\tthrow new Error(\n\t\t\t\t`AutoLockManager timeout must be a positive number, but received ${config.timeout}.`,\n\t\t\t)\n\t\t}\n\n\t\tthis._timeout = config.timeout\n\t\tthis._onLock = config.onLock\n\t}\n\n\t/**\n\t * Whether the manager is currently in the locked state.\n\t *\n\t * Becomes `true` when the inactivity timeout fires or {@link lock} is called manually.\n\t * Returns to `false` only when {@link unlock} is called.\n\t */\n\tget isLocked(): boolean {\n\t\treturn this._isLocked\n\t}\n\n\t/**\n\t * Starts monitoring for inactivity.\n\t *\n\t * Begins the inactivity countdown. If the manager is already running, this is\n\t * a no-op to prevent creating multiple timers. Calling `start()` also resets\n\t * the locked state if the manager was previously locked.\n\t */\n\tstart(): void {\n\t\tif (this._isRunning) {\n\t\t\treturn\n\t\t}\n\n\t\tthis._isRunning = true\n\t\tthis._isLocked = false\n\t\tthis._startTimer()\n\t}\n\n\t/**\n\t * Stops monitoring for inactivity.\n\t *\n\t * Clears the pending inactivity timer. Does not change the lock state: if\n\t * the manager was locked, it remains locked. If unlocked, it remains unlocked.\n\t * To resume monitoring, call {@link start} again.\n\t */\n\tstop(): void {\n\t\tthis._isRunning = false\n\t\tthis._clearTimer()\n\t}\n\n\t/**\n\t * Reports user activity, resetting the inactivity timer.\n\t *\n\t * Call this whenever the user interacts with the application (clicks, key presses,\n\t * touches, etc.). If the manager is not running or is already locked, this is a no-op.\n\t */\n\treportActivity(): void {\n\t\tif (!this._isRunning || this._isLocked) {\n\t\t\treturn\n\t\t}\n\n\t\tthis._clearTimer()\n\t\tthis._startTimer()\n\t}\n\n\t/**\n\t * Manually locks the manager immediately.\n\t *\n\t * Clears the inactivity timer and transitions to the locked state. The `onLock`\n\t * callback is invoked. The manager remains running but locked: call {@link unlock}\n\t * to return to the unlocked state, which will restart the inactivity timer.\n\t */\n\tlock(): void {\n\t\tthis._clearTimer()\n\n\t\tif (!this._isLocked) {\n\t\t\tthis._isLocked = true\n\t\t\tthis._onLock()\n\t\t}\n\t}\n\n\t/**\n\t * Unlocks the manager, returning to the unlocked state.\n\t *\n\t * If the manager is running, the inactivity timer is restarted. If the manager\n\t * is not running (was stopped), it simply clears the locked state without\n\t * starting a timer.\n\t */\n\tunlock(): void {\n\t\tthis._isLocked = false\n\n\t\tif (this._isRunning) {\n\t\t\tthis._clearTimer()\n\t\t\tthis._startTimer()\n\t\t}\n\t}\n\n\t/**\n\t * Starts the inactivity timer. When it fires, the manager locks.\n\t */\n\tprivate _startTimer(): void {\n\t\tthis._timerId = setTimeout(() => {\n\t\t\tthis._timerId = null\n\t\t\tthis._isLocked = true\n\t\t\tthis._onLock()\n\t\t}, this._timeout)\n\t}\n\n\t/**\n\t * Clears the pending inactivity timer, if any.\n\t */\n\tprivate _clearTimer(): void {\n\t\tif (this._timerId !== null) {\n\t\t\tclearTimeout(this._timerId)\n\t\t\tthis._timerId = null\n\t\t}\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,iBAAiB;AAWnB,IAAM,YAAN,cAAwB,UAAU;AAAA,EACxC,YAAY,SAAiB,MAAc,SAAmC;AAC7E,UAAM,SAAS,MAAM,OAAO;AAC5B,SAAK,OAAO;AAAA,EACb;AACD;AAyIA,IAAM,wBAAwB;AAQ9B,SAAS,iBAAiB,OAA+C;AACxE,QAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,MAAI,MAAM,WAAW,GAAG;AACvB,WAAO;AAAA,EACR;AAEA,MAAI;AAEH,UAAM,SAAU,MAAM,CAAC,EAAa,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG;AACxE,UAAM,OAAO,KAAK,MAAM;AACxB,WAAO,KAAK,MAAM,IAAI;AAAA,EACvB,QAAQ;AACP,WAAO;AAAA,EACR;AACD;AAMA,SAAS,eAAe,OAAwB;AAC/C,QAAM,UAAU,iBAAiB,KAAK;AACtC,MAAI,CAAC,WAAW,OAAO,QAAQ,QAAQ,UAAU;AAChD,WAAO;AAAA,EACR;AACA,QAAM,aAAa,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAC/C,SAAQ,QAAQ,OAAkB,aAAa;AAChD;AAMA,SAAS,mBAAmB,OAA8B;AACzD,QAAM,UAAU,iBAAiB,KAAK;AACtC,MAAI,CAAC,WAAW,OAAO,QAAQ,QAAQ,UAAU;AAChD,WAAO;AAAA,EACR;AACA,SAAO,QAAQ;AAChB;AAEA,SAAS,kBAAgC;AACxC,MAAI,OAAO,WAAW,UAAU,YAAY;AAC3C,WAAO,YAAY;AAClB,YAAM,IAAI;AAAA,QACT;AAAA,QACA;AAAA,MACD;AAAA,IACD;AAAA,EACD;AACA,SAAO,WAAW,MAAM,KAAK,UAAU;AACxC;AAEA,SAAS,kBAAkB,MAAqE;AAC/F,SAAO;AAAA,IACN,IAAI,KAAK;AAAA,IACT,OAAO,KAAK;AAAA,IACZ,MAAM,KAAK,QAAQ;AAAA,EACpB;AACD;AAEA,SAAS,2BAAoC;AAC5C,SACC,OAAO,WAAW,WAAW,eAC7B,OAAO,WAAW,OAAO,UAAU,WAAW;AAEhD;AAEA,SAAS,sBAAsB,KAAmB;AACjD,MAAI,CAAC,yBAAyB,GAAG;AAChC,UAAM,IAAI;AAAA,MACT;AAAA,MACA;AAAA,IACD;AAAA,EACD;AACA,aAAW,OAAO,SAAS,OAAO,GAAG;AACtC;AAaA,SAAS,mBAAmB,QAAkC;AAE7D,MAAI,kBAAkB;AACtB,MAAI;AACH,QAAI,OAAO,WAAW,eAAe,OAAO,OAAO,iBAAiB,aAAa;AAEhF,YAAM,UAAU,GAAG,MAAM;AACzB,aAAO,aAAa,QAAQ,SAAS,GAAG;AACxC,aAAO,aAAa,WAAW,OAAO;AACtC,wBAAkB;AAAA,IACnB;AAAA,EACD,QAAQ;AAAA,EAER;AAEA,MAAI,iBAAiB;AACpB,UAAM,YAAY,GAAG,MAAM;AAC3B,UAAM,aAAa,GAAG,MAAM;AAC5B,WAAO;AAAA,MACN,iBAAgC;AAC/B,eAAO,OAAO,aAAa,QAAQ,SAAS;AAAA,MAC7C;AAAA,MACA,kBAAiC;AAChC,eAAO,OAAO,aAAa,QAAQ,UAAU;AAAA,MAC9C;AAAA,MACA,UAAU,QAAgB,SAAuB;AAChD,eAAO,aAAa,QAAQ,WAAW,MAAM;AAC7C,eAAO,aAAa,QAAQ,YAAY,OAAO;AAAA,MAChD;AAAA,MACA,QAAc;AACb,eAAO,aAAa,WAAW,SAAS;AACxC,eAAO,aAAa,WAAW,UAAU;AAAA,MAC1C;AAAA,IACD;AAAA,EACD;AAGA,MAAI,cAA6B;AACjC,MAAI,eAA8B;AAClC,SAAO;AAAA,IACN,iBAAgC;AAC/B,aAAO;AAAA,IACR;AAAA,IACA,kBAAiC;AAChC,aAAO;AAAA,IACR;AAAA,IACA,UAAU,QAAgB,SAAuB;AAChD,oBAAc;AACd,qBAAe;AAAA,IAChB;AAAA,IACA,QAAc;AACb,oBAAc;AACd,qBAAe;AAAA,IAChB;AAAA,EACD;AACD;AA2BO,IAAM,aAAN,MAAiB;AAAA,EACN;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAA6C,oBAAI,IAAI;AAAA,EAE9D,SAAoB;AAAA,EACpB,QAAyB;AAAA,EACzB,kBAAiD;AAAA,EACjD,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOvB,YAAY,QAA0B;AAErC,SAAK,YAAY,OAAO,UAAU,QAAQ,QAAQ,EAAE;AACpD,UAAM,SAAS,OAAO,cAAc;AACpC,SAAK,UAAU,OAAO,WAAW,mBAAmB,MAAM;AAC1D,SAAK,UAAU,OAAO,SAAS,gBAAgB;AAC/C,SAAK,iBAAiB,OAAO;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI,QAAmB;AACtB,WAAO,KAAK;AAAA,EACb;AAAA;AAAA,EAGA,IAAI,cAA+B;AAClC,WAAO,KAAK;AAAA,EACb;AAAA;AAAA,EAGA,IAAI,kBAA2B;AAC9B,WAAO,KAAK,WAAW;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM,aAA4B;AAEjC,QAAI,KAAK,cAAc;AACtB;AAAA,IACD;AACA,SAAK,eAAe;AAEpB,UAAM,cAAc,MAAM,KAAK,QAAQ,eAAe;AACtD,UAAM,eAAe,MAAM,KAAK,QAAQ,gBAAgB;AAGxD,QAAI,CAAC,eAAe,CAAC,cAAc;AAClC,WAAK,SAAS,mBAAmB,IAAI;AACrC;AAAA,IACD;AAGA,QAAI,CAAC,eAAe,WAAW,GAAG;AACjC,YAAM,KAAK,eAAe,WAAW;AACrC;AAAA,IACD;AAGA,QAAI;AACH,YAAM,iBAAiB,MAAM,KAAK,mBAAmB,YAAY;AACjE,UAAI,gBAAgB;AACnB,cAAM,KAAK,eAAe,cAAc;AACxC;AAAA,MACD;AAAA,IACD,QAAQ;AAAA,IAER;AAGA,UAAM,KAAK,QAAQ,MAAM;AACzB,SAAK,SAAS,mBAAmB,IAAI;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM,OAAO,QAMS;AACrB,UAAM,OAAO,MAAM,KAAK,mBAAmB,MAAM;AACjD,UAAM,WAAW,MAAM,KAAK,QAAiD,gBAAgB;AAAA,MAC5F,QAAQ;AAAA,MACR;AAAA,IACD,CAAC;AAED,UAAM,SAAS,YAAY,WAAW,SAAS,SAAS;AACxD,UAAM,KAAK,QAAQ,UAAU,OAAO,aAAa,OAAO,YAAY;AAEpE,UAAM,OAAO,MAAM,KAAK,iBAAiB,OAAO,WAAW;AAC3D,SAAK,SAAS,iBAAiB,IAAI;AACnC,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,OAAO,QAKS;AACrB,UAAM,OAAO,MAAM,KAAK,mBAAmB,MAAM;AACjD,UAAM,WAAW,MAAM,KAAK,QAAiD,gBAAgB;AAAA,MAC5F,QAAQ;AAAA,MACR;AAAA,IACD,CAAC;AAED,UAAM,SAAS,YAAY,WAAW,SAAS,SAAS;AACxD,UAAM,KAAK,QAAQ,UAAU,OAAO,aAAa,OAAO,YAAY;AAEpE,UAAM,OAAO,MAAM,KAAK,iBAAiB,OAAO,WAAW;AAC3D,SAAK,SAAS,iBAAiB,IAAI;AACnC,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,gBACL,UACA,UAAqC,CAAC,GACF;AACpC,UAAM,SAAS,MAAM,KAAK,yBAAyB,UAAU,OAAO;AACpE,QAAI,QAAQ,YAAY,yBAAyB,GAAG;AACnD,4BAAsB,OAAO,GAAG;AAAA,IACjC;AACA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,oBAAoB,UAAkB,QAAgD;AAC3F,UAAM,OAAO,MAAM,KAAK,mBAAmB,MAAM;AACjD,UAAM,WAAW,MAAM,KAAK;AAAA,MAC3B,eAAe,mBAAmB,QAAQ,CAAC;AAAA,MAC3C;AAAA,QACC,QAAQ;AAAA,QACR,MAAM,EAAE,GAAG,KAAK;AAAA,MACjB;AAAA,IACD;AAEA,UAAM,KAAK,QAAQ,UAAU,SAAS,OAAO,aAAa,SAAS,OAAO,YAAY;AACtF,UAAM,OAAO,kBAAkB,SAAS,IAAI;AAC5C,SAAK,SAAS,iBAAiB,IAAI;AACnC,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,yBACL,UACA,UAAqC,CAAC,GACF;AACpC,WAAO,KAAK,yBAAyB,UAAU,EAAE,GAAG,SAAS,UAAU,MAAM,CAAC;AAAA,EAC/E;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAU,UAAkB,QAA0D;AAC3F,UAAM,QAAQ,MAAM,KAAK,mBAAmB;AAC5C,WAAO,KAAK,QAA4B,eAAe,mBAAmB,QAAQ,CAAC,SAAS;AAAA,MAC3F,QAAQ;AAAA,MACR,MAAM;AAAA,QACL,MAAM,OAAO;AAAA,QACb,OAAO,OAAO;AAAA,MACf;AAAA,MACA;AAAA,IACD,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,qBAAoD;AACzD,UAAM,QAAQ,MAAM,KAAK,mBAAmB;AAC5C,WAAO,KAAK,QAA8B,qBAAqB;AAAA,MAC9D,QAAQ;AAAA,MACR;AAAA,IACD,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAAY,UAAiC;AAClD,UAAM,QAAQ,MAAM,KAAK,mBAAmB;AAC5C,UAAM,KAAK,QAAsB,eAAe,mBAAmB,QAAQ,CAAC,SAAS;AAAA,MACpF,QAAQ;AAAA,MACR;AAAA,IACD,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,UAAyB;AAC9B,UAAM,cAAc,MAAM,KAAK,QAAQ,eAAe;AACtD,UAAM,eAAe,MAAM,KAAK,QAAQ,gBAAgB;AAGxD,UAAM,KAAK,QAAQ,MAAM;AACzB,SAAK,kBAAkB;AACvB,SAAK,SAAS,mBAAmB,IAAI;AAGrC,QAAI,aAAa;AAChB,UAAI;AACH,cAAM,KAAK,QAAQ,iBAAiB;AAAA,UACnC,QAAQ;AAAA,UACR,MAAM,EAAE,cAAc,gBAAgB,OAAU;AAAA,UAChD,OAAO;AAAA,QACR,CAAC;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,iBAAyC;AAC9C,UAAM,cAAc,MAAM,KAAK,QAAQ,eAAe;AAEtD,QAAI,eAAe,CAAC,eAAe,WAAW,GAAG;AAChD,aAAO;AAAA,IACR;AAGA,UAAM,eAAe,MAAM,KAAK,QAAQ,gBAAgB;AACxD,QAAI,CAAC,cAAc;AAClB,aAAO;AAAA,IACR;AAEA,QAAI;AACH,YAAM,iBAAiB,MAAM,KAAK,mBAAmB,YAAY;AACjE,aAAO;AAAA,IACR,QAAQ;AACP,aAAO;AAAA,IACR;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,eAAuC;AAC5C,WAAO,KAAK,eAAe;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuBA,aAAa,UAAkD;AAC9D,SAAK,UAAU,IAAI,QAAQ;AAC3B,WAAO,MAAM;AACZ,WAAK,UAAU,OAAO,QAAQ;AAAA,IAC/B;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,SAAS,OAAkB,MAA6B;AAC/D,UAAM,UAAU,KAAK,WAAW,SAAS,KAAK,UAAU;AACxD,SAAK,SAAS;AACd,SAAK,QAAQ;AAEb,QAAI,SAAS;AACZ,iBAAW,YAAY,KAAK,WAAW;AACtC,YAAI;AACH,mBAAS,KAAK;AAAA,QACf,QAAQ;AAAA,QAGR;AAAA,MACD;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,eAAe,aAAoC;AAChE,QAAI;AACH,YAAM,OAAO,MAAM,KAAK,iBAAiB,WAAW;AACpD,WAAK,SAAS,iBAAiB,IAAI;AAAA,IACpC,QAAQ;AAEP,YAAM,SAAS,mBAAmB,WAAW;AAC7C,UAAI,QAAQ;AACX,aAAK,SAAS,iBAAiB;AAAA,UAC9B,IAAI;AAAA,UACJ,OAAO;AAAA,UACP,MAAM;AAAA,QACP,CAAC;AAAA,MACF,OAAO;AACN,cAAM,KAAK,QAAQ,MAAM;AACzB,aAAK,SAAS,mBAAmB,IAAI;AAAA,MACtC;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,iBAAiB,aAAwC;AACtE,UAAM,UAAU,MAAM,KAAK,QAA6B,YAAY;AAAA,MACnE,QAAQ;AAAA,MACR,OAAO;AAAA,IACR,CAAC;AACD,WAAO,kBAAkB,OAAO;AAAA,EACjC;AAAA,EAEA,MAAc,yBACb,UACA,SACoC;AACpC,UAAM,SAAS,IAAI,gBAAgB;AACnC,UAAM,eAAe,MAAM,KAAK,mBAAmB;AAAA,MAClD,UAAU,QAAQ;AAAA,MAClB,iBAAiB,QAAQ;AAAA,IAC1B,CAAC;AAED,QAAI,QAAQ,UAAU;AACrB,aAAO,IAAI,YAAY,QAAQ,QAAQ;AAAA,IACxC;AACA,QAAI,aAAa,UAAU;AAC1B,aAAO,IAAI,YAAY,aAAa,QAAQ;AAAA,IAC7C;AACA,QAAI,aAAa,iBAAiB;AACjC,aAAO,IAAI,mBAAmB,aAAa,eAAe;AAAA,IAC3D;AACA,QAAI,QAAQ,UAAU;AACrB,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,QAAQ,QAAQ,GAAG;AAC5D,YAAI,UAAU,UAAa,UAAU,MAAM;AAC1C,iBAAO,IAAI,KAAK,OAAO,KAAK,CAAC;AAAA,QAC9B;AAAA,MACD;AAAA,IACD;AAEA,UAAM,QAAQ,OAAO,SAAS;AAC9B,WAAO,KAAK;AAAA,MACX,eAAe,mBAAmB,QAAQ,CAAC,GAAG,QAAQ,IAAI,KAAK,KAAK,EAAE;AAAA,MACtE;AAAA,QACC,QAAQ;AAAA,MACT;AAAA,IACD;AAAA,EACD;AAAA,EAEA,MAAc,mBACb,QACa;AACb,QAAI,CAAC,KAAK,kBAAmB,OAAO,YAAY,OAAO,iBAAkB;AACxE,aAAO;AAAA,IACR;AAEA,UAAM,WAAW,MAAM,KAAK,eAAe,kBAAkB;AAC7D,WAAO;AAAA,MACN,GAAG;AAAA,MACH,UAAU,OAAO,YAAY,SAAS;AAAA,MACtC,iBAAiB,OAAO,mBAAmB,SAAS;AAAA,IACrD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,mBAAmB,cAA8C;AAE9E,QAAI,KAAK,iBAAiB;AACzB,aAAO,KAAK;AAAA,IACb;AAEA,SAAK,kBAAkB,KAAK,eAAe,YAAY;AAEvD,QAAI;AACH,YAAM,SAAS,MAAM,KAAK;AAC1B,aAAO;AAAA,IACR,UAAE;AACD,WAAK,kBAAkB;AAAA,IACxB;AAAA,EACD;AAAA,EAEA,MAAc,qBAAsC;AACnD,UAAM,QAAQ,MAAM,KAAK,eAAe;AACxC,QAAI,CAAC,OAAO;AACX,YAAM,IAAI,UAAU,iDAAiD,eAAe;AAAA,IACrF;AACA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,eAAe,cAA8C;AAC1E,QAAI;AACH,YAAM,WAAW,MAAM,KAAK,QAA4B,iBAAiB;AAAA,QACxE,QAAQ;AAAA,QACR,MAAM,EAAE,aAAa;AAAA,MACtB,CAAC;AAED,YAAM,KAAK,QAAQ,UAAU,SAAS,aAAa,SAAS,YAAY;AACxE,aAAO,SAAS;AAAA,IACjB,QAAQ;AAEP,YAAM,KAAK,QAAQ,MAAM;AACzB,WAAK,SAAS,mBAAmB,IAAI;AACrC,aAAO;AAAA,IACR;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAc,QACb,MACA,SAKa;AACb,UAAM,MAAM,GAAG,KAAK,SAAS,GAAG,IAAI;AAEpC,UAAM,UAAkC,CAAC;AACzC,QAAI,QAAQ,MAAM;AACjB,cAAQ,cAAc,IAAI;AAAA,IAC3B;AACA,QAAI,QAAQ,OAAO;AAClB,cAAQ,gBAAgB,UAAU,QAAQ,KAAK;AAAA,IAChD;AAEA,QAAI;AACJ,QAAI;AACH,iBAAW,MAAM,KAAK,QAAQ,KAAK;AAAA,QAClC,QAAQ,QAAQ;AAAA,QAChB;AAAA,QACA,MAAM,QAAQ,OAAO,KAAK,UAAU,QAAQ,IAAI,IAAI;AAAA,MACrD,CAAC;AAAA,IACF,SAAS,OAAO;AACf,YAAM,IAAI;AAAA,QACT,sBAAsB,IAAI,+BAA+B,KAAK,SAAS;AAAA,QACvE;AAAA,QACA,EAAE,MAAM,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE;AAAA,MACvE;AAAA,IACD;AAEA,QAAI,CAAC,SAAS,IAAI;AACjB,UAAI,eAAe,6BAA6B,SAAS,MAAM;AAC/D,UAAI;AACJ,UAAI;AACH,cAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,YAAI,OAAO,KAAK,UAAU,UAAU;AACnC,yBAAe,KAAK;AACpB,wBAAc;AAAA,QACf,WAAW,OAAO,KAAK,YAAY,UAAU;AAC5C,yBAAe,KAAK;AACpB,wBAAc;AAAA,QACf;AAAA,MACD,QAAQ;AAAA,MAER;AAEA,YAAM,IAAI,UAAU,cAAc,qBAAqB;AAAA,QACtD;AAAA,QACA,QAAQ,SAAS;AAAA,QACjB;AAAA,MACD,CAAC;AAAA,IACF;AAEA,UAAM,OAAQ,MAAM,SAAS,KAAK;AAIlC,UAAM,OAAQ,KAAK,SAAS,SAAY,KAAK,OAAO;AACpD,WAAO;AAAA,EACR;AACD;;;ACl4BA,SAAS,aAAAA,kBAAiB;;;ACA1B,SAAS,aAAAC,kBAAiB;AAQnB,IAAM,sBAAN,cAAkCA,WAAU;AAAA,EAClD,YAAY,SAAiB,SAAmC;AAC/D,UAAM,SAAS,0BAA0B,OAAO;AAChD,SAAK,OAAO;AAAA,EACb;AACD;AA4DA,IAAM,oBAAoB;AAG1B,IAAM,iBAAiB;AAGvB,IAAM,cAAc;AAyBb,IAAM,0BAAN,MAAwD;AAAA,EACtD,YAAyC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASzC,eAAqC;AAG5C,QAAI,KAAK,cAAc,MAAM;AAC5B,aAAO,KAAK;AAAA,IACb;AAEA,SAAK,YAAY,IAAI,QAAqB,CAAC,SAAS,WAAW;AAC9D,UAAI;AAEJ,UAAI;AACH,kBAAU,WAAW,UAAU,KAAK,mBAAmB,WAAW;AAAA,MACnE,SAAS,OAAO;AACf,aAAK,YAAY;AACjB;AAAA,UACC,IAAI;AAAA,YACH;AAAA,YAEA,EAAE,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE;AAAA,UACjE;AAAA,QACD;AACA;AAAA,MACD;AAEA,cAAQ,kBAAkB,MAAM;AAC/B,cAAM,KAAK,QAAQ;AACnB,YAAI,CAAC,GAAG,iBAAiB,SAAS,cAAc,GAAG;AAClD,aAAG,kBAAkB,cAAc;AAAA,QACpC;AAAA,MACD;AAEA,cAAQ,YAAY,MAAM;AACzB,gBAAQ,QAAQ,MAAM;AAAA,MACvB;AAEA,cAAQ,UAAU,MAAM;AACvB,aAAK,YAAY;AACjB;AAAA,UACC,IAAI,oBAAoB,6DAA6D;AAAA,YACpF,OAAO,QAAQ,OAAO;AAAA,UACvB,CAAC;AAAA,QACF;AAAA,MACD;AAEA,cAAQ,YAAY,MAAM;AACzB,aAAK,YAAY;AACjB;AAAA,UACC,IAAI;AAAA,YACH;AAAA,UAGD;AAAA,QACD;AAAA,MACD;AAAA,IACD,CAAC;AAED,WAAO,KAAK;AAAA,EACb;AAAA;AAAA,EAGA,MAAM,YAAY,UAAkB,SAAuC;AAC1E,UAAM,KAAK,MAAM,KAAK,aAAa;AAEnC,WAAO,IAAI,QAAc,CAAC,SAAS,WAAW;AAC7C,UAAI;AACH,cAAM,KAAK,GAAG,YAAY,gBAAgB,WAAW;AACrD,cAAM,QAAQ,GAAG,YAAY,cAAc;AAI3C,cAAM,IAAI,SAAS,QAAQ;AAE3B,WAAG,aAAa,MAAM;AACrB,kBAAQ;AAAA,QACT;AAEA,WAAG,UAAU,MAAM;AAClB;AAAA,YACC,IAAI,oBAAoB,uCAAuC,QAAQ,MAAM;AAAA,cAC5E;AAAA,cACA,OAAO,GAAG,OAAO;AAAA,YAClB,CAAC;AAAA,UACF;AAAA,QACD;AAAA,MACD,SAAS,OAAO;AACf;AAAA,UACC,IAAI,oBAAoB,uCAAuC,QAAQ,MAAM;AAAA,YAC5E;AAAA,YACA,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,UAC7D,CAAC;AAAA,QACF;AAAA,MACD;AAAA,IACD,CAAC;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,YAAY,UAAiD;AAClE,UAAM,KAAK,MAAM,KAAK,aAAa;AAEnC,WAAO,IAAI,QAA8B,CAAC,SAAS,WAAW;AAC7D,UAAI;AACH,cAAM,KAAK,GAAG,YAAY,gBAAgB,UAAU;AACpD,cAAM,QAAQ,GAAG,YAAY,cAAc;AAC3C,cAAM,UAAU,MAAM,IAAI,QAAQ;AAElC,gBAAQ,YAAY,MAAM;AACzB,gBAAM,SAAS,QAAQ;AACvB,kBAAQ,UAAU,IAAI;AAAA,QACvB;AAEA,gBAAQ,UAAU,MAAM;AACvB;AAAA,YACC,IAAI,oBAAoB,uCAAuC,QAAQ,MAAM;AAAA,cAC5E;AAAA,cACA,OAAO,QAAQ,OAAO;AAAA,YACvB,CAAC;AAAA,UACF;AAAA,QACD;AAAA,MACD,SAAS,OAAO;AACf;AAAA,UACC,IAAI,oBAAoB,uCAAuC,QAAQ,MAAM;AAAA,YAC5E;AAAA,YACA,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,UAC7D,CAAC;AAAA,QACF;AAAA,MACD;AAAA,IACD,CAAC;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,cAAc,UAAiC;AACpD,UAAM,KAAK,MAAM,KAAK,aAAa;AAEnC,WAAO,IAAI,QAAc,CAAC,SAAS,WAAW;AAC7C,UAAI;AACH,cAAM,KAAK,GAAG,YAAY,gBAAgB,WAAW;AACrD,cAAM,QAAQ,GAAG,YAAY,cAAc;AAC3C,cAAM,OAAO,QAAQ;AAErB,WAAG,aAAa,MAAM;AACrB,kBAAQ;AAAA,QACT;AAEA,WAAG,UAAU,MAAM;AAClB;AAAA,YACC,IAAI,oBAAoB,yCAAyC,QAAQ,MAAM;AAAA,cAC9E;AAAA,cACA,OAAO,GAAG,OAAO;AAAA,YAClB,CAAC;AAAA,UACF;AAAA,QACD;AAAA,MACD,SAAS,OAAO;AACf;AAAA,UACC,IAAI,oBAAoB,yCAAyC,QAAQ,MAAM;AAAA,YAC9E;AAAA,YACA,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,UAC7D,CAAC;AAAA,QACF;AAAA,MACD;AAAA,IACD,CAAC;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,WAAW,UAAoC;AACpD,UAAM,KAAK,MAAM,KAAK,aAAa;AAEnC,WAAO,IAAI,QAAiB,CAAC,SAAS,WAAW;AAChD,UAAI;AACH,cAAM,KAAK,GAAG,YAAY,gBAAgB,UAAU;AACpD,cAAM,QAAQ,GAAG,YAAY,cAAc;AAI3C,cAAM,UAAU,MAAM,MAAM,QAAQ;AAEpC,gBAAQ,YAAY,MAAM;AACzB,kBAAQ,QAAQ,SAAS,CAAC;AAAA,QAC3B;AAEA,gBAAQ,UAAU,MAAM;AACvB;AAAA,YACC,IAAI;AAAA,cACH,kDAAkD,QAAQ;AAAA,cAC1D,EAAE,UAAU,OAAO,QAAQ,OAAO,QAAQ;AAAA,YAC3C;AAAA,UACD;AAAA,QACD;AAAA,MACD,SAAS,OAAO;AACf;AAAA,UACC,IAAI,oBAAoB,kDAAkD,QAAQ,MAAM;AAAA,YACvF;AAAA,YACA,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,UAC7D,CAAC;AAAA,QACF;AAAA,MACD;AAAA,IACD,CAAC;AAAA,EACF;AACD;AAoBO,IAAM,yBAAN,MAAuD;AAAA,EAC5C,QAAQ,oBAAI,IAA2B;AAAA;AAAA,EAGxD,MAAM,YAAY,UAAkB,SAAuC;AAC1E,SAAK,MAAM,IAAI,UAAU,OAAO;AAAA,EACjC;AAAA;AAAA,EAGA,MAAM,YAAY,UAAiD;AAClE,WAAO,KAAK,MAAM,IAAI,QAAQ,KAAK;AAAA,EACpC;AAAA;AAAA,EAGA,MAAM,cAAc,UAAiC;AACpD,SAAK,MAAM,OAAO,QAAQ;AAAA,EAC3B;AAAA;AAAA,EAGA,MAAM,WAAW,UAAoC;AACpD,WAAO,KAAK,MAAM,IAAI,QAAQ;AAAA,EAC/B;AACD;AAoBO,SAAS,uBAAuC;AACtD,MAAI,OAAO,WAAW,cAAc,aAAa;AAChD,WAAO,IAAI,wBAAwB;AAAA,EACpC;AACA,SAAO,IAAI,uBAAuB;AACnC;;;ADrXA,IAAM,wBAAwB;AAwBvB,IAAM,0BAAN,cAAsCC,WAAU;AAAA,EACtD,YAAY,SAAiB,SAAmC;AAC/D,UAAM,SAAS,8BAA8B,OAAO;AACpD,SAAK,OAAO;AAAA,EACb;AACD;AAUO,SAAS,+BACf,SAC6B;AAC7B,QAAM,UAAU,QAAQ;AACxB,QAAM,WAAW,QAAQ,YAAY,gCAAgC;AACrE,QAAM,cAAc,QAAQ,eAAe;AAC3C,QAAM,mBAAmB,QAAQ,oBAAoB;AAErD,SAAO;AAAA,IACN,MAAM,oBAAiD;AACtD,UAAI,WAAW,MAAM,QAAQ,QAAQ,WAAW;AAChD,UAAI,CAAC,UAAU;AACd,mBAAW,iBAAiB;AAC5B,cAAM,QAAQ,QAAQ,aAAa,QAAQ;AAAA,MAC5C;AAEA,UAAI,UAAU,MAAM,SAAS,YAAY,QAAQ;AACjD,UAAI,CAAC,SAAS;AACb,kBAAU,MAAM,sBAAsB;AACtC,cAAM,SAAS,YAAY,UAAU,OAAO;AAAA,MAC7C;AAEA,YAAM,YAAY,MAAM,mBAAmB,OAAO;AAClD,aAAO;AAAA,QACN;AAAA,QACA,iBAAiB,KAAK,UAAU,SAAS;AAAA,MAC1C;AAAA,IACD;AAAA,EACD;AACD;AAEA,SAAS,kCAAkD;AAC1D,MAAI,OAAO,WAAW,cAAc,aAAa;AAChD,WAAO,qBAAqB;AAAA,EAC7B;AAEA,QAAM,IAAI;AAAA,IACT;AAAA,EACD;AACD;AAEA,SAAS,kBAA0B;AAClC,MAAI,OAAO,WAAW,QAAQ,eAAe,YAAY;AACxD,WAAO,WAAW,OAAO,WAAW;AAAA,EACrC;AAEA,QAAM,QAAQ,IAAI,WAAW,EAAE;AAC/B,MAAI,OAAO,WAAW,QAAQ,oBAAoB,YAAY;AAC7D,eAAW,OAAO,gBAAgB,KAAK;AAAA,EACxC,OAAO;AACN,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACtC,YAAM,CAAC,IAAI,KAAK,MAAM,KAAK,OAAO,IAAI,GAAG;AAAA,IAC1C;AAAA,EACD;AAEA,QAAM,CAAC,IAAM,MAAM,CAAC,IAAe,KAAQ;AAC3C,QAAM,CAAC,IAAM,MAAM,CAAC,IAAe,KAAQ;AAC3C,QAAM,MAAM,CAAC,GAAG,KAAK,EAAE,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AAChF,SAAO,GAAG,IAAI,MAAM,GAAG,CAAC,CAAC,IAAI,IAAI,MAAM,GAAG,EAAE,CAAC,IAAI,IAAI,MAAM,IAAI,EAAE,CAAC,IAAI,IAAI,MAAM,IAAI,EAAE,CAAC,IAAI,IAAI,MAAM,EAAE,CAAC;AACzG;;;AEzEO,SAAS,uBAAuB,SAAoD;AAC1F,QAAM,SAAS,QAAQ,UAAU;AACjC,QAAM,YAAY,GAAG,MAAM;AAC3B,QAAM,aAAa,GAAG,MAAM;AAC5B,QAAM,QAAQ,QAAQ;AAEtB,SAAO;AAAA,IACN,gBAAgB,MAAM,MAAM,QAAQ,SAAS;AAAA,IAC7C,iBAAiB,MAAM,MAAM,QAAQ,UAAU;AAAA,IAC/C,MAAM,UAAU,aAAqB,cAAqC;AACzE,YAAM,MAAM,QAAQ,WAAW,WAAW;AAC1C,YAAM,MAAM,QAAQ,YAAY,YAAY;AAAA,IAC7C;AAAA,IACA,MAAM,QAAuB;AAC5B,YAAM,MAAM,WAAW,SAAS;AAChC,YAAM,MAAM,WAAW,UAAU;AAAA,IAClC;AAAA,EACD;AACD;AAQO,SAAS,+BAAiD;AAChE,MAAI,cAA6B;AACjC,MAAI,eAA8B;AAElC,SAAO;AAAA,IACN,gBAAgB,MAAM;AAAA,IACtB,iBAAiB,MAAM;AAAA,IACvB,UAAU,QAAgB,SAAuB;AAChD,oBAAc;AACd,qBAAe;AAAA,IAChB;AAAA,IACA,QAAc;AACb,oBAAc;AACd,qBAAe;AAAA,IAChB;AAAA,EACD;AACD;AAKO,SAAS,iCACf,SACA,QACmB;AACnB,SAAO,uBAAuB;AAAA,IAC7B;AAAA,IACA,OAAO;AAAA,MACN,SAAS,CAAC,QAAQ,QAAQ,QAAQ,GAAG;AAAA,MACrC,SAAS,CAAC,KAAK,UAAU;AACxB,gBAAQ,QAAQ,KAAK,KAAK;AAAA,MAC3B;AAAA,MACA,YAAY,CAAC,QAAQ;AACpB,gBAAQ,WAAW,GAAG;AAAA,MACvB;AAAA,IACD;AAAA,EACD,CAAC;AACF;;;ACpDO,SAAS,eAAe,SAA4C;AAC1E,QAAM,UACL,QAAQ,YACP,QAAQ,kBACN,uBAAuB;AAAA,IACvB,OAAO,QAAQ;AAAA,IACf,QAAQ,QAAQ;AAAA,EACjB,CAAC,IACA,6BAA6B,QAAQ,UAAU;AAEnD,QAAM,iBACL,QAAQ,mBAAmB,QACxB,SACC,QAAQ,kBACV,+BAA+B,QAAQ,iBAAiB,QAAQ,cAAc;AAEjF,SAAO,IAAI,WAAW;AAAA,IACrB,WAAW,QAAQ;AAAA,IACnB,YAAY,QAAQ;AAAA,IACpB;AAAA,IACA,OAAO,QAAQ;AAAA,IACf;AAAA,EACD,CAAC;AACF;AAEA,SAAS,6BACR,YAC+B;AAC/B,QAAM,UAAU,qBAAqB;AACrC,SAAO,UAAU,iCAAiC,SAAS,UAAU,IAAI;AAC1E;AAEA,SAAS,+BACR,iBACA,gBACyC;AACzC,QAAM,UAAU,mBAAmB,qBAAqB;AACxD,MAAI,CAAC,SAAS;AACb,WAAO;AAAA,EACR;AAEA,MAAI;AACH,WAAO,+BAA+B;AAAA,MACrC;AAAA,MACA,UAAU;AAAA,IACX,CAAC;AAAA,EACF,QAAQ;AACP,WAAO;AAAA,EACR;AACD;AAEA,SAAS,uBAAuC;AAC/C,MAAI;AACH,QAAI,OAAO,WAAW,iBAAiB,aAAa;AACnD,aAAO;AAAA,IACR;AACA,UAAM,MAAM;AACZ,eAAW,aAAa,QAAQ,KAAK,GAAG;AACxC,eAAW,aAAa,WAAW,GAAG;AACtC,WAAO,WAAW;AAAA,EACnB,QAAQ;AACP,WAAO;AAAA,EACR;AACD;;;ACxGA;AAAA,EAGC;AAAA,EACA;AAAA,OACM;AA6CP,SAASC,kBAAiB,OAA+C;AACxE,QAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,MAAI,MAAM,WAAW,GAAG;AACvB,WAAO;AAAA,EACR;AAEA,QAAM,iBAAiB,MAAM,CAAC;AAC9B,MAAI,mBAAmB,QAAW;AACjC,WAAO;AAAA,EACR;AAEA,MAAI;AACH,UAAM,SAAS,eAAe,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG;AAClE,UAAM,SAAS,OAAO,OAAO,OAAO,UAAW,IAAK,OAAO,SAAS,KAAM,GAAI,GAAG;AACjF,UAAM,OACL,OAAO,SAAS,aAAa,KAAK,MAAM,IAAI,OAAO,KAAK,QAAQ,QAAQ,EAAE,SAAS,OAAO;AAC3F,UAAM,SAAkB,KAAK,MAAM,IAAI;AACvC,QAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,MAAM,QAAQ,MAAM,GAAG;AAC3E,aAAO;AAAA,IACR;AACA,WAAO;AAAA,EACR,QAAQ;AACP,WAAO;AAAA,EACR;AACD;AAEA,SAAS,uBAAuB,QAAqD;AACpF,QAAM,MAAM,OAAO;AACnB,SAAO,OAAO,QAAQ,YAAY,IAAI,SAAS,IAAI,MAAM;AAC1D;AAwBO,SAAS,mBAAmB,SAAqD;AACvF,QAAM,EAAE,YAAY,QAAQ,iBAAiB,YAAY,UAAU,IAAI;AAEvE,QAAM,UAA2B;AAAA,IAChC,MAAM,YAAY;AACjB,YAAM,QAAQ,MAAM,WAAW,eAAe;AAC9C,aAAO,EAAE,OAAO,SAAS,GAAG;AAAA,IAC7B;AAAA,EACD;AAEA,UAAQ,mBAAmB,YAAY;AACtC,QAAI,WAAW,UAAU,UAAW,QAAO,EAAE,OAAO,UAAU;AAC9D,UAAM,QAAQ,MAAM,WAAW,eAAe;AAC9C,QAAI,CAAC,OAAO;AACX,aAAO,cAAc,UAClB,EAAE,OAAO,aAAa,uBAAuB,KAAK,IAClD,EAAE,OAAO,cAAc,uBAAuB,MAAM;AAAA,IACxD;AACA,UAAM,SAASA,kBAAiB,KAAK;AACrC,UAAM,SAAS,OAAO,QAAQ,QAAQ,WAAW,OAAO,MAAM;AAC9D,QAAI,CAAC,QAAQ;AACZ,aAAO,cAAc,UAClB,EAAE,OAAO,aAAa,uBAAuB,KAAK,IAClD,EAAE,OAAO,cAAc,uBAAuB,MAAM;AAAA,IACxD;AACA,WAAO,EAAE,OAAO,iBAAiB,QAAQ,MAAM;AAAA,EAChD;AAEA,MAAI,QAAQ;AACX,YAAQ,kBAAkB,YAAY;AACrC,YAAM,QAAQ,MAAM,WAAW,eAAe;AAC9C,UAAI,CAAC,OAAO;AACX,eAAO;AAAA,MACR;AAEA,YAAM,SAASA,kBAAiB,KAAK;AACrC,UAAI,CAAC,QAAQ;AACZ,eAAO;AAAA,MACR;AAEA,YAAM,cAAc,kBACjB,gBAAgB,MAAM,IACtB,6BAA6B,QAAQ,MAAM;AAE9C,aAAO,cAAc,QAAQ,WAAW;AAAA,IACzC;AAAA,EACD;AAEA,UAAQ,gBAAgB,YAAY;AACnC,UAAM,QAAQ,MAAM,WAAW,eAAe;AAC9C,QAAI,CAAC,OAAO;AACX,aAAO;AAAA,IACR;AAEA,UAAM,SAASA,kBAAiB,KAAK;AACrC,QAAI,CAAC,QAAQ;AACZ,aAAO;AAAA,IACR;AAEA,WAAO,uBAAuB,MAAM;AAAA,EACrC;AAEA,UAAQ,gBAAgB,YAAY;AACnC,UAAM,QAAQ,MAAM,WAAW,eAAe;AAC9C,QAAI,CAAC,OAAO;AACX,aAAO;AAAA,IACR;AAEA,UAAM,SAASA,kBAAiB,KAAK;AACrC,QAAI,CAAC,UAAU,OAAO,OAAO,QAAQ,YAAY,OAAO,IAAI,WAAW,GAAG;AACzE,aAAO;AAAA,IACR;AAEA,WAAO,OAAO;AAAA,EACf;AAEA,MAAI,WAAW,cAAc;AAC5B,YAAQ,YAAY,CAAC,aAAa,WAAW,eAAe,MAAM,SAAS,CAAC,MAAM,MAAM;AAAA,IAAC;AAAA,EAC1F;AAEA,SAAO;AACR;;;ACxLA,SAAS,aAAAC,kBAAiB;AA2DnB,IAAM,iBAAN,cAA6BA,WAAU;AAAA,EAC7C,YAAY,SAAiB,MAAc,SAAmC;AAC7E,UAAM,SAAS,MAAM,OAAO;AAC5B,SAAK,OAAO;AAAA,EACb;AACD;AAuBO,IAAM,YAAN,MAAgB;AAAA,EACL;AAAA,EACA;AAAA,EACA,YAAY,oBAAI,IAAoC;AAAA,EAE7D,eAA8B;AAAA,EAC9B,aAAwC;AAAA,EACxC,cAA6B;AAAA,EAErC,YAAY,QAAyB;AACpC,SAAK,YAAY,OAAO,UAAU,QAAQ,QAAQ,EAAE;AACpD,SAAK,iBAAiB,OAAO;AAAA,EAC9B;AAAA;AAAA;AAAA,EAKA,IAAI,cAA6B;AAChC,WAAO,KAAK;AAAA,EACb;AAAA;AAAA,EAGA,IAAI,YAAuC;AAC1C,WAAO,KAAK;AAAA,EACb;AAAA;AAAA,EAGA,IAAI,aAA4B;AAC/B,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,UAAU,QAIgB;AAC/B,WAAO,KAAK,QAA4B,SAAS;AAAA,MAChD,QAAQ;AAAA,MACR,MAAM;AAAA,IACP,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAA0C;AAC/C,WAAO,KAAK,QAA8B,SAAS,EAAE,QAAQ,MAAM,CAAC;AAAA,EACrE;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAO,OAA4C;AACxD,WAAO,KAAK,QAA4B,SAAS,KAAK,IAAI,EAAE,QAAQ,MAAM,CAAC;AAAA,EAC5E;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UACL,OACA,QAC8B;AAC9B,UAAM,SAAS,MAAM,KAAK,QAA4B,SAAS,KAAK,IAAI;AAAA,MACvE,QAAQ;AAAA,MACR,MAAM;AAAA,IACP,CAAC;AAED,QAAI,KAAK,iBAAiB,OAAO;AAChC,WAAK,aAAa;AAAA,IACnB;AACA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAU,OAA8B;AAC7C,UAAM,KAAK,QAAQ,SAAS,KAAK,IAAI,EAAE,QAAQ,SAAS,CAAC;AACzD,QAAI,KAAK,iBAAiB,OAAO;AAChC,WAAK,eAAe;AACpB,WAAK,aAAa;AAClB,WAAK,cAAc;AACnB,WAAK,gBAAgB;AAAA,IACtB;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,UAAU,OAA8B;AAC7C,UAAM,MAAM,MAAM,KAAK,QAA4B,SAAS,KAAK,IAAI,EAAE,QAAQ,MAAM,CAAC;AACtF,UAAM,aAAa,MAAM,KAAK,QAA0B,SAAS,KAAK,eAAe;AAAA,MACpF,QAAQ;AAAA,IACT,CAAC;AAED,SAAK,eAAe;AACpB,SAAK,aAAa;AAClB,SAAK,cAAc,WAAW;AAC9B,SAAK,gBAAgB;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAuB;AACtB,SAAK,eAAe;AACpB,SAAK,aAAa;AAClB,SAAK,cAAc;AACnB,SAAK,gBAAgB;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,YAAY,OAA4C;AAC7D,WAAO,KAAK,QAA4B,SAAS,KAAK,YAAY,EAAE,QAAQ,MAAM,CAAC;AAAA,EACpF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,OAAe,QAA+B;AAChE,UAAM,KAAK,QAAQ,SAAS,KAAK,YAAY,MAAM,IAAI,EAAE,QAAQ,SAAS,CAAC;AAAA,EAC5E;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,iBAAiB,OAAe,QAAgB,MAAyC;AAC9F,WAAO,KAAK,QAA0B,SAAS,KAAK,YAAY,MAAM,SAAS;AAAA,MAC9E,QAAQ;AAAA,MACR,MAAM,EAAE,KAAK;AAAA,IACd,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,kBAAkB,OAAe,YAAmC;AACzE,UAAM,KAAK,QAAQ,SAAS,KAAK,aAAa;AAAA,MAC7C,QAAQ;AAAA,MACR,MAAM,EAAE,WAAW;AAAA,IACpB,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SAAS,OAA8B;AAC5C,UAAM,KAAK,QAAQ,SAAS,KAAK,UAAU,EAAE,QAAQ,OAAO,CAAC;AAC7D,QAAI,KAAK,iBAAiB,OAAO;AAChC,WAAK,eAAe;AACpB,WAAK,aAAa;AAClB,WAAK,cAAc;AACnB,WAAK,gBAAgB;AAAA,IACtB;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,aACL,OACA,QAC4B;AAC5B,WAAO,KAAK,QAA0B,SAAS,KAAK,gBAAgB;AAAA,MACnE,QAAQ;AAAA,MACR,MAAM;AAAA,IACP,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,iBAAiB,OAA0C;AAChE,WAAO,KAAK,QAA0B,uBAAuB;AAAA,MAC5D,QAAQ;AAAA,MACR,MAAM,EAAE,MAAM;AAAA,IACf,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,gBAAgB,OAA4C;AACjE,WAAO,KAAK,QAA4B,SAAS,KAAK,gBAAgB,EAAE,QAAQ,MAAM,CAAC;AAAA,EACxF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,iBAAiB,OAAe,cAAqC;AAC1E,UAAM,KAAK,QAAQ,SAAS,KAAK,gBAAgB,YAAY,IAAI,EAAE,QAAQ,SAAS,CAAC;AAAA,EACtF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,kBAAkB,OAA4C;AACnE,WAAO,KAAK,QAA4B,sBAAsB,mBAAmB,KAAK,CAAC,IAAI;AAAA,MAC1F,QAAQ;AAAA,IACT,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,YAAY,UAAsD;AACjE,SAAK,UAAU,IAAI,QAAQ;AAC3B,WAAO,MAAM;AACZ,WAAK,UAAU,OAAO,QAAQ;AAAA,IAC/B;AAAA,EACD;AAAA;AAAA,EAIQ,kBAAwB;AAC/B,eAAW,YAAY,KAAK,WAAW;AACtC,UAAI;AACH,iBAAS,KAAK,YAAY;AAAA,MAC3B,QAAQ;AAAA,MAER;AAAA,IACD;AAAA,EACD;AAAA,EAEA,MAAc,QACb,MACA,SAIa;AACb,UAAM,QAAQ,MAAM,KAAK,eAAe;AACxC,QAAI,CAAC,OAAO;AACX,YAAM,IAAI;AAAA,QACT;AAAA,QACA;AAAA,MACD;AAAA,IACD;AAEA,UAAM,MAAM,GAAG,KAAK,SAAS,GAAG,IAAI;AACpC,UAAM,UAAkC;AAAA,MACvC,eAAe,UAAU,KAAK;AAAA,IAC/B;AACA,QAAI,QAAQ,MAAM;AACjB,cAAQ,cAAc,IAAI;AAAA,IAC3B;AAEA,QAAI;AACJ,QAAI;AACH,iBAAW,MAAM,MAAM,KAAK;AAAA,QAC3B,QAAQ,QAAQ;AAAA,QAChB;AAAA,QACA,MAAM,QAAQ,OAAO,KAAK,UAAU,QAAQ,IAAI,IAAI;AAAA,MACrD,CAAC;AAAA,IACF,SAAS,OAAO;AACf,YAAM,IAAI,eAAe,sBAAsB,IAAI,YAAY,qBAAqB;AAAA,QACnF;AAAA,QACA,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,MAC7D,CAAC;AAAA,IACF;AAEA,QAAI,CAAC,SAAS,IAAI;AACjB,UAAI,eAAe,wBAAwB,SAAS,MAAM;AAC1D,UAAI;AACH,cAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,YAAI,OAAO,KAAK,UAAU,UAAU;AACnC,yBAAe,KAAK;AAAA,QACrB;AAAA,MACD,QAAQ;AAAA,MAER;AACA,YAAM,IAAI,eAAe,cAAc,oBAAoB;AAAA,QAC1D;AAAA,QACA,QAAQ,SAAS;AAAA,MAClB,CAAC;AAAA,IACF;AAGA,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,QAAI,KAAK,WAAW,EAAG,QAAO;AAE9B,QAAI;AACH,YAAM,OAAO,KAAK,MAAM,IAAI;AAE5B,UAAI,QAAQ,OAAO,SAAS,YAAY,UAAU,MAAM;AACvD,eAAO,KAAK;AAAA,MACb;AACA,aAAO;AAAA,IACR,QAAQ;AACP,aAAO;AAAA,IACR;AAAA,EACD;AACD;;;AC1YA,IAAM,sBAAsB;AAiB5B,IAAM,gBAAN,MAA6C;AAAA,EACpC,QAAQ,oBAAI,IAAoB;AAAA,EAExC,QAAQ,KAA4B;AACnC,WAAO,KAAK,MAAM,IAAI,GAAG,KAAK;AAAA,EAC/B;AAAA,EAEA,QAAQ,KAAa,OAAqB;AACzC,SAAK,MAAM,IAAI,KAAK,KAAK;AAAA,EAC1B;AAAA,EAEA,WAAW,KAAmB;AAC7B,SAAK,MAAM,OAAO,GAAG;AAAA,EACtB;AACD;AAWA,SAAS,qBAA2C;AACnD,MAAI;AACH,QAAI,OAAO,eAAe,eAAe,kBAAkB,YAAY;AACtE,YAAM,UAAU,WAAW;AAE3B,YAAM,UAAU;AAChB,cAAQ,QAAQ,SAAS,GAAG;AAC5B,cAAQ,WAAW,OAAO;AAC1B,aAAO;AAAA,IACR;AAAA,EACD,QAAQ;AAAA,EAER;AACA,SAAO;AACR;AA0BO,IAAM,aAAN,MAAiB;AAAA,EACN;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQjB,YAAY,YAAqB;AAChC,SAAK,aAAa,cAAc;AAChC,SAAK,UAAU,mBAAmB,KAAK,IAAI,cAAc;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,WAAW,QAA0B;AACpC,UAAM,aAAyB;AAAA,MAC9B,aAAa,OAAO;AAAA,MACpB,cAAc,OAAO;AAAA,IACtB;AACA,QAAI,OAAO,qBAAqB,QAAW;AAC1C,iBAAW,mBAAmB,OAAO;AAAA,IACtC;AACA,SAAK,QAAQ,QAAQ,KAAK,YAAY,KAAK,UAAU,UAAU,CAAC;AAAA,EACjE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,aAAgC;AAC/B,UAAM,MAAM,KAAK,QAAQ,QAAQ,KAAK,UAAU;AAChD,QAAI,QAAQ,MAAM;AACjB,aAAO;AAAA,IACR;AAEA,QAAI;AACH,YAAM,SAAkB,KAAK,MAAM,GAAG;AACtC,UAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,MAAM,QAAQ,MAAM,GAAG;AAC3E,eAAO;AAAA,MACR;AACA,YAAM,SAAS;AAGf,UAAI,OAAO,OAAO,gBAAgB,YAAY,OAAO,OAAO,iBAAiB,UAAU;AACtF,eAAO;AAAA,MACR;AAEA,YAAM,SAAqB;AAAA,QAC1B,aAAa,OAAO;AAAA,QACpB,cAAc,OAAO;AAAA,MACtB;AAEA,UAAI,OAAO,OAAO,qBAAqB,UAAU;AAChD,eAAO,mBAAmB,OAAO;AAAA,MAClC;AAEA,aAAO;AAAA,IACR,QAAQ;AAEP,aAAO;AAAA,IACR;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,cAAoB;AACnB,SAAK,QAAQ,WAAW,KAAK,UAAU;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,iBAAgC;AAC/B,UAAM,SAAS,KAAK,WAAW;AAC/B,WAAO,QAAQ,eAAe;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,kBAAiC;AAChC,UAAM,SAAS,KAAK,WAAW;AAC/B,WAAO,QAAQ,gBAAgB;AAAA,EAChC;AACD;;;AC/LA,SAAS,aAAAC,kBAAiB;AAWnB,IAAM,2BAAN,cAAuCC,WAAU;AAAA,EACvD,YAAY,SAAiB,SAAmC;AAC/D,UAAM,SAAS,+BAA+B,OAAO;AACrD,SAAK,OAAO;AAAA,EACb;AACD;AAWA,SAASC,aAAY,OAA2B;AAC/C,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACtC,cAAU,OAAO,aAAa,MAAM,CAAC,CAAW;AAAA,EACjD;AACA,SAAO,KAAK,MAAM,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,EAAE;AAC9E;AAQA,SAASC,eAAc,KAAyB;AAC/C,MAAI,SAAS,IAAI,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG;AACrD,QAAM,iBAAiB,IAAK,OAAO,SAAS,KAAM;AAClD,YAAU,IAAI,OAAO,aAAa;AAElC,QAAM,SAAS,KAAK,MAAM;AAC1B,QAAM,QAAQ,IAAI,WAAW,OAAO,MAAM;AAC1C,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACvC,UAAM,CAAC,IAAI,OAAO,WAAW,CAAC;AAAA,EAC/B;AACA,SAAO;AACR;AAmBA,IAAMC,iBAAN,MAA6C;AAAA,EACpC,QAAQ,oBAAI,IAAoB;AAAA,EAExC,QAAQ,KAA4B;AACnC,WAAO,KAAK,MAAM,IAAI,GAAG,KAAK;AAAA,EAC/B;AAAA,EAEA,QAAQ,KAAa,OAAqB;AACzC,SAAK,MAAM,IAAI,KAAK,KAAK;AAAA,EAC1B;AAAA,EAEA,WAAW,KAAmB;AAC7B,SAAK,MAAM,OAAO,GAAG;AAAA,EACtB;AACD;AAWA,SAASC,sBAA2C;AACnD,MAAI;AACH,QAAI,OAAO,eAAe,eAAe,kBAAkB,YAAY;AACtE,YAAM,UAAU,WAAW;AAC3B,YAAM,UAAU;AAChB,cAAQ,QAAQ,SAAS,GAAG;AAC5B,cAAQ,WAAW,OAAO;AAC1B,aAAO;AAAA,IACR;AAAA,EACD,QAAQ;AAAA,EAER;AACA,SAAO;AACR;AAKA,IAAMC,uBAAsB;AAsErB,IAAM,sBAAN,MAA0B;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOjB,YAAY,QAAmC;AAC9C,SAAK,aAAa,OAAO,cAAcA;AACvC,SAAK,MAAM,OAAO;AAClB,SAAK,UAAUD,oBAAmB,KAAK,IAAID,eAAc;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,MAAM,WAAW,QAAmC;AAEnD,UAAM,aAAyB;AAAA,MAC9B,aAAa,OAAO;AAAA,MACpB,cAAc,OAAO;AAAA,IACtB;AACA,QAAI,OAAO,qBAAqB,QAAW;AAC1C,iBAAW,mBAAmB,OAAO;AAAA,IACtC;AAEA,UAAM,YAAY,IAAI,YAAY,EAAE,OAAO,KAAK,UAAU,UAAU,CAAC;AAErE,QAAI;AACH,YAAM,EAAE,YAAY,GAAG,IAAI,MAAM,YAAY,KAAK,KAAK,SAAS;AAEhE,YAAM,UAA4B;AAAA,QACjC,IAAIF,aAAY,EAAE;AAAA,QAClB,MAAMA,aAAY,UAAU;AAAA,MAC7B;AAEA,WAAK,QAAQ,QAAQ,KAAK,YAAY,KAAK,UAAU,OAAO,CAAC;AAAA,IAC9D,SAAS,OAAO;AAEf,UAAI,iBAAiB,0BAA0B;AAC9C,cAAM;AAAA,MACP;AAEA,YAAM,IAAI;AAAA,QACT;AAAA,QAEA,EAAE,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE;AAAA,MACjE;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBA,MAAM,aAAyC;AAC9C,UAAM,MAAM,KAAK,QAAQ,QAAQ,KAAK,UAAU;AAChD,QAAI,QAAQ,MAAM;AACjB,aAAO;AAAA,IACR;AAEA,QAAI;AAEH,YAAM,SAAkB,KAAK,MAAM,GAAG;AACtC,UAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,MAAM,QAAQ,MAAM,GAAG;AAC3E,eAAO;AAAA,MACR;AAEA,YAAM,SAAS;AACf,UAAI,OAAO,OAAO,OAAO,YAAY,OAAO,OAAO,SAAS,UAAU;AACrE,eAAO;AAAA,MACR;AAGA,YAAM,KAAKC,eAAc,OAAO,EAAE;AAClC,YAAM,aAAaA,eAAc,OAAO,IAAI;AAG5C,YAAM,iBAAiB,MAAM,YAAY,KAAK,KAAK,YAAY,EAAE;AACjE,YAAM,OAAO,IAAI,YAAY,EAAE,OAAO,cAAc;AAGpD,YAAM,YAAqB,KAAK,MAAM,IAAI;AAC1C,UAAI,OAAO,cAAc,YAAY,cAAc,QAAQ,MAAM,QAAQ,SAAS,GAAG;AACpF,eAAO;AAAA,MACR;AAEA,YAAM,cAAc;AACpB,UACC,OAAO,YAAY,gBAAgB,YACnC,OAAO,YAAY,iBAAiB,UACnC;AACD,eAAO;AAAA,MACR;AAEA,YAAM,SAAqB;AAAA,QAC1B,aAAa,YAAY;AAAA,QACzB,cAAc,YAAY;AAAA,MAC3B;AAEA,UAAI,OAAO,YAAY,qBAAqB,UAAU;AACrD,eAAO,mBAAmB,YAAY;AAAA,MACvC;AAEA,aAAO;AAAA,IACR,QAAQ;AAIP,aAAO;AAAA,IACR;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,cAAoB;AACnB,SAAK,QAAQ,WAAW,KAAK,UAAU;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,iBAAyC;AAC9C,UAAM,SAAS,MAAM,KAAK,WAAW;AACrC,WAAO,QAAQ,eAAe;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,kBAA0C;AAC/C,UAAM,SAAS,MAAM,KAAK,WAAW;AACrC,WAAO,QAAQ,gBAAgB;AAAA,EAChC;AACD;;;ACvWA,SAAS,aAAAI,kBAAiB;AAOnB,IAAM,qBAAN,cAAiCA,WAAU;AAAA,EACjD,YAAY,SAAiB,SAAmC;AAC/D,UAAM,SAAS,wBAAwB,OAAO;AAC9C,SAAK,OAAO;AAAA,EACb;AACD;AAKA,IAAM,cAAc;AAMpB,IAAM,oBAAoB;AAG1B,IAAM,qBAAqB;AAK3B,SAAS,wBAA8B;AACtC,MAAI,OAAO,WAAW,WAAW,eAAe,OAAO,WAAW,OAAO,WAAW,aAAa;AAChG,UAAM,IAAI;AAAA,MACT;AAAA,IAED;AAAA,EACD;AACD;AAkBO,SAAS,eAA2B;AAC1C,MAAI,OAAO,WAAW,WAAW,aAAa;AAC7C,UAAM,IAAI;AAAA,MACT;AAAA,IACD;AAAA,EACD;AAEA,SAAO,WAAW,OAAO,gBAAgB,IAAI,WAAW,WAAW,CAAC;AACrE;AA+BA,eAAsB,oBACrB,YACA,MACgD;AAChD,wBAAsB;AAEtB,MAAI,WAAW,WAAW,GAAG;AAC5B,UAAM,IAAI;AAAA,MACT;AAAA,IACD;AAAA,EACD;AAEA,QAAM,WAAW,QAAQ,aAAa;AAEtC,MAAI;AAEH,UAAM,kBAAkB,IAAI,YAAY,EAAE,OAAO,UAAU;AAC3D,UAAM,UAAU,MAAM,WAAW,OAAO,OAAO;AAAA,MAC9C;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,CAAC,cAAc,WAAW;AAAA,IAC3B;AAGA,UAAM,aAAa,MAAM,WAAW,OAAO,OAAO;AAAA,MACjD;AAAA,QACC,MAAM;AAAA,QACN,MAAM;AAAA,QACN,YAAY;AAAA,QACZ,MAAM;AAAA,MACP;AAAA,MACA;AAAA,MACA,EAAE,MAAM,WAAW,QAAQ,mBAAmB;AAAA;AAAA,MAE9C;AAAA,MACA,CAAC,WAAW,SAAS;AAAA,IACtB;AAEA,WAAO,EAAE,KAAK,YAAY,MAAM,SAAS;AAAA,EAC1C,SAAS,OAAO;AAEf,QAAI,iBAAiB,oBAAoB;AACxC,YAAM;AAAA,IACP;AAEA,UAAM,IAAI;AAAA,MACT;AAAA,MAEA,EAAE,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE;AAAA,IACjE;AAAA,EACD;AACD;;;ACvGO,IAAM,kBAAN,MAAsB;AAAA,EACX;AAAA,EACA;AAAA,EACT,WAAiD;AAAA,EACjD,YAAY;AAAA,EACZ,aAAa;AAAA,EAErB,YAAY,QAAwB;AACnC,QAAI,OAAO,WAAW,GAAG;AACxB,YAAM,IAAI;AAAA,QACT,mEAAmE,OAAO,OAAO;AAAA,MAClF;AAAA,IACD;AAEA,SAAK,WAAW,OAAO;AACvB,SAAK,UAAU,OAAO;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,IAAI,WAAoB;AACvB,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,QAAc;AACb,QAAI,KAAK,YAAY;AACpB;AAAA,IACD;AAEA,SAAK,aAAa;AAClB,SAAK,YAAY;AACjB,SAAK,YAAY;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,OAAa;AACZ,SAAK,aAAa;AAClB,SAAK,YAAY;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,iBAAuB;AACtB,QAAI,CAAC,KAAK,cAAc,KAAK,WAAW;AACvC;AAAA,IACD;AAEA,SAAK,YAAY;AACjB,SAAK,YAAY;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,OAAa;AACZ,SAAK,YAAY;AAEjB,QAAI,CAAC,KAAK,WAAW;AACpB,WAAK,YAAY;AACjB,WAAK,QAAQ;AAAA,IACd;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,SAAe;AACd,SAAK,YAAY;AAEjB,QAAI,KAAK,YAAY;AACpB,WAAK,YAAY;AACjB,WAAK,YAAY;AAAA,IAClB;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAoB;AAC3B,SAAK,WAAW,WAAW,MAAM;AAChC,WAAK,WAAW;AAChB,WAAK,YAAY;AACjB,WAAK,QAAQ;AAAA,IACd,GAAG,KAAK,QAAQ;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAoB;AAC3B,QAAI,KAAK,aAAa,MAAM;AAC3B,mBAAa,KAAK,QAAQ;AAC1B,WAAK,WAAW;AAAA,IACjB;AAAA,EACD;AACD;","names":["KoraError","KoraError","KoraError","decodeJwtPayload","KoraError","KoraError","KoraError","toBase64Url","fromBase64Url","MemoryStorage","tryGetLocalStorage","DEFAULT_STORAGE_KEY","KoraError"]}
1
+ {"version":3,"sources":["../src/client/auth-client.ts","../src/client/device-session.ts","../src/device/device-store.ts","../src/client/storage.ts","../src/client/quickstart.ts","../src/client/auth-sync.ts","../src/client/org-client.ts","../src/tokens/token-store.ts","../src/tokens/encrypted-token-store.ts","../src/encryption/key-derivation.ts","../src/encryption/auto-lock.ts"],"sourcesContent":["import { KoraError } from '@korajs/core'\nimport type { AuthDeviceIdentityProvider } from './device-session'\n\n// ---------------------------------------------------------------------------\n// Auth-specific error\n// ---------------------------------------------------------------------------\n\n/**\n * Thrown when an authentication operation fails.\n * Includes a machine-readable code and optional context for debugging.\n */\nexport class AuthError extends KoraError {\n\tconstructor(message: string, code: string, context?: Record<string, unknown>) {\n\t\tsuper(message, code, context)\n\t\tthis.name = 'AuthError'\n\t}\n}\n\n/**\n * Thrown by sign-in when the account requires a second factor (AUTH-10).\n * Complete it with {@link AuthClient.verifyMfa} using {@link mfaToken}.\n */\nexport class MfaRequiredError extends AuthError {\n\tconstructor(public readonly mfaToken: string) {\n\t\tsuper('A second factor is required to finish signing in.', 'AUTH_MFA_REQUIRED')\n\t\tthis.name = 'MfaRequiredError'\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Possible authentication states for the client.\n * - 'loading': Initial state while restoring tokens from storage\n * - 'authenticated': A session exists. It may be fresh or offline; see\n * {@link AuthClient.session} for the freshness of its credentials.\n * - 'unauthenticated': No session exists (never signed in, signed out, or the\n * auth server definitively rejected the session)\n */\nexport type AuthState = 'loading' | 'authenticated' | 'unauthenticated'\n\n/**\n * Freshness of an authenticated session.\n * - 'fresh': the last refresh or profile request reached the auth server\n * - 'offline': authenticated-offline. The identity is known from stored\n * credentials, but no fresh access token can be minted right now (network,\n * timeout, 5xx, captive portal...). Local data stays available; sync waits.\n * - 'locked': offline for longer than `maxOfflineGraceMs`, or the device clock\n * moved backwards. The UI should lock; local data is never wiped.\n */\nexport type AuthSessionStatus = 'fresh' | 'offline' | 'locked'\n\n/**\n * The identity of the stored session, independent of token freshness.\n */\nexport interface AuthClientSession {\n\t/** User id (`sub` of the stored credentials). */\n\tuserId: string\n\t/** Device id (`dev` of the stored credentials), when known. */\n\tdeviceId: string | null\n\t/** Freshness of the credentials. */\n\tstatus: AuthSessionStatus\n\t/** Last successful contact with the auth server (ms since epoch). */\n\tlastServerContactAt: number\n}\n\n/**\n * Authenticated user information.\n */\nexport interface AuthUser {\n\t/** Unique user identifier */\n\tid: string\n\n\t/** User email address */\n\temail: string\n\n\t/** Display name (may be absent if user did not provide one) */\n\tname: string | null\n}\n\nexport interface LinkedOAuthAccount {\n\tid: string\n\tuserId: string\n\tprovider: string\n\tproviderUserId: string\n\temail: string | null\n\tlinkedAt: number\n}\n\nexport interface OAuthAuthorizationResult {\n\turl: string\n\tstate: string\n\t/**\n\t * Client binding for this flow (AUTH-3). The client keeps it (session storage\n\t * on the web, memory on native) and presents it with the callback; a callback\n\t * carrying someone else's code and state is then refused.\n\t */\n\tbinding?: string\n}\n\nexport interface OAuthAuthorizationOptions {\n\t/**\n\t * Redirect the current browser window to the provider after creating the URL.\n\t * Defaults to true when `window.location.assign` is available.\n\t */\n\tredirect?: boolean\n\t/**\n\t * Optional app-specific return path stored in OAuth state metadata.\n\t */\n\treturnTo?: string\n\t/**\n\t * Optional extra metadata stored in OAuth state. Use this for app handoff data.\n\t */\n\tmetadata?: Record<string, unknown>\n\tdeviceId?: string\n\tdevicePublicKey?: string\n}\n\nexport interface OAuthCallbackParams {\n\tcode: string\n\tstate: string\n\t/** Flow binding; looked up from the started flow when omitted. */\n\tbinding?: string\n\tdeviceId?: string\n\tdevicePublicKey?: string\n}\n\n/**\n * Configuration for the AuthClient.\n */\nexport interface AuthClientConfig {\n\t/** Base URL of the auth server (e.g. 'http://localhost:3001') */\n\tserverUrl: string\n\n\t/** Storage key prefix for tokens. Defaults to 'kora_auth' */\n\tstorageKey?: string\n\n\t/**\n\t * Optional token storage adapter.\n\t *\n\t * Use this for runtimes where localStorage is not the right place for\n\t * credentials, such as React Native/Expo SecureStore, iOS Keychain,\n\t * Android Keystore, or a Tauri secure storage plugin.\n\t */\n\tstorage?: AuthTokenStorage\n\n\t/**\n\t * Optional fetch implementation. Defaults to globalThis.fetch.\n\t * Useful for tests, SSR adapters, and mobile runtimes with a custom fetch.\n\t */\n\tfetch?: typeof fetch\n\n\t/**\n\t * Optional local device identity provider.\n\t *\n\t * When configured, sign-up and sign-in automatically include stable\n\t * `deviceId` and `devicePublicKey` fields unless the caller provides them.\n\t */\n\tdeviceIdentity?: AuthDeviceIdentityProvider\n\n\t/**\n\t * Timeout for every auth request, in milliseconds. A request that has not\n\t * answered by then is aborted and treated as a transient failure.\n\t * @default 20000\n\t */\n\trequestTimeoutMs?: number\n\n\t/**\n\t * How long a session may stay authenticated-offline (no successful contact\n\t * with the auth server) before it is `locked`. Locking never wipes local data\n\t * or tokens; it only tells the UI to ask the user to reconnect.\n\t * Defaults to no limit beyond the refresh token's own expiry.\n\t */\n\tmaxOfflineGraceMs?: number\n\n\t/**\n\t * Backoff between refresh attempts after transient failures. The first retry\n\t * after a failure is immediate (it recovers a response lost on the wire);\n\t * later ones back off exponentially with jitter, honouring `Retry-After`.\n\t */\n\trefreshBackoff?: { baseDelayMs?: number; maxDelayMs?: number }\n}\n\ntype MaybePromise<T> = T | Promise<T>\n\n/**\n * Token pair returned by the auth server on sign-up, sign-in, and refresh.\n */\ninterface AuthTokensResponse {\n\taccessToken: string\n\trefreshToken: string\n}\n\n/**\n * Sign-up and sign-in responses include user data alongside tokens.\n */\ninterface AuthSignInResponse {\n\tuser: { id: string; email: string; name: string | null }\n\ttokens: AuthTokensResponse\n}\n\ninterface OAuthSignInResponse extends AuthSignInResponse {\n\tidentity: LinkedOAuthAccount\n}\n\ninterface MfaChallengeResponse {\n\tmfaRequired: true\n\tmfaToken: string\n}\n\n/**\n * User profile returned by the /auth/me endpoint.\n */\ninterface UserProfileResponse {\n\tid: string\n\temail: string\n\tname: string | null\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** Number of seconds before actual expiry at which we consider a token expired. */\nconst EXPIRY_BUFFER_SECONDS = 30\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 20_000\nconst DEFAULT_BACKOFF_BASE_MS = 2_000\nconst DEFAULT_BACKOFF_MAX_MS = 5 * 60_000\n\n/**\n * Tolerated backwards clock movement before a session is treated as tampered.\n * Matches the server's own skew allowance order of magnitude.\n */\nconst CLOCK_ROLLBACK_TOLERANCE_MS = 5 * 60_000\n\n/**\n * Decode the payload portion of a JWT without verifying the signature.\n * Client-side only -- verification is the server's responsibility.\n *\n * Returns null if the token is malformed.\n */\nfunction decodeJwtPayload(token: string): Record<string, unknown> | null {\n\tconst parts = token.split('.')\n\tif (parts.length !== 3) {\n\t\treturn null\n\t}\n\n\ttry {\n\t\t// Base64url -> standard base64\n\t\tconst base64 = (parts[1] as string).replace(/-/g, '+').replace(/_/g, '/')\n\t\tconst json = atob(base64)\n\t\tconst parsed: unknown = JSON.parse(json)\n\t\treturn isRecord(parsed) ? parsed : null\n\t} catch {\n\t\treturn null\n\t}\n}\n\n/**\n * Returns true if the JWT's `exp` claim is in the past (with a small buffer).\n * If the token cannot be decoded, returns true (treat as expired).\n */\nfunction isTokenExpired(token: string, bufferSeconds = EXPIRY_BUFFER_SECONDS): boolean {\n\tconst payload = decodeJwtPayload(token)\n\tif (!payload || typeof payload.exp !== 'number') {\n\t\treturn true\n\t}\n\tconst nowSeconds = Math.floor(Date.now() / 1000)\n\treturn payload.exp <= nowSeconds + bufferSeconds\n}\n\n/** Issue time of a token in ms, from `iatMs` or `iat`. */\nfunction tokenIssuedAtMs(token: string): number | null {\n\tconst payload = decodeJwtPayload(token)\n\tif (!payload) return null\n\tif (typeof payload.iatMs === 'number') return payload.iatMs\n\treturn typeof payload.iat === 'number' ? payload.iat * 1000 : null\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction getDefaultFetch(): typeof fetch {\n\tif (typeof globalThis.fetch !== 'function') {\n\t\treturn async () => {\n\t\t\tthrow new AuthError(\n\t\t\t\t'No fetch implementation is available in this runtime. Pass `fetch` to AuthClientConfig.',\n\t\t\t\t'AUTH_FETCH_UNAVAILABLE',\n\t\t\t)\n\t\t}\n\t}\n\treturn globalThis.fetch.bind(globalThis)\n}\n\nfunction normalizeAuthUser(user: { id: string; email: string; name?: string | null }): AuthUser {\n\treturn {\n\t\tid: user.id,\n\t\temail: user.email,\n\t\tname: user.name ?? null,\n\t}\n}\n\nfunction canRedirectCurrentWindow(): boolean {\n\treturn (\n\t\ttypeof globalThis.window !== 'undefined' &&\n\t\ttypeof globalThis.window.location?.assign === 'function'\n\t)\n}\n\nfunction redirectCurrentWindow(url: string): void {\n\tif (!canRedirectCurrentWindow()) {\n\t\tthrow new AuthError(\n\t\t\t'OAuth redirect is not available in this runtime. Pass redirect: false and open the returned URL with your platform browser API.',\n\t\t\t'AUTH_OAUTH_REDIRECT_UNAVAILABLE',\n\t\t)\n\t}\n\tglobalThis.window.location.assign(url)\n}\n\nconst OAUTH_BINDING_PREFIX = 'kora_oauth_binding:'\n\nfunction getSessionStorage(): Pick<Storage, 'getItem' | 'setItem' | 'removeItem'> | null {\n\ttry {\n\t\tconst storage = (globalThis as { sessionStorage?: Storage }).sessionStorage\n\t\treturn storage && typeof storage.getItem === 'function' ? storage : null\n\t} catch {\n\t\treturn null\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Refresh outcome classification (AUTH-13, LMS-1)\n// ---------------------------------------------------------------------------\n\n/**\n * Outcome of one refresh attempt. Only `rejected` ends a session, and only when\n * the auth server itself said so.\n */\ntype RefreshOutcome =\n\t| { kind: 'ok'; accessToken: string }\n\t| { kind: 'rejected' }\n\t| { kind: 'transient'; retryAfterMs?: number }\n\n/** Body codes the Kora auth server uses to reject a refresh token. */\nconst DEFINITIVE_REFRESH_CODES = new Set(['REFRESH_TOKEN_INVALID', 'invalid_grant'])\n\ninterface RawResponse {\n\tstatus: number\n\tok: boolean\n\t/** Parsed JSON body, or undefined when the body is not JSON (proxy/captive portal). */\n\tjson: unknown\n\tretryAfterMs?: number\n}\n\n/**\n * A response is a definitive rejection only when it is a 401 (or a 400\n * `invalid_grant`) whose body is a Kora JSON error. Captive portals, proxies and\n * load balancers also answer 401/403/407 or HTML, and must never sign a user out.\n */\nfunction isDefinitiveRejection(response: RawResponse): boolean {\n\tif (!isRecord(response.json)) return false\n\tconst code = typeof response.json.code === 'string' ? response.json.code : undefined\n\tconst error = typeof response.json.error === 'string' ? response.json.error : undefined\n\tif (response.status === 401) return code !== undefined || error !== undefined\n\tif (response.status === 400) {\n\t\treturn (\n\t\t\t(code !== undefined && DEFINITIVE_REFRESH_CODES.has(code)) ||\n\t\t\t(error !== undefined && DEFINITIVE_REFRESH_CODES.has(error))\n\t\t)\n\t}\n\treturn false\n}\n\nfunction parseRetryAfter(value: string | null | undefined): number | undefined {\n\tif (!value) return undefined\n\tconst seconds = Number(value)\n\tif (Number.isFinite(seconds) && seconds >= 0) return seconds * 1000\n\tconst date = Date.parse(value)\n\treturn Number.isNaN(date) ? undefined : Math.max(0, date - Date.now())\n}\n\nfunction readTokenPair(json: unknown): AuthTokensResponse | null {\n\tif (!isRecord(json)) return null\n\tconst data = json.data !== undefined ? json.data : json\n\tif (!isRecord(data)) return null\n\treturn typeof data.accessToken === 'string' && typeof data.refreshToken === 'string'\n\t\t? { accessToken: data.accessToken, refreshToken: data.refreshToken }\n\t\t: null\n}\n\n// ---------------------------------------------------------------------------\n// Single refresher across tabs (NEW-AUTH-2)\n// ---------------------------------------------------------------------------\n\ninterface WebLockManager {\n\trequest<T>(\n\t\tname: string,\n\t\toptions: { signal?: AbortSignal },\n\t\tcallback: () => Promise<T>,\n\t): Promise<T>\n}\n\n/** In-realm fallback when Web Locks are unavailable: one chain per token storage. */\nconst inProcessLocks = new WeakMap<object, Promise<unknown>>()\n\nfunction getWebLocks(): WebLockManager | null {\n\tconst nav = (globalThis as { navigator?: { locks?: unknown } }).navigator\n\tconst locks = nav?.locks\n\tif (typeof locks !== 'object' || locks === null) return null\n\treturn typeof (locks as { request?: unknown }).request === 'function'\n\t\t? (locks as WebLockManager)\n\t\t: null\n}\n\n// ---------------------------------------------------------------------------\n// Simple token storage backed by localStorage (browser) or in-memory fallback\n// ---------------------------------------------------------------------------\n\nexport interface AuthTokenStorage {\n\tgetAccessToken(): MaybePromise<string | null>\n\tgetRefreshToken(): MaybePromise<string | null>\n\tsetTokens(access: string, refresh: string): MaybePromise<void>\n\tclear(): MaybePromise<void>\n}\n\nfunction createTokenStorage(prefix: string): AuthTokenStorage {\n\t// Try localStorage; fall back to in-memory if unavailable (SSR, Web Worker, etc.)\n\tlet useLocalStorage = false\n\ttry {\n\t\tif (typeof window !== 'undefined' && typeof window.localStorage !== 'undefined') {\n\t\t\t// Smoke test: ensure we can actually write\n\t\t\tconst testKey = `${prefix}_test`\n\t\t\twindow.localStorage.setItem(testKey, '1')\n\t\t\twindow.localStorage.removeItem(testKey)\n\t\t\tuseLocalStorage = true\n\t\t}\n\t} catch {\n\t\t// localStorage not available (e.g., Safari private browsing throws in some contexts)\n\t}\n\n\tif (useLocalStorage) {\n\t\tconst accessKey = `${prefix}_access_token`\n\t\tconst refreshKey = `${prefix}_refresh_token`\n\t\treturn {\n\t\t\tgetAccessToken(): string | null {\n\t\t\t\treturn window.localStorage.getItem(accessKey)\n\t\t\t},\n\t\t\tgetRefreshToken(): string | null {\n\t\t\t\treturn window.localStorage.getItem(refreshKey)\n\t\t\t},\n\t\t\tsetTokens(access: string, refresh: string): void {\n\t\t\t\twindow.localStorage.setItem(accessKey, access)\n\t\t\t\twindow.localStorage.setItem(refreshKey, refresh)\n\t\t\t},\n\t\t\tclear(): void {\n\t\t\t\twindow.localStorage.removeItem(accessKey)\n\t\t\t\twindow.localStorage.removeItem(refreshKey)\n\t\t\t},\n\t\t}\n\t}\n\n\t// In-memory fallback\n\tlet accessToken: string | null = null\n\tlet refreshToken: string | null = null\n\treturn {\n\t\tgetAccessToken(): string | null {\n\t\t\treturn accessToken\n\t\t},\n\t\tgetRefreshToken(): string | null {\n\t\t\treturn refreshToken\n\t\t},\n\t\tsetTokens(access: string, refresh: string): void {\n\t\t\taccessToken = access\n\t\t\trefreshToken = refresh\n\t\t},\n\t\tclear(): void {\n\t\t\taccessToken = null\n\t\t\trefreshToken = null\n\t\t},\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// AuthClient\n// ---------------------------------------------------------------------------\n\n/**\n * Client-side authentication manager for Kora.js.\n *\n * Manages token storage, session restoration, sign-up, sign-in, sign-out,\n * token refresh, and auth state change notifications. Framework-agnostic --\n * works in any JavaScript environment with `fetch` and optionally `localStorage`.\n *\n * Offline-first session rules (AUTH-13):\n * - Only the auth server ends a session: tokens are cleared only on a 401 (or a\n * 400 `invalid_grant`) carrying a Kora JSON error, on an explicit sign-out, or\n * when the refresh token itself has expired.\n * - Every other failure (no network, timeout, abort, 5xx, 429, 511, HTML from a\n * captive portal) keeps the tokens, keeps the user signed in as\n * authenticated-offline and retries with jittered backoff.\n * - One tab refreshes at a time (Web Locks); the others adopt its result.\n *\n * @example\n * ```typescript\n * const auth = new AuthClient({ serverUrl: 'http://localhost:3001' })\n * await auth.initialize()\n *\n * if (!auth.isAuthenticated) {\n * await auth.signIn({ email: 'user@example.com', password: 'secret' })\n * }\n *\n * const unsub = auth.onAuthChange((state) => {\n * console.log('Auth state:', state)\n * })\n * ```\n */\nexport class AuthClient {\n\tprivate readonly serverUrl: string\n\tprivate readonly storage: AuthTokenStorage\n\tprivate readonly fetchFn: typeof fetch\n\tprivate readonly deviceIdentity: AuthDeviceIdentityProvider | undefined\n\tprivate readonly listeners: Set<(state: AuthState) => void> = new Set()\n\tprivate readonly sessionListeners: Set<(session: AuthClientSession | null) => void> = new Set()\n\tprivate readonly requestTimeoutMs: number\n\tprivate readonly maxOfflineGraceMs: number | undefined\n\tprivate readonly backoffBaseMs: number\n\tprivate readonly backoffMaxMs: number\n\tprivate readonly lockName: string\n\n\tprivate _state: AuthState = 'loading'\n\tprivate _user: AuthUser | null = null\n\tprivate _refreshPromise: Promise<RefreshOutcome> | null = null\n\tprivate _initialized = false\n\n\tprivate sessionStatus: AuthSessionStatus = 'fresh'\n\tprivate lastServerContactAt = 0\n\tprivate failureCount = 0\n\tprivate nextAttemptAt = 0\n\tprivate retryTimer: ReturnType<typeof setTimeout> | null = null\n\tprivate readonly detachEnvironment: () => void\n\n\t/**\n\t * Creates a new AuthClient.\n\t *\n\t * @param config - Auth client configuration\n\t */\n\tconstructor(config: AuthClientConfig) {\n\t\t// Strip trailing slash to normalize URLs\n\t\tthis.serverUrl = config.serverUrl.replace(/\\/+$/, '')\n\t\tconst prefix = config.storageKey ?? 'kora_auth'\n\t\tthis.storage = config.storage ?? createTokenStorage(prefix)\n\t\tthis.fetchFn = config.fetch ?? getDefaultFetch()\n\t\tthis.deviceIdentity = config.deviceIdentity\n\t\tthis.requestTimeoutMs = config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS\n\t\tthis.maxOfflineGraceMs = config.maxOfflineGraceMs\n\t\tthis.backoffBaseMs = config.refreshBackoff?.baseDelayMs ?? DEFAULT_BACKOFF_BASE_MS\n\t\tthis.backoffMaxMs = config.refreshBackoff?.maxDelayMs ?? DEFAULT_BACKOFF_MAX_MS\n\t\tthis.lockName = `kora-auth-refresh:${prefix}`\n\t\tthis.detachEnvironment = this.attachEnvironmentListeners()\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Public getters\n\t// -----------------------------------------------------------------------\n\n\t/** Current authentication state. */\n\tget state(): AuthState {\n\t\treturn this._state\n\t}\n\n\t/** Current authenticated user, or null if not signed in. */\n\tget currentUser(): AuthUser | null {\n\t\treturn this._user\n\t}\n\n\t/** Whether the user is currently authenticated (fresh or offline). */\n\tget isAuthenticated(): boolean {\n\t\treturn this._state === 'authenticated'\n\t}\n\n\t/**\n\t * The stored session identity and its freshness, or null when signed out.\n\t * Unlike {@link getAccessToken}, this is available offline: the identity is\n\t * decoupled from whether a fresh access token can be minted right now.\n\t */\n\tget session(): AuthClientSession | null {\n\t\tif (this._state !== 'authenticated' || !this._user) return null\n\t\treturn {\n\t\t\tuserId: this._user.id,\n\t\t\tdeviceId: this.cachedDeviceId,\n\t\t\tstatus: this.sessionStatus,\n\t\t\tlastServerContactAt: this.lastServerContactAt,\n\t\t}\n\t}\n\n\tprivate cachedDeviceId: string | null = null\n\n\t/**\n\t * Read the stored session identity (user id and device id) from storage,\n\t * without any network request. Returns null when no usable session is stored.\n\t */\n\tasync getStoredIdentity(): Promise<{ userId: string; deviceId: string | null } | null> {\n\t\tconst claims = await this.getStoredClaims()\n\t\tif (!claims || typeof claims.sub !== 'string' || claims.sub.length === 0) return null\n\t\treturn {\n\t\t\tuserId: claims.sub,\n\t\t\tdeviceId: typeof claims.dev === 'string' && claims.dev.length > 0 ? claims.dev : null,\n\t\t}\n\t}\n\n\t/**\n\t * Decoded (unverified) claims of the stored credentials, preferring the access\n\t * token even when it has expired. For client-side hints only (local database\n\t * name, sync node id, handshake scope narrowing); the server re-derives\n\t * everything it authorizes from a verified token.\n\t */\n\tasync getStoredClaims(): Promise<Record<string, unknown> | null> {\n\t\tconst access = await this.storage.getAccessToken()\n\t\tconst refresh = await this.storage.getRefreshToken()\n\t\tif (!refresh) return null\n\t\tfor (const token of [access, refresh]) {\n\t\t\tif (!token) continue\n\t\t\tconst claims = decodeJwtPayload(token)\n\t\t\tif (claims && typeof claims.sub === 'string' && claims.sub.length > 0) return claims\n\t\t}\n\t\treturn null\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Initialization\n\t// -----------------------------------------------------------------------\n\n\t/**\n\t * Initialize the auth client by restoring a session from stored tokens.\n\t *\n\t * Loads tokens from storage, validates the access token, and attempts a\n\t * refresh if the access token is expired but a refresh token is available.\n\t * When the auth server cannot be reached, the stored session is restored as\n\t * authenticated-offline instead of being discarded.\n\t * Safe to call multiple times -- subsequent calls are no-ops once initialized.\n\t */\n\tasync initialize(): Promise<void> {\n\t\t// Guard against double initialization (e.g., React StrictMode double-mount)\n\t\tif (this._initialized) {\n\t\t\treturn\n\t\t}\n\t\tthis._initialized = true\n\n\t\tconst accessToken = await this.storage.getAccessToken()\n\t\tconst refreshToken = await this.storage.getRefreshToken()\n\n\t\t// No stored tokens -- stay unauthenticated\n\t\tif (!accessToken || !refreshToken) {\n\t\t\tthis.setState('unauthenticated', null)\n\t\t\treturn\n\t\t}\n\t\tthis.noteIssuedCredential(refreshToken)\n\n\t\t// Access token still valid -- restore session from it\n\t\tif (!isTokenExpired(accessToken)) {\n\t\t\tawait this.restoreSession(accessToken)\n\t\t\treturn\n\t\t}\n\n\t\t// Access token expired -- try refreshing\n\t\tconst outcome = await this.refresh()\n\t\tif (outcome.kind === 'ok') {\n\t\t\tawait this.restoreSession(outcome.accessToken)\n\t\t\treturn\n\t\t}\n\t\tif (outcome.kind === 'rejected') {\n\t\t\t// refresh() already cleared tokens and moved to unauthenticated.\n\t\t\tthis.setState('unauthenticated', null)\n\t\t\treturn\n\t\t}\n\t\tawait this.enterOfflineSession()\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Sign up / Sign in / Sign out\n\t// -----------------------------------------------------------------------\n\n\t/**\n\t * Register a new user account.\n\t *\n\t * @param params - Sign-up credentials\n\t * @returns The newly created AuthUser\n\t * @throws {AuthError} If the request fails or the server returns an error\n\t */\n\tasync signUp(params: {\n\t\temail: string\n\t\tpassword: string\n\t\tname?: string\n\t\tdeviceId?: string\n\t\tdevicePublicKey?: string\n\t}): Promise<AuthUser> {\n\t\tconst body = await this.withDeviceIdentity(params)\n\t\tconst response = await this.request<AuthSignInResponse | AuthTokensResponse>('/auth/signup', {\n\t\t\tmethod: 'POST',\n\t\t\tbody,\n\t\t})\n\t\treturn this.completeSignIn(response)\n\t}\n\n\t/**\n\t * Sign in with email and password.\n\t *\n\t * @param params - Sign-in credentials\n\t * @returns The authenticated AuthUser\n\t * @throws {AuthError} If the credentials are invalid or the request fails\n\t */\n\tasync signIn(params: {\n\t\temail: string\n\t\tpassword: string\n\t\tdeviceId?: string\n\t\tdevicePublicKey?: string\n\t}): Promise<AuthUser> {\n\t\tconst body = await this.withDeviceIdentity(params)\n\t\tconst response = await this.request<\n\t\t\tAuthSignInResponse | AuthTokensResponse | MfaChallengeResponse\n\t\t>('/auth/signin', {\n\t\t\tmethod: 'POST',\n\t\t\tbody,\n\t\t})\n\t\treturn this.completeSignIn(response)\n\t}\n\n\t/**\n\t * Finish a sign-in that required a second factor.\n\t *\n\t * @param mfaToken - From the {@link MfaRequiredError} thrown by sign-in\n\t * @param proof - A current TOTP code, or a recovery code\n\t * @returns The authenticated AuthUser\n\t * @throws {AuthError} If the code or the MFA session is invalid\n\t */\n\tasync verifyMfa(\n\t\tmfaToken: string,\n\t\tproof: { code: string } | { recoveryCode: string },\n\t): Promise<AuthUser> {\n\t\tconst response = await this.request<AuthSignInResponse>('/auth/mfa/verify', {\n\t\t\tmethod: 'POST',\n\t\t\tbody: { mfaToken, ...proof },\n\t\t})\n\t\treturn this.completeSignIn(response)\n\t}\n\n\t/**\n\t * Create an OAuth authorization URL and optionally redirect the current window.\n\t *\n\t * For web apps, call this from a button click and keep the default redirect behavior.\n\t * For desktop/mobile, pass `redirect: false`, open the returned URL with the runtime's\n\t * browser API, then call `completeOAuthSignIn()` after receiving the callback.\n\t */\n\tasync signInWithOAuth(\n\t\tprovider: string,\n\t\toptions: OAuthAuthorizationOptions = {},\n\t): Promise<OAuthAuthorizationResult> {\n\t\tconst result = await this.createOAuthAuthorization(provider, options)\n\t\tif (options.redirect ?? canRedirectCurrentWindow()) {\n\t\t\tredirectCurrentWindow(result.url)\n\t\t}\n\t\treturn result\n\t}\n\n\t/**\n\t * Complete an OAuth sign-in callback and store the issued Kora tokens.\n\t */\n\tasync completeOAuthSignIn(provider: string, params: OAuthCallbackParams): Promise<AuthUser> {\n\t\tconst body = await this.withDeviceIdentity(params)\n\t\tconst binding = params.binding ?? this.takeOAuthBinding(params.state)\n\t\tconst response = await this.request<OAuthSignInResponse | MfaChallengeResponse>(\n\t\t\t`/auth/oauth/${encodeURIComponent(provider)}/callback`,\n\t\t\t{\n\t\t\t\tmethod: 'POST',\n\t\t\t\tbody: { ...body, ...(binding ? { binding } : {}) },\n\t\t\t},\n\t\t)\n\t\tif ('mfaRequired' in response) {\n\t\t\tthrow new MfaRequiredError(response.mfaToken)\n\t\t}\n\n\t\tawait this.storage.setTokens(response.tokens.accessToken, response.tokens.refreshToken)\n\t\tthis.markFresh(response.tokens.refreshToken)\n\t\tconst user = normalizeAuthUser(response.user)\n\t\tthis.setState('authenticated', user)\n\t\treturn user\n\t}\n\n\t/**\n\t * Create an OAuth authorization URL for linking another provider to the current user.\n\t */\n\tasync getOAuthAuthorizationUrl(\n\t\tprovider: string,\n\t\t_options: OAuthAuthorizationOptions = {},\n\t): Promise<OAuthAuthorizationResult> {\n\t\t// Linking starts from an authenticated endpoint so the state is bound to\n\t\t// this user and can never be redeemed by anyone else (AUTH-3).\n\t\tconst token = await this.requireAccessToken()\n\t\tconst result = await this.request<OAuthAuthorizationResult>(\n\t\t\t`/auth/oauth/${encodeURIComponent(provider)}/link/start`,\n\t\t\t{ method: 'POST', body: {}, token },\n\t\t)\n\t\tthis.rememberOAuthBinding(result)\n\t\treturn result\n\t}\n\n\t/**\n\t * Link an OAuth provider to the current authenticated user.\n\t */\n\tasync linkOAuth(provider: string, params: OAuthCallbackParams): Promise<LinkedOAuthAccount> {\n\t\tconst token = await this.requireAccessToken()\n\t\tconst binding = params.binding ?? this.takeOAuthBinding(params.state)\n\t\treturn this.request<LinkedOAuthAccount>(`/auth/oauth/${encodeURIComponent(provider)}/link`, {\n\t\t\tmethod: 'POST',\n\t\t\tbody: {\n\t\t\t\tcode: params.code,\n\t\t\t\tstate: params.state,\n\t\t\t\t...(binding ? { binding } : {}),\n\t\t\t},\n\t\t\ttoken,\n\t\t})\n\t}\n\n\t/**\n\t * List OAuth accounts linked to the current authenticated user.\n\t */\n\tasync listLinkedAccounts(): Promise<LinkedOAuthAccount[]> {\n\t\tconst token = await this.requireAccessToken()\n\t\treturn this.request<LinkedOAuthAccount[]>('/auth/oauth/links', {\n\t\t\tmethod: 'GET',\n\t\t\ttoken,\n\t\t})\n\t}\n\n\t/**\n\t * Unlink an OAuth provider from the current authenticated user.\n\t */\n\tasync unlinkOAuth(provider: string): Promise<void> {\n\t\tconst token = await this.requireAccessToken()\n\t\tawait this.request<{ ok: true }>(`/auth/oauth/${encodeURIComponent(provider)}/link`, {\n\t\t\tmethod: 'DELETE',\n\t\t\ttoken,\n\t\t})\n\t}\n\n\t/**\n\t * Sign out the current user.\n\t *\n\t * Clears local tokens and attempts to revoke the refresh token on the server\n\t * (best-effort — succeeds even if the server is unreachable). This ensures that\n\t * stolen refresh tokens cannot be used after the user explicitly signs out.\n\t */\n\tasync signOut(): Promise<void> {\n\t\tconst accessToken = await this.storage.getAccessToken()\n\t\tconst refreshToken = await this.storage.getRefreshToken()\n\n\t\t// Clear local state immediately (don't wait for server)\n\t\tawait this.storage.clear()\n\t\tthis._refreshPromise = null\n\t\tthis.resetBackoff()\n\t\tthis.sessionStatus = 'fresh'\n\t\tthis.cachedDeviceId = null\n\t\tthis.setState('unauthenticated', null)\n\n\t\t// Best-effort server-side revocation\n\t\tif (accessToken) {\n\t\t\ttry {\n\t\t\t\tawait this.request('/auth/signout', {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\tbody: { refreshToken: refreshToken ?? undefined },\n\t\t\t\t\ttoken: accessToken,\n\t\t\t\t})\n\t\t\t} catch {\n\t\t\t\t// Server may be unreachable (offline) — local sign-out still succeeds\n\t\t\t}\n\t\t}\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Token access\n\t// -----------------------------------------------------------------------\n\n\t/**\n\t * Get a valid access token, automatically refreshing if expired.\n\t *\n\t * Returns null when no fresh token can be obtained right now. That does NOT\n\t * mean the user is signed out: check {@link state} / {@link session}. A\n\t * transient failure keeps the session (authenticated-offline) and later calls\n\t * retry with backoff.\n\t *\n\t * @returns A valid access token string, or null if none is available now\n\t */\n\tasync getAccessToken(): Promise<string | null> {\n\t\tconst accessToken = await this.storage.getAccessToken()\n\n\t\tif (accessToken && !isTokenExpired(accessToken)) {\n\t\t\tif (this._state === 'authenticated' && this.sessionStatus !== 'fresh') {\n\t\t\t\t// Another tab refreshed while this one was offline.\n\t\t\t\tthis.markFresh(await this.storage.getRefreshToken())\n\t\t\t}\n\t\t\treturn accessToken\n\t\t}\n\n\t\tconst refreshToken = await this.storage.getRefreshToken()\n\t\tif (!refreshToken) {\n\t\t\treturn null\n\t\t}\n\n\t\tconst outcome = await this.refresh()\n\t\treturn outcome.kind === 'ok' ? outcome.accessToken : null\n\t}\n\n\t/**\n\t * Refresh the session now, even when the cached access token has not expired\n\t * locally. Used after the sync server ended a session with `AUTH_EXPIRED` or\n\t * `AUTH_REVOKED`: the server's clock or a revocation says the cached token is\n\t * no longer good. Concurrent calls (and other tabs) share one refresh.\n\t *\n\t * A transient failure keeps the session (authenticated-offline) and returns\n\t * null; only a definitive server rejection signs the user out.\n\t *\n\t * @returns The refreshed access token, or null if none could be obtained now\n\t */\n\tasync refreshAccessToken(): Promise<string | null> {\n\t\tconst refreshToken = await this.storage.getRefreshToken()\n\t\tif (!refreshToken) {\n\t\t\treturn null\n\t\t}\n\t\tconst outcome = await this.refresh()\n\t\treturn outcome.kind === 'ok' ? outcome.accessToken : null\n\t}\n\n\t/**\n\t * Get a valid token for the sync engine handshake.\n\t * Alias for {@link getAccessToken}.\n\t *\n\t * @returns A valid access token string, or null if unavailable\n\t */\n\tasync getSyncToken(): Promise<string | null> {\n\t\treturn this.getAccessToken()\n\t}\n\n\t/**\n\t * Retry immediately: clears the refresh backoff and attempts a refresh if the\n\t * session is offline. Call it when connectivity is known to be back (for\n\t * example when a sync transport opens). Also wired to the browser `online`\n\t * and `visibilitychange` events automatically.\n\t */\n\tasync retryNow(): Promise<void> {\n\t\tthis.resetBackoff()\n\t\tif (this._state === 'authenticated' && this.sessionStatus !== 'fresh') {\n\t\t\tawait this.getAccessToken()\n\t\t}\n\t}\n\n\t/** Remove environment listeners and timers (for tests and teardown). */\n\tdestroy(): void {\n\t\tthis.detachEnvironment()\n\t\tthis.clearRetryTimer()\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// State change subscriptions\n\t// -----------------------------------------------------------------------\n\n\t/**\n\t * Subscribe to authentication state changes.\n\t *\n\t * The callback is invoked whenever the auth state transitions (e.g., from\n\t * 'unauthenticated' to 'authenticated' on sign-in).\n\t *\n\t * @param callback - Function called with the new AuthState on each change\n\t * @returns An unsubscribe function that removes the listener\n\t *\n\t * @example\n\t * ```typescript\n\t * const unsub = auth.onAuthChange((state) => {\n\t * console.log('Auth state changed to:', state)\n\t * })\n\t * // Later: unsub()\n\t * ```\n\t */\n\tonAuthChange(callback: (state: AuthState) => void): () => void {\n\t\tthis.listeners.add(callback)\n\t\treturn () => {\n\t\t\tthis.listeners.delete(callback)\n\t\t}\n\t}\n\n\t/**\n\t * Subscribe to session freshness changes (fresh, authenticated-offline,\n\t * locked). Fires in addition to {@link onAuthChange}, including when the\n\t * state stays 'authenticated' but connectivity to the auth server changes.\n\t *\n\t * @param callback - Called with the current session (null when signed out)\n\t * @returns An unsubscribe function\n\t */\n\tonSessionChange(callback: (session: AuthClientSession | null) => void): () => void {\n\t\tthis.sessionListeners.add(callback)\n\t\treturn () => {\n\t\t\tthis.sessionListeners.delete(callback)\n\t\t}\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Internal helpers\n\t// -----------------------------------------------------------------------\n\n\t/**\n\t * Update internal state and notify all listeners.\n\t */\n\tprivate setState(state: AuthState, user: AuthUser | null): void {\n\t\tconst changed = this._state !== state || this._user !== user\n\t\tthis._state = state\n\t\tthis._user = user\n\n\t\tif (changed) {\n\t\t\tfor (const listener of this.listeners) {\n\t\t\t\ttry {\n\t\t\t\t\tlistener(state)\n\t\t\t\t} catch {\n\t\t\t\t\t// Listeners should not throw, but if they do, do not let it\n\t\t\t\t\t// break the notification loop for other listeners.\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.notifySession()\n\t\t}\n\t}\n\n\tprivate setSessionStatus(status: AuthSessionStatus): void {\n\t\tif (this.sessionStatus === status) return\n\t\tthis.sessionStatus = status\n\t\tthis.notifySession()\n\t}\n\n\tprivate notifySession(): void {\n\t\tconst session = this.session\n\t\tfor (const listener of this.sessionListeners) {\n\t\t\ttry {\n\t\t\t\tlistener(session)\n\t\t\t} catch {\n\t\t\t\t// Same isolation as auth listeners.\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async completeSignIn(\n\t\tresponse: AuthSignInResponse | AuthTokensResponse | MfaChallengeResponse,\n\t): Promise<AuthUser> {\n\t\tif ('mfaRequired' in response) {\n\t\t\tthrow new MfaRequiredError(response.mfaToken)\n\t\t}\n\t\tconst tokens = 'tokens' in response ? response.tokens : response\n\t\tawait this.storage.setTokens(tokens.accessToken, tokens.refreshToken)\n\t\tthis.markFresh(tokens.refreshToken)\n\t\tconst user =\n\t\t\t'user' in response && response.user\n\t\t\t\t? normalizeAuthUser(response.user)\n\t\t\t\t: await this.fetchUserProfile(tokens.accessToken)\n\t\tthis.setState('authenticated', user)\n\t\treturn user\n\t}\n\n\t/**\n\t * Restore a session from a valid access token by fetching the user profile.\n\t * A definitive 401 from `/auth/me` ends the session (NEW-AUTH-4); any other\n\t * failure restores it as authenticated-offline from the stored identity.\n\t */\n\tprivate async restoreSession(accessToken: string): Promise<void> {\n\t\tlet response: RawResponse\n\t\ttry {\n\t\t\tresponse = await this.rawRequest('/auth/me', { method: 'GET', token: accessToken })\n\t\t} catch {\n\t\t\tawait this.enterOfflineSession()\n\t\t\treturn\n\t\t}\n\t\tif (response.ok && isRecord(response.json)) {\n\t\t\tconst profile = (response.json.data !== undefined ? response.json.data : response.json) as\n\t\t\t\t| UserProfileResponse\n\t\t\t\t| undefined\n\t\t\tif (isRecord(profile) && typeof profile.id === 'string') {\n\t\t\t\tthis.markFresh(null)\n\t\t\t\tthis.setState('authenticated', normalizeAuthUser(profile))\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tif (response.status === 401 && isDefinitiveRejection(response)) {\n\t\t\tawait this.endSession()\n\t\t\treturn\n\t\t}\n\t\tawait this.enterOfflineSession()\n\t}\n\n\t/**\n\t * Restore the session from stored credentials while the auth server is\n\t * unreachable. Signs out only when the stored refresh token is unusable.\n\t */\n\tprivate async enterOfflineSession(): Promise<void> {\n\t\tconst refreshToken = await this.storage.getRefreshToken()\n\t\tconst identity = await this.getStoredIdentity()\n\t\tif (!refreshToken || !identity || isTokenExpired(refreshToken, 0)) {\n\t\t\tawait this.endSession()\n\t\t\treturn\n\t\t}\n\t\tthis.cachedDeviceId = identity.deviceId\n\t\tthis.noteIssuedCredential(refreshToken)\n\t\tconst user =\n\t\t\tthis._user && this._user.id === identity.userId\n\t\t\t\t? this._user\n\t\t\t\t: { id: identity.userId, email: '', name: null }\n\t\tthis.sessionStatus = this.offlineStatus()\n\t\tthis.setState('authenticated', user)\n\t\tthis.notifySession()\n\t}\n\n\t/** Offline, or locked when the grace period ran out or the clock went backwards. */\n\tprivate offlineStatus(): AuthSessionStatus {\n\t\tconst now = Date.now()\n\t\tif (\n\t\t\tthis.lastServerContactAt > 0 &&\n\t\t\tnow + CLOCK_ROLLBACK_TOLERANCE_MS < this.lastServerContactAt\n\t\t) {\n\t\t\t// The device clock is earlier than a moment we know already happened:\n\t\t\t// it was set back, which would otherwise extend the offline grace.\n\t\t\treturn 'locked'\n\t\t}\n\t\tif (\n\t\t\tthis.maxOfflineGraceMs !== undefined &&\n\t\t\tthis.lastServerContactAt > 0 &&\n\t\t\tnow - this.lastServerContactAt > this.maxOfflineGraceMs\n\t\t) {\n\t\t\treturn 'locked'\n\t\t}\n\t\treturn 'offline'\n\t}\n\n\tprivate async endSession(): Promise<void> {\n\t\tawait this.storage.clear()\n\t\tthis.resetBackoff()\n\t\tthis.sessionStatus = 'fresh'\n\t\tthis.cachedDeviceId = null\n\t\tthis.setState('unauthenticated', null)\n\t}\n\n\t/** The refresh token's issue time is the server's clock at the last rotation. */\n\tprivate noteIssuedCredential(refreshToken: string | null): void {\n\t\tif (!refreshToken) return\n\t\tconst issued = tokenIssuedAtMs(refreshToken)\n\t\tif (issued !== null) this.lastServerContactAt = Math.max(this.lastServerContactAt, issued)\n\t\tconst claims = decodeJwtPayload(refreshToken)\n\t\tif (claims && typeof claims.dev === 'string') this.cachedDeviceId = claims.dev\n\t}\n\n\tprivate markFresh(refreshToken: string | null): void {\n\t\tthis.resetBackoff()\n\t\tthis.lastServerContactAt = Math.max(this.lastServerContactAt, Date.now())\n\t\tthis.noteIssuedCredential(refreshToken)\n\t\tthis.setSessionStatus('fresh')\n\t}\n\n\tprivate markOffline(): void {\n\t\tif (this._state === 'authenticated') {\n\t\t\tthis.setSessionStatus(this.offlineStatus())\n\t\t}\n\t}\n\n\t/**\n\t * Fetch the current user profile from the server.\n\t */\n\tprivate async fetchUserProfile(accessToken: string): Promise<AuthUser> {\n\t\tconst profile = await this.request<UserProfileResponse>('/auth/me', {\n\t\t\tmethod: 'GET',\n\t\t\ttoken: accessToken,\n\t\t})\n\t\treturn normalizeAuthUser(profile)\n\t}\n\n\tprivate async createOAuthAuthorization(\n\t\tprovider: string,\n\t\toptions: OAuthAuthorizationOptions,\n\t): Promise<OAuthAuthorizationResult> {\n\t\tconst params = new URLSearchParams()\n\n\t\tif (options.returnTo) {\n\t\t\tparams.set('returnTo', options.returnTo)\n\t\t}\n\t\tif (options.metadata) {\n\t\t\tfor (const [key, value] of Object.entries(options.metadata)) {\n\t\t\t\tif (value !== undefined && value !== null) {\n\t\t\t\t\tparams.set(key, String(value))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst query = params.toString()\n\t\tconst result = await this.request<OAuthAuthorizationResult>(\n\t\t\t`/auth/oauth/${encodeURIComponent(provider)}${query ? `?${query}` : ''}`,\n\t\t\t{\n\t\t\t\tmethod: 'GET',\n\t\t\t},\n\t\t)\n\t\tthis.rememberOAuthBinding(result)\n\t\treturn result\n\t}\n\n\t/** Bindings of flows this client started, by state (survives the redirect on web). */\n\tprivate readonly oauthBindings = new Map<string, string>()\n\n\tprivate rememberOAuthBinding(result: OAuthAuthorizationResult): void {\n\t\tif (!result.binding || !result.state) return\n\t\tthis.oauthBindings.set(result.state, result.binding)\n\t\tconst session = getSessionStorage()\n\t\ttry {\n\t\t\tsession?.setItem(`${OAUTH_BINDING_PREFIX}${result.state}`, result.binding)\n\t\t} catch {\n\t\t\t// Session storage full or blocked: the in-memory copy still serves native flows.\n\t\t}\n\t}\n\n\tprivate takeOAuthBinding(state: string): string | undefined {\n\t\tconst inMemory = this.oauthBindings.get(state)\n\t\tthis.oauthBindings.delete(state)\n\t\tconst session = getSessionStorage()\n\t\tlet stored: string | null = null\n\t\ttry {\n\t\t\tstored = session?.getItem(`${OAUTH_BINDING_PREFIX}${state}`) ?? null\n\t\t\tsession?.removeItem(`${OAUTH_BINDING_PREFIX}${state}`)\n\t\t} catch {\n\t\t\tstored = null\n\t\t}\n\t\treturn inMemory ?? stored ?? undefined\n\t}\n\n\tprivate async withDeviceIdentity<T extends { deviceId?: string; devicePublicKey?: string }>(\n\t\tparams: T,\n\t): Promise<T> {\n\t\tif (!this.deviceIdentity || (params.deviceId && params.devicePublicKey)) {\n\t\t\treturn params\n\t\t}\n\n\t\tconst identity = await this.deviceIdentity.getDeviceIdentity()\n\t\treturn {\n\t\t\t...params,\n\t\t\tdeviceId: params.deviceId ?? identity.deviceId,\n\t\t\tdevicePublicKey: params.devicePublicKey ?? identity.devicePublicKey,\n\t\t}\n\t}\n\n\t/**\n\t * Refresh the session's tokens. De-duplicates concurrent calls in this\n\t * client, serializes refreshes across tabs, and applies backoff after\n\t * transient failures.\n\t */\n\tprivate refresh(): Promise<RefreshOutcome> {\n\t\tif (this._refreshPromise) {\n\t\t\treturn this._refreshPromise\n\t\t}\n\t\tconst promise = this.refreshOnce().finally(() => {\n\t\t\tif (this._refreshPromise === promise) this._refreshPromise = null\n\t\t})\n\t\tthis._refreshPromise = promise\n\t\treturn promise\n\t}\n\n\tprivate async refreshOnce(): Promise<RefreshOutcome> {\n\t\tconst refreshToken = await this.storage.getRefreshToken()\n\t\tif (!refreshToken) return { kind: 'rejected' }\n\n\t\t// A refresh token that has expired is unusable whatever the network says.\n\t\tif (isTokenExpired(refreshToken, 0)) {\n\t\t\tawait this.endSession()\n\t\t\treturn { kind: 'rejected' }\n\t\t}\n\n\t\tif (Date.now() < this.nextAttemptAt) {\n\t\t\tthis.markOffline()\n\t\t\treturn { kind: 'transient' }\n\t\t}\n\n\t\tlet outcome: RefreshOutcome\n\t\ttry {\n\t\t\toutcome = await this.withRefreshLock(async () => {\n\t\t\t\t// Another tab may have refreshed while we waited for the lock: adopt\n\t\t\t\t// its tokens instead of presenting a now-rotated refresh token.\n\t\t\t\tconst current = await this.storage.getRefreshToken()\n\t\t\t\tif (!current) return { kind: 'rejected' } as const\n\t\t\t\tconst currentAccess = await this.storage.getAccessToken()\n\t\t\t\tif (current !== refreshToken && currentAccess && !isTokenExpired(currentAccess)) {\n\t\t\t\t\treturn { kind: 'ok', accessToken: currentAccess } as const\n\t\t\t\t}\n\t\t\t\treturn this.performRefresh(current)\n\t\t\t})\n\t\t} catch {\n\t\t\t// Lock acquisition timed out (a hung tab holds it): transient.\n\t\t\toutcome = { kind: 'transient' }\n\t\t}\n\n\t\tif (outcome.kind === 'ok') {\n\t\t\tthis.markFresh(await this.storage.getRefreshToken())\n\t\t} else if (outcome.kind === 'rejected') {\n\t\t\tthis.resetBackoff()\n\t\t\tthis.cachedDeviceId = null\n\t\t\tthis.sessionStatus = 'fresh'\n\t\t\tthis.setState('unauthenticated', null)\n\t\t} else {\n\t\t\tthis.registerFailure(outcome.retryAfterMs)\n\t\t\tthis.markOffline()\n\t\t}\n\t\treturn outcome\n\t}\n\n\t/**\n\t * Execute the token refresh network request and classify the answer.\n\t */\n\tprivate async performRefresh(refreshToken: string): Promise<RefreshOutcome> {\n\t\tlet response: RawResponse\n\t\ttry {\n\t\t\tresponse = await this.rawRequest('/auth/refresh', {\n\t\t\t\tmethod: 'POST',\n\t\t\t\tbody: { refreshToken },\n\t\t\t})\n\t\t} catch {\n\t\t\t// No network, DNS, TLS, CORS, abort or timeout: says nothing about the token.\n\t\t\treturn { kind: 'transient' }\n\t\t}\n\n\t\tif (response.ok) {\n\t\t\tconst tokens = readTokenPair(response.json)\n\t\t\tif (!tokens) {\n\t\t\t\t// 2xx without tokens: a captive portal or proxy answered, not Kora.\n\t\t\t\treturn { kind: 'transient' }\n\t\t\t}\n\t\t\tawait this.storage.setTokens(tokens.accessToken, tokens.refreshToken)\n\t\t\treturn { kind: 'ok', accessToken: tokens.accessToken }\n\t\t}\n\n\t\tif (isDefinitiveRejection(response)) {\n\t\t\t// Never clear a token pair another tab stored after we read ours.\n\t\t\tif ((await this.storage.getRefreshToken()) === refreshToken) {\n\t\t\t\tawait this.storage.clear()\n\t\t\t\treturn { kind: 'rejected' }\n\t\t\t}\n\t\t\tconst adopted = await this.storage.getAccessToken()\n\t\t\treturn adopted && !isTokenExpired(adopted)\n\t\t\t\t? { kind: 'ok', accessToken: adopted }\n\t\t\t\t: { kind: 'transient' }\n\t\t}\n\n\t\treturn { kind: 'transient', retryAfterMs: response.retryAfterMs }\n\t}\n\n\tprivate async withRefreshLock<T>(fn: () => Promise<T>): Promise<T> {\n\t\tconst locks = getWebLocks()\n\t\tif (locks) {\n\t\t\tconst controller = typeof AbortController === 'function' ? new AbortController() : null\n\t\t\tconst timer = controller\n\t\t\t\t? setTimeout(() => controller.abort(), this.requestTimeoutMs * 2)\n\t\t\t\t: null\n\t\t\ttry {\n\t\t\t\treturn await locks.request(\n\t\t\t\t\tthis.lockName,\n\t\t\t\t\tcontroller ? { signal: controller.signal } : {},\n\t\t\t\t\tasync () => {\n\t\t\t\t\t\tif (timer) clearTimeout(timer)\n\t\t\t\t\t\treturn fn()\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t} finally {\n\t\t\t\tif (timer) clearTimeout(timer)\n\t\t\t}\n\t\t}\n\t\t// Same-realm fallback (Node, older browsers): serialize per storage object.\n\t\tconst key = this.storage as object\n\t\tconst previous = inProcessLocks.get(key) ?? Promise.resolve()\n\t\tconst run = previous.then(fn, fn)\n\t\tinProcessLocks.set(\n\t\t\tkey,\n\t\t\trun.then(\n\t\t\t\t() => undefined,\n\t\t\t\t() => undefined,\n\t\t\t),\n\t\t)\n\t\treturn run\n\t}\n\n\t/**\n\t * Schedule the next allowed refresh attempt. The first retry after a failure\n\t * is immediate (it recovers a rotation response lost on the wire); after\n\t * that, exponential backoff with equal jitter, never earlier than Retry-After.\n\t */\n\tprivate registerFailure(retryAfterMs: number | undefined): void {\n\t\tthis.failureCount++\n\t\tlet delay = 0\n\t\tif (this.failureCount > 1) {\n\t\t\tconst ceiling = Math.min(this.backoffBaseMs * 2 ** (this.failureCount - 2), this.backoffMaxMs)\n\t\t\tdelay = ceiling / 2 + Math.random() * (ceiling / 2)\n\t\t}\n\t\tif (retryAfterMs !== undefined) delay = Math.max(delay, retryAfterMs)\n\t\tthis.nextAttemptAt = Date.now() + delay\n\t\tthis.scheduleRetry(Math.max(delay, this.backoffBaseMs))\n\t}\n\n\tprivate resetBackoff(): void {\n\t\tthis.failureCount = 0\n\t\tthis.nextAttemptAt = 0\n\t\tthis.clearRetryTimer()\n\t}\n\n\t/** Background retry so an offline session recovers without the app calling in. */\n\tprivate scheduleRetry(delayMs: number): void {\n\t\tthis.clearRetryTimer()\n\t\tif (typeof setTimeout !== 'function') return\n\t\tconst timer = setTimeout(() => {\n\t\t\tthis.retryTimer = null\n\t\t\tif (this._state === 'authenticated' && this.sessionStatus !== 'fresh') {\n\t\t\t\tvoid this.getAccessToken().catch(() => undefined)\n\t\t\t}\n\t\t}, delayMs)\n\t\t// Never keep a Node process alive just to retry a refresh.\n\t\t;(timer as { unref?: () => void }).unref?.()\n\t\tthis.retryTimer = timer\n\t}\n\n\tprivate clearRetryTimer(): void {\n\t\tif (this.retryTimer !== null) {\n\t\t\tclearTimeout(this.retryTimer)\n\t\t\tthis.retryTimer = null\n\t\t}\n\t}\n\n\tprivate attachEnvironmentListeners(): () => void {\n\t\tconst target = (globalThis as { window?: unknown }).window as\n\t\t\t| {\n\t\t\t\t\taddEventListener?: (type: string, listener: () => void) => void\n\t\t\t\t\tremoveEventListener?: (type: string, listener: () => void) => void\n\t\t\t }\n\t\t\t| undefined\n\t\tif (!target || typeof target.addEventListener !== 'function') return () => {}\n\t\tconst wake = (): void => {\n\t\t\tconst doc = (globalThis as { document?: { visibilityState?: string } }).document\n\t\t\tif (doc?.visibilityState === 'hidden') return\n\t\t\tvoid this.retryNow().catch(() => undefined)\n\t\t}\n\t\ttarget.addEventListener('online', wake)\n\t\ttarget.addEventListener('visibilitychange', wake)\n\t\treturn () => {\n\t\t\ttarget.removeEventListener?.('online', wake)\n\t\t\ttarget.removeEventListener?.('visibilitychange', wake)\n\t\t}\n\t}\n\n\tprivate async requireAccessToken(): Promise<string> {\n\t\tconst token = await this.getAccessToken()\n\t\tif (!token) {\n\t\t\tthrow new AuthError('You must be signed in to perform this action.', 'AUTH_REQUIRED')\n\t\t}\n\t\treturn token\n\t}\n\n\t/**\n\t * Send one request with a timeout and return status plus parsed JSON (if any).\n\t * Throws only for transport failures (network, abort, timeout).\n\t */\n\tprivate async rawRequest(\n\t\tpath: string,\n\t\toptions: {\n\t\t\tmethod: 'GET' | 'POST' | 'DELETE'\n\t\t\tbody?: Record<string, unknown>\n\t\t\ttoken?: string\n\t\t},\n\t): Promise<RawResponse> {\n\t\tconst url = `${this.serverUrl}${path}`\n\t\tconst headers: Record<string, string> = {}\n\t\tif (options.body) {\n\t\t\theaders['Content-Type'] = 'application/json'\n\t\t}\n\t\tif (options.token) {\n\t\t\theaders.Authorization = `Bearer ${options.token}`\n\t\t}\n\n\t\tconst controller = typeof AbortController === 'function' ? new AbortController() : null\n\t\tlet timer: ReturnType<typeof setTimeout> | null = null\n\t\tconst timeout = new Promise<never>((_, reject) => {\n\t\t\ttimer = setTimeout(() => {\n\t\t\t\tcontroller?.abort()\n\t\t\t\treject(\n\t\t\t\t\tnew AuthError(\n\t\t\t\t\t\t`Request to ${path} timed out after ${this.requestTimeoutMs}ms.`,\n\t\t\t\t\t\t'AUTH_TIMEOUT',\n\t\t\t\t\t\t{ path },\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}, this.requestTimeoutMs)\n\t\t})\n\t\ttry {\n\t\t\tconst response = await Promise.race([\n\t\t\t\tthis.fetchFn(url, {\n\t\t\t\t\tmethod: options.method,\n\t\t\t\t\theaders,\n\t\t\t\t\tbody: options.body ? JSON.stringify(options.body) : undefined,\n\t\t\t\t\t...(controller ? { signal: controller.signal } : {}),\n\t\t\t\t}),\n\t\t\t\ttimeout,\n\t\t\t])\n\t\t\tlet json: unknown\n\t\t\ttry {\n\t\t\t\tjson = await Promise.race([response.json() as Promise<unknown>, timeout])\n\t\t\t} catch {\n\t\t\t\tjson = undefined\n\t\t\t}\n\t\t\tconst headerGetter = (response as { headers?: { get?: (name: string) => string | null } })\n\t\t\t\t.headers\n\t\t\treturn {\n\t\t\t\tstatus: response.status,\n\t\t\t\tok: response.ok,\n\t\t\t\tjson,\n\t\t\t\tretryAfterMs: parseRetryAfter(headerGetter?.get?.('Retry-After')),\n\t\t\t}\n\t\t} finally {\n\t\t\tif (timer !== null) clearTimeout(timer)\n\t\t}\n\t}\n\n\t/**\n\t * Make an HTTP request to the auth server.\n\t *\n\t * @param path - URL path relative to serverUrl (e.g. '/auth/signin')\n\t * @param options - Request options\n\t * @returns Parsed JSON response body\n\t * @throws {AuthError} On network failure, timeout, or non-2xx response\n\t */\n\tprivate async request<T>(\n\t\tpath: string,\n\t\toptions: {\n\t\t\tmethod: 'GET' | 'POST' | 'DELETE'\n\t\t\tbody?: Record<string, unknown>\n\t\t\ttoken?: string\n\t\t},\n\t): Promise<T> {\n\t\tlet response: RawResponse\n\t\ttry {\n\t\t\tresponse = await this.rawRequest(path, options)\n\t\t} catch (cause) {\n\t\t\tif (cause instanceof AuthError) throw cause\n\t\t\tthrow new AuthError(\n\t\t\t\t`Network request to ${path} failed. The auth server at ${this.serverUrl} may be unreachable. Check your network connection and serverUrl configuration.`,\n\t\t\t\t'AUTH_NETWORK_ERROR',\n\t\t\t\t{ path, cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t\t)\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tlet errorMessage = `Auth server returned HTTP ${response.status}`\n\t\t\tlet serverError: string | undefined\n\t\t\tlet serverCode: string | undefined\n\t\t\tif (isRecord(response.json)) {\n\t\t\t\tif (typeof response.json.error === 'string') {\n\t\t\t\t\terrorMessage = response.json.error\n\t\t\t\t\tserverError = errorMessage\n\t\t\t\t} else if (typeof response.json.message === 'string') {\n\t\t\t\t\terrorMessage = response.json.message\n\t\t\t\t\tserverError = errorMessage\n\t\t\t\t}\n\t\t\t\tif (typeof response.json.code === 'string') serverCode = response.json.code\n\t\t\t}\n\n\t\t\tthrow new AuthError(errorMessage, 'AUTH_SERVER_ERROR', {\n\t\t\t\tpath,\n\t\t\t\tstatus: response.status,\n\t\t\t\tserverError,\n\t\t\t\tserverCode,\n\t\t\t})\n\t\t}\n\n\t\tif (!isRecord(response.json) && !Array.isArray(response.json)) {\n\t\t\tthrow new AuthError(\n\t\t\t\t`Auth server returned a non-JSON response for ${path}. A captive portal or proxy may be intercepting requests.`,\n\t\t\t\t'AUTH_INVALID_RESPONSE',\n\t\t\t\t{ path, status: response.status },\n\t\t\t)\n\t\t}\n\t\tconst json = response.json as Record<string, unknown>\n\n\t\t// The BuiltInAuthRoutes server wraps success responses in { data: T }.\n\t\t// Unwrap the envelope so callers get the inner payload directly.\n\t\treturn (json.data !== undefined ? json.data : json) as T\n\t}\n}\n","import { KoraError } from '@korajs/core'\nimport { exportPublicKeyJwk, generateDeviceKeyPair } from '../device/device-identity'\nimport { type DeviceKeyStore, createDeviceKeyStore } from '../device/device-store'\nimport type { AuthKeyValueStorage } from './storage'\n\nconst DEFAULT_DEVICE_ID_KEY = 'kora_auth_device_id'\n\nexport interface AuthDeviceIdentity {\n\t/** Stable local device ID used in token `dev` claims. */\n\tdeviceId: string\n\t/** Public proof-of-possession key serialized as a JSON Web Key string. */\n\tdevicePublicKey: string\n}\n\nexport interface AuthDeviceIdentityProvider {\n\tgetDeviceIdentity(): Promise<AuthDeviceIdentity>\n}\n\nexport interface PersistentDeviceIdentityOptions {\n\t/** Store for the stable device ID. Use a platform credential store in production. */\n\tstorage: AuthKeyValueStorage\n\t/** Store for the non-extractable device key pair. Defaults to IndexedDB when available. */\n\tkeyStore?: DeviceKeyStore\n\t/** Storage key for the device ID. Defaults to `kora_auth_device_id`. */\n\tdeviceIdKey?: string\n\t/** Optional device ID generator for tests or custom device registries. */\n\tgenerateDeviceId?: () => string\n}\n\nexport class AuthDeviceIdentityError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'AUTH_DEVICE_IDENTITY_ERROR', context)\n\t\tthis.name = 'AuthDeviceIdentityError'\n\t}\n}\n\n/**\n * Creates a persistent device identity provider for `AuthClient`.\n *\n * The provider keeps a stable device ID in the supplied key-value store and a\n * non-extractable ECDSA P-256 key pair in the supplied `DeviceKeyStore`. The\n * public key is returned during sign-up/sign-in so the server can bind tokens\n * to a real offline device instead of a transient browser session.\n */\nexport function createPersistentDeviceIdentity(\n\toptions: PersistentDeviceIdentityOptions,\n): AuthDeviceIdentityProvider {\n\tconst storage = options.storage\n\tconst keyStore = options.keyStore ?? createDefaultPersistentKeyStore()\n\tconst deviceIdKey = options.deviceIdKey ?? DEFAULT_DEVICE_ID_KEY\n\tconst generateDeviceId = options.generateDeviceId ?? defaultDeviceId\n\n\treturn {\n\t\tasync getDeviceIdentity(): Promise<AuthDeviceIdentity> {\n\t\t\tlet deviceId = await storage.getItem(deviceIdKey)\n\t\t\tif (!deviceId) {\n\t\t\t\tdeviceId = generateDeviceId()\n\t\t\t\tawait storage.setItem(deviceIdKey, deviceId)\n\t\t\t}\n\n\t\t\tlet keyPair = await keyStore.loadKeyPair(deviceId)\n\t\t\tif (!keyPair) {\n\t\t\t\tkeyPair = await generateDeviceKeyPair()\n\t\t\t\tawait keyStore.saveKeyPair(deviceId, keyPair)\n\t\t\t}\n\n\t\t\tconst publicKey = await exportPublicKeyJwk(keyPair)\n\t\t\treturn {\n\t\t\t\tdeviceId,\n\t\t\t\tdevicePublicKey: JSON.stringify(publicKey),\n\t\t\t}\n\t\t},\n\t}\n}\n\nfunction createDefaultPersistentKeyStore(): DeviceKeyStore {\n\tif (typeof globalThis.indexedDB !== 'undefined') {\n\t\treturn createDeviceKeyStore()\n\t}\n\n\tthrow new AuthDeviceIdentityError(\n\t\t'No persistent device key store is available in this runtime. Pass `keyStore` to createPersistentDeviceIdentity().',\n\t)\n}\n\nfunction defaultDeviceId(): string {\n\tif (typeof globalThis.crypto?.randomUUID === 'function') {\n\t\treturn globalThis.crypto.randomUUID()\n\t}\n\n\tconst bytes = new Uint8Array(16)\n\tif (typeof globalThis.crypto?.getRandomValues === 'function') {\n\t\tglobalThis.crypto.getRandomValues(bytes)\n\t} else {\n\t\tfor (let i = 0; i < bytes.length; i++) {\n\t\t\tbytes[i] = Math.floor(Math.random() * 256)\n\t\t}\n\t}\n\n\tbytes[6] = ((bytes[6] as number) & 0x0f) | 0x40\n\tbytes[8] = ((bytes[8] as number) & 0x3f) | 0x80\n\tconst hex = [...bytes].map((byte) => byte.toString(16).padStart(2, '0')).join('')\n\treturn `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`\n}\n","import { KoraError } from '@korajs/core'\n\n// --- Device key store errors ---\n\n/**\n * Thrown when a device key store operation fails.\n * Provides context about the operation and the underlying cause.\n */\nexport class DeviceKeyStoreError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'DEVICE_KEY_STORE_ERROR', context)\n\t\tthis.name = 'DeviceKeyStoreError'\n\t}\n}\n\n// --- Interface ---\n\n/**\n * Persistent storage interface for ECDSA P-256 device key pairs.\n *\n * Device key pairs are used for proof-of-possession authentication:\n * the private key never leaves the device, and the public key is\n * registered with the server. This store persists key pairs across\n * page reloads and app restarts.\n *\n * In the browser, CryptoKey objects are structured-cloneable, so they\n * can be stored directly in IndexedDB without serialization. In Node.js\n * or test environments, an in-memory implementation is used instead.\n */\nexport interface DeviceKeyStore {\n\t/**\n\t * Persist a key pair for the given device.\n\t *\n\t * Overwrites any previously stored key pair for the same device ID.\n\t *\n\t * @param deviceId - The unique device identifier (typically a UUID v7)\n\t * @param keyPair - The ECDSA P-256 CryptoKeyPair to store\n\t * @throws {DeviceKeyStoreError} If the storage operation fails\n\t */\n\tsaveKeyPair(deviceId: string, keyPair: CryptoKeyPair): Promise<void>\n\n\t/**\n\t * Load a previously stored key pair for the given device.\n\t *\n\t * @param deviceId - The unique device identifier\n\t * @returns The stored CryptoKeyPair, or null if no key pair exists for the device\n\t * @throws {DeviceKeyStoreError} If the storage operation fails\n\t */\n\tloadKeyPair(deviceId: string): Promise<CryptoKeyPair | null>\n\n\t/**\n\t * Delete a stored key pair for the given device.\n\t *\n\t * No-op if no key pair exists for the device ID.\n\t *\n\t * @param deviceId - The unique device identifier\n\t * @throws {DeviceKeyStoreError} If the storage operation fails\n\t */\n\tdeleteKeyPair(deviceId: string): Promise<void>\n\n\t/**\n\t * Check whether a key pair exists for the given device.\n\t *\n\t * @param deviceId - The unique device identifier\n\t * @returns True if a key pair is stored for the device, false otherwise\n\t * @throws {DeviceKeyStoreError} If the storage operation fails\n\t */\n\thasKeyPair(deviceId: string): Promise<boolean>\n}\n\n// --- IndexedDB constants ---\n\n/** IndexedDB database name for device key storage. */\nconst IDB_DATABASE_NAME = 'kora_device_keys'\n\n/** IndexedDB object store name within the database. */\nconst IDB_STORE_NAME = 'keypairs'\n\n/** Current database schema version. */\nconst IDB_VERSION = 1\n\n// --- IndexedDB implementation ---\n\n/**\n * Browser-based device key store backed by IndexedDB.\n *\n * CryptoKey objects are structured-cloneable, so they can be stored\n * directly in IndexedDB without needing to export/import them as JWK.\n * This preserves the non-extractable flag on private keys, ensuring\n * they cannot be read even from storage.\n *\n * The database uses a single object store (`keypairs`) with the device ID\n * as the key and the full CryptoKeyPair as the value.\n *\n * @example\n * ```typescript\n * const store = new IndexedDBDeviceKeyStore()\n * const keyPair = await generateDeviceKeyPair()\n * await store.saveKeyPair('device-123', keyPair)\n *\n * const loaded = await store.loadKeyPair('device-123')\n * // loaded.privateKey is still non-extractable\n * ```\n */\nexport class IndexedDBDeviceKeyStore implements DeviceKeyStore {\n\tprivate dbPromise: Promise<IDBDatabase> | null = null\n\n\t/**\n\t * Opens (or creates) the IndexedDB database.\n\t *\n\t * The database connection is lazily initialized on first use and\n\t * reused for subsequent operations. If the database does not exist,\n\t * it is created with the `keypairs` object store.\n\t */\n\tprivate openDatabase(): Promise<IDBDatabase> {\n\t\t// Reuse the database connection if already opened.\n\t\t// This avoids repeatedly opening the database on every operation.\n\t\tif (this.dbPromise !== null) {\n\t\t\treturn this.dbPromise\n\t\t}\n\n\t\tthis.dbPromise = new Promise<IDBDatabase>((resolve, reject) => {\n\t\t\tlet request: IDBOpenDBRequest\n\n\t\t\ttry {\n\t\t\t\trequest = globalThis.indexedDB.open(IDB_DATABASE_NAME, IDB_VERSION)\n\t\t\t} catch (cause) {\n\t\t\t\tthis.dbPromise = null\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t'Failed to open IndexedDB database for device key storage. ' +\n\t\t\t\t\t\t\t'IndexedDB may be unavailable or access may be denied.',\n\t\t\t\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\trequest.onupgradeneeded = () => {\n\t\t\t\tconst db = request.result\n\t\t\t\tif (!db.objectStoreNames.contains(IDB_STORE_NAME)) {\n\t\t\t\t\tdb.createObjectStore(IDB_STORE_NAME)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\trequest.onsuccess = () => {\n\t\t\t\tresolve(request.result)\n\t\t\t}\n\n\t\t\trequest.onerror = () => {\n\t\t\t\tthis.dbPromise = null\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError('Failed to open IndexedDB database for device key storage.', {\n\t\t\t\t\t\terror: request.error?.message,\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\trequest.onblocked = () => {\n\t\t\t\tthis.dbPromise = null\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t'IndexedDB database open was blocked. ' +\n\t\t\t\t\t\t\t'Another tab may have an older version of the database open. ' +\n\t\t\t\t\t\t\t'Close other tabs and try again.',\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\n\t\treturn this.dbPromise\n\t}\n\n\t/** @inheritdoc */\n\tasync saveKeyPair(deviceId: string, keyPair: CryptoKeyPair): Promise<void> {\n\t\tconst db = await this.openDatabase()\n\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\ttry {\n\t\t\t\tconst tx = db.transaction(IDB_STORE_NAME, 'readwrite')\n\t\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\n\t\t\t\t// Store the CryptoKeyPair as a structured clone with the deviceId as the key.\n\t\t\t\t// IndexedDB handles structured cloning of CryptoKey objects natively.\n\t\t\t\tstore.put(keyPair, deviceId)\n\n\t\t\t\ttx.oncomplete = () => {\n\t\t\t\t\tresolve()\n\t\t\t\t}\n\n\t\t\t\ttx.onerror = () => {\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew DeviceKeyStoreError(`Failed to save key pair for device \"${deviceId}\".`, {\n\t\t\t\t\t\t\tdeviceId,\n\t\t\t\t\t\t\terror: tx.error?.message,\n\t\t\t\t\t\t}),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t} catch (cause) {\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(`Failed to save key pair for device \"${deviceId}\".`, {\n\t\t\t\t\t\tdeviceId,\n\t\t\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n\n\t/** @inheritdoc */\n\tasync loadKeyPair(deviceId: string): Promise<CryptoKeyPair | null> {\n\t\tconst db = await this.openDatabase()\n\n\t\treturn new Promise<CryptoKeyPair | null>((resolve, reject) => {\n\t\t\ttry {\n\t\t\t\tconst tx = db.transaction(IDB_STORE_NAME, 'readonly')\n\t\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\t\t\t\tconst request = store.get(deviceId)\n\n\t\t\t\trequest.onsuccess = () => {\n\t\t\t\t\tconst result = request.result as CryptoKeyPair | undefined\n\t\t\t\t\tresolve(result ?? null)\n\t\t\t\t}\n\n\t\t\t\trequest.onerror = () => {\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew DeviceKeyStoreError(`Failed to load key pair for device \"${deviceId}\".`, {\n\t\t\t\t\t\t\tdeviceId,\n\t\t\t\t\t\t\terror: request.error?.message,\n\t\t\t\t\t\t}),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t} catch (cause) {\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(`Failed to load key pair for device \"${deviceId}\".`, {\n\t\t\t\t\t\tdeviceId,\n\t\t\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n\n\t/** @inheritdoc */\n\tasync deleteKeyPair(deviceId: string): Promise<void> {\n\t\tconst db = await this.openDatabase()\n\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\ttry {\n\t\t\t\tconst tx = db.transaction(IDB_STORE_NAME, 'readwrite')\n\t\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\t\t\t\tstore.delete(deviceId)\n\n\t\t\t\ttx.oncomplete = () => {\n\t\t\t\t\tresolve()\n\t\t\t\t}\n\n\t\t\t\ttx.onerror = () => {\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew DeviceKeyStoreError(`Failed to delete key pair for device \"${deviceId}\".`, {\n\t\t\t\t\t\t\tdeviceId,\n\t\t\t\t\t\t\terror: tx.error?.message,\n\t\t\t\t\t\t}),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t} catch (cause) {\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(`Failed to delete key pair for device \"${deviceId}\".`, {\n\t\t\t\t\t\tdeviceId,\n\t\t\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n\n\t/** @inheritdoc */\n\tasync hasKeyPair(deviceId: string): Promise<boolean> {\n\t\tconst db = await this.openDatabase()\n\n\t\treturn new Promise<boolean>((resolve, reject) => {\n\t\t\ttry {\n\t\t\t\tconst tx = db.transaction(IDB_STORE_NAME, 'readonly')\n\t\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\n\t\t\t\t// Use count() with the key to check existence without loading the value.\n\t\t\t\t// This is more efficient than get() for large CryptoKeyPair objects.\n\t\t\t\tconst request = store.count(deviceId)\n\n\t\t\t\trequest.onsuccess = () => {\n\t\t\t\t\tresolve(request.result > 0)\n\t\t\t\t}\n\n\t\t\t\trequest.onerror = () => {\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew DeviceKeyStoreError(\n\t\t\t\t\t\t\t`Failed to check if key pair exists for device \"${deviceId}\".`,\n\t\t\t\t\t\t\t{ deviceId, error: request.error?.message },\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t} catch (cause) {\n\t\t\t\treject(\n\t\t\t\t\tnew DeviceKeyStoreError(`Failed to check if key pair exists for device \"${deviceId}\".`, {\n\t\t\t\t\t\tdeviceId,\n\t\t\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// --- In-memory implementation ---\n\n/**\n * In-memory device key store for Node.js and testing environments.\n *\n * Key pairs are stored in a plain Map and do not survive process restarts.\n * This is suitable for server-side rendering, Node.js scripts, and unit tests\n * where IndexedDB is not available.\n *\n * @example\n * ```typescript\n * const store = new InMemoryDeviceKeyStore()\n * const keyPair = await generateDeviceKeyPair()\n * await store.saveKeyPair('device-123', keyPair)\n *\n * const loaded = await store.loadKeyPair('device-123')\n * ```\n */\nexport class InMemoryDeviceKeyStore implements DeviceKeyStore {\n\tprivate readonly store = new Map<string, CryptoKeyPair>()\n\n\t/** @inheritdoc */\n\tasync saveKeyPair(deviceId: string, keyPair: CryptoKeyPair): Promise<void> {\n\t\tthis.store.set(deviceId, keyPair)\n\t}\n\n\t/** @inheritdoc */\n\tasync loadKeyPair(deviceId: string): Promise<CryptoKeyPair | null> {\n\t\treturn this.store.get(deviceId) ?? null\n\t}\n\n\t/** @inheritdoc */\n\tasync deleteKeyPair(deviceId: string): Promise<void> {\n\t\tthis.store.delete(deviceId)\n\t}\n\n\t/** @inheritdoc */\n\tasync hasKeyPair(deviceId: string): Promise<boolean> {\n\t\treturn this.store.has(deviceId)\n\t}\n}\n\n// --- Factory ---\n\n/**\n * Creates a DeviceKeyStore appropriate for the current environment.\n *\n * In browsers where IndexedDB is available, returns an {@link IndexedDBDeviceKeyStore}\n * that persists CryptoKeyPair objects as structured clones. In Node.js, SSR,\n * or environments without IndexedDB, returns an {@link InMemoryDeviceKeyStore}.\n *\n * @returns A DeviceKeyStore instance for the current environment\n *\n * @example\n * ```typescript\n * const store = createDeviceKeyStore()\n * const keyPair = await generateDeviceKeyPair()\n * await store.saveKeyPair(deviceId, keyPair)\n * ```\n */\nexport function createDeviceKeyStore(): DeviceKeyStore {\n\tif (typeof globalThis.indexedDB !== 'undefined') {\n\t\treturn new IndexedDBDeviceKeyStore()\n\t}\n\treturn new InMemoryDeviceKeyStore()\n}\n","import type { AuthTokenStorage } from './auth-client'\n\ntype MaybePromise<T> = T | Promise<T>\n\n/**\n * Minimal key-value credential storage interface used by Kora auth adapters.\n *\n * This intentionally matches the shape of secure stores across runtimes:\n * browser Storage, Tauri secure storage plugins, Expo SecureStore, iOS Keychain,\n * Android Keystore wrappers, and encrypted desktop stores.\n */\nexport interface AuthKeyValueStorage {\n\tgetItem(key: string): MaybePromise<string | null>\n\tsetItem(key: string, value: string): MaybePromise<void>\n\tremoveItem(key: string): MaybePromise<void>\n}\n\nexport interface AuthTokenStorageOptions {\n\t/** Backing credential store. */\n\tstore: AuthKeyValueStorage\n\t/** Storage key prefix. Defaults to `kora_auth`. */\n\tprefix?: string\n}\n\n/**\n * Creates an `AuthTokenStorage` adapter from a runtime key-value store.\n *\n * Use this with platform credential stores instead of wiring `AuthClient`\n * directly to browser localStorage in desktop and mobile apps.\n */\nexport function createAuthTokenStorage(options: AuthTokenStorageOptions): AuthTokenStorage {\n\tconst prefix = options.prefix ?? 'kora_auth'\n\tconst accessKey = `${prefix}_access_token`\n\tconst refreshKey = `${prefix}_refresh_token`\n\tconst store = options.store\n\n\treturn {\n\t\tgetAccessToken: () => store.getItem(accessKey),\n\t\tgetRefreshToken: () => store.getItem(refreshKey),\n\t\tasync setTokens(accessToken: string, refreshToken: string): Promise<void> {\n\t\t\tawait store.setItem(accessKey, accessToken)\n\t\t\tawait store.setItem(refreshKey, refreshToken)\n\t\t},\n\t\tasync clear(): Promise<void> {\n\t\t\tawait store.removeItem(accessKey)\n\t\t\tawait store.removeItem(refreshKey)\n\t\t},\n\t}\n}\n\n/**\n * Creates an in-memory token storage adapter.\n *\n * Useful for tests, demos, and SSR. Production desktop and mobile apps should\n * prefer a secure platform-backed store.\n */\nexport function createMemoryAuthTokenStorage(): AuthTokenStorage {\n\tlet accessToken: string | null = null\n\tlet refreshToken: string | null = null\n\n\treturn {\n\t\tgetAccessToken: () => accessToken,\n\t\tgetRefreshToken: () => refreshToken,\n\t\tsetTokens(access: string, refresh: string): void {\n\t\t\taccessToken = access\n\t\t\trefreshToken = refresh\n\t\t},\n\t\tclear(): void {\n\t\t\taccessToken = null\n\t\t\trefreshToken = null\n\t\t},\n\t}\n}\n\n/**\n * Adapts Web Storage-compatible APIs such as `localStorage` or `sessionStorage`.\n */\nexport function createWebStorageAuthTokenStorage(\n\tstorage: Storage,\n\tprefix?: string,\n): AuthTokenStorage {\n\treturn createAuthTokenStorage({\n\t\tprefix,\n\t\tstore: {\n\t\t\tgetItem: (key) => storage.getItem(key),\n\t\t\tsetItem: (key, value) => {\n\t\t\t\tstorage.setItem(key, value)\n\t\t\t},\n\t\t\tremoveItem: (key) => {\n\t\t\t\tstorage.removeItem(key)\n\t\t\t},\n\t\t},\n\t})\n}\n","import type { DeviceKeyStore } from '../device/device-store'\nimport { AuthClient, type AuthClientConfig, type AuthTokenStorage } from './auth-client'\nimport type { AuthDeviceIdentityProvider } from './device-session'\nimport { createPersistentDeviceIdentity } from './device-session'\nimport {\n\ttype AuthKeyValueStorage,\n\tcreateAuthTokenStorage,\n\tcreateWebStorageAuthTokenStorage,\n} from './storage'\n\nexport interface CreateKoraAuthOptions\n\textends Omit<AuthClientConfig, 'storage' | 'deviceIdentity'> {\n\t/**\n\t * Complete token storage adapter. Use this for fully custom runtimes.\n\t * If omitted, `credentialStore` is adapted automatically.\n\t */\n\tstorage?: AuthTokenStorage\n\t/**\n\t * Runtime credential store used for tokens and stable device ID.\n\t * Examples: Tauri secure storage, Expo SecureStore, iOS Keychain, Android Keystore.\n\t */\n\tcredentialStore?: AuthKeyValueStorage\n\t/**\n\t * Explicit device identity provider. Set to `false` to disable automatic\n\t * device binding during sign-up/sign-in.\n\t */\n\tdeviceIdentity?: AuthDeviceIdentityProvider | false\n\t/**\n\t * Device key store for runtimes without IndexedDB, such as React Native.\n\t */\n\tdeviceKeyStore?: DeviceKeyStore\n}\n\n/**\n * Create a production-shaped Kora auth client with minimal setup.\n *\n * Defaults:\n * - browser/Tauri WebView: localStorage for tokens, IndexedDB for device keys\n * - desktop/mobile: pass `credentialStore` and optionally `deviceKeyStore`\n * - automatic device identity is enabled when a persistent device ID store exists\n */\nexport function createKoraAuth(options: CreateKoraAuthOptions): AuthClient {\n\tconst storage =\n\t\toptions.storage ??\n\t\t(options.credentialStore\n\t\t\t? createAuthTokenStorage({\n\t\t\t\t\tstore: options.credentialStore,\n\t\t\t\t\tprefix: options.storageKey,\n\t\t\t\t})\n\t\t\t: tryCreateDefaultTokenStorage(options.storageKey))\n\n\tconst deviceIdentity =\n\t\toptions.deviceIdentity === false\n\t\t\t? undefined\n\t\t\t: (options.deviceIdentity ??\n\t\t\t\ttryCreateDefaultDeviceIdentity(options.credentialStore, options.deviceKeyStore))\n\n\treturn new AuthClient({\n\t\tserverUrl: options.serverUrl,\n\t\tstorageKey: options.storageKey,\n\t\tstorage,\n\t\tfetch: options.fetch,\n\t\tdeviceIdentity,\n\t\trequestTimeoutMs: options.requestTimeoutMs,\n\t\tmaxOfflineGraceMs: options.maxOfflineGraceMs,\n\t\trefreshBackoff: options.refreshBackoff,\n\t})\n}\n\nfunction tryCreateDefaultTokenStorage(\n\tstorageKey: string | undefined,\n): AuthTokenStorage | undefined {\n\tconst storage = tryGetBrowserStorage()\n\treturn storage ? createWebStorageAuthTokenStorage(storage, storageKey) : undefined\n}\n\nfunction tryCreateDefaultDeviceIdentity(\n\tcredentialStore: AuthKeyValueStorage | undefined,\n\tdeviceKeyStore: DeviceKeyStore | undefined,\n): AuthDeviceIdentityProvider | undefined {\n\tconst storage = credentialStore ?? tryGetBrowserStorage()\n\tif (!storage) {\n\t\treturn undefined\n\t}\n\n\ttry {\n\t\treturn createPersistentDeviceIdentity({\n\t\t\tstorage,\n\t\t\tkeyStore: deviceKeyStore,\n\t\t})\n\t} catch {\n\t\treturn undefined\n\t}\n}\n\nfunction tryGetBrowserStorage(): Storage | null {\n\ttry {\n\t\tif (typeof globalThis.localStorage === 'undefined') {\n\t\t\treturn null\n\t\t}\n\t\tconst key = '__kora_auth_quickstart_test__'\n\t\tglobalThis.localStorage.setItem(key, '1')\n\t\tglobalThis.localStorage.removeItem(key)\n\t\treturn globalThis.localStorage\n\t} catch {\n\t\treturn null\n\t}\n}\n","import {\n\ttype SchemaDefinition,\n\ttype ScopeMap,\n\tbuildScopeMap,\n\textractScopeValuesFromClaims,\n} from '@korajs/core'\nimport type { AuthSyncBinding, AuthSyncState } from '@korajs/core/bindings'\nimport type { AuthClientSession, AuthState } from './auth-client'\n\n/**\n * Minimal auth client surface required for sync integration.\n * Matches {@link AuthClient} without importing implementation details.\n */\nexport interface AuthSyncClient {\n\tgetAccessToken(): Promise<string | null>\n\t/**\n\t * Force a refresh even if the cached token looks valid locally. Called after\n\t * the sync server ends a session with `AUTH_EXPIRED` / `AUTH_REVOKED`.\n\t */\n\trefreshAccessToken?(): Promise<string | null>\n\treadonly state?: AuthState\n\tonAuthChange?(callback: (state: AuthState) => void): () => void\n\t/** Stored session freshness (authenticated-offline support). */\n\treadonly session?: AuthClientSession | null\n\t/** Unverified claims of the stored credentials, available offline. */\n\tgetStoredClaims?(): Promise<Record<string, unknown> | null>\n\t/** Notifies when session freshness changes (fresh, offline, locked). */\n\tonSessionChange?(callback: (session: AuthClientSession | null) => void): () => void\n}\n\n/**\n * Sync binding returned by {@link createKoraAuthSync}.\n * Passed to `createApp({ sync: { authClient } })` in korajs.\n *\n * @deprecated Use {@link AuthSyncBinding} from `@korajs/core/bindings` or `@korajs/auth`.\n */\nexport type KoraAuthSyncBinding = AuthSyncBinding\n\n/**\n * Configuration for {@link createKoraAuthSync}.\n */\nexport interface CreateKoraAuthSyncOptions {\n\t/** Kora auth client from `createKoraAuth()`. */\n\tauthClient: AuthSyncClient\n\t/**\n\t * Application schema. When provided, a client-side scope hint is built from\n\t * token claims and schema scope declarations. The server only uses it to\n\t * NARROW its own grant; it never authorizes anything.\n\t */\n\tschema?: SchemaDefinition\n\t/**\n\t * Custom claim → flat scope value mapping.\n\t * Defaults to {@link extractScopeValuesFromClaims}.\n\t */\n\tscopeFromClaims?: (claims: Record<string, unknown>) => Record<string, unknown>\n\t/** Signed-out behavior. Authenticated-only sync is suspended by default. */\n\tanonymous?: 'suspend' | 'allow'\n}\n\n/**\n * Decode JWT payload without signature verification (client-side scope hints only).\n */\nfunction decodeJwtPayload(token: string): Record<string, unknown> | null {\n\tconst parts = token.split('.')\n\tif (parts.length !== 3) {\n\t\treturn null\n\t}\n\n\tconst payloadSegment = parts[1]\n\tif (payloadSegment === undefined) {\n\t\treturn null\n\t}\n\n\ttry {\n\t\tconst base64 = payloadSegment.replace(/-/g, '+').replace(/_/g, '/')\n\t\tconst padded = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), '=')\n\t\tconst json =\n\t\t\ttypeof atob === 'function' ? atob(padded) : Buffer.from(padded, 'base64').toString('utf-8')\n\t\tconst parsed: unknown = JSON.parse(json)\n\t\tif (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n\t\t\treturn null\n\t\t}\n\t\treturn parsed as Record<string, unknown>\n\t} catch {\n\t\treturn null\n\t}\n}\n\nfunction readString(claims: Record<string, unknown> | null, key: string): string | undefined {\n\tconst value = claims?.[key]\n\treturn typeof value === 'string' && value.length > 0 ? value : undefined\n}\n\n/**\n * Creates a sync auth binding for `createApp({ sync: { authClient: binding } })`.\n *\n * Wires token refresh, a client-side scope hint, and device-bound sync node ids\n * (`dev` claim) separate from the user id (`sub`).\n *\n * Offline-first: identity (user id, device id) comes from the stored session,\n * not from token freshness. While the auth server is unreachable the binding\n * keeps reporting the signed-in user (authenticated-offline, `token: null`), so\n * the user's own local database opens and the app stays usable; only the sync\n * transport waits for a fresh token.\n *\n * @example\n * ```typescript\n * import { createKoraAuth, createKoraAuthSync } from '@korajs/auth'\n * import { createApp } from 'korajs'\n *\n * const authClient = createKoraAuth({ serverUrl: 'https://api.example.com' })\n *\n * const app = createApp({\n * schema,\n * sync: {\n * url: 'wss://api.example.com/kora-sync',\n * authClient: createKoraAuthSync({ authClient, schema }),\n * },\n * })\n * ```\n */\nexport function createKoraAuthSync(options: CreateKoraAuthSyncOptions): AuthSyncBinding {\n\tconst { authClient, schema, scopeFromClaims, anonymous = 'suspend' } = options\n\n\t/**\n\t * Claims of the session: from a fresh token when one can be minted, else from\n\t * the stored credentials while the client is still signed in.\n\t */\n\tconst resolveClaims = async (): Promise<{\n\t\tclaims: Record<string, unknown> | null\n\t\ttoken: string | null\n\t}> => {\n\t\tconst token = await authClient.getAccessToken()\n\t\tif (token) return { claims: decodeJwtPayload(token), token }\n\t\tif (authClient.state === 'unauthenticated' || !authClient.getStoredClaims) {\n\t\t\treturn { claims: null, token: null }\n\t\t}\n\t\treturn { claims: await authClient.getStoredClaims(), token: null }\n\t}\n\n\tconst binding: AuthSyncBinding = {\n\t\tauth: async (request) => {\n\t\t\t// After the server ended the session for an expired or revoked credential,\n\t\t\t// the cached token is known bad: refresh instead of re-presenting it.\n\t\t\tconst token =\n\t\t\t\trequest?.forceRefresh && authClient.refreshAccessToken\n\t\t\t\t\t? await authClient.refreshAccessToken()\n\t\t\t\t\t: await authClient.getAccessToken()\n\t\t\treturn { token: token ?? '' }\n\t\t},\n\t}\n\n\tbinding.resolveSyncState = async (): Promise<AuthSyncState> => {\n\t\tif (authClient.state === 'loading') return { state: 'loading' }\n\t\tconst { claims, token } = await resolveClaims()\n\t\tconst userId = readString(claims, 'sub')\n\t\tif (!userId) {\n\t\t\treturn anonymous === 'allow'\n\t\t\t\t? { state: 'anonymous', mayConnectAnonymously: true }\n\t\t\t\t: { state: 'signed-out', mayConnectAnonymously: false }\n\t\t}\n\t\tconst deviceId = readString(claims, 'dev')\n\t\tif (token) {\n\t\t\treturn { state: 'authenticated', userId, token, ...(deviceId ? { deviceId } : {}) }\n\t\t}\n\t\treturn {\n\t\t\tstate: 'authenticated',\n\t\t\tuserId,\n\t\t\ttoken: null,\n\t\t\toffline: true,\n\t\t\tlocked: authClient.session?.status === 'locked',\n\t\t\t...(deviceId ? { deviceId } : {}),\n\t\t}\n\t}\n\n\tif (schema) {\n\t\tbinding.resolveScopeMap = async () => {\n\t\t\tconst { claims } = await resolveClaims()\n\t\t\tif (!claims) {\n\t\t\t\treturn undefined\n\t\t\t}\n\n\t\t\tconst scopeValues = scopeFromClaims\n\t\t\t\t? scopeFromClaims(claims)\n\t\t\t\t: extractScopeValuesFromClaims(schema, claims)\n\n\t\t\treturn buildScopeMap(schema, scopeValues) as ScopeMap\n\t\t}\n\t}\n\n\tbinding.resolveNodeId = async () => {\n\t\tconst { claims } = await resolveClaims()\n\t\treturn readString(claims, 'dev')\n\t}\n\n\tbinding.resolveUserId = async () => {\n\t\tconst { claims } = await resolveClaims()\n\t\treturn readString(claims, 'sub')\n\t}\n\n\tif (authClient.onAuthChange || authClient.onSessionChange) {\n\t\tbinding.subscribe = (listener) => {\n\t\t\tconst offAuth = authClient.onAuthChange?.(() => listener()) ?? (() => {})\n\t\t\tconst offSession = authClient.onSessionChange?.(() => listener()) ?? (() => {})\n\t\t\treturn () => {\n\t\t\t\toffAuth()\n\t\t\t\toffSession()\n\t\t\t}\n\t\t}\n\t}\n\n\treturn binding\n}\n","import { KoraError } from '@korajs/core'\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Organization info returned by the server.\n */\nexport interface ClientOrganization {\n\tid: string\n\tname: string\n\tslug: string\n\townerId: string\n\tcreatedAt: number\n\tupdatedAt: number\n\tmetadata: Record<string, unknown>\n}\n\n/**\n * Membership info returned by the server.\n */\nexport interface ClientMembership {\n\tid: string\n\torgId: string\n\tuserId: string\n\trole: string\n\tinvitedBy: string | null\n\tjoinedAt: number\n}\n\n/**\n * Invitation info returned by the server.\n */\nexport interface ClientInvitation {\n\tid: string\n\torgId: string\n\temail: string\n\trole: string\n\tinvitedBy: string\n\ttoken: string\n\tcreatedAt: number\n\texpiresAt: number\n\tstatus: string\n}\n\n/**\n * Configuration for the OrgClient.\n */\nexport interface OrgClientConfig {\n\t/** Base URL of the auth/org server */\n\tserverUrl: string\n\t/** Function that returns a valid access token for authenticated requests */\n\tgetAccessToken: () => Promise<string | null>\n}\n\n/**\n * Thrown when an org operation fails.\n */\nexport class OrgClientError extends KoraError {\n\tconstructor(message: string, code: string, context?: Record<string, unknown>) {\n\t\tsuper(message, code, context)\n\t\tthis.name = 'OrgClientError'\n\t}\n}\n\n// ============================================================================\n// OrgClient\n// ============================================================================\n\n/**\n * Client-side organization manager.\n *\n * Handles org CRUD, member management, invitations, and active org switching.\n * Framework-agnostic — works in any JavaScript environment with `fetch`.\n *\n * @example\n * ```typescript\n * const orgClient = new OrgClient({\n * serverUrl: 'http://localhost:3001',\n * getAccessToken: () => authClient.getAccessToken(),\n * })\n *\n * const org = await orgClient.createOrg({ name: 'Acme Inc', slug: 'acme' })\n * orgClient.switchOrg(org.id)\n * ```\n */\nexport class OrgClient {\n\tprivate readonly serverUrl: string\n\tprivate readonly getAccessToken: () => Promise<string | null>\n\tprivate readonly listeners = new Set<(orgId: string | null) => void>()\n\n\tprivate _activeOrgId: string | null = null\n\tprivate _activeOrg: ClientOrganization | null = null\n\tprivate _activeRole: string | null = null\n\n\tconstructor(config: OrgClientConfig) {\n\t\tthis.serverUrl = config.serverUrl.replace(/\\/+$/, '')\n\t\tthis.getAccessToken = config.getAccessToken\n\t}\n\n\t// --- Getters ---\n\n\t/** Currently active organization ID */\n\tget activeOrgId(): string | null {\n\t\treturn this._activeOrgId\n\t}\n\n\t/** Currently active organization */\n\tget activeOrg(): ClientOrganization | null {\n\t\treturn this._activeOrg\n\t}\n\n\t/** Current user's role in the active organization */\n\tget activeRole(): string | null {\n\t\treturn this._activeRole\n\t}\n\n\t// --- Organization Operations ---\n\n\t/**\n\t * Create a new organization.\n\t */\n\tasync createOrg(params: {\n\t\tname: string\n\t\tslug?: string\n\t\tmetadata?: Record<string, unknown>\n\t}): Promise<ClientOrganization> {\n\t\treturn this.request<ClientOrganization>('/orgs', {\n\t\t\tmethod: 'POST',\n\t\t\tbody: params,\n\t\t})\n\t}\n\n\t/**\n\t * List all organizations the current user belongs to.\n\t */\n\tasync listOrgs(): Promise<ClientOrganization[]> {\n\t\treturn this.request<ClientOrganization[]>('/orgs', { method: 'GET' })\n\t}\n\n\t/**\n\t * Get an organization by ID.\n\t */\n\tasync getOrg(orgId: string): Promise<ClientOrganization> {\n\t\treturn this.request<ClientOrganization>(`/orgs/${orgId}`, { method: 'GET' })\n\t}\n\n\t/**\n\t * Update an organization.\n\t */\n\tasync updateOrg(\n\t\torgId: string,\n\t\tparams: { name?: string; slug?: string; metadata?: Record<string, unknown> },\n\t): Promise<ClientOrganization> {\n\t\tconst result = await this.request<ClientOrganization>(`/orgs/${orgId}`, {\n\t\t\tmethod: 'PATCH',\n\t\t\tbody: params,\n\t\t})\n\t\t// Update cached active org if this is the active one\n\t\tif (this._activeOrgId === orgId) {\n\t\t\tthis._activeOrg = result\n\t\t}\n\t\treturn result\n\t}\n\n\t/**\n\t * Delete an organization.\n\t */\n\tasync deleteOrg(orgId: string): Promise<void> {\n\t\tawait this.request(`/orgs/${orgId}`, { method: 'DELETE' })\n\t\tif (this._activeOrgId === orgId) {\n\t\t\tthis._activeOrgId = null\n\t\t\tthis._activeOrg = null\n\t\t\tthis._activeRole = null\n\t\t\tthis.notifyListeners()\n\t\t}\n\t}\n\n\t// --- Org Switching ---\n\n\t/**\n\t * Switch the active organization context.\n\t * Fetches the org details and the user's membership/role.\n\t */\n\tasync switchOrg(orgId: string): Promise<void> {\n\t\tconst org = await this.request<ClientOrganization>(`/orgs/${orgId}`, { method: 'GET' })\n\t\tconst membership = await this.request<ClientMembership>(`/orgs/${orgId}/membership`, {\n\t\t\tmethod: 'GET',\n\t\t})\n\n\t\tthis._activeOrgId = orgId\n\t\tthis._activeOrg = org\n\t\tthis._activeRole = membership.role\n\t\tthis.notifyListeners()\n\t}\n\n\t/**\n\t * Clear the active organization (no org selected).\n\t */\n\tclearActiveOrg(): void {\n\t\tthis._activeOrgId = null\n\t\tthis._activeOrg = null\n\t\tthis._activeRole = null\n\t\tthis.notifyListeners()\n\t}\n\n\t// --- Member Management ---\n\n\t/**\n\t * List members of an organization.\n\t */\n\tasync listMembers(orgId: string): Promise<ClientMembership[]> {\n\t\treturn this.request<ClientMembership[]>(`/orgs/${orgId}/members`, { method: 'GET' })\n\t}\n\n\t/**\n\t * Remove a member from an organization.\n\t */\n\tasync removeMember(orgId: string, userId: string): Promise<void> {\n\t\tawait this.request(`/orgs/${orgId}/members/${userId}`, { method: 'DELETE' })\n\t}\n\n\t/**\n\t * Update a member's role.\n\t */\n\tasync updateMemberRole(orgId: string, userId: string, role: string): Promise<ClientMembership> {\n\t\treturn this.request<ClientMembership>(`/orgs/${orgId}/members/${userId}/role`, {\n\t\t\tmethod: 'PATCH',\n\t\t\tbody: { role },\n\t\t})\n\t}\n\n\t/**\n\t * Transfer ownership to another member.\n\t */\n\tasync transferOwnership(orgId: string, newOwnerId: string): Promise<void> {\n\t\tawait this.request(`/orgs/${orgId}/transfer`, {\n\t\t\tmethod: 'POST',\n\t\t\tbody: { newOwnerId },\n\t\t})\n\t}\n\n\t/**\n\t * Leave an organization (remove yourself).\n\t */\n\tasync leaveOrg(orgId: string): Promise<void> {\n\t\tawait this.request(`/orgs/${orgId}/leave`, { method: 'POST' })\n\t\tif (this._activeOrgId === orgId) {\n\t\t\tthis._activeOrgId = null\n\t\t\tthis._activeOrg = null\n\t\t\tthis._activeRole = null\n\t\t\tthis.notifyListeners()\n\t\t}\n\t}\n\n\t// --- Invitations ---\n\n\t/**\n\t * Invite a user to an organization by email.\n\t */\n\tasync inviteMember(\n\t\torgId: string,\n\t\tparams: { email: string; role: string },\n\t): Promise<ClientInvitation> {\n\t\treturn this.request<ClientInvitation>(`/orgs/${orgId}/invitations`, {\n\t\t\tmethod: 'POST',\n\t\t\tbody: params,\n\t\t})\n\t}\n\n\t/**\n\t * Accept an invitation by token.\n\t */\n\tasync acceptInvitation(token: string): Promise<ClientMembership> {\n\t\treturn this.request<ClientMembership>('/invitations/accept', {\n\t\t\tmethod: 'POST',\n\t\t\tbody: { token },\n\t\t})\n\t}\n\n\t/**\n\t * List pending invitations for an organization.\n\t */\n\tasync listInvitations(orgId: string): Promise<ClientInvitation[]> {\n\t\treturn this.request<ClientInvitation[]>(`/orgs/${orgId}/invitations`, { method: 'GET' })\n\t}\n\n\t/**\n\t * Revoke a pending invitation.\n\t */\n\tasync revokeInvitation(orgId: string, invitationId: string): Promise<void> {\n\t\tawait this.request(`/orgs/${orgId}/invitations/${invitationId}`, { method: 'DELETE' })\n\t}\n\n\t/**\n\t * List pending invitations addressed to the signed-in user's verified email.\n\t * The server resolves the email from the session; tokens are never returned.\n\t */\n\tasync listMyInvitations(): Promise<Array<Omit<ClientInvitation, 'token'>>> {\n\t\treturn this.request<Array<Omit<ClientInvitation, 'token'>>>('/invitations', {\n\t\t\tmethod: 'GET',\n\t\t})\n\t}\n\n\t// --- Subscriptions ---\n\n\t/**\n\t * Subscribe to active org changes.\n\t * @returns Unsubscribe function\n\t */\n\tonOrgChange(callback: (orgId: string | null) => void): () => void {\n\t\tthis.listeners.add(callback)\n\t\treturn () => {\n\t\t\tthis.listeners.delete(callback)\n\t\t}\n\t}\n\n\t// --- Internal ---\n\n\tprivate notifyListeners(): void {\n\t\tfor (const listener of this.listeners) {\n\t\t\ttry {\n\t\t\t\tlistener(this._activeOrgId)\n\t\t\t} catch {\n\t\t\t\t// Don't let listener errors break the notification loop\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async request<T = void>(\n\t\tpath: string,\n\t\toptions: {\n\t\t\tmethod: 'GET' | 'POST' | 'PATCH' | 'DELETE'\n\t\t\tbody?: Record<string, unknown>\n\t\t},\n\t): Promise<T> {\n\t\tconst token = await this.getAccessToken()\n\t\tif (!token) {\n\t\t\tthrow new OrgClientError(\n\t\t\t\t'Not authenticated. Sign in before performing organization operations.',\n\t\t\t\t'ORG_NOT_AUTHENTICATED',\n\t\t\t)\n\t\t}\n\n\t\tconst url = `${this.serverUrl}${path}`\n\t\tconst headers: Record<string, string> = {\n\t\t\tAuthorization: `Bearer ${token}`,\n\t\t}\n\t\tif (options.body) {\n\t\t\theaders['Content-Type'] = 'application/json'\n\t\t}\n\n\t\tlet response: Response\n\t\ttry {\n\t\t\tresponse = await fetch(url, {\n\t\t\t\tmethod: options.method,\n\t\t\t\theaders,\n\t\t\t\tbody: options.body ? JSON.stringify(options.body) : undefined,\n\t\t\t})\n\t\t} catch (cause) {\n\t\t\tthrow new OrgClientError(`Network request to ${path} failed.`, 'ORG_NETWORK_ERROR', {\n\t\t\t\tpath,\n\t\t\t\tcause: cause instanceof Error ? cause.message : String(cause),\n\t\t\t})\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tlet errorMessage = `Server returned HTTP ${response.status}`\n\t\t\ttry {\n\t\t\t\tconst body = (await response.json()) as Record<string, unknown>\n\t\t\t\tif (typeof body.error === 'string') {\n\t\t\t\t\terrorMessage = body.error as string\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// not JSON\n\t\t\t}\n\t\t\tthrow new OrgClientError(errorMessage, 'ORG_SERVER_ERROR', {\n\t\t\t\tpath,\n\t\t\t\tstatus: response.status,\n\t\t\t})\n\t\t}\n\n\t\t// Handle empty responses (DELETE, etc.)\n\t\tconst text = await response.text()\n\t\tif (text.length === 0) return undefined as T\n\n\t\ttry {\n\t\t\tconst body = JSON.parse(text)\n\t\t\t// Unwrap { data: ... } envelope if present\n\t\t\tif (body && typeof body === 'object' && 'data' in body) {\n\t\t\t\treturn body.data as T\n\t\t\t}\n\t\t\treturn body as T\n\t\t} catch {\n\t\t\treturn undefined as T\n\t\t}\n\t}\n}\n","import type { AuthTokens } from '../types'\n\n/** Default key used for localStorage persistence. */\nconst DEFAULT_STORAGE_KEY = 'kora_auth_tokens'\n\n/**\n * Minimal storage interface matching the subset of the Web Storage API\n * that TokenStore needs. This allows both localStorage and in-memory\n * implementations to be used interchangeably.\n */\ninterface SimpleStorage {\n\tgetItem(key: string): string | null\n\tsetItem(key: string, value: string): void\n\tremoveItem(key: string): void\n}\n\n/**\n * In-memory storage fallback used when localStorage is unavailable\n * (e.g., in Node.js, SSR environments, or when storage access is denied).\n */\nclass MemoryStorage implements SimpleStorage {\n\tprivate store = new Map<string, string>()\n\n\tgetItem(key: string): string | null {\n\t\treturn this.store.get(key) ?? null\n\t}\n\n\tsetItem(key: string, value: string): void {\n\t\tthis.store.set(key, value)\n\t}\n\n\tremoveItem(key: string): void {\n\t\tthis.store.delete(key)\n\t}\n}\n\n/**\n * Attempts to access localStorage. Returns null if unavailable.\n *\n * localStorage may be unavailable in several scenarios:\n * - Node.js / server-side rendering (no window object)\n * - Private browsing modes with restricted storage\n * - iframe sandboxing without storage access\n * - User has disabled cookies/storage in browser settings\n */\nfunction tryGetLocalStorage(): SimpleStorage | null {\n\ttry {\n\t\tif (typeof globalThis !== 'undefined' && 'localStorage' in globalThis) {\n\t\t\tconst storage = globalThis.localStorage as SimpleStorage\n\t\t\t// Probe that read/write actually works (some browsers throw on access)\n\t\t\tconst testKey = '__kora_storage_test__'\n\t\t\tstorage.setItem(testKey, '1')\n\t\t\tstorage.removeItem(testKey)\n\t\t\treturn storage\n\t\t}\n\t} catch {\n\t\t// localStorage exists but access is denied (e.g., private mode in some browsers)\n\t}\n\treturn null\n}\n\n/**\n * Client-side token storage for Kora auth tokens.\n *\n * Persists tokens to localStorage when available, falling back to in-memory\n * storage in environments where localStorage is unavailable (Node.js, SSR,\n * restricted browser contexts). Encrypted storage is planned for Phase 2.\n *\n * All operations are synchronous since both localStorage and in-memory\n * storage are synchronous.\n *\n * @example\n * ```typescript\n * const store = new TokenStore()\n *\n * // After login\n * store.saveTokens({ accessToken: '...', refreshToken: '...' })\n *\n * // Before API calls\n * const token = store.getAccessToken()\n *\n * // On logout\n * store.clearTokens()\n * ```\n */\nexport class TokenStore {\n\tprivate readonly storageKey: string\n\tprivate readonly storage: SimpleStorage\n\n\t/**\n\t * Creates a new TokenStore instance.\n\t *\n\t * @param storageKey - The key under which tokens are stored. Defaults to 'kora_auth_tokens'.\n\t * Use different keys if your app runs multiple Kora instances with separate auth.\n\t */\n\tconstructor(storageKey?: string) {\n\t\tthis.storageKey = storageKey ?? DEFAULT_STORAGE_KEY\n\t\tthis.storage = tryGetLocalStorage() ?? new MemoryStorage()\n\t}\n\n\t/**\n\t * Save tokens to persistent storage.\n\t *\n\t * Overwrites any previously stored tokens. The tokens are serialized\n\t * as JSON. Only the `accessToken`, `refreshToken`, and optional\n\t * `deviceCredential` fields are persisted.\n\t *\n\t * @param tokens - The token set to store\n\t */\n\tsaveTokens(tokens: AuthTokens): void {\n\t\tconst serialized: AuthTokens = {\n\t\t\taccessToken: tokens.accessToken,\n\t\t\trefreshToken: tokens.refreshToken,\n\t\t}\n\t\tif (tokens.deviceCredential !== undefined) {\n\t\t\tserialized.deviceCredential = tokens.deviceCredential\n\t\t}\n\t\tthis.storage.setItem(this.storageKey, JSON.stringify(serialized))\n\t}\n\n\t/**\n\t * Load tokens from storage.\n\t *\n\t * @returns The stored {@link AuthTokens}, or null if no tokens have been saved\n\t */\n\tloadTokens(): AuthTokens | null {\n\t\tconst raw = this.storage.getItem(this.storageKey)\n\t\tif (raw === null) {\n\t\t\treturn null\n\t\t}\n\n\t\ttry {\n\t\t\tconst parsed: unknown = JSON.parse(raw)\n\t\t\tif (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst record = parsed as Record<string, unknown>\n\n\t\t\t// Validate required fields are present and are strings\n\t\t\tif (typeof record.accessToken !== 'string' || typeof record.refreshToken !== 'string') {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst tokens: AuthTokens = {\n\t\t\t\taccessToken: record.accessToken,\n\t\t\t\trefreshToken: record.refreshToken,\n\t\t\t}\n\n\t\t\tif (typeof record.deviceCredential === 'string') {\n\t\t\t\ttokens.deviceCredential = record.deviceCredential\n\t\t\t}\n\n\t\t\treturn tokens\n\t\t} catch {\n\t\t\t// Corrupted data in storage; treat as empty\n\t\t\treturn null\n\t\t}\n\t}\n\n\t/**\n\t * Clear all stored tokens.\n\t *\n\t * Call this on logout to remove credentials from persistent storage.\n\t */\n\tclearTokens(): void {\n\t\tthis.storage.removeItem(this.storageKey)\n\t}\n\n\t/**\n\t * Get the current access token.\n\t *\n\t * Returns the raw token string without validating expiration.\n\t * The caller is responsible for checking whether the token is\n\t * still valid and initiating a refresh if needed.\n\t *\n\t * @returns The access token string, or null if no tokens are stored\n\t */\n\tgetAccessToken(): string | null {\n\t\tconst tokens = this.loadTokens()\n\t\treturn tokens?.accessToken ?? null\n\t}\n\n\t/**\n\t * Get the current refresh token.\n\t *\n\t * @returns The refresh token string, or null if no tokens are stored\n\t */\n\tgetRefreshToken(): string | null {\n\t\tconst tokens = this.loadTokens()\n\t\treturn tokens?.refreshToken ?? null\n\t}\n}\n","import { KoraError } from '@korajs/core'\nimport { decryptData, encryptData } from '../encryption/database-encryption'\nimport type { AuthTokens } from '../types'\n\n// --- Errors ---\n\n/**\n * Thrown when encrypted token storage operations fail.\n * Includes context about what went wrong to aid debugging without\n * exposing sensitive key or token material.\n */\nexport class EncryptedTokenStoreError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'ENCRYPTED_TOKEN_STORE_ERROR', context)\n\t\tthis.name = 'EncryptedTokenStoreError'\n\t}\n}\n\n// --- Encoding helpers ---\n\n/**\n * Encodes a Uint8Array as a base64url string (no padding).\n * Implemented locally to avoid coupling to the device-identity module.\n *\n * @param bytes - The binary data to encode\n * @returns A base64url-encoded string without padding characters\n */\nfunction toBase64Url(bytes: Uint8Array): string {\n\tlet binary = ''\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tbinary += String.fromCharCode(bytes[i] as number)\n\t}\n\treturn btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\n/**\n * Decodes a base64url string (no padding) into a Uint8Array.\n *\n * @param str - A base64url-encoded string (with or without padding)\n * @returns The decoded binary data as a Uint8Array\n */\nfunction fromBase64Url(str: string): Uint8Array {\n\tlet base64 = str.replace(/-/g, '+').replace(/_/g, '/')\n\tconst paddingNeeded = (4 - (base64.length % 4)) % 4\n\tbase64 += '='.repeat(paddingNeeded)\n\n\tconst binary = atob(base64)\n\tconst bytes = new Uint8Array(binary.length)\n\tfor (let i = 0; i < binary.length; i++) {\n\t\tbytes[i] = binary.charCodeAt(i)\n\t}\n\treturn bytes\n}\n\n// --- Storage helpers ---\n\n/**\n * Minimal storage interface matching the subset of the Web Storage API\n * that EncryptedTokenStore needs. Allows both localStorage and in-memory\n * implementations to be used interchangeably.\n */\ninterface SimpleStorage {\n\tgetItem(key: string): string | null\n\tsetItem(key: string, value: string): void\n\tremoveItem(key: string): void\n}\n\n/**\n * In-memory storage fallback used when localStorage is unavailable\n * (e.g., in Node.js, SSR environments, or when storage access is denied).\n */\nclass MemoryStorage implements SimpleStorage {\n\tprivate store = new Map<string, string>()\n\n\tgetItem(key: string): string | null {\n\t\treturn this.store.get(key) ?? null\n\t}\n\n\tsetItem(key: string, value: string): void {\n\t\tthis.store.set(key, value)\n\t}\n\n\tremoveItem(key: string): void {\n\t\tthis.store.delete(key)\n\t}\n}\n\n/**\n * Attempts to access localStorage. Returns null if unavailable.\n *\n * localStorage may be unavailable in several scenarios:\n * - Node.js / server-side rendering (no window object)\n * - Private browsing modes with restricted storage\n * - iframe sandboxing without storage access\n * - User has disabled cookies/storage in browser settings\n */\nfunction tryGetLocalStorage(): SimpleStorage | null {\n\ttry {\n\t\tif (typeof globalThis !== 'undefined' && 'localStorage' in globalThis) {\n\t\t\tconst storage = globalThis.localStorage as SimpleStorage\n\t\t\tconst testKey = '__kora_encrypted_storage_test__'\n\t\t\tstorage.setItem(testKey, '1')\n\t\t\tstorage.removeItem(testKey)\n\t\t\treturn storage\n\t\t}\n\t} catch {\n\t\t// localStorage exists but access is denied\n\t}\n\treturn null\n}\n\n// --- Types ---\n\n/** Default storage key prefix for encrypted tokens. */\nconst DEFAULT_STORAGE_KEY = 'kora_auth_encrypted'\n\n/**\n * The serialized format stored in localStorage.\n * Both `iv` and `data` are base64url-encoded strings.\n */\ninterface EncryptedPayload {\n\t/** Base64url-encoded initialization vector (12 bytes for AES-GCM) */\n\tiv: string\n\t/** Base64url-encoded AES-256-GCM ciphertext of the JSON-serialized AuthTokens */\n\tdata: string\n}\n\n/**\n * Configuration for the encrypted token store.\n */\nexport interface EncryptedTokenStoreConfig {\n\t/**\n\t * Storage key prefix. Defaults to 'kora_auth_encrypted'.\n\t * Use different keys if your app runs multiple Kora instances with separate auth.\n\t */\n\tstorageKey?: string\n\t/**\n\t * The AES-256-GCM CryptoKey used to encrypt and decrypt tokens.\n\t *\n\t * This can be:\n\t * - A key derived from a user passphrase via {@link deriveEncryptionKey}\n\t * - A device-derived key (e.g., from secure hardware or biometric unlock)\n\t * - A randomly generated key from {@link generateEncryptionKey}\n\t *\n\t * The caller is responsible for obtaining the key before constructing the store.\n\t */\n\tkey: CryptoKey\n}\n\n// --- Implementation ---\n\n/**\n * Encrypted token store that protects auth tokens at rest.\n *\n * Addresses the security vulnerability of storing tokens in plaintext localStorage,\n * which is accessible to any JavaScript running on the page (XSS attack surface).\n * Tokens are encrypted with AES-256-GCM before being written to storage.\n *\n * The stored format is a JSON string with two base64url-encoded fields:\n * - `iv`: the 12-byte initialization vector (unique per encryption)\n * - `data`: the AES-256-GCM ciphertext of the JSON-serialized tokens\n *\n * AES-GCM provides both confidentiality (tokens are unreadable without the key)\n * and integrity (tampered ciphertext is detected and rejected).\n *\n * @example\n * ```typescript\n * import { deriveEncryptionKey } from '@korajs/auth'\n *\n * // Derive a key from the user's passphrase\n * const { key } = await deriveEncryptionKey('user-passphrase', storedSalt)\n *\n * const store = new EncryptedTokenStore({ key })\n *\n * // After login: encrypt and persist tokens\n * await store.saveTokens({ accessToken: '...', refreshToken: '...' })\n *\n * // Before API calls: decrypt and retrieve\n * const accessToken = await store.getAccessToken()\n *\n * // On logout: remove encrypted data\n * store.clearTokens()\n * ```\n */\nexport class EncryptedTokenStore {\n\tprivate readonly storageKey: string\n\tprivate readonly key: CryptoKey\n\tprivate readonly storage: SimpleStorage\n\n\t/**\n\t * Creates a new EncryptedTokenStore instance.\n\t *\n\t * @param config - Configuration including the encryption key and optional storage key\n\t */\n\tconstructor(config: EncryptedTokenStoreConfig) {\n\t\tthis.storageKey = config.storageKey ?? DEFAULT_STORAGE_KEY\n\t\tthis.key = config.key\n\t\tthis.storage = tryGetLocalStorage() ?? new MemoryStorage()\n\t}\n\n\t/**\n\t * Encrypt and save tokens to persistent storage.\n\t *\n\t * Serializes the tokens as JSON, encrypts with AES-256-GCM using a fresh\n\t * random IV, then stores the result as a JSON object containing the\n\t * base64url-encoded IV and ciphertext.\n\t *\n\t * Overwrites any previously stored tokens.\n\t *\n\t * @param tokens - The token set to encrypt and store\n\t * @throws {EncryptedTokenStoreError} If encryption fails\n\t */\n\tasync saveTokens(tokens: AuthTokens): Promise<void> {\n\t\t// Build a clean token object with only the expected fields\n\t\tconst serialized: AuthTokens = {\n\t\t\taccessToken: tokens.accessToken,\n\t\t\trefreshToken: tokens.refreshToken,\n\t\t}\n\t\tif (tokens.deviceCredential !== undefined) {\n\t\t\tserialized.deviceCredential = tokens.deviceCredential\n\t\t}\n\n\t\tconst plaintext = new TextEncoder().encode(JSON.stringify(serialized))\n\n\t\ttry {\n\t\t\tconst { ciphertext, iv } = await encryptData(this.key, plaintext)\n\n\t\t\tconst payload: EncryptedPayload = {\n\t\t\t\tiv: toBase64Url(iv),\n\t\t\t\tdata: toBase64Url(ciphertext),\n\t\t\t}\n\n\t\t\tthis.storage.setItem(this.storageKey, JSON.stringify(payload))\n\t\t} catch (cause) {\n\t\t\t// Re-throw EncryptedTokenStoreError as-is\n\t\t\tif (cause instanceof EncryptedTokenStoreError) {\n\t\t\t\tthrow cause\n\t\t\t}\n\n\t\t\tthrow new EncryptedTokenStoreError(\n\t\t\t\t'Failed to encrypt and save auth tokens. ' +\n\t\t\t\t\t'Ensure the encryption key is a valid AES-256-GCM CryptoKey.',\n\t\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Load and decrypt tokens from storage.\n\t *\n\t * Reads the encrypted payload from localStorage, decodes the base64url IV\n\t * and ciphertext, decrypts with AES-256-GCM, and parses the resulting JSON.\n\t *\n\t * Returns null (without throwing) if:\n\t * - No tokens have been saved\n\t * - The stored data is corrupted or not valid JSON\n\t * - Decryption fails (wrong key, tampered ciphertext, or wrong IV)\n\t * - The decrypted data does not contain valid token fields\n\t *\n\t * This fail-silent design prevents decryption errors from crashing the\n\t * application. The caller should treat null as \"no valid tokens available\"\n\t * and initiate a re-authentication flow.\n\t *\n\t * @returns The decrypted {@link AuthTokens}, or null if unavailable or decryption fails\n\t */\n\tasync loadTokens(): Promise<AuthTokens | null> {\n\t\tconst raw = this.storage.getItem(this.storageKey)\n\t\tif (raw === null) {\n\t\t\treturn null\n\t\t}\n\n\t\ttry {\n\t\t\t// Parse the stored encrypted payload\n\t\t\tconst parsed: unknown = JSON.parse(raw)\n\t\t\tif (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst record = parsed as Record<string, unknown>\n\t\t\tif (typeof record.iv !== 'string' || typeof record.data !== 'string') {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\t// Decode the base64url-encoded IV and ciphertext\n\t\t\tconst iv = fromBase64Url(record.iv)\n\t\t\tconst ciphertext = fromBase64Url(record.data)\n\n\t\t\t// Decrypt with AES-256-GCM\n\t\t\tconst plaintextBytes = await decryptData(this.key, ciphertext, iv)\n\t\t\tconst json = new TextDecoder().decode(plaintextBytes)\n\n\t\t\t// Parse the decrypted JSON and validate token structure\n\t\t\tconst tokenData: unknown = JSON.parse(json)\n\t\t\tif (typeof tokenData !== 'object' || tokenData === null || Array.isArray(tokenData)) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst tokenRecord = tokenData as Record<string, unknown>\n\t\t\tif (\n\t\t\t\ttypeof tokenRecord.accessToken !== 'string' ||\n\t\t\t\ttypeof tokenRecord.refreshToken !== 'string'\n\t\t\t) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst tokens: AuthTokens = {\n\t\t\t\taccessToken: tokenRecord.accessToken,\n\t\t\t\trefreshToken: tokenRecord.refreshToken,\n\t\t\t}\n\n\t\t\tif (typeof tokenRecord.deviceCredential === 'string') {\n\t\t\t\ttokens.deviceCredential = tokenRecord.deviceCredential\n\t\t\t}\n\n\t\t\treturn tokens\n\t\t} catch {\n\t\t\t// Decryption failure, corrupted data, or JSON parse error.\n\t\t\t// Return null instead of throwing to allow graceful fallback\n\t\t\t// to re-authentication.\n\t\t\treturn null\n\t\t}\n\t}\n\n\t/**\n\t * Clear all stored encrypted tokens.\n\t *\n\t * Removes the encrypted payload from storage. This is a synchronous\n\t * operation since it only removes the localStorage entry.\n\t *\n\t * Call this on logout to ensure no encrypted credential material\n\t * remains in persistent storage.\n\t */\n\tclearTokens(): void {\n\t\tthis.storage.removeItem(this.storageKey)\n\t}\n\n\t/**\n\t * Get the current access token by decrypting stored tokens.\n\t *\n\t * Returns the raw token string without validating expiration.\n\t * The caller is responsible for checking whether the token is\n\t * still valid and initiating a refresh if needed.\n\t *\n\t * @returns The decrypted access token string, or null if no valid tokens are stored\n\t */\n\tasync getAccessToken(): Promise<string | null> {\n\t\tconst tokens = await this.loadTokens()\n\t\treturn tokens?.accessToken ?? null\n\t}\n\n\t/**\n\t * Get the current refresh token by decrypting stored tokens.\n\t *\n\t * @returns The decrypted refresh token string, or null if no valid tokens are stored\n\t */\n\tasync getRefreshToken(): Promise<string | null> {\n\t\tconst tokens = await this.loadTokens()\n\t\treturn tokens?.refreshToken ?? null\n\t}\n}\n","import { KoraError } from '@korajs/core'\n\n// --- Key derivation errors ---\n\n/**\n * Thrown when a key derivation operation fails.\n */\nexport class KeyDerivationError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'KEY_DERIVATION_ERROR', context)\n\t\tthis.name = 'KeyDerivationError'\n\t}\n}\n\n// --- Internal helpers ---\n\n/** Salt length in bytes. 32 bytes (256 bits) provides sufficient randomness. */\nconst SALT_LENGTH = 32\n\n/**\n * PBKDF2 iteration count. 600,000 iterations is the OWASP-recommended minimum\n * for SHA-256 as of 2024, providing strong resistance against brute-force attacks.\n */\nconst PBKDF2_ITERATIONS = 600_000\n\n/** Derived key length in bits (AES-256). */\nconst DERIVED_KEY_LENGTH = 256\n\n/**\n * Asserts that `crypto.subtle` is available, throwing a clear error if not.\n */\nfunction assertCryptoAvailable(): void {\n\tif (typeof globalThis.crypto === 'undefined' || typeof globalThis.crypto.subtle === 'undefined') {\n\t\tthrow new KeyDerivationError(\n\t\t\t'Web Crypto API (crypto.subtle) is not available in this environment. ' +\n\t\t\t\t'Key derivation requires crypto.subtle, which is available in modern browsers and Node.js 20+.',\n\t\t)\n\t}\n}\n\n// --- Public API ---\n\n/**\n * Generates a cryptographically random 32-byte salt for key derivation.\n *\n * Each call returns a unique salt. The salt should be stored alongside the\n * encrypted data so that the same passphrase can reproduce the same key later.\n *\n * @returns A random 32-byte Uint8Array\n *\n * @example\n * ```typescript\n * const salt = generateSalt()\n * // salt.length === 32\n * ```\n */\nexport function generateSalt(): Uint8Array {\n\tif (typeof globalThis.crypto === 'undefined') {\n\t\tthrow new KeyDerivationError(\n\t\t\t'Web Crypto API (crypto) is not available. Cannot generate random salt.',\n\t\t)\n\t}\n\n\treturn globalThis.crypto.getRandomValues(new Uint8Array(SALT_LENGTH))\n}\n\n/**\n * Derives an AES-256-GCM encryption key from a passphrase using PBKDF2.\n *\n * Uses PBKDF2 with SHA-256 and 600,000 iterations (OWASP-recommended minimum)\n * to derive a 256-bit key from the passphrase. The derived key can be used with\n * the database encryption functions ({@link encryptData}, {@link decryptData}).\n *\n * If no salt is provided, a random 32-byte salt is generated. The salt must be\n * persisted alongside the encrypted data so the key can be re-derived later.\n *\n * **Deterministic:** The same passphrase and salt always produce the same key.\n * This is essential for decryption: the user enters their passphrase, the stored\n * salt is used, and the identical key is re-derived to decrypt the data.\n *\n * @param passphrase - The user's passphrase (any non-empty string)\n * @param salt - Optional salt bytes. If omitted, a random 32-byte salt is generated.\n * @returns An object containing the derived CryptoKey and the salt used\n * @throws {KeyDerivationError} If the passphrase is empty, crypto is unavailable, or derivation fails\n *\n * @example\n * ```typescript\n * // First time: derive key with a new salt\n * const { key, salt } = await deriveEncryptionKey('my-secure-passphrase')\n * // Store the salt alongside the encrypted data\n *\n * // Later: re-derive the same key using the stored salt\n * const { key: sameKey } = await deriveEncryptionKey('my-secure-passphrase', salt)\n * ```\n */\nexport async function deriveEncryptionKey(\n\tpassphrase: string,\n\tsalt?: Uint8Array,\n): Promise<{ key: CryptoKey; salt: Uint8Array }> {\n\tassertCryptoAvailable()\n\n\tif (passphrase.length === 0) {\n\t\tthrow new KeyDerivationError(\n\t\t\t'Passphrase must not be empty. Provide a non-empty string for key derivation.',\n\t\t)\n\t}\n\n\tconst usedSalt = salt ?? generateSalt()\n\n\ttry {\n\t\t// Step 1: Import the passphrase as raw key material for PBKDF2\n\t\tconst passphraseBytes = new TextEncoder().encode(passphrase)\n\t\tconst baseKey = await globalThis.crypto.subtle.importKey(\n\t\t\t'raw',\n\t\t\tpassphraseBytes,\n\t\t\t'PBKDF2',\n\t\t\tfalse,\n\t\t\t['deriveBits', 'deriveKey'],\n\t\t)\n\n\t\t// Step 2: Derive an AES-256-GCM key using PBKDF2 with SHA-256\n\t\tconst derivedKey = await globalThis.crypto.subtle.deriveKey(\n\t\t\t{\n\t\t\t\tname: 'PBKDF2',\n\t\t\t\tsalt: usedSalt as unknown as ArrayBuffer,\n\t\t\t\titerations: PBKDF2_ITERATIONS,\n\t\t\t\thash: 'SHA-256',\n\t\t\t},\n\t\t\tbaseKey,\n\t\t\t{ name: 'AES-GCM', length: DERIVED_KEY_LENGTH },\n\t\t\t// extractable: true so the derived key can be exported if needed\n\t\t\ttrue,\n\t\t\t['encrypt', 'decrypt'],\n\t\t)\n\n\t\treturn { key: derivedKey, salt: usedSalt }\n\t} catch (cause) {\n\t\t// Re-throw KeyDerivationError as-is (e.g., from assertCryptoAvailable)\n\t\tif (cause instanceof KeyDerivationError) {\n\t\t\tthrow cause\n\t\t}\n\n\t\tthrow new KeyDerivationError(\n\t\t\t'Failed to derive encryption key from passphrase using PBKDF2. ' +\n\t\t\t\t'Ensure the runtime supports PBKDF2 with SHA-256.',\n\t\t\t{ cause: cause instanceof Error ? cause.message : String(cause) },\n\t\t)\n\t}\n}\n","/**\n * Configuration for the AutoLockManager.\n */\nexport interface AutoLockConfig {\n\t/** Inactivity timeout in milliseconds before auto-locking. */\n\ttimeout: number\n\t/** Callback invoked when the lock engages (either by timeout or manual lock). */\n\tonLock: () => void\n}\n\n/**\n * Manages inactivity-based auto-locking for the encrypted local store.\n *\n * The AutoLockManager starts an inactivity timer when {@link start} is called.\n * If no user activity is reported via {@link reportActivity} within the configured\n * timeout, the manager transitions to the locked state and invokes the `onLock`\n * callback.\n *\n * This class has no DOM dependencies. It uses `setTimeout` and `clearTimeout` for\n * timing and accepts an `onLock` callback for side effects. The consuming code is\n * responsible for wiring DOM events (e.g., visibility changes, user interactions)\n * to {@link reportActivity}.\n *\n * @example\n * ```typescript\n * const manager = new AutoLockManager({\n * timeout: 15 * 60 * 1000, // 15 minutes\n * onLock: () => {\n * // Clear decrypted data from memory\n * // Show lock screen\n * }\n * })\n *\n * manager.start()\n *\n * // Call on user interactions to reset the timer\n * document.addEventListener('click', () => manager.reportActivity())\n * document.addEventListener('keydown', () => manager.reportActivity())\n *\n * // Check lock state\n * if (manager.isLocked) {\n * // Prompt for passphrase\n * }\n * ```\n */\nexport class AutoLockManager {\n\tprivate readonly _timeout: number\n\tprivate readonly _onLock: () => void\n\tprivate _timerId: ReturnType<typeof setTimeout> | null = null\n\tprivate _isLocked = false\n\tprivate _isRunning = false\n\n\tconstructor(config: AutoLockConfig) {\n\t\tif (config.timeout <= 0) {\n\t\t\tthrow new Error(\n\t\t\t\t`AutoLockManager timeout must be a positive number, but received ${config.timeout}.`,\n\t\t\t)\n\t\t}\n\n\t\tthis._timeout = config.timeout\n\t\tthis._onLock = config.onLock\n\t}\n\n\t/**\n\t * Whether the manager is currently in the locked state.\n\t *\n\t * Becomes `true` when the inactivity timeout fires or {@link lock} is called manually.\n\t * Returns to `false` only when {@link unlock} is called.\n\t */\n\tget isLocked(): boolean {\n\t\treturn this._isLocked\n\t}\n\n\t/**\n\t * Starts monitoring for inactivity.\n\t *\n\t * Begins the inactivity countdown. If the manager is already running, this is\n\t * a no-op to prevent creating multiple timers. Calling `start()` also resets\n\t * the locked state if the manager was previously locked.\n\t */\n\tstart(): void {\n\t\tif (this._isRunning) {\n\t\t\treturn\n\t\t}\n\n\t\tthis._isRunning = true\n\t\tthis._isLocked = false\n\t\tthis._startTimer()\n\t}\n\n\t/**\n\t * Stops monitoring for inactivity.\n\t *\n\t * Clears the pending inactivity timer. Does not change the lock state: if\n\t * the manager was locked, it remains locked. If unlocked, it remains unlocked.\n\t * To resume monitoring, call {@link start} again.\n\t */\n\tstop(): void {\n\t\tthis._isRunning = false\n\t\tthis._clearTimer()\n\t}\n\n\t/**\n\t * Reports user activity, resetting the inactivity timer.\n\t *\n\t * Call this whenever the user interacts with the application (clicks, key presses,\n\t * touches, etc.). If the manager is not running or is already locked, this is a no-op.\n\t */\n\treportActivity(): void {\n\t\tif (!this._isRunning || this._isLocked) {\n\t\t\treturn\n\t\t}\n\n\t\tthis._clearTimer()\n\t\tthis._startTimer()\n\t}\n\n\t/**\n\t * Manually locks the manager immediately.\n\t *\n\t * Clears the inactivity timer and transitions to the locked state. The `onLock`\n\t * callback is invoked. The manager remains running but locked: call {@link unlock}\n\t * to return to the unlocked state, which will restart the inactivity timer.\n\t */\n\tlock(): void {\n\t\tthis._clearTimer()\n\n\t\tif (!this._isLocked) {\n\t\t\tthis._isLocked = true\n\t\t\tthis._onLock()\n\t\t}\n\t}\n\n\t/**\n\t * Unlocks the manager, returning to the unlocked state.\n\t *\n\t * If the manager is running, the inactivity timer is restarted. If the manager\n\t * is not running (was stopped), it simply clears the locked state without\n\t * starting a timer.\n\t */\n\tunlock(): void {\n\t\tthis._isLocked = false\n\n\t\tif (this._isRunning) {\n\t\t\tthis._clearTimer()\n\t\t\tthis._startTimer()\n\t\t}\n\t}\n\n\t/**\n\t * Starts the inactivity timer. When it fires, the manager locks.\n\t */\n\tprivate _startTimer(): void {\n\t\tthis._timerId = setTimeout(() => {\n\t\t\tthis._timerId = null\n\t\t\tthis._isLocked = true\n\t\t\tthis._onLock()\n\t\t}, this._timeout)\n\t}\n\n\t/**\n\t * Clears the pending inactivity timer, if any.\n\t */\n\tprivate _clearTimer(): void {\n\t\tif (this._timerId !== null) {\n\t\t\tclearTimeout(this._timerId)\n\t\t\tthis._timerId = null\n\t\t}\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,iBAAiB;AAWnB,IAAM,YAAN,cAAwB,UAAU;AAAA,EACxC,YAAY,SAAiB,MAAc,SAAmC;AAC7E,UAAM,SAAS,MAAM,OAAO;AAC5B,SAAK,OAAO;AAAA,EACb;AACD;AAMO,IAAM,mBAAN,cAA+B,UAAU;AAAA,EAC/C,YAA4B,UAAkB;AAC7C,UAAM,qDAAqD,mBAAmB;AADnD;AAE3B,SAAK,OAAO;AAAA,EACb;AAAA,EAH4B;AAI7B;AAuMA,IAAM,wBAAwB;AAE9B,IAAM,6BAA6B;AACnC,IAAM,0BAA0B;AAChC,IAAM,yBAAyB,IAAI;AAMnC,IAAM,8BAA8B,IAAI;AAQxC,SAAS,iBAAiB,OAA+C;AACxE,QAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,MAAI,MAAM,WAAW,GAAG;AACvB,WAAO;AAAA,EACR;AAEA,MAAI;AAEH,UAAM,SAAU,MAAM,CAAC,EAAa,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG;AACxE,UAAM,OAAO,KAAK,MAAM;AACxB,UAAM,SAAkB,KAAK,MAAM,IAAI;AACvC,WAAO,SAAS,MAAM,IAAI,SAAS;AAAA,EACpC,QAAQ;AACP,WAAO;AAAA,EACR;AACD;AAMA,SAAS,eAAe,OAAe,gBAAgB,uBAAgC;AACtF,QAAM,UAAU,iBAAiB,KAAK;AACtC,MAAI,CAAC,WAAW,OAAO,QAAQ,QAAQ,UAAU;AAChD,WAAO;AAAA,EACR;AACA,QAAM,aAAa,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAC/C,SAAO,QAAQ,OAAO,aAAa;AACpC;AAGA,SAAS,gBAAgB,OAA8B;AACtD,QAAM,UAAU,iBAAiB,KAAK;AACtC,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI,OAAO,QAAQ,UAAU,SAAU,QAAO,QAAQ;AACtD,SAAO,OAAO,QAAQ,QAAQ,WAAW,QAAQ,MAAM,MAAO;AAC/D;AAEA,SAAS,SAAS,OAAkD;AACnE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC3E;AAEA,SAAS,kBAAgC;AACxC,MAAI,OAAO,WAAW,UAAU,YAAY;AAC3C,WAAO,YAAY;AAClB,YAAM,IAAI;AAAA,QACT;AAAA,QACA;AAAA,MACD;AAAA,IACD;AAAA,EACD;AACA,SAAO,WAAW,MAAM,KAAK,UAAU;AACxC;AAEA,SAAS,kBAAkB,MAAqE;AAC/F,SAAO;AAAA,IACN,IAAI,KAAK;AAAA,IACT,OAAO,KAAK;AAAA,IACZ,MAAM,KAAK,QAAQ;AAAA,EACpB;AACD;AAEA,SAAS,2BAAoC;AAC5C,SACC,OAAO,WAAW,WAAW,eAC7B,OAAO,WAAW,OAAO,UAAU,WAAW;AAEhD;AAEA,SAAS,sBAAsB,KAAmB;AACjD,MAAI,CAAC,yBAAyB,GAAG;AAChC,UAAM,IAAI;AAAA,MACT;AAAA,MACA;AAAA,IACD;AAAA,EACD;AACA,aAAW,OAAO,SAAS,OAAO,GAAG;AACtC;AAEA,IAAM,uBAAuB;AAE7B,SAAS,oBAAgF;AACxF,MAAI;AACH,UAAM,UAAW,WAA4C;AAC7D,WAAO,WAAW,OAAO,QAAQ,YAAY,aAAa,UAAU;AAAA,EACrE,QAAQ;AACP,WAAO;AAAA,EACR;AACD;AAgBA,IAAM,2BAA2B,oBAAI,IAAI,CAAC,yBAAyB,eAAe,CAAC;AAenF,SAAS,sBAAsB,UAAgC;AAC9D,MAAI,CAAC,SAAS,SAAS,IAAI,EAAG,QAAO;AACrC,QAAM,OAAO,OAAO,SAAS,KAAK,SAAS,WAAW,SAAS,KAAK,OAAO;AAC3E,QAAM,QAAQ,OAAO,SAAS,KAAK,UAAU,WAAW,SAAS,KAAK,QAAQ;AAC9E,MAAI,SAAS,WAAW,IAAK,QAAO,SAAS,UAAa,UAAU;AACpE,MAAI,SAAS,WAAW,KAAK;AAC5B,WACE,SAAS,UAAa,yBAAyB,IAAI,IAAI,KACvD,UAAU,UAAa,yBAAyB,IAAI,KAAK;AAAA,EAE5D;AACA,SAAO;AACR;AAEA,SAAS,gBAAgB,OAAsD;AAC9E,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,UAAU,OAAO,KAAK;AAC5B,MAAI,OAAO,SAAS,OAAO,KAAK,WAAW,EAAG,QAAO,UAAU;AAC/D,QAAM,OAAO,KAAK,MAAM,KAAK;AAC7B,SAAO,OAAO,MAAM,IAAI,IAAI,SAAY,KAAK,IAAI,GAAG,OAAO,KAAK,IAAI,CAAC;AACtE;AAEA,SAAS,cAAc,MAA0C;AAChE,MAAI,CAAC,SAAS,IAAI,EAAG,QAAO;AAC5B,QAAM,OAAO,KAAK,SAAS,SAAY,KAAK,OAAO;AACnD,MAAI,CAAC,SAAS,IAAI,EAAG,QAAO;AAC5B,SAAO,OAAO,KAAK,gBAAgB,YAAY,OAAO,KAAK,iBAAiB,WACzE,EAAE,aAAa,KAAK,aAAa,cAAc,KAAK,aAAa,IACjE;AACJ;AAeA,IAAM,iBAAiB,oBAAI,QAAkC;AAE7D,SAAS,cAAqC;AAC7C,QAAM,MAAO,WAAmD;AAChE,QAAM,QAAQ,KAAK;AACnB,MAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO;AACxD,SAAO,OAAQ,MAAgC,YAAY,aACvD,QACD;AACJ;AAaA,SAAS,mBAAmB,QAAkC;AAE7D,MAAI,kBAAkB;AACtB,MAAI;AACH,QAAI,OAAO,WAAW,eAAe,OAAO,OAAO,iBAAiB,aAAa;AAEhF,YAAM,UAAU,GAAG,MAAM;AACzB,aAAO,aAAa,QAAQ,SAAS,GAAG;AACxC,aAAO,aAAa,WAAW,OAAO;AACtC,wBAAkB;AAAA,IACnB;AAAA,EACD,QAAQ;AAAA,EAER;AAEA,MAAI,iBAAiB;AACpB,UAAM,YAAY,GAAG,MAAM;AAC3B,UAAM,aAAa,GAAG,MAAM;AAC5B,WAAO;AAAA,MACN,iBAAgC;AAC/B,eAAO,OAAO,aAAa,QAAQ,SAAS;AAAA,MAC7C;AAAA,MACA,kBAAiC;AAChC,eAAO,OAAO,aAAa,QAAQ,UAAU;AAAA,MAC9C;AAAA,MACA,UAAU,QAAgB,SAAuB;AAChD,eAAO,aAAa,QAAQ,WAAW,MAAM;AAC7C,eAAO,aAAa,QAAQ,YAAY,OAAO;AAAA,MAChD;AAAA,MACA,QAAc;AACb,eAAO,aAAa,WAAW,SAAS;AACxC,eAAO,aAAa,WAAW,UAAU;AAAA,MAC1C;AAAA,IACD;AAAA,EACD;AAGA,MAAI,cAA6B;AACjC,MAAI,eAA8B;AAClC,SAAO;AAAA,IACN,iBAAgC;AAC/B,aAAO;AAAA,IACR;AAAA,IACA,kBAAiC;AAChC,aAAO;AAAA,IACR;AAAA,IACA,UAAU,QAAgB,SAAuB;AAChD,oBAAc;AACd,qBAAe;AAAA,IAChB;AAAA,IACA,QAAc;AACb,oBAAc;AACd,qBAAe;AAAA,IAChB;AAAA,EACD;AACD;AAoCO,IAAM,aAAN,MAAiB;AAAA,EACN;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAA6C,oBAAI,IAAI;AAAA,EACrD,mBAAqE,oBAAI,IAAI;AAAA,EAC7E;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAET,SAAoB;AAAA,EACpB,QAAyB;AAAA,EACzB,kBAAkD;AAAA,EAClD,eAAe;AAAA,EAEf,gBAAmC;AAAA,EACnC,sBAAsB;AAAA,EACtB,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,aAAmD;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOjB,YAAY,QAA0B;AAErC,SAAK,YAAY,OAAO,UAAU,QAAQ,QAAQ,EAAE;AACpD,UAAM,SAAS,OAAO,cAAc;AACpC,SAAK,UAAU,OAAO,WAAW,mBAAmB,MAAM;AAC1D,SAAK,UAAU,OAAO,SAAS,gBAAgB;AAC/C,SAAK,iBAAiB,OAAO;AAC7B,SAAK,mBAAmB,OAAO,oBAAoB;AACnD,SAAK,oBAAoB,OAAO;AAChC,SAAK,gBAAgB,OAAO,gBAAgB,eAAe;AAC3D,SAAK,eAAe,OAAO,gBAAgB,cAAc;AACzD,SAAK,WAAW,qBAAqB,MAAM;AAC3C,SAAK,oBAAoB,KAAK,2BAA2B;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI,QAAmB;AACtB,WAAO,KAAK;AAAA,EACb;AAAA;AAAA,EAGA,IAAI,cAA+B;AAClC,WAAO,KAAK;AAAA,EACb;AAAA;AAAA,EAGA,IAAI,kBAA2B;AAC9B,WAAO,KAAK,WAAW;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI,UAAoC;AACvC,QAAI,KAAK,WAAW,mBAAmB,CAAC,KAAK,MAAO,QAAO;AAC3D,WAAO;AAAA,MACN,QAAQ,KAAK,MAAM;AAAA,MACnB,UAAU,KAAK;AAAA,MACf,QAAQ,KAAK;AAAA,MACb,qBAAqB,KAAK;AAAA,IAC3B;AAAA,EACD;AAAA,EAEQ,iBAAgC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMxC,MAAM,oBAAiF;AACtF,UAAM,SAAS,MAAM,KAAK,gBAAgB;AAC1C,QAAI,CAAC,UAAU,OAAO,OAAO,QAAQ,YAAY,OAAO,IAAI,WAAW,EAAG,QAAO;AACjF,WAAO;AAAA,MACN,QAAQ,OAAO;AAAA,MACf,UAAU,OAAO,OAAO,QAAQ,YAAY,OAAO,IAAI,SAAS,IAAI,OAAO,MAAM;AAAA,IAClF;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,kBAA2D;AAChE,UAAM,SAAS,MAAM,KAAK,QAAQ,eAAe;AACjD,UAAM,UAAU,MAAM,KAAK,QAAQ,gBAAgB;AACnD,QAAI,CAAC,QAAS,QAAO;AACrB,eAAW,SAAS,CAAC,QAAQ,OAAO,GAAG;AACtC,UAAI,CAAC,MAAO;AACZ,YAAM,SAAS,iBAAiB,KAAK;AACrC,UAAI,UAAU,OAAO,OAAO,QAAQ,YAAY,OAAO,IAAI,SAAS,EAAG,QAAO;AAAA,IAC/E;AACA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,MAAM,aAA4B;AAEjC,QAAI,KAAK,cAAc;AACtB;AAAA,IACD;AACA,SAAK,eAAe;AAEpB,UAAM,cAAc,MAAM,KAAK,QAAQ,eAAe;AACtD,UAAM,eAAe,MAAM,KAAK,QAAQ,gBAAgB;AAGxD,QAAI,CAAC,eAAe,CAAC,cAAc;AAClC,WAAK,SAAS,mBAAmB,IAAI;AACrC;AAAA,IACD;AACA,SAAK,qBAAqB,YAAY;AAGtC,QAAI,CAAC,eAAe,WAAW,GAAG;AACjC,YAAM,KAAK,eAAe,WAAW;AACrC;AAAA,IACD;AAGA,UAAM,UAAU,MAAM,KAAK,QAAQ;AACnC,QAAI,QAAQ,SAAS,MAAM;AAC1B,YAAM,KAAK,eAAe,QAAQ,WAAW;AAC7C;AAAA,IACD;AACA,QAAI,QAAQ,SAAS,YAAY;AAEhC,WAAK,SAAS,mBAAmB,IAAI;AACrC;AAAA,IACD;AACA,UAAM,KAAK,oBAAoB;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM,OAAO,QAMS;AACrB,UAAM,OAAO,MAAM,KAAK,mBAAmB,MAAM;AACjD,UAAM,WAAW,MAAM,KAAK,QAAiD,gBAAgB;AAAA,MAC5F,QAAQ;AAAA,MACR;AAAA,IACD,CAAC;AACD,WAAO,KAAK,eAAe,QAAQ;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,OAAO,QAKS;AACrB,UAAM,OAAO,MAAM,KAAK,mBAAmB,MAAM;AACjD,UAAM,WAAW,MAAM,KAAK,QAE1B,gBAAgB;AAAA,MACjB,QAAQ;AAAA,MACR;AAAA,IACD,CAAC;AACD,WAAO,KAAK,eAAe,QAAQ;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,UACL,UACA,OACoB;AACpB,UAAM,WAAW,MAAM,KAAK,QAA4B,oBAAoB;AAAA,MAC3E,QAAQ;AAAA,MACR,MAAM,EAAE,UAAU,GAAG,MAAM;AAAA,IAC5B,CAAC;AACD,WAAO,KAAK,eAAe,QAAQ;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,gBACL,UACA,UAAqC,CAAC,GACF;AACpC,UAAM,SAAS,MAAM,KAAK,yBAAyB,UAAU,OAAO;AACpE,QAAI,QAAQ,YAAY,yBAAyB,GAAG;AACnD,4BAAsB,OAAO,GAAG;AAAA,IACjC;AACA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,oBAAoB,UAAkB,QAAgD;AAC3F,UAAM,OAAO,MAAM,KAAK,mBAAmB,MAAM;AACjD,UAAM,UAAU,OAAO,WAAW,KAAK,iBAAiB,OAAO,KAAK;AACpE,UAAM,WAAW,MAAM,KAAK;AAAA,MAC3B,eAAe,mBAAmB,QAAQ,CAAC;AAAA,MAC3C;AAAA,QACC,QAAQ;AAAA,QACR,MAAM,EAAE,GAAG,MAAM,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC,EAAG;AAAA,MAClD;AAAA,IACD;AACA,QAAI,iBAAiB,UAAU;AAC9B,YAAM,IAAI,iBAAiB,SAAS,QAAQ;AAAA,IAC7C;AAEA,UAAM,KAAK,QAAQ,UAAU,SAAS,OAAO,aAAa,SAAS,OAAO,YAAY;AACtF,SAAK,UAAU,SAAS,OAAO,YAAY;AAC3C,UAAM,OAAO,kBAAkB,SAAS,IAAI;AAC5C,SAAK,SAAS,iBAAiB,IAAI;AACnC,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,yBACL,UACA,WAAsC,CAAC,GACH;AAGpC,UAAM,QAAQ,MAAM,KAAK,mBAAmB;AAC5C,UAAM,SAAS,MAAM,KAAK;AAAA,MACzB,eAAe,mBAAmB,QAAQ,CAAC;AAAA,MAC3C,EAAE,QAAQ,QAAQ,MAAM,CAAC,GAAG,MAAM;AAAA,IACnC;AACA,SAAK,qBAAqB,MAAM;AAChC,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAU,UAAkB,QAA0D;AAC3F,UAAM,QAAQ,MAAM,KAAK,mBAAmB;AAC5C,UAAM,UAAU,OAAO,WAAW,KAAK,iBAAiB,OAAO,KAAK;AACpE,WAAO,KAAK,QAA4B,eAAe,mBAAmB,QAAQ,CAAC,SAAS;AAAA,MAC3F,QAAQ;AAAA,MACR,MAAM;AAAA,QACL,MAAM,OAAO;AAAA,QACb,OAAO,OAAO;AAAA,QACd,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;AAAA,MAC9B;AAAA,MACA;AAAA,IACD,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,qBAAoD;AACzD,UAAM,QAAQ,MAAM,KAAK,mBAAmB;AAC5C,WAAO,KAAK,QAA8B,qBAAqB;AAAA,MAC9D,QAAQ;AAAA,MACR;AAAA,IACD,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAAY,UAAiC;AAClD,UAAM,QAAQ,MAAM,KAAK,mBAAmB;AAC5C,UAAM,KAAK,QAAsB,eAAe,mBAAmB,QAAQ,CAAC,SAAS;AAAA,MACpF,QAAQ;AAAA,MACR;AAAA,IACD,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,UAAyB;AAC9B,UAAM,cAAc,MAAM,KAAK,QAAQ,eAAe;AACtD,UAAM,eAAe,MAAM,KAAK,QAAQ,gBAAgB;AAGxD,UAAM,KAAK,QAAQ,MAAM;AACzB,SAAK,kBAAkB;AACvB,SAAK,aAAa;AAClB,SAAK,gBAAgB;AACrB,SAAK,iBAAiB;AACtB,SAAK,SAAS,mBAAmB,IAAI;AAGrC,QAAI,aAAa;AAChB,UAAI;AACH,cAAM,KAAK,QAAQ,iBAAiB;AAAA,UACnC,QAAQ;AAAA,UACR,MAAM,EAAE,cAAc,gBAAgB,OAAU;AAAA,UAChD,OAAO;AAAA,QACR,CAAC;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,MAAM,iBAAyC;AAC9C,UAAM,cAAc,MAAM,KAAK,QAAQ,eAAe;AAEtD,QAAI,eAAe,CAAC,eAAe,WAAW,GAAG;AAChD,UAAI,KAAK,WAAW,mBAAmB,KAAK,kBAAkB,SAAS;AAEtE,aAAK,UAAU,MAAM,KAAK,QAAQ,gBAAgB,CAAC;AAAA,MACpD;AACA,aAAO;AAAA,IACR;AAEA,UAAM,eAAe,MAAM,KAAK,QAAQ,gBAAgB;AACxD,QAAI,CAAC,cAAc;AAClB,aAAO;AAAA,IACR;AAEA,UAAM,UAAU,MAAM,KAAK,QAAQ;AACnC,WAAO,QAAQ,SAAS,OAAO,QAAQ,cAAc;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM,qBAA6C;AAClD,UAAM,eAAe,MAAM,KAAK,QAAQ,gBAAgB;AACxD,QAAI,CAAC,cAAc;AAClB,aAAO;AAAA,IACR;AACA,UAAM,UAAU,MAAM,KAAK,QAAQ;AACnC,WAAO,QAAQ,SAAS,OAAO,QAAQ,cAAc;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,eAAuC;AAC5C,WAAO,KAAK,eAAe;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,WAA0B;AAC/B,SAAK,aAAa;AAClB,QAAI,KAAK,WAAW,mBAAmB,KAAK,kBAAkB,SAAS;AACtE,YAAM,KAAK,eAAe;AAAA,IAC3B;AAAA,EACD;AAAA;AAAA,EAGA,UAAgB;AACf,SAAK,kBAAkB;AACvB,SAAK,gBAAgB;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuBA,aAAa,UAAkD;AAC9D,SAAK,UAAU,IAAI,QAAQ;AAC3B,WAAO,MAAM;AACZ,WAAK,UAAU,OAAO,QAAQ;AAAA,IAC/B;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,gBAAgB,UAAmE;AAClF,SAAK,iBAAiB,IAAI,QAAQ;AAClC,WAAO,MAAM;AACZ,WAAK,iBAAiB,OAAO,QAAQ;AAAA,IACtC;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,SAAS,OAAkB,MAA6B;AAC/D,UAAM,UAAU,KAAK,WAAW,SAAS,KAAK,UAAU;AACxD,SAAK,SAAS;AACd,SAAK,QAAQ;AAEb,QAAI,SAAS;AACZ,iBAAW,YAAY,KAAK,WAAW;AACtC,YAAI;AACH,mBAAS,KAAK;AAAA,QACf,QAAQ;AAAA,QAGR;AAAA,MACD;AACA,WAAK,cAAc;AAAA,IACpB;AAAA,EACD;AAAA,EAEQ,iBAAiB,QAAiC;AACzD,QAAI,KAAK,kBAAkB,OAAQ;AACnC,SAAK,gBAAgB;AACrB,SAAK,cAAc;AAAA,EACpB;AAAA,EAEQ,gBAAsB;AAC7B,UAAM,UAAU,KAAK;AACrB,eAAW,YAAY,KAAK,kBAAkB;AAC7C,UAAI;AACH,iBAAS,OAAO;AAAA,MACjB,QAAQ;AAAA,MAER;AAAA,IACD;AAAA,EACD;AAAA,EAEA,MAAc,eACb,UACoB;AACpB,QAAI,iBAAiB,UAAU;AAC9B,YAAM,IAAI,iBAAiB,SAAS,QAAQ;AAAA,IAC7C;AACA,UAAM,SAAS,YAAY,WAAW,SAAS,SAAS;AACxD,UAAM,KAAK,QAAQ,UAAU,OAAO,aAAa,OAAO,YAAY;AACpE,SAAK,UAAU,OAAO,YAAY;AAClC,UAAM,OACL,UAAU,YAAY,SAAS,OAC5B,kBAAkB,SAAS,IAAI,IAC/B,MAAM,KAAK,iBAAiB,OAAO,WAAW;AAClD,SAAK,SAAS,iBAAiB,IAAI;AACnC,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,eAAe,aAAoC;AAChE,QAAI;AACJ,QAAI;AACH,iBAAW,MAAM,KAAK,WAAW,YAAY,EAAE,QAAQ,OAAO,OAAO,YAAY,CAAC;AAAA,IACnF,QAAQ;AACP,YAAM,KAAK,oBAAoB;AAC/B;AAAA,IACD;AACA,QAAI,SAAS,MAAM,SAAS,SAAS,IAAI,GAAG;AAC3C,YAAM,UAAW,SAAS,KAAK,SAAS,SAAY,SAAS,KAAK,OAAO,SAAS;AAGlF,UAAI,SAAS,OAAO,KAAK,OAAO,QAAQ,OAAO,UAAU;AACxD,aAAK,UAAU,IAAI;AACnB,aAAK,SAAS,iBAAiB,kBAAkB,OAAO,CAAC;AACzD;AAAA,MACD;AAAA,IACD;AACA,QAAI,SAAS,WAAW,OAAO,sBAAsB,QAAQ,GAAG;AAC/D,YAAM,KAAK,WAAW;AACtB;AAAA,IACD;AACA,UAAM,KAAK,oBAAoB;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,sBAAqC;AAClD,UAAM,eAAe,MAAM,KAAK,QAAQ,gBAAgB;AACxD,UAAM,WAAW,MAAM,KAAK,kBAAkB;AAC9C,QAAI,CAAC,gBAAgB,CAAC,YAAY,eAAe,cAAc,CAAC,GAAG;AAClE,YAAM,KAAK,WAAW;AACtB;AAAA,IACD;AACA,SAAK,iBAAiB,SAAS;AAC/B,SAAK,qBAAqB,YAAY;AACtC,UAAM,OACL,KAAK,SAAS,KAAK,MAAM,OAAO,SAAS,SACtC,KAAK,QACL,EAAE,IAAI,SAAS,QAAQ,OAAO,IAAI,MAAM,KAAK;AACjD,SAAK,gBAAgB,KAAK,cAAc;AACxC,SAAK,SAAS,iBAAiB,IAAI;AACnC,SAAK,cAAc;AAAA,EACpB;AAAA;AAAA,EAGQ,gBAAmC;AAC1C,UAAM,MAAM,KAAK,IAAI;AACrB,QACC,KAAK,sBAAsB,KAC3B,MAAM,8BAA8B,KAAK,qBACxC;AAGD,aAAO;AAAA,IACR;AACA,QACC,KAAK,sBAAsB,UAC3B,KAAK,sBAAsB,KAC3B,MAAM,KAAK,sBAAsB,KAAK,mBACrC;AACD,aAAO;AAAA,IACR;AACA,WAAO;AAAA,EACR;AAAA,EAEA,MAAc,aAA4B;AACzC,UAAM,KAAK,QAAQ,MAAM;AACzB,SAAK,aAAa;AAClB,SAAK,gBAAgB;AACrB,SAAK,iBAAiB;AACtB,SAAK,SAAS,mBAAmB,IAAI;AAAA,EACtC;AAAA;AAAA,EAGQ,qBAAqB,cAAmC;AAC/D,QAAI,CAAC,aAAc;AACnB,UAAM,SAAS,gBAAgB,YAAY;AAC3C,QAAI,WAAW,KAAM,MAAK,sBAAsB,KAAK,IAAI,KAAK,qBAAqB,MAAM;AACzF,UAAM,SAAS,iBAAiB,YAAY;AAC5C,QAAI,UAAU,OAAO,OAAO,QAAQ,SAAU,MAAK,iBAAiB,OAAO;AAAA,EAC5E;AAAA,EAEQ,UAAU,cAAmC;AACpD,SAAK,aAAa;AAClB,SAAK,sBAAsB,KAAK,IAAI,KAAK,qBAAqB,KAAK,IAAI,CAAC;AACxE,SAAK,qBAAqB,YAAY;AACtC,SAAK,iBAAiB,OAAO;AAAA,EAC9B;AAAA,EAEQ,cAAoB;AAC3B,QAAI,KAAK,WAAW,iBAAiB;AACpC,WAAK,iBAAiB,KAAK,cAAc,CAAC;AAAA,IAC3C;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,iBAAiB,aAAwC;AACtE,UAAM,UAAU,MAAM,KAAK,QAA6B,YAAY;AAAA,MACnE,QAAQ;AAAA,MACR,OAAO;AAAA,IACR,CAAC;AACD,WAAO,kBAAkB,OAAO;AAAA,EACjC;AAAA,EAEA,MAAc,yBACb,UACA,SACoC;AACpC,UAAM,SAAS,IAAI,gBAAgB;AAEnC,QAAI,QAAQ,UAAU;AACrB,aAAO,IAAI,YAAY,QAAQ,QAAQ;AAAA,IACxC;AACA,QAAI,QAAQ,UAAU;AACrB,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,QAAQ,QAAQ,GAAG;AAC5D,YAAI,UAAU,UAAa,UAAU,MAAM;AAC1C,iBAAO,IAAI,KAAK,OAAO,KAAK,CAAC;AAAA,QAC9B;AAAA,MACD;AAAA,IACD;AAEA,UAAM,QAAQ,OAAO,SAAS;AAC9B,UAAM,SAAS,MAAM,KAAK;AAAA,MACzB,eAAe,mBAAmB,QAAQ,CAAC,GAAG,QAAQ,IAAI,KAAK,KAAK,EAAE;AAAA,MACtE;AAAA,QACC,QAAQ;AAAA,MACT;AAAA,IACD;AACA,SAAK,qBAAqB,MAAM;AAChC,WAAO;AAAA,EACR;AAAA;AAAA,EAGiB,gBAAgB,oBAAI,IAAoB;AAAA,EAEjD,qBAAqB,QAAwC;AACpE,QAAI,CAAC,OAAO,WAAW,CAAC,OAAO,MAAO;AACtC,SAAK,cAAc,IAAI,OAAO,OAAO,OAAO,OAAO;AACnD,UAAM,UAAU,kBAAkB;AAClC,QAAI;AACH,eAAS,QAAQ,GAAG,oBAAoB,GAAG,OAAO,KAAK,IAAI,OAAO,OAAO;AAAA,IAC1E,QAAQ;AAAA,IAER;AAAA,EACD;AAAA,EAEQ,iBAAiB,OAAmC;AAC3D,UAAM,WAAW,KAAK,cAAc,IAAI,KAAK;AAC7C,SAAK,cAAc,OAAO,KAAK;AAC/B,UAAM,UAAU,kBAAkB;AAClC,QAAI,SAAwB;AAC5B,QAAI;AACH,eAAS,SAAS,QAAQ,GAAG,oBAAoB,GAAG,KAAK,EAAE,KAAK;AAChE,eAAS,WAAW,GAAG,oBAAoB,GAAG,KAAK,EAAE;AAAA,IACtD,QAAQ;AACP,eAAS;AAAA,IACV;AACA,WAAO,YAAY,UAAU;AAAA,EAC9B;AAAA,EAEA,MAAc,mBACb,QACa;AACb,QAAI,CAAC,KAAK,kBAAmB,OAAO,YAAY,OAAO,iBAAkB;AACxE,aAAO;AAAA,IACR;AAEA,UAAM,WAAW,MAAM,KAAK,eAAe,kBAAkB;AAC7D,WAAO;AAAA,MACN,GAAG;AAAA,MACH,UAAU,OAAO,YAAY,SAAS;AAAA,MACtC,iBAAiB,OAAO,mBAAmB,SAAS;AAAA,IACrD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,UAAmC;AAC1C,QAAI,KAAK,iBAAiB;AACzB,aAAO,KAAK;AAAA,IACb;AACA,UAAM,UAAU,KAAK,YAAY,EAAE,QAAQ,MAAM;AAChD,UAAI,KAAK,oBAAoB,QAAS,MAAK,kBAAkB;AAAA,IAC9D,CAAC;AACD,SAAK,kBAAkB;AACvB,WAAO;AAAA,EACR;AAAA,EAEA,MAAc,cAAuC;AACpD,UAAM,eAAe,MAAM,KAAK,QAAQ,gBAAgB;AACxD,QAAI,CAAC,aAAc,QAAO,EAAE,MAAM,WAAW;AAG7C,QAAI,eAAe,cAAc,CAAC,GAAG;AACpC,YAAM,KAAK,WAAW;AACtB,aAAO,EAAE,MAAM,WAAW;AAAA,IAC3B;AAEA,QAAI,KAAK,IAAI,IAAI,KAAK,eAAe;AACpC,WAAK,YAAY;AACjB,aAAO,EAAE,MAAM,YAAY;AAAA,IAC5B;AAEA,QAAI;AACJ,QAAI;AACH,gBAAU,MAAM,KAAK,gBAAgB,YAAY;AAGhD,cAAM,UAAU,MAAM,KAAK,QAAQ,gBAAgB;AACnD,YAAI,CAAC,QAAS,QAAO,EAAE,MAAM,WAAW;AACxC,cAAM,gBAAgB,MAAM,KAAK,QAAQ,eAAe;AACxD,YAAI,YAAY,gBAAgB,iBAAiB,CAAC,eAAe,aAAa,GAAG;AAChF,iBAAO,EAAE,MAAM,MAAM,aAAa,cAAc;AAAA,QACjD;AACA,eAAO,KAAK,eAAe,OAAO;AAAA,MACnC,CAAC;AAAA,IACF,QAAQ;AAEP,gBAAU,EAAE,MAAM,YAAY;AAAA,IAC/B;AAEA,QAAI,QAAQ,SAAS,MAAM;AAC1B,WAAK,UAAU,MAAM,KAAK,QAAQ,gBAAgB,CAAC;AAAA,IACpD,WAAW,QAAQ,SAAS,YAAY;AACvC,WAAK,aAAa;AAClB,WAAK,iBAAiB;AACtB,WAAK,gBAAgB;AACrB,WAAK,SAAS,mBAAmB,IAAI;AAAA,IACtC,OAAO;AACN,WAAK,gBAAgB,QAAQ,YAAY;AACzC,WAAK,YAAY;AAAA,IAClB;AACA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,eAAe,cAA+C;AAC3E,QAAI;AACJ,QAAI;AACH,iBAAW,MAAM,KAAK,WAAW,iBAAiB;AAAA,QACjD,QAAQ;AAAA,QACR,MAAM,EAAE,aAAa;AAAA,MACtB,CAAC;AAAA,IACF,QAAQ;AAEP,aAAO,EAAE,MAAM,YAAY;AAAA,IAC5B;AAEA,QAAI,SAAS,IAAI;AAChB,YAAM,SAAS,cAAc,SAAS,IAAI;AAC1C,UAAI,CAAC,QAAQ;AAEZ,eAAO,EAAE,MAAM,YAAY;AAAA,MAC5B;AACA,YAAM,KAAK,QAAQ,UAAU,OAAO,aAAa,OAAO,YAAY;AACpE,aAAO,EAAE,MAAM,MAAM,aAAa,OAAO,YAAY;AAAA,IACtD;AAEA,QAAI,sBAAsB,QAAQ,GAAG;AAEpC,UAAK,MAAM,KAAK,QAAQ,gBAAgB,MAAO,cAAc;AAC5D,cAAM,KAAK,QAAQ,MAAM;AACzB,eAAO,EAAE,MAAM,WAAW;AAAA,MAC3B;AACA,YAAM,UAAU,MAAM,KAAK,QAAQ,eAAe;AAClD,aAAO,WAAW,CAAC,eAAe,OAAO,IACtC,EAAE,MAAM,MAAM,aAAa,QAAQ,IACnC,EAAE,MAAM,YAAY;AAAA,IACxB;AAEA,WAAO,EAAE,MAAM,aAAa,cAAc,SAAS,aAAa;AAAA,EACjE;AAAA,EAEA,MAAc,gBAAmB,IAAkC;AAClE,UAAM,QAAQ,YAAY;AAC1B,QAAI,OAAO;AACV,YAAM,aAAa,OAAO,oBAAoB,aAAa,IAAI,gBAAgB,IAAI;AACnF,YAAM,QAAQ,aACX,WAAW,MAAM,WAAW,MAAM,GAAG,KAAK,mBAAmB,CAAC,IAC9D;AACH,UAAI;AACH,eAAO,MAAM,MAAM;AAAA,UAClB,KAAK;AAAA,UACL,aAAa,EAAE,QAAQ,WAAW,OAAO,IAAI,CAAC;AAAA,UAC9C,YAAY;AACX,gBAAI,MAAO,cAAa,KAAK;AAC7B,mBAAO,GAAG;AAAA,UACX;AAAA,QACD;AAAA,MACD,UAAE;AACD,YAAI,MAAO,cAAa,KAAK;AAAA,MAC9B;AAAA,IACD;AAEA,UAAM,MAAM,KAAK;AACjB,UAAM,WAAW,eAAe,IAAI,GAAG,KAAK,QAAQ,QAAQ;AAC5D,UAAM,MAAM,SAAS,KAAK,IAAI,EAAE;AAChC,mBAAe;AAAA,MACd;AAAA,MACA,IAAI;AAAA,QACH,MAAM;AAAA,QACN,MAAM;AAAA,MACP;AAAA,IACD;AACA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,gBAAgB,cAAwC;AAC/D,SAAK;AACL,QAAI,QAAQ;AACZ,QAAI,KAAK,eAAe,GAAG;AAC1B,YAAM,UAAU,KAAK,IAAI,KAAK,gBAAgB,MAAM,KAAK,eAAe,IAAI,KAAK,YAAY;AAC7F,cAAQ,UAAU,IAAI,KAAK,OAAO,KAAK,UAAU;AAAA,IAClD;AACA,QAAI,iBAAiB,OAAW,SAAQ,KAAK,IAAI,OAAO,YAAY;AACpE,SAAK,gBAAgB,KAAK,IAAI,IAAI;AAClC,SAAK,cAAc,KAAK,IAAI,OAAO,KAAK,aAAa,CAAC;AAAA,EACvD;AAAA,EAEQ,eAAqB;AAC5B,SAAK,eAAe;AACpB,SAAK,gBAAgB;AACrB,SAAK,gBAAgB;AAAA,EACtB;AAAA;AAAA,EAGQ,cAAc,SAAuB;AAC5C,SAAK,gBAAgB;AACrB,QAAI,OAAO,eAAe,WAAY;AACtC,UAAM,QAAQ,WAAW,MAAM;AAC9B,WAAK,aAAa;AAClB,UAAI,KAAK,WAAW,mBAAmB,KAAK,kBAAkB,SAAS;AACtE,aAAK,KAAK,eAAe,EAAE,MAAM,MAAM,MAAS;AAAA,MACjD;AAAA,IACD,GAAG,OAAO;AAET,IAAC,MAAiC,QAAQ;AAC3C,SAAK,aAAa;AAAA,EACnB;AAAA,EAEQ,kBAAwB;AAC/B,QAAI,KAAK,eAAe,MAAM;AAC7B,mBAAa,KAAK,UAAU;AAC5B,WAAK,aAAa;AAAA,IACnB;AAAA,EACD;AAAA,EAEQ,6BAAyC;AAChD,UAAM,SAAU,WAAoC;AAMpD,QAAI,CAAC,UAAU,OAAO,OAAO,qBAAqB,WAAY,QAAO,MAAM;AAAA,IAAC;AAC5E,UAAM,OAAO,MAAY;AACxB,YAAM,MAAO,WAA2D;AACxE,UAAI,KAAK,oBAAoB,SAAU;AACvC,WAAK,KAAK,SAAS,EAAE,MAAM,MAAM,MAAS;AAAA,IAC3C;AACA,WAAO,iBAAiB,UAAU,IAAI;AACtC,WAAO,iBAAiB,oBAAoB,IAAI;AAChD,WAAO,MAAM;AACZ,aAAO,sBAAsB,UAAU,IAAI;AAC3C,aAAO,sBAAsB,oBAAoB,IAAI;AAAA,IACtD;AAAA,EACD;AAAA,EAEA,MAAc,qBAAsC;AACnD,UAAM,QAAQ,MAAM,KAAK,eAAe;AACxC,QAAI,CAAC,OAAO;AACX,YAAM,IAAI,UAAU,iDAAiD,eAAe;AAAA,IACrF;AACA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,WACb,MACA,SAKuB;AACvB,UAAM,MAAM,GAAG,KAAK,SAAS,GAAG,IAAI;AACpC,UAAM,UAAkC,CAAC;AACzC,QAAI,QAAQ,MAAM;AACjB,cAAQ,cAAc,IAAI;AAAA,IAC3B;AACA,QAAI,QAAQ,OAAO;AAClB,cAAQ,gBAAgB,UAAU,QAAQ,KAAK;AAAA,IAChD;AAEA,UAAM,aAAa,OAAO,oBAAoB,aAAa,IAAI,gBAAgB,IAAI;AACnF,QAAI,QAA8C;AAClD,UAAM,UAAU,IAAI,QAAe,CAAC,GAAG,WAAW;AACjD,cAAQ,WAAW,MAAM;AACxB,oBAAY,MAAM;AAClB;AAAA,UACC,IAAI;AAAA,YACH,cAAc,IAAI,oBAAoB,KAAK,gBAAgB;AAAA,YAC3D;AAAA,YACA,EAAE,KAAK;AAAA,UACR;AAAA,QACD;AAAA,MACD,GAAG,KAAK,gBAAgB;AAAA,IACzB,CAAC;AACD,QAAI;AACH,YAAM,WAAW,MAAM,QAAQ,KAAK;AAAA,QACnC,KAAK,QAAQ,KAAK;AAAA,UACjB,QAAQ,QAAQ;AAAA,UAChB;AAAA,UACA,MAAM,QAAQ,OAAO,KAAK,UAAU,QAAQ,IAAI,IAAI;AAAA,UACpD,GAAI,aAAa,EAAE,QAAQ,WAAW,OAAO,IAAI,CAAC;AAAA,QACnD,CAAC;AAAA,QACD;AAAA,MACD,CAAC;AACD,UAAI;AACJ,UAAI;AACH,eAAO,MAAM,QAAQ,KAAK,CAAC,SAAS,KAAK,GAAuB,OAAO,CAAC;AAAA,MACzE,QAAQ;AACP,eAAO;AAAA,MACR;AACA,YAAM,eAAgB,SACpB;AACF,aAAO;AAAA,QACN,QAAQ,SAAS;AAAA,QACjB,IAAI,SAAS;AAAA,QACb;AAAA,QACA,cAAc,gBAAgB,cAAc,MAAM,aAAa,CAAC;AAAA,MACjE;AAAA,IACD,UAAE;AACD,UAAI,UAAU,KAAM,cAAa,KAAK;AAAA,IACvC;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAc,QACb,MACA,SAKa;AACb,QAAI;AACJ,QAAI;AACH,iBAAW,MAAM,KAAK,WAAW,MAAM,OAAO;AAAA,IAC/C,SAAS,OAAO;AACf,UAAI,iBAAiB,UAAW,OAAM;AACtC,YAAM,IAAI;AAAA,QACT,sBAAsB,IAAI,+BAA+B,KAAK,SAAS;AAAA,QACvE;AAAA,QACA,EAAE,MAAM,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE;AAAA,MACvE;AAAA,IACD;AAEA,QAAI,CAAC,SAAS,IAAI;AACjB,UAAI,eAAe,6BAA6B,SAAS,MAAM;AAC/D,UAAI;AACJ,UAAI;AACJ,UAAI,SAAS,SAAS,IAAI,GAAG;AAC5B,YAAI,OAAO,SAAS,KAAK,UAAU,UAAU;AAC5C,yBAAe,SAAS,KAAK;AAC7B,wBAAc;AAAA,QACf,WAAW,OAAO,SAAS,KAAK,YAAY,UAAU;AACrD,yBAAe,SAAS,KAAK;AAC7B,wBAAc;AAAA,QACf;AACA,YAAI,OAAO,SAAS,KAAK,SAAS,SAAU,cAAa,SAAS,KAAK;AAAA,MACxE;AAEA,YAAM,IAAI,UAAU,cAAc,qBAAqB;AAAA,QACtD;AAAA,QACA,QAAQ,SAAS;AAAA,QACjB;AAAA,QACA;AAAA,MACD,CAAC;AAAA,IACF;AAEA,QAAI,CAAC,SAAS,SAAS,IAAI,KAAK,CAAC,MAAM,QAAQ,SAAS,IAAI,GAAG;AAC9D,YAAM,IAAI;AAAA,QACT,gDAAgD,IAAI;AAAA,QACpD;AAAA,QACA,EAAE,MAAM,QAAQ,SAAS,OAAO;AAAA,MACjC;AAAA,IACD;AACA,UAAM,OAAO,SAAS;AAItB,WAAQ,KAAK,SAAS,SAAY,KAAK,OAAO;AAAA,EAC/C;AACD;;;ACvjDA,SAAS,aAAAA,kBAAiB;;;ACA1B,SAAS,aAAAC,kBAAiB;AAQnB,IAAM,sBAAN,cAAkCA,WAAU;AAAA,EAClD,YAAY,SAAiB,SAAmC;AAC/D,UAAM,SAAS,0BAA0B,OAAO;AAChD,SAAK,OAAO;AAAA,EACb;AACD;AA4DA,IAAM,oBAAoB;AAG1B,IAAM,iBAAiB;AAGvB,IAAM,cAAc;AAyBb,IAAM,0BAAN,MAAwD;AAAA,EACtD,YAAyC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASzC,eAAqC;AAG5C,QAAI,KAAK,cAAc,MAAM;AAC5B,aAAO,KAAK;AAAA,IACb;AAEA,SAAK,YAAY,IAAI,QAAqB,CAAC,SAAS,WAAW;AAC9D,UAAI;AAEJ,UAAI;AACH,kBAAU,WAAW,UAAU,KAAK,mBAAmB,WAAW;AAAA,MACnE,SAAS,OAAO;AACf,aAAK,YAAY;AACjB;AAAA,UACC,IAAI;AAAA,YACH;AAAA,YAEA,EAAE,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE;AAAA,UACjE;AAAA,QACD;AACA;AAAA,MACD;AAEA,cAAQ,kBAAkB,MAAM;AAC/B,cAAM,KAAK,QAAQ;AACnB,YAAI,CAAC,GAAG,iBAAiB,SAAS,cAAc,GAAG;AAClD,aAAG,kBAAkB,cAAc;AAAA,QACpC;AAAA,MACD;AAEA,cAAQ,YAAY,MAAM;AACzB,gBAAQ,QAAQ,MAAM;AAAA,MACvB;AAEA,cAAQ,UAAU,MAAM;AACvB,aAAK,YAAY;AACjB;AAAA,UACC,IAAI,oBAAoB,6DAA6D;AAAA,YACpF,OAAO,QAAQ,OAAO;AAAA,UACvB,CAAC;AAAA,QACF;AAAA,MACD;AAEA,cAAQ,YAAY,MAAM;AACzB,aAAK,YAAY;AACjB;AAAA,UACC,IAAI;AAAA,YACH;AAAA,UAGD;AAAA,QACD;AAAA,MACD;AAAA,IACD,CAAC;AAED,WAAO,KAAK;AAAA,EACb;AAAA;AAAA,EAGA,MAAM,YAAY,UAAkB,SAAuC;AAC1E,UAAM,KAAK,MAAM,KAAK,aAAa;AAEnC,WAAO,IAAI,QAAc,CAAC,SAAS,WAAW;AAC7C,UAAI;AACH,cAAM,KAAK,GAAG,YAAY,gBAAgB,WAAW;AACrD,cAAM,QAAQ,GAAG,YAAY,cAAc;AAI3C,cAAM,IAAI,SAAS,QAAQ;AAE3B,WAAG,aAAa,MAAM;AACrB,kBAAQ;AAAA,QACT;AAEA,WAAG,UAAU,MAAM;AAClB;AAAA,YACC,IAAI,oBAAoB,uCAAuC,QAAQ,MAAM;AAAA,cAC5E;AAAA,cACA,OAAO,GAAG,OAAO;AAAA,YAClB,CAAC;AAAA,UACF;AAAA,QACD;AAAA,MACD,SAAS,OAAO;AACf;AAAA,UACC,IAAI,oBAAoB,uCAAuC,QAAQ,MAAM;AAAA,YAC5E;AAAA,YACA,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,UAC7D,CAAC;AAAA,QACF;AAAA,MACD;AAAA,IACD,CAAC;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,YAAY,UAAiD;AAClE,UAAM,KAAK,MAAM,KAAK,aAAa;AAEnC,WAAO,IAAI,QAA8B,CAAC,SAAS,WAAW;AAC7D,UAAI;AACH,cAAM,KAAK,GAAG,YAAY,gBAAgB,UAAU;AACpD,cAAM,QAAQ,GAAG,YAAY,cAAc;AAC3C,cAAM,UAAU,MAAM,IAAI,QAAQ;AAElC,gBAAQ,YAAY,MAAM;AACzB,gBAAM,SAAS,QAAQ;AACvB,kBAAQ,UAAU,IAAI;AAAA,QACvB;AAEA,gBAAQ,UAAU,MAAM;AACvB;AAAA,YACC,IAAI,oBAAoB,uCAAuC,QAAQ,MAAM;AAAA,cAC5E;AAAA,cACA,OAAO,QAAQ,OAAO;AAAA,YACvB,CAAC;AAAA,UACF;AAAA,QACD;AAAA,MACD,SAAS,OAAO;AACf;AAAA,UACC,IAAI,oBAAoB,uCAAuC,QAAQ,MAAM;AAAA,YAC5E;AAAA,YACA,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,UAC7D,CAAC;AAAA,QACF;AAAA,MACD;AAAA,IACD,CAAC;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,cAAc,UAAiC;AACpD,UAAM,KAAK,MAAM,KAAK,aAAa;AAEnC,WAAO,IAAI,QAAc,CAAC,SAAS,WAAW;AAC7C,UAAI;AACH,cAAM,KAAK,GAAG,YAAY,gBAAgB,WAAW;AACrD,cAAM,QAAQ,GAAG,YAAY,cAAc;AAC3C,cAAM,OAAO,QAAQ;AAErB,WAAG,aAAa,MAAM;AACrB,kBAAQ;AAAA,QACT;AAEA,WAAG,UAAU,MAAM;AAClB;AAAA,YACC,IAAI,oBAAoB,yCAAyC,QAAQ,MAAM;AAAA,cAC9E;AAAA,cACA,OAAO,GAAG,OAAO;AAAA,YAClB,CAAC;AAAA,UACF;AAAA,QACD;AAAA,MACD,SAAS,OAAO;AACf;AAAA,UACC,IAAI,oBAAoB,yCAAyC,QAAQ,MAAM;AAAA,YAC9E;AAAA,YACA,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,UAC7D,CAAC;AAAA,QACF;AAAA,MACD;AAAA,IACD,CAAC;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,WAAW,UAAoC;AACpD,UAAM,KAAK,MAAM,KAAK,aAAa;AAEnC,WAAO,IAAI,QAAiB,CAAC,SAAS,WAAW;AAChD,UAAI;AACH,cAAM,KAAK,GAAG,YAAY,gBAAgB,UAAU;AACpD,cAAM,QAAQ,GAAG,YAAY,cAAc;AAI3C,cAAM,UAAU,MAAM,MAAM,QAAQ;AAEpC,gBAAQ,YAAY,MAAM;AACzB,kBAAQ,QAAQ,SAAS,CAAC;AAAA,QAC3B;AAEA,gBAAQ,UAAU,MAAM;AACvB;AAAA,YACC,IAAI;AAAA,cACH,kDAAkD,QAAQ;AAAA,cAC1D,EAAE,UAAU,OAAO,QAAQ,OAAO,QAAQ;AAAA,YAC3C;AAAA,UACD;AAAA,QACD;AAAA,MACD,SAAS,OAAO;AACf;AAAA,UACC,IAAI,oBAAoB,kDAAkD,QAAQ,MAAM;AAAA,YACvF;AAAA,YACA,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,UAC7D,CAAC;AAAA,QACF;AAAA,MACD;AAAA,IACD,CAAC;AAAA,EACF;AACD;AAoBO,IAAM,yBAAN,MAAuD;AAAA,EAC5C,QAAQ,oBAAI,IAA2B;AAAA;AAAA,EAGxD,MAAM,YAAY,UAAkB,SAAuC;AAC1E,SAAK,MAAM,IAAI,UAAU,OAAO;AAAA,EACjC;AAAA;AAAA,EAGA,MAAM,YAAY,UAAiD;AAClE,WAAO,KAAK,MAAM,IAAI,QAAQ,KAAK;AAAA,EACpC;AAAA;AAAA,EAGA,MAAM,cAAc,UAAiC;AACpD,SAAK,MAAM,OAAO,QAAQ;AAAA,EAC3B;AAAA;AAAA,EAGA,MAAM,WAAW,UAAoC;AACpD,WAAO,KAAK,MAAM,IAAI,QAAQ;AAAA,EAC/B;AACD;AAoBO,SAAS,uBAAuC;AACtD,MAAI,OAAO,WAAW,cAAc,aAAa;AAChD,WAAO,IAAI,wBAAwB;AAAA,EACpC;AACA,SAAO,IAAI,uBAAuB;AACnC;;;ADrXA,IAAM,wBAAwB;AAwBvB,IAAM,0BAAN,cAAsCC,WAAU;AAAA,EACtD,YAAY,SAAiB,SAAmC;AAC/D,UAAM,SAAS,8BAA8B,OAAO;AACpD,SAAK,OAAO;AAAA,EACb;AACD;AAUO,SAAS,+BACf,SAC6B;AAC7B,QAAM,UAAU,QAAQ;AACxB,QAAM,WAAW,QAAQ,YAAY,gCAAgC;AACrE,QAAM,cAAc,QAAQ,eAAe;AAC3C,QAAM,mBAAmB,QAAQ,oBAAoB;AAErD,SAAO;AAAA,IACN,MAAM,oBAAiD;AACtD,UAAI,WAAW,MAAM,QAAQ,QAAQ,WAAW;AAChD,UAAI,CAAC,UAAU;AACd,mBAAW,iBAAiB;AAC5B,cAAM,QAAQ,QAAQ,aAAa,QAAQ;AAAA,MAC5C;AAEA,UAAI,UAAU,MAAM,SAAS,YAAY,QAAQ;AACjD,UAAI,CAAC,SAAS;AACb,kBAAU,MAAM,sBAAsB;AACtC,cAAM,SAAS,YAAY,UAAU,OAAO;AAAA,MAC7C;AAEA,YAAM,YAAY,MAAM,mBAAmB,OAAO;AAClD,aAAO;AAAA,QACN;AAAA,QACA,iBAAiB,KAAK,UAAU,SAAS;AAAA,MAC1C;AAAA,IACD;AAAA,EACD;AACD;AAEA,SAAS,kCAAkD;AAC1D,MAAI,OAAO,WAAW,cAAc,aAAa;AAChD,WAAO,qBAAqB;AAAA,EAC7B;AAEA,QAAM,IAAI;AAAA,IACT;AAAA,EACD;AACD;AAEA,SAAS,kBAA0B;AAClC,MAAI,OAAO,WAAW,QAAQ,eAAe,YAAY;AACxD,WAAO,WAAW,OAAO,WAAW;AAAA,EACrC;AAEA,QAAM,QAAQ,IAAI,WAAW,EAAE;AAC/B,MAAI,OAAO,WAAW,QAAQ,oBAAoB,YAAY;AAC7D,eAAW,OAAO,gBAAgB,KAAK;AAAA,EACxC,OAAO;AACN,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACtC,YAAM,CAAC,IAAI,KAAK,MAAM,KAAK,OAAO,IAAI,GAAG;AAAA,IAC1C;AAAA,EACD;AAEA,QAAM,CAAC,IAAM,MAAM,CAAC,IAAe,KAAQ;AAC3C,QAAM,CAAC,IAAM,MAAM,CAAC,IAAe,KAAQ;AAC3C,QAAM,MAAM,CAAC,GAAG,KAAK,EAAE,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AAChF,SAAO,GAAG,IAAI,MAAM,GAAG,CAAC,CAAC,IAAI,IAAI,MAAM,GAAG,EAAE,CAAC,IAAI,IAAI,MAAM,IAAI,EAAE,CAAC,IAAI,IAAI,MAAM,IAAI,EAAE,CAAC,IAAI,IAAI,MAAM,EAAE,CAAC;AACzG;;;AEzEO,SAAS,uBAAuB,SAAoD;AAC1F,QAAM,SAAS,QAAQ,UAAU;AACjC,QAAM,YAAY,GAAG,MAAM;AAC3B,QAAM,aAAa,GAAG,MAAM;AAC5B,QAAM,QAAQ,QAAQ;AAEtB,SAAO;AAAA,IACN,gBAAgB,MAAM,MAAM,QAAQ,SAAS;AAAA,IAC7C,iBAAiB,MAAM,MAAM,QAAQ,UAAU;AAAA,IAC/C,MAAM,UAAU,aAAqB,cAAqC;AACzE,YAAM,MAAM,QAAQ,WAAW,WAAW;AAC1C,YAAM,MAAM,QAAQ,YAAY,YAAY;AAAA,IAC7C;AAAA,IACA,MAAM,QAAuB;AAC5B,YAAM,MAAM,WAAW,SAAS;AAChC,YAAM,MAAM,WAAW,UAAU;AAAA,IAClC;AAAA,EACD;AACD;AAQO,SAAS,+BAAiD;AAChE,MAAI,cAA6B;AACjC,MAAI,eAA8B;AAElC,SAAO;AAAA,IACN,gBAAgB,MAAM;AAAA,IACtB,iBAAiB,MAAM;AAAA,IACvB,UAAU,QAAgB,SAAuB;AAChD,oBAAc;AACd,qBAAe;AAAA,IAChB;AAAA,IACA,QAAc;AACb,oBAAc;AACd,qBAAe;AAAA,IAChB;AAAA,EACD;AACD;AAKO,SAAS,iCACf,SACA,QACmB;AACnB,SAAO,uBAAuB;AAAA,IAC7B;AAAA,IACA,OAAO;AAAA,MACN,SAAS,CAAC,QAAQ,QAAQ,QAAQ,GAAG;AAAA,MACrC,SAAS,CAAC,KAAK,UAAU;AACxB,gBAAQ,QAAQ,KAAK,KAAK;AAAA,MAC3B;AAAA,MACA,YAAY,CAAC,QAAQ;AACpB,gBAAQ,WAAW,GAAG;AAAA,MACvB;AAAA,IACD;AAAA,EACD,CAAC;AACF;;;ACpDO,SAAS,eAAe,SAA4C;AAC1E,QAAM,UACL,QAAQ,YACP,QAAQ,kBACN,uBAAuB;AAAA,IACvB,OAAO,QAAQ;AAAA,IACf,QAAQ,QAAQ;AAAA,EACjB,CAAC,IACA,6BAA6B,QAAQ,UAAU;AAEnD,QAAM,iBACL,QAAQ,mBAAmB,QACxB,SACC,QAAQ,kBACV,+BAA+B,QAAQ,iBAAiB,QAAQ,cAAc;AAEjF,SAAO,IAAI,WAAW;AAAA,IACrB,WAAW,QAAQ;AAAA,IACnB,YAAY,QAAQ;AAAA,IACpB;AAAA,IACA,OAAO,QAAQ;AAAA,IACf;AAAA,IACA,kBAAkB,QAAQ;AAAA,IAC1B,mBAAmB,QAAQ;AAAA,IAC3B,gBAAgB,QAAQ;AAAA,EACzB,CAAC;AACF;AAEA,SAAS,6BACR,YAC+B;AAC/B,QAAM,UAAU,qBAAqB;AACrC,SAAO,UAAU,iCAAiC,SAAS,UAAU,IAAI;AAC1E;AAEA,SAAS,+BACR,iBACA,gBACyC;AACzC,QAAM,UAAU,mBAAmB,qBAAqB;AACxD,MAAI,CAAC,SAAS;AACb,WAAO;AAAA,EACR;AAEA,MAAI;AACH,WAAO,+BAA+B;AAAA,MACrC;AAAA,MACA,UAAU;AAAA,IACX,CAAC;AAAA,EACF,QAAQ;AACP,WAAO;AAAA,EACR;AACD;AAEA,SAAS,uBAAuC;AAC/C,MAAI;AACH,QAAI,OAAO,WAAW,iBAAiB,aAAa;AACnD,aAAO;AAAA,IACR;AACA,UAAM,MAAM;AACZ,eAAW,aAAa,QAAQ,KAAK,GAAG;AACxC,eAAW,aAAa,WAAW,GAAG;AACtC,WAAO,WAAW;AAAA,EACnB,QAAQ;AACP,WAAO;AAAA,EACR;AACD;;;AC3GA;AAAA,EAGC;AAAA,EACA;AAAA,OACM;AAyDP,SAASC,kBAAiB,OAA+C;AACxE,QAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,MAAI,MAAM,WAAW,GAAG;AACvB,WAAO;AAAA,EACR;AAEA,QAAM,iBAAiB,MAAM,CAAC;AAC9B,MAAI,mBAAmB,QAAW;AACjC,WAAO;AAAA,EACR;AAEA,MAAI;AACH,UAAM,SAAS,eAAe,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG;AAClE,UAAM,SAAS,OAAO,OAAO,OAAO,UAAW,IAAK,OAAO,SAAS,KAAM,GAAI,GAAG;AACjF,UAAM,OACL,OAAO,SAAS,aAAa,KAAK,MAAM,IAAI,OAAO,KAAK,QAAQ,QAAQ,EAAE,SAAS,OAAO;AAC3F,UAAM,SAAkB,KAAK,MAAM,IAAI;AACvC,QAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,MAAM,QAAQ,MAAM,GAAG;AAC3E,aAAO;AAAA,IACR;AACA,WAAO;AAAA,EACR,QAAQ;AACP,WAAO;AAAA,EACR;AACD;AAEA,SAAS,WAAW,QAAwC,KAAiC;AAC5F,QAAM,QAAQ,SAAS,GAAG;AAC1B,SAAO,OAAO,UAAU,YAAY,MAAM,SAAS,IAAI,QAAQ;AAChE;AA8BO,SAAS,mBAAmB,SAAqD;AACvF,QAAM,EAAE,YAAY,QAAQ,iBAAiB,YAAY,UAAU,IAAI;AAMvE,QAAM,gBAAgB,YAGhB;AACL,UAAM,QAAQ,MAAM,WAAW,eAAe;AAC9C,QAAI,MAAO,QAAO,EAAE,QAAQA,kBAAiB,KAAK,GAAG,MAAM;AAC3D,QAAI,WAAW,UAAU,qBAAqB,CAAC,WAAW,iBAAiB;AAC1E,aAAO,EAAE,QAAQ,MAAM,OAAO,KAAK;AAAA,IACpC;AACA,WAAO,EAAE,QAAQ,MAAM,WAAW,gBAAgB,GAAG,OAAO,KAAK;AAAA,EAClE;AAEA,QAAM,UAA2B;AAAA,IAChC,MAAM,OAAO,YAAY;AAGxB,YAAM,QACL,SAAS,gBAAgB,WAAW,qBACjC,MAAM,WAAW,mBAAmB,IACpC,MAAM,WAAW,eAAe;AACpC,aAAO,EAAE,OAAO,SAAS,GAAG;AAAA,IAC7B;AAAA,EACD;AAEA,UAAQ,mBAAmB,YAAoC;AAC9D,QAAI,WAAW,UAAU,UAAW,QAAO,EAAE,OAAO,UAAU;AAC9D,UAAM,EAAE,QAAQ,MAAM,IAAI,MAAM,cAAc;AAC9C,UAAM,SAAS,WAAW,QAAQ,KAAK;AACvC,QAAI,CAAC,QAAQ;AACZ,aAAO,cAAc,UAClB,EAAE,OAAO,aAAa,uBAAuB,KAAK,IAClD,EAAE,OAAO,cAAc,uBAAuB,MAAM;AAAA,IACxD;AACA,UAAM,WAAW,WAAW,QAAQ,KAAK;AACzC,QAAI,OAAO;AACV,aAAO,EAAE,OAAO,iBAAiB,QAAQ,OAAO,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC,EAAG;AAAA,IACnF;AACA,WAAO;AAAA,MACN,OAAO;AAAA,MACP;AAAA,MACA,OAAO;AAAA,MACP,SAAS;AAAA,MACT,QAAQ,WAAW,SAAS,WAAW;AAAA,MACvC,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;AAAA,IAChC;AAAA,EACD;AAEA,MAAI,QAAQ;AACX,YAAQ,kBAAkB,YAAY;AACrC,YAAM,EAAE,OAAO,IAAI,MAAM,cAAc;AACvC,UAAI,CAAC,QAAQ;AACZ,eAAO;AAAA,MACR;AAEA,YAAM,cAAc,kBACjB,gBAAgB,MAAM,IACtB,6BAA6B,QAAQ,MAAM;AAE9C,aAAO,cAAc,QAAQ,WAAW;AAAA,IACzC;AAAA,EACD;AAEA,UAAQ,gBAAgB,YAAY;AACnC,UAAM,EAAE,OAAO,IAAI,MAAM,cAAc;AACvC,WAAO,WAAW,QAAQ,KAAK;AAAA,EAChC;AAEA,UAAQ,gBAAgB,YAAY;AACnC,UAAM,EAAE,OAAO,IAAI,MAAM,cAAc;AACvC,WAAO,WAAW,QAAQ,KAAK;AAAA,EAChC;AAEA,MAAI,WAAW,gBAAgB,WAAW,iBAAiB;AAC1D,YAAQ,YAAY,CAAC,aAAa;AACjC,YAAM,UAAU,WAAW,eAAe,MAAM,SAAS,CAAC,MAAM,MAAM;AAAA,MAAC;AACvE,YAAM,aAAa,WAAW,kBAAkB,MAAM,SAAS,CAAC,MAAM,MAAM;AAAA,MAAC;AAC7E,aAAO,MAAM;AACZ,gBAAQ;AACR,mBAAW;AAAA,MACZ;AAAA,IACD;AAAA,EACD;AAEA,SAAO;AACR;;;ACpNA,SAAS,aAAAC,kBAAiB;AA2DnB,IAAM,iBAAN,cAA6BA,WAAU;AAAA,EAC7C,YAAY,SAAiB,MAAc,SAAmC;AAC7E,UAAM,SAAS,MAAM,OAAO;AAC5B,SAAK,OAAO;AAAA,EACb;AACD;AAuBO,IAAM,YAAN,MAAgB;AAAA,EACL;AAAA,EACA;AAAA,EACA,YAAY,oBAAI,IAAoC;AAAA,EAE7D,eAA8B;AAAA,EAC9B,aAAwC;AAAA,EACxC,cAA6B;AAAA,EAErC,YAAY,QAAyB;AACpC,SAAK,YAAY,OAAO,UAAU,QAAQ,QAAQ,EAAE;AACpD,SAAK,iBAAiB,OAAO;AAAA,EAC9B;AAAA;AAAA;AAAA,EAKA,IAAI,cAA6B;AAChC,WAAO,KAAK;AAAA,EACb;AAAA;AAAA,EAGA,IAAI,YAAuC;AAC1C,WAAO,KAAK;AAAA,EACb;AAAA;AAAA,EAGA,IAAI,aAA4B;AAC/B,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,UAAU,QAIgB;AAC/B,WAAO,KAAK,QAA4B,SAAS;AAAA,MAChD,QAAQ;AAAA,MACR,MAAM;AAAA,IACP,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAA0C;AAC/C,WAAO,KAAK,QAA8B,SAAS,EAAE,QAAQ,MAAM,CAAC;AAAA,EACrE;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAO,OAA4C;AACxD,WAAO,KAAK,QAA4B,SAAS,KAAK,IAAI,EAAE,QAAQ,MAAM,CAAC;AAAA,EAC5E;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UACL,OACA,QAC8B;AAC9B,UAAM,SAAS,MAAM,KAAK,QAA4B,SAAS,KAAK,IAAI;AAAA,MACvE,QAAQ;AAAA,MACR,MAAM;AAAA,IACP,CAAC;AAED,QAAI,KAAK,iBAAiB,OAAO;AAChC,WAAK,aAAa;AAAA,IACnB;AACA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAU,OAA8B;AAC7C,UAAM,KAAK,QAAQ,SAAS,KAAK,IAAI,EAAE,QAAQ,SAAS,CAAC;AACzD,QAAI,KAAK,iBAAiB,OAAO;AAChC,WAAK,eAAe;AACpB,WAAK,aAAa;AAClB,WAAK,cAAc;AACnB,WAAK,gBAAgB;AAAA,IACtB;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,UAAU,OAA8B;AAC7C,UAAM,MAAM,MAAM,KAAK,QAA4B,SAAS,KAAK,IAAI,EAAE,QAAQ,MAAM,CAAC;AACtF,UAAM,aAAa,MAAM,KAAK,QAA0B,SAAS,KAAK,eAAe;AAAA,MACpF,QAAQ;AAAA,IACT,CAAC;AAED,SAAK,eAAe;AACpB,SAAK,aAAa;AAClB,SAAK,cAAc,WAAW;AAC9B,SAAK,gBAAgB;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAuB;AACtB,SAAK,eAAe;AACpB,SAAK,aAAa;AAClB,SAAK,cAAc;AACnB,SAAK,gBAAgB;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,YAAY,OAA4C;AAC7D,WAAO,KAAK,QAA4B,SAAS,KAAK,YAAY,EAAE,QAAQ,MAAM,CAAC;AAAA,EACpF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,OAAe,QAA+B;AAChE,UAAM,KAAK,QAAQ,SAAS,KAAK,YAAY,MAAM,IAAI,EAAE,QAAQ,SAAS,CAAC;AAAA,EAC5E;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,iBAAiB,OAAe,QAAgB,MAAyC;AAC9F,WAAO,KAAK,QAA0B,SAAS,KAAK,YAAY,MAAM,SAAS;AAAA,MAC9E,QAAQ;AAAA,MACR,MAAM,EAAE,KAAK;AAAA,IACd,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,kBAAkB,OAAe,YAAmC;AACzE,UAAM,KAAK,QAAQ,SAAS,KAAK,aAAa;AAAA,MAC7C,QAAQ;AAAA,MACR,MAAM,EAAE,WAAW;AAAA,IACpB,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SAAS,OAA8B;AAC5C,UAAM,KAAK,QAAQ,SAAS,KAAK,UAAU,EAAE,QAAQ,OAAO,CAAC;AAC7D,QAAI,KAAK,iBAAiB,OAAO;AAChC,WAAK,eAAe;AACpB,WAAK,aAAa;AAClB,WAAK,cAAc;AACnB,WAAK,gBAAgB;AAAA,IACtB;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,aACL,OACA,QAC4B;AAC5B,WAAO,KAAK,QAA0B,SAAS,KAAK,gBAAgB;AAAA,MACnE,QAAQ;AAAA,MACR,MAAM;AAAA,IACP,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,iBAAiB,OAA0C;AAChE,WAAO,KAAK,QAA0B,uBAAuB;AAAA,MAC5D,QAAQ;AAAA,MACR,MAAM,EAAE,MAAM;AAAA,IACf,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,gBAAgB,OAA4C;AACjE,WAAO,KAAK,QAA4B,SAAS,KAAK,gBAAgB,EAAE,QAAQ,MAAM,CAAC;AAAA,EACxF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,iBAAiB,OAAe,cAAqC;AAC1E,UAAM,KAAK,QAAQ,SAAS,KAAK,gBAAgB,YAAY,IAAI,EAAE,QAAQ,SAAS,CAAC;AAAA,EACtF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,oBAAqE;AAC1E,WAAO,KAAK,QAAgD,gBAAgB;AAAA,MAC3E,QAAQ;AAAA,IACT,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,YAAY,UAAsD;AACjE,SAAK,UAAU,IAAI,QAAQ;AAC3B,WAAO,MAAM;AACZ,WAAK,UAAU,OAAO,QAAQ;AAAA,IAC/B;AAAA,EACD;AAAA;AAAA,EAIQ,kBAAwB;AAC/B,eAAW,YAAY,KAAK,WAAW;AACtC,UAAI;AACH,iBAAS,KAAK,YAAY;AAAA,MAC3B,QAAQ;AAAA,MAER;AAAA,IACD;AAAA,EACD;AAAA,EAEA,MAAc,QACb,MACA,SAIa;AACb,UAAM,QAAQ,MAAM,KAAK,eAAe;AACxC,QAAI,CAAC,OAAO;AACX,YAAM,IAAI;AAAA,QACT;AAAA,QACA;AAAA,MACD;AAAA,IACD;AAEA,UAAM,MAAM,GAAG,KAAK,SAAS,GAAG,IAAI;AACpC,UAAM,UAAkC;AAAA,MACvC,eAAe,UAAU,KAAK;AAAA,IAC/B;AACA,QAAI,QAAQ,MAAM;AACjB,cAAQ,cAAc,IAAI;AAAA,IAC3B;AAEA,QAAI;AACJ,QAAI;AACH,iBAAW,MAAM,MAAM,KAAK;AAAA,QAC3B,QAAQ,QAAQ;AAAA,QAChB;AAAA,QACA,MAAM,QAAQ,OAAO,KAAK,UAAU,QAAQ,IAAI,IAAI;AAAA,MACrD,CAAC;AAAA,IACF,SAAS,OAAO;AACf,YAAM,IAAI,eAAe,sBAAsB,IAAI,YAAY,qBAAqB;AAAA,QACnF;AAAA,QACA,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,MAC7D,CAAC;AAAA,IACF;AAEA,QAAI,CAAC,SAAS,IAAI;AACjB,UAAI,eAAe,wBAAwB,SAAS,MAAM;AAC1D,UAAI;AACH,cAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,YAAI,OAAO,KAAK,UAAU,UAAU;AACnC,yBAAe,KAAK;AAAA,QACrB;AAAA,MACD,QAAQ;AAAA,MAER;AACA,YAAM,IAAI,eAAe,cAAc,oBAAoB;AAAA,QAC1D;AAAA,QACA,QAAQ,SAAS;AAAA,MAClB,CAAC;AAAA,IACF;AAGA,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,QAAI,KAAK,WAAW,EAAG,QAAO;AAE9B,QAAI;AACH,YAAM,OAAO,KAAK,MAAM,IAAI;AAE5B,UAAI,QAAQ,OAAO,SAAS,YAAY,UAAU,MAAM;AACvD,eAAO,KAAK;AAAA,MACb;AACA,aAAO;AAAA,IACR,QAAQ;AACP,aAAO;AAAA,IACR;AAAA,EACD;AACD;;;AC3YA,IAAM,sBAAsB;AAiB5B,IAAM,gBAAN,MAA6C;AAAA,EACpC,QAAQ,oBAAI,IAAoB;AAAA,EAExC,QAAQ,KAA4B;AACnC,WAAO,KAAK,MAAM,IAAI,GAAG,KAAK;AAAA,EAC/B;AAAA,EAEA,QAAQ,KAAa,OAAqB;AACzC,SAAK,MAAM,IAAI,KAAK,KAAK;AAAA,EAC1B;AAAA,EAEA,WAAW,KAAmB;AAC7B,SAAK,MAAM,OAAO,GAAG;AAAA,EACtB;AACD;AAWA,SAAS,qBAA2C;AACnD,MAAI;AACH,QAAI,OAAO,eAAe,eAAe,kBAAkB,YAAY;AACtE,YAAM,UAAU,WAAW;AAE3B,YAAM,UAAU;AAChB,cAAQ,QAAQ,SAAS,GAAG;AAC5B,cAAQ,WAAW,OAAO;AAC1B,aAAO;AAAA,IACR;AAAA,EACD,QAAQ;AAAA,EAER;AACA,SAAO;AACR;AA0BO,IAAM,aAAN,MAAiB;AAAA,EACN;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQjB,YAAY,YAAqB;AAChC,SAAK,aAAa,cAAc;AAChC,SAAK,UAAU,mBAAmB,KAAK,IAAI,cAAc;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,WAAW,QAA0B;AACpC,UAAM,aAAyB;AAAA,MAC9B,aAAa,OAAO;AAAA,MACpB,cAAc,OAAO;AAAA,IACtB;AACA,QAAI,OAAO,qBAAqB,QAAW;AAC1C,iBAAW,mBAAmB,OAAO;AAAA,IACtC;AACA,SAAK,QAAQ,QAAQ,KAAK,YAAY,KAAK,UAAU,UAAU,CAAC;AAAA,EACjE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,aAAgC;AAC/B,UAAM,MAAM,KAAK,QAAQ,QAAQ,KAAK,UAAU;AAChD,QAAI,QAAQ,MAAM;AACjB,aAAO;AAAA,IACR;AAEA,QAAI;AACH,YAAM,SAAkB,KAAK,MAAM,GAAG;AACtC,UAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,MAAM,QAAQ,MAAM,GAAG;AAC3E,eAAO;AAAA,MACR;AACA,YAAM,SAAS;AAGf,UAAI,OAAO,OAAO,gBAAgB,YAAY,OAAO,OAAO,iBAAiB,UAAU;AACtF,eAAO;AAAA,MACR;AAEA,YAAM,SAAqB;AAAA,QAC1B,aAAa,OAAO;AAAA,QACpB,cAAc,OAAO;AAAA,MACtB;AAEA,UAAI,OAAO,OAAO,qBAAqB,UAAU;AAChD,eAAO,mBAAmB,OAAO;AAAA,MAClC;AAEA,aAAO;AAAA,IACR,QAAQ;AAEP,aAAO;AAAA,IACR;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,cAAoB;AACnB,SAAK,QAAQ,WAAW,KAAK,UAAU;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,iBAAgC;AAC/B,UAAM,SAAS,KAAK,WAAW;AAC/B,WAAO,QAAQ,eAAe;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,kBAAiC;AAChC,UAAM,SAAS,KAAK,WAAW;AAC/B,WAAO,QAAQ,gBAAgB;AAAA,EAChC;AACD;;;AC/LA,SAAS,aAAAC,kBAAiB;AAWnB,IAAM,2BAAN,cAAuCC,WAAU;AAAA,EACvD,YAAY,SAAiB,SAAmC;AAC/D,UAAM,SAAS,+BAA+B,OAAO;AACrD,SAAK,OAAO;AAAA,EACb;AACD;AAWA,SAASC,aAAY,OAA2B;AAC/C,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACtC,cAAU,OAAO,aAAa,MAAM,CAAC,CAAW;AAAA,EACjD;AACA,SAAO,KAAK,MAAM,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,EAAE;AAC9E;AAQA,SAASC,eAAc,KAAyB;AAC/C,MAAI,SAAS,IAAI,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG;AACrD,QAAM,iBAAiB,IAAK,OAAO,SAAS,KAAM;AAClD,YAAU,IAAI,OAAO,aAAa;AAElC,QAAM,SAAS,KAAK,MAAM;AAC1B,QAAM,QAAQ,IAAI,WAAW,OAAO,MAAM;AAC1C,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACvC,UAAM,CAAC,IAAI,OAAO,WAAW,CAAC;AAAA,EAC/B;AACA,SAAO;AACR;AAmBA,IAAMC,iBAAN,MAA6C;AAAA,EACpC,QAAQ,oBAAI,IAAoB;AAAA,EAExC,QAAQ,KAA4B;AACnC,WAAO,KAAK,MAAM,IAAI,GAAG,KAAK;AAAA,EAC/B;AAAA,EAEA,QAAQ,KAAa,OAAqB;AACzC,SAAK,MAAM,IAAI,KAAK,KAAK;AAAA,EAC1B;AAAA,EAEA,WAAW,KAAmB;AAC7B,SAAK,MAAM,OAAO,GAAG;AAAA,EACtB;AACD;AAWA,SAASC,sBAA2C;AACnD,MAAI;AACH,QAAI,OAAO,eAAe,eAAe,kBAAkB,YAAY;AACtE,YAAM,UAAU,WAAW;AAC3B,YAAM,UAAU;AAChB,cAAQ,QAAQ,SAAS,GAAG;AAC5B,cAAQ,WAAW,OAAO;AAC1B,aAAO;AAAA,IACR;AAAA,EACD,QAAQ;AAAA,EAER;AACA,SAAO;AACR;AAKA,IAAMC,uBAAsB;AAsErB,IAAM,sBAAN,MAA0B;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOjB,YAAY,QAAmC;AAC9C,SAAK,aAAa,OAAO,cAAcA;AACvC,SAAK,MAAM,OAAO;AAClB,SAAK,UAAUD,oBAAmB,KAAK,IAAID,eAAc;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,MAAM,WAAW,QAAmC;AAEnD,UAAM,aAAyB;AAAA,MAC9B,aAAa,OAAO;AAAA,MACpB,cAAc,OAAO;AAAA,IACtB;AACA,QAAI,OAAO,qBAAqB,QAAW;AAC1C,iBAAW,mBAAmB,OAAO;AAAA,IACtC;AAEA,UAAM,YAAY,IAAI,YAAY,EAAE,OAAO,KAAK,UAAU,UAAU,CAAC;AAErE,QAAI;AACH,YAAM,EAAE,YAAY,GAAG,IAAI,MAAM,YAAY,KAAK,KAAK,SAAS;AAEhE,YAAM,UAA4B;AAAA,QACjC,IAAIF,aAAY,EAAE;AAAA,QAClB,MAAMA,aAAY,UAAU;AAAA,MAC7B;AAEA,WAAK,QAAQ,QAAQ,KAAK,YAAY,KAAK,UAAU,OAAO,CAAC;AAAA,IAC9D,SAAS,OAAO;AAEf,UAAI,iBAAiB,0BAA0B;AAC9C,cAAM;AAAA,MACP;AAEA,YAAM,IAAI;AAAA,QACT;AAAA,QAEA,EAAE,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE;AAAA,MACjE;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBA,MAAM,aAAyC;AAC9C,UAAM,MAAM,KAAK,QAAQ,QAAQ,KAAK,UAAU;AAChD,QAAI,QAAQ,MAAM;AACjB,aAAO;AAAA,IACR;AAEA,QAAI;AAEH,YAAM,SAAkB,KAAK,MAAM,GAAG;AACtC,UAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,MAAM,QAAQ,MAAM,GAAG;AAC3E,eAAO;AAAA,MACR;AAEA,YAAM,SAAS;AACf,UAAI,OAAO,OAAO,OAAO,YAAY,OAAO,OAAO,SAAS,UAAU;AACrE,eAAO;AAAA,MACR;AAGA,YAAM,KAAKC,eAAc,OAAO,EAAE;AAClC,YAAM,aAAaA,eAAc,OAAO,IAAI;AAG5C,YAAM,iBAAiB,MAAM,YAAY,KAAK,KAAK,YAAY,EAAE;AACjE,YAAM,OAAO,IAAI,YAAY,EAAE,OAAO,cAAc;AAGpD,YAAM,YAAqB,KAAK,MAAM,IAAI;AAC1C,UAAI,OAAO,cAAc,YAAY,cAAc,QAAQ,MAAM,QAAQ,SAAS,GAAG;AACpF,eAAO;AAAA,MACR;AAEA,YAAM,cAAc;AACpB,UACC,OAAO,YAAY,gBAAgB,YACnC,OAAO,YAAY,iBAAiB,UACnC;AACD,eAAO;AAAA,MACR;AAEA,YAAM,SAAqB;AAAA,QAC1B,aAAa,YAAY;AAAA,QACzB,cAAc,YAAY;AAAA,MAC3B;AAEA,UAAI,OAAO,YAAY,qBAAqB,UAAU;AACrD,eAAO,mBAAmB,YAAY;AAAA,MACvC;AAEA,aAAO;AAAA,IACR,QAAQ;AAIP,aAAO;AAAA,IACR;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,cAAoB;AACnB,SAAK,QAAQ,WAAW,KAAK,UAAU;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,iBAAyC;AAC9C,UAAM,SAAS,MAAM,KAAK,WAAW;AACrC,WAAO,QAAQ,eAAe;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,kBAA0C;AAC/C,UAAM,SAAS,MAAM,KAAK,WAAW;AACrC,WAAO,QAAQ,gBAAgB;AAAA,EAChC;AACD;;;ACvWA,SAAS,aAAAI,kBAAiB;AAOnB,IAAM,qBAAN,cAAiCA,WAAU;AAAA,EACjD,YAAY,SAAiB,SAAmC;AAC/D,UAAM,SAAS,wBAAwB,OAAO;AAC9C,SAAK,OAAO;AAAA,EACb;AACD;AAKA,IAAM,cAAc;AAMpB,IAAM,oBAAoB;AAG1B,IAAM,qBAAqB;AAK3B,SAAS,wBAA8B;AACtC,MAAI,OAAO,WAAW,WAAW,eAAe,OAAO,WAAW,OAAO,WAAW,aAAa;AAChG,UAAM,IAAI;AAAA,MACT;AAAA,IAED;AAAA,EACD;AACD;AAkBO,SAAS,eAA2B;AAC1C,MAAI,OAAO,WAAW,WAAW,aAAa;AAC7C,UAAM,IAAI;AAAA,MACT;AAAA,IACD;AAAA,EACD;AAEA,SAAO,WAAW,OAAO,gBAAgB,IAAI,WAAW,WAAW,CAAC;AACrE;AA+BA,eAAsB,oBACrB,YACA,MACgD;AAChD,wBAAsB;AAEtB,MAAI,WAAW,WAAW,GAAG;AAC5B,UAAM,IAAI;AAAA,MACT;AAAA,IACD;AAAA,EACD;AAEA,QAAM,WAAW,QAAQ,aAAa;AAEtC,MAAI;AAEH,UAAM,kBAAkB,IAAI,YAAY,EAAE,OAAO,UAAU;AAC3D,UAAM,UAAU,MAAM,WAAW,OAAO,OAAO;AAAA,MAC9C;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,CAAC,cAAc,WAAW;AAAA,IAC3B;AAGA,UAAM,aAAa,MAAM,WAAW,OAAO,OAAO;AAAA,MACjD;AAAA,QACC,MAAM;AAAA,QACN,MAAM;AAAA,QACN,YAAY;AAAA,QACZ,MAAM;AAAA,MACP;AAAA,MACA;AAAA,MACA,EAAE,MAAM,WAAW,QAAQ,mBAAmB;AAAA;AAAA,MAE9C;AAAA,MACA,CAAC,WAAW,SAAS;AAAA,IACtB;AAEA,WAAO,EAAE,KAAK,YAAY,MAAM,SAAS;AAAA,EAC1C,SAAS,OAAO;AAEf,QAAI,iBAAiB,oBAAoB;AACxC,YAAM;AAAA,IACP;AAEA,UAAM,IAAI;AAAA,MACT;AAAA,MAEA,EAAE,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE;AAAA,IACjE;AAAA,EACD;AACD;;;ACvGO,IAAM,kBAAN,MAAsB;AAAA,EACX;AAAA,EACA;AAAA,EACT,WAAiD;AAAA,EACjD,YAAY;AAAA,EACZ,aAAa;AAAA,EAErB,YAAY,QAAwB;AACnC,QAAI,OAAO,WAAW,GAAG;AACxB,YAAM,IAAI;AAAA,QACT,mEAAmE,OAAO,OAAO;AAAA,MAClF;AAAA,IACD;AAEA,SAAK,WAAW,OAAO;AACvB,SAAK,UAAU,OAAO;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,IAAI,WAAoB;AACvB,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,QAAc;AACb,QAAI,KAAK,YAAY;AACpB;AAAA,IACD;AAEA,SAAK,aAAa;AAClB,SAAK,YAAY;AACjB,SAAK,YAAY;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,OAAa;AACZ,SAAK,aAAa;AAClB,SAAK,YAAY;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,iBAAuB;AACtB,QAAI,CAAC,KAAK,cAAc,KAAK,WAAW;AACvC;AAAA,IACD;AAEA,SAAK,YAAY;AACjB,SAAK,YAAY;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,OAAa;AACZ,SAAK,YAAY;AAEjB,QAAI,CAAC,KAAK,WAAW;AACpB,WAAK,YAAY;AACjB,WAAK,QAAQ;AAAA,IACd;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,SAAe;AACd,SAAK,YAAY;AAEjB,QAAI,KAAK,YAAY;AACpB,WAAK,YAAY;AACjB,WAAK,YAAY;AAAA,IAClB;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAoB;AAC3B,SAAK,WAAW,WAAW,MAAM;AAChC,WAAK,WAAW;AAChB,WAAK,YAAY;AACjB,WAAK,QAAQ;AAAA,IACd,GAAG,KAAK,QAAQ;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAoB;AAC3B,QAAI,KAAK,aAAa,MAAM;AAC3B,mBAAa,KAAK,QAAQ;AAC1B,WAAK,WAAW;AAAA,IACjB;AAAA,EACD;AACD;","names":["KoraError","KoraError","KoraError","decodeJwtPayload","KoraError","KoraError","KoraError","toBase64Url","fromBase64Url","MemoryStorage","tryGetLocalStorage","DEFAULT_STORAGE_KEY","KoraError"]}