UNPKG

@amadeus-it-group/microfrontends

Version:
3 lines (2 loc) 10.7 kB
const e={handshake:!0,error:!0,disconnect:!0,declare_messages:!0,connect:!0};function s(s){return void 0!==e[s?.type]}class t extends Error{messageObject;constructor(e,s){super(s),this.messageObject=e,this.name="MessageError"}}let n=!1;function i(e=!0){n=e}function o(...e){n&&console.log(...e)}function r(e,s="default"){if(!(e&&e.from&&e.to&&"string"==typeof e.from&&Array.isArray(e.to)))throw new t(e,"Message should have 'from'(string) and 'to'(string|string[]) properties");const{payload:n}=e;if(!n||!n.type||"string"!=typeof n.type)throw new t(e,"Message should have 'payload' property that has 'type'(string) defined");if("version"===s&&(!n.version||"string"!=typeof n.version))throw new t(e,"Message should have 'payload' property that has 'version'(string) defined")}function d(e){const s=URL.parse(e);if(!s)throw new Error(`'${e}' is not a valid URL`);if(s.origin!==e)throw new Error(`'${e}' is not a valid origin, did you mean '${s.origin}'?`)}function a(e,s,t){if(null!=e){const n=Array.isArray(e)?e:[e],i=n.map(e=>function(e){switch(typeof e){case"string":return{id:e};case"function":return{predicate:e};default:return e}}(e));for(const{origin:e}of i)void 0!==e&&d(e);s.length=0,s.push(...n),t.length=0,t.push(...i)}}function h(e,s,t){return structuredClone({from:e,to:[s],payload:{type:"handshake",version:"1.0",endpointId:s,remoteId:e,knownPeers:t}})}class c extends EventTarget{otherPort=null;onmessage=null;onmessageerror=null;postMessage(e){const s=new MessageEvent("message",{data:structuredClone(e)});this.otherPort?.dispatchEvent(s),this.otherPort?.onmessage?.call(this.otherPort,s)}start(){}close(){}}class g{port1;port2;constructor(){this.port1=new c,this.port2=new c,this.port1.otherPort=this.port2,this.port2.otherPort=this.port1}}class p{id;remoteId;port;connection;connected=!1;#e=[];#s=()=>{};reject=()=>{};constructor(e,s,t){this.id=e,this.remoteId=s,this.port=t,this.connection=new Promise((e,s)=>{this.#s=e,this.reject=s})}disconnect(){this.connected=!1,this.port.close()}resolve(e){this.connected=!0,this.#t(),this.#s(e)}send(e){this.connected&&this.port?(o(`EP(${this.id}): sending message '${e.payload.type}' to '${this.remoteId}':`,e),this.port.postMessage(e)):(o(`EP(${this.id}): queueing message:`,e),"connect"===e.payload.type?this.#e.unshift(structuredClone(e)):this.#e.push(structuredClone(e)))}#t(){for(const e of this.#e)this.send(e);this.#e.length=0}}class l extends p{targetWindow;targetOrigin;#n;constructor(e,s,t,n){const{port1:i,port2:o}=window===t?new g:new MessageChannel;super(e,s,i),this.targetWindow=t,this.targetOrigin=n,this.#n=o}sendHandshake(e){window===this.targetWindow?(o(`EP(${this.id}): sending 'CustomEvent' handshake to '${this.remoteId}':`,e),window.dispatchEvent(new CustomEvent("handshake",{detail:{data:e,origin:this.targetOrigin,ports:[this.#n]}}))):(o(`EP(${this.id}): sending 'postMessage' handshake to '${this.remoteId}':`,e),this.targetWindow.postMessage(e,{targetOrigin:this.targetOrigin,transfer:[this.#n]}))}}function u(e,s){const n=s.knownPeers.get(s.id),{payload:i}=e;if(n&&!n.find(({type:e})=>e===i.type)){const s=[...new Set(n.map(({type:e})=>e))];throw new t(e,`Unknown message type "${i.type}". Known types: ${JSON.stringify(s)}`)}}function f(e,s){const n=s.knownPeers.get(s.id),{payload:i}=e;if(n&&!n?.find(({type:e,version:s})=>e===i.type&&s===i.version)){const s=n.filter(({type:e})=>e===i.type).map(({version:e})=>e);throw new t(e,`Unknown message version "${i.version}". Known versions: ${JSON.stringify(s)}`)}}const m={unsubscribe:()=>{}};class w{#i=new Set;get subscribers(){return this.#i}subscribe(e){return e?(this.#i.add(e),{unsubscribe:()=>{this.#i.delete(e)}}):m}emit(e){for(const s of this.#i)"function"==typeof s?s(e):s.next?.(e)}[Symbol.observable](){return this}"@@observable"(){return this}}const y=new Map;const E=e=>{let s;e instanceof CustomEvent?(s=e.detail,o("Received 'CustomEvent'",e)):(s=e,o("Received 'postMessage'",e));const t=s.data;try{r(t);const{payload:e}=t;if("handshake"!==e.type)return;const n=y.get(e.endpointId);n?n(s):o(`HS declined: peer '${e.endpointId}' is not among listening peers:`,[...y.keys()])}catch{}};"undefined"!=typeof window&&(window.addEventListener("message",E),window.addEventListener("handshake",E));class v{#o;#r=new Map;#d=new Map;#a=[];#h=[];#c;#g=new w;#p=new w;#l=new w;#u;#f;#m=new Map;#e=[];constructor(e){if(this.#o=e.id,this.#m.set(this.id,[]),e.knownMessages)for(const s of e.knownMessages)this.registerMessage(s);this.#u=e.messageCheckStrategy||"default",this.#f=function(e){const s=[];return"type"!==e&&"version"!==e||s.push({description:"Check that message type is known",check:u}),"version"===e&&s.push({description:"Check that message version is known",check:f}),s}(this.#u),this.#c=()=>{var e;o(`PEER(${this.id}): stopped listening for connections`),e=this.id,y.delete(e)},o(`PEER(${this.id}): created`,this.#m)}get id(){return this.#o}get knownPeers(){return this.#m}get peerConnections(){return this.#d}get connectionFilters(){return this.#a}set connectionFilters(e){a(e,this.#a,this.#h)}get messages(){return this.#g}get serviceMessages(){return this.#p}get errors(){return this.#l}connect(e,s){if(o(`PEER(${this.id}): connecting to '${e}'`),"undefined"==typeof window)return Promise.resolve(()=>{});const t=s?.window||window,n=s?.origin||window.origin;d(n);let i=this.#r.get(e);if(!i){const s=new l(this.id,e,t,n);this.#r.set(e,s),s.port.onmessage=t=>{const i=t.data,r=i.payload;o(`PEER(${this.id}): '${r.type}' message received from '${e}':`,i),s.connected?this.#w(s,i):"handshake"===r.type?r.endpointId===this.id&&r.remoteId===e&&(o(`PEER(${this.id}): handshake received from ${e}:`,n),this.#w(s,i),s.resolve(()=>this.#y(s))):(o(`PEER(${this.id}): handshake was expected, got:`,i),s.reject(`Handshake was expected, got: ${JSON.stringify(i)}`))};const r=h(this.id,e,this.#m);s.sendHandshake(r),i=s}return i.connection}send(e,s){this.#E(e,s)}listen(e){var s,t;return a(e,this.#a,this.#h),s=this.id,t=this.#v.bind(this),y.set(s,t),o(`PEER(${this.id}): waiting for connections`,this.#h),this.#c}registerMessage(e){const s=this.#m.get(this.id);s.find(s=>s.type===e.type&&s.version===e.version)||s.push(e)}log(){const e=[...this.#r.values()].map(e=>`${e.id}:${e.connected?e.remoteId:e.remoteId+"*"}`),s=[...this.#d].map(([e,s])=>`${e}: ${[...s].join(", ")}`);console.log(`PEER(${this.id}):`,e,s,this.#m)}disconnect(e){if(e){const s=this.#r.get(e);s&&this.#y(s)}else for(const e of this.#r.values())this.#y(e)}#v(e){const{data:s,ports:t}=e,{payload:n}=s,{remoteId:i}=n;if(!function(e,s){const{origin:t,source:n,data:i}=e,{remoteId:o}=i.payload;return 0===s.length||s.some(e=>(void 0!==e.id||void 0!==e.source||void 0!==e.origin||e.predicate)&&(void 0===e.id||e.id===o)&&(void 0===e.source||e.source===n)&&(void 0===e.origin||e.origin===t||"null"===t&&e.source===n)&&(void 0===e.predicate||e.predicate(i,n,t)))}(e,this.#h))return void o(`PEER(${this.id}): HS declined: connection from '${i}' does not match any of the filters:`,this.#h);const r=this.#r.get(i);r&&(o(`PEER(${this.id}): already connected to '${i}' -> disconnecting`),this.#y(r));const[d]=t;d.onmessage=({data:e})=>this.#w(a,e);const a=new p(this.id,i,d);this.#r.set(i,a),o(`PEER(${this.id}): created endpoint '${i}'`,a);const c=h(this.id,i,this.#m);this.#w(a,s),a.port.postMessage(c),a.resolve(()=>this.disconnect(i))}#y(e){const{remoteId:s}=e,t=[...this.#d.get(s)||[]],n=[this.id];for(const[e,t]of this.#d)e!==s&&n.push(...t);e.connected&&e.send({from:this.id,to:[],payload:{type:"disconnect",version:"1.0",disconnected:this.id,unreachable:n}}),this.#d.delete(s);for(const e of t)this.#m.delete(e);this.#r.delete(s),e.disconnect(),this.#E({type:"disconnect",version:"1.0",disconnected:s,unreachable:t}),o(`PEER(${this.id}): disconnected from '${s}'`,this.#r,this.#m)}#k(e,s){this.#l.emit(s),0===this.#l.subscribers.size&&console.error(s),e.send({from:this.id,to:[s.messageObject.from],payload:{type:"error",version:"1.0",error:s.message,message:s.messageObject}})}#w(e,n){o(`PEER(${this.id}): received message`,n,this.#m);try{r(n,this.#u)}catch(s){return o(`PEER(${this.id}): message is malformed`,n),void this.#k(e,s)}const{payload:i}=n;if(s(i))switch(i.type){case"handshake":{o(`PEER(${this.id}): handshake message from '${i.remoteId}'`,i);const s=[...this.knownPeers.keys()];for(const[e,s]of i.knownPeers)this.#P(e,s);this.#d.set(i.remoteId,new Set(i.knownPeers.keys())),this.#p.emit(n);for(const s of this.#r.values())s!==e&&s.connected&&s.send({from:this.id,to:[],payload:{type:"connect",version:"1.0",knownPeers:new Map([...this.#m].filter(e=>[...i.knownPeers.keys()].includes(e[0]))),connected:[...i.knownPeers.keys()]}});e.send({from:this.id,to:[i.remoteId],payload:{type:"connect",version:"1.0",knownPeers:new Map([...this.#m].filter(e=>s.includes(e[0]))),connected:s}});break}case"connect":if(n.to.includes(this.id)||0===n.to.length){o(`PEER(${this.id}): connect message from '${e.remoteId}'`,i);for(const[e,s]of i.knownPeers)this.#P(e,s);const s=this.#d.get(e.remoteId);if(s)for(const e of i.knownPeers.keys())e===this.id||[...this.#r.keys()].includes(e)||s.add(e);this.#t(),this.#p.emit(n)}this.#$(e,n);break;case"disconnect":o(`PEER(${this.id}): disconnect message from '${e.remoteId}'`,i);this.#r.get(i.disconnected)&&e.disconnect();for(const e of i.unreachable){this.#m.delete(e),this.#r.delete(e);for(const[s,t]of this.#d)t.delete(e),0===t.size&&this.#d.delete(s)}this.#p.emit(n),this.#$(e,n);break;case"declare_messages":o(`PEER(${this.id}): declare_messages from '${n.from}'`,i),this.#P(n.from,i.messages),this.#p.emit(n),this.#$(e,n);break;case"error":o(`PEER(${this.id}): 'error' message from '${n.from}'`,i),n.to.includes(this.id)?this.#p.emit(n):this.#$(e,n);break;default:o(`PEER(${this.id}):`,`unknown message type: ${i.type}`),this.#k(e,new t(n,`unknown message type: ${i.type}`))}else try{if(n.to.includes(this.id)||0===n.to.length){for(const{check:e}of this.#f)e(n,this);this.#g.emit(n)}}catch(s){o(`PEER(${this.id}):`,s),this.#k(e,s)}finally{this.#$(e,n)}}#E(e,t){const n={from:this.id,to:t?.to?Array.isArray(t.to)?t.to:[t.to]:[],payload:e};if(this.#m.size>1)for(const e of this.#r.values())e.send(n);else s(e)||this.#e.push(n)}#$(e,s){if(1!==s.to.length||s.to[0]!==this.id)for(const t of this.#r.values())t!==e&&t.connected&&t.send(s)}#P(e,s){if(this.id!==e){const t=this.#m.get(e);if(t)for(const e of s)t.find(s=>s.type===e.type&&s.version===e.version)||t.push(e);else this.#m.set(e,s)}}#t(){for(const e of this.#e)for(const s of this.#r.values())s.send(e);this.#e.length=0}}export{t as MessageError,v as MessagePeer,i as enableLogging,s as isServiceMessage}; //# sourceMappingURL=microfrontends.min.mjs.map