@openreplay/tracker 3.4.14 → 3.4.17-beta.1
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 +10 -5
- package/cjs/app/index.js +32 -21
- 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 -12
- package/cjs/app/sanitizer.d.ts +16 -0
- package/cjs/app/sanitizer.js +46 -0
- package/cjs/index.d.ts +1 -0
- package/cjs/index.js +7 -1
- 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 +10 -5
- package/lib/app/index.js +32 -21
- 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 -11
- package/lib/app/sanitizer.d.ts +16 -0
- package/lib/app/sanitizer.js +44 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +7 -1
- 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
|
@@ -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,11 +41,7 @@ export default class TopObserver extends Observer {
|
|
|
38
41
|
if (!context) {
|
|
39
42
|
return;
|
|
40
43
|
}
|
|
41
|
-
const observer = new IFrameObserver(this.app,
|
|
42
|
-
// @ts-ignore
|
|
43
|
-
observer.commited = this.commited;
|
|
44
|
-
// @ts-ignore
|
|
45
|
-
//observers.recents = this.recents
|
|
44
|
+
const observer = new IFrameObserver(this.app, context);
|
|
46
45
|
this.iframeObservers.push(observer);
|
|
47
46
|
observer.observe(iframe);
|
|
48
47
|
});
|
|
@@ -50,7 +49,7 @@ export default class TopObserver extends Observer {
|
|
|
50
49
|
handle();
|
|
51
50
|
}
|
|
52
51
|
handleShadowRoot(shRoot) {
|
|
53
|
-
const observer = new ShadowRootObserver(this.app, this.
|
|
52
|
+
const observer = new ShadowRootObserver(this.app, this.context);
|
|
54
53
|
this.shadowRootObservers.push(observer);
|
|
55
54
|
observer.observe(shRoot.host);
|
|
56
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
package/lib/index.js
CHANGED
|
@@ -111,7 +111,7 @@ export default class API {
|
|
|
111
111
|
// no-cors issue only with text/plain or not-set Content-Type
|
|
112
112
|
// req.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
|
|
113
113
|
req.send(JSON.stringify({
|
|
114
|
-
trackerVersion: '3.4.
|
|
114
|
+
trackerVersion: '3.4.17-beta.1',
|
|
115
115
|
projectKey: options.projectKey,
|
|
116
116
|
doNotTrack,
|
|
117
117
|
// TODO: add precise reason (an exact API missing)
|
|
@@ -219,4 +219,10 @@ export default class API {
|
|
|
219
219
|
this.app.send(new CustomIssue(key, payload));
|
|
220
220
|
}
|
|
221
221
|
}
|
|
222
|
+
resetNextPageSession(flag) {
|
|
223
|
+
if (typeof flag !== 'boolean' || !this.app) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
this.app.resetNextPageSession(flag);
|
|
227
|
+
}
|
|
222
228
|
}
|
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 '';
|