@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.
Files changed (75) hide show
  1. package/README.md +19 -1
  2. package/dist/chunkSerializer.js +70 -23
  3. package/dist/chunkSerializer.js.br +0 -0
  4. package/dist/chunkSerializer.js.gz +0 -0
  5. package/dist/chunks/chunkSerializer-DDukZpgl.js +116 -0
  6. package/dist/chunks/chunkSerializer-DDukZpgl.js.br +0 -0
  7. package/dist/chunks/chunkSerializer-DDukZpgl.js.gz +0 -0
  8. package/dist/chunks/chunkSerializer-FQtY90Av.js +115 -0
  9. package/dist/chunks/chunkSerializer-FQtY90Av.js.br +0 -0
  10. package/dist/chunks/chunkSerializer-FQtY90Av.js.gz +0 -0
  11. package/dist/chunks/{index-DiGs9it7.js → index-C-qbsfKe.js} +724 -548
  12. package/dist/chunks/index-C-qbsfKe.js.br +0 -0
  13. package/dist/chunks/index-C-qbsfKe.js.gz +0 -0
  14. package/dist/chunks/{index-CIK1iDN9.js → index-D6axlCRu.js} +757 -577
  15. package/dist/chunks/index-D6axlCRu.js.br +0 -0
  16. package/dist/chunks/index-D6axlCRu.js.gz +0 -0
  17. package/dist/clockSync.js +196 -0
  18. package/dist/clockSync.js.br +0 -0
  19. package/dist/clockSync.js.gz +0 -0
  20. package/dist/errorInterceptor.js +42 -4
  21. package/dist/errorInterceptor.js.br +0 -0
  22. package/dist/errorInterceptor.js.gz +0 -0
  23. package/dist/graphql.js +5 -0
  24. package/dist/graphql.js.br +0 -0
  25. package/dist/graphql.js.gz +0 -0
  26. package/dist/inAppReportIssueModal/index.js +4 -1
  27. package/dist/inAppReportIssueModal/index.js.br +0 -0
  28. package/dist/inAppReportIssueModal/index.js.gz +0 -0
  29. package/dist/inAppReportIssueModal/integrations.js +36 -0
  30. package/dist/inAppReportIssueModal/integrations.js.br +0 -0
  31. package/dist/inAppReportIssueModal/integrations.js.gz +0 -0
  32. package/dist/inAppReportIssueModal/state.js +8 -0
  33. package/dist/inAppReportIssueModal/state.js.br +0 -0
  34. package/dist/inAppReportIssueModal/state.js.gz +0 -0
  35. package/dist/index.js +67 -5
  36. package/dist/index.js.br +0 -0
  37. package/dist/index.js.gz +0 -0
  38. package/dist/privacyMask.js +93 -0
  39. package/dist/privacyMask.js.br +0 -0
  40. package/dist/privacyMask.js.gz +0 -0
  41. package/dist/recorder.cjs +2 -2
  42. package/dist/recorder.cjs.br +0 -0
  43. package/dist/recorder.cjs.gz +0 -0
  44. package/dist/recorder.js +17 -14
  45. package/dist/recorder.js.br +0 -0
  46. package/dist/recorder.js.gz +0 -0
  47. package/dist/recorder.umd.cjs +1338 -1140
  48. package/dist/recorder.umd.cjs.br +0 -0
  49. package/dist/recorder.umd.cjs.gz +0 -0
  50. package/dist/recording.js +84 -13
  51. package/dist/recording.js.br +0 -0
  52. package/dist/recording.js.gz +0 -0
  53. package/dist/types/chunkSerializer.d.ts +14 -0
  54. package/dist/types/clockSync.d.ts +70 -0
  55. package/dist/types/inAppReportIssueModal/integrations.d.ts +1 -0
  56. package/dist/types/inAppReportIssueModal/state.d.ts +2 -0
  57. package/dist/types/index.d.ts +16 -2
  58. package/dist/types/privacyMask.d.ts +46 -0
  59. package/dist/types/recording.d.ts +1 -0
  60. package/dist/types/types.d.ts +23 -0
  61. package/dist/types/websocket.d.ts +1 -0
  62. package/dist/websocket.js +111 -0
  63. package/dist/websocket.js.br +0 -0
  64. package/dist/websocket.js.gz +0 -0
  65. package/package.json +1 -1
  66. package/dist/chunks/chunkSerializer-C8qtomKe.js +0 -95
  67. package/dist/chunks/chunkSerializer-C8qtomKe.js.br +0 -0
  68. package/dist/chunks/chunkSerializer-C8qtomKe.js.gz +0 -0
  69. package/dist/chunks/chunkSerializer-RWnu-UfC.js +0 -94
  70. package/dist/chunks/chunkSerializer-RWnu-UfC.js.br +0 -0
  71. package/dist/chunks/chunkSerializer-RWnu-UfC.js.gz +0 -0
  72. package/dist/chunks/index-CIK1iDN9.js.br +0 -0
  73. package/dist/chunks/index-CIK1iDN9.js.gz +0 -0
  74. package/dist/chunks/index-DiGs9it7.js.br +0 -0
  75. 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
@@ -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
- try {
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
- try {
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
- attrs.value = input.value;
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 = textContent.replace(/\S/g, "*");
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
+ };
@@ -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
+ };