@metamask-previews/core-backend 8.0.0-preview-a42e8d0d2 → 8.1.0-preview-5a03e1b92

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.
@@ -1 +1 @@
1
- {"version":3,"file":"AccountActivityService.mjs","sourceRoot":"","sources":["../../src/ws/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 AccountTreeControllerSelectedAccountGroupChangeEvent,\n AccountTreeControllerGetAccountsFromSelectedAccountGroupAction,\n} from '@metamask/account-tree-controller';\nimport type { TraceCallback } from '@metamask/controller-utils';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport type { Messenger } from '@metamask/messenger';\nimport type {\n FeatureFlags,\n RemoteFeatureFlagControllerGetStateAction,\n RemoteFeatureFlagControllerStateChangeEvent,\n} from '@metamask/remote-feature-flag-controller';\nimport { isObject } from '@metamask/utils';\n\nimport { projectLogger, createModuleLogger } from '../logger.js';\nimport type {\n Transaction,\n AccountActivityMessage,\n BalanceUpdate,\n} from '../types.js';\nimport type { BackendWebSocketServiceMethodActions } from './BackendWebSocketService-method-action-types.js';\nimport type {\n WebSocketConnectionInfo,\n BackendWebSocketServiceConnectionStateChangedEvent,\n ServerNotificationMessage,\n} from './BackendWebSocketService.js';\nimport { WebSocketState } from './BackendWebSocketService.js';\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 = [] as const;\n\nconst SUBSCRIPTION_NAMESPACE = 'account-activity.v1';\n\n// EVM subscriptions are always enabled.\nconst ALWAYS_SUPPORTED_CHAIN_PREFIXES = ['eip155'] as const;\n\n// Non-EVM chains are gated behind the\n// per-network snaps-migration remote feature flags: a chain is\n// enabled when its flag payload has `stage >= 1`.\nconst CHAIN_PREFIX_FEATURE_FLAGS = {\n solana: 'networkAssetsSnapsMigrationSolana',\n tron: 'networkAssetsSnapsMigrationTron',\n stellar: 'networkAssetsSnapsMigrationStellar',\n} as const;\n\n/**\n * Account subscription options\n */\nexport type SubscriptionOptions = {\n /**\n * Array of addresses to subscribe to, each in CAIP-10 format (e.g., \"eip155:0:0x1234...\" or \"solana:0:ABC123...\")\n */\n addresses: string[];\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\nexport type AccountActivityServiceActions = never;\n\n// Allowed actions that AccountActivityService can call on other controllers\nexport const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_ACTIONS = [\n 'AccountTreeController:getAccountsFromSelectedAccountGroup',\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 'RemoteFeatureFlagController:getState',\n] as const;\n\n// Allowed events that AccountActivityService can listen to\nexport const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_EVENTS = [\n 'AccountTreeController:selectedAccountGroupChange',\n 'BackendWebSocketService:connectionStateChanged',\n 'RemoteFeatureFlagController:stateChange',\n] as const;\n\nexport type AllowedActions =\n | AccountTreeControllerGetAccountsFromSelectedAccountGroupAction\n | BackendWebSocketServiceMethodActions\n | RemoteFeatureFlagControllerGetStateAction;\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 | AccountTreeControllerSelectedAccountGroupChangeEvent\n | BackendWebSocketServiceConnectionStateChangedEvent\n | RemoteFeatureFlagControllerStateChangeEvent;\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 'AccountTreeController:selectedAccountGroupChange',\n // Promise result intentionally not awaited\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n async () => await this.#handleSelectedAccountChange(),\n );\n this.#messenger.subscribe(\n 'BackendWebSocketService:connectionStateChanged',\n // Promise result intentionally not awaited\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n (connectionInfo: WebSocketConnectionInfo) =>\n this.#handleWebSocketStateChange(connectionInfo),\n );\n this.#messenger.subscribe(\n // eslint-disable-next-line no-restricted-syntax\n 'RemoteFeatureFlagController:stateChange',\n // Promise result intentionally not awaited\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n async () => await this.#handleFeatureFlagsStateChange(),\n // Only react to changes in the set of enabled chain prefixes. The\n // messenger compares selector results with strict equality, so the\n // selector must return a primitive rather than a fresh object.\n (state) =>\n this.#getSupportedChainPrefixes(state.remoteFeatureFlags).join(','),\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 * Subscribe to account activity (transactions and balance updates)\n * Addresses should be in CAIP-10 format (e.g., \"eip155:0:0x1234...\" or \"solana:0:ABC123...\")\n *\n * @param subscription - The subscription configuration\n * @param subscription.addresses - Array of addresses to subscribe to, each in CAIP-10 format\n * or an `addresses` array for batch subscription\n */\n async #subscribe({ addresses }: SubscriptionOptions): Promise<void> {\n try {\n await this.#messenger.call('BackendWebSocketService:connect');\n\n // Derive new subscriptions to be created from the provided addresses,\n // filtering out any channels that already have an active subscription\n const channels = addresses\n .map((address) => `${this.#options.subscriptionNamespace}.${address}`)\n .filter(\n (channel) =>\n !this.#messenger.call(\n 'BackendWebSocketService:channelHasSubscription',\n channel,\n ),\n );\n\n if (channels.length === 0) {\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,\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 * Handle account activity updates (transactions + balance changes)\n * Processes the comprehensive AccountActivityMessage format with detailed balance updates and transfers\n *\n * @param payload - The account activity message containing transaction and balance updates\n * @example AccountActivityMessage format handling:\n * Input: {\n * address: \"0xd14b52362b5b777ffa754c666ddec6722aaeee08\",\n * tx: { id: \"0x1cde...\", chain: \"eip155:8453\", status: \"confirmed\", timestamp: 1760099871, ... },\n * updates: [{\n * asset: { fungible: true, type: \"eip155:8453/erc20:0x833...\", unit: \"USDC\", decimals: 6 },\n * postBalance: { amount: \"0xc350\" },\n * transfers: [{ from: \"0x7b07...\", to: \"0xd14b...\", amount: \"0x2710\" }]\n * }]\n * }\n * Output: Transaction and balance updates published separately\n */\n #handleAccountActivityUpdate(payload: AccountActivityMessage): void {\n const { address, tx, updates } = payload;\n\n // Calculate time elapsed between transaction time and message receipt\n const txTimestampMs = tx.timestamp * 1000; // Convert Unix timestamp (seconds) to milliseconds\n const elapsedMs = Date.now() - txTimestampMs;\n\n log('Handling account activity update', {\n address,\n updateCount: updates.length,\n elapsedMs,\n });\n\n // Trace message receipt with latency from transaction time to now\n // Promise result intentionally not awaited\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.#trace(\n {\n name: `${SERVICE_NAME} Transaction Message`,\n data: {\n chain: tx.chain,\n status: tx.status,\n elapsed_ms: elapsedMs,\n },\n tags: {\n service: SERVICE_NAME,\n notification_type: this.#options.subscriptionNamespace,\n },\n },\n () => {\n // Process transaction update\n this.#messenger.publish(\n `AccountActivityService:transactionUpdated`,\n tx,\n );\n\n // Publish comprehensive balance updates with transfer details\n this.#messenger.publish(`AccountActivityService:balanceUpdated`, {\n address,\n chain: tx.chain,\n updates,\n });\n },\n );\n }\n\n /**\n * Handle selected account change event\n */\n async #handleSelectedAccountChange(): Promise<void> {\n const selectedAccounts = this.#messenger.call(\n 'AccountTreeController:getAccountsFromSelectedAccountGroup',\n );\n\n try {\n // First, unsubscribe from all current account activity subscriptions to avoid multiple subscriptions\n await this.#unsubscribeFromAllAccountActivity();\n\n // Subscribe to the new selected accounts in CAIP-10 format\n await this.#subscribe({\n addresses: this.#convertToCaip10Addresses(selectedAccounts),\n });\n } catch (error) {\n log('Account change failed', { error });\n }\n }\n\n /**\n * Handle system notification for chain status changes\n * Publishes only the status change (delta) for affected chains\n *\n * @param notification - Server notification message containing chain status updates and timestamp\n */\n #handleSystemNotification(notification: ServerNotificationMessage): void {\n const data = notification.data as SystemNotificationData;\n const { timestamp } = notification;\n\n // Validate required fields\n if (!data.chainIds || !Array.isArray(data.chainIds) || !data.status) {\n throw new Error(\n 'Invalid system notification data: missing chainIds or status',\n );\n }\n\n // Track chain status\n if (data.status === 'up') {\n for (const chainId of data.chainIds) {\n this.#chainsUp.add(chainId);\n }\n } else {\n for (const chainId of data.chainIds) {\n this.#chainsUp.delete(chainId);\n }\n }\n\n // Publish status change directly (delta update)\n this.#messenger.publish(`AccountActivityService:statusChanged`, {\n chainIds: data.chainIds,\n status: data.status,\n timestamp,\n });\n\n log(\n `WebSocket status change - Published tracked chains as ${data.status}`,\n {\n count: data.chainIds.length,\n chains: data.chainIds,\n status: data.status,\n },\n );\n }\n\n /**\n * Handle WebSocket connection state changes for fallback polling and resubscription\n *\n * @param connectionInfo - WebSocket connection state information\n */\n async #handleWebSocketStateChange(\n connectionInfo: WebSocketConnectionInfo,\n ): Promise<void> {\n const { state } = connectionInfo;\n\n if (state === WebSocketState.CONNECTED) {\n // WebSocket connected - resubscribe to selected account\n // The system notification will automatically provide the list of chains that are up\n await this.#subscribeToSelectedAccount();\n } else if (state === WebSocketState.DISCONNECTED) {\n // On disconnect, flush all tracked chains as down\n const chainsToMarkDown = Array.from(this.#chainsUp);\n\n if (chainsToMarkDown.length > 0) {\n this.#messenger.publish(`AccountActivityService:statusChanged`, {\n chainIds: chainsToMarkDown,\n status: 'down',\n timestamp: Date.now(),\n });\n\n log('WebSocket disconnection - Published tracked chains as down', {\n count: chainsToMarkDown.length,\n chains: chainsToMarkDown,\n });\n\n // Clear the tracking set since all chains are now down\n this.#chainsUp.clear();\n }\n }\n }\n\n // =============================================================================\n // Private Methods - Subscription Management\n // =============================================================================\n\n /**\n * Subscribe to the currently selected account only\n */\n async #subscribeToSelectedAccount(): Promise<void> {\n const selectedAccounts = this.#messenger.call(\n 'AccountTreeController:getAccountsFromSelectedAccountGroup',\n );\n\n await this.#subscribe({\n addresses: this.#convertToCaip10Addresses(selectedAccounts),\n });\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 a list of InternalAccount addresses to CAIP-10 format, using the first\n * supported chain prefix matching the account's scopes\n *\n * @param accounts - The internal accounts to convert\n * @returns The CAIP-10 formatted addresses (e.g. [`eip155:0:address`], meaning\n * all chains of that namespace), or an empty array if none of the account's\n * scopes are supported\n */\n #convertToCaip10Addresses(accounts: InternalAccount[]): string[] {\n const supportedChainPrefixes = this.#getSupportedChainPrefixes();\n return accounts.reduce<string[]>((result, account) => {\n const accountPrefix = supportedChainPrefixes.find((prefix) =>\n account.scopes.some((scope) => scope.startsWith(`${prefix}:`)),\n );\n\n if (!accountPrefix) {\n // Skip unsupported accounts\n return result;\n }\n\n result.push(`${accountPrefix}:0:${account.address}`);\n return result;\n }, []);\n }\n\n /**\n * Get the chain prefixes currently enabled for subscriptions: EVM is always\n * enabled, while other chains are gated behind their per-network remote\n * feature flag (enabled when the flag payload has `stage >= 1`).\n *\n * @param remoteFeatureFlags - The remote feature flags state to check for enabled chains.\n * @returns An array of enabled CAIP-2 namespace prefixes (e.g. `['eip155', 'solana']`)\n */\n #getSupportedChainPrefixes(\n remoteFeatureFlags: FeatureFlags = this.#messenger.call(\n 'RemoteFeatureFlagController:getState',\n ).remoteFeatureFlags,\n ): string[] {\n const prefixes: string[] = [...ALWAYS_SUPPORTED_CHAIN_PREFIXES];\n for (const [prefix, flagName] of Object.entries(\n CHAIN_PREFIX_FEATURE_FLAGS,\n )) {\n const flagValue = remoteFeatureFlags[flagName];\n if (\n isObject(flagValue) &&\n typeof flagValue.stage === 'number' &&\n flagValue.stage >= 1\n ) {\n prefixes.push(prefix);\n }\n }\n return prefixes;\n }\n\n /**\n * Handle remote feature flag changes: if the set of enabled chain prefixes\n * changed while connected, resubscribe the selected account so new chains\n * are picked up and disabled ones are dropped.\n */\n async #handleFeatureFlagsStateChange(): Promise<void> {\n try {\n const { state } = this.#messenger.call(\n 'BackendWebSocketService:getConnectionInfo',\n );\n if (state !== WebSocketState.CONNECTED) {\n // Not connected: the next connection will subscribe with fresh flags\n return;\n }\n\n await this.#unsubscribeFromAllAccountActivity();\n await this.#subscribeToSelectedAccount();\n } catch (error) {\n log('Feature flag change handling failed', { error });\n }\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/ws/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 AccountTreeControllerSelectedAccountGroupChangeEvent,\n AccountTreeControllerGetAccountsFromSelectedAccountGroupAction,\n} from '@metamask/account-tree-controller';\nimport type { TraceCallback } from '@metamask/controller-utils';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport type { Messenger } from '@metamask/messenger';\nimport type {\n FeatureFlags,\n RemoteFeatureFlagControllerGetStateAction,\n RemoteFeatureFlagControllerStateChangeEvent,\n} from '@metamask/remote-feature-flag-controller';\nimport { isObject } from '@metamask/utils';\n\nimport { projectLogger, createModuleLogger } from '../logger.js';\nimport type {\n Transaction,\n AccountActivityMessage,\n BalanceUpdate,\n} from '../types.js';\nimport type { BackendWebSocketServiceMethodActions } from './BackendWebSocketService-method-action-types.js';\nimport type {\n WebSocketConnectionInfo,\n BackendWebSocketServiceConnectionStateChangedEvent,\n ServerNotificationMessage,\n} from './BackendWebSocketService.js';\nimport { WebSocketState } from './BackendWebSocketService.js';\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 = [] as const;\n\nconst SUBSCRIPTION_NAMESPACE = 'account-activity.v1';\n\n// Window (in ms) over which consecutive system notifications are accumulated\n// before a single batched `statusChanged` event is published. Coalescing bursts\n// of chain up/down notifications avoids flooding consumers.\nconst STATUS_CHANGE_DEBOUNCE_MS = 1000;\n\n// Maximum random jitter (in ms) added to the debounce window before publishing.\n// Spreading the publish across a random delay prevents many clients reacting to\n// the same system notification from publishing in lockstep (a thundering herd\n// on downstream consumers).\nconst STATUS_CHANGE_JITTER_MS = 1000;\n\n// EVM subscriptions are always enabled.\nconst ALWAYS_SUPPORTED_CHAIN_PREFIXES = ['eip155'] as const;\n\n// Non-EVM chains are gated behind the\n// per-network snaps-migration remote feature flags: a chain is\n// enabled when its flag payload has `stage >= 1`.\nconst CHAIN_PREFIX_FEATURE_FLAGS = {\n solana: 'networkAssetsSnapsMigrationSolana',\n tron: 'networkAssetsSnapsMigrationTron',\n stellar: 'networkAssetsSnapsMigrationStellar',\n} as const;\n\n/**\n * Account subscription options\n */\nexport type SubscriptionOptions = {\n /**\n * Array of addresses to subscribe to, each in CAIP-10 format (e.g., \"eip155:0:0x1234...\" or \"solana:0:ABC123...\")\n */\n addresses: string[];\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\nexport type AccountActivityServiceActions = never;\n\n// Allowed actions that AccountActivityService can call on other controllers\nexport const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_ACTIONS = [\n 'AccountTreeController:getAccountsFromSelectedAccountGroup',\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 'RemoteFeatureFlagController:getState',\n] as const;\n\n// Allowed events that AccountActivityService can listen to\nexport const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_EVENTS = [\n 'AccountTreeController:selectedAccountGroupChange',\n 'BackendWebSocketService:connectionStateChanged',\n 'RemoteFeatureFlagController:stateChange',\n] as const;\n\nexport type AllowedActions =\n | AccountTreeControllerGetAccountsFromSelectedAccountGroupAction\n | BackendWebSocketServiceMethodActions\n | RemoteFeatureFlagControllerGetStateAction;\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 | AccountTreeControllerSelectedAccountGroupChangeEvent\n | BackendWebSocketServiceConnectionStateChangedEvent\n | RemoteFeatureFlagControllerStateChangeEvent;\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 // Debouncing for rapid status changes: buffers the latest status per chain\n // and coalesces bursts of system notifications into fewer downstream\n // `statusChanged` events.\n readonly #statusChangeDebouncer: {\n timer: NodeJS.Timeout | null;\n pendingChanges: Map<string, 'up' | 'down'>;\n } = {\n timer: null,\n pendingChanges: new Map(),\n };\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 'AccountTreeController:selectedAccountGroupChange',\n // Promise result intentionally not awaited\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n async () => await this.#handleSelectedAccountChange(),\n );\n this.#messenger.subscribe(\n 'BackendWebSocketService:connectionStateChanged',\n // Promise result intentionally not awaited\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n (connectionInfo: WebSocketConnectionInfo) =>\n this.#handleWebSocketStateChange(connectionInfo),\n );\n this.#messenger.subscribe(\n // eslint-disable-next-line no-restricted-syntax\n 'RemoteFeatureFlagController:stateChange',\n // Promise result intentionally not awaited\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n async () => await this.#handleFeatureFlagsStateChange(),\n // Only react to changes in the set of enabled chain prefixes. The\n // messenger compares selector results with strict equality, so the\n // selector must return a primitive rather than a fresh object.\n (state) =>\n this.#getSupportedChainPrefixes(state.remoteFeatureFlags).join(','),\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 * Subscribe to account activity (transactions and balance updates)\n * Addresses should be in CAIP-10 format (e.g., \"eip155:0:0x1234...\" or \"solana:0:ABC123...\")\n *\n * @param subscription - The subscription configuration\n * @param subscription.addresses - Array of addresses to subscribe to, each in CAIP-10 format\n * or an `addresses` array for batch subscription\n */\n async #subscribe({ addresses }: SubscriptionOptions): Promise<void> {\n try {\n await this.#messenger.call('BackendWebSocketService:connect');\n\n // Derive new subscriptions to be created from the provided addresses,\n // filtering out any channels that already have an active subscription\n const channels = addresses\n .map((address) => `${this.#options.subscriptionNamespace}.${address}`)\n .filter(\n (channel) =>\n !this.#messenger.call(\n 'BackendWebSocketService:channelHasSubscription',\n channel,\n ),\n );\n\n if (channels.length === 0) {\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,\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 * Handle account activity updates (transactions + balance changes)\n * Processes the comprehensive AccountActivityMessage format with detailed balance updates and transfers\n *\n * @param payload - The account activity message containing transaction and balance updates\n * @example AccountActivityMessage format handling:\n * Input: {\n * address: \"0xd14b52362b5b777ffa754c666ddec6722aaeee08\",\n * tx: { id: \"0x1cde...\", chain: \"eip155:8453\", status: \"confirmed\", timestamp: 1760099871, ... },\n * updates: [{\n * asset: { fungible: true, type: \"eip155:8453/erc20:0x833...\", unit: \"USDC\", decimals: 6 },\n * postBalance: { amount: \"0xc350\" },\n * transfers: [{ from: \"0x7b07...\", to: \"0xd14b...\", amount: \"0x2710\" }]\n * }]\n * }\n * Output: Transaction and balance updates published separately\n */\n #handleAccountActivityUpdate(payload: AccountActivityMessage): void {\n const { address, tx, updates } = payload;\n\n // Calculate time elapsed between transaction time and message receipt\n const txTimestampMs = tx.timestamp * 1000; // Convert Unix timestamp (seconds) to milliseconds\n const elapsedMs = Date.now() - txTimestampMs;\n\n log('Handling account activity update', {\n address,\n updateCount: updates.length,\n elapsedMs,\n });\n\n // Trace message receipt with latency from transaction time to now\n // Promise result intentionally not awaited\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.#trace(\n {\n name: `${SERVICE_NAME} Transaction Message`,\n data: {\n chain: tx.chain,\n status: tx.status,\n elapsed_ms: elapsedMs,\n },\n tags: {\n service: SERVICE_NAME,\n notification_type: this.#options.subscriptionNamespace,\n },\n },\n () => {\n // Process transaction update\n this.#messenger.publish(\n `AccountActivityService:transactionUpdated`,\n tx,\n );\n\n // Publish comprehensive balance updates with transfer details\n this.#messenger.publish(`AccountActivityService:balanceUpdated`, {\n address,\n chain: tx.chain,\n updates,\n });\n },\n );\n }\n\n /**\n * Handle selected account change event\n */\n async #handleSelectedAccountChange(): Promise<void> {\n const selectedAccounts = this.#messenger.call(\n 'AccountTreeController:getAccountsFromSelectedAccountGroup',\n );\n\n try {\n // First, unsubscribe from all current account activity subscriptions to avoid multiple subscriptions\n await this.#unsubscribeFromAllAccountActivity();\n\n // Subscribe to the new selected accounts in CAIP-10 format\n await this.#subscribe({\n addresses: this.#convertToCaip10Addresses(selectedAccounts),\n });\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\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 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 for (const chainId of data.chainIds) {\n this.#statusChangeDebouncer.pendingChanges.set(chainId, data.status);\n }\n\n if (this.#statusChangeDebouncer.timer) {\n clearTimeout(this.#statusChangeDebouncer.timer);\n }\n\n // Debounce window plus a random jitter, so clients reacting to the same\n // system notification don't publish in lockstep.\n const delay =\n STATUS_CHANGE_DEBOUNCE_MS + Math.random() * STATUS_CHANGE_JITTER_MS;\n\n this.#statusChangeDebouncer.timer = setTimeout(() => {\n this.#processAccumulatedStatusChanges();\n }, delay);\n\n log(`WebSocket status change - Buffered chains as ${data.status}`, {\n count: data.chainIds.length,\n chains: data.chainIds,\n status: data.status,\n });\n }\n\n /**\n * Publish the buffered status changes as batched `statusChanged` events, one\n * per status (`up`/`down`), then reset the buffer and timer.\n */\n #processAccumulatedStatusChanges(): void {\n const changes = Array.from(\n this.#statusChangeDebouncer.pendingChanges.entries(),\n );\n this.#statusChangeDebouncer.pendingChanges.clear();\n this.#statusChangeDebouncer.timer = null;\n\n // Group buffered chains by their latest status. A chain can only appear in\n // one group because the buffer keeps a single entry per chain.\n const grouped: Record<'up' | 'down', string[]> = { up: [], down: [] };\n for (const [chainId, status] of changes) {\n grouped[status].push(chainId);\n }\n\n for (const status of ['up', 'down'] as const) {\n const chainIds = grouped[status];\n if (chainIds.length === 0) {\n continue;\n }\n\n this.#messenger.publish(`AccountActivityService:statusChanged`, {\n chainIds,\n status,\n });\n\n log(`WebSocket status change - Published batched chains as ${status}`, {\n count: chainIds.length,\n chains: chainIds,\n status,\n });\n }\n }\n\n /**\n * Handle WebSocket connection state changes for fallback polling and resubscription\n *\n * @param connectionInfo - WebSocket connection state information\n */\n async #handleWebSocketStateChange(\n connectionInfo: WebSocketConnectionInfo,\n ): Promise<void> {\n const { state } = connectionInfo;\n\n if (state === WebSocketState.CONNECTED) {\n // WebSocket connected - resubscribe to selected account\n // The system notification will automatically provide the list of chains that are up\n await this.#subscribeToSelectedAccount();\n } else if (state === WebSocketState.DISCONNECTED) {\n if (this.#statusChangeDebouncer.timer) {\n clearTimeout(this.#statusChangeDebouncer.timer);\n this.#statusChangeDebouncer.timer = null;\n }\n\n const chainsToMarkDown = new Set(this.#chainsUp);\n for (const chainId of this.#statusChangeDebouncer.pendingChanges.keys()) {\n chainsToMarkDown.add(chainId);\n }\n this.#statusChangeDebouncer.pendingChanges.clear();\n\n if (chainsToMarkDown.size > 0) {\n const chainIds = Array.from(chainsToMarkDown);\n this.#messenger.publish(`AccountActivityService:statusChanged`, {\n chainIds,\n status: 'down',\n timestamp: Date.now(),\n });\n\n log('WebSocket disconnection - Published tracked chains as down', {\n count: chainIds.length,\n chains: chainIds,\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 selectedAccounts = this.#messenger.call(\n 'AccountTreeController:getAccountsFromSelectedAccountGroup',\n );\n\n await this.#subscribe({\n addresses: this.#convertToCaip10Addresses(selectedAccounts),\n });\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 a list of InternalAccount addresses to CAIP-10 format, using the first\n * supported chain prefix matching the account's scopes\n *\n * @param accounts - The internal accounts to convert\n * @returns The CAIP-10 formatted addresses (e.g. [`eip155:0:address`], meaning\n * all chains of that namespace), or an empty array if none of the account's\n * scopes are supported\n */\n #convertToCaip10Addresses(accounts: InternalAccount[]): string[] {\n const supportedChainPrefixes = this.#getSupportedChainPrefixes();\n return accounts.reduce<string[]>((result, account) => {\n const accountPrefix = supportedChainPrefixes.find((prefix) =>\n account.scopes.some((scope) => scope.startsWith(`${prefix}:`)),\n );\n\n if (!accountPrefix) {\n // Skip unsupported accounts\n return result;\n }\n\n result.push(`${accountPrefix}:0:${account.address}`);\n return result;\n }, []);\n }\n\n /**\n * Get the chain prefixes currently enabled for subscriptions: EVM is always\n * enabled, while other chains are gated behind their per-network remote\n * feature flag (enabled when the flag payload has `stage >= 1`).\n *\n * @param remoteFeatureFlags - The remote feature flags state to check for enabled chains.\n * @returns An array of enabled CAIP-2 namespace prefixes (e.g. `['eip155', 'solana']`)\n */\n #getSupportedChainPrefixes(\n remoteFeatureFlags: FeatureFlags = this.#messenger.call(\n 'RemoteFeatureFlagController:getState',\n ).remoteFeatureFlags,\n ): string[] {\n const prefixes: string[] = [...ALWAYS_SUPPORTED_CHAIN_PREFIXES];\n for (const [prefix, flagName] of Object.entries(\n CHAIN_PREFIX_FEATURE_FLAGS,\n )) {\n const flagValue = remoteFeatureFlags[flagName];\n if (\n isObject(flagValue) &&\n typeof flagValue.stage === 'number' &&\n flagValue.stage >= 1\n ) {\n prefixes.push(prefix);\n }\n }\n return prefixes;\n }\n\n /**\n * Handle remote feature flag changes: if the set of enabled chain prefixes\n * changed while connected, resubscribe the selected account so new chains\n * are picked up and disabled ones are dropped.\n */\n async #handleFeatureFlagsStateChange(): Promise<void> {\n try {\n const { state } = this.#messenger.call(\n 'BackendWebSocketService:getConnectionInfo',\n );\n if (state !== WebSocketState.CONNECTED) {\n // Not connected: the next connection will subscribe with fresh flags\n return;\n }\n\n await this.#unsubscribeFromAllAccountActivity();\n await this.#subscribeToSelectedAccount();\n } catch (error) {\n log('Feature flag change handling failed', { error });\n }\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 // Cancel any pending batched status-change flush\n if (this.#statusChangeDebouncer.timer) {\n clearTimeout(this.#statusChangeDebouncer.timer);\n this.#statusChangeDebouncer.timer = null;\n }\n\n // Clean up system notification callback\n this.#messenger.call(\n 'BackendWebSocketService:removeChannelCallback',\n `system-notifications.v1.${this.#options.subscriptionNamespace}`,\n );\n }\n}\n"]}
@@ -18,10 +18,11 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
18
18
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
19
19
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
20
20
  };
