@bodhiapp/bodhi-js-ext 0.0.28 → 0.0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bodhi-ext.cjs.js +1 -1
- package/dist/bodhi-ext.esm.js +41 -47
- package/dist/direct-client.d.ts +0 -2
- package/dist/ext-client.d.ts +2 -4
- package/dist/ext2ext-client.d.ts +1 -3
- package/dist/facade-client.d.ts +1 -3
- package/package.json +4 -4
package/dist/bodhi-ext.cjs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const n=require("@bodhiapp/bodhi-js-core"),g=require("@bodhiapp/bodhi-browser-types"),h={EXT2EXT_CLIENT_REQUEST:"EXT2EXT_CLIENT_REQUEST",EXT2EXT_CLIENT_RESPONSE:"EXT2EXT_CLIENT_RESPONSE",EXT2EXT_CLIENT_BROADCAST:"EXT2EXT_CLIENT_BROADCAST",EXT2EXT_CLIENT_API_REQUEST:"EXT2EXT_CLIENT_API_REQUEST",EXT2EXT_CLIENT_API_RESPONSE:"EXT2EXT_CLIENT_API_RESPONSE",EXT2EXT_CLIENT_STREAM_REQUEST:"EXT2EXT_CLIENT_STREAM_REQUEST",EXT2EXT_CLIENT_STREAM_CHUNK:"EXT2EXT_CLIENT_STREAM_CHUNK",EXT2EXT_CLIENT_STREAM_ERROR:"EXT2EXT_CLIENT_STREAM_ERROR",EXT2EXT_CLIENT_STREAM_API_ERROR:"EXT2EXT_CLIENT_STREAM_API_ERROR",EXT2EXT_CLIENT_STREAM_DONE:"EXT2EXT_CLIENT_STREAM_DONE"},f="ext2ext-client-stream",_={LOGIN:"login",LOGOUT:"logout",GET_AUTH_STATE:"getAuthState",DISCOVER_EXTENSION:"discoverBodhiExtension",GET_EXTENSION_ID:"get_extension_id",SET_EXTENSION_ID:"setExtensionId"},x=5e3,C=3,w=500,A=500,N=3e4;function P(S){return"error"in S}class v extends n.DirectClientBase{constructor(e,t){const r=n.createStoragePrefixWithBasePath(e.basePath,n.STORAGE_PREFIXES.EXT_DIRECT),i={authClientId:e.authClientId,authServerUrl:e.authServerUrl,userRole:e.userRole,storagePrefix:r,logLevel:e.logLevel,loggerPrefix:"DirectExtClient",apiTimeoutMs:e.apiTimeoutMs};super(i,t)}async login(e){const t=await this.getAuthState();if(t.status==="authenticated")return t;e?.flowType==="redirect"&&this.logger.warn("Extension mode does not support redirect flow type; using popup instead");const r=e?.userRole??this.userRole;e?.onProgress?.("requesting");const i=new n.AccessRequestBuilder(this.authClientId).requestedRole(r).flowType("popup");e?.requested&&i.requested(e.requested);const o=i.build(),s=await this.requestAccess(o),{id:a,review_url:E}=n.unwrapResponse(s);e?.onProgress?.("reviewing"),await chrome.tabs.create({url:E});const c=await this.pollAccessRequestStatus(a,{intervalMs:e?.pollIntervalMs??n.DEFAULT_POLL_INTERVAL_MS,timeoutMs:e?.pollTimeoutMs??n.DEFAULT_POLL_TIMEOUT_MS});if(c.status!=="approved")throw n.createOperationError("auth_error",`Access request ${c.status}`);const u=c.access_request_scope;return e?.onProgress?.("authenticating"),this.performOAuthPkce(`openid profile email roles ${u??""}`.trim())}async performOAuthPkce(e){const t=n.generateCodeVerifier(),r=await n.generateCodeChallenge(t),i=n.generateCodeVerifier();await chrome.storage.session.set({[this.storageKeys.CODE_VERIFIER]:t,[this.storageKeys.STATE]:i});const o=chrome.identity.getRedirectURL("callback"),s=new URL(this.authEndpoints.authorize);return s.searchParams.set("client_id",this.authClientId),s.searchParams.set("response_type","code"),s.searchParams.set("redirect_uri",o),s.searchParams.set("scope",e),s.searchParams.set("code_challenge",r),s.searchParams.set("code_challenge_method","S256"),s.searchParams.set("state",i),new Promise((a,E)=>{chrome.identity.launchWebAuthFlow({url:s.toString(),interactive:!0},async c=>{if(chrome.runtime.lastError){await chrome.storage.session.remove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),E(chrome.runtime.lastError);return}if(!c){await chrome.storage.session.remove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),E(n.createOperationError("oauth_error","No redirect URL received"));return}try{const u=new URL(c),d=u.searchParams.get("code"),T=u.searchParams.get("state"),m=await chrome.storage.session.get(this.storageKeys.STATE);if(T!==m[this.storageKeys.STATE]){await chrome.storage.session.remove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),E(n.createOperationError("oauth_error","State mismatch"));return}if(!d){await chrome.storage.session.remove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),E(n.createOperationError("oauth_error","No authorization code"));return}await this.exchangeCodeForTokens(d);const l=await this.getAuthState();if(l.status!=="authenticated")throw n.createOperationError("oauth_error","Login failed");this.setAuthState(l),await chrome.storage.session.remove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),a(l)}catch(u){await chrome.storage.session.remove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),E(u)}})})}async logout(){const t=(await chrome.storage.session.get(this.storageKeys.REFRESH_TOKEN))[this.storageKeys.REFRESH_TOKEN];if(t)try{const i=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:i})}catch(i){this.logger.warn("Token revocation failed:",i)}await chrome.storage.session.remove([this.storageKeys.ACCESS_TOKEN,this.storageKeys.REFRESH_TOKEN,this.storageKeys.EXPIRES_AT]);const r={status:"unauthenticated",user:null,accessToken:null,error:null};return this.setAuthState(r),r}async exchangeCodeForTokens(e){const r=(await chrome.storage.session.get(this.storageKeys.CODE_VERIFIER))[this.storageKeys.CODE_VERIFIER],i=chrome.identity.getRedirectURL("callback"),o=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:i,client_id:this.authClientId,code_verifier:r})});if(!o.ok){const E=await o.text();throw new Error(`Token exchange failed: ${o.status} ${E}`)}const s=await o.json(),a=Date.now()+(s.expires_in||3600)*1e3;await chrome.storage.session.set({[this.storageKeys.ACCESS_TOKEN]:s.access_token,[this.storageKeys.REFRESH_TOKEN]:s.refresh_token,[this.storageKeys.EXPIRES_AT]:a}),await chrome.storage.session.remove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE])}async _storageGet(e){const r=(await chrome.storage.session.get(e))[e];return r!==void 0?String(r):null}async _storageSet(e){await chrome.storage.session.set(e)}async _storageRemove(e){await chrome.storage.session.remove(e)}_getRedirectUri(){return chrome.identity.getRedirectURL("callback")}}class X{constructor(e={},t){this.state={type:"extension",extension:"not-initialized",extensionId:null,server:n.PENDING_EXTENSION_READY},this.extensionId=null,this.broadcastListenerActive=!1,this.config=e,this.logger=new n.Logger("ExtClient",e?.logLevel||"warn"),this.onStateChange=t??n.NOOP_STATE_CALLBACK,this.apiTimeoutMs=e.apiTimeoutMs??N,this.authClientId=e.authClientId??""}setState(e){this.state=e,this.onStateChange({type:"client-state",state:e})}setAuthState(e){this.onStateChange({type:"auth-state",state:e})}setStateCallback(e){this.onStateChange=e}setupBroadcastListener(){this.broadcastListenerActive||(this.broadcastListenerActive=!0,chrome.runtime.onMessage.addListener(e=>{const t=e;return t?.type===h.EXT2EXT_CLIENT_BROADCAST&&t.event==="authStateChanged"&&this.handleAuthStateChangedBroadcast(),!1}),this.logger.debug("Broadcast listener setup complete"))}async handleAuthStateChangedBroadcast(){this.logger.debug("Received authStateChanged broadcast, refreshing auth state");const e=await this.getAuthState();this.setAuthState(e)}generateRequestId(){return crypto.randomUUID()}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){this.logger.info("No testConnection or selectedConnection, returning not-initialized state");const i=n.createExtensionStateNotInitialized();return this.setState(i),i}if(this.extensionId&&!e.testConnection)return this.logger.debug("Already initialized with extensionId, skipping discovery"),this.state;const t=e.timeoutMs??this.config.initParams?.extension?.timeoutMs??x,r=e.savedState?.extensionId;try{if(!this.extensionId){if(r)this.logger.info("Restoring with known extensionId:",r),await this.sendExtMessageWithTimeout(_.SET_EXTENSION_ID,{extensionId:r},t),this.extensionId=r;else{this.logger.info("Discovering bodhi-browser extension...");const s={attempts:this.config.initParams?.extension?.attempts,attemptWaitMs:this.config.initParams?.extension?.attemptWaitMs,attemptTimeout:this.config.initParams?.extension?.attemptTimeout},a=await this.sendExtMessageWithTimeout(_.DISCOVER_EXTENSION,s,t);this.extensionId=a.extensionId,this.logger.info("Extension discovered:",this.extensionId)}this.setupBroadcastListener()}const i={type:"extension",extension:"ready",extensionId:this.extensionId,server:n.PENDING_EXTENSION_READY};let o=n.PENDING_EXTENSION_READY;if(e.testConnection)try{o=await this.getServerState(),this.logger.info("Server connectivity tested, state:",o.status)}catch(s){this.logger.error("Failed to get server state:",s),o=n.BACKEND_SERVER_NOT_REACHABLE}return this.setState({...i,server:o}),this.state}catch(i){this.logger.error("Failed to initialize extension:",i),this.extensionId=null;const o=n.createExtensionStateNotFound();return this.setState(o),this.state}}async sendExtMessageWithTimeout(e,t,r=1e4){const i=new Promise((o,s)=>setTimeout(()=>s(new Error("Timeout")),r));return Promise.race([this.sendExtRequest(e,t),i])}async sendExtRequest(e,t){try{const r=this.generateRequestId(),i=await chrome.runtime.sendMessage({type:h.EXT2EXT_CLIENT_REQUEST,requestId:r,request:{action:e,params:t}});if(!i)throw n.createOperationError("extension_error","No response from background script");if(i.type!==h.EXT2EXT_CLIENT_RESPONSE)throw n.createOperationError("extension_error","Invalid response type from background script");const o=i.response;if(g.isExtError(o)){const s=o.error.type||"extension_error";throw n.createOperationError(s,o.error.message)}return o}catch(r){throw r instanceof n.BodhiError?r:n.createOperationError("extension_error",r instanceof Error?r.message:"Unknown error occurred")}}async sendRawApiMessage(e,t,r,i,o){const s=this.generateRequestId();return await chrome.runtime.sendMessage({type:h.EXT2EXT_CLIENT_API_REQUEST,requestId:s,request:{method:e,endpoint:t,body:r,headers:i,authenticated:o}})}async sendApiRequest(e,t,r,i,o){try{const s=new Promise((E,c)=>setTimeout(()=>c(new Error(`[bodhi-js-sdk/ext] network timeout: api request not completed within configured/default timeout of ${this.apiTimeoutMs}ms`)),this.apiTimeoutMs)),a=await Promise.race([this.sendRawApiMessage(e,t,r,i,o),s]);if(P(a)){const E=a.error.type||"extension_error";throw new n.BodhiError(E,a.error.message)}return a.response}catch(s){if(s instanceof n.BodhiError)throw s;const a=s instanceof Error?s.message:String(s);throw new n.BodhiError("network_error",a)}}async login(e){return new Promise((t,r)=>{const i=async o=>{if(o&&typeof o=="object"&&"type"in o&&o.type==="EXT2EXT_CLIENT_BROADCAST"&&"event"in o&&o.event==="authStateChanged"){chrome.runtime.onMessage.removeListener(i);try{const s=await this.getAuthState();if(n.isAuthError(s)){r(n.createOperationError("auth_error",`Login failed: ${s.error?.message}`));return}if(s.status!=="authenticated"){r(n.createOperationError("auth_error","Login failed: User is not logged in"));return}this.setAuthState(s),t(s)}catch(s){r(s)}}};chrome.runtime.onMessage.addListener(i),this.sendExtRequest(_.LOGIN,e).catch(o=>{chrome.runtime.onMessage.removeListener(i),r(o)})})}async logout(){await this.sendExtRequest(_.LOGOUT);const e={status:"unauthenticated",user:null,accessToken:null,error:null};return this.setAuthState(e),e}async getAuthState(){return this.isClientInitialized()?(await this.sendExtRequest(_.GET_AUTH_STATE)).authState:n.INITIAL_AUTH_STATE}async pingApi(){return this.sendApiRequest("GET","/ping")}async getServerState(){let e;try{e=await this.sendApiRequest("GET","/bodhi/v1/info")}catch(o){const s=o instanceof Error?o.message:"Connection failed",a=o instanceof n.BodhiError?o.code:"extension_error";return{status:"not-reachable",version:null,error:{message:s,type:a}}}if(e.status>=400)return{status:"not-reachable",version:null,error:{message:"API error from server",type:"extension_error"}};const t=e.body,r=t.version||"unknown",i={deployment:t.deployment??null,client_id:t.client_id??null};switch(t.status){case"ready":return{status:"ready",version:r,error:null,...i};case"setup":return{status:"setup",version:r,error:t.error?{message:t.error.message,type:t.error.type}:{message:"Setup required",type:"extension_error"},...i};case"resource_admin":return{status:"resource_admin",version:r,error:t.error?{message:t.error.message,type:t.error.type}:{message:"Resource admin required",type:"extension_error"},...i};case"error":return{status:"error",version:r,error:t.error?{message:t.error.message,type:t.error.type}:{message:"Server error",type:"extension_error"},...i};default:return{status:"not-reachable",version:null,error:{message:"Unknown server status",type:"extension_error"}}}}async*stream(e,t,r,i,o=!0){const s=this.generateRequestId();this.logger.debug("Starting stream",{method:e,endpoint:t,requestId:s});const a=chrome.runtime.connect({name:f}),c=new ReadableStream({start:u=>{a.onMessage.addListener(d=>{if(d.requestId===s)switch(d.type){case h.EXT2EXT_CLIENT_STREAM_DONE:this.logger.debug("Stream complete",{requestId:s}),u.close(),a.disconnect();break;case h.EXT2EXT_CLIENT_STREAM_ERROR:this.logger.error("Stream error",{requestId:s,error:JSON.stringify(d.error)}),u.error(new n.BodhiError("extension_error",d.error.message)),a.disconnect();break;case h.EXT2EXT_CLIENT_STREAM_API_ERROR:{const T=d;this.logger.error("Stream API error",{requestId:s,error:T.response.body?.error}),u.error(new n.BodhiApiError(T.response.status,T.response.body,T.response.body?.error?.message||"API error")),a.disconnect();break}case h.EXT2EXT_CLIENT_STREAM_CHUNK:{const T=d;g.isApiSuccessResponse(T.response)&&u.enqueue(T.response.body);break}}}),a.onDisconnect.addListener(()=>{this.logger.debug("Port disconnected",{requestId:s});try{u.error(new n.BodhiError("connection_closed","Connection closed unexpectedly"))}catch{}}),a.postMessage({type:h.EXT2EXT_CLIENT_STREAM_REQUEST,requestId:s,request:{method:e,endpoint:t,body:r,headers:i,authenticated:o}})}}).getReader();try{for(;;){const{done:u,value:d}=await c.read();if(u){this.logger.debug("Stream iteration complete");break}yield d}}finally{c.releaseLock()}}get chat(){return this._chat??=new n.Chat(this)}get models(){return this._models??=new n.Models(this)}get embeddings(){return this._embeddings??=new n.Embeddings(this)}get toolsets(){return this._toolsets??=new n.Toolsets(this)}get mcps(){return this._mcps??=new n.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 n.pollAccessRequestUntilResolved(r=>this.getAccessRequestStatus(r),e,t)}serialize(){return{extensionId:this.extensionId??void 0}}async debug(){return{type:"ExtClient",state:this.state,authState:await this.getAuthState()}}}class L extends n.BaseFacadeClient{constructor(e,t,r){const i=t||{},o={basePath:i.basePath||"/",authServerUrl:i.authServerUrl||"https://id.getbodhi.app/realms/bodhi",userRole:i.userRole||"scope_user_user",logLevel:i.logLevel||"warn",apiTimeoutMs:i.apiTimeoutMs,initParams:i.initParams};super(e,o,r)}createLogger(e){return new n.Logger("ExtUIClient",e.logLevel)}createStoragePrefix(e){return n.createStoragePrefixWithBasePath(e.basePath,n.STORAGE_PREFIXES.EXT)}createExtClient(e,t){return new X({authClientId:this.authClientId,logLevel:e.logLevel,apiTimeoutMs:e.apiTimeoutMs,initParams:e.initParams},t)}createDirectClient(e,t,r){return new v({authClientId:e,authServerUrl:t.authServerUrl,userRole:t.userRole,logLevel:t.logLevel,basePath:t.basePath,apiTimeoutMs:t.apiTimeoutMs},r)}}const b=["ggedphdcbekjlomjaidbajglgihbeaon"],M=["bjdjhiombmfbcoeojijpfckljjghmjbf"],y="production",p=class p{constructor(e,t){this.isAuthenticating=!1,this.state="setup",this.listenersInitialized=!1,this.refreshPromise=null,this.activeStreamPorts=new Map,this.authClientId=e,this.authServerUrl=t?.authServerUrl||"https://id.getbodhi.app/realms/bodhi",this.userRole=t?.userRole||"scope_user_user",this.extensionId=t?.extensionId,this.logger=new n.Logger("BodhiExtClient",t?.logLevel||"warn"),this.attempts=t?.attempts??C,this.attemptWaitMs=t?.attemptWaitMs??w,this.attemptTimeout=t?.attemptTimeout??A,this.authEndpoints={authorize:`${this.authServerUrl}/protocol/openid-connect/auth`,token:`${this.authServerUrl}/protocol/openid-connect/token`,userinfo:`${this.authServerUrl}/protocol/openid-connect/userinfo`,logout:`${this.authServerUrl}/protocol/openid-connect/logout`,revoke:`${this.authServerUrl}/protocol/openid-connect/revoke`},this.extensionId?this.logger.info(`[BodhiExtClient] Created client for extension: ${this.extensionId}`):this.logger.info("[BodhiExtClient] Created client without extension ID (call init() to discover)")}static base64UrlEncode(e){return btoa(String.fromCharCode(...new Uint8Array(e))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}static generateCodeVerifier(){const e=new Uint8Array(32);return crypto.getRandomValues(e),p.base64UrlEncode(e.buffer)}static async generateCodeChallenge(e){const r=new TextEncoder().encode(e),i=await crypto.subtle.digest("SHA-256",r);return p.base64UrlEncode(i)}getState(){return this.state}getExtensionIdsForEnvironment(){const t=y!=="production"?b:M;return this.logger.info("[Ext2Ext/Registry] Environment: production"),this.logger.debug("[Ext2Ext/Registry] Using extension IDs:",t),t}async pingExtension(e){return this.logger.debug(`[Ext2Ext/Discovery] Pinging extension: ${e} with timeout ${this.attemptTimeout}ms`),new Promise((t,r)=>{const i=setTimeout(()=>{this.logger.debug(`[Ext2Ext/Discovery] Timeout waiting for extension ${e}`),r(new Error("Timeout"))},this.attemptTimeout);try{const o={type:g.MESSAGE_TYPES.EXT_REQUEST,requestId:crypto.randomUUID(),request:{action:"get_extension_id"}};this.logger.debug(`[Ext2Ext/Discovery] Sending message to ${e}:`,o),chrome.runtime.sendMessage(e,o,s=>{if(clearTimeout(i),chrome.runtime.lastError){this.logger.error(`[Ext2Ext/Discovery] Error from extension ${e}:`,chrome.runtime.lastError.message),r(new Error(chrome.runtime.lastError.message));return}this.logger.debug(`[Ext2Ext/Discovery] Response from ${e}:`,s);const a=s;a&&a.type===g.MESSAGE_TYPES.EXT_RESPONSE?(this.logger.debug(`[Ext2Ext/Discovery] ✓ Extension ${e} responded`),t(!0)):(this.logger.error(`[Ext2Ext/Discovery] Invalid response from ${e}:`,s),r(new Error("Invalid response")))})}catch(o){this.logger.error(`[Ext2Ext/Discovery] Exception pinging ${e}:`,o),clearTimeout(i),r(o)}})}sleep(e){return new Promise(t=>setTimeout(t,e))}async discoverBodhiExtension(e){const{attempts:t,attemptWaitMs:r,attemptTimeout:i}=e;this.logger.info(`[Ext2Ext/Discovery] Starting discovery: ${t} attempts per ID, ${i}ms timeout, ${r}ms between attempts`);const o=this.getExtensionIdsForEnvironment();this.logger.debug(`[Ext2Ext/Discovery] Will try ${o.length} extension(s):`,o);for(const E of o){for(let c=1;c<=t;c++){this.logger.debug(`[Ext2Ext/Discovery] Trying ${E} - attempt ${c}/${t}`);try{return await this.pingExtension(E),this.logger.info(`[Ext2Ext/Discovery] ✓ Found: ${E} on attempt ${c}`),{success:!0,extensionId:E}}catch(u){this.logger.debug(`[Ext2Ext/Discovery] Attempt ${c} failed for ${E}: ${u instanceof Error?u.message:"Unknown error"}`),c<t&&await this.sleep(r)}}this.logger.warn(`[Ext2Ext/Discovery] ✗ Not found: ${E} after ${t} attempts`)}const s=o.join(", "),a=`Extension not found. Tried ${o.length} IDs with ${t} attempts each: ${s}`;return this.logger.error(`[Ext2Ext/Discovery] ${a}`),{success:!1,error:a}}setupListeners(){if(this.listenersInitialized){this.logger.debug("[BodhiExtClient] Listeners already initialized, skipping");return}this.listenersInitialized=!0,chrome.runtime.onMessage.addListener((e,t,r)=>e.type!==h.EXT2EXT_CLIENT_REQUEST&&e.type!==h.EXT2EXT_CLIENT_API_REQUEST?!1:this.state!=="ready"&&!(e.type===h.EXT2EXT_CLIENT_REQUEST&&e.request.action===_.DISCOVER_EXTENSION)?(e.type===h.EXT2EXT_CLIENT_REQUEST?r({type:h.EXT2EXT_CLIENT_RESPONSE,requestId:e.requestId,response:{error:{message:this.createErrorClientNotInitialized(e),type:"NOT_INITIALIZED"}}}):r({type:h.EXT2EXT_CLIENT_API_RESPONSE,requestId:e.requestId,error:{message:`Client not initialized. Extension discovery not complete, cannot handle type:${e.type}, message:${JSON.stringify(e)}`,type:"NOT_INITIALIZED"}}),!0):(this.logger.debug(`[BodhiExtClient] Processing message.type=${e.type}`),(async()=>{const i=await this.handleAction(e);r(i)})(),!0)),chrome.runtime.onConnect.addListener(e=>{if(e.name!==f){this.logger.debug("[BodhiExtClient] Ignoring port with name:",e.name);return}this.logger.info("[BodhiExtClient] Streaming port connected"),e.onMessage.addListener(async t=>{if(t.type!==h.EXT2EXT_CLIENT_STREAM_REQUEST){this.logger.warn("[BodhiExtClient] Unknown stream message type:",t.type),e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_ERROR,requestId:t.requestId,error:{message:"Unknown stream message type",type:"extension_error"}});return}await this.handleStreamRequest(e,t)}),e.onDisconnect.addListener(()=>{this.logger.info("[BodhiExtClient] Streaming port disconnected")})}),this.logger.info("[BodhiExtClient] Streaming listeners initialized")}async init(e){if(this.setupListeners(),this.extensionId){this.state="ready",this.logger.warn(`[BodhiExtClient] Already initialized with extension ID: ${this.extensionId}`);return}this.logger.info("[BodhiExtClient] Starting discovery");const t={attempts:e?.attempts??this.attempts,attemptWaitMs:e?.attemptWaitMs??this.attemptWaitMs,attemptTimeout:e?.attemptTimeout??this.attemptTimeout},r=await this.discoverBodhiExtension(t);if(!r.success||!r.extensionId)throw new Error(r.error||"Discovery failed");this.extensionId=r.extensionId,this.state="ready",this.logger.info(`[BodhiExtClient] ✓ Initialized: ${this.extensionId}`)}broadcastAuthStateChange(){chrome.runtime.sendMessage({type:h.EXT2EXT_CLIENT_BROADCAST,event:"authStateChanged"}).catch(e=>{this.logger.debug("[BodhiExtClient] No listeners for broadcast:",e.message)})}async getExtensionIdFromExt(){this.logger.debug("[BodhiExtClient] Getting extension ID from bodhi-browser-ext");const e=await this.sendExtRequest("get_extension_id");return this.logger.debug("[BodhiExtClient] Extension ID response:",e),e.extension_id}async handleApiRequest(e){const{requestId:t}=e;this.logger.debug("[BodhiExtClient] Handling API request:",e.request);try{let r=e.request.headers||{};if(e.request.authenticated){const o=await this._getAccessTokenRaw();if(!o)return{type:h.EXT2EXT_CLIENT_API_RESPONSE,requestId:t,error:{message:"Not authenticated. Please log in first.",type:"auth_error"}};r={...r,Authorization:`Bearer ${o}`},this.logger.debug("[BodhiExtClient] Injected auth token for authenticated request")}const i=await this.sendApiRequest(e.request.method,e.request.endpoint,e.request.body,r);return{type:h.EXT2EXT_CLIENT_API_RESPONSE,requestId:t,response:i}}catch(r){return this.logger.error("[BodhiExtClient] API request failed:",r),{type:h.EXT2EXT_CLIENT_API_RESPONSE,requestId:t,error:{message:r instanceof Error?r.message:"Unknown error",type:"network_error"}}}}async handleExtClientRequest(e){const{requestId:t,request:r}=e,{action:i,params:o}=r;this.logger.debug(`[BodhiExtClient] Handling action: ${i}`);try{let s={};switch(i){case _.DISCOVER_EXTENSION:{const a=o;await this.init(a),this.logger.info("[BodhiExtClient] Discovery successful:",{extensionId:this.extensionId,environment:y}),s={extensionId:this.extensionId,environment:y};break}case _.SET_EXTENSION_ID:{const{extensionId:a}=o;this.extensionId=a,this.state="ready",this.logger.info("[BodhiExtClient] Extension ID set:",{extensionId:a}),s={success:!0};break}case _.GET_EXTENSION_ID:{const a=await this.sendExtRequestRaw(g.EXT_ACTIONS.GET_EXTENSION_ID,o);return g.isExtError(a.response)?{type:h.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:{error:{message:a.response.error.message||`Extension request failed to get extension ID: ${JSON.stringify(a.response)}`,type:a.response.error.type}}}:{type:h.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:a.response}}case _.LOGIN:{const a=o;await this.login(a),this.broadcastAuthStateChange();break}case _.LOGOUT:await this.logout(),this.broadcastAuthStateChange();break;case _.GET_AUTH_STATE:s={authState:await this.getAuthState()};break;default:return{type:h.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:{error:{message:`Unknown action: ${i}`,type:"UNKNOWN_ACTION"}}}}return{type:h.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:s}}catch(s){return this.logger.error("[BodhiExtClient] Unexpected error:",s),{type:h.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:{error:{message:s instanceof Error?s.message:`Unexpected error: ${JSON.stringify(s)}`}}}}}async handleAction(e){switch(e.type){case h.EXT2EXT_CLIENT_API_REQUEST:return this.handleApiRequest(e);case h.EXT2EXT_CLIENT_REQUEST:return this.handleExtClientRequest(e);default:{const{requestId:t}=e;return this.logger.error("[BodhiExtClient] Unknown message type:",e.type),{type:h.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:{error:{message:`Unknown message type: ${e.type}`,type:"UNKNOWN_MESSAGE_TYPE"}}}}}}async login(e){if(!(this.isAuthenticating||(await this.getAuthState()).status==="authenticated")){this.isAuthenticating=!0;try{if(!this.extensionId)throw new Error("Extension not discovered. Please detect Bodhi extension before login.");e?.flowType==="redirect"&&this.logger.warn("Extension mode does not support redirect flow type; using popup instead");const r=e?.userRole??this.userRole,i=new n.AccessRequestBuilder(this.authClientId).requestedRole(r).flowType("popup");e?.requested&&i.requested(e.requested);const o=i.build(),s=await this.requestAccess(o),{id:a,review_url:E}=n.unwrapResponse(s);await chrome.tabs.create({url:E});const c=await this.pollAccessRequestStatus(a,{intervalMs:e?.pollIntervalMs??n.DEFAULT_POLL_INTERVAL_MS,timeoutMs:e?.pollTimeoutMs??n.DEFAULT_POLL_TIMEOUT_MS});if(c.status!=="approved")throw n.createOperationError("auth_error",`Access request ${c.status}`);const d=`openid profile email roles ${c.access_request_scope??""}`.trim();await this.performOAuthPkce(d)}finally{this.isAuthenticating=!1}}}async exchangeCodeForTokens(e){if((await this.getAuthState()).status==="authenticated")return;const{codeVerifier:r}=await chrome.storage.session.get("codeVerifier"),i=chrome.identity.getRedirectURL("callback"),o=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:i,client_id:this.authClientId,code_verifier:r})});if(!o.ok){const a=await o.text();throw new Error(`Token exchange failed: ${o.status} ${a}`)}const s=await o.json();await this.storeTokens({accessToken:s.access_token,refreshToken:s.refresh_token,idToken:s.id_token,expiresIn:s.expires_in})}async getAuthState(){const e=await this._getAccessTokenRaw();if(!e)return{status:"unauthenticated",user:null,accessToken:null,error:null};try{const t=this.parseJwt(e);return{status:"authenticated",user:{sub:t.sub,email:t.email,name:t.name,given_name:t.given_name,family_name:t.family_name,preferred_username:t.preferred_username},accessToken:e,error:null}}catch(t){return this.logger.error("Failed to parse token:",t),{status:"unauthenticated",user:null,accessToken:null,error:null}}}async logout(){const{refreshToken:e}=await chrome.storage.session.get("refreshToken");if(e)try{await fetch(this.authEndpoints.revoke,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({token:e,client_id:this.authClientId,token_type_hint:"refresh_token"})})}catch(t){this.logger.warn("[OAuth] Token revocation failed:",t)}await this.clearTokens()}async requestAccess(e){return this.sendApiRequest("POST","/bodhi/v1/apps/request-access",e)}async getAccessRequestStatus(e){return this.sendApiRequest("GET",`/bodhi/v1/apps/access-requests/${e}?app_client_id=${encodeURIComponent(this.authClientId)}`)}async pollAccessRequestStatus(e,t){return n.pollAccessRequestUntilResolved(r=>this.getAccessRequestStatus(r),e,t)}async performOAuthPkce(e){const t=p.generateCodeVerifier(),r=await p.generateCodeChallenge(t),i=p.generateCodeVerifier();await chrome.storage.session.set({codeVerifier:t,state:i,authInProgress:!0});const o=chrome.identity.getRedirectURL("callback"),s=new URL(this.authEndpoints.authorize);return s.searchParams.set("client_id",this.authClientId),s.searchParams.set("response_type","code"),s.searchParams.set("redirect_uri",o),s.searchParams.set("scope",e),s.searchParams.set("code_challenge",r),s.searchParams.set("code_challenge_method","S256"),s.searchParams.set("state",i),new Promise((a,E)=>{chrome.identity.launchWebAuthFlow({url:s.toString(),interactive:!0},async c=>{if(await chrome.storage.session.set({authInProgress:!1}),chrome.runtime.lastError){await chrome.storage.session.remove(["codeVerifier","state"]),E(chrome.runtime.lastError);return}if(!c){await chrome.storage.session.remove(["codeVerifier","state"]),E(n.createOperationError("oauth_error","No redirect URL received"));return}try{const u=new URL(c),d=u.searchParams.get("code"),T=u.searchParams.get("state"),{state:m}=await chrome.storage.session.get("state");if(T!==m){await chrome.storage.session.remove(["codeVerifier","state"]),E(n.createOperationError("oauth_error","State mismatch"));return}if(!d){await chrome.storage.session.remove(["codeVerifier","state"]),E(n.createOperationError("oauth_error","No authorization code"));return}await this.exchangeCodeForTokens(d),await chrome.storage.session.remove(["codeVerifier","state"]);const l=await this.getAuthState();if(l.status!=="authenticated")throw n.createOperationError("oauth_error","Login failed");a(l)}catch(u){await chrome.storage.session.remove(["codeVerifier","state"]),E(u)}})})}async sendExtRequest(e,t){const r=await this.sendExtRequestRaw(e,t);if(g.isExtError(r.response))throw this.logger.error("[BodhiExtClient] Extension error:",r.response.error),new Error(r.response.error.message||`Extension request failed: ${JSON.stringify(r.response)}`);return r.response}async sendApiRequest(e,t,r,i){if(!this.extensionId)throw new n.BodhiError("not_initialized",this.createErrorClientNotInitialized({type:"api",method:e,endpoint:t}));this.logger.debug(`[BodhiExtClient] Sending API_REQUEST: method=${e}, endpoint=${t}`,r?{body:r}:"");const o=crypto.randomUUID(),s={type:g.MESSAGE_TYPES.API_REQUEST,requestId:o,request:{method:e,endpoint:t,body:r,headers:i}};return this.logger.debug(`[BodhiExtClient] Request ID: ${o}, Extension: ${this.extensionId}`),new Promise((a,E)=>{try{chrome.runtime.sendMessage(this.extensionId,s,c=>{if(chrome.runtime.lastError){this.logger.error(`[BodhiExtClient] Chrome runtime error for request ${o}:`,chrome.runtime.lastError),E(new n.BodhiError("extension_error",chrome.runtime.lastError.message??"Chrome runtime error"));return}if(this.logger.debug(`[BodhiExtClient] Response for request ${o}:`,c),!c){this.logger.error(`[BodhiExtClient] No response received for request ${o}`),E(new n.BodhiError("extension_error","No response from extension"));return}c.type===g.MESSAGE_TYPES.API_RESPONSE&&c.requestId===o?"error"in c?(this.logger.error(`[BodhiExtClient] API error for ${o}:`,c.error),E(new n.BodhiError(c.error.type||"extension_error",c.error.message))):(this.logger.debug(`[BodhiExtClient] ✓ Valid API_RESPONSE for ${o}`),a(c.response)):(this.logger.error(`[BodhiExtClient] Invalid response format for ${o}:`,c),E(new n.BodhiError("extension_error","Invalid response format")))})}catch(c){this.logger.error(`[BodhiExtClient] Exception sending message for ${o}:`,c),E(c)}})}async sendExtRequestRaw(e,t){if(!this.extensionId)throw new n.BodhiError("not_initialized",this.createErrorClientNotInitialized({type:"ext",action:e,params:t}));this.logger.debug(`[BodhiExtClient] Sending EXT_REQUEST (raw): action=${e}`,t?{params:t}:"");const r=crypto.randomUUID(),i={type:g.MESSAGE_TYPES.EXT_REQUEST,requestId:r,request:{action:e,params:t}};return this.logger.debug(`[BodhiExtClient] Request ID: ${r}, Extension: ${this.extensionId}`),new Promise((o,s)=>{try{chrome.runtime.sendMessage(this.extensionId,i,a=>{if(chrome.runtime.lastError){this.logger.error(`[BodhiExtClient] Chrome runtime error for request ${r}:`,chrome.runtime.lastError),s(new Error(chrome.runtime.lastError.message));return}if(this.logger.debug(`[BodhiExtClient] Response for request ${r}:`,a),!a){this.logger.error(`[BodhiExtClient] No response received for request ${r}`),s(new Error("No response from extension"));return}a.type===g.MESSAGE_TYPES.EXT_RESPONSE&&a.requestId===r?(this.logger.debug(`[BodhiExtClient] ✓ Valid EXT_RESPONSE for ${r}`),o(a)):(this.logger.error(`[BodhiExtClient] Invalid response format for ${r}:`,a),s(new n.BodhiError("extension_error","Invalid response format")))})}catch(a){this.logger.error(`[BodhiExtClient] Exception sending message for ${r}:`,a),s(a)}})}async handleStreamRequest(e,t){const{requestId:r,request:i}=t,{method:o,endpoint:s,body:a,headers:E,authenticated:c}=i;this.logger.debug("[BodhiExtClient] Processing stream request:",{requestId:r,method:o,endpoint:s,authenticated:c}),this.extensionId||e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_ERROR,requestId:r,error:{message:this.createErrorClientNotInitialized(t),type:"extension_error"}});try{let u={...E};if(c!==!1){const l=await this._getAccessTokenRaw();if(!l){e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_ERROR,requestId:r,error:{message:"Not authenticated. Please log in first.",type:"extension_error"}});return}u={...u,Authorization:`Bearer ${l}`},this.logger.debug("[BodhiExtClient] Injected auth token for authenticated request")}const d=chrome.runtime.connect(this.extensionId,{name:g.BODHI_STREAM_PORT});this.activeStreamPorts.set(r,d);const T=setTimeout(()=>{this.activeStreamPorts.has(r)&&(this.logger.error(`[BodhiExtClient] Stream timeout for ${r}`),e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_ERROR,requestId:r,error:{message:"Stream request timed out",type:"timeout_error"}}),this.cleanupStreamPort(r))},p.STREAM_TIMEOUT);d.onMessage.addListener(l=>{if(g.isStreamChunk(l)){const I=l.response,O=I.body;I.status>=400?e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_API_ERROR,requestId:r,response:I}):O?.done?(e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_DONE,requestId:r}),this.logger.info(`[BodhiExtClient] Stream complete for ${r}`),clearTimeout(T),this.cleanupStreamPort(r)):e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_CHUNK,requestId:r,response:I})}else g.isStreamApiError(l)?(this.logger.error(`[BodhiExtClient] Stream API error for ${r}: ${l.response.status}`),e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_API_ERROR,requestId:r,response:l.response})):g.isStreamError(l)&&(this.logger.error(`[BodhiExtClient] Stream error for ${r}:`,l.error.message),e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_ERROR,requestId:r,error:{message:`stream error: ${JSON.stringify(l)}`,type:"extension_error"}}),clearTimeout(T),this.cleanupStreamPort(r))}),d.onDisconnect.addListener(()=>{clearTimeout(T),this.activeStreamPorts.has(r)&&(this.logger.error(`[BodhiExtClient] Bodhi port disconnected for ${r}`),e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_ERROR,requestId:r,error:{message:"Connection to Bodhi extension closed unexpectedly",type:"network_error"}}),this.activeStreamPorts.delete(r))});const m={type:g.MESSAGE_TYPES.STREAM_REQUEST,requestId:r,request:{method:o,endpoint:s,body:a,headers:u}};this.logger.debug("[BodhiExtClient] Sending stream request to bodhi port:",m),d.postMessage(m)}catch(u){const d=u;this.logger.error("[BodhiExtClient] Stream error:",JSON.stringify(d.message)),e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_ERROR,requestId:r,error:{message:`uncaught error: ${JSON.stringify({error:d,message:d.message})}`,type:"extension_error"}})}}cleanupStreamPort(e){const t=this.activeStreamPorts.get(e);if(t){try{t.disconnect()}catch{}this.activeStreamPorts.delete(e)}}async storeTokens(e){const t=Date.now()+(e.expiresIn||3600)*1e3;await chrome.storage.session.set({accessToken:e.accessToken,refreshToken:e.refreshToken,idToken:e.idToken,expiresAt:t})}async _getAccessTokenRaw(){const{accessToken:e,expiresAt:t}=await chrome.storage.session.get(["accessToken","expiresAt"]);if(!e||!t)return null;if(Date.now()>=t-5*1e3){const{refreshToken:r}=await chrome.storage.session.get("refreshToken");return r?this._tryRefreshToken(r):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 n.refreshAccessToken(this.authEndpoints.token,e,this.authClientId);if(t.success)return await this._storeRefreshedTokens(t.tokens),this.logger.info("Token refreshed successfully"),this.broadcastAuthStateChange(),t.tokens.access_token;if(t.error==="invalid_grant")return this.logger.warn("Refresh token expired or revoked, clearing tokens and logging out"),await this.clearTokens(),this.broadcastAuthStateChange(),null}catch(t){this.logger.warn("Token refresh failed:",t)}throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"),n.createOperationError("auth_error","Access token expired and unable to refresh. Try logging out and logging in again.")}async _storeRefreshedTokens(e){const t=Date.now()+e.expires_in*1e3,r={accessToken:e.access_token,expiresAt:t};e.refresh_token&&(r.refreshToken=e.refresh_token),e.id_token&&(r.idToken=e.id_token),await chrome.storage.session.set(r)}async clearTokens(){await chrome.storage.session.remove(["accessToken","refreshToken","idToken","expiresAt","codeVerifier","state","authInProgress","bodhiUserInfo"])}parseJwt(e){const r=e.split(".")[1].replace(/-/g,"+").replace(/_/g,"/"),i=decodeURIComponent(atob(r).split("").map(o=>"%"+("00"+o.charCodeAt(0).toString(16)).slice(-2)).join(""));return JSON.parse(i)}createErrorClientNotInitialized(e){return`Client not initialized. Extension discovery not triggered nor extensionId set, cannot handle request: ${JSON.stringify(e)}`}};p.STREAM_TIMEOUT=6e4;let R=p;const k="production";exports.BodhiExtClient=R;exports.DEFAULT_API_TIMEOUT_MS=N;exports.DISCOVERY_ATTEMPTS=C;exports.DISCOVERY_ATTEMPT_TIMEOUT=A;exports.DISCOVERY_ATTEMPT_WAIT_MS=w;exports.DISCOVERY_TIMEOUT_MS=x;exports.EXT2EXT_CLIENT_ACTIONS=_;exports.EXT2EXT_CLIENT_MESSAGE_TYPES=h;exports.EXT2EXT_CLIENT_STREAM_PORT=f;exports.EXT_BUILD_MODE=k;exports.ExtUIClient=L;exports.isExtClientApiError=P;
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const n=require("@bodhiapp/bodhi-js-core"),g=require("@bodhiapp/bodhi-browser-types"),h={EXT2EXT_CLIENT_REQUEST:"EXT2EXT_CLIENT_REQUEST",EXT2EXT_CLIENT_RESPONSE:"EXT2EXT_CLIENT_RESPONSE",EXT2EXT_CLIENT_BROADCAST:"EXT2EXT_CLIENT_BROADCAST",EXT2EXT_CLIENT_API_REQUEST:"EXT2EXT_CLIENT_API_REQUEST",EXT2EXT_CLIENT_API_RESPONSE:"EXT2EXT_CLIENT_API_RESPONSE",EXT2EXT_CLIENT_STREAM_REQUEST:"EXT2EXT_CLIENT_STREAM_REQUEST",EXT2EXT_CLIENT_STREAM_CHUNK:"EXT2EXT_CLIENT_STREAM_CHUNK",EXT2EXT_CLIENT_STREAM_ERROR:"EXT2EXT_CLIENT_STREAM_ERROR",EXT2EXT_CLIENT_STREAM_API_ERROR:"EXT2EXT_CLIENT_STREAM_API_ERROR",EXT2EXT_CLIENT_STREAM_DONE:"EXT2EXT_CLIENT_STREAM_DONE"},f="ext2ext-client-stream",_={LOGIN:"login",LOGOUT:"logout",GET_AUTH_STATE:"getAuthState",DISCOVER_EXTENSION:"discoverBodhiExtension",GET_EXTENSION_ID:"get_extension_id",SET_EXTENSION_ID:"setExtensionId"},x=5e3,C=3,w=500,A=500,N=3e4;function P(S){return"error"in S}class v extends n.DirectClientBase{constructor(e,t){const r=n.createStoragePrefixWithBasePath(e.basePath,n.STORAGE_PREFIXES.EXT_DIRECT),i={authClientId:e.authClientId,authServerUrl:e.authServerUrl,storagePrefix:r,logLevel:e.logLevel,loggerPrefix:"DirectExtClient",apiTimeoutMs:e.apiTimeoutMs};super(i,t)}async login(e){const t=await this.getAuthState();if(t.status==="authenticated")return t;e?.flowType==="redirect"&&this.logger.warn("Extension mode does not support redirect flow type; using popup instead");const r=e?.userRole??"scope_user_user";e?.onProgress?.("requesting");const i=new n.AccessRequestBuilder(this.authClientId).requestedRole(r).flowType("popup");e?.requested&&i.requested(e.requested);const o=i.build(),s=await this.requestAccess(o),{id:a,review_url:E}=n.unwrapResponse(s);e?.onProgress?.("reviewing"),await chrome.tabs.create({url:E});const c=await this.pollAccessRequestStatus(a,{intervalMs:e?.pollIntervalMs??n.DEFAULT_POLL_INTERVAL_MS,timeoutMs:e?.pollTimeoutMs??n.DEFAULT_POLL_TIMEOUT_MS});if(c.status!=="approved")throw n.createOperationError("auth_error",`Access request ${c.status}`);const u=c.access_request_scope;return e?.onProgress?.("authenticating"),this.performOAuthPkce(`openid profile email roles ${u??""}`.trim())}async performOAuthPkce(e){const t=n.generateCodeVerifier(),r=await n.generateCodeChallenge(t),i=n.generateCodeVerifier();await chrome.storage.session.set({[this.storageKeys.CODE_VERIFIER]:t,[this.storageKeys.STATE]:i});const o=chrome.identity.getRedirectURL("callback"),s=new URL(this.authEndpoints.authorize);return s.searchParams.set("client_id",this.authClientId),s.searchParams.set("response_type","code"),s.searchParams.set("redirect_uri",o),s.searchParams.set("scope",e),s.searchParams.set("code_challenge",r),s.searchParams.set("code_challenge_method","S256"),s.searchParams.set("state",i),new Promise((a,E)=>{chrome.identity.launchWebAuthFlow({url:s.toString(),interactive:!0},async c=>{if(chrome.runtime.lastError){await chrome.storage.session.remove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),E(chrome.runtime.lastError);return}if(!c){await chrome.storage.session.remove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),E(n.createOperationError("oauth_error","No redirect URL received"));return}try{const u=new URL(c),d=u.searchParams.get("code"),T=u.searchParams.get("state"),m=await chrome.storage.session.get(this.storageKeys.STATE);if(T!==m[this.storageKeys.STATE]){await chrome.storage.session.remove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),E(n.createOperationError("oauth_error","State mismatch"));return}if(!d){await chrome.storage.session.remove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),E(n.createOperationError("oauth_error","No authorization code"));return}await this.exchangeCodeForTokens(d);const l=await this.getAuthState();if(l.status!=="authenticated")throw n.createOperationError("oauth_error","Login failed");this.setAuthState(l),await chrome.storage.session.remove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),a(l)}catch(u){await chrome.storage.session.remove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),E(u)}})})}async logout(){const t=(await chrome.storage.session.get(this.storageKeys.REFRESH_TOKEN))[this.storageKeys.REFRESH_TOKEN];if(t)try{const i=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:i})}catch(i){this.logger.warn("Token revocation failed:",i)}await chrome.storage.session.remove([this.storageKeys.ACCESS_TOKEN,this.storageKeys.REFRESH_TOKEN,this.storageKeys.EXPIRES_AT]);const r={status:"unauthenticated",user:null,accessToken:null,error:null};return this.setAuthState(r),r}async exchangeCodeForTokens(e){const r=(await chrome.storage.session.get(this.storageKeys.CODE_VERIFIER))[this.storageKeys.CODE_VERIFIER],i=chrome.identity.getRedirectURL("callback"),o=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:i,client_id:this.authClientId,code_verifier:r})});if(!o.ok){const E=await o.text();throw new Error(`Token exchange failed: ${o.status} ${E}`)}const s=await o.json(),a=Date.now()+(s.expires_in||3600)*1e3;await chrome.storage.session.set({[this.storageKeys.ACCESS_TOKEN]:s.access_token,[this.storageKeys.REFRESH_TOKEN]:s.refresh_token,[this.storageKeys.EXPIRES_AT]:a}),await chrome.storage.session.remove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE])}async _storageGet(e){const r=(await chrome.storage.session.get(e))[e];return r!==void 0?String(r):null}async _storageSet(e){await chrome.storage.session.set(e)}async _storageRemove(e){await chrome.storage.session.remove(e)}_getRedirectUri(){return chrome.identity.getRedirectURL("callback")}}class X{constructor(e={},t){this.state={type:"extension",extension:"not-initialized",extensionId:null,server:n.PENDING_EXTENSION_READY},this.extensionId=null,this.broadcastListenerActive=!1,this.config=e,this.logger=new n.Logger("ExtClient",e?.logLevel||"warn"),this.onStateChange=t??n.NOOP_STATE_CALLBACK,this.apiTimeoutMs=e.apiTimeoutMs??N,this.authClientId=e.authClientId??""}setState(e){this.state=e,this.onStateChange({type:"client-state",state:e})}setAuthState(e){this.onStateChange({type:"auth-state",state:e})}setStateCallback(e){this.onStateChange=e}setupBroadcastListener(){this.broadcastListenerActive||(this.broadcastListenerActive=!0,chrome.runtime.onMessage.addListener(e=>{const t=e;return t?.type===h.EXT2EXT_CLIENT_BROADCAST&&t.event==="authStateChanged"&&this.handleAuthStateChangedBroadcast(),!1}),this.logger.debug("Broadcast listener setup complete"))}async handleAuthStateChangedBroadcast(){this.logger.debug("Received authStateChanged broadcast, refreshing auth state");const e=await this.getAuthState();this.setAuthState(e)}generateRequestId(){return crypto.randomUUID()}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){this.logger.info("No testConnection or selectedConnection, returning not-initialized state");const i=n.createExtensionStateNotInitialized();return this.setState(i),i}if(this.extensionId&&!e.testConnection)return this.logger.debug("Already initialized with extensionId, skipping discovery"),this.state;const t=e.timeoutMs??this.config.initParams?.extension?.timeoutMs??x,r=e.savedState?.extensionId;try{if(!this.extensionId){if(r)this.logger.info("Restoring with known extensionId:",r),await this.sendExtMessageWithTimeout(_.SET_EXTENSION_ID,{extensionId:r},t),this.extensionId=r;else{this.logger.info("Discovering bodhi-browser extension...");const s={attempts:this.config.initParams?.extension?.attempts,attemptWaitMs:this.config.initParams?.extension?.attemptWaitMs,attemptTimeout:this.config.initParams?.extension?.attemptTimeout},a=await this.sendExtMessageWithTimeout(_.DISCOVER_EXTENSION,s,t);this.extensionId=a.extensionId,this.logger.info("Extension discovered:",this.extensionId)}this.setupBroadcastListener()}const i={type:"extension",extension:"ready",extensionId:this.extensionId,server:n.PENDING_EXTENSION_READY};let o=n.PENDING_EXTENSION_READY;if(e.testConnection)try{o=await this.getServerState(),this.logger.info("Server connectivity tested, state:",o.status)}catch(s){this.logger.error("Failed to get server state:",s),o=n.BACKEND_SERVER_NOT_REACHABLE}return this.setState({...i,server:o}),this.state}catch(i){this.logger.error("Failed to initialize extension:",i),this.extensionId=null;const o=n.createExtensionStateNotFound();return this.setState(o),this.state}}async sendExtMessageWithTimeout(e,t,r=1e4){const i=new Promise((o,s)=>setTimeout(()=>s(new Error("Timeout")),r));return Promise.race([this.sendExtRequest(e,t),i])}async sendExtRequest(e,t){try{const r=this.generateRequestId(),i=await chrome.runtime.sendMessage({type:h.EXT2EXT_CLIENT_REQUEST,requestId:r,request:{action:e,params:t}});if(!i)throw n.createOperationError("extension_error","No response from background script");if(i.type!==h.EXT2EXT_CLIENT_RESPONSE)throw n.createOperationError("extension_error","Invalid response type from background script");const o=i.response;if(g.isExtError(o)){const s=o.error.type||"extension_error";throw n.createOperationError(s,o.error.message)}return o}catch(r){throw r instanceof n.BodhiError?r:n.createOperationError("extension_error",r instanceof Error?r.message:"Unknown error occurred")}}async sendRawApiMessage(e,t,r,i,o){const s=this.generateRequestId();return await chrome.runtime.sendMessage({type:h.EXT2EXT_CLIENT_API_REQUEST,requestId:s,request:{method:e,endpoint:t,body:r,headers:i,authenticated:o}})}async sendApiRequest(e,t,r,i,o){try{const s=new Promise((E,c)=>setTimeout(()=>c(new Error(`[bodhi-js-sdk/ext] network timeout: api request not completed within configured/default timeout of ${this.apiTimeoutMs}ms`)),this.apiTimeoutMs)),a=await Promise.race([this.sendRawApiMessage(e,t,r,i,o),s]);if(P(a)){const E=a.error.type||"extension_error";throw new n.BodhiError(E,a.error.message)}return a.response}catch(s){if(s instanceof n.BodhiError)throw s;const a=s instanceof Error?s.message:String(s);throw new n.BodhiError("network_error",a)}}async login(e){return new Promise((t,r)=>{const i=async o=>{if(o&&typeof o=="object"&&"type"in o&&o.type==="EXT2EXT_CLIENT_BROADCAST"&&"event"in o&&o.event==="authStateChanged"){chrome.runtime.onMessage.removeListener(i);try{const s=await this.getAuthState();if(n.isAuthError(s)){r(n.createOperationError("auth_error",`Login failed: ${s.error?.message}`));return}if(s.status!=="authenticated"){r(n.createOperationError("auth_error","Login failed: User is not logged in"));return}this.setAuthState(s),t(s)}catch(s){r(s)}}};chrome.runtime.onMessage.addListener(i),this.sendExtRequest(_.LOGIN,e).catch(o=>{chrome.runtime.onMessage.removeListener(i),r(o)})})}async logout(){await this.sendExtRequest(_.LOGOUT);const e={status:"unauthenticated",user:null,accessToken:null,error:null};return this.setAuthState(e),e}async getAuthState(){return this.isClientInitialized()?(await this.sendExtRequest(_.GET_AUTH_STATE)).authState:n.INITIAL_AUTH_STATE}async pingApi(){return this.sendApiRequest("GET","/ping")}async getServerState(){let e;try{e=await this.sendApiRequest("GET","/bodhi/v1/info")}catch(o){const s=o instanceof Error?o.message:"Connection failed",a=o instanceof n.BodhiError?o.code:"extension_error";return{status:"not-reachable",version:null,error:{message:s,type:a}}}if(e.status>=400)return{status:"not-reachable",version:null,error:{message:"API error from server",type:"extension_error"}};const t=e.body,r=t.version||"unknown",i={deployment:t.deployment??null,client_id:t.client_id??null};switch(t.status){case"ready":return{status:"ready",version:r,error:null,...i};case"setup":return{status:"setup",version:r,error:t.error?{message:t.error.message,type:t.error.type}:{message:"Setup required",type:"extension_error"},...i};case"resource_admin":return{status:"resource_admin",version:r,error:t.error?{message:t.error.message,type:t.error.type}:{message:"Resource admin required",type:"extension_error"},...i};case"error":return{status:"error",version:r,error:t.error?{message:t.error.message,type:t.error.type}:{message:"Server error",type:"extension_error"},...i};default:return{status:"not-reachable",version:null,error:{message:"Unknown server status",type:"extension_error"}}}}async*stream(e,t,r,i,o=!0){const s=this.generateRequestId();this.logger.debug("Starting stream",{method:e,endpoint:t,requestId:s});const a=chrome.runtime.connect({name:f}),c=new ReadableStream({start:u=>{a.onMessage.addListener(d=>{if(d.requestId===s)switch(d.type){case h.EXT2EXT_CLIENT_STREAM_DONE:this.logger.debug("Stream complete",{requestId:s}),u.close(),a.disconnect();break;case h.EXT2EXT_CLIENT_STREAM_ERROR:this.logger.error("Stream error",{requestId:s,error:JSON.stringify(d.error)}),u.error(new n.BodhiError("extension_error",d.error.message)),a.disconnect();break;case h.EXT2EXT_CLIENT_STREAM_API_ERROR:{const T=d;this.logger.error("Stream API error",{requestId:s,error:T.response.body?.error}),u.error(new n.BodhiApiError(T.response.status,T.response.body,T.response.body?.error?.message||"API error")),a.disconnect();break}case h.EXT2EXT_CLIENT_STREAM_CHUNK:{const T=d;g.isApiSuccessResponse(T.response)&&u.enqueue(T.response.body);break}}}),a.onDisconnect.addListener(()=>{this.logger.debug("Port disconnected",{requestId:s});try{u.error(new n.BodhiError("connection_closed","Connection closed unexpectedly"))}catch{}}),a.postMessage({type:h.EXT2EXT_CLIENT_STREAM_REQUEST,requestId:s,request:{method:e,endpoint:t,body:r,headers:i,authenticated:o}})}}).getReader();try{for(;;){const{done:u,value:d}=await c.read();if(u){this.logger.debug("Stream iteration complete");break}yield d}}finally{c.releaseLock()}}get chat(){return this._chat??=new n.Chat(this)}get models(){return this._models??=new n.Models(this)}get embeddings(){return this._embeddings??=new n.Embeddings(this)}get mcps(){return this._mcps??=new n.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 n.pollAccessRequestUntilResolved(r=>this.getAccessRequestStatus(r),e,t)}serialize(){return{extensionId:this.extensionId??void 0}}async debug(){return{type:"ExtClient",state:this.state,authState:await this.getAuthState()}}}class L extends n.BaseFacadeClient{constructor(e,t,r){const i=t||{},o={basePath:i.basePath||"/",authServerUrl:i.authServerUrl||"https://id.getbodhi.app/realms/bodhi",logLevel:i.logLevel||"warn",apiTimeoutMs:i.apiTimeoutMs,initParams:i.initParams};super(e,o,r)}createLogger(e){return new n.Logger("ExtUIClient",e.logLevel)}createStoragePrefix(e){return n.createStoragePrefixWithBasePath(e.basePath,n.STORAGE_PREFIXES.EXT)}createExtClient(e,t){return new X({authClientId:this.authClientId,logLevel:e.logLevel,apiTimeoutMs:e.apiTimeoutMs,initParams:e.initParams},t)}createDirectClient(e,t,r){return new v({authClientId:e,authServerUrl:t.authServerUrl,logLevel:t.logLevel,basePath:t.basePath,apiTimeoutMs:t.apiTimeoutMs},r)}}const b=["ggedphdcbekjlomjaidbajglgihbeaon"],M=["bjdjhiombmfbcoeojijpfckljjghmjbf"],y="production",p=class p{constructor(e,t){this.isAuthenticating=!1,this.state="setup",this.listenersInitialized=!1,this.refreshPromise=null,this.activeStreamPorts=new Map,this.authClientId=e,this.authServerUrl=t?.authServerUrl||"https://id.getbodhi.app/realms/bodhi",this.extensionId=t?.extensionId,this.logger=new n.Logger("BodhiExtClient",t?.logLevel||"warn"),this.attempts=t?.attempts??C,this.attemptWaitMs=t?.attemptWaitMs??w,this.attemptTimeout=t?.attemptTimeout??A,this.authEndpoints={authorize:`${this.authServerUrl}/protocol/openid-connect/auth`,token:`${this.authServerUrl}/protocol/openid-connect/token`,userinfo:`${this.authServerUrl}/protocol/openid-connect/userinfo`,logout:`${this.authServerUrl}/protocol/openid-connect/logout`,revoke:`${this.authServerUrl}/protocol/openid-connect/revoke`},this.extensionId?this.logger.info(`[BodhiExtClient] Created client for extension: ${this.extensionId}`):this.logger.info("[BodhiExtClient] Created client without extension ID (call init() to discover)")}static base64UrlEncode(e){return btoa(String.fromCharCode(...new Uint8Array(e))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}static generateCodeVerifier(){const e=new Uint8Array(32);return crypto.getRandomValues(e),p.base64UrlEncode(e.buffer)}static async generateCodeChallenge(e){const r=new TextEncoder().encode(e),i=await crypto.subtle.digest("SHA-256",r);return p.base64UrlEncode(i)}getState(){return this.state}getExtensionIdsForEnvironment(){const t=y!=="production"?b:M;return this.logger.info("[Ext2Ext/Registry] Environment: production"),this.logger.debug("[Ext2Ext/Registry] Using extension IDs:",t),t}async pingExtension(e){return this.logger.debug(`[Ext2Ext/Discovery] Pinging extension: ${e} with timeout ${this.attemptTimeout}ms`),new Promise((t,r)=>{const i=setTimeout(()=>{this.logger.debug(`[Ext2Ext/Discovery] Timeout waiting for extension ${e}`),r(new Error("Timeout"))},this.attemptTimeout);try{const o={type:g.MESSAGE_TYPES.EXT_REQUEST,requestId:crypto.randomUUID(),request:{action:"get_extension_id"}};this.logger.debug(`[Ext2Ext/Discovery] Sending message to ${e}:`,o),chrome.runtime.sendMessage(e,o,s=>{if(clearTimeout(i),chrome.runtime.lastError){this.logger.error(`[Ext2Ext/Discovery] Error from extension ${e}:`,chrome.runtime.lastError.message),r(new Error(chrome.runtime.lastError.message));return}this.logger.debug(`[Ext2Ext/Discovery] Response from ${e}:`,s);const a=s;a&&a.type===g.MESSAGE_TYPES.EXT_RESPONSE?(this.logger.debug(`[Ext2Ext/Discovery] ✓ Extension ${e} responded`),t(!0)):(this.logger.error(`[Ext2Ext/Discovery] Invalid response from ${e}:`,s),r(new Error("Invalid response")))})}catch(o){this.logger.error(`[Ext2Ext/Discovery] Exception pinging ${e}:`,o),clearTimeout(i),r(o)}})}sleep(e){return new Promise(t=>setTimeout(t,e))}async discoverBodhiExtension(e){const{attempts:t,attemptWaitMs:r,attemptTimeout:i}=e;this.logger.info(`[Ext2Ext/Discovery] Starting discovery: ${t} attempts per ID, ${i}ms timeout, ${r}ms between attempts`);const o=this.getExtensionIdsForEnvironment();this.logger.debug(`[Ext2Ext/Discovery] Will try ${o.length} extension(s):`,o);for(const E of o){for(let c=1;c<=t;c++){this.logger.debug(`[Ext2Ext/Discovery] Trying ${E} - attempt ${c}/${t}`);try{return await this.pingExtension(E),this.logger.info(`[Ext2Ext/Discovery] ✓ Found: ${E} on attempt ${c}`),{success:!0,extensionId:E}}catch(u){this.logger.debug(`[Ext2Ext/Discovery] Attempt ${c} failed for ${E}: ${u instanceof Error?u.message:"Unknown error"}`),c<t&&await this.sleep(r)}}this.logger.warn(`[Ext2Ext/Discovery] ✗ Not found: ${E} after ${t} attempts`)}const s=o.join(", "),a=`Extension not found. Tried ${o.length} IDs with ${t} attempts each: ${s}`;return this.logger.error(`[Ext2Ext/Discovery] ${a}`),{success:!1,error:a}}setupListeners(){if(this.listenersInitialized){this.logger.debug("[BodhiExtClient] Listeners already initialized, skipping");return}this.listenersInitialized=!0,chrome.runtime.onMessage.addListener((e,t,r)=>e.type!==h.EXT2EXT_CLIENT_REQUEST&&e.type!==h.EXT2EXT_CLIENT_API_REQUEST?!1:this.state!=="ready"&&!(e.type===h.EXT2EXT_CLIENT_REQUEST&&e.request.action===_.DISCOVER_EXTENSION)?(e.type===h.EXT2EXT_CLIENT_REQUEST?r({type:h.EXT2EXT_CLIENT_RESPONSE,requestId:e.requestId,response:{error:{message:this.createErrorClientNotInitialized(e),type:"NOT_INITIALIZED"}}}):r({type:h.EXT2EXT_CLIENT_API_RESPONSE,requestId:e.requestId,error:{message:`Client not initialized. Extension discovery not complete, cannot handle type:${e.type}, message:${JSON.stringify(e)}`,type:"NOT_INITIALIZED"}}),!0):(this.logger.debug(`[BodhiExtClient] Processing message.type=${e.type}`),(async()=>{const i=await this.handleAction(e);r(i)})(),!0)),chrome.runtime.onConnect.addListener(e=>{if(e.name!==f){this.logger.debug("[BodhiExtClient] Ignoring port with name:",e.name);return}this.logger.info("[BodhiExtClient] Streaming port connected"),e.onMessage.addListener(async t=>{if(t.type!==h.EXT2EXT_CLIENT_STREAM_REQUEST){this.logger.warn("[BodhiExtClient] Unknown stream message type:",t.type),e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_ERROR,requestId:t.requestId,error:{message:"Unknown stream message type",type:"extension_error"}});return}await this.handleStreamRequest(e,t)}),e.onDisconnect.addListener(()=>{this.logger.info("[BodhiExtClient] Streaming port disconnected")})}),this.logger.info("[BodhiExtClient] Streaming listeners initialized")}async init(e){if(this.setupListeners(),this.extensionId){this.state="ready",this.logger.warn(`[BodhiExtClient] Already initialized with extension ID: ${this.extensionId}`);return}this.logger.info("[BodhiExtClient] Starting discovery");const t={attempts:e?.attempts??this.attempts,attemptWaitMs:e?.attemptWaitMs??this.attemptWaitMs,attemptTimeout:e?.attemptTimeout??this.attemptTimeout},r=await this.discoverBodhiExtension(t);if(!r.success||!r.extensionId)throw new Error(r.error||"Discovery failed");this.extensionId=r.extensionId,this.state="ready",this.logger.info(`[BodhiExtClient] ✓ Initialized: ${this.extensionId}`)}broadcastAuthStateChange(){chrome.runtime.sendMessage({type:h.EXT2EXT_CLIENT_BROADCAST,event:"authStateChanged"}).catch(e=>{this.logger.debug("[BodhiExtClient] No listeners for broadcast:",e.message)})}async getExtensionIdFromExt(){this.logger.debug("[BodhiExtClient] Getting extension ID from bodhi-browser-ext");const e=await this.sendExtRequest("get_extension_id");return this.logger.debug("[BodhiExtClient] Extension ID response:",e),e.extension_id}async handleApiRequest(e){const{requestId:t}=e;this.logger.debug("[BodhiExtClient] Handling API request:",e.request);try{let r=e.request.headers||{};if(e.request.authenticated){const o=await this._getAccessTokenRaw();if(!o)return{type:h.EXT2EXT_CLIENT_API_RESPONSE,requestId:t,error:{message:"Not authenticated. Please log in first.",type:"auth_error"}};r={...r,Authorization:`Bearer ${o}`},this.logger.debug("[BodhiExtClient] Injected auth token for authenticated request")}const i=await this.sendApiRequest(e.request.method,e.request.endpoint,e.request.body,r);return{type:h.EXT2EXT_CLIENT_API_RESPONSE,requestId:t,response:i}}catch(r){return this.logger.error("[BodhiExtClient] API request failed:",r),{type:h.EXT2EXT_CLIENT_API_RESPONSE,requestId:t,error:{message:r instanceof Error?r.message:"Unknown error",type:"network_error"}}}}async handleExtClientRequest(e){const{requestId:t,request:r}=e,{action:i,params:o}=r;this.logger.debug(`[BodhiExtClient] Handling action: ${i}`);try{let s={};switch(i){case _.DISCOVER_EXTENSION:{const a=o;await this.init(a),this.logger.info("[BodhiExtClient] Discovery successful:",{extensionId:this.extensionId,environment:y}),s={extensionId:this.extensionId,environment:y};break}case _.SET_EXTENSION_ID:{const{extensionId:a}=o;this.extensionId=a,this.state="ready",this.logger.info("[BodhiExtClient] Extension ID set:",{extensionId:a}),s={success:!0};break}case _.GET_EXTENSION_ID:{const a=await this.sendExtRequestRaw(g.EXT_ACTIONS.GET_EXTENSION_ID,o);return g.isExtError(a.response)?{type:h.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:{error:{message:a.response.error.message||`Extension request failed to get extension ID: ${JSON.stringify(a.response)}`,type:a.response.error.type}}}:{type:h.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:a.response}}case _.LOGIN:{const a=o;await this.login(a),this.broadcastAuthStateChange();break}case _.LOGOUT:await this.logout(),this.broadcastAuthStateChange();break;case _.GET_AUTH_STATE:s={authState:await this.getAuthState()};break;default:return{type:h.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:{error:{message:`Unknown action: ${i}`,type:"UNKNOWN_ACTION"}}}}return{type:h.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:s}}catch(s){return this.logger.error("[BodhiExtClient] Unexpected error:",s),{type:h.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:{error:{message:s instanceof Error?s.message:`Unexpected error: ${JSON.stringify(s)}`}}}}}async handleAction(e){switch(e.type){case h.EXT2EXT_CLIENT_API_REQUEST:return this.handleApiRequest(e);case h.EXT2EXT_CLIENT_REQUEST:return this.handleExtClientRequest(e);default:{const{requestId:t}=e;return this.logger.error("[BodhiExtClient] Unknown message type:",e.type),{type:h.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:{error:{message:`Unknown message type: ${e.type}`,type:"UNKNOWN_MESSAGE_TYPE"}}}}}}async login(e){if(!(this.isAuthenticating||(await this.getAuthState()).status==="authenticated")){this.isAuthenticating=!0;try{if(!this.extensionId)throw new Error("Extension not discovered. Please detect Bodhi extension before login.");e?.flowType==="redirect"&&this.logger.warn("Extension mode does not support redirect flow type; using popup instead");const r=e?.userRole??"scope_user_user",i=new n.AccessRequestBuilder(this.authClientId).requestedRole(r).flowType("popup");e?.requested&&i.requested(e.requested);const o=i.build(),s=await this.requestAccess(o),{id:a,review_url:E}=n.unwrapResponse(s);await chrome.tabs.create({url:E});const c=await this.pollAccessRequestStatus(a,{intervalMs:e?.pollIntervalMs??n.DEFAULT_POLL_INTERVAL_MS,timeoutMs:e?.pollTimeoutMs??n.DEFAULT_POLL_TIMEOUT_MS});if(c.status!=="approved")throw n.createOperationError("auth_error",`Access request ${c.status}`);const d=`openid profile email roles ${c.access_request_scope??""}`.trim();await this.performOAuthPkce(d)}finally{this.isAuthenticating=!1}}}async exchangeCodeForTokens(e){if((await this.getAuthState()).status==="authenticated")return;const{codeVerifier:r}=await chrome.storage.session.get("codeVerifier"),i=chrome.identity.getRedirectURL("callback"),o=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:i,client_id:this.authClientId,code_verifier:r})});if(!o.ok){const a=await o.text();throw new Error(`Token exchange failed: ${o.status} ${a}`)}const s=await o.json();await this.storeTokens({accessToken:s.access_token,refreshToken:s.refresh_token,idToken:s.id_token,expiresIn:s.expires_in})}async getAuthState(){const e=await this._getAccessTokenRaw();if(!e)return{status:"unauthenticated",user:null,accessToken:null,error:null};try{const t=this.parseJwt(e);return{status:"authenticated",user:{sub:t.sub,email:t.email,name:t.name,given_name:t.given_name,family_name:t.family_name,preferred_username:t.preferred_username},accessToken:e,error:null}}catch(t){return this.logger.error("Failed to parse token:",t),{status:"unauthenticated",user:null,accessToken:null,error:null}}}async logout(){const{refreshToken:e}=await chrome.storage.session.get("refreshToken");if(e)try{await fetch(this.authEndpoints.revoke,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({token:e,client_id:this.authClientId,token_type_hint:"refresh_token"})})}catch(t){this.logger.warn("[OAuth] Token revocation failed:",t)}await this.clearTokens()}async requestAccess(e){return this.sendApiRequest("POST","/bodhi/v1/apps/request-access",e)}async getAccessRequestStatus(e){return this.sendApiRequest("GET",`/bodhi/v1/apps/access-requests/${e}?app_client_id=${encodeURIComponent(this.authClientId)}`)}async pollAccessRequestStatus(e,t){return n.pollAccessRequestUntilResolved(r=>this.getAccessRequestStatus(r),e,t)}async performOAuthPkce(e){const t=p.generateCodeVerifier(),r=await p.generateCodeChallenge(t),i=p.generateCodeVerifier();await chrome.storage.session.set({codeVerifier:t,state:i,authInProgress:!0});const o=chrome.identity.getRedirectURL("callback"),s=new URL(this.authEndpoints.authorize);return s.searchParams.set("client_id",this.authClientId),s.searchParams.set("response_type","code"),s.searchParams.set("redirect_uri",o),s.searchParams.set("scope",e),s.searchParams.set("code_challenge",r),s.searchParams.set("code_challenge_method","S256"),s.searchParams.set("state",i),new Promise((a,E)=>{chrome.identity.launchWebAuthFlow({url:s.toString(),interactive:!0},async c=>{if(await chrome.storage.session.set({authInProgress:!1}),chrome.runtime.lastError){await chrome.storage.session.remove(["codeVerifier","state"]),E(chrome.runtime.lastError);return}if(!c){await chrome.storage.session.remove(["codeVerifier","state"]),E(n.createOperationError("oauth_error","No redirect URL received"));return}try{const u=new URL(c),d=u.searchParams.get("code"),T=u.searchParams.get("state"),{state:m}=await chrome.storage.session.get("state");if(T!==m){await chrome.storage.session.remove(["codeVerifier","state"]),E(n.createOperationError("oauth_error","State mismatch"));return}if(!d){await chrome.storage.session.remove(["codeVerifier","state"]),E(n.createOperationError("oauth_error","No authorization code"));return}await this.exchangeCodeForTokens(d),await chrome.storage.session.remove(["codeVerifier","state"]);const l=await this.getAuthState();if(l.status!=="authenticated")throw n.createOperationError("oauth_error","Login failed");a(l)}catch(u){await chrome.storage.session.remove(["codeVerifier","state"]),E(u)}})})}async sendExtRequest(e,t){const r=await this.sendExtRequestRaw(e,t);if(g.isExtError(r.response))throw this.logger.error("[BodhiExtClient] Extension error:",r.response.error),new Error(r.response.error.message||`Extension request failed: ${JSON.stringify(r.response)}`);return r.response}async sendApiRequest(e,t,r,i){if(!this.extensionId)throw new n.BodhiError("not_initialized",this.createErrorClientNotInitialized({type:"api",method:e,endpoint:t}));this.logger.debug(`[BodhiExtClient] Sending API_REQUEST: method=${e}, endpoint=${t}`,r?{body:r}:"");const o=crypto.randomUUID(),s={type:g.MESSAGE_TYPES.API_REQUEST,requestId:o,request:{method:e,endpoint:t,body:r,headers:i}};return this.logger.debug(`[BodhiExtClient] Request ID: ${o}, Extension: ${this.extensionId}`),new Promise((a,E)=>{try{chrome.runtime.sendMessage(this.extensionId,s,c=>{if(chrome.runtime.lastError){this.logger.error(`[BodhiExtClient] Chrome runtime error for request ${o}:`,chrome.runtime.lastError),E(new n.BodhiError("extension_error",chrome.runtime.lastError.message??"Chrome runtime error"));return}if(this.logger.debug(`[BodhiExtClient] Response for request ${o}:`,c),!c){this.logger.error(`[BodhiExtClient] No response received for request ${o}`),E(new n.BodhiError("extension_error","No response from extension"));return}c.type===g.MESSAGE_TYPES.API_RESPONSE&&c.requestId===o?"error"in c?(this.logger.error(`[BodhiExtClient] API error for ${o}:`,c.error),E(new n.BodhiError(c.error.type||"extension_error",c.error.message))):(this.logger.debug(`[BodhiExtClient] ✓ Valid API_RESPONSE for ${o}`),a(c.response)):(this.logger.error(`[BodhiExtClient] Invalid response format for ${o}:`,c),E(new n.BodhiError("extension_error","Invalid response format")))})}catch(c){this.logger.error(`[BodhiExtClient] Exception sending message for ${o}:`,c),E(c)}})}async sendExtRequestRaw(e,t){if(!this.extensionId)throw new n.BodhiError("not_initialized",this.createErrorClientNotInitialized({type:"ext",action:e,params:t}));this.logger.debug(`[BodhiExtClient] Sending EXT_REQUEST (raw): action=${e}`,t?{params:t}:"");const r=crypto.randomUUID(),i={type:g.MESSAGE_TYPES.EXT_REQUEST,requestId:r,request:{action:e,params:t}};return this.logger.debug(`[BodhiExtClient] Request ID: ${r}, Extension: ${this.extensionId}`),new Promise((o,s)=>{try{chrome.runtime.sendMessage(this.extensionId,i,a=>{if(chrome.runtime.lastError){this.logger.error(`[BodhiExtClient] Chrome runtime error for request ${r}:`,chrome.runtime.lastError),s(new Error(chrome.runtime.lastError.message));return}if(this.logger.debug(`[BodhiExtClient] Response for request ${r}:`,a),!a){this.logger.error(`[BodhiExtClient] No response received for request ${r}`),s(new Error("No response from extension"));return}a.type===g.MESSAGE_TYPES.EXT_RESPONSE&&a.requestId===r?(this.logger.debug(`[BodhiExtClient] ✓ Valid EXT_RESPONSE for ${r}`),o(a)):(this.logger.error(`[BodhiExtClient] Invalid response format for ${r}:`,a),s(new n.BodhiError("extension_error","Invalid response format")))})}catch(a){this.logger.error(`[BodhiExtClient] Exception sending message for ${r}:`,a),s(a)}})}async handleStreamRequest(e,t){const{requestId:r,request:i}=t,{method:o,endpoint:s,body:a,headers:E,authenticated:c}=i;this.logger.debug("[BodhiExtClient] Processing stream request:",{requestId:r,method:o,endpoint:s,authenticated:c}),this.extensionId||e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_ERROR,requestId:r,error:{message:this.createErrorClientNotInitialized(t),type:"extension_error"}});try{let u={...E};if(c!==!1){const l=await this._getAccessTokenRaw();if(!l){e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_ERROR,requestId:r,error:{message:"Not authenticated. Please log in first.",type:"extension_error"}});return}u={...u,Authorization:`Bearer ${l}`},this.logger.debug("[BodhiExtClient] Injected auth token for authenticated request")}const d=chrome.runtime.connect(this.extensionId,{name:g.BODHI_STREAM_PORT});this.activeStreamPorts.set(r,d);const T=setTimeout(()=>{this.activeStreamPorts.has(r)&&(this.logger.error(`[BodhiExtClient] Stream timeout for ${r}`),e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_ERROR,requestId:r,error:{message:"Stream request timed out",type:"timeout_error"}}),this.cleanupStreamPort(r))},p.STREAM_TIMEOUT);d.onMessage.addListener(l=>{if(g.isStreamChunk(l)){const I=l.response,O=I.body;I.status>=400?e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_API_ERROR,requestId:r,response:I}):O?.done?(e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_DONE,requestId:r}),this.logger.info(`[BodhiExtClient] Stream complete for ${r}`),clearTimeout(T),this.cleanupStreamPort(r)):e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_CHUNK,requestId:r,response:I})}else g.isStreamApiError(l)?(this.logger.error(`[BodhiExtClient] Stream API error for ${r}: ${l.response.status}`),e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_API_ERROR,requestId:r,response:l.response})):g.isStreamError(l)&&(this.logger.error(`[BodhiExtClient] Stream error for ${r}:`,l.error.message),e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_ERROR,requestId:r,error:{message:`stream error: ${JSON.stringify(l)}`,type:"extension_error"}}),clearTimeout(T),this.cleanupStreamPort(r))}),d.onDisconnect.addListener(()=>{clearTimeout(T),this.activeStreamPorts.has(r)&&(this.logger.error(`[BodhiExtClient] Bodhi port disconnected for ${r}`),e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_ERROR,requestId:r,error:{message:"Connection to Bodhi extension closed unexpectedly",type:"network_error"}}),this.activeStreamPorts.delete(r))});const m={type:g.MESSAGE_TYPES.STREAM_REQUEST,requestId:r,request:{method:o,endpoint:s,body:a,headers:u}};this.logger.debug("[BodhiExtClient] Sending stream request to bodhi port:",m),d.postMessage(m)}catch(u){const d=u;this.logger.error("[BodhiExtClient] Stream error:",JSON.stringify(d.message)),e.postMessage({type:h.EXT2EXT_CLIENT_STREAM_ERROR,requestId:r,error:{message:`uncaught error: ${JSON.stringify({error:d,message:d.message})}`,type:"extension_error"}})}}cleanupStreamPort(e){const t=this.activeStreamPorts.get(e);if(t){try{t.disconnect()}catch{}this.activeStreamPorts.delete(e)}}async storeTokens(e){const t=Date.now()+(e.expiresIn||3600)*1e3;await chrome.storage.session.set({accessToken:e.accessToken,refreshToken:e.refreshToken,idToken:e.idToken,expiresAt:t})}async _getAccessTokenRaw(){const{accessToken:e,expiresAt:t}=await chrome.storage.session.get(["accessToken","expiresAt"]);if(!e||!t)return null;if(Date.now()>=t-5*1e3){const{refreshToken:r}=await chrome.storage.session.get("refreshToken");return r?this._tryRefreshToken(r):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 n.refreshAccessToken(this.authEndpoints.token,e,this.authClientId);if(t.success)return await this._storeRefreshedTokens(t.tokens),this.logger.info("Token refreshed successfully"),this.broadcastAuthStateChange(),t.tokens.access_token;if(t.error==="invalid_grant")return this.logger.warn("Refresh token expired or revoked, clearing tokens and logging out"),await this.clearTokens(),this.broadcastAuthStateChange(),null}catch(t){this.logger.warn("Token refresh failed:",t)}throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"),n.createOperationError("auth_error","Access token expired and unable to refresh. Try logging out and logging in again.")}async _storeRefreshedTokens(e){const t=Date.now()+e.expires_in*1e3,r={accessToken:e.access_token,expiresAt:t};e.refresh_token&&(r.refreshToken=e.refresh_token),e.id_token&&(r.idToken=e.id_token),await chrome.storage.session.set(r)}async clearTokens(){await chrome.storage.session.remove(["accessToken","refreshToken","idToken","expiresAt","codeVerifier","state","authInProgress","bodhiUserInfo"])}parseJwt(e){const r=e.split(".")[1].replace(/-/g,"+").replace(/_/g,"/"),i=decodeURIComponent(atob(r).split("").map(o=>"%"+("00"+o.charCodeAt(0).toString(16)).slice(-2)).join(""));return JSON.parse(i)}createErrorClientNotInitialized(e){return`Client not initialized. Extension discovery not triggered nor extensionId set, cannot handle request: ${JSON.stringify(e)}`}};p.STREAM_TIMEOUT=6e4;let R=p;const k="production";exports.BodhiExtClient=R;exports.DEFAULT_API_TIMEOUT_MS=N;exports.DISCOVERY_ATTEMPTS=C;exports.DISCOVERY_ATTEMPT_TIMEOUT=A;exports.DISCOVERY_ATTEMPT_WAIT_MS=w;exports.DISCOVERY_TIMEOUT_MS=x;exports.EXT2EXT_CLIENT_ACTIONS=_;exports.EXT2EXT_CLIENT_MESSAGE_TYPES=h;exports.EXT2EXT_CLIENT_STREAM_PORT=f;exports.EXT_BUILD_MODE=k;exports.ExtUIClient=L;exports.isExtClientApiError=P;
|
package/dist/bodhi-ext.esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { DirectClientBase as M, createStoragePrefixWithBasePath as N, STORAGE_PREFIXES as v, AccessRequestBuilder as P, unwrapResponse as X, DEFAULT_POLL_TIMEOUT_MS as b, DEFAULT_POLL_INTERVAL_MS as O, createOperationError as l, generateCodeVerifier as C, generateCodeChallenge as U, PENDING_EXTENSION_READY as
|
|
2
|
-
import { isExtError as x, isApiSuccessResponse as
|
|
1
|
+
import { DirectClientBase as M, createStoragePrefixWithBasePath as N, STORAGE_PREFIXES as v, AccessRequestBuilder as P, unwrapResponse as X, DEFAULT_POLL_TIMEOUT_MS as b, DEFAULT_POLL_INTERVAL_MS as O, createOperationError as l, generateCodeVerifier as C, generateCodeChallenge as U, PENDING_EXTENSION_READY as f, Logger as w, NOOP_STATE_CALLBACK as D, createExtensionStateNotInitialized as $, BACKEND_SERVER_NOT_REACHABLE as B, createExtensionStateNotFound as K, BodhiError as T, INITIAL_AUTH_STATE as V, BodhiApiError as z, Chat as F, Models as G, Embeddings as Q, Mcps as H, pollAccessRequestUntilResolved as L, isAuthError as W, BaseFacadeClient as J, refreshAccessToken as Y } from "@bodhiapp/bodhi-js-core";
|
|
2
|
+
import { isExtError as x, isApiSuccessResponse as Z, MESSAGE_TYPES as _, EXT_ACTIONS as j, BODHI_STREAM_PORT as ee, isStreamChunk as te, isStreamApiError as re, isStreamError as se } from "@bodhiapp/bodhi-browser-types";
|
|
3
3
|
const c = {
|
|
4
4
|
EXT2EXT_CLIENT_REQUEST: "EXT2EXT_CLIENT_REQUEST",
|
|
5
5
|
EXT2EXT_CLIENT_RESPONSE: "EXT2EXT_CLIENT_RESPONSE",
|
|
@@ -19,11 +19,11 @@ const c = {
|
|
|
19
19
|
DISCOVER_EXTENSION: "discoverBodhiExtension",
|
|
20
20
|
GET_EXTENSION_ID: "get_extension_id",
|
|
21
21
|
SET_EXTENSION_ID: "setExtensionId"
|
|
22
|
-
},
|
|
23
|
-
function
|
|
22
|
+
}, oe = 5e3, ie = 3, ne = 500, ae = 500, ce = 3e4;
|
|
23
|
+
function he(y) {
|
|
24
24
|
return "error" in y;
|
|
25
25
|
}
|
|
26
|
-
class
|
|
26
|
+
class ue extends M {
|
|
27
27
|
constructor(e, t) {
|
|
28
28
|
const r = N(
|
|
29
29
|
e.basePath,
|
|
@@ -31,7 +31,6 @@ class de extends M {
|
|
|
31
31
|
), i = {
|
|
32
32
|
authClientId: e.authClientId,
|
|
33
33
|
authServerUrl: e.authServerUrl,
|
|
34
|
-
userRole: e.userRole,
|
|
35
34
|
storagePrefix: r,
|
|
36
35
|
logLevel: e.logLevel,
|
|
37
36
|
loggerPrefix: "DirectExtClient",
|
|
@@ -47,7 +46,7 @@ class de extends M {
|
|
|
47
46
|
if (t.status === "authenticated")
|
|
48
47
|
return t;
|
|
49
48
|
e?.flowType === "redirect" && this.logger.warn("Extension mode does not support redirect flow type; using popup instead");
|
|
50
|
-
const r = e?.userRole ??
|
|
49
|
+
const r = e?.userRole ?? "scope_user_user";
|
|
51
50
|
e?.onProgress?.("requesting");
|
|
52
51
|
const i = new P(this.authClientId).requestedRole(r).flowType("popup");
|
|
53
52
|
e?.requested && i.requested(e.requested);
|
|
@@ -198,14 +197,14 @@ class de extends M {
|
|
|
198
197
|
return chrome.identity.getRedirectURL("callback");
|
|
199
198
|
}
|
|
200
199
|
}
|
|
201
|
-
class
|
|
200
|
+
class de {
|
|
202
201
|
constructor(e = {}, t) {
|
|
203
202
|
this.state = {
|
|
204
203
|
type: "extension",
|
|
205
204
|
extension: "not-initialized",
|
|
206
205
|
extensionId: null,
|
|
207
|
-
server:
|
|
208
|
-
}, this.extensionId = null, this.broadcastListenerActive = !1, this.config = e, this.logger = new w("ExtClient", e?.logLevel || "warn"), this.onStateChange = t ?? D, this.apiTimeoutMs = e.apiTimeoutMs ??
|
|
206
|
+
server: f
|
|
207
|
+
}, this.extensionId = null, this.broadcastListenerActive = !1, this.config = e, this.logger = new w("ExtClient", e?.logLevel || "warn"), this.onStateChange = t ?? D, this.apiTimeoutMs = e.apiTimeoutMs ?? ce, this.authClientId = e.authClientId ?? "";
|
|
209
208
|
}
|
|
210
209
|
/**
|
|
211
210
|
* Set client state and notify callback
|
|
@@ -278,7 +277,7 @@ class Ee {
|
|
|
278
277
|
}
|
|
279
278
|
if (this.extensionId && !e.testConnection)
|
|
280
279
|
return this.logger.debug("Already initialized with extensionId, skipping discovery"), this.state;
|
|
281
|
-
const t = e.timeoutMs ?? this.config.initParams?.extension?.timeoutMs ??
|
|
280
|
+
const t = e.timeoutMs ?? this.config.initParams?.extension?.timeoutMs ?? oe, r = e.savedState?.extensionId;
|
|
282
281
|
try {
|
|
283
282
|
if (!this.extensionId) {
|
|
284
283
|
if (r)
|
|
@@ -306,9 +305,9 @@ class Ee {
|
|
|
306
305
|
type: "extension",
|
|
307
306
|
extension: "ready",
|
|
308
307
|
extensionId: this.extensionId,
|
|
309
|
-
server:
|
|
308
|
+
server: f
|
|
310
309
|
};
|
|
311
|
-
let o =
|
|
310
|
+
let o = f;
|
|
312
311
|
if (e.testConnection)
|
|
313
312
|
try {
|
|
314
313
|
o = await this.getServerState(), this.logger.info("Server connectivity tested, state:", o.status);
|
|
@@ -402,7 +401,7 @@ class Ee {
|
|
|
402
401
|
this.sendRawApiMessage(e, t, r, i, o),
|
|
403
402
|
s
|
|
404
403
|
]);
|
|
405
|
-
if (
|
|
404
|
+
if (he(n)) {
|
|
406
405
|
const h = n.error.type || "extension_error";
|
|
407
406
|
throw new T(h, n.error.message);
|
|
408
407
|
}
|
|
@@ -416,7 +415,7 @@ class Ee {
|
|
|
416
415
|
}
|
|
417
416
|
/**
|
|
418
417
|
* Login user via OAuth
|
|
419
|
-
* @param options - Optional login parameters
|
|
418
|
+
* @param options - Optional login parameters
|
|
420
419
|
* @throws ExtError if login fails
|
|
421
420
|
* @returns AuthState with login state and user info
|
|
422
421
|
*/
|
|
@@ -427,7 +426,7 @@ class Ee {
|
|
|
427
426
|
chrome.runtime.onMessage.removeListener(i);
|
|
428
427
|
try {
|
|
429
428
|
const s = await this.getAuthState();
|
|
430
|
-
if (
|
|
429
|
+
if (W(s)) {
|
|
431
430
|
r(
|
|
432
431
|
l("auth_error", `Login failed: ${s.error?.message}`)
|
|
433
432
|
);
|
|
@@ -582,7 +581,7 @@ class Ee {
|
|
|
582
581
|
}
|
|
583
582
|
case c.EXT2EXT_CLIENT_STREAM_CHUNK: {
|
|
584
583
|
const g = d;
|
|
585
|
-
|
|
584
|
+
Z(g.response) && u.enqueue(g.response.body);
|
|
586
585
|
break;
|
|
587
586
|
}
|
|
588
587
|
}
|
|
@@ -624,11 +623,8 @@ class Ee {
|
|
|
624
623
|
get embeddings() {
|
|
625
624
|
return this._embeddings ??= new Q(this);
|
|
626
625
|
}
|
|
627
|
-
get toolsets() {
|
|
628
|
-
return this._toolsets ??= new H(this);
|
|
629
|
-
}
|
|
630
626
|
get mcps() {
|
|
631
|
-
return this._mcps ??= new
|
|
627
|
+
return this._mcps ??= new H(this);
|
|
632
628
|
}
|
|
633
629
|
// ============================================================================
|
|
634
630
|
// Access Request Methods
|
|
@@ -675,12 +671,11 @@ class Ee {
|
|
|
675
671
|
};
|
|
676
672
|
}
|
|
677
673
|
}
|
|
678
|
-
class
|
|
674
|
+
class me extends J {
|
|
679
675
|
constructor(e, t, r) {
|
|
680
676
|
const i = t || {}, o = {
|
|
681
677
|
basePath: i.basePath || "/",
|
|
682
678
|
authServerUrl: i.authServerUrl || "https://id.getbodhi.app/realms/bodhi",
|
|
683
|
-
userRole: i.userRole || "scope_user_user",
|
|
684
679
|
logLevel: i.logLevel || "warn",
|
|
685
680
|
apiTimeoutMs: i.apiTimeoutMs,
|
|
686
681
|
initParams: i.initParams
|
|
@@ -694,7 +689,7 @@ class pe extends Y {
|
|
|
694
689
|
return N(e.basePath, v.EXT);
|
|
695
690
|
}
|
|
696
691
|
createExtClient(e, t) {
|
|
697
|
-
return new
|
|
692
|
+
return new de(
|
|
698
693
|
{
|
|
699
694
|
authClientId: this.authClientId,
|
|
700
695
|
logLevel: e.logLevel,
|
|
@@ -705,11 +700,10 @@ class pe extends Y {
|
|
|
705
700
|
);
|
|
706
701
|
}
|
|
707
702
|
createDirectClient(e, t, r) {
|
|
708
|
-
return new
|
|
703
|
+
return new ue(
|
|
709
704
|
{
|
|
710
705
|
authClientId: e,
|
|
711
706
|
authServerUrl: t.authServerUrl,
|
|
712
|
-
userRole: t.userRole,
|
|
713
707
|
logLevel: t.logLevel,
|
|
714
708
|
basePath: t.basePath,
|
|
715
709
|
apiTimeoutMs: t.apiTimeoutMs
|
|
@@ -718,12 +712,12 @@ class pe extends Y {
|
|
|
718
712
|
);
|
|
719
713
|
}
|
|
720
714
|
}
|
|
721
|
-
const
|
|
715
|
+
const Ee = ["ggedphdcbekjlomjaidbajglgihbeaon"], le = ["bjdjhiombmfbcoeojijpfckljjghmjbf"], R = "production", p = class p {
|
|
722
716
|
// ============================================================================
|
|
723
717
|
// Constructor
|
|
724
718
|
// ============================================================================
|
|
725
719
|
constructor(e, t) {
|
|
726
|
-
this.isAuthenticating = !1, this.state = "setup", this.listenersInitialized = !1, this.refreshPromise = null, this.activeStreamPorts = /* @__PURE__ */ new Map(), this.authClientId = e, this.authServerUrl = t?.authServerUrl || "https://id.getbodhi.app/realms/bodhi", this.
|
|
720
|
+
this.isAuthenticating = !1, this.state = "setup", this.listenersInitialized = !1, this.refreshPromise = null, this.activeStreamPorts = /* @__PURE__ */ new Map(), this.authClientId = e, this.authServerUrl = t?.authServerUrl || "https://id.getbodhi.app/realms/bodhi", this.extensionId = t?.extensionId, this.logger = new w("BodhiExtClient", t?.logLevel || "warn"), this.attempts = t?.attempts ?? ie, this.attemptWaitMs = t?.attemptWaitMs ?? ne, this.attemptTimeout = t?.attemptTimeout ?? ae, this.authEndpoints = {
|
|
727
721
|
authorize: `${this.authServerUrl}/protocol/openid-connect/auth`,
|
|
728
722
|
token: `${this.authServerUrl}/protocol/openid-connect/token`,
|
|
729
723
|
userinfo: `${this.authServerUrl}/protocol/openid-connect/userinfo`,
|
|
@@ -764,7 +758,7 @@ const le = ["ggedphdcbekjlomjaidbajglgihbeaon"], ge = ["bjdjhiombmfbcoeojijpfckl
|
|
|
764
758
|
* Get extension IDs for current environment
|
|
765
759
|
*/
|
|
766
760
|
getExtensionIdsForEnvironment() {
|
|
767
|
-
const t =
|
|
761
|
+
const t = R !== "production" ? Ee : le;
|
|
768
762
|
return this.logger.info("[Ext2Ext/Registry] Environment: production"), this.logger.debug("[Ext2Ext/Registry] Using extension IDs:", t), t;
|
|
769
763
|
}
|
|
770
764
|
/**
|
|
@@ -1012,10 +1006,10 @@ const le = ["ggedphdcbekjlomjaidbajglgihbeaon"], ge = ["bjdjhiombmfbcoeojijpfckl
|
|
|
1012
1006
|
const n = o;
|
|
1013
1007
|
await this.init(n), this.logger.info("[BodhiExtClient] Discovery successful:", {
|
|
1014
1008
|
extensionId: this.extensionId,
|
|
1015
|
-
environment:
|
|
1009
|
+
environment: R
|
|
1016
1010
|
}), s = {
|
|
1017
1011
|
extensionId: this.extensionId,
|
|
1018
|
-
environment:
|
|
1012
|
+
environment: R
|
|
1019
1013
|
};
|
|
1020
1014
|
break;
|
|
1021
1015
|
}
|
|
@@ -1025,7 +1019,7 @@ const le = ["ggedphdcbekjlomjaidbajglgihbeaon"], ge = ["bjdjhiombmfbcoeojijpfckl
|
|
|
1025
1019
|
break;
|
|
1026
1020
|
}
|
|
1027
1021
|
case m.GET_EXTENSION_ID: {
|
|
1028
|
-
const n = await this.sendExtRequestRaw(
|
|
1022
|
+
const n = await this.sendExtRequestRaw(j.GET_EXTENSION_ID, o);
|
|
1029
1023
|
return x(n.response) ? {
|
|
1030
1024
|
type: c.EXT2EXT_CLIENT_RESPONSE,
|
|
1031
1025
|
requestId: t,
|
|
@@ -1115,7 +1109,7 @@ const le = ["ggedphdcbekjlomjaidbajglgihbeaon"], ge = ["bjdjhiombmfbcoeojijpfckl
|
|
|
1115
1109
|
if (!this.extensionId)
|
|
1116
1110
|
throw new Error("Extension not discovered. Please detect Bodhi extension before login.");
|
|
1117
1111
|
e?.flowType === "redirect" && this.logger.warn("Extension mode does not support redirect flow type; using popup instead");
|
|
1118
|
-
const r = e?.userRole ??
|
|
1112
|
+
const r = e?.userRole ?? "scope_user_user", i = new P(this.authClientId).requestedRole(r).flowType("popup");
|
|
1119
1113
|
e?.requested && i.requested(e.requested);
|
|
1120
1114
|
const o = i.build(), s = await this.requestAccess(o), { id: n, review_url: h } = X(s);
|
|
1121
1115
|
await chrome.tabs.create({ url: h });
|
|
@@ -1445,7 +1439,7 @@ const le = ["ggedphdcbekjlomjaidbajglgihbeaon"], ge = ["bjdjhiombmfbcoeojijpfckl
|
|
|
1445
1439
|
}, this.logger.debug("[BodhiExtClient] Injected auth token for authenticated request");
|
|
1446
1440
|
}
|
|
1447
1441
|
const d = chrome.runtime.connect(this.extensionId, {
|
|
1448
|
-
name:
|
|
1442
|
+
name: ee
|
|
1449
1443
|
});
|
|
1450
1444
|
this.activeStreamPorts.set(r, d);
|
|
1451
1445
|
const g = setTimeout(() => {
|
|
@@ -1459,7 +1453,7 @@ const le = ["ggedphdcbekjlomjaidbajglgihbeaon"], ge = ["bjdjhiombmfbcoeojijpfckl
|
|
|
1459
1453
|
}), this.cleanupStreamPort(r));
|
|
1460
1454
|
}, p.STREAM_TIMEOUT);
|
|
1461
1455
|
d.onMessage.addListener((E) => {
|
|
1462
|
-
if (
|
|
1456
|
+
if (te(E)) {
|
|
1463
1457
|
const I = E.response, q = I.body;
|
|
1464
1458
|
I.status >= 400 ? e.postMessage({
|
|
1465
1459
|
type: c.EXT2EXT_CLIENT_STREAM_API_ERROR,
|
|
@@ -1473,13 +1467,13 @@ const le = ["ggedphdcbekjlomjaidbajglgihbeaon"], ge = ["bjdjhiombmfbcoeojijpfckl
|
|
|
1473
1467
|
requestId: r,
|
|
1474
1468
|
response: I
|
|
1475
1469
|
});
|
|
1476
|
-
} else
|
|
1470
|
+
} else re(E) ? (this.logger.error(
|
|
1477
1471
|
`[BodhiExtClient] Stream API error for ${r}: ${E.response.status}`
|
|
1478
1472
|
), e.postMessage({
|
|
1479
1473
|
type: c.EXT2EXT_CLIENT_STREAM_API_ERROR,
|
|
1480
1474
|
requestId: r,
|
|
1481
1475
|
response: E.response
|
|
1482
|
-
})) :
|
|
1476
|
+
})) : se(E) && (this.logger.error(
|
|
1483
1477
|
`[BodhiExtClient] Stream error for ${r}:`,
|
|
1484
1478
|
E.error.message
|
|
1485
1479
|
), e.postMessage({
|
|
@@ -1581,7 +1575,7 @@ const le = ["ggedphdcbekjlomjaidbajglgihbeaon"], ge = ["bjdjhiombmfbcoeojijpfckl
|
|
|
1581
1575
|
async _doRefreshToken(e) {
|
|
1582
1576
|
this.logger.debug("Refreshing access token");
|
|
1583
1577
|
try {
|
|
1584
|
-
const t = await
|
|
1578
|
+
const t = await Y(
|
|
1585
1579
|
this.authEndpoints.token,
|
|
1586
1580
|
e,
|
|
1587
1581
|
this.authClientId
|
|
@@ -1632,18 +1626,18 @@ const le = ["ggedphdcbekjlomjaidbajglgihbeaon"], ge = ["bjdjhiombmfbcoeojijpfckl
|
|
|
1632
1626
|
};
|
|
1633
1627
|
p.STREAM_TIMEOUT = 6e4;
|
|
1634
1628
|
let A = p;
|
|
1635
|
-
const
|
|
1629
|
+
const pe = "production";
|
|
1636
1630
|
export {
|
|
1637
1631
|
A as BodhiExtClient,
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1632
|
+
ce as DEFAULT_API_TIMEOUT_MS,
|
|
1633
|
+
ie as DISCOVERY_ATTEMPTS,
|
|
1634
|
+
ae as DISCOVERY_ATTEMPT_TIMEOUT,
|
|
1635
|
+
ne as DISCOVERY_ATTEMPT_WAIT_MS,
|
|
1636
|
+
oe as DISCOVERY_TIMEOUT_MS,
|
|
1643
1637
|
m as EXT2EXT_CLIENT_ACTIONS,
|
|
1644
1638
|
c as EXT2EXT_CLIENT_MESSAGE_TYPES,
|
|
1645
1639
|
k as EXT2EXT_CLIENT_STREAM_PORT,
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1640
|
+
pe as EXT_BUILD_MODE,
|
|
1641
|
+
me as ExtUIClient,
|
|
1642
|
+
he as isExtClientApiError
|
|
1649
1643
|
};
|
package/dist/direct-client.d.ts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { DirectClientBase, AuthState, LoginOptions, LogLevel, StateChangeCallback } from '@bodhiapp/bodhi-js-core';
|
|
2
|
-
import { UserScope } from '@bodhiapp/ts-client';
|
|
3
2
|
/**
|
|
4
3
|
* Configuration for DirectExtClient
|
|
5
4
|
*/
|
|
6
5
|
export interface DirectExtClientConfig {
|
|
7
6
|
authClientId: string;
|
|
8
7
|
authServerUrl: string;
|
|
9
|
-
userRole: UserScope;
|
|
10
8
|
basePath: string;
|
|
11
9
|
logLevel: LogLevel;
|
|
12
10
|
apiTimeoutMs?: number;
|
package/dist/ext-client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AccessRequestStatusResponse, CreateAccessRequest, CreateAccessRequestResponse, PingResponse } from '@bodhiapp/ts-client';
|
|
2
|
-
import { IExtensionClient, Chat, Models, Embeddings,
|
|
2
|
+
import { IExtensionClient, Chat, Models, Embeddings, Mcps, AuthState, BackendServerState, ClientState, ExtensionState, InitParams, LoginOptions, LogLevel, StateChangeCallback } from '@bodhiapp/bodhi-js-core';
|
|
3
3
|
import { ApiResponse } from '@bodhiapp/bodhi-browser-types';
|
|
4
4
|
export type SerializedExt2ExtState = {
|
|
5
5
|
extensionId?: string;
|
|
@@ -41,7 +41,6 @@ export declare class ExtClient implements IExtensionClient {
|
|
|
41
41
|
private _chat;
|
|
42
42
|
private _models;
|
|
43
43
|
private _embeddings;
|
|
44
|
-
private _toolsets;
|
|
45
44
|
private _mcps;
|
|
46
45
|
constructor(config?: ExtClientConfig, onStateChange?: StateChangeCallback);
|
|
47
46
|
/**
|
|
@@ -106,7 +105,7 @@ export declare class ExtClient implements IExtensionClient {
|
|
|
106
105
|
sendApiRequest<TReq = void, TRes = unknown>(method: string, endpoint: string, body?: TReq, headers?: Record<string, string>, authenticated?: boolean): Promise<ApiResponse<TRes>>;
|
|
107
106
|
/**
|
|
108
107
|
* Login user via OAuth
|
|
109
|
-
* @param options - Optional login parameters
|
|
108
|
+
* @param options - Optional login parameters
|
|
110
109
|
* @throws ExtError if login fails
|
|
111
110
|
* @returns AuthState with login state and user info
|
|
112
111
|
*/
|
|
@@ -139,7 +138,6 @@ export declare class ExtClient implements IExtensionClient {
|
|
|
139
138
|
get chat(): Chat;
|
|
140
139
|
get models(): Models;
|
|
141
140
|
get embeddings(): Embeddings;
|
|
142
|
-
get toolsets(): Toolsets;
|
|
143
141
|
get mcps(): Mcps;
|
|
144
142
|
requestAccess(body: CreateAccessRequest): Promise<ApiResponse<CreateAccessRequestResponse>>;
|
|
145
143
|
getAccessRequestStatus(requestId: string): Promise<ApiResponse<AccessRequestStatusResponse>>;
|
package/dist/ext2ext-client.d.ts
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { AccessRequestStatusResponse, CreateAccessRequest, CreateAccessRequestResponse } from '@bodhiapp/ts-client';
|
|
2
2
|
import { ExtClientApiRequestMessage, ExtClientApiResponseMessage, ExtClientRequestMessage, ExtClientResponseMessage } from './messages';
|
|
3
3
|
import { ApiResponse } from '@bodhiapp/bodhi-browser-types';
|
|
4
|
-
import { AuthState, LoginOptions, LogLevel
|
|
4
|
+
import { AuthState, LoginOptions, LogLevel } from '@bodhiapp/bodhi-js-core';
|
|
5
5
|
export type ClientExtState = 'setup' | 'ready' | 'error';
|
|
6
6
|
export interface BodhiExtClientConfig {
|
|
7
7
|
authServerUrl?: string;
|
|
8
8
|
extensionId?: string;
|
|
9
9
|
logLevel?: LogLevel;
|
|
10
|
-
userRole?: UserScope;
|
|
11
10
|
attempts?: number;
|
|
12
11
|
attemptWaitMs?: number;
|
|
13
12
|
attemptTimeout?: number;
|
|
@@ -20,7 +19,6 @@ export declare class BodhiExtClient {
|
|
|
20
19
|
private isAuthenticating;
|
|
21
20
|
private authClientId;
|
|
22
21
|
private authServerUrl;
|
|
23
|
-
private userRole;
|
|
24
22
|
private logger;
|
|
25
23
|
private state;
|
|
26
24
|
private listenersInitialized;
|
package/dist/facade-client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BaseFacadeClient, Logger, LogLevel, StateChange, StateChangeCallback, UIClient
|
|
1
|
+
import { BaseFacadeClient, Logger, LogLevel, StateChange, StateChangeCallback, UIClient } from '@bodhiapp/bodhi-js-core';
|
|
2
2
|
import { DirectExtClient } from './direct-client';
|
|
3
3
|
import { ExtClient } from './ext-client';
|
|
4
4
|
/**
|
|
@@ -7,7 +7,6 @@ import { ExtClient } from './ext-client';
|
|
|
7
7
|
*/
|
|
8
8
|
export interface ExtUIClientConfig {
|
|
9
9
|
authServerUrl: string;
|
|
10
|
-
userRole: UserScope;
|
|
11
10
|
basePath: string;
|
|
12
11
|
logLevel: LogLevel;
|
|
13
12
|
apiTimeoutMs?: number;
|
|
@@ -26,7 +25,6 @@ export interface ExtUIClientConfig {
|
|
|
26
25
|
*/
|
|
27
26
|
export interface ExtUIClientParams {
|
|
28
27
|
authServerUrl?: string;
|
|
29
|
-
userRole?: UserScope;
|
|
30
28
|
basePath?: string;
|
|
31
29
|
logLevel?: LogLevel;
|
|
32
30
|
apiTimeoutMs?: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bodhiapp/bodhi-js-ext",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.30",
|
|
4
4
|
"description": "Extension SDK for Bodhi Browser - chrome.runtime communication",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/bodhi-ext.cjs.js",
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
"typecheck": "tsc --noEmit"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@bodhiapp/bodhi-browser-types": "0.0.
|
|
41
|
-
"@bodhiapp/bodhi-js-core": "0.0.
|
|
42
|
-
"@bodhiapp/ts-client": "0.1.
|
|
40
|
+
"@bodhiapp/bodhi-browser-types": "0.0.30",
|
|
41
|
+
"@bodhiapp/bodhi-js-core": "0.0.30",
|
|
42
|
+
"@bodhiapp/ts-client": "0.1.24"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@eslint/js": "^9.23.0",
|