krishgokul92 commited on
Commit
ef3c306
·
verified ·
1 Parent(s): ab5b85b

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +100 -92
server.js CHANGED
@@ -1,92 +1,100 @@
1
- // server.js — HF Spaces / Docker ready (PORT + 0.0.0.0)
2
- // - Robust room/admin/client stats
3
- // - Commands: start/stop/reset/blackout
4
- // - NTP-style sync (sync:ping -> sync:pong)
5
-
6
- const path = require('path');
7
- const express = require('express');
8
- const http = require('http');
9
- const { Server } = require('socket.io');
10
-
11
- const app = express();
12
- const server = http.createServer(app);
13
- const io = new Server(server, {
14
- pingInterval: 10000,
15
- pingTimeout: 5000,
16
- cors: { origin: true }
17
- });
18
-
19
- // Serve static files
20
- app.use(express.static(path.join(__dirname, 'public')));
21
-
22
- // Single entry page (index.html handles role=admin|client)
23
- app.get('/', (req, res) => res.sendFile(path.join(__dirname, 'public', 'index.html')));
24
-
25
- // HF Spaces / Docker
26
- const HOST = '0.0.0.0';
27
- const PORT = process.env.PORT || 7860;
28
-
29
- // ---------- Helpers ----------
30
- function normRoom(x) { return String(x || 'default').trim().toLowerCase(); }
31
- function normRole(x) { return String(x || 'client').trim().toLowerCase(); }
32
-
33
- // Emit counts for a room using socket.data.role
34
- function emitStats(room) {
35
- const ids = io.sockets.adapter.rooms.get(room) || new Set();
36
- let numAdmins = 0, numClients = 0;
37
- for (const id of ids) {
38
- const s = io.sockets.sockets.get(id);
39
- if (!s) continue;
40
- (s.data?.role === 'admin') ? numAdmins++ : numClients++;
41
- }
42
- io.to(room).emit('stats', { numAdmins, numClients });
43
- }
44
-
45
- // ---------- Socket.io ----------
46
- io.on('connection', (socket) => {
47
- const room = normRoom(socket.handshake.query.room);
48
- const role = normRole(socket.handshake.query.role);
49
-
50
- socket.data.room = room;
51
- socket.data.role = role;
52
-
53
- socket.join(room);
54
- emitStats(room);
55
-
56
- // Optional: let any client/admin force a refresh
57
- socket.on('stats:refresh', () => emitStats(room));
58
-
59
- // NTP-like sync
60
- socket.on('sync:ping', (msg = {}) => {
61
- const t1 = Date.now();
62
- socket.emit('sync:pong', { t0: msg.t0, t1, t2: Date.now() });
63
- });
64
-
65
- // Admin commands
66
- socket.on('admin:start', ({ delayMs = 3000, label = '' } = {}) => {
67
- if (socket.data.role !== 'admin') return;
68
- const startAt = Date.now() + Math.max(500, Number(delayMs));
69
- io.to(room).emit('cmd', { type: 'start', startAt, label });
70
- });
71
-
72
- socket.on('admin:stop', () => {
73
- if (socket.data.role !== 'admin') return;
74
- io.to(room).emit('cmd', { type: 'stop' });
75
- });
76
-
77
- socket.on('admin:reset', () => {
78
- if (socket.data.role !== 'admin') return;
79
- io.to(room).emit('cmd', { type: 'reset' });
80
- });
81
-
82
- socket.on('admin:blackout', ({ on = true } = {}) => {
83
- if (socket.data.role !== 'admin') return;
84
- io.to(room).emit('cmd', { type: 'blackout', on: !!on });
85
- });
86
-
87
- socket.on('disconnect', () => emitStats(room));
88
- });
89
-
90
- server.listen(PORT, HOST, () => {
91
- console.log(`Timer server on http://${HOST}:${PORT}`);
92
- });
 
 
 
 
 
 
 
 
 