21
- var _OHLCVService_instances, _OHLCVService_messenger, _OHLCVService_trace, _OHLCVService_channels, _OHLCVService_mutex, _OHLCVService_chainsUp, _OHLCVService_subscribeInner, _OHLCVService_unsubscribeInner, _OHLCVService_performUnsubscribe, _OHLCVService_resubscribeActiveChannels, _OHLCVService_handleBarUpdate, _OHLCVService_handleSystemNotification, _OHLCVService_handleWebSocketStateChange, _OHLCVService_buildChannel;
21
+ var _OHLCVService_instances, _OHLCVService_messenger, _OHLCVService_trace, _OHLCVService_channels, _OHLCVService_mutex, _OHLCVService_chainsUp, _OHLCVService_subscribeInner, _OHLCVService_unsubscribeInner, _OHLCVService_flushOtherChannels, _OHLCVService_clearChannelTimers, _OHLCVService_unsubscribeChannelOnServer, _OHLCVService_scheduleUnsubscribeRetry, _OHLCVService_runUnsubRetryLoop, _OHLCVService_performUnsubscribe, _OHLCVService_resubscribeActiveChannels, _OHLCVService_handleBarUpdate, _OHLCVService_handleSystemNotification, _OHLCVService_handleWebSocketStateChange, _OHLCVService_buildChannel;
22
22
  Object.defineProperty(exports, "__esModule", { value: true });
