@metamask/core-backend 4.0.0 → 4.1.0

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,13 +9,18 @@ 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_stableConnectionTimer, _BackendWebSocketService_connectionPromise, _BackendWebSocketService_pendingRequests, _BackendWebSocketService_connectedAt, _BackendWebSocketService_manualDisconnect, _BackendWebSocketService_subscriptions, _BackendWebSocketService_channelCallbacks, _BackendWebSocketService_backoff, _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_newBackoff, _BackendWebSocketService_clearTimers, _BackendWebSocketService_clearPendingRequests, _BackendWebSocketService_clearSubscriptions, _BackendWebSocketService_setState;
12
+ var _BackendWebSocketService_instances, _BackendWebSocketService_messenger, _BackendWebSocketService_options, _BackendWebSocketService_isEnabled, _BackendWebSocketService_trace, _BackendWebSocketService_ws, _BackendWebSocketService_state, _BackendWebSocketService_reconnectAttempts, _BackendWebSocketService_reconnectTimer, _BackendWebSocketService_connectionTimeout, _BackendWebSocketService_stableConnectionTimer, _BackendWebSocketService_connectionPromise, _BackendWebSocketService_pendingRequests, _BackendWebSocketService_connectedAt, _BackendWebSocketService_subscriptions, _BackendWebSocketService_channelCallbacks, _BackendWebSocketService_backoff, _BackendWebSocketService_subscribeEvents, _BackendWebSocketService_buildAuthenticatedUrl, _BackendWebSocketService_establishConnection, _BackendWebSocketService_handleMessage, _BackendWebSocketService_isServerResponse, _BackendWebSocketService_isSubscriptionNotification, _BackendWebSocketService_isChannelMessage, _BackendWebSocketService_handleServerResponse, _BackendWebSocketService_handleChannelMessage, _BackendWebSocketService_handleSubscriptionNotification, _BackendWebSocketService_parseMessage, _BackendWebSocketService_handleError, _BackendWebSocketService_scheduleReconnect, _BackendWebSocketService_newBackoff, _BackendWebSocketService_clearTimers, _BackendWebSocketService_clearPendingRequests, _BackendWebSocketService_clearSubscriptions, _BackendWebSocketService_setState;
13
13
  import { ExponentialBackoff } from "@metamask/controller-utils";
14
14
  import { getErrorMessage } from "@metamask/utils";
15
15
  import { v4 as uuidV4 } from "uuid";
16
16
  import { projectLogger, createModuleLogger } from "./logger.mjs";
17
17
  const SERVICE_NAME = 'BackendWebSocketService';
18
18
  const log = createModuleLogger(projectLogger, SERVICE_NAME);
