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