@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.
@@ -9,7 +9,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
9
9
  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");
10
10
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
11
  };
12
- 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;
12
+ 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
13
  import { getErrorMessage } from "@metamask/utils";
14
14
  import { v4 as uuidV4 } from "uuid";
15
15
  import { projectLogger, createModuleLogger } from "./logger.mjs";
@@ -105,24 +105,18 @@ export var WebSocketEventType;
105
105
  /**
106
106
  * WebSocket Service with automatic reconnection, session management and direct callback routing
107
107
  *
108
- * Connection Management:
109
- * - Automatically subscribes to AuthenticationController:stateChange (sign in/out)
110
- * - Automatically subscribes to KeyringController:lock/unlock events
111
- * - Idempotent connect() function safe for multiple rapid calls
112
- * - Auto-reconnects on unexpected disconnects (manualDisconnect = false)
113
- *
114
- * Platform Responsibilities:
115
- * - Call connect() when app opens/foregrounds
116
- * - Call disconnect() when app closes/backgrounds
117
- * - Provide isEnabled() callback (feature flag)
118
- * - Call destroy() on app termination
119
- *
120
108
  * Real-Time Performance Optimizations:
121
109
  * - Fast path message routing (zero allocations)
122
110
  * - Production mode removes try-catch overhead
123
111
  * - Optimized JSON parsing with fail-fast
124
112
  * - Direct callback routing bypasses event emitters
125
113
  * - Memory cleanup and resource management
114
+ *
115
+ * Mobile Integration:
116
+ * Mobile apps should handle lifecycle events (background/foreground) by:
117
+ * 1. Calling disconnect() when app goes to background
118
+ * 2. Calling connect() when app returns to foreground
119
+ * 3. Calling destroy() on app termination
126
120
  */