23
23
  exports.OHLCVService = exports.OHLCV_SERVICE_ALLOWED_EVENTS = exports.OHLCV_SERVICE_ALLOWED_ACTIONS = void 0;
24
24
  const async_mutex_1 = require("async-mutex");
25
+ const cockatiel_1 = require("cockatiel");
25
26
  const logger_js_1 = require("../../logger.cjs");
26
27
  const BackendWebSocketService_js_1 = require("../BackendWebSocketService.cjs");
27
28
  // =============================================================================
@@ -34,6 +35,14 @@ const SUBSCRIPTION_NAMESPACE = 'market-data.v1';
34
35
  const SYSTEM_NOTIFICATIONS_CHANNEL = `system-notifications.v1.${SUBSCRIPTION_NAMESPACE}`;
35
36
  /** Delay before actually unsubscribing from a channel after refCount reaches 0. */
36
37
  const GRACE_PERIOD_MS = 3000;
38
+ /** Backoff delays between failed WebSocket unsubscribe attempts. */
39
+ const UNSUB_RETRY_DELAYS_MS = [1000, 2000, 4000];
40
+ const unsubRetryPolicy = (0, cockatiel_1.retry)(cockatiel_1.handleAll, {
41
+ // Cockatiel stops retrying once the failure index reaches `maxAttempts`, so
42
+ // `length` here yields one initial attempt plus three delayed retries (4 total).
43
+ maxAttempts: UNSUB_RETRY_DELAYS_MS.length,
44
+ backoff: new cockatiel_1.IterableBackoff([...UNSUB_RETRY_DELAYS_MS]),
45
+ });
37
46
  exports.OHLCV_SERVICE_ALLOWED_ACTIONS = [
38
47
  'BackendWebSocketService:connect',
39
48
  'BackendWebSocketService:forceReconnection',
@@ -58,7 +67,9 @@ exports.OHLCV_SERVICE_ALLOWED_EVENTS = [
58
67
  *
59
68
  * Features:
60
69
  * - Reference counting: multiple UI consumers share one WebSocket subscription
61
- * - Grace-period unsubscribe: avoids rapid unsub/resub during navigation
70
+ * - Grace-period unsubscribe: reuses same-channel subs on rapid back navigation
71
+ * - Grace-period flush: immediately unsubscribes other channels on navigation
72
+ * - Unsubscribe retry: retries failed unsubs with backoff before force reconnect
62
73
  * - Idempotency: duplicate subscribe calls for the same channel are no-ops
63
74
  * - Reconnect resilience: resubscribes all active channels on reconnect
64
75
  * - Chain-status forwarding: listens to system-notifications for chain up/down
@@ -142,9 +153,7 @@ class OHLCVService {
142
153
  */
143
154
  destroy() {
144
155
  for (const entry of __classPrivateFieldGet(this, _OHLCVService_channels, "f").values()) {
145
- if (entry.gracePeriodTimer) {
146
- clearTimeout(entry.gracePeriodTimer);
147
- }
156
+ __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_clearChannelTimers).call(this, entry);
148
157
  }
149
158
  __classPrivateFieldGet(this, _OHLCVService_channels, "f").clear();
150
159
  __classPrivateFieldGet(this, _OHLCVService_chainsUp, "f").clear();
@@ -154,13 +163,27 @@ class OHLCVService {
154
163
  exports.OHLCVService = OHLCVService;
155
164
  _OHLCVService_messenger = new WeakMap(), _OHLCVService_trace = new WeakMap(), _OHLCVService_channels = new WeakMap(), _OHLCVService_mutex = new WeakMap(), _OHLCVService_chainsUp = new WeakMap(), _OHLCVService_instances = new WeakSet(), _OHLCVService_subscribeInner = async function _OHLCVService_subscribeInner(channel) {
156
165
  const entry = __classPrivateFieldGet(this, _OHLCVService_channels, "f").get(channel);
157
- if (entry?.gracePeriodTimer) {
166
+ if (entry?.retryAbort) {
167
+ entry.retryAbort.abort();
168
+ entry.retryAbort = undefined;
169
+ entry.refCount = 1;
170
+ if (__classPrivateFieldGet(this, _OHLCVService_messenger, "f").call('BackendWebSocketService:channelHasSubscription', channel)) {
171
+ await __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_flushOtherChannels).call(this, channel);
172
+ log('OHLCV-WS: Cancelled unsubscribe retry — reusing WS subscription', {
173
+ channel,
174
+ });
175
+ return;
176
+ }
177
+ // WS subscription was lost — fall through to recreate it.
178
+ }
179
+ else if (entry?.gracePeriodTimer) {
158
180
  clearTimeout(entry.gracePeriodTimer);
159
181
  entry.gracePeriodTimer = undefined;
160
182
  log('OHLCV-WS: Cancelled grace-period unsubscribe', {
161
183
  channel,
162
184
  });
163
185
  if (__classPrivateFieldGet(this, _OHLCVService_messenger, "f").call('BackendWebSocketService:channelHasSubscription', channel)) {
186
+ await __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_flushOtherChannels).call(this, channel);
164
187
  entry.refCount += 1;
165
188
  log('OHLCV-WS: WS subscription still alive, bumped refCount', {
166
189
  channel,
@@ -175,6 +198,7 @@ _OHLCVService_messenger = new WeakMap(), _OHLCVService_trace = new WeakMap(), _O
175
198
  entry.refCount += 1;
176
199
  return;
177
200
  }
201
+ await __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_flushOtherChannels).call(this, channel);
178
202
  try {
179
203
  await __classPrivateFieldGet(this, _OHLCVService_messenger, "f").call('BackendWebSocketService:connect');
180
204
  if (__classPrivateFieldGet(this, _OHLCVService_messenger, "f").call('BackendWebSocketService:channelHasSubscription', channel)) {
@@ -216,15 +240,108 @@ _OHLCVService_messenger = new WeakMap(), _OHLCVService_trace = new WeakMap(), _O
216
240
  }
217
241
  entry.gracePeriodTimer = setTimeout(() => {
218
242
  entry.gracePeriodTimer = undefined;
219
- __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_performUnsubscribe).call(this, channel).catch(() => {
220
- // no-op
221
- });
243
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
244
+ __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_performUnsubscribe).call(this, channel);
222
245
  }, GRACE_PERIOD_MS);
223
- }, _OHLCVService_performUnsubscribe =
246
+ }, _OHLCVService_flushOtherChannels =
224
247
  // =============================================================================
225
248
  // Private — WebSocket Subscription Helpers
226
249
  // =============================================================================
227
- async function _OHLCVService_performUnsubscribe(channel) {
250
+ /**
251
+ * Immediately unsubscribe other channels in grace or failed-cleanup state.
252
+ * Called while the subscribe mutex is held before opening a new channel.
253
+ *
254
+ * @param exceptChannel - Channel being subscribed; excluded from flush.
255
+ */
256
+ async function _OHLCVService_flushOtherChannels(exceptChannel) {
257
+ for (const [channel, channelEntry] of __classPrivateFieldGet(this, _OHLCVService_channels, "f").entries()) {
258
+ if (channel === exceptChannel || channelEntry.refCount > 0) {
259
+ continue;
260
+ }
261
+ __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_clearChannelTimers).call(this, channelEntry);
262
+ log('OHLCV-WS: Flushing grace-period channel before new subscribe', {
263
+ flushedChannel: channel,
264
+ newChannel: exceptChannel,
265
+ });
266
+ const success = await __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_unsubscribeChannelOnServer).call(this, channel);
267
+ if (success) {
268
+ __classPrivateFieldGet(this, _OHLCVService_channels, "f").delete(channel);
269
+ }
270
+ else {
271
+ __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_scheduleUnsubscribeRetry).call(this, channel);
272
+ }
273
+ }
274
+ }, _OHLCVService_clearChannelTimers = function _OHLCVService_clearChannelTimers(entry) {
275
+ if (entry.gracePeriodTimer) {
276
+ clearTimeout(entry.gracePeriodTimer);
277
+ entry.gracePeriodTimer = undefined;
278
+ }
279
+ entry.retryAbort?.abort();
280
+ entry.retryAbort = undefined;
281
+ }, _OHLCVService_unsubscribeChannelOnServer = async function _OHLCVService_unsubscribeChannelOnServer(channel) {
282
+ try {
283
+ const subscriptions = __classPrivateFieldGet(this, _OHLCVService_messenger, "f").call('BackendWebSocketService:getSubscriptionsByChannel', channel);
284
+ for (const sub of subscriptions) {
285
+ await sub.unsubscribe();
286
+ }
287
+ return true;
288
+ }
289
+ catch (error) {
290
+ log('OHLCV-WS: Unsubscription failed', { channel, error });
291
+ __classPrivateFieldGet(this, _OHLCVService_messenger, "f").publish('OHLCVService:subscriptionError', {
292
+ channel,
293
+ error: String(error),
294
+ operation: 'unsubscribe',
295
+ });
296
+ return false;
297
+ }
298
+ }, _OHLCVService_scheduleUnsubscribeRetry = function _OHLCVService_scheduleUnsubscribeRetry(channel) {
299
+ const entry = __classPrivateFieldGet(this, _OHLCVService_channels, "f").get(channel) ?? { refCount: 0 };
300
+ entry.retryAbort?.abort();
301
+ entry.retryAbort = new AbortController();
302
+ __classPrivateFieldGet(this, _OHLCVService_channels, "f").set(channel, entry);
303
+ const { signal } = entry.retryAbort;
304
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
305
+ __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_runUnsubRetryLoop).call(this, channel, signal);
306
+ }, _OHLCVService_runUnsubRetryLoop = async function _OHLCVService_runUnsubRetryLoop(channel, signal) {
307
+ try {
308
+ await unsubRetryPolicy.execute(async () => {
309
+ const releaseLock = await __classPrivateFieldGet(this, _OHLCVService_mutex, "f").acquire();
310
+ try {
311
+ const current = __classPrivateFieldGet(this, _OHLCVService_channels, "f").get(channel);
312
+ if (current && current.refCount > 0) {
313
+ return;
314
+ }
315
+ const success = await __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_unsubscribeChannelOnServer).call(this, channel);
316
+ if (!success) {
317
+ throw new Error('unsubscribe failed');
318
+ }
319
+ __classPrivateFieldGet(this, _OHLCVService_channels, "f").delete(channel);
320
+ log('OHLCV-WS: WS unsubscribe completed', { channel });
321
+ }
322
+ finally {
323
+ releaseLock();
324
+ }
325
+ }, signal);
326
+ }
327
+ catch {
328
+ if (signal.aborted) {
329
+ return;
330
+ }
331
+ log('OHLCV-WS: Unsubscribe retries exhausted — forcing reconnection', {
332
+ channel,
333
+ });
334
+ __classPrivateFieldGet(this, _OHLCVService_channels, "f").delete(channel);
335
+ // Last resort: reconnects the shared BackendWebSocketService instance
336
+ // (AccountActivityService and other consumers share this connection).
337
+ // They resubscribe on CONNECTED; OHLCV only resubscribes refCount > 0.
338
+ await __classPrivateFieldGet(this, _OHLCVService_messenger, "f")
339
+ .call('BackendWebSocketService:forceReconnection')
340
+ .catch(() => {
341
+ // no-op
342
+ });
343
+ }
344
+ }, _OHLCVService_performUnsubscribe = async function _OHLCVService_performUnsubscribe(channel) {
228
345
  const releaseLock = await __classPrivateFieldGet(this, _OHLCVService_mutex, "f").acquire();
229
346
  try {
230
347
  const entry = __classPrivateFieldGet(this, _OHLCVService_channels, "f").get(channel);
@@ -235,26 +352,12 @@ async function _OHLCVService_performUnsubscribe(channel) {
235
352
  log('OHLCV-WS: Grace period expired — performing actual WS unsubscribe', {
236
353
  channel,
237
354
  });
238
- __classPrivateFieldGet(this, _OHLCVService_channels, "f").delete(channel);
239
- try {
240
- const subscriptions = __classPrivateFieldGet(this, _OHLCVService_messenger, "f").call('BackendWebSocketService:getSubscriptionsByChannel', channel);
241
- for (const sub of subscriptions) {
242
- await sub.unsubscribe();
243
- }
244
- log('OHLCV-WS: WS unsubscribe completed', { channel });
245
- }
246
- catch (error) {
247
- log('OHLCV-WS: Unsubscription failed', { channel, error });
248
- __classPrivateFieldGet(this, _OHLCVService_messenger, "f").publish('OHLCVService:subscriptionError', {
249
- channel,
250
- error: String(error),
251
- operation: 'unsubscribe',
252
- });
253
- }
355
+ __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_clearChannelTimers).call(this, __classPrivateFieldGet(this, _OHLCVService_channels, "f").get(channel) ?? { refCount: 0 });
254
356
  }
255
357
  finally {
256
358
  releaseLock();
257
359
  }
360
+ __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_scheduleUnsubscribeRetry).call(this, channel);
258
361
  }, _OHLCVService_resubscribeActiveChannels =
