@bodhiapp/bodhi-js 0.0.43 → 0.0.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bodhi-web.cjs.js +1 -1
- package/dist/bodhi-web.esm.js +14 -16
- package/package.json +4 -4
package/dist/bodhi-web.cjs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("@bodhiapp/bodhi-js-core");class E{async get(e){return localStorage.getItem(e)}async set(e){Object.entries(e).forEach(([t,i])=>{localStorage.setItem(t,String(i))})}async remove(e){e.forEach(t=>localStorage.removeItem(t))}}class f extends r.DirectClientBase{constructor(e,t){const i=r.createStoragePrefixWithNamespace(e.basePath,r.STORAGE_PREFIXES.WEB_DIRECT),s={authClientId:e.authClientId,authServerUrl:e.authServerUrl,storagePrefix:i,logLevel:e.logLevel,loggerPrefix:"DirectWebClient",apiTimeoutMs:e.apiTimeoutMs,storage:e.storage??new E,initialTokens:e.initialTokens};super(s,t),this.redirectUri=e.redirectUri}async login(e){const t=await this.getAuthState();if(t.status==="authenticated"&&!e?.reauthorize)return t;const i=e?.userRole??"scope_user_user",s=this.redirectUri;e?.onProgress?.("requesting");const a=r.generateCodeVerifier(),n=await r.generateCodeChallenge(a),h=r.generateCodeVerifier();await this._storageSet({[this.storageKeys.CODE_VERIFIER]:a,[this.storageKeys.STATE]:h});const c=r.buildAuthorizeUrl(this.authEndpoints,{clientId:this.authClientId,redirectUri:s,scope:r.BASE_OAUTH_SCOPE,state:h,codeChallenge:n}),o=r.buildErrorUrl(s),l=new r.AccessRequestBuilder(this.authClientId).requestedRole(i);e?.requested&&l.requested(e.requested);const d=await this.requestAccess(l.build()),{review_url:g}=r.unwrapResponse(d);return e?.onProgress?.("reviewing"),window.location.href=r.buildReviewUrl(g,c,o),new Promise(()=>{})}async handleOAuthCallback(e,t){const i=await this._storageGet(this.storageKeys.STATE);if(!i||i!==t)throw new Error("Invalid state parameter - possible CSRF attack");await this.exchangeCodeForTokens(e);const s=await this.getAuthState();if(s.status!=="authenticated")throw new Error("Login failed");return this.setAuthState(s),s}_getRedirectUri(){return this.redirectUri}}const S=500,w=5e3,T=3e4;class m{constructor(e,t,i){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=i??r.NOOP_STATE_CALLBACK;const s=r.createStoragePrefixWithNamespace(t.basePath,r.STORAGE_PREFIXES.WEB_EXT);this.storageKeys=r.createStorageKeys(s),this.apiTimeoutMs=t.apiTimeoutMs??T}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("not_initialized","Client not initialized")}async sendExtRequest(e,t){return this.ensureBodhiext(),this.bodhiext.sendExtRequest(e,t)}async sendApiRequest(e,t,i,s,a){this.ensureBodhiext();try{const n=new Promise((c,o)=>setTimeout(()=>o(new r.BodhiError("timeout_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=s||{};if(a){const o=await this._getAccessTokenRaw();if(!o)throw new r.BodhiError("auth_error","Not authenticated. Please log in first.");c={...c,Authorization:`Bearer ${o}`}}return this.bodhiext.sendApiRequest(e,t,i,c)})();return await Promise.race([h,n])}catch(n){if(n instanceof r.BodhiApiError||n instanceof r.BodhiError)throw n;if(n instanceof Error){const h=n;throw n.name==="BodhiApiError"&&typeof h.status=="number"&&h.body!=null?new r.BodhiApiError(h.status,h.body,n.message,h.headers):n.name==="BodhiError"&&typeof h.code=="string"?new r.BodhiError(h.code,n.message):new r.BodhiError("network_error",n.message)}throw new r.BodhiError("network_error",String(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??w,a=e.intervalMs??this.config.initParams?.extension?.intervalMs??S,n=Date.now();if(!await new Promise(c=>{const o=()=>{if(window.bodhiext){this.bodhiext=window.bodhiext,c(!0);return}if(Date.now()-n>=s){c(!1);return}setTimeout(o,a)};o()}))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 i={type:"extension",extension:"ready",extensionId:t,server:r.PENDING_EXTENSION_READY};if(e.testConnection)try{const s=await this.getServerState();this.setState({...i,server:s}),this.logger.info(`Server connectivity tested: ${s.status}`)}catch(s){this.logger.error("Failed to get server state:",s),this.setState({...i,server:r.BACKEND_SERVER_NOT_REACHABLE})}else this.setState(i);return this.state}async login(e){const t=await this.getAuthState();if(t.status==="authenticated"&&!e?.reauthorize)return t;this.ensureBodhiext();const i=e?.userRole??"scope_user_user",s=this.config.redirectUri;e?.onProgress?.("requesting");const a=r.generateCodeVerifier(),n=await r.generateCodeChallenge(a),h=r.generateCodeVerifier();localStorage.setItem(this.storageKeys.CODE_VERIFIER,a),localStorage.setItem(this.storageKeys.STATE,h);const c=r.buildAuthorizeUrl(this.authEndpoints,{clientId:this.authClientId,redirectUri:s,scope:r.BASE_OAUTH_SCOPE,state:h,codeChallenge:n}),o=r.buildErrorUrl(s),l=new r.AccessRequestBuilder(this.authClientId).requestedRole(i);e?.requested&&l.requested(e.requested);const d=await this.requestAccess(l.build()),{review_url:g}=r.unwrapResponse(d);return e?.onProgress?.("reviewing"),window.location.href=r.buildReviewUrl(g,c,o),new Promise(()=>{})}async handleOAuthCallback(e,t){const i=localStorage.getItem(this.storageKeys.STATE);if(!i||i!==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 i=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:i});if(!s.ok){const n=await s.text();throw new Error(`Token exchange failed: ${s.status} ${n}`)}const a=await s.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 n=Date.now()+a.expires_in*1e3;localStorage.setItem(this.storageKeys.EXPIRES_AT,n.toString())}}async logout(){const e=localStorage.getItem(this.storageKeys.REFRESH_TOKEN);if(e)try{const i=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:i})}catch(i){this.logger.warn("Token revocation failed:",i)}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);const t={status:"unauthenticated",user:null,accessToken:null,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!1};return this.setAuthState(t),t}async getAuthState(){const e=await this._getAccessTokenRaw();if(!e)return{status:"unauthenticated",user:null,accessToken:null,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!1};try{return{status:"authenticated",user:r.extractUserInfo(e),accessToken:e,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!1}}catch(t){return this.logger.error("Failed to parse token:",t),{status:"unauthenticated",user:null,accessToken:null,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!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 i=parseInt(t,10);if(Date.now()>=i-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 i=r.extractUserInfo(t.tokens.access_token);return this.setAuthState({status:"authenticated",user:i,accessToken:t.tokens.access_token,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!0}),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,refreshToken:null,expiresAt:null,isTokenRefresh:!1}),null}catch(t){this.logger.warn("Token refresh failed:",t)}throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"),r.createOperationError("auth_error","Access token expired and unable to refresh. Try logging out and logging in again.")}clearAuthStorage(){localStorage.removeItem(this.storageKeys.ACCESS_TOKEN),localStorage.removeItem(this.storageKeys.REFRESH_TOKEN),localStorage.removeItem(this.storageKeys.EXPIRES_AT)}_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(){try{const e=await this.sendApiRequest("GET","/bodhi/v1/info");if(e.status>=400)return r.BACKEND_SERVER_NOT_REACHABLE;const t=e.body,i=t.version||"unknown";switch(t.status){case"ready":return{status:"ready",version:i,error:null,deployment:t.deployment??null,client_id:t.client_id??null};case"setup":return r.backendServerNotReady("setup",i,void 0,t.deployment,t.client_id);case"resource_admin":return r.backendServerNotReady("resource_admin",i,void 0,t.deployment,t.client_id);case"error":return r.backendServerNotReady("error",i,t.error?{message:t.error.message,type:t.error.type}:r.SERVER_ERROR_CODES.SERVER_NOT_READY,t.deployment,t.client_id);default:return r.BACKEND_SERVER_NOT_REACHABLE}}catch{return r.BACKEND_SERVER_NOT_REACHABLE}}async*stream(e,t,i,s,a=!0){this.ensureBodhiext();let n=s||{};if(a){const o=await this._getAccessTokenRaw();if(!o)throw r.createOperationError("auth_error","Not authenticated. Please log in first.");n={...n,Authorization:`Bearer ${o}`}}const c=this.bodhiext.sendStreamRequest(e,t,i,n).getReader();try{for(;;){const{value:o,done:l}=await c.read();if(l||o?.done)break;yield o.body}}catch(o){if(o instanceof r.BodhiApiError||o instanceof r.BodhiError)throw o;if(o instanceof Error){const l=o;throw o.name==="BodhiApiError"&&typeof l.status=="number"&&l.body!=null?new r.BodhiApiError(l.status,l.body,o.message,l.headers):o.name==="BodhiError"&&typeof l.code=="string"?new r.BodhiError(l.code,o.message):new r.BodhiError("extension_error",o.message)}throw o}finally{c.releaseLock()}}async streamText(e,t,i,s,a=!0){this.ensureBodhiext();let n={...s};if(a){const o=await this._getAccessTokenRaw();o&&(n={...n,Authorization:`Bearer ${o}`})}const h=await this.bodhiext.sendStreamText(e,t,i,n);async function*c(o){const l=o.getReader();try{for(;;){const{done:d,value:g}=await l.read();if(d)break;yield g}}finally{l.releaseLock()}}return{status:h.status,headers:h.headers,body:c(h.body)}}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)}get mcps(){return this._mcps??=new r.Mcps(this)}async requestAccess(e){return this.sendApiRequest("POST","/bodhi/v1/apps/request-access",e,{},!1)}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}}}function _(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 y extends r.BaseFacadeClient{constructor(e,t,i){const s=t||{},a={basePath:s.basePath||"/",redirectUri:s.redirectUri||_(s.basePath||"/"),authServerUrl:s.authServerUrl||"https://id.getbodhi.app/realms/bodhi",logLevel:s.logLevel||"warn",apiTimeoutMs:s.apiTimeoutMs,storage:s.storage,initialTokens:s.initialTokens,initParams:s.initParams};super(e,a,i)}createLogger(e){return new r.Logger("WebUIClient",e.logLevel)}createStoragePrefix(e){return r.createStoragePrefixWithNamespace(e.basePath,r.STORAGE_PREFIXES.WEB)}createExtClient(e,t){return new m(this.authClientId,{authServerUrl:e.authServerUrl,redirectUri:e.redirectUri,basePath:e.basePath,logLevel:e.logLevel,apiTimeoutMs:e.apiTimeoutMs,initParams:e.initParams},t)}createDirectClient(e,t,i){return new f({authClientId:e,authServerUrl:t.authServerUrl,redirectUri:t.redirectUri,logLevel:t.logLevel,basePath:t.basePath,apiTimeoutMs:t.apiTimeoutMs,storage:t.storage,initialTokens:t.initialTokens},i)}async handleOAuthCallback(e,t){return this.connectionMode==="direct"?this.directClient.handleOAuthCallback(e,t):this.extClient.handleOAuthCallback(e,t)}}const A="production";Object.defineProperty(exports,"InMemoryStorage",{enumerable:!0,get:()=>r.InMemoryStorage});exports.DirectWebClient=f;exports.LocalStorageAdapter=E;exports.WEB_BUILD_MODE=A;exports.WebUIClient=y;
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("@bodhiapp/bodhi-js-core");class E{async get(e){return localStorage.getItem(e)}async set(e){Object.entries(e).forEach(([t,i])=>{localStorage.setItem(t,String(i))})}async remove(e){e.forEach(t=>localStorage.removeItem(t))}}class f extends r.DirectClientBase{constructor(e,t){const i=r.createStoragePrefixWithNamespace(e.basePath,r.STORAGE_PREFIXES.WEB_DIRECT),s={authClientId:e.authClientId,authServerUrl:e.authServerUrl,storagePrefix:i,logLevel:e.logLevel,loggerPrefix:"DirectWebClient",apiTimeoutMs:e.apiTimeoutMs,storage:e.storage??new E,initialTokens:e.initialTokens};super(s,t),this.redirectUri=e.redirectUri}async login(e){const t=await this.getAuthState();if(t.status==="authenticated"&&!e?.exchange)return t;const i=e?.userRole??"scope_user_user",s=this.redirectUri;e?.onProgress?.("requesting");const a=r.generateCodeVerifier(),n=await r.generateCodeChallenge(a),h=r.generateCodeVerifier();await this._storageSet({[this.storageKeys.CODE_VERIFIER]:a,[this.storageKeys.STATE]:h});const c=r.buildAuthorizeUrl(this.authEndpoints,{clientId:this.authClientId,redirectUri:s,scope:r.BASE_OAUTH_SCOPE,state:h,codeChallenge:n}),o=r.buildErrorUrl(s),l=new r.AccessRequestBuilder(this.authClientId).requestedRole(i);e?.requested&&l.requested(e.requested),e?.exchange&&l.exchange(!0);const d=await this.requestAccess(l.build()),{review_url:g}=r.unwrapResponse(d);return e?.onProgress?.("reviewing"),window.location.href=r.buildReviewUrl(g,c,o),new Promise(()=>{})}async handleOAuthCallback(e,t){const i=await this._storageGet(this.storageKeys.STATE);if(!i||i!==t)throw new Error("Invalid state parameter - possible CSRF attack");await this.exchangeCodeForTokens(e);const s=await this.getAuthState();if(s.status!=="authenticated")throw new Error("Login failed");return this.setAuthState(s),s}_getRedirectUri(){return this.redirectUri}}const S=500,w=5e3,T=3e4;class m{constructor(e,t,i){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=i??r.NOOP_STATE_CALLBACK;const s=r.createStoragePrefixWithNamespace(t.basePath,r.STORAGE_PREFIXES.WEB_EXT);this.storageKeys=r.createStorageKeys(s),this.apiTimeoutMs=t.apiTimeoutMs??T}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("not_initialized","Client not initialized")}async sendExtRequest(e,t){return this.ensureBodhiext(),this.bodhiext.sendExtRequest(e,t)}async sendApiRequest(e,t,i,s,a){this.ensureBodhiext();try{const n=new Promise((c,o)=>setTimeout(()=>o(new r.BodhiError("timeout_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=s||{};if(a){const o=await this._getAccessTokenRaw();o&&(c={...c,Authorization:`Bearer ${o}`})}return this.bodhiext.sendApiRequest(e,t,i,c)})();return await Promise.race([h,n])}catch(n){if(n instanceof r.BodhiApiError||n instanceof r.BodhiError)throw n;if(n instanceof Error){const h=n;throw n.name==="BodhiApiError"&&typeof h.status=="number"&&h.body!=null?new r.BodhiApiError(h.status,h.body,n.message,h.headers):n.name==="BodhiError"&&typeof h.code=="string"?new r.BodhiError(h.code,n.message):new r.BodhiError("network_error",n.message)}throw new r.BodhiError("network_error",String(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??w,a=e.intervalMs??this.config.initParams?.extension?.intervalMs??S,n=Date.now();if(!await new Promise(c=>{const o=()=>{if(window.bodhiext){this.bodhiext=window.bodhiext,c(!0);return}if(Date.now()-n>=s){c(!1);return}setTimeout(o,a)};o()}))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 i={type:"extension",extension:"ready",extensionId:t,server:r.PENDING_EXTENSION_READY};if(e.testConnection)try{const s=await this.getServerState();this.setState({...i,server:s}),this.logger.info(`Server connectivity tested: ${s.status}`)}catch(s){this.logger.error("Failed to get server state:",s),this.setState({...i,server:r.BACKEND_SERVER_NOT_REACHABLE})}else this.setState(i);return this.state}async login(e){const t=await this.getAuthState();if(t.status==="authenticated"&&!e?.exchange)return t;this.ensureBodhiext();const i=e?.userRole??"scope_user_user",s=this.config.redirectUri;e?.onProgress?.("requesting");const a=r.generateCodeVerifier(),n=await r.generateCodeChallenge(a),h=r.generateCodeVerifier();localStorage.setItem(this.storageKeys.CODE_VERIFIER,a),localStorage.setItem(this.storageKeys.STATE,h);const c=r.buildAuthorizeUrl(this.authEndpoints,{clientId:this.authClientId,redirectUri:s,scope:r.BASE_OAUTH_SCOPE,state:h,codeChallenge:n}),o=r.buildErrorUrl(s),l=new r.AccessRequestBuilder(this.authClientId).requestedRole(i);e?.requested&&l.requested(e.requested),e?.exchange&&l.exchange(!0);const d=await this.requestAccess(l.build()),{review_url:g}=r.unwrapResponse(d);return e?.onProgress?.("reviewing"),window.location.href=r.buildReviewUrl(g,c,o),new Promise(()=>{})}async handleOAuthCallback(e,t){const i=localStorage.getItem(this.storageKeys.STATE);if(!i||i!==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 i=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:i});if(!s.ok){const n=await s.text();throw new Error(`Token exchange failed: ${s.status} ${n}`)}const a=await s.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 n=Date.now()+a.expires_in*1e3;localStorage.setItem(this.storageKeys.EXPIRES_AT,n.toString())}}async logout(){const e=localStorage.getItem(this.storageKeys.REFRESH_TOKEN);if(e)try{const i=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:i})}catch(i){this.logger.warn("Token revocation failed:",i)}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);const t={status:"unauthenticated",user:null,accessToken:null,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!1};return this.setAuthState(t),t}async getAuthState(){const e=await this._getAccessTokenRaw();if(!e)return{status:"unauthenticated",user:null,accessToken:null,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!1};try{return{status:"authenticated",user:r.extractUserInfo(e),accessToken:e,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!1}}catch(t){return this.logger.error("Failed to parse token:",t),{status:"unauthenticated",user:null,accessToken:null,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!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 i=parseInt(t,10);if(Date.now()>=i-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 i=r.extractUserInfo(t.tokens.access_token);return this.setAuthState({status:"authenticated",user:i,accessToken:t.tokens.access_token,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!0}),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,refreshToken:null,expiresAt:null,isTokenRefresh:!1}),null}catch(t){this.logger.warn("Token refresh failed:",t)}throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"),r.createOperationError("auth_error","Access token expired and unable to refresh. Try logging out and logging in again.")}clearAuthStorage(){localStorage.removeItem(this.storageKeys.ACCESS_TOKEN),localStorage.removeItem(this.storageKeys.REFRESH_TOKEN),localStorage.removeItem(this.storageKeys.EXPIRES_AT)}_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(){try{const e=await this.sendApiRequest("GET","/bodhi/v1/info");if(e.status>=400)return r.BACKEND_SERVER_NOT_REACHABLE;const t=e.body,i=t.version||"unknown";switch(t.status){case"ready":return{status:"ready",version:i,error:null,deployment:t.deployment??null,client_id:t.client_id??null};case"setup":return r.backendServerNotReady("setup",i,void 0,t.deployment,t.client_id);case"resource_admin":return r.backendServerNotReady("resource_admin",i,void 0,t.deployment,t.client_id);case"error":return r.backendServerNotReady("error",i,t.error?{message:t.error.message,type:t.error.type}:r.SERVER_ERROR_CODES.SERVER_NOT_READY,t.deployment,t.client_id);default:return r.BACKEND_SERVER_NOT_REACHABLE}}catch{return r.BACKEND_SERVER_NOT_REACHABLE}}async*stream(e,t,i,s,a=!0){this.ensureBodhiext();let n=s||{};if(a){const o=await this._getAccessTokenRaw();if(!o)throw r.createOperationError("auth_error","Not authenticated. Please log in first.");n={...n,Authorization:`Bearer ${o}`}}const c=this.bodhiext.sendStreamRequest(e,t,i,n).getReader();try{for(;;){const{value:o,done:l}=await c.read();if(l||o?.done)break;yield o.body}}catch(o){if(o instanceof r.BodhiApiError||o instanceof r.BodhiError)throw o;if(o instanceof Error){const l=o;throw o.name==="BodhiApiError"&&typeof l.status=="number"&&l.body!=null?new r.BodhiApiError(l.status,l.body,o.message,l.headers):o.name==="BodhiError"&&typeof l.code=="string"?new r.BodhiError(l.code,o.message):new r.BodhiError("extension_error",o.message)}throw o}finally{c.releaseLock()}}async streamText(e,t,i,s,a=!0){this.ensureBodhiext();let n={...s};if(a){const o=await this._getAccessTokenRaw();o&&(n={...n,Authorization:`Bearer ${o}`})}const h=await this.bodhiext.sendStreamText(e,t,i,n);async function*c(o){const l=o.getReader();try{for(;;){const{done:d,value:g}=await l.read();if(d)break;yield g}}finally{l.releaseLock()}}return{status:h.status,headers:h.headers,body:c(h.body)}}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)}get mcps(){return this._mcps??=new r.Mcps(this)}async requestAccess(e){return this.sendApiRequest("POST","/bodhi/v1/apps/request-access",e,{},!0)}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}}}function _(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 y extends r.BaseFacadeClient{constructor(e,t,i){const s=t||{},a={basePath:s.basePath||"/",redirectUri:s.redirectUri||_(s.basePath||"/"),authServerUrl:s.authServerUrl||"https://id.getbodhi.app/realms/bodhi",logLevel:s.logLevel||"warn",apiTimeoutMs:s.apiTimeoutMs,storage:s.storage,initialTokens:s.initialTokens,initParams:s.initParams};super(e,a,i)}createLogger(e){return new r.Logger("WebUIClient",e.logLevel)}createStoragePrefix(e){return r.createStoragePrefixWithNamespace(e.basePath,r.STORAGE_PREFIXES.WEB)}createExtClient(e,t){return new m(this.authClientId,{authServerUrl:e.authServerUrl,redirectUri:e.redirectUri,basePath:e.basePath,logLevel:e.logLevel,apiTimeoutMs:e.apiTimeoutMs,initParams:e.initParams},t)}createDirectClient(e,t,i){return new f({authClientId:e,authServerUrl:t.authServerUrl,redirectUri:t.redirectUri,logLevel:t.logLevel,basePath:t.basePath,apiTimeoutMs:t.apiTimeoutMs,storage:t.storage,initialTokens:t.initialTokens},i)}async handleOAuthCallback(e,t){return this.connectionMode==="direct"?this.directClient.handleOAuthCallback(e,t):this.extClient.handleOAuthCallback(e,t)}}const A="production";Object.defineProperty(exports,"InMemoryStorage",{enumerable:!0,get:()=>r.InMemoryStorage});exports.DirectWebClient=f;exports.LocalStorageAdapter=E;exports.WEB_BUILD_MODE=A;exports.WebUIClient=y;
|
package/dist/bodhi-web.esm.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DirectClientBase as P, createStoragePrefixWithNamespace as T, STORAGE_PREFIXES as y, generateCodeVerifier as S, generateCodeChallenge as
|
|
1
|
+
import { DirectClientBase as P, createStoragePrefixWithNamespace as T, STORAGE_PREFIXES as y, generateCodeVerifier as S, generateCodeChallenge as p, buildAuthorizeUrl as R, BASE_OAUTH_SCOPE as A, buildErrorUrl as x, AccessRequestBuilder as I, unwrapResponse as b, buildReviewUrl as C, EXTENSION_STATE_NOT_INITIALIZED as _, Logger as v, createOAuthEndpoints as U, NOOP_STATE_CALLBACK as O, createStorageKeys as K, createOperationError as E, BodhiError as c, BodhiApiError as f, EXTENSION_STATE_NOT_FOUND as N, PENDING_EXTENSION_READY as B, BACKEND_SERVER_NOT_REACHABLE as w, extractUserInfo as k, refreshAccessToken as q, backendServerNotReady as m, SERVER_ERROR_CODES as L, Chat as D, Models as M, Embeddings as F, Mcps as z, BaseFacadeClient as V } from "@bodhiapp/bodhi-js-core";
|
|
2
2
|
import { InMemoryStorage as re } from "@bodhiapp/bodhi-js-core";
|
|
3
3
|
class W {
|
|
4
4
|
async get(e) {
|
|
@@ -35,11 +35,11 @@ class $ extends P {
|
|
|
35
35
|
// ============================================================================
|
|
36
36
|
async login(e) {
|
|
37
37
|
const t = await this.getAuthState();
|
|
38
|
-
if (t.status === "authenticated" && !e?.
|
|
38
|
+
if (t.status === "authenticated" && !e?.exchange)
|
|
39
39
|
return t;
|
|
40
40
|
const s = e?.userRole ?? "scope_user_user", r = this.redirectUri;
|
|
41
41
|
e?.onProgress?.("requesting");
|
|
42
|
-
const n = S(), o = await
|
|
42
|
+
const n = S(), o = await p(n), a = S();
|
|
43
43
|
await this._storageSet({
|
|
44
44
|
[this.storageKeys.CODE_VERIFIER]: n,
|
|
45
45
|
[this.storageKeys.STATE]: a
|
|
@@ -50,8 +50,8 @@ class $ extends P {
|
|
|
50
50
|
scope: A,
|
|
51
51
|
state: a,
|
|
52
52
|
codeChallenge: o
|
|
53
|
-
}), i =
|
|
54
|
-
e?.requested && h.requested(e.requested);
|
|
53
|
+
}), i = x(r), h = new I(this.authClientId).requestedRole(s);
|
|
54
|
+
e?.requested && h.requested(e.requested), e?.exchange && h.exchange(!0);
|
|
55
55
|
const d = await this.requestAccess(h.build()), { review_url: g } = b(d);
|
|
56
56
|
return e?.onProgress?.("reviewing"), window.location.href = C(g, l, i), new Promise(() => {
|
|
57
57
|
});
|
|
@@ -133,12 +133,10 @@ class j {
|
|
|
133
133
|
let l = r || {};
|
|
134
134
|
if (n) {
|
|
135
135
|
const i = await this._getAccessTokenRaw();
|
|
136
|
-
|
|
137
|
-
throw new c("auth_error", "Not authenticated. Please log in first.");
|
|
138
|
-
l = {
|
|
136
|
+
i && (l = {
|
|
139
137
|
...l,
|
|
140
138
|
Authorization: `Bearer ${i}`
|
|
141
|
-
};
|
|
139
|
+
});
|
|
142
140
|
}
|
|
143
141
|
return this.bodhiext.sendApiRequest(e, t, s, l);
|
|
144
142
|
})();
|
|
@@ -228,12 +226,12 @@ class j {
|
|
|
228
226
|
*/
|
|
229
227
|
async login(e) {
|
|
230
228
|
const t = await this.getAuthState();
|
|
231
|
-
if (t.status === "authenticated" && !e?.
|
|
229
|
+
if (t.status === "authenticated" && !e?.exchange)
|
|
232
230
|
return t;
|
|
233
231
|
this.ensureBodhiext();
|
|
234
232
|
const s = e?.userRole ?? "scope_user_user", r = this.config.redirectUri;
|
|
235
233
|
e?.onProgress?.("requesting");
|
|
236
|
-
const n = S(), o = await
|
|
234
|
+
const n = S(), o = await p(n), a = S();
|
|
237
235
|
localStorage.setItem(this.storageKeys.CODE_VERIFIER, n), localStorage.setItem(this.storageKeys.STATE, a);
|
|
238
236
|
const l = R(this.authEndpoints, {
|
|
239
237
|
clientId: this.authClientId,
|
|
@@ -241,8 +239,8 @@ class j {
|
|
|
241
239
|
scope: A,
|
|
242
240
|
state: a,
|
|
243
241
|
codeChallenge: o
|
|
244
|
-
}), i =
|
|
245
|
-
e?.requested && h.requested(e.requested);
|
|
242
|
+
}), i = x(r), h = new I(this.authClientId).requestedRole(s);
|
|
243
|
+
e?.requested && h.requested(e.requested), e?.exchange && h.exchange(!0);
|
|
246
244
|
const d = await this.requestAccess(h.build()), { review_url: g } = b(d);
|
|
247
245
|
return e?.onProgress?.("reviewing"), window.location.href = C(g, l, i), new Promise(() => {
|
|
248
246
|
});
|
|
@@ -347,7 +345,7 @@ class j {
|
|
|
347
345
|
try {
|
|
348
346
|
return {
|
|
349
347
|
status: "authenticated",
|
|
350
|
-
user:
|
|
348
|
+
user: k(e),
|
|
351
349
|
accessToken: e,
|
|
352
350
|
error: null,
|
|
353
351
|
refreshToken: null,
|
|
@@ -410,7 +408,7 @@ class j {
|
|
|
410
408
|
);
|
|
411
409
|
if (t.success) {
|
|
412
410
|
this._storeRefreshedTokens(t.tokens);
|
|
413
|
-
const s =
|
|
411
|
+
const s = k(t.tokens.access_token);
|
|
414
412
|
return this.setAuthState({
|
|
415
413
|
status: "authenticated",
|
|
416
414
|
user: s,
|
|
@@ -603,7 +601,7 @@ class j {
|
|
|
603
601
|
"/bodhi/v1/apps/request-access",
|
|
604
602
|
e,
|
|
605
603
|
{},
|
|
606
|
-
!
|
|
604
|
+
!0
|
|
607
605
|
);
|
|
608
606
|
}
|
|
609
607
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bodhiapp/bodhi-js",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.44",
|
|
4
4
|
"description": "Web SDK for Bodhi Browser - window.bodhiext communication",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/bodhi-web.cjs.js",
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
"typecheck": "tsc --noEmit"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@bodhiapp/bodhi-browser-types": "0.0.
|
|
41
|
-
"@bodhiapp/bodhi-js-core": "0.0.
|
|
42
|
-
"@bodhiapp/ts-client": "0.1.
|
|
40
|
+
"@bodhiapp/bodhi-browser-types": "0.0.44",
|
|
41
|
+
"@bodhiapp/bodhi-js-core": "0.0.44",
|
|
42
|
+
"@bodhiapp/ts-client": "0.1.38"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@eslint/js": "^9.23.0",
|