@bigmi/core
Version: 
TypeScript library for Bitcoin apps.
109 lines (99 loc) • 3.93 kB
text/typescript
import type { Chain } from '../types/chain.js'
import type { Client } from '../types/client.js'
import type { Transport } from '../types/transport.js'
import { getAction } from '../utils/getAction.js'
import { observe } from '../utils/observe.js'
import { poll } from '../utils/poll.js'
import { stringify } from '../utils/stringify.js'
import { type GetBlockCountReturnType, getBlockCount } from './getBlockCount.js'
export type OnBlockNumberParameter = GetBlockCountReturnType
export type OnBlockNumberFn = (
  blockNumber: OnBlockNumberParameter,
  prevBlockNumber: OnBlockNumberParameter | undefined
) => Promise<void>
export type WatchBlockNumberParameters = {
  /** The callback to call when a new block number is received. */
  onBlockNumber: OnBlockNumberFn
  /** The callback to call when an error occurred when trying to get for a new block. */
  onError?: ((error: Error) => void) | undefined
} & {
  /** Whether or not to emit the missed block numbers to the callback. */
  emitMissed?: boolean | undefined
  /** Whether or not to emit the latest block number to the callback when the subscription opens. */
  emitOnBegin?: boolean | undefined
  /** Polling frequency (in ms). Defaults to Client's pollingInterval config. */
  pollingInterval?: number | undefined
}
export type WatchBlockNumberReturnType = () => void
/**
 * Watches and returns incoming block numbers.
 * @param client - Client to use
 * @param parameters - {@link WatchBlockNumberParameters}
 * @returns A function that can be invoked to stop watching for new block numbers. {@link WatchBlockNumberReturnType}
 */
export function watchBlockNumber<
  chain extends Chain | undefined,
  transport extends Transport,
>(
  client: Client<transport, chain>,
  {
    emitOnBegin = false,
    emitMissed = false,
    onBlockNumber,
    onError,
    pollingInterval = client.pollingInterval,
  }: WatchBlockNumberParameters
): WatchBlockNumberReturnType {
  let prevBlockNumber: GetBlockCountReturnType | undefined
  const observerId = stringify([
    'watchBlockNumber',
    client.uid,
    emitOnBegin,
    emitMissed,
    pollingInterval,
  ])
  // TODO (edge cases):
  // 1) Stop iterating block numbers if we are happy with the result of one onBlockNumber execution but there is more in the queue.
  // 2) If we missed some time - user closed the page and came back when the block is already mined.
  // In this case we probably want to save the block when we send the transaction and track the currently checked blocks until we find the relevant one.
  return observe(observerId, { onBlockNumber, onError }, (emit) =>
    poll(
      async () => {
        try {
          const blockNumber = await getAction(
            client,
            getBlockCount,
            'getBlockCount'
          )({ cacheTime: 0 })
          if (prevBlockNumber) {
            // If the current block number is the same as the previous,
            // we can skip.
            if (blockNumber === prevBlockNumber) {
              return
            }
            // If we have missed out on some previous blocks, and the
            // `emitMissed` flag is truthy, let's emit those blocks.
            if (blockNumber - prevBlockNumber > 1 && emitMissed) {
              for (let i = prevBlockNumber + 1; i < blockNumber; i++) {
                await emit.onBlockNumber(i, prevBlockNumber)
                prevBlockNumber = i
              }
            }
          }
          // If the next block number is greater than the previous,
          // it is not in the past, and we can emit the new block number.
          if (!prevBlockNumber || blockNumber > prevBlockNumber) {
            await emit.onBlockNumber(blockNumber, prevBlockNumber)
            prevBlockNumber = blockNumber
          }
        } catch (err) {
          emit.onError?.(err as Error)
        }
      },
      {
        emitOnBegin,
        interval: pollingInterval,
      }
    )
  )
}