@bodhiapp/bodhi-js-ext 0.0.43 → 0.0.44
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 +151 -143
- package/dist/ext2ext-client.d.ts +2 -1
- 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 a=require("@bodhiapp/bodhi-js-core"),T=require("@bodhiapp/bodhi-browser-types"),E={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",EXT2EXT_CLIENT_STREAM_TEXT_REQUEST:"EXT2EXT_CLIENT_STREAM_TEXT_REQUEST",EXT2EXT_CLIENT_STREAM_TEXT_START:"EXT2EXT_CLIENT_STREAM_TEXT_START",EXT2EXT_CLIENT_STREAM_TEXT_CHUNK:"EXT2EXT_CLIENT_STREAM_TEXT_CHUNK",EXT2EXT_CLIENT_STREAM_TEXT_DONE:"EXT2EXT_CLIENT_STREAM_TEXT_DONE",EXT2EXT_CLIENT_STREAM_TEXT_ERROR:"EXT2EXT_CLIENT_STREAM_TEXT_ERROR"},f="ext2ext-client-stream",y="ext2ext-client-stream-text",_={LOGIN:"login",LOGOUT:"logout",GET_AUTH_STATE:"getAuthState",DISCOVER_EXTENSION:"discoverBodhiExtension",GET_EXTENSION_ID:"get_extension_id",SET_EXTENSION_ID:"setExtensionId"},C=5e3,A=3,w=500,X=500,N=3e4;function O(m){return"error"in m}class P{async get(e){const r=(await chrome.storage.session.get(e))[e];return r!==void 0?String(r):null}async set(e){await chrome.storage.session.set(e)}async remove(e){await chrome.storage.session.remove(e)}}class M extends a.DirectClientBase{constructor(e,t){const r=a.createStoragePrefixWithNamespace(e.basePath,a.STORAGE_PREFIXES.EXT_DIRECT),i={authClientId:e.authClientId,authServerUrl:e.authServerUrl,storagePrefix:r,logLevel:e.logLevel,loggerPrefix:"DirectExtClient",apiTimeoutMs:e.apiTimeoutMs,storage:e.storage??new P,initialTokens:e.initialTokens};super(i,t)}async login(e){const t=await this.getAuthState();if(t.status==="authenticated"&&!e?.reauthorize)return t;const r=e?.userRole??"scope_user_user",i=this._getRedirectUri();e?.onProgress?.("requesting");const s=a.generateCodeVerifier(),o=await a.generateCodeChallenge(s),n=a.generateCodeVerifier();await this._storageSet({[this.storageKeys.CODE_VERIFIER]:s,[this.storageKeys.STATE]:n});const h=a.buildAuthorizeUrl(this.authEndpoints,{clientId:this.authClientId,redirectUri:i,scope:a.BASE_OAUTH_SCOPE,state:n,codeChallenge:o}),c=a.buildErrorUrl(i),u=new a.AccessRequestBuilder(this.authClientId).requestedRole(r);e?.requested&&u.requested(e.requested);const l=await this.requestAccess(u.build()),{review_url:d}=a.unwrapResponse(l),g=a.buildReviewUrl(d,h,c);e?.onProgress?.("reviewing");const p=await this.launchReview(g);return e?.onProgress?.("authenticating"),this.completeOAuthRedirect(p)}launchReview(e){return new Promise((t,r)=>{chrome.identity.launchWebAuthFlow({url:e,interactive:!0},i=>{if(chrome.runtime.lastError){r(chrome.runtime.lastError);return}if(!i){r(a.createOperationError("oauth_error","No redirect URL received"));return}t(i)})})}async completeOAuthRedirect(e){const t=new URL(e),r=t.searchParams.get("error");if(r)throw await this._storageRemove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),t.searchParams.get("error_source")==="bodhi"&&a.throwAccessRequestDenialError(r==="access_denied"?"denied":r),a.createOperationError("oauth_error",t.searchParams.get("error_description")??r);const i=t.searchParams.get("code"),s=t.searchParams.get("state"),o=await this._storageGet(this.storageKeys.STATE);if(!s||s!==o)throw await this._storageRemove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),a.createOperationError("oauth_error","State mismatch");if(!i)throw await this._storageRemove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),a.createOperationError("oauth_error","No authorization code");await this.exchangeCodeForTokens(i);const n=await this.getAuthState();if(n.status!=="authenticated")throw await this._storageRemove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),a.createOperationError("oauth_error","Login failed");return this.setAuthState(n),n}_getRedirectUri(){return chrome.identity.getRedirectURL("callback")}}class k{constructor(e={},t){this.state={type:"extension",extension:"not-initialized",extensionId:null,server:a.PENDING_EXTENSION_READY},this.extensionId=null,this.broadcastListenerActive=!1,this.config=e,this.logger=new a.Logger("ExtClient",e?.logLevel||"warn"),this.onStateChange=t??a.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===E.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=a.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??C,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 o={attempts:this.config.initParams?.extension?.attempts,attemptWaitMs:this.config.initParams?.extension?.attemptWaitMs,attemptTimeout:this.config.initParams?.extension?.attemptTimeout},n=await this.sendExtMessageWithTimeout(_.DISCOVER_EXTENSION,o,t);this.extensionId=n.extensionId,this.logger.info("Extension discovered:",this.extensionId)}this.setupBroadcastListener()}const i={type:"extension",extension:"ready",extensionId:this.extensionId,server:a.PENDING_EXTENSION_READY};let s=a.PENDING_EXTENSION_READY;if(e.testConnection)try{s=await this.getServerState(),this.logger.info("Server connectivity tested, state:",s.status)}catch(o){this.logger.error("Failed to get server state:",o),s=a.BACKEND_SERVER_NOT_REACHABLE}return this.setState({...i,server:s}),this.state}catch(i){this.logger.error("Failed to initialize extension:",i),this.extensionId=null;const s=a.createExtensionStateNotFound();return this.setState(s),this.state}}async sendExtMessageWithTimeout(e,t,r=1e4){const i=new Promise((s,o)=>setTimeout(()=>o(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:E.EXT2EXT_CLIENT_REQUEST,requestId:r,request:{action:e,params:t}});if(!i)throw a.createOperationError("extension_error","No response from background script");if(i.type!==E.EXT2EXT_CLIENT_RESPONSE)throw a.createOperationError("extension_error","Invalid response type from background script");const s=i.response;if(T.isExtError(s)){const o=s.error.type||"extension_error";throw a.createOperationError(o,s.error.message)}return s}catch(r){throw r instanceof a.BodhiError?r:a.createOperationError("extension_error",r instanceof Error?r.message:"Unknown error occurred")}}async sendRawApiMessage(e,t,r,i,s){const o=this.generateRequestId();return await chrome.runtime.sendMessage({type:E.EXT2EXT_CLIENT_API_REQUEST,requestId:o,request:{method:e,endpoint:t,body:r,headers:i,authenticated:s}})}async sendApiRequest(e,t,r,i,s){try{const o=new Promise((h,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)),n=await Promise.race([this.sendRawApiMessage(e,t,r,i,s),o]);if(O(n)){const h=n.error.type||"extension_error";throw new a.BodhiError(h,n.error.message)}return n.response}catch(o){if(o instanceof a.BodhiError)throw o;const n=o instanceof Error?o.message:String(o);throw new a.BodhiError("network_error",n)}}async login(e){return new Promise((t,r)=>{const i=async s=>{if(s&&typeof s=="object"&&"type"in s&&s.type==="EXT2EXT_CLIENT_BROADCAST"&&"event"in s&&s.event==="authStateChanged"){chrome.runtime.onMessage.removeListener(i);try{const o=await this.getAuthState();if(a.isAuthError(o)){r(a.createOperationError("auth_error",`Login failed: ${o.error?.message}`));return}if(o.status!=="authenticated"){r(a.createOperationError("auth_error","Login failed: User is not logged in"));return}this.setAuthState(o),t(o)}catch(o){r(o)}}};chrome.runtime.onMessage.addListener(i),this.sendExtRequest(_.LOGIN,e).catch(s=>{chrome.runtime.onMessage.removeListener(i),r(s)})})}async logout(){await this.sendExtRequest(_.LOGOUT);const e={status:"unauthenticated",user:null,accessToken:null,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!1};return this.setAuthState(e),e}async getAuthState(){return this.isClientInitialized()?(await this.sendExtRequest(_.GET_AUTH_STATE)).authState:a.INITIAL_AUTH_STATE}async pingApi(){return this.sendApiRequest("GET","/ping")}async getServerState(){let e;try{e=await this.sendApiRequest("GET","/bodhi/v1/info")}catch(s){const o=s instanceof Error?s.message:"Connection failed",n=s instanceof a.BodhiError?s.code:"extension_error";return{status:"not-reachable",version:null,error:{message:o,type:n}}}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,s=!0){const o=this.generateRequestId();this.logger.debug("Starting stream",{method:e,endpoint:t,requestId:o});const n=chrome.runtime.connect({name:f}),c=new ReadableStream({start:u=>{n.onMessage.addListener(l=>{if(l.requestId===o)switch(l.type){case E.EXT2EXT_CLIENT_STREAM_DONE:this.logger.debug("Stream complete",{requestId:o}),u.close(),n.disconnect();break;case E.EXT2EXT_CLIENT_STREAM_ERROR:this.logger.error("Stream error",{requestId:o,error:JSON.stringify(l.error)}),u.error(new a.BodhiError("extension_error",l.error.message)),n.disconnect();break;case E.EXT2EXT_CLIENT_STREAM_API_ERROR:{const d=l;this.logger.error("Stream API error",{requestId:o,error:d.response.body?.error}),u.error(new a.BodhiApiError(d.response.status,d.response.body,d.response.body?.error?.message||"API error")),n.disconnect();break}case E.EXT2EXT_CLIENT_STREAM_CHUNK:{const d=l;T.isApiSuccessResponse(d.response)&&u.enqueue(d.response.body);break}}}),n.onDisconnect.addListener(()=>{this.logger.debug("Port disconnected",{requestId:o});try{u.error(new a.BodhiError("connection_closed","Connection closed unexpectedly"))}catch{}}),n.postMessage({type:E.EXT2EXT_CLIENT_STREAM_REQUEST,requestId:o,request:{method:e,endpoint:t,body:r,headers:i,authenticated:s}})}}).getReader();try{for(;;){const{done:u,value:l}=await c.read();if(u){this.logger.debug("Stream iteration complete");break}yield l}}finally{c.releaseLock()}}async streamText(e,t,r,i,s=!0){const o=this.generateRequestId();return new Promise((n,h)=>{let c,u=!1;const l=new ReadableStream({start:g=>{c=g}}),d=chrome.runtime.connect({name:y});d.onMessage.addListener(g=>{if(g.requestId===o)switch(g.type){case E.EXT2EXT_CLIENT_STREAM_TEXT_START:{u=!0;async function*p(v){const x=v.getReader();try{for(;;){const{done:b,value:L}=await x.read();if(b)break;yield L}}finally{x.releaseLock()}}n({status:g.status,headers:g.headers,body:p(l)});break}case E.EXT2EXT_CLIENT_STREAM_TEXT_CHUNK:c.enqueue(g.chunk);break;case E.EXT2EXT_CLIENT_STREAM_TEXT_DONE:c.close(),d.disconnect();break;case E.EXT2EXT_CLIENT_STREAM_TEXT_ERROR:{const p=a.createOperationError("extension_error",g.error.message);u?c.error(p):h(p),d.disconnect();break}}}),d.onDisconnect.addListener(()=>{if(!u)h(a.createOperationError("extension_error","Connection closed unexpectedly"));else try{c.error(a.createOperationError("extension_error","Connection closed unexpectedly"))}catch{}}),d.postMessage({type:E.EXT2EXT_CLIENT_STREAM_TEXT_REQUEST,requestId:o,request:{method:e,endpoint:t,body:r,headers:i,authenticated:s}})})}get chat(){return this._chat??=new a.Chat(this)}get models(){return this._models??=new a.Models(this)}get embeddings(){return this._embeddings??=new a.Embeddings(this)}get mcps(){return this._mcps??=new a.Mcps(this)}async requestAccess(e){return this.sendApiRequest("POST","/bodhi/v1/apps/request-access",e,{},!1)}serialize(){return{extensionId:this.extensionId??void 0}}async debug(){return{type:"ExtClient",state:this.state,authState:await this.getAuthState()}}}class U extends a.BaseFacadeClient{constructor(e,t,r){const i=t||{},s={basePath:i.basePath||"/",authServerUrl:i.authServerUrl||"https://id.getbodhi.app/realms/bodhi",logLevel:i.logLevel||"warn",apiTimeoutMs:i.apiTimeoutMs,storage:i.storage,initialTokens:i.initialTokens,initParams:i.initParams};super(e,s,r)}createLogger(e){return new a.Logger("ExtUIClient",e.logLevel)}createStoragePrefix(e){return a.createStoragePrefixWithNamespace(e.basePath,a.STORAGE_PREFIXES.EXT)}createExtClient(e,t){return new k({authClientId:this.authClientId,logLevel:e.logLevel,apiTimeoutMs:e.apiTimeoutMs,initParams:e.initParams},t)}createDirectClient(e,t,r){return new M({authClientId:e,authServerUrl:t.authServerUrl,logLevel:t.logLevel,basePath:t.basePath,apiTimeoutMs:t.apiTimeoutMs,storage:t.storage,initialTokens:t.initialTokens},r)}}const D=["ggedphdcbekjlomjaidbajglgihbeaon"],B=["bjdjhiombmfbcoeojijpfckljjghmjbf"],R="production",S=class S{constructor(e,t){this.isAuthenticating=!1,this.isReauthorizing=!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 a.Logger("BodhiExtClient",t?.logLevel||"warn"),this.attempts=t?.attempts??A,this.attemptWaitMs=t?.attemptWaitMs??w,this.attemptTimeout=t?.attemptTimeout??X,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),S.base64UrlEncode(e.buffer)}static async generateCodeChallenge(e){const r=new TextEncoder().encode(e),i=await crypto.subtle.digest("SHA-256",r);return S.base64UrlEncode(i)}getState(){return this.state}getExtensionIdsForEnvironment(){const t=R!=="production"?D:B;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 s={type:T.MESSAGE_TYPES.EXT_REQUEST,requestId:crypto.randomUUID(),request:{action:"get_extension_id"}};this.logger.debug(`[Ext2Ext/Discovery] Sending message to ${e}:`,s),chrome.runtime.sendMessage(e,s,o=>{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}:`,o);const n=o;n&&n.type===T.MESSAGE_TYPES.EXT_RESPONSE?(this.logger.debug(`[Ext2Ext/Discovery] ✓ Extension ${e} responded`),t(!0)):(this.logger.error(`[Ext2Ext/Discovery] Invalid response from ${e}:`,o),r(new Error("Invalid response")))})}catch(s){this.logger.error(`[Ext2Ext/Discovery] Exception pinging ${e}:`,s),clearTimeout(i),r(s)}})}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 s=this.getExtensionIdsForEnvironment();this.logger.debug(`[Ext2Ext/Discovery] Will try ${s.length} extension(s):`,s);for(const h of s){for(let c=1;c<=t;c++){this.logger.debug(`[Ext2Ext/Discovery] Trying ${h} - attempt ${c}/${t}`);try{return await this.pingExtension(h),this.logger.info(`[Ext2Ext/Discovery] ✓ Found: ${h} on attempt ${c}`),{success:!0,extensionId:h}}catch(u){this.logger.debug(`[Ext2Ext/Discovery] Attempt ${c} failed for ${h}: ${u instanceof Error?u.message:"Unknown error"}`),c<t&&await this.sleep(r)}}this.logger.warn(`[Ext2Ext/Discovery] ✗ Not found: ${h} after ${t} attempts`)}const o=s.join(", "),n=`Extension not found. Tried ${s.length} IDs with ${t} attempts each: ${o}`;return this.logger.error(`[Ext2Ext/Discovery] ${n}`),{success:!1,error:n}}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!==E.EXT2EXT_CLIENT_REQUEST&&e.type!==E.EXT2EXT_CLIENT_API_REQUEST?!1:this.state!=="ready"&&!(e.type===E.EXT2EXT_CLIENT_REQUEST&&e.request.action===_.DISCOVER_EXTENSION)?(e.type===E.EXT2EXT_CLIENT_REQUEST?r({type:E.EXT2EXT_CLIENT_RESPONSE,requestId:e.requestId,response:{error:{message:this.createErrorClientNotInitialized(e),type:"NOT_INITIALIZED"}}}):r({type:E.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)),this.registerStreamPortListener(f,E.EXT2EXT_CLIENT_STREAM_REQUEST,E.EXT2EXT_CLIENT_STREAM_ERROR,(e,t)=>this.handleStreamRequest(e,t)),this.registerStreamPortListener(y,E.EXT2EXT_CLIENT_STREAM_TEXT_REQUEST,E.EXT2EXT_CLIENT_STREAM_TEXT_ERROR,(e,t)=>this.handleStreamTextRequest(e,t)),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:E.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 s=await this._getAccessTokenRaw();if(!s)return{type:E.EXT2EXT_CLIENT_API_RESPONSE,requestId:t,error:{message:"Not authenticated. Please log in first.",type:"auth_error"}};r={...r,Authorization:`Bearer ${s}`},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:E.EXT2EXT_CLIENT_API_RESPONSE,requestId:t,response:i}}catch(r){return this.logger.error("[BodhiExtClient] API request failed:",r),{type:E.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:s}=r;this.logger.debug(`[BodhiExtClient] Handling action: ${i}`);try{let o={};switch(i){case _.DISCOVER_EXTENSION:{const n=s;await this.init(n),this.logger.info("[BodhiExtClient] Discovery successful:",{extensionId:this.extensionId,environment:R}),o={extensionId:this.extensionId,environment:R};break}case _.SET_EXTENSION_ID:{const{extensionId:n}=s;this.extensionId=n,this.state="ready",this.logger.info("[BodhiExtClient] Extension ID set:",{extensionId:n}),o={success:!0};break}case _.GET_EXTENSION_ID:{const n=await this.sendExtRequestRaw(T.EXT_ACTIONS.GET_EXTENSION_ID,s);return T.isExtError(n.response)?{type:E.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:{error:{message:n.response.error.message||`Extension request failed to get extension ID: ${JSON.stringify(n.response)}`,type:n.response.error.type}}}:{type:E.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:n.response}}case _.LOGIN:{const n=s;await this.login(n),this.broadcastAuthStateChange();break}case _.LOGOUT:await this.logout(),this.broadcastAuthStateChange();break;case _.GET_AUTH_STATE:o={authState:await this.getAuthState()};break;default:return{type:E.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:{error:{message:`Unknown action: ${i}`,type:"UNKNOWN_ACTION"}}}}return{type:E.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:o}}catch(o){return this.logger.error("[BodhiExtClient] Unexpected error:",o),{type:E.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:{error:{message:o instanceof Error?o.message:`Unexpected error: ${JSON.stringify(o)}`}}}}}async handleAction(e){switch(e.type){case E.EXT2EXT_CLIENT_API_REQUEST:return this.handleApiRequest(e);case E.EXT2EXT_CLIENT_REQUEST:return this.handleExtClientRequest(e);default:{const{requestId:t}=e;return this.logger.error("[BodhiExtClient] Unknown message type:",e.type),{type:E.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"&&!e?.reauthorize)){this.isAuthenticating=!0,this.isReauthorizing=e?.reauthorize??!1;try{if(!this.extensionId)throw new Error("Extension not discovered. Please detect Bodhi extension before login.");const r=e?.userRole??"scope_user_user",i=chrome.identity.getRedirectURL("callback"),s=S.generateCodeVerifier(),o=await S.generateCodeChallenge(s),n=S.generateCodeVerifier();await chrome.storage.session.set({codeVerifier:s,state:n,authInProgress:!0});const h=a.buildAuthorizeUrl(this.authEndpoints,{clientId:this.authClientId,redirectUri:i,scope:a.BASE_OAUTH_SCOPE,state:n,codeChallenge:o}),c=a.buildErrorUrl(i),u=new a.AccessRequestBuilder(this.authClientId).requestedRole(r);e?.requested&&u.requested(e.requested);const l=await this.requestAccess(u.build()),{review_url:d}=a.unwrapResponse(l),g=a.buildReviewUrl(d,h,c),p=await this.launchReview(g);await this.completeOAuthRedirect(p)}finally{this.isAuthenticating=!1,this.isReauthorizing=!1}}}async exchangeCodeForTokens(e){if((await this.getAuthState()).status==="authenticated"&&!this.isReauthorizing)return;const{codeVerifier:r}=await chrome.storage.session.get("codeVerifier"),i=chrome.identity.getRedirectURL("callback"),s=await fetch(this.authEndpoints.token,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",code:e,redirect_uri:i,client_id:this.authClientId,code_verifier:r})});if(!s.ok){const n=await s.text();throw new Error(`Token exchange failed: ${s.status} ${n}`)}const o=await s.json();await this.storeTokens({accessToken:o.access_token,refreshToken:o.refresh_token,idToken:o.id_token,expiresIn:o.expires_in})}async getAuthState(){const e=await this._getAccessTokenRaw();if(!e)return{status:"unauthenticated",user:null,accessToken:null,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!1};try{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,refreshToken:null,expiresAt:null,isTokenRefresh:!1}}catch(t){return this.logger.error("Failed to parse token:",t),{status:"unauthenticated",user:null,accessToken:null,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!1}}}async 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)}launchReview(e){return new Promise((t,r)=>{chrome.identity.launchWebAuthFlow({url:e,interactive:!0},async i=>{if(await chrome.storage.session.set({authInProgress:!1}),chrome.runtime.lastError){await chrome.storage.session.remove(["codeVerifier","state"]),r(chrome.runtime.lastError);return}if(!i){await chrome.storage.session.remove(["codeVerifier","state"]),r(a.createOperationError("oauth_error","No redirect URL received"));return}t(i)})})}async completeOAuthRedirect(e){const t=new URL(e),r=t.searchParams.get("error");if(r)throw await chrome.storage.session.remove(["codeVerifier","state"]),t.searchParams.get("error_source")==="bodhi"&&a.throwAccessRequestDenialError(r==="access_denied"?"denied":r),a.createOperationError("oauth_error",t.searchParams.get("error_description")??r);const i=t.searchParams.get("code"),s=t.searchParams.get("state"),{state:o}=await chrome.storage.session.get("state");if(!s||s!==o)throw await chrome.storage.session.remove(["codeVerifier","state"]),a.createOperationError("oauth_error","State mismatch");if(!i)throw await chrome.storage.session.remove(["codeVerifier","state"]),a.createOperationError("oauth_error","No authorization code");if(await this.exchangeCodeForTokens(i),await chrome.storage.session.remove(["codeVerifier","state"]),(await this.getAuthState()).status!=="authenticated")throw a.createOperationError("oauth_error","Login failed")}async sendExtRequest(e,t){const r=await this.sendExtRequestRaw(e,t);if(T.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 a.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 s=crypto.randomUUID(),o={type:T.MESSAGE_TYPES.API_REQUEST,requestId:s,request:{method:e,endpoint:t,body:r,headers:i}};return this.logger.debug(`[BodhiExtClient] Request ID: ${s}, Extension: ${this.extensionId}`),new Promise((n,h)=>{try{chrome.runtime.sendMessage(this.extensionId,o,c=>{if(chrome.runtime.lastError){this.logger.error(`[BodhiExtClient] Chrome runtime error for request ${s}:`,chrome.runtime.lastError),h(new a.BodhiError("extension_error",chrome.runtime.lastError.message??"Chrome runtime error"));return}if(this.logger.debug(`[BodhiExtClient] Response for request ${s}:`,c),!c){this.logger.error(`[BodhiExtClient] No response received for request ${s}`),h(new a.BodhiError("extension_error","No response from extension"));return}c.type===T.MESSAGE_TYPES.API_RESPONSE&&c.requestId===s?"error"in c?(this.logger.error(`[BodhiExtClient] API error for ${s}:`,c.error),h(new a.BodhiError(c.error.type||"extension_error",c.error.message))):(this.logger.debug(`[BodhiExtClient] ✓ Valid API_RESPONSE for ${s}`),n(c.response)):(this.logger.error(`[BodhiExtClient] Invalid response format for ${s}:`,c),h(new a.BodhiError("extension_error","Invalid response format")))})}catch(c){this.logger.error(`[BodhiExtClient] Exception sending message for ${s}:`,c),h(c)}})}async sendExtRequestRaw(e,t){if(!this.extensionId)throw new a.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:T.MESSAGE_TYPES.EXT_REQUEST,requestId:r,request:{action:e,params:t}};return this.logger.debug(`[BodhiExtClient] Request ID: ${r}, Extension: ${this.extensionId}`),new Promise((s,o)=>{try{chrome.runtime.sendMessage(this.extensionId,i,n=>{if(chrome.runtime.lastError){this.logger.error(`[BodhiExtClient] Chrome runtime error for request ${r}:`,chrome.runtime.lastError),o(new Error(chrome.runtime.lastError.message));return}if(this.logger.debug(`[BodhiExtClient] Response for request ${r}:`,n),!n){this.logger.error(`[BodhiExtClient] No response received for request ${r}`),o(new Error("No response from extension"));return}n.type===T.MESSAGE_TYPES.EXT_RESPONSE&&n.requestId===r?(this.logger.debug(`[BodhiExtClient] ✓ Valid EXT_RESPONSE for ${r}`),s(n)):(this.logger.error(`[BodhiExtClient] Invalid response format for ${r}:`,n),o(new a.BodhiError("extension_error","Invalid response format")))})}catch(n){this.logger.error(`[BodhiExtClient] Exception sending message for ${r}:`,n),o(n)}})}registerStreamPortListener(e,t,r,i){chrome.runtime.onConnect.addListener(s=>{s.name===e&&(this.logger.info(`[BodhiExtClient] Port connected: ${e}`),s.onMessage.addListener(async o=>{if(o.type!==t){this.logger.warn(`[BodhiExtClient] Unknown message type on ${e}:`,o.type),s.postMessage({type:r,requestId:o.requestId,error:{message:"Unknown message type",type:"extension_error"}});return}await i(s,o)}),s.onDisconnect.addListener(()=>{this.logger.info(`[BodhiExtClient] Port disconnected: ${e}`)}))})}async handleGenericStreamRelay(e,t,r,i,s,o,n){if(this.logger.debug("[BodhiExtClient] Processing stream relay:",{requestId:t,method:r.method,endpoint:r.endpoint,bodhiPortName:i}),!this.extensionId){e.postMessage({type:o,requestId:t,error:{message:"Client not initialized (no extensionId)",type:"extension_error"}});return}try{let h={...r.headers};if(r.authenticated!==!1){const d=await this._getAccessTokenRaw();if(!d){e.postMessage({type:o,requestId:t,error:{message:"Not authenticated. Please log in first.",type:"extension_error"}});return}h={...h,Authorization:`Bearer ${d}`},this.logger.debug("[BodhiExtClient] Injected auth token for stream relay")}const c=chrome.runtime.connect(this.extensionId,{name:i});this.activeStreamPorts.set(t,c);const u=setTimeout(()=>{this.activeStreamPorts.has(t)&&(this.logger.error(`[BodhiExtClient] Stream timeout for ${t}`),e.postMessage({type:o,requestId:t,error:{message:"Stream request timed out",type:"timeout_error"}}),this.cleanupStreamPort(t))},S.STREAM_TIMEOUT);c.onMessage.addListener(d=>{n(d,e,t)&&(clearTimeout(u),this.cleanupStreamPort(t))}),c.onDisconnect.addListener(()=>{clearTimeout(u),this.activeStreamPorts.has(t)&&(this.logger.error(`[BodhiExtClient] Bodhi port disconnected for ${t}`),e.postMessage({type:o,requestId:t,error:{message:"Connection to Bodhi extension closed unexpectedly",type:"network_error"}}),this.activeStreamPorts.delete(t))});const l={type:s,requestId:t,request:{method:r.method,endpoint:r.endpoint,body:r.body,headers:h}};this.logger.debug("[BodhiExtClient] Sending stream request to bodhi port:",l),c.postMessage(l)}catch(h){const c=h;this.logger.error("[BodhiExtClient] Stream relay error:",JSON.stringify(c.message)),e.postMessage({type:o,requestId:t,error:{message:`uncaught error: ${JSON.stringify({error:c,message:c.message})}`,type:"extension_error"}})}}cleanupStreamPort(e){const t=this.activeStreamPorts.get(e);if(t){try{t.disconnect()}catch{}this.activeStreamPorts.delete(e)}}async handleStreamRequest(e,t){const{requestId:r,request:i}=t;await this.handleGenericStreamRelay(e,r,i,T.BODHI_STREAM_PORT,T.MESSAGE_TYPES.STREAM_REQUEST,E.EXT2EXT_CLIENT_STREAM_ERROR,(s,o,n)=>{if(T.isStreamChunk(s)){const h=s.response,c=h.body;return h.status>=400?(o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_API_ERROR,requestId:n,response:h}),!1):c?.done?(o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_DONE,requestId:n}),this.logger.info(`[BodhiExtClient] Stream complete for ${n}`),!0):(o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_CHUNK,requestId:n,response:h}),!1)}else{if(T.isStreamApiError(s))return this.logger.error(`[BodhiExtClient] Stream API error for ${n}: ${s.response.status}`),o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_API_ERROR,requestId:n,response:s.response}),!1;if(T.isStreamError(s))return this.logger.error(`[BodhiExtClient] Stream error for ${n}:`,s.error.message),o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_ERROR,requestId:n,error:{message:`stream error: ${JSON.stringify(s)}`,type:"extension_error"}}),!0}return!1})}async handleStreamTextRequest(e,t){const{requestId:r,request:i}=t;await this.handleGenericStreamRelay(e,r,i,T.BODHI_STREAM_TEXT_PORT,T.MESSAGE_TYPES.STREAM_TEXT_REQUEST,E.EXT2EXT_CLIENT_STREAM_TEXT_ERROR,(s,o,n)=>{switch(s.type){case T.MESSAGE_TYPES.STREAM_TEXT_START:return o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_TEXT_START,requestId:n,status:s.status,headers:s.headers}),!1;case T.MESSAGE_TYPES.STREAM_TEXT_CHUNK:return o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_TEXT_CHUNK,requestId:n,chunk:s.chunk}),!1;case T.MESSAGE_TYPES.STREAM_TEXT_DONE:return this.logger.info(`[BodhiExtClient] Stream text complete for ${n}`),o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_TEXT_DONE,requestId:n}),!0;case T.MESSAGE_TYPES.STREAM_TEXT_ERROR:return this.logger.error(`[BodhiExtClient] Stream text error for ${n}:`,s.error.message),o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_TEXT_ERROR,requestId:n,error:s.error}),!0}return!1})}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 a.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"),a.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(s=>"%"+("00"+s.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)}`}};S.STREAM_TIMEOUT=6e4;let I=S;const q="production";Object.defineProperty(exports,"InMemoryStorage",{enumerable:!0,get:()=>a.InMemoryStorage});exports.BodhiExtClient=I;exports.ChromeSessionStorageAdapter=P;exports.DEFAULT_API_TIMEOUT_MS=N;exports.DISCOVERY_ATTEMPTS=A;exports.DISCOVERY_ATTEMPT_TIMEOUT=X;exports.DISCOVERY_ATTEMPT_WAIT_MS=w;exports.DISCOVERY_TIMEOUT_MS=C;exports.EXT2EXT_CLIENT_ACTIONS=_;exports.EXT2EXT_CLIENT_MESSAGE_TYPES=E;exports.EXT2EXT_CLIENT_STREAM_PORT=f;exports.EXT2EXT_CLIENT_STREAM_TEXT_PORT=y;exports.EXT_BUILD_MODE=q;exports.ExtUIClient=U;exports.isExtClientApiError=O;
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const a=require("@bodhiapp/bodhi-js-core"),l=require("@bodhiapp/bodhi-browser-types"),E={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",EXT2EXT_CLIENT_STREAM_TEXT_REQUEST:"EXT2EXT_CLIENT_STREAM_TEXT_REQUEST",EXT2EXT_CLIENT_STREAM_TEXT_START:"EXT2EXT_CLIENT_STREAM_TEXT_START",EXT2EXT_CLIENT_STREAM_TEXT_CHUNK:"EXT2EXT_CLIENT_STREAM_TEXT_CHUNK",EXT2EXT_CLIENT_STREAM_TEXT_DONE:"EXT2EXT_CLIENT_STREAM_TEXT_DONE",EXT2EXT_CLIENT_STREAM_TEXT_ERROR:"EXT2EXT_CLIENT_STREAM_TEXT_ERROR"},f="ext2ext-client-stream",y="ext2ext-client-stream-text",_={LOGIN:"login",LOGOUT:"logout",GET_AUTH_STATE:"getAuthState",DISCOVER_EXTENSION:"discoverBodhiExtension",GET_EXTENSION_ID:"get_extension_id",SET_EXTENSION_ID:"setExtensionId"},C=5e3,A=3,w=500,X=500,N=3e4;function O(m){return"error"in m}class P{async get(e){const r=(await chrome.storage.session.get(e))[e];return r!==void 0?String(r):null}async set(e){await chrome.storage.session.set(e)}async remove(e){await chrome.storage.session.remove(e)}}class M extends a.DirectClientBase{constructor(e,t){const r=a.createStoragePrefixWithNamespace(e.basePath,a.STORAGE_PREFIXES.EXT_DIRECT),n={authClientId:e.authClientId,authServerUrl:e.authServerUrl,storagePrefix:r,logLevel:e.logLevel,loggerPrefix:"DirectExtClient",apiTimeoutMs:e.apiTimeoutMs,storage:e.storage??new P,initialTokens:e.initialTokens};super(n,t)}async login(e){const t=await this.getAuthState();if(t.status==="authenticated"&&!e?.exchange)return t;const r=e?.userRole??"scope_user_user",n=this._getRedirectUri();e?.onProgress?.("requesting");const s=a.generateCodeVerifier(),o=await a.generateCodeChallenge(s),i=a.generateCodeVerifier();await this._storageSet({[this.storageKeys.CODE_VERIFIER]:s,[this.storageKeys.STATE]:i});const u=a.buildAuthorizeUrl(this.authEndpoints,{clientId:this.authClientId,redirectUri:n,scope:a.BASE_OAUTH_SCOPE,state:i,codeChallenge:o}),c=a.buildErrorUrl(n),h=new a.AccessRequestBuilder(this.authClientId).requestedRole(r);e?.requested&&h.requested(e.requested),e?.exchange&&h.exchange(!0);const d=await this.requestAccess(h.build()),{review_url:T}=a.unwrapResponse(d),g=a.buildReviewUrl(T,u,c);e?.onProgress?.("reviewing");const p=await this.launchReview(g);return e?.onProgress?.("authenticating"),this.completeOAuthRedirect(p)}launchReview(e){return new Promise((t,r)=>{chrome.identity.launchWebAuthFlow({url:e,interactive:!0},n=>{if(chrome.runtime.lastError){r(chrome.runtime.lastError);return}if(!n){r(a.createOperationError("oauth_error","No redirect URL received"));return}t(n)})})}async completeOAuthRedirect(e){const t=new URL(e),r=t.searchParams.get("error");if(r)throw await this._storageRemove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),t.searchParams.get("error_source")==="bodhi"&&a.throwAccessRequestDenialError(r==="access_denied"?"denied":r),a.createOperationError("oauth_error",t.searchParams.get("error_description")??r);const n=t.searchParams.get("code"),s=t.searchParams.get("state"),o=await this._storageGet(this.storageKeys.STATE);if(!s||s!==o)throw await this._storageRemove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),a.createOperationError("oauth_error","State mismatch");if(!n)throw await this._storageRemove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),a.createOperationError("oauth_error","No authorization code");await this.exchangeCodeForTokens(n);const i=await this.getAuthState();if(i.status!=="authenticated")throw await this._storageRemove([this.storageKeys.CODE_VERIFIER,this.storageKeys.STATE]),a.createOperationError("oauth_error","Login failed");return this.setAuthState(i),i}_getRedirectUri(){return chrome.identity.getRedirectURL("callback")}}class k{constructor(e={},t){this.state={type:"extension",extension:"not-initialized",extensionId:null,server:a.PENDING_EXTENSION_READY},this.extensionId=null,this.broadcastListenerActive=!1,this.config=e,this.logger=new a.Logger("ExtClient",e?.logLevel||"warn"),this.onStateChange=t??a.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===E.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 n=a.createExtensionStateNotInitialized();return this.setState(n),n}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??C,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 o={attempts:this.config.initParams?.extension?.attempts,attemptWaitMs:this.config.initParams?.extension?.attemptWaitMs,attemptTimeout:this.config.initParams?.extension?.attemptTimeout},i=await this.sendExtMessageWithTimeout(_.DISCOVER_EXTENSION,o,t);this.extensionId=i.extensionId,this.logger.info("Extension discovered:",this.extensionId)}this.setupBroadcastListener()}const n={type:"extension",extension:"ready",extensionId:this.extensionId,server:a.PENDING_EXTENSION_READY};let s=a.PENDING_EXTENSION_READY;if(e.testConnection)try{s=await this.getServerState(),this.logger.info("Server connectivity tested, state:",s.status)}catch(o){this.logger.error("Failed to get server state:",o),s=a.BACKEND_SERVER_NOT_REACHABLE}return this.setState({...n,server:s}),this.state}catch(n){this.logger.error("Failed to initialize extension:",n),this.extensionId=null;const s=a.createExtensionStateNotFound();return this.setState(s),this.state}}async sendExtMessageWithTimeout(e,t,r=1e4){const n=new Promise((s,o)=>setTimeout(()=>o(new Error("Timeout")),r));return Promise.race([this.sendExtRequest(e,t),n])}async sendExtRequest(e,t){try{const r=this.generateRequestId(),n=await chrome.runtime.sendMessage({type:E.EXT2EXT_CLIENT_REQUEST,requestId:r,request:{action:e,params:t}});if(!n)throw a.createOperationError("extension_error","No response from background script");if(n.type!==E.EXT2EXT_CLIENT_RESPONSE)throw a.createOperationError("extension_error","Invalid response type from background script");const s=n.response;if(l.isExtError(s)){const o=s.error.type||"extension_error";throw a.createOperationError(o,s.error.message)}return s}catch(r){throw r instanceof a.BodhiError?r:a.createOperationError("extension_error",r instanceof Error?r.message:"Unknown error occurred")}}async sendRawApiMessage(e,t,r,n,s){const o=this.generateRequestId();return await chrome.runtime.sendMessage({type:E.EXT2EXT_CLIENT_API_REQUEST,requestId:o,request:{method:e,endpoint:t,body:r,headers:n,authenticated:s}})}async sendApiRequest(e,t,r,n,s){try{const o=new Promise((u,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)),i=await Promise.race([this.sendRawApiMessage(e,t,r,n,s),o]);if(O(i)){const u=i.error.type||"extension_error";throw new a.BodhiError(u,i.error.message)}return i.response}catch(o){if(o instanceof a.BodhiError)throw o;const i=o instanceof Error?o.message:String(o);throw new a.BodhiError("network_error",i)}}async login(e){return new Promise((t,r)=>{const n=async s=>{if(s&&typeof s=="object"&&"type"in s&&s.type==="EXT2EXT_CLIENT_BROADCAST"&&"event"in s&&s.event==="authStateChanged"){chrome.runtime.onMessage.removeListener(n);try{const o=await this.getAuthState();if(a.isAuthError(o)){r(a.createOperationError("auth_error",`Login failed: ${o.error?.message}`));return}if(o.status!=="authenticated"){r(a.createOperationError("auth_error","Login failed: User is not logged in"));return}this.setAuthState(o),t(o)}catch(o){r(o)}}};chrome.runtime.onMessage.addListener(n),this.sendExtRequest(_.LOGIN,e).catch(s=>{chrome.runtime.onMessage.removeListener(n),r(s)})})}async logout(){await this.sendExtRequest(_.LOGOUT);const e={status:"unauthenticated",user:null,accessToken:null,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!1};return this.setAuthState(e),e}async getAuthState(){return this.isClientInitialized()?(await this.sendExtRequest(_.GET_AUTH_STATE)).authState:a.INITIAL_AUTH_STATE}async pingApi(){return this.sendApiRequest("GET","/ping")}async getServerState(){let e;try{e=await this.sendApiRequest("GET","/bodhi/v1/info")}catch(s){const o=s instanceof Error?s.message:"Connection failed",i=s instanceof a.BodhiError?s.code:"extension_error";return{status:"not-reachable",version:null,error:{message:o,type:i}}}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",n={deployment:t.deployment??null,client_id:t.client_id??null};switch(t.status){case"ready":return{status:"ready",version:r,error:null,...n};case"setup":return{status:"setup",version:r,error:t.error?{message:t.error.message,type:t.error.type}:{message:"Setup required",type:"extension_error"},...n};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"},...n};case"error":return{status:"error",version:r,error:t.error?{message:t.error.message,type:t.error.type}:{message:"Server error",type:"extension_error"},...n};default:return{status:"not-reachable",version:null,error:{message:"Unknown server status",type:"extension_error"}}}}async*stream(e,t,r,n,s=!0){const o=this.generateRequestId();this.logger.debug("Starting stream",{method:e,endpoint:t,requestId:o});const i=chrome.runtime.connect({name:f}),c=new ReadableStream({start:h=>{i.onMessage.addListener(d=>{if(d.requestId===o)switch(d.type){case E.EXT2EXT_CLIENT_STREAM_DONE:this.logger.debug("Stream complete",{requestId:o}),h.close(),i.disconnect();break;case E.EXT2EXT_CLIENT_STREAM_ERROR:this.logger.error("Stream error",{requestId:o,error:JSON.stringify(d.error)}),h.error(new a.BodhiError("extension_error",d.error.message)),i.disconnect();break;case E.EXT2EXT_CLIENT_STREAM_API_ERROR:{const T=d;this.logger.error("Stream API error",{requestId:o,error:T.response.body?.error}),h.error(new a.BodhiApiError(T.response.status,T.response.body,T.response.body?.error?.message||"API error")),i.disconnect();break}case E.EXT2EXT_CLIENT_STREAM_CHUNK:{const T=d;l.isApiSuccessResponse(T.response)&&h.enqueue(T.response.body);break}}}),i.onDisconnect.addListener(()=>{this.logger.debug("Port disconnected",{requestId:o});try{h.error(new a.BodhiError("connection_closed","Connection closed unexpectedly"))}catch{}}),i.postMessage({type:E.EXT2EXT_CLIENT_STREAM_REQUEST,requestId:o,request:{method:e,endpoint:t,body:r,headers:n,authenticated:s}})}}).getReader();try{for(;;){const{done:h,value:d}=await c.read();if(h){this.logger.debug("Stream iteration complete");break}yield d}}finally{c.releaseLock()}}async streamText(e,t,r,n,s=!0){const o=this.generateRequestId();return new Promise((i,u)=>{let c,h=!1;const d=new ReadableStream({start:g=>{c=g}}),T=chrome.runtime.connect({name:y});T.onMessage.addListener(g=>{if(g.requestId===o)switch(g.type){case E.EXT2EXT_CLIENT_STREAM_TEXT_START:{h=!0;async function*p(v){const x=v.getReader();try{for(;;){const{done:b,value:L}=await x.read();if(b)break;yield L}}finally{x.releaseLock()}}i({status:g.status,headers:g.headers,body:p(d)});break}case E.EXT2EXT_CLIENT_STREAM_TEXT_CHUNK:c.enqueue(g.chunk);break;case E.EXT2EXT_CLIENT_STREAM_TEXT_DONE:c.close(),T.disconnect();break;case E.EXT2EXT_CLIENT_STREAM_TEXT_ERROR:{const p=a.createOperationError("extension_error",g.error.message);h?c.error(p):u(p),T.disconnect();break}}}),T.onDisconnect.addListener(()=>{if(!h)u(a.createOperationError("extension_error","Connection closed unexpectedly"));else try{c.error(a.createOperationError("extension_error","Connection closed unexpectedly"))}catch{}}),T.postMessage({type:E.EXT2EXT_CLIENT_STREAM_TEXT_REQUEST,requestId:o,request:{method:e,endpoint:t,body:r,headers:n,authenticated:s}})})}get chat(){return this._chat??=new a.Chat(this)}get models(){return this._models??=new a.Models(this)}get embeddings(){return this._embeddings??=new a.Embeddings(this)}get mcps(){return this._mcps??=new a.Mcps(this)}async requestAccess(e){return this.sendApiRequest("POST","/bodhi/v1/apps/request-access",e,{},!1)}serialize(){return{extensionId:this.extensionId??void 0}}async debug(){return{type:"ExtClient",state:this.state,authState:await this.getAuthState()}}}class U extends a.BaseFacadeClient{constructor(e,t,r){const n=t||{},s={basePath:n.basePath||"/",authServerUrl:n.authServerUrl||"https://id.getbodhi.app/realms/bodhi",logLevel:n.logLevel||"warn",apiTimeoutMs:n.apiTimeoutMs,storage:n.storage,initialTokens:n.initialTokens,initParams:n.initParams};super(e,s,r)}createLogger(e){return new a.Logger("ExtUIClient",e.logLevel)}createStoragePrefix(e){return a.createStoragePrefixWithNamespace(e.basePath,a.STORAGE_PREFIXES.EXT)}createExtClient(e,t){return new k({authClientId:this.authClientId,logLevel:e.logLevel,apiTimeoutMs:e.apiTimeoutMs,initParams:e.initParams},t)}createDirectClient(e,t,r){return new M({authClientId:e,authServerUrl:t.authServerUrl,logLevel:t.logLevel,basePath:t.basePath,apiTimeoutMs:t.apiTimeoutMs,storage:t.storage,initialTokens:t.initialTokens},r)}}const D=["ggedphdcbekjlomjaidbajglgihbeaon"],B=["bjdjhiombmfbcoeojijpfckljjghmjbf"],R="production",S=class S{constructor(e,t){this.isAuthenticating=!1,this.isExchanging=!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 a.Logger("BodhiExtClient",t?.logLevel||"warn"),this.attempts=t?.attempts??A,this.attemptWaitMs=t?.attemptWaitMs??w,this.attemptTimeout=t?.attemptTimeout??X,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),S.base64UrlEncode(e.buffer)}static async generateCodeChallenge(e){const r=new TextEncoder().encode(e),n=await crypto.subtle.digest("SHA-256",r);return S.base64UrlEncode(n)}getState(){return this.state}getExtensionIdsForEnvironment(){const t=R!=="production"?D:B;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 n=setTimeout(()=>{this.logger.debug(`[Ext2Ext/Discovery] Timeout waiting for extension ${e}`),r(new Error("Timeout"))},this.attemptTimeout);try{const s={type:l.MESSAGE_TYPES.EXT_REQUEST,requestId:crypto.randomUUID(),request:{action:"get_extension_id"}};this.logger.debug(`[Ext2Ext/Discovery] Sending message to ${e}:`,s),chrome.runtime.sendMessage(e,s,o=>{if(clearTimeout(n),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}:`,o);const i=o;i&&i.type===l.MESSAGE_TYPES.EXT_RESPONSE?(this.logger.debug(`[Ext2Ext/Discovery] ✓ Extension ${e} responded`),t(!0)):(this.logger.error(`[Ext2Ext/Discovery] Invalid response from ${e}:`,o),r(new Error("Invalid response")))})}catch(s){this.logger.error(`[Ext2Ext/Discovery] Exception pinging ${e}:`,s),clearTimeout(n),r(s)}})}sleep(e){return new Promise(t=>setTimeout(t,e))}async discoverBodhiExtension(e){const{attempts:t,attemptWaitMs:r,attemptTimeout:n}=e;this.logger.info(`[Ext2Ext/Discovery] Starting discovery: ${t} attempts per ID, ${n}ms timeout, ${r}ms between attempts`);const s=this.getExtensionIdsForEnvironment();this.logger.debug(`[Ext2Ext/Discovery] Will try ${s.length} extension(s):`,s);for(const u of s){for(let c=1;c<=t;c++){this.logger.debug(`[Ext2Ext/Discovery] Trying ${u} - attempt ${c}/${t}`);try{return await this.pingExtension(u),this.logger.info(`[Ext2Ext/Discovery] ✓ Found: ${u} on attempt ${c}`),{success:!0,extensionId:u}}catch(h){this.logger.debug(`[Ext2Ext/Discovery] Attempt ${c} failed for ${u}: ${h instanceof Error?h.message:"Unknown error"}`),c<t&&await this.sleep(r)}}this.logger.warn(`[Ext2Ext/Discovery] ✗ Not found: ${u} after ${t} attempts`)}const o=s.join(", "),i=`Extension not found. Tried ${s.length} IDs with ${t} attempts each: ${o}`;return this.logger.error(`[Ext2Ext/Discovery] ${i}`),{success:!1,error:i}}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!==E.EXT2EXT_CLIENT_REQUEST&&e.type!==E.EXT2EXT_CLIENT_API_REQUEST?!1:this.state!=="ready"&&!(e.type===E.EXT2EXT_CLIENT_REQUEST&&e.request.action===_.DISCOVER_EXTENSION)?(e.type===E.EXT2EXT_CLIENT_REQUEST?r({type:E.EXT2EXT_CLIENT_RESPONSE,requestId:e.requestId,response:{error:{message:this.createErrorClientNotInitialized(e),type:"NOT_INITIALIZED"}}}):r({type:E.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 n=await this.handleAction(e);r(n)})(),!0)),this.registerStreamPortListener(f,E.EXT2EXT_CLIENT_STREAM_REQUEST,E.EXT2EXT_CLIENT_STREAM_ERROR,(e,t)=>this.handleStreamRequest(e,t)),this.registerStreamPortListener(y,E.EXT2EXT_CLIENT_STREAM_TEXT_REQUEST,E.EXT2EXT_CLIENT_STREAM_TEXT_ERROR,(e,t)=>this.handleStreamTextRequest(e,t)),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:E.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 s=await this._getAccessTokenRaw();if(!s)return{type:E.EXT2EXT_CLIENT_API_RESPONSE,requestId:t,error:{message:"Not authenticated. Please log in first.",type:"auth_error"}};r={...r,Authorization:`Bearer ${s}`},this.logger.debug("[BodhiExtClient] Injected auth token for authenticated request")}const n=await this.sendApiRequest(e.request.method,e.request.endpoint,e.request.body,r);return{type:E.EXT2EXT_CLIENT_API_RESPONSE,requestId:t,response:n}}catch(r){return this.logger.error("[BodhiExtClient] API request failed:",r),{type:E.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:n,params:s}=r;this.logger.debug(`[BodhiExtClient] Handling action: ${n}`);try{let o={};switch(n){case _.DISCOVER_EXTENSION:{const i=s;await this.init(i),this.logger.info("[BodhiExtClient] Discovery successful:",{extensionId:this.extensionId,environment:R}),o={extensionId:this.extensionId,environment:R};break}case _.SET_EXTENSION_ID:{const{extensionId:i}=s;this.extensionId=i,this.state="ready",this.logger.info("[BodhiExtClient] Extension ID set:",{extensionId:i}),o={success:!0};break}case _.GET_EXTENSION_ID:{const i=await this.sendExtRequestRaw(l.EXT_ACTIONS.GET_EXTENSION_ID,s);return l.isExtError(i.response)?{type:E.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:{error:{message:i.response.error.message||`Extension request failed to get extension ID: ${JSON.stringify(i.response)}`,type:i.response.error.type}}}:{type:E.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:i.response}}case _.LOGIN:{const i=s;await this.login(i),this.broadcastAuthStateChange();break}case _.LOGOUT:await this.logout(),this.broadcastAuthStateChange();break;case _.GET_AUTH_STATE:o={authState:await this.getAuthState()};break;default:return{type:E.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:{error:{message:`Unknown action: ${n}`,type:"UNKNOWN_ACTION"}}}}return{type:E.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:o}}catch(o){return this.logger.error("[BodhiExtClient] Unexpected error:",o),{type:E.EXT2EXT_CLIENT_RESPONSE,requestId:t,response:{error:{message:o instanceof Error?o.message:`Unexpected error: ${JSON.stringify(o)}`}}}}}async handleAction(e){switch(e.type){case E.EXT2EXT_CLIENT_API_REQUEST:return this.handleApiRequest(e);case E.EXT2EXT_CLIENT_REQUEST:return this.handleExtClientRequest(e);default:{const{requestId:t}=e;return this.logger.error("[BodhiExtClient] Unknown message type:",e.type),{type:E.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"&&!e?.exchange)){this.isAuthenticating=!0,this.isExchanging=e?.exchange??!1;try{if(!this.extensionId)throw new Error("Extension not discovered. Please detect Bodhi extension before login.");const r=e?.userRole??"scope_user_user",n=chrome.identity.getRedirectURL("callback"),s=S.generateCodeVerifier(),o=await S.generateCodeChallenge(s),i=S.generateCodeVerifier();await chrome.storage.session.set({codeVerifier:s,state:i,authInProgress:!0});const u=a.buildAuthorizeUrl(this.authEndpoints,{clientId:this.authClientId,redirectUri:n,scope:a.BASE_OAUTH_SCOPE,state:i,codeChallenge:o}),c=a.buildErrorUrl(n),h=new a.AccessRequestBuilder(this.authClientId).requestedRole(r);e?.requested&&h.requested(e.requested),e?.exchange&&h.exchange(!0);const d=await this.requestAccess(h.build()),{review_url:T}=a.unwrapResponse(d),g=a.buildReviewUrl(T,u,c),p=await this.launchReview(g);await this.completeOAuthRedirect(p)}finally{this.isAuthenticating=!1,this.isExchanging=!1}}}async exchangeCodeForTokens(e){if((await this.getAuthState()).status==="authenticated"&&!this.isExchanging)return;const{codeVerifier:r}=await chrome.storage.session.get("codeVerifier"),n=chrome.identity.getRedirectURL("callback"),s=await fetch(this.authEndpoints.token,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",code:e,redirect_uri:n,client_id:this.authClientId,code_verifier:r})});if(!s.ok){const i=await s.text();throw new Error(`Token exchange failed: ${s.status} ${i}`)}const o=await s.json();await this.storeTokens({accessToken:o.access_token,refreshToken:o.refresh_token,idToken:o.id_token,expiresIn:o.expires_in})}async getAuthState(){const e=await this._getAccessTokenRaw();if(!e)return{status:"unauthenticated",user:null,accessToken:null,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!1};try{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,refreshToken:null,expiresAt:null,isTokenRefresh:!1}}catch(t){return this.logger.error("Failed to parse token:",t),{status:"unauthenticated",user:null,accessToken:null,error:null,refreshToken:null,expiresAt:null,isTokenRefresh:!1}}}async 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,void 0,!0)}launchReview(e){return new Promise((t,r)=>{chrome.identity.launchWebAuthFlow({url:e,interactive:!0},async n=>{if(await chrome.storage.session.set({authInProgress:!1}),chrome.runtime.lastError){await chrome.storage.session.remove(["codeVerifier","state"]),r(chrome.runtime.lastError);return}if(!n){await chrome.storage.session.remove(["codeVerifier","state"]),r(a.createOperationError("oauth_error","No redirect URL received"));return}t(n)})})}async completeOAuthRedirect(e){const t=new URL(e),r=t.searchParams.get("error");if(r)throw await chrome.storage.session.remove(["codeVerifier","state"]),t.searchParams.get("error_source")==="bodhi"&&a.throwAccessRequestDenialError(r==="access_denied"?"denied":r),a.createOperationError("oauth_error",t.searchParams.get("error_description")??r);const n=t.searchParams.get("code"),s=t.searchParams.get("state"),{state:o}=await chrome.storage.session.get("state");if(!s||s!==o)throw await chrome.storage.session.remove(["codeVerifier","state"]),a.createOperationError("oauth_error","State mismatch");if(!n)throw await chrome.storage.session.remove(["codeVerifier","state"]),a.createOperationError("oauth_error","No authorization code");if(await this.exchangeCodeForTokens(n),await chrome.storage.session.remove(["codeVerifier","state"]),(await this.getAuthState()).status!=="authenticated")throw a.createOperationError("oauth_error","Login failed")}async sendExtRequest(e,t){const r=await this.sendExtRequestRaw(e,t);if(l.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,n,s=!1){if(!this.extensionId)throw new a.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}:"");let o=n;if(s){const c=await this._getAccessTokenRaw();c&&(o={...n,Authorization:`Bearer ${c}`})}const i=crypto.randomUUID(),u={type:l.MESSAGE_TYPES.API_REQUEST,requestId:i,request:{method:e,endpoint:t,body:r,headers:o}};return this.logger.debug(`[BodhiExtClient] Request ID: ${i}, Extension: ${this.extensionId}`),new Promise((c,h)=>{try{chrome.runtime.sendMessage(this.extensionId,u,d=>{if(chrome.runtime.lastError){this.logger.error(`[BodhiExtClient] Chrome runtime error for request ${i}:`,chrome.runtime.lastError),h(new a.BodhiError("extension_error",chrome.runtime.lastError.message??"Chrome runtime error"));return}if(this.logger.debug(`[BodhiExtClient] Response for request ${i}:`,d),!d){this.logger.error(`[BodhiExtClient] No response received for request ${i}`),h(new a.BodhiError("extension_error","No response from extension"));return}d.type===l.MESSAGE_TYPES.API_RESPONSE&&d.requestId===i?"error"in d?(this.logger.error(`[BodhiExtClient] API error for ${i}:`,d.error),h(new a.BodhiError(d.error.type||"extension_error",d.error.message))):(this.logger.debug(`[BodhiExtClient] ✓ Valid API_RESPONSE for ${i}`),c(d.response)):(this.logger.error(`[BodhiExtClient] Invalid response format for ${i}:`,d),h(new a.BodhiError("extension_error","Invalid response format")))})}catch(d){this.logger.error(`[BodhiExtClient] Exception sending message for ${i}:`,d),h(d)}})}async sendExtRequestRaw(e,t){if(!this.extensionId)throw new a.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(),n={type:l.MESSAGE_TYPES.EXT_REQUEST,requestId:r,request:{action:e,params:t}};return this.logger.debug(`[BodhiExtClient] Request ID: ${r}, Extension: ${this.extensionId}`),new Promise((s,o)=>{try{chrome.runtime.sendMessage(this.extensionId,n,i=>{if(chrome.runtime.lastError){this.logger.error(`[BodhiExtClient] Chrome runtime error for request ${r}:`,chrome.runtime.lastError),o(new Error(chrome.runtime.lastError.message));return}if(this.logger.debug(`[BodhiExtClient] Response for request ${r}:`,i),!i){this.logger.error(`[BodhiExtClient] No response received for request ${r}`),o(new Error("No response from extension"));return}i.type===l.MESSAGE_TYPES.EXT_RESPONSE&&i.requestId===r?(this.logger.debug(`[BodhiExtClient] ✓ Valid EXT_RESPONSE for ${r}`),s(i)):(this.logger.error(`[BodhiExtClient] Invalid response format for ${r}:`,i),o(new a.BodhiError("extension_error","Invalid response format")))})}catch(i){this.logger.error(`[BodhiExtClient] Exception sending message for ${r}:`,i),o(i)}})}registerStreamPortListener(e,t,r,n){chrome.runtime.onConnect.addListener(s=>{s.name===e&&(this.logger.info(`[BodhiExtClient] Port connected: ${e}`),s.onMessage.addListener(async o=>{if(o.type!==t){this.logger.warn(`[BodhiExtClient] Unknown message type on ${e}:`,o.type),s.postMessage({type:r,requestId:o.requestId,error:{message:"Unknown message type",type:"extension_error"}});return}await n(s,o)}),s.onDisconnect.addListener(()=>{this.logger.info(`[BodhiExtClient] Port disconnected: ${e}`)}))})}async handleGenericStreamRelay(e,t,r,n,s,o,i){if(this.logger.debug("[BodhiExtClient] Processing stream relay:",{requestId:t,method:r.method,endpoint:r.endpoint,bodhiPortName:n}),!this.extensionId){e.postMessage({type:o,requestId:t,error:{message:"Client not initialized (no extensionId)",type:"extension_error"}});return}try{let u={...r.headers};if(r.authenticated!==!1){const T=await this._getAccessTokenRaw();if(!T){e.postMessage({type:o,requestId:t,error:{message:"Not authenticated. Please log in first.",type:"extension_error"}});return}u={...u,Authorization:`Bearer ${T}`},this.logger.debug("[BodhiExtClient] Injected auth token for stream relay")}const c=chrome.runtime.connect(this.extensionId,{name:n});this.activeStreamPorts.set(t,c);const h=setTimeout(()=>{this.activeStreamPorts.has(t)&&(this.logger.error(`[BodhiExtClient] Stream timeout for ${t}`),e.postMessage({type:o,requestId:t,error:{message:"Stream request timed out",type:"timeout_error"}}),this.cleanupStreamPort(t))},S.STREAM_TIMEOUT);c.onMessage.addListener(T=>{i(T,e,t)&&(clearTimeout(h),this.cleanupStreamPort(t))}),c.onDisconnect.addListener(()=>{clearTimeout(h),this.activeStreamPorts.has(t)&&(this.logger.error(`[BodhiExtClient] Bodhi port disconnected for ${t}`),e.postMessage({type:o,requestId:t,error:{message:"Connection to Bodhi extension closed unexpectedly",type:"network_error"}}),this.activeStreamPorts.delete(t))});const d={type:s,requestId:t,request:{method:r.method,endpoint:r.endpoint,body:r.body,headers:u}};this.logger.debug("[BodhiExtClient] Sending stream request to bodhi port:",d),c.postMessage(d)}catch(u){const c=u;this.logger.error("[BodhiExtClient] Stream relay error:",JSON.stringify(c.message)),e.postMessage({type:o,requestId:t,error:{message:`uncaught error: ${JSON.stringify({error:c,message:c.message})}`,type:"extension_error"}})}}cleanupStreamPort(e){const t=this.activeStreamPorts.get(e);if(t){try{t.disconnect()}catch{}this.activeStreamPorts.delete(e)}}async handleStreamRequest(e,t){const{requestId:r,request:n}=t;await this.handleGenericStreamRelay(e,r,n,l.BODHI_STREAM_PORT,l.MESSAGE_TYPES.STREAM_REQUEST,E.EXT2EXT_CLIENT_STREAM_ERROR,(s,o,i)=>{if(l.isStreamChunk(s)){const u=s.response,c=u.body;return u.status>=400?(o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_API_ERROR,requestId:i,response:u}),!1):c?.done?(o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_DONE,requestId:i}),this.logger.info(`[BodhiExtClient] Stream complete for ${i}`),!0):(o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_CHUNK,requestId:i,response:u}),!1)}else{if(l.isStreamApiError(s))return this.logger.error(`[BodhiExtClient] Stream API error for ${i}: ${s.response.status}`),o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_API_ERROR,requestId:i,response:s.response}),!1;if(l.isStreamError(s))return this.logger.error(`[BodhiExtClient] Stream error for ${i}:`,s.error.message),o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_ERROR,requestId:i,error:{message:`stream error: ${JSON.stringify(s)}`,type:"extension_error"}}),!0}return!1})}async handleStreamTextRequest(e,t){const{requestId:r,request:n}=t;await this.handleGenericStreamRelay(e,r,n,l.BODHI_STREAM_TEXT_PORT,l.MESSAGE_TYPES.STREAM_TEXT_REQUEST,E.EXT2EXT_CLIENT_STREAM_TEXT_ERROR,(s,o,i)=>{switch(s.type){case l.MESSAGE_TYPES.STREAM_TEXT_START:return o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_TEXT_START,requestId:i,status:s.status,headers:s.headers}),!1;case l.MESSAGE_TYPES.STREAM_TEXT_CHUNK:return o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_TEXT_CHUNK,requestId:i,chunk:s.chunk}),!1;case l.MESSAGE_TYPES.STREAM_TEXT_DONE:return this.logger.info(`[BodhiExtClient] Stream text complete for ${i}`),o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_TEXT_DONE,requestId:i}),!0;case l.MESSAGE_TYPES.STREAM_TEXT_ERROR:return this.logger.error(`[BodhiExtClient] Stream text error for ${i}:`,s.error.message),o.postMessage({type:E.EXT2EXT_CLIENT_STREAM_TEXT_ERROR,requestId:i,error:s.error}),!0}return!1})}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 a.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"),a.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,"/"),n=decodeURIComponent(atob(r).split("").map(s=>"%"+("00"+s.charCodeAt(0).toString(16)).slice(-2)).join(""));return JSON.parse(n)}createErrorClientNotInitialized(e){return`Client not initialized. Extension discovery not triggered nor extensionId set, cannot handle request: ${JSON.stringify(e)}`}};S.STREAM_TIMEOUT=6e4;let I=S;const q="production";Object.defineProperty(exports,"InMemoryStorage",{enumerable:!0,get:()=>a.InMemoryStorage});exports.BodhiExtClient=I;exports.ChromeSessionStorageAdapter=P;exports.DEFAULT_API_TIMEOUT_MS=N;exports.DISCOVERY_ATTEMPTS=A;exports.DISCOVERY_ATTEMPT_TIMEOUT=X;exports.DISCOVERY_ATTEMPT_WAIT_MS=w;exports.DISCOVERY_TIMEOUT_MS=C;exports.EXT2EXT_CLIENT_ACTIONS=_;exports.EXT2EXT_CLIENT_MESSAGE_TYPES=E;exports.EXT2EXT_CLIENT_STREAM_PORT=f;exports.EXT2EXT_CLIENT_STREAM_TEXT_PORT=y;exports.EXT_BUILD_MODE=q;exports.ExtUIClient=U;exports.isExtClientApiError=O;
|
package/dist/bodhi-ext.esm.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { DirectClientBase as
|
|
1
|
+
import { DirectClientBase as V, createStoragePrefixWithNamespace as X, STORAGE_PREFIXES as N, generateCodeVerifier as w, generateCodeChallenge as z, buildAuthorizeUrl as v, BASE_OAUTH_SCOPE as L, buildErrorUrl as b, AccessRequestBuilder as k, unwrapResponse as P, buildReviewUrl as O, createOperationError as l, throwAccessRequestDenialError as M, PENDING_EXTENSION_READY as f, Logger as y, NOOP_STATE_CALLBACK as Q, createExtensionStateNotInitialized as G, BACKEND_SERVER_NOT_REACHABLE as K, createExtensionStateNotFound as F, BodhiError as T, INITIAL_AUTH_STATE as H, BodhiApiError as W, Chat as J, Models as Y, Embeddings as j, Mcps as Z, isAuthError as ee, BaseFacadeClient as te, refreshAccessToken as re } from "@bodhiapp/bodhi-js-core";
|
|
2
2
|
import { InMemoryStorage as Ae } from "@bodhiapp/bodhi-js-core";
|
|
3
|
-
import { isExtError as
|
|
3
|
+
import { isExtError as x, isApiSuccessResponse as se, MESSAGE_TYPES as _, EXT_ACTIONS as ie, BODHI_STREAM_PORT as ne, isStreamChunk as oe, isStreamApiError as ae, isStreamError as ce, BODHI_STREAM_TEXT_PORT as he } from "@bodhiapp/bodhi-browser-types";
|
|
4
4
|
const a = {
|
|
5
5
|
EXT2EXT_CLIENT_REQUEST: "EXT2EXT_CLIENT_REQUEST",
|
|
6
6
|
EXT2EXT_CLIENT_RESPONSE: "EXT2EXT_CLIENT_RESPONSE",
|
|
@@ -42,7 +42,7 @@ class _e {
|
|
|
42
42
|
await chrome.storage.session.remove(e);
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
-
class me extends
|
|
45
|
+
class me extends V {
|
|
46
46
|
constructor(e, t) {
|
|
47
47
|
const r = X(
|
|
48
48
|
e.basePath,
|
|
@@ -64,24 +64,24 @@ class me extends z {
|
|
|
64
64
|
// ============================================================================
|
|
65
65
|
async login(e) {
|
|
66
66
|
const t = await this.getAuthState();
|
|
67
|
-
if (t.status === "authenticated" && !e?.
|
|
67
|
+
if (t.status === "authenticated" && !e?.exchange)
|
|
68
68
|
return t;
|
|
69
69
|
const r = e?.userRole ?? "scope_user_user", o = this._getRedirectUri();
|
|
70
70
|
e?.onProgress?.("requesting");
|
|
71
|
-
const s = w(), i = await
|
|
71
|
+
const s = w(), i = await z(s), n = w();
|
|
72
72
|
await this._storageSet({
|
|
73
73
|
[this.storageKeys.CODE_VERIFIER]: s,
|
|
74
74
|
[this.storageKeys.STATE]: n
|
|
75
75
|
});
|
|
76
|
-
const
|
|
76
|
+
const u = v(this.authEndpoints, {
|
|
77
77
|
clientId: this.authClientId,
|
|
78
78
|
redirectUri: o,
|
|
79
79
|
scope: L,
|
|
80
80
|
state: n,
|
|
81
81
|
codeChallenge: i
|
|
82
|
-
}),
|
|
83
|
-
e?.requested &&
|
|
84
|
-
const
|
|
82
|
+
}), c = b(o), h = new k(this.authClientId).requestedRole(r);
|
|
83
|
+
e?.requested && h.requested(e.requested), e?.exchange && h.exchange(!0);
|
|
84
|
+
const E = await this.requestAccess(h.build()), { review_url: d } = P(E), g = O(d, u, c);
|
|
85
85
|
e?.onProgress?.("reviewing");
|
|
86
86
|
const S = await this.launchReview(g);
|
|
87
87
|
return e?.onProgress?.("authenticating"), this.completeOAuthRedirect(S);
|
|
@@ -94,7 +94,7 @@ class me extends z {
|
|
|
94
94
|
return;
|
|
95
95
|
}
|
|
96
96
|
if (!o) {
|
|
97
|
-
r(
|
|
97
|
+
r(l("oauth_error", "No redirect URL received"));
|
|
98
98
|
return;
|
|
99
99
|
}
|
|
100
100
|
t(o);
|
|
@@ -104,16 +104,16 @@ class me extends z {
|
|
|
104
104
|
async completeOAuthRedirect(e) {
|
|
105
105
|
const t = new URL(e), r = t.searchParams.get("error");
|
|
106
106
|
if (r)
|
|
107
|
-
throw await this._storageRemove([this.storageKeys.CODE_VERIFIER, this.storageKeys.STATE]), t.searchParams.get("error_source") === "bodhi" && M(r === "access_denied" ? "denied" : r),
|
|
107
|
+
throw await this._storageRemove([this.storageKeys.CODE_VERIFIER, this.storageKeys.STATE]), t.searchParams.get("error_source") === "bodhi" && M(r === "access_denied" ? "denied" : r), l("oauth_error", t.searchParams.get("error_description") ?? r);
|
|
108
108
|
const o = t.searchParams.get("code"), s = t.searchParams.get("state"), i = await this._storageGet(this.storageKeys.STATE);
|
|
109
109
|
if (!s || s !== i)
|
|
110
|
-
throw await this._storageRemove([this.storageKeys.CODE_VERIFIER, this.storageKeys.STATE]),
|
|
110
|
+
throw await this._storageRemove([this.storageKeys.CODE_VERIFIER, this.storageKeys.STATE]), l("oauth_error", "State mismatch");
|
|
111
111
|
if (!o)
|
|
112
|
-
throw await this._storageRemove([this.storageKeys.CODE_VERIFIER, this.storageKeys.STATE]),
|
|
112
|
+
throw await this._storageRemove([this.storageKeys.CODE_VERIFIER, this.storageKeys.STATE]), l("oauth_error", "No authorization code");
|
|
113
113
|
await this.exchangeCodeForTokens(o);
|
|
114
114
|
const n = await this.getAuthState();
|
|
115
115
|
if (n.status !== "authenticated")
|
|
116
|
-
throw await this._storageRemove([this.storageKeys.CODE_VERIFIER, this.storageKeys.STATE]),
|
|
116
|
+
throw await this._storageRemove([this.storageKeys.CODE_VERIFIER, this.storageKeys.STATE]), l("oauth_error", "Login failed");
|
|
117
117
|
return this.setAuthState(n), n;
|
|
118
118
|
}
|
|
119
119
|
_getRedirectUri() {
|
|
@@ -127,7 +127,7 @@ class pe {
|
|
|
127
127
|
extension: "not-initialized",
|
|
128
128
|
extensionId: null,
|
|
129
129
|
server: f
|
|
130
|
-
}, this.extensionId = null, this.broadcastListenerActive = !1, this.config = e, this.logger = new
|
|
130
|
+
}, this.extensionId = null, this.broadcastListenerActive = !1, this.config = e, this.logger = new y("ExtClient", e?.logLevel || "warn"), this.onStateChange = t ?? Q, this.apiTimeoutMs = e.apiTimeoutMs ?? Te, this.authClientId = e.authClientId ?? "";
|
|
131
131
|
}
|
|
132
132
|
/**
|
|
133
133
|
* Set client state and notify callback
|
|
@@ -268,20 +268,20 @@ class pe {
|
|
|
268
268
|
}
|
|
269
269
|
});
|
|
270
270
|
if (!o)
|
|
271
|
-
throw
|
|
271
|
+
throw l("extension_error", "No response from background script");
|
|
272
272
|
if (o.type !== a.EXT2EXT_CLIENT_RESPONSE)
|
|
273
|
-
throw
|
|
273
|
+
throw l(
|
|
274
274
|
"extension_error",
|
|
275
275
|
"Invalid response type from background script"
|
|
276
276
|
);
|
|
277
277
|
const s = o.response;
|
|
278
|
-
if (
|
|
278
|
+
if (x(s)) {
|
|
279
279
|
const i = s.error.type || "extension_error";
|
|
280
|
-
throw
|
|
280
|
+
throw l(i, s.error.message);
|
|
281
281
|
}
|
|
282
282
|
return s;
|
|
283
283
|
} catch (r) {
|
|
284
|
-
throw r instanceof T ? r :
|
|
284
|
+
throw r instanceof T ? r : l(
|
|
285
285
|
"extension_error",
|
|
286
286
|
r instanceof Error ? r.message : "Unknown error occurred"
|
|
287
287
|
);
|
|
@@ -312,8 +312,8 @@ class pe {
|
|
|
312
312
|
async sendApiRequest(e, t, r, o, s) {
|
|
313
313
|
try {
|
|
314
314
|
const i = new Promise(
|
|
315
|
-
(
|
|
316
|
-
() =>
|
|
315
|
+
(u, c) => setTimeout(
|
|
316
|
+
() => c(
|
|
317
317
|
new Error(
|
|
318
318
|
`[bodhi-js-sdk/ext] network timeout: api request not completed within configured/default timeout of ${this.apiTimeoutMs}ms`
|
|
319
319
|
)
|
|
@@ -325,8 +325,8 @@ class pe {
|
|
|
325
325
|
i
|
|
326
326
|
]);
|
|
327
327
|
if (ge(n)) {
|
|
328
|
-
const
|
|
329
|
-
throw new T(
|
|
328
|
+
const u = n.error.type || "extension_error";
|
|
329
|
+
throw new T(u, n.error.message);
|
|
330
330
|
}
|
|
331
331
|
return n.response;
|
|
332
332
|
} catch (i) {
|
|
@@ -351,12 +351,12 @@ class pe {
|
|
|
351
351
|
const i = await this.getAuthState();
|
|
352
352
|
if (ee(i)) {
|
|
353
353
|
r(
|
|
354
|
-
|
|
354
|
+
l("auth_error", `Login failed: ${i.error?.message}`)
|
|
355
355
|
);
|
|
356
356
|
return;
|
|
357
357
|
}
|
|
358
358
|
if (i.status !== "authenticated") {
|
|
359
|
-
r(
|
|
359
|
+
r(l("auth_error", "Login failed: User is not logged in"));
|
|
360
360
|
return;
|
|
361
361
|
}
|
|
362
362
|
this.setAuthState(i), t(i);
|
|
@@ -472,49 +472,49 @@ class pe {
|
|
|
472
472
|
endpoint: t,
|
|
473
473
|
requestId: i
|
|
474
474
|
});
|
|
475
|
-
const n = chrome.runtime.connect({ name: U }),
|
|
476
|
-
start: (
|
|
477
|
-
n.onMessage.addListener((
|
|
478
|
-
if (
|
|
479
|
-
switch (
|
|
475
|
+
const n = chrome.runtime.connect({ name: U }), c = new ReadableStream({
|
|
476
|
+
start: (h) => {
|
|
477
|
+
n.onMessage.addListener((E) => {
|
|
478
|
+
if (E.requestId === i)
|
|
479
|
+
switch (E.type) {
|
|
480
480
|
case a.EXT2EXT_CLIENT_STREAM_DONE:
|
|
481
|
-
this.logger.debug("Stream complete", { requestId: i }),
|
|
481
|
+
this.logger.debug("Stream complete", { requestId: i }), h.close(), n.disconnect();
|
|
482
482
|
break;
|
|
483
483
|
case a.EXT2EXT_CLIENT_STREAM_ERROR:
|
|
484
484
|
this.logger.error("Stream error", {
|
|
485
485
|
requestId: i,
|
|
486
|
-
error: JSON.stringify(
|
|
487
|
-
}),
|
|
486
|
+
error: JSON.stringify(E.error)
|
|
487
|
+
}), h.error(
|
|
488
488
|
new T(
|
|
489
489
|
"extension_error",
|
|
490
|
-
|
|
490
|
+
E.error.message
|
|
491
491
|
)
|
|
492
492
|
), n.disconnect();
|
|
493
493
|
break;
|
|
494
494
|
case a.EXT2EXT_CLIENT_STREAM_API_ERROR: {
|
|
495
|
-
const
|
|
495
|
+
const d = E;
|
|
496
496
|
this.logger.error("Stream API error", {
|
|
497
497
|
requestId: i,
|
|
498
|
-
error:
|
|
499
|
-
}),
|
|
498
|
+
error: d.response.body?.error
|
|
499
|
+
}), h.error(
|
|
500
500
|
new W(
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
501
|
+
d.response.status,
|
|
502
|
+
d.response.body,
|
|
503
|
+
d.response.body?.error?.message || "API error"
|
|
504
504
|
)
|
|
505
505
|
), n.disconnect();
|
|
506
506
|
break;
|
|
507
507
|
}
|
|
508
508
|
case a.EXT2EXT_CLIENT_STREAM_CHUNK: {
|
|
509
|
-
const
|
|
510
|
-
se(
|
|
509
|
+
const d = E;
|
|
510
|
+
se(d.response) && h.enqueue(d.response.body);
|
|
511
511
|
break;
|
|
512
512
|
}
|
|
513
513
|
}
|
|
514
514
|
}), n.onDisconnect.addListener(() => {
|
|
515
515
|
this.logger.debug("Port disconnected", { requestId: i });
|
|
516
516
|
try {
|
|
517
|
-
|
|
517
|
+
h.error(new T("connection_closed", "Connection closed unexpectedly"));
|
|
518
518
|
} catch {
|
|
519
519
|
}
|
|
520
520
|
}), n.postMessage({
|
|
@@ -526,15 +526,15 @@ class pe {
|
|
|
526
526
|
}).getReader();
|
|
527
527
|
try {
|
|
528
528
|
for (; ; ) {
|
|
529
|
-
const { done:
|
|
530
|
-
if (
|
|
529
|
+
const { done: h, value: E } = await c.read();
|
|
530
|
+
if (h) {
|
|
531
531
|
this.logger.debug("Stream iteration complete");
|
|
532
532
|
break;
|
|
533
533
|
}
|
|
534
|
-
yield
|
|
534
|
+
yield E;
|
|
535
535
|
}
|
|
536
536
|
} finally {
|
|
537
|
-
|
|
537
|
+
c.releaseLock();
|
|
538
538
|
}
|
|
539
539
|
}
|
|
540
540
|
/**
|
|
@@ -543,18 +543,18 @@ class pe {
|
|
|
543
543
|
*/
|
|
544
544
|
async streamText(e, t, r, o, s = !0) {
|
|
545
545
|
const i = this.generateRequestId();
|
|
546
|
-
return new Promise((n,
|
|
547
|
-
let
|
|
548
|
-
const
|
|
546
|
+
return new Promise((n, u) => {
|
|
547
|
+
let c, h = !1;
|
|
548
|
+
const E = new ReadableStream({
|
|
549
549
|
start: (g) => {
|
|
550
|
-
|
|
550
|
+
c = g;
|
|
551
551
|
}
|
|
552
|
-
}),
|
|
553
|
-
|
|
552
|
+
}), d = chrome.runtime.connect({ name: D });
|
|
553
|
+
d.onMessage.addListener((g) => {
|
|
554
554
|
if (g.requestId === i)
|
|
555
555
|
switch (g.type) {
|
|
556
556
|
case a.EXT2EXT_CLIENT_STREAM_TEXT_START: {
|
|
557
|
-
|
|
557
|
+
h = !0;
|
|
558
558
|
async function* S(q) {
|
|
559
559
|
const C = q.getReader();
|
|
560
560
|
try {
|
|
@@ -570,33 +570,33 @@ class pe {
|
|
|
570
570
|
n({
|
|
571
571
|
status: g.status,
|
|
572
572
|
headers: g.headers,
|
|
573
|
-
body: S(
|
|
573
|
+
body: S(E)
|
|
574
574
|
});
|
|
575
575
|
break;
|
|
576
576
|
}
|
|
577
577
|
case a.EXT2EXT_CLIENT_STREAM_TEXT_CHUNK:
|
|
578
|
-
|
|
578
|
+
c.enqueue(g.chunk);
|
|
579
579
|
break;
|
|
580
580
|
case a.EXT2EXT_CLIENT_STREAM_TEXT_DONE:
|
|
581
|
-
|
|
581
|
+
c.close(), d.disconnect();
|
|
582
582
|
break;
|
|
583
583
|
case a.EXT2EXT_CLIENT_STREAM_TEXT_ERROR: {
|
|
584
|
-
const S =
|
|
585
|
-
|
|
584
|
+
const S = l("extension_error", g.error.message);
|
|
585
|
+
h ? c.error(S) : u(S), d.disconnect();
|
|
586
586
|
break;
|
|
587
587
|
}
|
|
588
588
|
}
|
|
589
|
-
}),
|
|
590
|
-
if (!
|
|
591
|
-
|
|
589
|
+
}), d.onDisconnect.addListener(() => {
|
|
590
|
+
if (!h)
|
|
591
|
+
u(l("extension_error", "Connection closed unexpectedly"));
|
|
592
592
|
else
|
|
593
593
|
try {
|
|
594
|
-
|
|
595
|
-
|
|
594
|
+
c.error(
|
|
595
|
+
l("extension_error", "Connection closed unexpectedly")
|
|
596
596
|
);
|
|
597
597
|
} catch {
|
|
598
598
|
}
|
|
599
|
-
}),
|
|
599
|
+
}), d.postMessage({
|
|
600
600
|
type: a.EXT2EXT_CLIENT_STREAM_TEXT_REQUEST,
|
|
601
601
|
requestId: i,
|
|
602
602
|
request: { method: e, endpoint: t, body: r, headers: o, authenticated: s }
|
|
@@ -647,7 +647,7 @@ class pe {
|
|
|
647
647
|
};
|
|
648
648
|
}
|
|
649
649
|
}
|
|
650
|
-
class
|
|
650
|
+
class xe extends te {
|
|
651
651
|
constructor(e, t, r) {
|
|
652
652
|
const o = t || {}, s = {
|
|
653
653
|
basePath: o.basePath || "/",
|
|
@@ -661,7 +661,7 @@ class ye extends te {
|
|
|
661
661
|
super(e, s, r);
|
|
662
662
|
}
|
|
663
663
|
createLogger(e) {
|
|
664
|
-
return new
|
|
664
|
+
return new y("ExtUIClient", e.logLevel);
|
|
665
665
|
}
|
|
666
666
|
createStoragePrefix(e) {
|
|
667
667
|
return X(e.basePath, N.EXT);
|
|
@@ -697,7 +697,7 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
|
|
|
697
697
|
// Constructor
|
|
698
698
|
// ============================================================================
|
|
699
699
|
constructor(e, t) {
|
|
700
|
-
this.isAuthenticating = !1, this.
|
|
700
|
+
this.isAuthenticating = !1, this.isExchanging = !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 y("BodhiExtClient", t?.logLevel || "warn"), this.attempts = t?.attempts ?? ue, this.attemptWaitMs = t?.attemptWaitMs ?? de, this.attemptTimeout = t?.attemptTimeout ?? le, this.authEndpoints = {
|
|
701
701
|
authorize: `${this.authServerUrl}/protocol/openid-connect/auth`,
|
|
702
702
|
token: `${this.authServerUrl}/protocol/openid-connect/token`,
|
|
703
703
|
userinfo: `${this.authServerUrl}/protocol/openid-connect/userinfo`,
|
|
@@ -799,24 +799,24 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
|
|
|
799
799
|
`[Ext2Ext/Discovery] Will try ${s.length} extension(s):`,
|
|
800
800
|
s
|
|
801
801
|
);
|
|
802
|
-
for (const
|
|
803
|
-
for (let
|
|
802
|
+
for (const u of s) {
|
|
803
|
+
for (let c = 1; c <= t; c++) {
|
|
804
804
|
this.logger.debug(
|
|
805
|
-
`[Ext2Ext/Discovery] Trying ${
|
|
805
|
+
`[Ext2Ext/Discovery] Trying ${u} - attempt ${c}/${t}`
|
|
806
806
|
);
|
|
807
807
|
try {
|
|
808
|
-
return await this.pingExtension(
|
|
808
|
+
return await this.pingExtension(u), this.logger.info(`[Ext2Ext/Discovery] ✓ Found: ${u} on attempt ${c}`), {
|
|
809
809
|
success: !0,
|
|
810
|
-
extensionId:
|
|
810
|
+
extensionId: u
|
|
811
811
|
};
|
|
812
|
-
} catch (
|
|
812
|
+
} catch (h) {
|
|
813
813
|
this.logger.debug(
|
|
814
|
-
`[Ext2Ext/Discovery] Attempt ${
|
|
815
|
-
),
|
|
814
|
+
`[Ext2Ext/Discovery] Attempt ${c} failed for ${u}: ${h instanceof Error ? h.message : "Unknown error"}`
|
|
815
|
+
), c < t && await this.sleep(r);
|
|
816
816
|
}
|
|
817
817
|
}
|
|
818
818
|
this.logger.warn(
|
|
819
|
-
`[Ext2Ext/Discovery] ✗ Not found: ${
|
|
819
|
+
`[Ext2Ext/Discovery] ✗ Not found: ${u} after ${t} attempts`
|
|
820
820
|
);
|
|
821
821
|
}
|
|
822
822
|
const i = s.join(", "), n = `Extension not found. Tried ${s.length} IDs with ${t} attempts each: ${i}`;
|
|
@@ -989,7 +989,7 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
|
|
|
989
989
|
}
|
|
990
990
|
case m.GET_EXTENSION_ID: {
|
|
991
991
|
const n = await this.sendExtRequestRaw(ie.GET_EXTENSION_ID, s);
|
|
992
|
-
return
|
|
992
|
+
return x(n.response) ? {
|
|
993
993
|
type: a.EXT2EXT_CLIENT_RESPONSE,
|
|
994
994
|
requestId: t,
|
|
995
995
|
response: {
|
|
@@ -1072,25 +1072,25 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
|
|
|
1072
1072
|
* @throws Error if login fails
|
|
1073
1073
|
*/
|
|
1074
1074
|
async login(e) {
|
|
1075
|
-
if (!(this.isAuthenticating || (await this.getAuthState()).status === "authenticated" && !e?.
|
|
1076
|
-
this.isAuthenticating = !0, this.
|
|
1075
|
+
if (!(this.isAuthenticating || (await this.getAuthState()).status === "authenticated" && !e?.exchange)) {
|
|
1076
|
+
this.isAuthenticating = !0, this.isExchanging = e?.exchange ?? !1;
|
|
1077
1077
|
try {
|
|
1078
1078
|
if (!this.extensionId)
|
|
1079
1079
|
throw new Error("Extension not discovered. Please detect Bodhi extension before login.");
|
|
1080
1080
|
const r = e?.userRole ?? "scope_user_user", o = chrome.identity.getRedirectURL("callback"), s = p.generateCodeVerifier(), i = await p.generateCodeChallenge(s), n = p.generateCodeVerifier();
|
|
1081
1081
|
await chrome.storage.session.set({ codeVerifier: s, state: n, authInProgress: !0 });
|
|
1082
|
-
const
|
|
1082
|
+
const u = v(this.authEndpoints, {
|
|
1083
1083
|
clientId: this.authClientId,
|
|
1084
1084
|
redirectUri: o,
|
|
1085
1085
|
scope: L,
|
|
1086
1086
|
state: n,
|
|
1087
1087
|
codeChallenge: i
|
|
1088
|
-
}),
|
|
1089
|
-
e?.requested &&
|
|
1090
|
-
const
|
|
1088
|
+
}), c = b(o), h = new k(this.authClientId).requestedRole(r);
|
|
1089
|
+
e?.requested && h.requested(e.requested), e?.exchange && h.exchange(!0);
|
|
1090
|
+
const E = await this.requestAccess(h.build()), { review_url: d } = P(E), g = O(d, u, c), S = await this.launchReview(g);
|
|
1091
1091
|
await this.completeOAuthRedirect(S);
|
|
1092
1092
|
} finally {
|
|
1093
|
-
this.isAuthenticating = !1, this.
|
|
1093
|
+
this.isAuthenticating = !1, this.isExchanging = !1;
|
|
1094
1094
|
}
|
|
1095
1095
|
}
|
|
1096
1096
|
}
|
|
@@ -1099,7 +1099,7 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
|
|
|
1099
1099
|
* @param code Authorization code from OAuth callback
|
|
1100
1100
|
*/
|
|
1101
1101
|
async exchangeCodeForTokens(e) {
|
|
1102
|
-
if ((await this.getAuthState()).status === "authenticated" && !this.
|
|
1102
|
+
if ((await this.getAuthState()).status === "authenticated" && !this.isExchanging)
|
|
1103
1103
|
return;
|
|
1104
1104
|
const { codeVerifier: r } = await chrome.storage.session.get("codeVerifier"), o = chrome.identity.getRedirectURL("callback"), s = await fetch(this.authEndpoints.token, {
|
|
1105
1105
|
method: "POST",
|
|
@@ -1203,7 +1203,9 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
|
|
|
1203
1203
|
return this.sendApiRequest(
|
|
1204
1204
|
"POST",
|
|
1205
1205
|
"/bodhi/v1/apps/request-access",
|
|
1206
|
-
e
|
|
1206
|
+
e,
|
|
1207
|
+
void 0,
|
|
1208
|
+
!0
|
|
1207
1209
|
);
|
|
1208
1210
|
}
|
|
1209
1211
|
launchReview(e) {
|
|
@@ -1214,7 +1216,7 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
|
|
|
1214
1216
|
return;
|
|
1215
1217
|
}
|
|
1216
1218
|
if (!o) {
|
|
1217
|
-
await chrome.storage.session.remove(["codeVerifier", "state"]), r(
|
|
1219
|
+
await chrome.storage.session.remove(["codeVerifier", "state"]), r(l("oauth_error", "No redirect URL received"));
|
|
1218
1220
|
return;
|
|
1219
1221
|
}
|
|
1220
1222
|
t(o);
|
|
@@ -1224,14 +1226,14 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
|
|
|
1224
1226
|
async completeOAuthRedirect(e) {
|
|
1225
1227
|
const t = new URL(e), r = t.searchParams.get("error");
|
|
1226
1228
|
if (r)
|
|
1227
|
-
throw await chrome.storage.session.remove(["codeVerifier", "state"]), t.searchParams.get("error_source") === "bodhi" && M(r === "access_denied" ? "denied" : r),
|
|
1229
|
+
throw await chrome.storage.session.remove(["codeVerifier", "state"]), t.searchParams.get("error_source") === "bodhi" && M(r === "access_denied" ? "denied" : r), l("oauth_error", t.searchParams.get("error_description") ?? r);
|
|
1228
1230
|
const o = t.searchParams.get("code"), s = t.searchParams.get("state"), { state: i } = await chrome.storage.session.get("state");
|
|
1229
1231
|
if (!s || s !== i)
|
|
1230
|
-
throw await chrome.storage.session.remove(["codeVerifier", "state"]),
|
|
1232
|
+
throw await chrome.storage.session.remove(["codeVerifier", "state"]), l("oauth_error", "State mismatch");
|
|
1231
1233
|
if (!o)
|
|
1232
|
-
throw await chrome.storage.session.remove(["codeVerifier", "state"]),
|
|
1234
|
+
throw await chrome.storage.session.remove(["codeVerifier", "state"]), l("oauth_error", "No authorization code");
|
|
1233
1235
|
if (await this.exchangeCodeForTokens(o), await chrome.storage.session.remove(["codeVerifier", "state"]), (await this.getAuthState()).status !== "authenticated")
|
|
1234
|
-
throw
|
|
1236
|
+
throw l("oauth_error", "Login failed");
|
|
1235
1237
|
}
|
|
1236
1238
|
// Ext2Ext Communication (Private Methods)
|
|
1237
1239
|
// ============================================================================
|
|
@@ -1243,7 +1245,7 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
|
|
|
1243
1245
|
*/
|
|
1244
1246
|
async sendExtRequest(e, t) {
|
|
1245
1247
|
const r = await this.sendExtRequestRaw(e, t);
|
|
1246
|
-
if (
|
|
1248
|
+
if (x(r.response))
|
|
1247
1249
|
throw this.logger.error("[BodhiExtClient] Extension error:", r.response.error), new Error(
|
|
1248
1250
|
r.response.error.message || `Extension request failed: ${JSON.stringify(r.response)}`
|
|
1249
1251
|
);
|
|
@@ -1255,9 +1257,10 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
|
|
|
1255
1257
|
* @param endpoint API endpoint path
|
|
1256
1258
|
* @param body Optional request body
|
|
1257
1259
|
* @param headers Optional headers
|
|
1260
|
+
* @param authenticated Attach the current token when one exists (safe: skipped if none)
|
|
1258
1261
|
* @returns API response from LLM server via bodhi-browser-ext
|
|
1259
1262
|
*/
|
|
1260
|
-
async sendApiRequest(e, t, r, o) {
|
|
1263
|
+
async sendApiRequest(e, t, r, o, s = !1) {
|
|
1261
1264
|
if (!this.extensionId)
|
|
1262
1265
|
throw new T(
|
|
1263
1266
|
"not_initialized",
|
|
@@ -1267,24 +1270,29 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
|
|
|
1267
1270
|
`[BodhiExtClient] Sending API_REQUEST: method=${e}, endpoint=${t}`,
|
|
1268
1271
|
r ? { body: r } : ""
|
|
1269
1272
|
);
|
|
1270
|
-
|
|
1273
|
+
let i = o;
|
|
1274
|
+
if (s) {
|
|
1275
|
+
const c = await this._getAccessTokenRaw();
|
|
1276
|
+
c && (i = { ...o, Authorization: `Bearer ${c}` });
|
|
1277
|
+
}
|
|
1278
|
+
const n = crypto.randomUUID(), u = {
|
|
1271
1279
|
type: _.API_REQUEST,
|
|
1272
|
-
requestId:
|
|
1280
|
+
requestId: n,
|
|
1273
1281
|
request: {
|
|
1274
1282
|
method: e,
|
|
1275
1283
|
endpoint: t,
|
|
1276
1284
|
body: r,
|
|
1277
|
-
headers:
|
|
1285
|
+
headers: i
|
|
1278
1286
|
}
|
|
1279
1287
|
};
|
|
1280
|
-
return this.logger.debug(`[BodhiExtClient] Request ID: ${
|
|
1288
|
+
return this.logger.debug(`[BodhiExtClient] Request ID: ${n}, Extension: ${this.extensionId}`), new Promise((c, h) => {
|
|
1281
1289
|
try {
|
|
1282
|
-
chrome.runtime.sendMessage(this.extensionId,
|
|
1290
|
+
chrome.runtime.sendMessage(this.extensionId, u, (E) => {
|
|
1283
1291
|
if (chrome.runtime.lastError) {
|
|
1284
1292
|
this.logger.error(
|
|
1285
|
-
`[BodhiExtClient] Chrome runtime error for request ${
|
|
1293
|
+
`[BodhiExtClient] Chrome runtime error for request ${n}:`,
|
|
1286
1294
|
chrome.runtime.lastError
|
|
1287
|
-
),
|
|
1295
|
+
), h(
|
|
1288
1296
|
new T(
|
|
1289
1297
|
"extension_error",
|
|
1290
1298
|
chrome.runtime.lastError.message ?? "Chrome runtime error"
|
|
@@ -1292,19 +1300,19 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
|
|
|
1292
1300
|
);
|
|
1293
1301
|
return;
|
|
1294
1302
|
}
|
|
1295
|
-
if (this.logger.debug(`[BodhiExtClient] Response for request ${
|
|
1296
|
-
this.logger.error(`[BodhiExtClient] No response received for request ${
|
|
1303
|
+
if (this.logger.debug(`[BodhiExtClient] Response for request ${n}:`, E), !E) {
|
|
1304
|
+
this.logger.error(`[BodhiExtClient] No response received for request ${n}`), h(new T("extension_error", "No response from extension"));
|
|
1297
1305
|
return;
|
|
1298
1306
|
}
|
|
1299
|
-
|
|
1300
|
-
new T(
|
|
1301
|
-
)) : (this.logger.debug(`[BodhiExtClient] ✓ Valid API_RESPONSE for ${
|
|
1302
|
-
`[BodhiExtClient] Invalid response format for ${
|
|
1303
|
-
|
|
1304
|
-
),
|
|
1307
|
+
E.type === _.API_RESPONSE && E.requestId === n ? "error" in E ? (this.logger.error(`[BodhiExtClient] API error for ${n}:`, E.error), h(
|
|
1308
|
+
new T(E.error.type || "extension_error", E.error.message)
|
|
1309
|
+
)) : (this.logger.debug(`[BodhiExtClient] ✓ Valid API_RESPONSE for ${n}`), c(E.response)) : (this.logger.error(
|
|
1310
|
+
`[BodhiExtClient] Invalid response format for ${n}:`,
|
|
1311
|
+
E
|
|
1312
|
+
), h(new T("extension_error", "Invalid response format")));
|
|
1305
1313
|
});
|
|
1306
|
-
} catch (
|
|
1307
|
-
this.logger.error(`[BodhiExtClient] Exception sending message for ${
|
|
1314
|
+
} catch (E) {
|
|
1315
|
+
this.logger.error(`[BodhiExtClient] Exception sending message for ${n}:`, E), h(E);
|
|
1308
1316
|
}
|
|
1309
1317
|
});
|
|
1310
1318
|
}
|
|
@@ -1400,10 +1408,10 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
|
|
|
1400
1408
|
return;
|
|
1401
1409
|
}
|
|
1402
1410
|
try {
|
|
1403
|
-
let
|
|
1411
|
+
let u = { ...r.headers };
|
|
1404
1412
|
if (r.authenticated !== !1) {
|
|
1405
|
-
const
|
|
1406
|
-
if (!
|
|
1413
|
+
const d = await this._getAccessTokenRaw();
|
|
1414
|
+
if (!d) {
|
|
1407
1415
|
e.postMessage({
|
|
1408
1416
|
type: i,
|
|
1409
1417
|
requestId: t,
|
|
@@ -1411,21 +1419,21 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
|
|
|
1411
1419
|
});
|
|
1412
1420
|
return;
|
|
1413
1421
|
}
|
|
1414
|
-
|
|
1422
|
+
u = { ...u, Authorization: `Bearer ${d}` }, this.logger.debug("[BodhiExtClient] Injected auth token for stream relay");
|
|
1415
1423
|
}
|
|
1416
|
-
const
|
|
1417
|
-
this.activeStreamPorts.set(t,
|
|
1418
|
-
const
|
|
1424
|
+
const c = chrome.runtime.connect(this.extensionId, { name: o });
|
|
1425
|
+
this.activeStreamPorts.set(t, c);
|
|
1426
|
+
const h = setTimeout(() => {
|
|
1419
1427
|
this.activeStreamPorts.has(t) && (this.logger.error(`[BodhiExtClient] Stream timeout for ${t}`), e.postMessage({
|
|
1420
1428
|
type: i,
|
|
1421
1429
|
requestId: t,
|
|
1422
1430
|
error: { message: "Stream request timed out", type: "timeout_error" }
|
|
1423
1431
|
}), this.cleanupStreamPort(t));
|
|
1424
1432
|
}, p.STREAM_TIMEOUT);
|
|
1425
|
-
|
|
1426
|
-
n(
|
|
1427
|
-
}),
|
|
1428
|
-
clearTimeout(
|
|
1433
|
+
c.onMessage.addListener((d) => {
|
|
1434
|
+
n(d, e, t) && (clearTimeout(h), this.cleanupStreamPort(t));
|
|
1435
|
+
}), c.onDisconnect.addListener(() => {
|
|
1436
|
+
clearTimeout(h), this.activeStreamPorts.has(t) && (this.logger.error(`[BodhiExtClient] Bodhi port disconnected for ${t}`), e.postMessage({
|
|
1429
1437
|
type: i,
|
|
1430
1438
|
requestId: t,
|
|
1431
1439
|
error: {
|
|
@@ -1434,24 +1442,24 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
|
|
|
1434
1442
|
}
|
|
1435
1443
|
}), this.activeStreamPorts.delete(t));
|
|
1436
1444
|
});
|
|
1437
|
-
const
|
|
1445
|
+
const E = {
|
|
1438
1446
|
type: s,
|
|
1439
1447
|
requestId: t,
|
|
1440
1448
|
request: {
|
|
1441
1449
|
method: r.method,
|
|
1442
1450
|
endpoint: r.endpoint,
|
|
1443
1451
|
body: r.body,
|
|
1444
|
-
headers:
|
|
1452
|
+
headers: u
|
|
1445
1453
|
}
|
|
1446
1454
|
};
|
|
1447
|
-
this.logger.debug("[BodhiExtClient] Sending stream request to bodhi port:",
|
|
1448
|
-
} catch (
|
|
1449
|
-
const
|
|
1450
|
-
this.logger.error("[BodhiExtClient] Stream relay error:", JSON.stringify(
|
|
1455
|
+
this.logger.debug("[BodhiExtClient] Sending stream request to bodhi port:", E), c.postMessage(E);
|
|
1456
|
+
} catch (u) {
|
|
1457
|
+
const c = u;
|
|
1458
|
+
this.logger.error("[BodhiExtClient] Stream relay error:", JSON.stringify(c.message)), e.postMessage({
|
|
1451
1459
|
type: i,
|
|
1452
1460
|
requestId: t,
|
|
1453
1461
|
error: {
|
|
1454
|
-
message: `uncaught error: ${JSON.stringify({ error:
|
|
1462
|
+
message: `uncaught error: ${JSON.stringify({ error: c, message: c.message })}`,
|
|
1455
1463
|
type: "extension_error"
|
|
1456
1464
|
}
|
|
1457
1465
|
});
|
|
@@ -1479,23 +1487,23 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
|
|
|
1479
1487
|
e,
|
|
1480
1488
|
r,
|
|
1481
1489
|
o,
|
|
1482
|
-
|
|
1490
|
+
ne,
|
|
1483
1491
|
_.STREAM_REQUEST,
|
|
1484
1492
|
a.EXT2EXT_CLIENT_STREAM_ERROR,
|
|
1485
1493
|
(s, i, n) => {
|
|
1486
|
-
if (
|
|
1487
|
-
const
|
|
1488
|
-
return
|
|
1494
|
+
if (oe(s)) {
|
|
1495
|
+
const u = s.response, c = u.body;
|
|
1496
|
+
return u.status >= 400 ? (i.postMessage({
|
|
1489
1497
|
type: a.EXT2EXT_CLIENT_STREAM_API_ERROR,
|
|
1490
1498
|
requestId: n,
|
|
1491
|
-
response:
|
|
1492
|
-
}), !1) :
|
|
1499
|
+
response: u
|
|
1500
|
+
}), !1) : c?.done ? (i.postMessage({
|
|
1493
1501
|
type: a.EXT2EXT_CLIENT_STREAM_DONE,
|
|
1494
1502
|
requestId: n
|
|
1495
1503
|
}), this.logger.info(`[BodhiExtClient] Stream complete for ${n}`), !0) : (i.postMessage({
|
|
1496
1504
|
type: a.EXT2EXT_CLIENT_STREAM_CHUNK,
|
|
1497
1505
|
requestId: n,
|
|
1498
|
-
response:
|
|
1506
|
+
response: u
|
|
1499
1507
|
}), !1);
|
|
1500
1508
|
} else {
|
|
1501
1509
|
if (ae(s))
|
|
@@ -1506,7 +1514,7 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
|
|
|
1506
1514
|
requestId: n,
|
|
1507
1515
|
response: s.response
|
|
1508
1516
|
}), !1;
|
|
1509
|
-
if (
|
|
1517
|
+
if (ce(s))
|
|
1510
1518
|
return this.logger.error(
|
|
1511
1519
|
`[BodhiExtClient] Stream error for ${n}:`,
|
|
1512
1520
|
s.error.message
|
|
@@ -1532,7 +1540,7 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
|
|
|
1532
1540
|
e,
|
|
1533
1541
|
r,
|
|
1534
1542
|
o,
|
|
1535
|
-
|
|
1543
|
+
he,
|
|
1536
1544
|
_.STREAM_TEXT_REQUEST,
|
|
1537
1545
|
a.EXT2EXT_CLIENT_STREAM_TEXT_ERROR,
|
|
1538
1546
|
(s, i, n) => {
|
|
@@ -1626,7 +1634,7 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
|
|
|
1626
1634
|
} catch (t) {
|
|
1627
1635
|
this.logger.warn("Token refresh failed:", t);
|
|
1628
1636
|
}
|
|
1629
|
-
throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"),
|
|
1637
|
+
throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"), l(
|
|
1630
1638
|
"auth_error",
|
|
1631
1639
|
"Access token expired and unable to refresh. Try logging out and logging in again."
|
|
1632
1640
|
);
|
|
@@ -1665,7 +1673,7 @@ const Se = ["ggedphdcbekjlomjaidbajglgihbeaon"], Re = ["bjdjhiombmfbcoeojijpfckl
|
|
|
1665
1673
|
};
|
|
1666
1674
|
p.STREAM_TIMEOUT = 6e4;
|
|
1667
1675
|
let A = p;
|
|
1668
|
-
const
|
|
1676
|
+
const ye = "production";
|
|
1669
1677
|
export {
|
|
1670
1678
|
A as BodhiExtClient,
|
|
1671
1679
|
_e as ChromeSessionStorageAdapter,
|
|
@@ -1678,8 +1686,8 @@ export {
|
|
|
1678
1686
|
a as EXT2EXT_CLIENT_MESSAGE_TYPES,
|
|
1679
1687
|
U as EXT2EXT_CLIENT_STREAM_PORT,
|
|
1680
1688
|
D as EXT2EXT_CLIENT_STREAM_TEXT_PORT,
|
|
1681
|
-
|
|
1682
|
-
|
|
1689
|
+
ye as EXT_BUILD_MODE,
|
|
1690
|
+
xe as ExtUIClient,
|
|
1683
1691
|
Ae as InMemoryStorage,
|
|
1684
1692
|
ge as isExtClientApiError
|
|
1685
1693
|
};
|
package/dist/ext2ext-client.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export type { ExtClientApiError, ExtClientApiResponseMessage } from './messages'
|
|
|
17
17
|
export declare class BodhiExtClient {
|
|
18
18
|
private extensionId?;
|
|
19
19
|
private isAuthenticating;
|
|
20
|
-
private
|
|
20
|
+
private isExchanging;
|
|
21
21
|
private authClientId;
|
|
22
22
|
private authServerUrl;
|
|
23
23
|
private logger;
|
|
@@ -137,6 +137,7 @@ export declare class BodhiExtClient {
|
|
|
137
137
|
* @param endpoint API endpoint path
|
|
138
138
|
* @param body Optional request body
|
|
139
139
|
* @param headers Optional headers
|
|
140
|
+
* @param authenticated Attach the current token when one exists (safe: skipped if none)
|
|
140
141
|
* @returns API response from LLM server via bodhi-browser-ext
|
|
141
142
|
*/
|
|
142
143
|
private sendApiRequest;
|
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.44",
|
|
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.44",
|
|
41
|
+
"@bodhiapp/bodhi-js-core": "0.0.44",
|
|
42
|
+
"@bodhiapp/ts-client": "0.1.38"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@eslint/js": "^9.23.0",
|