mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
private rover request fixes finally
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -11,7 +11,7 @@
|
|||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||||
<meta name="apple-mobile-web-app-title" content="Roomba Rover" />
|
<meta name="apple-mobile-web-app-title" content="Roomba Rover" />
|
||||||
<title>Roomba Rover</title>
|
<title>Roomba Rover</title>
|
||||||
<script type="module" crossorigin src="/assets/index-BwoVmbyy.js"></script>
|
<script type="module" crossorigin src="/assets/index-OA6D7Zgr.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-0REyzzxs.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-0REyzzxs.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -64,6 +64,10 @@ function applySocketGrantCache(socket) {
|
|||||||
socket.data.privateClosedAccessRovers = listGrantedRoversForRequester(requesterKey);
|
socket.data.privateClosedAccessRovers = listGrantedRoversForRequester(requesterKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function refreshAllSocketGrantCaches() {
|
||||||
|
io.sockets.sockets.forEach((socket) => applySocketGrantCache(socket));
|
||||||
|
}
|
||||||
|
|
||||||
function getGrantForRequester(requesterKey, roverId) {
|
function getGrantForRequester(requesterKey, roverId) {
|
||||||
if (!requesterKey || !roverId) return null;
|
if (!requesterKey || !roverId) return null;
|
||||||
return grants.get(buildGrantKey(requesterKey, roverId)) || null;
|
return grants.get(buildGrantKey(requesterKey, roverId)) || null;
|
||||||
@@ -139,12 +143,19 @@ function tryAssignClosedPrivateRover(socket, roverId) {
|
|||||||
if (!socket || !roverId) return false;
|
if (!socket || !roverId) return false;
|
||||||
const record = roverManager.rovers.get(String(roverId));
|
const record = roverManager.rovers.get(String(roverId));
|
||||||
if (!record) return false;
|
if (!record) return false;
|
||||||
const access = roverManager.canRequestControl(roverId, socket, { allowUser: true });
|
const access = roverManager.canRequestControl(roverId, socket, {
|
||||||
|
allowUser: true,
|
||||||
|
allowClosedPrivateGrantInLockdown: true,
|
||||||
|
});
|
||||||
if (!access.ok) {
|
if (!access.ok) {
|
||||||
throw new Error(access.reason || 'Control denied');
|
throw new Error(access.reason || 'Control denied');
|
||||||
}
|
}
|
||||||
const previousJoined = roverManager.getRoversForSocket(socket.id);
|
const previousJoined = roverManager.getRoversForSocket(socket.id);
|
||||||
roverManager.requestControl(roverId, socket, { allowUser: true });
|
roverManager.requestControl(roverId, socket, {
|
||||||
|
allowUser: true,
|
||||||
|
allowClosedPrivateGrantInLockdown: true,
|
||||||
|
forceTurn: true,
|
||||||
|
});
|
||||||
previousJoined.forEach((rid) => {
|
previousJoined.forEach((rid) => {
|
||||||
if (rid !== roverId) roverManager.releaseControl(rid, socket);
|
if (rid !== roverId) roverManager.releaseControl(rid, socket);
|
||||||
});
|
});
|
||||||
@@ -156,6 +167,7 @@ function tryAssignClosedPrivateRover(socket, roverId) {
|
|||||||
logger.warn('Failed to move assignment after private access grant', { socketId: socket.id, error: err.message });
|
logger.warn('Failed to move assignment after private access grant', { socketId: socket.id, error: err.message });
|
||||||
}
|
}
|
||||||
socket.emit('controlGranted', { roverId: String(roverId) });
|
socket.emit('controlGranted', { roverId: String(roverId) });
|
||||||
|
roverManager.broadcastRoster();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,5 +316,6 @@ module.exports = {
|
|||||||
approveRequest,
|
approveRequest,
|
||||||
denyRequest,
|
denyRequest,
|
||||||
applySocketGrantCache,
|
applySocketGrantCache,
|
||||||
|
refreshAllSocketGrantCaches,
|
||||||
clearPendingForRover,
|
clearPendingForRover,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const roverManager = require('../roverManager');
|
|||||||
const { requestEvents, grants } = require('./state');
|
const { requestEvents, grants } = require('./state');
|
||||||
|
|
||||||
function registerPrivateRoverAccessHooks(deps) {
|
function registerPrivateRoverAccessHooks(deps) {
|
||||||
const { applySocketGrantCache, createRequest, clearPendingForRover } = deps;
|
const { applySocketGrantCache, refreshAllSocketGrantCaches, createRequest, clearPendingForRover } = deps;
|
||||||
|
|
||||||
roverManager.managerEvents.on('private', ({ roverId, open } = {}) => {
|
roverManager.managerEvents.on('private', ({ roverId, open } = {}) => {
|
||||||
if (!roverId) return;
|
if (!roverId) return;
|
||||||
@@ -17,6 +17,8 @@ function registerPrivateRoverAccessHooks(deps) {
|
|||||||
grants.delete(key);
|
grants.delete(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
refreshAllSocketGrantCaches();
|
||||||
|
roverManager.broadcastRoster();
|
||||||
}
|
}
|
||||||
requestEvents.emit('change', { reason: 'private_state', roverId: String(roverId), open: Boolean(open) });
|
requestEvents.emit('change', { reason: 'private_state', roverId: String(roverId), open: Boolean(open) });
|
||||||
});
|
});
|
||||||
@@ -30,6 +32,8 @@ function registerPrivateRoverAccessHooks(deps) {
|
|||||||
grants.delete(key);
|
grants.delete(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
refreshAllSocketGrantCaches();
|
||||||
|
roverManager.broadcastRoster();
|
||||||
}
|
}
|
||||||
requestEvents.emit('change', { reason: 'rover', roverId: String(roverId), action: action || null });
|
requestEvents.emit('change', { reason: 'rover', roverId: String(roverId), action: action || null });
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,12 +11,14 @@ const {
|
|||||||
approveRequest,
|
approveRequest,
|
||||||
denyRequest,
|
denyRequest,
|
||||||
applySocketGrantCache,
|
applySocketGrantCache,
|
||||||
|
refreshAllSocketGrantCaches,
|
||||||
clearPendingForRover,
|
clearPendingForRover,
|
||||||
} = require('./core');
|
} = require('./core');
|
||||||
const { registerPrivateRoverAccessHooks } = require('./hooks');
|
const { registerPrivateRoverAccessHooks } = require('./hooks');
|
||||||
|
|
||||||
registerPrivateRoverAccessHooks({
|
registerPrivateRoverAccessHooks({
|
||||||
applySocketGrantCache,
|
applySocketGrantCache,
|
||||||
|
refreshAllSocketGrantCaches,
|
||||||
createRequest,
|
createRequest,
|
||||||
clearPendingForRover,
|
clearPendingForRover,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -74,14 +74,25 @@ function createPrivateAccessPolicy(deps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getControlDenialReason(record, socket, options = {}) {
|
function getControlDenialReason(record, socket, options = {}) {
|
||||||
const { allowUser = false } = options;
|
const { allowUser = false, allowClosedPrivateGrantInLockdown = false } = options;
|
||||||
if (!record) return 'Unknown rover';
|
if (!record) return 'Unknown rover';
|
||||||
if (!allowUser && !isAdmin(socket)) return 'Only admins can request control';
|
if (!allowUser && !isAdmin(socket)) return 'Only admins can request control';
|
||||||
if (record.locked && !isAdmin(socket)) return 'Rover locked';
|
if (record.locked && !isAdmin(socket)) return 'Rover locked';
|
||||||
const mode = getMode();
|
const mode = getMode();
|
||||||
if (!allowUser && mode === MODES.ADMIN && !isAdmin(socket)) return 'Admins only';
|
if (!allowUser && mode === MODES.ADMIN && !isAdmin(socket)) return 'Admins only';
|
||||||
if (!allowUser && mode === MODES.LOCKDOWN && !isLockdownAdmin(socket)) return 'Server in lockdown';
|
if (!allowUser && mode === MODES.LOCKDOWN && !isLockdownAdmin(socket)) return 'Server in lockdown';
|
||||||
if (mode === MODES.LOCKDOWN && !isLockdownAdmin(socket)) return 'Server in lockdown';
|
if (
|
||||||
|
mode === MODES.LOCKDOWN &&
|
||||||
|
!isLockdownAdmin(socket) &&
|
||||||
|
!(
|
||||||
|
allowClosedPrivateGrantInLockdown &&
|
||||||
|
isPrivateRecord(record) &&
|
||||||
|
!isPrivateOpen(record) &&
|
||||||
|
socketHasClosedPrivateAccess(socket, record.id)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return 'Server in lockdown';
|
||||||
|
}
|
||||||
const { isDeterred } = require('../verificationService');
|
const { isDeterred } = require('../verificationService');
|
||||||
if (!isAdmin(socket) && isDeterred(socket)) return 'Not authorized';
|
if (!isAdmin(socket) && isDeterred(socket)) return 'Not authorized';
|
||||||
if (!isPrivateRecord(record)) return null;
|
if (!isPrivateRecord(record)) return null;
|
||||||
|
|||||||
@@ -33,9 +33,17 @@ function createRoverLifecycle(deps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function requestControl(roverId, socket, options = {}) {
|
function requestControl(roverId, socket, options = {}) {
|
||||||
const { force = false, allowUser = false } = options;
|
const {
|
||||||
|
force = false,
|
||||||
|
allowUser = false,
|
||||||
|
forceTurn = false,
|
||||||
|
allowClosedPrivateGrantInLockdown = false,
|
||||||
|
} = options;
|
||||||
const record = rovers.get(roverId);
|
const record = rovers.get(roverId);
|
||||||
const denied = getControlDenialReason(record, socket, { allowUser });
|
const denied = getControlDenialReason(record, socket, {
|
||||||
|
allowUser,
|
||||||
|
allowClosedPrivateGrantInLockdown,
|
||||||
|
});
|
||||||
if (denied) throw new Error(denied);
|
if (denied) throw new Error(denied);
|
||||||
record.drivers.add(socket.id);
|
record.drivers.add(socket.id);
|
||||||
if (!socketToRovers.has(socket.id)) {
|
if (!socketToRovers.has(socket.id)) {
|
||||||
@@ -43,7 +51,7 @@ function createRoverLifecycle(deps) {
|
|||||||
}
|
}
|
||||||
socketToRovers.get(socket.id).add(roverId);
|
socketToRovers.get(socket.id).add(roverId);
|
||||||
socket.join(record.room);
|
socket.join(record.room);
|
||||||
turnService.driverAdded(roverId, socket.id, force && isAdmin(socket));
|
turnService.driverAdded(roverId, socket.id, (force && isAdmin(socket)) || forceTurn);
|
||||||
socket.emit('controlGranted', { roverId });
|
socket.emit('controlGranted', { roverId });
|
||||||
managerEvents.emit('driver', { socketId: socket.id, roverId, action: 'add' });
|
managerEvents.emit('driver', { socketId: socket.id, roverId, action: 'add' });
|
||||||
sendAlert({
|
sendAlert({
|
||||||
@@ -77,7 +85,10 @@ function createRoverLifecycle(deps) {
|
|||||||
function canDrive(roverId, socket) {
|
function canDrive(roverId, socket) {
|
||||||
const record = rovers.get(roverId);
|
const record = rovers.get(roverId);
|
||||||
if (!record) return false;
|
if (!record) return false;
|
||||||
const denied = getControlDenialReason(record, socket, { allowUser: true });
|
const denied = getControlDenialReason(record, socket, {
|
||||||
|
allowUser: true,
|
||||||
|
allowClosedPrivateGrantInLockdown: true,
|
||||||
|
});
|
||||||
if (denied) return false;
|
if (denied) return false;
|
||||||
const _mode = getMode();
|
const _mode = getMode();
|
||||||
if (isAdmin(socket)) return true;
|
if (isAdmin(socket)) return true;
|
||||||
@@ -116,10 +127,13 @@ function createRoverLifecycle(deps) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function canSwitchRover(socket, targetRoverId) {
|
function canSwitchRover(socket, targetRoverId, options = {}) {
|
||||||
const target = rovers.get(targetRoverId);
|
const target = rovers.get(targetRoverId);
|
||||||
if (!target) return { ok: false, message: 'Unknown rover' };
|
if (!target) return { ok: false, message: 'Unknown rover' };
|
||||||
const denied = getControlDenialReason(target, socket, { allowUser: true });
|
const denied = getControlDenialReason(target, socket, {
|
||||||
|
allowUser: true,
|
||||||
|
allowClosedPrivateGrantInLockdown: Boolean(options.allowClosedPrivateGrantInLockdown),
|
||||||
|
});
|
||||||
if (denied) return { ok: false, message: denied };
|
if (denied) return { ok: false, message: denied };
|
||||||
const currentId = getPrimaryRoverForSocket(socket.id);
|
const currentId = getPrimaryRoverForSocket(socket.id);
|
||||||
if (!currentId || currentId === targetRoverId) return { ok: true, currentId };
|
if (!currentId || currentId === targetRoverId) return { ok: true, currentId };
|
||||||
|
|||||||
@@ -57,15 +57,20 @@ function registerSocketHandlers(deps) {
|
|||||||
throw new Error('No rovers available');
|
throw new Error('No rovers available');
|
||||||
}
|
}
|
||||||
const previousJoined = getRoversForSocket(socket.id);
|
const previousJoined = getRoversForSocket(socket.id);
|
||||||
|
const allowClosedPrivateGrantInLockdown = true;
|
||||||
if (!isAdmin(socket)) {
|
if (!isAdmin(socket)) {
|
||||||
const { ok, message } = canSwitchRover(socket, targetId);
|
const { ok, message } = canSwitchRover(socket, targetId, { allowClosedPrivateGrantInLockdown });
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
throw new Error(message || 'Switch denied');
|
throw new Error(message || 'Switch denied');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const forceAllowed = Boolean(force) && isAdmin(socket);
|
const forceAllowed = Boolean(force) && isAdmin(socket);
|
||||||
logger.info('Request control', socket.id, targetId, { force: forceAllowed });
|
logger.info('Request control', socket.id, targetId, { force: forceAllowed });
|
||||||
requestControl(targetId, socket, { force: forceAllowed, allowUser: true });
|
requestControl(targetId, socket, {
|
||||||
|
force: forceAllowed,
|
||||||
|
allowUser: true,
|
||||||
|
allowClosedPrivateGrantInLockdown,
|
||||||
|
});
|
||||||
previousJoined.forEach((rid) => {
|
previousJoined.forEach((rid) => {
|
||||||
if (rid !== targetId) {
|
if (rid !== targetId) {
|
||||||
releaseControl(rid, socket);
|
releaseControl(rid, socket);
|
||||||
|
|||||||
@@ -156,11 +156,16 @@ export default function RoverQueuesPanel({ title = 'Rovers' }) {
|
|||||||
const isSelfCurrent = Boolean(selfId && currentId && currentId === selfId);
|
const isSelfCurrent = Boolean(selfId && currentId && currentId === selfId);
|
||||||
const isSelfNext = Boolean(selfId && nextId && nextId === selfId);
|
const isSelfNext = Boolean(selfId && nextId && nextId === selfId);
|
||||||
const showTimer = remainingSeconds != null && (isSelfCurrent || isSelfNext);
|
const showTimer = remainingSeconds != null && (isSelfCurrent || isSelfNext);
|
||||||
const locked = Boolean(rover.locked);
|
|
||||||
const lockedBlocked = locked && !adminCapable;
|
|
||||||
const isPrivateOpen = Boolean(rover?.private?.enabled && rover?.private?.open);
|
const isPrivateOpen = Boolean(rover?.private?.enabled && rover?.private?.open);
|
||||||
|
const isGrantedClosedPrivate = Boolean(rover?.private?.enabled && !rover?.private?.open);
|
||||||
|
const locked = Boolean(rover.locked);
|
||||||
|
const lockedBlocked = locked && !adminCapable && !isGrantedClosedPrivate;
|
||||||
const lockLabel = rover.lockReason ? `locked: ${rover.lockReason}` : 'locked';
|
const lockLabel = rover.lockReason ? `locked: ${rover.lockReason}` : 'locked';
|
||||||
const buttonLabel = locked ? lockLabel : pending[roverId] ? '...' : 'request';
|
const buttonLabel = pending[roverId]
|
||||||
|
? '...'
|
||||||
|
: locked && !isGrantedClosedPrivate
|
||||||
|
? lockLabel
|
||||||
|
: 'request';
|
||||||
const canClickRow = canRequest && !lockedBlocked && !pending[roverId];
|
const canClickRow = canRequest && !lockedBlocked && !pending[roverId];
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
|
|||||||
Reference in New Issue
Block a user