Skip to main content

WebSocket Reverse Tunnel Usage

Overview

WebSocket support enables real-time bidirectional communication between browsers and device web services through AWS IoT Secure Tunnels.

Architecture

Browser WebSocket Client
    ↓ wss://device-abc123.fleet.roboticks.io
CloudFront (TLS Termination - HTTP only)

API Gateway WebSocket API

Lambda WebSocket Handler (websocket-handler.js)
    ↓ WebSocket with source_access_token
AWS IoT Secure Tunnel
    ↓ WebSocket with destination_access_token
Device Local Proxy Agent
    ↓ TCP connection
Device Web Service (localhost:8080)

Connection Flow

1. Browser Connects

// Frontend JavaScript
const dsn = 'abc123'; // Device DSN
const ws = new WebSocket(`wss://device-${dsn}.fleet.roboticks.io`);

ws.onopen = () => {
  console.log('Connected to device');

  // Send message to device
  ws.send(JSON.stringify({ action: 'get_temperature' }));
};

ws.onmessage = (event) => {
  console.log('Message from device:', event.data);
  const data = JSON.parse(event.data);
  // Handle real-time data from device
};

ws.onerror = (error) => {
  console.error('WebSocket error:', error);
};

ws.onclose = () => {
  console.log('Disconnected from device');
};

2. Lambda Handles Connection

The WebSocket Lambda handles three route events:

$connect

  • Validates device DSN from subdomain
  • Looks up tunnel configuration in DynamoDB
  • Verifies tunnel is OPEN
  • Stores connection metadata
  • Returns 200 to accept connection

$default (message)

  • Establishes IoT tunnel WebSocket if not connected
  • Forwards browser message to device
  • Forwards device response back to browser
  • Maintains bidirectional communication

$disconnect

  • Closes IoT tunnel WebSocket
  • Cleans up connection metadata

Use Cases

Live Sensor Dashboard

// Real-time temperature monitoring
const ws = new WebSocket(`wss://device-${dsn}.fleet.roboticks.io`);

ws.onopen = () => {
  // Request continuous sensor updates
  ws.send(JSON.stringify({
    type: 'subscribe',
    sensors: ['temperature', 'humidity', 'pressure']
  }));
};

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  updateChart(data.temperature);
  updateHumidityGauge(data.humidity);
};

Interactive Terminal

// Web-based terminal to device
const ws = new WebSocket(`wss://device-${dsn}.fleet.roboticks.io/terminal`);

ws.onopen = () => {
  console.log('Terminal connected');
};

// Send commands
function executeCommand(cmd) {
  ws.send(JSON.stringify({ type: 'command', data: cmd }));
}

// Receive output
ws.onmessage = (event) => {
  const output = JSON.parse(event.data);
  terminalDisplay.appendChild(output.data);
};

Live Video Stream

// Real-time video from device camera
const ws = new WebSocket(`wss://device-${dsn}.fleet.roboticks.io/video`);

ws.onopen = () => {
  ws.send(JSON.stringify({ action: 'start_stream', quality: 'medium' }));
};

ws.onmessage = (event) => {
  // event.data contains video frame (binary)
  const blob = new Blob([event.data], { type: 'image/jpeg' });
  const url = URL.createObjectURL(blob);
  videoElement.src = url;
};

Error Handling

The WebSocket Lambda returns specific error codes:

Connection Errors

ws.addEventListener('close', (event) => {
  switch (event.code) {
    case 400:
      console.error('Invalid device DSN');
      break;
    case 404:
      console.error('Tunnel not found or not active');
      break;
    case 503:
      console.error('Tunnel not open or device offline');
      break;
    case 502:
      console.error('Failed to connect to device tunnel');
      break;
    default:
      console.error('Connection closed:', event.reason);
  }
});

Deployment

After deploying the CDK stack, you’ll get two endpoints:
  1. HTTP API: For static content (HTML/CSS/JS)
    https://device-abc123.fleet.roboticks.io
    
  2. WebSocket API: For real-time communication
    wss://device-abc123.fleet.roboticks.io
    
Both use the same domain - CloudFront routes HTTP traffic to HTTP API and WebSocket upgrade requests to WebSocket API.

Testing

Test WebSocket Connection

# Install wscat
npm install -g wscat

# Connect to device WebSocket
wscat -c wss://device-abc123.fleet.roboticks.io

# Send test message
> {"action": "ping"}

# Receive response
< {"type": "pong", "timestamp": 1699564800}

Monitor Connection

Check CloudWatch Logs for the WebSocket Lambda:
aws logs tail /aws/lambda/RoboticksStack-ReverseTunnelWebSocketProxy --follow

Performance Considerations

Connection Limits

  • API Gateway WebSocket: 500 concurrent connections per account by default
  • Lambda concurrent executions: 1000 by default
  • AWS IoT Secure Tunnel: 1 tunnel per device

Timeouts

  • API Gateway idle timeout: 10 minutes
  • Lambda execution timeout: 5 minutes (configured)
  • IoT Secure Tunnel timeout: 12 hours (default)

Message Size

  • API Gateway WebSocket max message size: 32 KB
  • For larger data (video frames), chunk into multiple messages

Troubleshooting

Connection Refused

Problem: WebSocket connection fails immediately Solution:
  1. Check device tunnel is enabled and not expired
  2. Verify DNS is resolving correctly
  3. Check CloudWatch logs for Lambda errors

Messages Not Delivered

Problem: Messages sent but no response Solution:
  1. Verify device local proxy agent is running
  2. Check IoT tunnel status in CloudWatch
  3. Verify device service is listening on configured port

Connection Drops

Problem: WebSocket disconnects unexpectedly Solution:
  1. Implement ping/pong heartbeat (every 30 seconds)
  2. Automatic reconnection logic in frontend
  3. Check Lambda CloudWatch logs for errors

Security

Authentication

Currently, WebSocket connections are authenticated via:
  • Valid device DSN in subdomain
  • Active tunnel in DynamoDB cache
  • OPEN tunnel status in AWS IoT
TODO: Add JWT token validation for user authentication

Rate Limiting

TODO: Implement per-device connection limits

Encryption

  • TLS 1.2+ enforced by CloudFront
  • AWS IoT Secure Tunnel uses end-to-end encryption

References