@sailfish-ai/recorder 1.11.0 → 1.11.3

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 (65) hide show
  1. package/README.md +94 -0
  2. package/dist/chunks/{chunkSerializer-ZzIoYlP2.js → chunkSerializer-CV4nkb5-.js} +1 -1
  3. package/dist/chunks/chunkSerializer-CV4nkb5-.js.br +0 -0
  4. package/dist/chunks/chunkSerializer-CV4nkb5-.js.gz +0 -0
  5. package/dist/chunks/{chunkSerializer-CRDpgzTs.js → chunkSerializer-jzbHv2wf.js} +1 -1
  6. package/dist/chunks/chunkSerializer-jzbHv2wf.js.br +0 -0
  7. package/dist/chunks/chunkSerializer-jzbHv2wf.js.gz +0 -0
  8. package/dist/chunks/{index-BQn1Q-2-.js → index-BP-kNUGS.js} +174 -134
  9. package/dist/chunks/index-BP-kNUGS.js.br +0 -0
  10. package/dist/chunks/index-BP-kNUGS.js.gz +0 -0
  11. package/dist/chunks/{index-Dq_tjmkZ.js → index-BynFTRFv.js} +130 -87
  12. package/dist/chunks/index-BynFTRFv.js.br +0 -0
  13. package/dist/chunks/index-BynFTRFv.js.gz +0 -0
  14. package/dist/chunks/rrweb-plugin-performance-record-BYWkWb25.js +188 -0
  15. package/dist/chunks/rrweb-plugin-performance-record-BYWkWb25.js.br +0 -0
  16. package/dist/chunks/rrweb-plugin-performance-record-BYWkWb25.js.gz +0 -0
  17. package/dist/chunks/rrweb-plugin-performance-record-Dekf6xUi.js +186 -0
  18. package/dist/chunks/rrweb-plugin-performance-record-Dekf6xUi.js.br +0 -0
  19. package/dist/chunks/rrweb-plugin-performance-record-Dekf6xUi.js.gz +0 -0
  20. package/dist/constants.js +1 -0
  21. package/dist/constants.js.br +0 -0
  22. package/dist/constants.js.gz +0 -0
  23. package/dist/inAppReportIssueModal/index.js +15 -14
  24. package/dist/inAppReportIssueModal/index.js.br +0 -0
  25. package/dist/inAppReportIssueModal/index.js.gz +0 -0
  26. package/dist/inAppReportIssueModal/integrations.js +56 -4
  27. package/dist/inAppReportIssueModal/integrations.js.br +0 -0
  28. package/dist/inAppReportIssueModal/integrations.js.gz +0 -0
  29. package/dist/index.js +68 -42
  30. package/dist/index.js.br +0 -0
  31. package/dist/index.js.gz +0 -0
  32. package/dist/recorder.cjs +2 -2
  33. package/dist/recorder.cjs.br +0 -0
  34. package/dist/recorder.cjs.gz +0 -0
  35. package/dist/recorder.js +27 -25
  36. package/dist/recorder.js.br +0 -0
  37. package/dist/recorder.js.gz +0 -0
  38. package/dist/recorder.umd.cjs +4994 -4776
  39. package/dist/recorder.umd.cjs.br +0 -0
  40. package/dist/recorder.umd.cjs.gz +0 -0
  41. package/dist/recording.js +33 -3
  42. package/dist/recording.js.br +0 -0
  43. package/dist/recording.js.gz +0 -0
  44. package/dist/sendSailfishMessages.js +4 -0
  45. package/dist/sendSailfishMessages.js.br +0 -0
  46. package/dist/sendSailfishMessages.js.gz +0 -0
  47. package/dist/snippet-auto-init.js +147 -18
  48. package/dist/snippet-auto-init.js.br +0 -0
  49. package/dist/snippet-auto-init.js.gz +0 -0
  50. package/dist/types/constants.d.ts +1 -0
  51. package/dist/types/inAppReportIssueModal/integrations.d.ts +7 -0
  52. package/dist/types/index.d.ts +23 -1
  53. package/dist/types/recording.d.ts +1 -0
  54. package/dist/types/sendSailfishMessages.d.ts +4 -0
  55. package/dist/types/snippet-auto-init.d.ts +30 -0
  56. package/dist/types/types.d.ts +1 -0
  57. package/package.json +7 -4
  58. package/dist/chunks/chunkSerializer-CRDpgzTs.js.br +0 -0
  59. package/dist/chunks/chunkSerializer-CRDpgzTs.js.gz +0 -0
  60. package/dist/chunks/chunkSerializer-ZzIoYlP2.js.br +0 -0
  61. package/dist/chunks/chunkSerializer-ZzIoYlP2.js.gz +0 -0
  62. package/dist/chunks/index-BQn1Q-2-.js.br +0 -0
  63. package/dist/chunks/index-BQn1Q-2-.js.gz +0 -0
  64. package/dist/chunks/index-Dq_tjmkZ.js.br +0 -0
  65. package/dist/chunks/index-Dq_tjmkZ.js.gz +0 -0
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ var e = -1, g = function(t2) {
4
+ addEventListener("pageshow", function(n2) {
5
+ n2.persisted && (e = n2.timeStamp, t2(n2));
6
+ }, true);
7
+ }, A = function() {
8
+ var e2 = self.performance && performance.getEntriesByType && performance.getEntriesByType("navigation")[0];
9
+ if (e2 && e2.responseStart > 0 && e2.responseStart < performance.now()) return e2;
10
+ }, h = function() {
11
+ var e2 = A();
12
+ return e2 && e2.activationStart || 0;
13
+ }, l = function(t2, n2) {
14
+ var r2 = A(), i2 = "navigate";
15
+ return e >= 0 ? i2 = "back-forward-cache" : r2 && (document.prerendering || h() > 0 ? i2 = "prerender" : document.wasDiscarded ? i2 = "restore" : r2.type && (i2 = r2.type.replace(/_/g, "-"))), { name: t2, value: -1, rating: "good", delta: 0, entries: [], id: "v4-".concat(Date.now(), "-").concat(Math.floor(8999999999999 * Math.random()) + 1e12), navigationType: i2 };
16
+ }, M = function(e2, t2, n2) {
17
+ try {
18
+ if (PerformanceObserver.supportedEntryTypes.includes(e2)) {
19
+ var r2 = new PerformanceObserver(function(e3) {
20
+ Promise.resolve().then(function() {
21
+ t2(e3.getEntries());
22
+ });
23
+ });
24
+ return r2.observe(Object.assign({ type: e2, buffered: true }, n2 || {})), r2;
25
+ }
26
+ } catch {
27
+ }
28
+ }, m = function(e2, t2, n2, r2) {
29
+ var i2, s2;
30
+ return function(d2) {
31
+ var f, p;
32
+ t2.value >= 0 && (d2 || r2) && ((s2 = t2.value - (i2 || 0)) || void 0 === i2) && (i2 = t2.value, t2.delta = s2, t2.rating = (f = t2.value) > (p = n2)[1] ? "poor" : f > p[0] ? "needs-improvement" : "good", e2(t2));
33
+ };
34
+ }, D = function(e2) {
35
+ requestAnimationFrame(function() {
36
+ return requestAnimationFrame(function() {
37
+ return e2();
38
+ });
39
+ });
40
+ }, U = function(e2) {
41
+ document.addEventListener("visibilitychange", function() {
42
+ "hidden" === document.visibilityState && e2();
43
+ });
44
+ }, V = function(e2) {
45
+ var t2 = false;
46
+ return function() {
47
+ t2 || (e2(), t2 = true);
48
+ };
49
+ }, t = -1, b = function() {
50
+ return "hidden" !== document.visibilityState || document.prerendering ? 1 / 0 : 0;
51
+ }, v = function(e2) {
52
+ "hidden" === document.visibilityState && t > -1 && (t = "visibilitychange" === e2.type ? e2.timeStamp : 0, _());
53
+ }, L = function() {
54
+ addEventListener("visibilitychange", v, true), addEventListener("prerenderingchange", v, true);
55
+ }, _ = function() {
56
+ removeEventListener("visibilitychange", v, true), removeEventListener("prerenderingchange", v, true);
57
+ }, k = function() {
58
+ return t < 0 && (t = b(), L(), g(function() {
59
+ setTimeout(function() {
60
+ t = b(), L();
61
+ }, 0);
62
+ })), { get firstHiddenTime() {
63
+ return t;
64
+ } };
65
+ }, B = function(e2) {
66
+ document.prerendering ? addEventListener("prerenderingchange", function() {
67
+ return e2();
68
+ }, true) : e2();
69
+ }, n = [1800, 3e3], r = [2500, 4e3], i = {}, x = function(e2, t2) {
70
+ t2 = t2 || {}, B(function() {
71
+ var n2, s2 = k(), d2 = l("LCP"), a = function(e3) {
72
+ t2.reportAllChanges || (e3 = e3.slice(-1)), e3.forEach(function(e4) {
73
+ e4.startTime < s2.firstHiddenTime && (d2.value = Math.max(e4.startTime - h(), 0), d2.entries = [e4], n2());
74
+ });
75
+ }, f = M("largest-contentful-paint", a);
76
+ if (f) {
77
+ n2 = m(e2, d2, r, t2.reportAllChanges);
78
+ var p = V(function() {
79
+ i[d2.id] || (a(f.takeRecords()), f.disconnect(), i[d2.id] = true, n2(true));
80
+ });
81
+ ["keydown", "click"].forEach(function(e3) {
82
+ addEventListener(e3, function() {
83
+ return (function(e4) {
84
+ var t3 = self.requestIdleCallback || self.setTimeout, n3 = -1;
85
+ return e4 = V(e4), "hidden" === document.visibilityState ? e4() : (n3 = t3(e4), U(e4)), n3;
86
+ })(p);
87
+ }, { once: true, capture: true });
88
+ }), U(p), g(function(s3) {
89
+ d2 = l("LCP"), n2 = m(e2, d2, r, t2.reportAllChanges), D(function() {
90
+ d2.value = performance.now() - s3.timeStamp, i[d2.id] = true, n2(true);
91
+ });
92
+ });
93
+ }
94
+ });
95
+ };
96
+ const s = "@sailfish-rrweb/rrweb/performance@1", d = { getPageVisitUuid: () => null, captureFCP: true, captureLCP: true, captureTBT: true, captureDOMLoaded: true };
97
+ function F(e2) {
98
+ return { ...d, ...e2 ?? {} };
99
+ }
100
+ function S(e2, t2, n2) {
101
+ return { metric: e2.name, value: e2.value, rating: e2.rating, navigationType: e2.navigationType, pageVisitUuid: n2.getPageVisitUuid(), href: t2.location.href, timestamp: Date.now() };
102
+ }
103
+ function I(e2, t2, r2) {
104
+ const i2 = F(r2);
105
+ let s2 = false;
106
+ const a = (t3) => {
107
+ if (!s2) try {
108
+ e2(t3);
109
+ } catch {
110
+ }
111
+ };
112
+ if (i2.captureFCP) try {
113
+ !(function(e3, t3) {
114
+ t3 = t3 || {}, B(function() {
115
+ var r3, i3 = k(), s3 = l("FCP"), d3 = M("paint", function(e4) {
116
+ e4.forEach(function(e5) {
117
+ "first-contentful-paint" === e5.name && (d3.disconnect(), e5.startTime < i3.firstHiddenTime && (s3.value = Math.max(e5.startTime - h(), 0), s3.entries.push(e5), r3(true)));
118
+ });
119
+ });
120
+ d3 && (r3 = m(e3, s3, n, t3.reportAllChanges), g(function(i4) {
121
+ s3 = l("FCP"), r3 = m(e3, s3, n, t3.reportAllChanges), D(function() {
122
+ s3.value = performance.now() - i4.timeStamp, r3(true);
123
+ });
124
+ }));
125
+ });
126
+ })((e3) => a(S(e3, t2, i2)), { reportAllChanges: false });
127
+ } catch {
128
+ }
129
+ if (i2.captureLCP) try {
130
+ x((e3) => a(S(e3, t2, i2)), { reportAllChanges: false });
131
+ } catch {
132
+ }
133
+ const d2 = i2.captureTBT ? (function H(e3, t3, n2) {
134
+ if (typeof t3.PerformanceObserver > "u") return () => {
135
+ };
136
+ let r3 = 0, i3 = false, s3 = null;
137
+ try {
138
+ s3 = new t3.PerformanceObserver((e4) => {
139
+ const t4 = e4.getEntries();
140
+ for (let e5 = 0; e5 < t4.length; e5 += 1) {
141
+ const n3 = t4[e5];
142
+ "longtask" === n3.entryType && (r3 += Math.max(0, n3.duration - 50));
143
+ }
144
+ }), s3.observe({ type: "longtask", buffered: true });
145
+ } catch {
146
+ return () => {
147
+ };
148
+ }
149
+ const u = () => {
150
+ i3 || (i3 = true, e3({ metric: "TBT", value: r3, pageVisitUuid: n2.getPageVisitUuid(), href: t3.location.href, timestamp: Date.now() }));
151
+ };
152
+ return "complete" === t3.document.readyState ? Promise.resolve().then(u) : t3.addEventListener("load", u, { once: true }), () => {
153
+ try {
154
+ null == s3 || s3.disconnect();
155
+ } catch {
156
+ }
157
+ t3.removeEventListener("load", u);
158
+ };
159
+ })(a, t2, i2) : () => {
160
+ }, f = i2.captureDOMLoaded ? (function G(e3, t3, n2) {
161
+ let r3 = false, i3 = 0;
162
+ const c = () => {
163
+ if (r3) return;
164
+ let s3;
165
+ try {
166
+ s3 = t3.performance.getEntriesByType("navigation");
167
+ } catch {
168
+ return;
169
+ }
170
+ const d3 = s3 && s3[0];
171
+ if (!d3) return void (i3 < 6 && (i3 += 1, setTimeout(c, 100)));
172
+ if (0 === d3.loadEventEnd && i3 < 6) return i3 += 1, void setTimeout(c, 100);
173
+ r3 = true;
174
+ const f2 = t3.location.href, p = Date.now(), y = n2.getPageVisitUuid();
175
+ e3({ metric: "DCL", value: d3.domContentLoadedEventEnd, pageVisitUuid: y, href: f2, timestamp: p }), e3({ metric: "LOAD", value: d3.loadEventEnd, pageVisitUuid: y, href: f2, timestamp: p });
176
+ }, o = () => setTimeout(c, 0);
177
+ return "complete" === t3.document.readyState ? o() : t3.addEventListener("load", o, { once: true }), () => {
178
+ t3.removeEventListener("load", o);
179
+ };
180
+ })(a, t2, i2) : () => {
181
+ };
182
+ return () => {
183
+ s2 = true, d2(), f();
184
+ };
185
+ }
186
+ exports.PERFORMANCE_PLUGIN_NAME = s, exports.getRecordPerformancePlugin = (e2) => ({ name: s, observer: I, options: F(e2) });
package/dist/constants.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export const DomContentEventId = 24;
2
2
  export const NetworkRequestEventId = 27;
