🔦

Firefly

GoWebRTCPionKafka

Firefly is my generic WebRTC signaling and session service, built on Pion, so any game on the platform — chess in Rook, labyrinth crawlers from Goblin Shark, whatever comes next — can add real-time multiplayer without reinventing peer connection setup each time.

A player creates or joins a session, Firefly hands back the roster and ICE configuration, then relays the SDP and ICE handshake between peers until their browsers are talking directly. It owns Session and Participant as real entities but not game rules or gameplay traffic — once two peers connect, data flows peer-to-peer over a WebRTC DataChannel and Firefly is out of the path. A WebSocket bridge lets browser clients, which can’t use Connect bidi streams, join the same relay as native clients.

Firefly is my generic WebRTC signaling and session service — the piece two players’ browsers talk to before they can talk to each other. Built on Pion, it lets any game on the platform (chess in Rook, labyrinth crawlers from Goblin Shark, and whatever comes next) add real-time multiplayer without reinventing peer connection setup each time.

Why I Built It

Every game that wants two players in the same match needs the same unglamorous machinery: a way to create or join a session, a roster of who’s present, and a relay for the SDP offer/answer and ICE candidate exchange that WebRTC deliberately leaves for you to implement. Writing that once per game is wasteful, so Firefly does it once for all of them. A player joins a session, Firefly returns the roster and an ICE server list, and then it relays the handshake between peers until their browsers have a direct connection.

What It Is — and Isn’t

  • It is a signaling relay and a session/roster store. It owns Session (a multiplayer game instance’s connection topology — who’s present, is the game waiting, active, or ended) and Participant (one connected client) as real, persisted entities.
  • It does not own game rules or game state. Chess moves stay in Rook; labyrinth state stays in Goblin Shark’s client-side WASM engine. A Session.game_uuid is just a soft pointer to whichever compiled game elsewhere on the platform this session is playing.
  • It does not carry gameplay traffic. Once the handshake completes, game data flows peer-to-peer over a WebRTC DataChannel directly between browsers (or through a TURN relay if a direct path isn’t possible) — never back through Firefly’s own RPCs.

Tech Stack

  • Backend: Go, ConnectRPC (unary CRUD) plus a hand-written bidirectional Signal stream
  • Browser bridge: a plain WebSocket endpoint that feeds the same in-memory relay hub, since browsers can’t open Connect bidi streams from the client side
  • Messaging: Kafka for Session/Participant lifecycle events, matching every other platform service
  • Scaffolding: generated by Beaver from schemas/firefly/v1/*.proto

For a deeper look at how the pieces fit together, see Architecture.

Documentation