@openreplay/tracker 3.4.16 → 3.5.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/README.md +8 -1
- package/cjs/app/context.d.ts +18 -0
- package/cjs/app/context.js +48 -0
- package/cjs/app/index.d.ts +39 -9
- package/cjs/app/index.js +155 -121
- package/cjs/app/logger.d.ts +27 -0
- package/cjs/app/logger.js +42 -0
- 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 -9
- package/cjs/index.js +32 -21
- package/cjs/modules/console.js +1 -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 +3 -1
- package/cjs/utils.d.ts +0 -8
- package/cjs/utils.js +3 -4
- package/lib/app/context.d.ts +18 -0
- package/lib/app/context.js +43 -0
- package/lib/app/index.d.ts +39 -9
- package/lib/app/index.js +156 -122
- package/lib/app/logger.d.ts +27 -0
- package/lib/app/logger.js +39 -1
- 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 -9
- package/lib/index.js +32 -21
- package/lib/modules/console.js +1 -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 +3 -1
- package/lib/utils.d.ts +0 -8
- package/lib/utils.js +2 -3
- 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,30 +1,8 @@
|
|
|
1
|
-
import { stars, hasOpenreplayAttribute } from "../../utils.js";
|
|
2
1
|
import { RemoveNodeAttribute, SetNodeAttribute, SetNodeAttributeURLBased, SetCSSDataURLBased, SetNodeData, CreateTextNode, CreateElementNode, MoveNode, RemoveNode, } from "../../messages/index.js";
|
|
2
|
+
import { isInstance, inDocument } from "../context.js";
|
|
3
3
|
function isSVGElement(node) {
|
|
4
4
|
return node.namespaceURI === 'http://www.w3.org/2000/svg';
|
|
5
5
|
}
|
|
6
|
-
// TODO: we need a type expert here so we won't have to ignore the lines
|
|
7
|
-
// TODO: use it everywhere (static function; export from which file? <-- global Window typing required)
|
|
8
|
-
export function isInstance(node, constr) {
|
|
9
|
-
const doc = node.ownerDocument;
|
|
10
|
-
if (!doc) { // null if Document
|
|
11
|
-
return constr.name === 'Document';
|
|
12
|
-
}
|
|
13
|
-
let context =
|
|
14
|
-
// @ts-ignore (for EI, Safary)
|
|
15
|
-
doc.parentWindow ||
|
|
16
|
-
doc.defaultView; // TODO: smart global typing for Window object
|
|
17
|
-
while (context.parent && context.parent !== context) {
|
|
18
|
-
// @ts-ignore
|
|
19
|
-
if (node instanceof context[constr.name]) {
|
|
20
|
-
return true;
|
|
21
|
-
}
|
|
22
|
-
// @ts-ignore
|
|
23
|
-
context = context.parent;
|
|
24
|
-
}
|
|
25
|
-
// @ts-ignore
|
|
26
|
-
return node instanceof context[constr.name];
|
|
27
|
-
}
|
|
28
6
|
function isIgnored(node) {
|
|
29
7
|
if (isInstance(node, Text)) {
|
|
30
8
|
return false;
|
|
@@ -54,7 +32,7 @@ function isObservable(node) {
|
|
|
54
32
|
return !isIgnored(node);
|
|
55
33
|
}
|
|
56
34
|
export default class Observer {
|
|
57
|
-
constructor(app,
|
|
35
|
+
constructor(app, context = window) {
|
|
58
36
|
this.app = app;
|
|
59
37
|
this.context = context;
|
|
60
38
|
this.commited = [];
|
|
@@ -63,34 +41,12 @@ export default class Observer {
|
|
|
63
41
|
this.indexes = [];
|
|
64
42
|
this.attributesList = [];
|
|
65
43
|
this.textSet = new Set();
|
|
66
|
-
this.
|
|
67
|
-
this.options = Object.assign({
|
|
68
|
-
obscureTextEmails: true,
|
|
69
|
-
obscureTextNumbers: false,
|
|
70
|
-
}, options);
|
|
71
|
-
this.inUpperContext = context.parent === context;
|
|
44
|
+
this.inUpperContext = context.parent === context; //TODO: get rid of context here
|
|
72
45
|
this.observer = new MutationObserver(this.app.safe((mutations) => {
|
|
73
46
|
for (const mutation of mutations) {
|
|
74
47
|
const target = mutation.target;
|
|
75
48
|
const type = mutation.type;
|
|
76
|
-
|
|
77
|
-
// Special case
|
|
78
|
-
// 'childList' on Document might happen in case of iframe.
|
|
79
|
-
// TODO: generalize as much as possible
|
|
80
|
-
// if (isInstance(target, Document) // Also ShadowRoot can be here
|
|
81
|
-
// && type === 'childList'
|
|
82
|
-
// //&& new Array(mutation.addedNodes).some(node => isInstance(node, HTMLHtmlElement))
|
|
83
|
-
// ) {
|
|
84
|
-
// const parentFrame = target.defaultView?.frameElement
|
|
85
|
-
// if (!parentFrame) { continue }
|
|
86
|
-
// this.bindTree(target.documentElement)
|
|
87
|
-
// const frameID = this.app.nodes.getID(parentFrame)
|
|
88
|
-
// const docID = this.app.nodes.getID(target.documentElement)
|
|
89
|
-
// if (frameID === undefined || docID === undefined) { continue }
|
|
90
|
-
// this.app.send(CreateIFrameDocument(frameID, docID));
|
|
91
|
-
// continue;
|
|
92
|
-
// }
|
|
93
|
-
if (!isObservable(target) || !context.document.contains(target)) {
|
|
49
|
+
if (!isObservable(target) || !inDocument(target)) {
|
|
94
50
|
continue;
|
|
95
51
|
}
|
|
96
52
|
if (type === 'childList') {
|
|
@@ -135,7 +91,6 @@ export default class Observer {
|
|
|
135
91
|
this.indexes.length = 1;
|
|
136
92
|
this.attributesList.length = 0;
|
|
137
93
|
this.textSet.clear();
|
|
138
|
-
//this.textMasked.clear();
|
|
139
94
|
}
|
|
140
95
|
sendNodeAttribute(id, node, name, value) {
|
|
141
96
|
if (isSVGElement(node)) {
|
|
@@ -184,35 +139,14 @@ export default class Observer {
|
|
|
184
139
|
}
|
|
185
140
|
this.app.send(new SetNodeAttribute(id, name, value));
|
|
186
141
|
}
|
|
187
|
-
/* TODO: abstract sanitation */
|
|
188
|
-
getInnerTextSecure(el) {
|
|
189
|
-
const id = this.app.nodes.getID(el);
|
|
190
|
-
if (!id) {
|
|
191
|
-
return '';
|
|
192
|
-
}
|
|
193
|
-
return this.checkObscure(id, el.innerText);
|
|
194
|
-
}
|
|
195
|
-
checkObscure(id, data) {
|
|
196
|
-
if (this.textMasked.has(id)) {
|
|
197
|
-
return data.replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/g, '█');
|
|
198
|
-
}
|
|
199
|
-
if (this.options.obscureTextNumbers) {
|
|
200
|
-
data = data.replace(/\d/g, '0');
|
|
201
|
-
}
|
|
202
|
-
if (this.options.obscureTextEmails) {
|
|
203
|
-
data = data.replace(/([^\s]+)@([^\s]+)\.([^\s]+)/g, (...f) => stars(f[1]) + '@' + stars(f[2]) + '.' + stars(f[3]));
|
|
204
|
-
}
|
|
205
|
-
return data;
|
|
206
|
-
}
|
|
207
142
|
sendNodeData(id, parentElement, data) {
|
|
208
143
|
if (isInstance(parentElement, HTMLStyleElement) || isInstance(parentElement, SVGStyleElement)) {
|
|
209
144
|
this.app.send(new SetCSSDataURLBased(id, data, this.app.getBaseHref()));
|
|
210
145
|
return;
|
|
211
146
|
}
|
|
212
|
-
data = this.
|
|
147
|
+
data = this.app.sanitizer.sanitize(id, data);
|
|
213
148
|
this.app.send(new SetNodeData(id, data));
|
|
214
149
|
}
|
|
215
|
-
/* end TODO: abstract sanitation */
|
|
216
150
|
bindNode(node) {
|
|
217
151
|
const r = this.app.nodes.registerNode(node);
|
|
218
152
|
const id = r[0];
|
|
@@ -264,10 +198,7 @@ export default class Observer {
|
|
|
264
198
|
this.unbindNode(node);
|
|
265
199
|
return false;
|
|
266
200
|
}
|
|
267
|
-
|
|
268
|
-
(isInstance(node, Element) && hasOpenreplayAttribute(node, 'masked'))) {
|
|
269
|
-
this.textMasked.add(id);
|
|
270
|
-
}
|
|
201
|
+
this.app.sanitizer.handleNode(id, parentID, node);
|
|
271
202
|
}
|
|
272
203
|
let sibling = node.previousSibling;
|
|
273
204
|
while (sibling !== null) {
|
|
@@ -340,8 +271,7 @@ export default class Observer {
|
|
|
340
271
|
let node;
|
|
341
272
|
for (let id = 0; id < this.recents.length; id++) {
|
|
342
273
|
// TODO: make things/logic nice here.
|
|
343
|
-
// commit required in any case if recents[id] true or false (in case of unbinding).
|
|
344
|
-
// ???!?!?R@TW:$HKJ$WLKn
|
|
274
|
+
// commit required in any case if recents[id] true or false (in case of unbinding) or undefined (in case of attr change).
|
|
345
275
|
if (!this.myNodes[id]) {
|
|
346
276
|
continue;
|
|
347
277
|
}
|
|
@@ -369,8 +299,6 @@ export default class Observer {
|
|
|
369
299
|
disconnect() {
|
|
370
300
|
this.observer.disconnect();
|
|
371
301
|
this.clear();
|
|
372
|
-
// to sanitizer
|
|
373
|
-
this.textMasked.clear();
|
|
374
302
|
this.myNodes.length = 0;
|
|
375
303
|
}
|
|
376
304
|
}
|
|
@@ -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,15 +1,18 @@
|
|
|
1
|
-
import Observer
|
|
1
|
+
import Observer from "./observer.js";
|
|
2
|
+
import { isInstance } from "../context.js";
|
|
2
3
|
import IFrameObserver from "./iframe_observer.js";
|
|
3
4
|
import ShadowRootObserver from "./shadow_root_observer.js";
|
|
4
5
|
import { CreateDocument } from "../../messages/index.js";
|
|
5
|
-
|
|
6
|
+
import { IN_BROWSER } from '../../utils.js';
|
|
7
|
+
const attachShadowNativeFn = IN_BROWSER ? Element.prototype.attachShadow : () => new ShadowRoot();
|
|
6
8
|
export default class TopObserver extends Observer {
|
|
7
9
|
constructor(app, options) {
|
|
8
|
-
super(app
|
|
9
|
-
captureIFrames: false
|
|
10
|
-
}, options));
|
|
10
|
+
super(app);
|
|
11
11
|
this.iframeObservers = [];
|
|
12
12
|
this.shadowRootObservers = [];
|
|
13
|
+
this.options = Object.assign({
|
|
14
|
+
captureIFrames: false
|
|
15
|
+
}, options);
|
|
13
16
|
// IFrames
|
|
14
17
|
this.app.nodes.attachNodeCallback(node => {
|
|
15
18
|
if (isInstance(node, HTMLIFrameElement) &&
|
|
@@ -38,7 +41,7 @@ export default class TopObserver extends Observer {
|
|
|
38
41
|
if (!context) {
|
|
39
42
|
return;
|
|
40
43
|
}
|
|
41
|
-
const observer = new IFrameObserver(this.app,
|
|
44
|
+
const observer = new IFrameObserver(this.app, context);
|
|
42
45
|
this.iframeObservers.push(observer);
|
|
43
46
|
observer.observe(iframe);
|
|
44
47
|
});
|
|
@@ -46,7 +49,7 @@ export default class TopObserver extends Observer {
|
|
|
46
49
|
handle();
|
|
47
50
|
}
|
|
48
51
|
handleShadowRoot(shRoot) {
|
|
49
|
-
const observer = new ShadowRootObserver(this.app, this.
|
|
52
|
+
const observer = new ShadowRootObserver(this.app, this.context);
|
|
50
53
|
this.shadowRootObservers.push(observer);
|
|
51
54
|
observer.observe(shRoot.host);
|
|
52
55
|
}
|
package/lib/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/lib/app/sanitizer.js
CHANGED
|
@@ -1 +1,44 @@
|
|
|
1
|
-
|
|
1
|
+
import { stars, hasOpenreplayAttribute } from "../utils.js";
|
|
2
|
+
import { isInstance } from "./context.js";
|
|
3
|
+
export default class Sanitizer {
|
|
4
|
+
constructor(app, options) {
|
|
5
|
+
this.app = app;
|
|
6
|
+
this.masked = new Set();
|
|
7
|
+
this.options = Object.assign({
|
|
8
|
+
obscureTextEmails: true,
|
|
9
|
+
obscureTextNumbers: false,
|
|
10
|
+
}, options);
|
|
11
|
+
}
|
|
12
|
+
handleNode(id, parentID, node) {
|
|
13
|
+
if (this.masked.has(parentID) ||
|
|
14
|
+
(isInstance(node, Element) && hasOpenreplayAttribute(node, 'masked'))) {
|
|
15
|
+
this.masked.add(id);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
sanitize(id, data) {
|
|
19
|
+
if (this.masked.has(id)) {
|
|
20
|
+
// TODO: is it the best place to put trim() ? Might trimmed spaces be considered in layout in certain cases?
|
|
21
|
+
return data.trim().replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/g, '█');
|
|
22
|
+
}
|
|
23
|
+
if (this.options.obscureTextNumbers) {
|
|
24
|
+
data = data.replace(/\d/g, '0');
|
|
25
|
+
}
|
|
26
|
+
if (this.options.obscureTextEmails) {
|
|
27
|
+
data = data.replace(/([^\s]+)@([^\s]+)\.([^\s]+)/g, (...f) => stars(f[1]) + '@' + stars(f[2]) + '.' + stars(f[3]));
|
|
28
|
+
}
|
|
29
|
+
return data;
|
|
30
|
+
}
|
|
31
|
+
isMasked(id) {
|
|
32
|
+
return this.masked.has(id);
|
|
33
|
+
}
|
|
34
|
+
getInnerTextSecure(el) {
|
|
35
|
+
const id = this.app.nodes.getID(el);
|
|
36
|
+
if (!id) {
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
return this.sanitize(id, el.innerText);
|
|
40
|
+
}
|
|
41
|
+
clear() {
|
|
42
|
+
this.masked.clear();
|
|
43
|
+
}
|
|
44
|
+
}
|
package/lib/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 {
|
|
@@ -22,8 +24,7 @@ export default class API {
|
|
|
22
24
|
constructor(options: Options);
|
|
23
25
|
use<T>(fn: (app: App | null, options?: Options) => T): T;
|
|
24
26
|
isActive(): boolean;
|
|
25
|
-
|
|
26
|
-
start(): Promise<import("./app/index.js").OnStartInfo>;
|
|
27
|
+
start(startOpts?: StartOptions): Promise<OnStartInfo>;
|
|
27
28
|
stop(): void;
|
|
28
29
|
getSessionToken(): string | null | undefined;
|
|
29
30
|
getSessionID(): string | null | undefined;
|
package/lib/index.js
CHANGED
|
@@ -77,7 +77,7 @@ export default class API {
|
|
|
77
77
|
(navigator.doNotTrack == '1'
|
|
78
78
|
// @ts-ignore
|
|
79
79
|
|| window.doNotTrack == '1');
|
|
80
|
-
this.app = doNotTrack ||
|
|
80
|
+
const app = this.app = doNotTrack ||
|
|
81
81
|
!('Map' in window) ||
|
|
82
82
|
!('Set' in window) ||
|
|
83
83
|
!('MutationObserver' in window) ||
|
|
@@ -88,20 +88,34 @@ export default class API {
|
|
|
88
88
|
!('Worker' in window)
|
|
89
89
|
? null
|
|
90
90
|
: new App(options.projectKey, options.sessionToken, options);
|
|
91
|
-
if (
|
|
92
|
-
Viewport(
|
|
93
|
-
CSSRules(
|
|
94
|
-
Connection(
|
|
95
|
-
Console(
|
|
96
|
-
Exception(
|
|
97
|
-
Img(
|
|
98
|
-
Input(
|
|
99
|
-
Mouse(
|
|
100
|
-
Timing(
|
|
101
|
-
Performance(
|
|
102
|
-
Scroll(
|
|
103
|
-
Longtasks(
|
|
91
|
+
if (app !== null) {
|
|
92
|
+
Viewport(app);
|
|
93
|
+
CSSRules(app);
|
|
94
|
+
Connection(app);
|
|
95
|
+
Console(app, options);
|
|
96
|
+
Exception(app, options);
|
|
97
|
+
Img(app);
|
|
98
|
+
Input(app, options);
|
|
99
|
+
Mouse(app);
|
|
100
|
+
Timing(app, options);
|
|
101
|
+
Performance(app, options);
|
|
102
|
+
Scroll(app);
|
|
103
|
+
Longtasks(app);
|
|
104
104
|
window.__OPENREPLAY__ = this;
|
|
105
|
+
if (options.autoResetOnWindowOpen) {
|
|
106
|
+
const wOpen = window.open;
|
|
107
|
+
app.attachStartCallback(() => {
|
|
108
|
+
// @ts-ignore ?
|
|
109
|
+
window.open = function (...args) {
|
|
110
|
+
app.resetNextPageSession(true);
|
|
111
|
+
wOpen.call(window, ...args);
|
|
112
|
+
app.resetNextPageSession(false);
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
app.attachStopCallback(() => {
|
|
116
|
+
window.open = wOpen;
|
|
117
|
+
});
|
|
118
|
+
}
|
|
105
119
|
}
|
|
106
120
|
else {
|
|
107
121
|
console.log("OpenReplay: browser doesn't support API required for tracking or doNotTrack is set to 1.");
|
|
@@ -111,7 +125,7 @@ export default class API {
|
|
|
111
125
|
// no-cors issue only with text/plain or not-set Content-Type
|
|
112
126
|
// req.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
|
|
113
127
|
req.send(JSON.stringify({
|
|
114
|
-
trackerVersion: '3.
|
|
128
|
+
trackerVersion: '3.5.0',
|
|
115
129
|
projectKey: options.projectKey,
|
|
116
130
|
doNotTrack,
|
|
117
131
|
// TODO: add precise reason (an exact API missing)
|
|
@@ -127,11 +141,7 @@ export default class API {
|
|
|
127
141
|
}
|
|
128
142
|
return this.app.active();
|
|
129
143
|
}
|
|
130
|
-
|
|
131
|
-
deprecationWarn("'active' method", "'isActive' method", "/");
|
|
132
|
-
return this.isActive();
|
|
133
|
-
}
|
|
134
|
-
start() {
|
|
144
|
+
start(startOpts) {
|
|
135
145
|
if (!IN_BROWSER) {
|
|
136
146
|
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 ${DOCS_HOST}${DOCS_SETUP}`);
|
|
137
147
|
return Promise.reject("Trying to start not in browser.");
|
|
@@ -139,7 +149,8 @@ export default class API {
|
|
|
139
149
|
if (this.app === null) {
|
|
140
150
|
return Promise.reject("Browser doesn't support required api, or doNotTrack is active.");
|
|
141
151
|
}
|
|
142
|
-
|
|
152
|
+
// TODO: check argument typing
|
|
153
|
+
return this.app.start(startOpts);
|
|
143
154
|
}
|
|
144
155
|
stop() {
|
|
145
156
|
if (this.app === null) {
|
package/lib/modules/console.js
CHANGED
package/lib/modules/img.js
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { timestamp, isURL } from "../utils.js";
|
|
2
|
-
import { ResourceTiming, SetNodeAttributeURLBased } from "../messages/index.js";
|
|
2
|
+
import { ResourceTiming, SetNodeAttributeURLBased, SetNodeAttribute } from "../messages/index.js";
|
|
3
|
+
const PLACEHOLDER_SRC = "https://static.openreplay.com/tracker/placeholder.jpeg";
|
|
3
4
|
export default function (app) {
|
|
5
|
+
function sendPlaceholder(id, node) {
|
|
6
|
+
app.send(new SetNodeAttribute(id, "src", PLACEHOLDER_SRC));
|
|
7
|
+
const { width, height } = node.getBoundingClientRect();
|
|
8
|
+
if (!node.hasAttribute("width")) {
|
|
9
|
+
app.send(new SetNodeAttribute(id, "width", String(width)));
|
|
10
|
+
}
|
|
11
|
+
if (!node.hasAttribute("height")) {
|
|
12
|
+
app.send(new SetNodeAttribute(id, "height", String(height)));
|
|
13
|
+
}
|
|
14
|
+
}
|
|
4
15
|
const sendImgSrc = app.safe(function () {
|
|
5
16
|
const id = app.nodes.getID(this);
|
|
6
17
|
if (id === undefined) {
|
|
@@ -15,7 +26,10 @@ export default function (app) {
|
|
|
15
26
|
app.send(new ResourceTiming(timestamp(), 0, 0, 0, 0, 0, src, 'img'));
|
|
16
27
|
}
|
|
17
28
|
}
|
|
18
|
-
else if (src.length
|
|
29
|
+
else if (src.length >= 1e5 || app.sanitizer.isMasked(id)) {
|
|
30
|
+
sendPlaceholder(id, this);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
19
33
|
app.send(new SetNodeAttributeURLBased(id, 'src', src, app.getBaseHref()));
|
|
20
34
|
}
|
|
21
35
|
});
|
package/lib/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/lib/modules/input.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { normSpaces, IN_BROWSER, getLabelAttribute, hasOpenreplayAttribute } from "../utils.js";
|
|
2
2
|
import { SetInputTarget, SetInputValue, SetInputChecked } from "../messages/index.js";
|
|
3
|
-
function
|
|
3
|
+
function isTextEditable(node) {
|
|
4
|
+
if (node instanceof HTMLTextAreaElement) {
|
|
5
|
+
return true;
|
|
6
|
+
}
|
|
4
7
|
if (!(node instanceof HTMLInputElement)) {
|
|
5
8
|
return false;
|
|
6
9
|
}
|
|
@@ -107,7 +110,7 @@ export default function (app, opts) {
|
|
|
107
110
|
app.ticker.attach(() => {
|
|
108
111
|
inputValues.forEach((value, id) => {
|
|
109
112
|
const node = app.nodes.getNode(id);
|
|
110
|
-
if (!
|
|
113
|
+
if (!isTextEditable(node)) {
|
|
111
114
|
inputValues.delete(id);
|
|
112
115
|
return;
|
|
113
116
|
}
|
|
@@ -138,7 +141,7 @@ export default function (app, opts) {
|
|
|
138
141
|
if (id === undefined) {
|
|
139
142
|
return;
|
|
140
143
|
}
|
|
141
|
-
if (
|
|
144
|
+
if (isTextEditable(node)) {
|
|
142
145
|
inputValues.set(id, node.value);
|
|
143
146
|
sendInputValue(id, node);
|
|
144
147
|
return;
|
package/lib/modules/mouse.js
CHANGED
|
@@ -55,6 +55,7 @@ function _getTarget(target) {
|
|
|
55
55
|
if (tag === 'BUTTON' ||
|
|
56
56
|
tag === 'A' ||
|
|
57
57
|
tag === 'LI' ||
|
|
58
|
+
tag === 'SELECT' ||
|
|
58
59
|
element.onclick != null ||
|
|
59
60
|
element.getAttribute('role') === 'button' ||
|
|
60
61
|
getLabelAttribute(element) !== null) {
|
|
@@ -81,9 +82,10 @@ export default function (app) {
|
|
|
81
82
|
if (tag === 'BUTTON' ||
|
|
82
83
|
tag === 'A' ||
|
|
83
84
|
tag === 'LI' ||
|
|
85
|
+
tag === 'SELECT' ||
|
|
84
86
|
target.onclick != null ||
|
|
85
87
|
target.getAttribute('role') === 'button') {
|
|
86
|
-
const label = app.
|
|
88
|
+
const label = app.sanitizer.getInnerTextSecure(target);
|
|
87
89
|
return normSpaces(label).slice(0, 100);
|
|
88
90
|
}
|
|
89
91
|
return '';
|
package/lib/utils.d.ts
CHANGED
|
@@ -3,14 +3,6 @@ export declare const stars: (str: string) => string;
|
|
|
3
3
|
export declare function normSpaces(str: string): string;
|
|
4
4
|
export declare function isURL(s: string): boolean;
|
|
5
5
|
export declare const IN_BROWSER: boolean;
|
|
6
|
-
export declare const log: {
|
|
7
|
-
(...data: any[]): void;
|
|
8
|
-
(message?: any, ...optionalParams: any[]): void;
|
|
9
|
-
};
|
|
10
|
-
export declare const warn: {
|
|
11
|
-
(...data: any[]): void;
|
|
12
|
-
(message?: any, ...optionalParams: any[]): void;
|
|
13
|
-
};
|
|
14
6
|
export declare const DOCS_HOST = "https://docs.openreplay.com";
|
|
15
7
|
export declare function deprecationWarn(nameOfFeature: string, useInstead: string, docsPath?: string): void;
|
|
16
8
|
export declare function getLabelAttribute(e: Element): string | null;
|
package/lib/utils.js
CHANGED
|
@@ -12,15 +12,14 @@ export function isURL(s) {
|
|
|
12
12
|
return s.substr(0, 8) === 'https://' || s.substr(0, 7) === 'http://';
|
|
13
13
|
}
|
|
14
14
|
export const IN_BROWSER = !(typeof window === "undefined");
|
|
15
|
-
|
|
16
|
-
export const warn = console.warn;
|
|
15
|
+
// TODO: JOIN IT WITH LOGGER somehow (use logging decorators?); Don't forget about index.js loggin when there is no logger instance.
|
|
17
16
|
export const DOCS_HOST = 'https://docs.openreplay.com';
|
|
18
17
|
const warnedFeatures = {};
|
|
19
18
|
export function deprecationWarn(nameOfFeature, useInstead, docsPath = "/") {
|
|
20
19
|
if (warnedFeatures[nameOfFeature]) {
|
|
21
20
|
return;
|
|
22
21
|
}
|
|
23
|
-
warn(`OpenReplay: ${nameOfFeature} is deprecated. ${useInstead ? `Please, use ${useInstead} instead.` : ""} Visit ${DOCS_HOST}${docsPath} for more information.`);
|
|
22
|
+
console.warn(`OpenReplay: ${nameOfFeature} is deprecated. ${useInstead ? `Please, use ${useInstead} instead.` : ""} Visit ${DOCS_HOST}${docsPath} for more information.`);
|
|
24
23
|
warnedFeatures[nameOfFeature] = true;
|
|
25
24
|
}
|
|
26
25
|
export function getLabelAttribute(e) {
|
package/package.json
CHANGED
package/cjs/app/observer.d.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import App from "./index.js";
|
|
2
|
-
interface Window extends WindowProxy {
|
|
3
|
-
HTMLInputElement: typeof HTMLInputElement;
|
|
4
|
-
HTMLLinkElement: typeof HTMLLinkElement;
|
|
5
|
-
HTMLStyleElement: typeof HTMLStyleElement;
|
|
6
|
-
SVGStyleElement: typeof SVGStyleElement;
|
|
7
|
-
HTMLIFrameElement: typeof HTMLIFrameElement;
|
|
8
|
-
Text: typeof Text;
|
|
9
|
-
Element: typeof Element;
|
|
10
|
-
}
|
|
11
|
-
export interface Options {
|
|
12
|
-
obscureTextEmails: boolean;
|
|
13
|
-
obscureTextNumbers: boolean;
|
|
14
|
-
captureIFrames: boolean;
|
|
15
|
-
}
|
|
16
|
-
export default class Observer {
|
|
17
|
-
private readonly app;
|
|
18
|
-
private readonly options;
|
|
19
|
-
private readonly context;
|
|
20
|
-
private readonly observer;
|
|
21
|
-
private readonly commited;
|
|
22
|
-
private readonly recents;
|
|
23
|
-
private readonly indexes;
|
|
24
|
-
private readonly attributesList;
|
|
25
|
-
private readonly textSet;
|
|
26
|
-
private readonly textMasked;
|
|
27
|
-
constructor(app: App, options: Options, context?: Window);
|
|
28
|
-
private clear;
|
|
29
|
-
private isInstance;
|
|
30
|
-
private isIgnored;
|
|
31
|
-
private sendNodeAttribute;
|
|
32
|
-
getInnerTextSecure(el: HTMLElement): string;
|
|
33
|
-
private checkObscure;
|
|
34
|
-
private sendNodeData;
|
|
35
|
-
private bindNode;
|
|
36
|
-
private bindTree;
|
|
37
|
-
private unbindNode;
|
|
38
|
-
private _commitNode;
|
|
39
|
-
private commitNode;
|
|
40
|
-
private commitNodes;
|
|
41
|
-
private iframeObservers;
|
|
42
|
-
private handleIframe;
|
|
43
|
-
private observeIframe;
|
|
44
|
-
observe(): void;
|
|
45
|
-
disconnect(): void;
|
|
46
|
-
}
|
|
47
|
-
export {};
|