@openreplay/tracker 3.4.16 → 3.4.17-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/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 +19 -8
- 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 +9 -7
- 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/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 +19 -8
- 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 +8 -6
- 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/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
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
|
+
}
|
package/lib/app/index.d.ts
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import Message from "../messages/message.js";
|
|
2
2
|
import Nodes from "./nodes.js";
|
|
3
|
-
import
|
|
3
|
+
import Sanitizer from "./sanitizer.js";
|
|
4
4
|
import Ticker from "./ticker.js";
|
|
5
5
|
import type { Options as ObserverOptions } from "./observer/top_observer.js";
|
|
6
|
+
import type { Options as SanitizerOptions } from "./sanitizer.js";
|
|
6
7
|
import type { Options as WebworkerOptions } from "../messages/webworker.js";
|
|
7
8
|
export interface OnStartInfo {
|
|
8
9
|
sessionID: string;
|
|
9
10
|
sessionToken: string;
|
|
10
11
|
userUUID: string;
|
|
11
12
|
}
|
|
12
|
-
|
|
13
|
+
declare type AppOptions = {
|
|
13
14
|
revID: string;
|
|
14
15
|
node_id: string;
|
|
15
16
|
session_token_key: string;
|
|
16
17
|
session_pageno_key: string;
|
|
18
|
+
session_reset_key: string;
|
|
17
19
|
local_uuid_key: string;
|
|
18
20
|
ingestPoint: string;
|
|
19
21
|
resourceBaseHref: string | null;
|
|
@@ -21,7 +23,8 @@ export declare type Options = {
|
|
|
21
23
|
__debug_report_edp: string | null;
|
|
22
24
|
__debug_log: boolean;
|
|
23
25
|
onStart?: (info: OnStartInfo) => void;
|
|
24
|
-
} &
|
|
26
|
+
} & WebworkerOptions;
|
|
27
|
+
export declare type Options = AppOptions & ObserverOptions & SanitizerOptions;
|
|
25
28
|
declare type Callback = () => void;
|
|
26
29
|
declare type CommitCallback = (messages: Array<Message>) => void;
|
|
27
30
|
export declare const DEFAULT_INGEST_POINT = "https://api.openreplay.com/ingest";
|
|
@@ -29,8 +32,9 @@ export default class App {
|
|
|
29
32
|
readonly nodes: Nodes;
|
|
30
33
|
readonly ticker: Ticker;
|
|
31
34
|
readonly projectKey: string;
|
|
35
|
+
readonly sanitizer: Sanitizer;
|
|
32
36
|
private readonly messages;
|
|
33
|
-
readonly observer
|
|
37
|
+
private readonly observer;
|
|
34
38
|
private readonly startCallbacks;
|
|
35
39
|
private readonly stopCallbacks;
|
|
36
40
|
private readonly commitCallbacks;
|
|
@@ -40,7 +44,7 @@ export default class App {
|
|
|
40
44
|
private isActive;
|
|
41
45
|
private version;
|
|
42
46
|
private readonly worker?;
|
|
43
|
-
constructor(projectKey: string, sessionToken: string | null | undefined,
|
|
47
|
+
constructor(projectKey: string, sessionToken: string | null | undefined, options: Partial<Options>);
|
|
44
48
|
private _debug;
|
|
45
49
|
send(message: Message, urgent?: boolean): void;
|
|
46
50
|
private commit;
|
|
@@ -58,6 +62,7 @@ export default class App {
|
|
|
58
62
|
resolveResourceURL(resourceURL: string): string;
|
|
59
63
|
isServiceURL(url: string): boolean;
|
|
60
64
|
active(): boolean;
|
|
65
|
+
resetNextPageSession(flag: boolean): void;
|
|
61
66
|
private _start;
|
|
62
67
|
start(reset?: boolean): Promise<OnStartInfo>;
|
|
63
68
|
stop(): void;
|
package/lib/app/index.js
CHANGED
|
@@ -2,41 +2,41 @@ import { timestamp, log, warn } from "../utils.js";
|
|
|
2
2
|
import { Timestamp } from "../messages/index.js";
|
|
3
3
|
import Nodes from "./nodes.js";
|
|
4
4
|
import Observer from "./observer/top_observer.js";
|
|
5
|
+
import Sanitizer from "./sanitizer.js";
|
|
5
6
|
import Ticker from "./ticker.js";
|
|
6
7
|
import { deviceMemory, jsHeapSizeLimit } from "../modules/performance.js";
|
|
7
8
|
// TODO: use backendHost only
|
|
8
9
|
export const DEFAULT_INGEST_POINT = 'https://api.openreplay.com/ingest';
|
|
9
10
|
export default class App {
|
|
10
|
-
constructor(projectKey, sessionToken,
|
|
11
|
+
constructor(projectKey, sessionToken, options) {
|
|
11
12
|
this.messages = [];
|
|
12
13
|
this.startCallbacks = [];
|
|
13
14
|
this.stopCallbacks = [];
|
|
14
15
|
this.commitCallbacks = [];
|
|
15
16
|
this._sessionID = null;
|
|
16
17
|
this.isActive = false;
|
|
17
|
-
this.version = '3.4.
|
|
18
|
+
this.version = '3.4.17-beta.0';
|
|
18
19
|
this.projectKey = projectKey;
|
|
19
20
|
this.options = Object.assign({
|
|
20
21
|
revID: '',
|
|
21
22
|
node_id: '__openreplay_id',
|
|
22
23
|
session_token_key: '__openreplay_token',
|
|
23
24
|
session_pageno_key: '__openreplay_pageno',
|
|
25
|
+
session_reset_key: '__openreplay_reset',
|
|
24
26
|
local_uuid_key: '__openreplay_uuid',
|
|
25
27
|
ingestPoint: DEFAULT_INGEST_POINT,
|
|
26
28
|
resourceBaseHref: null,
|
|
27
29
|
__is_snippet: false,
|
|
28
30
|
__debug_report_edp: null,
|
|
29
31
|
__debug_log: false,
|
|
30
|
-
|
|
31
|
-
obscureTextNumbers: false,
|
|
32
|
-
captureIFrames: false,
|
|
33
|
-
}, opts);
|
|
32
|
+
}, options);
|
|
34
33
|
if (sessionToken != null) {
|
|
35
34
|
sessionStorage.setItem(this.options.session_token_key, sessionToken);
|
|
36
35
|
}
|
|
37
36
|
this.revID = this.options.revID;
|
|
37
|
+
this.sanitizer = new Sanitizer(this, options);
|
|
38
38
|
this.nodes = new Nodes(this.options.node_id);
|
|
39
|
-
this.observer = new Observer(this,
|
|
39
|
+
this.observer = new Observer(this, options);
|
|
40
40
|
this.ticker = new Ticker(this);
|
|
41
41
|
this.ticker.attach(() => this.commit());
|
|
42
42
|
try {
|
|
@@ -178,6 +178,14 @@ export default class App {
|
|
|
178
178
|
active() {
|
|
179
179
|
return this.isActive;
|
|
180
180
|
}
|
|
181
|
+
resetNextPageSession(flag) {
|
|
182
|
+
if (flag) {
|
|
183
|
+
sessionStorage.setItem(this.options.session_reset_key, 't');
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
sessionStorage.removeItem(this.options.session_reset_key);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
181
189
|
_start(reset) {
|
|
182
190
|
if (!this.isActive) {
|
|
183
191
|
if (!this.worker) {
|
|
@@ -205,6 +213,8 @@ export default class App {
|
|
|
205
213
|
// if (tokenIsActive) {
|
|
206
214
|
// token = null
|
|
207
215
|
// }
|
|
216
|
+
const sReset = sessionStorage.getItem(this.options.session_reset_key);
|
|
217
|
+
sessionStorage.removeItem(this.options.session_reset_key);
|
|
208
218
|
return window.fetch(this.options.ingestPoint + '/v1/web/start', {
|
|
209
219
|
method: 'POST',
|
|
210
220
|
headers: {
|
|
@@ -220,7 +230,7 @@ export default class App {
|
|
|
220
230
|
isSnippet: this.options.__is_snippet,
|
|
221
231
|
deviceMemory,
|
|
222
232
|
jsHeapSizeLimit,
|
|
223
|
-
reset,
|
|
233
|
+
reset: reset || sReset !== null,
|
|
224
234
|
}),
|
|
225
235
|
})
|
|
226
236
|
.then(r => {
|
|
@@ -298,6 +308,7 @@ export default class App {
|
|
|
298
308
|
if (this.worker) {
|
|
299
309
|
this.worker.postMessage("stop");
|
|
300
310
|
}
|
|
311
|
+
this.sanitizer.clear();
|
|
301
312
|
this.observer.disconnect();
|
|
302
313
|
this.nodes.clear();
|
|
303
314
|
this.ticker.stop();
|
|
@@ -1,25 +1,5 @@
|
|
|
1
1
|
import App from "../index.js";
|
|
2
|
-
export
|
|
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
|
-
ShadowRoot: typeof ShadowRoot;
|
|
11
|
-
}
|
|
12
|
-
declare type WindowConstructor = Document | Element | Text | ShadowRoot | HTMLInputElement | HTMLLinkElement | HTMLStyleElement | HTMLIFrameElement;
|
|
13
|
-
declare type Constructor<T> = {
|
|
14
|
-
new (...args: any[]): T;
|
|
15
|
-
name: string;
|
|
16
|
-
};
|
|
17
|
-
export declare function isInstance<T extends WindowConstructor>(node: Node, constr: Constructor<T>): node is T;
|
|
18
|
-
export interface Options {
|
|
19
|
-
obscureTextEmails: boolean;
|
|
20
|
-
obscureTextNumbers: boolean;
|
|
21
|
-
}
|
|
22
|
-
export default abstract class Observer<AdditionalOptions = {}> {
|
|
2
|
+
export default abstract class Observer {
|
|
23
3
|
protected readonly app: App;
|
|
24
4
|
protected readonly context: Window;
|
|
25
5
|
private readonly observer;
|
|
@@ -29,14 +9,10 @@ export default abstract class Observer<AdditionalOptions = {}> {
|
|
|
29
9
|
private readonly indexes;
|
|
30
10
|
private readonly attributesList;
|
|
31
11
|
private readonly textSet;
|
|
32
|
-
private readonly textMasked;
|
|
33
|
-
protected readonly options: Options & AdditionalOptions;
|
|
34
12
|
private readonly inUpperContext;
|
|
35
|
-
constructor(app: App,
|
|
13
|
+
constructor(app: App, context?: Window);
|
|
36
14
|
private clear;
|
|
37
15
|
private sendNodeAttribute;
|
|
38
|
-
getInnerTextSecure(el: HTMLElement): string;
|
|
39
|
-
private checkObscure;
|
|
40
16
|
private sendNodeData;
|
|
41
17
|
private bindNode;
|
|
42
18
|
private bindTree;
|
|
@@ -47,4 +23,3 @@ export default abstract class Observer<AdditionalOptions = {}> {
|
|
|
47
23
|
protected observeRoot(node: Node, beforeCommit: (id?: number) => unknown, nodeToBind?: Node): void;
|
|
48
24
|
disconnect(): void;
|
|
49
25
|
}
|
|
50
|
-
export {};
|
|
@@ -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,17 @@
|
|
|
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
|
const attachShadowNativeFn = Element.prototype.attachShadow;
|
|
6
7
|
export default class TopObserver extends Observer {
|
|
7
8
|
constructor(app, options) {
|
|
8
|
-
super(app
|
|
9
|
-
captureIFrames: false
|
|
10
|
-
}, options));
|
|
9
|
+
super(app);
|
|
11
10
|
this.iframeObservers = [];
|
|
12
11
|
this.shadowRootObservers = [];
|
|
12
|
+
this.options = Object.assign({
|
|
13
|
+
captureIFrames: false
|
|
14
|
+
}, options);
|
|
13
15
|
// IFrames
|
|
14
16
|
this.app.nodes.attachNodeCallback(node => {
|
|
15
17
|
if (isInstance(node, HTMLIFrameElement) &&
|
|
@@ -38,7 +40,7 @@ export default class TopObserver extends Observer {
|
|
|
38
40
|
if (!context) {
|
|
39
41
|
return;
|
|
40
42
|
}
|
|
41
|
-
const observer = new IFrameObserver(this.app,
|
|
43
|
+
const observer = new IFrameObserver(this.app, context);
|
|
42
44
|
this.iframeObservers.push(observer);
|
|
43
45
|
observer.observe(iframe);
|
|
44
46
|
});
|
|
@@ -46,7 +48,7 @@ export default class TopObserver extends Observer {
|
|
|
46
48
|
handle();
|
|
47
49
|
}
|
|
48
50
|
handleShadowRoot(shRoot) {
|
|
49
|
-
const observer = new ShadowRootObserver(this.app, this.
|
|
51
|
+
const observer = new ShadowRootObserver(this.app, this.context);
|
|
50
52
|
this.shadowRootObservers.push(observer);
|
|
51
53
|
observer.observe(shRoot.host);
|
|
52
54
|
}
|
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.0',
|
|
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
|
}
|