@metamask-previews/core-backend 1.0.1-preview-5f3688c1 → 1.0.1-preview-e7dac947

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.
@@ -10,7 +10,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
10
10
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11
11
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
12
  };
13
- var _BackendWebSocketService_instances, _BackendWebSocketService_messenger, _BackendWebSocketService_options, _BackendWebSocketService_isEnabled, _BackendWebSocketService_ws, _BackendWebSocketService_state, _BackendWebSocketService_reconnectAttempts, _BackendWebSocketService_reconnectTimer, _BackendWebSocketService_connectionTimeout, _BackendWebSocketService_connectionPromise, _BackendWebSocketService_pendingRequests, _BackendWebSocketService_connectedAt, _BackendWebSocketService_subscriptions, _BackendWebSocketService_channelCallbacks, _BackendWebSocketService_setupAuthentication, _BackendWebSocketService_buildAuthenticatedUrl, _BackendWebSocketService_establishConnection, _BackendWebSocketService_handleMessage, _BackendWebSocketService_isServerResponse, _BackendWebSocketService_isSubscriptionNotification, _BackendWebSocketService_isChannelMessage, _BackendWebSocketService_handleServerResponse, _BackendWebSocketService_handleChannelMessage, _BackendWebSocketService_handleSubscriptionNotification, _BackendWebSocketService_parseMessage, _BackendWebSocketService_handleClose, _BackendWebSocketService_handleError, _BackendWebSocketService_scheduleReconnect, _BackendWebSocketService_clearTimers, _BackendWebSocketService_clearPendingRequests, _BackendWebSocketService_clearSubscriptions, _BackendWebSocketService_setState, _BackendWebSocketService_shouldReconnectOnClose;
13
+ var _BackendWebSocketService_instances, _BackendWebSocketService_messenger, _BackendWebSocketService_options, _BackendWebSocketService_isEnabled, _BackendWebSocketService_trace, _BackendWebSocketService_ws, _BackendWebSocketService_state, _BackendWebSocketService_reconnectAttempts, _BackendWebSocketService_reconnectTimer, _BackendWebSocketService_connectionTimeout, _BackendWebSocketService_connectionPromise, _BackendWebSocketService_pendingRequests, _BackendWebSocketService_connectedAt, _BackendWebSocketService_manualDisconnect, _BackendWebSocketService_subscriptions, _BackendWebSocketService_channelCallbacks, _BackendWebSocketService_subscribeEvents, _BackendWebSocketService_buildAuthenticatedUrl, _BackendWebSocketService_establishConnection, _BackendWebSocketService_handleMessage, _BackendWebSocketService_isServerResponse, _BackendWebSocketService_isSubscriptionNotification, _BackendWebSocketService_isChannelMessage, _BackendWebSocketService_handleServerResponse, _BackendWebSocketService_handleChannelMessage, _BackendWebSocketService_handleSubscriptionNotification, _BackendWebSocketService_parseMessage, _BackendWebSocketService_handleClose, _BackendWebSocketService_handleError, _BackendWebSocketService_scheduleReconnect, _BackendWebSocketService_clearTimers, _BackendWebSocketService_clearPendingRequests, _BackendWebSocketService_clearSubscriptions, _BackendWebSocketService_setState;
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.BackendWebSocketService = exports.WebSocketEventType = exports.WebSocketState = exports.getCloseReason = void 0;
16
16
  const utils_1 = require("@metamask/utils");
