Case study · Real-time communication · 2026
Whimcall
as state machines.
Video calls that start from a link. The call survives network drops and deploys, finds a media path through strict networks, and moves to a media server when a third person joins.
The problem
A guest opens a link and is in a call. No account and no app. After that, much can go wrong: the Wi-Fi changes, a deploy restarts the server, a firewall blocks direct media, or a third person joins a call that was made for two.
The call must recover by itself from each of these, without a second seat for the same person and without a stuck screen.
Ali's role
Ali made Whimcall: the product concept, the brand and interface design, the full-stack development, the realtime media architecture, and the deployment.
The state machines
Each diagram comes from the same data as the tables under it. The tables give the same facts as text.
Machine 01 · 7 states · 10 transitions
The signaling connection
Each browser keeps one WebSocket to the Durable Object of its room. The socket carries offers, answers, ICE candidates, presence, and media state. Close codes tell the browser if it must reconnect or stop.
States
- Joining
- The browser opens the socket with its session ID and name.
- Retrying
- The first join failed. The browser waits 300 ms (then 900 ms) and tries again.
- Joined
- The room sent its state. The reconnect counter goes back to zero.
- Reconnecting
- The socket closed with no leave code. The browser waits 1 s, 2 s, 4 s, then 8 s at most, times a random 0.8 to 1.0.
- Room full
- The room has no free seat (close code 4005). The browser shows the limit and does not retry.
- Could not join
- The first join failed three times. The page shows the error.
- Ended
- The person left (4000), the moderator removed them (4003), or the lobby did not admit them (4004). No reconnect.
Transitions
| From | Event | To |
|---|---|---|
| Start | open the room link | Joining |
| Joining | room state received | Joined |
| Joining | open fails, retries left | Retrying |
| Retrying | wait ends | Joining |
| Joining | open fails, no retries left | Could not join |
| Joining | close code 4005 | Room full |
| Joined | socket closes | Reconnecting |
| Reconnecting | open fails, wait longer | Reconnecting |
| Reconnecting | room state received | Joined |
| Joined | leave, removed, or not admitted | Ended |
In the code
src/components/whimcall/call-room.tsxsrc/components/whimcall/call-reconnect.tssrc/server/room-protocol.ts
Machine 02 · 2 states · 4 transitions
The media path of a room
The room Durable Object stores which media path the room uses. Two people talk over a direct peer-to-peer connection. A third person moves the whole room to Cloudflare Realtime SFU.
States
- Peer to peer
- One or two people. Media goes directly between the browsers, through TURN only when ICE cannot find a direct path. Chat and captions use the WebRTC data channel.
- SFU
- Three or more people. Each browser publishes its tracks to Cloudflare Realtime SFU. Chat, captions, and presence go over the room WebSocket.
Transitions
| From | Event | To |
|---|---|---|
| Start | room created | Peer to peer |
| Peer to peer | a third admitted person joins | SFU |
| SFU | people leave, the room stays SFU | SFU |
| SFU | the last socket closes | Peer to peer |
In the code
src/server/room-durable-object.tssrc/components/whimcall/call-room.tsx
Machine 03 · 4 states · 8 transitions
Recovery of a direct call
In a two-person call, the browser watches the state of its peer connection. It gives a short drop time to heal, then restarts ICE, and then builds a new connection.
States
- Connected
- Media flows. All recovery timers are cleared.
- Disconnected
- ICE reports a drop. Many drops heal by themselves, so the browser waits 4 s first.
- Restarting ICE
- The browser reconnects signaling and loads new ICE servers. The host sends an ICE-restart offer; the guest asks the host for one.
- Rebuilding
- Still no media after 12 s. The browser closes the peer connection and makes a new one.
Transitions
| From | Event | To |
|---|---|---|
| Start | ICE connects | Connected |
| Connected | ICE disconnected | Disconnected |
| Disconnected | heals within 4 s | Connected |
| Disconnected | 4 s pass | Restarting ICE |
| Connected | ICE failed | Restarting ICE |
| Restarting ICE | ICE connects | Connected |
| Restarting ICE | 12 s pass | Rebuilding |
| Rebuilding | new offer answered | Connected |
In the code
src/components/whimcall/call-room.tsx
What can go wrong
Failure 01
Signaling drops during a call
The Wi-Fi changes or a deploy restarts the Worker. Every browser in the room loses its socket at the same time.
How the design handles it
- The browser reconnects with exponential backoff from 1 s to 8 s. Random jitter spreads the reconnects of many browsers over time.
- Rooms use the WebSocket Hibernation API of Durable Objects. Each socket stores its session (role, name, mute state, tracks) in its attachment, so an idle room can leave memory and keep its sockets.
- The browser keeps its signaling session ID in session storage. On reconnect, the room finds the old socket for that session, keeps the role, admission, and media state, and closes the old socket with code 4001.
- In SFU mode, the browser sends its media state, caption state, and track metadata again as soon as the new socket opens.
src/components/whimcall/call-reconnect.tssrc/components/whimcall/call-room.tsxsrc/server/room-durable-object.ts
Failure 02
A firewall blocks direct media
Restrictive NATs and company firewalls stop a direct peer-to-peer path, and the call shows no video.
How the design handles it
- The browser tries STUN first and uses TURN only when ICE needs it.
- The Worker mints time-limited Cloudflare Realtime TURN credentials.
- If that fails, the Worker falls back to static TURN settings.
- TURN credentials are only for an active signaling session, with a limit of 6 for each session, plus edge rate limits. Nobody can get credentials without an open socket in the room.
src/server/rooms.tssrc/server/functions.tssrc/server/room-durable-object.ts
Failure 03
A direct call loses its media path
The network changes under a two-person call. The peer connection goes to “disconnected” or “failed”.
How the design handles it
- A short drop gets 4 s to heal. After that, or at once on “failed”, the browser restarts ICE with fresh ICE servers.
- If media does not come back within 12 s, the browser closes the peer connection and builds a new one. Only one recovery runs at a time.
src/components/whimcall/call-room.tsx
Failure 04
A two-person call becomes a group
A full mesh of direct connections does not scale. Each new person adds an upload for each other person.
How the design handles it
- When a third admitted person joins, the room stores the SFU topology and tells every browser. Each browser closes its peer connection and publishes to Cloudflare Realtime SFU.
- The room stays in SFU mode when it drops back to two people, so the call does not switch paths again. It goes back to peer-to-peer only when the last socket closes.
- Camera video publishes three simulcast layers (full, half, and quarter size). The staged speaker gets the full layer; grid tiles get the quarter layer. A browser pulls video only for the tiles on screen, and audio only from people who are not muted.
src/server/room-durable-object.tssrc/components/whimcall/call-room.tsx
Failure 05
Two devices claim one seat
A person reconnects, or opens the link on a second device, while the room is full.
How the design handles it
- Open sockets are the room membership. A session that already has a socket never counts as a new seat.
- A full room answers with code 4005. The browser shows the limit and does not retry in a loop.
- Free rooms hold 2 people. Rooms from Pro hosts hold up to 100.
src/server/room-durable-object.tssrc/server/room-protocol.tssrc/components/whimcall/call-room.tsx
Numbers
Only numbers from the code or from a count. Each one says where it comes from.
- signaling backoff, with 0.8–1.0 jitter
- 1 → 8 s
- getSignalReconnectDelay in src/components/whimcall/call-reconnect.ts.
- ICE drop grace, then rebuild
- 4 s · 12 s
- ICE_DISCONNECTED_GRACE_MS and ICE_RECOVERY_TIMEOUT_MS in src/components/whimcall/call-room.tsx.
- TURN credentials for each session
- 6
- MAX_TURN_CREDENTIALS_PER_SESSION in src/server/room-durable-object.ts.
- people in a Pro room
- 100
- MAX_PEERS_PER_ROOM in src/server/room-protocol.ts.
Sources
Whimcall is a private repository. Paths are at commit acb7798. Every claim on this page comes from these files.
src/components/whimcall/call-reconnect.tssrc/components/whimcall/call-room.tsxsrc/server/functions.tssrc/server/room-durable-object.tssrc/server/room-protocol.tssrc/server/rooms.ts
Next case study
Pico