Skip to main content

WebSocket Protocol

The DEJA.js server provides a WebSocket interface for real-time bidirectional communication between browser apps and the backend. All messages are JSON objects with an action field and a payload field. The default port is 8082, configurable via the VITE_WS_PORT environment variable.

Connection

Connect to the WebSocket server at the configured host and port:

ws://localhost:8082

In production or when served over HTTPS, clients should use wss:// instead. The Monitor app resolves the protocol automatically based on window.location.protocol.

Connection Lifecycle

  1. Client connects -- The server adds the client to its general connections pool.
  2. Server sends ack -- Immediately after connection, the server sends an acknowledgment with the layout ID and server ID.
  3. Server sends wsconnected -- The server sends the client's IP address and server ID.
  4. Client sends/receives messages -- Normal bidirectional communication.
  5. Client subscribes to devices (optional) -- The client can subscribe to device-specific serial monitoring.
  6. Client disconnects -- The server removes the client from all connection pools and device subscriptions.

Message Format

Every message is a JSON object with two fields:

{
  "action": "string",
  "payload": {}
}

The action field identifies the message type. The payload field contains action-specific data and its structure varies by action.

Server-to-Client Actions

These messages are broadcast from the server to all connected clients.

ack -- Connection Acknowledgment

Sent immediately when a client connects.

{
  "action": "ack",
  "payload": {
    "layoutId": "tamarack",
    "serverId": "DEJA.js"
  }
}

wsconnected -- Client Connected

Sent after the ack message with client identification.

{
  "action": "wsconnected",
  "payload": {
    "ip": "192.168.1.42",
    "serverId": "DEJA.js"
  }
}

dcc -- DCC Command Broadcast

Broadcast whenever a DCC-EX command is sent to the serial port. Allows clients to display a live command log.

{
  "action": "dcc",
  "payload": "t 3 50 1"
}

connected -- Serial Port Connected

Broadcast when a serial connection to a DCC-EX CommandStation is established.

{
  "action": "connected",
  "payload": {
    "baudRate": 115200,
    "device": "CommandStation",
    "path": "/dev/tty.usbmodem1101"
  }
}

portList -- Available Serial Ports

Broadcast in response to a listPorts command.

{
  "action": "portList",
  "payload": [
    "/dev/tty.usbmodem1101",
    "/dev/tty.usbserial-110"
  ]
}

status -- Server Status

Broadcast in response to a getStatus, status, or ping command.

{
  "action": "status",
  "payload": {
    "client": "dejaJS",
    "isConnected": true
  }
}

broadcast -- General Broadcast

Wraps miscellaneous messages from serial connection handlers.

{
  "action": "broadcast",
  "payload": {}
}

Device Serial Monitoring

The WebSocket server supports device-specific serial monitoring through a subscribe/unsubscribe protocol. This allows the Monitor app to receive filtered serial I/O for individual hardware devices without getting traffic from other devices.

Client-to-Server: Subscribe to Device

{
  "action": "subscribe-device",
  "deviceId": "tj-eagle-nest-pico"
}

Server response:

{
  "action": "device-subscribed",
  "payload": {
    "deviceId": "tj-eagle-nest-pico",
    "success": true
  }
}

Client-to-Server: Unsubscribe from Device

{
  "action": "unsubscribe-device",
  "deviceId": "tj-eagle-nest-pico"
}

Server response:

{
  "action": "device-unsubscribed",
  "payload": {
    "deviceId": "tj-eagle-nest-pico",
    "success": true
  }
}

Server-to-Client: Serial Data

Sent only to clients subscribed to the specific device.

Incoming serial data (from device to server):

{
  "action": "serial-data",
  "payload": {
    "deviceId": "tj-eagle-nest-pico",
    "data": "<p1>",
    "timestamp": "2025-01-15T14:30:22.123Z",
    "direction": "incoming"
  }
}

Outgoing serial commands (from server to device):

{
  "action": "serial-data",
  "payload": {
    "deviceId": "tj-eagle-nest-pico",
    "data": "T 1 1",
    "timestamp": "2025-01-15T14:30:22.456Z",
    "direction": "outgoing"
  }
}

Connection Management

General Connections

All connected clients receive broadcast messages (DCC commands, status updates, port lists). The server maintains an array of active WebSocket connections and iterates over them for each broadcast, skipping any that are not in the OPEN state.

Device Connections

Device-specific connections are tracked in a separate Map<string, WebSocket[]> keyed by device ID. A single client can subscribe to multiple devices, and multiple clients can subscribe to the same device. When a client disconnects, the server removes it from all device subscription lists.

Cleanup

On server shutdown (SIGINT or SIGTERM):

  1. All general connections are closed with code 1000 (normal closure).
  2. All device-specific connections are closed.
  3. The connection maps are cleared.
  4. The WebSocket server is closed.

Example: Monitoring a Device

const ws = new WebSocket('ws://localhost:8082')

ws.onopen = () => {
  // Subscribe to a specific device
  ws.send(JSON.stringify({
    action: 'subscribe-device',
    deviceId: 'tj-thunder-city-pico'
  }))
}

ws.onmessage = (event) => {
  const message = JSON.parse(event.data)

  switch (message.action) {
    case 'device-subscribed':
      console.log('Subscribed to', message.payload.deviceId)
      break

    case 'serial-data':
      const { deviceId, data, direction, timestamp } = message.payload
      console.log(`[${direction}] ${deviceId}: ${data}`)
      break

    case 'dcc':
      // General DCC command broadcast
      console.log('DCC:', message.payload)
      break

    default:
      // Other broadcast messages
      break
  }
}