@bodhiapp/bodhi-js 0.0.28 → 0.0.29
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 +74 -80
- package/package.json +3 -3
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 f extends r.DirectClientBase{constructor(e,t){const s=r.createStoragePrefixWithBasePath(e.basePath,r.STORAGE_PREFIXES.WEB_DIRECT),o={authClientId:e.authClientId,authServerUrl:e.authServerUrl,userRole:e.userRole,storagePrefix:s,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 s=e?.userRole??this.userRole,o=e?.flowType??"popup";e?.onProgress?.("requesting");const i=new r.AccessRequestBuilder(this.authClientId).requestedRole(s).flowType(o);if(e?.requested&&i.requested(e.requested),o==="redirect"){const u=e?.redirectUrl??this.redirectUri;i.redirectUrl(u)}const n=i.build(),l=await this.requestAccess(n),{id:c,review_url:a}=r.unwrapResponse(l);e?.onProgress?.("reviewing");let h;if(o==="popup"){const u=async()=>{const E=await this.getAccessRequestStatus(c);if(E.status>=400)return null;const{status:S,access_request_scope:p}=E.body;return S==="approved"?{approved:!0,accessRequestScope:p??void 0}:["denied","failed","expired"].includes(S)?{approved:!1}:null},g=await r.openPopupReview(a,u,{intervalMs:e?.pollIntervalMs??r.DEFAULT_POLL_INTERVAL_MS,timeoutMs:e?.pollTimeoutMs??r.DEFAULT_POLL_TIMEOUT_MS});if(!g.approved)throw r.createOperationError("auth_error","Access request was denied or expired");h=g.accessRequestScope}else return localStorage.setItem(this.storageKeys.ACCESS_REQUEST_ID,c),window.location.href=a,new Promise(()=>{});return e?.onProgress?.("authenticating"),this.performOAuthPkce(`openid profile email roles ${h??""}`.trim())}async performOAuthPkce(e){const t=r.generateCodeVerifier(),s=await r.generateCodeChallenge(t),o=r.generateCodeVerifier();localStorage.setItem(this.storageKeys.CODE_VERIFIER,t),localStorage.setItem(this.storageKeys.STATE,o);const i=new URL(this.authEndpoints.authorize);throw i.searchParams.set("client_id",this.authClientId),i.searchParams.set("response_type","code"),i.searchParams.set("redirect_uri",this.redirectUri),i.searchParams.set("scope",e),i.searchParams.set("code_challenge",s),i.searchParams.set("code_challenge_method","S256"),i.searchParams.set("state",o),window.location.href=i.toString(),new Error("Redirect initiated")}async handleOAuthCallback(e,t){const s=localStorage.getItem(this.storageKeys.STATE);if(!s||s!==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 handleAccessRequestCallback(e){const t=await this.getAccessRequestStatus(e),{status:s,access_request_scope:o}=r.unwrapResponse(t);if(s!=="approved")throw r.createOperationError("auth_error",`Access request is not approved: ${s}`);const i=`openid profile email roles ${o??""}`.trim();return localStorage.removeItem(this.storageKeys.ACCESS_REQUEST_ID),this.performOAuthPkce(i)}async logout(){const e=localStorage.getItem(this.storageKeys.REFRESH_TOKEN);if(e)try{const s=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:s})}catch(s){this.logger.warn("Token revocation failed:",s)}localStorage.removeItem(this.storageKeys.ACCESS_TOKEN),localStorage.removeItem(this.storageKeys.REFRESH_TOKEN),localStorage.removeItem(this.storageKeys.EXPIRES_AT);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 s=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(!s.ok){const i=await s.text();throw new Error(`Token exchange failed: ${s.status} ${i}`)}const o=await s.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 i=Date.now()+o.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,s])=>{localStorage.setItem(t,String(s))})}async _storageRemove(e){e.forEach(t=>localStorage.removeItem(t))}_getRedirectUri(){return this.redirectUri}}const _=500,w=5e3,m=3e4;class R{constructor(e,t,s){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=s??r.NOOP_STATE_CALLBACK;const o=r.createStoragePrefixWithBasePath(t.basePath,r.STORAGE_PREFIXES.WEB_EXT);this.storageKeys=r.createStorageKeys(o),this.apiTimeoutMs=t.apiTimeoutMs??m}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,s,o,i){this.ensureBodhiext();try{const n=new Promise((c,a)=>setTimeout(()=>a(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)),l=(async()=>{let c=o||{};if(i){const a=await this._getAccessTokenRaw();if(!a)throw new r.BodhiError("auth_error","Not authenticated. Please log in first.");c={...c,Authorization:`Bearer ${a}`}}return this.bodhiext.sendApiRequest(e,t,s,c)})();return await Promise.race([l,n])}catch(n){if(n instanceof r.BodhiApiError||n instanceof r.BodhiError)throw n;if(n instanceof Error){const l=n;throw n.name==="BodhiApiError"&&typeof l.status=="number"&&l.body!=null?new r.BodhiApiError(l.status,l.body,n.message,l.headers):n.name==="BodhiError"&&typeof l.code=="string"?new r.BodhiError(l.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 o=e.timeoutMs??this.config.initParams?.extension?.timeoutMs??w,i=e.intervalMs??this.config.initParams?.extension?.intervalMs??_,n=Date.now();if(!await new Promise(c=>{const a=()=>{if(window.bodhiext){this.bodhiext=window.bodhiext,c(!0);return}if(Date.now()-n>=o){c(!1);return}setTimeout(a,i)};a()}))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 s={type:"extension",extension:"ready",extensionId:t,server:r.PENDING_EXTENSION_READY};if(e.testConnection)try{const o=await this.getServerState();this.setState({...s,server:o}),this.logger.info(`Server connectivity tested: ${o.status}`)}catch(o){this.logger.error("Failed to get server state:",o),this.setState({...s,server:r.BACKEND_SERVER_NOT_REACHABLE})}else this.setState(s);return this.state}async login(e){const t=await this.getAuthState();if(t.status==="authenticated")return t;this.ensureBodhiext();const s=e?.userRole??this.config.userRole,o=e?.flowType??"popup";e?.onProgress?.("requesting");const i=new r.AccessRequestBuilder(this.authClientId).requestedRole(s).flowType(o);if(e?.requested&&i.requested(e.requested),o==="redirect"){const u=e?.redirectUrl??this.config.redirectUri;i.redirectUrl(u)}const n=i.build(),l=await this.requestAccess(n),{id:c,review_url:a}=r.unwrapResponse(l);e?.onProgress?.("reviewing");let h;if(o==="popup"){const u=async()=>{const E=await this.getAccessRequestStatus(c);if(E.status>=400)return null;const{status:S,access_request_scope:p}=E.body;return S==="approved"?{approved:!0,accessRequestScope:p??void 0}:["denied","failed","expired"].includes(S)?{approved:!1}:null},g=await r.openPopupReview(a,u,{intervalMs:e?.pollIntervalMs??r.DEFAULT_POLL_INTERVAL_MS,timeoutMs:e?.pollTimeoutMs??r.DEFAULT_POLL_TIMEOUT_MS});if(!g.approved)throw r.createOperationError("auth_error","Access request was denied or expired");h=g.accessRequestScope}else return localStorage.setItem(this.storageKeys.ACCESS_REQUEST_ID,c),window.location.href=a,new Promise(()=>{});return e?.onProgress?.("authenticating"),this.performOAuthPkce(`openid profile email roles ${h??""}`.trim())}async handleOAuthCallback(e,t){const s=localStorage.getItem(this.storageKeys.STATE);if(!s||s!==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 s=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:s});if(!o.ok){const n=await o.text();throw new Error(`Token exchange failed: ${o.status} ${n}`)}const i=await o.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 n=Date.now()+i.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 s=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:s})}catch(s){this.logger.warn("Token revocation failed:",s)}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};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 s=parseInt(t,10);if(Date.now()>=s-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 r.refreshAccessToken(this.authEndpoints.token,e,this.authClientId);if(t.success){this._storeRefreshedTokens(t.tokens);const s=r.extractUserInfo(t.tokens.access_token);return this.setAuthState({status:"authenticated",user:s,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("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,s=t.version||"unknown";switch(t.status){case"ready":return{status:"ready",version:s,error:null,deployment:t.deployment??null,client_id:t.client_id??null};case"setup":return r.backendServerNotReady("setup",s,void 0,t.deployment,t.client_id);case"resource_admin":return r.backendServerNotReady("resource_admin",s,void 0,t.deployment,t.client_id);case"error":return r.backendServerNotReady("error",s,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,s,o,i=!0){this.ensureBodhiext();let n=o||{};if(i){const a=await this._getAccessTokenRaw();if(!a)throw r.createOperationError("auth_error","Not authenticated. Please log in first.");n={...n,Authorization:`Bearer ${a}`}}const c=this.bodhiext.sendStreamRequest(e,t,s,n).getReader();try{for(;;){const{value:a,done:h}=await c.read();if(h||a?.done)break;yield a.body}}catch(a){if(a instanceof r.BodhiApiError||a instanceof r.BodhiError)throw a;if(a instanceof Error){const h=a;throw a.name==="BodhiApiError"&&typeof h.status=="number"&&h.body!=null?new r.BodhiApiError(h.status,h.body,a.message,h.headers):a.name==="BodhiError"&&typeof h.code=="string"?new r.BodhiError(h.code,a.message):new r.BodhiError("extension_error",a.message)}throw a}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)}get toolsets(){return this._toolsets??=new r.Toolsets(this)}get mcps(){return this._mcps??=new r.Mcps(this)}async requestAccess(e){return this.sendApiRequest("POST","/bodhi/v1/apps/request-access",e,{},!1)}async getAccessRequestStatus(e){return this.sendApiRequest("GET",`/bodhi/v1/apps/access-requests/${e}?app_client_id=${encodeURIComponent(this.authClientId)}`,void 0,{},!1)}async pollAccessRequestStatus(e,t){return r.pollAccessRequestUntilResolved(s=>this.getAccessRequestStatus(s),e,t)}async performOAuthPkce(e){const t=r.generateCodeVerifier(),s=await r.generateCodeChallenge(t),o=r.generateCodeVerifier();localStorage.setItem(this.storageKeys.CODE_VERIFIER,t),localStorage.setItem(this.storageKeys.STATE,o);const i=e.split(" ").filter(Boolean),n=new URLSearchParams({response_type:"code",client_id:this.authClientId,redirect_uri:this.config.redirectUri,scope:i.join(" "),state:o,code_challenge:s,code_challenge_method:"S256"});return window.location.href=`${this.authEndpoints.authorize}?${n}`,new Promise(()=>{})}async handleAccessRequestCallback(e){const t=await this.getAccessRequestStatus(e),{status:s,access_request_scope:o}=r.unwrapResponse(t);if(s!=="approved")throw r.createOperationError("auth_error",`Access request is not approved: ${s}`);const i=`openid profile email roles ${o??""}`.trim();return localStorage.removeItem(this.storageKeys.ACCESS_REQUEST_ID),this.performOAuthPkce(i)}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,userRole:this.config.userRole}}}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 y extends r.BaseFacadeClient{constructor(e,t,s){const o=t||{},i={basePath:o.basePath||"/",redirectUri:o.redirectUri||T(o.basePath||"/"),authServerUrl:o.authServerUrl||"https://id.getbodhi.app/realms/bodhi",userRole:o.userRole||"scope_user_user",logLevel:o.logLevel||"warn",apiTimeoutMs:o.apiTimeoutMs,initParams:o.initParams};super(e,i,s)}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 R(this.authClientId,{authServerUrl:e.authServerUrl,redirectUri:e.redirectUri,userRole:e.userRole,basePath:e.basePath,logLevel:e.logLevel,apiTimeoutMs:e.apiTimeoutMs,initParams:e.initParams},t)}createDirectClient(e,t,s){return new f({authClientId:e,authServerUrl:t.authServerUrl,redirectUri:t.redirectUri,userRole:t.userRole,logLevel:t.logLevel,basePath:t.basePath,apiTimeoutMs:t.apiTimeoutMs},s)}async handleOAuthCallback(e,t){return this.connectionMode==="direct"?this.directClient.handleOAuthCallback(e,t):this.extClient.handleOAuthCallback(e,t)}async handleAccessRequestCallback(e){return this.connectionMode==="direct"?this.directClient.handleAccessRequestCallback(e):this.extClient.handleAccessRequestCallback(e)}}const A="production";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 p extends r.DirectClientBase{constructor(e,t){const s=r.createStoragePrefixWithBasePath(e.basePath,r.STORAGE_PREFIXES.WEB_DIRECT),o={authClientId:e.authClientId,authServerUrl:e.authServerUrl,userRole:e.userRole,storagePrefix:s,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 s=e?.userRole??this.userRole,o=e?.flowType??"popup";e?.onProgress?.("requesting");const i=new r.AccessRequestBuilder(this.authClientId).requestedRole(s).flowType(o);if(e?.requested&&i.requested(e.requested),o==="redirect"){const u=e?.redirectUrl??this.redirectUri;i.redirectUrl(u)}const n=i.build(),l=await this.requestAccess(n),{id:c,review_url:a}=r.unwrapResponse(l);e?.onProgress?.("reviewing");let h;if(o==="popup"){const u=async()=>{const S=await this.getAccessRequestStatus(c);if(S.status>=400)return null;const{status:g,access_request_scope:f}=S.body;return g==="approved"?{approved:!0,accessRequestScope:f??void 0}:["denied","failed","expired"].includes(g)?{approved:!1,status:g}:null},d=await r.openPopupReview(a,u,{intervalMs:e?.pollIntervalMs??r.DEFAULT_POLL_INTERVAL_MS,timeoutMs:e?.pollTimeoutMs??r.DEFAULT_POLL_TIMEOUT_MS});d.approved||r.throwAccessRequestDenialError(d.status??"unknown"),h=d.accessRequestScope}else return localStorage.setItem(this.storageKeys.ACCESS_REQUEST_ID,c),window.location.href=a,new Promise(()=>{});return e?.onProgress?.("authenticating"),this.performOAuthPkce(`openid profile email roles ${h??""}`.trim())}async performOAuthPkce(e){const t=r.generateCodeVerifier(),s=await r.generateCodeChallenge(t),o=r.generateCodeVerifier();localStorage.setItem(this.storageKeys.CODE_VERIFIER,t),localStorage.setItem(this.storageKeys.STATE,o);const i=new URL(this.authEndpoints.authorize);throw i.searchParams.set("client_id",this.authClientId),i.searchParams.set("response_type","code"),i.searchParams.set("redirect_uri",this.redirectUri),i.searchParams.set("scope",e),i.searchParams.set("code_challenge",s),i.searchParams.set("code_challenge_method","S256"),i.searchParams.set("state",o),window.location.href=i.toString(),new Error("Redirect initiated")}async handleOAuthCallback(e,t){const s=localStorage.getItem(this.storageKeys.STATE);if(!s||s!==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 handleAccessRequestCallback(e){const t=await this.getAccessRequestStatus(e),{status:s,access_request_scope:o}=r.unwrapResponse(t);localStorage.removeItem(this.storageKeys.ACCESS_REQUEST_ID),s!=="approved"&&r.throwAccessRequestDenialError(s);const i=`openid profile email roles ${o??""}`.trim();return this.performOAuthPkce(i)}async logout(){const e=localStorage.getItem(this.storageKeys.REFRESH_TOKEN);if(e)try{const s=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:s})}catch(s){this.logger.warn("Token revocation failed:",s)}localStorage.removeItem(this.storageKeys.ACCESS_TOKEN),localStorage.removeItem(this.storageKeys.REFRESH_TOKEN),localStorage.removeItem(this.storageKeys.EXPIRES_AT);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 s=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(!s.ok){const i=await s.text();throw new Error(`Token exchange failed: ${s.status} ${i}`)}const o=await s.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 i=Date.now()+o.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,s])=>{localStorage.setItem(t,String(s))})}async _storageRemove(e){e.forEach(t=>localStorage.removeItem(t))}_getRedirectUri(){return this.redirectUri}}const w=500,m=5e3,_=3e4;class R{constructor(e,t,s){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=s??r.NOOP_STATE_CALLBACK;const o=r.createStoragePrefixWithBasePath(t.basePath,r.STORAGE_PREFIXES.WEB_EXT);this.storageKeys=r.createStorageKeys(o),this.apiTimeoutMs=t.apiTimeoutMs??_}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,s,o,i){this.ensureBodhiext();try{const n=new Promise((c,a)=>setTimeout(()=>a(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)),l=(async()=>{let c=o||{};if(i){const a=await this._getAccessTokenRaw();if(!a)throw new r.BodhiError("auth_error","Not authenticated. Please log in first.");c={...c,Authorization:`Bearer ${a}`}}return this.bodhiext.sendApiRequest(e,t,s,c)})();return await Promise.race([l,n])}catch(n){if(n instanceof r.BodhiApiError||n instanceof r.BodhiError)throw n;if(n instanceof Error){const l=n;throw n.name==="BodhiApiError"&&typeof l.status=="number"&&l.body!=null?new r.BodhiApiError(l.status,l.body,n.message,l.headers):n.name==="BodhiError"&&typeof l.code=="string"?new r.BodhiError(l.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 o=e.timeoutMs??this.config.initParams?.extension?.timeoutMs??m,i=e.intervalMs??this.config.initParams?.extension?.intervalMs??w,n=Date.now();if(!await new Promise(c=>{const a=()=>{if(window.bodhiext){this.bodhiext=window.bodhiext,c(!0);return}if(Date.now()-n>=o){c(!1);return}setTimeout(a,i)};a()}))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 s={type:"extension",extension:"ready",extensionId:t,server:r.PENDING_EXTENSION_READY};if(e.testConnection)try{const o=await this.getServerState();this.setState({...s,server:o}),this.logger.info(`Server connectivity tested: ${o.status}`)}catch(o){this.logger.error("Failed to get server state:",o),this.setState({...s,server:r.BACKEND_SERVER_NOT_REACHABLE})}else this.setState(s);return this.state}async login(e){const t=await this.getAuthState();if(t.status==="authenticated")return t;this.ensureBodhiext();const s=e?.userRole??this.config.userRole,o=e?.flowType??"popup";e?.onProgress?.("requesting");const i=new r.AccessRequestBuilder(this.authClientId).requestedRole(s).flowType(o);if(e?.requested&&i.requested(e.requested),o==="redirect"){const u=e?.redirectUrl??this.config.redirectUri;i.redirectUrl(u)}const n=i.build(),l=await this.requestAccess(n),{id:c,review_url:a}=r.unwrapResponse(l);e?.onProgress?.("reviewing");let h;if(o==="popup"){const u=async()=>{const S=await this.getAccessRequestStatus(c);if(S.status>=400)return null;const{status:g,access_request_scope:f}=S.body;return g==="approved"?{approved:!0,accessRequestScope:f??void 0}:["denied","failed","expired"].includes(g)?{approved:!1,status:g}:null},d=await r.openPopupReview(a,u,{intervalMs:e?.pollIntervalMs??r.DEFAULT_POLL_INTERVAL_MS,timeoutMs:e?.pollTimeoutMs??r.DEFAULT_POLL_TIMEOUT_MS});d.approved||r.throwAccessRequestDenialError(d.status??"unknown"),h=d.accessRequestScope}else return localStorage.setItem(this.storageKeys.ACCESS_REQUEST_ID,c),window.location.href=a,new Promise(()=>{});return e?.onProgress?.("authenticating"),this.performOAuthPkce(`openid profile email roles ${h??""}`.trim())}async handleOAuthCallback(e,t){const s=localStorage.getItem(this.storageKeys.STATE);if(!s||s!==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 s=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:s});if(!o.ok){const n=await o.text();throw new Error(`Token exchange failed: ${o.status} ${n}`)}const i=await o.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 n=Date.now()+i.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 s=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:s})}catch(s){this.logger.warn("Token revocation failed:",s)}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};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 s=parseInt(t,10);if(Date.now()>=s-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 r.refreshAccessToken(this.authEndpoints.token,e,this.authClientId);if(t.success){this._storeRefreshedTokens(t.tokens);const s=r.extractUserInfo(t.tokens.access_token);return this.setAuthState({status:"authenticated",user:s,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("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,s=t.version||"unknown";switch(t.status){case"ready":return{status:"ready",version:s,error:null,deployment:t.deployment??null,client_id:t.client_id??null};case"setup":return r.backendServerNotReady("setup",s,void 0,t.deployment,t.client_id);case"resource_admin":return r.backendServerNotReady("resource_admin",s,void 0,t.deployment,t.client_id);case"error":return r.backendServerNotReady("error",s,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,s,o,i=!0){this.ensureBodhiext();let n=o||{};if(i){const a=await this._getAccessTokenRaw();if(!a)throw r.createOperationError("auth_error","Not authenticated. Please log in first.");n={...n,Authorization:`Bearer ${a}`}}const c=this.bodhiext.sendStreamRequest(e,t,s,n).getReader();try{for(;;){const{value:a,done:h}=await c.read();if(h||a?.done)break;yield a.body}}catch(a){if(a instanceof r.BodhiApiError||a instanceof r.BodhiError)throw a;if(a instanceof Error){const h=a;throw a.name==="BodhiApiError"&&typeof h.status=="number"&&h.body!=null?new r.BodhiApiError(h.status,h.body,a.message,h.headers):a.name==="BodhiError"&&typeof h.code=="string"?new r.BodhiError(h.code,a.message):new r.BodhiError("extension_error",a.message)}throw a}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)}get toolsets(){return this._toolsets??=new r.Toolsets(this)}get mcps(){return this._mcps??=new r.Mcps(this)}async requestAccess(e){return this.sendApiRequest("POST","/bodhi/v1/apps/request-access",e,{},!1)}async getAccessRequestStatus(e){return this.sendApiRequest("GET",`/bodhi/v1/apps/access-requests/${e}?app_client_id=${encodeURIComponent(this.authClientId)}`,void 0,{},!1)}async pollAccessRequestStatus(e,t){return r.pollAccessRequestUntilResolved(s=>this.getAccessRequestStatus(s),e,t)}async performOAuthPkce(e){const t=r.generateCodeVerifier(),s=await r.generateCodeChallenge(t),o=r.generateCodeVerifier();localStorage.setItem(this.storageKeys.CODE_VERIFIER,t),localStorage.setItem(this.storageKeys.STATE,o);const i=e.split(" ").filter(Boolean),n=new URLSearchParams({response_type:"code",client_id:this.authClientId,redirect_uri:this.config.redirectUri,scope:i.join(" "),state:o,code_challenge:s,code_challenge_method:"S256"});return window.location.href=`${this.authEndpoints.authorize}?${n}`,new Promise(()=>{})}async handleAccessRequestCallback(e){const t=await this.getAccessRequestStatus(e),{status:s,access_request_scope:o}=r.unwrapResponse(t);localStorage.removeItem(this.storageKeys.ACCESS_REQUEST_ID),s!=="approved"&&r.throwAccessRequestDenialError(s);const i=`openid profile email roles ${o??""}`.trim();return this.performOAuthPkce(i)}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,userRole:this.config.userRole}}}function T(E){if(typeof window>"u")throw new Error("redirectUri required in non-browser environment");const e=E==="/"?"":E.replace(/\/$/,"");return`${window.location.origin}${e}/callback`}class y extends r.BaseFacadeClient{constructor(e,t,s){const o=t||{},i={basePath:o.basePath||"/",redirectUri:o.redirectUri||T(o.basePath||"/"),authServerUrl:o.authServerUrl||"https://id.getbodhi.app/realms/bodhi",userRole:o.userRole||"scope_user_user",logLevel:o.logLevel||"warn",apiTimeoutMs:o.apiTimeoutMs,initParams:o.initParams};super(e,i,s)}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 R(this.authClientId,{authServerUrl:e.authServerUrl,redirectUri:e.redirectUri,userRole:e.userRole,basePath:e.basePath,logLevel:e.logLevel,apiTimeoutMs:e.apiTimeoutMs,initParams:e.initParams},t)}createDirectClient(e,t,s){return new p({authClientId:e,authServerUrl:t.authServerUrl,redirectUri:t.redirectUri,userRole:t.userRole,logLevel:t.logLevel,basePath:t.basePath,apiTimeoutMs:t.apiTimeoutMs},s)}async handleOAuthCallback(e,t){return this.connectionMode==="direct"?this.directClient.handleOAuthCallback(e,t):this.extClient.handleOAuthCallback(e,t)}async handleAccessRequestCallback(e){return this.connectionMode==="direct"?this.directClient.handleAccessRequestCallback(e):this.extClient.handleAccessRequestCallback(e)}}const A="production";exports.WEB_BUILD_MODE=A;exports.WebUIClient=y;
|
package/dist/bodhi-web.esm.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { DirectClientBase as
|
|
2
|
-
class
|
|
1
|
+
import { DirectClientBase as K, createStoragePrefixWithBasePath as I, STORAGE_PREFIXES as A, AccessRequestBuilder as k, unwrapResponse as w, openPopupReview as b, DEFAULT_POLL_TIMEOUT_MS as x, DEFAULT_POLL_INTERVAL_MS as P, throwAccessRequestDenialError as E, generateCodeVerifier as _, generateCodeChallenge as O, EXTENSION_STATE_NOT_INITIALIZED as C, Logger as U, createOAuthEndpoints as q, NOOP_STATE_CALLBACK as L, createStorageKeys as N, createOperationError as R, BodhiError as h, BodhiApiError as m, EXTENSION_STATE_NOT_FOUND as D, PENDING_EXTENSION_READY as M, BACKEND_SERVER_NOT_REACHABLE as p, extractUserInfo as v, refreshAccessToken as F, backendServerNotReady as T, SERVER_ERROR_CODES as B, Chat as $, Models as V, Embeddings as z, Toolsets as H, Mcps as W, pollAccessRequestUntilResolved as X, BaseFacadeClient as G } from "@bodhiapp/bodhi-js-core";
|
|
2
|
+
class j extends K {
|
|
3
3
|
constructor(e, t) {
|
|
4
|
-
const s =
|
|
4
|
+
const s = I(
|
|
5
5
|
e.basePath,
|
|
6
|
-
|
|
6
|
+
A.WEB_DIRECT
|
|
7
7
|
), r = {
|
|
8
8
|
authClientId: e.authClientId,
|
|
9
9
|
authServerUrl: e.authServerUrl,
|
|
@@ -24,34 +24,32 @@ class G extends U {
|
|
|
24
24
|
return t;
|
|
25
25
|
const s = e?.userRole ?? this.userRole, r = e?.flowType ?? "popup";
|
|
26
26
|
e?.onProgress?.("requesting");
|
|
27
|
-
const o = new
|
|
27
|
+
const o = new k(this.authClientId).requestedRole(s).flowType(r);
|
|
28
28
|
if (e?.requested && o.requested(e.requested), r === "redirect") {
|
|
29
|
-
const
|
|
30
|
-
o.redirectUrl(
|
|
29
|
+
const f = e?.redirectUrl ?? this.redirectUri;
|
|
30
|
+
o.redirectUrl(f);
|
|
31
31
|
}
|
|
32
|
-
const a = o.build(), c = await this.requestAccess(a), { id: n, review_url: i } =
|
|
32
|
+
const a = o.build(), c = await this.requestAccess(a), { id: n, review_url: i } = w(c);
|
|
33
33
|
e?.onProgress?.("reviewing");
|
|
34
34
|
let l;
|
|
35
35
|
if (r === "popup") {
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
if (
|
|
39
|
-
const { status:
|
|
40
|
-
return
|
|
36
|
+
const u = await b(i, async () => {
|
|
37
|
+
const S = await this.getAccessRequestStatus(n);
|
|
38
|
+
if (S.status >= 400) return null;
|
|
39
|
+
const { status: d, access_request_scope: y } = S.body;
|
|
40
|
+
return d === "approved" ? { approved: !0, accessRequestScope: y ?? void 0 } : ["denied", "failed", "expired"].includes(d) ? { approved: !1, status: d } : null;
|
|
41
41
|
}, {
|
|
42
|
-
intervalMs: e?.pollIntervalMs ??
|
|
42
|
+
intervalMs: e?.pollIntervalMs ?? P,
|
|
43
43
|
timeoutMs: e?.pollTimeoutMs ?? x
|
|
44
44
|
});
|
|
45
|
-
|
|
46
|
-
throw u("auth_error", "Access request was denied or expired");
|
|
47
|
-
l = f.accessRequestScope;
|
|
45
|
+
u.approved || E(u.status ?? "unknown"), l = u.accessRequestScope;
|
|
48
46
|
} else
|
|
49
47
|
return localStorage.setItem(this.storageKeys.ACCESS_REQUEST_ID, n), window.location.href = i, new Promise(() => {
|
|
50
48
|
});
|
|
51
49
|
return e?.onProgress?.("authenticating"), this.performOAuthPkce(`openid profile email roles ${l ?? ""}`.trim());
|
|
52
50
|
}
|
|
53
51
|
async performOAuthPkce(e) {
|
|
54
|
-
const t = _(), s = await
|
|
52
|
+
const t = _(), s = await O(t), r = _();
|
|
55
53
|
localStorage.setItem(this.storageKeys.CODE_VERIFIER, t), localStorage.setItem(this.storageKeys.STATE, r);
|
|
56
54
|
const o = new URL(this.authEndpoints.authorize);
|
|
57
55
|
throw o.searchParams.set("client_id", this.authClientId), o.searchParams.set("response_type", "code"), o.searchParams.set("redirect_uri", this.redirectUri), o.searchParams.set("scope", e), o.searchParams.set("code_challenge", s), o.searchParams.set("code_challenge_method", "S256"), o.searchParams.set("state", r), window.location.href = o.toString(), new Error("Redirect initiated");
|
|
@@ -67,11 +65,10 @@ class G extends U {
|
|
|
67
65
|
return this.setAuthState(r), r;
|
|
68
66
|
}
|
|
69
67
|
async handleAccessRequestCallback(e) {
|
|
70
|
-
const t = await this.getAccessRequestStatus(e), { status: s, access_request_scope: r } =
|
|
71
|
-
|
|
72
|
-
throw u("auth_error", `Access request is not approved: ${s}`);
|
|
68
|
+
const t = await this.getAccessRequestStatus(e), { status: s, access_request_scope: r } = w(t);
|
|
69
|
+
localStorage.removeItem(this.storageKeys.ACCESS_REQUEST_ID), s !== "approved" && E(s);
|
|
73
70
|
const o = `openid profile email roles ${r ?? ""}`.trim();
|
|
74
|
-
return
|
|
71
|
+
return this.performOAuthPkce(o);
|
|
75
72
|
}
|
|
76
73
|
async logout() {
|
|
77
74
|
const e = localStorage.getItem(this.storageKeys.REFRESH_TOKEN);
|
|
@@ -149,12 +146,12 @@ class G extends U {
|
|
|
149
146
|
return this.redirectUri;
|
|
150
147
|
}
|
|
151
148
|
}
|
|
152
|
-
const
|
|
153
|
-
class
|
|
149
|
+
const Q = 500, Y = 5e3, J = 3e4;
|
|
150
|
+
class Z {
|
|
154
151
|
constructor(e, t, s) {
|
|
155
|
-
this.state =
|
|
156
|
-
const r =
|
|
157
|
-
this.storageKeys =
|
|
152
|
+
this.state = C, this.bodhiext = null, this.refreshPromise = null, this.logger = new U("WindowBodhiextClient", t.logLevel), this.authClientId = e, this.config = t, this.authEndpoints = q(this.config.authServerUrl), this.onStateChange = s ?? L;
|
|
153
|
+
const r = I(t.basePath, A.WEB_EXT);
|
|
154
|
+
this.storageKeys = N(r), this.apiTimeoutMs = t.apiTimeoutMs ?? J;
|
|
158
155
|
}
|
|
159
156
|
/**
|
|
160
157
|
* Set client state and notify callback
|
|
@@ -183,7 +180,7 @@ class J {
|
|
|
183
180
|
*/
|
|
184
181
|
ensureBodhiext() {
|
|
185
182
|
if (!this.bodhiext && window.bodhiext && (this.logger.info("Acquiring window.bodhiext reference"), this.bodhiext = window.bodhiext), !this.bodhiext)
|
|
186
|
-
throw
|
|
183
|
+
throw R("not_initialized", "Client not initialized");
|
|
187
184
|
}
|
|
188
185
|
/**
|
|
189
186
|
* Send extension request via window.bodhiext.sendExtRequest
|
|
@@ -257,11 +254,11 @@ class J {
|
|
|
257
254
|
*/
|
|
258
255
|
async init(e = {}) {
|
|
259
256
|
if (!e.testConnection && !e.selectedConnection)
|
|
260
|
-
return this.logger.info("No testConnection or selectedConnection, returning not-initialized state"),
|
|
257
|
+
return this.logger.info("No testConnection or selectedConnection, returning not-initialized state"), C;
|
|
261
258
|
if (this.bodhiext && !e.testConnection)
|
|
262
259
|
return this.logger.debug("Already have bodhiext handle, skipping polling"), this.state;
|
|
263
260
|
if (!this.bodhiext) {
|
|
264
|
-
const r = e.timeoutMs ?? this.config.initParams?.extension?.timeoutMs ??
|
|
261
|
+
const r = e.timeoutMs ?? this.config.initParams?.extension?.timeoutMs ?? Y, o = e.intervalMs ?? this.config.initParams?.extension?.intervalMs ?? Q, a = Date.now();
|
|
265
262
|
if (!await new Promise((n) => {
|
|
266
263
|
const i = () => {
|
|
267
264
|
if (window.bodhiext) {
|
|
@@ -276,7 +273,7 @@ class J {
|
|
|
276
273
|
};
|
|
277
274
|
i();
|
|
278
275
|
}))
|
|
279
|
-
return this.logger.warn("Extension discovery timed out"), this.setState(
|
|
276
|
+
return this.logger.warn("Extension discovery timed out"), this.setState(D), this.state;
|
|
280
277
|
}
|
|
281
278
|
const t = await this.bodhiext.getExtensionId();
|
|
282
279
|
this.logger.info(`Extension discovered: ${t}`);
|
|
@@ -284,14 +281,14 @@ class J {
|
|
|
284
281
|
type: "extension",
|
|
285
282
|
extension: "ready",
|
|
286
283
|
extensionId: t,
|
|
287
|
-
server:
|
|
284
|
+
server: M
|
|
288
285
|
};
|
|
289
286
|
if (e.testConnection)
|
|
290
287
|
try {
|
|
291
288
|
const r = await this.getServerState();
|
|
292
289
|
this.setState({ ...s, server: r }), this.logger.info(`Server connectivity tested: ${r.status}`);
|
|
293
290
|
} catch (r) {
|
|
294
|
-
this.logger.error("Failed to get server state:", r), this.setState({ ...s, server:
|
|
291
|
+
this.logger.error("Failed to get server state:", r), this.setState({ ...s, server: p });
|
|
295
292
|
}
|
|
296
293
|
else
|
|
297
294
|
this.setState(s);
|
|
@@ -312,27 +309,25 @@ class J {
|
|
|
312
309
|
this.ensureBodhiext();
|
|
313
310
|
const s = e?.userRole ?? this.config.userRole, r = e?.flowType ?? "popup";
|
|
314
311
|
e?.onProgress?.("requesting");
|
|
315
|
-
const o = new
|
|
312
|
+
const o = new k(this.authClientId).requestedRole(s).flowType(r);
|
|
316
313
|
if (e?.requested && o.requested(e.requested), r === "redirect") {
|
|
317
|
-
const
|
|
318
|
-
o.redirectUrl(
|
|
314
|
+
const f = e?.redirectUrl ?? this.config.redirectUri;
|
|
315
|
+
o.redirectUrl(f);
|
|
319
316
|
}
|
|
320
|
-
const a = o.build(), c = await this.requestAccess(a), { id: n, review_url: i } =
|
|
317
|
+
const a = o.build(), c = await this.requestAccess(a), { id: n, review_url: i } = w(c);
|
|
321
318
|
e?.onProgress?.("reviewing");
|
|
322
319
|
let l;
|
|
323
320
|
if (r === "popup") {
|
|
324
|
-
const
|
|
325
|
-
const
|
|
326
|
-
if (
|
|
327
|
-
const { status:
|
|
328
|
-
return
|
|
321
|
+
const u = await b(i, async () => {
|
|
322
|
+
const S = await this.getAccessRequestStatus(n);
|
|
323
|
+
if (S.status >= 400) return null;
|
|
324
|
+
const { status: d, access_request_scope: y } = S.body;
|
|
325
|
+
return d === "approved" ? { approved: !0, accessRequestScope: y ?? void 0 } : ["denied", "failed", "expired"].includes(d) ? { approved: !1, status: d } : null;
|
|
329
326
|
}, {
|
|
330
|
-
intervalMs: e?.pollIntervalMs ??
|
|
327
|
+
intervalMs: e?.pollIntervalMs ?? P,
|
|
331
328
|
timeoutMs: e?.pollTimeoutMs ?? x
|
|
332
329
|
});
|
|
333
|
-
|
|
334
|
-
throw u("auth_error", "Access request was denied or expired");
|
|
335
|
-
l = f.accessRequestScope;
|
|
330
|
+
u.approved || E(u.status ?? "unknown"), l = u.accessRequestScope;
|
|
336
331
|
} else
|
|
337
332
|
return localStorage.setItem(this.storageKeys.ACCESS_REQUEST_ID, n), window.location.href = i, new Promise(() => {
|
|
338
333
|
});
|
|
@@ -425,7 +420,7 @@ class J {
|
|
|
425
420
|
if (!e)
|
|
426
421
|
return { status: "unauthenticated", user: null, accessToken: null, error: null };
|
|
427
422
|
try {
|
|
428
|
-
return { status: "authenticated", user:
|
|
423
|
+
return { status: "authenticated", user: v(e), accessToken: e, error: null };
|
|
429
424
|
} catch (t) {
|
|
430
425
|
return this.logger.error("Failed to parse token:", t), { status: "unauthenticated", user: null, accessToken: null, error: null };
|
|
431
426
|
}
|
|
@@ -467,14 +462,14 @@ class J {
|
|
|
467
462
|
async _doRefreshToken(e) {
|
|
468
463
|
this.logger.debug("Refreshing access token");
|
|
469
464
|
try {
|
|
470
|
-
const t = await
|
|
465
|
+
const t = await F(
|
|
471
466
|
this.authEndpoints.token,
|
|
472
467
|
e,
|
|
473
468
|
this.authClientId
|
|
474
469
|
);
|
|
475
470
|
if (t.success) {
|
|
476
471
|
this._storeRefreshedTokens(t.tokens);
|
|
477
|
-
const s =
|
|
472
|
+
const s = v(t.tokens.access_token);
|
|
478
473
|
return this.setAuthState({
|
|
479
474
|
status: "authenticated",
|
|
480
475
|
user: s,
|
|
@@ -492,7 +487,7 @@ class J {
|
|
|
492
487
|
} catch (t) {
|
|
493
488
|
this.logger.warn("Token refresh failed:", t);
|
|
494
489
|
}
|
|
495
|
-
throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"),
|
|
490
|
+
throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"), R(
|
|
496
491
|
"auth_error",
|
|
497
492
|
"Access token expired and unable to refresh. Try logging out and logging in again."
|
|
498
493
|
);
|
|
@@ -521,7 +516,7 @@ class J {
|
|
|
521
516
|
try {
|
|
522
517
|
const e = await this.sendApiRequest("GET", "/bodhi/v1/info");
|
|
523
518
|
if (e.status >= 400)
|
|
524
|
-
return
|
|
519
|
+
return p;
|
|
525
520
|
const t = e.body, s = t.version || "unknown";
|
|
526
521
|
switch (t.status) {
|
|
527
522
|
case "ready":
|
|
@@ -533,7 +528,7 @@ class J {
|
|
|
533
528
|
client_id: t.client_id ?? null
|
|
534
529
|
};
|
|
535
530
|
case "setup":
|
|
536
|
-
return
|
|
531
|
+
return T(
|
|
537
532
|
"setup",
|
|
538
533
|
s,
|
|
539
534
|
void 0,
|
|
@@ -541,7 +536,7 @@ class J {
|
|
|
541
536
|
t.client_id
|
|
542
537
|
);
|
|
543
538
|
case "resource_admin":
|
|
544
|
-
return
|
|
539
|
+
return T(
|
|
545
540
|
"resource_admin",
|
|
546
541
|
s,
|
|
547
542
|
void 0,
|
|
@@ -549,18 +544,18 @@ class J {
|
|
|
549
544
|
t.client_id
|
|
550
545
|
);
|
|
551
546
|
case "error":
|
|
552
|
-
return
|
|
547
|
+
return T(
|
|
553
548
|
"error",
|
|
554
549
|
s,
|
|
555
|
-
t.error ? { message: t.error.message, type: t.error.type } :
|
|
550
|
+
t.error ? { message: t.error.message, type: t.error.type } : B.SERVER_NOT_READY,
|
|
556
551
|
t.deployment,
|
|
557
552
|
t.client_id
|
|
558
553
|
);
|
|
559
554
|
default:
|
|
560
|
-
return
|
|
555
|
+
return p;
|
|
561
556
|
}
|
|
562
557
|
} catch {
|
|
563
|
-
return
|
|
558
|
+
return p;
|
|
564
559
|
}
|
|
565
560
|
}
|
|
566
561
|
/**
|
|
@@ -573,7 +568,7 @@ class J {
|
|
|
573
568
|
if (o) {
|
|
574
569
|
const i = await this._getAccessTokenRaw();
|
|
575
570
|
if (!i)
|
|
576
|
-
throw
|
|
571
|
+
throw R("auth_error", "Not authenticated. Please log in first.");
|
|
577
572
|
a = {
|
|
578
573
|
...a,
|
|
579
574
|
Authorization: `Bearer ${i}`
|
|
@@ -607,19 +602,19 @@ class J {
|
|
|
607
602
|
// OpenAI-Compatible Namespaced API
|
|
608
603
|
// ============================================================================
|
|
609
604
|
get chat() {
|
|
610
|
-
return this._chat ??= new
|
|
605
|
+
return this._chat ??= new $(this);
|
|
611
606
|
}
|
|
612
607
|
get models() {
|
|
613
|
-
return this._models ??= new
|
|
608
|
+
return this._models ??= new V(this);
|
|
614
609
|
}
|
|
615
610
|
get embeddings() {
|
|
616
|
-
return this._embeddings ??= new
|
|
611
|
+
return this._embeddings ??= new z(this);
|
|
617
612
|
}
|
|
618
613
|
get toolsets() {
|
|
619
|
-
return this._toolsets ??= new
|
|
614
|
+
return this._toolsets ??= new H(this);
|
|
620
615
|
}
|
|
621
616
|
get mcps() {
|
|
622
|
-
return this._mcps ??= new
|
|
617
|
+
return this._mcps ??= new W(this);
|
|
623
618
|
}
|
|
624
619
|
// ============================================================================
|
|
625
620
|
// Access Request Methods
|
|
@@ -643,14 +638,14 @@ class J {
|
|
|
643
638
|
);
|
|
644
639
|
}
|
|
645
640
|
async pollAccessRequestStatus(e, t) {
|
|
646
|
-
return
|
|
641
|
+
return X(
|
|
647
642
|
(s) => this.getAccessRequestStatus(s),
|
|
648
643
|
e,
|
|
649
644
|
t
|
|
650
645
|
);
|
|
651
646
|
}
|
|
652
647
|
async performOAuthPkce(e) {
|
|
653
|
-
const t = _(), s = await
|
|
648
|
+
const t = _(), s = await O(t), r = _();
|
|
654
649
|
localStorage.setItem(this.storageKeys.CODE_VERIFIER, t), localStorage.setItem(this.storageKeys.STATE, r);
|
|
655
650
|
const o = e.split(" ").filter(Boolean), a = new URLSearchParams({
|
|
656
651
|
response_type: "code",
|
|
@@ -665,11 +660,10 @@ class J {
|
|
|
665
660
|
});
|
|
666
661
|
}
|
|
667
662
|
async handleAccessRequestCallback(e) {
|
|
668
|
-
const t = await this.getAccessRequestStatus(e), { status: s, access_request_scope: r } =
|
|
669
|
-
|
|
670
|
-
throw u("auth_error", `Access request is not approved: ${s}`);
|
|
663
|
+
const t = await this.getAccessRequestStatus(e), { status: s, access_request_scope: r } = w(t);
|
|
664
|
+
localStorage.removeItem(this.storageKeys.ACCESS_REQUEST_ID), s !== "approved" && E(s);
|
|
671
665
|
const o = `openid profile email roles ${r ?? ""}`.trim();
|
|
672
|
-
return
|
|
666
|
+
return this.performOAuthPkce(o);
|
|
673
667
|
}
|
|
674
668
|
/**
|
|
675
669
|
* Serialize web extension client state (all transient, nothing to persist)
|
|
@@ -695,17 +689,17 @@ class J {
|
|
|
695
689
|
};
|
|
696
690
|
}
|
|
697
691
|
}
|
|
698
|
-
function
|
|
692
|
+
function ee(g) {
|
|
699
693
|
if (typeof window > "u")
|
|
700
694
|
throw new Error("redirectUri required in non-browser environment");
|
|
701
|
-
const e =
|
|
695
|
+
const e = g === "/" ? "" : g.replace(/\/$/, "");
|
|
702
696
|
return `${window.location.origin}${e}/callback`;
|
|
703
697
|
}
|
|
704
|
-
class
|
|
698
|
+
class se extends G {
|
|
705
699
|
constructor(e, t, s) {
|
|
706
700
|
const r = t || {}, o = {
|
|
707
701
|
basePath: r.basePath || "/",
|
|
708
|
-
redirectUri: r.redirectUri ||
|
|
702
|
+
redirectUri: r.redirectUri || ee(r.basePath || "/"),
|
|
709
703
|
authServerUrl: r.authServerUrl || "https://id.getbodhi.app/realms/bodhi",
|
|
710
704
|
userRole: r.userRole || "scope_user_user",
|
|
711
705
|
logLevel: r.logLevel || "warn",
|
|
@@ -715,13 +709,13 @@ class te extends X {
|
|
|
715
709
|
super(e, o, s);
|
|
716
710
|
}
|
|
717
711
|
createLogger(e) {
|
|
718
|
-
return new
|
|
712
|
+
return new U("WebUIClient", e.logLevel);
|
|
719
713
|
}
|
|
720
714
|
createStoragePrefix(e) {
|
|
721
|
-
return
|
|
715
|
+
return I(e.basePath, A.WEB);
|
|
722
716
|
}
|
|
723
717
|
createExtClient(e, t) {
|
|
724
|
-
return new
|
|
718
|
+
return new Z(
|
|
725
719
|
this.authClientId,
|
|
726
720
|
{
|
|
727
721
|
authServerUrl: e.authServerUrl,
|
|
@@ -736,7 +730,7 @@ class te extends X {
|
|
|
736
730
|
);
|
|
737
731
|
}
|
|
738
732
|
createDirectClient(e, t, s) {
|
|
739
|
-
return new
|
|
733
|
+
return new j(
|
|
740
734
|
{
|
|
741
735
|
authClientId: e,
|
|
742
736
|
authServerUrl: t.authServerUrl,
|
|
@@ -759,8 +753,8 @@ class te extends X {
|
|
|
759
753
|
return this.connectionMode === "direct" ? this.directClient.handleAccessRequestCallback(e) : this.extClient.handleAccessRequestCallback(e);
|
|
760
754
|
}
|
|
761
755
|
}
|
|
762
|
-
const
|
|
756
|
+
const re = "production";
|
|
763
757
|
export {
|
|
764
|
-
|
|
765
|
-
|
|
758
|
+
re as WEB_BUILD_MODE,
|
|
759
|
+
se as WebUIClient
|
|
766
760
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bodhiapp/bodhi-js",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.29",
|
|
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-browser-types": "0.0.
|
|
41
|
-
"@bodhiapp/bodhi-js-core": "0.0.
|
|
40
|
+
"@bodhiapp/bodhi-browser-types": "0.0.29",
|
|
41
|
+
"@bodhiapp/bodhi-js-core": "0.0.29",
|
|
42
42
|
"@bodhiapp/ts-client": "0.1.23"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|