@@ -109,18 +109,24 @@ var WebSocketEventType;
109
109
  /**
110
110
  * WebSocket Service with automatic reconnection, session management and direct callback routing
111
111
  *
112
+ * Connection Management:
113
+ * - Automatically subscribes to AuthenticationController:stateChange (sign in/out)
114
+ * - Automatically subscribes to KeyringController:lock/unlock events
115
+ * - Idempotent connect() function safe for multiple rapid calls
116
+ * - Auto-reconnects on unexpected disconnects (manualDisconnect = false)
117
+ *
118
+ * Platform Responsibilities:
119
+ * - Call connect() when app opens/foregrounds
120
+ * - Call disconnect() when app closes/backgrounds
121
+ * - Provide isEnabled() callback (feature flag)
122
+ * - Call destroy() on app termination
123
+ *
112
124
  * Real-Time Performance Optimizations:
113
125
  * - Fast path message routing (zero allocations)
114
126
  * - Production mode removes try-catch overhead
115
127
  * - Optimized JSON parsing with fail-fast
116
128
  * - Direct callback routing bypasses event emitters
117
129
  * - Memory cleanup and resource management
118
- *
119
- * Mobile Integration:
120
- * Mobile apps should handle lifecycle events (background/foreground) by:
121
- * 1. Calling disconnect() when app goes to background
122
- * 2. Calling connect() when app returns to foreground
123
- * 3. Calling destroy() on app termination
124
130
  */
