@bodhiapp/bodhi-js 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bodhiapp/bodhi-js",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Web SDK for Bodhi Browser - window.bodhiext communication",
5
5
  "type": "module",
6
6
  "main": "dist/bodhi-web.cjs.js",
@@ -34,7 +34,7 @@
34
34
  "lint:fix": "prettier --write . && eslint . --ext .js,.jsx,.ts,.tsx --fix"
35
35
  },
36
36
  "dependencies": {
37
- "@bodhiapp/bodhi-js-core": "0.0.2",
37
+ "@bodhiapp/bodhi-js-core": "0.0.4",
38
38
  "@bodhiapp/ts-client": "^0.1.7"
39
39
  },
40
40
  "devDependencies": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"bodhi-web.cjs.js","sources":["../src/direct-client.ts","../src/constants.ts","../src/ext-client.ts","../src/facade-client.ts"],"sourcesContent":["/**\n * DirectWebClient - Direct HTTP client for web mode\n *\n * Uses browser redirect OAuth flow with localStorage for token storage.\n */\n\nimport {\n DirectClientBase,\n STORAGE_PREFIXES,\n generateCodeChallenge,\n generateCodeVerifier,\n isApiResultOperationError,\n isApiResultSuccess,\n type AuthLoggedIn,\n type AuthLoggedOut,\n type DirectClientBaseConfig,\n type StateChangeCallback,\n} from '@bodhiapp/bodhi-js-core';\n\n/**\n * Configuration for DirectWebClient\n */\nexport interface DirectWebClientConfig extends DirectClientBaseConfig {\n redirectUri: string;\n}\n\n/**\n * DirectWebClient - Web mode implementation using browser redirect OAuth\n */\nexport class DirectWebClient extends DirectClientBase {\n private redirectUri: string;\n\n constructor(config: DirectWebClientConfig, onStateChange?: StateChangeCallback) {\n super({ ...config, storagePrefix: STORAGE_PREFIXES.DIRECT }, 'DirectWebClient', onStateChange);\n this.redirectUri = config.redirectUri;\n }\n\n // ============================================================================\n // Authentication (Browser Redirect OAuth)\n // ============================================================================\n\n async login(): Promise<AuthLoggedIn> {\n const existingAuth = await this.getAuthState();\n if (existingAuth.isLoggedIn) {\n return existingAuth;\n }\n\n const resourceScope = await this.requestResourceAccess();\n const fullScope = `openid profile email roles ${this.userScope} ${resourceScope}`;\n\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = await generateCodeChallenge(codeVerifier);\n const state = generateCodeVerifier();\n\n localStorage.setItem(this.storageKeys.CODE_VERIFIER, codeVerifier);\n localStorage.setItem(this.storageKeys.STATE, state);\n\n const authUrl = new URL(this.authEndpoints.authorize);\n authUrl.searchParams.set('client_id', this.authClientId);\n authUrl.searchParams.set('response_type', 'code');\n authUrl.searchParams.set('redirect_uri', this.redirectUri);\n authUrl.searchParams.set('scope', fullScope);\n authUrl.searchParams.set('code_challenge', codeChallenge);\n authUrl.searchParams.set('code_challenge_method', 'S256');\n authUrl.searchParams.set('state', state);\n\n window.location.href = authUrl.toString();\n // Note: This line is never reached due to redirect, but TypeScript requires a return\n throw new Error('Redirect initiated');\n }\n\n async handleOAuthCallback(code: string, state: string): Promise<AuthLoggedIn> {\n const storedState = localStorage.getItem(this.storageKeys.STATE);\n if (!storedState || storedState !== state) {\n throw new Error('Invalid state parameter - possible CSRF attack');\n }\n\n await this.exchangeCodeForTokens(code);\n\n localStorage.removeItem(this.storageKeys.CODE_VERIFIER);\n localStorage.removeItem(this.storageKeys.STATE);\n\n const authState = await this.getAuthState();\n\n if (!authState.isLoggedIn) {\n throw new Error('Login failed');\n }\n\n const result: AuthLoggedIn = authState;\n\n this.setAuthState(result);\n return result;\n }\n\n async logout(): Promise<AuthLoggedOut> {\n const refreshToken = localStorage.getItem(this.storageKeys.REFRESH_TOKEN);\n\n if (refreshToken) {\n try {\n const params = new URLSearchParams({\n token: refreshToken,\n client_id: this.authClientId,\n token_type_hint: 'refresh_token',\n });\n\n await fetch(this.authEndpoints.revoke, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: params,\n });\n } catch (error) {\n this.logger.warn('Token revocation failed:', error);\n }\n }\n\n localStorage.removeItem(this.storageKeys.ACCESS_TOKEN);\n localStorage.removeItem(this.storageKeys.REFRESH_TOKEN);\n localStorage.removeItem(this.storageKeys.EXPIRES_AT);\n localStorage.removeItem(this.storageKeys.RESOURCE_SCOPE);\n\n const result = {\n isLoggedIn: false as const,\n };\n\n this.setAuthState(result);\n return result;\n }\n\n // ============================================================================\n // OAuth Helper Methods\n // ============================================================================\n\n protected async requestResourceAccess(): Promise<string> {\n const response = await this.sendApiRequest<{ app_client_id: string }, { scope: string }>(\n 'POST',\n '/bodhi/v1/apps/request-access',\n { app_client_id: this.authClientId },\n {},\n false\n );\n\n if (isApiResultOperationError(response)) {\n throw new Error('Failed to get resource access scope from server');\n }\n\n if (!isApiResultSuccess(response)) {\n throw new Error('Failed to get resource access scope from server: API error');\n }\n\n const scope = response.body.scope;\n localStorage.setItem(this.storageKeys.RESOURCE_SCOPE, scope);\n return scope;\n }\n\n protected async exchangeCodeForTokens(code: string): Promise<void> {\n const codeVerifier = localStorage.getItem(this.storageKeys.CODE_VERIFIER);\n if (!codeVerifier) {\n throw new Error('Code verifier not found');\n }\n\n const response = await fetch(this.authEndpoints.token, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n code,\n redirect_uri: this.redirectUri,\n client_id: this.authClientId,\n code_verifier: codeVerifier,\n }),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Token exchange failed: ${response.status} ${errorText}`);\n }\n\n const tokens = await response.json();\n\n localStorage.setItem(this.storageKeys.ACCESS_TOKEN, tokens.access_token);\n if (tokens.refresh_token) {\n localStorage.setItem(this.storageKeys.REFRESH_TOKEN, tokens.refresh_token);\n }\n\n if (tokens.expires_in) {\n const expiresAt = Date.now() + tokens.expires_in * 1000;\n localStorage.setItem(this.storageKeys.EXPIRES_AT, expiresAt.toString());\n }\n }\n\n // ============================================================================\n // Storage Implementation (localStorage)\n // ============================================================================\n\n protected async _storageGet(key: string): Promise<string | null> {\n return localStorage.getItem(key);\n }\n\n protected async _storageSet(items: Record<string, string | number>): Promise<void> {\n Object.entries(items).forEach(([key, value]) => {\n localStorage.setItem(key, String(value));\n });\n }\n\n protected async _storageRemove(keys: string[]): Promise<void> {\n keys.forEach((key) => localStorage.removeItem(key));\n }\n\n protected _getRedirectUri(): string {\n return this.redirectUri;\n }\n}\n","/**\n * Constants for web2ext communication\n */\n\nimport { STORAGE_PREFIXES, createStorageKeys } from '@bodhiapp/bodhi-js-core';\n\nexport const POLL_INTERVAL = 500; // Poll every 100ms\nexport const POLL_TIMEOUT = 5000; // 5s timeout\n\n/**\n * LocalStorage keys for OAuth tokens and PKCE flow (namespaced with 'bodhi:web' prefix)\n */\nexport const STORAGE_KEYS = createStorageKeys(STORAGE_PREFIXES.WEB);\n","import {\n BACKEND_SERVER_NOT_REACHABLE,\n EXTENSION_STATE_NOT_FOUND,\n EXTENSION_STATE_NOT_INITIALIZED,\n Logger,\n NOOP_STATE_CALLBACK,\n PENDING_EXTENSION_READY,\n SERVER_ERROR_CODES,\n backendServerNotReady,\n createApiError,\n createOAuthEndpoints,\n createOperationError,\n extractUserInfo,\n generateCodeChallenge,\n generateCodeVerifier,\n isApiResultOperationError,\n isApiResultSuccess,\n refreshAccessToken,\n type ApiResponseResult,\n type AuthLoggedIn,\n type AuthLoggedOut,\n type AuthState,\n type BackendServerState,\n type ClientState,\n type ExtensionState,\n type IExtensionClient,\n type InitParams,\n type OAuthEndpoints,\n type RefreshTokenResponse,\n type ServerInfoResponse,\n type StateChangeCallback,\n} from '@bodhiapp/bodhi-js-core';\nimport { type BodhiExtPublicApi, type StreamChunk } from '@bodhiapp/bodhi-browser/types';\nimport type { CreateChatCompletionStreamResponse, OpenAiApiError } from '@bodhiapp/ts-client';\nimport { POLL_INTERVAL, POLL_TIMEOUT, STORAGE_KEYS } from './constants';\nimport { WebClientConfig } from './facade-client';\n\n// Empty object type for future-proofing\nexport type SerializedWebExtensionState = { extensionId?: string };\n\n/**\n * WindowBodhiextClient - web mode extension client using window.bodhiext\n *\n * Communicates with bodhi-browser-ext via window.bodhiext API\n *\n * Implements IExtensionClient interface with state callback for state changes\n * Additionally provides handleOAuthCallback for web-specific OAuth flow\n *\n */\nexport class WindowBodhiextClient implements IExtensionClient {\n private state: ExtensionState = EXTENSION_STATE_NOT_INITIALIZED;\n private logger: Logger;\n private bodhiext: BodhiExtPublicApi | null = null;\n private authClientId: string;\n private config: WebClientConfig;\n private authEndpoints: OAuthEndpoints;\n private onStateChange: StateChangeCallback;\n private refreshPromise: Promise<string | null> | null = null;\n\n constructor(authClientId: string, config: WebClientConfig, onStateChange?: StateChangeCallback) {\n this.logger = new Logger('WindowBodhiextClient', config.logLevel);\n this.authClientId = authClientId;\n this.config = config;\n this.authEndpoints = createOAuthEndpoints(this.config.authServerUrl);\n this.onStateChange = onStateChange ?? NOOP_STATE_CALLBACK;\n }\n\n /**\n * Set client state and notify callback\n */\n private setState(newState: ExtensionState): void {\n this.state = newState;\n this.logger.info(`{state: ${JSON.stringify(newState)}} - Setting client state`);\n this.onStateChange({ type: 'client-state', state: newState });\n }\n\n /**\n * Set auth state and notify callback\n */\n private setAuthState(authState: AuthState): void {\n this.onStateChange({ type: 'auth-state', state: authState });\n }\n\n /**\n * Set or update the state change callback\n */\n setStateCallback(callback: StateChangeCallback): void {\n this.onStateChange = callback;\n }\n\n // ============================================================================\n // Extension Communication\n // ============================================================================\n\n /**\n * Ensure bodhiext is available, attempting to acquire it if not already set\n * @throws Error if client not initialized\n */\n private ensureBodhiext(): void {\n if (!this.bodhiext && window.bodhiext) {\n this.logger.info('Acquiring window.bodhiext reference');\n this.bodhiext = window.bodhiext;\n }\n if (!this.bodhiext) {\n throw new Error('Client not initialized');\n }\n }\n\n /**\n * Send extension request via window.bodhiext.sendExtRequest\n */\n\n async sendExtRequest<TParams = void, TRes = unknown>(\n action: string,\n params?: TParams\n ): Promise<TRes> {\n this.ensureBodhiext();\n return this.bodhiext!.sendExtRequest(action, params) as Promise<TRes>;\n }\n\n /**\n * Send API message via window.bodhiext.sendApiRequest\n * Converts ApiResponse to ApiResponseResult\n */\n async sendApiRequest<TReq = void, TRes = unknown>(\n method: string,\n endpoint: string,\n body?: TReq,\n headers?: Record<string, string>,\n authenticated?: boolean\n ): Promise<ApiResponseResult<TRes>> {\n try {\n this.ensureBodhiext();\n } catch (err) {\n return {\n error: {\n message: err instanceof Error ? err.message : String(err),\n type: 'extension_error',\n },\n };\n }\n try {\n let requestHeaders = headers || {};\n\n // Token injection for authenticated requests\n if (authenticated) {\n const accessToken = await this._getAccessTokenRaw();\n if (!accessToken) {\n return {\n error: {\n message: 'Not authenticated. Please log in first.',\n type: 'extension_error',\n },\n };\n }\n requestHeaders = {\n ...requestHeaders,\n Authorization: `Bearer ${accessToken}`,\n };\n }\n\n const response = await this.bodhiext!.sendApiRequest<unknown, TRes>(\n method,\n endpoint,\n body,\n requestHeaders\n );\n return response;\n } catch (e) {\n const errorObj = (e as { error?: { message?: string; type?: string } })?.error;\n const message = errorObj?.message ?? (e instanceof Error ? e.message : String(e));\n const errorType = errorObj?.type || 'extension_error';\n return {\n error: {\n message,\n type: errorType,\n },\n };\n }\n }\n\n /**\n * Get current client state\n */\n getState(): ClientState {\n return this.state;\n }\n\n isClientInitialized(): boolean {\n return this.state.extension === 'ready';\n }\n\n isServerReady(): boolean {\n return this.isClientInitialized() && this.state.server.status === 'ready';\n }\n\n /**\n * Initialize extension discovery with optional timeout\n * Returns ExtensionState with extension and server status\n *\n * Note: Web mode uses stateless discovery (always polls for window.bodhiext)\n * No extensionId storage/restoration needed - window.bodhiext handle is ephemeral\n */\n async init(params: InitParams = {}): Promise<ExtensionState> {\n // testConnection: false, selectedConnection: false → not-initialized\n if (!params.testConnection && !params.selectedConnection) {\n this.logger.info('No testConnection or selectedConnection, returning not-initialized state');\n return EXTENSION_STATE_NOT_INITIALIZED;\n }\n\n // IDEMPOTENCY: If already have handle and not testing, skip polling\n if (this.bodhiext && !params.testConnection) {\n this.logger.debug('Already have bodhiext handle, skipping polling');\n return this.state;\n }\n\n // Only poll if don't have handle yet\n if (!this.bodhiext) {\n // Priority: params > constructor defaults > constants\n const timeoutMs =\n params.timeoutMs ?? this.config.initParams?.extension?.timeoutMs ?? POLL_TIMEOUT;\n const intervalMs =\n params.intervalMs ?? this.config.initParams?.extension?.intervalMs ?? POLL_INTERVAL;\n const startTime = Date.now();\n\n // Poll for window.bodhiext\n const found = await new Promise<boolean>((resolve) => {\n const check = () => {\n if (window.bodhiext) {\n this.bodhiext = window.bodhiext;\n resolve(true);\n return;\n }\n if (Date.now() - startTime >= timeoutMs) {\n resolve(false);\n return;\n }\n setTimeout(check, intervalMs);\n };\n check();\n });\n\n if (!found) {\n this.logger.warn(`Extension discovery timed out`);\n this.setState(EXTENSION_STATE_NOT_FOUND);\n return this.state;\n }\n }\n\n // Have handle - build state\n const extensionId = await this.bodhiext!.getExtensionId();\n this.logger.info(`Extension discovered: ${extensionId}`);\n\n const state: ExtensionState = {\n type: 'extension',\n extension: 'ready',\n extensionId,\n server: PENDING_EXTENSION_READY,\n };\n\n // Test server connectivity if requested\n if (params.testConnection) {\n try {\n const serverState = await this.getServerState();\n this.setState({ ...state, server: serverState });\n this.logger.info(`Server connectivity tested: ${serverState.status}`);\n } catch (error) {\n this.logger.error(`Failed to get server state:`, error);\n this.setState({ ...state, server: BACKEND_SERVER_NOT_REACHABLE });\n }\n } else {\n this.setState(state);\n }\n\n return this.state;\n }\n\n // ============================================================================\n // OAuth Methods\n // ============================================================================\n\n /**\n * Request resource access scope from backend\n * Required for authenticated API access\n */\n private async requestResourceAccess(): Promise<string> {\n this.ensureBodhiext();\n\n const response = await this.bodhiext!.sendApiRequest<\n { app_client_id: string },\n { scope: string }\n >('POST', '/bodhi/v1/apps/request-access', {\n app_client_id: this.authClientId,\n });\n\n if (!isApiResultSuccess(response)) {\n throw new Error('Failed to get resource access scope: API error');\n }\n\n const scope = response.body.scope;\n localStorage.setItem(STORAGE_KEYS.RESOURCE_SCOPE, scope);\n return scope;\n }\n\n /**\n * Login via browser redirect OAuth2 + PKCE flow\n * @returns AuthLoggedIn (though in practice, this redirects and never returns)\n */\n async login(): Promise<AuthLoggedIn> {\n // Check if already logged in\n const existingAuth = await this.getAuthState();\n if (existingAuth.isLoggedIn) {\n return existingAuth;\n }\n\n // Ensure extension discovered\n this.ensureBodhiext();\n\n // Request resource access scope\n const resourceScope = await this.requestResourceAccess();\n\n // Generate PKCE verifier and challenge\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = await generateCodeChallenge(codeVerifier);\n\n // Generate state for CSRF protection\n const state = generateCodeVerifier();\n\n // Store verifier and state for callback\n localStorage.setItem(STORAGE_KEYS.CODE_VERIFIER, codeVerifier);\n localStorage.setItem(STORAGE_KEYS.STATE, state);\n\n // Build OAuth authorization URL\n const scopes = ['openid', 'profile', 'email', 'roles', this.config.userScope, resourceScope];\n\n const params = new URLSearchParams({\n response_type: 'code',\n client_id: this.authClientId,\n redirect_uri: this.config.redirectUri,\n scope: scopes.join(' '),\n state: state,\n code_challenge: codeChallenge,\n code_challenge_method: 'S256',\n });\n\n const authUrl = `${this.authEndpoints.authorize}?${params}`;\n\n // Redirect to authorization server\n window.location.href = authUrl;\n\n // TypeScript requires a return statement, but this code never executes\n // because the browser redirects above\n return new Promise(() => {});\n }\n\n /**\n * Handle OAuth callback with authorization code\n * Should be called from callback page with extracted URL params\n * @returns AuthLoggedIn with login state and user info\n */\n async handleOAuthCallback(code: string, state: string): Promise<AuthLoggedIn> {\n // Validate state to prevent CSRF\n const storedState = localStorage.getItem(STORAGE_KEYS.STATE);\n if (!storedState || storedState !== state) {\n throw new Error('Invalid state parameter - possible CSRF attack');\n }\n\n // Exchange code for tokens\n await this.exchangeCodeForTokens(code);\n\n // Clean up temporary storage\n localStorage.removeItem(STORAGE_KEYS.CODE_VERIFIER);\n localStorage.removeItem(STORAGE_KEYS.STATE);\n\n const authState = await this.getAuthState();\n\n if (!authState.isLoggedIn) {\n throw new Error('Login failed');\n }\n\n this.setAuthState(authState);\n return authState;\n }\n\n /**\n * Exchange authorization code for tokens\n */\n private async exchangeCodeForTokens(code: string): Promise<void> {\n const codeVerifier = localStorage.getItem(STORAGE_KEYS.CODE_VERIFIER);\n if (!codeVerifier) {\n throw new Error('Code verifier not found');\n }\n\n const params = new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: this.authClientId,\n code: code,\n redirect_uri: this.config.redirectUri,\n code_verifier: codeVerifier,\n });\n\n const response = await fetch(this.authEndpoints.token, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: params,\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Token exchange failed: ${response.status} ${errorText}`);\n }\n\n const tokenData = await response.json();\n\n if (!tokenData.access_token) {\n throw new Error('No access token received');\n }\n\n // Store tokens in localStorage\n localStorage.setItem(STORAGE_KEYS.ACCESS_TOKEN, tokenData.access_token);\n if (tokenData.refresh_token) {\n localStorage.setItem(STORAGE_KEYS.REFRESH_TOKEN, tokenData.refresh_token);\n }\n\n // Calculate and store expiration time\n if (tokenData.expires_in) {\n const expiresAt = Date.now() + tokenData.expires_in * 1000;\n localStorage.setItem(STORAGE_KEYS.EXPIRES_AT, expiresAt.toString());\n }\n }\n\n /**\n * Logout user and revoke tokens\n * @returns AuthLoggedOut with logged out state\n */\n async logout(): Promise<AuthLoggedOut> {\n const refreshToken = localStorage.getItem(STORAGE_KEYS.REFRESH_TOKEN);\n\n // Attempt to revoke token at auth server\n if (refreshToken) {\n try {\n const params = new URLSearchParams({\n token: refreshToken,\n client_id: this.authClientId,\n token_type_hint: 'refresh_token',\n });\n\n await fetch(this.authEndpoints.revoke, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: params,\n });\n } catch (error) {\n this.logger.warn('Token revocation failed:', error);\n }\n }\n\n // Clear all OAuth-related localStorage keys\n localStorage.removeItem(STORAGE_KEYS.ACCESS_TOKEN);\n localStorage.removeItem(STORAGE_KEYS.REFRESH_TOKEN);\n localStorage.removeItem(STORAGE_KEYS.EXPIRES_AT);\n localStorage.removeItem(STORAGE_KEYS.CODE_VERIFIER);\n localStorage.removeItem(STORAGE_KEYS.STATE);\n localStorage.removeItem(STORAGE_KEYS.RESOURCE_SCOPE);\n\n const result = {\n isLoggedIn: false as const,\n };\n\n this.setAuthState(result);\n return result;\n }\n\n /**\n * Get current authentication state\n */\n async getAuthState(): Promise<AuthState> {\n const accessToken = await this._getAccessTokenRaw();\n\n if (!accessToken) {\n return { isLoggedIn: false };\n }\n\n try {\n const userInfo = extractUserInfo(accessToken);\n return { isLoggedIn: true, userInfo, accessToken };\n } catch (error) {\n this.logger.error('Failed to parse token:', error);\n return { isLoggedIn: false };\n }\n }\n\n /**\n * Get current access token\n * Returns null if not logged in or token expired\n */\n protected async _getAccessTokenRaw(): Promise<string | null> {\n const accessToken = localStorage.getItem(STORAGE_KEYS.ACCESS_TOKEN);\n const expiresAt = localStorage.getItem(STORAGE_KEYS.EXPIRES_AT);\n\n if (!accessToken) {\n return null;\n }\n\n // Check if token is expired\n if (expiresAt) {\n const expirationTime = parseInt(expiresAt, 10);\n if (Date.now() >= expirationTime - 5 * 1000) {\n // Token expired - try to refresh\n const refreshToken = localStorage.getItem(STORAGE_KEYS.REFRESH_TOKEN);\n if (refreshToken) {\n return this._tryRefreshToken(refreshToken);\n }\n return null;\n }\n }\n\n return accessToken;\n }\n\n /**\n * Try to refresh access token using refresh token\n * Race condition prevention: Returns existing promise if refresh already in progress\n */\n private async _tryRefreshToken(refreshToken: string): Promise<string | null> {\n // If already refreshing, return the existing promise (avoids duplicate requests)\n if (this.refreshPromise) {\n this.logger.debug('Refresh already in progress, returning existing promise');\n return this.refreshPromise;\n }\n\n // Start refresh and store promise\n this.refreshPromise = this._doRefreshToken(refreshToken);\n\n try {\n return await this.refreshPromise;\n } finally {\n this.refreshPromise = null;\n }\n }\n\n /**\n * Perform the actual token refresh\n */\n private async _doRefreshToken(refreshToken: string): Promise<string | null> {\n this.logger.debug('Refreshing access token');\n\n try {\n const tokens = await refreshAccessToken(\n this.authEndpoints.token,\n refreshToken,\n this.authClientId\n );\n\n if (tokens) {\n this._storeRefreshedTokens(tokens);\n const userInfo = extractUserInfo(tokens.access_token);\n this.setAuthState({\n isLoggedIn: true,\n userInfo,\n accessToken: tokens.access_token,\n });\n this.logger.info('Token refreshed successfully');\n return tokens.access_token;\n }\n } catch (error) {\n this.logger.warn('Token refresh failed:', error);\n }\n\n // Refresh failed - throw error (don't clear tokens, may be temp issue)\n this.logger.warn('Token refresh failed, keeping tokens for manual retry');\n throw createOperationError(\n 'Access token expired and unable to refresh. Try logging out and logging in again.',\n 'token_refresh_failed'\n );\n }\n\n /**\n * Store refreshed tokens\n */\n private _storeRefreshedTokens(tokens: RefreshTokenResponse): void {\n const expiresAt = Date.now() + tokens.expires_in * 1000;\n\n localStorage.setItem(STORAGE_KEYS.ACCESS_TOKEN, tokens.access_token);\n localStorage.setItem(STORAGE_KEYS.EXPIRES_AT, String(expiresAt));\n\n // Update refresh token if provided (Keycloak token rotation)\n if (tokens.refresh_token) {\n localStorage.setItem(STORAGE_KEYS.REFRESH_TOKEN, tokens.refresh_token);\n }\n }\n\n /**\n * Ping API\n */\n async pingApi(): Promise<ApiResponseResult<{ message: string }>> {\n return this.sendApiRequest<void, { message: string }>('GET', '/ping');\n }\n\n /**\n * Fetch models\n */\n async fetchModels(): Promise<ApiResponseResult<{ data: Array<{ id: string; object: string }> }>> {\n return this.sendApiRequest<void, { data: Array<{ id: string; object: string }> }>(\n 'GET',\n '/v1/models',\n undefined,\n undefined,\n true\n );\n }\n\n /**\n * Get backend server state\n * Calls /bodhi/v1/info and returns structured server state\n */\n async getServerState(): Promise<BackendServerState> {\n const result = await this.sendApiRequest<void, ServerInfoResponse>('GET', '/bodhi/v1/info');\n\n if (isApiResultOperationError(result)) {\n return BACKEND_SERVER_NOT_REACHABLE;\n }\n\n if (!isApiResultSuccess(result)) {\n return BACKEND_SERVER_NOT_REACHABLE;\n }\n\n const body = result.body;\n\n switch (body.status) {\n case 'ready':\n return { status: 'ready', version: body.version || 'unknown' };\n case 'setup':\n return backendServerNotReady('setup', body.version || 'unknown');\n case 'resource-admin':\n return backendServerNotReady('resource-admin', body.version || 'unknown');\n case 'error':\n return backendServerNotReady(\n 'error',\n body.version || 'unknown',\n body.error\n ? { message: body.error.message, type: body.error.type }\n : SERVER_ERROR_CODES.SERVER_NOT_READY\n );\n default:\n return BACKEND_SERVER_NOT_REACHABLE;\n }\n }\n\n /**\n * Generic streaming via window.bodhiext.sendStreamRequest\n * Wraps ReadableStream as AsyncGenerator\n */\n async *stream<TReq = unknown, TRes = unknown>(\n method: string,\n endpoint: string,\n body?: TReq,\n headers?: Record<string, string>,\n authenticated: boolean = true\n ): AsyncGenerator<TRes> {\n this.ensureBodhiext();\n let requestHeaders = headers || {};\n // Token injection for authenticated requests\n if (authenticated) {\n const accessToken = await this._getAccessTokenRaw();\n if (!accessToken) {\n throw new Error('Not authenticated. Please log in first.');\n }\n requestHeaders = {\n ...requestHeaders,\n Authorization: `Bearer ${accessToken}`,\n };\n }\n\n const stream = this.bodhiext!.sendStreamRequest<TReq>(method, endpoint, body, requestHeaders);\n const reader = stream.getReader();\n\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done || (value as StreamChunk)?.done) {\n break;\n }\n yield (value as StreamChunk).body as TRes;\n }\n } catch (err) {\n // Convert discriminated error types to ConnectionError/ApiError\n if (err instanceof Error) {\n // Check for 'response' field = API error\n if ('response' in err) {\n const apiErr = err as Error & { response: { status: number; body: OpenAiApiError } };\n throw createApiError(err.message, apiErr.response.status, apiErr.response.body);\n }\n // Check for 'error' field = network/extension error\n if ('error' in err) {\n throw createOperationError(err.message, 'extension_error');\n }\n // Fallback for other errors\n throw createOperationError(err.message, 'extension_error');\n }\n throw err;\n } finally {\n reader.releaseLock();\n }\n }\n\n /**\n * Chat streaming\n */\n async *streamChat(\n model: string,\n prompt: string,\n authenticated: boolean = true\n ): AsyncGenerator<CreateChatCompletionStreamResponse> {\n yield* this.stream<\n { model: string; messages: Array<{ role: string; content: string }>; stream: true },\n CreateChatCompletionStreamResponse\n >(\n 'POST',\n '/v1/chat/completions',\n {\n model,\n messages: [{ role: 'user', content: prompt }],\n stream: true,\n },\n undefined,\n authenticated\n );\n }\n\n /**\n * Serialize web extension client state (all transient, nothing to persist)\n */\n serialize(): SerializedWebExtensionState {\n return {\n extensionId:\n this.state.type === 'extension' && this.state.extension === 'ready'\n ? this.state.extensionId\n : undefined,\n } as SerializedWebExtensionState;\n }\n\n /**\n * Debug dump of WindowBodhiextClient internal state\n */\n async debug(): Promise<Record<string, unknown>> {\n return {\n type: 'WindowBodhiextClient',\n state: this.state,\n authState: await this.getAuthState(),\n bodhiextAvailable: this.bodhiext !== null,\n authClientId: this.authClientId,\n authServerUrl: this.config.authServerUrl,\n redirectUri: this.config.redirectUri,\n userScope: this.config.userScope,\n };\n }\n}\n","/**\n * WebUIClient - Public facade client for web mode\n *\n * Wraps either DirectClient or InternalWebUIClient based on user preferences.\n * Delegates all UIClient methods to the active client instance.\n */\n\nimport {\n BaseFacadeClient,\n Logger,\n STORAGE_PREFIXES,\n type AuthLoggedIn,\n type IWebUIClient,\n type LogLevel,\n type StateChange,\n type StateChangeCallback,\n type UserScope,\n} from '@bodhiapp/bodhi-js-core';\nimport { DirectWebClient } from './direct-client';\nimport { WindowBodhiextClient } from './ext-client';\n\n/**\n * Configuration for WebClient OAuth\n */\nexport interface WebClientConfig {\n authServerUrl: string;\n redirectUri: string;\n userScope: UserScope;\n logLevel: LogLevel;\n initParams?: {\n extension?: {\n timeoutMs?: number;\n intervalMs?: number;\n };\n };\n}\n\n/**\n * WebUIClient - Public facade for web mode\n *\n * Automatically switches between DirectClient and InternalWebUIClient\n * based on stored user preferences.\n */\nexport class WebUIClient\n extends BaseFacadeClient<WebClientConfig, WindowBodhiextClient, DirectWebClient>\n implements IWebUIClient\n{\n constructor(\n authClientId: string,\n config: {\n redirectUri: string;\n authServerUrl?: string;\n userScope?: UserScope;\n logLevel?: LogLevel;\n initParams?: {\n extension?: {\n timeoutMs?: number;\n intervalMs?: number;\n };\n };\n },\n onStateChange?: StateChangeCallback,\n storagePrefix?: string\n ) {\n // Normalize config with defaults\n const normalizedConfig: WebClientConfig = {\n redirectUri: config.redirectUri,\n authServerUrl: config.authServerUrl || 'https://id.getbodhi.app/realms/bodhi',\n userScope: config.userScope || 'scope_user_user',\n logLevel: config.logLevel || 'warn',\n initParams: config.initParams,\n };\n\n super(authClientId, normalizedConfig, onStateChange, storagePrefix);\n }\n\n protected createLogger(config: WebClientConfig): Logger {\n return new Logger('WebUIClient', config.logLevel);\n }\n\n protected createExtClient(\n config: WebClientConfig,\n onStateChange: (change: StateChange) => void\n ): WindowBodhiextClient {\n return new WindowBodhiextClient(this.authClientId, config, onStateChange);\n }\n\n protected createDirectClient(\n authClientId: string,\n config: WebClientConfig,\n onStateChange: (change: StateChange) => void\n ): DirectWebClient {\n return new DirectWebClient(\n {\n authClientId,\n authServerUrl: config.authServerUrl,\n redirectUri: config.redirectUri,\n userScope: config.userScope,\n logLevel: config.logLevel,\n storagePrefix: STORAGE_PREFIXES.WEB,\n },\n onStateChange\n );\n }\n\n // ============================================================================\n // Web-specific OAuth Callback\n // ============================================================================\n async handleOAuthCallback(code: string, state: string): Promise<AuthLoggedIn> {\n // Delegate to active client based on connection mode\n if (this.connectionMode === 'direct') {\n return this.directClient.handleOAuthCallback(code, state);\n }\n return this.extClient.handleOAuthCallback(code, state);\n }\n}\n"],"names":["DirectClientBase","STORAGE_PREFIXES","generateCodeVerifier","generateCodeChallenge","isApiResultOperationError","isApiResultSuccess","createStorageKeys","EXTENSION_STATE_NOT_INITIALIZED","Logger","createOAuthEndpoints","NOOP_STATE_CALLBACK","EXTENSION_STATE_NOT_FOUND","PENDING_EXTENSION_READY","BACKEND_SERVER_NOT_REACHABLE","extractUserInfo","refreshAccessToken","createOperationError","backendServerNotReady","SERVER_ERROR_CODES","createApiError","BaseFacadeClient"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"bodhi-web.esm.js","sources":["../src/direct-client.ts","../src/constants.ts","../src/ext-client.ts","../src/facade-client.ts"],"sourcesContent":["/**\n * DirectWebClient - Direct HTTP client for web mode\n *\n * Uses browser redirect OAuth flow with localStorage for token storage.\n */\n\nimport {\n DirectClientBase,\n STORAGE_PREFIXES,\n generateCodeChallenge,\n generateCodeVerifier,\n isApiResultOperationError,\n isApiResultSuccess,\n type AuthLoggedIn,\n type AuthLoggedOut,\n type DirectClientBaseConfig,\n type StateChangeCallback,\n} from '@bodhiapp/bodhi-js-core';\n\n/**\n * Configuration for DirectWebClient\n */\nexport interface DirectWebClientConfig extends DirectClientBaseConfig {\n redirectUri: string;\n}\n\n/**\n * DirectWebClient - Web mode implementation using browser redirect OAuth\n */\nexport class DirectWebClient extends DirectClientBase {\n private redirectUri: string;\n\n constructor(config: DirectWebClientConfig, onStateChange?: StateChangeCallback) {\n super({ ...config, storagePrefix: STORAGE_PREFIXES.DIRECT }, 'DirectWebClient', onStateChange);\n this.redirectUri = config.redirectUri;\n }\n\n // ============================================================================\n // Authentication (Browser Redirect OAuth)\n // ============================================================================\n\n async login(): Promise<AuthLoggedIn> {\n const existingAuth = await this.getAuthState();\n if (existingAuth.isLoggedIn) {\n return existingAuth;\n }\n\n const resourceScope = await this.requestResourceAccess();\n const fullScope = `openid profile email roles ${this.userScope} ${resourceScope}`;\n\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = await generateCodeChallenge(codeVerifier);\n const state = generateCodeVerifier();\n\n localStorage.setItem(this.storageKeys.CODE_VERIFIER, codeVerifier);\n localStorage.setItem(this.storageKeys.STATE, state);\n\n const authUrl = new URL(this.authEndpoints.authorize);\n authUrl.searchParams.set('client_id', this.authClientId);\n authUrl.searchParams.set('response_type', 'code');\n authUrl.searchParams.set('redirect_uri', this.redirectUri);\n authUrl.searchParams.set('scope', fullScope);\n authUrl.searchParams.set('code_challenge', codeChallenge);\n authUrl.searchParams.set('code_challenge_method', 'S256');\n authUrl.searchParams.set('state', state);\n\n window.location.href = authUrl.toString();\n // Note: This line is never reached due to redirect, but TypeScript requires a return\n throw new Error('Redirect initiated');\n }\n\n async handleOAuthCallback(code: string, state: string): Promise<AuthLoggedIn> {\n const storedState = localStorage.getItem(this.storageKeys.STATE);\n if (!storedState || storedState !== state) {\n throw new Error('Invalid state parameter - possible CSRF attack');\n }\n\n await this.exchangeCodeForTokens(code);\n\n localStorage.removeItem(this.storageKeys.CODE_VERIFIER);\n localStorage.removeItem(this.storageKeys.STATE);\n\n const authState = await this.getAuthState();\n\n if (!authState.isLoggedIn) {\n throw new Error('Login failed');\n }\n\n const result: AuthLoggedIn = authState;\n\n this.setAuthState(result);\n return result;\n }\n\n async logout(): Promise<AuthLoggedOut> {\n const refreshToken = localStorage.getItem(this.storageKeys.REFRESH_TOKEN);\n\n if (refreshToken) {\n try {\n const params = new URLSearchParams({\n token: refreshToken,\n client_id: this.authClientId,\n token_type_hint: 'refresh_token',\n });\n\n await fetch(this.authEndpoints.revoke, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: params,\n });\n } catch (error) {\n this.logger.warn('Token revocation failed:', error);\n }\n }\n\n localStorage.removeItem(this.storageKeys.ACCESS_TOKEN);\n localStorage.removeItem(this.storageKeys.REFRESH_TOKEN);\n localStorage.removeItem(this.storageKeys.EXPIRES_AT);\n localStorage.removeItem(this.storageKeys.RESOURCE_SCOPE);\n\n const result = {\n isLoggedIn: false as const,\n };\n\n this.setAuthState(result);\n return result;\n }\n\n // ============================================================================\n // OAuth Helper Methods\n // ============================================================================\n\n protected async requestResourceAccess(): Promise<string> {\n const response = await this.sendApiRequest<{ app_client_id: string }, { scope: string }>(\n 'POST',\n '/bodhi/v1/apps/request-access',\n { app_client_id: this.authClientId },\n {},\n false\n );\n\n if (isApiResultOperationError(response)) {\n throw new Error('Failed to get resource access scope from server');\n }\n\n if (!isApiResultSuccess(response)) {\n throw new Error('Failed to get resource access scope from server: API error');\n }\n\n const scope = response.body.scope;\n localStorage.setItem(this.storageKeys.RESOURCE_SCOPE, scope);\n return scope;\n }\n\n protected async exchangeCodeForTokens(code: string): Promise<void> {\n const codeVerifier = localStorage.getItem(this.storageKeys.CODE_VERIFIER);\n if (!codeVerifier) {\n throw new Error('Code verifier not found');\n }\n\n const response = await fetch(this.authEndpoints.token, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n code,\n redirect_uri: this.redirectUri,\n client_id: this.authClientId,\n code_verifier: codeVerifier,\n }),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Token exchange failed: ${response.status} ${errorText}`);\n }\n\n const tokens = await response.json();\n\n localStorage.setItem(this.storageKeys.ACCESS_TOKEN, tokens.access_token);\n if (tokens.refresh_token) {\n localStorage.setItem(this.storageKeys.REFRESH_TOKEN, tokens.refresh_token);\n }\n\n if (tokens.expires_in) {\n const expiresAt = Date.now() + tokens.expires_in * 1000;\n localStorage.setItem(this.storageKeys.EXPIRES_AT, expiresAt.toString());\n }\n }\n\n // ============================================================================\n // Storage Implementation (localStorage)\n // ============================================================================\n\n protected async _storageGet(key: string): Promise<string | null> {\n return localStorage.getItem(key);\n }\n\n protected async _storageSet(items: Record<string, string | number>): Promise<void> {\n Object.entries(items).forEach(([key, value]) => {\n localStorage.setItem(key, String(value));\n });\n }\n\n protected async _storageRemove(keys: string[]): Promise<void> {\n keys.forEach((key) => localStorage.removeItem(key));\n }\n\n protected _getRedirectUri(): string {\n return this.redirectUri;\n }\n}\n","/**\n * Constants for web2ext communication\n */\n\nimport { STORAGE_PREFIXES, createStorageKeys } from '@bodhiapp/bodhi-js-core';\n\nexport const POLL_INTERVAL = 500; // Poll every 100ms\nexport const POLL_TIMEOUT = 5000; // 5s timeout\n\n/**\n * LocalStorage keys for OAuth tokens and PKCE flow (namespaced with 'bodhi:web' prefix)\n */\nexport const STORAGE_KEYS = createStorageKeys(STORAGE_PREFIXES.WEB);\n","import {\n BACKEND_SERVER_NOT_REACHABLE,\n EXTENSION_STATE_NOT_FOUND,\n EXTENSION_STATE_NOT_INITIALIZED,\n Logger,\n NOOP_STATE_CALLBACK,\n PENDING_EXTENSION_READY,\n SERVER_ERROR_CODES,\n backendServerNotReady,\n createApiError,\n createOAuthEndpoints,\n createOperationError,\n extractUserInfo,\n generateCodeChallenge,\n generateCodeVerifier,\n isApiResultOperationError,\n isApiResultSuccess,\n refreshAccessToken,\n type ApiResponseResult,\n type AuthLoggedIn,\n type AuthLoggedOut,\n type AuthState,\n type BackendServerState,\n type ClientState,\n type ExtensionState,\n type IExtensionClient,\n type InitParams,\n type OAuthEndpoints,\n type RefreshTokenResponse,\n type ServerInfoResponse,\n type StateChangeCallback,\n} from '@bodhiapp/bodhi-js-core';\nimport { type BodhiExtPublicApi, type StreamChunk } from '@bodhiapp/bodhi-browser/types';\nimport type { CreateChatCompletionStreamResponse, OpenAiApiError } from '@bodhiapp/ts-client';\nimport { POLL_INTERVAL, POLL_TIMEOUT, STORAGE_KEYS } from './constants';\nimport { WebClientConfig } from './facade-client';\n\n// Empty object type for future-proofing\nexport type SerializedWebExtensionState = { extensionId?: string };\n\n/**\n * WindowBodhiextClient - web mode extension client using window.bodhiext\n *\n * Communicates with bodhi-browser-ext via window.bodhiext API\n *\n * Implements IExtensionClient interface with state callback for state changes\n * Additionally provides handleOAuthCallback for web-specific OAuth flow\n *\n */\nexport class WindowBodhiextClient implements IExtensionClient {\n private state: ExtensionState = EXTENSION_STATE_NOT_INITIALIZED;\n private logger: Logger;\n private bodhiext: BodhiExtPublicApi | null = null;\n private authClientId: string;\n private config: WebClientConfig;\n private authEndpoints: OAuthEndpoints;\n private onStateChange: StateChangeCallback;\n private refreshPromise: Promise<string | null> | null = null;\n\n constructor(authClientId: string, config: WebClientConfig, onStateChange?: StateChangeCallback) {\n this.logger = new Logger('WindowBodhiextClient', config.logLevel);\n this.authClientId = authClientId;\n this.config = config;\n this.authEndpoints = createOAuthEndpoints(this.config.authServerUrl);\n this.onStateChange = onStateChange ?? NOOP_STATE_CALLBACK;\n }\n\n /**\n * Set client state and notify callback\n */\n private setState(newState: ExtensionState): void {\n this.state = newState;\n this.logger.info(`{state: ${JSON.stringify(newState)}} - Setting client state`);\n this.onStateChange({ type: 'client-state', state: newState });\n }\n\n /**\n * Set auth state and notify callback\n */\n private setAuthState(authState: AuthState): void {\n this.onStateChange({ type: 'auth-state', state: authState });\n }\n\n /**\n * Set or update the state change callback\n */\n setStateCallback(callback: StateChangeCallback): void {\n this.onStateChange = callback;\n }\n\n // ============================================================================\n // Extension Communication\n // ============================================================================\n\n /**\n * Ensure bodhiext is available, attempting to acquire it if not already set\n * @throws Error if client not initialized\n */\n private ensureBodhiext(): void {\n if (!this.bodhiext && window.bodhiext) {\n this.logger.info('Acquiring window.bodhiext reference');\n this.bodhiext = window.bodhiext;\n }\n if (!this.bodhiext) {\n throw new Error('Client not initialized');\n }\n }\n\n /**\n * Send extension request via window.bodhiext.sendExtRequest\n */\n\n async sendExtRequest<TParams = void, TRes = unknown>(\n action: string,\n params?: TParams\n ): Promise<TRes> {\n this.ensureBodhiext();\n return this.bodhiext!.sendExtRequest(action, params) as Promise<TRes>;\n }\n\n /**\n * Send API message via window.bodhiext.sendApiRequest\n * Converts ApiResponse to ApiResponseResult\n */\n async sendApiRequest<TReq = void, TRes = unknown>(\n method: string,\n endpoint: string,\n body?: TReq,\n headers?: Record<string, string>,\n authenticated?: boolean\n ): Promise<ApiResponseResult<TRes>> {\n try {\n this.ensureBodhiext();\n } catch (err) {\n return {\n error: {\n message: err instanceof Error ? err.message : String(err),\n type: 'extension_error',\n },\n };\n }\n try {\n let requestHeaders = headers || {};\n\n // Token injection for authenticated requests\n if (authenticated) {\n const accessToken = await this._getAccessTokenRaw();\n if (!accessToken) {\n return {\n error: {\n message: 'Not authenticated. Please log in first.',\n type: 'extension_error',\n },\n };\n }\n requestHeaders = {\n ...requestHeaders,\n Authorization: `Bearer ${accessToken}`,\n };\n }\n\n const response = await this.bodhiext!.sendApiRequest<unknown, TRes>(\n method,\n endpoint,\n body,\n requestHeaders\n );\n return response;\n } catch (e) {\n const errorObj = (e as { error?: { message?: string; type?: string } })?.error;\n const message = errorObj?.message ?? (e instanceof Error ? e.message : String(e));\n const errorType = errorObj?.type || 'extension_error';\n return {\n error: {\n message,\n type: errorType,\n },\n };\n }\n }\n\n /**\n * Get current client state\n */\n getState(): ClientState {\n return this.state;\n }\n\n isClientInitialized(): boolean {\n return this.state.extension === 'ready';\n }\n\n isServerReady(): boolean {\n return this.isClientInitialized() && this.state.server.status === 'ready';\n }\n\n /**\n * Initialize extension discovery with optional timeout\n * Returns ExtensionState with extension and server status\n *\n * Note: Web mode uses stateless discovery (always polls for window.bodhiext)\n * No extensionId storage/restoration needed - window.bodhiext handle is ephemeral\n */\n async init(params: InitParams = {}): Promise<ExtensionState> {\n // testConnection: false, selectedConnection: false → not-initialized\n if (!params.testConnection && !params.selectedConnection) {\n this.logger.info('No testConnection or selectedConnection, returning not-initialized state');\n return EXTENSION_STATE_NOT_INITIALIZED;\n }\n\n // IDEMPOTENCY: If already have handle and not testing, skip polling\n if (this.bodhiext && !params.testConnection) {\n this.logger.debug('Already have bodhiext handle, skipping polling');\n return this.state;\n }\n\n // Only poll if don't have handle yet\n if (!this.bodhiext) {\n // Priority: params > constructor defaults > constants\n const timeoutMs =\n params.timeoutMs ?? this.config.initParams?.extension?.timeoutMs ?? POLL_TIMEOUT;\n const intervalMs =\n params.intervalMs ?? this.config.initParams?.extension?.intervalMs ?? POLL_INTERVAL;\n const startTime = Date.now();\n\n // Poll for window.bodhiext\n const found = await new Promise<boolean>((resolve) => {\n const check = () => {\n if (window.bodhiext) {\n this.bodhiext = window.bodhiext;\n resolve(true);\n return;\n }\n if (Date.now() - startTime >= timeoutMs) {\n resolve(false);\n return;\n }\n setTimeout(check, intervalMs);\n };\n check();\n });\n\n if (!found) {\n this.logger.warn(`Extension discovery timed out`);\n this.setState(EXTENSION_STATE_NOT_FOUND);\n return this.state;\n }\n }\n\n // Have handle - build state\n const extensionId = await this.bodhiext!.getExtensionId();\n this.logger.info(`Extension discovered: ${extensionId}`);\n\n const state: ExtensionState = {\n type: 'extension',\n extension: 'ready',\n extensionId,\n server: PENDING_EXTENSION_READY,\n };\n\n // Test server connectivity if requested\n if (params.testConnection) {\n try {\n const serverState = await this.getServerState();\n this.setState({ ...state, server: serverState });\n this.logger.info(`Server connectivity tested: ${serverState.status}`);\n } catch (error) {\n this.logger.error(`Failed to get server state:`, error);\n this.setState({ ...state, server: BACKEND_SERVER_NOT_REACHABLE });\n }\n } else {\n this.setState(state);\n }\n\n return this.state;\n }\n\n // ============================================================================\n // OAuth Methods\n // ============================================================================\n\n /**\n * Request resource access scope from backend\n * Required for authenticated API access\n */\n private async requestResourceAccess(): Promise<string> {\n this.ensureBodhiext();\n\n const response = await this.bodhiext!.sendApiRequest<\n { app_client_id: string },\n { scope: string }\n >('POST', '/bodhi/v1/apps/request-access', {\n app_client_id: this.authClientId,\n });\n\n if (!isApiResultSuccess(response)) {\n throw new Error('Failed to get resource access scope: API error');\n }\n\n const scope = response.body.scope;\n localStorage.setItem(STORAGE_KEYS.RESOURCE_SCOPE, scope);\n return scope;\n }\n\n /**\n * Login via browser redirect OAuth2 + PKCE flow\n * @returns AuthLoggedIn (though in practice, this redirects and never returns)\n */\n async login(): Promise<AuthLoggedIn> {\n // Check if already logged in\n const existingAuth = await this.getAuthState();\n if (existingAuth.isLoggedIn) {\n return existingAuth;\n }\n\n // Ensure extension discovered\n this.ensureBodhiext();\n\n // Request resource access scope\n const resourceScope = await this.requestResourceAccess();\n\n // Generate PKCE verifier and challenge\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = await generateCodeChallenge(codeVerifier);\n\n // Generate state for CSRF protection\n const state = generateCodeVerifier();\n\n // Store verifier and state for callback\n localStorage.setItem(STORAGE_KEYS.CODE_VERIFIER, codeVerifier);\n localStorage.setItem(STORAGE_KEYS.STATE, state);\n\n // Build OAuth authorization URL\n const scopes = ['openid', 'profile', 'email', 'roles', this.config.userScope, resourceScope];\n\n const params = new URLSearchParams({\n response_type: 'code',\n client_id: this.authClientId,\n redirect_uri: this.config.redirectUri,\n scope: scopes.join(' '),\n state: state,\n code_challenge: codeChallenge,\n code_challenge_method: 'S256',\n });\n\n const authUrl = `${this.authEndpoints.authorize}?${params}`;\n\n // Redirect to authorization server\n window.location.href = authUrl;\n\n // TypeScript requires a return statement, but this code never executes\n // because the browser redirects above\n return new Promise(() => {});\n }\n\n /**\n * Handle OAuth callback with authorization code\n * Should be called from callback page with extracted URL params\n * @returns AuthLoggedIn with login state and user info\n */\n async handleOAuthCallback(code: string, state: string): Promise<AuthLoggedIn> {\n // Validate state to prevent CSRF\n const storedState = localStorage.getItem(STORAGE_KEYS.STATE);\n if (!storedState || storedState !== state) {\n throw new Error('Invalid state parameter - possible CSRF attack');\n }\n\n // Exchange code for tokens\n await this.exchangeCodeForTokens(code);\n\n // Clean up temporary storage\n localStorage.removeItem(STORAGE_KEYS.CODE_VERIFIER);\n localStorage.removeItem(STORAGE_KEYS.STATE);\n\n const authState = await this.getAuthState();\n\n if (!authState.isLoggedIn) {\n throw new Error('Login failed');\n }\n\n this.setAuthState(authState);\n return authState;\n }\n\n /**\n * Exchange authorization code for tokens\n */\n private async exchangeCodeForTokens(code: string): Promise<void> {\n const codeVerifier = localStorage.getItem(STORAGE_KEYS.CODE_VERIFIER);\n if (!codeVerifier) {\n throw new Error('Code verifier not found');\n }\n\n const params = new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: this.authClientId,\n code: code,\n redirect_uri: this.config.redirectUri,\n code_verifier: codeVerifier,\n });\n\n const response = await fetch(this.authEndpoints.token, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: params,\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Token exchange failed: ${response.status} ${errorText}`);\n }\n\n const tokenData = await response.json();\n\n if (!tokenData.access_token) {\n throw new Error('No access token received');\n }\n\n // Store tokens in localStorage\n localStorage.setItem(STORAGE_KEYS.ACCESS_TOKEN, tokenData.access_token);\n if (tokenData.refresh_token) {\n localStorage.setItem(STORAGE_KEYS.REFRESH_TOKEN, tokenData.refresh_token);\n }\n\n // Calculate and store expiration time\n if (tokenData.expires_in) {\n const expiresAt = Date.now() + tokenData.expires_in * 1000;\n localStorage.setItem(STORAGE_KEYS.EXPIRES_AT, expiresAt.toString());\n }\n }\n\n /**\n * Logout user and revoke tokens\n * @returns AuthLoggedOut with logged out state\n */\n async logout(): Promise<AuthLoggedOut> {\n const refreshToken = localStorage.getItem(STORAGE_KEYS.REFRESH_TOKEN);\n\n // Attempt to revoke token at auth server\n if (refreshToken) {\n try {\n const params = new URLSearchParams({\n token: refreshToken,\n client_id: this.authClientId,\n token_type_hint: 'refresh_token',\n });\n\n await fetch(this.authEndpoints.revoke, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: params,\n });\n } catch (error) {\n this.logger.warn('Token revocation failed:', error);\n }\n }\n\n // Clear all OAuth-related localStorage keys\n localStorage.removeItem(STORAGE_KEYS.ACCESS_TOKEN);\n localStorage.removeItem(STORAGE_KEYS.REFRESH_TOKEN);\n localStorage.removeItem(STORAGE_KEYS.EXPIRES_AT);\n localStorage.removeItem(STORAGE_KEYS.CODE_VERIFIER);\n localStorage.removeItem(STORAGE_KEYS.STATE);\n localStorage.removeItem(STORAGE_KEYS.RESOURCE_SCOPE);\n\n const result = {\n isLoggedIn: false as const,\n };\n\n this.setAuthState(result);\n return result;\n }\n\n /**\n * Get current authentication state\n */\n async getAuthState(): Promise<AuthState> {\n const accessToken = await this._getAccessTokenRaw();\n\n if (!accessToken) {\n return { isLoggedIn: false };\n }\n\n try {\n const userInfo = extractUserInfo(accessToken);\n return { isLoggedIn: true, userInfo, accessToken };\n } catch (error) {\n this.logger.error('Failed to parse token:', error);\n return { isLoggedIn: false };\n }\n }\n\n /**\n * Get current access token\n * Returns null if not logged in or token expired\n */\n protected async _getAccessTokenRaw(): Promise<string | null> {\n const accessToken = localStorage.getItem(STORAGE_KEYS.ACCESS_TOKEN);\n const expiresAt = localStorage.getItem(STORAGE_KEYS.EXPIRES_AT);\n\n if (!accessToken) {\n return null;\n }\n\n // Check if token is expired\n if (expiresAt) {\n const expirationTime = parseInt(expiresAt, 10);\n if (Date.now() >= expirationTime - 5 * 1000) {\n // Token expired - try to refresh\n const refreshToken = localStorage.getItem(STORAGE_KEYS.REFRESH_TOKEN);\n if (refreshToken) {\n return this._tryRefreshToken(refreshToken);\n }\n return null;\n }\n }\n\n return accessToken;\n }\n\n /**\n * Try to refresh access token using refresh token\n * Race condition prevention: Returns existing promise if refresh already in progress\n */\n private async _tryRefreshToken(refreshToken: string): Promise<string | null> {\n // If already refreshing, return the existing promise (avoids duplicate requests)\n if (this.refreshPromise) {\n this.logger.debug('Refresh already in progress, returning existing promise');\n return this.refreshPromise;\n }\n\n // Start refresh and store promise\n this.refreshPromise = this._doRefreshToken(refreshToken);\n\n try {\n return await this.refreshPromise;\n } finally {\n this.refreshPromise = null;\n }\n }\n\n /**\n * Perform the actual token refresh\n */\n private async _doRefreshToken(refreshToken: string): Promise<string | null> {\n this.logger.debug('Refreshing access token');\n\n try {\n const tokens = await refreshAccessToken(\n this.authEndpoints.token,\n refreshToken,\n this.authClientId\n );\n\n if (tokens) {\n this._storeRefreshedTokens(tokens);\n const userInfo = extractUserInfo(tokens.access_token);\n this.setAuthState({\n isLoggedIn: true,\n userInfo,\n accessToken: tokens.access_token,\n });\n this.logger.info('Token refreshed successfully');\n return tokens.access_token;\n }\n } catch (error) {\n this.logger.warn('Token refresh failed:', error);\n }\n\n // Refresh failed - throw error (don't clear tokens, may be temp issue)\n this.logger.warn('Token refresh failed, keeping tokens for manual retry');\n throw createOperationError(\n 'Access token expired and unable to refresh. Try logging out and logging in again.',\n 'token_refresh_failed'\n );\n }\n\n /**\n * Store refreshed tokens\n */\n private _storeRefreshedTokens(tokens: RefreshTokenResponse): void {\n const expiresAt = Date.now() + tokens.expires_in * 1000;\n\n localStorage.setItem(STORAGE_KEYS.ACCESS_TOKEN, tokens.access_token);\n localStorage.setItem(STORAGE_KEYS.EXPIRES_AT, String(expiresAt));\n\n // Update refresh token if provided (Keycloak token rotation)\n if (tokens.refresh_token) {\n localStorage.setItem(STORAGE_KEYS.REFRESH_TOKEN, tokens.refresh_token);\n }\n }\n\n /**\n * Ping API\n */\n async pingApi(): Promise<ApiResponseResult<{ message: string }>> {\n return this.sendApiRequest<void, { message: string }>('GET', '/ping');\n }\n\n /**\n * Fetch models\n */\n async fetchModels(): Promise<ApiResponseResult<{ data: Array<{ id: string; object: string }> }>> {\n return this.sendApiRequest<void, { data: Array<{ id: string; object: string }> }>(\n 'GET',\n '/v1/models',\n undefined,\n undefined,\n true\n );\n }\n\n /**\n * Get backend server state\n * Calls /bodhi/v1/info and returns structured server state\n */\n async getServerState(): Promise<BackendServerState> {\n const result = await this.sendApiRequest<void, ServerInfoResponse>('GET', '/bodhi/v1/info');\n\n if (isApiResultOperationError(result)) {\n return BACKEND_SERVER_NOT_REACHABLE;\n }\n\n if (!isApiResultSuccess(result)) {\n return BACKEND_SERVER_NOT_REACHABLE;\n }\n\n const body = result.body;\n\n switch (body.status) {\n case 'ready':\n return { status: 'ready', version: body.version || 'unknown' };\n case 'setup':\n return backendServerNotReady('setup', body.version || 'unknown');\n case 'resource-admin':\n return backendServerNotReady('resource-admin', body.version || 'unknown');\n case 'error':\n return backendServerNotReady(\n 'error',\n body.version || 'unknown',\n body.error\n ? { message: body.error.message, type: body.error.type }\n : SERVER_ERROR_CODES.SERVER_NOT_READY\n );\n default:\n return BACKEND_SERVER_NOT_REACHABLE;\n }\n }\n\n /**\n * Generic streaming via window.bodhiext.sendStreamRequest\n * Wraps ReadableStream as AsyncGenerator\n */\n async *stream<TReq = unknown, TRes = unknown>(\n method: string,\n endpoint: string,\n body?: TReq,\n headers?: Record<string, string>,\n authenticated: boolean = true\n ): AsyncGenerator<TRes> {\n this.ensureBodhiext();\n let requestHeaders = headers || {};\n // Token injection for authenticated requests\n if (authenticated) {\n const accessToken = await this._getAccessTokenRaw();\n if (!accessToken) {\n throw new Error('Not authenticated. Please log in first.');\n }\n requestHeaders = {\n ...requestHeaders,\n Authorization: `Bearer ${accessToken}`,\n };\n }\n\n const stream = this.bodhiext!.sendStreamRequest<TReq>(method, endpoint, body, requestHeaders);\n const reader = stream.getReader();\n\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done || (value as StreamChunk)?.done) {\n break;\n }\n yield (value as StreamChunk).body as TRes;\n }\n } catch (err) {\n // Convert discriminated error types to ConnectionError/ApiError\n if (err instanceof Error) {\n // Check for 'response' field = API error\n if ('response' in err) {\n const apiErr = err as Error & { response: { status: number; body: OpenAiApiError } };\n throw createApiError(err.message, apiErr.response.status, apiErr.response.body);\n }\n // Check for 'error' field = network/extension error\n if ('error' in err) {\n throw createOperationError(err.message, 'extension_error');\n }\n // Fallback for other errors\n throw createOperationError(err.message, 'extension_error');\n }\n throw err;\n } finally {\n reader.releaseLock();\n }\n }\n\n /**\n * Chat streaming\n */\n async *streamChat(\n model: string,\n prompt: string,\n authenticated: boolean = true\n ): AsyncGenerator<CreateChatCompletionStreamResponse> {\n yield* this.stream<\n { model: string; messages: Array<{ role: string; content: string }>; stream: true },\n CreateChatCompletionStreamResponse\n >(\n 'POST',\n '/v1/chat/completions',\n {\n model,\n messages: [{ role: 'user', content: prompt }],\n stream: true,\n },\n undefined,\n authenticated\n );\n }\n\n /**\n * Serialize web extension client state (all transient, nothing to persist)\n */\n serialize(): SerializedWebExtensionState {\n return {\n extensionId:\n this.state.type === 'extension' && this.state.extension === 'ready'\n ? this.state.extensionId\n : undefined,\n } as SerializedWebExtensionState;\n }\n\n /**\n * Debug dump of WindowBodhiextClient internal state\n */\n async debug(): Promise<Record<string, unknown>> {\n return {\n type: 'WindowBodhiextClient',\n state: this.state,\n authState: await this.getAuthState(),\n bodhiextAvailable: this.bodhiext !== null,\n authClientId: this.authClientId,\n authServerUrl: this.config.authServerUrl,\n redirectUri: this.config.redirectUri,\n userScope: this.config.userScope,\n };\n }\n}\n","/**\n * WebUIClient - Public facade client for web mode\n *\n * Wraps either DirectClient or InternalWebUIClient based on user preferences.\n * Delegates all UIClient methods to the active client instance.\n */\n\nimport {\n BaseFacadeClient,\n Logger,\n STORAGE_PREFIXES,\n type AuthLoggedIn,\n type IWebUIClient,\n type LogLevel,\n type StateChange,\n type StateChangeCallback,\n type UserScope,\n} from '@bodhiapp/bodhi-js-core';\nimport { DirectWebClient } from './direct-client';\nimport { WindowBodhiextClient } from './ext-client';\n\n/**\n * Configuration for WebClient OAuth\n */\nexport interface WebClientConfig {\n authServerUrl: string;\n redirectUri: string;\n userScope: UserScope;\n logLevel: LogLevel;\n initParams?: {\n extension?: {\n timeoutMs?: number;\n intervalMs?: number;\n };\n };\n}\n\n/**\n * WebUIClient - Public facade for web mode\n *\n * Automatically switches between DirectClient and InternalWebUIClient\n * based on stored user preferences.\n */\nexport class WebUIClient\n extends BaseFacadeClient<WebClientConfig, WindowBodhiextClient, DirectWebClient>\n implements IWebUIClient\n{\n constructor(\n authClientId: string,\n config: {\n redirectUri: string;\n authServerUrl?: string;\n userScope?: UserScope;\n logLevel?: LogLevel;\n initParams?: {\n extension?: {\n timeoutMs?: number;\n intervalMs?: number;\n };\n };\n },\n onStateChange?: StateChangeCallback,\n storagePrefix?: string\n ) {\n // Normalize config with defaults\n const normalizedConfig: WebClientConfig = {\n redirectUri: config.redirectUri,\n authServerUrl: config.authServerUrl || 'https://id.getbodhi.app/realms/bodhi',\n userScope: config.userScope || 'scope_user_user',\n logLevel: config.logLevel || 'warn',\n initParams: config.initParams,\n };\n\n super(authClientId, normalizedConfig, onStateChange, storagePrefix);\n }\n\n protected createLogger(config: WebClientConfig): Logger {\n return new Logger('WebUIClient', config.logLevel);\n }\n\n protected createExtClient(\n config: WebClientConfig,\n onStateChange: (change: StateChange) => void\n ): WindowBodhiextClient {\n return new WindowBodhiextClient(this.authClientId, config, onStateChange);\n }\n\n protected createDirectClient(\n authClientId: string,\n config: WebClientConfig,\n onStateChange: (change: StateChange) => void\n ): DirectWebClient {\n return new DirectWebClient(\n {\n authClientId,\n authServerUrl: config.authServerUrl,\n redirectUri: config.redirectUri,\n userScope: config.userScope,\n logLevel: config.logLevel,\n storagePrefix: STORAGE_PREFIXES.WEB,\n },\n onStateChange\n );\n }\n\n // ============================================================================\n // Web-specific OAuth Callback\n // ============================================================================\n async handleOAuthCallback(code: string, state: string): Promise<AuthLoggedIn> {\n // Delegate to active client based on connection mode\n if (this.connectionMode === 'direct') {\n return this.directClient.handleOAuthCallback(code, state);\n }\n return this.extClient.handleOAuthCallback(code, state);\n }\n}\n"],"names":[],"mappings":"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"}