Comparing version 3.1.1 to 3.1.2
@@ -37,2 +37,3 @@ // Derived from https://github.com/umdjs/umd/blob/master/templates/amdWebGlobal.js | ||
exports.ERR_IFRAME_ALREADY_ATTACHED_TO_DOM = ERR_IFRAME_ALREADY_ATTACHED_TO_DOM; | ||
var CHECK_IFRAME_IN_DOC_INTERVAL = 60000; | ||
var DEFAULT_PORTS = { | ||
@@ -202,3 +203,3 @@ 'http:': '80', | ||
var connectCallSender = function connectCallSender(callSender, info, methodNames, destructionPromise) { | ||
var connectCallSender = function connectCallSender(callSender, info, methodNames, destroy, destructionPromise) { | ||
var localName = info.localName, | ||
@@ -217,4 +218,13 @@ local = info.local, | ||
log("".concat(localName, ": Sending ").concat(methodName, "() call")); | ||
log("".concat(localName, ": Sending ").concat(methodName, "() call")); // This handles the case where the iframe has been removed from the DOM | ||
// (and therefore its window closed), the consumer has not yet | ||
// called destroy(), and the user calls a method exposed by | ||
// the remote. We detect the iframe has been removed and force | ||
// a destroy() immediately so that the consumer sees the error saying | ||
// the connection has been destroyed. | ||
if (remote.closed) { | ||
destroy(); | ||
} | ||
if (destroyed) { | ||
@@ -393,7 +403,2 @@ var error = new Error("Unable to send ".concat(methodName, "() call due ") + "to destroyed connection"); | ||
iframe.src = url; | ||
connectionDestructionPromise.then(function () { | ||
if (iframe.parentNode) { | ||
iframe.parentNode.removeChild(iframe); | ||
} | ||
}); | ||
var childOrigin = getOriginFromUrl(url); | ||
@@ -460,3 +465,3 @@ var promise = new Penpal.Promise(function (resolveConnectionPromise, reject) { | ||
receiverMethodNames = event.data.methodNames; | ||
connectCallSender(callSender, info, receiverMethodNames, connectionDestructionPromise); | ||
connectCallSender(callSender, info, receiverMethodNames, destroy, connectionDestructionPromise); | ||
clearTimeout(connectionTimeoutId); | ||
@@ -468,4 +473,23 @@ resolveConnectionPromise(callSender); | ||
parent.addEventListener(MESSAGE, handleMessage); | ||
log('Parent: Loading iframe'); | ||
(appendTo || document.body).appendChild(iframe); // This is to prevent memory leaks when the iframe is removed | ||
// from the document and the consumer hasn't called destroy(). | ||
// Without this, event listeners attached to the window would | ||
// stick around and since the event handlers have a reference | ||
// to the iframe in their closures, the iframe would stick around | ||
// too. | ||
var checkIframeInDocIntervalId = setInterval(function () { | ||
if (!document.contains(iframe)) { | ||
clearInterval(checkIframeInDocIntervalId); | ||
destroy(); | ||
} | ||
}, CHECK_IFRAME_IN_DOC_INTERVAL); | ||
connectionDestructionPromise.then(function () { | ||
if (iframe.parentNode) { | ||
iframe.parentNode.removeChild(iframe); | ||
} | ||
parent.removeEventListener(MESSAGE, handleMessage); | ||
clearInterval(checkIframeInDocIntervalId); | ||
var error = new Error('Connection destroyed'); | ||
@@ -475,4 +499,2 @@ error.code = ERR_CONNECTION_DESTROYED; | ||
}); | ||
log('Parent: Loading iframe'); | ||
(appendTo || document.body).appendChild(iframe); | ||
}); | ||
@@ -546,3 +568,3 @@ return { | ||
connectCallReceiver(info, methods, connectionDestructionPromise); | ||
connectCallSender(callSender, info, event.data.methodNames, connectionDestructionPromise); | ||
connectCallSender(callSender, info, event.data.methodNames, destroy, connectionDestructionPromise); | ||
clearTimeout(connectionTimeoutId); | ||
@@ -549,0 +571,0 @@ resolveConnectionPromise(callSender); |
@@ -1,1 +0,1 @@ | ||
!function(e,n){var t={};!function(e){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=e.ERR_IFRAME_ALREADY_ATTACHED_TO_DOM=e.ERR_NOT_IN_IFRAME=e.ERR_CONNECTION_TIMEOUT=e.ERR_CONNECTION_DESTROYED=void 0;e.ERR_CONNECTION_DESTROYED="ConnectionDestroyed";e.ERR_CONNECTION_TIMEOUT="ConnectionTimeout";e.ERR_NOT_IN_IFRAME="NotInIframe";e.ERR_IFRAME_ALREADY_ATTACHED_TO_DOM="IframeAlreadyAttachedToDom";var n={"http:":"80","https:":"443"},t=/^(https?:|file:)?\/\/([^\/:]+)?(:(\d+))?/,o={ERR_CONNECTION_DESTROYED:"ConnectionDestroyed",ERR_CONNECTION_TIMEOUT:"ConnectionTimeout",ERR_NOT_IN_IFRAME:"NotInIframe",ERR_IFRAME_ALREADY_ATTACHED_TO_DOM:"IframeAlreadyAttachedToDom",Promise:function(){try{return window?window.Promise:null}catch(e){return null}}(),debug:!1},r=(u=0,function(){return++u}),a=function(){if(o.debug){for(var e,n=arguments.length,t=new Array(n),r=0;r<n;r++)t[r]=arguments[r];(e=console).log.apply(e,["[Penpal]"].concat(t))}},i=function(e){var n=[];return e(function(){n.forEach(function(e){e()})}),{then:function(e){n.push(e)}}},c=function(e){var n=e.name,t=e.message,o=e.stack;return{name:n,message:t,stack:o}},d=function(e,n,t,i){var c=n.localName,d=n.local,s=n.remote,u=n.remoteOrigin,l=!1;a("".concat(c,": Connecting call sender"));var f=function(e){return function(){for(var n=arguments.length,t=new Array(n),i=0;i<n;i++)t[i]=arguments[i];if(a("".concat(c,": Sending ").concat(e,"() call")),l){var f=new Error("Unable to send ".concat(e,"() call due ")+"to destroyed connection");throw f.code="ConnectionDestroyed",f}return new o.Promise(function(n,o){var i=r();d.addEventListener("message",function t(r){if(r.source===s&&r.origin===u&&"reply"===r.data.penpal&&r.data.id===i){a("".concat(c,": Received ").concat(e,"() reply")),d.removeEventListener("message",t);var l=r.data.returnValue;r.data.returnValueIsError&&(f=l,m=new Error,Object.keys(f).forEach(function(e){return m[e]=f[e]}),l=m),("fulfilled"===r.data.resolution?n:o)(l)}var f,m}),s.postMessage({penpal:"call",id:i,methodName:e,args:t},u)})}};i.then(function(){l=!0}),t.reduce(function(e,n){return e[n]=f(n),e},e)},s=function(e,n,t){var r=e.localName,i=e.local,d=e.remote,s=e.remoteOrigin,u=!1;a("".concat(r,": Connecting call receiver"));var l=function(e){if(e.source===d&&e.origin===s&&"call"===e.data.penpal){var t=e.data,i=t.methodName,l=t.args,f=t.id;if(a("".concat(r,": Received ").concat(i,"() call")),i in n){var m=function(e){return function(n){if(a("".concat(r,": Sending ").concat(i,"() reply")),u)a("".concat(r,": Unable to send ").concat(i,"() reply due to destroyed connection"));else{var t={penpal:"reply",id:f,resolution:e,returnValue:n};"rejected"===e&&n instanceof Error&&(t.returnValue=c(n),t.returnValueIsError=!0);try{d.postMessage(t,s)}catch(e){throw"DataCloneError"===e.name&&d.postMessage({penpal:"reply",id:f,resolution:"rejected",returnValue:c(e),returnValueIsError:!0},s),e}}}};new o.Promise(function(e){return e(n[i].apply(n,l))}).then(m("fulfilled"),m("rejected"))}}};i.addEventListener("message",l),t.then(function(){u=!0,i.removeEventListener("message",l)})};var u;o.connectToChild=function(e){var r,c=e.url,u=e.appendTo,l=e.iframe,f=e.methods,m=void 0===f?{}:f,v=e.timeout;if(l&&l.parentNode){var E=new Error("connectToChild() must not be called with an iframe already attached to DOM");throw E.code="IframeAlreadyAttachedToDom",E}var p=new i(function(e){r=e}),h=window;(l=l||document.createElement("iframe")).src=c,p.then(function(){l.parentNode&&l.parentNode.removeChild(l)});var T=function(e){var o,r,a,i=document.location,c=t.exec(e);c?(o=c[1]?c[1]:i.protocol,r=c[2],a=c[4]):(o=i.protocol,r=i.hostname,a=i.port);if("file:"===o)return"null";var d=a&&a!==n[o]?":".concat(a):"";return"".concat(o,"//").concat(r).concat(d)}(c),g=new o.Promise(function(e,n){var t;void 0!==v&&(t=setTimeout(function(){var e=new Error("Connection to child timed out after ".concat(v,"ms"));e.code="ConnectionTimeout",n(e),r()},v));var o,c,f={},E=function(n){var r=l.contentWindow;if(n.source===r&&n.origin===T&&"handshake"===n.data.penpal){a("Parent: Received handshake, sending reply");var u="null"===n.origin?"*":n.origin;n.source.postMessage({penpal:"handshake-reply",methodNames:Object.keys(m)},u);var v={localName:"Parent",local:h,remote:r,remoteOrigin:u};c&&c();var E=new i(function(e){p.then(e),c=e});s(v,m,E),o&&o.forEach(function(e){delete f[e]}),o=n.data.methodNames,d(f,v,o,p),clearTimeout(t),e(f)}};h.addEventListener("message",E),p.then(function(){h.removeEventListener("message",E);var e=new Error("Connection destroyed");e.code="ConnectionDestroyed",n(e)}),a("Parent: Loading iframe"),(u||document.body).appendChild(l)});return{promise:g,iframe:l,destroy:r}},o.connectToParent=function(){var e,n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=n.parentOrigin,r=void 0===t?"*":t,c=n.methods,u=void 0===c?{}:c,l=n.timeout;if(window===window.top){var f=new Error("connectToParent() must be called within an iframe");throw f.code="NotInIframe",f}var m=new i(function(n){e=n}),v=window,E=v.parent,p=new o.Promise(function(n,t){var o;void 0!==l&&(o=setTimeout(function(){var n=new Error("Connection to parent timed out after ".concat(l,"ms"));n.code="ConnectionTimeout",t(n),e()},l));var i=function e(t){if(("*"===r||r===t.origin)&&t.source===E&&"handshake-reply"===t.data.penpal){a("Child: Received handshake reply"),v.removeEventListener("message",e);var i={localName:"Child",local:v,remote:E,remoteOrigin:t.origin},c={};s(i,u,m),d(c,i,t.data.methodNames,m),clearTimeout(o),n(c)}};v.addEventListener("message",i),m.then(function(){v.removeEventListener("message",i);var e=new Error("Connection destroyed");e.code="ConnectionDestroyed",t(e)}),a("Child: Sending handshake"),E.postMessage({penpal:"handshake",methodNames:Object.keys(u)},r)});return{promise:p,destroy:e}};var l=o;e.default=l}(t),"function"==typeof define&&define.amd?define("Penpal",t.default):e.Penpal=t.default}(this); | ||
!function(e,n){var t={};!function(e){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=e.ERR_IFRAME_ALREADY_ATTACHED_TO_DOM=e.ERR_NOT_IN_IFRAME=e.ERR_CONNECTION_TIMEOUT=e.ERR_CONNECTION_DESTROYED=void 0;e.ERR_CONNECTION_DESTROYED="ConnectionDestroyed";e.ERR_CONNECTION_TIMEOUT="ConnectionTimeout";e.ERR_NOT_IN_IFRAME="NotInIframe";e.ERR_IFRAME_ALREADY_ATTACHED_TO_DOM="IframeAlreadyAttachedToDom";var n={"http:":"80","https:":"443"},t=/^(https?:|file:)?\/\/([^\/:]+)?(:(\d+))?/,o={ERR_CONNECTION_DESTROYED:"ConnectionDestroyed",ERR_CONNECTION_TIMEOUT:"ConnectionTimeout",ERR_NOT_IN_IFRAME:"NotInIframe",ERR_IFRAME_ALREADY_ATTACHED_TO_DOM:"IframeAlreadyAttachedToDom",Promise:function(){try{return window?window.Promise:null}catch(e){return null}}(),debug:!1},r=(l=0,function(){return++l}),a=function(){if(o.debug){for(var e,n=arguments.length,t=new Array(n),r=0;r<n;r++)t[r]=arguments[r];(e=console).log.apply(e,["[Penpal]"].concat(t))}},i=function(e){var n=[];return e(function(){n.forEach(function(e){e()})}),{then:function(e){n.push(e)}}},c=function(e){var n=e.name,t=e.message,o=e.stack;return{name:n,message:t,stack:o}},d=function(e,n,t,i,c){var d=n.localName,s=n.local,l=n.remote,u=n.remoteOrigin,f=!1;a("".concat(d,": Connecting call sender"));var m=function(e){return function(){for(var n=arguments.length,t=new Array(n),c=0;c<n;c++)t[c]=arguments[c];if(a("".concat(d,": Sending ").concat(e,"() call")),l.closed&&i(),f){var m=new Error("Unable to send ".concat(e,"() call due ")+"to destroyed connection");throw m.code="ConnectionDestroyed",m}return new o.Promise(function(n,o){var i=r();s.addEventListener("message",function t(r){if(r.source===l&&r.origin===u&&"reply"===r.data.penpal&&r.data.id===i){a("".concat(d,": Received ").concat(e,"() reply")),s.removeEventListener("message",t);var c=r.data.returnValue;r.data.returnValueIsError&&(f=c,m=new Error,Object.keys(f).forEach(function(e){return m[e]=f[e]}),c=m),("fulfilled"===r.data.resolution?n:o)(c)}var f,m}),l.postMessage({penpal:"call",id:i,methodName:e,args:t},u)})}};c.then(function(){f=!0}),t.reduce(function(e,n){return e[n]=m(n),e},e)},s=function(e,n,t){var r=e.localName,i=e.local,d=e.remote,s=e.remoteOrigin,l=!1;a("".concat(r,": Connecting call receiver"));var u=function(e){if(e.source===d&&e.origin===s&&"call"===e.data.penpal){var t=e.data,i=t.methodName,u=t.args,f=t.id;if(a("".concat(r,": Received ").concat(i,"() call")),i in n){var m=function(e){return function(n){if(a("".concat(r,": Sending ").concat(i,"() reply")),l)a("".concat(r,": Unable to send ").concat(i,"() reply due to destroyed connection"));else{var t={penpal:"reply",id:f,resolution:e,returnValue:n};"rejected"===e&&n instanceof Error&&(t.returnValue=c(n),t.returnValueIsError=!0);try{d.postMessage(t,s)}catch(e){throw"DataCloneError"===e.name&&d.postMessage({penpal:"reply",id:f,resolution:"rejected",returnValue:c(e),returnValueIsError:!0},s),e}}}};new o.Promise(function(e){return e(n[i].apply(n,u))}).then(m("fulfilled"),m("rejected"))}}};i.addEventListener("message",u),t.then(function(){l=!0,i.removeEventListener("message",u)})};var l;o.connectToChild=function(e){var r,c=e.url,l=e.appendTo,u=e.iframe,f=e.methods,m=void 0===f?{}:f,v=e.timeout;if(u&&u.parentNode){var E=new Error("connectToChild() must not be called with an iframe already attached to DOM");throw E.code="IframeAlreadyAttachedToDom",E}var p=new i(function(e){r=e}),h=window;(u=u||document.createElement("iframe")).src=c;var T=function(e){var o,r,a,i=document.location,c=t.exec(e);c?(o=c[1]?c[1]:i.protocol,r=c[2],a=c[4]):(o=i.protocol,r=i.hostname,a=i.port);if("file:"===o)return"null";var d=a&&a!==n[o]?":".concat(a):"";return"".concat(o,"//").concat(r).concat(d)}(c),g=new o.Promise(function(e,n){var t;void 0!==v&&(t=setTimeout(function(){var e=new Error("Connection to child timed out after ".concat(v,"ms"));e.code="ConnectionTimeout",n(e),r()},v));var o,c,f={},E=function(n){var l=u.contentWindow;if(n.source===l&&n.origin===T&&"handshake"===n.data.penpal){a("Parent: Received handshake, sending reply");var v="null"===n.origin?"*":n.origin;n.source.postMessage({penpal:"handshake-reply",methodNames:Object.keys(m)},v);var E={localName:"Parent",local:h,remote:l,remoteOrigin:v};c&&c();var g=new i(function(e){p.then(e),c=e});s(E,m,g),o&&o.forEach(function(e){delete f[e]}),o=n.data.methodNames,d(f,E,o,r,p),clearTimeout(t),e(f)}};h.addEventListener("message",E),a("Parent: Loading iframe"),(l||document.body).appendChild(u);var g=setInterval(function(){document.contains(u)||(clearInterval(g),r())},6e4);p.then(function(){u.parentNode&&u.parentNode.removeChild(u),h.removeEventListener("message",E),clearInterval(g);var e=new Error("Connection destroyed");e.code="ConnectionDestroyed",n(e)})});return{promise:g,iframe:u,destroy:r}},o.connectToParent=function(){var e,n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=n.parentOrigin,r=void 0===t?"*":t,c=n.methods,l=void 0===c?{}:c,u=n.timeout;if(window===window.top){var f=new Error("connectToParent() must be called within an iframe");throw f.code="NotInIframe",f}var m=new i(function(n){e=n}),v=window,E=v.parent,p=new o.Promise(function(n,t){var o;void 0!==u&&(o=setTimeout(function(){var n=new Error("Connection to parent timed out after ".concat(u,"ms"));n.code="ConnectionTimeout",t(n),e()},u));var i=function t(i){if(("*"===r||r===i.origin)&&i.source===E&&"handshake-reply"===i.data.penpal){a("Child: Received handshake reply"),v.removeEventListener("message",t);var c={localName:"Child",local:v,remote:E,remoteOrigin:i.origin},u={};s(c,l,m),d(u,c,i.data.methodNames,e,m),clearTimeout(o),n(u)}};v.addEventListener("message",i),m.then(function(){v.removeEventListener("message",i);var e=new Error("Connection destroyed");e.code="ConnectionDestroyed",t(e)}),a("Child: Sending handshake"),E.postMessage({penpal:"handshake",methodNames:Object.keys(l)},r)});return{promise:p,destroy:e}};var u=o;e.default=u}(t),"function"==typeof define&&define.amd?define("Penpal",t.default):e.Penpal=t.default}(this); |
@@ -23,2 +23,3 @@ "use strict"; | ||
exports.ERR_IFRAME_ALREADY_ATTACHED_TO_DOM = ERR_IFRAME_ALREADY_ATTACHED_TO_DOM; | ||
var CHECK_IFRAME_IN_DOC_INTERVAL = 60000; | ||
var DEFAULT_PORTS = { | ||
@@ -188,3 +189,3 @@ 'http:': '80', | ||
var connectCallSender = function connectCallSender(callSender, info, methodNames, destructionPromise) { | ||
var connectCallSender = function connectCallSender(callSender, info, methodNames, destroy, destructionPromise) { | ||
var localName = info.localName, | ||
@@ -203,4 +204,13 @@ local = info.local, | ||
log("".concat(localName, ": Sending ").concat(methodName, "() call")); | ||
log("".concat(localName, ": Sending ").concat(methodName, "() call")); // This handles the case where the iframe has been removed from the DOM | ||
// (and therefore its window closed), the consumer has not yet | ||
// called destroy(), and the user calls a method exposed by | ||
// the remote. We detect the iframe has been removed and force | ||
// a destroy() immediately so that the consumer sees the error saying | ||
// the connection has been destroyed. | ||
if (remote.closed) { | ||
destroy(); | ||
} | ||
if (destroyed) { | ||
@@ -379,7 +389,2 @@ var error = new Error("Unable to send ".concat(methodName, "() call due ") + "to destroyed connection"); | ||
iframe.src = url; | ||
connectionDestructionPromise.then(function () { | ||
if (iframe.parentNode) { | ||
iframe.parentNode.removeChild(iframe); | ||
} | ||
}); | ||
var childOrigin = getOriginFromUrl(url); | ||
@@ -446,3 +451,3 @@ var promise = new Penpal.Promise(function (resolveConnectionPromise, reject) { | ||
receiverMethodNames = event.data.methodNames; | ||
connectCallSender(callSender, info, receiverMethodNames, connectionDestructionPromise); | ||
connectCallSender(callSender, info, receiverMethodNames, destroy, connectionDestructionPromise); | ||
clearTimeout(connectionTimeoutId); | ||
@@ -454,4 +459,23 @@ resolveConnectionPromise(callSender); | ||
parent.addEventListener(MESSAGE, handleMessage); | ||
log('Parent: Loading iframe'); | ||
(appendTo || document.body).appendChild(iframe); // This is to prevent memory leaks when the iframe is removed | ||
// from the document and the consumer hasn't called destroy(). | ||
// Without this, event listeners attached to the window would | ||
// stick around and since the event handlers have a reference | ||
// to the iframe in their closures, the iframe would stick around | ||
// too. | ||
var checkIframeInDocIntervalId = setInterval(function () { | ||
if (!document.contains(iframe)) { | ||
clearInterval(checkIframeInDocIntervalId); | ||
destroy(); | ||
} | ||
}, CHECK_IFRAME_IN_DOC_INTERVAL); | ||
connectionDestructionPromise.then(function () { | ||
if (iframe.parentNode) { | ||
iframe.parentNode.removeChild(iframe); | ||
} | ||
parent.removeEventListener(MESSAGE, handleMessage); | ||
clearInterval(checkIframeInDocIntervalId); | ||
var error = new Error('Connection destroyed'); | ||
@@ -461,4 +485,2 @@ error.code = ERR_CONNECTION_DESTROYED; | ||
}); | ||
log('Parent: Loading iframe'); | ||
(appendTo || document.body).appendChild(iframe); | ||
}); | ||
@@ -532,3 +554,3 @@ return { | ||
connectCallReceiver(info, methods, connectionDestructionPromise); | ||
connectCallSender(callSender, info, event.data.methodNames, connectionDestructionPromise); | ||
connectCallSender(callSender, info, event.data.methodNames, destroy, connectionDestructionPromise); | ||
clearTimeout(connectionTimeoutId); | ||
@@ -535,0 +557,0 @@ resolveConnectionPromise(callSender); |
{ | ||
"name": "penpal", | ||
"version": "3.1.1", | ||
"version": "3.1.2", | ||
"description": "A promise-based library for communicating with iframes via postMessage.", | ||
@@ -5,0 +5,0 @@ "author": "Aaron Hardy <aaron@aaronhardy.com>", |
@@ -1,2 +0,2 @@ | ||
[![npm version](https://badge.fury.io/js/penpal.svg)](https://badge.fury.io/js/penpal) [![Build Status](https://travis-ci.org/Aaronius/penpal.svg?branch=master)](https://travis-ci.org/Aaronius/penpal) | ||
[![npm version](https://badge.fury.io/js/penpal.svg)](https://badge.fury.io/js/penpal) | ||
@@ -3,0 +3,0 @@ [![Build Status](https://saucelabs.com/browser-matrix/Aaronius9erPenpalMaster.svg)](https://saucelabs.com/u/Aaronius9erPenpalMaster) |
1090
79866