125
131
  class BackendWebSocketService {
126
132
  // =============================================================================
@@ -140,6 +146,7 @@ class BackendWebSocketService {
140
146
  _BackendWebSocketService_messenger.set(this, void 0);
141
147
  _BackendWebSocketService_options.set(this, void 0);
142
148
  _BackendWebSocketService_isEnabled.set(this, void 0);
149
+ _BackendWebSocketService_trace.set(this, void 0);
143
150
  _BackendWebSocketService_ws.set(this, void 0);
144
151
  _BackendWebSocketService_state.set(this, WebSocketState.DISCONNECTED);
145
152
  _BackendWebSocketService_reconnectAttempts.set(this, 0);
@@ -148,7 +155,9 @@ class BackendWebSocketService {
148
155
  // Track the current connection promise to handle concurrent connection attempts
149
156
  _BackendWebSocketService_connectionPromise.set(this, null);
150
157
  _BackendWebSocketService_pendingRequests.set(this, new Map());
151
- _BackendWebSocketService_connectedAt.set(this, null);
158
+ _BackendWebSocketService_connectedAt.set(this, 0);
159
+ // Track manual disconnects to prevent automatic reconnection
160
+ _BackendWebSocketService_manualDisconnect.set(this, false);
152
161
  // Simplified subscription storage (single flat map)
153
162
  // Key: subscription ID string (e.g., 'sub_abc123def456')
154
163
  // Value: WebSocketSubscription object with channels, callback and metadata
@@ -159,6 +168,8 @@ class BackendWebSocketService {
159
168
  _BackendWebSocketService_channelCallbacks.set(this, new Map());
160
169
  __classPrivateFieldSet(this, _BackendWebSocketService_messenger, options.messenger, "f");
161
170
  __classPrivateFieldSet(this, _BackendWebSocketService_isEnabled, options.isEnabled, "f");
171
+ // Default to no-op trace function to keep core platform-agnostic
172
+ __classPrivateFieldSet(this, _BackendWebSocketService_trace, options.traceFn ?? ((_request, fn) => fn?.()), "f");
162
173
  __classPrivateFieldSet(this, _BackendWebSocketService_options, {
163
174
  url: options.url,
164
175
  timeout: options.timeout ?? 10000,
@@ -166,8 +177,8 @@ class BackendWebSocketService {
166
177
  maxReconnectDelay: options.maxReconnectDelay ?? 5000,
167
178
  requestTimeout: options.requestTimeout ?? 30000,
168
179
  }, "f");
169
- // Setup authentication (always enabled)
170
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setupAuthentication).call(this);
180
+ // Subscribe to authentication and keyring controller events
181
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_subscribeEvents).call(this);
171
182
  // Register action handlers using the method actions pattern
172
183
  __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
173
184
  }
@@ -177,18 +188,23 @@ class BackendWebSocketService {
177
188
  /**
178
189
  * Establishes WebSocket connection with smart reconnection behavior
179
190
  *
180
- * Simplified Priority System (using AuthenticationController):
181
- * 1. App closed/backgrounded Stop all attempts (save resources)
182
- * 2. User not signed in (wallet locked OR not authenticated) → Keep retrying
183
- * 3. User signed in (wallet unlocked + authenticated) → Connect successfully
191
+ * Connection Requirements (all must be true):
192
+ * 1. Feature enabled (isEnabled() = true)
193
+ * 2. Wallet unlocked (checked by getBearerToken)
194
+ * 3. User signed in (checked by getBearerToken)
195
+ *
196
+ * Platform code should call this when app opens/foregrounds.
197
+ * Automatically called on KeyringController:unlock event.
184
198
  *
185
199
  * @returns Promise that resolves when connection is established
186
200
  */
187
201
  async connect() {
188
- // Priority 1: Check if connection is enabled via callback (app lifecycle check)
189
- // If app is closed/backgrounded, stop all connection attempts to save resources
202
+ // Reset manual disconnect flag when explicitly connecting
203
+ __classPrivateFieldSet(this, _BackendWebSocketService_manualDisconnect, false, "f");
204
+ // Priority 1: Check if feature is enabled via callback (feature flag check)
205
+ // If feature is disabled, stop all connection attempts
190
206
  if (__classPrivateFieldGet(this, _BackendWebSocketService_isEnabled, "f") && !__classPrivateFieldGet(this, _BackendWebSocketService_isEnabled, "f").call(this)) {
191
- // Clear any pending reconnection attempts since app is disabled
207
+ // Clear any pending reconnection attempts since feature is disabled
192
208
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearTimers).call(this);
193
209
  __classPrivateFieldSet(this, _BackendWebSocketService_reconnectAttempts, 0, "f");
194
210
  return;
@@ -202,10 +218,9 @@ class BackendWebSocketService {
202
218
  await __classPrivateFieldGet(this, _BackendWebSocketService_connectionPromise, "f");
203
219
  return;
204
220
  }
205
- // Priority 2: Check authentication requirements (simplified - just check if signed in)
221
+ // Priority 2: Check authentication requirements (signed in)
206
222
  let bearerToken;
207
223
  try {
208
- // AuthenticationController.getBearerToken() handles wallet unlock checks internally
209
224
  const token = await __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").call('AuthenticationController:getBearerToken');
210
225
  if (!token) {
211
226
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_scheduleReconnect).call(this);
@@ -214,8 +229,10 @@ class BackendWebSocketService {
214
229
  bearerToken = token;
215
230
  }
216
231
  catch (error) {
217
- log('Failed to check authentication requirements', { error });
218
- // If we can't connect for ANY reason, schedule a retry
232
+ log('Failed to get bearer token (wallet locked or not signed in)', {
233
+ error,
234
+ });
235
+ // Can't connect - schedule retry
219
236
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_scheduleReconnect).call(this);
220
237
  return;
221
238
  }
@@ -229,7 +246,8 @@ class BackendWebSocketService {
229
246
  const errorMessage = (0, utils_1.getErrorMessage)(error);
230
247
  log('Connection attempt failed', { errorMessage, error });
231
248
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.ERROR);
232
- throw new Error(`Failed to connect to WebSocket: ${errorMessage}`);
249
+ // Rethrow to propagate error to caller
250
+ throw error;
233
251
  }
234
252
  finally {
235
253
  // Clear the connection promise when done (success or failure)
@@ -246,6 +264,8 @@ class BackendWebSocketService {
246
264
  __classPrivateFieldGet(this, _BackendWebSocketService_state, "f") === WebSocketState.DISCONNECTING) {
247
265
  return;
248
266
  }
267
+ // Mark this as a manual disconnect to prevent automatic reconnection
268
+ __classPrivateFieldSet(this, _BackendWebSocketService_manualDisconnect, true, "f");
249
269
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.DISCONNECTING);
250
270
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearTimers).call(this);
251
271
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearPendingRequests).call(this, new Error('WebSocket disconnected'));
@@ -254,7 +274,6 @@ class BackendWebSocketService {
254
274
  if (__classPrivateFieldGet(this, _BackendWebSocketService_ws, "f")) {
255
275
  __classPrivateFieldGet(this, _BackendWebSocketService_ws, "f").close(1000, 'Normal closure');
256
276
  }
257
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.DISCONNECTED);
258
277
  log('WebSocket manually disconnected');
259
278
  }
