@openreplay/tracker 3.4.13 → 3.4.17-beta.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/cjs/app/context.d.ts +18 -0
- package/cjs/app/context.js +48 -0
- package/cjs/app/index.d.ts +11 -6
- package/cjs/app/index.js +33 -22
- package/cjs/app/observer/iframe_observer.d.ts +4 -0
- package/cjs/app/observer/iframe_observer.js +22 -0
- package/cjs/app/observer/observer.d.ts +25 -0
- package/cjs/app/{observer.js → observer/observer.js} +82 -170
- package/cjs/app/observer/shadow_root_observer.d.ts +4 -0
- package/cjs/app/observer/shadow_root_observer.js +21 -0
- package/cjs/app/observer/top_observer.d.ts +15 -0
- package/cjs/app/observer/top_observer.js +84 -0
- package/cjs/app/sanitizer.d.ts +16 -0
- package/cjs/app/sanitizer.js +46 -0
- package/cjs/index.d.ts +1 -0
- package/cjs/index.js +7 -1
- package/cjs/modules/exception.js +8 -1
- package/cjs/modules/img.js +15 -1
- package/cjs/modules/input.d.ts +3 -1
- package/cjs/modules/input.js +6 -3
- package/cjs/modules/mouse.js +1 -1
- package/lib/app/context.d.ts +18 -0
- package/lib/app/context.js +43 -0
- package/lib/app/index.d.ts +11 -6
- package/lib/app/index.js +33 -22
- package/lib/app/observer/iframe_observer.d.ts +4 -0
- package/lib/app/observer/iframe_observer.js +19 -0
- package/lib/app/observer/observer.d.ts +25 -0
- package/lib/app/{observer.js → observer/observer.js} +82 -170
- package/lib/app/observer/shadow_root_observer.d.ts +4 -0
- package/lib/app/observer/shadow_root_observer.js +18 -0
- package/lib/app/observer/top_observer.d.ts +15 -0
- package/lib/app/observer/top_observer.js +81 -0
- package/lib/app/sanitizer.d.ts +16 -0
- package/lib/app/sanitizer.js +44 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +7 -1
- package/lib/modules/exception.js +8 -1
- package/lib/modules/img.js +16 -2
- package/lib/modules/input.d.ts +3 -1
- package/lib/modules/input.js +6 -3
- package/lib/modules/mouse.js +1 -1
- package/package.json +1 -1
- package/cjs/app/observer.d.ts +0 -47
- package/lib/app/observer.d.ts +0 -47
|
@@ -1,42 +1,54 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const
|
|
4
|
-
const
|
|
3
|
+
const index_js_1 = require("../../messages/index.js");
|
|
4
|
+
const context_js_1 = require("../context.js");
|
|
5
5
|
function isSVGElement(node) {
|
|
6
6
|
return node.namespaceURI === 'http://www.w3.org/2000/svg';
|
|
7
7
|
}
|
|
8
|
+
function isIgnored(node) {
|
|
9
|
+
if ((0, context_js_1.isInstance)(node, Text)) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
if (!(0, context_js_1.isInstance)(node, Element)) {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
const tag = node.tagName.toUpperCase();
|
|
16
|
+
if (tag === 'LINK') {
|
|
17
|
+
const rel = node.getAttribute('rel');
|
|
18
|
+
const as = node.getAttribute('as');
|
|
19
|
+
return !((rel === null || rel === void 0 ? void 0 : rel.includes('stylesheet')) || as === "style" || as === "font");
|
|
20
|
+
}
|
|
21
|
+
return (tag === 'SCRIPT' ||
|
|
22
|
+
tag === 'NOSCRIPT' ||
|
|
23
|
+
tag === 'META' ||
|
|
24
|
+
tag === 'TITLE' ||
|
|
25
|
+
tag === 'BASE');
|
|
26
|
+
}
|
|
27
|
+
function isRootNode(node) {
|
|
28
|
+
return (0, context_js_1.isInstance)(node, Document) || (0, context_js_1.isInstance)(node, ShadowRoot);
|
|
29
|
+
}
|
|
30
|
+
function isObservable(node) {
|
|
31
|
+
if (isRootNode(node)) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
return !isIgnored(node);
|
|
35
|
+
}
|
|
8
36
|
class Observer {
|
|
9
|
-
constructor(app,
|
|
37
|
+
constructor(app, context = window) {
|
|
10
38
|
this.app = app;
|
|
11
|
-
this.options = options;
|
|
12
39
|
this.context = context;
|
|
13
|
-
this.
|
|
40
|
+
this.commited = [];
|
|
41
|
+
this.recents = [];
|
|
42
|
+
this.myNodes = [];
|
|
43
|
+
this.indexes = [];
|
|
44
|
+
this.attributesList = [];
|
|
45
|
+
this.textSet = new Set();
|
|
46
|
+
this.inUpperContext = context.parent === context; //TODO: get rid of context here
|
|
14
47
|
this.observer = new MutationObserver(this.app.safe((mutations) => {
|
|
15
|
-
var _a;
|
|
16
48
|
for (const mutation of mutations) {
|
|
17
49
|
const target = mutation.target;
|
|
18
50
|
const type = mutation.type;
|
|
19
|
-
|
|
20
|
-
// Document 'childList' might happen in case of iframe.
|
|
21
|
-
// TODO: generalize as much as possible
|
|
22
|
-
if (this.isInstance(target, Document)
|
|
23
|
-
&& type === 'childList'
|
|
24
|
-
//&& new Array(mutation.addedNodes).some(node => this.isInstance(node, HTMLHtmlElement))
|
|
25
|
-
) {
|
|
26
|
-
const parentFrame = (_a = target.defaultView) === null || _a === void 0 ? void 0 : _a.frameElement;
|
|
27
|
-
if (!parentFrame) {
|
|
28
|
-
continue;
|
|
29
|
-
}
|
|
30
|
-
this.bindTree(target.documentElement);
|
|
31
|
-
const frameID = this.app.nodes.getID(parentFrame);
|
|
32
|
-
const docID = this.app.nodes.getID(target.documentElement);
|
|
33
|
-
if (frameID === undefined || docID === undefined) {
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
this.app.send((0, index_js_1.CreateIFrameDocument)(frameID, docID));
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
if (this.isIgnored(target) || !context.document.contains(target)) {
|
|
51
|
+
if (!isObservable(target) || !(0, context_js_1.inDocument)(target)) {
|
|
40
52
|
continue;
|
|
41
53
|
}
|
|
42
54
|
if (type === 'childList') {
|
|
@@ -52,7 +64,7 @@ class Observer {
|
|
|
52
64
|
if (id === undefined) {
|
|
53
65
|
continue;
|
|
54
66
|
}
|
|
55
|
-
if (id >= this.recents.length) {
|
|
67
|
+
if (id >= this.recents.length) { // TODO: something more convinient
|
|
56
68
|
this.recents[id] = undefined;
|
|
57
69
|
}
|
|
58
70
|
if (type === 'attributes') {
|
|
@@ -74,12 +86,6 @@ class Observer {
|
|
|
74
86
|
}
|
|
75
87
|
this.commitNodes();
|
|
76
88
|
}));
|
|
77
|
-
this.commited = [];
|
|
78
|
-
this.recents = [];
|
|
79
|
-
this.indexes = [0];
|
|
80
|
-
this.attributesList = [];
|
|
81
|
-
this.textSet = new Set();
|
|
82
|
-
this.textMasked = new Set();
|
|
83
89
|
}
|
|
84
90
|
clear() {
|
|
85
91
|
this.commited.length = 0;
|
|
@@ -87,40 +93,6 @@ class Observer {
|
|
|
87
93
|
this.indexes.length = 1;
|
|
88
94
|
this.attributesList.length = 0;
|
|
89
95
|
this.textSet.clear();
|
|
90
|
-
this.textMasked.clear();
|
|
91
|
-
}
|
|
92
|
-
// TODO: we need a type expert here so we won't have to ignore the lines
|
|
93
|
-
isInstance(node, constr) {
|
|
94
|
-
let context = this.context;
|
|
95
|
-
while (context.parent && context.parent !== context) {
|
|
96
|
-
// @ts-ignore
|
|
97
|
-
if (node instanceof context[constr.name]) {
|
|
98
|
-
return true;
|
|
99
|
-
}
|
|
100
|
-
// @ts-ignore
|
|
101
|
-
context = context.parent;
|
|
102
|
-
}
|
|
103
|
-
// @ts-ignore
|
|
104
|
-
return node instanceof context[constr.name];
|
|
105
|
-
}
|
|
106
|
-
isIgnored(node) {
|
|
107
|
-
if (this.isInstance(node, Text)) {
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
if (!this.isInstance(node, Element)) {
|
|
111
|
-
return true;
|
|
112
|
-
}
|
|
113
|
-
const tag = node.tagName.toUpperCase();
|
|
114
|
-
if (tag === 'LINK') {
|
|
115
|
-
const rel = node.getAttribute('rel');
|
|
116
|
-
const as = node.getAttribute('as');
|
|
117
|
-
return !((rel === null || rel === void 0 ? void 0 : rel.includes('stylesheet')) || as === "style" || as === "font");
|
|
118
|
-
}
|
|
119
|
-
return (tag === 'SCRIPT' ||
|
|
120
|
-
tag === 'NOSCRIPT' ||
|
|
121
|
-
tag === 'META' ||
|
|
122
|
-
tag === 'TITLE' ||
|
|
123
|
-
tag === 'BASE');
|
|
124
96
|
}
|
|
125
97
|
sendNodeAttribute(id, node, name, value) {
|
|
126
98
|
if (isSVGElement(node)) {
|
|
@@ -150,7 +122,7 @@ class Observer {
|
|
|
150
122
|
return;
|
|
151
123
|
}
|
|
152
124
|
if (name === 'value' &&
|
|
153
|
-
|
|
125
|
+
(0, context_js_1.isInstance)(node, HTMLInputElement) &&
|
|
154
126
|
node.type !== 'button' &&
|
|
155
127
|
node.type !== 'reset' &&
|
|
156
128
|
node.type !== 'submit') {
|
|
@@ -160,7 +132,7 @@ class Observer {
|
|
|
160
132
|
this.app.send(new index_js_1.RemoveNodeAttribute(id, name));
|
|
161
133
|
return;
|
|
162
134
|
}
|
|
163
|
-
if (name === 'style' || name === 'href' &&
|
|
135
|
+
if (name === 'style' || name === 'href' && (0, context_js_1.isInstance)(node, HTMLLinkElement)) {
|
|
164
136
|
this.app.send(new index_js_1.SetNodeAttributeURLBased(id, name, value, this.app.getBaseHref()));
|
|
165
137
|
return;
|
|
166
138
|
}
|
|
@@ -169,47 +141,27 @@ class Observer {
|
|
|
169
141
|
}
|
|
170
142
|
this.app.send(new index_js_1.SetNodeAttribute(id, name, value));
|
|
171
143
|
}
|
|
172
|
-
/* TODO: abstract sanitation */
|
|
173
|
-
getInnerTextSecure(el) {
|
|
174
|
-
const id = this.app.nodes.getID(el);
|
|
175
|
-
if (!id) {
|
|
176
|
-
return '';
|
|
177
|
-
}
|
|
178
|
-
return this.checkObscure(id, el.innerText);
|
|
179
|
-
}
|
|
180
|
-
checkObscure(id, data) {
|
|
181
|
-
if (this.textMasked.has(id)) {
|
|
182
|
-
return data.replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/g, '█');
|
|
183
|
-
}
|
|
184
|
-
if (this.options.obscureTextNumbers) {
|
|
185
|
-
data = data.replace(/\d/g, '0');
|
|
186
|
-
}
|
|
187
|
-
if (this.options.obscureTextEmails) {
|
|
188
|
-
data = data.replace(/([^\s]+)@([^\s]+)\.([^\s]+)/g, (...f) => (0, utils_js_1.stars)(f[1]) + '@' + (0, utils_js_1.stars)(f[2]) + '.' + (0, utils_js_1.stars)(f[3]));
|
|
189
|
-
}
|
|
190
|
-
return data;
|
|
191
|
-
}
|
|
192
144
|
sendNodeData(id, parentElement, data) {
|
|
193
|
-
if (
|
|
145
|
+
if ((0, context_js_1.isInstance)(parentElement, HTMLStyleElement) || (0, context_js_1.isInstance)(parentElement, SVGStyleElement)) {
|
|
194
146
|
this.app.send(new index_js_1.SetCSSDataURLBased(id, data, this.app.getBaseHref()));
|
|
195
147
|
return;
|
|
196
148
|
}
|
|
197
|
-
data = this.
|
|
149
|
+
data = this.app.sanitizer.sanitize(id, data);
|
|
198
150
|
this.app.send(new index_js_1.SetNodeData(id, data));
|
|
199
151
|
}
|
|
200
|
-
/* end TODO: abstract sanitation */
|
|
201
152
|
bindNode(node) {
|
|
202
153
|
const r = this.app.nodes.registerNode(node);
|
|
203
154
|
const id = r[0];
|
|
204
155
|
this.recents[id] = r[1] || this.recents[id] || false;
|
|
156
|
+
this.myNodes[id] = true;
|
|
205
157
|
}
|
|
206
158
|
bindTree(node) {
|
|
207
|
-
if (
|
|
159
|
+
if (!isObservable(node)) {
|
|
208
160
|
return;
|
|
209
161
|
}
|
|
210
162
|
this.bindNode(node);
|
|
211
163
|
const walker = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_TEXT, {
|
|
212
|
-
acceptNode: (node) =>
|
|
164
|
+
acceptNode: (node) => isIgnored(node) || this.app.nodes.getID(node) !== undefined
|
|
213
165
|
? NodeFilter.FILTER_REJECT
|
|
214
166
|
: NodeFilter.FILTER_ACCEPT,
|
|
215
167
|
},
|
|
@@ -226,12 +178,15 @@ class Observer {
|
|
|
226
178
|
}
|
|
227
179
|
}
|
|
228
180
|
_commitNode(id, node) {
|
|
181
|
+
if (isRootNode(node)) {
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
229
184
|
const parent = node.parentNode;
|
|
230
185
|
let parentID;
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
186
|
+
// Disable parent check for the upper context HTMLHtmlElement, because it is root there... (before)
|
|
187
|
+
// TODO: get rid of "special" cases (there is an issue with CreateDocument altered behaviour though)
|
|
188
|
+
// TODO: Clean the logic (though now it workd fine)
|
|
189
|
+
if (!(0, context_js_1.isInstance)(node, HTMLHtmlElement) || !this.inUpperContext) {
|
|
235
190
|
if (parent === null) {
|
|
236
191
|
this.unbindNode(node);
|
|
237
192
|
return false;
|
|
@@ -245,23 +200,20 @@ class Observer {
|
|
|
245
200
|
this.unbindNode(node);
|
|
246
201
|
return false;
|
|
247
202
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
this.indexes[id] = this.indexes[siblingID] + 1;
|
|
258
|
-
break;
|
|
259
|
-
}
|
|
260
|
-
sibling = sibling.previousSibling;
|
|
261
|
-
}
|
|
262
|
-
if (sibling === null) {
|
|
263
|
-
this.indexes[id] = 0;
|
|
203
|
+
this.app.sanitizer.handleNode(id, parentID, node);
|
|
204
|
+
}
|
|
205
|
+
let sibling = node.previousSibling;
|
|
206
|
+
while (sibling !== null) {
|
|
207
|
+
const siblingID = this.app.nodes.getID(sibling);
|
|
208
|
+
if (siblingID !== undefined) {
|
|
209
|
+
this.commitNode(siblingID);
|
|
210
|
+
this.indexes[id] = this.indexes[siblingID] + 1;
|
|
211
|
+
break;
|
|
264
212
|
}
|
|
213
|
+
sibling = sibling.previousSibling;
|
|
214
|
+
}
|
|
215
|
+
if (sibling === null) {
|
|
216
|
+
this.indexes[id] = 0; //
|
|
265
217
|
}
|
|
266
218
|
const isNew = this.recents[id];
|
|
267
219
|
const index = this.indexes[id];
|
|
@@ -269,7 +221,7 @@ class Observer {
|
|
|
269
221
|
throw 'commitNode: missing node index';
|
|
270
222
|
}
|
|
271
223
|
if (isNew === true) {
|
|
272
|
-
if (
|
|
224
|
+
if ((0, context_js_1.isInstance)(node, Element)) {
|
|
273
225
|
if (parentID !== undefined) {
|
|
274
226
|
this.app.send(new index_js_1.CreateElementNode(id, parentID, index, node.tagName, isSVGElement(node)));
|
|
275
227
|
}
|
|
@@ -277,12 +229,8 @@ class Observer {
|
|
|
277
229
|
const attr = node.attributes[i];
|
|
278
230
|
this.sendNodeAttribute(id, node, attr.nodeName, attr.value);
|
|
279
231
|
}
|
|
280
|
-
if (this.isInstance(node, HTMLIFrameElement) &&
|
|
281
|
-
(this.options.captureIFrames || node.getAttribute("data-openreplay-capture"))) {
|
|
282
|
-
this.handleIframe(node);
|
|
283
|
-
}
|
|
284
232
|
}
|
|
285
|
-
else if (
|
|
233
|
+
else if ((0, context_js_1.isInstance)(node, Text)) {
|
|
286
234
|
// for text node id != 0, hence parentID !== undefined and parent is Element
|
|
287
235
|
this.app.send(new index_js_1.CreateTextNode(id, parentID, index));
|
|
288
236
|
this.sendNodeData(id, parent, node.data);
|
|
@@ -294,7 +242,7 @@ class Observer {
|
|
|
294
242
|
}
|
|
295
243
|
const attr = this.attributesList[id];
|
|
296
244
|
if (attr !== undefined) {
|
|
297
|
-
if (!
|
|
245
|
+
if (!(0, context_js_1.isInstance)(node, Element)) {
|
|
298
246
|
throw 'commitNode: node is not an element';
|
|
299
247
|
}
|
|
300
248
|
for (const name of attr) {
|
|
@@ -302,7 +250,7 @@ class Observer {
|
|
|
302
250
|
}
|
|
303
251
|
}
|
|
304
252
|
if (this.textSet.has(id)) {
|
|
305
|
-
if (!
|
|
253
|
+
if (!(0, context_js_1.isInstance)(node, Text)) {
|
|
306
254
|
throw 'commitNode: node is not a text';
|
|
307
255
|
}
|
|
308
256
|
// for text node id != 0, hence parent is Element
|
|
@@ -324,6 +272,11 @@ class Observer {
|
|
|
324
272
|
commitNodes() {
|
|
325
273
|
let node;
|
|
326
274
|
for (let id = 0; id < this.recents.length; id++) {
|
|
275
|
+
// TODO: make things/logic nice here.
|
|
276
|
+
// commit required in any case if recents[id] true or false (in case of unbinding) or undefined (in case of attr change).
|
|
277
|
+
if (!this.myNodes[id]) {
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
327
280
|
this.commitNode(id);
|
|
328
281
|
if (this.recents[id] === true && (node = this.app.nodes.getNode(id))) {
|
|
329
282
|
this.app.nodes.callNodeCallbacks(node);
|
|
@@ -331,49 +284,9 @@ class Observer {
|
|
|
331
284
|
}
|
|
332
285
|
this.clear();
|
|
333
286
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const id = this.app.nodes.getID(iframe);
|
|
338
|
-
if (id === undefined) {
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
if (iframe.contentWindow === context) {
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
context = iframe.contentWindow;
|
|
345
|
-
if (!context) {
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
const observer = new Observer(this.app, this.options, context);
|
|
349
|
-
this.iframeObservers.push(observer);
|
|
350
|
-
observer.observeIframe(id, context);
|
|
351
|
-
});
|
|
352
|
-
this.app.attachEventListener(iframe, "load", handle);
|
|
353
|
-
handle();
|
|
354
|
-
}
|
|
355
|
-
// TODO: abstract common functionality, separate FrameObserver
|
|
356
|
-
observeIframe(id, context) {
|
|
357
|
-
const doc = context.document;
|
|
358
|
-
this.observer.observe(doc, {
|
|
359
|
-
childList: true,
|
|
360
|
-
attributes: true,
|
|
361
|
-
characterData: true,
|
|
362
|
-
subtree: true,
|
|
363
|
-
attributeOldValue: false,
|
|
364
|
-
characterDataOldValue: false,
|
|
365
|
-
});
|
|
366
|
-
this.bindTree(doc.documentElement);
|
|
367
|
-
const docID = this.app.nodes.getID(doc.documentElement);
|
|
368
|
-
if (docID === undefined) {
|
|
369
|
-
console.log("Wrong");
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
|
-
this.app.send((0, index_js_1.CreateIFrameDocument)(id, docID));
|
|
373
|
-
this.commitNodes();
|
|
374
|
-
}
|
|
375
|
-
observe() {
|
|
376
|
-
this.observer.observe(this.context.document, {
|
|
287
|
+
// ISSSUE
|
|
288
|
+
observeRoot(node, beforeCommit, nodeToBind = node) {
|
|
289
|
+
this.observer.observe(node, {
|
|
377
290
|
childList: true,
|
|
378
291
|
attributes: true,
|
|
379
292
|
characterData: true,
|
|
@@ -381,15 +294,14 @@ class Observer {
|
|
|
381
294
|
attributeOldValue: false,
|
|
382
295
|
characterDataOldValue: false,
|
|
383
296
|
});
|
|
384
|
-
this.
|
|
385
|
-
|
|
297
|
+
this.bindTree(nodeToBind);
|
|
298
|
+
beforeCommit(this.app.nodes.getID(node));
|
|
386
299
|
this.commitNodes();
|
|
387
300
|
}
|
|
388
301
|
disconnect() {
|
|
389
|
-
this.iframeObservers.forEach(o => o.disconnect());
|
|
390
|
-
this.iframeObservers = [];
|
|
391
302
|
this.observer.disconnect();
|
|
392
303
|
this.clear();
|
|
304
|
+
this.myNodes.length = 0;
|
|
393
305
|
}
|
|
394
306
|
}
|
|
395
307
|
exports.default = Observer;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const observer_js_1 = require("./observer.js");
|
|
4
|
+
const index_js_1 = require("../../messages/index.js");
|
|
5
|
+
class ShadowRootObserver extends observer_js_1.default {
|
|
6
|
+
observe(el) {
|
|
7
|
+
const shRoot = el.shadowRoot;
|
|
8
|
+
const hostID = this.app.nodes.getID(el);
|
|
9
|
+
if (!shRoot || hostID === undefined) {
|
|
10
|
+
return;
|
|
11
|
+
} // log
|
|
12
|
+
this.observeRoot(shRoot, (rootID) => {
|
|
13
|
+
if (rootID === undefined) {
|
|
14
|
+
console.log("OpenReplay: Shadow Root was not bound");
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
this.app.send((0, index_js_1.CreateIFrameDocument)(hostID, rootID));
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
exports.default = ShadowRootObserver;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import Observer from "./observer.js";
|
|
2
|
+
import App from "../index.js";
|
|
3
|
+
export interface Options {
|
|
4
|
+
captureIFrames: boolean;
|
|
5
|
+
}
|
|
6
|
+
export default class TopObserver extends Observer {
|
|
7
|
+
private readonly options;
|
|
8
|
+
constructor(app: App, options: Partial<Options>);
|
|
9
|
+
private iframeObservers;
|
|
10
|
+
private handleIframe;
|
|
11
|
+
private shadowRootObservers;
|
|
12
|
+
private handleShadowRoot;
|
|
13
|
+
observe(): void;
|
|
14
|
+
disconnect(): void;
|
|
15
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const observer_js_1 = require("./observer.js");
|
|
4
|
+
const context_js_1 = require("../context.js");
|
|
5
|
+
const iframe_observer_js_1 = require("./iframe_observer.js");
|
|
6
|
+
const shadow_root_observer_js_1 = require("./shadow_root_observer.js");
|
|
7
|
+
const index_js_1 = require("../../messages/index.js");
|
|
8
|
+
const attachShadowNativeFn = Element.prototype.attachShadow;
|
|
9
|
+
class TopObserver extends observer_js_1.default {
|
|
10
|
+
constructor(app, options) {
|
|
11
|
+
super(app);
|
|
12
|
+
this.iframeObservers = [];
|
|
13
|
+
this.shadowRootObservers = [];
|
|
14
|
+
this.options = Object.assign({
|
|
15
|
+
captureIFrames: false
|
|
16
|
+
}, options);
|
|
17
|
+
// IFrames
|
|
18
|
+
this.app.nodes.attachNodeCallback(node => {
|
|
19
|
+
if ((0, context_js_1.isInstance)(node, HTMLIFrameElement) &&
|
|
20
|
+
(this.options.captureIFrames || node.getAttribute("data-openreplay-capture"))) {
|
|
21
|
+
this.handleIframe(node);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
// ShadowDOM
|
|
25
|
+
this.app.nodes.attachNodeCallback(node => {
|
|
26
|
+
if ((0, context_js_1.isInstance)(node, Element) && node.shadowRoot !== null) {
|
|
27
|
+
this.handleShadowRoot(node.shadowRoot);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
handleIframe(iframe) {
|
|
32
|
+
let context = null;
|
|
33
|
+
const handle = this.app.safe(() => {
|
|
34
|
+
const id = this.app.nodes.getID(iframe);
|
|
35
|
+
if (id === undefined) {
|
|
36
|
+
return;
|
|
37
|
+
} //log
|
|
38
|
+
if (iframe.contentWindow === context) {
|
|
39
|
+
return;
|
|
40
|
+
} //Does this happen frequently?
|
|
41
|
+
context = iframe.contentWindow;
|
|
42
|
+
if (!context) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const observer = new iframe_observer_js_1.default(this.app, context);
|
|
46
|
+
this.iframeObservers.push(observer);
|
|
47
|
+
observer.observe(iframe);
|
|
48
|
+
});
|
|
49
|
+
this.app.attachEventListener(iframe, "load", handle);
|
|
50
|
+
handle();
|
|
51
|
+
}
|
|
52
|
+
handleShadowRoot(shRoot) {
|
|
53
|
+
const observer = new shadow_root_observer_js_1.default(this.app, this.context);
|
|
54
|
+
this.shadowRootObservers.push(observer);
|
|
55
|
+
observer.observe(shRoot.host);
|
|
56
|
+
}
|
|
57
|
+
observe() {
|
|
58
|
+
// Protection from several subsequent calls?
|
|
59
|
+
const observer = this;
|
|
60
|
+
Element.prototype.attachShadow = function () {
|
|
61
|
+
const shadow = attachShadowNativeFn.apply(this, arguments);
|
|
62
|
+
observer.handleShadowRoot(shadow);
|
|
63
|
+
return shadow;
|
|
64
|
+
};
|
|
65
|
+
// Can observe documentElement (<html>) here, because it is not supposed to be changing.
|
|
66
|
+
// However, it is possible in some exotic cases and may cause an ignorance of the newly created <html>
|
|
67
|
+
// In this case context.document have to be observed, but this will cause
|
|
68
|
+
// the change in the re-player behaviour caused by CreateDocument message:
|
|
69
|
+
// the 0-node ("fRoot") will become #document rather than documentElement as it is now.
|
|
70
|
+
// Alternatively - observe(#document) then bindNode(documentElement)
|
|
71
|
+
this.observeRoot(this.context.document, () => {
|
|
72
|
+
this.app.send(new index_js_1.CreateDocument());
|
|
73
|
+
}, this.context.document.documentElement);
|
|
74
|
+
}
|
|
75
|
+
disconnect() {
|
|
76
|
+
Element.prototype.attachShadow = attachShadowNativeFn;
|
|
77
|
+
this.iframeObservers.forEach(o => o.disconnect());
|
|
78
|
+
this.iframeObservers = [];
|
|
79
|
+
this.shadowRootObservers.forEach(o => o.disconnect());
|
|
80
|
+
this.shadowRootObservers = [];
|
|
81
|
+
super.disconnect();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
exports.default = TopObserver;
|
package/cjs/app/sanitizer.d.ts
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import App from "./index.js";
|
|
2
|
+
export interface Options {
|
|
3
|
+
obscureTextEmails: boolean;
|
|
4
|
+
obscureTextNumbers: boolean;
|
|
5
|
+
}
|
|
6
|
+
export default class Sanitizer {
|
|
7
|
+
private readonly app;
|
|
8
|
+
private readonly masked;
|
|
9
|
+
private readonly options;
|
|
10
|
+
constructor(app: App, options: Partial<Options>);
|
|
11
|
+
handleNode(id: number, parentID: number, node: Node): void;
|
|
12
|
+
sanitize(id: number, data: string): string;
|
|
13
|
+
isMasked(id: number): boolean;
|
|
14
|
+
getInnerTextSecure(el: HTMLElement): string;
|
|
15
|
+
clear(): void;
|
|
16
|
+
}
|
package/cjs/app/sanitizer.js
CHANGED
|
@@ -1 +1,47 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const utils_js_1 = require("../utils.js");
|
|
4
|
+
const context_js_1 = require("./context.js");
|
|
5
|
+
class Sanitizer {
|
|
6
|
+
constructor(app, options) {
|
|
7
|
+
this.app = app;
|
|
8
|
+
this.masked = new Set();
|
|
9
|
+
this.options = Object.assign({
|
|
10
|
+
obscureTextEmails: true,
|
|
11
|
+
obscureTextNumbers: false,
|
|
12
|
+
}, options);
|
|
13
|
+
}
|
|
14
|
+
handleNode(id, parentID, node) {
|
|
15
|
+
if (this.masked.has(parentID) ||
|
|
16
|
+
((0, context_js_1.isInstance)(node, Element) && (0, utils_js_1.hasOpenreplayAttribute)(node, 'masked'))) {
|
|
17
|
+
this.masked.add(id);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
sanitize(id, data) {
|
|
21
|
+
if (this.masked.has(id)) {
|
|
22
|
+
// TODO: is it the best place to put trim() ? Might trimmed spaces be considered in layout in certain cases?
|
|
23
|
+
return data.trim().replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/g, '█');
|
|
24
|
+
}
|
|
25
|
+
if (this.options.obscureTextNumbers) {
|
|
26
|
+
data = data.replace(/\d/g, '0');
|
|
27
|
+
}
|
|
28
|
+
if (this.options.obscureTextEmails) {
|
|
29
|
+
data = data.replace(/([^\s]+)@([^\s]+)\.([^\s]+)/g, (...f) => (0, utils_js_1.stars)(f[1]) + '@' + (0, utils_js_1.stars)(f[2]) + '.' + (0, utils_js_1.stars)(f[3]));
|
|
30
|
+
}
|
|
31
|
+
return data;
|
|
32
|
+
}
|
|
33
|
+
isMasked(id) {
|
|
34
|
+
return this.masked.has(id);
|
|
35
|
+
}
|
|
36
|
+
getInnerTextSecure(el) {
|
|
37
|
+
const id = this.app.nodes.getID(el);
|
|
38
|
+
if (!id) {
|
|
39
|
+
return '';
|
|
40
|
+
}
|
|
41
|
+
return this.sanitize(id, el.innerText);
|
|
42
|
+
}
|
|
43
|
+
clear() {
|
|
44
|
+
this.masked.clear();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.default = Sanitizer;
|
package/cjs/index.d.ts
CHANGED
package/cjs/index.js
CHANGED
|
@@ -115,7 +115,7 @@ class API {
|
|
|
115
115
|
// no-cors issue only with text/plain or not-set Content-Type
|
|
116
116
|
// req.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
|
|
117
117
|
req.send(JSON.stringify({
|
|
118
|
-
trackerVersion: '3.4.
|
|
118
|
+
trackerVersion: '3.4.17-beta.0',
|
|
119
119
|
projectKey: options.projectKey,
|
|
120
120
|
doNotTrack,
|
|
121
121
|
// TODO: add precise reason (an exact API missing)
|
|
@@ -223,5 +223,11 @@ class API {
|
|
|
223
223
|
this.app.send(new index_js_3.CustomIssue(key, payload));
|
|
224
224
|
}
|
|
225
225
|
}
|
|
226
|
+
resetNextPageSession(flag) {
|
|
227
|
+
if (typeof flag !== 'boolean' || !this.app) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
this.app.resetNextPageSession(flag);
|
|
231
|
+
}
|
|
226
232
|
}
|
|
227
233
|
exports.default = API;
|
package/cjs/modules/exception.js
CHANGED
|
@@ -41,7 +41,14 @@ function getExceptionMessageFromEvent(e) {
|
|
|
41
41
|
return getExceptionMessage(e.reason, []);
|
|
42
42
|
}
|
|
43
43
|
else {
|
|
44
|
-
|
|
44
|
+
let message;
|
|
45
|
+
try {
|
|
46
|
+
message = JSON.stringify(e.reason);
|
|
47
|
+
}
|
|
48
|
+
catch (_) {
|
|
49
|
+
message = String(e.reason);
|
|
50
|
+
}
|
|
51
|
+
return new index_js_1.JSException('Unhandled Promise Rejection', message, '[]');
|
|
45
52
|
}
|
|
46
53
|
}
|
|
47
54
|
return null;
|
package/cjs/modules/img.js
CHANGED
|
@@ -2,7 +2,18 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const utils_js_1 = require("../utils.js");
|
|
4
4
|
const index_js_1 = require("../messages/index.js");
|
|
5
|
+
const PLACEHOLDER_SRC = "https://static.openreplay.com/tracker/placeholder.jpeg";
|
|
5
6
|
function default_1(app) {
|
|
7
|
+
function sendPlaceholder(id, node) {
|
|
8
|
+
app.send(new index_js_1.SetNodeAttribute(id, "src", PLACEHOLDER_SRC));
|
|
9
|
+
const { width, height } = node.getBoundingClientRect();
|
|
10
|
+
if (!node.hasAttribute("width")) {
|
|
11
|
+
app.send(new index_js_1.SetNodeAttribute(id, "width", String(width)));
|
|
12
|
+
}
|
|
13
|
+
if (!node.hasAttribute("height")) {
|
|
14
|
+
app.send(new index_js_1.SetNodeAttribute(id, "height", String(height)));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
6
17
|
const sendImgSrc = app.safe(function () {
|
|
7
18
|
const id = app.nodes.getID(this);
|
|
8
19
|
if (id === undefined) {
|
|
@@ -17,7 +28,10 @@ function default_1(app) {
|
|
|
17
28
|
app.send(new index_js_1.ResourceTiming((0, utils_js_1.timestamp)(), 0, 0, 0, 0, 0, src, 'img'));
|
|
18
29
|
}
|
|
19
30
|
}
|
|
20
|
-
else if (src.length
|
|
31
|
+
else if (src.length >= 1e5 || app.sanitizer.isMasked(id)) {
|
|
32
|
+
sendPlaceholder(id, this);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
21
35
|
app.send(new index_js_1.SetNodeAttributeURLBased(id, 'src', src, app.getBaseHref()));
|
|
22
36
|
}
|
|
23
37
|
});
|
package/cjs/modules/input.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import App from "../app/index.js";
|
|
2
|
-
|
|
2
|
+
declare type TextEditableElement = HTMLInputElement | HTMLTextAreaElement;
|
|
3
|
+
export declare function getInputLabel(node: TextEditableElement): string;
|
|
3
4
|
export declare const enum InputMode {
|
|
4
5
|
Plain = 0,
|
|
5
6
|
Obscured = 1,
|
|
@@ -11,3 +12,4 @@ export interface Options {
|
|
|
11
12
|
defaultInputMode: InputMode;
|
|
12
13
|
}
|
|
13
14
|
export default function (app: App, opts: Partial<Options>): void;
|
|
15
|
+
export {};
|