@bodhiapp/bodhi-js 0.0.4 → 0.0.6
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-browser-ext/src/types/bodhiext.d.ts +0 -1
- package/dist/bodhi-browser-ext/src/types/common.d.ts +1 -0
- package/dist/bodhi-browser-ext/src/types/protocol.d.ts +0 -1
- package/dist/bodhi-js-sdk/core/src/direct-client-base.d.ts +5 -5
- package/dist/bodhi-js-sdk/core/src/errors.d.ts +0 -1
- package/dist/bodhi-js-sdk/core/src/facade-client-base.d.ts +5 -6
- package/dist/bodhi-js-sdk/core/src/interface.d.ts +14 -7
- package/dist/bodhi-js-sdk/core/src/logger.d.ts +0 -1
- package/dist/bodhi-js-sdk/core/src/oauth.d.ts +0 -1
- package/dist/bodhi-js-sdk/core/src/onboarding/config.d.ts +0 -1
- package/dist/bodhi-js-sdk/core/src/onboarding/modal.d.ts +1 -2
- package/dist/bodhi-js-sdk/core/src/onboarding/protocol-utils.d.ts +0 -1
- package/dist/bodhi-js-sdk/core/src/platform.d.ts +0 -1
- package/dist/bodhi-js-sdk/core/src/types/api.d.ts +0 -1
- package/dist/bodhi-js-sdk/core/src/types/auth.d.ts +37 -0
- package/dist/bodhi-js-sdk/core/src/types/callback.d.ts +1 -2
- package/dist/bodhi-js-sdk/core/src/types/client-state.d.ts +43 -94
- package/dist/bodhi-js-sdk/core/src/types/config.d.ts +0 -1
- package/dist/bodhi-js-sdk/core/src/types/index.d.ts +4 -3
- package/dist/bodhi-js-sdk/core/src/types/platform.d.ts +0 -1
- package/dist/bodhi-js-sdk/core/src/types/user-info.d.ts +0 -32
- package/dist/bodhi-js-sdk/web/src/constants.d.ts +0 -1
- package/dist/bodhi-js-sdk/web/src/direct-client.d.ts +4 -6
- package/dist/bodhi-js-sdk/web/src/ext-client.d.ts +7 -8
- package/dist/bodhi-js-sdk/web/src/facade-client.d.ts +2 -3
- package/dist/bodhi-js-sdk/web/src/index.d.ts +0 -1
- package/dist/bodhi-web.cjs.js +1 -1
- package/dist/bodhi-web.esm.d.ts +1 -0
- package/dist/bodhi-web.esm.js +274 -265
- package/dist/setup-modal/src/types/message-types.d.ts +0 -1
- package/dist/setup-modal/src/types/protocol.d.ts +0 -1
- package/dist/setup-modal/src/types/state.d.ts +0 -1
- package/dist/setup-modal/src/types/type-guards.d.ts +0 -1
- package/package.json +10 -6
package/dist/bodhi-web.cjs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=require("@bodhiapp/bodhi-js-core");class E extends s.DirectClientBase{constructor(e,t){const r=e.basePath||"/",o=s.createStoragePrefixWithBasePath(r,s.STORAGE_PREFIXES.DIRECT);super({...e,storagePrefix:o},"DirectWebClient",t),this.redirectUri=e.redirectUri}async login(){const e=await this.getAuthState();if(e.isLoggedIn)return e;const t=await this.requestResourceAccess(),r=`openid profile email roles ${this.userScope} ${t}`,o=s.generateCodeVerifier(),a=await s.generateCodeChallenge(o),i=s.generateCodeVerifier();localStorage.setItem(this.storageKeys.CODE_VERIFIER,o),localStorage.setItem(this.storageKeys.STATE,i);const n=new URL(this.authEndpoints.authorize);throw n.searchParams.set("client_id",this.authClientId),n.searchParams.set("response_type","code"),n.searchParams.set("redirect_uri",this.redirectUri),n.searchParams.set("scope",r),n.searchParams.set("code_challenge",a),n.searchParams.set("code_challenge_method","S256"),n.searchParams.set("state",i),window.location.href=n.toString(),new Error("Redirect initiated")}async handleOAuthCallback(e,t){const r=localStorage.getItem(this.storageKeys.STATE);if(!r||r!==t)throw new Error("Invalid state parameter - possible CSRF attack");await this.exchangeCodeForTokens(e),localStorage.removeItem(this.storageKeys.CODE_VERIFIER),localStorage.removeItem(this.storageKeys.STATE);const o=await this.getAuthState();if(!o.isLoggedIn)throw new Error("Login failed");const a=o;return this.setAuthState(a),a}async logout(){const e=localStorage.getItem(this.storageKeys.REFRESH_TOKEN);if(e)try{const r=new URLSearchParams({token:e,client_id:this.authClientId,token_type_hint:"refresh_token"});await fetch(this.authEndpoints.revoke,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:r})}catch(r){this.logger.warn("Token revocation failed:",r)}localStorage.removeItem(this.storageKeys.ACCESS_TOKEN),localStorage.removeItem(this.storageKeys.REFRESH_TOKEN),localStorage.removeItem(this.storageKeys.EXPIRES_AT),localStorage.removeItem(this.storageKeys.RESOURCE_SCOPE);const t={isLoggedIn:!1};return this.setAuthState(t),t}async requestResourceAccess(){const e=await this.sendApiRequest("POST","/bodhi/v1/apps/request-access",{app_client_id:this.authClientId},{},!1);if(s.isApiResultOperationError(e))throw new Error("Failed to get resource access scope from server");if(!s.isApiResultSuccess(e))throw new Error("Failed to get resource access scope from server: API error");const t=e.body.scope;return localStorage.setItem(this.storageKeys.RESOURCE_SCOPE,t),t}async exchangeCodeForTokens(e){const t=localStorage.getItem(this.storageKeys.CODE_VERIFIER);if(!t)throw new Error("Code verifier not found");const r=await fetch(this.authEndpoints.token,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",code:e,redirect_uri:this.redirectUri,client_id:this.authClientId,code_verifier:t})});if(!r.ok){const a=await r.text();throw new Error(`Token exchange failed: ${r.status} ${a}`)}const o=await r.json();if(localStorage.setItem(this.storageKeys.ACCESS_TOKEN,o.access_token),o.refresh_token&&localStorage.setItem(this.storageKeys.REFRESH_TOKEN,o.refresh_token),o.expires_in){const a=Date.now()+o.expires_in*1e3;localStorage.setItem(this.storageKeys.EXPIRES_AT,a.toString())}}async _storageGet(e){return localStorage.getItem(e)}async _storageSet(e){Object.entries(e).forEach(([t,r])=>{localStorage.setItem(t,String(r))})}async _storageRemove(e){e.forEach(t=>localStorage.removeItem(t))}_getRedirectUri(){return this.redirectUri}}const S=500,f=5e3;s.createStorageKeys(s.STORAGE_PREFIXES.WEB);function _(d="/"){const e=s.createStoragePrefixWithBasePath(d,s.STORAGE_PREFIXES.WEB);return s.createStorageKeys(e)}class p{constructor(e,t,r,o){this.state=s.EXTENSION_STATE_NOT_INITIALIZED,this.bodhiext=null,this.refreshPromise=null,this.logger=new s.Logger("WindowBodhiextClient",t.logLevel),this.authClientId=e,this.config=t,this.authEndpoints=s.createOAuthEndpoints(this.config.authServerUrl),this.onStateChange=r??s.NOOP_STATE_CALLBACK,this.storageKeys=_(o||"/")}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 new Error("Client not initialized")}async sendExtRequest(e,t){return this.ensureBodhiext(),this.bodhiext.sendExtRequest(e,t)}async sendApiRequest(e,t,r,o,a){try{this.ensureBodhiext()}catch(i){return{error:{message:i instanceof Error?i.message:String(i),type:"extension_error"}}}try{let i=o||{};if(a){const h=await this._getAccessTokenRaw();if(!h)return{error:{message:"Not authenticated. Please log in first.",type:"extension_error"}};i={...i,Authorization:`Bearer ${h}`}}return await this.bodhiext.sendApiRequest(e,t,r,i)}catch(i){const n=i==null?void 0:i.error,h=(n==null?void 0:n.message)??(i instanceof Error?i.message:String(i)),c=(n==null?void 0:n.type)||"extension_error";return{error:{message:h,type:c}}}}getState(){return this.state}isClientInitialized(){return this.state.extension==="ready"}isServerReady(){return this.isClientInitialized()&&this.state.server.status==="ready"}async init(e={}){var o,a,i,n;if(!e.testConnection&&!e.selectedConnection)return this.logger.info("No testConnection or selectedConnection, returning not-initialized state"),s.EXTENSION_STATE_NOT_INITIALIZED;if(this.bodhiext&&!e.testConnection)return this.logger.debug("Already have bodhiext handle, skipping polling"),this.state;if(!this.bodhiext){const h=e.timeoutMs??((a=(o=this.config.initParams)==null?void 0:o.extension)==null?void 0:a.timeoutMs)??f,c=e.intervalMs??((n=(i=this.config.initParams)==null?void 0:i.extension)==null?void 0:n.intervalMs)??S,l=Date.now();if(!await new Promise(g=>{const u=()=>{if(window.bodhiext){this.bodhiext=window.bodhiext,g(!0);return}if(Date.now()-l>=h){g(!1);return}setTimeout(u,c)};u()}))return this.logger.warn("Extension discovery timed out"),this.setState(s.EXTENSION_STATE_NOT_FOUND),this.state}const t=await this.bodhiext.getExtensionId();this.logger.info(`Extension discovered: ${t}`);const r={type:"extension",extension:"ready",extensionId:t,server:s.PENDING_EXTENSION_READY};if(e.testConnection)try{const h=await this.getServerState();this.setState({...r,server:h}),this.logger.info(`Server connectivity tested: ${h.status}`)}catch(h){this.logger.error("Failed to get server state:",h),this.setState({...r,server:s.BACKEND_SERVER_NOT_REACHABLE})}else this.setState(r);return this.state}async requestResourceAccess(){this.ensureBodhiext();const e=await this.bodhiext.sendApiRequest("POST","/bodhi/v1/apps/request-access",{app_client_id:this.authClientId});if(!s.isApiResultSuccess(e))throw new Error("Failed to get resource access scope: API error");const t=e.body.scope;return localStorage.setItem(this.storageKeys.RESOURCE_SCOPE,t),t}async login(){const e=await this.getAuthState();if(e.isLoggedIn)return e;this.ensureBodhiext();const t=await this.requestResourceAccess(),r=s.generateCodeVerifier(),o=await s.generateCodeChallenge(r),a=s.generateCodeVerifier();localStorage.setItem(this.storageKeys.CODE_VERIFIER,r),localStorage.setItem(this.storageKeys.STATE,a);const i=["openid","profile","email","roles",this.config.userScope,t],n=new URLSearchParams({response_type:"code",client_id:this.authClientId,redirect_uri:this.config.redirectUri,scope:i.join(" "),state:a,code_challenge:o,code_challenge_method:"S256"}),h=`${this.authEndpoints.authorize}?${n}`;return window.location.href=h,new Promise(()=>{})}async handleOAuthCallback(e,t){const r=localStorage.getItem(this.storageKeys.STATE);if(!r||r!==t)throw new Error("Invalid state parameter - possible CSRF attack");await this.exchangeCodeForTokens(e),localStorage.removeItem(this.storageKeys.CODE_VERIFIER),localStorage.removeItem(this.storageKeys.STATE);const o=await this.getAuthState();if(!o.isLoggedIn)throw new Error("Login failed");return this.setAuthState(o),o}async exchangeCodeForTokens(e){const t=localStorage.getItem(this.storageKeys.CODE_VERIFIER);if(!t)throw new Error("Code verifier not found");const r=new URLSearchParams({grant_type:"authorization_code",client_id:this.authClientId,code:e,redirect_uri:this.config.redirectUri,code_verifier:t}),o=await fetch(this.authEndpoints.token,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:r});if(!o.ok){const i=await o.text();throw new Error(`Token exchange failed: ${o.status} ${i}`)}const a=await o.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 i=Date.now()+a.expires_in*1e3;localStorage.setItem(this.storageKeys.EXPIRES_AT,i.toString())}}async logout(){const e=localStorage.getItem(this.storageKeys.REFRESH_TOKEN);if(e)try{const r=new URLSearchParams({token:e,client_id:this.authClientId,token_type_hint:"refresh_token"});await fetch(this.authEndpoints.revoke,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:r})}catch(r){this.logger.warn("Token revocation failed:",r)}localStorage.removeItem(this.storageKeys.ACCESS_TOKEN),localStorage.removeItem(this.storageKeys.REFRESH_TOKEN),localStorage.removeItem(this.storageKeys.EXPIRES_AT),localStorage.removeItem(this.storageKeys.CODE_VERIFIER),localStorage.removeItem(this.storageKeys.STATE),localStorage.removeItem(this.storageKeys.RESOURCE_SCOPE);const t={isLoggedIn:!1};return this.setAuthState(t),t}async getAuthState(){const e=await this._getAccessTokenRaw();if(!e)return{isLoggedIn:!1};try{return{isLoggedIn:!0,userInfo:s.extractUserInfo(e),accessToken:e}}catch(t){return this.logger.error("Failed to parse token:",t),{isLoggedIn:!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 r=parseInt(t,10);if(Date.now()>=r-5*1e3){const o=localStorage.getItem(this.storageKeys.REFRESH_TOKEN);return o?this._tryRefreshToken(o):null}}return e}async _tryRefreshToken(e){if(this.refreshPromise)return this.logger.debug("Refresh already in progress, returning existing promise"),this.refreshPromise;this.refreshPromise=this._doRefreshToken(e);try{return await this.refreshPromise}finally{this.refreshPromise=null}}async _doRefreshToken(e){this.logger.debug("Refreshing access token");try{const t=await s.refreshAccessToken(this.authEndpoints.token,e,this.authClientId);if(t){this._storeRefreshedTokens(t);const r=s.extractUserInfo(t.access_token);return this.setAuthState({isLoggedIn:!0,userInfo:r,accessToken:t.access_token}),this.logger.info("Token refreshed successfully"),t.access_token}}catch(t){this.logger.warn("Token refresh failed:",t)}throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"),s.createOperationError("Access token expired and unable to refresh. Try logging out and logging in again.","token_refresh_failed")}_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 fetchModels(){return this.sendApiRequest("GET","/v1/models",void 0,void 0,!0)}async getServerState(){const e=await this.sendApiRequest("GET","/bodhi/v1/info");if(s.isApiResultOperationError(e)||!s.isApiResultSuccess(e))return s.BACKEND_SERVER_NOT_REACHABLE;const t=e.body;switch(t.status){case"ready":return{status:"ready",version:t.version||"unknown"};case"setup":return s.backendServerNotReady("setup",t.version||"unknown");case"resource-admin":return s.backendServerNotReady("resource-admin",t.version||"unknown");case"error":return s.backendServerNotReady("error",t.version||"unknown",t.error?{message:t.error.message,type:t.error.type}:s.SERVER_ERROR_CODES.SERVER_NOT_READY);default:return s.BACKEND_SERVER_NOT_REACHABLE}}async*stream(e,t,r,o,a=!0){this.ensureBodhiext();let i=o||{};if(a){const c=await this._getAccessTokenRaw();if(!c)throw new Error("Not authenticated. Please log in first.");i={...i,Authorization:`Bearer ${c}`}}const h=this.bodhiext.sendStreamRequest(e,t,r,i).getReader();try{for(;;){const{value:c,done:l}=await h.read();if(l||c!=null&&c.done)break;yield c.body}}catch(c){if(c instanceof Error){if("response"in c){const l=c;throw s.createApiError(c.message,l.response.status,l.response.body)}throw"error"in c,s.createOperationError(c.message,"extension_error")}throw c}finally{h.releaseLock()}}async*streamChat(e,t,r=!0){yield*this.stream("POST","/v1/chat/completions",{model:e,messages:[{role:"user",content:t}],stream:!0},void 0,r)}serialize(){return{extensionId:this.state.type==="extension"&&this.state.extension==="ready"?this.state.extensionId:void 0}}async debug(){return{type:"WindowBodhiextClient",state:this.state,authState:await this.getAuthState(),bodhiextAvailable:this.bodhiext!==null,authClientId:this.authClientId,authServerUrl:this.config.authServerUrl,redirectUri:this.config.redirectUri,userScope:this.config.userScope}}}class y extends s.BaseFacadeClient{constructor(e,t,r,o){const a={redirectUri:t.redirectUri,authServerUrl:t.authServerUrl||"https://id.getbodhi.app/realms/bodhi",userScope:t.userScope||"scope_user_user",basePath:t.basePath||"/",logLevel:t.logLevel||"warn",initParams:t.initParams};super(e,a,r,o,t.basePath)}createLogger(e){return new s.Logger("WebUIClient",e.logLevel)}createExtClient(e,t){return new p(this.authClientId,e,t,e.basePath)}createDirectClient(e,t,r){return new E({authClientId:e,authServerUrl:t.authServerUrl,redirectUri:t.redirectUri,userScope:t.userScope,logLevel:t.logLevel,storagePrefix:s.STORAGE_PREFIXES.WEB,basePath:t.basePath},r)}async handleOAuthCallback(e,t){return this.connectionMode==="direct"?this.directClient.handleOAuthCallback(e,t):this.extClient.handleOAuthCallback(e,t)}}const m="production";exports.WEB_BUILD_MODE=m;exports.WebUIClient=y;
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("@bodhiapp/bodhi-js-core");class d extends r.DirectClientBase{constructor(t,e){const s=t.basePath||"/",o=r.createStoragePrefixWithBasePath(s,r.STORAGE_PREFIXES.DIRECT);super({...t,storagePrefix:o},"DirectWebClient",e),this.redirectUri=t.redirectUri}async login(){const t=await this.getAuthState();if(t.status==="authenticated")return t;const e=await this.requestResourceAccess();if(r.isApiResultOperationError(e))throw r.createOperationError(e.error.message,e.error.type);if(r.isApiResultError(e)){const{message:n}=e.body.error;throw r.createOperationError(n,"auth_error")}if(!r.isApiResultSuccess(e))throw r.createOperationError(`Unexpected HTTP ${e.status}`,"auth_error");const s=e.body.scope;localStorage.setItem(this.storageKeys.RESOURCE_SCOPE,s);const o=`openid profile email roles ${this.userScope} ${s}`,i=r.generateCodeVerifier(),a=await r.generateCodeChallenge(i),h=r.generateCodeVerifier();localStorage.setItem(this.storageKeys.CODE_VERIFIER,i),localStorage.setItem(this.storageKeys.STATE,h);const c=new URL(this.authEndpoints.authorize);throw c.searchParams.set("client_id",this.authClientId),c.searchParams.set("response_type","code"),c.searchParams.set("redirect_uri",this.redirectUri),c.searchParams.set("scope",o),c.searchParams.set("code_challenge",a),c.searchParams.set("code_challenge_method","S256"),c.searchParams.set("state",h),window.location.href=c.toString(),new Error("Redirect initiated")}async handleOAuthCallback(t,e){const s=localStorage.getItem(this.storageKeys.STATE);if(!s||s!==e)throw new Error("Invalid state parameter - possible CSRF attack");await this.exchangeCodeForTokens(t),localStorage.removeItem(this.storageKeys.CODE_VERIFIER),localStorage.removeItem(this.storageKeys.STATE);const o=await this.getAuthState();if(o.status!=="authenticated")throw new Error("Login failed");return this.setAuthState(o),o}async logout(){const t=localStorage.getItem(this.storageKeys.REFRESH_TOKEN);if(t)try{const s=new URLSearchParams({token:t,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.RESOURCE_SCOPE);const e={status:"unauthenticated",user:null,accessToken:null,error:null};return this.setAuthState(e),e}async exchangeCodeForTokens(t){const e=localStorage.getItem(this.storageKeys.CODE_VERIFIER);if(!e)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:t,redirect_uri:this.redirectUri,client_id:this.authClientId,code_verifier:e})});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(t){return localStorage.getItem(t)}async _storageSet(t){Object.entries(t).forEach(([e,s])=>{localStorage.setItem(e,String(s))})}async _storageRemove(t){t.forEach(e=>localStorage.removeItem(e))}_getRedirectUri(){return this.redirectUri}}const g=500,E=5e3;r.createStorageKeys(r.STORAGE_PREFIXES.WEB);function S(u="/"){const t=r.createStoragePrefixWithBasePath(u,r.STORAGE_PREFIXES.WEB);return r.createStorageKeys(t)}class f{constructor(t,e,s,o){this.state=r.EXTENSION_STATE_NOT_INITIALIZED,this.bodhiext=null,this.refreshPromise=null,this.logger=new r.Logger("WindowBodhiextClient",e.logLevel),this.authClientId=t,this.config=e,this.authEndpoints=r.createOAuthEndpoints(this.config.authServerUrl),this.onStateChange=s??r.NOOP_STATE_CALLBACK,this.storageKeys=S(o||"/")}setState(t){this.state=t,this.logger.info(`{state: ${JSON.stringify(t)}} - Setting client state`),this.onStateChange({type:"client-state",state:t})}setAuthState(t){this.onStateChange({type:"auth-state",state:t})}setStateCallback(t){this.onStateChange=t}ensureBodhiext(){if(!this.bodhiext&&window.bodhiext&&(this.logger.info("Acquiring window.bodhiext reference"),this.bodhiext=window.bodhiext),!this.bodhiext)throw r.createOperationError("Client not initialized","extension_error")}async sendExtRequest(t,e){return this.ensureBodhiext(),this.bodhiext.sendExtRequest(t,e)}async sendApiRequest(t,e,s,o,i){try{this.ensureBodhiext()}catch(a){return{error:{message:a instanceof Error?a.message:String(a),type:"extension_error"}}}try{let a=o||{};if(i){const c=await this._getAccessTokenRaw();if(!c)return{error:{message:"Not authenticated. Please log in first.",type:"extension_error"}};a={...a,Authorization:`Bearer ${c}`}}return await this.bodhiext.sendApiRequest(t,e,s,a)}catch(a){const h=a?.error,c=h?.message??(a instanceof Error?a.message:String(a)),n=h?.type||"extension_error";return{error:{message:c,type:n}}}}getState(){return this.state}isClientInitialized(){return this.state.extension==="ready"}isServerReady(){return this.isClientInitialized()&&this.state.server.status==="ready"}async init(t={}){if(!t.testConnection&&!t.selectedConnection)return this.logger.info("No testConnection or selectedConnection, returning not-initialized state"),r.EXTENSION_STATE_NOT_INITIALIZED;if(this.bodhiext&&!t.testConnection)return this.logger.debug("Already have bodhiext handle, skipping polling"),this.state;if(!this.bodhiext){const o=t.timeoutMs??this.config.initParams?.extension?.timeoutMs??E,i=t.intervalMs??this.config.initParams?.extension?.intervalMs??g,a=Date.now();if(!await new Promise(c=>{const n=()=>{if(window.bodhiext){this.bodhiext=window.bodhiext,c(!0);return}if(Date.now()-a>=o){c(!1);return}setTimeout(n,i)};n()}))return this.logger.warn("Extension discovery timed out"),this.setState(r.EXTENSION_STATE_NOT_FOUND),this.state}const e=await this.bodhiext.getExtensionId();this.logger.info(`Extension discovered: ${e}`);const s={type:"extension",extension:"ready",extensionId:e,server:r.PENDING_EXTENSION_READY};if(t.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 requestResourceAccess(){return this.ensureBodhiext(),this.bodhiext.sendApiRequest("POST","/bodhi/v1/apps/request-access",{app_client_id:this.authClientId})}async login(){const t=await this.getAuthState();if(t.status==="authenticated")return t;this.ensureBodhiext();const e=await this.requestResourceAccess();if(r.isApiResultOperationError(e))throw r.createOperationError(e.error.message,e.error.type);if(r.isApiResultError(e)){const{message:l}=e.body.error;throw r.createOperationError(l,"auth_error")}if(!r.isApiResultSuccess(e))throw r.createOperationError(`Unexpected HTTP ${e.status}`,"auth_error");const s=e.body.scope;localStorage.setItem(this.storageKeys.RESOURCE_SCOPE,s);const o=r.generateCodeVerifier(),i=await r.generateCodeChallenge(o),a=r.generateCodeVerifier();localStorage.setItem(this.storageKeys.CODE_VERIFIER,o),localStorage.setItem(this.storageKeys.STATE,a);const h=["openid","profile","email","roles",this.config.userScope,s],c=new URLSearchParams({response_type:"code",client_id:this.authClientId,redirect_uri:this.config.redirectUri,scope:h.join(" "),state:a,code_challenge:i,code_challenge_method:"S256"}),n=`${this.authEndpoints.authorize}?${c}`;return window.location.href=n,new Promise(()=>{})}async handleOAuthCallback(t,e){const s=localStorage.getItem(this.storageKeys.STATE);if(!s||s!==e)throw new Error("Invalid state parameter - possible CSRF attack");await this.exchangeCodeForTokens(t),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(t){const e=localStorage.getItem(this.storageKeys.CODE_VERIFIER);if(!e)throw new Error("Code verifier not found");const s=new URLSearchParams({grant_type:"authorization_code",client_id:this.authClientId,code:t,redirect_uri:this.config.redirectUri,code_verifier:e}),o=await fetch(this.authEndpoints.token,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:s});if(!o.ok){const a=await o.text();throw new Error(`Token exchange failed: ${o.status} ${a}`)}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 a=Date.now()+i.expires_in*1e3;localStorage.setItem(this.storageKeys.EXPIRES_AT,a.toString())}}async logout(){const t=localStorage.getItem(this.storageKeys.REFRESH_TOKEN);if(t)try{const s=new URLSearchParams({token:t,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),localStorage.removeItem(this.storageKeys.RESOURCE_SCOPE);const e={status:"unauthenticated",user:null,accessToken:null,error:null};return this.setAuthState(e),e}async getAuthState(){const t=await this._getAccessTokenRaw();if(!t)return{status:"unauthenticated",user:null,accessToken:null,error:null};try{return{status:"authenticated",user:r.extractUserInfo(t),accessToken:t,error:null}}catch(e){return this.logger.error("Failed to parse token:",e),{status:"unauthenticated",user:null,accessToken:null,error:null}}}async _getAccessTokenRaw(){const t=localStorage.getItem(this.storageKeys.ACCESS_TOKEN),e=localStorage.getItem(this.storageKeys.EXPIRES_AT);if(!t)return null;if(e){const s=parseInt(e,10);if(Date.now()>=s-5*1e3){const o=localStorage.getItem(this.storageKeys.REFRESH_TOKEN);return o?this._tryRefreshToken(o):null}}return t}async _tryRefreshToken(t){if(this.refreshPromise)return this.logger.debug("Refresh already in progress, returning existing promise"),this.refreshPromise;this.refreshPromise=this._doRefreshToken(t);try{return await this.refreshPromise}finally{this.refreshPromise=null}}async _doRefreshToken(t){this.logger.debug("Refreshing access token");try{const e=await r.refreshAccessToken(this.authEndpoints.token,t,this.authClientId);if(e){this._storeRefreshedTokens(e);const s=r.extractUserInfo(e.access_token);return this.setAuthState({status:"authenticated",user:s,accessToken:e.access_token,error:null}),this.logger.info("Token refreshed successfully"),e.access_token}}catch(e){this.logger.warn("Token refresh failed:",e)}throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"),r.createOperationError("Access token expired and unable to refresh. Try logging out and logging in again.","token_refresh_failed")}_storeRefreshedTokens(t){const e=Date.now()+t.expires_in*1e3;localStorage.setItem(this.storageKeys.ACCESS_TOKEN,t.access_token),localStorage.setItem(this.storageKeys.EXPIRES_AT,String(e)),t.refresh_token&&localStorage.setItem(this.storageKeys.REFRESH_TOKEN,t.refresh_token)}async pingApi(){return this.sendApiRequest("GET","/ping")}async fetchModels(){return this.sendApiRequest("GET","/v1/models",void 0,void 0,!0)}async getServerState(){const t=await this.sendApiRequest("GET","/bodhi/v1/info");if(r.isApiResultOperationError(t)||!r.isApiResultSuccess(t))return r.BACKEND_SERVER_NOT_REACHABLE;const e=t.body;switch(e.status){case"ready":return{status:"ready",version:e.version||"unknown",error:null};case"setup":return r.backendServerNotReady("setup",e.version||"unknown");case"resource-admin":return r.backendServerNotReady("resource-admin",e.version||"unknown");case"error":return r.backendServerNotReady("error",e.version||"unknown",e.error?{message:e.error.message,type:e.error.type}:r.SERVER_ERROR_CODES.SERVER_NOT_READY);default:return r.BACKEND_SERVER_NOT_REACHABLE}}async*stream(t,e,s,o,i=!0){this.ensureBodhiext();let a=o||{};if(i){const n=await this._getAccessTokenRaw();if(!n)throw r.createOperationError("Not authenticated. Please log in first.","auth_error");a={...a,Authorization:`Bearer ${n}`}}const c=this.bodhiext.sendStreamRequest(t,e,s,a).getReader();try{for(;;){const{value:n,done:l}=await c.read();if(l||n?.done)break;yield n.body}}catch(n){if(n instanceof Error){if("response"in n){const l=n;throw r.createApiError(n.message,l.response.status,l.response.body)}throw"error"in n,r.createOperationError(n.message,"extension_error")}throw n}finally{c.releaseLock()}}async*streamChat(t,e,s=!0){yield*this.stream("POST","/v1/chat/completions",{model:t,messages:[{role:"user",content:e}],stream:!0},void 0,s)}serialize(){return{extensionId:this.state.type==="extension"&&this.state.extension==="ready"?this.state.extensionId:void 0}}async debug(){return{type:"WindowBodhiextClient",state:this.state,authState:await this.getAuthState(),bodhiextAvailable:this.bodhiext!==null,authClientId:this.authClientId,authServerUrl:this.config.authServerUrl,redirectUri:this.config.redirectUri,userScope:this.config.userScope}}}class _ extends r.BaseFacadeClient{constructor(t,e,s,o){const i={redirectUri:e.redirectUri,authServerUrl:e.authServerUrl||"https://id.getbodhi.app/realms/bodhi",userScope:e.userScope||"scope_user_user",basePath:e.basePath||"/",logLevel:e.logLevel||"warn",initParams:e.initParams};super(t,i,s,o,e.basePath)}createLogger(t){return new r.Logger("WebUIClient",t.logLevel)}createExtClient(t,e){return new f(this.authClientId,t,e,t.basePath)}createDirectClient(t,e,s){return new d({authClientId:t,authServerUrl:e.authServerUrl,redirectUri:e.redirectUri,userScope:e.userScope,logLevel:e.logLevel,storagePrefix:r.STORAGE_PREFIXES.WEB,basePath:e.basePath},s)}async handleOAuthCallback(t,e){return this.connectionMode==="direct"?this.directClient.handleOAuthCallback(t,e):this.extClient.handleOAuthCallback(t,e)}}const p="production";exports.WEB_BUILD_MODE=p;exports.WebUIClient=_;
|
package/dist/bodhi-web.esm.d.ts
CHANGED