260
279
  /**
@@ -356,7 +375,7 @@ class BackendWebSocketService {
356
375
  state: __classPrivateFieldGet(this, _BackendWebSocketService_state, "f"),
357
376
  url: __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").url,
358
377
  reconnectAttempts: __classPrivateFieldGet(this, _BackendWebSocketService_reconnectAttempts, "f"),
359
- connectedAt: __classPrivateFieldGet(this, _BackendWebSocketService_connectedAt, "f") ?? undefined,
378
+ connectedAt: __classPrivateFieldGet(this, _BackendWebSocketService_connectedAt, "f"),
360
379
  };
361
380
  }
362
381
  /**
@@ -372,6 +391,7 @@ class BackendWebSocketService {
372
391
  matchingSubscriptions.push({
373
392
  subscriptionId,
374
393
  channels: subscription.channels,
394
+ channelType: subscription.channelType,
375
395
  unsubscribe: subscription.unsubscribe,
376
396
  });
377
397
  }
@@ -407,6 +427,7 @@ class BackendWebSocketService {
407
427
  matchingSubscriptions.push({
408
428
  subscriptionId,
409
429
  channels: subscription.channels,
430
+ channelType: subscription.channelType,
410
431
  unsubscribe: subscription.unsubscribe,
411
432
  });
412
433
  }
@@ -500,6 +521,8 @@ class BackendWebSocketService {
500
521
  if (__classPrivateFieldGet(this, _BackendWebSocketService_ws, "f") && __classPrivateFieldGet(this, _BackendWebSocketService_ws, "f").readyState === WebSocket.OPEN) {
501
522
  __classPrivateFieldGet(this, _BackendWebSocketService_ws, "f").close(1000, 'Service cleanup');
502
523
  }
524
+ // Set state to disconnected immediately
525
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.DISCONNECTED);
503
526
  }
504
527
  /**
505
528
  * Create and manage a subscription with server-side registration (recommended for most use cases)
@@ -528,6 +551,7 @@ class BackendWebSocketService {
528
551
  * @param options.channels - Array of channel names to subscribe to
529
552
  * @param options.callback - Callback function for handling notifications
530
553
  * @param options.requestId - Optional request ID for testing (will generate UUID if not provided)
554
+ * @param options.channelType - Channel type identifier
531
555
  * @returns Subscription object with unsubscribe method
532
556
  *
533
557
  * @example
@@ -547,7 +571,7 @@ class BackendWebSocketService {
547
571
  * @see addChannelCallback for local callbacks without server-side subscription
548
572
  */
549
573
  async subscribe(options) {
550
- const { channels, callback, requestId } = options;
574
+ const { channels, channelType, callback, requestId } = options;
551
575
  if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") !== WebSocketState.CONNECTED) {
552
576
  throw new Error(`Cannot create subscription(s) ${channels.join(', ')}: WebSocket is ${__classPrivateFieldGet(this, _BackendWebSocketService_state, "f")}`);
553
577
  }
@@ -560,10 +584,6 @@ class BackendWebSocketService {
560
584
  throw new Error('Invalid subscription response: missing subscription ID');
561
585
  }
562
586
  const { subscriptionId } = subscriptionResponse;
563
- // Check for failures
564
- if (subscriptionResponse.failed && subscriptionResponse.failed.length > 0) {
565
- throw new Error(`Subscription failed for channels: ${subscriptionResponse.failed.join(', ')}`);
566
- }
567
587
  // Create unsubscribe function
