@openreplay/tracker 5.0.2-beta → 5.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 +21 -0
- package/cjs/app/guards.js +37 -0
- package/cjs/app/index.d.ts +118 -0
- package/cjs/app/index.js +438 -0
- package/cjs/app/logger.d.ts +26 -0
- package/cjs/app/logger.js +45 -0
- package/cjs/app/messages.gen.d.ts +63 -0
- package/cjs/app/messages.gen.js +551 -0
- package/cjs/app/nodes.d.ts +18 -0
- package/cjs/app/nodes.js +82 -0
- package/cjs/app/observer/iframe_observer.d.ts +4 -0
- package/cjs/app/observer/iframe_observer.js +23 -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.d.ts +23 -0
- package/cjs/app/observer/observer.js +340 -0
- 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 +24 -0
- package/cjs/app/observer/top_observer.js +113 -0
- package/cjs/app/sanitizer.d.ts +24 -0
- package/cjs/app/sanitizer.js +76 -0
- package/cjs/app/session.d.ts +38 -0
- package/cjs/app/session.js +114 -0
- package/cjs/app/ticker.d.ts +12 -0
- package/cjs/app/ticker.js +42 -0
- package/cjs/common/interaction.d.ts +24 -0
- package/cjs/common/interaction.js +2 -0
- package/cjs/common/messages.gen.d.ts +427 -0
- package/cjs/common/messages.gen.js +4 -0
- package/cjs/index.d.ts +47 -0
- package/cjs/index.js +254 -0
- package/cjs/modules/connection.d.ts +2 -0
- package/cjs/modules/connection.js +15 -0
- package/cjs/modules/console.d.ts +6 -0
- package/cjs/modules/console.js +119 -0
- package/cjs/modules/constructedStyleSheets.d.ts +4 -0
- package/cjs/modules/constructedStyleSheets.js +131 -0
- package/cjs/modules/cssrules.d.ts +2 -0
- package/cjs/modules/cssrules.js +99 -0
- package/cjs/modules/exception.d.ts +16 -0
- package/cjs/modules/exception.js +77 -0
- package/cjs/modules/focus.d.ts +2 -0
- package/cjs/modules/focus.js +45 -0
- package/cjs/modules/fonts.d.ts +2 -0
- package/cjs/modules/fonts.js +57 -0
- package/cjs/modules/img.d.ts +2 -0
- package/cjs/modules/img.js +110 -0
- package/cjs/modules/input.d.ts +16 -0
- package/cjs/modules/input.js +163 -0
- package/cjs/modules/mouse.d.ts +2 -0
- package/cjs/modules/mouse.js +148 -0
- package/cjs/modules/network.d.ts +28 -0
- package/cjs/modules/network.js +203 -0
- package/cjs/modules/performance.d.ts +7 -0
- package/cjs/modules/performance.js +53 -0
- package/cjs/modules/scroll.d.ts +2 -0
- package/cjs/modules/scroll.js +79 -0
- package/cjs/modules/timing.d.ts +7 -0
- package/cjs/modules/timing.js +160 -0
- package/cjs/modules/viewport.d.ts +2 -0
- package/cjs/modules/viewport.js +43 -0
- package/cjs/package.json +1 -0
- package/cjs/utils.d.ts +13 -0
- package/cjs/utils.js +71 -0
- package/cjs/vendors/finder/finder.d.ts +12 -0
- package/cjs/vendors/finder/finder.js +352 -0
- package/lib/app/guards.d.ts +21 -0
- package/lib/app/guards.js +26 -0
- package/lib/app/index.d.ts +118 -0
- package/lib/app/index.js +434 -0
- package/lib/app/logger.d.ts +26 -0
- package/lib/app/logger.js +41 -0
- package/lib/app/messages.gen.d.ts +63 -0
- package/lib/app/messages.gen.js +486 -0
- package/lib/app/nodes.d.ts +18 -0
- package/lib/app/nodes.js +79 -0
- package/lib/app/observer/iframe_observer.d.ts +4 -0
- package/lib/app/observer/iframe_observer.js +20 -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.d.ts +23 -0
- package/lib/app/observer/observer.js +337 -0
- 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 +24 -0
- package/lib/app/observer/top_observer.js +110 -0
- package/lib/app/sanitizer.d.ts +24 -0
- package/lib/app/sanitizer.js +72 -0
- package/lib/app/session.d.ts +38 -0
- package/lib/app/session.js +111 -0
- package/lib/app/ticker.d.ts +12 -0
- package/lib/app/ticker.js +39 -0
- package/lib/common/interaction.d.ts +24 -0
- package/lib/common/interaction.js +1 -0
- package/lib/common/messages.gen.d.ts +427 -0
- package/lib/common/messages.gen.js +3 -0
- package/lib/common/tsconfig.tsbuildinfo +1 -0
- package/lib/index.d.ts +47 -0
- package/lib/index.js +248 -0
- package/lib/modules/connection.d.ts +2 -0
- package/lib/modules/connection.js +12 -0
- package/lib/modules/console.d.ts +6 -0
- package/lib/modules/console.js +116 -0
- package/lib/modules/constructedStyleSheets.d.ts +4 -0
- package/lib/modules/constructedStyleSheets.js +126 -0
- package/lib/modules/cssrules.d.ts +2 -0
- package/lib/modules/cssrules.js +97 -0
- package/lib/modules/exception.d.ts +16 -0
- package/lib/modules/exception.js +71 -0
- package/lib/modules/focus.d.ts +2 -0
- package/lib/modules/focus.js +42 -0
- package/lib/modules/fonts.d.ts +2 -0
- package/lib/modules/fonts.js +54 -0
- package/lib/modules/img.d.ts +2 -0
- package/lib/modules/img.js +107 -0
- package/lib/modules/input.d.ts +16 -0
- package/lib/modules/input.js +158 -0
- package/lib/modules/mouse.d.ts +2 -0
- package/lib/modules/mouse.js +145 -0
- package/lib/modules/network.d.ts +28 -0
- package/lib/modules/network.js +200 -0
- package/lib/modules/performance.d.ts +7 -0
- package/lib/modules/performance.js +49 -0
- package/lib/modules/scroll.d.ts +2 -0
- package/lib/modules/scroll.js +76 -0
- package/lib/modules/timing.d.ts +7 -0
- package/lib/modules/timing.js +157 -0
- package/lib/modules/viewport.d.ts +2 -0
- package/lib/modules/viewport.js +40 -0
- package/lib/utils.d.ts +13 -0
- package/lib/utils.js +61 -0
- package/lib/vendors/finder/finder.d.ts +12 -0
- package/lib/vendors/finder/finder.js +348 -0
- package/package.json +1 -1
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { AdoptedSSReplaceURLBased, AdoptedSSInsertRuleURLBased, AdoptedSSAddOwner, AdoptedSSRemoveOwner, } from '../app/messages.gen.js';
|
|
2
|
+
import { isRootNode } from '../app/guards.js';
|
|
3
|
+
function hasAdoptedSS(node) {
|
|
4
|
+
return (isRootNode(node) &&
|
|
5
|
+
// @ts-ignore
|
|
6
|
+
!!node.adoptedStyleSheets);
|
|
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();
|
|
14
|
+
export default function (app) {
|
|
15
|
+
if (app === null) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (!hasAdoptedSS(document)) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const styleSheetIDMap = new Map();
|
|
22
|
+
const adoptedStyleSheetsOwnings = new Map();
|
|
23
|
+
const sendAdoptedStyleSheetsUpdate = (root) => setTimeout(() => {
|
|
24
|
+
let nodeID = app.nodes.getID(root);
|
|
25
|
+
if (root === document) {
|
|
26
|
+
nodeID = 0; // main document doesn't have nodeID. ID count starts from the documentElement
|
|
27
|
+
}
|
|
28
|
+
if (nodeID === undefined) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
let pastOwning = adoptedStyleSheetsOwnings.get(nodeID);
|
|
32
|
+
if (!pastOwning) {
|
|
33
|
+
pastOwning = [];
|
|
34
|
+
}
|
|
35
|
+
const nowOwning = [];
|
|
36
|
+
const styleSheets = root.adoptedStyleSheets;
|
|
37
|
+
for (const s of styleSheets) {
|
|
38
|
+
let sheetID = styleSheetIDMap.get(s);
|
|
39
|
+
const init = !sheetID;
|
|
40
|
+
if (!sheetID) {
|
|
41
|
+
sheetID = nextID();
|
|
42
|
+
styleSheetIDMap.set(s, sheetID);
|
|
43
|
+
}
|
|
44
|
+
if (!pastOwning.includes(sheetID)) {
|
|
45
|
+
app.send(AdoptedSSAddOwner(sheetID, nodeID));
|
|
46
|
+
}
|
|
47
|
+
if (init) {
|
|
48
|
+
const rules = s.cssRules;
|
|
49
|
+
for (let i = 0; i < rules.length; i++) {
|
|
50
|
+
app.send(AdoptedSSInsertRuleURLBased(sheetID, rules[i].cssText, i, app.getBaseHref()));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
nowOwning.push(sheetID);
|
|
54
|
+
}
|
|
55
|
+
for (const sheetID of pastOwning) {
|
|
56
|
+
if (!nowOwning.includes(sheetID)) {
|
|
57
|
+
app.send(AdoptedSSRemoveOwner(sheetID, nodeID));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
adoptedStyleSheetsOwnings.set(nodeID, nowOwning);
|
|
61
|
+
}, 20); // Misterious bug:
|
|
62
|
+
/* On the page https://explore.fast.design/components/fast-accordion
|
|
63
|
+
the only rule inside the only adoptedStyleSheet of the iframe-s document
|
|
64
|
+
gets changed during first milliseconds after the load.
|
|
65
|
+
Howerer, none of the documented methods (replace, insertRule) is triggered.
|
|
66
|
+
The rule is not substituted (remains the same object), however the text gets changed.
|
|
67
|
+
*/
|
|
68
|
+
function patchAdoptedStyleSheets(prototype) {
|
|
69
|
+
const nativeAdoptedStyleSheetsDescriptor = Object.getOwnPropertyDescriptor(prototype, 'adoptedStyleSheets');
|
|
70
|
+
if (nativeAdoptedStyleSheetsDescriptor) {
|
|
71
|
+
Object.defineProperty(prototype, 'adoptedStyleSheets', Object.assign(Object.assign({}, nativeAdoptedStyleSheetsDescriptor), { set: function (value) {
|
|
72
|
+
// @ts-ignore
|
|
73
|
+
const retVal = nativeAdoptedStyleSheetsDescriptor.set.call(this, value);
|
|
74
|
+
sendAdoptedStyleSheetsUpdate(this);
|
|
75
|
+
return retVal;
|
|
76
|
+
} }));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const patchContext = (context) => {
|
|
80
|
+
// @ts-ignore
|
|
81
|
+
if (context.__openreplay_adpss_patched__) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
// @ts-ignore
|
|
86
|
+
context.__openreplay_adpss_patched__ = true;
|
|
87
|
+
}
|
|
88
|
+
patchAdoptedStyleSheets(context.Document.prototype);
|
|
89
|
+
patchAdoptedStyleSheets(context.ShadowRoot.prototype);
|
|
90
|
+
//@ts-ignore TODO: upgrade ts to 4.8+
|
|
91
|
+
const { replace, replaceSync } = context.CSSStyleSheet.prototype;
|
|
92
|
+
//@ts-ignore
|
|
93
|
+
context.CSSStyleSheet.prototype.replace = function (text) {
|
|
94
|
+
return replace.call(this, text).then((sheet) => {
|
|
95
|
+
const sheetID = styleSheetIDMap.get(this);
|
|
96
|
+
if (sheetID) {
|
|
97
|
+
app.send(AdoptedSSReplaceURLBased(sheetID, text, app.getBaseHref()));
|
|
98
|
+
}
|
|
99
|
+
return sheet;
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
//@ts-ignore
|
|
103
|
+
context.CSSStyleSheet.prototype.replaceSync = function (text) {
|
|
104
|
+
const sheetID = styleSheetIDMap.get(this);
|
|
105
|
+
if (sheetID) {
|
|
106
|
+
app.send(AdoptedSSReplaceURLBased(sheetID, text, app.getBaseHref()));
|
|
107
|
+
}
|
|
108
|
+
return replaceSync.call(this, text);
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
patchContext(window);
|
|
112
|
+
app.observer.attachContextCallback(app.safe(patchContext));
|
|
113
|
+
app.attachStopCallback(() => {
|
|
114
|
+
styleSheetIDMap.clear();
|
|
115
|
+
adoptedStyleSheetsOwnings.clear();
|
|
116
|
+
});
|
|
117
|
+
// So far main Document is not triggered with nodeCallbacks
|
|
118
|
+
app.attachStartCallback(() => {
|
|
119
|
+
sendAdoptedStyleSheetsUpdate(document);
|
|
120
|
+
});
|
|
121
|
+
app.nodes.attachNodeCallback((node) => {
|
|
122
|
+
if (hasAdoptedSS(node)) {
|
|
123
|
+
sendAdoptedStyleSheetsUpdate(node);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { AdoptedSSInsertRuleURLBased, // TODO: rename to common StyleSheet names
|
|
2
|
+
AdoptedSSDeleteRule, AdoptedSSAddOwner, TechnicalInfo, } from '../app/messages.gen.js';
|
|
3
|
+
import { hasTag } from '../app/guards.js';
|
|
4
|
+
import { nextID, styleSheetIDMap } from './constructedStyleSheets.js';
|
|
5
|
+
export default function (app) {
|
|
6
|
+
if (app === null) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
if (!window.CSSStyleSheet) {
|
|
10
|
+
app.send(TechnicalInfo('no_stylesheet_prototype_in_window', ''));
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const sendInsertDeleteRule = 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
|
+
}
|
|
52
|
+
});
|
|
53
|
+
const patchContext = app.safe((context) => {
|
|
54
|
+
const { insertRule, deleteRule } = context.CSSStyleSheet.prototype;
|
|
55
|
+
const { insertRule: groupInsertRule, deleteRule: groupDeleteRule } = context.CSSGroupingRule.prototype;
|
|
56
|
+
context.CSSStyleSheet.prototype.insertRule = function (rule, index = 0) {
|
|
57
|
+
sendInsertDeleteRule(this, index, rule);
|
|
58
|
+
return insertRule.call(this, rule, index);
|
|
59
|
+
};
|
|
60
|
+
context.CSSStyleSheet.prototype.deleteRule = function (index) {
|
|
61
|
+
sendInsertDeleteRule(this, index);
|
|
62
|
+
return deleteRule.call(this, index);
|
|
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
|
+
};
|
|
74
|
+
});
|
|
75
|
+
patchContext(window);
|
|
76
|
+
app.observer.attachContextCallback(patchContext);
|
|
77
|
+
app.nodes.attachNodeCallback((node) => {
|
|
78
|
+
if (!hasTag(node, 'style') || !node.sheet) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (node.textContent !== null && node.textContent.trim().length > 0) {
|
|
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;
|
|
87
|
+
}
|
|
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;
|
|
93
|
+
for (let i = 0; i < rules.length; i++) {
|
|
94
|
+
sendInsertDeleteRule(sheet, i, rules[i].cssText);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type App from '../app/index.js';
|
|
2
|
+
import type Message from '../app/messages.gen.js';
|
|
3
|
+
export interface Options {
|
|
4
|
+
captureExceptions: boolean;
|
|
5
|
+
}
|
|
6
|
+
interface StackFrame {
|
|
7
|
+
columnNumber?: number;
|
|
8
|
+
lineNumber?: number;
|
|
9
|
+
fileName?: string;
|
|
10
|
+
functionName?: string;
|
|
11
|
+
source?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function getExceptionMessage(error: Error, fallbackStack: Array<StackFrame>, metadata?: Record<string, any>): Message;
|
|
14
|
+
export declare function getExceptionMessageFromEvent(e: ErrorEvent | PromiseRejectionEvent, context?: typeof globalThis, metadata?: Record<string, any>): Message | null;
|
|
15
|
+
export default function (app: App, opts: Partial<Options>): void;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { JSException } from '../app/messages.gen.js';
|
|
2
|
+
import ErrorStackParser from 'error-stack-parser';
|
|
3
|
+
function getDefaultStack(e) {
|
|
4
|
+
return [
|
|
5
|
+
{
|
|
6
|
+
columnNumber: e.colno,
|
|
7
|
+
lineNumber: e.lineno,
|
|
8
|
+
fileName: e.filename,
|
|
9
|
+
functionName: '',
|
|
10
|
+
source: '',
|
|
11
|
+
},
|
|
12
|
+
];
|
|
13
|
+
}
|
|
14
|
+
export function getExceptionMessage(error, fallbackStack, metadata = {}) {
|
|
15
|
+
let stack = fallbackStack;
|
|
16
|
+
try {
|
|
17
|
+
stack = ErrorStackParser.parse(error);
|
|
18
|
+
}
|
|
19
|
+
catch (e) { }
|
|
20
|
+
return JSException(error.name, error.message, JSON.stringify(stack), JSON.stringify(metadata));
|
|
21
|
+
}
|
|
22
|
+
export function getExceptionMessageFromEvent(e, context = window, metadata = {}) {
|
|
23
|
+
if (e instanceof ErrorEvent) {
|
|
24
|
+
if (e.error instanceof Error) {
|
|
25
|
+
return getExceptionMessage(e.error, getDefaultStack(e), metadata);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
let [name, message] = e.message.split(':');
|
|
29
|
+
if (!message) {
|
|
30
|
+
name = 'Error';
|
|
31
|
+
message = e.message;
|
|
32
|
+
}
|
|
33
|
+
return JSException(name, message, JSON.stringify(getDefaultStack(e)), JSON.stringify(metadata));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else if ('PromiseRejectionEvent' in context && e instanceof context.PromiseRejectionEvent) {
|
|
37
|
+
if (e.reason instanceof Error) {
|
|
38
|
+
return getExceptionMessage(e.reason, [], metadata);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
let message;
|
|
42
|
+
try {
|
|
43
|
+
message = JSON.stringify(e.reason);
|
|
44
|
+
}
|
|
45
|
+
catch (_) {
|
|
46
|
+
message = String(e.reason);
|
|
47
|
+
}
|
|
48
|
+
return JSException('Unhandled Promise Rejection', message, '[]', JSON.stringify(metadata));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
export default function (app, opts) {
|
|
54
|
+
const options = Object.assign({
|
|
55
|
+
captureExceptions: true,
|
|
56
|
+
}, opts);
|
|
57
|
+
function patchContext(context) {
|
|
58
|
+
function handler(e) {
|
|
59
|
+
const msg = getExceptionMessageFromEvent(e, context);
|
|
60
|
+
if (msg != null) {
|
|
61
|
+
app.send(msg);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
app.attachEventListener(context, 'unhandledrejection', handler);
|
|
65
|
+
app.attachEventListener(context, 'error', handler);
|
|
66
|
+
}
|
|
67
|
+
if (options.captureExceptions) {
|
|
68
|
+
app.observer.attachContextCallback(patchContext); // TODO: attach once-per-iframe (?)
|
|
69
|
+
patchContext(window);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { isNode, hasTag } from '../app/guards.js';
|
|
2
|
+
import { SetNodeFocus } from '../app/messages.gen.js';
|
|
3
|
+
export default function (app) {
|
|
4
|
+
function sendSetNodeFocus(n) {
|
|
5
|
+
const id = app.nodes.getID(n);
|
|
6
|
+
if (id !== undefined) {
|
|
7
|
+
app.send(SetNodeFocus(id));
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
let blurred = false;
|
|
11
|
+
app.nodes.attachNodeCallback((node) => {
|
|
12
|
+
if (!hasTag(node, 'body')) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
app.nodes.attachNodeListener(node, 'focus', (e) => {
|
|
16
|
+
if (!isNode(e.target)) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
sendSetNodeFocus(e.target);
|
|
20
|
+
blurred = false;
|
|
21
|
+
});
|
|
22
|
+
app.nodes.attachNodeListener(node, 'blur', (e) => {
|
|
23
|
+
if (e.relatedTarget === null) {
|
|
24
|
+
blurred = true;
|
|
25
|
+
setTimeout(() => {
|
|
26
|
+
if (blurred) {
|
|
27
|
+
app.send(SetNodeFocus(-1));
|
|
28
|
+
}
|
|
29
|
+
}, 0);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
app.attachStartCallback(() => {
|
|
34
|
+
let elem = document.activeElement;
|
|
35
|
+
while (elem && hasTag(elem, 'iframe') && elem.contentDocument) {
|
|
36
|
+
elem = elem.contentDocument.activeElement;
|
|
37
|
+
}
|
|
38
|
+
if (elem && elem !== elem.ownerDocument.body) {
|
|
39
|
+
sendSetNodeFocus(elem);
|
|
40
|
+
}
|
|
41
|
+
}, true);
|
|
42
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { isDocument } from '../app/guards.js';
|
|
2
|
+
import { LoadFontFace } from '../app/messages.gen.js';
|
|
3
|
+
export default function (app) {
|
|
4
|
+
if (!window.FontFace) {
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
const docFonts = new Map();
|
|
8
|
+
const patchWindow = (wnd) => {
|
|
9
|
+
class FontFaceInterceptor extends wnd.FontFace {
|
|
10
|
+
constructor(...args) {
|
|
11
|
+
//maybe do this on load(). In this case check if the document.fonts.load(...) function calls the font's load()
|
|
12
|
+
if (typeof args[1] === 'string') {
|
|
13
|
+
let desc = '';
|
|
14
|
+
if (args[2]) {
|
|
15
|
+
app.safe(() => {
|
|
16
|
+
desc = JSON.stringify(args[2]);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
const ffData = [args[0], args[1], desc];
|
|
20
|
+
const ffDataArr = docFonts.get(wnd.document) || [];
|
|
21
|
+
ffDataArr.push(ffData);
|
|
22
|
+
docFonts.set(wnd.document, ffDataArr);
|
|
23
|
+
const parentID = wnd === window ? 0 : app.nodes.getID(wnd.document);
|
|
24
|
+
if (parentID === undefined) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (app.active()) {
|
|
28
|
+
app.send(LoadFontFace(parentID, ...ffData));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
super(...args);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
wnd.FontFace = FontFaceInterceptor;
|
|
35
|
+
};
|
|
36
|
+
app.observer.attachContextCallback(patchWindow);
|
|
37
|
+
patchWindow(window);
|
|
38
|
+
app.nodes.attachNodeCallback(app.safe((node) => {
|
|
39
|
+
if (!isDocument(node)) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const ffDataArr = docFonts.get(node);
|
|
43
|
+
if (!ffDataArr) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const parentID = node.defaultView === window ? 0 : app.nodes.getID(node);
|
|
47
|
+
if (parentID === undefined) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
ffDataArr.forEach((ffData) => {
|
|
51
|
+
app.send(LoadFontFace(parentID, ...ffData));
|
|
52
|
+
});
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { isURL, IS_FIREFOX, MAX_STR_LEN } from '../utils.js';
|
|
2
|
+
import { ResourceTiming, SetNodeAttributeURLBased, SetNodeAttribute } from '../app/messages.gen.js';
|
|
3
|
+
import { hasTag } from '../app/guards.js';
|
|
4
|
+
function resolveURL(url, location = document.location) {
|
|
5
|
+
url = url.trim();
|
|
6
|
+
if (url.startsWith('//') ||
|
|
7
|
+
url.startsWith('http://') ||
|
|
8
|
+
url.startsWith('https://') ||
|
|
9
|
+
url.startsWith('data:') // any other possible value here? https://bugzilla.mozilla.org/show_bug.cgi?id=1758035
|
|
10
|
+
) {
|
|
11
|
+
return url;
|
|
12
|
+
}
|
|
13
|
+
else if (url.startsWith('/')) {
|
|
14
|
+
return location.origin + url;
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
return location.origin + location.pathname + url;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
// https://bugzilla.mozilla.org/show_bug.cgi?id=1607081
|
|
21
|
+
function isSVGInFireFox(url) {
|
|
22
|
+
return IS_FIREFOX && (url.startsWith('data:image/svg+xml') || url.match(/.svg$|/i));
|
|
23
|
+
}
|
|
24
|
+
const PLACEHOLDER_SRC = 'https://static.openreplay.com/tracker/placeholder.jpeg';
|
|
25
|
+
export default function (app) {
|
|
26
|
+
function sendPlaceholder(id, node) {
|
|
27
|
+
app.send(SetNodeAttribute(id, 'src', PLACEHOLDER_SRC));
|
|
28
|
+
const { width, height } = node.getBoundingClientRect();
|
|
29
|
+
if (!node.hasAttribute('width')) {
|
|
30
|
+
app.send(SetNodeAttribute(id, 'width', String(width)));
|
|
31
|
+
}
|
|
32
|
+
if (!node.hasAttribute('height')) {
|
|
33
|
+
app.send(SetNodeAttribute(id, 'height', String(height)));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const sendSrcset = function (id, img) {
|
|
37
|
+
const { srcset } = img;
|
|
38
|
+
if (!srcset) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const resolvedSrcset = srcset
|
|
42
|
+
.split(',')
|
|
43
|
+
.map((str) => resolveURL(str))
|
|
44
|
+
.join(',');
|
|
45
|
+
app.send(SetNodeAttribute(id, 'srcset', resolvedSrcset));
|
|
46
|
+
};
|
|
47
|
+
const sendSrc = function (id, img) {
|
|
48
|
+
if (img.src.length > MAX_STR_LEN) {
|
|
49
|
+
sendPlaceholder(id, img);
|
|
50
|
+
}
|
|
51
|
+
app.send(SetNodeAttributeURLBased(id, 'src', img.src, app.getBaseHref()));
|
|
52
|
+
};
|
|
53
|
+
const sendImgError = app.safe(function (img) {
|
|
54
|
+
const resolvedSrc = resolveURL(img.src || ''); // Src type is null sometimes. - is it true?
|
|
55
|
+
if (isURL(resolvedSrc)) {
|
|
56
|
+
app.send(ResourceTiming(app.timestamp(), 0, 0, 0, 0, 0, resolvedSrc, 'img'));
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
const sendImgAttrs = app.safe(function (img) {
|
|
60
|
+
const id = app.nodes.getID(img);
|
|
61
|
+
if (id === undefined) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (!img.complete) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (img.naturalHeight === 0 && img.naturalWidth === 0 && !isSVGInFireFox(img.src)) {
|
|
68
|
+
sendImgError(img);
|
|
69
|
+
}
|
|
70
|
+
else if (app.sanitizer.isHidden(id) || app.sanitizer.isObscured(id)) {
|
|
71
|
+
sendPlaceholder(id, img);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
sendSrc(id, img);
|
|
75
|
+
sendSrcset(id, img);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
const observer = new MutationObserver((mutations) => {
|
|
79
|
+
for (const mutation of mutations) {
|
|
80
|
+
if (mutation.type === 'attributes') {
|
|
81
|
+
const target = mutation.target;
|
|
82
|
+
const id = app.nodes.getID(target);
|
|
83
|
+
if (id === undefined) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (mutation.attributeName === 'src') {
|
|
87
|
+
sendSrc(id, target);
|
|
88
|
+
}
|
|
89
|
+
if (mutation.attributeName === 'srcset') {
|
|
90
|
+
sendSrcset(id, target);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
app.attachStopCallback(() => {
|
|
96
|
+
observer.disconnect();
|
|
97
|
+
});
|
|
98
|
+
app.nodes.attachNodeCallback((node) => {
|
|
99
|
+
if (!hasTag(node, 'img')) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
app.nodes.attachNodeListener(node, 'error', () => sendImgError(node));
|
|
103
|
+
app.nodes.attachNodeListener(node, 'load', () => sendImgAttrs(node));
|
|
104
|
+
sendImgAttrs(node);
|
|
105
|
+
observer.observe(node, { attributes: true, attributeFilter: ['src', 'srcset'] });
|
|
106
|
+
});
|
|
107
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type App from '../app/index.js';
|
|
2
|
+
type TextEditableElement = HTMLInputElement | HTMLTextAreaElement;
|
|
3
|
+
export declare function getInputLabel(node: TextEditableElement): string;
|
|
4
|
+
export declare const enum InputMode {
|
|
5
|
+
Plain = 0,
|
|
6
|
+
Obscured = 1,
|
|
7
|
+
Hidden = 2
|
|
8
|
+
}
|
|
9
|
+
export interface Options {
|
|
10
|
+
obscureInputNumbers: boolean;
|
|
11
|
+
obscureInputEmails: boolean;
|
|
12
|
+
defaultInputMode: InputMode;
|
|
13
|
+
obscureInputDates: boolean;
|
|
14
|
+
}
|
|
15
|
+
export default function (app: App, opts: Partial<Options>): void;
|
|
16
|
+
export {};
|