Shout
How Shout is built
Shout, our own platform of live apps, runs on Thuum like any customer's app. Its architecture in one picture, and the five patterns every app shares.
Shout is a small platform of live apps for streamers, teachers and speakers: audience Q&A, live quizzes, polls, scoreboards, boards, games and chat. We build it on Thuum as an ordinary customer, with no back doors: everything on this page uses the same API and the same client library you do.
Architecture#
Browser (each app's page) Shout's server (SvelteKit) Thuum.dev
┌───────────────────────┐ 1. ask ┌──────────────────────────┐ App Key ┌──────────────────┐
│ │ ───────▶ │ token route │ ───────▶ │ POST /token │
│ │ ◀─────── │ scope: this app, this │ ◀─────── │ (scoped token) │
│ │ token │ instance, this room │ │ │
│ │ │ │ │ │
│ │ ◀─────── │ page load: the snapshot │ ══REST══▶│ history, states │
│ │ 2. data └──────────────────────────┘ │ │
│ │ ═══════════ 3. WebSockets, one per broker ════▶│ Event, State, │
└───────────────────────┘ │ Wire, Relay │
└──────────────────┘- The App Key never leaves Shout's server. A page asks Shout's token route for a token, which Shout exchanges with a scope naming only the topics that page needs, for a few seconds. One key serves every app; scopes keep them apart.
- The server reads, the browser listens. The page load reads history and current states over REST, on the server. The browser only opens WebSockets.
- One WebSocket per broker, each with its own token: subscribe, then publish, set or serve.
There is no Shout database for live data: polls, boards, messages and scores live in Thuum. Shout's
own database holds accounts, spaces, the links their owners share and the titles listed on a
space's home. Each space is a Domain of its own, so one creator's data is isolated from every
other's; the free area at /apps is one more Domain.
Five patterns#
One Main Topic per app, one sub-topic per instance#
Each app owns a Main Topic (poll, board,
arena) and each instance is a sub-topic: poll:{id}. Retention is set once per Main Topic; a
token scoped to poll:{id} reaches that poll and nothing else.
const { token } = await thuum.exchangeToken('shout', process.env.THUUM_APP_KEY!, {
// P S R on this poll only: publish, subscribe, read.
scope: { state: [{ topic: `poll:${id}`, flags: 11 }] }
});Pick the broker by what the data is#
| The data… | Broker | In Shout |
|---|---|---|
| happened, and latecomers must catch up | Event Broker | chat messages, paint strokes, a game's log |
| is how things are now | State Broker | votes, scores, items on a board, who's online |
| is worthless a second later, or mustn't stay | Wire Broker | Arena's reactions and presence |
| needs an answer, to one person only | Relay Broker | Quiz Desk's answers, Arena's host election |
Most apps use one or two brokers; Shout Arena uses four.
Subscribe, then catch up#
A page subscribes first, then fetches what happened before, and merges the two by event id or
state version. Nothing falls in the gap, and a reconnect does the same.
Build reliable clients shows the loop.
Trust nothing you read#
Anyone holding a token for a topic can write anything on it. Every Shout app checks each event and state against its expected shape and ignores the rest. Render names and text as text, never HTML.
Let scopes draw the rooms#
What a person may see is decided by their token, not by what the page hides. In Shout Arena a participant's token may publish to the host's inbox but not read it, so nobody sees anyone else's message before the host shows it, even with DevTools open.
Next#
- The apps, broker by broker: what each Shout app keeps where.
- Build the same designs yourself: a chat with presence and collaborative editing.