JavaScript SDK

The official NoLag SDK for JavaScript and TypeScript. Full TypeScript support with comprehensive type definitions.

Installation

# npm
npm install @nolag/js-sdk

# yarn
yarn add @nolag/js-sdk

# pnpm
pnpm add @nolag/js-sdk

Quick Start

import { NoLag } from '@nolag/js-sdk'

// Create client with access token
const client = NoLag('your-access-token')

// Connect to NoLag
await client.connect()

// Subscribe to a topic
client.subscribe('chat/room-1')

// Listen for messages
client.on('chat/room-1', (data, meta) => {
  console.log('Received:', data)
})

// Publish a message
client.emit('chat/room-1', { text: 'Hello!' })

Connection Options

const client = NoLag('your-access-token', {
  // Connection
  url: 'wss://broker.nolag.app/ws',  // WebSocket URL (default)
  reconnect: true,                    // Auto-reconnect (default: true)
  reconnectInterval: 5000,            // Initial reconnect delay ms (default: 5000)
  maxReconnectAttempts: 10,           // Max reconnection attempts (default: 10)

  // Messaging
  qos: 1,                             // Default QoS level: 0, 1, or 2 (default: 1)

  // Load Balancing
  loadBalance: false,                 // Enable load balancing (default: false)
  loadBalanceGroup: 'worker-pool',    // Load balance group name

  // Browser-specific
  disconnectOnHidden: false,          // Disconnect when tab hidden (default: false)
  heartbeatInterval: 30000,           // Heartbeat interval ms (default: 30000)

  // Debugging
  debug: false,                       // Enable debug logging (default: false)
})

Connection Management

// Connect to NoLag
await client.connect()

// Check connection status
console.log(client.status)     // 'disconnected' | 'connecting' | 'connected' | 'reconnecting'
console.log(client.connected)  // true or false

// Disconnect (prevents auto-reconnect)
client.disconnect()

// Access client info (available after connect)
console.log(client.actorId)    // Your actor token ID
console.log(client.actorType)  // 'device' | 'user' | 'server'
console.log(client.projectId)  // Project ID

Client Events

// Connection established
client.on('connect', () => {
  console.log('Connected to NoLag')
})

// Connection lost
client.on('disconnect', (reason) => {
  console.log('Disconnected:', reason)
})

// Reconnection starting
client.on('reconnect', () => {
  console.log('Attempting to reconnect...')
})

// Error occurred
client.on('error', (error) => {
  console.error('Error:', error)
})

Subscribing to Topics

// Basic subscription
client.subscribe('chat/room-1')

// With options
client.subscribe('chat/room-1', {
  qos: 2,                        // Override default QoS
  loadBalance: true,             // Enable load balancing for this topic
  loadBalanceGroup: 'workers',   // Specific load balance group
})

// With acknowledgment callback
client.subscribe('chat/room-1', (error) => {
  if (error) {
    console.error('Subscribe failed:', error)
  } else {
    console.log('Subscribed successfully')
  }
})

// Wildcard subscriptions
client.subscribe('chat/+/messages')  // Single level (+)
client.subscribe('users/123/#')      // Multi level (#)

Listening for Messages

