UNPKG

porter-source

Version:

Messaging Library for Web Extensions

3 lines (2 loc) 26.5 kB
import te from"webextension-polyfill";var P=(a=>(a.ContentScript="contentscript",a.Extension="extension",a.Popup="popup",a.Sidepanel="sidepanel",a.Devtools="devtools",a.Options="options",a.Unknown="unknown",a))(P||{}),K=(o=>(o.NewTab="new-tab",o.NewFrame="new-frame",o.Refresh="refresh",o.NewExtensionContext="new-extension-context",o))(K||{}),L=(i=>(i.CONNECTION_FAILED="connection-failed",i.CONNECTION_TIMEOUT="connection-timeout",i.INVALID_TARGET="invalid-target",i.MESSAGE_FAILED="message-failed",i.INVALID_CONTEXT="invalid-context",i.INVALID_PORT="invalid-port",i))(L||{}),c=class extends Error{constructor(t,n,o){super(n);this.type=t;this.details=o;this.name="PorterError"}};function z(){return typeof ServiceWorkerGlobalScope!="undefined"&&self instanceof ServiceWorkerGlobalScope}var V=(r=>(r[r.ERROR=0]="ERROR",r[r.WARN=1]="WARN",r[r.INFO=2]="INFO",r[r.DEBUG=3]="DEBUG",r[r.TRACE=4]="TRACE",r))(V||{}),l=class l{constructor(e){this.context=e}static getLevel(){var t,n;return((t=l.globalOptions)==null?void 0:t.level)!==void 0?l.globalOptions.level:typeof process!="undefined"&&((n=process.env)==null?void 0:n.PORTER_ENV)==="production"?1:4}static configure(e){l.globalOptions=e,e.level!==void 0&&(l.level=e.level),e.enabled!==void 0&&(l.enabled=e.enabled)}static getLogger(e){return this.instances.has(e)||this.instances.set(e,new l(e)),this.instances.get(e)}error(e,...t){l.enabled&&l.level>=0&&console.error(`[Porter:${this.context}] ${e}`,...t)}warn(e,...t){l.enabled&&l.level>=1&&console.warn(`[Porter:${this.context}] ${e}`,...t)}info(e,...t){l.enabled&&l.level>=2&&console.info(`[Porter:${this.context}] ${e}`,...t)}debug(e,...t){l.enabled&&l.level>=3&&console.debug(`[Porter:${this.context}] ${e}`,...t)}trace(e,...t){l.enabled&&l.level>=4&&console.trace(`[Porter:${this.context}] ${e}`,...t)}};l.level=l.getLevel(),l.enabled=!1,l.instances=new Map;var p=l;import j from"webextension-polyfill";import{v4 as Z}from"uuid";var S=class{constructor(e){this.logger=e;this.agents=new Map;this.agentsInfo=new Map;this.eventHandlers=new Map;this.eventHandlers.set("agentSetup",new Set),this.eventHandlers.set("agentMessage",new Set),this.eventHandlers.set("agentDisconnect",new Set)}addAgent(e,t){var A,m;this.logger.debug("Adding agent",{context:t,port:e});let n=this.identifyConnectionSource(e);if(!n){this.logger.error("Cannot add agent that did not have a sender");return}let o=n.context,r=n.tabId||-1,i=n.frameId||0;this.logger.debug("Determined context for new agent",{determinedContext:o,tabId:r,frameId:i});let a=Array.from(this.agentsInfo.values()).filter(f=>f.location.context===o&&f.location.tabId===r&&f.location.frameId===i);a.length>0&&this.logger.debug("Adding agent: Found existing similar agent.",{tabAgentsInfo:a});let u=((m=(A=this.getAgentByLocation({context:o,tabId:r,frameId:i}))==null?void 0:A.info)==null?void 0:m.id)||Z();this.logger.debug(`Adding agent with id: ${u}`),this.agents.set(u,e);let g={id:u,location:{context:o,tabId:r,frameId:i},createdAt:Date.now(),lastActiveAt:Date.now()};this.agentsInfo.set(u,g),this.logger.debug(`Constructed agent info: ${JSON.stringify(g)}`),e.onMessage.addListener(f=>this.emit("agentMessage",f,g));let d={port:e,info:g};return e.onDisconnect.addListener(()=>{this.emit("agentDisconnect",g),this.logger.debug("Agent disconnected, removing from manager. ",{agentInfo:g}),this.removeAgent(u)}),this.emit("agentSetup",d),this.logger.debug("Setup complete for adding agent. ",{agentInfo:g}),u}getAgentByLocation(e){let{context:t,tabId:n,frameId:o}=e,r=Array.from(this.agentsInfo.entries()).find(([g,d])=>d.location.context===t&&d.location.tabId===n&&d.location.frameId===o);if(r===void 0)return this.logger.error("No agent found for location. ",{location:e}),null;let i=r[0],a=this.agents.get(i),u=this.agentsInfo.get(i);return!a||!u?(this.logger.error("No agent found for location. ",{location:e}),null):{port:a,info:u}}getAgentsByContext(e){return Array.from(this.agentsInfo.entries()).filter(([n,o])=>o.location.context===e).map(([n,o])=>({port:this.agents.get(n),info:o}))}getAllAgents(){return Array.from(this.agentsInfo.entries()).map(([t,n])=>({port:this.agents.get(t),info:n}))}queryAgents(e){return Array.from(this.agentsInfo.entries()).filter(([n,o])=>{let r=e.context?o.location.context===e.context:!0,i=e.tabId?o.location.tabId===e.tabId:!0,a=e.frameId?o.location.frameId===e.frameId:!0;return r&&i&&a}).map(([n,o])=>({port:this.agents.get(n),info:o}))}getAgentById(e){let t=this.agents.get(e),n=this.agentsInfo.get(e);return!t||!n?(this.logger.error("No agent found for agentId. ",{id:e}),null):{port:t,info:n}}getAllAgentsInfo(){return Array.from(this.agentsInfo.values())}hasPort(e){return!!Array.from(this.agents.values()).find(n=>n.name===e.name)}removeAgent(e){this.agents.has(e)&&this.agentsInfo.has(e)?(this.agents.delete(e),this.agentsInfo.delete(e)):this.logger.error("No agent found to remove. ",{agentId:e})}printAgents(){let e=Array.from(this.agents.entries()),t=Array.from(this.agentsInfo.entries());this.logger.debug("Current agents:",{allAgents:e,allAgentsInfo:t})}on(e,t){let n=this.eventHandlers.get(e);n&&n.add(t)}emit(e,...t){let n=this.eventHandlers.get(e);n==null||n.forEach(o=>o(...t))}identifyConnectionSource(e){var m,f,v,y,x,T,h;let t=e.sender;if(!t)return this.logger.error("Cannot add agent that did not have a sender"),null;let n=j.runtime.getManifest(),o=((m=n==null?void 0:n.side_panel)==null?void 0:m.default_path)||"",r=n.options_page||"",i=((f=n.action)==null?void 0:f.default_popup)||"",a=n.devtools_page||"",u=((v=n.chrome_url_overrides)==null?void 0:v.newtab)||"",g=((y=n.chrome_url_overrides)==null?void 0:y.bookmarks)||"",d=((x=n.chrome_url_overrides)==null?void 0:x.history)||"",A={sidepanel:o?o.split("/").pop():"sidepanel.html",options:r?r.split("/").pop():"options.html",popup:i?i.split("/").pop():"popup.html",devtools:a?a.split("/").pop():"devtools.html",newtab:u?u.split("/").pop():"newtab.html",bookmarks:g?g.split("/").pop():"bookmarks.html",history:d?d.split("/").pop():"history.html"};if(t.tab&&t.url&&!t.url.includes("extension://"))return{context:"contentscript",tabId:t.tab.id,frameId:t.frameId||0,url:t.url,portName:e.name};if(t.url&&t.url.includes("extension://")){let M=new URL(t.url).pathname.split("/").pop();for(let[R,U]of Object.entries(A))if(M===U)return{context:R,tabId:((T=t.tab)==null?void 0:T.id)||0,frameId:t.frameId||0,url:t.url,portName:e.name};return{context:"unknown",tabId:((h=t.tab)==null?void 0:h.id)||0,frameId:t.frameId||0,url:t.url,portName:e.name}}return{context:"unknown",tabId:0,url:t.url,portName:e.name}}};var N=class{constructor(e,t,n){this.agentOperations=e;this.namespace=t;this.logger=n}handleConnection(e){try{if(this.logger.info("New connection request:",e.name),!e.name)throw new c("invalid-context","Port name not provided");if(!e.name||!e.name.startsWith(this.namespace+":"))throw new c("invalid-context",`Invalid namespace or port name format. port name was ${(e==null?void 0:e.name)||"port undefined"} but namespace is ${this.namespace}`);e.onMessage.addListener(this.handleInitMessage.bind(this,e)),setTimeout(()=>{if(!this.agentOperations.hasPort(e))try{e.disconnect()}catch(t){this.logger.error("Failed to disconnect port:",t)}},5e3)}catch(t){this.handleConnectionError(e,t)}}handleInitMessage(e,t){if(t.action==="porter-init")try{e.onMessage.removeListener(this.handleInitMessage.bind(this,e));let{connectionId:n}=t.payload;if(!n)throw new c("invalid-context","Missing context or connection ID. Message was: "+JSON.stringify(t));let o=this.agentOperations.addAgent(e);if(!o)throw new c("invalid-context","Failed to add agent");let r=this.agentOperations.getAgentById(o);r&&this.confirmConnection(r),this.agentOperations.printAgents()}catch(n){this.handleConnectionError(e,n)}}handleConnectionError(e,t){let n=t instanceof c?t:new c("connection-failed",t instanceof Error?t.message:"Unknown connection error",{originalError:t});this.logger.error("Connection handling failed: ",{porterError:n});try{e.postMessage({action:"porter-error",payload:{error:n}})}catch(o){this.logger.error("Failed to send error message: ",{error:o})}try{e.disconnect()}catch(o){this.logger.error("Failed to disconnect port: ",{error:o})}}confirmConnection(e){if(this.logger.debug("Sending confirmation message back to initiator ",{agent:e}),!e.port)throw new c("invalid-port","Agent port is undefined when confirming connection");e.port.postMessage({action:"porter-handshake",payload:{info:e.info,currentConnections:this.agentOperations.getAllAgentsInfo()}})}};var O=class{constructor(e,t){this.agentOperations=e;this.logger=t;this.eventListeners=new Map;this.messageListeners=new Set;this.initializationHandler={"porter-messages-established":(n,o)=>{var i;if(!o||!o.id)return;let r=(i=this.agentOperations.getAgentById(o.id))==null?void 0:i.info;if(!r){this.logger.error("No agent info found for agent id: ",o.id);return}this.logger.debug("internalHandlers, established message received: ",o.id,n),this.emitEvent("onMessagesSet",r)}}}async post(e,t){return new Promise((n,o)=>{try{this.logger.debug("Post request received:",{action:e.action,target:t});let r=setTimeout(()=>{let i=new Error("Message posting timed out");this.logger.error("Post timeout:",i),o(i)},5e3);t===void 0?this.broadcastMessage(e):Y(t)?this.postToLocation(e,t):ee(t)?this.postToContext(e,t):typeof t=="string"?this.postToId(e,t):this.postToTab(e,t),clearTimeout(r),n()}catch(r){let i=r instanceof Error?r.message:"Unknown error";this.logger.error("Failed to post message:",i),o(new Error(`Failed to post message: ${i}`))}})}broadcastMessage(e){this.logger.info("Broadcasting message to all agents: ",e),this.agentOperations.getAllAgents().forEach(t=>{t.port&&t.port.postMessage(e)})}postToTab(e,t){let n=this.agentOperations.queryAgents({context:"contentscript",tabId:t});if(n.length===0){throw this.logger.warn("post: No agents found for tab: ",t),new c("message-failed",`Failed to post message to tabId ${t}`,{originalError:e});return}n.forEach(o=>{o.port&&this.postToPort(e,o.port)})}postToLocation(e,t){this.agentOperations.queryAgents(t).forEach(o=>{if(!o.port)throw new c("invalid-target","No port found for agent",{agentInfo:o.info});this.postToPort(e,o.port)})}postToContext(e,t){this.agentOperations.queryAgents({context:t}).forEach(o=>{if(!o.port)throw new c("invalid-target","No port found for agent",{agentInfo:o.info});this.postToPort(e,o.port)})}postToPort(e,t){try{t.postMessage(e)}catch(n){throw new c("message-failed","Failed to post message to port",{originalError:n,message:e})}}postToId(e,t){let n=this.agentOperations.getAgentById(t);if(!(n!=null&&n.port))throw new c("invalid-target",`No agent found for key: ${t}`);this.postToPort(e,n.port)}onMessage(e){Array.from(this.messageListeners).find(o=>JSON.stringify(o.config)===JSON.stringify(e))&&this.logger.warn(`Listener with same config already exists: ${JSON.stringify(e)}`);let n={config:e,listener:o=>{let r=e[o.message.action];if(r){this.logger.debug("onMessage, calling handler ",{event:o});let{message:i,...a}=o;r(i,a)}else this.logger.debug("onMessage, no handler found ",{event:o})}};return this.messageListeners.add(n),()=>{this.messageListeners.delete(n)}}on(e){return this.onMessage(e)}handleIncomingMessage(e,t){this.logger.debug("Received message",{message:e,info:t}),this.emitMessage({...t,message:e})}emitEvent(e,t){var n;this.logger.debug("emitting event: ",e,t),(n=this.eventListeners.get(e))==null||n.forEach(o=>o(t))}emitMessage(e){if(this.logger.debug("Dispatching incoming message to subscribers",{messageEvent:e}),e.message.action.startsWith("porter-")){let n=this.initializationHandler[e.message.action];if(n){this.logger.debug("Internal message being handled",{messageEvent:e});let{message:o,...r}=e;n(o,r);return}}e.message.target&&(this.logger.debug("Relaying message to target:",e.message.target),this.post(e.message,e.message.target));let t=0;this.logger.trace("Processing message with registered handlers");for(let{listener:n,config:o}of this.messageListeners)o[e.message.action]&&(n(e),t++,this.logger.debug("Message handled by registered listener: ",{listener:n,config:o}));t===0?this.logger.warn("No handler found for message:",e.message.action):this.logger.debug(`Message handled by ${t} registered listeners`)}addListener(e,t){return this.eventListeners.has(e)||this.eventListeners.set(e,new Set),this.eventListeners.get(e).add(t),()=>{var n;(n=this.eventListeners.get(e))==null||n.delete(t)}}handleDisconnect(e){this.messageListeners.forEach(t=>{t.config[e.id]&&this.messageListeners.delete(t)}),this.logger.info("Agent disconnected:",{info:e}),this.emitEvent("onDisconnect",e)}handleConnect(e){this.logger.info("Agent connected:",{info:e}),this.emitEvent("onConnect",e)}onConnect(e){return this.addListener("onConnect",e)}onMessagesSet(e){return this.addListener("onMessagesSet",e)}onDisconnect(e){return this.addListener("onDisconnect",e)}};function Y(s){return typeof s=="object"&&s!==null&&"context"in s&&"tabId"in s&&"frameId"in s}function ee(s){return typeof s=="string"&&Object.values(P).includes(s)}var b=class b{constructor(e,t){if((t==null?void 0:t.debug)!==void 0&&p.configure({enabled:t.debug}),this.logger=p.getLogger("SW"),this.namespace=e||"porter",e||this.logger.error('No namespace provided, defaulting to "porter"'),this.agentManager=new S(this.logger),this.messageHandler=new O(this.agentManager,this.logger),this.connectionManager=new N(this.agentManager,this.namespace,this.logger),this.logger.info(`Constructing Porter with namespace: ${this.namespace}`),!z())throw new c("invalid-context","Can only create in a service worker");this.agentManager.on("agentMessage",(n,o)=>{this.messageHandler.handleIncomingMessage(n,o)}),this.agentManager.on("agentDisconnect",n=>{this.messageHandler.handleDisconnect(n)}),this.agentManager.on("agentSetup",n=>{this.logger.debug("Handling agent setup",{agent:n}),this.messageHandler.handleConnect(n.info),this.connectionManager.confirmConnection(n)}),te.runtime.onConnect.addListener(this.connectionManager.handleConnection.bind(this.connectionManager))}static getInstance(e="porter",t){return b.staticLogger.debug(`Getting instance for namespace: ${e}`),b.instances.has(e)?(t==null?void 0:t.debug)!==void 0&&p.configure({enabled:t.debug}):(b.staticLogger.info(`Creating new instance for namespace: ${e}`),b.instances.set(e,new b(e,t))),b.instances.get(e)}post(e,t){return this.messageHandler.post(e,t)}onMessage(e){return this.messageHandler.onMessage(e)}on(e){return this.messageHandler.on(e)}onConnect(e){return this.messageHandler.onConnect(e)}onDisconnect(e){return this.messageHandler.onDisconnect(e)}onMessagesSet(e){return this.messageHandler.onMessagesSet(e)}getInfo(e){var t;return((t=this.agentManager.getAgentById(e))==null?void 0:t.info)||null}getAgentById(e){return this.agentManager.getAgentById(e)}getAgentByLocation(e){return this.agentManager.getAgentByLocation(e)}queryAgents(e){return this.agentManager.queryAgents(e)}};b.instances=new Map,b.staticLogger=p.getLogger("SW");var $=b;function ne(s="porter",e){let t=$.getInstance(s,e);return{type:"source",post:t.post.bind(t),onMessage:t.onMessage.bind(t),on:t.on.bind(t),onConnect:t.onConnect.bind(t),onDisconnect:t.onDisconnect.bind(t),onMessagesSet:t.onMessagesSet.bind(t),getAgentById:t.getAgentById.bind(t),getAgentByLocation:t.getAgentByLocation.bind(t),queryAgents:t.queryAgents.bind(t)}}import oe from"webextension-polyfill";var D=class{constructor(e){this.queue=[];this.maxQueueSize=1e3;this.maxMessageAge=5*60*1e3;this.logger=e,this.logger.debug("MessageQueue initialized",{maxQueueSize:this.maxQueueSize,maxMessageAge:`${this.maxMessageAge/1e3} seconds`})}enqueue(e,t){let n=this.queue.length;this.cleanup(),n!==this.queue.length&&this.logger.debug(`Cleaned up ${n-this.queue.length} old messages`),this.queue.length>=this.maxQueueSize&&(this.logger.warn("Message queue is full, dropping oldest message",{queueSize:this.queue.length,maxSize:this.maxQueueSize}),this.queue.shift()),this.queue.push({message:e,target:t,timestamp:Date.now()}),this.logger.debug("Message queued",{queueSize:this.queue.length,message:e,target:t,timestamp:new Date().toISOString()})}dequeue(){let e=[...this.queue];return this.queue=[],this.logger.info(`Dequeued ${e.length} messages`,{oldestMessage:e[0]?new Date(e[0].timestamp).toISOString():null,newestMessage:e[e.length-1]?new Date(e[e.length-1].timestamp).toISOString():null}),e}isEmpty(){return this.queue.length===0}cleanup(){let e=Date.now(),t=this.queue.length;this.queue=this.queue.filter(n=>e-n.timestamp<this.maxMessageAge),t!==this.queue.length&&this.logger.debug(`Cleaned up ${t-this.queue.length} expired messages`,{remaining:this.queue.length,maxAge:`${this.maxMessageAge/1e3} seconds`})}};var k=class{constructor(e,t){this.namespace=e;this.CONNECTION_TIMEOUT=5e3;this.RECONNECT_INTERVAL=1e3;this.connectionTimer=null;this.reconnectTimer=null;this.agentInfo=null;this.port=null;this.isReconnecting=!1;this.reconnectAttemptCount=0;this.disconnectCallbacks=new Set;this.reconnectCallbacks=new Set;this.connectionId=`${Date.now()}-${Math.random().toString(36).substring(2,9)}`,this.logger=t,this.messageQueue=new D(t)}onDisconnect(e){return this.disconnectCallbacks.add(e),()=>{this.disconnectCallbacks.delete(e)}}onReconnect(e){return this.reconnectCallbacks.add(e),()=>{this.reconnectCallbacks.delete(e)}}emitDisconnect(){this.logger.debug("Emitting disconnect event",{callbackCount:this.disconnectCallbacks.size}),this.disconnectCallbacks.forEach(e=>{try{e()}catch(t){this.logger.error("Error in disconnect callback:",t)}})}emitReconnect(e){this.logger.debug("Emitting reconnect event",{callbackCount:this.reconnectCallbacks.size,info:e}),this.reconnectCallbacks.forEach(t=>{try{t(e)}catch(n){this.logger.error("Error in reconnect callback:",n)}})}async initializeConnection(){var e;try{this.connectionTimer&&clearTimeout(this.connectionTimer);let t=`${this.namespace}:${this.connectionId}`;this.logger.debug("Connecting new port with name: ",{portName:t}),this.port=oe.runtime.connect({name:t});let n=new Promise((o,r)=>{var u;let i=setTimeout(()=>r(new c("connection-timeout","Connection timed out waiting for handshake")),this.CONNECTION_TIMEOUT),a=g=>{var d,A;g.action==="porter-handshake"?(this.logger.debug("Received handshake:",g),clearTimeout(i),this.agentInfo=g.payload.info,this.logger.debug("Handshake agent info:",{agentInfo:this.agentInfo}),(d=this.port)==null||d.onMessage.removeListener(a),o()):g.action==="porter-error"&&(clearTimeout(i),(A=this.port)==null||A.onMessage.removeListener(a),this.logger.error("Error:",g),r(new c(g.payload.type,g.payload.message,g.payload.details)))};(u=this.port)==null||u.onMessage.addListener(a)});(e=this.port)==null||e.postMessage({action:"porter-init",payload:{info:this.agentInfo,connectionId:this.connectionId}}),await n,await this.processQueuedMessages()}catch(t){throw this.logger.error("Connection initialization failed:",t),this.handleDisconnect(this.port),t}}async processQueuedMessages(){if(!this.port||this.messageQueue.isEmpty())return;let e=this.messageQueue.dequeue();this.logger.info(`Processing ${e.length} queued messages after reconnection`);for(let{message:t,target:n}of e)try{let o={...t};n&&(o.target=n),this.port.postMessage(o),this.logger.debug("Successfully resent queued message:",{message:o})}catch(o){this.logger.error("Failed to process queued message:",o),this.messageQueue.enqueue(t,n),this.logger.debug("Re-queued failed message for retry")}}getPort(){return this.port}getAgentInfo(){return this.agentInfo}getNamespace(){return this.namespace}handleDisconnect(e){this.logger.info("Port disconnected",{portName:e.name,connectionId:this.connectionId,queuedMessages:this.messageQueue.isEmpty()?0:"some"}),this.port=null,this.agentInfo=null,this.emitDisconnect(),this.isReconnecting||this.startReconnectionAttempts()}startReconnectionAttempts(){this.isReconnecting=!0,this.reconnectAttemptCount=0,this.reconnectTimer&&clearInterval(this.reconnectTimer),this.logger.info("Starting reconnection attempts",{interval:this.RECONNECT_INTERVAL,queuedMessages:this.messageQueue.isEmpty()?0:"some"}),this.reconnectTimer=setInterval(async()=>{this.reconnectAttemptCount++;try{this.logger.debug(`Reconnection attempt ${this.reconnectAttemptCount}`),await this.initializeConnection(),this.isReconnecting=!1,this.reconnectTimer&&(clearInterval(this.reconnectTimer),this.reconnectTimer=null),this.logger.info("Reconnection successful",{attempts:this.reconnectAttemptCount,queuedMessages:this.messageQueue.isEmpty()?0:"some"}),this.agentInfo&&this.emitReconnect(this.agentInfo)}catch(e){this.logger.debug(`Reconnection attempt ${this.reconnectAttemptCount} failed:`,e)}},this.RECONNECT_INTERVAL)}queueMessage(e,t){this.messageQueue.enqueue(e,t),this.logger.debug("Message queued for retry",{message:e,target:t,queueSize:this.messageQueue.isEmpty()?0:"some"})}};var q=class{constructor(e){this.logger=e;this.MAX_QUEUE_SIZE=1e3;this.MESSAGE_TIMEOUT=3e4;this.messageQueue=[];this.handlers=new Map}handleMessage(e,t){if(this.logger.debug("handleMessage, message: ",t),this.handlers.size===0){if(this.messageQueue.length>=this.MAX_QUEUE_SIZE){this.logger.warn("Message queue full, dropping message:",t);return}this.logger.warn("No message handlers configured yet, queueing message: ",t),this.messageQueue.push({message:t,timestamp:Date.now()});return}this.processMessage(e,t)}onMessage(e){this.logger.debug("Setting message handlers from config: ",e),this.handlers.clear(),this.on(e),this.processQueuedMessages()}on(e){this.logger.debug("Adding message handlers from config: ",e),Object.entries(e).forEach(([t,n])=>{this.handlers.has(t)||this.handlers.set(t,[]),this.handlers.get(t).push(n)}),this.processQueuedMessages()}processQueuedMessages(){for(;this.messageQueue.length>0;){let e=this.messageQueue[0];if(Date.now()-e.timestamp>this.MESSAGE_TIMEOUT){this.logger.warn("Message timeout, dropping message: ",this.messageQueue.shift());continue}this.processMessage(null,e.message),this.messageQueue.shift()}}processMessage(e,t){let n=t.action,o=this.handlers.get(n)||[];o.length>0?(this.logger.debug(`Found ${o.length} handlers for action: ${n}`),o.forEach(r=>r(t))):this.logger.debug(`No handlers for message with action: ${n}`)}post(e,t,n){this.logger.debug("Sending message",{action:t.action,target:n,hasPayload:!!t.payload});try{n&&(t.target=n),e.postMessage(t)}catch(o){throw new c("message-failed","Failed to post message",{originalError:o,message:t,target:n})}}};var I=class I{constructor(e={}){var o,r;let t=(o=e.namespace)!=null?o:"porter",n=(r=e.agentContext)!=null?r:this.determineContext();e.debug!==void 0&&p.configure({enabled:e.debug}),this.logger=p.getLogger("Agent"),this.connectionManager=new k(t,this.logger),this.messageHandler=new q(this.logger),this.connectionManager.onReconnect(i=>{this.logger.info("Reconnected, re-wiring port listeners",{info:i}),this.setupPortListeners()}),this.logger.info("Initializing with options: ",{options:e,context:n}),this.initializeConnection()}static getInstance(e={}){return!I.instance||I.instance.connectionManager.getNamespace()!==e.namespace?I.instance=new I(e):e.debug!==void 0&&p.configure({enabled:e.debug}),I.instance}async initializeConnection(){await this.connectionManager.initializeConnection(),this.setupPortListeners()}setupPortListeners(){let e=this.connectionManager.getPort();e?(this.logger.debug("Setting up port listeners"),e.onMessage.addListener(t=>this.messageHandler.handleMessage(e,t)),e.onDisconnect.addListener(t=>this.connectionManager.handleDisconnect(t))):this.logger.warn("Cannot setup port listeners: no port available")}onMessage(e){this.messageHandler.onMessage(e);let t=this.connectionManager.getPort();t==null||t.postMessage({action:"porter-messages-established"})}on(e){this.messageHandler.on(e);let t=this.connectionManager.getPort();t==null||t.postMessage({action:"porter-messages-established"})}post(e,t){let n=this.connectionManager.getPort();if(this.logger.debug("Posting message",{message:e,target:t,port:n}),n)try{this.messageHandler.post(n,e,t)}catch(o){this.logger.error("Failed to post message, queueing for retry",{error:o}),this.connectionManager.queueMessage(e,t)}else this.logger.debug("No port found, queueing message",{message:e,target:t}),this.connectionManager.queueMessage(e,t)}determineContext(){return window.location.protocol.includes("extension")?"extension":"contentscript"}getAgentInfo(){return this.connectionManager.getAgentInfo()||null}onDisconnect(e){return this.connectionManager.onDisconnect(e)}onReconnect(e){return this.connectionManager.onReconnect(e)}};I.instance=null;var Q=I;function F(s){let e=Q.getInstance(s);return{type:"agent",post:e.post.bind(e),onMessage:e.onMessage.bind(e),on:e.on.bind(e),getAgentInfo:e.getAgentInfo.bind(e),onDisconnect:e.onDisconnect.bind(e),onReconnect:e.onReconnect.bind(e)}}import{useState as B,useEffect as se,useCallback as G,useRef as E,useMemo as re}from"react";function ie(s){let[e,t]=B(!1),[n,o]=B(!1),[r,i]=B(null),[a,u]=B(null),g=E(null),d=E(null),A=E(null),m=E([]),f=re(()=>({agentContext:s==null?void 0:s.agentContext,namespace:s==null?void 0:s.namespace,debug:s==null?void 0:s.debug}),[s==null?void 0:s.agentContext,s==null?void 0:s.namespace,s==null?void 0:s.debug]),v=E(s==null?void 0:s.onDisconnect),y=E(s==null?void 0:s.onReconnect);v.current=s==null?void 0:s.onDisconnect,y.current=s==null?void 0:s.onReconnect,se(()=>{let h=!0;return(async()=>{try{let{post:M,on:R,getAgentInfo:U,onDisconnect:W,onReconnect:_}=F(f);if(h){g.current=M,d.current=R,A.current=U,t(!0),o(!1),i(null);let X=W(()=>{var w;h&&(t(!1),o(!0),u(null),(w=v.current)==null||w.call(v))});m.current.push(X);let J=_(w=>{var H;h&&(t(!0),o(!1),u(w),(H=y.current)==null||H.call(y,w))});m.current.push(J),R({"porter-handshake":w=>{h&&u(w.payload.info)}})}}catch(M){h&&(console.error("[PORTER] initializePorter error ",M),i(M instanceof Error?M:new Error("Failed to connect to Porter")),t(!1),o(!1))}})(),()=>{h=!1,m.current.forEach(M=>M()),m.current=[]}},[f]);let x=G(h=>{if(g.current)try{g.current(h)}catch(C){i(C instanceof Error?C:new Error("Failed to send message"))}else i(new Error("Porter is not connected"))},[]),T=G(h=>{if(d.current)try{d.current(h)}catch(C){i(C instanceof Error?C:new Error("Failed to set message handlers"))}else i(new Error("Porter is not connected"))},[]);return{post:x,on:T,isConnected:e,isReconnecting:n,error:r,agentInfo:a}}export{K as ConnectionType,V as LogLevel,p as Logger,P as PorterContext,c as PorterError,L as PorterErrorType,F as connect,ne as source,ie as usePorter}; //# sourceMappingURL=index.js.map