@metamask-previews/core-backend 3.0.0-preview-9fa15fd0 → 4.0.0-preview-46d2c977
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 +17 -1
- package/dist/AccountActivityService.cjs.map +1 -1
- package/dist/AccountActivityService.d.cts +4 -4
- package/dist/AccountActivityService.d.cts.map +1 -1
- package/dist/AccountActivityService.d.mts +4 -4
- package/dist/AccountActivityService.d.mts.map +1 -1
- package/dist/AccountActivityService.mjs.map +1 -1
- package/dist/BackendWebSocketService.cjs.map +1 -1
- package/dist/BackendWebSocketService.d.cts +4 -4
- package/dist/BackendWebSocketService.d.cts.map +1 -1
- package/dist/BackendWebSocketService.d.mts +4 -4
- package/dist/BackendWebSocketService.d.mts.map +1 -1
- package/dist/BackendWebSocketService.mjs.map +1 -1
- package/dist/index.cjs +2 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -3
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +2 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +0 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -7,8 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [4.0.0]
|
|
11
|
+
|
|
10
12
|
### Changed
|
|
11
13
|
|
|
14
|
+
- **BREAKING:** Use new `Messenger` from `@metamask/messenger` ([#6823](https://github.com/MetaMask/core/pull/6823))
|
|
15
|
+
- Previously, `AccountActivityService` and `BackendWebSocketService` accepted a `RestrictedMessenger` instance from `@metamask/base-controller`.
|
|
16
|
+
- **BREAKING:** Metadata property `anonymous` renamed to `includeInDebugSnapshot` ([#6823](https://github.com/MetaMask/core/pull/6823))
|
|
17
|
+
- **BREAKING:** Bump `@metamask/accounts-controller` from `^33.0.0` to `^34.0.0` ([#6962](https://github.com/MetaMask/core/pull/6962))
|
|
18
|
+
- **BREAKING:** Bump `@metamask/keyring-controller` from `^23.0.0` to `^24.0.0` ([#6962](https://github.com/MetaMask/core/pull/6962))
|
|
19
|
+
- Bump `@metamask/profile-sync-controller` from `^25.1.2` to `^26.0.0` ([#6962](https://github.com/MetaMask/core/pull/6962))
|
|
20
|
+
|
|
21
|
+
### Removed
|
|
22
|
+
|
|
23
|
+
- **BREAKING:** Remove exported type aliases and constants that were specific to controller messenger integration ([#6823](https://github.com/MetaMask/core/pull/6823))
|
|
24
|
+
- Removed type exports: `BackendWebSocketServiceAllowedActions`, `BackendWebSocketServiceAllowedEvents`, `AccountActivityServiceAllowedActions`, `AccountActivityServiceAllowedEvents`
|
|
25
|
+
- Removed constant exports: `ACCOUNT_ACTIVITY_SERVICE_ALLOWED_ACTIONS`, `ACCOUNT_ACTIVITY_SERVICE_ALLOWED_EVENTS`
|
|
26
|
+
- These types and constants were internal implementation details that should not have been exposed. Consumers should use the service-specific messenger types directly.
|
|
12
27
|
- Bump `@metamask/profile-sync-controller` from `^25.1.1` to `^25.1.2` ([#6940](https://github.com/MetaMask/core/pull/6940))
|
|
13
28
|
|
|
14
29
|
## [3.0.0]
|
|
@@ -118,7 +133,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
118
133
|
- **Type definitions** - Comprehensive TypeScript types for transactions, balances, WebSocket messages, and service configurations
|
|
119
134
|
- **Logging infrastructure** - Structured logging with module-specific loggers for debugging and monitoring
|
|
120
135
|
|
|
121
|
-
[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/core-backend@
|
|
136
|
+
[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/core-backend@4.0.0...HEAD
|
|
137
|
+
[4.0.0]: https://github.com/MetaMask/core/compare/@metamask/core-backend@3.0.0...@metamask/core-backend@4.0.0
|
|
122
138
|
[3.0.0]: https://github.com/MetaMask/core/compare/@metamask/core-backend@2.1.0...@metamask/core-backend@3.0.0
|
|
123
139
|
[2.1.0]: https://github.com/MetaMask/core/compare/@metamask/core-backend@2.0.0...@metamask/core-backend@2.1.0
|
|
124
140
|
[2.0.0]: https://github.com/MetaMask/core/compare/@metamask/core-backend@1.0.1...@metamask/core-backend@2.0.0
|
|
@@ -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 { RestrictedMessenger } from '@metamask/base-controller';\nimport type { TraceCallback } from '@metamask/controller-utils';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\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 AccountActivityServiceAllowedActions =\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 AccountActivityServiceAllowedEvents =\n | AccountsControllerSelectedAccountChangeEvent\n | BackendWebSocketServiceConnectionStateChangedEvent;\n\nexport type AccountActivityServiceMessenger = RestrictedMessenger<\n typeof SERVICE_NAME,\n AccountActivityServiceActions | AccountActivityServiceAllowedActions,\n AccountActivityServiceEvents | AccountActivityServiceAllowedEvents,\n AccountActivityServiceAllowedActions['type'],\n AccountActivityServiceAllowedEvents['type']\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 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"]}
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* and balance updates for those accounts via the comprehensive AccountActivityMessage format.
|
|
6
6
|
*/
|
|
7
7
|
import type { AccountsControllerGetSelectedAccountAction, AccountsControllerSelectedAccountChangeEvent } from "@metamask/accounts-controller";
|
|
8
|
-
import type { RestrictedMessenger } from "@metamask/base-controller";
|
|
9
8
|
import type { TraceCallback } from "@metamask/controller-utils";
|
|
9
|
+
import type { Messenger } from "@metamask/messenger";
|
|
10
10
|
import type { AccountActivityServiceMethodActions } from "./AccountActivityService-method-action-types.cjs";
|
|
11
11
|
import type { BackendWebSocketServiceConnectionStateChangedEvent } from "./BackendWebSocketService.cjs";
|
|
12
12
|
import type { BackendWebSocketServiceMethodActions } from "./BackendWebSocketService-method-action-types.cjs";
|
|
@@ -41,7 +41,7 @@ export type AccountActivityServiceOptions = {
|
|
|
41
41
|
export type AccountActivityServiceActions = AccountActivityServiceMethodActions;
|
|
42
42
|
export declare const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_ACTIONS: readonly ["AccountsController:getSelectedAccount", "BackendWebSocketService:connect", "BackendWebSocketService:forceReconnection", "BackendWebSocketService:subscribe", "BackendWebSocketService:getConnectionInfo", "BackendWebSocketService:channelHasSubscription", "BackendWebSocketService:getSubscriptionsByChannel", "BackendWebSocketService:findSubscriptionsByChannelPrefix", "BackendWebSocketService:addChannelCallback", "BackendWebSocketService:removeChannelCallback"];
|
|
43
43
|
export declare const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_EVENTS: readonly ["AccountsController:selectedAccountChange", "BackendWebSocketService:connectionStateChanged"];
|
|
44
|
-
export type
|
|
44
|
+
export type AllowedActions = AccountsControllerGetSelectedAccountAction | BackendWebSocketServiceMethodActions;
|
|
45
45
|
export type AccountActivityServiceTransactionUpdatedEvent = {
|
|
46
46
|
type: `AccountActivityService:transactionUpdated`;
|
|
47
47
|
payload: [Transaction];
|
|
@@ -73,8 +73,8 @@ export type AccountActivityServiceStatusChangedEvent = {
|
|
|
73
73
|
];
|
|
74
74
|
};
|
|
75
75
|
export type AccountActivityServiceEvents = AccountActivityServiceTransactionUpdatedEvent | AccountActivityServiceBalanceUpdatedEvent | AccountActivityServiceSubscriptionErrorEvent | AccountActivityServiceStatusChangedEvent;
|
|
76
|
-
export type
|
|
77
|
-
export type AccountActivityServiceMessenger =
|
|
76
|
+
export type AllowedEvents = AccountsControllerSelectedAccountChangeEvent | BackendWebSocketServiceConnectionStateChangedEvent;
|
|
77
|
+
export type AccountActivityServiceMessenger = Messenger<typeof SERVICE_NAME, AccountActivityServiceActions | AllowedActions, AccountActivityServiceEvents | AllowedEvents>;
|
|
78
78
|
/**
|
|
79
79
|
* High-performance service for real-time account activity monitoring using optimized
|
|
80
80
|
* WebSocket subscriptions with direct callback routing. Automatically subscribes to
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AccountActivityService.d.cts","sourceRoot":"","sources":["../src/AccountActivityService.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,0CAA0C,EAC1C,4CAA4C,EAC7C,sCAAsC;AACvC,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"AccountActivityService.d.cts","sourceRoot":"","sources":["../src/AccountActivityService.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* and balance updates for those accounts via the comprehensive AccountActivityMessage format.
|
|
6
6
|
*/
|
|
7
7
|
import type { AccountsControllerGetSelectedAccountAction, AccountsControllerSelectedAccountChangeEvent } from "@metamask/accounts-controller";
|
|
8
|
-
import type { RestrictedMessenger } from "@metamask/base-controller";
|
|
9
8
|
import type { TraceCallback } from "@metamask/controller-utils";
|
|
9
|
+
import type { Messenger } from "@metamask/messenger";
|
|
10
10
|
import type { AccountActivityServiceMethodActions } from "./AccountActivityService-method-action-types.mjs";
|
|
11
11
|
import type { BackendWebSocketServiceConnectionStateChangedEvent } from "./BackendWebSocketService.mjs";
|
|
12
12
|
import type { BackendWebSocketServiceMethodActions } from "./BackendWebSocketService-method-action-types.mjs";
|
|
@@ -41,7 +41,7 @@ export type AccountActivityServiceOptions = {
|
|
|
41
41
|
export type AccountActivityServiceActions = AccountActivityServiceMethodActions;
|
|
42
42
|
export declare const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_ACTIONS: readonly ["AccountsController:getSelectedAccount", "BackendWebSocketService:connect", "BackendWebSocketService:forceReconnection", "BackendWebSocketService:subscribe", "BackendWebSocketService:getConnectionInfo", "BackendWebSocketService:channelHasSubscription", "BackendWebSocketService:getSubscriptionsByChannel", "BackendWebSocketService:findSubscriptionsByChannelPrefix", "BackendWebSocketService:addChannelCallback", "BackendWebSocketService:removeChannelCallback"];
|
|
43
43
|
export declare const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_EVENTS: readonly ["AccountsController:selectedAccountChange", "BackendWebSocketService:connectionStateChanged"];
|
|
44
|
-
export type
|
|
44
|
+
export type AllowedActions = AccountsControllerGetSelectedAccountAction | BackendWebSocketServiceMethodActions;
|
|
45
45
|
export type AccountActivityServiceTransactionUpdatedEvent = {
|
|
46
46
|
type: `AccountActivityService:transactionUpdated`;
|
|
47
47
|
payload: [Transaction];
|
|
@@ -73,8 +73,8 @@ export type AccountActivityServiceStatusChangedEvent = {
|
|
|
73
73
|
];
|
|
74
74
|
};
|
|
75
75
|
export type AccountActivityServiceEvents = AccountActivityServiceTransactionUpdatedEvent | AccountActivityServiceBalanceUpdatedEvent | AccountActivityServiceSubscriptionErrorEvent | AccountActivityServiceStatusChangedEvent;
|
|
76
|
-
export type
|
|
77
|
-
export type AccountActivityServiceMessenger =
|
|
76
|
+
export type AllowedEvents = AccountsControllerSelectedAccountChangeEvent | BackendWebSocketServiceConnectionStateChangedEvent;
|
|
77
|
+
export type AccountActivityServiceMessenger = Messenger<typeof SERVICE_NAME, AccountActivityServiceActions | AllowedActions, AccountActivityServiceEvents | AllowedEvents>;
|
|
78
78
|
/**
|
|
79
79
|
* High-performance service for real-time account activity monitoring using optimized
|
|
80
80
|
* WebSocket subscriptions with direct callback routing. Automatically subscribes to
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AccountActivityService.d.mts","sourceRoot":"","sources":["../src/AccountActivityService.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,0CAA0C,EAC1C,4CAA4C,EAC7C,sCAAsC;AACvC,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"AccountActivityService.d.mts","sourceRoot":"","sources":["../src/AccountActivityService.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,0CAA0C,EAC1C,4CAA4C,EAC7C,sCAAsC;AACvC,OAAO,KAAK,EAAE,aAAa,EAAE,mCAAmC;AAEhE,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AAErD,OAAO,KAAK,EAAE,mCAAmC,EAAE,yDAAqD;AACxG,OAAO,KAAK,EAEV,kDAAkD,EAEnD,sCAAkC;AAEnC,OAAO,KAAK,EAAE,oCAAoC,EAAE,0DAAsD;AAE1G,OAAO,KAAK,EACV,WAAW,EAEX,aAAa,EACd,oBAAgB;AAMjB;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACnC,qEAAqE;IACrE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,2CAA2C;IAC3C,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,oCAAoC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,QAAA,MAAM,YAAY,2BAA2B,CAAC;AAQ9C;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,6BAA6B,GAAG;IAC1C,qEAAqE;IACrE,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,6FAA6F;IAC7F,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB,CAAC;AAOF,MAAM,MAAM,6BAA6B,GAAG,mCAAmC,CAAC;AAGhF,eAAO,MAAM,wCAAwC,wdAW3C,CAAC;AAGX,eAAO,MAAM,uCAAuC,yGAG1C,CAAC;AAEX,MAAM,MAAM,cAAc,GACtB,0CAA0C,GAC1C,oCAAoC,CAAC;AAIzC,MAAM,MAAM,6CAA6C,GAAG;IAC1D,IAAI,EAAE,2CAA2C,CAAC;IAClD,OAAO,EAAE,CAAC,WAAW,CAAC,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,yCAAyC,GAAG;IACtD,IAAI,EAAE,uCAAuC,CAAC;IAC9C,OAAO,EAAE,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,aAAa,EAAE,CAAA;KAAE,CAAC,CAAC;CACzE,CAAC;AAEF,MAAM,MAAM,4CAA4C,GAAG;IACzD,IAAI,EAAE,0CAA0C,CAAC;IACjD,OAAO,EAAE,CAAC;QAAE,SAAS,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACtE,CAAC;AAEF,MAAM,MAAM,wCAAwC,GAAG;IACrD,IAAI,EAAE,sCAAsC,CAAC;IAC7C,OAAO,EAAE;QACP;YACE,QAAQ,EAAE,MAAM,EAAE,CAAC;YACnB,MAAM,EAAE,IAAI,GAAG,MAAM,CAAC;YACtB,SAAS,CAAC,EAAE,MAAM,CAAC;SACpB;KACF,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,4BAA4B,GACpC,6CAA6C,GAC7C,yCAAyC,GACzC,4CAA4C,GAC5C,wCAAwC,CAAC;AAE7C,MAAM,MAAM,aAAa,GACrB,4CAA4C,GAC5C,kDAAkD,CAAC;AAEvD,MAAM,MAAM,+BAA+B,GAAG,SAAS,CACrD,OAAO,YAAY,EACnB,6BAA6B,GAAG,cAAc,EAC9C,4BAA4B,GAAG,aAAa,CAC7C,CAAC;AAMF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,qBAAa,sBAAsB;;IACjC;;OAEG;IACH,QAAQ,CAAC,IAAI,4BAAgB;IAe7B;;;;OAIG;gBAED,OAAO,EAAE,6BAA6B,GAAG;QACvC,SAAS,EAAE,+BAA+B,CAAC;KAC5C;IAyCH;;;;;OAKG;IACG,SAAS,CAAC,YAAY,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAiCjE;;;;;OAKG;IACG,WAAW,CAAC,YAAY,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAiRnE;;;OAGG;IACH,OAAO,IAAI,IAAI;CAOhB"}
|
|
@@ -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 { RestrictedMessenger } from '@metamask/base-controller';\nimport type { TraceCallback } from '@metamask/controller-utils';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\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 AccountActivityServiceAllowedActions =\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 AccountActivityServiceAllowedEvents =\n | AccountsControllerSelectedAccountChangeEvent\n | BackendWebSocketServiceConnectionStateChangedEvent;\n\nexport type AccountActivityServiceMessenger = RestrictedMessenger<\n typeof SERVICE_NAME,\n AccountActivityServiceActions | AccountActivityServiceAllowedActions,\n AccountActivityServiceEvents | AccountActivityServiceAllowedEvents,\n AccountActivityServiceAllowedActions['type'],\n AccountActivityServiceAllowedEvents['type']\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 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"]}
|