@bodhiapp/bodhi-js 0.0.3 → 0.0.5
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/protocol.d.ts +0 -1
- package/dist/bodhi-js-sdk/core/src/build-info.d.ts +1 -0
- package/dist/bodhi-js-sdk/core/src/errors.d.ts +0 -1
- package/dist/bodhi-js-sdk/core/src/facade-client-base.d.ts +1 -2
- package/dist/bodhi-js-sdk/core/src/index.d.ts +1 -0
- package/dist/bodhi-js-sdk/core/src/interface.d.ts +0 -1
- 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 +0 -1
- 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/storage.d.ts +12 -0
- package/dist/bodhi-js-sdk/core/src/types/api.d.ts +0 -1
- package/dist/bodhi-js-sdk/core/src/types/callback.d.ts +0 -1
- package/dist/bodhi-js-sdk/core/src/types/client-state.d.ts +0 -1
- package/dist/bodhi-js-sdk/core/src/types/config.d.ts +0 -1
- package/dist/bodhi-js-sdk/core/src/types/platform.d.ts +0 -1
- package/dist/bodhi-js-sdk/web/src/build-info.d.ts +1 -0
- package/dist/bodhi-js-sdk/web/src/constants.d.ts +14 -4
- package/dist/bodhi-js-sdk/web/src/direct-client.d.ts +1 -1
- package/dist/bodhi-js-sdk/web/src/ext-client.d.ts +3 -3
- package/dist/bodhi-js-sdk/web/src/facade-client.d.ts +2 -1
- package/dist/bodhi-js-sdk/web/src/index.d.ts +1 -1
- package/dist/bodhi-web.cjs.js +1 -1
- package/dist/bodhi-web.esm.d.ts +1 -0
- package/dist/bodhi-web.esm.js +97 -88
- 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 +6 -5
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { OpenAiApiError, PingResponse, CreateChatCompletionRequest, CreateChatCompletionResponse, CreateChatCompletionStreamResponse } from '@bodhiapp/ts-client';
|
|
2
|
-
|
|
3
2
|
/**
|
|
4
3
|
* HTTP response wrapper - body can be success type OR error type
|
|
5
4
|
* Use isApiErrorResponse() to narrow the type based on status
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const BUILD_MODE: string;
|
|
@@ -3,7 +3,6 @@ import { IConnectionClient } from './interface';
|
|
|
3
3
|
import { Logger } from './logger';
|
|
4
4
|
import { BodhiClientUserPrefsManager } from './storage';
|
|
5
5
|
import { ApiResponseResult, AuthLoggedIn, AuthLoggedOut, AuthState, BackendServerState, ClientState, ConnectionMode, DirectState, ExtensionState, InitParams, SerializedClientState, SerializedDirectState, SerializedExtensionState, StateChange, StateChangeCallback } from './types';
|
|
6
|
-
|
|
7
6
|
/**
|
|
8
7
|
* Base facade client with common delegation logic
|
|
9
8
|
*
|
|
@@ -20,7 +19,7 @@ export declare abstract class BaseFacadeClient<TConfig, TExtClient extends IConn
|
|
|
20
19
|
protected authClientId: string;
|
|
21
20
|
protected config: TConfig;
|
|
22
21
|
protected onStateChange: StateChangeCallback;
|
|
23
|
-
constructor(authClientId: string, config: TConfig, onStateChange?: StateChangeCallback, storagePrefix?: string);
|
|
22
|
+
constructor(authClientId: string, config: TConfig, onStateChange?: StateChangeCallback, storagePrefix?: string, basePath?: string);
|
|
24
23
|
/**
|
|
25
24
|
* Create logger instance
|
|
26
25
|
* Subclasses extract logLevel from their specific config type
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { CreateChatCompletionStreamResponse } from '@bodhiapp/ts-client';
|
|
2
2
|
import { ApiResponseResult, AuthLoggedIn, AuthLoggedOut, AuthState, BackendServerState, ClientState, ConnectionMode, DirectState, ExtensionState, InitParams, StateChangeCallback } from './types';
|
|
3
|
-
|
|
4
3
|
/**
|
|
5
4
|
* ConnectionClient - Base interface for all client implementations
|
|
6
5
|
*
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { MessageType, RequestPayload, ResponsePayload, RequestMessage, RequestId } from '../../../../setup-modal/src/types';
|
|
2
|
-
|
|
3
2
|
/** Build fire-and-forget event message */
|
|
4
3
|
export declare function buildEvent<T extends MessageType>(type: T, payload: RequestPayload<T>): {
|
|
5
4
|
kind: "event";
|
|
@@ -27,6 +27,18 @@ export declare const STORAGE_PREFIXES: {
|
|
|
27
27
|
readonly WEB: "bodhi:web";
|
|
28
28
|
readonly EXT: "bodhi:ext";
|
|
29
29
|
};
|
|
30
|
+
/**
|
|
31
|
+
* Create storage prefix with basePath for path isolation
|
|
32
|
+
*
|
|
33
|
+
* @param basePath - Base path of app (e.g., '/', '/app1/')
|
|
34
|
+
* @param prefix - Storage prefix (e.g., 'bodhi:web', 'bodhijs:')
|
|
35
|
+
* @returns Combined prefix with basePath isolation
|
|
36
|
+
*
|
|
37
|
+
* Examples:
|
|
38
|
+
* - createStoragePrefixWithBasePath('/', 'bodhi:web') => '/:bodhi:web'
|
|
39
|
+
* - createStoragePrefixWithBasePath('/app1/', 'bodhi:web') => '/app1/:bodhi:web'
|
|
40
|
+
*/
|
|
41
|
+
export declare function createStoragePrefixWithBasePath(basePath: string, prefix: string): string;
|
|
30
42
|
/**
|
|
31
43
|
* User Preferences Storage Manager
|
|
32
44
|
*
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const BUILD_MODE: string;
|
|
@@ -1,9 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
* Constants for web2ext communication
|
|
3
|
-
*/
|
|
1
|
+
import { StorageKeys } from '../../core/src/index.ts';
|
|
4
2
|
export declare const POLL_INTERVAL = 500;
|
|
5
3
|
export declare const POLL_TIMEOUT = 5000;
|
|
6
4
|
/**
|
|
7
5
|
* LocalStorage keys for OAuth tokens and PKCE flow (namespaced with 'bodhi:web' prefix)
|
|
6
|
+
* @deprecated Use createWebStorageKeys(basePath) instead for basePath-aware keys
|
|
7
|
+
*/
|
|
8
|
+
export declare const STORAGE_KEYS: StorageKeys;
|
|
9
|
+
/**
|
|
10
|
+
* Create storage keys for web mode with optional basePath isolation
|
|
11
|
+
*
|
|
12
|
+
* @param basePath - Base path of app (e.g., '/', '/app1/'), defaults to '/'
|
|
13
|
+
* @returns Storage keys with basePath-aware prefix
|
|
14
|
+
*
|
|
15
|
+
* Examples:
|
|
16
|
+
* - createWebStorageKeys('/') => keys with 'bodhi:web:' prefix
|
|
17
|
+
* - createWebStorageKeys('/app1/') => keys with '/app1/:bodhi:web:' prefix
|
|
8
18
|
*/
|
|
9
|
-
export declare
|
|
19
|
+
export declare function createWebStorageKeys(basePath?: string): StorageKeys;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { DirectClientBase, AuthLoggedIn, AuthLoggedOut, DirectClientBaseConfig, StateChangeCallback } from '../../core/src/index.ts';
|
|
2
|
-
|
|
3
2
|
/**
|
|
4
3
|
* Configuration for DirectWebClient
|
|
5
4
|
*/
|
|
6
5
|
export interface DirectWebClientConfig extends DirectClientBaseConfig {
|
|
7
6
|
redirectUri: string;
|
|
7
|
+
basePath?: string;
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
10
|
* DirectWebClient - Web mode implementation using browser redirect OAuth
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { ApiResponseResult, AuthLoggedIn, AuthLoggedOut, AuthState, BackendServerState, ClientState, ExtensionState, IExtensionClient, InitParams, StateChangeCallback } from '../../core/src/index.ts';
|
|
2
2
|
import { CreateChatCompletionStreamResponse } from '@bodhiapp/ts-client';
|
|
3
3
|
import { WebClientConfig } from './facade-client';
|
|
4
|
-
|
|
5
4
|
export type SerializedWebExtensionState = {
|
|
6
5
|
extensionId?: string;
|
|
7
6
|
};
|
|
@@ -23,7 +22,8 @@ export declare class WindowBodhiextClient implements IExtensionClient {
|
|
|
23
22
|
private authEndpoints;
|
|
24
23
|
private onStateChange;
|
|
25
24
|
private refreshPromise;
|
|
26
|
-
|
|
25
|
+
private storageKeys;
|
|
26
|
+
constructor(authClientId: string, config: WebClientConfig, onStateChange?: StateChangeCallback, basePath?: string);
|
|
27
27
|
/**
|
|
28
28
|
* Set client state and notify callback
|
|
29
29
|
*/
|
|
@@ -38,7 +38,7 @@ export declare class WindowBodhiextClient implements IExtensionClient {
|
|
|
38
38
|
setStateCallback(callback: StateChangeCallback): void;
|
|
39
39
|
/**
|
|
40
40
|
* Ensure bodhiext is available, attempting to acquire it if not already set
|
|
41
|
-
* @throws
|
|
41
|
+
* @throws OperationError if client not initialized
|
|
42
42
|
*/
|
|
43
43
|
private ensureBodhiext;
|
|
44
44
|
/**
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { BaseFacadeClient, Logger, AuthLoggedIn, IWebUIClient, LogLevel, StateChange, StateChangeCallback, UserScope } from '../../core/src/index.ts';
|
|
2
2
|
import { DirectWebClient } from './direct-client';
|
|
3
3
|
import { WindowBodhiextClient } from './ext-client';
|
|
4
|
-
|
|
5
4
|
/**
|
|
6
5
|
* Configuration for WebClient OAuth
|
|
7
6
|
*/
|
|
@@ -9,6 +8,7 @@ export interface WebClientConfig {
|
|
|
9
8
|
authServerUrl: string;
|
|
10
9
|
redirectUri: string;
|
|
11
10
|
userScope: UserScope;
|
|
11
|
+
basePath?: string;
|
|
12
12
|
logLevel: LogLevel;
|
|
13
13
|
initParams?: {
|
|
14
14
|
extension?: {
|
|
@@ -28,6 +28,7 @@ export declare class WebUIClient extends BaseFacadeClient<WebClientConfig, Windo
|
|
|
28
28
|
redirectUri: string;
|
|
29
29
|
authServerUrl?: string;
|
|
30
30
|
userScope?: UserScope;
|
|
31
|
+
basePath?: string;
|
|
31
32
|
logLevel?: LogLevel;
|
|
32
33
|
initParams?: {
|
|
33
34
|
extension?: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SerializedWebExtensionState } from './ext-client';
|
|
2
2
|
import { IWebUIClient } from './interface';
|
|
3
|
-
|
|
4
3
|
export { WebUIClient } from './facade-client';
|
|
5
4
|
export type { IWebUIClient, SerializedWebExtensionState };
|
|
5
|
+
export { BUILD_MODE as WEB_BUILD_MODE } from './build-info';
|
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 S extends s.DirectClientBase{constructor(e,t){super({...e,storagePrefix:s.STORAGE_PREFIXES.DIRECT},"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(),n=await s.generateCodeChallenge(o),i=s.generateCodeVerifier();localStorage.setItem(this.storageKeys.CODE_VERIFIER,o),localStorage.setItem(this.storageKeys.STATE,i);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",r),c.searchParams.set("code_challenge",n),c.searchParams.set("code_challenge_method","S256"),c.searchParams.set("state",i),window.location.href=c.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 n=o;return this.setAuthState(n),n}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 n=await r.text();throw new Error(`Token exchange failed: ${r.status} ${n}`)}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 n=Date.now()+o.expires_in*1e3;localStorage.setItem(this.storageKeys.EXPIRES_AT,n.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 f=500,_=5e3,a=s.createStorageKeys(s.STORAGE_PREFIXES.WEB);class p{constructor(e,t,r){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}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,n){try{this.ensureBodhiext()}catch(i){return{error:{message:i instanceof Error?i.message:String(i),type:"extension_error"}}}try{let i=o||{};if(n){const l=await this._getAccessTokenRaw();if(!l)return{error:{message:"Not authenticated. Please log in first.",type:"extension_error"}};i={...i,Authorization:`Bearer ${l}`}}return await this.bodhiext.sendApiRequest(e,t,r,i)}catch(i){const c=i==null?void 0:i.error,l=(c==null?void 0:c.message)??(i instanceof Error?i.message:String(i)),h=(c==null?void 0:c.type)||"extension_error";return{error:{message:l,type:h}}}}getState(){return this.state}isClientInitialized(){return this.state.extension==="ready"}isServerReady(){return this.isClientInitialized()&&this.state.server.status==="ready"}async init(e={}){var o,n,i,c;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 l=e.timeoutMs??((n=(o=this.config.initParams)==null?void 0:o.extension)==null?void 0:n.timeoutMs)??_,h=e.intervalMs??((c=(i=this.config.initParams)==null?void 0:i.extension)==null?void 0:c.intervalMs)??f,d=Date.now();if(!await new Promise(u=>{const E=()=>{if(window.bodhiext){this.bodhiext=window.bodhiext,u(!0);return}if(Date.now()-d>=l){u(!1);return}setTimeout(E,h)};E()}))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 l=await this.getServerState();this.setState({...r,server:l}),this.logger.info(`Server connectivity tested: ${l.status}`)}catch(l){this.logger.error("Failed to get server state:",l),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(a.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),n=s.generateCodeVerifier();localStorage.setItem(a.CODE_VERIFIER,r),localStorage.setItem(a.STATE,n);const i=["openid","profile","email","roles",this.config.userScope,t],c=new URLSearchParams({response_type:"code",client_id:this.authClientId,redirect_uri:this.config.redirectUri,scope:i.join(" "),state:n,code_challenge:o,code_challenge_method:"S256"}),l=`${this.authEndpoints.authorize}?${c}`;return window.location.href=l,new Promise(()=>{})}async handleOAuthCallback(e,t){const r=localStorage.getItem(a.STATE);if(!r||r!==t)throw new Error("Invalid state parameter - possible CSRF attack");await this.exchangeCodeForTokens(e),localStorage.removeItem(a.CODE_VERIFIER),localStorage.removeItem(a.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(a.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 n=await o.json();if(!n.access_token)throw new Error("No access token received");if(localStorage.setItem(a.ACCESS_TOKEN,n.access_token),n.refresh_token&&localStorage.setItem(a.REFRESH_TOKEN,n.refresh_token),n.expires_in){const i=Date.now()+n.expires_in*1e3;localStorage.setItem(a.EXPIRES_AT,i.toString())}}async logout(){const e=localStorage.getItem(a.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(a.ACCESS_TOKEN),localStorage.removeItem(a.REFRESH_TOKEN),localStorage.removeItem(a.EXPIRES_AT),localStorage.removeItem(a.CODE_VERIFIER),localStorage.removeItem(a.STATE),localStorage.removeItem(a.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(a.ACCESS_TOKEN),t=localStorage.getItem(a.EXPIRES_AT);if(!e)return null;if(t){const r=parseInt(t,10);if(Date.now()>=r-5*1e3){const o=localStorage.getItem(a.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(a.ACCESS_TOKEN,e.access_token),localStorage.setItem(a.EXPIRES_AT,String(t)),e.refresh_token&&localStorage.setItem(a.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,n=!0){this.ensureBodhiext();let i=o||{};if(n){const h=await this._getAccessTokenRaw();if(!h)throw new Error("Not authenticated. Please log in first.");i={...i,Authorization:`Bearer ${h}`}}const l=this.bodhiext.sendStreamRequest(e,t,r,i).getReader();try{for(;;){const{value:h,done:d}=await l.read();if(d||h!=null&&h.done)break;yield h.body}}catch(h){if(h instanceof Error){if("response"in h){const d=h;throw s.createApiError(h.message,d.response.status,d.response.body)}throw"error"in h,s.createOperationError(h.message,"extension_error")}throw h}finally{l.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 m extends s.BaseFacadeClient{constructor(e,t,r,o){const n={redirectUri:t.redirectUri,authServerUrl:t.authServerUrl||"https://id.getbodhi.app/realms/bodhi",userScope:t.userScope||"scope_user_user",logLevel:t.logLevel||"warn",initParams:t.initParams};super(e,n,r,o)}createLogger(e){return new s.Logger("WebUIClient",e.logLevel)}createExtClient(e,t){return new p(this.authClientId,e,t)}createDirectClient(e,t,r){return new S({authClientId:e,authServerUrl:t.authServerUrl,redirectUri:t.redirectUri,userScope:t.userScope,logLevel:t.logLevel,storagePrefix:s.STORAGE_PREFIXES.WEB},r)}async handleOAuthCallback(e,t){return this.connectionMode==="direct"?this.directClient.handleOAuthCallback(e,t):this.extClient.handleOAuthCallback(e,t)}}exports.WebUIClient=m;
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=require("@bodhiapp/bodhi-js-core");class g 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 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",r),c.searchParams.set("code_challenge",a),c.searchParams.set("code_challenge_method","S256"),c.searchParams.set("state",i),window.location.href=c.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 u=500,E=5e3;s.createStorageKeys(s.STORAGE_PREFIXES.WEB);function S(l="/"){const e=s.createStoragePrefixWithBasePath(l,s.STORAGE_PREFIXES.WEB);return s.createStorageKeys(e)}class f{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=S(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 s.createOperationError("Client not initialized","extension_error")}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 c=i?.error,h=c?.message??(i instanceof Error?i.message:String(i)),n=c?.type||"extension_error";return{error:{message:h,type: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"),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 o=e.timeoutMs??this.config.initParams?.extension?.timeoutMs??E,a=e.intervalMs??this.config.initParams?.extension?.intervalMs??u,i=Date.now();if(!await new Promise(h=>{const n=()=>{if(window.bodhiext){this.bodhiext=window.bodhiext,h(!0);return}if(Date.now()-i>=o){h(!1);return}setTimeout(n,a)};n()}))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 o=await this.getServerState();this.setState({...r,server:o}),this.logger.info(`Server connectivity tested: ${o.status}`)}catch(o){this.logger.error("Failed to get server state:",o),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],c=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}?${c}`;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 n=await this._getAccessTokenRaw();if(!n)throw s.createOperationError("Not authenticated. Please log in first.","auth_error");i={...i,Authorization:`Bearer ${n}`}}const h=this.bodhiext.sendStreamRequest(e,t,r,i).getReader();try{for(;;){const{value:n,done:d}=await h.read();if(d||n?.done)break;yield n.body}}catch(n){if(n instanceof Error){if("response"in n){const d=n;throw s.createApiError(n.message,d.response.status,d.response.body)}throw"error"in n,s.createOperationError(n.message,"extension_error")}throw n}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 _ 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 f(this.authClientId,e,t,e.basePath)}createDirectClient(e,t,r){return new g({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 p="production";exports.WEB_BUILD_MODE=p;exports.WebUIClient=_;
|
package/dist/bodhi-web.esm.d.ts
CHANGED
package/dist/bodhi-web.esm.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { DirectClientBase as
|
|
2
|
-
class
|
|
1
|
+
import { DirectClientBase as R, createStoragePrefixWithBasePath as y, STORAGE_PREFIXES as S, generateCodeVerifier as u, generateCodeChallenge as _, isApiResultOperationError as w, isApiResultSuccess as f, createStorageKeys as I, EXTENSION_STATE_NOT_INITIALIZED as p, Logger as T, createOAuthEndpoints as C, NOOP_STATE_CALLBACK as x, createOperationError as h, EXTENSION_STATE_NOT_FOUND as A, PENDING_EXTENSION_READY as k, BACKEND_SERVER_NOT_REACHABLE as g, extractUserInfo as m, refreshAccessToken as v, backendServerNotReady as E, SERVER_ERROR_CODES as b, createApiError as O, BaseFacadeClient as P } from "@bodhiapp/bodhi-js-core";
|
|
2
|
+
class K extends R {
|
|
3
3
|
constructor(e, t) {
|
|
4
|
-
|
|
4
|
+
const r = e.basePath || "/", s = y(r, S.DIRECT);
|
|
5
|
+
super({ ...e, storagePrefix: s }, "DirectWebClient", t), this.redirectUri = e.redirectUri;
|
|
5
6
|
}
|
|
6
7
|
// ============================================================================
|
|
7
8
|
// Authentication (Browser Redirect OAuth)
|
|
@@ -10,10 +11,10 @@ class L extends C {
|
|
|
10
11
|
const e = await this.getAuthState();
|
|
11
12
|
if (e.isLoggedIn)
|
|
12
13
|
return e;
|
|
13
|
-
const t = await this.requestResourceAccess(), r = `openid profile email roles ${this.userScope} ${t}`, s =
|
|
14
|
+
const t = await this.requestResourceAccess(), r = `openid profile email roles ${this.userScope} ${t}`, s = u(), i = await _(s), o = u();
|
|
14
15
|
localStorage.setItem(this.storageKeys.CODE_VERIFIER, s), localStorage.setItem(this.storageKeys.STATE, o);
|
|
15
|
-
const
|
|
16
|
-
throw
|
|
16
|
+
const n = new URL(this.authEndpoints.authorize);
|
|
17
|
+
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", i), n.searchParams.set("code_challenge_method", "S256"), n.searchParams.set("state", o), window.location.href = n.toString(), new Error("Redirect initiated");
|
|
17
18
|
}
|
|
18
19
|
async handleOAuthCallback(e, t) {
|
|
19
20
|
const r = localStorage.getItem(this.storageKeys.STATE);
|
|
@@ -62,9 +63,9 @@ class L extends C {
|
|
|
62
63
|
{},
|
|
63
64
|
!1
|
|
64
65
|
);
|
|
65
|
-
if (
|
|
66
|
+
if (w(e))
|
|
66
67
|
throw new Error("Failed to get resource access scope from server");
|
|
67
|
-
if (!
|
|
68
|
+
if (!f(e))
|
|
68
69
|
throw new Error("Failed to get resource access scope from server: API error");
|
|
69
70
|
const t = e.body.scope;
|
|
70
71
|
return localStorage.setItem(this.storageKeys.RESOURCE_SCOPE, t), t;
|
|
@@ -114,10 +115,15 @@ class L extends C {
|
|
|
114
115
|
return this.redirectUri;
|
|
115
116
|
}
|
|
116
117
|
}
|
|
117
|
-
const
|
|
118
|
+
const U = 500, L = 5e3;
|
|
119
|
+
I(S.WEB);
|
|
120
|
+
function N(l = "/") {
|
|
121
|
+
const e = y(l, S.WEB);
|
|
122
|
+
return I(e);
|
|
123
|
+
}
|
|
118
124
|
class D {
|
|
119
|
-
constructor(e, t, r) {
|
|
120
|
-
this.state =
|
|
125
|
+
constructor(e, t, r, s) {
|
|
126
|
+
this.state = p, this.bodhiext = null, this.refreshPromise = null, this.logger = new T("WindowBodhiextClient", t.logLevel), this.authClientId = e, this.config = t, this.authEndpoints = C(this.config.authServerUrl), this.onStateChange = r ?? x, this.storageKeys = N(s || "/");
|
|
121
127
|
}
|
|
122
128
|
/**
|
|
123
129
|
* Set client state and notify callback
|
|
@@ -142,11 +148,11 @@ class D {
|
|
|
142
148
|
// ============================================================================
|
|
143
149
|
/**
|
|
144
150
|
* Ensure bodhiext is available, attempting to acquire it if not already set
|
|
145
|
-
* @throws
|
|
151
|
+
* @throws OperationError if client not initialized
|
|
146
152
|
*/
|
|
147
153
|
ensureBodhiext() {
|
|
148
154
|
if (!this.bodhiext && window.bodhiext && (this.logger.info("Acquiring window.bodhiext reference"), this.bodhiext = window.bodhiext), !this.bodhiext)
|
|
149
|
-
throw
|
|
155
|
+
throw h("Client not initialized", "extension_error");
|
|
150
156
|
}
|
|
151
157
|
/**
|
|
152
158
|
* Send extension request via window.bodhiext.sendExtRequest
|
|
@@ -172,8 +178,8 @@ class D {
|
|
|
172
178
|
try {
|
|
173
179
|
let o = s || {};
|
|
174
180
|
if (i) {
|
|
175
|
-
const
|
|
176
|
-
if (!
|
|
181
|
+
const c = await this._getAccessTokenRaw();
|
|
182
|
+
if (!c)
|
|
177
183
|
return {
|
|
178
184
|
error: {
|
|
179
185
|
message: "Not authenticated. Please log in first.",
|
|
@@ -182,7 +188,7 @@ class D {
|
|
|
182
188
|
};
|
|
183
189
|
o = {
|
|
184
190
|
...o,
|
|
185
|
-
Authorization: `Bearer ${
|
|
191
|
+
Authorization: `Bearer ${c}`
|
|
186
192
|
};
|
|
187
193
|
}
|
|
188
194
|
return await this.bodhiext.sendApiRequest(
|
|
@@ -192,11 +198,11 @@ class D {
|
|
|
192
198
|
o
|
|
193
199
|
);
|
|
194
200
|
} catch (o) {
|
|
195
|
-
const
|
|
201
|
+
const n = o?.error, c = n?.message ?? (o instanceof Error ? o.message : String(o)), a = n?.type || "extension_error";
|
|
196
202
|
return {
|
|
197
203
|
error: {
|
|
198
|
-
message:
|
|
199
|
-
type:
|
|
204
|
+
message: c,
|
|
205
|
+
type: a
|
|
200
206
|
}
|
|
201
207
|
};
|
|
202
208
|
}
|
|
@@ -221,28 +227,27 @@ class D {
|
|
|
221
227
|
* No extensionId storage/restoration needed - window.bodhiext handle is ephemeral
|
|
222
228
|
*/
|
|
223
229
|
async init(e = {}) {
|
|
224
|
-
var s, i, o, a;
|
|
225
230
|
if (!e.testConnection && !e.selectedConnection)
|
|
226
|
-
return this.logger.info("No testConnection or selectedConnection, returning not-initialized state"),
|
|
231
|
+
return this.logger.info("No testConnection or selectedConnection, returning not-initialized state"), p;
|
|
227
232
|
if (this.bodhiext && !e.testConnection)
|
|
228
233
|
return this.logger.debug("Already have bodhiext handle, skipping polling"), this.state;
|
|
229
234
|
if (!this.bodhiext) {
|
|
230
|
-
const
|
|
231
|
-
if (!await new Promise((
|
|
232
|
-
const
|
|
235
|
+
const s = e.timeoutMs ?? this.config.initParams?.extension?.timeoutMs ?? L, i = e.intervalMs ?? this.config.initParams?.extension?.intervalMs ?? U, o = Date.now();
|
|
236
|
+
if (!await new Promise((c) => {
|
|
237
|
+
const a = () => {
|
|
233
238
|
if (window.bodhiext) {
|
|
234
|
-
this.bodhiext = window.bodhiext,
|
|
239
|
+
this.bodhiext = window.bodhiext, c(!0);
|
|
235
240
|
return;
|
|
236
241
|
}
|
|
237
|
-
if (Date.now() -
|
|
238
|
-
|
|
242
|
+
if (Date.now() - o >= s) {
|
|
243
|
+
c(!1);
|
|
239
244
|
return;
|
|
240
245
|
}
|
|
241
|
-
setTimeout(
|
|
246
|
+
setTimeout(a, i);
|
|
242
247
|
};
|
|
243
|
-
|
|
248
|
+
a();
|
|
244
249
|
}))
|
|
245
|
-
return this.logger.warn("Extension discovery timed out"), this.setState(
|
|
250
|
+
return this.logger.warn("Extension discovery timed out"), this.setState(A), this.state;
|
|
246
251
|
}
|
|
247
252
|
const t = await this.bodhiext.getExtensionId();
|
|
248
253
|
this.logger.info(`Extension discovered: ${t}`);
|
|
@@ -250,14 +255,14 @@ class D {
|
|
|
250
255
|
type: "extension",
|
|
251
256
|
extension: "ready",
|
|
252
257
|
extensionId: t,
|
|
253
|
-
server:
|
|
258
|
+
server: k
|
|
254
259
|
};
|
|
255
260
|
if (e.testConnection)
|
|
256
261
|
try {
|
|
257
|
-
const
|
|
258
|
-
this.setState({ ...r, server:
|
|
259
|
-
} catch (
|
|
260
|
-
this.logger.error("Failed to get server state:",
|
|
262
|
+
const s = await this.getServerState();
|
|
263
|
+
this.setState({ ...r, server: s }), this.logger.info(`Server connectivity tested: ${s.status}`);
|
|
264
|
+
} catch (s) {
|
|
265
|
+
this.logger.error("Failed to get server state:", s), this.setState({ ...r, server: g });
|
|
261
266
|
}
|
|
262
267
|
else
|
|
263
268
|
this.setState(r);
|
|
@@ -275,10 +280,10 @@ class D {
|
|
|
275
280
|
const e = await this.bodhiext.sendApiRequest("POST", "/bodhi/v1/apps/request-access", {
|
|
276
281
|
app_client_id: this.authClientId
|
|
277
282
|
});
|
|
278
|
-
if (!
|
|
283
|
+
if (!f(e))
|
|
279
284
|
throw new Error("Failed to get resource access scope: API error");
|
|
280
285
|
const t = e.body.scope;
|
|
281
|
-
return localStorage.setItem(
|
|
286
|
+
return localStorage.setItem(this.storageKeys.RESOURCE_SCOPE, t), t;
|
|
282
287
|
}
|
|
283
288
|
/**
|
|
284
289
|
* Login via browser redirect OAuth2 + PKCE flow
|
|
@@ -289,9 +294,9 @@ class D {
|
|
|
289
294
|
if (e.isLoggedIn)
|
|
290
295
|
return e;
|
|
291
296
|
this.ensureBodhiext();
|
|
292
|
-
const t = await this.requestResourceAccess(), r =
|
|
293
|
-
localStorage.setItem(
|
|
294
|
-
const o = ["openid", "profile", "email", "roles", this.config.userScope, t],
|
|
297
|
+
const t = await this.requestResourceAccess(), r = u(), s = await _(r), i = u();
|
|
298
|
+
localStorage.setItem(this.storageKeys.CODE_VERIFIER, r), localStorage.setItem(this.storageKeys.STATE, i);
|
|
299
|
+
const o = ["openid", "profile", "email", "roles", this.config.userScope, t], n = new URLSearchParams({
|
|
295
300
|
response_type: "code",
|
|
296
301
|
client_id: this.authClientId,
|
|
297
302
|
redirect_uri: this.config.redirectUri,
|
|
@@ -299,8 +304,8 @@ class D {
|
|
|
299
304
|
state: i,
|
|
300
305
|
code_challenge: s,
|
|
301
306
|
code_challenge_method: "S256"
|
|
302
|
-
}),
|
|
303
|
-
return window.location.href =
|
|
307
|
+
}), c = `${this.authEndpoints.authorize}?${n}`;
|
|
308
|
+
return window.location.href = c, new Promise(() => {
|
|
304
309
|
});
|
|
305
310
|
}
|
|
306
311
|
/**
|
|
@@ -309,10 +314,10 @@ class D {
|
|
|
309
314
|
* @returns AuthLoggedIn with login state and user info
|
|
310
315
|
*/
|
|
311
316
|
async handleOAuthCallback(e, t) {
|
|
312
|
-
const r = localStorage.getItem(
|
|
317
|
+
const r = localStorage.getItem(this.storageKeys.STATE);
|
|
313
318
|
if (!r || r !== t)
|
|
314
319
|
throw new Error("Invalid state parameter - possible CSRF attack");
|
|
315
|
-
await this.exchangeCodeForTokens(e), localStorage.removeItem(
|
|
320
|
+
await this.exchangeCodeForTokens(e), localStorage.removeItem(this.storageKeys.CODE_VERIFIER), localStorage.removeItem(this.storageKeys.STATE);
|
|
316
321
|
const s = await this.getAuthState();
|
|
317
322
|
if (!s.isLoggedIn)
|
|
318
323
|
throw new Error("Login failed");
|
|
@@ -322,7 +327,7 @@ class D {
|
|
|
322
327
|
* Exchange authorization code for tokens
|
|
323
328
|
*/
|
|
324
329
|
async exchangeCodeForTokens(e) {
|
|
325
|
-
const t = localStorage.getItem(
|
|
330
|
+
const t = localStorage.getItem(this.storageKeys.CODE_VERIFIER);
|
|
326
331
|
if (!t)
|
|
327
332
|
throw new Error("Code verifier not found");
|
|
328
333
|
const r = new URLSearchParams({
|
|
@@ -345,9 +350,9 @@ class D {
|
|
|
345
350
|
const i = await s.json();
|
|
346
351
|
if (!i.access_token)
|
|
347
352
|
throw new Error("No access token received");
|
|
348
|
-
if (localStorage.setItem(
|
|
353
|
+
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) {
|
|
349
354
|
const o = Date.now() + i.expires_in * 1e3;
|
|
350
|
-
localStorage.setItem(
|
|
355
|
+
localStorage.setItem(this.storageKeys.EXPIRES_AT, o.toString());
|
|
351
356
|
}
|
|
352
357
|
}
|
|
353
358
|
/**
|
|
@@ -355,7 +360,7 @@ class D {
|
|
|
355
360
|
* @returns AuthLoggedOut with logged out state
|
|
356
361
|
*/
|
|
357
362
|
async logout() {
|
|
358
|
-
const e = localStorage.getItem(
|
|
363
|
+
const e = localStorage.getItem(this.storageKeys.REFRESH_TOKEN);
|
|
359
364
|
if (e)
|
|
360
365
|
try {
|
|
361
366
|
const r = new URLSearchParams({
|
|
@@ -373,7 +378,7 @@ class D {
|
|
|
373
378
|
} catch (r) {
|
|
374
379
|
this.logger.warn("Token revocation failed:", r);
|
|
375
380
|
}
|
|
376
|
-
localStorage.removeItem(
|
|
381
|
+
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);
|
|
377
382
|
const t = {
|
|
378
383
|
isLoggedIn: !1
|
|
379
384
|
};
|
|
@@ -387,7 +392,7 @@ class D {
|
|
|
387
392
|
if (!e)
|
|
388
393
|
return { isLoggedIn: !1 };
|
|
389
394
|
try {
|
|
390
|
-
return { isLoggedIn: !0, userInfo:
|
|
395
|
+
return { isLoggedIn: !0, userInfo: m(e), accessToken: e };
|
|
391
396
|
} catch (t) {
|
|
392
397
|
return this.logger.error("Failed to parse token:", t), { isLoggedIn: !1 };
|
|
393
398
|
}
|
|
@@ -397,13 +402,13 @@ class D {
|
|
|
397
402
|
* Returns null if not logged in or token expired
|
|
398
403
|
*/
|
|
399
404
|
async _getAccessTokenRaw() {
|
|
400
|
-
const e = localStorage.getItem(
|
|
405
|
+
const e = localStorage.getItem(this.storageKeys.ACCESS_TOKEN), t = localStorage.getItem(this.storageKeys.EXPIRES_AT);
|
|
401
406
|
if (!e)
|
|
402
407
|
return null;
|
|
403
408
|
if (t) {
|
|
404
409
|
const r = parseInt(t, 10);
|
|
405
410
|
if (Date.now() >= r - 5 * 1e3) {
|
|
406
|
-
const s = localStorage.getItem(
|
|
411
|
+
const s = localStorage.getItem(this.storageKeys.REFRESH_TOKEN);
|
|
407
412
|
return s ? this._tryRefreshToken(s) : null;
|
|
408
413
|
}
|
|
409
414
|
}
|
|
@@ -429,14 +434,14 @@ class D {
|
|
|
429
434
|
async _doRefreshToken(e) {
|
|
430
435
|
this.logger.debug("Refreshing access token");
|
|
431
436
|
try {
|
|
432
|
-
const t = await
|
|
437
|
+
const t = await v(
|
|
433
438
|
this.authEndpoints.token,
|
|
434
439
|
e,
|
|
435
440
|
this.authClientId
|
|
436
441
|
);
|
|
437
442
|
if (t) {
|
|
438
443
|
this._storeRefreshedTokens(t);
|
|
439
|
-
const r =
|
|
444
|
+
const r = m(t.access_token);
|
|
440
445
|
return this.setAuthState({
|
|
441
446
|
isLoggedIn: !0,
|
|
442
447
|
userInfo: r,
|
|
@@ -446,7 +451,7 @@ class D {
|
|
|
446
451
|
} catch (t) {
|
|
447
452
|
this.logger.warn("Token refresh failed:", t);
|
|
448
453
|
}
|
|
449
|
-
throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"),
|
|
454
|
+
throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"), h(
|
|
450
455
|
"Access token expired and unable to refresh. Try logging out and logging in again.",
|
|
451
456
|
"token_refresh_failed"
|
|
452
457
|
);
|
|
@@ -456,7 +461,7 @@ class D {
|
|
|
456
461
|
*/
|
|
457
462
|
_storeRefreshedTokens(e) {
|
|
458
463
|
const t = Date.now() + e.expires_in * 1e3;
|
|
459
|
-
localStorage.setItem(
|
|
464
|
+
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);
|
|
460
465
|
}
|
|
461
466
|
/**
|
|
462
467
|
* Ping API
|
|
@@ -482,26 +487,26 @@ class D {
|
|
|
482
487
|
*/
|
|
483
488
|
async getServerState() {
|
|
484
489
|
const e = await this.sendApiRequest("GET", "/bodhi/v1/info");
|
|
485
|
-
if (
|
|
486
|
-
return
|
|
487
|
-
if (!
|
|
488
|
-
return
|
|
490
|
+
if (w(e))
|
|
491
|
+
return g;
|
|
492
|
+
if (!f(e))
|
|
493
|
+
return g;
|
|
489
494
|
const t = e.body;
|
|
490
495
|
switch (t.status) {
|
|
491
496
|
case "ready":
|
|
492
497
|
return { status: "ready", version: t.version || "unknown" };
|
|
493
498
|
case "setup":
|
|
494
|
-
return
|
|
499
|
+
return E("setup", t.version || "unknown");
|
|
495
500
|
case "resource-admin":
|
|
496
|
-
return
|
|
501
|
+
return E("resource-admin", t.version || "unknown");
|
|
497
502
|
case "error":
|
|
498
|
-
return
|
|
503
|
+
return E(
|
|
499
504
|
"error",
|
|
500
505
|
t.version || "unknown",
|
|
501
|
-
t.error ? { message: t.error.message, type: t.error.type } :
|
|
506
|
+
t.error ? { message: t.error.message, type: t.error.type } : b.SERVER_NOT_READY
|
|
502
507
|
);
|
|
503
508
|
default:
|
|
504
|
-
return
|
|
509
|
+
return g;
|
|
505
510
|
}
|
|
506
511
|
}
|
|
507
512
|
/**
|
|
@@ -512,33 +517,33 @@ class D {
|
|
|
512
517
|
this.ensureBodhiext();
|
|
513
518
|
let o = s || {};
|
|
514
519
|
if (i) {
|
|
515
|
-
const
|
|
516
|
-
if (!
|
|
517
|
-
throw
|
|
520
|
+
const a = await this._getAccessTokenRaw();
|
|
521
|
+
if (!a)
|
|
522
|
+
throw h("Not authenticated. Please log in first.", "auth_error");
|
|
518
523
|
o = {
|
|
519
524
|
...o,
|
|
520
|
-
Authorization: `Bearer ${
|
|
525
|
+
Authorization: `Bearer ${a}`
|
|
521
526
|
};
|
|
522
527
|
}
|
|
523
|
-
const
|
|
528
|
+
const c = this.bodhiext.sendStreamRequest(e, t, r, o).getReader();
|
|
524
529
|
try {
|
|
525
530
|
for (; ; ) {
|
|
526
|
-
const { value:
|
|
527
|
-
if (
|
|
531
|
+
const { value: a, done: d } = await c.read();
|
|
532
|
+
if (d || a?.done)
|
|
528
533
|
break;
|
|
529
|
-
yield
|
|
534
|
+
yield a.body;
|
|
530
535
|
}
|
|
531
|
-
} catch (
|
|
532
|
-
if (
|
|
533
|
-
if ("response" in
|
|
534
|
-
const
|
|
535
|
-
throw
|
|
536
|
+
} catch (a) {
|
|
537
|
+
if (a instanceof Error) {
|
|
538
|
+
if ("response" in a) {
|
|
539
|
+
const d = a;
|
|
540
|
+
throw O(a.message, d.response.status, d.response.body);
|
|
536
541
|
}
|
|
537
|
-
throw "error" in
|
|
542
|
+
throw "error" in a ? h(a.message, "extension_error") : h(a.message, "extension_error");
|
|
538
543
|
}
|
|
539
|
-
throw
|
|
544
|
+
throw a;
|
|
540
545
|
} finally {
|
|
541
|
-
|
|
546
|
+
c.releaseLock();
|
|
542
547
|
}
|
|
543
548
|
}
|
|
544
549
|
/**
|
|
@@ -581,32 +586,34 @@ class D {
|
|
|
581
586
|
};
|
|
582
587
|
}
|
|
583
588
|
}
|
|
584
|
-
class
|
|
589
|
+
class B extends P {
|
|
585
590
|
constructor(e, t, r, s) {
|
|
586
591
|
const i = {
|
|
587
592
|
redirectUri: t.redirectUri,
|
|
588
593
|
authServerUrl: t.authServerUrl || "https://id.getbodhi.app/realms/bodhi",
|
|
589
594
|
userScope: t.userScope || "scope_user_user",
|
|
595
|
+
basePath: t.basePath || "/",
|
|
590
596
|
logLevel: t.logLevel || "warn",
|
|
591
597
|
initParams: t.initParams
|
|
592
598
|
};
|
|
593
|
-
super(e, i, r, s);
|
|
599
|
+
super(e, i, r, s, t.basePath);
|
|
594
600
|
}
|
|
595
601
|
createLogger(e) {
|
|
596
|
-
return new
|
|
602
|
+
return new T("WebUIClient", e.logLevel);
|
|
597
603
|
}
|
|
598
604
|
createExtClient(e, t) {
|
|
599
|
-
return new D(this.authClientId, e, t);
|
|
605
|
+
return new D(this.authClientId, e, t, e.basePath);
|
|
600
606
|
}
|
|
601
607
|
createDirectClient(e, t, r) {
|
|
602
|
-
return new
|
|
608
|
+
return new K(
|
|
603
609
|
{
|
|
604
610
|
authClientId: e,
|
|
605
611
|
authServerUrl: t.authServerUrl,
|
|
606
612
|
redirectUri: t.redirectUri,
|
|
607
613
|
userScope: t.userScope,
|
|
608
614
|
logLevel: t.logLevel,
|
|
609
|
-
storagePrefix:
|
|
615
|
+
storagePrefix: S.WEB,
|
|
616
|
+
basePath: t.basePath
|
|
610
617
|
},
|
|
611
618
|
r
|
|
612
619
|
);
|
|
@@ -618,6 +625,8 @@ class V extends N {
|
|
|
618
625
|
return this.connectionMode === "direct" ? this.directClient.handleOAuthCallback(e, t) : this.extClient.handleOAuthCallback(e, t);
|
|
619
626
|
}
|
|
620
627
|
}
|
|
628
|
+
const q = "production";
|
|
621
629
|
export {
|
|
622
|
-
|
|
630
|
+
q as WEB_BUILD_MODE,
|
|
631
|
+
B as WebUIClient
|
|
623
632
|
};
|
|
@@ -2,7 +2,6 @@ import { Browser, EnvState, OS } from './platform';
|
|
|
2
2
|
import { ExtensionState } from './extension';
|
|
3
3
|
import { ServerState } from './server';
|
|
4
4
|
import { LnaServerState, LnaState } from './lna';
|
|
5
|
-
|
|
6
5
|
export declare enum SetupStep {
|
|
7
6
|
PLATFORM_CHECK = "platform-check",
|
|
8
7
|
SERVER_SETUP = "server-setup",
|
|
@@ -2,7 +2,6 @@ import { ExtensionState, ExtensionStateNotReady, ExtensionStateReady } from './e
|
|
|
2
2
|
import { ServerState, ServerStateError, ServerStatePending, ServerStateReachable, ServerStateReady, ServerStateUnreachable } from './server';
|
|
3
3
|
import { LnaServerState, LnaServerStateError, LnaServerStatePending, LnaServerStateReady, LnaServerStateResourceAdmin, LnaServerStateSetup, LnaState, LnaStateDenied, LnaStateGranted, LnaStatePrompt, LnaStateSkipped, LnaStateUnreachable, LnaStateUnsupported } from './lna';
|
|
4
4
|
import { Browser, NotSupportedBrowser, NotSupportedOS, OS, SupportedBrowser, SupportedOS } from './platform';
|
|
5
|
-
|
|
6
5
|
export declare function isExtensionStateReady(ext: ExtensionState): ext is ExtensionStateReady;
|
|
7
6
|
export declare function isExtensionStateNotReady(ext: ExtensionState): ext is ExtensionStateNotReady;
|
|
8
7
|
export declare function isServerStateReady(server: ServerState): server is ServerStateReady;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bodhiapp/bodhi-js",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "Web SDK for Bodhi Browser - window.bodhiext communication",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/bodhi-web.cjs.js",
|
|
@@ -34,11 +34,12 @@
|
|
|
34
34
|
"lint:fix": "prettier --write . && eslint . --ext .js,.jsx,.ts,.tsx --fix"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@bodhiapp/bodhi-js-core": "0.0.
|
|
38
|
-
"@bodhiapp/ts-client": "
|
|
37
|
+
"@bodhiapp/bodhi-js-core": "0.0.5",
|
|
38
|
+
"@bodhiapp/ts-client": "0.1.8"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@eslint/js": "^9.23.0",
|
|
42
|
+
"@types/node": "^20.19.10",
|
|
42
43
|
"@typescript-eslint/eslint-plugin": "8.28.0",
|
|
43
44
|
"@typescript-eslint/parser": "8.28.0",
|
|
44
45
|
"eslint": "9.32.0",
|
|
@@ -48,7 +49,7 @@
|
|
|
48
49
|
"rimraf": "^6.0.1",
|
|
49
50
|
"tslib": "^2.6.2",
|
|
50
51
|
"typescript": "^5.8.3",
|
|
51
|
-
"vite": "^
|
|
52
|
-
"vite-plugin-dts": "^
|
|
52
|
+
"vite": "^7.1.12",
|
|
53
|
+
"vite-plugin-dts": "^4.5.4"
|
|
53
54
|
}
|
|
54
55
|
}
|