timer / server.js
krishgokul92's picture
Update server.js
a4ce88a verified
// server.js — HF Spaces / Docker ready (PORT + 0.0.0.0)
// - Robust room/admin/client stats
// - Commands: start/stop/reset/blackout
// - NTP-style sync (sync:ping -> sync:pong)
const path = require('path');
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = new Server(server, {
pingInterval: 10000,
pingTimeout: 5000,
cors: { origin: true }
});
// Serve static files
app.use(express.static(path.join(__dirname, 'public')));
// Single entry page (index.html handles role=admin|client)
app.get('/', (req, res) => res.sendFile(path.join(__dirname, 'public', 'index.html')));
// HF Spaces / Docker
const HOST = '0.0.0.0';
const PORT = process.env.PORT || 7860;
// ---------- Helpers ----------
function normRoom(x) { return String(x || 'default').trim().toLowerCase(); }
function normRole(x) { return String(x || 'client').trim().toLowerCase(); }
// Emit counts for a room using socket.data.role
function emitStats(room) {
const ids = io.sockets.adapter.rooms.get(room) || new Set();
let numAdmins = 0, numClients = 0;
for (const id of ids) {
const s = io.sockets.sockets.get(id);
if (!s) continue;
(s.data?.role === 'admin') ? numAdmins++ : numClients++;
}
io.to(room).emit('stats', { numAdmins, numClients });
}
// ---------- Socket.io ----------
io.on('connection', (socket) => {
const room = normRoom(socket.handshake.query.room);
const role = normRole(socket.handshake.query.role);
socket.data.room = room;
socket.data.role = role;
socket.join(room);
emitStats(room);
// Optional: let any client/admin force a refresh
socket.on('stats:refresh', () => emitStats(room));
// NTP-like sync
socket.on('sync:ping', (msg = {}) => {
const t1 = Date.now();
socket.emit('sync:pong', { t0: msg.t0, t1, t2: Date.now() });
});
// Admin commands
socket.on('admin:start', ({ delayMs = 3000, label = '' } = {}) => {
if (socket.data.role !== 'admin') return;
const startAt = Date.now() + Math.max(500, Number(delayMs));
io.to(room).emit('cmd', { type: 'start', startAt, label });
});
socket.on('admin:stop', () => {
if (socket.data.role !== 'admin') return;
io.to(room).emit('cmd', { type: 'stop' });
});
socket.on('admin:reset', () => {
if (socket.data.role !== 'admin') return;
io.to(room).emit('cmd', { type: 'reset' });
});
socket.on('admin:blackout', ({ on = true } = {}) => {
if (socket.data.role !== 'admin') return;
io.to(room).emit('cmd', { type: 'blackout', on: !!on });
});
socket.on('disconnect', () => emitStats(room));
});
server.listen(PORT, HOST, () => {
console.log(`Timer server on http://${HOST}:${PORT}`);
});