@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,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 {
|
|
@@ -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/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.4.
|
|
128
|
+
trackerVersion: '3.4.17',
|
|
115
129
|
projectKey: options.projectKey,
|
|
116
130
|
doNotTrack,
|
|
117
131
|
// TODO: add precise reason (an exact API missing)
|
|
@@ -131,7 +145,7 @@ export default class API {
|
|
|
131
145
|
deprecationWarn("'active' method", "'isActive' method", "/");
|
|
132
146
|
return this.isActive();
|
|
133
147
|
}
|
|
134
|
-
start() {
|
|
148
|
+
start(startOpts) {
|
|
135
149
|
if (!IN_BROWSER) {
|
|
136
150
|
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
151
|
return Promise.reject("Trying to start not in browser.");
|
|
@@ -139,7 +153,7 @@ export default class API {
|
|
|
139
153
|
if (this.app === null) {
|
|
140
154
|
return Promise.reject("Browser doesn't support required api, or doNotTrack is active.");
|
|
141
155
|
}
|
|
142
|
-
return this.app.start();
|
|
156
|
+
return this.app.start(startOpts);
|
|
143
157
|
}
|
|
144
158
|
stop() {
|
|
145
159
|
if (this.app === null) {
|
package/lib/modules/exception.js
CHANGED
|
@@ -37,7 +37,14 @@ export function getExceptionMessageFromEvent(e) {
|
|
|
37
37
|
return getExceptionMessage(e.reason, []);
|
|
38
38
|
}
|
|
39
39
|
else {
|
|
40
|
-
|
|
40
|
+
let message;
|
|
41
|
+
try {
|
|
42
|
+
message = JSON.stringify(e.reason);
|
|
43
|
+
}
|
|
44
|
+
catch (_) {
|
|
45
|
+
message = String(e.reason);
|
|
46
|
+
}
|
|
47
|
+
return new JSException('Unhandled Promise Rejection', message, '[]');
|
|
41
48
|
}
|
|
42
49
|
}
|
|
43
50
|
return null;
|
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
|
@@ -83,7 +83,7 @@ export default function (app) {
|
|
|
83
83
|
tag === 'LI' ||
|
|
84
84
|
target.onclick != null ||
|
|
85
85
|
target.getAttribute('role') === 'button') {
|
|
86
|
-
const label = app.
|
|
86
|
+
const label = app.sanitizer.getInnerTextSecure(target);
|
|
87
87
|
return normSpaces(label).slice(0, 100);
|
|
88
88
|
}
|
|
89
89
|
return '';
|
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 {};
|