Architecture walkthrough

Building ephemeral encrypted chat with Cloudflare Durable Objects

Babili uses one Cloudflare Worker and one Durable Object per room to coordinate a live encrypted conversation without turning the server into a transcript database.

The shape of the system

The React client creates rooms, derives keys with Web Crypto, encrypts messages and files, and maintains the current transcript in memory. A single Cloudflare Worker serves that app and routes the room API. One Durable Object per room owns membership, presence, one-time invites, expiry, and a live WebSocket relay.

The Durable Object persists room metadata and invite state. It relays encrypted message envelopes, encrypted file chunks, and peer-supplied transcript sync payloads without persisting those payloads as a server-side transcript.

From room creation to encrypted relay

  1. The browser generates a random 256-bit room secret.
  2. The Worker creates room metadata and addresses a Durable Object by room ID.
  3. The secret stays in the URL fragment during normal navigation.
  4. The browser derives an AES-GCM-256 room key with HKDF-SHA-256.
  5. Participants join through creator-issued, single-use invite links.
  6. A single WebSocket carries encrypted chat, file, presence, and sync events.
  7. The room expires after its policy or is destroyed by the creator.

Newly connected participants request transcript sync from clients that are already in the room. If no connected client still has an item, the server cannot reconstruct it. That loss is part of the disposable model rather than a durability bug.

Why an encrypted relay instead of WebRTC

Direct WebRTC is attractive, but ICE negotiation can reveal participant IP addresses to other room members, and reliable connectivity often requires a TURN relay anyway. Babili deliberately sends ciphertext through the Durable Object so participants never connect directly and the flow works on restrictive networks.

This is a tradeoff, not magic. Cloudflare can still observe IP addresses, connection timing, payload sizes, and presence. The relay cannot read message or file plaintext, but Babili does not claim to hide metadata from its infrastructure.

What is encrypted—and what is not

  • Messages use AES-GCM with a fresh random nonce per message.
  • Files are split into 64 KiB chunks and encrypted chunk by chunk.
  • The browser holds the room key and current transcript in memory.
  • The server stores room policy, status, creator capability, and invite state.
  • Protocol v3 signs peer events with admitted ephemeral session keys. Those keys do not establish real-world identity.

The current design does not solve device compromise, screenshots, malicious recipients, traffic analysis, denial of service, or strong anonymous routing. It has not had an independent security audit.

A deploy button needs a root contract

Babili is an npm-workspaces repository. Its original Wrangler configuration lived under workers/api, which worked for local and production commands but was invisible to the Deploy to Cloudflare setup flow. The button existed, yet a fresh visitor reached No Wrangler configuration detected and Cloudflare fell back to automatic project configuration.

The repository now has a root wrangler.jsonc, root build and deploy scripts, and a check that fails when the root and workspace runtime resources drift. The public template deliberately omits Babili's optional Analytics Engine dataset because Cloudflare does not list Analytics Engine among the resources its deploy button automatically provisions. Independent instances work without that measurement binding.

The useful test is the public journey, not the presence of a badge: open the repository as a visitor, follow the button, select an account, and confirm that Cloudflare detects npm run build, npm run deploy, and the repository root. Stop if the fallback warning appears. Cloudflare's current resource rules are documented in its Deploy to Cloudflare guide, and the Babili fix is public in pull request 89.

Fork it, inspect it, or run your own

The complete TypeScript project is AGPL-3.0 licensed. The repository includes the architecture, threat model, deployment instructions, contributor guide, and a one-click Cloudflare deploy path.

Read the source and deployment guide.