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