UNPKG

penpal

Version:

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

122 lines (99 loc) 4.54 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _generateId = _interopRequireDefault(require("./generateId")); var _errorSerialization = require("./errorSerialization"); var _methodSerialization = require("./methodSerialization"); var _enums = require("./enums"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /** * Augments an object with methods that match those defined by the remote. When these methods are * called, a "call" message will be sent to the remote, the remote's corresponding method will be * executed, and the method's return value will be returned via a message. * @param {Object} callSender Sender object that should be augmented with methods. * @param {Object} info Information about the local and remote windows. * @param {Array} methodKeyPaths Key paths of methods available to be called on the remote. * @param {Promise} destructionPromise A promise resolved when destroy() is called on the penpal * connection. * @returns {Object} The call sender object with methods that may be called. */ var _default = (callSender, info, methodKeyPaths, destroyConnection, log) => { const { localName, local, remote, originForSending, originForReceiving } = info; let destroyed = false; log("".concat(localName, ": Connecting call sender")); const createMethodProxy = methodName => { return (...args) => { log("".concat(localName, ": Sending ").concat(methodName, "() call")); // This handles the case where the iframe has been removed from the DOM // (and therefore its window closed), the consumer has not yet // called destroy(), and the user calls a method exposed by // the remote. We detect the iframe has been removed and force // a destroy() immediately so that the consumer sees the error saying // the connection has been destroyed. We wrap this check in a try catch // because Edge throws an "Object expected" error when accessing // contentWindow.closed on a contentWindow from an iframe that's been // removed from the DOM. let iframeRemoved; try { if (remote.closed) { iframeRemoved = true; } } catch (e) { iframeRemoved = true; } if (iframeRemoved) { destroyConnection(); } if (destroyed) { const error = new Error("Unable to send ".concat(methodName, "() call due ") + "to destroyed connection"); error.code = _enums.ErrorCode.ConnectionDestroyed; throw error; } return new Promise((resolve, reject) => { const id = (0, _generateId.default)(); const handleMessageEvent = event => { if (event.source !== remote || event.data.penpal !== _enums.MessageType.Reply || event.data.id !== id) { return; } if (originForReceiving !== '*' && event.origin !== originForReceiving) { log("".concat(localName, " received message from origin ").concat(event.origin, " which did not match expected origin ").concat(originForReceiving)); return; } const replyMessage = event.data; log("".concat(localName, ": Received ").concat(methodName, "() reply")); local.removeEventListener(_enums.NativeEventType.Message, handleMessageEvent); let returnValue = replyMessage.returnValue; if (replyMessage.returnValueIsError) { returnValue = (0, _errorSerialization.deserializeError)(returnValue); } (replyMessage.resolution === _enums.Resolution.Fulfilled ? resolve : reject)(returnValue); }; local.addEventListener(_enums.NativeEventType.Message, handleMessageEvent); const callMessage = { penpal: _enums.MessageType.Call, id, methodName, args }; remote.postMessage(callMessage, originForSending); }); }; }; // Wrap each method in a proxy which sends it to the corresponding receiver. const flattenedMethods = methodKeyPaths.reduce((api, name) => { api[name] = createMethodProxy(name); return api; }, {}); // Unpack the structure of the provided methods object onto the CallSender, exposing // the methods in the same shape they were provided. Object.assign(callSender, (0, _methodSerialization.deserializeMethods)(flattenedMethods)); return () => { destroyed = true; }; }; exports.default = _default;