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

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_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;
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;
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,24 +109,18 @@ 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
- *
124
112
  * Real-Time Performance Optimizations:
125
113
  * - Fast path message routing (zero allocations)
126
114
  * - Production mode removes try-catch overhead
127
115
  * - Optimized JSON parsing with fail-fast
128
116
  * - Direct callback routing bypasses event emitters
129
117
  * - 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
130
124
  */
131
125
  class BackendWebSocketService {
132
126
  // =============================================================================
@@ -146,7 +140,6 @@ class BackendWebSocketService {
146
140
  _BackendWebSocketService_messenger.set(this, void 0);
147
141
  _BackendWebSocketService_options.set(this, void 0);
148
142
  _BackendWebSocketService_isEnabled.set(this, void 0);
149
- _BackendWebSocketService_trace.set(this, void 0);
150
143
  _BackendWebSocketService_ws.set(this, void 0);
151
144
  _BackendWebSocketService_state.set(this, WebSocketState.DISCONNECTED);
152
145
  _BackendWebSocketService_reconnectAttempts.set(this, 0);
@@ -155,9 +148,7 @@ class BackendWebSocketService {
155
148
  // Track the current connection promise to handle concurrent connection attempts
156
149
  _BackendWebSocketService_connectionPromise.set(this, null);
157
150
  _BackendWebSocketService_pendingRequests.set(this, new Map());
158
- _BackendWebSocketService_connectedAt.set(this, 0);
159
- // Track manual disconnects to prevent automatic reconnection
160
- _BackendWebSocketService_manualDisconnect.set(this, false);
151
+ _BackendWebSocketService_connectedAt.set(this, null);
161
152
  // Simplified subscription storage (single flat map)
162
153
  // Key: subscription ID string (e.g., 'sub_abc123def456')
163
154
  // Value: WebSocketSubscription object with channels, callback and metadata
@@ -168,10 +159,6 @@ class BackendWebSocketService {
168
159
  _BackendWebSocketService_channelCallbacks.set(this, new Map());
169
160
  __classPrivateFieldSet(this, _BackendWebSocketService_messenger, options.messenger, "f");
170
161
  __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");
175
162
  __classPrivateFieldSet(this, _BackendWebSocketService_options, {
176
163
  url: options.url,
177
164
  timeout: options.timeout ?? 10000,
@@ -179,8 +166,8 @@ class BackendWebSocketService {
179
166
  maxReconnectDelay: options.maxReconnectDelay ?? 5000,
180
167
  requestTimeout: options.requestTimeout ?? 30000,
181
168
  }, "f");
182
- // Subscribe to authentication and keyring controller events
183
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_subscribeEvents).call(this);
169
+ // Setup authentication (always enabled)
170
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setupAuthentication).call(this);
184
171
  // Register action handlers using the method actions pattern
185
172
  __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
186
173
  }
@@ -190,23 +177,18 @@ class BackendWebSocketService {
190
177
  /**
191
178
  * Establishes WebSocket connection with smart reconnection behavior
192
179
  *
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.
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
200
184
  *
201
185
  * @returns Promise that resolves when connection is established
202
186
  */
203
187
  async connect() {
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
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
208
190
  if (__classPrivateFieldGet(this, _BackendWebSocketService_isEnabled, "f") && !__classPrivateFieldGet(this, _BackendWebSocketService_isEnabled, "f").call(this)) {
209
- // Clear any pending reconnection attempts since feature is disabled
191
+ // Clear any pending reconnection attempts since app is disabled
210
192
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearTimers).call(this);
211
193
  __classPrivateFieldSet(this, _BackendWebSocketService_reconnectAttempts, 0, "f");
212
194
  return;
@@ -220,9 +202,10 @@ class BackendWebSocketService {
220
202
  await __classPrivateFieldGet(this, _BackendWebSocketService_connectionPromise, "f");
221
203
  return;
222
204
  }
223
- // Priority 2: Check authentication requirements (signed in)
205
+ // Priority 2: Check authentication requirements (simplified - just check if signed in)
224
206
  let bearerToken;
225
207
  try {
208
+ // AuthenticationController.getBearerToken() handles wallet unlock checks internally
226
209
  const token = await __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").call('AuthenticationController:getBearerToken');
227
210
  if (!token) {
228
211
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_scheduleReconnect).call(this);
@@ -232,7 +215,7 @@ class BackendWebSocketService {
232
215
  }
233
216
  catch (error) {
234
217
  log('Failed to check authentication requirements', { error });
235
- // Can't connect - schedule retry
218
+ // If we can't connect for ANY reason, schedule a retry
236
219
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_scheduleReconnect).call(this);
237
220
  return;
238
221
  }
@@ -246,8 +229,7 @@ class BackendWebSocketService {
246
229
  const errorMessage = (0, utils_1.getErrorMessage)(error);
247
230
  log('Connection attempt failed', { errorMessage, error });
248
231
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.ERROR);
249
- // Rethrow to propagate error to caller
250
- throw error;
232
+ throw new Error(`Failed to connect to WebSocket: ${errorMessage}`);
251
233
  }
252
234
  finally {
253
235
  // Clear the connection promise when done (success or failure)
@@ -264,8 +246,6 @@ class BackendWebSocketService {
264
246
  __classPrivateFieldGet(this, _BackendWebSocketService_state, "f") === WebSocketState.DISCONNECTING) {
265
247
  return;
266
248
  }
267
- // Mark this as a manual disconnect to prevent automatic reconnection
268
- __classPrivateFieldSet(this, _BackendWebSocketService_manualDisconnect, true, "f");
269
249
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.DISCONNECTING);
270
250
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearTimers).call(this);
271
251
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearPendingRequests).call(this, new Error('WebSocket disconnected'));
@@ -274,6 +254,7 @@ class BackendWebSocketService {
274
254
  if (__classPrivateFieldGet(this, _BackendWebSocketService_ws, "f")) {
275
255
  __classPrivateFieldGet(this, _BackendWebSocketService_ws, "f").close(1000, 'Normal closure');
276
256
  }
257
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.DISCONNECTED);
277
258
  log('WebSocket manually disconnected');
278
259
  }
