UNPKG

crann

Version:

Effortless State Synchronization for Web Extensions

4 lines 57.1 kB
var ne=Object.defineProperty;var ke=Object.getOwnPropertyDescriptor;var He=Object.getOwnPropertyNames;var ze=Object.prototype.hasOwnProperty;var je=(r,e)=>()=>(r&&(e=r(r=0)),e);var _e=(r,e)=>{for(var t in e)ne(r,t,{get:e[t],enumerable:!0})},Qe=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of He(e))!ze.call(r,i)&&i!==t&&ne(r,i,{get:()=>e[i],enumerable:!(n=ke(e,i))||n.enumerable});return r};var Ge=r=>Qe(ne({},"__esModule",{value:!0}),r),Ce=(r,e,t,n)=>{for(var i=n>1?void 0:n?ke(e,t):e,o=r.length-1,s;o>=0;o--)(s=r[o])&&(i=(n?s(e,t,i):s(i))||i);return n&&i&&ne(e,t,i),i};var Le={};_e(Le,{ActionError:()=>U,ConfigError:()=>le,ConnectionError:()=>Te,CrannError:()=>k,LifecycleError:()=>q,StorageCollisionError:()=>Ie,ValidationError:()=>Q});var k,le,q,Te,U,Q,Ie,G=je(()=>{"use strict";k=class extends Error{constructor(e){super(e),this.name="CrannError",Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor)}},le=class extends k{constructor(t,n){super(n?`Config error in '${n}': ${t}`:`Config error: ${t}`);this.field=n;this.name="ConfigError"}},q=class extends k{constructor(t,n){super(`${n==="store"?"Store":"Agent"} "${t}" has been ${n==="store"?"destroyed":"disconnected"} and cannot be used.`);this.storeName=t;this.entity=n;this.name="LifecycleError"}},Te=class extends k{constructor(t,n,i){super(`Connection error for store "${n}": ${t}`);this.storeName=n;this.reason=i;this.name="ConnectionError"}},U=class extends k{constructor(t,n,i){super(`Action "${t}" failed in store "${n}": ${i}`);this.actionName=t;this.storeName=n;this.reason=i;this.name="ActionError"}},Q=class extends k{constructor(t,n,i){super(`Validation failed for action "${t}" in store "${n}": ${i}`);this.actionName=t;this.storeName=n;this.reason=i;this.name="ValidationError"}},Ie=class extends k{constructor(t){super(`Store name "${t}" is already in use. Each store must have a unique name. If you're trying to connect to an existing store, use connectStore() instead.`);this.storeName=t;this.name="StorageCollisionError"}}});import Ye from"webextension-polyfill";var W=(a=>(a.ContentScript="contentscript",a.Extension="extension",a.Popup="popup",a.Sidepanel="sidepanel",a.Devtools="devtools",a.Options="options",a.Unknown="unknown",a))(W||{});var S=class extends Error{constructor(t,n,i){super(n);this.type=t;this.details=i;this.name="PorterError"}};function Re(){return typeof ServiceWorkerGlobalScope!="undefined"&&self instanceof ServiceWorkerGlobalScope}var v=class v{constructor(e){this.context=e}static getLevel(){var t,n,i;return((t=v.globalOptions)==null?void 0:t.level)!==void 0?v.globalOptions.level:typeof process!="undefined"&&(((n=process.env)==null?void 0:n.NODE_ENV)==="production"||((i=process.env)==null?void 0:i.PORTER_ENV)==="production")?1:4}static configure(e){v.globalOptions=e,e.level!==void 0&&(v.level=e.level),e.enabled!==void 0&&(v.enabled=e.enabled)}static getLogger(e){return this.instances.has(e)||this.instances.set(e,new v(e)),this.instances.get(e)}error(e,...t){v.enabled&&v.level>=0&&console.error(`[Porter:${this.context}] ${e}`,...t)}warn(e,...t){v.enabled&&v.level>=1&&console.warn(`[Porter:${this.context}] ${e}`,...t)}info(e,...t){v.enabled&&v.level>=2&&console.info(`[Porter:${this.context}] ${e}`,...t)}debug(e,...t){v.enabled&&v.level>=3&&console.debug(`[Porter:${this.context}] ${e}`,...t)}trace(e,...t){v.enabled&&v.level>=4&&console.trace(`[Porter:${this.context}] ${e}`,...t)}};v.level=v.getLevel(),v.enabled=!1,v.instances=new Map;var x=v;import We from"webextension-polyfill";import{v4 as Xe}from"uuid";var ie=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 f,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 i=n.context,o=n.tabId||-1,s=n.frameId||0;this.logger.debug("Determined context for new agent",{determinedContext:i,tabId:o,frameId:s});let a=Array.from(this.agentsInfo.values()).filter(u=>u.location.context===i&&u.location.tabId===o&&u.location.frameId===s);a.length>0&&this.logger.debug("Adding agent: Found existing similar agent.",{tabAgentsInfo:a});let g=((m=(f=this.getAgentByLocation({context:i,tabId:o,frameId:s}))==null?void 0:f.info)==null?void 0:m.id)||Xe();this.logger.debug(`Adding agent with id: ${g}`),this.agents.set(g,e);let c={id:g,location:{context:i,tabId:o,frameId:s},createdAt:Date.now(),lastActiveAt:Date.now()};this.agentsInfo.set(g,c),this.logger.debug(`Constructed agent info: ${JSON.stringify(c)}`),e.onMessage.addListener(u=>this.emit("agentMessage",u,c));let l={port:e,info:c};return e.onDisconnect.addListener(()=>{this.emit("agentDisconnect",c),this.logger.debug("Agent disconnected, removing from manager. ",{agentInfo:c}),this.removeAgent(g)}),this.emit("agentSetup",l),this.logger.debug("Setup complete for adding agent. ",{agentInfo:c}),g}getAgentByLocation(e){let{context:t,tabId:n,frameId:i}=e,o=Array.from(this.agentsInfo.entries()).find(([c,l])=>l.location.context===t&&l.location.tabId===n&&l.location.frameId===i);if(o===void 0)return this.logger.error("No agent found for location. ",{location:e}),null;let s=o[0],a=this.agents.get(s),g=this.agentsInfo.get(s);return!a||!g?(this.logger.error("No agent found for location. ",{location:e}),null):{port:a,info:g}}getAgentsByContext(e){return Array.from(this.agentsInfo.entries()).filter(([n,i])=>i.location.context===e).map(([n,i])=>({port:this.agents.get(n),info:i}))}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,i])=>{let o=e.context?i.location.context===e.context:!0,s=e.tabId?i.location.tabId===e.tabId:!0,a=e.frameId?i.location.frameId===e.frameId:!0;return o&&s&&a}).map(([n,i])=>({port:this.agents.get(n),info:i}))}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(i=>i(...t))}identifyConnectionSource(e){var m,u,y,C,h,P,b;let t=e.sender;if(!t)return this.logger.error("Cannot add agent that did not have a sender"),null;let n=We.runtime.getManifest(),i=((m=n==null?void 0:n.side_panel)==null?void 0:m.default_path)||"",o=n.options_page||"",s=((u=n.action)==null?void 0:u.default_popup)||"",a=n.devtools_page||"",g=((y=n.chrome_url_overrides)==null?void 0:y.newtab)||"",c=((C=n.chrome_url_overrides)==null?void 0:C.bookmarks)||"",l=((h=n.chrome_url_overrides)==null?void 0:h.history)||"",f={sidepanel:i?i.split("/").pop():"sidepanel.html",options:o?o.split("/").pop():"options.html",popup:s?s.split("/").pop():"popup.html",devtools:a?a.split("/").pop():"devtools.html",newtab:g?g.split("/").pop():"newtab.html",bookmarks:c?c.split("/").pop():"bookmarks.html",history:l?l.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 N=new URL(t.url).pathname.split("/").pop();for(let[F,Me]of Object.entries(f))if(N===Me)return{context:F,tabId:((P=t.tab)==null?void 0:P.id)||0,frameId:t.frameId||0,url:t.url,portName:e.name};return{context:"unknown",tabId:((b=t.tab)==null?void 0:b.id)||0,frameId:t.frameId||0,url:t.url,portName:e.name}}return{context:"unknown",tabId:0,url:t.url,portName:e.name}}};var oe=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 S("invalid-context","Port name not provided");if(!e.name||!e.name.startsWith(this.namespace+":"))throw new S("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 S("invalid-context","Missing context or connection ID. Message was: "+JSON.stringify(t));let i=this.agentOperations.addAgent(e);if(!i)throw new S("invalid-context","Failed to add agent");let o=this.agentOperations.getAgentById(i);o&&this.confirmConnection(o),this.agentOperations.printAgents()}catch(n){this.handleConnectionError(e,n)}}handleConnectionError(e,t){let n=t instanceof S?t:new S("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(i){this.logger.error("Failed to send error message: ",{error:i})}try{e.disconnect()}catch(i){this.logger.error("Failed to disconnect port: ",{error:i})}}confirmConnection(e){if(this.logger.debug("Sending confirmation message back to initiator ",{agent:e}),!e.port)throw new S("invalid-port","Agent port is undefined when confirming connection");e.port.postMessage({action:"porter-handshake",payload:{info:e.info,currentConnections:this.agentOperations.getAllAgentsInfo()}})}};var se=class{constructor(e,t){this.agentOperations=e;this.logger=t;this.eventListeners=new Map;this.messageListeners=new Set;this.initializationHandler={"porter-messages-established":(n,i)=>{var s;if(!i||!i.id)return;let o=(s=this.agentOperations.getAgentById(i.id))==null?void 0:s.info;if(!o){this.logger.error("No agent info found for agent id: ",i.id);return}this.logger.debug("internalHandlers, established message received: ",i.id,n),this.emitEvent("onMessagesSet",o)}}}async post(e,t){return new Promise((n,i)=>{try{this.logger.debug("Post request received:",{action:e.action,target:t});let o=setTimeout(()=>{let s=new Error("Message posting timed out");this.logger.error("Post timeout:",s),i(s)},5e3);t===void 0?this.broadcastMessage(e):Je(t)?this.postToLocation(e,t):Ze(t)?this.postToContext(e,t):typeof t=="string"?this.postToId(e,t):this.postToTab(e,t),clearTimeout(o),n()}catch(o){let s=o instanceof Error?o.message:"Unknown error";this.logger.error("Failed to post message:",s),i(new Error(`Failed to post message: ${s}`))}})}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 S("message-failed",`Failed to post message to tabId ${t}`,{originalError:e});return}n.forEach(i=>{i.port&&this.postToPort(e,i.port)})}postToLocation(e,t){this.agentOperations.queryAgents(t).forEach(i=>{if(!i.port)throw new S("invalid-target","No port found for agent",{agentInfo:i.info});this.postToPort(e,i.port)})}postToContext(e,t){this.agentOperations.queryAgents({context:t}).forEach(i=>{if(!i.port)throw new S("invalid-target","No port found for agent",{agentInfo:i.info});this.postToPort(e,i.port)})}postToPort(e,t){try{t.postMessage(e)}catch(n){throw new S("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 S("invalid-target",`No agent found for key: ${t}`);this.postToPort(e,n.port)}onMessage(e){Array.from(this.messageListeners).find(i=>JSON.stringify(i.config)===JSON.stringify(e))&&this.logger.warn(`Listener with same config already exists: ${JSON.stringify(e)}`);let n={config:e,listener:i=>{let o=e[i.message.action];if(o){this.logger.debug("onMessage, calling handler ",{event:i});let{message:s,...a}=i;o(s,a)}else this.logger.debug("onMessage, no handler found ",{event:i})}};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(i=>i(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:i,...o}=e;n(i,o);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:i}of this.messageListeners)i[e.message.action]&&(n(e),t++,this.logger.debug("Message handled by registered listener: ",{listener:n,config:i}));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 Je(r){return typeof r=="object"&&r!==null&&"context"in r&&"tabId"in r&&"frameId"in r}function Ze(r){return typeof r=="string"&&Object.values(W).includes(r)}var E=class E{constructor(e,t){if((t==null?void 0:t.debug)!==void 0&&x.configure({enabled:t.debug}),this.logger=x.getLogger("SW"),this.namespace=e||"porter",e||this.logger.error('No namespace provided, defaulting to "porter"'),this.agentManager=new ie(this.logger),this.messageHandler=new se(this.agentManager,this.logger),this.connectionManager=new oe(this.agentManager,this.namespace,this.logger),this.logger.info(`Constructing Porter with namespace: ${this.namespace}`),!Re())throw new S("invalid-context","Can only create in a service worker");this.agentManager.on("agentMessage",(n,i)=>{this.messageHandler.handleIncomingMessage(n,i)}),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)}),Ye.runtime.onConnect.addListener(this.connectionManager.handleConnection.bind(this.connectionManager))}static getInstance(e="porter",t){return E.staticLogger.debug(`Getting instance for namespace: ${e}`),E.instances.has(e)?(t==null?void 0:t.debug)!==void 0&&x.configure({enabled:t.debug}):(E.staticLogger.info(`Creating new instance for namespace: ${e}`),E.instances.set(e,new E(e,t))),E.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)}};E.instances=new Map,E.staticLogger=x.getLogger("SW");var be=E;function V(r="porter",e){let t=be.getInstance(r,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 et from"webextension-polyfill";var ae=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 ce=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 ae(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=et.runtime.connect({name:t});let n=new Promise((i,o)=>{var g;let s=setTimeout(()=>o(new S("connection-timeout","Connection timed out waiting for handshake")),this.CONNECTION_TIMEOUT),a=c=>{var l,f;c.action==="porter-handshake"?(this.logger.debug("Received handshake:",c),clearTimeout(s),this.agentInfo=c.payload.info,this.logger.debug("Handshake agent info:",{agentInfo:this.agentInfo}),(l=this.port)==null||l.onMessage.removeListener(a),i()):c.action==="porter-error"&&(clearTimeout(s),(f=this.port)==null||f.onMessage.removeListener(a),this.logger.error("Error:",c),o(new S(c.payload.type,c.payload.message,c.payload.details)))};(g=this.port)==null||g.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 i={...t};n&&(i.target=n),this.port.postMessage(i),this.logger.debug("Successfully resent queued message:",{message:i})}catch(i){this.logger.error("Failed to process queued message:",i),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 ge=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,i=this.handlers.get(n)||[];i.length>0?(this.logger.debug(`Found ${i.length} handlers for action: ${n}`),i.forEach(o=>o(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(i){throw new S("message-failed","Failed to post message",{originalError:i,message:t,target:n})}}};var $=class ${constructor(e={}){var i,o;let t=(i=e.namespace)!=null?i:"porter",n=(o=e.agentContext)!=null?o:this.determineContext();e.debug!==void 0&&x.configure({enabled:e.debug}),this.logger=x.getLogger("Agent"),this.connectionManager=new ce(t,this.logger),this.messageHandler=new ge(this.logger),this.connectionManager.onReconnect(s=>{this.logger.info("Reconnected, re-wiring port listeners",{info:s}),this.setupPortListeners()}),this.logger.info("Initializing with options: ",{options:e,context:n}),this.initializeConnection()}static getInstance(e={}){return!$.instance||$.instance.connectionManager.getNamespace()!==e.namespace?$.instance=new $(e):e.debug!==void 0&&x.configure({enabled:e.debug}),$.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(i){this.logger.error("Failed to post message, queueing for retry",{error:i}),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)}};$.instance=null;var Ae=$;function _(r){let e=Ae.getInstance(r);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 Mn,useEffect as Pn,useCallback as En,useRef as kn,useMemo as Rn}from"react";var B={Shared:"shared",Agent:"agent"},R={Local:"local",Session:"session",None:"none"};function L(r){return typeof r=="object"&&r!==null&&"default"in r&&!("handler"in r)}function tt(r){return typeof r=="object"&&r!==null&&"handler"in r}function Oe(r){var t;let{ConfigError:e}=(G(),Ge(Le));if(!r.name)throw new e("'name' is required","name");if(typeof r.name!="string")throw new e("'name' must be a string","name");if(!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(r.name))throw new e("'name' must start with a letter and contain only letters, numbers, underscores, and hyphens","name");if(r.version!==void 0&&(typeof r.version!="number"||!Number.isInteger(r.version)||r.version<1))throw new e("'version' must be a positive integer","version");for(let[n,i]of Object.entries(r))if(!(n==="name"||n==="version"||n==="actions")&&L(i)){if(i.scope!==void 0&&i.scope!==B.Shared&&i.scope!==B.Agent)throw new e(`Invalid scope '${i.scope}'. Must be 'shared' or 'agent'`,n);if(i.persist!==void 0&&i.persist!==R.Local&&i.persist!==R.Session&&i.persist!==R.None)throw new e(`Invalid persist '${i.persist}'. Must be 'local', 'session', or 'none'`,n)}if(r.actions!==void 0){if(typeof r.actions!="object"||r.actions===null)throw new e("'actions' must be an object","actions");for(let[n,i]of Object.entries(r.actions)){if(!tt(i))throw new e(`Action '${n}' must have a 'handler' function`,`actions.${n}`);if(typeof i.handler!="function")throw new e(`Action '${n}' handler must be a function`,`actions.${n}`)}}return{...r,version:(t=r.version)!=null?t:1}}function K(r,e){if(r===e)return!0;if(r==null||typeof r!="object"||e==null||typeof e!="object")return!1;let t=Object.keys(r),n=Object.keys(e);if(t.length!==n.length)return!1;t.sort(),n.sort();for(let i=0;i<t.length;i++){let o=t[i];if(o!==n[i]||!K(r[o],e[o]))return!1}return!0}var de=class{constructor(e,t){this.agentStates=new Map;this.config=e,this.persistence=t,this.defaultSharedState=this.buildDefaultSharedState(),this.defaultAgentState=this.buildDefaultAgentState(),this.sharedState={...this.defaultSharedState}}getState(){return{...this.sharedState}}getSharedState(){return{...this.sharedState}}getAgentState(e){var t;return(t=this.agentStates.get(e))!=null?t:{...this.defaultAgentState}}getFullStateForAgent(e){let t=this.getAgentState(e);return{...this.sharedState,...t}}setState(e,t){var o;let n={},i={};for(let s of Object.keys(e)){let a=this.config[s];if(!L(a))continue;let g=e[s],c=a.scope===B.Agent;c&&t?i[s]=g:c||(n[s]=g)}if(Object.keys(n).length>0){let s={...this.sharedState,...n};K(this.sharedState,s)||(this.sharedState=s)}if(t&&Object.keys(i).length>0){let s=(o=this.agentStates.get(t))!=null?o:{...this.defaultAgentState},a={...s,...i};K(s,a)||this.agentStates.set(t,a)}return{sharedChanges:n,agentChanges:i}}setAgentState(e,t){var o;let n=(o=this.agentStates.get(e))!=null?o:{...this.defaultAgentState},i={...n,...t};K(n,i)||this.agentStates.set(e,i)}hydrateSharedState(e){this.sharedState={...this.defaultSharedState,...e}}initializeAgentState(e){this.agentStates.has(e)||this.agentStates.set(e,{...this.defaultAgentState})}removeAgentState(e){this.agentStates.delete(e)}clear(){this.sharedState={...this.defaultSharedState},this.agentStates.clear()}buildDefaultSharedState(){let e={};for(let[t,n]of Object.entries(this.config))t==="name"||t==="version"||t==="actions"||L(n)&&(!n.scope||n.scope===B.Shared)&&(e[t]=n.default);return e}buildDefaultAgentState(){let e={};for(let[t,n]of Object.entries(this.config))t==="name"||t==="version"||t==="actions"||L(n)&&n.scope===B.Agent&&(e[t]=n.default);return e}};import D from"webextension-polyfill";var fe=class{constructor(e,t={}){this.config=e,this.options=t}buildKey(e){return`crann:${this.config.name}:v${this.config.version}:${e}`}parseKey(e){let t=e.match(/^crann:([^:]+):v(\d+):(.+)$/);return t?{name:t[1],version:parseInt(t[2],10),key:t[3]}:null}getMetaKey(){return`crann:${this.config.name}:__meta`}async hydrate(){let[e,t]=await Promise.all([D.storage.local.get(null),D.storage.session.get(null)]),n={...e,...t},i={};for(let[o,s]of Object.entries(this.config)){if(o==="name"||o==="version"||o==="actions"||!L(s)||s.scope==="agent"||!s.persist||s.persist===R.None)continue;let a=this.buildKey(o);a in n&&(i[o]=n[a])}return await this.updateMetadata(),i}async persist(e){let t={},n={};for(let[o,s]of Object.entries(e)){let a=this.config[o];if(!L(a))continue;let g=this.buildKey(o);switch(a.persist){case R.Local:t[g]=s;break;case R.Session:n[g]=s;break}}let i=[];Object.keys(t).length>0&&i.push(D.storage.local.set(t)),Object.keys(n).length>0&&i.push(D.storage.session.set(n)),await Promise.all(i)}async clearAll(){let e=[];for(let[t,n]of Object.entries(this.config))t==="name"||t==="version"||t==="actions"||L(n)&&n.persist&&n.persist!==R.None&&e.push(this.buildKey(t));e.push(this.getMetaKey()),await Promise.all([D.storage.local.remove(e),D.storage.session.remove(e)])}async updateMetadata(){var i,o;let e=this.getMetaKey(),t=await D.storage.local.get(e),n={version:this.config.version,createdAt:(o=(i=t[e])==null?void 0:i.createdAt)!=null?o:Date.now(),lastAccessed:Date.now()};await D.storage.local.set({[e]:n})}};async function Ne(r){let{keepStores:e,dryRun:t=!1}=r,[n,i]=await Promise.all([D.storage.local.get(null),D.storage.session.get(null)]),o=new Set([...Object.keys(n),...Object.keys(i)]),s=[];for(let a of o){if(!a.startsWith("crann:"))continue;let g=a.match(/^crann:([^:]+):/);if(!g)continue;let c=g[1];e.includes(c)||s.push(a)}return!t&&s.length>0&&await Promise.all([D.storage.local.remove(s),D.storage.session.remove(s)]),{keys:s,count:s.length}}var ue=class{constructor(){this.agents=new Map}add(e){let t={id:e.id,tabId:e.location.tabId,frameId:e.location.frameId,context:e.location.context,connectedAt:Date.now()};return this.agents.set(e.id,t),t}remove(e){return this.agents.delete(e)}get(e){return this.agents.get(e)}getAll(){return Array.from(this.agents.values())}query(e){return this.getAll().filter(t=>!(e.context!==void 0&&t.context!==e.context||e.tabId!==void 0&&t.tabId!==e.tabId||e.frameId!==void 0&&t.frameId!==e.frameId))}has(e){return this.agents.has(e)}get size(){return this.agents.size}clear(){this.agents.clear()}};G();var he=class{constructor(e,t,n){this.config=e,this.getState=t,this.setState=n}async execute(e,t,n){let i=this.config.actions;if(!i)throw new U(e,this.config.name,"No actions defined");let o=i[e];if(!o)throw new U(e,this.config.name,"Unknown action");if(o.validate)try{o.validate(...t)}catch(a){throw new Q(e,this.config.name,a instanceof Error?a.message:String(a))}let s={state:this.getState(),setState:async a=>{await this.setState(a,n.id)},agentId:n.id,agentLocation:n.location};try{return await o.handler(s,...t)}catch(a){throw new U(e,this.config.name,a instanceof Error?a.message:String(a))}}hasAction(e){var t;return((t=this.config.actions)==null?void 0:t[e])!==void 0}getActionNames(){var e;return Object.keys((e=this.config.actions)!=null?e:{})}};G();var X=class{constructor(e,t={}){this.stateChangeListeners=new Set;this.agentConnectListeners=new Set;this.agentDisconnectListeners=new Set;this.isDestroyed=!1;var n;this.config=e,this.options=t,this.porter=V(e.name,{debug:(n=t.debug)!=null?n:!1}),this.persistence=new fe(e,t),this.stateManager=new de(e,this.persistence),this.agentRegistry=new ue,this.actionExecutor=new he(e,()=>this.getState(),(i,o)=>this.setState(i,o)),this.setupMessageHandlers(),this.hydrate()}getState(){return this.assertNotDestroyed(),this.stateManager.getState()}getAgentState(e){return this.assertNotDestroyed(),this.stateManager.getAgentState(e)}async setState(e,t){this.assertNotDestroyed();let{sharedChanges:n,agentChanges:i}=this.stateManager.setState(e,t);Object.keys(n).length>0&&await this.persistence.persist(n),this.notifyStateChange(e,t)}async setAgentState(e,t){this.assertNotDestroyed(),this.stateManager.setAgentState(e,t),this.notifyStateChange(t,e)}async clear(){this.assertNotDestroyed(),this.stateManager.clear(),await this.persistence.clearAll(),this.notifyStateChange({})}subscribe(e,t){this.assertNotDestroyed();let n,i;typeof e=="function"?i=e:(n=e,i=t);let o={keys:n,callback:i};return this.stateChangeListeners.add(o),()=>{this.stateChangeListeners.delete(o)}}onAgentConnect(e){return this.assertNotDestroyed(),this.agentConnectListeners.add(e),()=>{this.agentConnectListeners.delete(e)}}onAgentDisconnect(e){return this.assertNotDestroyed(),this.agentDisconnectListeners.add(e),()=>{this.agentDisconnectListeners.delete(e)}}getAgents(e){return this.assertNotDestroyed(),e?this.porter.queryAgents(e).map(n=>this.agentRegistry.get(n.info.id)).filter(n=>n!==void 0):this.agentRegistry.getAll()}destroy(e){this.isDestroyed||(this.isDestroyed=!0,this.stateChangeListeners.clear(),this.agentConnectListeners.clear(),this.agentDisconnectListeners.clear(),this.agentRegistry.clear(),e!=null&&e.clearPersisted&&this.persistence.clearAll())}setupMessageHandlers(){let e=new Set;this.porter.on({setState:(t,n)=>{n&&this.setState(t.payload.state,n.id)}}),this.porter.onMessagesSet(t=>{if(!t||e.has(t.id))return;e.add(t.id);let n=this.stateManager.getFullStateForAgent(t.id);this.porter.post({action:"initialState",payload:{state:n,info:t}},t.location)}),this.porter.onConnect(t=>{if(!t)return;let n=this.agentRegistry.add(t);this.stateManager.initializeAgentState(t.id),this.agentConnectListeners.forEach(i=>i(n))}),this.porter.onDisconnect(t=>{if(!t)return;let n=this.agentRegistry.get(t.id);n&&(this.agentRegistry.remove(t.id),this.stateManager.removeAgentState(t.id),e.delete(t.id),this.agentDisconnectListeners.forEach(i=>i(n)))}),this.setupRpcHandlers()}setupRpcHandlers(){this.porter.on({rpc:async(e,t)=>{if(!t)return;let{callId:n,actionName:i,args:o}=e.payload;try{let s=await this.actionExecutor.execute(i,o,t);this.porter.post({action:"rpcResult",payload:{callId:n,actionName:i,result:s,success:!0}},t.location)}catch(s){this.porter.post({action:"rpcResult",payload:{callId:n,actionName:i,error:s instanceof Error?s.message:String(s),success:!1}},t.location)}}})}async hydrate(){let e=await this.persistence.hydrate();Object.keys(e).length>0&&this.stateManager.hydrateSharedState(e)}notifyStateChange(e,t){var o;let n=this.getState(),i=t?(o=this.porter.getAgentById(t))==null?void 0:o.info:void 0;if(this.stateChangeListeners.forEach(s=>{if(!(s.keys&&!s.keys.some(g=>g in e)))try{s.callback(n,e,i)}catch(a){console.error("[Crann Store] Error in subscriber callback:",a)}}),t){let s=this.porter.getAgentById(t);s!=null&&s.info.location&&this.porter.post({action:"stateUpdate",payload:{state:e}},s.info.location)}else for(let s of this.agentRegistry.getAll())this.porter.post({action:"stateUpdate",payload:{state:e}},s.id)}assertNotDestroyed(){if(this.isDestroyed)throw new q(this.config.name,"store")}};function $e(r,e){return new X(r,e)}G();var J=class{constructor(e,t={}){this._agentInfo=null;this._isConnected=!1;this._isDisconnected=!1;this.subscribers=new Set;this.readyCallbacks=new Set;this.disconnectCallbacks=new Set;this.reconnectCallbacks=new Set;var n;this.config=e,this.options=t,this._state=this.buildDefaultState(),this.readyPromise=new Promise(i=>{this.readyResolve=i}),this.porter=_({namespace:e.name,debug:(n=t.debug)!=null?n:!1}),this.setupMessageHandlers(),this.actionsProxy=this.createActionsProxy()}ready(){return this.assertNotDisconnected(),this.readyPromise}onReady(e){return this.assertNotDisconnected(),this._isConnected&&setTimeout(()=>e(this._state),0),this.readyCallbacks.add(e),()=>{this.readyCallbacks.delete(e)}}getState(){return this.assertNotDisconnected(),{...this._state}}get state(){return this.getState()}async setState(e){this.assertNotDisconnected(),this._state={...this._state,...e},this.porter.post({action:"setState",payload:{state:e}})}subscribe(e,t){this.assertNotDisconnected();let n,i;typeof e=="function"?i=e:(n=e,i=t);let o={keys:n,callback:i};return this.subscribers.add(o),()=>{this.subscribers.delete(o)}}get actions(){return this.assertNotDisconnected(),this.actionsProxy}getInfo(){return this._agentInfo?{id:this._agentInfo.id,tabId:this._agentInfo.location.tabId,frameId:this._agentInfo.location.frameId,context:this._agentInfo.location.context}:null}onDisconnect(e){return this.assertNotDisconnected(),this.disconnectCallbacks.add(e),()=>{this.disconnectCallbacks.delete(e)}}onReconnect(e){return this.assertNotDisconnected(),this.reconnectCallbacks.add(e),()=>{this.reconnectCallbacks.delete(e)}}disconnect(){this._isDisconnected||(this._isDisconnected=!0,this._isConnected=!1,this.subscribers.clear(),this.readyCallbacks.clear(),this.disconnectCallbacks.clear(),this.reconnectCallbacks.clear())}setupMessageHandlers(){this.porter.on({initialState:e=>{let{state:t,info:n}=e.payload;this._state=t,this._agentInfo=n,this._isConnected=!0,this.readyResolve(this._state),this.readyCallbacks.forEach(i=>{try{i(this._state)}catch(o){console.error("[Crann Agent] Error in onReady callback:",o)}}),this.notifySubscribers(t)},stateUpdate:e=>{let{state:t}=e.payload;this._state={...this._state,...t},this.notifySubscribers(t)}}),this.porter.onDisconnect(()=>{this._isConnected=!1,this.disconnectCallbacks.forEach(e=>{try{e()}catch(t){console.error("[Crann Agent] Error in onDisconnect callback:",t)}})}),this.porter.onReconnect(e=>{this._agentInfo=e,this._isConnected=!0,this.reconnectCallbacks.forEach(t=>{try{t(this._state)}catch(n){console.error("[Crann Agent] Error in onReconnect callback:",n)}})})}notifySubscribers(e){this.subscribers.forEach(t=>{if(!(t.keys&&!t.keys.some(i=>i in e)))try{t.callback(e,this._state)}catch(n){console.error("[Crann Agent] Error in subscriber callback:",n)}})}createActionsProxy(){let e=new Map,t=0;return this.porter.on({rpcResult:n=>{let{callId:i,result:o,error:s,success:a}=n.payload,g=e.get(i);g&&(e.delete(i),a?g.resolve(o):g.reject(new Error(s)))}}),new Proxy({},{get:(n,i)=>async(...o)=>{let s=`${t++}`;return new Promise((a,g)=>{e.set(s,{resolve:a,reject:g}),this.porter.post({action:"rpc",payload:{callId:s,actionName:i,args:o}})})}})}buildDefaultState(){let e={};for(let[t,n]of Object.entries(this.config))t==="name"||t==="version"||t==="actions"||typeof n=="object"&&n!==null&&"default"in n&&(e[t]=n.default);return e}assertNotDisconnected(){if(this._isDisconnected)throw new q(this.config.name,"agent")}};function qe(r,e){return new J(r,e)}G();import Se from"webextension-polyfill";var pe={Instance:"instance",Service:"service"},nt={Session:"session",Local:"local",None:"none"},Z=r=>!("handler"in r),me=r=>"handler"in r;var Y=class Y{static setDebug(e){Y._debug=e}static isDebugEnabled(){return Y._debug}};Y._debug=!1;var ee=Y;function Ue(){return ee.isDebugEnabled()}function xe(r,e,t){let n=t.value;return t.value=function(...i){var o;if(Ue()){let s=new Error().stack,g=(o=((s==null?void 0:s.split(`
`))||[])[3])==null?void 0:o.match(/at\s+(\S+)\s+/),c=g?g[1]:"unknown",l=i.length>1?i[1]:void 0,f={source:c,timestamp:Date.now(),instanceKey:i[0],changes:l};console.log("Crann State Change:",f)}return n.apply(this,i)},t}var A=class A{constructor(e){this.tag=null;this.context=e}static setDebug(e){A.debug=e}static setPrefix(e){A.prefix=e}setTag(e){this.tag=e}withTag(e){let t=new A(this.context);return t.setTag(e),t}getFullContext(){return this.tag?`${this.context}:${this.tag}`:this.context}createLogMethods(){let e=this.getFullContext(),t=`%c${A.prefix}%c [%c${e}%c]`,n="color: #3fcbff; font-weight: bold",i="color: #d58cff; font-weight: bold",o="";return A.debug?{debug:console.log.bind(console,t,n,o,i,o),log:console.log.bind(console,t,n,o,i,o),info:console.info.bind(console,t,n,o,i,o),warn:console.warn.bind(console,t,n,o,i,o),error:console.error.bind(console,t,n,o,i,o)}:{debug:A.noOp,log:A.noOp,info:A.noOp,warn:A.noOp,error:A.noOp}}get debug(){return this.createLogMethods().debug}get log(){return this.createLogMethods().log}get info(){return this.createLogMethods().info}get warn(){return this.createLogMethods().warn}get error(){return this.createLogMethods().error}static forContext(e,t){let n=new A(e);return t&&n.setTag(t),n}};A.debug=!1,A.prefix="CrannLogger",A.noOp=function(){};var I=A;function T(r,e={terse:!0}){let n=(i=>{let o=String(i);return o.length>=4?o.slice(-4):o.padStart(4,"0")})(r.location.tabId);return e!=null&&e.terse?`${n}:${r.location.frameId}`:`${r.location.context}:${n}:${r.location.frameId}`}function Be(r,e,t,n,i){var l,f;let o=new Map,s=new Map,a=(l=r.context)!=null&&l.isServiceWorker?"Core":"Agent",g=I.forContext(`${a}:RPC`);return(f=r.context)!=null&&f.agentInfo&&g.setTag(T(r.context.agentInfo)),r.addEventListener("message",m=>{g.debug("Message received:",m);let[u,y]=m.data;if("call"in y&&"args"in y.call){g.debug("Processing call message:",y);let C=y.call,{id:h,args:P,target:b}=C,z=t[h];if(!z){r.postMessage([u,{error:{id:h,error:"Action not found",target:b}}]);return}try{if(z.validate&&z.validate(...P),!b){r.postMessage([u,{error:{id:h,error:"No target provided for action call",target:b}}]);return}let N=e();g.debug("Executing action with most current state:",N),Promise.resolve(z.handler(N,n,b,...P)).then(F=>{g.debug("Action handler result:",{result:F,target:b}),r.postMessage([u,{result:{id:h,result:F,target:b}}])},F=>{r.postMessage([u,{error:{id:h,error:F.message,target:b}}])})}catch(N){N instanceof Error?r.postMessage([u,{error:{id:h,error:N.message,target:b}}]):r.postMessage([u,{error:{id:h,error:"Unknown error occurred",target:b}}])}}else if("result"in y){let C=y.result,h=o.get(u);h&&(h(C.result),o.delete(u))}else if("error"in y){let C=y.error,h=o.get(u);h&&(h(Promise.reject(new Error(C.error))),o.delete(u))}else if("release"in y){let C=y.release,h=s.get(C.id);h&&(h.clear(),s.delete(C.id))}}),new Proxy({},{get(m,u){return(...y)=>{let C=Math.random();return new Promise((h,P)=>{o.set(C,b=>{b instanceof Promise?b.then(h,P):h(b)}),r.postMessage([C,{call:{id:u,args:y}}])})}}})}function ye(r,e,t,n){let i=t||V("crann"),o=i.type!=="agent",s=I.forContext(o?"Core:RPC":"Agent:RPC"),a={postMessage:(g,c)=>{if(o){s.debug("Posting message from service worker:",{message:g,transferables:c});let[,l]=g,f=it(l);if(!f){s.warn("No target specified for RPC response in service worker");return}i.post({action:"rpc",payload:{message:g,transferables:c||[]}},f)}else{let l=i.getAgentInfo();if(!l){s.warn("No agent info found for posting message",{agentInfo:l});return}let f=T(l),[,m]=g;"call"in m&&(m.call.target=l==null?void 0:l.location),s.withTag(f).debug("Sending RPC message from agent:",{rpcPayload:m,message:g}),i.post({action:"rpc",payload:{message:g,transferables:c||[]}})}},addEventListener:(g,c)=>{i.on({rpc:(l,f)=>{try{if(!f)s.debug("RPC message received:",{message:l,event:g});else{let h=T(f);s.withTag(h).debug("RPC message received:",{message:l,event:g})}let{payload:m}=l,{message:u,transferables:y=[]}=m,C=new MessageEvent("message",{data:u,ports:y.filter(h=>h instanceof MessagePort)||[]});c(C)}catch(m){s.error("Failed to parse RPC message payload:",m)}}})},removeEventListener:()=>{},context:{isServiceWorker:o,agentInfo:o?void 0:i.getAgentInfo()}};return Be(a,r,e,n)}function it(r){if("result"in r)return r.result.target;if("error"in r)return r.error.target;if("call"in r)return r.call.target;if("release"in r)return r.release.target}var O=class O{constructor(e,t){this.config=e;this.instances=new Map;this.stateChangeListeners=[];this.instanceReadyListeners=[];this.storagePrefix="crann_";this.porter=V("crann",{debug:!1});var g;t!=null&&t.debug&&(ee.setDebug(!0),I.setDebug(!0)),this.storagePrefix=(g=t==null?void 0:t.storagePrefix)!=null?g:this.storagePrefix,this.logger=I.forContext("Core"),this.logger.log("Constructing Crann with new logger"),this.defaultInstanceState=this.initializeInstanceDefault(),this.defaultServiceState=this.serviceState=this.initializeServiceDefault(),this.hydrate(),this.logger.log("Crann constructed, setting initial message handlers"),this.porter.on({setState:(c,l)=>{if(!l){this.logger.warn("setState message heard from unknown agent");return}let f=T(l);this.logger.withTag(f).log("Setting state:",c),this.set(c.payload.state,l.id)}});let n=new Set;this.porter.onMessagesSet(c=>{if(!c){this.logger.error("Messages set but no agent info.",{info:c});return}let l=T(c);if(this.logger.withTag(l).log("onMessagesSet received for agent:",{id:c.id,context:c.location.context,tabId:c.location.tabId,frameId:c.location.frameId,alreadyInitialized:n.has(c.id)}),n.has(c.id)){this.logger.withTag(l).log("Already sent initialState to agent, skipping:",c.id);return}n.add(c.id),this.logger.withTag(l).log("Messages set received. Sending initial state.",{info:c});let f=this.get(c.id);this.porter.post({action:"initialState",payload:{state:f,info:c}},c.location),this.notifyInstanceReady(c.id,c)}),this.porter.onConnect(c=>{if(!c){this.logger.error("Agent connected but no agent info.",{info:c});return}let l=T(c);this.logger.withTag(l).log("Agent connected",{info:c}),this.addInstance(c.id,l),this.porter.onDisconnect(f=>{this.logger.withTag(T(f)).log("Agent disconnect heard. Connection type, context and location:",{info:f}),this.removeInstance(f.id)})});let o=(c,l)=>l!==void 0?this.set(c,l):this.set(c),s=this.extractActions(e),a=()=>{let c=this.get();return this.logger.log("State getter called, returning current state:",c),c};this.rpcEndpoint=ye(a,s,this.porter,o)}static getInstance(e,t){return O.instance?t!=null&&t.debug&&I.forContext("Core").log("Instance requested and already existed, returning"):O.instance=new O(e,t),O.instance}async addInstance(e,t){if(this.instances.has(e))this.logger.withTag(t).log("Instance was already registered, ignoring request from key");else{this.logger.withTag(t).log("Adding instance from agent key");let n={...this.defaultInstanceState};this.instances.set(e,n)}}async removeInstance(e){this.instances.has(e)?(this.logger.withTag(e).log("Remove instance requested"),this.instances.delete(e)):this.logger.withTag(e).log("Remove instance requested but it did not exist!")}async setServiceState(e){this.logger.log("Request to set service state with update:",e),this.logger.log("Existing service state was ",this.serviceState);let t={...this.serviceState,...e};K(this.serviceState,t)?this.logger.log("New state seems to be the same as existing, skipping"):(this.logger.log("Confirmed new state was different than existing so proceeding to persist then notify all connected instances."),this.serviceState=t,await this.persist(e),this.notify(e))}async setInstanceState(e,t){this.logger.withTag(e).log("Request to update instance state, update:",t);let n=this.instances.get(e)||this.defaultInstanceState,i={...n,...t};K(n,i)?this.logger.withTag(e).log("Instance state update is not different, skipping update."):(this.logger.withTag(e).log("Instance state update is different, updating and notifying."),this.instances.set(e,i),this.notify(t,e))}async persist(e){this.logger.log("Persisting state");let t=!1;for(let n in e||this.serviceState){let o=this.config[n].persist||"none",s=e?e[n]:this.serviceState[n];switch(o){case"session":await Se.storage.session.set({[this.storagePrefix+n]:s}),t=!0;break;case"local":await Se.storage.local.set({[this.storagePrefix+n]:s}),t=!0;break;default:break}}t?this.logger.log("State was persisted"):this.logger.log("Nothing to persist")}async clear(){this.logger.log("Clearing state"),this.serviceState=this.defaultServiceState,this.instances.forEach((e,t)=>{this.instances.set(t,this.defaultInstanceState)}),await this.persist(),this.notify({})}subscribe(e){this.logger.log("Subscribing to state"),this.stateChangeListeners.push(e)}notify(e,t){let n=t?this.porter.getAgentById(t):void 0,i=t?this.get(t):this.get();this.stateChangeListeners.length>0&&(this.logger.log("Notifying state change listeners in source"),this.stateChangeListeners.forEach(o=>{o(i,e,n==null?void 0:n.info)})),t&&(n!=null&&n.info.location)?(this.logger.withTag(t).log("Notifying of state change."),this.porter.post({action:"stateUpdate",payload:{state:e}},n.info.location)):(this.logger.log("Notifying everyone"),this.instances.forEach((o,s)=>{this.porter.post({action:"stateUpdate",payload:{state:e}},s)}))}get(e){return e?{...this.serviceState,...this.instances.get(e)}:{...this.serviceState}}findInstance(e){let t=this.porter.getAgentByLocation(e);if(!t)return this.logger.log("Could not find agent for location:",{location:e}),null;for(let[n,i]of this.instances)if(n===t.info.id)return this.logger.log("Found instance for key:",n),n;return this.logger.log("Could not find instance for context and location:",{location:e}),null}queryAgents(e){return this.porter.queryAgents(e)}async set(e,t){let n={},i={};for(let o in e){let s=this.config[o];if(rt(s)){if(s.partition==="instance"){let a=o,g=e;n[a]=g[a]}else if(!s.partition||s.partition===pe.Service){let a=o,g=e;i[a]=g[a]}}}t&&Object.keys(n).length>0&&(this.logger.withTag(t).log("Setting instance state:",n),this.setInstanceState(t,n)),Object.keys(i).length>0&&(this.logger.log("Setting service state:",i),this.setServiceState(i))}async hydrate(){this.logger.log("Hydrating state from storage.");let e=await Se.storage.local.get(null),t=await Se.storage.session.get(null),n={...e,...t};this.logger.log("Storage data is:",{local:e,session:t,combined:n});let i={},o=!1;for(let s in n){let a=this.removePrefix(s);if(this.logger.log(`Checking storage key ${s} -> ${a}`),this.config.hasOwnProperty(a)){let g=n[s];this.logger.log(`Found storage value for ${a}:`,g),i[a]=g,o=!0}}o?this.logger.log("Hydrated some items."):this.logger.log("No items found in storage."),this.serviceState={...this.defaultServiceState,...i}}removePrefix(e){return e.startsWith(this.storagePrefix)?e.replace(this.storagePrefix,""):e}initializeInstanceDefault(){let e={};return Object.keys(this.config).forEach(t=>{let n=this.config[t];Z(n)&&n.partition==="instance"&&(e[t]=n.default)}),e}initializeServiceDefault(){this.logger.log("Initializing service default state"),this.logger.log("Config is:",this.config);let e={};return Object.keys(this.config).forEach(t=>{let n=this.config[t];this.logger.log("Item is:",n),Z(n)&&(!n.partition||n.partition===pe.Service)&&(e[t]=n.default,this.logger.log("Setting service state for key:",t,"to",n.default))}),this.logger.log("Final service state is:",e),e}subscribeToInstanceReady(e){return this.logger.log("Subscribing to instance ready events"),this.instanceReadyListeners.push(e),this.instances.forEach((t,n)=>{let i=this.porter.getAgentById(n);i!=null&&i.info&&e(n,i.info)}),()=>{this.logger.log("Unsubscribing from instance ready events");let t=this.instanceReadyListeners.indexOf(e);t!==-1&&this.instanceReadyListeners.splice(t,1)}}notifyInstanceReady(e,t){if(this.instanceReadyListeners.length>0){let n=T(t);this.logger.withTag(n).log("Notifying instance ready listeners"),this.instanceReadyListeners.forEach(i=>{i(e,t)})}}extractActions(e){return Object.entries(e).filter(([t,n])=>me(n)).reduce((t,[n,i])=>{let o=i;return{...t,[n]:o}},{})}};O.instance=null,Ce([xe],O.prototype,"setServiceState",1),Ce([xe],O.prototype,"setInstanceState",1);var ve=O;function ot(r,e){let t=ve.getInstance(r,e);return{get:t.get.bind(t),set:t.set.bind(t),subscribe:t.subscribe.bind(t),onInstanceReady:t.subscribeToInstanceReady.bind(t),findInstance:t.findInstance.bind(t),queryAgents:t.queryAgents.bind(t),clear:t.clear.bind(t)}}function rt(r){return r&&typeof r=="object"&&"default"in r}var M={connected:!1},H=null,De=new Set,we=new Set;function st(r,e){let t=(e==null?void 0:e.debug)||!1,n=e==null?void 0:e.context;t&&I.setDebug(!0);let i=I.forContext("Agent"),o,s="unset",a=new Set;if(i.log("Initializing Crann Agent"+(n?` with context: ${n}`:"")),H&&M.connected)return i.log("We had an instance already and it's connected, returning"),i.log("Connect, calling onReady callback"),setTimeout(()=>{a.forEach(d=>d(M))},0),H;H&&!M.connected&&(i.log("We had an instance but it's disconnected, creating new connection"),H=null),i.log("No existing instance, creating a new one");let g=_({namespace:"crann",debug:!1});i.log("Porter connection created"),g.onDisconnect(()=>{i.log("Porter connection lost, updating connection status"),M={connected:!1},De.forEach(d=>{try{d()}catch(p){i.error("Error in disconnect callback:",p)}}),a.forEach(d=>{try{d(M)}catch(p){i.error("Error in onReady callback during disconnect:",p)}})}),g.onReconnect(d=>{i.log("Porter reconnected, updating connection status",d),M={connected:!0,agent:d},o=d,s=T(d),i.setTag(s),we.forEach(p=>{try{p(d)}catch(w){i.error("Error in reconnect callback:",w)}}),a.forEach(p=>{try{p(M)}catch(w){i.error("Error in onReady callback during reconnect:",w)}})});let c=Object.entries(r).filter(([d,p])=>me(p)).reduce((d,[p,w])=>{let j=w;return{...d,[p]:{type:"action",handler:j.handler,validate:j.validate}}},{}),l=ye(()=>Ke(r),c,g),f=!1;g.on({initialState:d=>{if(i.log("initialState received",{alreadyReceived:f,payload:d.payload}),f){i.log("Ignoring duplicate initialState message");return}f=!0,m=d.payload.state,o=d.payload.info,s=T(o),M={connected:!0,agent:o},i.setTag(s),i.log(`Initial state received and ${y.size} listeners notified`,{message:d}),a.forEach(p=>{i.log("Calling onReady callbacks"),p(M)}),y.forEach(p=>{p.callback(m)})},stateUpdate:d=>{u=d.payload.state,m={...m,...u},i.log("State updated:",{message:d,changes:u,_state:m}),u&&y.forEach(p=>{(p.keys===void 0||p.keys.some(j=>j in u))&&p.callback(u)})}}),i.log("Porter connected. Setting up state and listeners");let m=Ke(r),u=null,y=new Set;i.log("Completed setup, returning instance");let C=()=>m,h=d=>{i.log("Calling post with setState",d),g.post({action:"setState",payload:{state:d}})},P=(d,p)=>{let w={keys:p,callback:d};return y.add(w),()=>{y.delete(w)}};return H={useCrann:d=>{let p=()=>C()[d],w=te=>h({[d]:te}),j=te=>{let Pe=p();return P(Fe=>{if(d in Fe){let Ee=p(),Ve=C();te({current:Ee,previous:Pe,state:Ve}),Pe=Ee}},[d])};return[p(),w,j]},get:C,set:h,subscribe:P,getAgentInfo:()=>o,onReady:d=>(i.log("onReady callback added"),a.add(d),M.connected&&(i.log("calling onReady callback"),setTimeout(()=>{d(M)},0)),()=>a.delete(d)),callAction:async(d,...p)=>(i.log("Calling action",d,p),l[d](...p)),onDisconnect:d=>(i.log("onDisconnect callback added"),De.add(d),()=>{De.delete(d)}),onReconnect:d=>(i.log("onReconnect callback added"),we.add(d),()=>{we.delete(d)})},H}function at(){return H!==null}function Ke(r){let e={};return Object.keys(r).forEach(t=>{let n=r[t];Z(n)&&(e[t]=n.default)}),e}export{U as ActionError,J as Agent,le as ConfigError,ve as Crann,k as CrannError,q as LifecycleError,pe as Partition,R as Persist,nt as Persistence,B as Scope,X as Store,Q as ValidationError,Ne as clearOrphanedData,st as connect,qe as connectStore,at as connected,ot as create,Oe as createConfig,$e as createStore};
//# sourceMappingURL=index.js.map