@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.
| Topic | Purpose | Replay |
|---|---|---|
comments | Viewer comments: text, author info, timestamp | 7 days |
_reactions | Emoji reaction bursts (ephemeral) | None |
polls | Poll creation, vote updates, and close events | 1 day |
Installation
npm install @nolag/stream @nolag/js-sdkQuick 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 / Property | Description |
|---|---|
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 |
viewerCount | Reactive property with the current total viewer count across all joined streams |
Events: NoLagStream
| Event | Payload | Description |
|---|---|---|
connected | none | WebSocket connection established |
disconnected | reason: string | Connection closed |
reconnected | none | Reconnection successful; streams are restored automatically |
error | error: Error | Unrecoverable error occurred |
viewerOnline | viewer: StreamViewer | A viewer joined any stream |
viewerOffline | viewer: StreamViewer | A 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 / Property | Description |
|---|---|
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 |
comments | Reactive array of all comments in the local store |
activePoll | The currently open poll, or null if none |
viewerCount | Number of viewers currently in this stream room |
Events: StreamRoom
| Event | Payload | Description |
|---|---|---|
comment | StreamComment | A comment arrived from another viewer |
commentSent | StreamComment | Confirmation that your own comment was delivered |
reaction | { emoji: string, count: number } | A reaction burst arrived; animate accordingly |
pollCreated | StreamPoll | A new poll was opened |
pollUpdated | StreamPoll | Vote tallies updated |
pollClosed | StreamPoll | Poll closed with final results |
viewerJoined | StreamViewer | A viewer joined this stream room |
viewerLeft | StreamViewer | A 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 |