@bodhiapp/bodhi-js 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/bodhi-browser-ext/src/types/bodhiext.d.ts +0 -1
  2. package/dist/bodhi-browser-ext/src/types/protocol.d.ts +0 -1
  3. package/dist/bodhi-js-sdk/core/src/errors.d.ts +0 -1
  4. package/dist/bodhi-js-sdk/core/src/facade-client-base.d.ts +0 -1
  5. package/dist/bodhi-js-sdk/core/src/interface.d.ts +0 -1
  6. package/dist/bodhi-js-sdk/core/src/logger.d.ts +0 -1
  7. package/dist/bodhi-js-sdk/core/src/oauth.d.ts +0 -1
  8. package/dist/bodhi-js-sdk/core/src/onboarding/config.d.ts +0 -1
  9. package/dist/bodhi-js-sdk/core/src/onboarding/modal.d.ts +0 -1
  10. package/dist/bodhi-js-sdk/core/src/onboarding/protocol-utils.d.ts +0 -1
  11. package/dist/bodhi-js-sdk/core/src/platform.d.ts +0 -1
  12. package/dist/bodhi-js-sdk/core/src/types/api.d.ts +0 -1
  13. package/dist/bodhi-js-sdk/core/src/types/callback.d.ts +0 -1
  14. package/dist/bodhi-js-sdk/core/src/types/client-state.d.ts +0 -1
  15. package/dist/bodhi-js-sdk/core/src/types/config.d.ts +0 -1
  16. package/dist/bodhi-js-sdk/core/src/types/platform.d.ts +0 -1
  17. package/dist/bodhi-js-sdk/web/src/constants.d.ts +0 -1
  18. package/dist/bodhi-js-sdk/web/src/direct-client.d.ts +0 -1
  19. package/dist/bodhi-js-sdk/web/src/ext-client.d.ts +1 -2
  20. package/dist/bodhi-js-sdk/web/src/facade-client.d.ts +0 -1
  21. package/dist/bodhi-js-sdk/web/src/index.d.ts +0 -1
  22. package/dist/bodhi-web.cjs.js +1 -1
  23. package/dist/bodhi-web.esm.d.ts +1 -0
  24. package/dist/bodhi-web.esm.js +125 -126
  25. package/dist/setup-modal/src/types/message-types.d.ts +0 -1
  26. package/dist/setup-modal/src/types/protocol.d.ts +0 -1
  27. package/dist/setup-modal/src/types/state.d.ts +0 -1
  28. package/dist/setup-modal/src/types/type-guards.d.ts +0 -1
  29. package/package.json +6 -5
@@ -1,5 +1,4 @@
1
1
  import { OpenAiApiError, PingResponse, CreateChatCompletionRequest, CreateChatCompletionResponse, CreateChatCompletionStreamResponse } from '@bodhiapp/ts-client';
2
-
3
2
  /**
4
3
  * HTTP response wrapper - body can be success type OR error type
5
4
  * Use isApiErrorResponse() to narrow the type based on status
@@ -1,6 +1,5 @@
1
1
  import { OpenAiApiError, ErrorBody } from '@bodhiapp/ts-client';
2
2
  import { ApiResponse, ServerStateInfo } from './bodhiext';
3
-
4
3
  /**
5
4
  * Validate OpenAI API error body structure
6
5
  * { error: { message: string, type: string } }
@@ -1,6 +1,5 @@
1
1
  import { ApiError, OperationError } from '../../../bodhi-browser-ext/src/types';
2
2
  import { OpenAiApiError } from '@bodhiapp/ts-client';
3
-
4
3
  /**
5
4
  * Create API error (HTTP 4xx/5xx from server)
6
5
  * Thrown for streaming responses when server returns error
@@ -3,7 +3,6 @@ import { IConnectionClient } from './interface';
3
3
  import { Logger } from './logger';
4
4
  import { BodhiClientUserPrefsManager } from './storage';
5
5
  import { ApiResponseResult, AuthLoggedIn, AuthLoggedOut, AuthState, BackendServerState, ClientState, ConnectionMode, DirectState, ExtensionState, InitParams, SerializedClientState, SerializedDirectState, SerializedExtensionState, StateChange, StateChangeCallback } from './types';
6
-
7
6
  /**
8
7
  * Base facade client with common delegation logic
9
8
  *
@@ -1,6 +1,5 @@
1
1
  import { CreateChatCompletionStreamResponse } from '@bodhiapp/ts-client';
2
2
  import { ApiResponseResult, AuthLoggedIn, AuthLoggedOut, AuthState, BackendServerState, ClientState, ConnectionMode, DirectState, ExtensionState, InitParams, StateChangeCallback } from './types';
3
-
4
3
  /**
5
4
  * ConnectionClient - Base interface for all client implementations
6
5
  *
@@ -1,5 +1,4 @@
1
1
  import { LogLevel } from './types/config';
2
-
3
2
  export declare class Logger {
4
3
  private prefix;
5
4
  private level;
@@ -1,5 +1,4 @@
1
1
  import { UserInfo } from './types';
2
-
3
2
  /**
4
3
  * Base64 URL encode a buffer (for PKCE)
5
4
  */
@@ -1,5 +1,4 @@
1
1
  import { Browser, OS } from '../../../../setup-modal/src/types';
2
-
3
2
  /**
4
3
  * Browser configurations with extension store URLs
5
4
  */
@@ -1,5 +1,4 @@
1
1
  import { MessageType, RequestMessage, ResponsePayload } from '../../../../setup-modal/src/types';
2
-
3
2
  import type * as ModalTypes from '@bodhiapp/setup-modal/types';
4
3
  /**
5
4
  * Async handler for modal requests
@@ -1,5 +1,4 @@
1
1
  import { MessageType, RequestPayload, ResponsePayload, RequestMessage, RequestId } from '../../../../setup-modal/src/types';
2
-
3
2
  /** Build fire-and-forget event message */
