@metamask-previews/core-backend 0.0.0-preview-bc80f5a1

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.
Files changed (60) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/LICENSE +20 -0
  3. package/README.md +360 -0
  4. package/dist/AccountActivityService-method-action-types.cjs +7 -0
  5. package/dist/AccountActivityService-method-action-types.cjs.map +1 -0
  6. package/dist/AccountActivityService-method-action-types.d.cts +30 -0
  7. package/dist/AccountActivityService-method-action-types.d.cts.map +1 -0
  8. package/dist/AccountActivityService-method-action-types.d.mts +30 -0
  9. package/dist/AccountActivityService-method-action-types.d.mts.map +1 -0
  10. package/dist/AccountActivityService-method-action-types.mjs +6 -0
  11. package/dist/AccountActivityService-method-action-types.mjs.map +1 -0
  12. package/dist/AccountActivityService.cjs +380 -0
  13. package/dist/AccountActivityService.cjs.map +1 -0
  14. package/dist/AccountActivityService.d.cts +148 -0
  15. package/dist/AccountActivityService.d.cts.map +1 -0
  16. package/dist/AccountActivityService.d.mts +148 -0
  17. package/dist/AccountActivityService.d.mts.map +1 -0
  18. package/dist/AccountActivityService.mjs +376 -0
  19. package/dist/AccountActivityService.mjs.map +1 -0
  20. package/dist/BackendWebSocketService-method-action-types.cjs +7 -0
  21. package/dist/BackendWebSocketService-method-action-types.cjs.map +1 -0
  22. package/dist/BackendWebSocketService-method-action-types.d.cts +146 -0
  23. package/dist/BackendWebSocketService-method-action-types.d.cts.map +1 -0
  24. package/dist/BackendWebSocketService-method-action-types.d.mts +146 -0
  25. package/dist/BackendWebSocketService-method-action-types.d.mts.map +1 -0
  26. package/dist/BackendWebSocketService-method-action-types.mjs +6 -0
  27. package/dist/BackendWebSocketService-method-action-types.mjs.map +1 -0
  28. package/dist/BackendWebSocketService.cjs +841 -0
  29. package/dist/BackendWebSocketService.cjs.map +1 -0
  30. package/dist/BackendWebSocketService.d.cts +366 -0
  31. package/dist/BackendWebSocketService.d.cts.map +1 -0
  32. package/dist/BackendWebSocketService.d.mts +366 -0
  33. package/dist/BackendWebSocketService.d.mts.map +1 -0
  34. package/dist/BackendWebSocketService.mjs +836 -0
  35. package/dist/BackendWebSocketService.mjs.map +1 -0
  36. package/dist/index.cjs +14 -0
  37. package/dist/index.cjs.map +1 -0
  38. package/dist/index.d.cts +10 -0
  39. package/dist/index.d.cts.map +1 -0
  40. package/dist/index.d.mts +10 -0
  41. package/dist/index.d.mts.map +1 -0
  42. package/dist/index.mjs +7 -0
  43. package/dist/index.mjs.map +1 -0
  44. package/dist/logger.cjs +7 -0
  45. package/dist/logger.cjs.map +1 -0
  46. package/dist/logger.d.cts +5 -0
  47. package/dist/logger.d.cts.map +1 -0
  48. package/dist/logger.d.mts +5 -0
  49. package/dist/logger.d.mts.map +1 -0
  50. package/dist/logger.mjs +4 -0
  51. package/dist/logger.mjs.map +1 -0
  52. package/dist/types.cjs +3 -0
  53. package/dist/types.cjs.map +1 -0
  54. package/dist/types.d.cts +71 -0
  55. package/dist/types.d.cts.map +1 -0
  56. package/dist/types.d.mts +71 -0
  57. package/dist/types.d.mts.map +1 -0
  58. package/dist/types.mjs +2 -0
  59. package/dist/types.mjs.map +1 -0
  60. package/package.json +80 -0
