@metamask/core-backend 4.0.0 → 5.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 CHANGED
@@ -7,6 +7,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [5.0.0]
11
+
12
+ ### Changed
13
+
14
+ - Bump `@metamask/profile-sync-controller` from `^26.0.0` to `^27.0.0` ([#7202](https://github.com/MetaMask/core/pull/7202))
15
+ - Bump `@metamask/controller-utils` from `^11.15.0` to `^11.16.0` ([#7202](https://github.com/MetaMask/core/pull/7202))
16
+ - **BREAKING:** Bump `@metamask/keyring-controller` from `^24.0.0` to `^25.0.0` ([#7202](https://github.com/MetaMask/core/pull/7202))
17
+ - **BREAKING:** Bump `@metamask/accounts-controller` from `^34.0.0` to `^35.0.0` ([#7202](https://github.com/MetaMask/core/pull/7202))
18
+
19
+ ## [4.1.0]
20
+
21
+ ### Changed
22
+
23
+ - Improve WebSocket connection lifecycle tracing in `BackendWebSocketService` ([#7101](https://github.com/MetaMask/core/pull/7101))
24
+ - WebSocket connection duration is now properly reflected in trace span duration instead of only in custom data
25
+ - Trace all disconnections (both manual and unexpected) to provide complete connection lifecycle visibility in traces
26
+ - Omit `connectionDuration_ms` from disconnection traces when connection never established (onClose without onOpen)
27
+ - Update `BackendWebSocketService` default exponential backoff options for reconnection ([#7101](https://github.com/MetaMask/core/pull/7101))
28
+ - Increase default `reconnectDelay` from 500 milliseconds to 10 seconds
29
+ - Increase default `maxReconnectDelay` from 30 seconds to 60 seconds
30
+ - Simplify WebSocket disconnection code in `BackendWebSocketService` ([#7101](https://github.com/MetaMask/core/pull/7101))
31
+ - Centralize all disconnection logic in `ws.onclose` handler for single source of truth
32
+ - Centralize all state changes within `#establishConnection` method - state transitions only occur in `onopen` (CONNECTING → CONNECTED) and `onclose` (any state → DISCONNECTED)
33
+ - Add `MANUAL_DISCONNECT_CODE` (4999) and `MANUAL_DISCONNECT_REASON` constants to distinguish manual from unexpected disconnects
34
+ - Bump `@ts-bridge/cli` from `^0.6.1` to `^0.6.4` ([#7039](https://github.com/MetaMask/core/pull/7039))
35
+
36
+ ### Removed
37
+
38
+ - Remove `BackendWebSocketService Channel Message` trace as it provided no useful performance insights ([#7101](https://github.com/MetaMask/core/pull/7101))
39
+
10
40
  ## [4.0.0]
11
41
 
12
42
  ### Changed
@@ -133,7 +163,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
133
163
  - **Type definitions** - Comprehensive TypeScript types for transactions, balances, WebSocket messages, and service configurations
134
164
  - **Logging infrastructure** - Structured logging with module-specific loggers for debugging and monitoring
135
165
 
136
- [Unreleased]: https://github.com/MetaMask/core/compare/@metamask/core-backend@4.0.0...HEAD
166
+ [Unreleased]: https://github.com/MetaMask/core/compare/@metamask/core-backend@5.0.0...HEAD
167
+ [5.0.0]: https://github.com/MetaMask/core/compare/@metamask/core-backend@4.1.0...@metamask/core-backend@5.0.0
168
+ [4.1.0]: https://github.com/MetaMask/core/compare/@metamask/core-backend@4.0.0...@metamask/core-backend@4.1.0
137
169
  [4.0.0]: https://github.com/MetaMask/core/compare/@metamask/core-backend@3.0.0...@metamask/core-backend@4.0.0
138
170
  [3.0.0]: https://github.com/MetaMask/core/compare/@metamask/core-backend@2.1.0...@metamask/core-backend@3.0.0
139
171
  [2.1.0]: https://github.com/MetaMask/core/compare/@metamask/core-backend@2.0.0...@metamask/core-backend@2.1.0
package/README.md CHANGED
@@ -311,7 +311,7 @@ sequenceDiagram
311
311
  #### Key Flow Characteristics
312
312
 
313
313
  1. **Initial Setup**: BackendWebSocketService establishes connection, then AccountActivityService subscribes to selected account. Backend automatically sends a system notification with all chains that are currently up. AccountActivityService tracks these chains internally and notifies TokenBalancesController, which increases polling interval to 5 min
314
- 2. **Chain Status Tracking**: AccountActivityService maintains an internal set of chains that are 'up' based on system notifications. On disconnect/error, it marks all tracked chains as 'down' before clearing the set
314
+ 2. **Chain Status Tracking**: AccountActivityService maintains an internal set of chains that are 'up' based on system notifications. On disconnect, it marks all tracked chains as 'down' before clearing the set
315
315
  3. **System Notifications**: Backend automatically sends chain status updates (up/down) upon subscription and when status changes. AccountActivityService forwards these to TokenBalancesController, which adjusts polling intervals (up: 5min, down: 30s + immediate fetch)
316
316
  4. **User Account Changes**: When users switch accounts, AccountActivityService unsubscribes from old account and subscribes to new account. Backend sends fresh system notification with current chain status for the new account
317
317
  5. **Connection Resilience**: On reconnection, AccountActivityService resubscribes to selected account and receives fresh chain status via system notification. Automatic reconnection with exponential backoff
@@ -110,8 +110,14 @@ class AccountActivityService {
110
110
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
111
111
  ((_request, fn) => fn?.()), "f");
112
112
  __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
113
- __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").subscribe('AccountsController:selectedAccountChange', async (account) => await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_handleSelectedAccountChange).call(this, account));
114
- __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").subscribe('BackendWebSocketService:connectionStateChanged', (connectionInfo) => __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_handleWebSocketStateChange).call(this, connectionInfo));
113
+ __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").subscribe('AccountsController:selectedAccountChange',
114
+ // Promise result intentionally not awaited
115
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
116
+ async (account) => await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_handleSelectedAccountChange).call(this, account));
117
+ __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").subscribe('BackendWebSocketService:connectionStateChanged',
118
+ // Promise result intentionally not awaited
119
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
120
+ (connectionInfo) => __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_handleWebSocketStateChange).call(this, connectionInfo));
115
121
  __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('BackendWebSocketService:addChannelCallback', {
116
122
  channelName: `system-notifications.v1.${__classPrivateFieldGet(this, _AccountActivityService_options, "f").subscriptionNamespace}`,
117
123
  callback: (notification) => __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_handleSystemNotification).call(this, notification),
@@ -138,7 +144,7 @@ class AccountActivityService {
138
144
  // Create subscription using the proper subscribe method (this will be stored in WebSocketService's internal tracking)
139
145
  await __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('BackendWebSocketService:subscribe', {
140
146
  channels: [channel],
141
- channelType: __classPrivateFieldGet(this, _AccountActivityService_options, "f").subscriptionNamespace,
147
+ channelType: __classPrivateFieldGet(this, _AccountActivityService_options, "f").subscriptionNamespace, // e.g., 'account-activity.v1'
142
148
  callback: (notification) => {
143
149
  __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_handleAccountActivityUpdate).call(this, notification.data);
144
150
  },
@@ -199,6 +205,8 @@ _AccountActivityService_messenger = new WeakMap(), _AccountActivityService_optio
199
205
  elapsedMs,
200
206
  });
201
207
  // Trace message receipt with latency from transaction time to now
208
+ // Promise result intentionally not awaited
209
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
202
210
  __classPrivateFieldGet(this, _AccountActivityService_trace, "f").call(this, {
203
211
  name: `${SERVICE_NAME} Transaction Message`,
204
212
  data: {
@@ -265,6 +273,11 @@ async function _AccountActivityService_handleSelectedAccountChange(newAccount) {
265
273
  status: data.status,
266
274
  timestamp,
267
275
  });
276
+ log(`WebSocket status change - Published tracked chains as ${data.status}`, {
277
+ count: data.chainIds.length,
278
+ chains: data.chainIds,
279
+ status: data.status,
280
+ });
268
281
  }, _AccountActivityService_handleWebSocketStateChange =
269
282
  /**
270
283
  * Handle WebSocket connection state changes for fallback polling and resubscription
@@ -278,9 +291,8 @@ async function _AccountActivityService_handleWebSocketStateChange(connectionInfo
278
291
  // The system notification will automatically provide the list of chains that are up
279
292
  await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_subscribeToSelectedAccount).call(this);
280
293
  }
281
- else if (state === BackendWebSocketService_1.WebSocketState.DISCONNECTED ||
282
- state === BackendWebSocketService_1.WebSocketState.ERROR) {
283
- // On disconnect/error, flush all tracked chains as down
294
+ else if (state === BackendWebSocketService_1.WebSocketState.DISCONNECTED) {
295
+ // On disconnect, flush all tracked chains as down
284
296
  const chainsToMarkDown = Array.from(__classPrivateFieldGet(this, _AccountActivityService_chainsUp, "f"));
285
297
  if (chainsToMarkDown.length > 0) {
286
298
  __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").publish(`AccountActivityService:statusChanged`, {
@@ -288,7 +300,7 @@ async function _AccountActivityService_handleWebSocketStateChange(connectionInfo
288
300
  status: 'down',
289
301
  timestamp: Date.now(),
290
302
  });
291
- log('WebSocket error/disconnection - Published tracked chains as down', {
303
+ log('WebSocket disconnection - Published tracked chains as down', {
292
304
  count: chainsToMarkDown.length,
293
305
  chains: chainsToMarkDown,
294
306
  });
@@ -1 +1 @@
1
- {"version":3,"file":"AccountActivityService.cjs","sourceRoot":"","sources":["../src/AccountActivityService.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Account Activity Service for monitoring account transactions and balance changes\n *\n * This service subscribes to account activity and receives all transactions\n * and balance updates for those accounts via the comprehensive AccountActivityMessage format.\n */\n\nimport type {\n AccountsControllerGetSelectedAccountAction,\n AccountsControllerSelectedAccountChangeEvent,\n} from '@metamask/accounts-controller';\nimport type { TraceCallback } from '@metamask/controller-utils';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport type { Messenger } from '@metamask/messenger';\n\nimport type { AccountActivityServiceMethodActions } from './AccountActivityService-method-action-types';\nimport type {\n WebSocketConnectionInfo,\n BackendWebSocketServiceConnectionStateChangedEvent,\n ServerNotificationMessage,\n} from './BackendWebSocketService';\nimport { WebSocketState } from './BackendWebSocketService';\nimport type { BackendWebSocketServiceMethodActions } from './BackendWebSocketService-method-action-types';\nimport { projectLogger, createModuleLogger } from './logger';\nimport type {\n Transaction,\n AccountActivityMessage,\n BalanceUpdate,\n} from './types';\n\n// =============================================================================\n// Types and Constants\n// =============================================================================\n\n/**\n * System notification data for chain status updates\n */\nexport type SystemNotificationData = {\n /** Array of chain IDs affected (e.g., ['eip155:137', 'eip155:1']) */\n chainIds: string[];\n /** Status of the chains: 'down' or 'up' */\n status: 'down' | 'up';\n /** Timestamp of the notification */\n timestamp?: number;\n};\n\nconst SERVICE_NAME = 'AccountActivityService';\n\nconst log = createModuleLogger(projectLogger, SERVICE_NAME);\n\nconst MESSENGER_EXPOSED_METHODS = ['subscribe', 'unsubscribe'] as const;\n\nconst SUBSCRIPTION_NAMESPACE = 'account-activity.v1';\n\n/**\n * Account subscription options\n */\nexport type SubscriptionOptions = {\n address: string; // Should be in CAIP-10 format, e.g., \"eip155:0:0x1234...\" or \"solana:0:ABC123...\"\n};\n\n/**\n * Configuration options for the account activity service\n */\nexport type AccountActivityServiceOptions = {\n /** Custom subscription namespace (default: 'account-activity.v1') */\n subscriptionNamespace?: string;\n /** Optional callback to trace performance of account activity operations (default: no-op) */\n traceFn?: TraceCallback;\n};\n\n// =============================================================================\n// Action and Event Types\n// =============================================================================\n\n// Action types for the messaging system - using generated method actions\nexport type AccountActivityServiceActions = AccountActivityServiceMethodActions;\n\n// Allowed actions that AccountActivityService can call on other controllers\nexport const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_ACTIONS = [\n 'AccountsController:getSelectedAccount',\n 'BackendWebSocketService:connect',\n 'BackendWebSocketService:forceReconnection',\n 'BackendWebSocketService:subscribe',\n 'BackendWebSocketService:getConnectionInfo',\n 'BackendWebSocketService:channelHasSubscription',\n 'BackendWebSocketService:getSubscriptionsByChannel',\n 'BackendWebSocketService:findSubscriptionsByChannelPrefix',\n 'BackendWebSocketService:addChannelCallback',\n 'BackendWebSocketService:removeChannelCallback',\n] as const;\n\n// Allowed events that AccountActivityService can listen to\nexport const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_EVENTS = [\n 'AccountsController:selectedAccountChange',\n 'BackendWebSocketService:connectionStateChanged',\n] as const;\n\nexport type AllowedActions =\n | AccountsControllerGetSelectedAccountAction\n | BackendWebSocketServiceMethodActions;\n\n// Event types for the messaging system\n\nexport type AccountActivityServiceTransactionUpdatedEvent = {\n type: `AccountActivityService:transactionUpdated`;\n payload: [Transaction];\n};\n\nexport type AccountActivityServiceBalanceUpdatedEvent = {\n type: `AccountActivityService:balanceUpdated`;\n payload: [{ address: string; chain: string; updates: BalanceUpdate[] }];\n};\n\nexport type AccountActivityServiceSubscriptionErrorEvent = {\n type: `AccountActivityService:subscriptionError`;\n payload: [{ addresses: string[]; error: string; operation: string }];\n};\n\nexport type AccountActivityServiceStatusChangedEvent = {\n type: `AccountActivityService:statusChanged`;\n payload: [\n {\n chainIds: string[];\n status: 'up' | 'down';\n timestamp?: number;\n },\n ];\n};\n\nexport type AccountActivityServiceEvents =\n | AccountActivityServiceTransactionUpdatedEvent\n | AccountActivityServiceBalanceUpdatedEvent\n | AccountActivityServiceSubscriptionErrorEvent\n | AccountActivityServiceStatusChangedEvent;\n\nexport type AllowedEvents =\n | AccountsControllerSelectedAccountChangeEvent\n | BackendWebSocketServiceConnectionStateChangedEvent;\n\nexport type AccountActivityServiceMessenger = Messenger<\n typeof SERVICE_NAME,\n AccountActivityServiceActions | AllowedActions,\n AccountActivityServiceEvents | AllowedEvents\n>;\n\n// =============================================================================\n// Main Service Class\n// =============================================================================\n\n/**\n * High-performance service for real-time account activity monitoring using optimized\n * WebSocket subscriptions with direct callback routing. Automatically subscribes to\n * the currently selected account and switches subscriptions when the selected account changes.\n * Receives transactions and balance updates using the comprehensive AccountActivityMessage format.\n *\n * Performance Features:\n * - Direct callback routing (no EventEmitter overhead)\n * - Minimal subscription tracking (no duplication with BackendWebSocketService)\n * - Optimized cleanup for mobile environments\n * - Single-account subscription (only selected account)\n * - Comprehensive balance updates with transfer tracking\n *\n * Architecture:\n * - Uses messenger pattern to communicate with BackendWebSocketService\n * - AccountActivityService tracks channel-to-subscriptionId mappings via messenger calls\n * - Automatically subscribes to selected account on initialization\n * - Switches subscriptions when selected account changes\n * - No direct dependency on BackendWebSocketService (uses messenger instead)\n *\n * @example\n * ```typescript\n * const service = new AccountActivityService({\n * messenger: activityMessenger,\n * });\n *\n * // Service automatically subscribes to the currently selected account\n * // When user switches accounts, service automatically resubscribes\n *\n * // All transactions and balance updates are received via optimized\n * // WebSocket callbacks and processed with zero-allocation routing\n * // Balance updates include comprehensive transfer details and post-transaction balances\n * ```\n */\nexport class AccountActivityService {\n /**\n * The name of the service.\n */\n readonly name = SERVICE_NAME;\n\n readonly #messenger: AccountActivityServiceMessenger;\n\n readonly #options: Required<Omit<AccountActivityServiceOptions, 'traceFn'>>;\n\n readonly #trace: TraceCallback;\n\n // Track chains that are currently up (based on system notifications)\n readonly #chainsUp: Set<string> = new Set();\n\n // =============================================================================\n // Constructor and Initialization\n // =============================================================================\n\n /**\n * Creates a new Account Activity service instance\n *\n * @param options - Configuration options including messenger\n */\n constructor(\n options: AccountActivityServiceOptions & {\n messenger: AccountActivityServiceMessenger;\n },\n ) {\n this.#messenger = options.messenger;\n\n // Set configuration with defaults\n this.#options = {\n subscriptionNamespace:\n options.subscriptionNamespace ?? SUBSCRIPTION_NAMESPACE,\n };\n\n // Default to no-op trace function to keep core platform-agnostic\n this.#trace =\n options.traceFn ??\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (((_request: any, fn?: any) => fn?.()) as TraceCallback);\n\n this.#messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n this.#messenger.subscribe(\n 'AccountsController:selectedAccountChange',\n async (account: InternalAccount) =>\n await this.#handleSelectedAccountChange(account),\n );\n this.#messenger.subscribe(\n 'BackendWebSocketService:connectionStateChanged',\n (connectionInfo: WebSocketConnectionInfo) =>\n this.#handleWebSocketStateChange(connectionInfo),\n );\n this.#messenger.call('BackendWebSocketService:addChannelCallback', {\n channelName: `system-notifications.v1.${this.#options.subscriptionNamespace}`,\n callback: (notification: ServerNotificationMessage) =>\n this.#handleSystemNotification(notification),\n });\n }\n\n // =============================================================================\n // Account Subscription Methods\n // =============================================================================\n\n /**\n * Subscribe to account activity (transactions and balance updates)\n * Address should be in CAIP-10 format (e.g., \"eip155:0:0x1234...\" or \"solana:0:ABC123...\")\n *\n * @param subscription - Account subscription configuration with address\n */\n async subscribe(subscription: SubscriptionOptions): Promise<void> {\n try {\n await this.#messenger.call('BackendWebSocketService:connect');\n\n // Create channel name from address\n const channel = `${this.#options.subscriptionNamespace}.${subscription.address}`;\n\n // Check if already subscribed\n if (\n this.#messenger.call(\n 'BackendWebSocketService:channelHasSubscription',\n channel,\n )\n ) {\n return;\n }\n\n // Create subscription using the proper subscribe method (this will be stored in WebSocketService's internal tracking)\n await this.#messenger.call('BackendWebSocketService:subscribe', {\n channels: [channel],\n channelType: this.#options.subscriptionNamespace, // e.g., 'account-activity.v1'\n callback: (notification: ServerNotificationMessage) => {\n this.#handleAccountActivityUpdate(\n notification.data as AccountActivityMessage,\n );\n },\n });\n } catch (error) {\n log('Subscription failed, forcing reconnection', { error });\n await this.#forceReconnection();\n }\n }\n\n /**\n * Unsubscribe from account activity for specified address\n * Address should be in CAIP-10 format (e.g., \"eip155:0:0x1234...\" or \"solana:0:ABC123...\")\n *\n * @param subscription - Account subscription configuration with address to unsubscribe\n */\n async unsubscribe(subscription: SubscriptionOptions): Promise<void> {\n const { address } = subscription;\n try {\n // Find channel for the specified address\n const channel = `${this.#options.subscriptionNamespace}.${address}`;\n const subscriptions = this.#messenger.call(\n 'BackendWebSocketService:getSubscriptionsByChannel',\n channel,\n );\n\n if (subscriptions.length === 0) {\n return;\n }\n\n // Fast path: Direct unsubscribe using stored unsubscribe function\n // Unsubscribe from all matching subscriptions\n for (const subscriptionInfo of subscriptions) {\n await subscriptionInfo.unsubscribe();\n }\n } catch (error) {\n log('Unsubscription failed, forcing reconnection', { error });\n await this.#forceReconnection();\n }\n }\n\n // =============================================================================\n // Private Methods - Event Handlers\n // =============================================================================\n\n /**\n * Handle account activity updates (transactions + balance changes)\n * Processes the comprehensive AccountActivityMessage format with detailed balance updates and transfers\n *\n * @param payload - The account activity message containing transaction and balance updates\n * @example AccountActivityMessage format handling:\n * Input: {\n * address: \"0xd14b52362b5b777ffa754c666ddec6722aaeee08\",\n * tx: { id: \"0x1cde...\", chain: \"eip155:8453\", status: \"confirmed\", timestamp: 1760099871, ... },\n * updates: [{\n * asset: { fungible: true, type: \"eip155:8453/erc20:0x833...\", unit: \"USDC\", decimals: 6 },\n * postBalance: { amount: \"0xc350\" },\n * transfers: [{ from: \"0x7b07...\", to: \"0xd14b...\", amount: \"0x2710\" }]\n * }]\n * }\n * Output: Transaction and balance updates published separately\n */\n #handleAccountActivityUpdate(payload: AccountActivityMessage): void {\n const { address, tx, updates } = payload;\n\n // Calculate time elapsed between transaction time and message receipt\n const txTimestampMs = tx.timestamp * 1000; // Convert Unix timestamp (seconds) to milliseconds\n const elapsedMs = Date.now() - txTimestampMs;\n\n log('Handling account activity update', {\n address,\n updateCount: updates.length,\n elapsedMs,\n });\n\n // Trace message receipt with latency from transaction time to now\n this.#trace(\n {\n name: `${SERVICE_NAME} Transaction Message`,\n data: {\n chain: tx.chain,\n status: tx.status,\n elapsed_ms: elapsedMs,\n },\n tags: {\n service: SERVICE_NAME,\n notification_type: this.#options.subscriptionNamespace,\n },\n },\n () => {\n // Process transaction update\n this.#messenger.publish(\n `AccountActivityService:transactionUpdated`,\n tx,\n );\n\n // Publish comprehensive balance updates with transfer details\n this.#messenger.publish(`AccountActivityService:balanceUpdated`, {\n address,\n chain: tx.chain,\n updates,\n });\n },\n );\n }\n\n /**\n * Handle selected account change event\n *\n * @param newAccount - The newly selected account\n */\n async #handleSelectedAccountChange(\n newAccount: InternalAccount | null,\n ): Promise<void> {\n if (!newAccount?.address) {\n return;\n }\n\n try {\n // Convert new account to CAIP-10 format\n const newAddress = this.#convertToCaip10Address(newAccount);\n\n // First, unsubscribe from all current account activity subscriptions to avoid multiple subscriptions\n await this.#unsubscribeFromAllAccountActivity();\n\n // Then, subscribe to the new selected account\n await this.subscribe({ address: newAddress });\n } catch (error) {\n log('Account change failed', { error });\n }\n }\n\n /**\n * Handle system notification for chain status changes\n * Publishes only the status change (delta) for affected chains\n *\n * @param notification - Server notification message containing chain status updates and timestamp\n */\n #handleSystemNotification(notification: ServerNotificationMessage): void {\n const data = notification.data as SystemNotificationData;\n const { timestamp } = notification;\n\n // Validate required fields\n if (!data.chainIds || !Array.isArray(data.chainIds) || !data.status) {\n throw new Error(\n 'Invalid system notification data: missing chainIds or status',\n );\n }\n\n // Track chain status\n if (data.status === 'up') {\n for (const chainId of data.chainIds) {\n this.#chainsUp.add(chainId);\n }\n } else {\n for (const chainId of data.chainIds) {\n this.#chainsUp.delete(chainId);\n }\n }\n\n // Publish status change directly (delta update)\n this.#messenger.publish(`AccountActivityService:statusChanged`, {\n chainIds: data.chainIds,\n status: data.status,\n timestamp,\n });\n }\n\n /**\n * Handle WebSocket connection state changes for fallback polling and resubscription\n *\n * @param connectionInfo - WebSocket connection state information\n */\n async #handleWebSocketStateChange(\n connectionInfo: WebSocketConnectionInfo,\n ): Promise<void> {\n const { state } = connectionInfo;\n\n if (state === WebSocketState.CONNECTED) {\n // WebSocket connected - resubscribe to selected account\n // The system notification will automatically provide the list of chains that are up\n await this.#subscribeToSelectedAccount();\n } else if (\n state === WebSocketState.DISCONNECTED ||\n state === WebSocketState.ERROR\n ) {\n // On disconnect/error, flush all tracked chains as down\n const chainsToMarkDown = Array.from(this.#chainsUp);\n\n if (chainsToMarkDown.length > 0) {\n this.#messenger.publish(`AccountActivityService:statusChanged`, {\n chainIds: chainsToMarkDown,\n status: 'down',\n timestamp: Date.now(),\n });\n\n log(\n 'WebSocket error/disconnection - Published tracked chains as down',\n {\n count: chainsToMarkDown.length,\n chains: chainsToMarkDown,\n },\n );\n\n // Clear the tracking set since all chains are now down\n this.#chainsUp.clear();\n }\n }\n }\n\n // =============================================================================\n // Private Methods - Subscription Management\n // =============================================================================\n\n /**\n * Subscribe to the currently selected account only\n */\n async #subscribeToSelectedAccount(): Promise<void> {\n const selectedAccount = this.#messenger.call(\n 'AccountsController:getSelectedAccount',\n );\n\n if (!selectedAccount || !selectedAccount.address) {\n return;\n }\n\n // Convert to CAIP-10 format and subscribe\n const address = this.#convertToCaip10Address(selectedAccount);\n await this.subscribe({ address });\n }\n\n /**\n * Unsubscribe from all account activity subscriptions for this service\n * Finds all channels matching the service's namespace and unsubscribes from them\n */\n async #unsubscribeFromAllAccountActivity(): Promise<void> {\n const accountActivitySubscriptions = this.#messenger.call(\n 'BackendWebSocketService:findSubscriptionsByChannelPrefix',\n this.#options.subscriptionNamespace,\n );\n\n // Unsubscribe from all matching subscriptions\n for (const subscription of accountActivitySubscriptions) {\n await subscription.unsubscribe();\n }\n }\n\n // =============================================================================\n // Private Methods - Utility Functions\n // =============================================================================\n\n /**\n * Convert an InternalAccount address to CAIP-10 format or raw address\n *\n * @param account - The internal account to convert\n * @returns The CAIP-10 formatted address or raw address\n */\n #convertToCaip10Address(account: InternalAccount): string {\n // Check if account has EVM scopes\n if (account.scopes.some((scope) => scope.startsWith('eip155:'))) {\n // CAIP-10 format: eip155:0:address (subscribe to all EVM chains)\n return `eip155:0:${account.address}`;\n }\n\n // Check if account has Solana scopes\n if (account.scopes.some((scope) => scope.startsWith('solana:'))) {\n // CAIP-10 format: solana:0:address (subscribe to all Solana chains)\n return `solana:0:${account.address}`;\n }\n\n // For other chains or unknown scopes, return raw address\n return account.address;\n }\n\n /**\n * Force WebSocket reconnection to clean up subscription state\n */\n async #forceReconnection(): Promise<void> {\n log('Forcing WebSocket reconnection to clean up subscription state');\n\n // Use the dedicated forceReconnection method which performs a controlled\n // disconnect-then-connect sequence to clean up subscription state\n await this.#messenger.call('BackendWebSocketService:forceReconnection');\n }\n\n // =============================================================================\n // Public Methods - Cleanup\n // =============================================================================\n\n /**\n * Destroy the service and clean up all resources\n * Optimized for fast cleanup during service destruction or mobile app termination\n */\n destroy(): void {\n // Clean up system notification callback\n this.#messenger.call(\n 'BackendWebSocketService:removeChannelCallback',\n `system-notifications.v1.${this.#options.subscriptionNamespace}`,\n );\n }\n}\n"]}
1
+ {"version":3,"file":"AccountActivityService.cjs","sourceRoot":"","sources":["../src/AccountActivityService.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Account Activity Service for monitoring account transactions and balance changes\n *\n * This service subscribes to account activity and receives all transactions\n * and balance updates for those accounts via the comprehensive AccountActivityMessage format.\n */\n\nimport type {\n AccountsControllerGetSelectedAccountAction,\n AccountsControllerSelectedAccountChangeEvent,\n} from '@metamask/accounts-controller';\nimport type { TraceCallback } from '@metamask/controller-utils';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport type { Messenger } from '@metamask/messenger';\n\nimport type { AccountActivityServiceMethodActions } from './AccountActivityService-method-action-types';\nimport type {\n WebSocketConnectionInfo,\n BackendWebSocketServiceConnectionStateChangedEvent,\n ServerNotificationMessage,\n} from './BackendWebSocketService';\nimport { WebSocketState } from './BackendWebSocketService';\nimport type { BackendWebSocketServiceMethodActions } from './BackendWebSocketService-method-action-types';\nimport { projectLogger, createModuleLogger } from './logger';\nimport type {\n Transaction,\n AccountActivityMessage,\n BalanceUpdate,\n} from './types';\n\n// =============================================================================\n// Types and Constants\n// =============================================================================\n\n/**\n * System notification data for chain status updates\n */\nexport type SystemNotificationData = {\n /** Array of chain IDs affected (e.g., ['eip155:137', 'eip155:1']) */\n chainIds: string[];\n /** Status of the chains: 'down' or 'up' */\n status: 'down' | 'up';\n /** Timestamp of the notification */\n timestamp?: number;\n};\n\nconst SERVICE_NAME = 'AccountActivityService';\n\nconst log = createModuleLogger(projectLogger, SERVICE_NAME);\n\nconst MESSENGER_EXPOSED_METHODS = ['subscribe', 'unsubscribe'] as const;\n\nconst SUBSCRIPTION_NAMESPACE = 'account-activity.v1';\n\n/**\n * Account subscription options\n */\nexport type SubscriptionOptions = {\n address: string; // Should be in CAIP-10 format, e.g., \"eip155:0:0x1234...\" or \"solana:0:ABC123...\"\n};\n\n/**\n * Configuration options for the account activity service\n */\nexport type AccountActivityServiceOptions = {\n /** Custom subscription namespace (default: 'account-activity.v1') */\n subscriptionNamespace?: string;\n /** Optional callback to trace performance of account activity operations (default: no-op) */\n traceFn?: TraceCallback;\n};\n\n// =============================================================================\n// Action and Event Types\n// =============================================================================\n\n// Action types for the messaging system - using generated method actions\nexport type AccountActivityServiceActions = AccountActivityServiceMethodActions;\n\n// Allowed actions that AccountActivityService can call on other controllers\nexport const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_ACTIONS = [\n 'AccountsController:getSelectedAccount',\n 'BackendWebSocketService:connect',\n 'BackendWebSocketService:forceReconnection',\n 'BackendWebSocketService:subscribe',\n 'BackendWebSocketService:getConnectionInfo',\n 'BackendWebSocketService:channelHasSubscription',\n 'BackendWebSocketService:getSubscriptionsByChannel',\n 'BackendWebSocketService:findSubscriptionsByChannelPrefix',\n 'BackendWebSocketService:addChannelCallback',\n 'BackendWebSocketService:removeChannelCallback',\n] as const;\n\n// Allowed events that AccountActivityService can listen to\nexport const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_EVENTS = [\n 'AccountsController:selectedAccountChange',\n 'BackendWebSocketService:connectionStateChanged',\n] as const;\n\nexport type AllowedActions =\n | AccountsControllerGetSelectedAccountAction\n | BackendWebSocketServiceMethodActions;\n\n// Event types for the messaging system\n\nexport type AccountActivityServiceTransactionUpdatedEvent = {\n type: `AccountActivityService:transactionUpdated`;\n payload: [Transaction];\n};\n\nexport type AccountActivityServiceBalanceUpdatedEvent = {\n type: `AccountActivityService:balanceUpdated`;\n payload: [{ address: string; chain: string; updates: BalanceUpdate[] }];\n};\n\nexport type AccountActivityServiceSubscriptionErrorEvent = {\n type: `AccountActivityService:subscriptionError`;\n payload: [{ addresses: string[]; error: string; operation: string }];\n};\n\nexport type AccountActivityServiceStatusChangedEvent = {\n type: `AccountActivityService:statusChanged`;\n payload: [\n {\n chainIds: string[];\n status: 'up' | 'down';\n timestamp?: number;\n },\n ];\n};\n\nexport type AccountActivityServiceEvents =\n | AccountActivityServiceTransactionUpdatedEvent\n | AccountActivityServiceBalanceUpdatedEvent\n | AccountActivityServiceSubscriptionErrorEvent\n | AccountActivityServiceStatusChangedEvent;\n\nexport type AllowedEvents =\n | AccountsControllerSelectedAccountChangeEvent\n | BackendWebSocketServiceConnectionStateChangedEvent;\n\nexport type AccountActivityServiceMessenger = Messenger<\n typeof SERVICE_NAME,\n AccountActivityServiceActions | AllowedActions,\n AccountActivityServiceEvents | AllowedEvents\n>;\n\n// =============================================================================\n// Main Service Class\n// =============================================================================\n\n/**\n * High-performance service for real-time account activity monitoring using optimized\n * WebSocket subscriptions with direct callback routing. Automatically subscribes to\n * the currently selected account and switches subscriptions when the selected account changes.\n * Receives transactions and balance updates using the comprehensive AccountActivityMessage format.\n *\n * Performance Features:\n * - Direct callback routing (no EventEmitter overhead)\n * - Minimal subscription tracking (no duplication with BackendWebSocketService)\n * - Optimized cleanup for mobile environments\n * - Single-account subscription (only selected account)\n * - Comprehensive balance updates with transfer tracking\n *\n * Architecture:\n * - Uses messenger pattern to communicate with BackendWebSocketService\n * - AccountActivityService tracks channel-to-subscriptionId mappings via messenger calls\n * - Automatically subscribes to selected account on initialization\n * - Switches subscriptions when selected account changes\n * - No direct dependency on BackendWebSocketService (uses messenger instead)\n *\n * @example\n * ```typescript\n * const service = new AccountActivityService({\n * messenger: activityMessenger,\n * });\n *\n * // Service automatically subscribes to the currently selected account\n * // When user switches accounts, service automatically resubscribes\n *\n * // All transactions and balance updates are received via optimized\n * // WebSocket callbacks and processed with zero-allocation routing\n * // Balance updates include comprehensive transfer details and post-transaction balances\n * ```\n */\nexport class AccountActivityService {\n /**\n * The name of the service.\n */\n readonly name = SERVICE_NAME;\n\n readonly #messenger: AccountActivityServiceMessenger;\n\n readonly #options: Required<Omit<AccountActivityServiceOptions, 'traceFn'>>;\n\n readonly #trace: TraceCallback;\n\n // Track chains that are currently up (based on system notifications)\n readonly #chainsUp: Set<string> = new Set();\n\n // =============================================================================\n // Constructor and Initialization\n // =============================================================================\n\n /**\n * Creates a new Account Activity service instance\n *\n * @param options - Configuration options including messenger\n */\n constructor(\n options: AccountActivityServiceOptions & {\n messenger: AccountActivityServiceMessenger;\n },\n ) {\n this.#messenger = options.messenger;\n\n // Set configuration with defaults\n this.#options = {\n subscriptionNamespace:\n options.subscriptionNamespace ?? SUBSCRIPTION_NAMESPACE,\n };\n\n // Default to no-op trace function to keep core platform-agnostic\n this.#trace =\n options.traceFn ??\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (((_request: any, fn?: any) => fn?.()) as TraceCallback);\n\n this.#messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n this.#messenger.subscribe(\n 'AccountsController:selectedAccountChange',\n // Promise result intentionally not awaited\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n async (account: InternalAccount) =>\n await this.#handleSelectedAccountChange(account),\n );\n this.#messenger.subscribe(\n 'BackendWebSocketService:connectionStateChanged',\n // Promise result intentionally not awaited\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n (connectionInfo: WebSocketConnectionInfo) =>\n this.#handleWebSocketStateChange(connectionInfo),\n );\n this.#messenger.call('BackendWebSocketService:addChannelCallback', {\n channelName: `system-notifications.v1.${this.#options.subscriptionNamespace}`,\n callback: (notification: ServerNotificationMessage) =>\n this.#handleSystemNotification(notification),\n });\n }\n\n // =============================================================================\n // Account Subscription Methods\n // =============================================================================\n\n /**\n * Subscribe to account activity (transactions and balance updates)\n * Address should be in CAIP-10 format (e.g., \"eip155:0:0x1234...\" or \"solana:0:ABC123...\")\n *\n * @param subscription - Account subscription configuration with address\n */\n async subscribe(subscription: SubscriptionOptions): Promise<void> {\n try {\n await this.#messenger.call('BackendWebSocketService:connect');\n\n // Create channel name from address\n const channel = `${this.#options.subscriptionNamespace}.${subscription.address}`;\n\n // Check if already subscribed\n if (\n this.#messenger.call(\n 'BackendWebSocketService:channelHasSubscription',\n channel,\n )\n ) {\n return;\n }\n\n // Create subscription using the proper subscribe method (this will be stored in WebSocketService's internal tracking)\n await this.#messenger.call('BackendWebSocketService:subscribe', {\n channels: [channel],\n channelType: this.#options.subscriptionNamespace, // e.g., 'account-activity.v1'\n callback: (notification: ServerNotificationMessage) => {\n this.#handleAccountActivityUpdate(\n notification.data as AccountActivityMessage,\n );\n },\n });\n } catch (error) {\n log('Subscription failed, forcing reconnection', { error });\n await this.#forceReconnection();\n }\n }\n\n /**\n * Unsubscribe from account activity for specified address\n * Address should be in CAIP-10 format (e.g., \"eip155:0:0x1234...\" or \"solana:0:ABC123...\")\n *\n * @param subscription - Account subscription configuration with address to unsubscribe\n */\n async unsubscribe(subscription: SubscriptionOptions): Promise<void> {\n const { address } = subscription;\n try {\n // Find channel for the specified address\n const channel = `${this.#options.subscriptionNamespace}.${address}`;\n const subscriptions = this.#messenger.call(\n 'BackendWebSocketService:getSubscriptionsByChannel',\n channel,\n );\n\n if (subscriptions.length === 0) {\n return;\n }\n\n // Fast path: Direct unsubscribe using stored unsubscribe function\n // Unsubscribe from all matching subscriptions\n for (const subscriptionInfo of subscriptions) {\n await subscriptionInfo.unsubscribe();\n }\n } catch (error) {\n log('Unsubscription failed, forcing reconnection', { error });\n await this.#forceReconnection();\n }\n }\n\n // =============================================================================\n // Private Methods - Event Handlers\n // =============================================================================\n\n /**\n * Handle account activity updates (transactions + balance changes)\n * Processes the comprehensive AccountActivityMessage format with detailed balance updates and transfers\n *\n * @param payload - The account activity message containing transaction and balance updates\n * @example AccountActivityMessage format handling:\n * Input: {\n * address: \"0xd14b52362b5b777ffa754c666ddec6722aaeee08\",\n * tx: { id: \"0x1cde...\", chain: \"eip155:8453\", status: \"confirmed\", timestamp: 1760099871, ... },\n * updates: [{\n * asset: { fungible: true, type: \"eip155:8453/erc20:0x833...\", unit: \"USDC\", decimals: 6 },\n * postBalance: { amount: \"0xc350\" },\n * transfers: [{ from: \"0x7b07...\", to: \"0xd14b...\", amount: \"0x2710\" }]\n * }]\n * }\n * Output: Transaction and balance updates published separately\n */\n #handleAccountActivityUpdate(payload: AccountActivityMessage): void {\n const { address, tx, updates } = payload;\n\n // Calculate time elapsed between transaction time and message receipt\n const txTimestampMs = tx.timestamp * 1000; // Convert Unix timestamp (seconds) to milliseconds\n const elapsedMs = Date.now() - txTimestampMs;\n\n log('Handling account activity update', {\n address,\n updateCount: updates.length,\n elapsedMs,\n });\n\n // Trace message receipt with latency from transaction time to now\n // Promise result intentionally not awaited\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.#trace(\n {\n name: `${SERVICE_NAME} Transaction Message`,\n data: {\n chain: tx.chain,\n status: tx.status,\n elapsed_ms: elapsedMs,\n },\n tags: {\n service: SERVICE_NAME,\n notification_type: this.#options.subscriptionNamespace,\n },\n },\n () => {\n // Process transaction update\n this.#messenger.publish(\n `AccountActivityService:transactionUpdated`,\n tx,\n );\n\n // Publish comprehensive balance updates with transfer details\n this.#messenger.publish(`AccountActivityService:balanceUpdated`, {\n address,\n chain: tx.chain,\n updates,\n });\n },\n );\n }\n\n /**\n * Handle selected account change event\n *\n * @param newAccount - The newly selected account\n */\n async #handleSelectedAccountChange(\n newAccount: InternalAccount | null,\n ): Promise<void> {\n if (!newAccount?.address) {\n return;\n }\n\n try {\n // Convert new account to CAIP-10 format\n const newAddress = this.#convertToCaip10Address(newAccount);\n\n // First, unsubscribe from all current account activity subscriptions to avoid multiple subscriptions\n await this.#unsubscribeFromAllAccountActivity();\n\n // Then, subscribe to the new selected account\n await this.subscribe({ address: newAddress });\n } catch (error) {\n log('Account change failed', { error });\n }\n }\n\n /**\n * Handle system notification for chain status changes\n * Publishes only the status change (delta) for affected chains\n *\n * @param notification - Server notification message containing chain status updates and timestamp\n */\n #handleSystemNotification(notification: ServerNotificationMessage): void {\n const data = notification.data as SystemNotificationData;\n const { timestamp } = notification;\n\n // Validate required fields\n if (!data.chainIds || !Array.isArray(data.chainIds) || !data.status) {\n throw new Error(\n 'Invalid system notification data: missing chainIds or status',\n );\n }\n\n // Track chain status\n if (data.status === 'up') {\n for (const chainId of data.chainIds) {\n this.#chainsUp.add(chainId);\n }\n } else {\n for (const chainId of data.chainIds) {\n this.#chainsUp.delete(chainId);\n }\n }\n\n // Publish status change directly (delta update)\n this.#messenger.publish(`AccountActivityService:statusChanged`, {\n chainIds: data.chainIds,\n status: data.status,\n timestamp,\n });\n\n log(\n `WebSocket status change - Published tracked chains as ${data.status}`,\n {\n count: data.chainIds.length,\n chains: data.chainIds,\n status: data.status,\n },\n );\n }\n\n /**\n * Handle WebSocket connection state changes for fallback polling and resubscription\n *\n * @param connectionInfo - WebSocket connection state information\n */\n async #handleWebSocketStateChange(\n connectionInfo: WebSocketConnectionInfo,\n ): Promise<void> {\n const { state } = connectionInfo;\n\n if (state === WebSocketState.CONNECTED) {\n // WebSocket connected - resubscribe to selected account\n // The system notification will automatically provide the list of chains that are up\n await this.#subscribeToSelectedAccount();\n } else if (state === WebSocketState.DISCONNECTED) {\n // On disconnect, flush all tracked chains as down\n const chainsToMarkDown = Array.from(this.#chainsUp);\n\n if (chainsToMarkDown.length > 0) {\n this.#messenger.publish(`AccountActivityService:statusChanged`, {\n chainIds: chainsToMarkDown,\n status: 'down',\n timestamp: Date.now(),\n });\n\n log('WebSocket disconnection - Published tracked chains as down', {\n count: chainsToMarkDown.length,\n chains: chainsToMarkDown,\n });\n\n // Clear the tracking set since all chains are now down\n this.#chainsUp.clear();\n }\n }\n }\n\n // =============================================================================\n // Private Methods - Subscription Management\n // =============================================================================\n\n /**\n * Subscribe to the currently selected account only\n */\n async #subscribeToSelectedAccount(): Promise<void> {\n const selectedAccount = this.#messenger.call(\n 'AccountsController:getSelectedAccount',\n );\n\n if (!selectedAccount || !selectedAccount.address) {\n return;\n }\n\n // Convert to CAIP-10 format and subscribe\n const address = this.#convertToCaip10Address(selectedAccount);\n await this.subscribe({ address });\n }\n\n /**\n * Unsubscribe from all account activity subscriptions for this service\n * Finds all channels matching the service's namespace and unsubscribes from them\n */\n async #unsubscribeFromAllAccountActivity(): Promise<void> {\n const accountActivitySubscriptions = this.#messenger.call(\n 'BackendWebSocketService:findSubscriptionsByChannelPrefix',\n this.#options.subscriptionNamespace,\n );\n\n // Unsubscribe from all matching subscriptions\n for (const subscription of accountActivitySubscriptions) {\n await subscription.unsubscribe();\n }\n }\n\n // =============================================================================\n // Private Methods - Utility Functions\n // =============================================================================\n\n /**\n * Convert an InternalAccount address to CAIP-10 format or raw address\n *\n * @param account - The internal account to convert\n * @returns The CAIP-10 formatted address or raw address\n */\n #convertToCaip10Address(account: InternalAccount): string {\n // Check if account has EVM scopes\n if (account.scopes.some((scope) => scope.startsWith('eip155:'))) {\n // CAIP-10 format: eip155:0:address (subscribe to all EVM chains)\n return `eip155:0:${account.address}`;\n }\n\n // Check if account has Solana scopes\n if (account.scopes.some((scope) => scope.startsWith('solana:'))) {\n // CAIP-10 format: solana:0:address (subscribe to all Solana chains)\n return `solana:0:${account.address}`;\n }\n\n // For other chains or unknown scopes, return raw address\n return account.address;\n }\n\n /**\n * Force WebSocket reconnection to clean up subscription state\n */\n async #forceReconnection(): Promise<void> {\n log('Forcing WebSocket reconnection to clean up subscription state');\n\n // Use the dedicated forceReconnection method which performs a controlled\n // disconnect-then-connect sequence to clean up subscription state\n await this.#messenger.call('BackendWebSocketService:forceReconnection');\n }\n\n // =============================================================================\n // Public Methods - Cleanup\n // =============================================================================\n\n /**\n * Destroy the service and clean up all resources\n * Optimized for fast cleanup during service destruction or mobile app termination\n */\n destroy(): void {\n // Clean up system notification callback\n this.#messenger.call(\n 'BackendWebSocketService:removeChannelCallback',\n `system-notifications.v1.${this.#options.subscriptionNamespace}`,\n );\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"AccountActivityService.d.cts","sourceRoot":"","sources":["../src/AccountActivityService.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AccountActivityService.d.cts","sourceRoot":"","sources":["../src/AccountActivityService.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"AccountActivityService.d.mts","sourceRoot":"","sources":["../src/AccountActivityService.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AccountActivityService.d.mts","sourceRoot":"","sources":["../src/AccountActivityService.ts"],"names":[],"mappings":"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"}
@@ -107,8 +107,14 @@ export class AccountActivityService {
107
107
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
108
108
  ((_request, fn) => fn?.()), "f");
109
109
  __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
110
- __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").subscribe('AccountsController:selectedAccountChange', async (account) => await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_handleSelectedAccountChange).call(this, account));
111
- __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").subscribe('BackendWebSocketService:connectionStateChanged', (connectionInfo) => __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_handleWebSocketStateChange).call(this, connectionInfo));
110
+ __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").subscribe('AccountsController:selectedAccountChange',
111
+ // Promise result intentionally not awaited
112
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
113
+ async (account) => await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_handleSelectedAccountChange).call(this, account));
114
+ __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").subscribe('BackendWebSocketService:connectionStateChanged',
115
+ // Promise result intentionally not awaited
116
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
117
+ (connectionInfo) => __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_handleWebSocketStateChange).call(this, connectionInfo));
112
118
  __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('BackendWebSocketService:addChannelCallback', {
113
119
  channelName: `system-notifications.v1.${__classPrivateFieldGet(this, _AccountActivityService_options, "f").subscriptionNamespace}`,
114
120
  callback: (notification) => __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_handleSystemNotification).call(this, notification),
@@ -135,7 +141,7 @@ export class AccountActivityService {
135
141
  // Create subscription using the proper subscribe method (this will be stored in WebSocketService's internal tracking)
136
142
  await __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('BackendWebSocketService:subscribe', {
137
143
  channels: [channel],
138
- channelType: __classPrivateFieldGet(this, _AccountActivityService_options, "f").subscriptionNamespace,
144
+ channelType: __classPrivateFieldGet(this, _AccountActivityService_options, "f").subscriptionNamespace, // e.g., 'account-activity.v1'
139
145
  callback: (notification) => {
140
146
  __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_handleAccountActivityUpdate).call(this, notification.data);
141
147
  },
@@ -195,6 +201,8 @@ _AccountActivityService_messenger = new WeakMap(), _AccountActivityService_optio
195
201
  elapsedMs,
196
202
  });
197
203
  // Trace message receipt with latency from transaction time to now
204
+ // Promise result intentionally not awaited
205
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
198
206
  __classPrivateFieldGet(this, _AccountActivityService_trace, "f").call(this, {
199
207
  name: `${SERVICE_NAME} Transaction Message`,
200
208
  data: {
@@ -261,6 +269,11 @@ async function _AccountActivityService_handleSelectedAccountChange(newAccount) {
261
269
  status: data.status,
262
270
  timestamp,
263
271
  });
272
+ log(`WebSocket status change - Published tracked chains as ${data.status}`, {
273
+ count: data.chainIds.length,
274
+ chains: data.chainIds,
275
+ status: data.status,
276
+ });
264
277
  }, _AccountActivityService_handleWebSocketStateChange =
265
278
  /**
266
279
  * Handle WebSocket connection state changes for fallback polling and resubscription
@@ -274,9 +287,8 @@ async function _AccountActivityService_handleWebSocketStateChange(connectionInfo
274
287
  // The system notification will automatically provide the list of chains that are up
275
288
  await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_subscribeToSelectedAccount).call(this);
276
289
  }
277
- else if (state === WebSocketState.DISCONNECTED ||
278
- state === WebSocketState.ERROR) {
279
- // On disconnect/error, flush all tracked chains as down
290
+ else if (state === WebSocketState.DISCONNECTED) {
291
+ // On disconnect, flush all tracked chains as down
280
292
  const chainsToMarkDown = Array.from(__classPrivateFieldGet(this, _AccountActivityService_chainsUp, "f"));
281
293
  if (chainsToMarkDown.length > 0) {
282
294
  __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").publish(`AccountActivityService:statusChanged`, {
@@ -284,7 +296,7 @@ async function _AccountActivityService_handleWebSocketStateChange(connectionInfo
284
296
  status: 'down',
285
297
  timestamp: Date.now(),
286
298
  });
287
- log('WebSocket error/disconnection - Published tracked chains as down', {
299
+ log('WebSocket disconnection - Published tracked chains as down', {
288
300
  count: chainsToMarkDown.length,
289
301
  chains: chainsToMarkDown,
290
302
  });
@@ -1 +1 @@
1
- {"version":3,"file":"AccountActivityService.mjs","sourceRoot":"","sources":["../src/AccountActivityService.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Account Activity Service for monitoring account transactions and balance changes\n *\n * This service subscribes to account activity and receives all transactions\n * and balance updates for those accounts via the comprehensive AccountActivityMessage format.\n */\n\nimport type {\n AccountsControllerGetSelectedAccountAction,\n AccountsControllerSelectedAccountChangeEvent,\n} from '@metamask/accounts-controller';\nimport type { TraceCallback } from '@metamask/controller-utils';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport type { Messenger } from '@metamask/messenger';\n\nimport type { AccountActivityServiceMethodActions } from './AccountActivityService-method-action-types';\nimport type {\n WebSocketConnectionInfo,\n BackendWebSocketServiceConnectionStateChangedEvent,\n ServerNotificationMessage,\n} from './BackendWebSocketService';\nimport { WebSocketState } from './BackendWebSocketService';\nimport type { BackendWebSocketServiceMethodActions } from './BackendWebSocketService-method-action-types';\nimport { projectLogger, createModuleLogger } from './logger';\nimport type {\n Transaction,\n AccountActivityMessage,\n BalanceUpdate,\n} from './types';\n\n// =============================================================================\n// Types and Constants\n// =============================================================================\n\n/**\n * System notification data for chain status updates\n */\nexport type SystemNotificationData = {\n /** Array of chain IDs affected (e.g., ['eip155:137', 'eip155:1']) */\n chainIds: string[];\n /** Status of the chains: 'down' or 'up' */\n status: 'down' | 'up';\n /** Timestamp of the notification */\n timestamp?: number;\n};\n\nconst SERVICE_NAME = 'AccountActivityService';\n\nconst log = createModuleLogger(projectLogger, SERVICE_NAME);\n\nconst MESSENGER_EXPOSED_METHODS = ['subscribe', 'unsubscribe'] as const;\n\nconst SUBSCRIPTION_NAMESPACE = 'account-activity.v1';\n\n/**\n * Account subscription options\n */\nexport type SubscriptionOptions = {\n address: string; // Should be in CAIP-10 format, e.g., \"eip155:0:0x1234...\" or \"solana:0:ABC123...\"\n};\n\n/**\n * Configuration options for the account activity service\n */\nexport type AccountActivityServiceOptions = {\n /** Custom subscription namespace (default: 'account-activity.v1') */\n subscriptionNamespace?: string;\n /** Optional callback to trace performance of account activity operations (default: no-op) */\n traceFn?: TraceCallback;\n};\n\n// =============================================================================\n// Action and Event Types\n// =============================================================================\n\n// Action types for the messaging system - using generated method actions\nexport type AccountActivityServiceActions = AccountActivityServiceMethodActions;\n\n// Allowed actions that AccountActivityService can call on other controllers\nexport const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_ACTIONS = [\n 'AccountsController:getSelectedAccount',\n 'BackendWebSocketService:connect',\n 'BackendWebSocketService:forceReconnection',\n 'BackendWebSocketService:subscribe',\n 'BackendWebSocketService:getConnectionInfo',\n 'BackendWebSocketService:channelHasSubscription',\n 'BackendWebSocketService:getSubscriptionsByChannel',\n 'BackendWebSocketService:findSubscriptionsByChannelPrefix',\n 'BackendWebSocketService:addChannelCallback',\n 'BackendWebSocketService:removeChannelCallback',\n] as const;\n\n// Allowed events that AccountActivityService can listen to\nexport const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_EVENTS = [\n 'AccountsController:selectedAccountChange',\n 'BackendWebSocketService:connectionStateChanged',\n] as const;\n\nexport type AllowedActions =\n | AccountsControllerGetSelectedAccountAction\n | BackendWebSocketServiceMethodActions;\n\n// Event types for the messaging system\n\nexport type AccountActivityServiceTransactionUpdatedEvent = {\n type: `AccountActivityService:transactionUpdated`;\n payload: [Transaction];\n};\n\nexport type AccountActivityServiceBalanceUpdatedEvent = {\n type: `AccountActivityService:balanceUpdated`;\n payload: [{ address: string; chain: string; updates: BalanceUpdate[] }];\n};\n\nexport type AccountActivityServiceSubscriptionErrorEvent = {\n type: `AccountActivityService:subscriptionError`;\n payload: [{ addresses: string[]; error: string; operation: string }];\n};\n\nexport type AccountActivityServiceStatusChangedEvent = {\n type: `AccountActivityService:statusChanged`;\n payload: [\n {\n chainIds: string[];\n status: 'up' | 'down';\n timestamp?: number;\n },\n ];\n};\n\nexport type AccountActivityServiceEvents =\n | AccountActivityServiceTransactionUpdatedEvent\n | AccountActivityServiceBalanceUpdatedEvent\n | AccountActivityServiceSubscriptionErrorEvent\n | AccountActivityServiceStatusChangedEvent;\n\nexport type AllowedEvents =\n | AccountsControllerSelectedAccountChangeEvent\n | BackendWebSocketServiceConnectionStateChangedEvent;\n\nexport type AccountActivityServiceMessenger = Messenger<\n typeof SERVICE_NAME,\n AccountActivityServiceActions | AllowedActions,\n AccountActivityServiceEvents | AllowedEvents\n>;\n\n// =============================================================================\n// Main Service Class\n// =============================================================================\n\n/**\n * High-performance service for real-time account activity monitoring using optimized\n * WebSocket subscriptions with direct callback routing. Automatically subscribes to\n * the currently selected account and switches subscriptions when the selected account changes.\n * Receives transactions and balance updates using the comprehensive AccountActivityMessage format.\n *\n * Performance Features:\n * - Direct callback routing (no EventEmitter overhead)\n * - Minimal subscription tracking (no duplication with BackendWebSocketService)\n * - Optimized cleanup for mobile environments\n * - Single-account subscription (only selected account)\n * - Comprehensive balance updates with transfer tracking\n *\n * Architecture:\n * - Uses messenger pattern to communicate with BackendWebSocketService\n * - AccountActivityService tracks channel-to-subscriptionId mappings via messenger calls\n * - Automatically subscribes to selected account on initialization\n * - Switches subscriptions when selected account changes\n * - No direct dependency on BackendWebSocketService (uses messenger instead)\n *\n * @example\n * ```typescript\n * const service = new AccountActivityService({\n * messenger: activityMessenger,\n * });\n *\n * // Service automatically subscribes to the currently selected account\n * // When user switches accounts, service automatically resubscribes\n *\n * // All transactions and balance updates are received via optimized\n * // WebSocket callbacks and processed with zero-allocation routing\n * // Balance updates include comprehensive transfer details and post-transaction balances\n * ```\n */\nexport class AccountActivityService {\n /**\n * The name of the service.\n */\n readonly name = SERVICE_NAME;\n\n readonly #messenger: AccountActivityServiceMessenger;\n\n readonly #options: Required<Omit<AccountActivityServiceOptions, 'traceFn'>>;\n\n readonly #trace: TraceCallback;\n\n // Track chains that are currently up (based on system notifications)\n readonly #chainsUp: Set<string> = new Set();\n\n // =============================================================================\n // Constructor and Initialization\n // =============================================================================\n\n /**\n * Creates a new Account Activity service instance\n *\n * @param options - Configuration options including messenger\n */\n constructor(\n options: AccountActivityServiceOptions & {\n messenger: AccountActivityServiceMessenger;\n },\n ) {\n this.#messenger = options.messenger;\n\n // Set configuration with defaults\n this.#options = {\n subscriptionNamespace:\n options.subscriptionNamespace ?? SUBSCRIPTION_NAMESPACE,\n };\n\n // Default to no-op trace function to keep core platform-agnostic\n this.#trace =\n options.traceFn ??\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (((_request: any, fn?: any) => fn?.()) as TraceCallback);\n\n this.#messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n this.#messenger.subscribe(\n 'AccountsController:selectedAccountChange',\n async (account: InternalAccount) =>\n await this.#handleSelectedAccountChange(account),\n );\n this.#messenger.subscribe(\n 'BackendWebSocketService:connectionStateChanged',\n (connectionInfo: WebSocketConnectionInfo) =>\n this.#handleWebSocketStateChange(connectionInfo),\n );\n this.#messenger.call('BackendWebSocketService:addChannelCallback', {\n channelName: `system-notifications.v1.${this.#options.subscriptionNamespace}`,\n callback: (notification: ServerNotificationMessage) =>\n this.#handleSystemNotification(notification),\n });\n }\n\n // =============================================================================\n // Account Subscription Methods\n // =============================================================================\n\n /**\n * Subscribe to account activity (transactions and balance updates)\n * Address should be in CAIP-10 format (e.g., \"eip155:0:0x1234...\" or \"solana:0:ABC123...\")\n *\n * @param subscription - Account subscription configuration with address\n */\n async subscribe(subscription: SubscriptionOptions): Promise<void> {\n try {\n await this.#messenger.call('BackendWebSocketService:connect');\n\n // Create channel name from address\n const channel = `${this.#options.subscriptionNamespace}.${subscription.address}`;\n\n // Check if already subscribed\n if (\n this.#messenger.call(\n 'BackendWebSocketService:channelHasSubscription',\n channel,\n )\n ) {\n return;\n }\n\n // Create subscription using the proper subscribe method (this will be stored in WebSocketService's internal tracking)\n await this.#messenger.call('BackendWebSocketService:subscribe', {\n channels: [channel],\n channelType: this.#options.subscriptionNamespace, // e.g., 'account-activity.v1'\n callback: (notification: ServerNotificationMessage) => {\n this.#handleAccountActivityUpdate(\n notification.data as AccountActivityMessage,\n );\n },\n });\n } catch (error) {\n log('Subscription failed, forcing reconnection', { error });\n await this.#forceReconnection();\n }\n }\n\n /**\n * Unsubscribe from account activity for specified address\n * Address should be in CAIP-10 format (e.g., \"eip155:0:0x1234...\" or \"solana:0:ABC123...\")\n *\n * @param subscription - Account subscription configuration with address to unsubscribe\n */\n async unsubscribe(subscription: SubscriptionOptions): Promise<void> {\n const { address } = subscription;\n try {\n // Find channel for the specified address\n const channel = `${this.#options.subscriptionNamespace}.${address}`;\n const subscriptions = this.#messenger.call(\n 'BackendWebSocketService:getSubscriptionsByChannel',\n channel,\n );\n\n if (subscriptions.length === 0) {\n return;\n }\n\n // Fast path: Direct unsubscribe using stored unsubscribe function\n // Unsubscribe from all matching subscriptions\n for (const subscriptionInfo of subscriptions) {\n await subscriptionInfo.unsubscribe();\n }\n } catch (error) {\n log('Unsubscription failed, forcing reconnection', { error });\n await this.#forceReconnection();\n }\n }\n\n // =============================================================================\n // Private Methods - Event Handlers\n // =============================================================================\n\n /**\n * Handle account activity updates (transactions + balance changes)\n * Processes the comprehensive AccountActivityMessage format with detailed balance updates and transfers\n *\n * @param payload - The account activity message containing transaction and balance updates\n * @example AccountActivityMessage format handling:\n * Input: {\n * address: \"0xd14b52362b5b777ffa754c666ddec6722aaeee08\",\n * tx: { id: \"0x1cde...\", chain: \"eip155:8453\", status: \"confirmed\", timestamp: 1760099871, ... },\n * updates: [{\n * asset: { fungible: true, type: \"eip155:8453/erc20:0x833...\", unit: \"USDC\", decimals: 6 },\n * postBalance: { amount: \"0xc350\" },\n * transfers: [{ from: \"0x7b07...\", to: \"0xd14b...\", amount: \"0x2710\" }]\n * }]\n * }\n * Output: Transaction and balance updates published separately\n */\n #handleAccountActivityUpdate(payload: AccountActivityMessage): void {\n const { address, tx, updates } = payload;\n\n // Calculate time elapsed between transaction time and message receipt\n const txTimestampMs = tx.timestamp * 1000; // Convert Unix timestamp (seconds) to milliseconds\n const elapsedMs = Date.now() - txTimestampMs;\n\n log('Handling account activity update', {\n address,\n updateCount: updates.length,\n elapsedMs,\n });\n\n // Trace message receipt with latency from transaction time to now\n this.#trace(\n {\n name: `${SERVICE_NAME} Transaction Message`,\n data: {\n chain: tx.chain,\n status: tx.status,\n elapsed_ms: elapsedMs,\n },\n tags: {\n service: SERVICE_NAME,\n notification_type: this.#options.subscriptionNamespace,\n },\n },\n () => {\n // Process transaction update\n this.#messenger.publish(\n `AccountActivityService:transactionUpdated`,\n tx,\n );\n\n // Publish comprehensive balance updates with transfer details\n this.#messenger.publish(`AccountActivityService:balanceUpdated`, {\n address,\n chain: tx.chain,\n updates,\n });\n },\n );\n }\n\n /**\n * Handle selected account change event\n *\n * @param newAccount - The newly selected account\n */\n async #handleSelectedAccountChange(\n newAccount: InternalAccount | null,\n ): Promise<void> {\n if (!newAccount?.address) {\n return;\n }\n\n try {\n // Convert new account to CAIP-10 format\n const newAddress = this.#convertToCaip10Address(newAccount);\n\n // First, unsubscribe from all current account activity subscriptions to avoid multiple subscriptions\n await this.#unsubscribeFromAllAccountActivity();\n\n // Then, subscribe to the new selected account\n await this.subscribe({ address: newAddress });\n } catch (error) {\n log('Account change failed', { error });\n }\n }\n\n /**\n * Handle system notification for chain status changes\n * Publishes only the status change (delta) for affected chains\n *\n * @param notification - Server notification message containing chain status updates and timestamp\n */\n #handleSystemNotification(notification: ServerNotificationMessage): void {\n const data = notification.data as SystemNotificationData;\n const { timestamp } = notification;\n\n // Validate required fields\n if (!data.chainIds || !Array.isArray(data.chainIds) || !data.status) {\n throw new Error(\n 'Invalid system notification data: missing chainIds or status',\n );\n }\n\n // Track chain status\n if (data.status === 'up') {\n for (const chainId of data.chainIds) {\n this.#chainsUp.add(chainId);\n }\n } else {\n for (const chainId of data.chainIds) {\n this.#chainsUp.delete(chainId);\n }\n }\n\n // Publish status change directly (delta update)\n this.#messenger.publish(`AccountActivityService:statusChanged`, {\n chainIds: data.chainIds,\n status: data.status,\n timestamp,\n });\n }\n\n /**\n * Handle WebSocket connection state changes for fallback polling and resubscription\n *\n * @param connectionInfo - WebSocket connection state information\n */\n async #handleWebSocketStateChange(\n connectionInfo: WebSocketConnectionInfo,\n ): Promise<void> {\n const { state } = connectionInfo;\n\n if (state === WebSocketState.CONNECTED) {\n // WebSocket connected - resubscribe to selected account\n // The system notification will automatically provide the list of chains that are up\n await this.#subscribeToSelectedAccount();\n } else if (\n state === WebSocketState.DISCONNECTED ||\n state === WebSocketState.ERROR\n ) {\n // On disconnect/error, flush all tracked chains as down\n const chainsToMarkDown = Array.from(this.#chainsUp);\n\n if (chainsToMarkDown.length > 0) {\n this.#messenger.publish(`AccountActivityService:statusChanged`, {\n chainIds: chainsToMarkDown,\n status: 'down',\n timestamp: Date.now(),\n });\n\n log(\n 'WebSocket error/disconnection - Published tracked chains as down',\n {\n count: chainsToMarkDown.length,\n chains: chainsToMarkDown,\n },\n );\n\n // Clear the tracking set since all chains are now down\n this.#chainsUp.clear();\n }\n }\n }\n\n // =============================================================================\n // Private Methods - Subscription Management\n // =============================================================================\n\n /**\n * Subscribe to the currently selected account only\n */\n async #subscribeToSelectedAccount(): Promise<void> {\n const selectedAccount = this.#messenger.call(\n 'AccountsController:getSelectedAccount',\n );\n\n if (!selectedAccount || !selectedAccount.address) {\n return;\n }\n\n // Convert to CAIP-10 format and subscribe\n const address = this.#convertToCaip10Address(selectedAccount);\n await this.subscribe({ address });\n }\n\n /**\n * Unsubscribe from all account activity subscriptions for this service\n * Finds all channels matching the service's namespace and unsubscribes from them\n */\n async #unsubscribeFromAllAccountActivity(): Promise<void> {\n const accountActivitySubscriptions = this.#messenger.call(\n 'BackendWebSocketService:findSubscriptionsByChannelPrefix',\n this.#options.subscriptionNamespace,\n );\n\n // Unsubscribe from all matching subscriptions\n for (const subscription of accountActivitySubscriptions) {\n await subscription.unsubscribe();\n }\n }\n\n // =============================================================================\n // Private Methods - Utility Functions\n // =============================================================================\n\n /**\n * Convert an InternalAccount address to CAIP-10 format or raw address\n *\n * @param account - The internal account to convert\n * @returns The CAIP-10 formatted address or raw address\n */\n #convertToCaip10Address(account: InternalAccount): string {\n // Check if account has EVM scopes\n if (account.scopes.some((scope) => scope.startsWith('eip155:'))) {\n // CAIP-10 format: eip155:0:address (subscribe to all EVM chains)\n return `eip155:0:${account.address}`;\n }\n\n // Check if account has Solana scopes\n if (account.scopes.some((scope) => scope.startsWith('solana:'))) {\n // CAIP-10 format: solana:0:address (subscribe to all Solana chains)\n return `solana:0:${account.address}`;\n }\n\n // For other chains or unknown scopes, return raw address\n return account.address;\n }\n\n /**\n * Force WebSocket reconnection to clean up subscription state\n */\n async #forceReconnection(): Promise<void> {\n log('Forcing WebSocket reconnection to clean up subscription state');\n\n // Use the dedicated forceReconnection method which performs a controlled\n // disconnect-then-connect sequence to clean up subscription state\n await this.#messenger.call('BackendWebSocketService:forceReconnection');\n }\n\n // =============================================================================\n // Public Methods - Cleanup\n // =============================================================================\n\n /**\n * Destroy the service and clean up all resources\n * Optimized for fast cleanup during service destruction or mobile app termination\n */\n destroy(): void {\n // Clean up system notification callback\n this.#messenger.call(\n 'BackendWebSocketService:removeChannelCallback',\n `system-notifications.v1.${this.#options.subscriptionNamespace}`,\n );\n }\n}\n"]}
1
+ {"version":3,"file":"AccountActivityService.mjs","sourceRoot":"","sources":["../src/AccountActivityService.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Account Activity Service for monitoring account transactions and balance changes\n *\n * This service subscribes to account activity and receives all transactions\n * and balance updates for those accounts via the comprehensive AccountActivityMessage format.\n */\n\nimport type {\n AccountsControllerGetSelectedAccountAction,\n AccountsControllerSelectedAccountChangeEvent,\n} from '@metamask/accounts-controller';\nimport type { TraceCallback } from '@metamask/controller-utils';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport type { Messenger } from '@metamask/messenger';\n\nimport type { AccountActivityServiceMethodActions } from './AccountActivityService-method-action-types';\nimport type {\n WebSocketConnectionInfo,\n BackendWebSocketServiceConnectionStateChangedEvent,\n ServerNotificationMessage,\n} from './BackendWebSocketService';\nimport { WebSocketState } from './BackendWebSocketService';\nimport type { BackendWebSocketServiceMethodActions } from './BackendWebSocketService-method-action-types';\nimport { projectLogger, createModuleLogger } from './logger';\nimport type {\n Transaction,\n AccountActivityMessage,\n BalanceUpdate,\n} from './types';\n\n// =============================================================================\n// Types and Constants\n// =============================================================================\n\n/**\n * System notification data for chain status updates\n */\nexport type SystemNotificationData = {\n /** Array of chain IDs affected (e.g., ['eip155:137', 'eip155:1']) */\n chainIds: string[];\n /** Status of the chains: 'down' or 'up' */\n status: 'down' | 'up';\n /** Timestamp of the notification */\n timestamp?: number;\n};\n\nconst SERVICE_NAME = 'AccountActivityService';\n\nconst log = createModuleLogger(projectLogger, SERVICE_NAME);\n\nconst MESSENGER_EXPOSED_METHODS = ['subscribe', 'unsubscribe'] as const;\n\nconst SUBSCRIPTION_NAMESPACE = 'account-activity.v1';\n\n/**\n * Account subscription options\n */\nexport type SubscriptionOptions = {\n address: string; // Should be in CAIP-10 format, e.g., \"eip155:0:0x1234...\" or \"solana:0:ABC123...\"\n};\n\n/**\n * Configuration options for the account activity service\n */\nexport type AccountActivityServiceOptions = {\n /** Custom subscription namespace (default: 'account-activity.v1') */\n subscriptionNamespace?: string;\n /** Optional callback to trace performance of account activity operations (default: no-op) */\n traceFn?: TraceCallback;\n};\n\n// =============================================================================\n// Action and Event Types\n// =============================================================================\n\n// Action types for the messaging system - using generated method actions\nexport type AccountActivityServiceActions = AccountActivityServiceMethodActions;\n\n// Allowed actions that AccountActivityService can call on other controllers\nexport const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_ACTIONS = [\n 'AccountsController:getSelectedAccount',\n 'BackendWebSocketService:connect',\n 'BackendWebSocketService:forceReconnection',\n 'BackendWebSocketService:subscribe',\n 'BackendWebSocketService:getConnectionInfo',\n 'BackendWebSocketService:channelHasSubscription',\n 'BackendWebSocketService:getSubscriptionsByChannel',\n 'BackendWebSocketService:findSubscriptionsByChannelPrefix',\n 'BackendWebSocketService:addChannelCallback',\n 'BackendWebSocketService:removeChannelCallback',\n] as const;\n\n// Allowed events that AccountActivityService can listen to\nexport const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_EVENTS = [\n 'AccountsController:selectedAccountChange',\n 'BackendWebSocketService:connectionStateChanged',\n] as const;\n\nexport type AllowedActions =\n | AccountsControllerGetSelectedAccountAction\n | BackendWebSocketServiceMethodActions;\n\n// Event types for the messaging system\n\nexport type AccountActivityServiceTransactionUpdatedEvent = {\n type: `AccountActivityService:transactionUpdated`;\n payload: [Transaction];\n};\n\nexport type AccountActivityServiceBalanceUpdatedEvent = {\n type: `AccountActivityService:balanceUpdated`;\n payload: [{ address: string; chain: string; updates: BalanceUpdate[] }];\n};\n\nexport type AccountActivityServiceSubscriptionErrorEvent = {\n type: `AccountActivityService:subscriptionError`;\n payload: [{ addresses: string[]; error: string; operation: string }];\n};\n\nexport type AccountActivityServiceStatusChangedEvent = {\n type: `AccountActivityService:statusChanged`;\n payload: [\n {\n chainIds: string[];\n status: 'up' | 'down';\n timestamp?: number;\n },\n ];\n};\n\nexport type AccountActivityServiceEvents =\n | AccountActivityServiceTransactionUpdatedEvent\n | AccountActivityServiceBalanceUpdatedEvent\n | AccountActivityServiceSubscriptionErrorEvent\n | AccountActivityServiceStatusChangedEvent;\n\nexport type AllowedEvents =\n | AccountsControllerSelectedAccountChangeEvent\n | BackendWebSocketServiceConnectionStateChangedEvent;\n\nexport type AccountActivityServiceMessenger = Messenger<\n typeof SERVICE_NAME,\n AccountActivityServiceActions | AllowedActions,\n AccountActivityServiceEvents | AllowedEvents\n>;\n\n// =============================================================================\n// Main Service Class\n// =============================================================================\n\n/**\n * High-performance service for real-time account activity monitoring using optimized\n * WebSocket subscriptions with direct callback routing. Automatically subscribes to\n * the currently selected account and switches subscriptions when the selected account changes.\n * Receives transactions and balance updates using the comprehensive AccountActivityMessage format.\n *\n * Performance Features:\n * - Direct callback routing (no EventEmitter overhead)\n * - Minimal subscription tracking (no duplication with BackendWebSocketService)\n * - Optimized cleanup for mobile environments\n * - Single-account subscription (only selected account)\n * - Comprehensive balance updates with transfer tracking\n *\n * Architecture:\n * - Uses messenger pattern to communicate with BackendWebSocketService\n * - AccountActivityService tracks channel-to-subscriptionId mappings via messenger calls\n * - Automatically subscribes to selected account on initialization\n * - Switches subscriptions when selected account changes\n * - No direct dependency on BackendWebSocketService (uses messenger instead)\n *\n * @example\n * ```typescript\n * const service = new AccountActivityService({\n * messenger: activityMessenger,\n * });\n *\n * // Service automatically subscribes to the currently selected account\n * // When user switches accounts, service automatically resubscribes\n *\n * // All transactions and balance updates are received via optimized\n * // WebSocket callbacks and processed with zero-allocation routing\n * // Balance updates include comprehensive transfer details and post-transaction balances\n * ```\n */\nexport class AccountActivityService {\n /**\n * The name of the service.\n */\n readonly name = SERVICE_NAME;\n\n readonly #messenger: AccountActivityServiceMessenger;\n\n readonly #options: Required<Omit<AccountActivityServiceOptions, 'traceFn'>>;\n\n readonly #trace: TraceCallback;\n\n // Track chains that are currently up (based on system notifications)\n readonly #chainsUp: Set<string> = new Set();\n\n // =============================================================================\n // Constructor and Initialization\n // =============================================================================\n\n /**\n * Creates a new Account Activity service instance\n *\n * @param options - Configuration options including messenger\n */\n constructor(\n options: AccountActivityServiceOptions & {\n messenger: AccountActivityServiceMessenger;\n },\n ) {\n this.#messenger = options.messenger;\n\n // Set configuration with defaults\n this.#options = {\n subscriptionNamespace:\n options.subscriptionNamespace ?? SUBSCRIPTION_NAMESPACE,\n };\n\n // Default to no-op trace function to keep core platform-agnostic\n this.#trace =\n options.traceFn ??\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (((_request: any, fn?: any) => fn?.()) as TraceCallback);\n\n this.#messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n this.#messenger.subscribe(\n 'AccountsController:selectedAccountChange',\n // Promise result intentionally not awaited\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n async (account: InternalAccount) =>\n await this.#handleSelectedAccountChange(account),\n );\n this.#messenger.subscribe(\n 'BackendWebSocketService:connectionStateChanged',\n // Promise result intentionally not awaited\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n (connectionInfo: WebSocketConnectionInfo) =>\n this.#handleWebSocketStateChange(connectionInfo),\n );\n this.#messenger.call('BackendWebSocketService:addChannelCallback', {\n channelName: `system-notifications.v1.${this.#options.subscriptionNamespace}`,\n callback: (notification: ServerNotificationMessage) =>\n this.#handleSystemNotification(notification),\n });\n }\n\n // =============================================================================\n // Account Subscription Methods\n // =============================================================================\n\n /**\n * Subscribe to account activity (transactions and balance updates)\n * Address should be in CAIP-10 format (e.g., \"eip155:0:0x1234...\" or \"solana:0:ABC123...\")\n *\n * @param subscription - Account subscription configuration with address\n */\n async subscribe(subscription: SubscriptionOptions): Promise<void> {\n try {\n await this.#messenger.call('BackendWebSocketService:connect');\n\n // Create channel name from address\n const channel = `${this.#options.subscriptionNamespace}.${subscription.address}`;\n\n // Check if already subscribed\n if (\n this.#messenger.call(\n 'BackendWebSocketService:channelHasSubscription',\n channel,\n )\n ) {\n return;\n }\n\n // Create subscription using the proper subscribe method (this will be stored in WebSocketService's internal tracking)\n await this.#messenger.call('BackendWebSocketService:subscribe', {\n channels: [channel],\n channelType: this.#options.subscriptionNamespace, // e.g., 'account-activity.v1'\n callback: (notification: ServerNotificationMessage) => {\n this.#handleAccountActivityUpdate(\n notification.data as AccountActivityMessage,\n );\n },\n });\n } catch (error) {\n log('Subscription failed, forcing reconnection', { error });\n await this.#forceReconnection();\n }\n }\n\n /**\n * Unsubscribe from account activity for specified address\n * Address should be in CAIP-10 format (e.g., \"eip155:0:0x1234...\" or \"solana:0:ABC123...\")\n *\n * @param subscription - Account subscription configuration with address to unsubscribe\n */\n async unsubscribe(subscription: SubscriptionOptions): Promise<void> {\n const { address } = subscription;\n try {\n // Find channel for the specified address\n const channel = `${this.#options.subscriptionNamespace}.${address}`;\n const subscriptions = this.#messenger.call(\n 'BackendWebSocketService:getSubscriptionsByChannel',\n channel,\n );\n\n if (subscriptions.length === 0) {\n return;\n }\n\n // Fast path: Direct unsubscribe using stored unsubscribe function\n // Unsubscribe from all matching subscriptions\n for (const subscriptionInfo of subscriptions) {\n await subscriptionInfo.unsubscribe();\n }\n } catch (error) {\n log('Unsubscription failed, forcing reconnection', { error });\n await this.#forceReconnection();\n }\n }\n\n // =============================================================================\n // Private Methods - Event Handlers\n // =============================================================================\n\n /**\n * Handle account activity updates (transactions + balance changes)\n * Processes the comprehensive AccountActivityMessage format with detailed balance updates and transfers\n *\n * @param payload - The account activity message containing transaction and balance updates\n * @example AccountActivityMessage format handling:\n * Input: {\n * address: \"0xd14b52362b5b777ffa754c666ddec6722aaeee08\",\n * tx: { id: \"0x1cde...\", chain: \"eip155:8453\", status: \"confirmed\", timestamp: 1760099871, ... },\n * updates: [{\n * asset: { fungible: true, type: \"eip155:8453/erc20:0x833...\", unit: \"USDC\", decimals: 6 },\n * postBalance: { amount: \"0xc350\" },\n * transfers: [{ from: \"0x7b07...\", to: \"0xd14b...\", amount: \"0x2710\" }]\n * }]\n * }\n * Output: Transaction and balance updates published separately\n */\n #handleAccountActivityUpdate(payload: AccountActivityMessage): void {\n const { address, tx, updates } = payload;\n\n // Calculate time elapsed between transaction time and message receipt\n const txTimestampMs = tx.timestamp * 1000; // Convert Unix timestamp (seconds) to milliseconds\n const elapsedMs = Date.now() - txTimestampMs;\n\n log('Handling account activity update', {\n address,\n updateCount: updates.length,\n elapsedMs,\n });\n\n // Trace message receipt with latency from transaction time to now\n // Promise result intentionally not awaited\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.#trace(\n {\n name: `${SERVICE_NAME} Transaction Message`,\n data: {\n chain: tx.chain,\n status: tx.status,\n elapsed_ms: elapsedMs,\n },\n tags: {\n service: SERVICE_NAME,\n notification_type: this.#options.subscriptionNamespace,\n },\n },\n () => {\n // Process transaction update\n this.#messenger.publish(\n `AccountActivityService:transactionUpdated`,\n tx,\n );\n\n // Publish comprehensive balance updates with transfer details\n this.#messenger.publish(`AccountActivityService:balanceUpdated`, {\n address,\n chain: tx.chain,\n updates,\n });\n },\n );\n }\n\n /**\n * Handle selected account change event\n *\n * @param newAccount - The newly selected account\n */\n async #handleSelectedAccountChange(\n newAccount: InternalAccount | null,\n ): Promise<void> {\n if (!newAccount?.address) {\n return;\n }\n\n try {\n // Convert new account to CAIP-10 format\n const newAddress = this.#convertToCaip10Address(newAccount);\n\n // First, unsubscribe from all current account activity subscriptions to avoid multiple subscriptions\n await this.#unsubscribeFromAllAccountActivity();\n\n // Then, subscribe to the new selected account\n await this.subscribe({ address: newAddress });\n } catch (error) {\n log('Account change failed', { error });\n }\n }\n\n /**\n * Handle system notification for chain status changes\n * Publishes only the status change (delta) for affected chains\n *\n * @param notification - Server notification message containing chain status updates and timestamp\n */\n #handleSystemNotification(notification: ServerNotificationMessage): void {\n const data = notification.data as SystemNotificationData;\n const { timestamp } = notification;\n\n // Validate required fields\n if (!data.chainIds || !Array.isArray(data.chainIds) || !data.status) {\n throw new Error(\n 'Invalid system notification data: missing chainIds or status',\n );\n }\n\n // Track chain status\n if (data.status === 'up') {\n for (const chainId of data.chainIds) {\n this.#chainsUp.add(chainId);\n }\n } else {\n for (const chainId of data.chainIds) {\n this.#chainsUp.delete(chainId);\n }\n }\n\n // Publish status change directly (delta update)\n this.#messenger.publish(`AccountActivityService:statusChanged`, {\n chainIds: data.chainIds,\n status: data.status,\n timestamp,\n });\n\n log(\n `WebSocket status change - Published tracked chains as ${data.status}`,\n {\n count: data.chainIds.length,\n chains: data.chainIds,\n status: data.status,\n },\n );\n }\n\n /**\n * Handle WebSocket connection state changes for fallback polling and resubscription\n *\n * @param connectionInfo - WebSocket connection state information\n */\n async #handleWebSocketStateChange(\n connectionInfo: WebSocketConnectionInfo,\n ): Promise<void> {\n const { state } = connectionInfo;\n\n if (state === WebSocketState.CONNECTED) {\n // WebSocket connected - resubscribe to selected account\n // The system notification will automatically provide the list of chains that are up\n await this.#subscribeToSelectedAccount();\n } else if (state === WebSocketState.DISCONNECTED) {\n // On disconnect, flush all tracked chains as down\n const chainsToMarkDown = Array.from(this.#chainsUp);\n\n if (chainsToMarkDown.length > 0) {\n this.#messenger.publish(`AccountActivityService:statusChanged`, {\n chainIds: chainsToMarkDown,\n status: 'down',\n timestamp: Date.now(),\n });\n\n log('WebSocket disconnection - Published tracked chains as down', {\n count: chainsToMarkDown.length,\n chains: chainsToMarkDown,\n });\n\n // Clear the tracking set since all chains are now down\n this.#chainsUp.clear();\n }\n }\n }\n\n // =============================================================================\n // Private Methods - Subscription Management\n // =============================================================================\n\n /**\n * Subscribe to the currently selected account only\n */\n async #subscribeToSelectedAccount(): Promise<void> {\n const selectedAccount = this.#messenger.call(\n 'AccountsController:getSelectedAccount',\n );\n\n if (!selectedAccount || !selectedAccount.address) {\n return;\n }\n\n // Convert to CAIP-10 format and subscribe\n const address = this.#convertToCaip10Address(selectedAccount);\n await this.subscribe({ address });\n }\n\n /**\n * Unsubscribe from all account activity subscriptions for this service\n * Finds all channels matching the service's namespace and unsubscribes from them\n */\n async #unsubscribeFromAllAccountActivity(): Promise<void> {\n const accountActivitySubscriptions = this.#messenger.call(\n 'BackendWebSocketService:findSubscriptionsByChannelPrefix',\n this.#options.subscriptionNamespace,\n );\n\n // Unsubscribe from all matching subscriptions\n for (const subscription of accountActivitySubscriptions) {\n await subscription.unsubscribe();\n }\n }\n\n // =============================================================================\n // Private Methods - Utility Functions\n // =============================================================================\n\n /**\n * Convert an InternalAccount address to CAIP-10 format or raw address\n *\n * @param account - The internal account to convert\n * @returns The CAIP-10 formatted address or raw address\n */\n #convertToCaip10Address(account: InternalAccount): string {\n // Check if account has EVM scopes\n if (account.scopes.some((scope) => scope.startsWith('eip155:'))) {\n // CAIP-10 format: eip155:0:address (subscribe to all EVM chains)\n return `eip155:0:${account.address}`;\n }\n\n // Check if account has Solana scopes\n if (account.scopes.some((scope) => scope.startsWith('solana:'))) {\n // CAIP-10 format: solana:0:address (subscribe to all Solana chains)\n return `solana:0:${account.address}`;\n }\n\n // For other chains or unknown scopes, return raw address\n return account.address;\n }\n\n /**\n * Force WebSocket reconnection to clean up subscription state\n */\n async #forceReconnection(): Promise<void> {\n log('Forcing WebSocket reconnection to clean up subscription state');\n\n // Use the dedicated forceReconnection method which performs a controlled\n // disconnect-then-connect sequence to clean up subscription state\n await this.#messenger.call('BackendWebSocketService:forceReconnection');\n }\n\n // =============================================================================\n // Public Methods - Cleanup\n // =============================================================================\n\n /**\n * Destroy the service and clean up all resources\n * Optimized for fast cleanup during service destruction or mobile app termination\n */\n destroy(): void {\n // Clean up system notification callback\n this.#messenger.call(\n 'BackendWebSocketService:removeChannelCallback',\n `system-notifications.v1.${this.#options.subscriptionNamespace}`,\n );\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"BackendWebSocketService-method-action-types.cjs","sourceRoot":"","sources":["../src/BackendWebSocketService-method-action-types.ts"],"names":[],"mappings":";AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated by `scripts/generate-method-action-types.ts`.\n * Do not edit manually.\n */\n\nimport type { BackendWebSocketService } from './BackendWebSocketService';\n\n/**\n * Establishes WebSocket connection\n *\n * @returns Promise that resolves when connection is established\n */\nexport type BackendWebSocketServiceConnectAction = {\n type: `BackendWebSocketService:connect`;\n handler: BackendWebSocketService['connect'];\n};\n\n/**\n * Closes WebSocket connection\n *\n * @returns Promise that resolves when disconnection is complete\n */\nexport type BackendWebSocketServiceDisconnectAction = {\n type: `BackendWebSocketService:disconnect`;\n handler: BackendWebSocketService['disconnect'];\n};\n\n/**\n * Forces a WebSocket reconnection to clean up subscription state\n *\n * This method is useful when subscription state may be out of sync and needs to be reset.\n * It performs a controlled disconnect-then-reconnect sequence:\n * - Disconnects cleanly to trigger subscription cleanup\n * - Schedules reconnection with exponential backoff to prevent rapid loops\n * - All subscriptions will be cleaned up automatically on disconnect\n *\n * Use cases:\n * - Recovering from subscription/unsubscription issues\n * - Cleaning up orphaned subscriptions\n * - Forcing a fresh subscription state\n *\n * @returns Promise that resolves when disconnection is complete (reconnection is scheduled)\n */\nexport type BackendWebSocketServiceForceReconnectionAction = {\n type: `BackendWebSocketService:forceReconnection`;\n handler: BackendWebSocketService['forceReconnection'];\n};\n\n/**\n * Sends a message through the WebSocket\n *\n * @param message - The message to send\n * @returns Promise that resolves when message is sent\n */\nexport type BackendWebSocketServiceSendMessageAction = {\n type: `BackendWebSocketService:sendMessage`;\n handler: BackendWebSocketService['sendMessage'];\n};\n\n/**\n * Sends a request and waits for a correlated response\n *\n * @param message - The request message\n * @returns Promise that resolves with the response data\n */\nexport type BackendWebSocketServiceSendRequestAction = {\n type: `BackendWebSocketService:sendRequest`;\n handler: BackendWebSocketService['sendRequest'];\n};\n\n/**\n * Gets current connection information\n *\n * @returns Current connection status and details\n */\nexport type BackendWebSocketServiceGetConnectionInfoAction = {\n type: `BackendWebSocketService:getConnectionInfo`;\n handler: BackendWebSocketService['getConnectionInfo'];\n};\n\n/**\n * Gets all subscription information for a specific channel\n *\n * @param channel - The channel name to look up\n * @returns Array of subscription details for all subscriptions containing the channel\n */\nexport type BackendWebSocketServiceGetSubscriptionsByChannelAction = {\n type: `BackendWebSocketService:getSubscriptionsByChannel`;\n handler: BackendWebSocketService['getSubscriptionsByChannel'];\n};\n\n/**\n * Checks if a channel has a subscription\n *\n * @param channel - The channel name to check\n * @returns True if the channel has a subscription, false otherwise\n */\nexport type BackendWebSocketServiceChannelHasSubscriptionAction = {\n type: `BackendWebSocketService:channelHasSubscription`;\n handler: BackendWebSocketService['channelHasSubscription'];\n};\n\n/**\n * Finds all subscriptions that have channels starting with the specified prefix\n *\n * @param channelPrefix - The channel prefix to search for (e.g., \"account-activity.v1\")\n * @returns Array of subscription info for matching subscriptions\n */\nexport type BackendWebSocketServiceFindSubscriptionsByChannelPrefixAction = {\n type: `BackendWebSocketService:findSubscriptionsByChannelPrefix`;\n handler: BackendWebSocketService['findSubscriptionsByChannelPrefix'];\n};\n\n/**\n * Register a callback for specific channels\n *\n * @param options - Channel callback configuration\n * @param options.channelName - Channel name to match exactly\n * @param options.callback - Function to call when channel matches\n *\n * @example\n * ```typescript\n * // Listen to specific account activity channel\n * webSocketService.addChannelCallback({\n * channelName: 'account-activity.v1.eip155:0:0x1234...',\n * callback: (notification) => {\n * console.log('Account activity:', notification.data);\n * }\n * });\n *\n * // Listen to system notifications channel\n * webSocketService.addChannelCallback({\n * channelName: 'system-notifications.v1',\n * callback: (notification) => {\n * console.log('System notification:', notification.data);\n * }\n * });\n * ```\n */\nexport type BackendWebSocketServiceAddChannelCallbackAction = {\n type: `BackendWebSocketService:addChannelCallback`;\n handler: BackendWebSocketService['addChannelCallback'];\n};\n\n/**\n * Remove a channel callback\n *\n * @param channelName - The channel name to remove callback for\n * @returns True if callback was found and removed, false otherwise\n */\nexport type BackendWebSocketServiceRemoveChannelCallbackAction = {\n type: `BackendWebSocketService:removeChannelCallback`;\n handler: BackendWebSocketService['removeChannelCallback'];\n};\n\n/**\n * Get all registered channel callbacks (for debugging)\n */\nexport type BackendWebSocketServiceGetChannelCallbacksAction = {\n type: `BackendWebSocketService:getChannelCallbacks`;\n handler: BackendWebSocketService['getChannelCallbacks'];\n};\n\n/**\n * Create and manage a subscription with direct callback routing\n *\n * @param options - Subscription configuration\n * @param options.channels - Array of channel names to subscribe to\n * @param options.callback - Callback function for handling notifications\n * @returns Promise that resolves with subscription object containing unsubscribe method\n */\nexport type BackendWebSocketServiceSubscribeAction = {\n type: `BackendWebSocketService:subscribe`;\n handler: BackendWebSocketService['subscribe'];\n};\n\n/**\n * Union of all BackendWebSocketService action types.\n */\nexport type BackendWebSocketServiceMethodActions =\n | BackendWebSocketServiceConnectAction\n | BackendWebSocketServiceDisconnectAction\n | BackendWebSocketServiceForceReconnectionAction\n | BackendWebSocketServiceSendMessageAction\n | BackendWebSocketServiceSendRequestAction\n | BackendWebSocketServiceGetConnectionInfoAction\n | BackendWebSocketServiceGetSubscriptionsByChannelAction\n | BackendWebSocketServiceChannelHasSubscriptionAction\n | BackendWebSocketServiceFindSubscriptionsByChannelPrefixAction\n | BackendWebSocketServiceAddChannelCallbackAction\n | BackendWebSocketServiceRemoveChannelCallbackAction\n | BackendWebSocketServiceGetChannelCallbacksAction\n | BackendWebSocketServiceSubscribeAction;\n"]}
1
+ {"version":3,"file":"BackendWebSocketService-method-action-types.cjs","sourceRoot":"","sources":["../src/BackendWebSocketService-method-action-types.ts"],"names":[],"mappings":";AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated by `scripts/generate-method-action-types.ts`.\n * Do not edit manually.\n */\n\nimport type { BackendWebSocketService } from './BackendWebSocketService';\n\n/**\n * Establishes WebSocket connection with smart reconnection behavior\n *\n * Connection Requirements (all must be true):\n * 1. Feature enabled (isEnabled() = true)\n * 2. Wallet unlocked (checked by getBearerToken)\n * 3. User signed in (checked by getBearerToken)\n *\n * Platform code should call this when app opens/foregrounds.\n * Automatically called on KeyringController:unlock event.\n *\n * @returns Promise that resolves when connection is established\n */\nexport type BackendWebSocketServiceConnectAction = {\n type: `BackendWebSocketService:connect`;\n handler: BackendWebSocketService['connect'];\n};\n\n/**\n * Closes WebSocket connection\n */\nexport type BackendWebSocketServiceDisconnectAction = {\n type: `BackendWebSocketService:disconnect`;\n handler: BackendWebSocketService['disconnect'];\n};\n\n/**\n * Forces a WebSocket reconnection to clean up subscription state\n *\n * This method is useful when subscription state may be out of sync and needs to be reset.\n * It performs a controlled disconnect-then-reconnect sequence:\n * - Disconnects cleanly to trigger subscription cleanup\n * - Schedules reconnection with exponential backoff to prevent rapid loops\n * - All subscriptions will be cleaned up automatically on disconnect\n *\n * Use cases:\n * - Recovering from subscription/unsubscription issues\n * - Cleaning up orphaned subscriptions\n * - Forcing a fresh subscription state\n *\n * @returns Promise that resolves when disconnection is complete (reconnection is scheduled)\n */\nexport type BackendWebSocketServiceForceReconnectionAction = {\n type: `BackendWebSocketService:forceReconnection`;\n handler: BackendWebSocketService['forceReconnection'];\n};\n\n/**\n * Sends a message through the WebSocket (fire-and-forget, no response expected)\n *\n * This is a low-level method for sending messages without waiting for a response.\n * Most consumers should use `sendRequest()` instead, which handles request-response\n * correlation and provides proper error handling with timeouts.\n *\n * Use this method only when:\n * - You don't need a response from the server\n * - You're implementing custom message protocols\n * - You need fine-grained control over message timing\n *\n * @param message - The message to send\n * @throws Error if WebSocket is not connected or send fails\n *\n * @see sendRequest for request-response pattern with automatic correlation\n */\nexport type BackendWebSocketServiceSendMessageAction = {\n type: `BackendWebSocketService:sendMessage`;\n handler: BackendWebSocketService['sendMessage'];\n};\n\n/**\n * Sends a request and waits for a correlated response (recommended for most use cases)\n *\n * This is the recommended high-level method for request-response communication.\n * It automatically handles:\n * - Request ID generation and correlation\n * - Response matching with timeout protection\n * - Automatic reconnection on timeout\n * - Proper cleanup of pending requests\n *\n * @param message - The request message (can include optional requestId for testing)\n * @returns Promise that resolves with the response data\n * @throws Error if WebSocket is not connected, request times out, or response indicates failure\n *\n * @see sendMessage for fire-and-forget messaging without response handling\n */\nexport type BackendWebSocketServiceSendRequestAction = {\n type: `BackendWebSocketService:sendRequest`;\n handler: BackendWebSocketService['sendRequest'];\n};\n\n/**\n * Gets current connection information\n *\n * @returns Current connection status and details\n */\nexport type BackendWebSocketServiceGetConnectionInfoAction = {\n type: `BackendWebSocketService:getConnectionInfo`;\n handler: BackendWebSocketService['getConnectionInfo'];\n};\n\n/**\n * Gets all subscription information for a specific channel\n *\n * @param channel - The channel name to look up\n * @returns Array of subscription details for all subscriptions containing the channel\n */\nexport type BackendWebSocketServiceGetSubscriptionsByChannelAction = {\n type: `BackendWebSocketService:getSubscriptionsByChannel`;\n handler: BackendWebSocketService['getSubscriptionsByChannel'];\n};\n\n/**\n * Checks if a channel has a subscription\n *\n * @param channel - The channel name to check\n * @returns True if the channel has a subscription, false otherwise\n */\nexport type BackendWebSocketServiceChannelHasSubscriptionAction = {\n type: `BackendWebSocketService:channelHasSubscription`;\n handler: BackendWebSocketService['channelHasSubscription'];\n};\n\n/**\n * Finds all subscriptions that have channels starting with the specified prefix\n *\n * @param channelPrefix - The channel prefix to search for (e.g., \"account-activity.v1\")\n * @returns Array of subscription info for matching subscriptions\n */\nexport type BackendWebSocketServiceFindSubscriptionsByChannelPrefixAction = {\n type: `BackendWebSocketService:findSubscriptionsByChannelPrefix`;\n handler: BackendWebSocketService['findSubscriptionsByChannelPrefix'];\n};\n\n/**\n * Register a callback for specific channels (local callback only, no server subscription)\n *\n * **Key Difference from `subscribe()`:**\n * - `addChannelCallback()`: Registers a local callback without creating a server-side subscription.\n * The callback triggers on ANY message matching the channel name, regardless of subscriptionId.\n * Useful for system-wide notifications or when you don't control the subscription lifecycle.\n *\n * - `subscribe()`: Creates a proper server-side subscription with a subscriptionId.\n * The callback only triggers for messages with the matching subscriptionId.\n * Includes proper lifecycle management (unsubscribe, automatic cleanup on disconnect).\n *\n * **When to use `addChannelCallback()`:**\n * - Listening to system-wide notifications (e.g., 'system-notifications.v1')\n * - Monitoring channels where subscriptions are managed elsewhere\n * - Debug/logging scenarios where you want to observe all channel messages\n *\n * **When to use `subscribe()` instead:**\n * - Creating new subscriptions that need server-side registration\n * - When you need proper cleanup via unsubscribe\n * - Most application use cases (recommended approach)\n *\n * @param options - Channel callback configuration\n * @param options.channelName - Channel name to match exactly\n * @param options.callback - Function to call when channel matches\n *\n * @example\n * ```typescript\n * // Listen to system notifications (no server subscription needed)\n * webSocketService.addChannelCallback({\n * channelName: 'system-notifications.v1',\n * callback: (notification) => {\n * console.log('System notification:', notification.data);\n * }\n * });\n *\n * // For account-specific subscriptions, use subscribe() instead:\n * // const sub = await webSocketService.subscribe({\n * // channels: ['account-activity.v1.eip155:0:0x1234...'],\n * // callback: (notification) => { ... }\n * // });\n * ```\n *\n * @see subscribe for creating proper server-side subscriptions with lifecycle management\n */\nexport type BackendWebSocketServiceAddChannelCallbackAction = {\n type: `BackendWebSocketService:addChannelCallback`;\n handler: BackendWebSocketService['addChannelCallback'];\n};\n\n/**\n * Remove a channel callback\n *\n * @param channelName - The channel name returned from addChannelCallback\n * @returns True if callback was found and removed, false otherwise\n */\nexport type BackendWebSocketServiceRemoveChannelCallbackAction = {\n type: `BackendWebSocketService:removeChannelCallback`;\n handler: BackendWebSocketService['removeChannelCallback'];\n};\n\n/**\n * Get all registered channel callbacks (for debugging)\n *\n * @returns Array of all registered channel callbacks\n */\nexport type BackendWebSocketServiceGetChannelCallbacksAction = {\n type: `BackendWebSocketService:getChannelCallbacks`;\n handler: BackendWebSocketService['getChannelCallbacks'];\n};\n\n/**\n * Create and manage a subscription with server-side registration (recommended for most use cases)\n *\n * This is the recommended subscription API for high-level services. It creates a proper\n * server-side subscription and routes notifications based on subscriptionId.\n *\n * **Key Features:**\n * - Creates server-side subscription with unique subscriptionId\n * - Callback triggered only for messages with matching subscriptionId\n * - Automatic lifecycle management (cleanup on disconnect)\n * - Includes unsubscribe method for proper cleanup\n * - Request-response pattern with error handling\n *\n * **When to use `subscribe()`:**\n * - Creating new subscriptions (account activity, price updates, etc.)\n * - When you need proper cleanup/unsubscribe functionality\n * - Most application use cases\n *\n * **When to use `addChannelCallback()` instead:**\n * - System-wide notifications without server-side subscription\n * - Observing channels managed elsewhere\n * - Debug/logging scenarios\n *\n * @param options - Subscription configuration\n * @param options.channels - Array of channel names to subscribe to\n * @param options.callback - Callback function for handling notifications\n * @param options.requestId - Optional request ID for testing (will generate UUID if not provided)\n * @param options.channelType - Channel type identifier\n * @returns Subscription object with unsubscribe method\n *\n * @example\n * ```typescript\n * // AccountActivityService usage\n * const subscription = await webSocketService.subscribe({\n * channels: ['account-activity.v1.eip155:0:0x1234...'],\n * callback: (notification) => {\n * this.handleAccountActivity(notification.data);\n * }\n * });\n *\n * // Later, clean up\n * await subscription.unsubscribe();\n * ```\n *\n * @see addChannelCallback for local callbacks without server-side subscription\n */\nexport type BackendWebSocketServiceSubscribeAction = {\n type: `BackendWebSocketService:subscribe`;\n handler: BackendWebSocketService['subscribe'];\n};\n\n/**\n * Union of all BackendWebSocketService action types.\n */\nexport type BackendWebSocketServiceMethodActions =\n | BackendWebSocketServiceConnectAction\n | BackendWebSocketServiceDisconnectAction\n | BackendWebSocketServiceForceReconnectionAction\n | BackendWebSocketServiceSendMessageAction\n | BackendWebSocketServiceSendRequestAction\n | BackendWebSocketServiceGetConnectionInfoAction\n | BackendWebSocketServiceGetSubscriptionsByChannelAction\n | BackendWebSocketServiceChannelHasSubscriptionAction\n | BackendWebSocketServiceFindSubscriptionsByChannelPrefixAction\n | BackendWebSocketServiceAddChannelCallbackAction\n | BackendWebSocketServiceRemoveChannelCallbackAction\n | BackendWebSocketServiceGetChannelCallbacksAction\n | BackendWebSocketServiceSubscribeAction;\n"]}