Monorepo for Aesthetic.Computer aesthetic.computer
core plans chat-migration-to-session-server.md
14 kB

Chat Migration to Session Server #

Goal: Merge chat-system, chat-clock, and chat-sotce from the /nanos directory (currently deployed on Google Cloud) into the existing /session-server (running on DigitalOcean at session-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.225
  • chat-clock.aesthetic.computer β†’ 157.245.134.225
  • chat.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:

  1. Re-run nanos/conductor.mjs to deploy back to GCP
  2. DNS will automatically update back to GCP IPs
  3. 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