@nolag/notify

Real-time notifications with channels, read/unread tracking, and badge counts.

Overview

@nolag/notify delivers real-time notifications to your users with full read/unread lifecycle management. Notifications are organised into channels (logical groupings like 'alerts', 'mentions', or 'system') and each channel tracks its own unread count independently. Your app owns one core NoLag client and injects it into NoLagNotify; the wrapper attaches its behaviour to that connection.

Key Features

  • Channel-based notification delivery with independent unread counts
  • Per-notification and bulk read/unread tracking
  • Global badge counts aggregated across all subscribed channels
  • Rich notification payloads with title, body, icon, and custom data
  • Per-user or per-segment delivery through subscription filters
  • Automatic reconnection with channel subscriptions restored

How It Works

NoLagNotify attaches to an injected @nolag/js-sdk client. Each channel you subscribe to creates a NotifyChannel instance that subscribes to two topics: notifications for notification delivery and _read for read-receipt signals. A NotificationStore inside each channel accumulates notifications and tracks read state, while the main class aggregates badge counts across all channels. The app owns the socket lifecycle; the wrapper never opens or closes it.

TopicPurpose
notificationsNotification payloads: title, body, icon, and custom data
_readRead receipt signals

A notification reaches the users who are connected and subscribed when it is published. A fresh subscribe or an ordinary reconnect never replays history; see Replay for the one case where the broker replays messages.

Before you start. Create an app from the nolag-notify-sdk blueprint. That seeds the rooms this SDK expects (alerts, updates) and the online lobby, and returns an app slug with a random suffix. That slug is the appName you pass to the wrapper. You can also do this in the portal: Apps, New App, pick the blueprint.

import { NoLagApi } from "@nolag/js-sdk";

const api = new NoLagApi(process.env.NOLAG_API_KEY); // nlg_live_...
const app = await api.apps.create({ name: "My App", blueprintId: "nolag-notify-sdk" });
console.log(app.slug); // e.g. "my-app-a3f9": this is your appName

const actor = await api.actors.create({ name: "web-client", actorType: "user" });
console.log(actor.accessToken); // shown once; keep it

Installation

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

Shared connection. One core NoLag client can back several wrapper SDKs at once, for example notify, chat, and a dashboard 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 { NoLagNotify } from '@nolag/notify'

// 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 notify wrapper
const notify = new NoLagNotify({
  client,
  appName: APP_SLUG, // the slug returned when you created the app
  metadata: { name: 'Alice' },
})

await client.connect()   // the app owns the connection
await notify.ready()     // wrapper setup complete

// Subscribe to a seeded channel
const channel = notify.subscribe('alerts')

// Listen for new notifications and mark each one read once it is shown
channel.on('notification', (n) => {
  console.log(`[${n.title}]: ${n.body}`)
  console.log('Unread:', channel.getUnread().length)
  channel.markRead(n.id)
})

// Mark everything in the channel as read
channel.markAllRead()

// Get global badge counts across all channels
const badges = notify.getBadgeCounts()
console.log('Total unread:', badges.total, badges.byChannel)

// Mark all channels read at once
notify.markAllRead()

// Publish a notification (typically from a server-side actor on the same app).
// Publishers never receive their own notifications back, so the sender's
// local store and badge counts are not updated by its own send.
channel.send('Deploy finished', { body: 'v2.4.0 is live', data: { version: '2.4.0' } })

// Teardown: the wrapper releases its handlers; the app closes the socket.
notify.detach()
client.disconnect()

API Reference

NoLagNotify

The main class. Attaches to the injected core client, manages channel subscriptions, and global badge counts.

Constructor Options

OptionTypeDescription
clientNoLagSocketRequired. The injected core NoLag client the app owns and connects.
metadataRecord<string, unknown>Optional custom user data attached to presence.
appNamestringThe app slug returned when you created the app (default 'notify', which only works if an app with exactly that slug exists).
channelsstring[]Channels to subscribe to once the wrapper is ready.
maxNotificationCachenumberMax notifications kept in memory per channel (default 500).
debugbooleanEnable wrapper debug logging (default false).
MethodReturnsDescription
ready()Promise<void>Resolves once wrapper setup completed
detach()voidRelease this wrapper's handlers and topics; terminal, never closes the socket
subscribe(channel, opts?)NotifyChannelSubscribe to a notification channel; opts.filters limits delivery to matching notifications; throws before ready()
unsubscribe(channel)voidUnsubscribe from a channel and remove it from badge tracking
getBadgeCounts()BadgeCounts{ total, byChannel }: unread counts per subscribed channel plus the aggregate
markAllRead()voidMark all notifications across all subscribed channels as read

Events: NoLagNotify

EventPayloadDescription
connectednoneWrapper setup completed on a fresh connection
disconnectedreason: stringConnection closed
reconnectingnoneThe client is attempting to reconnect
reconnectednoneReconnection successful; channels are restored automatically
errorerror: ErrorUnrecoverable error occurred
notificationNotificationA new notification arrived on any subscribed channel (notification.channel says which)
badgeUpdatedBadgeCountsUnread counts changed; { total, byChannel }

NotifyChannel

Returned by subscribe(). Scoped to a single channel; handles notification delivery, storage, and read state.

MethodReturnsDescription
send(title, opts?)voidPublish a notification to this channel; opts accepts body, icon, data, filter, filters
markRead(id)voidMark a single notification as read by ID
markAllRead()voidMark every notification in this channel as read
getNotifications()Notification[]All notifications in the local store
getUnread()Notification[]Only unread notifications
setFilters(values)voidReplace this channel's subscription filters; see Filters

A Notification has id, channel, title, body?, icon?, data?, timestamp, read, and isReplay.

Events: NotifyChannel

EventPayloadDescription
notificationNotificationA new notification arrived on this channel
readid: stringA notification was marked as read
readAllnoneAll notifications in this channel were marked as read