Volcano DocumentationExamplesRealtime chat

Volcano Realtime Chat

Minimal real-time chat with presence tracking using the Volcano SDK. Shows how easy it is to build a full-featured chat app with online user lists.

Volcano Realtime Chat

Minimal real-time chat with presence tracking using the Volcano SDK. Shows how easy it is to build a full-featured chat app with online user lists.

Features

  • ✅ Anonymous authentication - No account required, just pick a name
  • ✅ Real-time messaging - Chat updates instantly via broadcast channels
  • ✅ Presence tracking - See who's online with their display names
  • ✅ User metadata - Display names from signup appear in presence
  • ✅ Symmetric visibility - Late joiners see existing users
  • ✅ In-browser config - Set up credentials directly in the UI

Quick Start

yarn install
yarn dev
# Open http://localhost:3005

How It Works

All SDK calls are in app/page.tsx. Condensed, the flow is:

import { VolcanoAuth } from '@volcano.dev/sdk';
import { VolcanoRealtime } from '@volcano.dev/sdk/realtime';

// 1. Sign in anonymously with display_name
const volcano = new VolcanoAuth({ apiUrl, anonKey });
const { session, error } = await volcano.auth.signInAnonymously({
  display_name: 'Alice', // This appears in presence events!
});
if (error || !session) throw error ?? new Error('No session returned');

// 2. Connect to realtime
const realtime = new VolcanoRealtime({
  apiUrl,
  anonKey,
  accessToken: session.access_token,
});
await realtime.connect();

// 3. Subscribe to chat (broadcast channel)
const chat = realtime.channel('chat-general');
chat.on('message', (msg) => {
  console.log('New message:', msg); // payloads are untyped; app/page.tsx checks their shape
});
await chat.subscribe();

// 4. Subscribe to presence (see who's online)
const presence = realtime.channel('chat-general', { type: 'presence' });

presence.onPresenceSync((state) => {
  const users = Object.values(state).map((info) => {
    // connInfo is untyped; app/page.tsx checks each field's type
    const metadata = info.connInfo?.user_metadata as { display_name?: string } | undefined;
    return { id: info.client, name: metadata?.display_name || 'Anonymous' };
  });
  console.log('Online users:', users);
});

await presence.subscribe();
await presence.track(); // Automatic

// 5. Send messages
await chat.send({
  username: 'Alice',
  text: 'Hello everyone!',
  timestamp: new Date().toISOString(),
});

Key Concepts

User Metadata in Presence

Display names come from anonymous sign-in metadata:

// Sign in with display_name
await volcano.auth.signInAnonymously({ display_name: 'Alice' });

// It appears in presence events automatically:
presence.on('join', (info) => {
  const name = info.connInfo?.user_metadata?.display_name;
  console.log(`${name} joined`); // "Alice joined"
});

Event Names

Use the correct event names:

  • ✅ on('presence_sync', callback) - Presence state updated
  • ✅ onPresenceSync(callback) - Helper method (recommended)
  • ❌ on('sync', callback) - Wrong! This won't fire

Prerequisites

  1. Volcano server running with realtime enabled
  2. Project created with:
    • Anon key generated
    • Anonymous signups enabled in auth settings
    • Realtime enabled in project settings

Project Structure

realtime-chat/
├── app/
│   ├── page.tsx          # SDK calls and screen state
│   ├── ConfigScreen.tsx  # API URL and anon key form
│   ├── JoinScreen.tsx    # Name entry
│   ├── ChatRoom.tsx      # Messages and message form
│   ├── OnlineUsers.tsx   # Online user list
│   ├── types.ts          # Shared types
│   ├── layout.tsx        # Root layout
│   └── globals.css       # Styles
├── package.json
└── README.md

All SDK calls are in app/page.tsx. The other components get data and callbacks as props and never call the SDK.

On this page