debug-time-machine-cli
Version:
π Debug Time Machine CLI - μμ μλνλ React λλ²κΉ λꡬ
278 lines (238 loc) β’ 7.39 kB
JavaScript
// λ²λ€λ λ°±μλ μλ² (apps/backendλ₯Ό κΈ°λ°μΌλ‘ ν¨)
const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const cors = require('cors');
const helmet = require('helmet');
const compression = require('compression');
const path = require('path');
const app = express();
const port = process.env.PORT || 4000;
// λ―Έλ€μ¨μ΄ μ€μ
app.use(helmet());
app.use(compression());
app.use(cors({
origin: true,
credentials: true
}));
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// ν΄λΌμ΄μΈνΈ μ°κ²° κ΄λ¦¬
const clients = new Map();
// Health check μλν¬μΈνΈ
app.get('/health', (req, res) => {
res.json({
status: 'ok',
timestamp: Date.now(),
clients: clients.size,
uptime: process.uptime()
});
});
// Info μλν¬μΈνΈ
app.get('/info', (req, res) => {
res.json({
name: 'Debug Time Machine Backend',
version: '1.0.0',
environment: process.env.NODE_ENV || 'development',
port: port,
clients: clients.size,
uptime: process.uptime()
});
});
// HTTP μλ² μμ±
const server = http.createServer(app);
// WebSocket μλ² μ€μ
const wss = new WebSocket.Server({
server,
path: '/ws'
});
// ν΄λΌμ΄μΈνΈ ID μμ±
function generateClientId() {
return `client_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
}
// λΈλ‘λμΊμ€νΈ ν¨μ
function broadcast(message, excludeClient = null) {
const messageStr = typeof message === 'string' ? message : JSON.stringify(message);
clients.forEach((clientInfo, ws) => {
if (ws !== excludeClient && ws.readyState === WebSocket.OPEN) {
try {
ws.send(messageStr);
} catch (error) {
console.error('λΈλ‘λμΊμ€νΈ μ€λ₯:', error.message);
clients.delete(ws);
}
}
});
}
// WebSocket μ°κ²° μ²λ¦¬
wss.on('connection', (ws, req) => {
const clientId = generateClientId();
const clientIP = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
const clientInfo = {
id: clientId,
ip: clientIP,
connectedAt: new Date(),
lastSeen: new Date(),
userAgent: req.headers['user-agent'],
messagesReceived: 0,
messagesSent: 0
};
clients.set(ws, clientInfo);
console.log(`π ν΄λΌμ΄μΈνΈ μ°κ²°: ${clientId} (${clientIP})`);
console.log(`π μ΄ μ°κ²°λ ν΄λΌμ΄μΈνΈ: ${clients.size}`);
// Welcome λ©μμ§ μ μ‘
const welcomeMessage = {
type: 'CONNECTION',
payload: {
type: 'welcome',
clientId: clientId,
serverTime: Date.now(),
message: 'Debug Time Machineμ μ°κ²°λμμ΅λλ€!'
},
timestamp: Date.now()
};
try {
ws.send(JSON.stringify(welcomeMessage));
clientInfo.messagesSent++;
} catch (error) {
console.error('Welcome λ©μμ§ μ μ‘ μ€ν¨:', error.message);
}
// λ©μμ§ μμ μ²λ¦¬
ws.on('message', (data) => {
try {
const message = JSON.parse(data.toString());
clientInfo.lastSeen = new Date();
clientInfo.messagesReceived++;
console.log(`π¨ [${clientId}] λ©μμ§ μμ : ${message.type}`);
// λ©μμ§ νμ
λ³ μ²λ¦¬
switch (message.type) {
case 'CONNECTION':
console.log(`π€ [${clientId}] μ°κ²° νμΈ:`, message.payload?.type);
break;
case 'USER_ACTION':
console.log(`π [${clientId}] μ¬μ©μ μ‘μ
:`, message.payload?.actionType);
// λ€λ₯Έ ν΄λΌμ΄μΈνΈλ€μκ² λΈλ‘λμΊμ€νΈ
broadcast({
type: 'USER_ACTION_BROADCAST',
payload: message.payload,
from: clientId,
timestamp: Date.now()
}, ws);
break;
case 'STATE_CHANGE':
console.log(`π [${clientId}] μν λ³κ²½:`, message.payload?.componentName);
broadcast({
type: 'STATE_CHANGE_BROADCAST',
payload: message.payload,
from: clientId,
timestamp: Date.now()
}, ws);
break;
case 'ERROR':
console.log(`π΄ [${clientId}] μλ¬:`, message.payload?.message);
broadcast({
type: 'ERROR_BROADCAST',
payload: message.payload,
from: clientId,
timestamp: Date.now()
}, ws);
break;
case 'PING':
// PONG μλ΅
ws.send(JSON.stringify({
type: 'PONG',
payload: {},
timestamp: Date.now()
}));
clientInfo.messagesSent++;
break;
case 'PONG':
console.log(`π [${clientId}] PONG μμ `);
break;
default:
console.log(`β [${clientId}] μ μ μλ λ©μμ§ νμ
: ${message.type}`);
}
} catch (error) {
console.error(`β [${clientId}] λ©μμ§ νμ± μ€λ₯:`, error.message);
console.error('μλ³Έ λ°μ΄ν°:', data.toString());
}
});
// μ°κ²° μ’
λ£ μ²λ¦¬
ws.on('close', (code, reason) => {
clients.delete(ws);
console.log(`π ν΄λΌμ΄μΈνΈ μ°κ²° ν΄μ : ${clientId} (μ½λ: ${code}, μ΄μ : ${reason || 'μμ'})`);
console.log(`π λ¨μ ν΄λΌμ΄μΈνΈ: ${clients.size}`);
// μ°κ²° ν΄μ μλ¦Ό λΈλ‘λμΊμ€νΈ
broadcast({
type: 'CLIENT_DISCONNECTED',
payload: {
clientId: clientId,
timestamp: Date.now()
}
});
});
// μλ¬ μ²λ¦¬
ws.on('error', (error) => {
console.error(`β [${clientId}] WebSocket μ€λ₯:`, error.message);
clients.delete(ws);
});
});
// Ping νμ΄λ¨Έ (30μ΄λ§λ€)
setInterval(() => {
const pingMessage = {
type: 'PING',
payload: {},
timestamp: Date.now()
};
clients.forEach((clientInfo, ws) => {
if (ws.readyState === WebSocket.OPEN) {
try {
ws.send(JSON.stringify(pingMessage));
clientInfo.messagesSent++;
} catch (error) {
console.error('PING μ μ‘ μ€ν¨:', error.message);
clients.delete(ws);
}
} else {
clients.delete(ws);
}
});
if (clients.size > 0) {
console.log(`π PING μ μ‘ (${clients.size}κ° ν΄λΌμ΄μΈνΈ)`);
}
}, 30000);
// μλ² μμ
server.listen(port, () => {
console.log('');
console.log('π Debug Time Machine Backend Server');
console.log(`π Port: ${port}`);
console.log(`π Health: http://localhost:${port}/health`);
console.log(`π WebSocket: ws://localhost:${port}/ws`);
console.log(`π Started: ${new Date().toISOString()}`);
console.log('');
});
// Graceful shutdown
process.on('SIGTERM', () => {
console.log('π SIGTERM μ νΈ μμ , μλ² μ’
λ£ μ€...');
// λͺ¨λ ν΄λΌμ΄μΈνΈμκ² μ’
λ£ μλ¦Ό
broadcast({
type: 'SERVER_SHUTDOWN',
payload: {
message: 'μλ²κ° μ’
λ£λ©λλ€.',
timestamp: Date.now()
}
});
// μ°κ²° μ’
λ£
clients.forEach((_, ws) => {
ws.close(1001, 'Server shutdown');
});
server.close(() => {
console.log('β
μλ²κ° μ μμ μΌλ‘ μ’
λ£λμμ΅λλ€.');
process.exit(0);
});
});
process.on('SIGINT', () => {
console.log('\nπ SIGINT μ νΈ μμ , μλ² μ’
λ£ μ€...');
process.emit('SIGTERM');
});