Self-hosting the room
A built deck is a static site: copy dist/ to any host and it works, offline features and all. The
live room needs a server. You can use deck.kadal.cc, or run your own.
What a room server does
Section titled “What a room server does”The deck’s live client talks to live.api (/api by default, same origin) and joins the room named
by live.room (the deck’s slug when unset). A room server answers:
| Path | What |
|---|---|
GET {api}/rooms/{room}/ws |
the room, as a WebSocket. ?role=presenter asks for the presenter role; ?t= carries a Turnstile token when the room requires one |
GET {api}/rooms/{room} |
a read-only snapshot: position and presence |
GET {api}/decks/{room}/presenter |
{ required, ok }: does the presenter view need a passcode, and is this window signed in |
POST {api}/decks/{room}/presenter/login · /logout |
{ passcode } sets or clears the presenter cookie |
POST {api}/decks/{room}/beacon |
{ viewer, dwell, enters } from each viewer, for time-per-slide analytics |
GET {api}/decks/{room}/stats |
the stats page’s data (presenter only) |
POST {api}/decks/{room}/share |
{ h, v, click?, label? } → a short link to a slide |
The messages on the WebSocket are typed in the engine — import them to build a server that can’t drift from the client:
import type { ClientMessage, ServerMessage, RoomState } from "@kadal/deck/protocol";A client says hello with its role and a viewer ID; the server answers welcome with the full
RoomState, then sends state patches and the transient events (nav, pointer, react, draw,
term, confetti). Viewers’ messages that only a presenter may send — nav, poll:open, draw
for everyone, session — must be dropped, and every viewer’s traffic throttled.
The reference host
Section titled “The reference host”deck.kadal.cc runs on Cloudflare:
- a Worker (Hono) routes the endpoints above and serves the decks;
- one Durable Object per room, SQLite-backed, with WebSocket hibernation so an idle room costs nothing between messages; the room’s state is written to the object’s storage, so it survives eviction and a reload;
- D1 keeps what outlives a room: passcode hashes, sessions, questions, poll results, time per slide, share links;
- R2 holds the published files and the export archive;
- Workers rate limiting on passcode attempts and writes.
Everything runs on Cloudflare’s free plan for a personal deck host.
What the AGPL asks of you
Section titled “What the AGPL asks of you”The engine is licensed under the AGPL-3.0-or-later. In practice:
- Presenting with it, publishing decks built with it, and running an unmodified engine trigger nothing.
- If you run a modified engine as a network service, the people using that service must be able to get your modified source (AGPL §13).
- A room server you write yourself against the protocol types is your own program.
If those terms don’t suit your product, a commercial licence removes them.