@amadeus-it-group/microfrontends
Version:
Amadeus Micro Frontend Toolkit
3 lines (2 loc) • 10.4 kB
JavaScript
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){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){switch(typeof e){case"string":return{id:e};case"function":return{predicate:e};default:return e}}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 m(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 f={unsubscribe:()=>{}};class w{#i=new Set;get subscribers(){return this.#i}subscribe(e){return e?(this.#i.add(e),{unsubscribe:()=>{this.#i.delete(e)}}):f}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{}};window.addEventListener("message",E),window.addEventListener("handshake",E);class v{#o;#r=new Map;#d=new Map;#a=[];#h;#c=new w;#g=new w;#p=new w;#l;#u;#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.#l=e.messageCheckStrategy||"default",this.#u=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:m}),s}(this.#l),this.#h=()=>{var e;o(`PEER(${this.id}): stopped listening for connections`),e=this.id,y.delete(e),this.#a.length=0},o(`PEER(${this.id}): created`,this.#m)}get id(){return this.#o}get knownPeers(){return this.#m}get peerConnections(){return this.#d}get messages(){return this.#c}get serviceMessages(){return this.#g}get errors(){return this.#p}connect(e,s){o(`PEER(${this.id}): connecting to '${e}'`);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.#f(s,i):"handshake"===r.type?r.endpointId===this.id&&r.remoteId===e&&(o(`PEER(${this.id}): handshake received from ${e}:`,n),this.#f(s,i),s.resolve((()=>this.#w(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.#y(e,s)}listen(e=[]){const s=Array.isArray(e)?e.map(a):[a(e)];for(const{origin:e}of s)void 0!==e&&d(e);var t,n;return this.#a.length=0,this.#a.push(...s),t=this.id,n=this.#E.bind(this),y.set(t,n),o(`PEER(${this.id}): waiting for connections`,this.#a),this.#h}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.#w(s)}else for(const e of this.#r.values())this.#w(e)}#E(e){const{data:s,ports:t}=e,{payload:n}=s,{remoteId:i}=n;if(this.#r.has(i))return void o(`PEER(${this.id}): HS declined: already connected to '${i}'`);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.#a))return void o(`PEER(${this.id}): HS declined: connection from '${i}' does not match any of the filters:`,this.#a);const[r]=t;r.onmessage=({data:e})=>this.#f(d,e);const d=new p(this.id,i,r);this.#r.set(i,d),o(`PEER(${this.id}): created endpoint '${i}'`,d);const a=h(this.id,i,this.#m);this.#f(d,s),d.port.postMessage(a),d.resolve((()=>this.disconnect(i)))}#w(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.#y({type:"disconnect",version:"1.0",disconnected:s,unreachable:t}),o(`PEER(${this.id}): disconnected from '${s}'`,this.#r,this.#m)}#v(e,s){this.#p.emit(s),0===this.#p.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}})}#f(e,n){o(`PEER(${this.id}): received message`,n,this.#m);try{r(n,this.#l)}catch(s){return o(`PEER(${this.id}): message is malformed`,n),void this.#v(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.#k(e,s);this.#d.set(i.remoteId,new Set(i.knownPeers.keys())),this.#g.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.#k(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.#g.emit(n)}this.#P(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.#g.emit(n),this.#P(e,n);break;case"declare_messages":o(`PEER(${this.id}): declare_messages from '${n.from}'`,i),this.#k(n.from,i.messages),this.#g.emit(n),this.#P(e,n);break;case"error":o(`PEER(${this.id}): 'error' message from '${n.from}'`,i),n.to.includes(this.id)?this.#g.emit(n):this.#P(e,n);break;default:o(`PEER(${this.id}):`,`unknown message type: ${i.type}`),this.#v(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.#u)e(n,this);this.#c.emit(n)}}catch(s){o(`PEER(${this.id}):`,s),this.#v(e,s)}finally{this.#P(e,n)}}#y(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)}#P(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)}#k(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