Initial commit: SyncWatch Chrome extension + WebSocket server

This commit is contained in:
2026-05-23 22:22:19 -04:00
commit 24d88f4754
8 changed files with 649 additions and 0 deletions

15
server/package.json Normal file
View File

@@ -0,0 +1,15 @@
{
"name": "syncwatch-server",
"version": "1.0.0",
"description": "WebSocket relay server for SyncWatch Chrome extension",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"engines": {
"node": ">=16"
},
"dependencies": {
"ws": "^8.18.0"
}
}

98
server/server.js Normal file
View File

@@ -0,0 +1,98 @@
const { WebSocketServer, WebSocket } = require('ws');
const http = require('http');
const PORT = process.env.PORT || 8080;
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('SyncWatch server running\n');
});
const wss = new WebSocketServer({ server });
// Map<roomCode, Set<WebSocket>>
const rooms = new Map();
function generateCode() {
let code;
do {
code = Math.floor(100000 + Math.random() * 900000).toString();
} while (rooms.has(code));
return code;
}
function broadcast(room, message, exclude) {
const data = JSON.stringify(message);
for (const client of room) {
if (client !== exclude && client.readyState === WebSocket.OPEN) {
client.send(data);
}
}
}
wss.on('connection', (ws) => {
ws.roomCode = null;
ws.on('message', (raw) => {
let msg;
try { msg = JSON.parse(raw); } catch { return; }
switch (msg.type) {
case 'create': {
if (ws.roomCode) return;
const code = generateCode();
rooms.set(code, new Set([ws]));
ws.roomCode = code;
ws.send(JSON.stringify({ type: 'created', code }));
break;
}
case 'join': {
if (ws.roomCode) return;
const code = String(msg.code || '');
const room = rooms.get(code);
if (!room) {
ws.send(JSON.stringify({ type: 'error', message: 'Room not found' }));
return;
}
if (room.size >= 2) {
ws.send(JSON.stringify({ type: 'error', message: 'Room is full' }));
return;
}
room.add(ws);
ws.roomCode = code;
ws.send(JSON.stringify({ type: 'joined', code, memberCount: room.size }));
broadcast(room, { type: 'peer_joined' }, ws);
break;
}
case 'sync': {
if (!ws.roomCode) return;
const room = rooms.get(ws.roomCode);
if (!room) return;
broadcast(room, { type: 'sync', action: msg.action, time: msg.time }, ws);
break;
}
case 'ping':
ws.send(JSON.stringify({ type: 'pong' }));
break;
}
});
ws.on('close', () => {
if (!ws.roomCode) return;
const room = rooms.get(ws.roomCode);
if (!room) return;
room.delete(ws);
if (room.size === 0) {
rooms.delete(ws.roomCode);
} else {
broadcast(room, { type: 'peer_left' }, null);
}
});
});
server.listen(PORT, () => {
console.log(`SyncWatch server listening on port ${PORT}`);
});