#!/usr/bin/env node /** * Multi-User Transcription Server (Node.js) * * Much better than PHP for real-time applications: * - Native WebSocket support * - No buffering issues * - Better for long-lived connections * - Lower resource usage * * Install: npm install express ws body-parser * Run: node server.js */ const express = require('express'); const WebSocket = require('ws'); const http = require('http'); const bodyParser = require('body-parser'); const fs = require('fs').promises; const path = require('path'); const crypto = require('crypto'); const app = express(); const server = http.createServer(app); const wss = new WebSocket.Server({ server }); // Configuration const PORT = process.env.PORT || 3000; const DATA_DIR = path.join(__dirname, 'data'); const MAX_TRANSCRIPTIONS = 100; const CLEANUP_INTERVAL = 2 * 60 * 60 * 1000; // 2 hours // Middleware app.use(bodyParser.json()); app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.header('Access-Control-Allow-Headers', 'Content-Type'); if (req.method === 'OPTIONS') { return res.sendStatus(200); } next(); }); // In-memory cache of rooms (reduces file I/O) const rooms = new Map(); // Track WebSocket connections by room const roomConnections = new Map(); // Ensure data directory exists async function ensureDataDir() { try { await fs.mkdir(DATA_DIR, { recursive: true }); } catch (err) { console.error('Error creating data directory:', err); } } // Get room file path function getRoomFile(room) { const hash = crypto.createHash('md5').update(room).digest('hex'); return path.join(DATA_DIR, `room_${hash}.json`); } // Load room data async function loadRoom(room) { if (rooms.has(room)) { return rooms.get(room); } const file = getRoomFile(room); try { const data = await fs.readFile(file, 'utf8'); const roomData = JSON.parse(data); rooms.set(room, roomData); return roomData; } catch (err) { return null; } } // Save room data async function saveRoom(room, roomData) { rooms.set(room, roomData); const file = getRoomFile(room); await fs.writeFile(file, JSON.stringify(roomData, null, 2)); } // Verify passphrase async function verifyPassphrase(room, passphrase) { let roomData = await loadRoom(room); // If room doesn't exist, create it if (!roomData) { const bcrypt = require('bcrypt'); const hash = await bcrypt.hash(passphrase, 10); roomData = { passphrase_hash: hash, created_at: Date.now(), last_activity: Date.now(), transcriptions: [] }; await saveRoom(room, roomData); return true; } // Verify passphrase const bcrypt = require('bcrypt'); return await bcrypt.compare(passphrase, roomData.passphrase_hash); } // Add transcription async function addTranscription(room, transcription) { let roomData = await loadRoom(room); if (!roomData) { throw new Error('Room not found'); } roomData.transcriptions.push(transcription); // Limit transcriptions if (roomData.transcriptions.length > MAX_TRANSCRIPTIONS) { roomData.transcriptions = roomData.transcriptions.slice(-MAX_TRANSCRIPTIONS); } roomData.last_activity = Date.now(); await saveRoom(room, roomData); // Broadcast to all connected clients in this room broadcastToRoom(room, transcription); } // Broadcast to all clients in a room function broadcastToRoom(room, data) { const connections = roomConnections.get(room) || new Set(); const message = JSON.stringify(data); connections.forEach(ws => { if (ws.readyState === WebSocket.OPEN) { ws.send(message); } }); } // Cleanup old rooms async function cleanupOldRooms() { const now = Date.now(); const files = await fs.readdir(DATA_DIR); for (const file of files) { if (!file.startsWith('room_') || !file.endsWith('.json')) { continue; } const filepath = path.join(DATA_DIR, file); try { const data = JSON.parse(await fs.readFile(filepath, 'utf8')); const lastActivity = data.last_activity || data.created_at || 0; if (now - lastActivity > CLEANUP_INTERVAL) { await fs.unlink(filepath); console.log(`Cleaned up old room: ${file}`); } } catch (err) { console.error(`Error processing ${file}:`, err); } } } // Routes // Server info / landing page app.get('/', (req, res) => { res.send(`
Multi-User Server (Node.js)
Generate a unique room with random credentials:
Try this curl command to send a test message:
curl -X POST "http://${req.headers.host}/api/send" \\
-H "Content-Type: application/json" \\
-d '{
"room": "demo",
"passphrase": "demopass",
"user_name": "TestUser",
"text": "Hello from the API!",
"timestamp": "12:34:56"
}'
Then view it at: /display?room=demo