-
-
Notifications
You must be signed in to change notification settings - Fork 3
collaboration server
The teXt0wnz collaboration server enables real-time multi-user editing of text art. Built with Node.js and Express, it provides WebSocket-based communication for synchronized drawing and chat.
- Real-time collaboration - Multiple users can edit the same canvas simultaneously
- Canvas persistence - Canvas state auto-saved to disk at configurable intervals
- Chat functionality - Repositionable chat with user messages and server activity logs
- Desktop notifications - Optional notifications for chat messages and user events
- Session management - Named sessions with automatic state restoration
- SSL support - Optional HTTPS/WSS encryption
- Minimal overhead - Efficient message broadcasting and state management
- User tracking - Track connected users and their activities
- Timestamped backups - Automatic backup creation on save
src/js/server/
βββ main.js # Entry point and CLI argument processing
βββ config.js # Configuration parsing and validation
βββ server.js # Express server and middleware setup
βββ text0wnz.js # Collaboration engine and state management
βββ websockets.js # WebSocket server and message handling
βββ fileio.js # File I/O operations and SAUCE handling
βββ utils.js # Utility functions and helpers
main.js - Server entry point. Processes command-line arguments and starts the server.
config.js - Parses and validates configuration options from CLI arguments.
server.js - Sets up Express server with session middleware, SSL support, and WebSocket routing.
text0wnz.js - Collaboration engine that manages canvas state, user sessions, and message broadcasting.
websockets.js - Handles WebSocket connections, message routing, and session cleanup.
fileio.js - Manages file reading/writing, SAUCE metadata creation, and binary data conversions.
utils.js - Helper functions for logging, data validation, and format conversions.
- Server starts and listens on the configured port (default: 1337)
- Clients connect via WebSocket (direct or through reverse proxy)
- Session loads existing canvas state or creates new session
- Users collaborate by sending drawing commands and chat messages
- Server broadcasts all changes to connected clients in real-time
- Auto-save persists canvas and chat at configured intervals
- Cleanup manages disconnected users and stale sessions
Install required Node.js modules:
npm install
# or
bun iRequired packages:
-
express(v5.1.0+) - Web framework -
express-session(v1.18.2+) - Session middleware -
express-ws(v5.0.2+) - WebSocket support
Basic start:
bun server
# or
node src/js/server/main.jsThe server starts on port 1337 by default.
| Option | Description | Default |
|---|---|---|
[port] |
Port to run the server on | 1337 |
--ssl |
Enable SSL (requires certificates in ssl-dir) |
Disabled |
--ssl-dir <path> |
SSL certificate directory | /etc/ssl/private |
--save-interval <n> |
Auto-save interval in minutes |
30 (minutes) |
--session-name <str> |
Session file prefix (for state and chat backups) | joint |
--debug |
Enable verbose logging | false |
--help |
Show help message and usage examples | - |
Basic usage with custom port:
bun server 8080With SSL and custom session name:
bun server 443 --ssl --ssl-dir /etc/letsencrypt --session-name myjamWith custom save interval and debug mode:
bun server 1337 --save-interval 10 --debugComplete configuration:
bun server 8080 --ssl --ssl-dir /etc/letsencrypt --save-interval 15 --session-name collab --debugYou can set environment variables before starting the server:
| Variable | Description | Example |
|---|---|---|
NODE_ENV |
Node environment setting | production |
SESSION_KEY |
Session secret key for express | supersecretkey |
Important
By default, the session secret is set to "sauce". For production use, set a strong value via SESSION_KEY or modify in src/js/server/server.js.
Example with environment variables:
NODE_ENV=production SESSION_KEY=your-secret-key bun server 1337The WebSocket connection implements multiple security layers:
Client-Side Security:
-
Worker Initialization: The WebSocket worker must receive an
initcommand before accepting any other messages. This establishes the security context. -
Trusted URL Construction: WebSocket URLs are constructed exclusively from the worker's own
locationobject:- Protocol:
wss:for HTTPS origins,ws:for HTTP origins - Hostname: Matches the page's hostname
- Port: Uses the page's port or defaults (443 for HTTPS, 80 for HTTP)
- Protocol:
-
URL Validation: All WebSocket URLs are validated using the URL constructor. Malformed URLs are rejected with sanitized error messages.
-
Input Sanitization: All error messages and unknown commands have their output sanitized (newlines stripped, length limited) to prevent injection attacks.
-
JSON Protection: Server messages are parsed with try-catch blocks. Invalid JSON is safely logged without crashing the worker.
-
Silent Connection Check: The application performs a non-intrusive server availability check before prompting the user, preventing error dialogs when no server is present.
Connection Sequence:
// 1. Worker creation and initialization
const worker = new Worker('websocket.js');
worker.postMessage({ cmd: 'init' }); // Required first message
// 2. Worker establishes trusted parameters from location
// - allowedHostname = self.location.hostname
// - trustedProtocol = 'wss:' or 'ws:' based on page protocol
// - trustedPort = self.location.port or default
// 3. Silent server check (non-blocking)
worker.postMessage({ cmd: 'connect', silentCheck: true });
// 4. User chooses collaboration mode if server available
// 5. Full connection for collaboration
worker.postMessage({ cmd: 'connect', silentCheck: false });Error Handling:
- Malformed URLs: Sanitized error message, connection rejected
- Invalid JSON: Logged to console, message ignored
- Unknown commands: Logged with sanitized command name (max 6 chars)
- Connection failures: Clean failure handling with user notification
| Message Type | Format | Description |
|---|---|---|
join |
["join", username] |
User joins collaboration session |
nick |
["nick", newUsername] |
User changes display name |
chat |
["chat", message] |
Chat message |
draw |
["draw", blocks] |
Drawing command with array of canvas blocks |
resize |
["resize", {columns, rows}] |
Canvas size change |
fontChange |
["fontChange", {fontName}] |
Font selection change |
iceColorsChange |
["iceColorsChange", {iceColors}] |
Ice colors toggle |
letterSpacingChange |
["letterSpacingChange", {letterSpacing}] |
Letter spacing toggle |
| Message Type | Format | Description |
|---|---|---|
start |
["start", sessionData, sessionID, userList] |
Initial session data |
join |
["join", username, sessionID] |
User joined notification |
part |
["part", sessionID] |
User left notification |
nick |
["nick", username, sessionID] |
User name change |
chat |
["chat", username, message] |
Chat message broadcast |
draw |
["draw", blocks] |
Drawing command broadcast |
resize |
["resize", {columns, rows}] |
Canvas resize broadcast |
fontChange |
["fontChange", {fontName}] |
Font change broadcast |
iceColorsChange |
["iceColorsChange", {iceColors}] |
Ice colors broadcast |
letterSpacingChange |
["letterSpacingChange, {letterSpacing}]` |
Letter spacing broadcast |
The collaboration server includes a fully-featured chat system for real-time communication between users.
Repositionable Window:
- Drag the chat header to move the window anywhere on screen
- Position does not persist between sessions (resets on reload)
- To close/hide the chat window:
- Click the door icon on the right of header
- Or the chat icon in the main editor menu
Message Display:
- User messages: Displayed with username and message text
- Server logs: Join/leave/nickname change events styled differently
- Auto-scroll to newest messages
- Message history preserved during session
User Controls:
- Handle input: Change your display name (max
14characters) - Message input: Send chat messages (max
140characters) - Notifications toggle: Enable/disable desktop notifications
- User list: View all currently connected users
User Messages:
username: message text
Server Log Messages:
-
username has joined- User connects to session -
oldname is now newname- User changes display name -
username has quit- User disconnects from session
Optional desktop notifications for:
- New chat messages
- User join/leave events
- Nickname changes
Users must grant browser notification permissions and enable the setting in the chat header.
The server maintains canvas state in the following structure:
{
columns: number, // Canvas width in characters
rows: number, // Canvas height in characters
data: Uint16Array, // Character/attribute data
iceColours: boolean, // Extended color palette enabled
letterSpacing: boolean, // 9px font spacing enabled
fontName: string // Selected font name
}Session files are stored in the server working directory:
-
{sessionName}.bin- Binary canvas data (current state) -
{sessionName}.json- Chat history and metadata -
{sessionName} {timestamp}.bin- Timestamped backups
Example:
-
joint.bin- Current canvas state -
joint.json- Current chat history -
joint 2024-01-15T10-30-00.bin- Backup from specific time
Auto-save:
- Saves at configured intervals (default: 30 minutes)
- Creates timestamped backup on each save
- Persists both canvas and chat data
Manual save:
- Triggered by configuration changes
- Runs on server shutdown (graceful)
State restoration:
- Loads existing session on startup
- New users receive current state
- Seamless mid-session joins
Create a systemd service file: /etc/systemd/system/text0wnz.service
[Unit]
Description=teXt0wnz Collaboration Server
After=network.target
[Service]
ExecStart=/usr/bin/node /path/to/text0wnz/src/js/server/main.js 1337
Restart=always
User=youruser
Environment=NODE_ENV=production
Environment=SESSION_KEY=your-secret-key
WorkingDirectory=/path/to/text0wnz/
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=text0wnz
[Install]
WantedBy=multi-user.targetEnable and start:
sudo systemctl daemon-reload
sudo systemctl enable --now text0wnz.serviceCheck status:
sudo systemctl status text0wnz.serviceView logs:
sudo journalctl -u text0wnz.service -fSimple Node.js CLI tool for process management:
# Install
bun i -g forever
# Start server
forever start src/js/server/main.js 1337
# List running processes
forever list
# Stop server
forever stop src/js/server/main.js
# Restart server
forever restart src/js/server/main.jsPros:
- Simple and lightweight
- Easy to set up
Cons:
- Less robust than systemd
- No system-level integration
The server expects SSL certificates in the configured directory (default: /etc/ssl/private):
-
letsencrypt-domain.pem- Certificate file -
letsencrypt-domain.key- Private key file
# Obtain certificates
sudo certbot certonly --standalone -d text.0w.nz
# Copy to server directory
sudo cp /etc/letsencrypt/live/text.0w.nz/fullchain.pem /etc/ssl/private/letsencrypt-domain.pem
sudo cp /etc/letsencrypt/live/text.0w.nz/privkey.pem /etc/ssl/private/letsencrypt-domain.key
# Set permissions
sudo chmod 644 /etc/ssl/private/letsencrypt-domain.pem
sudo chmod 600 /etc/ssl/private/letsencrypt-domain.key
# Start server with SSL
bun server 443 --ssl --ssl-dir /etc/ssl/privateSet up automatic certificate renewal:
# Renewal script with automatic server restart
sudo crontab -e
# Add this line for monthly renewal
0 3 1 * * certbot renew --quiet && systemctl restart text0wnz.serviceAll canvas settings automatically sync across connected clients:
- Canvas size - Resizing affects all connected users
- Font selection - Font changes apply to everyone
- iCE colors - Extended palette toggle syncs
- Letter spacing - 9px font spacing syncs
New users joining mid-session receive the current collaboration state, not the defaults. This ensures consistency across all participants.
Port already in use:
# Check what's using the port
sudo lsof -i :1337
# Kill the process or choose a different port
bun server 8080Permission denied (port < 1024):
# Use sudo for privileged ports
sudo bun server 443 --ssl
# Or use a higher port and proxy with nginx
bun server 8443 --sslSSL certificate errors:
# Verify certificate files exist
ls -la /etc/ssl/private/letsencrypt-domain.*
# Check permissions
sudo chmod 644 /etc/ssl/private/letsencrypt-domain.pem
sudo chmod 600 /etc/ssl/private/letsencrypt-domain.key
# Verify certificate validity
openssl x509 -in /etc/ssl/private/letsencrypt-domain.pem -noout -datesSession not saving:
# Check write permissions in working directory
ls -la session/
# Create session directory if needed
mkdir -p session
chmod 755 session
# Verify with debug mode
bun server 1337 --debugWebSocket connection fails:
- Check firewall rules (ufw, iptables)
- Verify nginx proxy configuration
- Test direct connection (bypass proxy)
- Check browser console for errors
Enable verbose logging:
bun server 1337 --debugDebug mode logs:
- Connection attempts
- Message broadcasts
- Save operations
- User joins/leaves
- Error details
Memory Usage:
- Minimal baseline (Node.js + Express)
- Scales with number of connected users
- Canvas state kept in memory
- Chat history accumulates over time
Network Bandwidth:
- Drawing commands broadcast to all users
- Efficient binary protocol
- Message deduplication by worker
Disk I/O:
- Auto-save at intervals (not continuous)
- Timestamped backups on each save
- Consider SSD for better performance
Optimization Tips:
- Lower save interval for busy sessions
- Limit chat history size
- Use compression in nginx proxy
- Monitor with
toporhtop
-
Use strong session secrets - Set
SESSION_KEYenvironment variable - Enable SSL in production - Use Let's Encrypt certificates
- Run as non-root user - Use systemd with dedicated user account
-
Keep dependencies updated - Regular
bun updateornpm update - Use firewall rules - Restrict access to collaboration port
- Monitor logs - Watch for suspicious activity
- Backup session files - Regular backups of session directory
For production deployments with high traffic:
Load Balancing:
- Not currently supported (sticky sessions required)
- Use nginx for SSL termination only
- Consider horizontal scaling in future versions
Database Integration:
- Currently file-based storage
- Future: Redis/MongoDB for session storage
- Would enable multi-server deployments
Monitoring:
- Use PM2 for process monitoring
- Set up health check endpoints
- Monitor with tools like Grafana/Prometheus
- Webserver Configuration - Nginx and reverse proxy setup
- Client Editor Manual - Visual guide to the Frontend text art editor
- Building and Developing - Development workflow