CloudSignal Docs
Use Cases

Presence

Track online status and typing indicators using MQTT retained messages and will messages.

Presence tracking lets your application show who is online, when they were last active, and whether they are currently typing. MQTT's retained messages and will messages make this reliable without any server-side polling or heartbeat infrastructure.

Architecture

Each user publishes a retained message to their presence topic on connect and sets a will message that the broker publishes automatically on disconnect.

Client connects:
  → Publishes retained "online" to presence/{user_id}
  → Sets will message "offline" on presence/{user_id}

Client disconnects (graceful or unexpected):
  → Broker publishes will message "offline" to presence/{user_id}

Other clients:
  → Subscribe to presence/+ to receive all presence updates
  → On subscribe, receive last retained status for each user

Topic Design

TopicPurposeRetained
presence/{user_id}Online/offline statusYes
presence/{user_id}/typingTyping indicatorNo
presence/{user_id}/activityLast active timestampYes

The main presence topic uses retained messages so that new subscribers immediately receive the current status of all users - no need to wait for the next status change.

Typing indicators are not retained because they are transient. A typing message is only relevant to clients connected at that moment.

How It Works

Online/Offline Tracking

  1. Client connects with a will message configured for its presence topic
  2. Client publishes a retained online payload to presence/{user_id}
  3. Other clients subscribing to presence/+ receive the retained message immediately
  4. When the client disconnects - whether gracefully or due to network loss - the broker publishes the will message with an offline payload

The will message is the key mechanism. Even if the client crashes or loses network without a clean disconnect, the broker detects the broken connection and publishes the will message automatically.

Will messages are published by the broker, not the client. This means offline detection works even when the client cannot send a goodbye message - the broker handles it.

Typing Indicators

Typing indicators follow a simpler pattern. The client publishes a short-lived message when the user starts typing and stops publishing when they stop.

Implementation

Connect with Will Message

import { connect } from '@cloudsignal/mqtt-client'

const userId = 'user-alice'

const client = await connect({
  host: 'wss://connect.cloudsignal.app:18885/',
  username: 'alice',
  password: 'your-token',
  will: {
    topic: `presence/${userId}`,
    payload: JSON.stringify({
      status: 'offline',
      timestamp: Date.now()
    }),
    qos: 1,
    retain: true
  }
})

// Publish online status (retained)
client.publish(`presence/${userId}`, JSON.stringify({
  status: 'online',
  timestamp: Date.now()
}), { qos: 1, retain: true })

Subscribe to Presence Updates

// Subscribe to all user presence topics
await client.subscribe('presence/+', { qos: 1 })

client.on('message', (topic, payload) => {
  const segments = topic.split('/')

  // Presence status update
  if (segments.length === 2) {
    const userId = segments[1]
    const data = JSON.parse(payload.toString())
    updateUserStatus(userId, data.status, data.timestamp)
  }

  // Typing indicator
  if (segments.length === 3 && segments[2] === 'typing') {
    const userId = segments[1]
    const data = JSON.parse(payload.toString())
    updateTypingIndicator(userId, data.isTyping)
  }
})

Send Typing Indicators

let typingTimeout = null

function onUserTyping() {
  client.publish(`presence/${userId}/typing`, JSON.stringify({
    isTyping: true
  }))

  // Clear previous timeout
  clearTimeout(typingTimeout)

  // Auto-clear typing after 3 seconds of inactivity
  typingTimeout = setTimeout(() => {
    client.publish(`presence/${userId}/typing`, JSON.stringify({
      isTyping: false
    }))
  }, 3000)
}

Query Presence via REST API

You can also query current presence state from your server using the CloudSignal REST API:

curl -X GET "https://api.cloudsignal.com/v2/topics/presence" \
  -H "Authorization: Bearer sk_your_secret_key" \
  -H "Content-Type: application/json"

This returns the last retained message for each presence topic, giving you a snapshot of all users' current status.

Graceful Disconnect

For a clean shutdown, publish the offline status explicitly before disconnecting:

async function disconnect() {
  await client.publish(`presence/${userId}`, JSON.stringify({
    status: 'offline',
    timestamp: Date.now()
  }), { qos: 1, retain: true })

  await client.end()
}

This ensures the status updates immediately rather than waiting for the broker's keepalive timeout to detect the disconnect.

Next Steps

On this page