@bodhiapp/bodhi-js 0.0.15 → 0.0.17

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.
@@ -3,3 +3,9 @@
3
3
  */
4
4
  export declare const POLL_INTERVAL = 500;
5
5
  export declare const POLL_TIMEOUT = 5000;
6
+ /**
7
+ * Default API request timeout in milliseconds
8
+ * Used for API requests through window.bodhiext
9
+ * Default: 30 seconds
10
+ */
11
+ export declare const DEFAULT_API_TIMEOUT_MS = 30000;
@@ -1,4 +1,4 @@
1
- import { DirectClientBase, AuthState, LogLevel, StateChangeCallback } from '../../core/src/index.ts';
1
+ import { DirectClientBase, AuthState, LoginOptions, LogLevel, StateChangeCallback } from '../../core/src/index.ts';
2
2
  /**
3
3
  * Configuration for DirectWebClient
4
4
  */
@@ -9,6 +9,7 @@ export interface DirectWebClientConfig {
9
9
  basePath: string;
10
10
  logLevel: LogLevel;
11
11
  redirectUri: string;
12
+ apiTimeoutMs?: number;
12
13
  }
13
14
  /**
14
15
  * DirectWebClient - Web mode implementation using browser redirect OAuth
@@ -16,7 +17,7 @@ export interface DirectWebClientConfig {
16
17
  export declare class DirectWebClient extends DirectClientBase {
17
18
  private redirectUri;
18
19
  constructor(config: DirectWebClientConfig, onStateChange?: StateChangeCallback);
19
- login(): Promise<AuthState>;
20
+ login(options?: LoginOptions): Promise<AuthState>;
20
21
  handleOAuthCallback(code: string, state: string): Promise<AuthState>;
21
22
  logout(): Promise<AuthState>;
22
23
  protected exchangeCodeForTokens(code: string): Promise<void>;
@@ -1,4 +1,4 @@
1
- import { Chat, Models, Embeddings, ApiResponseResult, AuthState, BackendServerState, ClientState, ExtensionState, IExtensionClient, InitParams, LogLevel, StateChangeCallback } from '../../core/src/index.ts';
1
+ import { Chat, Models, Embeddings, ApiResponseResult, AuthState, BackendServerState, ClientState, ExtensionState, IExtensionClient, InitParams, LoginOptions, LogLevel, StateChangeCallback } from '../../core/src/index.ts';
2
2
  export type SerializedWebExtensionState = {
3
3
  extensionId?: string;
4
4
  };
@@ -12,6 +12,7 @@ export interface WindowBodhiextClientConfig {
12
12
  userScope: string;
13
13
  basePath: string;
14
14
  logLevel: LogLevel;
15
+ apiTimeoutMs?: number;
15
16
  initParams?: {
16
17
  extension?: {
17
18
  timeoutMs?: number;
@@ -38,6 +39,7 @@ export declare class WindowBodhiextClient implements IExtensionClient {
38
39
  private onStateChange;
39
40
  private refreshPromise;
40
41
  private storageKeys;
42
+ private apiTimeoutMs;
41
43
  private _chat;
42
44
  private _models;
43
45
  private _embeddings;
@@ -89,9 +91,10 @@ export declare class WindowBodhiextClient implements IExtensionClient {
89
91
  private requestResourceAccess;
90
92
  /**
91
93
  * Login via browser redirect OAuth2 + PKCE flow
94
+ * @param options - Optional login options (toolsetScopeIds, version)
92
95
  * @returns AuthState (though in practice, this redirects and never returns)
93
96
  */
