This commit is contained in:
legop3
2026-04-28 19:53:10 -04:00
parent f13b85109e
commit 49f801330f
59 changed files with 686 additions and 401 deletions
+11 -5
View File
@@ -17,6 +17,9 @@
- Split very large service files into smaller focused modules.
- Keep files concise and single-purpose.
- Add clear title comments at top of split files.
- Every service/module file should start with a descriptive comment header containing:
- a title line naming the service/module file
- a longer purpose/scope description (not a one-liner)
## WebUI frontend
- Every component must live in its own folder, even when it remains a single-file implementation.
@@ -48,17 +51,20 @@
- [ ] replay services (already partly split; reformat consistently)
- [ ] room camera services (already partly split; reformat consistently)
- [ ] rover manager service
- [ ] session service
- [ ] turn service
- [x] session service
- [x] turn service
- [ ] verification service
- [ ] video auth service
- [ ] All remaining services: reorganize to folder structure where needed
- [x] All remaining services: reorganize to folder structure where needed
### COMPLETED SERVICES
- None yet.
- turn service
- session service
### LARGE CHANGES
- None yet.
- Folderized all files in `server/src/services/` into per-service folders with `index.js` entrypoints and updated internal relative imports for new path depth.
- Split `server/src/services/turnService/index.js` by extracting constants, shared state helpers, and side-effect action helpers into `turnService/constants.js`, `turnService/state.js`, and `turnService/actions.js`.
- Split `server/src/services/sessionService/index.js` by extracting config/timing constants, sync-throttle state storage, and visibility filter helpers into `sessionService/constants.js`, `sessionService/state.js`, and `sessionService/filters.js`.
## WebUI frontend
### BIGGEST OFFENDERS
@@ -1,7 +1,10 @@
// admin Log Service
// Purpose: Defines the admin Log Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const { v4: uuidv4 } = require('uuid');
const io = require('../globals/io');
const { getRole, roleEvents } = require('./roleService');
const { getSocketIp } = require('../helpers/ipResolver');
const io = require('../../globals/io');
const { getRole, roleEvents } = require('../roleService');
const { getSocketIp } = require('../../helpers/ipResolver');
const ADMIN_ROLES = new Set(['admin', 'lockdown', 'lockdown-admin']);
const MAX_HISTORY = 200;
@@ -1,9 +1,12 @@
// admin Reason Service
// Purpose: Defines the admin Reason Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const fs = require('fs');
const path = require('path');
const io = require('../globals/io');
const logger = require('../globals/logger').child('adminReasonService');
const { isAdmin } = require('./roleService');
const { publishEvent } = require('./eventBus');
const io = require('../../globals/io');
const logger = require('../../globals/logger').child('adminReasonService');
const { isAdmin } = require('../roleService');
const { publishEvent } = require('../eventBus');
const DATA_DIR = path.join(__dirname, '..', '..', 'data');
const STORE_PATH = path.join(DATA_DIR, 'admin-reason.json');
@@ -1,4 +1,7 @@
const io = require('../globals/io');
// alert Service
// Purpose: Defines the alert Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const io = require('../../globals/io');
function sendAlert({ color, title, message, ts = Date.now() }) {
const payload = {
@@ -1,9 +1,12 @@
// assignment Service
// Purpose: Defines the assignment Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const EventEmitter = require('events');
const io = require('../globals/io');
const logger = require('../globals/logger').child('assignment');
const { MODES, getMode, modeEvents } = require('./modeManager');
const { roleEvents, getRole, isAdmin, isLockdownAdmin } = require('./roleService');
const roverManager = require('./roverManager');
const io = require('../../globals/io');
const logger = require('../../globals/logger').child('assignment');
const { MODES, getMode, modeEvents } = require('../modeManager');
const { roleEvents, getRole, isAdmin, isLockdownAdmin } = require('../roleService');
const roverManager = require('../roverManager');
const socketRefs = new Map(); // socketId -> socket
const assignments = new Map(); // socketId -> roverId
@@ -1,14 +1,17 @@
// audio Forward Service
// Purpose: Defines the audio Forward Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const fs = require('fs');
const path = require('path');
const { spawn, spawnSync } = require('child_process');
const EventEmitter = require('events');
const io = require('../globals/io');
const logger = require('../globals/logger').child('audioForwardService');
const { loadConfig } = require('../helpers/configLoader');
const roverManager = require('./roverManager');
const turnService = require('./turnService');
const { isVerified } = require('./verificationService');
const videoSessions = require('./videoSessions');
const io = require('../../globals/io');
const logger = require('../../globals/logger').child('audioForwardService');
const { loadConfig } = require('../../helpers/configLoader');
const roverManager = require('../roverManager');
const turnService = require('../turnService');
const { isVerified } = require('../verificationService');
const videoSessions = require('../videoSessions');
const audioForwardEvents = new EventEmitter();
const config = loadConfig();
@@ -1,12 +1,15 @@
// audio Levels Service
// Purpose: Defines the audio Levels Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const fs = require('fs');
const path = require('path');
const EventEmitter = require('events');
const io = require('../globals/io');
const logger = require('../globals/logger').child('audioLevelsService');
const { loadConfig } = require('../helpers/configLoader');
const { isAdmin } = require('./roleService');
const roverManager = require('./roverManager');
const { issueCommand } = require('./commandService');
const io = require('../../globals/io');
const logger = require('../../globals/logger').child('audioLevelsService');
const { loadConfig } = require('../../helpers/configLoader');
const { isAdmin } = require('../roleService');
const roverManager = require('../roverManager');
const { issueCommand } = require('../commandService');
const audioLevelsEvents = new EventEmitter();
const DATA_DIR = path.join(__dirname, '..', '..', 'data');
@@ -1,10 +1,13 @@
// auth Service
// Purpose: Defines the auth Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const bcrypt = require('bcrypt');
const io = require('../globals/io');
const logger = require('../globals/logger').child('authService');
const { loadConfig } = require('../helpers/configLoader');
const { clearLockdownTimer } = require('./lockdownGuard');
const { getMode, MODES } = require('./modeManager');
const { setRole } = require('./roleService');
const io = require('../../globals/io');
const logger = require('../../globals/logger').child('authService');
const { loadConfig } = require('../../helpers/configLoader');
const { clearLockdownTimer } = require('../lockdownGuard');
const { getMode, MODES } = require('../modeManager');
const { setRole } = require('../roleService');
const config = loadConfig();
const admins = config.admins || [];
@@ -1,6 +1,9 @@
const logger = require('../globals/logger').child('batteryManager');
const { publishEvent } = require('./eventBus');
const { managerEvents, lockRover, rovers } = require('./roverManager');
// battery Manager
// Purpose: Defines the battery Manager module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const logger = require('../../globals/logger').child('batteryManager');
const { publishEvent } = require('../eventBus');
const { managerEvents, lockRover, rovers } = require('../roverManager');
const STATES = {
NORMAL: 'normal',
@@ -1,24 +1,27 @@
// button Box Service
// Purpose: Defines the button Box Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const fs = require('fs');
const path = require('path');
const express = require('express');
const { app } = require('../globals/http');
const io = require('../globals/io');
const logger = require('../globals/logger').child('buttonBoxService');
const { publishEvent } = require('./eventBus');
const { getRewardById, listRewards } = require('../rewards');
const roverManager = require('./roverManager');
const { issueCommand } = require('./commandService');
const { sendAlert } = require('./alertService');
const { sendExternalTyping, sendExternalMessage, sendSystemMessage } = require('./chatService');
const { setMode, getMode } = require('./modeManager');
const { getAdminReason, setAdminReason, clearAdminReason } = require('./adminReasonService');
const assignmentService = require('./assignmentService');
const { app } = require('../../globals/http');
const io = require('../../globals/io');
const logger = require('../../globals/logger').child('buttonBoxService');
const { publishEvent } = require('../eventBus');
const { getRewardById, listRewards } = require('../../rewards');
const roverManager = require('../roverManager');
const { issueCommand } = require('../commandService');
const { sendAlert } = require('../alertService');
const { sendExternalTyping, sendExternalMessage, sendSystemMessage } = require('../chatService');
const { setMode, getMode } = require('../modeManager');
const { getAdminReason, setAdminReason, clearAdminReason } = require('../adminReasonService');
const assignmentService = require('../assignmentService');
const {
getState: getHomeAssistantState,
setEntityState: setHomeAssistantEntityState,
setLightsLockedOn: setHomeAssistantLightsLockedOn,
} = require('./homeAssistantService');
const { getRequestIp, isLocalNetwork, normalizeIp } = require('../helpers/ipResolver');
} = require('../homeAssistantService');
const { getRequestIp, isLocalNetwork, normalizeIp } = require('../../helpers/ipResolver');
const DATA_DIR = path.join(__dirname, '..', '..', 'data');
const STORE_PATH = path.join(DATA_DIR, 'buttonbox-state.json');
@@ -1,15 +1,18 @@
// chat Service
// Purpose: Defines the chat Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const { v4: uuidv4 } = require('uuid');
const { DataSet, RegExpMatcher, englishDataset, englishRecommendedTransformers } = require('obscenity');
const io = require('../globals/io');
const logger = require('../globals/logger').child('chatService');
const { publishEvent, subscribe } = require('./eventBus');
const { getRole } = require('./roleService');
const { getMode, MODES } = require('./modeManager');
const { describeAssignment } = require('./assignmentService');
const roverManager = require('./roverManager');
const { getNickname } = require('./nicknameService');
const { issueCommand } = require('./commandService');
const { getAdminReason } = require('./adminReasonService');
const io = require('../../globals/io');
const logger = require('../../globals/logger').child('chatService');
const { publishEvent, subscribe } = require('../eventBus');
const { getRole } = require('../roleService');
const { getMode, MODES } = require('../modeManager');
const { describeAssignment } = require('../assignmentService');
const roverManager = require('../roverManager');
const { getNickname } = require('../nicknameService');
const { issueCommand } = require('../commandService');
const { getAdminReason } = require('../adminReasonService');
const RATE_LIMIT_WINDOW_MS = 8000;
const RATE_LIMIT_MAX = 5;
@@ -141,7 +144,7 @@ function buildRoverCtxSnapshot(roverId) {
if (!record) return null;
const sensors = record?.lastSensor?.decoded || {};
const batteryState = record?.batteryState || null;
const { getActiveDrivers } = require('./turnService');
const { getActiveDrivers } = require('../turnService');
const activeDrivers = getActiveDrivers();
const driverSocketId = activeDrivers[key] || record?.drivers?.values?.().next?.().value || null;
const charging = isChargingFromSensors(sensors);
@@ -430,7 +433,7 @@ function maybeSpeak(socket, message, ttsOptions) {
const audio = record?.meta?.audio || {};
const ttsEnabled = Boolean(audio.ttsEnabled);
if (!ttsEnabled) return;
const { isQueuedDriver } = require('./turnService');
const { isQueuedDriver } = require('../turnService');
if (
!roverManager.canDrive(message.roverId, socket) &&
!isQueuedDriver(message.roverId, socket?.id)
@@ -1,10 +1,13 @@
// command Service
// Purpose: Defines the command Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const { v4: uuidv4 } = require('uuid');
const io = require('../globals/io');
const roverManager = require('./roverManager');
const { isAdmin, isLockdownAdmin } = require('./roleService');
const { isDeterred } = require('./verificationService');
const logger = require('../globals/logger').child('commandService');
const { isNightVisionBlocked } = require('../rewards/definitions/darkness');
const io = require('../../globals/io');
const roverManager = require('../roverManager');
const { isAdmin, isLockdownAdmin } = require('../roleService');
const { isDeterred } = require('../verificationService');
const logger = require('../../globals/logger').child('commandService');
const { isNightVisionBlocked } = require('../../rewards/definitions/darkness');
const pendingCommands = new Map(); // id -> { roverId }
const lastDriveActivity = new Map(); // roverId -> { ts, socketId, direction, speed, isAdmin }
@@ -122,7 +125,7 @@ io.on('connection', (socket) => {
const id = issueCommand(roverId, { type, ...payload });
logger.info('Queued command', socket.id, roverId, type);
try {
const { recordActivity } = require('./turnService');
const { recordActivity } = require('../turnService');
recordActivity(roverId, socket.id);
} catch (err) {
// best effort; ignore activity update errors
@@ -1,9 +1,12 @@
// community Goal Service
// Purpose: Defines the community Goal Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const fs = require('fs');
const path = require('path');
const io = require('../globals/io');
const logger = require('../globals/logger').child('communityGoalService');
const { isAdmin } = require('./roleService');
const { publishEvent } = require('./eventBus');
const io = require('../../globals/io');
const logger = require('../../globals/logger').child('communityGoalService');
const { isAdmin } = require('../roleService');
const { publishEvent } = require('../eventBus');
const DATA_DIR = path.join(__dirname, '..', '..', 'data');
const STORE_PATH = path.join(DATA_DIR, 'community-goal.json');
@@ -1,3 +1,6 @@
// discord Bot Service
// Purpose: Defines the discord Bot Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const {
Client,
GatewayIntentBits,
@@ -9,21 +12,21 @@ const {
WebhookClient,
MessageFlags,
} = require('discord.js');
const logger = require('../globals/logger').child('discordBot');
const io = require('../globals/io');
const { loadConfig } = require('../helpers/configLoader');
const { subscribe } = require('./eventBus');
const roverManager = require('./roverManager');
const logger = require('../../globals/logger').child('discordBot');
const io = require('../../globals/io');
const { loadConfig } = require('../../helpers/configLoader');
const { subscribe } = require('../eventBus');
const roverManager = require('../roverManager');
const { getRoster, lockRover, rovers } = roverManager;
const { MODES, getMode, setMode } = require('./modeManager');
const { sendExternalMessage, sendExternalTyping } = require('./chatService');
const { buildReplayVideo } = require('./replayBuildService');
const { getReplaySources, getDefaultDiscordSources, validateSources } = require('./replaySourceService');
const { getActiveDrivers } = require('./turnService');
const { getNickname } = require('./nicknameService');
const { tryTriggerReplay } = require('./replayService');
const { getCommunityGoal, setCommunityGoal, clearCommunityGoal } = require('./communityGoalService');
const { getAdminReason, setAdminReason, clearAdminReason } = require('./adminReasonService');
const { MODES, getMode, setMode } = require('../modeManager');
const { sendExternalMessage, sendExternalTyping } = require('../chatService');
const { buildReplayVideo } = require('../replayBuildService');
const { getReplaySources, getDefaultDiscordSources, validateSources } = require('../replaySourceService');
const { getActiveDrivers } = require('../turnService');
const { getNickname } = require('../nicknameService');
const { tryTriggerReplay } = require('../replayService');
const { getCommunityGoal, setCommunityGoal, clearCommunityGoal } = require('../communityGoalService');
const { getAdminReason, setAdminReason, clearAdminReason } = require('../adminReasonService');
const {
getGuildConfig,
listGuildConfigs,
@@ -31,7 +34,7 @@ const {
setGuildConfig,
normalizeMode,
VALID_MODES,
} = require('./discordGuildStore');
} = require('../discordGuildStore');
const {
attachDmMessage,
getRequestByMessageId,
@@ -42,7 +45,7 @@ const {
listDeterredUsers,
deterUser,
undeterUser,
} = require('./verificationService');
} = require('../verificationService');
const {
DM_APPROVE_EMOJI: PRIVATE_ACCESS_APPROVE_EMOJI,
DM_DENY_EMOJI: PRIVATE_ACCESS_DENY_EMOJI,
@@ -50,7 +53,7 @@ const {
getRequestByMessageId: getPrivateAccessRequestByMessageId,
approveRequest: approvePrivateAccessRequest,
denyRequest: denyPrivateAccessRequest,
} = require('./privateRoverAccessRequestService');
} = require('../privateRoverAccessRequestService');
const config = loadConfig();
const discordConfig = config.discord || {};
const enabled = Boolean(discordConfig.token);
@@ -1,6 +1,9 @@
// discord Guild Store
// Purpose: Defines the discord Guild Store module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const fs = require('fs');
const path = require('path');
const logger = require('../globals/logger').child('discordGuildStore');
const logger = require('../../globals/logger').child('discordGuildStore');
const DATA_DIR = path.join(__dirname, '..', '..', 'data');
const STORE_PATH = path.join(DATA_DIR, 'discord-guilds.json');
@@ -1,5 +1,8 @@
const { app } = require('../globals/http');
const { renderIndexHtml, renderOgImage } = require('./embedService');
// embed Http Service
// Purpose: Defines the embed Http Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const { app } = require('../../globals/http');
const { renderIndexHtml, renderOgImage } = require('../embedService');
app.get(['/', '/spectate', '/mini'], async (req, res) => {
try {
@@ -1,13 +1,16 @@
// embed Service
// Purpose: Defines the embed Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const path = require('path');
const fsp = require('fs/promises');
const sharp = require('sharp');
const { getMode } = require('./modeManager');
const roverManager = require('./roverManager');
const { getActiveDrivers, getTurnQueues } = require('./turnService');
const { getRoomCameras } = require('./roomCameraService');
const { getRoomCameraState } = require('./roomCameraSnapshotService');
const { loadConfig } = require('../helpers/configLoader');
const { getMode } = require('../modeManager');
const roverManager = require('../roverManager');
const { getActiveDrivers, getTurnQueues } = require('../turnService');
const { getRoomCameras } = require('../roomCameraService');
const { getRoomCameraState } = require('../roomCameraSnapshotService');
const { loadConfig } = require('../../helpers/configLoader');
const INDEX_HTML_PATH = path.join(__dirname, '..', '..', 'public', 'index.html');
const BITMAP_PATH = path.join(__dirname, '..', '..', 'public', 'bitmap.png');
@@ -1,5 +1,8 @@
// event Bus
// Purpose: Defines the event Bus module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const EventEmitter = require('events');
const baseLogger = require('../globals/logger');
const baseLogger = require('../../globals/logger');
const logger = baseLogger.child ? baseLogger.child('eventBus') : baseLogger;
const eventBus = new EventEmitter();
@@ -1,9 +1,12 @@
// health Service
// Purpose: Defines the health Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const fsp = require('fs/promises');
const path = require('path');
const roverManager = require('./roverManager');
const { getRoomCameras } = require('./roomCameraService');
const { getRoomCameraState } = require('./roomCameraSnapshotService');
const { getReplayHealthSnapshot } = require('./replayEngineV2');
const roverManager = require('../roverManager');
const { getRoomCameras } = require('../roomCameraService');
const { getRoomCameraState } = require('../roomCameraSnapshotService');
const { getReplayHealthSnapshot } = require('../replayEngineV2');
const ROVER_SNAPSHOT_DIR = process.env.ROVER_SNAPSHOT_DIR || '/var/lib/rover-snapshots';
const HEALTH_INTERVAL_MS = 5000;
@@ -1,15 +1,18 @@
// home Assistant Service
// Purpose: Defines the home Assistant Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const EventEmitter = require('events');
const WebSocket = require('ws');
const { createConnection, subscribeEntities, callService, Auth } = require('home-assistant-js-websocket');
const io = require('../globals/io');
const logger = require('../globals/logger').child('homeAssistantService');
const { loadConfig } = require('../helpers/configLoader');
const { getMode, MODES, modeEvents } = require('./modeManager');
const { isAdmin, isLockdownAdmin } = require('./roleService');
const roverManager = require('./roverManager');
const { issueCommand } = require('./commandService');
const { publishEvent } = require('./eventBus');
const { getActiveDrivers, turnEvents } = require('./turnService');
const io = require('../../globals/io');
const logger = require('../../globals/logger').child('homeAssistantService');
const { loadConfig } = require('../../helpers/configLoader');
const { getMode, MODES, modeEvents } = require('../modeManager');
const { isAdmin, isLockdownAdmin } = require('../roleService');
const roverManager = require('../roverManager');
const { issueCommand } = require('../commandService');
const { publishEvent } = require('../eventBus');
const { getActiveDrivers, turnEvents } = require('../turnService');
// home-assistant-js-websocket expects a global WebSocket in Node.
if (!global.WebSocket) {
-7
View File
@@ -1,7 +0,0 @@
const { httpServer } = require('../globals/http');
const config = require('../globals/config');
const logger = require('../globals/logger').child('httpServer');
httpServer.listen(config.port, () => {
logger.info(`Server listening on :${config.port}`);
});
+10
View File
@@ -0,0 +1,10 @@
// http Server
// Purpose: Defines the http Server module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const { httpServer } = require('../../globals/http');
const config = require('../../globals/config');
const logger = require('../../globals/logger').child('httpServer');
httpServer.listen(config.port, () => {
logger.info(`Server listening on :${config.port}`);
});
@@ -1,12 +1,15 @@
// human Alert Button Service
// Purpose: Defines the human Alert Button Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const sharp = require('sharp');
const logger = require('../globals/logger').child('humanAlertButton');
const { subscribe, publishEvent } = require('./eventBus');
const { getMode, MODES, setMode } = require('./modeManager');
const { issueCommand } = require('./commandService');
const roverManager = require('./roverManager');
const { toggleLightsLockedOn } = require('./homeAssistantService');
const { getRoomCameras } = require('./roomCameraService');
const { getRoomCameraState } = require('./roomCameraSnapshotService');
const logger = require('../../globals/logger').child('humanAlertButton');
const { subscribe, publishEvent } = require('../eventBus');
const { getMode, MODES, setMode } = require('../modeManager');
const { issueCommand } = require('../commandService');
const roverManager = require('../roverManager');
const { toggleLightsLockedOn } = require('../homeAssistantService');
const { getRoomCameras } = require('../roomCameraService');
const { getRoomCameraState } = require('../roomCameraSnapshotService');
const HA_BUTTON_EVENT_TYPE = 'ha.button.action';
const HUMAN_ALERT_ACTION = 'humanAlert';
@@ -1,6 +1,9 @@
// identity Service
// Purpose: Defines the identity Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const fs = require('fs');
const crypto = require('crypto');
const { getSocketIp, normalizeIp } = require('../helpers/ipResolver');
const { getSocketIp, normalizeIp } = require('../../helpers/ipResolver');
const COOKIE_USER_ID_RE = /^cu_[a-f0-9]{32}$/;
@@ -1,14 +1,17 @@
// llm Commentary Service
// Purpose: Defines the llm Commentary Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const fsp = require('fs/promises');
const path = require('path');
const { Ollama } = require('ollama');
const io = require('../globals/io');
const logger = require('../globals/logger').child('llmCommentary');
const { loadConfig } = require('../helpers/configLoader');
const { getRole, roleEvents } = require('./roleService');
const roverManager = require('./roverManager');
const { getActiveDrivers } = require('./turnService');
const { getNickname } = require('./nicknameService');
const { getRecentMessages, sendSystemMessage } = require('./chatService');
const io = require('../../globals/io');
const logger = require('../../globals/logger').child('llmCommentary');
const { loadConfig } = require('../../helpers/configLoader');
const { getRole, roleEvents } = require('../roleService');
const roverManager = require('../roverManager');
const { getActiveDrivers } = require('../turnService');
const { getNickname } = require('../nicknameService');
const { getRecentMessages, sendSystemMessage } = require('../chatService');
const PROMPT_PATH = path.join(__dirname, '..', '..', 'prompts', 'commentary_system.txt');
const DEFAULT_FREQUENCY_MS = 0;
@@ -1,6 +1,9 @@
const io = require('../globals/io');
const { MODES, getMode, modeEvents } = require('./modeManager');
const { isLockdownAdmin } = require('./roleService');
// lockdown Guard
// Purpose: Defines the lockdown Guard module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const io = require('../../globals/io');
const { MODES, getMode, modeEvents } = require('../modeManager');
const { isLockdownAdmin } = require('../roleService');
function disconnectForLockdown(socket) {
socket.emit('lockdown', { message: 'Server is in lockdown mode' });
@@ -1,6 +1,9 @@
// log Stream Service
// Purpose: Defines the log Stream Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const { v4: uuidv4 } = require('uuid');
const io = require('../globals/io');
const loggerRoot = require('../globals/logger');
const io = require('../../globals/io');
const loggerRoot = require('../../globals/logger');
const logger = loggerRoot.child('logStream');
const MAX_HISTORY = 200;
@@ -1,9 +1,12 @@
// mode Manager
// Purpose: Defines the mode Manager module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const EventEmitter = require('events');
const io = require('../globals/io');
const { sendAlert } = require('./alertService');
const io = require('../../globals/io');
const { sendAlert } = require('../alertService');
const ALERT_COLOR = '#ff9800';
const { isAdmin, isLockdownAdmin } = require('./roleService');
const { publishEvent } = require('./eventBus');
const { isAdmin, isLockdownAdmin } = require('../roleService');
const { publishEvent } = require('../eventBus');
const MODES = {
OPEN: 'open',
@@ -1,15 +1,18 @@
// neato Service
// Purpose: Defines the neato Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const EventEmitter = require('events');
const io = require('../globals/io');
const logger = require('../globals/logger').child('neatoService');
const { loadConfig } = require('../helpers/configLoader');
const { isVerified } = require('./verificationService');
const io = require('../../globals/io');
const logger = require('../../globals/logger').child('neatoService');
const { loadConfig } = require('../../helpers/configLoader');
const { isVerified } = require('../verificationService');
const {
homeAssistantEvents,
getRawEntitySnapshot,
callHomeAssistantService,
isConnected: isHomeAssistantConnected,
enabled: homeAssistantEnabled,
} = require('./homeAssistantService');
} = require('../homeAssistantService');
const events = new EventEmitter();
const config = loadConfig();
@@ -1,6 +1,9 @@
// nickname Service
// Purpose: Defines the nickname Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const EventEmitter = require('events');
const io = require('../globals/io');
const logger = require('../globals/logger').child('nicknameService');
const io = require('../../globals/io');
const logger = require('../../globals/logger').child('nicknameService');
const nicknameEvents = new EventEmitter();
@@ -1,12 +1,15 @@
// private Rover Access Request Service
// Purpose: Defines the private Rover Access Request Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const crypto = require('crypto');
const EventEmitter = require('events');
const io = require('../globals/io');
const logger = require('../globals/logger').child('privateRoverAccessRequest');
const { publishEvent } = require('./eventBus');
const roverManager = require('./roverManager');
const { getNickname } = require('./nicknameService');
const { getRole, isLockdownAdmin } = require('./roleService');
const { getSocketIp, normalizeIp } = require('../helpers/ipResolver');
const io = require('../../globals/io');
const logger = require('../../globals/logger').child('privateRoverAccessRequest');
const { publishEvent } = require('../eventBus');
const roverManager = require('../roverManager');
const { getNickname } = require('../nicknameService');
const { getRole, isLockdownAdmin } = require('../roleService');
const { getSocketIp, normalizeIp } = require('../../helpers/ipResolver');
const requestEvents = new EventEmitter();
const REQUEST_COOLDOWN_MS = 15 * 1000;
@@ -200,7 +203,7 @@ function tryAssignClosedPrivateRover(socket, roverId) {
});
roverManager.managerEvents.emit('switch', { socketId: socket.id, roverId: String(roverId) });
try {
const assignmentService = require('./assignmentService');
const assignmentService = require('../assignmentService');
assignmentService.moveAssignment(socket, String(roverId), { releasePrevious: false });
} catch (err) {
logger.warn('Failed to move assignment after private access grant', { socketId: socket.id, error: err.message });
@@ -1,5 +0,0 @@
const { buildReplayVideo } = require('./replayEngineV2');
module.exports = {
buildReplayVideo,
};
@@ -0,0 +1,8 @@
// replay Build Service
// Purpose: Defines the replay Build Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const { buildReplayVideo } = require('../replayEngineV2');
module.exports = {
buildReplayVideo,
};
@@ -1,3 +1,6 @@
// replay Engine V2
// Purpose: Defines the replay Engine V2 module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const { spawn, execFile } = require('child_process');
const fsp = require('fs/promises');
const fs = require('fs');
@@ -5,13 +8,13 @@ const os = require('os');
const path = require('path');
const { promisify } = require('util');
const EventEmitter = require('events');
const logger = require('../globals/logger').child('replayEngineV2');
const roverManager = require('./roverManager');
const { getRoomCameras, roomCameraEvents } = require('./roomCameraService');
const io = require('../globals/io');
const { getActiveDrivers } = require('./turnService');
const { getNickname } = require('./nicknameService');
const { getRecentMessages } = require('./chatService');
const logger = require('../../globals/logger').child('replayEngineV2');
const roverManager = require('../roverManager');
const { getRoomCameras, roomCameraEvents } = require('../roomCameraService');
const io = require('../../globals/io');
const { getActiveDrivers } = require('../turnService');
const { getNickname } = require('../nicknameService');
const { getRecentMessages } = require('../chatService');
const sharp = require('sharp');
const execFileAsync = promisify(execFile);
@@ -1,3 +1,6 @@
// replay Service
// Purpose: Defines the replay Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const EventEmitter = require('events');
const COOLDOWN_MS = 10 * 1000;
@@ -1,12 +1,15 @@
const io = require('../globals/io');
const logger = require('../globals/logger').child('replaySocket');
const { getMode, MODES } = require('./modeManager');
const { publishEvent } = require('./eventBus');
const { tryTriggerReplay } = require('./replayService');
const { validateSources, getDefaultWebSources } = require('./replaySourceService');
const assignmentService = require('./assignmentService');
const { getNickname } = require('./nicknameService');
const { loadConfig } = require('../helpers/configLoader');
// replay Socket Service
// Purpose: Defines the replay Socket Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const io = require('../../globals/io');
const logger = require('../../globals/logger').child('replaySocket');
const { getMode, MODES } = require('../modeManager');
const { publishEvent } = require('../eventBus');
const { tryTriggerReplay } = require('../replayService');
const { validateSources, getDefaultWebSources } = require('../replaySourceService');
const assignmentService = require('../assignmentService');
const { getNickname } = require('../nicknameService');
const { loadConfig } = require('../../helpers/configLoader');
const config = loadConfig();
const discordConfig = config.discord || {};
@@ -1,5 +1,8 @@
const roverManager = require('./roverManager');
const { getRoomCameras } = require('./roomCameraService');
// replay Source Service
// Purpose: Defines the replay Source Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const roverManager = require('../roverManager');
const { getRoomCameras } = require('../roomCameraService');
function getReplaySources(socket = null) {
const roster = socket ? roverManager.getRosterForSocket(socket) : roverManager.getRoster();
@@ -1,3 +1,6 @@
// role Service
// Purpose: Defines the role Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const EventEmitter = require('events');
const roleEvents = new EventEmitter();
@@ -1,3 +1,6 @@
// room Camera Replay Service
// Purpose: Defines the room Camera Replay Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const { execFile } = require('child_process');
const EventEmitter = require('events');
const fsp = require('fs/promises');
@@ -5,8 +8,8 @@ const os = require('os');
const path = require('path');
const { promisify } = require('util');
const logger = require('../globals/logger').child('roomCameraReplay');
const { getRoomCamera, getRoomCameras } = require('./roomCameraService');
const logger = require('../../globals/logger').child('roomCameraReplay');
const { getRoomCamera, getRoomCameras } = require('../roomCameraService');
const execFileAsync = promisify(execFile);
@@ -1,6 +1,9 @@
// room Camera Service
// Purpose: Defines the room Camera Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const EventEmitter = require('events');
const logger = require('../globals/logger').child('roomCameraService');
const { loadConfig } = require('../helpers/configLoader');
const logger = require('../../globals/logger').child('roomCameraService');
const { loadConfig } = require('../../helpers/configLoader');
const events = new EventEmitter();
const config = loadConfig();
@@ -1,8 +1,11 @@
// room Camera Snapshot Service
// Purpose: Defines the room Camera Snapshot Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const EventEmitter = require('events');
const http = require('http');
const https = require('https');
const logger = require('../globals/logger').child('roomCameraSnapshot');
const { getRoomCameras, roomCameraEvents } = require('./roomCameraService');
const logger = require('../../globals/logger').child('roomCameraSnapshot');
const { getRoomCameras, roomCameraEvents } = require('../roomCameraService');
const POLL_INTERVAL_MS = 67;
const FETCH_TIMEOUT_MS = 2000;
@@ -1,9 +1,12 @@
const io = require('../globals/io');
const logger = require('../globals/logger').child('roomCameraSocket');
const { getMode, MODES } = require('./modeManager');
const { isAdmin, isLockdownAdmin, getRole } = require('./roleService');
const { getRoomCamera, getRoomCameras } = require('./roomCameraService');
const { roomCameraStreamEvents, getRoomCameraState } = require('./roomCameraSnapshotService');
// room Camera Socket Service
// Purpose: Defines the room Camera Socket Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const io = require('../../globals/io');
const logger = require('../../globals/logger').child('roomCameraSocket');
const { getMode, MODES } = require('../modeManager');
const { isAdmin, isLockdownAdmin, getRole } = require('../roleService');
const { getRoomCamera, getRoomCameras } = require('../roomCameraService');
const { roomCameraStreamEvents, getRoomCameraState } = require('../roomCameraSnapshotService');
const SUBSCRIBE_LIMIT = 50;
const SUBSCRIBE_WINDOW_MS = 10000;
@@ -1,9 +1,12 @@
const roverWSS = require('../globals/ws');
const logger = require('../globals/logger').child('roverConnection');
const roverManager = require('./roverManager');
const { sendAlert } = require('./alertService');
// rover Connection Service
// Purpose: Defines the rover Connection Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const roverWSS = require('../../globals/ws');
const logger = require('../../globals/logger').child('roverConnection');
const roverManager = require('../roverManager');
const { sendAlert } = require('../alertService');
const ALERT_COLOR = '#00bcd4';
const { handleAck } = require('./commandService');
const { handleAck } = require('../commandService');
function coerceBool(value) {
if (typeof value === 'boolean') return value;
@@ -1,18 +1,21 @@
// rover Manager
// Purpose: Defines the rover Manager module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const EventEmitter = require('events');
const io = require('../globals/io');
const logger = require('../globals/logger').child('roverManager');
const { sendAlert } = require('./alertService');
const io = require('../../globals/io');
const logger = require('../../globals/logger').child('roverManager');
const { sendAlert } = require('../alertService');
const ALERT_COLOR = '#8bc34a';
const { parseSensorFrame } = require('../helpers/sensorDecoder');
const { MODES, getMode } = require('./modeManager');
const { isAdmin, isLockdownAdmin, roleEvents } = require('./roleService');
const { publishEvent } = require('./eventBus');
const videoSessions = require('./videoSessions');
const { parseSensorFrame } = require('../../helpers/sensorDecoder');
const { MODES, getMode } = require('../modeManager');
const { isAdmin, isLockdownAdmin, roleEvents } = require('../roleService');
const { publishEvent } = require('../eventBus');
const videoSessions = require('../videoSessions');
const rovers = new Map(); // roverId -> record
const socketToRovers = new Map(); // socketId -> Set(roverId)
const spectatorSockets = new Set();
const turnService = require('./turnService');
const turnService = require('../turnService');
const managerEvents = new EventEmitter();
const DOCK_GUARD_WINDOW_MS = 2 * 1000;
const IDLE_UNDOCKED_MS = 2 * 60 * 1000;
@@ -186,7 +189,7 @@ function getControlDenialReason(record, socket, options = {}) {
if (mode === MODES.LOCKDOWN && !isLockdownAdmin(socket)) {
return 'Server in lockdown';
}
const { isDeterred } = require('./verificationService');
const { isDeterred } = require('../verificationService');
if (!isAdmin(socket) && isDeterred(socket)) {
return 'Not authorized';
}
@@ -205,7 +208,7 @@ function getControlDenialReason(record, socket, options = {}) {
if (isLockdownAdmin(socket)) {
return null;
}
const { isVerified } = require('./verificationService');
const { isVerified } = require('../verificationService');
if (!isVerified(socket)) {
return 'Private rover requires verification';
}
@@ -306,7 +309,7 @@ function removeRover(id) {
}
function sendPrivateToggleTTS(roverId, open, reason) {
const { issueCommand } = require('./commandService');
const { issueCommand } = require('../commandService');
let text = open ? 'Private rover is now open.' : 'Private rover is now closed.';
if (!open && reason === 'auto_idle') {
text = 'Private rover closed due to inactivity.';
@@ -600,7 +603,7 @@ function stopSafetyBackoffTimer(roverId) {
function triggerSafetyAction(record, mode, options = {}) {
if (!record) return;
const roverId = record.id;
const { issueCommand, setDriveCooldown } = require('./commandService');
const { issueCommand, setDriveCooldown } = require('../commandService');
const now = Date.now();
const cooldownMs = clampInt(options.cooldownMs, 100, 10000, DEFAULT_PRIVATE_SAFETY.triggerCooldownMs);
const backoffMs = clampInt(options.backoffMs, 50, 5000, 0);
@@ -917,7 +920,7 @@ function attemptDockGuard(roverId) {
stopDockGuard(roverId);
return;
}
const { issueCommand } = require('./commandService');
const { issueCommand } = require('../commandService');
try {
issueCommand(roverId, { type: 'sensorStream', sensorStream: { enable: true } });
issueCommand(roverId, { type: 'raw', raw: DOCK_COMMAND_BASE64 });
@@ -928,7 +931,7 @@ function attemptDockGuard(roverId) {
function handleIdleUndock(undockedRecord) {
if (!undockedRecord || undockedRecord.drivers.size > 0) return;
const now = Date.now();
const { getRecentDriveActivity, setDriveCooldown, issueCommand } = require('./commandService');
const { getRecentDriveActivity, setDriveCooldown, issueCommand } = require('../commandService');
const candidates = getRecentDriveActivity(DOCK_GUARD_WINDOW_MS, { excludeAdmins: true })
.filter((candidate) => candidate.roverId !== undockedRecord.id);
if (candidates.length === 0) return;
@@ -965,7 +968,7 @@ function handleIdleUndock(undockedRecord) {
function nudgeRover(roverId, direction = 'backward') {
if (!roverId) return;
const { issueCommand } = require('./commandService');
const { issueCommand } = require('../commandService');
clearTimeout(backoffTimers.get(roverId));
const speed = direction === 'forward' ? BACKOFF_SPEED : -BACKOFF_SPEED;
try {
@@ -1,8 +1,11 @@
// rover Snapshot Service
// Purpose: Defines the rover Snapshot Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const EventEmitter = require('events');
const fs = require('fs/promises');
const path = require('path');
const logger = require('../globals/logger').child('roverSnapshot');
const roverManager = require('./roverManager');
const logger = require('../../globals/logger').child('roverSnapshot');
const roverManager = require('../roverManager');
const SNAPSHOT_DIR = process.env.ROVER_SNAPSHOT_DIR || '/var/lib/rover-snapshots';
const POLL_INTERVAL_MS = 300;
@@ -1,9 +1,12 @@
const io = require('../globals/io');
const logger = require('../globals/logger').child('roverSnapshotSocket');
const { getMode, MODES } = require('./modeManager');
const { isAdmin, isLockdownAdmin, getRole } = require('./roleService');
const roverManager = require('./roverManager');
const { roverSnapshotEvents, getRoverSnapshotState } = require('./roverSnapshotService');
// rover Snapshot Socket Service
// Purpose: Defines the rover Snapshot Socket Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const io = require('../../globals/io');
const logger = require('../../globals/logger').child('roverSnapshotSocket');
const { getMode, MODES } = require('../modeManager');
const { isAdmin, isLockdownAdmin, getRole } = require('../roleService');
const roverManager = require('../roverManager');
const { roverSnapshotEvents, getRoverSnapshotState } = require('../roverSnapshotService');
const SUBSCRIBE_LIMIT = 50;
const SUBSCRIBE_WINDOW_MS = 10000;
@@ -1,8 +1,11 @@
// server Control Service
// Purpose: Defines the server Control Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const { spawn } = require('child_process');
const io = require('../globals/io');
const logger = require('../globals/logger').child('serverControlService');
const { isAdmin } = require('./roleService');
const { sendAlert } = require('./alertService');
const io = require('../../globals/io');
const logger = require('../../globals/logger').child('serverControlService');
const { isAdmin } = require('../roleService');
const { sendAlert } = require('../alertService');
const ALERT_COLOR = '#ff5722';
let rebootPending = false;
@@ -0,0 +1,24 @@
// session Service constants
// Purpose: Defines timing and static social/config constants used by session synchronization behavior.
// Scope: Keeps runtime behavior unchanged while isolating constants from orchestration logic.
const { loadConfig } = require('../../helpers/configLoader');
const config = loadConfig();
const discordInvite = config.discord?.invite || null;
const kofiLink = config.kofi?.link || null;
const serverTimezone = config.timezone || null;
const configuredSocials = Array.isArray(config.socials) ? config.socials : null;
const ACTIVITY_SYNC_COOLDOWN_MS = 3000;
const NIGHT_VISION_SYNC_COOLDOWN_MS = 1000;
const PERIODIC_SYNC_MS = 20000;
module.exports = {
discordInvite,
kofiLink,
serverTimezone,
configuredSocials,
ACTIVITY_SYNC_COOLDOWN_MS,
NIGHT_VISION_SYNC_COOLDOWN_MS,
PERIODIC_SYNC_MS,
};
@@ -0,0 +1,33 @@
// session Service filters
// Purpose: Defines visibility-filter helpers for per-socket session payload shaping.
// Scope: Keeps runtime behavior unchanged while isolating rover/queue filtering concerns from orchestration.
const roverManager = require('../roverManager');
function filterVisibleRoverId(socket, roverId) {
if (!roverId) return null;
return roverManager.canSeeRover(roverId, socket) ? roverId : null;
}
function filterActiveDriversForSocket(activeDrivers = {}, socket) {
const next = {};
Object.entries(activeDrivers || {}).forEach(([roverId, socketId]) => {
if (!roverManager.canSeeRover(roverId, socket)) return;
next[roverId] = socketId;
});
return next;
}
function filterTurnQueuesForSocket(turnQueues = {}, socket) {
const next = {};
Object.entries(turnQueues || {}).forEach(([roverId, info]) => {
if (!roverManager.canSeeRover(roverId, socket)) return;
next[roverId] = info;
});
return next;
}
module.exports = {
filterVisibleRoverId,
filterActiveDriversForSocket,
filterTurnQueuesForSocket,
};
@@ -1,53 +1,57 @@
const io = require('../globals/io');
const logger = require('../globals/logger').child('sessionService');
const { getRole, roleEvents } = require('./roleService');
const { getMode, modeEvents } = require('./modeManager');
const roverManager = require('./roverManager');
// session Service
// Purpose: Defines the session Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const io = require('../../globals/io');
const logger = require('../../globals/logger').child('sessionService');
const { getRole, roleEvents } = require('../roleService');
const { getMode, modeEvents } = require('../modeManager');
const roverManager = require('../roverManager');
const { managerEvents } = roverManager;
const assignmentService = require('./assignmentService');
const { getActiveDrivers, getTurnQueues, turnEvents } = require('./turnService');
const { getRoomCameras, roomCameraEvents } = require('./roomCameraService');
const { getState: getHomeAssistantState, homeAssistantEvents } = require('./homeAssistantService');
const { getState: getNeatoState, neatoEvents } = require('./neatoService');
const { getNickname, nicknameEvents } = require('./nicknameService');
const assignmentService = require('../assignmentService');
const { getActiveDrivers, getTurnQueues, turnEvents } = require('../turnService');
const { getRoomCameras, roomCameraEvents } = require('../roomCameraService');
const { getState: getHomeAssistantState, homeAssistantEvents } = require('../homeAssistantService');
const { getState: getNeatoState, neatoEvents } = require('../neatoService');
const { getNickname, nicknameEvents } = require('../nicknameService');
const {
getVerificationStateForSocket,
getModerationStateForSocket,
getIdentitySummary,
verificationEvents,
} = require('./verificationService');
} = require('../verificationService');
const {
getStateForSocket: getPrivateRoverAccessStateForSocket,
requestEvents: privateRoverAccessRequestEvents,
} = require('./privateRoverAccessRequestService');
const { getReplayState, replayEvents } = require('./replayService');
const { getReplaySources } = require('./replaySourceService');
const { getHealthSnapshot } = require('./healthService');
const { loadConfig } = require('../helpers/configLoader');
const { getCommunityGoal } = require('./communityGoalService');
const { getAdminReason } = require('./adminReasonService');
const { subscribe } = require('./eventBus');
const { getSocketIp, isLocalNetwork } = require('../helpers/ipResolver');
const { getAudioForwardState, audioForwardEvents } = require('./audioForwardService');
const { getAudioLevels, audioLevelsEvents } = require('./audioLevelsService');
const { getButtonBoxState } = require('./buttonBoxService');
const config = loadConfig();
const discordInvite = config.discord?.invite || null;
const kofiLink = config.kofi?.link || null;
const serverTimezone = config.timezone || null;
const configuredSocials = Array.isArray(config.socials) ? config.socials : null;
} = require('../privateRoverAccessRequestService');
const { getReplayState, replayEvents } = require('../replayService');
const { getReplaySources } = require('../replaySourceService');
const { getHealthSnapshot } = require('../healthService');
const { getCommunityGoal } = require('../communityGoalService');
const { getAdminReason } = require('../adminReasonService');
const { subscribe } = require('../eventBus');
const { getSocketIp, isLocalNetwork } = require('../../helpers/ipResolver');
const { getAudioForwardState, audioForwardEvents } = require('../audioForwardService');
const { getAudioLevels, audioLevelsEvents } = require('../audioLevelsService');
const { getButtonBoxState } = require('../buttonBoxService');
const {
discordInvite,
kofiLink,
serverTimezone,
configuredSocials,
ACTIVITY_SYNC_COOLDOWN_MS,
NIGHT_VISION_SYNC_COOLDOWN_MS,
PERIODIC_SYNC_MS,
} = require('./constants');
const { getState, setState } = require('./state');
const {
filterVisibleRoverId,
filterActiveDriversForSocket,
filterTurnQueuesForSocket,
} = require('./filters');
logger.info('Discord invite loaded:', discordInvite ? 'present' : 'not configured');
logger.info('Ko-fi link loaded:', kofiLink ? 'present' : 'not configured');
logger.info('Socials config loaded:', configuredSocials?.length ? `${configuredSocials.length} entries` : 'not configured');
const ACTIVITY_SYNC_COOLDOWN_MS = 3000;
const NIGHT_VISION_SYNC_COOLDOWN_MS = 1000;
let lastActivitySync = 0;
let pendingActivitySync = null;
let lastNightVisionSync = 0;
let pendingNightVisionSync = null;
function buildUserEntry(socket) {
if (!socket) return null;
const role = getRole(socket);
@@ -61,29 +65,6 @@ function buildUserEntry(socket) {
};
}
function filterVisibleRoverId(socket, roverId) {
if (!roverId) return null;
return roverManager.canSeeRover(roverId, socket) ? roverId : null;
}
function filterActiveDriversForSocket(activeDrivers = {}, socket) {
const next = {};
Object.entries(activeDrivers || {}).forEach(([roverId, socketId]) => {
if (!roverManager.canSeeRover(roverId, socket)) return;
next[roverId] = socketId;
});
return next;
}
function filterTurnQueuesForSocket(turnQueues = {}, socket) {
const next = {};
Object.entries(turnQueues || {}).forEach(([roverId, info]) => {
if (!roverManager.canSeeRover(roverId, socket)) return;
next[roverId] = info;
});
return next;
}
function buildSession(socket) {
const users = Array.from(io.sockets.sockets.values())
.map((sock) => buildUserEntry(sock))
@@ -182,29 +163,30 @@ modeEvents.on('change', () => {
});
managerEvents.on('rover', (event = {}) => {
const state = getState();
if (event.action === 'nightVision') {
const now = Date.now();
const elapsed = now - lastNightVisionSync;
const elapsed = now - state.lastNightVisionSync;
if (elapsed >= NIGHT_VISION_SYNC_COOLDOWN_MS) {
lastNightVisionSync = now;
setState({ lastNightVisionSync: now });
logger.info('Night vision update; syncing all clients (immediate)');
syncAll();
return;
}
if (!pendingNightVisionSync) {
if (!state.pendingNightVisionSync) {
const delay = NIGHT_VISION_SYNC_COOLDOWN_MS - elapsed;
pendingNightVisionSync = setTimeout(() => {
lastNightVisionSync = Date.now();
pendingNightVisionSync = null;
const timer = setTimeout(() => {
setState({ lastNightVisionSync: Date.now(), pendingNightVisionSync: null });
logger.info('Night vision update; syncing all clients (delayed)');
syncAll();
}, delay);
setState({ pendingNightVisionSync: timer });
}
return;
}
if (pendingNightVisionSync) {
clearTimeout(pendingNightVisionSync);
pendingNightVisionSync = null;
if (state.pendingNightVisionSync) {
clearTimeout(state.pendingNightVisionSync);
setState({ pendingNightVisionSync: null });
}
logger.info('Rover roster change; syncing all clients');
syncAll();
@@ -244,30 +226,31 @@ turnEvents.on('activeDriver', () => {
syncAll();
});
turnEvents.on('queue', (event = {}) => {
const state = getState();
const { reason } = event;
if (reason === 'activity') {
const now = Date.now();
const elapsed = now - lastActivitySync;
const elapsed = now - state.lastActivitySync;
if (elapsed >= ACTIVITY_SYNC_COOLDOWN_MS) {
lastActivitySync = now;
setState({ lastActivitySync: now });
logger.info('Turn activity; syncing all clients (immediate)');
syncAll();
return;
}
if (!pendingActivitySync) {
if (!state.pendingActivitySync) {
const delay = ACTIVITY_SYNC_COOLDOWN_MS - elapsed;
pendingActivitySync = setTimeout(() => {
lastActivitySync = Date.now();
pendingActivitySync = null;
const timer = setTimeout(() => {
setState({ lastActivitySync: Date.now(), pendingActivitySync: null });
logger.info('Turn activity; syncing all clients (delayed)');
syncAll();
}, delay);
setState({ pendingActivitySync: timer });
}
return;
}
if (pendingActivitySync) {
clearTimeout(pendingActivitySync);
pendingActivitySync = null;
if (state.pendingActivitySync) {
clearTimeout(state.pendingActivitySync);
setState({ pendingActivitySync: null });
}
logger.info('Turn queue change; syncing all clients');
syncAll();
@@ -345,7 +328,7 @@ audioLevelsEvents.on('change', () => {
setInterval(() => {
logger.info('Periodic session sync for all clients');
syncAll();
}, 20000);
}, PERIODIC_SYNC_MS);
module.exports = {
buildSession,
@@ -0,0 +1,36 @@
// session Service state
// Purpose: Stores session sync throttling state and timer references used across event handlers.
// Scope: Keeps runtime behavior unchanged while centralizing mutable session-sync state in one module.
let lastActivitySync = 0;
let pendingActivitySync = null;
let lastNightVisionSync = 0;
let pendingNightVisionSync = null;
function getState() {
return {
lastActivitySync,
pendingActivitySync,
lastNightVisionSync,
pendingNightVisionSync,
};
}
function setState(patch = {}) {
if (Object.prototype.hasOwnProperty.call(patch, 'lastActivitySync')) {
lastActivitySync = patch.lastActivitySync;
}
if (Object.prototype.hasOwnProperty.call(patch, 'pendingActivitySync')) {
pendingActivitySync = patch.pendingActivitySync;
}
if (Object.prototype.hasOwnProperty.call(patch, 'lastNightVisionSync')) {
lastNightVisionSync = patch.lastNightVisionSync;
}
if (Object.prototype.hasOwnProperty.call(patch, 'pendingNightVisionSync')) {
pendingNightVisionSync = patch.pendingNightVisionSync;
}
}
module.exports = {
getState,
setState,
};
@@ -0,0 +1,26 @@
// actions
// Purpose: Defines the actions module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
function stopRover(roverId) {
try {
const { issueCommand } = require('../commandService');
issueCommand(roverId, { type: 'drive', driveDirect: { left: 0, right: 0 } });
issueCommand(roverId, { type: 'motors', motorPwm: { main: 0, side: 0, vacuum: 0 } });
} catch (err) {
// best effort; log elsewhere if needed
}
}
function removeDriverCompletely(roverId, socketId) {
try {
const assignmentService = require('../assignmentService');
assignmentService.forceRelease(roverId, socketId);
} catch (err) {
// best effort; log elsewhere if needed
}
}
module.exports = {
stopRover,
removeDriverCompletely,
};
@@ -0,0 +1,16 @@
// constants
// Purpose: Defines the constants module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const ALERT_COLOR = '#ff5722';
const TURN_DURATION_MS = 60 * 1000;
const IDLE_TIMEOUT_MS = 7 * 1000;
const MAX_IDLE_SKIPS = 3;
const STALE_REAPER_MS = 5000;
module.exports = {
ALERT_COLOR,
TURN_DURATION_MS,
IDLE_TIMEOUT_MS,
MAX_IDLE_SKIPS,
STALE_REAPER_MS,
};
@@ -1,21 +1,30 @@
const EventEmitter = require('events');
const io = require('../globals/io');
const { sendAlert } = require('./alertService');
const ALERT_COLOR = '#ff5722';
const { MODES, getMode, modeEvents } = require('./modeManager');
const driverQueues = new Map(); // roverId -> { queue: [], current: socketId, timer: Timeout | null }
const activeDrivers = new Map();
const TURN_DURATION_MS = 60 * 1000;
const IDLE_TIMEOUT_MS = 7 * 1000;
const MAX_IDLE_SKIPS = 3;
const STALE_REAPER_MS = 5000;
const turnEvents = new EventEmitter();
const turnDeadlines = new Map(); // roverId -> timestamp when current driver expires
const idleDeadlines = new Map(); // roverId -> timestamp when idle skip will happen
const idleTimers = new Map(); // roverId -> Timeout
const idleSkips = new Map(); // roverId -> Map(socketId -> count)
const idleDisarmed = new Map(); // roverId -> boolean, true once driver has acted this turn
// turn Service
// Purpose: Defines the turn Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const io = require('../../globals/io');
const { sendAlert } = require('../alertService');
const { MODES, getMode, modeEvents } = require('../modeManager');
const {
ALERT_COLOR,
TURN_DURATION_MS,
IDLE_TIMEOUT_MS,
MAX_IDLE_SKIPS,
STALE_REAPER_MS,
} = require('./constants');
const {
driverQueues,
activeDrivers,
turnEvents,
turnDeadlines,
idleDeadlines,
idleTimers,
idleSkips,
idleDisarmed,
ensureQueue,
setActiveDriver,
getActiveDrivers,
} = require('./state');
const { stopRover, removeDriverCompletely } = require('./actions');
function driverAdded(roverId, socketId, force) {
const queue = ensureQueue(roverId);
@@ -79,13 +88,6 @@ function isQueuedDriver(roverId, socketId) {
return queue.queue.includes(socketId);
}
function ensureQueue(roverId) {
if (!driverQueues.has(roverId)) {
driverQueues.set(roverId, { queue: [], current: null, timer: null });
}
return driverQueues.get(roverId);
}
function syncState(roverId) {
const mode = getMode();
const queue = ensureQueue(roverId);
@@ -222,23 +224,6 @@ function advanceTurn(roverId) {
scheduleIdleTimer(roverId);
}
function setActiveDriver(roverId, socketId) {
if (!socketId) {
activeDrivers.delete(roverId);
} else {
activeDrivers.set(roverId, socketId);
}
turnEvents.emit('activeDriver', { roverId, socketId });
}
function getActiveDrivers() {
const map = {};
activeDrivers.forEach((socketId, roverId) => {
map[roverId] = socketId;
});
return map;
}
function getTurnQueues() {
const mode = getMode();
const payload = {};
@@ -254,25 +239,6 @@ function getTurnQueues() {
return payload;
}
function stopRover(roverId) {
try {
const { issueCommand } = require('./commandService');
issueCommand(roverId, { type: 'drive', driveDirect: { left: 0, right: 0 } });
issueCommand(roverId, { type: 'motors', motorPwm: { main: 0, side: 0, vacuum: 0 } });
} catch (err) {
// best effort; log elsewhere if needed
}
}
function removeDriverCompletely(roverId, socketId) {
try {
const assignmentService = require('./assignmentService');
assignmentService.forceRelease(roverId, socketId);
} catch (err) {
// best effort; log elsewhere if needed
}
}
function recordActivity(roverId, socketId) {
const queue = driverQueues.get(roverId);
if (!queue || queue.current !== socketId) return;
+51
View File
@@ -0,0 +1,51 @@
// state
// Purpose: Defines the state module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const EventEmitter = require('events');
const driverQueues = new Map();
const activeDrivers = new Map();
const turnEvents = new EventEmitter();
const turnDeadlines = new Map();
const idleDeadlines = new Map();
const idleTimers = new Map();
const idleSkips = new Map();
const idleDisarmed = new Map();
function ensureQueue(roverId) {
if (!driverQueues.has(roverId)) {
driverQueues.set(roverId, { queue: [], current: null, timer: null });
}
return driverQueues.get(roverId);
}
function setActiveDriver(roverId, socketId) {
if (!socketId) {
activeDrivers.delete(roverId);
} else {
activeDrivers.set(roverId, socketId);
}
turnEvents.emit('activeDriver', { roverId, socketId });
}
function getActiveDrivers() {
const map = {};
activeDrivers.forEach((socketId, roverId) => {
map[roverId] = socketId;
});
return map;
}
module.exports = {
driverQueues,
activeDrivers,
turnEvents,
turnDeadlines,
idleDeadlines,
idleTimers,
idleSkips,
idleDisarmed,
ensureQueue,
setActiveDriver,
getActiveDrivers,
};
@@ -1,13 +1,16 @@
// verification Service
// Purpose: Defines the verification Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const path = require('path');
const crypto = require('crypto');
const net = require('net');
const EventEmitter = require('events');
const io = require('../globals/io');
const logger = require('../globals/logger').child('verificationService');
const { publishEvent } = require('./eventBus');
const { normalizeIp } = require('../helpers/ipResolver');
const { getNickname, setNickname } = require('./nicknameService');
const { getRole, roleEvents } = require('./roleService');
const io = require('../../globals/io');
const logger = require('../../globals/logger').child('verificationService');
const { publishEvent } = require('../eventBus');
const { normalizeIp } = require('../../helpers/ipResolver');
const { getNickname, setNickname } = require('../nicknameService');
const { getRole, roleEvents } = require('../roleService');
const {
sanitizeNickname,
normalizeCookieUserId,
@@ -15,7 +18,7 @@ const {
generateCookieUserId,
getKnownIp,
createJsonStore,
} = require('./identityService');
} = require('../identityService');
const DATA_DIR = path.join(__dirname, '..', '..', 'data');
const STORE_PATH = path.join(DATA_DIR, 'verified-users.json');
@@ -1,15 +1,18 @@
const { app } = require('../globals/http');
const io = require('../globals/io');
const logger = require('../globals/logger').child('videoAuth');
const videoSessions = require('./videoSessions');
const { getMode, MODES } = require('./modeManager');
const { isAdmin, isLockdownAdmin, getRole } = require('./roleService');
const { isVerified } = require('./verificationService');
const turnService = require('./turnService');
const roverManager = require('./roverManager');
const { loadConfig } = require('../helpers/configLoader');
const { getRequestIp, getSocketIp, isLocalNetwork } = require('../helpers/ipResolver');
const { logAdminEvent } = require('./adminLogService');
// video Auth Service
// Purpose: Defines the video Auth Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const { app } = require('../../globals/http');
const io = require('../../globals/io');
const logger = require('../../globals/logger').child('videoAuth');
const videoSessions = require('../videoSessions');
const { getMode, MODES } = require('../modeManager');
const { isAdmin, isLockdownAdmin, getRole } = require('../roleService');
const { isVerified } = require('../verificationService');
const turnService = require('../turnService');
const roverManager = require('../roverManager');
const { loadConfig } = require('../../helpers/configLoader');
const { getRequestIp, getSocketIp, isLocalNetwork } = require('../../helpers/ipResolver');
const { logAdminEvent } = require('../adminLogService');
const config = loadConfig();
const mediaConfig = config.media || {};
@@ -1,5 +1,8 @@
// video Sessions
// Purpose: Defines the video Sessions module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const { v4: uuidv4 } = require('uuid');
const io = require('../globals/io');
const io = require('../../globals/io');
const sessions = new Map(); // sessionId -> { socketId, sourceType, sourceId }
const socketSessions = new Map(); // socketId -> Set(sessionId)
@@ -1,11 +1,14 @@
const io = require('../globals/io');
const logger = require('../globals/logger').child('videoSocket');
const { getMode, MODES } = require('./modeManager');
const { isAdmin, isLockdownAdmin, getRole } = require('./roleService');
const videoSessions = require('./videoSessions');
const roverManager = require('./roverManager');
const { loadConfig } = require('../helpers/configLoader');
const { getSocketIp, isLocalNetwork } = require('../helpers/ipResolver');
// video Socket Service
// Purpose: Defines the video Socket Service module and the helpers/state used by this service unit.
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
const io = require('../../globals/io');
const logger = require('../../globals/logger').child('videoSocket');
const { getMode, MODES } = require('../modeManager');
const { isAdmin, isLockdownAdmin, getRole } = require('../roleService');
const videoSessions = require('../videoSessions');
const roverManager = require('../roverManager');
const { loadConfig } = require('../../helpers/configLoader');
const { getSocketIp, isLocalNetwork } = require('../../helpers/ipResolver');
const config = loadConfig();
const mediaConfig = config.media || {};