Channels and presence
Channels carry messages between connected clients that never touch your database: typing indicators, cursor positions, game moves. Presence shows which connections are online and what state each one has published.
Both use the same connection as row-change subscriptions, so they need the same client setup: a publishable API key, and the user’s access token when you have one.
Broadcast
Section titled “Broadcast”Get a channel with vaif.channel(name), listen for events, subscribe, then send:
import { createVaifClient } from "@vaifstudio/client";
const vaif = createVaifClient({ baseUrl: "https://api.vaif.studio", projectId: process.env.VAIF_PROJECT_ID, apiKey: process.env.VAIF_PUBLISHABLE_KEY,});
type Typing = { userId: string; typing: boolean };
const room = vaif.channel("room:42");
room.on<Typing>("typing", (payload, meta) => { console.log(`${payload.userId} typing: ${payload.typing}`, meta?.senderId);});
await room.subscribe();
await room.broadcast({ event: "typing", payload: { userId: "user-1", typing: true } });
// Leave the channel and forget it:await vaif.removeChannel(room);Rules for channel names and messages:
- A channel name is letters, digits and
_ - : . @ /, up to 200 characters. Names that start withtable:are reserved for row-change subscriptions. - You must be subscribed to a channel before you broadcast on it.
- The server does not send a broadcast back to the connection that sent it. Pass
includeSelf: truetobroadcast()to also deliver it to your own listeners. - Listen for every event on a channel with
room.on("*", handler);meta.eventtells you which one arrived. - Broadcasts are not stored. A client that is offline when a message is sent never receives it.
Presence
Section titled “Presence”Create a presence channel, track your own state, and listen for joins and leaves:
type Cursor = { name: string; x: number; y: number };
const lobby = vaif.channel<Cursor>("lobby", { type: "presence" });
lobby.presence.onSync((state) => { console.log("online connections:", Object.keys(state).length);});lobby.presence.onJoin((key, current, joined) => { for (const p of joined) console.log(`${p.state.name} joined`);});lobby.presence.onLeave((key, current, left) => { for (const p of left) console.log(`${p.state.name} left`);});
await lobby.subscribe();await lobby.presence.track({ state: { name: "Alice", x: 0, y: 0 } });
// Change part of your state later:await lobby.presence.update({ x: 120, y: 80 });
// Go offline without closing the channel:await lobby.presence.untrack();How presence works:
- Presence is per connection, across the project. Each connection has one presence state, and every presence channel in the project sees the same set of tracked connections. To show who is in one room, include the room in the state you track and filter on it.
- Entries are keyed by connection id. A user with two tabs open appears twice, once per connection; put a user id in the state if you need to count people.
- When a connection closes, its presence is removed and a leave is sent to everyone else.
lobby.presence.state()returns the current state at any time.
When to use which
Section titled “When to use which”| You want to | Use |
|---|---|
| Show a row as soon as it is written | Row changes |
| Send something that should not be stored (typing, cursors) | Broadcast |
| Show who is online | Presence |
@vaifstudio/react wraps all three: useRealtime, useChannel, useBroadcast and usePresence. See Realtime in React.