Search documentation

Search pages and headings

Client Library

Events & Options

The four events, the constructor options, and the pacing and retry behaviour the client implements for you.

Events

on() is chainable and may be called more than once per event — every registered listener is invoked.

Event Payload When
connected none After each successful session bootstrap, including automatic re-bootstraps
message MessageSchema Once per message, in order
error ApiHttpError On every failure, including ones the client then recovers from
end string (reason) Exactly once, when the stream is over
client
  .on('connected', () => console.log('connected'))
  .on('message', (message) => render(message))
  .on('error', (error) => console.error(error.code, error.message))
  .on('end', (reason) => console.log('ended:', reason))

error is not fatal

The client emits error and then decides whether to continue. Receiving one does not mean the stream stopped — wait for end to know that. A recovered TOKEN_EXPIRED emits error and then connected, and the stream carries on.

end reasons

Reason Meaning
"no more continuations" The chat finished; YouTube returned a null token
"giving up after N error(s)" Consecutive failures hit the limit
"stopped" stop() was called

end fires exactly once per stream, whichever path got there.

Options

new LiveChatApiClient(
  { baseUrl, videoUrl },
  { messageSpacingMs: 0, fetchFn: myFetch },
)
Option Type Default Description
messageSpacingMs number 500 Minimum gap between message events. 0 disables spacing
fetchFn FetchFn globalThis.fetch Replacement fetch, for proxies, auth headers or tests

messageSpacingMs

Messages are not emitted as they arrive in a batch. Each poll's messages go into an internal queue and a consumer emits them one at a time, at least messageSpacingMs apart — so an initial backlog of two hundred messages trickles into your UI instead of arriving in a single frame.

Spacing only ever delays; it never speeds anything up. A stream slower than the gap is not affected. Set 0 to deliver as fast as messages arrive:

const client = new LiveChatApiClient({ baseUrl, videoUrl }, { messageSpacingMs: 0 })

Ordering is preserved regardless of the value.

fetchFn

import type { FetchFn } from '@gettersethya/yt-livechat-client'

const authorized: FetchFn = (url, init) =>
  fetch(url, { ...init, headers: { ...init.headers, Authorization: token } })

const client = new LiveChatApiClient({ baseUrl, videoUrl }, { fetchFn: authorized })

Useful for putting the API behind your own gateway, or for injecting a stub in tests.

Pacing the poll loop

The client waits timeoutMs from each poll response, clamped:

Value
Minimum poll interval 500 ms
Maximum poll interval 15 000 ms

The wait is interruptible: stop() is noticed within about 250 ms rather than after the full delay.

Retries and recovery

Value
Backoff Exponential from 500 ms
Backoff cap 15 000 ms
Consecutive failures tolerated 3

A successful poll resets the failure counter. When the limit is reached, the client emits end with "giving up after 3 error(s)".

TOKEN_EXPIRED is handled specially: instead of counting as a failure, the client re-bootstraps the session, emits connected again, resets the counter and continues. Only if that re-bootstrap itself fails does it count against the limit.

Instance properties

Property Type Description
videoId string The resolved 11-character id, available immediately after construction
session SessionResponseSchema | null The current session; null before connect()

session.token changes on every poll — read it for debugging, don't cache it.

Typing against the interface

import type { ILiveChatApiClient } from '@gettersethya/yt-livechat-client'

function attach(client: ILiveChatApiClient) {
  client.on('message', (message) => render(message))
}

Depending on ILiveChatApiClient rather than the concrete class keeps a fake substitutable in tests.