// リポジトリ構成と実行手順(ローカルで確認する手順) // 1) ディレクトリ作成 // chat-app/ // ├─ server.js // └─ public/ // └─ index.html // 2) server のセットアップ // npm init -y // npm install express ws // 3) このファイルの server.js を chat-app/server.js に保存 // public/index.html は下の "// === public/index.html ===" 以下を保存 // 4) 実行: // node server.js // 5) ブラウザで http://localhost:3000 を開く // 注意: 簡単のためにファイル・メッセージはメモリで管理します。 // 本番で使う場合は認証、持続化(DB)、XSS対策、CSRF、スケーリング(複数プロセス)などを追加してください。 // === server.js === const express = require('express'); const http = require('http'); const WebSocket = require('ws'); const path = require('path'); const app = express(); const server = http.createServer(app); const wss = new WebSocket.Server({ server }); // serve static files from public/ app.use(express.static(path.join(__dirname, 'public'))); // In-memory message history (keeps last 200 messages) const history = []; function broadcastJSON(obj, exceptSocket = null) { const raw = JSON.stringify(obj); wss.clients.forEach((client) => { if (client.readyState === WebSocket.OPEN && client !== exceptSocket) { client.send(raw); } }); } wss.on('connection', (ws, req) => { // Send existing history to newly connected client ws.send(JSON.stringify({ type: 'history', data: history })); ws.on('message', (msg) => { let parsed; try { parsed = JSON.parse(msg); } catch (e) { console.warn('invalid json', e); return; } if (parsed && parsed.type === 'message') { const m = { id: Date.now() + Math.random().toString(36).slice(2, 9), name: String(parsed.name || '名無し'), text: String(parsed.text || ''), ts: new Date().toISOString(), }; history.push(m); if (history.length > 200) history.shift(); broadcastJSON({ type: 'message', data: m }); } if (parsed && parsed.type === 'ping') { ws.send(JSON.stringify({ type: 'pong' })); } }); ws.on('close', () => { // no-op for now }); }); const PORT = process.env.PORT || 3000; server.listen(PORT, () => { console.log(`Server listening on http://localhost:${PORT}`); }); // === public/index.html === // Save this file to chat-app/public/index.html /*