@openreplay/tracker 3.4.13 → 3.4.14
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/index.d.ts +2 -2
- package/cjs/app/index.js +3 -3
- package/cjs/app/observer/iframe_observer.d.ts +4 -0
- package/cjs/app/observer/iframe_observer.js +22 -0
- package/cjs/app/{observer.d.ts → observer/observer.d.ts} +17 -14
- package/cjs/app/{observer.js → observer/observer.js} +128 -142
- package/cjs/app/observer/shadow_root_observer.d.ts +4 -0
- package/cjs/app/observer/shadow_root_observer.js +21 -0
- package/cjs/app/observer/top_observer.d.ts +15 -0
- package/cjs/app/observer/top_observer.js +86 -0
- package/cjs/index.js +1 -1
- package/lib/app/index.d.ts +2 -2
- package/lib/app/index.js +2 -2
- package/lib/app/observer/iframe_observer.d.ts +4 -0
- package/lib/app/observer/iframe_observer.js +19 -0
- package/lib/app/{observer.d.ts → observer/observer.d.ts} +17 -14
- package/lib/app/{observer.js → observer/observer.js} +126 -142
- package/lib/app/observer/shadow_root_observer.d.ts +4 -0
- package/lib/app/observer/shadow_root_observer.js +18 -0
- package/lib/app/observer/top_observer.d.ts +15 -0
- package/lib/app/observer/top_observer.js +83 -0
- package/lib/index.js +1 -1
- package/package.json +1 -1
package/cjs/app/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import Message from "../messages/message.js";
|
|
2
2
|
import Nodes from "./nodes.js";
|
|
3
|
-
import Observer from "./observer.js";
|
|
3
|
+
import Observer from "./observer/top_observer.js";
|
|
4
4
|
import Ticker from "./ticker.js";
|
|
5
|
-
import type { Options as ObserverOptions } from "./observer.js";
|
|
5
|
+
import type { Options as ObserverOptions } from "./observer/top_observer.js";
|
|
6
6
|
import type { Options as WebworkerOptions } from "../messages/webworker.js";
|
|
7
7
|
export interface OnStartInfo {
|
|
8
8
|
sessionID: string;
|
package/cjs/app/index.js
CHANGED
|
@@ -4,7 +4,7 @@ exports.DEFAULT_INGEST_POINT = void 0;
|
|
|
4
4
|
const utils_js_1 = require("../utils.js");
|
|
5
5
|
const index_js_1 = require("../messages/index.js");
|
|
6
6
|
const nodes_js_1 = require("./nodes.js");
|
|
7
|
-
const
|
|
7
|
+
const top_observer_js_1 = require("./observer/top_observer.js");
|
|
8
8
|
const ticker_js_1 = require("./ticker.js");
|
|
9
9
|
const performance_js_1 = require("../modules/performance.js");
|
|
10
10
|
// TODO: use backendHost only
|
|
@@ -17,7 +17,7 @@ class App {
|
|
|
17
17
|
this.commitCallbacks = [];
|
|
18
18
|
this._sessionID = null;
|
|
19
19
|
this.isActive = false;
|
|
20
|
-
this.version = '3.4.
|
|
20
|
+
this.version = '3.4.14';
|
|
21
21
|
this.projectKey = projectKey;
|
|
22
22
|
this.options = Object.assign({
|
|
23
23
|
revID: '',
|
|
@@ -39,7 +39,7 @@ class App {
|
|
|
39
39
|
}
|
|
40
40
|
this.revID = this.options.revID;
|
|
41
41
|
this.nodes = new nodes_js_1.default(this.options.node_id);
|
|
42
|
-
this.observer = new
|
|
42
|
+
this.observer = new top_observer_js_1.default(this, this.options);
|
|
43
43
|
this.ticker = new ticker_js_1.default(this);
|
|
44
44
|
this.ticker.attach(() => this.commit());
|
|
45
45
|
try {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const observer_js_1 = require("./observer.js");
|
|
4
|
+
const index_js_1 = require("../../messages/index.js");
|
|
5
|
+
class IFrameObserver extends observer_js_1.default {
|
|
6
|
+
observe(iframe) {
|
|
7
|
+
const doc = iframe.contentDocument;
|
|
8
|
+
const hostID = this.app.nodes.getID(iframe);
|
|
9
|
+
if (!doc || hostID === undefined) {
|
|
10
|
+
return;
|
|
11
|
+
} //log TODO common app.logger
|
|
12
|
+
// Have to observe document, because the inner <html> might be changed
|
|
13
|
+
this.observeRoot(doc, (docID) => {
|
|
14
|
+
if (docID === undefined) {
|
|
15
|
+
console.log("OpenReplay: Iframe document not bound");
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
this.app.send((0, index_js_1.CreateIFrameDocument)(hostID, docID));
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.default = IFrameObserver;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import App from "
|
|
2
|
-
interface Window extends
|
|
1
|
+
import App from "../index.js";
|
|
2
|
+
export interface Window extends globalThis.Window {
|
|
3
3
|
HTMLInputElement: typeof HTMLInputElement;
|
|
4
4
|
HTMLLinkElement: typeof HTMLLinkElement;
|
|
5
5
|
HTMLStyleElement: typeof HTMLStyleElement;
|
|
@@ -7,27 +7,33 @@ interface Window extends WindowProxy {
|
|
|
7
7
|
HTMLIFrameElement: typeof HTMLIFrameElement;
|
|
8
8
|
Text: typeof Text;
|
|
9
9
|
Element: typeof Element;
|
|
10
|
+
ShadowRoot: typeof ShadowRoot;
|
|
10
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;
|
|
11
18
|
export interface Options {
|
|
12
19
|
obscureTextEmails: boolean;
|
|
13
20
|
obscureTextNumbers: boolean;
|
|
14
|
-
captureIFrames: boolean;
|
|
15
21
|
}
|
|
16
|
-
export default class Observer {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
private readonly context;
|
|
22
|
+
export default abstract class Observer<AdditionalOptions = {}> {
|
|
23
|
+
protected readonly app: App;
|
|
24
|
+
protected readonly context: Window;
|
|
20
25
|
private readonly observer;
|
|
21
26
|
private readonly commited;
|
|
22
27
|
private readonly recents;
|
|
28
|
+
private readonly myNodes;
|
|
23
29
|
private readonly indexes;
|
|
24
30
|
private readonly attributesList;
|
|
25
31
|
private readonly textSet;
|
|
26
32
|
private readonly textMasked;
|
|
27
|
-
|
|
33
|
+
protected readonly options: Options & AdditionalOptions;
|
|
34
|
+
private readonly inUpperContext;
|
|
35
|
+
constructor(app: App, options: Partial<Options> & AdditionalOptions, context?: Window);
|
|
28
36
|
private clear;
|
|
29
|
-
private isInstance;
|
|
30
|
-
private isIgnored;
|
|
31
37
|
private sendNodeAttribute;
|
|
32
38
|
getInnerTextSecure(el: HTMLElement): string;
|
|
33
39
|
private checkObscure;
|
|
@@ -38,10 +44,7 @@ export default class Observer {
|
|
|
38
44
|
private _commitNode;
|
|
39
45
|
private commitNode;
|
|
40
46
|
private commitNodes;
|
|
41
|
-
|
|
42
|
-
private handleIframe;
|
|
43
|
-
private observeIframe;
|
|
44
|
-
observe(): void;
|
|
47
|
+
protected observeRoot(node: Node, beforeCommit: (id?: number) => unknown, nodeToBind?: Node): void;
|
|
45
48
|
disconnect(): void;
|
|
46
49
|
}
|
|
47
50
|
export {};
|
|
@@ -1,42 +1,100 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
|
|
4
|
-
const
|
|
3
|
+
exports.isInstance = void 0;
|
|
4
|
+
const utils_js_1 = require("../../utils.js");
|
|
5
|
+
const index_js_1 = require("../../messages/index.js");
|
|
5
6
|
function isSVGElement(node) {
|
|
6
7
|
return node.namespaceURI === 'http://www.w3.org/2000/svg';
|
|
7
8
|
}
|
|
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
|
+
function isIgnored(node) {
|
|
33
|
+
if (isInstance(node, Text)) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
if (!isInstance(node, Element)) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
const tag = node.tagName.toUpperCase();
|
|
40
|
+
if (tag === 'LINK') {
|
|
41
|
+
const rel = node.getAttribute('rel');
|
|
42
|
+
const as = node.getAttribute('as');
|
|
43
|
+
return !((rel === null || rel === void 0 ? void 0 : rel.includes('stylesheet')) || as === "style" || as === "font");
|
|
44
|
+
}
|
|
45
|
+
return (tag === 'SCRIPT' ||
|
|
46
|
+
tag === 'NOSCRIPT' ||
|
|
47
|
+
tag === 'META' ||
|
|
48
|
+
tag === 'TITLE' ||
|
|
49
|
+
tag === 'BASE');
|
|
50
|
+
}
|
|
51
|
+
function isRootNode(node) {
|
|
52
|
+
return isInstance(node, Document) || isInstance(node, ShadowRoot);
|
|
53
|
+
}
|
|
54
|
+
function isObservable(node) {
|
|
55
|
+
if (isRootNode(node)) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
return !isIgnored(node);
|
|
59
|
+
}
|
|
8
60
|
class Observer {
|
|
9
61
|
constructor(app, options, context = window) {
|
|
10
62
|
this.app = app;
|
|
11
|
-
this.options = options;
|
|
12
63
|
this.context = context;
|
|
13
|
-
this.
|
|
64
|
+
this.commited = [];
|
|
65
|
+
this.recents = [];
|
|
66
|
+
this.myNodes = [];
|
|
67
|
+
this.indexes = [];
|
|
68
|
+
this.attributesList = [];
|
|
69
|
+
this.textSet = new Set();
|
|
70
|
+
this.textMasked = new Set();
|
|
71
|
+
this.options = Object.assign({
|
|
72
|
+
obscureTextEmails: true,
|
|
73
|
+
obscureTextNumbers: false,
|
|
74
|
+
}, options);
|
|
75
|
+
this.inUpperContext = context.parent === context;
|
|
14
76
|
this.observer = new MutationObserver(this.app.safe((mutations) => {
|
|
15
|
-
var _a;
|
|
16
77
|
for (const mutation of mutations) {
|
|
17
78
|
const target = mutation.target;
|
|
18
79
|
const type = mutation.type;
|
|
80
|
+
// TODO TODO TODO: move to iframe_observer/remove??? (check if )
|
|
19
81
|
// Special case
|
|
20
|
-
//
|
|
82
|
+
// 'childList' on Document might happen in case of iframe.
|
|
21
83
|
// TODO: generalize as much as possible
|
|
22
|
-
if (
|
|
23
|
-
|
|
24
|
-
//&& new Array(mutation.addedNodes).some(node =>
|
|
25
|
-
) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
this.app.send((0, index_js_1.CreateIFrameDocument)(frameID, docID));
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
if (this.isIgnored(target) || !context.document.contains(target)) {
|
|
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)) {
|
|
40
98
|
continue;
|
|
41
99
|
}
|
|
42
100
|
if (type === 'childList') {
|
|
@@ -52,7 +110,7 @@ class Observer {
|
|
|
52
110
|
if (id === undefined) {
|
|
53
111
|
continue;
|
|
54
112
|
}
|
|
55
|
-
if (id >= this.recents.length) {
|
|
113
|
+
if (id >= this.recents.length) { // TODO: something more convinient
|
|
56
114
|
this.recents[id] = undefined;
|
|
57
115
|
}
|
|
58
116
|
if (type === 'attributes') {
|
|
@@ -74,12 +132,6 @@ class Observer {
|
|
|
74
132
|
}
|
|
75
133
|
this.commitNodes();
|
|
76
134
|
}));
|
|
77
|
-
this.commited = [];
|
|
78
|
-
this.recents = [];
|
|
79
|
-
this.indexes = [0];
|
|
80
|
-
this.attributesList = [];
|
|
81
|
-
this.textSet = new Set();
|
|
82
|
-
this.textMasked = new Set();
|
|
83
135
|
}
|
|
84
136
|
clear() {
|
|
85
137
|
this.commited.length = 0;
|
|
@@ -87,40 +139,7 @@ class Observer {
|
|
|
87
139
|
this.indexes.length = 1;
|
|
88
140
|
this.attributesList.length = 0;
|
|
89
141
|
this.textSet.clear();
|
|
90
|
-
this.textMasked.clear();
|
|
91
|
-
}
|
|
92
|
-
// TODO: we need a type expert here so we won't have to ignore the lines
|
|
93
|
-
isInstance(node, constr) {
|
|
94
|
-
let context = this.context;
|
|
95
|
-
while (context.parent && context.parent !== context) {
|
|
96
|
-
// @ts-ignore
|
|
97
|
-
if (node instanceof context[constr.name]) {
|
|
98
|
-
return true;
|
|
99
|
-
}
|
|
100
|
-
// @ts-ignore
|
|
101
|
-
context = context.parent;
|
|
102
|
-
}
|
|
103
|
-
// @ts-ignore
|
|
104
|
-
return node instanceof context[constr.name];
|
|
105
|
-
}
|
|
106
|
-
isIgnored(node) {
|
|
107
|
-
if (this.isInstance(node, Text)) {
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
if (!this.isInstance(node, Element)) {
|
|
111
|
-
return true;
|
|
112
|
-
}
|
|
113
|
-
const tag = node.tagName.toUpperCase();
|
|
114
|
-
if (tag === 'LINK') {
|
|
115
|
-
const rel = node.getAttribute('rel');
|
|
116
|
-
const as = node.getAttribute('as');
|
|
117
|
-
return !((rel === null || rel === void 0 ? void 0 : rel.includes('stylesheet')) || as === "style" || as === "font");
|
|
118
|
-
}
|
|
119
|
-
return (tag === 'SCRIPT' ||
|
|
120
|
-
tag === 'NOSCRIPT' ||
|
|
121
|
-
tag === 'META' ||
|
|
122
|
-
tag === 'TITLE' ||
|
|
123
|
-
tag === 'BASE');
|
|
142
|
+
//this.textMasked.clear();
|
|
124
143
|
}
|
|
125
144
|
sendNodeAttribute(id, node, name, value) {
|
|
126
145
|
if (isSVGElement(node)) {
|
|
@@ -150,7 +169,7 @@ class Observer {
|
|
|
150
169
|
return;
|
|
151
170
|
}
|
|
152
171
|
if (name === 'value' &&
|
|
153
|
-
|
|
172
|
+
isInstance(node, HTMLInputElement) &&
|
|
154
173
|
node.type !== 'button' &&
|
|
155
174
|
node.type !== 'reset' &&
|
|
156
175
|
node.type !== 'submit') {
|
|
@@ -160,7 +179,7 @@ class Observer {
|
|
|
160
179
|
this.app.send(new index_js_1.RemoveNodeAttribute(id, name));
|
|
161
180
|
return;
|
|
162
181
|
}
|
|
163
|
-
if (name === 'style' || name === 'href' &&
|
|
182
|
+
if (name === 'style' || name === 'href' && isInstance(node, HTMLLinkElement)) {
|
|
164
183
|
this.app.send(new index_js_1.SetNodeAttributeURLBased(id, name, value, this.app.getBaseHref()));
|
|
165
184
|
return;
|
|
166
185
|
}
|
|
@@ -190,7 +209,7 @@ class Observer {
|
|
|
190
209
|
return data;
|
|
191
210
|
}
|
|
192
211
|
sendNodeData(id, parentElement, data) {
|
|
193
|
-
if (
|
|
212
|
+
if (isInstance(parentElement, HTMLStyleElement) || isInstance(parentElement, SVGStyleElement)) {
|
|
194
213
|
this.app.send(new index_js_1.SetCSSDataURLBased(id, data, this.app.getBaseHref()));
|
|
195
214
|
return;
|
|
196
215
|
}
|
|
@@ -202,14 +221,15 @@ class Observer {
|
|
|
202
221
|
const r = this.app.nodes.registerNode(node);
|
|
203
222
|
const id = r[0];
|
|
204
223
|
this.recents[id] = r[1] || this.recents[id] || false;
|
|
224
|
+
this.myNodes[id] = true;
|
|
205
225
|
}
|
|
206
226
|
bindTree(node) {
|
|
207
|
-
if (
|
|
227
|
+
if (!isObservable(node)) {
|
|
208
228
|
return;
|
|
209
229
|
}
|
|
210
230
|
this.bindNode(node);
|
|
211
231
|
const walker = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_TEXT, {
|
|
212
|
-
acceptNode: (node) =>
|
|
232
|
+
acceptNode: (node) => isIgnored(node) || this.app.nodes.getID(node) !== undefined
|
|
213
233
|
? NodeFilter.FILTER_REJECT
|
|
214
234
|
: NodeFilter.FILTER_ACCEPT,
|
|
215
235
|
},
|
|
@@ -226,12 +246,15 @@ class Observer {
|
|
|
226
246
|
}
|
|
227
247
|
}
|
|
228
248
|
_commitNode(id, node) {
|
|
249
|
+
if (isRootNode(node)) {
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
229
252
|
const parent = node.parentNode;
|
|
230
253
|
let parentID;
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
254
|
+
// Disable parent check for the upper context HTMLHtmlElement, because it is root there... (before)
|
|
255
|
+
// TODO: get rid of "special" cases (there is an issue with CreateDocument altered behaviour though)
|
|
256
|
+
// TODO: Clean the logic (though now it workd fine)
|
|
257
|
+
if (!isInstance(node, HTMLHtmlElement) || !this.inUpperContext) {
|
|
235
258
|
if (parent === null) {
|
|
236
259
|
this.unbindNode(node);
|
|
237
260
|
return false;
|
|
@@ -246,22 +269,22 @@ class Observer {
|
|
|
246
269
|
return false;
|
|
247
270
|
}
|
|
248
271
|
if (this.textMasked.has(parentID) ||
|
|
249
|
-
(
|
|
272
|
+
(isInstance(node, Element) && (0, utils_js_1.hasOpenreplayAttribute)(node, 'masked'))) {
|
|
250
273
|
this.textMasked.add(id);
|
|
251
274
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
sibling = sibling.previousSibling;
|
|
261
|
-
}
|
|
262
|
-
if (sibling === null) {
|
|
263
|
-
this.indexes[id] = 0;
|
|
275
|
+
}
|
|
276
|
+
let sibling = node.previousSibling;
|
|
277
|
+
while (sibling !== null) {
|
|
278
|
+
const siblingID = this.app.nodes.getID(sibling);
|
|
279
|
+
if (siblingID !== undefined) {
|
|
280
|
+
this.commitNode(siblingID);
|
|
281
|
+
this.indexes[id] = this.indexes[siblingID] + 1;
|
|
282
|
+
break;
|
|
264
283
|
}
|
|
284
|
+
sibling = sibling.previousSibling;
|
|
285
|
+
}
|
|
286
|
+
if (sibling === null) {
|
|
287
|
+
this.indexes[id] = 0; //
|
|
265
288
|
}
|
|
266
289
|
const isNew = this.recents[id];
|
|
267
290
|
const index = this.indexes[id];
|
|
@@ -269,7 +292,7 @@ class Observer {
|
|
|
269
292
|
throw 'commitNode: missing node index';
|
|
270
293
|
}
|
|
271
294
|
if (isNew === true) {
|
|
272
|
-
if (
|
|
295
|
+
if (isInstance(node, Element)) {
|
|
273
296
|
if (parentID !== undefined) {
|
|
274
297
|
this.app.send(new index_js_1.CreateElementNode(id, parentID, index, node.tagName, isSVGElement(node)));
|
|
275
298
|
}
|
|
@@ -277,12 +300,8 @@ class Observer {
|
|
|
277
300
|
const attr = node.attributes[i];
|
|
278
301
|
this.sendNodeAttribute(id, node, attr.nodeName, attr.value);
|
|
279
302
|
}
|
|
280
|
-
if (this.isInstance(node, HTMLIFrameElement) &&
|
|
281
|
-
(this.options.captureIFrames || node.getAttribute("data-openreplay-capture"))) {
|
|
282
|
-
this.handleIframe(node);
|
|
283
|
-
}
|
|
284
303
|
}
|
|
285
|
-
else if (
|
|
304
|
+
else if (isInstance(node, Text)) {
|
|
286
305
|
// for text node id != 0, hence parentID !== undefined and parent is Element
|
|
287
306
|
this.app.send(new index_js_1.CreateTextNode(id, parentID, index));
|
|
288
307
|
this.sendNodeData(id, parent, node.data);
|
|
@@ -294,7 +313,7 @@ class Observer {
|
|
|
294
313
|
}
|
|
295
314
|
const attr = this.attributesList[id];
|
|
296
315
|
if (attr !== undefined) {
|
|
297
|
-
if (!
|
|
316
|
+
if (!isInstance(node, Element)) {
|
|
298
317
|
throw 'commitNode: node is not an element';
|
|
299
318
|
}
|
|
300
319
|
for (const name of attr) {
|
|
@@ -302,7 +321,7 @@ class Observer {
|
|
|
302
321
|
}
|
|
303
322
|
}
|
|
304
323
|
if (this.textSet.has(id)) {
|
|
305
|
-
if (!
|
|
324
|
+
if (!isInstance(node, Text)) {
|
|
306
325
|
throw 'commitNode: node is not a text';
|
|
307
326
|
}
|
|
308
327
|
// for text node id != 0, hence parent is Element
|
|
@@ -324,6 +343,12 @@ class Observer {
|
|
|
324
343
|
commitNodes() {
|
|
325
344
|
let node;
|
|
326
345
|
for (let id = 0; id < this.recents.length; id++) {
|
|
346
|
+
// 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
|
|
349
|
+
if (!this.myNodes[id]) {
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
327
352
|
this.commitNode(id);
|
|
328
353
|
if (this.recents[id] === true && (node = this.app.nodes.getNode(id))) {
|
|
329
354
|
this.app.nodes.callNodeCallbacks(node);
|
|
@@ -331,31 +356,9 @@ class Observer {
|
|
|
331
356
|
}
|
|
332
357
|
this.clear();
|
|
333
358
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const id = this.app.nodes.getID(iframe);
|
|
338
|
-
if (id === undefined) {
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
if (iframe.contentWindow === context) {
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
context = iframe.contentWindow;
|
|
345
|
-
if (!context) {
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
const observer = new Observer(this.app, this.options, context);
|
|
349
|
-
this.iframeObservers.push(observer);
|
|
350
|
-
observer.observeIframe(id, context);
|
|
351
|
-
});
|
|
352
|
-
this.app.attachEventListener(iframe, "load", handle);
|
|
353
|
-
handle();
|
|
354
|
-
}
|
|
355
|
-
// TODO: abstract common functionality, separate FrameObserver
|
|
356
|
-
observeIframe(id, context) {
|
|
357
|
-
const doc = context.document;
|
|
358
|
-
this.observer.observe(doc, {
|
|
359
|
+
// ISSSUE
|
|
360
|
+
observeRoot(node, beforeCommit, nodeToBind = node) {
|
|
361
|
+
this.observer.observe(node, {
|
|
359
362
|
childList: true,
|
|
360
363
|
attributes: true,
|
|
361
364
|
characterData: true,
|
|
@@ -363,33 +366,16 @@ class Observer {
|
|
|
363
366
|
attributeOldValue: false,
|
|
364
367
|
characterDataOldValue: false,
|
|
365
368
|
});
|
|
366
|
-
this.bindTree(
|
|
367
|
-
|
|
368
|
-
if (docID === undefined) {
|
|
369
|
-
console.log("Wrong");
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
|
-
this.app.send((0, index_js_1.CreateIFrameDocument)(id, docID));
|
|
373
|
-
this.commitNodes();
|
|
374
|
-
}
|
|
375
|
-
observe() {
|
|
376
|
-
this.observer.observe(this.context.document, {
|
|
377
|
-
childList: true,
|
|
378
|
-
attributes: true,
|
|
379
|
-
characterData: true,
|
|
380
|
-
subtree: true,
|
|
381
|
-
attributeOldValue: false,
|
|
382
|
-
characterDataOldValue: false,
|
|
383
|
-
});
|
|
384
|
-
this.app.send(new index_js_1.CreateDocument());
|
|
385
|
-
this.bindTree(this.context.document.documentElement);
|
|
369
|
+
this.bindTree(nodeToBind);
|
|
370
|
+
beforeCommit(this.app.nodes.getID(node));
|
|
386
371
|
this.commitNodes();
|
|
387
372
|
}
|
|
388
373
|
disconnect() {
|
|
389
|
-
this.iframeObservers.forEach(o => o.disconnect());
|
|
390
|
-
this.iframeObservers = [];
|
|
391
374
|
this.observer.disconnect();
|
|
392
375
|
this.clear();
|
|
376
|
+
// to sanitizer
|
|
377
|
+
this.textMasked.clear();
|
|
378
|
+
this.myNodes.length = 0;
|
|
393
379
|
}
|
|
394
380
|
}
|
|
395
381
|
exports.default = Observer;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const observer_js_1 = require("./observer.js");
|
|
4
|
+
const index_js_1 = require("../../messages/index.js");
|
|
5
|
+
class ShadowRootObserver extends observer_js_1.default {
|
|
6
|
+
observe(el) {
|
|
7
|
+
const shRoot = el.shadowRoot;
|
|
8
|
+
const hostID = this.app.nodes.getID(el);
|
|
9
|
+
if (!shRoot || hostID === undefined) {
|
|
10
|
+
return;
|
|
11
|
+
} // log
|
|
12
|
+
this.observeRoot(shRoot, (rootID) => {
|
|
13
|
+
if (rootID === undefined) {
|
|
14
|
+
console.log("OpenReplay: Shadow Root was not bound");
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
this.app.send((0, index_js_1.CreateIFrameDocument)(hostID, rootID));
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
exports.default = ShadowRootObserver;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import Observer from "./observer.js";
|
|
2
|
+
import type { Options as BaseOptions } from "./observer.js";
|
|
3
|
+
import App from "../index.js";
|
|
4
|
+
export interface Options extends Partial<BaseOptions> {
|
|
5
|
+
captureIFrames: boolean;
|
|
6
|
+
}
|
|
7
|
+
export default class TopObserver extends Observer<Options> {
|
|
8
|
+
constructor(app: App, options: Partial<Options>);
|
|
9
|
+
private iframeObservers;
|
|
10
|
+
private handleIframe;
|
|
11
|
+
private shadowRootObservers;
|
|
12
|
+
private handleShadowRoot;
|
|
13
|
+
observe(): void;
|
|
14
|
+
disconnect(): void;
|
|
15
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const observer_js_1 = require("./observer.js");
|
|
4
|
+
const iframe_observer_js_1 = require("./iframe_observer.js");
|
|
5
|
+
const shadow_root_observer_js_1 = require("./shadow_root_observer.js");
|
|
6
|
+
const index_js_1 = require("../../messages/index.js");
|
|
7
|
+
const attachShadowNativeFn = Element.prototype.attachShadow;
|
|
8
|
+
class TopObserver extends observer_js_1.default {
|
|
9
|
+
constructor(app, options) {
|
|
10
|
+
super(app, Object.assign({
|
|
11
|
+
captureIFrames: false
|
|
12
|
+
}, options));
|
|
13
|
+
this.iframeObservers = [];
|
|
14
|
+
this.shadowRootObservers = [];
|
|
15
|
+
// IFrames
|
|
16
|
+
this.app.nodes.attachNodeCallback(node => {
|
|
17
|
+
if ((0, observer_js_1.isInstance)(node, HTMLIFrameElement) &&
|
|
18
|
+
(this.options.captureIFrames || node.getAttribute("data-openreplay-capture"))) {
|
|
19
|
+
this.handleIframe(node);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
// ShadowDOM
|
|
23
|
+
this.app.nodes.attachNodeCallback(node => {
|
|
24
|
+
if ((0, observer_js_1.isInstance)(node, Element) && node.shadowRoot !== null) {
|
|
25
|
+
this.handleShadowRoot(node.shadowRoot);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
handleIframe(iframe) {
|
|
30
|
+
let context = null;
|
|
31
|
+
const handle = this.app.safe(() => {
|
|
32
|
+
const id = this.app.nodes.getID(iframe);
|
|
33
|
+
if (id === undefined) {
|
|
34
|
+
return;
|
|
35
|
+
} //log
|
|
36
|
+
if (iframe.contentWindow === context) {
|
|
37
|
+
return;
|
|
38
|
+
} //Does this happen frequently?
|
|
39
|
+
context = iframe.contentWindow;
|
|
40
|
+
if (!context) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const observer = new iframe_observer_js_1.default(this.app, this.options, context);
|
|
44
|
+
// @ts-ignore
|
|
45
|
+
observer.commited = this.commited;
|
|
46
|
+
// @ts-ignore
|
|
47
|
+
//observers.recents = this.recents
|
|
48
|
+
this.iframeObservers.push(observer);
|
|
49
|
+
observer.observe(iframe);
|
|
50
|
+
});
|
|
51
|
+
this.app.attachEventListener(iframe, "load", handle);
|
|
52
|
+
handle();
|
|
53
|
+
}
|
|
54
|
+
handleShadowRoot(shRoot) {
|
|
55
|
+
const observer = new shadow_root_observer_js_1.default(this.app, this.options, this.context);
|
|
56
|
+
this.shadowRootObservers.push(observer);
|
|
57
|
+
observer.observe(shRoot.host);
|
|
58
|
+
}
|
|
59
|
+
observe() {
|
|
60
|
+
// Protection from several subsequent calls?
|
|
61
|
+
const observer = this;
|
|
62
|
+
Element.prototype.attachShadow = function () {
|
|
63
|
+
const shadow = attachShadowNativeFn.apply(this, arguments);
|
|
64
|
+
observer.handleShadowRoot(shadow);
|
|
65
|
+
return shadow;
|
|
66
|
+
};
|
|
67
|
+
// Can observe documentElement (<html>) here, because it is not supposed to be changing.
|
|
68
|
+
// However, it is possible in some exotic cases and may cause an ignorance of the newly created <html>
|
|
69
|
+
// In this case context.document have to be observed, but this will cause
|
|
70
|
+
// the change in the re-player behaviour caused by CreateDocument message:
|
|
71
|
+
// the 0-node ("fRoot") will become #document rather than documentElement as it is now.
|
|
72
|
+
// Alternatively - observe(#document) then bindNode(documentElement)
|
|
73
|
+
this.observeRoot(this.context.document, () => {
|
|
74
|
+
this.app.send(new index_js_1.CreateDocument());
|
|
75
|
+
}, this.context.document.documentElement);
|
|
76
|
+
}
|
|
77
|
+
disconnect() {
|
|
78
|
+
Element.prototype.attachShadow = attachShadowNativeFn;
|
|
79
|
+
this.iframeObservers.forEach(o => o.disconnect());
|
|
80
|
+
this.iframeObservers = [];
|
|
81
|
+
this.shadowRootObservers.forEach(o => o.disconnect());
|
|
82
|
+
this.shadowRootObservers = [];
|
|
83
|
+
super.disconnect();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
exports.default = TopObserver;
|