94
- login(): Promise<AuthState>;
97
+ login(options?: LoginOptions): Promise<AuthState>;
95
98
  /**
96
99
  * Handle OAuth callback with authorization code
97
100
  * Should be called from callback page with extracted URL params
@@ -11,6 +11,7 @@ export interface WebClientConfig {
11
11
  userScope: string;
12
12
  basePath: string;
13
13
  logLevel: LogLevel;
14
+ apiTimeoutMs?: number;
14
15
  initParams?: {
15
16
  extension?: {
16
17
  timeoutMs?: number;
@@ -28,6 +29,7 @@ export interface WebUIClientParams {
28
29
  userScope?: UserScope;
29
30
  basePath?: string;
30
31
  logLevel?: LogLevel;
32
+ apiTimeoutMs?: number;
31
33
  initParams?: {
32
34
  extension?: {
33
35
  timeoutMs?: number;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("@bodhiapp/bodhi-js-core");class d extends r.DirectClientBase{constructor(e,t){const o=r.createStoragePrefixWithBasePath(e.basePath,r.STORAGE_PREFIXES.WEB_DIRECT),s={authClientId:e.authClientId,authServerUrl:e.authServerUrl,userScope:e.userScope,storagePrefix:o,logLevel:e.logLevel,loggerPrefix:"DirectWebClient"};super(s,t),this.redirectUri=e.redirectUri}async login(){const e=await this.getAuthState();if(e.status==="authenticated")return e;const t=await this.requestResourceAccess();if(r.isApiResultOperationError(t))throw r.createOperationError(t.error.message,t.error.type);if(r.isApiResultError(t)){const{message:n}=t.body.error;throw r.createOperationError(n,"auth_error")}if(!r.isApiResultSuccess(t))throw r.createOperationError(`Unexpected HTTP ${t.status}`,"auth_error");const o=t.body.scope;localStorage.setItem(this.storageKeys.RESOURCE_SCOPE,o);const s=`openid profile email roles ${this.userScope} ${o}`,i=r.generateCodeVerifier(),a=await r.generateCodeChallenge(i),h=r.generateCodeVerifier();localStorage.setItem(this.storageKeys.CODE_VERIFIER,i),localStorage.setItem(this.storageKeys.STATE,h);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",s),c.searchParams.set("code_challenge",a),c.searchParams.set("code_challenge_method","S256"),c.searchParams.set("state",h),window.location.href=c.toString(),new Error("Redirect initiated")}async handleOAuthCallback(e,t){const o=localStorage.getItem(this.storageKeys.STATE);if(!o||o!==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 s=await this.getAuthState();if(s.status!=="authenticated")throw new Error("Login failed");return this.setAuthState(s),s}async logout(){const e=localStorage.getItem(this.storageKeys.REFRESH_TOKEN);if(e)try{const o=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:o})}catch(o){this.logger.warn("Token revocation failed:",o)}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={status:"unauthenticated",user:null,accessToken:null,error:null};return this.setAuthState(t),t}async exchangeCodeForTokens(e){const t=localStorage.getItem(this.storageKeys.CODE_VERIFIER);if(!t)throw new Error("Code verifier not found");const o=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(!o.ok){const i=await o.text();throw new Error(`Token exchange failed: ${o.status} ${i}`)}const s=await o.json();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){const i=Date.now()+s.expires_in*1e3;localStorage.setItem(this.storageKeys.EXPIRES_AT,i.toString())}}async _storageGet(e){return localStorage.getItem(e)}async _storageSet(e){Object.entries(e).forEach(([t,o])=>{localStorage.setItem(t,String(o))})}async _storageRemove(e){e.forEach(t=>localStorage.removeItem(t))}_getRedirectUri(){return this.redirectUri}}const g=500,S=5e3;class E{constructor(e,t,o){this.state=r.EXTENSION_STATE_NOT_INITIALIZED,this.bodhiext=null,this.refreshPromise=null,this.logger=new r.Logger("WindowBodhiextClient",t.logLevel),this.authClientId=e,this.config=t,this.authEndpoints=r.createOAuthEndpoints(this.config.authServerUrl),this.onStateChange=o??r.NOOP_STATE_CALLBACK;const s=r.createStoragePrefixWithBasePath(t.basePath,r.STORAGE_PREFIXES.WEB_EXT);this.storageKeys=r.createStorageKeys(s)}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 r.createOperationError("Client not initialized","extension_error")}async sendExtRequest(e,t){return this.ensureBodhiext(),this.bodhiext.sendExtRequest(e,t)}async sendApiRequest(e,t,o,s,i){try{this.ensureBodhiext()}catch(a){return{error:{message:a instanceof Error?a.message:String(a),type:"extension_error"}}}try{let a=s||{};if(i){const c=await this._getAccessTokenRaw();if(!c)return{error:{message:"Not authenticated. Please log in first.",type:"extension_error"}};a={...a,Authorization:`Bearer ${c}`}}return await this.bodhiext.sendApiRequest(e,t,o,a)}catch(a){const h=a?.error,c=h?.message??(a instanceof Error?a.message:String(a)),n=h?.type||"extension_error";return{error:{message:c,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"),r.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 s=e.timeoutMs??this.config.initParams?.extension?.timeoutMs??S,i=e.intervalMs??this.config.initParams?.extension?.intervalMs??g,a=Date.now();if(!await new Promise(c=>{const n=()=>{if(window.bodhiext){this.bodhiext=window.bodhiext,c(!0);return}if(Date.now()-a>=s){c(!1);return}setTimeout(n,i)};n()}))return this.logger.warn("Extension discovery timed out"),this.setState(r.EXTENSION_STATE_NOT_FOUND),this.state}const t=await this.bodhiext.getExtensionId();this.logger.info(`Extension discovered: ${t}`);const o={type:"extension",extension:"ready",extensionId:t,server:r.PENDING_EXTENSION_READY};if(e.testConnection)try{const s=await this.getServerState();this.setState({...o,server:s}),this.logger.info(`Server connectivity tested: ${s.status}`)}catch(s){this.logger.error("Failed to get server state:",s),this.setState({...o,server:r.BACKEND_SERVER_NOT_REACHABLE})}else this.setState(o);return this.state}async requestResourceAccess(){return this.ensureBodhiext(),this.bodhiext.sendApiRequest("POST","/bodhi/v1/apps/request-access",{app_client_id:this.authClientId})}async login(){const e=await this.getAuthState();if(e.status==="authenticated")return e;this.ensureBodhiext();const t=await this.requestResourceAccess();if(r.isApiResultOperationError(t))throw r.createOperationError(t.error.message,t.error.type);if(r.isApiResultError(t)){const{message:l}=t.body.error;throw r.createOperationError(l,"auth_error")}if(!r.isApiResultSuccess(t))throw r.createOperationError(`Unexpected HTTP ${t.status}`,"auth_error");const o=t.body.scope;localStorage.setItem(this.storageKeys.RESOURCE_SCOPE,o);const s=r.generateCodeVerifier(),i=await r.generateCodeChallenge(s),a=r.generateCodeVerifier();localStorage.setItem(this.storageKeys.CODE_VERIFIER,s),localStorage.setItem(this.storageKeys.STATE,a);const h=["openid","profile","email","roles",this.config.userScope,o],c=new URLSearchParams({response_type:"code",client_id:this.authClientId,redirect_uri:this.config.redirectUri,scope:h.join(" "),state:a,code_challenge:i,code_challenge_method:"S256"}),n=`${this.authEndpoints.authorize}?${c}`;return window.location.href=n,new Promise(()=>{})}async handleOAuthCallback(e,t){const o=localStorage.getItem(this.storageKeys.STATE);if(!o||o!==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 s=await this.getAuthState();if(s.status!=="authenticated")throw new Error("Login failed");return this.setAuthState(s),s}async exchangeCodeForTokens(e){const t=localStorage.getItem(this.storageKeys.CODE_VERIFIER);if(!t)throw new Error("Code verifier not found");const o=new URLSearchParams({grant_type:"authorization_code",client_id:this.authClientId,code:e,redirect_uri:this.config.redirectUri,code_verifier:t}),s=await fetch(this.authEndpoints.token,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:o});if(!s.ok){const a=await s.text();throw new Error(`Token exchange failed: ${s.status} ${a}`)}const i=await s.json();if(!i.access_token)throw new Error("No access token received");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){const a=Date.now()+i.expires_in*1e3;localStorage.setItem(this.storageKeys.EXPIRES_AT,a.toString())}}async logout(){const e=localStorage.getItem(this.storageKeys.REFRESH_TOKEN);if(e)try{const o=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:o})}catch(o){this.logger.warn("Token revocation failed:",o)}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={status:"unauthenticated",user:null,accessToken:null,error:null};return this.setAuthState(t),t}async getAuthState(){const e=await this._getAccessTokenRaw();if(!e)return{status:"unauthenticated",user:null,accessToken:null,error:null};try{return{status:"authenticated",user:r.extractUserInfo(e),accessToken:e,error:null}}catch(t){return this.logger.error("Failed to parse token:",t),{status:"unauthenticated",user:null,accessToken:null,error:null}}}async _getAccessTokenRaw(){const e=localStorage.getItem(this.storageKeys.ACCESS_TOKEN),t=localStorage.getItem(this.storageKeys.EXPIRES_AT);if(!e)return null;if(t){const o=parseInt(t,10);if(Date.now()>=o-5*1e3){const s=localStorage.getItem(this.storageKeys.REFRESH_TOKEN);return s?this._tryRefreshToken(s):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 r.refreshAccessToken(this.authEndpoints.token,e,this.authClientId);if(t.success){this._storeRefreshedTokens(t.tokens);const o=r.extractUserInfo(t.tokens.access_token);return this.setAuthState({status:"authenticated",user:o,accessToken:t.tokens.access_token,error:null}),this.logger.info("Token refreshed successfully"),t.tokens.access_token}if(t.error==="invalid_grant")return this.logger.warn("Refresh token expired or revoked, clearing tokens and logging out"),this.clearAuthStorage(),this.setAuthState({status:"unauthenticated",user:null,accessToken:null,error:null}),null}catch(t){this.logger.warn("Token refresh failed:",t)}throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"),r.createOperationError("Access token expired and unable to refresh. Try logging out and logging in again.","token_refresh_failed")}clearAuthStorage(){localStorage.removeItem(this.storageKeys.ACCESS_TOKEN),localStorage.removeItem(this.storageKeys.REFRESH_TOKEN),localStorage.removeItem(this.storageKeys.EXPIRES_AT),localStorage.removeItem(this.storageKeys.RESOURCE_SCOPE)}_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 getServerState(){const e=await this.sendApiRequest("GET","/bodhi/v1/info");if(r.isApiResultOperationError(e)||!r.isApiResultSuccess(e))return r.BACKEND_SERVER_NOT_REACHABLE;const t=e.body;switch(t.status){case"ready":return{status:"ready",version:t.version||"unknown",error:null};case"setup":return r.backendServerNotReady("setup",t.version||"unknown");case"resource-admin":return r.backendServerNotReady("resource-admin",t.version||"unknown");case"error":return r.backendServerNotReady("error",t.version||"unknown",t.error?{message:t.error.message,type:t.error.type}:r.SERVER_ERROR_CODES.SERVER_NOT_READY);default:return r.BACKEND_SERVER_NOT_REACHABLE}}async*stream(e,t,o,s,i=!0){this.ensureBodhiext();let a=s||{};if(i){const n=await this._getAccessTokenRaw();if(!n)throw r.createOperationError("Not authenticated. Please log in first.","auth_error");a={...a,Authorization:`Bearer ${n}`}}const c=this.bodhiext.sendStreamRequest(e,t,o,a).getReader();try{for(;;){const{value:n,done:l}=await c.read();if(l||n?.done)break;yield n.body}}catch(n){if(n instanceof Error){if("response"in n){const l=n;throw r.createApiError(n.message,l.response.status,l.response.body)}throw"error"in n,r.createOperationError(n.message,"extension_error")}throw n}finally{c.releaseLock()}}get chat(){return this._chat??=new r.Chat(this)}get models(){return this._models??=new r.Models(this)}get embeddings(){return this._embeddings??=new r.Embeddings(this)}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}}}function f(u){if(typeof window>"u")throw new Error("redirectUri required in non-browser environment");const e=u==="/"?"":u.replace(/\/$/,"");return`${window.location.origin}${e}/callback`}class _ extends r.BaseFacadeClient{constructor(e,t,o){const s=t||{},i={basePath:s.basePath||"/",redirectUri:s.redirectUri||f(s.basePath||"/"),authServerUrl:s.authServerUrl||"https://id.getbodhi.app/realms/bodhi",userScope:s.userScope||"scope_user_user",logLevel:s.logLevel||"warn",initParams:s.initParams};super(e,i,o)}createLogger(e){return new r.Logger("WebUIClient",e.logLevel)}createStoragePrefix(e){return r.createStoragePrefixWithBasePath(e.basePath,r.STORAGE_PREFIXES.WEB)}createExtClient(e,t){return new E(this.authClientId,{authServerUrl:e.authServerUrl,redirectUri:e.redirectUri,userScope:e.userScope,basePath:e.basePath,logLevel:e.logLevel,initParams:e.initParams},t)}createDirectClient(e,t,o){return new d({authClientId:e,authServerUrl:t.authServerUrl,redirectUri:t.redirectUri,userScope:t.userScope,logLevel:t.logLevel,basePath:t.basePath},o)}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
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=require("@bodhiapp/bodhi-js-core");class p extends s.DirectClientBase{constructor(e,t){const r=s.createStoragePrefixWithBasePath(e.basePath,s.STORAGE_PREFIXES.WEB_DIRECT),o={authClientId:e.authClientId,authServerUrl:e.authServerUrl,userScope:e.userScope,storagePrefix:r,logLevel:e.logLevel,loggerPrefix:"DirectWebClient",apiTimeoutMs:e.apiTimeoutMs};super(o,t),this.redirectUri=e.redirectUri}async login(e){const t=await this.getAuthState();if(t.status==="authenticated")return t;const r=await this.requestResourceAccess(e?.toolsetScopeIds,e?.version);if(s.isApiResultOperationError(r))throw s.createOperationError(r.error.message,r.error.type);if(s.isApiResultError(r)){const{message:S}=r.body.error;throw s.createOperationError(S,"auth_error")}if(!s.isApiResultSuccess(r))throw s.createOperationError(`Unexpected HTTP ${r.status}`,"auth_error");const o=r.body.scope;localStorage.setItem(this.storageKeys.RESOURCE_SCOPE,o);const n=r.body.toolsets||[],a=s.getMissingToolsetScopeIds(e?.toolsetScopeIds,n);if(a.length>0)throw s.createOperationError(`toolsetScopeIds not received back from request-access call: [${a.join(", ")}], check developer console on configuring the toolset scopes correctly`,"auth_error");const h=s.getRequestedToolsetScopes(e?.toolsetScopeIds,n),c=`openid profile email roles ${this.userScope} ${o} ${h}`.trim(),i=s.generateCodeVerifier(),u=await s.generateCodeChallenge(i),g=s.generateCodeVerifier();localStorage.setItem(this.storageKeys.CODE_VERIFIER,i),localStorage.setItem(this.storageKeys.STATE,g);const l=new URL(this.authEndpoints.authorize);throw l.searchParams.set("client_id",this.authClientId),l.searchParams.set("response_type","code"),l.searchParams.set("redirect_uri",this.redirectUri),l.searchParams.set("scope",c),l.searchParams.set("code_challenge",u),l.searchParams.set("code_challenge_method","S256"),l.searchParams.set("state",g),window.location.href=l.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.status!=="authenticated")throw new Error("Login failed");return this.setAuthState(o),o}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={status:"unauthenticated",user:null,accessToken:null,error:null};return this.setAuthState(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 n=await r.text();throw new Error(`Token exchange failed: ${r.status} ${n}`)}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 n=Date.now()+o.expires_in*1e3;localStorage.setItem(this.storageKeys.EXPIRES_AT,n.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 m=500,_=5e3,f=3e4;class y{constructor(e,t,r){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;const o=s.createStoragePrefixWithBasePath(t.basePath,s.STORAGE_PREFIXES.WEB_EXT);this.storageKeys=s.createStorageKeys(o),this.apiTimeoutMs=t.apiTimeoutMs??f}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,n){try{this.ensureBodhiext()}catch(a){return{error:{message:a instanceof Error?a.message:String(a),type:"extension_error"}}}try{const a=new Promise((c,i)=>setTimeout(()=>i(new Error(`[bodhi-js-sdk/web] network timeout: api request not completed within configured/default timeout of ${this.apiTimeoutMs}ms`)),this.apiTimeoutMs)),h=(async()=>{let c=o||{};if(n){const i=await this._getAccessTokenRaw();if(!i)return{error:{message:"Not authenticated. Please log in first.",type:"extension_error"}};c={...c,Authorization:`Bearer ${i}`}}return this.bodhiext.sendApiRequest(e,t,r,c)})();return await Promise.race([h,a])}catch(a){const h=a?.error,c=h?.message??(a instanceof Error?a.message:String(a)),i=h?.type||"network_error";return{error:{message:c,type:i}}}}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??_,n=e.intervalMs??this.config.initParams?.extension?.intervalMs??m,a=Date.now();if(!await new Promise(c=>{const i=()=>{if(window.bodhiext){this.bodhiext=window.bodhiext,c(!0);return}if(Date.now()-a>=o){c(!1);return}setTimeout(i,n)};i()}))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(e,t){this.ensureBodhiext();const r={app_client_id:this.authClientId,...e&&{toolset_scope_ids:e},...t&&{version:t}};return this.bodhiext.sendApiRequest("POST","/bodhi/v1/apps/request-access",r)}async login(e){const t=await this.getAuthState();if(t.status==="authenticated")return t;this.ensureBodhiext();const r=await this.requestResourceAccess(e?.toolsetScopeIds,e?.version);if(s.isApiResultOperationError(r))throw s.createOperationError(r.error.message,r.error.type);if(s.isApiResultError(r)){const{message:E}=r.body.error;throw s.createOperationError(E,"auth_error")}if(!s.isApiResultSuccess(r))throw s.createOperationError(`Unexpected HTTP ${r.status}`,"auth_error");const o=r.body.scope;localStorage.setItem(this.storageKeys.RESOURCE_SCOPE,o);const n=r.body.toolsets||[],a=s.getMissingToolsetScopeIds(e?.toolsetScopeIds,n);if(a.length>0)throw s.createOperationError(`toolsetScopeIds not received back from request-access call: [${a.join(", ")}], check developer console on configuring the toolset scopes correctly`,"auth_error");const h=s.getRequestedToolsetScopes(e?.toolsetScopeIds,n),c=s.generateCodeVerifier(),i=await s.generateCodeChallenge(c),u=s.generateCodeVerifier();localStorage.setItem(this.storageKeys.CODE_VERIFIER,c),localStorage.setItem(this.storageKeys.STATE,u);const g=["openid","profile","email","roles",this.config.userScope,o,...h?h.split(" "):[]],l=new URLSearchParams({response_type:"code",client_id:this.authClientId,redirect_uri:this.config.redirectUri,scope:g.join(" "),state:u,code_challenge:i,code_challenge_method:"S256"}),S=`${this.authEndpoints.authorize}?${l}`;return window.location.href=S,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.status!=="authenticated")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 a=await o.text();throw new Error(`Token exchange failed: ${o.status} ${a}`)}const n=await o.json();if(!n.access_token)throw new Error("No access token received");if(localStorage.setItem(this.storageKeys.ACCESS_TOKEN,n.access_token),n.refresh_token&&localStorage.setItem(this.storageKeys.REFRESH_TOKEN,n.refresh_token),n.expires_in){const a=Date.now()+n.expires_in*1e3;localStorage.setItem(this.storageKeys.EXPIRES_AT,a.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={status:"unauthenticated",user:null,accessToken:null,error:null};return this.setAuthState(t),t}async getAuthState(){const e=await this._getAccessTokenRaw();if(!e)return{status:"unauthenticated",user:null,accessToken:null,error:null};try{return{status:"authenticated",user:s.extractUserInfo(e),accessToken:e,error:null}}catch(t){return this.logger.error("Failed to parse token:",t),{status:"unauthenticated",user:null,accessToken:null,error:null}}}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.success){this._storeRefreshedTokens(t.tokens);const r=s.extractUserInfo(t.tokens.access_token);return this.setAuthState({status:"authenticated",user:r,accessToken:t.tokens.access_token,error:null}),this.logger.info("Token refreshed successfully"),t.tokens.access_token}if(t.error==="invalid_grant")return this.logger.warn("Refresh token expired or revoked, clearing tokens and logging out"),this.clearAuthStorage(),this.setAuthState({status:"unauthenticated",user:null,accessToken:null,error:null}),null}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")}clearAuthStorage(){localStorage.removeItem(this.storageKeys.ACCESS_TOKEN),localStorage.removeItem(this.storageKeys.REFRESH_TOKEN),localStorage.removeItem(this.storageKeys.EXPIRES_AT),localStorage.removeItem(this.storageKeys.RESOURCE_SCOPE)}_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 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",error:null};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,n=!0){this.ensureBodhiext();let a=o||{};if(n){const i=await this._getAccessTokenRaw();if(!i)throw s.createOperationError("Not authenticated. Please log in first.","auth_error");a={...a,Authorization:`Bearer ${i}`}}const c=this.bodhiext.sendStreamRequest(e,t,r,a).getReader();try{for(;;){const{value:i,done:u}=await c.read();if(u||i?.done)break;yield i.body}}catch(i){if(i instanceof Error){if("response"in i){const u=i;throw s.createApiError(i.message,u.response.status,u.response.body)}throw"error"in i,s.createOperationError(i.message,"extension_error")}throw i}finally{c.releaseLock()}}get chat(){return this._chat??=new s.Chat(this)}get models(){return this._models??=new s.Models(this)}get embeddings(){return this._embeddings??=new s.Embeddings(this)}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}}}function T(d){if(typeof window>"u")throw new Error("redirectUri required in non-browser environment");const e=d==="/"?"":d.replace(/\/$/,"");return`${window.location.origin}${e}/callback`}class w extends s.BaseFacadeClient{constructor(e,t,r){const o=t||{},n={basePath:o.basePath||"/",redirectUri:o.redirectUri||T(o.basePath||"/"),authServerUrl:o.authServerUrl||"https://id.getbodhi.app/realms/bodhi",userScope:o.userScope||"scope_user_user",logLevel:o.logLevel||"warn",apiTimeoutMs:o.apiTimeoutMs,initParams:o.initParams};super(e,n,r)}createLogger(e){return new s.Logger("WebUIClient",e.logLevel)}createStoragePrefix(e){return s.createStoragePrefixWithBasePath(e.basePath,s.STORAGE_PREFIXES.WEB)}createExtClient(e,t){return new y(this.authClientId,{authServerUrl:e.authServerUrl,redirectUri:e.redirectUri,userScope:e.userScope,basePath:e.basePath,logLevel:e.logLevel,apiTimeoutMs:e.apiTimeoutMs,initParams:e.initParams},t)}createDirectClient(e,t,r){return new p({authClientId:e,authServerUrl:t.authServerUrl,redirectUri:t.redirectUri,userScope:t.userScope,logLevel:t.logLevel,basePath:t.basePath,apiTimeoutMs:t.apiTimeoutMs},r)}async handleOAuthCallback(e,t){return this.connectionMode==="direct"?this.directClient.handleOAuthCallback(e,t):this.extClient.handleOAuthCallback(e,t)}}const I="production";exports.WEB_BUILD_MODE=I;exports.WebUIClient=w;
@@ -1,57 +1,64 @@
1
- import { DirectClientBase as R, createStoragePrefixWithBasePath as m, STORAGE_PREFIXES as _, isApiResultOperationError as E, createOperationError as h, isApiResultError as p, isApiResultSuccess as f, generateCodeVerifier as g, generateCodeChallenge as T, EXTENSION_STATE_NOT_INITIALIZED as w, Logger as I, createOAuthEndpoints as C, NOOP_STATE_CALLBACK as k, createStorageKeys as x, EXTENSION_STATE_NOT_FOUND as A, PENDING_EXTENSION_READY as v, BACKEND_SERVER_NOT_REACHABLE as d, extractUserInfo as y, refreshAccessToken as b, backendServerNotReady as S, SERVER_ERROR_CODES as O, createApiError as P, Chat as K, Models as U, Embeddings as N, BaseFacadeClient as L } from "@bodhiapp/bodhi-js-core";
2
- class D extends R {
1
+ import { DirectClientBase as b, createStoragePrefixWithBasePath as w, STORAGE_PREFIXES as y, isApiResultOperationError as f, createOperationError as c, isApiResultError as R, isApiResultSuccess as _, getMissingToolsetScopeIds as C, getRequestedToolsetScopes as k, generateCodeVerifier as E, generateCodeChallenge as x, EXTENSION_STATE_NOT_INITIALIZED as T, Logger as v, createOAuthEndpoints as P, NOOP_STATE_CALLBACK as O, createStorageKeys as K, EXTENSION_STATE_NOT_FOUND as U, PENDING_EXTENSION_READY as N, BACKEND_SERVER_NOT_REACHABLE as S, extractUserInfo as I, refreshAccessToken as L, backendServerNotReady as p, SERVER_ERROR_CODES as D, createApiError as F, Chat as M, Models as B, Embeddings as $, BaseFacadeClient as q } from "@bodhiapp/bodhi-js-core";
2
+ class V extends b {
3
3
  constructor(e, t) {
4
- const s = m(
4
+ const r = w(
5
5
  e.basePath,
6
- _.WEB_DIRECT
7
- ), r = {
6
+ y.WEB_DIRECT
7
+ ), s = {
8
8
  authClientId: e.authClientId,
9
9
  authServerUrl: e.authServerUrl,
10
10
  userScope: e.userScope,
11
- storagePrefix: s,
11
+ storagePrefix: r,
12
12
  logLevel: e.logLevel,
13
- loggerPrefix: "DirectWebClient"
13
+ loggerPrefix: "DirectWebClient",
14
+ apiTimeoutMs: e.apiTimeoutMs
14
15
  };
15
- super(r, t), this.redirectUri = e.redirectUri;
16
+ super(s, t), this.redirectUri = e.redirectUri;
16
17
  }
17
18
  // ============================================================================
18
19
  // Authentication (Browser Redirect OAuth)
19
20
  // ============================================================================
20
- async login() {
21
- const e = await this.getAuthState();
22
- if (e.status === "authenticated")
23
- return e;
24
- const t = await this.requestResourceAccess();
25
- if (E(t))
26
- throw h(t.error.message, t.error.type);
27
- if (p(t)) {
28
- const { message: i } = t.body.error;
29
- throw h(i, "auth_error");
21
+ async login(e) {
22
+ const t = await this.getAuthState();
23
+ if (t.status === "authenticated")
24
+ return t;
25
+ const r = await this.requestResourceAccess(e?.toolsetScopeIds, e?.version);
26
+ if (f(r))
27
+ throw c(r.error.message, r.error.type);
28
+ if (R(r)) {
29
+ const { message: m } = r.body.error;
30
+ throw c(m, "auth_error");
30
31
  }
31
- if (!f(t))
32
- throw h(`Unexpected HTTP ${t.status}`, "auth_error");
33
- const s = t.body.scope;
32
+ if (!_(r))
33
+ throw c(`Unexpected HTTP ${r.status}`, "auth_error");
34
+ const s = r.body.scope;
34
35
  localStorage.setItem(this.storageKeys.RESOURCE_SCOPE, s);
35
- const r = `openid profile email roles ${this.userScope} ${s}`, a = g(), o = await T(a), c = g();
36
- localStorage.setItem(this.storageKeys.CODE_VERIFIER, a), localStorage.setItem(this.storageKeys.STATE, c);
37
- const n = new URL(this.authEndpoints.authorize);
38
- 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", o), n.searchParams.set("code_challenge_method", "S256"), n.searchParams.set("state", c), window.location.href = n.toString(), new Error("Redirect initiated");
36
+ const a = r.body.toolsets || [], i = C(e?.toolsetScopeIds, a);
37
+ if (i.length > 0)
38
+ throw c(
39
+ `toolsetScopeIds not received back from request-access call: [${i.join(", ")}], check developer console on configuring the toolset scopes correctly`,
40
+ "auth_error"
41
+ );
42
+ const h = k(e?.toolsetScopeIds, a), n = `openid profile email roles ${this.userScope} ${s} ${h}`.trim(), o = E(), u = await x(o), g = E();
43
+ localStorage.setItem(this.storageKeys.CODE_VERIFIER, o), localStorage.setItem(this.storageKeys.STATE, g);
44
+ const l = new URL(this.authEndpoints.authorize);
45
+ throw l.searchParams.set("client_id", this.authClientId), l.searchParams.set("response_type", "code"), l.searchParams.set("redirect_uri", this.redirectUri), l.searchParams.set("scope", n), l.searchParams.set("code_challenge", u), l.searchParams.set("code_challenge_method", "S256"), l.searchParams.set("state", g), window.location.href = l.toString(), new Error("Redirect initiated");
39
46
  }
40
47
  async handleOAuthCallback(e, t) {
41
- const s = localStorage.getItem(this.storageKeys.STATE);
42
- if (!s || s !== t)
48
+ const r = localStorage.getItem(this.storageKeys.STATE);
49
+ if (!r || r !== t)
43
50
  throw new Error("Invalid state parameter - possible CSRF attack");
44
51
  await this.exchangeCodeForTokens(e), localStorage.removeItem(this.storageKeys.CODE_VERIFIER), localStorage.removeItem(this.storageKeys.STATE);
45
- const r = await this.getAuthState();
46
- if (r.status !== "authenticated")
52
+ const s = await this.getAuthState();
53
+ if (s.status !== "authenticated")
47
54
  throw new Error("Login failed");
48
- return this.setAuthState(r), r;
55
+ return this.setAuthState(s), s;
49
56
  }
50
57
  async logout() {
51
58
  const e = localStorage.getItem(this.storageKeys.REFRESH_TOKEN);
52
59
  if (e)
53
60
  try {
54
- const s = new URLSearchParams({
61
+ const r = new URLSearchParams({
55
62
  token: e,
56
63
  client_id: this.authClientId,
57
64
  token_type_hint: "refresh_token"
@@ -61,10 +68,10 @@ class D extends R {
61
68
  headers: {
62
69
  "Content-Type": "application/x-www-form-urlencoded"
63
70
  },
64
- body: s
71
+ body: r
65
72
  });
66
- } catch (s) {
67
- this.logger.warn("Token revocation failed:", s);
73
+ } catch (r) {
74
+ this.logger.warn("Token revocation failed:", r);
68
75
  }
69
76
  localStorage.removeItem(this.storageKeys.ACCESS_TOKEN), localStorage.removeItem(this.storageKeys.REFRESH_TOKEN), localStorage.removeItem(this.storageKeys.EXPIRES_AT), localStorage.removeItem(this.storageKeys.RESOURCE_SCOPE);
70
77
  const t = {
@@ -82,7 +89,7 @@ class D extends R {
82
89
  const t = localStorage.getItem(this.storageKeys.CODE_VERIFIER);
83
90
  if (!t)
84
91
  throw new Error("Code verifier not found");
85
- const s = await fetch(this.authEndpoints.token, {
92
+ const r = await fetch(this.authEndpoints.token, {
86
93
  method: "POST",
87
94
  headers: {
88
95
  "Content-Type": "application/x-www-form-urlencoded"
@@ -95,13 +102,13 @@ class D extends R {
95
102
  code_verifier: t
96
103
  })
97
104
  });
98
- if (!s.ok) {
99
- const a = await s.text();
100
- throw new Error(`Token exchange failed: ${s.status} ${a}`);
105
+ if (!r.ok) {
106
+ const a = await r.text();
107
+ throw new Error(`Token exchange failed: ${r.status} ${a}`);
101
108
  }
102
- const r = await s.json();
103
- 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) {
104
- const a = Date.now() + r.expires_in * 1e3;
109
+ const s = await r.json();
110
+ 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) {
111
+ const a = Date.now() + s.expires_in * 1e3;
105
112
  localStorage.setItem(this.storageKeys.EXPIRES_AT, a.toString());
106
113
  }
107
114
  }
@@ -112,8 +119,8 @@ class D extends R {
112
119
  return localStorage.getItem(e);
113
120
  }
114
121
  async _storageSet(e) {
115
- Object.entries(e).forEach(([t, s]) => {
116
- localStorage.setItem(t, String(s));
122
+ Object.entries(e).forEach(([t, r]) => {
123
+ localStorage.setItem(t, String(r));
117
124
  });
118
125
  }
119
126
  async _storageRemove(e) {
@@ -123,12 +130,12 @@ class D extends R {
123
130
  return this.redirectUri;
124
131
  }
125
132
  }
126
- const F = 500, B = 5e3;
127
- class $ {
128
- constructor(e, t, s) {
129
- this.state = w, this.bodhiext = null, this.refreshPromise = null, this.logger = new I("WindowBodhiextClient", t.logLevel), this.authClientId = e, this.config = t, this.authEndpoints = C(this.config.authServerUrl), this.onStateChange = s ?? k;
130
- const r = m(t.basePath, _.WEB_EXT);
131
- this.storageKeys = x(r);
133
+ const H = 500, z = 5e3, W = 3e4;
134
+ class X {
135
+ constructor(e, t, r) {
136
+ this.state = T, this.bodhiext = null, this.refreshPromise = null, this.logger = new v("WindowBodhiextClient", t.logLevel), this.authClientId = e, this.config = t, this.authEndpoints = P(this.config.authServerUrl), this.onStateChange = r ?? O;
137
+ const s = w(t.basePath, y.WEB_EXT);
138
+ this.storageKeys = K(s), this.apiTimeoutMs = t.apiTimeoutMs ?? W;
132
139
  }
133
140
  /**
134
141
  * Set client state and notify callback
@@ -157,7 +164,7 @@ class $ {
157
164
  */
