mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-15 17:12:59 -04:00
dead socket kicking and connection info pill
This commit is contained in:
@@ -2,6 +2,9 @@
|
||||
2. add "reboot your own rover" feature
|
||||
1. time delay and you can only do it if youre the only one on. server locks the rover then tells it to reboot
|
||||
3. add a clear socket disconnection indicator in the web UI
|
||||
1. fix reconnection stuff, make sure identity always sends. maybe even on a timer
|
||||
2. stop allowing dead sockets (no identity) on rovers... idk how to handle this
|
||||
1. disconnect dead sockets after 2 minutes, if identity is sent every minute no matter what, no one should ever get disconnected while functional
|
||||
4. add votekicking
|
||||
1. simply just disconnect the users socket.
|
||||
5. add faster way for admins to login, like an invisible button in top left or something
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -11,8 +11,8 @@
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="apple-mobile-web-app-title" content="Roomba Rover" />
|
||||
<title>Roomba Rover</title>
|
||||
<script type="module" crossorigin src="/assets/index-IYeJHM9w.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BsongPZ9.css">
|
||||
<script type="module" crossorigin src="/assets/index-4wUtzGNB.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BpQe7PYg.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -14,11 +14,14 @@ function registerVerificationHooks(deps) {
|
||||
} = deps;
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
socket.data = socket.data || {};
|
||||
socket.data.connectedAt = Date.now();
|
||||
identifySocket(socket, {});
|
||||
|
||||
socket.on('session:identify', (payload = {}, cb = () => {}) => {
|
||||
try {
|
||||
const result = identifySocket(socket, payload || {});
|
||||
socket.data.lastClientIdentifyAt = Date.now();
|
||||
cb({ success: true, ...result });
|
||||
} catch (err) {
|
||||
cb({ error: err.message });
|
||||
|
||||
@@ -28,6 +28,8 @@ const { createRequestFlow } = require('./requestFlow');
|
||||
const { registerVerificationHooks } = require('./hooks');
|
||||
|
||||
const verificationEvents = new EventEmitter();
|
||||
const IDENTITY_TIMEOUT_MS = 2 * 60 * 1000;
|
||||
const IDENTITY_SWEEP_INTERVAL_MS = 15 * 1000;
|
||||
|
||||
function emitChange(reason, payload = {}) {
|
||||
verificationEvents.emit('change', { reason, ...payload });
|
||||
@@ -145,6 +147,27 @@ registerVerificationHooks({
|
||||
emitChange,
|
||||
});
|
||||
|
||||
setInterval(() => {
|
||||
const now = Date.now();
|
||||
io.sockets.sockets.forEach((socket) => {
|
||||
if (!socket?.id) return;
|
||||
const role = getRole(socket);
|
||||
if (role === 'admin' || role === 'lockdown') return;
|
||||
const connectedAt = Number(socket?.data?.connectedAt || 0);
|
||||
const lastClientIdentifyAt = Number(socket?.data?.lastClientIdentifyAt || 0);
|
||||
const referenceTs = lastClientIdentifyAt || connectedAt;
|
||||
if (!referenceTs) return;
|
||||
if (now - referenceTs < IDENTITY_TIMEOUT_MS) return;
|
||||
logger.info('Disconnecting socket due to stale identity heartbeat', {
|
||||
socketId: socket.id,
|
||||
role,
|
||||
ageMs: now - referenceTs,
|
||||
hadClientIdentify: Boolean(lastClientIdentifyAt),
|
||||
});
|
||||
socket.disconnect(true);
|
||||
});
|
||||
}, IDENTITY_SWEEP_INTERVAL_MS);
|
||||
|
||||
module.exports = {
|
||||
identifySocket,
|
||||
getVerificationStatus: verificationFlow.getVerificationStatus,
|
||||
|
||||
@@ -42,6 +42,7 @@ import VipPanel from './components/VipPanel/index.jsx';
|
||||
import { useSessionSelector } from './context/SessionContext.jsx';
|
||||
import ButtonBoxPanel from './components/ButtonBoxPanel/index.jsx';
|
||||
import RewardRunOverlay from './components/RewardRunOverlay/index.jsx';
|
||||
import SocketConnectionPill from './components/SocketConnectionPill/index.jsx';
|
||||
|
||||
function useLayoutMode() {
|
||||
const [mode, setMode] = useState(() => {
|
||||
@@ -333,6 +334,7 @@ function AppWithProviders({ layout, isDesktop, fullscreen }) {
|
||||
<RewardRunOverlay />
|
||||
<TurnAlertListener />
|
||||
<ModeGateOverlay />
|
||||
<SocketConnectionPill />
|
||||
|
||||
<HelpOverlay
|
||||
visible={helpVisible}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useSocket } from '../../context/SocketContext.jsx';
|
||||
|
||||
const CONNECTED_FADE_DELAY_MS = 4000;
|
||||
|
||||
function cls(...values) {
|
||||
return values.filter(Boolean).join(' ');
|
||||
}
|
||||
|
||||
export default function SocketConnectionPill() {
|
||||
const socket = useSocket();
|
||||
const [connected, setConnected] = useState(Boolean(socket?.connected));
|
||||
const [lastReason, setLastReason] = useState('');
|
||||
const [visible, setVisible] = useState(true);
|
||||
const [faded, setFaded] = useState(false);
|
||||
const fadeTimerRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const clearFadeTimer = () => {
|
||||
if (fadeTimerRef.current) {
|
||||
clearTimeout(fadeTimerRef.current);
|
||||
fadeTimerRef.current = null;
|
||||
}
|
||||
};
|
||||
|
||||
const show = () => {
|
||||
clearFadeTimer();
|
||||
setVisible(true);
|
||||
setFaded(false);
|
||||
};
|
||||
|
||||
const scheduleFade = () => {
|
||||
clearFadeTimer();
|
||||
fadeTimerRef.current = setTimeout(() => {
|
||||
setFaded(true);
|
||||
}, CONNECTED_FADE_DELAY_MS);
|
||||
};
|
||||
|
||||
const onConnect = () => {
|
||||
setConnected(true);
|
||||
setLastReason('');
|
||||
show();
|
||||
scheduleFade();
|
||||
};
|
||||
|
||||
const onDisconnect = (reason) => {
|
||||
setConnected(false);
|
||||
setLastReason(typeof reason === 'string' ? reason : 'disconnected');
|
||||
show();
|
||||
};
|
||||
|
||||
const onConnectError = (err) => {
|
||||
setConnected(false);
|
||||
setLastReason(err?.message || 'connect error');
|
||||
show();
|
||||
};
|
||||
|
||||
const onReconnectAttempt = () => {
|
||||
setConnected(false);
|
||||
show();
|
||||
};
|
||||
|
||||
socket.on('connect', onConnect);
|
||||
socket.on('disconnect', onDisconnect);
|
||||
socket.on('connect_error', onConnectError);
|
||||
socket.io.on('reconnect_attempt', onReconnectAttempt);
|
||||
|
||||
if (socket.connected) {
|
||||
scheduleFade();
|
||||
}
|
||||
|
||||
return () => {
|
||||
clearFadeTimer();
|
||||
socket.off('connect', onConnect);
|
||||
socket.off('disconnect', onDisconnect);
|
||||
socket.off('connect_error', onConnectError);
|
||||
socket.io.off('reconnect_attempt', onReconnectAttempt);
|
||||
};
|
||||
}, [socket]);
|
||||
|
||||
const offline = typeof navigator !== 'undefined' ? navigator.onLine === false : false;
|
||||
const statusLabel = connected
|
||||
? 'Connected to socket.io'
|
||||
: offline
|
||||
? 'Offline'
|
||||
: 'Reconnecting...';
|
||||
const detail = connected ? '' : lastReason;
|
||||
|
||||
const toneClass = connected
|
||||
? 'border-emerald-300/40 bg-emerald-700/70 text-emerald-100'
|
||||
: offline
|
||||
? 'border-red-300/50 bg-red-800/80 text-red-100'
|
||||
: 'border-amber-300/50 bg-amber-800/80 text-amber-100';
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cls(
|
||||
'fixed bottom-2 left-2 z-[120] transition-opacity duration-500',
|
||||
!visible && 'hidden',
|
||||
faded ? 'opacity-0 pointer-events-none' : 'opacity-100',
|
||||
)}
|
||||
aria-live="polite"
|
||||
>
|
||||
<div className={cls('rounded-md border px-2 py-1 text-xs shadow-lg backdrop-blur-sm', toneClass)}>
|
||||
<span className="font-semibold">{statusLabel}</span>
|
||||
{detail ? <span className="ml-1 opacity-85">({detail})</span> : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -95,5 +95,14 @@ export default function useUserIdentitySync() {
|
||||
};
|
||||
}, [sendIdentify, socket?.connected]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ready || !socket?.connected) return undefined;
|
||||
const timer = setInterval(() => {
|
||||
if (!socket?.connected) return;
|
||||
sendIdentify();
|
||||
}, 60000);
|
||||
return () => clearInterval(timer);
|
||||
}, [ready, sendIdentify, socket?.connected]);
|
||||
|
||||
useEffect(() => () => clearRetry(), [clearRetry]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user