568
588
  const unsubscribe = async (unsubRequestId) => {
569
589
  // Send unsubscribe request first
@@ -581,12 +601,14 @@ class BackendWebSocketService {
581
601
  const subscription = {
582
602
  subscriptionId,
583
603
  channels: [...channels],
604
+ channelType,
584
605
  unsubscribe,
585
606
  };
586
607
  // Store subscription with subscription ID as key
587
608
  __classPrivateFieldGet(this, _BackendWebSocketService_subscriptions, "f").set(subscriptionId, {
588
609
  subscriptionId,
589
610
  channels: [...channels],
611
+ channelType,
590
612
  callback,
591
613
  unsubscribe,
592
614
  });
@@ -594,33 +616,28 @@ class BackendWebSocketService {
594
616
  }
595
617
  }
596
618
  exports.BackendWebSocketService = BackendWebSocketService;
597
- _BackendWebSocketService_messenger = new WeakMap(), _BackendWebSocketService_options = new WeakMap(), _BackendWebSocketService_isEnabled = new WeakMap(), _BackendWebSocketService_ws = new WeakMap(), _BackendWebSocketService_state = new WeakMap(), _BackendWebSocketService_reconnectAttempts = new WeakMap(), _BackendWebSocketService_reconnectTimer = new WeakMap(), _BackendWebSocketService_connectionTimeout = new WeakMap(), _BackendWebSocketService_connectionPromise = new WeakMap(), _BackendWebSocketService_pendingRequests = new WeakMap(), _BackendWebSocketService_connectedAt = new WeakMap(), _BackendWebSocketService_subscriptions = new WeakMap(), _BackendWebSocketService_channelCallbacks = new WeakMap(), _BackendWebSocketService_instances = new WeakSet(), _BackendWebSocketService_setupAuthentication = function _BackendWebSocketService_setupAuthentication() {
598
- try {
599
- // Subscribe to authentication state changes - this includes wallet unlock state
600
- // AuthenticationController can only be signed in if wallet is unlocked
601
- // Using selector to only listen for isSignedIn property changes for better performance
602
- __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").subscribe('AuthenticationController:stateChange', (isSignedIn) => {
603
- if (isSignedIn) {
604
- // User signed in (wallet unlocked + authenticated) - try to connect
605
- // Clear any pending reconnection timer since we're attempting connection
606
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearTimers).call(this);
607
- this.connect().catch((error) => {
608
- log('Failed to connect after sign-in', { error });
609
- });
610
- }
611
- else {
612
- // User signed out (wallet locked OR signed out) - disconnect and stop reconnection attempts
613
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearTimers).call(this);
614
- __classPrivateFieldSet(this, _BackendWebSocketService_reconnectAttempts, 0, "f");
615
- this.disconnect().catch((error) => {
616
- log('Failed to disconnect after sign-out', { error });
617
- });
618
- }
619
- }, (state) => state.isSignedIn);
620
- }
621
- catch (error) {
622
- throw new Error(`Authentication setup failed: ${(0, utils_1.getErrorMessage)(error)}`);
623
- }
619
+ _BackendWebSocketService_messenger = new WeakMap(), _BackendWebSocketService_options = new WeakMap(), _BackendWebSocketService_isEnabled = new WeakMap(), _BackendWebSocketService_trace = new WeakMap(), _BackendWebSocketService_ws = new WeakMap(), _BackendWebSocketService_state = new WeakMap(), _BackendWebSocketService_reconnectAttempts = new WeakMap(), _BackendWebSocketService_reconnectTimer = new WeakMap(), _BackendWebSocketService_connectionTimeout = new WeakMap(), _BackendWebSocketService_connectionPromise = new WeakMap(), _BackendWebSocketService_pendingRequests = new WeakMap(), _BackendWebSocketService_connectedAt = new WeakMap(), _BackendWebSocketService_manualDisconnect = new WeakMap(), _BackendWebSocketService_subscriptions = new WeakMap(), _BackendWebSocketService_channelCallbacks = new WeakMap(), _BackendWebSocketService_instances = new WeakSet(), _BackendWebSocketService_subscribeEvents = function _BackendWebSocketService_subscribeEvents() {
620
+ // Subscribe to authentication state changes (sign in/out)
621
+ __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").subscribe('AuthenticationController:stateChange', (state) => {
622
+ if (state.isSignedIn) {
623
+ // eslint-disable-next-line no-void
624
+ void this.connect();
625
+ }
626
+ else {
627
+ // eslint-disable-next-line no-void
628
+ void this.disconnect();
629
+ }
630
+ });
631
+ // Subscribe to wallet unlock event
632
+ __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").subscribe('KeyringController:unlock', () => {
633
+ // eslint-disable-next-line no-void
634
+ void this.connect();
635
+ });
636
+ // Subscribe to wallet lock event
637
+ __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").subscribe('KeyringController:lock', () => {
638
+ // eslint-disable-next-line no-void
639
+ void this.disconnect();
640
+ });
624
641
  }, _BackendWebSocketService_buildAuthenticatedUrl = function _BackendWebSocketService_buildAuthenticatedUrl(bearerToken) {
625
642
  const baseUrl = __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").url;
626
643
  // Add token as query parameter to the WebSocket URL
@@ -636,6 +653,7 @@ _BackendWebSocketService_messenger = new WeakMap(), _BackendWebSocketService_opt
636
653
  */
637
654
  async function _BackendWebSocketService_establishConnection(bearerToken) {
638
655
  const wsUrl = __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_buildAuthenticatedUrl).call(this, bearerToken);
656
+ const connectionStartTime = Date.now();
639
657
  return new Promise((resolve, reject) => {
640
658
  const ws = new WebSocket(wsUrl);
641
659
  __classPrivateFieldSet(this, _BackendWebSocketService_connectionTimeout, setTimeout(() => {
@@ -643,40 +661,48 @@ async function _BackendWebSocketService_establishConnection(bearerToken) {
643
661
  timeout: __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").timeout,
644
662
  });
645
663
  ws.close();
646
- reject(new Error(`Connection timeout after ${__classPrivateFieldGet(this, _BackendWebSocketService_options, "f").timeout}ms`));
664
+ reject(new Error(`Failed to connect to WebSocket: Connection timeout after ${__classPrivateFieldGet(this, _BackendWebSocketService_options, "f").timeout}ms`));
647
665
  }, __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").timeout), "f");
648
666
  ws.onopen = () => {
649
667
  if (__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f")) {
650
668
  clearTimeout(__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f"));
651
669
  __classPrivateFieldSet(this, _BackendWebSocketService_connectionTimeout, null, "f");
652
670
  }
653
- __classPrivateFieldSet(this, _BackendWebSocketService_ws, ws, "f");
654
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.CONNECTED);
655
- __classPrivateFieldSet(this, _BackendWebSocketService_connectedAt, Date.now(), "f");
656
- // Reset reconnect attempts on successful connection
657
- __classPrivateFieldSet(this, _BackendWebSocketService_reconnectAttempts, 0, "f");
658
- resolve();
671
+ // Calculate connection latency
672
+ const connectionLatency = Date.now() - connectionStartTime;
673
+ // Trace successful connection with latency
674
+ __classPrivateFieldGet(this, _BackendWebSocketService_trace, "f").call(this, {
675
+ name: `${SERVICE_NAME} Connection`,
676
+ data: {
677
+ reconnectAttempt: __classPrivateFieldGet(this, _BackendWebSocketService_reconnectAttempts, "f"),
678
+ latency_ms: connectionLatency,
679
+ },
680
+ tags: {
681
+ service: SERVICE_NAME,
682
+ },
683
+ }, () => {
684
+ __classPrivateFieldSet(this, _BackendWebSocketService_ws, ws, "f");
685
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.CONNECTED);
686
+ __classPrivateFieldSet(this, _BackendWebSocketService_connectedAt, Date.now(), "f");
687
+ // Reset reconnect attempts on successful connection
688
+ __classPrivateFieldSet(this, _BackendWebSocketService_reconnectAttempts, 0, "f");
689
+ resolve();
690
+ });
659
691
  };
660
692
  ws.onerror = (event) => {
661
693
  log('WebSocket onerror event triggered', { event });
662
- if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") === WebSocketState.CONNECTING) {
663
- // Handle connection-phase errors
664
- if (__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f")) {
665
- clearTimeout(__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f"));
666
- __classPrivateFieldSet(this, _BackendWebSocketService_connectionTimeout, null, "f");
667
- }
668
- const error = new Error(`WebSocket connection error to ${wsUrl}`);
669
- reject(error);
670
- }
671
- else {
672
- // Handle runtime errors
673
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_handleError).call(this, new Error(`WebSocket error: ${event.type}`));
694
+ // Handle connection-phase errors
695
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f")) {
696
+ clearTimeout(__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f"));
697
+ __classPrivateFieldSet(this, _BackendWebSocketService_connectionTimeout, null, "f");
674
698
  }
699
+ const error = new Error(`WebSocket connection error to ${wsUrl}`);
700
+ reject(error);
675
701
  };
676
702
  ws.onclose = (event) => {
677
703
  log('WebSocket onclose event triggered', {
678
704
  code: event.code,
679
- reason: event.reason || 'none',
705
+ reason: event.reason,
680
706
  wasClean: event.wasClean,
681
707
  });
682
708
  if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") === WebSocketState.CONNECTING) {
@@ -748,42 +774,98 @@ async function _BackendWebSocketService_establishConnection(bearerToken) {
748
774
  if (__classPrivateFieldGet(this, _BackendWebSocketService_channelCallbacks, "f").size === 0) {
749
775
  return;
750
776
  }
751
- // Direct lookup for exact channel match
752
- __classPrivateFieldGet(this, _BackendWebSocketService_channelCallbacks, "f").get(message.channel)?.callback(message);
777
+ // Calculate notification latency: time from server sent to client received
778
+ const receivedAt = Date.now();
779
+ const latency = receivedAt - message.timestamp;
780
+ // Trace channel message processing with latency data
781
+ __classPrivateFieldGet(this, _BackendWebSocketService_trace, "f").call(this, {
782
+ name: `${SERVICE_NAME} Channel Message`,
783
+ data: {
784
+ channel: message.channel,
785
+ latency_ms: latency,
786
+ event: message.event,
787
+ },
788
+ tags: {
789
+ service: SERVICE_NAME,
790
+ channel_type: message.channel,
791
+ },
792
+ }, () => {
793
+ // Direct lookup for exact channel match
794
+ __classPrivateFieldGet(this, _BackendWebSocketService_channelCallbacks, "f").get(message.channel)?.callback(message);
795
+ });
753
796
  }, _BackendWebSocketService_handleSubscriptionNotification = function _BackendWebSocketService_handleSubscriptionNotification(message) {
754
- const { subscriptionId } = message;
797
+ const { subscriptionId, timestamp, channel } = message;
755
798
  // Only handle if subscriptionId is defined and not null (allows "0" as valid ID)
756
799
  if (subscriptionId !== null && subscriptionId !== undefined) {
757
- __classPrivateFieldGet(this, _BackendWebSocketService_subscriptions, "f").get(subscriptionId)?.callback?.(message);
800
+ const subscription = __classPrivateFieldGet(this, _BackendWebSocketService_subscriptions, "f").get(subscriptionId);
801
+ if (!subscription) {
802
+ return false;
803
+ }
804
+ // Calculate notification latency: time from server sent to client received
805
+ const receivedAt = Date.now();
806
+ const latency = receivedAt - timestamp;
807
+ // Trace notification processing with latency data
808
+ // Use stored channelType instead of parsing each time
809
+ __classPrivateFieldGet(this, _BackendWebSocketService_trace, "f").call(this, {
810
+ name: `${SERVICE_NAME} Notification`,
811
+ data: {
812
+ channel,
813
+ latency_ms: latency,
814
+ subscriptionId,
815
+ },
816
+ tags: {
817
+ service: SERVICE_NAME,
818
+ notification_type: subscription.channelType,
819
+ },
820
+ }, () => {
821
+ subscription.callback?.(message);
822
+ });
758
823
  return true;
759
824
  }
760
825
  return false;
761
826
  }, _BackendWebSocketService_parseMessage = function _BackendWebSocketService_parseMessage(data) {
762
827
  return JSON.parse(data);
763
828
  }, _BackendWebSocketService_handleClose = function _BackendWebSocketService_handleClose(event) {
829
+ // Calculate connection duration before we clear state
830
+ const connectionDuration = Date.now() - __classPrivateFieldGet(this, _BackendWebSocketService_connectedAt, "f");
764
831
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearTimers).call(this);
765
- __classPrivateFieldSet(this, _BackendWebSocketService_connectedAt, null, "f");
832
+ __classPrivateFieldSet(this, _BackendWebSocketService_connectedAt, 0, "f");
766
833
  // Clear any pending connection promise
767
834
  __classPrivateFieldSet(this, _BackendWebSocketService_connectionPromise, null, "f");
768
835
  // Clear subscriptions and pending requests on any disconnect
769
836
  // This ensures clean state for reconnection
770
837
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearPendingRequests).call(this, new Error('WebSocket connection closed'));
771
838
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearSubscriptions).call(this);
772
- if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") === WebSocketState.DISCONNECTING) {
773
- // Manual disconnect
774
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.DISCONNECTED);
839
+ // Update state to disconnected
840
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.DISCONNECTED);
841
+ // Check if this was a manual disconnect
842
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_manualDisconnect, "f")) {
843
+ // Manual disconnect - don't reconnect
844
+ log('WebSocket closed due to manual disconnect, not reconnecting');
775
845
  return;
776
846
  }
