REALTIME PLUGIN - nself-org/nchat GitHub Wiki
Plugin Name: realtime
Version: 1.0.0
Category: Communication
Status: Production Ready
Priority: CRITICAL
The Realtime Plugin provides WebSocket-based real-time communication infrastructure for ɳChat. It handles instant message delivery, presence tracking, typing indicators, and live updates.
- ✅ WebSocket Server - Bi-directional real-time communication
- ✅ Presence Tracking - Online/away/dnd/offline status
- ✅ Typing Indicators - Real-time typing notifications
- ✅ Room Management - Channel-based message routing
- ✅ Connection Management - Auto-reconnection, heartbeat
- ✅ Event Broadcasting - Pub/sub event system
- ✅ Message Delivery - Real-time message push
- ✅ Read Receipts - Message read status tracking
- ✅ Reactions - Real-time reaction updates
- ✅ Offline Queue - Queue messages when offline
- ✅ Presence Sync - Synchronize presence across devices
- ✅ Scalability - Redis-backed scaling to 10,000+ connections
- ✅ Authentication - JWT token validation
- ✅ Rate Limiting - Prevent message spam
- ✅ Monitoring - Connection metrics and health checks
- Docker running
- nself CLI v0.9.8+
- Redis service (provided by nself stack)
cd /Users/admin/Sites/nself-nchat/backend
nself plugin install realtimeAdd to backend/.env.plugins:
# Realtime Plugin
REALTIME_ENABLED=true
REALTIME_PORT=3101
REALTIME_ROUTE=realtime.${BASE_DOMAIN:-localhost}
REALTIME_MEMORY=256M
# WebSocket Configuration
REALTIME_WEBSOCKET_MAX_CONNECTIONS=10000
REALTIME_WEBSOCKET_PING_INTERVAL=25000
REALTIME_WEBSOCKET_PING_TIMEOUT=5000
# Presence Configuration
REALTIME_PRESENCE_TIMEOUT=30000
REALTIME_PRESENCE_SYNC_INTERVAL=10000
# Typing Configuration
REALTIME_TYPING_TIMEOUT=3000
REALTIME_TYPING_DEBOUNCE=300
# Redis Configuration
REALTIME_REDIS_HOST=redis
REALTIME_REDIS_PORT=6379
REALTIME_REDIS_DB=1
REALTIME_REDIS_PASSWORD=${REDIS_PASSWORD:-}
# Authentication
REALTIME_JWT_SECRET=${JWT_SECRET}
REALTIME_JWT_ALGORITHM=HS256
# Rate Limiting
REALTIME_RATE_LIMIT_ENABLED=true
REALTIME_RATE_LIMIT_POINTS=100
REALTIME_RATE_LIMIT_DURATION=60
# Monitoring
REALTIME_METRICS_ENABLED=true
REALTIME_HEALTH_CHECK_INTERVAL=30nself restartGET /healthResponse:
{
"status": "healthy",
"service": "realtime",
"version": "1.0.0",
"uptime": 86400,
"websocket": {
"running": true,
"connections": 1234
},
"dependencies": {
"redis": {
"status": "connected",
"latency": 2
}
}
}GET /presence/:channelIdResponse:
{
"channelId": "channel-123",
"users": [
{
"userId": "user-1",
"status": "online",
"lastSeen": "2026-02-03T12:00:00Z"
}
],
"count": {
"online": 5,
"away": 2,
"dnd": 1,
"offline": 10
}
}POST /presence/:channelId
Content-Type: application/json
{
"userId": "user-123",
"status": "online"
}POST /typing
Content-Type: application/json
{
"userId": "user-123",
"channelId": "channel-456",
"isTyping": true
}POST /messages
Content-Type: application/json
{
"channelId": "channel-123",
"userId": "user-456",
"content": "Hello world!",
"mentions": ["user-789"]
}GET /poll?channelId=channel-123&since=1234567890For clients that can't use WebSocket, provides HTTP polling.
socket.connect({
auth: {
token: 'jwt-token-here',
userId: 'user-123',
},
})socket.emit('channel:join', {
channelId: 'channel-123',
})socket.emit('channel:leave', {
channelId: 'channel-123',
})socket.emit('message:send', {
channelId: 'channel-123',
content: 'Hello!',
mentions: [],
})socket.emit('presence:update', {
status: 'online', // online, away, dnd, offline
})socket.emit('typing', {
channelId: 'channel-123',
isTyping: true,
})socket.on('message', (message) => {
console.log('New message:', message)
})socket.on('presence', (presence) => {
console.log('Presence update:', presence)
})socket.on('typing', (typing) => {
console.log('User typing:', typing)
})socket.on('read', (receipt) => {
console.log('Message read:', receipt)
})socket.on('reaction', (reaction) => {
console.log('Message reaction:', reaction)
})socket.on('error', (error) => {
console.error('WebSocket error:', error)
})# .env.local
NEXT_PUBLIC_REALTIME_URL=http://realtime.localhost:3101
NEXT_PUBLIC_REALTIME_WS_URL=ws://realtime.localhost:3101
NEXT_PUBLIC_REALTIME_ENABLED=trueimport { useRealtime } from '@/hooks/use-realtime'
function ChatChannel({ channelId }) {
const {
isConnected,
joinChannel,
leaveChannel,
sendTyping,
updatePresence
} = useRealtime()
useEffect(() => {
if (isConnected) {
joinChannel(channelId)
}
return () => leaveChannel(channelId)
}, [channelId, isConnected])
const handleTyping = () => {
sendTyping(channelId, true)
}
return (
<div>
{isConnected ? 'Connected' : 'Connecting...'}
</div>
)
}import { realtimeClient } from '@/services/realtime/realtime-client'
// Connect
await realtimeClient.connect(userId, token)
// Join channel
realtimeClient.joinChannel('channel-123')
// Listen for messages
realtimeClient.on('message', (message) => {
console.log('New message:', message)
})
// Send typing indicator
realtimeClient.sendTyping('channel-123', true)
// Update presence
realtimeClient.updatePresence('online')
// Disconnect
realtimeClient.disconnect()curl http://realtime.localhost:3101/healthdescribe('Realtime Plugin', () => {
it('should connect and join channel', async () => {
const client = new RealtimeClient()
await client.connect('user-123', 'token')
client.joinChannel('channel-123')
const message = await new Promise((resolve) => {
client.on('message', resolve)
// Trigger message from another client
})
expect(message).toHaveProperty('content')
})
})- Connections: Supports 10,000+ concurrent connections
- Message Latency: < 50ms average
- CPU: ~0.5 core at 1000 connections
- Memory: ~256MB baseline, scales with connections
- Redis: 1-2ms average latency
For horizontal scaling:
# docker-compose.yml
realtime:
deploy:
replicas: 3
environment:
- REALTIME_REDIS_HOST=redis-clusterUse Redis Cluster for pub/sub across instances.
curl http://realtime.localhost:3101/healthcurl http://realtime.localhost:3101/metricsKey Metrics:
-
realtime_connections_total- Active connections -
realtime_messages_total- Messages processed -
realtime_presence_updates_total- Presence updates -
realtime_latency_ms- Message latency histogram
nself logs realtime --followProblem: WebSocket connection fails
Solutions:
- Check if service is running:
nself status realtime - Verify JWT token is valid
- Check firewall/proxy settings for WebSocket
- Test with HTTP polling as fallback
Problem: Messages delayed
Solutions:
- Check Redis latency:
redis-cli --latency - Monitor connection count:
/healthendpoint - Scale horizontally if at capacity
- Check network bandwidth
Problem: High memory usage
Solutions:
- Check connection count
- Review presence timeout settings
- Enable connection limits
- Monitor with:
docker stats realtime
- Auto-Reconnect: Enable exponential backoff
- Heartbeat: Keep connection alive with ping/pong
- Fallback: Implement HTTP polling for unstable connections
- Token Refresh: Update JWT before expiration
- Error Handling: Gracefully handle disconnections
- Rate Limiting: Prevent message spam
- Connection Limits: Set max connections per user
- Monitoring: Track metrics and set alerts
- Scaling: Use Redis Cluster for horizontal scaling
- Security: Validate all incoming events
All WebSocket connections require JWT authentication:
socket.connect({
auth: {
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
},
})Room access is validated before join:
socket.emit('channel:join', { channelId: 'private-channel' })
// Validated against user permissionsConfigured per-user limits:
- 100 events per minute (default)
- Configurable via
REALTIME_RATE_LIMIT_POINTS
- Initial release
- WebSocket server with Socket.IO
- Presence tracking
- Typing indicators
- Room management
- Redis pub/sub scaling
- Health checks and monitoring
- Documentation: https://nself.org/docs/plugins/realtime
- Issues: https://github.com/nself-org/plugins/issues
- Discord: https://discord.gg/nself