From 8277fc306d44f14e3b6f51f8ecc592f8da795bdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Thu, 8 Mar 2018 04:13:09 +0100 Subject: [PATCH] =?UTF-8?q?Wrapped=20every=20log=20in=20process.env.NODE?= =?UTF-8?q?=5FENV=20check=20for=20better=20minificati=E2=80=A6=20(#55)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Wrapped every log in process.env.NODE_ENV check for better minification. Also restructured build configs in the process. * [bug] by Jeff => reads comma to package.json --- README.md | 2 +- build/postmate.dev.js | 496 ++++++++++++++++++++++++++++++ build/postmate.es.js | 92 ++++-- build/postmate.js | 490 +++++++++++++++++++++++++++++ build/postmate.min.js | 2 +- configs/rollup.config.es.js | 19 -- configs/rollup.config.js | 77 +++++ configs/rollup.config.standard.js | 39 --- package.json | 10 +- scripts/update-readme.js | 3 +- src/postmate.js | 69 +++-- 11 files changed, 1194 insertions(+), 105 deletions(-) create mode 100644 build/postmate.dev.js create mode 100644 build/postmate.js delete mode 100644 configs/rollup.config.es.js create mode 100644 configs/rollup.config.js delete mode 100644 configs/rollup.config.standard.js diff --git a/README.md b/README.md index 254324b4..63c7fe0e 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ You can download the compiled javascript directly [here](/build/postmate.min.js) * Child emits events that the parent can listen to. * Parent can `call` functions within a `child` * *Zero* dependencies. Provide your own polyfill or abstraction for the `Promise` API if needed. -* Lightweight, weighing in at ~ `4.8kb`. +* Lightweight, weighing in at ~ `1.5kb` (minified & gzipped). ## Installing Postmate can be installed via NPM or Bower. diff --git a/build/postmate.dev.js b/build/postmate.dev.js new file mode 100644 index 00000000..e050a808 --- /dev/null +++ b/build/postmate.dev.js @@ -0,0 +1,496 @@ +/** + postmate - A powerful, simple, promise-based postMessage library + @version v1.4.0 + @link https://github.com/dollarshaveclub/postmate + @author Jacob Kelley + @license MIT +**/ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global.Postmate = factory()); +}(this, (function () { 'use strict'; + +/** + * The type of messages our frames our sending + * @type {String} + */ +var messsageType = 'application/x-postmate-v1+json'; +/** + * hasOwnProperty() + * @type {Function} + * @return {Boolean} + */ + +var hasOwnProperty = Object.prototype.hasOwnProperty; +/** + * The maximum number of attempts to send a handshake request to the parent + * @type {Number} + */ + +var maxHandshakeRequests = 5; +/** + * A unique message ID that is used to ensure responses are sent to the correct requests + * @type {Number} + */ + +var _messageId = 0; +/** + * Increments and returns a message ID + * @return {Number} A unique ID for a message + */ + +var messageId = function messageId() { + return ++_messageId; +}; +/** + * Postmate logging function that enables/disables via config + * @param {Object} ...args Rest Arguments + */ + +var log = function log() { + var _console; + + return Postmate.debug ? (_console = console).log.apply(_console, arguments) : null; +}; // eslint-disable-line no-console + +/** + * Takes a URL and returns the origin + * @param {String} url The full URL being requested + * @return {String} The URLs origin + */ + +var resolveOrigin = function resolveOrigin(url) { + var a = document.createElement('a'); + a.href = url; + return a.origin || a.protocol + "//" + a.hostname; +}; +/** + * Ensures that a message is safe to interpret + * @param {Object} message The postmate message being sent + * @param {String} allowedOrigin The whitelisted origin + * @return {Boolean} + */ + +var sanitize = function sanitize(message, allowedOrigin) { + if (message.origin !== allowedOrigin) return false; + if (typeof message.data !== 'object') return false; + if (!('postmate' in message.data)) return false; + if (message.data.type !== messsageType) return false; + if (!{ + 'handshake-reply': 1, + call: 1, + emit: 1, + reply: 1, + request: 1 + }[message.data.postmate]) return false; + return true; +}; +/** + * Takes a model, and searches for a value by the property + * @param {Object} model The dictionary to search against + * @param {String} property A path within a dictionary (i.e. 'window.location.href') + * @param {Object} data Additional information from the get request that is + * passed to functions in the child model + * @return {Promise} + */ + +var resolveValue = function resolveValue(model, property) { + var unwrappedContext = typeof model[property] === 'function' ? model[property]() : model[property]; + return Postmate.Promise.resolve(unwrappedContext); +}; +/** + * Composes an API to be used by the parent + * @param {Object} info Information on the consumer + */ + +var ParentAPI = +/*#__PURE__*/ +function () { + function ParentAPI(info) { + var _this = this; + + this.parent = info.parent; + this.frame = info.frame; + this.child = info.child; + this.childOrigin = info.childOrigin; + this.events = {}; + + { + log('Parent: Registering API'); + log('Parent: Awaiting messages...'); + } + + this.listener = function (e) { + var _ref = ((e || {}).data || {}).value || {}, + data = _ref.data, + name = _ref.name; + + if (e.data.postmate === 'emit') { + { + log("Parent: Received event emission: " + name); + } + + if (name in _this.events) { + _this.events[name].call(_this, data); + } + } + }; + + this.parent.addEventListener('message', this.listener, false); + + { + log('Parent: Awaiting event emissions from Child'); + } + } + + var _proto = ParentAPI.prototype; + + _proto.get = function get(property) { + var _this2 = this; + + return new Postmate.Promise(function (resolve) { + // Extract data from response and kill listeners + var uid = messageId(); + + var transact = function transact(e) { + if (e.data.uid === uid && e.data.postmate === 'reply') { + _this2.parent.removeEventListener('message', transact, false); + + resolve(e.data.value); + } + }; // Prepare for response from Child... + + + _this2.parent.addEventListener('message', transact, false); // Then ask child for information + + + _this2.child.postMessage({ + postmate: 'request', + type: messsageType, + property: property, + uid: uid + }, _this2.childOrigin); + }); + }; + + _proto.call = function call(property, data) { + // Send information to the child + this.child.postMessage({ + postmate: 'call', + type: messsageType, + property: property, + data: data + }, this.childOrigin); + }; + + _proto.on = function on(eventName, callback) { + this.events[eventName] = callback; + }; + + _proto.destroy = function destroy() { + { + log('Parent: Destroying Postmate instance'); + } + + window.removeEventListener('message', this.listener, false); + this.frame.parentNode.removeChild(this.frame); + }; + + return ParentAPI; +}(); +/** + * Composes an API to be used by the child + * @param {Object} info Information on the consumer + */ + +var ChildAPI = +/*#__PURE__*/ +function () { + function ChildAPI(info) { + var _this3 = this; + + this.model = info.model; + this.parent = info.parent; + this.parentOrigin = info.parentOrigin; + this.child = info.child; + + { + log('Child: Registering API'); + log('Child: Awaiting messages...'); + } + + this.child.addEventListener('message', function (e) { + if (!sanitize(e, _this3.parentOrigin)) return; + + { + log('Child: Received request', e.data); + } + + var _e$data = e.data, + property = _e$data.property, + uid = _e$data.uid, + data = _e$data.data; + + if (e.data.postmate === 'call') { + if (property in _this3.model && typeof _this3.model[property] === 'function') { + _this3.model[property].call(_this3, data); + } + + return; + } // Reply to Parent + + + resolveValue(_this3.model, property).then(function (value) { + return e.source.postMessage({ + property: property, + postmate: 'reply', + type: messsageType, + uid: uid, + value: value + }, e.origin); + }); + }); + } + + var _proto2 = ChildAPI.prototype; + + _proto2.emit = function emit(name, data) { + { + log("Child: Emitting Event \"" + name + "\"", data); + } + + this.parent.postMessage({ + postmate: 'emit', + type: messsageType, + value: { + name: name, + data: data + } + }, this.parentOrigin); + }; + + return ChildAPI; +}(); +/** + * The entry point of the Parent. + * @type {Class} + */ + +var Postmate = +/*#__PURE__*/ +function () { + // eslint-disable-line no-undef + // Internet Explorer craps itself + + /** + * Sets options related to the Parent + * @param {Object} userOptions The element to inject the frame into, and the url + * @return {Promise} + */ + function Postmate(_temp) { + var _ref2 = _temp === void 0 ? userOptions : _temp, + _ref2$container = _ref2.container, + container = _ref2$container === void 0 ? typeof container !== 'undefined' ? container : document.body : _ref2$container, + model = _ref2.model, + url = _ref2.url; + + // eslint-disable-line no-undef + this.parent = window; + this.frame = document.createElement('iframe'); + container.appendChild(this.frame); + this.child = this.frame.contentWindow || this.frame.contentDocument.parentWindow; + this.model = model || {}; + return this.sendHandshake(url); + } + /** + * Begins the handshake strategy + * @param {String} url The URL to send a handshake request to + * @return {Promise} Promise that resolves when the handshake is complete + */ + + + var _proto3 = Postmate.prototype; + + _proto3.sendHandshake = function sendHandshake(url) { + var _this4 = this; + + var childOrigin = resolveOrigin(url); + var attempt = 0; + var responseInterval; + return new Postmate.Promise(function (resolve, reject) { + var reply = function reply(e) { + if (!sanitize(e, childOrigin)) return false; + + if (e.data.postmate === 'handshake-reply') { + clearInterval(responseInterval); + + { + log('Parent: Received handshake reply from Child'); + } + + _this4.parent.removeEventListener('message', reply, false); + + _this4.childOrigin = e.origin; + + { + log('Parent: Saving Child origin', _this4.childOrigin); + } + + return resolve(new ParentAPI(_this4)); + } // Might need to remove since parent might be receiving different messages + // from different hosts + + + { + log('Parent: Invalid handshake reply'); + } + + return reject('Failed handshake'); + }; + + _this4.parent.addEventListener('message', reply, false); + + var doSend = function doSend() { + attempt++; + + { + log("Parent: Sending handshake attempt " + attempt, { + childOrigin: childOrigin + }); + } + + _this4.child.postMessage({ + postmate: 'handshake', + type: messsageType, + model: _this4.model + }, childOrigin); + + if (attempt === maxHandshakeRequests) { + clearInterval(responseInterval); + } + }; + + var loaded = function loaded() { + doSend(); + responseInterval = setInterval(doSend, 500); + }; + + if (_this4.frame.attachEvent) { + _this4.frame.attachEvent('onload', loaded); + } else { + _this4.frame.onload = loaded; + } + + { + log('Parent: Loading frame', { + url: url + }); + } + + _this4.frame.src = url; + }); + }; + + return Postmate; +}(); +/** + * The entry point of the Child + * @type {Class} + */ + + +Postmate.debug = false; + +Postmate.Promise = function () { + try { + return window ? window.Promise : Promise; + } catch (e) { + return null; + } +}(); + +Postmate.Model = +/*#__PURE__*/ +function () { + /** + * Initializes the child, model, parent, and responds to the Parents handshake + * @param {Object} model Hash of values, functions, or promises + * @return {Promise} The Promise that resolves when the handshake has been received + */ + function Model(model) { + this.child = window; + this.model = model; + this.parent = this.child.parent; + return this.sendHandshakeReply(); + } + /** + * Responds to a handshake initiated by the Parent + * @return {Promise} Resolves an object that exposes an API for the Child + */ + + + var _proto4 = Model.prototype; + + _proto4.sendHandshakeReply = function sendHandshakeReply() { + var _this5 = this; + + return new Postmate.Promise(function (resolve, reject) { + var shake = function shake(e) { + if (!e.data.postmate) { + return; + } + + if (e.data.postmate === 'handshake') { + { + log('Child: Received handshake from Parent'); + } + + _this5.child.removeEventListener('message', shake, false); + + { + log('Child: Sending handshake reply to Parent'); + } + + e.source.postMessage({ + postmate: 'handshake-reply', + type: messsageType + }, e.origin); + _this5.parentOrigin = e.origin; // Extend model with the one provided by the parent + + var defaults = e.data.model; + + if (defaults) { + var keys = Object.keys(defaults); + + for (var i = 0; i < keys.length; i++) { + if (hasOwnProperty.call(defaults, keys[i])) { + _this5.model[keys[i]] = defaults[keys[i]]; + } + } + + { + log('Child: Inherited and extended model from Parent'); + } + } + + { + log('Child: Saving Parent origin', _this5.parentOrigin); + } + + return resolve(new ChildAPI(_this5)); + } + + return reject('Handshake Reply Failed'); + }; + + _this5.child.addEventListener('message', shake, false); + }); + }; + + return Model; +}(); + +return Postmate; + +}))); diff --git a/build/postmate.es.js b/build/postmate.es.js index cf92b468..f61d752d 100644 --- a/build/postmate.es.js +++ b/build/postmate.es.js @@ -109,8 +109,11 @@ function () { this.child = info.child; this.childOrigin = info.childOrigin; this.events = {}; - log('Parent: Registering API'); - log('Parent: Awaiting messages...'); + + if (process.env.NODE_ENV !== 'production') { + log('Parent: Registering API'); + log('Parent: Awaiting messages...'); + } this.listener = function (e) { var _ref = ((e || {}).data || {}).value || {}, @@ -118,7 +121,9 @@ function () { name = _ref.name; if (e.data.postmate === 'emit') { - log("Parent: Received event emission: " + name); + if (process.env.NODE_ENV !== 'production') { + log("Parent: Received event emission: " + name); + } if (name in _this.events) { _this.events[name].call(_this, data); @@ -127,7 +132,10 @@ function () { }; this.parent.addEventListener('message', this.listener, false); - log('Parent: Awaiting event emissions from Child'); + + if (process.env.NODE_ENV !== 'production') { + log('Parent: Awaiting event emissions from Child'); + } } var _proto = ParentAPI.prototype; @@ -175,7 +183,10 @@ function () { }; _proto.destroy = function destroy() { - log('Parent: Destroying Postmate instance'); + if (process.env.NODE_ENV !== 'production') { + log('Parent: Destroying Postmate instance'); + } + window.removeEventListener('message', this.listener, false); this.frame.parentNode.removeChild(this.frame); }; @@ -197,11 +208,19 @@ function () { this.parent = info.parent; this.parentOrigin = info.parentOrigin; this.child = info.child; - log('Child: Registering API'); - log('Child: Awaiting messages...'); + + if (process.env.NODE_ENV !== 'production') { + log('Child: Registering API'); + log('Child: Awaiting messages...'); + } + this.child.addEventListener('message', function (e) { if (!sanitize(e, _this3.parentOrigin)) return; - log('Child: Received request', e.data); + + if (process.env.NODE_ENV !== 'production') { + log('Child: Received request', e.data); + } + var _e$data = e.data, property = _e$data.property, uid = _e$data.uid, @@ -231,7 +250,10 @@ function () { var _proto2 = ChildAPI.prototype; _proto2.emit = function emit(name, data) { - log("Child: Emitting Event \"" + name + "\"", data); + if (process.env.NODE_ENV !== 'production') { + log("Child: Emitting Event \"" + name + "\"", data); + } + this.parent.postMessage({ postmate: 'emit', type: messsageType, @@ -296,18 +318,28 @@ function () { if (e.data.postmate === 'handshake-reply') { clearInterval(responseInterval); - log('Parent: Received handshake reply from Child'); + + if (process.env.NODE_ENV !== 'production') { + log('Parent: Received handshake reply from Child'); + } _this4.parent.removeEventListener('message', reply, false); _this4.childOrigin = e.origin; - log('Parent: Saving Child origin', _this4.childOrigin); + + if (process.env.NODE_ENV !== 'production') { + log('Parent: Saving Child origin', _this4.childOrigin); + } + return resolve(new ParentAPI(_this4)); } // Might need to remove since parent might be receiving different messages // from different hosts - log('Parent: Invalid handshake reply'); + if (process.env.NODE_ENV !== 'production') { + log('Parent: Invalid handshake reply'); + } + return reject('Failed handshake'); }; @@ -315,9 +347,12 @@ function () { var doSend = function doSend() { attempt++; - log("Parent: Sending handshake attempt " + attempt, { - childOrigin: childOrigin - }); + + if (process.env.NODE_ENV !== 'production') { + log("Parent: Sending handshake attempt " + attempt, { + childOrigin: childOrigin + }); + } _this4.child.postMessage({ postmate: 'handshake', @@ -341,9 +376,12 @@ function () { _this4.frame.onload = loaded; } - log('Parent: Loading frame', { - url: url - }); + if (process.env.NODE_ENV !== 'production') { + log('Parent: Loading frame', { + url: url + }); + } + _this4.frame.src = url; }); }; @@ -398,11 +436,16 @@ function () { } if (e.data.postmate === 'handshake') { - log('Child: Received handshake from Parent'); + if (process.env.NODE_ENV !== 'production') { + log('Child: Received handshake from Parent'); + } _this5.child.removeEventListener('message', shake, false); - log('Child: Sending handshake reply to Parent'); + if (process.env.NODE_ENV !== 'production') { + log('Child: Sending handshake reply to Parent'); + } + e.source.postMessage({ postmate: 'handshake-reply', type: messsageType @@ -420,10 +463,15 @@ function () { } } - log('Child: Inherited and extended model from Parent'); + if (process.env.NODE_ENV !== 'production') { + log('Child: Inherited and extended model from Parent'); + } + } + + if (process.env.NODE_ENV !== 'production') { + log('Child: Saving Parent origin', _this5.parentOrigin); } - log('Child: Saving Parent origin', _this5.parentOrigin); return resolve(new ChildAPI(_this5)); } diff --git a/build/postmate.js b/build/postmate.js new file mode 100644 index 00000000..30880703 --- /dev/null +++ b/build/postmate.js @@ -0,0 +1,490 @@ +/** + postmate - A powerful, simple, promise-based postMessage library + @version v1.4.0 + @link https://github.com/dollarshaveclub/postmate + @author Jacob Kelley + @license MIT +**/ +'use strict'; + +/** + * The type of messages our frames our sending + * @type {String} + */ +var messsageType = 'application/x-postmate-v1+json'; +/** + * hasOwnProperty() + * @type {Function} + * @return {Boolean} + */ + +var hasOwnProperty = Object.prototype.hasOwnProperty; +/** + * The maximum number of attempts to send a handshake request to the parent + * @type {Number} + */ + +var maxHandshakeRequests = 5; +/** + * A unique message ID that is used to ensure responses are sent to the correct requests + * @type {Number} + */ + +var _messageId = 0; +/** + * Increments and returns a message ID + * @return {Number} A unique ID for a message + */ + +var messageId = function messageId() { + return ++_messageId; +}; +/** + * Postmate logging function that enables/disables via config + * @param {Object} ...args Rest Arguments + */ + +var log = function log() { + var _console; + + return Postmate.debug ? (_console = console).log.apply(_console, arguments) : null; +}; // eslint-disable-line no-console + +/** + * Takes a URL and returns the origin + * @param {String} url The full URL being requested + * @return {String} The URLs origin + */ + +var resolveOrigin = function resolveOrigin(url) { + var a = document.createElement('a'); + a.href = url; + return a.origin || a.protocol + "//" + a.hostname; +}; +/** + * Ensures that a message is safe to interpret + * @param {Object} message The postmate message being sent + * @param {String} allowedOrigin The whitelisted origin + * @return {Boolean} + */ + +var sanitize = function sanitize(message, allowedOrigin) { + if (message.origin !== allowedOrigin) return false; + if (typeof message.data !== 'object') return false; + if (!('postmate' in message.data)) return false; + if (message.data.type !== messsageType) return false; + if (!{ + 'handshake-reply': 1, + call: 1, + emit: 1, + reply: 1, + request: 1 + }[message.data.postmate]) return false; + return true; +}; +/** + * Takes a model, and searches for a value by the property + * @param {Object} model The dictionary to search against + * @param {String} property A path within a dictionary (i.e. 'window.location.href') + * @param {Object} data Additional information from the get request that is + * passed to functions in the child model + * @return {Promise} + */ + +var resolveValue = function resolveValue(model, property) { + var unwrappedContext = typeof model[property] === 'function' ? model[property]() : model[property]; + return Postmate.Promise.resolve(unwrappedContext); +}; +/** + * Composes an API to be used by the parent + * @param {Object} info Information on the consumer + */ + +var ParentAPI = +/*#__PURE__*/ +function () { + function ParentAPI(info) { + var _this = this; + + this.parent = info.parent; + this.frame = info.frame; + this.child = info.child; + this.childOrigin = info.childOrigin; + this.events = {}; + + if (process.env.NODE_ENV !== 'production') { + log('Parent: Registering API'); + log('Parent: Awaiting messages...'); + } + + this.listener = function (e) { + var _ref = ((e || {}).data || {}).value || {}, + data = _ref.data, + name = _ref.name; + + if (e.data.postmate === 'emit') { + if (process.env.NODE_ENV !== 'production') { + log("Parent: Received event emission: " + name); + } + + if (name in _this.events) { + _this.events[name].call(_this, data); + } + } + }; + + this.parent.addEventListener('message', this.listener, false); + + if (process.env.NODE_ENV !== 'production') { + log('Parent: Awaiting event emissions from Child'); + } + } + + var _proto = ParentAPI.prototype; + + _proto.get = function get(property) { + var _this2 = this; + + return new Postmate.Promise(function (resolve) { + // Extract data from response and kill listeners + var uid = messageId(); + + var transact = function transact(e) { + if (e.data.uid === uid && e.data.postmate === 'reply') { + _this2.parent.removeEventListener('message', transact, false); + + resolve(e.data.value); + } + }; // Prepare for response from Child... + + + _this2.parent.addEventListener('message', transact, false); // Then ask child for information + + + _this2.child.postMessage({ + postmate: 'request', + type: messsageType, + property: property, + uid: uid + }, _this2.childOrigin); + }); + }; + + _proto.call = function call(property, data) { + // Send information to the child + this.child.postMessage({ + postmate: 'call', + type: messsageType, + property: property, + data: data + }, this.childOrigin); + }; + + _proto.on = function on(eventName, callback) { + this.events[eventName] = callback; + }; + + _proto.destroy = function destroy() { + if (process.env.NODE_ENV !== 'production') { + log('Parent: Destroying Postmate instance'); + } + + window.removeEventListener('message', this.listener, false); + this.frame.parentNode.removeChild(this.frame); + }; + + return ParentAPI; +}(); +/** + * Composes an API to be used by the child + * @param {Object} info Information on the consumer + */ + +var ChildAPI = +/*#__PURE__*/ +function () { + function ChildAPI(info) { + var _this3 = this; + + this.model = info.model; + this.parent = info.parent; + this.parentOrigin = info.parentOrigin; + this.child = info.child; + + if (process.env.NODE_ENV !== 'production') { + log('Child: Registering API'); + log('Child: Awaiting messages...'); + } + + this.child.addEventListener('message', function (e) { + if (!sanitize(e, _this3.parentOrigin)) return; + + if (process.env.NODE_ENV !== 'production') { + log('Child: Received request', e.data); + } + + var _e$data = e.data, + property = _e$data.property, + uid = _e$data.uid, + data = _e$data.data; + + if (e.data.postmate === 'call') { + if (property in _this3.model && typeof _this3.model[property] === 'function') { + _this3.model[property].call(_this3, data); + } + + return; + } // Reply to Parent + + + resolveValue(_this3.model, property).then(function (value) { + return e.source.postMessage({ + property: property, + postmate: 'reply', + type: messsageType, + uid: uid, + value: value + }, e.origin); + }); + }); + } + + var _proto2 = ChildAPI.prototype; + + _proto2.emit = function emit(name, data) { + if (process.env.NODE_ENV !== 'production') { + log("Child: Emitting Event \"" + name + "\"", data); + } + + this.parent.postMessage({ + postmate: 'emit', + type: messsageType, + value: { + name: name, + data: data + } + }, this.parentOrigin); + }; + + return ChildAPI; +}(); +/** + * The entry point of the Parent. + * @type {Class} + */ + +var Postmate = +/*#__PURE__*/ +function () { + // eslint-disable-line no-undef + // Internet Explorer craps itself + + /** + * Sets options related to the Parent + * @param {Object} userOptions The element to inject the frame into, and the url + * @return {Promise} + */ + function Postmate(_temp) { + var _ref2 = _temp === void 0 ? userOptions : _temp, + _ref2$container = _ref2.container, + container = _ref2$container === void 0 ? typeof container !== 'undefined' ? container : document.body : _ref2$container, + model = _ref2.model, + url = _ref2.url; + + // eslint-disable-line no-undef + this.parent = window; + this.frame = document.createElement('iframe'); + container.appendChild(this.frame); + this.child = this.frame.contentWindow || this.frame.contentDocument.parentWindow; + this.model = model || {}; + return this.sendHandshake(url); + } + /** + * Begins the handshake strategy + * @param {String} url The URL to send a handshake request to + * @return {Promise} Promise that resolves when the handshake is complete + */ + + + var _proto3 = Postmate.prototype; + + _proto3.sendHandshake = function sendHandshake(url) { + var _this4 = this; + + var childOrigin = resolveOrigin(url); + var attempt = 0; + var responseInterval; + return new Postmate.Promise(function (resolve, reject) { + var reply = function reply(e) { + if (!sanitize(e, childOrigin)) return false; + + if (e.data.postmate === 'handshake-reply') { + clearInterval(responseInterval); + + if (process.env.NODE_ENV !== 'production') { + log('Parent: Received handshake reply from Child'); + } + + _this4.parent.removeEventListener('message', reply, false); + + _this4.childOrigin = e.origin; + + if (process.env.NODE_ENV !== 'production') { + log('Parent: Saving Child origin', _this4.childOrigin); + } + + return resolve(new ParentAPI(_this4)); + } // Might need to remove since parent might be receiving different messages + // from different hosts + + + if (process.env.NODE_ENV !== 'production') { + log('Parent: Invalid handshake reply'); + } + + return reject('Failed handshake'); + }; + + _this4.parent.addEventListener('message', reply, false); + + var doSend = function doSend() { + attempt++; + + if (process.env.NODE_ENV !== 'production') { + log("Parent: Sending handshake attempt " + attempt, { + childOrigin: childOrigin + }); + } + + _this4.child.postMessage({ + postmate: 'handshake', + type: messsageType, + model: _this4.model + }, childOrigin); + + if (attempt === maxHandshakeRequests) { + clearInterval(responseInterval); + } + }; + + var loaded = function loaded() { + doSend(); + responseInterval = setInterval(doSend, 500); + }; + + if (_this4.frame.attachEvent) { + _this4.frame.attachEvent('onload', loaded); + } else { + _this4.frame.onload = loaded; + } + + if (process.env.NODE_ENV !== 'production') { + log('Parent: Loading frame', { + url: url + }); + } + + _this4.frame.src = url; + }); + }; + + return Postmate; +}(); +/** + * The entry point of the Child + * @type {Class} + */ + + +Postmate.debug = false; + +Postmate.Promise = function () { + try { + return window ? window.Promise : Promise; + } catch (e) { + return null; + } +}(); + +Postmate.Model = +/*#__PURE__*/ +function () { + /** + * Initializes the child, model, parent, and responds to the Parents handshake + * @param {Object} model Hash of values, functions, or promises + * @return {Promise} The Promise that resolves when the handshake has been received + */ + function Model(model) { + this.child = window; + this.model = model; + this.parent = this.child.parent; + return this.sendHandshakeReply(); + } + /** + * Responds to a handshake initiated by the Parent + * @return {Promise} Resolves an object that exposes an API for the Child + */ + + + var _proto4 = Model.prototype; + + _proto4.sendHandshakeReply = function sendHandshakeReply() { + var _this5 = this; + + return new Postmate.Promise(function (resolve, reject) { + var shake = function shake(e) { + if (!e.data.postmate) { + return; + } + + if (e.data.postmate === 'handshake') { + if (process.env.NODE_ENV !== 'production') { + log('Child: Received handshake from Parent'); + } + + _this5.child.removeEventListener('message', shake, false); + + if (process.env.NODE_ENV !== 'production') { + log('Child: Sending handshake reply to Parent'); + } + + e.source.postMessage({ + postmate: 'handshake-reply', + type: messsageType + }, e.origin); + _this5.parentOrigin = e.origin; // Extend model with the one provided by the parent + + var defaults = e.data.model; + + if (defaults) { + var keys = Object.keys(defaults); + + for (var i = 0; i < keys.length; i++) { + if (hasOwnProperty.call(defaults, keys[i])) { + _this5.model[keys[i]] = defaults[keys[i]]; + } + } + + if (process.env.NODE_ENV !== 'production') { + log('Child: Inherited and extended model from Parent'); + } + } + + if (process.env.NODE_ENV !== 'production') { + log('Child: Saving Parent origin', _this5.parentOrigin); + } + + return resolve(new ChildAPI(_this5)); + } + + return reject('Handshake Reply Failed'); + }; + + _this5.child.addEventListener('message', shake, false); + }); + }; + + return Model; +}(); + +module.exports = Postmate; diff --git a/build/postmate.min.js b/build/postmate.min.js index c92cc3c6..23822d4e 100644 --- a/build/postmate.min.js +++ b/build/postmate.min.js @@ -5,4 +5,4 @@ @author Jacob Kelley @license MIT **/ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.Postmate=t()}(this,function(){"use strict";var e="application/x-postmate-v1+json",t=Object.prototype.hasOwnProperty,n=0,i=function(){var e;return d.debug?(e=console).log.apply(e,arguments):null},a=function(t,n){return t.origin===n&&("object"==typeof t.data&&("postmate"in t.data&&(t.data.type===e&&!!{"handshake-reply":1,call:1,emit:1,reply:1,request:1}[t.data.postmate])))},r=function(e,t){var n="function"==typeof e[t]?e[t]():e[t];return d.Promise.resolve(n)},s=function(){function t(e){var t=this;this.parent=e.parent,this.frame=e.frame,this.child=e.child,this.childOrigin=e.childOrigin,this.events={},i("Parent: Registering API"),i("Parent: Awaiting messages..."),this.listener=function(e){var n=((e||{}).data||{}).value||{},a=n.data,r=n.name;"emit"===e.data.postmate&&(i("Parent: Received event emission: "+r),r in t.events&&t.events[r].call(t,a))},this.parent.addEventListener("message",this.listener,!1),i("Parent: Awaiting event emissions from Child")}var a=t.prototype;return a.get=function(t){var i=this;return new d.Promise(function(a){var r=++n;i.parent.addEventListener("message",function e(t){t.data.uid===r&&"reply"===t.data.postmate&&(i.parent.removeEventListener("message",e,!1),a(t.data.value))},!1),i.child.postMessage({postmate:"request",type:e,property:t,uid:r},i.childOrigin)})},a.call=function(t,n){this.child.postMessage({postmate:"call",type:e,property:t,data:n},this.childOrigin)},a.on=function(e,t){this.events[e]=t},a.destroy=function(){i("Parent: Destroying Postmate instance"),window.removeEventListener("message",this.listener,!1),this.frame.parentNode.removeChild(this.frame)},t}(),o=function(){function t(t){var n=this;this.model=t.model,this.parent=t.parent,this.parentOrigin=t.parentOrigin,this.child=t.child,i("Child: Registering API"),i("Child: Awaiting messages..."),this.child.addEventListener("message",function(t){if(a(t,n.parentOrigin)){i("Child: Received request",t.data);var s=t.data,o=s.property,d=s.uid,h=s.data;"call"!==t.data.postmate?r(n.model,o).then(function(n){return t.source.postMessage({property:o,postmate:"reply",type:e,uid:d,value:n},t.origin)}):o in n.model&&"function"==typeof n.model[o]&&n.model[o].call(n,h)}})}return t.prototype.emit=function(t,n){i('Child: Emitting Event "'+t+'"',n),this.parent.postMessage({postmate:"emit",type:e,value:{name:t,data:n}},this.parentOrigin)},t}(),d=function(){function t(e){var t=void 0===e?userOptions:e,n=t.container,i=void 0===n?void 0!==i?i:document.body:n,a=t.model,r=t.url;return this.parent=window,this.frame=document.createElement("iframe"),i.appendChild(this.frame),this.child=this.frame.contentWindow||this.frame.contentDocument.parentWindow,this.model=a||{},this.sendHandshake(r)}return t.prototype.sendHandshake=function(n){var r,o=this,d=function(e){var t=document.createElement("a");return t.href=e,t.origin||t.protocol+"//"+t.hostname}(n),h=0;return new t.Promise(function(t,l){o.parent.addEventListener("message",function e(n){return!!a(n,d)&&("handshake-reply"===n.data.postmate?(clearInterval(r),i("Parent: Received handshake reply from Child"),o.parent.removeEventListener("message",e,!1),o.childOrigin=n.origin,i("Parent: Saving Child origin",o.childOrigin),t(new s(o))):(i("Parent: Invalid handshake reply"),l("Failed handshake")))},!1);var p=function(){i("Parent: Sending handshake attempt "+ ++h,{childOrigin:d}),o.child.postMessage({postmate:"handshake",type:e,model:o.model},d),5===h&&clearInterval(r)},c=function(){p(),r=setInterval(p,500)};o.frame.attachEvent?o.frame.attachEvent("onload",c):o.frame.onload=c,i("Parent: Loading frame",{url:n}),o.frame.src=n})},t}();return d.debug=!1,d.Promise=function(){try{return window?window.Promise:Promise}catch(e){return null}}(),d.Model=function(){function n(e){return this.child=window,this.model=e,this.parent=this.child.parent,this.sendHandshakeReply()}return n.prototype.sendHandshakeReply=function(){var n=this;return new d.Promise(function(a,r){n.child.addEventListener("message",function s(d){if(d.data.postmate){if("handshake"===d.data.postmate){i("Child: Received handshake from Parent"),n.child.removeEventListener("message",s,!1),i("Child: Sending handshake reply to Parent"),d.source.postMessage({postmate:"handshake-reply",type:e},d.origin),n.parentOrigin=d.origin;var h=d.data.model;if(h){for(var l=Object.keys(h),p=0;p Array.isArray(maybeArr) ? maybeArr : [maybeArr] + +const uglifyOutput = { + output: { + comments: function (node, comment) { // eslint-disable-line func-names + const text = comment.value + const type = comment.type + if (type === 'comment2') { + // multiline comment + return /@preserve|@license|@cc_on/i.test(text) + } + }, + }, +} + +const createConfig = ({ output, env } = {}) => { + const umd = output.format === 'umd' + + if (umd && typeof env === 'undefined') { + throw new Error('You need to specify `env` when using umd format.') + } + + const min = umd && env === 'production' + + return { + input: 'src/index.js', + plugins: [ + babel(babelSetup), + env && replace({ + 'process.env.NODE_ENV': JSON.stringify(env), + }), + min && uglify(uglifyOutput), + ].filter(Boolean), + output: ensureArray(output).map(format => + Object.assign( + {}, + format, + { + banner, + name: 'Postmate', + } + ) + ), + } +} + +export default [ + createConfig({ + output: [ + { file: pkg.main, format: 'cjs' }, + { file: pkg.module, format: 'es' }, + ], + }), + createConfig({ + output: { + file: 'build/postmate.min.js', + format: 'umd', + }, + env: 'production', + }), + createConfig({ + output: { + file: 'build/postmate.dev.js', + format: 'umd', + }, + env: 'development', + }), +] diff --git a/configs/rollup.config.standard.js b/configs/rollup.config.standard.js deleted file mode 100644 index dc0c116c..00000000 --- a/configs/rollup.config.standard.js +++ /dev/null @@ -1,39 +0,0 @@ -import { - babelSetup, - banner, -} from '../configs/config' -import babel from 'rollup-plugin-babel' -import uglify from 'rollup-plugin-uglify' - -const uglifyOutput = { - // compress: { - // pure_getters: true, - // unsafe: true, - // }, - // toplevel: true, - output: { - comments: function (node, comment) { // eslint-disable-line func-names - const text = comment.value - const type = comment.type - if (type === 'comment2') { - // multiline comment - return /@preserve|@license|@cc_on/i.test(text) - } - }, - }, -} - -export default { - input: 'src/index.js', - plugins: [ - babel(babelSetup), - uglify(uglifyOutput), - ], - output: { - banner, - file: 'build/postmate.min.js', - format: 'umd', - name: 'Postmate', - sourcemap: false, - }, -} diff --git a/package.json b/package.json index d2f63a19..a9b7944b 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,9 @@ "name": "postmate", "version": "1.4.0", "description": "A powerful, simple, promise-based postMessage library", - "main": "build/postmate.min.js", + "main": "build/postmate.js", "module": "build/postmate.es.js", + "unpkg": "build/postmate.min.js", "files": [ "build", "src" @@ -22,6 +23,7 @@ "eslint-config-dollarshaveclub": "^3.0.4", "gulp": "^3.9.1", "gulp-mocha-phantomjs": "^0.12.2", + "gzip-size": "^4.1.0", "jest": "^22.0.0", "mocha": "^5.0.1", "mocha-phantomjs": "^4.1.0", @@ -29,6 +31,7 @@ "regenerator-runtime": "^0.11.1", "rollup": "^0.56.0", "rollup-plugin-babel": "^4.0.0-beta.0", + "rollup-plugin-replace": "^2.0.0", "rollup-plugin-uglify": "^3.0.0", "rsvp": "^4.8.1", "serve-static": "^1.11.1", @@ -41,10 +44,9 @@ "test:es-check": "es-check es5 build/postmate.min.js", "test:unit": "nyc jest --coverage", "test:acceptance": "gulp test --coverage", - "build": "npm run build:es && npm run build:standard && npm run build:readme", + "build": "npm run build:dist && npm run build:readme", "build:readme": "node ./scripts/update-readme.js", - "build:es": "rollup --config configs/rollup.config.es.js", - "build:standard": "rollup --config configs/rollup.config.standard.js", + "build:dist": "rollup --config configs/rollup.config.js", "postpublish": "git tag $npm_package_version && git push origin --tags" }, "jest": { diff --git a/scripts/update-readme.js b/scripts/update-readme.js index 654fa34f..24c02a78 100644 --- a/scripts/update-readme.js +++ b/scripts/update-readme.js @@ -1,8 +1,9 @@ const fs = require('fs') const path = require('path') +const gzipSize = require('gzip-size') const readme = path.join(__dirname, '/../README.md') const data = fs.readFileSync(readme, 'utf-8') -const distSize = fs.statSync(path.join(__dirname, 'build', '../../build/postmate.min.js')).size +const distSize = gzipSize.sync(fs.readFileSync(path.join(__dirname, 'build', '../../build/postmate.min.js'))) const updated = data.replace( /(.*?)<\/span>/, `\`${(distSize / 1024).toFixed(1)}kb\`` diff --git a/src/postmate.js b/src/postmate.js index fc0260b5..bb083221 100644 --- a/src/postmate.js +++ b/src/postmate.js @@ -95,13 +95,17 @@ export class ParentAPI { this.events = {} - log('Parent: Registering API') - log('Parent: Awaiting messages...') + if (process.env.NODE_ENV !== 'production') { + log('Parent: Registering API') + log('Parent: Awaiting messages...') + } this.listener = (e) => { const { data, name } = (((e || {}).data || {}).value || {}) if (e.data.postmate === 'emit') { - log(`Parent: Received event emission: ${name}`) + if (process.env.NODE_ENV !== 'production') { + log(`Parent: Received event emission: ${name}`) + } if (name in this.events) { this.events[name].call(this, data) } @@ -109,7 +113,9 @@ export class ParentAPI { } this.parent.addEventListener('message', this.listener, false) - log('Parent: Awaiting event emissions from Child') + if (process.env.NODE_ENV !== 'production') { + log('Parent: Awaiting event emissions from Child') + } } get (property) { @@ -151,7 +157,9 @@ export class ParentAPI { } destroy () { - log('Parent: Destroying Postmate instance') + if (process.env.NODE_ENV !== 'production') { + log('Parent: Destroying Postmate instance') + } window.removeEventListener('message', this.listener, false) this.frame.parentNode.removeChild(this.frame) } @@ -168,12 +176,17 @@ export class ChildAPI { this.parentOrigin = info.parentOrigin this.child = info.child - log('Child: Registering API') - log('Child: Awaiting messages...') + if (process.env.NODE_ENV !== 'production') { + log('Child: Registering API') + log('Child: Awaiting messages...') + } this.child.addEventListener('message', (e) => { if (!sanitize(e, this.parentOrigin)) return - log('Child: Received request', e.data) + + if (process.env.NODE_ENV !== 'production') { + log('Child: Received request', e.data) + } const { property, uid, data } = e.data @@ -197,7 +210,9 @@ export class ChildAPI { } emit (name, data) { - log(`Child: Emitting Event "${name}"`, data) + if (process.env.NODE_ENV !== 'production') { + log(`Child: Emitting Event "${name}"`, data) + } this.parent.postMessage({ postmate: 'emit', type: messsageType, @@ -258,16 +273,22 @@ class Postmate { if (!sanitize(e, childOrigin)) return false if (e.data.postmate === 'handshake-reply') { clearInterval(responseInterval) - log('Parent: Received handshake reply from Child') + if (process.env.NODE_ENV !== 'production') { + log('Parent: Received handshake reply from Child') + } this.parent.removeEventListener('message', reply, false) this.childOrigin = e.origin - log('Parent: Saving Child origin', this.childOrigin) + if (process.env.NODE_ENV !== 'production') { + log('Parent: Saving Child origin', this.childOrigin) + } return resolve(new ParentAPI(this)) } // Might need to remove since parent might be receiving different messages // from different hosts - log('Parent: Invalid handshake reply') + if (process.env.NODE_ENV !== 'production') { + log('Parent: Invalid handshake reply') + } return reject('Failed handshake') } @@ -275,7 +296,9 @@ class Postmate { const doSend = () => { attempt++ - log(`Parent: Sending handshake attempt ${attempt}`, { childOrigin }) + if (process.env.NODE_ENV !== 'production') { + log(`Parent: Sending handshake attempt ${attempt}`, { childOrigin }) + } this.child.postMessage({ postmate: 'handshake', type: messsageType, @@ -298,7 +321,9 @@ class Postmate { this.frame.onload = loaded } - log('Parent: Loading frame', { url }) + if (process.env.NODE_ENV !== 'production') { + log('Parent: Loading frame', { url }) + } this.frame.src = url }) } @@ -332,9 +357,13 @@ Postmate.Model = class Model { return } if (e.data.postmate === 'handshake') { - log('Child: Received handshake from Parent') + if (process.env.NODE_ENV !== 'production') { + log('Child: Received handshake from Parent') + } this.child.removeEventListener('message', shake, false) - log('Child: Sending handshake reply to Parent') + if (process.env.NODE_ENV !== 'production') { + log('Child: Sending handshake reply to Parent') + } e.source.postMessage({ postmate: 'handshake-reply', type: messsageType, @@ -350,10 +379,14 @@ Postmate.Model = class Model { this.model[keys[i]] = defaults[keys[i]] } } - log('Child: Inherited and extended model from Parent') + if (process.env.NODE_ENV !== 'production') { + log('Child: Inherited and extended model from Parent') + } } - log('Child: Saving Parent origin', this.parentOrigin) + if (process.env.NODE_ENV !== 'production') { + log('Child: Saving Parent origin', this.parentOrigin) + } return resolve(new ChildAPI(this)) } return reject('Handshake Reply Failed')