127
121
  export class BackendWebSocketService {
128
122
  // =============================================================================
@@ -142,7 +136,6 @@ export class BackendWebSocketService {
142
136
  _BackendWebSocketService_messenger.set(this, void 0);
143
137
  _BackendWebSocketService_options.set(this, void 0);
144
138
  _BackendWebSocketService_isEnabled.set(this, void 0);
145
- _BackendWebSocketService_trace.set(this, void 0);
146
139
  _BackendWebSocketService_ws.set(this, void 0);
147
140
  _BackendWebSocketService_state.set(this, WebSocketState.DISCONNECTED);
148
141
  _BackendWebSocketService_reconnectAttempts.set(this, 0);
@@ -151,9 +144,7 @@ export class BackendWebSocketService {
151
144
  // Track the current connection promise to handle concurrent connection attempts
152
145
  _BackendWebSocketService_connectionPromise.set(this, null);
153
146
  _BackendWebSocketService_pendingRequests.set(this, new Map());
154
- _BackendWebSocketService_connectedAt.set(this, 0);
155
- // Track manual disconnects to prevent automatic reconnection
156
- _BackendWebSocketService_manualDisconnect.set(this, false);
147
+ _BackendWebSocketService_connectedAt.set(this, null);
157
148
  // Simplified subscription storage (single flat map)
158
149
  // Key: subscription ID string (e.g., 'sub_abc123def456')
159
150
  // Value: WebSocketSubscription object with channels, callback and metadata
@@ -164,10 +155,6 @@ export class BackendWebSocketService {
164
155
  _BackendWebSocketService_channelCallbacks.set(this, new Map());
165
156
  __classPrivateFieldSet(this, _BackendWebSocketService_messenger, options.messenger, "f");
166
157
  __classPrivateFieldSet(this, _BackendWebSocketService_isEnabled, options.isEnabled, "f");
167
- // Default to no-op trace function to keep core platform-agnostic
168
- __classPrivateFieldSet(this, _BackendWebSocketService_trace, options.traceFn ??
169
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
170
- ((_request, fn) => fn?.()), "f");
171
158
  __classPrivateFieldSet(this, _BackendWebSocketService_options, {
172
159
  url: options.url,
173
160
  timeout: options.timeout ?? 10000,
@@ -175,8 +162,8 @@ export class BackendWebSocketService {
175
162
  maxReconnectDelay: options.maxReconnectDelay ?? 5000,
176
163
  requestTimeout: options.requestTimeout ?? 30000,
177
164
  }, "f");
178
- // Subscribe to authentication and keyring controller events
179
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_subscribeEvents).call(this);
165
+ // Setup authentication (always enabled)
166
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setupAuthentication).call(this);
180
167
  // Register action handlers using the method actions pattern
181
168
  __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
182
169
  }
@@ -186,23 +173,18 @@ export class BackendWebSocketService {
186
173
  /**
187
174
  * Establishes WebSocket connection with smart reconnection behavior
188
175
  *
189
- * Connection Requirements (all must be true):
190
- * 1. Feature enabled (isEnabled() = true)
191
- * 2. Wallet unlocked (checked by getBearerToken)
192
- * 3. User signed in (checked by getBearerToken)
193
- *
194
- * Platform code should call this when app opens/foregrounds.
195
- * Automatically called on KeyringController:unlock event.
176
+ * Simplified Priority System (using AuthenticationController):
177
+ * 1. App closed/backgrounded → Stop all attempts (save resources)
178
+ * 2. User not signed in (wallet locked OR not authenticated) → Keep retrying
179
+ * 3. User signed in (wallet unlocked + authenticated) → Connect successfully
196
180
  *
197
181
  * @returns Promise that resolves when connection is established
198
182
  */
199
183
  async connect() {
200
- // Reset manual disconnect flag when explicitly connecting
201
- __classPrivateFieldSet(this, _BackendWebSocketService_manualDisconnect, false, "f");
202
- // Priority 1: Check if feature is enabled via callback (feature flag check)
203
- // If feature is disabled, stop all connection attempts
184
+ // Priority 1: Check if connection is enabled via callback (app lifecycle check)
185
+ // If app is closed/backgrounded, stop all connection attempts to save resources
204
186
  if (__classPrivateFieldGet(this, _BackendWebSocketService_isEnabled, "f") && !__classPrivateFieldGet(this, _BackendWebSocketService_isEnabled, "f").call(this)) {
205
- // Clear any pending reconnection attempts since feature is disabled
187
+ // Clear any pending reconnection attempts since app is disabled
206
188
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearTimers).call(this);
207
189
  __classPrivateFieldSet(this, _BackendWebSocketService_reconnectAttempts, 0, "f");
208
190
  return;
@@ -216,9 +198,10 @@ export class BackendWebSocketService {
216
198
  await __classPrivateFieldGet(this, _BackendWebSocketService_connectionPromise, "f");
217
199
  return;
218
200
  }
219
- // Priority 2: Check authentication requirements (signed in)
201
+ // Priority 2: Check authentication requirements (simplified - just check if signed in)
220
202
  let bearerToken;
221
203
  try {
204
+ // AuthenticationController.getBearerToken() handles wallet unlock checks internally
222
205
  const token = await __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").call('AuthenticationController:getBearerToken');
223
206
  if (!token) {
224
207
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_scheduleReconnect).call(this);
@@ -228,7 +211,7 @@ export class BackendWebSocketService {
228
211
  }
229
212
  catch (error) {
230
213
  log('Failed to check authentication requirements', { error });
231
- // Can't connect - schedule retry
214
+ // If we can't connect for ANY reason, schedule a retry
232
215
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_scheduleReconnect).call(this);
233
216
  return;
234
217
  }
@@ -242,8 +225,7 @@ export class BackendWebSocketService {
242
225
  const errorMessage = getErrorMessage(error);
243
226
  log('Connection attempt failed', { errorMessage, error });
244
227
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.ERROR);
245
- // Rethrow to propagate error to caller
246
- throw error;
228
+ throw new Error(`Failed to connect to WebSocket: ${errorMessage}`);
247
229
  }
248
230
  finally {
249
231
  // Clear the connection promise when done (success or failure)
@@ -260,8 +242,6 @@ export class BackendWebSocketService {
260
242
  __classPrivateFieldGet(this, _BackendWebSocketService_state, "f") === WebSocketState.DISCONNECTING) {
261
243
  return;
262
244
  }
263
- // Mark this as a manual disconnect to prevent automatic reconnection
264
- __classPrivateFieldSet(this, _BackendWebSocketService_manualDisconnect, true, "f");
265
245
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.DISCONNECTING);
266
246
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearTimers).call(this);
267
247
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearPendingRequests).call(this, new Error('WebSocket disconnected'));
@@ -270,6 +250,7 @@ export class BackendWebSocketService {
270
250
  if (__classPrivateFieldGet(this, _BackendWebSocketService_ws, "f")) {
271
251
  __classPrivateFieldGet(this, _BackendWebSocketService_ws, "f").close(1000, 'Normal closure');
272
252
  }
253
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.DISCONNECTED);
273
254
  log('WebSocket manually disconnected');
274
255
  }
