Skip to main content

Real-time updates

Cortex pushes changes to you over Server-Sent Events. The SDK wraps this in a Firestore-style watch() API: you get the current state immediately (over REST) and then a live stream of changes — with reconnection handled for you. No polling, no manual EventSource, no merge logic.

Watch a live transcript

The most common real-time use case: render a transcript as people speak.

import { CortexClient } from "@4players/odin-cortex";

const client = new CortexClient({
baseUrl: "https://ots.odin.4players.io",
apiKey: process.env.CORTEX_API_KEY,
});

const project = client.project("your-project-id");

// Watch the session itself (status changes, etc.)
const sessionSub = project.sessions.watch(sessionId);
sessionSub.onSnapshot((session) => {
if (!session) {
console.log("[session] deleted or not found");
return;
}
console.log(`[session] ${session.title} — status=${session.status}`);
});

// Watch the transcript: initial messages via REST, new ones streamed live.
const session = await project.sessions.get(sessionId);
const messageSub = session.watchMessages();
messageSub.onSnapshot((messages, changes) => {
for (const change of changes) {
if (change.type === "added") {
console.log(`+ ${change.data.senderName}: ${change.data.content}`);
}
}
if (changes.length === 0) {
console.log(`[transcript] ${messages.length} message(s) loaded`);
}
});

process.on("SIGINT", () => {
sessionSub.unsubscribe();
messageSub.unsubscribe();
process.exit(0);
});

Each snapshot gives you the full current array plus the changes that triggered it, so you can either re-render everything or apply incremental updates.

Watch a collection

Watch every session in a project and react as they’re created, started or stopped:

const sub = project.sessions.watch();

sub.onSnapshot((sessions, changes) => {
console.log(`\n[snapshot] ${sessions.length} session(s)`);
for (const change of changes) {
// change.type is "added" | "modified" | "removed"
console.log(` ${change.type.padEnd(8)} ${change.id} (${change.data.status})`);
}
});

sub.onError((err) => console.error("[error]", err));

// Read the latest value synchronously at any time:
console.log(sub.current);

The same pattern works for project.participants.watch(), project.sanctions.watch() and project.gatherings.watch().

Watch nested collections

Some collections live on a live object you fetch first:

const gathering = await project.gatherings.get(gatheringId);
gathering.watchMembers().onSnapshot((members) => { /* member states */ });
gathering.watchInvitations().onSnapshot((invites) => { /* invitation states */ });

Resilience

  • All watchers in a project share a single SSE connection (closed when the last one unsubscribes).
  • The connection auto-reconnects with backoff, and on every reconnect each subscription re-fetches its snapshot — so a dropped connection never leaves you with stale data.
  • An idle-timeout guards against dead sockets; you can tune it via realtime.idleTimeoutMs on the client (default 45s, 0 to disable).
const client = new CortexClient({
baseUrl,
apiKey,
realtime: { idleTimeoutMs: 45_000 },
});

watch() works with both API-key and JWT auth. Streaming requires a WHATWG fetch (the global fetch on Node 18+ or any modern browser).

Don’t need the SDK?

You can consume the raw SSE stream directly — see Events & Real-time. For durable, replayable delivery, use webhooks instead.