fix(vitalsharing): clear stopped peers from the NetworkUI window

Peers who unsubscribed or disconnected from vital sharing were lingering
forever in the Vital Sharing browser window because nothing ever deleted
them from the server-side state or told the browser to drop them.

Backend:
- share_unsubscribe now pops the character from _vital_sharing_peer_state
  (not just flips connected=false) and broadcasts a share_peer_removed
  envelope to browser clients.
- On real plugin disconnect, do the same: pop the state entry and
  broadcast share_peer_removed so the NetworkUI updates immediately.

Frontend:
- New removeVitalSharingPeer(name) deletes from the local
  vitalSharingPeers dict and re-renders.
- socket.onmessage now routes share_peer_removed to it.
- refreshVitalSharingPeers() reconciles against the server's list and
  prunes any local entries the server no longer knows about, catching
  any race where the broadcast was missed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-11 15:25:08 +02:00
parent 7a31469d69
commit da4e840581
2 changed files with 32 additions and 4 deletions

View file

@ -3074,6 +3074,8 @@ function initWebSocket() {
const rw = radarWindows[msg.character_name];
if (rw) rw._radarDungeonLandblock = msg.landblock;
}
} else if (msg.type === 'share_peer_removed') {
removeVitalSharingPeer(msg.character_name);
} else if (typeof msg.type === 'string' && msg.type.startsWith('share_')) {
updateVitalSharingPeer(msg);
}
@ -4438,6 +4440,14 @@ function showCommentsSection(row, issue, win) {
const vitalSharingPeers = {};
let _vitalSharingPollTimer = null;
function removeVitalSharingPeer(name) {
if (!name) return;
if (vitalSharingPeers[name]) {
delete vitalSharingPeers[name];
renderVitalSharingWindow();
}
}
function updateVitalSharingPeer(msg) {
const name = msg.character_name;
if (!name) return;
@ -4504,10 +4514,18 @@ async function refreshVitalSharingPeers() {
const resp = await fetch('/vital-sharing/peers');
if (!resp.ok) return;
const data = await resp.json();
const serverNames = new Set();
(data.peers || []).forEach(p => {
serverNames.add(p.character_name);
const existing = vitalSharingPeers[p.character_name] || {};
vitalSharingPeers[p.character_name] = { ...existing, ...p };
});
// Prune any local entries the server no longer knows about (unsubscribed
// or disconnected). This catches any race where the share_peer_removed
// broadcast didn't arrive.
Object.keys(vitalSharingPeers).forEach(name => {
if (!serverNames.has(name)) delete vitalSharingPeers[name];
});
} catch (e) {
// ignore transient errors
}