@bodhiapp/bodhi-js 0.0.28 → 0.0.30
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 +61 -75
- package/dist/direct-client.d.ts +0 -2
- package/dist/ext-client.d.ts +2 -5
- package/dist/facade-client.d.ts +1 -3
- 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 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,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??"scope_user_user",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(),h=await this.requestAccess(n),{id:c,review_url:a}=r.unwrapResponse(h);e?.onProgress?.("reviewing");let l;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"),l=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 ${l??""}`.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 m=500,w=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)),h=(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([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 o=e.timeoutMs??this.config.initParams?.extension?.timeoutMs??w,i=e.intervalMs??this.config.initParams?.extension?.intervalMs??m,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??"scope_user_user",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(),h=await this.requestAccess(n),{id:c,review_url:a}=r.unwrapResponse(h);e?.onProgress?.("reviewing");let l;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"),l=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 ${l??""}`.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:l}=await c.read();if(l||a?.done)break;yield a.body}}catch(a){if(a instanceof r.BodhiApiError||a instanceof r.BodhiError)throw a;if(a instanceof Error){const l=a;throw a.name==="BodhiApiError"&&typeof l.status=="number"&&l.body!=null?new r.BodhiApiError(l.status,l.body,a.message,l.headers):a.name==="BodhiError"&&typeof l.code=="string"?new r.BodhiError(l.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 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}}}function y(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 T extends r.BaseFacadeClient{constructor(e,t,s){const o=t||{},i={basePath:o.basePath||"/",redirectUri:o.redirectUri||y(o.basePath||"/"),authServerUrl:o.authServerUrl||"https://id.getbodhi.app/realms/bodhi",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,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,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=T;
|
package/dist/bodhi-web.esm.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import { DirectClientBase as
|
|
2
|
-
class G extends
|
|
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 T, 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 R, SERVER_ERROR_CODES as B, Chat as $, Models as V, Embeddings as z, Mcps as H, pollAccessRequestUntilResolved as W, BaseFacadeClient as X } from "@bodhiapp/bodhi-js-core";
|
|
2
|
+
class G 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,
|
|
10
|
-
userRole: e.userRole,
|
|
11
10
|
storagePrefix: s,
|
|
12
11
|
logLevel: e.logLevel,
|
|
13
12
|
loggerPrefix: "DirectWebClient",
|
|
@@ -22,36 +21,34 @@ class G extends U {
|
|
|
22
21
|
const t = await this.getAuthState();
|
|
23
22
|
if (t.status === "authenticated")
|
|
24
23
|
return t;
|
|
25
|
-
const s = e?.userRole ??
|
|
24
|
+
const s = e?.userRole ?? "scope_user_user", r = e?.flowType ?? "popup";
|
|
26
25
|
e?.onProgress?.("requesting");
|
|
27
|
-
const o = new
|
|
26
|
+
const o = new k(this.authClientId).requestedRole(s).flowType(r);
|
|
28
27
|
if (e?.requested && o.requested(e.requested), r === "redirect") {
|
|
29
|
-
const
|
|
30
|
-
o.redirectUrl(
|
|
28
|
+
const f = e?.redirectUrl ?? this.redirectUri;
|
|
29
|
+
o.redirectUrl(f);
|
|
31
30
|
}
|
|
32
|
-
const a = o.build(), c = await this.requestAccess(a), { id: n, review_url: i } =
|
|
31
|
+
const a = o.build(), c = await this.requestAccess(a), { id: n, review_url: i } = w(c);
|
|
33
32
|
e?.onProgress?.("reviewing");
|
|
34
33
|
let l;
|
|
35
34
|
if (r === "popup") {
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
if (
|
|
39
|
-
const { status:
|
|
40
|
-
return
|
|
35
|
+
const u = await b(i, async () => {
|
|
36
|
+
const S = await this.getAccessRequestStatus(n);
|
|
37
|
+
if (S.status >= 400) return null;
|
|
38
|
+
const { status: d, access_request_scope: y } = S.body;
|
|
39
|
+
return d === "approved" ? { approved: !0, accessRequestScope: y ?? void 0 } : ["denied", "failed", "expired"].includes(d) ? { approved: !1, status: d } : null;
|
|
41
40
|
}, {
|
|
42
|
-
intervalMs: e?.pollIntervalMs ??
|
|
41
|
+
intervalMs: e?.pollIntervalMs ?? P,
|
|
43
42
|
timeoutMs: e?.pollTimeoutMs ?? x
|
|
44
43
|
});
|
|
45
|
-
|
|
46
|
-
throw u("auth_error", "Access request was denied or expired");
|
|
47
|
-
l = f.accessRequestScope;
|
|
44
|
+
u.approved || E(u.status ?? "unknown"), l = u.accessRequestScope;
|
|
48
45
|
} else
|
|
49
46
|
return localStorage.setItem(this.storageKeys.ACCESS_REQUEST_ID, n), window.location.href = i, new Promise(() => {
|
|
50
47
|
});
|
|
51
48
|
return e?.onProgress?.("authenticating"), this.performOAuthPkce(`openid profile email roles ${l ?? ""}`.trim());
|
|
52
49
|
}
|
|
53
50
|
async performOAuthPkce(e) {
|
|
54
|
-
const t = _(), s = await
|
|
51
|
+
const t = _(), s = await O(t), r = _();
|
|
55
52
|
localStorage.setItem(this.storageKeys.CODE_VERIFIER, t), localStorage.setItem(this.storageKeys.STATE, r);
|
|
56
53
|
const o = new URL(this.authEndpoints.authorize);
|
|
57
54
|
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 +64,10 @@ class G extends U {
|
|
|
67
64
|
return this.setAuthState(r), r;
|
|
68
65
|
}
|
|
69
66
|
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}`);
|
|
67
|
+
const t = await this.getAccessRequestStatus(e), { status: s, access_request_scope: r } = w(t);
|
|
68
|
+
localStorage.removeItem(this.storageKeys.ACCESS_REQUEST_ID), s !== "approved" && E(s);
|
|
73
69
|
const o = `openid profile email roles ${r ?? ""}`.trim();
|
|
74
|
-
return
|
|
70
|
+
return this.performOAuthPkce(o);
|
|
75
71
|
}
|
|
76
72
|
async logout() {
|
|
77
73
|
const e = localStorage.getItem(this.storageKeys.REFRESH_TOKEN);
|
|
@@ -152,9 +148,9 @@ class G extends U {
|
|
|
152
148
|
const j = 500, Q = 5e3, Y = 3e4;
|
|
153
149
|
class J {
|
|
154
150
|
constructor(e, t, s) {
|
|
155
|
-
this.state =
|
|
156
|
-
const r =
|
|
157
|
-
this.storageKeys =
|
|
151
|
+
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;
|
|
152
|
+
const r = I(t.basePath, A.WEB_EXT);
|
|
153
|
+
this.storageKeys = N(r), this.apiTimeoutMs = t.apiTimeoutMs ?? Y;
|
|
158
154
|
}
|
|
159
155
|
/**
|
|
160
156
|
* Set client state and notify callback
|
|
@@ -183,7 +179,7 @@ class J {
|
|
|
183
179
|
*/
|
|
184
180
|
ensureBodhiext() {
|
|
185
181
|
if (!this.bodhiext && window.bodhiext && (this.logger.info("Acquiring window.bodhiext reference"), this.bodhiext = window.bodhiext), !this.bodhiext)
|
|
186
|
-
throw
|
|
182
|
+
throw T("not_initialized", "Client not initialized");
|
|
187
183
|
}
|
|
188
184
|
/**
|
|
189
185
|
* Send extension request via window.bodhiext.sendExtRequest
|
|
@@ -257,7 +253,7 @@ class J {
|
|
|
257
253
|
*/
|
|
258
254
|
async init(e = {}) {
|
|
259
255
|
if (!e.testConnection && !e.selectedConnection)
|
|
260
|
-
return this.logger.info("No testConnection or selectedConnection, returning not-initialized state"),
|
|
256
|
+
return this.logger.info("No testConnection or selectedConnection, returning not-initialized state"), C;
|
|
261
257
|
if (this.bodhiext && !e.testConnection)
|
|
262
258
|
return this.logger.debug("Already have bodhiext handle, skipping polling"), this.state;
|
|
263
259
|
if (!this.bodhiext) {
|
|
@@ -276,7 +272,7 @@ class J {
|
|
|
276
272
|
};
|
|
277
273
|
i();
|
|
278
274
|
}))
|
|
279
|
-
return this.logger.warn("Extension discovery timed out"), this.setState(
|
|
275
|
+
return this.logger.warn("Extension discovery timed out"), this.setState(D), this.state;
|
|
280
276
|
}
|
|
281
277
|
const t = await this.bodhiext.getExtensionId();
|
|
282
278
|
this.logger.info(`Extension discovered: ${t}`);
|
|
@@ -284,14 +280,14 @@ class J {
|
|
|
284
280
|
type: "extension",
|
|
285
281
|
extension: "ready",
|
|
286
282
|
extensionId: t,
|
|
287
|
-
server:
|
|
283
|
+
server: M
|
|
288
284
|
};
|
|
289
285
|
if (e.testConnection)
|
|
290
286
|
try {
|
|
291
287
|
const r = await this.getServerState();
|
|
292
288
|
this.setState({ ...s, server: r }), this.logger.info(`Server connectivity tested: ${r.status}`);
|
|
293
289
|
} catch (r) {
|
|
294
|
-
this.logger.error("Failed to get server state:", r), this.setState({ ...s, server:
|
|
290
|
+
this.logger.error("Failed to get server state:", r), this.setState({ ...s, server: p });
|
|
295
291
|
}
|
|
296
292
|
else
|
|
297
293
|
this.setState(s);
|
|
@@ -310,29 +306,27 @@ class J {
|
|
|
310
306
|
if (t.status === "authenticated")
|
|
311
307
|
return t;
|
|
312
308
|
this.ensureBodhiext();
|
|
313
|
-
const s = e?.userRole ??
|
|
309
|
+
const s = e?.userRole ?? "scope_user_user", r = e?.flowType ?? "popup";
|
|
314
310
|
e?.onProgress?.("requesting");
|
|
315
|
-
const o = new
|
|
311
|
+
const o = new k(this.authClientId).requestedRole(s).flowType(r);
|
|
316
312
|
if (e?.requested && o.requested(e.requested), r === "redirect") {
|
|
317
|
-
const
|
|
318
|
-
o.redirectUrl(
|
|
313
|
+
const f = e?.redirectUrl ?? this.config.redirectUri;
|
|
314
|
+
o.redirectUrl(f);
|
|
319
315
|
}
|
|
320
|
-
const a = o.build(), c = await this.requestAccess(a), { id: n, review_url: i } =
|
|
316
|
+
const a = o.build(), c = await this.requestAccess(a), { id: n, review_url: i } = w(c);
|
|
321
317
|
e?.onProgress?.("reviewing");
|
|
322
318
|
let l;
|
|
323
319
|
if (r === "popup") {
|
|
324
|
-
const
|
|
325
|
-
const
|
|
326
|
-
if (
|
|
327
|
-
const { status:
|
|
328
|
-
return
|
|
320
|
+
const u = await b(i, async () => {
|
|
321
|
+
const S = await this.getAccessRequestStatus(n);
|
|
322
|
+
if (S.status >= 400) return null;
|
|
323
|
+
const { status: d, access_request_scope: y } = S.body;
|
|
324
|
+
return d === "approved" ? { approved: !0, accessRequestScope: y ?? void 0 } : ["denied", "failed", "expired"].includes(d) ? { approved: !1, status: d } : null;
|
|
329
325
|
}, {
|
|
330
|
-
intervalMs: e?.pollIntervalMs ??
|
|
326
|
+
intervalMs: e?.pollIntervalMs ?? P,
|
|
331
327
|
timeoutMs: e?.pollTimeoutMs ?? x
|
|
332
328
|
});
|
|
333
|
-
|
|
334
|
-
throw u("auth_error", "Access request was denied or expired");
|
|
335
|
-
l = f.accessRequestScope;
|
|
329
|
+
u.approved || E(u.status ?? "unknown"), l = u.accessRequestScope;
|
|
336
330
|
} else
|
|
337
331
|
return localStorage.setItem(this.storageKeys.ACCESS_REQUEST_ID, n), window.location.href = i, new Promise(() => {
|
|
338
332
|
});
|
|
@@ -425,7 +419,7 @@ class J {
|
|
|
425
419
|
if (!e)
|
|
426
420
|
return { status: "unauthenticated", user: null, accessToken: null, error: null };
|
|
427
421
|
try {
|
|
428
|
-
return { status: "authenticated", user:
|
|
422
|
+
return { status: "authenticated", user: v(e), accessToken: e, error: null };
|
|
429
423
|
} catch (t) {
|
|
430
424
|
return this.logger.error("Failed to parse token:", t), { status: "unauthenticated", user: null, accessToken: null, error: null };
|
|
431
425
|
}
|
|
@@ -467,14 +461,14 @@ class J {
|
|
|
467
461
|
async _doRefreshToken(e) {
|
|
468
462
|
this.logger.debug("Refreshing access token");
|
|
469
463
|
try {
|
|
470
|
-
const t = await
|
|
464
|
+
const t = await F(
|
|
471
465
|
this.authEndpoints.token,
|
|
472
466
|
e,
|
|
473
467
|
this.authClientId
|
|
474
468
|
);
|
|
475
469
|
if (t.success) {
|
|
476
470
|
this._storeRefreshedTokens(t.tokens);
|
|
477
|
-
const s =
|
|
471
|
+
const s = v(t.tokens.access_token);
|
|
478
472
|
return this.setAuthState({
|
|
479
473
|
status: "authenticated",
|
|
480
474
|
user: s,
|
|
@@ -492,7 +486,7 @@ class J {
|
|
|
492
486
|
} catch (t) {
|
|
493
487
|
this.logger.warn("Token refresh failed:", t);
|
|
494
488
|
}
|
|
495
|
-
throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"),
|
|
489
|
+
throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"), T(
|
|
496
490
|
"auth_error",
|
|
497
491
|
"Access token expired and unable to refresh. Try logging out and logging in again."
|
|
498
492
|
);
|
|
@@ -521,7 +515,7 @@ class J {
|
|
|
521
515
|
try {
|
|
522
516
|
const e = await this.sendApiRequest("GET", "/bodhi/v1/info");
|
|
523
517
|
if (e.status >= 400)
|
|
524
|
-
return
|
|
518
|
+
return p;
|
|
525
519
|
const t = e.body, s = t.version || "unknown";
|
|
526
520
|
switch (t.status) {
|
|
527
521
|
case "ready":
|
|
@@ -552,15 +546,15 @@ class J {
|
|
|
552
546
|
return R(
|
|
553
547
|
"error",
|
|
554
548
|
s,
|
|
555
|
-
t.error ? { message: t.error.message, type: t.error.type } :
|
|
549
|
+
t.error ? { message: t.error.message, type: t.error.type } : B.SERVER_NOT_READY,
|
|
556
550
|
t.deployment,
|
|
557
551
|
t.client_id
|
|
558
552
|
);
|
|
559
553
|
default:
|
|
560
|
-
return
|
|
554
|
+
return p;
|
|
561
555
|
}
|
|
562
556
|
} catch {
|
|
563
|
-
return
|
|
557
|
+
return p;
|
|
564
558
|
}
|
|
565
559
|
}
|
|
566
560
|
/**
|
|
@@ -573,7 +567,7 @@ class J {
|
|
|
573
567
|
if (o) {
|
|
574
568
|
const i = await this._getAccessTokenRaw();
|
|
575
569
|
if (!i)
|
|
576
|
-
throw
|
|
570
|
+
throw T("auth_error", "Not authenticated. Please log in first.");
|
|
577
571
|
a = {
|
|
578
572
|
...a,
|
|
579
573
|
Authorization: `Bearer ${i}`
|
|
@@ -607,16 +601,13 @@ class J {
|
|
|
607
601
|
// OpenAI-Compatible Namespaced API
|
|
608
602
|
// ============================================================================
|
|
609
603
|
get chat() {
|
|
610
|
-
return this._chat ??= new
|
|
604
|
+
return this._chat ??= new $(this);
|
|
611
605
|
}
|
|
612
606
|
get models() {
|
|
613
|
-
return this._models ??= new
|
|
607
|
+
return this._models ??= new V(this);
|
|
614
608
|
}
|
|
615
609
|
get embeddings() {
|
|
616
|
-
return this._embeddings ??= new
|
|
617
|
-
}
|
|
618
|
-
get toolsets() {
|
|
619
|
-
return this._toolsets ??= new z(this);
|
|
610
|
+
return this._embeddings ??= new z(this);
|
|
620
611
|
}
|
|
621
612
|
get mcps() {
|
|
622
613
|
return this._mcps ??= new H(this);
|
|
@@ -650,7 +641,7 @@ class J {
|
|
|
650
641
|
);
|
|
651
642
|
}
|
|
652
643
|
async performOAuthPkce(e) {
|
|
653
|
-
const t = _(), s = await
|
|
644
|
+
const t = _(), s = await O(t), r = _();
|
|
654
645
|
localStorage.setItem(this.storageKeys.CODE_VERIFIER, t), localStorage.setItem(this.storageKeys.STATE, r);
|
|
655
646
|
const o = e.split(" ").filter(Boolean), a = new URLSearchParams({
|
|
656
647
|
response_type: "code",
|
|
@@ -665,11 +656,10 @@ class J {
|
|
|
665
656
|
});
|
|
666
657
|
}
|
|
667
658
|
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}`);
|
|
659
|
+
const t = await this.getAccessRequestStatus(e), { status: s, access_request_scope: r } = w(t);
|
|
660
|
+
localStorage.removeItem(this.storageKeys.ACCESS_REQUEST_ID), s !== "approved" && E(s);
|
|
671
661
|
const o = `openid profile email roles ${r ?? ""}`.trim();
|
|
672
|
-
return
|
|
662
|
+
return this.performOAuthPkce(o);
|
|
673
663
|
}
|
|
674
664
|
/**
|
|
675
665
|
* Serialize web extension client state (all transient, nothing to persist)
|
|
@@ -690,15 +680,14 @@ class J {
|
|
|
690
680
|
bodhiextAvailable: this.bodhiext !== null,
|
|
691
681
|
authClientId: this.authClientId,
|
|
692
682
|
authServerUrl: this.config.authServerUrl,
|
|
693
|
-
redirectUri: this.config.redirectUri
|
|
694
|
-
userRole: this.config.userRole
|
|
683
|
+
redirectUri: this.config.redirectUri
|
|
695
684
|
};
|
|
696
685
|
}
|
|
697
686
|
}
|
|
698
|
-
function Z(
|
|
687
|
+
function Z(g) {
|
|
699
688
|
if (typeof window > "u")
|
|
700
689
|
throw new Error("redirectUri required in non-browser environment");
|
|
701
|
-
const e =
|
|
690
|
+
const e = g === "/" ? "" : g.replace(/\/$/, "");
|
|
702
691
|
return `${window.location.origin}${e}/callback`;
|
|
703
692
|
}
|
|
704
693
|
class te extends X {
|
|
@@ -707,7 +696,6 @@ class te extends X {
|
|
|
707
696
|
basePath: r.basePath || "/",
|
|
708
697
|
redirectUri: r.redirectUri || Z(r.basePath || "/"),
|
|
709
698
|
authServerUrl: r.authServerUrl || "https://id.getbodhi.app/realms/bodhi",
|
|
710
|
-
userRole: r.userRole || "scope_user_user",
|
|
711
699
|
logLevel: r.logLevel || "warn",
|
|
712
700
|
apiTimeoutMs: r.apiTimeoutMs,
|
|
713
701
|
initParams: r.initParams
|
|
@@ -715,10 +703,10 @@ class te extends X {
|
|
|
715
703
|
super(e, o, s);
|
|
716
704
|
}
|
|
717
705
|
createLogger(e) {
|
|
718
|
-
return new
|
|
706
|
+
return new U("WebUIClient", e.logLevel);
|
|
719
707
|
}
|
|
720
708
|
createStoragePrefix(e) {
|
|
721
|
-
return
|
|
709
|
+
return I(e.basePath, A.WEB);
|
|
722
710
|
}
|
|
723
711
|
createExtClient(e, t) {
|
|
724
712
|
return new J(
|
|
@@ -726,7 +714,6 @@ class te extends X {
|
|
|
726
714
|
{
|
|
727
715
|
authServerUrl: e.authServerUrl,
|
|
728
716
|
redirectUri: e.redirectUri,
|
|
729
|
-
userRole: e.userRole,
|
|
730
717
|
basePath: e.basePath,
|
|
731
718
|
logLevel: e.logLevel,
|
|
732
719
|
apiTimeoutMs: e.apiTimeoutMs,
|
|
@@ -741,7 +728,6 @@ class te extends X {
|
|
|
741
728
|
authClientId: e,
|
|
742
729
|
authServerUrl: t.authServerUrl,
|
|
743
730
|
redirectUri: t.redirectUri,
|
|
744
|
-
userRole: t.userRole,
|
|
745
731
|
logLevel: t.logLevel,
|
|
746
732
|
basePath: t.basePath,
|
|
747
733
|
apiTimeoutMs: t.apiTimeoutMs
|
package/dist/direct-client.d.ts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { DirectClientBase, AuthState, LoginOptions, LogLevel, StateChangeCallback } from '@bodhiapp/bodhi-js-core';
|
|
2
|
-
import { UserScope } from '@bodhiapp/ts-client';
|
|
3
2
|
/**
|
|
4
3
|
* Configuration for DirectWebClient
|
|
5
4
|
*/
|
|
6
5
|
export interface DirectWebClientConfig {
|
|
7
6
|
authClientId: string;
|
|
8
7
|
authServerUrl: string;
|
|
9
|
-
userRole: UserScope;
|
|
10
8
|
basePath: string;
|
|
11
9
|
logLevel: LogLevel;
|
|
12
10
|
redirectUri: string;
|
package/dist/ext-client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AccessRequestStatusResponse, CreateAccessRequest, CreateAccessRequestResponse, PingResponse
|
|
2
|
-
import { Chat, Models, Embeddings,
|
|
1
|
+
import { AccessRequestStatusResponse, CreateAccessRequest, CreateAccessRequestResponse, PingResponse } from '@bodhiapp/ts-client';
|
|
2
|
+
import { Chat, Models, Embeddings, Mcps, AuthState, BackendServerState, ClientState, ExtensionState, IExtensionClient, InitParams, LoginOptions, LogLevel, StateChangeCallback } from '@bodhiapp/bodhi-js-core';
|
|
3
3
|
import { ApiResponse } from '@bodhiapp/bodhi-browser-types';
|
|
4
4
|
export type SerializedWebExtensionState = {
|
|
5
5
|
extensionId?: string;
|
|
@@ -11,7 +11,6 @@ export type SerializedWebExtensionState = {
|
|
|
11
11
|
export interface WindowBodhiextClientConfig {
|
|
12
12
|
authServerUrl: string;
|
|
13
13
|
redirectUri: string;
|
|
14
|
-
userRole: UserScope;
|
|
15
14
|
basePath: string;
|
|
16
15
|
logLevel: LogLevel;
|
|
17
16
|
apiTimeoutMs?: number;
|
|
@@ -45,7 +44,6 @@ export declare class WindowBodhiextClient implements IExtensionClient {
|
|
|
45
44
|
private _chat;
|
|
46
45
|
private _models;
|
|
47
46
|
private _embeddings;
|
|
48
|
-
private _toolsets;
|
|
49
47
|
private _mcps;
|
|
50
48
|
constructor(authClientId: string, config: WindowBodhiextClientConfig, onStateChange?: StateChangeCallback);
|
|
51
49
|
/**
|
|
@@ -149,7 +147,6 @@ export declare class WindowBodhiextClient implements IExtensionClient {
|
|
|
149
147
|
get chat(): Chat;
|
|
150
148
|
get models(): Models;
|
|
151
149
|
get embeddings(): Embeddings;
|
|
152
|
-
get toolsets(): Toolsets;
|
|
153
150
|
get mcps(): Mcps;
|
|
154
151
|
requestAccess(body: CreateAccessRequest): Promise<ApiResponse<CreateAccessRequestResponse>>;
|
|
155
152
|
getAccessRequestStatus(requestId: string): Promise<ApiResponse<AccessRequestStatusResponse>>;
|
package/dist/facade-client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BaseFacadeClient, Logger, AuthState, IWebUIClient, LogLevel, StateChange, StateChangeCallback
|
|
1
|
+
import { BaseFacadeClient, Logger, AuthState, IWebUIClient, LogLevel, StateChange, StateChangeCallback } from '@bodhiapp/bodhi-js-core';
|
|
2
2
|
import { DirectWebClient } from './direct-client';
|
|
3
3
|
import { WindowBodhiextClient } from './ext-client';
|
|
4
4
|
/**
|
|
@@ -8,7 +8,6 @@ import { WindowBodhiextClient } from './ext-client';
|
|
|
8
8
|
export interface WebClientConfig {
|
|
9
9
|
authServerUrl: string;
|
|
10
10
|
redirectUri: string;
|
|
11
|
-
userRole: UserScope;
|
|
12
11
|
basePath: string;
|
|
13
12
|
logLevel: LogLevel;
|
|
14
13
|
apiTimeoutMs?: number;
|
|
@@ -26,7 +25,6 @@ export interface WebClientConfig {
|
|
|
26
25
|
export interface WebUIClientParams {
|
|
27
26
|
redirectUri?: string;
|
|
28
27
|
authServerUrl?: string;
|
|
29
|
-
userRole?: UserScope;
|
|
30
28
|
basePath?: string;
|
|
31
29
|
logLevel?: LogLevel;
|
|
32
30
|
apiTimeoutMs?: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bodhiapp/bodhi-js",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.30",
|
|
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.30",
|
|
41
|
+
"@bodhiapp/bodhi-js-core": "0.0.30",
|
|
42
|
+
"@bodhiapp/ts-client": "0.1.24"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@eslint/js": "^9.23.0",
|