Show who is online in a room, document, or shared resource.
Build Presence
Use presence to receive membership updates for a presence:* channel.
Model The Channel
Use a presence:* channel for the shared resource:
const channelName = `presence:document:${document.id}`;The authenticated userId in the channel token becomes the member ID exposed by presence updates.
Sign A Presence Token
export async function POST(request: Request) {
const user = await requireUser(request);
const { channel } = await request.json();
const documentId = channel.replace("presence:document:", "");
if (!(await canViewDocument(user.id, documentId))) {
return Response.json({ error: "Forbidden" }, { status: 403 });
}
const session = edge.auth.signIn({
channel,
expiresInSeconds: 60,
permissions: ["subscribe"],
userId: user.id,
});
return Response.json({ token: session.token });
}Presence member data contains identifiers only. Load names, avatars, and roles from your own application data.
Subscribe To Presence Updates
const channel = edge.channel(`presence:document:${documentId}`);
channel.onRawEvent((event) => {
if (event.type === "channel.snapshot" || event.type === "presence.updated") {
setOnlineMemberIds(event.presence?.members.map((member) => member.id) ?? []);
}
});channel.snapshot can include the initial presence state. presence.updated carries later membership changes.
Combine Presence With Activity
Presence tells you who is connected. Publish separate events for what people are doing:
await channel.publish("cursor.moved", {
x,
y,
});Grant browser publish permissions only for ephemeral events such as cursors, selection, typing, and viewing state.
Query Presence From The Backend
Your backend can query current occupancy:
const state = await edge.channels.presence(`presence:document:${documentId}`);
console.log(state.count);
console.log(state.members);Generate This With AI
Use this prompt to generate a complete TanStack Start app that demonstrates the flow end to end.
Build a complete TanStack Start app that demonstrates live presence with Seuraa Edge.
Use @seuraa/edge in the browser and @seuraa/edge-node only in trusted backend code.
Implement:
- A minimal document-room UI with a signed-in demo user, an online-members list, and a shared document panel.
- A presence channel named `presence:document:${documentId}`.
- A TanStack Start API route at /api/edge/token.
- The endpoint must use a small mock requireUser() helper, accept { channel }, extract documentId from `presence:document:${documentId}`, and check that the user can view that document.
- The endpoint must sign a short-lived channel token locally with edge.auth.signIn({ channel, expiresInSeconds: 60, permissions: ["subscribe"], userId: user.id }).
- Do not call Edge to mint channel tokens. The backend SDK signs locally with SEURAA_APP_SECRET.
- A browser component that creates edge.channel(`presence:document:${documentId}`).
- Use channel.onRawEvent(...) to handle both "channel.snapshot" and "presence.updated".
- Store online member IDs from event.presence.members.
- Load display names and avatars from the app's own user data, not from Seuraa presence payloads.
- Call channel.unsubscribe() when the document view unmounts.
- A TanStack Start API route at /api/documents/demo/presence that uses edge.channels.presence(`presence:document:${documentId}`) to read current presence state.
- A button in the UI that calls /api/documents/demo/presence and displays the returned count and members.
- Loading, connected, and error states in the UI.
- A .env.example file and short README instructions for running the app.
Use these environment variables:
- SEURAA_APP_KEY
- SEURAA_APP_SECRET
Do not expose SEURAA_APP_SECRET to browser code.