@mml-io/observable-dom 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.
@@ -0,0 +1 @@
1
+ export * from "../src/index";
package/build/index.js ADDED
@@ -0,0 +1,601 @@
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
+ JSDOMRunner: () => JSDOMRunner,
33
+ JSDOMRunnerFactory: () => JSDOMRunnerFactory,
34
+ ObservableDom: () => ObservableDom
35
+ });
36
+ module.exports = __toCommonJS(src_exports);
37
+
38
+ // src/utils.ts
39
+ function virtualDomElementToStatic(el) {
40
+ return {
41
+ nodeId: el.nodeId,
42
+ tag: el.tag,
43
+ attributes: el.attributes,
44
+ childNodes: el.childNodes.map((child) => virtualDomElementToStatic(child)),
45
+ textContent: el.textContent
46
+ };
47
+ }
48
+
49
+ // src/ObservableDom.ts
50
+ var ObservableDom = class {
51
+ constructor(observableDOMParameters, callback, runnerFactory) {
52
+ this.nodeToNodeId = /* @__PURE__ */ new Map();
53
+ this.nodeIdToNode = /* @__PURE__ */ new Map();
54
+ this.realElementToVirtualElement = /* @__PURE__ */ new Map();
55
+ this.ignoreTextNodes = true;
56
+ this.nextNodeId = 1;
57
+ this.htmlPath = observableDOMParameters.htmlPath;
58
+ this.ignoreTextNodes = observableDOMParameters.ignoreTextNodes;
59
+ this.callback = callback;
60
+ this.documentTimeIntervalTimer = setInterval(() => {
61
+ this.callback({
62
+ documentTime: this.getDocumentTime()
63
+ });
64
+ }, observableDOMParameters.pingIntervalMilliseconds || 5e3);
65
+ this.domRunner = runnerFactory(
66
+ observableDOMParameters.htmlPath,
67
+ observableDOMParameters.htmlContents,
68
+ observableDOMParameters.params,
69
+ (domRunnerMessage) => {
70
+ if (domRunnerMessage.loaded) {
71
+ this.createVirtualDomElementWithChildren(
72
+ this.domRunner.getDocument(),
73
+ null
74
+ );
75
+ const snapshot = virtualDomElementToStatic(
76
+ this.getVirtualDomElementForRealElementOrThrow(
77
+ this.domRunner.getDocument()
78
+ )
79
+ );
80
+ this.callback({
81
+ snapshot,
82
+ documentTime: this.getDocumentTime()
83
+ });
84
+ } else if (domRunnerMessage.mutationList) {
85
+ this.processModificationList(domRunnerMessage.mutationList);
86
+ } else if (domRunnerMessage.logMessage) {
87
+ this.callback({
88
+ logMessage: domRunnerMessage.logMessage,
89
+ documentTime: this.getDocumentTime()
90
+ });
91
+ }
92
+ }
93
+ );
94
+ }
95
+ addIPCWebsocket(webSocket) {
96
+ return this.domRunner.addIPCWebsocket(webSocket);
97
+ }
98
+ addConnectedUserId(connectionId) {
99
+ this.domRunner.getWindow().dispatchEvent(
100
+ new (this.domRunner.getWindow()).CustomEvent("connected", {
101
+ detail: { connectionId }
102
+ })
103
+ );
104
+ }
105
+ removeConnectedUserId(connectionId) {
106
+ this.domRunner.getWindow().dispatchEvent(
107
+ new (this.domRunner.getWindow()).CustomEvent("disconnected", {
108
+ detail: { connectionId }
109
+ })
110
+ );
111
+ }
112
+ processModificationList(mutationList) {
113
+ const documentEl = this.domRunner.getDocument();
114
+ const documentVirtualDomElement = this.realElementToVirtualElement.get(documentEl);
115
+ if (!documentVirtualDomElement) {
116
+ throw new Error(`document not created in processModificationList`);
117
+ }
118
+ if (mutationList.length > 1) {
119
+ console.error(
120
+ "More than one mutation record received. It is possible that intermediate states are incorrect."
121
+ );
122
+ }
123
+ for (const mutation of mutationList) {
124
+ if (this.isIgnoredElement(mutation.target)) {
125
+ continue;
126
+ }
127
+ if (mutation.type === "attributes" && // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
128
+ this.isIgnoredAttribute(mutation.target, mutation.attributeName)) {
129
+ continue;
130
+ }
131
+ this.addKnownNodesInMutation(mutation);
132
+ const firstNonIgnoredPreviousSibling = mutation.previousSibling ? this.getFirstNonIgnoredPreviousSibling(mutation.previousSibling) : null;
133
+ const targetElement = this.getVirtualDomElementForRealElementOrThrow(
134
+ mutation.target
135
+ );
136
+ const addedNodes = [];
137
+ for (const node of mutation.addedNodes) {
138
+ if (this.isIgnoredElement(node)) {
139
+ continue;
140
+ }
141
+ const virtualDomElement = this.getVirtualDomElementForRealElementOrThrow(
142
+ node
143
+ );
144
+ addedNodes.push(virtualDomElementToStatic(virtualDomElement));
145
+ }
146
+ const removedNodeIds = [];
147
+ for (const node of mutation.removedNodes) {
148
+ if (this.isIgnoredElement(node)) {
149
+ continue;
150
+ }
151
+ const virtualDomElement = this.getVirtualDomElementForRealElementOrThrow(
152
+ node
153
+ );
154
+ removedNodeIds.push(virtualDomElement.nodeId);
155
+ }
156
+ const mutationRecord = {
157
+ type: mutation.type,
158
+ targetId: targetElement.nodeId,
159
+ addedNodes,
160
+ removedNodeIds,
161
+ previousSiblingId: firstNonIgnoredPreviousSibling !== null ? this.getVirtualDomElementForRealElementOrThrow(firstNonIgnoredPreviousSibling).nodeId : null,
162
+ attribute: mutation.attributeName ? {
163
+ attributeName: mutation.attributeName,
164
+ value: mutation.target.getAttribute(mutation.attributeName)
165
+ } : null
166
+ };
167
+ this.callback({
168
+ mutation: mutationRecord,
169
+ documentTime: this.getDocumentTime()
170
+ });
171
+ this.removeKnownNodesInMutation(mutation);
172
+ }
173
+ }
174
+ addKnownNodesInMutation(mutation) {
175
+ const targetNode = mutation.target;
176
+ const virtualDomElement = this.realElementToVirtualElement.get(targetNode);
177
+ if (!virtualDomElement) {
178
+ throw new Error(
179
+ "Unknown node in addKnownNodesInMutation:" + targetNode + "," + mutation.type
180
+ );
181
+ }
182
+ if (mutation.type === "childList") {
183
+ let previousSibling = mutation.previousSibling;
184
+ let index = 0;
185
+ while (previousSibling && this.isIgnoredElement(previousSibling)) {
186
+ previousSibling = previousSibling.previousSibling;
187
+ }
188
+ if (previousSibling) {
189
+ const previousSiblingElement = this.realElementToVirtualElement.get(
190
+ previousSibling
191
+ );
192
+ if (!previousSiblingElement) {
193
+ throw new Error("Unknown previous sibling");
194
+ }
195
+ index = virtualDomElement.childNodes.indexOf(previousSiblingElement);
196
+ if (index === -1) {
197
+ throw new Error("Previous sibling is not currently a child of the parent element");
198
+ }
199
+ index += 1;
200
+ }
201
+ mutation.addedNodes.forEach((node) => {
202
+ const asElementOrText = node;
203
+ const childVirtualDomElement = this.createVirtualDomElementWithChildren(
204
+ asElementOrText,
205
+ virtualDomElement
206
+ );
207
+ if (childVirtualDomElement) {
208
+ if (virtualDomElement.childNodes.indexOf(childVirtualDomElement) === -1) {
209
+ virtualDomElement.childNodes.splice(index, 0, childVirtualDomElement);
210
+ index++;
211
+ }
212
+ }
213
+ });
214
+ } else if (mutation.type === "attributes") {
215
+ const attributeName = mutation.attributeName;
216
+ if (this.isIgnoredAttribute(targetNode, attributeName)) {
217
+ return;
218
+ }
219
+ const attributeValue = targetNode.getAttribute(attributeName);
220
+ if (attributeValue === null) {
221
+ delete virtualDomElement.attributes[attributeName];
222
+ } else {
223
+ virtualDomElement.attributes[attributeName] = attributeValue;
224
+ }
225
+ } else if (mutation.type === "characterData") {
226
+ virtualDomElement.textContent = targetNode.textContent ? targetNode.textContent : void 0;
227
+ }
228
+ }
229
+ removeKnownNodesInMutation(mutation) {
230
+ const targetNode = mutation.target;
231
+ const virtualDomElement = this.realElementToVirtualElement.get(targetNode);
232
+ if (!virtualDomElement) {
233
+ throw new Error("Unknown node in mutation list:" + targetNode + ", " + mutation.type);
234
+ }
235
+ if (mutation.type === "childList") {
236
+ for (const node of mutation.removedNodes) {
237
+ const asElementOrText = node;
238
+ if (this.isIgnoredElement(asElementOrText)) {
239
+ continue;
240
+ }
241
+ const childDomElement = this.realElementToVirtualElement.get(asElementOrText);
242
+ if (!childDomElement) {
243
+ console.warn(this.htmlPath, "Unknown node in removeKnownNodesInMutation");
244
+ continue;
245
+ } else {
246
+ this.removeVirtualDomElement(childDomElement);
247
+ const index = virtualDomElement.childNodes.indexOf(childDomElement);
248
+ virtualDomElement.childNodes.splice(index, 1);
249
+ }
250
+ }
251
+ return;
252
+ }
253
+ }
254
+ removeVirtualDomElement(virtualDomElement) {
255
+ this.nodeIdToNode.delete(virtualDomElement.nodeId);
256
+ this.nodeToNodeId.delete(virtualDomElement);
257
+ this.realElementToVirtualElement.delete(virtualDomElement.realElement);
258
+ for (const child of virtualDomElement.childNodes) {
259
+ this.removeVirtualDomElement(child);
260
+ }
261
+ }
262
+ createVirtualDomElementWithChildren(node, parent) {
263
+ const virtualElement = this.createVirtualDomElement(node, parent);
264
+ if (!virtualElement) {
265
+ return null;
266
+ }
267
+ if (node.childNodes) {
268
+ for (let i = 0; i < node.childNodes.length; i++) {
269
+ const child = node.childNodes[i];
270
+ const childVirtualElement = this.createVirtualDomElementWithChildren(
271
+ child,
272
+ virtualElement
273
+ );
274
+ if (childVirtualElement) {
275
+ virtualElement.childNodes.push(childVirtualElement);
276
+ }
277
+ }
278
+ }
279
+ return virtualElement;
280
+ }
281
+ createVirtualDomElement(node, parent) {
282
+ if (this.isIgnoredElement(node)) {
283
+ return null;
284
+ }
285
+ const existingValue = this.realElementToVirtualElement.get(node);
286
+ if (existingValue !== void 0) {
287
+ throw new Error("Node already has a virtual element: " + node.nodeName);
288
+ }
289
+ if (!node) {
290
+ throw new Error("Cannot assign node id to null");
291
+ }
292
+ const attributes = {};
293
+ if (node.attributes) {
294
+ const asHTMLElement = node;
295
+ for (const key of asHTMLElement.getAttributeNames()) {
296
+ const value = asHTMLElement.getAttribute(key);
297
+ if (value === null) {
298
+ throw new Error("Null attribute value for key: " + key);
299
+ }
300
+ if (!this.isIgnoredAttribute(node, key)) {
301
+ attributes[key] = value;
302
+ }
303
+ }
304
+ }
305
+ const nodeId = this.nextNodeId++;
306
+ const virtualElement = {
307
+ nodeId,
308
+ tag: node.nodeName,
309
+ attributes,
310
+ childNodes: [],
311
+ realElement: node,
312
+ parent
313
+ };
314
+ if (node instanceof this.domRunner.getWindow().Text && node.textContent) {
315
+ virtualElement.textContent = node.textContent;
316
+ }
317
+ this.nodeToNodeId.set(virtualElement, nodeId);
318
+ this.nodeIdToNode.set(nodeId, virtualElement);
319
+ this.realElementToVirtualElement.set(node, virtualElement);
320
+ return virtualElement;
321
+ }
322
+ getFirstNonIgnoredPreviousSibling(node) {
323
+ let currentNode = node;
324
+ if (!this.isIgnoredElement(currentNode)) {
325
+ return currentNode;
326
+ }
327
+ while (currentNode && currentNode.previousSibling) {
328
+ currentNode = currentNode.previousSibling;
329
+ if (!this.isIgnoredElement(currentNode)) {
330
+ return currentNode;
331
+ }
332
+ }
333
+ return null;
334
+ }
335
+ getVirtualDomElementForRealElementOrThrow(realElement) {
336
+ const virtualElement = this.realElementToVirtualElement.get(realElement);
337
+ if (!virtualElement) {
338
+ throw new Error(`Virtual element not found for real element`);
339
+ }
340
+ return virtualElement;
341
+ }
342
+ isIgnoredElement(node) {
343
+ if (this.ignoreTextNodes && node instanceof this.domRunner.getWindow().Text) {
344
+ return true;
345
+ } else if (node instanceof this.domRunner.getWindow().HTMLScriptElement) {
346
+ return true;
347
+ } else if (node instanceof this.domRunner.getWindow().Comment) {
348
+ return true;
349
+ }
350
+ return false;
351
+ }
352
+ isIgnoredAttribute(node, attributeName) {
353
+ return attributeName.startsWith("on");
354
+ }
355
+ dispatchRemoteEventFromConnectionId(connectionId, remoteEvent) {
356
+ const domNode = this.nodeIdToNode.get(remoteEvent.nodeId);
357
+ if (!domNode) {
358
+ console.error("Unknown node ID in remote event: " + remoteEvent.nodeId);
359
+ return;
360
+ }
361
+ if (domNode instanceof this.domRunner.getWindow().Text) {
362
+ console.warn("Cannot dispatch remote event to text node");
363
+ return;
364
+ }
365
+ this.domRunner.dispatchRemoteEventFromConnectionId(
366
+ connectionId,
367
+ domNode.realElement,
368
+ remoteEvent
369
+ );
370
+ }
371
+ dispose() {
372
+ clearInterval(this.documentTimeIntervalTimer);
373
+ this.domRunner.dispose();
374
+ }
375
+ getDocumentTime() {
376
+ return this.domRunner.getDocumentTime();
377
+ }
378
+ };
379
+
380
+ // src/JSDOMRunner.ts
381
+ var import_vm = __toESM(require("vm"));
382
+ var import_jsdom = require("jsdom");
383
+ var monkeyPatchedMutationRecordCallbacks = /* @__PURE__ */ new Set();
384
+ function installMutationObserverMonkeyPatch() {
385
+ const MutationRecordExports = require("jsdom/lib/jsdom/living/generated/MutationRecord");
386
+ const originalCreateImpl = MutationRecordExports.createImpl;
387
+ MutationRecordExports.createImpl = (...args) => {
388
+ for (const callback of monkeyPatchedMutationRecordCallbacks) {
389
+ callback();
390
+ }
391
+ return originalCreateImpl.call(null, ...args);
392
+ };
393
+ }
394
+ var monkeyPatchInstalled = false;
395
+ var JSDOMRunnerFactory = (htmlPath, htmlContents, params, callback) => {
396
+ return new JSDOMRunner(htmlPath, htmlContents, params, callback);
397
+ };
398
+ var RejectionResourceLoader = class extends import_jsdom.ResourceLoader {
399
+ fetch(url) {
400
+ console.error("RejectionResourceLoader.fetch", url);
401
+ return null;
402
+ }
403
+ };
404
+ var JSDOMRunner = class {
405
+ constructor(htmlPath, htmlContents, params, callback) {
406
+ this.ipcWebsockets = /* @__PURE__ */ new Set();
407
+ this.ipcListeners = /* @__PURE__ */ new Set();
408
+ this.documentStartTime = Date.now();
409
+ this.isLoaded = false;
410
+ this.logBuffer = [];
411
+ this.htmlPath = htmlPath;
412
+ this.callback = callback;
413
+ if (!monkeyPatchInstalled) {
414
+ installMutationObserverMonkeyPatch();
415
+ monkeyPatchInstalled = true;
416
+ }
417
+ this.monkeyPatchMutationRecordCallback = () => {
418
+ const records = this.mutationObserver.takeRecords();
419
+ if (records.length > 1) {
420
+ throw new Error(
421
+ "The monkey patching should have prevented more than one record being handled at a time"
422
+ );
423
+ } else if (records.length > 0) {
424
+ this.callback({
425
+ mutationList: records
426
+ });
427
+ }
428
+ };
429
+ monkeyPatchedMutationRecordCallbacks.add(this.monkeyPatchMutationRecordCallback);
430
+ this.jsDom = new import_jsdom.JSDOM(htmlContents, {
431
+ runScripts: "dangerously",
432
+ resources: new RejectionResourceLoader(),
433
+ url: this.htmlPath,
434
+ virtualConsole: this.createVirtualConsole(),
435
+ beforeParse: (window) => {
436
+ this.domWindow = window;
437
+ const timeline = {};
438
+ Object.defineProperty(timeline, "currentTime", {
439
+ get: () => {
440
+ return this.getDocumentTime();
441
+ }
442
+ });
443
+ window.document.timeline = timeline;
444
+ window.params = JSON.parse(JSON.stringify(params));
445
+ const oldDocumentAddEventListener = window.document.addEventListener;
446
+ window.document.addEventListener = (...args) => {
447
+ const [eventName, listener] = args;
448
+ if (eventName === "ipc") {
449
+ this.ipcListeners.add(listener);
450
+ }
451
+ return oldDocumentAddEventListener.call(window.document, ...args);
452
+ };
453
+ const oldDocumentRemoveEventListener = window.document.addEventListener;
454
+ window.document.removeEventListener = (...args) => {
455
+ const [eventName, listener] = args;
456
+ if (eventName === "ipc") {
457
+ this.ipcListeners.delete(listener);
458
+ }
459
+ return oldDocumentRemoveEventListener.call(window.document, ...args);
460
+ };
461
+ this.mutationObserver = new window.MutationObserver((mutationList) => {
462
+ this.callback({
463
+ mutationList
464
+ });
465
+ });
466
+ window.addEventListener("load", () => {
467
+ this.mutationObserver.observe(window.document, {
468
+ attributes: true,
469
+ childList: true,
470
+ subtree: true,
471
+ characterData: true
472
+ });
473
+ this.isLoaded = true;
474
+ this.callback({
475
+ loaded: true
476
+ });
477
+ this.flushLogBuffer();
478
+ });
479
+ }
480
+ });
481
+ }
482
+ flushLogBuffer() {
483
+ for (const logMessage of this.logBuffer) {
484
+ this.callback({
485
+ logMessage
486
+ });
487
+ }
488
+ this.logBuffer = [];
489
+ }
490
+ log(message) {
491
+ if (!this.isLoaded) {
492
+ this.logBuffer.push(message);
493
+ return;
494
+ }
495
+ this.callback({
496
+ logMessage: message
497
+ });
498
+ }
499
+ getDocument() {
500
+ return this.domWindow.document;
501
+ }
502
+ getWindow() {
503
+ return this.domWindow;
504
+ }
505
+ addIPCWebsocket(webSocket) {
506
+ if (this.ipcListeners.size === 0) {
507
+ console.error("ipc requested, but no ipc listeners registered on document:", this.htmlPath);
508
+ webSocket.close();
509
+ return;
510
+ }
511
+ this.ipcWebsockets.add(webSocket);
512
+ const event = new this.domWindow.CustomEvent("ipc", {
513
+ detail: {
514
+ webSocket
515
+ }
516
+ });
517
+ for (const ipcListener of this.ipcListeners) {
518
+ ipcListener(event);
519
+ }
520
+ webSocket.addEventListener("close", () => {
521
+ this.ipcWebsockets.delete(webSocket);
522
+ });
523
+ return;
524
+ }
525
+ dispose() {
526
+ const records = this.mutationObserver.takeRecords();
527
+ this.callback({
528
+ mutationList: records
529
+ });
530
+ monkeyPatchedMutationRecordCallbacks.delete(this.monkeyPatchMutationRecordCallback);
531
+ this.mutationObserver.disconnect();
532
+ this.jsDom.window.close();
533
+ }
534
+ getDocumentTime() {
535
+ return Date.now() - this.documentStartTime;
536
+ }
537
+ dispatchRemoteEventFromConnectionId(connectionId, domNode, remoteEvent) {
538
+ const bubbles = remoteEvent.bubbles || false;
539
+ const remoteEventObject = new this.domWindow.CustomEvent(remoteEvent.name, {
540
+ bubbles,
541
+ detail: { ...remoteEvent.params, connectionId }
542
+ });
543
+ const eventTypeLowerCase = remoteEvent.name.toLowerCase();
544
+ if (eventTypeLowerCase !== "click") {
545
+ const handlerAttributeName = "on" + eventTypeLowerCase;
546
+ const handlerAttributeValue = domNode.getAttribute(handlerAttributeName);
547
+ if (handlerAttributeValue) {
548
+ const script = handlerAttributeValue;
549
+ const vmContext = this.jsDom.getInternalVMContext();
550
+ try {
551
+ const invoke = import_vm.default.runInContext(`(function(event){ ${script} })`, vmContext);
552
+ Reflect.apply(invoke, domNode, [remoteEventObject]);
553
+ } catch (e) {
554
+ console.error("Error running event handler:", e);
555
+ }
556
+ }
557
+ }
558
+ domNode.dispatchEvent(remoteEventObject);
559
+ }
560
+ createVirtualConsole() {
561
+ const virtualConsole = new import_jsdom.VirtualConsole();
562
+ virtualConsole.on("jsdomError", (...args) => {
563
+ this.log({
564
+ level: "system",
565
+ content: args
566
+ });
567
+ });
568
+ virtualConsole.on("error", (...args) => {
569
+ this.log({
570
+ level: "error",
571
+ content: args
572
+ });
573
+ });
574
+ virtualConsole.on("warn", (...args) => {
575
+ this.log({
576
+ level: "warn",
577
+ content: args
578
+ });
579
+ });
580
+ virtualConsole.on("log", (...args) => {
581
+ this.log({
582
+ level: "log",
583
+ content: args
584
+ });
585
+ });
586
+ virtualConsole.on("info", (...args) => {
587
+ this.log({
588
+ level: "info",
589
+ content: args
590
+ });
591
+ });
592
+ return virtualConsole;
593
+ }
594
+ };
595
+ // Annotate the CommonJS export names for ESM import in node:
596
+ 0 && (module.exports = {
597
+ JSDOMRunner,
598
+ JSDOMRunnerFactory,
599
+ ObservableDom
600
+ });
601
+ //# sourceMappingURL=index.js.map