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