@nolag/js-sdk
v1.1.0
Published
NoLag real-time messaging SDK for browser and Node.js
Readme
@nolag/js-sdk
Real-time messaging SDK for browser and Node.js. Connects to NoLag via WebSocket with MessagePack binary protocol.
Installation
npm install @nolag/js-sdkSetup
Before using the SDK, you need to set up your NoLag project:
- Create a Project in the NoLag Dashboard
- Create an App with your desired topics
- Create Topics in your App schema (e.g.,
messages,status,commands) - Create Actors to get access tokens (
at_xxx...)
Note: Topics must be defined in your App schema before you can subscribe or publish to them. Rooms are created dynamically at runtime.
Quick Start
import { NoLag } from "@nolag/js-sdk";
// Create client with access token
const client = NoLag("your_actor_access_token");
// Set up event handlers
client.on("connect", () => {
console.log("Connected!");
// Set your presence
client.setPresence({ username: "Alice", status: "online" });
// Subscribe to topics
client.subscribe("chat/lobby/messages");
});
// Listen for messages
client.on("chat/lobby/messages", (data, meta) => {
console.log("Received:", data);
});
// Connect
await client.connect();
// Publish messages
client.emit("chat/lobby/messages", { text: "Hello world!" });Features
- WebSocket + MessagePack - Efficient binary protocol
- Auto-reconnect - Automatic reconnection with exponential backoff
- Server-side subscription persistence - Subscriptions restored automatically on reconnect
- Presence - Project-level presence tracking
- Load Balancing - Distribute messages across worker pools
- Fluent API - Scoped pub/sub with
setApp().setRoom() - QoS Levels - At-most-once, at-least-once, exactly-once delivery
- TypeScript - Full type definitions included
- REST API Client - Manage apps, rooms, and actors programmatically
API Reference
Creating a Client
import { NoLag } from "@nolag/js-sdk";
const client = NoLag(token, options?);Parameters:
| Parameter | Type | Description |
|-----------|------|-------------|
| token | string | Actor access token (required) |
| options | NoLagOptions | Configuration options (optional) |
Options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| url | string | wss://broker.nolag.app/ws | WebSocket URL |
| reconnect | boolean | true | Auto-reconnect on disconnect |
| reconnectInterval | number | 5000 | Initial reconnect delay (ms) |
| disconnectOnHidden | boolean | false | Disconnect when browser tab hidden |
| heartbeatInterval | number | 30000 | Heartbeat interval (ms), 0 to disable |
| debug | boolean | false | Enable debug logging |
| qos | 0 \| 1 \| 2 | 1 | Default QoS level |
| loadBalance | boolean | false | Enable load balancing for all subscriptions |
| loadBalanceGroup | string | - | Load balance group name |
Connection
// Connect
await client.connect();
// Disconnect (prevents auto-reconnect)
client.disconnect();
// Check status
console.log(client.connected); // boolean
console.log(client.status); // "disconnected" | "connecting" | "connected" | "reconnecting"
// Client info (available after connect)
console.log(client.actorId); // Actor token ID
console.log(client.actorType); // "device" | "user" | "server"
console.log(client.projectId); // Project IDConnection Events
client.on("connect", () => {
console.log("Connected!");
});
client.on("disconnect", (reason) => {
console.log("Disconnected:", reason);
});
client.on("reconnect", () => {
console.log("Reconnecting...");
});
client.on("error", (error) => {
console.error("Error:", error);
});Reconnection Behavior
The SDK automatically handles reconnection:
- Connection lost - WebSocket closes unexpectedly
- Exponential backoff - Waits 5s, 7.5s, 11.25s... (max 30s)
- Re-authenticate - Sends auth token to server
- Server restores subscriptions - All subscriptions automatically restored
- Re-send presence - Presence data re-sent if previously set
Max reconnect attempts: 10 (then stops trying)
Topics
Topics use path-style naming: app/room/topic
Subscribing
// Basic subscription
client.subscribe("chat/lobby/messages");
// With options
client.subscribe("chat/lobby/messages", {
qos: 2, // Override default QoS
loadBalance: true, // Enable load balancing
loadBalanceGroup: "workers", // Specific group
});
// With acknowledgment callback
client.subscribe("chat/lobby/messages", (error) => {
if (error) console.error("Subscribe failed:", error);
else console.log("Subscribed!");
});
// Wildcard subscriptions
client.subscribe("chat/+/messages"); // Single level (+)
client.subscribe("users/123/#"); // Multi level (#)Unsubscribing
client.unsubscribe("chat/lobby/messages");
// With callback
client.unsubscribe("chat/lobby/messages", (error) => {
if (error) console.error("Unsubscribe failed:", error);
});Receiving Messages
// Listen to specific topic
client.on("chat/lobby/messages", (data, meta) => {
console.log("Data:", data);
console.log("From:", meta.from); // Sender's actorTokenId
console.log("Time:", meta.timestamp); // Server timestamp
});
// Listen to all subscribed topics (client-side wildcard)
client.onAny((topic, data, meta) => {
console.log(`[${topic}]`, data);
});
// Remove handler
client.off("chat/lobby/messages", handler);
// Remove all handlers for a topic
client.off("chat/lobby/messages");Publishing Messages
// Basic emit
client.emit("chat/lobby/messages", { text: "Hello!" });
// With options
client.emit("chat/lobby/messages", { text: "Hello!" }, {
qos: 2, // Override default QoS
retain: true, // Retain message for new subscribers
echo: false, // Don't receive your own message (default: true)
});
// With callback
client.emit("chat/lobby/messages", { text: "Hello!" }, (error) => {
if (error) console.error("Emit failed:", error);
});
// With options and callback
client.emit("chat/lobby/messages", { text: "Hello!" }, { qos: 2 }, (error) => {
if (error) console.error(error);
});Fluent API (Scoped Pub/Sub)
Scope subscriptions and messages to a specific app and room:
// Create a room context
const room = client.setApp("chat").setRoom("general");
// Subscribe (topic auto-prefixed to "chat/general/messages")
room.subscribe("messages");
// Listen for messages
room.on("messages", (data, meta) => {
console.log("Message:", data);
});
// Publish (also auto-prefixed)
room.emit("messages", { text: "Hello room!" });
// Get the full topic prefix
console.log(room.prefix); // "chat/general"
// Unsubscribe
room.unsubscribe("messages");Presence
Presence is project-level - when you're online, you're visible to all actors in the project.
Setting Presence
// Set your presence data (persists across reconnects)
client.setPresence({
username: "Alice",
status: "online",
avatar: "/img/alice.png"
});Presence Events
// Someone joined
client.on("presence:join", (actor) => {
console.log(`${actor.presence.username} is online`);
});
// Someone left
client.on("presence:leave", (actor) => {
console.log(`${actor.actorTokenId} went offline`);
});
// Someone updated their presence
client.on("presence:update", (actor) => {
console.log(`${actor.presence.username} is now ${actor.presence.status}`);
});Getting Presence
// Get all online actors (local cache)
const everyone = client.getPresence();
// Get specific actor
const alice = client.getPresence("actor_token_id_123");
// Fetch fresh list from server (async)
const presenceList = await client.fetchPresence();QoS Levels
| Level | Name | Description | |-------|------|-------------| | 0 | At most once | Fire and forget, no guarantee | | 1 | At least once | Guaranteed delivery, may duplicate | | 2 | Exactly once | Guaranteed single delivery |
// Set default QoS
const client = NoLag("token", { qos: 1 });
// Override per message
client.emit("important/data", data, { qos: 2 });
// Override per subscription
client.subscribe("critical/events", { qos: 2 });Load Balancing
Distribute messages across multiple clients in a group (round-robin):
// Enable for all subscriptions
const client = NoLag("token", {
loadBalance: true,
loadBalanceGroup: "worker-pool-1"
});
// Or per-subscription
client.subscribe("jobs/process", {
loadBalance: true,
loadBalanceGroup: "job-workers"
});
// Only ONE client in the group receives each messageUse cases:
- Worker queues
- Distributed task processing
- Horizontal scaling
REST API Client
Manage apps, rooms, and actors programmatically:
import { NoLagApi } from "@nolag/js-sdk";
const api = new NoLagApi("your-api-key", {
baseUrl: "https://api.nolag.app/v1", // Optional
timeout: 30000, // Optional
});
// Apps
const apps = await api.apps.list();
const app = await api.apps.create({ name: "My App" });
await api.apps.update(app.appId, { name: "Updated Name" });
await api.apps.delete(app.appId);
// Rooms
const rooms = await api.rooms.list(appId);
const room = await api.rooms.create(appId, { name: "General", slug: "general" });
await api.rooms.delete(appId, room.roomId);
// Actors
const actors = await api.actors.list();
const actor = await api.actors.create({
name: "Device 1",
actorType: "device"
});
console.log("Access Token:", actor.accessToken); // Save this! Only shown once
await api.actors.update(actor.actorTokenId, { name: "Updated Device" });
await api.actors.delete(actor.actorTokenId);Full Example
import { NoLag } from "@nolag/js-sdk";
async function main() {
const client = NoLag("your_access_token", {
debug: true,
qos: 1,
});
// Connection events
client.on("connect", () => {
console.log("Connected as:", client.actorId);
// Set presence
client.setPresence({
username: "Alice",
status: "online",
});
// Subscribe using fluent API
const room = client.setApp("chat").setRoom("general");
room.subscribe("messages");
room.on("messages", (data, meta) => {
console.log(`Message from ${meta.from}:`, data);
});
});
client.on("disconnect", (reason) => {
console.log("Disconnected:", reason);
});
client.on("reconnect", () => {
console.log("Reconnecting...");
});
client.on("error", (error) => {
console.error("Error:", error);
});
// Presence events
client.on("presence:join", (actor) => {
console.log(`${actor.presence.username} joined`);
});
client.on("presence:leave", (actor) => {
console.log(`${actor.actorTokenId} left`);
});
// Connect
await client.connect();
// Send a message
const room = client.setApp("chat").setRoom("general");
room.emit("messages", { text: "Hello everyone!" });
}
main().catch(console.error);Browser Usage
<script type="module">
import { NoLag } from "https://unpkg.com/@nolag/js-sdk/dist/browser.js";
const client = NoLag("your_token");
client.on("connect", () => {
console.log("Connected!");
client.subscribe("updates");
});
client.on("updates", (data) => {
document.getElementById("output").textContent = JSON.stringify(data);
});
client.connect();
</script>TypeScript
Full TypeScript support included:
import {
NoLag,
NoLagApi,
NoLagApiError,
NoLagOptions,
ConnectionStatus,
ActorPresence,
ActorType,
MessageMeta,
PresenceData,
QoS,
SubscribeOptions,
EmitOptions,
AppContext,
RoomContext,
} from "@nolag/js-sdk";
// Type your message data
interface ChatMessage {
text: string;
sender: string;
timestamp: number;
}
client.on("chat/room", (data: ChatMessage, meta: MessageMeta) => {
console.log(`[${data.sender}]: ${data.text}`);
});Error Handling
// Connection errors
client.on("error", (error) => {
console.error("Client error:", error.message);
});
// Subscribe/emit callbacks
client.subscribe("topic", (error) => {
if (error) console.error("Subscribe failed:", error.message);
});
client.emit("topic", data, (error) => {
if (error) console.error("Emit failed:", error.message);
});
// REST API errors
import { NoLagApiError } from "@nolag/js-sdk";
try {
await api.apps.get("invalid-id");
} catch (error) {
if (error instanceof NoLagApiError) {
console.error("API Error:", error.statusCode, error.message);
}
}License
MIT