275
256
  /**
@@ -370,12 +351,8 @@ export class BackendWebSocketService {
370
351
  return {
371
352
  state: __classPrivateFieldGet(this, _BackendWebSocketService_state, "f"),
372
353
  url: __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").url,
373
- timeout: __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").timeout,
374
- reconnectDelay: __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").reconnectDelay,
375
- maxReconnectDelay: __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").maxReconnectDelay,
376
- requestTimeout: __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").requestTimeout,
377
354
  reconnectAttempts: __classPrivateFieldGet(this, _BackendWebSocketService_reconnectAttempts, "f"),
378
- connectedAt: __classPrivateFieldGet(this, _BackendWebSocketService_connectedAt, "f"),
355
+ connectedAt: __classPrivateFieldGet(this, _BackendWebSocketService_connectedAt, "f") ?? undefined,
379
356
  };
380
357
  }
381
358
  /**
@@ -391,7 +368,6 @@ export class BackendWebSocketService {
391
368
  matchingSubscriptions.push({
392
369
  subscriptionId,
393
370
  channels: subscription.channels,
394
- channelType: subscription.channelType,
395
371
  unsubscribe: subscription.unsubscribe,
396
372
  });
397
373
  }
@@ -427,7 +403,6 @@ export class BackendWebSocketService {
427
403
  matchingSubscriptions.push({
428
404
  subscriptionId,
429
405
  channels: subscription.channels,
430
- channelType: subscription.channelType,
431
406
  unsubscribe: subscription.unsubscribe,
432
407
  });
433
408
  }
@@ -521,8 +496,6 @@ export class BackendWebSocketService {
521
496
  if (__classPrivateFieldGet(this, _BackendWebSocketService_ws, "f") && __classPrivateFieldGet(this, _BackendWebSocketService_ws, "f").readyState === WebSocket.OPEN) {
522
497
  __classPrivateFieldGet(this, _BackendWebSocketService_ws, "f").close(1000, 'Service cleanup');
523
498
  }
524
- // Set state to disconnected immediately
525
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.DISCONNECTED);
526
499
  }
527
500
  /**
528
501
  * Create and manage a subscription with server-side registration (recommended for most use cases)
@@ -551,7 +524,6 @@ export class BackendWebSocketService {
551
524
  * @param options.channels - Array of channel names to subscribe to
552
525
  * @param options.callback - Callback function for handling notifications
553
526
  * @param options.requestId - Optional request ID for testing (will generate UUID if not provided)
554
- * @param options.channelType - Channel type identifier
555
527
  * @returns Subscription object with unsubscribe method
556
528
  *
557
529
  * @example
@@ -571,7 +543,7 @@ export class BackendWebSocketService {
571
543
  * @see addChannelCallback for local callbacks without server-side subscription
572
544
  */
