@ont-community/window-post-message-proxy
Version:
A library used in place of the native window.postMessage which when used on both the sending and receiving windows allow for a nicer asynchronouse promise messaging between the windows
311 lines (308 loc) • 14.1 kB
JavaScript
/*! @ont-community/window-post-message-proxy v0.2.14 | (c) 2016 Microsoft Corporation MIT */
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define([], factory);
else if(typeof exports === 'object')
exports["@ont-community/window-post-message-proxy"] = factory();
else
root["@ont-community/window-post-message-proxy"] = factory();
})(this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var WindowPostMessageProxy = /** @class */ (function () {
function WindowPostMessageProxy(options) {
if (options === void 0) { options = {
processTrackingProperties: {
addTrackingProperties: WindowPostMessageProxy.defaultAddTrackingProperties,
getTrackingProperties: WindowPostMessageProxy.defaultGetTrackingProperties
},
isErrorMessage: WindowPostMessageProxy.defaultIsErrorMessage,
extractErrorMessage: WindowPostMessageProxy.defaultExtractErrorMessage,
receiveWindow: window,
name: WindowPostMessageProxy.createRandomString()
}; }
var _this = this;
this.pendingRequestPromises = {};
// save options with defaults
this.addTrackingProperties = (options.processTrackingProperties && options.processTrackingProperties.addTrackingProperties) || WindowPostMessageProxy.defaultAddTrackingProperties;
this.getTrackingProperties = (options.processTrackingProperties && options.processTrackingProperties.getTrackingProperties) || WindowPostMessageProxy.defaultGetTrackingProperties;
this.isErrorMessage = options.isErrorMessage || WindowPostMessageProxy.defaultIsErrorMessage;
this.extractErrorMessage = options.extractErrorMessage || WindowPostMessageProxy.defaultExtractErrorMessage;
this.receiveWindow = options.receiveWindow || window;
this.name = options.name || WindowPostMessageProxy.createRandomString();
this.target = options.target;
this.logMessages = options.logMessages || false;
this.eventSourceOverrideWindow = options.eventSourceOverrideWindow;
this.suppressWarnings = options.suppressWarnings || false;
if (this.logMessages) {
console.log("new WindowPostMessageProxy created with name: " + this.name + " receiving on window: " + this.receiveWindow.document.title);
}
// Initialize
this.handlers = [];
this.windowMessageHandler = function (event) { return _this.onMessageReceived(event); };
this.start();
}
// Static
WindowPostMessageProxy.defaultAddTrackingProperties = function (message, trackingProperties) {
message[WindowPostMessageProxy.messagePropertyName] = trackingProperties;
return message;
};
WindowPostMessageProxy.defaultGetTrackingProperties = function (message) {
return message[WindowPostMessageProxy.messagePropertyName];
};
WindowPostMessageProxy.defaultIsErrorMessage = function (message) {
return !!message.error;
};
WindowPostMessageProxy.defaultExtractErrorMessage = function (message) {
return message.error;
};
/**
* Utility to create a deferred object.
*/
// TODO: Look to use RSVP library instead of doing this manually.
// From what I searched RSVP would work better because it has .finally and .deferred; however, it doesn't have Typings information.
WindowPostMessageProxy.createDeferred = function () {
var deferred = {
resolve: null,
reject: null,
promise: null
};
var promise = new Promise(function (resolve, reject) {
deferred.resolve = resolve;
deferred.reject = reject;
});
deferred.promise = promise;
return deferred;
};
/**
* Utility to generate random sequence of characters used as tracking id for promises.
*/
WindowPostMessageProxy.createRandomString = function () {
return (Math.random() + 1).toString(36).substring(7);
};
/**
* Adds handler.
* If the first handler whose test method returns true will handle the message and provide a response.
*/
WindowPostMessageProxy.prototype.addHandler = function (handler) {
this.handlers.push(handler);
};
/**
* Removes handler.
* The reference must match the original object that was provided when adding the handler.
*/
WindowPostMessageProxy.prototype.removeHandler = function (handler) {
var handlerIndex = this.handlers.indexOf(handler);
if (handlerIndex === -1) {
throw new Error("You attempted to remove a handler but no matching handler was found.");
}
this.handlers.splice(handlerIndex, 1);
};
/**
* Start listening to message events.
*/
WindowPostMessageProxy.prototype.start = function () {
this.receiveWindow.addEventListener('message', this.windowMessageHandler);
};
/**
* Stops listening to message events.
*/
WindowPostMessageProxy.prototype.stop = function () {
this.receiveWindow.removeEventListener('message', this.windowMessageHandler);
};
/**
* Post message to target window with tracking properties added and save deferred object referenced by tracking id.
*/
WindowPostMessageProxy.prototype.postMessage = function (targetWindow, message) {
// Add tracking properties to indicate message came from this proxy
var trackingProperties = {
id: WindowPostMessageProxy.createRandomString(),
source: this.name,
target: this.target
};
this.addTrackingProperties(message, trackingProperties);
if (this.logMessages) {
console.warn("Proxy(" + this.name + "): Sending:", JSON.stringify(message, null, ' '));
}
targetWindow.postMessage(message, "*");
var deferred = WindowPostMessageProxy.createDeferred();
this.pendingRequestPromises[trackingProperties.id] = deferred;
return deferred.promise;
};
/**
* Send response message to target window.
* Response messages re-use tracking properties from a previous request message.
*/
WindowPostMessageProxy.prototype.sendResponse = function (targetWindow, message, trackingProperties) {
this.addTrackingProperties(message, trackingProperties);
if (this.logMessages) {
console.warn("Proxy(" + this.name + "): Responding:", JSON.stringify(message, null, ' '));
}
targetWindow.postMessage(message, "*");
};
/**
* Message handler.
*/
WindowPostMessageProxy.prototype.onMessageReceived = function (event) {
var _this = this;
var sendingWindow = this.eventSourceOverrideWindow || event.source;
var message = event.data;
if (typeof message !== "object") {
if (!this.suppressWarnings) {
console.warn("Proxy(" + this.name + "): Received message that was not an object. Discarding message");
}
return;
}
var trackingProperties;
try {
trackingProperties = this.getTrackingProperties(message);
if (trackingProperties && trackingProperties.target != null) {
if (trackingProperties.target !== this.name) {
// skips messages which are not for me
return;
}
}
if (this.logMessages) {
console.warn("Proxy(" + this.name + "): Receiving:", JSON.stringify(event.data, null, ' '));
}
}
catch (e) {
if (!this.suppressWarnings) {
console.warn("Proxy(" + this.name + "): Error occurred when attempting to get tracking properties from incoming message:", JSON.stringify(message, null, ' '), "Error: ", e);
}
}
var deferred;
if (trackingProperties) {
deferred = this.pendingRequestPromises[trackingProperties.id];
}
// If message does not have a known ID, treat it as a request
// Otherwise, treat message as response
if (!deferred) {
var handled = this.handlers.some(function (handler) {
var canMessageBeHandled = false;
try {
canMessageBeHandled = handler.test(message);
}
catch (e) {
if (!_this.suppressWarnings) {
console.warn("Proxy(" + _this.name + "): Error occurred when handler was testing incoming message:", JSON.stringify(message, null, ' '), "Error: ", e);
}
}
if (canMessageBeHandled) {
var responseMessagePromise = void 0;
try {
responseMessagePromise = Promise.resolve(handler.handle(message));
}
catch (e) {
if (!_this.suppressWarnings) {
console.warn("Proxy(" + _this.name + "): Error occurred when handler was processing incoming message:", JSON.stringify(message, null, ' '), "Error: ", e);
}
responseMessagePromise = Promise.resolve();
}
responseMessagePromise
.then(function (responseMessage) {
if (!responseMessage) {
var warningMessage = "Handler for message: " + JSON.stringify(message, null, ' ') + " did not return a response message. The default response message will be returned instead.";
if (!_this.suppressWarnings) {
console.warn("Proxy(" + _this.name + "): " + warningMessage);
}
responseMessage = {
warning: warningMessage
};
}
trackingProperties.target = trackingProperties.source;
trackingProperties.source = _this.name;
_this.sendResponse(sendingWindow, responseMessage, trackingProperties);
});
return true;
}
});
/**
* TODO: Consider returning an error message if nothing handled the message.
* In the case of the Report receiving messages all of them should be handled,
* however, in the case of the SDK receiving messages it's likely it won't register handlers
* for all events. Perhaps make this an option at construction time.
*/
if (!handled && !this.suppressWarnings) {
console.warn("Proxy(" + this.name + ") did not handle message. Handlers: " + this.handlers.length + " Message: " + JSON.stringify(message, null, '') + ".");
// this.sendResponse({ notHandled: true }, trackingProperties);
}
}
else {
/**
* If error message reject promise,
* Otherwise, resolve promise
*/
var isErrorMessage = true;
try {
isErrorMessage = this.isErrorMessage(message);
}
catch (e) {
console.warn("Proxy(" + this.name + ") Error occurred when trying to determine if message is consider an error response. Message: ", JSON.stringify(message, null, ''), 'Error: ', e);
}
if (isErrorMessage) {
deferred.reject(this.extractErrorMessage(message));
}
else {
deferred.resolve(message);
}
// TODO: Move to .finally clause up where promise is created for better maitenance like original proxy code.
delete this.pendingRequestPromises[trackingProperties.id];
}
};
WindowPostMessageProxy.messagePropertyName = "windowPostMessageProxy";
return WindowPostMessageProxy;
}());
exports.WindowPostMessageProxy = WindowPostMessageProxy;
/***/ })
/******/ ])
});
;
//# sourceMappingURL=windowPostMessageProxy.js.map