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