@sailfish-ai/recorder 1.8.11 → 1.8.12-alpha-2

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/recording.js CHANGED
@@ -155,8 +155,8 @@ export function initializeConsolePlugin(consoleRecordSettings, sessionId) {
155
155
  }
156
156
  export async function initializeRecording(captureSettings, // TODO - Sibyl post-launch - replace type
157
157
  // networkRecordSettings: NetworkRecordOptions,
158
- backendApi, apiKey, sessionId) {
159
- const webSocket = initializeWebSocket(backendApi, apiKey, sessionId);
158
+ backendApi, apiKey, sessionId, envValue) {
159
+ const webSocket = initializeWebSocket(backendApi, apiKey, sessionId, envValue);
160
160
  try {
161
161
  record({
162
162
  emit(event) {
@@ -1,8 +1,9 @@
1
- import { CaptureSettingsResponse, CreateTriageResponse, GraphQLResponse, StartSessionResponse } from "./types";
1
+ import { CaptureSettingsResponse, CreateTriageResponse, FunctionSpanTrackingEnabledResponse, GraphQLResponse, StartSessionResponse } from "./types";
2
2
  export declare function sendGraphQLRequest<T>(operationName: string, query: string, variables: object, retries?: number, // Number of retries before giving up
3
3
  initialBackoff?: number, // Initial backoff in milliseconds
4
4
  backoffFactor?: number): Promise<GraphQLResponse<T>>;
5
5
  export declare function fetchCaptureSettings(apiKey: string, backendApi: string): Promise<GraphQLResponse<CaptureSettingsResponse>>;
6
+ export declare function fetchFunctionSpanTrackingEnabled(apiKey: string, backendApi: string): Promise<GraphQLResponse<FunctionSpanTrackingEnabledResponse>>;
6
7
  export declare function startRecordingSession(apiKey: string, recordingId: string, backendApi: string, serviceIdentifier: string, serviceVersion?: string, mapUuid?: string, gitSha?: string, library?: string, serviceAdditionalMetadata?: Record<string, any>): Promise<GraphQLResponse<StartSessionResponse>>;
7
8
  export declare function sendDomainsToNotPropagateHeaderTo(apiKey: string, domains: string[], backendApi: string): Promise<GraphQLResponse<void>>;
8
9
  export declare function createTriageFromRecorder(apiKey: string, backendApi: string, recordingSessionId: string, timestampStart: string, timestampEnd: string, description?: string): Promise<GraphQLResponse<CreateTriageResponse>>;
@@ -10,4 +10,4 @@ export declare const getUrlAndStoredUuids: () => {
10
10
  export declare function initializeDomContentEvents(sessionId: string): void;
11
11
  export declare function initializeConsolePlugin(consoleRecordSettings: LogRecordOptions, sessionId: string): void;
12
12
  export declare function initializeRecording(captureSettings: any, // TODO - Sibyl post-launch - replace type
13
- backendApi: string, apiKey: string, sessionId: string): Promise<ReconnectingWebSocket>;
13
+ backendApi: string, apiKey: string, sessionId: string, envValue?: string): Promise<ReconnectingWebSocket>;
@@ -46,6 +46,9 @@ export interface GraphQLResponse<T> {
46
46
  export interface CaptureSettingsResponse {
47
47
  captureSettingsFromApiKey: CaptureSettings;
48
48
  }
49
+ export interface FunctionSpanTrackingEnabledResponse {
50
+ isFunctionSpanTrackingEnabledFromApiKey: boolean;
51
+ }
49
52
  export interface StartSessionResponse {
50
53
  startRecordingSession: {
51
54
  id: string;
@@ -1,7 +1,12 @@
1
1
  import ReconnectingWebSocket from "reconnecting-websocket";
2
+ /**
3
+ * Clear stale function span tracking state (called from index.tsx to validate localStorage)
4
+ * This disables tracking and clears localStorage if backend says tracking is not active
5
+ */
6
+ export declare function clearStaleFuncSpanState(): void;
2
7
  export declare function flushBufferedEvents(): Promise<void>;
3
8
  export declare function sendEvent(event: any): void;
4
- export declare function initializeWebSocket(backendApi: string, apiKey: string, sessionId: string): ReconnectingWebSocket;
9
+ export declare function initializeWebSocket(backendApi: string, apiKey: string, sessionId: string, envValue?: string): ReconnectingWebSocket;
5
10
  export declare function sendMessage(message: Record<string, any>): void;
6
11
  /**
7
12
  * Enable function span tracking for this session (e.g., when report issue recording starts)
@@ -17,6 +22,11 @@ export declare function disableFunctionSpanTracking(): void;
17
22
  * Check if function span tracking is currently enabled
18
23
  */
19
24
  export declare function isFunctionSpanTrackingEnabled(): boolean;
25
+ /**
26
+ * Initialize function span tracking state from API response (during page load)
27
+ * This is called before the WebSocket connects to ensure headers are added from the start
28
+ */
29
+ export declare function initializeFunctionSpanTrackingFromApi(isEnabled: boolean): void;
20
30
  /**
21
31
  * Get the current funcspan header name and value
22
32
  * Header constants are defined here but used by HTTP interceptors in index.tsx
package/dist/websocket.js CHANGED
@@ -14,12 +14,24 @@ const FUNCSPAN_HEADER_VALUE = "1-0-5-5-0-1.0";
14
14
  // Tracking configuration type constants (must match backend TrackingConfigurationType enum)
15
15
  const TRACKING_CONFIG_GLOBAL = "global";
16
16
  const TRACKING_CONFIG_PER_SESSION = "per_session";
17
- // localStorage key for persisting global function span tracking state
18
- const FUNCSPAN_GLOBAL_STATE_KEY = "sailfish_funcspan_global_state";
19
17
  let webSocket = null;
20
18
  let isDraining = false;
21
19
  let inFlightFlush = null;
22
20
  let flushIntervalId = null;
21
+ // Function span tracking state (only manages enabled/disabled)
22
+ let funcSpanTrackingEnabled = false;
23
+ let funcSpanTimeoutId = null;
24
+ let funcSpanExpirationTime = null; // Timestamp when tracking should expire (milliseconds)
25
+ let isLocalTrackingMode = false; // True when tracking is enabled locally (Report Issue), not globally
26
+ // Saved global state when switching to local mode
27
+ let savedGlobalTimeoutId = null;
28
+ let savedGlobalExpirationTime = null;
29
+ // LocalStorage key for persisting global function span tracking state
30
+ const FUNCSPAN_STORAGE_KEY = "sailfish_funcspan_global_state";
31
+ /**
32
+ * Save global function span tracking state to localStorage
33
+ * This persists the state across page refreshes
34
+ */
23
35
  function saveGlobalFuncSpanState(enabled, expirationTimestampMs) {
24
36
  try {
25
37
  if (typeof localStorage === "undefined")
@@ -27,73 +39,73 @@ function saveGlobalFuncSpanState(enabled, expirationTimestampMs) {
27
39
  const state = {
28
40
  enabled,
29
41
  expirationTimestampMs,
42
+ savedAt: Date.now()
30
43
  };
31
- localStorage.setItem(FUNCSPAN_GLOBAL_STATE_KEY, JSON.stringify(state));
44
+ localStorage.setItem(FUNCSPAN_STORAGE_KEY, JSON.stringify(state));
32
45
  if (DEBUG) {
33
- console.log(`[Sailfish] Saved global function span state to localStorage:`, state);
46
+ console.log("[Sailfish] Saved funcSpan state to localStorage:", state);
34
47
  }
35
48
  }
36
49
  catch (e) {
37
50
  if (DEBUG) {
38
- console.warn(`[Sailfish] Failed to save global function span state to localStorage:`, e);
51
+ console.warn("[Sailfish] Failed to save funcSpan state to localStorage:", e);
39
52
  }
40
53
  }
41
54
  }
55
+ /**
56
+ * Load global function span tracking state from localStorage
57
+ */
42
58
  function loadGlobalFuncSpanState() {
43
59
  try {
44
60
  if (typeof localStorage === "undefined")
45
61
  return null;
46
- const stored = localStorage.getItem(FUNCSPAN_GLOBAL_STATE_KEY);
62
+ const stored = localStorage.getItem(FUNCSPAN_STORAGE_KEY);
47
63
  if (!stored)
48
64
  return null;
49
65
  const state = JSON.parse(stored);
50
- // Check if the stored state has expired
51
- if (state.enabled && state.expirationTimestampMs) {
52
- const now = Date.now();
53
- if (now >= state.expirationTimestampMs) {
54
- // Expired - clear localStorage and return null
55
- if (DEBUG) {
56
- console.log(`[Sailfish] Stored global function span state has expired, clearing`);
57
- }
58
- localStorage.removeItem(FUNCSPAN_GLOBAL_STATE_KEY);
59
- return null;
60
- }
61
- }
62
66
  if (DEBUG) {
63
- console.log(`[Sailfish] Loaded global function span state from localStorage:`, state);
67
+ console.log("[Sailfish] Loaded funcSpan state from localStorage:", state);
64
68
  }
65
69
  return state;
66
70
  }
67
71
  catch (e) {
68
72
  if (DEBUG) {
69
- console.warn(`[Sailfish] Failed to load global function span state from localStorage:`, e);
73
+ console.warn("[Sailfish] Failed to load funcSpan state from localStorage:", e);
70
74
  }
71
75
  return null;
72
76
  }
73
77
  }
78
+ /**
79
+ * Clear global function span tracking state from localStorage
80
+ */
74
81
  function clearGlobalFuncSpanState() {
75
82
  try {
76
83
  if (typeof localStorage === "undefined")
77
84
  return;
78
- localStorage.removeItem(FUNCSPAN_GLOBAL_STATE_KEY);
85
+ localStorage.removeItem(FUNCSPAN_STORAGE_KEY);
79
86
  if (DEBUG) {
80
- console.log(`[Sailfish] Cleared global function span state from localStorage`);
87
+ console.log("[Sailfish] Cleared funcSpan state from localStorage");
81
88
  }
82
89
  }
83
90
  catch (e) {
84
91
  if (DEBUG) {
85
- console.warn(`[Sailfish] Failed to clear global function span state from localStorage:`, e);
92
+ console.warn("[Sailfish] Failed to clear funcSpan state from localStorage:", e);
86
93
  }
87
94
  }
88
95
  }
89
- // Function span tracking state (only manages enabled/disabled)
90
- let funcSpanTrackingEnabled = false;
91
- let funcSpanTimeoutId = null;
92
- let funcSpanExpirationTime = null; // Timestamp when tracking should expire (milliseconds)
93
- let isLocalTrackingMode = false; // True when tracking is enabled locally (Report Issue), not globally
94
- // Saved global state when switching to local mode
95
- let savedGlobalTimeoutId = null;
96
- let savedGlobalExpirationTime = null;
96
+ /**
97
+ * Clear stale function span tracking state (called from index.tsx to validate localStorage)
98
+ * This disables tracking and clears localStorage if backend says tracking is not active
99
+ */
100
+ export function clearStaleFuncSpanState() {
101
+ funcSpanTrackingEnabled = false;
102
+ funcSpanExpirationTime = null;
103
+ isLocalTrackingMode = false;
104
+ clearGlobalFuncSpanState();
105
+ if (DEBUG) {
106
+ console.log("[Sailfish] Cleared stale function span tracking state (backend validation failed)");
107
+ }
108
+ }
97
109
  // Load persisted global state immediately on module initialization
98
110
  // This ensures headers are added from the very first HTTP request, even before WebSocket connects
99
111
  (() => {
@@ -105,9 +117,34 @@ let savedGlobalExpirationTime = null;
105
117
  if (DEBUG) {
106
118
  console.log(`[Sailfish] Module init: Restored global function span tracking from localStorage:`, {
107
119
  enabled: true,
108
- expirationTimestampMs: funcSpanExpirationTime,
120
+ expirationTime: funcSpanExpirationTime
109
121
  });
110
122
  }
123
+ // Check if tracking has already expired
124
+ if (funcSpanExpirationTime !== null) {
125
+ const now = Date.now();
126
+ if (now >= funcSpanExpirationTime) {
127
+ // Already expired, clear it
128
+ funcSpanTrackingEnabled = false;
129
+ funcSpanExpirationTime = null;
130
+ clearGlobalFuncSpanState();
131
+ if (DEBUG) {
132
+ console.log("[Sailfish] Module init: Persisted tracking already expired, cleared state");
133
+ }
134
+ }
135
+ else {
136
+ // State is still valid, keep it temporarily until WebSocket provides the true state
137
+ if (DEBUG) {
138
+ console.log("[Sailfish] Module init: Function span tracking is active and valid (temporary until WebSocket confirms)");
139
+ }
140
+ }
141
+ }
142
+ else {
143
+ // No expiration time - keep enabled temporarily until WebSocket provides the true state
144
+ if (DEBUG) {
145
+ console.log("[Sailfish] Module init: Function span tracking is active (no expiration, temporary until WebSocket confirms)");
146
+ }
147
+ }
111
148
  }
112
149
  })();
113
150
  function isWebSocketOpen(ws) {
@@ -203,14 +240,15 @@ export function sendEvent(event) {
203
240
  saveEventToIDB(enrichedEvent);
204
241
  }
205
242
  }
206
- export function initializeWebSocket(backendApi, apiKey, sessionId) {
207
- // Note: Global function span tracking state is now loaded at module initialization time
208
- // (see IIFE above) so headers work immediately, even before WebSocket connects.
209
- // The WebSocket "funcSpanTrackingControl" message will update the state if needed.
243
+ export function initializeWebSocket(backendApi, apiKey, sessionId, envValue) {
210
244
  const wsHost = getWebSocketHost(backendApi);
211
245
  const apiProtocol = new URL(backendApi).protocol;
212
246
  const wsScheme = apiProtocol === "https:" ? "wss" : "ws";
213
- const wsUrl = `${wsScheme}://${wsHost}/ws/notify/?apiKey=${apiKey}&sessionId=${sessionId}&sender=JS%2FTS&version=${version}`;
247
+ let wsUrl = `${wsScheme}://${wsHost}/ws/notify/?apiKey=${apiKey}&sessionId=${sessionId}&sender=JS%2FTS&version=${version}`;
248
+ // Append envValue if provided
249
+ if (envValue) {
250
+ wsUrl += `&envValue=${encodeURIComponent(envValue)}`;
251
+ }
214
252
  const options = {
215
253
  connectionTimeout: 30000,
216
254
  };
@@ -289,6 +327,25 @@ export function initializeWebSocket(backendApi, apiKey, sessionId) {
289
327
  if (DEBUG) {
290
328
  console.log(`[Sailfish] GLOBAL function span tracking auto-disabled at server expiration time`);
291
329
  }
330
+ // Notify backend that this client's tracking has expired
331
+ // Backend will update database and broadcast to all other clients
332
+ try {
333
+ if (isWebSocketOpen(webSocket)) {
334
+ webSocket.send(JSON.stringify({
335
+ type: "funcSpanTrackingExpired",
336
+ sessionId: getOrSetSessionId(),
337
+ expiredAt: Date.now(),
338
+ }));
339
+ if (DEBUG) {
340
+ console.log(`[Sailfish] Notified backend that function span tracking expired`);
341
+ }
342
+ }
343
+ }
344
+ catch (e) {
345
+ if (DEBUG) {
346
+ console.warn(`[Sailfish] Failed to notify backend of tracking expiry:`, e);
347
+ }
348
+ }
292
349
  }
293
350
  }, msUntilExpiration);
294
351
  }
@@ -319,6 +376,24 @@ export function initializeWebSocket(backendApi, apiKey, sessionId) {
319
376
  if (DEBUG) {
320
377
  console.log(`[Sailfish] GLOBAL function span tracking auto-disabled after ${timeoutSeconds}s (legacy)`);
321
378
  }
379
+ // Notify backend that this client's tracking has expired
380
+ try {
381
+ if (isWebSocketOpen(webSocket)) {
382
+ webSocket.send(JSON.stringify({
383
+ type: "funcSpanTrackingExpired",
384
+ sessionId: getOrSetSessionId(),
385
+ expiredAt: Date.now(),
386
+ }));
387
+ if (DEBUG) {
388
+ console.log(`[Sailfish] Notified backend that function span tracking expired (legacy timeout)`);
389
+ }
390
+ }
391
+ }
392
+ catch (e) {
393
+ if (DEBUG) {
394
+ console.warn(`[Sailfish] Failed to notify backend of tracking expiry:`, e);
395
+ }
396
+ }
322
397
  }
323
398
  }, timeoutSeconds * 1000);
324
399
  }
@@ -391,18 +466,14 @@ export function enableFunctionSpanTracking() {
391
466
  if (DEBUG) {
392
467
  console.log("[Sailfish] enableFunctionSpanTracking() called - Report Issue recording started (LOCAL MODE)");
393
468
  }
394
- // If global tracking was already active, save its state so we can restore it later
395
- if (funcSpanTrackingEnabled && !isLocalTrackingMode) {
396
- savedGlobalTimeoutId = funcSpanTimeoutId;
397
- savedGlobalExpirationTime = funcSpanExpirationTime;
398
- if (DEBUG) {
399
- console.log("[Sailfish] Saved global tracking state while switching to local mode");
400
- }
401
- }
402
469
  funcSpanTrackingEnabled = true;
403
470
  isLocalTrackingMode = true; // Mark as local tracking
404
471
  funcSpanExpirationTime = null; // Local mode has no expiration
405
- funcSpanTimeoutId = null; // Clear timeout (saved above if it was global)
472
+ // Clear any existing timeout
473
+ if (funcSpanTimeoutId !== null) {
474
+ window.clearTimeout(funcSpanTimeoutId);
475
+ funcSpanTimeoutId = null;
476
+ }
406
477
  // Report this session to backend for tracking with per_session configuration type
407
478
  if (isWebSocketOpen(webSocket)) {
408
479
  try {
@@ -455,29 +526,17 @@ export function disableFunctionSpanTracking() {
455
526
  console.warn(`[FUNCSPAN STOP] ✗ WebSocket not open, cannot notify tracking end`);
456
527
  }
457
528
  if (isLocalTrackingMode) {
529
+ funcSpanTrackingEnabled = false;
458
530
  isLocalTrackingMode = false;
459
- // Restore global tracking state if it was active before local mode
460
- if (savedGlobalTimeoutId !== null || savedGlobalExpirationTime !== null) {
461
- funcSpanTrackingEnabled = true; // Keep tracking enabled (global was active)
462
- funcSpanExpirationTime = savedGlobalExpirationTime;
463
- funcSpanTimeoutId = savedGlobalTimeoutId;
464
- if (DEBUG) {
465
- console.log("[Sailfish] LOCAL tracking mode disabled, restored GLOBAL tracking state");
466
- }
467
- // Clear saved state
468
- savedGlobalTimeoutId = null;
469
- savedGlobalExpirationTime = null;
470
- }
471
- else {
472
- // No global tracking was active, fully disable
473
- funcSpanTrackingEnabled = false;
474
- funcSpanExpirationTime = null;
475
- funcSpanTimeoutId = null;
476
- if (DEBUG) {
477
- console.log("[Sailfish] LOCAL tracking mode disabled, no global tracking to restore");
478
- }
531
+ funcSpanExpirationTime = null;
532
+ if (DEBUG) {
533
+ console.log("[Sailfish] LOCAL tracking mode disabled");
479
534
  }
480
535
  }
536
+ if (funcSpanTimeoutId !== null) {
537
+ window.clearTimeout(funcSpanTimeoutId);
538
+ funcSpanTimeoutId = null;
539
+ }
481
540
  }
482
541
  /**
483
542
  * Check if function span tracking is currently enabled
@@ -485,6 +544,28 @@ export function disableFunctionSpanTracking() {
485
544
  export function isFunctionSpanTrackingEnabled() {
486
545
  return funcSpanTrackingEnabled;
487
546
  }
547
+ /**
548
+ * Initialize function span tracking state from API response (during page load)
549
+ * This is called before the WebSocket connects to ensure headers are added from the start
550
+ */
551
+ export function initializeFunctionSpanTrackingFromApi(isEnabled) {
552
+ if (isEnabled && !funcSpanTrackingEnabled) {
553
+ funcSpanTrackingEnabled = true;
554
+ isLocalTrackingMode = false; // This is global tracking from API
555
+ funcSpanExpirationTime = null; // Will be set by WebSocket message later
556
+ if (DEBUG) {
557
+ console.log("[Sailfish] Function span tracking initialized as ENABLED from API check");
558
+ }
559
+ }
560
+ else if (!isEnabled && funcSpanTrackingEnabled) {
561
+ funcSpanTrackingEnabled = false;
562
+ isLocalTrackingMode = false;
563
+ funcSpanExpirationTime = null;
564
+ if (DEBUG) {
565
+ console.log("[Sailfish] Function span tracking initialized as DISABLED from API check");
566
+ }
567
+ }
568
+ }
488
569
  /**
489
570
  * Get the current funcspan header name and value
490
571
  * Header constants are defined here but used by HTTP interceptors in index.tsx
@@ -501,6 +582,7 @@ export function getFuncSpanHeader() {
501
582
  // Tracking has expired, disable it immediately
502
583
  funcSpanTrackingEnabled = false;
503
584
  funcSpanExpirationTime = null;
585
+ clearGlobalFuncSpanState();
504
586
  if (DEBUG) {
505
587
  console.log("[Sailfish] Function span tracking expired on header check - disabling now");
506
588
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sailfish-ai/recorder",
3
- "version": "1.8.11",
3
+ "version": "1.8.12-alpha-2",
4
4
  "publishPublicly": true,
5
5
  "type": "module",
6
6
  "main": "dist/recorder.cjs",
@@ -37,7 +37,7 @@
37
37
  "dist"
38
38
  ],
39
39
  "dependencies": {
40
- "@sailfish-ai/sf-map-utils": "0.4.3",
40
+ "@sailfish-ai/sf-map-utils": "0.4.4",
41
41
  "@sailfish-rrweb/rrweb-plugin-console-record": "0.5.2",
42
42
  "@sailfish-rrweb/rrweb-record-only": "0.5.2",
43
43
  "@sailfish-rrweb/types": "0.5.2",