penpal
Version:
A promise-based library for communicating with iframes via postMessage.
122 lines (99 loc) • 4.54 kB
JavaScript
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;
;