@sailfish-ai/recorder 1.12.7 → 1.12.9
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/dist/chunks/{canvasManager-Ce7vazTt.js → canvasManager-CLCtW5BZ.js} +1 -1
- package/dist/chunks/canvasManager-CLCtW5BZ.js.br +0 -0
- package/dist/chunks/canvasManager-CLCtW5BZ.js.gz +0 -0
- package/dist/chunks/{canvasManager-B9uQ-PFS.js → canvasManager-Dtum5H28.js} +1 -1
- package/dist/chunks/canvasManager-Dtum5H28.js.br +0 -0
- package/dist/chunks/canvasManager-Dtum5H28.js.gz +0 -0
- package/dist/chunks/{chunkSerializer-C1knjCQ5.js → chunkSerializer-9QANwItv.js} +1 -1
- package/dist/chunks/chunkSerializer-9QANwItv.js.br +0 -0
- package/dist/chunks/chunkSerializer-9QANwItv.js.gz +0 -0
- package/dist/chunks/{chunkSerializer-B-z7p0r-.js → chunkSerializer-Ds0Flu_e.js} +1 -1
- package/dist/chunks/chunkSerializer-Ds0Flu_e.js.br +0 -0
- package/dist/chunks/chunkSerializer-Ds0Flu_e.js.gz +0 -0
- package/dist/chunks/hoverDetection-Bl-yKKI3.js +137 -0
- package/dist/chunks/hoverDetection-Bl-yKKI3.js.br +0 -0
- package/dist/chunks/hoverDetection-Bl-yKKI3.js.gz +0 -0
- package/dist/chunks/hoverDetection-CYsyBQjj.js +139 -0
- package/dist/chunks/hoverDetection-CYsyBQjj.js.br +0 -0
- package/dist/chunks/hoverDetection-CYsyBQjj.js.gz +0 -0
- package/dist/chunks/{index-BMHQ-0NY.js → index-BRybWPUS.js} +142 -128
- package/dist/chunks/index-BRybWPUS.js.br +0 -0
- package/dist/chunks/index-BRybWPUS.js.gz +0 -0
- package/dist/chunks/{index-qgM1hFXO.js → index-Cp1vLB5Q.js} +120 -107
- package/dist/chunks/index-Cp1vLB5Q.js.br +0 -0
- package/dist/chunks/index-Cp1vLB5Q.js.gz +0 -0
- package/dist/hoverDetection.js +371 -0
- package/dist/hoverDetection.js.br +0 -0
- package/dist/hoverDetection.js.gz +0 -0
- package/dist/hoverDetection.test.js +187 -0
- package/dist/hoverDetection.test.js.br +0 -0
- package/dist/hoverDetection.test.js.gz +0 -0
- package/dist/index.js +23 -1
- package/dist/index.js.br +0 -0
- package/dist/index.js.gz +0 -0
- package/dist/recorder.cjs +2 -2
- package/dist/recorder.cjs.br +0 -0
- package/dist/recorder.cjs.gz +0 -0
- package/dist/recorder.js +13 -12
- package/dist/recorder.js.br +7 -2
- package/dist/recorder.js.gz +0 -0
- package/dist/recorder.umd.cjs +597 -452
- package/dist/recorder.umd.cjs.br +0 -0
- package/dist/recorder.umd.cjs.gz +0 -0
- package/dist/recording.js +27 -0
- package/dist/recording.js.br +0 -0
- package/dist/recording.js.gz +0 -0
- package/dist/types/hoverDetection.d.ts +37 -0
- package/dist/types/hoverDetection.test.d.ts +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/types.d.ts +6 -0
- package/package.json +1 -1
- package/dist/chunks/canvasManager-B9uQ-PFS.js.br +0 -0
- package/dist/chunks/canvasManager-B9uQ-PFS.js.gz +0 -0
- package/dist/chunks/canvasManager-Ce7vazTt.js.br +0 -0
- package/dist/chunks/canvasManager-Ce7vazTt.js.gz +0 -0
- package/dist/chunks/chunkSerializer-B-z7p0r-.js.br +0 -0
- package/dist/chunks/chunkSerializer-B-z7p0r-.js.gz +0 -0
- package/dist/chunks/chunkSerializer-C1knjCQ5.js.br +0 -0
- package/dist/chunks/chunkSerializer-C1knjCQ5.js.gz +0 -0
- package/dist/chunks/index-BMHQ-0NY.js.br +0 -0
- package/dist/chunks/index-BMHQ-0NY.js.gz +0 -0
- package/dist/chunks/index-qgM1hFXO.js.br +0 -0
- package/dist/chunks/index-qgM1hFXO.js.gz +0 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
// Hover action detection (DIAGPLAT-1790) — v2.
|
|
2
|
+
//
|
|
3
|
+
// rrweb has no native hover/mouseover interaction, so the recorder detects it
|
|
4
|
+
// itself. v2 design:
|
|
5
|
+
// • Detect dwell over an element with hover-like affordance (tooltip, menu/
|
|
6
|
+
// popover trigger, cursor:pointer, or a matching `:hover` CSS rule).
|
|
7
|
+
// • At the dwell mark, RE-CHECK `el.matches(":hover")` — only then is it a
|
|
8
|
+
// real, intentional hover (kills the entered-then-left race).
|
|
9
|
+
// • Emit ONCE at hover-END, carrying `duration_ms` (real dwell time).
|
|
10
|
+
// • Suppress at the source: if the hover ended because the user CLICKED the
|
|
11
|
+
// element, don't emit (a hover→click is just a click). Because this is
|
|
12
|
+
// decided locally and in order, it needs no backend/Kafka-batch dedup.
|
|
13
|
+
// • Volume guard before anything is emitted: per-element cooldown +
|
|
14
|
+
// token-bucket rate limit + hard session cap — so a pathological page can
|
|
15
|
+
// never flood the WebSocket/Kafka/Postgres.
|
|
16
|
+
//
|
|
17
|
+
// SSR-safe: every entry point guards against a missing window/document.
|
|
18
|
+
import { EventType } from "@sailfish-rrweb/types";
|
|
19
|
+
// ARIA roles that imply hover-revealed UI (menus, popovers, tooltips).
|
|
20
|
+
const MENU_ROLES = new Set(["menu", "menuitem", "menubar", "tooltip"]);
|
|
21
|
+
// Memoized "base" selectors (rightmost compound, `:hover` stripped) from every
|
|
22
|
+
// readable stylesheet. Recomputed only when the stylesheet count changes.
|
|
23
|
+
let cachedSheetCount = -1;
|
|
24
|
+
let hoverBaseSelectors = [];
|
|
25
|
+
// From a single comma-separated selector that contains `:hover`, push the
|
|
26
|
+
// "base" selector of the element that is actually HOVERED. The `:hover` may
|
|
27
|
+
// qualify any compound in the chain, not the rightmost one: in
|
|
28
|
+
// `.menu:hover .item` the user hovers `.menu` (and `.item` is merely revealed),
|
|
29
|
+
// so the affordance element is `.menu`, NOT `.item`. We therefore split the
|
|
30
|
+
// selector into its compounds (by combinator) and, for each compound that
|
|
31
|
+
// carries `:hover`, strip the `:hover` (and any pseudo-element) to recover the
|
|
32
|
+
// structural selector to match against.
|
|
33
|
+
function collectHoverBases(selectorText, out) {
|
|
34
|
+
for (const part of selectorText.split(",")) {
|
|
35
|
+
if (part.indexOf(":hover") === -1)
|
|
36
|
+
continue;
|
|
37
|
+
// Split into compounds on combinators (descendant / > / + / ~).
|
|
38
|
+
for (const compound of part.split(/\s*[>+~]\s*|\s+/)) {
|
|
39
|
+
if (compound.indexOf(":hover") === -1)
|
|
40
|
+
continue;
|
|
41
|
+
const base = compound
|
|
42
|
+
.replace(/:hover\b/g, "")
|
|
43
|
+
// pseudo-elements (::before/::after) el.matches() can't evaluate —
|
|
44
|
+
// drop them so e.g. `.tip:hover::after` still resolves to `.tip`.
|
|
45
|
+
.replace(/::[a-zA-Z-]+/g, "")
|
|
46
|
+
.trim();
|
|
47
|
+
if (base)
|
|
48
|
+
out.push(base);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Walk a rule list, descending into grouping rules (@media / @supports /
|
|
53
|
+
// @layer / @container) and CSS-nested rules — a `:hover` rule nested inside
|
|
54
|
+
// `@media (hover: hover) { … }` (the modern best-practice pattern) would
|
|
55
|
+
// otherwise be invisible, so strict affordance would miss it entirely.
|
|
56
|
+
function collectFromRules(rules, out, depth) {
|
|
57
|
+
if (depth > 10)
|
|
58
|
+
return; // pathological nesting guard
|
|
59
|
+
for (let j = 0; j < rules.length; j++) {
|
|
60
|
+
const rule = rules[j];
|
|
61
|
+
const selectorText = rule.selectorText;
|
|
62
|
+
if (selectorText && selectorText.indexOf(":hover") !== -1) {
|
|
63
|
+
collectHoverBases(selectorText, out);
|
|
64
|
+
}
|
|
65
|
+
// CSSGroupingRule (and CSS-nested CSSStyleRule) expose `.cssRules`.
|
|
66
|
+
let nested = null;
|
|
67
|
+
try {
|
|
68
|
+
nested = rule.cssRules ?? null;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
nested = null; // inaccessible
|
|
72
|
+
}
|
|
73
|
+
if (nested && nested.length)
|
|
74
|
+
collectFromRules(nested, out, depth + 1);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function getHoverBaseSelectors() {
|
|
78
|
+
const sheets = document.styleSheets;
|
|
79
|
+
if (sheets.length === cachedSheetCount)
|
|
80
|
+
return hoverBaseSelectors;
|
|
81
|
+
cachedSheetCount = sheets.length;
|
|
82
|
+
const selectors = [];
|
|
83
|
+
for (let i = 0; i < sheets.length; i++) {
|
|
84
|
+
let rules = null;
|
|
85
|
+
try {
|
|
86
|
+
rules = sheets[i].cssRules;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
continue; // cross-origin / inaccessible stylesheet
|
|
90
|
+
}
|
|
91
|
+
if (!rules)
|
|
92
|
+
continue;
|
|
93
|
+
collectFromRules(rules, selectors, 0);
|
|
94
|
+
}
|
|
95
|
+
hoverBaseSelectors = selectors;
|
|
96
|
+
return selectors;
|
|
97
|
+
}
|
|
98
|
+
function matchesHoverRule(el) {
|
|
99
|
+
const selectors = getHoverBaseSelectors();
|
|
100
|
+
for (let i = 0; i < selectors.length; i++) {
|
|
101
|
+
try {
|
|
102
|
+
if (el.matches(selectors[i]))
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// .matches() rejects malformed/unsupported selectors — ignore them.
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* True when `el` itself exposes hover-like behavior. Cheapest checks first.
|
|
113
|
+
* In `strict` mode bare `cursor:pointer` does NOT qualify — only a genuine
|
|
114
|
+
* hover effect (tooltip, menu/popover, or a matching `:hover` CSS rule).
|
|
115
|
+
*/
|
|
116
|
+
function hasHoverAffordance(el, strict) {
|
|
117
|
+
// Hover-revealed UI (tooltip / menu / popover) — real hover behavior even
|
|
118
|
+
// when implemented in JS rather than a `:hover` rule.
|
|
119
|
+
if (el.hasAttribute("title") ||
|
|
120
|
+
el.hasAttribute("data-tooltip") ||
|
|
121
|
+
el.hasAttribute("aria-describedby")) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
const role = el.getAttribute("role");
|
|
125
|
+
if (el.hasAttribute("aria-haspopup") || (role && MENU_ROLES.has(role))) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
// A genuine CSS hover effect targets this element — the gold standard.
|
|
129
|
+
if (matchesHoverRule(el))
|
|
130
|
+
return true;
|
|
131
|
+
// cursor:pointer is broad (most clickable elements set it, with no actual
|
|
132
|
+
// hover effect) — only counts in non-strict mode.
|
|
133
|
+
if (!strict) {
|
|
134
|
+
try {
|
|
135
|
+
if (typeof window.getComputedStyle === "function") {
|
|
136
|
+
if (window.getComputedStyle(el).cursor === "pointer")
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// getComputedStyle can throw on detached nodes — ignore.
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
/** Walk up from `start` to the nearest element with hover affordance. */
|
|
147
|
+
function resolveHoverAffordance(start, maxDepth, strict) {
|
|
148
|
+
let el = start;
|
|
149
|
+
let depth = 0;
|
|
150
|
+
while (el && depth <= maxDepth) {
|
|
151
|
+
if (hasHoverAffordance(el, strict))
|
|
152
|
+
return el;
|
|
153
|
+
el = el.parentElement;
|
|
154
|
+
depth++;
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Confirm the cursor is actually still over `el` at emit time. `:hover` matches
|
|
160
|
+
* an element when the pointer is over it OR a descendant — exactly what we want
|
|
161
|
+
* (we track the affordance ancestor). If the environment can't evaluate `:hover`
|
|
162
|
+
* (older jsdom, etc.), don't drop the hover — we only got here via a real
|
|
163
|
+
* pointerover + dwell with no intervening pointerout.
|
|
164
|
+
*/
|
|
165
|
+
function stillHovering(el) {
|
|
166
|
+
try {
|
|
167
|
+
return el.matches(":hover");
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/** Simple token bucket: `capacity` burst, refills `refillPerSec` tokens/sec. */
|
|
174
|
+
class TokenBucket {
|
|
175
|
+
capacity;
|
|
176
|
+
refillPerSec;
|
|
177
|
+
tokens;
|
|
178
|
+
last;
|
|
179
|
+
constructor(capacity, refillPerSec, now) {
|
|
180
|
+
this.capacity = capacity;
|
|
181
|
+
this.refillPerSec = refillPerSec;
|
|
182
|
+
this.tokens = capacity;
|
|
183
|
+
this.last = now;
|
|
184
|
+
}
|
|
185
|
+
tryConsume(now) {
|
|
186
|
+
this.tokens = Math.min(this.capacity, this.tokens + ((now - this.last) / 1000) * this.refillPerSec);
|
|
187
|
+
this.last = now;
|
|
188
|
+
if (this.tokens < 1)
|
|
189
|
+
return false;
|
|
190
|
+
this.tokens -= 1;
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const COOLDOWN_LRU_CAP = 500;
|
|
195
|
+
// Single detector per document. If the recorder re-initializes (reconnect, SPA
|
|
196
|
+
// re-init, bfcache pageshow re-arm), a second install would otherwise STACK a
|
|
197
|
+
// second set of capture-phase listeners on top of the first — double-emitting
|
|
198
|
+
// every hover and leaking the originals. Tear down the previous install first.
|
|
199
|
+
let activeTeardown = null;
|
|
200
|
+
/**
|
|
201
|
+
* Install document-level hover detection. Returns a teardown function. No-op
|
|
202
|
+
* (returns a no-op teardown) outside the browser. Idempotent: a second call
|
|
203
|
+
* tears down the previous install before arming a new one.
|
|
204
|
+
*/
|
|
205
|
+
export function installHoverDetection(record, options) {
|
|
206
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
207
|
+
return () => { };
|
|
208
|
+
}
|
|
209
|
+
// Reclaim any detector from a prior install before arming a new one.
|
|
210
|
+
if (activeTeardown) {
|
|
211
|
+
activeTeardown();
|
|
212
|
+
activeTeardown = null;
|
|
213
|
+
}
|
|
214
|
+
const dwellMs = Math.max(0, options.dwellMs);
|
|
215
|
+
const cooldownMs = Math.max(0, options.cooldownMs ?? 3000);
|
|
216
|
+
const maxPerSession = options.maxPerSession ?? 300;
|
|
217
|
+
const maxPerSecond = options.maxPerSecond ?? 1;
|
|
218
|
+
const maxDwellMs = options.maxDwellMs ?? 60_000;
|
|
219
|
+
const maxDepth = options.maxAncestorDepth ?? 5;
|
|
220
|
+
const strict = options.strictAffordance ?? true;
|
|
221
|
+
// Date.now() (ms) is plenty for dwell timing and is advanced by fake timers
|
|
222
|
+
// in tests; performance.now() isn't reliably faked.
|
|
223
|
+
const now = () => Date.now();
|
|
224
|
+
// ── volume guard state ──────────────────────────────────────────────────
|
|
225
|
+
const lastEmitById = new Map();
|
|
226
|
+
let sessionHoverCount = 0;
|
|
227
|
+
const bucket = new TokenBucket(Math.max(10, maxPerSecond), maxPerSecond, now());
|
|
228
|
+
const passesVolumeGate = (id, t) => {
|
|
229
|
+
if (sessionHoverCount >= maxPerSession)
|
|
230
|
+
return false;
|
|
231
|
+
const last = lastEmitById.get(id);
|
|
232
|
+
if (last !== undefined && t - last < cooldownMs)
|
|
233
|
+
return false;
|
|
234
|
+
if (!bucket.tryConsume(t))
|
|
235
|
+
return false;
|
|
236
|
+
// record + LRU-cap the cooldown map
|
|
237
|
+
lastEmitById.delete(id);
|
|
238
|
+
lastEmitById.set(id, t);
|
|
239
|
+
if (lastEmitById.size > COOLDOWN_LRU_CAP) {
|
|
240
|
+
const oldest = lastEmitById.keys().next().value;
|
|
241
|
+
if (oldest !== undefined)
|
|
242
|
+
lastEmitById.delete(oldest);
|
|
243
|
+
}
|
|
244
|
+
sessionHoverCount += 1;
|
|
245
|
+
return true;
|
|
246
|
+
};
|
|
247
|
+
// ── per-hover tracking state ────────────────────────────────────────────
|
|
248
|
+
let trackedEl = null;
|
|
249
|
+
let enterTime = 0;
|
|
250
|
+
let dwellReached = false;
|
|
251
|
+
let dwellTimer = null;
|
|
252
|
+
let capTimer = null;
|
|
253
|
+
const clearTimers = () => {
|
|
254
|
+
if (dwellTimer !== null) {
|
|
255
|
+
clearTimeout(dwellTimer);
|
|
256
|
+
dwellTimer = null;
|
|
257
|
+
}
|
|
258
|
+
if (capTimer !== null) {
|
|
259
|
+
clearTimeout(capTimer);
|
|
260
|
+
capTimer = null;
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
const reset = () => {
|
|
264
|
+
clearTimers();
|
|
265
|
+
trackedEl = null;
|
|
266
|
+
dwellReached = false;
|
|
267
|
+
};
|
|
268
|
+
const emit = (el, startedAt, endedAt) => {
|
|
269
|
+
const id = record.mirror?.getId?.(el) ?? -1;
|
|
270
|
+
if (!id || id <= 0)
|
|
271
|
+
return; // not in the rrweb mirror → backend can't resolve it
|
|
272
|
+
if (!passesVolumeGate(id, endedAt))
|
|
273
|
+
return;
|
|
274
|
+
record.addSailfishEvent?.(EventType.SailfishCustom, {
|
|
275
|
+
action: "hover",
|
|
276
|
+
element_id: id,
|
|
277
|
+
started_at: Math.round(startedAt),
|
|
278
|
+
ended_at: Math.round(endedAt),
|
|
279
|
+
// Clamp ≥ 0: Date.now() isn't monotonic, so a clock step could otherwise
|
|
280
|
+
// produce a negative span.
|
|
281
|
+
duration_ms: Math.max(0, Math.round(endedAt - startedAt)),
|
|
282
|
+
});
|
|
283
|
+
};
|
|
284
|
+
// End the current hover. `emitted-by` semantics: emit only for genuine ends
|
|
285
|
+
// (leave / switch / cap / flush) once dwell was reached. A click END
|
|
286
|
+
// suppresses (hover→click is just a click); a raced-out dwell never set
|
|
287
|
+
// dwellReached so it can't emit.
|
|
288
|
+
const endHover = (cause) => {
|
|
289
|
+
const el = trackedEl;
|
|
290
|
+
const reached = dwellReached;
|
|
291
|
+
const started = enterTime;
|
|
292
|
+
reset();
|
|
293
|
+
if (el && reached && cause !== "click") {
|
|
294
|
+
emit(el, started, now());
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
const startHover = (el) => {
|
|
298
|
+
reset();
|
|
299
|
+
trackedEl = el;
|
|
300
|
+
enterTime = now();
|
|
301
|
+
dwellReached = false;
|
|
302
|
+
dwellTimer = setTimeout(() => {
|
|
303
|
+
dwellTimer = null;
|
|
304
|
+
if (trackedEl && stillHovering(trackedEl)) {
|
|
305
|
+
dwellReached = true;
|
|
306
|
+
// Arm the hard cap so an indefinitely-parked cursor still emits once.
|
|
307
|
+
capTimer = setTimeout(() => {
|
|
308
|
+
capTimer = null;
|
|
309
|
+
endHover("cap");
|
|
310
|
+
}, Math.max(0, maxDwellMs - dwellMs));
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
reset(); // entered then left before the dwell — no hover
|
|
314
|
+
}
|
|
315
|
+
}, dwellMs);
|
|
316
|
+
};
|
|
317
|
+
const onPointerOver = (event) => {
|
|
318
|
+
const target = event.target;
|
|
319
|
+
if (!(target instanceof Element))
|
|
320
|
+
return;
|
|
321
|
+
const affordanceEl = resolveHoverAffordance(target, maxDepth, strict);
|
|
322
|
+
if (!affordanceEl) {
|
|
323
|
+
if (trackedEl && !trackedEl.contains(target))
|
|
324
|
+
endHover("leave");
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (affordanceEl === trackedEl)
|
|
328
|
+
return; // already tracking it
|
|
329
|
+
if (trackedEl)
|
|
330
|
+
endHover("switch"); // moved A→B: A's hover ends (may emit)
|
|
331
|
+
startHover(affordanceEl);
|
|
332
|
+
};
|
|
333
|
+
const onPointerOut = (event) => {
|
|
334
|
+
const related = event.relatedTarget;
|
|
335
|
+
if (trackedEl &&
|
|
336
|
+
(!(related instanceof Node) || !trackedEl.contains(related))) {
|
|
337
|
+
endHover("leave");
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
const onPointerDown = (event) => {
|
|
341
|
+
const target = event.target;
|
|
342
|
+
if (trackedEl &&
|
|
343
|
+
target instanceof Node &&
|
|
344
|
+
(trackedEl === target || trackedEl.contains(target))) {
|
|
345
|
+
endHover("click"); // suppress — this hover became a click
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
const onVisibility = () => {
|
|
349
|
+
if (document.visibilityState === "hidden")
|
|
350
|
+
endHover("flush");
|
|
351
|
+
};
|
|
352
|
+
const capture = { capture: true, passive: true };
|
|
353
|
+
document.addEventListener("pointerover", onPointerOver, capture);
|
|
354
|
+
document.addEventListener("pointerout", onPointerOut, capture);
|
|
355
|
+
document.addEventListener("pointerdown", onPointerDown, capture);
|
|
356
|
+
document.addEventListener("visibilitychange", onVisibility, true);
|
|
357
|
+
window.addEventListener("pagehide", onVisibility, true);
|
|
358
|
+
const teardown = () => {
|
|
359
|
+
endHover("flush");
|
|
360
|
+
reset();
|
|
361
|
+
document.removeEventListener("pointerover", onPointerOver, { capture: true });
|
|
362
|
+
document.removeEventListener("pointerout", onPointerOut, { capture: true });
|
|
363
|
+
document.removeEventListener("pointerdown", onPointerDown, { capture: true });
|
|
364
|
+
document.removeEventListener("visibilitychange", onVisibility, true);
|
|
365
|
+
window.removeEventListener("pagehide", onVisibility, true);
|
|
366
|
+
if (activeTeardown === teardown)
|
|
367
|
+
activeTeardown = null;
|
|
368
|
+
};
|
|
369
|
+
activeTeardown = teardown;
|
|
370
|
+
return teardown;
|
|
371
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { installHoverDetection } from "./hoverDetection";
|
|
3
|
+
// EventType.SailfishCustom === 25 (must match the backend's SailfishCustomEvent).
|
|
4
|
+
const SAILFISH_CUSTOM = 25;
|
|
5
|
+
function makeRecord(id = 42) {
|
|
6
|
+
return {
|
|
7
|
+
addSailfishEvent: vi.fn(),
|
|
8
|
+
mirror: { getId: vi.fn((_node) => id) },
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
// Affordance via the `title` attribute (cheapest path, no CSS needed in jsdom).
|
|
12
|
+
// We stub matches() so the `:hover` recheck is deterministic regardless of how
|
|
13
|
+
// jsdom handles the dynamic `:hover` pseudo-class.
|
|
14
|
+
function makeHoverable(hovering = true) {
|
|
15
|
+
const el = document.createElement("button");
|
|
16
|
+
el.setAttribute("title", "Help");
|
|
17
|
+
vi.spyOn(el, "matches").mockReturnValue(hovering);
|
|
18
|
+
document.body.appendChild(el);
|
|
19
|
+
return el;
|
|
20
|
+
}
|
|
21
|
+
const over = (el) => el.dispatchEvent(new Event("pointerover", { bubbles: true }));
|
|
22
|
+
const out = (el) => el.dispatchEvent(new Event("pointerout", { bubbles: true })); // null relatedTarget => "left"
|
|
23
|
+
const down = (el) => el.dispatchEvent(new Event("pointerdown", { bubbles: true }));
|
|
24
|
+
describe("installHoverDetection (v2)", () => {
|
|
25
|
+
let teardown = () => { };
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
vi.useFakeTimers();
|
|
28
|
+
document.body.innerHTML = "";
|
|
29
|
+
});
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
teardown();
|
|
32
|
+
teardown = () => { };
|
|
33
|
+
vi.useRealTimers();
|
|
34
|
+
vi.restoreAllMocks();
|
|
35
|
+
});
|
|
36
|
+
it("emits once at hover-END with a duration, only after the dwell", () => {
|
|
37
|
+
const el = makeHoverable(true);
|
|
38
|
+
const record = makeRecord();
|
|
39
|
+
teardown = installHoverDetection(record, { dwellMs: 500 });
|
|
40
|
+
over(el);
|
|
41
|
+
vi.advanceTimersByTime(500); // dwell reached, but no end yet
|
|
42
|
+
expect(record.addSailfishEvent).not.toHaveBeenCalled();
|
|
43
|
+
vi.advanceTimersByTime(300); // dwell another 300ms
|
|
44
|
+
out(el); // hover ends -> emit
|
|
45
|
+
expect(record.addSailfishEvent).toHaveBeenCalledTimes(1);
|
|
46
|
+
const [type, payload] = record.addSailfishEvent.mock.calls[0];
|
|
47
|
+
expect(type).toBe(SAILFISH_CUSTOM);
|
|
48
|
+
expect(payload.action).toBe("hover");
|
|
49
|
+
expect(payload.element_id).toBe(42);
|
|
50
|
+
expect(payload.duration_ms).toBeGreaterThanOrEqual(800);
|
|
51
|
+
// start + end are explicit epoch-ms; end − start === duration.
|
|
52
|
+
expect(typeof payload.started_at).toBe("number");
|
|
53
|
+
expect(typeof payload.ended_at).toBe("number");
|
|
54
|
+
expect(payload.ended_at - payload.started_at).toBe(payload.duration_ms);
|
|
55
|
+
});
|
|
56
|
+
it("does NOT emit if the cursor is no longer hovering at the dwell mark", () => {
|
|
57
|
+
const el = makeHoverable(false); // matches(":hover") === false at recheck
|
|
58
|
+
const record = makeRecord();
|
|
59
|
+
teardown = installHoverDetection(record, { dwellMs: 500 });
|
|
60
|
+
over(el);
|
|
61
|
+
vi.advanceTimersByTime(500);
|
|
62
|
+
out(el);
|
|
63
|
+
expect(record.addSailfishEvent).not.toHaveBeenCalled();
|
|
64
|
+
});
|
|
65
|
+
it("suppresses the hover when it ends in a click on the same element", () => {
|
|
66
|
+
const el = makeHoverable(true);
|
|
67
|
+
const record = makeRecord();
|
|
68
|
+
teardown = installHoverDetection(record, { dwellMs: 500 });
|
|
69
|
+
over(el);
|
|
70
|
+
vi.advanceTimersByTime(500); // dwell reached
|
|
71
|
+
down(el); // click -> suppress
|
|
72
|
+
out(el);
|
|
73
|
+
expect(record.addSailfishEvent).not.toHaveBeenCalled();
|
|
74
|
+
});
|
|
75
|
+
it("strict mode (default) ignores cursor:pointer-only elements", () => {
|
|
76
|
+
const div = document.createElement("div"); // no tooltip / menu / :hover rule
|
|
77
|
+
document.body.appendChild(div);
|
|
78
|
+
vi.spyOn(window, "getComputedStyle").mockReturnValue({
|
|
79
|
+
cursor: "pointer",
|
|
80
|
+
});
|
|
81
|
+
const record = makeRecord();
|
|
82
|
+
teardown = installHoverDetection(record, { dwellMs: 100 }); // strict default
|
|
83
|
+
over(div);
|
|
84
|
+
vi.advanceTimersByTime(200);
|
|
85
|
+
out(div);
|
|
86
|
+
expect(record.addSailfishEvent).not.toHaveBeenCalled();
|
|
87
|
+
});
|
|
88
|
+
it("non-strict mode accepts cursor:pointer as affordance", () => {
|
|
89
|
+
const div = document.createElement("div");
|
|
90
|
+
vi.spyOn(div, "matches").mockReturnValue(true); // :hover recheck passes
|
|
91
|
+
document.body.appendChild(div);
|
|
92
|
+
vi.spyOn(window, "getComputedStyle").mockReturnValue({
|
|
93
|
+
cursor: "pointer",
|
|
94
|
+
});
|
|
95
|
+
const record = makeRecord();
|
|
96
|
+
teardown = installHoverDetection(record, {
|
|
97
|
+
dwellMs: 100,
|
|
98
|
+
strictAffordance: false,
|
|
99
|
+
});
|
|
100
|
+
over(div);
|
|
101
|
+
vi.advanceTimersByTime(100);
|
|
102
|
+
out(div);
|
|
103
|
+
expect(record.addSailfishEvent).toHaveBeenCalledTimes(1);
|
|
104
|
+
});
|
|
105
|
+
it("does not emit for elements without hover affordance", () => {
|
|
106
|
+
const div = document.createElement("div");
|
|
107
|
+
document.body.appendChild(div);
|
|
108
|
+
const record = makeRecord();
|
|
109
|
+
teardown = installHoverDetection(record, { dwellMs: 500 });
|
|
110
|
+
over(div);
|
|
111
|
+
vi.advanceTimersByTime(1000);
|
|
112
|
+
out(div);
|
|
113
|
+
expect(record.addSailfishEvent).not.toHaveBeenCalled();
|
|
114
|
+
});
|
|
115
|
+
it("dedups repeated hovers on the same element within the cooldown", () => {
|
|
116
|
+
const el = makeHoverable(true);
|
|
117
|
+
const record = makeRecord();
|
|
118
|
+
teardown = installHoverDetection(record, {
|
|
119
|
+
dwellMs: 100,
|
|
120
|
+
cooldownMs: 3000,
|
|
121
|
+
});
|
|
122
|
+
// First hover cycle -> emits.
|
|
123
|
+
over(el);
|
|
124
|
+
vi.advanceTimersByTime(100);
|
|
125
|
+
out(el);
|
|
126
|
+
// Second hover cycle on the same element, well within cooldown -> suppressed.
|
|
127
|
+
over(el);
|
|
128
|
+
vi.advanceTimersByTime(100);
|
|
129
|
+
out(el);
|
|
130
|
+
expect(record.addSailfishEvent).toHaveBeenCalledTimes(1);
|
|
131
|
+
});
|
|
132
|
+
it("enforces the per-session cap", () => {
|
|
133
|
+
const record = makeRecord();
|
|
134
|
+
teardown = installHoverDetection(record, {
|
|
135
|
+
dwellMs: 100,
|
|
136
|
+
cooldownMs: 0,
|
|
137
|
+
maxPerSession: 1,
|
|
138
|
+
maxPerSecond: 1000,
|
|
139
|
+
});
|
|
140
|
+
const a = makeHoverable(true);
|
|
141
|
+
const b = makeHoverable(true);
|
|
142
|
+
record.mirror.getId.mockImplementation((n) => (n === a ? 1 : 2));
|
|
143
|
+
over(a);
|
|
144
|
+
vi.advanceTimersByTime(100);
|
|
145
|
+
out(a); // emits (1/1)
|
|
146
|
+
over(b);
|
|
147
|
+
vi.advanceTimersByTime(100);
|
|
148
|
+
out(b); // capped
|
|
149
|
+
expect(record.addSailfishEvent).toHaveBeenCalledTimes(1);
|
|
150
|
+
});
|
|
151
|
+
it("emits on the hard dwell cap when the cursor is parked indefinitely", () => {
|
|
152
|
+
const el = makeHoverable(true);
|
|
153
|
+
const record = makeRecord();
|
|
154
|
+
teardown = installHoverDetection(record, {
|
|
155
|
+
dwellMs: 500,
|
|
156
|
+
maxDwellMs: 5000,
|
|
157
|
+
});
|
|
158
|
+
over(el);
|
|
159
|
+
vi.advanceTimersByTime(5000); // never leaves; cap fires
|
|
160
|
+
expect(record.addSailfishEvent).toHaveBeenCalledTimes(1);
|
|
161
|
+
expect(record.addSailfishEvent.mock.calls[0][1].duration_ms).toBeGreaterThanOrEqual(5000);
|
|
162
|
+
});
|
|
163
|
+
it("skips emission when the element is not in the rrweb mirror", () => {
|
|
164
|
+
const el = makeHoverable(true);
|
|
165
|
+
const record = makeRecord(-1); // getId returns -1 (unmapped)
|
|
166
|
+
teardown = installHoverDetection(record, { dwellMs: 100 });
|
|
167
|
+
over(el);
|
|
168
|
+
vi.advanceTimersByTime(100);
|
|
169
|
+
out(el);
|
|
170
|
+
expect(record.addSailfishEvent).not.toHaveBeenCalled();
|
|
171
|
+
});
|
|
172
|
+
it("is idempotent: re-installing tears down the prior detector (no double-emit)", () => {
|
|
173
|
+
const el = makeHoverable(true);
|
|
174
|
+
// First install, then a second install on the SAME document (simulates a
|
|
175
|
+
// recorder re-init). The second must reclaim the first's listeners so a
|
|
176
|
+
// single hover fires exactly once, not once per stacked detector.
|
|
177
|
+
const record1 = makeRecord();
|
|
178
|
+
installHoverDetection(record1, { dwellMs: 100 });
|
|
179
|
+
const record2 = makeRecord();
|
|
180
|
+
teardown = installHoverDetection(record2, { dwellMs: 100 });
|
|
181
|
+
over(el);
|
|
182
|
+
vi.advanceTimersByTime(100);
|
|
183
|
+
out(el);
|
|
184
|
+
expect(record1.addSailfishEvent).not.toHaveBeenCalled(); // old detector gone
|
|
185
|
+
expect(record2.addSailfishEvent).toHaveBeenCalledTimes(1); // only the new one
|
|
186
|
+
});
|
|
187
|
+
});
|
|
Binary file
|
|
Binary file
|
package/dist/index.js
CHANGED
|
@@ -84,6 +84,14 @@ export const DEFAULT_CAPTURE_SETTINGS = {
|
|
|
84
84
|
blockSelector: "",
|
|
85
85
|
unmaskSelector: "",
|
|
86
86
|
maskInputOptions: { password: true },
|
|
87
|
+
// Hover action (DIAGPLAT-1790): on by default; dwell ≈ when a tooltip/label
|
|
88
|
+
// would appear. All tunable per-account via the captureSettings API payload.
|
|
89
|
+
recordHover: true,
|
|
90
|
+
hoverDwellMs: 500,
|
|
91
|
+
hoverStrictAffordance: true,
|
|
92
|
+
hoverCooldownMs: 3000,
|
|
93
|
+
hoverMaxPerSession: 300,
|
|
94
|
+
hoverMaxPerSecond: 1,
|
|
87
95
|
};
|
|
88
96
|
export const DEFAULT_CONSOLE_RECORDING_SETTINGS = {
|
|
89
97
|
level: ["info", "log", "warn", "error"],
|
|
@@ -644,6 +652,15 @@ function setupXMLHttpRequestInterceptor(domainsToNotPropagateHeaderTo = [], body
|
|
|
644
652
|
return originalSend.apply(this, args);
|
|
645
653
|
};
|
|
646
654
|
}
|
|
655
|
+
// A rejected fetch carries no HTTP status — the browser hides it for CORS and
|
|
656
|
+
// network failures — so never fabricate one (DIAGPLAT-1699: a CORS-blocked 401
|
|
657
|
+
// was reported as 500). Prefer a status attached by an HTTP-lib wrapper
|
|
658
|
+
// (axios-style error.response.status), then the last status actually observed
|
|
659
|
+
// on the wire (e.g. the 400/403 that triggered the header-less retry which
|
|
660
|
+
// then threw), else 0 = "no response received", matching the XHR error path.
|
|
661
|
+
export function resolveFailedFetchStatus(error, observedStatus) {
|
|
662
|
+
return error?.response?.status ?? observedStatus ?? 0;
|
|
663
|
+
}
|
|
647
664
|
// Updated fetch interceptor with exclusion handling
|
|
648
665
|
function setupFetchInterceptor(domainsToNotPropagateHeadersTo = [], bodyCaptureConfig = { captureStreamingResponseBody: true, captureResponseBodyMaxMb: 10, captureStreamPrefixKb: 64, captureStreamTimeoutMs: 10000 }, domainsToPropagateHeaderTo = []) {
|
|
649
666
|
const originalFetch = window.fetch;
|
|
@@ -830,8 +847,12 @@ function setupFetchInterceptor(domainsToNotPropagateHeadersTo = [], bodyCaptureC
|
|
|
830
847
|
}
|
|
831
848
|
// Mask authorization header for privacy (preserve scheme like "Bearer", mask the token)
|
|
832
849
|
maskAuthorizationHeader(requestHeaders);
|
|
850
|
+
// Last HTTP status actually received, so the catch block can report it if
|
|
851
|
+
// a later step (e.g. the header-less retry) throws.
|
|
852
|
+
let observedStatus;
|
|
833
853
|
try {
|
|
834
854
|
let response = await injectHeader(target, thisArg, input, init, sessionId, urlAndStoredUuids.page_visit_uuid, networkUUID);
|
|
855
|
+
observedStatus = response.status;
|
|
835
856
|
let isRetry = false;
|
|
836
857
|
// Retry logic for 400/403 before logging finished event
|
|
837
858
|
if (BAD_HTTP_STATUS.includes(response.status)) {
|
|
@@ -839,6 +860,7 @@ function setupFetchInterceptor(domainsToNotPropagateHeadersTo = [], bodyCaptureC
|
|
|
839
860
|
// Remove x-sf3-rid from captured headers since retry doesn't include it
|
|
840
861
|
delete requestHeaders[xSf3RidHeader];
|
|
841
862
|
response = await retryWithoutPropagateHeaders(target, thisArg, args, url);
|
|
863
|
+
observedStatus = response.status;
|
|
842
864
|
isRetry = true;
|
|
843
865
|
}
|
|
844
866
|
const endTime = Date.now();
|
|
@@ -956,7 +978,7 @@ function setupFetchInterceptor(domainsToNotPropagateHeadersTo = [], bodyCaptureC
|
|
|
956
978
|
catch (error) {
|
|
957
979
|
const endTime = Date.now();
|
|
958
980
|
const success = false;
|
|
959
|
-
const responseCode = error
|
|
981
|
+
const responseCode = resolveFailedFetchStatus(error, observedStatus);
|
|
960
982
|
const errorMessage = error.message || "Fetch request failed";
|
|
961
983
|
if (error instanceof TypeError &&
|
|
962
984
|
error?.message?.toLowerCase().includes(CORS_KEYWORD.toLowerCase())) {
|
package/dist/index.js.br
CHANGED
|
Binary file
|
package/dist/index.js.gz
CHANGED
|
Binary file
|
package/dist/recorder.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
const e = require("./chunks/index-
|
|
4
|
-
require("./chunks/patch2d-9voowhJ_.js"), exports.DEFAULT_CAPTURE_SETTINGS = e.DEFAULT_CAPTURE_SETTINGS, exports.DEFAULT_CONSOLE_RECORDING_SETTINGS = e.DEFAULT_CONSOLE_RECORDING_SETTINGS, exports.STORAGE_VERSION = e.STORAGE_VERSION, exports.addOrUpdateMetadata = e.addOrUpdateMetadata, exports.buildBatches = e.buildBatches, exports.clearStaleFuncSpanState = e.clearStaleFuncSpanState, exports.createSkipHeadersPropagationChecker = e.createSkipHeadersPropagationChecker, exports.createTriageAndIssueFromRecorder = e.createTriageAndIssueFromRecorder, exports.createTriageFromRecorder = e.createTriageFromRecorder, exports.disableFunctionSpanTracking = e.disableFunctionSpanTracking, exports.enableFunctionSpanTracking = e.enableFunctionSpanTracking, exports.ensureHrefCache = e.ensureHrefCache, exports.eventSize = e.eventSize, exports.fetchAndSendIp = e.fetchAndSendIp, exports.fetchCaptureSettings = e.fetchCaptureSettings, exports.fetchEngineeringTicketPlatformIntegrations = e.fetchEngineeringTicketPlatformIntegrations, exports.fetchFunctionSpanTrackingEnabled = e.fetchFunctionSpanTrackingEnabled, exports.flushBufferedEvents = e.flushBufferedEvents, exports.getBinaryDropCount = e.getBinaryDropCount, exports.getBufferedAmount = e.getBufferedAmount, exports.getCachedHref = e.getCachedHref, exports.getCachedHrefNoQuery = e.getCachedHrefNoQuery, exports.getFuncSpanHeader = e.getFuncSpanHeader, exports.getIdentifiedUser = e.getIdentifiedUser, exports.getOrSetSessionId = e.getOrSetSessionId, exports.getUrlAndStoredUuids = e.getUrlAndStoredUuids, exports.identify = e.identify, exports.initRecorder = e.initRecorder, exports.initializeConsolePlugin = e.initializeConsolePlugin, exports.initializeDomContentEvents = e.initializeDomContentEvents, exports.initializeFunctionSpanTrackingFromApi = e.initializeFunctionSpanTrackingFromApi, exports.initializePerformancePlugin = e.initializePerformancePlugin, exports.initializeRecording = e.initializeRecording, exports.initializeWebSocket = e.initializeWebSocket, exports.invalidateUrlCache = e.invalidateUrlCache, exports.isFunctionSpanTrackingEnabled = e.isFunctionSpanTrackingEnabled, exports.isTransportSaturated = e.isTransportSaturated, exports.maskCanvasDraws = e.maskCanvasDraws, exports.maskInputFn = e.maskInputFn, exports.matchUrlWithWildcard = e.matchUrlWithWildcard, Object.defineProperty(exports, "nowTimestamp", { enumerable: true, get: () => e.nowTimestamp }), exports.onNavigationChange = e.onNavigationChange, exports.openReportIssueModal = e.openReportIssueModal, exports.registerChart = e.registerChart, exports.registerFabricCanvas = e.registerFabricCanvas, exports.registerKonvaStage = e.registerKonvaStage, exports.registerPixiApp = e.registerPixiApp, exports.registerThreeScene = e.registerThreeScene, exports.requestTimeSync = e.requestTimeSync, exports.restoreFuncSpanState = e.restoreFuncSpanState, exports.sendDomainsToNotPropagateHeaderTo = e.sendDomainsToNotPropagateHeaderTo, exports.sendEvent = e.sendEvent, exports.sendGraphQLRequest = e.sendGraphQLRequest, exports.sendMessage = e.sendMessage, exports.startRecording = e.startRecording, exports.startRecordingSession = e.startRecordingSession, exports.toAbsoluteUrl = e.toAbsoluteUrl, exports.trackingEvent = e.trackingEvent, exports.withAppUrlMetadata = e.withAppUrlMetadata, exports.wsSendBinary = e.wsSendBinary;
|
|
3
|
+
const e = require("./chunks/index-Cp1vLB5Q.js");
|
|
4
|
+
require("./chunks/patch2d-9voowhJ_.js"), exports.DEFAULT_CAPTURE_SETTINGS = e.DEFAULT_CAPTURE_SETTINGS, exports.DEFAULT_CONSOLE_RECORDING_SETTINGS = e.DEFAULT_CONSOLE_RECORDING_SETTINGS, exports.STORAGE_VERSION = e.STORAGE_VERSION, exports.addOrUpdateMetadata = e.addOrUpdateMetadata, exports.buildBatches = e.buildBatches, exports.clearStaleFuncSpanState = e.clearStaleFuncSpanState, exports.createSkipHeadersPropagationChecker = e.createSkipHeadersPropagationChecker, exports.createTriageAndIssueFromRecorder = e.createTriageAndIssueFromRecorder, exports.createTriageFromRecorder = e.createTriageFromRecorder, exports.disableFunctionSpanTracking = e.disableFunctionSpanTracking, exports.enableFunctionSpanTracking = e.enableFunctionSpanTracking, exports.ensureHrefCache = e.ensureHrefCache, exports.eventSize = e.eventSize, exports.fetchAndSendIp = e.fetchAndSendIp, exports.fetchCaptureSettings = e.fetchCaptureSettings, exports.fetchEngineeringTicketPlatformIntegrations = e.fetchEngineeringTicketPlatformIntegrations, exports.fetchFunctionSpanTrackingEnabled = e.fetchFunctionSpanTrackingEnabled, exports.flushBufferedEvents = e.flushBufferedEvents, exports.getBinaryDropCount = e.getBinaryDropCount, exports.getBufferedAmount = e.getBufferedAmount, exports.getCachedHref = e.getCachedHref, exports.getCachedHrefNoQuery = e.getCachedHrefNoQuery, exports.getFuncSpanHeader = e.getFuncSpanHeader, exports.getIdentifiedUser = e.getIdentifiedUser, exports.getOrSetSessionId = e.getOrSetSessionId, exports.getUrlAndStoredUuids = e.getUrlAndStoredUuids, exports.identify = e.identify, exports.initRecorder = e.initRecorder, exports.initializeConsolePlugin = e.initializeConsolePlugin, exports.initializeDomContentEvents = e.initializeDomContentEvents, exports.initializeFunctionSpanTrackingFromApi = e.initializeFunctionSpanTrackingFromApi, exports.initializePerformancePlugin = e.initializePerformancePlugin, exports.initializeRecording = e.initializeRecording, exports.initializeWebSocket = e.initializeWebSocket, exports.invalidateUrlCache = e.invalidateUrlCache, exports.isFunctionSpanTrackingEnabled = e.isFunctionSpanTrackingEnabled, exports.isTransportSaturated = e.isTransportSaturated, exports.maskCanvasDraws = e.maskCanvasDraws, exports.maskInputFn = e.maskInputFn, exports.matchUrlWithWildcard = e.matchUrlWithWildcard, Object.defineProperty(exports, "nowTimestamp", { enumerable: true, get: () => e.nowTimestamp }), exports.onNavigationChange = e.onNavigationChange, exports.openReportIssueModal = e.openReportIssueModal, exports.registerChart = e.registerChart, exports.registerFabricCanvas = e.registerFabricCanvas, exports.registerKonvaStage = e.registerKonvaStage, exports.registerPixiApp = e.registerPixiApp, exports.registerThreeScene = e.registerThreeScene, exports.requestTimeSync = e.requestTimeSync, exports.resolveFailedFetchStatus = e.resolveFailedFetchStatus, exports.restoreFuncSpanState = e.restoreFuncSpanState, exports.sendDomainsToNotPropagateHeaderTo = e.sendDomainsToNotPropagateHeaderTo, exports.sendEvent = e.sendEvent, exports.sendGraphQLRequest = e.sendGraphQLRequest, exports.sendMessage = e.sendMessage, exports.startRecording = e.startRecording, exports.startRecordingSession = e.startRecordingSession, exports.toAbsoluteUrl = e.toAbsoluteUrl, exports.trackingEvent = e.trackingEvent, exports.withAppUrlMetadata = e.withAppUrlMetadata, exports.wsSendBinary = e.wsSendBinary;
|
package/dist/recorder.cjs.br
CHANGED
|
Binary file
|
package/dist/recorder.cjs.gz
CHANGED
|
Binary file
|