@openreplay/tracker 3.4.10 → 3.4.14

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.
@@ -1,392 +0,0 @@
1
- import { stars, hasOpenreplayAttribute } from "../utils.js";
2
- import { CreateDocument, CreateElementNode, CreateTextNode, SetNodeData, SetCSSDataURLBased, SetNodeAttribute, SetNodeAttributeURLBased, RemoveNodeAttribute, MoveNode, RemoveNode, CreateIFrameDocument, } from "../messages/index.js";
3
- function isSVGElement(node) {
4
- return node.namespaceURI === 'http://www.w3.org/2000/svg';
5
- }
6
- export default class Observer {
7
- constructor(app, options, context = window) {
8
- this.app = app;
9
- this.options = options;
10
- this.context = context;
11
- this.iframeObservers = [];
12
- this.observer = new MutationObserver(this.app.safe((mutations) => {
13
- var _a;
14
- for (const mutation of mutations) {
15
- const target = mutation.target;
16
- const type = mutation.type;
17
- // Special case
18
- // Document 'childList' might happen in case of iframe.
19
- // TODO: generalize as much as possible
20
- if (this.isInstance(target, Document)
21
- && type === 'childList'
22
- //&& new Array(mutation.addedNodes).some(node => this.isInstance(node, HTMLHtmlElement))
23
- ) {
24
- const parentFrame = (_a = target.defaultView) === null || _a === void 0 ? void 0 : _a.frameElement;
25
- if (!parentFrame) {
26
- continue;
27
- }
28
- this.bindTree(target.documentElement);
29
- const frameID = this.app.nodes.getID(parentFrame);
30
- const docID = this.app.nodes.getID(target.documentElement);
31
- if (frameID === undefined || docID === undefined) {
32
- continue;
33
- }
34
- this.app.send(CreateIFrameDocument(frameID, docID));
35
- continue;
36
- }
37
- if (this.isIgnored(target) || !context.document.contains(target)) {
38
- continue;
39
- }
40
- if (type === 'childList') {
41
- for (let i = 0; i < mutation.removedNodes.length; i++) {
42
- this.bindTree(mutation.removedNodes[i]);
43
- }
44
- for (let i = 0; i < mutation.addedNodes.length; i++) {
45
- this.bindTree(mutation.addedNodes[i]);
46
- }
47
- continue;
48
- }
49
- const id = this.app.nodes.getID(target);
50
- if (id === undefined) {
51
- continue;
52
- }
53
- if (id >= this.recents.length) {
54
- this.recents[id] = undefined;
55
- }
56
- if (type === 'attributes') {
57
- const name = mutation.attributeName;
58
- if (name === null) {
59
- continue;
60
- }
61
- let attr = this.attributesList[id];
62
- if (attr === undefined) {
63
- this.attributesList[id] = attr = new Set();
64
- }
65
- attr.add(name);
66
- continue;
67
- }
68
- if (type === 'characterData') {
69
- this.textSet.add(id);
70
- continue;
71
- }
72
- }
73
- this.commitNodes();
74
- }));
75
- this.commited = [];
76
- this.recents = [];
77
- this.indexes = [0];
78
- this.attributesList = [];
79
- this.textSet = new Set();
80
- this.textMasked = new Set();
81
- }
82
- clear() {
83
- this.commited.length = 0;
84
- this.recents.length = 0;
85
- this.indexes.length = 1;
86
- this.attributesList.length = 0;
87
- this.textSet.clear();
88
- this.textMasked.clear();
89
- }
90
- // TODO: we need a type expert here so we won't have to ignore the lines
91
- isInstance(node, constr) {
92
- let context = this.context;
93
- while (context.parent && context.parent !== context) {
94
- // @ts-ignore
95
- if (node instanceof context[constr.name]) {
96
- return true;
97
- }
98
- // @ts-ignore
99
- context = context.parent;
100
- }
101
- // @ts-ignore
102
- return node instanceof context[constr.name];
103
- }
104
- isIgnored(node) {
105
- if (this.isInstance(node, Text)) {
106
- return false;
107
- }
108
- if (!this.isInstance(node, Element)) {
109
- return true;
110
- }
111
- const tag = node.tagName.toUpperCase();
112
- if (tag === 'LINK') {
113
- const rel = node.getAttribute('rel');
114
- const as = node.getAttribute('as');
115
- return !((rel === null || rel === void 0 ? void 0 : rel.includes('stylesheet')) || as === "style" || as === "font");
116
- }
117
- return (tag === 'SCRIPT' ||
118
- tag === 'NOSCRIPT' ||
119
- tag === 'META' ||
120
- tag === 'TITLE' ||
121
- tag === 'BASE');
122
- }
123
- sendNodeAttribute(id, node, name, value) {
124
- if (isSVGElement(node)) {
125
- if (name.substr(0, 6) === 'xlink:') {
126
- name = name.substr(6);
127
- }
128
- if (value === null) {
129
- this.app.send(new RemoveNodeAttribute(id, name));
130
- }
131
- else if (name === 'href') {
132
- if (value.length > 1e5) {
133
- value = '';
134
- }
135
- this.app.send(new SetNodeAttributeURLBased(id, name, value, this.app.getBaseHref()));
136
- }
137
- else {
138
- this.app.send(new SetNodeAttribute(id, name, value));
139
- }
140
- return;
141
- }
142
- if (name === 'src' ||
143
- name === 'srcset' ||
144
- name === 'integrity' ||
145
- name === 'crossorigin' ||
146
- name === 'autocomplete' ||
147
- name.substr(0, 2) === 'on') {
148
- return;
149
- }
150
- if (name === 'value' &&
151
- this.isInstance(node, HTMLInputElement) &&
152
- node.type !== 'button' &&
153
- node.type !== 'reset' &&
154
- node.type !== 'submit') {
155
- return;
156
- }
157
- if (value === null) {
158
- this.app.send(new RemoveNodeAttribute(id, name));
159
- return;
160
- }
161
- if (name === 'style' || name === 'href' && this.isInstance(node, HTMLLinkElement)) {
162
- this.app.send(new SetNodeAttributeURLBased(id, name, value, this.app.getBaseHref()));
163
- return;
164
- }
165
- if (name === 'href' || value.length > 1e5) {
166
- value = '';
167
- }
168
- this.app.send(new SetNodeAttribute(id, name, value));
169
- }
170
- /* TODO: abstract sanitation */
171
- getInnerTextSecure(el) {
172
- const id = this.app.nodes.getID(el);
173
- if (!id) {
174
- return '';
175
- }
176
- return this.checkObscure(id, el.innerText);
177
- }
178
- checkObscure(id, data) {
179
- if (this.textMasked.has(id)) {
180
- return data.replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/g, '█');
181
- }
182
- if (this.options.obscureTextNumbers) {
183
- data = data.replace(/\d/g, '0');
184
- }
185
- if (this.options.obscureTextEmails) {
186
- data = data.replace(/([^\s]+)@([^\s]+)\.([^\s]+)/g, (...f) => stars(f[1]) + '@' + stars(f[2]) + '.' + stars(f[3]));
187
- }
188
- return data;
189
- }
190
- sendNodeData(id, parentElement, data) {
191
- if (this.isInstance(parentElement, HTMLStyleElement) || this.isInstance(parentElement, SVGStyleElement)) {
192
- this.app.send(new SetCSSDataURLBased(id, data, this.app.getBaseHref()));
193
- return;
194
- }
195
- data = this.checkObscure(id, data);
196
- this.app.send(new SetNodeData(id, data));
197
- }
198
- /* end TODO: abstract sanitation */
199
- bindNode(node) {
200
- const r = this.app.nodes.registerNode(node);
201
- const id = r[0];
202
- this.recents[id] = r[1] || this.recents[id] || false;
203
- }
204
- bindTree(node) {
205
- if (this.isIgnored(node)) {
206
- return;
207
- }
208
- this.bindNode(node);
209
- const walker = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_TEXT, {
210
- acceptNode: (node) => this.isIgnored(node) || this.app.nodes.getID(node) !== undefined
211
- ? NodeFilter.FILTER_REJECT
212
- : NodeFilter.FILTER_ACCEPT,
213
- },
214
- // @ts-ignore
215
- false);
216
- while (walker.nextNode()) {
217
- this.bindNode(walker.currentNode);
218
- }
219
- }
220
- unbindNode(node) {
221
- const id = this.app.nodes.unregisterNode(node);
222
- if (id !== undefined && this.recents[id] === false) {
223
- this.app.send(new RemoveNode(id));
224
- }
225
- }
226
- _commitNode(id, node) {
227
- const parent = node.parentNode;
228
- let parentID;
229
- if (this.isInstance(node, HTMLHtmlElement)) {
230
- this.indexes[id] = 0;
231
- }
232
- else {
233
- if (parent === null) {
234
- this.unbindNode(node);
235
- return false;
236
- }
237
- parentID = this.app.nodes.getID(parent);
238
- if (parentID === undefined) {
239
- this.unbindNode(node);
240
- return false;
241
- }
242
- if (!this.commitNode(parentID)) {
243
- this.unbindNode(node);
244
- return false;
245
- }
246
- if (this.textMasked.has(parentID) ||
247
- (this.isInstance(node, Element) && hasOpenreplayAttribute(node, 'masked'))) {
248
- this.textMasked.add(id);
249
- }
250
- let sibling = node.previousSibling;
251
- while (sibling !== null) {
252
- const siblingID = this.app.nodes.getID(sibling);
253
- if (siblingID !== undefined) {
254
- this.commitNode(siblingID);
255
- this.indexes[id] = this.indexes[siblingID] + 1;
256
- break;
257
- }
258
- sibling = sibling.previousSibling;
259
- }
260
- if (sibling === null) {
261
- this.indexes[id] = 0;
262
- }
263
- }
264
- const isNew = this.recents[id];
265
- const index = this.indexes[id];
266
- if (index === undefined) {
267
- throw 'commitNode: missing node index';
268
- }
269
- if (isNew === true) {
270
- if (this.isInstance(node, Element)) {
271
- if (parentID !== undefined) {
272
- this.app.send(new CreateElementNode(id, parentID, index, node.tagName, isSVGElement(node)));
273
- }
274
- for (let i = 0; i < node.attributes.length; i++) {
275
- const attr = node.attributes[i];
276
- this.sendNodeAttribute(id, node, attr.nodeName, attr.value);
277
- }
278
- if (this.isInstance(node, HTMLIFrameElement) &&
279
- (this.options.captureIFrames || node.getAttribute("data-openreplay-capture"))) {
280
- this.handleIframe(node);
281
- }
282
- }
283
- else if (this.isInstance(node, Text)) {
284
- // for text node id != 0, hence parentID !== undefined and parent is Element
285
- this.app.send(new CreateTextNode(id, parentID, index));
286
- this.sendNodeData(id, parent, node.data);
287
- }
288
- return true;
289
- }
290
- if (isNew === false && parentID !== undefined) {
291
- this.app.send(new MoveNode(id, parentID, index));
292
- }
293
- const attr = this.attributesList[id];
294
- if (attr !== undefined) {
295
- if (!this.isInstance(node, Element)) {
296
- throw 'commitNode: node is not an element';
297
- }
298
- for (const name of attr) {
299
- this.sendNodeAttribute(id, node, name, node.getAttribute(name));
300
- }
301
- }
302
- if (this.textSet.has(id)) {
303
- if (!this.isInstance(node, Text)) {
304
- throw 'commitNode: node is not a text';
305
- }
306
- // for text node id != 0, hence parent is Element
307
- this.sendNodeData(id, parent, node.data);
308
- }
309
- return true;
310
- }
311
- commitNode(id) {
312
- const node = this.app.nodes.getNode(id);
313
- if (node === undefined) {
314
- return false;
315
- }
316
- const cmt = this.commited[id];
317
- if (cmt !== undefined) {
318
- return cmt;
319
- }
320
- return (this.commited[id] = this._commitNode(id, node));
321
- }
322
- commitNodes() {
323
- let node;
324
- for (let id = 0; id < this.recents.length; id++) {
325
- this.commitNode(id);
326
- if (this.recents[id] === true && (node = this.app.nodes.getNode(id))) {
327
- this.app.nodes.callNodeCallbacks(node);
328
- }
329
- }
330
- this.clear();
331
- }
332
- handleIframe(iframe) {
333
- let context = null;
334
- const handle = this.app.safe(() => {
335
- const id = this.app.nodes.getID(iframe);
336
- if (id === undefined) {
337
- return;
338
- }
339
- if (iframe.contentWindow === context) {
340
- return;
341
- }
342
- context = iframe.contentWindow;
343
- if (!context) {
344
- return;
345
- }
346
- const observer = new Observer(this.app, this.options, context);
347
- this.iframeObservers.push(observer);
348
- observer.observeIframe(id, context);
349
- });
350
- this.app.attachEventListener(iframe, "load", handle);
351
- handle();
352
- }
353
- // TODO: abstract common functionality, separate FrameObserver
354
- observeIframe(id, context) {
355
- const doc = context.document;
356
- this.observer.observe(doc, {
357
- childList: true,
358
- attributes: true,
359
- characterData: true,
360
- subtree: true,
361
- attributeOldValue: false,
362
- characterDataOldValue: false,
363
- });
364
- this.bindTree(doc.documentElement);
365
- const docID = this.app.nodes.getID(doc.documentElement);
366
- if (docID === undefined) {
367
- console.log("Wrong");
368
- return;
369
- }
370
- this.app.send(CreateIFrameDocument(id, docID));
371
- this.commitNodes();
372
- }
373
- observe() {
374
- this.observer.observe(this.context.document, {
375
- childList: true,
376
- attributes: true,
377
- characterData: true,
378
- subtree: true,
379
- attributeOldValue: false,
380
- characterDataOldValue: false,
381
- });
382
- this.app.send(new CreateDocument());
383
- this.bindTree(this.context.document.documentElement);
384
- this.commitNodes();
385
- }
386
- disconnect() {
387
- this.iframeObservers.forEach(o => o.disconnect());
388
- this.iframeObservers = [];
389
- this.observer.disconnect();
390
- this.clear();
391
- }
392
- }