4
3
  export declare function buildEvent<T extends MessageType>(type: T, payload: RequestPayload<T>): {
5
4
  kind: "event";
@@ -1,5 +1,4 @@
1
1
  import { BrowserInfo, OSInfo } from './types';
2
-
3
2
  /**
4
3
  * Detects the current browser using UAParser.js
5
4
  */
@@ -1,6 +1,5 @@
1
1
  import { ApiResponse, OperationErrorResponse } from '../../../../bodhi-browser-ext/src/types';
2
2
  import { OpenAiApiError } from '@bodhiapp/ts-client';
3
-
4
3
  /**
5
4
  * Public API result type - discriminated union without protocol fields
6
5
  *
@@ -1,6 +1,5 @@
1
1
  import { ClientState } from './client-state';
2
2
  import { AuthState } from './user-info';
3
-
4
3
  /**
5
4
  * Discriminated union for state changes.
6
5
  * Allows single callback to handle both client state and auth state changes.
@@ -1,5 +1,4 @@
1
1
  import { OperationErrorResponse } from '../../../../bodhi-browser-ext/src/types';
2
-
3
2
  /**
4
3
  * Serialized direct client state for persistence
5
4
  * Stores minimal state needed to restore direct connection
@@ -1,5 +1,4 @@
1
1
  import { UserScope } from './user-info';
2
-
3
2
  /**
4
3
  * Log levels for client logging
5
4
  */
@@ -1,5 +1,4 @@
1
1
  import { BrowserType, OSType } from '../../../../setup-modal/src/types';
2
-
3
2
  /**
4
3
  * Browser detection result
5
4
  */
@@ -1,5 +1,4 @@
1
1
  import { StorageKeys } from '../../core/src/index.ts';
2
-
3
2
  export declare const POLL_INTERVAL = 500;
4
3
  export declare const POLL_TIMEOUT = 5000;
5
4
  /**
@@ -1,5 +1,4 @@
1
1
  import { DirectClientBase, AuthLoggedIn, AuthLoggedOut, DirectClientBaseConfig, StateChangeCallback } from '../../core/src/index.ts';
2
-
3
2
  /**
4
3
  * Configuration for DirectWebClient
5
4
  */
@@ -1,7 +1,6 @@
1
1
  import { ApiResponseResult, AuthLoggedIn, AuthLoggedOut, AuthState, BackendServerState, ClientState, ExtensionState, IExtensionClient, InitParams, StateChangeCallback } from '../../core/src/index.ts';
2
2
  import { CreateChatCompletionStreamResponse } from '@bodhiapp/ts-client';
3
3
  import { WebClientConfig } from './facade-client';
4
-
5
4
  export type SerializedWebExtensionState = {
6
5
  extensionId?: string;
7
6
  };
@@ -39,7 +38,7 @@ export declare class WindowBodhiextClient implements IExtensionClient {
39
38
  setStateCallback(callback: StateChangeCallback): void;
40
39
  /**
41
40
  * Ensure bodhiext is available, attempting to acquire it if not already set
42
- * @throws Error if client not initialized
41
+ * @throws OperationError if client not initialized
43
42
  */
44
43
  private ensureBodhiext;
45
44
  /**
@@ -1,7 +1,6 @@
1
1
  import { BaseFacadeClient, Logger, AuthLoggedIn, IWebUIClient, LogLevel, StateChange, StateChangeCallback, UserScope } from '../../core/src/index.ts';
2
2
  import { DirectWebClient } from './direct-client';
3
3
  import { WindowBodhiextClient } from './ext-client';
4
-
5
4
  /**
6
5
  * Configuration for WebClient OAuth
7
6
  */
@@ -1,6 +1,5 @@
1
1
  import { SerializedWebExtensionState } from './ext-client';
2
2
  import { IWebUIClient } from './interface';
3
-
4
3
  export { WebUIClient } from './facade-client';
5
4
  export type { IWebUIClient, SerializedWebExtensionState };
6
5
  export { BUILD_MODE as WEB_BUILD_MODE } from './build-info';
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=require("@bodhiapp/bodhi-js-core");class E extends s.DirectClientBase{constructor(e,t){const r=e.basePath||"/",o=s.createStoragePrefixWithBasePath(r,s.STORAGE_PREFIXES.DIRECT);super({...e,storagePrefix:o},"DirectWebClient",t),this.redirectUri=e.redirectUri}async login(){const e=await this.getAuthState();if(e.isLoggedIn)return e;const t=await this.requestResourceAccess(),r=`openid profile email roles ${this.userScope} ${t}`,o=s.generateCodeVerifier(),a=await s.generateCodeChallenge(o),i=s.generateCodeVerifier();localStorage.setItem(this.storageKeys.CODE_VERIFIER,o),localStorage.setItem(this.storageKeys.STATE,i);const n=new URL(this.authEndpoints.authorize);throw n.searchParams.set("client_id",this.authClientId),n.searchParams.set("response_type","code"),n.searchParams.set("redirect_uri",this.redirectUri),n.searchParams.set("scope",r),n.searchParams.set("code_challenge",a),n.searchParams.set("code_challenge_method","S256"),n.searchParams.set("state",i),window.location.href=n.toString(),new Error("Redirect initiated")}async handleOAuthCallback(e,t){const r=localStorage.getItem(this.storageKeys.STATE);if(!r||r!==t)throw new Error("Invalid state parameter - possible CSRF attack");await this.exchangeCodeForTokens(e),localStorage.removeItem(this.storageKeys.CODE_VERIFIER),localStorage.removeItem(this.storageKeys.STATE);const o=await this.getAuthState();if(!o.isLoggedIn)throw new Error("Login failed");const a=o;return this.setAuthState(a),a}async logout(){const e=localStorage.getItem(this.storageKeys.REFRESH_TOKEN);if(e)try{const r=new URLSearchParams({token:e,client_id:this.authClientId,token_type_hint:"refresh_token"});await fetch(this.authEndpoints.revoke,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:r})}catch(r){this.logger.warn("Token revocation failed:",r)}localStorage.removeItem(this.storageKeys.ACCESS_TOKEN),localStorage.removeItem(this.storageKeys.REFRESH_TOKEN),localStorage.removeItem(this.storageKeys.EXPIRES_AT),localStorage.removeItem(this.storageKeys.RESOURCE_SCOPE);const t={isLoggedIn:!1};return this.setAuthState(t),t}async requestResourceAccess(){const e=await this.sendApiRequest("POST","/bodhi/v1/apps/request-access",{app_client_id:this.authClientId},{},!1);if(s.isApiResultOperationError(e))throw new Error("Failed to get resource access scope from server");if(!s.isApiResultSuccess(e))throw new Error("Failed to get resource access scope from server: API error");const t=e.body.scope;return localStorage.setItem(this.storageKeys.RESOURCE_SCOPE,t),t}async exchangeCodeForTokens(e){const t=localStorage.getItem(this.storageKeys.CODE_VERIFIER);if(!t)throw new Error("Code verifier not found");const r=await fetch(this.authEndpoints.token,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",code:e,redirect_uri:this.redirectUri,client_id:this.authClientId,code_verifier:t})});if(!r.ok){const a=await r.text();throw new Error(`Token exchange failed: ${r.status} ${a}`)}const o=await r.json();if(localStorage.setItem(this.storageKeys.ACCESS_TOKEN,o.access_token),o.refresh_token&&localStorage.setItem(this.storageKeys.REFRESH_TOKEN,o.refresh_token),o.expires_in){const a=Date.now()+o.expires_in*1e3;localStorage.setItem(this.storageKeys.EXPIRES_AT,a.toString())}}async _storageGet(e){return localStorage.getItem(e)}async _storageSet(e){Object.entries(e).forEach(([t,r])=>{localStorage.setItem(t,String(r))})}async _storageRemove(e){e.forEach(t=>localStorage.removeItem(t))}_getRedirectUri(){return this.redirectUri}}const S=500,f=5e3;s.createStorageKeys(s.STORAGE_PREFIXES.WEB);function _(d="/"){const e=s.createStoragePrefixWithBasePath(d,s.STORAGE_PREFIXES.WEB);return s.createStorageKeys(e)}class p{constructor(e,t,r,o){this.state=s.EXTENSION_STATE_NOT_INITIALIZED,this.bodhiext=null,this.refreshPromise=null,this.logger=new s.Logger("WindowBodhiextClient",t.logLevel),this.authClientId=e,this.config=t,this.authEndpoints=s.createOAuthEndpoints(this.config.authServerUrl),this.onStateChange=r??s.NOOP_STATE_CALLBACK,this.storageKeys=_(o||"/")}setState(e){this.state=e,this.logger.info(`{state: ${JSON.stringify(e)}} - Setting client state`),this.onStateChange({type:"client-state",state:e})}setAuthState(e){this.onStateChange({type:"auth-state",state:e})}setStateCallback(e){this.onStateChange=e}ensureBodhiext(){if(!this.bodhiext&&window.bodhiext&&(this.logger.info("Acquiring window.bodhiext reference"),this.bodhiext=window.bodhiext),!this.bodhiext)throw new Error("Client not initialized")}async sendExtRequest(e,t){return this.ensureBodhiext(),this.bodhiext.sendExtRequest(e,t)}async sendApiRequest(e,t,r,o,a){try{this.ensureBodhiext()}catch(i){return{error:{message:i instanceof Error?i.message:String(i),type:"extension_error"}}}try{let i=o||{};if(a){const h=await this._getAccessTokenRaw();if(!h)return{error:{message:"Not authenticated. Please log in first.",type:"extension_error"}};i={...i,Authorization:`Bearer ${h}`}}return await this.bodhiext.sendApiRequest(e,t,r,i)}catch(i){const n=i==null?void 0:i.error,h=(n==null?void 0:n.message)??(i instanceof Error?i.message:String(i)),c=(n==null?void 0:n.type)||"extension_error";return{error:{message:h,type:c}}}}getState(){return this.state}isClientInitialized(){return this.state.extension==="ready"}isServerReady(){return this.isClientInitialized()&&this.state.server.status==="ready"}async init(e={}){var o,a,i,n;if(!e.testConnection&&!e.selectedConnection)return this.logger.info("No testConnection or selectedConnection, returning not-initialized state"),s.EXTENSION_STATE_NOT_INITIALIZED;if(this.bodhiext&&!e.testConnection)return this.logger.debug("Already have bodhiext handle, skipping polling"),this.state;if(!this.bodhiext){const h=e.timeoutMs??((a=(o=this.config.initParams)==null?void 0:o.extension)==null?void 0:a.timeoutMs)??f,c=e.intervalMs??((n=(i=this.config.initParams)==null?void 0:i.extension)==null?void 0:n.intervalMs)??S,l=Date.now();if(!await new Promise(g=>{const u=()=>{if(window.bodhiext){this.bodhiext=window.bodhiext,g(!0);return}if(Date.now()-l>=h){g(!1);return}setTimeout(u,c)};u()}))return this.logger.warn("Extension discovery timed out"),this.setState(s.EXTENSION_STATE_NOT_FOUND),this.state}const t=await this.bodhiext.getExtensionId();this.logger.info(`Extension discovered: ${t}`);const r={type:"extension",extension:"ready",extensionId:t,server:s.PENDING_EXTENSION_READY};if(e.testConnection)try{const h=await this.getServerState();this.setState({...r,server:h}),this.logger.info(`Server connectivity tested: ${h.status}`)}catch(h){this.logger.error("Failed to get server state:",h),this.setState({...r,server:s.BACKEND_SERVER_NOT_REACHABLE})}else this.setState(r);return this.state}async requestResourceAccess(){this.ensureBodhiext();const e=await this.bodhiext.sendApiRequest("POST","/bodhi/v1/apps/request-access",{app_client_id:this.authClientId});if(!s.isApiResultSuccess(e))throw new Error("Failed to get resource access scope: API error");const t=e.body.scope;return localStorage.setItem(this.storageKeys.RESOURCE_SCOPE,t),t}async login(){const e=await this.getAuthState();if(e.isLoggedIn)return e;this.ensureBodhiext();const t=await this.requestResourceAccess(),r=s.generateCodeVerifier(),o=await s.generateCodeChallenge(r),a=s.generateCodeVerifier();localStorage.setItem(this.storageKeys.CODE_VERIFIER,r),localStorage.setItem(this.storageKeys.STATE,a);const i=["openid","profile","email","roles",this.config.userScope,t],n=new URLSearchParams({response_type:"code",client_id:this.authClientId,redirect_uri:this.config.redirectUri,scope:i.join(" "),state:a,code_challenge:o,code_challenge_method:"S256"}),h=`${this.authEndpoints.authorize}?${n}`;return window.location.href=h,new Promise(()=>{})}async handleOAuthCallback(e,t){const r=localStorage.getItem(this.storageKeys.STATE);if(!r||r!==t)throw new Error("Invalid state parameter - possible CSRF attack");await this.exchangeCodeForTokens(e),localStorage.removeItem(this.storageKeys.CODE_VERIFIER),localStorage.removeItem(this.storageKeys.STATE);const o=await this.getAuthState();if(!o.isLoggedIn)throw new Error("Login failed");return this.setAuthState(o),o}async exchangeCodeForTokens(e){const t=localStorage.getItem(this.storageKeys.CODE_VERIFIER);if(!t)throw new Error("Code verifier not found");const r=new URLSearchParams({grant_type:"authorization_code",client_id:this.authClientId,code:e,redirect_uri:this.config.redirectUri,code_verifier:t}),o=await fetch(this.authEndpoints.token,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:r});if(!o.ok){const i=await o.text();throw new Error(`Token exchange failed: ${o.status} ${i}`)}const a=await o.json();if(!a.access_token)throw new Error("No access token received");if(localStorage.setItem(this.storageKeys.ACCESS_TOKEN,a.access_token),a.refresh_token&&localStorage.setItem(this.storageKeys.REFRESH_TOKEN,a.refresh_token),a.expires_in){const i=Date.now()+a.expires_in*1e3;localStorage.setItem(this.storageKeys.EXPIRES_AT,i.toString())}}async logout(){const e=localStorage.getItem(this.storageKeys.REFRESH_TOKEN);if(e)try{const r=new URLSearchParams({token:e,client_id:this.authClientId,token_type_hint:"refresh_token"});await fetch(this.authEndpoints.revoke,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:r})}catch(r){this.logger.warn("Token revocation failed:",r)}localStorage.removeItem(this.storageKeys.ACCESS_TOKEN),localStorage.removeItem(this.storageKeys.REFRESH_TOKEN),localStorage.removeItem(this.storageKeys.EXPIRES_AT),localStorage.removeItem(this.storageKeys.CODE_VERIFIER),localStorage.removeItem(this.storageKeys.STATE),localStorage.removeItem(this.storageKeys.RESOURCE_SCOPE);const t={isLoggedIn:!1};return this.setAuthState(t),t}async getAuthState(){const e=await this._getAccessTokenRaw();if(!e)return{isLoggedIn:!1};try{return{isLoggedIn:!0,userInfo:s.extractUserInfo(e),accessToken:e}}catch(t){return this.logger.error("Failed to parse token:",t),{isLoggedIn:!1}}}async _getAccessTokenRaw(){const e=localStorage.getItem(this.storageKeys.ACCESS_TOKEN),t=localStorage.getItem(this.storageKeys.EXPIRES_AT);if(!e)return null;if(t){const r=parseInt(t,10);if(Date.now()>=r-5*1e3){const o=localStorage.getItem(this.storageKeys.REFRESH_TOKEN);return o?this._tryRefreshToken(o):null}}return e}async _tryRefreshToken(e){if(this.refreshPromise)return this.logger.debug("Refresh already in progress, returning existing promise"),this.refreshPromise;this.refreshPromise=this._doRefreshToken(e);try{return await this.refreshPromise}finally{this.refreshPromise=null}}async _doRefreshToken(e){this.logger.debug("Refreshing access token");try{const t=await s.refreshAccessToken(this.authEndpoints.token,e,this.authClientId);if(t){this._storeRefreshedTokens(t);const r=s.extractUserInfo(t.access_token);return this.setAuthState({isLoggedIn:!0,userInfo:r,accessToken:t.access_token}),this.logger.info("Token refreshed successfully"),t.access_token}}catch(t){this.logger.warn("Token refresh failed:",t)}throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"),s.createOperationError("Access token expired and unable to refresh. Try logging out and logging in again.","token_refresh_failed")}_storeRefreshedTokens(e){const t=Date.now()+e.expires_in*1e3;localStorage.setItem(this.storageKeys.ACCESS_TOKEN,e.access_token),localStorage.setItem(this.storageKeys.EXPIRES_AT,String(t)),e.refresh_token&&localStorage.setItem(this.storageKeys.REFRESH_TOKEN,e.refresh_token)}async pingApi(){return this.sendApiRequest("GET","/ping")}async fetchModels(){return this.sendApiRequest("GET","/v1/models",void 0,void 0,!0)}async getServerState(){const e=await this.sendApiRequest("GET","/bodhi/v1/info");if(s.isApiResultOperationError(e)||!s.isApiResultSuccess(e))return s.BACKEND_SERVER_NOT_REACHABLE;const t=e.body;switch(t.status){case"ready":return{status:"ready",version:t.version||"unknown"};case"setup":return s.backendServerNotReady("setup",t.version||"unknown");case"resource-admin":return s.backendServerNotReady("resource-admin",t.version||"unknown");case"error":return s.backendServerNotReady("error",t.version||"unknown",t.error?{message:t.error.message,type:t.error.type}:s.SERVER_ERROR_CODES.SERVER_NOT_READY);default:return s.BACKEND_SERVER_NOT_REACHABLE}}async*stream(e,t,r,o,a=!0){this.ensureBodhiext();let i=o||{};if(a){const c=await this._getAccessTokenRaw();if(!c)throw new Error("Not authenticated. Please log in first.");i={...i,Authorization:`Bearer ${c}`}}const h=this.bodhiext.sendStreamRequest(e,t,r,i).getReader();try{for(;;){const{value:c,done:l}=await h.read();if(l||c!=null&&c.done)break;yield c.body}}catch(c){if(c instanceof Error){if("response"in c){const l=c;throw s.createApiError(c.message,l.response.status,l.response.body)}throw"error"in c,s.createOperationError(c.message,"extension_error")}throw c}finally{h.releaseLock()}}async*streamChat(e,t,r=!0){yield*this.stream("POST","/v1/chat/completions",{model:e,messages:[{role:"user",content:t}],stream:!0},void 0,r)}serialize(){return{extensionId:this.state.type==="extension"&&this.state.extension==="ready"?this.state.extensionId:void 0}}async debug(){return{type:"WindowBodhiextClient",state:this.state,authState:await this.getAuthState(),bodhiextAvailable:this.bodhiext!==null,authClientId:this.authClientId,authServerUrl:this.config.authServerUrl,redirectUri:this.config.redirectUri,userScope:this.config.userScope}}}class y extends s.BaseFacadeClient{constructor(e,t,r,o){const a={redirectUri:t.redirectUri,authServerUrl:t.authServerUrl||"https://id.getbodhi.app/realms/bodhi",userScope:t.userScope||"scope_user_user",basePath:t.basePath||"/",logLevel:t.logLevel||"warn",initParams:t.initParams};super(e,a,r,o,t.basePath)}createLogger(e){return new s.Logger("WebUIClient",e.logLevel)}createExtClient(e,t){return new p(this.authClientId,e,t,e.basePath)}createDirectClient(e,t,r){return new E({authClientId:e,authServerUrl:t.authServerUrl,redirectUri:t.redirectUri,userScope:t.userScope,logLevel:t.logLevel,storagePrefix:s.STORAGE_PREFIXES.WEB,basePath:t.basePath},r)}async handleOAuthCallback(e,t){return this.connectionMode==="direct"?this.directClient.handleOAuthCallback(e,t):this.extClient.handleOAuthCallback(e,t)}}const m="production";exports.WEB_BUILD_MODE=m;exports.WebUIClient=y;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=require("@bodhiapp/bodhi-js-core");class g extends s.DirectClientBase{constructor(e,t){const r=e.basePath||"/",o=s.createStoragePrefixWithBasePath(r,s.STORAGE_PREFIXES.DIRECT);super({...e,storagePrefix:o},"DirectWebClient",t),this.redirectUri=e.redirectUri}async login(){const e=await this.getAuthState();if(e.isLoggedIn)return e;const t=await this.requestResourceAccess(),r=`openid profile email roles ${this.userScope} ${t}`,o=s.generateCodeVerifier(),a=await s.generateCodeChallenge(o),i=s.generateCodeVerifier();localStorage.setItem(this.storageKeys.CODE_VERIFIER,o),localStorage.setItem(this.storageKeys.STATE,i);const c=new URL(this.authEndpoints.authorize);throw c.searchParams.set("client_id",this.authClientId),c.searchParams.set("response_type","code"),c.searchParams.set("redirect_uri",this.redirectUri),c.searchParams.set("scope",r),c.searchParams.set("code_challenge",a),c.searchParams.set("code_challenge_method","S256"),c.searchParams.set("state",i),window.location.href=c.toString(),new Error("Redirect initiated")}async handleOAuthCallback(e,t){const r=localStorage.getItem(this.storageKeys.STATE);if(!r||r!==t)throw new Error("Invalid state parameter - possible CSRF attack");await this.exchangeCodeForTokens(e),localStorage.removeItem(this.storageKeys.CODE_VERIFIER),localStorage.removeItem(this.storageKeys.STATE);const o=await this.getAuthState();if(!o.isLoggedIn)throw new Error("Login failed");const a=o;return this.setAuthState(a),a}async logout(){const e=localStorage.getItem(this.storageKeys.REFRESH_TOKEN);if(e)try{const r=new URLSearchParams({token:e,client_id:this.authClientId,token_type_hint:"refresh_token"});await fetch(this.authEndpoints.revoke,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:r})}catch(r){this.logger.warn("Token revocation failed:",r)}localStorage.removeItem(this.storageKeys.ACCESS_TOKEN),localStorage.removeItem(this.storageKeys.REFRESH_TOKEN),localStorage.removeItem(this.storageKeys.EXPIRES_AT),localStorage.removeItem(this.storageKeys.RESOURCE_SCOPE);const t={isLoggedIn:!1};return this.setAuthState(t),t}async requestResourceAccess(){const e=await this.sendApiRequest("POST","/bodhi/v1/apps/request-access",{app_client_id:this.authClientId},{},!1);if(s.isApiResultOperationError(e))throw new Error("Failed to get resource access scope from server");if(!s.isApiResultSuccess(e))throw new Error("Failed to get resource access scope from server: API error");const t=e.body.scope;return localStorage.setItem(this.storageKeys.RESOURCE_SCOPE,t),t}async exchangeCodeForTokens(e){const t=localStorage.getItem(this.storageKeys.CODE_VERIFIER);if(!t)throw new Error("Code verifier not found");const r=await fetch(this.authEndpoints.token,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",code:e,redirect_uri:this.redirectUri,client_id:this.authClientId,code_verifier:t})});if(!r.ok){const a=await r.text();throw new Error(`Token exchange failed: ${r.status} ${a}`)}const o=await r.json();if(localStorage.setItem(this.storageKeys.ACCESS_TOKEN,o.access_token),o.refresh_token&&localStorage.setItem(this.storageKeys.REFRESH_TOKEN,o.refresh_token),o.expires_in){const a=Date.now()+o.expires_in*1e3;localStorage.setItem(this.storageKeys.EXPIRES_AT,a.toString())}}async _storageGet(e){return localStorage.getItem(e)}async _storageSet(e){Object.entries(e).forEach(([t,r])=>{localStorage.setItem(t,String(r))})}async _storageRemove(e){e.forEach(t=>localStorage.removeItem(t))}_getRedirectUri(){return this.redirectUri}}const u=500,E=5e3;s.createStorageKeys(s.STORAGE_PREFIXES.WEB);function S(l="/"){const e=s.createStoragePrefixWithBasePath(l,s.STORAGE_PREFIXES.WEB);return s.createStorageKeys(e)}class f{constructor(e,t,r,o){this.state=s.EXTENSION_STATE_NOT_INITIALIZED,this.bodhiext=null,this.refreshPromise=null,this.logger=new s.Logger("WindowBodhiextClient",t.logLevel),this.authClientId=e,this.config=t,this.authEndpoints=s.createOAuthEndpoints(this.config.authServerUrl),this.onStateChange=r??s.NOOP_STATE_CALLBACK,this.storageKeys=S(o||"/")}setState(e){this.state=e,this.logger.info(`{state: ${JSON.stringify(e)}} - Setting client state`),this.onStateChange({type:"client-state",state:e})}setAuthState(e){this.onStateChange({type:"auth-state",state:e})}setStateCallback(e){this.onStateChange=e}ensureBodhiext(){if(!this.bodhiext&&window.bodhiext&&(this.logger.info("Acquiring window.bodhiext reference"),this.bodhiext=window.bodhiext),!this.bodhiext)throw s.createOperationError("Client not initialized","extension_error")}async sendExtRequest(e,t){return this.ensureBodhiext(),this.bodhiext.sendExtRequest(e,t)}async sendApiRequest(e,t,r,o,a){try{this.ensureBodhiext()}catch(i){return{error:{message:i instanceof Error?i.message:String(i),type:"extension_error"}}}try{let i=o||{};if(a){const h=await this._getAccessTokenRaw();if(!h)return{error:{message:"Not authenticated. Please log in first.",type:"extension_error"}};i={...i,Authorization:`Bearer ${h}`}}return await this.bodhiext.sendApiRequest(e,t,r,i)}catch(i){const c=i?.error,h=c?.message??(i instanceof Error?i.message:String(i)),n=c?.type||"extension_error";return{error:{message:h,type:n}}}}getState(){return this.state}isClientInitialized(){return this.state.extension==="ready"}isServerReady(){return this.isClientInitialized()&&this.state.server.status==="ready"}async init(e={}){if(!e.testConnection&&!e.selectedConnection)return this.logger.info("No testConnection or selectedConnection, returning not-initialized state"),s.EXTENSION_STATE_NOT_INITIALIZED;if(this.bodhiext&&!e.testConnection)return this.logger.debug("Already have bodhiext handle, skipping polling"),this.state;if(!this.bodhiext){const o=e.timeoutMs??this.config.initParams?.extension?.timeoutMs??E,a=e.intervalMs??this.config.initParams?.extension?.intervalMs??u,i=Date.now();if(!await new Promise(h=>{const n=()=>{if(window.bodhiext){this.bodhiext=window.bodhiext,h(!0);return}if(Date.now()-i>=o){h(!1);return}setTimeout(n,a)};n()}))return this.logger.warn("Extension discovery timed out"),this.setState(s.EXTENSION_STATE_NOT_FOUND),this.state}const t=await this.bodhiext.getExtensionId();this.logger.info(`Extension discovered: ${t}`);const r={type:"extension",extension:"ready",extensionId:t,server:s.PENDING_EXTENSION_READY};if(e.testConnection)try{const o=await this.getServerState();this.setState({...r,server:o}),this.logger.info(`Server connectivity tested: ${o.status}`)}catch(o){this.logger.error("Failed to get server state:",o),this.setState({...r,server:s.BACKEND_SERVER_NOT_REACHABLE})}else this.setState(r);return this.state}async requestResourceAccess(){this.ensureBodhiext();const e=await this.bodhiext.sendApiRequest("POST","/bodhi/v1/apps/request-access",{app_client_id:this.authClientId});if(!s.isApiResultSuccess(e))throw new Error("Failed to get resource access scope: API error");const t=e.body.scope;return localStorage.setItem(this.storageKeys.RESOURCE_SCOPE,t),t}async login(){const e=await this.getAuthState();if(e.isLoggedIn)return e;this.ensureBodhiext();const t=await this.requestResourceAccess(),r=s.generateCodeVerifier(),o=await s.generateCodeChallenge(r),a=s.generateCodeVerifier();localStorage.setItem(this.storageKeys.CODE_VERIFIER,r),localStorage.setItem(this.storageKeys.STATE,a);const i=["openid","profile","email","roles",this.config.userScope,t],c=new URLSearchParams({response_type:"code",client_id:this.authClientId,redirect_uri:this.config.redirectUri,scope:i.join(" "),state:a,code_challenge:o,code_challenge_method:"S256"}),h=`${this.authEndpoints.authorize}?${c}`;return window.location.href=h,new Promise(()=>{})}async handleOAuthCallback(e,t){const r=localStorage.getItem(this.storageKeys.STATE);if(!r||r!==t)throw new Error("Invalid state parameter - possible CSRF attack");await this.exchangeCodeForTokens(e),localStorage.removeItem(this.storageKeys.CODE_VERIFIER),localStorage.removeItem(this.storageKeys.STATE);const o=await this.getAuthState();if(!o.isLoggedIn)throw new Error("Login failed");return this.setAuthState(o),o}async exchangeCodeForTokens(e){const t=localStorage.getItem(this.storageKeys.CODE_VERIFIER);if(!t)throw new Error("Code verifier not found");const r=new URLSearchParams({grant_type:"authorization_code",client_id:this.authClientId,code:e,redirect_uri:this.config.redirectUri,code_verifier:t}),o=await fetch(this.authEndpoints.token,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:r});if(!o.ok){const i=await o.text();throw new Error(`Token exchange failed: ${o.status} ${i}`)}const a=await o.json();if(!a.access_token)throw new Error("No access token received");if(localStorage.setItem(this.storageKeys.ACCESS_TOKEN,a.access_token),a.refresh_token&&localStorage.setItem(this.storageKeys.REFRESH_TOKEN,a.refresh_token),a.expires_in){const i=Date.now()+a.expires_in*1e3;localStorage.setItem(this.storageKeys.EXPIRES_AT,i.toString())}}async logout(){const e=localStorage.getItem(this.storageKeys.REFRESH_TOKEN);if(e)try{const r=new URLSearchParams({token:e,client_id:this.authClientId,token_type_hint:"refresh_token"});await fetch(this.authEndpoints.revoke,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:r})}catch(r){this.logger.warn("Token revocation failed:",r)}localStorage.removeItem(this.storageKeys.ACCESS_TOKEN),localStorage.removeItem(this.storageKeys.REFRESH_TOKEN),localStorage.removeItem(this.storageKeys.EXPIRES_AT),localStorage.removeItem(this.storageKeys.CODE_VERIFIER),localStorage.removeItem(this.storageKeys.STATE),localStorage.removeItem(this.storageKeys.RESOURCE_SCOPE);const t={isLoggedIn:!1};return this.setAuthState(t),t}async getAuthState(){const e=await this._getAccessTokenRaw();if(!e)return{isLoggedIn:!1};try{return{isLoggedIn:!0,userInfo:s.extractUserInfo(e),accessToken:e}}catch(t){return this.logger.error("Failed to parse token:",t),{isLoggedIn:!1}}}async _getAccessTokenRaw(){const e=localStorage.getItem(this.storageKeys.ACCESS_TOKEN),t=localStorage.getItem(this.storageKeys.EXPIRES_AT);if(!e)return null;if(t){const r=parseInt(t,10);if(Date.now()>=r-5*1e3){const o=localStorage.getItem(this.storageKeys.REFRESH_TOKEN);return o?this._tryRefreshToken(o):null}}return e}async _tryRefreshToken(e){if(this.refreshPromise)return this.logger.debug("Refresh already in progress, returning existing promise"),this.refreshPromise;this.refreshPromise=this._doRefreshToken(e);try{return await this.refreshPromise}finally{this.refreshPromise=null}}async _doRefreshToken(e){this.logger.debug("Refreshing access token");try{const t=await s.refreshAccessToken(this.authEndpoints.token,e,this.authClientId);if(t){this._storeRefreshedTokens(t);const r=s.extractUserInfo(t.access_token);return this.setAuthState({isLoggedIn:!0,userInfo:r,accessToken:t.access_token}),this.logger.info("Token refreshed successfully"),t.access_token}}catch(t){this.logger.warn("Token refresh failed:",t)}throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"),s.createOperationError("Access token expired and unable to refresh. Try logging out and logging in again.","token_refresh_failed")}_storeRefreshedTokens(e){const t=Date.now()+e.expires_in*1e3;localStorage.setItem(this.storageKeys.ACCESS_TOKEN,e.access_token),localStorage.setItem(this.storageKeys.EXPIRES_AT,String(t)),e.refresh_token&&localStorage.setItem(this.storageKeys.REFRESH_TOKEN,e.refresh_token)}async pingApi(){return this.sendApiRequest("GET","/ping")}async fetchModels(){return this.sendApiRequest("GET","/v1/models",void 0,void 0,!0)}async getServerState(){const e=await this.sendApiRequest("GET","/bodhi/v1/info");if(s.isApiResultOperationError(e)||!s.isApiResultSuccess(e))return s.BACKEND_SERVER_NOT_REACHABLE;const t=e.body;switch(t.status){case"ready":return{status:"ready",version:t.version||"unknown"};case"setup":return s.backendServerNotReady("setup",t.version||"unknown");case"resource-admin":return s.backendServerNotReady("resource-admin",t.version||"unknown");case"error":return s.backendServerNotReady("error",t.version||"unknown",t.error?{message:t.error.message,type:t.error.type}:s.SERVER_ERROR_CODES.SERVER_NOT_READY);default:return s.BACKEND_SERVER_NOT_REACHABLE}}async*stream(e,t,r,o,a=!0){this.ensureBodhiext();let i=o||{};if(a){const n=await this._getAccessTokenRaw();if(!n)throw s.createOperationError("Not authenticated. Please log in first.","auth_error");i={...i,Authorization:`Bearer ${n}`}}const h=this.bodhiext.sendStreamRequest(e,t,r,i).getReader();try{for(;;){const{value:n,done:d}=await h.read();if(d||n?.done)break;yield n.body}}catch(n){if(n instanceof Error){if("response"in n){const d=n;throw s.createApiError(n.message,d.response.status,d.response.body)}throw"error"in n,s.createOperationError(n.message,"extension_error")}throw n}finally{h.releaseLock()}}async*streamChat(e,t,r=!0){yield*this.stream("POST","/v1/chat/completions",{model:e,messages:[{role:"user",content:t}],stream:!0},void 0,r)}serialize(){return{extensionId:this.state.type==="extension"&&this.state.extension==="ready"?this.state.extensionId:void 0}}async debug(){return{type:"WindowBodhiextClient",state:this.state,authState:await this.getAuthState(),bodhiextAvailable:this.bodhiext!==null,authClientId:this.authClientId,authServerUrl:this.config.authServerUrl,redirectUri:this.config.redirectUri,userScope:this.config.userScope}}}class _ extends s.BaseFacadeClient{constructor(e,t,r,o){const a={redirectUri:t.redirectUri,authServerUrl:t.authServerUrl||"https://id.getbodhi.app/realms/bodhi",userScope:t.userScope||"scope_user_user",basePath:t.basePath||"/",logLevel:t.logLevel||"warn",initParams:t.initParams};super(e,a,r,o,t.basePath)}createLogger(e){return new s.Logger("WebUIClient",e.logLevel)}createExtClient(e,t){return new f(this.authClientId,e,t,e.basePath)}createDirectClient(e,t,r){return new g({authClientId:e,authServerUrl:t.authServerUrl,redirectUri:t.redirectUri,userScope:t.userScope,logLevel:t.logLevel,storagePrefix:s.STORAGE_PREFIXES.WEB,basePath:t.basePath},r)}async handleOAuthCallback(e,t){return this.connectionMode==="direct"?this.directClient.handleOAuthCallback(e,t):this.extClient.handleOAuthCallback(e,t)}}const p="production";exports.WEB_BUILD_MODE=p;exports.WebUIClient=_;
@@ -1 +1,2 @@
1
1
  export * from './bodhi-js-sdk/web/src/index'
