# Collaboration Server Setup Guide
This guide explains how to set up a WebSocket server for yaze's real-time collaboration feature, enabling multiple users to edit ROMs together.
## Quick Start with yaze-server
The official collaboration server is **[yaze-server](https://github.com/scawful/yaze-server)**, a Node.js WebSocket server with:
- Real-time session management
- AI agent integration (Gemini/Genkit)
- ROM synchronization and diff broadcasting
- Rate limiting and security features
### Local Development
```bash
git clone https://github.com/scawful/yaze-server.git
cd yaze-server
npm ci
npm start
# Server runs on ws://localhost:8765 (default port 8765)
```
### Production Deployment
For production, deploy yaze-server behind an SSL proxy when possible:
- **halext-server**: `ws://org.halext.org:8765` (pm2 process `yaze-collab`, no TLS on 8765 today; front with nginx/Caddy for `wss://` if desired)
- **Self-hosted**: Deploy to Railway, Render, Fly.io, or your own VPS
### Current halext deployment (ssh halext-server)
- Process: pm2 `yaze-collab`
- Port: `8765` (plain WS/HTTP; add TLS proxy for WSS)
- Health: `http://org.halext.org:8765/health`, metrics at `/metrics`
- AI: enable with `GEMINI_API_KEY` or `AI_AGENT_ENDPOINT` + `ENABLE_AI_AGENT=true`
### Server v2.1 Features
- **Persistence**: Configurable SQLite storage (`SQLITE_DB_PATH` env var)
- **Admin API**: Protected endpoints for session/room management
- **Enhanced Health**: AI status, TLS detection, persistence info in `/health`
- **Configurable Limits**: Tunable rate limits via environment variables
---
## Overview
The yaze web app (WASM build) supports real-time collaboration through WebSocket connections. Since GitHub Pages only serves static files, you'll need a separate WebSocket server to enable this feature.
## Architecture
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ User A │ │ WebSocket │ │ User B │
│ (Browser) │◄───►│ Server │◄───►│ (Browser) │
│ yaze WASM │ │ (Your Server) │ │ yaze WASM │
└─────────────────┘ └─────────────────┘ └─────────────────┘
```
## Protocol Specification
The halext deployment (`yaze-collab` pm2 on port 8765) runs the official **yaze-server v2.0** and speaks two compatible protocols:
- **WASM compatibility protocol** – used by the current web/WASM build (flat `type` JSON messages, no `payload` wrapper).
- **Session/AI protocol** – used by advanced clients (desktop/editor + AI/ROM sync/proposals) with `{ type, payload }` envelopes.
### WASM compatibility protocol (web build)
All messages are JSON with a `type` field and flat attributes.
**Client → Server**
- `create`: `room`, `name?`, `user`, `user_id`, `color?`, `password?`
- `join`: `room`, `user`, `user_id`, `color?`, `password?`
- `leave`: `room`, `user_id`
- `change`: `room`, `user_id`, `offset`, `old_data`, `new_data`, `timestamp?`
- `cursor`: `room`, `user_id`, `editor`, `x`, `y`, `map_id`
- `ping`: optional keep-alive (`{ "type": "ping" }`)
**Server → Client**
- `create_response`: `{ "type": "create_response", "success": true, "session_name": "..." }`
- `join_response`: `{ "type": "join_response", "success": true, "session_name": "..." }`
- `users`: `{ "type": "users", "list": [{ "id": "...", "name": "...", "color": "#4ECDC4", "active": true }] }`
- `change`: Echoed to room with `timestamp` added
- `cursor`: Broadcast presence updates
- `error`: `{ "type": "error", "message": "...", "payload": { "error": "..." } }`
- `pong`: `{ "type": "pong", "payload": { "timestamp": 1700000000000 } }`
**Notes**
- Passwords are supported (`password` hashed server-side); rooms are deleted when empty.
- Rate limits: 100 messages/min/IP; 10 join/host attempts/min/IP.
- Size limits: ROM diffs ≤ 5 MB, snapshots ≤ 10 MB. Heartbeat every 30s terminates dead sockets.
### Session/AI protocol (advanced clients)
Messages use `{ "type": "...", "payload": { ... } }`.
**Key client messages**
- `host_session`: `session_name`, `username`, `rom_hash?`, `ai_enabled? (default true)`, `session_password?`
- `join_session`: `session_code`, `username`, `session_password?`
- `chat_message`: `sender`, `message`, `message_type?`, `metadata?`
- `rom_sync`: `sender`, `diff_data` (base64), `rom_hash`
- `snapshot_share`: `sender`, `snapshot_data` (base64), `snapshot_type`
- `proposal_share` / `proposal_vote` / `proposal_update`
- `ai_query`: `username`, `query` (requires `ENABLE_AI_AGENT` plus `GEMINI_API_KEY` or `AI_AGENT_ENDPOINT`)
- `leave_session`, `ping`
**Key server broadcasts**
- `session_hosted`, `session_joined`, `participant_joined`, `participant_left`
- `chat_message`, `rom_sync`, `snapshot_shared`
- `proposal_shared`, `proposal_vote_received`, `proposal_updated`
- `ai_response` (only when AI is enabled and configured)
- `pong`, `error`, `server_shutdown`
See `yaze-server/README.md` for full payload examples.
---
## Deployment Options
### Self-Hosted (VPS/Dedicated Server)
1. Install Node.js 18+
2. Clone/copy the server code and install deps:
```bash
npm ci
```
3. Configure environment variables:
**Core Settings:**
```bash
PORT=8765 # WebSocket/HTTP port (default: 8765)
ENABLE_AI_AGENT=true # Enable AI query handling (default: true)
GEMINI_API_KEY=your_api_key # Gemini API key for AI responses
AI_AGENT_ENDPOINT=http://... # Alternative: external AI endpoint
```
**Persistence (v2.1+):**
```bash
SQLITE_DB_PATH=/var/lib/yaze-collab.db # File-based persistence (default: :memory:)
```
**Rate Limiting:**
```bash
RATE_LIMIT_MAX_MESSAGES=100 # Messages per minute per IP (default: 100)
JOIN_LIMIT_MAX_ATTEMPTS=10 # Join/host attempts per minute per IP (default: 10)
```
**Admin API:**
```bash
ADMIN_API_KEY=your_secret_key # Protect admin endpoints (optional)
```
4. Run with PM2 for process management:
```bash
npm install -g pm2
pm2 start server.js --name yaze-collab --env production
pm2 save
```
5. Add TLS reverse proxy (recommended)
**nginx example (translate `wss://` to local `ws://localhost:8765`):**
```nginx
server {
listen 443 ssl;
server_name collab.yourdomain.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location /ws {
proxy_pass http://localhost:8765;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 86400;
proxy_send_timeout 86400;
}
}
```
**Caddy example:**
```caddy
collab.yourdomain.com {
reverse_proxy /ws localhost:8765 {
header_up Upgrade {>Upgrade}
header_up Connection {>Connection}
}
tls you@example.com
}
```
**Why:** avoids mixed-content errors in browsers, encrypts ROM diffs/chat/passwords, and centralizes cert/ALPN handling.
### Platform-as-a-Service
| Platform | Pros | Cons |
|----------|------|------|
| **Railway** | Easy deploy, free tier | Limited free hours |
| **Render** | Free tier, auto-deploy | Spins down on inactivity |
| **Fly.io** | Global edge, generous free | More complex setup |
| **Deno Deploy** | Free, edge deployment | Deno runtime only |
| **Cloudflare Workers** | Free tier, global edge | Durable Objects cost |
---
## Client Configuration
### Method 1: JavaScript Configuration (Recommended)
Add before loading yaze:
```html
```
### Method 2: Meta Tag
```html
```
### Method 3: Runtime Configuration
In your integration code:
```cpp
auto& collab = WasmCollaboration::GetInstance();
collab.SetWebSocketUrl("ws://org.halext.org:8765");
```
---
## Security Considerations
- Transport: terminate TLS in front of the server (`wss://`). The halext deployment currently runs plain `ws://org.halext.org:8765`; add nginx/Caddy to secure it.
- Built-in guardrails: 100 messages/min/IP, 10 join/host attempts/min/IP, 5 MB ROM diff limit, 10 MB snapshot limit, 30s heartbeat that drops dead sockets.
- Passwords: supported on both protocols (`password` for WASM, `session_password` for full sessions). Hashing is SHA-256 on the server side.
- AI: only enabled when `ENABLE_AI_AGENT=true` **and** `GEMINI_API_KEY` or `AI_AGENT_ENDPOINT` is set. Leave unset to disable AI endpoints.
- Persistence: halext uses in-memory SQLite (sessions reset on restart). For durability, run sqlite on disk (`SQLITE_DB_PATH=/var/lib/yaze-collab.db`) or swap to Postgres/MySQL with a lightweight adapter. Add backups/retention for audit.
- Authentication: front the service with an auth gateway if you need verified identities; yaze-server does not issue tokens itself.
---
## Troubleshooting
- **Handshake issues:** Match the scheme to the deployment. halext runs `ws://org.halext.org:8765`; use `wss://` only when you have a TLS proxy forwarding `Upgrade` headers.
- **Health checks:** `curl http://org.halext.org:8765/health` and `/metrics` to confirm the service is live.
- **TLS errors:** If you front with nginx/Caddy, ensure HTTP/1.1, `Upgrade`/`Connection` headers, and a valid certificate. Remove `wss://` if you have not enabled TLS.
- **Disconnects/rate limits:** Server sends heartbeats every 30s and enforces limits. Check `pm2 logs yaze-collab` on halext for details.
- **Performance:** Keep diffs under 5 MB, snapshots under 10 MB, and batch cursor updates on the client. Enable compression at the proxy if needed.
---
## Operations Playbook (halext-friendly)
- **Status:** `curl http://org.halext.org:8765/health` and `/metrics`; add `/metrics` scrape to Prometheus if available.
- **Logs:** `pm2 logs yaze-collab` (rotate externally if needed).
- **Restart/Redeploy:** `pm2 restart yaze-collab`; `pm2 list` to verify uptime.
- **Admin actions:** Use Admin API (see below) or block abusive IPs at the proxy.
- **Scaling path:** add Redis pub/sub for multi-instance broadcast; place proxy in front with sticky room affinity if you shard.
---
## Admin API (v2.1+)
Protected endpoints for server administration. Set `ADMIN_API_KEY` to require authentication.
### Authentication
Include the key in requests:
```bash
curl -H "X-Admin-Key: your_secret_key" http://localhost:8765/admin/sessions
# Or as query param: http://localhost:8765/admin/sessions?admin_key=your_secret_key
```
### Endpoints
**List all sessions/rooms:**
```bash
GET /admin/sessions
# Response: { sessions: [...], wasm_rooms: [...], total_connections: N }
```
**List users in a session:**
```bash
GET /admin/sessions/:code/users
# Response: { code: "ABC123", type: "full"|"wasm", users: [...] }
```
**Close a session (kick all users):**
```bash
DELETE /admin/sessions/:code
# Body: { "reason": "Maintenance" } (optional)
# Response: { success: true, code: "ABC123", reason: "..." }
```
**Kick a specific user:**
```bash
DELETE /admin/sessions/:code/users/:userId
# Body: { "reason": "Violation of rules" } (optional)
# Response: { success: true, code: "ABC123", userId: "user-123", reason: "..." }
```
**Broadcast message to session:**
```bash
POST /admin/sessions/:code/broadcast
# Body: { "message": "Server maintenance in 5 minutes", "message_type": "admin" }
# Response: { success: true, code: "ABC123", recipients: N }
```
---
## Halext TLS Deployment Guide
Step-by-step guide to add WSS (TLS) to the halext deployment.
### Prerequisites
- SSH access to halext-server
- Domain DNS pointing to server (e.g., `collab.halext.org` or use existing `org.halext.org`)
- Certbot or existing SSL certificates
### Option A: nginx reverse proxy
1. **Install nginx (if not present):**
```bash
sudo apt update && sudo apt install nginx certbot python3-certbot-nginx
```
2. **Create nginx config:**
```bash
sudo nano /etc/nginx/sites-available/yaze-collab
```
```nginx
server {
listen 80;
server_name collab.halext.org; # or org.halext.org
# Redirect HTTP to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name collab.halext.org;
ssl_certificate /etc/letsencrypt/live/collab.halext.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/collab.halext.org/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# WebSocket proxy
location / {
proxy_pass http://127.0.0.1:8765;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 86400;
proxy_send_timeout 86400;
}
}
```
3. **Enable site and get certificate:**
```bash
sudo ln -s /etc/nginx/sites-available/yaze-collab /etc/nginx/sites-enabled/
sudo certbot --nginx -d collab.halext.org
sudo nginx -t && sudo systemctl reload nginx
```
4. **Update client config to use WSS:**
```javascript
window.YAZE_CONFIG = {
collaboration: { serverUrl: 'wss://collab.halext.org' }
};
```
### Option B: Caddy (simpler, auto-TLS)
1. **Install Caddy:**
```bash
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install caddy
```
2. **Create Caddyfile:**
```bash
sudo nano /etc/caddy/Caddyfile
```
```caddy
collab.halext.org {
reverse_proxy localhost:8765
# Caddy handles TLS automatically
}
```
3. **Reload Caddy:**
```bash
sudo systemctl reload caddy
```
### Verify TLS is working
```bash
# Check health endpoint shows TLS detected
curl -s https://collab.halext.org/health | jq '.tls'
# Expected: { "detected": true, "note": "Request via TLS proxy" }
# Test WebSocket connection
wscat -c wss://collab.halext.org
```
---
## PM2 Ecosystem File
For more control, use a PM2 ecosystem file:
**ecosystem.config.js:**
```javascript
module.exports = {
apps: [{
name: 'yaze-collab',
script: 'server.js',
instances: 1,
autorestart: true,
watch: false,
max_memory_restart: '500M',
env: {
NODE_ENV: 'development',
PORT: 8765
},
env_production: {
NODE_ENV: 'production',
PORT: 8765,
SQLITE_DB_PATH: '/var/lib/yaze-collab/sessions.db',
ENABLE_AI_AGENT: 'true',
// GEMINI_API_KEY: 'your_key_here',
// ADMIN_API_KEY: 'your_admin_key'
}
}]
};
```
**Usage:**
```bash
pm2 start ecosystem.config.js --env production
pm2 save
pm2 startup # Enable startup on boot
```
---
## Persistence & Backup
### Enable file-based persistence
```bash
# Create data directory
sudo mkdir -p /var/lib/yaze-collab
sudo chown $(whoami) /var/lib/yaze-collab
# Set environment variable
export SQLITE_DB_PATH=/var/lib/yaze-collab/sessions.db
pm2 restart yaze-collab --update-env
```
### Backup strategy
```bash
# Daily backup cron job
echo "0 3 * * * sqlite3 /var/lib/yaze-collab/sessions.db '.backup /backups/yaze-collab-$(date +%Y%m%d).db'" | crontab -
# Retain last 7 days
echo "0 4 * * * find /backups -name 'yaze-collab-*.db' -mtime +7 -delete" | crontab -e
```
### Health endpoint (v2.1+)
The `/health` endpoint now reports persistence status:
```json
{
"status": "healthy",
"version": "2.1",
"persistence": {
"type": "file",
"path": "/var/lib/yaze-collab/sessions.db"
},
"ai": {
"enabled": true,
"configured": true,
"provider": "gemini"
},
"tls": {
"detected": true,
"note": "Request via TLS proxy"
}
}
```
## Client UX hints
- Surface server status in the web UI by calling `/health` once on load and showing: server reachable, AI enabled/disabled (from health/metrics), and whether TLS is in use.
- Default `window.YAZE_CONFIG.collaboration.serverUrl` to `wss://collab.yourdomain.com/ws` when a TLS proxy is present; fall back to `ws://localhost:8765` for local dev.
- Show a small banner when AI is disabled or when the connection is downgraded to plain WS to set user expectations.
---
## Example: Complete Docker Deployment
**Dockerfile:**
```dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY server.js .
EXPOSE 8765
CMD ["node", "server.js"]
```
**docker-compose.yml:**
```yaml
version: '3.8'
services:
collab:
build: .
ports:
- "8765:8765"
restart: unless-stopped
environment:
- NODE_ENV=production
- ENABLE_AI_AGENT=true
# Uncomment one of the following if AI responses are desired
# - GEMINI_API_KEY=your_api_key
# - AI_AGENT_ENDPOINT=http://ai-service:5000
```
Deploy:
```bash
docker-compose up -d
```
---
## Testing Your Server
Use wscat to test:
```bash
npm install -g wscat
wscat -c ws://org.halext.org:8765
# Send create message
{"type":"create","room":"TEST01","name":"Test","user":"TestUser","user_id":"test-123","color":"#FF0000"}
# Check response
# < {"type":"create_response","success":true,"session_name":"Test"}
# Test full session protocol (AI disabled)
{"type":"host_session","payload":{"session_name":"DocsCheck","username":"tester","ai_enabled":false}}
```
Health check:
```bash
curl http://org.halext.org:8765/health
curl http://org.halext.org:8765/metrics
```
Or use the browser console on your yaze deployment:
```javascript
window.YAZE_CONFIG = {
collaboration: { serverUrl: 'ws://org.halext.org:8765' }
};
// Then use the collaboration UI in yaze
```