@sailfish-ai/recorder 1.7.29 → 1.7.34

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/graphql.js CHANGED
@@ -41,11 +41,33 @@ export function fetchCaptureSettings(apiKey, backendApi) {
41
41
  }
42
42
  `, { apiKey, backendApi });
43
43
  }
44
- export function startRecordingSession(apiKey, recordingId, backendApi) {
45
- return sendGraphQLRequest("StartSession", `mutation StartSession($apiKey: UUID!, $recordingSessionId: UUID!) {
44
+ export function startRecordingSession(apiKey, recordingId, backendApi, serviceIdentifier, serviceVersion, mapUuid, gitSha, library, serviceAdditionalMetadata) {
45
+ // These must be sent as None/Null for now (not user-provided)
46
+ const startRecordingFilePath = null;
47
+ const startRecordingFileNumber = null;
48
+ return sendGraphQLRequest("StartSession", `mutation StartSession(
49
+ $apiKey: UUID!,
50
+ $recordingSessionId: UUID!,
51
+ $serviceIdentifier: String!,
52
+ $serviceVersion: String,
53
+ $mapUuid: String,
54
+ $gitSha: String,
55
+ $library: String,
56
+ $serviceAdditionalMetadata: JSON,
57
+ $startRecordingFilePath: String,
58
+ $startRecordingFileNumber: Int
59
+ ) {
46
60
  startRecordingSession(
47
61
  companyApiKey: $apiKey,
48
- sessionId: $recordingSessionId
62
+ sessionId: $recordingSessionId,
63
+ serviceIdentifier: $serviceIdentifier,
64
+ serviceVersion: $serviceVersion,
65
+ mapUuid: $mapUuid,
66
+ gitSha: $gitSha,
67
+ library: $library,
68
+ serviceAdditionalMetadata: $serviceAdditionalMetadata,
69
+ startRecordingFilePath: $startRecordingFilePath,
70
+ startRecordingFileNumber: $startRecordingFileNumber
49
71
  ) {
50
72
  id
51
73
  }
@@ -53,6 +75,14 @@ export function startRecordingSession(apiKey, recordingId, backendApi) {
53
75
  apiKey,
54
76
  recordingSessionId: recordingId,
55
77
  backendApi,
78
+ serviceIdentifier,
79
+ serviceVersion,
80
+ mapUuid,
81
+ gitSha,
82
+ library,
83
+ serviceAdditionalMetadata,
84
+ startRecordingFilePath,
85
+ startRecordingFileNumber,
56
86
  });
57
87
  }
58
88
  export function sendDomainsToNotPropagateHeaderTo(apiKey, domains, backendApi) {
@@ -1,6 +1,10 @@
1
1
  import { createTriageFromRecorder } from "./graphql";
2
2
  let modalEl = null;
3
- let currentState = { mode: "lookback", description: "" };
3
+ let currentState = {
4
+ mode: "lookback",
5
+ description: "",
6
+ occurredInThisTab: true,
7
+ };
4
8
  let recordingStartTime = null;
5
9
  let recordingEndTime = null;
6
10
  let timerInterval = null;
@@ -8,6 +12,19 @@ let isRecording = false;
8
12
  let resolveSessionId = null;
9
13
  let apiKey = null;
10
14
  let backendApi = null;
15
+ let triageBaseUrl = "https://app.sailfishqa.com";
16
+ function isMacPlatform() {
17
+ // Newer API (Chrome, Edge, Opera)
18
+ const uaData = navigator.userAgentData;
19
+ if (uaData?.platform) {
20
+ return uaData.platform.toLowerCase().includes("mac");
21
+ }
22
+ // Fallback: parse userAgent string
23
+ return /mac/i.test(navigator.userAgent);
24
+ }
25
+ function getShortcutKeyCmdCtrlLabel() {
26
+ return isMacPlatform() ? "⌘" : "Ctrl";
27
+ }
11
28
  function isTypingInInput() {
12
29
  const el = document.activeElement;
13
30
  return (el instanceof HTMLInputElement ||
@@ -40,6 +57,12 @@ export function setupIssueReporting(options) {
40
57
  const modalOpen = !!document.getElementById("sf-report-issue-modal");
41
58
  if (!modalOpen && !isRecording)
42
59
  return;
60
+ // Escape key → close modal (only if not recording and modal is open)
61
+ if (!isCmdOrCtrl && key === "escape" && modalOpen && !isRecording) {
62
+ e.preventDefault();
63
+ closeModal();
64
+ return;
65
+ }
43
66
  // Cmd/Ctrl + Enter → submit
44
67
  if (isCmdOrCtrl && key === "enter" && modalOpen) {
45
68
  const submitBtn = document.getElementById("sf-issue-submit-btn");
@@ -73,7 +96,7 @@ function getSessionIdSafely() {
73
96
  }
74
97
  return resolveSessionId();
75
98
  }
76
- export function openReportIssueModal() {
99
+ export function openReportIssueModal(customBaseUrl) {
77
100
  if (isRecording) {
78
101
  stopRecording();
79
102
  return;
@@ -81,14 +104,24 @@ export function openReportIssueModal() {
81
104
  injectModalHTML();
82
105
  if (modalEl)
83
106
  document.body.appendChild(modalEl);
107
+ // Store custom base URL globally for reuse
108
+ triageBaseUrl = customBaseUrl ?? "https://app.sailfishqa.com";
84
109
  }
85
110
  function closeModal() {
111
+ // ✅ Explicitly blur the focused element
112
+ if (document.activeElement instanceof HTMLElement) {
113
+ document.activeElement.blur();
114
+ }
86
115
  if (modalEl?.parentNode) {
87
116
  modalEl.parentNode.removeChild(modalEl);
88
117
  }
89
118
  modalEl = null;
90
119
  if (!isRecording) {
91
- currentState = { mode: "lookback", description: "" };
120
+ currentState = {
121
+ mode: "lookback",
122
+ description: "",
123
+ occurredInThisTab: true,
124
+ };
92
125
  recordingStartTime = null;
93
126
  recordingEndTime = null;
94
127
  }
@@ -123,11 +156,11 @@ function injectModalHTML(initialMode = "lookback") {
123
156
  <div id="sf-issue-tabs" style="display:flex; gap:4px; margin-bottom:16px; background:#f1f5f9; padding:6px; border-radius:6px; width: fit-content;">
124
157
  <button id="sf-tab-lookback" data-mode="lookback" class="sf-issue-tab ${!isStartNow ? "active" : ""}"
125
158
  style="padding:6px 12px; border:none; background:${!isStartNow ? "white" : "transparent"}; color: ${!isStartNow ? "#0F172A" : "#64748B"}; border-radius:4px; font-size:14px; cursor:pointer; font-weight:500; height:32px; display: flex; align-items: center; gap: 8px;">
126
- Existing <span style="background: #F1F5F9; border:1px solid #cbd5e1; border-radius: 4px; width: 18px; height: 18px; display: flex; align-items: center; justify-content: center; color: #94A3B8; font-weight: 500;">e</span>
159
+ Existing <span style="background: #F1F5F9; border:1px solid #cbd5e1; border-radius: 4px; width: 16px; height: 16px; font-size: 12px; display: flex; align-items: center; justify-content: center; color: #94A3B8; font-weight: 500;">E</span>
127
160
  </button>
128
161
  <button id="sf-tab-startnow" data-mode="startnow" class="sf-issue-tab ${isStartNow ? "active" : ""}"
129
162
  style="padding:6px 12px; border:none; background:${isStartNow ? "white" : "transparent"}; color: ${isStartNow ? "#0F172A" : "#64748B"}; border-radius:4px; font-size:14px; cursor:pointer; font-weight:500; height:32px; display: flex; align-items: center; gap: 8px;">
130
- Capture new <span style="background: #F1F5F9; border:1px solid #cbd5e1; border-radius: 4px; width: 18px; height: 18px; display: flex; align-items: center; justify-content: center; color: #94A3B8; font-weight: 500;">n</span>
163
+ Capture new <span style="background: #F1F5F9; border:1px solid #cbd5e1; border-radius: 4px; width: 16px; height: 16px; font-size: 12px; display: flex; align-items: center; justify-content: center; color: #94A3B8; font-weight: 500;">N</span>
131
164
  </button>
132
165
  </div>
133
166
 
@@ -159,8 +192,8 @@ function injectModalHTML(initialMode = "lookback") {
159
192
  <label for="sf-lookback-minutes" style="display:block; font-size:14px; font-weight:500; margin-bottom:6px;">
160
193
  When did this happen?
161
194
  </label>
162
- <div style="display:flex; align-items:center; gap:6px;">
163
- <div style="position:relative;">
195
+ <div style="display:flex; align-items:center; justify-content:space-between; gap:12px;">
196
+ <div style="position:relative; display:flex; align-items:center; gap:6px;">
164
197
  <select id="sf-lookback-minutes"
165
198
  style="
166
199
  background: white;
@@ -193,8 +226,21 @@ function injectModalHTML(initialMode = "lookback") {
193
226
  stroke-linecap="round" stroke-linejoin="round"/>
194
227
  </svg>
195
228
  </div>
229
+ <span style="font-size:14px; color: #64748B;">minutes ago</span>
230
+ </div>
231
+ <div>
232
+ <label for="sf-occurred-in-tab"
233
+ style="display:flex; align-items:center; gap:6px; font-size:14px; color:#0F172A; cursor:pointer; white-space:nowrap;">
234
+ <input
235
+ type="checkbox"
236
+ id="sf-occurred-in-tab"
237
+ checked
238
+ disabled
239
+ style="width:16px; height:16px; accent-color:#295DBF; cursor:pointer;"
240
+ />
241
+ The issue occurred in this tab?
242
+ </label>
196
243
  </div>
197
- <span style="font-size:14px; color: #64748B;">minutes ago</span>
198
244
  </div>
199
245
  </div>
200
246
 
@@ -212,7 +258,7 @@ function injectModalHTML(initialMode = "lookback") {
212
258
  <div style="width: 14px; height: 14px; background: #fc5555; border-radius: 50%; border: 1px solid #991b1b;"></div>
213
259
  </div>
214
260
  <span>Start Recording</span>
215
- <span style="background: #F1F5F9; border:1px solid #cbd5e1; border-radius: 4px; width: 18px; height: 18px; display: flex; align-items: center; justify-content: center; color: #94A3B8; font-weight: 500;">r</span>
261
+ <span style="background: #F1F5F9; border:1px solid #cbd5e1; border-radius: 4px; width: 16px; height: 16px; font-size: 12px; display: flex; align-items: center; justify-content: center; color: #94A3B8; font-weight: 500;">R</span>
216
262
  </button>
217
263
  </div>
218
264
 
@@ -220,10 +266,10 @@ function injectModalHTML(initialMode = "lookback") {
220
266
  style="background: #295DBF; color:white; border:none; padding:8px 16px;
221
267
  border-radius:6px; font-size:14px; line-height: 24px; font-weight:500;
222
268
  cursor:${isStartNow ? "not-allowed" : "pointer"}; opacity:${isStartNow ? "0.4" : "1"}; margin-bottom:1px" ${isStartNow ? "disabled" : ""}>
223
- Create Triage <span style="margin-left: 8px; display: inline-flex; align-items: center; gap: 4px; color: #94A3B8; font-size: 14px; line-height:18px;">
224
- <span style="background: #F1F5F9; border-radius: 4px; padding: 0 6px; font-weight: 500;">cmd</span>
269
+ Create Triage <span style="margin-left: 8px; display: inline-flex; align-items: center; gap: 4px; color: #94A3B8; font-size: 12px; line-height:16px;">
270
+ <span style="background: #F1F5F9; border-radius: 4px; padding: 0 4px; font-weight: 500;">${getShortcutKeyCmdCtrlLabel()}</span>
225
271
  +
226
- <span style="background: #F1F5F9; border-radius: 4px; padding: 0 6px; font-weight: 500;">enter</span>
272
+ <span style="background: #F1F5F9; border-radius: 4px; padding: 0 4px; font-weight: 500;">ENTER</span>
227
273
  </span>
228
274
  </button>
229
275
  </div>
@@ -437,10 +483,10 @@ function showFloatingTimer() {
437
483
  </div>
438
484
  <div style="display:flex; align-items:center; gap:4px; margin-left:auto; font-size:14px; color:#0F172A;">
439
485
  <span style="color: #71717A;">stop</span>
440
- <span style="display: inline-flex; align-items: center; gap: 4px; color: #94A3B8; font-size: 14px; line-height:18px;">
441
- <span style="background: #F1F5F9; border-radius: 4px; border:1px solid #cbd5e1; padding: 0 6px; font-weight: 500;">cmd</span>
486
+ <span style="display: inline-flex; align-items: center; gap: 4px; color: #94A3B8; font-size: 12px; line-height:16px;">
487
+ <span style="background: #F1F5F9; border-radius: 4px; border:1px solid #cbd5e1; padding: 0 4px; font-weight: 500;">${getShortcutKeyCmdCtrlLabel()}</span>
442
488
  +
443
- <span style="background: #F1F5F9; border-radius: 4px; border:1px solid #cbd5e1; padding: 0 6px; font-weight: 500;">esc</span>
489
+ <span style="background: #F1F5F9; border-radius: 4px; border:1px solid #cbd5e1; padding: 0 4px; font-weight: 500;">ESC</span>
444
490
  </span>
445
491
  </div>
446
492
  `;
@@ -532,9 +578,7 @@ async function createTriage(startTimestamp, endTimestamp, description) {
532
578
  }
533
579
  function showTriageStatusModal(isLoading, triageId) {
534
580
  removeExistingModals();
535
- const triageUrl = triageId
536
- ? `https://app.sailfishqa.com/triage/${triageId}`
537
- : "";
581
+ const triageUrl = triageId ? `${triageBaseUrl}/triage/${triageId}` : "";
538
582
  const container = document.createElement("div");
539
583
  container.id = "sf-triage-status-modal";
540
584
  const statusTitle = isLoading ? "Creating Triage..." : "Triage created!";
package/dist/index.js CHANGED
@@ -4,8 +4,8 @@ import { v4 as uuidv4 } from "uuid";
4
4
  import { NetworkRequestEventId, STATIC_EXTENSIONS, xSf3RidHeader, } from "./constants";
5
5
  import { gatherAndCacheDeviceInfo } from "./deviceInfo";
6
6
  import { fetchCaptureSettings, sendDomainsToNotPropagateHeaderTo, startRecordingSession, } from "./graphql";
7
+ import { setupIssueReporting } from "./inAppReportIssueModal";
7
8
  import { sendMapUuidIfAvailable } from "./mapUuid";
8
- import { setupIssueReporting } from "./modal";
9
9
  import { getUrlAndStoredUuids, initializeConsolePlugin, initializeDomContentEvents, initializeRecording, } from "./recording";
10
10
  import { sendEvent, sendMessage } from "./websocket";
11
11
  // Default list of domains to ignore
@@ -771,9 +771,60 @@ function setupFetchInterceptor(domainsToNotPropagateHeadersTo = []) {
771
771
  }
772
772
  }
773
773
  }
774
+ // Note - if you update this, you must copy this function to the documentation in docs/docs/getting-started/updating-autoinstall-prs.md.
775
+ function getCompileTimeGitSha() {
776
+ // 1) Preferred: a hard-coded global replaced at build time (all bundlers support "define")
777
+ // e.g. webpack DefinePlugin/esbuild/rollup/vite: __GIT_SHA__ => JSON.stringify('<sha>')
778
+ try {
779
+ const g = globalThis;
780
+ if (g && typeof g.__GIT_SHA__ === "string" && g.__GIT_SHA__) {
781
+ return g.__GIT_SHA__;
782
+ }
783
+ }
784
+ catch { }
785
+ // 2) Common pattern: process.env.GIT_SHA (webpack/rollup/esbuild/parcel)
786
+ try {
787
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
788
+ const p = typeof process !== "undefined" ? process : undefined;
789
+ if (p?.env?.GIT_SHA)
790
+ return String(p.env.GIT_SHA);
791
+ }
792
+ catch { }
793
+ // 3) Vite (or other tools exposing import.meta.env)
794
+ try {
795
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
796
+ const im = import.meta;
797
+ if (im?.env?.VITE_GIT_SHA)
798
+ return String(im.env.VITE_GIT_SHA);
799
+ if (im?.env?.GIT_SHA)
800
+ return String(im.env.GIT_SHA);
801
+ }
802
+ catch { }
803
+ return undefined;
804
+ }
805
+ // --- helper for mapUuid default from window ---
806
+ function getMapUuidFromWindow() {
807
+ try {
808
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
809
+ const w = window;
810
+ if (w && typeof w.sfMapUuid === "string" && w.sfMapUuid) {
811
+ return w.sfMapUuid;
812
+ }
813
+ }
814
+ catch { }
815
+ return undefined;
816
+ }
774
817
  // Main Recording Function
775
- export async function startRecording({ apiKey, backendApi = "https://api-service.sailfishqa.com", domainsToPropagateHeaderTo = [], domainsToNotPropagateHeaderTo = [], serviceVersion = "", serviceIdentifier = "", }) {
776
- let sessionId = getOrSetSessionId();
818
+ // Note - we do NOT send serviceIdentifier because
819
+ // it would be 1 serviceIdentifier per frontend user session,
820
+ // which is very wasteful
821
+ export async function startRecording({ apiKey, backendApi = "https://api-service.sailfishqa.com", domainsToPropagateHeaderTo = [], domainsToNotPropagateHeaderTo = [], serviceVersion, serviceIdentifier, gitSha, serviceAdditionalMetadata, }) {
822
+ const effectiveGitSha = gitSha ?? getCompileTimeGitSha();
823
+ const effectiveServiceIdentifier = serviceIdentifier ?? "";
824
+ const effectiveServiceVersion = serviceVersion ?? "";
825
+ const effectiveLibrary = "JS/TS"; // from Recorder.LIBRARY_CHOICES
826
+ const effectiveMapUuid = getMapUuidFromWindow();
827
+ const sessionId = getOrSetSessionId();
777
828
  initializeDomContentEvents(sessionId);
778
829
  initializeConsolePlugin(DEFAULT_CONSOLE_RECORDING_SETTINGS, sessionId);
779
830
  storeCredentialsAndConnection({ apiKey, backendApi });
@@ -806,7 +857,7 @@ export async function startRecording({ apiKey, backendApi = "https://api-service
806
857
  const captureSettingsResponse = await fetchCaptureSettings(apiKey, backendApi);
807
858
  const captureSettings = captureSettingsResponse.data?.captureSettingsFromApiKey ||
808
859
  DEFAULT_CAPTURE_SETTINGS;
809
- const sessionResponse = await startRecordingSession(apiKey, sessionId, backendApi);
860
+ const sessionResponse = await startRecordingSession(apiKey, sessionId, backendApi, effectiveServiceIdentifier, effectiveServiceVersion, effectiveMapUuid, effectiveGitSha, effectiveLibrary, serviceAdditionalMetadata);
810
861
  if (sessionResponse.data?.startRecordingSession) {
811
862
  const websocket = await initializeRecording(captureSettings,
812
863
  // DEFAULT_NETWORK_CAPTURE_SETTINGS,
@@ -839,7 +890,7 @@ export const initRecorder = async (options) => {
839
890
  };
840
891
  // Re-export from other modules
841
892
  export * from "./graphql";
842
- export { openReportIssueModal } from "./modal";
893
+ export { openReportIssueModal } from "./inAppReportIssueModal";
843
894
  export * from "./recording";
844
895
  export * from "./sendSailfishMessages";
845
896
  export * from "./types";
package/dist/recording.js CHANGED
@@ -3,8 +3,9 @@ import { getRecordConsolePlugin, } from "@sailfish-rrweb/rrweb-plugin-console-re
3
3
  // import { NetworkRecordOptions } from "@sailfish-rrweb/rrweb-plugin-network-record";
4
4
  import { EventType } from "@sailfish-rrweb/types";
5
5
  import { ZendeskAPI } from "react-zendesk";
6
+ import { Complete, DomContentEventId, DomContentSource, Loading, } from "./constants";
7
+ import suppressConsoleLogsDuringCall from "./suppressConsoleLogsDuringCall";
6
8
  import { initializeWebSocket, sendEvent } from "./websocket";
7
- import { DomContentEventId, DomContentSource, Loading, Complete } from "./constants";
8
9
  const MASK_CLASS = "sailfishSanitize";
9
10
  const ZENDESK_ELEMENT_ID = "zendesk_chat";
10
11
  const ZENDESK_PROVIDER = "Zendesk";
@@ -121,9 +122,11 @@ backendApi, apiKey, sessionId) {
121
122
  maskTextClass: MASK_CLASS,
122
123
  ...captureSettings,
123
124
  });
124
- ZendeskAPI("messenger:set", "conversationTags", [
125
- `${ZENDESK_TAG_PREFIX}${sessionId}`,
126
- ]);
125
+ suppressConsoleLogsDuringCall(() => {
126
+ ZendeskAPI("messenger:set", "conversationTags", [
127
+ `${ZENDESK_TAG_PREFIX}${sessionId}`,
128
+ ]);
129
+ });
127
130
  const handleWidgetOpen = () => {
128
131
  record.addSailfishEvent(EventType.SailfishCustom, {
129
132
  action: "customer support chat opened",
@@ -145,9 +148,11 @@ backendApi, apiKey, sessionId) {
145
148
  provider: ZENDESK_PROVIDER,
146
149
  });
147
150
  };
148
- ZendeskAPI("messenger:on", "open", handleWidgetOpen);
149
- ZendeskAPI("messenger:on", "close", handleWidgetClose);
150
- ZendeskAPI("messenger:on", "unreadMessages", handleUnreadMessages);
151
+ suppressConsoleLogsDuringCall(() => {
152
+ ZendeskAPI("messenger:on", "open", handleWidgetOpen);
153
+ ZendeskAPI("messenger:on", "close", handleWidgetClose);
154
+ ZendeskAPI("messenger:on", "unreadMessages", handleUnreadMessages);
155
+ });
151
156
  }
152
157
  catch (error) {
153
158
  console.error("Error importing plugins!", error);