@metamask-previews/core-backend 1.0.1-preview-5f3688c1 → 1.0.1-preview-0189b42
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 +30 -0
- package/README.md +62 -22
- package/dist/AccountActivityService.cjs +37 -88
- package/dist/AccountActivityService.cjs.map +1 -1
- package/dist/AccountActivityService.d.cts +3 -7
- package/dist/AccountActivityService.d.cts.map +1 -1
- package/dist/AccountActivityService.d.mts +3 -7
- package/dist/AccountActivityService.d.mts.map +1 -1
- package/dist/AccountActivityService.mjs +37 -88
- package/dist/AccountActivityService.mjs.map +1 -1
- package/dist/BackendWebSocketService.cjs +175 -96
- package/dist/BackendWebSocketService.cjs.map +1 -1
- package/dist/BackendWebSocketService.d.cts +31 -12
- package/dist/BackendWebSocketService.d.cts.map +1 -1
- package/dist/BackendWebSocketService.d.mts +31 -12
- package/dist/BackendWebSocketService.d.mts.map +1 -1
- package/dist/BackendWebSocketService.mjs +175 -96
- 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,8 @@ 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 ?? ((_request, fn) => fn?.()), "f");
|
|
158
169
|
__classPrivateFieldSet(this, _BackendWebSocketService_options, {
|
|
159
170
|
url: options.url,
|
|
160
171
|
timeout: options.timeout ?? 10000,
|
|
@@ -162,8 +173,8 @@ export class BackendWebSocketService {
|
|
|
162
173
|
maxReconnectDelay: options.maxReconnectDelay ?? 5000,
|
|
163
174
|
requestTimeout: options.requestTimeout ?? 30000,
|
|
164
175
|
}, "f");
|
|
165
|
-
//
|
|
166
|
-
__classPrivateFieldGet(this, _BackendWebSocketService_instances, "m",
|
|
176
|
+
// Subscribe to authentication and keyring controller events
|
|
177
|
+
__classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_subscribeEvents).call(this);
|
|
167
178
|
// Register action handlers using the method actions pattern
|
|
168
179
|
__classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
|
|
169
180
|
}
|
|
@@ -173,18 +184,23 @@ export class BackendWebSocketService {
|
|
|
173
184
|
/**
|
|
174
185
|
* Establishes WebSocket connection with smart reconnection behavior
|
|
175
186
|
*
|
|
176
|
-
*
|
|
177
|
-
* 1.
|
|
178
|
-
* 2.
|
|
179
|
-
* 3. User signed in (
|
|
187
|
+
* Connection Requirements (all must be true):
|
|
188
|
+
* 1. Feature enabled (isEnabled() = true)
|
|
189
|
+
* 2. Wallet unlocked (checked by getBearerToken)
|
|
190
|
+
* 3. User signed in (checked by getBearerToken)
|
|
191
|
+
*
|
|
192
|
+
* Platform code should call this when app opens/foregrounds.
|
|
193
|
+
* Automatically called on KeyringController:unlock event.
|
|
180
194
|
*
|
|
181
195
|
* @returns Promise that resolves when connection is established
|
|
182
196
|
*/
|
|
183
197
|
async connect() {
|
|
184
|
-
//
|
|
185
|
-
|
|
198
|
+
// Reset manual disconnect flag when explicitly connecting
|
|
199
|
+
__classPrivateFieldSet(this, _BackendWebSocketService_manualDisconnect, false, "f");
|
|
200
|
+
// Priority 1: Check if feature is enabled via callback (feature flag check)
|
|
201
|
+
// If feature is disabled, stop all connection attempts
|
|
186
202
|
if (__classPrivateFieldGet(this, _BackendWebSocketService_isEnabled, "f") && !__classPrivateFieldGet(this, _BackendWebSocketService_isEnabled, "f").call(this)) {
|
|
187
|
-
// Clear any pending reconnection attempts since
|
|
203
|
+
// Clear any pending reconnection attempts since feature is disabled
|
|
188
204
|
__classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearTimers).call(this);
|
|
189
205
|
__classPrivateFieldSet(this, _BackendWebSocketService_reconnectAttempts, 0, "f");
|
|
190
206
|
return;
|
|
@@ -198,10 +214,9 @@ export class BackendWebSocketService {
|
|
|
198
214
|
await __classPrivateFieldGet(this, _BackendWebSocketService_connectionPromise, "f");
|
|
199
215
|
return;
|
|
200
216
|
}
|
|
201
|
-
// Priority 2: Check authentication requirements (
|
|
217
|
+
// Priority 2: Check authentication requirements (signed in)
|
|
202
218
|
let bearerToken;
|
|
203
219
|
try {
|
|
204
|
-
// AuthenticationController.getBearerToken() handles wallet unlock checks internally
|
|
205
220
|
const token = await __classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").call('AuthenticationController:getBearerToken');
|
|
206
221
|
if (!token) {
|
|
207
222
|
__classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_scheduleReconnect).call(this);
|
|
@@ -210,8 +225,10 @@ export class BackendWebSocketService {
|
|
|
210
225
|
bearerToken = token;
|
|
211
226
|
}
|
|
212
227
|
catch (error) {
|
|
213
|
-
log('Failed to
|
|
214
|
-
|
|
228
|
+
log('Failed to get bearer token (wallet locked or not signed in)', {
|
|
229
|
+
error,
|
|
230
|
+
});
|
|
231
|
+
// Can't connect - schedule retry
|
|
215
232
|
__classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_scheduleReconnect).call(this);
|
|
216
233
|
return;
|
|
217
234
|
}
|
|
@@ -225,7 +242,8 @@ export class BackendWebSocketService {
|
|
|
225
242
|
const errorMessage = getErrorMessage(error);
|
|
226
243
|
log('Connection attempt failed', { errorMessage, error });
|
|
227
244
|
__classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.ERROR);
|
|
228
|
-
|
|
245
|
+
// Rethrow to propagate error to caller
|
|
246
|
+
throw error;
|
|
229
247
|
}
|
|
230
248
|
finally {
|
|
231
249
|
// Clear the connection promise when done (success or failure)
|
|
@@ -242,6 +260,8 @@ export class BackendWebSocketService {
|
|
|
242
260
|
__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") === WebSocketState.DISCONNECTING) {
|
|
243
261
|
return;
|
|
244
262
|
}
|
|
263
|
+
// Mark this as a manual disconnect to prevent automatic reconnection
|
|
264
|
+
__classPrivateFieldSet(this, _BackendWebSocketService_manualDisconnect, true, "f");
|
|
245
265
|
__classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.DISCONNECTING);
|
|
246
266
|
__classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearTimers).call(this);
|
|
247
267
|
__classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearPendingRequests).call(this, new Error('WebSocket disconnected'));
|
|
@@ -250,7 +270,6 @@ export class BackendWebSocketService {
|
|
|
250
270
|
if (__classPrivateFieldGet(this, _BackendWebSocketService_ws, "f")) {
|
|
251
271
|
__classPrivateFieldGet(this, _BackendWebSocketService_ws, "f").close(1000, 'Normal closure');
|
|
252
272
|
}
|
|
253
|
-
__classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.DISCONNECTED);
|
|
254
273
|
log('WebSocket manually disconnected');
|
|
255
274
|
}
|
|
256
275
|
/**
|
|
@@ -352,7 +371,7 @@ export class BackendWebSocketService {
|
|
|
352
371
|
state: __classPrivateFieldGet(this, _BackendWebSocketService_state, "f"),
|
|
353
372
|
url: __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").url,
|
|
354
373
|
reconnectAttempts: __classPrivateFieldGet(this, _BackendWebSocketService_reconnectAttempts, "f"),
|
|
355
|
-
connectedAt: __classPrivateFieldGet(this, _BackendWebSocketService_connectedAt, "f")
|
|
374
|
+
connectedAt: __classPrivateFieldGet(this, _BackendWebSocketService_connectedAt, "f"),
|
|
356
375
|
};
|
|
357
376
|
}
|
|
358
377
|
/**
|
|
@@ -368,6 +387,7 @@ export class BackendWebSocketService {
|
|
|
368
387
|
matchingSubscriptions.push({
|
|
369
388
|
subscriptionId,
|
|
370
389
|
channels: subscription.channels,
|
|
390
|
+
channelType: subscription.channelType,
|
|
371
391
|
unsubscribe: subscription.unsubscribe,
|
|
372
392
|
});
|
|
373
393
|
}
|
|
@@ -403,6 +423,7 @@ export class BackendWebSocketService {
|
|
|
403
423
|
matchingSubscriptions.push({
|
|
404
424
|
subscriptionId,
|
|
405
425
|
channels: subscription.channels,
|
|
426
|
+
channelType: subscription.channelType,
|
|
406
427
|
unsubscribe: subscription.unsubscribe,
|
|
407
428
|
});
|
|
408
429
|
}
|
|
@@ -496,6 +517,8 @@ export class BackendWebSocketService {
|
|
|
496
517
|
if (__classPrivateFieldGet(this, _BackendWebSocketService_ws, "f") && __classPrivateFieldGet(this, _BackendWebSocketService_ws, "f").readyState === WebSocket.OPEN) {
|
|
497
518
|
__classPrivateFieldGet(this, _BackendWebSocketService_ws, "f").close(1000, 'Service cleanup');
|
|
498
519
|
}
|
|
520
|
+
// Set state to disconnected immediately
|
|
521
|
+
__classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.DISCONNECTED);
|
|
499
522
|
}
|
|
500
523
|
/**
|
|
501
524
|
* Create and manage a subscription with server-side registration (recommended for most use cases)
|
|
@@ -524,6 +547,7 @@ export class BackendWebSocketService {
|
|
|
524
547
|
* @param options.channels - Array of channel names to subscribe to
|
|
525
548
|
* @param options.callback - Callback function for handling notifications
|
|
526
549
|
* @param options.requestId - Optional request ID for testing (will generate UUID if not provided)
|
|
550
|
+
* @param options.channelType - Channel type identifier
|
|
527
551
|
* @returns Subscription object with unsubscribe method
|
|
528
552
|
*
|
|
529
553
|
* @example
|
|
@@ -543,7 +567,7 @@ export class BackendWebSocketService {
|
|
|
543
567
|
* @see addChannelCallback for local callbacks without server-side subscription
|
|
544
568
|
*/
|
|
545
569
|
async subscribe(options) {
|
|
546
|
-
const { channels, callback, requestId } = options;
|
|
570
|
+
const { channels, channelType, callback, requestId } = options;
|
|
547
571
|
if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") !== WebSocketState.CONNECTED) {
|
|
548
572
|
throw new Error(`Cannot create subscription(s) ${channels.join(', ')}: WebSocket is ${__classPrivateFieldGet(this, _BackendWebSocketService_state, "f")}`);
|
|
549
573
|
}
|
|
@@ -556,10 +580,6 @@ export class BackendWebSocketService {
|
|
|
556
580
|
throw new Error('Invalid subscription response: missing subscription ID');
|
|
557
581
|
}
|
|
558
582
|
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
583
|
// Create unsubscribe function
|
|
564
584
|
const unsubscribe = async (unsubRequestId) => {
|
|
565
585
|
// Send unsubscribe request first
|
|
@@ -577,45 +597,42 @@ export class BackendWebSocketService {
|
|
|
577
597
|
const subscription = {
|
|
578
598
|
subscriptionId,
|
|
579
599
|
channels: [...channels],
|
|
600
|
+
channelType,
|
|
580
601
|
unsubscribe,
|
|
581
602
|
};
|
|
582
603
|
// Store subscription with subscription ID as key
|
|
583
604
|
__classPrivateFieldGet(this, _BackendWebSocketService_subscriptions, "f").set(subscriptionId, {
|
|
584
605
|
subscriptionId,
|
|
585
606
|
channels: [...channels],
|
|
607
|
+
channelType,
|
|
586
608
|
callback,
|
|
587
609
|
unsubscribe,
|
|
588
610
|
});
|
|
589
611
|
return subscription;
|
|
590
612
|
}
|
|
591
613
|
}
|
|
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
|
-
}
|
|
614
|
+
_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() {
|
|
615
|
+
// Subscribe to authentication state changes (sign in/out)
|
|
616
|
+
__classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").subscribe('AuthenticationController:stateChange', (state) => {
|
|
617
|
+
if (state.isSignedIn) {
|
|
618
|
+
// eslint-disable-next-line no-void
|
|
619
|
+
void this.connect();
|
|
620
|
+
}
|
|
621
|
+
else {
|
|
622
|
+
// eslint-disable-next-line no-void
|
|
623
|
+
void this.disconnect();
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
// Subscribe to wallet unlock event
|
|
627
|
+
__classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").subscribe('KeyringController:unlock', () => {
|
|
628
|
+
// eslint-disable-next-line no-void
|
|
629
|
+
void this.connect();
|
|
630
|
+
});
|
|
631
|
+
// Subscribe to wallet lock event
|
|
632
|
+
__classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").subscribe('KeyringController:lock', () => {
|
|
633
|
+
// eslint-disable-next-line no-void
|
|
634
|
+
void this.disconnect();
|
|
635
|
+
});
|
|
619
636
|
}, _BackendWebSocketService_buildAuthenticatedUrl = function _BackendWebSocketService_buildAuthenticatedUrl(bearerToken) {
|
|
620
637
|
const baseUrl = __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").url;
|
|
621
638
|
// Add token as query parameter to the WebSocket URL
|
|
@@ -631,6 +648,7 @@ _BackendWebSocketService_messenger = new WeakMap(), _BackendWebSocketService_opt
|
|
|
631
648
|
*/
|
|
632
649
|
async function _BackendWebSocketService_establishConnection(bearerToken) {
|
|
633
650
|
const wsUrl = __classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_buildAuthenticatedUrl).call(this, bearerToken);
|
|
651
|
+
const connectionStartTime = Date.now();
|
|
634
652
|
return new Promise((resolve, reject) => {
|
|
635
653
|
const ws = new WebSocket(wsUrl);
|
|
636
654
|
__classPrivateFieldSet(this, _BackendWebSocketService_connectionTimeout, setTimeout(() => {
|
|
@@ -638,40 +656,48 @@ async function _BackendWebSocketService_establishConnection(bearerToken) {
|
|
|
638
656
|
timeout: __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").timeout,
|
|
639
657
|
});
|
|
640
658
|
ws.close();
|
|
641
|
-
reject(new Error(`Connection timeout after ${__classPrivateFieldGet(this, _BackendWebSocketService_options, "f").timeout}ms`));
|
|
659
|
+
reject(new Error(`Failed to connect to WebSocket: Connection timeout after ${__classPrivateFieldGet(this, _BackendWebSocketService_options, "f").timeout}ms`));
|
|
642
660
|
}, __classPrivateFieldGet(this, _BackendWebSocketService_options, "f").timeout), "f");
|
|
643
661
|
ws.onopen = () => {
|
|
644
662
|
if (__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f")) {
|
|
645
663
|
clearTimeout(__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f"));
|
|
646
664
|
__classPrivateFieldSet(this, _BackendWebSocketService_connectionTimeout, null, "f");
|
|
647
665
|
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
666
|
+
// Calculate connection latency
|
|
667
|
+
const connectionLatency = Date.now() - connectionStartTime;
|
|
668
|
+
// Trace successful connection with latency
|
|
669
|
+
__classPrivateFieldGet(this, _BackendWebSocketService_trace, "f").call(this, {
|
|
670
|
+
name: `${SERVICE_NAME} Connection`,
|
|
671
|
+
data: {
|
|
672
|
+
reconnectAttempt: __classPrivateFieldGet(this, _BackendWebSocketService_reconnectAttempts, "f"),
|
|
673
|
+
latency_ms: connectionLatency,
|
|
674
|
+
},
|
|
675
|
+
tags: {
|
|
676
|
+
service: SERVICE_NAME,
|
|
677
|
+
},
|
|
678
|
+
}, () => {
|
|
679
|
+
__classPrivateFieldSet(this, _BackendWebSocketService_ws, ws, "f");
|
|
680
|
+
__classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.CONNECTED);
|
|
681
|
+
__classPrivateFieldSet(this, _BackendWebSocketService_connectedAt, Date.now(), "f");
|
|
682
|
+
// Reset reconnect attempts on successful connection
|
|
683
|
+
__classPrivateFieldSet(this, _BackendWebSocketService_reconnectAttempts, 0, "f");
|
|
684
|
+
resolve();
|
|
685
|
+
});
|
|
654
686
|
};
|
|
655
687
|
ws.onerror = (event) => {
|
|
656
688
|
log('WebSocket onerror event triggered', { event });
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
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}`));
|
|
689
|
+
// Handle connection-phase errors
|
|
690
|
+
if (__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f")) {
|
|
691
|
+
clearTimeout(__classPrivateFieldGet(this, _BackendWebSocketService_connectionTimeout, "f"));
|
|
692
|
+
__classPrivateFieldSet(this, _BackendWebSocketService_connectionTimeout, null, "f");
|
|
669
693
|
}
|
|
694
|
+
const error = new Error(`WebSocket connection error to ${wsUrl}`);
|
|
695
|
+
reject(error);
|
|
670
696
|
};
|
|
671
697
|
ws.onclose = (event) => {
|
|
672
698
|
log('WebSocket onclose event triggered', {
|
|
673
699
|
code: event.code,
|
|
674
|
-
reason: event.reason
|
|
700
|
+
reason: event.reason,
|
|
675
701
|
wasClean: event.wasClean,
|
|
676
702
|
});
|
|
677
703
|
if (__classPrivateFieldGet(this, _BackendWebSocketService_state, "f") === WebSocketState.CONNECTING) {
|
|
@@ -743,42 +769,98 @@ async function _BackendWebSocketService_establishConnection(bearerToken) {
|
|
|
743
769
|
if (__classPrivateFieldGet(this, _BackendWebSocketService_channelCallbacks, "f").size === 0) {
|
|
744
770
|
return;
|
|
745
771
|
}
|
|
746
|
-
//
|
|
747
|
-
|
|
772
|
+
// Calculate notification latency: time from server sent to client received
|
|
773
|
+
const receivedAt = Date.now();
|
|
774
|
+
const latency = receivedAt - message.timestamp;
|
|
775
|
+
// Trace channel message processing with latency data
|
|
776
|
+
__classPrivateFieldGet(this, _BackendWebSocketService_trace, "f").call(this, {
|
|
777
|
+
name: `${SERVICE_NAME} Channel Message`,
|
|
778
|
+
data: {
|
|
779
|
+
channel: message.channel,
|
|
780
|
+
latency_ms: latency,
|
|
781
|
+
event: message.event,
|
|
782
|
+
},
|
|
783
|
+
tags: {
|
|
784
|
+
service: SERVICE_NAME,
|
|
785
|
+
channel_type: message.channel,
|
|
786
|
+
},
|
|
787
|
+
}, () => {
|
|
788
|
+
// Direct lookup for exact channel match
|
|
789
|
+
__classPrivateFieldGet(this, _BackendWebSocketService_channelCallbacks, "f").get(message.channel)?.callback(message);
|
|
790
|
+
});
|
|
748
791
|
}, _BackendWebSocketService_handleSubscriptionNotification = function _BackendWebSocketService_handleSubscriptionNotification(message) {
|
|
749
|
-
const { subscriptionId } = message;
|
|
792
|
+
const { subscriptionId, timestamp, channel } = message;
|
|
750
793
|
// Only handle if subscriptionId is defined and not null (allows "0" as valid ID)
|
|
751
794
|
if (subscriptionId !== null && subscriptionId !== undefined) {
|
|
752
|
-
__classPrivateFieldGet(this, _BackendWebSocketService_subscriptions, "f").get(subscriptionId)
|
|
795
|
+
const subscription = __classPrivateFieldGet(this, _BackendWebSocketService_subscriptions, "f").get(subscriptionId);
|
|
796
|
+
if (!subscription) {
|
|
797
|
+
return false;
|
|
798
|
+
}
|
|
799
|
+
// Calculate notification latency: time from server sent to client received
|
|
800
|
+
const receivedAt = Date.now();
|
|
801
|
+
const latency = receivedAt - timestamp;
|
|
802
|
+
// Trace notification processing with latency data
|
|
803
|
+
// Use stored channelType instead of parsing each time
|
|
804
|
+
__classPrivateFieldGet(this, _BackendWebSocketService_trace, "f").call(this, {
|
|
805
|
+
name: `${SERVICE_NAME} Notification`,
|
|
806
|
+
data: {
|
|
807
|
+
channel,
|
|
808
|
+
latency_ms: latency,
|
|
809
|
+
subscriptionId,
|
|
810
|
+
},
|
|
811
|
+
tags: {
|
|
812
|
+
service: SERVICE_NAME,
|
|
813
|
+
notification_type: subscription.channelType,
|
|
814
|
+
},
|
|
815
|
+
}, () => {
|
|
816
|
+
subscription.callback?.(message);
|
|
817
|
+
});
|
|
753
818
|
return true;
|
|
754
819
|
}
|
|
755
820
|
return false;
|
|
756
821
|
}, _BackendWebSocketService_parseMessage = function _BackendWebSocketService_parseMessage(data) {
|
|
757
822
|
return JSON.parse(data);
|
|
758
823
|
}, _BackendWebSocketService_handleClose = function _BackendWebSocketService_handleClose(event) {
|
|
824
|
+
// Calculate connection duration before we clear state
|
|
825
|
+
const connectionDuration = Date.now() - __classPrivateFieldGet(this, _BackendWebSocketService_connectedAt, "f");
|
|
759
826
|
__classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearTimers).call(this);
|
|
760
|
-
__classPrivateFieldSet(this, _BackendWebSocketService_connectedAt,
|
|
827
|
+
__classPrivateFieldSet(this, _BackendWebSocketService_connectedAt, 0, "f");
|
|
761
828
|
// Clear any pending connection promise
|
|
762
829
|
__classPrivateFieldSet(this, _BackendWebSocketService_connectionPromise, null, "f");
|
|
763
830
|
// Clear subscriptions and pending requests on any disconnect
|
|
764
831
|
// This ensures clean state for reconnection
|
|
765
832
|
__classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearPendingRequests).call(this, new Error('WebSocket connection closed'));
|
|
766
833
|
__classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_clearSubscriptions).call(this);
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
834
|
+
// Update state to disconnected
|
|
835
|
+
__classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_setState).call(this, WebSocketState.DISCONNECTED);
|
|
836
|
+
// Check if this was a manual disconnect
|
|
837
|
+
if (__classPrivateFieldGet(this, _BackendWebSocketService_manualDisconnect, "f")) {
|
|
838
|
+
// Manual disconnect - don't reconnect
|
|
839
|
+
log('WebSocket closed due to manual disconnect, not reconnecting');
|
|
770
840
|
return;
|
|
771
841
|
}
|
|
772
|
-
//
|
|
773
|
-
__classPrivateFieldGet(this,
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
if (shouldReconnect) {
|
|
777
|
-
log('Connection lost unexpectedly, will attempt reconnection', {
|
|
842
|
+
// Trace unexpected disconnect with details
|
|
843
|
+
__classPrivateFieldGet(this, _BackendWebSocketService_trace, "f").call(this, {
|
|
844
|
+
name: `${SERVICE_NAME} Disconnect`,
|
|
845
|
+
data: {
|
|
778
846
|
code: event.code,
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
847
|
+
reason: event.reason || getCloseReason(event.code),
|
|
848
|
+
connectionDuration_ms: connectionDuration,
|
|
849
|
+
},
|
|
850
|
+
tags: {
|
|
851
|
+
service: SERVICE_NAME,
|
|
852
|
+
disconnect_type: 'unexpected',
|
|
853
|
+
},
|
|
854
|
+
}, () => {
|
|
855
|
+
// Empty trace callback - just measuring the event
|
|
856
|
+
});
|
|
857
|
+
// For any unexpected disconnects, attempt reconnection
|
|
858
|
+
// The manualDisconnect flag is the only gate - if it's false, we reconnect
|
|
859
|
+
log('Connection lost unexpectedly, will attempt reconnection', {
|
|
860
|
+
code: event.code,
|
|
861
|
+
reason: event.reason,
|
|
862
|
+
});
|
|
863
|
+
__classPrivateFieldGet(this, _BackendWebSocketService_instances, "m", _BackendWebSocketService_scheduleReconnect).call(this);
|
|
782
864
|
}, _BackendWebSocketService_handleError = function _BackendWebSocketService_handleError(_error) {
|
|
783
865
|
// Placeholder for future error handling logic
|
|
784
866
|
}, _BackendWebSocketService_scheduleReconnect = function _BackendWebSocketService_scheduleReconnect() {
|
|
@@ -829,8 +911,5 @@ async function _BackendWebSocketService_establishConnection(bearerToken) {
|
|
|
829
911
|
// Messenger handles listener errors internally, no need for try-catch
|
|
830
912
|
__classPrivateFieldGet(this, _BackendWebSocketService_messenger, "f").publish('BackendWebSocketService:connectionStateChanged', this.getConnectionInfo());
|
|
831
913
|
}
|
|
832
|
-
}, _BackendWebSocketService_shouldReconnectOnClose = function _BackendWebSocketService_shouldReconnectOnClose(code) {
|
|
833
|
-
// Don't reconnect only on normal closure (manual disconnect)
|
|
834
|
-
return code !== 1000;
|
|
835
914
|
};
|
|
836
915
|
//# sourceMappingURL=BackendWebSocketService.mjs.map
|