19
+ // WebSocket close codes and reasons for internal operations
20
+ const MANUAL_DISCONNECT_CODE = 4999;
21
+ const MANUAL_DISCONNECT_REASON = 'Internal: Manual disconnect';
22
+ const FORCE_RECONNECT_CODE = 4998;
23
+ const FORCE_RECONNECT_REASON = 'Internal: Force reconnect';
19
24
  const MESSENGER_EXPOSED_METHODS = [
20
25
  'connect',
21
26
  'disconnect',
@@ -88,8 +93,10 @@ export var WebSocketState;
88
93
  (function (WebSocketState) {
89
94
  WebSocketState["CONNECTING"] = "connecting";
90
95
  WebSocketState["CONNECTED"] = "connected";
96
+ /** @deprecated This value is no longer used internally and will be removed in a future major release */
91
97
  WebSocketState["DISCONNECTING"] = "disconnecting";
92
98
  WebSocketState["DISCONNECTED"] = "disconnected";
99
+ /** @deprecated TThis value is no longer used internally and will be removed in a future major release */
93
100
  WebSocketState["ERROR"] = "error";
94
101
  })(WebSocketState || (WebSocketState = {}));
95
102
  /**
@@ -155,8 +162,6 @@ export class BackendWebSocketService {
155
162
  _BackendWebSocketService_connectionPromise.set(this, null);
156
163
  _BackendWebSocketService_pendingRequests.set(this, new Map());
157
164
  _BackendWebSocketService_connectedAt.set(this, 0);
158
- // Track manual disconnects to prevent automatic reconnection
159
- _BackendWebSocketService_manualDisconnect.set(this, false);
160
165
  // Simplified subscription storage (single flat map)
161
166
  // Key: subscription ID string (e.g., 'sub_abc123def456')
162
167
  // Value: WebSocketSubscription object with channels, callback and metadata
@@ -176,8 +181,8 @@ export class BackendWebSocketService {
176
181
  __classPrivateFieldSet(this, _BackendWebSocketService_options, {
177
182
  url: options.url,
178
183
  timeout: options.timeout ?? 10000,
179
- reconnectDelay: options.reconnectDelay ?? 500,
180
- maxReconnectDelay: options.maxReconnectDelay ?? 5000,
184
+ reconnectDelay: options.reconnectDelay ?? 10000,
185
+ maxReconnectDelay: options.maxReconnectDelay ?? 60000,
181
186
  requestTimeout: options.requestTimeout ?? 30000,
182
187
  }, "f");
183
188
  // Initialize backoff for reconnection delays
@@ -204,8 +209,6 @@ export class BackendWebSocketService {
204
209
  * @returns Promise that resolves when connection is established
205
210
  */
206
211
  async connect() {
207
- // Reset manual disconnect flag when explicitly connecting
208
- __classPrivateFieldSet(this, _BackendWebSocketService_manualDisconnect, false, "f");
209
212
  // Priority 1: Check if feature is enabled via callback (feature flag check)
210
213
  // If feature is disabled, stop all connection attempts
211
214
  if (__classPrivateFieldGet(this, _BackendWebSocketService_isEnabled, "f") && !__classPrivateFieldGet(this, _BackendWebSocketService_isEnabled, "f").call(this)) {
@@ -244,17 +247,8 @@ export class BackendWebSocketService {
244
247
  log('Failed to check authentication requirements', { error });
245
248
  throw error;
246
249
  }
247
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.CONNECTING);
248
250
  // Establish the actual WebSocket connection
249
- try {
250
- await __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_establishConnection).call(this, bearerToken);
251
- }
252
- catch (error) {
253
- const errorMessage = getErrorMessage(error);
254
- log('Connection attempt failed', { errorMessage, error });
255
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.ERROR);
256
- throw error;
257
- }
251
+ await __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_establishConnection).call(this, bearerToken);
258
252
  })(), "f");
259
253
  try {
260
254
  await __classPrivateFieldGet(this, _BackendWebSocketService_connectionPromise, "f");
@@ -273,22 +267,11 @@ export class BackendWebSocketService {
273
267
  * Closes WebSocket connection
274
268
  */
275
269
  disconnect() {
276
- if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") === WebSocketState.DISCONNECTED ||
277
- __classPrivateFieldGet(this, _BackendWebSocketService_state, "f") === WebSocketState.DISCONNECTING) {
270
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") === WebSocketState.DISCONNECTED || !__classPrivateFieldGet(this, _BackendWebSocketService_ws, "f")) {
278
271
  return;
279
272
  }
280
- // Mark this as a manual disconnect to prevent automatic reconnection
281
- __classPrivateFieldSet(this, _BackendWebSocketService_manualDisconnect, true, "f");
282
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.DISCONNECTING);
283
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearTimers).call(this);
284
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearPendingRequests).call(this, new Error('WebSocket disconnected'));
285
- // Clear any pending connection promise
286
- __classPrivateFieldSet(this, _BackendWebSocketService_connectionPromise, null, "f");
287
- // Reset reconnect attempts on manual disconnect
288
- __classPrivateFieldSet(this, _BackendWebSocketService_reconnectAttempts, 0, "f");
289
- if (__classPrivateFieldGet(this, _BackendWebSocketService_ws, "f")) {
290
- __classPrivateFieldGet(this, _BackendWebSocketService_ws, "f").close(1000, 'Normal closure');
291
- }
273
+ // Close WebSocket with manual disconnect code and reason
274
+ __classPrivateFieldGet(this, _BackendWebSocketService_ws, "f").close(MANUAL_DISCONNECT_CODE, MANUAL_DISCONNECT_REASON);
292
275
  log('WebSocket manually disconnected');
293
276
  }
294
277
  /**
@@ -308,16 +291,15 @@ export class BackendWebSocketService {
308
291
  * @returns Promise that resolves when disconnection is complete (reconnection is scheduled)
309
292
  */
