@openreplay/tracker 3.6.1 → 3.6.2
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/LICENSE +1 -1
- package/cjs/app/guards.d.ts +1 -2
- package/cjs/app/guards.js +3 -6
- package/cjs/app/index.d.ts +23 -28
- package/cjs/app/index.js +86 -107
- package/cjs/app/logger.js +3 -6
- package/cjs/app/nodes.d.ts +1 -1
- package/cjs/app/nodes.js +0 -2
- package/cjs/app/observer/iframe_observer.d.ts +1 -1
- package/cjs/app/observer/iframe_observer.js +3 -3
- package/cjs/app/observer/observer.d.ts +3 -2
- package/cjs/app/observer/observer.js +52 -50
- package/cjs/app/observer/shadow_root_observer.d.ts +1 -1
- package/cjs/app/observer/shadow_root_observer.js +3 -3
- package/cjs/app/observer/top_observer.d.ts +2 -13
- package/cjs/app/observer/top_observer.js +23 -58
- package/cjs/app/sanitizer.d.ts +1 -1
- package/cjs/app/sanitizer.js +5 -5
- package/cjs/app/session.d.ts +2 -20
- package/cjs/app/session.js +6 -65
- package/cjs/app/ticker.d.ts +1 -1
- package/cjs/common/messages.d.ts +444 -0
- package/cjs/common/messages.js +794 -0
- package/cjs/common/types.d.ts +9 -0
- package/cjs/common/{interaction.js → types.js} +0 -0
- package/cjs/common/{interaction.d.ts → webworker.d.ts} +5 -5
- package/cjs/common/{messages.gen.js → webworker.js} +0 -1
- package/cjs/index.d.ts +9 -10
- package/cjs/index.js +36 -47
- package/cjs/modules/connection.d.ts +1 -1
- package/cjs/modules/connection.js +2 -2
- package/cjs/modules/console.d.ts +1 -1
- package/cjs/modules/console.js +21 -7
- package/cjs/modules/cssrules.d.ts +1 -1
- package/cjs/modules/cssrules.js +14 -18
- package/cjs/modules/exception.d.ts +3 -3
- package/cjs/modules/exception.js +18 -23
- package/cjs/modules/img.d.ts +1 -1
- package/cjs/modules/img.js +26 -39
- package/cjs/modules/input.d.ts +1 -1
- package/cjs/modules/input.js +21 -21
- package/cjs/modules/longtasks.d.ts +2 -0
- package/cjs/modules/longtasks.js +26 -0
- package/cjs/modules/mouse.d.ts +1 -1
- package/cjs/modules/mouse.js +43 -50
- package/cjs/modules/performance.d.ts +1 -1
- package/cjs/modules/performance.js +2 -2
- package/cjs/modules/scroll.d.ts +1 -1
- package/cjs/modules/scroll.js +7 -16
- package/cjs/modules/timing.d.ts +1 -1
- package/cjs/modules/timing.js +26 -14
- package/cjs/modules/viewport.d.ts +1 -1
- package/cjs/modules/viewport.js +4 -4
- package/cjs/utils.js +7 -7
- package/cjs/vendors/finder/finder.js +48 -53
- package/lib/app/guards.d.ts +1 -2
- package/lib/app/guards.js +2 -4
- package/lib/app/index.d.ts +23 -28
- package/lib/app/index.js +94 -115
- package/lib/app/logger.js +3 -6
- package/lib/app/nodes.d.ts +1 -1
- package/lib/app/nodes.js +0 -2
- package/lib/app/observer/iframe_observer.d.ts +1 -1
- package/lib/app/observer/iframe_observer.js +3 -3
- package/lib/app/observer/observer.d.ts +3 -2
- package/lib/app/observer/observer.js +53 -51
- package/lib/app/observer/shadow_root_observer.d.ts +1 -1
- package/lib/app/observer/shadow_root_observer.js +3 -3
- package/lib/app/observer/top_observer.d.ts +2 -13
- package/lib/app/observer/top_observer.js +27 -62
- package/lib/app/sanitizer.d.ts +1 -1
- package/lib/app/sanitizer.js +7 -7
- package/lib/app/session.d.ts +2 -20
- package/lib/app/session.js +6 -65
- package/lib/app/ticker.d.ts +1 -1
- package/lib/common/messages.d.ts +444 -0
- package/lib/common/messages.js +790 -0
- package/lib/common/tsconfig.tsbuildinfo +1 -1
- package/lib/common/types.d.ts +9 -0
- package/lib/common/{interaction.js → types.js} +0 -0
- package/lib/common/{interaction.d.ts → webworker.d.ts} +5 -5
- package/lib/common/webworker.js +1 -0
- package/lib/index.d.ts +9 -10
- package/lib/index.js +49 -60
- package/lib/modules/connection.d.ts +1 -1
- package/lib/modules/connection.js +2 -2
- package/lib/modules/console.d.ts +1 -1
- package/lib/modules/console.js +22 -8
- package/lib/modules/cssrules.d.ts +1 -1
- package/lib/modules/cssrules.js +15 -19
- package/lib/modules/exception.d.ts +3 -3
- package/lib/modules/exception.js +18 -23
- package/lib/modules/img.d.ts +1 -1
- package/lib/modules/img.js +28 -41
- package/lib/modules/input.d.ts +1 -1
- package/lib/modules/input.js +23 -23
- package/lib/modules/longtasks.d.ts +2 -0
- package/lib/modules/longtasks.js +23 -0
- package/lib/modules/mouse.d.ts +1 -1
- package/lib/modules/mouse.js +46 -53
- package/lib/modules/performance.d.ts +1 -1
- package/lib/modules/performance.js +3 -3
- package/lib/modules/scroll.d.ts +1 -1
- package/lib/modules/scroll.js +8 -17
- package/lib/modules/timing.d.ts +1 -1
- package/lib/modules/timing.js +28 -16
- package/lib/modules/viewport.d.ts +1 -1
- package/lib/modules/viewport.js +4 -4
- package/lib/utils.js +7 -7
- package/lib/vendors/finder/finder.js +48 -53
- package/package.json +10 -27
- package/.eslintignore +0 -8
- package/.prettierignore +0 -1
- package/cjs/app/messages.d.ts +0 -52
- package/cjs/app/messages.gen.d.ts +0 -57
- package/cjs/app/messages.gen.js +0 -493
- package/cjs/app/messages.js +0 -234
- package/cjs/common/messages.gen.d.ts +0 -382
- package/cjs/modules/adoptedStyleSheets.d.ts +0 -2
- package/cjs/modules/adoptedStyleSheets.js +0 -127
- package/lib/app/messages.d.ts +0 -52
- package/lib/app/messages.gen.d.ts +0 -57
- package/lib/app/messages.gen.js +0 -434
- package/lib/app/messages.js +0 -181
- package/lib/common/messages.gen.d.ts +0 -382
- package/lib/common/messages.gen.js +0 -2
- package/lib/modules/adoptedStyleSheets.d.ts +0 -2
- package/lib/modules/adoptedStyleSheets.js +0 -124
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { RemoveNodeAttribute, SetNodeAttribute, SetNodeAttributeURLBased, SetCSSDataURLBased, SetNodeData, CreateTextNode, CreateElementNode, MoveNode, RemoveNode, } from
|
|
2
|
-
import { isRootNode, isTextNode, isElementNode, isSVGElement, hasTag } from
|
|
1
|
+
import { RemoveNodeAttribute, SetNodeAttribute, SetNodeAttributeURLBased, SetCSSDataURLBased, SetNodeData, CreateTextNode, CreateElementNode, MoveNode, RemoveNode, } from "../../common/messages.js";
|
|
2
|
+
import { isRootNode, isTextNode, isElementNode, isSVGElement, hasTag, } from "../guards.js";
|
|
3
3
|
function isIgnored(node) {
|
|
4
4
|
if (isTextNode(node)) {
|
|
5
5
|
return false;
|
|
@@ -11,9 +11,13 @@ function isIgnored(node) {
|
|
|
11
11
|
if (tag === 'LINK') {
|
|
12
12
|
const rel = node.getAttribute('rel');
|
|
13
13
|
const as = node.getAttribute('as');
|
|
14
|
-
return !((rel === null || rel === void 0 ? void 0 : rel.includes('stylesheet')) || as ===
|
|
14
|
+
return !((rel === null || rel === void 0 ? void 0 : rel.includes('stylesheet')) || as === "style" || as === "font");
|
|
15
15
|
}
|
|
16
|
-
return (tag === 'SCRIPT' ||
|
|
16
|
+
return (tag === 'SCRIPT' ||
|
|
17
|
+
tag === 'NOSCRIPT' ||
|
|
18
|
+
tag === 'META' ||
|
|
19
|
+
tag === 'TITLE' ||
|
|
20
|
+
tag === 'BASE');
|
|
17
21
|
}
|
|
18
22
|
function isObservable(node) {
|
|
19
23
|
if (isRootNode(node)) {
|
|
@@ -26,11 +30,17 @@ function isObservable(node) {
|
|
|
26
30
|
- fix unbinding logic + send all removals first (ensure sequence is correct)
|
|
27
31
|
- use document as a 0-node in the upper context (should be updated in player at first)
|
|
28
32
|
*/
|
|
33
|
+
/*
|
|
34
|
+
Nikita:
|
|
35
|
+
- rn we only send unbind event for parent (all child nodes will be cut in the live replay anyways)
|
|
36
|
+
to prevent sending 1k+ unbinds for child nodes and making replay file bigger than it should be
|
|
37
|
+
*/
|
|
29
38
|
var RecentsType;
|
|
30
39
|
(function (RecentsType) {
|
|
31
40
|
RecentsType[RecentsType["New"] = 0] = "New";
|
|
32
41
|
RecentsType[RecentsType["Removed"] = 1] = "Removed";
|
|
33
42
|
RecentsType[RecentsType["Changed"] = 2] = "Changed";
|
|
43
|
+
RecentsType[RecentsType["RemovedChild"] = 3] = "RemovedChild";
|
|
34
44
|
})(RecentsType || (RecentsType = {}));
|
|
35
45
|
export default class Observer {
|
|
36
46
|
constructor(app, isTopContext = false) {
|
|
@@ -42,8 +52,7 @@ export default class Observer {
|
|
|
42
52
|
this.attributesMap = new Map();
|
|
43
53
|
this.textSet = new Set();
|
|
44
54
|
this.observer = new MutationObserver(this.app.safe((mutations) => {
|
|
45
|
-
for (const mutation of mutations) {
|
|
46
|
-
// mutations order is sequential
|
|
55
|
+
for (const mutation of mutations) { // mutations order is sequential
|
|
47
56
|
const target = mutation.target;
|
|
48
57
|
const type = mutation.type;
|
|
49
58
|
if (!isObservable(target)) {
|
|
@@ -51,10 +60,7 @@ export default class Observer {
|
|
|
51
60
|
}
|
|
52
61
|
if (type === 'childList') {
|
|
53
62
|
for (let i = 0; i < mutation.removedNodes.length; i++) {
|
|
54
|
-
|
|
55
|
-
if (isObservable(mutation.removedNodes[i])) {
|
|
56
|
-
this.bindNode(mutation.removedNodes[i]);
|
|
57
|
-
}
|
|
63
|
+
this.bindTree(mutation.removedNodes[i], true);
|
|
58
64
|
}
|
|
59
65
|
for (let i = 0; i < mutation.addedNodes.length; i++) {
|
|
60
66
|
this.bindTree(mutation.addedNodes[i]);
|
|
@@ -75,7 +81,7 @@ export default class Observer {
|
|
|
75
81
|
}
|
|
76
82
|
let attr = this.attributesMap.get(id);
|
|
77
83
|
if (attr === undefined) {
|
|
78
|
-
this.attributesMap.set(id,
|
|
84
|
+
this.attributesMap.set(id, attr = new Set());
|
|
79
85
|
}
|
|
80
86
|
attr.add(name);
|
|
81
87
|
continue;
|
|
@@ -101,16 +107,16 @@ export default class Observer {
|
|
|
101
107
|
name = name.substr(6);
|
|
102
108
|
}
|
|
103
109
|
if (value === null) {
|
|
104
|
-
this.app.send(RemoveNodeAttribute(id, name));
|
|
110
|
+
this.app.send(new RemoveNodeAttribute(id, name));
|
|
105
111
|
}
|
|
106
112
|
else if (name === 'href') {
|
|
107
113
|
if (value.length > 1e5) {
|
|
108
114
|
value = '';
|
|
109
115
|
}
|
|
110
|
-
this.app.send(SetNodeAttributeURLBased(id, name, value, this.app.getBaseHref()));
|
|
116
|
+
this.app.send(new SetNodeAttributeURLBased(id, name, value, this.app.getBaseHref()));
|
|
111
117
|
}
|
|
112
118
|
else {
|
|
113
|
-
this.app.send(SetNodeAttribute(id, name, value));
|
|
119
|
+
this.app.send(new SetNodeAttribute(id, name, value));
|
|
114
120
|
}
|
|
115
121
|
return;
|
|
116
122
|
}
|
|
@@ -123,75 +129,72 @@ export default class Observer {
|
|
|
123
129
|
return;
|
|
124
130
|
}
|
|
125
131
|
if (name === 'value' &&
|
|
126
|
-
hasTag(node,
|
|
132
|
+
hasTag(node, "INPUT") &&
|
|
127
133
|
node.type !== 'button' &&
|
|
128
134
|
node.type !== 'reset' &&
|
|
129
135
|
node.type !== 'submit') {
|
|
130
136
|
return;
|
|
131
137
|
}
|
|
132
138
|
if (value === null) {
|
|
133
|
-
this.app.send(RemoveNodeAttribute(id, name));
|
|
139
|
+
this.app.send(new RemoveNodeAttribute(id, name));
|
|
134
140
|
return;
|
|
135
141
|
}
|
|
136
|
-
if (name === 'style' ||
|
|
137
|
-
this.app.send(SetNodeAttributeURLBased(id, name, value, this.app.getBaseHref()));
|
|
142
|
+
if (name === 'style' || name === 'href' && hasTag(node, "LINK")) {
|
|
143
|
+
this.app.send(new SetNodeAttributeURLBased(id, name, value, this.app.getBaseHref()));
|
|
138
144
|
return;
|
|
139
145
|
}
|
|
140
146
|
if (name === 'href' || value.length > 1e5) {
|
|
141
147
|
value = '';
|
|
142
148
|
}
|
|
143
|
-
this.app.send(SetNodeAttribute(id, name, value));
|
|
149
|
+
this.app.send(new SetNodeAttribute(id, name, value));
|
|
144
150
|
}
|
|
145
151
|
sendNodeData(id, parentElement, data) {
|
|
146
|
-
if (hasTag(parentElement,
|
|
147
|
-
this.app.send(SetCSSDataURLBased(id, data, this.app.getBaseHref()));
|
|
152
|
+
if (hasTag(parentElement, "STYLE") || hasTag(parentElement, "style")) {
|
|
153
|
+
this.app.send(new SetCSSDataURLBased(id, data, this.app.getBaseHref()));
|
|
148
154
|
return;
|
|
149
155
|
}
|
|
150
156
|
data = this.app.sanitizer.sanitize(id, data);
|
|
151
|
-
this.app.send(SetNodeData(id, data));
|
|
157
|
+
this.app.send(new SetNodeData(id, data));
|
|
152
158
|
}
|
|
153
159
|
bindNode(node) {
|
|
154
160
|
const [id, isNew] = this.app.nodes.registerNode(node);
|
|
155
161
|
if (isNew) {
|
|
156
162
|
this.recents.set(id, RecentsType.New);
|
|
157
163
|
}
|
|
158
|
-
else if (this.recents.get(id) !== RecentsType.New) {
|
|
164
|
+
else if (this.recents.get(id) !== RecentsType.New) { // can we do just `else` here?
|
|
159
165
|
this.recents.set(id, RecentsType.Removed);
|
|
160
166
|
}
|
|
161
167
|
}
|
|
162
|
-
|
|
168
|
+
unbindChildNode(node) {
|
|
169
|
+
const [id] = this.app.nodes.registerNode(node);
|
|
170
|
+
this.recents.set(id, RecentsType.RemovedChild);
|
|
171
|
+
}
|
|
172
|
+
bindTree(node, isChildUnbinding = false) {
|
|
163
173
|
if (!isObservable(node)) {
|
|
164
174
|
return;
|
|
165
175
|
}
|
|
166
176
|
this.bindNode(node);
|
|
167
177
|
const walker = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_TEXT, {
|
|
168
|
-
acceptNode: (node) => isIgnored(node)
|
|
178
|
+
acceptNode: (node) => isIgnored(node)
|
|
179
|
+
|| (this.app.nodes.getID(node) !== undefined && !isChildUnbinding)
|
|
169
180
|
? NodeFilter.FILTER_REJECT
|
|
170
181
|
: NodeFilter.FILTER_ACCEPT,
|
|
171
182
|
},
|
|
172
183
|
// @ts-ignore
|
|
173
184
|
false);
|
|
174
185
|
while (walker.nextNode()) {
|
|
175
|
-
|
|
186
|
+
if (isChildUnbinding) {
|
|
187
|
+
this.unbindChildNode(walker.currentNode);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
this.bindNode(walker.currentNode);
|
|
191
|
+
}
|
|
176
192
|
}
|
|
177
193
|
}
|
|
178
|
-
|
|
194
|
+
unbindNode(node) {
|
|
179
195
|
const id = this.app.nodes.unregisterNode(node);
|
|
180
196
|
if (id !== undefined && this.recents.get(id) === RecentsType.Removed) {
|
|
181
|
-
|
|
182
|
-
this.app.send(RemoveNode(id));
|
|
183
|
-
// Unregistering all the children in order to clear the memory
|
|
184
|
-
const walker = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_TEXT, {
|
|
185
|
-
acceptNode: (node) => isIgnored(node) || this.app.nodes.getID(node) === undefined
|
|
186
|
-
? NodeFilter.FILTER_REJECT
|
|
187
|
-
: NodeFilter.FILTER_ACCEPT,
|
|
188
|
-
},
|
|
189
|
-
// @ts-ignore
|
|
190
|
-
false);
|
|
191
|
-
while (walker.nextNode()) {
|
|
192
|
-
this.app.nodes.unregisterNode(walker.currentNode);
|
|
193
|
-
}
|
|
194
|
-
// MBTODO: count and send RemovedNodesCount (for the page crash detection in heuristics)
|
|
197
|
+
this.app.send(new RemoveNode(id));
|
|
195
198
|
}
|
|
196
199
|
}
|
|
197
200
|
// A top-consumption function on the infinite lists test. (~1% of performance resources)
|
|
@@ -204,20 +207,20 @@ export default class Observer {
|
|
|
204
207
|
// Disable parent check for the upper context HTMLHtmlElement, because it is root there... (before)
|
|
205
208
|
// TODO: get rid of "special" cases (there is an issue with CreateDocument altered behaviour though)
|
|
206
209
|
// TODO: Clean the logic (though now it workd fine)
|
|
207
|
-
if (!hasTag(node,
|
|
210
|
+
if (!hasTag(node, "HTML") || !this.isTopContext) {
|
|
208
211
|
if (parent === null) {
|
|
209
212
|
// Sometimes one observation contains attribute mutations for the removimg node, which gets ignored here.
|
|
210
|
-
// That shouldn't affect the visual rendering ( should it?
|
|
211
|
-
this.
|
|
213
|
+
// That shouldn't affect the visual rendering ( should it? )
|
|
214
|
+
this.unbindNode(node);
|
|
212
215
|
return false;
|
|
213
216
|
}
|
|
214
217
|
parentID = this.app.nodes.getID(parent);
|
|
215
218
|
if (parentID === undefined) {
|
|
216
|
-
this.
|
|
219
|
+
this.unbindNode(node);
|
|
217
220
|
return false;
|
|
218
221
|
}
|
|
219
222
|
if (!this.commitNode(parentID)) {
|
|
220
|
-
this.
|
|
223
|
+
this.unbindNode(node);
|
|
221
224
|
return false;
|
|
222
225
|
}
|
|
223
226
|
this.app.sanitizer.handleNode(id, parentID, node);
|
|
@@ -256,7 +259,7 @@ export default class Observer {
|
|
|
256
259
|
el.style.width = width + 'px';
|
|
257
260
|
el.style.height = height + 'px';
|
|
258
261
|
}
|
|
259
|
-
this.app.send(CreateElementNode(id, parentID, index, el.tagName, isSVGElement(node)));
|
|
262
|
+
this.app.send(new CreateElementNode(id, parentID, index, el.tagName, isSVGElement(node)));
|
|
260
263
|
}
|
|
261
264
|
for (let i = 0; i < el.attributes.length; i++) {
|
|
262
265
|
const attr = el.attributes[i];
|
|
@@ -265,13 +268,13 @@ export default class Observer {
|
|
|
265
268
|
}
|
|
266
269
|
else if (isTextNode(node)) {
|
|
267
270
|
// for text node id != 0, hence parentID !== undefined and parent is Element
|
|
268
|
-
this.app.send(CreateTextNode(id, parentID, index));
|
|
271
|
+
this.app.send(new CreateTextNode(id, parentID, index));
|
|
269
272
|
this.sendNodeData(id, parent, node.data);
|
|
270
273
|
}
|
|
271
274
|
return true;
|
|
272
275
|
}
|
|
273
276
|
if (recentsType === RecentsType.Removed && parentID !== undefined) {
|
|
274
|
-
this.app.send(MoveNode(id, parentID, index));
|
|
277
|
+
this.app.send(new MoveNode(id, parentID, index));
|
|
275
278
|
}
|
|
276
279
|
const attr = this.attributesMap.get(id);
|
|
277
280
|
if (attr !== undefined) {
|
|
@@ -312,8 +315,7 @@ export default class Observer {
|
|
|
312
315
|
});
|
|
313
316
|
this.clear();
|
|
314
317
|
}
|
|
315
|
-
// ISSSUE
|
|
316
|
-
// TODO: use one observer instance for all iframes/shadowRoots (composition instiad of inheritance)
|
|
318
|
+
// ISSSUE
|
|
317
319
|
observeRoot(node, beforeCommit, nodeToBind = node) {
|
|
318
320
|
this.observer.observe(node, {
|
|
319
321
|
childList: true,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import Observer from
|
|
2
|
-
import { CreateIFrameDocument } from
|
|
1
|
+
import Observer from "./observer.js";
|
|
2
|
+
import { CreateIFrameDocument } from "../../common/messages.js";
|
|
3
3
|
export default class ShadowRootObserver extends Observer {
|
|
4
4
|
observe(el) {
|
|
5
5
|
const shRoot = el.shadowRoot;
|
|
@@ -9,7 +9,7 @@ export default class ShadowRootObserver extends Observer {
|
|
|
9
9
|
} // log
|
|
10
10
|
this.observeRoot(shRoot, (rootID) => {
|
|
11
11
|
if (rootID === undefined) {
|
|
12
|
-
console.log(
|
|
12
|
+
console.log("OpenReplay: Shadow Root was not bound");
|
|
13
13
|
return;
|
|
14
14
|
}
|
|
15
15
|
this.app.send(CreateIFrameDocument(hostID, rootID));
|
|
@@ -1,21 +1,11 @@
|
|
|
1
|
-
import Observer from
|
|
2
|
-
import App from
|
|
1
|
+
import Observer from "./observer.js";
|
|
2
|
+
import App from "../index.js";
|
|
3
3
|
export interface Options {
|
|
4
4
|
captureIFrames: boolean;
|
|
5
5
|
}
|
|
6
|
-
declare type Context = Window & typeof globalThis;
|
|
7
|
-
declare type ContextCallback = (context: Context) => void;
|
|
8
|
-
declare type Offset = {
|
|
9
|
-
top: number;
|
|
10
|
-
left: number;
|
|
11
|
-
};
|
|
12
6
|
export default class TopObserver extends Observer {
|
|
13
7
|
private readonly options;
|
|
14
8
|
constructor(app: App, options: Partial<Options>);
|
|
15
|
-
private readonly contextCallbacks;
|
|
16
|
-
private readonly contextsSet;
|
|
17
|
-
attachContextCallback(cb: ContextCallback): void;
|
|
18
|
-
getDocumentOffset(doc: Document): Offset;
|
|
19
9
|
private iframeObservers;
|
|
20
10
|
private handleIframe;
|
|
21
11
|
private shadowRootObservers;
|
|
@@ -23,4 +13,3 @@ export default class TopObserver extends Observer {
|
|
|
23
13
|
observe(): void;
|
|
24
14
|
disconnect(): void;
|
|
25
15
|
}
|
|
26
|
-
export {};
|
|
@@ -1,86 +1,52 @@
|
|
|
1
|
-
import Observer from
|
|
2
|
-
import { isElementNode, hasTag } from
|
|
3
|
-
import IFrameObserver from
|
|
4
|
-
import ShadowRootObserver from
|
|
5
|
-
import { CreateDocument } from
|
|
1
|
+
import Observer from "./observer.js";
|
|
2
|
+
import { isElementNode, hasTag, } from "../guards.js";
|
|
3
|
+
import IFrameObserver from "./iframe_observer.js";
|
|
4
|
+
import ShadowRootObserver from "./shadow_root_observer.js";
|
|
5
|
+
import { CreateDocument } from "../../common/messages.js";
|
|
6
6
|
import { IN_BROWSER, hasOpenreplayAttribute } from '../../utils.js';
|
|
7
|
-
function isPatchedDocument(doc) {
|
|
8
|
-
// @ts-ignore
|
|
9
|
-
return typeof doc.__openreplay__getOffset === 'function';
|
|
10
|
-
}
|
|
11
7
|
const attachShadowNativeFn = IN_BROWSER ? Element.prototype.attachShadow : () => new ShadowRoot();
|
|
12
8
|
export default class TopObserver extends Observer {
|
|
13
9
|
constructor(app, options) {
|
|
14
10
|
super(app, true);
|
|
15
|
-
this.contextCallbacks = [];
|
|
16
|
-
// Attached once per Tracker instance
|
|
17
|
-
this.contextsSet = new Set();
|
|
18
11
|
this.iframeObservers = [];
|
|
19
12
|
this.shadowRootObservers = [];
|
|
20
13
|
this.options = Object.assign({
|
|
21
|
-
captureIFrames: true
|
|
14
|
+
captureIFrames: true
|
|
22
15
|
}, options);
|
|
23
16
|
// IFrames
|
|
24
|
-
this.app.nodes.attachNodeCallback(
|
|
25
|
-
if (hasTag(node,
|
|
26
|
-
((this.options.captureIFrames && !hasOpenreplayAttribute(node,
|
|
27
|
-
hasOpenreplayAttribute(node,
|
|
17
|
+
this.app.nodes.attachNodeCallback(node => {
|
|
18
|
+
if (hasTag(node, "IFRAME") &&
|
|
19
|
+
((this.options.captureIFrames && !hasOpenreplayAttribute(node, "obscured"))
|
|
20
|
+
|| hasOpenreplayAttribute(node, "capture"))) {
|
|
28
21
|
this.handleIframe(node);
|
|
29
22
|
}
|
|
30
23
|
});
|
|
31
24
|
// ShadowDOM
|
|
32
|
-
this.app.nodes.attachNodeCallback(
|
|
25
|
+
this.app.nodes.attachNodeCallback(node => {
|
|
33
26
|
if (isElementNode(node) && node.shadowRoot !== null) {
|
|
34
27
|
this.handleShadowRoot(node.shadowRoot);
|
|
35
28
|
}
|
|
36
29
|
});
|
|
37
30
|
}
|
|
38
|
-
attachContextCallback(cb) {
|
|
39
|
-
this.contextCallbacks.push(cb);
|
|
40
|
-
}
|
|
41
|
-
// Le truc
|
|
42
|
-
getDocumentOffset(doc) {
|
|
43
|
-
if (isPatchedDocument(doc)) {
|
|
44
|
-
return doc.__openreplay__getOffset();
|
|
45
|
-
}
|
|
46
|
-
return { top: 0, left: 0 };
|
|
47
|
-
}
|
|
48
31
|
handleIframe(iframe) {
|
|
49
32
|
let doc = null;
|
|
50
|
-
let win = null;
|
|
51
33
|
const handle = this.app.safe(() => {
|
|
52
34
|
const id = this.app.nodes.getID(iframe);
|
|
53
35
|
if (id === undefined) {
|
|
54
|
-
|
|
36
|
+
return;
|
|
37
|
+
} //log
|
|
38
|
+
if (iframe.contentDocument === doc) {
|
|
39
|
+
return;
|
|
40
|
+
} // How frequently can it happen?
|
|
41
|
+
doc = iframe.contentDocument;
|
|
42
|
+
if (!doc || !iframe.contentWindow) {
|
|
55
43
|
return;
|
|
56
44
|
}
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const observer = new IFrameObserver(this.app);
|
|
61
|
-
this.iframeObservers.push(observer);
|
|
62
|
-
observer.observe(iframe);
|
|
63
|
-
doc = currentDoc;
|
|
64
|
-
doc.__openreplay__getOffset = () => {
|
|
65
|
-
const { top, left } = this.getDocumentOffset(iframe.ownerDocument);
|
|
66
|
-
return {
|
|
67
|
-
top: iframe.offsetTop + top,
|
|
68
|
-
left: iframe.offsetLeft + left,
|
|
69
|
-
};
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
if (currentWin &&
|
|
73
|
-
// Sometimes currentWin.window is null (not in specification). Such window object is not functional
|
|
74
|
-
currentWin === currentWin.window &&
|
|
75
|
-
!this.contextsSet.has(currentWin) // for each context callbacks called once per Tracker (TopObserver) instance
|
|
76
|
-
) {
|
|
77
|
-
this.contextsSet.add(currentWin);
|
|
78
|
-
//@ts-ignore https://github.com/microsoft/TypeScript/issues/41684
|
|
79
|
-
this.contextCallbacks.forEach((cb) => cb(currentWin));
|
|
80
|
-
win = currentWin;
|
|
81
|
-
}
|
|
45
|
+
const observer = new IFrameObserver(this.app);
|
|
46
|
+
this.iframeObservers.push(observer);
|
|
47
|
+
observer.observe(iframe);
|
|
82
48
|
});
|
|
83
|
-
iframe.addEventListener(
|
|
49
|
+
iframe.addEventListener("load", handle); // why app.attachEventListener not working?
|
|
84
50
|
handle();
|
|
85
51
|
}
|
|
86
52
|
handleShadowRoot(shRoot) {
|
|
@@ -92,26 +58,25 @@ export default class TopObserver extends Observer {
|
|
|
92
58
|
// Protection from several subsequent calls?
|
|
93
59
|
const observer = this;
|
|
94
60
|
Element.prototype.attachShadow = function () {
|
|
95
|
-
// eslint-disable-next-line
|
|
96
61
|
const shadow = attachShadowNativeFn.apply(this, arguments);
|
|
97
62
|
observer.handleShadowRoot(shadow);
|
|
98
63
|
return shadow;
|
|
99
64
|
};
|
|
100
65
|
// Can observe documentElement (<html>) here, because it is not supposed to be changing.
|
|
101
66
|
// However, it is possible in some exotic cases and may cause an ignorance of the newly created <html>
|
|
102
|
-
// In this case context.document have to be observed, but this will cause
|
|
103
|
-
// the change in the re-player behaviour caused by CreateDocument message:
|
|
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:
|
|
104
69
|
// the 0-node ("fRoot") will become #document rather than documentElement as it is now.
|
|
105
70
|
// Alternatively - observe(#document) then bindNode(documentElement)
|
|
106
71
|
this.observeRoot(window.document, () => {
|
|
107
|
-
this.app.send(CreateDocument());
|
|
72
|
+
this.app.send(new CreateDocument());
|
|
108
73
|
}, window.document.documentElement);
|
|
109
74
|
}
|
|
110
75
|
disconnect() {
|
|
111
76
|
Element.prototype.attachShadow = attachShadowNativeFn;
|
|
112
|
-
this.iframeObservers.forEach(
|
|
77
|
+
this.iframeObservers.forEach(o => o.disconnect());
|
|
113
78
|
this.iframeObservers = [];
|
|
114
|
-
this.shadowRootObservers.forEach(
|
|
79
|
+
this.shadowRootObservers.forEach(o => o.disconnect());
|
|
115
80
|
this.shadowRootObservers = [];
|
|
116
81
|
super.disconnect();
|
|
117
82
|
}
|
package/lib/app/sanitizer.d.ts
CHANGED
package/lib/app/sanitizer.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { stars, hasOpenreplayAttribute } from
|
|
2
|
-
import { isElementNode } from
|
|
1
|
+
import { stars, hasOpenreplayAttribute } from "../utils.js";
|
|
2
|
+
import { isElementNode } from "./guards.js";
|
|
3
3
|
export default class Sanitizer {
|
|
4
4
|
constructor(app, options) {
|
|
5
5
|
this.app = app;
|
|
@@ -12,20 +12,20 @@ export default class Sanitizer {
|
|
|
12
12
|
}
|
|
13
13
|
handleNode(id, parentID, node) {
|
|
14
14
|
if (this.masked.has(parentID) ||
|
|
15
|
-
(isElementNode(node) &&
|
|
15
|
+
(isElementNode(node) &&
|
|
16
|
+
hasOpenreplayAttribute(node, 'masked'))) {
|
|
16
17
|
this.masked.add(id);
|
|
17
18
|
}
|
|
18
19
|
if (this.maskedContainers.has(parentID) ||
|
|
19
|
-
(isElementNode(node) &&
|
|
20
|
+
(isElementNode(node) &&
|
|
21
|
+
hasOpenreplayAttribute(node, 'htmlmasked'))) {
|
|
20
22
|
this.maskedContainers.add(id);
|
|
21
23
|
}
|
|
22
24
|
}
|
|
23
25
|
sanitize(id, data) {
|
|
24
26
|
if (this.masked.has(id)) {
|
|
25
27
|
// TODO: is it the best place to put trim() ? Might trimmed spaces be considered in layout in certain cases?
|
|
26
|
-
return data
|
|
27
|
-
.trim()
|
|
28
|
-
.replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/g, '█');
|
|
28
|
+
return data.trim().replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/g, '█');
|
|
29
29
|
}
|
|
30
30
|
if (this.options.obscureTextNumbers) {
|
|
31
31
|
data = data.replace(/\d/g, '0');
|
package/lib/app/session.d.ts
CHANGED
|
@@ -1,37 +1,19 @@
|
|
|
1
|
-
import type App from './index.js';
|
|
2
1
|
interface SessionInfo {
|
|
3
|
-
sessionID: string |
|
|
2
|
+
sessionID: string | null;
|
|
4
3
|
metadata: Record<string, string>;
|
|
5
4
|
userID: string | null;
|
|
6
|
-
timestamp: number;
|
|
7
|
-
projectID?: string;
|
|
8
5
|
}
|
|
9
6
|
declare type OnUpdateCallback = (i: Partial<SessionInfo>) => void;
|
|
10
|
-
export declare type Options = {
|
|
11
|
-
session_token_key: string;
|
|
12
|
-
session_pageno_key: string;
|
|
13
|
-
};
|
|
14
7
|
export default class Session {
|
|
15
|
-
private readonly app;
|
|
16
|
-
private readonly options;
|
|
17
8
|
private metadata;
|
|
18
9
|
private userID;
|
|
19
10
|
private sessionID;
|
|
20
|
-
private
|
|
21
|
-
private timestamp;
|
|
22
|
-
private projectID;
|
|
23
|
-
constructor(app: App, options: Options);
|
|
11
|
+
private callbacks;
|
|
24
12
|
attachUpdateCallback(cb: OnUpdateCallback): void;
|
|
25
13
|
private handleUpdate;
|
|
26
14
|
update(newInfo: Partial<SessionInfo>): void;
|
|
27
15
|
setMetadata(key: string, value: string): void;
|
|
28
16
|
setUserID(userID: string): void;
|
|
29
|
-
private getPageNumber;
|
|
30
|
-
incPageNo(): number;
|
|
31
|
-
getSessionToken(): string | undefined;
|
|
32
|
-
setSessionToken(token: string): void;
|
|
33
|
-
applySessionHash(hash: string): void;
|
|
34
|
-
getSessionHash(): string | undefined;
|
|
35
17
|
getInfo(): SessionInfo;
|
|
36
18
|
reset(): void;
|
|
37
19
|
}
|
package/lib/app/session.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
export default class Session {
|
|
2
|
-
constructor(
|
|
3
|
-
this.app = app;
|
|
4
|
-
this.options = options;
|
|
2
|
+
constructor() {
|
|
5
3
|
this.metadata = {};
|
|
6
4
|
this.userID = null;
|
|
5
|
+
this.sessionID = null;
|
|
7
6
|
this.callbacks = [];
|
|
8
|
-
this.timestamp = 0;
|
|
9
7
|
}
|
|
10
8
|
attachUpdateCallback(cb) {
|
|
11
9
|
this.callbacks.push(cb);
|
|
@@ -17,25 +15,18 @@ export default class Session {
|
|
|
17
15
|
if (newInfo.sessionID == null) {
|
|
18
16
|
delete newInfo.sessionID;
|
|
19
17
|
}
|
|
20
|
-
this.callbacks.forEach(
|
|
18
|
+
this.callbacks.forEach(cb => cb(newInfo));
|
|
21
19
|
}
|
|
22
20
|
update(newInfo) {
|
|
23
|
-
if (newInfo.userID !== undefined) {
|
|
24
|
-
// TODO clear nullable/undefinable types
|
|
21
|
+
if (newInfo.userID !== undefined) { // TODO clear nullable/undefinable types
|
|
25
22
|
this.userID = newInfo.userID;
|
|
26
23
|
}
|
|
27
24
|
if (newInfo.metadata !== undefined) {
|
|
28
|
-
Object.entries(newInfo.metadata).forEach(([k, v]) =>
|
|
25
|
+
Object.entries(newInfo.metadata).forEach(([k, v]) => this.metadata[k] = v);
|
|
29
26
|
}
|
|
30
27
|
if (newInfo.sessionID !== undefined) {
|
|
31
28
|
this.sessionID = newInfo.sessionID;
|
|
32
29
|
}
|
|
33
|
-
if (newInfo.timestamp !== undefined) {
|
|
34
|
-
this.timestamp = newInfo.timestamp;
|
|
35
|
-
}
|
|
36
|
-
if (newInfo.projectID !== undefined) {
|
|
37
|
-
this.projectID = newInfo.projectID;
|
|
38
|
-
}
|
|
39
30
|
this.handleUpdate(newInfo);
|
|
40
31
|
}
|
|
41
32
|
setMetadata(key, value) {
|
|
@@ -46,66 +37,16 @@ export default class Session {
|
|
|
46
37
|
this.userID = userID;
|
|
47
38
|
this.handleUpdate({ userID });
|
|
48
39
|
}
|
|
49
|
-
getPageNumber() {
|
|
50
|
-
const pageNoStr = this.app.sessionStorage.getItem(this.options.session_pageno_key);
|
|
51
|
-
if (pageNoStr == null) {
|
|
52
|
-
return undefined;
|
|
53
|
-
}
|
|
54
|
-
return parseInt(pageNoStr);
|
|
55
|
-
}
|
|
56
|
-
incPageNo() {
|
|
57
|
-
let pageNo = this.getPageNumber();
|
|
58
|
-
if (pageNo === undefined) {
|
|
59
|
-
pageNo = 0;
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
pageNo++;
|
|
63
|
-
}
|
|
64
|
-
this.app.sessionStorage.setItem(this.options.session_pageno_key, pageNo.toString());
|
|
65
|
-
return pageNo;
|
|
66
|
-
}
|
|
67
|
-
getSessionToken() {
|
|
68
|
-
return this.app.sessionStorage.getItem(this.options.session_token_key) || undefined;
|
|
69
|
-
}
|
|
70
|
-
setSessionToken(token) {
|
|
71
|
-
this.app.sessionStorage.setItem(this.options.session_token_key, token);
|
|
72
|
-
}
|
|
73
|
-
applySessionHash(hash) {
|
|
74
|
-
const hashParts = decodeURI(hash).split('&');
|
|
75
|
-
let token = hash;
|
|
76
|
-
let pageNoStr = '100500'; // back-compat for sessionToken
|
|
77
|
-
if (hashParts.length == 2) {
|
|
78
|
-
;
|
|
79
|
-
[token, pageNoStr] = hashParts;
|
|
80
|
-
}
|
|
81
|
-
if (!pageNoStr || !token) {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
this.app.sessionStorage.setItem(this.options.session_token_key, token);
|
|
85
|
-
this.app.sessionStorage.setItem(this.options.session_pageno_key, pageNoStr);
|
|
86
|
-
}
|
|
87
|
-
getSessionHash() {
|
|
88
|
-
const pageNo = this.getPageNumber();
|
|
89
|
-
const token = this.getSessionToken();
|
|
90
|
-
if (pageNo === undefined || token === undefined) {
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
return encodeURI(String(pageNo) + '&' + token);
|
|
94
|
-
}
|
|
95
40
|
getInfo() {
|
|
96
41
|
return {
|
|
97
42
|
sessionID: this.sessionID,
|
|
98
43
|
metadata: this.metadata,
|
|
99
44
|
userID: this.userID,
|
|
100
|
-
timestamp: this.timestamp,
|
|
101
|
-
projectID: this.projectID,
|
|
102
45
|
};
|
|
103
46
|
}
|
|
104
47
|
reset() {
|
|
105
|
-
this.app.sessionStorage.removeItem(this.options.session_token_key);
|
|
106
48
|
this.metadata = {};
|
|
107
49
|
this.userID = null;
|
|
108
|
-
this.sessionID =
|
|
109
|
-
this.timestamp = 0;
|
|
50
|
+
this.sessionID = null;
|
|
110
51
|
}
|
|
111
52
|
}
|
package/lib/app/ticker.d.ts
CHANGED