@dloizides/auth-client 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/buildKeycloakUrls.ts","../src/utils/isTokenExpired.ts","../src/utils/parseRealmFromIssuer.ts","../src/AuthClient.ts","../src/types/KeycloakRoles.ts","../src/storage/BrowserStorageTokenStorage.ts","../src/storage/InMemoryTokenStorage.ts","../src/utils/normalizeKeycloakUser.ts","../src/utils/buildTokenRequestBody.ts","../src/utils/extractAuthCode.ts","../src/utils/decodeJwt.ts","../src/utils/normalizeTokenResponse.ts"],"names":["KeycloakRoles"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * URL builders for the realm-aware Keycloak surface area.\n *\n * Every helper takes `baseUrl` and `realm` explicitly — no hardcoded realm\n * names. This is the contract that Phase 2 of the product split relies on:\n * the same package serves the future Questioner-realm app and OnlineMenu-realm\n * app without code change.\n */\n\nconst REALM_PATH_PREFIX = '/realms';\nconst PROTOCOL_PATH = '/protocol/openid-connect';\n\nfunction trimTrailingSlash(value: string): string {\n return value.replace(/\\/$/, '');\n}\n\n/**\n * Compute the issuer URL: `{baseUrl}/realms/{realm}`.\n */\nexport function buildIssuerUrl(baseUrl: string, realm: string): string {\n return `${trimTrailingSlash(baseUrl)}${REALM_PATH_PREFIX}/${encodeURIComponent(realm)}`;\n}\n\n/**\n * Compute the authorization endpoint URL.\n */\nexport function buildAuthorizationEndpoint(baseUrl: string, realm: string): string {\n return `${buildIssuerUrl(baseUrl, realm)}${PROTOCOL_PATH}/auth`;\n}\n\n/**\n * Compute the token endpoint URL.\n */\nexport function buildTokenEndpoint(baseUrl: string, realm: string): string {\n return `${buildIssuerUrl(baseUrl, realm)}${PROTOCOL_PATH}/token`;\n}\n\n/**\n * Compute the userinfo endpoint URL.\n */\nexport function buildUserInfoEndpoint(baseUrl: string, realm: string): string {\n return `${buildIssuerUrl(baseUrl, realm)}${PROTOCOL_PATH}/userinfo`;\n}\n\n/**\n * Compute the logout endpoint URL.\n */\nexport function buildLogoutEndpoint(baseUrl: string, realm: string): string {\n return `${buildIssuerUrl(baseUrl, realm)}${PROTOCOL_PATH}/logout`;\n}\n\nexport interface AuthorizationUrlInput {\n baseUrl: string;\n realm: string;\n clientId: string;\n redirectUri: string;\n scope?: string;\n state?: string;\n codeChallenge?: string;\n codeChallengeMethod?: 'S256' | 'plain';\n}\n\n/**\n * Build a complete authorization URL the user agent can navigate to.\n *\n * All PKCE-related fields are optional so this helper also serves\n * non-PKCE flows (e.g. confidential server-side clients) — but PKCE is\n * the recommended path for SPA / native consumers.\n */\nexport function buildAuthorizationUrl(input: AuthorizationUrlInput): string {\n const params = new URLSearchParams({\n client_id: input.clientId,\n redirect_uri: input.redirectUri,\n response_type: 'code',\n });\n if (typeof input.scope === 'string' && input.scope !== '') {\n params.set('scope', input.scope);\n }\n if (typeof input.state === 'string' && input.state !== '') {\n params.set('state', input.state);\n }\n if (typeof input.codeChallenge === 'string' && input.codeChallenge !== '') {\n params.set('code_challenge', input.codeChallenge);\n params.set('code_challenge_method', input.codeChallengeMethod ?? 'S256');\n }\n return `${buildAuthorizationEndpoint(input.baseUrl, input.realm)}?${params.toString()}`;\n}\n","import type { AuthTokens } from '../types/AuthTokens';\n\nconst SECONDS_TO_MILLIS = 1000;\nconst DEFAULT_LEEWAY_MS = 30 * SECONDS_TO_MILLIS;\n\n/**\n * Determine whether a token bundle is expired.\n *\n * `expiresAt` is interpreted as an absolute UNIX millisecond timestamp.\n *\n * `leewayMs` (default 30 s) shaves a small window off the expiry to compensate\n * for clock skew and round-trip latency: a token that expires at exactly\n * `Date.now()` is essentially useless because by the time the request arrives\n * at the API it will have expired.\n */\nexport function isTokenExpired(\n tokens: Pick<AuthTokens, 'expiresAt'> | null | undefined,\n leewayMs: number = DEFAULT_LEEWAY_MS,\n now: number = Date.now(),\n): boolean {\n if (!tokens) {\n return true;\n }\n if (typeof tokens.expiresAt !== 'number' || tokens.expiresAt <= 0) {\n return true;\n }\n return tokens.expiresAt - leewayMs <= now;\n}\n\n/**\n * Compute the absolute expiry timestamp from a token endpoint `expires_in` value.\n *\n * Returns `0` when `expiresIn` is missing or non-positive — signalling \"unknown\n * expiry, treat as expired\" downstream.\n */\nexport function computeExpiresAt(\n expiresInSeconds: number | undefined,\n now: number = Date.now(),\n): number {\n if (typeof expiresInSeconds !== 'number' || expiresInSeconds <= 0) {\n return 0;\n }\n return now + expiresInSeconds * SECONDS_TO_MILLIS;\n}\n","/**\n * Extract the realm name from a Keycloak issuer URL.\n *\n * Keycloak issuer URLs follow the shape `{baseUrl}/realms/{realm}` (with optional\n * `/protocol/openid-connect` suffix on token endpoints). This helper reverses\n * that convention so existing apps that store only the issuer URL can derive\n * `realm` for the realm-aware {@link AuthClient} constructor.\n *\n * @returns the realm name, or `null` if the URL doesn't match the convention.\n */\nexport function parseRealmFromIssuer(issuerUrl: string | null | undefined): string | null {\n if (typeof issuerUrl !== 'string' || issuerUrl === '') {\n return null;\n }\n const match = /\\/realms\\/([^/?#]+)/i.exec(issuerUrl);\n if (!match || match[1] === undefined || match[1] === '') {\n return null;\n }\n return decodeURIComponent(match[1]);\n}\n\n/**\n * Extract the base URL (scheme + host + optional path prefix) from a\n * Keycloak issuer URL by stripping the `/realms/{realm}...` suffix.\n *\n * Returns the original input unchanged when no `/realms/` segment is found —\n * callers that need strict validation should pair this with `parseRealmFromIssuer`.\n */\nexport function parseBaseUrlFromIssuer(issuerUrl: string | null | undefined): string | null {\n if (typeof issuerUrl !== 'string' || issuerUrl === '') {\n return null;\n }\n const idx = issuerUrl.search(/\\/realms\\//i);\n if (idx === -1) {\n return issuerUrl.replace(/\\/$/, '');\n }\n return issuerUrl.substring(0, idx).replace(/\\/$/, '');\n}\n","import {\n buildAuthorizationEndpoint,\n buildAuthorizationUrl,\n buildIssuerUrl,\n buildLogoutEndpoint,\n buildTokenEndpoint,\n buildUserInfoEndpoint,\n} from './utils/buildKeycloakUrls';\nimport { isTokenExpired } from './utils/isTokenExpired';\nimport { parseBaseUrlFromIssuer, parseRealmFromIssuer } from './utils/parseRealmFromIssuer';\n\nimport type { AuthClientConfig } from './types/AuthClientConfig';\nimport type { AuthTokens } from './types/AuthTokens';\nimport type { TokenStorage } from './types/TokenStorage';\n\nconst DEFAULT_SCOPE = 'openid profile email';\n\n/**\n * Inputs to {@link AuthClient.fromIssuerUrl}.\n *\n * Used by consumers that store only an issuer URL and want to derive `realm`\n * + `baseUrl` rather than configure them separately.\n */\nexport interface AuthClientFromIssuerInput {\n issuerUrl: string;\n clientId: string;\n redirectUri?: string;\n scope?: string;\n}\n\nexport class AuthClient {\n private readonly config: AuthClientConfig;\n private readonly tokenStorage: TokenStorage;\n\n /**\n * @throws Error when `baseUrl`, `realm`, or `clientId` is missing or empty.\n */\n constructor(config: AuthClientConfig, storage: TokenStorage) {\n AuthClient.validateConfig(config);\n this.config = {\n ...config,\n scope: config.scope ?? DEFAULT_SCOPE,\n };\n this.tokenStorage = storage;\n }\n\n /**\n * Build an {@link AuthClient} from a standalone issuer URL by parsing the\n * realm and base URL. Useful when migrating from the legacy\n * `KEYCLOAK_ISSUER` env var convention.\n *\n * @throws Error when the issuer URL doesn't match `{base}/realms/{realm}`.\n */\n static fromIssuerUrl(input: AuthClientFromIssuerInput, storage: TokenStorage): AuthClient {\n const realm = parseRealmFromIssuer(input.issuerUrl);\n const baseUrl = parseBaseUrlFromIssuer(input.issuerUrl);\n if (realm === null || baseUrl === null || baseUrl === '') {\n throw new Error(`AuthClient.fromIssuerUrl: cannot parse realm from \"${input.issuerUrl}\"`);\n }\n return new AuthClient(\n {\n baseUrl,\n realm,\n clientId: input.clientId,\n redirectUri: input.redirectUri,\n scope: input.scope,\n },\n storage,\n );\n }\n\n private static validateConfig(config: AuthClientConfig): void {\n if (typeof config.baseUrl !== 'string' || config.baseUrl === '') {\n throw new Error('AuthClient: baseUrl is required');\n }\n if (typeof config.realm !== 'string' || config.realm === '') {\n throw new Error('AuthClient: realm is required');\n }\n if (typeof config.clientId !== 'string' || config.clientId === '') {\n throw new Error('AuthClient: clientId is required');\n }\n }\n\n get realm(): string {\n return this.config.realm;\n }\n\n get clientId(): string {\n return this.config.clientId;\n }\n\n get baseUrl(): string {\n return this.config.baseUrl.replace(/\\/$/, '');\n }\n\n get scope(): string {\n // Constructor always materialises a scope (either user-supplied or DEFAULT_SCOPE).\n return this.config.scope as string;\n }\n\n get redirectUri(): string | undefined {\n return this.config.redirectUri;\n }\n\n /** Issuer URL: `{baseUrl}/realms/{realm}`. */\n get issuerUrl(): string {\n return buildIssuerUrl(this.baseUrl, this.realm);\n }\n\n get authorizationEndpoint(): string {\n return buildAuthorizationEndpoint(this.baseUrl, this.realm);\n }\n\n get tokenEndpoint(): string {\n return buildTokenEndpoint(this.baseUrl, this.realm);\n }\n\n get userInfoEndpoint(): string {\n return buildUserInfoEndpoint(this.baseUrl, this.realm);\n }\n\n get logoutEndpoint(): string {\n return buildLogoutEndpoint(this.baseUrl, this.realm);\n }\n\n /**\n * Build a fully-formed authorization URL the user agent can navigate to.\n *\n * @throws Error when `redirectUri` is not configured.\n */\n buildAuthorizationUrl(input: {\n state?: string;\n codeChallenge?: string;\n codeChallengeMethod?: 'S256' | 'plain';\n } = {}): string {\n if (typeof this.config.redirectUri !== 'string' || this.config.redirectUri === '') {\n throw new Error('AuthClient.buildAuthorizationUrl: redirectUri is required');\n }\n return buildAuthorizationUrl({\n baseUrl: this.baseUrl,\n realm: this.realm,\n clientId: this.clientId,\n redirectUri: this.config.redirectUri,\n scope: this.scope,\n state: input.state,\n codeChallenge: input.codeChallenge,\n codeChallengeMethod: input.codeChallengeMethod,\n });\n }\n\n async getTokens(): Promise<AuthTokens | null> {\n return this.tokenStorage.read();\n }\n\n async setTokens(tokens: AuthTokens): Promise<void> {\n return this.tokenStorage.write(tokens);\n }\n\n async clearTokens(): Promise<void> {\n return this.tokenStorage.clear();\n }\n\n /**\n * Read the current access token if it exists and is not expired.\n * Returns `null` for \"no usable token\".\n */\n async getAccessToken(now: number = Date.now()): Promise<string | null> {\n const tokens = await this.tokenStorage.read();\n if (tokens === null) {\n return null;\n }\n if (isTokenExpired(tokens, undefined, now)) {\n return null;\n }\n return tokens.accessToken;\n }\n}\n","/**\n * Roles emitted by Keycloak realms in the dloizides.com portfolio.\n *\n * Lives in its own file per the project convention: each exported `const enum`\n * sits alone so it can be imported without dragging the rest of the type tree.\n */\nexport const enum KeycloakRoles {\n SuperUser = 'superUser',\n Admin = 'admin',\n User = 'user',\n}\n\nconst KEYCLOAK_ROLE_VALUES: readonly string[] = [\n KeycloakRoles.SuperUser,\n KeycloakRoles.Admin,\n KeycloakRoles.User,\n];\n\n/**\n * Type guard that narrows a string to a known {@link KeycloakRoles} value.\n *\n * Use this when ingesting role claims from the network, where the wire payload\n * is `string[]` but downstream code wants `KeycloakRoles[]`.\n */\nexport function isKeycloakRole(value: string): value is KeycloakRoles {\n return KEYCLOAK_ROLE_VALUES.includes(value);\n}\n","import type { AuthTokens } from '../types/AuthTokens';\nimport type { TokenStorage } from '../types/TokenStorage';\n\n/**\n * Subset of `Storage` we actually use. Lets callers inject `localStorage`,\n * `sessionStorage`, or any compatible polyfill.\n */\nexport interface StorageLike {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\nexport interface BrowserStorageTokenStorageOptions {\n storage: StorageLike;\n /** Storage key. Defaults to `auth.tokens`. */\n key?: string;\n}\n\nconst DEFAULT_KEY = 'auth.tokens';\n\nfunction isAuthTokens(value: unknown): value is AuthTokens {\n if (typeof value !== 'object' || value === null) {\n return false;\n }\n const candidate = value as Record<string, unknown>;\n return typeof candidate.accessToken === 'string' && typeof candidate.expiresAt === 'number';\n}\n\n/**\n * Persist tokens in any `Storage`-shaped backend (`localStorage`, `sessionStorage`,\n * AsyncStorage shim, etc.). The class is sync-aware but exposes a Promise-based\n * API to match {@link TokenStorage}.\n *\n * Errors during read are swallowed and surfaced as `null` (corrupt JSON, denied\n * access in some private-mode browsers, etc.). Errors during write/clear are\n * propagated so callers can decide whether to retry or fall back.\n */\nexport class BrowserStorageTokenStorage implements TokenStorage {\n private readonly storage: StorageLike;\n private readonly key: string;\n\n constructor(options: BrowserStorageTokenStorageOptions) {\n this.storage = options.storage;\n this.key = options.key ?? DEFAULT_KEY;\n }\n\n read(): Promise<AuthTokens | null> {\n return Promise.resolve(this.readSync());\n }\n\n write(tokens: AuthTokens): Promise<void> {\n this.storage.setItem(this.key, JSON.stringify(tokens));\n return Promise.resolve();\n }\n\n clear(): Promise<void> {\n this.storage.removeItem(this.key);\n return Promise.resolve();\n }\n\n private readSync(): AuthTokens | null {\n try {\n const raw = this.storage.getItem(this.key);\n if (raw === null || raw === '') {\n return null;\n }\n const parsed: unknown = JSON.parse(raw);\n return isAuthTokens(parsed) ? parsed : null;\n } catch {\n return null;\n }\n }\n}\n","import type { AuthTokens } from '../types/AuthTokens';\nimport type { TokenStorage } from '../types/TokenStorage';\n\n/**\n * In-memory storage backed by a single instance variable.\n *\n * Useful for tests, server-side rendering, and as a default fallback when no\n * platform-specific storage is available. Tokens are lost on process exit.\n */\nexport class InMemoryTokenStorage implements TokenStorage {\n private tokens: AuthTokens | null = null;\n\n read(): Promise<AuthTokens | null> {\n return Promise.resolve(this.tokens);\n }\n\n write(tokens: AuthTokens): Promise<void> {\n this.tokens = tokens;\n return Promise.resolve();\n }\n\n clear(): Promise<void> {\n this.tokens = null;\n return Promise.resolve();\n }\n}\n","import { KeycloakRoles } from '../types/KeycloakRoles';\n\nimport type { KeycloakUserInfo } from '../types/KeycloakUserInfo';\nimport type { NormalizedUser } from '../types/NormalizedUser';\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value !== '';\n}\n\nfunction firstNonEmptyString(...values: unknown[]): string | undefined {\n for (const v of values) {\n if (isNonEmptyString(v)) {\n return v;\n }\n }\n return undefined;\n}\n\nfunction collectRoles(u: KeycloakUserInfo): KeycloakRoles[] {\n const roles: KeycloakRoles[] = [];\n const realmAccessRoles = u.realm_access?.roles;\n if (Array.isArray(realmAccessRoles)) {\n roles.push(...realmAccessRoles);\n }\n\n const resourceAccess = u.resource_access;\n if (resourceAccess !== undefined) {\n for (const v of Object.values(resourceAccess)) {\n const resourceRoles = v.roles;\n if (Array.isArray(resourceRoles)) {\n roles.push(...resourceRoles);\n }\n }\n }\n return roles;\n}\n\n/**\n * Convert a Keycloak `/userinfo` payload into a flat, app-friendly user object.\n *\n * - Aggregates `realm_access.roles` and every `resource_access[*].roles` into a\n * deduplicated `roles` array.\n * - Picks a sensible `displayName` / `username` from whatever claims are\n * present (Keycloak realms vary in which fields they emit).\n * - Returns a safe default (`{ roles: [] }`) when input is undefined.\n */\nexport function normalizeKeycloakUser(u?: KeycloakUserInfo): NormalizedUser {\n if (!u) {\n return { roles: [] };\n }\n const roles = collectRoles(u);\n const fullName = [u.given_name, u.family_name].filter(isNonEmptyString).join(' ');\n const username = firstNonEmptyString(u.preferred_username, u.name, u.email, u.sub);\n const displayName = firstNonEmptyString(u.name, fullName, u.preferred_username, u.email, u.sub);\n\n return {\n id: u.sub,\n username,\n email: u.email,\n displayName,\n firstName: u.given_name,\n lastName: u.family_name,\n emailVerified: Boolean(u.email_verified),\n roles: Array.from(new Set(roles)),\n raw: u,\n };\n}\n","/**\n * Inputs for the OAuth `authorization_code` token request.\n */\nexport interface AuthorizationCodeBodyInput {\n clientId: string;\n code: string;\n redirectUri: string;\n codeVerifier: string;\n}\n\n/**\n * Inputs for the OAuth `refresh_token` token request.\n */\nexport interface RefreshTokenBodyInput {\n clientId: string;\n refreshToken: string;\n}\n\n/**\n * Build the `application/x-www-form-urlencoded` body for the\n * `grant_type=authorization_code` token endpoint call (PKCE flow).\n */\nexport function buildAuthorizationCodeBody(input: AuthorizationCodeBodyInput): string {\n return new URLSearchParams({\n client_id: input.clientId,\n grant_type: 'authorization_code',\n code: input.code,\n redirect_uri: input.redirectUri,\n code_verifier: input.codeVerifier,\n }).toString();\n}\n\n/**\n * Build the `application/x-www-form-urlencoded` body for the\n * `grant_type=refresh_token` token endpoint call.\n */\nexport function buildRefreshTokenBody(input: RefreshTokenBodyInput): string {\n return new URLSearchParams({\n client_id: input.clientId,\n grant_type: 'refresh_token',\n refresh_token: input.refreshToken,\n }).toString();\n}\n","/**\n * Loose shape of an `expo-auth-session` (or browser-side) authorization response.\n *\n * Kept as a structural type rather than importing from `expo-auth-session` so\n * the package stays usable in plain web apps and Node tests.\n */\nexport interface AuthorizationResponseLike {\n type?: string;\n params?: { code?: string; error?: string };\n}\n\n/**\n * Pull the authorization `code` out of a successful redirect response.\n *\n * Returns `undefined` when the response is missing, indicates an error type,\n * or doesn't carry a non-empty `code` query param.\n */\nexport function extractAuthCode(\n response: AuthorizationResponseLike | null | undefined,\n): string | undefined {\n if (!response) {\n return undefined;\n }\n if (response.type !== 'success') {\n return undefined;\n }\n const code = response.params?.code;\n if (typeof code !== 'string' || code === '') {\n return undefined;\n }\n return code;\n}\n","/**\n * Decode the payload segment of a compact JWT.\n *\n * No signature verification — that responsibility belongs to the backend that\n * accepts the token. This helper is for UI concerns: reading `exp` to schedule\n * refresh, reading custom claims for routing decisions, etc.\n *\n * Returns `null` when the input is malformed, base64url-decodes incorrectly, or\n * does not produce a JSON object payload.\n *\n * Runtime requirement: a global `atob` function. Available in browsers, in\n * Node ≥ 16, and in modern bundler test envs (jsdom, node-jest).\n */\nexport function decodeJwt<T = Record<string, unknown>>(token: string | null | undefined): T | null {\n if (typeof token !== 'string' || token === '') {\n return null;\n }\n const parts = token.split('.');\n if (parts.length !== 3) {\n return null;\n }\n const payload = parts[1];\n if (payload === undefined || payload === '') {\n return null;\n }\n try {\n const json = base64UrlDecode(payload);\n const parsed: unknown = JSON.parse(json);\n if (typeof parsed !== 'object' || parsed === null) {\n return null;\n }\n return parsed as T;\n } catch {\n return null;\n }\n}\n\nconst BASE64_PAD_LENGTH = 4;\n\nfunction base64UrlDecode(input: string): string {\n const normalized = input.replace(/-/g, '+').replace(/_/g, '/');\n const padLength =\n (BASE64_PAD_LENGTH - (normalized.length % BASE64_PAD_LENGTH)) % BASE64_PAD_LENGTH;\n const padded = normalized + '='.repeat(padLength);\n if (typeof globalThis.atob !== 'function') {\n throw new Error('decodeJwt: globalThis.atob is unavailable in this runtime');\n }\n return decodeUtf8(globalThis.atob(padded));\n}\n\nfunction decodeUtf8(binary: string): string {\n // `atob` returns a \"binary string\" where each char code is one byte. Convert\n // to UTF-8 properly using TextDecoder when available (browsers + Node).\n if (typeof TextDecoder === 'undefined') {\n return binary;\n }\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return new TextDecoder('utf-8').decode(bytes);\n}\n","import type { AuthTokens } from '../types/AuthTokens';\nimport type { RawTokenResponse, TokenResponse } from '../types/TokenResponse';\nimport { computeExpiresAt } from './isTokenExpired';\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === 'string' && value !== '' ? value : undefined;\n}\n\nfunction asNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\n/**\n * Map a raw OIDC token endpoint response (snake_case) to camelCase.\n *\n * Throws when `access_token` is missing or empty — callers should let this\n * propagate to the auth state machine, which treats it as a login failure.\n */\nexport function normalizeTokenResponse(raw: RawTokenResponse): TokenResponse {\n const accessToken = asString(raw.access_token);\n if (accessToken === undefined) {\n throw new Error('Token response missing access_token');\n }\n return {\n accessToken,\n refreshToken: asString(raw.refresh_token),\n idToken: asString(raw.id_token),\n expiresIn: asNumber(raw.expires_in),\n tokenType: asString(raw.token_type),\n scope: asString(raw.scope),\n };\n}\n\n/**\n * Convert a normalized {@link TokenResponse} into a persistable\n * {@link AuthTokens} bundle by computing `expiresAt` from `expiresIn`.\n */\nexport function tokenResponseToAuthTokens(\n response: TokenResponse,\n now: number = Date.now(),\n): AuthTokens {\n return {\n accessToken: response.accessToken,\n refreshToken: response.refreshToken,\n idToken: response.idToken,\n expiresAt: computeExpiresAt(response.expiresIn, now),\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/utils/buildKeycloakUrls.ts","../src/utils/isTokenExpired.ts","../src/utils/parseRealmFromIssuer.ts","../src/utils/normalizeTokenResponse.ts","../src/events/AuthEventEmitter.ts","../src/AuthClient.ts","../src/types/KeycloakRoles.ts","../src/storage/BrowserStorageTokenStorage.ts","../src/storage/InMemoryTokenStorage.ts","../src/storage/CookieTokenStorage.ts","../src/storage/SecureStoreTokenStorage.ts","../src/biometric/BiometricGate.ts","../src/interceptor/RefreshInterceptor.ts","../src/inactivity/InactivityTracker.ts","../src/http/HttpClient.ts","../src/api/AuthApiClient.ts","../src/utils/normalizeKeycloakUser.ts","../src/utils/buildTokenRequestBody.ts","../src/utils/extractAuthCode.ts","../src/utils/decodeJwt.ts"],"names":["KeycloakRoles"],"mappings":";AASA,IAAM,iBAAA,GAAoB,SAAA;AAC1B,IAAM,aAAA,GAAgB,0BAAA;AAEtB,SAAS,kBAAkB,KAAA,EAAuB;AAChD,EAAA,OAAO,KAAA,CAAM,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AAChC;AAKO,SAAS,cAAA,CAAe,SAAiB,KAAA,EAAuB;AACrE,EAAA,OAAO,CAAA,EAAG,kBAAkB,OAAO,CAAC,GAAG,iBAAiB,CAAA,CAAA,EAAI,kBAAA,CAAmB,KAAK,CAAC,CAAA,CAAA;AACvF;AAKO,SAAS,0BAAA,CAA2B,SAAiB,KAAA,EAAuB;AACjF,EAAA,OAAO,GAAG,cAAA,CAAe,OAAA,EAAS,KAAK,CAAC,GAAG,aAAa,CAAA,KAAA,CAAA;AAC1D;AAKO,SAAS,kBAAA,CAAmB,SAAiB,KAAA,EAAuB;AACzE,EAAA,OAAO,GAAG,cAAA,CAAe,OAAA,EAAS,KAAK,CAAC,GAAG,aAAa,CAAA,MAAA,CAAA;AAC1D;AAKO,SAAS,qBAAA,CAAsB,SAAiB,KAAA,EAAuB;AAC5E,EAAA,OAAO,GAAG,cAAA,CAAe,OAAA,EAAS,KAAK,CAAC,GAAG,aAAa,CAAA,SAAA,CAAA;AAC1D;AAKO,SAAS,mBAAA,CAAoB,SAAiB,KAAA,EAAuB;AAC1E,EAAA,OAAO,GAAG,cAAA,CAAe,OAAA,EAAS,KAAK,CAAC,GAAG,aAAa,CAAA,OAAA,CAAA;AAC1D;AAoBO,SAAS,sBAAsB,KAAA,EAAsC;AAC1E,EAAA,MAAM,MAAA,GAAS,IAAI,eAAA,CAAgB;AAAA,IACjC,WAAW,KAAA,CAAM,QAAA;AAAA,IACjB,cAAc,KAAA,CAAM,WAAA;AAAA,IACpB,aAAA,EAAe;AAAA,GAChB,CAAA;AACD,EAAA,IAAI,OAAO,KAAA,CAAM,KAAA,KAAU,QAAA,IAAY,KAAA,CAAM,UAAU,EAAA,EAAI;AACzD,IAAA,MAAA,CAAO,GAAA,CAAI,OAAA,EAAS,KAAA,CAAM,KAAK,CAAA;AAAA,EACjC;AACA,EAAA,IAAI,OAAO,KAAA,CAAM,KAAA,KAAU,QAAA,IAAY,KAAA,CAAM,UAAU,EAAA,EAAI;AACzD,IAAA,MAAA,CAAO,GAAA,CAAI,OAAA,EAAS,KAAA,CAAM,KAAK,CAAA;AAAA,EACjC;AACA,EAAA,IAAI,OAAO,KAAA,CAAM,aAAA,KAAkB,QAAA,IAAY,KAAA,CAAM,kBAAkB,EAAA,EAAI;AACzE,IAAA,MAAA,CAAO,GAAA,CAAI,gBAAA,EAAkB,KAAA,CAAM,aAAa,CAAA;AAChD,IAAA,MAAA,CAAO,GAAA,CAAI,uBAAA,EAAyB,KAAA,CAAM,mBAAA,IAAuB,MAAM,CAAA;AAAA,EACzE;AACA,EAAA,OAAO,CAAA,EAAG,0BAAA,CAA2B,KAAA,CAAM,OAAA,EAAS,KAAA,CAAM,KAAK,CAAC,CAAA,CAAA,EAAI,MAAA,CAAO,QAAA,EAAU,CAAA,CAAA;AACvF;;;ACpFA,IAAM,iBAAA,GAAoB,GAAA;AAC1B,IAAM,oBAAoB,EAAA,GAAK,iBAAA;AAYxB,SAAS,eACd,MAAA,EACA,QAAA,GAAmB,mBACnB,GAAA,GAAc,IAAA,CAAK,KAAI,EACd;AACT,EAAA,IAAI,CAAC,MAAA,EAAQ;AACX,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,IAAI,OAAO,MAAA,CAAO,SAAA,KAAc,QAAA,IAAY,MAAA,CAAO,aAAa,CAAA,EAAG;AACjE,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,OAAO,MAAA,CAAO,YAAY,QAAA,IAAY,GAAA;AACxC;AAQO,SAAS,gBAAA,CACd,gBAAA,EACA,GAAA,GAAc,IAAA,CAAK,KAAI,EACf;AACR,EAAA,IAAI,OAAO,gBAAA,KAAqB,QAAA,IAAY,gBAAA,IAAoB,CAAA,EAAG;AACjE,IAAA,OAAO,CAAA;AAAA,EACT;AACA,EAAA,OAAO,MAAM,gBAAA,GAAmB,iBAAA;AAClC;;;ACjCO,SAAS,qBAAqB,SAAA,EAAqD;AACxF,EAAA,IAAI,OAAO,SAAA,KAAc,QAAA,IAAY,SAAA,KAAc,EAAA,EAAI;AACrD,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,MAAM,KAAA,GAAQ,sBAAA,CAAuB,IAAA,CAAK,SAAS,CAAA;AACnD,EAAA,IAAI,CAAC,SAAS,KAAA,CAAM,CAAC,MAAM,MAAA,IAAa,KAAA,CAAM,CAAC,CAAA,KAAM,EAAA,EAAI;AACvD,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,OAAO,kBAAA,CAAmB,KAAA,CAAM,CAAC,CAAC,CAAA;AACpC;AASO,SAAS,uBAAuB,SAAA,EAAqD;AAC1F,EAAA,IAAI,OAAO,SAAA,KAAc,QAAA,IAAY,SAAA,KAAc,EAAA,EAAI;AACrD,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,MAAM,GAAA,GAAM,SAAA,CAAU,MAAA,CAAO,aAAa,CAAA;AAC1C,EAAA,IAAI,QAAQ,EAAA,EAAI;AACd,IAAA,OAAO,SAAA,CAAU,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AAAA,EACpC;AACA,EAAA,OAAO,UAAU,SAAA,CAAU,CAAA,EAAG,GAAG,CAAA,CAAE,OAAA,CAAQ,OAAO,EAAE,CAAA;AACtD;;;ACjCA,SAAS,SAAS,KAAA,EAAoC;AACpD,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,KAAK,KAAA,GAAQ,MAAA;AAC7D;AAEA,SAAS,SAAS,KAAA,EAAoC;AACpD,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,OAAO,QAAA,CAAS,KAAK,IAAI,KAAA,GAAQ,MAAA;AACvE;AAQO,SAAS,uBAAuB,GAAA,EAAsC;AAC3E,EAAA,MAAM,WAAA,GAAc,QAAA,CAAS,GAAA,CAAI,YAAY,CAAA;AAC7C,EAAA,IAAI,gBAAgB,MAAA,EAAW;AAC7B,IAAA,MAAM,IAAI,MAAM,qCAAqC,CAAA;AAAA,EACvD;AACA,EAAA,OAAO;AAAA,IACL,WAAA;AAAA,IACA,YAAA,EAAc,QAAA,CAAS,GAAA,CAAI,aAAa,CAAA;AAAA,IACxC,OAAA,EAAS,QAAA,CAAS,GAAA,CAAI,QAAQ,CAAA;AAAA,IAC9B,SAAA,EAAW,QAAA,CAAS,GAAA,CAAI,UAAU,CAAA;AAAA,IAClC,SAAA,EAAW,QAAA,CAAS,GAAA,CAAI,UAAU,CAAA;AAAA,IAClC,KAAA,EAAO,QAAA,CAAS,GAAA,CAAI,KAAK;AAAA,GAC3B;AACF;AAMO,SAAS,yBAAA,CACd,QAAA,EACA,GAAA,GAAc,IAAA,CAAK,KAAI,EACX;AACZ,EAAA,OAAO;AAAA,IACL,aAAa,QAAA,CAAS,WAAA;AAAA,IACtB,cAAc,QAAA,CAAS,YAAA;AAAA,IACvB,SAAS,QAAA,CAAS,OAAA;AAAA,IAClB,SAAA,EAAW,gBAAA,CAAiB,QAAA,CAAS,SAAA,EAAW,GAAG;AAAA,GACrD;AACF;;;AC/BO,IAAM,mBAAN,MAAuB;AAAA,EAAvB,WAAA,GAAA;AACL,IAAA,IAAA,CAAiB,SAAA,uBAA4D,GAAA,EAAI;AAAA,EAAA;AAAA,EAEjF,EAAA,CAAG,OAAsB,QAAA,EAAmD;AAC1E,IAAA,IAAI,MAAA,GAAS,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA;AACrC,IAAA,IAAI,WAAW,MAAA,EAAW;AACxB,MAAA,MAAA,uBAAa,GAAA,EAAI;AACjB,MAAA,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAA,EAAO,MAAM,CAAA;AAAA,IAClC;AACA,IAAA,MAAA,CAAO,IAAI,QAAQ,CAAA;AACnB,IAAA,OAAO,MAAY;AACjB,MAAA,MAAM,OAAA,GAAU,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA;AACxC,MAAA,IAAI,YAAY,MAAA,EAAW;AACzB,QAAA,OAAA,CAAQ,OAAO,QAAQ,CAAA;AAAA,MACzB;AAAA,IACF,CAAA;AAAA,EACF;AAAA,EAEA,KAAK,KAAA,EAA4B;AAC/B,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA;AACvC,IAAA,IAAI,WAAW,MAAA,EAAW;AACxB,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,QAAA,GAAW,KAAA,CAAM,IAAA,CAAK,MAAM,CAAA;AAClC,IAAA,KAAA,MAAW,YAAY,QAAA,EAAU;AAC/B,MAAA,QAAA,EAAS;AAAA,IACX;AAAA,EACF;AAAA;AAAA,EAGA,KAAA,GAAc;AACZ,IAAA,IAAA,CAAK,UAAU,KAAA,EAAM;AAAA,EACvB;AACF;;;AC9BA,IAAM,aAAA,GAAgB,sBAAA;AACtB,IAAM,oBAAA,GAAuB,gBAAA;AA4CtB,IAAM,UAAA,GAAN,MAAM,WAAA,CAAW;AAAA;AAAA;AAAA;AAAA,EAWtB,WAAA,CAAY,MAAA,EAA0B,OAAA,EAAuB,aAAA,GAAyC,EAAC,EAAG;AACxG,IAAA,WAAA,CAAW,eAAe,MAAM,CAAA;AAChC,IAAA,IAAA,CAAK,MAAA,GAAS;AAAA,MACZ,GAAG,MAAA;AAAA,MACH,KAAA,EAAO,OAAO,KAAA,IAAS;AAAA,KACzB;AACA,IAAA,IAAA,CAAK,YAAA,GAAe,OAAA;AACpB,IAAA,IAAA,CAAK,MAAM,aAAA,CAAc,GAAA;AACzB,IAAA,IAAA,CAAK,cAAc,aAAA,CAAc,WAAA;AACjC,IAAA,IAAA,CAAK,oBAAoB,aAAA,CAAc,iBAAA;AACvC,IAAA,IAAA,CAAK,MAAA,GAAS,aAAA,CAAc,MAAA,IAAU,IAAI,gBAAA,EAAiB;AAAA,EAC7D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,OAAO,aAAA,CACL,KAAA,EACA,OAAA,EACA,aAAA,GAAyC,EAAC,EAC9B;AACZ,IAAA,MAAM,KAAA,GAAQ,oBAAA,CAAqB,KAAA,CAAM,SAAS,CAAA;AAClD,IAAA,MAAM,OAAA,GAAU,sBAAA,CAAuB,KAAA,CAAM,SAAS,CAAA;AACtD,IAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,OAAA,KAAY,IAAA,IAAQ,YAAY,EAAA,EAAI;AACxD,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,mDAAA,EAAsD,KAAA,CAAM,SAAS,CAAA,CAAA,CAAG,CAAA;AAAA,IAC1F;AACA,IAAA,OAAO,IAAI,WAAA;AAAA,MACT;AAAA,QACE,OAAA;AAAA,QACA,KAAA;AAAA,QACA,UAAU,KAAA,CAAM,QAAA;AAAA,QAChB,aAAa,KAAA,CAAM,WAAA;AAAA,QACnB,OAAO,KAAA,CAAM;AAAA,OACf;AAAA,MACA,OAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AAAA,EAEA,OAAe,eAAe,MAAA,EAAgC;AAC5D,IAAA,IAAI,OAAO,MAAA,CAAO,OAAA,KAAY,QAAA,IAAY,MAAA,CAAO,YAAY,EAAA,EAAI;AAC/D,MAAA,MAAM,IAAI,MAAM,iCAAiC,CAAA;AAAA,IACnD;AACA,IAAA,IAAI,OAAO,MAAA,CAAO,KAAA,KAAU,QAAA,IAAY,MAAA,CAAO,UAAU,EAAA,EAAI;AAC3D,MAAA,MAAM,IAAI,MAAM,+BAA+B,CAAA;AAAA,IACjD;AACA,IAAA,IAAI,OAAO,MAAA,CAAO,QAAA,KAAa,QAAA,IAAY,MAAA,CAAO,aAAa,EAAA,EAAI;AACjE,MAAA,MAAM,IAAI,MAAM,kCAAkC,CAAA;AAAA,IACpD;AAAA,EACF;AAAA,EAEA,IAAI,KAAA,GAAgB;AAClB,IAAA,OAAO,KAAK,MAAA,CAAO,KAAA;AAAA,EACrB;AAAA,EAEA,IAAI,QAAA,GAAmB;AACrB,IAAA,OAAO,KAAK,MAAA,CAAO,QAAA;AAAA,EACrB;AAAA,EAEA,IAAI,OAAA,GAAkB;AACpB,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,OAAA,CAAQ,OAAA,CAAQ,OAAO,EAAE,CAAA;AAAA,EAC9C;AAAA,EAEA,IAAI,KAAA,GAAgB;AAElB,IAAA,OAAO,KAAK,MAAA,CAAO,KAAA;AAAA,EACrB;AAAA,EAEA,IAAI,WAAA,GAAkC;AACpC,IAAA,OAAO,KAAK,MAAA,CAAO,WAAA;AAAA,EACrB;AAAA;AAAA,EAGA,IAAI,SAAA,GAAoB;AACtB,IAAA,OAAO,cAAA,CAAe,IAAA,CAAK,OAAA,EAAS,IAAA,CAAK,KAAK,CAAA;AAAA,EAChD;AAAA,EAEA,IAAI,qBAAA,GAAgC;AAClC,IAAA,OAAO,0BAAA,CAA2B,IAAA,CAAK,OAAA,EAAS,IAAA,CAAK,KAAK,CAAA;AAAA,EAC5D;AAAA,EAEA,IAAI,aAAA,GAAwB;AAC1B,IAAA,OAAO,kBAAA,CAAmB,IAAA,CAAK,OAAA,EAAS,IAAA,CAAK,KAAK,CAAA;AAAA,EACpD;AAAA,EAEA,IAAI,gBAAA,GAA2B;AAC7B,IAAA,OAAO,qBAAA,CAAsB,IAAA,CAAK,OAAA,EAAS,IAAA,CAAK,KAAK,CAAA;AAAA,EACvD;AAAA,EAEA,IAAI,cAAA,GAAyB;AAC3B,IAAA,OAAO,mBAAA,CAAoB,IAAA,CAAK,OAAA,EAAS,IAAA,CAAK,KAAK,CAAA;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,qBAAA,CAAsB,KAAA,GAKlB,EAAC,EAAW;AACd,IAAA,IAAI,OAAO,KAAK,MAAA,CAAO,WAAA,KAAgB,YAAY,IAAA,CAAK,MAAA,CAAO,gBAAgB,EAAA,EAAI;AACjF,MAAA,MAAM,IAAI,MAAM,2DAA2D,CAAA;AAAA,IAC7E;AACA,IAAA,OAAO,qBAAA,CAAsB;AAAA,MAC3B,SAAS,IAAA,CAAK,OAAA;AAAA,MACd,OAAO,IAAA,CAAK,KAAA;AAAA,MACZ,UAAU,IAAA,CAAK,QAAA;AAAA,MACf,WAAA,EAAa,KAAK,MAAA,CAAO,WAAA;AAAA,MACzB,KAAA,EAAO,IAAA,CAAK,YAAA,CAAa,KAAA,CAAM,aAAa,CAAA;AAAA,MAC5C,OAAO,KAAA,CAAM,KAAA;AAAA,MACb,eAAe,KAAA,CAAM,aAAA;AAAA,MACrB,qBAAqB,KAAA,CAAM;AAAA,KAC5B,CAAA;AAAA,EACH;AAAA,EAEA,MAAM,SAAA,GAAwC;AAC5C,IAAA,OAAO,IAAA,CAAK,aAAa,IAAA,EAAK;AAAA,EAChC;AAAA,EAEA,MAAM,UAAU,MAAA,EAAmC;AACjD,IAAA,OAAO,IAAA,CAAK,YAAA,CAAa,KAAA,CAAM,MAAM,CAAA;AAAA,EACvC;AAAA,EAEA,MAAM,WAAA,GAA6B;AACjC,IAAA,OAAO,IAAA,CAAK,aAAa,KAAA,EAAM;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,cAAA,CAAe,GAAA,GAAc,IAAA,CAAK,KAAI,EAA2B;AACrE,IAAA,MAAM,MAAA,GAAS,MAAM,IAAA,CAAK,YAAA,CAAa,IAAA,EAAK;AAC5C,IAAA,IAAI,WAAW,IAAA,EAAM;AACnB,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,IAAI,cAAA,CAAe,MAAA,EAAQ,MAAA,EAAW,GAAG,CAAA,EAAG;AAC1C,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,OAAO,MAAA,CAAO,WAAA;AAAA,EAChB;AAAA;AAAA,EAGA,EAAA,CAAG,OAAsB,QAAA,EAAmD;AAC1E,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,EAAA,CAAG,KAAA,EAAO,QAAQ,CAAA;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,IAAA,GAAyC;AAC7C,IAAA,IAAI,IAAA,CAAK,sBAAsB,MAAA,EAAW;AACxC,MAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,iBAAA,CAAkB,SAAA,EAAU;AACvD,MAAA,IAAI,OAAA,EAAS;AACX,QAAA,MAAM,IAAA,CAAK,aAAa,KAAA,EAAM;AAC9B,QAAA,MAAM,IAAA,CAAK,kBAAkB,KAAA,EAAM;AACnC,QAAA,IAAA,CAAK,MAAA,CAAO,KAAK,gBAAgB,CAAA;AACjC,QAAA,OAAO,EAAE,YAAY,KAAA,EAAM;AAAA,MAC7B;AAAA,IACF;AACA,IAAA,MAAM,MAAA,GAAS,MAAM,IAAA,CAAK,YAAA,CAAa,IAAA,EAAK;AAC5C,IAAA,OAAO,EAAE,UAAA,EAAY,MAAA,KAAW,IAAA,EAAK;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,OAAA,GAAsC;AAC1C,IAAA,IAAI,IAAA,CAAK,gBAAgB,MAAA,EAAW;AAClC,MAAA,MAAM,IAAI,MAAM,sDAAsD,CAAA;AAAA,IACxE;AACA,IAAA,OAAO,IAAA,CAAK,YAAY,aAAA,EAAc;AAAA,EACxC;AAAA,EAEA,MAAM,aAAa,KAAA,EAA8F;AAC/G,IAAA,OAAO,IAAA,CAAK,QAAA,CAAS,IAAA,CAAK,UAAA,GAAa,YAAA,CAAa;AAAA,MAClD,OAAO,KAAA,CAAM,KAAA;AAAA,MACb,KAAK,KAAA,CAAM,GAAA;AAAA,MACX,UAAU,KAAA,CAAM,QAAA;AAAA,MAChB,aAAA,EAAe,MAAM,aAAA,IAAiB;AAAA,KACvC,CAAC,CAAA;AAAA,EACJ;AAAA,EAEA,MAAM,kBACJ,KAAA,EACqB;AACrB,IAAA,OAAO,IAAA,CAAK,QAAA,CAAS,IAAA,CAAK,UAAA,GAAa,iBAAA,CAAkB;AAAA,MACvD,OAAO,KAAA,CAAM,KAAA;AAAA,MACb,UAAU,KAAA,CAAM,QAAA;AAAA,MAChB,UAAU,KAAA,CAAM,QAAA;AAAA,MAChB,aAAA,EAAe,MAAM,aAAA,IAAiB;AAAA,KACvC,CAAC,CAAA;AAAA,EACJ;AAAA,EAEA,MAAM,MAAA,CAAO,OAAA,GAAyB,EAAC,EAAkB;AACvD,IAAA,MAAM,GAAA,GAAM,KAAK,UAAA,EAAW;AAC5B,IAAA,IAAI;AACF,MAAA,MAAM,GAAA,CAAI,MAAA,CAAO,OAAA,CAAQ,UAAA,IAAc,KAAK,CAAA;AAAA,IAC9C,CAAA,SAAE;AACA,MAAA,MAAM,IAAA,CAAK,aAAa,KAAA,EAAM;AAC9B,MAAA,IAAI,IAAA,CAAK,sBAAsB,MAAA,EAAW;AACxC,QAAA,MAAM,IAAA,CAAK,kBAAkB,KAAA,EAAM;AAAA,MACrC;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,qBAAqB,KAAA,EAA4D;AACrF,IAAA,OAAO,IAAA,CAAK,UAAA,EAAW,CAAE,cAAA,CAAe,EAAE,KAAA,EAAO,KAAA,CAAM,KAAA,EAAO,QAAA,EAAU,KAAA,CAAM,QAAA,EAAU,CAAA;AAAA,EAC1F;AAAA,EAEA,MAAM,qBAAqB,KAAA,EAA8D;AACvF,IAAA,OAAO,IAAA,CAAK,UAAA,EAAW,CAAE,aAAA,CAAc,EAAE,KAAA,EAAO,KAAA,CAAM,KAAA,EAAO,WAAA,EAAa,KAAA,CAAM,WAAA,EAAa,CAAA;AAAA,EAC/F;AAAA;AAAA,EAGA,MAAc,SAAS,OAAA,EAA6D;AAClF,IAAA,MAAM,MAAM,MAAM,OAAA;AAClB,IAAA,IAAI,OAAO,GAAA,CAAI,YAAA,KAAiB,QAAA,IAAY,GAAA,CAAI,iBAAiB,EAAA,EAAI;AACnE,MAAA,MAAM,IAAI,MAAM,iDAAiD,CAAA;AAAA,IACnE;AAGA,IAAA,MAAM,UAAA,GAAa,uBAAuB,EAAE,GAAG,KAAK,YAAA,EAAc,GAAA,CAAI,cAAc,CAAA;AACpF,IAAA,MAAM,MAAA,GAAS,0BAA0B,UAAU,CAAA;AACnD,IAAA,MAAM,IAAA,CAAK,YAAA,CAAa,KAAA,CAAM,MAAM,CAAA;AACpC,IAAA,IAAI,IAAA,CAAK,sBAAsB,MAAA,EAAW;AACxC,MAAA,MAAM,IAAA,CAAK,kBAAkB,UAAA,EAAW;AAAA,IAC1C;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AAAA,EAEQ,UAAA,GAA4B;AAClC,IAAA,IAAI,IAAA,CAAK,QAAQ,MAAA,EAAW;AAC1B,MAAA,MAAM,IAAI,MAAM,yCAAyC,CAAA;AAAA,IAC3D;AACA,IAAA,OAAO,IAAA,CAAK,GAAA;AAAA,EACd;AAAA,EAEQ,aAAa,aAAA,EAAiC;AACpD,IAAA,IAAI,kBAAkB,IAAA,EAAM;AAC1B,MAAA,OAAO,IAAA,CAAK,KAAA;AAAA,IACd;AACA,IAAA,IAAI,IAAA,CAAK,KAAA,CAAM,QAAA,CAAS,oBAAoB,CAAA,EAAG;AAC7C,MAAA,OAAO,IAAA,CAAK,KAAA;AAAA,IACd;AACA,IAAA,OAAO,GAAG,IAAA,CAAK,KAAK,CAAA,CAAA,EAAI,oBAAoB,GAAG,IAAA,EAAK;AAAA,EACtD;AACF;;;AC3UO,IAAW,aAAA,qBAAAA,cAAAA,KAAX;AACL,EAAAA,eAAA,WAAA,CAAA,GAAY,WAAA;AACZ,EAAAA,eAAA,OAAA,CAAA,GAAQ,OAAA;AACR,EAAAA,eAAA,MAAA,CAAA,GAAO,MAAA;AAHS,EAAA,OAAAA,cAAAA;AAAA,CAAA,EAAA,aAAA,IAAA,EAAA;AAMlB,IAAM,oBAAA,GAA0C;AAAA,EAC9C,WAAA;AAAA,EACA,OAAA;AAAA,EACA,MAAA;AACF,CAAA;AAQO,SAAS,eAAe,KAAA,EAAuC;AACpE,EAAA,OAAO,oBAAA,CAAqB,SAAS,KAAK,CAAA;AAC5C;;;ACPA,IAAM,WAAA,GAAc,aAAA;AAEpB,SAAS,aAAa,KAAA,EAAqC;AACzD,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,IAAA,EAAM;AAC/C,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,MAAM,SAAA,GAAY,KAAA;AAClB,EAAA,OAAO,OAAO,SAAA,CAAU,WAAA,KAAgB,QAAA,IAAY,OAAO,UAAU,SAAA,KAAc,QAAA;AACrF;AAWO,IAAM,6BAAN,MAAyD;AAAA,EAI9D,YAAY,OAAA,EAA4C;AACtD,IAAA,IAAA,CAAK,UAAU,OAAA,CAAQ,OAAA;AACvB,IAAA,IAAA,CAAK,GAAA,GAAM,QAAQ,GAAA,IAAO,WAAA;AAAA,EAC5B;AAAA,EAEA,IAAA,GAAmC;AACjC,IAAA,OAAO,OAAA,CAAQ,OAAA,CAAQ,IAAA,CAAK,QAAA,EAAU,CAAA;AAAA,EACxC;AAAA,EAEA,MAAM,MAAA,EAAmC;AACvC,IAAA,IAAA,CAAK,QAAQ,OAAA,CAAQ,IAAA,CAAK,KAAK,IAAA,CAAK,SAAA,CAAU,MAAM,CAAC,CAAA;AACrD,IAAA,OAAO,QAAQ,OAAA,EAAQ;AAAA,EACzB;AAAA,EAEA,KAAA,GAAuB;AACrB,IAAA,IAAA,CAAK,OAAA,CAAQ,UAAA,CAAW,IAAA,CAAK,GAAG,CAAA;AAChC,IAAA,OAAO,QAAQ,OAAA,EAAQ;AAAA,EACzB;AAAA,EAEQ,QAAA,GAA8B;AACpC,IAAA,IAAI;AACF,MAAA,MAAM,GAAA,GAAM,IAAA,CAAK,OAAA,CAAQ,OAAA,CAAQ,KAAK,GAAG,CAAA;AACzC,MAAA,IAAI,GAAA,KAAQ,IAAA,IAAQ,GAAA,KAAQ,EAAA,EAAI;AAC9B,QAAA,OAAO,IAAA;AAAA,MACT;AACA,MAAA,MAAM,MAAA,GAAkB,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AACtC,MAAA,OAAO,YAAA,CAAa,MAAM,CAAA,GAAI,MAAA,GAAS,IAAA;AAAA,IACzC,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,EACF;AACF;;;AChEO,IAAM,uBAAN,MAAmD;AAAA,EAAnD,WAAA,GAAA;AACL,IAAA,IAAA,CAAQ,MAAA,GAA4B,IAAA;AAAA,EAAA;AAAA,EAEpC,IAAA,GAAmC;AACjC,IAAA,OAAO,OAAA,CAAQ,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAA;AAAA,EACpC;AAAA,EAEA,MAAM,MAAA,EAAmC;AACvC,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,IAAA,OAAO,QAAQ,OAAA,EAAQ;AAAA,EACzB;AAAA,EAEA,KAAA,GAAuB;AACrB,IAAA,IAAA,CAAK,MAAA,GAAS,IAAA;AACd,IAAA,OAAO,QAAQ,OAAA,EAAQ;AAAA,EACzB;AACF;;;ACNO,IAAM,qBAAN,MAAiD;AAAA,EAAjD,WAAA,GAAA;AACL,IAAA,IAAA,CAAQ,WAAA,GAA6B,IAAA;AACrC,IAAA,IAAA,CAAQ,OAAA,GAA8B,MAAA;AACtC,IAAA,IAAA,CAAQ,SAAA,GAAoB,CAAA;AAAA,EAAA;AAAA,EAE5B,IAAA,GAAmC;AACjC,IAAA,IAAI,IAAA,CAAK,gBAAgB,IAAA,EAAM;AAC7B,MAAA,OAAO,OAAA,CAAQ,QAAQ,IAAI,CAAA;AAAA,IAC7B;AACA,IAAA,MAAM,MAAA,GAAqB;AAAA,MACzB,aAAa,IAAA,CAAK,WAAA;AAAA,MAClB,SAAS,IAAA,CAAK,OAAA;AAAA,MACd,WAAW,IAAA,CAAK;AAAA,KAClB;AACA,IAAA,OAAO,OAAA,CAAQ,QAAQ,MAAM,CAAA;AAAA,EAC/B;AAAA,EAEA,MAAM,MAAA,EAAmC;AACvC,IAAA,IAAA,CAAK,cAAc,MAAA,CAAO,WAAA;AAC1B,IAAA,IAAA,CAAK,UAAU,MAAA,CAAO,OAAA;AACtB,IAAA,IAAA,CAAK,YAAY,MAAA,CAAO,SAAA;AAGxB,IAAA,OAAO,QAAQ,OAAA,EAAQ;AAAA,EACzB;AAAA,EAEA,KAAA,GAAuB;AACrB,IAAA,IAAA,CAAK,WAAA,GAAc,IAAA;AACnB,IAAA,IAAA,CAAK,OAAA,GAAU,MAAA;AACf,IAAA,IAAA,CAAK,SAAA,GAAY,CAAA;AACjB,IAAA,OAAO,QAAQ,OAAA,EAAQ;AAAA,EACzB;AACF;;;ACCA,IAAM,cAAA,GAAiB,MAAA;AACvB,IAAM,UAAA,GAAa,QAAA;AACnB,IAAM,WAAA,GAAc,SAAA;AACpB,IAAM,MAAA,GAAS,IAAA;AACf,IAAM,WAAA,GAAc,WAAA;AAab,IAAM,0BAAN,MAAsD;AAAA,EAM3D,YAAY,OAAA,EAAyC;AACnD,IAAA,IAAA,CAAK,cAAc,OAAA,CAAQ,WAAA;AAC3B,IAAA,IAAA,CAAK,MAAA,GAAS,QAAQ,SAAA,IAAa,cAAA;AACnC,IAAA,IAAA,CAAK,qBAAA,GAAwB,QAAQ,qBAAA,IAAyB,KAAA;AAC9D,IAAA,IAAA,CAAK,gBAAgB,OAAA,CAAQ,aAAA;AAAA,EAC/B;AAAA,EAEA,MAAM,IAAA,GAAmC;AACvC,IAAA,IAAI,IAAA,CAAK,wBAAuB,EAAG;AAGjC,MAAA,MAAO,IAAA,CAAK,cAAoC,MAAA,EAAO;AAAA,IACzD;AACA,IAAA,MAAM,cAAc,IAAA,CAAK,qBAAA,GAAwB,EAAE,qBAAA,EAAuB,MAAK,GAAI,MAAA;AACnF,IAAA,MAAM,WAAA,GAAc,MAAM,IAAA,CAAK,WAAA,CAAY,aAAa,IAAA,CAAK,OAAA,CAAQ,UAAU,CAAA,EAAG,WAAW,CAAA;AAC7F,IAAA,IAAI,gBAAgB,IAAA,EAAM;AACxB,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,MAAM,eAAA,GAAkB,MAAM,IAAA,CAAK,WAAA,CAAY,aAAa,IAAA,CAAK,OAAA,CAAQ,WAAW,CAAA,EAAG,WAAW,CAAA;AAClG,IAAA,MAAM,UAAA,GAAa,MAAM,IAAA,CAAK,WAAA,CAAY,aAAa,IAAA,CAAK,OAAA,CAAQ,MAAM,CAAC,CAAA;AAC3E,IAAA,MAAM,YAAA,GAAe,MAAM,IAAA,CAAK,WAAA,CAAY,aAAa,IAAA,CAAK,OAAA,CAAQ,WAAW,CAAC,CAAA;AAClF,IAAA,MAAM,SAAA,GAAY,eAAe,YAAY,CAAA;AAC7C,IAAA,OAAO;AAAA,MACL,WAAA;AAAA,MACA,YAAA,EAAc,eAAA,KAAoB,IAAA,GAAO,MAAA,GAAY,eAAA;AAAA,MACrD,OAAA,EAAS,UAAA,KAAe,IAAA,GAAO,MAAA,GAAY,UAAA;AAAA,MAC3C;AAAA,KACF;AAAA,EACF;AAAA,EAEA,MAAM,MAAM,MAAA,EAAmC;AAC7C,IAAA,MAAM,IAAA,CAAK,YAAY,YAAA,CAAa,IAAA,CAAK,QAAQ,UAAU,CAAA,EAAG,OAAO,WAAW,CAAA;AAChF,IAAA,IAAI,MAAA,CAAO,YAAA,KAAiB,MAAA,IAAa,MAAA,CAAO,iBAAiB,EAAA,EAAI;AACnE,MAAA,MAAM,IAAA,CAAK,YAAY,YAAA,CAAa,IAAA,CAAK,QAAQ,WAAW,CAAA,EAAG,OAAO,YAAY,CAAA;AAAA,IACpF,CAAA,MAAO;AACL,MAAA,MAAM,KAAK,WAAA,CAAY,eAAA,CAAgB,IAAA,CAAK,OAAA,CAAQ,WAAW,CAAC,CAAA;AAAA,IAClE;AACA,IAAA,IAAI,MAAA,CAAO,OAAA,KAAY,MAAA,IAAa,MAAA,CAAO,YAAY,EAAA,EAAI;AACzD,MAAA,MAAM,IAAA,CAAK,YAAY,YAAA,CAAa,IAAA,CAAK,QAAQ,MAAM,CAAA,EAAG,OAAO,OAAO,CAAA;AAAA,IAC1E,CAAA,MAAO;AACL,MAAA,MAAM,KAAK,WAAA,CAAY,eAAA,CAAgB,IAAA,CAAK,OAAA,CAAQ,MAAM,CAAC,CAAA;AAAA,IAC7D;AACA,IAAA,MAAM,IAAA,CAAK,WAAA,CAAY,YAAA,CAAa,IAAA,CAAK,OAAA,CAAQ,WAAW,CAAA,EAAG,MAAA,CAAO,MAAA,CAAO,SAAS,CAAC,CAAA;AAAA,EACzF;AAAA,EAEA,MAAM,KAAA,GAAuB;AAC3B,IAAA,MAAM,KAAK,WAAA,CAAY,eAAA,CAAgB,IAAA,CAAK,OAAA,CAAQ,UAAU,CAAC,CAAA;AAC/D,IAAA,MAAM,KAAK,WAAA,CAAY,eAAA,CAAgB,IAAA,CAAK,OAAA,CAAQ,WAAW,CAAC,CAAA;AAChE,IAAA,MAAM,KAAK,WAAA,CAAY,eAAA,CAAgB,IAAA,CAAK,OAAA,CAAQ,MAAM,CAAC,CAAA;AAC3D,IAAA,MAAM,KAAK,WAAA,CAAY,eAAA,CAAgB,IAAA,CAAK,OAAA,CAAQ,WAAW,CAAC,CAAA;AAAA,EAClE;AAAA,EAEQ,sBAAA,GAAkC;AACxC,IAAA,OAAO,IAAA,CAAK,aAAA,KAAkB,MAAA,IAAa,IAAA,CAAK,cAAc,SAAA,EAAU;AAAA,EAC1E;AAAA,EAEQ,QAAQ,IAAA,EAAsB;AACpC,IAAA,OAAO,CAAA,EAAG,IAAA,CAAK,MAAM,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA;AAAA,EAC/B;AACF;AAEA,SAAS,eAAe,GAAA,EAA4B;AAClD,EAAA,IAAI,GAAA,KAAQ,IAAA,IAAQ,GAAA,KAAQ,EAAA,EAAI;AAC9B,IAAA,OAAO,CAAA;AAAA,EACT;AACA,EAAA,MAAM,MAAA,GAAS,OAAO,GAAG,CAAA;AACzB,EAAA,OAAO,MAAA,CAAO,QAAA,CAAS,MAAM,CAAA,GAAI,MAAA,GAAS,CAAA;AAC5C;;;AChGA,IAAM,cAAA,GAAiB,oBAAA;AACvB,IAAM,oBAAA,GAAuB,CAAA;AAmBtB,IAAM,gBAAN,MAAoB;AAAA,EASzB,YAAY,OAAA,EAA+B;AAJ3C,IAAA,IAAA,CAAQ,OAAA,GAAmB,KAAA;AAC3B,IAAA,IAAA,CAAQ,YAAA,GAAuB,CAAA;AAC/B,IAAA,IAAA,CAAQ,QAAA,GAAoB,KAAA;AAG1B,IAAA,IAAA,CAAK,YAAY,OAAA,CAAQ,SAAA;AACzB,IAAA,IAAA,CAAK,YAAY,OAAA,CAAQ,SAAA;AACzB,IAAA,IAAA,CAAK,aAAA,GAAgB,QAAQ,aAAA,IAAiB,cAAA;AAC9C,IAAA,IAAA,CAAK,WAAA,GAAc,QAAQ,WAAA,IAAe,oBAAA;AAAA,EAC5C;AAAA;AAAA,EAGA,MAAM,WAAA,GAAgC;AACpC,IAAA,MAAM,WAAA,GAAc,MAAM,IAAA,CAAK,SAAA,CAAU,gBAAA,EAAiB;AAC1D,IAAA,IAAI,CAAC,WAAA,EAAa;AAChB,MAAA,OAAO,KAAA;AAAA,IACT;AACA,IAAA,OAAO,IAAA,CAAK,UAAU,eAAA,EAAgB;AAAA,EACxC;AAAA;AAAA,EAGA,SAAA,GAAqB;AACnB,IAAA,OAAO,IAAA,CAAK,OAAA;AAAA,EACd;AAAA;AAAA,EAGA,MAAM,OAAA,GAAyB;AAC7B,IAAA,IAAI,KAAK,QAAA,EAAU;AACjB,MAAA;AAAA,IACF;AACA,IAAA,IAAA,CAAK,QAAA,GAAW,IAAA;AAChB,IAAA,IAAI,IAAA,CAAK,cAAc,MAAA,EAAW;AAChC,MAAA,IAAA,CAAK,OAAA,GAAU,MAAM,IAAA,CAAK,SAAA,CAAU,IAAA,EAAK;AAAA,IAC3C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,WAAW,OAAA,EAAiC;AAChD,IAAA,IAAA,CAAK,OAAA,GAAU,OAAA;AACf,IAAA,IAAA,CAAK,YAAA,GAAe,CAAA;AACpB,IAAA,IAAI,IAAA,CAAK,cAAc,MAAA,EAAW;AAChC,MAAA,MAAM,IAAA,CAAK,SAAA,CAAU,KAAA,CAAM,OAAO,CAAA;AAAA,IACpC;AAAA,EACF;AAAA;AAAA,EAGA,aAAA,GAAsB;AACpB,IAAA,IAAA,CAAK,YAAA,GAAe,CAAA;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,MAAA,GAA2B;AAC/B,IAAA,MAAM,MAAA,GAAS,MAAM,IAAA,CAAK,SAAA,CAAU,iBAAA,CAAkB;AAAA,MACpD,eAAe,IAAA,CAAK;AAAA,KACrB,CAAA;AACD,IAAA,OAAO,MAAA,CAAO,OAAA;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,MAAA,GAAwB;AAC5B,IAAA,IAAI,CAAC,KAAK,OAAA,EAAS;AACjB,MAAA;AAAA,IACF;AACA,IAAA,MAAM,MAAA,GAAS,MAAM,IAAA,CAAK,SAAA,CAAU,iBAAA,CAAkB;AAAA,MACpD,eAAe,IAAA,CAAK;AAAA,KACrB,CAAA;AACD,IAAA,IAAI,OAAO,OAAA,EAAS;AAClB,MAAA,IAAA,CAAK,YAAA,GAAe,CAAA;AACpB,MAAA;AAAA,IACF;AACA,IAAA,IAAA,CAAK,YAAA,IAAgB,CAAA;AACrB,IAAA,IAAI,IAAA,CAAK,YAAA,IAAgB,IAAA,CAAK,WAAA,EAAa;AACzC,MAAA,MAAM,IAAI,MAAM,6CAA6C,CAAA;AAAA,IAC/D;AACA,IAAA,MAAM,IAAI,MAAM,iCAAiC,CAAA;AAAA,EACnD;AACF;;;ACxHO,IAAM,qBAAN,MAAyB;AAAA,EAO9B,YAAY,OAAA,EAAoC;AAFhD,IAAA,IAAA,CAAQ,QAAA,GAA8C,IAAA;AAGpD,IAAA,IAAA,CAAK,UAAU,OAAA,CAAQ,OAAA;AACvB,IAAA,IAAA,CAAK,UAAU,OAAA,CAAQ,OAAA;AACvB,IAAA,IAAA,CAAK,SAAS,OAAA,CAAQ,MAAA;AACtB,IAAA,IAAA,CAAK,mBAAmB,OAAA,CAAQ,gBAAA;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,aAAA,GAA4C;AAChD,IAAA,IAAI,IAAA,CAAK,aAAa,IAAA,EAAM;AAC1B,MAAA,OAAO,IAAA,CAAK,QAAA;AAAA,IACd;AACA,IAAA,IAAA,CAAK,QAAA,GAAW,KAAK,UAAA,EAAW;AAChC,IAAA,IAAI;AACF,MAAA,OAAO,MAAM,IAAA,CAAK,QAAA;AAAA,IACpB,CAAA,SAAE;AACA,MAAA,IAAA,CAAK,QAAA,GAAW,IAAA;AAAA,IAClB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,YAAA,GAAwB;AAC1B,IAAA,OAAO,KAAK,QAAA,KAAa,IAAA;AAAA,EAC3B;AAAA,EAEA,MAAc,UAAA,GAAyC;AACrD,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,OAAA,CAAQ,IAAA,EAAK;AACxC,IAAA,IAAI,IAAA;AACJ,IAAA,IAAI;AACF,MAAA,IAAA,GAAO,MAAM,IAAA,CAAK,OAAA,CAAQ,OAAO,CAAA;AAAA,IACnC,CAAA,CAAA,MAAQ;AACN,MAAA,MAAM,KAAK,QAAA,EAAS;AACpB,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,IAAI,SAAS,IAAA,EAAM;AACjB,MAAA,MAAM,KAAK,QAAA,EAAS;AACpB,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,MAAM,IAAA,CAAK,OAAA,CAAQ,KAAA,CAAM,IAAI,CAAA;AAC7B,IAAA,IAAI,IAAA,CAAK,qBAAqB,MAAA,EAAW;AACvC,MAAA,MAAM,IAAA,CAAK,iBAAiB,IAAI,CAAA;AAAA,IAClC;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,MAAc,QAAA,GAA0B;AACtC,IAAA,MAAM,IAAA,CAAK,QAAQ,KAAA,EAAM;AACzB,IAAA,IAAA,CAAK,MAAA,CAAO,KAAK,gBAAgB,CAAA;AAAA,EACnC;AACF;;;AC7EA,IAAM,gBAAA,GAAmB,EAAA;AACzB,IAAM,UAAA,GAAa,EAAA,GAAK,EAAA,GAAK,EAAA,GAAK,GAAA;AAc3B,IAAM,oBAAN,MAAwB;AAAA,EAK7B,YAAY,OAAA,EAAmC;AAC7C,IAAA,IAAA,CAAK,QAAQ,OAAA,CAAQ,KAAA;AACrB,IAAA,MAAM,IAAA,GAAO,QAAQ,iBAAA,IAAqB,gBAAA;AAC1C,IAAA,IAAA,CAAK,kBAAkB,IAAA,GAAO,UAAA;AAC9B,IAAA,IAAA,CAAK,GAAA,GAAM,OAAA,CAAQ,GAAA,IAAO,IAAA,CAAK,GAAA;AAAA,EACjC;AAAA,EAEA,MAAM,WAAW,SAAA,EAAmC;AAClD,IAAA,MAAM,KAAK,KAAA,CAAM,KAAA,CAAM,SAAA,IAAa,IAAA,CAAK,KAAK,CAAA;AAAA,EAChD;AAAA,EAEA,MAAM,aAAA,GAAwC;AAC5C,IAAA,OAAO,IAAA,CAAK,MAAM,IAAA,EAAK;AAAA,EACzB;AAAA,EAEA,MAAM,SAAA,GAA8B;AAClC,IAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,KAAA,CAAM,IAAA,EAAK;AACnC,IAAA,IAAI,SAAS,IAAA,EAAM;AAEjB,MAAA,OAAO,KAAA;AAAA,IACT;AACA,IAAA,OAAO,IAAA,CAAK,GAAA,EAAI,GAAI,IAAA,GAAO,IAAA,CAAK,eAAA;AAAA,EAClC;AAAA,EAEA,MAAM,KAAA,GAAuB;AAC3B,IAAA,MAAM,IAAA,CAAK,MAAM,KAAA,EAAM;AAAA,EACzB;AACF;;;ACrCO,SAAS,sBAAsB,SAAA,EAAqC;AACzE,EAAA,OAAO,OAAO,OAAA,KAAgD;AAC5D,IAAA,MAAM,IAAA,GAAoB;AAAA,MACxB,QAAQ,OAAA,CAAQ,MAAA;AAAA,MAChB,SAAS,OAAA,CAAQ,OAAA;AAAA,MACjB,MAAM,OAAA,CAAQ;AAAA,KAChB;AACA,IAAA,IAAI,OAAA,CAAQ,gBAAgB,MAAA,EAAW;AACrC,MAAA,IAAA,CAAK,cAAc,OAAA,CAAQ,WAAA;AAAA,IAC7B;AACA,IAAA,MAAM,QAAA,GAAW,MAAM,SAAA,CAAU,OAAA,CAAQ,KAAK,IAAI,CAAA;AAClD,IAAA,IAAI,IAAA;AACJ,IAAA,IAAI,QAAA,CAAS,WAAW,GAAA,EAAK;AAC3B,MAAA,MAAM,WAAA,GAAc,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,cAAc,CAAA,IAAK,EAAA;AAC5D,MAAA,IAAI,WAAA,CAAY,QAAA,CAAS,kBAAkB,CAAA,EAAG;AAC5C,QAAA,IAAA,GAAQ,MAAM,SAAS,IAAA,EAAK;AAAA,MAC9B;AAAA,IACF;AACA,IAAA,OAAO;AAAA,MACL,QAAQ,QAAA,CAAS,MAAA;AAAA,MACjB,IAAI,QAAA,CAAS,EAAA;AAAA,MACb;AAAA,KACF;AAAA,EACF,CAAA;AACF;;;AC8BO,IAAM,gBAAN,MAAoB;AAAA,EAMzB,YAAY,OAAA,EAA+B;AACzC,IAAA,IAAA,CAAK,OAAO,OAAA,CAAQ,IAAA;AACpB,IAAA,IAAA,CAAK,OAAA,GAAU,OAAA,CAAQ,OAAA,CAAQ,OAAA,CAAQ,OAAO,EAAE,CAAA;AAChD,IAAA,IAAA,CAAK,iBAAiB,OAAA,CAAQ,cAAA;AAC9B,IAAA,IAAA,CAAK,cAAA,GAAiB,QAAQ,cAAA,IAAkB,KAAA;AAAA,EAClD;AAAA,EAEA,aAAa,OAAA,EAAyD;AACpE,IAAA,OAAO,IAAA,CAAK,SAAA,CAAU,kBAAA,EAAoB,OAAO,CAAA;AAAA,EACnD;AAAA,EAEA,kBAAkB,OAAA,EAA8D;AAC9E,IAAA,OAAO,IAAA,CAAK,SAAA,CAAU,aAAA,EAAe,OAAO,CAAA;AAAA,EAC9C;AAAA;AAAA,EAGA,MAAM,aAAA,GAA+C;AACnD,IAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,IAAA,CAAK;AAAA,MAC/B,GAAA,EAAK,CAAA,EAAG,IAAA,CAAK,OAAO,CAAA,oBAAA,CAAA;AAAA,MACpB,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,EAAmB;AAAA,MAC9C,WAAA,EAAa,IAAA,CAAK,cAAA,GAAiB,SAAA,GAAY;AAAA,KAChD,CAAA;AACD,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,kCAAA,EAAqC,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAAA,IACxE;AACA,IAAA,OAAQ,QAAA,CAAS,QAAQ,EAAC;AAAA,EAC5B;AAAA,EAEA,MAAM,MAAA,CAAO,UAAA,GAAsB,KAAA,EAAsB;AACvD,IAAA,MAAM,GAAA,GAAM,aAAa,CAAA,EAAG,IAAA,CAAK,OAAO,CAAA,4BAAA,CAAA,GAAiC,CAAA,EAAG,KAAK,OAAO,CAAA,YAAA,CAAA;AACxF,IAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,IAAA,CAAK;AAAA,MAC/B,GAAA;AAAA,MACA,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS,MAAM,IAAA,CAAK,WAAA,EAAY;AAAA,MAChC,WAAA,EAAa,IAAA,CAAK,cAAA,GAAiB,SAAA,GAAY;AAAA,KAChD,CAAA;AACD,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,0BAAA,EAA6B,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAAA,IAChE;AAAA,EACF;AAAA,EAEA,MAAM,eAAe,OAAA,EAA+C;AAClE,IAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,IAAA,CAAK;AAAA,MAC/B,GAAA,EAAK,CAAA,EAAG,IAAA,CAAK,OAAO,CAAA,qBAAA,CAAA;AAAA,MACpB,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,EAAmB;AAAA,MAC9C,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,OAAO;AAAA,KAC7B,CAAA;AAGD,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,mCAAA,EAAsC,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAAA,IACzE;AAAA,EACF;AAAA,EAEA,MAAM,cAAc,OAAA,EAA8C;AAChE,IAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,IAAA,CAAK;AAAA,MAC/B,GAAA,EAAK,CAAA,EAAG,IAAA,CAAK,OAAO,CAAA,oBAAA,CAAA;AAAA,MACpB,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,EAAmB;AAAA,MAC9C,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,OAAO;AAAA,KAC7B,CAAA;AACD,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,kCAAA,EAAqC,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAAA,IACxE;AAAA,EACF;AAAA,EAEA,MAAM,YAAA,GAA2C;AAC/C,IAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,IAAA,CAAK;AAAA,MAC/B,GAAA,EAAK,CAAA,EAAG,IAAA,CAAK,OAAO,CAAA,YAAA,CAAA;AAAA,MACpB,MAAA,EAAQ,KAAA;AAAA,MACR,OAAA,EAAS,MAAM,IAAA,CAAK,WAAA,EAAY;AAAA,MAChC,WAAA,EAAa,IAAA,CAAK,cAAA,GAAiB,SAAA,GAAY;AAAA,KAChD,CAAA;AACD,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,gCAAA,EAAmC,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAAA,IACtE;AACA,IAAA,OAAO,MAAM,OAAA,CAAQ,QAAA,CAAS,IAAI,CAAA,GAAK,QAAA,CAAS,OAA6B,EAAC;AAAA,EAChF;AAAA,EAEA,MAAM,cAAc,SAAA,EAAkC;AACpD,IAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,IAAA,CAAK;AAAA,MAC/B,KAAK,CAAA,EAAG,IAAA,CAAK,OAAO,CAAA,aAAA,EAAgB,kBAAA,CAAmB,SAAS,CAAC,CAAA,OAAA,CAAA;AAAA,MACjE,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS,MAAM,IAAA,CAAK,WAAA,EAAY;AAAA,MAChC,WAAA,EAAa,IAAA,CAAK,cAAA,GAAiB,SAAA,GAAY;AAAA,KAChD,CAAA;AACD,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,iCAAA,EAAoC,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAAA,IACvE;AAAA,EACF;AAAA,EAEA,MAAc,SAAA,CAAU,IAAA,EAAc,IAAA,EAA6C;AACjF,IAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,IAAA,CAAK;AAAA,MAC/B,GAAA,EAAK,CAAA,EAAG,IAAA,CAAK,OAAO,GAAG,IAAI,CAAA,CAAA;AAAA,MAC3B,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,EAAmB;AAAA,MAC9C,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,IAAI,CAAA;AAAA,MACzB,WAAA,EAAa,IAAA,CAAK,cAAA,GAAiB,SAAA,GAAY;AAAA,KAChD,CAAA;AACD,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,yBAAA,EAA4B,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAAA,IAC/D;AACA,IAAA,OAAQ,QAAA,CAAS,QAAQ,EAAC;AAAA,EAC5B;AAAA,EAEA,MAAc,WAAA,GAA+C;AAC3D,IAAA,MAAM,OAAA,GAAkC,EAAE,cAAA,EAAgB,kBAAA,EAAmB;AAC7E,IAAA,IAAI,IAAA,CAAK,mBAAmB,MAAA,EAAW;AACrC,MAAA,OAAO,OAAA;AAAA,IACT;AACA,IAAA,MAAM,KAAA,GAAQ,MAAM,IAAA,CAAK,cAAA,EAAe;AACxC,IAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,KAAA,KAAU,EAAA,EAAI;AAClC,MAAA,OAAA,CAAQ,aAAA,GAAgB,UAAU,KAAK,CAAA,CAAA;AAAA,IACzC;AACA,IAAA,OAAO,OAAA;AAAA,EACT;AACF;;;AC/MA,SAAS,iBAAiB,KAAA,EAAiC;AACzD,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,EAAA;AAChD;AAEA,SAAS,uBAAuB,MAAA,EAAuC;AACrE,EAAA,KAAA,MAAW,KAAK,MAAA,EAAQ;AACtB,IAAA,IAAI,gBAAA,CAAiB,CAAC,CAAA,EAAG;AACvB,MAAA,OAAO,CAAA;AAAA,IACT;AAAA,EACF;AACA,EAAA,OAAO,MAAA;AACT;AAEA,SAAS,aAAa,CAAA,EAAsC;AAC1D,EAAA,MAAM,QAAyB,EAAC;AAChC,EAAA,MAAM,gBAAA,GAAmB,EAAE,YAAA,EAAc,KAAA;AACzC,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,gBAAgB,CAAA,EAAG;AACnC,IAAA,KAAA,CAAM,IAAA,CAAK,GAAG,gBAAgB,CAAA;AAAA,EAChC;AAEA,EAAA,MAAM,iBAAiB,CAAA,CAAE,eAAA;AACzB,EAAA,IAAI,mBAAmB,MAAA,EAAW;AAChC,IAAA,KAAA,MAAW,CAAA,IAAK,MAAA,CAAO,MAAA,CAAO,cAAc,CAAA,EAAG;AAC7C,MAAA,MAAM,gBAAgB,CAAA,CAAE,KAAA;AACxB,MAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,aAAa,CAAA,EAAG;AAChC,QAAA,KAAA,CAAM,IAAA,CAAK,GAAG,aAAa,CAAA;AAAA,MAC7B;AAAA,IACF;AAAA,EACF;AACA,EAAA,OAAO,KAAA;AACT;AAWO,SAAS,sBAAsB,CAAA,EAAsC;AAC1E,EAAA,IAAI,CAAC,CAAA,EAAG;AACN,IAAA,OAAO,EAAE,KAAA,EAAO,EAAC,EAAE;AAAA,EACrB;AACA,EAAA,MAAM,KAAA,GAAQ,aAAa,CAAC,CAAA;AAC5B,EAAA,MAAM,QAAA,GAAW,CAAC,CAAA,CAAE,UAAA,EAAY,CAAA,CAAE,WAAW,CAAA,CAAE,MAAA,CAAO,gBAAgB,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA;AAChF,EAAA,MAAM,QAAA,GAAW,oBAAoB,CAAA,CAAE,kBAAA,EAAoB,EAAE,IAAA,EAAM,CAAA,CAAE,KAAA,EAAO,CAAA,CAAE,GAAG,CAAA;AACjF,EAAA,MAAM,WAAA,GAAc,mBAAA,CAAoB,CAAA,CAAE,IAAA,EAAM,QAAA,EAAU,EAAE,kBAAA,EAAoB,CAAA,CAAE,KAAA,EAAO,CAAA,CAAE,GAAG,CAAA;AAE9F,EAAA,OAAO;AAAA,IACL,IAAI,CAAA,CAAE,GAAA;AAAA,IACN,QAAA;AAAA,IACA,OAAO,CAAA,CAAE,KAAA;AAAA,IACT,WAAA;AAAA,IACA,WAAW,CAAA,CAAE,UAAA;AAAA,IACb,UAAU,CAAA,CAAE,WAAA;AAAA,IACZ,aAAA,EAAe,OAAA,CAAQ,CAAA,CAAE,cAAc,CAAA;AAAA,IACvC,OAAO,KAAA,CAAM,IAAA,CAAK,IAAI,GAAA,CAAI,KAAK,CAAC,CAAA;AAAA,IAChC,GAAA,EAAK;AAAA,GACP;AACF;;;AC5CO,SAAS,2BAA2B,KAAA,EAA2C;AACpF,EAAA,OAAO,IAAI,eAAA,CAAgB;AAAA,IACzB,WAAW,KAAA,CAAM,QAAA;AAAA,IACjB,UAAA,EAAY,oBAAA;AAAA,IACZ,MAAM,KAAA,CAAM,IAAA;AAAA,IACZ,cAAc,KAAA,CAAM,WAAA;AAAA,IACpB,eAAe,KAAA,CAAM;AAAA,GACtB,EAAE,QAAA,EAAS;AACd;AAMO,SAAS,sBAAsB,KAAA,EAAsC;AAC1E,EAAA,OAAO,IAAI,eAAA,CAAgB;AAAA,IACzB,WAAW,KAAA,CAAM,QAAA;AAAA,IACjB,UAAA,EAAY,eAAA;AAAA,IACZ,eAAe,KAAA,CAAM;AAAA,GACtB,EAAE,QAAA,EAAS;AACd;;;ACzBO,SAAS,gBACd,QAAA,EACoB;AACpB,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,IAAI,QAAA,CAAS,SAAS,SAAA,EAAW;AAC/B,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,MAAM,IAAA,GAAO,SAAS,MAAA,EAAQ,IAAA;AAC9B,EAAA,IAAI,OAAO,IAAA,KAAS,QAAA,IAAY,IAAA,KAAS,EAAA,EAAI;AAC3C,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,OAAO,IAAA;AACT;;;AClBO,SAAS,UAAuC,KAAA,EAA4C;AACjG,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,EAAA,EAAI;AAC7C,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,KAAA,CAAM,GAAG,CAAA;AAC7B,EAAA,IAAI,KAAA,CAAM,WAAW,CAAA,EAAG;AACtB,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,MAAM,OAAA,GAAU,MAAM,CAAC,CAAA;AACvB,EAAA,IAAI,OAAA,KAAY,MAAA,IAAa,OAAA,KAAY,EAAA,EAAI;AAC3C,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,IAAI;AACF,IAAA,MAAM,IAAA,GAAO,gBAAgB,OAAO,CAAA;AACpC,IAAA,MAAM,MAAA,GAAkB,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA;AACvC,IAAA,IAAI,OAAO,MAAA,KAAW,QAAA,IAAY,MAAA,KAAW,IAAA,EAAM;AACjD,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,OAAO,MAAA;AAAA,EACT,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAEA,IAAM,iBAAA,GAAoB,CAAA;AAE1B,SAAS,gBAAgB,KAAA,EAAuB;AAC9C,EAAA,MAAM,UAAA,GAAa,MAAM,OAAA,CAAQ,IAAA,EAAM,GAAG,CAAA,CAAE,OAAA,CAAQ,MAAM,GAAG,CAAA;AAC7D,EAAA,MAAM,SAAA,GAAA,CACH,iBAAA,GAAqB,UAAA,CAAW,MAAA,GAAS,iBAAA,IAAsB,iBAAA;AAClE,EAAA,MAAM,MAAA,GAAS,UAAA,GAAa,GAAA,CAAI,MAAA,CAAO,SAAS,CAAA;AAChD,EAAA,IAAI,OAAO,UAAA,CAAW,IAAA,KAAS,UAAA,EAAY;AACzC,IAAA,MAAM,IAAI,MAAM,2DAA2D,CAAA;AAAA,EAC7E;AACA,EAAA,OAAO,UAAA,CAAW,UAAA,CAAW,IAAA,CAAK,MAAM,CAAC,CAAA;AAC3C;AAEA,SAAS,WAAW,MAAA,EAAwB;AAG1C,EAAA,IAAI,OAAO,gBAAgB,WAAA,EAAa;AACtC,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,MAAM,KAAA,GAAQ,IAAI,UAAA,CAAW,MAAA,CAAO,MAAM,CAAA;AAC1C,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,MAAA,CAAO,QAAQ,CAAA,EAAA,EAAK;AACtC,IAAA,KAAA,CAAM,CAAC,CAAA,GAAI,MAAA,CAAO,UAAA,CAAW,CAAC,CAAA;AAAA,EAChC;AACA,EAAA,OAAO,IAAI,WAAA,CAAY,OAAO,CAAA,CAAE,OAAO,KAAK,CAAA;AAC9C","file":"index.mjs","sourcesContent":["/**\n * URL builders for the realm-aware Keycloak surface area.\n *\n * Every helper takes `baseUrl` and `realm` explicitly — no hardcoded realm\n * names. This is the contract that Phase 2 of the product split relies on:\n * the same package serves the future Questioner-realm app and OnlineMenu-realm\n * app without code change.\n */\n\nconst REALM_PATH_PREFIX = '/realms';\nconst PROTOCOL_PATH = '/protocol/openid-connect';\n\nfunction trimTrailingSlash(value: string): string {\n return value.replace(/\\/$/, '');\n}\n\n/**\n * Compute the issuer URL: `{baseUrl}/realms/{realm}`.\n */\nexport function buildIssuerUrl(baseUrl: string, realm: string): string {\n return `${trimTrailingSlash(baseUrl)}${REALM_PATH_PREFIX}/${encodeURIComponent(realm)}`;\n}\n\n/**\n * Compute the authorization endpoint URL.\n */\nexport function buildAuthorizationEndpoint(baseUrl: string, realm: string): string {\n return `${buildIssuerUrl(baseUrl, realm)}${PROTOCOL_PATH}/auth`;\n}\n\n/**\n * Compute the token endpoint URL.\n */\nexport function buildTokenEndpoint(baseUrl: string, realm: string): string {\n return `${buildIssuerUrl(baseUrl, realm)}${PROTOCOL_PATH}/token`;\n}\n\n/**\n * Compute the userinfo endpoint URL.\n */\nexport function buildUserInfoEndpoint(baseUrl: string, realm: string): string {\n return `${buildIssuerUrl(baseUrl, realm)}${PROTOCOL_PATH}/userinfo`;\n}\n\n/**\n * Compute the logout endpoint URL.\n */\nexport function buildLogoutEndpoint(baseUrl: string, realm: string): string {\n return `${buildIssuerUrl(baseUrl, realm)}${PROTOCOL_PATH}/logout`;\n}\n\nexport interface AuthorizationUrlInput {\n baseUrl: string;\n realm: string;\n clientId: string;\n redirectUri: string;\n scope?: string;\n state?: string;\n codeChallenge?: string;\n codeChallengeMethod?: 'S256' | 'plain';\n}\n\n/**\n * Build a complete authorization URL the user agent can navigate to.\n *\n * All PKCE-related fields are optional so this helper also serves\n * non-PKCE flows (e.g. confidential server-side clients) — but PKCE is\n * the recommended path for SPA / native consumers.\n */\nexport function buildAuthorizationUrl(input: AuthorizationUrlInput): string {\n const params = new URLSearchParams({\n client_id: input.clientId,\n redirect_uri: input.redirectUri,\n response_type: 'code',\n });\n if (typeof input.scope === 'string' && input.scope !== '') {\n params.set('scope', input.scope);\n }\n if (typeof input.state === 'string' && input.state !== '') {\n params.set('state', input.state);\n }\n if (typeof input.codeChallenge === 'string' && input.codeChallenge !== '') {\n params.set('code_challenge', input.codeChallenge);\n params.set('code_challenge_method', input.codeChallengeMethod ?? 'S256');\n }\n return `${buildAuthorizationEndpoint(input.baseUrl, input.realm)}?${params.toString()}`;\n}\n","import type { AuthTokens } from '../types/AuthTokens';\n\nconst SECONDS_TO_MILLIS = 1000;\nconst DEFAULT_LEEWAY_MS = 30 * SECONDS_TO_MILLIS;\n\n/**\n * Determine whether a token bundle is expired.\n *\n * `expiresAt` is interpreted as an absolute UNIX millisecond timestamp.\n *\n * `leewayMs` (default 30 s) shaves a small window off the expiry to compensate\n * for clock skew and round-trip latency: a token that expires at exactly\n * `Date.now()` is essentially useless because by the time the request arrives\n * at the API it will have expired.\n */\nexport function isTokenExpired(\n tokens: Pick<AuthTokens, 'expiresAt'> | null | undefined,\n leewayMs: number = DEFAULT_LEEWAY_MS,\n now: number = Date.now(),\n): boolean {\n if (!tokens) {\n return true;\n }\n if (typeof tokens.expiresAt !== 'number' || tokens.expiresAt <= 0) {\n return true;\n }\n return tokens.expiresAt - leewayMs <= now;\n}\n\n/**\n * Compute the absolute expiry timestamp from a token endpoint `expires_in` value.\n *\n * Returns `0` when `expiresIn` is missing or non-positive — signalling \"unknown\n * expiry, treat as expired\" downstream.\n */\nexport function computeExpiresAt(\n expiresInSeconds: number | undefined,\n now: number = Date.now(),\n): number {\n if (typeof expiresInSeconds !== 'number' || expiresInSeconds <= 0) {\n return 0;\n }\n return now + expiresInSeconds * SECONDS_TO_MILLIS;\n}\n","/**\n * Extract the realm name from a Keycloak issuer URL.\n *\n * Keycloak issuer URLs follow the shape `{baseUrl}/realms/{realm}` (with optional\n * `/protocol/openid-connect` suffix on token endpoints). This helper reverses\n * that convention so existing apps that store only the issuer URL can derive\n * `realm` for the realm-aware {@link AuthClient} constructor.\n *\n * @returns the realm name, or `null` if the URL doesn't match the convention.\n */\nexport function parseRealmFromIssuer(issuerUrl: string | null | undefined): string | null {\n if (typeof issuerUrl !== 'string' || issuerUrl === '') {\n return null;\n }\n const match = /\\/realms\\/([^/?#]+)/i.exec(issuerUrl);\n if (!match || match[1] === undefined || match[1] === '') {\n return null;\n }\n return decodeURIComponent(match[1]);\n}\n\n/**\n * Extract the base URL (scheme + host + optional path prefix) from a\n * Keycloak issuer URL by stripping the `/realms/{realm}...` suffix.\n *\n * Returns the original input unchanged when no `/realms/` segment is found —\n * callers that need strict validation should pair this with `parseRealmFromIssuer`.\n */\nexport function parseBaseUrlFromIssuer(issuerUrl: string | null | undefined): string | null {\n if (typeof issuerUrl !== 'string' || issuerUrl === '') {\n return null;\n }\n const idx = issuerUrl.search(/\\/realms\\//i);\n if (idx === -1) {\n return issuerUrl.replace(/\\/$/, '');\n }\n return issuerUrl.substring(0, idx).replace(/\\/$/, '');\n}\n","import type { AuthTokens } from '../types/AuthTokens';\nimport type { RawTokenResponse, TokenResponse } from '../types/TokenResponse';\nimport { computeExpiresAt } from './isTokenExpired';\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === 'string' && value !== '' ? value : undefined;\n}\n\nfunction asNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\n/**\n * Map a raw OIDC token endpoint response (snake_case) to camelCase.\n *\n * Throws when `access_token` is missing or empty — callers should let this\n * propagate to the auth state machine, which treats it as a login failure.\n */\nexport function normalizeTokenResponse(raw: RawTokenResponse): TokenResponse {\n const accessToken = asString(raw.access_token);\n if (accessToken === undefined) {\n throw new Error('Token response missing access_token');\n }\n return {\n accessToken,\n refreshToken: asString(raw.refresh_token),\n idToken: asString(raw.id_token),\n expiresIn: asNumber(raw.expires_in),\n tokenType: asString(raw.token_type),\n scope: asString(raw.scope),\n };\n}\n\n/**\n * Convert a normalized {@link TokenResponse} into a persistable\n * {@link AuthTokens} bundle by computing `expiresAt` from `expiresIn`.\n */\nexport function tokenResponseToAuthTokens(\n response: TokenResponse,\n now: number = Date.now(),\n): AuthTokens {\n return {\n accessToken: response.accessToken,\n refreshToken: response.refreshToken,\n idToken: response.idToken,\n expiresAt: computeExpiresAt(response.expiresIn, now),\n };\n}\n","/**\n * Tiny dependency-free event emitter for auth lifecycle events.\n *\n * Consumers subscribe to `onSessionExpired` to navigate to the login screen\n * when refresh fails or the inactivity timeout fires. We don't reach for\n * `EventTarget`/`EventEmitter` because we want one consistent API across web,\n * React Native, and node test environments without polyfills.\n */\nexport type AuthEventName = 'sessionExpired';\n\nexport type AuthEventListener = () => void;\n\nexport interface AuthEventUnsubscribe {\n (): void;\n}\n\nexport class AuthEventEmitter {\n private readonly listeners: Map<AuthEventName, Set<AuthEventListener>> = new Map();\n\n on(event: AuthEventName, listener: AuthEventListener): AuthEventUnsubscribe {\n let bucket = this.listeners.get(event);\n if (bucket === undefined) {\n bucket = new Set();\n this.listeners.set(event, bucket);\n }\n bucket.add(listener);\n return (): void => {\n const current = this.listeners.get(event);\n if (current !== undefined) {\n current.delete(listener);\n }\n };\n }\n\n emit(event: AuthEventName): void {\n const bucket = this.listeners.get(event);\n if (bucket === undefined) {\n return;\n }\n // Snapshot so listeners can unsubscribe during dispatch without skipping siblings.\n const snapshot = Array.from(bucket);\n for (const listener of snapshot) {\n listener();\n }\n }\n\n /** Remove all listeners. Useful for `AuthClient.dispose()` and tests. */\n clear(): void {\n this.listeners.clear();\n }\n}\n","import {\n buildAuthorizationEndpoint,\n buildAuthorizationUrl,\n buildIssuerUrl,\n buildLogoutEndpoint,\n buildTokenEndpoint,\n buildUserInfoEndpoint,\n} from './utils/buildKeycloakUrls';\nimport { isTokenExpired } from './utils/isTokenExpired';\nimport { parseBaseUrlFromIssuer, parseRealmFromIssuer } from './utils/parseRealmFromIssuer';\nimport { normalizeTokenResponse, tokenResponseToAuthTokens } from './utils/normalizeTokenResponse';\nimport { AuthEventEmitter, type AuthEventListener, type AuthEventName, type AuthEventUnsubscribe } from './events/AuthEventEmitter';\n\nimport type { AuthApiClient, RawAuthLoginResponse } from './api/AuthApiClient';\nimport type { AuthClientConfig } from './types/AuthClientConfig';\nimport type { AuthTokens } from './types/AuthTokens';\nimport type { TokenStorage } from './types/TokenStorage';\nimport type { InactivityTracker } from './inactivity/InactivityTracker';\nimport type { RefreshInterceptor } from './interceptor/RefreshInterceptor';\n\nconst DEFAULT_SCOPE = 'openid profile email';\nconst OFFLINE_ACCESS_SCOPE = 'offline_access';\n\n/**\n * Inputs to {@link AuthClient.fromIssuerUrl}.\n *\n * Used by consumers that store only an issuer URL and want to derive `realm`\n * + `baseUrl` rather than configure them separately.\n */\nexport interface AuthClientFromIssuerInput {\n issuerUrl: string;\n clientId: string;\n redirectUri?: string;\n scope?: string;\n}\n\n/**\n * Optional collaborators wired into {@link AuthClient} for the v2 surface.\n *\n * - `api` enables `loginWith*`, `logout`, `requestPasswordReset`,\n * `confirmPasswordReset`. Without it, those methods throw.\n * - `interceptor` enables `init()` to silently refresh tokens at boot, and is\n * used by `loginWithOtp/Password` to mark inactivity-active.\n * - `inactivityTracker` enforces the 90-day timeout at `init()`.\n *\n * Consumers can omit any/all of these — the v1 PKCE / token-storage surface\n * keeps working unchanged.\n */\nexport interface AuthClientCollaborators {\n api?: AuthApiClient;\n interceptor?: RefreshInterceptor;\n inactivityTracker?: InactivityTracker;\n events?: AuthEventEmitter;\n}\n\nexport interface LoginOptions {\n /** When true, request `offline_access` scope so the IdP issues a long-lived refresh token. */\n offlineAccess?: boolean;\n}\n\nexport interface LogoutOptions {\n /** Revoke all sessions on the IdP, not just the current one. */\n everywhere?: boolean;\n}\n\nexport class AuthClient {\n private readonly config: AuthClientConfig;\n private readonly tokenStorage: TokenStorage;\n private readonly api: AuthApiClient | undefined;\n private readonly interceptor: RefreshInterceptor | undefined;\n private readonly inactivityTracker: InactivityTracker | undefined;\n private readonly events: AuthEventEmitter;\n\n /**\n * @throws Error when `baseUrl`, `realm`, or `clientId` is missing or empty.\n */\n constructor(config: AuthClientConfig, storage: TokenStorage, collaborators: AuthClientCollaborators = {}) {\n AuthClient.validateConfig(config);\n this.config = {\n ...config,\n scope: config.scope ?? DEFAULT_SCOPE,\n };\n this.tokenStorage = storage;\n this.api = collaborators.api;\n this.interceptor = collaborators.interceptor;\n this.inactivityTracker = collaborators.inactivityTracker;\n this.events = collaborators.events ?? new AuthEventEmitter();\n }\n\n /**\n * Build an {@link AuthClient} from a standalone issuer URL by parsing the\n * realm and base URL. Useful when migrating from the legacy\n * `KEYCLOAK_ISSUER` env var convention.\n *\n * @throws Error when the issuer URL doesn't match `{base}/realms/{realm}`.\n */\n static fromIssuerUrl(\n input: AuthClientFromIssuerInput,\n storage: TokenStorage,\n collaborators: AuthClientCollaborators = {},\n ): AuthClient {\n const realm = parseRealmFromIssuer(input.issuerUrl);\n const baseUrl = parseBaseUrlFromIssuer(input.issuerUrl);\n if (realm === null || baseUrl === null || baseUrl === '') {\n throw new Error(`AuthClient.fromIssuerUrl: cannot parse realm from \"${input.issuerUrl}\"`);\n }\n return new AuthClient(\n {\n baseUrl,\n realm,\n clientId: input.clientId,\n redirectUri: input.redirectUri,\n scope: input.scope,\n },\n storage,\n collaborators,\n );\n }\n\n private static validateConfig(config: AuthClientConfig): void {\n if (typeof config.baseUrl !== 'string' || config.baseUrl === '') {\n throw new Error('AuthClient: baseUrl is required');\n }\n if (typeof config.realm !== 'string' || config.realm === '') {\n throw new Error('AuthClient: realm is required');\n }\n if (typeof config.clientId !== 'string' || config.clientId === '') {\n throw new Error('AuthClient: clientId is required');\n }\n }\n\n get realm(): string {\n return this.config.realm;\n }\n\n get clientId(): string {\n return this.config.clientId;\n }\n\n get baseUrl(): string {\n return this.config.baseUrl.replace(/\\/$/, '');\n }\n\n get scope(): string {\n // Constructor always materialises a scope (either user-supplied or DEFAULT_SCOPE).\n return this.config.scope as string;\n }\n\n get redirectUri(): string | undefined {\n return this.config.redirectUri;\n }\n\n /** Issuer URL: `{baseUrl}/realms/{realm}`. */\n get issuerUrl(): string {\n return buildIssuerUrl(this.baseUrl, this.realm);\n }\n\n get authorizationEndpoint(): string {\n return buildAuthorizationEndpoint(this.baseUrl, this.realm);\n }\n\n get tokenEndpoint(): string {\n return buildTokenEndpoint(this.baseUrl, this.realm);\n }\n\n get userInfoEndpoint(): string {\n return buildUserInfoEndpoint(this.baseUrl, this.realm);\n }\n\n get logoutEndpoint(): string {\n return buildLogoutEndpoint(this.baseUrl, this.realm);\n }\n\n /**\n * Build a fully-formed authorization URL the user agent can navigate to.\n *\n * @throws Error when `redirectUri` is not configured.\n */\n buildAuthorizationUrl(input: {\n state?: string;\n codeChallenge?: string;\n codeChallengeMethod?: 'S256' | 'plain';\n offlineAccess?: boolean;\n } = {}): string {\n if (typeof this.config.redirectUri !== 'string' || this.config.redirectUri === '') {\n throw new Error('AuthClient.buildAuthorizationUrl: redirectUri is required');\n }\n return buildAuthorizationUrl({\n baseUrl: this.baseUrl,\n realm: this.realm,\n clientId: this.clientId,\n redirectUri: this.config.redirectUri,\n scope: this.resolveScope(input.offlineAccess),\n state: input.state,\n codeChallenge: input.codeChallenge,\n codeChallengeMethod: input.codeChallengeMethod,\n });\n }\n\n async getTokens(): Promise<AuthTokens | null> {\n return this.tokenStorage.read();\n }\n\n async setTokens(tokens: AuthTokens): Promise<void> {\n return this.tokenStorage.write(tokens);\n }\n\n async clearTokens(): Promise<void> {\n return this.tokenStorage.clear();\n }\n\n /**\n * Read the current access token if it exists and is not expired.\n * Returns `null` for \"no usable token\".\n */\n async getAccessToken(now: number = Date.now()): Promise<string | null> {\n const tokens = await this.tokenStorage.read();\n if (tokens === null) {\n return null;\n }\n if (isTokenExpired(tokens, undefined, now)) {\n return null;\n }\n return tokens.accessToken;\n }\n\n /** Subscribe to lifecycle events (currently `sessionExpired` only). */\n on(event: AuthEventName, listener: AuthEventListener): AuthEventUnsubscribe {\n return this.events.on(event, listener);\n }\n\n /**\n * Boot-time wiring. Checks the inactivity tracker; if expired, clears\n * tokens and emits `sessionExpired`. Returns whether a usable session\n * survived.\n */\n async init(): Promise<{ hasSession: boolean }> {\n if (this.inactivityTracker !== undefined) {\n const expired = await this.inactivityTracker.isExpired();\n if (expired) {\n await this.tokenStorage.clear();\n await this.inactivityTracker.clear();\n this.events.emit('sessionExpired');\n return { hasSession: false };\n }\n }\n const tokens = await this.tokenStorage.read();\n return { hasSession: tokens !== null };\n }\n\n /**\n * Trigger a refresh via the configured interceptor. Returns the new tokens\n * or `null` when the refresh failed (in which case `sessionExpired` has\n * already fired).\n *\n * @throws Error when no interceptor is configured.\n */\n async refresh(): Promise<AuthTokens | null> {\n if (this.interceptor === undefined) {\n throw new Error('AuthClient.refresh: no RefreshInterceptor configured');\n }\n return this.interceptor.refreshTokens();\n }\n\n async loginWithOtp(input: { email: string; otp: string; tenantId?: string } & LoginOptions): Promise<AuthTokens> {\n return this.runLogin(this.requireApi().loginWithOtp({\n email: input.email,\n otp: input.otp,\n tenantId: input.tenantId,\n offlineAccess: input.offlineAccess ?? false,\n }));\n }\n\n async loginWithPassword(\n input: { email: string; password: string; tenantId?: string } & LoginOptions,\n ): Promise<AuthTokens> {\n return this.runLogin(this.requireApi().loginWithPassword({\n email: input.email,\n password: input.password,\n tenantId: input.tenantId,\n offlineAccess: input.offlineAccess ?? false,\n }));\n }\n\n async logout(options: LogoutOptions = {}): Promise<void> {\n const api = this.requireApi();\n try {\n await api.logout(options.everywhere ?? false);\n } finally {\n await this.tokenStorage.clear();\n if (this.inactivityTracker !== undefined) {\n await this.inactivityTracker.clear();\n }\n }\n }\n\n async requestPasswordReset(input: { email: string; tenantId?: string }): Promise<void> {\n return this.requireApi().forgotPassword({ email: input.email, tenantId: input.tenantId });\n }\n\n async confirmPasswordReset(input: { token: string; newPassword: string }): Promise<void> {\n return this.requireApi().resetPassword({ token: input.token, newPassword: input.newPassword });\n }\n\n /** Internal: run a login HTTP call, persist tokens, mark inactivity-active. */\n private async runLogin(promise: Promise<RawAuthLoginResponse>): Promise<AuthTokens> {\n const raw = await promise;\n if (typeof raw.access_token !== 'string' || raw.access_token === '') {\n throw new Error('AuthClient: login response missing access_token');\n }\n // After the guard above, raw.access_token is `string`; widen the optional-shaped\n // RawAuthLoginResponse into the strict RawTokenResponse the normaliser expects.\n const normalized = normalizeTokenResponse({ ...raw, access_token: raw.access_token });\n const tokens = tokenResponseToAuthTokens(normalized);\n await this.tokenStorage.write(tokens);\n if (this.inactivityTracker !== undefined) {\n await this.inactivityTracker.markActive();\n }\n return tokens;\n }\n\n private requireApi(): AuthApiClient {\n if (this.api === undefined) {\n throw new Error('AuthClient: no AuthApiClient configured');\n }\n return this.api;\n }\n\n private resolveScope(offlineAccess?: boolean): string {\n if (offlineAccess !== true) {\n return this.scope;\n }\n if (this.scope.includes(OFFLINE_ACCESS_SCOPE)) {\n return this.scope;\n }\n return `${this.scope} ${OFFLINE_ACCESS_SCOPE}`.trim();\n }\n}\n","/**\n * Roles emitted by Keycloak realms in the dloizides.com portfolio.\n *\n * Lives in its own file per the project convention: each exported `const enum`\n * sits alone so it can be imported without dragging the rest of the type tree.\n */\nexport const enum KeycloakRoles {\n SuperUser = 'superUser',\n Admin = 'admin',\n User = 'user',\n}\n\nconst KEYCLOAK_ROLE_VALUES: readonly string[] = [\n KeycloakRoles.SuperUser,\n KeycloakRoles.Admin,\n KeycloakRoles.User,\n];\n\n/**\n * Type guard that narrows a string to a known {@link KeycloakRoles} value.\n *\n * Use this when ingesting role claims from the network, where the wire payload\n * is `string[]` but downstream code wants `KeycloakRoles[]`.\n */\nexport function isKeycloakRole(value: string): value is KeycloakRoles {\n return KEYCLOAK_ROLE_VALUES.includes(value);\n}\n","import type { AuthTokens } from '../types/AuthTokens';\nimport type { TokenStorage } from '../types/TokenStorage';\n\n/**\n * Subset of `Storage` we actually use. Lets callers inject `localStorage`,\n * `sessionStorage`, or any compatible polyfill.\n */\nexport interface StorageLike {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\nexport interface BrowserStorageTokenStorageOptions {\n storage: StorageLike;\n /** Storage key. Defaults to `auth.tokens`. */\n key?: string;\n}\n\nconst DEFAULT_KEY = 'auth.tokens';\n\nfunction isAuthTokens(value: unknown): value is AuthTokens {\n if (typeof value !== 'object' || value === null) {\n return false;\n }\n const candidate = value as Record<string, unknown>;\n return typeof candidate.accessToken === 'string' && typeof candidate.expiresAt === 'number';\n}\n\n/**\n * Persist tokens in any `Storage`-shaped backend (`localStorage`, `sessionStorage`,\n * AsyncStorage shim, etc.). The class is sync-aware but exposes a Promise-based\n * API to match {@link TokenStorage}.\n *\n * Errors during read are swallowed and surfaced as `null` (corrupt JSON, denied\n * access in some private-mode browsers, etc.). Errors during write/clear are\n * propagated so callers can decide whether to retry or fall back.\n */\nexport class BrowserStorageTokenStorage implements TokenStorage {\n private readonly storage: StorageLike;\n private readonly key: string;\n\n constructor(options: BrowserStorageTokenStorageOptions) {\n this.storage = options.storage;\n this.key = options.key ?? DEFAULT_KEY;\n }\n\n read(): Promise<AuthTokens | null> {\n return Promise.resolve(this.readSync());\n }\n\n write(tokens: AuthTokens): Promise<void> {\n this.storage.setItem(this.key, JSON.stringify(tokens));\n return Promise.resolve();\n }\n\n clear(): Promise<void> {\n this.storage.removeItem(this.key);\n return Promise.resolve();\n }\n\n private readSync(): AuthTokens | null {\n try {\n const raw = this.storage.getItem(this.key);\n if (raw === null || raw === '') {\n return null;\n }\n const parsed: unknown = JSON.parse(raw);\n return isAuthTokens(parsed) ? parsed : null;\n } catch {\n return null;\n }\n }\n}\n","import type { AuthTokens } from '../types/AuthTokens';\nimport type { TokenStorage } from '../types/TokenStorage';\n\n/**\n * In-memory storage backed by a single instance variable.\n *\n * Useful for tests, server-side rendering, and as a default fallback when no\n * platform-specific storage is available. Tokens are lost on process exit.\n */\nexport class InMemoryTokenStorage implements TokenStorage {\n private tokens: AuthTokens | null = null;\n\n read(): Promise<AuthTokens | null> {\n return Promise.resolve(this.tokens);\n }\n\n write(tokens: AuthTokens): Promise<void> {\n this.tokens = tokens;\n return Promise.resolve();\n }\n\n clear(): Promise<void> {\n this.tokens = null;\n return Promise.resolve();\n }\n}\n","import type { AuthTokens } from '../types/AuthTokens';\nimport type { TokenStorage } from '../types/TokenStorage';\n\n/**\n * Web token storage that pairs an in-memory access token with a backend-managed\n * httpOnly + Secure + SameSite=Lax refresh-token cookie.\n *\n * The browser handles the refresh cookie (`__Host-refresh` by default — set by\n * the IdentityService on login and rotated on every `/auth/refresh-cookie`\n * call). JavaScript MUST NOT have access to it, so this adapter intentionally\n * does NOT persist `refreshToken` into the cookie itself; that's the backend's\n * job. The adapter just keeps the access token in memory and exposes the same\n * `TokenStorage` interface as `BrowserStorageTokenStorage` so the rest of the\n * library doesn't need to know which transport is in use.\n *\n * Page reloads drop the access token (memory clears), but the refresh cookie\n * survives — `RefreshInterceptor` swaps it for a new access token via\n * `/auth/refresh-cookie` with `credentials: 'include'`.\n */\nexport class CookieTokenStorage implements TokenStorage {\n private accessToken: string | null = null;\n private idToken: string | undefined = undefined;\n private expiresAt: number = 0;\n\n read(): Promise<AuthTokens | null> {\n if (this.accessToken === null) {\n return Promise.resolve(null);\n }\n const tokens: AuthTokens = {\n accessToken: this.accessToken,\n idToken: this.idToken,\n expiresAt: this.expiresAt,\n };\n return Promise.resolve(tokens);\n }\n\n write(tokens: AuthTokens): Promise<void> {\n this.accessToken = tokens.accessToken;\n this.idToken = tokens.idToken;\n this.expiresAt = tokens.expiresAt;\n // Intentionally drop tokens.refreshToken — the backend cookie is the\n // source of truth for refresh material, and we never want it on JS heap.\n return Promise.resolve();\n }\n\n clear(): Promise<void> {\n this.accessToken = null;\n this.idToken = undefined;\n this.expiresAt = 0;\n return Promise.resolve();\n }\n}\n","import type { AuthTokens } from '../types/AuthTokens';\nimport type { TokenStorage } from '../types/TokenStorage';\n\n/**\n * Subset of `expo-secure-store` we use, abstracted so the package itself never\n * imports `expo-secure-store` (and so web bundles never pull it in).\n *\n * Mobile consumers wire this up at the edge:\n *\n * ```ts\n * import * as SecureStore from 'expo-secure-store';\n * const adapter: SecureStoreLike = {\n * getItemAsync: SecureStore.getItemAsync,\n * setItemAsync: SecureStore.setItemAsync,\n * deleteItemAsync: SecureStore.deleteItemAsync,\n * };\n * ```\n */\nexport interface SecureStoreLike {\n getItemAsync(key: string, options?: { requireAuthentication?: boolean }): Promise<string | null>;\n setItemAsync(key: string, value: string, options?: { requireAuthentication?: boolean }): Promise<void>;\n deleteItemAsync(key: string, options?: { requireAuthentication?: boolean }): Promise<void>;\n}\n\n/**\n * Optional biometric gate. When provided AND `requireBiometric` is `true`, the\n * gate's `unlock()` is called before reading the refresh token. Used by mobile\n * consumers that opt in to biometric-protected sessions.\n */\nexport interface BiometricGateLike {\n unlock(): Promise<void>;\n isEnabled(): boolean;\n}\n\nexport interface SecureStoreTokenStorageOptions {\n secureStore: SecureStoreLike;\n /** Defaults applied to every key — usually `'auth'`. */\n keyPrefix?: string;\n /**\n * When true, secure-store reads use `requireAuthentication: true`, prompting\n * the OS biometric / device-passcode dialog (iOS Keychain access control,\n * Android Keystore strongbox).\n */\n requireAuthentication?: boolean;\n /**\n * Optional biometric gate run BEFORE the secure-store read. Belt-and-braces\n * with `requireAuthentication`: the gate enforces our own retry/lockout\n * semantics, while `requireAuthentication` enforces the OS keychain ACL.\n */\n biometricGate?: BiometricGateLike;\n}\n\nconst DEFAULT_PREFIX = 'auth';\nconst ACCESS_KEY = 'access';\nconst REFRESH_KEY = 'refresh';\nconst ID_KEY = 'id';\nconst EXPIRES_KEY = 'expiresAt';\n\n/**\n * Persist tokens in iOS Keychain / Android Keystore via `expo-secure-store`.\n *\n * Keys are split (access / refresh / id / expiresAt) rather than stored as a\n * single JSON blob so the OS-level ACL on the refresh token slot can be\n * tightened independently. With `requireAuthentication: true`, reads of any\n * key trigger the OS biometric prompt — that's why we keep it OFF for writes\n * (login flows must not prompt) and ON for reads (boot-time session restore).\n *\n * Storage key shape: `{prefix}.{slot}` (e.g. `auth.refresh`).\n */\nexport class SecureStoreTokenStorage implements TokenStorage {\n private readonly secureStore: SecureStoreLike;\n private readonly prefix: string;\n private readonly requireAuthentication: boolean;\n private readonly biometricGate: BiometricGateLike | undefined;\n\n constructor(options: SecureStoreTokenStorageOptions) {\n this.secureStore = options.secureStore;\n this.prefix = options.keyPrefix ?? DEFAULT_PREFIX;\n this.requireAuthentication = options.requireAuthentication ?? false;\n this.biometricGate = options.biometricGate;\n }\n\n async read(): Promise<AuthTokens | null> {\n if (this.shouldRunBiometricGate()) {\n // Biometric gate may throw — let it propagate. Caller (RefreshInterceptor\n // / AuthClient.init) will treat that as a failed restore and clear.\n await (this.biometricGate as BiometricGateLike).unlock();\n }\n const readOptions = this.requireAuthentication ? { requireAuthentication: true } : undefined;\n const accessToken = await this.secureStore.getItemAsync(this.fullKey(ACCESS_KEY), readOptions);\n if (accessToken === null) {\n return null;\n }\n const refreshTokenRaw = await this.secureStore.getItemAsync(this.fullKey(REFRESH_KEY), readOptions);\n const idTokenRaw = await this.secureStore.getItemAsync(this.fullKey(ID_KEY));\n const expiresAtRaw = await this.secureStore.getItemAsync(this.fullKey(EXPIRES_KEY));\n const expiresAt = parseExpiresAt(expiresAtRaw);\n return {\n accessToken,\n refreshToken: refreshTokenRaw === null ? undefined : refreshTokenRaw,\n idToken: idTokenRaw === null ? undefined : idTokenRaw,\n expiresAt,\n };\n }\n\n async write(tokens: AuthTokens): Promise<void> {\n await this.secureStore.setItemAsync(this.fullKey(ACCESS_KEY), tokens.accessToken);\n if (tokens.refreshToken !== undefined && tokens.refreshToken !== '') {\n await this.secureStore.setItemAsync(this.fullKey(REFRESH_KEY), tokens.refreshToken);\n } else {\n await this.secureStore.deleteItemAsync(this.fullKey(REFRESH_KEY));\n }\n if (tokens.idToken !== undefined && tokens.idToken !== '') {\n await this.secureStore.setItemAsync(this.fullKey(ID_KEY), tokens.idToken);\n } else {\n await this.secureStore.deleteItemAsync(this.fullKey(ID_KEY));\n }\n await this.secureStore.setItemAsync(this.fullKey(EXPIRES_KEY), String(tokens.expiresAt));\n }\n\n async clear(): Promise<void> {\n await this.secureStore.deleteItemAsync(this.fullKey(ACCESS_KEY));\n await this.secureStore.deleteItemAsync(this.fullKey(REFRESH_KEY));\n await this.secureStore.deleteItemAsync(this.fullKey(ID_KEY));\n await this.secureStore.deleteItemAsync(this.fullKey(EXPIRES_KEY));\n }\n\n private shouldRunBiometricGate(): boolean {\n return this.biometricGate !== undefined && this.biometricGate.isEnabled();\n }\n\n private fullKey(slot: string): string {\n return `${this.prefix}.${slot}`;\n }\n}\n\nfunction parseExpiresAt(raw: string | null): number {\n if (raw === null || raw === '') {\n return 0;\n }\n const parsed = Number(raw);\n return Number.isFinite(parsed) ? parsed : 0;\n}\n","/**\n * Subset of `expo-local-authentication` we use, abstracted so the package\n * itself never imports `expo-local-authentication`.\n *\n * Mobile consumers wire this up at the edge:\n *\n * ```ts\n * import * as LocalAuthentication from 'expo-local-authentication';\n * const adapter: LocalAuthLike = {\n * hasHardwareAsync: LocalAuthentication.hasHardwareAsync,\n * isEnrolledAsync: LocalAuthentication.isEnrolledAsync,\n * authenticateAsync: (opts) => LocalAuthentication.authenticateAsync(opts),\n * };\n * ```\n */\nexport interface LocalAuthLike {\n hasHardwareAsync(): Promise<boolean>;\n isEnrolledAsync(): Promise<boolean>;\n authenticateAsync(options?: {\n promptMessage?: string;\n cancelLabel?: string;\n disableDeviceFallback?: boolean;\n }): Promise<{ success: boolean; error?: string }>;\n}\n\n/**\n * Optional persistence so the \"enabled\" flag survives app restart. Backed by\n * any `TokenStorage`-shaped key/value store via the calling consumer (or the\n * app's settings store). We keep it pluggable to avoid coupling\n * `BiometricGate` to a specific storage adapter.\n */\nexport interface BiometricFlagStore {\n read(): Promise<boolean>;\n write(enabled: boolean): Promise<void>;\n}\n\nexport interface BiometricGateOptions {\n localAuth: LocalAuthLike;\n /** Optional persistence for the user's opt-in choice. */\n flagStore?: BiometricFlagStore;\n /** Default prompt message; consumers usually override. */\n promptMessage?: string;\n /** Max consecutive prompt failures before {@link unlock} throws. Default 3. */\n maxFailures?: number;\n}\n\nconst DEFAULT_PROMPT = 'Unlock to continue';\nconst DEFAULT_MAX_FAILURES = 3;\n\n/**\n * Biometric gate wrapping `expo-local-authentication`.\n *\n * Lifecycle:\n *\n * 1. `isAvailable()` — checks hardware + enrolment. Pure read.\n * 2. `setEnabled(true|false)` — consumer's settings UI flips this. Persisted\n * via the optional flag store. Default = disabled (opt-in).\n * 3. `unlock()` — called by `SecureStoreTokenStorage` (when wired) or by\n * consumer code before sensitive operations. Counts consecutive failures;\n * after `maxFailures` (default 3), throws `BiometricLockedOutError` and\n * consumers MUST navigate to login.\n * 4. `prompt()` — one-shot biometric prompt that doesn't change the failure\n * counter. Useful for re-confirming an action mid-session.\n *\n * The failure counter resets on success.\n */\nexport class BiometricGate {\n private readonly localAuth: LocalAuthLike;\n private readonly flagStore: BiometricFlagStore | undefined;\n private readonly promptMessage: string;\n private readonly maxFailures: number;\n private enabled: boolean = false;\n private failureCount: number = 0;\n private hydrated: boolean = false;\n\n constructor(options: BiometricGateOptions) {\n this.localAuth = options.localAuth;\n this.flagStore = options.flagStore;\n this.promptMessage = options.promptMessage ?? DEFAULT_PROMPT;\n this.maxFailures = options.maxFailures ?? DEFAULT_MAX_FAILURES;\n }\n\n /** Hardware present AND a fingerprint/face ID is enrolled. */\n async isAvailable(): Promise<boolean> {\n const hasHardware = await this.localAuth.hasHardwareAsync();\n if (!hasHardware) {\n return false;\n }\n return this.localAuth.isEnrolledAsync();\n }\n\n /** Synchronous read of the current enabled flag (post-hydration). */\n isEnabled(): boolean {\n return this.enabled;\n }\n\n /** Read the persisted opt-in flag once at app boot. Idempotent. */\n async hydrate(): Promise<void> {\n if (this.hydrated) {\n return;\n }\n this.hydrated = true;\n if (this.flagStore !== undefined) {\n this.enabled = await this.flagStore.read();\n }\n }\n\n /**\n * Toggle biometric requirement. Persists via {@link BiometricFlagStore} when\n * configured. Resets the failure counter so a re-enable starts fresh.\n */\n async setEnabled(enabled: boolean): Promise<void> {\n this.enabled = enabled;\n this.failureCount = 0;\n if (this.flagStore !== undefined) {\n await this.flagStore.write(enabled);\n }\n }\n\n /** Reset the failure counter. Tests + consumer recovery flows. */\n resetFailures(): void {\n this.failureCount = 0;\n }\n\n /**\n * One-shot biometric prompt. Returns `true` on success. Does NOT throw on\n * failure or update the failure counter — useful for action confirmation.\n */\n async prompt(): Promise<boolean> {\n const result = await this.localAuth.authenticateAsync({\n promptMessage: this.promptMessage,\n });\n return result.success;\n }\n\n /**\n * Required pre-condition for sensitive token reads. No-op when disabled.\n *\n * @throws Error after {@link maxFailures} consecutive failures.\n * @throws Error on a single failure (lower in the count, but still throws so\n * `SecureStoreTokenStorage.read()` short-circuits).\n */\n async unlock(): Promise<void> {\n if (!this.enabled) {\n return;\n }\n const result = await this.localAuth.authenticateAsync({\n promptMessage: this.promptMessage,\n });\n if (result.success) {\n this.failureCount = 0;\n return;\n }\n this.failureCount += 1;\n if (this.failureCount >= this.maxFailures) {\n throw new Error('Biometric authentication failed; locked out');\n }\n throw new Error('Biometric authentication failed');\n }\n}\n","import type { AuthEventEmitter } from '../events/AuthEventEmitter';\nimport type { AuthTokens } from '../types/AuthTokens';\nimport type { TokenStorage } from '../types/TokenStorage';\n\n/**\n * The pluggable refresh function the interceptor calls when an access token\n * is missing or expired. Implementations differ per transport:\n *\n * - **Mobile (SecureStore)**: posts to `/auth/refresh` with the refresh token\n * from `AuthTokens.refreshToken`.\n * - **Web (Cookie)**: posts to `/auth/refresh-cookie` with `credentials:\n * 'include'` — the refresh token rides on the httpOnly cookie; `current`\n * carries only the access token (refresh token will be undefined).\n *\n * Returns the new token bundle, or `null` when refresh failed in a way that\n * means \"session over\" (e.g., 401 from the auth server).\n */\nexport type RefreshFn = (current: AuthTokens | null) => Promise<AuthTokens | null>;\n\nexport interface RefreshInterceptorOptions {\n storage: TokenStorage;\n refresh: RefreshFn;\n events: AuthEventEmitter;\n /**\n * Optional callback fired AFTER tokens are persisted on a successful\n * refresh. Used by `AuthClient` to update the inactivity tracker.\n */\n onRefreshSuccess?: (tokens: AuthTokens) => Promise<void> | void;\n}\n\n/**\n * Coordinates refresh-token swaps so concurrent 401s don't trigger N parallel\n * refreshes. The first caller to hit `refreshTokens()` while no refresh is\n * already in flight wins the role of \"refresher\"; everyone else awaits the\n * same promise.\n *\n * On failure, storage is cleared and `sessionExpired` is emitted exactly once\n * per refresh attempt.\n */\nexport class RefreshInterceptor {\n private readonly storage: TokenStorage;\n private readonly refresh: RefreshFn;\n private readonly events: AuthEventEmitter;\n private readonly onRefreshSuccess: ((tokens: AuthTokens) => Promise<void> | void) | undefined;\n private inflight: Promise<AuthTokens | null> | null = null;\n\n constructor(options: RefreshInterceptorOptions) {\n this.storage = options.storage;\n this.refresh = options.refresh;\n this.events = options.events;\n this.onRefreshSuccess = options.onRefreshSuccess;\n }\n\n /**\n * Trigger (or join) a refresh. Returns the new tokens, or `null` if the\n * refresh failed — in which case storage has already been cleared and\n * `sessionExpired` already fired.\n */\n async refreshTokens(): Promise<AuthTokens | null> {\n if (this.inflight !== null) {\n return this.inflight;\n }\n this.inflight = this.runRefresh();\n try {\n return await this.inflight;\n } finally {\n this.inflight = null;\n }\n }\n\n /**\n * Whether a refresh is currently in flight. Exposed for tests / debug.\n */\n get isRefreshing(): boolean {\n return this.inflight !== null;\n }\n\n private async runRefresh(): Promise<AuthTokens | null> {\n const current = await this.storage.read();\n let next: AuthTokens | null;\n try {\n next = await this.refresh(current);\n } catch {\n await this.failHard();\n return null;\n }\n if (next === null) {\n await this.failHard();\n return null;\n }\n await this.storage.write(next);\n if (this.onRefreshSuccess !== undefined) {\n await this.onRefreshSuccess(next);\n }\n return next;\n }\n\n private async failHard(): Promise<void> {\n await this.storage.clear();\n this.events.emit('sessionExpired');\n }\n}\n","/**\n * Pluggable persistence for the `lastRefreshedAt` timestamp.\n *\n * Decoupled from `TokenStorage` so consumers can pick a different backend\n * (e.g., write through `AsyncStorage` on RN where the secure store would\n * gate every read on biometric).\n */\nexport interface InactivityStore {\n read(): Promise<number | null>;\n write(timestamp: number): Promise<void>;\n clear(): Promise<void>;\n}\n\nexport interface InactivityTrackerOptions {\n store: InactivityStore;\n /**\n * Maximum days the user can be inactive (no successful refresh) before\n * sessions are forcibly cleared. Default 90 (matches mobile decision).\n */\n maxInactivityDays?: number;\n /** Inject for tests; defaults to `Date.now`. */\n now?: () => number;\n}\n\nconst DEFAULT_MAX_DAYS = 90;\nconst MS_PER_DAY = 24 * 60 * 60 * 1000;\n\n/**\n * Tracks the last time a refresh succeeded and decides whether the session\n * has aged past its inactivity threshold.\n *\n * - `markActive(now?)` is called by `RefreshInterceptor` after every\n * successful token swap.\n * - `isExpired()` is called from `AuthClient.init()` at boot. If true,\n * consumers clear tokens and emit `sessionExpired`.\n *\n * Choosing days (not e.g. minutes) makes the policy match what users\n * understand: a session left untouched for 90 days needs re-auth.\n */\nexport class InactivityTracker {\n private readonly store: InactivityStore;\n private readonly maxInactivityMs: number;\n private readonly now: () => number;\n\n constructor(options: InactivityTrackerOptions) {\n this.store = options.store;\n const days = options.maxInactivityDays ?? DEFAULT_MAX_DAYS;\n this.maxInactivityMs = days * MS_PER_DAY;\n this.now = options.now ?? Date.now;\n }\n\n async markActive(timestamp?: number): Promise<void> {\n await this.store.write(timestamp ?? this.now());\n }\n\n async getLastActive(): Promise<number | null> {\n return this.store.read();\n }\n\n async isExpired(): Promise<boolean> {\n const last = await this.store.read();\n if (last === null) {\n // Never refreshed — treat as not expired so a fresh login isn't punished.\n return false;\n }\n return this.now() - last > this.maxInactivityMs;\n }\n\n async clear(): Promise<void> {\n await this.store.clear();\n }\n}\n","/**\n * Minimal HTTP transport the package depends on.\n *\n * `AuthClient` orchestrates token-related HTTP calls (login, refresh, logout,\n * password reset) but doesn't import `fetch` directly — keeping the package\n * runtime-agnostic. Consumers wire native fetch, axios, ky, or whatever their\n * platform exposes.\n */\nexport interface HttpRequest {\n url: string;\n method: 'GET' | 'POST' | 'DELETE';\n headers?: Record<string, string>;\n /** When set, body is sent as the request body; serialization is the caller's job. */\n body?: string;\n /** Browser fetch only — pass through `credentials: 'include'` for cookie auth. */\n credentials?: 'include' | 'same-origin' | 'omit';\n}\n\nexport interface HttpResponse {\n status: number;\n ok: boolean;\n /** Parsed body (already JSON-decoded). `undefined` for 204 / empty bodies. */\n data?: unknown;\n}\n\nexport type HttpClient = (request: HttpRequest) => Promise<HttpResponse>;\n\n/**\n * Wrap the platform's native `fetch` into the package's `HttpClient` shape.\n * Decoded JSON when `Content-Type` is JSON, otherwise leaves data undefined.\n *\n * Errors thrown by `fetch` (network / abort) are NOT swallowed — callers\n * decide whether to treat them as session-ending.\n */\nexport function createFetchHttpClient(fetchImpl: typeof fetch): HttpClient {\n return async (request: HttpRequest): Promise<HttpResponse> => {\n const init: RequestInit = {\n method: request.method,\n headers: request.headers,\n body: request.body,\n };\n if (request.credentials !== undefined) {\n init.credentials = request.credentials;\n }\n const response = await fetchImpl(request.url, init);\n let data: unknown;\n if (response.status !== 204) {\n const contentType = response.headers.get('content-type') ?? '';\n if (contentType.includes('application/json')) {\n data = (await response.json()) as unknown;\n }\n }\n return {\n status: response.status,\n ok: response.ok,\n data,\n };\n };\n}\n","import type { HttpClient } from '../http/HttpClient';\n\n/**\n * Backend session record returned by `GET /me/sessions`.\n *\n * Shape mirrors the existing IdentityService response — see\n * `Services/Identity/.../GetSessions.cs`. Defined as a permissive interface\n * so newer fields (added server-side) flow through without a package bump.\n */\nexport interface AuthSessionInfo {\n id: string;\n isCurrent?: boolean;\n ipAddress?: string;\n userAgent?: string;\n createdAt?: string;\n lastSeenAt?: string;\n [key: string]: unknown;\n}\n\nexport interface AuthApiClientOptions {\n http: HttpClient;\n /** API base, e.g. `https://api.dloizides.com`. No trailing slash needed. */\n baseUrl: string;\n /**\n * Optional supplier of the current access token, used as a Bearer header on\n * authenticated calls (sessions list, revoke, logout). When omitted those\n * calls send no Authorization header — typical for cookie-based web auth.\n */\n getAccessToken?: () => Promise<string | null>;\n /**\n * When true, every request adds `credentials: 'include'`. Required for\n * cookie-based web auth (`__Host-refresh` lives in an httpOnly cookie).\n */\n useCredentials?: boolean;\n}\n\nexport interface OtpLoginRequest {\n email: string;\n otp: string;\n tenantId?: string;\n offlineAccess?: boolean;\n}\n\nexport interface PasswordLoginRequest {\n email: string;\n password: string;\n tenantId?: string;\n offlineAccess?: boolean;\n}\n\nexport interface ForgotPasswordRequest {\n email: string;\n tenantId?: string;\n}\n\nexport interface ResetPasswordRequest {\n token: string;\n newPassword: string;\n}\n\nexport interface RawAuthLoginResponse {\n access_token?: string;\n refresh_token?: string;\n id_token?: string;\n expires_in?: number;\n token_type?: string;\n scope?: string;\n [key: string]: unknown;\n}\n\n/**\n * Thin HTTP client for the IdentityService auth surface.\n *\n * Endpoint paths match the backend task `auth-password-reset-backend.md`:\n *\n * - `POST /auth/verify-otp`\n * - `POST /auth/login` (password)\n * - `POST /auth/logout` and `POST /auth/logout?everywhere=true`\n * - `POST /auth/refresh-cookie` (web cookie flow)\n * - `POST /auth/forgot-password`\n * - `POST /auth/reset-password`\n * - `GET /me/sessions`\n * - `POST /me/sessions/{id}/revoke`\n *\n * Doesn't touch token storage — that's `AuthClient`'s job. Doesn't decide\n * what to do with errors — callers handle them. Just builds requests and\n * deserialises responses.\n */\nexport class AuthApiClient {\n private readonly http: HttpClient;\n private readonly baseUrl: string;\n private readonly getAccessToken: (() => Promise<string | null>) | undefined;\n private readonly useCredentials: boolean;\n\n constructor(options: AuthApiClientOptions) {\n this.http = options.http;\n this.baseUrl = options.baseUrl.replace(/\\/$/, '');\n this.getAccessToken = options.getAccessToken;\n this.useCredentials = options.useCredentials ?? false;\n }\n\n loginWithOtp(request: OtpLoginRequest): Promise<RawAuthLoginResponse> {\n return this.postLogin('/auth/verify-otp', request);\n }\n\n loginWithPassword(request: PasswordLoginRequest): Promise<RawAuthLoginResponse> {\n return this.postLogin('/auth/login', request);\n }\n\n /** Web cookie-flow refresh. Sends no body; cookie travels via `credentials`. */\n async refreshCookie(): Promise<RawAuthLoginResponse> {\n const response = await this.http({\n url: `${this.baseUrl}/auth/refresh-cookie`,\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n credentials: this.useCredentials ? 'include' : undefined,\n });\n if (!response.ok) {\n throw new Error(`refresh-cookie failed with status ${response.status}`);\n }\n return (response.data ?? {}) as RawAuthLoginResponse;\n }\n\n async logout(everywhere: boolean = false): Promise<void> {\n const url = everywhere ? `${this.baseUrl}/auth/logout?everywhere=true` : `${this.baseUrl}/auth/logout`;\n const response = await this.http({\n url,\n method: 'POST',\n headers: await this.authHeaders(),\n credentials: this.useCredentials ? 'include' : undefined,\n });\n if (!response.ok) {\n throw new Error(`logout failed with status ${response.status}`);\n }\n }\n\n async forgotPassword(request: ForgotPasswordRequest): Promise<void> {\n const response = await this.http({\n url: `${this.baseUrl}/auth/forgot-password`,\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(request),\n });\n // Backend returns 200 unconditionally (no enumeration). Anything else is\n // a real failure.\n if (!response.ok) {\n throw new Error(`forgot-password failed with status ${response.status}`);\n }\n }\n\n async resetPassword(request: ResetPasswordRequest): Promise<void> {\n const response = await this.http({\n url: `${this.baseUrl}/auth/reset-password`,\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(request),\n });\n if (!response.ok) {\n throw new Error(`reset-password failed with status ${response.status}`);\n }\n }\n\n async listSessions(): Promise<AuthSessionInfo[]> {\n const response = await this.http({\n url: `${this.baseUrl}/me/sessions`,\n method: 'GET',\n headers: await this.authHeaders(),\n credentials: this.useCredentials ? 'include' : undefined,\n });\n if (!response.ok) {\n throw new Error(`listSessions failed with status ${response.status}`);\n }\n return Array.isArray(response.data) ? (response.data as AuthSessionInfo[]) : [];\n }\n\n async revokeSession(sessionId: string): Promise<void> {\n const response = await this.http({\n url: `${this.baseUrl}/me/sessions/${encodeURIComponent(sessionId)}/revoke`,\n method: 'POST',\n headers: await this.authHeaders(),\n credentials: this.useCredentials ? 'include' : undefined,\n });\n if (!response.ok) {\n throw new Error(`revokeSession failed with status ${response.status}`);\n }\n }\n\n private async postLogin(path: string, body: object): Promise<RawAuthLoginResponse> {\n const response = await this.http({\n url: `${this.baseUrl}${path}`,\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n credentials: this.useCredentials ? 'include' : undefined,\n });\n if (!response.ok) {\n throw new Error(`login failed with status ${response.status}`);\n }\n return (response.data ?? {}) as RawAuthLoginResponse;\n }\n\n private async authHeaders(): Promise<Record<string, string>> {\n const headers: Record<string, string> = { 'content-type': 'application/json' };\n if (this.getAccessToken === undefined) {\n return headers;\n }\n const token = await this.getAccessToken();\n if (token !== null && token !== '') {\n headers.authorization = `Bearer ${token}`;\n }\n return headers;\n }\n}\n","import { KeycloakRoles } from '../types/KeycloakRoles';\n\nimport type { KeycloakUserInfo } from '../types/KeycloakUserInfo';\nimport type { NormalizedUser } from '../types/NormalizedUser';\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value !== '';\n}\n\nfunction firstNonEmptyString(...values: unknown[]): string | undefined {\n for (const v of values) {\n if (isNonEmptyString(v)) {\n return v;\n }\n }\n return undefined;\n}\n\nfunction collectRoles(u: KeycloakUserInfo): KeycloakRoles[] {\n const roles: KeycloakRoles[] = [];\n const realmAccessRoles = u.realm_access?.roles;\n if (Array.isArray(realmAccessRoles)) {\n roles.push(...realmAccessRoles);\n }\n\n const resourceAccess = u.resource_access;\n if (resourceAccess !== undefined) {\n for (const v of Object.values(resourceAccess)) {\n const resourceRoles = v.roles;\n if (Array.isArray(resourceRoles)) {\n roles.push(...resourceRoles);\n }\n }\n }\n return roles;\n}\n\n/**\n * Convert a Keycloak `/userinfo` payload into a flat, app-friendly user object.\n *\n * - Aggregates `realm_access.roles` and every `resource_access[*].roles` into a\n * deduplicated `roles` array.\n * - Picks a sensible `displayName` / `username` from whatever claims are\n * present (Keycloak realms vary in which fields they emit).\n * - Returns a safe default (`{ roles: [] }`) when input is undefined.\n */\nexport function normalizeKeycloakUser(u?: KeycloakUserInfo): NormalizedUser {\n if (!u) {\n return { roles: [] };\n }\n const roles = collectRoles(u);\n const fullName = [u.given_name, u.family_name].filter(isNonEmptyString).join(' ');\n const username = firstNonEmptyString(u.preferred_username, u.name, u.email, u.sub);\n const displayName = firstNonEmptyString(u.name, fullName, u.preferred_username, u.email, u.sub);\n\n return {\n id: u.sub,\n username,\n email: u.email,\n displayName,\n firstName: u.given_name,\n lastName: u.family_name,\n emailVerified: Boolean(u.email_verified),\n roles: Array.from(new Set(roles)),\n raw: u,\n };\n}\n","/**\n * Inputs for the OAuth `authorization_code` token request.\n */\nexport interface AuthorizationCodeBodyInput {\n clientId: string;\n code: string;\n redirectUri: string;\n codeVerifier: string;\n}\n\n/**\n * Inputs for the OAuth `refresh_token` token request.\n */\nexport interface RefreshTokenBodyInput {\n clientId: string;\n refreshToken: string;\n}\n\n/**\n * Build the `application/x-www-form-urlencoded` body for the\n * `grant_type=authorization_code` token endpoint call (PKCE flow).\n */\nexport function buildAuthorizationCodeBody(input: AuthorizationCodeBodyInput): string {\n return new URLSearchParams({\n client_id: input.clientId,\n grant_type: 'authorization_code',\n code: input.code,\n redirect_uri: input.redirectUri,\n code_verifier: input.codeVerifier,\n }).toString();\n}\n\n/**\n * Build the `application/x-www-form-urlencoded` body for the\n * `grant_type=refresh_token` token endpoint call.\n */\nexport function buildRefreshTokenBody(input: RefreshTokenBodyInput): string {\n return new URLSearchParams({\n client_id: input.clientId,\n grant_type: 'refresh_token',\n refresh_token: input.refreshToken,\n }).toString();\n}\n","/**\n * Loose shape of an `expo-auth-session` (or browser-side) authorization response.\n *\n * Kept as a structural type rather than importing from `expo-auth-session` so\n * the package stays usable in plain web apps and Node tests.\n */\nexport interface AuthorizationResponseLike {\n type?: string;\n params?: { code?: string; error?: string };\n}\n\n/**\n * Pull the authorization `code` out of a successful redirect response.\n *\n * Returns `undefined` when the response is missing, indicates an error type,\n * or doesn't carry a non-empty `code` query param.\n */\nexport function extractAuthCode(\n response: AuthorizationResponseLike | null | undefined,\n): string | undefined {\n if (!response) {\n return undefined;\n }\n if (response.type !== 'success') {\n return undefined;\n }\n const code = response.params?.code;\n if (typeof code !== 'string' || code === '') {\n return undefined;\n }\n return code;\n}\n","/**\n * Decode the payload segment of a compact JWT.\n *\n * No signature verification — that responsibility belongs to the backend that\n * accepts the token. This helper is for UI concerns: reading `exp` to schedule\n * refresh, reading custom claims for routing decisions, etc.\n *\n * Returns `null` when the input is malformed, base64url-decodes incorrectly, or\n * does not produce a JSON object payload.\n *\n * Runtime requirement: a global `atob` function. Available in browsers, in\n * Node ≥ 16, and in modern bundler test envs (jsdom, node-jest).\n */\nexport function decodeJwt<T = Record<string, unknown>>(token: string | null | undefined): T | null {\n if (typeof token !== 'string' || token === '') {\n return null;\n }\n const parts = token.split('.');\n if (parts.length !== 3) {\n return null;\n }\n const payload = parts[1];\n if (payload === undefined || payload === '') {\n return null;\n }\n try {\n const json = base64UrlDecode(payload);\n const parsed: unknown = JSON.parse(json);\n if (typeof parsed !== 'object' || parsed === null) {\n return null;\n }\n return parsed as T;\n } catch {\n return null;\n }\n}\n\nconst BASE64_PAD_LENGTH = 4;\n\nfunction base64UrlDecode(input: string): string {\n const normalized = input.replace(/-/g, '+').replace(/_/g, '/');\n const padLength =\n (BASE64_PAD_LENGTH - (normalized.length % BASE64_PAD_LENGTH)) % BASE64_PAD_LENGTH;\n const padded = normalized + '='.repeat(padLength);\n if (typeof globalThis.atob !== 'function') {\n throw new Error('decodeJwt: globalThis.atob is unavailable in this runtime');\n }\n return decodeUtf8(globalThis.atob(padded));\n}\n\nfunction decodeUtf8(binary: string): string {\n // `atob` returns a \"binary string\" where each char code is one byte. Convert\n // to UTF-8 properly using TextDecoder when available (browsers + Node).\n if (typeof TextDecoder === 'undefined') {\n return binary;\n }\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return new TextDecoder('utf-8').decode(bytes);\n}\n"]}
@@ -0,0 +1,62 @@
1
+ import { UseMutationOptions, UseMutationResult, UseQueryOptions, UseQueryResult } from '@tanstack/react-query';
2
+ import { F as ForgotPasswordRequest, a as AuthApiClient, t as ResetPasswordRequest, k as AuthSessionInfo, c as AuthClient } from './AuthClient-Dim7HPRz.mjs';
3
+
4
+ interface UseForgotPasswordOptions extends Omit<UseMutationOptions<void, Error, ForgotPasswordRequest>, 'mutationFn'> {
5
+ api: AuthApiClient;
6
+ }
7
+ /**
8
+ * React Query mutation that POSTs to `/auth/forgot-password`.
9
+ *
10
+ * The backend returns 200 unconditionally (no email enumeration). UI should
11
+ * always show "if that email exists, we sent a reset link" regardless of
12
+ * whether `onSuccess` or `onError` fires.
13
+ */
14
+ declare function useForgotPassword(options: UseForgotPasswordOptions): UseMutationResult<void, Error, ForgotPasswordRequest>;
15
+
16
+ interface UseResetPasswordOptions extends Omit<UseMutationOptions<void, Error, ResetPasswordRequest>, 'mutationFn'> {
17
+ api: AuthApiClient;
18
+ }
19
+ /**
20
+ * React Query mutation that POSTs to `/auth/reset-password`.
21
+ *
22
+ * On success, the user can log in with the new password. The backend
23
+ * also revokes existing sessions, so any other devices stay logged in until
24
+ * their access token expires (mobile) or the cookie is cleared (web).
25
+ */
26
+ declare function useResetPassword(options: UseResetPasswordOptions): UseMutationResult<void, Error, ResetPasswordRequest>;
27
+
28
+ declare const SESSIONS_QUERY_KEY: readonly ["auth", "sessions"];
29
+ interface UseSessionsOptions extends Omit<UseQueryOptions<AuthSessionInfo[], Error, AuthSessionInfo[]>, 'queryKey' | 'queryFn'> {
30
+ api: AuthApiClient;
31
+ }
32
+ /**
33
+ * React Query wrapper around `GET /me/sessions`. Returns the active sessions
34
+ * for the current user.
35
+ *
36
+ * Use the exported `SESSIONS_QUERY_KEY` for invalidation from other hooks
37
+ * (e.g., after `useRevokeSession` or `useLogoutEverywhere`).
38
+ */
39
+ declare function useSessions(options: UseSessionsOptions): UseQueryResult<AuthSessionInfo[], Error>;
40
+
41
+ interface UseRevokeSessionOptions extends Omit<UseMutationOptions<void, Error, string>, 'mutationFn'> {
42
+ api: AuthApiClient;
43
+ }
44
+ /**
45
+ * React Query mutation that POSTs to `/me/sessions/{id}/revoke`. Pass the
46
+ * session id as the variable. Automatically invalidates the sessions query.
47
+ */
48
+ declare function useRevokeSession(options: UseRevokeSessionOptions): UseMutationResult<void, Error, string>;
49
+
50
+ interface UseLogoutEverywhereOptions extends Omit<UseMutationOptions<void, Error, void>, 'mutationFn'> {
51
+ client: AuthClient;
52
+ }
53
+ /**
54
+ * React Query mutation calling `AuthClient.logout({ everywhere: true })`.
55
+ *
56
+ * Routes through `AuthClient` (not `AuthApiClient` directly) so storage and
57
+ * inactivity tracker are cleared as well. Invalidates the sessions query so
58
+ * any open sessions screen reflects the empty list.
59
+ */
60
+ declare function useLogoutEverywhere(options: UseLogoutEverywhereOptions): UseMutationResult<void, Error, void>;
61
+
62
+ export { SESSIONS_QUERY_KEY, type UseForgotPasswordOptions, type UseLogoutEverywhereOptions, type UseResetPasswordOptions, type UseRevokeSessionOptions, type UseSessionsOptions, useForgotPassword, useLogoutEverywhere, useResetPassword, useRevokeSession, useSessions };
@@ -0,0 +1,62 @@
1
+ import { UseMutationOptions, UseMutationResult, UseQueryOptions, UseQueryResult } from '@tanstack/react-query';
2
+ import { F as ForgotPasswordRequest, a as AuthApiClient, t as ResetPasswordRequest, k as AuthSessionInfo, c as AuthClient } from './AuthClient-Dim7HPRz.js';
3
+
4
+ interface UseForgotPasswordOptions extends Omit<UseMutationOptions<void, Error, ForgotPasswordRequest>, 'mutationFn'> {
5
+ api: AuthApiClient;
6
+ }
7
+ /**
8
+ * React Query mutation that POSTs to `/auth/forgot-password`.
9
+ *
10
+ * The backend returns 200 unconditionally (no email enumeration). UI should
11
+ * always show "if that email exists, we sent a reset link" regardless of
12
+ * whether `onSuccess` or `onError` fires.
13
+ */
14
+ declare function useForgotPassword(options: UseForgotPasswordOptions): UseMutationResult<void, Error, ForgotPasswordRequest>;
15
+
16
+ interface UseResetPasswordOptions extends Omit<UseMutationOptions<void, Error, ResetPasswordRequest>, 'mutationFn'> {
17
+ api: AuthApiClient;
18
+ }
19
+ /**
20
+ * React Query mutation that POSTs to `/auth/reset-password`.
21
+ *
22
+ * On success, the user can log in with the new password. The backend
23
+ * also revokes existing sessions, so any other devices stay logged in until
24
+ * their access token expires (mobile) or the cookie is cleared (web).
25
+ */
26
+ declare function useResetPassword(options: UseResetPasswordOptions): UseMutationResult<void, Error, ResetPasswordRequest>;
27
+
28
+ declare const SESSIONS_QUERY_KEY: readonly ["auth", "sessions"];
29
+ interface UseSessionsOptions extends Omit<UseQueryOptions<AuthSessionInfo[], Error, AuthSessionInfo[]>, 'queryKey' | 'queryFn'> {
30
+ api: AuthApiClient;
31
+ }
32
+ /**
33
+ * React Query wrapper around `GET /me/sessions`. Returns the active sessions
34
+ * for the current user.
35
+ *
36
+ * Use the exported `SESSIONS_QUERY_KEY` for invalidation from other hooks
37
+ * (e.g., after `useRevokeSession` or `useLogoutEverywhere`).
38
+ */
39
+ declare function useSessions(options: UseSessionsOptions): UseQueryResult<AuthSessionInfo[], Error>;
40
+
41
+ interface UseRevokeSessionOptions extends Omit<UseMutationOptions<void, Error, string>, 'mutationFn'> {
42
+ api: AuthApiClient;
43
+ }
44
+ /**
45
+ * React Query mutation that POSTs to `/me/sessions/{id}/revoke`. Pass the
46
+ * session id as the variable. Automatically invalidates the sessions query.
47
+ */
48
+ declare function useRevokeSession(options: UseRevokeSessionOptions): UseMutationResult<void, Error, string>;
49
+
50
+ interface UseLogoutEverywhereOptions extends Omit<UseMutationOptions<void, Error, void>, 'mutationFn'> {
51
+ client: AuthClient;
52
+ }
53
+ /**
54
+ * React Query mutation calling `AuthClient.logout({ everywhere: true })`.
55
+ *
56
+ * Routes through `AuthClient` (not `AuthApiClient` directly) so storage and
57
+ * inactivity tracker are cleared as well. Invalidates the sessions query so
58
+ * any open sessions screen reflects the empty list.
59
+ */
60
+ declare function useLogoutEverywhere(options: UseLogoutEverywhereOptions): UseMutationResult<void, Error, void>;
61
+
62
+ export { SESSIONS_QUERY_KEY, type UseForgotPasswordOptions, type UseLogoutEverywhereOptions, type UseResetPasswordOptions, type UseRevokeSessionOptions, type UseSessionsOptions, useForgotPassword, useLogoutEverywhere, useResetPassword, useRevokeSession, useSessions };
package/dist/react.js ADDED
@@ -0,0 +1,65 @@
1
+ 'use strict';
2
+
3
+ var reactQuery = require('@tanstack/react-query');
4
+
5
+ // src/hooks/useForgotPassword.ts
6
+ function useForgotPassword(options) {
7
+ const { api, ...rest } = options;
8
+ return reactQuery.useMutation({
9
+ mutationFn: (request) => api.forgotPassword(request),
10
+ ...rest
11
+ });
12
+ }
13
+ function useResetPassword(options) {
14
+ const { api, ...rest } = options;
15
+ return reactQuery.useMutation({
16
+ mutationFn: (request) => api.resetPassword(request),
17
+ ...rest
18
+ });
19
+ }
20
+ var SESSIONS_QUERY_KEY = ["auth", "sessions"];
21
+ function useSessions(options) {
22
+ const { api, ...rest } = options;
23
+ return reactQuery.useQuery({
24
+ queryKey: SESSIONS_QUERY_KEY,
25
+ queryFn: () => api.listSessions(),
26
+ ...rest
27
+ });
28
+ }
29
+ function useRevokeSession(options) {
30
+ const { api, ...rest } = options;
31
+ const queryClient = reactQuery.useQueryClient();
32
+ return reactQuery.useMutation({
33
+ mutationFn: (sessionId) => api.revokeSession(sessionId),
34
+ ...rest,
35
+ onSuccess: (data, variables, onMutateResult, context) => {
36
+ void queryClient.invalidateQueries({ queryKey: SESSIONS_QUERY_KEY });
37
+ if (rest.onSuccess !== void 0) {
38
+ rest.onSuccess(data, variables, onMutateResult, context);
39
+ }
40
+ }
41
+ });
42
+ }
43
+ function useLogoutEverywhere(options) {
44
+ const { client, ...rest } = options;
45
+ const queryClient = reactQuery.useQueryClient();
46
+ return reactQuery.useMutation({
47
+ mutationFn: () => client.logout({ everywhere: true }),
48
+ ...rest,
49
+ onSuccess: (data, variables, onMutateResult, context) => {
50
+ void queryClient.invalidateQueries({ queryKey: SESSIONS_QUERY_KEY });
51
+ if (rest.onSuccess !== void 0) {
52
+ rest.onSuccess(data, variables, onMutateResult, context);
53
+ }
54
+ }
55
+ });
56
+ }
57
+
58
+ exports.SESSIONS_QUERY_KEY = SESSIONS_QUERY_KEY;
59
+ exports.useForgotPassword = useForgotPassword;
60
+ exports.useLogoutEverywhere = useLogoutEverywhere;
61
+ exports.useResetPassword = useResetPassword;
62
+ exports.useRevokeSession = useRevokeSession;
63
+ exports.useSessions = useSessions;
64
+ //# sourceMappingURL=react.js.map
65
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hooks/useForgotPassword.ts","../src/hooks/useResetPassword.ts","../src/hooks/useSessions.ts","../src/hooks/useRevokeSession.ts","../src/hooks/useLogoutEverywhere.ts"],"names":["useMutation","useQuery","useQueryClient"],"mappings":";;;;;AAgBO,SAAS,kBACd,OAAA,EACuD;AACvD,EAAA,MAAM,EAAE,GAAA,EAAK,GAAG,IAAA,EAAK,GAAI,OAAA;AACzB,EAAA,OAAOA,sBAAA,CAAgD;AAAA,IACrD,UAAA,EAAY,CAAC,OAAA,KAAY,GAAA,CAAI,eAAe,OAAO,CAAA;AAAA,IACnD,GAAG;AAAA,GACJ,CAAA;AACH;ACRO,SAAS,iBACd,OAAA,EACsD;AACtD,EAAA,MAAM,EAAE,GAAA,EAAK,GAAG,IAAA,EAAK,GAAI,OAAA;AACzB,EAAA,OAAOA,sBAAAA,CAA+C;AAAA,IACpD,UAAA,EAAY,CAAC,OAAA,KAAY,GAAA,CAAI,cAAc,OAAO,CAAA;AAAA,IAClD,GAAG;AAAA,GACJ,CAAA;AACH;ACpBO,IAAM,kBAAA,GAAqB,CAAC,MAAA,EAAQ,UAAU;AAc9C,SAAS,YAAY,OAAA,EAAuE;AACjG,EAAA,MAAM,EAAE,GAAA,EAAK,GAAG,IAAA,EAAK,GAAI,OAAA;AACzB,EAAA,OAAOC,mBAAA,CAAsD;AAAA,IAC3D,QAAA,EAAU,kBAAA;AAAA,IACV,OAAA,EAAS,MAAM,GAAA,CAAI,YAAA,EAAa;AAAA,IAChC,GAAG;AAAA,GACJ,CAAA;AACH;ACLO,SAAS,iBACd,OAAA,EACwC;AACxC,EAAA,MAAM,EAAE,GAAA,EAAK,GAAG,IAAA,EAAK,GAAI,OAAA;AACzB,EAAA,MAAM,cAAcC,yBAAA,EAAe;AACnC,EAAA,OAAOF,sBAAAA,CAAiC;AAAA,IACtC,UAAA,EAAY,CAAC,SAAA,KAAc,GAAA,CAAI,cAAc,SAAS,CAAA;AAAA,IACtD,GAAG,IAAA;AAAA,IACH,SAAA,EAAW,CAAC,IAAA,EAAM,SAAA,EAAW,gBAAgB,OAAA,KAAY;AACvD,MAAA,KAAK,WAAA,CAAY,iBAAA,CAAkB,EAAE,QAAA,EAAU,oBAAoB,CAAA;AACnE,MAAA,IAAI,IAAA,CAAK,cAAc,MAAA,EAAW;AAChC,QAAA,IAAA,CAAK,SAAA,CAAU,IAAA,EAAM,SAAA,EAAW,cAAA,EAAgB,OAAO,CAAA;AAAA,MACzD;AAAA,IACF;AAAA,GACD,CAAA;AACH;ACZO,SAAS,oBACd,OAAA,EACsC;AACtC,EAAA,MAAM,EAAE,MAAA,EAAQ,GAAG,IAAA,EAAK,GAAI,OAAA;AAC5B,EAAA,MAAM,cAAcE,yBAAAA,EAAe;AACnC,EAAA,OAAOF,sBAAAA,CAA+B;AAAA,IACpC,YAAY,MAAM,MAAA,CAAO,OAAO,EAAE,UAAA,EAAY,MAAM,CAAA;AAAA,IACpD,GAAG,IAAA;AAAA,IACH,SAAA,EAAW,CAAC,IAAA,EAAM,SAAA,EAAW,gBAAgB,OAAA,KAAY;AACvD,MAAA,KAAK,WAAA,CAAY,iBAAA,CAAkB,EAAE,QAAA,EAAU,oBAAoB,CAAA;AACnE,MAAA,IAAI,IAAA,CAAK,cAAc,MAAA,EAAW;AAChC,QAAA,IAAA,CAAK,SAAA,CAAU,IAAA,EAAM,SAAA,EAAW,cAAA,EAAgB,OAAO,CAAA;AAAA,MACzD;AAAA,IACF;AAAA,GACD,CAAA;AACH","file":"react.js","sourcesContent":["import { useMutation, type UseMutationOptions, type UseMutationResult } from '@tanstack/react-query';\n\nimport type { AuthApiClient, ForgotPasswordRequest } from '../api/AuthApiClient';\n\nexport interface UseForgotPasswordOptions\n extends Omit<UseMutationOptions<void, Error, ForgotPasswordRequest>, 'mutationFn'> {\n api: AuthApiClient;\n}\n\n/**\n * React Query mutation that POSTs to `/auth/forgot-password`.\n *\n * The backend returns 200 unconditionally (no email enumeration). UI should\n * always show \"if that email exists, we sent a reset link\" regardless of\n * whether `onSuccess` or `onError` fires.\n */\nexport function useForgotPassword(\n options: UseForgotPasswordOptions,\n): UseMutationResult<void, Error, ForgotPasswordRequest> {\n const { api, ...rest } = options;\n return useMutation<void, Error, ForgotPasswordRequest>({\n mutationFn: (request) => api.forgotPassword(request),\n ...rest,\n });\n}\n","import { useMutation, type UseMutationOptions, type UseMutationResult } from '@tanstack/react-query';\n\nimport type { AuthApiClient, ResetPasswordRequest } from '../api/AuthApiClient';\n\nexport interface UseResetPasswordOptions\n extends Omit<UseMutationOptions<void, Error, ResetPasswordRequest>, 'mutationFn'> {\n api: AuthApiClient;\n}\n\n/**\n * React Query mutation that POSTs to `/auth/reset-password`.\n *\n * On success, the user can log in with the new password. The backend\n * also revokes existing sessions, so any other devices stay logged in until\n * their access token expires (mobile) or the cookie is cleared (web).\n */\nexport function useResetPassword(\n options: UseResetPasswordOptions,\n): UseMutationResult<void, Error, ResetPasswordRequest> {\n const { api, ...rest } = options;\n return useMutation<void, Error, ResetPasswordRequest>({\n mutationFn: (request) => api.resetPassword(request),\n ...rest,\n });\n}\n","import { useQuery, type UseQueryOptions, type UseQueryResult } from '@tanstack/react-query';\n\nimport type { AuthApiClient, AuthSessionInfo } from '../api/AuthApiClient';\n\nexport const SESSIONS_QUERY_KEY = ['auth', 'sessions'] as const;\n\nexport interface UseSessionsOptions\n extends Omit<UseQueryOptions<AuthSessionInfo[], Error, AuthSessionInfo[]>, 'queryKey' | 'queryFn'> {\n api: AuthApiClient;\n}\n\n/**\n * React Query wrapper around `GET /me/sessions`. Returns the active sessions\n * for the current user.\n *\n * Use the exported `SESSIONS_QUERY_KEY` for invalidation from other hooks\n * (e.g., after `useRevokeSession` or `useLogoutEverywhere`).\n */\nexport function useSessions(options: UseSessionsOptions): UseQueryResult<AuthSessionInfo[], Error> {\n const { api, ...rest } = options;\n return useQuery<AuthSessionInfo[], Error, AuthSessionInfo[]>({\n queryKey: SESSIONS_QUERY_KEY,\n queryFn: () => api.listSessions(),\n ...rest,\n });\n}\n","import {\n useMutation,\n useQueryClient,\n type UseMutationOptions,\n type UseMutationResult,\n} from '@tanstack/react-query';\n\nimport { SESSIONS_QUERY_KEY } from './useSessions';\n\nimport type { AuthApiClient } from '../api/AuthApiClient';\n\nexport interface UseRevokeSessionOptions\n extends Omit<UseMutationOptions<void, Error, string>, 'mutationFn'> {\n api: AuthApiClient;\n}\n\n/**\n * React Query mutation that POSTs to `/me/sessions/{id}/revoke`. Pass the\n * session id as the variable. Automatically invalidates the sessions query.\n */\nexport function useRevokeSession(\n options: UseRevokeSessionOptions,\n): UseMutationResult<void, Error, string> {\n const { api, ...rest } = options;\n const queryClient = useQueryClient();\n return useMutation<void, Error, string>({\n mutationFn: (sessionId) => api.revokeSession(sessionId),\n ...rest,\n onSuccess: (data, variables, onMutateResult, context) => {\n void queryClient.invalidateQueries({ queryKey: SESSIONS_QUERY_KEY });\n if (rest.onSuccess !== undefined) {\n rest.onSuccess(data, variables, onMutateResult, context);\n }\n },\n });\n}\n","import {\n useMutation,\n useQueryClient,\n type UseMutationOptions,\n type UseMutationResult,\n} from '@tanstack/react-query';\n\nimport { SESSIONS_QUERY_KEY } from './useSessions';\n\nimport type { AuthClient } from '../AuthClient';\n\nexport interface UseLogoutEverywhereOptions\n extends Omit<UseMutationOptions<void, Error, void>, 'mutationFn'> {\n client: AuthClient;\n}\n\n/**\n * React Query mutation calling `AuthClient.logout({ everywhere: true })`.\n *\n * Routes through `AuthClient` (not `AuthApiClient` directly) so storage and\n * inactivity tracker are cleared as well. Invalidates the sessions query so\n * any open sessions screen reflects the empty list.\n */\nexport function useLogoutEverywhere(\n options: UseLogoutEverywhereOptions,\n): UseMutationResult<void, Error, void> {\n const { client, ...rest } = options;\n const queryClient = useQueryClient();\n return useMutation<void, Error, void>({\n mutationFn: () => client.logout({ everywhere: true }),\n ...rest,\n onSuccess: (data, variables, onMutateResult, context) => {\n void queryClient.invalidateQueries({ queryKey: SESSIONS_QUERY_KEY });\n if (rest.onSuccess !== undefined) {\n rest.onSuccess(data, variables, onMutateResult, context);\n }\n },\n });\n}\n"]}
package/dist/react.mjs ADDED
@@ -0,0 +1,58 @@
1
+ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
2
+
3
+ // src/hooks/useForgotPassword.ts
4
+ function useForgotPassword(options) {
5
+ const { api, ...rest } = options;
6
+ return useMutation({
7
+ mutationFn: (request) => api.forgotPassword(request),
8
+ ...rest
9
+ });
10
+ }
11
+ function useResetPassword(options) {
12
+ const { api, ...rest } = options;
13
+ return useMutation({
14
+ mutationFn: (request) => api.resetPassword(request),
15
+ ...rest
16
+ });
17
+ }
18
+ var SESSIONS_QUERY_KEY = ["auth", "sessions"];
19
+ function useSessions(options) {
20
+ const { api, ...rest } = options;
21
+ return useQuery({
22
+ queryKey: SESSIONS_QUERY_KEY,
23
+ queryFn: () => api.listSessions(),
24
+ ...rest
25
+ });
26
+ }
27
+ function useRevokeSession(options) {
28
+ const { api, ...rest } = options;
29
+ const queryClient = useQueryClient();
30
+ return useMutation({
31
+ mutationFn: (sessionId) => api.revokeSession(sessionId),
32
+ ...rest,
33
+ onSuccess: (data, variables, onMutateResult, context) => {
34
+ void queryClient.invalidateQueries({ queryKey: SESSIONS_QUERY_KEY });
35
+ if (rest.onSuccess !== void 0) {
36
+ rest.onSuccess(data, variables, onMutateResult, context);
37
+ }
38
+ }
39
+ });
40
+ }
41
+ function useLogoutEverywhere(options) {
42
+ const { client, ...rest } = options;
43
+ const queryClient = useQueryClient();
44
+ return useMutation({
45
+ mutationFn: () => client.logout({ everywhere: true }),
46
+ ...rest,
47
+ onSuccess: (data, variables, onMutateResult, context) => {
48
+ void queryClient.invalidateQueries({ queryKey: SESSIONS_QUERY_KEY });
49
+ if (rest.onSuccess !== void 0) {
50
+ rest.onSuccess(data, variables, onMutateResult, context);
51
+ }
52
+ }
53
+ });
54
+ }
55
+
56
+ export { SESSIONS_QUERY_KEY, useForgotPassword, useLogoutEverywhere, useResetPassword, useRevokeSession, useSessions };
57
+ //# sourceMappingURL=react.mjs.map
58
+ //# sourceMappingURL=react.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hooks/useForgotPassword.ts","../src/hooks/useResetPassword.ts","../src/hooks/useSessions.ts","../src/hooks/useRevokeSession.ts","../src/hooks/useLogoutEverywhere.ts"],"names":["useMutation","useQueryClient"],"mappings":";;;AAgBO,SAAS,kBACd,OAAA,EACuD;AACvD,EAAA,MAAM,EAAE,GAAA,EAAK,GAAG,IAAA,EAAK,GAAI,OAAA;AACzB,EAAA,OAAO,WAAA,CAAgD;AAAA,IACrD,UAAA,EAAY,CAAC,OAAA,KAAY,GAAA,CAAI,eAAe,OAAO,CAAA;AAAA,IACnD,GAAG;AAAA,GACJ,CAAA;AACH;ACRO,SAAS,iBACd,OAAA,EACsD;AACtD,EAAA,MAAM,EAAE,GAAA,EAAK,GAAG,IAAA,EAAK,GAAI,OAAA;AACzB,EAAA,OAAOA,WAAAA,CAA+C;AAAA,IACpD,UAAA,EAAY,CAAC,OAAA,KAAY,GAAA,CAAI,cAAc,OAAO,CAAA;AAAA,IAClD,GAAG;AAAA,GACJ,CAAA;AACH;ACpBO,IAAM,kBAAA,GAAqB,CAAC,MAAA,EAAQ,UAAU;AAc9C,SAAS,YAAY,OAAA,EAAuE;AACjG,EAAA,MAAM,EAAE,GAAA,EAAK,GAAG,IAAA,EAAK,GAAI,OAAA;AACzB,EAAA,OAAO,QAAA,CAAsD;AAAA,IAC3D,QAAA,EAAU,kBAAA;AAAA,IACV,OAAA,EAAS,MAAM,GAAA,CAAI,YAAA,EAAa;AAAA,IAChC,GAAG;AAAA,GACJ,CAAA;AACH;ACLO,SAAS,iBACd,OAAA,EACwC;AACxC,EAAA,MAAM,EAAE,GAAA,EAAK,GAAG,IAAA,EAAK,GAAI,OAAA;AACzB,EAAA,MAAM,cAAc,cAAA,EAAe;AACnC,EAAA,OAAOA,WAAAA,CAAiC;AAAA,IACtC,UAAA,EAAY,CAAC,SAAA,KAAc,GAAA,CAAI,cAAc,SAAS,CAAA;AAAA,IACtD,GAAG,IAAA;AAAA,IACH,SAAA,EAAW,CAAC,IAAA,EAAM,SAAA,EAAW,gBAAgB,OAAA,KAAY;AACvD,MAAA,KAAK,WAAA,CAAY,iBAAA,CAAkB,EAAE,QAAA,EAAU,oBAAoB,CAAA;AACnE,MAAA,IAAI,IAAA,CAAK,cAAc,MAAA,EAAW;AAChC,QAAA,IAAA,CAAK,SAAA,CAAU,IAAA,EAAM,SAAA,EAAW,cAAA,EAAgB,OAAO,CAAA;AAAA,MACzD;AAAA,IACF;AAAA,GACD,CAAA;AACH;ACZO,SAAS,oBACd,OAAA,EACsC;AACtC,EAAA,MAAM,EAAE,MAAA,EAAQ,GAAG,IAAA,EAAK,GAAI,OAAA;AAC5B,EAAA,MAAM,cAAcC,cAAAA,EAAe;AACnC,EAAA,OAAOD,WAAAA,CAA+B;AAAA,IACpC,YAAY,MAAM,MAAA,CAAO,OAAO,EAAE,UAAA,EAAY,MAAM,CAAA;AAAA,IACpD,GAAG,IAAA;AAAA,IACH,SAAA,EAAW,CAAC,IAAA,EAAM,SAAA,EAAW,gBAAgB,OAAA,KAAY;AACvD,MAAA,KAAK,WAAA,CAAY,iBAAA,CAAkB,EAAE,QAAA,EAAU,oBAAoB,CAAA;AACnE,MAAA,IAAI,IAAA,CAAK,cAAc,MAAA,EAAW;AAChC,QAAA,IAAA,CAAK,SAAA,CAAU,IAAA,EAAM,SAAA,EAAW,cAAA,EAAgB,OAAO,CAAA;AAAA,MACzD;AAAA,IACF;AAAA,GACD,CAAA;AACH","file":"react.mjs","sourcesContent":["import { useMutation, type UseMutationOptions, type UseMutationResult } from '@tanstack/react-query';\n\nimport type { AuthApiClient, ForgotPasswordRequest } from '../api/AuthApiClient';\n\nexport interface UseForgotPasswordOptions\n extends Omit<UseMutationOptions<void, Error, ForgotPasswordRequest>, 'mutationFn'> {\n api: AuthApiClient;\n}\n\n/**\n * React Query mutation that POSTs to `/auth/forgot-password`.\n *\n * The backend returns 200 unconditionally (no email enumeration). UI should\n * always show \"if that email exists, we sent a reset link\" regardless of\n * whether `onSuccess` or `onError` fires.\n */\nexport function useForgotPassword(\n options: UseForgotPasswordOptions,\n): UseMutationResult<void, Error, ForgotPasswordRequest> {\n const { api, ...rest } = options;\n return useMutation<void, Error, ForgotPasswordRequest>({\n mutationFn: (request) => api.forgotPassword(request),\n ...rest,\n });\n}\n","import { useMutation, type UseMutationOptions, type UseMutationResult } from '@tanstack/react-query';\n\nimport type { AuthApiClient, ResetPasswordRequest } from '../api/AuthApiClient';\n\nexport interface UseResetPasswordOptions\n extends Omit<UseMutationOptions<void, Error, ResetPasswordRequest>, 'mutationFn'> {\n api: AuthApiClient;\n}\n\n/**\n * React Query mutation that POSTs to `/auth/reset-password`.\n *\n * On success, the user can log in with the new password. The backend\n * also revokes existing sessions, so any other devices stay logged in until\n * their access token expires (mobile) or the cookie is cleared (web).\n */\nexport function useResetPassword(\n options: UseResetPasswordOptions,\n): UseMutationResult<void, Error, ResetPasswordRequest> {\n const { api, ...rest } = options;\n return useMutation<void, Error, ResetPasswordRequest>({\n mutationFn: (request) => api.resetPassword(request),\n ...rest,\n });\n}\n","import { useQuery, type UseQueryOptions, type UseQueryResult } from '@tanstack/react-query';\n\nimport type { AuthApiClient, AuthSessionInfo } from '../api/AuthApiClient';\n\nexport const SESSIONS_QUERY_KEY = ['auth', 'sessions'] as const;\n\nexport interface UseSessionsOptions\n extends Omit<UseQueryOptions<AuthSessionInfo[], Error, AuthSessionInfo[]>, 'queryKey' | 'queryFn'> {\n api: AuthApiClient;\n}\n\n/**\n * React Query wrapper around `GET /me/sessions`. Returns the active sessions\n * for the current user.\n *\n * Use the exported `SESSIONS_QUERY_KEY` for invalidation from other hooks\n * (e.g., after `useRevokeSession` or `useLogoutEverywhere`).\n */\nexport function useSessions(options: UseSessionsOptions): UseQueryResult<AuthSessionInfo[], Error> {\n const { api, ...rest } = options;\n return useQuery<AuthSessionInfo[], Error, AuthSessionInfo[]>({\n queryKey: SESSIONS_QUERY_KEY,\n queryFn: () => api.listSessions(),\n ...rest,\n });\n}\n","import {\n useMutation,\n useQueryClient,\n type UseMutationOptions,\n type UseMutationResult,\n} from '@tanstack/react-query';\n\nimport { SESSIONS_QUERY_KEY } from './useSessions';\n\nimport type { AuthApiClient } from '../api/AuthApiClient';\n\nexport interface UseRevokeSessionOptions\n extends Omit<UseMutationOptions<void, Error, string>, 'mutationFn'> {\n api: AuthApiClient;\n}\n\n/**\n * React Query mutation that POSTs to `/me/sessions/{id}/revoke`. Pass the\n * session id as the variable. Automatically invalidates the sessions query.\n */\nexport function useRevokeSession(\n options: UseRevokeSessionOptions,\n): UseMutationResult<void, Error, string> {\n const { api, ...rest } = options;\n const queryClient = useQueryClient();\n return useMutation<void, Error, string>({\n mutationFn: (sessionId) => api.revokeSession(sessionId),\n ...rest,\n onSuccess: (data, variables, onMutateResult, context) => {\n void queryClient.invalidateQueries({ queryKey: SESSIONS_QUERY_KEY });\n if (rest.onSuccess !== undefined) {\n rest.onSuccess(data, variables, onMutateResult, context);\n }\n },\n });\n}\n","import {\n useMutation,\n useQueryClient,\n type UseMutationOptions,\n type UseMutationResult,\n} from '@tanstack/react-query';\n\nimport { SESSIONS_QUERY_KEY } from './useSessions';\n\nimport type { AuthClient } from '../AuthClient';\n\nexport interface UseLogoutEverywhereOptions\n extends Omit<UseMutationOptions<void, Error, void>, 'mutationFn'> {\n client: AuthClient;\n}\n\n/**\n * React Query mutation calling `AuthClient.logout({ everywhere: true })`.\n *\n * Routes through `AuthClient` (not `AuthApiClient` directly) so storage and\n * inactivity tracker are cleared as well. Invalidates the sessions query so\n * any open sessions screen reflects the empty list.\n */\nexport function useLogoutEverywhere(\n options: UseLogoutEverywhereOptions,\n): UseMutationResult<void, Error, void> {\n const { client, ...rest } = options;\n const queryClient = useQueryClient();\n return useMutation<void, Error, void>({\n mutationFn: () => client.logout({ everywhere: true }),\n ...rest,\n onSuccess: (data, variables, onMutateResult, context) => {\n void queryClient.invalidateQueries({ queryKey: SESSIONS_QUERY_KEY });\n if (rest.onSuccess !== undefined) {\n rest.onSuccess(data, variables, onMutateResult, context);\n }\n },\n });\n}\n"]}