Files
MP-Server/macropad-relay/src/server.ts
T

125 lines
3.6 KiB
TypeScript
Raw Normal View History

2026-01-05 19:46:33 -08:00
// Express + WebSocket Server Setup
import express from 'express';
import fs from 'fs';
import http from 'http';
import WebSocket, { WebSocketServer } from 'ws';
import cors from 'cors';
import helmet from 'helmet';
import rateLimit from 'express-rate-limit';
import path from 'path';
import { config } from './config';
import { logger } from './utils/logger';
import { SessionManager } from './services/SessionManager';
import { ConnectionManager } from './services/ConnectionManager';
import { handleDesktopConnection } from './handlers/desktopHandler';
import { handleWebClientConnection } from './handlers/webClientHandler';
import { createApiProxy } from './handlers/apiProxy';
export function createServer() {
const app = express();
const server = http.createServer(app);
// Initialize managers
const sessionManager = new SessionManager();
const connectionManager = new ConnectionManager();
// Middleware
app.use(helmet({
contentSecurityPolicy: false // Allow inline scripts for login page
}));
app.use(cors());
app.use(express.json());
// Rate limiting
const limiter = rateLimit({
windowMs: config.rateLimitWindowMs,
max: config.rateLimitMax,
message: { error: 'Too many requests, please try again later' }
});
app.use(limiter);
// Static files - check both locations (dev vs production)
const publicPath = fs.existsSync(path.join(__dirname, 'public'))
? path.join(__dirname, 'public')
: path.join(__dirname, '..', 'public');
app.use('/static', express.static(publicPath));
// Health check
app.get('/health', (req, res) => {
const stats = connectionManager.getStats();
res.json({
status: 'ok',
...stats
});
});
// Login page for session
app.get('/:sessionId', (req, res) => {
const session = sessionManager.getSession(req.params.sessionId);
if (!session) {
return res.status(404).send('Session not found');
}
res.sendFile(path.join(publicPath, 'login.html'));
});
// PWA app page (after authentication)
app.get('/:sessionId/app', (req, res) => {
const session = sessionManager.getSession(req.params.sessionId);
if (!session) {
return res.status(404).send('Session not found');
}
res.sendFile(path.join(publicPath, 'app.html'));
});
// API proxy routes
const apiProxy = createApiProxy(connectionManager, sessionManager);
app.all('/:sessionId/api/*', apiProxy);
// WebSocket server
const wss = new WebSocketServer({ noServer: true });
// Handle HTTP upgrade for WebSocket
server.on('upgrade', (request, socket, head) => {
const url = new URL(request.url || '', `http://${request.headers.host}`);
const pathname = url.pathname;
// Desktop connection: /desktop
if (pathname === '/desktop') {
wss.handleUpgrade(request, socket, head, (ws) => {
handleDesktopConnection(ws, connectionManager, sessionManager);
});
return;
}
// Web client connection: /:sessionId/ws
const webClientMatch = pathname.match(/^\/([a-zA-Z0-9]+)\/ws$/);
if (webClientMatch) {
const sessionId = webClientMatch[1];
wss.handleUpgrade(request, socket, head, (ws) => {
handleWebClientConnection(ws, sessionId, connectionManager, sessionManager);
});
return;
}
// Invalid path
socket.destroy();
});
// Graceful shutdown
const shutdown = () => {
logger.info('Shutting down...');
connectionManager.shutdown();
server.close(() => {
logger.info('Server closed');
process.exit(0);
});
};
process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);
return { app, server, sessionManager, connectionManager };
}