const express = require('express');
const http = require('http');
const https = require('https');
const { Server } = require('socket.io');
const cors = require('cors');
const path = require('path');
const fs = require('fs');
const config = require('./config');
// Ensure upload directories exist
[config.contentDir, config.screenshotsDir].forEach(dir => {
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
});
const app = express();
const { trustedProxies } = require('./config/cloudflareIps');
const { getClientIp } = require('./services/activity');
// Trust loopback / link-local / unique-local (local dev, LAN reverse proxies)
// and Cloudflare's published edge ranges. With this list, req.ip resolves to
// the original client when fronted by Cloudflare; X-Forwarded-For from any
// non-trusted source is ignored, so the value can't be spoofed.
app.set('trust proxy', trustedProxies);
// Determine if SSL certs are available
const hasSsl = fs.existsSync(config.sslCert) && fs.existsSync(config.sslKey);
let server;
if (hasSsl) {
const sslOptions = {
cert: fs.readFileSync(config.sslCert),
key: fs.readFileSync(config.sslKey),
};
server = https.createServer(sslOptions, app);
} else {
server = http.createServer(app);
}
// Socket.IO CORS is checked via the same corsOriginCheck function defined below
// (after config is loaded). Hoisted into a closure so we can reference it before
// the function is defined — at first connection time, corsOriginCheck exists.
const io = new Server(server, {
cors: {
origin: (origin, cb) => corsOriginCheck(origin, cb),
credentials: true,
},
maxHttpBufferSize: 10 * 1024 * 1024, // 10MB for screenshot uploads
pingInterval: config.pingInterval,
pingTimeout: config.pingTimeout,
});
// Middleware
const helmet = require('helmet');
// CSP applies to the dashboard / app pages only. Widget and kiosk renders are
// publicly accessed by devices and intentionally use inline scripts/styles —
// they're served from /api/widgets/:id/render and /api/kiosk/:id/render and
// skip the CSP layer below via path-based opt-out.
//
// scriptSrc 'self' blocks \n';
// Inject right before the debug-overlay.js script tag. If for any reason
// the tag isn't present (e.g. file edited out), fall back to injecting
// before so the flag still lands.
let modified;
if (html.indexOf('