@nolag/feed
Activity feeds with posts, likes, comments, and fan-out delivery.
Overview
@nolag/feed powers real-time activity feeds for social apps, community platforms, and content aggregators. Posts, likes, and comments are delivered live to all subscribers of a channel and replayed for up to 30 days so users joining late always see a full timeline. Fan-out happens server-side: publish once to a channel and every subscriber receives the update instantly. Your app owns one core NoLag client and injects it into NoLagFeed; the wrapper attaches its behaviour to that connection.
Key Features
- Real-time post, like, and comment delivery to all channel subscribers
- 30-day replay across all three topics: posts, reactions, and comments
- Unread post tracking with per-channel badge counts
- Unlike support with live tally updates
- Nested comment threads per post
- Automatic reconnection with channel and state restoration
How It Works
NoLagFeed attaches to an injected @nolag/js-sdk client. Calling joinChannel() creates a FeedChannel that subscribes to three topics: posts for post creation events, reactions for like and unlike events, and comments for comment creation events. A PostStore accumulates posts and their reaction counts, while a CommentStore groups comments by post ID. An unread counter tracks posts that arrived while the user was away. The app owns the socket lifecycle; the wrapper never opens or closes it.
| Topic | Purpose | Replay |
|---|---|---|
posts | Post creation events: text, media, author, timestamp | 30 days |
reactions | Like and unlike events with running tally | 30 days |
comments | Comment creation events keyed by post ID | 30 days |
Installation
npm install @nolag/feed @nolag/js-sdkOne core NoLag client can back several wrapper SDKs at once, for example a feed, chat, and notify on a single socket, as long as each wrapper uses a distinct appName. Each wrapper attaches its handlers on construction and releases them with detach(), and never touches the socket itself. Your app owns connect() and disconnect().
Quick Start
import { NoLag } from '@nolag/js-sdk'
import { NoLagFeed } from '@nolag/feed'
// The app owns one core client. In a browser, pass a token provider so the
// SDK can mint fresh short-lived client tokens from your backend.
const client = NoLag(async () => (await (await fetch('/api/nolag-token')).json()).token)
// Inject the client into the feed wrapper
const feed = new NoLagFeed({ client, username: 'Alice', avatar: '/img/alice.png' })
await client.connect() // the app owns the connection
await feed.ready() // wrapper setup complete
// Join a feed channel (e.g. a user timeline or community feed)
const channel = await feed.joinChannel('user-123-timeline')
// Listen for new posts in real time
channel.on('postCreated', (post) => {
console.log(`${post.author.name}: ${post.text}`)
console.log('Likes:', post.likeCount)
})
// Create a post
await channel.createPost({
text: 'Just shipped a new feature!',
media: [{ type: 'image', url: '/uploads/screenshot.png' }],
})
// Like a post
await channel.likePost('post-abc')
channel.on('postLiked', ({ postId, likeCount }) => {
console.log(`Post ${postId} now has ${likeCount} likes`)
})
// Comment on a post
await channel.addComment('post-abc', 'Congrats!')
channel.on('commentAdded', ({ postId, comment }) => {
console.log(`New comment on ${postId}: ${comment.text}`)
})
// Get unread count and mark as read
console.log('Unread:', channel.unreadCount)
await channel.markRead()
// Teardown: the wrapper releases its handlers; the app closes the socket.
feed.detach()
client.disconnect()API Reference
NoLagFeed
The main class. Attaches to the injected core client, manages global user presence, and the feed channel lifecycle.
Constructor Options
| Option | Type | Description |
|---|---|---|
client | NoLagSocket | Required. The injected core NoLag client the app owns and connects. |
username | string | Required. Display name for this user. |
avatar | string | Optional avatar URL. |
metadata | Record<string, unknown> | Optional custom user data attached to presence. |
appName | string | NoLag app for topic prefixes (default 'feed'). |
channels | string[] | Channels to subscribe to once the wrapper is ready. |
maxPostCache | number | Max posts kept in memory per channel (default 200). |
maxCommentCache | number | Max comments kept in memory per post (default 100). |
debug | boolean | Enable wrapper debug logging (default false). |
| Method | Description |
|---|---|
ready() | Resolves once wrapper setup completed |
detach() | Release this wrapper's handlers and topics; terminal, never closes the socket |
joinChannel(name) | Join a feed channel; returns a FeedChannel instance |
leaveChannel(name) | Leave a channel and unsubscribe from its topics |
getOnlineUsers() | Return all users currently online across all joined channels |
Events: NoLagFeed
| Event | Payload | Description |
|---|---|---|
connected | none | WebSocket connection established |
disconnected | reason: string | Connection closed |
reconnected | none | Reconnection successful; channels are restored automatically |
error | error: Error | Unrecoverable error occurred |
userOnline | user: FeedUser | A user has come online |
userOffline | user: FeedUser | A user has gone offline |
FeedChannel
Returned by joinChannel(). Handles posts, reactions, comments, and unread tracking for a single channel.
| Method / Property | Description |
|---|---|
createPost(opts) | Publish a new post with text, optional media array, and custom metadata |
likePost(postId) | Like a post; increments the like count for all subscribers |
unlikePost(postId) | Remove your like from a post |
addComment(postId, text) | Add a comment to the specified post |
getPosts() | Return all posts in the local store, newest first |
getComments(postId) | Return all comments for the specified post |
unreadCount | Number of posts that arrived since markRead() was last called |
markRead() | Reset the unread count to zero |
Events: FeedChannel
| Event | Payload | Description |
|---|---|---|
postCreated | FeedPost | A new post arrived from another user |
postSent | FeedPost | Confirmation that your own post was delivered |
postLiked | { postId: string, likeCount: number, likedBy: string } | A post received a new like |
postUnliked | { postId: string, likeCount: number, unlikedBy: string } | A like was removed from a post |
commentAdded | { postId: string, comment: FeedComment } | A comment was added to a post |
commentSent | { postId: string, comment: FeedComment } | Confirmation that your own comment was delivered |
subscriberJoined | user: FeedUser | A user joined this channel |
subscriberLeft | user: FeedUser | A user left this channel |
replayStart | { count: number } | Historical feed replay is beginning |
replayEnd | { replayed: number } | Historical feed replay is complete |
unreadChanged | { count: number } | The unread post count for this channel changed |