@metamask/assets-controller 9.1.0 → 10.0.1
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 +34 -1
- package/dist/AssetsController.cjs +351 -90
- package/dist/AssetsController.cjs.map +1 -1
- package/dist/AssetsController.d.cts +11 -8
- package/dist/AssetsController.d.cts.map +1 -1
- package/dist/AssetsController.d.mts +11 -8
- package/dist/AssetsController.d.mts.map +1 -1
- package/dist/AssetsController.mjs +351 -90
- package/dist/AssetsController.mjs.map +1 -1
- package/dist/data-sources/AccountsApiDataSource.cjs +14 -1
- package/dist/data-sources/AccountsApiDataSource.cjs.map +1 -1
- package/dist/data-sources/AccountsApiDataSource.d.cts +8 -0
- package/dist/data-sources/AccountsApiDataSource.d.cts.map +1 -1
- package/dist/data-sources/AccountsApiDataSource.d.mts +8 -0
- package/dist/data-sources/AccountsApiDataSource.d.mts.map +1 -1
- package/dist/data-sources/AccountsApiDataSource.mjs +14 -1
- package/dist/data-sources/AccountsApiDataSource.mjs.map +1 -1
- package/dist/data-sources/BackendWebsocketDataSource.cjs +236 -149
- package/dist/data-sources/BackendWebsocketDataSource.cjs.map +1 -1
- package/dist/data-sources/BackendWebsocketDataSource.d.cts +15 -0
- package/dist/data-sources/BackendWebsocketDataSource.d.cts.map +1 -1
- package/dist/data-sources/BackendWebsocketDataSource.d.mts +15 -0
- package/dist/data-sources/BackendWebsocketDataSource.d.mts.map +1 -1
- package/dist/data-sources/BackendWebsocketDataSource.mjs +236 -146
- package/dist/data-sources/BackendWebsocketDataSource.mjs.map +1 -1
- package/dist/data-sources/PriceDataSource.cjs +18 -9
- package/dist/data-sources/PriceDataSource.cjs.map +1 -1
- package/dist/data-sources/PriceDataSource.d.cts.map +1 -1
- package/dist/data-sources/PriceDataSource.d.mts.map +1 -1
- package/dist/data-sources/PriceDataSource.mjs +19 -10
- package/dist/data-sources/PriceDataSource.mjs.map +1 -1
- package/dist/data-sources/RpcDataSource.cjs +29 -9
- package/dist/data-sources/RpcDataSource.cjs.map +1 -1
- package/dist/data-sources/RpcDataSource.d.cts +5 -0
- package/dist/data-sources/RpcDataSource.d.cts.map +1 -1
- package/dist/data-sources/RpcDataSource.d.mts +5 -0
- package/dist/data-sources/RpcDataSource.d.mts.map +1 -1
- package/dist/data-sources/RpcDataSource.mjs +29 -9
- package/dist/data-sources/RpcDataSource.mjs.map +1 -1
- package/dist/middlewares/DetectionMiddleware.cjs +22 -1
- package/dist/middlewares/DetectionMiddleware.cjs.map +1 -1
- package/dist/middlewares/DetectionMiddleware.d.cts +3 -0
- package/dist/middlewares/DetectionMiddleware.d.cts.map +1 -1
- package/dist/middlewares/DetectionMiddleware.d.mts +3 -0
- package/dist/middlewares/DetectionMiddleware.d.mts.map +1 -1
- package/dist/middlewares/DetectionMiddleware.mjs +22 -1
- package/dist/middlewares/DetectionMiddleware.mjs.map +1 -1
- package/dist/middlewares/ParallelMiddleware.cjs +7 -0
- package/dist/middlewares/ParallelMiddleware.cjs.map +1 -1
- package/dist/middlewares/ParallelMiddleware.d.cts.map +1 -1
- package/dist/middlewares/ParallelMiddleware.d.mts.map +1 -1
- package/dist/middlewares/ParallelMiddleware.mjs +7 -0
- package/dist/middlewares/ParallelMiddleware.mjs.map +1 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +20 -4
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.mts +20 -4
- package/dist/types.d.mts.map +1 -1
- package/dist/types.mjs.map +1 -1
- package/dist/utils/dedupingBatchFetcher.cjs +11 -0
- package/dist/utils/dedupingBatchFetcher.cjs.map +1 -1
- package/dist/utils/dedupingBatchFetcher.d.cts +7 -0
- package/dist/utils/dedupingBatchFetcher.d.cts.map +1 -1
- package/dist/utils/dedupingBatchFetcher.d.mts +7 -0
- package/dist/utils/dedupingBatchFetcher.d.mts.map +1 -1
- package/dist/utils/dedupingBatchFetcher.mjs +11 -0
- package/dist/utils/dedupingBatchFetcher.mjs.map +1 -1
- package/dist/utils/processAccountActivityBalanceUpdates.cjs +54 -0
- package/dist/utils/processAccountActivityBalanceUpdates.cjs.map +1 -0
- package/dist/utils/processAccountActivityBalanceUpdates.d.cts +13 -0
- package/dist/utils/processAccountActivityBalanceUpdates.d.cts.map +1 -0
- package/dist/utils/processAccountActivityBalanceUpdates.d.mts +13 -0
- package/dist/utils/processAccountActivityBalanceUpdates.d.mts.map +1 -0
- package/dist/utils/processAccountActivityBalanceUpdates.mjs +47 -0
- package/dist/utils/processAccountActivityBalanceUpdates.mjs.map +1 -0
- package/package.json +8 -8
|
@@ -9,10 +9,10 @@ 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 _BackendWebsocketDataSource_instances, _BackendWebsocketDataSource_messenger, _BackendWebsocketDataSource_apiClient, _BackendWebsocketDataSource_onActiveChainsUpdated, _BackendWebsocketDataSource_getAssetType, _BackendWebsocketDataSource_chainsRefreshTimer, _BackendWebsocketDataSource_supportedChains, _BackendWebsocketDataSource_isConnected, _BackendWebsocketDataSource_wsSubscriptions, _BackendWebsocketDataSource_pendingSubscriptions, _BackendWebsocketDataSource_subscriptionRequests, _BackendWebsocketDataSource_initializeActiveChains, _BackendWebsocketDataSource_refreshActiveChains, _BackendWebsocketDataSource_fetchActiveChains, _BackendWebsocketDataSource_subscribeToEvents, _BackendWebsocketDataSource_handleDisconnect, _BackendWebsocketDataSource_handleReconnect, _BackendWebsocketDataSource_handleNotification, _BackendWebsocketDataSource_processBalanceUpdates;
|
|
12
|
+
var _BackendWebsocketDataSource_instances, _BackendWebsocketDataSource_messenger, _BackendWebsocketDataSource_apiClient, _BackendWebsocketDataSource_onActiveChainsUpdated, _BackendWebsocketDataSource_getAssetType, _BackendWebsocketDataSource_chainsRefreshTimer, _BackendWebsocketDataSource_supportedChains, _BackendWebsocketDataSource_isConnected, _BackendWebsocketDataSource_wsSubscriptions, _BackendWebsocketDataSource_pendingSubscriptions, _BackendWebsocketDataSource_subscriptionRequests, _BackendWebsocketDataSource_registeredChannelCallbacks, _BackendWebsocketDataSource_subscribeLock, _BackendWebsocketDataSource_initializeActiveChains, _BackendWebsocketDataSource_refreshActiveChains, _BackendWebsocketDataSource_fetchActiveChains, _BackendWebsocketDataSource_subscribeToEvents, _BackendWebsocketDataSource_handleDisconnect, _BackendWebsocketDataSource_handleReconnect, _BackendWebsocketDataSource_subscribeInternal, _BackendWebsocketDataSource_handleNotification, _BackendWebsocketDataSource_processBalanceUpdates, _BackendWebsocketDataSource_teardownSubscription, _BackendWebsocketDataSource_registerChannelCallbacks, _BackendWebsocketDataSource_unregisterChannelCallback, _BackendWebsocketDataSource_removeChannelCallbacks;
|
|
13
13
|
import { isCaipChainId, KnownCaipNamespace, toCaipChainId } from "@metamask/utils";
|
|
14
|
-
import BigNumberJS from "bignumber.js";
|
|
15
14
|
import { projectLogger, createModuleLogger } from "../logger.mjs";
|
|
15
|
+
import { processAccountActivityBalanceUpdates } from "../utils/processAccountActivityBalanceUpdates.mjs";
|
|
16
16
|
import { AbstractDataSource } from "./AbstractDataSource.mjs";
|
|
17
17
|
// ============================================================================
|
|
18
18
|
// CONSTANTS
|
|
@@ -89,6 +89,34 @@ function buildAccountActivityChannel(namespace, address) {
|
|
|
89
89
|
const formatted = namespace === 'eip155' ? address.toLowerCase() : address;
|
|
90
90
|
return `${CHANNEL_TYPE}.${namespace}:0:${formatted}`;
|
|
91
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Normalize addresses for stable comparison when detecting account changes.
|
|
94
|
+
*
|
|
95
|
+
* @param address - Account address (hex or base58).
|
|
96
|
+
* @returns Normalized address for comparison.
|
|
97
|
+
*/
|
|
98
|
+
function normalizeAddressForComparison(address) {
|
|
99
|
+
return address.startsWith('0x') ? address.toLowerCase() : address;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Check whether subscribed account addresses changed (case-insensitive for EVM).
|
|
103
|
+
*
|
|
104
|
+
* @param nextAddresses - Addresses from the incoming subscribe request.
|
|
105
|
+
* @param existingAddresses - Addresses from the active subscription.
|
|
106
|
+
* @returns True when the address sets differ.
|
|
107
|
+
*/
|
|
108
|
+
function haveAddressesChanged(nextAddresses, existingAddresses) {
|
|
109
|
+
if (nextAddresses.length !== existingAddresses.length) {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
const normalizedNext = nextAddresses
|
|
113
|
+
.map(normalizeAddressForComparison)
|
|
114
|
+
.sort();
|
|
115
|
+
const normalizedExisting = existingAddresses
|
|
116
|
+
.map(normalizeAddressForComparison)
|
|
117
|
+
.sort();
|
|
118
|
+
return normalizedNext.some((address, index) => address !== normalizedExisting[index]);
|
|
119
|
+
}
|
|
92
120
|
/**
|
|
93
121
|
* Normalize API chain identifier to CAIP-2 ChainId.
|
|
94
122
|
* Passes through strings already in CAIP-2 form (e.g. eip155:1, solana:5eykt...).
|
|
@@ -136,6 +164,8 @@ function toChainId(chainIdOrDecimal) {
|
|
|
136
164
|
* - BackendWebSocketService:subscribe
|
|
137
165
|
* - BackendWebSocketService:getConnectionInfo
|
|
138
166
|
* - BackendWebSocketService:findSubscriptionsByChannelPrefix
|
|
167
|
+
* - BackendWebSocketService:addChannelCallback
|
|
168
|
+
* - BackendWebSocketService:removeChannelCallback
|
|
139
169
|
*/
|
|
140
170
|
const DEFAULT_CHAINS_REFRESH_INTERVAL_MS = 20 * 60 * 1000; // 20 minutes
|
|
141
171
|
export class BackendWebsocketDataSource extends AbstractDataSource {
|
|
@@ -161,6 +191,10 @@ export class BackendWebsocketDataSource extends AbstractDataSource {
|
|
|
161
191
|
_BackendWebsocketDataSource_pendingSubscriptions.set(this, new Map());
|
|
162
192
|
/** Store original subscription requests for reconnection */
|
|
163
193
|
_BackendWebsocketDataSource_subscriptionRequests.set(this, new Map());
|
|
194
|
+
/** Channels with registered BackendWebSocketService channel callbacks */
|
|
195
|
+
_BackendWebsocketDataSource_registeredChannelCallbacks.set(this, new Set());
|
|
196
|
+
/** Serializes subscribe/unsubscribe so account switches cannot interleave. */
|
|
197
|
+
_BackendWebsocketDataSource_subscribeLock.set(this, Promise.resolve());
|
|
164
198
|
__classPrivateFieldSet(this, _BackendWebsocketDataSource_messenger, options.messenger, "f");
|
|
165
199
|
__classPrivateFieldSet(this, _BackendWebsocketDataSource_apiClient, options.queryApiClient, "f");
|
|
166
200
|
__classPrivateFieldSet(this, _BackendWebsocketDataSource_onActiveChainsUpdated, options.onActiveChainsUpdated, "f");
|
|
@@ -168,6 +202,16 @@ export class BackendWebsocketDataSource extends AbstractDataSource {
|
|
|
168
202
|
__classPrivateFieldGet(this, _BackendWebsocketDataSource_instances, "m", _BackendWebsocketDataSource_subscribeToEvents).call(this);
|
|
169
203
|
__classPrivateFieldGet(this, _BackendWebsocketDataSource_instances, "m", _BackendWebsocketDataSource_initializeActiveChains).call(this).catch(console.error);
|
|
170
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* Re-fetch supported networks and refresh `activeChains` when connected.
|
|
207
|
+
* When disconnected, only `#supportedChains` is updated so reconnect can
|
|
208
|
+
* reclaim chains. Called on EVM network switch from AssetsController.
|
|
209
|
+
*
|
|
210
|
+
* @returns Resolves when supported networks have been re-fetched.
|
|
211
|
+
*/
|
|
212
|
+
refreshActiveChains() {
|
|
213
|
+
return __classPrivateFieldGet(this, _BackendWebsocketDataSource_instances, "m", _BackendWebsocketDataSource_refreshActiveChains).call(this);
|
|
214
|
+
}
|
|
171
215
|
/**
|
|
172
216
|
* Sync active chains from AccountsApiDataSource.
|
|
173
217
|
* When the data source invokes the onActiveChainsUpdated callback, the
|
|
@@ -194,96 +238,40 @@ export class BackendWebsocketDataSource extends AbstractDataSource {
|
|
|
194
238
|
// SUBSCRIBE
|
|
195
239
|
// ============================================================================
|
|
196
240
|
async subscribe(subscriptionRequest) {
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
// Check WebSocket connection status
|
|
241
|
+
const previousLock = __classPrivateFieldGet(this, _BackendWebsocketDataSource_subscribeLock, "f");
|
|
242
|
+
let releaseLock = () => undefined;
|
|
243
|
+
__classPrivateFieldSet(this, _BackendWebsocketDataSource_subscribeLock, new Promise((resolve) => {
|
|
244
|
+
releaseLock = resolve;
|
|
245
|
+
}), "f");
|
|
246
|
+
await previousLock;
|
|
205
247
|
try {
|
|
206
|
-
|
|
207
|
-
if (connectionInfo.state !== 'connected') {
|
|
208
|
-
// Store the subscription request to process when WebSocket connects
|
|
209
|
-
__classPrivateFieldGet(this, _BackendWebsocketDataSource_pendingSubscriptions, "f").set(subscriptionId, subscriptionRequest);
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
catch {
|
|
214
|
-
// Store anyway - will be processed when we can connect
|
|
215
|
-
__classPrivateFieldGet(this, _BackendWebsocketDataSource_pendingSubscriptions, "f").set(subscriptionId, subscriptionRequest);
|
|
216
|
-
return;
|
|
248
|
+
await __classPrivateFieldGet(this, _BackendWebsocketDataSource_instances, "m", _BackendWebsocketDataSource_subscribeInternal).call(this, subscriptionRequest);
|
|
217
249
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
// Handle subscription update
|
|
221
|
-
if (isUpdate) {
|
|
222
|
-
const existing = this.activeSubscriptions.get(subscriptionId);
|
|
223
|
-
if (existing) {
|
|
224
|
-
// Check if accounts changed - if so, we need to re-subscribe to different channels
|
|
225
|
-
const existingAddresses = existing.addresses ?? [];
|
|
226
|
-
const addressesChanged = addresses.length !== existingAddresses.length ||
|
|
227
|
-
addresses.some((addr) => !existingAddresses.includes(addr));
|
|
228
|
-
if (!addressesChanged) {
|
|
229
|
-
// Only chains changed - just update chains and return
|
|
230
|
-
existing.chains = chainsToSubscribe;
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
// Accounts changed - fall through to re-subscribe with new channels
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
// Clean up existing subscription if any
|
|
237
|
-
await this.unsubscribe(subscriptionId);
|
|
238
|
-
// Always subscribe to eip155 and solana account activity, plus any namespaces from requested chains
|
|
239
|
-
const namespaces = getNamespacesForAccountActivity(chainsToSubscribe);
|
|
240
|
-
// Build channel names: use namespace-appropriate address per account (eip155 = hex, solana = base58)
|
|
241
|
-
const channels = [];
|
|
242
|
-
for (const namespace of namespaces) {
|
|
243
|
-
for (const { account } of request.accountsWithSupportedChains) {
|
|
244
|
-
const address = getAddressForAccountActivity(account, namespace);
|
|
245
|
-
if (address) {
|
|
246
|
-
channels.push(buildAccountActivityChannel(namespace, address));
|
|
247
|
-
}
|
|
248
|
-
}
|
|
250
|
+
finally {
|
|
251
|
+
releaseLock();
|
|
249
252
|
}
|
|
253
|
+
}
|
|
254
|
+
// ============================================================================
|
|
255
|
+
// UNSUBSCRIBE
|
|
256
|
+
// ============================================================================
|
|
257
|
+
/**
|
|
258
|
+
* Unsubscribe and await server-side teardown so a re-subscribe does not race
|
|
259
|
+
* with stale subscription IDs on incoming notifications.
|
|
260
|
+
*
|
|
261
|
+
* @param subscriptionId - The ID of the subscription to cancel.
|
|
262
|
+
*/
|
|
263
|
+
async unsubscribe(subscriptionId) {
|
|
264
|
+
const previousLock = __classPrivateFieldGet(this, _BackendWebsocketDataSource_subscribeLock, "f");
|
|
265
|
+
let releaseLock = () => undefined;
|
|
266
|
+
__classPrivateFieldSet(this, _BackendWebsocketDataSource_subscribeLock, new Promise((resolve) => {
|
|
267
|
+
releaseLock = resolve;
|
|
268
|
+
}), "f");
|
|
269
|
+
await previousLock;
|
|
250
270
|
try {
|
|
251
|
-
|
|
252
|
-
const wsSubscription = await __classPrivateFieldGet(this, _BackendWebsocketDataSource_messenger, "f").call('BackendWebSocketService:subscribe', {
|
|
253
|
-
channels,
|
|
254
|
-
channelType: CHANNEL_TYPE,
|
|
255
|
-
callback: (notification) => {
|
|
256
|
-
__classPrivateFieldGet(this, _BackendWebsocketDataSource_instances, "m", _BackendWebsocketDataSource_handleNotification).call(this, notification, subscriptionId);
|
|
257
|
-
},
|
|
258
|
-
});
|
|
259
|
-
// Store WebSocket subscription
|
|
260
|
-
__classPrivateFieldGet(this, _BackendWebsocketDataSource_wsSubscriptions, "f").set(subscriptionId, wsSubscription);
|
|
261
|
-
// Store in abstract class tracking
|
|
262
|
-
this.activeSubscriptions.set(subscriptionId, {
|
|
263
|
-
cleanup: () => {
|
|
264
|
-
const wsSub = __classPrivateFieldGet(this, _BackendWebsocketDataSource_wsSubscriptions, "f").get(subscriptionId);
|
|
265
|
-
if (wsSub) {
|
|
266
|
-
wsSub.unsubscribe().catch((unsubErr) => {
|
|
267
|
-
log('Error unsubscribing', { subscriptionId, error: unsubErr });
|
|
268
|
-
});
|
|
269
|
-
__classPrivateFieldGet(this, _BackendWebsocketDataSource_wsSubscriptions, "f").delete(subscriptionId);
|
|
270
|
-
}
|
|
271
|
-
// Also clean up the stored request
|
|
272
|
-
__classPrivateFieldGet(this, _BackendWebsocketDataSource_subscriptionRequests, "f").delete(subscriptionId);
|
|
273
|
-
},
|
|
274
|
-
chains: chainsToSubscribe,
|
|
275
|
-
addresses,
|
|
276
|
-
onAssetsUpdate: subscriptionRequest.onAssetsUpdate,
|
|
277
|
-
});
|
|
278
|
-
// Store original request for reconnection
|
|
279
|
-
__classPrivateFieldGet(this, _BackendWebsocketDataSource_subscriptionRequests, "f").set(subscriptionId, subscriptionRequest);
|
|
271
|
+
await __classPrivateFieldGet(this, _BackendWebsocketDataSource_instances, "m", _BackendWebsocketDataSource_teardownSubscription).call(this, subscriptionId);
|
|
280
272
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
subscriptionId,
|
|
284
|
-
error,
|
|
285
|
-
chains: chainsToSubscribe,
|
|
286
|
-
});
|
|
273
|
+
finally {
|
|
274
|
+
releaseLock();
|
|
287
275
|
}
|
|
288
276
|
}
|
|
289
277
|
// ============================================================================
|
|
@@ -294,26 +282,20 @@ export class BackendWebsocketDataSource extends AbstractDataSource {
|
|
|
294
282
|
clearInterval(__classPrivateFieldGet(this, _BackendWebsocketDataSource_chainsRefreshTimer, "f"));
|
|
295
283
|
__classPrivateFieldSet(this, _BackendWebsocketDataSource_chainsRefreshTimer, null, "f");
|
|
296
284
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
catch {
|
|
308
|
-
// Ignore errors during cleanup
|
|
309
|
-
}
|
|
285
|
+
const subscriptionIds = [
|
|
286
|
+
...new Set([
|
|
287
|
+
...__classPrivateFieldGet(this, _BackendWebsocketDataSource_wsSubscriptions, "f").keys(),
|
|
288
|
+
...this.activeSubscriptions.keys(),
|
|
289
|
+
]),
|
|
290
|
+
];
|
|
291
|
+
for (const subscriptionId of subscriptionIds) {
|
|
292
|
+
__classPrivateFieldGet(this, _BackendWebsocketDataSource_instances, "m", _BackendWebsocketDataSource_teardownSubscription).call(this, subscriptionId).catch(() => undefined);
|
|
310
293
|
}
|
|
311
|
-
|
|
312
|
-
// Clean up base class subscriptions
|
|
294
|
+
// Clean up base class subscriptions (no-op if already torn down)
|
|
313
295
|
super.destroy();
|
|
314
296
|
}
|
|
315
297
|
}
|
|
316
|
-
_BackendWebsocketDataSource_messenger = new WeakMap(), _BackendWebsocketDataSource_apiClient = new WeakMap(), _BackendWebsocketDataSource_onActiveChainsUpdated = new WeakMap(), _BackendWebsocketDataSource_getAssetType = new WeakMap(), _BackendWebsocketDataSource_chainsRefreshTimer = new WeakMap(), _BackendWebsocketDataSource_supportedChains = new WeakMap(), _BackendWebsocketDataSource_isConnected = new WeakMap(), _BackendWebsocketDataSource_wsSubscriptions = new WeakMap(), _BackendWebsocketDataSource_pendingSubscriptions = new WeakMap(), _BackendWebsocketDataSource_subscriptionRequests = new WeakMap(), _BackendWebsocketDataSource_instances = new WeakSet(), _BackendWebsocketDataSource_initializeActiveChains =
|
|
298
|
+
_BackendWebsocketDataSource_messenger = new WeakMap(), _BackendWebsocketDataSource_apiClient = new WeakMap(), _BackendWebsocketDataSource_onActiveChainsUpdated = new WeakMap(), _BackendWebsocketDataSource_getAssetType = new WeakMap(), _BackendWebsocketDataSource_chainsRefreshTimer = new WeakMap(), _BackendWebsocketDataSource_supportedChains = new WeakMap(), _BackendWebsocketDataSource_isConnected = new WeakMap(), _BackendWebsocketDataSource_wsSubscriptions = new WeakMap(), _BackendWebsocketDataSource_pendingSubscriptions = new WeakMap(), _BackendWebsocketDataSource_subscriptionRequests = new WeakMap(), _BackendWebsocketDataSource_registeredChannelCallbacks = new WeakMap(), _BackendWebsocketDataSource_subscribeLock = new WeakMap(), _BackendWebsocketDataSource_instances = new WeakSet(), _BackendWebsocketDataSource_initializeActiveChains =
|
|
317
299
|
// ============================================================================
|
|
318
300
|
// INITIALIZATION
|
|
319
301
|
// ============================================================================
|
|
@@ -412,14 +394,111 @@ async function _BackendWebsocketDataSource_initializeActiveChains() {
|
|
|
412
394
|
const previous = [...this.state.activeChains];
|
|
413
395
|
this.updateActiveChains(__classPrivateFieldGet(this, _BackendWebsocketDataSource_supportedChains, "f"), (updatedChains) => __classPrivateFieldGet(this, _BackendWebsocketDataSource_onActiveChainsUpdated, "f").call(this, this.getName(), updatedChains, previous));
|
|
414
396
|
}
|
|
397
|
+
}, _BackendWebsocketDataSource_subscribeInternal = async function _BackendWebsocketDataSource_subscribeInternal(subscriptionRequest) {
|
|
398
|
+
const { request, subscriptionId, isUpdate } = subscriptionRequest;
|
|
399
|
+
// Filter to active chains only
|
|
400
|
+
const chainsToSubscribe = request.chainIds.filter((chainId) => this.state.activeChains.includes(chainId));
|
|
401
|
+
const addresses = request.accountsWithSupportedChains.map((a) => a.account.address);
|
|
402
|
+
if (addresses.length === 0) {
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
// Check WebSocket connection status
|
|
406
|
+
try {
|
|
407
|
+
const connectionInfo = __classPrivateFieldGet(this, _BackendWebsocketDataSource_messenger, "f").call('BackendWebSocketService:getConnectionInfo');
|
|
408
|
+
if (connectionInfo.state !== 'connected') {
|
|
409
|
+
// Store the subscription request to process when WebSocket connects
|
|
410
|
+
__classPrivateFieldGet(this, _BackendWebsocketDataSource_pendingSubscriptions, "f").set(subscriptionId, subscriptionRequest);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
catch {
|
|
415
|
+
// Store anyway - will be processed when we can connect
|
|
416
|
+
__classPrivateFieldGet(this, _BackendWebsocketDataSource_pendingSubscriptions, "f").set(subscriptionId, subscriptionRequest);
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
// Remove from pending if it was there (we're processing it now)
|
|
420
|
+
__classPrivateFieldGet(this, _BackendWebsocketDataSource_pendingSubscriptions, "f").delete(subscriptionId);
|
|
421
|
+
// Handle subscription update
|
|
422
|
+
if (isUpdate) {
|
|
423
|
+
const existing = this.activeSubscriptions.get(subscriptionId);
|
|
424
|
+
if (existing) {
|
|
425
|
+
// Check if accounts changed - if so, we need to re-subscribe to different channels
|
|
426
|
+
const existingAddresses = existing.addresses ?? [];
|
|
427
|
+
const addressesChanged = haveAddressesChanged(addresses, existingAddresses);
|
|
428
|
+
if (!addressesChanged) {
|
|
429
|
+
// Only chains changed - update chains, request, and callback
|
|
430
|
+
existing.chains = chainsToSubscribe;
|
|
431
|
+
existing.onAssetsUpdate = subscriptionRequest.onAssetsUpdate;
|
|
432
|
+
__classPrivateFieldGet(this, _BackendWebsocketDataSource_subscriptionRequests, "f").set(subscriptionId, subscriptionRequest);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
// Accounts changed - fall through to re-subscribe with new channels
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
// Clean up existing subscription if any (inline teardown — subscribe holds the lock)
|
|
439
|
+
await __classPrivateFieldGet(this, _BackendWebsocketDataSource_instances, "m", _BackendWebsocketDataSource_teardownSubscription).call(this, subscriptionId);
|
|
440
|
+
// Always subscribe to eip155 and solana account activity, plus any namespaces from requested chains
|
|
441
|
+
const namespaces = getNamespacesForAccountActivity(chainsToSubscribe);
|
|
442
|
+
// Build channel names: use namespace-appropriate address per account (eip155 = hex, solana = base58)
|
|
443
|
+
const channels = [];
|
|
444
|
+
for (const namespace of namespaces) {
|
|
445
|
+
for (const { account } of request.accountsWithSupportedChains) {
|
|
446
|
+
const address = getAddressForAccountActivity(account, namespace);
|
|
447
|
+
if (address) {
|
|
448
|
+
channels.push(buildAccountActivityChannel(namespace, address));
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
if (channels.length === 0) {
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
try {
|
|
456
|
+
// Register request/callback before awaiting server subscribe so notifications
|
|
457
|
+
// that arrive during the subscribe handshake are not dropped.
|
|
458
|
+
__classPrivateFieldGet(this, _BackendWebsocketDataSource_subscriptionRequests, "f").set(subscriptionId, subscriptionRequest);
|
|
459
|
+
this.activeSubscriptions.set(subscriptionId, {
|
|
460
|
+
cleanup: () => {
|
|
461
|
+
__classPrivateFieldGet(this, _BackendWebsocketDataSource_instances, "m", _BackendWebsocketDataSource_teardownSubscription).call(this, subscriptionId).catch(() => undefined);
|
|
462
|
+
},
|
|
463
|
+
chains: chainsToSubscribe,
|
|
464
|
+
addresses,
|
|
465
|
+
onAssetsUpdate: subscriptionRequest.onAssetsUpdate,
|
|
466
|
+
});
|
|
467
|
+
// Create WebSocket subscription
|
|
468
|
+
const wsSubscription = await __classPrivateFieldGet(this, _BackendWebsocketDataSource_messenger, "f").call('BackendWebSocketService:subscribe', {
|
|
469
|
+
channels,
|
|
470
|
+
channelType: CHANNEL_TYPE,
|
|
471
|
+
callback: (notification) => {
|
|
472
|
+
__classPrivateFieldGet(this, _BackendWebsocketDataSource_instances, "m", _BackendWebsocketDataSource_handleNotification).call(this, notification, subscriptionId);
|
|
473
|
+
},
|
|
474
|
+
});
|
|
475
|
+
__classPrivateFieldGet(this, _BackendWebsocketDataSource_wsSubscriptions, "f").set(subscriptionId, wsSubscription);
|
|
476
|
+
try {
|
|
477
|
+
__classPrivateFieldGet(this, _BackendWebsocketDataSource_instances, "m", _BackendWebsocketDataSource_registerChannelCallbacks).call(this, subscriptionId, channels);
|
|
478
|
+
}
|
|
479
|
+
catch (channelCallbackError) {
|
|
480
|
+
log('Channel callback registration failed; ws subscription still active', { subscriptionId, error: channelCallbackError });
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
catch (error) {
|
|
484
|
+
this.activeSubscriptions.delete(subscriptionId);
|
|
485
|
+
__classPrivateFieldGet(this, _BackendWebsocketDataSource_subscriptionRequests, "f").delete(subscriptionId);
|
|
486
|
+
log('WebSocket subscription FAILED', {
|
|
487
|
+
subscriptionId,
|
|
488
|
+
error,
|
|
489
|
+
chains: chainsToSubscribe,
|
|
490
|
+
});
|
|
491
|
+
}
|
|
415
492
|
}, _BackendWebsocketDataSource_handleNotification = function _BackendWebsocketDataSource_handleNotification(notification, subscriptionId) {
|
|
416
493
|
try {
|
|
417
|
-
const
|
|
418
|
-
const
|
|
494
|
+
const activityMessage = notification.data;
|
|
495
|
+
const storedSubscription = __classPrivateFieldGet(this, _BackendWebsocketDataSource_subscriptionRequests, "f").get(subscriptionId);
|
|
496
|
+
const request = storedSubscription?.request;
|
|
497
|
+
const onAssetsUpdate = this.activeSubscriptions.get(subscriptionId)?.onAssetsUpdate ??
|
|
498
|
+
storedSubscription?.onAssetsUpdate;
|
|
419
499
|
if (!request) {
|
|
420
500
|
return;
|
|
421
501
|
}
|
|
422
|
-
const activityMessage = notification.data;
|
|
423
502
|
const { address, tx, updates } = activityMessage;
|
|
424
503
|
if (!address || !tx || !updates) {
|
|
425
504
|
return;
|
|
@@ -438,54 +517,65 @@ async function _BackendWebsocketDataSource_initializeActiveChains() {
|
|
|
438
517
|
const accountId = account.id;
|
|
439
518
|
// Process all balance updates from the activity message
|
|
440
519
|
const response = __classPrivateFieldGet(this, _BackendWebsocketDataSource_instances, "m", _BackendWebsocketDataSource_processBalanceUpdates).call(this, updates, chainId, accountId);
|
|
441
|
-
|
|
442
|
-
|
|
520
|
+
const balanceEntries = response.assetsBalance?.[accountId] ?? {};
|
|
521
|
+
const hasBalances = Object.keys(balanceEntries).length > 0;
|
|
522
|
+
if (hasBalances && onAssetsUpdate) {
|
|
523
|
+
Promise.resolve(onAssetsUpdate(response, request)).catch((error) => {
|
|
524
|
+
console.error(error);
|
|
525
|
+
});
|
|
443
526
|
}
|
|
444
527
|
}
|
|
445
528
|
catch (error) {
|
|
446
529
|
log('Error handling notification', error);
|
|
447
530
|
}
|
|
448
531
|
}, _BackendWebsocketDataSource_processBalanceUpdates = function _BackendWebsocketDataSource_processBalanceUpdates(updates, _chainId, accountId) {
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
532
|
+
return processAccountActivityBalanceUpdates(updates, accountId, (assetId) => __classPrivateFieldGet(this, _BackendWebsocketDataSource_getAssetType, "f").call(this, assetId));
|
|
533
|
+
}, _BackendWebsocketDataSource_teardownSubscription = async function _BackendWebsocketDataSource_teardownSubscription(subscriptionId) {
|
|
534
|
+
const wsSub = __classPrivateFieldGet(this, _BackendWebsocketDataSource_wsSubscriptions, "f").get(subscriptionId);
|
|
535
|
+
if (wsSub) {
|
|
536
|
+
const channels = [...wsSub.channels];
|
|
537
|
+
try {
|
|
538
|
+
await wsSub.unsubscribe();
|
|
539
|
+
}
|
|
540
|
+
catch (unsubErr) {
|
|
541
|
+
log('Error unsubscribing', { subscriptionId, error: unsubErr });
|
|
542
|
+
}
|
|
543
|
+
__classPrivateFieldGet(this, _BackendWebsocketDataSource_wsSubscriptions, "f").delete(subscriptionId);
|
|
544
|
+
__classPrivateFieldGet(this, _BackendWebsocketDataSource_instances, "m", _BackendWebsocketDataSource_removeChannelCallbacks).call(this, channels);
|
|
545
|
+
}
|
|
546
|
+
__classPrivateFieldGet(this, _BackendWebsocketDataSource_subscriptionRequests, "f").delete(subscriptionId);
|
|
547
|
+
this.activeSubscriptions.delete(subscriptionId);
|
|
548
|
+
}, _BackendWebsocketDataSource_registerChannelCallbacks = function _BackendWebsocketDataSource_registerChannelCallbacks(subscriptionId, channels) {
|
|
549
|
+
for (const channel of channels) {
|
|
550
|
+
__classPrivateFieldGet(this, _BackendWebsocketDataSource_instances, "m", _BackendWebsocketDataSource_unregisterChannelCallback).call(this, channel);
|
|
551
|
+
try {
|
|
552
|
+
__classPrivateFieldGet(this, _BackendWebsocketDataSource_messenger, "f").call('BackendWebSocketService:addChannelCallback', {
|
|
553
|
+
channelName: channel,
|
|
554
|
+
callback: (notification) => {
|
|
555
|
+
__classPrivateFieldGet(this, _BackendWebsocketDataSource_instances, "m", _BackendWebsocketDataSource_handleNotification).call(this, notification, subscriptionId);
|
|
556
|
+
},
|
|
557
|
+
});
|
|
558
|
+
__classPrivateFieldGet(this, _BackendWebsocketDataSource_registeredChannelCallbacks, "f").add(channel);
|
|
457
559
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
const assetId = asset.type;
|
|
461
|
-
const tokenType = __classPrivateFieldGet(this, _BackendWebsocketDataSource_getAssetType, "f").call(this, assetId);
|
|
462
|
-
// We assume decimals are always present; skip malformed updates
|
|
463
|
-
if (asset.decimals === undefined) {
|
|
464
|
-
continue;
|
|
560
|
+
catch {
|
|
561
|
+
// Channel callbacks are optional; ws subscription still works without them.
|
|
465
562
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
}
|
|
483
|
-
const response = { updateMode: 'merge' };
|
|
484
|
-
if (Object.keys(assetsBalance[accountId]).length > 0) {
|
|
485
|
-
response.assetsBalance = assetsBalance;
|
|
486
|
-
response.assetsInfo = assetsMetadata;
|
|
487
|
-
}
|
|
488
|
-
return response;
|
|
563
|
+
}
|
|
564
|
+
}, _BackendWebsocketDataSource_unregisterChannelCallback = function _BackendWebsocketDataSource_unregisterChannelCallback(channel) {
|
|
565
|
+
if (!__classPrivateFieldGet(this, _BackendWebsocketDataSource_registeredChannelCallbacks, "f").has(channel)) {
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
try {
|
|
569
|
+
__classPrivateFieldGet(this, _BackendWebsocketDataSource_messenger, "f").call('BackendWebSocketService:removeChannelCallback', channel);
|
|
570
|
+
}
|
|
571
|
+
catch {
|
|
572
|
+
// Best-effort cleanup when the channel callback was never registered.
|
|
573
|
+
}
|
|
574
|
+
__classPrivateFieldGet(this, _BackendWebsocketDataSource_registeredChannelCallbacks, "f").delete(channel);
|
|
575
|
+
}, _BackendWebsocketDataSource_removeChannelCallbacks = function _BackendWebsocketDataSource_removeChannelCallbacks(channels) {
|
|
576
|
+
for (const channel of channels) {
|
|
577
|
+
__classPrivateFieldGet(this, _BackendWebsocketDataSource_instances, "m", _BackendWebsocketDataSource_unregisterChannelCallback).call(this, channel);
|
|
578
|
+
}
|
|
489
579
|
};
|
|
490
580
|
// ============================================================================
|
|
491
581
|
// FACTORY FUNCTION
|