subscribe

Executes a callback when an on-chain object is updated. The client will receive two messages, one when the transaction is first seen in the mempool, and another when it is confirmed on-chain. The callback receives the new revision of the object and the hex of the transaction encoding the update. This function uses Server-Sent Events (SSEs) to provide real-time updates.

Type

subscribe(
  id: string,
  onMessage: ({ rev, hex }: { rev: string; hex: string }) => void,
  onError?: (error: Event) => void,
): Promise<() => void>

Parameters

id

The id of an on-chain object.

onMessage

The function to call when the object is updated. The callback has two parameters, the new revision of the object and the hex of the transaction encoding the update.

onError

The function to call when an error is thrown.

Return Value

A function to close the connection to the server.

Description

The function enables real-time updates via Server-Sent Events (SSEs). The function takes an on-chain ID and a callback function as arguments. The callback is triggered whenever a method is called on the on-chain object with the specified ID. The callback receives two parameters: the new revision of the object and the hex of the transaction encoding the update.

When invoked, the function establishes a connection to the server to listen for updates related to the specified on-chain ID. It returns a function that can be called to close the connection when real-time updates are no longer needed.

Example

import { Computer, Contract } from '@bitcoin-computer/lib'
import { chain, expect, network, url } from '../../utils/index.js'

describe('subscribe', () => {
  // A smart contract
  class Counter extends Contract {
    n: number

    constructor() {
      super({ n: 0 })
    }
    inc() {
      this.n += 1
    }
  }

  let computer: Computer
  let close: () => void

  beforeEach('Before Computer', async () => {
    computer = new Computer({ chain, network, url })
    await computer.faucet(10e8)
  })

  it('Should work when subscribing by id', async () => {
    // Create on-chain object
    const c = await computer.new(Counter, [])

    // Subscribe to updates on object c
    let eventCount = 0
    const close = await computer.subscribe(c._id, ({ rev, hex }) => {
      expect(rev).to.be.a('string')
      expect(hex).to.be.a('string')
      eventCount += 1
    })

    // Update on-chain object
    await c.inc()
    await c.inc()

    // Wait for events using a Promise
    await new Promise<void>((resolve, reject) => {
      let retryCount = 0

      // Retry every 200 ms
      const interval = setInterval(() => {
        retryCount += 1

        // if at least two events have been received the test passes
        if (eventCount >= 2) {
          clearInterval(interval)
          resolve()
        }

        // After 4 s the test fails
        if (retryCount > 20) {
          clearInterval(interval)
          close()
          reject(new Error('Missed SSE'))
        }
      }, 200)
    })

    // Close connection to the server
    close()
  })

  it('Should not emit events for unsubscribed keys', async () => {
    let eventCount = 0
    const nonValidId = 'error:0'
    const c = await computer.new(Counter, [])
    close = await computer.subscribe(nonValidId, ({}) => {
      eventCount += 1
    })

    await c.inc()
    await c.inc()

    await new Promise<void>((resolve, reject) => {
      let retryCount = 0
      const interval = setInterval(() => {
        retryCount += 1

        if (retryCount > 10) {
          clearInterval(interval)
          if (eventCount === 0) {
            resolve() // Success: no events
          } else {
            reject(new Error('Received unexpected SSE for unsubscribed key'))
          }
        }
      }, 200)
    })
    close()
  })
})

Source