Back to work

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.

The signaling connection: state diagram. The tables below list the same states and transitions.

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

Transitions of the signaling connection: the state before, the event, and the state after.
FromEventTo
Startopen the room linkJoining
Joiningroom state receivedJoined
Joiningopen fails, retries leftRetrying
Retryingwait endsJoining
Joiningopen fails, no retries leftCould not join
Joiningclose code 4005Room full
Joinedsocket closesReconnecting
Reconnectingopen fails, wait longerReconnecting
Reconnectingroom state receivedJoined
Joinedleave, removed, or not admittedEnded

In the code

  • src/components/whimcall/call-room.tsx
  • src/components/whimcall/call-reconnect.ts
  • src/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.

The media path of a room: state diagram. The tables below list the same states and transitions.

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

Transitions of the media path of a room: the state before, the event, and the state after.
FromEventTo
Startroom createdPeer to peer
Peer to peera third admitted person joinsSFU
SFUpeople leave, the room stays SFUSFU
SFUthe last socket closesPeer to peer

In the code

  • src/server/room-durable-object.ts
  • src/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.

Recovery of a direct call: state diagram. The tables below list the same states and transitions.

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

Transitions of recovery of a direct call: the state before, the event, and the state after.
FromEventTo
StartICE connectsConnected
ConnectedICE disconnectedDisconnected
Disconnectedheals within 4 sConnected
Disconnected4 s passRestarting ICE
ConnectedICE failedRestarting ICE
Restarting ICEICE connectsConnected
Restarting ICE12 s passRebuilding
Rebuildingnew offer answeredConnected

In the code

  • src/components/whimcall/call-room.tsx

What can go wrong

  1. 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.ts
    • src/components/whimcall/call-room.tsx
    • src/server/room-durable-object.ts
  2. 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.ts
    • src/server/functions.ts
    • src/server/room-durable-object.ts
  3. 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
  4. 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.ts
    • src/components/whimcall/call-room.tsx
  5. 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.ts
    • src/server/room-protocol.ts
    • src/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.ts
  • src/components/whimcall/call-room.tsx
  • src/server/functions.ts
  • src/server/room-durable-object.ts
  • src/server/room-protocol.ts
  • src/server/rooms.ts

Next case study

Pico