573
545
  async subscribe(options) {
574
- const { channels, channelType, callback, requestId } = options;
546
+ const { channels, callback, requestId } = options;
575
547
  if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") !== WebSocketState.CONNECTED) {
576
548
  throw new Error(`Cannot create subscription(s) ${channels.join(', ')}: WebSocket is ${__classPrivateFieldGet(this, _BackendWebSocketService_state, "f")}`);
577
549
  }
@@ -584,6 +556,10 @@ export class BackendWebSocketService {
584
556
  throw new Error('Invalid subscription response: missing subscription ID');
585
557
  }
586
558
  const { subscriptionId } = subscriptionResponse;
559
+ // Check for failures
560
+ if (subscriptionResponse.failed && subscriptionResponse.failed.length > 0) {
561
+ throw new Error(`Subscription failed for channels: ${subscriptionResponse.failed.join(', ')}`);
562
+ }
587
563
  // Create unsubscribe function
588
564
  const unsubscribe = async (unsubRequestId) => {
589
565
  // Send unsubscribe request first
@@ -601,42 +577,45 @@ export class BackendWebSocketService {
601
577
  const subscription = {
602
578
  subscriptionId,
603
579
  channels: [...channels],
604
- channelType,
605
580
  unsubscribe,
606
581
  };
607
582
  // Store subscription with subscription ID as key
608
583
  __classPrivateFieldGet(this, _BackendWebSocketService_subscriptions, "f").set(subscriptionId, {
609
584
  subscriptionId,
610
585
  channels: [...channels],
611
- channelType,
612
586
  callback,
613
587
  unsubscribe,
614
588
  });
615
589
  return subscription;
616
590
  }
617
591
  }
