Spaces:
Sleeping
Sleeping
File size: 2,783 Bytes
a4ce88a f40632a a4ce88a f40632a a4ce88a 52f8019 f40632a a4ce88a f40632a a4ce88a f40632a a4ce88a f40632a a4ce88a f40632a a4ce88a f40632a f40c2cc a4ce88a f40632a f07c935 a4ce88a f07c935 a4ce88a f07c935 f40632a a4ce88a f40632a a4ce88a f40632a |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
// 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}`);
});
|