@@ -0,0 +1,841 @@
1
+ "use strict";
2
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
3
+ if (kind === "m") throw new TypeError("Private method is not writable");
4
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
5
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
6
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
7
+ };
8
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
9
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
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
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
+ };
13
+ var _BackendWebSocketService_instances, _BackendWebSocketService_messenger, _BackendWebSocketService_options, _BackendWebSocketService_isEnabled, _BackendWebSocketService_ws, _BackendWebSocketService_state, _BackendWebSocketService_reconnectAttempts, _BackendWebSocketService_reconnectTimer, _BackendWebSocketService_connectionTimeout, _BackendWebSocketService_connectionPromise, _BackendWebSocketService_pendingRequests, _BackendWebSocketService_connectedAt, _BackendWebSocketService_subscriptions, _BackendWebSocketService_channelCallbacks, _BackendWebSocketService_setupAuthentication, _BackendWebSocketService_buildAuthenticatedUrl, _BackendWebSocketService_establishConnection, _BackendWebSocketService_handleMessage, _BackendWebSocketService_isServerResponse, _BackendWebSocketService_isSubscriptionNotification, _BackendWebSocketService_isChannelMessage, _BackendWebSocketService_handleServerResponse, _BackendWebSocketService_handleChannelMessage, _BackendWebSocketService_handleSubscriptionNotification, _BackendWebSocketService_parseMessage, _BackendWebSocketService_handleClose, _BackendWebSocketService_handleError, _BackendWebSocketService_scheduleReconnect, _BackendWebSocketService_clearTimers, _BackendWebSocketService_clearPendingRequests, _BackendWebSocketService_clearSubscriptions, _BackendWebSocketService_setState, _BackendWebSocketService_shouldReconnectOnClose;
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.BackendWebSocketService = exports.WebSocketEventType = exports.WebSocketState = exports.getCloseReason = void 0;
16
+ const utils_1 = require("@metamask/utils");
17
+ const uuid_1 = require("uuid");
18
+ const logger_1 = require("./logger.cjs");
19
+ const SERVICE_NAME = 'BackendWebSocketService';
20
+ const log = (0, logger_1.createModuleLogger)(logger_1.projectLogger, SERVICE_NAME);
21
+ const MESSENGER_EXPOSED_METHODS = [
22
+ 'connect',
23
+ 'disconnect',
24
+ 'sendMessage',
25
+ 'sendRequest',
26
+ 'subscribe',
27
+ 'getConnectionInfo',
28
+ 'getSubscriptionsByChannel',
29
+ 'channelHasSubscription',
30
+ 'findSubscriptionsByChannelPrefix',
31
+ 'addChannelCallback',
32
+ 'removeChannelCallback',
33
+ 'getChannelCallbacks',
34
+ ];
35
+ /**
36
+ * Gets human-readable close reason from RFC 6455 close code
37
+ *
38
+ * @param code - WebSocket close code
39
+ * @returns Human-readable close reason
40
+ */
41
+ function getCloseReason(code) {
42
+ switch (code) {
43
+ case 1000:
44
+ return 'Normal Closure';
45
+ case 1001:
46
+ return 'Going Away';
47
+ case 1002:
48
+ return 'Protocol Error';
49
+ case 1003:
50
+ return 'Unsupported Data';
51
+ case 1004:
52
+ return 'Reserved';
53
+ case 1005:
54
+ return 'No Status Received';
55
+ case 1006:
56
+ return 'Abnormal Closure';
57
+ case 1007:
58
+ return 'Invalid frame payload data';
59
+ case 1008:
60
+ return 'Policy Violation';
61
+ case 1009:
62
+ return 'Message Too Big';
63
+ case 1010:
64
+ return 'Mandatory Extension';
65
+ case 1011:
66
+ return 'Internal Server Error';
67
+ case 1012:
68
+ return 'Service Restart';
69
+ case 1013:
70
+ return 'Try Again Later';
71
+ case 1014:
72
+ return 'Bad Gateway';
73
+ case 1015:
74
+ return 'TLS Handshake';
75
+ default:
76
+ if (code >= 3000 && code <= 3999) {
77
+ return 'Library/Framework Error';
78
+ }
79
+ if (code >= 4000 && code <= 4999) {
80
+ return 'Application Error';
81
+ }
82
+ return 'Unknown';
83
+ }
84
+ }
85
+ exports.getCloseReason = getCloseReason;
86
+ /**
87
+ * WebSocket connection states
88
+ */
89
+ var WebSocketState;
90
+ (function (WebSocketState) {
91
+ WebSocketState["CONNECTING"] = "connecting";
92
+ WebSocketState["CONNECTED"] = "connected";
93
+ WebSocketState["DISCONNECTING"] = "disconnecting";
94
+ WebSocketState["DISCONNECTED"] = "disconnected";
95
+ WebSocketState["ERROR"] = "error";
96
+ })(WebSocketState || (exports.WebSocketState = WebSocketState = {}));
97
+ /**
98
+ * WebSocket event types
99
+ */
100
+ var WebSocketEventType;
101
+ (function (WebSocketEventType) {
102
+ WebSocketEventType["CONNECTED"] = "connected";
103
+ WebSocketEventType["DISCONNECTED"] = "disconnected";
104
+ WebSocketEventType["MESSAGE"] = "message";
105
+ WebSocketEventType["ERROR"] = "error";
106
+ WebSocketEventType["RECONNECTING"] = "reconnecting";
107
+ WebSocketEventType["RECONNECTED"] = "reconnected";
108
+ })(WebSocketEventType || (exports.WebSocketEventType = WebSocketEventType = {}));
109
+ /**
110
+ * WebSocket Service with automatic reconnection, session management and direct callback routing
111
+ *
112
+ * Real-Time Performance Optimizations:
113
+ * - Fast path message routing (zero allocations)
114
+ * - Production mode removes try-catch overhead
115
+ * - Optimized JSON parsing with fail-fast
116
+ * - Direct callback routing bypasses event emitters
117
+ * - Memory cleanup and resource management
118
+ *
119
+ * Mobile Integration:
120
+ * Mobile apps should handle lifecycle events (background/foreground) by:
121
+ * 1. Calling disconnect() when app goes to background
122
+ * 2. Calling connect() when app returns to foreground
123
+ * 3. Calling destroy() on app termination
124
+ */
125
+ class BackendWebSocketService {
126
+ // =============================================================================
127
+ // 1. CONSTRUCTOR & INITIALIZATION
128
+ // =============================================================================
129
+ /**
130
+ * Creates a new WebSocket service instance
131
+ *
132
+ * @param options - Configuration options for the WebSocket service
133
+ */
134
+ constructor(options) {
135
+ _BackendWebSocketService_instances.add(this);
136
+ /**
137
+ * The name of the service.
138
+ */
139
+ this.name = SERVICE_NAME;
140
+ _BackendWebSocketService_messenger.set(this, void 0);
141
+ _BackendWebSocketService_options.set(this, void 0);
142
+ _BackendWebSocketService_isEnabled.set(this, void 0);
143
+ _BackendWebSocketService_ws.set(this, void 0);
144
+ _BackendWebSocketService_state.set(this, WebSocketState.DISCONNECTED);
145
+ _BackendWebSocketService_reconnectAttempts.set(this, 0);
146
+ _BackendWebSocketService_reconnectTimer.set(this, null);
147
+ _BackendWebSocketService_connectionTimeout.set(this, null);
148
+ // Track the current connection promise to handle concurrent connection attempts
149
+ _BackendWebSocketService_connectionPromise.set(this, null);
150
+ _BackendWebSocketService_pendingRequests.set(this, new Map());
151
+ _BackendWebSocketService_connectedAt.set(this, null);
152
+ // Simplified subscription storage (single flat map)
153
+ // Key: subscription ID string (e.g., 'sub_abc123def456')
154
+ // Value: WebSocketSubscription object with channels, callback and metadata
155
+ _BackendWebSocketService_subscriptions.set(this, new Map());
156
+ // Channel-based callback storage
157
+ // Key: channel name (serves as unique identifier)
158
+ // Value: ChannelCallback configuration
159
+ _BackendWebSocketService_channelCallbacks.set(this, new Map());
160
+ __classPrivateFieldSet(this, _BackendWebSocketService_messenger, options.messenger, "f");
161
+ __classPrivateFieldSet(this, _BackendWebSocketService_isEnabled, options.isEnabled, "f");
162
+ __classPrivateFieldSet(this, _BackendWebSocketService_options, {
163
+ url: options.url,
164
+ timeout: options.timeout ?? 10000,
165
+ reconnectDelay: options.reconnectDelay ?? 500,
166
+ maxReconnectDelay: options.maxReconnectDelay ?? 5000,
167
+ requestTimeout: options.requestTimeout ?? 30000,
168
+ }, "f");
169
+ // Setup authentication (always enabled)
170
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setupAuthentication).call(this);
171
+ // Register action handlers using the method actions pattern
172
+ __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
173
+ }
174
+ // =============================================================================
175
+ // 2. PUBLIC API METHODS
176
+ // =============================================================================
177
+ /**
178
+ * Establishes WebSocket connection with smart reconnection behavior
179
+ *
180
+ * Simplified Priority System (using AuthenticationController):
181
+ * 1. App closed/backgrounded → Stop all attempts (save resources)
182
+ * 2. User not signed in (wallet locked OR not authenticated) → Keep retrying
183
+ * 3. User signed in (wallet unlocked + authenticated) → Connect successfully
184
+ *
185
+ * @returns Promise that resolves when connection is established
186
+ */
187
+ async connect() {
188
+ // Priority 1: Check if connection is enabled via callback (app lifecycle check)
189
+ // If app is closed/backgrounded, stop all connection attempts to save resources
190
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_isEnabled, "f") && !__classPrivateFieldGet(this, _BackendWebSocketService_isEnabled, "f").call(this)) {
191
+ // Clear any pending reconnection attempts since app is disabled
192
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearTimers).call(this);
193
+ __classPrivateFieldSet(this, _BackendWebSocketService_reconnectAttempts, 0, "f");
194
+ return;
195
+ }
196
+ // If already connected, return immediately
197
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") === WebSocketState.CONNECTED) {
198
+ return;
199
+ }
200
+ // If already connecting, wait for the existing connection attempt to complete
201
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") === WebSocketState.CONNECTING && __classPrivateFieldGet(this, _BackendWebSocketService_connectionPromise, "f")) {
202
+ await __classPrivateFieldGet(this, _BackendWebSocketService_connectionPromise, "f");
203
+ return;
204
+ }
205
+ // Priority 2: Check authentication requirements (simplified - just check if signed in)
206
+ let bearerToken;
207
+ try {
208
+ // AuthenticationController.getBearerToken() handles wallet unlock checks internally
209
+ const token = await __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").call('AuthenticationController:getBearerToken');
210
+ if (!token) {
211
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_scheduleReconnect).call(this);
212
+ return;
213
+ }
214
+ bearerToken = token;
215
+ }
216
+ catch (error) {
217
+ log('Failed to check authentication requirements', { error });
218
+ // If we can't connect for ANY reason, schedule a retry
219
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_scheduleReconnect).call(this);
220
+ return;
221
+ }
222
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.CONNECTING);
223
+ // Create and store the connection promise
224
+ __classPrivateFieldSet(this, _BackendWebSocketService_connectionPromise, __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_establishConnection).call(this, bearerToken), "f");
225
+ try {
226
+ await __classPrivateFieldGet(this, _BackendWebSocketService_connectionPromise, "f");
227
+ }
228
+ catch (error) {
229
+ const errorMessage = (0, utils_1.getErrorMessage)(error);
230
+ log('Connection attempt failed', { errorMessage, error });
231
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.ERROR);
232
+ throw new Error(`Failed to connect to WebSocket: ${errorMessage}`);
233
+ }
234
+ finally {
235
+ // Clear the connection promise when done (success or failure)
236
+ __classPrivateFieldSet(this, _BackendWebSocketService_connectionPromise, null, "f");
237
+ }
238
+ }
239
+ /**
240
+ * Closes WebSocket connection
241
+ *
242
+ * @returns Promise that resolves when disconnection is complete
243
+ */
244
+ async disconnect() {
245
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") === WebSocketState.DISCONNECTED ||
246
+ __classPrivateFieldGet(this, _BackendWebSocketService_state, "f") === WebSocketState.DISCONNECTING) {
247
+ return;
248
+ }
249
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.DISCONNECTING);
250
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearTimers).call(this);
251
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearPendingRequests).call(this, new Error('WebSocket disconnected'));
252
+ // Clear any pending connection promise
253
+ __classPrivateFieldSet(this, _BackendWebSocketService_connectionPromise, null, "f");
254
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_ws, "f")) {
255
+ __classPrivateFieldGet(this, _BackendWebSocketService_ws, "f").close(1000, 'Normal closure');
256
+ }
257
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.DISCONNECTED);
258
+ log('WebSocket manually disconnected');
259
+ }
260
+ /**
261
+ * Sends a message through the WebSocket (fire-and-forget, no response expected)
262
+ *
263
+ * This is a low-level method for sending messages without waiting for a response.
264
+ * Most consumers should use `sendRequest()` instead, which handles request-response
265
+ * correlation and provides proper error handling with timeouts.
266
+ *
267
+ * Use this method only when:
268
+ * - You don't need a response from the server
269
+ * - You're implementing custom message protocols
270
+ * - You need fine-grained control over message timing
271
+ *
272
+ * @param message - The message to send
273
+ * @throws Error if WebSocket is not connected or send fails
274
+ *
275
+ * @see sendRequest for request-response pattern with automatic correlation
276
+ */
277
+ sendMessage(message) {
278
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") !== WebSocketState.CONNECTED || !__classPrivateFieldGet(this, _BackendWebSocketService_ws, "f")) {
279
+ throw new Error(`Cannot send message: WebSocket is ${__classPrivateFieldGet(this, _BackendWebSocketService_state, "f")}`);
280
+ }
281
+ try {
282
+ __classPrivateFieldGet(this, _BackendWebSocketService_ws, "f").send(JSON.stringify(message));
283
+ }
284
+ catch (error) {
285
+ const errorMessage = (0, utils_1.getErrorMessage)(error);
286
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_handleError).call(this, new Error(errorMessage));
287
+ throw new Error(errorMessage);
288
+ }
289
+ }
290
+ /**
291
+ * Sends a request and waits for a correlated response (recommended for most use cases)
292
+ *
293
+ * This is the recommended high-level method for request-response communication.
294
+ * It automatically handles:
295
+ * - Request ID generation and correlation
296
+ * - Response matching with timeout protection
297
+ * - Automatic reconnection on timeout
298
+ * - Proper cleanup of pending requests
299
+ *
300
+ * @param message - The request message (can include optional requestId for testing)
301
+ * @returns Promise that resolves with the response data
302
+ * @throws Error if WebSocket is not connected, request times out, or response indicates failure
303
+ *
304
+ * @see sendMessage for fire-and-forget messaging without response handling
305
+ */
306
+ async sendRequest(message) {
307
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") !== WebSocketState.CONNECTED) {
308
+ throw new Error(`Cannot send request: WebSocket is ${__classPrivateFieldGet(this, _BackendWebSocketService_state, "f")}`);
309
+ }
310
+ // Use provided requestId if available, otherwise generate a new one
311
+ const requestId = message.data?.requestId ?? (0, uuid_1.v4)();
312
+ const requestMessage = {
313
+ event: message.event,
314
+ data: {
315
+ ...message.data,
316
+ requestId, // Set after spread to ensure it's not overwritten by undefined
317
+ },
318
+ };
319
+ return new Promise((resolve, reject) => {
320
+ const timeout = setTimeout(() => {
321
+ __classPrivateFieldGet(this, _BackendWebSocketService_pendingRequests, "f").delete(requestId);
322
+ log('Request timeout - triggering reconnection', {
323
+ timeout: __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").requestTimeout,
324
+ });
325
+ // Trigger reconnection on request timeout as it may indicate stale connection
326
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") === WebSocketState.CONNECTED && __classPrivateFieldGet(this, _BackendWebSocketService_ws, "f")) {
327
+ // Force close the current connection to trigger reconnection logic
328
+ __classPrivateFieldGet(this, _BackendWebSocketService_ws, "f").close(3000, 'Request timeout - forcing reconnect');
329
+ }
330
+ reject(new Error(`Request timeout after ${__classPrivateFieldGet(this, _BackendWebSocketService_options, "f").requestTimeout}ms`));
331
+ }, __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").requestTimeout);
332
+ // Store in pending requests for response correlation
333
+ __classPrivateFieldGet(this, _BackendWebSocketService_pendingRequests, "f").set(requestId, {
334
+ resolve: resolve,
335
+ reject,
336
+ timeout,
337
+ });
338
+ // Send the request
339
+ try {
340
+ this.sendMessage(requestMessage);
341
+ }
342
+ catch (error) {
343
+ __classPrivateFieldGet(this, _BackendWebSocketService_pendingRequests, "f").delete(requestId);
344
+ clearTimeout(timeout);
345
+ reject(error instanceof Error ? error : new Error(String(error)));
346
+ }
347
+ });
348
+ }
349
+ /**
350
+ * Gets current connection information
351
+ *
352
+ * @returns Current connection status and details
353
+ */
354
+ getConnectionInfo() {
355
+ return {
356
+ state: __classPrivateFieldGet(this, _BackendWebSocketService_state, "f"),
357
+ url: __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").url,
358
+ reconnectAttempts: __classPrivateFieldGet(this, _BackendWebSocketService_reconnectAttempts, "f"),
359
+ connectedAt: __classPrivateFieldGet(this, _BackendWebSocketService_connectedAt, "f") ?? undefined,
360
+ };
361
+ }
362
+ /**
363
+ * Gets all subscription information for a specific channel
364
+ *
365
+ * @param channel - The channel name to look up
366
+ * @returns Array of subscription details for all subscriptions containing the channel
367
+ */
368
+ getSubscriptionsByChannel(channel) {
369
+ const matchingSubscriptions = [];
370
+ for (const [subscriptionId, subscription] of __classPrivateFieldGet(this, _BackendWebSocketService_subscriptions, "f")) {
371
+ if (subscription.channels.includes(channel)) {
372
+ matchingSubscriptions.push({
373
+ subscriptionId,
374
+ channels: subscription.channels,
375
+ unsubscribe: subscription.unsubscribe,
376
+ });
377
+ }
378
+ }
379
+ return matchingSubscriptions;
380
+ }
381
+ /**
382
+ * Checks if a channel has a subscription
383
+ *
384
+ * @param channel - The channel name to check
385
+ * @returns True if the channel has a subscription, false otherwise
386
+ */
387
+ channelHasSubscription(channel) {
388
+ for (const subscription of __classPrivateFieldGet(this, _BackendWebSocketService_subscriptions, "f").values()) {
389
+ if (subscription.channels.includes(channel)) {
390
+ return true;
391
+ }
392
+ }
393
+ return false;
394
+ }
395
+ /**
396
+ * Finds all subscriptions that have channels starting with the specified prefix
397
+ *
398
+ * @param channelPrefix - The channel prefix to search for (e.g., "account-activity.v1")
399
+ * @returns Array of subscription info for matching subscriptions
400
+ */
401
+ findSubscriptionsByChannelPrefix(channelPrefix) {
402
+ const matchingSubscriptions = [];
403
+ for (const [subscriptionId, subscription] of __classPrivateFieldGet(this, _BackendWebSocketService_subscriptions, "f")) {
404
+ // Check if any channel in this subscription starts with the prefix
405
+ const hasMatchingChannel = subscription.channels.some((channel) => channel.startsWith(channelPrefix));
406
+ if (hasMatchingChannel) {
407
+ matchingSubscriptions.push({
408
+ subscriptionId,
409
+ channels: subscription.channels,
410
+ unsubscribe: subscription.unsubscribe,
411
+ });
412
+ }
413
+ }
414
+ return matchingSubscriptions;
415
+ }
416
+ /**
417
+ * Register a callback for specific channels (local callback only, no server subscription)
418
+ *
419
+ * **Key Difference from `subscribe()`:**
420
+ * - `addChannelCallback()`: Registers a local callback without creating a server-side subscription.
421
+ * The callback triggers on ANY message matching the channel name, regardless of subscriptionId.
422
+ * Useful for system-wide notifications or when you don't control the subscription lifecycle.
423
+ *
424
+ * - `subscribe()`: Creates a proper server-side subscription with a subscriptionId.
425
+ * The callback only triggers for messages with the matching subscriptionId.
426
+ * Includes proper lifecycle management (unsubscribe, automatic cleanup on disconnect).
427
+ *
428
+ * **When to use `addChannelCallback()`:**
429
+ * - Listening to system-wide notifications (e.g., 'system-notifications.v1')
430
+ * - Monitoring channels where subscriptions are managed elsewhere
431
+ * - Debug/logging scenarios where you want to observe all channel messages
432
+ *
433
+ * **When to use `subscribe()` instead:**
434
+ * - Creating new subscriptions that need server-side registration
435
+ * - When you need proper cleanup via unsubscribe
436
+ * - Most application use cases (recommended approach)
437
+ *
438
+ * @param options - Channel callback configuration
439
+ * @param options.channelName - Channel name to match exactly
440
+ * @param options.callback - Function to call when channel matches
441
+ *
442
+ * @example
443
+ * ```typescript
444
+ * // Listen to system notifications (no server subscription needed)
445
+ * webSocketService.addChannelCallback({
446
+ * channelName: 'system-notifications.v1',
447
+ * callback: (notification) => {
448
+ * console.log('System notification:', notification.data);
449
+ * }
450
+ * });
451
+ *
452
+ * // For account-specific subscriptions, use subscribe() instead:
453
+ * // const sub = await webSocketService.subscribe({
454
+ * // channels: ['account-activity.v1.eip155:0:0x1234...'],
455
+ * // callback: (notification) => { ... }
456
+ * // });
457
+ * ```
458
+ *
459
+ * @see subscribe for creating proper server-side subscriptions with lifecycle management
460
+ */
461
+ addChannelCallback(options) {
462
+ const channelCallback = {
463
+ channelName: options.channelName,
464
+ callback: options.callback,
465
+ };
466
+ // Check if callback already exists for this channel
467
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_channelCallbacks, "f").has(options.channelName)) {
468
+ return;
469
+ }
470
+ __classPrivateFieldGet(this, _BackendWebSocketService_channelCallbacks, "f").set(options.channelName, channelCallback);
471
+ }
472
+ /**
473
+ * Remove a channel callback
474
+ *
475
+ * @param channelName - The channel name returned from addChannelCallback
476
+ * @returns True if callback was found and removed, false otherwise
477
+ */
478
+ removeChannelCallback(channelName) {
479
+ return __classPrivateFieldGet(this, _BackendWebSocketService_channelCallbacks, "f").delete(channelName);
480
+ }
481
+ /**
482
+ * Get all registered channel callbacks (for debugging)
483
+ *
484
+ * @returns Array of all registered channel callbacks
485
+ */
486
+ getChannelCallbacks() {
487
+ return Array.from(__classPrivateFieldGet(this, _BackendWebSocketService_channelCallbacks, "f").values());
488
+ }
489
+ /**
490
+ * Destroy the service and clean up resources
491
+ * Called when service is being destroyed or app is terminating
492
+ */
493
+ destroy() {
494
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearTimers).call(this);
495
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearSubscriptions).call(this);
496
+ // Clear any pending connection promise
497
+ __classPrivateFieldSet(this, _BackendWebSocketService_connectionPromise, null, "f");
498
+ // Clear all pending requests
499
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearPendingRequests).call(this, new Error('Service cleanup'));
500
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_ws, "f") && __classPrivateFieldGet(this, _BackendWebSocketService_ws, "f").readyState === WebSocket.OPEN) {
501
+ __classPrivateFieldGet(this, _BackendWebSocketService_ws, "f").close(1000, 'Service cleanup');
502
+ }
503
+ }
504
+ /**
505
+ * Create and manage a subscription with server-side registration (recommended for most use cases)
506
+ *
507
+ * This is the recommended subscription API for high-level services. It creates a proper
508
+ * server-side subscription and routes notifications based on subscriptionId.
509
+ *
510
+ * **Key Features:**
511
+ * - Creates server-side subscription with unique subscriptionId
512
+ * - Callback triggered only for messages with matching subscriptionId
513
+ * - Automatic lifecycle management (cleanup on disconnect)
514
+ * - Includes unsubscribe method for proper cleanup
515
+ * - Request-response pattern with error handling
516
+ *
517
+ * **When to use `subscribe()`:**
518
+ * - Creating new subscriptions (account activity, price updates, etc.)
519
+ * - When you need proper cleanup/unsubscribe functionality
520
+ * - Most application use cases
521
+ *
522
+ * **When to use `addChannelCallback()` instead:**
523
+ * - System-wide notifications without server-side subscription
524
+ * - Observing channels managed elsewhere
525
+ * - Debug/logging scenarios
526
+ *
527
+ * @param options - Subscription configuration
528
+ * @param options.channels - Array of channel names to subscribe to
529
+ * @param options.callback - Callback function for handling notifications
530
+ * @param options.requestId - Optional request ID for testing (will generate UUID if not provided)
531
+ * @returns Subscription object with unsubscribe method
532
+ *
533
+ * @example
534
+ * ```typescript
535
+ * // AccountActivityService usage
536
+ * const subscription = await webSocketService.subscribe({
537
+ * channels: ['account-activity.v1.eip155:0:0x1234...'],
538
+ * callback: (notification) => {
539
+ * this.handleAccountActivity(notification.data);
540
+ * }
541
+ * });
542
+ *
543
+ * // Later, clean up
544
+ * await subscription.unsubscribe();
545
+ * ```
546
+ *
547
+ * @see addChannelCallback for local callbacks without server-side subscription
548
+ */
549
+ async subscribe(options) {
550
+ const { channels, callback, requestId } = options;
551
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") !== WebSocketState.CONNECTED) {
552
+ throw new Error(`Cannot create subscription(s) ${channels.join(', ')}: WebSocket is ${__classPrivateFieldGet(this, _BackendWebSocketService_state, "f")}`);
553
+ }
554
+ // Send subscription request and wait for response
555
+ const subscriptionResponse = await this.sendRequest({
556
+ event: 'subscribe',
557
+ data: { channels, requestId },
558
+ });
559
+ if (!subscriptionResponse?.subscriptionId) {
560
+ throw new Error('Invalid subscription response: missing subscription ID');
561
+ }
562
+ const { subscriptionId } = subscriptionResponse;
563
+ // Check for failures
564
+ if (subscriptionResponse.failed && subscriptionResponse.failed.length > 0) {
565
+ throw new Error(`Subscription failed for channels: ${subscriptionResponse.failed.join(', ')}`);
566
+ }
567
+ // Create unsubscribe function
568
+ const unsubscribe = async (unsubRequestId) => {
569
+ // Send unsubscribe request first
570
+ await this.sendRequest({
571
+ event: 'unsubscribe',
572
+ data: {
573
+ subscription: subscriptionId,
574
+ channels,
575
+ requestId: unsubRequestId,
576
+ },
577
+ });
578
+ // Clean up subscription mapping
579
+ __classPrivateFieldGet(this, _BackendWebSocketService_subscriptions, "f").delete(subscriptionId);
580
+ };
581
+ const subscription = {
582
+ subscriptionId,
583
+ channels: [...channels],
584
+ unsubscribe,
585
+ };
586
+ // Store subscription with subscription ID as key
587
+ __classPrivateFieldGet(this, _BackendWebSocketService_subscriptions, "f").set(subscriptionId, {
588
+ subscriptionId,
589
+ channels: [...channels],
590
+ callback,
591
+ unsubscribe,
592
+ });
593
+ return subscription;
594
+ }
595
+ }
596
+ exports.BackendWebSocketService = BackendWebSocketService;
597
+ _BackendWebSocketService_messenger = new WeakMap(), _BackendWebSocketService_options = new WeakMap(), _BackendWebSocketService_isEnabled = new WeakMap(), _BackendWebSocketService_ws = new WeakMap(), _BackendWebSocketService_state = new WeakMap(), _BackendWebSocketService_reconnectAttempts = new WeakMap(), _BackendWebSocketService_reconnectTimer = new WeakMap(), _BackendWebSocketService_connectionTimeout = new WeakMap(), _BackendWebSocketService_connectionPromise = new WeakMap(), _BackendWebSocketService_pendingRequests = new WeakMap(), _BackendWebSocketService_connectedAt = new WeakMap(), _BackendWebSocketService_subscriptions = new WeakMap(), _BackendWebSocketService_channelCallbacks = new WeakMap(), _BackendWebSocketService_instances = new WeakSet(), _BackendWebSocketService_setupAuthentication = function _BackendWebSocketService_setupAuthentication() {
598
+ try {
599
+ // Subscribe to authentication state changes - this includes wallet unlock state
600
+ // AuthenticationController can only be signed in if wallet is unlocked
601
+ // Using selector to only listen for isSignedIn property changes for better performance
602
+ __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").subscribe('AuthenticationController:stateChange', (isSignedIn) => {
603
+ if (isSignedIn) {
604
+ // User signed in (wallet unlocked + authenticated) - try to connect
605
+ // Clear any pending reconnection timer since we're attempting connection
606
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearTimers).call(this);
607
+ this.connect().catch((error) => {
608
+ log('Failed to connect after sign-in', { error });
609
+ });
610
+ }
611
+ else {
612
+ // User signed out (wallet locked OR signed out) - disconnect and stop reconnection attempts
613
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearTimers).call(this);
614
+ __classPrivateFieldSet(this, _BackendWebSocketService_reconnectAttempts, 0, "f");
615
+ this.disconnect().catch((error) => {
616
+ log('Failed to disconnect after sign-out', { error });
617
+ });
618
+ }
619
+ }, (state) => state.isSignedIn);
620
+ }
621
+ catch (error) {
622
+ throw new Error(`Authentication setup failed: ${(0, utils_1.getErrorMessage)(error)}`);
623
+ }
624
+ }, _BackendWebSocketService_buildAuthenticatedUrl = function _BackendWebSocketService_buildAuthenticatedUrl(bearerToken) {
625
+ const baseUrl = __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").url;
626
+ // Add token as query parameter to the WebSocket URL
627
+ const url = new URL(baseUrl);
628
+ url.searchParams.set('token', bearerToken);
629
+ return url.toString();
630
+ }, _BackendWebSocketService_establishConnection =
631
+ /**
632
+ * Establishes the actual WebSocket connection
633
+ *
634
+ * @param bearerToken - The bearer token to use for authentication
635
+ * @returns Promise that resolves when connection is established
636
+ */
637
+ async function _BackendWebSocketService_establishConnection(bearerToken) {
638
+ const wsUrl = __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_buildAuthenticatedUrl).call(this, bearerToken);
639
+ return new Promise((resolve, reject) => {
640
+ const ws = new WebSocket(wsUrl);
641
+ __classPrivateFieldSet(this, _BackendWebSocketService_connectionTimeout, setTimeout(() => {
642
+ log('WebSocket connection timeout - forcing close', {
643
+ timeout: __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").timeout,
644
+ });
645
+ ws.close();
646
+ reject(new Error(`Connection timeout after ${__classPrivateFieldGet(this, _BackendWebSocketService_options, "f").timeout}ms`));
647
+ }, __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").timeout), "f");
648
+ ws.onopen = () => {
649
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f")) {
650
+ clearTimeout(__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f"));
651
+ __classPrivateFieldSet(this, _BackendWebSocketService_connectionTimeout, null, "f");
652
+ }
653
+ __classPrivateFieldSet(this, _BackendWebSocketService_ws, ws, "f");
654
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.CONNECTED);
655
+ __classPrivateFieldSet(this, _BackendWebSocketService_connectedAt, Date.now(), "f");
656
+ // Reset reconnect attempts on successful connection
657
+ __classPrivateFieldSet(this, _BackendWebSocketService_reconnectAttempts, 0, "f");
658
+ resolve();
659
+ };
660
+ ws.onerror = (event) => {
661
+ log('WebSocket onerror event triggered', { event });
662
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") === WebSocketState.CONNECTING) {
663
+ // Handle connection-phase errors
664
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f")) {
665
+ clearTimeout(__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f"));
666
+ __classPrivateFieldSet(this, _BackendWebSocketService_connectionTimeout, null, "f");
667
+ }
668
+ const error = new Error(`WebSocket connection error to ${wsUrl}`);
669
+ reject(error);
670
+ }
671
+ else {
672
+ // Handle runtime errors
673
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_handleError).call(this, new Error(`WebSocket error: ${event.type}`));
674
+ }
675
+ };
676
+ ws.onclose = (event) => {
677
+ log('WebSocket onclose event triggered', {
678
+ code: event.code,
679
+ reason: event.reason || 'none',
680
+ wasClean: event.wasClean,
681
+ });
682
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") === WebSocketState.CONNECTING) {
683
+ // Handle connection-phase close events
684
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f")) {
685
+ clearTimeout(__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f"));
686
+ __classPrivateFieldSet(this, _BackendWebSocketService_connectionTimeout, null, "f");
687
+ }
688
+ reject(new Error(`WebSocket connection closed during connection: ${event.code} ${event.reason}`));
689
+ }
690
+ else {
691
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_handleClose).call(this, event);
692
+ }
693
+ };
694
+ // Set up message handler immediately - no need to wait for connection
695
+ ws.onmessage = (event) => {
696
+ try {
697
+ const message = __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_parseMessage).call(this, event.data);
698
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_handleMessage).call(this, message);
699
+ }
700
+ catch {
701
+ // Silently ignore invalid JSON messages
702
+ }
703
+ };
704
+ });
705
+ }, _BackendWebSocketService_handleMessage = function _BackendWebSocketService_handleMessage(message) {
706
+ // Handle server responses (correlated with requests) first
707
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_isServerResponse).call(this, message)) {
708
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_handleServerResponse).call(this, message);
709
+ return;
710
+ }
711
+ // Handle subscription notifications with valid subscriptionId
712
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_isSubscriptionNotification).call(this, message)) {
713
+ const handled = __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_handleSubscriptionNotification).call(this, message);
714
+ // If subscription notification wasn't handled (falsy subscriptionId), fall through to channel handling
715
+ if (handled) {
716
+ return;
717
+ }
718
+ }
719
+ // Trigger channel callbacks for any message with a channel property
720
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_isChannelMessage).call(this, message)) {
721
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_handleChannelMessage).call(this, message);
722
+ }
723
+ }, _BackendWebSocketService_isServerResponse = function _BackendWebSocketService_isServerResponse(message) {
724
+ return ('data' in message &&
725
+ message.data &&
726
+ typeof message.data === 'object' &&
727
+ 'requestId' in message.data);
728
+ }, _BackendWebSocketService_isSubscriptionNotification = function _BackendWebSocketService_isSubscriptionNotification(message) {
729
+ return 'subscriptionId' in message && !__classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_isServerResponse).call(this, message);
730
+ }, _BackendWebSocketService_isChannelMessage = function _BackendWebSocketService_isChannelMessage(message) {
731
+ return 'channel' in message;
732
+ }, _BackendWebSocketService_handleServerResponse = function _BackendWebSocketService_handleServerResponse(message) {
733
+ const { requestId } = message.data;
734
+ const request = __classPrivateFieldGet(this, _BackendWebSocketService_pendingRequests, "f").get(requestId);
735
+ if (!request) {
736
+ return;
737
+ }
738
+ __classPrivateFieldGet(this, _BackendWebSocketService_pendingRequests, "f").delete(requestId);
739
+ clearTimeout(request.timeout);
740
+ // Check if the response indicates failure
741
+ if (message.data.failed && message.data.failed.length > 0) {
742
+ request.reject(new Error(`Request failed: ${message.data.failed.join(', ')}`));
743
+ }
744
+ else {
745
+ request.resolve(message.data);
746
+ }
747
+ }, _BackendWebSocketService_handleChannelMessage = function _BackendWebSocketService_handleChannelMessage(message) {
748
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_channelCallbacks, "f").size === 0) {
749
+ return;
750
+ }
751
+ // Direct lookup for exact channel match
752
+ __classPrivateFieldGet(this, _BackendWebSocketService_channelCallbacks, "f").get(message.channel)?.callback(message);
753
+ }, _BackendWebSocketService_handleSubscriptionNotification = function _BackendWebSocketService_handleSubscriptionNotification(message) {
754
+ const { subscriptionId } = message;
755
+ // Only handle if subscriptionId is defined and not null (allows "0" as valid ID)
756
+ if (subscriptionId !== null && subscriptionId !== undefined) {
757
+ __classPrivateFieldGet(this, _BackendWebSocketService_subscriptions, "f").get(subscriptionId)?.callback?.(message);
758
+ return true;
759
+ }
760
+ return false;
761
+ }, _BackendWebSocketService_parseMessage = function _BackendWebSocketService_parseMessage(data) {
762
+ return JSON.parse(data);
763
+ }, _BackendWebSocketService_handleClose = function _BackendWebSocketService_handleClose(event) {
764
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearTimers).call(this);
765
+ __classPrivateFieldSet(this, _BackendWebSocketService_connectedAt, null, "f");
766
+ // Clear any pending connection promise
767
+ __classPrivateFieldSet(this, _BackendWebSocketService_connectionPromise, null, "f");
768
+ // Clear subscriptions and pending requests on any disconnect
769
+ // This ensures clean state for reconnection
770
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearPendingRequests).call(this, new Error('WebSocket connection closed'));
771
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearSubscriptions).call(this);
772
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") === WebSocketState.DISCONNECTING) {
773
+ // Manual disconnect
774
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.DISCONNECTED);
775
+ return;
776
+ }
777
+ // For unexpected disconnects, update the state to reflect that we're disconnected
778
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.DISCONNECTED);
779
+ // Check if we should attempt reconnection based on close code
780
+ const shouldReconnect = __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_shouldReconnectOnClose).call(this, event.code);
781
+ if (shouldReconnect) {
782
+ log('Connection lost unexpectedly, will attempt reconnection', {
783
+ code: event.code,
784
+ });
785
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_scheduleReconnect).call(this);
786
+ }
787
+ }, _BackendWebSocketService_handleError = function _BackendWebSocketService_handleError(_error) {
788
+ // Placeholder for future error handling logic
789
+ }, _BackendWebSocketService_scheduleReconnect = function _BackendWebSocketService_scheduleReconnect() {
790
+ __classPrivateFieldSet(this, _BackendWebSocketService_reconnectAttempts, __classPrivateFieldGet(this, _BackendWebSocketService_reconnectAttempts, "f") + 1, "f");
791
+ const rawDelay = __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").reconnectDelay * Math.pow(1.5, __classPrivateFieldGet(this, _BackendWebSocketService_reconnectAttempts, "f") - 1);
792
+ const delay = Math.min(rawDelay, __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").maxReconnectDelay);
793
+ log('Scheduling reconnection attempt', {
794
+ attempt: __classPrivateFieldGet(this, _BackendWebSocketService_reconnectAttempts, "f"),
795
+ delayMs: delay,
796
+ });
797
+ __classPrivateFieldSet(this, _BackendWebSocketService_reconnectTimer, setTimeout(() => {
798
+ // Clear timer reference first
799
+ __classPrivateFieldSet(this, _BackendWebSocketService_reconnectTimer, null, "f");
800
+ // Check if connection is still enabled before reconnecting
801
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_isEnabled, "f") && !__classPrivateFieldGet(this, _BackendWebSocketService_isEnabled, "f").call(this)) {
802
+ log('Reconnection disabled by isEnabled - stopping all attempts');
803
+ __classPrivateFieldSet(this, _BackendWebSocketService_reconnectAttempts, 0, "f");
804
+ return;
805
+ }
806
+ // Attempt to reconnect - if it fails, schedule another attempt
807
+ this.connect().catch(() => {
808
+ __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_scheduleReconnect).call(this);
809
+ });
810
+ }, delay), "f");
811
+ }, _BackendWebSocketService_clearTimers = function _BackendWebSocketService_clearTimers() {
812
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_reconnectTimer, "f")) {
813
+ clearTimeout(__classPrivateFieldGet(this, _BackendWebSocketService_reconnectTimer, "f"));
814
+ __classPrivateFieldSet(this, _BackendWebSocketService_reconnectTimer, null, "f");
815
+ }
816
+ if (__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f")) {
817
+ clearTimeout(__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f"));
818
+ __classPrivateFieldSet(this, _BackendWebSocketService_connectionTimeout, null, "f");
819
+ }
820
+ }, _BackendWebSocketService_clearPendingRequests = function _BackendWebSocketService_clearPendingRequests(error) {
821
+ for (const [, request] of __classPrivateFieldGet(this, _BackendWebSocketService_pendingRequests, "f")) {
822
+ clearTimeout(request.timeout);
823
+ request.reject(error);
824
+ }
825
+ __classPrivateFieldGet(this, _BackendWebSocketService_pendingRequests, "f").clear();
826
+ }, _BackendWebSocketService_clearSubscriptions = function _BackendWebSocketService_clearSubscriptions() {
827
+ __classPrivateFieldGet(this, _BackendWebSocketService_subscriptions, "f").clear();
828
+ }, _BackendWebSocketService_setState = function _BackendWebSocketService_setState(newState) {
829
+ const oldState = __classPrivateFieldGet(this, _BackendWebSocketService_state, "f");
830
+ __classPrivateFieldSet(this, _BackendWebSocketService_state, newState, "f");
831
+ if (oldState !== newState) {
832
+ log('WebSocket state changed', { oldState, newState });
833
+ // Publish connection state change event
834
+ // Messenger handles listener errors internally, no need for try-catch
835
+ __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").publish('BackendWebSocketService:connectionStateChanged', this.getConnectionInfo());
836
+ }
837
+ }, _BackendWebSocketService_shouldReconnectOnClose = function _BackendWebSocketService_shouldReconnectOnClose(code) {
838
+ // Don't reconnect only on normal closure (manual disconnect)
839
+ return code !== 1000;
840
+ };
841
+ //# sourceMappingURL=BackendWebSocketService.cjs.map