- Introduction
- Server Architecture
- Key Features
- Rate Limiting System
- Room Management
- User Experience Features
- Bot Development Guide
- Installation and Setup
- Security Features
- Performance Optimizations
Talkomatic Classic is an open-source real-time chat platform that revives the original 1973 PLATO system chatroom experience. The unique feature of Talkomatic is that users can see messages appear letter-by-letter as they are being typed, allowing for a more immediate and interactive conversation experience.
Key characteristics:
- Each participant has their own section of the screen
- Messages appear in real-time as they're typed
- Up to five users per room
- Three room types: public, semi-private (with access code), and private
- No registration required
- Modern implementation of a classic interface
The server is built on Node.js using Express for HTTP endpoints and Socket.IO for real-time communication. It employs an event-driven architecture with several core components:
- Express Server - Handles HTTP requests, serves static files, and provides API endpoints
- Socket.IO Server - Manages WebSocket connections for real-time updates
- Room Management System - Handles creation, joining, and administration of chat rooms
- Message Processing System - Processes and relays real-time typing updates
- Rate Limiting System - Ensures system stability and fair resource allocation
- Security Layer - Implements protections against abuse and attacks
Talkomatic's signature feature is showing messages as they are typed, character by character. This is implemented through a differential update system:
// Examples of different diff types
// 1. Full replace - completely replace current message
{
type: 'full-replace',
text: 'Hello, world!'
}
// 2. Add - insert text at a specific position
{
type: 'add',
index: 7,
text: 'beautiful '
}
// 3. Delete - remove text at a specific position
{
type: 'delete',
index: 7,
count: 5
}
// 4. Replace - replace text at a specific position
{
type: 'replace',
index: 0,
text: 'Greetings'
}To optimize performance, multiple updates are batched and processed at intervals:
// Batch processing occurs every 20ms (50 times per second)
CONFIG.TIMING.BATCH_PROCESSING_INTERVAL = 20;The server implements multiple rate limiting mechanisms to ensure stability and fair resource allocation.
Controls the frequency of message updates:
const chatUpdateLimiter = new RateLimiterMemory({
points: CONFIG.LIMITS.CHAT_UPDATE_RATE_LIMIT, // 300 points
duration: 5, // 5 second window
blockDuration: 2, // Block for 2 seconds if exceeded
});- Maximum throughput: 60 operations per second (300 ÷ 5)
- Recommended usage: Stay below 40 updates per second
- Batching: Updates are collected and processed every 20ms
- Point consumption: Based on batch size:
const pointsToConsume = Math.min( 1 + Math.floor(pendingData.diffs.length / 10), 2 );
Limits general socket operations:
const socketRateLimiter = new RateLimiterMemory({
points: CONFIG.LIMITS.SOCKET_MAX_REQUESTS_PER_WINDOW, // 50 points
duration: CONFIG.LIMITS.SOCKET_MAX_REQUESTS_WINDOW, // 1 second window
blockDuration: 5, // Block for 5 seconds if exceeded
});- Maximum throughput: 50 operations per second
- Exempt operations:
["error", "connect", "disconnect", "disconnecting", "typing", "get rooms", "get room state"] - Recommended usage: Stay below 30 operations per second
Controls typing indicator events:
const typingLimiter = new RateLimiterMemory({
points: CONFIG.LIMITS.TYPING_RATE_LIMIT, // 60 points
duration: 1, // 1 second window
});- Maximum throughput: 60 typing events per second
- Behavior when exceeded: Events are silently dropped
Prevents connection flooding:
const ipRateLimiter = new RateLimiterMemory({
points: 20,
duration: 15,
blockDuration: 30,
});- New connections: Maximum 20 new connections per 15 seconds
- Total connections: Maximum 30 simultaneous connections per IP
- Block duration: 30 seconds when exceeded
Limits requests to HTTP API endpoints:
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 2000, // 2000 requests per window
});- Maximum throughput: ~2.2 requests per second
When approaching rate limits, the system:
- Reduces batch sizes from 50 to 10 updates
- Sends warning notifications for significant rate limit events
- Implements graduated throttling rather than immediate blocking
A circuit breaker protects against system overload:
const chatCircuitState = {
failures: 0,
lastFailure: 0,
isOpen: false,
threshold: 50,
resetTimeout: 15000,
};- Opens after 50 consecutive failures
- Resets after 15 seconds
- Prevents system degradation during high load
- Public: Visible to all users, no access restrictions
- Semi-Private: Visible in the lobby but requires a 6-digit access code
- Private: Not visible in the lobby, join by direct link/code
// Create a new room
socket.emit("create room", {
name: "My Chat Room", // Room name
type: "public", // 'public', 'semi-private', or 'private'
layout: "horizontal", // 'horizontal' or 'vertical'
accessCode: "123456", // Required for semi-private rooms
});// Join an existing room
socket.emit("join room", {
roomId: "123456", // 6-digit room ID
accessCode: "123456", // Required for semi-private rooms
});- Rooms are created with a unique 6-digit ID
- Empty rooms are automatically deleted after inactivity (default: 30 seconds)
- Users can be voted out of a room (majority vote)
- Configuration limits rooms to 5 concurrent users per room
The server monitors user activity and manages AFK (Away From Keyboard) status:
CONFIG.LIMITS.MAX_AFK_TIME = 180000; // 3 minutes until AFK timeout
CONFIG.TIMING.AFK_WARNING_TIME = 150000; // 2.5 minutes - warn before kick- Users inactive for 2.5 minutes receive a warning
- After 3 minutes of inactivity, users are returned to the lobby
- Any user activity (typing, sending messages) resets the AFK timer
socket.emit("typing", { isTyping: true }); // Start typing indicator
socket.emit("typing", { isTyping: false }); // Stop typing indicator- Typing indicators automatically expire after 2 seconds of inactivity
- Rate limited to 60 events per second globally
const io = require("socket.io-client");
const socket = io("https://classic.talkomatic.co", {
transports: ["websocket"],
reconnection: true,
reconnectionDelay: 1000,
reconnectionDelayMax: 5000,
});
// Sign in to the system
socket.emit("join lobby", {
username: "MyBot",
location: "Bot Server",
});
socket.on("signin status", (data) => {
if (data.isSignedIn) {
console.log(`Signed in as ${data.username} with ID ${data.userId}`);
}
});For bots that display information (time, status, data):
// Update at 10 fps (well within rate limits)
const UPDATE_INTERVAL = 100; // milliseconds
let lastMessage = "";
setInterval(() => {
const timeString = new Date().toLocaleTimeString();
// Only send if content changed (optimization)
if (timeString !== lastMessage) {
lastMessage = timeString;
// Use full-replace instead of character-by-character updates
socket.emit("chat update", {
diff: {
type: "full-replace",
text: `Current time: ${timeString}`,
},
});
}
}, UPDATE_INTERVAL);| Operation Type | Maximum Rate | Recommended Rate | Notes |
|---|---|---|---|
| Chat Updates | 60/second | 10-20/second | Use batch updates |
| Socket Operations | 50/second | 25/second | Cache when possible |
| Typing Events | 60/second | 5/second | Limit frequency |
| API Calls | 2.2/second | 1/second | Use for non-real-time operations |
Implement your own rate limiting to avoid server-side rejections:
const UPDATE_INTERVAL = 100; // Min ms between updates
let lastUpdateTime = 0;
let updateQueue = [];
function sendUpdate(content) {
const now = Date.now();
if (now - lastUpdateTime < UPDATE_INTERVAL) {
// Queue the update for later
updateQueue.push(content);
if (updateQueue.length === 1) {
setTimeout(processQueue, UPDATE_INTERVAL - (now - lastUpdateTime));
}
return;
}
lastUpdateTime = now;
socket.emit("chat update", { diff: content });
}
function processQueue() {
if (updateQueue.length === 0) return;
// Send the next update
const content = updateQueue.shift();
lastUpdateTime = Date.now();
socket.emit("chat update", { diff: content });
// Schedule next update if queue isn't empty
if (updateQueue.length > 0) {
setTimeout(processQueue, UPDATE_INTERVAL);
}
}Listen for rate limit warnings and errors:
// Track current update interval
let currentUpdateInterval = 100;
socket.on("error", (error) => {
if (error.error && error.error.code === "RATE_LIMITED") {
// Implement exponential backoff
currentUpdateInterval *= 1.5;
console.warn(`Rate limited. New interval: ${currentUpdateInterval}ms`);
setTimeout(() => {
// Gradually return to normal after some time
currentUpdateInterval = Math.max(100, currentUpdateInterval * 0.9);
}, 10000);
}
});
socket.on("message", (message) => {
if (message.type === "warning" && message.text.includes("Slow down")) {
// Reduce frequency preemptively
currentUpdateInterval *= 1.2;
}
});Bots should listen for and respond to AFK warnings to prevent being kicked:
socket.on("afk warning", (data) => {
console.log(
`AFK Warning: ${data.message}, ${data.secondsRemaining}s remaining`
);
// Send a response to show the bot is still active
socket.emit("afk response", { active: true });
// Also send some activity to reset the timer
socket.emit("typing", { isTyping: true });
setTimeout(() => {
socket.emit("typing", { isTyping: false });
}, 500);
});
socket.on("afk timeout", (data) => {
console.log(`AFK Timeout: ${data.message}`);
// Handle reconnection if needed
});- Use full-replace updates instead of character-by-character changes
- Implement adaptive rates based on system feedback
- Batch multiple small changes into single updates
- Maintain persistent connections rather than reconnecting frequently
- Target 10-15 fps for real-time display bots (plenty below the 60/sec limit)
- Implement client-side rate limiting before server-side limits are reached
- Handle AFK warnings to prevent automatic disconnection
- Cache room and user data to reduce unnecessary requests
- Use exponential backoff when encountering rate limits
- Monitor connectivity and implement graceful reconnection
- Node.js 14.x or higher
- NPM 6.x or higher
# Clone the repository
git clone https://github.com/yourusername/talkomatic-classic.git
# Navigate to the project directory
cd talkomatic-classic
# Install dependencies
npm install
# Start the server
npm startCreate a .env file in the root directory with the following variables:
PORT=3000
NODE_ENV=development
SESSION_SECRET=your_session_secret
CLOUDFLARE_ENABLED=false
TALKOMATIC_API_KEY=your_api_key
The server implements several security measures:
- Helmet.js Integration - Configures secure HTTP headers
- Content Security Policy - Restricts resource loading to trusted sources
- Rate Limiting - Prevents abuse and DoS attacks
- Input Validation - Sanitizes all user inputs
- Word Filtering - Optional filtering of offensive content
- XSS Protection - Prevents cross-site scripting attacks
- Session Security - Secure cookies and session management
- Circuit Breaker Pattern - Prevents cascading failures under high load
The server includes several optimizations for performance:
- Message Batching - Processes multiple updates in batches
- Caching - Uses in-memory caches for frequently accessed data
- Debounced Saving - Coalesces multiple save operations
- Staggered Cleanup - Distributes cleanup tasks to minimize impact
- Early Exit Strategies - Avoids unnecessary processing
- Memory Monitoring - Tracks and manages memory usage
- Optimized Socket.IO Configuration - Tuned for WebSocket performance
The server actively monitors memory usage:
setInterval(() => {
try {
const memoryUsage = process.memoryUsage();
const heapUsedPercentage = Math.round(
(memoryUsage.heapUsed / memoryUsage.heapTotal) * 100
);
// Only log when usage is concerning
if (heapUsedPercentage > 85) {
console.warn(`MEMORY WARNING: Heap usage at ${heapUsedPercentage}%`);
// Emergency cleanup of large message buffers
if (heapUsedPercentage > 90) {
console.warn(
"EMERGENCY MEMORY CLEANUP: Clearing large message buffers"
);
// Truncate large messages
for (const [userId, message] of userMessageBuffers.entries()) {
if (message.length > 1000) {
userMessageBuffers.set(
userId,
message.substring(0, 1000) +
"... [truncated for system stability]"
);
}
}
// Purge caches
normalizeCache.clear();
apiCache.clear();
}
}
} catch (err) {
console.error("Error in server monitor:", err);
}
}, 120000); // Every 2 minutesThis documentation is intended to provide a comprehensive overview of the Talkomatic Classic server implementation. For more detailed information or to contribute to the project, please visit https://github.com/mohdyahyamahmodi/talkomatic-classic.