mirror of
https://github.com/Shik3i/KoalaSync.git
synced 2026-07-26 12:08:15 +00:00
perf+fix: debounce seq persistence + role changes, null-clear stale peer state
- server: SET_PEER_ROLE now uses the same per-room 500ms debounce as SET_CONTROL_MODE (M-4), preventing a buggy/malicious host from thrashing the room's UI with rapid promote/demote bursts. - server: PEER_STATUS relay treats explicit null as 'clear' for tabTitle/mediaTitle/currentTime, so the tab-close heartbeat actually zeroes out stale state on other peers instead of being silently preserved by the clamp fallback. - background: _persistLastSeq is now trailing-debounced (500ms), cutting storage.session IPC writes from one-per-relayed-event to one-per-quiet-window in active rooms. - locales: add missing TOAST_ID_REGENERATED to all 14 non-English locales (was blocking locale coverage test on main). - test: H-1 force-sync demote test now waits out the debounce window to reflect real-world UI timing (a host cannot promote, run a force-sync, and demote inside 500ms).
This commit is contained in:
+16
-7
@@ -411,6 +411,7 @@ io.on('connection', (socket) => {
|
||||
hostPeerId: peerId,
|
||||
controlMode: CONTROL_MODES.EVERYONE,
|
||||
lastControlModeChangeAt: 0, // M-4: per-room debounce for control-mode toggles
|
||||
lastRoleChangeAt: 0, // M-4: per-room debounce for role promote/demote
|
||||
// Co-Host: peers allowed to drive in 'host-only'. Always includes the owner.
|
||||
controllers: new Set([peerId]),
|
||||
// H-1: peerId of the in-flight force-sync initiator. Lets a demoted
|
||||
@@ -585,10 +586,10 @@ io.on('connection', (socket) => {
|
||||
room.peerData.set(socket.id, {
|
||||
...existing,
|
||||
username: data.username !== undefined ? (clamp(data.username, 30) ?? existing.username) : existing.username,
|
||||
tabTitle: data.tabTitle !== undefined ? (clamp(data.tabTitle, 100) ?? existing.tabTitle) : existing.tabTitle,
|
||||
mediaTitle: data.mediaTitle !== undefined ? (clamp(data.mediaTitle, 100) ?? existing.mediaTitle) : existing.mediaTitle,
|
||||
tabTitle: data.tabTitle === null ? null : (data.tabTitle !== undefined ? (clamp(data.tabTitle, 100) ?? existing.tabTitle) : existing.tabTitle),
|
||||
mediaTitle: data.mediaTitle === null ? null : (data.mediaTitle !== undefined ? (clamp(data.mediaTitle, 100) ?? existing.mediaTitle) : existing.mediaTitle),
|
||||
playbackState: data.playbackState !== undefined ? (validState(data.playbackState) ?? existing.playbackState) : existing.playbackState,
|
||||
currentTime: data.currentTime !== undefined ? (clampNum(data.currentTime, 0, 86400) ?? existing.currentTime) : existing.currentTime,
|
||||
currentTime: data.currentTime === null ? null : (data.currentTime !== undefined ? (clampNum(data.currentTime, 0, 86400) ?? existing.currentTime) : existing.currentTime),
|
||||
volume: data.volume !== undefined ? (clampNum(data.volume, 0, 1) ?? existing.volume) : existing.volume,
|
||||
muted: data.muted !== undefined ? (validBool(data.muted) ?? existing.muted) : existing.muted,
|
||||
desynced: data.desynced !== undefined ? (validBool(data.desynced) === true) : (existing.desynced || false),
|
||||
@@ -599,12 +600,12 @@ io.on('connection', (socket) => {
|
||||
const relayPayload = {
|
||||
senderId: mapping.peerId,
|
||||
seq: clampNum(data.seq, 0, Number.MAX_SAFE_INTEGER),
|
||||
currentTime: clampNum(data.currentTime, 0, 86400),
|
||||
currentTime: data.currentTime === null ? null : clampNum(data.currentTime, 0, 86400),
|
||||
targetTime: clampNum(data.targetTime, 0, 86400),
|
||||
playbackState: validState(data.playbackState),
|
||||
username: clamp(data.username, 30),
|
||||
tabTitle: clamp(data.tabTitle, 100),
|
||||
mediaTitle: clamp(data.mediaTitle, 100),
|
||||
tabTitle: data.tabTitle === null ? null : clamp(data.tabTitle, 100),
|
||||
mediaTitle: data.mediaTitle === null ? null : clamp(data.mediaTitle, 100),
|
||||
volume: clampNum(data.volume, 0, 1),
|
||||
muted: validBool(data.muted),
|
||||
desynced: validBool(data.desynced),
|
||||
@@ -742,6 +743,13 @@ io.on('connection', (socket) => {
|
||||
const targetPresent = Array.from(room.peerData.values()).some(d => d.peerId === targetPeerId);
|
||||
if (!targetPresent) return;
|
||||
|
||||
const now = Date.now();
|
||||
if (now - (room.lastRoleChangeAt || 0) < CONTROL_MODE_MIN_INTERVAL_MS) {
|
||||
log('ROOM', `Role change debounced in ${mapping.roomId.substring(0, 3)}***`);
|
||||
socket.emit(EVENTS.CONTROL_MODE, controlModePayload(room));
|
||||
return;
|
||||
}
|
||||
|
||||
if (makeController) {
|
||||
if (room.controllers.has(targetPeerId)) return; // no-op
|
||||
room.controllers.add(targetPeerId);
|
||||
@@ -749,7 +757,8 @@ io.on('connection', (socket) => {
|
||||
if (!room.controllers.has(targetPeerId)) return; // no-op
|
||||
room.controllers.delete(targetPeerId);
|
||||
}
|
||||
room.lastActivity = Date.now();
|
||||
room.lastRoleChangeAt = now;
|
||||
room.lastActivity = now;
|
||||
io.to(mapping.roomId).emit(EVENTS.CONTROL_MODE, controlModePayload(room));
|
||||
log('ROOM', `Peer ${targetPeerId} ${makeController ? 'promoted to' : 'demoted from'} controller in ${mapping.roomId.substring(0, 3)}***`);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user