mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-15 17:12:59 -04:00
reboot your own rover
This commit is contained in:
@@ -15,6 +15,7 @@ require('./src/services/roverManager');
|
||||
require('./src/services/commandService');
|
||||
require('./src/services/roverConnectionService');
|
||||
require('./src/services/assignmentService');
|
||||
require('./src/services/roverRebootService');
|
||||
require('./src/services/nicknameService');
|
||||
require('./src/services/verificationService');
|
||||
require('./src/services/privateRoverAccessRequestService');
|
||||
|
||||
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
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-BEU30yxg.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BGy4iG0w.css">
|
||||
<script type="module" crossorigin src="/assets/index-IYeJHM9w.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BsongPZ9.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -192,6 +192,14 @@ function createBusEventHandler(deps) {
|
||||
announce({ channelId: channels.adminAlerts, pingRoleId: roles.adminPing || null, color: 0xe53935, title: 'Rover Offline', description: `${payload?.roverId} went offline.` });
|
||||
schedulePresenceRotation();
|
||||
break;
|
||||
case 'rover.reboot.userRequested':
|
||||
announce({
|
||||
channelId: channels.adminAlerts,
|
||||
color: 0xf0b651,
|
||||
title: 'User Rover Reboot Requested',
|
||||
description: `${payload?.by || 'unknown user'} requested reboot for ${payload?.roverName || payload?.roverId || 'unknown rover'}.`,
|
||||
});
|
||||
break;
|
||||
case 'rover.dockGuard':
|
||||
announce({ channelId: channels.adminAlerts, color: 0xf0b651, title: 'Dock Guard Triggered', description: `${payload?.roverId} (${payload?.reasonText || 'undocked'}) for ${formatDuration(payload?.idleMs)}.` });
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
// rover Reboot Service
|
||||
// Purpose: Handles one-shot user-triggered rover reboot requests with server-side safety checks.
|
||||
// Scope: Validates ownership/control, stops the rover, issues reboot, and publishes admin-alert events.
|
||||
const io = require('../../globals/io');
|
||||
const logger = require('../../globals/logger').child('roverReboot');
|
||||
const { getRole } = require('../roleService');
|
||||
const roverManager = require('../roverManager');
|
||||
const assignmentService = require('../assignmentService');
|
||||
const { stopRover } = require('../turnService/actions');
|
||||
const { issueCommand } = require('../commandService');
|
||||
const { publishEvent } = require('../eventBus');
|
||||
|
||||
const USER_COOLDOWN_MS = 2 * 60 * 1000;
|
||||
const userCooldowns = new Map(); // socketId -> blockedUntil
|
||||
|
||||
function getCooldownLeftMs(socketId) {
|
||||
const blockedUntil = Number(userCooldowns.get(socketId) || 0);
|
||||
return Math.max(0, blockedUntil - Date.now());
|
||||
}
|
||||
|
||||
function setCooldown(socketId) {
|
||||
userCooldowns.set(socketId, Date.now() + USER_COOLDOWN_MS);
|
||||
}
|
||||
|
||||
function canRequestReboot(socket) {
|
||||
if (!socket?.id) throw new Error('Socket required');
|
||||
const role = getRole(socket);
|
||||
if (role === 'spectator') throw new Error('Spectators cannot reboot rovers');
|
||||
const roverId = assignmentService.getAssignedRover(socket.id);
|
||||
if (!roverId) throw new Error('No assigned rover');
|
||||
const record = roverManager.rovers.get(String(roverId));
|
||||
if (!record || !record.ws) throw new Error('Assigned rover is offline');
|
||||
if (!roverManager.canDrive(roverId, socket)) throw new Error('Not your turn or no control');
|
||||
if (!(record.drivers instanceof Set) || record.drivers.size !== 1 || !record.drivers.has(socket.id)) {
|
||||
throw new Error('Reboot blocked while other drivers are connected');
|
||||
}
|
||||
const cooldownLeftMs = getCooldownLeftMs(socket.id);
|
||||
if (cooldownLeftMs > 0) {
|
||||
throw new Error(`Reboot cooldown active (${Math.ceil(cooldownLeftMs / 1000)}s)`);
|
||||
}
|
||||
return { roverId: String(roverId), record };
|
||||
}
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
function handleRebootOwnRover(_, cb = () => {}) {
|
||||
try {
|
||||
const { roverId, record } = canRequestReboot(socket);
|
||||
stopRover(roverId);
|
||||
issueCommand(roverId, { type: 'reboot', reboot: { delayMs: 300 } });
|
||||
setCooldown(socket.id);
|
||||
const requester = socket?.data?.user?.username || socket.id;
|
||||
publishEvent({
|
||||
source: 'roverRebootService',
|
||||
type: 'rover.reboot.userRequested',
|
||||
payload: {
|
||||
roverId,
|
||||
roverName: record?.meta?.name || roverId,
|
||||
by: requester,
|
||||
},
|
||||
});
|
||||
logger.info('User-requested rover reboot', { roverId, by: requester, socketId: socket.id });
|
||||
cb({ success: true, roverId });
|
||||
} catch (err) {
|
||||
cb({ error: err.message || 'Reboot failed' });
|
||||
}
|
||||
}
|
||||
|
||||
socket.on('session:rebootOwnRover', handleRebootOwnRover);
|
||||
socket.on('rebootOwnRover', handleRebootOwnRover);
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
userCooldowns.delete(socket.id);
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = {};
|
||||
Reference in New Issue
Block a user