310
293
  async forceReconnection() {
311
- // If a reconnect is already scheduled, don't force another one
312
- if (__classPrivateFieldGet(this, _BackendWebSocketService_reconnectTimer, "f")) {
313
- log('Reconnect already scheduled, skipping force reconnection');
294
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") === WebSocketState.DISCONNECTED || !__classPrivateFieldGet(this, _BackendWebSocketService_ws, "f")) {
295
+ log('WebSocket already disconnected, scheduling reconnect');
296
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_scheduleReconnect).call(this);
314
297
  return;
315
298
  }
316
299
  log('Forcing WebSocket reconnection to clean up subscription state');
317
- // Perform controlled disconnect
318
- this.disconnect();
319
- // Schedule reconnection with exponential backoff
320
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_scheduleReconnect).call(this);
300
+ // This ensures ws.onclose will schedule a reconnect (not treat it as manual disconnect)
301
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
302
+ __classPrivateFieldGet(this, _BackendWebSocketService_ws, "f").close(FORCE_RECONNECT_CODE, FORCE_RECONNECT_REASON);
321
303
  }
322
304
  /**
323
305
  * Sends a message through the WebSocket (fire-and-forget, no response expected)
@@ -559,17 +541,13 @@ export class BackendWebSocketService {
559
541
  * Called when service is being destroyed or app is terminating
560
542
  */
561
543
  destroy() {
544
+ // Always clear timers first to prevent reconnection attempts after destruction
545
+ // This handles the case where destroy() is called while DISCONNECTED with a pending reconnect timer
562
546
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearTimers).call(this);
563
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearSubscriptions).call(this);
564
- // Clear any pending connection promise
565
- __classPrivateFieldSet(this, _BackendWebSocketService_connectionPromise, null, "f");
566
- // Clear all pending requests
567
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearPendingRequests).call(this, new Error('Service cleanup'));
568
- if (__classPrivateFieldGet(this, _BackendWebSocketService_ws, "f") && __classPrivateFieldGet(this, _BackendWebSocketService_ws, "f").readyState === WebSocket.OPEN) {
569
- __classPrivateFieldGet(this, _BackendWebSocketService_ws, "f").close(1000, 'Service cleanup');
570
- }
571
- // Set state to disconnected immediately
572
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.DISCONNECTED);
547
+ // Reset reconnect attempts to prevent any future reconnection logic
548
+ __classPrivateFieldSet(this, _BackendWebSocketService_reconnectAttempts, 0, "f");
549
+ // Disconnect the WebSocket if connected (will be no-op if already disconnected)
550
+ this.disconnect();
573
551
  }
574
552
  /**
575
553
  * Create and manage a subscription with server-side registration (recommended for most use cases)
@@ -654,7 +632,7 @@ export class BackendWebSocketService {
654
632
  // Store subscription with subscription ID as key
655
633
  __classPrivateFieldGet(this, _BackendWebSocketService_subscriptions, "f").set(subscriptionId, {
656
634
  subscriptionId,
657
- channels: [...channels],
635
+ channels: [...channels], // Store copy of channels
658
636
  channelType,
659
637
  callback,
660
638
  unsubscribe,
@@ -662,7 +640,7 @@ export class BackendWebSocketService {
662
640
  return subscription;
663
641
  }
664
642
  }
665
- _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_stableConnectionTimer = 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_backoff = new WeakMap(), _BackendWebSocketService_instances = new WeakSet(), _BackendWebSocketService_subscribeEvents = function _BackendWebSocketService_subscribeEvents() {
643
+ _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_stableConnectionTimer = new WeakMap(), _BackendWebSocketService_connectionPromise = new WeakMap(), _BackendWebSocketService_pendingRequests = new WeakMap(), _BackendWebSocketService_connectedAt = new WeakMap(), _BackendWebSocketService_subscriptions = new WeakMap(), _BackendWebSocketService_channelCallbacks = new WeakMap(), _BackendWebSocketService_backoff = new WeakMap(), _BackendWebSocketService_instances = new WeakSet(), _BackendWebSocketService_subscribeEvents = function _BackendWebSocketService_subscribeEvents() {
666
644
  // Subscribe to authentication state changes (sign in/out)
667
645
  __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").subscribe('AuthenticationController:stateChange', (state) => {
668
646
  if (state.isSignedIn) {
@@ -697,34 +675,31 @@ _BackendWebSocketService_messenger = new WeakMap(), _BackendWebSocketService_opt
697
675
  */
698
676
  async function _BackendWebSocketService_establishConnection(bearerToken) {
699
677
  const wsUrl = __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_buildAuthenticatedUrl).call(this, bearerToken);
