feat: heleo#
This commit is contained in:
75
server.js
75
server.js
@@ -1,46 +1,79 @@
|
||||
const express = require('express');
|
||||
const http = require('http');
|
||||
const { Server } = require('socket.io');
|
||||
const basicAuth = require('express-basic-auth');
|
||||
const cookieParser = require('cookie-parser');
|
||||
const { db, init } = require('./database');
|
||||
|
||||
const app = express();
|
||||
const server = http.createServer(app);
|
||||
const io = new Server(server);
|
||||
const io = new Server(server, {
|
||||
cookie: true
|
||||
});
|
||||
|
||||
// Initialize DB
|
||||
init();
|
||||
|
||||
app.use(express.static('public'));
|
||||
app.use(cookieParser());
|
||||
|
||||
// Rate limit helper
|
||||
function canClick(name) {
|
||||
const row = db.prepare('SELECT timestamp FROM logs WHERE name = ? ORDER BY timestamp DESC LIMIT 1').get(name);
|
||||
if (!row) return true;
|
||||
// Rate limit helper using IP and Cookie
|
||||
// Map: ip -> timestamp
|
||||
const ipLimits = new Map();
|
||||
|
||||
const lastClickTime = new Date(row.timestamp).getTime();
|
||||
function canClick(req, socket) {
|
||||
const ip = req ? req.ip : socket.handshake.address;
|
||||
|
||||
// Check IP limit
|
||||
const lastClickTime = ipLimits.get(ip);
|
||||
const now = Date.now();
|
||||
// 30 seconds cooldown
|
||||
return (now - lastClickTime) >= 30000;
|
||||
|
||||
if (lastClickTime && (now - lastClickTime) < 30000) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function updateLimit(req, socket) {
|
||||
const ip = req ? req.ip : socket.handshake.address;
|
||||
ipLimits.set(ip, Date.now());
|
||||
}
|
||||
|
||||
function getGlobalCount() {
|
||||
return db.prepare('SELECT count FROM global_count WHERE id = 1').get().count;
|
||||
}
|
||||
|
||||
// Security: Basic Auth for Admin
|
||||
app.use('/admin.html', basicAuth({
|
||||
users: { 'admin': 'admin123' }, // In a real app, use env vars!
|
||||
challenge: true
|
||||
}));
|
||||
|
||||
app.use(express.static('public'));
|
||||
|
||||
// Input Sanitization
|
||||
function sanitize(str) {
|
||||
if (!str) return '';
|
||||
return str.replace(/</g, "<").replace(/>/g, ">");
|
||||
}
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
// Send current count immediately
|
||||
socket.emit('update', { count: getGlobalCount() });
|
||||
|
||||
// Handle increment
|
||||
socket.on('increment', (data) => {
|
||||
const { name, quote } = data;
|
||||
let { name, quote } = data;
|
||||
|
||||
name = sanitize(name);
|
||||
quote = sanitize(quote);
|
||||
|
||||
if (!name || name.trim() === "") {
|
||||
socket.emit('error', 'Name is required');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!canClick(name)) {
|
||||
if (!canClick(null, socket)) {
|
||||
socket.emit('error', 'You must wait 30 seconds between clicks.');
|
||||
return;
|
||||
}
|
||||
@@ -54,12 +87,12 @@ io.on('connection', (socket) => {
|
||||
|
||||
const newCount = incrementTx();
|
||||
|
||||
// Update rate limit
|
||||
updateLimit(null, socket);
|
||||
|
||||
// Broadcast new count to EVERYONE
|
||||
io.emit('update', { count: newCount });
|
||||
|
||||
// Broadcast new log entry to admins (in a real app, we'd check auth, here we broadcast to "admin" room or just all for simplicity, but let's be nice and use a room if we were distinguishing. For now, I'll just emit a 'new_log' event globally or maybe just to the sender?
|
||||
// The admin panel "gets logs". I should probably emit 'new_log' to everyone on the admin page.
|
||||
// Let's assume admin listeners join a room 'admin'.
|
||||
io.to('admin').emit('new_log', {
|
||||
name,
|
||||
quote,
|
||||
@@ -70,9 +103,19 @@ io.on('connection', (socket) => {
|
||||
// Admin room join
|
||||
socket.on('join_admin', () => {
|
||||
socket.join('admin');
|
||||
// Send recent logs
|
||||
// Send recent logs and simplified stats for chart
|
||||
const logs = db.prepare('SELECT * FROM logs ORDER BY timestamp DESC LIMIT 50').all();
|
||||
socket.emit('admin_data', { logs });
|
||||
|
||||
// Stats: Last 24 hours logs for chart
|
||||
const statsCus = db.prepare(`
|
||||
SELECT strftime('%H:00', timestamp) as hour, COUNT(*) as count
|
||||
FROM logs
|
||||
WHERE timestamp >= datetime('now', '-24 hours')
|
||||
GROUP BY hour
|
||||
ORDER BY hour
|
||||
`).all();
|
||||
|
||||
socket.emit('admin_data', { logs, stats: statsCus });
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user