279
260
  /**
@@ -374,12 +355,8 @@ class BackendWebSocketService {
374
355
  return {
375
356
  state: __classPrivateFieldGet(this, _BackendWebSocketService_state, "f"),
376
357
  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,
381
358
  reconnectAttempts: __classPrivateFieldGet(this, _BackendWebSocketService_reconnectAttempts, "f"),
382
- connectedAt: __classPrivateFieldGet(this, _BackendWebSocketService_connectedAt, "f"),
359
+ connectedAt: __classPrivateFieldGet(this, _BackendWebSocketService_connectedAt, "f") ?? undefined,
383
360
  };
384
361
  }
385
362
  /**
@@ -395,7 +372,6 @@ class BackendWebSocketService {
395
372
  matchingSubscriptions.push({
396
373
  subscriptionId,
397
374
  channels: subscription.channels,
398
- channelType: subscription.channelType,
399
375
  unsubscribe: subscription.unsubscribe,
400
376
  });
401
377
  }
@@ -431,7 +407,6 @@ class BackendWebSocketService {
431
407
  matchingSubscriptions.push({
432
408
  subscriptionId,
433
409
  channels: subscription.channels,
434
- channelType: subscription.channelType,
435
410
  unsubscribe: subscription.unsubscribe,
436
411
  });
437
412
  }
@@ -525,8 +500,6 @@ class BackendWebSocketService {
525
500
  if (__classPrivateFieldGet(this, _BackendWebSocketService_ws, "f") && __classPrivateFieldGet(this, _BackendWebSocketService_ws, "f").readyState === WebSocket.OPEN) {
526
501
  __classPrivateFieldGet(this, _BackendWebSocketService_ws, "f").close(1000, 'Service cleanup');
527
502
  }
528
- // Set state to disconnected immediately
529
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.DISCONNECTED);
530
503
  }
531
504
  /**
532
505
  * Create and manage a subscription with server-side registration (recommended for most use cases)
@@ -555,7 +528,6 @@ class BackendWebSocketService {
555
528
  * @param options.channels - Array of channel names to subscribe to
556
529
  * @param options.callback - Callback function for handling notifications
557
530
  * @param options.requestId - Optional request ID for testing (will generate UUID if not provided)
558
- * @param options.channelType - Channel type identifier
559
531
  * @returns Subscription object with unsubscribe method
560
532
  *
561
533
  * @example
@@ -575,7 +547,7 @@ class BackendWebSocketService {
575
547
  * @see addChannelCallback for local callbacks without server-side subscription
576
548
  */
577
549
  async subscribe(options) {
578
- const { channels, channelType, callback, requestId } = options;
550
+ const { channels, callback, requestId } = options;
579
551
  if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") !== WebSocketState.CONNECTED) {
580
552
  throw new Error(`Cannot create subscription(s) ${channels.join(', ')}: WebSocket is ${__classPrivateFieldGet(this, _BackendWebSocketService_state, "f")}`);
581
553
  }
@@ -588,6 +560,10 @@ class BackendWebSocketService {
588
560
  throw new Error('Invalid subscription response: missing subscription ID');
589
561
  }
590
562
  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
+ }
591
567
  // Create unsubscribe function