618
- _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() {
619
- // Subscribe to authentication state changes (sign in/out)
620
- __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").subscribe('AuthenticationController:stateChange', (state) => {
621
- if (state.isSignedIn) {
622
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
623
- this.connect();
624
- }
625
- else {
626
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
627
- this.disconnect();
628
- }
629
- }, (state) => ({ isSignedIn: state.isSignedIn }));
630
- // Subscribe to wallet unlock event
631
- __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").subscribe('KeyringController:unlock', () => {
632
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
633
- this.connect();
634
- });
635
- // Subscribe to wallet lock event
636
- __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").subscribe('KeyringController:lock', () => {
637
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
638
- this.disconnect();
639
- });
592
+ _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() {
593
+ try {
594
+ // Subscribe to authentication state changes - this includes wallet unlock state
595
+ // AuthenticationController can only be signed in if wallet is unlocked
596
+ // Using selector to only listen for isSignedIn property changes for better performance
597
+ __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").subscribe('AuthenticationController:stateChange', (isSignedIn) => {
598
+ if (isSignedIn) {
599
+ // User signed in (wallet unlocked + authenticated) - try to connect
600
+ // Clear any pending reconnection timer since we're attempting connection
601
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearTimers).call(this);
602
+ this.connect().catch((error) => {
603
+ log('Failed to connect after sign-in', { error });
604
+ });
605
+ }
606
+ else {
607
+ // User signed out (wallet locked OR signed out) - disconnect and stop reconnection attempts
608
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearTimers).call(this);
609
+ __classPrivateFieldSet(this, _BackendWebSocketService_reconnectAttempts, 0, "f");
610
+ this.disconnect().catch((error) => {
611
+ log('Failed to disconnect after sign-out', { error });
612
+ });
613
+ }
614
+ }, (state) => state.isSignedIn);
615
+ }
616
+ catch (error) {
617
+ throw new Error(`Authentication setup failed: ${getErrorMessage(error)}`);
618
+ }
640
619
  }, _BackendWebSocketService_buildAuthenticatedUrl = function _BackendWebSocketService_buildAuthenticatedUrl(bearerToken) {
641
620
  const baseUrl = __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").url;
642
621
  // Add token as query parameter to the WebSocket URL
@@ -652,7 +631,6 @@ _BackendWebSocketService_messenger = new WeakMap(), _BackendWebSocketService_opt
652
631
  */
653
632
  async function _BackendWebSocketService_establishConnection(bearerToken) {
654
633
  const wsUrl = __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_buildAuthenticatedUrl).call(this, bearerToken);
655
- const connectionStartTime = Date.now();
656
634
  return new Promise((resolve, reject) => {
657
635
  const ws = new WebSocket(wsUrl);
658
636
  __classPrivateFieldSet(this, _BackendWebSocketService_connectionTimeout, setTimeout(() => {
@@ -660,47 +638,40 @@ async function _BackendWebSocketService_establishConnection(bearerToken) {
660
638
  timeout: __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").timeout,
661
639
  });
662
640
  ws.close();
663
- reject(new Error(`Failed to connect to WebSocket: Connection timeout after ${__classPrivateFieldGet(this, _BackendWebSocketService_options, "f").timeout}ms`));
641
+ reject(new Error(`Connection timeout after ${__classPrivateFieldGet(this, _BackendWebSocketService_options, "f").timeout}ms`));
664
642
  }, __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").timeout), "f");
665
643
  ws.onopen = () => {
666
644
  if (__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f")) {
667
645
  clearTimeout(__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f"));
668
646
  __classPrivateFieldSet(this, _BackendWebSocketService_connectionTimeout, null, "f");
669
647
  }
670
- // Calculate connection latency
671
- const connectionLatency = Date.now() - connectionStartTime;
672
- // Trace successful connection with latency
673
- __classPrivateFieldGet(this, _BackendWebSocketService_trace, "f").call(this, {
674
- name: `${SERVICE_NAME} Connection`,
675
- data: {
676
- reconnectAttempt: __classPrivateFieldGet(this, _BackendWebSocketService_reconnectAttempts, "f"),
677
- latency_ms: connectionLatency,
678
- },
679
- tags: {
680
- service: SERVICE_NAME,
681
- },
682
- }, () => {
683
- __classPrivateFieldSet(this, _BackendWebSocketService_ws, ws, "f");
684
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.CONNECTED);
685
- __classPrivateFieldSet(this, _BackendWebSocketService_connectedAt, Date.now(), "f");
686
- // Reset reconnect attempts on successful connection
687
- __classPrivateFieldSet(this, _BackendWebSocketService_reconnectAttempts, 0, "f");
688
- resolve();
689
- });
648
+ __classPrivateFieldSet(this, _BackendWebSocketService_ws, ws, "f");
649
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.CONNECTED);
650
+ __classPrivateFieldSet(this, _BackendWebSocketService_connectedAt, Date.now(), "f");
651
+ // Reset reconnect attempts on successful connection
652
+ __classPrivateFieldSet(this, _BackendWebSocketService_reconnectAttempts, 0, "f");
653
+ resolve();
690
654
  };
691
655
  ws.onerror = (event) => {
692
656
  log('WebSocket onerror event triggered', { event });
693
- if (__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f")) {
694
- clearTimeout(__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f"));
695
- __classPrivateFieldSet(this, _BackendWebSocketService_connectionTimeout, null, "f");
657
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") === WebSocketState.CONNECTING) {
658
+ // Handle connection-phase errors
659
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f")) {
660
+ clearTimeout(__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f"));
661
+ __classPrivateFieldSet(this, _BackendWebSocketService_connectionTimeout, null, "f");
662
+ }
663
+ const error = new Error(`WebSocket connection error to ${wsUrl}`);
664
+ reject(error);
665
+ }
666
+ else {
667
+ // Handle runtime errors
668
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_handleError).call(this, new Error(`WebSocket error: ${event.type}`));
696
669
  }
697
- const error = new Error(`WebSocket connection error to ${wsUrl}`);
698
- reject(error);
699
670
  };
700
671
  ws.onclose = (event) => {
701
672
  log('WebSocket onclose event triggered', {
702
673
  code: event.code,
703
- reason: event.reason,
674
+ reason: event.reason || 'none',
704
675
  wasClean: event.wasClean,
705
676
  });
706
677
  if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") === WebSocketState.CONNECTING) {
@@ -734,8 +705,7 @@ async function _BackendWebSocketService_establishConnection(bearerToken) {
734
705
  }
735
706
  // Handle subscription notifications with valid subscriptionId
736
707
  if (__classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_isSubscriptionNotification).call(this, message)) {
737
- const notificationMsg = message;
738
- const handled = __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_handleSubscriptionNotification).call(this, notificationMsg);
708
+ const handled = __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_handleSubscriptionNotification).call(this, message);
739
709
  // If subscription notification wasn't handled (falsy subscriptionId), fall through to channel handling
740
710
  if (handled) {
741
711
  return;
@@ -743,8 +713,7 @@ async function _BackendWebSocketService_establishConnection(bearerToken) {
743
713
  }
744
714
  // Trigger channel callbacks for any message with a channel property
745
715
  if (__classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_isChannelMessage).call(this, message)) {
746
- const channelMsg = message;
747
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_handleChannelMessage).call(this, channelMsg);
716
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_handleChannelMessage).call(this, message);
748
717
  }
749
718
  }, _BackendWebSocketService_isServerResponse = function _BackendWebSocketService_isServerResponse(message) {
750
719
  return ('data' in message &&
@@ -774,104 +743,58 @@ async function _BackendWebSocketService_establishConnection(bearerToken) {
774
743
  if (__classPrivateFieldGet(this, _BackendWebSocketService_channelCallbacks, "f").size === 0) {
775
744
  return;
776
745
  }
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
- });
746
+ // Direct lookup for exact channel match
747
+ __classPrivateFieldGet(this, _BackendWebSocketService_channelCallbacks, "f").get(message.channel)?.callback(message);
796
748
  }, _BackendWebSocketService_handleSubscriptionNotification = function _BackendWebSocketService_handleSubscriptionNotification(message) {
797
- const { subscriptionId, timestamp, channel } = message;
749
+ const { subscriptionId } = message;
798
750
  // Only handle if subscriptionId is defined and not null (allows "0" as valid ID)
799
751
  if (subscriptionId !== null && subscriptionId !== undefined) {
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
- });
752
+ __classPrivateFieldGet(this, _BackendWebSocketService_subscriptions, "f").get(subscriptionId)?.callback?.(message);
823
753
  return true;
824
754
  }
825
755
  return false;
826
756
  }, _BackendWebSocketService_parseMessage = function _BackendWebSocketService_parseMessage(data) {
827
757
  return JSON.parse(data);
828
758
  }, _BackendWebSocketService_handleClose = function _BackendWebSocketService_handleClose(event) {
829
- // Calculate connection duration before we clear state
830
- const connectionDuration = Date.now() - __classPrivateFieldGet(this, _BackendWebSocketService_connectedAt, "f");
831
759
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearTimers).call(this);
832
- __classPrivateFieldSet(this, _BackendWebSocketService_connectedAt, 0, "f");
760
+ __classPrivateFieldSet(this, _BackendWebSocketService_connectedAt, null, "f");
833
761
  // Clear any pending connection promise
834
762
  __classPrivateFieldSet(this, _BackendWebSocketService_connectionPromise, null, "f");
835
763
  // Clear subscriptions and pending requests on any disconnect
836
764
  // This ensures clean state for reconnection
837
765
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearPendingRequests).call(this, new Error('WebSocket connection closed'));
838
766
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearSubscriptions).call(this);
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
767
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") === WebSocketState.DISCONNECTING) {
768
+ // Manual disconnect
769
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.DISCONNECTED);
844
770
  return;
845
771
  }
