@mml-io/networked-dom-web-client 0.6.2 → 0.8.0

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
@@ -1,524 +1,462 @@
1
- var __create = Object.create;
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __getProtoOf = Object.getPrototypeOf;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __commonJS = (cb, mod) => function __require() {
8
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
19
- // If the importer is in node compatibility mode or this is not an ESM
20
- // file that has been converted to a CommonJS file using a Babel-
21
- // compatible transform (i.e. "__esModule" has not been set), then set
22
- // "default" to the CommonJS "module.exports" for node compatibility.
23
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
24
- mod
25
- ));
26
-
27
1
  // ../networked-dom-web/build/index.js
28
- var require_build = __commonJS({
29
- "../networked-dom-web/build/index.js"(exports, module2) {
30
- var __defProp2 = Object.defineProperty;
31
- var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
32
- var __getOwnPropNames2 = Object.getOwnPropertyNames;
33
- var __hasOwnProp2 = Object.prototype.hasOwnProperty;
34
- var __export = (target, all) => {
35
- for (var name in all)
36
- __defProp2(target, name, { get: all[name], enumerable: true });
37
- };
38
- var __copyProps2 = (to, from, except, desc) => {
39
- if (from && typeof from === "object" || typeof from === "function") {
40
- for (let key of __getOwnPropNames2(from))
41
- if (!__hasOwnProp2.call(to, key) && key !== except)
42
- __defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
43
- }
44
- return to;
45
- };
46
- var __toCommonJS = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
47
- var src_exports = {};
48
- __export(src_exports, {
49
- DOMSanitizer: () => DOMSanitizer,
50
- NetworkedDOMWebsocket: () => NetworkedDOMWebsocket2,
51
- NetworkedDOMWebsocketStatus: () => NetworkedDOMWebsocketStatus
52
- });
53
- module2.exports = __toCommonJS(src_exports);
54
- var DOMSanitizer = class _DOMSanitizer {
55
- static sanitise(node) {
56
- if (node.getAttributeNames) {
57
- for (const attr of node.getAttributeNames()) {
58
- if (!_DOMSanitizer.IsValidAttributeName(attr)) {
59
- node.removeAttribute(attr);
60
- }
61
- }
62
- }
63
- if (node.nodeName === "SCRIPT") {
64
- node.innerText = "";
65
- } else {
66
- if (node.getAttributeNames) {
67
- for (const attr of node.getAttributeNames()) {
68
- if (!_DOMSanitizer.shouldAcceptAttribute(attr)) {
69
- node.removeAttribute(attr);
70
- }
71
- }
72
- }
73
- for (let i = 0; i < node.childNodes.length; i++) {
74
- _DOMSanitizer.sanitise(node.childNodes[i]);
75
- }
2
+ var DOMSanitizer = class _DOMSanitizer {
3
+ static sanitise(node) {
4
+ if (node.getAttributeNames) {
5
+ for (const attr of node.getAttributeNames()) {
6
+ if (!_DOMSanitizer.IsValidAttributeName(attr)) {
7
+ node.removeAttribute(attr);
76
8
  }
77
9
  }
78
- static IsASCIIDigit(c) {
79
- return c >= "0" && c <= "9";
80
- }
81
- static IsASCIIAlpha(c) {
82
- return c >= "a" && c <= "z";
83
- }
84
- static IsValidAttributeName(characters) {
85
- const c = characters[0];
86
- if (!(_DOMSanitizer.IsASCIIAlpha(c) || c === ":" || c === "_")) {
87
- return false;
88
- }
89
- for (let i = 1; i < characters.length; i++) {
90
- const c2 = characters[i];
91
- if (!(_DOMSanitizer.IsASCIIDigit(c2) || _DOMSanitizer.IsASCIIAlpha(c2) || c2 === ":" || c2 === "_" || c2 === "-" || c2 === ".")) {
92
- return false;
10
+ }
11
+ if (node.nodeName === "SCRIPT") {
12
+ node.innerText = "";
13
+ } else {
14
+ if (node.getAttributeNames) {
15
+ for (const attr of node.getAttributeNames()) {
16
+ if (!_DOMSanitizer.shouldAcceptAttribute(attr)) {
17
+ node.removeAttribute(attr);
93
18
  }
94
19
  }
95
- return true;
96
- }
97
- static shouldAcceptAttribute(attribute) {
98
- if (!_DOMSanitizer.IsValidAttributeName(attribute)) {
99
- console.warn("Invalid attribute name", attribute);
100
- return false;
101
- }
102
- return !attribute.startsWith("on");
103
- }
104
- };
105
- var websocketProtocol = "networked-dom-v0.1";
106
- var startingBackoffTimeMilliseconds = 100;
107
- var maximumBackoffTimeMilliseconds = 1e4;
108
- var maximumWebsocketConnectionTimeout = 5e3;
109
- var NetworkedDOMWebsocketStatus = /* @__PURE__ */ ((NetworkedDOMWebsocketStatus2) => {
110
- NetworkedDOMWebsocketStatus2[NetworkedDOMWebsocketStatus2["Connecting"] = 0] = "Connecting";
111
- NetworkedDOMWebsocketStatus2[NetworkedDOMWebsocketStatus2["Connected"] = 1] = "Connected";
112
- NetworkedDOMWebsocketStatus2[NetworkedDOMWebsocketStatus2["Reconnecting"] = 2] = "Reconnecting";
113
- NetworkedDOMWebsocketStatus2[NetworkedDOMWebsocketStatus2["Disconnected"] = 3] = "Disconnected";
114
- return NetworkedDOMWebsocketStatus2;
115
- })(NetworkedDOMWebsocketStatus || {});
116
- var NetworkedDOMWebsocket2 = class {
117
- constructor(url, websocketFactory, parentElement, timeCallback, statusUpdateCallback) {
118
- this.idToElement = /* @__PURE__ */ new Map();
119
- this.elementToId = /* @__PURE__ */ new Map();
120
- this.websocket = null;
121
- this.currentRoot = null;
122
- this.stopped = false;
123
- this.backoffTime = startingBackoffTimeMilliseconds;
124
- this.status = null;
125
- this.url = url;
126
- this.websocketFactory = websocketFactory;
127
- this.parentElement = parentElement;
128
- this.timeCallback = timeCallback || (() => {
129
- });
130
- this.statusUpdateCallback = statusUpdateCallback || (() => {
131
- });
132
- this.setStatus(
133
- 0
134
- /* Connecting */
135
- );
136
- this.startWebSocketConnectionAttempt();
137
- }
138
- static createWebSocket(url) {
139
- return new WebSocket(url, [websocketProtocol]);
140
- }
141
- setStatus(status) {
142
- if (this.status !== status) {
143
- this.status = status;
144
- this.statusUpdateCallback(status);
145
- }
146
- }
147
- isHTMLElement(node) {
148
- if (node instanceof HTMLElement) {
149
- return true;
150
- }
151
- if (!this.parentElement.ownerDocument.defaultView) {
152
- return false;
153
- }
154
- return node instanceof this.parentElement.ownerDocument.defaultView.HTMLElement;
155
20
  }
156
- isText(node) {
157
- if (node instanceof Text) {
158
- return true;
159
- }
160
- if (!this.parentElement.ownerDocument.defaultView) {
161
- return false;
162
- }
163
- return node instanceof this.parentElement.ownerDocument.defaultView.Text;
21
+ for (let i = 0; i < node.childNodes.length; i++) {
22
+ _DOMSanitizer.sanitise(node.childNodes[i]);
164
23
  }
165
- createWebsocketWithTimeout(timeout) {
166
- return new Promise((resolve, reject) => {
167
- const timeoutId = setTimeout(() => {
168
- reject(new Error("websocket connection timed out"));
169
- }, timeout);
170
- const websocket = this.websocketFactory(this.url);
171
- websocket.addEventListener("open", () => {
172
- clearTimeout(timeoutId);
173
- this.websocket = websocket;
174
- websocket.addEventListener("message", (event) => {
175
- if (websocket !== this.websocket) {
176
- console.log("Ignoring websocket message event because it is no longer current");
177
- websocket.close();
178
- return;
179
- }
180
- this.handleIncomingWebsocketMessage(event);
181
- });
182
- const onWebsocketClose = async () => {
183
- const hadContents = this.currentRoot !== null;
184
- this.clearContents();
185
- if (this.stopped) {
186
- this.setStatus(
187
- 3
188
- /* Disconnected */
189
- );
190
- return;
191
- }
192
- if (!hadContents) {
193
- await this.waitBackoffTime();
194
- }
195
- this.setStatus(
196
- 2
197
- /* Reconnecting */
198
- );
199
- this.startWebSocketConnectionAttempt();
200
- };
201
- websocket.addEventListener("close", (e) => {
202
- if (websocket !== this.websocket) {
203
- console.warn("Ignoring websocket close event because it is no longer current");
204
- return;
205
- }
206
- console.log("NetworkedDOMWebsocket close", e);
207
- onWebsocketClose();
208
- });
209
- websocket.addEventListener("error", (e) => {
210
- if (websocket !== this.websocket) {
211
- console.log("Ignoring websocket error event because it is no longer current");
212
- return;
213
- }
214
- console.error("NetworkedDOMWebsocket error", e);
215
- onWebsocketClose();
216
- });
217
- resolve(websocket);
218
- });
219
- websocket.addEventListener("error", (e) => {
220
- clearTimeout(timeoutId);
221
- reject(e);
222
- });
223
- });
224
- }
225
- async waitBackoffTime() {
226
- console.warn(`Websocket connection to '${this.url}' failed: retrying in ${this.backoffTime}ms`);
227
- await new Promise((resolve) => setTimeout(resolve, this.backoffTime));
228
- this.backoffTime = Math.min(
229
- // Introduce a small amount of randomness to prevent clients from retrying in lockstep
230
- this.backoffTime * (1.5 + Math.random() * 0.5),
231
- maximumBackoffTimeMilliseconds
232
- );
24
+ }
25
+ }
26
+ static IsASCIIDigit(c) {
27
+ return c >= "0" && c <= "9";
28
+ }
29
+ static IsASCIIAlpha(c) {
30
+ return c >= "a" && c <= "z";
31
+ }
32
+ static IsValidAttributeName(characters) {
33
+ const c = characters[0];
34
+ if (!(_DOMSanitizer.IsASCIIAlpha(c) || c === ":" || c === "_")) {
35
+ return false;
36
+ }
37
+ for (let i = 1; i < characters.length; i++) {
38
+ const c2 = characters[i];
39
+ if (!(_DOMSanitizer.IsASCIIDigit(c2) || _DOMSanitizer.IsASCIIAlpha(c2) || c2 === ":" || c2 === "_" || c2 === "-" || c2 === ".")) {
40
+ return false;
233
41
  }
234
- async startWebSocketConnectionAttempt() {
235
- if (this.stopped) {
236
- return;
237
- }
238
- while (true) {
239
- if (this.stopped) {
42
+ }
43
+ return true;
44
+ }
45
+ static shouldAcceptAttribute(attribute) {
46
+ if (!_DOMSanitizer.IsValidAttributeName(attribute)) {
47
+ console.warn("Invalid attribute name", attribute);
48
+ return false;
49
+ }
50
+ return !attribute.startsWith("on");
51
+ }
52
+ };
53
+ var websocketProtocol = "networked-dom-v0.1";
54
+ var startingBackoffTimeMilliseconds = 100;
55
+ var maximumBackoffTimeMilliseconds = 1e4;
56
+ var maximumWebsocketConnectionTimeout = 5e3;
57
+ var NetworkedDOMWebsocket = class {
58
+ constructor(url, websocketFactory, parentElement, timeCallback, statusUpdateCallback) {
59
+ this.idToElement = /* @__PURE__ */ new Map();
60
+ this.elementToId = /* @__PURE__ */ new Map();
61
+ this.websocket = null;
62
+ this.currentRoot = null;
63
+ this.stopped = false;
64
+ this.backoffTime = startingBackoffTimeMilliseconds;
65
+ this.status = null;
66
+ this.url = url;
67
+ this.websocketFactory = websocketFactory;
68
+ this.parentElement = parentElement;
69
+ this.timeCallback = timeCallback || (() => {
70
+ });
71
+ this.statusUpdateCallback = statusUpdateCallback || (() => {
72
+ });
73
+ this.setStatus(
74
+ 0
75
+ /* Connecting */
76
+ );
77
+ this.startWebSocketConnectionAttempt();
78
+ }
79
+ static createWebSocket(url) {
80
+ return new WebSocket(url, [websocketProtocol]);
81
+ }
82
+ setStatus(status) {
83
+ if (this.status !== status) {
84
+ this.status = status;
85
+ this.statusUpdateCallback(status);
86
+ }
87
+ }
88
+ isHTMLElement(node) {
89
+ if (node instanceof HTMLElement) {
90
+ return true;
91
+ }
92
+ if (!this.parentElement.ownerDocument.defaultView) {
93
+ return false;
94
+ }
95
+ return node instanceof this.parentElement.ownerDocument.defaultView.HTMLElement;
96
+ }
97
+ isText(node) {
98
+ if (node instanceof Text) {
99
+ return true;
100
+ }
101
+ if (!this.parentElement.ownerDocument.defaultView) {
102
+ return false;
103
+ }
104
+ return node instanceof this.parentElement.ownerDocument.defaultView.Text;
105
+ }
106
+ createWebsocketWithTimeout(timeout) {
107
+ return new Promise((resolve, reject) => {
108
+ const timeoutId = setTimeout(() => {
109
+ reject(new Error("websocket connection timed out"));
110
+ }, timeout);
111
+ const websocket = this.websocketFactory(this.url);
112
+ websocket.addEventListener("open", () => {
113
+ clearTimeout(timeoutId);
114
+ this.websocket = websocket;
115
+ websocket.addEventListener("message", (event) => {
116
+ if (websocket !== this.websocket) {
117
+ console.log("Ignoring websocket message event because it is no longer current");
118
+ websocket.close();
240
119
  return;
241
120
  }
242
- try {
243
- await this.createWebsocketWithTimeout(maximumWebsocketConnectionTimeout);
244
- break;
245
- } catch (e) {
121
+ this.handleIncomingWebsocketMessage(event);
122
+ });
123
+ const onWebsocketClose = async () => {
124
+ const hadContents = this.currentRoot !== null;
125
+ this.clearContents();
126
+ if (this.stopped) {
246
127
  this.setStatus(
247
- 2
248
- /* Reconnecting */
128
+ 3
129
+ /* Disconnected */
249
130
  );
131
+ return;
132
+ }
133
+ if (!hadContents) {
250
134
  await this.waitBackoffTime();
251
135
  }
252
- }
253
- }
254
- handleIncomingWebsocketMessage(event) {
255
- const messages = JSON.parse(event.data);
256
- for (const message of messages) {
257
- switch (message.type) {
258
- case "error":
259
- console.error("Error from server", message);
260
- break;
261
- case "warning":
262
- console.warn("Warning from server", message);
263
- break;
264
- default: {
265
- if (message.documentTime) {
266
- if (this.timeCallback) {
267
- this.timeCallback(message.documentTime);
268
- }
269
- }
270
- switch (message.type) {
271
- case "snapshot":
272
- this.handleSnapshot(message);
273
- break;
274
- case "attributeChange":
275
- this.handleAttributeChange(message);
276
- break;
277
- case "childrenChanged":
278
- this.handleChildrenChanged(message);
279
- break;
280
- case "textChanged":
281
- this.handleTextChanged(message);
282
- break;
283
- case "ping":
284
- this.send({
285
- type: "pong",
286
- pong: message.ping
287
- });
288
- break;
289
- default:
290
- console.warn("unknown message type", message);
291
- break;
292
- }
293
- }
136
+ this.setStatus(
137
+ 2
138
+ /* Reconnecting */
139
+ );
140
+ this.startWebSocketConnectionAttempt();
141
+ };
142
+ websocket.addEventListener("close", (e) => {
143
+ if (websocket !== this.websocket) {
144
+ console.warn("Ignoring websocket close event because it is no longer current");
145
+ return;
294
146
  }
295
- }
296
- }
297
- stop() {
298
- this.stopped = true;
299
- if (this.websocket !== null) {
300
- this.websocket.close();
301
- this.websocket = null;
302
- }
147
+ console.log("NetworkedDOMWebsocket close", e);
148
+ onWebsocketClose();
149
+ });
150
+ websocket.addEventListener("error", (e) => {
151
+ if (websocket !== this.websocket) {
152
+ console.log("Ignoring websocket error event because it is no longer current");
153
+ return;
154
+ }
155
+ console.error("NetworkedDOMWebsocket error", e);
156
+ onWebsocketClose();
157
+ });
158
+ resolve(websocket);
159
+ });
160
+ websocket.addEventListener("error", (e) => {
161
+ clearTimeout(timeoutId);
162
+ reject(e);
163
+ });
164
+ });
165
+ }
166
+ async waitBackoffTime() {
167
+ console.warn(`Websocket connection to '${this.url}' failed: retrying in ${this.backoffTime}ms`);
168
+ await new Promise((resolve) => setTimeout(resolve, this.backoffTime));
169
+ this.backoffTime = Math.min(
170
+ // Introduce a small amount of randomness to prevent clients from retrying in lockstep
171
+ this.backoffTime * (1.5 + Math.random() * 0.5),
172
+ maximumBackoffTimeMilliseconds
173
+ );
174
+ }
175
+ async startWebSocketConnectionAttempt() {
176
+ if (this.stopped) {
177
+ return;
178
+ }
179
+ while (true) {
180
+ if (this.stopped) {
181
+ return;
303
182
  }
304
- handleEvent(element, event) {
305
- const nodeId = this.elementToId.get(element);
306
- if (nodeId === void 0 || nodeId === null) {
307
- throw new Error("Element not found");
308
- }
309
- console.log(
310
- `Sending event to websocket: "${event.type}" on node: ${nodeId} type: ${element.tagName}`
183
+ try {
184
+ await this.createWebsocketWithTimeout(maximumWebsocketConnectionTimeout);
185
+ break;
186
+ } catch (e) {
187
+ this.setStatus(
188
+ 2
189
+ /* Reconnecting */
311
190
  );
312
- const detailWithoutElement = {
313
- ...event.detail
314
- };
315
- delete detailWithoutElement.element;
316
- const remoteEvent = {
317
- type: "event",
318
- nodeId,
319
- name: event.type,
320
- bubbles: event.bubbles,
321
- params: detailWithoutElement
322
- };
323
- this.send(remoteEvent);
324
- }
325
- send(fromClientMessage) {
326
- if (!this.websocket) {
327
- throw new Error("No websocket created");
328
- }
329
- this.websocket.send(JSON.stringify(fromClientMessage));
330
- }
331
- handleTextChanged(message) {
332
- const { nodeId, text } = message;
333
- if (nodeId === void 0 || nodeId === null) {
334
- console.warn("No nodeId in textChanged message");
335
- return;
336
- }
337
- const node = this.idToElement.get(nodeId);
338
- if (!node) {
339
- throw new Error("No node found for textChanged message");
340
- }
341
- if (!this.isText(node)) {
342
- throw new Error("Node for textChanged message is not a Text node");
343
- }
344
- node.textContent = text;
191
+ await this.waitBackoffTime();
345
192
  }
346
- handleChildrenChanged(message) {
347
- const { nodeId, addedNodes, removedNodes, previousNodeId } = message;
348
- if (nodeId === void 0 || nodeId === null) {
349
- console.warn("No nodeId in childrenChanged message");
350
- return;
351
- }
352
- const parent = this.idToElement.get(nodeId);
353
- if (!parent) {
354
- throw new Error("No parent found for childrenChanged message");
355
- }
356
- if (!parent.isConnected) {
357
- console.error("Parent is not connected", parent);
358
- }
359
- if (!this.isHTMLElement(parent)) {
360
- throw new Error("Parent is not an HTMLElement (that supports children)");
361
- }
362
- let previousElement;
363
- if (previousNodeId) {
364
- previousElement = this.idToElement.get(previousNodeId);
365
- if (!previousElement) {
366
- throw new Error("No previous element found for childrenChanged message");
367
- }
368
- }
369
- for (const addedNode of addedNodes) {
370
- const childElement = this.handleNewElement(addedNode);
371
- if (childElement) {
372
- if (previousElement) {
373
- const nextElement = previousElement.nextSibling;
374
- if (nextElement) {
375
- parent.insertBefore(childElement, nextElement);
376
- } else {
377
- parent.append(childElement);
378
- }
379
- } else {
380
- parent.append(childElement);
193
+ }
194
+ }
195
+ handleIncomingWebsocketMessage(event) {
196
+ const messages = JSON.parse(event.data);
197
+ for (const message of messages) {
198
+ switch (message.type) {
199
+ case "error":
200
+ console.error("Error from server", message);
201
+ break;
202
+ case "warning":
203
+ console.warn("Warning from server", message);
204
+ break;
205
+ default: {
206
+ if (message.documentTime) {
207
+ if (this.timeCallback) {
208
+ this.timeCallback(message.documentTime);
381
209
  }
382
210
  }
383
- }
384
- for (const removedNode of removedNodes) {
385
- const childElement = this.idToElement.get(removedNode);
386
- if (!childElement) {
387
- throw new Error(`Child element not found: ${removedNode}`);
388
- }
389
- this.elementToId.delete(childElement);
390
- this.idToElement.delete(removedNode);
391
- parent.removeChild(childElement);
392
- if (this.isHTMLElement(childElement)) {
393
- this.removeChildElementIds(childElement);
394
- }
395
- }
396
- }
397
- removeChildElementIds(parent) {
398
- for (let i = 0; i < parent.children.length; i++) {
399
- const child = parent.children[i];
400
- const childId = this.elementToId.get(child);
401
- if (!childId) {
402
- console.error("Inner child of removed element had no id", child);
403
- } else {
404
- this.elementToId.delete(child);
405
- this.idToElement.delete(childId);
211
+ switch (message.type) {
212
+ case "snapshot":
213
+ this.handleSnapshot(message);
214
+ break;
215
+ case "attributeChange":
216
+ this.handleAttributeChange(message);
217
+ break;
218
+ case "childrenChanged":
219
+ this.handleChildrenChanged(message);
220
+ break;
221
+ case "textChanged":
222
+ this.handleTextChanged(message);
223
+ break;
224
+ case "ping":
225
+ this.send({
226
+ type: "pong",
227
+ pong: message.ping
228
+ });
229
+ break;
230
+ default:
231
+ console.warn("unknown message type", message);
232
+ break;
406
233
  }
407
- this.removeChildElementIds(child);
408
234
  }
409
235
  }
410
- handleSnapshot(message) {
411
- this.backoffTime = startingBackoffTimeMilliseconds;
412
- this.setStatus(
413
- 1
414
- /* Connected */
415
- );
416
- if (this.currentRoot) {
417
- this.currentRoot.remove();
418
- this.currentRoot = null;
419
- this.elementToId.clear();
420
- this.idToElement.clear();
421
- }
422
- const element = this.handleNewElement(message.snapshot);
423
- if (!element) {
424
- throw new Error("Snapshot element not created");
425
- }
426
- if (!this.isHTMLElement(element)) {
427
- throw new Error("Snapshot element is not an HTMLElement");
428
- }
429
- this.currentRoot = element;
430
- this.parentElement.append(element);
236
+ }
237
+ }
238
+ stop() {
239
+ this.stopped = true;
240
+ if (this.websocket !== null) {
241
+ this.websocket.close();
242
+ this.websocket = null;
243
+ }
244
+ }
245
+ handleEvent(element, event) {
246
+ const nodeId = this.elementToId.get(element);
247
+ if (nodeId === void 0 || nodeId === null) {
248
+ throw new Error("Element not found");
249
+ }
250
+ console.log(
251
+ `Sending event to websocket: "${event.type}" on node: ${nodeId} type: ${element.tagName}`
252
+ );
253
+ const detailWithoutElement = {
254
+ ...event.detail
255
+ };
256
+ delete detailWithoutElement.element;
257
+ const remoteEvent = {
258
+ type: "event",
259
+ nodeId,
260
+ name: event.type,
261
+ bubbles: event.bubbles,
262
+ params: detailWithoutElement
263
+ };
264
+ this.send(remoteEvent);
265
+ }
266
+ send(fromClientMessage) {
267
+ if (!this.websocket) {
268
+ throw new Error("No websocket created");
269
+ }
270
+ this.websocket.send(JSON.stringify(fromClientMessage));
271
+ }
272
+ handleTextChanged(message) {
273
+ const { nodeId, text } = message;
274
+ if (nodeId === void 0 || nodeId === null) {
275
+ console.warn("No nodeId in textChanged message");
276
+ return;
277
+ }
278
+ const node = this.idToElement.get(nodeId);
279
+ if (!node) {
280
+ throw new Error("No node found for textChanged message");
281
+ }
282
+ if (!this.isText(node)) {
283
+ throw new Error("Node for textChanged message is not a Text node");
284
+ }
285
+ node.textContent = text;
286
+ }
287
+ handleChildrenChanged(message) {
288
+ const { nodeId, addedNodes, removedNodes, previousNodeId } = message;
289
+ if (nodeId === void 0 || nodeId === null) {
290
+ console.warn("No nodeId in childrenChanged message");
291
+ return;
292
+ }
293
+ const parent = this.idToElement.get(nodeId);
294
+ if (!parent) {
295
+ throw new Error("No parent found for childrenChanged message");
296
+ }
297
+ if (!parent.isConnected) {
298
+ console.error("Parent is not connected", parent);
299
+ }
300
+ if (!this.isHTMLElement(parent)) {
301
+ throw new Error("Parent is not an HTMLElement (that supports children)");
302
+ }
303
+ let previousElement;
304
+ if (previousNodeId) {
305
+ previousElement = this.idToElement.get(previousNodeId);
306
+ if (!previousElement) {
307
+ throw new Error("No previous element found for childrenChanged message");
431
308
  }
432
- handleAttributeChange(message) {
433
- const { nodeId, attribute, newValue } = message;
434
- if (nodeId === void 0 || nodeId === null) {
435
- console.warn("No nodeId in attributeChange message");
436
- return;
437
- }
438
- const element = this.idToElement.get(nodeId);
439
- if (element) {
440
- if (this.isHTMLElement(element)) {
441
- if (newValue === null) {
442
- element.removeAttribute(attribute);
443
- } else {
444
- if (DOMSanitizer.shouldAcceptAttribute(attribute)) {
445
- element.setAttribute(attribute, newValue);
446
- }
447
- }
309
+ }
310
+ for (const addedNode of addedNodes) {
311
+ const childElement = this.handleNewElement(addedNode);
312
+ if (childElement) {
313
+ if (previousElement) {
314
+ const nextElement = previousElement.nextSibling;
315
+ if (nextElement) {
316
+ parent.insertBefore(childElement, nextElement);
448
317
  } else {
449
- console.error("Element is not an HTMLElement and cannot support attributes", element);
318
+ parent.append(childElement);
450
319
  }
451
320
  } else {
452
- console.error("No element found for attributeChange message");
321
+ parent.append(childElement);
453
322
  }
454
323
  }
455
- handleNewElement(message) {
456
- if (message.type === "text") {
457
- const { nodeId: nodeId2, text: text2 } = message;
458
- const textNode = document.createTextNode("");
459
- textNode.textContent = text2;
460
- this.idToElement.set(nodeId2, textNode);
461
- this.elementToId.set(textNode, nodeId2);
462
- return textNode;
463
- }
464
- const { tag, nodeId, attributes, children, text } = message;
465
- if (nodeId === void 0 || nodeId === null) {
466
- console.warn("No nodeId in handleNewElement message", message);
467
- return null;
468
- }
469
- if (this.idToElement.has(nodeId)) {
470
- console.error(
471
- "Received nodeId to add that is already present",
472
- nodeId,
473
- this.idToElement.get(nodeId)
474
- );
475
- }
476
- if (tag === "#text") {
477
- const textNode = document.createTextNode("");
478
- textNode.textContent = text || null;
479
- this.idToElement.set(nodeId, textNode);
480
- this.elementToId.set(textNode, nodeId);
481
- return textNode;
482
- }
483
- let element;
484
- try {
485
- element = document.createElement(tag);
486
- } catch (e) {
487
- console.error(`Error creating element: (${tag})`, e);
488
- element = document.createElement("div");
489
- }
490
- this.idToElement.set(nodeId, element);
491
- this.elementToId.set(element, nodeId);
492
- for (const key in attributes) {
493
- if (DOMSanitizer.shouldAcceptAttribute(key)) {
494
- const value = attributes[key];
495
- element.setAttribute(key, value);
496
- }
497
- }
498
- if (children) {
499
- for (const child of children) {
500
- const childElement = this.handleNewElement(child);
501
- if (childElement) {
502
- element.append(childElement);
503
- }
324
+ }
325
+ for (const removedNode of removedNodes) {
326
+ const childElement = this.idToElement.get(removedNode);
327
+ if (!childElement) {
328
+ throw new Error(`Child element not found: ${removedNode}`);
329
+ }
330
+ this.elementToId.delete(childElement);
331
+ this.idToElement.delete(removedNode);
332
+ parent.removeChild(childElement);
333
+ if (this.isHTMLElement(childElement)) {
334
+ this.removeChildElementIds(childElement);
335
+ }
336
+ }
337
+ }
338
+ removeChildElementIds(parent) {
339
+ for (let i = 0; i < parent.children.length; i++) {
340
+ const child = parent.children[i];
341
+ const childId = this.elementToId.get(child);
342
+ if (!childId) {
343
+ console.error("Inner child of removed element had no id", child);
344
+ } else {
345
+ this.elementToId.delete(child);
346
+ this.idToElement.delete(childId);
347
+ }
348
+ this.removeChildElementIds(child);
349
+ }
350
+ }
351
+ handleSnapshot(message) {
352
+ this.backoffTime = startingBackoffTimeMilliseconds;
353
+ this.setStatus(
354
+ 1
355
+ /* Connected */
356
+ );
357
+ if (this.currentRoot) {
358
+ this.currentRoot.remove();
359
+ this.currentRoot = null;
360
+ this.elementToId.clear();
361
+ this.idToElement.clear();
362
+ }
363
+ const element = this.handleNewElement(message.snapshot);
364
+ if (!element) {
365
+ throw new Error("Snapshot element not created");
366
+ }
367
+ if (!this.isHTMLElement(element)) {
368
+ throw new Error("Snapshot element is not an HTMLElement");
369
+ }
370
+ this.currentRoot = element;
371
+ this.parentElement.append(element);
372
+ }
373
+ handleAttributeChange(message) {
374
+ const { nodeId, attribute, newValue } = message;
375
+ if (nodeId === void 0 || nodeId === null) {
376
+ console.warn("No nodeId in attributeChange message");
377
+ return;
378
+ }
379
+ const element = this.idToElement.get(nodeId);
380
+ if (element) {
381
+ if (this.isHTMLElement(element)) {
382
+ if (newValue === null) {
383
+ element.removeAttribute(attribute);
384
+ } else {
385
+ if (DOMSanitizer.shouldAcceptAttribute(attribute)) {
386
+ element.setAttribute(attribute, newValue);
504
387
  }
505
388
  }
506
- return element;
389
+ } else {
390
+ console.error("Element is not an HTMLElement and cannot support attributes", element);
507
391
  }
508
- clearContents() {
509
- this.idToElement.clear();
510
- this.elementToId.clear();
511
- if (this.currentRoot) {
512
- this.currentRoot.remove();
513
- this.currentRoot = null;
392
+ } else {
393
+ console.error("No element found for attributeChange message");
394
+ }
395
+ }
396
+ handleNewElement(message) {
397
+ if (message.type === "text") {
398
+ const { nodeId: nodeId2, text: text2 } = message;
399
+ const textNode = document.createTextNode("");
400
+ textNode.textContent = text2;
401
+ this.idToElement.set(nodeId2, textNode);
402
+ this.elementToId.set(textNode, nodeId2);
403
+ return textNode;
404
+ }
405
+ const { tag, nodeId, attributes, children, text } = message;
406
+ if (nodeId === void 0 || nodeId === null) {
407
+ console.warn("No nodeId in handleNewElement message", message);
408
+ return null;
409
+ }
410
+ if (this.idToElement.has(nodeId)) {
411
+ console.error(
412
+ "Received nodeId to add that is already present",
413
+ nodeId,
414
+ this.idToElement.get(nodeId)
415
+ );
416
+ }
417
+ if (tag === "#text") {
418
+ const textNode = document.createTextNode("");
419
+ textNode.textContent = text || null;
420
+ this.idToElement.set(nodeId, textNode);
421
+ this.elementToId.set(textNode, nodeId);
422
+ return textNode;
423
+ }
424
+ let element;
425
+ try {
426
+ element = document.createElement(tag);
427
+ } catch (e) {
428
+ console.error(`Error creating element: (${tag})`, e);
429
+ element = document.createElement("div");
430
+ }
431
+ this.idToElement.set(nodeId, element);
432
+ this.elementToId.set(element, nodeId);
433
+ for (const key in attributes) {
434
+ if (DOMSanitizer.shouldAcceptAttribute(key)) {
435
+ const value = attributes[key];
436
+ element.setAttribute(key, value);
437
+ }
438
+ }
439
+ if (children) {
440
+ for (const child of children) {
441
+ const childElement = this.handleNewElement(child);
442
+ if (childElement) {
443
+ element.append(childElement);
514
444
  }
515
445
  }
516
- };
446
+ }
447
+ return element;
517
448
  }
518
- });
449
+ clearContents() {
450
+ this.idToElement.clear();
451
+ this.elementToId.clear();
452
+ if (this.currentRoot) {
453
+ this.currentRoot.remove();
454
+ this.currentRoot = null;
455
+ }
456
+ }
457
+ };
519
458
 
520
459
  // src/index.ts
521
- var import_networked_dom_web = __toESM(require_build());
522
460
  var thisScript = document.currentScript;
523
461
  var scriptUrl = new URL(thisScript.src);
524
462
  (function() {
@@ -543,9 +481,9 @@ var scriptUrl = new URL(thisScript.src);
543
481
  eventHandler(event.target, event);
544
482
  return false;
545
483
  });
546
- const websocket = new import_networked_dom_web.NetworkedDOMWebsocket(
484
+ const websocket = new NetworkedDOMWebsocket(
547
485
  documentWebsocketUrl,
548
- import_networked_dom_web.NetworkedDOMWebsocket.createWebSocket,
486
+ NetworkedDOMWebsocket.createWebSocket,
549
487
  remoteDocumentHolder
550
488
  );
551
489
  overriddenHandler = (element, event) => {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../networked-dom-web/src/index.ts", "../../networked-dom-web/src/DOMSanitizer.ts", "../../networked-dom-web/src/NetworkedDOMWebsocket.ts", "../src/index.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 SnapshotMessage,\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 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\n private 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\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 switch (message.type) {\n case \"error\":\n console.error(\"Error from server\", message);\n break;\n case \"warning\":\n console.warn(\"Warning from server\", message);\n break;\n default: {\n if (message.documentTime) {\n if (this.timeCallback) {\n this.timeCallback(message.documentTime);\n }\n }\n switch (message.type) {\n case \"snapshot\":\n this.handleSnapshot(message);\n break;\n case \"attributeChange\":\n this.handleAttributeChange(message);\n break;\n case \"childrenChanged\":\n this.handleChildrenChanged(message);\n break;\n case \"textChanged\":\n this.handleTextChanged(message);\n break;\n case \"ping\":\n this.send({\n type: \"pong\",\n pong: message.ping,\n });\n break;\n default:\n console.warn(\"unknown message type\", message);\n break;\n }\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 (!this.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 (!this.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 (this.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 handleSnapshot(message: SnapshotMessage) {\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 MElement constructors 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 (!this.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 }\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 (this.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", "import { NetworkedDOMWebsocket } from \"@mml-io/networked-dom-web\";\n\nconst thisScript = document.currentScript as HTMLScriptElement;\nconst scriptUrl = new URL(thisScript.src);\n\n(function () {\n const websocketUrl = scriptUrl.searchParams.get(\"websocketUrl\");\n if (!websocketUrl) {\n console.error(\"websocketUrl not set\");\n return;\n }\n window.addEventListener(\"load\", () => {\n const documentWebsocketUrls = websocketUrl.split(\",\");\n\n for (const documentWebsocketUrl of documentWebsocketUrls) {\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 const remoteDocumentHolder = document.createElement(\"div\");\n document.body.append(remoteDocumentHolder);\n\n remoteDocumentHolder.addEventListener(\"click\", (event: Event) => {\n eventHandler(event.target as HTMLElement, event as CustomEvent);\n return false;\n });\n\n const websocket = new NetworkedDOMWebsocket(\n documentWebsocketUrl,\n NetworkedDOMWebsocket.createWebSocket,\n remoteDocumentHolder,\n );\n overriddenHandler = (element: HTMLElement, event: CustomEvent) => {\n websocket.handleEvent(element, event);\n };\n }\n });\n})();\n"],
5
- "mappings": "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",
6
- "names": ["NetworkedDOMWebsocket", "module", "c", "NetworkedDOMWebsocketStatus", "nodeId", "text"]
3
+ "sources": ["../../networked-dom-web/src/DOMSanitizer.ts", "../../networked-dom-web/src/NetworkedDOMWebsocket.ts", "../src/index.ts"],
4
+ "sourcesContent": ["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 SnapshotMessage,\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 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\n private 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\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 switch (message.type) {\n case \"error\":\n console.error(\"Error from server\", message);\n break;\n case \"warning\":\n console.warn(\"Warning from server\", message);\n break;\n default: {\n if (message.documentTime) {\n if (this.timeCallback) {\n this.timeCallback(message.documentTime);\n }\n }\n switch (message.type) {\n case \"snapshot\":\n this.handleSnapshot(message);\n break;\n case \"attributeChange\":\n this.handleAttributeChange(message);\n break;\n case \"childrenChanged\":\n this.handleChildrenChanged(message);\n break;\n case \"textChanged\":\n this.handleTextChanged(message);\n break;\n case \"ping\":\n this.send({\n type: \"pong\",\n pong: message.ping,\n });\n break;\n default:\n console.warn(\"unknown message type\", message);\n break;\n }\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 (!this.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 (!this.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 (this.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 handleSnapshot(message: SnapshotMessage) {\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 MElement constructors 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 (!this.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 }\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 (this.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", "import { NetworkedDOMWebsocket } from \"@mml-io/networked-dom-web\";\n\nconst thisScript = document.currentScript as HTMLScriptElement;\nconst scriptUrl = new URL(thisScript.src);\n\n(function () {\n const websocketUrl = scriptUrl.searchParams.get(\"websocketUrl\");\n if (!websocketUrl) {\n console.error(\"websocketUrl not set\");\n return;\n }\n window.addEventListener(\"load\", () => {\n const documentWebsocketUrls = websocketUrl.split(\",\");\n\n for (const documentWebsocketUrl of documentWebsocketUrls) {\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 const remoteDocumentHolder = document.createElement(\"div\");\n document.body.append(remoteDocumentHolder);\n\n remoteDocumentHolder.addEventListener(\"click\", (event: Event) => {\n eventHandler(event.target as HTMLElement, event as CustomEvent);\n return false;\n });\n\n const websocket = new NetworkedDOMWebsocket(\n documentWebsocketUrl,\n NetworkedDOMWebsocket.createWebSocket,\n remoteDocumentHolder,\n );\n overriddenHandler = (element: HTMLElement, event: CustomEvent) => {\n websocket.handleEvent(element, event);\n };\n }\n });\n})();\n"],
5
+ "mappings": "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",
6
+ "names": ["c", "nodeId", "text"]
7
7
  }
package/package.json CHANGED
@@ -1,22 +1,23 @@
1
1
  {
2
2
  "name": "@mml-io/networked-dom-web-client",
3
- "version": "0.6.2",
3
+ "version": "0.8.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
7
7
  "files": [
8
8
  "/build"
9
9
  ],
10
+ "type": "module",
10
11
  "main": "build/index.js",
11
12
  "scripts": {
12
13
  "type-check": "tsc --noEmit",
13
14
  "build": "tsx ./build.ts --build",
14
15
  "iterate": "cross-env PORT=28892 tsx ./build.ts --serve",
15
- "lint": "eslint \"./src/**/*.{js,jsx,ts,tsx}\" --max-warnings 0",
16
- "lint-fix": "eslint \"./src/**/*.{js,jsx,ts,tsx}\" --fix"
16
+ "lint": "eslint \"./{src,test}/**/*.{js,jsx,ts,tsx}\" --max-warnings 0",
17
+ "lint-fix": "eslint \"./{src,test}/**/*.{js,jsx,ts,tsx}\" --fix"
17
18
  },
18
19
  "dependencies": {
19
- "@mml-io/networked-dom-web": "^0.6.2"
20
+ "@mml-io/networked-dom-web": "^0.8.0"
20
21
  },
21
- "gitHead": "0ac70ab8ff9164c5dc2b9541ff7319a94ebaccf4"
22
+ "gitHead": "136413e409957ca6d76373d8c511912536ea6e1c"
22
23
  }