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