Chat WebSocket — events
Realtime JSON trên chat-api. CMS đi qua admin-api-gateway
/api/v1/chat/ws; game vẫn nối thẳng chat-api /ws.
Ping server là binary 1 byte. Gửi/xóa/react tin vẫn gọi HTTP/RPC — WS chỉ fan-out.
1. Tổng quan
| Mục | Giá trị |
|---|---|
| Host | Game: chat-api. CMS: admin-api-gateway (cùng public key JWT) |
| Path | Game GET /ws · CMS GET /api/v1/chat/ws |
| Game | ?access_token=<game jwt> — kind trống / game / user |
| CMS | wss://<admin-gateway>/api/v1/chat/ws?access_token=<cms jwt> (gateway mặc định kind=cms) |
| Protocol | JSON text events + binary ping/pong |
| Read limit | 4096 bytes / frame text |
2. Handshake
GET /ws?access_token=<jwt> // game → chat-api GET /api/v1/chat/ws?access_token=<cms_jwt> // CMS → admin-api-gateway GET /api/v1/chat/ws?kind=cms&access_token=<cms_jwt> // tương đương
| Kết quả | HTTP | Body |
|---|---|---|
| Token invalid / hết hạn | 200, không upgrade | {"code":4} |
| Upgrade fail | 200, không upgrade | {"code":3} |
| OK | 101 Switching Protocols | — |
Chat-api: kind trống parse game JWT trước, fail thì thử CMS JWT.
kind=game|user chỉ game. kind=cms|admin chỉ CMS.
Admin gateway luôn gắn kind=cms nếu client không gửi kind / token_kind.
3. Heartbeat
| Hướng | Frame | Giá trị |
|---|---|---|
| Server → client | binary | 1 byte 0x00 mỗi ~15s |
| Client → server | binary | 1 byte 0x01 (pong) |
| Cả hai | text JSON | {"type":"ping"} / {"type":"pong"} |
4. Client packets
Text JSON chỉ dùng cho heartbeat; field không dùng có thể bỏ.
{
"type": "subscribe",
"conversation_id": "p2p:social:user_1001_user_1002"
}
| type | conversation_id | Server làm gì |
|---|---|---|
subscribe | bất kỳ | Hiện chưa được socket handler xử lý; không thay đổi fan-out |
unsubscribe | bất kỳ | Hiện chưa được socket handler xử lý; không thay đổi fan-out |
ping | — | Trả {"type":"pong"}, reset idle |
pong | — | Reset idle |
JSON lỗi parse bị bỏ qua. Disconnect: server unregister connection khỏi actor.
5. Event envelope
Mọi sự kiện (trừ ping/pong) cùng shape:
{
"type": "message",
"conversation_id": "support:1001:1757812345678901234",
"actor_id": 1001,
"origin": "a1b2c3d4-....",
"message": { /* Message, optional */ },
"conversation": { /* Conversation, optional */ }
}
| Field | Ý nghĩa |
|---|---|
type | Tên event — switch UI theo bảng dưới |
conversation_id | Room. Dùng upsert inbox / mở thread |
actor_id | Người gây ra (sender, admin claim, user join…). 0 với game_room.created |
origin | UUID instance chat-api (de-dupe multi-pod). Client có thể bỏ |
message | Có với message, delete, edit, reaction và system event |
conversation | Có với mọi fan-out hiện tại |
message_id, reaction, count | Có với message.reaction.updated |
user_id, last_read_seq | Có với conversation.read.updated |
6. Message events
| type | Khi nào | Dùng field |
|---|---|---|
message | Send thành công (mọi category) | message đầy đủ, actor_id = sender |
message.deleted | Unsend tin của mình | message với deleted=true |
message.edited | Sửa tin hoặc hoàn tất bản dịch support | message đã cập nhật |
message.reaction.created | Gắn / gỡ reaction P2P, self hoặc game room | message đã cập nhật reactions |
message.reaction.updated | Đổi tổng reaction group/channel | message_id, reaction, count |
message.typing.created | Actor báo đang gõ | actor_id, conversation_id |
{
"type": "message",
"conversation_id": "p2p:social:user_1001_user_1002",
"actor_id": 1001,
"origin": "...",
"message": {
"id": "66e1ab0c2f1a4b0012345678",
"conversation_id": "p2p:social:user_1001_user_1002",
"seq": 8,
"sender_id": 1001,
"sender_type": "user",
"type": "text",
"content": {
"text": "hello",
"attachments": [],
"p2p_reactions": {},
"reaction_count": {}
},
"deleted": false,
"created_at": "2026-09-14T03:20:00.123Z"
}
}
Replace bubble theo message.id (deleted/reacted). Inbox preview = content.text hoặc [image]/[audio]/…
7. Conversation events
| type | Khi nào | Dùng field |
|---|---|---|
conversation.invited | Actor được mời vào P2P/group | conversation; chỉ gửi recipient pending |
conversation.user.joined | Pending member accept/join | message system + conversation |
conversation.user.left | Member leave hoặc bị remove | message system + conversation |
conversation.settings.updated | Đổi settings conversation | message system + conversation |
conversation.read.updated | Member mark read | user_id, last_read_seq, conversation |
Ba event member/settings là system message: message.content.text bằng đúng type event; metadata có actor/user liên quan.
8. Support events
| type | Khi nào | actor_id |
|---|---|---|
support.started | User /chat/support/start tạo ticket mới | user |
support.claimed | Admin claim | admin |
support.agent.added | Thêm agent role support | admin thao tác |
support.closed | Đóng ticket | admin đóng |
Các event này kèm conversation (claimed_by, status, participants…) và được gửi tới toàn bộ admin đang online. CMS pending queue: support.started → thêm card; support.claimed → gỡ khỏi pending.
9. Game room events
| type | Khi nào | actor_id |
|---|---|---|
game_room.created | Node tạo / recreate room | 0 |
game_room.joined | User vào bàn | user vừa join |
game_room.left | User rời bàn | user vừa leave |
conversation.metadata.game_id / room_id,
reference.id = {gameID}/{roomID}.
Client vào bàn nhận event theo membership, rồi gọi history RPC.
10. Payloads conversation / message
Cùng schema REST (snake_case). Timestamp ISO-8601. id message là hex ObjectId.
conversation (rút gọn):
{
"id": "game_room:tienlen:room-9",
"type": "group",
"category": "game_room",
"name": "Bàn 9",
"owner_id": 1001,
"participants": [{ "id": 1001, "type": "user", "role": "member" }],
"settings": { "only_admin_send": false, "expired_at": "2026-09-14T04:00:00Z" },
"reference": { "type": "game_room", "id": "tienlen/room-9" },
"metadata": { "game_id": "tienlen", "room_id": "room-9" },
"last_seq": 3,
"status": "active",
"created_at": "2026-09-14T03:30:00Z",
"updated_at": "2026-09-14T03:30:00Z"
}
11. Flow client
- Có JWT → game
GET /ws?access_token=; CMSGET /api/v1/chat/ws?access_token=trên admin gateway. - Nếu HTTP 200 +
code=4: refresh token, không giả định đã connected. - On open: binary pong
0x01khi nhận0x00. - Song song gọi list RPC/REST để fill inbox (WS không dump history).
- Mở thread mới / vào bàn: conversation membership quyết định fan-out; không gửi subscribe.
- On
message: append nếuseqmới; trùng id thì ignore. - On
message.deleted/message.edited/message.reaction.created: patch theo id. - On
support.closed/ room expired (send lỗi): khóa composer. - Reconnect: history
before_seq=0để bù tin miss.
send trước (hoặc optimistic local), WS message để peer + confirm seq/id server.
12. Lỗi
| Hiện tượng | Nguyên nhân / xử lý |
|---|---|
{"code":4} lúc GET /ws | JWT sai/hết hạn — login lại |
{"code":3} | Upgrade fail — retry |
| Socket đóng im lặng | Thiếu pong 25s, queue full, hoặc process restart — reconnect |
| Không nhận tin room mới | Chưa là member hoặc event phát trước lúc socket kết nối — refresh history/list |
| JSON client > 4096 bytes | Server cắt connection |
WS không trả domain error của send/delete — lỗi nằm ở RPC/REST (game / cms).