mirror of
https://github.com/Shik3i/KoalaSync.git
synced 2026-07-26 12:08:15 +00:00
feat: add graceful shutdown, input validation, and peer data factory (Phase 3)
This commit is contained in:
+24
-26
@@ -101,6 +101,26 @@ let forceSyncTimeout = null;
|
||||
|
||||
// --- Storage Utils ---
|
||||
|
||||
/**
|
||||
* Canonical peer data factory. All peer object construction must go through
|
||||
* here to guarantee a consistent shape with predictable null defaults.
|
||||
* @param {object} raw - Raw data from server event or heartbeat payload.
|
||||
* @returns {object} Normalized peer data object.
|
||||
*/
|
||||
function createPeerData(raw) {
|
||||
return {
|
||||
peerId: raw.peerId || null,
|
||||
username: raw.username || null,
|
||||
tabTitle: raw.tabTitle || null,
|
||||
mediaTitle: raw.mediaTitle || null,
|
||||
playbackState: raw.playbackState || null,
|
||||
currentTime: raw.currentTime != null ? raw.currentTime : null,
|
||||
volume: raw.volume != null ? raw.volume : null,
|
||||
muted: raw.muted != null ? raw.muted : null,
|
||||
lastHeartbeat: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
async function getPeerId() {
|
||||
const data = await chrome.storage.local.get(['peerId']);
|
||||
if (data.peerId) return data.peerId;
|
||||
@@ -413,9 +433,7 @@ function handleServerEvent(event, data) {
|
||||
if (storageInitialized) chrome.storage.session.set({ currentRoom });
|
||||
addLog(`Joined Room: ${data.roomId}`, 'success');
|
||||
chrome.runtime.sendMessage({ type: 'PEER_UPDATE', peers: data.peers }).catch(() => {});
|
||||
|
||||
|
||||
|
||||
|
||||
// Inform Website Bridge & Popup
|
||||
const joinStatusMsg = { type: 'JOIN_STATUS', success: true, message: 'Joined' };
|
||||
chrome.runtime.sendMessage(joinStatusMsg).catch(() => {});
|
||||
@@ -506,17 +524,7 @@ function handleServerEvent(event, data) {
|
||||
if (!Array.isArray(currentRoom.peers)) currentRoom.peers = [];
|
||||
if (data.status === 'joined') {
|
||||
if (!currentRoom.peers.find(p => (p.peerId || p) === data.peerId)) {
|
||||
currentRoom.peers.push({
|
||||
peerId: data.peerId,
|
||||
username: data.username,
|
||||
tabTitle: data.tabTitle,
|
||||
mediaTitle: data.mediaTitle || null,
|
||||
playbackState: data.playbackState || null,
|
||||
currentTime: data.currentTime != null ? data.currentTime : null,
|
||||
volume: data.volume != null ? data.volume : null,
|
||||
muted: data.muted != null ? data.muted : null,
|
||||
lastHeartbeat: Date.now()
|
||||
});
|
||||
currentRoom.peers.push(createPeerData(data));
|
||||
if (storageInitialized) chrome.storage.session.set({ currentRoom });
|
||||
chrome.runtime.sendMessage({ type: 'PEER_UPDATE', peers: currentRoom.peers }).catch(() => {});
|
||||
}
|
||||
@@ -538,19 +546,9 @@ function handleServerEvent(event, data) {
|
||||
peer.muted = data.muted !== undefined ? data.muted : peer.muted;
|
||||
peer.lastHeartbeat = Date.now();
|
||||
} else {
|
||||
// Migration: replace string with object
|
||||
// Migration: replace string peer with normalized object
|
||||
const idx = currentRoom.peers.indexOf(peer);
|
||||
currentRoom.peers[idx] = {
|
||||
peerId: data.peerId,
|
||||
username: data.username,
|
||||
tabTitle: data.tabTitle,
|
||||
mediaTitle: data.mediaTitle || null,
|
||||
playbackState: data.playbackState || null,
|
||||
currentTime: data.currentTime != null ? data.currentTime : null,
|
||||
volume: data.volume != null ? data.volume : null,
|
||||
muted: data.muted != null ? data.muted : null,
|
||||
lastHeartbeat: Date.now()
|
||||
};
|
||||
currentRoom.peers[idx] = createPeerData(data);
|
||||
}
|
||||
if (storageInitialized) chrome.storage.session.set({ currentRoom });
|
||||
chrome.runtime.sendMessage({ type: 'PEER_UPDATE', peers: currentRoom.peers }).catch(() => {});
|
||||
|
||||
+30
-1
@@ -200,7 +200,16 @@ io.on('connection', (socket) => {
|
||||
return;
|
||||
}
|
||||
if (!payload || typeof payload.roomId !== 'string') return;
|
||||
const { roomId, password, peerId, username, tabTitle, mediaTitle, protocolVersion } = payload;
|
||||
const { password, peerId, protocolVersion } = payload;
|
||||
|
||||
// --- M-2: Sanitize and clamp all string fields ---
|
||||
const roomId = String(payload.roomId || '').substring(0, 64);
|
||||
const username = typeof payload.username === 'string' ? payload.username.substring(0, 30) : null;
|
||||
const tabTitle = typeof payload.tabTitle === 'string' ? payload.tabTitle.substring(0, 100) : null;
|
||||
const mediaTitle = typeof payload.mediaTitle === 'string' ? payload.mediaTitle.substring(0, 100) : null;
|
||||
|
||||
if (!roomId) return; // Guard: empty after sanitization
|
||||
|
||||
try {
|
||||
// Protocol check
|
||||
if (protocolVersion !== '1.0.0') {
|
||||
@@ -422,3 +431,23 @@ setInterval(() => {
|
||||
httpServer.listen(PORT, () => {
|
||||
log('SERVER', `KoalaSync Relay running on port ${PORT}`);
|
||||
});
|
||||
|
||||
// --- M-4: Graceful Shutdown ---
|
||||
function gracefulShutdown(signal) {
|
||||
log('SERVER', `${signal} received — starting graceful shutdown...`);
|
||||
// 1. Notify all connected clients so they can display a meaningful message
|
||||
io.emit(EVENTS.ERROR, { message: 'Server is restarting. Please reconnect in a moment.' });
|
||||
// 2. Stop accepting new HTTP connections
|
||||
httpServer.close(() => {
|
||||
log('SERVER', 'HTTP server closed. Exiting.');
|
||||
process.exit(0);
|
||||
});
|
||||
// 3. Safety net: force-exit after 5s if connections don't drain
|
||||
setTimeout(() => {
|
||||
log('SERVER', 'Force-exit after timeout.');
|
||||
process.exit(1);
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
||||
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
||||
|
||||
Reference in New Issue
Block a user