{"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';\nimport type {\n  WebSocketConnectionInfo,\n  BackendWebSocketServiceConnectionStateChangedEvent,\n  ServerNotificationMessage,\n} from '../BackendWebSocketService';\nimport { WebSocketState } from '../BackendWebSocketService';\nimport type { BackendWebSocketServiceMethodActions } from '../BackendWebSocketService-method-action-types';\nimport type { OHLCVServiceMethodActions } from './OHLCVService-method-action-types';\nimport type { OHLCVBar, OHLCVSubscriptionOptions } from './types';\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"]}