259
362
  /**
260
363
  * Resubscribe all channels that were active before a disconnect.
@@ -267,8 +370,10 @@ async function _OHLCVService_resubscribeActiveChannels() {
267
370
  log('OHLCV-WS: Resubscribing active channels after reconnect', {
268
371
  count: channelCount,
269
372
  });
270
- for (const [channel, entry] of __classPrivateFieldGet(this, _OHLCVService_channels, "f").entries()) {
373
+ for (const [channel, entry] of [...__classPrivateFieldGet(this, _OHLCVService_channels, "f").entries()]) {
271
374
  if (entry.refCount === 0) {
375
+ __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_clearChannelTimers).call(this, entry);
376
+ __classPrivateFieldGet(this, _OHLCVService_channels, "f").delete(channel);
272
377
  continue;
273
378
  }
274
379
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"OHLCVService.cjs","sourceRoot":"","sources":["../../../src/ws/ohlcv/OHLCVService.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OHLCV Service for real-time candlestick data streaming via WebSocket.\n *\n * Wraps {@link BackendWebSocketService} through the messenger pattern to\n * provide subscribe/unsubscribe semantics for OHLCV market-data channels.\n * Includes reference counting, grace-period unsubscribe, idempotency checks,\n * chain-status forwarding, and automatic resubscription on reconnect.\n */\n\nimport type {\n TraceCallback,\n TraceContext,\n TraceRequest,\n} from '@metamask/controller-utils';\nimport type { Messenger } from '@metamask/messenger';\nimport { Mutex } from 'async-mutex';\n\nimport { projectLogger, createModuleLogger } from '../../logger.js';\nimport type { BackendWebSocketServiceMethodActions } from '../BackendWebSocketService-method-action-types.js';\nimport type {\n WebSocketConnectionInfo,\n BackendWebSocketServiceConnectionStateChangedEvent,\n ServerNotificationMessage,\n} from '../BackendWebSocketService.js';\nimport { WebSocketState } from '../BackendWebSocketService.js';\nimport type { OHLCVServiceMethodActions } from './OHLCVService-method-action-types.js';\nimport type { OHLCVBar, OHLCVSubscriptionOptions } from './types.js';\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nconst SERVICE_NAME = 'OHLCVService';\n\nconst log = createModuleLogger(projectLogger, SERVICE_NAME);\n\nconst MESSENGER_EXPOSED_METHODS = ['subscribe', 'unsubscribe'] as const;\n\nconst SUBSCRIPTION_NAMESPACE = 'market-data.v1';\n\nconst SYSTEM_NOTIFICATIONS_CHANNEL = `system-notifications.v1.${SUBSCRIPTION_NAMESPACE}`;\n\n/** Delay before actually unsubscribing from a channel after refCount reaches 0. */\nconst GRACE_PERIOD_MS = 3_000;\n\n// =============================================================================\n// Types — Channel Tracking\n// =============================================================================\n\ntype ChannelEntry = {\n refCount: number;\n gracePeriodTimer?: ReturnType<typeof setTimeout>;\n};\n\n// =============================================================================\n// Types — System Notifications\n// =============================================================================\n\n/**\n * System notification data for chain status updates on market-data channels.\n */\nexport type OHLCVSystemNotificationData = {\n chainIds: string[];\n status: 'down' | 'up';\n timestamp?: number;\n};\n\n// =============================================================================\n// Types — Service Options\n// =============================================================================\n\n/**\n * Configuration options for the OHLCV service.\n */\nexport type OHLCVServiceOptions = {\n /** Optional callback to trace performance of OHLCV operations (default: no-op) */\n traceFn?: TraceCallback;\n};\n\n// =============================================================================\n// Action and Event Types\n// =============================================================================\n\nexport type OHLCVServiceActions = OHLCVServiceMethodActions;\n\nexport const OHLCV_SERVICE_ALLOWED_ACTIONS = [\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\nexport const OHLCV_SERVICE_ALLOWED_EVENTS = [\n 'BackendWebSocketService:connectionStateChanged',\n] as const;\n\nexport type AllowedActions = BackendWebSocketServiceMethodActions;\n\n// Events published by OHLCVService\n\nexport type OHLCVServiceBarUpdatedEvent = {\n type: `OHLCVService:barUpdated`;\n payload: [{ channel: string; bar: OHLCVBar }];\n};\n\nexport type OHLCVServiceChainStatusChangedEvent = {\n type: `OHLCVService:chainStatusChanged`;\n payload: [{ chainIds: string[]; status: 'up' | 'down'; timestamp?: number }];\n};\n\nexport type OHLCVServiceSubscriptionErrorEvent = {\n type: `OHLCVService:subscriptionError`;\n payload: [{ channel: string; error: string; operation: string }];\n};\n\nexport type OHLCVServiceEvents =\n | OHLCVServiceBarUpdatedEvent\n | OHLCVServiceChainStatusChangedEvent\n | OHLCVServiceSubscriptionErrorEvent;\n\nexport type AllowedEvents = BackendWebSocketServiceConnectionStateChangedEvent;\n\nexport type OHLCVServiceMessenger = Messenger<\n typeof SERVICE_NAME,\n OHLCVServiceActions | AllowedActions,\n OHLCVServiceEvents | AllowedEvents\n>;\n\n// =============================================================================\n// Main Service Class\n// =============================================================================\n\n/**\n * Service for real-time OHLCV candlestick streaming via the backend WebSocket\n * gateway. Communicates with {@link BackendWebSocketService} exclusively\n * through the messenger — no direct import of the class.\n *\n * Features:\n * - Reference counting: multiple UI consumers share one WebSocket subscription\n * - Grace-period unsubscribe: avoids rapid unsub/resub during navigation\n * - Idempotency: duplicate subscribe calls for the same channel are no-ops\n * - Reconnect resilience: resubscribes all active channels on reconnect\n * - Chain-status forwarding: listens to system-notifications for chain up/down\n *\n */\nexport class OHLCVService {\n readonly name = SERVICE_NAME;\n\n readonly #messenger: OHLCVServiceMessenger;\n\n readonly #trace: TraceCallback;\n\n readonly #channels = new Map<string, ChannelEntry>();\n\n readonly #mutex = new Mutex();\n\n readonly #chainsUp = new Set<string>();\n\n // =============================================================================\n // Constructor\n // =============================================================================\n\n constructor(\n options: OHLCVServiceOptions & { messenger: OHLCVServiceMessenger },\n ) {\n this.#messenger = options.messenger;\n\n this.#trace =\n options.traceFn ??\n ((<Result>(\n _request: TraceRequest,\n fn?: (context?: TraceContext) => Result,\n ) => fn?.()) as TraceCallback);\n\n this.#messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n\n this.#messenger.subscribe(\n 'BackendWebSocketService:connectionStateChanged',\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n (connectionInfo: WebSocketConnectionInfo) =>\n this.#handleWebSocketStateChange(connectionInfo),\n );\n }\n\n /**\n * Register the system-notifications channel callback.\n */\n init(): void {\n log('OHLCV-WS: Initializing — registering system-notifications callback');\n this.#messenger.call('BackendWebSocketService:addChannelCallback', {\n channelName: SYSTEM_NOTIFICATIONS_CHANNEL,\n callback: (notification: ServerNotificationMessage) =>\n this.#handleSystemNotification(notification),\n });\n }\n\n // =============================================================================\n // Public — Subscribe / Unsubscribe\n // =============================================================================\n\n /**\n * Subscribe to an OHLCV channel. If this is the first subscriber for the\n * given asset/interval/currency combination a WebSocket subscription is\n * created. Additional calls for the same combination only bump the reference\n * count.\n *\n * @param options - The subscription parameters.\n * @returns A promise that resolves once the subscription is established.\n */\n async subscribe(options: OHLCVSubscriptionOptions): Promise<void> {\n const channel = this.#buildChannel(options);\n const releaseLock = await this.#mutex.acquire();\n try {\n await this.#subscribeInner(channel);\n } finally {\n releaseLock();\n }\n }\n\n async #subscribeInner(channel: string): Promise<void> {\n const entry = this.#channels.get(channel);\n\n if (entry?.gracePeriodTimer) {\n clearTimeout(entry.gracePeriodTimer);\n entry.gracePeriodTimer = undefined;\n log('OHLCV-WS: Cancelled grace-period unsubscribe', {\n channel,\n });\n\n if (\n this.#messenger.call(\n 'BackendWebSocketService:channelHasSubscription',\n channel,\n )\n ) {\n entry.refCount += 1;\n log('OHLCV-WS: WS subscription still alive, bumped refCount', {\n channel,\n refCount: entry.refCount,\n });\n return;\n }\n // WS subscription was lost (e.g. after disconnect/reconnect) — fall\n // through to recreate it. refCount is bumped only after success below.\n } else if (entry && entry.refCount > 0) {\n entry.refCount += 1;\n return;\n }\n try {\n await this.#messenger.call('BackendWebSocketService:connect');\n\n if (\n this.#messenger.call(\n 'BackendWebSocketService:channelHasSubscription',\n channel,\n )\n ) {\n log(\n 'OHLCV-WS: Channel already has WS subscription (idempotency), skipping',\n {\n channel,\n },\n );\n this.#channels.set(channel, { refCount: 1 });\n return;\n }\n\n await this.#messenger.call('BackendWebSocketService:subscribe', {\n channels: [channel],\n channelType: SUBSCRIPTION_NAMESPACE,\n callback: (notification: ServerNotificationMessage) => {\n this.#handleBarUpdate(channel, notification);\n },\n });\n\n this.#channels.set(channel, { refCount: 1 });\n log('OHLCV-WS: Subscribe succeeded — new WS subscription created', {\n channel,\n });\n } catch (error) {\n log('OHLCV-WS: Subscription failed', { channel, error });\n this.#channels.delete(channel);\n this.#messenger.publish('OHLCVService:subscriptionError', {\n channel,\n error: String(error),\n operation: 'subscribe',\n });\n }\n }\n\n /**\n * Unsubscribe from an OHLCV channel. Decrements the reference count and,\n * when it reaches zero, starts a grace-period timer before actually\n * unsubscribing from the WebSocket to absorb rapid navigation patterns.\n *\n * @param options - The subscription parameters to unsubscribe from.\n * @returns A promise that resolves once the unsubscription is processed.\n */\n async unsubscribe(options: OHLCVSubscriptionOptions): Promise<void> {\n const channel = this.#buildChannel(options);\n const releaseLock = await this.#mutex.acquire();\n try {\n await this.#unsubscribeInner(channel);\n } finally {\n releaseLock();\n }\n }\n\n async #unsubscribeInner(channel: string): Promise<void> {\n const entry = this.#channels.get(channel);\n\n if (!entry || entry.refCount <= 0) {\n return;\n }\n\n entry.refCount -= 1;\n\n if (entry.refCount > 0) {\n return;\n }\n\n entry.gracePeriodTimer = setTimeout(() => {\n entry.gracePeriodTimer = undefined;\n this.#performUnsubscribe(channel).catch(() => {\n // no-op\n });\n }, GRACE_PERIOD_MS);\n }\n\n // =============================================================================\n // Private — WebSocket Subscription Helpers\n // =============================================================================\n\n async #performUnsubscribe(channel: string): Promise<void> {\n const releaseLock = await this.#mutex.acquire();\n try {\n const entry = this.#channels.get(channel);\n if (entry && entry.refCount > 0) {\n log(\n 'OHLCV-WS: Skipping unsubscribe — new subscriber arrived while queued',\n { channel, refCount: entry.refCount },\n );\n return;\n }\n\n log('OHLCV-WS: Grace period expired — performing actual WS unsubscribe', {\n channel,\n });\n this.#channels.delete(channel);\n\n try {\n const subscriptions = this.#messenger.call(\n 'BackendWebSocketService:getSubscriptionsByChannel',\n channel,\n );\n\n for (const sub of subscriptions) {\n await sub.unsubscribe();\n }\n log('OHLCV-WS: WS unsubscribe completed', { channel });\n } catch (error) {\n log('OHLCV-WS: Unsubscription failed', { channel, error });\n this.#messenger.publish('OHLCVService:subscriptionError', {\n channel,\n error: String(error),\n operation: 'unsubscribe',\n });\n }\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Resubscribe all channels that were active before a disconnect.\n * Called when WebSocket transitions to CONNECTED.\n */\n async #resubscribeActiveChannels(): Promise<void> {\n const releaseLock = await this.#mutex.acquire();\n try {\n const channelCount = this.#channels.size;\n log('OHLCV-WS: Resubscribing active channels after reconnect', {\n count: channelCount,\n });\n\n for (const [channel, entry] of this.#channels.entries()) {\n if (entry.refCount === 0) {\n continue;\n }\n\n try {\n if (\n this.#messenger.call(\n 'BackendWebSocketService:channelHasSubscription',\n channel,\n )\n ) {\n log(\n 'OHLCV-WS: Channel already subscribed on server, skipping resubscribe',\n {\n channel,\n },\n );\n continue;\n }\n\n await this.#messenger.call('BackendWebSocketService:subscribe', {\n channels: [channel],\n channelType: SUBSCRIPTION_NAMESPACE,\n callback: (notification: ServerNotificationMessage) => {\n this.#handleBarUpdate(channel, notification);\n },\n });\n log('OHLCV-WS: Resubscription succeeded', { channel });\n } catch (error) {\n log('OHLCV-WS: Resubscription failed for channel', {\n channel,\n error,\n });\n }\n }\n } finally {\n releaseLock();\n }\n }\n\n // =============================================================================\n // Private — Message Handlers\n // =============================================================================\n\n #handleBarUpdate(\n channel: string,\n notification: ServerNotificationMessage,\n ): void {\n const bar = notification.data as OHLCVBar;\n\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.#trace(\n {\n name: `${SERVICE_NAME} Bar Update`,\n data: { channel, timestamp: bar.timestamp },\n tags: { service: SERVICE_NAME },\n },\n () => {\n this.#messenger.publish('OHLCVService:barUpdated', { channel, bar });\n },\n );\n }\n\n #handleSystemNotification(notification: ServerNotificationMessage): void {\n const data = notification.data as OHLCVSystemNotificationData;\n const { timestamp } = notification;\n\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 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 this.#messenger.publish('OHLCVService:chainStatusChanged', {\n chainIds: data.chainIds,\n status: data.status,\n timestamp,\n });\n\n log(`OHLCV-WS: Chain status change: ${data.status}`, {\n chains: data.chainIds,\n status: data.status,\n });\n }\n\n async #handleWebSocketStateChange(\n connectionInfo: WebSocketConnectionInfo,\n ): Promise<void> {\n const { state } = connectionInfo;\n\n if (state === WebSocketState.CONNECTED) {\n await this.#resubscribeActiveChannels();\n } else if (state === WebSocketState.DISCONNECTED) {\n const chainsToMarkDown = Array.from(this.#chainsUp);\n\n if (chainsToMarkDown.length > 0) {\n this.#messenger.publish('OHLCVService:chainStatusChanged', {\n chainIds: chainsToMarkDown,\n status: 'down',\n timestamp: Date.now(),\n });\n\n log(\n 'OHLCV-WS: WebSocket disconnection — marked tracked chains as down',\n {\n count: chainsToMarkDown.length,\n chains: chainsToMarkDown,\n },\n );\n\n this.#chainsUp.clear();\n }\n }\n }\n\n // =============================================================================\n // Private — Utility\n // =============================================================================\n\n #buildChannel(options: OHLCVSubscriptionOptions): string {\n return `${SUBSCRIPTION_NAMESPACE}.${options.assetId}.${options.interval}.${options.currency}`;\n }\n\n // =============================================================================\n // Public — Cleanup\n // =============================================================================\n\n /**\n * Destroy the service and clean up all resources.\n */\n destroy(): void {\n for (const entry of this.#channels.values()) {\n if (entry.gracePeriodTimer) {\n clearTimeout(entry.gracePeriodTimer);\n }\n }\n this.#channels.clear();\n this.#chainsUp.clear();\n\n this.#messenger.call(\n 'BackendWebSocketService:removeChannelCallback',\n SYSTEM_NOTIFICATIONS_CHANNEL,\n );\n }\n}\n"]}
1
+ {"version":3,"file":"OHLCVService.cjs","sourceRoot":"","sources":["../../../src/ws/ohlcv/OHLCVService.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OHLCV Service for real-time candlestick data streaming via WebSocket.\n *\n * Wraps {@link BackendWebSocketService} through the messenger pattern to\n * provide subscribe/unsubscribe semantics for OHLCV market-data channels.\n * Includes reference counting, grace-period unsubscribe, idempotency checks,\n * chain-status forwarding, and automatic resubscription on reconnect.\n */\n\nimport type {\n TraceCallback,\n TraceContext,\n TraceRequest,\n} from '@metamask/controller-utils';\nimport type { Messenger } from '@metamask/messenger';\nimport { Mutex } from 'async-mutex';\nimport { handleAll, IterableBackoff, retry } from 'cockatiel';\n\nimport { projectLogger, createModuleLogger } from '../../logger.js';\nimport type { BackendWebSocketServiceMethodActions } from '../BackendWebSocketService-method-action-types.js';\nimport type {\n WebSocketConnectionInfo,\n BackendWebSocketServiceConnectionStateChangedEvent,\n ServerNotificationMessage,\n} from '../BackendWebSocketService.js';\nimport { WebSocketState } from '../BackendWebSocketService.js';\nimport type { OHLCVServiceMethodActions } from './OHLCVService-method-action-types.js';\nimport type { OHLCVBar, OHLCVSubscriptionOptions } from './types.js';\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nconst SERVICE_NAME = 'OHLCVService';\n\nconst log = createModuleLogger(projectLogger, SERVICE_NAME);\n\nconst MESSENGER_EXPOSED_METHODS = ['subscribe', 'unsubscribe'] as const;\n\nconst SUBSCRIPTION_NAMESPACE = 'market-data.v1';\n\nconst SYSTEM_NOTIFICATIONS_CHANNEL = `system-notifications.v1.${SUBSCRIPTION_NAMESPACE}`;\n\n/** Delay before actually unsubscribing from a channel after refCount reaches 0. */\nconst GRACE_PERIOD_MS = 3_000;\n\n/** Backoff delays between failed WebSocket unsubscribe attempts. */\nconst UNSUB_RETRY_DELAYS_MS = [1_000, 2_000, 4_000] as const;\n\nconst unsubRetryPolicy = retry(handleAll, {\n // Cockatiel stops retrying once the failure index reaches `maxAttempts`, so\n // `length` here yields one initial attempt plus three delayed retries (4 total).\n maxAttempts: UNSUB_RETRY_DELAYS_MS.length,\n backoff: new IterableBackoff([...UNSUB_RETRY_DELAYS_MS]),\n});\n\n// =============================================================================\n// Types — Channel Tracking\n// =============================================================================\n\ntype ChannelEntry = {\n refCount: number;\n gracePeriodTimer?: ReturnType<typeof setTimeout>;\n retryAbort?: AbortController;\n};\n\n// =============================================================================\n// Types — System Notifications\n// =============================================================================\n\n/**\n * System notification data for chain status updates on market-data channels.\n */\nexport type OHLCVSystemNotificationData = {\n chainIds: string[];\n status: 'down' | 'up';\n timestamp?: number;\n};\n\n// =============================================================================\n// Types — Service Options\n// =============================================================================\n\n/**\n * Configuration options for the OHLCV service.\n */\nexport type OHLCVServiceOptions = {\n /** Optional callback to trace performance of OHLCV operations (default: no-op) */\n traceFn?: TraceCallback;\n};\n\n// =============================================================================\n// Action and Event Types\n// =============================================================================\n\nexport type OHLCVServiceActions = OHLCVServiceMethodActions;\n\nexport const OHLCV_SERVICE_ALLOWED_ACTIONS = [\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\nexport const OHLCV_SERVICE_ALLOWED_EVENTS = [\n 'BackendWebSocketService:connectionStateChanged',\n] as const;\n\nexport type AllowedActions = BackendWebSocketServiceMethodActions;\n\n// Events published by OHLCVService\n\nexport type OHLCVServiceBarUpdatedEvent = {\n type: `OHLCVService:barUpdated`;\n payload: [{ channel: string; bar: OHLCVBar }];\n};\n\nexport type OHLCVServiceChainStatusChangedEvent = {\n type: `OHLCVService:chainStatusChanged`;\n payload: [{ chainIds: string[]; status: 'up' | 'down'; timestamp?: number }];\n};\n\nexport type OHLCVServiceSubscriptionErrorEvent = {\n type: `OHLCVService:subscriptionError`;\n payload: [{ channel: string; error: string; operation: string }];\n};\n\nexport type OHLCVServiceEvents =\n | OHLCVServiceBarUpdatedEvent\n | OHLCVServiceChainStatusChangedEvent\n | OHLCVServiceSubscriptionErrorEvent;\n\nexport type AllowedEvents = BackendWebSocketServiceConnectionStateChangedEvent;\n\nexport type OHLCVServiceMessenger = Messenger<\n typeof SERVICE_NAME,\n OHLCVServiceActions | AllowedActions,\n OHLCVServiceEvents | AllowedEvents\n>;\n\n// =============================================================================\n// Main Service Class\n// =============================================================================\n\n/**\n * Service for real-time OHLCV candlestick streaming via the backend WebSocket\n * gateway. Communicates with {@link BackendWebSocketService} exclusively\n * through the messenger — no direct import of the class.\n *\n * Features:\n * - Reference counting: multiple UI consumers share one WebSocket subscription\n * - Grace-period unsubscribe: reuses same-channel subs on rapid back navigation\n * - Grace-period flush: immediately unsubscribes other channels on navigation\n * - Unsubscribe retry: retries failed unsubs with backoff before force reconnect\n * - Idempotency: duplicate subscribe calls for the same channel are no-ops\n * - Reconnect resilience: resubscribes all active channels on reconnect\n * - Chain-status forwarding: listens to system-notifications for chain up/down\n *\n */\nexport class OHLCVService {\n readonly name = SERVICE_NAME;\n\n readonly #messenger: OHLCVServiceMessenger;\n\n readonly #trace: TraceCallback;\n\n readonly #channels = new Map<string, ChannelEntry>();\n\n readonly #mutex = new Mutex();\n\n readonly #chainsUp = new Set<string>();\n\n // =============================================================================\n // Constructor\n // =============================================================================\n\n constructor(\n options: OHLCVServiceOptions & { messenger: OHLCVServiceMessenger },\n ) {\n this.#messenger = options.messenger;\n\n this.#trace =\n options.traceFn ??\n ((<Result>(\n _request: TraceRequest,\n fn?: (context?: TraceContext) => Result,\n ) => fn?.()) as TraceCallback);\n\n this.#messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n\n this.#messenger.subscribe(\n 'BackendWebSocketService:connectionStateChanged',\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n (connectionInfo: WebSocketConnectionInfo) =>\n this.#handleWebSocketStateChange(connectionInfo),\n );\n }\n\n /**\n * Register the system-notifications channel callback.\n */\n init(): void {\n log('OHLCV-WS: Initializing — registering system-notifications callback');\n this.#messenger.call('BackendWebSocketService:addChannelCallback', {\n channelName: SYSTEM_NOTIFICATIONS_CHANNEL,\n callback: (notification: ServerNotificationMessage) =>\n this.#handleSystemNotification(notification),\n });\n }\n\n // =============================================================================\n // Public — Subscribe / Unsubscribe\n // =============================================================================\n\n /**\n * Subscribe to an OHLCV channel. If this is the first subscriber for the\n * given asset/interval/currency combination a WebSocket subscription is\n * created. Additional calls for the same combination only bump the reference\n * count.\n *\n * @param options - The subscription parameters.\n * @returns A promise that resolves once the subscription is established.\n */\n async subscribe(options: OHLCVSubscriptionOptions): Promise<void> {\n const channel = this.#buildChannel(options);\n const releaseLock = await this.#mutex.acquire();\n try {\n await this.#subscribeInner(channel);\n } finally {\n releaseLock();\n }\n }\n\n async #subscribeInner(channel: string): Promise<void> {\n const entry = this.#channels.get(channel);\n\n if (entry?.retryAbort) {\n entry.retryAbort.abort();\n entry.retryAbort = undefined;\n entry.refCount = 1;\n\n if (\n this.#messenger.call(\n 'BackendWebSocketService:channelHasSubscription',\n channel,\n )\n ) {\n await this.#flushOtherChannels(channel);\n log('OHLCV-WS: Cancelled unsubscribe retry — reusing WS subscription', {\n channel,\n });\n return;\n }\n // WS subscription was lost — fall through to recreate it.\n } else if (entry?.gracePeriodTimer) {\n clearTimeout(entry.gracePeriodTimer);\n entry.gracePeriodTimer = undefined;\n log('OHLCV-WS: Cancelled grace-period unsubscribe', {\n channel,\n });\n\n if (\n this.#messenger.call(\n 'BackendWebSocketService:channelHasSubscription',\n channel,\n )\n ) {\n await this.#flushOtherChannels(channel);\n entry.refCount += 1;\n log('OHLCV-WS: WS subscription still alive, bumped refCount', {\n channel,\n refCount: entry.refCount,\n });\n return;\n }\n // WS subscription was lost (e.g. after disconnect/reconnect) — fall\n // through to recreate it. refCount is bumped only after success below.\n } else if (entry && entry.refCount > 0) {\n entry.refCount += 1;\n return;\n }\n\n await this.#flushOtherChannels(channel);\n\n try {\n await this.#messenger.call('BackendWebSocketService:connect');\n\n if (\n this.#messenger.call(\n 'BackendWebSocketService:channelHasSubscription',\n channel,\n )\n ) {\n log(\n 'OHLCV-WS: Channel already has WS subscription (idempotency), skipping',\n {\n channel,\n },\n );\n this.#channels.set(channel, { refCount: 1 });\n return;\n }\n\n await this.#messenger.call('BackendWebSocketService:subscribe', {\n channels: [channel],\n channelType: SUBSCRIPTION_NAMESPACE,\n callback: (notification: ServerNotificationMessage) => {\n this.#handleBarUpdate(channel, notification);\n },\n });\n\n this.#channels.set(channel, { refCount: 1 });\n log('OHLCV-WS: Subscribe succeeded — new WS subscription created', {\n channel,\n });\n } catch (error) {\n log('OHLCV-WS: Subscription failed', { channel, error });\n this.#channels.delete(channel);\n this.#messenger.publish('OHLCVService:subscriptionError', {\n channel,\n error: String(error),\n operation: 'subscribe',\n });\n }\n }\n\n /**\n * Unsubscribe from an OHLCV channel. Decrements the reference count and,\n * when it reaches zero, starts a grace-period timer before actually\n * unsubscribing from the WebSocket to absorb rapid navigation patterns.\n *\n * @param options - The subscription parameters to unsubscribe from.\n * @returns A promise that resolves once the unsubscription is processed.\n */\n async unsubscribe(options: OHLCVSubscriptionOptions): Promise<void> {\n const channel = this.#buildChannel(options);\n const releaseLock = await this.#mutex.acquire();\n try {\n await this.#unsubscribeInner(channel);\n } finally {\n releaseLock();\n }\n }\n\n async #unsubscribeInner(channel: string): Promise<void> {\n const entry = this.#channels.get(channel);\n\n if (!entry || entry.refCount <= 0) {\n return;\n }\n\n entry.refCount -= 1;\n\n if (entry.refCount > 0) {\n return;\n }\n\n entry.gracePeriodTimer = setTimeout(() => {\n entry.gracePeriodTimer = undefined;\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.#performUnsubscribe(channel);\n }, GRACE_PERIOD_MS);\n }\n\n // =============================================================================\n // Private — WebSocket Subscription Helpers\n // =============================================================================\n\n /**\n * Immediately unsubscribe other channels in grace or failed-cleanup state.\n * Called while the subscribe mutex is held before opening a new channel.\n *\n * @param exceptChannel - Channel being subscribed; excluded from flush.\n */\n async #flushOtherChannels(exceptChannel: string): Promise<void> {\n for (const [channel, channelEntry] of this.#channels.entries()) {\n if (channel === exceptChannel || channelEntry.refCount > 0) {\n continue;\n }\n\n this.#clearChannelTimers(channelEntry);\n log('OHLCV-WS: Flushing grace-period channel before new subscribe', {\n flushedChannel: channel,\n newChannel: exceptChannel,\n });\n\n const success = await this.#unsubscribeChannelOnServer(channel);\n if (success) {\n this.#channels.delete(channel);\n } else {\n this.#scheduleUnsubscribeRetry(channel);\n }\n }\n }\n\n #clearChannelTimers(entry: ChannelEntry): void {\n if (entry.gracePeriodTimer) {\n clearTimeout(entry.gracePeriodTimer);\n entry.gracePeriodTimer = undefined;\n }\n entry.retryAbort?.abort();\n entry.retryAbort = undefined;\n }\n\n async #unsubscribeChannelOnServer(channel: string): Promise<boolean> {\n try {\n const subscriptions = this.#messenger.call(\n 'BackendWebSocketService:getSubscriptionsByChannel',\n channel,\n );\n\n for (const sub of subscriptions) {\n await sub.unsubscribe();\n }\n return true;\n } catch (error) {\n log('OHLCV-WS: Unsubscription failed', { channel, error });\n this.#messenger.publish('OHLCVService:subscriptionError', {\n channel,\n error: String(error),\n operation: 'unsubscribe',\n });\n return false;\n }\n }\n\n #scheduleUnsubscribeRetry(channel: string): void {\n const entry = this.#channels.get(channel) ?? { refCount: 0 };\n entry.retryAbort?.abort();\n entry.retryAbort = new AbortController();\n this.#channels.set(channel, entry);\n\n const { signal } = entry.retryAbort;\n\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.#runUnsubRetryLoop(channel, signal);\n }\n\n async #runUnsubRetryLoop(\n channel: string,\n signal: AbortSignal,\n ): Promise<void> {\n try {\n await unsubRetryPolicy.execute(async () => {\n const releaseLock = await this.#mutex.acquire();\n try {\n const current = this.#channels.get(channel);\n if (current && current.refCount > 0) {\n return;\n }\n\n const success = await this.#unsubscribeChannelOnServer(channel);\n if (!success) {\n throw new Error('unsubscribe failed');\n }\n\n this.#channels.delete(channel);\n log('OHLCV-WS: WS unsubscribe completed', { channel });\n } finally {\n releaseLock();\n }\n }, signal);\n } catch {\n if (signal.aborted) {\n return;\n }\n\n log('OHLCV-WS: Unsubscribe retries exhausted — forcing reconnection', {\n channel,\n });\n this.#channels.delete(channel);\n // Last resort: reconnects the shared BackendWebSocketService instance\n // (AccountActivityService and other consumers share this connection).\n // They resubscribe on CONNECTED; OHLCV only resubscribes refCount > 0.\n await this.#messenger\n .call('BackendWebSocketService:forceReconnection')\n .catch(() => {\n // no-op\n });\n }\n }\n\n async #performUnsubscribe(channel: string): Promise<void> {\n const releaseLock = await this.#mutex.acquire();\n try {\n const entry = this.#channels.get(channel);\n if (entry && entry.refCount > 0) {\n log(\n 'OHLCV-WS: Skipping unsubscribe — new subscriber arrived while queued',\n { channel, refCount: entry.refCount },\n );\n return;\n }\n\n log('OHLCV-WS: Grace period expired — performing actual WS unsubscribe', {\n channel,\n });\n\n this.#clearChannelTimers(this.#channels.get(channel) ?? { refCount: 0 });\n } finally {\n releaseLock();\n }\n\n this.#scheduleUnsubscribeRetry(channel);\n }\n\n /**\n * Resubscribe all channels that were active before a disconnect.\n * Called when WebSocket transitions to CONNECTED.\n */\n async #resubscribeActiveChannels(): Promise<void> {\n const releaseLock = await this.#mutex.acquire();\n try {\n const channelCount = this.#channels.size;\n log('OHLCV-WS: Resubscribing active channels after reconnect', {\n count: channelCount,\n });\n\n for (const [channel, entry] of [...this.#channels.entries()]) {\n if (entry.refCount === 0) {\n this.#clearChannelTimers(entry);\n this.#channels.delete(channel);\n continue;\n }\n\n try {\n if (\n this.#messenger.call(\n 'BackendWebSocketService:channelHasSubscription',\n channel,\n )\n ) {\n log(\n 'OHLCV-WS: Channel already subscribed on server, skipping resubscribe',\n {\n channel,\n },\n );\n continue;\n }\n\n await this.#messenger.call('BackendWebSocketService:subscribe', {\n channels: [channel],\n channelType: SUBSCRIPTION_NAMESPACE,\n callback: (notification: ServerNotificationMessage) => {\n this.#handleBarUpdate(channel, notification);\n },\n });\n log('OHLCV-WS: Resubscription succeeded', { channel });\n } catch (error) {\n log('OHLCV-WS: Resubscription failed for channel', {\n channel,\n error,\n });\n }\n }\n } finally {\n releaseLock();\n }\n }\n\n // =============================================================================\n // Private — Message Handlers\n // =============================================================================\n\n #handleBarUpdate(\n channel: string,\n notification: ServerNotificationMessage,\n ): void {\n const bar = notification.data as OHLCVBar;\n\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.#trace(\n {\n name: `${SERVICE_NAME} Bar Update`,\n data: { channel, timestamp: bar.timestamp },\n tags: { service: SERVICE_NAME },\n },\n () => {\n this.#messenger.publish('OHLCVService:barUpdated', { channel, bar });\n },\n );\n }\n\n #handleSystemNotification(notification: ServerNotificationMessage): void {\n const data = notification.data as OHLCVSystemNotificationData;\n const { timestamp } = notification;\n\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 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 this.#messenger.publish('OHLCVService:chainStatusChanged', {\n chainIds: data.chainIds,\n status: data.status,\n timestamp,\n });\n\n log(`OHLCV-WS: Chain status change: ${data.status}`, {\n chains: data.chainIds,\n status: data.status,\n });\n }\n\n async #handleWebSocketStateChange(\n connectionInfo: WebSocketConnectionInfo,\n ): Promise<void> {\n const { state } = connectionInfo;\n\n if (state === WebSocketState.CONNECTED) {\n await this.#resubscribeActiveChannels();\n } else if (state === WebSocketState.DISCONNECTED) {\n const chainsToMarkDown = Array.from(this.#chainsUp);\n\n if (chainsToMarkDown.length > 0) {\n this.#messenger.publish('OHLCVService:chainStatusChanged', {\n chainIds: chainsToMarkDown,\n status: 'down',\n timestamp: Date.now(),\n });\n\n log(\n 'OHLCV-WS: WebSocket disconnection — marked tracked chains as down',\n {\n count: chainsToMarkDown.length,\n chains: chainsToMarkDown,\n },\n );\n\n this.#chainsUp.clear();\n }\n }\n }\n\n // =============================================================================\n // Private — Utility\n // =============================================================================\n\n #buildChannel(options: OHLCVSubscriptionOptions): string {\n return `${SUBSCRIPTION_NAMESPACE}.${options.assetId}.${options.interval}.${options.currency}`;\n }\n\n // =============================================================================\n // Public — Cleanup\n // =============================================================================\n\n /**\n * Destroy the service and clean up all resources.\n */\n destroy(): void {\n for (const entry of this.#channels.values()) {\n this.#clearChannelTimers(entry);\n }\n this.#channels.clear();\n this.#chainsUp.clear();\n\n this.#messenger.call(\n 'BackendWebSocketService:removeChannelCallback',\n SYSTEM_NOTIFICATIONS_CHANNEL,\n );\n }\n}\n"]}
@@ -65,7 +65,9 @@ export type OHLCVServiceMessenger = Messenger<typeof SERVICE_NAME, OHLCVServiceA
65
65
  *
