define(['exports', 'tslib'], (function (exports, tslib) { 'use strict';

    var smallestRandom = parseInt("10000000000", 36);
    var maxSafeInteger = Number.MAX_SAFE_INTEGER || 9007199254740991;
    /**
     * Create a new random 22-character fingerprint.
     * @return string fingerprint
     */
    function newFingerprint() {
        // smallestRandom ensures we will get a 11-character result from the base-36 conversion.
        return Math.floor((Math.random() * (maxSafeInteger - smallestRandom)) + smallestRandom).toString(36) +
            Math.floor((Math.random() * (maxSafeInteger - smallestRandom)) + smallestRandom).toString(36);
    }
    /**
     * Gets all own and inherited property names of the given object, excluding
     * those that are inherited from Object's prototype and "constructor".
     * @param obj
     */
    function getAllPropertyNames(obj) {
        var properties = {};
        while (obj && obj !== Object.prototype) {
            var ownPropertyNames = Object.getOwnPropertyNames(obj);
            for (var _i = 0, ownPropertyNames_1 = ownPropertyNames; _i < ownPropertyNames_1.length; _i++) {
                var name_1 = ownPropertyNames_1[_i];
                if (name_1 !== "constructor") {
                    properties[name_1] = true;
                }
            }
            obj = Object.getPrototypeOf(obj);
        }
        return properties;
    }
    /**
     * Catalog of objects exposed for XDM
     */
    var XDMObjectRegistry = /** @class */ (function () {
        function XDMObjectRegistry() {
            this.objects = {};
        }
        /**
        * Register an object (instance or factory method) exposed by this frame to callers in a remote frame
        *
        * @param instanceId - unique id of the registered object
        * @param instance - Either: (1) an object instance, or (2) a function that takes optional context data and returns an object instance.
        */
        XDMObjectRegistry.prototype.register = function (instanceId, instance) {
            this.objects[instanceId] = instance;
        };
        /**
        * Unregister an object (instance or factory method) that was previously registered by this frame
        *
        * @param instanceId - unique id of the registered object
        */
        XDMObjectRegistry.prototype.unregister = function (instanceId) {
            delete this.objects[instanceId];
        };
        /**
        * Get an instance of an object registered with the given id
        *
        * @param instanceId - unique id of the registered object
        * @param contextData - Optional context data to pass to a registered object's factory method
        */
        XDMObjectRegistry.prototype.getInstance = function (instanceId, contextData) {
            var instance = this.objects[instanceId];
            if (!instance) {
                return undefined;
            }
            if (typeof instance === "function") {
                return instance(contextData);
            }
            else {
                return instance;
            }
        };
        return XDMObjectRegistry;
    }());
    var MAX_XDM_DEPTH = 100;
    var nextChannelId = 1;
    /**
     * Represents a channel of communication between frames\document
     * Stays "alive" across multiple funtion\method calls
     */
    var XDMChannel = /** @class */ (function () {
        function XDMChannel(postToWindow, targetOrigin) {
            this.promises = {};
            this.nextMessageId = 1;
            this.nextProxyId = 1;
            this.proxyFunctions = {};
            this.postToWindow = postToWindow;
            this.targetOrigin = targetOrigin;
            this.registry = new XDMObjectRegistry();
            this.channelId = nextChannelId++;
            if (!this.targetOrigin) {
                this.handshakeToken = newFingerprint();
            }
        }
        /**
        * Get the object registry to handle messages from this specific channel.
        * Upon receiving a message, this channel registry will be used first, then
        * the global registry will be used if no handler is found here.
        */
        XDMChannel.prototype.getObjectRegistry = function () {
            return this.registry;
        };
        /**
        * Invoke a method via RPC. Lookup the registered object on the remote end of the channel and invoke the specified method.
        *
        * @param method - Name of the method to invoke
        * @param instanceId - unique id of the registered object
        * @param params - Arguments to the method to invoke
        * @param instanceContextData - Optional context data to pass to a registered object's factory method
        * @param serializationSettings - Optional serialization settings
        */
        XDMChannel.prototype.invokeRemoteMethod = function (methodName, instanceId, params, instanceContextData, serializationSettings) {
            return tslib.__awaiter(this, void 0, void 0, function () {
                var message, promise;
                var _this = this;
                return tslib.__generator(this, function (_a) {
                    message = {
                        id: this.nextMessageId++,
                        methodName: methodName,
                        instanceId: instanceId,
                        instanceContext: instanceContextData,
                        params: this._customSerializeObject(params, serializationSettings),
                        serializationSettings: serializationSettings
                    };
                    if (!this.targetOrigin) {
                        message.handshakeToken = this.handshakeToken;
                    }
                    promise = new Promise(function (resolve, reject) {
                        _this.promises[message.id] = { resolve: resolve, reject: reject };
                    });
                    this._sendRpcMessage(message);
                    return [2 /*return*/, promise];
                });
            });
        };
        /**
        * Get a proxied object that represents the object registered with the given instance id on the remote side of this channel.
        *
        * @param instanceId - unique id of the registered object
        * @param contextData - Optional context data to pass to a registered object's factory method
        */
        XDMChannel.prototype.getRemoteObjectProxy = function (instanceId, contextData) {
            return this.invokeRemoteMethod("", instanceId, undefined, contextData);
        };
        XDMChannel.prototype.invokeMethod = function (registeredInstance, rpcMessage) {
            var _this = this;
            if (!rpcMessage.methodName) {
                // Null/empty method name indicates to return the registered object itself.
                this._success(rpcMessage, registeredInstance, rpcMessage.handshakeToken);
                return;
            }
            var method = registeredInstance[rpcMessage.methodName];
            if (typeof method !== "function") {
                this.error(rpcMessage, new Error("RPC method not found: " + rpcMessage.methodName));
                return;
            }
            try {
                // Call specified method.  Add nested success and error call backs with closure
                // so we can post back a response as a result or error as appropriate
                var methodArgs = [];
                if (rpcMessage.params) {
                    methodArgs = this._customDeserializeObject(rpcMessage.params, {});
                }
                var result = method.apply(registeredInstance, methodArgs);
                if (result && result.then && typeof result.then === "function") {
                    result.then(function (asyncResult) {
                        _this._success(rpcMessage, asyncResult, rpcMessage.handshakeToken);
                    }, function (e) {
                        _this.error(rpcMessage, e);
                    });
                }
                else {
                    this._success(rpcMessage, result, rpcMessage.handshakeToken);
                }
            }
            catch (exception) {
                // send back as error if an exception is thrown
                this.error(rpcMessage, exception);
            }
        };
        XDMChannel.prototype.getRegisteredObject = function (instanceId, instanceContext) {
            if (instanceId === "__proxyFunctions") {
                // Special case for proxied functions of remote instances
                return this.proxyFunctions;
            }
            // Look in the channel registry first
            var registeredObject = this.registry.getInstance(instanceId, instanceContext);
            if (!registeredObject) {
                // Look in the global registry as a fallback
                registeredObject = globalObjectRegistry.getInstance(instanceId, instanceContext);
            }
            return registeredObject;
        };
        /**
        * Handle a received message on this channel. Dispatch to the appropriate object found via object registry
        *
        * @param rpcMessage - Message data
        * @return True if the message was handled by this channel. Otherwise false.
        */
        XDMChannel.prototype.onMessage = function (rpcMessage) {
            var _this = this;
            if (rpcMessage.instanceId) {
                // Find the object that handles this requestNeed to find implementation
                // Look in the channel registry first
                var registeredObject = this.getRegisteredObject(rpcMessage.instanceId, rpcMessage.instanceContext);
                if (!registeredObject) {
                    // If not found return false to indicate that the message was not handled
                    return false;
                }
                if (typeof registeredObject["then"] === "function") {
                    registeredObject.then(function (resolvedInstance) {
                        _this.invokeMethod(resolvedInstance, rpcMessage);
                    }, function (e) {
                        _this.error(rpcMessage, e);
                    });
                }
                else {
                    this.invokeMethod(registeredObject, rpcMessage);
                }
            }
            else {
                var promise = this.promises[rpcMessage.id];
                if (!promise) {
                    // Message not handled by this channel.
                    return false;
                }
                if (rpcMessage.error) {
                    promise.reject(this._customDeserializeObject([rpcMessage.error], {})[0]);
                }
                else {
                    promise.resolve(this._customDeserializeObject([rpcMessage.result], {})[0]);
                }
                delete this.promises[rpcMessage.id];
            }
            // Message handled by this channel
            return true;
        };
        XDMChannel.prototype.owns = function (source, origin, rpcMessage) {
            /// Determines whether the current message belongs to this channel or not
            if (this.postToWindow === source) {
                // For messages coming from sandboxed iframes the origin will be set to the string "null".  This is 
                // how onprem works.  If it is not a sandboxed iFrame we will get the origin as expected.
                if (this.targetOrigin) {
                    if (origin) {
                        return origin.toLowerCase() === "null" || this.targetOrigin.toLowerCase().indexOf(origin.toLowerCase()) === 0;
                    }
                    else {
                        return false;
                    }
                }
                else {
                    if (rpcMessage.handshakeToken && rpcMessage.handshakeToken === this.handshakeToken) {
                        this.targetOrigin = origin;
                        return true;
                    }
                }
            }
            return false;
        };
        XDMChannel.prototype.error = function (messageObj, errorObj) {
            this._sendRpcMessage({
                id: messageObj.id,
                error: this._customSerializeObject([errorObj], messageObj.serializationSettings)[0],
                handshakeToken: messageObj.handshakeToken
            });
        };
        XDMChannel.prototype._success = function (messageObj, result, handshakeToken) {
            this._sendRpcMessage({
                id: messageObj.id,
                result: this._customSerializeObject([result], messageObj.serializationSettings)[0],
                handshakeToken: handshakeToken
            });
        };
        XDMChannel.prototype._sendRpcMessage = function (message) {
            this.postToWindow.postMessage(JSON.stringify(message), "*");
        };
        XDMChannel.prototype._customSerializeObject = function (obj, settings, prevParentObjects, nextCircularRefId, depth) {
            var _this = this;
            if (nextCircularRefId === void 0) { nextCircularRefId = 1; }
            if (depth === void 0) { depth = 1; }
            if (!obj || depth > MAX_XDM_DEPTH) {
                return undefined;
            }
            if (obj instanceof Node || obj instanceof Window || obj instanceof Event) {
                return undefined;
            }
            var returnValue;
            var parentObjects;
            if (!prevParentObjects) {
                parentObjects = {
                    newObjects: [],
                    originalObjects: []
                };
            }
            else {
                parentObjects = prevParentObjects;
            }
            parentObjects.originalObjects.push(obj);
            var serializeMember = function (parentObject, newObject, key) {
                var item;
                try {
                    item = parentObject[key];
                }
                catch (ex) {
                    // Cannot access this property. Skip its serialization.
                }
                var itemType = typeof item;
                if (itemType === "undefined") {
                    return;
                }
                // Check for a circular reference by looking at parent objects
                var parentItemIndex = -1;
                if (itemType === "object") {
                    parentItemIndex = parentObjects.originalObjects.indexOf(item);
                }
                if (parentItemIndex >= 0) {
                    // Circular reference found. Add reference to parent
                    var parentItem = parentObjects.newObjects[parentItemIndex];
                    if (!parentItem.__circularReferenceId) {
                        parentItem.__circularReferenceId = nextCircularRefId++;
                    }
                    newObject[key] = {
                        __circularReference: parentItem.__circularReferenceId
                    };
                }
                else {
                    if (itemType === "function") {
                        _this.nextProxyId++;
                        newObject[key] = {
                            __proxyFunctionId: _this._registerProxyFunction(item, obj),
                            _channelId: _this.channelId
                        };
                    }
                    else if (itemType === "object") {
                        if (item && item instanceof Date) {
                            newObject[key] = {
                                __proxyDate: item.getTime()
                            };
                        }
                        else {
                            newObject[key] = _this._customSerializeObject(item, settings, parentObjects, nextCircularRefId, depth + 1);
                        }
                    }
                    else if (key !== "__proxyFunctionId") {
                        // Just add non object/function properties as-is. Don't include "__proxyFunctionId" to protect
                        // our proxy methods from being invoked from other messages.
                        newObject[key] = item;
                    }
                }
            };
            if (obj instanceof Array) {
                returnValue = [];
                parentObjects.newObjects.push(returnValue);
                for (var i = 0, l = obj.length; i < l; i++) {
                    serializeMember(obj, returnValue, i);
                }
            }
            else {
                returnValue = {};
                parentObjects.newObjects.push(returnValue);
                var keys = {};
                try {
                    keys = getAllPropertyNames(obj);
                }
                catch (ex) {
                    // We may not be able to access the iterator of this object. Skip its serialization.
                }
                for (var key in keys) {
                    // Don't serialize properties that start with an underscore.
                    if ((key && key[0] !== "_") || (settings && settings.includeUnderscoreProperties)) {
                        serializeMember(obj, returnValue, key);
                    }
                }
            }
            parentObjects.originalObjects.pop();
            parentObjects.newObjects.pop();
            return returnValue;
        };
        XDMChannel.prototype._registerProxyFunction = function (func, context) {
            var proxyFunctionId = this.nextProxyId++;
            this.proxyFunctions["proxy" + proxyFunctionId] = function () {
                return func.apply(context, Array.prototype.slice.call(arguments, 0));
            };
            return proxyFunctionId;
        };
        XDMChannel.prototype._customDeserializeObject = function (obj, circularRefs) {
            var _this = this;
            var that = this;
            if (!obj) {
                return null;
            }
            var deserializeMember = function (parentObject, key) {
                var item = parentObject[key];
                var itemType = typeof item;
                if (key === "__circularReferenceId" && itemType === 'number') {
                    circularRefs[item] = parentObject;
                    delete parentObject[key];
                }
                else if (itemType === "object" && item) {
                    if (item.__proxyFunctionId) {
                        parentObject[key] = function () {
                            return that.invokeRemoteMethod("proxy" + item.__proxyFunctionId, "__proxyFunctions", Array.prototype.slice.call(arguments, 0), {}, { includeUnderscoreProperties: true });
                        };
                    }
                    else if (item.__proxyDate) {
                        parentObject[key] = new Date(item.__proxyDate);
                    }
                    else if (item.__circularReference) {
                        parentObject[key] = circularRefs[item.__circularReference];
                    }
                    else {
                        _this._customDeserializeObject(item, circularRefs);
                    }
                }
            };
            if (obj instanceof Array) {
                for (var i = 0, l = obj.length; i < l; i++) {
                    deserializeMember(obj, i);
                }
            }
            else if (typeof obj === "object") {
                for (var key in obj) {
                    deserializeMember(obj, key);
                }
            }
            return obj;
        };
        return XDMChannel;
    }());
    /**
    * Registry of XDM channels kept per target frame/window
    */
    var XDMChannelManager = /** @class */ (function () {
        function XDMChannelManager() {
            var _this = this;
            this._channels = [];
            this._handleMessageReceived = function (event) {
                // get channel and dispatch to it
                var rpcMessage;
                if (typeof event.data === "string") {
                    try {
                        rpcMessage = JSON.parse(event.data);
                    }
                    catch (error) {
                        // The message is not a valid JSON string. Not one of our events.
                    }
                }
                if (rpcMessage) {
                    var handled = false;
                    var channelOwner = void 0;
                    for (var _i = 0, _a = _this._channels; _i < _a.length; _i++) {
                        var channel = _a[_i];
                        if (channel.owns(event.source, event.origin, rpcMessage)) {
                            // keep a reference to the channel owner found. 
                            channelOwner = channel;
                            handled = channel.onMessage(rpcMessage) || handled;
                        }
                    }
                    if (channelOwner && !handled) {
                        if (window.console) {
                            console.error("No handler found on any channel for message: ".concat(JSON.stringify(rpcMessage)));
                        }
                        // for instance based proxies, send an error on the channel owning the message to resolve any control creation promises
                        // on the host frame. 
                        if (rpcMessage.instanceId) {
                            channelOwner.error(rpcMessage, new Error("The registered object ".concat(rpcMessage.instanceId, " could not be found.")));
                        }
                    }
                }
            };
            window.addEventListener("message", this._handleMessageReceived);
        }
        /**
        * Add an XDM channel for the given target window/iframe
        *
        * @param window - Target iframe window to communicate with
        * @param targetOrigin - Url of the target iframe (if known)
        */
        XDMChannelManager.prototype.addChannel = function (window, targetOrigin) {
            var channel = new XDMChannel(window, targetOrigin);
            this._channels.push(channel);
            return channel;
        };
        XDMChannelManager.prototype.removeChannel = function (channel) {
            this._channels = this._channels.filter(function (c) { return c !== channel; });
        };
        return XDMChannelManager;
    }());
    /**
    * The registry of global XDM handlers
    */
    var globalObjectRegistry = new XDMObjectRegistry();
    /**
    * Manages XDM channels per target window/frame
    */
    var channelManager = new XDMChannelManager();

    exports.XDMChannel = XDMChannel;
    exports.XDMObjectRegistry = XDMObjectRegistry;
    exports.channelManager = channelManager;
    exports.globalObjectRegistry = globalObjectRegistry;

}));
//# sourceMappingURL=XDM.js.map