2
+ export {}
@@ -1,8 +1,8 @@
1
- import { DirectClientBase as x, createStoragePrefixWithBasePath as w, STORAGE_PREFIXES as u, generateCodeVerifier as g, generateCodeChallenge as I, isApiResultOperationError as T, isApiResultSuccess as f, createStorageKeys as R, EXTENSION_STATE_NOT_INITIALIZED as y, Logger as C, createOAuthEndpoints as A, NOOP_STATE_CALLBACK as k, EXTENSION_STATE_NOT_FOUND as v, PENDING_EXTENSION_READY as b, BACKEND_SERVER_NOT_REACHABLE as d, extractUserInfo as _, refreshAccessToken as P, createOperationError as S, backendServerNotReady as E, SERVER_ERROR_CODES as O, createApiError as K, BaseFacadeClient as U } from "@bodhiapp/bodhi-js-core";
2
- class L extends x {
1
+ import { DirectClientBase as R, createStoragePrefixWithBasePath as y, STORAGE_PREFIXES as S, generateCodeVerifier as u, generateCodeChallenge as _, isApiResultOperationError as w, isApiResultSuccess as f, createStorageKeys as I, EXTENSION_STATE_NOT_INITIALIZED as p, Logger as T, createOAuthEndpoints as C, NOOP_STATE_CALLBACK as x, createOperationError as h, EXTENSION_STATE_NOT_FOUND as A, PENDING_EXTENSION_READY as k, BACKEND_SERVER_NOT_REACHABLE as g, extractUserInfo as m, refreshAccessToken as v, backendServerNotReady as E, SERVER_ERROR_CODES as b, createApiError as O, BaseFacadeClient as P } from "@bodhiapp/bodhi-js-core";
2
+ class K extends R {
3
3
  constructor(e, t) {
4
- const s = e.basePath || "/", r = w(s, u.DIRECT);
5
- super({ ...e, storagePrefix: r }, "DirectWebClient", t), this.redirectUri = e.redirectUri;
4
+ const r = e.basePath || "/", s = y(r, S.DIRECT);
5
+ super({ ...e, storagePrefix: s }, "DirectWebClient", t), this.redirectUri = e.redirectUri;
6
6
  }
7
7
  // ============================================================================
8
8
  // Authentication (Browser Redirect OAuth)
@@ -11,27 +11,27 @@ class L extends x {
11
11
  const e = await this.getAuthState();
12
12
  if (e.isLoggedIn)
13
13
  return e;
14
- const t = await this.requestResourceAccess(), s = `openid profile email roles ${this.userScope} ${t}`, r = g(), i = await I(r), o = g();
15
- localStorage.setItem(this.storageKeys.CODE_VERIFIER, r), localStorage.setItem(this.storageKeys.STATE, o);
16
- const a = new URL(this.authEndpoints.authorize);
17
- throw a.searchParams.set("client_id", this.authClientId), a.searchParams.set("response_type", "code"), a.searchParams.set("redirect_uri", this.redirectUri), a.searchParams.set("scope", s), a.searchParams.set("code_challenge", i), a.searchParams.set("code_challenge_method", "S256"), a.searchParams.set("state", o), window.location.href = a.toString(), new Error("Redirect initiated");
14
+ const t = await this.requestResourceAccess(), r = `openid profile email roles ${this.userScope} ${t}`, s = u(), i = await _(s), o = u();
15
+ localStorage.setItem(this.storageKeys.CODE_VERIFIER, s), localStorage.setItem(this.storageKeys.STATE, o);
16
+ const n = new URL(this.authEndpoints.authorize);
17
+ throw n.searchParams.set("client_id", this.authClientId), n.searchParams.set("response_type", "code"), n.searchParams.set("redirect_uri", this.redirectUri), n.searchParams.set("scope", r), n.searchParams.set("code_challenge", i), n.searchParams.set("code_challenge_method", "S256"), n.searchParams.set("state", o), window.location.href = n.toString(), new Error("Redirect initiated");
18
18
  }
19
19
  async handleOAuthCallback(e, t) {
20
- const s = localStorage.getItem(this.storageKeys.STATE);
21
- if (!s || s !== t)
20
+ const r = localStorage.getItem(this.storageKeys.STATE);
21
+ if (!r || r !== t)
22
22
  throw new Error("Invalid state parameter - possible CSRF attack");
23
23
  await this.exchangeCodeForTokens(e), localStorage.removeItem(this.storageKeys.CODE_VERIFIER), localStorage.removeItem(this.storageKeys.STATE);
24
- const r = await this.getAuthState();
25
- if (!r.isLoggedIn)
24
+ const s = await this.getAuthState();
25
+ if (!s.isLoggedIn)
26
26
  throw new Error("Login failed");
27
- const i = r;
27
+ const i = s;
28
28
  return this.setAuthState(i), i;
29
29
  }
30
30
  async logout() {
31
31
  const e = localStorage.getItem(this.storageKeys.REFRESH_TOKEN);
32
32
  if (e)
33
33
  try {
34
- const s = new URLSearchParams({
34
+ const r = new URLSearchParams({
35
35
  token: e,
36
36
  client_id: this.authClientId,
37
37
  token_type_hint: "refresh_token"
@@ -41,10 +41,10 @@ class L extends x {
41
41
  headers: {
42
42
  "Content-Type": "application/x-www-form-urlencoded"
43
43
  },
44
- body: s
44
+ body: r
45
45
  });
46
- } catch (s) {
47
- this.logger.warn("Token revocation failed:", s);
46
+ } catch (r) {
47
+ this.logger.warn("Token revocation failed:", r);
48
48
  }
49
49
  localStorage.removeItem(this.storageKeys.ACCESS_TOKEN), localStorage.removeItem(this.storageKeys.REFRESH_TOKEN), localStorage.removeItem(this.storageKeys.EXPIRES_AT), localStorage.removeItem(this.storageKeys.RESOURCE_SCOPE);
50
50
  const t = {
@@ -63,7 +63,7 @@ class L extends x {
63
63
  {},
64
64
  !1
65
65
  );
66
- if (T(e))
66
+ if (w(e))
67
67
  throw new Error("Failed to get resource access scope from server");
68
68
  if (!f(e))
69
69
  throw new Error("Failed to get resource access scope from server: API error");
@@ -74,7 +74,7 @@ class L extends x {
74
74
  const t = localStorage.getItem(this.storageKeys.CODE_VERIFIER);
75
75
  if (!t)
76
76
  throw new Error("Code verifier not found");
77
- const s = await fetch(this.authEndpoints.token, {
77
+ const r = await fetch(this.authEndpoints.token, {
78
78
  method: "POST",
79
79
  headers: {
80
80
  "Content-Type": "application/x-www-form-urlencoded"
@@ -87,13 +87,13 @@ class L extends x {
87
87
  code_verifier: t
88
88
  })
89
89
  });
90
- if (!s.ok) {
91
- const i = await s.text();
92
- throw new Error(`Token exchange failed: ${s.status} ${i}`);
90
+ if (!r.ok) {
91
+ const i = await r.text();
92
+ throw new Error(`Token exchange failed: ${r.status} ${i}`);
93
93
  }
94
- const r = await s.json();
95
- if (localStorage.setItem(this.storageKeys.ACCESS_TOKEN, r.access_token), r.refresh_token && localStorage.setItem(this.storageKeys.REFRESH_TOKEN, r.refresh_token), r.expires_in) {
96
- const i = Date.now() + r.expires_in * 1e3;
94
+ const s = await r.json();
95
+ if (localStorage.setItem(this.storageKeys.ACCESS_TOKEN, s.access_token), s.refresh_token && localStorage.setItem(this.storageKeys.REFRESH_TOKEN, s.refresh_token), s.expires_in) {
96
+ const i = Date.now() + s.expires_in * 1e3;
97
97
  localStorage.setItem(this.storageKeys.EXPIRES_AT, i.toString());
98
98
  }
99
99
  }
@@ -104,8 +104,8 @@ class L extends x {
104
104
  return localStorage.getItem(e);
105
105
  }
106
106
  async _storageSet(e) {
107
- Object.entries(e).forEach(([t, s]) => {
108
- localStorage.setItem(t, String(s));
107
+ Object.entries(e).forEach(([t, r]) => {
108
+ localStorage.setItem(t, String(r));
109
109
  });
110
110
  }
111
111
  async _storageRemove(e) {
@@ -115,15 +115,15 @@ class L extends x {
115
115
  return this.redirectUri;
116
116
  }
117
117
  }
118
- const N = 500, D = 5e3;
119
- R(u.WEB);
120
- function F(l = "/") {
121
- const e = w(l, u.WEB);
122
- return R(e);
118
+ const U = 500, L = 5e3;
119
+ I(S.WEB);
120
+ function N(l = "/") {
121
+ const e = y(l, S.WEB);
122
+ return I(e);
123
123
  }
124
- class B {
125
- constructor(e, t, s, r) {
126
- this.state = y, this.bodhiext = null, this.refreshPromise = null, this.logger = new C("WindowBodhiextClient", t.logLevel), this.authClientId = e, this.config = t, this.authEndpoints = A(this.config.authServerUrl), this.onStateChange = s ?? k, this.storageKeys = F(r || "/");
124
+ class D {
125
+ constructor(e, t, r, s) {
126
+ this.state = p, this.bodhiext = null, this.refreshPromise = null, this.logger = new T("WindowBodhiextClient", t.logLevel), this.authClientId = e, this.config = t, this.authEndpoints = C(this.config.authServerUrl), this.onStateChange = r ?? x, this.storageKeys = N(s || "/");
127
127
  }
128
128
  /**
129
129
  * Set client state and notify callback
@@ -148,11 +148,11 @@ class B {
148
148
  // ============================================================================
149
149
  /**
150
150
  * Ensure bodhiext is available, attempting to acquire it if not already set
151
- * @throws Error if client not initialized
151
+ * @throws OperationError if client not initialized
152
152
  */
153
153
  ensureBodhiext() {
154
154
  if (!this.bodhiext && window.bodhiext && (this.logger.info("Acquiring window.bodhiext reference"), this.bodhiext = window.bodhiext), !this.bodhiext)
155
- throw new Error("Client not initialized");
155
+ throw h("Client not initialized", "extension_error");
156
156
  }
157
157
  /**
158
158
  * Send extension request via window.bodhiext.sendExtRequest
@@ -164,7 +164,7 @@ class B {
164
164
  * Send API message via window.bodhiext.sendApiRequest
165
165
  * Converts ApiResponse to ApiResponseResult
166
166
  */
167
- async sendApiRequest(e, t, s, r, i) {
167
+ async sendApiRequest(e, t, r, s, i) {
168
168
  try {
169
169
  this.ensureBodhiext();
170
170
  } catch (o) {
@@ -176,7 +176,7 @@ class B {
176
176
  };
177
177
  }
178
178
  try {
179
- let o = r || {};
179
+ let o = s || {};
180
180
  if (i) {
181
181
  const c = await this._getAccessTokenRaw();
182
182
  if (!c)
@@ -194,15 +194,15 @@ class B {
194
194
  return await this.bodhiext.sendApiRequest(
195
195
  e,
196
196
  t,
197
- s,
197
+ r,
198
198
  o
199
199
  );
200
200
  } catch (o) {
201
- const a = o == null ? void 0 : o.error, c = (a == null ? void 0 : a.message) ?? (o instanceof Error ? o.message : String(o)), n = (a == null ? void 0 : a.type) || "extension_error";
201
+ const n = o?.error, c = n?.message ?? (o instanceof Error ? o.message : String(o)), a = n?.type || "extension_error";
202
202
  return {
203
203
  error: {
204
204
  message: c,
205
- type: n
205
+ type: a
206
206
  }
207
207
  };
208
208
  }
@@ -227,46 +227,45 @@ class B {
227
227
  * No extensionId storage/restoration needed - window.bodhiext handle is ephemeral
228
228
  */
229
229
  async init(e = {}) {
230
- var r, i, o, a;
231
230
  if (!e.testConnection && !e.selectedConnection)
232
- return this.logger.info("No testConnection or selectedConnection, returning not-initialized state"), y;
231
+ return this.logger.info("No testConnection or selectedConnection, returning not-initialized state"), p;
233
232
  if (this.bodhiext && !e.testConnection)
234
233
  return this.logger.debug("Already have bodhiext handle, skipping polling"), this.state;
235
234
  if (!this.bodhiext) {
236
- const c = e.timeoutMs ?? ((i = (r = this.config.initParams) == null ? void 0 : r.extension) == null ? void 0 : i.timeoutMs) ?? D, n = e.intervalMs ?? ((a = (o = this.config.initParams) == null ? void 0 : o.extension) == null ? void 0 : a.intervalMs) ?? N, h = Date.now();
237
- if (!await new Promise((p) => {
238
- const m = () => {
235
+ const s = e.timeoutMs ?? this.config.initParams?.extension?.timeoutMs ?? L, i = e.intervalMs ?? this.config.initParams?.extension?.intervalMs ?? U, o = Date.now();
236
+ if (!await new Promise((c) => {
237
+ const a = () => {
239
238
  if (window.bodhiext) {
240
- this.bodhiext = window.bodhiext, p(!0);
239
+ this.bodhiext = window.bodhiext, c(!0);
241
240
  return;
242
241
  }
243
- if (Date.now() - h >= c) {
244
- p(!1);
242
+ if (Date.now() - o >= s) {
243
+ c(!1);
245
244
  return;
246
245
  }
247
- setTimeout(m, n);
246
+ setTimeout(a, i);
248
247
  };
249
- m();
248
+ a();
250
249
  }))
251
- return this.logger.warn("Extension discovery timed out"), this.setState(v), this.state;
250
+ return this.logger.warn("Extension discovery timed out"), this.setState(A), this.state;
252
251
  }
253
252
  const t = await this.bodhiext.getExtensionId();
254
253
  this.logger.info(`Extension discovered: ${t}`);
255
- const s = {
254
+ const r = {
256
255
  type: "extension",
257
256
  extension: "ready",
258
257
  extensionId: t,
259
- server: b
258
+ server: k
260
259
  };
261
260
  if (e.testConnection)
262
261
  try {
263
- const c = await this.getServerState();
264
- this.setState({ ...s, server: c }), this.logger.info(`Server connectivity tested: ${c.status}`);
265
- } catch (c) {
266
- this.logger.error("Failed to get server state:", c), this.setState({ ...s, server: d });
262
+ const s = await this.getServerState();
263
+ this.setState({ ...r, server: s }), this.logger.info(`Server connectivity tested: ${s.status}`);
264
+ } catch (s) {
265
+ this.logger.error("Failed to get server state:", s), this.setState({ ...r, server: g });
267
266
  }
268
267
  else
269
- this.setState(s);
268
+ this.setState(r);
270
269
  return this.state;
271
270
  }
272
271
  // ============================================================================
@@ -295,17 +294,17 @@ class B {
295
294
  if (e.isLoggedIn)
296
295
  return e;
297
296
  this.ensureBodhiext();
298
- const t = await this.requestResourceAccess(), s = g(), r = await I(s), i = g();
299
- localStorage.setItem(this.storageKeys.CODE_VERIFIER, s), localStorage.setItem(this.storageKeys.STATE, i);
300
- const o = ["openid", "profile", "email", "roles", this.config.userScope, t], a = new URLSearchParams({
297
+ const t = await this.requestResourceAccess(), r = u(), s = await _(r), i = u();
298
+ localStorage.setItem(this.storageKeys.CODE_VERIFIER, r), localStorage.setItem(this.storageKeys.STATE, i);
299
+ const o = ["openid", "profile", "email", "roles", this.config.userScope, t], n = new URLSearchParams({
301
300
  response_type: "code",
302
301
  client_id: this.authClientId,
303
302
  redirect_uri: this.config.redirectUri,
304
303
  scope: o.join(" "),
305
304
  state: i,
306
- code_challenge: r,
305
+ code_challenge: s,
307
306
  code_challenge_method: "S256"
308
- }), c = `${this.authEndpoints.authorize}?${a}`;
307
+ }), c = `${this.authEndpoints.authorize}?${n}`;
309
308
  return window.location.href = c, new Promise(() => {
310
309
  });
311
310
  }
@@ -315,14 +314,14 @@ class B {
315
314
  * @returns AuthLoggedIn with login state and user info
316
315
  */
317
316
  async handleOAuthCallback(e, t) {
318
- const s = localStorage.getItem(this.storageKeys.STATE);
319
- if (!s || s !== t)
317
+ const r = localStorage.getItem(this.storageKeys.STATE);
318
+ if (!r || r !== t)
320
319
  throw new Error("Invalid state parameter - possible CSRF attack");
321
320
  await this.exchangeCodeForTokens(e), localStorage.removeItem(this.storageKeys.CODE_VERIFIER), localStorage.removeItem(this.storageKeys.STATE);
322
- const r = await this.getAuthState();
323
- if (!r.isLoggedIn)
321
+ const s = await this.getAuthState();
322
+ if (!s.isLoggedIn)
324
323
  throw new Error("Login failed");
325
- return this.setAuthState(r), r;
324
+ return this.setAuthState(s), s;
326
325
  }
327
326
  /**
328
327
  * Exchange authorization code for tokens
@@ -331,24 +330,24 @@ class B {
331
330
  const t = localStorage.getItem(this.storageKeys.CODE_VERIFIER);
332
331
  if (!t)
333
332
  throw new Error("Code verifier not found");
334
- const s = new URLSearchParams({
333
+ const r = new URLSearchParams({
335
334
  grant_type: "authorization_code",
336
335
  client_id: this.authClientId,
337
336
  code: e,
338
337
  redirect_uri: this.config.redirectUri,
339
338
  code_verifier: t
340
- }), r = await fetch(this.authEndpoints.token, {
339
+ }), s = await fetch(this.authEndpoints.token, {
341
340
  method: "POST",
342
341
  headers: {
343
342
  "Content-Type": "application/x-www-form-urlencoded"
344
343
  },
345
- body: s
344
+ body: r
346
345
  });
347
- if (!r.ok) {
348
- const o = await r.text();
349
- throw new Error(`Token exchange failed: ${r.status} ${o}`);
346
+ if (!s.ok) {
347
+ const o = await s.text();
348
+ throw new Error(`Token exchange failed: ${s.status} ${o}`);
350
349
  }
351
- const i = await r.json();
350
+ const i = await s.json();
352
351
  if (!i.access_token)
353
352
  throw new Error("No access token received");
354
353
  if (localStorage.setItem(this.storageKeys.ACCESS_TOKEN, i.access_token), i.refresh_token && localStorage.setItem(this.storageKeys.REFRESH_TOKEN, i.refresh_token), i.expires_in) {
@@ -364,7 +363,7 @@ class B {
364
363
  const e = localStorage.getItem(this.storageKeys.REFRESH_TOKEN);
365
364
  if (e)
366
365
  try {
367
- const s = new URLSearchParams({
366
+ const r = new URLSearchParams({
368
367
  token: e,
369
368
  client_id: this.authClientId,
370
369
  token_type_hint: "refresh_token"
@@ -374,10 +373,10 @@ class B {
374
373
  headers: {
375
374
  "Content-Type": "application/x-www-form-urlencoded"
376
375
  },
377
- body: s
376
+ body: r
378
377
  });
379
- } catch (s) {
380
- this.logger.warn("Token revocation failed:", s);
378
+ } catch (r) {
379
+ this.logger.warn("Token revocation failed:", r);
381
380
  }
382
381
  localStorage.removeItem(this.storageKeys.ACCESS_TOKEN), localStorage.removeItem(this.storageKeys.REFRESH_TOKEN), localStorage.removeItem(this.storageKeys.EXPIRES_AT), localStorage.removeItem(this.storageKeys.CODE_VERIFIER), localStorage.removeItem(this.storageKeys.STATE), localStorage.removeItem(this.storageKeys.RESOURCE_SCOPE);
383
382
  const t = {
@@ -393,7 +392,7 @@ class B {
393
392
  if (!e)
394
393
  return { isLoggedIn: !1 };
395
394
  try {
396
- return { isLoggedIn: !0, userInfo: _(e), accessToken: e };
395
+ return { isLoggedIn: !0, userInfo: m(e), accessToken: e };
397
396
  } catch (t) {
398
397
  return this.logger.error("Failed to parse token:", t), { isLoggedIn: !1 };
399
398
  }
@@ -407,10 +406,10 @@ class B {
407
406
  if (!e)
408
407
  return null;
409
408
  if (t) {
410
- const s = parseInt(t, 10);
411
- if (Date.now() >= s - 5 * 1e3) {
412
- const r = localStorage.getItem(this.storageKeys.REFRESH_TOKEN);
413
- return r ? this._tryRefreshToken(r) : null;
409
+ const r = parseInt(t, 10);
410
+ if (Date.now() >= r - 5 * 1e3) {
411
+ const s = localStorage.getItem(this.storageKeys.REFRESH_TOKEN);
412
+ return s ? this._tryRefreshToken(s) : null;
414
413
  }
415
414
  }
416
415
  return e;
@@ -435,24 +434,24 @@ class B {
435
434
  async _doRefreshToken(e) {
436
435
  this.logger.debug("Refreshing access token");
437
436
  try {
438
- const t = await P(
437
+ const t = await v(
439
438
  this.authEndpoints.token,
440
439
  e,
441
440
  this.authClientId
442
441
  );
443
442
  if (t) {
444
443
  this._storeRefreshedTokens(t);
445
- const s = _(t.access_token);
444
+ const r = m(t.access_token);
446
445
  return this.setAuthState({
447
446
  isLoggedIn: !0,
448
- userInfo: s,
447
+ userInfo: r,
449
448
  accessToken: t.access_token
450
449
  }), this.logger.info("Token refreshed successfully"), t.access_token;
451
450
  }
452
451
  } catch (t) {
453
452
  this.logger.warn("Token refresh failed:", t);
454
453
  }
455
- throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"), S(
454
+ throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"), h(
456
455
  "Access token expired and unable to refresh. Try logging out and logging in again.",
457
456
  "token_refresh_failed"
458
457
  );
@@ -488,10 +487,10 @@ class B {
488
487
  */
489
488
  async getServerState() {
490
489
  const e = await this.sendApiRequest("GET", "/bodhi/v1/info");
491
- if (T(e))
492
- return d;
490
+ if (w(e))
491
+ return g;
493
492
  if (!f(e))
494
- return d;
493
+ return g;
495
494
  const t = e.body;
496
495
  switch (t.status) {
497
496
  case "ready":
@@ -504,45 +503,45 @@ class B {
504
503
  return E(
505
504
  "error",
506
505
  t.version || "unknown",
507
- t.error ? { message: t.error.message, type: t.error.type } : O.SERVER_NOT_READY
506
+ t.error ? { message: t.error.message, type: t.error.type } : b.SERVER_NOT_READY
508
507
  );
509
508
  default:
510
- return d;
509
+ return g;
511
510
  }
512
511
  }
513
512
  /**
514
513
  * Generic streaming via window.bodhiext.sendStreamRequest
515
514
  * Wraps ReadableStream as AsyncGenerator
516
515
  */
517
- async *stream(e, t, s, r, i = !0) {
516
+ async *stream(e, t, r, s, i = !0) {
518
517
  this.ensureBodhiext();
519
- let o = r || {};
518
+ let o = s || {};
520
519
  if (i) {
521
- const n = await this._getAccessTokenRaw();
522
- if (!n)
523
- throw new Error("Not authenticated. Please log in first.");
520
+ const a = await this._getAccessTokenRaw();
521
+ if (!a)
522
+ throw h("Not authenticated. Please log in first.", "auth_error");
524
523
  o = {
525
524
  ...o,
526
- Authorization: `Bearer ${n}`
525
+ Authorization: `Bearer ${a}`
527
526
  };
528
527
  }
529
- const c = this.bodhiext.sendStreamRequest(e, t, s, o).getReader();
528
+ const c = this.bodhiext.sendStreamRequest(e, t, r, o).getReader();
530
529
  try {
531
530
  for (; ; ) {
532
- const { value: n, done: h } = await c.read();
533
- if (h || n != null && n.done)
531
+ const { value: a, done: d } = await c.read();
532
+ if (d || a?.done)
534
533
  break;
535
- yield n.body;
534
+ yield a.body;
536
535
  }
537
- } catch (n) {
538
- if (n instanceof Error) {
539
- if ("response" in n) {
540
- const h = n;
541
- throw K(n.message, h.response.status, h.response.body);
536
+ } catch (a) {
537
+ if (a instanceof Error) {
538
+ if ("response" in a) {
539
+ const d = a;
540
+ throw O(a.message, d.response.status, d.response.body);
542
541
  }
543
- throw "error" in n ? S(n.message, "extension_error") : S(n.message, "extension_error");
542
+ throw "error" in a ? h(a.message, "extension_error") : h(a.message, "extension_error");
544
543
  }
545
- throw n;
544
+ throw a;
546
545
  } finally {
547
546
  c.releaseLock();
548
547
  }
@@ -550,7 +549,7 @@ class B {
550
549
  /**
551
550
  * Chat streaming
552
551
  */
553
- async *streamChat(e, t, s = !0) {
552
+ async *streamChat(e, t, r = !0) {
554
553
  yield* this.stream(
555
554
  "POST",
556
555
  "/v1/chat/completions",
@@ -560,7 +559,7 @@ class B {
560
559
  stream: !0
561
560
  },
562
561
  void 0,
563
- s
562
+ r
564
563
  );
565
564
  }
566
565
  /**
@@ -587,8 +586,8 @@ class B {
587
586
  };
588
587
  }
589
588
  }
590
- class W extends U {
591
- constructor(e, t, s, r) {
589
+ class B extends P {
590
+ constructor(e, t, r, s) {
592
591
  const i = {
593
592
  redirectUri: t.redirectUri,
594
593
  authServerUrl: t.authServerUrl || "https://id.getbodhi.app/realms/bodhi",
@@ -597,26 +596,26 @@ class W extends U {
597
596
  logLevel: t.logLevel || "warn",
598
597
  initParams: t.initParams
599
598
  };
600
- super(e, i, s, r, t.basePath);
599
+ super(e, i, r, s, t.basePath);
601
600
  }
602
601
  createLogger(e) {
603
- return new C("WebUIClient", e.logLevel);
602
+ return new T("WebUIClient", e.logLevel);
604
603
  }
605
604
  createExtClient(e, t) {
606
- return new B(this.authClientId, e, t, e.basePath);
605
+ return new D(this.authClientId, e, t, e.basePath);
607
606
  }
608
- createDirectClient(e, t, s) {
609
- return new L(
607
+ createDirectClient(e, t, r) {
608
+ return new K(
610
609
  {
611
610
  authClientId: e,
612
611
  authServerUrl: t.authServerUrl,
613
612
  redirectUri: t.redirectUri,
614
613
  userScope: t.userScope,
615
614
  logLevel: t.logLevel,
616
- storagePrefix: u.WEB,
615
+ storagePrefix: S.WEB,
617
616
  basePath: t.basePath
618
617
  },
619
- s
618
+ r
620
619
  );
621
620
  }
622
621
  // ============================================================================
@@ -626,8 +625,8 @@ class W extends U {
626
625
  return this.connectionMode === "direct" ? this.directClient.handleOAuthCallback(e, t) : this.extClient.handleOAuthCallback(e, t);
627
626
  }
628
627
  }
629
- const $ = "production";
628
+ const q = "production";
630
629
  export {
631
- $ as WEB_BUILD_MODE,
632
- W as WebUIClient
630
+ q as WEB_BUILD_MODE,
631
+ B as WebUIClient
633
632
  };
@@ -1,5 +1,4 @@
1
1
  import { SetupState } from './state';
2
-
3
2
  /**
4
3
  * Central registry mapping message types to their payload/response shapes
5
4
  * Single source of truth - everything else is inferred!
@@ -1,5 +1,4 @@
1
1
  import { MessageType, RequestPayload, ResponsePayload } from './message-types';
2
-
3
2
  /** Branded type for type-safe request IDs */
4
3
  export type RequestId = string & {
5
4
  readonly __brand: 'RequestId';
@@ -2,7 +2,6 @@ import { Browser, EnvState, OS } from './platform';
2
2
  import { ExtensionState } from './extension';
3
3
  import { ServerState } from './server';
4
4
  import { LnaServerState, LnaState } from './lna';
5
-
6
5
  export declare enum SetupStep {
7
6
  PLATFORM_CHECK = "platform-check",
8
7
  SERVER_SETUP = "server-setup",
@@ -2,7 +2,6 @@ import { ExtensionState, ExtensionStateNotReady, ExtensionStateReady } from './e
2
2
  import { ServerState, ServerStateError, ServerStatePending, ServerStateReachable, ServerStateReady, ServerStateUnreachable } from './server';
3
3
  import { LnaServerState, LnaServerStateError, LnaServerStatePending, LnaServerStateReady, LnaServerStateResourceAdmin, LnaServerStateSetup, LnaState, LnaStateDenied, LnaStateGranted, LnaStatePrompt, LnaStateSkipped, LnaStateUnreachable, LnaStateUnsupported } from './lna';
4
4
  import { Browser, NotSupportedBrowser, NotSupportedOS, OS, SupportedBrowser, SupportedOS } from './platform';
5
-
6
5
  export declare function isExtensionStateReady(ext: ExtensionState): ext is ExtensionStateReady;
7
6
  export declare function isExtensionStateNotReady(ext: ExtensionState): ext is ExtensionStateNotReady;
8
7
  export declare function isServerStateReady(server: ServerState): server is ServerStateReady;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bodhiapp/bodhi-js",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "Web SDK for Bodhi Browser - window.bodhiext communication",
5
5
  "type": "module",
6
6
  "main": "dist/bodhi-web.cjs.js",
@@ -34,11 +34,12 @@
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.4",
38
- "@bodhiapp/ts-client": "^0.1.7"
37
+ "@bodhiapp/bodhi-js-core": "0.0.5",
38
+ "@bodhiapp/ts-client": "0.1.8"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@eslint/js": "^9.23.0",
42
+ "@types/node": "^20.19.10",
42
43
  "@typescript-eslint/eslint-plugin": "8.28.0",
43
44
  "@typescript-eslint/parser": "8.28.0",
44
45
  "eslint": "9.32.0",
@@ -48,7 +49,7 @@
48
49
  "rimraf": "^6.0.1",
49
50
  "tslib": "^2.6.2",
50
51
  "typescript": "^5.8.3",
51
- "vite": "^5.2.0",
52
- "vite-plugin-dts": "^3.9.1"
52
+ "vite": "^7.1.12",
53
+ "vite-plugin-dts": "^4.5.4"
53
54
  }
54
55
  }