WebSocket Protocol
Real-time bidirectional communication protocol for STORM DAY. All messages are JSON-encoded.
Connection
Connect to the WebSocket endpoint with a valid JWT token:
GET /ws?token=<JWT>
// Example
ws://localhost:8080/ws?token=eyJhbGciOiJIUzI1NiIs...After successful connection, the gateway extracts userId and username from the JWT and stores them in the socket session.
Client → Server Frames
Messages sent from the client to the server:
| Action | Description |
|---|---|
join | Join a conversation room |
typing | Signal typing (debounced to once per 2s) |
ping | Heartbeat (sent every 25s) |
delivered | Acknowledge message delivery |
seen | Mark message as seen |
Join Room
{
"action": "join",
"room": "conversation:abc123-def456-ghi789"
}Typing Indicator
{
"action": "typing",
"room": "conversation:abc123-def456-ghi789"
}Heartbeat
{
"action": "ping"
}Delivery Receipt
{
"action": "delivered",
"room": "conversation:abc123-def456-ghi789",
"message_id": "msg-uuid"
}Seen Receipt
{
"action": "seen",
"room": "conversation:abc123-def456-ghi789",
"message_id": "msg-uuid",
"seen_user_id": "user-uuid",
"seen_display_name": "John Doe"
}Server → Client Frames
Messages broadcast from the server via NATS subject message.broadcast.conversation:{id}:
| Action | Description |
|---|---|
message | New message received |
typing | Someone is typing |
delivered | Message delivered to recipient |
seen | Message seen by recipient |
message_updated | A message was edited |
group_created | New conversation created |
pong | Heartbeat reply |
New Message
{
"action": "message",
"room": "conversation:abc123",
"user": "user-uuid",
"username": "john_doe",
"content": "Hello, world!",
"message_id": "msg-uuid",
"reply_to": {
"id": "original-msg-uuid",
"author": "jane_doe",
"text": "Original message..."
},
"forward_from": null
}Message Updated
{
"action": "message_updated",
"room": "conversation:abc123",
"message_id": "msg-uuid",
"content": "Updated message content"
}Group Created
{
"action": "group_created",
"id": "group-uuid",
"name": "Project Team",
"members": ["user1", "user2", "user3"]
}Reconnection Logic
The client automatically handles reconnection with the following behavior:
Auto-reconnect
After disconnect, client waits 3 seconds before attempting reconnection
Re-join Rooms
On successful reconnection, client re-joins all previously subscribed conversation rooms
Token Refresh
JWT is refreshed before reconnection if expired
Client Implementation Example
websocket-client.ts
// Initialize WebSocket connection
const token = authStore.accessToken
const ws = new WebSocket(`ws://localhost:8080/ws?token=${token}`)
ws.onopen = () => {
console.log('Connected to WebSocket')
// Join conversation rooms
conversations.forEach(conv => {
ws.send(JSON.stringify({
action: 'join',
room: `conversation:${conv.id}`
}))
})
// Start heartbeat
setInterval(() => {
ws.send(JSON.stringify({ action: 'ping' }))
}, 25000)
}
ws.onmessage = (event) => {
const data = JSON.parse(event.data)
switch (data.action) {
case 'message':
handleNewMessage(data)
break
case 'typing':
handleTypingIndicator(data)
break
case 'delivered':
handleDeliveryReceipt(data)
break
case 'seen':
handleSeenReceipt(data)
break
case 'pong':
// Heartbeat acknowledged
break
}
}
ws.onclose = () => {
console.log('Disconnected, reconnecting in 3s...')
setTimeout(() => initWebSocket(), 3000)
}