@nolag/stream

Live streaming engagement with comments, reactions, and polls.

Overview

@nolag/stream adds real-time viewer engagement to any live stream. Viewers can post comments, fire reaction bursts, and vote in polls, all delivered with sub-100ms latency. Broadcasters get live viewer counts, comment moderation hooks, and the ability to create and close polls on the fly. Comments are replayed on join so late arrivals see the full conversation; reactions and poll votes are ephemeral to keep things snappy.

Key Features

  • Threaded comment stream with 7-day replay for late joiners
  • Ephemeral reaction bursts for fire-and-forget emoji animations
  • Live polls with real-time vote tallying and automatic close
  • Live viewer count updated as viewers join and leave
  • 1-day poll result replay so viewers can see recent poll outcomes
  • Automatic reconnection with state restoration

How It Works

NoLagStream wraps the @nolag/js-sdk client and maintains a lobby for viewer counts. Calling joinStream() creates a StreamRoom that subscribes to three topics: comments for durable comment history, _reactions for ephemeral emoji bursts, and polls for durable poll state. A CommentStore accumulates comments while a PollManager tracks the active poll and its running vote totals.

TopicPurposeReplay
commentsViewer comments: text, author info, timestamp7 days
_reactionsEmoji reaction bursts (ephemeral)None
pollsPoll creation, vote updates, and close events1 day

Installation

npm install @nolag/stream @nolag/js-sdk

Quick Start

import { NoLagStream } from '@nolag/stream'

// Create and connect the client
const stream = new NoLagStream('your-access-token', {
  user: { id: 'viewer-123', name: 'Alice' },
})

await stream.connect()

// Join a stream session
const room = await stream.joinStream('live-event-2026')

// Display live viewer count
stream.on('viewerCountChanged', ({ count }) => {
  console.log('Viewers:', count)
})

// Send a comment
await room.sendComment('This is amazing!')

// Listen for comments from other viewers
room.on('comment', (c) => {
  console.log(`[${c.author.name}]: ${c.text}`)
})

// Send a reaction burst
await room.sendReaction('🔥')

room.on('reaction', ({ emoji, count }) => {
  console.log(`${count}x ${emoji}`)
})

// Create a poll (typically broadcaster-side)
const poll = await room.createPoll({
  question: 'Which feature next?',
  options: ['Dark mode', 'Mobile app', 'API access'],
  durationMs: 60_000,
})

// Vote on the active poll
await room.votePoll(poll.pollId, 1) // vote for index 1

room.on('pollUpdated', (p) => {
  console.log('Results:', p.options.map(o => `${o.label}: ${o.votes}`))
})

await stream.leaveStream('live-event-2026')
stream.disconnect()

API Reference

NoLagStream

The main class. Manages the WebSocket connection, lobby viewer counts, and stream room lifecycle.

Method / PropertyDescription
connect()Establish WebSocket connection and join the viewer lobby
disconnect()Gracefully close the connection and leave all streams
joinStream(name)Join a live stream session; returns a StreamRoom instance
leaveStream(name)Leave a stream and unsubscribe from its topics
getOnlineViewers()Return all viewers currently online across all joined streams
viewerCountReactive property with the current total viewer count across all joined streams

Events: NoLagStream

EventPayloadDescription
connectednoneWebSocket connection established
disconnectedreason: stringConnection closed
reconnectednoneReconnection successful; streams are restored automatically
errorerror: ErrorUnrecoverable error occurred
viewerOnlineviewer: StreamViewerA viewer joined any stream
viewerOfflineviewer: StreamViewerA viewer left any stream
viewerCountChanged{ count: number }Total viewer count changed

StreamRoom

Returned by joinStream(). Handles comments, reactions, polls, and per-stream viewer presence.

Method / PropertyDescription
sendComment(text)Publish a comment to this stream
sendReaction(emoji)Fire an ephemeral reaction burst to all viewers
createPoll(opts)Create a new poll with a question, options array, and optional duration
votePoll(pollId, optionIndex)Submit a vote for an option by its zero-based index
closePoll(pollId)Close the poll early and broadcast final results
commentsReactive array of all comments in the local store
activePollThe currently open poll, or null if none
viewerCountNumber of viewers currently in this stream room

Events: StreamRoom

EventPayloadDescription
commentStreamCommentA comment arrived from another viewer
commentSentStreamCommentConfirmation that your own comment was delivered
reaction{ emoji: string, count: number }A reaction burst arrived; animate accordingly
pollCreatedStreamPollA new poll was opened
pollUpdatedStreamPollVote tallies updated
pollClosedStreamPollPoll closed with final results
viewerJoinedStreamViewerA viewer joined this stream room
viewerLeftStreamViewerA viewer left this stream room
viewerCountChanged{ count: number }Viewer count for this stream changed
replayStart{ count: number }Historical comment replay is beginning
replayEnd{ replayed: number }Historical comment replay is complete