diff --git a/.github/workflows/build-branches.yml b/.github/workflows/build-branches.yml new file mode 100644 index 00000000..977600ab --- /dev/null +++ b/.github/workflows/build-branches.yml @@ -0,0 +1,18 @@ +name: Build branches +on: + push: + branches-ignore: + - master +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + registry-url: https://registry.npmjs.org/ + - run: yarn + - run: yarn eslint:ci + - run: yarn build + - run: yarn test + - run: npx codecov diff --git a/.github/workflows/publish-on-npm-branches.yml b/.github/workflows/publish-on-npm-branches.yml new file mode 100644 index 00000000..fd9cc765 --- /dev/null +++ b/.github/workflows/publish-on-npm-branches.yml @@ -0,0 +1,21 @@ +name: Publish on NPM - branches +on: + workflow_dispatch: + branches-ignore: + - master +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + registry-url: https://registry.npmjs.org/ + - run: yarn + - run: yarn eslint:ci + - run: yarn build + - run: yarn test + - run: npx codecov + - run: npm publish --tag beta + env: + NODE_AUTH_TOKEN: ${{secrets.npm_token}} \ No newline at end of file diff --git a/.github/workflows/publish-on-npm.yml b/.github/workflows/publish-on-npm.yml index 34e98249..66eccb49 100644 --- a/.github/workflows/publish-on-npm.yml +++ b/.github/workflows/publish-on-npm.yml @@ -1,6 +1,8 @@ name: Publish on NPM -on: - push +on: + push: + branches: + - master jobs: build: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 95547141..201857c5 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ coverage/ coverage.lcov coverage_*/ .idea/ +build/ \ No newline at end of file diff --git a/build/postmate.dev.js b/build/postmate.dev.js deleted file mode 100644 index 936454ab..00000000 --- a/build/postmate.dev.js +++ /dev/null @@ -1,503 +0,0 @@ -/** - postmate - A powerful, simple, promise-based postMessage library - @version v1.6.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 = global || self, global.Postmate = factory()); -}(this, (function () { 'use strict'; - - /** - * The type of messages our frames our sending - * @type {String} - */ - var messageType = 'application/x-postmate-v1+json'; - /** - * 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 generateNewMessageId = function generateNewMessageId() { - 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; - var protocol = a.protocol.length > 4 ? a.protocol : window.location.protocol; - var host = a.host.length ? a.port === '80' || a.port === '443' ? a.hostname : a.host : window.location.host; - return a.origin || protocol + "//" + host; - }; - var messageTypes = { - handshake: 1, - 'handshake-reply': 1, - call: 1, - emit: 1, - reply: 1, - request: 1 - /** - * Ensures that a message is safe to interpret - * @param {Object} message The postmate message being sent - * @param {String|Boolean} allowedOrigin The whitelisted origin or false to skip origin check - * @return {Boolean} - */ - - }; - var sanitize = function sanitize(message, allowedOrigin) { - if (typeof allowedOrigin === 'string' && message.origin !== allowedOrigin) return false; - if (!message.data) return false; - if (typeof message.data === 'object' && !('postmate' in message.data)) return false; - if (message.data.type !== messageType) return false; - if (!messageTypes[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) { - if (!sanitize(e, _this.childOrigin)) return false; - /** - * the assignments below ensures that e, data, and value are all defined - */ - - 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].forEach(function (callback) { - callback.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 = generateNewMessageId(); - - 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: messageType, - property: property, - uid: uid - }, _this2.childOrigin); - }); - }; - - _proto.call = function call(property, data) { - // Send information to the child - this.child.postMessage({ - postmate: 'call', - type: messageType, - property: property, - data: data - }, this.childOrigin); - }; - - _proto.on = function on(eventName, callback) { - if (!this.events[eventName]) { - this.events[eventName] = []; - } - - this.events[eventName].push(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](data); - } - - return; - } // Reply to Parent - - - resolveValue(_this3.model, property).then(function (value) { - return e.source.postMessage({ - property: property, - postmate: 'reply', - type: messageType, - 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: messageType, - 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} object The element to inject the frame into, and the url - * @return {Promise} - */ - function Postmate(_ref2) { - var _ref2$container = _ref2.container, - container = _ref2$container === void 0 ? typeof container !== 'undefined' ? container : document.body : _ref2$container, - model = _ref2.model, - url = _ref2.url, - name = _ref2.name, - _ref2$classListArray = _ref2.classListArray, - classListArray = _ref2$classListArray === void 0 ? [] : _ref2$classListArray; - // eslint-disable-line no-undef - this.parent = window; - this.frame = document.createElement('iframe'); - this.frame.name = name || ''; - this.frame.classList.add.apply(this.frame.classList, classListArray); - 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: messageType, - 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.addEventListener('load', 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: messageType - }, e.origin); - _this5.parentOrigin = e.origin; // Extend model with the one provided by the parent - - var defaults = e.data.model; - - if (defaults) { - Object.keys(defaults).forEach(function (key) { - _this5.model[key] = defaults[key]; - }); - - { - 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 deleted file mode 100644 index e6ce5057..00000000 --- a/build/postmate.es.js +++ /dev/null @@ -1,495 +0,0 @@ -/** - postmate - A powerful, simple, promise-based postMessage library - @version v1.6.0 - @link https://github.com/dollarshaveclub/postmate - @author Jacob Kelley - @license MIT -**/ -/** - * The type of messages our frames our sending - * @type {String} - */ -var messageType = 'application/x-postmate-v1+json'; -/** - * 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 generateNewMessageId = function generateNewMessageId() { - 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; - var protocol = a.protocol.length > 4 ? a.protocol : window.location.protocol; - var host = a.host.length ? a.port === '80' || a.port === '443' ? a.hostname : a.host : window.location.host; - return a.origin || protocol + "//" + host; -}; -var messageTypes = { - handshake: 1, - 'handshake-reply': 1, - call: 1, - emit: 1, - reply: 1, - request: 1 - /** - * Ensures that a message is safe to interpret - * @param {Object} message The postmate message being sent - * @param {String|Boolean} allowedOrigin The whitelisted origin or false to skip origin check - * @return {Boolean} - */ - -}; -var sanitize = function sanitize(message, allowedOrigin) { - if (typeof allowedOrigin === 'string' && message.origin !== allowedOrigin) return false; - if (!message.data) return false; - if (typeof message.data === 'object' && !('postmate' in message.data)) return false; - if (message.data.type !== messageType) return false; - if (!messageTypes[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) { - if (!sanitize(e, _this.childOrigin)) return false; - /** - * the assignments below ensures that e, data, and value are all defined - */ - - 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].forEach(function (callback) { - callback.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 = generateNewMessageId(); - - 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: messageType, - property: property, - uid: uid - }, _this2.childOrigin); - }); - }; - - _proto.call = function call(property, data) { - // Send information to the child - this.child.postMessage({ - postmate: 'call', - type: messageType, - property: property, - data: data - }, this.childOrigin); - }; - - _proto.on = function on(eventName, callback) { - if (!this.events[eventName]) { - this.events[eventName] = []; - } - - this.events[eventName].push(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](data); - } - - return; - } // Reply to Parent - - - resolveValue(_this3.model, property).then(function (value) { - return e.source.postMessage({ - property: property, - postmate: 'reply', - type: messageType, - 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: messageType, - 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} object The element to inject the frame into, and the url - * @return {Promise} - */ - function Postmate(_ref2) { - var _ref2$container = _ref2.container, - container = _ref2$container === void 0 ? typeof container !== 'undefined' ? container : document.body : _ref2$container, - model = _ref2.model, - url = _ref2.url, - name = _ref2.name, - _ref2$classListArray = _ref2.classListArray, - classListArray = _ref2$classListArray === void 0 ? [] : _ref2$classListArray; - // eslint-disable-line no-undef - this.parent = window; - this.frame = document.createElement('iframe'); - this.frame.name = name || ''; - this.frame.classList.add.apply(this.frame.classList, classListArray); - 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: messageType, - 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.addEventListener('load', 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: messageType - }, e.origin); - _this5.parentOrigin = e.origin; // Extend model with the one provided by the parent - - var defaults = e.data.model; - - if (defaults) { - Object.keys(defaults).forEach(function (key) { - _this5.model[key] = defaults[key]; - }); - - 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; -}(); - -export default Postmate; diff --git a/build/postmate.js b/build/postmate.js deleted file mode 100644 index 8f83a10d..00000000 --- a/build/postmate.js +++ /dev/null @@ -1,497 +0,0 @@ -/** - postmate - A powerful, simple, promise-based postMessage library - @version v1.6.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 messageType = 'application/x-postmate-v1+json'; -/** - * 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 generateNewMessageId = function generateNewMessageId() { - 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; - var protocol = a.protocol.length > 4 ? a.protocol : window.location.protocol; - var host = a.host.length ? a.port === '80' || a.port === '443' ? a.hostname : a.host : window.location.host; - return a.origin || protocol + "//" + host; -}; -var messageTypes = { - handshake: 1, - 'handshake-reply': 1, - call: 1, - emit: 1, - reply: 1, - request: 1 - /** - * Ensures that a message is safe to interpret - * @param {Object} message The postmate message being sent - * @param {String|Boolean} allowedOrigin The whitelisted origin or false to skip origin check - * @return {Boolean} - */ - -}; -var sanitize = function sanitize(message, allowedOrigin) { - if (typeof allowedOrigin === 'string' && message.origin !== allowedOrigin) return false; - if (!message.data) return false; - if (typeof message.data === 'object' && !('postmate' in message.data)) return false; - if (message.data.type !== messageType) return false; - if (!messageTypes[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) { - if (!sanitize(e, _this.childOrigin)) return false; - /** - * the assignments below ensures that e, data, and value are all defined - */ - - 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].forEach(function (callback) { - callback.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 = generateNewMessageId(); - - 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: messageType, - property: property, - uid: uid - }, _this2.childOrigin); - }); - }; - - _proto.call = function call(property, data) { - // Send information to the child - this.child.postMessage({ - postmate: 'call', - type: messageType, - property: property, - data: data - }, this.childOrigin); - }; - - _proto.on = function on(eventName, callback) { - if (!this.events[eventName]) { - this.events[eventName] = []; - } - - this.events[eventName].push(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](data); - } - - return; - } // Reply to Parent - - - resolveValue(_this3.model, property).then(function (value) { - return e.source.postMessage({ - property: property, - postmate: 'reply', - type: messageType, - 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: messageType, - 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} object The element to inject the frame into, and the url - * @return {Promise} - */ - function Postmate(_ref2) { - var _ref2$container = _ref2.container, - container = _ref2$container === void 0 ? typeof container !== 'undefined' ? container : document.body : _ref2$container, - model = _ref2.model, - url = _ref2.url, - name = _ref2.name, - _ref2$classListArray = _ref2.classListArray, - classListArray = _ref2$classListArray === void 0 ? [] : _ref2$classListArray; - // eslint-disable-line no-undef - this.parent = window; - this.frame = document.createElement('iframe'); - this.frame.name = name || ''; - this.frame.classList.add.apply(this.frame.classList, classListArray); - 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: messageType, - 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.addEventListener('load', 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: messageType - }, e.origin); - _this5.parentOrigin = e.origin; // Extend model with the one provided by the parent - - var defaults = e.data.model; - - if (defaults) { - Object.keys(defaults).forEach(function (key) { - _this5.model[key] = defaults[key]; - }); - - 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 deleted file mode 100644 index fe713a4c..00000000 --- a/build/postmate.min.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - postmate - A powerful, simple, promise-based postMessage library - @version v1.6.0 - @link https://github.com/dollarshaveclub/postmate - @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=e||self).Postmate=t()}(this,function(){"use strict";var h="application/x-postmate-v1+json",r=0,n={handshake:1,"handshake-reply":1,call:1,emit:1,reply:1,request:1},p=function(e,t){return("string"!=typeof t||e.origin===t)&&(!!e.data&&(("object"!=typeof e.data||"postmate"in e.data)&&(e.data.type===h&&!!n[e.data.postmate])))},c=function(){function e(e){var a=this;this.parent=e.parent,this.frame=e.frame,this.child=e.child,this.childOrigin=e.childOrigin,this.events={},this.listener=function(e){if(!p(e,a.childOrigin))return!1;var t=((e||{}).data||{}).value||{},n=t.data,i=t.name;"emit"===e.data.postmate&&i in a.events&&a.events[i].forEach(function(e){e.call(a,n)})},this.parent.addEventListener("message",this.listener,!1)}var t=e.prototype;return t.get=function(e){var a=this;return new l.Promise(function(n){var i=++r;a.parent.addEventListener("message",function e(t){t.data.uid===i&&"reply"===t.data.postmate&&(a.parent.removeEventListener("message",e,!1),n(t.data.value))},!1),a.child.postMessage({postmate:"request",type:h,property:e,uid:i},a.childOrigin)})},t.call=function(e,t){this.child.postMessage({postmate:"call",type:h,property:e,data:t},this.childOrigin)},t.on=function(e,t){this.events[e]||(this.events[e]=[]),this.events[e].push(t)},t.destroy=function(){window.removeEventListener("message",this.listener,!1),this.frame.parentNode.removeChild(this.frame)},e}(),s=function(){function e(e){var d=this;this.model=e.model,this.parent=e.parent,this.parentOrigin=e.parentOrigin,this.child=e.child,this.child.addEventListener("message",function(t){if(p(t,d.parentOrigin)){var e,n,i,a=t.data,r=a.property,s=a.uid,o=a.data;if("call"!==t.data.postmate)(e=d.model,n=r,i="function"==typeof e[n]?e[n]():e[n],l.Promise.resolve(i)).then(function(e){return t.source.postMessage({property:r,postmate:"reply",type:h,uid:s,value:e},t.origin)});else r in d.model&&"function"==typeof d.model[r]&&d.model[r](o)}})}return e.prototype.emit=function(e,t){this.parent.postMessage({postmate:"emit",type:h,value:{name:e,data:t}},this.parentOrigin)},e}(),l=function(){function e(e){var t=e.container,n=void 0===t?void 0!==n?n:document.body:t,i=e.model,a=e.url,r=e.name,s=e.classListArray,o=void 0===s?[]:s;return this.parent=window,this.frame=document.createElement("iframe"),this.frame.name=r||"",this.frame.classList.add.apply(this.frame.classList,o),n.appendChild(this.frame),this.child=this.frame.contentWindow||this.frame.contentDocument.parentWindow,this.model=i||{},this.sendHandshake(a)}return e.prototype.sendHandshake=function(a){var r,s=this,o=function(e){var t=document.createElement("a");t.href=e;var n=4 { { banner, name: 'Postmate', - } - ) + }, + ), ), } } diff --git a/package.json b/package.json index 2b47655d..4d1e4b8a 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "popostmate", - "version": "1.6.0", + "version": "1.6.2", "description": "A powerful, simple, promise-based postMessage library", - "main": "build/popostmate.js", - "module": "build/popostmate.es.js", - "unpkg": "build/popostmate.min.js", + "main": "build/postmate.js", + "module": "build/postmate.es.js", + "unpkg": "build/postmate.min.js", "files": [ "build", "src" @@ -48,7 +48,7 @@ "test": "npm run test:unit && npm run test:acceptance && npm run test:es-check", "test:es-check": "es-check es5 build/postmate.min.js", "test:unit": "jest --coverage", - "test:acceptance": "gulp test", + "test:acceptance": "gulp test ", "build": "npm run build:dist && npm run build:readme", "build:readme": "node ./scripts/update-readme.js", "build:dist": "rollup --config configs/rollup.config.js", diff --git a/scripts/update-readme.js b/scripts/update-readme.js index 24c02a78..ee647c2a 100644 --- a/scripts/update-readme.js +++ b/scripts/update-readme.js @@ -6,6 +6,6 @@ const data = fs.readFileSync(readme, 'utf-8') const distSize = gzipSize.sync(fs.readFileSync(path.join(__dirname, 'build', '../../build/postmate.min.js'))) const updated = data.replace( /(.*?)<\/span>/, - `\`${(distSize / 1024).toFixed(1)}kb\`` + `\`${(distSize / 1024).toFixed(1)}kb\``, ) fs.writeFileSync(readme, updated) diff --git a/src/postmate.js b/src/postmate.js index 0196f0ae..f35e46d6 100644 --- a/src/postmate.js +++ b/src/postmate.js @@ -23,6 +23,20 @@ let _messageId = 0 */ export const generateNewMessageId = () => ++_messageId +/** + * A unique child ID that is used to ensure responses are received from the correct child iframes + * @type {Number} + */ +let _childId = 0 + +/** + * Increments and returns a message ID + * @return {Number} A unique ID for a message + */ +function childId () { + return ++_childId +} + /** * Postmate logging function that enables/disables via config * @param {Object} ...args Rest Arguments @@ -96,6 +110,7 @@ export class ParentAPI { this.frame = info.frame this.child = info.child this.childOrigin = info.childOrigin + this.childId = info.childId this.events = {} @@ -112,7 +127,7 @@ export class ParentAPI { */ const { data, name } = (((e || {}).data || {}).value || {}) - if (e.data.postmate === 'emit') { + if (e.data.postmate === 'emit' && e.data.childId === this.childId) { if (process.env.NODE_ENV !== 'production') { log(`Parent: Received event emission: ${name}`) } @@ -135,7 +150,7 @@ export class ParentAPI { // Extract data from response and kill listeners const uid = generateNewMessageId() const transact = (e) => { - if (e.data.uid === uid && e.data.postmate === 'reply') { + if (e.data.uid === uid && e.data.postmate === 'reply' && e.data.childId === this.childId) { this.parent.removeEventListener('message', transact, false) resolve(e.data.value) } @@ -190,6 +205,7 @@ export class ChildAPI { this.parent = info.parent this.parentOrigin = info.parentOrigin this.child = info.child + this.childId = info.childId if (process.env.NODE_ENV !== 'production') { log('Child: Registering API') @@ -218,6 +234,7 @@ export class ChildAPI { property, postmate: 'reply', type: messageType, + childId: this.childId, uid, value, }, e.origin)) @@ -231,6 +248,7 @@ export class ChildAPI { this.parent.postMessage({ postmate: 'emit', type: messageType, + childId: this.childId, value: { name, data, @@ -274,6 +292,7 @@ class Postmate { container.appendChild(this.frame) this.child = this.frame.contentWindow || this.frame.contentDocument.parentWindow this.model = model || {} + this.childId = childId() return this.sendHandshake(url) } @@ -290,6 +309,7 @@ class Postmate { return new Postmate.Promise((resolve, reject) => { const reply = (e) => { if (!sanitize(e, childOrigin)) return false + if (e.data.childId !== this.childId) return false if (e.data.postmate === 'handshake-reply') { clearInterval(responseInterval) if (process.env.NODE_ENV !== 'production') { @@ -322,6 +342,7 @@ class Postmate { postmate: 'handshake', type: messageType, model: this.model, + childId: this.childId, }, childOrigin) if (attempt === maxHandshakeRequests) { @@ -386,9 +407,10 @@ Postmate.Model = class Model { e.source.postMessage({ postmate: 'handshake-reply', type: messageType, + childId: e.data.childId, }, e.origin) + this.childId = e.data.childId this.parentOrigin = e.origin - // Extend model with the one provided by the parent const defaults = e.data.model if (defaults) { diff --git a/test/acceptance/test.js b/test/acceptance/test.js index d0984986..496d4713 100644 --- a/test/acceptance/test.js +++ b/test/acceptance/test.js @@ -124,6 +124,48 @@ describe('postmate', function () { }) }) + it('should listen and receive events from the child, but only from the set child', function (done) { + var uid1 = new Date().getTime() - 47 + var uid2 = new Date().getTime() + 21961 + + var build = function (uid) { + return new Postmate({ + container: document.getElementById('frame'), + url: 'http://localhost:9000/child.html', + model: { + uid: uid, + }, + }) + } + RSVP.hash({ + c1: build(uid1), + c2: build(uid2), + }).then(function (data) { + var callCount = 0 + data.c1.on('validated', function (response) { + expect(response).to.equal(uid1) + callCount++ + if (callCount >= 2) { + data.c1.destroy() + done() + } + }) + + data.c2.on('validated', function (response) { + expect(response).to.equal(uid2) + callCount++ + if (callCount >= 2) { + data.c2.destroy() + done() + } + }) + + // This is abnormal, but we are going to trigger the event 1 second after this function is called + data.c1.get('doValidate').catch(function (err) { done(err) }) + data.c2.get('doValidate').catch(function (err) { done(err) }) + }).catch(function (err) { done(err) }) + }) + it('should resolve multiple promises', function (done) { new Postmate({ container: document.getElementById('frame'),