WebSocket API

WebSocket API

Cloudillo provides three WebSocket endpoints for real-time features: Bus (pub/sub messaging), RTDB (real-time database), and CRDT (collaborative documents).

Message Bus (/ws/bus)

The message bus provides pub/sub messaging, presence tracking, and notifications.

Connection

import * as cloudillo from '@cloudillo/base'

await cloudillo.init('my-app')

const bus = cloudillo.openMessageBus({
  channels: ['notifications', 'presence']
})

Protocol

Client → Server:

// Subscribe to channel
{
  type: 'subscribe',
  channel: 'notifications'
}

// Unsubscribe
{
  type: 'unsubscribe',
  channel: 'notifications'
}

// Publish message
{
  type: 'publish',
  channel: 'notifications',
  data: {
    event: 'new-post',
    actionId: 'act_123'
  }
}

// Set presence
{
  type: 'presence',
  status: 'online',
  data: {
    currentPage: '/posts'
  }
}

Server → Client:

// Message received
{
  type: 'message',
  channel: 'notifications',
  data: {
    event: 'new-post',
    actionId: 'act_123'
  },
  from: 'alice@example.com',
  timestamp: 1735000000
}

// Presence update
{
  type: 'presence',
  userId: 'bob@example.com',
  status: 'online',
  data: {...}
}

// Subscription confirmed
{
  type: 'subscribed',
  channel: 'notifications'
}

Usage Example

const bus = cloudillo.openMessageBus()

// Subscribe to notifications
bus.subscribe('notifications', (message) => {
  console.log('Notification:', message)
  showToast(message.data)
})

// Publish typing indicator
bus.publish('typing', {
  conversationId: 'conv_123',
  typing: true
})

// Set presence
bus.setPresence('online', {
  currentPage: window.location.pathname
})

// Listen for presence changes
bus.on('presence', (update) => {
  console.log(`${update.userId} is ${update.status}`)
})

Real-Time Database (/ws/rtdb/:fileId)

Real-time synchronization of structured data. See RTDB for full documentation.

Connection

import { RtdbClient } from '@cloudillo/rtdb'

const rtdb = new RtdbClient({
  fileId: 'my-database',
  token: cloudillo.accessToken,
  url: 'wss://server.com/ws/rtdb'
})

Protocol

Client → Server:

// Subscribe to collection
{
  type: 'subscribe',
  collection: 'todos',
  query: {
    where: [['completed', '==', false]],
    orderBy: [['createdAt', 'desc']],
    limit: 20
  }
}

// Create document
{
  type: 'create',
  collection: 'todos',
  data: {
    title: 'Learn Cloudillo',
    completed: false
  }
}

// Update document
{
  type: 'update',
  collection: 'todos',
  id: 'todo_123',
  data: {
    completed: true
  }
}

// Delete document
{
  type: 'delete',
  collection: 'todos',
  id: 'todo_123'
}

Server → Client:

// Snapshot update
{
  type: 'snapshot',
  collection: 'todos',
  data: [
    { id: 'todo_123', title: 'Learn Cloudillo', completed: false },
    { id: 'todo_456', title: 'Build app', completed: false }
  ]
}

// Document created
{
  type: 'created',
  collection: 'todos',
  data: { id: 'todo_789', title: 'New todo', completed: false }
}

// Document updated
{
  type: 'updated',
  collection: 'todos',
  id: 'todo_123',
  data: { completed: true }
}

// Document deleted
{
  type: 'deleted',
  collection: 'todos',
  id: 'todo_123'
}

Collaborative Documents (/ws/crdt/:docId)

CRDT synchronization using Yjs protocol. See CRDT for full documentation.

Connection

import * as Y from 'yjs'
import * as cloudillo from '@cloudillo/base'

const yDoc = new Y.Doc()
const { provider } = await cloudillo.openYDoc(yDoc, 'my-document')

Protocol

The CRDT endpoint uses the y-websocket protocol:

Binary messages:

  • Sync Step 1: Client sends document state
  • Sync Step 2: Server responds with missing updates
  • Updates: Incremental document changes
  • Awareness: Cursor positions, selections, user info

Awareness format:

{
  user: {
    name: 'Alice Johnson',
    idTag: 'alice@example.com',
    color: '#ff6b6b'
  },
  cursor: {
    line: 10,
    column: 5
  },
  selection: {
    start: { line: 10, column: 5 },
    end: { line: 10, column: 10 }
  }
}

Usage Example

const yDoc = new Y.Doc()
const { provider } = await cloudillo.openYDoc(yDoc, 'doc_123')

// Use shared types
const yText = yDoc.getText('content')
yText.insert(0, 'Hello, collaborative world!')

// Listen for remote changes
yText.observe((event) => {
  console.log('Text changed by:', event.transaction.origin)
})

// Awareness (see other users)
provider.awareness.on('change', () => {
  const states = provider.awareness.getStates()
  states.forEach((state, clientId) => {
    console.log(`User ${state.user.name} at cursor ${state.cursor}`)
  })
})

// Set own awareness
provider.awareness.setLocalState({
  user: {
    name: cloudillo.name,
    idTag: cloudillo.idTag,
    color: '#' + Math.random().toString(16).slice(2, 8)
  },
  cursor: { line: 5, column: 10 }
})

Authentication

All WebSocket connections require authentication via query parameter:

wss://server.com/ws/bus?token=eyJhbGc...
wss://server.com/ws/rtdb/file_123?token=eyJhbGc...
wss://server.com/ws/crdt/doc_123?token=eyJhbGc...

The client libraries handle this automatically.

Reconnection

All WebSocket connections implement automatic reconnection with exponential backoff:

  • Initial retry: 1 second
  • Max retry delay: 30 seconds
  • Exponential factor: 1.5

Handling reconnection:

provider.on('status', ({ status }) => {
  if (status === 'connected') {
    console.log('Connected to server')
  } else if (status === 'disconnected') {
    console.log('Disconnected, will retry...')
  }
})

Best Practices

1. Clean Up Connections

// React example
useEffect(() => {
  const bus = cloudillo.openMessageBus()

  bus.subscribe('notifications', handleNotification)

  return () => {
    bus.close() // Clean up on unmount
  }
}, [])

2. Handle Connection State

const [connected, setConnected] = useState(false)

provider.on('status', ({ status }) => {
  setConnected(status === 'connected')
})

// Show offline indicator
{!connected && <div className="offline-banner">Reconnecting...</div>}

3. Batch Updates

// ❌ Don't send updates individually
for (let i = 0; i < 100; i++) {
  yText.insert(i, 'x')
}

// ✅ Batch in a transaction
yDoc.transact(() => {
  for (let i = 0; i < 100; i++) {
    yText.insert(i, 'x')
  }
})

See Also

  • RTDB - Real-time database documentation
  • CRDT - Collaborative editing documentation
  • Authentication - WebSocket authentication