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

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,8 +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 ?? ((_request, fn) => fn?.()), "f");
169
158
  __classPrivateFieldSet(this, _BackendWebSocketService_options, {
170
159
  url: options.url,
171
160
  timeout: options.timeout ?? 10000,
@@ -173,8 +162,8 @@ export class BackendWebSocketService {
173
162
  maxReconnectDelay: options.maxReconnectDelay ?? 5000,
174
163
  requestTimeout: options.requestTimeout ?? 30000,
175
164
  }, "f");
176
- // Subscribe to authentication and keyring controller events
177
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_subscribeEvents).call(this);
165
+ // Setup authentication (always enabled)
166
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setupAuthentication).call(this);
178
167
  // Register action handlers using the method actions pattern
179
168
  __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
180
169
  }
@@ -184,23 +173,18 @@ export class BackendWebSocketService {
184
173
  /**
185
174
  * Establishes WebSocket connection with smart reconnection behavior
186
175
  *
187
- * Connection Requirements (all must be true):
188
- * 1. Feature enabled (isEnabled() = true)
189
- * 2. Wallet unlocked (checked by getBearerToken)
190
- * 3. User signed in (checked by getBearerToken)
191
- *
192
- * Platform code should call this when app opens/foregrounds.
193
- * 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
194
180
  *
195
181
  * @returns Promise that resolves when connection is established
196
182
  */
197
183
  async connect() {
198
- // Reset manual disconnect flag when explicitly connecting
199
- __classPrivateFieldSet(this, _BackendWebSocketService_manualDisconnect, false, "f");
200
- // Priority 1: Check if feature is enabled via callback (feature flag check)
201
- // 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
202
186
  if (__classPrivateFieldGet(this, _BackendWebSocketService_isEnabled, "f") && !__classPrivateFieldGet(this, _BackendWebSocketService_isEnabled, "f").call(this)) {
203
- // Clear any pending reconnection attempts since feature is disabled
187
+ // Clear any pending reconnection attempts since app is disabled
204
188
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearTimers).call(this);
205
189
  __classPrivateFieldSet(this, _BackendWebSocketService_reconnectAttempts, 0, "f");
206
190
  return;
@@ -214,9 +198,10 @@ export class BackendWebSocketService {
214
198
  await __classPrivateFieldGet(this, _BackendWebSocketService_connectionPromise, "f");
215
199
  return;
216
200
  }
217
- // Priority 2: Check authentication requirements (signed in)
201
+ // Priority 2: Check authentication requirements (simplified - just check if signed in)
218
202
  let bearerToken;
219
203
  try {
204
+ // AuthenticationController.getBearerToken() handles wallet unlock checks internally
220
205
  const token = await __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").call('AuthenticationController:getBearerToken');
221
206
  if (!token) {
222
207
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_scheduleReconnect).call(this);
@@ -225,10 +210,8 @@ export class BackendWebSocketService {
225
210
  bearerToken = token;
226
211
  }
227
212
  catch (error) {
228
- log('Failed to get bearer token (wallet locked or not signed in)', {
229
- error,
230
- });
231
- // Can't connect - schedule retry
213
+ log('Failed to check authentication requirements', { error });
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
  /**
@@ -371,7 +352,7 @@ export class BackendWebSocketService {
371
352
  state: __classPrivateFieldGet(this, _BackendWebSocketService_state, "f"),
372
353
  url: __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").url,
373
354
  reconnectAttempts: __classPrivateFieldGet(this, _BackendWebSocketService_reconnectAttempts, "f"),
374
- connectedAt: __classPrivateFieldGet(this, _BackendWebSocketService_connectedAt, "f"),
355
+ connectedAt: __classPrivateFieldGet(this, _BackendWebSocketService_connectedAt, "f") ?? undefined,
375
356
  };
376
357
  }
377
358
  /**
@@ -387,7 +368,6 @@ export class BackendWebSocketService {
387
368
  matchingSubscriptions.push({
388
369
  subscriptionId,
389
370
  channels: subscription.channels,
390
- channelType: subscription.channelType,
391
371
  unsubscribe: subscription.unsubscribe,
392
372
  });
393
373
  }
@@ -423,7 +403,6 @@ export class BackendWebSocketService {
423
403
  matchingSubscriptions.push({
424
404
  subscriptionId,
425
405
  channels: subscription.channels,
426
- channelType: subscription.channelType,
427
406
  unsubscribe: subscription.unsubscribe,
428
407
  });
429
408
  }
@@ -517,8 +496,6 @@ export class BackendWebSocketService {
517
496
  if (__classPrivateFieldGet(this, _BackendWebSocketService_ws, "f") && __classPrivateFieldGet(this, _BackendWebSocketService_ws, "f").readyState === WebSocket.OPEN) {
518
497
  __classPrivateFieldGet(this, _BackendWebSocketService_ws, "f").close(1000, 'Service cleanup');
519
498
  }
520
- // Set state to disconnected immediately
521
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.DISCONNECTED);
522
499
  }
523
500
  /**
524
501
  * Create and manage a subscription with server-side registration (recommended for most use cases)
@@ -547,7 +524,6 @@ export class BackendWebSocketService {
547
524
  * @param options.channels - Array of channel names to subscribe to
548
525
  * @param options.callback - Callback function for handling notifications
549
526
  * @param options.requestId - Optional request ID for testing (will generate UUID if not provided)
550
- * @param options.channelType - Channel type identifier
551
527
  * @returns Subscription object with unsubscribe method
552
528
  *
553
529
  * @example
@@ -567,7 +543,7 @@ export class BackendWebSocketService {
567
543
  * @see addChannelCallback for local callbacks without server-side subscription
568
544
  */
569
545
  async subscribe(options) {
570
- const { channels, channelType, callback, requestId } = options;
546
+ const { channels, callback, requestId } = options;
571
547
  if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") !== WebSocketState.CONNECTED) {
572
548
  throw new Error(`Cannot create subscription(s) ${channels.join(', ')}: WebSocket is ${__classPrivateFieldGet(this, _BackendWebSocketService_state, "f")}`);
573
549
  }
@@ -580,6 +556,10 @@ export class BackendWebSocketService {
580
556
  throw new Error('Invalid subscription response: missing subscription ID');
581
557
  }
582
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
+ }
583
563
  // Create unsubscribe function
584
564
  const unsubscribe = async (unsubRequestId) => {
585
565
  // Send unsubscribe request first
@@ -597,42 +577,45 @@ export class BackendWebSocketService {
597
577
  const subscription = {
598
578
  subscriptionId,
599
579
  channels: [...channels],
600
- channelType,
601
580
  unsubscribe,
602
581
  };
603
582
  // Store subscription with subscription ID as key
604
583
  __classPrivateFieldGet(this, _BackendWebSocketService_subscriptions, "f").set(subscriptionId, {
605
584
  subscriptionId,
606
585
  channels: [...channels],
607
- channelType,
608
586
  callback,
609
587
  unsubscribe,
610
588
  });
611
589
  return subscription;
612
590
  }
613
591
  }
614
- _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() {
615
- // Subscribe to authentication state changes (sign in/out)
616
- __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").subscribe('AuthenticationController:stateChange', (state) => {
617
- if (state.isSignedIn) {
618
- // eslint-disable-next-line no-void
619
- void this.connect();
620
- }
621
- else {
622
- // eslint-disable-next-line no-void
623
- void this.disconnect();
624
- }
625
- });
626
- // Subscribe to wallet unlock event
627
- __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").subscribe('KeyringController:unlock', () => {
628
- // eslint-disable-next-line no-void
629
- void this.connect();
630
- });
631
- // Subscribe to wallet lock event
632
- __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").subscribe('KeyringController:lock', () => {
633
- // eslint-disable-next-line no-void
634
- void this.disconnect();
635
- });
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
+ }
636
619
  }, _BackendWebSocketService_buildAuthenticatedUrl = function _BackendWebSocketService_buildAuthenticatedUrl(bearerToken) {
637
620
  const baseUrl = __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").url;
638
621
  // Add token as query parameter to the WebSocket URL
@@ -648,7 +631,6 @@ _BackendWebSocketService_messenger = new WeakMap(), _BackendWebSocketService_opt
648
631
  */
649
632
  async function _BackendWebSocketService_establishConnection(bearerToken) {
650
633
  const wsUrl = __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_buildAuthenticatedUrl).call(this, bearerToken);
651
- const connectionStartTime = Date.now();
652
634
  return new Promise((resolve, reject) => {
653
635
  const ws = new WebSocket(wsUrl);
654
636
  __classPrivateFieldSet(this, _BackendWebSocketService_connectionTimeout, setTimeout(() => {
@@ -656,48 +638,40 @@ async function _BackendWebSocketService_establishConnection(bearerToken) {
656
638
  timeout: __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").timeout,
657
639
  });
658
640
  ws.close();
659
- 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`));
660
642
  }, __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").timeout), "f");
661
643
  ws.onopen = () => {
662
644
  if (__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f")) {
663
645
  clearTimeout(__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f"));
664
646
  __classPrivateFieldSet(this, _BackendWebSocketService_connectionTimeout, null, "f");
665
647
  }
666
- // Calculate connection latency
667
- const connectionLatency = Date.now() - connectionStartTime;
668
- // Trace successful connection with latency
669
- __classPrivateFieldGet(this, _BackendWebSocketService_trace, "f").call(this, {
670
- name: `${SERVICE_NAME} Connection`,
671
- data: {
672
- reconnectAttempt: __classPrivateFieldGet(this, _BackendWebSocketService_reconnectAttempts, "f"),
673
- latency_ms: connectionLatency,
674
- },
675
- tags: {
676
- service: SERVICE_NAME,
677
- },
678
- }, () => {
679
- __classPrivateFieldSet(this, _BackendWebSocketService_ws, ws, "f");
680
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.CONNECTED);
681
- __classPrivateFieldSet(this, _BackendWebSocketService_connectedAt, Date.now(), "f");
682
- // Reset reconnect attempts on successful connection
683
- __classPrivateFieldSet(this, _BackendWebSocketService_reconnectAttempts, 0, "f");
684
- resolve();
685
- });
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();
686
654
  };
687
655
  ws.onerror = (event) => {
688
656
  log('WebSocket onerror event triggered', { event });
689
- // Handle connection-phase errors
690
- if (__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f")) {
691
- clearTimeout(__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f"));
692
- __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}`));
693
669
  }
694
- const error = new Error(`WebSocket connection error to ${wsUrl}`);
695
- reject(error);
696
670
  };
697
671
  ws.onclose = (event) => {
698
672
  log('WebSocket onclose event triggered', {
699
673
  code: event.code,
700
- reason: event.reason,
674
+ reason: event.reason || 'none',
701
675
  wasClean: event.wasClean,
702
676
  });
703
677
  if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") === WebSocketState.CONNECTING) {
@@ -769,98 +743,42 @@ async function _BackendWebSocketService_establishConnection(bearerToken) {
769
743
  if (__classPrivateFieldGet(this, _BackendWebSocketService_channelCallbacks, "f").size === 0) {
770
744
  return;
771
745
  }
772
- // Calculate notification latency: time from server sent to client received
773
- const receivedAt = Date.now();
774
- const latency = receivedAt - message.timestamp;
775
- // Trace channel message processing with latency data
776
- __classPrivateFieldGet(this, _BackendWebSocketService_trace, "f").call(this, {
777
- name: `${SERVICE_NAME} Channel Message`,
778
- data: {
779
- channel: message.channel,
780
- latency_ms: latency,
781
- event: message.event,
782
- },
783
- tags: {
784
- service: SERVICE_NAME,
785
- channel_type: message.channel,
786
- },
787
- }, () => {
788
- // Direct lookup for exact channel match
789
- __classPrivateFieldGet(this, _BackendWebSocketService_channelCallbacks, "f").get(message.channel)?.callback(message);
790
- });
746
+ // Direct lookup for exact channel match
747
+ __classPrivateFieldGet(this, _BackendWebSocketService_channelCallbacks, "f").get(message.channel)?.callback(message);
791
748
  }, _BackendWebSocketService_handleSubscriptionNotification = function _BackendWebSocketService_handleSubscriptionNotification(message) {
792
- const { subscriptionId, timestamp, channel } = message;
749
+ const { subscriptionId } = message;
793
750
  // Only handle if subscriptionId is defined and not null (allows "0" as valid ID)
794
751
  if (subscriptionId !== null && subscriptionId !== undefined) {
795
- const subscription = __classPrivateFieldGet(this, _BackendWebSocketService_subscriptions, "f").get(subscriptionId);
796
- if (!subscription) {
797
- return false;
798
- }
799
- // Calculate notification latency: time from server sent to client received
800
- const receivedAt = Date.now();
801
- const latency = receivedAt - timestamp;
802
- // Trace notification processing with latency data
803
- // Use stored channelType instead of parsing each time
804
- __classPrivateFieldGet(this, _BackendWebSocketService_trace, "f").call(this, {
805
- name: `${SERVICE_NAME} Notification`,
806
- data: {
807
- channel,
808
- latency_ms: latency,
809
- subscriptionId,
810
- },
811
- tags: {
812
- service: SERVICE_NAME,
813
- notification_type: subscription.channelType,
814
- },
815
- }, () => {
816
- subscription.callback?.(message);
817
- });
752
+ __classPrivateFieldGet(this, _BackendWebSocketService_subscriptions, "f").get(subscriptionId)?.callback?.(message);
818
753
  return true;
819
754
  }
820
755
  return false;
821
756
  }, _BackendWebSocketService_parseMessage = function _BackendWebSocketService_parseMessage(data) {
822
757
  return JSON.parse(data);
823
758
  }, _BackendWebSocketService_handleClose = function _BackendWebSocketService_handleClose(event) {
824
- // Calculate connection duration before we clear state
825
- const connectionDuration = Date.now() - __classPrivateFieldGet(this, _BackendWebSocketService_connectedAt, "f");
826
759
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearTimers).call(this);
827
- __classPrivateFieldSet(this, _BackendWebSocketService_connectedAt, 0, "f");
760
+ __classPrivateFieldSet(this, _BackendWebSocketService_connectedAt, null, "f");
828
761
  // Clear any pending connection promise
829
762
  __classPrivateFieldSet(this, _BackendWebSocketService_connectionPromise, null, "f");
830
763
  // Clear subscriptions and pending requests on any disconnect
831
764
  // This ensures clean state for reconnection
832
765
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearPendingRequests).call(this, new Error('WebSocket connection closed'));
833
766
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearSubscriptions).call(this);
834
- // Update state to disconnected
835
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.DISCONNECTED);
836
- // Check if this was a manual disconnect
837
- if (__classPrivateFieldGet(this, _BackendWebSocketService_manualDisconnect, "f")) {
838
- // Manual disconnect - don't reconnect
839
- log('WebSocket closed due to manual disconnect, not reconnecting');
767
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") === WebSocketState.DISCONNECTING) {
768
+ // Manual disconnect
769
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.DISCONNECTED);
840
770
  return;
841
771
  }
842
- // Trace unexpected disconnect with details
843
- __classPrivateFieldGet(this, _BackendWebSocketService_trace, "f").call(this, {
844
- name: `${SERVICE_NAME} Disconnect`,
845
- 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', {
846
778
  code: event.code,
847
- reason: event.reason || getCloseReason(event.code),
848
- connectionDuration_ms: connectionDuration,
849
- },
850
- tags: {
851
- service: SERVICE_NAME,
852
- disconnect_type: 'unexpected',
853
- },
854
- }, () => {
855
- // Empty trace callback - just measuring the event
856
- });
857
- // For any unexpected disconnects, attempt reconnection
858
- // The manualDisconnect flag is the only gate - if it's false, we reconnect
859
- log('Connection lost unexpectedly, will attempt reconnection', {
860
- code: event.code,
861
- reason: event.reason,
862
- });
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() {
@@ -911,5 +829,8 @@ async function _BackendWebSocketService_establishConnection(bearerToken) {
911
829
  // Messenger handles listener errors internally, no need for try-catch
912
830
  __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").publish('BackendWebSocketService:connectionStateChanged', this.getConnectionInfo());
913
831
  }
832
+ }, _BackendWebSocketService_shouldReconnectOnClose = function _BackendWebSocketService_shouldReconnectOnClose(code) {
833
+ // Don't reconnect only on normal closure (manual disconnect)
834
+ return code !== 1000;
914
835
  };
915
836
  //# sourceMappingURL=BackendWebSocketService.mjs.map