@openreplay/tracker 3.4.15 → 3.4.17
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 +16 -6
- package/cjs/app/index.js +122 -106
- package/cjs/app/observer/observer.d.ts +2 -27
- package/cjs/app/observer/observer.js +18 -92
- package/cjs/app/observer/top_observer.d.ts +3 -3
- package/cjs/app/observer/top_observer.js +11 -8
- package/cjs/app/sanitizer.d.ts +16 -0
- package/cjs/app/sanitizer.js +46 -0
- package/cjs/index.d.ts +10 -8
- package/cjs/index.js +31 -17
- 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 +16 -6
- package/lib/app/index.js +122 -106
- package/lib/app/observer/observer.d.ts +2 -27
- package/lib/app/observer/observer.js +7 -79
- package/lib/app/observer/top_observer.d.ts +3 -3
- package/lib/app/observer/top_observer.js +10 -7
- package/lib/app/sanitizer.d.ts +16 -0
- package/lib/app/sanitizer.js +44 -1
- package/lib/index.d.ts +10 -8
- package/lib/index.js +31 -17
- 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/cjs/app/observer.js +0 -395
- package/lib/app/observer.d.ts +0 -47
- package/lib/app/observer.js +0 -392
|
@@ -1,39 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isInstance = void 0;
|
|
4
|
-
const utils_js_1 = require("../../utils.js");
|
|
5
3
|
const index_js_1 = require("../../messages/index.js");
|
|
4
|
+
const context_js_1 = require("../context.js");
|
|
6
5
|
function isSVGElement(node) {
|
|
7
6
|
return node.namespaceURI === 'http://www.w3.org/2000/svg';
|
|
8
7
|
}
|
|
9
|
-
// TODO: we need a type expert here so we won't have to ignore the lines
|
|
10
|
-
// TODO: use it everywhere (static function; export from which file? <-- global Window typing required)
|
|
11
|
-
function isInstance(node, constr) {
|
|
12
|
-
const doc = node.ownerDocument;
|
|
13
|
-
if (!doc) { // null if Document
|
|
14
|
-
return constr.name === 'Document';
|
|
15
|
-
}
|
|
16
|
-
let context =
|
|
17
|
-
// @ts-ignore (for EI, Safary)
|
|
18
|
-
doc.parentWindow ||
|
|
19
|
-
doc.defaultView; // TODO: smart global typing for Window object
|
|
20
|
-
while (context.parent && context.parent !== context) {
|
|
21
|
-
// @ts-ignore
|
|
22
|
-
if (node instanceof context[constr.name]) {
|
|
23
|
-
return true;
|
|
24
|
-
}
|
|
25
|
-
// @ts-ignore
|
|
26
|
-
context = context.parent;
|
|
27
|
-
}
|
|
28
|
-
// @ts-ignore
|
|
29
|
-
return node instanceof context[constr.name];
|
|
30
|
-
}
|
|
31
|
-
exports.isInstance = isInstance;
|
|
32
8
|
function isIgnored(node) {
|
|
33
|
-
if (isInstance(node, Text)) {
|
|
9
|
+
if ((0, context_js_1.isInstance)(node, Text)) {
|
|
34
10
|
return false;
|
|
35
11
|
}
|
|
36
|
-
if (!isInstance(node, Element)) {
|
|
12
|
+
if (!(0, context_js_1.isInstance)(node, Element)) {
|
|
37
13
|
return true;
|
|
38
14
|
}
|
|
39
15
|
const tag = node.tagName.toUpperCase();
|
|
@@ -49,7 +25,7 @@ function isIgnored(node) {
|
|
|
49
25
|
tag === 'BASE');
|
|
50
26
|
}
|
|
51
27
|
function isRootNode(node) {
|
|
52
|
-
return isInstance(node, Document) || isInstance(node, ShadowRoot);
|
|
28
|
+
return (0, context_js_1.isInstance)(node, Document) || (0, context_js_1.isInstance)(node, ShadowRoot);
|
|
53
29
|
}
|
|
54
30
|
function isObservable(node) {
|
|
55
31
|
if (isRootNode(node)) {
|
|
@@ -58,7 +34,7 @@ function isObservable(node) {
|
|
|
58
34
|
return !isIgnored(node);
|
|
59
35
|
}
|
|
60
36
|
class Observer {
|
|
61
|
-
constructor(app,
|
|
37
|
+
constructor(app, context = window) {
|
|
62
38
|
this.app = app;
|
|
63
39
|
this.context = context;
|
|
64
40
|
this.commited = [];
|
|
@@ -67,34 +43,12 @@ class Observer {
|
|
|
67
43
|
this.indexes = [];
|
|
68
44
|
this.attributesList = [];
|
|
69
45
|
this.textSet = new Set();
|
|
70
|
-
this.
|
|
71
|
-
this.options = Object.assign({
|
|
72
|
-
obscureTextEmails: true,
|
|
73
|
-
obscureTextNumbers: false,
|
|
74
|
-
}, options);
|
|
75
|
-
this.inUpperContext = context.parent === context;
|
|
46
|
+
this.inUpperContext = context.parent === context; //TODO: get rid of context here
|
|
76
47
|
this.observer = new MutationObserver(this.app.safe((mutations) => {
|
|
77
48
|
for (const mutation of mutations) {
|
|
78
49
|
const target = mutation.target;
|
|
79
50
|
const type = mutation.type;
|
|
80
|
-
|
|
81
|
-
// Special case
|
|
82
|
-
// 'childList' on Document might happen in case of iframe.
|
|
83
|
-
// TODO: generalize as much as possible
|
|
84
|
-
// if (isInstance(target, Document) // Also ShadowRoot can be here
|
|
85
|
-
// && type === 'childList'
|
|
86
|
-
// //&& new Array(mutation.addedNodes).some(node => isInstance(node, HTMLHtmlElement))
|
|
87
|
-
// ) {
|
|
88
|
-
// const parentFrame = target.defaultView?.frameElement
|
|
89
|
-
// if (!parentFrame) { continue }
|
|
90
|
-
// this.bindTree(target.documentElement)
|
|
91
|
-
// const frameID = this.app.nodes.getID(parentFrame)
|
|
92
|
-
// const docID = this.app.nodes.getID(target.documentElement)
|
|
93
|
-
// if (frameID === undefined || docID === undefined) { continue }
|
|
94
|
-
// this.app.send(CreateIFrameDocument(frameID, docID));
|
|
95
|
-
// continue;
|
|
96
|
-
// }
|
|
97
|
-
if (!isObservable(target) || !context.document.contains(target)) {
|
|
51
|
+
if (!isObservable(target) || !(0, context_js_1.inDocument)(target)) {
|
|
98
52
|
continue;
|
|
99
53
|
}
|
|
100
54
|
if (type === 'childList') {
|
|
@@ -139,7 +93,6 @@ class Observer {
|
|
|
139
93
|
this.indexes.length = 1;
|
|
140
94
|
this.attributesList.length = 0;
|
|
141
95
|
this.textSet.clear();
|
|
142
|
-
//this.textMasked.clear();
|
|
143
96
|
}
|
|
144
97
|
sendNodeAttribute(id, node, name, value) {
|
|
145
98
|
if (isSVGElement(node)) {
|
|
@@ -169,7 +122,7 @@ class Observer {
|
|
|
169
122
|
return;
|
|
170
123
|
}
|
|
171
124
|
if (name === 'value' &&
|
|
172
|
-
isInstance(node, HTMLInputElement) &&
|
|
125
|
+
(0, context_js_1.isInstance)(node, HTMLInputElement) &&
|
|
173
126
|
node.type !== 'button' &&
|
|
174
127
|
node.type !== 'reset' &&
|
|
175
128
|
node.type !== 'submit') {
|
|
@@ -179,7 +132,7 @@ class Observer {
|
|
|
179
132
|
this.app.send(new index_js_1.RemoveNodeAttribute(id, name));
|
|
180
133
|
return;
|
|
181
134
|
}
|
|
182
|
-
if (name === 'style' || name === 'href' && isInstance(node, HTMLLinkElement)) {
|
|
135
|
+
if (name === 'style' || name === 'href' && (0, context_js_1.isInstance)(node, HTMLLinkElement)) {
|
|
183
136
|
this.app.send(new index_js_1.SetNodeAttributeURLBased(id, name, value, this.app.getBaseHref()));
|
|
184
137
|
return;
|
|
185
138
|
}
|
|
@@ -188,35 +141,14 @@ class Observer {
|
|
|
188
141
|
}
|
|
189
142
|
this.app.send(new index_js_1.SetNodeAttribute(id, name, value));
|
|
190
143
|
}
|
|
191
|
-
/* TODO: abstract sanitation */
|
|
192
|
-
getInnerTextSecure(el) {
|
|
193
|
-
const id = this.app.nodes.getID(el);
|
|
194
|
-
if (!id) {
|
|
195
|
-
return '';
|
|
196
|
-
}
|
|
197
|
-
return this.checkObscure(id, el.innerText);
|
|
198
|
-
}
|
|
199
|
-
checkObscure(id, data) {
|
|
200
|
-
if (this.textMasked.has(id)) {
|
|
201
|
-
return data.replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/g, '█');
|
|
202
|
-
}
|
|
203
|
-
if (this.options.obscureTextNumbers) {
|
|
204
|
-
data = data.replace(/\d/g, '0');
|
|
205
|
-
}
|
|
206
|
-
if (this.options.obscureTextEmails) {
|
|
207
|
-
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]));
|
|
208
|
-
}
|
|
209
|
-
return data;
|
|
210
|
-
}
|
|
211
144
|
sendNodeData(id, parentElement, data) {
|
|
212
|
-
if (isInstance(parentElement, HTMLStyleElement) || isInstance(parentElement, SVGStyleElement)) {
|
|
145
|
+
if ((0, context_js_1.isInstance)(parentElement, HTMLStyleElement) || (0, context_js_1.isInstance)(parentElement, SVGStyleElement)) {
|
|
213
146
|
this.app.send(new index_js_1.SetCSSDataURLBased(id, data, this.app.getBaseHref()));
|
|
214
147
|
return;
|
|
215
148
|
}
|
|
216
|
-
data = this.
|
|
149
|
+
data = this.app.sanitizer.sanitize(id, data);
|
|
217
150
|
this.app.send(new index_js_1.SetNodeData(id, data));
|
|
218
151
|
}
|
|
219
|
-
/* end TODO: abstract sanitation */
|
|
220
152
|
bindNode(node) {
|
|
221
153
|
const r = this.app.nodes.registerNode(node);
|
|
222
154
|
const id = r[0];
|
|
@@ -254,7 +186,7 @@ class Observer {
|
|
|
254
186
|
// Disable parent check for the upper context HTMLHtmlElement, because it is root there... (before)
|
|
255
187
|
// TODO: get rid of "special" cases (there is an issue with CreateDocument altered behaviour though)
|
|
256
188
|
// TODO: Clean the logic (though now it workd fine)
|
|
257
|
-
if (!isInstance(node, HTMLHtmlElement) || !this.inUpperContext) {
|
|
189
|
+
if (!(0, context_js_1.isInstance)(node, HTMLHtmlElement) || !this.inUpperContext) {
|
|
258
190
|
if (parent === null) {
|
|
259
191
|
this.unbindNode(node);
|
|
260
192
|
return false;
|
|
@@ -268,10 +200,7 @@ class Observer {
|
|
|
268
200
|
this.unbindNode(node);
|
|
269
201
|
return false;
|
|
270
202
|
}
|
|
271
|
-
|
|
272
|
-
(isInstance(node, Element) && (0, utils_js_1.hasOpenreplayAttribute)(node, 'masked'))) {
|
|
273
|
-
this.textMasked.add(id);
|
|
274
|
-
}
|
|
203
|
+
this.app.sanitizer.handleNode(id, parentID, node);
|
|
275
204
|
}
|
|
276
205
|
let sibling = node.previousSibling;
|
|
277
206
|
while (sibling !== null) {
|
|
@@ -292,7 +221,7 @@ class Observer {
|
|
|
292
221
|
throw 'commitNode: missing node index';
|
|
293
222
|
}
|
|
294
223
|
if (isNew === true) {
|
|
295
|
-
if (isInstance(node, Element)) {
|
|
224
|
+
if ((0, context_js_1.isInstance)(node, Element)) {
|
|
296
225
|
if (parentID !== undefined) {
|
|
297
226
|
this.app.send(new index_js_1.CreateElementNode(id, parentID, index, node.tagName, isSVGElement(node)));
|
|
298
227
|
}
|
|
@@ -301,7 +230,7 @@ class Observer {
|
|
|
301
230
|
this.sendNodeAttribute(id, node, attr.nodeName, attr.value);
|
|
302
231
|
}
|
|
303
232
|
}
|
|
304
|
-
else if (isInstance(node, Text)) {
|
|
233
|
+
else if ((0, context_js_1.isInstance)(node, Text)) {
|
|
305
234
|
// for text node id != 0, hence parentID !== undefined and parent is Element
|
|
306
235
|
this.app.send(new index_js_1.CreateTextNode(id, parentID, index));
|
|
307
236
|
this.sendNodeData(id, parent, node.data);
|
|
@@ -313,7 +242,7 @@ class Observer {
|
|
|
313
242
|
}
|
|
314
243
|
const attr = this.attributesList[id];
|
|
315
244
|
if (attr !== undefined) {
|
|
316
|
-
if (!isInstance(node, Element)) {
|
|
245
|
+
if (!(0, context_js_1.isInstance)(node, Element)) {
|
|
317
246
|
throw 'commitNode: node is not an element';
|
|
318
247
|
}
|
|
319
248
|
for (const name of attr) {
|
|
@@ -321,7 +250,7 @@ class Observer {
|
|
|
321
250
|
}
|
|
322
251
|
}
|
|
323
252
|
if (this.textSet.has(id)) {
|
|
324
|
-
if (!isInstance(node, Text)) {
|
|
253
|
+
if (!(0, context_js_1.isInstance)(node, Text)) {
|
|
325
254
|
throw 'commitNode: node is not a text';
|
|
326
255
|
}
|
|
327
256
|
// for text node id != 0, hence parent is Element
|
|
@@ -344,8 +273,7 @@ class Observer {
|
|
|
344
273
|
let node;
|
|
345
274
|
for (let id = 0; id < this.recents.length; id++) {
|
|
346
275
|
// TODO: make things/logic nice here.
|
|
347
|
-
// commit required in any case if recents[id] true or false (in case of unbinding).
|
|
348
|
-
// ???!?!?R@TW:$HKJ$WLKn
|
|
276
|
+
// commit required in any case if recents[id] true or false (in case of unbinding) or undefined (in case of attr change).
|
|
349
277
|
if (!this.myNodes[id]) {
|
|
350
278
|
continue;
|
|
351
279
|
}
|
|
@@ -373,8 +301,6 @@ class Observer {
|
|
|
373
301
|
disconnect() {
|
|
374
302
|
this.observer.disconnect();
|
|
375
303
|
this.clear();
|
|
376
|
-
// to sanitizer
|
|
377
|
-
this.textMasked.clear();
|
|
378
304
|
this.myNodes.length = 0;
|
|
379
305
|
}
|
|
380
306
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import Observer from "./observer.js";
|
|
2
|
-
import type { Options as BaseOptions } from "./observer.js";
|
|
3
2
|
import App from "../index.js";
|
|
4
|
-
export interface Options
|
|
3
|
+
export interface Options {
|
|
5
4
|
captureIFrames: boolean;
|
|
6
5
|
}
|
|
7
|
-
export default class TopObserver extends Observer
|
|
6
|
+
export default class TopObserver extends Observer {
|
|
7
|
+
private readonly options;
|
|
8
8
|
constructor(app: App, options: Partial<Options>);
|
|
9
9
|
private iframeObservers;
|
|
10
10
|
private handleIframe;
|
|
@@ -1,27 +1,30 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const observer_js_1 = require("./observer.js");
|
|
4
|
+
const context_js_1 = require("../context.js");
|
|
4
5
|
const iframe_observer_js_1 = require("./iframe_observer.js");
|
|
5
6
|
const shadow_root_observer_js_1 = require("./shadow_root_observer.js");
|
|
6
7
|
const index_js_1 = require("../../messages/index.js");
|
|
7
|
-
const
|
|
8
|
+
const utils_js_1 = require("../../utils.js");
|
|
9
|
+
const attachShadowNativeFn = utils_js_1.IN_BROWSER ? Element.prototype.attachShadow : () => new ShadowRoot();
|
|
8
10
|
class TopObserver extends observer_js_1.default {
|
|
9
11
|
constructor(app, options) {
|
|
10
|
-
super(app
|
|
11
|
-
captureIFrames: false
|
|
12
|
-
}, options));
|
|
12
|
+
super(app);
|
|
13
13
|
this.iframeObservers = [];
|
|
14
14
|
this.shadowRootObservers = [];
|
|
15
|
+
this.options = Object.assign({
|
|
16
|
+
captureIFrames: false
|
|
17
|
+
}, options);
|
|
15
18
|
// IFrames
|
|
16
19
|
this.app.nodes.attachNodeCallback(node => {
|
|
17
|
-
if ((0,
|
|
20
|
+
if ((0, context_js_1.isInstance)(node, HTMLIFrameElement) &&
|
|
18
21
|
(this.options.captureIFrames || node.getAttribute("data-openreplay-capture"))) {
|
|
19
22
|
this.handleIframe(node);
|
|
20
23
|
}
|
|
21
24
|
});
|
|
22
25
|
// ShadowDOM
|
|
23
26
|
this.app.nodes.attachNodeCallback(node => {
|
|
24
|
-
if ((0,
|
|
27
|
+
if ((0, context_js_1.isInstance)(node, Element) && node.shadowRoot !== null) {
|
|
25
28
|
this.handleShadowRoot(node.shadowRoot);
|
|
26
29
|
}
|
|
27
30
|
});
|
|
@@ -40,7 +43,7 @@ class TopObserver extends observer_js_1.default {
|
|
|
40
43
|
if (!context) {
|
|
41
44
|
return;
|
|
42
45
|
}
|
|
43
|
-
const observer = new iframe_observer_js_1.default(this.app,
|
|
46
|
+
const observer = new iframe_observer_js_1.default(this.app, context);
|
|
44
47
|
this.iframeObservers.push(observer);
|
|
45
48
|
observer.observe(iframe);
|
|
46
49
|
});
|
|
@@ -48,7 +51,7 @@ class TopObserver extends observer_js_1.default {
|
|
|
48
51
|
handle();
|
|
49
52
|
}
|
|
50
53
|
handleShadowRoot(shRoot) {
|
|
51
|
-
const observer = new shadow_root_observer_js_1.default(this.app, this.
|
|
54
|
+
const observer = new shadow_root_observer_js_1.default(this.app, this.context);
|
|
52
55
|
this.shadowRootObservers.push(observer);
|
|
53
56
|
observer.observe(shRoot.host);
|
|
54
57
|
}
|
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
|
@@ -2,18 +2,20 @@ import App from "./app/index.js";
|
|
|
2
2
|
export { default as App } from './app/index.js';
|
|
3
3
|
import * as _Messages from "./messages/index.js";
|
|
4
4
|
export declare const Messages: typeof _Messages;
|
|
5
|
-
import { Options as AppOptions } from "./app/index.js";
|
|
6
|
-
import { Options as ConsoleOptions } from "./modules/console.js";
|
|
7
|
-
import { Options as ExceptionOptions } from "./modules/exception.js";
|
|
8
|
-
import { Options as InputOptions } from "./modules/input.js";
|
|
9
|
-
import { Options as PerformanceOptions } from "./modules/performance.js";
|
|
10
|
-
import { Options as TimingOptions } from "./modules/timing.js";
|
|
11
|
-
|
|
5
|
+
import type { Options as AppOptions } from "./app/index.js";
|
|
6
|
+
import type { Options as ConsoleOptions } from "./modules/console.js";
|
|
7
|
+
import type { Options as ExceptionOptions } from "./modules/exception.js";
|
|
8
|
+
import type { Options as InputOptions } from "./modules/input.js";
|
|
9
|
+
import type { Options as PerformanceOptions } from "./modules/performance.js";
|
|
10
|
+
import type { Options as TimingOptions } from "./modules/timing.js";
|
|
11
|
+
import type { StartOptions } from './app/index.js';
|
|
12
|
+
import type { OnStartInfo } from './app/index.js';
|
|
12
13
|
export declare type Options = Partial<AppOptions & ConsoleOptions & ExceptionOptions & InputOptions & PerformanceOptions & TimingOptions> & {
|
|
13
14
|
projectID?: number;
|
|
14
15
|
projectKey: string;
|
|
15
16
|
sessionToken?: string;
|
|
16
17
|
respectDoNotTrack?: boolean;
|
|
18
|
+
autoResetOnWindowOpen?: boolean;
|
|
17
19
|
__DISABLE_SECURE_MODE?: boolean;
|
|
18
20
|
};
|
|
19
21
|
export default class API {
|
|
@@ -23,7 +25,7 @@ export default class API {
|
|
|
23
25
|
use<T>(fn: (app: App | null, options?: Options) => T): T;
|
|
24
26
|
isActive(): boolean;
|
|
25
27
|
active(): boolean;
|
|
26
|
-
start(): Promise<
|
|
28
|
+
start(startOpts?: StartOptions): Promise<OnStartInfo>;
|
|
27
29
|
stop(): void;
|
|
28
30
|
getSessionToken(): string | null | undefined;
|
|
29
31
|
getSessionID(): string | null | undefined;
|
package/cjs/index.js
CHANGED
|
@@ -81,7 +81,7 @@ class API {
|
|
|
81
81
|
(navigator.doNotTrack == '1'
|
|
82
82
|
// @ts-ignore
|
|
83
83
|
|| window.doNotTrack == '1');
|
|
84
|
-
this.app = doNotTrack ||
|
|
84
|
+
const app = this.app = doNotTrack ||
|
|
85
85
|
!('Map' in window) ||
|
|
86
86
|
!('Set' in window) ||
|
|
87
87
|
!('MutationObserver' in window) ||
|
|
@@ -92,20 +92,34 @@ class API {
|
|
|
92
92
|
!('Worker' in window)
|
|
93
93
|
? null
|
|
94
94
|
: new index_js_1.default(options.projectKey, options.sessionToken, options);
|
|
95
|
-
if (
|
|
96
|
-
(0, viewport_js_1.default)(
|
|
97
|
-
(0, cssrules_js_1.default)(
|
|
98
|
-
(0, connection_js_1.default)(
|
|
99
|
-
(0, console_js_1.default)(
|
|
100
|
-
(0, exception_js_1.default)(
|
|
101
|
-
(0, img_js_1.default)(
|
|
102
|
-
(0, input_js_1.default)(
|
|
103
|
-
(0, mouse_js_1.default)(
|
|
104
|
-
(0, timing_js_1.default)(
|
|
105
|
-
(0, performance_js_1.default)(
|
|
106
|
-
(0, scroll_js_1.default)(
|
|
107
|
-
(0, longtasks_js_1.default)(
|
|
95
|
+
if (app !== null) {
|
|
96
|
+
(0, viewport_js_1.default)(app);
|
|
97
|
+
(0, cssrules_js_1.default)(app);
|
|
98
|
+
(0, connection_js_1.default)(app);
|
|
99
|
+
(0, console_js_1.default)(app, options);
|
|
100
|
+
(0, exception_js_1.default)(app, options);
|
|
101
|
+
(0, img_js_1.default)(app);
|
|
102
|
+
(0, input_js_1.default)(app, options);
|
|
103
|
+
(0, mouse_js_1.default)(app);
|
|
104
|
+
(0, timing_js_1.default)(app, options);
|
|
105
|
+
(0, performance_js_1.default)(app, options);
|
|
106
|
+
(0, scroll_js_1.default)(app);
|
|
107
|
+
(0, longtasks_js_1.default)(app);
|
|
108
108
|
window.__OPENREPLAY__ = this;
|
|
109
|
+
if (options.autoResetOnWindowOpen) {
|
|
110
|
+
const wOpen = window.open;
|
|
111
|
+
app.attachStartCallback(() => {
|
|
112
|
+
// @ts-ignore ?
|
|
113
|
+
window.open = function (...args) {
|
|
114
|
+
app.resetNextPageSession(true);
|
|
115
|
+
wOpen.call(window, ...args);
|
|
116
|
+
app.resetNextPageSession(false);
|
|
117
|
+
};
|
|
118
|
+
});
|
|
119
|
+
app.attachStopCallback(() => {
|
|
120
|
+
window.open = wOpen;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
109
123
|
}
|
|
110
124
|
else {
|
|
111
125
|
console.log("OpenReplay: browser doesn't support API required for tracking or doNotTrack is set to 1.");
|
|
@@ -115,7 +129,7 @@ class API {
|
|
|
115
129
|
// no-cors issue only with text/plain or not-set Content-Type
|
|
116
130
|
// req.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
|
|
117
131
|
req.send(JSON.stringify({
|
|
118
|
-
trackerVersion: '3.4.
|
|
132
|
+
trackerVersion: '3.4.17',
|
|
119
133
|
projectKey: options.projectKey,
|
|
120
134
|
doNotTrack,
|
|
121
135
|
// TODO: add precise reason (an exact API missing)
|
|
@@ -135,7 +149,7 @@ class API {
|
|
|
135
149
|
(0, utils_js_1.deprecationWarn)("'active' method", "'isActive' method", "/");
|
|
136
150
|
return this.isActive();
|
|
137
151
|
}
|
|
138
|
-
start() {
|
|
152
|
+
start(startOpts) {
|
|
139
153
|
if (!utils_js_1.IN_BROWSER) {
|
|
140
154
|
console.error(`OpenReplay: you are trying to start Tracker on a node.js environment. If you want to use OpenReplay with SSR, please, use componentDidMount or useEffect API for placing the \`tracker.start()\` line. Check documentation on ${utils_js_1.DOCS_HOST}${DOCS_SETUP}`);
|
|
141
155
|
return Promise.reject("Trying to start not in browser.");
|
|
@@ -143,7 +157,7 @@ class API {
|
|
|
143
157
|
if (this.app === null) {
|
|
144
158
|
return Promise.reject("Browser doesn't support required api, or doNotTrack is active.");
|
|
145
159
|
}
|
|
146
|
-
return this.app.start();
|
|
160
|
+
return this.app.start(startOpts);
|
|
147
161
|
}
|
|
148
162
|
stop() {
|
|
149
163
|
if (this.app === null) {
|
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 {};
|
package/cjs/modules/input.js
CHANGED
|
@@ -3,7 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.getInputLabel = void 0;
|
|
4
4
|
const utils_js_1 = require("../utils.js");
|
|
5
5
|
const index_js_1 = require("../messages/index.js");
|
|
6
|
-
function
|
|
6
|
+
function isTextEditable(node) {
|
|
7
|
+
if (node instanceof HTMLTextAreaElement) {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
7
10
|
if (!(node instanceof HTMLInputElement)) {
|
|
8
11
|
return false;
|
|
9
12
|
}
|
|
@@ -111,7 +114,7 @@ function default_1(app, opts) {
|
|
|
111
114
|
app.ticker.attach(() => {
|
|
112
115
|
inputValues.forEach((value, id) => {
|
|
113
116
|
const node = app.nodes.getNode(id);
|
|
114
|
-
if (!
|
|
117
|
+
if (!isTextEditable(node)) {
|
|
115
118
|
inputValues.delete(id);
|
|
116
119
|
return;
|
|
117
120
|
}
|
|
@@ -142,7 +145,7 @@ function default_1(app, opts) {
|
|
|
142
145
|
if (id === undefined) {
|
|
143
146
|
return;
|
|
144
147
|
}
|
|
145
|
-
if (
|
|
148
|
+
if (isTextEditable(node)) {
|
|
146
149
|
inputValues.set(id, node.value);
|
|
147
150
|
sendInputValue(id, node);
|
|
148
151
|
return;
|
package/cjs/modules/mouse.js
CHANGED
|
@@ -85,7 +85,7 @@ function default_1(app) {
|
|
|
85
85
|
tag === 'LI' ||
|
|
86
86
|
target.onclick != null ||
|
|
87
87
|
target.getAttribute('role') === 'button') {
|
|
88
|
-
const label = app.
|
|
88
|
+
const label = app.sanitizer.getInnerTextSecure(target);
|
|
89
89
|
return (0, utils_js_1.normSpaces)(label).slice(0, 100);
|
|
90
90
|
}
|
|
91
91
|
return '';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface Window extends globalThis.Window {
|
|
2
|
+
HTMLInputElement: typeof HTMLInputElement;
|
|
3
|
+
HTMLLinkElement: typeof HTMLLinkElement;
|
|
4
|
+
HTMLStyleElement: typeof HTMLStyleElement;
|
|
5
|
+
SVGStyleElement: typeof SVGStyleElement;
|
|
6
|
+
HTMLIFrameElement: typeof HTMLIFrameElement;
|
|
7
|
+
Text: typeof Text;
|
|
8
|
+
Element: typeof Element;
|
|
9
|
+
ShadowRoot: typeof ShadowRoot;
|
|
10
|
+
}
|
|
11
|
+
declare type WindowConstructor = Document | Element | Text | ShadowRoot | HTMLInputElement | HTMLLinkElement | HTMLStyleElement | HTMLIFrameElement;
|
|
12
|
+
declare type Constructor<T> = {
|
|
13
|
+
new (...args: any[]): T;
|
|
14
|
+
name: string;
|
|
15
|
+
};
|
|
16
|
+
export declare function isInstance<T extends WindowConstructor>(node: Node, constr: Constructor<T>): node is T;
|
|
17
|
+
export declare function inDocument(node: Node): boolean;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// TODO: we need a type expert here so we won't have to ignore the lines
|
|
2
|
+
// TODO: use it everywhere (static function; export from which file? <-- global Window typing required)
|
|
3
|
+
export function isInstance(node, constr) {
|
|
4
|
+
const doc = node.ownerDocument;
|
|
5
|
+
if (!doc) { // null if Document
|
|
6
|
+
return constr.name === 'Document';
|
|
7
|
+
}
|
|
8
|
+
let context =
|
|
9
|
+
// @ts-ignore (for EI, Safary)
|
|
10
|
+
doc.parentWindow ||
|
|
11
|
+
doc.defaultView; // TODO: smart global typing for Window object
|
|
12
|
+
while (context.parent && context.parent !== context) {
|
|
13
|
+
// @ts-ignore
|
|
14
|
+
if (node instanceof context[constr.name]) {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
// @ts-ignore
|
|
18
|
+
context = context.parent;
|
|
19
|
+
}
|
|
20
|
+
// @ts-ignore
|
|
21
|
+
return node instanceof context[constr.name];
|
|
22
|
+
}
|
|
23
|
+
export function inDocument(node) {
|
|
24
|
+
const doc = node.ownerDocument;
|
|
25
|
+
if (!doc) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
if (doc.contains(node)) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
let context =
|
|
32
|
+
// @ts-ignore (for EI, Safary)
|
|
33
|
+
doc.parentWindow ||
|
|
34
|
+
doc.defaultView;
|
|
35
|
+
while (context.parent && context.parent !== context) {
|
|
36
|
+
if (context.document.contains(node)) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
// @ts-ignore
|
|
40
|
+
context = context.parent;
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
43
|
+
}
|