@mml-io/networked-dom-web-runner 0.0.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/index.d.ts +1 -0
- package/build/index.js +246 -0
- package/build/index.js.map +7 -0
- package/package.json +31 -0
- package/src/FakeWebsocket.ts +56 -0
- package/src/IframeObservableDOMFactory.ts +53 -0
- package/src/NetworkedDOMWebRunnerClient.ts +82 -0
- package/src/RunnerIframe.ts +81 -0
- package/src/index.ts +6 -0
- package/src/message-types.ts +30 -0
package/build/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../src/index";
|
package/build/index.js
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
FakeWebsocket: () => FakeWebsocket,
|
|
24
|
+
IframeObservableDOMFactory: () => IframeObservableDOMFactory,
|
|
25
|
+
NetworkedDOMWebRunnerClient: () => NetworkedDOMWebRunnerClient,
|
|
26
|
+
RunnerIframe: () => RunnerIframe
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(src_exports);
|
|
29
|
+
__reExport(src_exports, require("@mml-io/networked-dom-document"), module.exports);
|
|
30
|
+
|
|
31
|
+
// runner-iframe-js-text-namespace:/Users/marcuslongmuir/workspace/mml/packages/networked-dom-web-runner/networked-dom-web-runner-iframe/build/index.js
|
|
32
|
+
var build_default = 'var __defProp = Object.defineProperty;\nvar __defProps = Object.defineProperties;\nvar __getOwnPropDescs = Object.getOwnPropertyDescriptors;\nvar __getOwnPropSymbols = Object.getOwnPropertySymbols;\nvar __hasOwnProp = Object.prototype.hasOwnProperty;\nvar __propIsEnum = Object.prototype.propertyIsEnumerable;\nvar __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;\nvar __spreadValues = (a, b) => {\n for (var prop in b ||= {})\n if (__hasOwnProp.call(b, prop))\n __defNormalProp(a, prop, b[prop]);\n if (__getOwnPropSymbols)\n for (var prop of __getOwnPropSymbols(b)) {\n if (__propIsEnum.call(b, prop))\n __defNormalProp(a, prop, b[prop]);\n }\n return a;\n};\nvar __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));\n\n// ../../observable-dom/src/utils.ts\nfunction virtualDomElementToStatic(el) {\n return {\n nodeId: el.nodeId,\n tag: el.tag,\n attributes: el.attributes,\n childNodes: el.childNodes.map((child) => virtualDomElementToStatic(child)),\n textContent: el.textContent\n };\n}\n\n// ../../observable-dom/src/ObservableDom.ts\nvar ObservableDom = class {\n constructor(observableDOMParameters, callback, runnerFactory) {\n this.nodeToNodeId = /* @__PURE__ */ new Map();\n this.nodeIdToNode = /* @__PURE__ */ new Map();\n this.realElementToVirtualElement = /* @__PURE__ */ new Map();\n this.ignoreTextNodes = true;\n this.nextNodeId = 1;\n this.htmlPath = observableDOMParameters.htmlPath;\n this.ignoreTextNodes = observableDOMParameters.ignoreTextNodes;\n this.callback = callback;\n this.documentTimeIntervalTimer = setInterval(() => {\n this.callback({\n documentTime: this.getDocumentTime()\n });\n }, observableDOMParameters.pingIntervalMilliseconds || 5e3);\n this.domRunner = runnerFactory(\n observableDOMParameters.htmlPath,\n observableDOMParameters.htmlContents,\n observableDOMParameters.params,\n (domRunnerMessage) => {\n if (domRunnerMessage.loaded) {\n this.createVirtualDomElementWithChildren(\n this.domRunner.getDocument(),\n null\n );\n const snapshot = virtualDomElementToStatic(\n this.getVirtualDomElementForRealElementOrThrow(\n this.domRunner.getDocument()\n )\n );\n this.callback({\n snapshot,\n documentTime: this.getDocumentTime()\n });\n } else if (domRunnerMessage.mutationList) {\n this.processModificationList(domRunnerMessage.mutationList);\n } else if (domRunnerMessage.logMessage) {\n this.callback({\n logMessage: domRunnerMessage.logMessage,\n documentTime: this.getDocumentTime()\n });\n }\n }\n );\n }\n addIPCWebsocket(webSocket) {\n return this.domRunner.addIPCWebsocket(webSocket);\n }\n addConnectedUserId(connectionId) {\n this.domRunner.getWindow().dispatchEvent(\n new (this.domRunner.getWindow()).CustomEvent("connected", {\n detail: { connectionId }\n })\n );\n }\n removeConnectedUserId(connectionId) {\n this.domRunner.getWindow().dispatchEvent(\n new (this.domRunner.getWindow()).CustomEvent("disconnected", {\n detail: { connectionId }\n })\n );\n }\n processModificationList(mutationList) {\n const documentEl = this.domRunner.getDocument();\n const documentVirtualDomElement = this.realElementToVirtualElement.get(documentEl);\n if (!documentVirtualDomElement) {\n throw new Error(`document not created in processModificationList`);\n }\n if (mutationList.length > 1) {\n console.error(\n "More than one mutation record received. It is possible that intermediate states are incorrect."\n );\n }\n for (const mutation of mutationList) {\n if (this.isIgnoredElement(mutation.target)) {\n continue;\n }\n if (mutation.type === "attributes" && // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n this.isIgnoredAttribute(mutation.target, mutation.attributeName)) {\n continue;\n }\n this.addKnownNodesInMutation(mutation);\n const firstNonIgnoredPreviousSibling = mutation.previousSibling ? this.getFirstNonIgnoredPreviousSibling(mutation.previousSibling) : null;\n const targetElement = this.getVirtualDomElementForRealElementOrThrow(\n mutation.target\n );\n const addedNodes = [];\n for (const node of mutation.addedNodes) {\n if (this.isIgnoredElement(node)) {\n continue;\n }\n const virtualDomElement = this.getVirtualDomElementForRealElementOrThrow(\n node\n );\n addedNodes.push(virtualDomElementToStatic(virtualDomElement));\n }\n const removedNodeIds = [];\n for (const node of mutation.removedNodes) {\n if (this.isIgnoredElement(node)) {\n continue;\n }\n const virtualDomElement = this.getVirtualDomElementForRealElementOrThrow(\n node\n );\n removedNodeIds.push(virtualDomElement.nodeId);\n }\n const mutationRecord = {\n type: mutation.type,\n targetId: targetElement.nodeId,\n addedNodes,\n removedNodeIds,\n previousSiblingId: firstNonIgnoredPreviousSibling !== null ? this.getVirtualDomElementForRealElementOrThrow(firstNonIgnoredPreviousSibling).nodeId : null,\n attribute: mutation.attributeName ? {\n attributeName: mutation.attributeName,\n value: mutation.target.getAttribute(mutation.attributeName)\n } : null\n };\n this.callback({\n mutation: mutationRecord,\n documentTime: this.getDocumentTime()\n });\n this.removeKnownNodesInMutation(mutation);\n }\n }\n addKnownNodesInMutation(mutation) {\n const targetNode = mutation.target;\n const virtualDomElement = this.realElementToVirtualElement.get(targetNode);\n if (!virtualDomElement) {\n throw new Error(\n "Unknown node in addKnownNodesInMutation:" + targetNode + "," + mutation.type\n );\n }\n if (mutation.type === "childList") {\n let previousSibling = mutation.previousSibling;\n let index = 0;\n while (previousSibling && this.isIgnoredElement(previousSibling)) {\n previousSibling = previousSibling.previousSibling;\n }\n if (previousSibling) {\n const previousSiblingElement = this.realElementToVirtualElement.get(\n previousSibling\n );\n if (!previousSiblingElement) {\n throw new Error("Unknown previous sibling");\n }\n index = virtualDomElement.childNodes.indexOf(previousSiblingElement);\n if (index === -1) {\n throw new Error("Previous sibling is not currently a child of the parent element");\n }\n index += 1;\n }\n mutation.addedNodes.forEach((node) => {\n const asElementOrText = node;\n const childVirtualDomElement = this.createVirtualDomElementWithChildren(\n asElementOrText,\n virtualDomElement\n );\n if (childVirtualDomElement) {\n if (virtualDomElement.childNodes.indexOf(childVirtualDomElement) === -1) {\n virtualDomElement.childNodes.splice(index, 0, childVirtualDomElement);\n index++;\n }\n }\n });\n } else if (mutation.type === "attributes") {\n const attributeName = mutation.attributeName;\n if (this.isIgnoredAttribute(targetNode, attributeName)) {\n return;\n }\n const attributeValue = targetNode.getAttribute(attributeName);\n if (attributeValue === null) {\n delete virtualDomElement.attributes[attributeName];\n } else {\n virtualDomElement.attributes[attributeName] = attributeValue;\n }\n } else if (mutation.type === "characterData") {\n virtualDomElement.textContent = targetNode.textContent ? targetNode.textContent : void 0;\n }\n }\n removeKnownNodesInMutation(mutation) {\n const targetNode = mutation.target;\n const virtualDomElement = this.realElementToVirtualElement.get(targetNode);\n if (!virtualDomElement) {\n throw new Error("Unknown node in mutation list:" + targetNode + ", " + mutation.type);\n }\n if (mutation.type === "childList") {\n for (const node of mutation.removedNodes) {\n const asElementOrText = node;\n if (this.isIgnoredElement(asElementOrText)) {\n continue;\n }\n const childDomElement = this.realElementToVirtualElement.get(asElementOrText);\n if (!childDomElement) {\n console.warn(this.htmlPath, "Unknown node in removeKnownNodesInMutation");\n continue;\n } else {\n this.removeVirtualDomElement(childDomElement);\n const index = virtualDomElement.childNodes.indexOf(childDomElement);\n virtualDomElement.childNodes.splice(index, 1);\n }\n }\n return;\n }\n }\n removeVirtualDomElement(virtualDomElement) {\n this.nodeIdToNode.delete(virtualDomElement.nodeId);\n this.nodeToNodeId.delete(virtualDomElement);\n this.realElementToVirtualElement.delete(virtualDomElement.realElement);\n for (const child of virtualDomElement.childNodes) {\n this.removeVirtualDomElement(child);\n }\n }\n createVirtualDomElementWithChildren(node, parent) {\n const virtualElement = this.createVirtualDomElement(node, parent);\n if (!virtualElement) {\n return null;\n }\n if (node.childNodes) {\n for (let i = 0; i < node.childNodes.length; i++) {\n const child = node.childNodes[i];\n const childVirtualElement = this.createVirtualDomElementWithChildren(\n child,\n virtualElement\n );\n if (childVirtualElement) {\n virtualElement.childNodes.push(childVirtualElement);\n }\n }\n }\n return virtualElement;\n }\n createVirtualDomElement(node, parent) {\n if (this.isIgnoredElement(node)) {\n return null;\n }\n const existingValue = this.realElementToVirtualElement.get(node);\n if (existingValue !== void 0) {\n throw new Error("Node already has a virtual element: " + node.nodeName);\n }\n if (!node) {\n throw new Error("Cannot assign node id to null");\n }\n const attributes = {};\n if (node.attributes) {\n const asHTMLElement = node;\n for (const key of asHTMLElement.getAttributeNames()) {\n const value = asHTMLElement.getAttribute(key);\n if (value === null) {\n throw new Error("Null attribute value for key: " + key);\n }\n if (!this.isIgnoredAttribute(node, key)) {\n attributes[key] = value;\n }\n }\n }\n const nodeId = this.nextNodeId++;\n const virtualElement = {\n nodeId,\n tag: node.nodeName,\n attributes,\n childNodes: [],\n realElement: node,\n parent\n };\n if (node instanceof this.domRunner.getWindow().Text && node.textContent) {\n virtualElement.textContent = node.textContent;\n }\n this.nodeToNodeId.set(virtualElement, nodeId);\n this.nodeIdToNode.set(nodeId, virtualElement);\n this.realElementToVirtualElement.set(node, virtualElement);\n return virtualElement;\n }\n getFirstNonIgnoredPreviousSibling(node) {\n let currentNode = node;\n if (!this.isIgnoredElement(currentNode)) {\n return currentNode;\n }\n while (currentNode && currentNode.previousSibling) {\n currentNode = currentNode.previousSibling;\n if (!this.isIgnoredElement(currentNode)) {\n return currentNode;\n }\n }\n return null;\n }\n getVirtualDomElementForRealElementOrThrow(realElement) {\n const virtualElement = this.realElementToVirtualElement.get(realElement);\n if (!virtualElement) {\n throw new Error(`Virtual element not found for real element`);\n }\n return virtualElement;\n }\n isIgnoredElement(node) {\n if (this.ignoreTextNodes && node instanceof this.domRunner.getWindow().Text) {\n return true;\n } else if (node instanceof this.domRunner.getWindow().HTMLScriptElement) {\n return true;\n } else if (node instanceof this.domRunner.getWindow().Comment) {\n return true;\n }\n return false;\n }\n isIgnoredAttribute(node, attributeName) {\n return attributeName.startsWith("on");\n }\n dispatchRemoteEventFromConnectionId(connectionId, remoteEvent) {\n const domNode = this.nodeIdToNode.get(remoteEvent.nodeId);\n if (!domNode) {\n console.error("Unknown node ID in remote event: " + remoteEvent.nodeId);\n return;\n }\n if (domNode instanceof this.domRunner.getWindow().Text) {\n console.warn("Cannot dispatch remote event to text node");\n return;\n }\n this.domRunner.dispatchRemoteEventFromConnectionId(\n connectionId,\n domNode.realElement,\n remoteEvent\n );\n }\n dispose() {\n clearInterval(this.documentTimeIntervalTimer);\n this.domRunner.dispose();\n }\n getDocumentTime() {\n return this.domRunner.getDocumentTime();\n }\n};\n\n// src/WebBrowserDOMRunner.ts\nvar WebBrowserDOMRunnerFactory = (htmlPath, htmlContents, params, callback) => {\n return new WebBrowserDOMRunner(htmlPath, htmlContents, params, callback);\n};\nvar documentLoadTime = Date.now();\nvar WebBrowserDOMRunner = class {\n constructor(htmlPath, htmlContents, params, callback) {\n this.htmlPath = htmlPath;\n this.callback = callback;\n let didSendLoad = false;\n this.mutationObserver = new window.MutationObserver((mutationList) => {\n if (!document) {\n return;\n }\n if (!didSendLoad) {\n throw new Error("MutationObserver called before load");\n }\n this.callback({\n mutationList\n });\n });\n window.params = params;\n const finishLoad = () => {\n if (didSendLoad) {\n throw new Error("finishLoad called twice");\n }\n didSendLoad = true;\n this.callback({\n loaded: true\n });\n this.mutationObserver.observe(window.document, {\n attributes: true,\n childList: true,\n subtree: true,\n characterData: true\n });\n };\n if (document.body) {\n setTimeout(finishLoad, 0);\n } else {\n window.addEventListener("DOMContentLoaded", finishLoad);\n }\n }\n addIPCWebsocket() {\n throw new Error("Not implemented.");\n }\n dispatchRemoteEventFromConnectionId(connectionId, realElement, remoteEvent) {\n const bubbles = remoteEvent.bubbles || false;\n const remoteEventObject = new CustomEvent(remoteEvent.name, {\n bubbles,\n detail: __spreadProps(__spreadValues({}, remoteEvent.params), { connectionId })\n });\n realElement.dispatchEvent(remoteEventObject);\n }\n dispose() {\n console.log("WebBrowserDOMRunner.dispose");\n }\n getDocument() {\n return document;\n }\n getDocumentTime() {\n if (document.timeline && document.timeline.currentTime) {\n return document.timeline.currentTime;\n }\n return Date.now() - documentLoadTime;\n }\n // TODO - resolve types (Window needs to expose classes such as CustomEvent as properties)\n getWindow() {\n return window;\n }\n};\n\n// src/IframeWebRunner.ts\nfunction setupIframeWebRunner(argsString) {\n const observableDOMParams = JSON.parse(atob(argsString));\n const sendMessageToHandler = (message) => {\n window.parent.postMessage(JSON.stringify(message), "*");\n };\n const observableDOM = new ObservableDom(\n __spreadProps(__spreadValues({}, observableDOMParams), {\n htmlContents: ""\n // This must be empty as the contents are assumed to be provided by the srcdoc\n }),\n (observableDomMessage) => {\n sendMessageToHandler({\n type: "dom",\n message: observableDomMessage\n });\n },\n WebBrowserDOMRunnerFactory\n );\n window.addEventListener("message", (e) => {\n const parsed = JSON.parse(e.data);\n if (parsed.type === "dispatchRemoteEventFromConnectionId") {\n observableDOM.dispatchRemoteEventFromConnectionId(parsed.connectionId, parsed.event);\n }\n });\n}\n\n// src/index.ts\nvar args = window.args;\nsetupIframeWebRunner(args);\n//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vb2JzZXJ2YWJsZS1kb20vc3JjL3V0aWxzLnRzIiwgIi4uLy4uLy4uL29ic2VydmFibGUtZG9tL3NyYy9PYnNlcnZhYmxlRG9tLnRzIiwgIi4uL3NyYy9XZWJCcm93c2VyRE9NUnVubmVyLnRzIiwgIi4uL3NyYy9JZnJhbWVXZWJSdW5uZXIudHMiLCAiLi4vc3JjL2luZGV4LnRzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyJpbXBvcnQgeyBTdGF0aWNWaXJ0dWFsRG9tRWxlbWVudCB9IGZyb20gXCJAbW1sLWlvL29ic2VydmFibGUtZG9tLWNvbW1vblwiO1xuXG5pbXBvcnQgeyBMaXZlVmlydHVhbERvbUVsZW1lbnQgfSBmcm9tIFwiLi9PYnNlcnZhYmxlRG9tXCI7XG5cbmV4cG9ydCBmdW5jdGlvbiB2aXJ0dWFsRG9tRWxlbWVudFRvU3RhdGljKGVsOiBMaXZlVmlydHVhbERvbUVsZW1lbnQpOiBTdGF0aWNWaXJ0dWFsRG9tRWxlbWVudCB7XG4gIHJldHVybiB7XG4gICAgbm9kZUlkOiBlbC5ub2RlSWQsXG4gICAgdGFnOiBlbC50YWcsXG4gICAgYXR0cmlidXRlczogZWwuYXR0cmlidXRlcyxcbiAgICBjaGlsZE5vZGVzOiBlbC5jaGlsZE5vZGVzLm1hcCgoY2hpbGQpID0+IHZpcnR1YWxEb21FbGVtZW50VG9TdGF0aWMoY2hpbGQpKSxcbiAgICB0ZXh0Q29udGVudDogZWwudGV4dENvbnRlbnQsXG4gIH07XG59XG4iLCAiaW1wb3J0IHtcbiAgTG9nTWVzc2FnZSxcbiAgT2JzZXJ2YWJsZURvbUludGVyZmFjZSxcbiAgT2JzZXJ2YWJsZURvbU1lc3NhZ2UsXG4gIE9ic2VydmFibGVET01QYXJhbWV0ZXJzLFxuICBSZW1vdGVFdmVudCxcbiAgU3RhdGljVmlydHVhbERvbUVsZW1lbnQsXG4gIFN0YXRpY1ZpcnR1YWxEb21NdXRhdGlvbklkc1JlY29yZCxcbn0gZnJvbSBcIkBtbWwtaW8vb2JzZXJ2YWJsZS1kb20tY29tbW9uXCI7XG5cbmltcG9ydCB7IHZpcnR1YWxEb21FbGVtZW50VG9TdGF0aWMgfSBmcm9tIFwiLi91dGlsc1wiO1xuXG5leHBvcnQgdHlwZSBET01SdW5uZXJNZXNzYWdlID0ge1xuICBsb2FkZWQ/OiBib29sZWFuO1xuICBtdXRhdGlvbkxpc3Q/OiBBcnJheTxNdXRhdGlvblJlY29yZD47XG4gIGxvZ01lc3NhZ2U/OiBMb2dNZXNzYWdlO1xufTtcblxuZXhwb3J0IHR5cGUgRE9NUnVubmVySW50ZXJmYWNlID0ge1xuICBnZXREb2N1bWVudCgpOiBEb2N1bWVudDtcbiAgZ2V0V2luZG93KCk6IFdpbmRvdyAmIHtcbiAgICBDdXN0b21FdmVudDogdHlwZW9mIEN1c3RvbUV2ZW50O1xuICAgIFRleHQ6IHR5cGVvZiBUZXh0O1xuICAgIEhUTUxTY3JpcHRFbGVtZW50OiB0eXBlb2YgSFRNTFNjcmlwdEVsZW1lbnQ7XG4gICAgQ29tbWVudDogdHlwZW9mIENvbW1lbnQ7XG4gIH07IC8vIFRPRE8gLSBEZWZpbmUgdGhpcyB3aXRob3V0IHVzaW5nIEpTRE9NIHR5cGVzXG4gIGFkZElQQ1dlYnNvY2tldCh3ZWJTb2NrZXQ6IFdlYlNvY2tldCk6IHZvaWQ7XG4gIGRpc3BhdGNoUmVtb3RlRXZlbnRGcm9tQ29ubmVjdGlvbklkKFxuICAgIGNvbm5lY3Rpb25JZDogbnVtYmVyLFxuICAgIHJlYWxFbGVtZW50OiBFbGVtZW50LFxuICAgIHJlbW90ZUV2ZW50OiBSZW1vdGVFdmVudCxcbiAgKTogdm9pZDtcbiAgZGlzcG9zZSgpOiB2b2lkO1xuICBnZXREb2N1bWVudFRpbWUoKTogbnVtYmVyO1xufTtcblxuZXhwb3J0IHR5cGUgRE9NUnVubmVyRmFjdG9yeSA9IChcbiAgaHRtbFBhdGg6IHN0cmluZyxcbiAgaHRtbENvbnRlbnRzOiBzdHJpbmcsXG4gIHBhcmFtczogb2JqZWN0LFxuICBjYWxsYmFjazogKGRvbVJ1bm5lck1lc3NhZ2U6IERPTVJ1bm5lck1lc3NhZ2UpID0+IHZvaWQsXG4pID0+IERPTVJ1bm5lckludGVyZmFjZTtcblxuZXhwb3J0IHR5cGUgTGl2ZVZpcnR1YWxEb21FbGVtZW50ID0gT21pdDxTdGF0aWNWaXJ0dWFsRG9tRWxlbWVudCwgXCJjaGlsZE5vZGVzXCI+ICYge1xuICByZWFsRWxlbWVudDogRWxlbWVudCB8IFRleHQ7XG4gIGNoaWxkTm9kZXM6IEFycmF5PExpdmVWaXJ0dWFsRG9tRWxlbWVudD47XG4gIHBhcmVudDogTGl2ZVZpcnR1YWxEb21FbGVtZW50IHwgbnVsbDtcbn07XG5cbmV4cG9ydCBjbGFzcyBPYnNlcnZhYmxlRG9tIGltcGxlbWVudHMgT2JzZXJ2YWJsZURvbUludGVyZmFjZSB7XG4gIHByaXZhdGUgbm9kZVRvTm9kZUlkID0gbmV3IE1hcDxMaXZlVmlydHVhbERvbUVsZW1lbnQsIG51bWJlcj4oKTtcbiAgcHJpdmF0ZSBub2RlSWRUb05vZGUgPSBuZXcgTWFwPG51bWJlciwgTGl2ZVZpcnR1YWxEb21FbGVtZW50PigpO1xuICBwcml2YXRlIHJlYWxFbGVtZW50VG9WaXJ0dWFsRWxlbWVudCA9IG5ldyBNYXA8RWxlbWVudCB8IFRleHQsIExpdmVWaXJ0dWFsRG9tRWxlbWVudD4oKTtcbiAgcHJpdmF0ZSBpZ25vcmVUZXh0Tm9kZXMgPSB0cnVlO1xuICBwcml2YXRlIGNhbGxiYWNrOiAobWVzc2FnZTogT2JzZXJ2YWJsZURvbU1lc3NhZ2UpID0+IHZvaWQ7XG4gIHByaXZhdGUgbmV4dE5vZGVJZCA9IDE7XG4gIHByaXZhdGUgaHRtbFBhdGg6IHN0cmluZztcbiAgcHJpdmF0ZSBkb21SdW5uZXI6IERPTVJ1bm5lckludGVyZmFjZTtcblxuICBwcml2YXRlIGRvY3VtZW50VGltZUludGVydmFsVGltZXI6IE5vZGVKUy5UaW1lcjtcblxuICBjb25zdHJ1Y3RvcihcbiAgICBvYnNlcnZhYmxlRE9NUGFyYW1ldGVyczogT2JzZXJ2YWJsZURPTVBhcmFtZXRlcnMsXG4gICAgY2FsbGJhY2s6IChtZXNzYWdlOiBPYnNlcnZhYmxlRG9tTWVzc2FnZSkgPT4gdm9pZCxcbiAgICBydW5uZXJGYWN0b3J5OiBET01SdW5uZXJGYWN0b3J5LFxuICApIHtcbiAgICB0aGlzLmh0bWxQYXRoID0gb2JzZXJ2YWJsZURPTVBhcmFtZXRlcnMuaHRtbFBhdGg7XG4gICAgdGhpcy5pZ25vcmVUZXh0Tm9kZXMgPSBvYnNlcnZhYmxlRE9NUGFyYW1ldGVycy5pZ25vcmVUZXh0Tm9kZXM7XG4gICAgdGhpcy5jYWxsYmFjayA9IGNhbGxiYWNrO1xuXG4gICAgdGhpcy5kb2N1bWVudFRpbWVJbnRlcnZhbFRpbWVyID0gc2V0SW50ZXJ2YWwoKCkgPT4ge1xuICAgICAgdGhpcy5jYWxsYmFjayh7XG4gICAgICAgIGRvY3VtZW50VGltZTogdGhpcy5nZXREb2N1bWVudFRpbWUoKSxcbiAgICAgIH0pO1xuICAgIH0sIG9ic2VydmFibGVET01QYXJhbWV0ZXJzLnBpbmdJbnRlcnZhbE1pbGxpc2Vjb25kcyB8fCA1MDAwKTtcblxuICAgIHRoaXMuZG9tUnVubmVyID0gcnVubmVyRmFjdG9yeShcbiAgICAgIG9ic2VydmFibGVET01QYXJhbWV0ZXJzLmh0bWxQYXRoLFxuICAgICAgb2JzZXJ2YWJsZURPTVBhcmFtZXRlcnMuaHRtbENvbnRlbnRzLFxuICAgICAgb2JzZXJ2YWJsZURPTVBhcmFtZXRlcnMucGFyYW1zLFxuICAgICAgKGRvbVJ1bm5lck1lc3NhZ2U6IERPTVJ1bm5lck1lc3NhZ2UpID0+IHtcbiAgICAgICAgaWYgKGRvbVJ1bm5lck1lc3NhZ2UubG9hZGVkKSB7XG4gICAgICAgICAgdGhpcy5jcmVhdGVWaXJ0dWFsRG9tRWxlbWVudFdpdGhDaGlsZHJlbihcbiAgICAgICAgICAgIHRoaXMuZG9tUnVubmVyLmdldERvY3VtZW50KCkgYXMgdW5rbm93biBhcyBFbGVtZW50LFxuICAgICAgICAgICAgbnVsbCxcbiAgICAgICAgICApO1xuXG4gICAgICAgICAgY29uc3Qgc25hcHNob3QgPSB2aXJ0dWFsRG9tRWxlbWVudFRvU3RhdGljKFxuICAgICAgICAgICAgdGhpcy5nZXRWaXJ0dWFsRG9tRWxlbWVudEZvclJlYWxFbGVtZW50T3JUaHJvdyhcbiAgICAgICAgICAgICAgdGhpcy5kb21SdW5uZXIuZ2V0RG9jdW1lbnQoKSBhcyB1bmtub3duIGFzIEVsZW1lbnQsXG4gICAgICAgICAgICApLFxuICAgICAgICAgICk7XG5cbiAgICAgICAgICB0aGlzLmNhbGxiYWNrKHtcbiAgICAgICAgICAgIHNuYXBzaG90LFxuICAgICAgICAgICAgZG9jdW1lbnRUaW1lOiB0aGlzLmdldERvY3VtZW50VGltZSgpLFxuICAgICAgICAgIH0pO1xuICAgICAgICB9IGVsc2UgaWYgKGRvbVJ1bm5lck1lc3NhZ2UubXV0YXRpb25MaXN0KSB7XG4gICAgICAgICAgdGhpcy5wcm9jZXNzTW9kaWZpY2F0aW9uTGlzdChkb21SdW5uZXJNZXNzYWdlLm11dGF0aW9uTGlzdCk7XG4gICAgICAgIH0gZWxzZSBpZiAoZG9tUnVubmVyTWVzc2FnZS5sb2dNZXNzYWdlKSB7XG4gICAgICAgICAgdGhpcy5jYWxsYmFjayh7XG4gICAgICAgICAgICBsb2dNZXNzYWdlOiBkb21SdW5uZXJNZXNzYWdlLmxvZ01lc3NhZ2UsXG4gICAgICAgICAgICBkb2N1bWVudFRpbWU6IHRoaXMuZ2V0RG9jdW1lbnRUaW1lKCksXG4gICAgICAgICAgfSk7XG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgKTtcbiAgfVxuXG4gIHB1YmxpYyBhZGRJUENXZWJzb2NrZXQod2ViU29ja2V0OiBXZWJTb2NrZXQpIHtcbiAgICByZXR1cm4gdGhpcy5kb21SdW5uZXIuYWRkSVBDV2Vic29ja2V0KHdlYlNvY2tldCk7XG4gIH1cblxuICBwdWJsaWMgYWRkQ29ubmVjdGVkVXNlcklkKGNvbm5lY3Rpb25JZDogbnVtYmVyKTogdm9pZCB7XG4gICAgdGhpcy5kb21SdW5uZXIuZ2V0V2luZG93KCkuZGlzcGF0Y2hFdmVudChcbiAgICAgIG5ldyAodGhpcy5kb21SdW5uZXIuZ2V0V2luZG93KCkuQ3VzdG9tRXZlbnQpKFwiY29ubmVjdGVkXCIsIHtcbiAgICAgICAgZGV0YWlsOiB7IGNvbm5lY3Rpb25JZCB9LFxuICAgICAgfSksXG4gICAgKTtcbiAgfVxuXG4gIHB1YmxpYyByZW1vdmVDb25uZWN0ZWRVc2VySWQoY29ubmVjdGlvbklkOiBudW1iZXIpOiB2b2lkIHtcbiAgICB0aGlzLmRvbVJ1bm5lci5nZXRXaW5kb3coKS5kaXNwYXRjaEV2ZW50KFxuICAgICAgbmV3ICh0aGlzLmRvbVJ1bm5lci5nZXRXaW5kb3coKS5DdXN0b21FdmVudCkoXCJkaXNjb25uZWN0ZWRcIiwge1xuICAgICAgICBkZXRhaWw6IHsgY29ubmVjdGlvbklkIH0sXG4gICAgICB9KSxcbiAgICApO1xuICB9XG5cbiAgcHJpdmF0ZSBwcm9jZXNzTW9kaWZpY2F0aW9uTGlzdChtdXRhdGlvbkxpc3Q6IEFycmF5PE11dGF0aW9uUmVjb3JkPik6IHZvaWQge1xuICAgIGNvbnN0IGRvY3VtZW50RWwgPSB0aGlzLmRvbVJ1bm5lci5nZXREb2N1bWVudCgpIGFzIHVua25vd24gYXMgRWxlbWVudDtcbiAgICBjb25zdCBkb2N1bWVudFZpcnR1YWxEb21FbGVtZW50ID0gdGhpcy5yZWFsRWxlbWVudFRvVmlydHVhbEVsZW1lbnQuZ2V0KGRvY3VtZW50RWwpO1xuICAgIGlmICghZG9jdW1lbnRWaXJ0dWFsRG9tRWxlbWVudCkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKGBkb2N1bWVudCBub3QgY3JlYXRlZCBpbiBwcm9jZXNzTW9kaWZpY2F0aW9uTGlzdGApO1xuICAgIH1cblxuICAgIGlmIChtdXRhdGlvbkxpc3QubGVuZ3RoID4gMSkge1xuICAgICAgLy8gVE9ETyAtIHdhbGsgYmFjayB0aHJvdWdoIHRoZSByZWNvcmRzIHRvIGRlcml2ZSB0aGUgaW50ZXJtZWRpYXRlIHN0YXRlcyAoZS5nLiBpZiBhbiBhdHRyaWJ1dGUgaXMgbGF0ZXIgYWRkZWQgdG9cbiAgICAgIC8vICBhbiBlbGVtZW50IGNyZWF0ZWQgaW4gYW4gZWFybGllciByZWNvcmQgdGhlbiBpdCBzaG91bGQgbm90IGhhdmUgdGhhdCBhdHRyaWJ1dGUgd2hlbiB0aGUgZWxlbWVudCBpcyBhZGRlZC5cbiAgICAgIC8vICBUaGlzIGlzIGltcG9ydGFudCBhcyBpbmNvcnJlY3QgYXR0cmlidXRlIHNldHMgY2FuIGFmZmVjdCB2aXNpYmlsaXR5IGFuZCBleHBlY3RlZCBjbGllbnQgcGVyZm9ybWFuY2UuXG4gICAgICBjb25zb2xlLmVycm9yKFxuICAgICAgICBcIk1vcmUgdGhhbiBvbmUgbXV0YXRpb24gcmVjb3JkIHJlY2VpdmVkLiBJdCBpcyBwb3NzaWJsZSB0aGF0IGludGVybWVkaWF0ZSBzdGF0ZXMgYXJlIGluY29ycmVjdC5cIixcbiAgICAgICk7XG4gICAgfVxuXG4gICAgZm9yIChjb25zdCBtdXRhdGlvbiBvZiBtdXRhdGlvbkxpc3QpIHtcbiAgICAgIGlmICh0aGlzLmlzSWdub3JlZEVsZW1lbnQobXV0YXRpb24udGFyZ2V0IGFzIEVsZW1lbnQgfCBUZXh0KSkge1xuICAgICAgICBjb250aW51ZTtcbiAgICAgIH1cblxuICAgICAgaWYgKFxuICAgICAgICBtdXRhdGlvbi50eXBlID09PSBcImF0dHJpYnV0ZXNcIiAmJlxuICAgICAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgQHR5cGVzY3JpcHQtZXNsaW50L25vLW5vbi1udWxsLWFzc2VydGlvblxuICAgICAgICB0aGlzLmlzSWdub3JlZEF0dHJpYnV0ZShtdXRhdGlvbi50YXJnZXQgYXMgRWxlbWVudCB8IFRleHQsIG11dGF0aW9uLmF0dHJpYnV0ZU5hbWUhKVxuICAgICAgKSB7XG4gICAgICAgIGNvbnRpbnVlO1xuICAgICAgfVxuXG4gICAgICB0aGlzLmFkZEtub3duTm9kZXNJbk11dGF0aW9uKG11dGF0aW9uKTtcblxuICAgICAgLy8gQ29udmVydCB0aGUgXCJyZWFsXCIgRE9NIE11dGF0aW9uUmVjb3JkIGludG8gYSBcInZpcnR1YWxcIiBET00gTXV0YXRpb25SZWNvcmQgdGhhdCByZWZlcmVuY2VzIHRoZSBWaXJ0dWFsRE9NRWxlbWVudHNcbiAgICAgIC8vIFRoaXMgaXMgZG9uZSBzbyB0aGF0IHRoZSBzYW1lIHByb2Nlc3MgZm9yIGhhbmRsaW5nIG11dGF0aW9ucyBjYW4gYmUgdXNlZCBmb3IgYm90aCBjaGFuZ2VzIHRvIGEgbGl2ZSBET00gYW5kIGFsc29cbiAgICAgIC8vIHRvIGRpZmZzIGJldHdlZW4gRE9NIHNuYXBzaG90cyB3aGVuIHJlbG9hZGluZ1xuICAgICAgY29uc3QgZmlyc3ROb25JZ25vcmVkUHJldmlvdXNTaWJsaW5nID0gbXV0YXRpb24ucHJldmlvdXNTaWJsaW5nXG4gICAgICAgID8gdGhpcy5nZXRGaXJzdE5vbklnbm9yZWRQcmV2aW91c1NpYmxpbmcobXV0YXRpb24ucHJldmlvdXNTaWJsaW5nIGFzIEVsZW1lbnQgfCBUZXh0KVxuICAgICAgICA6IG51bGw7XG4gICAgICBjb25zdCB0YXJnZXRFbGVtZW50ID0gdGhpcy5nZXRWaXJ0dWFsRG9tRWxlbWVudEZvclJlYWxFbGVtZW50T3JUaHJvdyhcbiAgICAgICAgbXV0YXRpb24udGFyZ2V0IGFzIEVsZW1lbnQgfCBUZXh0LFxuICAgICAgKTtcbiAgICAgIGNvbnN0IGFkZGVkTm9kZXM6IEFycmF5PFN0YXRpY1ZpcnR1YWxEb21FbGVtZW50PiA9IFtdO1xuICAgICAgZm9yIChjb25zdCBub2RlIG9mIG11dGF0aW9uLmFkZGVkTm9kZXMpIHtcbiAgICAgICAgaWYgKHRoaXMuaXNJZ25vcmVkRWxlbWVudChub2RlIGFzIEVsZW1lbnQgfCBUZXh0KSkge1xuICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICB9XG4gICAgICAgIGNvbnN0IHZpcnR1YWxEb21FbGVtZW50ID0gdGhpcy5nZXRWaXJ0dWFsRG9tRWxlbWVudEZvclJlYWxFbGVtZW50T3JUaHJvdyhcbiAgICAgICAgICBub2RlIGFzIEVsZW1lbnQgfCBUZXh0LFxuICAgICAgICApO1xuICAgICAgICBhZGRlZE5vZGVzLnB1c2godmlydHVhbERvbUVsZW1lbnRUb1N0YXRpYyh2aXJ0dWFsRG9tRWxlbWVudCkpO1xuICAgICAgfVxuXG4gICAgICBjb25zdCByZW1vdmVkTm9kZUlkczogQXJyYXk8bnVtYmVyPiA9IFtdO1xuICAgICAgZm9yIChjb25zdCBub2RlIG9mIG11dGF0aW9uLnJlbW92ZWROb2Rlcykge1xuICAgICAgICBpZiAodGhpcy5pc0lnbm9yZWRFbGVtZW50KG5vZGUgYXMgRWxlbWVudCB8IFRleHQpKSB7XG4gICAgICAgICAgY29udGludWU7XG4gICAgICAgIH1cbiAgICAgICAgY29uc3QgdmlydHVhbERvbUVsZW1lbnQgPSB0aGlzLmdldFZpcnR1YWxEb21FbGVtZW50Rm9yUmVhbEVsZW1lbnRPclRocm93KFxuICAgICAgICAgIG5vZGUgYXMgRWxlbWVudCB8IFRleHQsXG4gICAgICAgICk7XG4gICAgICAgIHJlbW92ZWROb2RlSWRzLnB1c2godmlydHVhbERvbUVsZW1lbnQubm9kZUlkKTtcbiAgICAgIH1cblxuICAgICAgY29uc3QgbXV0YXRpb25SZWNvcmQ6IFN0YXRpY1ZpcnR1YWxEb21NdXRhdGlvbklkc1JlY29yZCA9IHtcbiAgICAgICAgdHlwZTogbXV0YXRpb24udHlwZSxcbiAgICAgICAgdGFyZ2V0SWQ6IHRhcmdldEVsZW1lbnQubm9kZUlkLFxuICAgICAgICBhZGRlZE5vZGVzLFxuICAgICAgICByZW1vdmVkTm9kZUlkcyxcbiAgICAgICAgcHJldmlvdXNTaWJsaW5nSWQ6XG4gICAgICAgICAgZmlyc3ROb25JZ25vcmVkUHJldmlvdXNTaWJsaW5nICE9PSBudWxsXG4gICAgICAgICAgICA/IHRoaXMuZ2V0VmlydHVhbERvbUVsZW1lbnRGb3JSZWFsRWxlbWVudE9yVGhyb3coZmlyc3ROb25JZ25vcmVkUHJldmlvdXNTaWJsaW5nKS5ub2RlSWRcbiAgICAgICAgICAgIDogbnVsbCxcbiAgICAgICAgYXR0cmlidXRlOiBtdXRhdGlvbi5hdHRyaWJ1dGVOYW1lXG4gICAgICAgICAgPyB7XG4gICAgICAgICAgICAgIGF0dHJpYnV0ZU5hbWU6IG11dGF0aW9uLmF0dHJpYnV0ZU5hbWUsXG4gICAgICAgICAgICAgIHZhbHVlOiAobXV0YXRpb24udGFyZ2V0IGFzIEVsZW1lbnQpLmdldEF0dHJpYnV0ZShtdXRhdGlvbi5hdHRyaWJ1dGVOYW1lKSxcbiAgICAgICAgICAgIH1cbiAgICAgICAgICA6IG51bGwsXG4gICAgICB9O1xuXG4gICAgICB0aGlzLmNhbGxiYWNrKHtcbiAgICAgICAgbXV0YXRpb246IG11dGF0aW9uUmVjb3JkLFxuICAgICAgICBkb2N1bWVudFRpbWU6IHRoaXMuZ2V0RG9jdW1lbnRUaW1lKCksXG4gICAgICB9KTtcblxuICAgICAgdGhpcy5yZW1vdmVLbm93bk5vZGVzSW5NdXRhdGlvbihtdXRhdGlvbik7XG4gICAgfVxuICB9XG5cbiAgcHJpdmF0ZSBhZGRLbm93bk5vZGVzSW5NdXRhdGlvbihtdXRhdGlvbjogTXV0YXRpb25SZWNvcmQpOiB2b2lkIHtcbiAgICBjb25zdCB0YXJnZXROb2RlID0gbXV0YXRpb24udGFyZ2V0IGFzIEVsZW1lbnQgfCBUZXh0O1xuICAgIGNvbnN0IHZpcnR1YWxEb21FbGVtZW50ID0gdGhpcy5yZWFsRWxlbWVudFRvVmlydHVhbEVsZW1lbnQuZ2V0KHRhcmdldE5vZGUpO1xuICAgIGlmICghdmlydHVhbERvbUVsZW1lbnQpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgXCJVbmtub3duIG5vZGUgaW4gYWRkS25vd25Ob2Rlc0luTXV0YXRpb246XCIgKyB0YXJnZXROb2RlICsgXCIsXCIgKyBtdXRhdGlvbi50eXBlLFxuICAgICAgKTtcbiAgICB9XG4gICAgaWYgKG11dGF0aW9uLnR5cGUgPT09IFwiY2hpbGRMaXN0XCIpIHtcbiAgICAgIGxldCBwcmV2aW91c1NpYmxpbmcgPSBtdXRhdGlvbi5wcmV2aW91c1NpYmxpbmc7XG4gICAgICBsZXQgaW5kZXggPSAwO1xuICAgICAgd2hpbGUgKHByZXZpb3VzU2libGluZyAmJiB0aGlzLmlzSWdub3JlZEVsZW1lbnQocHJldmlvdXNTaWJsaW5nIGFzIEVsZW1lbnQgfCBUZXh0KSkge1xuICAgICAgICBwcmV2aW91c1NpYmxpbmcgPSBwcmV2aW91c1NpYmxpbmcucHJldmlvdXNTaWJsaW5nO1xuICAgICAgfVxuICAgICAgaWYgKHByZXZpb3VzU2libGluZykge1xuICAgICAgICBjb25zdCBwcmV2aW91c1NpYmxpbmdFbGVtZW50ID0gdGhpcy5yZWFsRWxlbWVudFRvVmlydHVhbEVsZW1lbnQuZ2V0KFxuICAgICAgICAgIHByZXZpb3VzU2libGluZyBhcyBFbGVtZW50IHwgVGV4dCxcbiAgICAgICAgKTtcbiAgICAgICAgaWYgKCFwcmV2aW91c1NpYmxpbmdFbGVtZW50KSB7XG4gICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiVW5rbm93biBwcmV2aW91cyBzaWJsaW5nXCIpO1xuICAgICAgICB9XG4gICAgICAgIGluZGV4ID0gdmlydHVhbERvbUVsZW1lbnQuY2hpbGROb2Rlcy5pbmRleE9mKHByZXZpb3VzU2libGluZ0VsZW1lbnQpO1xuICAgICAgICBpZiAoaW5kZXggPT09IC0xKSB7XG4gICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiUHJldmlvdXMgc2libGluZyBpcyBub3QgY3VycmVudGx5IGEgY2hpbGQgb2YgdGhlIHBhcmVudCBlbGVtZW50XCIpO1xuICAgICAgICB9XG4gICAgICAgIGluZGV4ICs9IDE7XG4gICAgICB9XG4gICAgICBtdXRhdGlvbi5hZGRlZE5vZGVzLmZvckVhY2goKG5vZGU6IE5vZGUpID0+IHtcbiAgICAgICAgY29uc3QgYXNFbGVtZW50T3JUZXh0ID0gbm9kZSBhcyBFbGVtZW50IHwgVGV4dDtcbiAgICAgICAgY29uc3QgY2hpbGRWaXJ0dWFsRG9tRWxlbWVudCA9IHRoaXMuY3JlYXRlVmlydHVhbERvbUVsZW1lbnRXaXRoQ2hpbGRyZW4oXG4gICAgICAgICAgYXNFbGVtZW50T3JUZXh0LFxuICAgICAgICAgIHZpcnR1YWxEb21FbGVtZW50LFxuICAgICAgICApO1xuICAgICAgICBpZiAoY2hpbGRWaXJ0dWFsRG9tRWxlbWVudCkge1xuICAgICAgICAgIGlmICh2aXJ0dWFsRG9tRWxlbWVudC5jaGlsZE5vZGVzLmluZGV4T2YoY2hpbGRWaXJ0dWFsRG9tRWxlbWVudCkgPT09IC0xKSB7XG4gICAgICAgICAgICB2aXJ0dWFsRG9tRWxlbWVudC5jaGlsZE5vZGVzLnNwbGljZShpbmRleCwgMCwgY2hpbGRWaXJ0dWFsRG9tRWxlbWVudCk7XG4gICAgICAgICAgICBpbmRleCsrO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfSk7XG4gICAgfSBlbHNlIGlmIChtdXRhdGlvbi50eXBlID09PSBcImF0dHJpYnV0ZXNcIikge1xuICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEB0eXBlc2NyaXB0LWVzbGludC9uby1ub24tbnVsbC1hc3NlcnRpb25cbiAgICAgIGNvbnN0IGF0dHJpYnV0ZU5hbWUgPSBtdXRhdGlvbi5hdHRyaWJ1dGVOYW1lITtcbiAgICAgIGlmICh0aGlzLmlzSWdub3JlZEF0dHJpYnV0ZSh0YXJnZXROb2RlLCBhdHRyaWJ1dGVOYW1lKSkge1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICBjb25zdCBhdHRyaWJ1dGVWYWx1ZSA9ICh0YXJnZXROb2RlIGFzIEVsZW1lbnQpLmdldEF0dHJpYnV0ZShhdHRyaWJ1dGVOYW1lKTtcbiAgICAgIGlmIChhdHRyaWJ1dGVWYWx1ZSA9PT0gbnVsbCkge1xuICAgICAgICBkZWxldGUgdmlydHVhbERvbUVsZW1lbnQuYXR0cmlidXRlc1thdHRyaWJ1dGVOYW1lXTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHZpcnR1YWxEb21FbGVtZW50LmF0dHJpYnV0ZXNbYXR0cmlidXRlTmFtZV0gPSBhdHRyaWJ1dGVWYWx1ZTtcbiAgICAgIH1cbiAgICB9IGVsc2UgaWYgKG11dGF0aW9uLnR5cGUgPT09IFwiY2hhcmFjdGVyRGF0YVwiKSB7XG4gICAgICB2aXJ0dWFsRG9tRWxlbWVudC50ZXh0Q29udGVudCA9IHRhcmdldE5vZGUudGV4dENvbnRlbnQgPyB0YXJnZXROb2RlLnRleHRDb250ZW50IDogdW5kZWZpbmVkO1xuICAgIH1cbiAgfVxuXG4gIHByaXZhdGUgcmVtb3ZlS25vd25Ob2Rlc0luTXV0YXRpb24obXV0YXRpb246IE11dGF0aW9uUmVjb3JkKTogdm9pZCB7XG4gICAgY29uc3QgdGFyZ2V0Tm9kZSA9IG11dGF0aW9uLnRhcmdldCBhcyBFbGVtZW50IHwgVGV4dDtcbiAgICBjb25zdCB2aXJ0dWFsRG9tRWxlbWVudCA9IHRoaXMucmVhbEVsZW1lbnRUb1ZpcnR1YWxFbGVtZW50LmdldCh0YXJnZXROb2RlKTtcbiAgICBpZiAoIXZpcnR1YWxEb21FbGVtZW50KSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXCJVbmtub3duIG5vZGUgaW4gbXV0YXRpb24gbGlzdDpcIiArIHRhcmdldE5vZGUgKyBcIiwgXCIgKyBtdXRhdGlvbi50eXBlKTtcbiAgICB9XG4gICAgaWYgKG11dGF0aW9uLnR5cGUgPT09IFwiY2hpbGRMaXN0XCIpIHtcbiAgICAgIGZvciAoY29uc3Qgbm9kZSBvZiBtdXRhdGlvbi5yZW1vdmVkTm9kZXMpIHtcbiAgICAgICAgY29uc3QgYXNFbGVtZW50T3JUZXh0ID0gbm9kZSBhcyBFbGVtZW50IHwgVGV4dDtcbiAgICAgICAgaWYgKHRoaXMuaXNJZ25vcmVkRWxlbWVudChhc0VsZW1lbnRPclRleHQpKSB7XG4gICAgICAgICAgY29udGludWU7XG4gICAgICAgIH1cbiAgICAgICAgY29uc3QgY2hpbGREb21FbGVtZW50ID0gdGhpcy5yZWFsRWxlbWVudFRvVmlydHVhbEVsZW1lbnQuZ2V0KGFzRWxlbWVudE9yVGV4dCk7XG4gICAgICAgIGlmICghY2hpbGREb21FbGVtZW50KSB7XG4gICAgICAgICAgY29uc29sZS53YXJuKHRoaXMuaHRtbFBhdGgsIFwiVW5rbm93biBub2RlIGluIHJlbW92ZUtub3duTm9kZXNJbk11dGF0aW9uXCIpO1xuICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHRoaXMucmVtb3ZlVmlydHVhbERvbUVsZW1lbnQoY2hpbGREb21FbGVtZW50KTtcbiAgICAgICAgICBjb25zdCBpbmRleCA9IHZpcnR1YWxEb21FbGVtZW50LmNoaWxkTm9kZXMuaW5kZXhPZihjaGlsZERvbUVsZW1lbnQpO1xuICAgICAgICAgIHZpcnR1YWxEb21FbGVtZW50LmNoaWxkTm9kZXMuc3BsaWNlKGluZGV4LCAxKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgfVxuXG4gIHByaXZhdGUgcmVtb3ZlVmlydHVhbERvbUVsZW1lbnQodmlydHVhbERvbUVsZW1lbnQ6IExpdmVWaXJ0dWFsRG9tRWxlbWVudCk6IHZvaWQge1xuICAgIHRoaXMubm9kZUlkVG9Ob2RlLmRlbGV0ZSh2aXJ0dWFsRG9tRWxlbWVudC5ub2RlSWQpO1xuICAgIHRoaXMubm9kZVRvTm9kZUlkLmRlbGV0ZSh2aXJ0dWFsRG9tRWxlbWVudCk7XG4gICAgdGhpcy5yZWFsRWxlbWVudFRvVmlydHVhbEVsZW1lbnQuZGVsZXRlKHZpcnR1YWxEb21FbGVtZW50LnJlYWxFbGVtZW50KTtcbiAgICBmb3IgKGNvbnN0IGNoaWxkIG9mIHZpcnR1YWxEb21FbGVtZW50LmNoaWxkTm9kZXMpIHtcbiAgICAgIHRoaXMucmVtb3ZlVmlydHVhbERvbUVsZW1lbnQoY2hpbGQpO1xuICAgIH1cbiAgfVxuXG4gIHByaXZhdGUgY3JlYXRlVmlydHVhbERvbUVsZW1lbnRXaXRoQ2hpbGRyZW4oXG4gICAgbm9kZTogRWxlbWVudCB8IFRleHQsXG4gICAgcGFyZW50OiBMaXZlVmlydHVhbERvbUVsZW1lbnQgfCBudWxsLFxuICApOiBMaXZlVmlydHVhbERvbUVsZW1lbnQgfCBudWxsIHtcbiAgICBjb25zdCB2aXJ0dWFsRWxlbWVudCA9IHRoaXMuY3JlYXRlVmlydHVhbERvbUVsZW1lbnQobm9kZSwgcGFyZW50KTtcbiAgICBpZiAoIXZpcnR1YWxFbGVtZW50KSB7XG4gICAgICByZXR1cm4gbnVsbDtcbiAgICB9XG4gICAgaWYgKChub2RlIGFzIEVsZW1lbnQpLmNoaWxkTm9kZXMpIHtcbiAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgKG5vZGUgYXMgRWxlbWVudCkuY2hpbGROb2Rlcy5sZW5ndGg7IGkrKykge1xuICAgICAgICBjb25zdCBjaGlsZCA9IChub2RlIGFzIEVsZW1lbnQpLmNoaWxkTm9kZXNbaV07XG4gICAgICAgIGNvbnN0IGNoaWxkVmlydHVhbEVsZW1lbnQgPSB0aGlzLmNyZWF0ZVZpcnR1YWxEb21FbGVtZW50V2l0aENoaWxkcmVuKFxuICAgICAgICAgIGNoaWxkIGFzIEVsZW1lbnQgfCBUZXh0LFxuICAgICAgICAgIHZpcnR1YWxFbGVtZW50LFxuICAgICAgICApO1xuICAgICAgICBpZiAoY2hpbGRWaXJ0dWFsRWxlbWVudCkge1xuICAgICAgICAgIHZpcnR1YWxFbGVtZW50LmNoaWxkTm9kZXMucHVzaChjaGlsZFZpcnR1YWxFbGVtZW50KTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiB2aXJ0dWFsRWxlbWVudDtcbiAgfVxuXG4gIHByaXZhdGUgY3JlYXRlVmlydHVhbERvbUVsZW1lbnQoXG4gICAgbm9kZTogRWxlbWVudCB8IFRleHQsXG4gICAgcGFyZW50OiBMaXZlVmlydHVhbERvbUVsZW1lbnQgfCBudWxsLFxuICApOiBMaXZlVmlydHVhbERvbUVsZW1lbnQgfCBudWxsIHtcbiAgICBpZiAodGhpcy5pc0lnbm9yZWRFbGVtZW50KG5vZGUpKSB7XG4gICAgICByZXR1cm4gbnVsbDtcbiAgICB9XG4gICAgY29uc3QgZXhpc3RpbmdWYWx1ZSA9IHRoaXMucmVhbEVsZW1lbnRUb1ZpcnR1YWxFbGVtZW50LmdldChub2RlKTtcbiAgICBpZiAoZXhpc3RpbmdWYWx1ZSAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXCJOb2RlIGFscmVhZHkgaGFzIGEgdmlydHVhbCBlbGVtZW50OiBcIiArIG5vZGUubm9kZU5hbWUpO1xuICAgIH1cbiAgICBpZiAoIW5vZGUpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihcIkNhbm5vdCBhc3NpZ24gbm9kZSBpZCB0byBudWxsXCIpO1xuICAgIH1cblxuICAgIGNvbnN0IGF0dHJpYnV0ZXM6IHsgW2tleTogc3RyaW5nXTogc3RyaW5nIH0gPSB7fTtcbiAgICBpZiAoKG5vZGUgYXMgYW55KS5hdHRyaWJ1dGVzKSB7XG4gICAgICBjb25zdCBhc0hUTUxFbGVtZW50ID0gbm9kZSBhcyBIVE1MRWxlbWVudDtcbiAgICAgIGZvciAoY29uc3Qga2V5IG9mIGFzSFRNTEVsZW1lbnQuZ2V0QXR0cmlidXRlTmFtZXMoKSkge1xuICAgICAgICBjb25zdCB2YWx1ZSA9IGFzSFRNTEVsZW1lbnQuZ2V0QXR0cmlidXRlKGtleSk7XG4gICAgICAgIGlmICh2YWx1ZSA9PT0gbnVsbCkge1xuICAgICAgICAgIHRocm93IG5ldyBFcnJvcihcIk51bGwgYXR0cmlidXRlIHZhbHVlIGZvciBrZXk6IFwiICsga2V5KTtcbiAgICAgICAgfVxuICAgICAgICBpZiAoIXRoaXMuaXNJZ25vcmVkQXR0cmlidXRlKG5vZGUsIGtleSkpIHtcbiAgICAgICAgICBhdHRyaWJ1dGVzW2tleV0gPSB2YWx1ZTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cblxuICAgIGNvbnN0IG5vZGVJZCA9IHRoaXMubmV4dE5vZGVJZCsrO1xuICAgIGNvbnN0IHZpcnR1YWxFbGVtZW50OiBMaXZlVmlydHVhbERvbUVsZW1lbnQgPSB7XG4gICAgICBub2RlSWQsXG4gICAgICB0YWc6IG5vZGUubm9kZU5hbWUsXG4gICAgICBhdHRyaWJ1dGVzLFxuICAgICAgY2hpbGROb2RlczogW10sXG4gICAgICByZWFsRWxlbWVudDogbm9kZSxcbiAgICAgIHBhcmVudCxcbiAgICB9O1xuICAgIGlmIChub2RlIGluc3RhbmNlb2YgdGhpcy5kb21SdW5uZXIuZ2V0V2luZG93KCkuVGV4dCAmJiBub2RlLnRleHRDb250ZW50KSB7XG4gICAgICB2aXJ0dWFsRWxlbWVudC50ZXh0Q29udGVudCA9IG5vZGUudGV4dENvbnRlbnQ7XG4gICAgfVxuICAgIHRoaXMubm9kZVRvTm9kZUlkLnNldCh2aXJ0dWFsRWxlbWVudCwgbm9kZUlkKTtcbiAgICB0aGlzLm5vZGVJZFRvTm9kZS5zZXQobm9kZUlkLCB2aXJ0dWFsRWxlbWVudCk7XG4gICAgdGhpcy5yZWFsRWxlbWVudFRvVmlydHVhbEVsZW1lbnQuc2V0KG5vZGUsIHZpcnR1YWxFbGVtZW50KTtcbiAgICByZXR1cm4gdmlydHVhbEVsZW1lbnQ7XG4gIH1cblxuICBwcml2YXRlIGdldEZpcnN0Tm9uSWdub3JlZFByZXZpb3VzU2libGluZyhub2RlOiBFbGVtZW50IHwgVGV4dCk6IEVsZW1lbnQgfCBUZXh0IHwgbnVsbCB7XG4gICAgbGV0IGN1cnJlbnROb2RlID0gbm9kZTtcbiAgICBpZiAoIXRoaXMuaXNJZ25vcmVkRWxlbWVudChjdXJyZW50Tm9kZSkpIHtcbiAgICAgIHJldHVybiBjdXJyZW50Tm9kZTtcbiAgICB9XG4gICAgd2hpbGUgKGN1cnJlbnROb2RlICYmIGN1cnJlbnROb2RlLnByZXZpb3VzU2libGluZykge1xuICAgICAgY3VycmVudE5vZGUgPSBjdXJyZW50Tm9kZS5wcmV2aW91c1NpYmxpbmcgYXMgRWxlbWVudCB8IFRleHQ7XG4gICAgICBpZiAoIXRoaXMuaXNJZ25vcmVkRWxlbWVudChjdXJyZW50Tm9kZSkpIHtcbiAgICAgICAgcmV0dXJuIGN1cnJlbnROb2RlO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuXG4gIHByaXZhdGUgZ2V0VmlydHVhbERvbUVsZW1lbnRGb3JSZWFsRWxlbWVudE9yVGhyb3coXG4gICAgcmVhbEVsZW1lbnQ6IEVsZW1lbnQgfCBUZXh0LFxuICApOiBMaXZlVmlydHVhbERvbUVsZW1lbnQge1xuICAgIGNvbnN0IHZpcnR1YWxFbGVtZW50ID0gdGhpcy5yZWFsRWxlbWVudFRvVmlydHVhbEVsZW1lbnQuZ2V0KHJlYWxFbGVtZW50KTtcbiAgICBpZiAoIXZpcnR1YWxFbGVtZW50KSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoYFZpcnR1YWwgZWxlbWVudCBub3QgZm91bmQgZm9yIHJlYWwgZWxlbWVudGApO1xuICAgIH1cbiAgICByZXR1cm4gdmlydHVhbEVsZW1lbnQ7XG4gIH1cblxuICBwcml2YXRlIGlzSWdub3JlZEVsZW1lbnQobm9kZTogRWxlbWVudCB8IFRleHQpOiBib29sZWFuIHtcbiAgICBpZiAodGhpcy5pZ25vcmVUZXh0Tm9kZXMgJiYgbm9kZSBpbnN0YW5jZW9mIHRoaXMuZG9tUnVubmVyLmdldFdpbmRvdygpLlRleHQpIHtcbiAgICAgIHJldHVybiB0cnVlO1xuICAgIH0gZWxzZSBpZiAobm9kZSBpbnN0YW5jZW9mIHRoaXMuZG9tUnVubmVyLmdldFdpbmRvdygpLkhUTUxTY3JpcHRFbGVtZW50KSB7XG4gICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9IGVsc2UgaWYgKG5vZGUgaW5zdGFuY2VvZiB0aGlzLmRvbVJ1bm5lci5nZXRXaW5kb3coKS5Db21tZW50KSB7XG4gICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG5cbiAgcHJpdmF0ZSBpc0lnbm9yZWRBdHRyaWJ1dGUobm9kZTogRWxlbWVudCB8IFRleHQsIGF0dHJpYnV0ZU5hbWU6IHN0cmluZyk6IGJvb2xlYW4ge1xuICAgIHJldHVybiBhdHRyaWJ1dGVOYW1lLnN0YXJ0c1dpdGgoXCJvblwiKTtcbiAgfVxuXG4gIHB1YmxpYyBkaXNwYXRjaFJlbW90ZUV2ZW50RnJvbUNvbm5lY3Rpb25JZChjb25uZWN0aW9uSWQ6IG51bWJlciwgcmVtb3RlRXZlbnQ6IFJlbW90ZUV2ZW50KTogdm9pZCB7XG4gICAgY29uc3QgZG9tTm9kZSA9IHRoaXMubm9kZUlkVG9Ob2RlLmdldChyZW1vdGVFdmVudC5ub2RlSWQpO1xuICAgIGlmICghZG9tTm9kZSkge1xuICAgICAgY29uc29sZS5lcnJvcihcIlVua25vd24gbm9kZSBJRCBpbiByZW1vdGUgZXZlbnQ6IFwiICsgcmVtb3RlRXZlbnQubm9kZUlkKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICBpZiAoZG9tTm9kZSBpbnN0YW5jZW9mIHRoaXMuZG9tUnVubmVyLmdldFdpbmRvdygpLlRleHQpIHtcbiAgICAgIGNvbnNvbGUud2FybihcIkNhbm5vdCBkaXNwYXRjaCByZW1vdGUgZXZlbnQgdG8gdGV4dCBub2RlXCIpO1xuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIHRoaXMuZG9tUnVubmVyLmRpc3BhdGNoUmVtb3RlRXZlbnRGcm9tQ29ubmVjdGlvbklkKFxuICAgICAgY29ubmVjdGlvbklkLFxuICAgICAgZG9tTm9kZS5yZWFsRWxlbWVudCBhcyBFbGVtZW50LFxuICAgICAgcmVtb3RlRXZlbnQsXG4gICAgKTtcbiAgfVxuXG4gIHB1YmxpYyBkaXNwb3NlKCkge1xuICAgIGNsZWFySW50ZXJ2YWwodGhpcy5kb2N1bWVudFRpbWVJbnRlcnZhbFRpbWVyKTtcbiAgICB0aGlzLmRvbVJ1bm5lci5kaXNwb3NlKCk7XG4gIH1cblxuICBwcml2YXRlIGdldERvY3VtZW50VGltZSgpIHtcbiAgICByZXR1cm4gdGhpcy5kb21SdW5uZXIuZ2V0RG9jdW1lbnRUaW1lKCk7XG4gIH1cbn1cbiIsICJpbXBvcnQgeyBSZW1vdGVFdmVudCB9IGZyb20gXCJAbW1sLWlvL25ldHdvcmtlZC1kb20tcHJvdG9jb2xcIjtcbmltcG9ydCB7IERPTVJ1bm5lckZhY3RvcnksIERPTVJ1bm5lckludGVyZmFjZSwgRE9NUnVubmVyTWVzc2FnZSB9IGZyb20gXCJAbW1sLWlvL29ic2VydmFibGUtZG9tXCI7XG5cbmV4cG9ydCBjb25zdCBXZWJCcm93c2VyRE9NUnVubmVyRmFjdG9yeTogRE9NUnVubmVyRmFjdG9yeSA9IChcbiAgaHRtbFBhdGg6IHN0cmluZyxcbiAgaHRtbENvbnRlbnRzOiBzdHJpbmcsXG4gIHBhcmFtczogb2JqZWN0LFxuICBjYWxsYmFjazogKG11dGF0aW9uTGlzdDogRE9NUnVubmVyTWVzc2FnZSkgPT4gdm9pZCxcbik6IERPTVJ1bm5lckludGVyZmFjZSA9PiB7XG4gIHJldHVybiBuZXcgV2ViQnJvd3NlckRPTVJ1bm5lcihodG1sUGF0aCwgaHRtbENvbnRlbnRzLCBwYXJhbXMsIGNhbGxiYWNrKTtcbn07XG5cbmNvbnN0IGRvY3VtZW50TG9hZFRpbWUgPSBEYXRlLm5vdygpO1xuXG5leHBvcnQgY2xhc3MgV2ViQnJvd3NlckRPTVJ1bm5lciBpbXBsZW1lbnRzIERPTVJ1bm5lckludGVyZmFjZSB7XG4gIHByaXZhdGUgbXV0YXRpb25PYnNlcnZlcjogTXV0YXRpb25PYnNlcnZlcjtcbiAgcHJpdmF0ZSBodG1sUGF0aDogc3RyaW5nO1xuICBwcml2YXRlIGNhbGxiYWNrOiAoZG9tUnVubmVyTWVzc2FnZTogRE9NUnVubmVyTWVzc2FnZSkgPT4gdm9pZDtcblxuICBjb25zdHJ1Y3RvcihcbiAgICBodG1sUGF0aDogc3RyaW5nLFxuICAgIGh0bWxDb250ZW50czogc3RyaW5nLFxuICAgIHBhcmFtczogb2JqZWN0LFxuICAgIGNhbGxiYWNrOiAoZG9tUnVubmVyTWVzc2FnZTogRE9NUnVubmVyTWVzc2FnZSkgPT4gdm9pZCxcbiAgKSB7XG4gICAgdGhpcy5odG1sUGF0aCA9IGh0bWxQYXRoO1xuICAgIHRoaXMuY2FsbGJhY2sgPSBjYWxsYmFjaztcblxuICAgIGxldCBkaWRTZW5kTG9hZCA9IGZhbHNlO1xuXG4gICAgdGhpcy5tdXRhdGlvbk9ic2VydmVyID0gbmV3IHdpbmRvdy5NdXRhdGlvbk9ic2VydmVyKChtdXRhdGlvbkxpc3QpID0+IHtcbiAgICAgIGlmICghZG9jdW1lbnQpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgaWYgKCFkaWRTZW5kTG9hZCkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXCJNdXRhdGlvbk9ic2VydmVyIGNhbGxlZCBiZWZvcmUgbG9hZFwiKTtcbiAgICAgIH1cbiAgICAgIHRoaXMuY2FsbGJhY2soe1xuICAgICAgICBtdXRhdGlvbkxpc3QsXG4gICAgICB9KTtcbiAgICB9KTtcblxuICAgICh3aW5kb3cgYXMgYW55KS5wYXJhbXMgPSBwYXJhbXM7XG5cbiAgICBjb25zdCBmaW5pc2hMb2FkID0gKCkgPT4ge1xuICAgICAgaWYgKGRpZFNlbmRMb2FkKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcImZpbmlzaExvYWQgY2FsbGVkIHR3aWNlXCIpO1xuICAgICAgfVxuICAgICAgZGlkU2VuZExvYWQgPSB0cnVlO1xuICAgICAgdGhpcy5jYWxsYmFjayh7XG4gICAgICAgIGxvYWRlZDogdHJ1ZSxcbiAgICAgIH0pO1xuICAgICAgdGhpcy5tdXRhdGlvbk9ic2VydmVyLm9ic2VydmUod2luZG93LmRvY3VtZW50LCB7XG4gICAgICAgIGF0dHJpYnV0ZXM6IHRydWUsXG4gICAgICAgIGNoaWxkTGlzdDogdHJ1ZSxcbiAgICAgICAgc3VidHJlZTogdHJ1ZSxcbiAgICAgICAgY2hhcmFjdGVyRGF0YTogdHJ1ZSxcbiAgICAgIH0pO1xuICAgIH07XG4gICAgaWYgKGRvY3VtZW50LmJvZHkpIHtcbiAgICAgIHNldFRpbWVvdXQoZmluaXNoTG9hZCwgMCk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKFwiRE9NQ29udGVudExvYWRlZFwiLCBmaW5pc2hMb2FkKTtcbiAgICB9XG4gIH1cblxuICBhZGRJUENXZWJzb2NrZXQoKTogdm9pZCB7XG4gICAgdGhyb3cgbmV3IEVycm9yKFwiTm90IGltcGxlbWVudGVkLlwiKTtcbiAgfVxuXG4gIGRpc3BhdGNoUmVtb3RlRXZlbnRGcm9tQ29ubmVjdGlvbklkKFxuICAgIGNvbm5lY3Rpb25JZDogbnVtYmVyLFxuICAgIHJlYWxFbGVtZW50OiBFbGVtZW50LFxuICAgIHJlbW90ZUV2ZW50OiBSZW1vdGVFdmVudCxcbiAgKTogdm9pZCB7XG4gICAgY29uc3QgYnViYmxlcyA9IHJlbW90ZUV2ZW50LmJ1YmJsZXMgfHwgZmFsc2U7XG4gICAgY29uc3QgcmVtb3RlRXZlbnRPYmplY3QgPSBuZXcgQ3VzdG9tRXZlbnQocmVtb3RlRXZlbnQubmFtZSwge1xuICAgICAgYnViYmxlcyxcbiAgICAgIGRldGFpbDogeyAuLi5yZW1vdGVFdmVudC5wYXJhbXMsIGNvbm5lY3Rpb25JZCB9LFxuICAgIH0pO1xuXG4gICAgLy8gRGlzcGF0Y2ggdGhlIGV2ZW50IHZpYSBKYXZhU2NyaXB0LlxuICAgIHJlYWxFbGVtZW50LmRpc3BhdGNoRXZlbnQocmVtb3RlRXZlbnRPYmplY3QpO1xuICB9XG5cbiAgZGlzcG9zZSgpOiB2b2lkIHtcbiAgICAvLyBUT0RPIC0gaGFuZGxlIGRpc3Bvc2VcbiAgICBjb25zb2xlLmxvZyhcIldlYkJyb3dzZXJET01SdW5uZXIuZGlzcG9zZVwiKTtcbiAgfVxuXG4gIGdldERvY3VtZW50KCk6IERvY3VtZW50IHtcbiAgICByZXR1cm4gZG9jdW1lbnQ7XG4gIH1cblxuICBnZXREb2N1bWVudFRpbWUoKTogbnVtYmVyIHtcbiAgICBpZiAoZG9jdW1lbnQudGltZWxpbmUgJiYgZG9jdW1lbnQudGltZWxpbmUuY3VycmVudFRpbWUpIHtcbiAgICAgIHJldHVybiBkb2N1bWVudC50aW1lbGluZS5jdXJyZW50VGltZTtcbiAgICB9XG4gICAgcmV0dXJuIERhdGUubm93KCkgLSBkb2N1bWVudExvYWRUaW1lO1xuICB9XG5cbiAgLy8gVE9ETyAtIHJlc29sdmUgdHlwZXMgKFdpbmRvdyBuZWVkcyB0byBleHBvc2UgY2xhc3NlcyBzdWNoIGFzIEN1c3RvbUV2ZW50IGFzIHByb3BlcnRpZXMpXG4gIGdldFdpbmRvdygpOiBhbnkge1xuICAgIHJldHVybiB3aW5kb3c7XG4gIH1cbn1cbiIsICJpbXBvcnQge1xuICBGcm9tSW5zdGFuY2VNZXNzYWdlVHlwZXMsXG4gIFRvSW5zdGFuY2VNZXNzYWdlVHlwZXMsXG59IGZyb20gXCJAbW1sLWlvL25ldHdvcmtlZC1kb20td2ViLXJ1bm5lci9zcmMvbWVzc2FnZS10eXBlc1wiO1xuaW1wb3J0IHsgT2JzZXJ2YWJsZURvbSB9IGZyb20gXCJAbW1sLWlvL29ic2VydmFibGUtZG9tL3NyYy9PYnNlcnZhYmxlRG9tXCI7XG5pbXBvcnQgeyBPYnNlcnZhYmxlRG9tTWVzc2FnZSwgT2JzZXJ2YWJsZURPTVBhcmFtZXRlcnMgfSBmcm9tIFwiQG1tbC1pby9vYnNlcnZhYmxlLWRvbS1jb21tb25cIjtcblxuaW1wb3J0IHsgV2ViQnJvd3NlckRPTVJ1bm5lckZhY3RvcnkgfSBmcm9tIFwiLi9XZWJCcm93c2VyRE9NUnVubmVyXCI7XG5cbi8vIFRoaXMgcnVucyBpbiB0aGUgaWZyYW1lIHRoYXQgd2lsbCBleGVjdXRlIHRoZSBkb2N1bWVudCBzY3JpcHQgdG8gc2V0dXAgdGhlIGxpc3RlbmluZyBmb3IgZXZlbnRzIG1lc3NhZ2VzXG5leHBvcnQgZnVuY3Rpb24gc2V0dXBJZnJhbWVXZWJSdW5uZXIoYXJnc1N0cmluZzogc3RyaW5nKSB7XG4gIGNvbnN0IG9ic2VydmFibGVET01QYXJhbXMgPSBKU09OLnBhcnNlKGF0b2IoYXJnc1N0cmluZykpIGFzIE9ic2VydmFibGVET01QYXJhbWV0ZXJzO1xuXG4gIGNvbnN0IHNlbmRNZXNzYWdlVG9IYW5kbGVyID0gKG1lc3NhZ2U6IEZyb21JbnN0YW5jZU1lc3NhZ2VUeXBlcykgPT4ge1xuICAgIHdpbmRvdy5wYXJlbnQucG9zdE1lc3NhZ2UoSlNPTi5zdHJpbmdpZnkobWVzc2FnZSksIFwiKlwiKTtcbiAgfTtcblxuICBjb25zdCBvYnNlcnZhYmxlRE9NID0gbmV3IE9ic2VydmFibGVEb20oXG4gICAge1xuICAgICAgLi4ub2JzZXJ2YWJsZURPTVBhcmFtcyxcbiAgICAgIGh0bWxDb250ZW50czogXCJcIiwgLy8gVGhpcyBtdXN0IGJlIGVtcHR5IGFzIHRoZSBjb250ZW50cyBhcmUgYXNzdW1lZCB0byBiZSBwcm92aWRlZCBieSB0aGUgc3JjZG9jXG4gICAgfSxcbiAgICAob2JzZXJ2YWJsZURvbU1lc3NhZ2U6IE9ic2VydmFibGVEb21NZXNzYWdlKSA9PiB7XG4gICAgICBzZW5kTWVzc2FnZVRvSGFuZGxlcih7XG4gICAgICAgIHR5cGU6IFwiZG9tXCIsXG4gICAgICAgIG1lc3NhZ2U6IG9ic2VydmFibGVEb21NZXNzYWdlLFxuICAgICAgfSk7XG4gICAgfSxcbiAgICBXZWJCcm93c2VyRE9NUnVubmVyRmFjdG9yeSxcbiAgKTtcblxuICB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcihcIm1lc3NhZ2VcIiwgKGUpID0+IHtcbiAgICBjb25zdCBwYXJzZWQgPSBKU09OLnBhcnNlKGUuZGF0YSkgYXMgVG9JbnN0YW5jZU1lc3NhZ2VUeXBlcztcbiAgICBpZiAocGFyc2VkLnR5cGUgPT09IFwiZGlzcGF0Y2hSZW1vdGVFdmVudEZyb21Db25uZWN0aW9uSWRcIikge1xuICAgICAgb2JzZXJ2YWJsZURPTS5kaXNwYXRjaFJlbW90ZUV2ZW50RnJvbUNvbm5lY3Rpb25JZChwYXJzZWQuY29ubmVjdGlvbklkLCBwYXJzZWQuZXZlbnQpO1xuICAgIH1cbiAgfSk7XG59XG4iLCAiaW1wb3J0IHsgc2V0dXBJZnJhbWVXZWJSdW5uZXIgfSBmcm9tIFwiLi9JZnJhbWVXZWJSdW5uZXJcIjtcblxuY29uc3QgYXJncyA9ICh3aW5kb3cgYXMgYW55KS5hcmdzO1xuc2V0dXBJZnJhbWVXZWJSdW5uZXIoYXJncyk7XG4iXSwKICAibWFwcGluZ3MiOiAiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFJTyxTQUFTLDBCQUEwQixJQUFvRDtBQUM1RixTQUFPO0FBQUEsSUFDTCxRQUFRLEdBQUc7QUFBQSxJQUNYLEtBQUssR0FBRztBQUFBLElBQ1IsWUFBWSxHQUFHO0FBQUEsSUFDZixZQUFZLEdBQUcsV0FBVyxJQUFJLENBQUMsVUFBVSwwQkFBMEIsS0FBSyxDQUFDO0FBQUEsSUFDekUsYUFBYSxHQUFHO0FBQUEsRUFDbEI7QUFDRjs7O0FDcUNPLElBQU0sZ0JBQU4sTUFBc0Q7QUFBQSxFQVkzRCxZQUNFLHlCQUNBLFVBQ0EsZUFDQTtBQWZGLFNBQVEsZUFBZSxvQkFBSSxJQUFtQztBQUM5RCxTQUFRLGVBQWUsb0JBQUksSUFBbUM7QUFDOUQsU0FBUSw4QkFBOEIsb0JBQUksSUFBMkM7QUFDckYsU0FBUSxrQkFBa0I7QUFFMUIs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\n';
|
|
33
|
+
|
|
34
|
+
// src/RunnerIframe.ts
|
|
35
|
+
var RunnerIframe = class {
|
|
36
|
+
constructor(observableDOMParameters, onMessageCallback) {
|
|
37
|
+
this.iframe = document.createElement("iframe");
|
|
38
|
+
this.iframe.setAttribute("sandbox", "allow-scripts");
|
|
39
|
+
this.iframe.style.position = "fixed";
|
|
40
|
+
this.iframe.style.top = "0";
|
|
41
|
+
this.iframe.style.left = "0";
|
|
42
|
+
this.iframe.style.width = "0";
|
|
43
|
+
this.iframe.style.height = "0";
|
|
44
|
+
this.iframe.style.border = "none";
|
|
45
|
+
const paramsMinusCode = {
|
|
46
|
+
...observableDOMParameters
|
|
47
|
+
};
|
|
48
|
+
delete paramsMinusCode.htmlContents;
|
|
49
|
+
const args = btoa(JSON.stringify(paramsMinusCode));
|
|
50
|
+
const isJSDOM = navigator.userAgent.includes("jsdom");
|
|
51
|
+
if (isJSDOM) {
|
|
52
|
+
document.body.append(this.iframe);
|
|
53
|
+
const iframeBody = this.iframe.contentWindow.document.body;
|
|
54
|
+
const argsScriptElement = document.createElement("script");
|
|
55
|
+
argsScriptElement.innerHTML = `window.args="${args}";`;
|
|
56
|
+
iframeBody.append(argsScriptElement);
|
|
57
|
+
const runnerScriptElement = document.createElement("script");
|
|
58
|
+
runnerScriptElement.innerHTML = build_default;
|
|
59
|
+
iframeBody.append(runnerScriptElement);
|
|
60
|
+
const contentHolder = document.createElement("div");
|
|
61
|
+
iframeBody.append(contentHolder);
|
|
62
|
+
contentHolder.innerHTML = observableDOMParameters.htmlContents;
|
|
63
|
+
} else {
|
|
64
|
+
this.iframe.setAttribute(
|
|
65
|
+
"srcdoc",
|
|
66
|
+
`
|
|
67
|
+
<script>window.args="${args}";</script>
|
|
68
|
+
<script>${build_default}</script>
|
|
69
|
+
${observableDOMParameters.htmlContents}
|
|
70
|
+
`
|
|
71
|
+
);
|
|
72
|
+
document.body.append(this.iframe);
|
|
73
|
+
}
|
|
74
|
+
this.onMessageCallback = onMessageCallback;
|
|
75
|
+
this.postMessageListener = (e) => {
|
|
76
|
+
if (e.source === this.iframe.contentWindow || isJSDOM && e.source === null) {
|
|
77
|
+
const parsed = JSON.parse(e.data);
|
|
78
|
+
onMessageCallback(parsed);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
window.addEventListener("message", this.postMessageListener);
|
|
82
|
+
}
|
|
83
|
+
sendMessageToRunner(message) {
|
|
84
|
+
this.iframe.contentWindow.postMessage(JSON.stringify(message), "*");
|
|
85
|
+
}
|
|
86
|
+
dispose() {
|
|
87
|
+
window.removeEventListener("message", this.postMessageListener);
|
|
88
|
+
this.iframe.remove();
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// src/NetworkedDOMWebRunnerClient.ts
|
|
93
|
+
var import_networked_dom_web = require("@mml-io/networked-dom-web");
|
|
94
|
+
|
|
95
|
+
// src/FakeWebsocket.ts
|
|
96
|
+
var WebsocketEnd = class extends EventTarget {
|
|
97
|
+
constructor(protocol, sendCallback) {
|
|
98
|
+
super();
|
|
99
|
+
this.protocol = protocol;
|
|
100
|
+
this.sendCallback = sendCallback;
|
|
101
|
+
}
|
|
102
|
+
close() {
|
|
103
|
+
this.dispatchEvent(new CloseEvent("close"));
|
|
104
|
+
}
|
|
105
|
+
addEventListener(type, listener, options) {
|
|
106
|
+
if (type === "open") {
|
|
107
|
+
listener.bind(this)(new Event("open"));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
super.addEventListener(type, listener, options);
|
|
111
|
+
}
|
|
112
|
+
send(data) {
|
|
113
|
+
this.sendCallback(data);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
var FakeWebsocket = class {
|
|
117
|
+
constructor(protocol) {
|
|
118
|
+
this.clientSideWebsocket = new WebsocketEnd(protocol, (data) => {
|
|
119
|
+
this.serverSideWebsocket.dispatchEvent(
|
|
120
|
+
new MessageEvent("message", {
|
|
121
|
+
data
|
|
122
|
+
})
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
this.serverSideWebsocket = new WebsocketEnd(protocol, (data) => {
|
|
126
|
+
this.clientSideWebsocket.dispatchEvent(
|
|
127
|
+
new MessageEvent("message", {
|
|
128
|
+
data
|
|
129
|
+
})
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// src/NetworkedDOMWebRunnerClient.ts
|
|
136
|
+
var NetworkedDOMWebRunnerClient = class {
|
|
137
|
+
constructor(enableEventHandling = true) {
|
|
138
|
+
this.connectedState = null;
|
|
139
|
+
this.enableEventHandling = enableEventHandling;
|
|
140
|
+
this.element = document.createElement("div");
|
|
141
|
+
this.element.style.position = "relative";
|
|
142
|
+
this.element.style.width = "100%";
|
|
143
|
+
this.element.style.height = "100%";
|
|
144
|
+
this.element.style.border = "1px solid black";
|
|
145
|
+
this.remoteDocumentHolder = document.createElement("div");
|
|
146
|
+
this.element.append(this.remoteDocumentHolder);
|
|
147
|
+
}
|
|
148
|
+
disconnect() {
|
|
149
|
+
if (!this.connectedState) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
this.connectedState.document.removeWebSocket(
|
|
153
|
+
this.connectedState.fakeWebsocket.serverSideWebsocket
|
|
154
|
+
);
|
|
155
|
+
this.connectedState = null;
|
|
156
|
+
}
|
|
157
|
+
dispose() {
|
|
158
|
+
this.disconnect();
|
|
159
|
+
this.remoteDocumentHolder.remove();
|
|
160
|
+
this.element.remove();
|
|
161
|
+
}
|
|
162
|
+
connect(document2) {
|
|
163
|
+
if (this.connectedState) {
|
|
164
|
+
this.disconnect();
|
|
165
|
+
}
|
|
166
|
+
const fakeWebsocket = new FakeWebsocket("networked-dom-v0.1");
|
|
167
|
+
let overriddenHandler = null;
|
|
168
|
+
const eventHandler = (element, event) => {
|
|
169
|
+
if (!overriddenHandler) {
|
|
170
|
+
throw new Error("overriddenHandler not set");
|
|
171
|
+
}
|
|
172
|
+
overriddenHandler(element, event);
|
|
173
|
+
};
|
|
174
|
+
if (this.enableEventHandling) {
|
|
175
|
+
this.remoteDocumentHolder.addEventListener("click", (event) => {
|
|
176
|
+
eventHandler(event.target, event);
|
|
177
|
+
event.stopPropagation();
|
|
178
|
+
event.preventDefault();
|
|
179
|
+
return false;
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
const domWebsocket = new import_networked_dom_web.NetworkedDOMWebsocket(
|
|
183
|
+
"ws://localhost",
|
|
184
|
+
() => fakeWebsocket.clientSideWebsocket,
|
|
185
|
+
this.remoteDocumentHolder
|
|
186
|
+
);
|
|
187
|
+
overriddenHandler = (element, event) => {
|
|
188
|
+
domWebsocket.handleEvent(element, event);
|
|
189
|
+
};
|
|
190
|
+
document2.addWebSocket(fakeWebsocket.serverSideWebsocket);
|
|
191
|
+
this.connectedState = {
|
|
192
|
+
document: document2,
|
|
193
|
+
fakeWebsocket,
|
|
194
|
+
domWebsocket
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// src/IframeObservableDOMFactory.ts
|
|
200
|
+
var IframeObservableDOMFactory = (observableDOMParameters, callback) => {
|
|
201
|
+
const runnerIframe = new RunnerIframe(
|
|
202
|
+
observableDOMParameters,
|
|
203
|
+
(msg) => {
|
|
204
|
+
if (msg.type === "dom") {
|
|
205
|
+
callback(msg.message);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
);
|
|
209
|
+
const remoteObservableDOM = {
|
|
210
|
+
addConnectedUserId(connectionId) {
|
|
211
|
+
runnerIframe.sendMessageToRunner({
|
|
212
|
+
type: "addConnectedUserId",
|
|
213
|
+
connectionId
|
|
214
|
+
});
|
|
215
|
+
},
|
|
216
|
+
addIPCWebsocket() {
|
|
217
|
+
throw new Error("Not implemented");
|
|
218
|
+
},
|
|
219
|
+
dispatchRemoteEventFromConnectionId(connectionId, remoteEvent) {
|
|
220
|
+
runnerIframe.sendMessageToRunner({
|
|
221
|
+
type: "dispatchRemoteEventFromConnectionId",
|
|
222
|
+
connectionId,
|
|
223
|
+
event: remoteEvent
|
|
224
|
+
});
|
|
225
|
+
},
|
|
226
|
+
dispose() {
|
|
227
|
+
runnerIframe.dispose();
|
|
228
|
+
},
|
|
229
|
+
removeConnectedUserId(connectionId) {
|
|
230
|
+
runnerIframe.sendMessageToRunner({
|
|
231
|
+
type: "removeConnectedUserId",
|
|
232
|
+
connectionId
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
return remoteObservableDOM;
|
|
237
|
+
};
|
|
238
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
239
|
+
0 && (module.exports = {
|
|
240
|
+
FakeWebsocket,
|
|
241
|
+
IframeObservableDOMFactory,
|
|
242
|
+
NetworkedDOMWebRunnerClient,
|
|
243
|
+
RunnerIframe,
|
|
244
|
+
...require("@mml-io/networked-dom-document")
|
|
245
|
+
});
|
|
246
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/index.ts", "../src/RunnerIframe.ts", "../src/NetworkedDOMWebRunnerClient.ts", "../src/FakeWebsocket.ts", "../src/IframeObservableDOMFactory.ts"],
|
|
4
|
+
"sourcesContent": ["export * from \"@mml-io/networked-dom-document\";\n\nexport * from \"./RunnerIframe\";\nexport * from \"./NetworkedDOMWebRunnerClient\";\nexport * from \"./FakeWebsocket\";\nexport * from \"./IframeObservableDOMFactory\";\n", "import { ObservableDOMParameters } from \"@mml-io/observable-dom-common\";\n// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-ignore\n// eslint-disable-next-line import/no-unresolved\nimport runnerText from \"runner-iframe-js-text\";\n\nimport { FromInstanceMessageTypes, ToInstanceMessageTypes } from \"./message-types\";\n\nexport class RunnerIframe {\n private iframe: HTMLIFrameElement;\n private onMessageCallback: (msg: FromInstanceMessageTypes) => void;\n private postMessageListener: (msg: MessageEvent) => void;\n\n constructor(\n observableDOMParameters: ObservableDOMParameters,\n onMessageCallback: (msg: FromInstanceMessageTypes) => void,\n ) {\n this.iframe = document.createElement(\"iframe\");\n this.iframe.setAttribute(\"sandbox\", \"allow-scripts\");\n this.iframe.style.position = \"fixed\";\n this.iframe.style.top = \"0\";\n this.iframe.style.left = \"0\";\n this.iframe.style.width = \"0\";\n this.iframe.style.height = \"0\";\n this.iframe.style.border = \"none\";\n\n const paramsMinusCode: Partial<ObservableDOMParameters> = {\n ...observableDOMParameters,\n };\n delete paramsMinusCode.htmlContents;\n\n const args = btoa(JSON.stringify(paramsMinusCode));\n\n const isJSDOM = navigator.userAgent.includes(\"jsdom\");\n if (isJSDOM) {\n // srcdoc not supported, so we have to append elements to the iframe's document\n document.body.append(this.iframe);\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const iframeBody = this.iframe.contentWindow!.document.body;\n const argsScriptElement = document.createElement(\"script\");\n argsScriptElement.innerHTML = `window.args=\"${args}\";`;\n iframeBody.append(argsScriptElement);\n const runnerScriptElement = document.createElement(\"script\");\n runnerScriptElement.innerHTML = runnerText;\n iframeBody.append(runnerScriptElement);\n const contentHolder = document.createElement(\"div\");\n iframeBody.append(contentHolder);\n contentHolder.innerHTML = observableDOMParameters.htmlContents;\n } else {\n this.iframe.setAttribute(\n \"srcdoc\",\n `\n <script>window.args=\"${args}\";</script>\n <script>${runnerText}</script>\n ${observableDOMParameters.htmlContents}\n `,\n );\n document.body.append(this.iframe);\n }\n\n this.onMessageCallback = onMessageCallback;\n\n this.postMessageListener = (e: MessageEvent) => {\n if (e.source === this.iframe.contentWindow || (isJSDOM && e.source === null)) {\n const parsed = JSON.parse(e.data) as FromInstanceMessageTypes;\n onMessageCallback(parsed);\n }\n };\n window.addEventListener(\"message\", this.postMessageListener);\n }\n\n sendMessageToRunner(message: ToInstanceMessageTypes) {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n this.iframe.contentWindow!.postMessage(JSON.stringify(message), \"*\");\n }\n\n dispose() {\n window.removeEventListener(\"message\", this.postMessageListener);\n this.iframe.remove();\n }\n}\n", "import { EditableNetworkedDOM, NetworkedDOM } from \"@mml-io/networked-dom-document\";\nimport { NetworkedDOMWebsocket } from \"@mml-io/networked-dom-web\";\n\nimport { FakeWebsocket } from \"./FakeWebsocket\";\n\nexport class NetworkedDOMWebRunnerClient {\n public readonly element: HTMLDivElement;\n protected remoteDocumentHolder: HTMLElement;\n\n protected connectedState: {\n document: NetworkedDOM | EditableNetworkedDOM;\n domWebsocket: NetworkedDOMWebsocket;\n fakeWebsocket: FakeWebsocket;\n } | null = null;\n private enableEventHandling: boolean;\n\n constructor(enableEventHandling = true) {\n this.enableEventHandling = enableEventHandling;\n this.element = document.createElement(\"div\");\n this.element.style.position = \"relative\";\n this.element.style.width = \"100%\";\n this.element.style.height = \"100%\";\n this.element.style.border = \"1px solid black\";\n\n this.remoteDocumentHolder = document.createElement(\"div\");\n this.element.append(this.remoteDocumentHolder);\n }\n\n public disconnect() {\n if (!this.connectedState) {\n return;\n }\n this.connectedState.document.removeWebSocket(\n this.connectedState.fakeWebsocket.serverSideWebsocket as unknown as WebSocket,\n );\n this.connectedState = null;\n }\n\n public dispose() {\n this.disconnect();\n this.remoteDocumentHolder.remove();\n this.element.remove();\n }\n\n public connect(document: NetworkedDOM | EditableNetworkedDOM) {\n if (this.connectedState) {\n this.disconnect();\n }\n const fakeWebsocket = new FakeWebsocket(\"networked-dom-v0.1\");\n let overriddenHandler: ((element: HTMLElement, event: CustomEvent) => void) | null = null;\n const eventHandler = (element: HTMLElement, event: CustomEvent) => {\n if (!overriddenHandler) {\n throw new Error(\"overriddenHandler not set\");\n }\n overriddenHandler(element, event);\n };\n\n if (this.enableEventHandling) {\n this.remoteDocumentHolder.addEventListener(\"click\", (event: Event) => {\n eventHandler(event.target as HTMLElement, event as CustomEvent);\n event.stopPropagation();\n event.preventDefault();\n return false;\n });\n }\n\n const domWebsocket = new NetworkedDOMWebsocket(\n \"ws://localhost\",\n () => fakeWebsocket.clientSideWebsocket as unknown as WebSocket,\n this.remoteDocumentHolder,\n );\n overriddenHandler = (element: HTMLElement, event: CustomEvent) => {\n domWebsocket.handleEvent(element, event);\n };\n document.addWebSocket(fakeWebsocket.serverSideWebsocket as unknown as WebSocket);\n this.connectedState = {\n document,\n fakeWebsocket,\n domWebsocket,\n };\n }\n}\n", "class WebsocketEnd extends EventTarget {\n private readonly sendCallback: (data: string | ArrayBufferLike | Blob | ArrayBufferView) => void;\n public readonly protocol: string;\n\n constructor(\n protocol: string,\n sendCallback: (data: string | ArrayBufferLike | Blob | ArrayBufferView) => void,\n ) {\n super();\n this.protocol = protocol;\n this.sendCallback = sendCallback;\n }\n\n public close() {\n this.dispatchEvent(new CloseEvent(\"close\"));\n }\n\n public addEventListener<K extends keyof WebSocketEventMap>(\n type: K,\n listener: (this: WebSocket, ev: WebSocketEventMap[K]) => any,\n options?: boolean | AddEventListenerOptions,\n ) {\n if (type === \"open\") {\n listener.bind(this)(new Event(\"open\"));\n return;\n }\n super.addEventListener(type, listener, options);\n }\n\n public send(data: string | ArrayBufferLike | Blob | ArrayBufferView) {\n this.sendCallback(data);\n }\n}\n\nexport class FakeWebsocket {\n public clientSideWebsocket: WebsocketEnd;\n public serverSideWebsocket: WebsocketEnd;\n\n constructor(protocol: string) {\n this.clientSideWebsocket = new WebsocketEnd(protocol, (data) => {\n this.serverSideWebsocket.dispatchEvent(\n new MessageEvent(\"message\", {\n data,\n }),\n );\n });\n\n this.serverSideWebsocket = new WebsocketEnd(protocol, (data) => {\n this.clientSideWebsocket.dispatchEvent(\n new MessageEvent(\"message\", {\n data,\n }),\n );\n });\n }\n}\n", "import { ObservableDomFactory } from \"@mml-io/networked-dom-document\";\nimport {\n ObservableDomInterface,\n ObservableDomMessage,\n ObservableDOMParameters,\n RemoteEvent,\n} from \"@mml-io/observable-dom-common\";\n\nimport { FromInstanceMessageTypes } from \"./message-types\";\nimport { RunnerIframe } from \"./RunnerIframe\";\n\nexport const IframeObservableDOMFactory: ObservableDomFactory = (\n observableDOMParameters: ObservableDOMParameters,\n callback: (message: ObservableDomMessage) => void,\n) => {\n const runnerIframe = new RunnerIframe(\n observableDOMParameters,\n (msg: FromInstanceMessageTypes) => {\n if (msg.type === \"dom\") {\n callback(msg.message);\n }\n },\n );\n\n const remoteObservableDOM: ObservableDomInterface = {\n addConnectedUserId(connectionId: number): void {\n runnerIframe.sendMessageToRunner({\n type: \"addConnectedUserId\",\n connectionId,\n });\n },\n addIPCWebsocket(): void {\n throw new Error(\"Not implemented\");\n },\n dispatchRemoteEventFromConnectionId(connectionId: number, remoteEvent: RemoteEvent): void {\n runnerIframe.sendMessageToRunner({\n type: \"dispatchRemoteEventFromConnectionId\",\n connectionId,\n event: remoteEvent,\n });\n },\n dispose(): void {\n runnerIframe.dispose();\n },\n removeConnectedUserId(connectionId: number): void {\n runnerIframe.sendMessageToRunner({\n type: \"removeConnectedUserId\",\n connectionId,\n });\n },\n };\n return remoteObservableDOM;\n};\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,wBAAc,2CAAd;;;;;;ACQO,IAAM,eAAN,MAAmB;AAAA,EAKxB,YACE,yBACA,mBACA;AACA,SAAK,SAAS,SAAS,cAAc,QAAQ;AAC7C,SAAK,OAAO,aAAa,WAAW,eAAe;AACnD,SAAK,OAAO,MAAM,WAAW;AAC7B,SAAK,OAAO,MAAM,MAAM;AACxB,SAAK,OAAO,MAAM,OAAO;AACzB,SAAK,OAAO,MAAM,QAAQ;AAC1B,SAAK,OAAO,MAAM,SAAS;AAC3B,SAAK,OAAO,MAAM,SAAS;AAE3B,UAAM,kBAAoD;AAAA,MACxD,GAAG;AAAA,IACL;AACA,WAAO,gBAAgB;AAEvB,UAAM,OAAO,KAAK,KAAK,UAAU,eAAe,CAAC;AAEjD,UAAM,UAAU,UAAU,UAAU,SAAS,OAAO;AACpD,QAAI,SAAS;AAEX,eAAS,KAAK,OAAO,KAAK,MAAM;AAEhC,YAAM,aAAa,KAAK,OAAO,cAAe,SAAS;AACvD,YAAM,oBAAoB,SAAS,cAAc,QAAQ;AACzD,wBAAkB,YAAY,gBAAgB;AAC9C,iBAAW,OAAO,iBAAiB;AACnC,YAAM,sBAAsB,SAAS,cAAc,QAAQ;AAC3D,0BAAoB,YAAY;AAChC,iBAAW,OAAO,mBAAmB;AACrC,YAAM,gBAAgB,SAAS,cAAc,KAAK;AAClD,iBAAW,OAAO,aAAa;AAC/B,oBAAc,YAAY,wBAAwB;AAAA,IACpD,OAAO;AACL,WAAK,OAAO;AAAA,QACV;AAAA,QACA;AAAA,6BACqB;AAAA,gBACb;AAAA,QACR,wBAAwB;AAAA;AAAA,MAE1B;AACA,eAAS,KAAK,OAAO,KAAK,MAAM;AAAA,IAClC;AAEA,SAAK,oBAAoB;AAEzB,SAAK,sBAAsB,CAAC,MAAoB;AAC9C,UAAI,EAAE,WAAW,KAAK,OAAO,iBAAkB,WAAW,EAAE,WAAW,MAAO;AAC5E,cAAM,SAAS,KAAK,MAAM,EAAE,IAAI;AAChC,0BAAkB,MAAM;AAAA,MAC1B;AAAA,IACF;AACA,WAAO,iBAAiB,WAAW,KAAK,mBAAmB;AAAA,EAC7D;AAAA,EAEA,oBAAoB,SAAiC;AAEnD,SAAK,OAAO,cAAe,YAAY,KAAK,UAAU,OAAO,GAAG,GAAG;AAAA,EACrE;AAAA,EAEA,UAAU;AACR,WAAO,oBAAoB,WAAW,KAAK,mBAAmB;AAC9D,SAAK,OAAO,OAAO;AAAA,EACrB;AACF;;;AC/EA,+BAAsC;;;ACDtC,IAAM,eAAN,cAA2B,YAAY;AAAA,EAIrC,YACE,UACA,cACA;AACA,UAAM;AACN,SAAK,WAAW;AAChB,SAAK,eAAe;AAAA,EACtB;AAAA,EAEO,QAAQ;AACb,SAAK,cAAc,IAAI,WAAW,OAAO,CAAC;AAAA,EAC5C;AAAA,EAEO,iBACL,MACA,UACA,SACA;AACA,QAAI,SAAS,QAAQ;AACnB,eAAS,KAAK,IAAI,EAAE,IAAI,MAAM,MAAM,CAAC;AACrC;AAAA,IACF;AACA,UAAM,iBAAiB,MAAM,UAAU,OAAO;AAAA,EAChD;AAAA,EAEO,KAAK,MAAyD;AACnE,SAAK,aAAa,IAAI;AAAA,EACxB;AACF;AAEO,IAAM,gBAAN,MAAoB;AAAA,EAIzB,YAAY,UAAkB;AAC5B,SAAK,sBAAsB,IAAI,aAAa,UAAU,CAAC,SAAS;AAC9D,WAAK,oBAAoB;AAAA,QACvB,IAAI,aAAa,WAAW;AAAA,UAC1B;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAED,SAAK,sBAAsB,IAAI,aAAa,UAAU,CAAC,SAAS;AAC9D,WAAK,oBAAoB;AAAA,QACvB,IAAI,aAAa,WAAW;AAAA,UAC1B;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAAA,EACH;AACF;;;ADlDO,IAAM,8BAAN,MAAkC;AAAA,EAWvC,YAAY,sBAAsB,MAAM;AAPxC,SAAU,iBAIC;AAIT,SAAK,sBAAsB;AAC3B,SAAK,UAAU,SAAS,cAAc,KAAK;AAC3C,SAAK,QAAQ,MAAM,WAAW;AAC9B,SAAK,QAAQ,MAAM,QAAQ;AAC3B,SAAK,QAAQ,MAAM,SAAS;AAC5B,SAAK,QAAQ,MAAM,SAAS;AAE5B,SAAK,uBAAuB,SAAS,cAAc,KAAK;AACxD,SAAK,QAAQ,OAAO,KAAK,oBAAoB;AAAA,EAC/C;AAAA,EAEO,aAAa;AAClB,QAAI,CAAC,KAAK,gBAAgB;AACxB;AAAA,IACF;AACA,SAAK,eAAe,SAAS;AAAA,MAC3B,KAAK,eAAe,cAAc;AAAA,IACpC;AACA,SAAK,iBAAiB;AAAA,EACxB;AAAA,EAEO,UAAU;AACf,SAAK,WAAW;AAChB,SAAK,qBAAqB,OAAO;AACjC,SAAK,QAAQ,OAAO;AAAA,EACtB;AAAA,EAEO,QAAQA,WAA+C;AAC5D,QAAI,KAAK,gBAAgB;AACvB,WAAK,WAAW;AAAA,IAClB;AACA,UAAM,gBAAgB,IAAI,cAAc,oBAAoB;AAC5D,QAAI,oBAAiF;AACrF,UAAM,eAAe,CAAC,SAAsB,UAAuB;AACjE,UAAI,CAAC,mBAAmB;AACtB,cAAM,IAAI,MAAM,2BAA2B;AAAA,MAC7C;AACA,wBAAkB,SAAS,KAAK;AAAA,IAClC;AAEA,QAAI,KAAK,qBAAqB;AAC5B,WAAK,qBAAqB,iBAAiB,SAAS,CAAC,UAAiB;AACpE,qBAAa,MAAM,QAAuB,KAAoB;AAC9D,cAAM,gBAAgB;AACtB,cAAM,eAAe;AACrB,eAAO;AAAA,MACT,CAAC;AAAA,IACH;AAEA,UAAM,eAAe,IAAI;AAAA,MACvB;AAAA,MACA,MAAM,cAAc;AAAA,MACpB,KAAK;AAAA,IACP;AACA,wBAAoB,CAAC,SAAsB,UAAuB;AAChE,mBAAa,YAAY,SAAS,KAAK;AAAA,IACzC;AACA,IAAAA,UAAS,aAAa,cAAc,mBAA2C;AAC/E,SAAK,iBAAiB;AAAA,MACpB,UAAAA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;;;AEtEO,IAAM,6BAAmD,CAC9D,yBACA,aACG;AACH,QAAM,eAAe,IAAI;AAAA,IACvB;AAAA,IACA,CAAC,QAAkC;AACjC,UAAI,IAAI,SAAS,OAAO;AACtB,iBAAS,IAAI,OAAO;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AAEA,QAAM,sBAA8C;AAAA,IAClD,mBAAmB,cAA4B;AAC7C,mBAAa,oBAAoB;AAAA,QAC/B,MAAM;AAAA,QACN;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IACA,kBAAwB;AACtB,YAAM,IAAI,MAAM,iBAAiB;AAAA,IACnC;AAAA,IACA,oCAAoC,cAAsB,aAAgC;AACxF,mBAAa,oBAAoB;AAAA,QAC/B,MAAM;AAAA,QACN;AAAA,QACA,OAAO;AAAA,MACT,CAAC;AAAA,IACH;AAAA,IACA,UAAgB;AACd,mBAAa,QAAQ;AAAA,IACvB;AAAA,IACA,sBAAsB,cAA4B;AAChD,mBAAa,oBAAoB;AAAA,QAC/B,MAAM;AAAA,QACN;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACA,SAAO;AACT;",
|
|
6
|
+
"names": ["document"]
|
|
7
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mml-io/networked-dom-web-runner",
|
|
3
|
+
"version": "0.0.42",
|
|
4
|
+
"main": "./build/index.js",
|
|
5
|
+
"types": "./build/index.d.ts",
|
|
6
|
+
"files": [
|
|
7
|
+
"/build",
|
|
8
|
+
"/src"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"type-check": "tsc --noEmit",
|
|
12
|
+
"build": "node ./build.js --build",
|
|
13
|
+
"iterate": "node ./build.js --watch",
|
|
14
|
+
"npm-publish": "npm run build && publish-if-not-exists --access=public",
|
|
15
|
+
"lint": "eslint \"./src/**/*.{js,jsx,ts,tsx}\" --max-warnings 0",
|
|
16
|
+
"lint-fix": "eslint \"./src/**/*.{js,jsx,ts,tsx}\" --fix",
|
|
17
|
+
"test": "npm run build && jest",
|
|
18
|
+
"test-iterate": "jest --watch"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@mml-io/networked-dom-web": "^0.0.42"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@mml-io/networked-dom-web-runner-iframe": "file:../networked-dom-web-runner-iframe",
|
|
25
|
+
"@types/three": "0.151.0",
|
|
26
|
+
"jest-canvas-mock": "2.5.0",
|
|
27
|
+
"jest-environment-jsdom": "29.5.0",
|
|
28
|
+
"jest-expect-message": "1.1.3",
|
|
29
|
+
"standardized-audio-context-mock": "9.6.17"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
class WebsocketEnd extends EventTarget {
|
|
2
|
+
private readonly sendCallback: (data: string | ArrayBufferLike | Blob | ArrayBufferView) => void;
|
|
3
|
+
public readonly protocol: string;
|
|
4
|
+
|
|
5
|
+
constructor(
|
|
6
|
+
protocol: string,
|
|
7
|
+
sendCallback: (data: string | ArrayBufferLike | Blob | ArrayBufferView) => void,
|
|
8
|
+
) {
|
|
9
|
+
super();
|
|
10
|
+
this.protocol = protocol;
|
|
11
|
+
this.sendCallback = sendCallback;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public close() {
|
|
15
|
+
this.dispatchEvent(new CloseEvent("close"));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public addEventListener<K extends keyof WebSocketEventMap>(
|
|
19
|
+
type: K,
|
|
20
|
+
listener: (this: WebSocket, ev: WebSocketEventMap[K]) => any,
|
|
21
|
+
options?: boolean | AddEventListenerOptions,
|
|
22
|
+
) {
|
|
23
|
+
if (type === "open") {
|
|
24
|
+
listener.bind(this)(new Event("open"));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
super.addEventListener(type, listener, options);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public send(data: string | ArrayBufferLike | Blob | ArrayBufferView) {
|
|
31
|
+
this.sendCallback(data);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class FakeWebsocket {
|
|
36
|
+
public clientSideWebsocket: WebsocketEnd;
|
|
37
|
+
public serverSideWebsocket: WebsocketEnd;
|
|
38
|
+
|
|
39
|
+
constructor(protocol: string) {
|
|
40
|
+
this.clientSideWebsocket = new WebsocketEnd(protocol, (data) => {
|
|
41
|
+
this.serverSideWebsocket.dispatchEvent(
|
|
42
|
+
new MessageEvent("message", {
|
|
43
|
+
data,
|
|
44
|
+
}),
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
this.serverSideWebsocket = new WebsocketEnd(protocol, (data) => {
|
|
49
|
+
this.clientSideWebsocket.dispatchEvent(
|
|
50
|
+
new MessageEvent("message", {
|
|
51
|
+
data,
|
|
52
|
+
}),
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ObservableDomFactory } from "@mml-io/networked-dom-document";
|
|
2
|
+
import {
|
|
3
|
+
ObservableDomInterface,
|
|
4
|
+
ObservableDomMessage,
|
|
5
|
+
ObservableDOMParameters,
|
|
6
|
+
RemoteEvent,
|
|
7
|
+
} from "@mml-io/observable-dom-common";
|
|
8
|
+
|
|
9
|
+
import { FromInstanceMessageTypes } from "./message-types";
|
|
10
|
+
import { RunnerIframe } from "./RunnerIframe";
|
|
11
|
+
|
|
12
|
+
export const IframeObservableDOMFactory: ObservableDomFactory = (
|
|
13
|
+
observableDOMParameters: ObservableDOMParameters,
|
|
14
|
+
callback: (message: ObservableDomMessage) => void,
|
|
15
|
+
) => {
|
|
16
|
+
const runnerIframe = new RunnerIframe(
|
|
17
|
+
observableDOMParameters,
|
|
18
|
+
(msg: FromInstanceMessageTypes) => {
|
|
19
|
+
if (msg.type === "dom") {
|
|
20
|
+
callback(msg.message);
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const remoteObservableDOM: ObservableDomInterface = {
|
|
26
|
+
addConnectedUserId(connectionId: number): void {
|
|
27
|
+
runnerIframe.sendMessageToRunner({
|
|
28
|
+
type: "addConnectedUserId",
|
|
29
|
+
connectionId,
|
|
30
|
+
});
|
|
31
|
+
},
|
|
32
|
+
addIPCWebsocket(): void {
|
|
33
|
+
throw new Error("Not implemented");
|
|
34
|
+
},
|
|
35
|
+
dispatchRemoteEventFromConnectionId(connectionId: number, remoteEvent: RemoteEvent): void {
|
|
36
|
+
runnerIframe.sendMessageToRunner({
|
|
37
|
+
type: "dispatchRemoteEventFromConnectionId",
|
|
38
|
+
connectionId,
|
|
39
|
+
event: remoteEvent,
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
dispose(): void {
|
|
43
|
+
runnerIframe.dispose();
|
|
44
|
+
},
|
|
45
|
+
removeConnectedUserId(connectionId: number): void {
|
|
46
|
+
runnerIframe.sendMessageToRunner({
|
|
47
|
+
type: "removeConnectedUserId",
|
|
48
|
+
connectionId,
|
|
49
|
+
});
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
return remoteObservableDOM;
|
|
53
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { EditableNetworkedDOM, NetworkedDOM } from "@mml-io/networked-dom-document";
|
|
2
|
+
import { NetworkedDOMWebsocket } from "@mml-io/networked-dom-web";
|
|
3
|
+
|
|
4
|
+
import { FakeWebsocket } from "./FakeWebsocket";
|
|
5
|
+
|
|
6
|
+
export class NetworkedDOMWebRunnerClient {
|
|
7
|
+
public readonly element: HTMLDivElement;
|
|
8
|
+
protected remoteDocumentHolder: HTMLElement;
|
|
9
|
+
|
|
10
|
+
protected connectedState: {
|
|
11
|
+
document: NetworkedDOM | EditableNetworkedDOM;
|
|
12
|
+
domWebsocket: NetworkedDOMWebsocket;
|
|
13
|
+
fakeWebsocket: FakeWebsocket;
|
|
14
|
+
} | null = null;
|
|
15
|
+
private enableEventHandling: boolean;
|
|
16
|
+
|
|
17
|
+
constructor(enableEventHandling = true) {
|
|
18
|
+
this.enableEventHandling = enableEventHandling;
|
|
19
|
+
this.element = document.createElement("div");
|
|
20
|
+
this.element.style.position = "relative";
|
|
21
|
+
this.element.style.width = "100%";
|
|
22
|
+
this.element.style.height = "100%";
|
|
23
|
+
this.element.style.border = "1px solid black";
|
|
24
|
+
|
|
25
|
+
this.remoteDocumentHolder = document.createElement("div");
|
|
26
|
+
this.element.append(this.remoteDocumentHolder);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public disconnect() {
|
|
30
|
+
if (!this.connectedState) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
this.connectedState.document.removeWebSocket(
|
|
34
|
+
this.connectedState.fakeWebsocket.serverSideWebsocket as unknown as WebSocket,
|
|
35
|
+
);
|
|
36
|
+
this.connectedState = null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public dispose() {
|
|
40
|
+
this.disconnect();
|
|
41
|
+
this.remoteDocumentHolder.remove();
|
|
42
|
+
this.element.remove();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public connect(document: NetworkedDOM | EditableNetworkedDOM) {
|
|
46
|
+
if (this.connectedState) {
|
|
47
|
+
this.disconnect();
|
|
48
|
+
}
|
|
49
|
+
const fakeWebsocket = new FakeWebsocket("networked-dom-v0.1");
|
|
50
|
+
let overriddenHandler: ((element: HTMLElement, event: CustomEvent) => void) | null = null;
|
|
51
|
+
const eventHandler = (element: HTMLElement, event: CustomEvent) => {
|
|
52
|
+
if (!overriddenHandler) {
|
|
53
|
+
throw new Error("overriddenHandler not set");
|
|
54
|
+
}
|
|
55
|
+
overriddenHandler(element, event);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
if (this.enableEventHandling) {
|
|
59
|
+
this.remoteDocumentHolder.addEventListener("click", (event: Event) => {
|
|
60
|
+
eventHandler(event.target as HTMLElement, event as CustomEvent);
|
|
61
|
+
event.stopPropagation();
|
|
62
|
+
event.preventDefault();
|
|
63
|
+
return false;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const domWebsocket = new NetworkedDOMWebsocket(
|
|
68
|
+
"ws://localhost",
|
|
69
|
+
() => fakeWebsocket.clientSideWebsocket as unknown as WebSocket,
|
|
70
|
+
this.remoteDocumentHolder,
|
|
71
|
+
);
|
|
72
|
+
overriddenHandler = (element: HTMLElement, event: CustomEvent) => {
|
|
73
|
+
domWebsocket.handleEvent(element, event);
|
|
74
|
+
};
|
|
75
|
+
document.addWebSocket(fakeWebsocket.serverSideWebsocket as unknown as WebSocket);
|
|
76
|
+
this.connectedState = {
|
|
77
|
+
document,
|
|
78
|
+
fakeWebsocket,
|
|
79
|
+
domWebsocket,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { ObservableDOMParameters } from "@mml-io/observable-dom-common";
|
|
2
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
3
|
+
// @ts-ignore
|
|
4
|
+
// eslint-disable-next-line import/no-unresolved
|
|
5
|
+
import runnerText from "runner-iframe-js-text";
|
|
6
|
+
|
|
7
|
+
import { FromInstanceMessageTypes, ToInstanceMessageTypes } from "./message-types";
|
|
8
|
+
|
|
9
|
+
export class RunnerIframe {
|
|
10
|
+
private iframe: HTMLIFrameElement;
|
|
11
|
+
private onMessageCallback: (msg: FromInstanceMessageTypes) => void;
|
|
12
|
+
private postMessageListener: (msg: MessageEvent) => void;
|
|
13
|
+
|
|
14
|
+
constructor(
|
|
15
|
+
observableDOMParameters: ObservableDOMParameters,
|
|
16
|
+
onMessageCallback: (msg: FromInstanceMessageTypes) => void,
|
|
17
|
+
) {
|
|
18
|
+
this.iframe = document.createElement("iframe");
|
|
19
|
+
this.iframe.setAttribute("sandbox", "allow-scripts");
|
|
20
|
+
this.iframe.style.position = "fixed";
|
|
21
|
+
this.iframe.style.top = "0";
|
|
22
|
+
this.iframe.style.left = "0";
|
|
23
|
+
this.iframe.style.width = "0";
|
|
24
|
+
this.iframe.style.height = "0";
|
|
25
|
+
this.iframe.style.border = "none";
|
|
26
|
+
|
|
27
|
+
const paramsMinusCode: Partial<ObservableDOMParameters> = {
|
|
28
|
+
...observableDOMParameters,
|
|
29
|
+
};
|
|
30
|
+
delete paramsMinusCode.htmlContents;
|
|
31
|
+
|
|
32
|
+
const args = btoa(JSON.stringify(paramsMinusCode));
|
|
33
|
+
|
|
34
|
+
const isJSDOM = navigator.userAgent.includes("jsdom");
|
|
35
|
+
if (isJSDOM) {
|
|
36
|
+
// srcdoc not supported, so we have to append elements to the iframe's document
|
|
37
|
+
document.body.append(this.iframe);
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
39
|
+
const iframeBody = this.iframe.contentWindow!.document.body;
|
|
40
|
+
const argsScriptElement = document.createElement("script");
|
|
41
|
+
argsScriptElement.innerHTML = `window.args="${args}";`;
|
|
42
|
+
iframeBody.append(argsScriptElement);
|
|
43
|
+
const runnerScriptElement = document.createElement("script");
|
|
44
|
+
runnerScriptElement.innerHTML = runnerText;
|
|
45
|
+
iframeBody.append(runnerScriptElement);
|
|
46
|
+
const contentHolder = document.createElement("div");
|
|
47
|
+
iframeBody.append(contentHolder);
|
|
48
|
+
contentHolder.innerHTML = observableDOMParameters.htmlContents;
|
|
49
|
+
} else {
|
|
50
|
+
this.iframe.setAttribute(
|
|
51
|
+
"srcdoc",
|
|
52
|
+
`
|
|
53
|
+
<script>window.args="${args}";</script>
|
|
54
|
+
<script>${runnerText}</script>
|
|
55
|
+
${observableDOMParameters.htmlContents}
|
|
56
|
+
`,
|
|
57
|
+
);
|
|
58
|
+
document.body.append(this.iframe);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
this.onMessageCallback = onMessageCallback;
|
|
62
|
+
|
|
63
|
+
this.postMessageListener = (e: MessageEvent) => {
|
|
64
|
+
if (e.source === this.iframe.contentWindow || (isJSDOM && e.source === null)) {
|
|
65
|
+
const parsed = JSON.parse(e.data) as FromInstanceMessageTypes;
|
|
66
|
+
onMessageCallback(parsed);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
window.addEventListener("message", this.postMessageListener);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
sendMessageToRunner(message: ToInstanceMessageTypes) {
|
|
73
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
74
|
+
this.iframe.contentWindow!.postMessage(JSON.stringify(message), "*");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
dispose() {
|
|
78
|
+
window.removeEventListener("message", this.postMessageListener);
|
|
79
|
+
this.iframe.remove();
|
|
80
|
+
}
|
|
81
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { RemoteEvent } from "@mml-io/networked-dom-protocol";
|
|
2
|
+
import { ObservableDomMessage } from "@mml-io/observable-dom-common";
|
|
3
|
+
|
|
4
|
+
export type AddConnectedUserIdMessage = {
|
|
5
|
+
type: "addConnectedUserId";
|
|
6
|
+
connectionId: number;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type RemoveConnectedUserIdMessage = {
|
|
10
|
+
type: "removeConnectedUserId";
|
|
11
|
+
connectionId: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type DispatchRemoteEventFromConnectionIdMessage = {
|
|
15
|
+
type: "dispatchRemoteEventFromConnectionId";
|
|
16
|
+
connectionId: number;
|
|
17
|
+
event: RemoteEvent;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type ToInstanceMessageTypes =
|
|
21
|
+
| AddConnectedUserIdMessage
|
|
22
|
+
| RemoveConnectedUserIdMessage
|
|
23
|
+
| DispatchRemoteEventFromConnectionIdMessage;
|
|
24
|
+
|
|
25
|
+
type DOMMessage = {
|
|
26
|
+
type: "dom";
|
|
27
|
+
message: ObservableDomMessage;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type FromInstanceMessageTypes = DOMMessage;
|