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#

Text
  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      │
                                                                          └──────────────────┘
  1. 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.
  2. The server reads, the browser listens. The page load reads history and current states over REST, on the server. The browser only opens WebSockets.
  3. 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.

Shout's token route, simplified
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…BrokerIn Shout
happened, and latecomers must catch upEvent Brokerchat messages, paint strokes, a game's log
is how things are nowState Brokervotes, scores, items on a board, who's online
is worthless a second later, or mustn't stayWire BrokerArena's reactions and presence
needs an answer, to one person onlyRelay BrokerQuiz 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#