@openreplay/tracker 4.0.1 → 4.0.2
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/guards.d.ts +1 -0
- package/cjs/app/guards.js +6 -1
- package/cjs/app/index.d.ts +1 -1
- package/cjs/app/index.js +16 -10
- package/cjs/app/messages.gen.js +1 -0
- package/cjs/app/nodes.d.ts +1 -1
- package/cjs/app/nodes.js +3 -5
- package/cjs/app/observer/iframe_observer.js +1 -0
- package/cjs/app/observer/iframe_offsets.d.ts +8 -0
- package/cjs/app/observer/iframe_offsets.js +59 -0
- package/cjs/app/observer/observer.js +4 -4
- package/cjs/app/observer/top_observer.d.ts +2 -4
- package/cjs/app/observer/top_observer.js +11 -21
- package/cjs/app/sanitizer.d.ts +10 -4
- package/cjs/app/sanitizer.js +33 -15
- package/cjs/app/session.js +1 -1
- package/cjs/common/messages.gen.js +1 -0
- package/cjs/index.d.ts +1 -0
- package/cjs/index.js +7 -7
- package/cjs/modules/constructedStyleSheets.d.ts +4 -0
- package/cjs/modules/{adoptedStyleSheets.js → constructedStyleSheets.js} +21 -20
- package/cjs/modules/cssrules.js +65 -18
- package/cjs/modules/img.js +27 -19
- package/cjs/modules/input.js +2 -2
- package/cjs/modules/mouse.js +11 -7
- package/cjs/modules/scroll.js +32 -12
- package/cjs/utils.d.ts +5 -3
- package/cjs/utils.js +18 -13
- package/lib/app/guards.d.ts +1 -0
- package/lib/app/guards.js +4 -0
- package/lib/app/index.d.ts +1 -1
- package/lib/app/index.js +16 -10
- package/lib/app/messages.gen.js +1 -0
- package/lib/app/nodes.d.ts +1 -1
- package/lib/app/nodes.js +3 -5
- package/lib/app/observer/iframe_observer.js +1 -0
- package/lib/app/observer/iframe_offsets.d.ts +8 -0
- package/lib/app/observer/iframe_offsets.js +56 -0
- package/lib/app/observer/observer.js +4 -4
- package/lib/app/observer/top_observer.d.ts +2 -4
- package/lib/app/observer/top_observer.js +11 -21
- package/lib/app/sanitizer.d.ts +10 -4
- package/lib/app/sanitizer.js +32 -15
- package/lib/app/session.js +1 -1
- package/lib/common/messages.gen.js +1 -0
- package/lib/common/tsconfig.tsbuildinfo +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +5 -6
- package/lib/modules/constructedStyleSheets.d.ts +4 -0
- package/lib/modules/{adoptedStyleSheets.js → constructedStyleSheets.js} +20 -21
- package/lib/modules/cssrules.js +67 -19
- package/lib/modules/img.js +28 -20
- package/lib/modules/input.js +3 -3
- package/lib/modules/mouse.js +11 -7
- package/lib/modules/scroll.js +33 -13
- package/lib/utils.d.ts +5 -3
- package/lib/utils.js +17 -11
- package/package.json +1 -1
- package/cjs/modules/adoptedStyleSheets.d.ts +0 -2
- package/lib/modules/adoptedStyleSheets.d.ts +0 -2
package/lib/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export { default as App } from './app/index.js';
|
|
|
3
3
|
import { UserAnonymousID, RawCustomEvent, CustomIssue } from './app/messages.gen.js';
|
|
4
4
|
import * as _Messages from './app/messages.gen.js';
|
|
5
5
|
export const Messages = _Messages;
|
|
6
|
+
export { SanitizeLevel } from './app/sanitizer.js';
|
|
6
7
|
import Connection from './modules/connection.js';
|
|
7
8
|
import Console from './modules/console.js';
|
|
8
9
|
import Exception, { getExceptionMessageFromEvent, getExceptionMessage, } from './modules/exception.js';
|
|
@@ -14,7 +15,7 @@ import Performance from './modules/performance.js';
|
|
|
14
15
|
import Scroll from './modules/scroll.js';
|
|
15
16
|
import Viewport from './modules/viewport.js';
|
|
16
17
|
import CSSRules from './modules/cssrules.js';
|
|
17
|
-
import
|
|
18
|
+
import ConstructedStyleSheets from './modules/constructedStyleSheets.js';
|
|
18
19
|
import { IN_BROWSER, deprecationWarn, DOCS_HOST } from './utils.js';
|
|
19
20
|
const DOCS_SETUP = '/installation/setup-or';
|
|
20
21
|
function processOptions(obj) {
|
|
@@ -93,7 +94,7 @@ export default class API {
|
|
|
93
94
|
if (app !== null) {
|
|
94
95
|
Viewport(app);
|
|
95
96
|
CSSRules(app);
|
|
96
|
-
|
|
97
|
+
ConstructedStyleSheets(app);
|
|
97
98
|
Connection(app);
|
|
98
99
|
Console(app, options);
|
|
99
100
|
Exception(app, options);
|
|
@@ -127,7 +128,7 @@ export default class API {
|
|
|
127
128
|
// no-cors issue only with text/plain or not-set Content-Type
|
|
128
129
|
// req.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
|
|
129
130
|
req.send(JSON.stringify({
|
|
130
|
-
trackerVersion: '4.0.
|
|
131
|
+
trackerVersion: '4.0.2',
|
|
131
132
|
projectKey: options.projectKey,
|
|
132
133
|
doNotTrack,
|
|
133
134
|
// TODO: add precise reason (an exact API missing)
|
|
@@ -159,9 +160,7 @@ export default class API {
|
|
|
159
160
|
return;
|
|
160
161
|
}
|
|
161
162
|
this.app.stop();
|
|
162
|
-
|
|
163
|
-
this.app.session.reset();
|
|
164
|
-
return sessionHash;
|
|
163
|
+
return this.app.session.getSessionHash();
|
|
165
164
|
}
|
|
166
165
|
getSessionToken() {
|
|
167
166
|
if (this.app === null) {
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
import { TechnicalInfo, AdoptedSSReplaceURLBased, AdoptedSSInsertRuleURLBased,
|
|
1
|
+
import { TechnicalInfo, AdoptedSSReplaceURLBased, AdoptedSSInsertRuleURLBased, AdoptedSSAddOwner, AdoptedSSRemoveOwner, } from '../app/messages.gen.js';
|
|
2
2
|
import { isRootNode } from '../app/guards.js';
|
|
3
3
|
function hasAdoptedSS(node) {
|
|
4
4
|
return (isRootNode(node) &&
|
|
5
5
|
// @ts-ignore
|
|
6
6
|
!!node.adoptedStyleSheets);
|
|
7
7
|
}
|
|
8
|
+
// TODO: incapsulate to be init-ed on-start and join with cssrules.ts under one folder
|
|
9
|
+
let _id = 0xf;
|
|
10
|
+
export function nextID() {
|
|
11
|
+
return _id++;
|
|
12
|
+
}
|
|
13
|
+
export const styleSheetIDMap = new Map();
|
|
8
14
|
export default function (app) {
|
|
9
15
|
if (app === null) {
|
|
10
16
|
return;
|
|
@@ -16,10 +22,9 @@ export default function (app) {
|
|
|
16
22
|
});
|
|
17
23
|
return;
|
|
18
24
|
}
|
|
19
|
-
let nextID = 0xf;
|
|
20
25
|
const styleSheetIDMap = new Map();
|
|
21
26
|
const adoptedStyleSheetsOwnings = new Map();
|
|
22
|
-
const
|
|
27
|
+
const sendAdoptedStyleSheetsUpdate = (root) => {
|
|
23
28
|
let nodeID = app.nodes.getID(root);
|
|
24
29
|
if (root === document) {
|
|
25
30
|
nodeID = 0; // main document doesn't have nodeID. ID count starts from the documentElement
|
|
@@ -37,7 +42,7 @@ export default function (app) {
|
|
|
37
42
|
let sheetID = styleSheetIDMap.get(s);
|
|
38
43
|
const init = !sheetID;
|
|
39
44
|
if (!sheetID) {
|
|
40
|
-
sheetID =
|
|
45
|
+
sheetID = nextID();
|
|
41
46
|
}
|
|
42
47
|
nowOwning.push(sheetID);
|
|
43
48
|
if (!pastOwning.includes(sheetID)) {
|
|
@@ -63,12 +68,20 @@ export default function (app) {
|
|
|
63
68
|
Object.defineProperty(prototype, 'adoptedStyleSheets', Object.assign(Object.assign({}, nativeAdoptedStyleSheetsDescriptor), { set: function (value) {
|
|
64
69
|
// @ts-ignore
|
|
65
70
|
const retVal = nativeAdoptedStyleSheetsDescriptor.set.call(this, value);
|
|
66
|
-
|
|
71
|
+
sendAdoptedStyleSheetsUpdate(this);
|
|
67
72
|
return retVal;
|
|
68
73
|
} }));
|
|
69
74
|
}
|
|
70
75
|
}
|
|
71
76
|
const patchContext = (context) => {
|
|
77
|
+
// @ts-ignore
|
|
78
|
+
if (context.__openreplay_adpss_patched__) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
// @ts-ignore
|
|
83
|
+
context.__openreplay_adpss_patched__ = true;
|
|
84
|
+
}
|
|
72
85
|
patchAdoptedStyleSheets(context.Document.prototype);
|
|
73
86
|
patchAdoptedStyleSheets(context.ShadowRoot.prototype);
|
|
74
87
|
//@ts-ignore TODO: configure ts (use necessary lib)
|
|
@@ -91,20 +104,6 @@ export default function (app) {
|
|
|
91
104
|
}
|
|
92
105
|
return replaceSync.call(this, text);
|
|
93
106
|
};
|
|
94
|
-
context.CSSStyleSheet.prototype.insertRule = function (rule, index = 0) {
|
|
95
|
-
const sheetID = styleSheetIDMap.get(this);
|
|
96
|
-
if (sheetID) {
|
|
97
|
-
app.send(AdoptedSSInsertRuleURLBased(sheetID, rule, index, app.getBaseHref()));
|
|
98
|
-
}
|
|
99
|
-
return insertRule.call(this, rule, index);
|
|
100
|
-
};
|
|
101
|
-
context.CSSStyleSheet.prototype.deleteRule = function (index) {
|
|
102
|
-
const sheetID = styleSheetIDMap.get(this);
|
|
103
|
-
if (sheetID) {
|
|
104
|
-
app.send(AdoptedSSDeleteRule(sheetID, index));
|
|
105
|
-
}
|
|
106
|
-
return deleteRule.call(this, index);
|
|
107
|
-
};
|
|
108
107
|
};
|
|
109
108
|
patchContext(window);
|
|
110
109
|
app.observer.attachContextCallback(patchContext);
|
|
@@ -114,11 +113,11 @@ export default function (app) {
|
|
|
114
113
|
});
|
|
115
114
|
// So far main Document is not triggered with nodeCallbacks
|
|
116
115
|
app.attachStartCallback(() => {
|
|
117
|
-
|
|
116
|
+
sendAdoptedStyleSheetsUpdate(document);
|
|
118
117
|
});
|
|
119
118
|
app.nodes.attachNodeCallback((node) => {
|
|
120
119
|
if (hasAdoptedSS(node)) {
|
|
121
|
-
|
|
120
|
+
sendAdoptedStyleSheetsUpdate(node);
|
|
122
121
|
}
|
|
123
122
|
});
|
|
124
123
|
}
|
package/lib/modules/cssrules.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AdoptedSSInsertRuleURLBased, // TODO: rename to common StyleSheet names
|
|
2
|
+
AdoptedSSDeleteRule, AdoptedSSAddOwner, TechnicalInfo, } from '../app/messages.gen.js';
|
|
2
3
|
import { hasTag } from '../app/guards.js';
|
|
4
|
+
import { nextID, styleSheetIDMap } from './constructedStyleSheets.js';
|
|
3
5
|
export default function (app) {
|
|
4
6
|
if (app === null) {
|
|
5
7
|
return;
|
|
@@ -8,42 +10,88 @@ export default function (app) {
|
|
|
8
10
|
app.send(TechnicalInfo('no_stylesheet_prototype_in_window', ''));
|
|
9
11
|
return;
|
|
10
12
|
}
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
13
|
+
const sendInserDeleteRule = app.safe((sheet, index, rule) => {
|
|
14
|
+
const sheetID = styleSheetIDMap.get(sheet);
|
|
15
|
+
if (!sheetID) {
|
|
16
|
+
// OK-case. Sheet haven't been registered yet. Rules will be sent on registration.
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (typeof rule === 'string') {
|
|
20
|
+
app.send(AdoptedSSInsertRuleURLBased(sheetID, rule, index, app.getBaseHref()));
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
app.send(AdoptedSSDeleteRule(sheetID, index));
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
// TODO: proper rule insertion/removal (how?)
|
|
27
|
+
const sendReplaceGroupingRule = app.safe((rule) => {
|
|
28
|
+
let topmostRule = rule;
|
|
29
|
+
while (topmostRule.parentRule) {
|
|
30
|
+
topmostRule = topmostRule.parentRule;
|
|
31
|
+
}
|
|
32
|
+
const sheet = topmostRule.parentStyleSheet;
|
|
33
|
+
if (!sheet) {
|
|
34
|
+
app.debug.warn('No parent StyleSheet found for', topmostRule, rule);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const sheetID = styleSheetIDMap.get(sheet);
|
|
38
|
+
if (!sheetID) {
|
|
39
|
+
app.debug.warn('No sheedID found for', sheet, styleSheetIDMap);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const cssText = topmostRule.cssText;
|
|
43
|
+
const ruleList = sheet.cssRules;
|
|
44
|
+
const idx = Array.from(ruleList).indexOf(topmostRule);
|
|
45
|
+
if (idx >= 0) {
|
|
46
|
+
app.send(AdoptedSSInsertRuleURLBased(sheetID, cssText, idx, app.getBaseHref()));
|
|
47
|
+
app.send(AdoptedSSDeleteRule(sheetID, idx + 1)); // Remove previous clone
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
app.debug.warn('Rule index not found in', sheet, topmostRule);
|
|
51
|
+
}
|
|
23
52
|
});
|
|
24
53
|
const patchContext = (context) => {
|
|
25
54
|
const { insertRule, deleteRule } = context.CSSStyleSheet.prototype;
|
|
55
|
+
const { insertRule: groupInsertRule, deleteRule: groupDeleteRule } = context.CSSGroupingRule.prototype;
|
|
26
56
|
context.CSSStyleSheet.prototype.insertRule = function (rule, index = 0) {
|
|
27
|
-
|
|
57
|
+
sendInserDeleteRule(this, index, rule);
|
|
28
58
|
return insertRule.call(this, rule, index);
|
|
29
59
|
};
|
|
30
60
|
context.CSSStyleSheet.prototype.deleteRule = function (index) {
|
|
31
|
-
|
|
61
|
+
sendInserDeleteRule(this, index);
|
|
32
62
|
return deleteRule.call(this, index);
|
|
33
63
|
};
|
|
64
|
+
context.CSSGroupingRule.prototype.insertRule = function (rule, index = 0) {
|
|
65
|
+
const result = groupInsertRule.call(this, rule, index);
|
|
66
|
+
sendReplaceGroupingRule(this);
|
|
67
|
+
return result;
|
|
68
|
+
};
|
|
69
|
+
context.CSSGroupingRule.prototype.deleteRule = function (index = 0) {
|
|
70
|
+
const result = groupDeleteRule.call(this, index);
|
|
71
|
+
sendReplaceGroupingRule(this);
|
|
72
|
+
return result;
|
|
73
|
+
};
|
|
34
74
|
};
|
|
35
75
|
patchContext(window);
|
|
36
76
|
app.observer.attachContextCallback(patchContext);
|
|
37
77
|
app.nodes.attachNodeCallback((node) => {
|
|
38
|
-
if (!hasTag(node, 'STYLE') || !node.sheet) {
|
|
78
|
+
if (!(hasTag(node, 'STYLE') || hasTag(node, 'style')) || !node.sheet) {
|
|
39
79
|
return;
|
|
40
80
|
}
|
|
41
81
|
if (node.textContent !== null && node.textContent.trim().length > 0) {
|
|
42
|
-
return; //
|
|
82
|
+
return; // Non-virtual styles captured by the observer as a text
|
|
83
|
+
}
|
|
84
|
+
const nodeID = app.nodes.getID(node);
|
|
85
|
+
if (!nodeID) {
|
|
86
|
+
return;
|
|
43
87
|
}
|
|
44
|
-
const
|
|
88
|
+
const sheet = node.sheet;
|
|
89
|
+
const sheetID = nextID();
|
|
90
|
+
styleSheetIDMap.set(sheet, sheetID);
|
|
91
|
+
app.send(AdoptedSSAddOwner(sheetID, nodeID));
|
|
92
|
+
const rules = sheet.cssRules;
|
|
45
93
|
for (let i = 0; i < rules.length; i++) {
|
|
46
|
-
|
|
94
|
+
sendInserDeleteRule(sheet, i, rules[i].cssText);
|
|
47
95
|
}
|
|
48
96
|
});
|
|
49
97
|
}
|
package/lib/modules/img.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { timestamp, isURL } from '../utils.js';
|
|
1
|
+
import { timestamp, isURL, IS_FIREFOX, MAX_STR_LEN } from '../utils.js';
|
|
2
2
|
import { ResourceTiming, SetNodeAttributeURLBased, SetNodeAttribute } from '../app/messages.gen.js';
|
|
3
3
|
import { hasTag } from '../app/guards.js';
|
|
4
4
|
function resolveURL(url, location = document.location) {
|
|
@@ -8,7 +8,7 @@ function resolveURL(url, location = document.location) {
|
|
|
8
8
|
}
|
|
9
9
|
else if (url.startsWith('http://') ||
|
|
10
10
|
url.startsWith('https://') ||
|
|
11
|
-
url.startsWith('data:') // any other possible value here?
|
|
11
|
+
url.startsWith('data:') // any other possible value here? https://bugzilla.mozilla.org/show_bug.cgi?id=1758035
|
|
12
12
|
) {
|
|
13
13
|
return url;
|
|
14
14
|
}
|
|
@@ -16,6 +16,10 @@ function resolveURL(url, location = document.location) {
|
|
|
16
16
|
return location.origin + location.pathname + url;
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
+
// https://bugzilla.mozilla.org/show_bug.cgi?id=1607081
|
|
20
|
+
function isSVGInFireFox(url) {
|
|
21
|
+
return IS_FIREFOX && (url.startsWith('data:image/svg+xml') || url.match(/.svg$|/i));
|
|
22
|
+
}
|
|
19
23
|
const PLACEHOLDER_SRC = 'https://static.openreplay.com/tracker/placeholder.jpeg';
|
|
20
24
|
export default function (app) {
|
|
21
25
|
function sendPlaceholder(id, node) {
|
|
@@ -40,30 +44,34 @@ export default function (app) {
|
|
|
40
44
|
app.send(SetNodeAttribute(id, 'srcset', resolvedSrcset));
|
|
41
45
|
};
|
|
42
46
|
const sendSrc = function (id, img) {
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
if (img.src.length > MAX_STR_LEN) {
|
|
48
|
+
sendPlaceholder(id, img);
|
|
49
|
+
}
|
|
50
|
+
app.send(SetNodeAttributeURLBased(id, 'src', img.src, app.getBaseHref()));
|
|
45
51
|
};
|
|
46
|
-
const
|
|
47
|
-
const
|
|
52
|
+
const sendImgError = app.safe(function (img) {
|
|
53
|
+
const resolvedSrc = resolveURL(img.src || ''); // Src type is null sometimes. - is it true?
|
|
54
|
+
if (isURL(resolvedSrc)) {
|
|
55
|
+
app.send(ResourceTiming(timestamp(), 0, 0, 0, 0, 0, resolvedSrc, 'img'));
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
const sendImgAttrs = app.safe(function (img) {
|
|
59
|
+
const id = app.nodes.getID(img);
|
|
48
60
|
if (id === undefined) {
|
|
49
61
|
return;
|
|
50
62
|
}
|
|
51
|
-
|
|
52
|
-
if (!complete) {
|
|
63
|
+
if (!img.complete) {
|
|
53
64
|
return;
|
|
54
65
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (isURL(resolvedSrc)) {
|
|
58
|
-
app.send(ResourceTiming(timestamp(), 0, 0, 0, 0, 0, resolvedSrc, 'img'));
|
|
59
|
-
}
|
|
66
|
+
if (img.naturalHeight === 0 && img.naturalWidth === 0 && !isSVGInFireFox(img.src)) {
|
|
67
|
+
sendImgError(img);
|
|
60
68
|
}
|
|
61
|
-
else if (
|
|
62
|
-
sendPlaceholder(id,
|
|
69
|
+
else if (app.sanitizer.isHidden(id) || app.sanitizer.isObscured(id)) {
|
|
70
|
+
sendPlaceholder(id, img);
|
|
63
71
|
}
|
|
64
72
|
else {
|
|
65
|
-
sendSrc(id,
|
|
66
|
-
sendSrcset(id,
|
|
73
|
+
sendSrc(id, img);
|
|
74
|
+
sendSrcset(id, img);
|
|
67
75
|
}
|
|
68
76
|
});
|
|
69
77
|
const observer = new MutationObserver((mutations) => {
|
|
@@ -90,9 +98,9 @@ export default function (app) {
|
|
|
90
98
|
if (!hasTag(node, 'IMG')) {
|
|
91
99
|
return;
|
|
92
100
|
}
|
|
93
|
-
app.nodes.
|
|
94
|
-
app.nodes.
|
|
95
|
-
sendImgAttrs
|
|
101
|
+
app.nodes.attachNodeListener(node, 'error', () => sendImgError(node));
|
|
102
|
+
app.nodes.attachNodeListener(node, 'load', () => sendImgAttrs(node));
|
|
103
|
+
sendImgAttrs(node);
|
|
96
104
|
observer.observe(node, { attributes: true, attributeFilter: ['src', 'srcset'] });
|
|
97
105
|
});
|
|
98
106
|
}
|
package/lib/modules/input.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { normSpaces, IN_BROWSER, getLabelAttribute
|
|
1
|
+
import { normSpaces, IN_BROWSER, getLabelAttribute } from '../utils.js';
|
|
2
2
|
import { hasTag } from '../app/guards.js';
|
|
3
3
|
import { SetInputTarget, SetInputValue, SetInputChecked } from '../app/messages.gen.js';
|
|
4
4
|
const INPUT_TYPES = ['text', 'password', 'email', 'search', 'number', 'range', 'date'];
|
|
@@ -76,10 +76,10 @@ export default function (app, opts) {
|
|
|
76
76
|
function sendInputValue(id, node) {
|
|
77
77
|
let value = node.value;
|
|
78
78
|
let inputMode = options.defaultInputMode;
|
|
79
|
-
if (node.type === 'password' ||
|
|
79
|
+
if (node.type === 'password' || app.sanitizer.isHidden(id)) {
|
|
80
80
|
inputMode = 2 /* Hidden */;
|
|
81
81
|
}
|
|
82
|
-
else if (
|
|
82
|
+
else if (app.sanitizer.isObscured(id) ||
|
|
83
83
|
(inputMode === 0 /* Plain */ &&
|
|
84
84
|
((options.obscureInputNumbers && node.type !== 'date' && /\d\d\d\d/.test(value)) ||
|
|
85
85
|
(options.obscureInputDates && node.type === 'date') ||
|
package/lib/modules/mouse.js
CHANGED
|
@@ -96,11 +96,13 @@ export default function (app) {
|
|
|
96
96
|
let mousePositionChanged = false;
|
|
97
97
|
let mouseTarget = null;
|
|
98
98
|
let mouseTargetTime = 0;
|
|
99
|
+
let selectorMap = {};
|
|
99
100
|
app.attachStopCallback(() => {
|
|
100
101
|
mousePositionX = -1;
|
|
101
102
|
mousePositionY = -1;
|
|
102
103
|
mousePositionChanged = false;
|
|
103
104
|
mouseTarget = null;
|
|
105
|
+
selectorMap = {};
|
|
104
106
|
});
|
|
105
107
|
const sendMouseMove = () => {
|
|
106
108
|
if (mousePositionChanged) {
|
|
@@ -108,25 +110,27 @@ export default function (app) {
|
|
|
108
110
|
mousePositionChanged = false;
|
|
109
111
|
}
|
|
110
112
|
};
|
|
111
|
-
const patchDocument = (document) => {
|
|
112
|
-
const selectorMap = {};
|
|
113
|
+
const patchDocument = (document, topframe = false) => {
|
|
113
114
|
function getSelector(id, target) {
|
|
114
115
|
return (selectorMap[id] = selectorMap[id] || _getSelector(target, document));
|
|
115
116
|
}
|
|
116
|
-
|
|
117
|
+
const attachListener = topframe
|
|
118
|
+
? app.attachEventListener.bind(app) // attached/removed on start/stop
|
|
119
|
+
: app.nodes.attachNodeListener.bind(app.nodes); // attached/removed on node register/unregister
|
|
120
|
+
attachListener(document.documentElement, 'mouseover', (e) => {
|
|
117
121
|
const target = getTarget(e.target, document);
|
|
118
122
|
if (target !== mouseTarget) {
|
|
119
123
|
mouseTarget = target;
|
|
120
124
|
mouseTargetTime = performance.now();
|
|
121
125
|
}
|
|
122
126
|
});
|
|
123
|
-
|
|
124
|
-
const
|
|
127
|
+
attachListener(document, 'mousemove', (e) => {
|
|
128
|
+
const [left, top] = app.observer.getDocumentOffset(document); // MBTODO?: document-id related message
|
|
125
129
|
mousePositionX = e.clientX + left;
|
|
126
130
|
mousePositionY = e.clientY + top;
|
|
127
131
|
mousePositionChanged = true;
|
|
128
132
|
}, false);
|
|
129
|
-
|
|
133
|
+
attachListener(document, 'click', (e) => {
|
|
130
134
|
const target = getTarget(e.target, document);
|
|
131
135
|
if ((!e.clientX && !e.clientY) || target === null) {
|
|
132
136
|
return;
|
|
@@ -144,6 +148,6 @@ export default function (app) {
|
|
|
144
148
|
patchDocument(node);
|
|
145
149
|
}
|
|
146
150
|
});
|
|
147
|
-
patchDocument(document);
|
|
151
|
+
patchDocument(document, true);
|
|
148
152
|
app.ticker.attach(sendMouseMove, 10);
|
|
149
153
|
}
|
package/lib/modules/scroll.js
CHANGED
|
@@ -1,20 +1,33 @@
|
|
|
1
1
|
import { SetViewportScroll, SetNodeScroll } from '../app/messages.gen.js';
|
|
2
|
-
import { isElementNode, isRootNode } from '../app/guards.js';
|
|
2
|
+
import { isNode, isElementNode, isRootNode, isDocument } from '../app/guards.js';
|
|
3
|
+
function getDocumentScroll(doc) {
|
|
4
|
+
const win = doc.defaultView;
|
|
5
|
+
return [
|
|
6
|
+
(win && win.pageXOffset) ||
|
|
7
|
+
(doc.documentElement && doc.documentElement.scrollLeft) ||
|
|
8
|
+
(doc.body && doc.body.scrollLeft) ||
|
|
9
|
+
0,
|
|
10
|
+
(win && win.pageYOffset) ||
|
|
11
|
+
(doc.documentElement && doc.documentElement.scrollTop) ||
|
|
12
|
+
(doc.body && doc.body.scrollTop) ||
|
|
13
|
+
0,
|
|
14
|
+
];
|
|
15
|
+
}
|
|
3
16
|
export default function (app) {
|
|
4
17
|
let documentScroll = false;
|
|
5
18
|
const nodeScroll = new Map();
|
|
6
19
|
function setNodeScroll(target) {
|
|
7
|
-
if (target
|
|
20
|
+
if (!isNode(target)) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (isElementNode(target)) {
|
|
8
24
|
nodeScroll.set(target, [target.scrollLeft, target.scrollTop]);
|
|
9
25
|
}
|
|
26
|
+
if (isDocument(target)) {
|
|
27
|
+
nodeScroll.set(target, getDocumentScroll(target));
|
|
28
|
+
}
|
|
10
29
|
}
|
|
11
|
-
const sendSetViewportScroll = app.safe(() => app.send(SetViewportScroll(
|
|
12
|
-
(document.documentElement && document.documentElement.scrollLeft) ||
|
|
13
|
-
(document.body && document.body.scrollLeft) ||
|
|
14
|
-
0, window.pageYOffset ||
|
|
15
|
-
(document.documentElement && document.documentElement.scrollTop) ||
|
|
16
|
-
(document.body && document.body.scrollTop) ||
|
|
17
|
-
0)));
|
|
30
|
+
const sendSetViewportScroll = app.safe(() => app.send(SetViewportScroll(...getDocumentScroll(document))));
|
|
18
31
|
const sendSetNodeScroll = app.safe((s, node) => {
|
|
19
32
|
const id = app.nodes.getID(node);
|
|
20
33
|
if (id !== undefined) {
|
|
@@ -27,12 +40,19 @@ export default function (app) {
|
|
|
27
40
|
nodeScroll.clear();
|
|
28
41
|
});
|
|
29
42
|
app.nodes.attachNodeCallback((node, isStart) => {
|
|
30
|
-
|
|
31
|
-
|
|
43
|
+
// MBTODO: iterate over all the nodes on start instead of using isStart hack
|
|
44
|
+
if (isStart) {
|
|
45
|
+
if (isElementNode(node) && node.scrollLeft + node.scrollTop > 0) {
|
|
46
|
+
nodeScroll.set(node, [node.scrollLeft, node.scrollTop]);
|
|
47
|
+
}
|
|
48
|
+
else if (isDocument(node)) {
|
|
49
|
+
// DRY somehow?
|
|
50
|
+
nodeScroll.set(node, getDocumentScroll(node));
|
|
51
|
+
}
|
|
32
52
|
}
|
|
33
|
-
|
|
53
|
+
if (isRootNode(node)) {
|
|
34
54
|
// scroll is not-composed event (https://javascript.info/shadow-dom-events)
|
|
35
|
-
app.
|
|
55
|
+
app.nodes.attachNodeListener(node, 'scroll', (e) => {
|
|
36
56
|
setNodeScroll(e.target);
|
|
37
57
|
});
|
|
38
58
|
}
|
package/lib/utils.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
export declare
|
|
1
|
+
export declare const IN_BROWSER: boolean;
|
|
2
|
+
export declare const IS_FIREFOX: false | RegExpMatchArray | null;
|
|
3
|
+
export declare const MAX_STR_LEN = 100000;
|
|
4
|
+
export declare const timestamp: () => number;
|
|
2
5
|
export declare const stars: (str: string) => string;
|
|
3
6
|
export declare function normSpaces(str: string): string;
|
|
4
7
|
export declare function isURL(s: string): boolean;
|
|
5
|
-
export declare const IN_BROWSER: boolean;
|
|
6
8
|
export declare const DOCS_HOST = "https://docs.openreplay.com";
|
|
7
9
|
export declare function deprecationWarn(nameOfFeature: string, useInstead: string, docsPath?: string): void;
|
|
8
10
|
export declare function getLabelAttribute(e: Element): string | null;
|
|
9
|
-
export declare function hasOpenreplayAttribute(e: Element,
|
|
11
|
+
export declare function hasOpenreplayAttribute(e: Element, attr: string): boolean;
|
package/lib/utils.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
const DEPRECATED_ATTRS = { htmlmasked: 'hidden', masked: 'obscured' };
|
|
2
|
+
export const IN_BROWSER = !(typeof window === 'undefined');
|
|
3
|
+
export const IS_FIREFOX = IN_BROWSER && navigator.userAgent.match(/firefox|fxios/i);
|
|
4
|
+
export const MAX_STR_LEN = 1e5;
|
|
5
|
+
const navigationStart = (IN_BROWSER && performance.timing.navigationStart) || performance.timeOrigin;
|
|
6
|
+
// performance.now() is buggy in some browsers
|
|
7
|
+
export const timestamp = IN_BROWSER && performance.now() && navigationStart
|
|
8
|
+
? () => Math.round(performance.now() + navigationStart)
|
|
9
|
+
: () => Date.now();
|
|
4
10
|
export const stars = 'repeat' in String.prototype
|
|
5
11
|
? (str) => '*'.repeat(str.length)
|
|
6
12
|
: (str) => str.replace(/./g, '*');
|
|
@@ -11,7 +17,6 @@ export function normSpaces(str) {
|
|
|
11
17
|
export function isURL(s) {
|
|
12
18
|
return s.startsWith('https://') || s.startsWith('http://');
|
|
13
19
|
}
|
|
14
|
-
export const IN_BROWSER = !(typeof window === 'undefined');
|
|
15
20
|
// TODO: JOIN IT WITH LOGGER somehow (use logging decorators?); Don't forget about index.js loggin when there is no logger instance.
|
|
16
21
|
export const DOCS_HOST = 'https://docs.openreplay.com';
|
|
17
22
|
const warnedFeatures = {};
|
|
@@ -33,14 +38,15 @@ export function getLabelAttribute(e) {
|
|
|
33
38
|
}
|
|
34
39
|
return value;
|
|
35
40
|
}
|
|
36
|
-
export function hasOpenreplayAttribute(e,
|
|
37
|
-
const newName = `data-openreplay-${
|
|
41
|
+
export function hasOpenreplayAttribute(e, attr) {
|
|
42
|
+
const newName = `data-openreplay-${attr}`;
|
|
38
43
|
if (e.hasAttribute(newName)) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
// @ts-ignore
|
|
45
|
+
if (DEPRECATED_ATTRS[attr]) {
|
|
46
|
+
deprecationWarn(`"${newName}" attribute`,
|
|
47
|
+
// @ts-ignore
|
|
48
|
+
`"${DEPRECATED_ATTRS[attr]}" attribute`, '/installation/sanitize-data');
|
|
49
|
+
}
|
|
44
50
|
return true;
|
|
45
51
|
}
|
|
46
52
|
return false;
|
package/package.json
CHANGED