@bodhiapp/bodhi-js 0.0.38 → 0.0.40
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 +209 -281
- package/dist/direct-client.d.ts +0 -2
- package/dist/ext-client.d.ts +1 -8
- package/dist/facade-client.d.ts +0 -1
- 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 S{async get(e){return localStorage.getItem(e)}async set(e){Object.entries(e).forEach(([t,i])=>{localStorage.setItem(t,String(i))})}async remove(e){e.forEach(t=>localStorage.removeItem(t))}}class w extends r.DirectClientBase{constructor(e,t){const i=r.createStoragePrefixWithNamespace(e.basePath,r.STORAGE_PREFIXES.WEB_DIRECT),s={authClientId:e.authClientId,authServerUrl:e.authServerUrl,storagePrefix:i,logLevel:e.logLevel,loggerPrefix:"DirectWebClient",apiTimeoutMs:e.apiTimeoutMs,storage:e.storage??new S,initialTokens:e.initialTokens};super(s,t),this.redirectUri=e.redirectUri}async login(e){const t=await this.getAuthState();if(t.status==="authenticated")return t;const i=e?.userRole??"scope_user_user",s=e?.flowType??"popup";e?.onProgress?.("requesting");const o=new r.AccessRequestBuilder(this.authClientId).requestedRole(i).flowType(s);if(e?.requested&&o.requested(e.requested),s==="redirect"){const u=e?.redirectUrl??this.redirectUri;o.redirectUrl(u)}const n=o.build(),c=await this.requestAccess(n),{id:l,review_url:a}=r.unwrapResponse(c);e?.onProgress?.("reviewing");let h;if(s==="popup"){const u=async()=>{const p=await this.getAccessRequestStatus(l);if(p.status>=400)return null;const{status:g,access_request_scope:E}=p.body;return g==="approved"?{approved:!0,accessRequestScope:E??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 await this._storageSet({[this.storageKeys.ACCESS_REQUEST_ID]:l}),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(),i=await r.generateCodeChallenge(t),s=r.generateCodeVerifier();await this._storageSet({[this.storageKeys.CODE_VERIFIER]:t,[this.storageKeys.STATE]:s});const o=new URL(this.authEndpoints.authorize);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",i),o.searchParams.set("code_challenge_method","S256"),o.searchParams.set("state",s),window.location.href=o.toString(),new Error("Redirect initiated")}async handleOAuthCallback(e,t){const i=await this._storageGet(this.storageKeys.STATE);if(!i||i!==t)throw new Error("Invalid state parameter - possible CSRF attack");await this.exchangeCodeForTokens(e);const s=await this.getAuthState();if(s.status!=="authenticated")throw new Error("Login failed");return this.setAuthState(s),s}async handleAccessRequestCallback(e){const t=await this.getAccessRequestStatus(e),{status:i,access_request_scope:s}=r.unwrapResponse(t);await this._storageRemove([this.storageKeys.ACCESS_REQUEST_ID]),i!=="approved"&&r.throwAccessRequestDenialError(i);const o=`openid profile email roles ${s??""}`.trim();return this.performOAuthPkce(o)}_getRedirectUri(){return this.redirectUri}}const m=500,_=5e3,T=3e4;class R{constructor(e,t,i){this.state=r.EXTENSION_STATE_NOT_INITIALIZED,this.bodhiext=null,this.refreshPromise=null,this.logger=new r.Logger("WindowBodhiextClient",t.logLevel),this.authClientId=e,this.config=t,this.authEndpoints=r.createOAuthEndpoints(this.config.authServerUrl),this.onStateChange=i??r.NOOP_STATE_CALLBACK;const s=r.createStoragePrefixWithNamespace(t.basePath,r.STORAGE_PREFIXES.WEB_EXT);this.storageKeys=r.createStorageKeys(s),this.apiTimeoutMs=t.apiTimeoutMs??T}setState(e){this.state=e,this.logger.info(`{state: ${JSON.stringify(e)}} - Setting client state`),this.onStateChange({type:"client-state",state:e})}setAuthState(e){this.onStateChange({type:"auth-state",state:e})}setStateCallback(e){this.onStateChange=e}ensureBodhiext(){if(!this.bodhiext&&window.bodhiext&&(this.logger.info("Acquiring window.bodhiext reference"),this.bodhiext=window.bodhiext),!this.bodhiext)throw r.createOperationError("not_initialized","Client not initialized")}async sendExtRequest(e,t){return this.ensureBodhiext(),this.bodhiext.sendExtRequest(e,t)}async sendApiRequest(e,t,i,s,o){this.ensureBodhiext();try{const n=new Promise((l,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)),c=(async()=>{let l=s||{};if(o){const a=await this._getAccessTokenRaw();if(!a)throw new r.BodhiError("auth_error","Not authenticated. Please log in first.");l={...l,Authorization:`Bearer ${a}`}}return this.bodhiext.sendApiRequest(e,t,i,l)})();return await Promise.race([c,n])}catch(n){if(n instanceof r.BodhiApiError||n instanceof r.BodhiError)throw n;if(n instanceof Error){const c=n;throw n.name==="BodhiApiError"&&typeof c.status=="number"&&c.body!=null?new r.BodhiApiError(c.status,c.body,n.message,c.headers):n.name==="BodhiError"&&typeof c.code=="string"?new r.BodhiError(c.code,n.message):new r.BodhiError("network_error",n.message)}throw new r.BodhiError("network_error",String(n))}}getState(){return this.state}isClientInitialized(){return this.state.extension==="ready"}isServerReady(){return this.isClientInitialized()&&this.state.server.status==="ready"}async init(e={}){if(!e.testConnection&&!e.selectedConnection)return this.logger.info("No testConnection or selectedConnection, returning not-initialized state"),r.EXTENSION_STATE_NOT_INITIALIZED;if(this.bodhiext&&!e.testConnection)return this.logger.debug("Already have bodhiext handle, skipping polling"),this.state;if(!this.bodhiext){const s=e.timeoutMs??this.config.initParams?.extension?.timeoutMs??_,o=e.intervalMs??this.config.initParams?.extension?.intervalMs??m,n=Date.now();if(!await new Promise(l=>{const a=()=>{if(window.bodhiext){this.bodhiext=window.bodhiext,l(!0);return}if(Date.now()-n>=s){l(!1);return}setTimeout(a,o)};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 i={type:"extension",extension:"ready",extensionId:t,server:r.PENDING_EXTENSION_READY};if(e.testConnection)try{const s=await this.getServerState();this.setState({...i,server:s}),this.logger.info(`Server connectivity tested: ${s.status}`)}catch(s){this.logger.error("Failed to get server state:",s),this.setState({...i,server:r.BACKEND_SERVER_NOT_REACHABLE})}else this.setState(i);return this.state}async login(e){const t=await this.getAuthState();if(t.status==="authenticated")return t;this.ensureBodhiext();const i=e?.userRole??"scope_user_user",s=e?.flowType??"popup";e?.onProgress?.("requesting");const o=new r.AccessRequestBuilder(this.authClientId).requestedRole(i).flowType(s);if(e?.requested&&o.requested(e.requested),s==="redirect"){const u=e?.redirectUrl??this.config.redirectUri;o.redirectUrl(u)}const n=o.build(),c=await this.requestAccess(n),{id:l,review_url:a}=r.unwrapResponse(c);e?.onProgress?.("reviewing");let h;if(s==="popup"){const u=async()=>{const p=await this.getAccessRequestStatus(l);if(p.status>=400)return null;const{status:g,access_request_scope:E}=p.body;return g==="approved"?{approved:!0,accessRequestScope:E??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,l),window.location.href=a,new Promise(()=>{});return e?.onProgress?.("authenticating"),this.performOAuthPkce(`openid profile email roles ${h??""}`.trim())}async handleOAuthCallback(e,t){const i=localStorage.getItem(this.storageKeys.STATE);if(!i||i!==t)throw new Error("Invalid state parameter - possible CSRF attack");await this.exchangeCodeForTokens(e),localStorage.removeItem(this.storageKeys.CODE_VERIFIER),localStorage.removeItem(this.storageKeys.STATE);const s=await this.getAuthState();if(s.status!=="authenticated")throw new Error("Login failed");return this.setAuthState(s),s}async exchangeCodeForTokens(e){const t=localStorage.getItem(this.storageKeys.CODE_VERIFIER);if(!t)throw new Error("Code verifier not found");const i=new URLSearchParams({grant_type:"authorization_code",client_id:this.authClientId,code:e,redirect_uri:this.config.redirectUri,code_verifier:t}),s=await fetch(this.authEndpoints.token,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:i});if(!s.ok){const n=await s.text();throw new Error(`Token exchange failed: ${s.status} ${n}`)}const o=await s.json();if(!o.access_token)throw new Error("No access token received");if(localStorage.setItem(this.storageKeys.ACCESS_TOKEN,o.access_token),o.refresh_token&&localStorage.setItem(this.storageKeys.REFRESH_TOKEN,o.refresh_token),o.expires_in){const n=Date.now()+o.expires_in*1e3;localStorage.setItem(this.storageKeys.EXPIRES_AT,n.toString())}}async logout(){const e=localStorage.getItem(this.storageKeys.REFRESH_TOKEN);if(e)try{const i=new URLSearchParams({token:e,client_id:this.authClientId,token_type_hint:"refresh_token"});await fetch(this.authEndpoints.revoke,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:i})}catch(i){this.logger.warn("Token revocation failed:",i)}localStorage.removeItem(this.storageKeys.ACCESS_TOKEN),localStorage.removeItem(this.storageKeys.REFRESH_TOKEN),localStorage.removeItem(this.storageKeys.EXPIRES_AT),localStorage.removeItem(this.storageKeys.CODE_VERIFIER),localStorage.removeItem(this.storageKeys.STATE);const t={status:"unauthenticated",user:null,accessToken:null,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!1};return this.setAuthState(t),t}async getAuthState(){const e=await this._getAccessTokenRaw();if(!e)return{status:"unauthenticated",user:null,accessToken:null,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!1};try{return{status:"authenticated",user:r.extractUserInfo(e),accessToken:e,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!1}}catch(t){return this.logger.error("Failed to parse token:",t),{status:"unauthenticated",user:null,accessToken:null,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!1}}}async _getAccessTokenRaw(){const e=localStorage.getItem(this.storageKeys.ACCESS_TOKEN),t=localStorage.getItem(this.storageKeys.EXPIRES_AT);if(!e)return null;if(t){const i=parseInt(t,10);if(Date.now()>=i-5*1e3){const s=localStorage.getItem(this.storageKeys.REFRESH_TOKEN);return s?this._tryRefreshToken(s):null}}return e}async _tryRefreshToken(e){if(this.refreshPromise)return this.logger.debug("Refresh already in progress, returning existing promise"),this.refreshPromise;this.refreshPromise=this._doRefreshToken(e);try{return await this.refreshPromise}finally{this.refreshPromise=null}}async _doRefreshToken(e){this.logger.debug("Refreshing access token");try{const t=await r.refreshAccessToken(this.authEndpoints.token,e,this.authClientId);if(t.success){this._storeRefreshedTokens(t.tokens);const i=r.extractUserInfo(t.tokens.access_token);return this.setAuthState({status:"authenticated",user:i,accessToken:t.tokens.access_token,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!0}),this.logger.info("Token refreshed successfully"),t.tokens.access_token}if(t.error==="invalid_grant")return this.logger.warn("Refresh token expired or revoked, clearing tokens and logging out"),this.clearAuthStorage(),this.setAuthState({status:"unauthenticated",user:null,accessToken:null,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!1}),null}catch(t){this.logger.warn("Token refresh failed:",t)}throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"),r.createOperationError("auth_error","Access token expired and unable to refresh. Try logging out and logging in again.")}clearAuthStorage(){localStorage.removeItem(this.storageKeys.ACCESS_TOKEN),localStorage.removeItem(this.storageKeys.REFRESH_TOKEN),localStorage.removeItem(this.storageKeys.EXPIRES_AT)}_storeRefreshedTokens(e){const t=Date.now()+e.expires_in*1e3;localStorage.setItem(this.storageKeys.ACCESS_TOKEN,e.access_token),localStorage.setItem(this.storageKeys.EXPIRES_AT,String(t)),e.refresh_token&&localStorage.setItem(this.storageKeys.REFRESH_TOKEN,e.refresh_token)}async pingApi(){return this.sendApiRequest("GET","/ping")}async getServerState(){try{const e=await this.sendApiRequest("GET","/bodhi/v1/info");if(e.status>=400)return r.BACKEND_SERVER_NOT_REACHABLE;const t=e.body,i=t.version||"unknown";switch(t.status){case"ready":return{status:"ready",version:i,error:null,deployment:t.deployment??null,client_id:t.client_id??null};case"setup":return r.backendServerNotReady("setup",i,void 0,t.deployment,t.client_id);case"resource_admin":return r.backendServerNotReady("resource_admin",i,void 0,t.deployment,t.client_id);case"error":return r.backendServerNotReady("error",i,t.error?{message:t.error.message,type:t.error.type}:r.SERVER_ERROR_CODES.SERVER_NOT_READY,t.deployment,t.client_id);default:return r.BACKEND_SERVER_NOT_REACHABLE}}catch{return r.BACKEND_SERVER_NOT_REACHABLE}}async*stream(e,t,i,s,o=!0){this.ensureBodhiext();let n=s||{};if(o){const a=await this._getAccessTokenRaw();if(!a)throw r.createOperationError("auth_error","Not authenticated. Please log in first.");n={...n,Authorization:`Bearer ${a}`}}const l=this.bodhiext.sendStreamRequest(e,t,i,n).getReader();try{for(;;){const{value:a,done:h}=await l.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{l.releaseLock()}}async streamText(e,t,i,s,o=!0){this.ensureBodhiext();let n={...s};if(o){const a=await this._getAccessTokenRaw();a&&(n={...n,Authorization:`Bearer ${a}`})}const c=await this.bodhiext.sendStreamText(e,t,i,n);async function*l(a){const h=a.getReader();try{for(;;){const{done:u,value:d}=await h.read();if(u)break;yield d}}finally{h.releaseLock()}}return{status:c.status,headers:c.headers,body:l(c.body)}}get chat(){return this._chat??=new r.Chat(this)}get models(){return this._models??=new r.Models(this)}get embeddings(){return this._embeddings??=new r.Embeddings(this)}get mcps(){return this._mcps??=new r.Mcps(this)}async requestAccess(e){return this.sendApiRequest("POST","/bodhi/v1/apps/request-access",e,{},!1)}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(i=>this.getAccessRequestStatus(i),e,t)}async performOAuthPkce(e){const t=r.generateCodeVerifier(),i=await r.generateCodeChallenge(t),s=r.generateCodeVerifier();localStorage.setItem(this.storageKeys.CODE_VERIFIER,t),localStorage.setItem(this.storageKeys.STATE,s);const o=e.split(" ").filter(Boolean),n=new URLSearchParams({response_type:"code",client_id:this.authClientId,redirect_uri:this.config.redirectUri,scope:o.join(" "),state:s,code_challenge:i,code_challenge_method:"S256"});return window.location.href=`${this.authEndpoints.authorize}?${n}`,new Promise(()=>{})}async handleAccessRequestCallback(e){const t=await this.getAccessRequestStatus(e),{status:i,access_request_scope:s}=r.unwrapResponse(t);localStorage.removeItem(this.storageKeys.ACCESS_REQUEST_ID),i!=="approved"&&r.throwAccessRequestDenialError(i);const o=`openid profile email roles ${s??""}`.trim();return this.performOAuthPkce(o)}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(f){if(typeof window>"u")throw new Error("redirectUri required in non-browser environment");const e=f==="/"?"":f.replace(/\/$/,"");return`${window.location.origin}${e}/callback`}class A extends r.BaseFacadeClient{constructor(e,t,i){const s=t||{},o={basePath:s.basePath||"/",redirectUri:s.redirectUri||y(s.basePath||"/"),authServerUrl:s.authServerUrl||"https://id.getbodhi.app/realms/bodhi",logLevel:s.logLevel||"warn",apiTimeoutMs:s.apiTimeoutMs,storage:s.storage,initialTokens:s.initialTokens,initParams:s.initParams};super(e,o,i)}createLogger(e){return new r.Logger("WebUIClient",e.logLevel)}createStoragePrefix(e){return r.createStoragePrefixWithNamespace(e.basePath,r.STORAGE_PREFIXES.WEB)}createExtClient(e,t){return new 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,i){return new w({authClientId:e,authServerUrl:t.authServerUrl,redirectUri:t.redirectUri,logLevel:t.logLevel,basePath:t.basePath,apiTimeoutMs:t.apiTimeoutMs,storage:t.storage,initialTokens:t.initialTokens},i)}async handleOAuthCallback(e,t){return this.connectionMode==="direct"?this.directClient.handleOAuthCallback(e,t):this.extClient.handleOAuthCallback(e,t)}async handleAccessRequestCallback(e){return this.connectionMode==="direct"?this.directClient.handleAccessRequestCallback(e):this.extClient.handleAccessRequestCallback(e)}}const k="production";Object.defineProperty(exports,"InMemoryStorage",{enumerable:!0,get:()=>r.InMemoryStorage});exports.DirectWebClient=w;exports.LocalStorageAdapter=S;exports.WEB_BUILD_MODE=k;exports.WebUIClient=A;
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("@bodhiapp/bodhi-js-core");class E{async get(e){return localStorage.getItem(e)}async set(e){Object.entries(e).forEach(([t,i])=>{localStorage.setItem(t,String(i))})}async remove(e){e.forEach(t=>localStorage.removeItem(t))}}class f extends r.DirectClientBase{constructor(e,t){const i=r.createStoragePrefixWithNamespace(e.basePath,r.STORAGE_PREFIXES.WEB_DIRECT),s={authClientId:e.authClientId,authServerUrl:e.authServerUrl,storagePrefix:i,logLevel:e.logLevel,loggerPrefix:"DirectWebClient",apiTimeoutMs:e.apiTimeoutMs,storage:e.storage??new E,initialTokens:e.initialTokens};super(s,t),this.redirectUri=e.redirectUri}async login(e){const t=await this.getAuthState();if(t.status==="authenticated")return t;const i=e?.userRole??"scope_user_user",s=this.redirectUri;e?.onProgress?.("requesting");const a=r.generateCodeVerifier(),n=await r.generateCodeChallenge(a),h=r.generateCodeVerifier();await this._storageSet({[this.storageKeys.CODE_VERIFIER]:a,[this.storageKeys.STATE]:h});const c=r.buildAuthorizeUrl(this.authEndpoints,{clientId:this.authClientId,redirectUri:s,scope:r.BASE_OAUTH_SCOPE,state:h,codeChallenge:n}),o=r.buildErrorUrl(s),l=new r.AccessRequestBuilder(this.authClientId).requestedRole(i);e?.requested&&l.requested(e.requested);const d=await this.requestAccess(l.build()),{review_url:g}=r.unwrapResponse(d);return e?.onProgress?.("reviewing"),window.location.href=r.buildReviewUrl(g,c,o),new Promise(()=>{})}async handleOAuthCallback(e,t){const i=await this._storageGet(this.storageKeys.STATE);if(!i||i!==t)throw new Error("Invalid state parameter - possible CSRF attack");await this.exchangeCodeForTokens(e);const s=await this.getAuthState();if(s.status!=="authenticated")throw new Error("Login failed");return this.setAuthState(s),s}_getRedirectUri(){return this.redirectUri}}const S=500,w=5e3,T=3e4;class m{constructor(e,t,i){this.state=r.EXTENSION_STATE_NOT_INITIALIZED,this.bodhiext=null,this.refreshPromise=null,this.logger=new r.Logger("WindowBodhiextClient",t.logLevel),this.authClientId=e,this.config=t,this.authEndpoints=r.createOAuthEndpoints(this.config.authServerUrl),this.onStateChange=i??r.NOOP_STATE_CALLBACK;const s=r.createStoragePrefixWithNamespace(t.basePath,r.STORAGE_PREFIXES.WEB_EXT);this.storageKeys=r.createStorageKeys(s),this.apiTimeoutMs=t.apiTimeoutMs??T}setState(e){this.state=e,this.logger.info(`{state: ${JSON.stringify(e)}} - Setting client state`),this.onStateChange({type:"client-state",state:e})}setAuthState(e){this.onStateChange({type:"auth-state",state:e})}setStateCallback(e){this.onStateChange=e}ensureBodhiext(){if(!this.bodhiext&&window.bodhiext&&(this.logger.info("Acquiring window.bodhiext reference"),this.bodhiext=window.bodhiext),!this.bodhiext)throw r.createOperationError("not_initialized","Client not initialized")}async sendExtRequest(e,t){return this.ensureBodhiext(),this.bodhiext.sendExtRequest(e,t)}async sendApiRequest(e,t,i,s,a){this.ensureBodhiext();try{const n=new Promise((c,o)=>setTimeout(()=>o(new r.BodhiError("timeout_error",`[bodhi-js-sdk/web] network timeout: api request not completed within configured/default timeout of ${this.apiTimeoutMs}ms`)),this.apiTimeoutMs)),h=(async()=>{let c=s||{};if(a){const o=await this._getAccessTokenRaw();if(!o)throw new r.BodhiError("auth_error","Not authenticated. Please log in first.");c={...c,Authorization:`Bearer ${o}`}}return this.bodhiext.sendApiRequest(e,t,i,c)})();return await Promise.race([h,n])}catch(n){if(n instanceof r.BodhiApiError||n instanceof r.BodhiError)throw n;if(n instanceof Error){const h=n;throw n.name==="BodhiApiError"&&typeof h.status=="number"&&h.body!=null?new r.BodhiApiError(h.status,h.body,n.message,h.headers):n.name==="BodhiError"&&typeof h.code=="string"?new r.BodhiError(h.code,n.message):new r.BodhiError("network_error",n.message)}throw new r.BodhiError("network_error",String(n))}}getState(){return this.state}isClientInitialized(){return this.state.extension==="ready"}isServerReady(){return this.isClientInitialized()&&this.state.server.status==="ready"}async init(e={}){if(!e.testConnection&&!e.selectedConnection)return this.logger.info("No testConnection or selectedConnection, returning not-initialized state"),r.EXTENSION_STATE_NOT_INITIALIZED;if(this.bodhiext&&!e.testConnection)return this.logger.debug("Already have bodhiext handle, skipping polling"),this.state;if(!this.bodhiext){const s=e.timeoutMs??this.config.initParams?.extension?.timeoutMs??w,a=e.intervalMs??this.config.initParams?.extension?.intervalMs??S,n=Date.now();if(!await new Promise(c=>{const o=()=>{if(window.bodhiext){this.bodhiext=window.bodhiext,c(!0);return}if(Date.now()-n>=s){c(!1);return}setTimeout(o,a)};o()}))return this.logger.warn("Extension discovery timed out"),this.setState(r.EXTENSION_STATE_NOT_FOUND),this.state}const t=await this.bodhiext.getExtensionId();this.logger.info(`Extension discovered: ${t}`);const i={type:"extension",extension:"ready",extensionId:t,server:r.PENDING_EXTENSION_READY};if(e.testConnection)try{const s=await this.getServerState();this.setState({...i,server:s}),this.logger.info(`Server connectivity tested: ${s.status}`)}catch(s){this.logger.error("Failed to get server state:",s),this.setState({...i,server:r.BACKEND_SERVER_NOT_REACHABLE})}else this.setState(i);return this.state}async login(e){const t=await this.getAuthState();if(t.status==="authenticated")return t;this.ensureBodhiext();const i=e?.userRole??"scope_user_user",s=this.config.redirectUri;e?.onProgress?.("requesting");const a=r.generateCodeVerifier(),n=await r.generateCodeChallenge(a),h=r.generateCodeVerifier();localStorage.setItem(this.storageKeys.CODE_VERIFIER,a),localStorage.setItem(this.storageKeys.STATE,h);const c=r.buildAuthorizeUrl(this.authEndpoints,{clientId:this.authClientId,redirectUri:s,scope:r.BASE_OAUTH_SCOPE,state:h,codeChallenge:n}),o=r.buildErrorUrl(s),l=new r.AccessRequestBuilder(this.authClientId).requestedRole(i);e?.requested&&l.requested(e.requested);const d=await this.requestAccess(l.build()),{review_url:g}=r.unwrapResponse(d);return e?.onProgress?.("reviewing"),window.location.href=r.buildReviewUrl(g,c,o),new Promise(()=>{})}async handleOAuthCallback(e,t){const i=localStorage.getItem(this.storageKeys.STATE);if(!i||i!==t)throw new Error("Invalid state parameter - possible CSRF attack");await this.exchangeCodeForTokens(e),localStorage.removeItem(this.storageKeys.CODE_VERIFIER),localStorage.removeItem(this.storageKeys.STATE);const s=await this.getAuthState();if(s.status!=="authenticated")throw new Error("Login failed");return this.setAuthState(s),s}async exchangeCodeForTokens(e){const t=localStorage.getItem(this.storageKeys.CODE_VERIFIER);if(!t)throw new Error("Code verifier not found");const i=new URLSearchParams({grant_type:"authorization_code",client_id:this.authClientId,code:e,redirect_uri:this.config.redirectUri,code_verifier:t}),s=await fetch(this.authEndpoints.token,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:i});if(!s.ok){const n=await s.text();throw new Error(`Token exchange failed: ${s.status} ${n}`)}const a=await s.json();if(!a.access_token)throw new Error("No access token received");if(localStorage.setItem(this.storageKeys.ACCESS_TOKEN,a.access_token),a.refresh_token&&localStorage.setItem(this.storageKeys.REFRESH_TOKEN,a.refresh_token),a.expires_in){const n=Date.now()+a.expires_in*1e3;localStorage.setItem(this.storageKeys.EXPIRES_AT,n.toString())}}async logout(){const e=localStorage.getItem(this.storageKeys.REFRESH_TOKEN);if(e)try{const i=new URLSearchParams({token:e,client_id:this.authClientId,token_type_hint:"refresh_token"});await fetch(this.authEndpoints.revoke,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:i})}catch(i){this.logger.warn("Token revocation failed:",i)}localStorage.removeItem(this.storageKeys.ACCESS_TOKEN),localStorage.removeItem(this.storageKeys.REFRESH_TOKEN),localStorage.removeItem(this.storageKeys.EXPIRES_AT),localStorage.removeItem(this.storageKeys.CODE_VERIFIER),localStorage.removeItem(this.storageKeys.STATE);const t={status:"unauthenticated",user:null,accessToken:null,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!1};return this.setAuthState(t),t}async getAuthState(){const e=await this._getAccessTokenRaw();if(!e)return{status:"unauthenticated",user:null,accessToken:null,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!1};try{return{status:"authenticated",user:r.extractUserInfo(e),accessToken:e,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!1}}catch(t){return this.logger.error("Failed to parse token:",t),{status:"unauthenticated",user:null,accessToken:null,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!1}}}async _getAccessTokenRaw(){const e=localStorage.getItem(this.storageKeys.ACCESS_TOKEN),t=localStorage.getItem(this.storageKeys.EXPIRES_AT);if(!e)return null;if(t){const i=parseInt(t,10);if(Date.now()>=i-5*1e3){const s=localStorage.getItem(this.storageKeys.REFRESH_TOKEN);return s?this._tryRefreshToken(s):null}}return e}async _tryRefreshToken(e){if(this.refreshPromise)return this.logger.debug("Refresh already in progress, returning existing promise"),this.refreshPromise;this.refreshPromise=this._doRefreshToken(e);try{return await this.refreshPromise}finally{this.refreshPromise=null}}async _doRefreshToken(e){this.logger.debug("Refreshing access token");try{const t=await r.refreshAccessToken(this.authEndpoints.token,e,this.authClientId);if(t.success){this._storeRefreshedTokens(t.tokens);const i=r.extractUserInfo(t.tokens.access_token);return this.setAuthState({status:"authenticated",user:i,accessToken:t.tokens.access_token,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!0}),this.logger.info("Token refreshed successfully"),t.tokens.access_token}if(t.error==="invalid_grant")return this.logger.warn("Refresh token expired or revoked, clearing tokens and logging out"),this.clearAuthStorage(),this.setAuthState({status:"unauthenticated",user:null,accessToken:null,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!1}),null}catch(t){this.logger.warn("Token refresh failed:",t)}throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"),r.createOperationError("auth_error","Access token expired and unable to refresh. Try logging out and logging in again.")}clearAuthStorage(){localStorage.removeItem(this.storageKeys.ACCESS_TOKEN),localStorage.removeItem(this.storageKeys.REFRESH_TOKEN),localStorage.removeItem(this.storageKeys.EXPIRES_AT)}_storeRefreshedTokens(e){const t=Date.now()+e.expires_in*1e3;localStorage.setItem(this.storageKeys.ACCESS_TOKEN,e.access_token),localStorage.setItem(this.storageKeys.EXPIRES_AT,String(t)),e.refresh_token&&localStorage.setItem(this.storageKeys.REFRESH_TOKEN,e.refresh_token)}async pingApi(){return this.sendApiRequest("GET","/ping")}async getServerState(){try{const e=await this.sendApiRequest("GET","/bodhi/v1/info");if(e.status>=400)return r.BACKEND_SERVER_NOT_REACHABLE;const t=e.body,i=t.version||"unknown";switch(t.status){case"ready":return{status:"ready",version:i,error:null,deployment:t.deployment??null,client_id:t.client_id??null};case"setup":return r.backendServerNotReady("setup",i,void 0,t.deployment,t.client_id);case"resource_admin":return r.backendServerNotReady("resource_admin",i,void 0,t.deployment,t.client_id);case"error":return r.backendServerNotReady("error",i,t.error?{message:t.error.message,type:t.error.type}:r.SERVER_ERROR_CODES.SERVER_NOT_READY,t.deployment,t.client_id);default:return r.BACKEND_SERVER_NOT_REACHABLE}}catch{return r.BACKEND_SERVER_NOT_REACHABLE}}async*stream(e,t,i,s,a=!0){this.ensureBodhiext();let n=s||{};if(a){const o=await this._getAccessTokenRaw();if(!o)throw r.createOperationError("auth_error","Not authenticated. Please log in first.");n={...n,Authorization:`Bearer ${o}`}}const c=this.bodhiext.sendStreamRequest(e,t,i,n).getReader();try{for(;;){const{value:o,done:l}=await c.read();if(l||o?.done)break;yield o.body}}catch(o){if(o instanceof r.BodhiApiError||o instanceof r.BodhiError)throw o;if(o instanceof Error){const l=o;throw o.name==="BodhiApiError"&&typeof l.status=="number"&&l.body!=null?new r.BodhiApiError(l.status,l.body,o.message,l.headers):o.name==="BodhiError"&&typeof l.code=="string"?new r.BodhiError(l.code,o.message):new r.BodhiError("extension_error",o.message)}throw o}finally{c.releaseLock()}}async streamText(e,t,i,s,a=!0){this.ensureBodhiext();let n={...s};if(a){const o=await this._getAccessTokenRaw();o&&(n={...n,Authorization:`Bearer ${o}`})}const h=await this.bodhiext.sendStreamText(e,t,i,n);async function*c(o){const l=o.getReader();try{for(;;){const{done:d,value:g}=await l.read();if(d)break;yield g}}finally{l.releaseLock()}}return{status:h.status,headers:h.headers,body:c(h.body)}}get chat(){return this._chat??=new r.Chat(this)}get models(){return this._models??=new r.Models(this)}get embeddings(){return this._embeddings??=new r.Embeddings(this)}get mcps(){return this._mcps??=new r.Mcps(this)}async requestAccess(e){return this.sendApiRequest("POST","/bodhi/v1/apps/request-access",e,{},!1)}serialize(){return{extensionId:this.state.type==="extension"&&this.state.extension==="ready"?this.state.extensionId:void 0}}async debug(){return{type:"WindowBodhiextClient",state:this.state,authState:await this.getAuthState(),bodhiextAvailable:this.bodhiext!==null,authClientId:this.authClientId,authServerUrl:this.config.authServerUrl,redirectUri:this.config.redirectUri}}}function _(u){if(typeof window>"u")throw new Error("redirectUri required in non-browser environment");const e=u==="/"?"":u.replace(/\/$/,"");return`${window.location.origin}${e}/callback`}class y extends r.BaseFacadeClient{constructor(e,t,i){const s=t||{},a={basePath:s.basePath||"/",redirectUri:s.redirectUri||_(s.basePath||"/"),authServerUrl:s.authServerUrl||"https://id.getbodhi.app/realms/bodhi",logLevel:s.logLevel||"warn",apiTimeoutMs:s.apiTimeoutMs,storage:s.storage,initialTokens:s.initialTokens,initParams:s.initParams};super(e,a,i)}createLogger(e){return new r.Logger("WebUIClient",e.logLevel)}createStoragePrefix(e){return r.createStoragePrefixWithNamespace(e.basePath,r.STORAGE_PREFIXES.WEB)}createExtClient(e,t){return new m(this.authClientId,{authServerUrl:e.authServerUrl,redirectUri:e.redirectUri,basePath:e.basePath,logLevel:e.logLevel,apiTimeoutMs:e.apiTimeoutMs,initParams:e.initParams},t)}createDirectClient(e,t,i){return new f({authClientId:e,authServerUrl:t.authServerUrl,redirectUri:t.redirectUri,logLevel:t.logLevel,basePath:t.basePath,apiTimeoutMs:t.apiTimeoutMs,storage:t.storage,initialTokens:t.initialTokens},i)}async handleOAuthCallback(e,t){return this.connectionMode==="direct"?this.directClient.handleOAuthCallback(e,t):this.extClient.handleOAuthCallback(e,t)}}const A="production";Object.defineProperty(exports,"InMemoryStorage",{enumerable:!0,get:()=>r.InMemoryStorage});exports.DirectWebClient=f;exports.LocalStorageAdapter=E;exports.WEB_BUILD_MODE=A;exports.WebUIClient=y;
|
package/dist/bodhi-web.esm.js
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
import { DirectClientBase as
|
|
2
|
-
import { InMemoryStorage as
|
|
3
|
-
class
|
|
1
|
+
import { DirectClientBase as P, createStoragePrefixWithNamespace as T, STORAGE_PREFIXES as y, generateCodeVerifier as S, generateCodeChallenge as k, buildAuthorizeUrl as R, BASE_OAUTH_SCOPE as A, buildErrorUrl as I, AccessRequestBuilder as x, unwrapResponse as b, buildReviewUrl as C, EXTENSION_STATE_NOT_INITIALIZED as _, Logger as v, createOAuthEndpoints as U, NOOP_STATE_CALLBACK as O, createStorageKeys as K, createOperationError as E, BodhiError as c, BodhiApiError as f, EXTENSION_STATE_NOT_FOUND as N, PENDING_EXTENSION_READY as B, BACKEND_SERVER_NOT_REACHABLE as w, extractUserInfo as p, refreshAccessToken as q, backendServerNotReady as m, SERVER_ERROR_CODES as L, Chat as D, Models as M, Embeddings as F, Mcps as z, BaseFacadeClient as V } from "@bodhiapp/bodhi-js-core";
|
|
2
|
+
import { InMemoryStorage as re } from "@bodhiapp/bodhi-js-core";
|
|
3
|
+
class W {
|
|
4
4
|
async get(e) {
|
|
5
5
|
return localStorage.getItem(e);
|
|
6
6
|
}
|
|
7
7
|
async set(e) {
|
|
8
|
-
Object.entries(e).forEach(([t,
|
|
9
|
-
localStorage.setItem(t, String(
|
|
8
|
+
Object.entries(e).forEach(([t, s]) => {
|
|
9
|
+
localStorage.setItem(t, String(s));
|
|
10
10
|
});
|
|
11
11
|
}
|
|
12
12
|
async remove(e) {
|
|
13
13
|
e.forEach((t) => localStorage.removeItem(t));
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
|
-
class
|
|
16
|
+
class $ extends P {
|
|
17
17
|
constructor(e, t) {
|
|
18
|
-
const
|
|
18
|
+
const s = T(
|
|
19
19
|
e.basePath,
|
|
20
|
-
|
|
21
|
-
),
|
|
20
|
+
y.WEB_DIRECT
|
|
21
|
+
), r = {
|
|
22
22
|
authClientId: e.authClientId,
|
|
23
23
|
authServerUrl: e.authServerUrl,
|
|
24
|
-
storagePrefix:
|
|
24
|
+
storagePrefix: s,
|
|
25
25
|
logLevel: e.logLevel,
|
|
26
26
|
loggerPrefix: "DirectWebClient",
|
|
27
27
|
apiTimeoutMs: e.apiTimeoutMs,
|
|
28
|
-
storage: e.storage ?? new
|
|
28
|
+
storage: e.storage ?? new W(),
|
|
29
29
|
initialTokens: e.initialTokens
|
|
30
30
|
};
|
|
31
|
-
super(
|
|
31
|
+
super(r, t), this.redirectUri = e.redirectUri;
|
|
32
32
|
}
|
|
33
33
|
// ============================================================================
|
|
34
34
|
// Authentication (Browser Redirect OAuth)
|
|
@@ -37,67 +37,45 @@ class j extends O {
|
|
|
37
37
|
const t = await this.getAuthState();
|
|
38
38
|
if (t.status === "authenticated")
|
|
39
39
|
return t;
|
|
40
|
-
const
|
|
40
|
+
const s = e?.userRole ?? "scope_user_user", r = this.redirectUri;
|
|
41
41
|
e?.onProgress?.("requesting");
|
|
42
|
-
const
|
|
43
|
-
if (e?.requested && i.requested(e.requested), s === "redirect") {
|
|
44
|
-
const d = e?.redirectUrl ?? this.redirectUri;
|
|
45
|
-
i.redirectUrl(d);
|
|
46
|
-
}
|
|
47
|
-
const a = i.build(), n = await this.requestAccess(a), { id: c, review_url: o } = S(n);
|
|
48
|
-
e?.onProgress?.("reviewing");
|
|
49
|
-
let l;
|
|
50
|
-
if (s === "popup") {
|
|
51
|
-
const h = await b(o, async () => {
|
|
52
|
-
const p = await this.getAccessRequestStatus(c);
|
|
53
|
-
if (p.status >= 400) return null;
|
|
54
|
-
const { status: g, access_request_scope: T } = p.body;
|
|
55
|
-
return g === "approved" ? { approved: !0, accessRequestScope: T ?? void 0 } : ["denied", "failed", "expired"].includes(g) ? { approved: !1, status: g } : null;
|
|
56
|
-
}, {
|
|
57
|
-
intervalMs: e?.pollIntervalMs ?? P,
|
|
58
|
-
timeoutMs: e?.pollTimeoutMs ?? x
|
|
59
|
-
});
|
|
60
|
-
h.approved || E(h.status ?? "unknown"), l = h.accessRequestScope;
|
|
61
|
-
} else
|
|
62
|
-
return await this._storageSet({ [this.storageKeys.ACCESS_REQUEST_ID]: c }), window.location.href = o, new Promise(() => {
|
|
63
|
-
});
|
|
64
|
-
return e?.onProgress?.("authenticating"), this.performOAuthPkce(`openid profile email roles ${l ?? ""}`.trim());
|
|
65
|
-
}
|
|
66
|
-
async performOAuthPkce(e) {
|
|
67
|
-
const t = _(), r = await U(t), s = _();
|
|
42
|
+
const n = S(), o = await k(n), a = S();
|
|
68
43
|
await this._storageSet({
|
|
69
|
-
[this.storageKeys.CODE_VERIFIER]:
|
|
70
|
-
[this.storageKeys.STATE]:
|
|
44
|
+
[this.storageKeys.CODE_VERIFIER]: n,
|
|
45
|
+
[this.storageKeys.STATE]: a
|
|
46
|
+
});
|
|
47
|
+
const l = R(this.authEndpoints, {
|
|
48
|
+
clientId: this.authClientId,
|
|
49
|
+
redirectUri: r,
|
|
50
|
+
scope: A,
|
|
51
|
+
state: a,
|
|
52
|
+
codeChallenge: o
|
|
53
|
+
}), i = I(r), h = new x(this.authClientId).requestedRole(s);
|
|
54
|
+
e?.requested && h.requested(e.requested);
|
|
55
|
+
const d = await this.requestAccess(h.build()), { review_url: g } = b(d);
|
|
56
|
+
return e?.onProgress?.("reviewing"), window.location.href = C(g, l, i), new Promise(() => {
|
|
71
57
|
});
|
|
72
|
-
const i = new URL(this.authEndpoints.authorize);
|
|
73
|
-
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", r), i.searchParams.set("code_challenge_method", "S256"), i.searchParams.set("state", s), window.location.href = i.toString(), new Error("Redirect initiated");
|
|
74
58
|
}
|
|
75
59
|
async handleOAuthCallback(e, t) {
|
|
76
|
-
const
|
|
77
|
-
if (!
|
|
60
|
+
const s = await this._storageGet(this.storageKeys.STATE);
|
|
61
|
+
if (!s || s !== t)
|
|
78
62
|
throw new Error("Invalid state parameter - possible CSRF attack");
|
|
79
63
|
await this.exchangeCodeForTokens(e);
|
|
80
|
-
const
|
|
81
|
-
if (
|
|
64
|
+
const r = await this.getAuthState();
|
|
65
|
+
if (r.status !== "authenticated")
|
|
82
66
|
throw new Error("Login failed");
|
|
83
|
-
return this.setAuthState(
|
|
84
|
-
}
|
|
85
|
-
async handleAccessRequestCallback(e) {
|
|
86
|
-
const t = await this.getAccessRequestStatus(e), { status: r, access_request_scope: s } = S(t);
|
|
87
|
-
await this._storageRemove([this.storageKeys.ACCESS_REQUEST_ID]), r !== "approved" && E(r);
|
|
88
|
-
const i = `openid profile email roles ${s ?? ""}`.trim();
|
|
89
|
-
return this.performOAuthPkce(i);
|
|
67
|
+
return this.setAuthState(r), r;
|
|
90
68
|
}
|
|
91
69
|
_getRedirectUri() {
|
|
92
70
|
return this.redirectUri;
|
|
93
71
|
}
|
|
94
72
|
}
|
|
95
|
-
const
|
|
96
|
-
class
|
|
97
|
-
constructor(e, t,
|
|
98
|
-
this.state =
|
|
99
|
-
const
|
|
100
|
-
this.storageKeys =
|
|
73
|
+
const H = 500, X = 5e3, G = 3e4;
|
|
74
|
+
class j {
|
|
75
|
+
constructor(e, t, s) {
|
|
76
|
+
this.state = _, this.bodhiext = null, this.refreshPromise = null, this.logger = new v("WindowBodhiextClient", t.logLevel), this.authClientId = e, this.config = t, this.authEndpoints = U(this.config.authServerUrl), this.onStateChange = s ?? O;
|
|
77
|
+
const r = T(t.basePath, y.WEB_EXT);
|
|
78
|
+
this.storageKeys = K(r), this.apiTimeoutMs = t.apiTimeoutMs ?? G;
|
|
101
79
|
}
|
|
102
80
|
/**
|
|
103
81
|
* Set client state and notify callback
|
|
@@ -126,7 +104,7 @@ class Z {
|
|
|
126
104
|
*/
|
|
127
105
|
ensureBodhiext() {
|
|
128
106
|
if (!this.bodhiext && window.bodhiext && (this.logger.info("Acquiring window.bodhiext reference"), this.bodhiext = window.bodhiext), !this.bodhiext)
|
|
129
|
-
throw
|
|
107
|
+
throw E("not_initialized", "Client not initialized");
|
|
130
108
|
}
|
|
131
109
|
/**
|
|
132
110
|
* Send extension request via window.bodhiext.sendExtRequest
|
|
@@ -138,45 +116,45 @@ class Z {
|
|
|
138
116
|
* Send API message via window.bodhiext.sendApiRequest
|
|
139
117
|
* @throws BodhiError on operational errors (extension not ready, auth, network, timeout)
|
|
140
118
|
*/
|
|
141
|
-
async sendApiRequest(e, t,
|
|
119
|
+
async sendApiRequest(e, t, s, r, n) {
|
|
142
120
|
this.ensureBodhiext();
|
|
143
121
|
try {
|
|
144
|
-
const
|
|
145
|
-
(
|
|
146
|
-
() =>
|
|
147
|
-
new
|
|
122
|
+
const o = new Promise(
|
|
123
|
+
(l, i) => setTimeout(
|
|
124
|
+
() => i(
|
|
125
|
+
new c(
|
|
148
126
|
"timeout_error",
|
|
149
127
|
`[bodhi-js-sdk/web] network timeout: api request not completed within configured/default timeout of ${this.apiTimeoutMs}ms`
|
|
150
128
|
)
|
|
151
129
|
),
|
|
152
130
|
this.apiTimeoutMs
|
|
153
131
|
)
|
|
154
|
-
),
|
|
155
|
-
let
|
|
156
|
-
if (
|
|
157
|
-
const
|
|
158
|
-
if (!
|
|
159
|
-
throw new
|
|
160
|
-
|
|
161
|
-
...
|
|
162
|
-
Authorization: `Bearer ${
|
|
132
|
+
), a = (async () => {
|
|
133
|
+
let l = r || {};
|
|
134
|
+
if (n) {
|
|
135
|
+
const i = await this._getAccessTokenRaw();
|
|
136
|
+
if (!i)
|
|
137
|
+
throw new c("auth_error", "Not authenticated. Please log in first.");
|
|
138
|
+
l = {
|
|
139
|
+
...l,
|
|
140
|
+
Authorization: `Bearer ${i}`
|
|
163
141
|
};
|
|
164
142
|
}
|
|
165
|
-
return this.bodhiext.sendApiRequest(e, t,
|
|
143
|
+
return this.bodhiext.sendApiRequest(e, t, s, l);
|
|
166
144
|
})();
|
|
167
|
-
return await Promise.race([
|
|
168
|
-
} catch (
|
|
169
|
-
if (
|
|
170
|
-
if (
|
|
171
|
-
const
|
|
172
|
-
throw
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
) :
|
|
145
|
+
return await Promise.race([a, o]);
|
|
146
|
+
} catch (o) {
|
|
147
|
+
if (o instanceof f || o instanceof c) throw o;
|
|
148
|
+
if (o instanceof Error) {
|
|
149
|
+
const a = o;
|
|
150
|
+
throw o.name === "BodhiApiError" && typeof a.status == "number" && a.body != null ? new f(
|
|
151
|
+
a.status,
|
|
152
|
+
a.body,
|
|
153
|
+
o.message,
|
|
154
|
+
a.headers
|
|
155
|
+
) : o.name === "BodhiError" && typeof a.code == "string" ? new c(a.code, o.message) : new c("network_error", o.message);
|
|
178
156
|
}
|
|
179
|
-
throw new
|
|
157
|
+
throw new c("network_error", String(o));
|
|
180
158
|
}
|
|
181
159
|
}
|
|
182
160
|
/**
|
|
@@ -200,44 +178,44 @@ class Z {
|
|
|
200
178
|
*/
|
|
201
179
|
async init(e = {}) {
|
|
202
180
|
if (!e.testConnection && !e.selectedConnection)
|
|
203
|
-
return this.logger.info("No testConnection or selectedConnection, returning not-initialized state"),
|
|
181
|
+
return this.logger.info("No testConnection or selectedConnection, returning not-initialized state"), _;
|
|
204
182
|
if (this.bodhiext && !e.testConnection)
|
|
205
183
|
return this.logger.debug("Already have bodhiext handle, skipping polling"), this.state;
|
|
206
184
|
if (!this.bodhiext) {
|
|
207
|
-
const
|
|
208
|
-
if (!await new Promise((
|
|
209
|
-
const
|
|
185
|
+
const r = e.timeoutMs ?? this.config.initParams?.extension?.timeoutMs ?? X, n = e.intervalMs ?? this.config.initParams?.extension?.intervalMs ?? H, o = Date.now();
|
|
186
|
+
if (!await new Promise((l) => {
|
|
187
|
+
const i = () => {
|
|
210
188
|
if (window.bodhiext) {
|
|
211
|
-
this.bodhiext = window.bodhiext,
|
|
189
|
+
this.bodhiext = window.bodhiext, l(!0);
|
|
212
190
|
return;
|
|
213
191
|
}
|
|
214
|
-
if (Date.now() -
|
|
215
|
-
|
|
192
|
+
if (Date.now() - o >= r) {
|
|
193
|
+
l(!1);
|
|
216
194
|
return;
|
|
217
195
|
}
|
|
218
|
-
setTimeout(
|
|
196
|
+
setTimeout(i, n);
|
|
219
197
|
};
|
|
220
|
-
|
|
198
|
+
i();
|
|
221
199
|
}))
|
|
222
|
-
return this.logger.warn("Extension discovery timed out"), this.setState(
|
|
200
|
+
return this.logger.warn("Extension discovery timed out"), this.setState(N), this.state;
|
|
223
201
|
}
|
|
224
202
|
const t = await this.bodhiext.getExtensionId();
|
|
225
203
|
this.logger.info(`Extension discovered: ${t}`);
|
|
226
|
-
const
|
|
204
|
+
const s = {
|
|
227
205
|
type: "extension",
|
|
228
206
|
extension: "ready",
|
|
229
207
|
extensionId: t,
|
|
230
|
-
server:
|
|
208
|
+
server: B
|
|
231
209
|
};
|
|
232
210
|
if (e.testConnection)
|
|
233
211
|
try {
|
|
234
|
-
const
|
|
235
|
-
this.setState({ ...
|
|
236
|
-
} catch (
|
|
237
|
-
this.logger.error("Failed to get server state:",
|
|
212
|
+
const r = await this.getServerState();
|
|
213
|
+
this.setState({ ...s, server: r }), this.logger.info(`Server connectivity tested: ${r.status}`);
|
|
214
|
+
} catch (r) {
|
|
215
|
+
this.logger.error("Failed to get server state:", r), this.setState({ ...s, server: w });
|
|
238
216
|
}
|
|
239
217
|
else
|
|
240
|
-
this.setState(
|
|
218
|
+
this.setState(s);
|
|
241
219
|
return this.state;
|
|
242
220
|
}
|
|
243
221
|
// ============================================================================
|
|
@@ -253,31 +231,21 @@ class Z {
|
|
|
253
231
|
if (t.status === "authenticated")
|
|
254
232
|
return t;
|
|
255
233
|
this.ensureBodhiext();
|
|
256
|
-
const
|
|
234
|
+
const s = e?.userRole ?? "scope_user_user", r = this.config.redirectUri;
|
|
257
235
|
e?.onProgress?.("requesting");
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
return g === "approved" ? { approved: !0, accessRequestScope: T ?? void 0 } : ["denied", "failed", "expired"].includes(g) ? { approved: !1, status: g } : null;
|
|
272
|
-
}, {
|
|
273
|
-
intervalMs: e?.pollIntervalMs ?? P,
|
|
274
|
-
timeoutMs: e?.pollTimeoutMs ?? x
|
|
275
|
-
});
|
|
276
|
-
h.approved || E(h.status ?? "unknown"), l = h.accessRequestScope;
|
|
277
|
-
} else
|
|
278
|
-
return localStorage.setItem(this.storageKeys.ACCESS_REQUEST_ID, c), window.location.href = o, new Promise(() => {
|
|
279
|
-
});
|
|
280
|
-
return e?.onProgress?.("authenticating"), this.performOAuthPkce(`openid profile email roles ${l ?? ""}`.trim());
|
|
236
|
+
const n = S(), o = await k(n), a = S();
|
|
237
|
+
localStorage.setItem(this.storageKeys.CODE_VERIFIER, n), localStorage.setItem(this.storageKeys.STATE, a);
|
|
238
|
+
const l = R(this.authEndpoints, {
|
|
239
|
+
clientId: this.authClientId,
|
|
240
|
+
redirectUri: r,
|
|
241
|
+
scope: A,
|
|
242
|
+
state: a,
|
|
243
|
+
codeChallenge: o
|
|
244
|
+
}), i = I(r), h = new x(this.authClientId).requestedRole(s);
|
|
245
|
+
e?.requested && h.requested(e.requested);
|
|
246
|
+
const d = await this.requestAccess(h.build()), { review_url: g } = b(d);
|
|
247
|
+
return e?.onProgress?.("reviewing"), window.location.href = C(g, l, i), new Promise(() => {
|
|
248
|
+
});
|
|
281
249
|
}
|
|
282
250
|
/**
|
|
283
251
|
* Handle OAuth callback with authorization code
|
|
@@ -285,14 +253,14 @@ class Z {
|
|
|
285
253
|
* @returns AuthState with login state and user info
|
|
286
254
|
*/
|
|
287
255
|
async handleOAuthCallback(e, t) {
|
|
288
|
-
const
|
|
289
|
-
if (!
|
|
256
|
+
const s = localStorage.getItem(this.storageKeys.STATE);
|
|
257
|
+
if (!s || s !== t)
|
|
290
258
|
throw new Error("Invalid state parameter - possible CSRF attack");
|
|
291
259
|
await this.exchangeCodeForTokens(e), localStorage.removeItem(this.storageKeys.CODE_VERIFIER), localStorage.removeItem(this.storageKeys.STATE);
|
|
292
|
-
const
|
|
293
|
-
if (
|
|
260
|
+
const r = await this.getAuthState();
|
|
261
|
+
if (r.status !== "authenticated")
|
|
294
262
|
throw new Error("Login failed");
|
|
295
|
-
return this.setAuthState(
|
|
263
|
+
return this.setAuthState(r), r;
|
|
296
264
|
}
|
|
297
265
|
/**
|
|
298
266
|
* Exchange authorization code for tokens
|
|
@@ -301,29 +269,29 @@ class Z {
|
|
|
301
269
|
const t = localStorage.getItem(this.storageKeys.CODE_VERIFIER);
|
|
302
270
|
if (!t)
|
|
303
271
|
throw new Error("Code verifier not found");
|
|
304
|
-
const
|
|
272
|
+
const s = new URLSearchParams({
|
|
305
273
|
grant_type: "authorization_code",
|
|
306
274
|
client_id: this.authClientId,
|
|
307
275
|
code: e,
|
|
308
276
|
redirect_uri: this.config.redirectUri,
|
|
309
277
|
code_verifier: t
|
|
310
|
-
}),
|
|
278
|
+
}), r = await fetch(this.authEndpoints.token, {
|
|
311
279
|
method: "POST",
|
|
312
280
|
headers: {
|
|
313
281
|
"Content-Type": "application/x-www-form-urlencoded"
|
|
314
282
|
},
|
|
315
|
-
body:
|
|
283
|
+
body: s
|
|
316
284
|
});
|
|
317
|
-
if (!
|
|
318
|
-
const
|
|
319
|
-
throw new Error(`Token exchange failed: ${
|
|
285
|
+
if (!r.ok) {
|
|
286
|
+
const o = await r.text();
|
|
287
|
+
throw new Error(`Token exchange failed: ${r.status} ${o}`);
|
|
320
288
|
}
|
|
321
|
-
const
|
|
322
|
-
if (!
|
|
289
|
+
const n = await r.json();
|
|
290
|
+
if (!n.access_token)
|
|
323
291
|
throw new Error("No access token received");
|
|
324
|
-
if (localStorage.setItem(this.storageKeys.ACCESS_TOKEN,
|
|
325
|
-
const
|
|
326
|
-
localStorage.setItem(this.storageKeys.EXPIRES_AT,
|
|
292
|
+
if (localStorage.setItem(this.storageKeys.ACCESS_TOKEN, n.access_token), n.refresh_token && localStorage.setItem(this.storageKeys.REFRESH_TOKEN, n.refresh_token), n.expires_in) {
|
|
293
|
+
const o = Date.now() + n.expires_in * 1e3;
|
|
294
|
+
localStorage.setItem(this.storageKeys.EXPIRES_AT, o.toString());
|
|
327
295
|
}
|
|
328
296
|
}
|
|
329
297
|
/**
|
|
@@ -334,7 +302,7 @@ class Z {
|
|
|
334
302
|
const e = localStorage.getItem(this.storageKeys.REFRESH_TOKEN);
|
|
335
303
|
if (e)
|
|
336
304
|
try {
|
|
337
|
-
const
|
|
305
|
+
const s = new URLSearchParams({
|
|
338
306
|
token: e,
|
|
339
307
|
client_id: this.authClientId,
|
|
340
308
|
token_type_hint: "refresh_token"
|
|
@@ -344,10 +312,10 @@ class Z {
|
|
|
344
312
|
headers: {
|
|
345
313
|
"Content-Type": "application/x-www-form-urlencoded"
|
|
346
314
|
},
|
|
347
|
-
body:
|
|
315
|
+
body: s
|
|
348
316
|
});
|
|
349
|
-
} catch (
|
|
350
|
-
this.logger.warn("Token revocation failed:",
|
|
317
|
+
} catch (s) {
|
|
318
|
+
this.logger.warn("Token revocation failed:", s);
|
|
351
319
|
}
|
|
352
320
|
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);
|
|
353
321
|
const t = {
|
|
@@ -379,7 +347,7 @@ class Z {
|
|
|
379
347
|
try {
|
|
380
348
|
return {
|
|
381
349
|
status: "authenticated",
|
|
382
|
-
user:
|
|
350
|
+
user: p(e),
|
|
383
351
|
accessToken: e,
|
|
384
352
|
error: null,
|
|
385
353
|
refreshToken: null,
|
|
@@ -407,10 +375,10 @@ class Z {
|
|
|
407
375
|
if (!e)
|
|
408
376
|
return null;
|
|
409
377
|
if (t) {
|
|
410
|
-
const
|
|
411
|
-
if (Date.now() >=
|
|
412
|
-
const
|
|
413
|
-
return
|
|
378
|
+
const s = parseInt(t, 10);
|
|
379
|
+
if (Date.now() >= s - 5 * 1e3) {
|
|
380
|
+
const r = localStorage.getItem(this.storageKeys.REFRESH_TOKEN);
|
|
381
|
+
return r ? this._tryRefreshToken(r) : null;
|
|
414
382
|
}
|
|
415
383
|
}
|
|
416
384
|
return e;
|
|
@@ -435,17 +403,17 @@ class Z {
|
|
|
435
403
|
async _doRefreshToken(e) {
|
|
436
404
|
this.logger.debug("Refreshing access token");
|
|
437
405
|
try {
|
|
438
|
-
const t = await
|
|
406
|
+
const t = await q(
|
|
439
407
|
this.authEndpoints.token,
|
|
440
408
|
e,
|
|
441
409
|
this.authClientId
|
|
442
410
|
);
|
|
443
411
|
if (t.success) {
|
|
444
412
|
this._storeRefreshedTokens(t.tokens);
|
|
445
|
-
const
|
|
413
|
+
const s = p(t.tokens.access_token);
|
|
446
414
|
return this.setAuthState({
|
|
447
415
|
status: "authenticated",
|
|
448
|
-
user:
|
|
416
|
+
user: s,
|
|
449
417
|
accessToken: t.tokens.access_token,
|
|
450
418
|
error: null,
|
|
451
419
|
refreshToken: null,
|
|
@@ -466,7 +434,7 @@ class Z {
|
|
|
466
434
|
} catch (t) {
|
|
467
435
|
this.logger.warn("Token refresh failed:", t);
|
|
468
436
|
}
|
|
469
|
-
throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"),
|
|
437
|
+
throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"), E(
|
|
470
438
|
"auth_error",
|
|
471
439
|
"Access token expired and unable to refresh. Try logging out and logging in again."
|
|
472
440
|
);
|
|
@@ -495,86 +463,86 @@ class Z {
|
|
|
495
463
|
try {
|
|
496
464
|
const e = await this.sendApiRequest("GET", "/bodhi/v1/info");
|
|
497
465
|
if (e.status >= 400)
|
|
498
|
-
return
|
|
499
|
-
const t = e.body,
|
|
466
|
+
return w;
|
|
467
|
+
const t = e.body, s = t.version || "unknown";
|
|
500
468
|
switch (t.status) {
|
|
501
469
|
case "ready":
|
|
502
470
|
return {
|
|
503
471
|
status: "ready",
|
|
504
|
-
version:
|
|
472
|
+
version: s,
|
|
505
473
|
error: null,
|
|
506
474
|
deployment: t.deployment ?? null,
|
|
507
475
|
client_id: t.client_id ?? null
|
|
508
476
|
};
|
|
509
477
|
case "setup":
|
|
510
|
-
return
|
|
478
|
+
return m(
|
|
511
479
|
"setup",
|
|
512
|
-
|
|
480
|
+
s,
|
|
513
481
|
void 0,
|
|
514
482
|
t.deployment,
|
|
515
483
|
t.client_id
|
|
516
484
|
);
|
|
517
485
|
case "resource_admin":
|
|
518
|
-
return
|
|
486
|
+
return m(
|
|
519
487
|
"resource_admin",
|
|
520
|
-
|
|
488
|
+
s,
|
|
521
489
|
void 0,
|
|
522
490
|
t.deployment,
|
|
523
491
|
t.client_id
|
|
524
492
|
);
|
|
525
493
|
case "error":
|
|
526
|
-
return
|
|
494
|
+
return m(
|
|
527
495
|
"error",
|
|
528
|
-
|
|
529
|
-
t.error ? { message: t.error.message, type: t.error.type } :
|
|
496
|
+
s,
|
|
497
|
+
t.error ? { message: t.error.message, type: t.error.type } : L.SERVER_NOT_READY,
|
|
530
498
|
t.deployment,
|
|
531
499
|
t.client_id
|
|
532
500
|
);
|
|
533
501
|
default:
|
|
534
|
-
return
|
|
502
|
+
return w;
|
|
535
503
|
}
|
|
536
504
|
} catch {
|
|
537
|
-
return
|
|
505
|
+
return w;
|
|
538
506
|
}
|
|
539
507
|
}
|
|
540
508
|
/**
|
|
541
509
|
* Generic streaming via window.bodhiext.sendStreamRequest
|
|
542
510
|
* Wraps ReadableStream as AsyncGenerator
|
|
543
511
|
*/
|
|
544
|
-
async *stream(e, t,
|
|
512
|
+
async *stream(e, t, s, r, n = !0) {
|
|
545
513
|
this.ensureBodhiext();
|
|
546
|
-
let
|
|
547
|
-
if (
|
|
548
|
-
const
|
|
549
|
-
if (!
|
|
550
|
-
throw
|
|
551
|
-
|
|
552
|
-
...
|
|
553
|
-
Authorization: `Bearer ${
|
|
514
|
+
let o = r || {};
|
|
515
|
+
if (n) {
|
|
516
|
+
const i = await this._getAccessTokenRaw();
|
|
517
|
+
if (!i)
|
|
518
|
+
throw E("auth_error", "Not authenticated. Please log in first.");
|
|
519
|
+
o = {
|
|
520
|
+
...o,
|
|
521
|
+
Authorization: `Bearer ${i}`
|
|
554
522
|
};
|
|
555
523
|
}
|
|
556
|
-
const
|
|
524
|
+
const l = this.bodhiext.sendStreamRequest(e, t, s, o).getReader();
|
|
557
525
|
try {
|
|
558
526
|
for (; ; ) {
|
|
559
|
-
const { value:
|
|
560
|
-
if (
|
|
527
|
+
const { value: i, done: h } = await l.read();
|
|
528
|
+
if (h || i?.done)
|
|
561
529
|
break;
|
|
562
|
-
yield
|
|
530
|
+
yield i.body;
|
|
563
531
|
}
|
|
564
|
-
} catch (
|
|
565
|
-
if (
|
|
566
|
-
if (
|
|
567
|
-
const
|
|
568
|
-
throw
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
) :
|
|
532
|
+
} catch (i) {
|
|
533
|
+
if (i instanceof f || i instanceof c) throw i;
|
|
534
|
+
if (i instanceof Error) {
|
|
535
|
+
const h = i;
|
|
536
|
+
throw i.name === "BodhiApiError" && typeof h.status == "number" && h.body != null ? new f(
|
|
537
|
+
h.status,
|
|
538
|
+
h.body,
|
|
539
|
+
i.message,
|
|
540
|
+
h.headers
|
|
541
|
+
) : i.name === "BodhiError" && typeof h.code == "string" ? new c(h.code, i.message) : new c("extension_error", i.message);
|
|
574
542
|
}
|
|
575
|
-
throw
|
|
543
|
+
throw i;
|
|
576
544
|
} finally {
|
|
577
|
-
|
|
545
|
+
l.releaseLock();
|
|
578
546
|
}
|
|
579
547
|
}
|
|
580
548
|
/**
|
|
@@ -582,49 +550,49 @@ class Z {
|
|
|
582
550
|
* Returns status, headers, and async generator of raw text chunks.
|
|
583
551
|
* No SSE/JSON parsing. Non-2xx responses are returned as data (not thrown).
|
|
584
552
|
*/
|
|
585
|
-
async streamText(e, t,
|
|
553
|
+
async streamText(e, t, s, r, n = !0) {
|
|
586
554
|
this.ensureBodhiext();
|
|
587
|
-
let
|
|
588
|
-
if (
|
|
589
|
-
const
|
|
590
|
-
|
|
591
|
-
...
|
|
592
|
-
Authorization: `Bearer ${
|
|
555
|
+
let o = { ...r };
|
|
556
|
+
if (n) {
|
|
557
|
+
const i = await this._getAccessTokenRaw();
|
|
558
|
+
i && (o = {
|
|
559
|
+
...o,
|
|
560
|
+
Authorization: `Bearer ${i}`
|
|
593
561
|
});
|
|
594
562
|
}
|
|
595
|
-
const
|
|
596
|
-
async function*
|
|
597
|
-
const
|
|
563
|
+
const a = await this.bodhiext.sendStreamText(e, t, s, o);
|
|
564
|
+
async function* l(i) {
|
|
565
|
+
const h = i.getReader();
|
|
598
566
|
try {
|
|
599
567
|
for (; ; ) {
|
|
600
|
-
const { done: d, value:
|
|
568
|
+
const { done: d, value: g } = await h.read();
|
|
601
569
|
if (d) break;
|
|
602
|
-
yield
|
|
570
|
+
yield g;
|
|
603
571
|
}
|
|
604
572
|
} finally {
|
|
605
|
-
|
|
573
|
+
h.releaseLock();
|
|
606
574
|
}
|
|
607
575
|
}
|
|
608
576
|
return {
|
|
609
|
-
status:
|
|
610
|
-
headers:
|
|
611
|
-
body:
|
|
577
|
+
status: a.status,
|
|
578
|
+
headers: a.headers,
|
|
579
|
+
body: l(a.body)
|
|
612
580
|
};
|
|
613
581
|
}
|
|
614
582
|
// ============================================================================
|
|
615
583
|
// OpenAI-Compatible Namespaced API
|
|
616
584
|
// ============================================================================
|
|
617
585
|
get chat() {
|
|
618
|
-
return this._chat ??= new
|
|
586
|
+
return this._chat ??= new D(this);
|
|
619
587
|
}
|
|
620
588
|
get models() {
|
|
621
|
-
return this._models ??= new
|
|
589
|
+
return this._models ??= new M(this);
|
|
622
590
|
}
|
|
623
591
|
get embeddings() {
|
|
624
|
-
return this._embeddings ??= new
|
|
592
|
+
return this._embeddings ??= new F(this);
|
|
625
593
|
}
|
|
626
594
|
get mcps() {
|
|
627
|
-
return this._mcps ??= new
|
|
595
|
+
return this._mcps ??= new z(this);
|
|
628
596
|
}
|
|
629
597
|
// ============================================================================
|
|
630
598
|
// Access Request Methods
|
|
@@ -638,43 +606,6 @@ class Z {
|
|
|
638
606
|
!1
|
|
639
607
|
);
|
|
640
608
|
}
|
|
641
|
-
async getAccessRequestStatus(e) {
|
|
642
|
-
return this.sendApiRequest(
|
|
643
|
-
"GET",
|
|
644
|
-
`/bodhi/v1/apps/access-requests/${e}?app_client_id=${encodeURIComponent(this.authClientId)}`,
|
|
645
|
-
void 0,
|
|
646
|
-
{},
|
|
647
|
-
!1
|
|
648
|
-
);
|
|
649
|
-
}
|
|
650
|
-
async pollAccessRequestStatus(e, t) {
|
|
651
|
-
return H(
|
|
652
|
-
(r) => this.getAccessRequestStatus(r),
|
|
653
|
-
e,
|
|
654
|
-
t
|
|
655
|
-
);
|
|
656
|
-
}
|
|
657
|
-
async performOAuthPkce(e) {
|
|
658
|
-
const t = _(), r = await U(t), s = _();
|
|
659
|
-
localStorage.setItem(this.storageKeys.CODE_VERIFIER, t), localStorage.setItem(this.storageKeys.STATE, s);
|
|
660
|
-
const i = e.split(" ").filter(Boolean), a = new URLSearchParams({
|
|
661
|
-
response_type: "code",
|
|
662
|
-
client_id: this.authClientId,
|
|
663
|
-
redirect_uri: this.config.redirectUri,
|
|
664
|
-
scope: i.join(" "),
|
|
665
|
-
state: s,
|
|
666
|
-
code_challenge: r,
|
|
667
|
-
code_challenge_method: "S256"
|
|
668
|
-
});
|
|
669
|
-
return window.location.href = `${this.authEndpoints.authorize}?${a}`, new Promise(() => {
|
|
670
|
-
});
|
|
671
|
-
}
|
|
672
|
-
async handleAccessRequestCallback(e) {
|
|
673
|
-
const t = await this.getAccessRequestStatus(e), { status: r, access_request_scope: s } = S(t);
|
|
674
|
-
localStorage.removeItem(this.storageKeys.ACCESS_REQUEST_ID), r !== "approved" && E(r);
|
|
675
|
-
const i = `openid profile email roles ${s ?? ""}`.trim();
|
|
676
|
-
return this.performOAuthPkce(i);
|
|
677
|
-
}
|
|
678
609
|
/**
|
|
679
610
|
* Serialize web extension client state (all transient, nothing to persist)
|
|
680
611
|
*/
|
|
@@ -698,34 +629,34 @@ class Z {
|
|
|
698
629
|
};
|
|
699
630
|
}
|
|
700
631
|
}
|
|
701
|
-
function
|
|
632
|
+
function Y(u) {
|
|
702
633
|
if (typeof window > "u")
|
|
703
634
|
throw new Error("redirectUri required in non-browser environment");
|
|
704
|
-
const e =
|
|
635
|
+
const e = u === "/" ? "" : u.replace(/\/$/, "");
|
|
705
636
|
return `${window.location.origin}${e}/callback`;
|
|
706
637
|
}
|
|
707
|
-
class
|
|
708
|
-
constructor(e, t,
|
|
709
|
-
const
|
|
710
|
-
basePath:
|
|
711
|
-
redirectUri:
|
|
712
|
-
authServerUrl:
|
|
713
|
-
logLevel:
|
|
714
|
-
apiTimeoutMs:
|
|
715
|
-
storage:
|
|
716
|
-
initialTokens:
|
|
717
|
-
initParams:
|
|
638
|
+
class Z extends V {
|
|
639
|
+
constructor(e, t, s) {
|
|
640
|
+
const r = t || {}, n = {
|
|
641
|
+
basePath: r.basePath || "/",
|
|
642
|
+
redirectUri: r.redirectUri || Y(r.basePath || "/"),
|
|
643
|
+
authServerUrl: r.authServerUrl || "https://id.getbodhi.app/realms/bodhi",
|
|
644
|
+
logLevel: r.logLevel || "warn",
|
|
645
|
+
apiTimeoutMs: r.apiTimeoutMs,
|
|
646
|
+
storage: r.storage,
|
|
647
|
+
initialTokens: r.initialTokens,
|
|
648
|
+
initParams: r.initParams
|
|
718
649
|
};
|
|
719
|
-
super(e,
|
|
650
|
+
super(e, n, s);
|
|
720
651
|
}
|
|
721
652
|
createLogger(e) {
|
|
722
|
-
return new
|
|
653
|
+
return new v("WebUIClient", e.logLevel);
|
|
723
654
|
}
|
|
724
655
|
createStoragePrefix(e) {
|
|
725
|
-
return
|
|
656
|
+
return T(e.basePath, y.WEB);
|
|
726
657
|
}
|
|
727
658
|
createExtClient(e, t) {
|
|
728
|
-
return new
|
|
659
|
+
return new j(
|
|
729
660
|
this.authClientId,
|
|
730
661
|
{
|
|
731
662
|
authServerUrl: e.authServerUrl,
|
|
@@ -738,8 +669,8 @@ class se extends X {
|
|
|
738
669
|
t
|
|
739
670
|
);
|
|
740
671
|
}
|
|
741
|
-
createDirectClient(e, t,
|
|
742
|
-
return new
|
|
672
|
+
createDirectClient(e, t, s) {
|
|
673
|
+
return new $(
|
|
743
674
|
{
|
|
744
675
|
authClientId: e,
|
|
745
676
|
authServerUrl: t.authServerUrl,
|
|
@@ -750,7 +681,7 @@ class se extends X {
|
|
|
750
681
|
storage: t.storage,
|
|
751
682
|
initialTokens: t.initialTokens
|
|
752
683
|
},
|
|
753
|
-
|
|
684
|
+
s
|
|
754
685
|
);
|
|
755
686
|
}
|
|
756
687
|
// ============================================================================
|
|
@@ -759,15 +690,12 @@ class se extends X {
|
|
|
759
690
|
async handleOAuthCallback(e, t) {
|
|
760
691
|
return this.connectionMode === "direct" ? this.directClient.handleOAuthCallback(e, t) : this.extClient.handleOAuthCallback(e, t);
|
|
761
692
|
}
|
|
762
|
-
async handleAccessRequestCallback(e) {
|
|
763
|
-
return this.connectionMode === "direct" ? this.directClient.handleAccessRequestCallback(e) : this.extClient.handleAccessRequestCallback(e);
|
|
764
|
-
}
|
|
765
693
|
}
|
|
766
|
-
const
|
|
694
|
+
const Q = "production";
|
|
767
695
|
export {
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
696
|
+
$ as DirectWebClient,
|
|
697
|
+
re as InMemoryStorage,
|
|
698
|
+
W as LocalStorageAdapter,
|
|
699
|
+
Q as WEB_BUILD_MODE,
|
|
700
|
+
Z as WebUIClient
|
|
773
701
|
};
|
package/dist/direct-client.d.ts
CHANGED
|
@@ -19,8 +19,6 @@ export declare class DirectWebClient extends DirectClientBase {
|
|
|
19
19
|
private redirectUri;
|
|
20
20
|
constructor(config: DirectWebClientConfig, onStateChange?: StateChangeCallback);
|
|
21
21
|
login(options?: LoginOptions): Promise<AuthState>;
|
|
22
|
-
protected performOAuthPkce(scope: string): Promise<AuthState>;
|
|
23
22
|
handleOAuthCallback(code: string, state: string): Promise<AuthState>;
|
|
24
|
-
handleAccessRequestCallback(requestId: string): Promise<AuthState>;
|
|
25
23
|
protected _getRedirectUri(): string;
|
|
26
24
|
}
|
package/dist/ext-client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CreateAccessRequest, CreateAccessRequestResponse, PingResponse } from '@bodhiapp/ts-client';
|
|
2
2
|
import { Chat, Models, Embeddings, Mcps, AuthState, BackendServerState, ClientState, ExtensionState, IExtensionClient, InitParams, LoginOptions, LogLevel, StateChangeCallback, StreamTextResult } from '@bodhiapp/bodhi-js-core';
|
|
3
3
|
import { ApiResponse } from '@bodhiapp/bodhi-browser-types';
|
|
4
4
|
export type SerializedWebExtensionState = {
|
|
@@ -155,13 +155,6 @@ export declare class WindowBodhiextClient implements IExtensionClient {
|
|
|
155
155
|
get embeddings(): Embeddings;
|
|
156
156
|
get mcps(): Mcps;
|
|
157
157
|
requestAccess(body: CreateAccessRequest): Promise<ApiResponse<CreateAccessRequestResponse>>;
|
|
158
|
-
getAccessRequestStatus(requestId: string): Promise<ApiResponse<AccessRequestStatusResponse>>;
|
|
159
|
-
pollAccessRequestStatus(requestId: string, options?: {
|
|
160
|
-
intervalMs?: number;
|
|
161
|
-
timeoutMs?: number;
|
|
162
|
-
}): Promise<AccessRequestStatusResponse>;
|
|
163
|
-
private performOAuthPkce;
|
|
164
|
-
handleAccessRequestCallback(requestId: string): Promise<AuthState>;
|
|
165
158
|
/**
|
|
166
159
|
* Serialize web extension client state (all transient, nothing to persist)
|
|
167
160
|
*/
|
package/dist/facade-client.d.ts
CHANGED
|
@@ -52,5 +52,4 @@ export declare class WebUIClient extends BaseFacadeClient<WebClientConfig, Windo
|
|
|
52
52
|
protected createExtClient(config: WebClientConfig, onStateChange: (change: StateChange) => void): WindowBodhiextClient;
|
|
53
53
|
protected createDirectClient(authClientId: string, config: WebClientConfig, onStateChange: (change: StateChange) => void): DirectWebClient;
|
|
54
54
|
handleOAuthCallback(code: string, state: string): Promise<AuthState>;
|
|
55
|
-
handleAccessRequestCallback(requestId: string): Promise<AuthState>;
|
|
56
55
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bodhiapp/bodhi-js",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.40",
|
|
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.40",
|
|
41
|
+
"@bodhiapp/bodhi-js-core": "0.0.40",
|
|
42
|
+
"@bodhiapp/ts-client": "0.1.35"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@eslint/js": "^9.23.0",
|