777
- // For unexpected disconnects, update the state to reflect that we're disconnected
778
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.DISCONNECTED);
779
- // Check if we should attempt reconnection based on close code
780
- const shouldReconnect = __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_shouldReconnectOnClose).call(this, event.code);
781
- if (shouldReconnect) {
782
- log('Connection lost unexpectedly, will attempt reconnection', {
847
+ // Trace unexpected disconnect with details
848
+ __classPrivateFieldGet(this, _BackendWebSocketService_trace, "f").call(this, {
849
+ name: `${SERVICE_NAME} Disconnect`,
850
+ data: {
783
851
  code: event.code,
784
- });
785
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_scheduleReconnect).call(this);
786
- }
852
+ reason: event.reason || getCloseReason(event.code),
853
+ connectionDuration_ms: connectionDuration,
854
+ },
855
+ tags: {
856
+ service: SERVICE_NAME,
857
+ disconnect_type: 'unexpected',
858
+ },
859
+ }, () => {
860
+ // Empty trace callback - just measuring the event
861
+ });
862
+ // For any unexpected disconnects, attempt reconnection
863
+ // The manualDisconnect flag is the only gate - if it's false, we reconnect
864
+ log('Connection lost unexpectedly, will attempt reconnection', {
865
+ code: event.code,
866
+ reason: event.reason,
867
+ });
868
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_scheduleReconnect).call(this);
787
869
  }, _BackendWebSocketService_handleError = function _BackendWebSocketService_handleError(_error) {
788
870
  // Placeholder for future error handling logic
789
871
  }, _BackendWebSocketService_scheduleReconnect = function _BackendWebSocketService_scheduleReconnect() {
@@ -834,8 +916,5 @@ async function _BackendWebSocketService_establishConnection(bearerToken) {
834
916
  // Messenger handles listener errors internally, no need for try-catch
835
917
  __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").publish('BackendWebSocketService:connectionStateChanged', this.getConnectionInfo());
836
918
  }
837
- }, _BackendWebSocketService_shouldReconnectOnClose = function _BackendWebSocketService_shouldReconnectOnClose(code) {
838
- // Don't reconnect only on normal closure (manual disconnect)
839
- return code !== 1000;
840
919
  };
841
920
  //# sourceMappingURL=BackendWebSocketService.cjs.map