66
66
  * Features:
67
67
  * - Reference counting: multiple UI consumers share one WebSocket subscription
68
- * - Grace-period unsubscribe: avoids rapid unsub/resub during navigation
68
+ * - Grace-period unsubscribe: reuses same-channel subs on rapid back navigation
69
+ * - Grace-period flush: immediately unsubscribes other channels on navigation
70
+ * - Unsubscribe retry: retries failed unsubs with backoff before force reconnect
69
71
  * - Idempotency: duplicate subscribe calls for the same channel are no-ops
70
72
  * - Reconnect resilience: resubscribes all active channels on reconnect
71
73
  * - Chain-status forwarding: listens to system-notifications for chain up/down
@@ -1 +1 @@
1
- {"version":3,"file":"OHLCVService.d.cts","sourceRoot":"","sources":["../../../src/ws/ohlcv/OHLCVService.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,aAAa,EAGd,mCAAmC;AACpC,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AAIrD,OAAO,KAAK,EAAE,oCAAoC,EAAE,2DAA0D;AAC9G,OAAO,KAAK,EAEV,kDAAkD,EAEnD,uCAAsC;AAEvC,OAAO,KAAK,EAAE,yBAAyB,EAAE,+CAA8C;AACvF,OAAO,KAAK,EAAE,QAAQ,EAAE,wBAAwB,EAAE,oBAAmB;AAMrE,QAAA,MAAM,YAAY,iBAAiB,CAAC;AA0BpC;;GAEG;AACH,MAAM,MAAM,2BAA2B,GAAG;IACxC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAMF;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,kFAAkF;IAClF,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB,CAAC;AAMF,MAAM,MAAM,mBAAmB,GAAG,yBAAyB,CAAC;AAE5D,eAAO,MAAM,6BAA6B,+aAUhC,CAAC;AAEX,eAAO,MAAM,4BAA4B,6DAE/B,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,oCAAoC,CAAC;AAIlE,MAAM,MAAM,2BAA2B,GAAG;IACxC,IAAI,EAAE,yBAAyB,CAAC;IAChC,OAAO,EAAE,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,QAAQ,CAAA;KAAE,CAAC,CAAC;CAC/C,CAAC;AAEF,MAAM,MAAM,mCAAmC,GAAG;IAChD,IAAI,EAAE,iCAAiC,CAAC;IACxC,OAAO,EAAE,CAAC;QAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;QAAC,MAAM,EAAE,IAAI,GAAG,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC9E,CAAC;AAEF,MAAM,MAAM,kCAAkC,GAAG;IAC/C,IAAI,EAAE,gCAAgC,CAAC;IACvC,OAAO,EAAE,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClE,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAC1B,2BAA2B,GAC3B,mCAAmC,GACnC,kCAAkC,CAAC;AAEvC,MAAM,MAAM,aAAa,GAAG,kDAAkD,CAAC;AAE/E,MAAM,MAAM,qBAAqB,GAAG,SAAS,CAC3C,OAAO,YAAY,EACnB,mBAAmB,GAAG,cAAc,EACpC,kBAAkB,GAAG,aAAa,CACnC,CAAC;AAMF;;;;;;;;;;;;GAYG;AACH,qBAAa,YAAY;;IACvB,QAAQ,CAAC,IAAI,kBAAgB;gBAiB3B,OAAO,EAAE,mBAAmB,GAAG;QAAE,SAAS,EAAE,qBAAqB,CAAA;KAAE;IAwBrE;;OAEG;IACH,IAAI,IAAI,IAAI;IAaZ;;;;;;;;OAQG;IACG,SAAS,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAiFjE;;;;;;;OAOG;IACG,WAAW,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAiOnE;;OAEG;IACH,OAAO,IAAI,IAAI;CAchB"}
1
+ {"version":3,"file":"OHLCVService.d.cts","sourceRoot":"","sources":["../../../src/ws/ohlcv/OHLCVService.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,aAAa,EAGd,mCAAmC;AACpC,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AAKrD,OAAO,KAAK,EAAE,oCAAoC,EAAE,2DAA0D;AAC9G,OAAO,KAAK,EAEV,kDAAkD,EAEnD,uCAAsC;AAEvC,OAAO,KAAK,EAAE,yBAAyB,EAAE,+CAA8C;AACvF,OAAO,KAAK,EAAE,QAAQ,EAAE,wBAAwB,EAAE,oBAAmB;AAMrE,QAAA,MAAM,YAAY,iBAAiB,CAAC;AAqCpC;;GAEG;AACH,MAAM,MAAM,2BAA2B,GAAG;IACxC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAMF;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,kFAAkF;IAClF,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB,CAAC;AAMF,MAAM,MAAM,mBAAmB,GAAG,yBAAyB,CAAC;AAE5D,eAAO,MAAM,6BAA6B,+aAUhC,CAAC;AAEX,eAAO,MAAM,4BAA4B,6DAE/B,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,oCAAoC,CAAC;AAIlE,MAAM,MAAM,2BAA2B,GAAG;IACxC,IAAI,EAAE,yBAAyB,CAAC;IAChC,OAAO,EAAE,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,QAAQ,CAAA;KAAE,CAAC,CAAC;CAC/C,CAAC;AAEF,MAAM,MAAM,mCAAmC,GAAG;IAChD,IAAI,EAAE,iCAAiC,CAAC;IACxC,OAAO,EAAE,CAAC;QAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;QAAC,MAAM,EAAE,IAAI,GAAG,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC9E,CAAC;AAEF,MAAM,MAAM,kCAAkC,GAAG;IAC/C,IAAI,EAAE,gCAAgC,CAAC;IACvC,OAAO,EAAE,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClE,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAC1B,2BAA2B,GAC3B,mCAAmC,GACnC,kCAAkC,CAAC;AAEvC,MAAM,MAAM,aAAa,GAAG,kDAAkD,CAAC;AAE/E,MAAM,MAAM,qBAAqB,GAAG,SAAS,CAC3C,OAAO,YAAY,EACnB,mBAAmB,GAAG,cAAc,EACpC,kBAAkB,GAAG,aAAa,CACnC,CAAC;AAMF;;;;;;;;;;;;;;GAcG;AACH,qBAAa,YAAY;;IACvB,QAAQ,CAAC,IAAI,kBAAgB;gBAiB3B,OAAO,EAAE,mBAAmB,GAAG;QAAE,SAAS,EAAE,qBAAqB,CAAA;KAAE;IAwBrE;;OAEG;IACH,IAAI,IAAI,IAAI;IAaZ;;;;;;;;OAQG;IACG,SAAS,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAuGjE;;;;;;;OAOG;IACG,WAAW,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAoUnE;;OAEG;IACH,OAAO,IAAI,IAAI;CAYhB"}
@@ -65,7 +65,9 @@ export type OHLCVServiceMessenger = Messenger<typeof SERVICE_NAME, OHLCVServiceA
65
65
  *
66
66
  * Features:
67
67
  * - Reference counting: multiple UI consumers share one WebSocket subscription
68
- * - Grace-period unsubscribe: avoids rapid unsub/resub during navigation
68
+ * - Grace-period unsubscribe: reuses same-channel subs on rapid back navigation
69
+ * - Grace-period flush: immediately unsubscribes other channels on navigation
70
+ * - Unsubscribe retry: retries failed unsubs with backoff before force reconnect
69
71
  * - Idempotency: duplicate subscribe calls for the same channel are no-ops
70
72
  * - Reconnect resilience: resubscribes all active channels on reconnect
71
73
  * - Chain-status forwarding: listens to system-notifications for chain up/down
@@ -1 +1 @@
1
- {"version":3,"file":"OHLCVService.d.mts","sourceRoot":"","sources":["../../../src/ws/ohlcv/OHLCVService.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,aAAa,EAGd,mCAAmC;AACpC,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AAIrD,OAAO,KAAK,EAAE,oCAAoC,EAAE,2DAA0D;AAC9G,OAAO,KAAK,EAEV,kDAAkD,EAEnD,uCAAsC;AAEvC,OAAO,KAAK,EAAE,yBAAyB,EAAE,+CAA8C;AACvF,OAAO,KAAK,EAAE,QAAQ,EAAE,wBAAwB,EAAE,oBAAmB;AAMrE,QAAA,MAAM,YAAY,iBAAiB,CAAC;AA0BpC;;GAEG;AACH,MAAM,MAAM,2BAA2B,GAAG;IACxC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAMF;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,kFAAkF;IAClF,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB,CAAC;AAMF,MAAM,MAAM,mBAAmB,GAAG,yBAAyB,CAAC;AAE5D,eAAO,MAAM,6BAA6B,+aAUhC,CAAC;AAEX,eAAO,MAAM,4BAA4B,6DAE/B,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,oCAAoC,CAAC;AAIlE,MAAM,MAAM,2BAA2B,GAAG;IACxC,IAAI,EAAE,yBAAyB,CAAC;IAChC,OAAO,EAAE,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,QAAQ,CAAA;KAAE,CAAC,CAAC;CAC/C,CAAC;AAEF,MAAM,MAAM,mCAAmC,GAAG;IAChD,IAAI,EAAE,iCAAiC,CAAC;IACxC,OAAO,EAAE,CAAC;QAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;QAAC,MAAM,EAAE,IAAI,GAAG,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC9E,CAAC;AAEF,MAAM,MAAM,kCAAkC,GAAG;IAC/C,IAAI,EAAE,gCAAgC,CAAC;IACvC,OAAO,EAAE,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClE,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAC1B,2BAA2B,GAC3B,mCAAmC,GACnC,kCAAkC,CAAC;AAEvC,MAAM,MAAM,aAAa,GAAG,kDAAkD,CAAC;AAE/E,MAAM,MAAM,qBAAqB,GAAG,SAAS,CAC3C,OAAO,YAAY,EACnB,mBAAmB,GAAG,cAAc,EACpC,kBAAkB,GAAG,aAAa,CACnC,CAAC;AAMF;;;;;;;;;;;;GAYG;AACH,qBAAa,YAAY;;IACvB,QAAQ,CAAC,IAAI,kBAAgB;gBAiB3B,OAAO,EAAE,mBAAmB,GAAG;QAAE,SAAS,EAAE,qBAAqB,CAAA;KAAE;IAwBrE;;OAEG;IACH,IAAI,IAAI,IAAI;IAaZ;;;;;;;;OAQG;IACG,SAAS,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAiFjE;;;;;;;OAOG;IACG,WAAW,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAiOnE;;OAEG;IACH,OAAO,IAAI,IAAI;CAchB"}
1
+ {"version":3,"file":"OHLCVService.d.mts","sourceRoot":"","sources":["../../../src/ws/ohlcv/OHLCVService.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,aAAa,EAGd,mCAAmC;AACpC,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AAKrD,OAAO,KAAK,EAAE,oCAAoC,EAAE,2DAA0D;AAC9G,OAAO,KAAK,EAEV,kDAAkD,EAEnD,uCAAsC;AAEvC,OAAO,KAAK,EAAE,yBAAyB,EAAE,+CAA8C;AACvF,OAAO,KAAK,EAAE,QAAQ,EAAE,wBAAwB,EAAE,oBAAmB;AAMrE,QAAA,MAAM,YAAY,iBAAiB,CAAC;AAqCpC;;GAEG;AACH,MAAM,MAAM,2BAA2B,GAAG;IACxC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAMF;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,kFAAkF;IAClF,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB,CAAC;AAMF,MAAM,MAAM,mBAAmB,GAAG,yBAAyB,CAAC;AAE5D,eAAO,MAAM,6BAA6B,+aAUhC,CAAC;AAEX,eAAO,MAAM,4BAA4B,6DAE/B,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,oCAAoC,CAAC;AAIlE,MAAM,MAAM,2BAA2B,GAAG;IACxC,IAAI,EAAE,yBAAyB,CAAC;IAChC,OAAO,EAAE,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,QAAQ,CAAA;KAAE,CAAC,CAAC;CAC/C,CAAC;AAEF,MAAM,MAAM,mCAAmC,GAAG;IAChD,IAAI,EAAE,iCAAiC,CAAC;IACxC,OAAO,EAAE,CAAC;QAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;QAAC,MAAM,EAAE,IAAI,GAAG,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC9E,CAAC;AAEF,MAAM,MAAM,kCAAkC,GAAG;IAC/C,IAAI,EAAE,gCAAgC,CAAC;IACvC,OAAO,EAAE,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClE,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAC1B,2BAA2B,GAC3B,mCAAmC,GACnC,kCAAkC,CAAC;AAEvC,MAAM,MAAM,aAAa,GAAG,kDAAkD,CAAC;AAE/E,MAAM,MAAM,qBAAqB,GAAG,SAAS,CAC3C,OAAO,YAAY,EACnB,mBAAmB,GAAG,cAAc,EACpC,kBAAkB,GAAG,aAAa,CACnC,CAAC;AAMF;;;;;;;;;;;;;;GAcG;AACH,qBAAa,YAAY;;IACvB,QAAQ,CAAC,IAAI,kBAAgB;gBAiB3B,OAAO,EAAE,mBAAmB,GAAG;QAAE,SAAS,EAAE,qBAAqB,CAAA;KAAE;IAwBrE;;OAEG;IACH,IAAI,IAAI,IAAI;IAaZ;;;;;;;;OAQG;IACG,SAAS,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAuGjE;;;;;;;OAOG;IACG,WAAW,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAoUnE;;OAEG;IACH,OAAO,IAAI,IAAI;CAYhB"}