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')