592
568
  const unsubscribe = async (unsubRequestId) => {
593
569
  // Send unsubscribe request first
@@ -605,14 +581,12 @@ class BackendWebSocketService {
605
581
  const subscription = {
606
582
  subscriptionId,
607
583
  channels: [...channels],
608
- channelType,
609
584
  unsubscribe,
610
585
  };
611
586
  // Store subscription with subscription ID as key
612
587
  __classPrivateFieldGet(this, _BackendWebSocketService_subscriptions, "f").set(subscriptionId, {
613
588
  subscriptionId,
614
589
  channels: [...channels],
615
- channelType,
616
590
  callback,
617
591
  unsubscribe,
618
592
  });
@@ -620,28 +594,33 @@ class BackendWebSocketService {
620
594
  }
621
595
  }
622
596
  exports.BackendWebSocketService = BackendWebSocketService;
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
- });
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
+ }
645
624
  }, _BackendWebSocketService_buildAuthenticatedUrl = function _BackendWebSocketService_buildAuthenticatedUrl(bearerToken) {
646
625
  const baseUrl = __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").url;
647
626
  // Add token as query parameter to the WebSocket URL
@@ -657,7 +636,6 @@ _BackendWebSocketService_messenger = new WeakMap(), _BackendWebSocketService_opt
657
636
  */
658
637
  async function _BackendWebSocketService_establishConnection(bearerToken) {
659
638
  const wsUrl = __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_buildAuthenticatedUrl).call(this, bearerToken);
660
- const connectionStartTime = Date.now();
661
639
  return new Promise((resolve, reject) => {
662
640
  const ws = new WebSocket(wsUrl);
663
641
  __classPrivateFieldSet(this, _BackendWebSocketService_connectionTimeout, setTimeout(() => {
@@ -665,47 +643,40 @@ async function _BackendWebSocketService_establishConnection(bearerToken) {
665
643
  timeout: __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").timeout,
666
644
  });
667
645
  ws.close();
668
- reject(new Error(`Failed to connect to WebSocket: Connection timeout after ${__classPrivateFieldGet(this, _BackendWebSocketService_options, "f").timeout}ms`));
646
+ reject(new Error(`Connection timeout after ${__classPrivateFieldGet(this, _BackendWebSocketService_options, "f").timeout}ms`));
669
647
  }, __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").timeout), "f");
670
648
  ws.onopen = () => {
671
649
  if (__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f")) {
672
650
  clearTimeout(__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f"));
673
651
  __classPrivateFieldSet(this, _BackendWebSocketService_connectionTimeout, null, "f");
674
652
  }
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
- });
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();
695
659
  };
696
660
  ws.onerror = (event) => {
697
661
  log('WebSocket onerror event triggered', { event });
698
- if (__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f")) {
699
- clearTimeout(__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f"));
700
- __classPrivateFieldSet(this, _BackendWebSocketService_connectionTimeout, null, "f");
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}`));
701
674
  }
702
- const error = new Error(`WebSocket connection error to ${wsUrl}`);
703
- reject(error);
704
675
  };
705
676
  ws.onclose = (event) => {
706
677
  log('WebSocket onclose event triggered', {
707
678
  code: event.code,
708
- reason: event.reason,
679
+ reason: event.reason || 'none',
709
680
  wasClean: event.wasClean,
710
681
  });
711
682
  if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") === WebSocketState.CONNECTING) {
@@ -739,8 +710,7 @@ async function _BackendWebSocketService_establishConnection(bearerToken) {
739
710
  }
740
711
  // Handle subscription notifications with valid subscriptionId
741
712
  if (__classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_isSubscriptionNotification).call(this, message)) {
742
- const notificationMsg = message;
743
- const handled = __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_handleSubscriptionNotification).call(this, notificationMsg);
713
+ const handled = __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_handleSubscriptionNotification).call(this, message);
744
714
  // If subscription notification wasn't handled (falsy subscriptionId), fall through to channel handling
745
715
  if (handled) {
746
716
  return;
@@ -748,8 +718,7 @@ async function _BackendWebSocketService_establishConnection(bearerToken) {
748
718
  }
749
719
  // Trigger channel callbacks for any message with a channel property
750
720
  if (__classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_isChannelMessage).call(this, message)) {
751
- const channelMsg = message;
752
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_handleChannelMessage).call(this, channelMsg);
721
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_handleChannelMessage).call(this, message);
753
722
  }
754
723
  }, _BackendWebSocketService_isServerResponse = function _BackendWebSocketService_isServerResponse(message) {
755
724
  return ('data' in message &&
@@ -779,104 +748,58 @@ async function _BackendWebSocketService_establishConnection(bearerToken) {
779
748
  if (__classPrivateFieldGet(this, _BackendWebSocketService_channelCallbacks, "f").size === 0) {
780
749
  return;
781
750
  }
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
- });
751
+ // Direct lookup for exact channel match
752
+ __classPrivateFieldGet(this, _BackendWebSocketService_channelCallbacks, "f").get(message.channel)?.callback(message);
801
753
  }, _BackendWebSocketService_handleSubscriptionNotification = function _BackendWebSocketService_handleSubscriptionNotification(message) {
802
- const { subscriptionId, timestamp, channel } = message;
754
+ const { subscriptionId } = message;
803
755
  // Only handle if subscriptionId is defined and not null (allows "0" as valid ID)
804
756
  if (subscriptionId !== null && subscriptionId !== undefined) {
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
- });
757
+ __classPrivateFieldGet(this, _BackendWebSocketService_subscriptions, "f").get(subscriptionId)?.callback?.(message);
828
758
  return true;
829
759
  }
830
760
  return false;
831
761
  }, _BackendWebSocketService_parseMessage = function _BackendWebSocketService_parseMessage(data) {
832
762
  return JSON.parse(data);
833
763
  }, _BackendWebSocketService_handleClose = function _BackendWebSocketService_handleClose(event) {
834
- // Calculate connection duration before we clear state
835
- const connectionDuration = Date.now() - __classPrivateFieldGet(this, _BackendWebSocketService_connectedAt, "f");
836
764
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearTimers).call(this);
837
- __classPrivateFieldSet(this, _BackendWebSocketService_connectedAt, 0, "f");
765
+ __classPrivateFieldSet(this, _BackendWebSocketService_connectedAt, null, "f");
838
766
  // Clear any pending connection promise
839
767
  __classPrivateFieldSet(this, _BackendWebSocketService_connectionPromise, null, "f");
840
768
  // Clear subscriptions and pending requests on any disconnect
841
769
  // This ensures clean state for reconnection
842
770
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearPendingRequests).call(this, new Error('WebSocket connection closed'));
843
771
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearSubscriptions).call(this);
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
772
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") === WebSocketState.DISCONNECTING) {
773
+ // Manual disconnect
774
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.DISCONNECTED);
849
775
  return;
850
776
  }
851
- // Trace unexpected disconnect with details
852
- __classPrivateFieldGet(this, _BackendWebSocketService_trace, "f").call(this, {
853
- name: `${SERVICE_NAME} Disconnect`,
854
- data: {
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', {
855
783
  code: event.code,
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);
784
+ });
785
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_scheduleReconnect).call(this);
786
+ }
869
787
  }, _BackendWebSocketService_handleError = function _BackendWebSocketService_handleError(_error) {
870
788
  // Placeholder for future error handling logic
871
789
  }, _BackendWebSocketService_scheduleReconnect = function _BackendWebSocketService_scheduleReconnect() {
872
790
  __classPrivateFieldSet(this, _BackendWebSocketService_reconnectAttempts, __classPrivateFieldGet(this, _BackendWebSocketService_reconnectAttempts, "f") + 1, "f");
873
791
  const rawDelay = __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").reconnectDelay * Math.pow(1.5, __classPrivateFieldGet(this, _BackendWebSocketService_reconnectAttempts, "f") - 1);
874
792
  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
+ });
875
797
  __classPrivateFieldSet(this, _BackendWebSocketService_reconnectTimer, setTimeout(() => {
876
798
  // Clear timer reference first
877
799
  __classPrivateFieldSet(this, _BackendWebSocketService_reconnectTimer, null, "f");
878
800
  // Check if connection is still enabled before reconnecting
879
801
  if (__classPrivateFieldGet(this, _BackendWebSocketService_isEnabled, "f") && !__classPrivateFieldGet(this, _BackendWebSocketService_isEnabled, "f").call(this)) {
802
+ log('Reconnection disabled by isEnabled - stopping all attempts');
880
803
  __classPrivateFieldSet(this, _BackendWebSocketService_reconnectAttempts, 0, "f");
881
804
  return;
882
805
  }
@@ -906,9 +829,13 @@ async function _BackendWebSocketService_establishConnection(bearerToken) {
906
829
  const oldState = __classPrivateFieldGet(this, _BackendWebSocketService_state, "f");
907
830
  __classPrivateFieldSet(this, _BackendWebSocketService_state, newState, "f");
908
831
  if (oldState !== newState) {
832
+ log('WebSocket state changed', { oldState, newState });
909
833
  // Publish connection state change event
910
834
  // Messenger handles listener errors internally, no need for try-catch
911
835
  __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").publish('BackendWebSocketService:connectionStateChanged', this.getConnectionInfo());
912
836
  }
837
+ }, _BackendWebSocketService_shouldReconnectOnClose = function _BackendWebSocketService_shouldReconnectOnClose(code) {
838
+ // Don't reconnect only on normal closure (manual disconnect)
839
+ return code !== 1000;
913
840
  };
914
841
  //# sourceMappingURL=BackendWebSocketService.cjs.map