846
- // Trace unexpected disconnect with details
847
- __classPrivateFieldGet(this, _BackendWebSocketService_trace, "f").call(this, {
848
- name: `${SERVICE_NAME} Disconnect`,
849
- data: {
772
+ // For unexpected disconnects, update the state to reflect that we're disconnected
773
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.DISCONNECTED);
774
+ // Check if we should attempt reconnection based on close code
775
+ const shouldReconnect = __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_shouldReconnectOnClose).call(this, event.code);
776
+ if (shouldReconnect) {
777
+ log('Connection lost unexpectedly, will attempt reconnection', {
850
778
  code: event.code,
851
- reason: event.reason || getCloseReason(event.code),
852
- connectionDuration_ms: connectionDuration,
853
- },
854
- tags: {
855
- service: SERVICE_NAME,
856
- disconnect_type: 'unexpected',
857
- },
858
- }, () => {
859
- // Empty trace callback - just measuring the event
860
- });
861
- // For any unexpected disconnects, attempt reconnection
862
- // The manualDisconnect flag is the only gate - if it's false, we reconnect
863
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_scheduleReconnect).call(this);
779
+ });
780
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_scheduleReconnect).call(this);
781
+ }
864
782
  }, _BackendWebSocketService_handleError = function _BackendWebSocketService_handleError(_error) {
865
783
  // Placeholder for future error handling logic
866
784
  }, _BackendWebSocketService_scheduleReconnect = function _BackendWebSocketService_scheduleReconnect() {
867
785
  __classPrivateFieldSet(this, _BackendWebSocketService_reconnectAttempts, __classPrivateFieldGet(this, _BackendWebSocketService_reconnectAttempts, "f") + 1, "f");
868
786
  const rawDelay = __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").reconnectDelay * Math.pow(1.5, __classPrivateFieldGet(this, _BackendWebSocketService_reconnectAttempts, "f") - 1);
869
787
  const delay = Math.min(rawDelay, __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").maxReconnectDelay);
788
+ log('Scheduling reconnection attempt', {
789
+ attempt: __classPrivateFieldGet(this, _BackendWebSocketService_reconnectAttempts, "f"),
790
+ delayMs: delay,
791
+ });
870
792
  __classPrivateFieldSet(this, _BackendWebSocketService_reconnectTimer, setTimeout(() => {
871
793
  // Clear timer reference first
872
794
  __classPrivateFieldSet(this, _BackendWebSocketService_reconnectTimer, null, "f");
873
795
  // Check if connection is still enabled before reconnecting
874
796
  if (__classPrivateFieldGet(this, _BackendWebSocketService_isEnabled, "f") && !__classPrivateFieldGet(this, _BackendWebSocketService_isEnabled, "f").call(this)) {
797
+ log('Reconnection disabled by isEnabled - stopping all attempts');
875
798
  __classPrivateFieldSet(this, _BackendWebSocketService_reconnectAttempts, 0, "f");
876
799
  return;
877
800
  }
@@ -901,9 +824,13 @@ async function _BackendWebSocketService_establishConnection(bearerToken) {
901
824
  const oldState = __classPrivateFieldGet(this, _BackendWebSocketService_state, "f");
902
825
  __classPrivateFieldSet(this, _BackendWebSocketService_state, newState, "f");
903
826
  if (oldState !== newState) {
827
+ log('WebSocket state changed', { oldState, newState });
904
828
  // Publish connection state change event
905
829
  // Messenger handles listener errors internally, no need for try-catch
906
830
  __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").publish('BackendWebSocketService:connectionStateChanged', this.getConnectionInfo());
907
831
  }
832
+ }, _BackendWebSocketService_shouldReconnectOnClose = function _BackendWebSocketService_shouldReconnectOnClose(code) {
833
+ // Don't reconnect only on normal closure (manual disconnect)
834
+ return code !== 1000;
908
835
  };
909
836
  //# sourceMappingURL=BackendWebSocketService.mjs.map