700
- const connectionStartTime = Date.now();
701
- return new Promise((resolve, reject) => {
702
- const ws = new WebSocket(wsUrl);
703
- __classPrivateFieldSet(this, _BackendWebSocketService_connectionTimeout, setTimeout(() => {
704
- log('WebSocket connection timeout - forcing close', {
705
- timeout: __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").timeout,
706
- });
707
- ws.close();
708
- reject(new Error(`Failed to connect to WebSocket: Connection timeout after ${__classPrivateFieldGet(this, _BackendWebSocketService_options, "f").timeout}ms`));
709
- }, __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").timeout), "f");
710
- ws.onopen = () => {
711
- if (__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f")) {
712
- clearTimeout(__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f"));
713
- __classPrivateFieldSet(this, _BackendWebSocketService_connectionTimeout, null, "f");
714
- }
715
- // Calculate connection latency
716
- const connectionLatency = Date.now() - connectionStartTime;
717
- // Trace successful connection with latency
718
- __classPrivateFieldGet(this, _BackendWebSocketService_trace, "f").call(this, {
719
- name: `${SERVICE_NAME} Connection`,
720
- data: {
721
- reconnectAttempt: __classPrivateFieldGet(this, _BackendWebSocketService_reconnectAttempts, "f"),
722
- latency_ms: connectionLatency,
723
- },
724
- tags: {
725
- service: SERVICE_NAME,
726
- },
727
- }, () => {
678
+ // Transition to CONNECTING state before creating WebSocket
679
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.CONNECTING);
680
+ return __classPrivateFieldGet(this, _BackendWebSocketService_trace, "f").call(this, {
681
+ name: `${SERVICE_NAME} Connection`,
682
+ data: {
683
+ reconnectAttempt: __classPrivateFieldGet(this, _BackendWebSocketService_reconnectAttempts, "f"),
684
+ },
685
+ tags: {
686
+ service: SERVICE_NAME,
687
+ },
688
+ }, () => {
689
+ return new Promise((resolve, reject) => {
690
+ const ws = new WebSocket(wsUrl);
691
+ __classPrivateFieldSet(this, _BackendWebSocketService_connectionTimeout, setTimeout(() => {
692
+ log('WebSocket connection timeout - forcing close', {
693
+ timeout: __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").timeout,
694
+ });
695
+ // Close the WebSocket - onclose will handle rejection and state change
696
+ ws.close();
697
+ }, __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").timeout), "f");
698
+ ws.onopen = () => {
699
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f")) {
700
+ clearTimeout(__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f"));
701
+ __classPrivateFieldSet(this, _BackendWebSocketService_connectionTimeout, null, "f");
702
+ }
728
703
  __classPrivateFieldSet(this, _BackendWebSocketService_ws, ws, "f");
729
704
  __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.CONNECTED);
730
705
  __classPrivateFieldSet(this, _BackendWebSocketService_connectedAt, Date.now(), "f");
@@ -738,45 +713,79 @@ async function _BackendWebSocketService_establishConnection(bearerToken) {
738
713
  log('Connection stable - reset reconnect attempts and backoff');
739
714
  }, 10000), "f");
740
715
  resolve();
741
- });
742
- };
743
- ws.onerror = (event) => {
744
- log('WebSocket onerror event triggered', { event });
745
- if (__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f")) {
746
- clearTimeout(__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f"));
747
- __classPrivateFieldSet(this, _BackendWebSocketService_connectionTimeout, null, "f");
748
- }
749
- const error = new Error(`WebSocket connection error to ${wsUrl}`);
750
- reject(error);
751
- };
752
- ws.onclose = (event) => {
753
- log('WebSocket onclose event triggered', {
754
- code: event.code,
755
- reason: event.reason,
756
- wasClean: event.wasClean,
757
- });
758
- if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") === WebSocketState.CONNECTING) {
759
- // Handle connection-phase close events
760
- if (__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f")) {
761
- clearTimeout(__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f"));
762
- __classPrivateFieldSet(this, _BackendWebSocketService_connectionTimeout, null, "f");
716
+ };
717
+ ws.onclose = (event) => {
718
+ log('WebSocket onclose event triggered', {
719
+ code: event.code,
720
+ reason: event.reason || getCloseReason(event.code),
721
+ wasClean: event.wasClean,
722
+ });
723
+ // Guard against duplicate close events
724
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") === WebSocketState.DISCONNECTED) {
725
+ return;
763
726
  }
764
- reject(new Error(`WebSocket connection closed during connection: ${event.code} ${event.reason}`));
765
- }
766
- else {
767
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_handleClose).call(this, event);
768
- }
769
- };
770
- // Set up message handler immediately - no need to wait for connection
771
- ws.onmessage = (event) => {
772
- try {
773
- const message = __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_parseMessage).call(this, event.data);
774
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_handleMessage).call(this, message);
775
- }
776
- catch {
777
- // Silently ignore invalid JSON messages
778
- }
779
- };
727
+ // Detect if this is a manual disconnect or service cleanup based on close code
728
+ const isManualDisconnect = event.code === MANUAL_DISCONNECT_CODE &&
729
+ event.reason === MANUAL_DISCONNECT_REASON;
730
+ // If connection hasn't been established yet, handle the connection promise
731
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") === WebSocketState.CONNECTING) {
732
+ if (isManualDisconnect) {
733
+ // Manual disconnect during connection - resolve to prevent reconnection
734
+ resolve();
735
+ }
736
+ else {
737
+ // Failed connection attempt - reject to trigger reconnection
738
+ reject(new Error(`WebSocket connection closed during connection: ${event.code} ${event.reason}`));
739
+ }
740
+ }
741
+ // Calculate connection duration before we clear state (only if we were connected)
742
+ const connectionDuration_ms = __classPrivateFieldGet(this, _BackendWebSocketService_connectedAt, "f") > 0 ? Date.now() - __classPrivateFieldGet(this, _BackendWebSocketService_connectedAt, "f") : 0;
743
+ // Clear all timers
744
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearTimers).call(this);
745
+ // Clear WebSocket reference to allow garbage collection
746
+ __classPrivateFieldSet(this, _BackendWebSocketService_ws, undefined, "f");
747
+ // Clear connection tracking
748
+ __classPrivateFieldSet(this, _BackendWebSocketService_connectionPromise, null, "f");
749
+ __classPrivateFieldSet(this, _BackendWebSocketService_connectedAt, 0, "f");
750
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearPendingRequests).call(this, new Error(`WebSocket connection closed: ${event.code} ${event.reason || getCloseReason(event.code)}`));
751
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearSubscriptions).call(this);
752
+ // Update state to disconnected
753
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.DISCONNECTED);
754
+ // Check if this was a manual disconnect
755
+ if (isManualDisconnect) {
756
+ // Manual disconnect - reset attempts and don't reconnect
757
+ __classPrivateFieldSet(this, _BackendWebSocketService_reconnectAttempts, 0, "f");
758
+ }
759
+ else {
760
+ // Unexpected disconnect - schedule reconnection
761
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_scheduleReconnect).call(this);
762
+ }
763
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
764
+ __classPrivateFieldGet(this, _BackendWebSocketService_trace, "f").call(this, {
765
+ name: `${SERVICE_NAME} Disconnection`,
766
+ data: {
767
+ code: event.code,
768
+ reason: event.reason || getCloseReason(event.code),
769
+ wasClean: event.wasClean,
770
+ reconnectAttempts: __classPrivateFieldGet(this, _BackendWebSocketService_reconnectAttempts, "f"),
771
+ ...(connectionDuration_ms > 0 && { connectionDuration_ms }),
772
+ },
773
+ tags: {
774
+ service: SERVICE_NAME,
775
+ },
776
+ });
777
+ };
778
+ // Set up message handler immediately - no need to wait for connection
779
+ ws.onmessage = (event) => {
780
+ try {
781
+ const message = __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_parseMessage).call(this, event.data);
782
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_handleMessage).call(this, message);
783
+ }
784
+ catch {
785
+ // Silently ignore invalid JSON messages
786
+ }
787
+ };
788
+ });
780
789
  });
