Chat Migration to Session Server #
Goal: Merge
chat-system,chat-clock, andchat-sotcefrom the/nanosdirectory (currently deployed on Google Cloud) into the existing/session-server(running on DigitalOcean atsession-server.aesthetic.computer).
π Current Architecture #
Google Cloud (Going Away) #
- chat-system β
chat-system.aesthetic.computer - chat-clock β
chat-clock.aesthetic.computer - chat-sotce β
chat.sotce.net
All three are separate VM instances running the same nanos/chat.mjs with different CHAT_INSTANCE environment variables.
DigitalOcean (Keeping) #
- session-server β
session-server.aesthetic.computer(157.245.134.225) - Single Node.js process managed by pm2
- Already handles: WebSocket connections, UDP/WebRTC, Redis pub/sub, Firebase notifications, world state management
π― Migration Strategy #
Phase 1: Import Chat Module as Plugin #
Keep the nanos/chat.mjs code intact (for potential future re-deployment) and import it as a module into session-server.
/session-server
βββ session.mjs # Main server (add chat routing)
βββ chat.mjs # Import from nanos (symlink or copy)
βββ chat-instance.mjs # NEW: Multi-instance chat manager
βββ filter.mjs # Already exists (profanity filter)
βββ redact.mjs # NEW: Copy from nanos
βββ package.json # Add mongodb dependency
Phase 2: DNS Updates (Cloudflare) #
Using credentials from aesthetic-computer-vault/nanos/conductor.env:
CLOUDFLARE_EMAIL=<from-vault>
CLOUDFLARE_API_TOKEN=<from-vault>
Update A records to point all chat subdomains to session-server IP:
chat-system.aesthetic.computerβ157.245.134.225chat-clock.aesthetic.computerβ157.245.134.225chat.sotce.netβ157.245.134.225
π Implementation Plan #
Task 1: Create Chat Module Wrapper #
File: /session-server/chat-manager.mjs
// Chat Manager - Multi-instance chat support for session-server
// Wraps the original nanos/chat.mjs logic for integration
import { WebSocketServer, WebSocket } from "ws";
import { MongoClient } from "mongodb";
import { initializeApp, cert } from "firebase-admin/app";
import { getMessaging } from "firebase-admin/messaging";
import { filter } from "./filter.mjs";
import { redact, unredact } from "./redact.mjs";
const MAX_MESSAGES = 500;
export const chatInstances = {
"chat-system": {
name: "chat-system",
allowedHost: "chat-system.aesthetic.computer",
userInfoEndpoint: "https://aesthetic.us.auth0.com/userinfo",
topic: "mood", // FCM topic
},
"chat-sotce": {
name: "chat-sotce",
allowedHost: "chat.sotce.net",
userInfoEndpoint: "https://sotce.us.auth0.com/userinfo",
topic: "mood",
},
"chat-clock": {
name: "chat-clock",
allowedHost: "chat-clock.aesthetic.computer",
userInfoEndpoint: "https://aesthetic.us.auth0.com/userinfo",
topic: "mood",
},
};
export class ChatInstance {
constructor(config, db) {
this.config = config;
this.db = db;
this.messages = [];
this.connections = {};
this.connectionId = 0;
this.authorizedConnections = {};
this.subsToHandles = {};
this.subsToSubscribers = {};
}
// ... methods from nanos/chat.mjs
}
Task 2: Modify Session Server Entry Point #
File: /session-server/session.mjs
Add host-based routing for chat connections:
// In the wss.on("connection") handler:
wss.on("connection", (ws, req) => {
const host = req.headers.host;
// Route to chat instances based on host
if (chatInstances[host] || isChatHost(host)) {
return chatManager.handleConnection(ws, req, host);
}
// ... existing session-server logic
});
Task 3: Add Required Dependencies #
Update: /session-server/package.json
{
"dependencies": {
"mongodb": "^6.20.0",
"node-fetch": "^3.3.2"
// ... existing deps
}
}
Task 4: Environment Variables #
Update: /session-server/.env
Add from aesthetic-computer-vault/nanos/chat.env:
# Existing (from vault)
REDIS_CONNECTION_STRING=<from-vault>
GCM_FIREBASE_CONFIG_URL=<from-vault>
# New for chat (from vault)
MONGODB_NAME=aesthetic
MONGODB_CONNECTION_STRING=<from-vault>
LOGGER_KEY=<from-vault>
Task 5: Update Cloudflare DNS #
Script: /session-server/update-chat-dns.mjs
// Updates all chat subdomains to point to session-server IP
const CLOUDFLARE_EMAIL = process.env.CLOUDFLARE_EMAIL;
const CLOUDFLARE_API_TOKEN = process.env.CLOUDFLARE_API_TOKEN;
const SESSION_SERVER_IP = "157.245.134.225";
const domains = [
{ subdomain: "chat-system.aesthetic.computer", zone: "aesthetic.computer" },
{ subdomain: "chat-clock.aesthetic.computer", zone: "aesthetic.computer" },
{ subdomain: "chat.sotce.net", zone: "sotce.net" },
];
// Use Cloudflare API to update A records
π§ͺ Testing Plan #
Unit Tests (/session-server/tests/) #
tests/
βββ chat.test.mjs # Chat functionality tests
βββ filter.test.mjs # Profanity filter tests
βββ auth.test.mjs # Auth0 authorization tests
βββ integration.test.mjs # Full integration tests
Test 1: Chat Message Flow #
// tests/chat.test.mjs
import { describe, it, expect } from 'vitest'; // or mocha/chai
describe('Chat Message Flow', () => {
it('should accept authorized chat messages', async () => {
// 1. Connect WebSocket to chat-system endpoint
// 2. Send chat:message with valid token
// 3. Verify message is broadcast to all clients
// 4. Verify message is stored in MongoDB
});
it('should reject unauthorized chat messages', async () => {
// 1. Connect WebSocket
// 2. Send chat:message with invalid/no token
// 3. Verify 'unauthorized' response
});
it('should filter profanity', async () => {
// 1. Send message with profanity
// 2. Verify message text is filtered
});
it('should enforce message length limits', async () => {
// 1. Send message > 128 chars
// 2. Verify 'too-long' response
});
});
Test 2: Multi-Instance Isolation #
describe('Chat Instance Isolation', () => {
it('chat-system messages should not appear in chat-clock', async () => {
// 1. Connect to chat-system, send message
// 2. Connect to chat-clock
// 3. Verify message not in chat-clock history
});
it('sotce.net requires subscription check', async () => {
// 1. Connect to chat.sotce.net
// 2. Send message without subscription
// 3. Verify rejection with subscription required
});
});
Test 3: Push Notifications #
describe('Push Notifications', () => {
it('should send FCM notification on chat-system message', async () => {
// Mock FCM and verify notification payload
});
it('should use clock emoji for chat-clock notifications', async () => {
// Verify clock emoji based on time
});
});
CLI Test Script #
File: /session-server/test-chat.fish
#!/usr/bin/env fish
echo "π§ͺ Testing Chat Integration..."
# Test 1: WebSocket connection
echo "Test 1: WebSocket Connection"
echo '{"type":"ping"}' | websocat -1 wss://chat-system.aesthetic.computer
# Test 2: HTTP health check
echo "Test 2: HTTP Health Check"
curl -s https://chat-system.aesthetic.computer | head -1
# Test 3: Log endpoint (requires auth)
echo "Test 3: Log Endpoint"
curl -s -X POST https://chat-system.aesthetic.computer/log \
-H "Authorization: Bearer $LOGGER_KEY" \
-H "Content-Type: application/json" \
-d '{"text":"Test log message","when":"2024-01-01T00:00:00Z"}'
echo "β
All tests complete"
π Implementation Checklist #
Pre-Migration #
Code Changes #
Testing #
Deployment #
Post-Migration #
π Rollback Plan #
If issues occur:
- Re-run
nanos/conductor.mjsto deploy back to GCP - DNS will automatically update back to GCP IPs
- Session-server can continue running without chat functionality
π° Cost Savings #
- Before: 3x GCP VM instances for chat
- After: 0 GCP instances, chat runs on existing DigitalOcean droplet
- Savings: Full GCP compute bill elimination
πΊοΈ Architecture Diagram #
βββββββββββββββββββββββββββββββββββ
β Cloudflare DNS β
β (Proxied A Records) β
βββββββββββββββ¬ββββββββββββββββββββ
β
βββββββββββββββββββββββββΌββββββββββββββββββββββββ
β β β
βΌ βΌ βΌ
chat-system.ac chat-clock.ac chat.sotce.net
β β β
βββββββββββββββββββββββββΌββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββ
β DigitalOcean Droplet β
β 157.245.134.225 β
β β
β ββββββββββββββββββββββββββββ β
β β session.mjs β β
β β β β
β β βββββββββββββββββββββββ β β
β β β Chat Manager β β β
β β β β β β
β β β β’ chat-system β β β
β β β β’ chat-clock β β β
β β β β’ chat-sotce β β β
β β βββββββββββββββββββββββ β β
β β β β
β β βββββββββββββββββββββββ β β
β β β Existing Session β β β
β β β (worlds, UDP, etc) β β β
β β βββββββββββββββββββββββ β β
β ββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββ
β
βββββββββββββββ΄ββββββββββββββ
β β
βΌ βΌ
ββββββββββββββββ ββββββββββββββββ
β MongoDB β β Redis β
β Atlas β β DigitalOceanβ
ββββββββββββββββ ββββββββββββββββ
π File Structure After Migration #
/session-server
βββ session.mjs # Main entry (updated with chat routing)
βββ chat-manager.mjs # NEW: Multi-instance chat manager
βββ filter.mjs # Profanity filter (existing)
βββ redact.mjs # NEW: Message redaction (from nanos)
βββ package.json # Updated with mongodb dep
βββ .env # Updated with MongoDB connection
βββ update-chat-dns.mjs # NEW: DNS update script
βββ test-chat.fish # NEW: CLI test script
βββ tests/
β βββ chat.test.mjs
β βββ filter.test.mjs
β βββ integration.test.mjs
βββ DEPLOY.md # Updated deployment docs
βββ README.md # Updated with chat info
π Quick Start Commands #
# Local development (all services)
cd /workspaces/aesthetic-computer/session-server
npm run dev
# Test chat locally
curl -s http://localhost:8889/ # Session server
# Chat instances will be on different ports or host-routed
# Deploy to production
./deploy.fish
# Update DNS after deploy
node update-chat-dns.mjs
# Run tests
npm test
Author: Claude (AI Assistant) Created: 2024-11-28 Status: Planning Phase