# 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

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

## How It Works

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

```typescript
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**:

```javascript
// 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.
