# WebSocket (WS/WSS) Implementation Guide ## Overview The SaaS Platform includes a production-ready WebSocket service for real-time bidirectional communication between clients and the server. The service supports both **WS** (unencrypted) and **WSS** (encrypted over TLS) protocols. ## Connection URLs - **Development (HTTP/WS)**: `ws://localhost:8888/ws` - **Production (HTTPS/WSS)**: `wss://localhost:8443/ws` or `wss://yourdomain.com/ws` ## Features ✅ **JWT Authentication** - Secure connection with JWT tokens ✅ **Channel-based Pub/Sub** - Subscribe to specific event channels ✅ **Authorization** - Role-based and user-specific channel access ✅ **Heartbeat/Ping-Pong** - Automatic connection health monitoring ✅ **Redis Integration** - Scalable pub/sub messaging ✅ **Connection Management** - Track and manage all active connections ✅ **Graceful Reconnection** - Client reconnection support ✅ **Real-time Events** - Live updates for apps, users, database, deployments --- ## Authentication ### Method 1: Query Parameter (Recommended) ```javascript const token = 'your_jwt_token'; const ws = new WebSocket(`ws://localhost:8888/ws?token=${token}`); ``` ### Method 2: Authorization Header (Node.js/Advanced) ```javascript const WebSocket = require('ws'); const ws = new WebSocket('ws://localhost:8888/ws', { headers: { 'Authorization': `Bearer ${token}` } }); ``` ### Method 3: Sec-WebSocket-Protocol (Browser Compatibility) ```javascript const ws = new WebSocket('ws://localhost:8888/ws', [`token-${token}`]); ``` --- ## Connection Flow ``` 1. Client connects to ws://localhost:8888/ws?token=JWT 2. Server extracts and verifies JWT token 3. Server sends "connected" message with user info and authorized channels 4. Client can now subscribe to channels and receive events ``` --- ## Message Protocol All messages are JSON-formatted strings. ### Client → Server Messages #### Subscribe to Channel ```json { "type": "subscribe", "channel": "apps:created" } ``` #### Unsubscribe from Channel ```json { "type": "unsubscribe", "channel": "apps:created" } ``` #### Ping (Keep-Alive) ```json { "type": "ping" } ``` #### List Available Channels ```json { "type": "list_channels" } ``` #### Get Connection Stats (Admin Only) ```json { "type": "stats" } ``` ### Server → Client Messages #### Connected (Welcome Message) ```json { "type": "connected", "connectionId": "uuid-v4", "user": { "userId": "user-uuid", "email": "user@example.com", "role": "admin" }, "authorizedChannels": [ "apps:created", "apps:updated", "user:user-uuid", "system:health" ], "timestamp": "2025-11-25T12:00:00.000Z" } ``` #### Subscription Confirmed ```json { "type": "subscribed", "channel": "apps:created", "timestamp": "2025-11-25T12:00:00.000Z" } ``` #### Event Received ```json { "type": "event", "channel": "apps:created", "data": { "id": "app-uuid", "name": "my-app", "status": "pending" }, "timestamp": "2025-11-25T12:00:00.000Z" } ``` #### Pong (Heartbeat Response) ```json { "type": "pong", "timestamp": "2025-11-25T12:00:00.000Z" } ``` #### Error ```json { "type": "error", "message": "Not authorized to subscribe to this channel", "timestamp": "2025-11-25T12:00:00.000Z" } ``` --- ## Available Channels ### Application Events - `apps:created` - New application created - `apps:updated` - Application updated - `apps:deleted` - Application deleted - `apps:deployed` - Application deployed - `apps:status` - Application status changed ### User Events - `users:created` - New user registered - `users:updated` - User profile updated - `users:deleted` - User deleted ### User-Specific Channel (Private) - `user:{userId}` - Private channel for specific user (only accessible by the user or admin) ### Database Events - `database:table_created` - New table created - `database:table_updated` - Table schema updated - `database:table_deleted` - Table deleted - `database:row_updated` - Row data changed ### Deployment Events - `deployments:started` - Deployment started - `deployments:completed` - Deployment finished successfully - `deployments:failed` - Deployment failed - `deployments:status` - Deployment status update ### Storage Events - `storage:uploaded` - File uploaded - `storage:deleted` - File deleted - `storage:updated` - File updated ### System Events (Admin Only) - `system:health` - System health updates - `system:metrics` - Performance metrics - `system:alerts` - System alerts ### Public Channels (No Auth Required) - `announcements` - Public announcements --- ## Channel Authorization Channels have different authorization requirements: | Channel Pattern | Requires Auth | Allowed Roles | Special Rules | |----------------|---------------|---------------|---------------| | `announcements` | No | All | Public channel | | `apps:*` | Yes | All authenticated | - | | `users:*` | Yes | All authenticated | - | | `user:{userId}` | Yes | All authenticated | Only own user or admin | | `database:*` | Yes | All authenticated | - | | `deployments:*` | Yes | All authenticated | - | | `storage:*` | Yes | All authenticated | - | | `system:*` | Yes | Admin only | Restricted to admins | --- ## JavaScript Client Example ### Basic Connection ```javascript class RealtimeClient { constructor(token, url = 'ws://localhost:8888/ws') { this.token = token; this.url = url; this.ws = null; this.reconnectAttempts = 0; this.maxReconnectAttempts = 5; this.reconnectDelay = 1000; } connect() { // Connect with token as query parameter this.ws = new WebSocket(`${this.url}?token=${this.token}`); this.ws.onopen = () => { console.log('WebSocket connected'); this.reconnectAttempts = 0; }; this.ws.onmessage = (event) => { const message = JSON.parse(event.data); this.handleMessage(message); }; this.ws.onerror = (error) => { console.error('WebSocket error:', error); }; this.ws.onclose = () => { console.log('WebSocket closed'); this.reconnect(); }; } handleMessage(message) { console.log('Received:', message); switch (message.type) { case 'connected': console.log('Connection ID:', message.connectionId); console.log('Authorized channels:', message.authorizedChannels); break; case 'subscribed': console.log(`Subscribed to ${message.channel}`); break; case 'event': console.log(`Event on ${message.channel}:`, message.data); // Handle your event here break; case 'error': console.error('Server error:', message.message); break; case 'pong': console.log('Pong received'); break; } } subscribe(channel) { this.send({ type: 'subscribe', channel: channel }); } unsubscribe(channel) { this.send({ type: 'unsubscribe', channel: channel }); } send(data) { if (this.ws.readyState === WebSocket.OPEN) { this.ws.send(JSON.stringify(data)); } else { console.error('WebSocket is not open'); } } reconnect() { if (this.reconnectAttempts < this.maxReconnectAttempts) { this.reconnectAttempts++; const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1); console.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`); setTimeout(() => { this.connect(); }, delay); } else { console.error('Max reconnection attempts reached'); } } disconnect() { if (this.ws) { this.ws.close(); } } } // Usage const token = 'your_jwt_token_here'; const client = new RealtimeClient(token, 'ws://localhost:8888/ws'); client.connect(); // Subscribe to channels client.ws.onopen = () => { client.subscribe('apps:created'); client.subscribe('users:updated'); client.subscribe('user:my-user-id'); }; ``` ### React Hook Example ```javascript import { useEffect, useRef, useState } from 'react'; export function useWebSocket(token, channels = []) { const [connected, setConnected] = useState(false); const [messages, setMessages] = useState([]); const wsRef = useRef(null); useEffect(() => { if (!token) return; const ws = new WebSocket(`ws://localhost:8888/ws?token=${token}`); wsRef.current = ws; ws.onopen = () => { console.log('WebSocket connected'); setConnected(true); // Subscribe to channels channels.forEach(channel => { ws.send(JSON.stringify({ type: 'subscribe', channel })); }); }; ws.onmessage = (event) => { const message = JSON.parse(event.data); if (message.type === 'event') { setMessages(prev => [...prev, message]); } }; ws.onclose = () => { console.log('WebSocket disconnected'); setConnected(false); }; ws.onerror = (error) => { console.error('WebSocket error:', error); }; return () => { ws.close(); }; }, [token, channels]); const send = (data) => { if (wsRef.current?.readyState === WebSocket.OPEN) { wsRef.current.send(JSON.stringify(data)); } }; return { connected, messages, send }; } // Usage in component function MyComponent() { const { connected, messages, send } = useWebSocket( localStorage.getItem('token'), ['apps:created', 'users:updated'] ); return (
Status: {connected ? 'Connected' : 'Disconnected'}