@sailfish-ai/recorder 1.11.5 → 1.12.4
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/README.md +19 -1
- package/dist/chunkSerializer.js +70 -23
- package/dist/chunkSerializer.js.br +0 -0
- package/dist/chunkSerializer.js.gz +0 -0
- package/dist/chunks/chunkSerializer-DDukZpgl.js +116 -0
- package/dist/chunks/chunkSerializer-DDukZpgl.js.br +0 -0
- package/dist/chunks/chunkSerializer-DDukZpgl.js.gz +0 -0
- package/dist/chunks/chunkSerializer-FQtY90Av.js +115 -0
- package/dist/chunks/chunkSerializer-FQtY90Av.js.br +0 -0
- package/dist/chunks/chunkSerializer-FQtY90Av.js.gz +0 -0
- package/dist/chunks/{index-DiGs9it7.js → index-C-qbsfKe.js} +724 -548
- package/dist/chunks/index-C-qbsfKe.js.br +0 -0
- package/dist/chunks/index-C-qbsfKe.js.gz +0 -0
- package/dist/chunks/{index-CIK1iDN9.js → index-D6axlCRu.js} +757 -577
- package/dist/chunks/index-D6axlCRu.js.br +0 -0
- package/dist/chunks/index-D6axlCRu.js.gz +0 -0
- package/dist/clockSync.js +196 -0
- package/dist/clockSync.js.br +0 -0
- package/dist/clockSync.js.gz +0 -0
- package/dist/errorInterceptor.js +42 -4
- package/dist/errorInterceptor.js.br +0 -0
- package/dist/errorInterceptor.js.gz +0 -0
- package/dist/graphql.js +5 -0
- package/dist/graphql.js.br +0 -0
- package/dist/graphql.js.gz +0 -0
- package/dist/inAppReportIssueModal/index.js +4 -1
- package/dist/inAppReportIssueModal/index.js.br +0 -0
- package/dist/inAppReportIssueModal/index.js.gz +0 -0
- package/dist/inAppReportIssueModal/integrations.js +36 -0
- package/dist/inAppReportIssueModal/integrations.js.br +0 -0
- package/dist/inAppReportIssueModal/integrations.js.gz +0 -0
- package/dist/inAppReportIssueModal/state.js +8 -0
- package/dist/inAppReportIssueModal/state.js.br +0 -0
- package/dist/inAppReportIssueModal/state.js.gz +0 -0
- package/dist/index.js +67 -5
- package/dist/index.js.br +0 -0
- package/dist/index.js.gz +0 -0
- package/dist/privacyMask.js +93 -0
- package/dist/privacyMask.js.br +0 -0
- package/dist/privacyMask.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 +17 -14
- package/dist/recorder.js.br +0 -0
- package/dist/recorder.js.gz +0 -0
- package/dist/recorder.umd.cjs +1338 -1140
- package/dist/recorder.umd.cjs.br +0 -0
- package/dist/recorder.umd.cjs.gz +0 -0
- package/dist/recording.js +84 -13
- package/dist/recording.js.br +0 -0
- package/dist/recording.js.gz +0 -0
- package/dist/types/chunkSerializer.d.ts +14 -0
- package/dist/types/clockSync.d.ts +70 -0
- package/dist/types/inAppReportIssueModal/integrations.d.ts +1 -0
- package/dist/types/inAppReportIssueModal/state.d.ts +2 -0
- package/dist/types/index.d.ts +16 -2
- package/dist/types/privacyMask.d.ts +46 -0
- package/dist/types/recording.d.ts +1 -0
- package/dist/types/types.d.ts +23 -0
- package/dist/types/websocket.d.ts +1 -0
- package/dist/websocket.js +111 -0
- package/dist/websocket.js.br +0 -0
- package/dist/websocket.js.gz +0 -0
- package/package.json +1 -1
- package/dist/chunks/chunkSerializer-C8qtomKe.js +0 -95
- package/dist/chunks/chunkSerializer-C8qtomKe.js.br +0 -0
- package/dist/chunks/chunkSerializer-C8qtomKe.js.gz +0 -0
- package/dist/chunks/chunkSerializer-RWnu-UfC.js +0 -94
- package/dist/chunks/chunkSerializer-RWnu-UfC.js.br +0 -0
- package/dist/chunks/chunkSerializer-RWnu-UfC.js.gz +0 -0
- package/dist/chunks/index-CIK1iDN9.js.br +0 -0
- package/dist/chunks/index-CIK1iDN9.js.gz +0 -0
- package/dist/chunks/index-DiGs9it7.js.br +0 -0
- package/dist/chunks/index-DiGs9it7.js.gz +0 -0
package/README.md
CHANGED
|
@@ -131,7 +131,7 @@ await initRecorder({
|
|
|
131
131
|
| `serviceAdditionalMetadata` | `Record<string, any>` | — | Arbitrary metadata attached to every session. |
|
|
132
132
|
| `domainsToPropagateHeaderTo` | `string[]` | `[]` | Domains (wildcards allowed) that receive the `X-Sf3-Rid` tracing header, letting Sailfish join frontend sessions with backend traces. |
|
|
133
133
|
| `domainsToNotPropagateHeaderTo` | `string[]` | `[]` | Domains to exclude, appended to the built-in denylist. |
|
|
134
|
-
| `enableFiberTracking` | `boolean` | `false` | Adds `data-sf-source="file.tsx:42"` attributes to DOM nodes for source-mapped coverage (React only). Pairs with the Babel plugin. |
|
|
134
|
+
| `enableFiberTracking` | `boolean` | `false` | **Superseded — see note below.** Adds `data-sf-source="file.tsx:42"` attributes to DOM nodes for source-mapped coverage (React only). Pairs with the legacy Babel plugin. Disabled by default and retained for backward compatibility only; new integrations should use the build plugin's `data-sf-id` path instead. |
|
|
135
135
|
| `enableIpTracking` | `boolean` | `false` | Fetches the visitor IP asynchronously for session metadata. |
|
|
136
136
|
| `captureStreamingResponseBody` | `boolean` | `true` | Capture prefixes of streaming (SSE, ndjson, chunked) responses. |
|
|
137
137
|
| `captureResponseBodyMaxMb` | `number` | `10` | Max non-streaming response body to capture, in MB. `0` disables body capture. |
|
|
@@ -325,6 +325,24 @@ Place `<SailfishProvider>` in `app/layout.tsx`.
|
|
|
325
325
|
|
|
326
326
|
## Babel plugin — source-mapped coverage
|
|
327
327
|
|
|
328
|
+
> **⚠️ Deprecated / superseded.** The `@sailfish-ai/recorder/babel-plugin`
|
|
329
|
+
> (which injects `data-sf-source="file.tsx:42"`) and the matching
|
|
330
|
+
> `enableFiberTracking` recorder option are **legacy** and are **disabled by
|
|
331
|
+
> default**. They are retained for backward compatibility only and are no longer
|
|
332
|
+
> the recommended way to get click-to-code / source-mapped coverage.
|
|
333
|
+
>
|
|
334
|
+
> New integrations should use the **build plugin** (`sf-map-utils` — the
|
|
335
|
+
> Vite / Rollup / esbuild / webpack adapters, or `withSailfish(next.config)` for
|
|
336
|
+
> Next.js / SWC), which stamps the canonical **`data-sf-id`** attribute at build
|
|
337
|
+
> time and uploads a per-module source manifest. This is the path click-to-code
|
|
338
|
+
> resolution is built against.
|
|
339
|
+
>
|
|
340
|
+
> **Coexistence:** both attributes may appear on the same element if you enable
|
|
341
|
+
> both paths during a migration. This is safe — the click-to-code resolver
|
|
342
|
+
> **prefers `data-sf-id`** and only falls back to `data-sf-source` / fiber data
|
|
343
|
+
> when no `data-sf-id` is present. There is no need to remove the Babel plugin
|
|
344
|
+
> before adopting the build plugin; you can migrate incrementally.
|
|
345
|
+
|
|
328
346
|
`@sailfish-ai/recorder/babel-plugin` injects `data-sf-source="file.tsx:42"` attributes into your JSX at compile time so clicks and views in the Sailfish dashboard link back to the exact source line.
|
|
329
347
|
|
|
330
348
|
### Vite + React
|
package/dist/chunkSerializer.js
CHANGED
|
@@ -26,7 +26,51 @@ const CHUNK_ID_BASE = 100_001;
|
|
|
26
26
|
export async function chunkedSnapshot(doc, mirror, options = {}) {
|
|
27
27
|
const chunkSize = options.chunkSize ?? 500;
|
|
28
28
|
const maxChunkMs = options.maxChunkMs ?? 16;
|
|
29
|
-
const { blockClass, blockSelector, maskTextClass, maskTextSelector } = options;
|
|
29
|
+
const { blockClass, blockSelector, maskTextClass, maskTextSelector, maskInputSelector, maskInputOptions, maskInputFn, maskTextFn, unmaskSelector, } = options;
|
|
30
|
+
/** Safe wrapper — a malformed user-supplied selector must not crash the walker. */
|
|
31
|
+
function matchesSelectorSafe(el, selector) {
|
|
32
|
+
if (!selector)
|
|
33
|
+
return false;
|
|
34
|
+
try {
|
|
35
|
+
return el.matches(selector);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function closestSelectorSafe(el, selector) {
|
|
42
|
+
if (!el || !selector)
|
|
43
|
+
return false;
|
|
44
|
+
try {
|
|
45
|
+
return !!el.closest(selector);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function isUnmasked(el) {
|
|
52
|
+
return closestSelectorSafe(el, unmaskSelector);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Decide whether an input/textarea/select's value should be masked, given
|
|
56
|
+
* the configured maskInputOptions and maskInputSelector. unmaskSelector
|
|
57
|
+
* (checked separately at call sites) overrides this.
|
|
58
|
+
*/
|
|
59
|
+
function shouldMaskInputValue(el) {
|
|
60
|
+
const tag = el.tagName;
|
|
61
|
+
if (tag === "TEXTAREA" && maskInputOptions?.textarea)
|
|
62
|
+
return true;
|
|
63
|
+
if (tag === "SELECT" && maskInputOptions?.select)
|
|
64
|
+
return true;
|
|
65
|
+
if (tag === "INPUT") {
|
|
66
|
+
const inputType = el.type || "text";
|
|
67
|
+
if (maskInputOptions?.[inputType])
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
if (matchesSelectorSafe(el, maskInputSelector))
|
|
71
|
+
return true;
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
30
74
|
let nextId = CHUNK_ID_BASE;
|
|
31
75
|
let nodeCount = 0;
|
|
32
76
|
let chunkStart = performance.now();
|
|
@@ -46,34 +90,26 @@ export async function chunkedSnapshot(doc, mirror, options = {}) {
|
|
|
46
90
|
}
|
|
47
91
|
}
|
|
48
92
|
function isBlocked(el) {
|
|
93
|
+
// unmaskSelector exempts elements from block as well as mask.
|
|
94
|
+
if (isUnmasked(el))
|
|
95
|
+
return false;
|
|
49
96
|
if (blockClass && el.classList?.contains(blockClass))
|
|
50
97
|
return true;
|
|
51
|
-
if (blockSelector)
|
|
52
|
-
|
|
53
|
-
if (el.matches(blockSelector))
|
|
54
|
-
return true;
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
/* invalid selector */
|
|
58
|
-
}
|
|
59
|
-
}
|
|
98
|
+
if (matchesSelectorSafe(el, blockSelector))
|
|
99
|
+
return true;
|
|
60
100
|
return false;
|
|
61
101
|
}
|
|
62
102
|
function shouldMaskText(node) {
|
|
63
103
|
const parent = node.parentElement;
|
|
64
104
|
if (!parent)
|
|
65
105
|
return false;
|
|
106
|
+
// unmaskSelector overrides every other mask rule — short-circuit early.
|
|
107
|
+
if (isUnmasked(parent))
|
|
108
|
+
return false;
|
|
66
109
|
if (maskTextClass && parent.closest(`.${maskTextClass}`))
|
|
67
110
|
return true;
|
|
68
|
-
if (maskTextSelector)
|
|
69
|
-
|
|
70
|
-
if (parent.closest(maskTextSelector))
|
|
71
|
-
return true;
|
|
72
|
-
}
|
|
73
|
-
catch {
|
|
74
|
-
/* invalid selector */
|
|
75
|
-
}
|
|
76
|
-
}
|
|
111
|
+
if (closestSelectorSafe(parent, maskTextSelector))
|
|
112
|
+
return true;
|
|
77
113
|
return false;
|
|
78
114
|
}
|
|
79
115
|
function getAttributes(el) {
|
|
@@ -83,11 +119,18 @@ export async function chunkedSnapshot(doc, mirror, options = {}) {
|
|
|
83
119
|
attrs[attr.name] = attr.value;
|
|
84
120
|
}
|
|
85
121
|
const tag = el.tagName;
|
|
86
|
-
// Capture live input/textarea/select values
|
|
122
|
+
// Capture live input/textarea/select values, applying Form Privacy Rules.
|
|
87
123
|
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") {
|
|
88
124
|
const input = el;
|
|
89
125
|
if (input.value !== undefined && input.value !== "") {
|
|
90
|
-
|
|
126
|
+
let value = input.value;
|
|
127
|
+
const mask = !isUnmasked(el) && shouldMaskInputValue(el);
|
|
128
|
+
if (mask) {
|
|
129
|
+
value = maskInputFn
|
|
130
|
+
? maskInputFn(value, el)
|
|
131
|
+
: "*".repeat(value.length);
|
|
132
|
+
}
|
|
133
|
+
attrs.value = value;
|
|
91
134
|
}
|
|
92
135
|
}
|
|
93
136
|
if (tag === "INPUT") {
|
|
@@ -168,9 +211,13 @@ export async function chunkedSnapshot(doc, mirror, options = {}) {
|
|
|
168
211
|
let textContent = node.textContent || "";
|
|
169
212
|
const parent = node.parentElement;
|
|
170
213
|
const isStyle = parent?.tagName === "STYLE" || undefined;
|
|
171
|
-
// Mask text content when under maskTextClass/maskTextSelector
|
|
214
|
+
// Mask text content when under maskTextClass/maskTextSelector.
|
|
215
|
+
// Defer to caller-supplied maskTextFn when provided so the same
|
|
216
|
+
// unmask shim used by rrweb's record path also applies here.
|
|
172
217
|
if (!isStyle && shouldMaskText(node) && textContent) {
|
|
173
|
-
textContent =
|
|
218
|
+
textContent = maskTextFn
|
|
219
|
+
? maskTextFn(textContent, parent)
|
|
220
|
+
: textContent.replace(/\S/g, "*");
|
|
174
221
|
}
|
|
175
222
|
const result = {
|
|
176
223
|
type: TEXT,
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { y as e } from "./index-D6axlCRu.js";
|
|
2
|
+
async function chunkedSnapshot(t, n, s = {}) {
|
|
3
|
+
const o = s.chunkSize ?? 500, r = s.maxChunkMs ?? 16, { blockClass: a, blockSelector: c, maskTextClass: i, maskTextSelector: u, maskInputSelector: d, maskInputOptions: l, maskInputFn: f, maskTextFn: N, unmaskSelector: m } = s;
|
|
4
|
+
function matchesSelectorSafe(e2, t2) {
|
|
5
|
+
if (!t2) return false;
|
|
6
|
+
try {
|
|
7
|
+
return e2.matches(t2);
|
|
8
|
+
} catch {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function closestSelectorSafe(e2, t2) {
|
|
13
|
+
if (!e2 || !t2) return false;
|
|
14
|
+
try {
|
|
15
|
+
return !!e2.closest(t2);
|
|
16
|
+
} catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function isUnmasked(e2) {
|
|
21
|
+
return closestSelectorSafe(e2, m);
|
|
22
|
+
}
|
|
23
|
+
let h = 100001, p = 0, S = performance.now();
|
|
24
|
+
function genId(e2) {
|
|
25
|
+
if (n.hasNode(e2)) return n.getId(e2);
|
|
26
|
+
const t2 = h++;
|
|
27
|
+
return n.add(e2, { id: t2 }), t2;
|
|
28
|
+
}
|
|
29
|
+
function getAttributes(e2) {
|
|
30
|
+
const t2 = {};
|
|
31
|
+
for (let n3 = 0; n3 < e2.attributes.length; n3++) {
|
|
32
|
+
const s2 = e2.attributes[n3];
|
|
33
|
+
t2[s2.name] = s2.value;
|
|
34
|
+
}
|
|
35
|
+
const n2 = e2.tagName;
|
|
36
|
+
if ("INPUT" === n2 || "TEXTAREA" === n2 || "SELECT" === n2) {
|
|
37
|
+
const n3 = e2;
|
|
38
|
+
if (void 0 !== n3.value && "" !== n3.value) {
|
|
39
|
+
let s2 = n3.value;
|
|
40
|
+
const o2 = !isUnmasked(e2) && (function shouldMaskInputValue(e3) {
|
|
41
|
+
const t3 = e3.tagName;
|
|
42
|
+
if ("TEXTAREA" === t3 && (l == null ? void 0 : l.textarea)) return true;
|
|
43
|
+
if ("SELECT" === t3 && (l == null ? void 0 : l.select)) return true;
|
|
44
|
+
if ("INPUT" === t3) {
|
|
45
|
+
const t4 = e3.type || "text";
|
|
46
|
+
if (l == null ? void 0 : l[t4]) return true;
|
|
47
|
+
}
|
|
48
|
+
return !!matchesSelectorSafe(e3, d);
|
|
49
|
+
})(e2);
|
|
50
|
+
o2 && (s2 = f ? f(s2, e2) : "*".repeat(s2.length)), t2.value = s2;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if ("INPUT" === n2) {
|
|
54
|
+
const n3 = e2;
|
|
55
|
+
"checkbox" !== n3.type && "radio" !== n3.type || (t2.checked = n3.checked);
|
|
56
|
+
}
|
|
57
|
+
return "OPTION" === n2 && (t2.selected = e2.selected), t2;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
return await (async function serialize(n2) {
|
|
61
|
+
if (n2 !== t && !n2.parentNode) return null;
|
|
62
|
+
switch (await (async function maybeYield() {
|
|
63
|
+
p++, (p % o === 0 || performance.now() - S > r) && (await e(), S = performance.now());
|
|
64
|
+
})(), n2.nodeType) {
|
|
65
|
+
case Node.DOCUMENT_NODE: {
|
|
66
|
+
const e2 = genId(n2), t2 = [];
|
|
67
|
+
for (const e3 of Array.from(n2.childNodes)) {
|
|
68
|
+
const n3 = await serialize(e3);
|
|
69
|
+
n3 && t2.push(n3);
|
|
70
|
+
}
|
|
71
|
+
return { type: 0, childNodes: t2, id: e2 };
|
|
72
|
+
}
|
|
73
|
+
case Node.DOCUMENT_TYPE_NODE: {
|
|
74
|
+
const e2 = n2;
|
|
75
|
+
return { type: 1, childNodes: [], id: genId(n2), name: e2.name, publicId: e2.publicId, systemId: e2.systemId };
|
|
76
|
+
}
|
|
77
|
+
case Node.ELEMENT_NODE: {
|
|
78
|
+
const e2 = n2, t2 = genId(n2), s2 = e2.tagName.toLowerCase(), o2 = "http://www.w3.org/2000/svg" === e2.namespaceURI || void 0;
|
|
79
|
+
if ((function isBlocked(e3) {
|
|
80
|
+
var _a;
|
|
81
|
+
return !(isUnmasked(e3) || (!a || !((_a = e3.classList) == null ? void 0 : _a.contains(a))) && !matchesSelectorSafe(e3, c));
|
|
82
|
+
})(e2)) return { type: 2, tagName: s2, attributes: getAttributes(e2), childNodes: [], id: t2, isSVG: o2, needBlock: true };
|
|
83
|
+
const r2 = [];
|
|
84
|
+
for (const t3 of Array.from(e2.childNodes)) {
|
|
85
|
+
if (!t3.parentNode) continue;
|
|
86
|
+
const e3 = await serialize(t3);
|
|
87
|
+
e3 && r2.push(e3);
|
|
88
|
+
}
|
|
89
|
+
const i2 = { type: 2, tagName: s2, attributes: getAttributes(e2), childNodes: r2, id: t2 };
|
|
90
|
+
return o2 && (i2.isSVG = true), i2;
|
|
91
|
+
}
|
|
92
|
+
case Node.TEXT_NODE: {
|
|
93
|
+
let e2 = n2.textContent || "";
|
|
94
|
+
const t2 = n2.parentElement, s2 = "STYLE" === (t2 == null ? void 0 : t2.tagName) || void 0;
|
|
95
|
+
!s2 && (function shouldMaskText(e3) {
|
|
96
|
+
const t3 = e3.parentElement;
|
|
97
|
+
return !(!t3 || isUnmasked(t3) || (!i || !t3.closest(`.${i}`)) && !closestSelectorSafe(t3, u));
|
|
98
|
+
})(n2) && e2 && (e2 = N ? N(e2, t2) : e2.replace(/\S/g, "*"));
|
|
99
|
+
const o2 = { type: 3, textContent: e2, childNodes: [], id: genId(n2) };
|
|
100
|
+
return s2 && (o2.isStyle = true), o2;
|
|
101
|
+
}
|
|
102
|
+
case Node.COMMENT_NODE:
|
|
103
|
+
return { type: 5, textContent: n2.textContent || "", childNodes: [], id: genId(n2) };
|
|
104
|
+
case Node.CDATA_SECTION_NODE:
|
|
105
|
+
return { type: 4, textContent: "", childNodes: [], id: genId(n2) };
|
|
106
|
+
default:
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
})(t);
|
|
110
|
+
} catch (e2) {
|
|
111
|
+
return console.warn("[Sailfish] chunkSnapshot serialization error:", e2), null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
export {
|
|
115
|
+
chunkedSnapshot
|
|
116
|
+
};
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const e = require("./index-C-qbsfKe.js");
|
|
4
|
+
exports.chunkedSnapshot = async function chunkedSnapshot(t, n, s = {}) {
|
|
5
|
+
const o = s.chunkSize ?? 500, r = s.maxChunkMs ?? 16, { blockClass: a, blockSelector: c, maskTextClass: i, maskTextSelector: u, maskInputSelector: d, maskInputOptions: l, maskInputFn: f, maskTextFn: N, unmaskSelector: h } = s;
|
|
6
|
+
function matchesSelectorSafe(e2, t2) {
|
|
7
|
+
if (!t2) return false;
|
|
8
|
+
try {
|
|
9
|
+
return e2.matches(t2);
|
|
10
|
+
} catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function closestSelectorSafe(e2, t2) {
|
|
15
|
+
if (!e2 || !t2) return false;
|
|
16
|
+
try {
|
|
17
|
+
return !!e2.closest(t2);
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function isUnmasked(e2) {
|
|
23
|
+
return closestSelectorSafe(e2, h);
|
|
24
|
+
}
|
|
25
|
+
let m = 100001, p = 0, S = performance.now();
|
|
26
|
+
function genId(e2) {
|
|
27
|
+
if (n.hasNode(e2)) return n.getId(e2);
|
|
28
|
+
const t2 = m++;
|
|
29
|
+
return n.add(e2, { id: t2 }), t2;
|
|
30
|
+
}
|
|
31
|
+
function getAttributes(e2) {
|
|
32
|
+
const t2 = {};
|
|
33
|
+
for (let n3 = 0; n3 < e2.attributes.length; n3++) {
|
|
34
|
+
const s2 = e2.attributes[n3];
|
|
35
|
+
t2[s2.name] = s2.value;
|
|
36
|
+
}
|
|
37
|
+
const n2 = e2.tagName;
|
|
38
|
+
if ("INPUT" === n2 || "TEXTAREA" === n2 || "SELECT" === n2) {
|
|
39
|
+
const n3 = e2;
|
|
40
|
+
if (void 0 !== n3.value && "" !== n3.value) {
|
|
41
|
+
let s2 = n3.value;
|
|
42
|
+
const o2 = !isUnmasked(e2) && (function shouldMaskInputValue(e3) {
|
|
43
|
+
const t3 = e3.tagName;
|
|
44
|
+
if ("TEXTAREA" === t3 && (l == null ? void 0 : l.textarea)) return true;
|
|
45
|
+
if ("SELECT" === t3 && (l == null ? void 0 : l.select)) return true;
|
|
46
|
+
if ("INPUT" === t3) {
|
|
47
|
+
const t4 = e3.type || "text";
|
|
48
|
+
if (l == null ? void 0 : l[t4]) return true;
|
|
49
|
+
}
|
|
50
|
+
return !!matchesSelectorSafe(e3, d);
|
|
51
|
+
})(e2);
|
|
52
|
+
o2 && (s2 = f ? f(s2, e2) : "*".repeat(s2.length)), t2.value = s2;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if ("INPUT" === n2) {
|
|
56
|
+
const n3 = e2;
|
|
57
|
+
"checkbox" !== n3.type && "radio" !== n3.type || (t2.checked = n3.checked);
|
|
58
|
+
}
|
|
59
|
+
return "OPTION" === n2 && (t2.selected = e2.selected), t2;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
return await (async function serialize(n2) {
|
|
63
|
+
if (n2 !== t && !n2.parentNode) return null;
|
|
64
|
+
switch (await (async function maybeYield() {
|
|
65
|
+
p++, (p % o === 0 || performance.now() - S > r) && (await e.yieldToMain(), S = performance.now());
|
|
66
|
+
})(), n2.nodeType) {
|
|
67
|
+
case Node.DOCUMENT_NODE: {
|
|
68
|
+
const e2 = genId(n2), t2 = [];
|
|
69
|
+
for (const e3 of Array.from(n2.childNodes)) {
|
|
70
|
+
const n3 = await serialize(e3);
|
|
71
|
+
n3 && t2.push(n3);
|
|
72
|
+
}
|
|
73
|
+
return { type: 0, childNodes: t2, id: e2 };
|
|
74
|
+
}
|
|
75
|
+
case Node.DOCUMENT_TYPE_NODE: {
|
|
76
|
+
const e2 = n2;
|
|
77
|
+
return { type: 1, childNodes: [], id: genId(n2), name: e2.name, publicId: e2.publicId, systemId: e2.systemId };
|
|
78
|
+
}
|
|
79
|
+
case Node.ELEMENT_NODE: {
|
|
80
|
+
const e2 = n2, t2 = genId(n2), s2 = e2.tagName.toLowerCase(), o2 = "http://www.w3.org/2000/svg" === e2.namespaceURI || void 0;
|
|
81
|
+
if ((function isBlocked(e3) {
|
|
82
|
+
var _a;
|
|
83
|
+
return !(isUnmasked(e3) || (!a || !((_a = e3.classList) == null ? void 0 : _a.contains(a))) && !matchesSelectorSafe(e3, c));
|
|
84
|
+
})(e2)) return { type: 2, tagName: s2, attributes: getAttributes(e2), childNodes: [], id: t2, isSVG: o2, needBlock: true };
|
|
85
|
+
const r2 = [];
|
|
86
|
+
for (const t3 of Array.from(e2.childNodes)) {
|
|
87
|
+
if (!t3.parentNode) continue;
|
|
88
|
+
const e3 = await serialize(t3);
|
|
89
|
+
e3 && r2.push(e3);
|
|
90
|
+
}
|
|
91
|
+
const i2 = { type: 2, tagName: s2, attributes: getAttributes(e2), childNodes: r2, id: t2 };
|
|
92
|
+
return o2 && (i2.isSVG = true), i2;
|
|
93
|
+
}
|
|
94
|
+
case Node.TEXT_NODE: {
|
|
95
|
+
let e2 = n2.textContent || "";
|
|
96
|
+
const t2 = n2.parentElement, s2 = "STYLE" === (t2 == null ? void 0 : t2.tagName) || void 0;
|
|
97
|
+
!s2 && (function shouldMaskText(e3) {
|
|
98
|
+
const t3 = e3.parentElement;
|
|
99
|
+
return !(!t3 || isUnmasked(t3) || (!i || !t3.closest(`.${i}`)) && !closestSelectorSafe(t3, u));
|
|
100
|
+
})(n2) && e2 && (e2 = N ? N(e2, t2) : e2.replace(/\S/g, "*"));
|
|
101
|
+
const o2 = { type: 3, textContent: e2, childNodes: [], id: genId(n2) };
|
|
102
|
+
return s2 && (o2.isStyle = true), o2;
|
|
103
|
+
}
|
|
104
|
+
case Node.COMMENT_NODE:
|
|
105
|
+
return { type: 5, textContent: n2.textContent || "", childNodes: [], id: genId(n2) };
|
|
106
|
+
case Node.CDATA_SECTION_NODE:
|
|
107
|
+
return { type: 4, textContent: "", childNodes: [], id: genId(n2) };
|
|
108
|
+
default:
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
})(t);
|
|
112
|
+
} catch (e2) {
|
|
113
|
+
return console.warn("[Sailfish] chunkSnapshot serialization error:", e2), null;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
Binary file
|
|
Binary file
|