UNPKG

azure-devops-extension-sdk

Version:
507 lines (503 loc) 22.8 kB
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