3
+ export const PerformanceMetricEventId = 28;
3
4
  export const xSf3RidHeader = "X-Sf3-Rid";
4
5
  // Values for DomContentLoadedEvent
5
6
  export const DomContentSource = {
Binary file
Binary file
@@ -1,5 +1,5 @@
1
1
  import { createTriageAndIssueFromRecorder, createTriageFromRecorder, } from "../graphql";
2
- import { getFieldsForProject, getIntegrationData, getProjectsForTeam, getSprintFieldId, getUsers, hasValidIntegration, refreshIntegrationData, updateFormWithIntegrationData, updateIssueTypeOptions, } from "./integrations";
2
+ import { getDefaultReporterAccountId, getFieldsForProject, getIntegrationData, getProjectsForTeam, getSprintFieldId, getUsers, hasValidIntegration, refreshIntegrationData, updateFormWithIntegrationData, updateIssueTypeOptions, } from "./integrations";
3
3
  import { currentState, isRecording, recordingEndTime, recordingStartTime, resetState, setIsRecording, setRecordingEndTime, setRecordingStartTime, setTimerInterval, timerInterval, } from "./state";
4
4
  import { STORAGE_KEYS } from "./types";
5
5
  import { getChevronSVG, renderCustomMultiSelect, renderDynamicField, } from "./ui";
@@ -837,15 +837,16 @@ function initializeEngTicketForm() {
837
837
  }
838
838
  // Update form with integration data
839
839
  updateFormWithIntegrationData(currentState);
840
- // Handle reporter field for Jira
840
+ // Handle reporter field for Jira — match identified user by name/email
841
841
  if (integrationData.provider?.toLowerCase() === "jira" &&
842
- integrationData.jiraReporterAccountId &&
843
- currentState.engTicketProject) {
842
+ currentState.engTicketProject &&
843
+ !currentState.engTicketCustomFields["reporter"]) {
844
844
  const fields = getFieldsForProject(currentState.engTicketProject, currentState.engTicketIssueType);
845
845
  const reporterField = fields.find((f) => f.fieldId === "reporter");
846
- if (reporterField && !currentState.engTicketCustomFields["reporter"]) {
846
+ if (reporterField) {
847
+ const matchedReporter = getDefaultReporterAccountId();
847
848
  currentState.engTicketCustomFields["reporter"] =
848
- integrationData.jiraReporterAccountId;
849
+ matchedReporter || integrationData.jiraReporterAccountId || "";
849
850
  }
850
851
  }
851
852
  // If a project is already selected, render dynamic fields
@@ -961,13 +962,13 @@ function bindEngTicketListeners() {
961
962
  }
962
963
  if (integrationData &&
963
964
  integrationData.provider?.toLowerCase() === "jira" &&
964
- integrationData.jiraReporterAccountId &&
965
965
  engProjectSelect.value) {
966
966
  const fields = getFieldsForProject(engProjectSelect.value, currentState.engTicketIssueType);
967
967
  const reporterField = fields.find((f) => f.fieldId === "reporter");
968
968
  if (reporterField) {
969
+ const matchedReporter = getDefaultReporterAccountId();
969
970
  currentState.engTicketCustomFields["reporter"] =
970
- integrationData.jiraReporterAccountId;
971
+ matchedReporter || integrationData.jiraReporterAccountId || "";
971
972
  }
972
973
  }
973
974
  renderDynamicFields(engProjectSelect.value, currentState.engTicketIssueType);
@@ -1160,16 +1161,16 @@ function bindListeners() {
1160
1161
  currentState.engTicketPriority = integrationData.defaultPriority;
1161
1162
  }
1162
1163
  updateFormWithIntegrationData(currentState);
1163
- // Handle reporter field for Jira
1164
+ // Handle reporter field for Jira — match identified user
1164
1165
  if (integrationData.provider?.toLowerCase() === "jira" &&
1165
- integrationData.jiraReporterAccountId &&
1166
- currentState.engTicketProject) {
1166
+ currentState.engTicketProject &&
1167
+ !currentState.engTicketCustomFields["reporter"]) {
1167
1168
  const fields = getFieldsForProject(currentState.engTicketProject, currentState.engTicketIssueType);
1168
1169
  const reporterField = fields.find((f) => f.fieldId === "reporter");
1169
- if (reporterField &&
1170
- !currentState.engTicketCustomFields["reporter"]) {
1170
+ if (reporterField) {
1171
+ const matchedReporter = getDefaultReporterAccountId();
1171
1172
  currentState.engTicketCustomFields["reporter"] =
1172
- integrationData.jiraReporterAccountId;
1173
+ matchedReporter || integrationData.jiraReporterAccountId || "";
1173
1174
  }
1174
1175
  }
1175
1176
  // Render dynamic fields if a project is already selected
@@ -1,4 +1,5 @@
1
1
  import { fetchEngineeringTicketPlatformIntegrations } from "../graphql";
2
+ import { getIdentifiedUser } from "../sendSailfishMessages";
2
3
  const SUPPORT_TICKET_INTEGRATIONS = ["jira", "linear", "zendesk"];
3
4
  let integrationData = null;
4
5
  // Cache per cloud so switching between clouds is instant
@@ -220,11 +221,14 @@ export function getFieldsForProject(projectId, issueTypeId) {
220
221
  if (!integrationData?.fieldConfigurations || !projectId) {
221
222
  return [];
222
223
  }
223
- // fieldConfigurations is organized by project+issue_type combination
224
- // Match on both project_key and issue_type_id
224
+ // fieldConfigurations is organized by project+issue_type combination.
225
+ // Match project by key OR numeric id, and issue type with string coercion
226
+ // to avoid type mismatches (number vs string from different code paths).
225
227
  const projectConfig = Array.isArray(integrationData.fieldConfigurations)
226
- ? integrationData.fieldConfigurations.find((config) => config.project_key === projectId &&
227
- (!issueTypeId || config.issue_type_id === issueTypeId))
228
+ ? integrationData.fieldConfigurations.find((config) => (config.project_key === projectId ||
229
+ String(config.project_id) === String(projectId)) &&
230
+ (!issueTypeId ||
231
+ String(config.issue_type_id) === String(issueTypeId)))
228
232
  : integrationData.fieldConfigurations[projectId];
229
233
  if (!projectConfig || !projectConfig.fields) {
230
234
  return [];
@@ -237,6 +241,54 @@ export function getUsers() {
237
241
  }
238
242
  return integrationData.users;
239
243
  }
244
+ /**
245
+ * Match the current identified user against the Jira users list and
246
+ * return their accountId. Uses email, name, and email-prefix matching
247
+ * (same logic as the dashboard). Only returns a match when exactly one
248
+ * user matches to avoid ambiguity.
249
+ */
250
+ export function getDefaultReporterAccountId() {
251
+ const users = getUsers();
252
+ const identified = getIdentifiedUser();
253
+ if (!users.length || !identified)
254
+ return null;
255
+ const traits = identified.traits || {};
256
+ const email = (traits.email || identified.userId || "").toLowerCase().trim();
257
+ const name = (traits.name ||
258
+ traits.displayName ||
259
+ traits.fullName ||
260
+ traits.full_name ||
261
+ (traits.firstName || traits.first_name
262
+ ? `${traits.firstName || traits.first_name} ${traits.lastName || traits.last_name || ""}`.trim()
263
+ : "") ||
264
+ "").toLowerCase().trim();
265
+ const findUnique = (pred) => {
266
+ const matches = users.filter(pred);
267
+ return matches.length === 1 ? matches[0] : null;
268
+ };
269
+ // 1. Exact email match
270
+ let matched = email
271
+ ? findUnique((u) => (u.email || u.emailAddress || "").toLowerCase().trim() === email)
272
+ : null;
273
+ // 2. Exact name match
274
+ if (!matched && name) {
275
+ matched = findUnique((u) => {
276
+ const userName = (u.name || u.displayName || "").toLowerCase().trim();
277
+ return userName === name;
278
+ });
279
+ }
280
+ // 3. Email prefix match against name
281
+ if (!matched && email) {
282
+ const prefix = email.split("@")[0];
283
+ if (prefix.length >= 3) {
284
+ matched = findUnique((u) => {
285
+ const userName = (u.name || u.displayName || "").toLowerCase().trim();
286
+ return userName.includes(prefix);
287
+ });
288
+ }
289
+ }
290
+ return matched ? (matched.id || matched.accountId || null) : null;
291
+ }
240
292
  // Get projects based on team selection (for Linear) or directly from integration (for Jira)
241
293
  export function getProjectsForTeam(teamId) {
242
294
  if (!integrationData)
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import { readGitSha } from "./env";
8
8
  import { initializeErrorInterceptor } from "./errorInterceptor";
9
9
  import { fetchCaptureSettings, fetchFunctionSpanTrackingEnabled, sendDomainsToNotPropagateHeaderTo, startRecordingSession, } from "./graphql";
10
10
  import { sendMapUuidIfAvailable } from "./mapUuid";
11
- import { getUrlAndStoredUuids, initializeConsolePlugin, initializeDomContentEvents, initializeRecording, invalidateUrlCache, } from "./recording";
11
+ import { getUrlAndStoredUuids, initializeConsolePlugin, initializeDomContentEvents, initializePerformancePlugin, initializeRecording, invalidateUrlCache, } from "./recording";
12
12
  import { HAS_DOCUMENT, HAS_LOCAL_STORAGE, HAS_SESSION_STORAGE, HAS_WINDOW, } from "./runtimeEnv";
13
13
  import { isHeadlessOrLighthouse } from "./headlessDetection";
14
14
  import { ensureSessionListeners, getOrSetSessionId } from "./session";
@@ -293,40 +293,11 @@ function storeCredentialsAndConnection({ apiKey, backendApi, }) {
293
293
  sessionStorage.setItem("sailfishApiKey", apiKey);
294
294
  sessionStorage.setItem("sailfishBackendApi", backendApi);
295
295
  }
296
- // Utility function to match domains or paths with wildcard support
297
- export function matchUrlWithWildcard(input, patterns) {
298
- // Tolerate non-string inputs (Request, URL, etc.); coerce to string when possible.
299
- let urlStr;
300
- if (typeof input === "string") {
301
- urlStr = input;
302
- }
303
- else if (typeof URL !== "undefined" && input instanceof URL) {
304
- urlStr = input.href;
305
- }
306
- else if (typeof Request !== "undefined" && input instanceof Request) {
307
- urlStr = input.url;
308
- }
309
- else if (input != null && typeof input.toString === "function") {
310
- // As per web APIs, fetch/open may accept any object with a stringifier.
311
- urlStr = input.toString();
312
- }
313
- if (!urlStr)
314
- return false;
315
- // Use WHATWG URL parsing with a base for relative URLs.
316
- let parsed;
317
- try {
318
- const base = typeof window !== "undefined"
319
- ? window.location.href
320
- : "http://localhost/";
321
- parsed = new URL(urlStr, base);
322
- }
323
- catch {
324
- return false; // If we can't parse, just say "no match" instead of throwing.
325
- }
326
- const { hostname, pathname, port, protocol } = parsed;
327
- // Only match http(s) hosts/paths; ignore others like data:, blob:, about:.
328
- if (!/^https?:$/.test(protocol))
329
- return false;
296
+ // Internal: match a *pre-parsed* URL against patterns. Hot path on the
297
+ // header-propagation gate, called twice per request (deny + allow). Splitting
298
+ // this from matchUrlWithWildcard() lets callers parse the URL once and reuse.
299
+ function matchParsedUrlAgainstPatterns(parsed, patterns) {
300
+ const { hostname, pathname, port } = parsed;
330
301
  // Handle stripping 'www.' and port
331
302
  const domain = hostname.startsWith("www.")
332
303
  ? hostname.slice(4).toLowerCase()
@@ -383,6 +354,38 @@ export function matchUrlWithWildcard(input, patterns) {
383
354
  return true;
384
355
  });
385
356
  }
357
+ // Public wrapper: parse + delegate. Preserved for callers outside the gate
358
+ // (interceptor wrappers, tests). Hot paths should call
359
+ // matchParsedUrlAgainstPatterns() directly with a pre-parsed URL.
360
+ export function matchUrlWithWildcard(input, patterns) {
361
+ // Tolerate non-string inputs (Request, URL, etc.); coerce to string when possible.
362
+ let urlStr;
363
+ if (typeof input === "string") {
364
+ urlStr = input;
365
+ }
366
+ else if (typeof URL !== "undefined" && input instanceof URL) {
367
+ urlStr = input.href;
368
+ }
369
+ else if (typeof Request !== "undefined" && input instanceof Request) {
370
+ urlStr = input.url;
371
+ }
372
+ else if (input != null && typeof input.toString === "function") {
373
+ urlStr = input.toString();
374
+ }
375
+ if (!urlStr)
376
+ return false;
377
+ let parsed;
378
+ try {
379
+ const base = typeof window !== "undefined"
380
+ ? window.location.href
381
+ : "http://localhost/";
382
+ parsed = new URL(urlStr, base);
383
+ }
384
+ catch {
385
+ return false;
386
+ }
387
+ return matchParsedUrlAgainstPatterns(parsed, patterns);
388
+ }
386
389
  export function createSkipHeadersPropagationChecker(domainsToNotPropagateHeaderTo = [], domainsToPropagateHeaderTo = []) {
387
390
  // Pre-compute the combined domain exclusion patterns once per interceptor setup
388
391
  const combinedPatterns = [
@@ -392,26 +395,30 @@ export function createSkipHeadersPropagationChecker(domainsToNotPropagateHeaderT
392
395
  // Pre-compute allowlist presence once (avoids per-request .length check overhead)
393
396
  const hasAllowlist = domainsToPropagateHeaderTo.length > 0;
394
397
  return function shouldSkipHeadersPropagation(url) {
395
- let urlObj;
398
+ // Parse the URL ONCE and reuse for every subsequent check. URL parsing is
399
+ // the dominant cost on this hot path (called for every fetch/XHR), so
400
+ // calling matchParsedUrlAgainstPatterns() with the cached parse instead of
401
+ // matchUrlWithWildcard() (which would re-parse) is the key perf win.
402
+ let parsed;
396
403
  try {
397
- urlObj = new URL(typeof url === "string" ? url : String(url?.url ?? url), window.location.href);
404
+ parsed = new URL(typeof url === "string" ? url : String(url?.url ?? url), window.location.href);
398
405
  }
399
406
  catch {
400
407
  // If we cannot parse, play it safe and do NOT inject headers.
401
408
  return true;
402
409
  }
403
410
  // 1️⃣ STATIC ASSET EXCLUSIONS (by comprehensive file extension list) — O(1) Set lookup
404
- const lowerPathname = urlObj.pathname.toLowerCase();
411
+ const lowerPathname = parsed.pathname.toLowerCase();
405
412
  const lastDotIdx = lowerPathname.lastIndexOf(".");
406
413
  if (lastDotIdx !== -1 && STATIC_EXTENSIONS_SET.has(lowerPathname.slice(lastDotIdx))) {
407
414
  return true;
408
415
  }
409
416
  // 2️⃣ ALLOWLIST CHECK — if provided, URL must match at least one allowlist pattern
410
- if (hasAllowlist && !matchUrlWithWildcard(url, domainsToPropagateHeaderTo)) {
417
+ if (hasAllowlist && !matchParsedUrlAgainstPatterns(parsed, domainsToPropagateHeaderTo)) {
411
418
  return true;
412
419
  }
413
420
  // 3️⃣ WILDCARD-BASED EXCLUSION (domain + path) — blocklist still applies even if allowlisted
414
- if (matchUrlWithWildcard(url, combinedPatterns)) {
421
+ if (matchParsedUrlAgainstPatterns(parsed, combinedPatterns)) {
415
422
  return true;
416
423
  }
417
424
  return false;
@@ -1028,12 +1035,18 @@ function getMapUuidFromWindow() {
1028
1035
  // Note - we do NOT send serviceIdentifier because
1029
1036
  // it would be 1 serviceIdentifier per frontend user session,
1030
1037
  // which is very wasteful
1031
- export async function startRecording({ apiKey, backendApi = "https://api-service.sailfishqa.com", domainsToPropagateHeaderTo = [], domainsToNotPropagateHeaderTo = [], serviceVersion, serviceIdentifier, gitSha, serviceAdditionalMetadata, enableIpTracking, captureStreamingResponseBody = true, captureResponseBodyMaxMb = 10, captureStreamPrefixKb = 64, captureStreamTimeoutMs = 10000, enableFiberTracking = false, deferRecording, deferRecordingStart, chunkSnapshot, useWsWorker = true, library, }) {
1038
+ export async function startRecording({ apiKey, backendApi = "https://api-service.sailfishqa.com",
1039
+ // Default: ["*"] = propagate to every URL (deny list still applies).
1040
+ // Pass [] to fully disable header propagation. Pass exact patterns
1041
+ // (e.g. ["api.myapp.com", "*.internal.com"]) to restrict propagation
1042
+ // to a known set of domains.
1043
+ domainsToPropagateHeaderTo = ["*"], domainsToNotPropagateHeaderTo = [], serviceVersion, serviceIdentifier, gitSha, serviceAdditionalMetadata, enableIpTracking, captureStreamingResponseBody = true, captureResponseBodyMaxMb = 10, captureStreamPrefixKb = 64, captureStreamTimeoutMs = 10000, enableFiberTracking = false, deferRecording, deferRecordingStart, chunkSnapshot, useWsWorker = true, capturePerformanceMetrics = true, maskTextClass, library, headlessRecording = false, }) {
1032
1044
  // Synthetic-environment no-op: Lighthouse/PSI, HeadlessChrome, WebPageTest
1033
1045
  // (PTST), Puppeteer/Playwright/Selenium (navigator.webdriver). We skip init
1034
1046
  // entirely to avoid WSS retry noise, third-party perf penalties in audits,
1035
1047
  // and polluting real-user session data.
1036
- if (isHeadlessOrLighthouse()) {
1048
+ // headlessRecording: true bypasses this check to allow explicit recording in headless environments.
1049
+ if (!headlessRecording && isHeadlessOrLighthouse()) {
1037
1050
  return;
1038
1051
  }
1039
1052
  // deferRecording takes precedence; fall back to deprecated deferRecordingStart; default true
@@ -1099,6 +1112,16 @@ export async function startRecording({ apiKey, backendApi = "https://api-service
1099
1112
  g.errorInit = true;
1100
1113
  }
1101
1114
  await yieldToMain();
1115
+ // 1f. Performance metrics plugin (FCP, LCP, TBT, DCL, LOAD).
1116
+ // Fire-and-forget: the plugin dynamically imports web-vitals and its own
1117
+ // observer wiring, so nothing blocks startRecording() while it loads.
1118
+ // Skip entirely when the caller opts out via capturePerformanceMetrics:false
1119
+ // — pays zero bundle/runtime cost since the plugin is dynamically imported.
1120
+ if (!g.perfInit && capturePerformanceMetrics) {
1121
+ initializePerformancePlugin(sessionId);
1122
+ g.perfInit = true;
1123
+ }
1124
+ await yieldToMain();
1102
1125
  // ── Phase 2: Deferred async work ────────────────────────────────────────
1103
1126
  // Everything else — GraphQL calls, WebSocket init, device info, domain
1104
1127
  // reporting, visibility listeners, funcspan state restore, etc.
@@ -1170,6 +1193,9 @@ export async function startRecording({ apiKey, backendApi = "https://api-service
1170
1193
  ...DEFAULT_CAPTURE_SETTINGS,
1171
1194
  ...captureSettingsResponse.data?.captureSettingsFromApiKey,
1172
1195
  enableFiberTracking,
1196
+ // Caller-supplied maskTextClass wins over server-fetched value; only
1197
+ // applied when explicitly set so undefined cannot clobber the server.
1198
+ ...(maskTextClass !== undefined ? { maskTextClass } : {}),
1173
1199
  };
1174
1200
  // If a socket is already open now, stop here.
1175
1201
  if (g.ws && g.ws.readyState === 1) {
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-BQn1Q-2-.js");
4
- 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.getCachedHref = e.getCachedHref, exports.getCachedHrefNoQuery = e.getCachedHrefNoQuery, exports.getFuncSpanHeader = e.getFuncSpanHeader, 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.initializeRecording = e.initializeRecording, exports.initializeWebSocket = e.initializeWebSocket, exports.invalidateUrlCache = e.invalidateUrlCache, exports.isFunctionSpanTrackingEnabled = e.isFunctionSpanTrackingEnabled, exports.matchUrlWithWildcard = e.matchUrlWithWildcard, Object.defineProperty(exports, "nowTimestamp", { enumerable: true, get: () => e.nowTimestamp }), exports.onNavigationChange = e.onNavigationChange, exports.openReportIssueModal = e.openReportIssueModal, 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.trackingEvent = e.trackingEvent, exports.withAppUrlMetadata = e.withAppUrlMetadata;
3
+ const e = require("./chunks/index-BP-kNUGS.js");
4
+ 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.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.matchUrlWithWildcard = e.matchUrlWithWildcard, Object.defineProperty(exports, "nowTimestamp", { enumerable: true, get: () => e.nowTimestamp }), exports.onNavigationChange = e.onNavigationChange, exports.openReportIssueModal = e.openReportIssueModal, 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.trackingEvent = e.trackingEvent, exports.withAppUrlMetadata = e.withAppUrlMetadata;
Binary file
Binary file
package/dist/recorder.js CHANGED
@@ -1,4 +1,4 @@
1
- import { D, a, S, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, z, A, B, C, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, T } from "./chunks/index-Dq_tjmkZ.js";
1
+ import { D, a, S, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, z, A, B, C, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, T, U, V } from "./chunks/index-BynFTRFv.js";
2
2
  export {
3
3
  D as DEFAULT_CAPTURE_SETTINGS,
4
4
  a as DEFAULT_CONSOLE_RECORDING_SETTINGS,
@@ -21,28 +21,30 @@ export {
21
21
  q as getCachedHref,
22
22
  r as getCachedHrefNoQuery,
23
23
  s as getFuncSpanHeader,
24
- t as getOrSetSessionId,
25
- u as getUrlAndStoredUuids,
26
- v as identify,
27
- w as initRecorder,
28
- x as initializeConsolePlugin,
29
- z as initializeDomContentEvents,
30
- A as initializeFunctionSpanTrackingFromApi,
31
- B as initializeRecording,
32
- C as initializeWebSocket,
33
- E as invalidateUrlCache,
34
- F as isFunctionSpanTrackingEnabled,
35
- G as matchUrlWithWildcard,
36
- H as nowTimestamp,
37
- I as onNavigationChange,
38
- J as openReportIssueModal,
39
- K as restoreFuncSpanState,
40
- L as sendDomainsToNotPropagateHeaderTo,
41
- M as sendEvent,
42
- N as sendGraphQLRequest,
43
- O as sendMessage,
44
- P as startRecording,
45
- Q as startRecordingSession,
46
- R as trackingEvent,
47
- T as withAppUrlMetadata
24
+ t as getIdentifiedUser,
25
+ u as getOrSetSessionId,
26
+ v as getUrlAndStoredUuids,
27
+ w as identify,
28
+ x as initRecorder,
29
+ z as initializeConsolePlugin,
30
+ A as initializeDomContentEvents,
31
+ B as initializeFunctionSpanTrackingFromApi,
32
+ C as initializePerformancePlugin,
33
+ E as initializeRecording,
34
+ F as initializeWebSocket,
35
+ G as invalidateUrlCache,
36
+ H as isFunctionSpanTrackingEnabled,
37
+ I as matchUrlWithWildcard,
38
+ J as nowTimestamp,
39
+ K as onNavigationChange,
40
+ L as openReportIssueModal,
41
+ M as restoreFuncSpanState,
42
+ N as sendDomainsToNotPropagateHeaderTo,
43
+ O as sendEvent,
44
+ P as sendGraphQLRequest,
45
+ Q as sendMessage,
46
+ R as startRecording,
47
+ T as startRecordingSession,
48
+ U as trackingEvent,
49
+ V as withAppUrlMetadata
48
50
  };
Binary file
Binary file