@mml-io/networked-dom-web 0.1.0 → 0.1.2

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.js CHANGED
@@ -90,6 +90,24 @@ var NetworkedDOMWebsocketStatus = /* @__PURE__ */ ((NetworkedDOMWebsocketStatus2
90
90
  NetworkedDOMWebsocketStatus2[NetworkedDOMWebsocketStatus2["Disconnected"] = 3] = "Disconnected";
91
91
  return NetworkedDOMWebsocketStatus2;
92
92
  })(NetworkedDOMWebsocketStatus || {});
93
+ function isHTMLElement(node) {
94
+ if (node instanceof HTMLElement) {
95
+ return true;
96
+ }
97
+ if (!this.parentElement.ownerDocument.defaultView) {
98
+ return false;
99
+ }
100
+ return node instanceof this.parentElement.ownerDocument.defaultView.HTMLElement;
101
+ }
102
+ function isText(node) {
103
+ if (node instanceof Text) {
104
+ return true;
105
+ }
106
+ if (!this.parentElement.ownerDocument.defaultView) {
107
+ return false;
108
+ }
109
+ return node instanceof this.parentElement.ownerDocument.defaultView.Text;
110
+ }
93
111
  var NetworkedDOMWebsocket = class {
94
112
  constructor(url, websocketFactory, parentElement, timeCallback, statusUpdateCallback) {
95
113
  this.idToElement = /* @__PURE__ */ new Map();
@@ -224,7 +242,7 @@ var NetworkedDOMWebsocket = class {
224
242
  if (!element) {
225
243
  throw new Error("Snapshot element not created");
226
244
  }
227
- if (!(element instanceof HTMLElement)) {
245
+ if (!isHTMLElement(element)) {
228
246
  throw new Error("Snapshot element is not an HTMLElement");
229
247
  }
230
248
  this.currentRoot = element;
@@ -290,7 +308,7 @@ var NetworkedDOMWebsocket = class {
290
308
  if (!node) {
291
309
  throw new Error("No node found for textChanged message");
292
310
  }
293
- if (!(node instanceof Text)) {
311
+ if (!isText(node)) {
294
312
  throw new Error("Node for textChanged message is not a Text node");
295
313
  }
296
314
  node.textContent = text;
@@ -308,7 +326,7 @@ var NetworkedDOMWebsocket = class {
308
326
  if (!parent.isConnected) {
309
327
  console.error("Parent is not connected", parent);
310
328
  }
311
- if (!(parent instanceof HTMLElement)) {
329
+ if (!isHTMLElement(parent)) {
312
330
  throw new Error("Parent is not an HTMLElement (that supports children)");
313
331
  }
314
332
  let previousElement;
@@ -341,7 +359,7 @@ var NetworkedDOMWebsocket = class {
341
359
  this.elementToId.delete(childElement);
342
360
  this.idToElement.delete(removedNode);
343
361
  parent.removeChild(childElement);
344
- if (childElement instanceof HTMLElement) {
362
+ if (isHTMLElement(childElement)) {
345
363
  this.removeChildElementIds(childElement);
346
364
  }
347
365
  }
@@ -367,7 +385,7 @@ var NetworkedDOMWebsocket = class {
367
385
  }
368
386
  const element = this.idToElement.get(nodeId);
369
387
  if (element) {
370
- if (element instanceof HTMLElement) {
388
+ if (isHTMLElement(element)) {
371
389
  if (newValue === null) {
372
390
  element.removeAttribute(attribute);
373
391
  } else {
@@ -410,7 +428,13 @@ var NetworkedDOMWebsocket = class {
410
428
  this.elementToId.set(textNode, nodeId);
411
429
  return textNode;
412
430
  }
413
- const element = document.createElement(tag);
431
+ let element;
432
+ try {
433
+ element = document.createElement(tag);
434
+ } catch (e) {
435
+ console.error(`Error creating element: (${tag})`, e);
436
+ element = document.createElement("div");
437
+ }
414
438
  this.idToElement.set(nodeId, element);
415
439
  this.elementToId.set(element, nodeId);
416
440
  for (const key in attributes) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts", "../src/DOMSanitizer.ts", "../src/NetworkedDOMWebsocket.ts"],
4
- "sourcesContent": ["export * from \"./NetworkedDOMWebsocket\";\nexport * from \"./DOMSanitizer\";\n", "export class DOMSanitizer {\n static sanitise(node: HTMLElement) {\n if (node.getAttributeNames) {\n for (const attr of node.getAttributeNames()) {\n if (!DOMSanitizer.IsValidAttributeName(attr)) {\n node.removeAttribute(attr);\n }\n }\n }\n if (node.nodeName === \"SCRIPT\") {\n // set text to empty string\n node.innerText = \"\";\n } else {\n if (node.getAttributeNames) {\n for (const attr of node.getAttributeNames()) {\n if (!DOMSanitizer.shouldAcceptAttribute(attr)) {\n node.removeAttribute(attr);\n }\n }\n }\n for (let i = 0; i < node.childNodes.length; i++) {\n DOMSanitizer.sanitise(node.childNodes[i] as HTMLElement);\n }\n }\n }\n\n static IsASCIIDigit(c: string): boolean {\n return c >= \"0\" && c <= \"9\";\n }\n\n static IsASCIIAlpha(c: string) {\n return c >= \"a\" && c <= \"z\";\n }\n\n static IsValidAttributeName(characters: string): boolean {\n const c = characters[0];\n if (!(DOMSanitizer.IsASCIIAlpha(c) || c === \":\" || c === \"_\")) {\n return false;\n }\n\n for (let i = 1; i < characters.length; i++) {\n const c = characters[i];\n if (\n !(\n DOMSanitizer.IsASCIIDigit(c) ||\n DOMSanitizer.IsASCIIAlpha(c) ||\n c === \":\" ||\n c === \"_\" ||\n c === \"-\" ||\n c === \".\"\n )\n ) {\n return false;\n }\n }\n\n return true;\n }\n\n static shouldAcceptAttribute(attribute: string) {\n if (!DOMSanitizer.IsValidAttributeName(attribute)) {\n console.warn(\"Invalid attribute name\", attribute);\n return false;\n }\n\n // TODO - this might be overly restrictive - apologies to someone that finds this because you have a non-event attribute filtered by this\n return !attribute.startsWith(\"on\");\n }\n}\n", "import {\n AttributeChangedDiff,\n ChildrenChangedDiff,\n ClientMessage,\n NodeDescription,\n RemoteEvent,\n ServerMessage,\n TextChangedDiff,\n} from \"@mml-io/networked-dom-protocol\";\n\nimport { DOMSanitizer } from \"./DOMSanitizer\";\n\nconst websocketProtocol = \"networked-dom-v0.1\";\n\nconst startingBackoffTimeMilliseconds = 100;\nconst maximumBackoffTimeMilliseconds = 10000;\nconst maximumWebsocketConnectionTimeout = 5000;\n\nexport type NetworkedDOMWebsocketFactory = (url: string) => WebSocket;\n\nexport enum NetworkedDOMWebsocketStatus {\n Connecting,\n Connected,\n Reconnecting,\n Disconnected,\n}\n\nexport class NetworkedDOMWebsocket {\n private idToElement = new Map<number, Node>();\n private elementToId = new Map<Node, number>();\n private websocket: WebSocket | null = null;\n private currentRoot: HTMLElement | null = null;\n\n private url: string;\n private websocketFactory: NetworkedDOMWebsocketFactory;\n private parentElement: HTMLElement;\n private timeCallback: (time: number) => void;\n private statusUpdateCallback: (status: NetworkedDOMWebsocketStatus) => void;\n private stopped = false;\n private backoffTime = startingBackoffTimeMilliseconds;\n private status: NetworkedDOMWebsocketStatus | null = null;\n\n public static createWebSocket(url: string): WebSocket {\n return new WebSocket(url, [websocketProtocol]);\n }\n\n constructor(\n url: string,\n websocketFactory: NetworkedDOMWebsocketFactory,\n parentElement: HTMLElement,\n timeCallback?: (time: number) => void,\n statusUpdateCallback?: (status: NetworkedDOMWebsocketStatus) => void,\n ) {\n this.url = url;\n this.websocketFactory = websocketFactory;\n this.parentElement = parentElement;\n this.timeCallback =\n timeCallback ||\n (() => {\n // no-op\n });\n this.statusUpdateCallback =\n statusUpdateCallback ||\n (() => {\n // no-op\n });\n this.setStatus(NetworkedDOMWebsocketStatus.Connecting);\n this.startWebSocketConnectionAttempt();\n }\n\n private setStatus(status: NetworkedDOMWebsocketStatus) {\n if (this.status !== status) {\n this.status = status;\n this.statusUpdateCallback(status);\n }\n }\n\n private createWebsocketWithTimeout(timeout: number): Promise<WebSocket> {\n return new Promise((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n reject(new Error(\"websocket connection timed out\"));\n }, timeout);\n const websocket = this.websocketFactory(this.url);\n websocket.addEventListener(\"open\", () => {\n clearTimeout(timeoutId);\n\n this.websocket = websocket;\n\n websocket.addEventListener(\"message\", (event) => {\n if (websocket !== this.websocket) {\n console.log(\"Ignoring websocket message event because it is no longer current\");\n websocket.close();\n return;\n }\n this.handleIncomingWebsocketMessage(event);\n });\n\n const onWebsocketClose = async () => {\n const hadContents = this.currentRoot !== null;\n this.clearContents();\n if (this.stopped) {\n // This closing is expected. The client closed the websocket.\n this.setStatus(NetworkedDOMWebsocketStatus.Disconnected);\n return;\n }\n if (!hadContents) {\n // The websocket did not deliver any contents. It may have been successfully opened, but immediately closed. This client should back off to prevent this happening in a rapid loop.\n await this.waitBackoffTime();\n }\n // The websocket closed unexpectedly. Try to reconnect.\n this.setStatus(NetworkedDOMWebsocketStatus.Reconnecting);\n this.startWebSocketConnectionAttempt();\n };\n\n websocket.addEventListener(\"close\", (e) => {\n if (websocket !== this.websocket) {\n console.warn(\"Ignoring websocket close event because it is no longer current\");\n return;\n }\n console.log(\"NetworkedDOMWebsocket close\", e);\n onWebsocketClose();\n });\n websocket.addEventListener(\"error\", (e) => {\n if (websocket !== this.websocket) {\n console.log(\"Ignoring websocket error event because it is no longer current\");\n return;\n }\n console.error(\"NetworkedDOMWebsocket error\", e);\n onWebsocketClose();\n });\n\n resolve(websocket);\n });\n websocket.addEventListener(\"error\", (e) => {\n clearTimeout(timeoutId);\n reject(e);\n });\n });\n }\n\n private async waitBackoffTime(): Promise<void> {\n console.warn(`Websocket connection to '${this.url}' failed: retrying in ${this.backoffTime}ms`);\n await new Promise((resolve) => setTimeout(resolve, this.backoffTime));\n this.backoffTime = Math.min(\n // Introduce a small amount of randomness to prevent clients from retrying in lockstep\n this.backoffTime * (1.5 + Math.random() * 0.5),\n maximumBackoffTimeMilliseconds,\n );\n }\n\n private async startWebSocketConnectionAttempt() {\n if (this.stopped) {\n return;\n }\n // eslint-disable-next-line no-constant-condition\n while (true) {\n if (this.stopped) {\n return;\n }\n try {\n await this.createWebsocketWithTimeout(maximumWebsocketConnectionTimeout);\n break;\n } catch (e) {\n // Connection failed, retry with backoff\n this.setStatus(NetworkedDOMWebsocketStatus.Reconnecting);\n await this.waitBackoffTime();\n }\n }\n }\n\n private handleIncomingWebsocketMessage(event: MessageEvent) {\n const messages = JSON.parse(event.data) as Array<ServerMessage>;\n for (const message of messages) {\n if (message.type === \"error\") {\n console.error(\"Error from server\", message);\n } else if (message.type === \"warning\") {\n console.warn(\"Warning from server\", message);\n } else {\n if (message.documentTime) {\n if (this.timeCallback) {\n this.timeCallback(message.documentTime);\n }\n }\n if (message.type === \"snapshot\") {\n // This websocket is successfully connected. Reset the backoff time.\n this.backoffTime = startingBackoffTimeMilliseconds;\n this.setStatus(NetworkedDOMWebsocketStatus.Connected);\n\n if (this.currentRoot) {\n this.currentRoot.remove();\n this.currentRoot = null;\n this.elementToId.clear();\n this.idToElement.clear();\n }\n\n // create a tree of DOM elements\n // NOTE: the MLElement contructors are not executed during this stage\n const element = this.handleNewElement(message.snapshot);\n if (!element) {\n throw new Error(\"Snapshot element not created\");\n }\n if (!(element instanceof HTMLElement)) {\n throw new Error(\"Snapshot element is not an HTMLElement\");\n }\n this.currentRoot = element;\n // appending to the tree causes MElements to be constructed\n this.parentElement.append(element);\n } else if (message.type === \"attributeChange\") {\n this.handleAttributeChange(message);\n } else if (message.type === \"childrenChanged\") {\n this.handleChildrenChanged(message);\n } else if (message.type === \"textChanged\") {\n this.handleTextChanged(message);\n } else if (message.type === \"ping\") {\n this.send({\n type: \"pong\",\n pong: message.ping,\n });\n } else {\n console.warn(\"unknown message type\", message);\n }\n }\n }\n }\n\n public stop() {\n this.stopped = true;\n if (this.websocket !== null) {\n this.websocket.close();\n this.websocket = null;\n }\n }\n\n public handleEvent(element: HTMLElement, event: CustomEvent<{ element: HTMLElement }>) {\n const nodeId = this.elementToId.get(element);\n if (nodeId === undefined || nodeId === null) {\n throw new Error(\"Element not found\");\n }\n\n console.log(\n `Sending event to websocket: \"${event.type}\" on node: ${nodeId} type: ${element.tagName}`,\n );\n\n const detailWithoutElement: Partial<typeof event.detail> = {\n ...event.detail,\n };\n delete detailWithoutElement.element;\n\n const remoteEvent: RemoteEvent = {\n type: \"event\",\n nodeId,\n name: event.type,\n bubbles: event.bubbles,\n params: detailWithoutElement,\n };\n\n this.send(remoteEvent);\n }\n\n private send(fromClientMessage: ClientMessage) {\n if (!this.websocket) {\n throw new Error(\"No websocket created\");\n }\n this.websocket.send(JSON.stringify(fromClientMessage));\n }\n\n private handleTextChanged(message: TextChangedDiff) {\n const { nodeId, text } = message;\n\n if (nodeId === undefined || nodeId === null) {\n console.warn(\"No nodeId in textChanged message\");\n return;\n }\n const node = this.idToElement.get(nodeId);\n if (!node) {\n throw new Error(\"No node found for textChanged message\");\n }\n if (!(node instanceof Text)) {\n throw new Error(\"Node for textChanged message is not a Text node\");\n }\n node.textContent = text;\n }\n\n private handleChildrenChanged(message: ChildrenChangedDiff) {\n const { nodeId, addedNodes, removedNodes, previousNodeId } = message;\n if (nodeId === undefined || nodeId === null) {\n console.warn(\"No nodeId in childrenChanged message\");\n return;\n }\n const parent = this.idToElement.get(nodeId);\n if (!parent) {\n throw new Error(\"No parent found for childrenChanged message\");\n }\n if (!parent.isConnected) {\n console.error(\"Parent is not connected\", parent);\n }\n if (!(parent instanceof HTMLElement)) {\n throw new Error(\"Parent is not an HTMLElement (that supports children)\");\n }\n let previousElement;\n if (previousNodeId) {\n previousElement = this.idToElement.get(previousNodeId);\n if (!previousElement) {\n throw new Error(\"No previous element found for childrenChanged message\");\n }\n }\n\n for (const addedNode of addedNodes) {\n const childElement = this.handleNewElement(addedNode);\n if (childElement) {\n if (previousElement) {\n const nextElement = previousElement.nextSibling;\n if (nextElement) {\n parent.insertBefore(childElement, nextElement);\n } else {\n parent.append(childElement);\n }\n } else {\n parent.append(childElement);\n }\n }\n }\n for (const removedNode of removedNodes) {\n const childElement = this.idToElement.get(removedNode);\n if (!childElement) {\n throw new Error(`Child element not found: ${removedNode}`);\n }\n this.elementToId.delete(childElement);\n this.idToElement.delete(removedNode);\n parent.removeChild(childElement);\n if (childElement instanceof HTMLElement) {\n // If child is capable of supporting children then remove any that exist\n this.removeChildElementIds(childElement);\n }\n }\n }\n\n private removeChildElementIds(parent: HTMLElement) {\n for (let i = 0; i < parent.children.length; i++) {\n const child = parent.children[i];\n const childId = this.elementToId.get(child as HTMLElement);\n if (!childId) {\n console.error(\"Inner child of removed element had no id\", child);\n } else {\n this.elementToId.delete(child);\n this.idToElement.delete(childId);\n }\n this.removeChildElementIds(child as HTMLElement);\n }\n }\n\n private handleAttributeChange(message: AttributeChangedDiff) {\n const { nodeId, attribute, newValue } = message;\n if (nodeId === undefined || nodeId === null) {\n console.warn(\"No nodeId in attributeChange message\");\n return;\n }\n const element = this.idToElement.get(nodeId);\n if (element) {\n if (element instanceof HTMLElement) {\n if (newValue === null) {\n element.removeAttribute(attribute);\n } else {\n if (DOMSanitizer.shouldAcceptAttribute(attribute)) {\n element.setAttribute(attribute, newValue);\n }\n }\n } else {\n console.error(\"Element is not an HTMLElement and cannot support attributes\", element);\n }\n } else {\n console.error(\"No element found for attributeChange message\");\n }\n }\n\n private handleNewElement(message: NodeDescription): Node | null {\n if (message.type === \"text\") {\n const { nodeId, text } = message;\n const textNode = document.createTextNode(\"\");\n textNode.textContent = text;\n this.idToElement.set(nodeId, textNode);\n this.elementToId.set(textNode, nodeId);\n return textNode;\n }\n const { tag, nodeId, attributes, children, text } = message;\n if (nodeId === undefined || nodeId === null) {\n console.warn(\"No nodeId in handleNewElement message\", message);\n return null;\n }\n if (this.idToElement.has(nodeId)) {\n console.error(\n \"Received nodeId to add that is already present\",\n nodeId,\n this.idToElement.get(nodeId),\n );\n }\n if (tag === \"#text\") {\n const textNode = document.createTextNode(\"\");\n textNode.textContent = text || null;\n this.idToElement.set(nodeId, textNode);\n this.elementToId.set(textNode, nodeId);\n return textNode;\n }\n\n const element = document.createElement(tag);\n this.idToElement.set(nodeId, element);\n this.elementToId.set(element, nodeId);\n for (const key in attributes) {\n if (DOMSanitizer.shouldAcceptAttribute(key)) {\n const value = attributes[key];\n element.setAttribute(key, value);\n }\n }\n if (children) {\n for (const child of children) {\n const childElement = this.handleNewElement(child);\n if (childElement) {\n element.append(childElement);\n }\n }\n }\n return element;\n }\n\n private clearContents() {\n this.idToElement.clear();\n this.elementToId.clear();\n if (this.currentRoot) {\n this.currentRoot.remove();\n this.currentRoot = null;\n }\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["export * from \"./NetworkedDOMWebsocket\";\nexport * from \"./DOMSanitizer\";\n", "export class DOMSanitizer {\n static sanitise(node: HTMLElement) {\n if (node.getAttributeNames) {\n for (const attr of node.getAttributeNames()) {\n if (!DOMSanitizer.IsValidAttributeName(attr)) {\n node.removeAttribute(attr);\n }\n }\n }\n if (node.nodeName === \"SCRIPT\") {\n // set text to empty string\n node.innerText = \"\";\n } else {\n if (node.getAttributeNames) {\n for (const attr of node.getAttributeNames()) {\n if (!DOMSanitizer.shouldAcceptAttribute(attr)) {\n node.removeAttribute(attr);\n }\n }\n }\n for (let i = 0; i < node.childNodes.length; i++) {\n DOMSanitizer.sanitise(node.childNodes[i] as HTMLElement);\n }\n }\n }\n\n static IsASCIIDigit(c: string): boolean {\n return c >= \"0\" && c <= \"9\";\n }\n\n static IsASCIIAlpha(c: string) {\n return c >= \"a\" && c <= \"z\";\n }\n\n static IsValidAttributeName(characters: string): boolean {\n const c = characters[0];\n if (!(DOMSanitizer.IsASCIIAlpha(c) || c === \":\" || c === \"_\")) {\n return false;\n }\n\n for (let i = 1; i < characters.length; i++) {\n const c = characters[i];\n if (\n !(\n DOMSanitizer.IsASCIIDigit(c) ||\n DOMSanitizer.IsASCIIAlpha(c) ||\n c === \":\" ||\n c === \"_\" ||\n c === \"-\" ||\n c === \".\"\n )\n ) {\n return false;\n }\n }\n\n return true;\n }\n\n static shouldAcceptAttribute(attribute: string) {\n if (!DOMSanitizer.IsValidAttributeName(attribute)) {\n console.warn(\"Invalid attribute name\", attribute);\n return false;\n }\n\n // TODO - this might be overly restrictive - apologies to someone that finds this because you have a non-event attribute filtered by this\n return !attribute.startsWith(\"on\");\n }\n}\n", "import {\n AttributeChangedDiff,\n ChildrenChangedDiff,\n ClientMessage,\n NodeDescription,\n RemoteEvent,\n ServerMessage,\n TextChangedDiff,\n} from \"@mml-io/networked-dom-protocol\";\n\nimport { DOMSanitizer } from \"./DOMSanitizer\";\n\nconst websocketProtocol = \"networked-dom-v0.1\";\n\nconst startingBackoffTimeMilliseconds = 100;\nconst maximumBackoffTimeMilliseconds = 10000;\nconst maximumWebsocketConnectionTimeout = 5000;\n\nexport type NetworkedDOMWebsocketFactory = (url: string) => WebSocket;\n\nexport enum NetworkedDOMWebsocketStatus {\n Connecting,\n Connected,\n Reconnecting,\n Disconnected,\n}\n\nfunction isHTMLElement(node: unknown): node is HTMLElement {\n if (node instanceof HTMLElement) {\n return true;\n }\n if (!this.parentElement.ownerDocument.defaultView) {\n return false;\n }\n return node instanceof this.parentElement.ownerDocument.defaultView.HTMLElement;\n}\n\nfunction isText(node: unknown): node is Text {\n if (node instanceof Text) {\n return true;\n }\n if (!this.parentElement.ownerDocument.defaultView) {\n return false;\n }\n return node instanceof this.parentElement.ownerDocument.defaultView.Text;\n}\n\nexport class NetworkedDOMWebsocket {\n private idToElement = new Map<number, Node>();\n private elementToId = new Map<Node, number>();\n private websocket: WebSocket | null = null;\n private currentRoot: HTMLElement | null = null;\n\n private url: string;\n private websocketFactory: NetworkedDOMWebsocketFactory;\n private parentElement: HTMLElement;\n private timeCallback: (time: number) => void;\n private statusUpdateCallback: (status: NetworkedDOMWebsocketStatus) => void;\n private stopped = false;\n private backoffTime = startingBackoffTimeMilliseconds;\n private status: NetworkedDOMWebsocketStatus | null = null;\n\n public static createWebSocket(url: string): WebSocket {\n return new WebSocket(url, [websocketProtocol]);\n }\n\n constructor(\n url: string,\n websocketFactory: NetworkedDOMWebsocketFactory,\n parentElement: HTMLElement,\n timeCallback?: (time: number) => void,\n statusUpdateCallback?: (status: NetworkedDOMWebsocketStatus) => void,\n ) {\n this.url = url;\n this.websocketFactory = websocketFactory;\n this.parentElement = parentElement;\n this.timeCallback =\n timeCallback ||\n (() => {\n // no-op\n });\n this.statusUpdateCallback =\n statusUpdateCallback ||\n (() => {\n // no-op\n });\n this.setStatus(NetworkedDOMWebsocketStatus.Connecting);\n this.startWebSocketConnectionAttempt();\n }\n\n private setStatus(status: NetworkedDOMWebsocketStatus) {\n if (this.status !== status) {\n this.status = status;\n this.statusUpdateCallback(status);\n }\n }\n\n private createWebsocketWithTimeout(timeout: number): Promise<WebSocket> {\n return new Promise((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n reject(new Error(\"websocket connection timed out\"));\n }, timeout);\n const websocket = this.websocketFactory(this.url);\n websocket.addEventListener(\"open\", () => {\n clearTimeout(timeoutId);\n\n this.websocket = websocket;\n\n websocket.addEventListener(\"message\", (event) => {\n if (websocket !== this.websocket) {\n console.log(\"Ignoring websocket message event because it is no longer current\");\n websocket.close();\n return;\n }\n this.handleIncomingWebsocketMessage(event);\n });\n\n const onWebsocketClose = async () => {\n const hadContents = this.currentRoot !== null;\n this.clearContents();\n if (this.stopped) {\n // This closing is expected. The client closed the websocket.\n this.setStatus(NetworkedDOMWebsocketStatus.Disconnected);\n return;\n }\n if (!hadContents) {\n // The websocket did not deliver any contents. It may have been successfully opened, but immediately closed. This client should back off to prevent this happening in a rapid loop.\n await this.waitBackoffTime();\n }\n // The websocket closed unexpectedly. Try to reconnect.\n this.setStatus(NetworkedDOMWebsocketStatus.Reconnecting);\n this.startWebSocketConnectionAttempt();\n };\n\n websocket.addEventListener(\"close\", (e) => {\n if (websocket !== this.websocket) {\n console.warn(\"Ignoring websocket close event because it is no longer current\");\n return;\n }\n console.log(\"NetworkedDOMWebsocket close\", e);\n onWebsocketClose();\n });\n websocket.addEventListener(\"error\", (e) => {\n if (websocket !== this.websocket) {\n console.log(\"Ignoring websocket error event because it is no longer current\");\n return;\n }\n console.error(\"NetworkedDOMWebsocket error\", e);\n onWebsocketClose();\n });\n\n resolve(websocket);\n });\n websocket.addEventListener(\"error\", (e) => {\n clearTimeout(timeoutId);\n reject(e);\n });\n });\n }\n\n private async waitBackoffTime(): Promise<void> {\n console.warn(`Websocket connection to '${this.url}' failed: retrying in ${this.backoffTime}ms`);\n await new Promise((resolve) => setTimeout(resolve, this.backoffTime));\n this.backoffTime = Math.min(\n // Introduce a small amount of randomness to prevent clients from retrying in lockstep\n this.backoffTime * (1.5 + Math.random() * 0.5),\n maximumBackoffTimeMilliseconds,\n );\n }\n\n private async startWebSocketConnectionAttempt() {\n if (this.stopped) {\n return;\n }\n // eslint-disable-next-line no-constant-condition\n while (true) {\n if (this.stopped) {\n return;\n }\n try {\n await this.createWebsocketWithTimeout(maximumWebsocketConnectionTimeout);\n break;\n } catch (e) {\n // Connection failed, retry with backoff\n this.setStatus(NetworkedDOMWebsocketStatus.Reconnecting);\n await this.waitBackoffTime();\n }\n }\n }\n\n private handleIncomingWebsocketMessage(event: MessageEvent) {\n const messages = JSON.parse(event.data) as Array<ServerMessage>;\n for (const message of messages) {\n if (message.type === \"error\") {\n console.error(\"Error from server\", message);\n } else if (message.type === \"warning\") {\n console.warn(\"Warning from server\", message);\n } else {\n if (message.documentTime) {\n if (this.timeCallback) {\n this.timeCallback(message.documentTime);\n }\n }\n if (message.type === \"snapshot\") {\n // This websocket is successfully connected. Reset the backoff time.\n this.backoffTime = startingBackoffTimeMilliseconds;\n this.setStatus(NetworkedDOMWebsocketStatus.Connected);\n\n if (this.currentRoot) {\n this.currentRoot.remove();\n this.currentRoot = null;\n this.elementToId.clear();\n this.idToElement.clear();\n }\n\n // create a tree of DOM elements\n // NOTE: the MLElement contructors are not executed during this stage\n const element = this.handleNewElement(message.snapshot);\n if (!element) {\n throw new Error(\"Snapshot element not created\");\n }\n if (!isHTMLElement(element)) {\n throw new Error(\"Snapshot element is not an HTMLElement\");\n }\n this.currentRoot = element;\n // appending to the tree causes MElements to be constructed\n this.parentElement.append(element);\n } else if (message.type === \"attributeChange\") {\n this.handleAttributeChange(message);\n } else if (message.type === \"childrenChanged\") {\n this.handleChildrenChanged(message);\n } else if (message.type === \"textChanged\") {\n this.handleTextChanged(message);\n } else if (message.type === \"ping\") {\n this.send({\n type: \"pong\",\n pong: message.ping,\n });\n } else {\n console.warn(\"unknown message type\", message);\n }\n }\n }\n }\n\n public stop() {\n this.stopped = true;\n if (this.websocket !== null) {\n this.websocket.close();\n this.websocket = null;\n }\n }\n\n public handleEvent(element: HTMLElement, event: CustomEvent<{ element: HTMLElement }>) {\n const nodeId = this.elementToId.get(element);\n if (nodeId === undefined || nodeId === null) {\n throw new Error(\"Element not found\");\n }\n\n console.log(\n `Sending event to websocket: \"${event.type}\" on node: ${nodeId} type: ${element.tagName}`,\n );\n\n const detailWithoutElement: Partial<typeof event.detail> = {\n ...event.detail,\n };\n delete detailWithoutElement.element;\n\n const remoteEvent: RemoteEvent = {\n type: \"event\",\n nodeId,\n name: event.type,\n bubbles: event.bubbles,\n params: detailWithoutElement,\n };\n\n this.send(remoteEvent);\n }\n\n private send(fromClientMessage: ClientMessage) {\n if (!this.websocket) {\n throw new Error(\"No websocket created\");\n }\n this.websocket.send(JSON.stringify(fromClientMessage));\n }\n\n private handleTextChanged(message: TextChangedDiff) {\n const { nodeId, text } = message;\n\n if (nodeId === undefined || nodeId === null) {\n console.warn(\"No nodeId in textChanged message\");\n return;\n }\n const node = this.idToElement.get(nodeId);\n if (!node) {\n throw new Error(\"No node found for textChanged message\");\n }\n if (!isText(node)) {\n throw new Error(\"Node for textChanged message is not a Text node\");\n }\n node.textContent = text;\n }\n\n private handleChildrenChanged(message: ChildrenChangedDiff) {\n const { nodeId, addedNodes, removedNodes, previousNodeId } = message;\n if (nodeId === undefined || nodeId === null) {\n console.warn(\"No nodeId in childrenChanged message\");\n return;\n }\n const parent = this.idToElement.get(nodeId);\n if (!parent) {\n throw new Error(\"No parent found for childrenChanged message\");\n }\n if (!parent.isConnected) {\n console.error(\"Parent is not connected\", parent);\n }\n if (!isHTMLElement(parent)) {\n throw new Error(\"Parent is not an HTMLElement (that supports children)\");\n }\n let previousElement;\n if (previousNodeId) {\n previousElement = this.idToElement.get(previousNodeId);\n if (!previousElement) {\n throw new Error(\"No previous element found for childrenChanged message\");\n }\n }\n\n for (const addedNode of addedNodes) {\n const childElement = this.handleNewElement(addedNode);\n if (childElement) {\n if (previousElement) {\n const nextElement = previousElement.nextSibling;\n if (nextElement) {\n parent.insertBefore(childElement, nextElement);\n } else {\n parent.append(childElement);\n }\n } else {\n parent.append(childElement);\n }\n }\n }\n for (const removedNode of removedNodes) {\n const childElement = this.idToElement.get(removedNode);\n if (!childElement) {\n throw new Error(`Child element not found: ${removedNode}`);\n }\n this.elementToId.delete(childElement);\n this.idToElement.delete(removedNode);\n parent.removeChild(childElement);\n if (isHTMLElement(childElement)) {\n // If child is capable of supporting children then remove any that exist\n this.removeChildElementIds(childElement);\n }\n }\n }\n\n private removeChildElementIds(parent: HTMLElement) {\n for (let i = 0; i < parent.children.length; i++) {\n const child = parent.children[i];\n const childId = this.elementToId.get(child as HTMLElement);\n if (!childId) {\n console.error(\"Inner child of removed element had no id\", child);\n } else {\n this.elementToId.delete(child);\n this.idToElement.delete(childId);\n }\n this.removeChildElementIds(child as HTMLElement);\n }\n }\n\n private handleAttributeChange(message: AttributeChangedDiff) {\n const { nodeId, attribute, newValue } = message;\n if (nodeId === undefined || nodeId === null) {\n console.warn(\"No nodeId in attributeChange message\");\n return;\n }\n const element = this.idToElement.get(nodeId);\n if (element) {\n if (isHTMLElement(element)) {\n if (newValue === null) {\n element.removeAttribute(attribute);\n } else {\n if (DOMSanitizer.shouldAcceptAttribute(attribute)) {\n element.setAttribute(attribute, newValue);\n }\n }\n } else {\n console.error(\"Element is not an HTMLElement and cannot support attributes\", element);\n }\n } else {\n console.error(\"No element found for attributeChange message\");\n }\n }\n\n private handleNewElement(message: NodeDescription): Node | null {\n if (message.type === \"text\") {\n const { nodeId, text } = message;\n const textNode = document.createTextNode(\"\");\n textNode.textContent = text;\n this.idToElement.set(nodeId, textNode);\n this.elementToId.set(textNode, nodeId);\n return textNode;\n }\n const { tag, nodeId, attributes, children, text } = message;\n if (nodeId === undefined || nodeId === null) {\n console.warn(\"No nodeId in handleNewElement message\", message);\n return null;\n }\n if (this.idToElement.has(nodeId)) {\n console.error(\n \"Received nodeId to add that is already present\",\n nodeId,\n this.idToElement.get(nodeId),\n );\n }\n if (tag === \"#text\") {\n const textNode = document.createTextNode(\"\");\n textNode.textContent = text || null;\n this.idToElement.set(nodeId, textNode);\n this.elementToId.set(textNode, nodeId);\n return textNode;\n }\n\n let element;\n try {\n element = document.createElement(tag);\n } catch (e) {\n console.error(`Error creating element: (${tag})`, e);\n element = document.createElement(\"div\");\n }\n this.idToElement.set(nodeId, element);\n this.elementToId.set(element, nodeId);\n for (const key in attributes) {\n if (DOMSanitizer.shouldAcceptAttribute(key)) {\n const value = attributes[key];\n element.setAttribute(key, value);\n }\n }\n if (children) {\n for (const child of children) {\n const childElement = this.handleNewElement(child);\n if (childElement) {\n element.append(childElement);\n }\n }\n }\n return element;\n }\n\n private clearContents() {\n this.idToElement.clear();\n this.elementToId.clear();\n if (this.currentRoot) {\n this.currentRoot.remove();\n this.currentRoot = null;\n }\n }\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAO,IAAM,eAAN,MAAmB;AAAA,EACxB,OAAO,SAAS,MAAmB;AACjC,QAAI,KAAK,mBAAmB;AAC1B,iBAAW,QAAQ,KAAK,kBAAkB,GAAG;AAC3C,YAAI,CAAC,aAAa,qBAAqB,IAAI,GAAG;AAC5C,eAAK,gBAAgB,IAAI;AAAA,QAC3B;AAAA,MACF;AAAA,IACF;AACA,QAAI,KAAK,aAAa,UAAU;AAE9B,WAAK,YAAY;AAAA,IACnB,OAAO;AACL,UAAI,KAAK,mBAAmB;AAC1B,mBAAW,QAAQ,KAAK,kBAAkB,GAAG;AAC3C,cAAI,CAAC,aAAa,sBAAsB,IAAI,GAAG;AAC7C,iBAAK,gBAAgB,IAAI;AAAA,UAC3B;AAAA,QACF;AAAA,MACF;AACA,eAAS,IAAI,GAAG,IAAI,KAAK,WAAW,QAAQ,KAAK;AAC/C,qBAAa,SAAS,KAAK,WAAW,CAAC,CAAgB;AAAA,MACzD;AAAA,IACF;AAAA,EACF;AAAA,EAEA,OAAO,aAAa,GAAoB;AACtC,WAAO,KAAK,OAAO,KAAK;AAAA,EAC1B;AAAA,EAEA,OAAO,aAAa,GAAW;AAC7B,WAAO,KAAK,OAAO,KAAK;AAAA,EAC1B;AAAA,EAEA,OAAO,qBAAqB,YAA6B;AACvD,UAAM,IAAI,WAAW,CAAC;AACtB,QAAI,EAAE,aAAa,aAAa,CAAC,KAAK,MAAM,OAAO,MAAM,MAAM;AAC7D,aAAO;AAAA,IACT;AAEA,aAAS,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK;AAC1C,YAAMA,KAAI,WAAW,CAAC;AACtB,UACE,EACE,aAAa,aAAaA,EAAC,KAC3B,aAAa,aAAaA,EAAC,KAC3BA,OAAM,OACNA,OAAM,OACNA,OAAM,OACNA,OAAM,MAER;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,OAAO,sBAAsB,WAAmB;AAC9C,QAAI,CAAC,aAAa,qBAAqB,SAAS,GAAG;AACjD,cAAQ,KAAK,0BAA0B,SAAS;AAChD,aAAO;AAAA,IACT;AAGA,WAAO,CAAC,UAAU,WAAW,IAAI;AAAA,EACnC;AACF;;;ACxDA,IAAM,oBAAoB;AAE1B,IAAM,kCAAkC;AACxC,IAAM,iCAAiC;AACvC,IAAM,oCAAoC;AAInC,IAAK,8BAAL,kBAAKC,iCAAL;AACL,EAAAA,0DAAA;AACA,EAAAA,0DAAA;AACA,EAAAA,0DAAA;AACA,EAAAA,0DAAA;AAJU,SAAAA;AAAA,GAAA;AAOZ,SAAS,cAAc,MAAoC;AACzD,MAAI,gBAAgB,aAAa;AAC/B,WAAO;AAAA,EACT;AACA,MAAI,CAAC,KAAK,cAAc,cAAc,aAAa;AACjD,WAAO;AAAA,EACT;AACA,SAAO,gBAAgB,KAAK,cAAc,cAAc,YAAY;AACtE;AAEA,SAAS,OAAO,MAA6B;AAC3C,MAAI,gBAAgB,MAAM;AACxB,WAAO;AAAA,EACT;AACA,MAAI,CAAC,KAAK,cAAc,cAAc,aAAa;AACjD,WAAO;AAAA,EACT;AACA,SAAO,gBAAgB,KAAK,cAAc,cAAc,YAAY;AACtE;AAEO,IAAM,wBAAN,MAA4B;AAAA,EAmBjC,YACE,KACA,kBACA,eACA,cACA,sBACA;AAxBF,SAAQ,cAAc,oBAAI,IAAkB;AAC5C,SAAQ,cAAc,oBAAI,IAAkB;AAC5C,SAAQ,YAA8B;AACtC,SAAQ,cAAkC;AAO1C,SAAQ,UAAU;AAClB,SAAQ,cAAc;AACtB,SAAQ,SAA6C;AAanD,SAAK,MAAM;AACX,SAAK,mBAAmB;AACxB,SAAK,gBAAgB;AACrB,SAAK,eACH,iBACC,MAAM;AAAA,IAEP;AACF,SAAK,uBACH,yBACC,MAAM;AAAA,IAEP;AACF,SAAK,UAAU,kBAAsC;AACrD,SAAK,gCAAgC;AAAA,EACvC;AAAA,EA1BA,OAAc,gBAAgB,KAAwB;AACpD,WAAO,IAAI,UAAU,KAAK,CAAC,iBAAiB,CAAC;AAAA,EAC/C;AAAA,EA0BQ,UAAU,QAAqC;AACrD,QAAI,KAAK,WAAW,QAAQ;AAC1B,WAAK,SAAS;AACd,WAAK,qBAAqB,MAAM;AAAA,IAClC;AAAA,EACF;AAAA,EAEQ,2BAA2B,SAAqC;AACtE,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,YAAM,YAAY,WAAW,MAAM;AACjC,eAAO,IAAI,MAAM,gCAAgC,CAAC;AAAA,MACpD,GAAG,OAAO;AACV,YAAM,YAAY,KAAK,iBAAiB,KAAK,GAAG;AAChD,gBAAU,iBAAiB,QAAQ,MAAM;AACvC,qBAAa,SAAS;AAEtB,aAAK,YAAY;AAEjB,kBAAU,iBAAiB,WAAW,CAAC,UAAU;AAC/C,cAAI,cAAc,KAAK,WAAW;AAChC,oBAAQ,IAAI,kEAAkE;AAC9E,sBAAU,MAAM;AAChB;AAAA,UACF;AACA,eAAK,+BAA+B,KAAK;AAAA,QAC3C,CAAC;AAED,cAAM,mBAAmB,YAAY;AACnC,gBAAM,cAAc,KAAK,gBAAgB;AACzC,eAAK,cAAc;AACnB,cAAI,KAAK,SAAS;AAEhB,iBAAK,UAAU,oBAAwC;AACvD;AAAA,UACF;AACA,cAAI,CAAC,aAAa;AAEhB,kBAAM,KAAK,gBAAgB;AAAA,UAC7B;AAEA,eAAK,UAAU,oBAAwC;AACvD,eAAK,gCAAgC;AAAA,QACvC;AAEA,kBAAU,iBAAiB,SAAS,CAAC,MAAM;AACzC,cAAI,cAAc,KAAK,WAAW;AAChC,oBAAQ,KAAK,gEAAgE;AAC7E;AAAA,UACF;AACA,kBAAQ,IAAI,+BAA+B,CAAC;AAC5C,2BAAiB;AAAA,QACnB,CAAC;AACD,kBAAU,iBAAiB,SAAS,CAAC,MAAM;AACzC,cAAI,cAAc,KAAK,WAAW;AAChC,oBAAQ,IAAI,gEAAgE;AAC5E;AAAA,UACF;AACA,kBAAQ,MAAM,+BAA+B,CAAC;AAC9C,2BAAiB;AAAA,QACnB,CAAC;AAED,gBAAQ,SAAS;AAAA,MACnB,CAAC;AACD,gBAAU,iBAAiB,SAAS,CAAC,MAAM;AACzC,qBAAa,SAAS;AACtB,eAAO,CAAC;AAAA,MACV,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,kBAAiC;AAC7C,YAAQ,KAAK,4BAA4B,KAAK,4BAA4B,KAAK,eAAe;AAC9F,UAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,KAAK,WAAW,CAAC;AACpE,SAAK,cAAc,KAAK;AAAA;AAAA,MAEtB,KAAK,eAAe,MAAM,KAAK,OAAO,IAAI;AAAA,MAC1C;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,kCAAkC;AAC9C,QAAI,KAAK,SAAS;AAChB;AAAA,IACF;AAEA,WAAO,MAAM;AACX,UAAI,KAAK,SAAS;AAChB;AAAA,MACF;AACA,UAAI;AACF,cAAM,KAAK,2BAA2B,iCAAiC;AACvE;AAAA,MACF,SAAS,GAAP;AAEA,aAAK,UAAU,oBAAwC;AACvD,cAAM,KAAK,gBAAgB;AAAA,MAC7B;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,+BAA+B,OAAqB;AAC1D,UAAM,WAAW,KAAK,MAAM,MAAM,IAAI;AACtC,eAAW,WAAW,UAAU;AAC9B,UAAI,QAAQ,SAAS,SAAS;AAC5B,gBAAQ,MAAM,qBAAqB,OAAO;AAAA,MAC5C,WAAW,QAAQ,SAAS,WAAW;AACrC,gBAAQ,KAAK,uBAAuB,OAAO;AAAA,MAC7C,OAAO;AACL,YAAI,QAAQ,cAAc;AACxB,cAAI,KAAK,cAAc;AACrB,iBAAK,aAAa,QAAQ,YAAY;AAAA,UACxC;AAAA,QACF;AACA,YAAI,QAAQ,SAAS,YAAY;AAE/B,eAAK,cAAc;AACnB,eAAK,UAAU,iBAAqC;AAEpD,cAAI,KAAK,aAAa;AACpB,iBAAK,YAAY,OAAO;AACxB,iBAAK,cAAc;AACnB,iBAAK,YAAY,MAAM;AACvB,iBAAK,YAAY,MAAM;AAAA,UACzB;AAIA,gBAAM,UAAU,KAAK,iBAAiB,QAAQ,QAAQ;AACtD,cAAI,CAAC,SAAS;AACZ,kBAAM,IAAI,MAAM,8BAA8B;AAAA,UAChD;AACA,cAAI,CAAC,cAAc,OAAO,GAAG;AAC3B,kBAAM,IAAI,MAAM,wCAAwC;AAAA,UAC1D;AACA,eAAK,cAAc;AAEnB,eAAK,cAAc,OAAO,OAAO;AAAA,QACnC,WAAW,QAAQ,SAAS,mBAAmB;AAC7C,eAAK,sBAAsB,OAAO;AAAA,QACpC,WAAW,QAAQ,SAAS,mBAAmB;AAC7C,eAAK,sBAAsB,OAAO;AAAA,QACpC,WAAW,QAAQ,SAAS,eAAe;AACzC,eAAK,kBAAkB,OAAO;AAAA,QAChC,WAAW,QAAQ,SAAS,QAAQ;AAClC,eAAK,KAAK;AAAA,YACR,MAAM;AAAA,YACN,MAAM,QAAQ;AAAA,UAChB,CAAC;AAAA,QACH,OAAO;AACL,kBAAQ,KAAK,wBAAwB,OAAO;AAAA,QAC9C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEO,OAAO;AACZ,SAAK,UAAU;AACf,QAAI,KAAK,cAAc,MAAM;AAC3B,WAAK,UAAU,MAAM;AACrB,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA,EAEO,YAAY,SAAsB,OAA8C;AACrF,UAAM,SAAS,KAAK,YAAY,IAAI,OAAO;AAC3C,QAAI,WAAW,UAAa,WAAW,MAAM;AAC3C,YAAM,IAAI,MAAM,mBAAmB;AAAA,IACrC;AAEA,YAAQ;AAAA,MACN,gCAAgC,MAAM,kBAAkB,gBAAgB,QAAQ;AAAA,IAClF;AAEA,UAAM,uBAAqD;AAAA,MACzD,GAAG,MAAM;AAAA,IACX;AACA,WAAO,qBAAqB;AAE5B,UAAM,cAA2B;AAAA,MAC/B,MAAM;AAAA,MACN;AAAA,MACA,MAAM,MAAM;AAAA,MACZ,SAAS,MAAM;AAAA,MACf,QAAQ;AAAA,IACV;AAEA,SAAK,KAAK,WAAW;AAAA,EACvB;AAAA,EAEQ,KAAK,mBAAkC;AAC7C,QAAI,CAAC,KAAK,WAAW;AACnB,YAAM,IAAI,MAAM,sBAAsB;AAAA,IACxC;AACA,SAAK,UAAU,KAAK,KAAK,UAAU,iBAAiB,CAAC;AAAA,EACvD;AAAA,EAEQ,kBAAkB,SAA0B;AAClD,UAAM,EAAE,QAAQ,KAAK,IAAI;AAEzB,QAAI,WAAW,UAAa,WAAW,MAAM;AAC3C,cAAQ,KAAK,kCAAkC;AAC/C;AAAA,IACF;AACA,UAAM,OAAO,KAAK,YAAY,IAAI,MAAM;AACxC,QAAI,CAAC,MAAM;AACT,YAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AACA,QAAI,CAAC,OAAO,IAAI,GAAG;AACjB,YAAM,IAAI,MAAM,iDAAiD;AAAA,IACnE;AACA,SAAK,cAAc;AAAA,EACrB;AAAA,EAEQ,sBAAsB,SAA8B;AAC1D,UAAM,EAAE,QAAQ,YAAY,cAAc,eAAe,IAAI;AAC7D,QAAI,WAAW,UAAa,WAAW,MAAM;AAC3C,cAAQ,KAAK,sCAAsC;AACnD;AAAA,IACF;AACA,UAAM,SAAS,KAAK,YAAY,IAAI,MAAM;AAC1C,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAC/D;AACA,QAAI,CAAC,OAAO,aAAa;AACvB,cAAQ,MAAM,2BAA2B,MAAM;AAAA,IACjD;AACA,QAAI,CAAC,cAAc,MAAM,GAAG;AAC1B,YAAM,IAAI,MAAM,uDAAuD;AAAA,IACzE;AACA,QAAI;AACJ,QAAI,gBAAgB;AAClB,wBAAkB,KAAK,YAAY,IAAI,cAAc;AACrD,UAAI,CAAC,iBAAiB;AACpB,cAAM,IAAI,MAAM,uDAAuD;AAAA,MACzE;AAAA,IACF;AAEA,eAAW,aAAa,YAAY;AAClC,YAAM,eAAe,KAAK,iBAAiB,SAAS;AACpD,UAAI,cAAc;AAChB,YAAI,iBAAiB;AACnB,gBAAM,cAAc,gBAAgB;AACpC,cAAI,aAAa;AACf,mBAAO,aAAa,cAAc,WAAW;AAAA,UAC/C,OAAO;AACL,mBAAO,OAAO,YAAY;AAAA,UAC5B;AAAA,QACF,OAAO;AACL,iBAAO,OAAO,YAAY;AAAA,QAC5B;AAAA,MACF;AAAA,IACF;AACA,eAAW,eAAe,cAAc;AACtC,YAAM,eAAe,KAAK,YAAY,IAAI,WAAW;AACrD,UAAI,CAAC,cAAc;AACjB,cAAM,IAAI,MAAM,4BAA4B,aAAa;AAAA,MAC3D;AACA,WAAK,YAAY,OAAO,YAAY;AACpC,WAAK,YAAY,OAAO,WAAW;AACnC,aAAO,YAAY,YAAY;AAC/B,UAAI,cAAc,YAAY,GAAG;AAE/B,aAAK,sBAAsB,YAAY;AAAA,MACzC;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,sBAAsB,QAAqB;AACjD,aAAS,IAAI,GAAG,IAAI,OAAO,SAAS,QAAQ,KAAK;AAC/C,YAAM,QAAQ,OAAO,SAAS,CAAC;AAC/B,YAAM,UAAU,KAAK,YAAY,IAAI,KAAoB;AACzD,UAAI,CAAC,SAAS;AACZ,gBAAQ,MAAM,4CAA4C,KAAK;AAAA,MACjE,OAAO;AACL,aAAK,YAAY,OAAO,KAAK;AAC7B,aAAK,YAAY,OAAO,OAAO;AAAA,MACjC;AACA,WAAK,sBAAsB,KAAoB;AAAA,IACjD;AAAA,EACF;AAAA,EAEQ,sBAAsB,SAA+B;AAC3D,UAAM,EAAE,QAAQ,WAAW,SAAS,IAAI;AACxC,QAAI,WAAW,UAAa,WAAW,MAAM;AAC3C,cAAQ,KAAK,sCAAsC;AACnD;AAAA,IACF;AACA,UAAM,UAAU,KAAK,YAAY,IAAI,MAAM;AAC3C,QAAI,SAAS;AACX,UAAI,cAAc,OAAO,GAAG;AAC1B,YAAI,aAAa,MAAM;AACrB,kBAAQ,gBAAgB,SAAS;AAAA,QACnC,OAAO;AACL,cAAI,aAAa,sBAAsB,SAAS,GAAG;AACjD,oBAAQ,aAAa,WAAW,QAAQ;AAAA,UAC1C;AAAA,QACF;AAAA,MACF,OAAO;AACL,gBAAQ,MAAM,+DAA+D,OAAO;AAAA,MACtF;AAAA,IACF,OAAO;AACL,cAAQ,MAAM,8CAA8C;AAAA,IAC9D;AAAA,EACF;AAAA,EAEQ,iBAAiB,SAAuC;AAC9D,QAAI,QAAQ,SAAS,QAAQ;AAC3B,YAAM,EAAE,QAAAC,SAAQ,MAAAC,MAAK,IAAI;AACzB,YAAM,WAAW,SAAS,eAAe,EAAE;AAC3C,eAAS,cAAcA;AACvB,WAAK,YAAY,IAAID,SAAQ,QAAQ;AACrC,WAAK,YAAY,IAAI,UAAUA,OAAM;AACrC,aAAO;AAAA,IACT;AACA,UAAM,EAAE,KAAK,QAAQ,YAAY,UAAU,KAAK,IAAI;AACpD,QAAI,WAAW,UAAa,WAAW,MAAM;AAC3C,cAAQ,KAAK,yCAAyC,OAAO;AAC7D,aAAO;AAAA,IACT;AACA,QAAI,KAAK,YAAY,IAAI,MAAM,GAAG;AAChC,cAAQ;AAAA,QACN;AAAA,QACA;AAAA,QACA,KAAK,YAAY,IAAI,MAAM;AAAA,MAC7B;AAAA,IACF;AACA,QAAI,QAAQ,SAAS;AACnB,YAAM,WAAW,SAAS,eAAe,EAAE;AAC3C,eAAS,cAAc,QAAQ;AAC/B,WAAK,YAAY,IAAI,QAAQ,QAAQ;AACrC,WAAK,YAAY,IAAI,UAAU,MAAM;AACrC,aAAO;AAAA,IACT;AAEA,QAAI;AACJ,QAAI;AACF,gBAAU,SAAS,cAAc,GAAG;AAAA,IACtC,SAAS,GAAP;AACA,cAAQ,MAAM,4BAA4B,QAAQ,CAAC;AACnD,gBAAU,SAAS,cAAc,KAAK;AAAA,IACxC;AACA,SAAK,YAAY,IAAI,QAAQ,OAAO;AACpC,SAAK,YAAY,IAAI,SAAS,MAAM;AACpC,eAAW,OAAO,YAAY;AAC5B,UAAI,aAAa,sBAAsB,GAAG,GAAG;AAC3C,cAAM,QAAQ,WAAW,GAAG;AAC5B,gBAAQ,aAAa,KAAK,KAAK;AAAA,MACjC;AAAA,IACF;AACA,QAAI,UAAU;AACZ,iBAAW,SAAS,UAAU;AAC5B,cAAM,eAAe,KAAK,iBAAiB,KAAK;AAChD,YAAI,cAAc;AAChB,kBAAQ,OAAO,YAAY;AAAA,QAC7B;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,gBAAgB;AACtB,SAAK,YAAY,MAAM;AACvB,SAAK,YAAY,MAAM;AACvB,QAAI,KAAK,aAAa;AACpB,WAAK,YAAY,OAAO;AACxB,WAAK,cAAc;AAAA,IACrB;AAAA,EACF;AACF;",
6
6
  "names": ["c", "NetworkedDOMWebsocketStatus", "nodeId", "text"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mml-io/networked-dom-web",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "main": "./build/index.js",
5
5
  "types": "./build/index.d.ts",
6
6
  "files": [
@@ -25,6 +25,26 @@ export enum NetworkedDOMWebsocketStatus {
25
25
  Disconnected,
26
26
  }
27
27
 
28
+ function isHTMLElement(node: unknown): node is HTMLElement {
29
+ if (node instanceof HTMLElement) {
30
+ return true;
31
+ }
32
+ if (!this.parentElement.ownerDocument.defaultView) {
33
+ return false;
34
+ }
35
+ return node instanceof this.parentElement.ownerDocument.defaultView.HTMLElement;
36
+ }
37
+
38
+ function isText(node: unknown): node is Text {
39
+ if (node instanceof Text) {
40
+ return true;
41
+ }
42
+ if (!this.parentElement.ownerDocument.defaultView) {
43
+ return false;
44
+ }
45
+ return node instanceof this.parentElement.ownerDocument.defaultView.Text;
46
+ }
47
+
28
48
  export class NetworkedDOMWebsocket {
29
49
  private idToElement = new Map<number, Node>();
30
50
  private elementToId = new Map<Node, number>();
@@ -199,7 +219,7 @@ export class NetworkedDOMWebsocket {
199
219
  if (!element) {
200
220
  throw new Error("Snapshot element not created");
201
221
  }
202
- if (!(element instanceof HTMLElement)) {
222
+ if (!isHTMLElement(element)) {
203
223
  throw new Error("Snapshot element is not an HTMLElement");
204
224
  }
205
225
  this.currentRoot = element;
@@ -275,7 +295,7 @@ export class NetworkedDOMWebsocket {
275
295
  if (!node) {
276
296
  throw new Error("No node found for textChanged message");
277
297
  }
278
- if (!(node instanceof Text)) {
298
+ if (!isText(node)) {
279
299
  throw new Error("Node for textChanged message is not a Text node");
280
300
  }
281
301
  node.textContent = text;
@@ -294,7 +314,7 @@ export class NetworkedDOMWebsocket {
294
314
  if (!parent.isConnected) {
295
315
  console.error("Parent is not connected", parent);
296
316
  }
297
- if (!(parent instanceof HTMLElement)) {
317
+ if (!isHTMLElement(parent)) {
298
318
  throw new Error("Parent is not an HTMLElement (that supports children)");
299
319
  }
300
320
  let previousElement;
@@ -328,7 +348,7 @@ export class NetworkedDOMWebsocket {
328
348
  this.elementToId.delete(childElement);
329
349
  this.idToElement.delete(removedNode);
330
350
  parent.removeChild(childElement);
331
- if (childElement instanceof HTMLElement) {
351
+ if (isHTMLElement(childElement)) {
332
352
  // If child is capable of supporting children then remove any that exist
333
353
  this.removeChildElementIds(childElement);
334
354
  }
@@ -357,7 +377,7 @@ export class NetworkedDOMWebsocket {
357
377
  }
358
378
  const element = this.idToElement.get(nodeId);
359
379
  if (element) {
360
- if (element instanceof HTMLElement) {
380
+ if (isHTMLElement(element)) {
361
381
  if (newValue === null) {
362
382
  element.removeAttribute(attribute);
363
383
  } else {
@@ -402,7 +422,13 @@ export class NetworkedDOMWebsocket {
402
422
  return textNode;
403
423
  }
404
424
 
405
- const element = document.createElement(tag);
425
+ let element;
426
+ try {
427
+ element = document.createElement(tag);
428
+ } catch (e) {
429
+ console.error(`Error creating element: (${tag})`, e);
430
+ element = document.createElement("div");
431
+ }
406
432
  this.idToElement.set(nodeId, element);
407
433
  this.elementToId.set(element, nodeId);
408
434
  for (const key in attributes) {