@mml-io/networked-dom-document 0.0.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/index.d.ts +1 -0
- package/build/index.js +1133 -0
- package/build/index.js.map +7 -0
- package/package.json +24 -0
- package/src/EditableNetworkedDOM.ts +128 -0
- package/src/NetworkedDOM.ts +717 -0
- package/src/common.ts +23 -0
- package/src/diffing.ts +542 -0
- package/src/index.ts +2 -0
package/build/index.js
ADDED
|
@@ -0,0 +1,1133 @@
|
|
|
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 __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
|
|
29
|
+
// src/index.ts
|
|
30
|
+
var src_exports = {};
|
|
31
|
+
__export(src_exports, {
|
|
32
|
+
EditableNetworkedDOM: () => EditableNetworkedDOM,
|
|
33
|
+
NetworkedDOM: () => NetworkedDOM,
|
|
34
|
+
defaultWebsocketSubProtocol: () => defaultWebsocketSubProtocol,
|
|
35
|
+
networkedDomProtocolSubProtocol_v0_1: () => networkedDomProtocolSubProtocol_v0_1
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(src_exports);
|
|
38
|
+
|
|
39
|
+
// src/NetworkedDOM.ts
|
|
40
|
+
var import_rfc6902 = require("rfc6902");
|
|
41
|
+
|
|
42
|
+
// src/diffing.ts
|
|
43
|
+
var rfc6902 = __toESM(require("rfc6902"));
|
|
44
|
+
var visibleToAttrName = "visible-to";
|
|
45
|
+
var hiddenFromAttrName = "hidden-from";
|
|
46
|
+
function diffFromApplicationOfStaticVirtualDomMutationRecordToConnection(mutation, parentNode, connectionId, visibleNodesForConnection) {
|
|
47
|
+
const virtualDomElement = mutation.target;
|
|
48
|
+
if (mutation.type === "attributes") {
|
|
49
|
+
const visible = visibleNodesForConnection.has(virtualDomElement.nodeId);
|
|
50
|
+
if (!parentNode) {
|
|
51
|
+
throw new Error("Node has no parent");
|
|
52
|
+
}
|
|
53
|
+
const parentNodeId = parentNode.nodeId;
|
|
54
|
+
const shouldBeVisible = shouldShowNodeToConnectionId(virtualDomElement, connectionId) && visibleNodesForConnection.has(parentNodeId);
|
|
55
|
+
const attributeName = mutation.attributeName;
|
|
56
|
+
if (visible && shouldBeVisible) {
|
|
57
|
+
let newValue = null;
|
|
58
|
+
if (virtualDomElement.attributes[attributeName] !== void 0) {
|
|
59
|
+
newValue = virtualDomElement.attributes[attributeName];
|
|
60
|
+
}
|
|
61
|
+
const diff = {
|
|
62
|
+
type: "attributeChange",
|
|
63
|
+
nodeId: virtualDomElement.nodeId,
|
|
64
|
+
attribute: attributeName,
|
|
65
|
+
newValue
|
|
66
|
+
};
|
|
67
|
+
return diff;
|
|
68
|
+
} else if (!visible && shouldBeVisible) {
|
|
69
|
+
visibleNodesForConnection.add(virtualDomElement.nodeId);
|
|
70
|
+
const index = parentNode.childNodes.indexOf(virtualDomElement);
|
|
71
|
+
if (index === -1) {
|
|
72
|
+
throw new Error("Node not found in parent's children");
|
|
73
|
+
}
|
|
74
|
+
let previousNodeId = null;
|
|
75
|
+
if (index > 0) {
|
|
76
|
+
previousNodeId = getNodeIdOfPreviousVisibleSibling(
|
|
77
|
+
parentNode,
|
|
78
|
+
index - 1,
|
|
79
|
+
visibleNodesForConnection
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
const nodeDescription = describeNodeWithChildrenForConnectionId(
|
|
83
|
+
virtualDomElement,
|
|
84
|
+
connectionId,
|
|
85
|
+
visibleNodesForConnection
|
|
86
|
+
);
|
|
87
|
+
if (!nodeDescription) {
|
|
88
|
+
throw new Error("Node description not found");
|
|
89
|
+
}
|
|
90
|
+
const diff = {
|
|
91
|
+
type: "childrenChanged",
|
|
92
|
+
nodeId: parentNodeId,
|
|
93
|
+
previousNodeId,
|
|
94
|
+
addedNodes: [nodeDescription],
|
|
95
|
+
removedNodes: []
|
|
96
|
+
};
|
|
97
|
+
return diff;
|
|
98
|
+
} else if (visible && !shouldBeVisible) {
|
|
99
|
+
removeNodeAndChildrenFromVisibleNodes(virtualDomElement, visibleNodesForConnection);
|
|
100
|
+
const diff = {
|
|
101
|
+
type: "childrenChanged",
|
|
102
|
+
nodeId: parentNodeId,
|
|
103
|
+
previousNodeId: null,
|
|
104
|
+
addedNodes: [],
|
|
105
|
+
removedNodes: [virtualDomElement.nodeId]
|
|
106
|
+
};
|
|
107
|
+
return diff;
|
|
108
|
+
} else if (!visible && !shouldBeVisible) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (!visibleNodesForConnection.has(virtualDomElement.nodeId)) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
if (mutation.type === "characterData") {
|
|
116
|
+
const diff = {
|
|
117
|
+
type: "textChanged",
|
|
118
|
+
nodeId: virtualDomElement.nodeId,
|
|
119
|
+
text: virtualDomElement.textContent || ""
|
|
120
|
+
};
|
|
121
|
+
return diff;
|
|
122
|
+
}
|
|
123
|
+
if (mutation.type === "childList") {
|
|
124
|
+
let previousSibling = mutation.previousSibling;
|
|
125
|
+
let previousNodeId = null;
|
|
126
|
+
if (previousSibling) {
|
|
127
|
+
let previousIndex = virtualDomElement.childNodes.indexOf(previousSibling);
|
|
128
|
+
while (previousIndex !== -1) {
|
|
129
|
+
previousSibling = virtualDomElement.childNodes[previousIndex];
|
|
130
|
+
if (visibleNodesForConnection.has(previousSibling.nodeId)) {
|
|
131
|
+
previousNodeId = previousSibling.nodeId;
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
previousIndex--;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const diff = {
|
|
138
|
+
type: "childrenChanged",
|
|
139
|
+
nodeId: virtualDomElement.nodeId,
|
|
140
|
+
previousNodeId,
|
|
141
|
+
addedNodes: [],
|
|
142
|
+
removedNodes: []
|
|
143
|
+
};
|
|
144
|
+
mutation.addedNodes.forEach((childVirtualDomElement) => {
|
|
145
|
+
const describedNode = describeNodeWithChildrenForConnectionId(
|
|
146
|
+
childVirtualDomElement,
|
|
147
|
+
connectionId,
|
|
148
|
+
visibleNodesForConnection
|
|
149
|
+
);
|
|
150
|
+
if (!describedNode) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
diff.addedNodes.push(describedNode);
|
|
154
|
+
});
|
|
155
|
+
mutation.removedNodes.forEach((childVirtualDomElement) => {
|
|
156
|
+
if (visibleNodesForConnection.has(childVirtualDomElement.nodeId)) {
|
|
157
|
+
removeNodeAndChildrenFromVisibleNodes(childVirtualDomElement, visibleNodesForConnection);
|
|
158
|
+
diff.removedNodes.push(childVirtualDomElement.nodeId);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
if (diff.addedNodes.length > 0 || diff.removedNodes.length > 0) {
|
|
162
|
+
return diff;
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
console.error("Unknown mutation type: " + mutation.type);
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
function getNodeIdOfPreviousVisibleSibling(parentVirtualElement, candidateIndex, visibleNodesForConnection) {
|
|
170
|
+
if (candidateIndex > 0) {
|
|
171
|
+
let previousSiblingIndex = candidateIndex;
|
|
172
|
+
while (previousSiblingIndex >= 0) {
|
|
173
|
+
const previousSibling = parentVirtualElement.childNodes[previousSiblingIndex];
|
|
174
|
+
if (visibleNodesForConnection.has(previousSibling.nodeId)) {
|
|
175
|
+
return previousSibling.nodeId;
|
|
176
|
+
}
|
|
177
|
+
previousSiblingIndex--;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
function shouldShowNodeToConnectionId(virtualDomElement, connectionId) {
|
|
183
|
+
const visibleToAttr = virtualDomElement.attributes[visibleToAttrName];
|
|
184
|
+
const hiddenFromAttr = virtualDomElement.attributes[hiddenFromAttrName];
|
|
185
|
+
const connectionIdString = connectionId.toString();
|
|
186
|
+
if (visibleToAttr !== void 0) {
|
|
187
|
+
const visibleToList = visibleToAttr.split(" ");
|
|
188
|
+
const explicityVisible = visibleToList.includes(connectionIdString);
|
|
189
|
+
if (!explicityVisible) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (hiddenFromAttr !== void 0) {
|
|
194
|
+
const hiddenFromList = hiddenFromAttr.split(" ");
|
|
195
|
+
const explicityHidden = hiddenFromList.includes(connectionIdString);
|
|
196
|
+
if (explicityHidden) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
function describeNodeWithChildrenForConnectionId(virtualDomElement, connectionId, visibleNodesForConnection) {
|
|
203
|
+
if (!shouldShowNodeToConnectionId(virtualDomElement, connectionId)) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
let emittedTagName = virtualDomElement.tag;
|
|
207
|
+
if (emittedTagName === "#document") {
|
|
208
|
+
emittedTagName = "DIV";
|
|
209
|
+
}
|
|
210
|
+
if (emittedTagName === "#text") {
|
|
211
|
+
const textNode = {
|
|
212
|
+
type: "text",
|
|
213
|
+
nodeId: virtualDomElement.nodeId,
|
|
214
|
+
text: virtualDomElement.textContent || ""
|
|
215
|
+
};
|
|
216
|
+
visibleNodesForConnection.add(textNode.nodeId);
|
|
217
|
+
return textNode;
|
|
218
|
+
} else {
|
|
219
|
+
const node = {
|
|
220
|
+
type: "element",
|
|
221
|
+
nodeId: virtualDomElement.nodeId,
|
|
222
|
+
tag: emittedTagName,
|
|
223
|
+
attributes: virtualDomElement.attributes,
|
|
224
|
+
children: [],
|
|
225
|
+
text: virtualDomElement.textContent
|
|
226
|
+
};
|
|
227
|
+
visibleNodesForConnection.add(node.nodeId);
|
|
228
|
+
for (const child of virtualDomElement.childNodes) {
|
|
229
|
+
const childNodeDescription = describeNodeWithChildrenForConnectionId(
|
|
230
|
+
child,
|
|
231
|
+
connectionId,
|
|
232
|
+
visibleNodesForConnection
|
|
233
|
+
);
|
|
234
|
+
if (childNodeDescription) {
|
|
235
|
+
node.children.push(childNodeDescription);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return node;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function removeNodeAndChildrenFromVisibleNodes(virtualDomElement, visibleNodesForConnection) {
|
|
242
|
+
visibleNodesForConnection.delete(virtualDomElement.nodeId);
|
|
243
|
+
for (const child of virtualDomElement.childNodes) {
|
|
244
|
+
if (!visibleNodesForConnection.has(child.nodeId)) {
|
|
245
|
+
console.error("Inner child of removed element was not visible", child.nodeId);
|
|
246
|
+
}
|
|
247
|
+
removeNodeAndChildrenFromVisibleNodes(child, visibleNodesForConnection);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function findParentNodeOfNodeId(virtualDomElement, targetNodeId) {
|
|
251
|
+
for (const child of virtualDomElement.childNodes) {
|
|
252
|
+
if (child.nodeId === targetNodeId) {
|
|
253
|
+
return virtualDomElement;
|
|
254
|
+
} else {
|
|
255
|
+
const foundParentId = findParentNodeOfNodeId(child, targetNodeId);
|
|
256
|
+
if (foundParentId) {
|
|
257
|
+
return foundParentId;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
function virtualDOMDiffToVirtualDOMMutationRecord(virtualStructure, domDiff) {
|
|
264
|
+
const pointer = rfc6902.Pointer.fromJSON(domDiff.path);
|
|
265
|
+
const grandParentTokens = pointer.tokens.slice(0, pointer.tokens.length - 2);
|
|
266
|
+
const lastToken = pointer.tokens[pointer.tokens.length - 1];
|
|
267
|
+
const secondLastToken = pointer.tokens[pointer.tokens.length - 2];
|
|
268
|
+
if (lastToken === "textContent") {
|
|
269
|
+
const nodePointer = new rfc6902.Pointer(pointer.tokens.slice(0, pointer.tokens.length - 1));
|
|
270
|
+
const node = nodePointer.get(virtualStructure);
|
|
271
|
+
return [
|
|
272
|
+
{
|
|
273
|
+
type: "characterData",
|
|
274
|
+
target: node,
|
|
275
|
+
addedNodes: [],
|
|
276
|
+
removedNodes: [],
|
|
277
|
+
attributeName: null,
|
|
278
|
+
previousSibling: null
|
|
279
|
+
}
|
|
280
|
+
];
|
|
281
|
+
}
|
|
282
|
+
if (secondLastToken === "attributes") {
|
|
283
|
+
const nodePointer = new rfc6902.Pointer(grandParentTokens);
|
|
284
|
+
const node = nodePointer.get(virtualStructure);
|
|
285
|
+
return [
|
|
286
|
+
{
|
|
287
|
+
type: "attributes",
|
|
288
|
+
target: node,
|
|
289
|
+
addedNodes: [],
|
|
290
|
+
removedNodes: [],
|
|
291
|
+
attributeName: lastToken,
|
|
292
|
+
previousSibling: null
|
|
293
|
+
}
|
|
294
|
+
];
|
|
295
|
+
}
|
|
296
|
+
if (secondLastToken === "childNodes") {
|
|
297
|
+
const nodePointer = new rfc6902.Pointer(grandParentTokens);
|
|
298
|
+
const node = nodePointer.get(virtualStructure);
|
|
299
|
+
let previousSibling = null;
|
|
300
|
+
if (lastToken === "-") {
|
|
301
|
+
} else {
|
|
302
|
+
const index = parseInt(lastToken, 10);
|
|
303
|
+
if (index === 0) {
|
|
304
|
+
previousSibling = null;
|
|
305
|
+
} else {
|
|
306
|
+
previousSibling = node.childNodes[index - 1];
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const addedNodes = [];
|
|
310
|
+
const removedNodes = [];
|
|
311
|
+
if (domDiff.op === "add") {
|
|
312
|
+
addedNodes.push(domDiff.value);
|
|
313
|
+
return [
|
|
314
|
+
{
|
|
315
|
+
type: "childList",
|
|
316
|
+
target: node,
|
|
317
|
+
addedNodes,
|
|
318
|
+
removedNodes,
|
|
319
|
+
previousSibling,
|
|
320
|
+
attributeName: null
|
|
321
|
+
}
|
|
322
|
+
];
|
|
323
|
+
} else if (domDiff.op === "remove") {
|
|
324
|
+
const removedNode = pointer.get(virtualStructure);
|
|
325
|
+
removedNodes.push(removedNode);
|
|
326
|
+
return [
|
|
327
|
+
{
|
|
328
|
+
type: "childList",
|
|
329
|
+
target: node,
|
|
330
|
+
addedNodes,
|
|
331
|
+
removedNodes,
|
|
332
|
+
previousSibling,
|
|
333
|
+
attributeName: null
|
|
334
|
+
}
|
|
335
|
+
];
|
|
336
|
+
} else if (domDiff.op === "replace") {
|
|
337
|
+
const removedNode = pointer.get(virtualStructure);
|
|
338
|
+
removedNodes.push(removedNode);
|
|
339
|
+
addedNodes.push(domDiff.value);
|
|
340
|
+
return [
|
|
341
|
+
{
|
|
342
|
+
type: "childList",
|
|
343
|
+
target: node,
|
|
344
|
+
addedNodes: [],
|
|
345
|
+
removedNodes,
|
|
346
|
+
previousSibling,
|
|
347
|
+
attributeName: null
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
type: "childList",
|
|
351
|
+
target: node,
|
|
352
|
+
addedNodes,
|
|
353
|
+
removedNodes: [],
|
|
354
|
+
previousSibling,
|
|
355
|
+
attributeName: null
|
|
356
|
+
}
|
|
357
|
+
];
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
console.error("Unhandled JSON diff:", JSON.stringify(domDiff, null, 2));
|
|
361
|
+
throw new Error("Unhandled diff type");
|
|
362
|
+
}
|
|
363
|
+
function calculateStaticVirtualDomDiff(originalState, latestState) {
|
|
364
|
+
const jsonPatchDiffs = rfc6902.createPatch(
|
|
365
|
+
originalState,
|
|
366
|
+
latestState,
|
|
367
|
+
(a, b, ptr) => {
|
|
368
|
+
if (a.tag !== b.tag) {
|
|
369
|
+
return [{ op: "replace", path: ptr.toString(), value: b }];
|
|
370
|
+
}
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
);
|
|
374
|
+
const nodeIdRemappings = [];
|
|
375
|
+
const virtualDOMDiffs = [];
|
|
376
|
+
for (const diff of jsonPatchDiffs) {
|
|
377
|
+
if (diff.op === "replace" && diff.path.endsWith("/nodeId")) {
|
|
378
|
+
const pointer = rfc6902.Pointer.fromJSON(diff.path);
|
|
379
|
+
const value = pointer.get(originalState);
|
|
380
|
+
nodeIdRemappings.push({
|
|
381
|
+
internalNodeId: diff.value,
|
|
382
|
+
clientFacingNodeId: value
|
|
383
|
+
});
|
|
384
|
+
} else {
|
|
385
|
+
virtualDOMDiffs.push(diff);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return remapDuplicatedNodeIdsInOperations(
|
|
389
|
+
{
|
|
390
|
+
originalState,
|
|
391
|
+
nodeIdRemappings,
|
|
392
|
+
virtualDOMDiffs
|
|
393
|
+
},
|
|
394
|
+
latestState
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
function getHighestNodeId(node) {
|
|
398
|
+
let highest = node.nodeId;
|
|
399
|
+
for (const child of node.childNodes) {
|
|
400
|
+
highest = Math.max(highest, getHighestNodeId(child));
|
|
401
|
+
}
|
|
402
|
+
return highest;
|
|
403
|
+
}
|
|
404
|
+
function getRemovedNodeIds(before, diff) {
|
|
405
|
+
const removedIds = /* @__PURE__ */ new Set();
|
|
406
|
+
function addNode(node) {
|
|
407
|
+
removedIds.add(node.nodeId);
|
|
408
|
+
for (const child of node.childNodes) {
|
|
409
|
+
addNode(child);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
if (diff.op === "replace" || diff.op === "remove") {
|
|
413
|
+
const removedNode = rfc6902.Pointer.fromJSON(diff.path).get(before);
|
|
414
|
+
addNode(removedNode);
|
|
415
|
+
}
|
|
416
|
+
return removedIds;
|
|
417
|
+
}
|
|
418
|
+
function getNodeIdsFromNodeAndChildren(node) {
|
|
419
|
+
const nodeIds = /* @__PURE__ */ new Set();
|
|
420
|
+
function addNode(node2) {
|
|
421
|
+
nodeIds.add(node2.nodeId);
|
|
422
|
+
for (const child of node2.childNodes) {
|
|
423
|
+
addNode(child);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
addNode(node);
|
|
427
|
+
return nodeIds;
|
|
428
|
+
}
|
|
429
|
+
function remapDuplicatedNodeIdsInOperations(virtualDOMDiffStruct, latestState) {
|
|
430
|
+
const { originalState, nodeIdRemappings, virtualDOMDiffs } = virtualDOMDiffStruct;
|
|
431
|
+
const highestNodeIdAcrossStartAndEnd = Math.max(
|
|
432
|
+
getHighestNodeId(originalState),
|
|
433
|
+
getHighestNodeId(latestState)
|
|
434
|
+
);
|
|
435
|
+
let nextNodeId = highestNodeIdAcrossStartAndEnd + 1;
|
|
436
|
+
const before = JSON.parse(JSON.stringify(originalState));
|
|
437
|
+
function checkAndReplaceNodeIdsIfAlreadyInUse(node, addingNodeIds, removedIds) {
|
|
438
|
+
if (existingNodeIds.has(node.nodeId) && removedIds && !removedIds.has(node.nodeId)) {
|
|
439
|
+
const newNodeId = nextNodeId++;
|
|
440
|
+
nodeIdRemappings.push({
|
|
441
|
+
internalNodeId: node.nodeId,
|
|
442
|
+
clientFacingNodeId: newNodeId
|
|
443
|
+
});
|
|
444
|
+
node.nodeId = newNodeId;
|
|
445
|
+
addingNodeIds.add(newNodeId);
|
|
446
|
+
} else {
|
|
447
|
+
addingNodeIds.add(node.nodeId);
|
|
448
|
+
}
|
|
449
|
+
for (const child of node.childNodes) {
|
|
450
|
+
checkAndReplaceNodeIdsIfAlreadyInUse(child, addingNodeIds, removedIds);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
const existingNodeIds = getNodeIdsFromNodeAndChildren(before);
|
|
454
|
+
for (const diff of virtualDOMDiffs) {
|
|
455
|
+
const pointer = rfc6902.Pointer.fromJSON(diff.path);
|
|
456
|
+
const secondLastToken = pointer.tokens[pointer.tokens.length - 2];
|
|
457
|
+
if (secondLastToken !== "childNodes") {
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
const removedIds = getRemovedNodeIds(before, diff);
|
|
461
|
+
const addingNodeIds = /* @__PURE__ */ new Set();
|
|
462
|
+
if (diff.op === "replace" || diff.op === "add") {
|
|
463
|
+
checkAndReplaceNodeIdsIfAlreadyInUse(diff.value, addingNodeIds, removedIds);
|
|
464
|
+
}
|
|
465
|
+
removedIds.forEach((removedId) => {
|
|
466
|
+
existingNodeIds.delete(removedId);
|
|
467
|
+
});
|
|
468
|
+
addingNodeIds.forEach((addingNodeId) => {
|
|
469
|
+
existingNodeIds.add(addingNodeId);
|
|
470
|
+
});
|
|
471
|
+
const patchErrors = rfc6902.applyPatch(before, [diff]);
|
|
472
|
+
if (patchErrors.length !== 1 || patchErrors[0] !== null) {
|
|
473
|
+
throw new Error("Patch failed");
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return virtualDOMDiffStruct;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// src/NetworkedDOM.ts
|
|
480
|
+
var networkedDomProtocolSubProtocol_v0_1 = "networked-dom-v0.1";
|
|
481
|
+
var defaultWebsocketSubProtocol = networkedDomProtocolSubProtocol_v0_1;
|
|
482
|
+
var _NetworkedDOM = class {
|
|
483
|
+
constructor(observableDomFactory, htmlPath, htmlContents, oldInstanceDocumentRoot, onLoad, params = {}, ignoreTextNodes = true, logCallback) {
|
|
484
|
+
// Map from the node ids that the DOM uses internally to the node ids that clients refer to.
|
|
485
|
+
this.internalNodeIdToClientNodeId = /* @__PURE__ */ new Map();
|
|
486
|
+
// Map from the node ids that clients refer to to the node ids that the DOM uses internally.
|
|
487
|
+
this.clientNodeIdToInternalNodeId = /* @__PURE__ */ new Map();
|
|
488
|
+
this.nextNodeId = 1;
|
|
489
|
+
this.ipcWebsockets = /* @__PURE__ */ new Set();
|
|
490
|
+
this.currentConnectionId = 1;
|
|
491
|
+
this.connectionIdToWebSocketContext = /* @__PURE__ */ new Map();
|
|
492
|
+
this.webSocketToConnectionId = /* @__PURE__ */ new Map();
|
|
493
|
+
this.visibleNodeIdsByConnectionId = /* @__PURE__ */ new Map();
|
|
494
|
+
this.initialLoad = true;
|
|
495
|
+
this.disposed = false;
|
|
496
|
+
this.nodeIdToNode = /* @__PURE__ */ new Map();
|
|
497
|
+
this.nodeIdToParentNodeId = /* @__PURE__ */ new Map();
|
|
498
|
+
this.documentEffectiveStartTime = Date.now();
|
|
499
|
+
this.latestDocumentTime = 0;
|
|
500
|
+
this.pingCounter = 1;
|
|
501
|
+
this.maximumNodeId = 0;
|
|
502
|
+
this.htmlPath = htmlPath;
|
|
503
|
+
this.ignoreTextNodes = ignoreTextNodes;
|
|
504
|
+
this.logCallback = logCallback || this.defaultLogCallback;
|
|
505
|
+
this.observableDom = observableDomFactory(
|
|
506
|
+
{
|
|
507
|
+
htmlPath,
|
|
508
|
+
htmlContents,
|
|
509
|
+
params,
|
|
510
|
+
ignoreTextNodes,
|
|
511
|
+
pingIntervalMilliseconds: 5e3
|
|
512
|
+
},
|
|
513
|
+
(message) => {
|
|
514
|
+
if (message.documentTime) {
|
|
515
|
+
this.documentEffectiveStartTime = Date.now() - message.documentTime;
|
|
516
|
+
this.latestDocumentTime = message.documentTime;
|
|
517
|
+
}
|
|
518
|
+
if (message.snapshot) {
|
|
519
|
+
this.documentRoot = message.snapshot;
|
|
520
|
+
const clonedSnapshot = JSON.parse(JSON.stringify(message.snapshot));
|
|
521
|
+
if (!this.initialLoad) {
|
|
522
|
+
throw new Error("Received snapshot after initial load");
|
|
523
|
+
}
|
|
524
|
+
this.initialLoad = false;
|
|
525
|
+
let domDiff = null;
|
|
526
|
+
if (oldInstanceDocumentRoot) {
|
|
527
|
+
domDiff = calculateStaticVirtualDomDiff(oldInstanceDocumentRoot, clonedSnapshot);
|
|
528
|
+
for (const remapping of domDiff.nodeIdRemappings) {
|
|
529
|
+
this.addRemappedNodeId(remapping.clientFacingNodeId, remapping.internalNodeId);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
this.addAndRemapNodeFromInstance(this.documentRoot, -1);
|
|
533
|
+
onLoad(domDiff);
|
|
534
|
+
} else if (message.mutation) {
|
|
535
|
+
if (this.initialLoad) {
|
|
536
|
+
throw new Error("Received mutation before initial load");
|
|
537
|
+
}
|
|
538
|
+
const mutation = this.addKnownNodesInMutation(message.mutation);
|
|
539
|
+
this.processModification(mutation);
|
|
540
|
+
this.removeKnownNodesInMutation(mutation);
|
|
541
|
+
} else if (message.logMessage) {
|
|
542
|
+
if (this.logCallback) {
|
|
543
|
+
this.logCallback(message.logMessage);
|
|
544
|
+
}
|
|
545
|
+
} else {
|
|
546
|
+
if (message.documentTime) {
|
|
547
|
+
this.sendPings();
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
console.error("Unknown message type from observableDom", message);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
defaultLogCallback(message) {
|
|
556
|
+
const getLogFn = (level) => {
|
|
557
|
+
switch (level) {
|
|
558
|
+
case "system":
|
|
559
|
+
return console.error;
|
|
560
|
+
case "error":
|
|
561
|
+
return console.error;
|
|
562
|
+
case "warn":
|
|
563
|
+
return console.warn;
|
|
564
|
+
case "log":
|
|
565
|
+
return console.log;
|
|
566
|
+
case "info":
|
|
567
|
+
return console.info;
|
|
568
|
+
default:
|
|
569
|
+
return console.log;
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
const logFn = getLogFn(message.level);
|
|
573
|
+
logFn(`${message.level.toUpperCase()} (${this.htmlPath}):`, ...message.content);
|
|
574
|
+
}
|
|
575
|
+
addRemappedNodeId(clientFacingNodeId, internalNodeId) {
|
|
576
|
+
this.internalNodeIdToClientNodeId.set(internalNodeId, clientFacingNodeId);
|
|
577
|
+
this.clientNodeIdToInternalNodeId.set(clientFacingNodeId, internalNodeId);
|
|
578
|
+
this.maximumNodeId = Math.max(this.maximumNodeId, Math.max(clientFacingNodeId, internalNodeId));
|
|
579
|
+
}
|
|
580
|
+
sendPings() {
|
|
581
|
+
const ping = this.pingCounter++;
|
|
582
|
+
if (this.pingCounter > 1e3) {
|
|
583
|
+
this.pingCounter = 1;
|
|
584
|
+
}
|
|
585
|
+
const pingMessage = [
|
|
586
|
+
{
|
|
587
|
+
type: "ping",
|
|
588
|
+
ping,
|
|
589
|
+
documentTime: this.getDocumentTime()
|
|
590
|
+
}
|
|
591
|
+
];
|
|
592
|
+
const stringified = JSON.stringify(pingMessage);
|
|
593
|
+
this.connectionIdToWebSocketContext.forEach((webSocketContext) => {
|
|
594
|
+
webSocketContext.webSocket.send(stringified);
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
getInitialSnapshot(connectionId, documentVirtualDomElement) {
|
|
598
|
+
const visibleNodesForConnection = this.visibleNodeIdsByConnectionId.get(connectionId);
|
|
599
|
+
if (!visibleNodesForConnection) {
|
|
600
|
+
const err = new Error(
|
|
601
|
+
`visibleNodesForConnection not found for connectionId in getInitialSnapshot: ${connectionId}`
|
|
602
|
+
);
|
|
603
|
+
console.error(err);
|
|
604
|
+
throw err;
|
|
605
|
+
}
|
|
606
|
+
const domSnapshot = describeNodeWithChildrenForConnectionId(
|
|
607
|
+
documentVirtualDomElement,
|
|
608
|
+
connectionId,
|
|
609
|
+
visibleNodesForConnection
|
|
610
|
+
);
|
|
611
|
+
if (!domSnapshot) {
|
|
612
|
+
throw new Error(`domSnapshot was not generated`);
|
|
613
|
+
}
|
|
614
|
+
return {
|
|
615
|
+
type: "snapshot",
|
|
616
|
+
snapshot: domSnapshot,
|
|
617
|
+
documentTime: Date.now() - this.documentEffectiveStartTime
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
getDocumentTime() {
|
|
621
|
+
return this.latestDocumentTime;
|
|
622
|
+
}
|
|
623
|
+
addExistingWebsockets(websockets, existingWebsocketMap, domDiff) {
|
|
624
|
+
const connectionIds = [];
|
|
625
|
+
for (const websocket of websockets) {
|
|
626
|
+
let existingId = null;
|
|
627
|
+
if (existingWebsocketMap !== null) {
|
|
628
|
+
existingId = existingWebsocketMap.get(websocket);
|
|
629
|
+
}
|
|
630
|
+
const { connectionId } = this.registerWebsocket(websocket, existingId);
|
|
631
|
+
connectionIds.push(connectionId);
|
|
632
|
+
}
|
|
633
|
+
if (domDiff) {
|
|
634
|
+
const diffsByConnectionId = new Map(
|
|
635
|
+
connectionIds.map((connectionId) => [connectionId, []])
|
|
636
|
+
);
|
|
637
|
+
for (const connectionId of connectionIds) {
|
|
638
|
+
this.getInitialSnapshot(connectionId, domDiff.originalState);
|
|
639
|
+
}
|
|
640
|
+
for (const virtualDOMDiff of domDiff.virtualDOMDiffs) {
|
|
641
|
+
const mutationRecordLikes = virtualDOMDiffToVirtualDOMMutationRecord(
|
|
642
|
+
domDiff.originalState,
|
|
643
|
+
virtualDOMDiff
|
|
644
|
+
);
|
|
645
|
+
const patchResults = (0, import_rfc6902.applyPatch)(domDiff.originalState, [virtualDOMDiff]);
|
|
646
|
+
for (const patchResult of patchResults) {
|
|
647
|
+
if (patchResult !== null) {
|
|
648
|
+
console.error("Patching virtual dom structure resulted in error", patchResult);
|
|
649
|
+
throw patchResult;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
for (const mutationRecordLike of mutationRecordLikes) {
|
|
653
|
+
const targetNodeId = mutationRecordLike.target.nodeId;
|
|
654
|
+
const virtualElementParent = findParentNodeOfNodeId(domDiff.originalState, targetNodeId);
|
|
655
|
+
if (!virtualElementParent) {
|
|
656
|
+
throw new Error(`could not find parent node of nodeId ${targetNodeId}`);
|
|
657
|
+
}
|
|
658
|
+
diffsByConnectionId.forEach((diffs, connectionId) => {
|
|
659
|
+
const mutationDiff = diffFromApplicationOfStaticVirtualDomMutationRecordToConnection(
|
|
660
|
+
mutationRecordLike,
|
|
661
|
+
virtualElementParent,
|
|
662
|
+
connectionId,
|
|
663
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
664
|
+
this.visibleNodeIdsByConnectionId.get(connectionId)
|
|
665
|
+
);
|
|
666
|
+
if (mutationDiff) {
|
|
667
|
+
diffs.push(mutationDiff);
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
diffsByConnectionId.forEach((diffs, connectionId) => {
|
|
673
|
+
if (diffs.length === 0) {
|
|
674
|
+
diffs.push({
|
|
675
|
+
type: "childrenChanged",
|
|
676
|
+
nodeId: this.documentRoot.nodeId,
|
|
677
|
+
previousNodeId: null,
|
|
678
|
+
addedNodes: [],
|
|
679
|
+
removedNodes: []
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
const asServerMessages = diffs;
|
|
683
|
+
const firstDiff = diffs[0];
|
|
684
|
+
firstDiff.documentTime = this.getDocumentTime();
|
|
685
|
+
const serializedDiffs = JSON.stringify(asServerMessages, null, 4);
|
|
686
|
+
const webSocketContext = this.connectionIdToWebSocketContext.get(connectionId);
|
|
687
|
+
if (!webSocketContext) {
|
|
688
|
+
throw new Error(`webSocketContext not found in addExistingWebsockets`);
|
|
689
|
+
}
|
|
690
|
+
webSocketContext.webSocket.send(serializedDiffs);
|
|
691
|
+
});
|
|
692
|
+
} else {
|
|
693
|
+
const documentVirtualDomElement = this.documentRoot;
|
|
694
|
+
if (!documentVirtualDomElement) {
|
|
695
|
+
throw new Error(`documentVirtualDomElement not found in getInitialSnapshot`);
|
|
696
|
+
}
|
|
697
|
+
for (const connectionId of connectionIds) {
|
|
698
|
+
const webSocketContext = this.connectionIdToWebSocketContext.get(connectionId);
|
|
699
|
+
if (!webSocketContext) {
|
|
700
|
+
throw new Error(`webSocketContext not found in addExistingWebsockets`);
|
|
701
|
+
}
|
|
702
|
+
const asServerMessages = [
|
|
703
|
+
this.getInitialSnapshot(connectionId, documentVirtualDomElement)
|
|
704
|
+
];
|
|
705
|
+
const serializedSnapshotMessage = JSON.stringify(asServerMessages);
|
|
706
|
+
webSocketContext.webSocket.send(serializedSnapshotMessage);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
for (const connectionId of connectionIds) {
|
|
710
|
+
this.observableDom.addConnectedUserId(connectionId);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
findParentNodeOfNodeId(targetNodeId) {
|
|
714
|
+
const parentNodeId = this.nodeIdToParentNodeId.get(targetNodeId);
|
|
715
|
+
if (parentNodeId === void 0) {
|
|
716
|
+
throw new Error("Parent node ID not found");
|
|
717
|
+
}
|
|
718
|
+
return this.getStaticVirtualDomElementByInternalNodeIdOrThrow(parentNodeId);
|
|
719
|
+
}
|
|
720
|
+
registerWebsocket(webSocket, existingConnectionId = null) {
|
|
721
|
+
let connectionId;
|
|
722
|
+
if (existingConnectionId !== null) {
|
|
723
|
+
connectionId = existingConnectionId;
|
|
724
|
+
this.currentConnectionId = Math.max(this.currentConnectionId, connectionId + 1);
|
|
725
|
+
} else {
|
|
726
|
+
connectionId = this.currentConnectionId++;
|
|
727
|
+
}
|
|
728
|
+
const webSocketContext = {
|
|
729
|
+
webSocket,
|
|
730
|
+
messageListener: (msg) => {
|
|
731
|
+
const string = String(msg.data);
|
|
732
|
+
let parsed;
|
|
733
|
+
try {
|
|
734
|
+
parsed = JSON.parse(string);
|
|
735
|
+
} catch (e) {
|
|
736
|
+
console.error(`Error parsing message from websocket: ${string}`);
|
|
737
|
+
console.trace();
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
if (_NetworkedDOM.IsPongMessage(parsed)) {
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
this.dispatchRemoteEvent(webSocket, parsed);
|
|
744
|
+
}
|
|
745
|
+
};
|
|
746
|
+
this.connectionIdToWebSocketContext.set(connectionId, webSocketContext);
|
|
747
|
+
this.visibleNodeIdsByConnectionId.set(connectionId, /* @__PURE__ */ new Set());
|
|
748
|
+
this.webSocketToConnectionId.set(webSocket, connectionId);
|
|
749
|
+
webSocket.addEventListener("message", webSocketContext.messageListener);
|
|
750
|
+
return { connectionId };
|
|
751
|
+
}
|
|
752
|
+
addIPCWebSocket(webSocket) {
|
|
753
|
+
this.ipcWebsockets.add(webSocket);
|
|
754
|
+
webSocket.addEventListener("close", () => {
|
|
755
|
+
this.ipcWebsockets.delete(webSocket);
|
|
756
|
+
});
|
|
757
|
+
this.observableDom.addIPCWebsocket(webSocket);
|
|
758
|
+
}
|
|
759
|
+
static handleWebsocketSubprotocol(protocols) {
|
|
760
|
+
const protocolsSet = new Set(protocols);
|
|
761
|
+
for (const protocol of _NetworkedDOM.SupportedWebsocketSubProtocolsPreferenceOrder) {
|
|
762
|
+
if (protocolsSet.has(protocol)) {
|
|
763
|
+
return protocol;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
return false;
|
|
767
|
+
}
|
|
768
|
+
addWebSocket(webSocket) {
|
|
769
|
+
if (this.initialLoad) {
|
|
770
|
+
throw new Error("addWebSocket called before initial load - unsupported at this time");
|
|
771
|
+
}
|
|
772
|
+
if (this.disposed) {
|
|
773
|
+
console.error("addWebSocket called on disposed NetworkedDOM");
|
|
774
|
+
throw new Error("This NetworkedDOM has been disposed");
|
|
775
|
+
}
|
|
776
|
+
if (webSocket.protocol) {
|
|
777
|
+
if (_NetworkedDOM.SupportedWebsocketSubProtocolsPreferenceOrder.indexOf(webSocket.protocol) === -1) {
|
|
778
|
+
const errorMessageString = `Unsupported websocket subprotocol: ${webSocket.protocol}`;
|
|
779
|
+
const errorMessage = [
|
|
780
|
+
{
|
|
781
|
+
type: "error",
|
|
782
|
+
message: errorMessageString
|
|
783
|
+
}
|
|
784
|
+
];
|
|
785
|
+
webSocket.send(JSON.stringify(errorMessage));
|
|
786
|
+
webSocket.close();
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
} else {
|
|
790
|
+
const warningMessageString = `No websocket subprotocol specified. Please specify a subprotocol to ensure compatibility with networked-dom servers. Assuming subprotocol "${defaultWebsocketSubProtocol}" for this connection.`;
|
|
791
|
+
const warningMessage = [
|
|
792
|
+
{
|
|
793
|
+
type: "warning",
|
|
794
|
+
message: warningMessageString
|
|
795
|
+
}
|
|
796
|
+
];
|
|
797
|
+
webSocket.send(JSON.stringify(warningMessage));
|
|
798
|
+
}
|
|
799
|
+
const { connectionId } = this.registerWebsocket(webSocket);
|
|
800
|
+
const documentVirtualDomElement = this.documentRoot;
|
|
801
|
+
if (!documentVirtualDomElement) {
|
|
802
|
+
throw new Error(`documentVirtualDomElement not found in getInitialSnapshot`);
|
|
803
|
+
}
|
|
804
|
+
const asServerMessages = [
|
|
805
|
+
this.getInitialSnapshot(connectionId, documentVirtualDomElement)
|
|
806
|
+
];
|
|
807
|
+
const serializedSnapshotMessage = JSON.stringify(asServerMessages);
|
|
808
|
+
webSocket.send(serializedSnapshotMessage);
|
|
809
|
+
this.observableDom.addConnectedUserId(connectionId);
|
|
810
|
+
}
|
|
811
|
+
removeWebSocket(webSocket) {
|
|
812
|
+
const connectionId = this.webSocketToConnectionId.get(webSocket);
|
|
813
|
+
if (!connectionId) {
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
this.observableDom.removeConnectedUserId(connectionId);
|
|
817
|
+
const webSocketContext = this.connectionIdToWebSocketContext.get(connectionId);
|
|
818
|
+
if (!webSocketContext) {
|
|
819
|
+
throw new Error("Missing context for websocket");
|
|
820
|
+
}
|
|
821
|
+
webSocket.removeEventListener("message", webSocketContext.messageListener);
|
|
822
|
+
this.connectionIdToWebSocketContext.delete(connectionId);
|
|
823
|
+
this.visibleNodeIdsByConnectionId.delete(connectionId);
|
|
824
|
+
this.webSocketToConnectionId.delete(webSocket);
|
|
825
|
+
}
|
|
826
|
+
dispose() {
|
|
827
|
+
this.disposed = true;
|
|
828
|
+
for (const [, connectionId] of this.webSocketToConnectionId) {
|
|
829
|
+
this.observableDom.removeConnectedUserId(connectionId);
|
|
830
|
+
}
|
|
831
|
+
this.observableDom.dispose();
|
|
832
|
+
for (const [webSocket, connectionId] of this.webSocketToConnectionId) {
|
|
833
|
+
const webSocketContext = this.connectionIdToWebSocketContext.get(connectionId);
|
|
834
|
+
if (!webSocketContext) {
|
|
835
|
+
throw new Error("Missing context for websocket");
|
|
836
|
+
}
|
|
837
|
+
webSocket.removeEventListener("message", webSocketContext.messageListener);
|
|
838
|
+
this.connectionIdToWebSocketContext.delete(connectionId);
|
|
839
|
+
this.visibleNodeIdsByConnectionId.delete(connectionId);
|
|
840
|
+
this.webSocketToConnectionId.delete(webSocket);
|
|
841
|
+
}
|
|
842
|
+
for (const ipcWebsocket of this.ipcWebsockets) {
|
|
843
|
+
ipcWebsocket.close();
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
processModification(mutationRecord) {
|
|
847
|
+
const documentVirtualDomElement = this.documentRoot;
|
|
848
|
+
if (!documentVirtualDomElement) {
|
|
849
|
+
throw new Error(`document not created in processModification`);
|
|
850
|
+
}
|
|
851
|
+
for (const [, visibleNodesForConnection] of this.visibleNodeIdsByConnectionId) {
|
|
852
|
+
visibleNodesForConnection.add(documentVirtualDomElement.nodeId);
|
|
853
|
+
}
|
|
854
|
+
const diffsByConnectionId = new Map(
|
|
855
|
+
Array.from(this.connectionIdToWebSocketContext.keys()).map((connectionId) => [
|
|
856
|
+
connectionId,
|
|
857
|
+
[]
|
|
858
|
+
])
|
|
859
|
+
);
|
|
860
|
+
diffsByConnectionId.forEach((diffs, connectionId) => {
|
|
861
|
+
const parentNode = this.findParentNodeOfNodeId(mutationRecord.target.nodeId);
|
|
862
|
+
if (mutationRecord.type === "attributes" && !parentNode) {
|
|
863
|
+
console.error("parentNode not found for attribute mutationRecord", mutationRecord);
|
|
864
|
+
console.error("this.documentRoot", JSON.stringify(this.documentRoot, null, 2));
|
|
865
|
+
}
|
|
866
|
+
const diff = diffFromApplicationOfStaticVirtualDomMutationRecordToConnection(
|
|
867
|
+
mutationRecord,
|
|
868
|
+
parentNode,
|
|
869
|
+
connectionId,
|
|
870
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
871
|
+
this.visibleNodeIdsByConnectionId.get(connectionId)
|
|
872
|
+
);
|
|
873
|
+
if (diff) {
|
|
874
|
+
diffs.push(diff);
|
|
875
|
+
}
|
|
876
|
+
});
|
|
877
|
+
diffsByConnectionId.forEach((diffs, connectionId) => {
|
|
878
|
+
if (diffs.length > 0) {
|
|
879
|
+
const asServerMessages = diffs;
|
|
880
|
+
const serializedDiffs = JSON.stringify(asServerMessages, null, 4);
|
|
881
|
+
const webSocketContext = this.connectionIdToWebSocketContext.get(connectionId);
|
|
882
|
+
if (!webSocketContext) {
|
|
883
|
+
throw new Error(`webSocketContext not found in processModificationList`);
|
|
884
|
+
}
|
|
885
|
+
webSocketContext.webSocket.send(serializedDiffs);
|
|
886
|
+
}
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
removeKnownNodesInMutation(mutation) {
|
|
890
|
+
const virtualDomElement = mutation.target;
|
|
891
|
+
if (mutation.type === "childList") {
|
|
892
|
+
mutation.removedNodes.forEach((childDomElement) => {
|
|
893
|
+
this.removeVirtualDomElement(childDomElement);
|
|
894
|
+
const index = virtualDomElement.childNodes.indexOf(childDomElement);
|
|
895
|
+
virtualDomElement.childNodes.splice(index, 1);
|
|
896
|
+
});
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
removeVirtualDomElement(virtualDomElement) {
|
|
901
|
+
this.nodeIdToNode.delete(virtualDomElement.nodeId);
|
|
902
|
+
this.nodeIdToParentNodeId.delete(virtualDomElement.nodeId);
|
|
903
|
+
for (const child of virtualDomElement.childNodes) {
|
|
904
|
+
this.removeVirtualDomElement(child);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
static IsPongMessage(message) {
|
|
908
|
+
return message.type === "pong";
|
|
909
|
+
}
|
|
910
|
+
dispatchRemoteEvent(webSocket, remoteEvent) {
|
|
911
|
+
if (this.disposed) {
|
|
912
|
+
console.error("Cannot dispatch remote event after dispose");
|
|
913
|
+
throw new Error("This NetworkedDOM has been disposed");
|
|
914
|
+
}
|
|
915
|
+
const connectionId = this.webSocketToConnectionId.get(webSocket);
|
|
916
|
+
if (!connectionId) {
|
|
917
|
+
console.error("Unknown web socket dispatched event:", webSocket);
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
const remappedNode = this.clientNodeIdToInternalNodeId.get(remoteEvent.nodeId);
|
|
921
|
+
if (remappedNode) {
|
|
922
|
+
remoteEvent.nodeId = remappedNode;
|
|
923
|
+
}
|
|
924
|
+
const visibleNodes = this.visibleNodeIdsByConnectionId.get(connectionId);
|
|
925
|
+
if (!visibleNodes) {
|
|
926
|
+
console.error("No visible nodes for connection: " + connectionId);
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
if (!visibleNodes.has(remoteEvent.nodeId)) {
|
|
930
|
+
console.error("Node not visible for connection: " + remoteEvent.nodeId);
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
this.observableDom.dispatchRemoteEventFromConnectionId(connectionId, remoteEvent);
|
|
934
|
+
}
|
|
935
|
+
getStaticVirtualDomElementByInternalNodeIdOrThrow(internalNodeId) {
|
|
936
|
+
const remappedId = this.internalNodeIdToClientNodeId.get(internalNodeId);
|
|
937
|
+
if (remappedId !== void 0) {
|
|
938
|
+
const node2 = this.nodeIdToNode.get(remappedId);
|
|
939
|
+
if (!node2) {
|
|
940
|
+
throw new Error("Remapped node not found with nodeId " + remappedId);
|
|
941
|
+
}
|
|
942
|
+
return node2;
|
|
943
|
+
}
|
|
944
|
+
const node = this.nodeIdToNode.get(internalNodeId);
|
|
945
|
+
if (!node) {
|
|
946
|
+
throw new Error("Node not found with nodeId:" + internalNodeId);
|
|
947
|
+
}
|
|
948
|
+
return node;
|
|
949
|
+
}
|
|
950
|
+
addKnownNodesInMutation(mutation) {
|
|
951
|
+
const target = this.getStaticVirtualDomElementByInternalNodeIdOrThrow(mutation.targetId);
|
|
952
|
+
if (mutation.attribute) {
|
|
953
|
+
if (mutation.attribute.value !== null) {
|
|
954
|
+
target.attributes[mutation.attribute.attributeName] = mutation.attribute.value;
|
|
955
|
+
} else {
|
|
956
|
+
delete target.attributes[mutation.attribute.attributeName];
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
const previousSibling = mutation.previousSiblingId ? this.getStaticVirtualDomElementByInternalNodeIdOrThrow(mutation.previousSiblingId) : null;
|
|
960
|
+
if (mutation.type === "childList") {
|
|
961
|
+
let index = 0;
|
|
962
|
+
if (previousSibling) {
|
|
963
|
+
index = target.childNodes.indexOf(previousSibling);
|
|
964
|
+
if (index === -1) {
|
|
965
|
+
throw new Error("Previous sibling is not currently a child of the parent element");
|
|
966
|
+
}
|
|
967
|
+
index += 1;
|
|
968
|
+
}
|
|
969
|
+
mutation.addedNodes.forEach((childVirtualDomElement) => {
|
|
970
|
+
this.addAndRemapNodeFromInstance(childVirtualDomElement, target.nodeId);
|
|
971
|
+
if (target.childNodes.indexOf(childVirtualDomElement) === -1) {
|
|
972
|
+
target.childNodes.splice(index, 0, childVirtualDomElement);
|
|
973
|
+
index++;
|
|
974
|
+
}
|
|
975
|
+
});
|
|
976
|
+
} else if (mutation.type === "attributes") {
|
|
977
|
+
const attributePair = mutation.attribute;
|
|
978
|
+
if (attributePair.value === null) {
|
|
979
|
+
delete target.attributes[attributePair.attributeName];
|
|
980
|
+
} else {
|
|
981
|
+
target.attributes[attributePair.attributeName] = attributePair.value;
|
|
982
|
+
}
|
|
983
|
+
} else if (mutation.type === "characterData") {
|
|
984
|
+
throw new Error("characterData not supported");
|
|
985
|
+
}
|
|
986
|
+
const record = {
|
|
987
|
+
type: mutation.type,
|
|
988
|
+
target,
|
|
989
|
+
addedNodes: mutation.addedNodes,
|
|
990
|
+
removedNodes: mutation.removedNodeIds.map((nodeId) => {
|
|
991
|
+
return this.getStaticVirtualDomElementByInternalNodeIdOrThrow(nodeId);
|
|
992
|
+
}),
|
|
993
|
+
previousSibling: mutation.previousSiblingId ? this.getStaticVirtualDomElementByInternalNodeIdOrThrow(mutation.previousSiblingId) : null,
|
|
994
|
+
attributeName: mutation.attribute ? mutation.attribute.attributeName : null
|
|
995
|
+
};
|
|
996
|
+
return record;
|
|
997
|
+
}
|
|
998
|
+
getSnapshot() {
|
|
999
|
+
return this.documentRoot;
|
|
1000
|
+
}
|
|
1001
|
+
addAndRemapNodeFromInstance(node, parentNodeId) {
|
|
1002
|
+
const remappedNodeId = this.internalNodeIdToClientNodeId.get(node.nodeId);
|
|
1003
|
+
if (remappedNodeId !== void 0) {
|
|
1004
|
+
node.nodeId = remappedNodeId;
|
|
1005
|
+
} else {
|
|
1006
|
+
const existingClientReference = this.clientNodeIdToInternalNodeId.get(node.nodeId);
|
|
1007
|
+
if (existingClientReference) {
|
|
1008
|
+
const newNodeId = ++this.maximumNodeId;
|
|
1009
|
+
this.addRemappedNodeId(newNodeId, node.nodeId);
|
|
1010
|
+
node.nodeId = newNodeId;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
if (this.nodeIdToNode.has(node.nodeId)) {
|
|
1014
|
+
throw new Error("Node already exists with id " + node.nodeId);
|
|
1015
|
+
}
|
|
1016
|
+
this.nodeIdToNode.set(node.nodeId, node);
|
|
1017
|
+
this.nodeIdToParentNodeId.set(node.nodeId, parentNodeId);
|
|
1018
|
+
this.maximumNodeId = Math.max(this.maximumNodeId, node.nodeId);
|
|
1019
|
+
for (const childNode of node.childNodes) {
|
|
1020
|
+
this.addAndRemapNodeFromInstance(childNode, node.nodeId);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
getWebsocketConnectionIdMap() {
|
|
1024
|
+
return new Map(this.webSocketToConnectionId);
|
|
1025
|
+
}
|
|
1026
|
+
};
|
|
1027
|
+
var NetworkedDOM = _NetworkedDOM;
|
|
1028
|
+
// First to last in order of preference
|
|
1029
|
+
NetworkedDOM.SupportedWebsocketSubProtocolsPreferenceOrder = [
|
|
1030
|
+
networkedDomProtocolSubProtocol_v0_1
|
|
1031
|
+
];
|
|
1032
|
+
|
|
1033
|
+
// src/EditableNetworkedDOM.ts
|
|
1034
|
+
var EditableNetworkedDOM = class {
|
|
1035
|
+
constructor(htmlPath, observableDOMFactory, ignoreTextNodes = true, logCallback) {
|
|
1036
|
+
this.params = {};
|
|
1037
|
+
this.websockets = /* @__PURE__ */ new Set();
|
|
1038
|
+
this.loadedState = null;
|
|
1039
|
+
this.htmlPath = htmlPath;
|
|
1040
|
+
this.observableDOMFactory = observableDOMFactory;
|
|
1041
|
+
this.ignoreTextNodes = ignoreTextNodes;
|
|
1042
|
+
this.logCallback = logCallback;
|
|
1043
|
+
}
|
|
1044
|
+
isLoaded() {
|
|
1045
|
+
return this.loadedState !== null;
|
|
1046
|
+
}
|
|
1047
|
+
load(htmlContents, params) {
|
|
1048
|
+
if (params !== void 0) {
|
|
1049
|
+
this.params = params;
|
|
1050
|
+
}
|
|
1051
|
+
let oldInstanceRoot = null;
|
|
1052
|
+
let existingWebsocketMap = null;
|
|
1053
|
+
if (this.loadedState) {
|
|
1054
|
+
const oldInstance = this.loadedState.networkedDOM;
|
|
1055
|
+
existingWebsocketMap = oldInstance.getWebsocketConnectionIdMap();
|
|
1056
|
+
oldInstance.dispose();
|
|
1057
|
+
oldInstanceRoot = oldInstance.getSnapshot();
|
|
1058
|
+
}
|
|
1059
|
+
this.loadedState = null;
|
|
1060
|
+
let didLoad = false;
|
|
1061
|
+
const networkedDOM = new NetworkedDOM(
|
|
1062
|
+
this.observableDOMFactory,
|
|
1063
|
+
this.htmlPath,
|
|
1064
|
+
htmlContents,
|
|
1065
|
+
oldInstanceRoot,
|
|
1066
|
+
(domDiff) => {
|
|
1067
|
+
didLoad = true;
|
|
1068
|
+
if (this.loadedState) {
|
|
1069
|
+
this.loadedState.loaded = true;
|
|
1070
|
+
}
|
|
1071
|
+
networkedDOM.addExistingWebsockets(
|
|
1072
|
+
Array.from(this.websockets),
|
|
1073
|
+
existingWebsocketMap,
|
|
1074
|
+
domDiff
|
|
1075
|
+
);
|
|
1076
|
+
},
|
|
1077
|
+
this.params,
|
|
1078
|
+
this.ignoreTextNodes,
|
|
1079
|
+
this.logCallback
|
|
1080
|
+
);
|
|
1081
|
+
this.loadedState = {
|
|
1082
|
+
htmlContents,
|
|
1083
|
+
networkedDOM,
|
|
1084
|
+
loaded: didLoad
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
reload() {
|
|
1088
|
+
if (this.loadedState) {
|
|
1089
|
+
this.load(this.loadedState.htmlContents, this.params);
|
|
1090
|
+
} else {
|
|
1091
|
+
console.warn("EditableNetworkedDOM.reload called whilst not loaded");
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
dispose() {
|
|
1095
|
+
for (const ws of this.websockets) {
|
|
1096
|
+
ws.close();
|
|
1097
|
+
}
|
|
1098
|
+
this.websockets.clear();
|
|
1099
|
+
if (this.loadedState) {
|
|
1100
|
+
this.loadedState.networkedDOM.dispose();
|
|
1101
|
+
}
|
|
1102
|
+
this.loadedState = null;
|
|
1103
|
+
}
|
|
1104
|
+
addWebSocket(webSocket) {
|
|
1105
|
+
this.websockets.add(webSocket);
|
|
1106
|
+
if (this.loadedState && this.loadedState.loaded) {
|
|
1107
|
+
this.loadedState.networkedDOM.addWebSocket(webSocket);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
removeWebSocket(webSocket) {
|
|
1111
|
+
this.websockets.delete(webSocket);
|
|
1112
|
+
if (this.loadedState && this.loadedState.loaded) {
|
|
1113
|
+
this.loadedState.networkedDOM.removeWebSocket(webSocket);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
addIPCWebSocket(webSocket) {
|
|
1117
|
+
if (this.loadedState && this.loadedState.loaded) {
|
|
1118
|
+
this.loadedState.networkedDOM.addIPCWebSocket(webSocket);
|
|
1119
|
+
} else {
|
|
1120
|
+
console.error("Dom instance not loaded/ready to accept IPC websocket");
|
|
1121
|
+
webSocket.close();
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
};
|
|
1126
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1127
|
+
0 && (module.exports = {
|
|
1128
|
+
EditableNetworkedDOM,
|
|
1129
|
+
NetworkedDOM,
|
|
1130
|
+
defaultWebsocketSubProtocol,
|
|
1131
|
+
networkedDomProtocolSubProtocol_v0_1
|
|
1132
|
+
});
|
|
1133
|
+
//# sourceMappingURL=index.js.map
|