Skip to content

Commit 44bb1df

Browse files
sleroqSean-Der
authored andcommitted
Add chat connection documentation
1 parent 4aa4e21 commit 44bb1df

File tree

2 files changed

+120
-2
lines changed

2 files changed

+120
-2
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -319,8 +319,10 @@ These values are parsed by the Go backend and applied to WHIP/WHEP `PeerConnecti
319319
| `CHAT_DEFAULT_TTL` | How long idle chat sessions stay alive before they expire. |
320320
| `CHAT_CLEANUP_INTERVAL` | How often expired chat sessions are cleaned up. |
321321

322-
Broadcast Box also attaches a WebRTC data channel (`bb-chat-v1`) to WHIP/WHEP peer connections for simple
323-
per-stream chat state, although the bundled frontend does not currently expose a chat UI.
322+
Broadcast Box attaches a WebRTC data channel (`bb-chat-v1`) to WHIP/WHEP peer connections for simple per-stream
323+
chat state. The bundled frontend does not currently expose a chat UI, but you can connect from your own client.
324+
325+
See [CONNECTING.md](internal/chat/CONNECTING.md) for the message contract and a minimal standalone client example.
324326

325327
## CLI Flags
326328

internal/chat/CONNECTING.md

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Chat Connection Quick Reference
2+
3+
Use the WebRTC data channel label `bb-chat-v1` to connect to per-stream chat.
4+
5+
## What the server expects
6+
7+
- Data channel label: `bb-chat-v1`
8+
- Outbound client message type: `chat.send`
9+
- `text` length: 1-2000 chars
10+
- `displayName` length: 1-80 chars
11+
12+
Client -> server payload:
13+
14+
```json
15+
{
16+
"type": "chat.send",
17+
"clientMsgId": "uuid-or-any-unique-id",
18+
"text": "hello",
19+
"displayName": "alice"
20+
}
21+
```
22+
23+
Server -> client message types:
24+
25+
- `chat.connected`
26+
- `chat.history` (contains `{ type: "message", message: ... }[]`)
27+
- `chat.message` (single live message)
28+
- `chat.ack` (echoes `clientMsgId`)
29+
- `chat.error` (may include `clientMsgId`)
30+
31+
Message shape:
32+
33+
```json
34+
{
35+
"id": "message-id",
36+
"ts": 1730000000,
37+
"text": "hello",
38+
"displayName": "alice"
39+
}
40+
```
41+
42+
## Simple client example
43+
44+
```ts
45+
const CHAT_LABEL = "bb-chat-v1";
46+
47+
type Outbound =
48+
| { type: "chat.connected" }
49+
| { type: "chat.history"; events: Array<{ type: "message"; message: ChatMessage }> }
50+
| { type: "chat.message"; eventId: number; message: ChatMessage }
51+
| { type: "chat.ack"; clientMsgId: string }
52+
| { type: "chat.error"; error: string; clientMsgId?: string };
53+
54+
type ChatMessage = {
55+
id: string;
56+
ts: number;
57+
text: string;
58+
displayName: string;
59+
};
60+
61+
const chatChannel = peerConnection.createDataChannel(CHAT_LABEL);
62+
const pending = new Map<string, (error?: Error) => void>();
63+
64+
chatChannel.addEventListener("message", (event) => {
65+
const payload = JSON.parse(event.data) as Outbound;
66+
67+
if (payload.type === "chat.history") {
68+
payload.events.forEach((e) => console.log("history", e.message));
69+
}
70+
71+
if (payload.type === "chat.message") {
72+
console.log("live", payload.message);
73+
}
74+
75+
if (payload.type === "chat.ack") {
76+
pending.get(payload.clientMsgId)?.();
77+
pending.delete(payload.clientMsgId);
78+
}
79+
80+
if (payload.type === "chat.error" && payload.clientMsgId) {
81+
pending.get(payload.clientMsgId)?.(new Error(payload.error));
82+
pending.delete(payload.clientMsgId);
83+
}
84+
});
85+
86+
function sendChat(text: string, displayName: string) {
87+
if (chatChannel.readyState !== "open") {
88+
throw new Error("chat channel is not open");
89+
}
90+
91+
const clientMsgId = crypto.randomUUID();
92+
const payload = {
93+
type: "chat.send",
94+
clientMsgId,
95+
text,
96+
displayName,
97+
};
98+
99+
return new Promise<void>((resolve, reject) => {
100+
pending.set(clientMsgId, (error?: Error) => {
101+
if (error) reject(error);
102+
else resolve();
103+
});
104+
105+
chatChannel.send(JSON.stringify(payload));
106+
});
107+
}
108+
```
109+
110+
## Connection flow
111+
112+
1. Create `RTCPeerConnection`.
113+
2. Create chat data channel with label `bb-chat-v1` before SDP offer.
114+
3. Handle inbound `chat.connected`, `chat.history`, `chat.message`, `chat.ack`, and `chat.error` payloads.
115+
4. Send messages as `{ "type": "chat.send", "clientMsgId", "text", "displayName" }`.
116+
5. Treat `chat.ack` as send success and `chat.error` as send failure.

0 commit comments

Comments
 (0)