781
790
  }, _BackendWebSocketService_handleMessage = function _BackendWebSocketService_handleMessage(message) {
782
791
  // Handle server responses (correlated with requests) first
@@ -826,23 +835,7 @@ async function _BackendWebSocketService_establishConnection(bearerToken) {
826
835
  if (__classPrivateFieldGet(this, _BackendWebSocketService_channelCallbacks, "f").size === 0) {
827
836
  return;
828
837
  }
829
- // Calculate notification latency: time from server sent to client received
830
- const receivedAt = Date.now();
831
- const latency = receivedAt - message.timestamp;
832
- // Trace channel message processing with latency data
833
- __classPrivateFieldGet(this, _BackendWebSocketService_trace, "f").call(this, {
834
- name: `${SERVICE_NAME} Channel Message`,
835
- data: {
836
- latency_ms: latency,
837
- event: message.event,
838
- },
839
- tags: {
840
- service: SERVICE_NAME,
841
- },
842
- }, () => {
843
- // Direct lookup for exact channel match
844
- __classPrivateFieldGet(this, _BackendWebSocketService_channelCallbacks, "f").get(message.channel)?.callback(message);
845
- });
838
+ __classPrivateFieldGet(this, _BackendWebSocketService_channelCallbacks, "f").get(message.channel)?.callback(message);
846
839
  }, _BackendWebSocketService_handleSubscriptionNotification = function _BackendWebSocketService_handleSubscriptionNotification(message) {
847
840
  const { subscriptionId, timestamp, channel } = message;
848
841
  // Only handle if subscriptionId is defined and not null (allows "0" as valid ID)
@@ -856,6 +849,8 @@ async function _BackendWebSocketService_establishConnection(bearerToken) {
856
849
  const latency = receivedAt - timestamp;
857
850
  // Trace notification processing wi th latency data
858
851
  // Use stored channelType instead of parsing each time
852
+ // Promise result intentionally not awaited
853
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
859
854
  __classPrivateFieldGet(this, _BackendWebSocketService_trace, "f").call(this, {
860
855
  name: `${SERVICE_NAME} Notification`,
861
856
  data: {
@@ -875,47 +870,6 @@ async function _BackendWebSocketService_establishConnection(bearerToken) {
875
870
  return false;
876
871
  }, _BackendWebSocketService_parseMessage = function _BackendWebSocketService_parseMessage(data) {
877
872
  return JSON.parse(data);
878
- }, _BackendWebSocketService_handleClose = function _BackendWebSocketService_handleClose(event) {
879
- // Calculate connection duration before we clear state
880
- const connectionDuration = Date.now() - __classPrivateFieldGet(this, _BackendWebSocketService_connectedAt, "f");
881
- if (__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f")) {
882
- clearTimeout(__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f"));
883
- __classPrivateFieldSet(this, _BackendWebSocketService_connectionTimeout, null, "f");
884
- }
885
- if (__classPrivateFieldGet(this, _BackendWebSocketService_stableConnectionTimer, "f")) {
886
- clearTimeout(__classPrivateFieldGet(this, _BackendWebSocketService_stableConnectionTimer, "f"));
887
- __classPrivateFieldSet(this, _BackendWebSocketService_stableConnectionTimer, null, "f");
888
- }
889
- __classPrivateFieldSet(this, _BackendWebSocketService_connectedAt, 0, "f");
890
- // Clear any pending connection promise
891
- __classPrivateFieldSet(this, _BackendWebSocketService_connectionPromise, null, "f");
892
- // Clear subscriptions and pending requests on any disconnect
893
- // This ensures clean state for reconnection
894
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearPendingRequests).call(this, new Error('WebSocket connection closed'));
895
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearSubscriptions).call(this);
896
- // Update state to disconnected
897
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.DISCONNECTED);
898
- // Check if this was a manual disconnect
899
- if (__classPrivateFieldGet(this, _BackendWebSocketService_manualDisconnect, "f")) {
900
- // Manual disconnect - don't reconnect
901
- return;
902
- }
903
- // Trace unexpected disconnect with details
904
- __classPrivateFieldGet(this, _BackendWebSocketService_trace, "f").call(this, {
905
- name: `${SERVICE_NAME} Disconnect`,
906
- data: {
907
- code: event.code,
908
- reason: event.reason || getCloseReason(event.code),
909
- connectionDuration_ms: connectionDuration,
910
- },
911
- tags: {
912
- service: SERVICE_NAME,
913
- disconnect_type: 'unexpected',
914
- },
915
- }, () => {
916
- // Empty trace callback - just measuring the event
917
- });
918
- __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_scheduleReconnect).call(this);
919
873
  }, _BackendWebSocketService_handleError = function _BackendWebSocketService_handleError(_error) {
920
874
  // Placeholder for future error handling logic
921
875
  }, _BackendWebSocketService_scheduleReconnect = function _BackendWebSocketService_scheduleReconnect() {