UNPKG

penpal

Version:

A promise-based library for communicating with iframes via postMessage.

1 lines 6.9 kB
var Penpal=function(){"use strict";var e;(function(e){e["Call"]="call";e["Reply"]="reply";e["Syn"]="syn";e["SynAck"]="synAck";e["Ack"]="ack"})(e||(e={}));var n;(function(e){e["Fulfilled"]="fulfilled";e["Rejected"]="rejected"})(n||(n={}));var t;(function(e){e["ConnectionDestroyed"]="ConnectionDestroyed";e["ConnectionTimeout"]="ConnectionTimeout";e["NotInIframe"]="NotInIframe";e["NoIframeSrc"]="NoIframeSrc"})(t||(t={}));var o;(function(e){e["DataCloneError"]="DataCloneError"})(o||(o={}));var r;(function(e){e["Message"]="message"})(r||(r={}));var c=()=>{const e=[];let n=false;return{destroy(t){n=true;e.forEach(e=>{e(t)})},onDestroy(t){n?t():e.push(t)}}};var a=e=>(...n)=>{if(e){console.log("[Penpal]",...n)}};const i={"http:":"80","https:":"443"};const s=/^(https?:)?\/\/([^/:]+)?(:(\d+))?/;const d=["file:","data:"];var l=e=>{if(e&&d.find(n=>e.startsWith(n))){return"null"}const n=document.location;const t=s.exec(e);let o;let r;let c;if(t){o=t[1]?t[1]:n.protocol;r=t[2];c=t[4]}else{o=n.protocol;r=n.hostname;c=n.port}const a=c&&c!==i[o]?":".concat(c):"";return"".concat(o,"//").concat(r).concat(a)};const u=({name:e,message:n,stack:t})=>({name:e,message:n,stack:t});const g=e=>{const n=new Error;Object.keys(e).forEach(t=>n[t]=e[t]);return n};var f=(t,c,a)=>{const{localName:i,local:s,remote:d,originForSending:l,originForReceiving:g}=t;let f=false;const m=t=>{if(t.source!==d||t.data.penpal!==e.Call){return}if(t.origin!==g){a("".concat(i," received message from origin ").concat(t.origin," which did not match expected origin ").concat(g));return}const r=t.data;const{methodName:s,args:m,id:p}=r;a("".concat(i,": Received ").concat(s,"() call"));const h=t=>r=>{a("".concat(i,": Sending ").concat(s,"() reply"));if(f){a("".concat(i,": Unable to send ").concat(s,"() reply due to destroyed connection"));return}const c={penpal:e.Reply,id:p,resolution:t,returnValue:r};if(t===n.Rejected&&r instanceof Error){c.returnValue=u(r);c.returnValueIsError=true}try{d.postMessage(c,l)}catch(t){if(t.name===o.DataCloneError){const o={penpal:e.Reply,id:p,resolution:n.Rejected,returnValue:u(t),returnValueIsError:true};d.postMessage(o,l)}throw t}};new Promise(e=>e(c[s].apply(c,m))).then(h(n.Fulfilled),h(n.Rejected))};s.addEventListener(r.Message,m);return()=>{f=true;s.removeEventListener(r.Message,m)}};let m=0;var p=()=>++m;var h=(o,c,a,i,s)=>{const{localName:d,local:l,remote:u,originForSending:f,originForReceiving:m}=c;let h=false;s("".concat(d,": Connecting call sender"));const v=o=>(...c)=>{s("".concat(d,": Sending ").concat(o,"() call"));let a;try{if(u.closed){a=true}}catch(e){a=true}if(a){i()}if(h){const e=new Error("Unable to send ".concat(o,"() call due ")+"to destroyed connection");e.code=t.ConnectionDestroyed;throw e}return new Promise((t,a)=>{const i=p();const h=c=>{if(c.source!==u||c.data.penpal!==e.Reply||c.data.id!==i){return}if(c.origin!==m){s("".concat(d," received message from origin ").concat(c.origin," which did not match expected origin ").concat(m));return}const f=c.data;s("".concat(d,": Received ").concat(o,"() reply"));l.removeEventListener(r.Message,h);let p=f.returnValue;if(f.returnValueIsError){p=g(p)}(f.resolution===n.Fulfilled?t:a)(p)};l.addEventListener(r.Message,h);const v={penpal:e.Call,id:i,methodName:o,args:c};u.postMessage(v,f)})};a.reduce((e,n)=>{e[n]=v(n);return e},o);return()=>{h=true}};var v=(e,n,t,o,r)=>{const{destroy:c,onDestroy:a}=o;let i;let s;const d={};return o=>{if(o.origin!==n){r("Parent: Handshake - Received ACK message from origin ".concat(o.origin," which did not match expected origin ").concat(n));return}r("Parent: Handshake - Received ACK");const l={localName:"Parent",local:window,remote:o.source,originForSending:t,originForReceiving:n};if(i){i()}i=f(l,e,r);a(i);if(s){s.forEach(e=>{delete d[e]})}s=o.data.methodNames;const u=h(d,l,s,c,r);a(u);return d}};var w=(n,t,o,r)=>c=>{if(c.origin!==o){n("Parent: Handshake - Received SYN message from origin ".concat(c.origin," which did not match expected origin ").concat(o));return}n("Parent: Handshake - Received SYN, responding with SYN-ACK");const a={penpal:e.SynAck,methodNames:Object.keys(t)};c.source.postMessage(a,r)};const y=6e4;var C=(e,n)=>{const{destroy:t,onDestroy:o}=n;const r=setInterval(()=>{if(!e.isConnected){clearInterval(r);t()}},y);o(()=>{clearInterval(r)})};var E=(e,n)=>{let o;if(e!==undefined){o=window.setTimeout(()=>{const o=new Error("Connection timed out after ".concat(e,"ms"));o.code=t.ConnectionTimeout;n(o)},e)}return()=>{clearTimeout(o)}};var N=e=>{if(!e.src&&!e.srcdoc){const e=new Error("Iframe must have src or srcdoc property defined.");e.code=t.NoIframeSrc;throw e}};var R=n=>{let{iframe:o,methods:i={},childOrigin:s,timeout:d,debug:u=false}=n;const g=a(u);const f=c();const{onDestroy:m,destroy:p}=f;if(!s){N(o);s=l(o.src)}const h=s==="null"?"*":s;const y=w(g,i,s,h);const R=v(i,s,h,f,g);const S=new Promise((n,c)=>{const a=E(d,p);const i=t=>{if(t.source!==o.contentWindow||!t.data){return}if(t.data.penpal===e.Syn){y(t);return}if(t.data.penpal===e.Ack){const e=R(t);if(e){a();n(e)}return}};window.addEventListener(r.Message,i);g("Parent: Awaiting handshake");C(o,f);m(e=>{window.removeEventListener(r.Message,i);if(!e){e=new Error("Connection destroyed");e.code=t.ConnectionDestroyed}c(e)})});return{promise:S,destroy(){p()}}};var S=()=>{if(window===window.top){const e=new Error("connectToParent() must be called within an iframe");e.code=t.NotInIframe;throw e}};var k=(n,t,o,r)=>{const{destroy:c,onDestroy:a}=o;return o=>{let i=n instanceof RegExp?n.test(o.origin):n==="*"||n===o.origin;if(!i){r("Child: Handshake - Received SYN-ACK from origin ".concat(o.origin," which did not match expected origin ").concat(n));return}r("Child: Handshake - Received SYN-ACK, responding with ACK");const s=o.origin==="null"?"*":o.origin;const d={penpal:e.Ack,methodNames:Object.keys(t)};window.parent.postMessage(d,s);const l={localName:"Child",local:window,remote:window.parent,originForSending:s,originForReceiving:o.origin};const u=f(l,t,r);a(u);const g={};const m=h(g,l,o.data.methodNames,c,r);a(m);return g}};const I=()=>{try{clearTimeout()}catch(e){return false}return true};var M=(n={})=>{const{parentOrigin:o="*",methods:i={},timeout:s,debug:d=false}=n;const l=a(d);const u=c();const{destroy:g,onDestroy:f}=u;S();const m=k(o,i,u,l);const p=()=>{l("Child: Handshake - Sending SYN");const n={penpal:e.Syn};const t=o instanceof RegExp?"*":o;window.parent.postMessage(n,t)};const h=new Promise((n,o)=>{const c=E(s,g);const a=t=>{if(!I()){return}if(t.source!==parent||!t.data){return}if(t.data.penpal===e.SynAck){const e=m(t);if(e){window.removeEventListener(r.Message,a);c();n(e)}}};window.addEventListener(r.Message,a);p();f(e=>{window.removeEventListener(r.Message,a);if(!e){e=new Error("Connection destroyed");e.code=t.ConnectionDestroyed}o(e)})});return{promise:h,destroy(){g()}}};var A={connectToChild:R,connectToParent:M,ErrorCode:t};return A}();