1
+ // server.js — Pure async (monotonic) sync, no wall clock.
2
+ // - Uses perf_hooks.performance.now() on server
3
+ // - Robust room/admin/client stats
4
+ // - Commands: start/stop/reset/blackout
5
+ // - Redundant START + preSync burst for tight simultaneity
6
+ // - Docker/Spaces-ready (binds 0.0.0.0, uses PORT)
7
+
8
+ const path = require('path');
9
+ const express = require('express');
10
+ const http = require('http');
11
+ const { Server } = require('socket.io');
12
+ const { performance } = require('perf_hooks');
13
+
14
+ const app = express();
15
+ const server = http.createServer(app);
16
+ const io = new Server(server, {
17
+ pingInterval: 10000,
18
+ pingTimeout: 5000,
19
+ cors: { origin: true }
20
+ });
21
+
22
+ // Serve static files
23
+ app.use(express.static(path.join(__dirname, 'public')));
24
+
25
+ // Single entry (merged Admin+Client UI lives in public/index.html)
26
+ app.get('/', (req, res) => res.sendFile(path.join(__dirname, 'public', 'index.html')));
27
+
28
+ const HOST = '0.0.0.0';
29
+ const PORT = process.env.PORT || 7860;
30
+
31
+ // ---------- Helpers ----------
32
+ function normRoom(x) { return String(x || 'default').trim().toLowerCase(); }
33
+ function normRole(x) { return String(x || 'client').trim().toLowerCase(); }
34
+
35
+ function emitStats(room) {
36
+ const ids = io.sockets.adapter.rooms.get(room) || new Set();
37
+ let numAdmins = 0, numClients = 0;
38
+ for (const id of ids) {
39
+ const s = io.sockets.sockets.get(id);
40
+ if (!s) continue;
41
+ (s.data?.role === 'admin') ? numAdmins++ : numClients++;
42
+ }
43
+ io.to(room).emit('stats', { numAdmins, numClients });
44
+ }
45
+
46
+ // ---------- Socket.io ----------
47
+ io.on('connection', (socket) => {
48
+ const room = normRoom(socket.handshake.query.room);
49
+ const role = normRole(socket.handshake.query.role);
50
+
51
+ socket.data.room = room;
52
+ socket.data.role = role;
53
+
54
+ socket.join(room);
55
+ emitStats(room);
56
+
57
+ socket.on('stats:refresh', () => emitStats(room));
58
+
59
+ // SYNC: server sends its monotonic time; client samples arrival with its own monotonic time
60
+ socket.on('sync:ping', () => {
61
+ const tS = performance.now(); // server monotonic ms
62
+ socket.emit('sync:pong', { tS });
63
+ });
64
+
65
+ // Admin commands
66
+ socket.on('admin:start', ({ delayMs = 3000, label = '' } = {}) => {
67
+ if (socket.data.role !== 'admin') return;
68
+ const startServerPerf = performance.now() + Math.max(800, Number(delayMs)); // future server perf time (ms)
69
+ const payload = { type: 'start', startServerPerf, label };
70
+
71
+ // Redundant emits for resilience
72
+ io.to(room).emit('cmd', payload);
73
+ setTimeout(() => io.to(room).emit('cmd', payload), 250);
74
+ setTimeout(() => io.to(room).emit('cmd', payload), 500);
75
+
76
+ // Ask clients to enter high-rate sync mode until T0
77
+ io.to(room).emit('cmd', { type: 'preSync', untilServerPerf: startServerPerf });
78
+ });
79
+
80
+ socket.on('admin:stop', () => {
81
+ if (socket.data.role !== 'admin') return;
82
+ io.to(room).emit('cmd', { type: 'stop' });
83
+ });
84
+
85
+ socket.on('admin:reset', () => {
86
+ if (socket.data.role !== 'admin') return;
87
+ io.to(room).emit('cmd', { type: 'reset' });
88
+ });
89
+
90
+ socket.on('admin:blackout', ({ on = true } = {}) => {
91
+ if (socket.data.role !== 'admin') return;
92
+ io.to(room).emit('cmd', { type: 'blackout', on: !!on });
93
+ });
94
+
95
+ socket.on('disconnect', () => emitStats(room));
96
+ });
97
+
98
+ server.listen(PORT, HOST, () => {
99
+ console.log(`Timer server listening on http://${HOST}:${PORT}`);
100
+ });