chat

command
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 13, 2026 License: MIT Imports: 9 Imported by: 0

README

chat

A runnable demonstration of topic broadcast in the gateway package: a room-based WebSocket chat where every message a client sends is fanned out to every other client in the same room via Registry.Broadcast, with WithExclude keeping the sender from getting an echo of its own line back.

What this sample shows

  • WithWSAuth resolving identity once, at the handshake: the room and user query parameters become ConnInfo.Topics and ConnInfo.Group respectively, so the rest of the handler never touches *http.Request again.
  • Registry.Broadcast(ctx, topic, payload, gateway.WithExclude(senderID)) - the "send to everyone in this room except me" pattern.
  • DeliveryResult as the thing a caller actually inspects after a fan-out, not just an error: the sample logs delivered/dropped/remote for every chat broadcast.
  • The topic vs. group distinction (see below), because a chat room is the textbook case where conflating the two breaks the feature.
  • The P0-2 text-frame fix: the bundled HTML page asserts typeof event.data === 'string' on every inbound WebSocket message and would visibly report an error if the server ever sent a binary frame instead.

What this sample does not show

  • Cross-instance delivery. Like examples/echo, this runs a single, non-clustered actor system, so every broadcast in this room is delivered to every member in the same process - DeliveryResult.Remote will read 0 for the whole session. See examples/echo/README.md for how to turn a sample like this into a real multi-node one (swap actor.NewActorSystem for a clustered one and run two copies against the same discovery backend); the broadcast code here does not change at all to get that, because Registry.Broadcast already fans out to the topic bridge regardless of cluster size.
  • Presence/offline handling. DeliveryResult.None() is the signal a real chat app would use to fall back to a push notification when a room has nobody connected anywhere in the cluster, but that needs a Presence backend (see presence/redis), which is out of scope for a single-process sample.
  • Message history / reconnect replay. That is what SSE's SSEHistory (Last-Event-ID) solves; this sample is plain WebSocket with no replay on reconnect.

Topic vs. group

The sample deliberately keeps these separate to make the distinction concrete:

  • Room = topic. room=lobby becomes the pub/sub topic room:lobby. Every connection that joined that topic - regardless of who it belongs to - receives a Broadcast to it. This is "who is listening to this channel right now".
  • User = group. user=alice becomes ConnInfo.Group = "user:alice". The connection ID itself is left empty in authConn, so the handler mints a fresh UUID per socket: opening two browser tabs as alice produces two different connection IDs sharing one group. A group is "who is this, across however many devices/tabs they currently have open" - it is what Registry.SendToGroup and Registry.IsOnline operate on, and this sample does not call either, precisely because a chat room broadcast is a topic operation, not a group one.

Mixing these up is the usual bug: broadcasting to a group would only reach one user's own devices, never the room; sending to a topic named after a single user would work by accident until a second device joined and either doubled delivery or silently split it.

Run it

go run ./examples/chat

This starts an HTTP server on http://127.0.0.1:8082 with two endpoints:

  • GET / - the chat UI (a single embedded HTML page, no build step, no external assets).
  • GET /ws?room=<room>&user=<user> - the WebSocket upgrade. Both query parameters are required; a missing one is rejected during the handshake.

Try it

Open http://127.0.0.1:8082/ in two browser tabs (or two different browsers). In both, type the same room name (the form defaults to lobby) but a different user name, e.g. alice in one tab and bob in the other, and click Join in each.

What you should see:

  • Each tab logs its own join as a system line ("alice" joined the room), then the other tab's join once it connects.
  • Typing a message in alice's tab and pressing Enter shows up in bob's tab as alice: <message> - and does not reappear in alice's own log. That is WithExclude(info.ID) at work.
  • Closing a tab (or clicking Leave) produces a "<user> left the room" line in the other tab.
  • The server's stdout logs one line per broadcast, e.g. chat: room="lobby" user="alice" delivered=1 dropped=0 remote=0 - with two tabs open, delivered is 1 (bob's connection only; alice's own connection was excluded) and remote stays 0 because this sample is single-node.

To see the P0-2 regression check fire, you would need a server that regressed back to sending binary frames; against this build there is nothing to see except its absence, which is the point - the sample's onmessage handler checks typeof event.data === 'string' before doing anything else and would print ERROR: received a non-text frame (...) into the log pane instead of silently rendering a [object Blob].

Verification

The flow above was exercised end-to-end with a coder/websocket client (two connections, same room, different users) during development: join notices arrive on both sides, a chat message from alice is delivered to bob only (delivered=1 dropped=0 remote=0, confirming the exclude), and alice's socket receives nothing further for that message. That check was a temporary test file, not part of this sample, and was removed after confirming the behavior - go run ./examples/chat plus two browser tabs is the intended way to see it.

Documentation

Overview

Command chat is a runnable demonstration of topic broadcast in the gateway package: a room-based WebSocket chat where every message a client sends is fanned out to every other client in the same room via Registry.Broadcast, and WithExclude keeps the sender from getting an echo of its own line back. See README.md for what this sample does and does not show, and for the distinction it draws between a "room" (a pub/sub topic, shared by everyone listening) and a "user" (a ConnInfo.Group, shared by one identity's own devices/tabs).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL