@metamask-previews/core-backend 1.0.1-preview-aab5504 → 1.0.1-preview-25cab83

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,10 @@ 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 ??
173
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
174
+ ((_request, fn) => fn?.()), "f");
162
175
  __classPrivateFieldSet(this, _BackendWebSocketService_options, {
163
176
  url: options.url,
164
177
  timeout: options.timeout ?? 10000,
@@ -166,8 +179,8 @@ class BackendWebSocketService {
166
179
  maxReconnectDelay: options.maxReconnectDelay ?? 5000,
167
180
  requestTimeout: options.requestTimeout ?? 30000,
168
181
  }, "f");
169
- // Setup authentication (always enabled)
170
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setupAuthentication).call(this);
182
+ // Subscribe to authentication and keyring controller events
183
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_subscribeEvents).call(this);
171
184
  // Register action handlers using the method actions pattern
172
185
  __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
173
186
  }
@@ -177,18 +190,23 @@ class BackendWebSocketService {
177
190
  /**
178
191
  * Establishes WebSocket connection with smart reconnection behavior
179
192
  *
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
193
+ * Connection Requirements (all must be true):
194
+ * 1. Feature enabled (isEnabled() = true)
195
+ * 2. Wallet unlocked (checked by getBearerToken)
196
+ * 3. User signed in (checked by getBearerToken)
197
+ *
198
+ * Platform code should call this when app opens/foregrounds.
199
+ * Automatically called on KeyringController:unlock event.
184
200
  *
185
201
  * @returns Promise that resolves when connection is established
186
202
  */
187
203
  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
204
+ // Reset manual disconnect flag when explicitly connecting
205
+ __classPrivateFieldSet(this, _BackendWebSocketService_manualDisconnect, false, "f");
206
+ // Priority 1: Check if feature is enabled via callback (feature flag check)
207
+ // If feature is disabled, stop all connection attempts
190
208
  if (__classPrivateFieldGet(this, _BackendWebSocketService_isEnabled, "f") && !__classPrivateFieldGet(this, _BackendWebSocketService_isEnabled, "f").call(this)) {
191
- // Clear any pending reconnection attempts since app is disabled
209
+ // Clear any pending reconnection attempts since feature is disabled
192
210
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearTimers).call(this);
193
211
  __classPrivateFieldSet(this, _BackendWebSocketService_reconnectAttempts, 0, "f");
194
212
  return;
@@ -202,10 +220,9 @@ class BackendWebSocketService {
202
220
  await __classPrivateFieldGet(this, _BackendWebSocketService_connectionPromise, "f");
203
221
  return;
204
222
  }
205
- // Priority 2: Check authentication requirements (simplified - just check if signed in)
223
+ // Priority 2: Check authentication requirements (signed in)
206
224
  let bearerToken;
207
225
  try {
208
- // AuthenticationController.getBearerToken() handles wallet unlock checks internally
209
226
  const token = await __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").call('AuthenticationController:getBearerToken');
210
227
  if (!token) {
211
228
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_scheduleReconnect).call(this);
@@ -215,7 +232,7 @@ class BackendWebSocketService {
215
232
  }
216
233
  catch (error) {
217
234
  log('Failed to check authentication requirements', { error });
218
- // If we can't connect for ANY reason, schedule a retry
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
  /**
@@ -355,8 +374,12 @@ class BackendWebSocketService {
355
374
  return {
356
375
  state: __classPrivateFieldGet(this, _BackendWebSocketService_state, "f"),
357
376
  url: __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").url,
377
+ timeout: __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").timeout,
378
+ reconnectDelay: __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").reconnectDelay,
379
+ maxReconnectDelay: __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").maxReconnectDelay,
380
+ requestTimeout: __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").requestTimeout,
358
381
  reconnectAttempts: __classPrivateFieldGet(this, _BackendWebSocketService_reconnectAttempts, "f"),
359
- connectedAt: __classPrivateFieldGet(this, _BackendWebSocketService_connectedAt, "f") ?? undefined,
382
+ connectedAt: __classPrivateFieldGet(this, _BackendWebSocketService_connectedAt, "f"),
360
383
  };
361
384
  }
362
385
  /**
@@ -372,6 +395,7 @@ class BackendWebSocketService {
372
395
  matchingSubscriptions.push({
373
396
  subscriptionId,
374
397
  channels: subscription.channels,
398
+ channelType: subscription.channelType,
375
399
  unsubscribe: subscription.unsubscribe,
376
400
  });
377
401
  }
@@ -407,6 +431,7 @@ class BackendWebSocketService {
407
431
  matchingSubscriptions.push({
408
432
  subscriptionId,
409
433
  channels: subscription.channels,
434
+ channelType: subscription.channelType,
410
435
  unsubscribe: subscription.unsubscribe,
411
436
  });
412
437
  }
@@ -500,6 +525,8 @@ class BackendWebSocketService {
500
525
  if (__classPrivateFieldGet(this, _BackendWebSocketService_ws, "f") && __classPrivateFieldGet(this, _BackendWebSocketService_ws, "f").readyState === WebSocket.OPEN) {
501
526
  __classPrivateFieldGet(this, _BackendWebSocketService_ws, "f").close(1000, 'Service cleanup');
502
527
  }
528
+ // Set state to disconnected immediately
529
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.DISCONNECTED);
503
530
  }
504
531
  /**
505
532
  * Create and manage a subscription with server-side registration (recommended for most use cases)
@@ -528,6 +555,7 @@ class BackendWebSocketService {
528
555
  * @param options.channels - Array of channel names to subscribe to
529
556
  * @param options.callback - Callback function for handling notifications
530
557
  * @param options.requestId - Optional request ID for testing (will generate UUID if not provided)
558
+ * @param options.channelType - Channel type identifier
531
559
  * @returns Subscription object with unsubscribe method
532
560
  *
533
561
  * @example
@@ -547,7 +575,7 @@ class BackendWebSocketService {
547
575
  * @see addChannelCallback for local callbacks without server-side subscription
548
576
  */
549
577
  async subscribe(options) {
550
- const { channels, callback, requestId } = options;
578
+ const { channels, channelType, callback, requestId } = options;
551
579
  if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") !== WebSocketState.CONNECTED) {
552
580
  throw new Error(`Cannot create subscription(s) ${channels.join(', ')}: WebSocket is ${__classPrivateFieldGet(this, _BackendWebSocketService_state, "f")}`);
553
581
  }
@@ -560,10 +588,6 @@ class BackendWebSocketService {
560
588
  throw new Error('Invalid subscription response: missing subscription ID');
561
589
  }
562
590
  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
591
  // Create unsubscribe function
568
592
  const unsubscribe = async (unsubRequestId) => {
569
593
  // Send unsubscribe request first
@@ -581,12 +605,14 @@ class BackendWebSocketService {
581
605
  const subscription = {
582
606
  subscriptionId,
583
607
  channels: [...channels],
608
+ channelType,
584
609
  unsubscribe,
585
610
  };
586
611
  // Store subscription with subscription ID as key
587
612
  __classPrivateFieldGet(this, _BackendWebSocketService_subscriptions, "f").set(subscriptionId, {
588
613
  subscriptionId,
589
614
  channels: [...channels],
615
+ channelType,
590
616
  callback,
591
617
  unsubscribe,
592
618
  });
@@ -594,33 +620,28 @@ class BackendWebSocketService {
594
620
  }
595
621
  }
596
622
  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
- }
623
+ _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() {
624
+ // Subscribe to authentication state changes (sign in/out)
625
+ __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").subscribe('AuthenticationController:stateChange', (state) => {
626
+ if (state.isSignedIn) {
627
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
628
+ this.connect();
629
+ }
630
+ else {
631
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
632
+ this.disconnect();
633
+ }
634
+ }, (state) => ({ isSignedIn: state.isSignedIn }));
635
+ // Subscribe to wallet unlock event
636
+ __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").subscribe('KeyringController:unlock', () => {
637
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
638
+ this.connect();
639
+ });
640
+ // Subscribe to wallet lock event
641
+ __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").subscribe('KeyringController:lock', () => {
642
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
643
+ this.disconnect();
644
+ });
624
645
  }, _BackendWebSocketService_buildAuthenticatedUrl = function _BackendWebSocketService_buildAuthenticatedUrl(bearerToken) {
625
646
  const baseUrl = __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").url;
626
647
  // Add token as query parameter to the WebSocket URL
@@ -636,6 +657,7 @@ _BackendWebSocketService_messenger = new WeakMap(), _BackendWebSocketService_opt
636
657
  */
637
658
  async function _BackendWebSocketService_establishConnection(bearerToken) {
638
659
  const wsUrl = __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_buildAuthenticatedUrl).call(this, bearerToken);
660
+ const connectionStartTime = Date.now();
639
661
  return new Promise((resolve, reject) => {
640
662
  const ws = new WebSocket(wsUrl);
641
663
  __classPrivateFieldSet(this, _BackendWebSocketService_connectionTimeout, setTimeout(() => {
@@ -643,40 +665,47 @@ async function _BackendWebSocketService_establishConnection(bearerToken) {
643
665
  timeout: __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").timeout,
644
666
  });
645
667
  ws.close();
646
- reject(new Error(`Connection timeout after ${__classPrivateFieldGet(this, _BackendWebSocketService_options, "f").timeout}ms`));
668
+ reject(new Error(`Failed to connect to WebSocket: Connection timeout after ${__classPrivateFieldGet(this, _BackendWebSocketService_options, "f").timeout}ms`));
647
669
  }, __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").timeout), "f");
648
670
  ws.onopen = () => {
649
671
  if (__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f")) {
650
672
  clearTimeout(__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f"));
651
673
  __classPrivateFieldSet(this, _BackendWebSocketService_connectionTimeout, null, "f");
652
674
  }
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();
675
+ // Calculate connection latency
676
+ const connectionLatency = Date.now() - connectionStartTime;
677
+ // Trace successful connection with latency
678
+ __classPrivateFieldGet(this, _BackendWebSocketService_trace, "f").call(this, {
679
+ name: `${SERVICE_NAME} Connection`,
680
+ data: {
681
+ reconnectAttempt: __classPrivateFieldGet(this, _BackendWebSocketService_reconnectAttempts, "f"),
682
+ latency_ms: connectionLatency,
683
+ },
684
+ tags: {
685
+ service: SERVICE_NAME,
686
+ },
687
+ }, () => {
688
+ __classPrivateFieldSet(this, _BackendWebSocketService_ws, ws, "f");
689
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.CONNECTED);
690
+ __classPrivateFieldSet(this, _BackendWebSocketService_connectedAt, Date.now(), "f");
691
+ // Reset reconnect attempts on successful connection
692
+ __classPrivateFieldSet(this, _BackendWebSocketService_reconnectAttempts, 0, "f");
693
+ resolve();
694
+ });
659
695
  };
660
696
  ws.onerror = (event) => {
661
697
  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}`));
698
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f")) {
699
+ clearTimeout(__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f"));
700
+ __classPrivateFieldSet(this, _BackendWebSocketService_connectionTimeout, null, "f");
674
701
  }
702
+ const error = new Error(`WebSocket connection error to ${wsUrl}`);
703
+ reject(error);
675
704
  };
676
705
  ws.onclose = (event) => {
677
706
  log('WebSocket onclose event triggered', {
678
707
  code: event.code,
679
- reason: event.reason || 'none',
708
+ reason: event.reason,
680
709
  wasClean: event.wasClean,
681
710
  });
682
711
  if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") === WebSocketState.CONNECTING) {
@@ -710,7 +739,8 @@ async function _BackendWebSocketService_establishConnection(bearerToken) {
710
739
  }
711
740
  // Handle subscription notifications with valid subscriptionId
712
741
  if (__classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_isSubscriptionNotification).call(this, message)) {
713
- const handled = __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_handleSubscriptionNotification).call(this, message);
742
+ const notificationMsg = message;
743
+ const handled = __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_handleSubscriptionNotification).call(this, notificationMsg);
714
744
  // If subscription notification wasn't handled (falsy subscriptionId), fall through to channel handling
715
745
  if (handled) {
716
746
  return;
@@ -718,7 +748,8 @@ async function _BackendWebSocketService_establishConnection(bearerToken) {
718
748
  }
719
749
  // Trigger channel callbacks for any message with a channel property
720
750
  if (__classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_isChannelMessage).call(this, message)) {
721
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_handleChannelMessage).call(this, message);
751
+ const channelMsg = message;
752
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_handleChannelMessage).call(this, channelMsg);
722
753
  }
723
754
  }, _BackendWebSocketService_isServerResponse = function _BackendWebSocketService_isServerResponse(message) {
724
755
  return ('data' in message &&
@@ -748,58 +779,104 @@ async function _BackendWebSocketService_establishConnection(bearerToken) {
748
779
  if (__classPrivateFieldGet(this, _BackendWebSocketService_channelCallbacks, "f").size === 0) {
749
780
  return;
750
781
  }
751
- // Direct lookup for exact channel match
752
- __classPrivateFieldGet(this, _BackendWebSocketService_channelCallbacks, "f").get(message.channel)?.callback(message);
782
+ // Calculate notification latency: time from server sent to client received
783
+ const receivedAt = Date.now();
784
+ const latency = receivedAt - message.timestamp;
785
+ // Trace channel message processing with latency data
786
+ __classPrivateFieldGet(this, _BackendWebSocketService_trace, "f").call(this, {
787
+ name: `${SERVICE_NAME} Channel Message`,
788
+ data: {
789
+ channel: message.channel,
790
+ latency_ms: latency,
791
+ event: message.event,
792
+ },
793
+ tags: {
794
+ service: SERVICE_NAME,
795
+ channel_type: message.channel,
796
+ },
797
+ }, () => {
798
+ // Direct lookup for exact channel match
799
+ __classPrivateFieldGet(this, _BackendWebSocketService_channelCallbacks, "f").get(message.channel)?.callback(message);
800
+ });
753
801
  }, _BackendWebSocketService_handleSubscriptionNotification = function _BackendWebSocketService_handleSubscriptionNotification(message) {
754
- const { subscriptionId } = message;
802
+ const { subscriptionId, timestamp, channel } = message;
755
803
  // Only handle if subscriptionId is defined and not null (allows "0" as valid ID)
756
804
  if (subscriptionId !== null && subscriptionId !== undefined) {
757
- __classPrivateFieldGet(this, _BackendWebSocketService_subscriptions, "f").get(subscriptionId)?.callback?.(message);
805
+ const subscription = __classPrivateFieldGet(this, _BackendWebSocketService_subscriptions, "f").get(subscriptionId);
806
+ if (!subscription) {
807
+ return false;
808
+ }
809
+ // Calculate notification latency: time from server sent to client received
810
+ const receivedAt = Date.now();
811
+ const latency = receivedAt - timestamp;
812
+ // Trace notification processing with latency data
813
+ // Use stored channelType instead of parsing each time
814
+ __classPrivateFieldGet(this, _BackendWebSocketService_trace, "f").call(this, {
815
+ name: `${SERVICE_NAME} Notification`,
816
+ data: {
817
+ channel,
818
+ latency_ms: latency,
819
+ subscriptionId,
820
+ },
821
+ tags: {
822
+ service: SERVICE_NAME,
823
+ notification_type: subscription.channelType,
824
+ },
825
+ }, () => {
826
+ subscription.callback?.(message);
827
+ });
758
828
  return true;
759
829
  }
760
830
  return false;
761
831
  }, _BackendWebSocketService_parseMessage = function _BackendWebSocketService_parseMessage(data) {
762
832
  return JSON.parse(data);
763
833
  }, _BackendWebSocketService_handleClose = function _BackendWebSocketService_handleClose(event) {
834
+ // Calculate connection duration before we clear state
835
+ const connectionDuration = Date.now() - __classPrivateFieldGet(this, _BackendWebSocketService_connectedAt, "f");
764
836
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearTimers).call(this);
765
- __classPrivateFieldSet(this, _BackendWebSocketService_connectedAt, null, "f");
837
+ __classPrivateFieldSet(this, _BackendWebSocketService_connectedAt, 0, "f");
766
838
  // Clear any pending connection promise
767
839
  __classPrivateFieldSet(this, _BackendWebSocketService_connectionPromise, null, "f");
768
840
  // Clear subscriptions and pending requests on any disconnect
769
841
  // This ensures clean state for reconnection
770
842
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearPendingRequests).call(this, new Error('WebSocket connection closed'));
771
843
  __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);
844
+ // Update state to disconnected
845
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.DISCONNECTED);
846
+ // Check if this was a manual disconnect
847
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_manualDisconnect, "f")) {
848
+ // Manual disconnect - don't reconnect
775
849
  return;
776
850
  }
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', {
851
+ // Trace unexpected disconnect with details
852
+ __classPrivateFieldGet(this, _BackendWebSocketService_trace, "f").call(this, {
853
+ name: `${SERVICE_NAME} Disconnect`,
854
+ data: {
783
855
  code: event.code,
784
- });
785
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_scheduleReconnect).call(this);
786
- }
856
+ reason: event.reason || getCloseReason(event.code),
857
+ connectionDuration_ms: connectionDuration,
858
+ },
859
+ tags: {
860
+ service: SERVICE_NAME,
861
+ disconnect_type: 'unexpected',
862
+ },
863
+ }, () => {
864
+ // Empty trace callback - just measuring the event
865
+ });
866
+ // For any unexpected disconnects, attempt reconnection
867
+ // The manualDisconnect flag is the only gate - if it's false, we reconnect
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() {
790
872
  __classPrivateFieldSet(this, _BackendWebSocketService_reconnectAttempts, __classPrivateFieldGet(this, _BackendWebSocketService_reconnectAttempts, "f") + 1, "f");
791
873
  const rawDelay = __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").reconnectDelay * Math.pow(1.5, __classPrivateFieldGet(this, _BackendWebSocketService_reconnectAttempts, "f") - 1);
792
874
  const delay = Math.min(rawDelay, __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").maxReconnectDelay);
793
- log('Scheduling reconnection attempt', {
794
- attempt: __classPrivateFieldGet(this, _BackendWebSocketService_reconnectAttempts, "f"),
795
- delayMs: delay,
796
- });
797
875
  __classPrivateFieldSet(this, _BackendWebSocketService_reconnectTimer, setTimeout(() => {
798
876
  // Clear timer reference first
799
877
  __classPrivateFieldSet(this, _BackendWebSocketService_reconnectTimer, null, "f");
800
878
  // Check if connection is still enabled before reconnecting
801
879
  if (__classPrivateFieldGet(this, _BackendWebSocketService_isEnabled, "f") && !__classPrivateFieldGet(this, _BackendWebSocketService_isEnabled, "f").call(this)) {
802
- log('Reconnection disabled by isEnabled - stopping all attempts');
803
880
  __classPrivateFieldSet(this, _BackendWebSocketService_reconnectAttempts, 0, "f");
804
881
  return;
805
882
  }
@@ -829,13 +906,9 @@ async function _BackendWebSocketService_establishConnection(bearerToken) {
829
906
  const oldState = __classPrivateFieldGet(this, _BackendWebSocketService_state, "f");
830
907
  __classPrivateFieldSet(this, _BackendWebSocketService_state, newState, "f");
831
908
  if (oldState !== newState) {
832
- log('WebSocket state changed', { oldState, newState });
833
909
  // Publish connection state change event
834
910
  // Messenger handles listener errors internally, no need for try-catch
835
911
  __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").publish('BackendWebSocketService:connectionStateChanged', this.getConnectionInfo());
836
912
  }
837
- }, _BackendWebSocketService_shouldReconnectOnClose = function _BackendWebSocketService_shouldReconnectOnClose(code) {
838
- // Don't reconnect only on normal closure (manual disconnect)
839
- return code !== 1000;
840
913
  };
841
914
  //# sourceMappingURL=BackendWebSocketService.cjs.map