// Listen to a specific topic
client.on('chat/room-1', (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 (wildcard handler)
client.onAny((topic, data, meta) => {
  console.log(`[${topic}]`, data)
})

// Remove a specific handler
const handler = (data) => console.log(data)
client.on('chat/room-1', handler)
client.off('chat/room-1', handler)

// Remove all handlers for a topic
client.off('chat/room-1')

Publishing Messages

// Basic publish
client.emit('chat/room-1', { text: 'Hello!' })

// With options
client.emit('chat/room-1', { text: 'Important' }, {
  qos: 2,           // Override default QoS
  retain: true,     // Retain message for new subscribers
  echo: false,      // Don't receive your own message (default: true)
})

// With acknowledgment callback
client.emit('chat/room-1', { text: 'Hello' }, (error) => {
  if (error) {
    console.error('Publish failed:', error)
  } else {
    console.log('Message sent')
  }
})

// With options and callback
client.emit('chat/room-1', { text: 'Hello' }, { qos: 2 }, (error) => {
  if (error) console.error(error)
})

Room Presence

Presence is scoped to rooms. Use the fluent API to set and observe presence:

// Join a room and set presence
const room = client.setApp('chat').setRoom('general')

room.setPresence({
  username: 'Alice',
  status: 'online',
  avatar: '/img/alice.png'
})

// Get all actors in this room (from local cache)
const actors = room.getPresence()
console.log('In room:', Object.keys(actors).length)

// Fetch presence list from server (async)
const freshList = await room.fetchPresence()

// Listen for presence events in this room
room.on('presence:join', (actor) => {
  console.log(`${actor.presence.username} joined`)
})

room.on('presence:leave', (actor) => {
  console.log(`${actor.presence.username} left`)
})

room.on('presence:update', (actor) => {
  console.log(`${actor.presence.username} updated status`)
})

Lobbies (Multi-Room Presence)

Lobbies let you observe presence across multiple rooms. Use them for dashboards and monitoring interfaces:

// Subscribe to a lobby to observe all rooms
const lobby = client.setApp('rides').setLobby('active-trips')

// Subscribe returns a snapshot of current presence
const snapshot = await lobby.subscribe()
// snapshot: { roomId -> { actorId -> presenceData } }
console.log('Active trips:', Object.keys(snapshot).length)

// Listen for presence events (includes room context)
lobby.on('presence:join', (event) => {
  // event: { lobbyId, roomId, actorId, data }
  console.log(`${event.actorId} joined room ${event.roomId}`)
  addToMap(event.roomId, event.actorId, event.data)
})

lobby.on('presence:update', (event) => {
  updateOnMap(event.roomId, event.actorId, event.data)
})

lobby.on('presence:leave', (event) => {
  removeFromMap(event.roomId, event.actorId)
})

// Fetch fresh presence at any time
const freshPresence = await lobby.fetchPresence()

// Unsubscribe when done
lobby.unsubscribe()
Read-only: Lobbies are for observation only. Actors set presence on rooms, not lobbies. See Lobbies documentation for details.

Fluent API (Scoped Pub/Sub)

Use the fluent API to scope subscriptions and messages to a specific app and room:

// Create a room context
const room = client.setApp('chat').setRoom('general')

// Subscribe (topic is 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')

Unsubscribing

// Unsubscribe from a topic
client.unsubscribe('chat/room-1')

// With callback
client.unsubscribe('chat/room-1', (error) => {
  if (error) {
    console.error('Unsubscribe failed:', error)
  }
})

Quality of Service (QoS)

// QoS 0: At most once (fire and forget)
client.emit('telemetry/data', data, { qos: 0 })

// QoS 1: At least once (guaranteed delivery, may duplicate)
client.emit('orders/new', order, { qos: 1 })

// QoS 2: Exactly once (guaranteed single delivery)
client.emit('payments/process', payment, { qos: 2 })

// Set default QoS for all messages
const client = NoLag(token, { qos: 1 })

Load Balancing

Distribute messages across multiple clients in a group using 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 message

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)

TypeScript Support

The SDK includes full TypeScript definitions:

import {
  NoLag,
  NoLagApi,
  NoLagOptions,
  ConnectionStatus,
  MessageMeta,
  ActorPresence,
  PresenceData,
  LobbyPresenceEvent,
  LobbyPresenceState,
  QoS,
  SubscribeOptions,
  EmitOptions,
  RoomContext,
  LobbyContext,
} from '@nolag/js-sdk'

// Type your message data
interface ChatMessage {
  text: string
  sender: string
  timestamp: number
}

client.on('chat/room-1', (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)
  }
}

Browser Usage

<script type="module">
  import { NoLag } from 'https://unpkg.com/@nolag/js-sdk/dist/browser.js'

  const client = NoLag('your-access-token')
  await client.connect()

  client.subscribe('updates')
  client.on('updates', (data) => {
    document.getElementById('output').textContent = JSON.stringify(data)
  })
</script>

Complete Example

import { NoLag, NoLagApi } from '@nolag/js-sdk'

// Create an actor via REST API (server-side)
const api = new NoLagApi('your-api-key')
const actor = await api.actors.create({
  name: 'chat-client',
  actorType: 'user'
})

// Connect with the actor's access token (client-side)
const client = NoLag(actor.accessToken, {
  reconnect: true,
  qos: 1
})

client.on('connect', () => {
  console.log('Connected as:', client.actorId)

  // Set presence
  client.setPresence({ username: 'Alice', status: 'online' })

  // Subscribe to chat room
  const room = client.setApp('chat').setRoom('general')
  room.subscribe('messages')

  room.on('messages', (data, meta) => {
    console.log(`Message from ${meta.from}:`, data)
  })
})

client.on('presence:join', (actor) => {
  console.log(`${actor.presence.username} joined`)
})

client.on('disconnect', (reason) => {
  console.log('Disconnected:', reason)
})

client.on('error', (error) => {
  console.error('Error:', error)
})

await client.connect()

// Send a message
const room = client.setApp('chat').setRoom('general')
room.emit('messages', { text: 'Hello everyone!' })

Next Steps