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:

ActionDescription
joinJoin a conversation room
typingSignal typing (debounced to once per 2s)
pingHeartbeat (sent every 25s)
deliveredAcknowledge message delivery
seenMark 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}:

ActionDescription
messageNew message received
typingSomeone is typing
deliveredMessage delivered to recipient
seenMessage seen by recipient
message_updatedA message was edited
group_createdNew conversation created
pongHeartbeat 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)
}