158
165
  ensureBodhiext() {
159
166
  if (!this.bodhiext && window.bodhiext && (this.logger.info("Acquiring window.bodhiext reference"), this.bodhiext = window.bodhiext), !this.bodhiext)
160
- throw h("Client not initialized", "extension_error");
167
+ throw c("Client not initialized", "extension_error");
161
168
  }
162
169
  /**
163
170
  * Send extension request via window.bodhiext.sendExtRequest
@@ -169,45 +176,52 @@ class $ {
169
176
  * Send API message via window.bodhiext.sendApiRequest
170
177
  * Converts ApiResponse to ApiResponseResult
171
178
  */
172
- async sendApiRequest(e, t, s, r, a) {
179
+ async sendApiRequest(e, t, r, s, a) {
173
180
  try {
174
181
  this.ensureBodhiext();
175
- } catch (o) {
182
+ } catch (i) {
176
183
  return {
177
184
  error: {
178
- message: o instanceof Error ? o.message : String(o),
185
+ message: i instanceof Error ? i.message : String(i),
179
186
  type: "extension_error"
180
187
  }
181
188
  };
182
189
  }
183
190
  try {
184
- let o = r || {};
185
- if (a) {
186
- const n = await this._getAccessTokenRaw();
187
- if (!n)
188
- return {
189
- error: {
190
- message: "Not authenticated. Please log in first.",
191
- type: "extension_error"
192
- }
191
+ const i = new Promise(
192
+ (n, o) => setTimeout(
193
+ () => o(
194
+ new Error(
195
+ `[bodhi-js-sdk/web] network timeout: api request not completed within configured/default timeout of ${this.apiTimeoutMs}ms`
196
+ )
197
+ ),
198
+ this.apiTimeoutMs
199
+ )
200
+ ), h = (async () => {
201
+ let n = s || {};
202
+ if (a) {
203
+ const o = await this._getAccessTokenRaw();
204
+ if (!o)
205
+ return {
206
+ error: {
207
+ message: "Not authenticated. Please log in first.",
208
+ type: "extension_error"
209
+ }
210
+ };
211
+ n = {
212
+ ...n,
213
+ Authorization: `Bearer ${o}`
193
214
  };
194
- o = {
195
- ...o,
196
- Authorization: `Bearer ${n}`
197
- };
198
- }
199
- return await this.bodhiext.sendApiRequest(
200
- e,
201
- t,
202
- s,
203
- o
204
- );
205
- } catch (o) {
206
- const c = o?.error, n = c?.message ?? (o instanceof Error ? o.message : String(o)), i = c?.type || "extension_error";
215
+ }
216
+ return this.bodhiext.sendApiRequest(e, t, r, n);
217
+ })();
218
+ return await Promise.race([h, i]);
219
+ } catch (i) {
220
+ const h = i?.error, n = h?.message ?? (i instanceof Error ? i.message : String(i)), o = h?.type || "network_error";
207
221
  return {
208
222
  error: {
209
223
  message: n,
210
- type: i
224
+ type: o
211
225
  }
212
226
  };
213
227
  }
@@ -233,44 +247,44 @@ class $ {
233
247
  */
234
248
  async init(e = {}) {
235
249
  if (!e.testConnection && !e.selectedConnection)
236
- return this.logger.info("No testConnection or selectedConnection, returning not-initialized state"), w;
250
+ return this.logger.info("No testConnection or selectedConnection, returning not-initialized state"), T;
237
251
  if (this.bodhiext && !e.testConnection)
238
252
  return this.logger.debug("Already have bodhiext handle, skipping polling"), this.state;
239
253
  if (!this.bodhiext) {
240
- const r = e.timeoutMs ?? this.config.initParams?.extension?.timeoutMs ?? B, a = e.intervalMs ?? this.config.initParams?.extension?.intervalMs ?? F, o = Date.now();
254
+ const s = e.timeoutMs ?? this.config.initParams?.extension?.timeoutMs ?? z, a = e.intervalMs ?? this.config.initParams?.extension?.intervalMs ?? H, i = Date.now();
241
255
  if (!await new Promise((n) => {
242
- const i = () => {
256
+ const o = () => {
243
257
  if (window.bodhiext) {
244
258
  this.bodhiext = window.bodhiext, n(!0);
245
259
  return;
246
260
  }
247
- if (Date.now() - o >= r) {
261
+ if (Date.now() - i >= s) {
248
262
  n(!1);
249
263
  return;
250
264
  }
251
- setTimeout(i, a);
265
+ setTimeout(o, a);
252
266
  };
253
- i();
267
+ o();
254
268
  }))
255
- return this.logger.warn("Extension discovery timed out"), this.setState(A), this.state;
269
+ return this.logger.warn("Extension discovery timed out"), this.setState(U), this.state;
256
270
  }
257
271
  const t = await this.bodhiext.getExtensionId();
258
272
  this.logger.info(`Extension discovered: ${t}`);
259
- const s = {
273
+ const r = {
260
274
  type: "extension",
261
275
  extension: "ready",
262
276
  extensionId: t,
263
- server: v
277
+ server: N
264
278
  };
265
279
  if (e.testConnection)
266
280
  try {
267
- const r = await this.getServerState();
268
- this.setState({ ...s, server: r }), this.logger.info(`Server connectivity tested: ${r.status}`);
269
- } catch (r) {
270
- this.logger.error("Failed to get server state:", r), this.setState({ ...s, server: d });
281
+ const s = await this.getServerState();
282
+ this.setState({ ...r, server: s }), this.logger.info(`Server connectivity tested: ${s.status}`);
283
+ } catch (s) {
284
+ this.logger.error("Failed to get server state:", s), this.setState({ ...r, server: S });
271
285
  }
272
286
  else
273
- this.setState(s);
287
+ this.setState(r);
274
288
  return this.state;
275
289
  }
276
290
  // ============================================================================
@@ -280,45 +294,66 @@ class $ {
280
294
  * Request resource access scope from backend
281
295
  * Required for authenticated API access
282
296
  */
283
- async requestResourceAccess() {
284
- return this.ensureBodhiext(), this.bodhiext.sendApiRequest(
297
+ async requestResourceAccess(e, t) {
298
+ this.ensureBodhiext();
299
+ const r = {
300
+ app_client_id: this.authClientId,
301
+ ...e && { toolset_scope_ids: e },
302
+ ...t && { version: t }
303
+ };
304
+ return this.bodhiext.sendApiRequest(
285
305
  "POST",
286
306
  "/bodhi/v1/apps/request-access",
287
- { app_client_id: this.authClientId }
307
+ r
288
308
  );
289
309
  }
290
310
  /**
291
311
  * Login via browser redirect OAuth2 + PKCE flow
312
+ * @param options - Optional login options (toolsetScopeIds, version)
292
313
  * @returns AuthState (though in practice, this redirects and never returns)
293
314
  */
294
- async login() {
295
- const e = await this.getAuthState();
296
- if (e.status === "authenticated")
297
- return e;
315
+ async login(e) {
316
+ const t = await this.getAuthState();
317
+ if (t.status === "authenticated")
318
+ return t;
298
319
  this.ensureBodhiext();
299
- const t = await this.requestResourceAccess();
300
- if (E(t))
301
- throw h(t.error.message, t.error.type);
302
- if (p(t)) {
303
- const { message: l } = t.body.error;
304
- throw h(l, "auth_error");
320
+ const r = await this.requestResourceAccess(e?.toolsetScopeIds, e?.version);
321
+ if (f(r))
322
+ throw c(r.error.message, r.error.type);
323
+ if (R(r)) {
324
+ const { message: A } = r.body.error;
325
+ throw c(A, "auth_error");
305
326
  }
306
- if (!f(t))
307
- throw h(`Unexpected HTTP ${t.status}`, "auth_error");
308
- const s = t.body.scope;
327
+ if (!_(r))
328
+ throw c(`Unexpected HTTP ${r.status}`, "auth_error");
329
+ const s = r.body.scope;
309
330
  localStorage.setItem(this.storageKeys.RESOURCE_SCOPE, s);
310
- const r = g(), a = await T(r), o = g();
311
- localStorage.setItem(this.storageKeys.CODE_VERIFIER, r), localStorage.setItem(this.storageKeys.STATE, o);
312
- const c = ["openid", "profile", "email", "roles", this.config.userScope, s], n = new URLSearchParams({
331
+ const a = r.body.toolsets || [], i = C(e?.toolsetScopeIds, a);
332
+ if (i.length > 0)
333
+ throw c(
334
+ `toolsetScopeIds not received back from request-access call: [${i.join(", ")}], check developer console on configuring the toolset scopes correctly`,
335
+ "auth_error"
336
+ );
337
+ const h = k(e?.toolsetScopeIds, a), n = E(), o = await x(n), u = E();
338
+ localStorage.setItem(this.storageKeys.CODE_VERIFIER, n), localStorage.setItem(this.storageKeys.STATE, u);
339
+ const g = [
340
+ "openid",
341
+ "profile",
342
+ "email",
343
+ "roles",
344
+ this.config.userScope,
345
+ s,
346
+ ...h ? h.split(" ") : []
347
+ ], l = new URLSearchParams({
313
348
  response_type: "code",
314
349
  client_id: this.authClientId,
315
350
  redirect_uri: this.config.redirectUri,
316
- scope: c.join(" "),
317
- state: o,
318
- code_challenge: a,
351
+ scope: g.join(" "),
352
+ state: u,
353
+ code_challenge: o,
319
354
  code_challenge_method: "S256"
320
- }), i = `${this.authEndpoints.authorize}?${n}`;
321
- return window.location.href = i, new Promise(() => {
355
+ }), m = `${this.authEndpoints.authorize}?${l}`;
356
+ return window.location.href = m, new Promise(() => {
322
357
  });
323
358
  }
324
359
  /**
@@ -327,14 +362,14 @@ class $ {
327
362
  * @returns AuthState with login state and user info
328
363
  */
329
364
  async handleOAuthCallback(e, t) {
330
- const s = localStorage.getItem(this.storageKeys.STATE);
331
- if (!s || s !== t)
365
+ const r = localStorage.getItem(this.storageKeys.STATE);
366
+ if (!r || r !== t)
332
367
  throw new Error("Invalid state parameter - possible CSRF attack");
333
368
  await this.exchangeCodeForTokens(e), localStorage.removeItem(this.storageKeys.CODE_VERIFIER), localStorage.removeItem(this.storageKeys.STATE);
334
- const r = await this.getAuthState();
335
- if (r.status !== "authenticated")
369
+ const s = await this.getAuthState();
370
+ if (s.status !== "authenticated")
336
371
  throw new Error("Login failed");
337
- return this.setAuthState(r), r;
372
+ return this.setAuthState(s), s;
338
373
  }
339
374
  /**
340
375
  * Exchange authorization code for tokens
@@ -343,29 +378,29 @@ class $ {
343
378
  const t = localStorage.getItem(this.storageKeys.CODE_VERIFIER);
344
379
  if (!t)
345
380
  throw new Error("Code verifier not found");
346
- const s = new URLSearchParams({
381
+ const r = new URLSearchParams({
347
382
  grant_type: "authorization_code",
348
383
  client_id: this.authClientId,
349
384
  code: e,
350
385
  redirect_uri: this.config.redirectUri,
351
386
  code_verifier: t
352
- }), r = await fetch(this.authEndpoints.token, {
387
+ }), s = await fetch(this.authEndpoints.token, {
353
388
  method: "POST",
354
389
  headers: {
355
390
  "Content-Type": "application/x-www-form-urlencoded"
356
391
  },
357
- body: s
392
+ body: r
358
393
  });
359
- if (!r.ok) {
360
- const o = await r.text();
361
- throw new Error(`Token exchange failed: ${r.status} ${o}`);
394
+ if (!s.ok) {
395
+ const i = await s.text();
396
+ throw new Error(`Token exchange failed: ${s.status} ${i}`);
362
397
  }
363
- const a = await r.json();
398
+ const a = await s.json();
364
399
  if (!a.access_token)
365
400
  throw new Error("No access token received");
366
401
  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) {
367
- const o = Date.now() + a.expires_in * 1e3;
368
- localStorage.setItem(this.storageKeys.EXPIRES_AT, o.toString());
402
+ const i = Date.now() + a.expires_in * 1e3;
403
+ localStorage.setItem(this.storageKeys.EXPIRES_AT, i.toString());
369
404
  }
370
405
  }
371
406
  /**
@@ -376,7 +411,7 @@ class $ {
376
411
  const e = localStorage.getItem(this.storageKeys.REFRESH_TOKEN);
377
412
  if (e)
378
413
  try {
379
- const s = new URLSearchParams({
414
+ const r = new URLSearchParams({
380
415
  token: e,
381
416
  client_id: this.authClientId,
382
417
  token_type_hint: "refresh_token"
@@ -386,10 +421,10 @@ class $ {
386
421
  headers: {
387
422
  "Content-Type": "application/x-www-form-urlencoded"
388
423
  },
389
- body: s
424
+ body: r
390
425
  });
391
- } catch (s) {
392
- this.logger.warn("Token revocation failed:", s);
426
+ } catch (r) {
427
+ this.logger.warn("Token revocation failed:", r);
393
428
  }
394
429
  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);
395
430
  const t = {
@@ -408,7 +443,7 @@ class $ {
408
443
  if (!e)
409
444
  return { status: "unauthenticated", user: null, accessToken: null, error: null };
410
445
  try {
411
- return { status: "authenticated", user: y(e), accessToken: e, error: null };
446
+ return { status: "authenticated", user: I(e), accessToken: e, error: null };
412
447
  } catch (t) {
413
448
  return this.logger.error("Failed to parse token:", t), { status: "unauthenticated", user: null, accessToken: null, error: null };
414
449
  }
@@ -422,10 +457,10 @@ class $ {
422
457
  if (!e)
423
458
  return null;
424
459
  if (t) {
425
- const s = parseInt(t, 10);
426
- if (Date.now() >= s - 5 * 1e3) {
427
- const r = localStorage.getItem(this.storageKeys.REFRESH_TOKEN);
428
- return r ? this._tryRefreshToken(r) : null;
460
+ const r = parseInt(t, 10);
461
+ if (Date.now() >= r - 5 * 1e3) {
462
+ const s = localStorage.getItem(this.storageKeys.REFRESH_TOKEN);
463
+ return s ? this._tryRefreshToken(s) : null;
429
464
  }
430
465
  }
431
466
  return e;
@@ -450,17 +485,17 @@ class $ {
450
485
  async _doRefreshToken(e) {
451
486
  this.logger.debug("Refreshing access token");
452
487
  try {
453
- const t = await b(
488
+ const t = await L(
454
489
  this.authEndpoints.token,
455
490
  e,
456
491
  this.authClientId
457
492
  );
458
493
  if (t.success) {
459
494
  this._storeRefreshedTokens(t.tokens);
460
- const s = y(t.tokens.access_token);
495
+ const r = I(t.tokens.access_token);
461
496
  return this.setAuthState({
462
497
  status: "authenticated",
463
- user: s,
498
+ user: r,
464
499
  accessToken: t.tokens.access_token,
465
500
  error: null
466
501
  }), this.logger.info("Token refreshed successfully"), t.tokens.access_token;
@@ -475,7 +510,7 @@ class $ {
475
510
  } catch (t) {
476
511
  this.logger.warn("Token refresh failed:", t);
477
512
  }
478
- throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"), h(
513
+ throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"), c(
479
514
  "Access token expired and unable to refresh. Try logging out and logging in again.",
480
515
  "token_refresh_failed"
481
516
  );
@@ -502,61 +537,61 @@ class $ {
502
537
  */
503
538
  async getServerState() {
504
539
  const e = await this.sendApiRequest("GET", "/bodhi/v1/info");
505
- if (E(e))
506
- return d;
507
- if (!f(e))
508
- return d;
540
+ if (f(e))
541
+ return S;
542
+ if (!_(e))
543
+ return S;
509
544
  const t = e.body;
510
545
  switch (t.status) {
511
546
  case "ready":
512
547
  return { status: "ready", version: t.version || "unknown", error: null };
513
548
  case "setup":
514
- return S("setup", t.version || "unknown");
549
+ return p("setup", t.version || "unknown");
515
550
  case "resource-admin":
516
- return S("resource-admin", t.version || "unknown");
551
+ return p("resource-admin", t.version || "unknown");
517
552
  case "error":
518
- return S(
553
+ return p(
519
554
  "error",
520
555
  t.version || "unknown",
521
- t.error ? { message: t.error.message, type: t.error.type } : O.SERVER_NOT_READY
556
+ t.error ? { message: t.error.message, type: t.error.type } : D.SERVER_NOT_READY
522
557
  );
523
558
  default:
524
- return d;
559
+ return S;
525
560
  }
526
561
  }
527
562
  /**
528
563
  * Generic streaming via window.bodhiext.sendStreamRequest
529
564
  * Wraps ReadableStream as AsyncGenerator
530
565
  */
531
- async *stream(e, t, s, r, a = !0) {
566
+ async *stream(e, t, r, s, a = !0) {
532
567
  this.ensureBodhiext();
533
- let o = r || {};
568
+ let i = s || {};
534
569
  if (a) {
535
- const i = await this._getAccessTokenRaw();
536
- if (!i)
537
- throw h("Not authenticated. Please log in first.", "auth_error");
538
- o = {
539
- ...o,
540
- Authorization: `Bearer ${i}`
570
+ const o = await this._getAccessTokenRaw();
571
+ if (!o)
572
+ throw c("Not authenticated. Please log in first.", "auth_error");
573
+ i = {
574
+ ...i,
575
+ Authorization: `Bearer ${o}`
541
576
  };
542
577
  }
543
- const n = this.bodhiext.sendStreamRequest(e, t, s, o).getReader();
578
+ const n = this.bodhiext.sendStreamRequest(e, t, r, i).getReader();
544
579
  try {
545
580
  for (; ; ) {
546
- const { value: i, done: l } = await n.read();
547
- if (l || i?.done)
581
+ const { value: o, done: u } = await n.read();
582
+ if (u || o?.done)
548
583
  break;
549
- yield i.body;
584
+ yield o.body;
550
585
  }
551
- } catch (i) {
552
- if (i instanceof Error) {
553
- if ("response" in i) {
554
- const l = i;
555
- throw P(i.message, l.response.status, l.response.body);
586
+ } catch (o) {
587
+ if (o instanceof Error) {
588
+ if ("response" in o) {
589
+ const u = o;
590
+ throw F(o.message, u.response.status, u.response.body);
556
591
  }
557
- throw "error" in i ? h(i.message, "extension_error") : h(i.message, "extension_error");
592
+ throw "error" in o ? c(o.message, "extension_error") : c(o.message, "extension_error");
558
593
  }
559
- throw i;
594
+ throw o;
560
595
  } finally {
561
596
  n.releaseLock();
562
597
  }
@@ -565,13 +600,13 @@ class $ {
565
600
  // OpenAI-Compatible Namespaced API
566
601
  // ============================================================================
567
602
  get chat() {
568
- return this._chat ??= new K(this);
603
+ return this._chat ??= new M(this);
569
604
  }
570
605
  get models() {
571
- return this._models ??= new U(this);
606
+ return this._models ??= new B(this);
572
607
  }
573
608
  get embeddings() {
574
- return this._embeddings ??= new N(this);
609
+ return this._embeddings ??= new $(this);
575
610
  }
576
611
  /**
577
612
  * Serialize web extension client state (all transient, nothing to persist)
@@ -597,32 +632,33 @@ class $ {
597
632
  };
598
633
  }
599
634
  }
600
- function q(u) {
635
+ function j(d) {
601
636
  if (typeof window > "u")
602
637
  throw new Error("redirectUri required in non-browser environment");
603
- const e = u === "/" ? "" : u.replace(/\/$/, "");
638
+ const e = d === "/" ? "" : d.replace(/\/$/, "");
604
639
  return `${window.location.origin}${e}/callback`;
605
640
  }
606
- class H extends L {
607
- constructor(e, t, s) {
608
- const r = t || {}, a = {
609
- basePath: r.basePath || "/",
610
- redirectUri: r.redirectUri || q(r.basePath || "/"),
611
- authServerUrl: r.authServerUrl || "https://id.getbodhi.app/realms/bodhi",
612
- userScope: r.userScope || "scope_user_user",
613
- logLevel: r.logLevel || "warn",
614
- initParams: r.initParams
641
+ class Y extends q {
642
+ constructor(e, t, r) {
643
+ const s = t || {}, a = {
644
+ basePath: s.basePath || "/",
645
+ redirectUri: s.redirectUri || j(s.basePath || "/"),
646
+ authServerUrl: s.authServerUrl || "https://id.getbodhi.app/realms/bodhi",
647
+ userScope: s.userScope || "scope_user_user",
648
+ logLevel: s.logLevel || "warn",
649
+ apiTimeoutMs: s.apiTimeoutMs,
650
+ initParams: s.initParams
615
651
  };
616
- super(e, a, s);
652
+ super(e, a, r);
617
653
  }
618
654
  createLogger(e) {
619
- return new I("WebUIClient", e.logLevel);
655
+ return new v("WebUIClient", e.logLevel);
620
656
  }
621
657
  createStoragePrefix(e) {
622
- return m(e.basePath, _.WEB);
658
+ return w(e.basePath, y.WEB);
623
659
  }
624
660
  createExtClient(e, t) {
625
- return new $(
661
+ return new X(
626
662
  this.authClientId,
627
663
  {
628
664
  authServerUrl: e.authServerUrl,
@@ -630,22 +666,24 @@ class H extends L {
630
666
  userScope: e.userScope,
631
667
  basePath: e.basePath,
632
668
  logLevel: e.logLevel,
669
+ apiTimeoutMs: e.apiTimeoutMs,
633
670
  initParams: e.initParams
634
671
  },
635
672
  t
636
673
  );
637
674
  }
638
- createDirectClient(e, t, s) {
639
- return new D(
675
+ createDirectClient(e, t, r) {
676
+ return new V(
640
677
  {
641
678
  authClientId: e,
642
679
  authServerUrl: t.authServerUrl,
643
680
  redirectUri: t.redirectUri,
644
681
  userScope: t.userScope,
645
682
  logLevel: t.logLevel,
646
- basePath: t.basePath
683
+ basePath: t.basePath,
684
+ apiTimeoutMs: t.apiTimeoutMs
647
685
  },
648
- s
686
+ r
649
687
  );
650
688
  }
651
689
  // ============================================================================
@@ -655,8 +693,8 @@ class H extends L {
655
693
  return this.connectionMode === "direct" ? this.directClient.handleOAuthCallback(e, t) : this.extClient.handleOAuthCallback(e, t);
656
694
  }
657
695
  }
658
- const z = "production";
696
+ const J = "production";
659
697
  export {
660
- z as WEB_BUILD_MODE,
661
- H as WebUIClient
698
+ J as WEB_BUILD_MODE,
699
+ Y as WebUIClient
662
700
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bodhiapp/bodhi-js",
3
- "version": "0.0.15",
3
+ "version": "0.0.17",
4
4
  "description": "Web SDK for Bodhi Browser - window.bodhiext communication",
5
5
  "type": "module",
6
6
  "main": "dist/bodhi-web.cjs.js",
@@ -37,8 +37,8 @@
37
37
  "typecheck": "tsc --noEmit"
38
38
  },
39
39
  "dependencies": {
40
- "@bodhiapp/bodhi-js-core": "0.0.15",
41
- "@bodhiapp/ts-client": "0.1.9"
40
+ "@bodhiapp/bodhi-js-core": "0.0.17",
41
+ "@bodhiapp/ts-client": "0.1.14"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@eslint/js": "^9.23.0",