@metamask-previews/assets-controller 11.2.1-preview-2dc3d26a4 → 11.2.1-preview-aa202ec10
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 +5 -6
- package/dist/AssetsController.cjs +51 -23
- package/dist/AssetsController.cjs.map +1 -1
- package/dist/AssetsController.d.cts +3 -3
- package/dist/AssetsController.d.cts.map +1 -1
- package/dist/AssetsController.d.mts +3 -3
- package/dist/AssetsController.d.mts.map +1 -1
- package/dist/AssetsController.mjs +51 -23
- package/dist/AssetsController.mjs.map +1 -1
- package/dist/data-sources/BackendWebsocketDataSource.cjs +597 -0
- package/dist/data-sources/BackendWebsocketDataSource.cjs.map +1 -0
- package/dist/data-sources/BackendWebsocketDataSource.d.cts +65 -0
- package/dist/data-sources/BackendWebsocketDataSource.d.cts.map +1 -0
- package/dist/data-sources/BackendWebsocketDataSource.d.mts +65 -0
- package/dist/data-sources/BackendWebsocketDataSource.d.mts.map +1 -0
- package/dist/data-sources/BackendWebsocketDataSource.mjs +592 -0
- package/dist/data-sources/BackendWebsocketDataSource.mjs.map +1 -0
- package/dist/data-sources/index.cjs +4 -4
- package/dist/data-sources/index.cjs.map +1 -1
- package/dist/data-sources/index.d.cts +1 -1
- package/dist/data-sources/index.d.cts.map +1 -1
- package/dist/data-sources/index.d.mts +1 -1
- package/dist/data-sources/index.d.mts.map +1 -1
- package/dist/data-sources/index.mjs +1 -1
- package/dist/data-sources/index.mjs.map +1 -1
- package/dist/index.cjs +26 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +2 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -1
- package/dist/middlewares/CustomAssetGraduationMiddleware.cjs +1 -1
- package/dist/middlewares/CustomAssetGraduationMiddleware.cjs.map +1 -1
- package/dist/middlewares/CustomAssetGraduationMiddleware.mjs +1 -1
- package/dist/middlewares/CustomAssetGraduationMiddleware.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 +1 -1
- package/dist/data-sources/AccountActivityDataSource.cjs +0 -262
- package/dist/data-sources/AccountActivityDataSource.cjs.map +0 -1
- package/dist/data-sources/AccountActivityDataSource.d.cts +0 -80
- package/dist/data-sources/AccountActivityDataSource.d.cts.map +0 -1
- package/dist/data-sources/AccountActivityDataSource.d.mts +0 -80
- package/dist/data-sources/AccountActivityDataSource.d.mts.map +0 -1
- package/dist/data-sources/AccountActivityDataSource.mjs +0 -254
- package/dist/data-sources/AccountActivityDataSource.mjs.map +0 -1
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
2
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
4
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
5
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
6
|
+
};
|
|
7
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
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
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
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_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
|
+
import { isCaipChainId, KnownCaipNamespace, toCaipChainId } from "@metamask/utils";
|
|
14
|
+
import { projectLogger, createModuleLogger } from "../logger.mjs";
|
|
15
|
+
import { processAccountActivityBalanceUpdates } from "../utils/processAccountActivityBalanceUpdates.mjs";
|
|
16
|
+
import { AbstractDataSource } from "./AbstractDataSource.mjs";
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// CONSTANTS
|
|
19
|
+
// ============================================================================
|
|
20
|
+
const CONTROLLER_NAME = 'BackendWebsocketDataSource';
|
|
21
|
+
const CHANNEL_TYPE = 'account-activity.v1';
|
|
22
|
+
const log = createModuleLogger(projectLogger, CONTROLLER_NAME);
|
|
23
|
+
const defaultState = {
|
|
24
|
+
activeChains: [],
|
|
25
|
+
};
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// HELPER FUNCTIONS
|
|
28
|
+
// ============================================================================
|
|
29
|
+
/**
|
|
30
|
+
* Extract namespace from a CAIP-2 chain ID.
|
|
31
|
+
* E.g., "eip155:1" -> "eip155", "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" -> "solana"
|
|
32
|
+
*
|
|
33
|
+
* @param chainId - The CAIP-2 chain ID to extract namespace from.
|
|
34
|
+
* @returns The namespace portion of the chain ID.
|
|
35
|
+
*/
|
|
36
|
+
function extractNamespace(chainId) {
|
|
37
|
+
const [namespace] = chainId.split(':');
|
|
38
|
+
return namespace;
|
|
39
|
+
}
|
|
40
|
+
/** Namespaces we always subscribe to for account activity (EVM + Solana). */
|
|
41
|
+
const ACCOUNT_ACTIVITY_NAMESPACES = ['eip155', 'solana'];
|
|
42
|
+
/**
|
|
43
|
+
* Get unique namespaces for account-activity subscriptions.
|
|
44
|
+
* Always includes eip155 and solana so we subscribe to both EVM and Solana account activity,
|
|
45
|
+
* plus any additional namespaces from the requested chain IDs.
|
|
46
|
+
*
|
|
47
|
+
* @param chainIds - Array of CAIP-2 chain IDs (from the subscription request).
|
|
48
|
+
* @returns Array of unique namespaces (at least eip155 and solana).
|
|
49
|
+
*/
|
|
50
|
+
function getNamespacesForAccountActivity(chainIds) {
|
|
51
|
+
const namespaces = new Set(ACCOUNT_ACTIVITY_NAMESPACES);
|
|
52
|
+
for (const chainId of chainIds) {
|
|
53
|
+
namespaces.add(extractNamespace(chainId));
|
|
54
|
+
}
|
|
55
|
+
return Array.from(namespaces);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Returns the address to use for account-activity subscription in the given namespace.
|
|
59
|
+
* EIP-155 accounts use hex (0x...) address; Solana accounts use base58.
|
|
60
|
+
* Returns null if this account type does not have an address in that namespace.
|
|
61
|
+
*
|
|
62
|
+
* @param account - Internal account (type + address).
|
|
63
|
+
* @param account.type - Account type (e.g. "eip155:eoa", "solana:data-account").
|
|
64
|
+
* @param account.address - Account address (hex for eip155, base58 for solana).
|
|
65
|
+
* @param namespace - The chain namespace (e.g., "eip155", "solana").
|
|
66
|
+
* @returns The address for that namespace, or null if the account does not support the namespace.
|
|
67
|
+
*/
|
|
68
|
+
function getAddressForAccountActivity(account, namespace) {
|
|
69
|
+
if (namespace === 'eip155') {
|
|
70
|
+
return account.type.startsWith('eip155') ? account.address : null;
|
|
71
|
+
}
|
|
72
|
+
if (namespace === 'solana') {
|
|
73
|
+
return account.type.startsWith('solana') ? account.address : null;
|
|
74
|
+
}
|
|
75
|
+
// Other namespaces (e.g. from chainIds): use address if account type matches namespace
|
|
76
|
+
const typePrefix = `${namespace}:`;
|
|
77
|
+
return account.type.startsWith(typePrefix) ? account.address : null;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Build WebSocket channel name for account activity using CAIP-10 wildcard format.
|
|
81
|
+
* Uses 0 as the chain reference to subscribe to all chains in the namespace.
|
|
82
|
+
* EIP-155 addresses are lowercased (hex); Solana addresses are left as-is (base58).
|
|
83
|
+
*
|
|
84
|
+
* @param namespace - The chain namespace (e.g., "eip155", "solana").
|
|
85
|
+
* @param address - The account address (hex for eip155, base58 for solana).
|
|
86
|
+
* @returns The WebSocket channel name.
|
|
87
|
+
*/
|
|
88
|
+
function buildAccountActivityChannel(namespace, address) {
|
|
89
|
+
const formatted = namespace === 'eip155' ? address.toLowerCase() : address;
|
|
90
|
+
return `${CHANNEL_TYPE}.${namespace}:0:${formatted}`;
|
|
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
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Normalize API chain identifier to CAIP-2 ChainId.
|
|
122
|
+
* Passes through strings already in CAIP-2 form (e.g. eip155:1, solana:5eykt...).
|
|
123
|
+
* Converts bare decimals to eip155:decimal.
|
|
124
|
+
* Uses @metamask/utils for CAIP parsing.
|
|
125
|
+
*
|
|
126
|
+
* @param chainIdOrDecimal - Chain ID string (CAIP-2 or decimal) or decimal number.
|
|
127
|
+
* @returns CAIP-2 ChainId.
|
|
128
|
+
*/
|
|
129
|
+
function toChainId(chainIdOrDecimal) {
|
|
130
|
+
if (typeof chainIdOrDecimal === 'string') {
|
|
131
|
+
if (isCaipChainId(chainIdOrDecimal)) {
|
|
132
|
+
return chainIdOrDecimal;
|
|
133
|
+
}
|
|
134
|
+
return toCaipChainId(KnownCaipNamespace.Eip155, chainIdOrDecimal);
|
|
135
|
+
}
|
|
136
|
+
return toCaipChainId(KnownCaipNamespace.Eip155, String(chainIdOrDecimal));
|
|
137
|
+
}
|
|
138
|
+
// Note: AccountActivityMessage and BalanceUpdate types are imported from @metamask/core-backend
|
|
139
|
+
// ============================================================================
|
|
140
|
+
// BACKEND WEBSOCKET DATA SOURCE
|
|
141
|
+
// ============================================================================
|
|
142
|
+
/**
|
|
143
|
+
* Data source for receiving real-time balance updates via WebSocket.
|
|
144
|
+
*
|
|
145
|
+
* This data source connects directly to BackendWebSocketService to receive
|
|
146
|
+
* push notifications for account balance changes. Unlike AccountsApiDataSource
|
|
147
|
+
* which polls for data, this provides instant updates.
|
|
148
|
+
*
|
|
149
|
+
* Uses Messenger pattern for all interactions:
|
|
150
|
+
* - Calls BackendWebSocketService methods via messenger actions
|
|
151
|
+
* - Exposes its own actions for AssetsController to call
|
|
152
|
+
* - Publishes events for AssetsController to subscribe to
|
|
153
|
+
*
|
|
154
|
+
* Actions exposed:
|
|
155
|
+
* - BackendWebsocketDataSource:getActiveChains
|
|
156
|
+
* - BackendWebsocketDataSource:subscribe
|
|
157
|
+
* - BackendWebsocketDataSource:unsubscribe
|
|
158
|
+
*
|
|
159
|
+
* Events published:
|
|
160
|
+
* - BackendWebsocketDataSource:activeChainsUpdated
|
|
161
|
+
* - BackendWebsocketDataSource:assetsUpdated
|
|
162
|
+
*
|
|
163
|
+
* Actions called (from BackendWebSocketService):
|
|
164
|
+
* - BackendWebSocketService:subscribe
|
|
165
|
+
* - BackendWebSocketService:getConnectionInfo
|
|
166
|
+
* - BackendWebSocketService:findSubscriptionsByChannelPrefix
|
|
167
|
+
* - BackendWebSocketService:addChannelCallback
|
|
168
|
+
* - BackendWebSocketService:removeChannelCallback
|
|
169
|
+
*/
|
|
170
|
+
const DEFAULT_CHAINS_REFRESH_INTERVAL_MS = 20 * 60 * 1000; // 20 minutes
|
|
171
|
+
export class BackendWebsocketDataSource extends AbstractDataSource {
|
|
172
|
+
constructor(options) {
|
|
173
|
+
super(CONTROLLER_NAME, {
|
|
174
|
+
...defaultState,
|
|
175
|
+
...options.state,
|
|
176
|
+
});
|
|
177
|
+
_BackendWebsocketDataSource_instances.add(this);
|
|
178
|
+
_BackendWebsocketDataSource_messenger.set(this, void 0);
|
|
179
|
+
_BackendWebsocketDataSource_apiClient.set(this, void 0);
|
|
180
|
+
_BackendWebsocketDataSource_onActiveChainsUpdated.set(this, void 0);
|
|
181
|
+
_BackendWebsocketDataSource_getAssetType.set(this, void 0);
|
|
182
|
+
/** Chains refresh timer */
|
|
183
|
+
_BackendWebsocketDataSource_chainsRefreshTimer.set(this, null);
|
|
184
|
+
/** Chains the backend API reports as supported (preserved across disconnects). */
|
|
185
|
+
_BackendWebsocketDataSource_supportedChains.set(this, []);
|
|
186
|
+
/** Whether the WebSocket is currently connected. Chains are only claimed when true. */
|
|
187
|
+
_BackendWebsocketDataSource_isConnected.set(this, false);
|
|
188
|
+
/** WebSocket subscriptions by our internal subscription ID */
|
|
189
|
+
_BackendWebsocketDataSource_wsSubscriptions.set(this, new Map());
|
|
190
|
+
/** Pending subscription requests to process when WebSocket connects */
|
|
191
|
+
_BackendWebsocketDataSource_pendingSubscriptions.set(this, new Map());
|
|
192
|
+
/** Store original subscription requests for reconnection */
|
|
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());
|
|
198
|
+
__classPrivateFieldSet(this, _BackendWebsocketDataSource_messenger, options.messenger, "f");
|
|
199
|
+
__classPrivateFieldSet(this, _BackendWebsocketDataSource_apiClient, options.queryApiClient, "f");
|
|
200
|
+
__classPrivateFieldSet(this, _BackendWebsocketDataSource_onActiveChainsUpdated, options.onActiveChainsUpdated, "f");
|
|
201
|
+
__classPrivateFieldSet(this, _BackendWebsocketDataSource_getAssetType, options.getAssetType, "f");
|
|
202
|
+
__classPrivateFieldGet(this, _BackendWebsocketDataSource_instances, "m", _BackendWebsocketDataSource_subscribeToEvents).call(this);
|
|
203
|
+
__classPrivateFieldGet(this, _BackendWebsocketDataSource_instances, "m", _BackendWebsocketDataSource_initializeActiveChains).call(this).catch(console.error);
|
|
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
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Sync active chains from AccountsApiDataSource.
|
|
217
|
+
* When the data source invokes the onActiveChainsUpdated callback, the
|
|
218
|
+
* controller processes the active chains update (no messenger call; controller already updated).
|
|
219
|
+
*
|
|
220
|
+
* @param chains - Updated active chain IDs from AccountsApiDataSource.
|
|
221
|
+
*/
|
|
222
|
+
setActiveChainsFromAccountsApi(chains) {
|
|
223
|
+
this.updateActiveChains(chains, () => undefined);
|
|
224
|
+
}
|
|
225
|
+
// ============================================================================
|
|
226
|
+
// ACTIVE CHAINS
|
|
227
|
+
// ============================================================================
|
|
228
|
+
/**
|
|
229
|
+
* Update active chains when AccountsApiDataSource reports new supported chains.
|
|
230
|
+
*
|
|
231
|
+
* @param chains - Array of supported chain IDs.
|
|
232
|
+
*/
|
|
233
|
+
updateSupportedChains(chains) {
|
|
234
|
+
const previous = [...this.state.activeChains];
|
|
235
|
+
this.updateActiveChains(chains, (updatedChains) => __classPrivateFieldGet(this, _BackendWebsocketDataSource_onActiveChainsUpdated, "f").call(this, this.getName(), updatedChains, previous));
|
|
236
|
+
}
|
|
237
|
+
// ============================================================================
|
|
238
|
+
// SUBSCRIBE
|
|
239
|
+
// ============================================================================
|
|
240
|
+
async subscribe(subscriptionRequest) {
|
|
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;
|
|
247
|
+
try {
|
|
248
|
+
await __classPrivateFieldGet(this, _BackendWebsocketDataSource_instances, "m", _BackendWebsocketDataSource_subscribeInternal).call(this, subscriptionRequest);
|
|
249
|
+
}
|
|
250
|
+
finally {
|
|
251
|
+
releaseLock();
|
|
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;
|
|
270
|
+
try {
|
|
271
|
+
await __classPrivateFieldGet(this, _BackendWebsocketDataSource_instances, "m", _BackendWebsocketDataSource_teardownSubscription).call(this, subscriptionId);
|
|
272
|
+
}
|
|
273
|
+
finally {
|
|
274
|
+
releaseLock();
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
// ============================================================================
|
|
278
|
+
// CLEANUP
|
|
279
|
+
// ============================================================================
|
|
280
|
+
destroy() {
|
|
281
|
+
if (__classPrivateFieldGet(this, _BackendWebsocketDataSource_chainsRefreshTimer, "f")) {
|
|
282
|
+
clearInterval(__classPrivateFieldGet(this, _BackendWebsocketDataSource_chainsRefreshTimer, "f"));
|
|
283
|
+
__classPrivateFieldSet(this, _BackendWebsocketDataSource_chainsRefreshTimer, null, "f");
|
|
284
|
+
}
|
|
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);
|
|
293
|
+
}
|
|
294
|
+
// Clean up base class subscriptions (no-op if already torn down)
|
|
295
|
+
super.destroy();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
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 =
|
|
299
|
+
// ============================================================================
|
|
300
|
+
// INITIALIZATION
|
|
301
|
+
// ============================================================================
|
|
302
|
+
async function _BackendWebsocketDataSource_initializeActiveChains() {
|
|
303
|
+
try {
|
|
304
|
+
const chains = await __classPrivateFieldGet(this, _BackendWebsocketDataSource_instances, "m", _BackendWebsocketDataSource_fetchActiveChains).call(this);
|
|
305
|
+
__classPrivateFieldSet(this, _BackendWebsocketDataSource_supportedChains, chains, "f");
|
|
306
|
+
// Only claim chains if the websocket is already connected.
|
|
307
|
+
// If not connected, chains stay unclaimed so AccountsApiDataSource
|
|
308
|
+
// can pick them up via polling. They'll be claimed on reconnect.
|
|
309
|
+
if (__classPrivateFieldGet(this, _BackendWebsocketDataSource_isConnected, "f")) {
|
|
310
|
+
const previous = [...this.state.activeChains];
|
|
311
|
+
this.updateActiveChains(chains, (updatedChains) => __classPrivateFieldGet(this, _BackendWebsocketDataSource_onActiveChainsUpdated, "f").call(this, this.getName(), updatedChains, previous));
|
|
312
|
+
}
|
|
313
|
+
__classPrivateFieldSet(this, _BackendWebsocketDataSource_chainsRefreshTimer, setInterval(() => {
|
|
314
|
+
__classPrivateFieldGet(this, _BackendWebsocketDataSource_instances, "m", _BackendWebsocketDataSource_refreshActiveChains).call(this).catch(console.error);
|
|
315
|
+
}, DEFAULT_CHAINS_REFRESH_INTERVAL_MS), "f");
|
|
316
|
+
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
log('Failed to fetch active chains', error);
|
|
319
|
+
}
|
|
320
|
+
}, _BackendWebsocketDataSource_refreshActiveChains = async function _BackendWebsocketDataSource_refreshActiveChains() {
|
|
321
|
+
try {
|
|
322
|
+
const chains = await __classPrivateFieldGet(this, _BackendWebsocketDataSource_instances, "m", _BackendWebsocketDataSource_fetchActiveChains).call(this);
|
|
323
|
+
__classPrivateFieldSet(this, _BackendWebsocketDataSource_supportedChains, chains, "f");
|
|
324
|
+
// Only update activeChains if connected; otherwise keep them unclaimed.
|
|
325
|
+
if (!__classPrivateFieldGet(this, _BackendWebsocketDataSource_isConnected, "f")) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
const previousChains = new Set(this.state.activeChains);
|
|
329
|
+
const newChains = new Set(chains);
|
|
330
|
+
const added = chains.filter((chain) => !previousChains.has(chain));
|
|
331
|
+
const removed = Array.from(previousChains).filter((chain) => !newChains.has(chain));
|
|
332
|
+
if (added.length > 0 || removed.length > 0) {
|
|
333
|
+
const previous = [...this.state.activeChains];
|
|
334
|
+
this.updateActiveChains(chains, (updatedChains) => __classPrivateFieldGet(this, _BackendWebsocketDataSource_onActiveChainsUpdated, "f").call(this, this.getName(), updatedChains, previous));
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
catch (error) {
|
|
338
|
+
log('Failed to refresh active chains', error);
|
|
339
|
+
}
|
|
340
|
+
}, _BackendWebsocketDataSource_fetchActiveChains = async function _BackendWebsocketDataSource_fetchActiveChains() {
|
|
341
|
+
const response = await __classPrivateFieldGet(this, _BackendWebsocketDataSource_apiClient, "f").accounts.fetchV2SupportedNetworks();
|
|
342
|
+
return response.fullSupport.map(toChainId);
|
|
343
|
+
}, _BackendWebsocketDataSource_subscribeToEvents = function _BackendWebsocketDataSource_subscribeToEvents() {
|
|
344
|
+
// Listen for WebSocket connection state changes (event not in AssetsControllerEvents).
|
|
345
|
+
__classPrivateFieldGet(this, _BackendWebsocketDataSource_messenger, "f").subscribe('BackendWebSocketService:connectionStateChanged', (connectionInfo) => {
|
|
346
|
+
if (connectionInfo.state === 'connected') {
|
|
347
|
+
__classPrivateFieldSet(this, _BackendWebsocketDataSource_isConnected, true, "f");
|
|
348
|
+
__classPrivateFieldGet(this, _BackendWebsocketDataSource_instances, "m", _BackendWebsocketDataSource_handleReconnect).call(this);
|
|
349
|
+
}
|
|
350
|
+
else if (connectionInfo.state === 'disconnected') {
|
|
351
|
+
__classPrivateFieldSet(this, _BackendWebsocketDataSource_isConnected, false, "f");
|
|
352
|
+
__classPrivateFieldGet(this, _BackendWebsocketDataSource_instances, "m", _BackendWebsocketDataSource_handleDisconnect).call(this);
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
}, _BackendWebsocketDataSource_handleDisconnect = function _BackendWebsocketDataSource_handleDisconnect() {
|
|
356
|
+
log('WebSocket disconnected, releasing chains for fallback', {
|
|
357
|
+
activeSubscriptionCount: this.activeSubscriptions.size,
|
|
358
|
+
wsSubscriptionCount: __classPrivateFieldGet(this, _BackendWebsocketDataSource_wsSubscriptions, "f").size,
|
|
359
|
+
chainCount: this.state.activeChains.length,
|
|
360
|
+
});
|
|
361
|
+
// Move active subscriptions to pending for re-subscription
|
|
362
|
+
for (const [subscriptionId] of this.activeSubscriptions) {
|
|
363
|
+
const originalRequest = __classPrivateFieldGet(this, _BackendWebsocketDataSource_subscriptionRequests, "f").get(subscriptionId);
|
|
364
|
+
if (originalRequest) {
|
|
365
|
+
__classPrivateFieldGet(this, _BackendWebsocketDataSource_pendingSubscriptions, "f").set(subscriptionId, {
|
|
366
|
+
...originalRequest,
|
|
367
|
+
isUpdate: false,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
// Clear WebSocket subscriptions (server-side already cleared)
|
|
372
|
+
__classPrivateFieldGet(this, _BackendWebsocketDataSource_wsSubscriptions, "f").clear();
|
|
373
|
+
// Clear active subscriptions (they're no longer valid)
|
|
374
|
+
this.activeSubscriptions.clear();
|
|
375
|
+
// Release chains so the chain-claiming loop assigns them to
|
|
376
|
+
// AccountsApiDataSource (polling fallback) on the next #subscribeAssets.
|
|
377
|
+
const previous = [...this.state.activeChains];
|
|
378
|
+
if (previous.length > 0) {
|
|
379
|
+
this.updateActiveChains([], (updatedChains) => __classPrivateFieldGet(this, _BackendWebsocketDataSource_onActiveChainsUpdated, "f").call(this, this.getName(), updatedChains, previous));
|
|
380
|
+
}
|
|
381
|
+
}, _BackendWebsocketDataSource_handleReconnect = function _BackendWebsocketDataSource_handleReconnect() {
|
|
382
|
+
log('WebSocket reconnected, reclaiming chains', {
|
|
383
|
+
supportedChainCount: __classPrivateFieldGet(this, _BackendWebsocketDataSource_supportedChains, "f").length,
|
|
384
|
+
pendingSubscriptionCount: __classPrivateFieldGet(this, _BackendWebsocketDataSource_pendingSubscriptions, "f").size,
|
|
385
|
+
});
|
|
386
|
+
// Discard stale pending subscriptions captured at disconnect time.
|
|
387
|
+
// The chain reclaim below triggers #onActiveChainsUpdated →
|
|
388
|
+
// #subscribeAssets() in AssetsController, which creates fresh
|
|
389
|
+
// subscriptions with current accounts and chains. Processing the
|
|
390
|
+
// stale pending entries afterwards would overwrite those with
|
|
391
|
+
// outdated request data.
|
|
392
|
+
__classPrivateFieldGet(this, _BackendWebsocketDataSource_pendingSubscriptions, "f").clear();
|
|
393
|
+
if (__classPrivateFieldGet(this, _BackendWebsocketDataSource_supportedChains, "f").length > 0) {
|
|
394
|
+
const previous = [...this.state.activeChains];
|
|
395
|
+
this.updateActiveChains(__classPrivateFieldGet(this, _BackendWebsocketDataSource_supportedChains, "f"), (updatedChains) => __classPrivateFieldGet(this, _BackendWebsocketDataSource_onActiveChainsUpdated, "f").call(this, this.getName(), updatedChains, previous));
|
|
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
|
+
}
|
|
492
|
+
}, _BackendWebsocketDataSource_handleNotification = function _BackendWebsocketDataSource_handleNotification(notification, subscriptionId) {
|
|
493
|
+
try {
|
|
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;
|
|
499
|
+
if (!request) {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
const { address, tx, updates } = activityMessage;
|
|
503
|
+
if (!address || !tx || !updates) {
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
// Extract chain ID from transaction (CAIP-2 format, e.g., "eip155:8453")
|
|
507
|
+
const chainId = tx.chain;
|
|
508
|
+
// Find matching account in request (eip155: case-insensitive hex; solana: exact base58)
|
|
509
|
+
const account = request.accountsWithSupportedChains
|
|
510
|
+
.map((entry) => entry.account)
|
|
511
|
+
.find((a) => a.address.startsWith('0x')
|
|
512
|
+
? a.address.toLowerCase() === address.toLowerCase()
|
|
513
|
+
: a.address === address);
|
|
514
|
+
if (!account) {
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
const accountId = account.id;
|
|
518
|
+
// Process all balance updates from the activity message
|
|
519
|
+
const response = __classPrivateFieldGet(this, _BackendWebsocketDataSource_instances, "m", _BackendWebsocketDataSource_processBalanceUpdates).call(this, updates, chainId, accountId);
|
|
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
|
+
});
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
catch (error) {
|
|
529
|
+
log('Error handling notification', error);
|
|
530
|
+
}
|
|
531
|
+
}, _BackendWebsocketDataSource_processBalanceUpdates = function _BackendWebsocketDataSource_processBalanceUpdates(updates, _chainId, accountId) {
|
|
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);
|
|
559
|
+
}
|
|
560
|
+
catch {
|
|
561
|
+
// Channel callbacks are optional; ws subscription still works without them.
|
|
562
|
+
}
|
|
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
|
+
}
|
|
579
|
+
};
|
|
580
|
+
// ============================================================================
|
|
581
|
+
// FACTORY FUNCTION
|
|
582
|
+
// ============================================================================
|
|
583
|
+
/**
|
|
584
|
+
* Creates a BackendWebsocketDataSource instance.
|
|
585
|
+
*
|
|
586
|
+
* @param options - Configuration options for the data source.
|
|
587
|
+
* @returns A new BackendWebsocketDataSource instance.
|
|
588
|
+
*/
|
|
589
|
+
export function createBackendWebsocketDataSource(options) {
|
|
590
|
+
return new BackendWebsocketDataSource(options);
|
|
591
|
+
}
|
|
592
|
+
//# sourceMappingURL=BackendWebsocketDataSource.mjs.map
|