UNPKG

porter-source

Version:

Messaging Library for Web Extensions

3 lines (2 loc) 12.8 kB
"use strict";var V=Object.create;var E=Object.defineProperty;var G=Object.getOwnPropertyDescriptor;var H=Object.getOwnPropertyNames;var W=Object.getPrototypeOf,K=Object.prototype.hasOwnProperty;var X=(n,e)=>{for(var t in e)E(n,t,{get:e[t],enumerable:!0})},L=(n,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of H(e))!K.call(n,r)&&r!==t&&E(n,r,{get:()=>e[r],enumerable:!(s=G(e,r))||s.enumerable});return n};var J=(n,e,t)=>(t=n!=null?V(W(n)):{},L(e||!n||!n.__esModule?E(t,"default",{value:n,enumerable:!0}):t,n)),Z=n=>L(E({},"__esModule",{value:!0}),n);var Y={};X(Y,{usePorter:()=>k});module.exports=Z(Y);var i=require("react");var h=class extends Error{constructor(t,s,r){super(s);this.type=t;this.details=r;this.name="PorterError"}};var O=J(require("webextension-polyfill"));var R=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 s=this.queue.length;this.cleanup(),s!==this.queue.length&&this.logger.debug(`Cleaned up ${s-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(s=>e-s.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 w=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 R(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(s){this.logger.error("Error in reconnect callback:",s)}})}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=O.default.runtime.connect({name:t});let s=new Promise((r,m)=>{var p;let g=setTimeout(()=>m(new h("connection-timeout","Connection timed out waiting for handshake")),this.CONNECTION_TIMEOUT),v=a=>{var f,I;a.action==="porter-handshake"?(this.logger.debug("Received handshake:",a),clearTimeout(g),this.agentInfo=a.payload.info,this.logger.debug("Handshake agent info:",{agentInfo:this.agentInfo}),(f=this.port)==null||f.onMessage.removeListener(v),r()):a.action==="porter-error"&&(clearTimeout(g),(I=this.port)==null||I.onMessage.removeListener(v),this.logger.error("Error:",a),m(new h(a.payload.type,a.payload.message,a.payload.details)))};(p=this.port)==null||p.onMessage.addListener(v)});(e=this.port)==null||e.postMessage({action:"porter-init",payload:{info:this.agentInfo,connectionId:this.connectionId}}),await s,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:s}of e)try{let r={...t};s&&(r.target=s),this.port.postMessage(r),this.logger.debug("Successfully resent queued message:",{message:r})}catch(r){this.logger.error("Failed to process queued message:",r),this.messageQueue.enqueue(t,s),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 x=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,s])=>{this.handlers.has(t)||this.handlers.set(t,[]),this.handlers.get(t).push(s)}),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 s=t.action,r=this.handlers.get(s)||[];r.length>0?(this.logger.debug(`Found ${r.length} handlers for action: ${s}`),r.forEach(m=>m(t))):this.logger.debug(`No handlers for message with action: ${s}`)}post(e,t,s){this.logger.debug("Sending message",{action:t.action,target:s,hasPayload:!!t.payload});try{s&&(t.target=s),e.postMessage(t)}catch(r){throw new h("message-failed","Failed to post message",{originalError:r,message:t,target:s})}}};var o=class o{constructor(e){this.context=e}static getLevel(){var t,s;return((t=o.globalOptions)==null?void 0:t.level)!==void 0?o.globalOptions.level:typeof process<"u"&&((s=process.env)==null?void 0:s.PORTER_ENV)==="production"?1:4}static configure(e){o.globalOptions=e,e.level!==void 0&&(o.level=e.level),e.enabled!==void 0&&(o.enabled=e.enabled)}static getLogger(e){return this.instances.has(e)||this.instances.set(e,new o(e)),this.instances.get(e)}error(e,...t){o.enabled&&o.level>=0&&console.error(`[Porter:${this.context}] ${e}`,...t)}warn(e,...t){o.enabled&&o.level>=1&&console.warn(`[Porter:${this.context}] ${e}`,...t)}info(e,...t){o.enabled&&o.level>=2&&console.info(`[Porter:${this.context}] ${e}`,...t)}debug(e,...t){o.enabled&&o.level>=3&&console.debug(`[Porter:${this.context}] ${e}`,...t)}trace(e,...t){o.enabled&&o.level>=4&&console.trace(`[Porter:${this.context}] ${e}`,...t)}};o.level=o.getLevel(),o.enabled=!1,o.instances=new Map;var M=o;var l=class l{constructor(e={}){let t=e.namespace??"porter",s=e.agentContext??this.determineContext();e.debug!==void 0&&M.configure({enabled:e.debug}),this.logger=M.getLogger("Agent"),this.connectionManager=new w(t,this.logger),this.messageHandler=new x(this.logger),this.connectionManager.onReconnect(r=>{this.logger.info("Reconnected, re-wiring port listeners",{info:r}),this.setupPortListeners()}),this.logger.info("Initializing with options: ",{options:e,context:s}),this.initializeConnection()}static getInstance(e={}){return!l.instance||l.instance.connectionManager.getNamespace()!==e.namespace?l.instance=new l(e):e.debug!==void 0&&M.configure({enabled:e.debug}),l.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 s=this.connectionManager.getPort();if(this.logger.debug("Posting message",{message:e,target:t,port:s}),s)try{this.messageHandler.post(s,e,t)}catch(r){this.logger.error("Failed to post message, queueing for retry",{error:r}),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)}};l.instance=null;var P=l;function q(n){let e=P.getInstance(n);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)}}function k(n){let[e,t]=(0,i.useState)(!1),[s,r]=(0,i.useState)(!1),[m,g]=(0,i.useState)(null),[v,p]=(0,i.useState)(null),a=(0,i.useRef)(null),f=(0,i.useRef)(null),I=(0,i.useRef)(null),A=(0,i.useRef)([]),S=(0,i.useMemo)(()=>({agentContext:n==null?void 0:n.agentContext,namespace:n==null?void 0:n.namespace,debug:n==null?void 0:n.debug}),[n==null?void 0:n.agentContext,n==null?void 0:n.namespace,n==null?void 0:n.debug]),y=(0,i.useRef)(n==null?void 0:n.onDisconnect),C=(0,i.useRef)(n==null?void 0:n.onReconnect);y.current=n==null?void 0:n.onDisconnect,C.current=n==null?void 0:n.onReconnect,(0,i.useEffect)(()=>{let c=!0;return(async()=>{try{let{post:u,on:N,getAgentInfo:$,onDisconnect:z,onReconnect:U}=q(S);if(c){a.current=u,f.current=N,I.current=$,t(!0),r(!1),g(null);let F=z(()=>{var d;c&&(t(!1),r(!0),p(null),(d=y.current)==null||d.call(y))});A.current.push(F);let B=U(d=>{var T;c&&(t(!0),r(!1),p(d),(T=C.current)==null||T.call(C,d))});A.current.push(B),N({"porter-handshake":d=>{c&&p(d.payload.info)}})}}catch(u){c&&(console.error("[PORTER] initializePorter error ",u),g(u instanceof Error?u:new Error("Failed to connect to Porter")),t(!1),r(!1))}})(),()=>{c=!1,A.current.forEach(u=>u()),A.current=[]}},[S]);let D=(0,i.useCallback)(c=>{if(a.current)try{a.current(c)}catch(b){g(b instanceof Error?b:new Error("Failed to send message"))}else g(new Error("Porter is not connected"))},[]),Q=(0,i.useCallback)(c=>{if(f.current)try{f.current(c)}catch(b){g(b instanceof Error?b:new Error("Failed to set message handlers"))}else g(new Error("Porter is not connected"))},[]);return{post:D,on:Q,isConnected:e,isReconnecting:s,error:m,agentInfo:v}}0&&(module.exports={usePorter}); //# sourceMappingURL=index.js.map