diff --git a/rulesdocs/refector_rules_and_tracking.md b/rulesdocs/refector_rules_and_tracking.md index af1dadd2..9dfb2785 100644 --- a/rulesdocs/refector_rules_and_tracking.md +++ b/rulesdocs/refector_rules_and_tracking.md @@ -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 diff --git a/server/src/services/adminLogService.js b/server/src/services/adminLogService/index.js similarity index 82% rename from server/src/services/adminLogService.js rename to server/src/services/adminLogService/index.js index a64f9863..cc692615 100644 --- a/server/src/services/adminLogService.js +++ b/server/src/services/adminLogService/index.js @@ -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; diff --git a/server/src/services/adminReasonService.js b/server/src/services/adminReasonService/index.js similarity index 83% rename from server/src/services/adminReasonService.js rename to server/src/services/adminReasonService/index.js index ca35410b..f690a875 100644 --- a/server/src/services/adminReasonService.js +++ b/server/src/services/adminReasonService/index.js @@ -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'); diff --git a/server/src/services/alertService.js b/server/src/services/alertService/index.js similarity index 54% rename from server/src/services/alertService.js rename to server/src/services/alertService/index.js index afb33f22..f40807e3 100644 --- a/server/src/services/alertService.js +++ b/server/src/services/alertService/index.js @@ -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 = { diff --git a/server/src/services/assignmentService.js b/server/src/services/assignmentService/index.js similarity index 94% rename from server/src/services/assignmentService.js rename to server/src/services/assignmentService/index.js index aff8445c..231c4c6c 100644 --- a/server/src/services/assignmentService.js +++ b/server/src/services/assignmentService/index.js @@ -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 diff --git a/server/src/services/audioForwardService.js b/server/src/services/audioForwardService/index.js similarity index 96% rename from server/src/services/audioForwardService.js rename to server/src/services/audioForwardService/index.js index a883f75b..0282652f 100644 --- a/server/src/services/audioForwardService.js +++ b/server/src/services/audioForwardService/index.js @@ -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(); diff --git a/server/src/services/audioLevelsService.js b/server/src/services/audioLevelsService/index.js similarity index 87% rename from server/src/services/audioLevelsService.js rename to server/src/services/audioLevelsService/index.js index b66975a4..ead4e050 100644 --- a/server/src/services/audioLevelsService.js +++ b/server/src/services/audioLevelsService/index.js @@ -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'); diff --git a/server/src/services/authService.js b/server/src/services/authService/index.js similarity index 80% rename from server/src/services/authService.js rename to server/src/services/authService/index.js index db21efbd..cc5c491e 100644 --- a/server/src/services/authService.js +++ b/server/src/services/authService/index.js @@ -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 || []; diff --git a/server/src/services/batteryManager.js b/server/src/services/batteryManager/index.js similarity index 93% rename from server/src/services/batteryManager.js rename to server/src/services/batteryManager/index.js index b600a14a..2eed063f 100644 --- a/server/src/services/batteryManager.js +++ b/server/src/services/batteryManager/index.js @@ -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', diff --git a/server/src/services/buttonBoxService.js b/server/src/services/buttonBoxService/index.js similarity index 91% rename from server/src/services/buttonBoxService.js rename to server/src/services/buttonBoxService/index.js index 3d556fac..cdbe769e 100644 --- a/server/src/services/buttonBoxService.js +++ b/server/src/services/buttonBoxService/index.js @@ -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'); diff --git a/server/src/services/chatService.js b/server/src/services/chatService/index.js similarity index 95% rename from server/src/services/chatService.js rename to server/src/services/chatService/index.js index c9cf38ec..3143e5e4 100644 --- a/server/src/services/chatService.js +++ b/server/src/services/chatService/index.js @@ -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) diff --git a/server/src/services/commandService.js b/server/src/services/commandService/index.js similarity index 88% rename from server/src/services/commandService.js rename to server/src/services/commandService/index.js index a67bba9a..ddcdb5b6 100644 --- a/server/src/services/commandService.js +++ b/server/src/services/commandService/index.js @@ -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 diff --git a/server/src/services/communityGoalService.js b/server/src/services/communityGoalService/index.js similarity index 83% rename from server/src/services/communityGoalService.js rename to server/src/services/communityGoalService/index.js index 48d3329b..959efe21 100644 --- a/server/src/services/communityGoalService.js +++ b/server/src/services/communityGoalService/index.js @@ -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'); diff --git a/server/src/services/discordBotService.js b/server/src/services/discordBotService/index.js similarity index 98% rename from server/src/services/discordBotService.js rename to server/src/services/discordBotService/index.js index 2b4bdeb8..2edc99ca 100644 --- a/server/src/services/discordBotService.js +++ b/server/src/services/discordBotService/index.js @@ -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); diff --git a/server/src/services/discordGuildStore.js b/server/src/services/discordGuildStore/index.js similarity index 87% rename from server/src/services/discordGuildStore.js rename to server/src/services/discordGuildStore/index.js index c4146995..9e05caf1 100644 --- a/server/src/services/discordGuildStore.js +++ b/server/src/services/discordGuildStore/index.js @@ -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'); diff --git a/server/src/services/embedHttpService.js b/server/src/services/embedHttpService/index.js similarity index 58% rename from server/src/services/embedHttpService.js rename to server/src/services/embedHttpService/index.js index 18b194ab..d2ee664c 100644 --- a/server/src/services/embedHttpService.js +++ b/server/src/services/embedHttpService/index.js @@ -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 { diff --git a/server/src/services/embedService.js b/server/src/services/embedService/index.js similarity index 93% rename from server/src/services/embedService.js rename to server/src/services/embedService/index.js index 718fd347..e93fbb53 100644 --- a/server/src/services/embedService.js +++ b/server/src/services/embedService/index.js @@ -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'); diff --git a/server/src/services/eventBus.js b/server/src/services/eventBus/index.js similarity index 84% rename from server/src/services/eventBus.js rename to server/src/services/eventBus/index.js index f1d859de..7bd404d8 100644 --- a/server/src/services/eventBus.js +++ b/server/src/services/eventBus/index.js @@ -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(); diff --git a/server/src/services/healthService.js b/server/src/services/healthService/index.js similarity index 82% rename from server/src/services/healthService.js rename to server/src/services/healthService/index.js index 599bae6c..0f5a98a5 100644 --- a/server/src/services/healthService.js +++ b/server/src/services/healthService/index.js @@ -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; diff --git a/server/src/services/homeAssistantService.js b/server/src/services/homeAssistantService/index.js similarity index 96% rename from server/src/services/homeAssistantService.js rename to server/src/services/homeAssistantService/index.js index 161ed526..8baa332d 100644 --- a/server/src/services/homeAssistantService.js +++ b/server/src/services/homeAssistantService/index.js @@ -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) { diff --git a/server/src/services/httpServer.js b/server/src/services/httpServer.js deleted file mode 100644 index c98251ab..00000000 --- a/server/src/services/httpServer.js +++ /dev/null @@ -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}`); -}); diff --git a/server/src/services/httpServer/index.js b/server/src/services/httpServer/index.js new file mode 100644 index 00000000..15377326 --- /dev/null +++ b/server/src/services/httpServer/index.js @@ -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}`); +}); diff --git a/server/src/services/humanAlertButtonService.js b/server/src/services/humanAlertButtonService/index.js similarity index 85% rename from server/src/services/humanAlertButtonService.js rename to server/src/services/humanAlertButtonService/index.js index 8ffab8fb..ccbb023b 100644 --- a/server/src/services/humanAlertButtonService.js +++ b/server/src/services/humanAlertButtonService/index.js @@ -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'; diff --git a/server/src/services/identityService.js b/server/src/services/identityService/index.js similarity index 86% rename from server/src/services/identityService.js rename to server/src/services/identityService/index.js index cb67c664..be80c505 100644 --- a/server/src/services/identityService.js +++ b/server/src/services/identityService/index.js @@ -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}$/; diff --git a/server/src/services/llmCommentaryService.js b/server/src/services/llmCommentaryService/index.js similarity index 98% rename from server/src/services/llmCommentaryService.js rename to server/src/services/llmCommentaryService/index.js index 7cb0677e..81b596c4 100644 --- a/server/src/services/llmCommentaryService.js +++ b/server/src/services/llmCommentaryService/index.js @@ -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; diff --git a/server/src/services/lockdownGuard.js b/server/src/services/lockdownGuard/index.js similarity index 63% rename from server/src/services/lockdownGuard.js rename to server/src/services/lockdownGuard/index.js index cec98814..9d23a6d7 100644 --- a/server/src/services/lockdownGuard.js +++ b/server/src/services/lockdownGuard/index.js @@ -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' }); diff --git a/server/src/services/logStreamService.js b/server/src/services/logStreamService/index.js similarity index 69% rename from server/src/services/logStreamService.js rename to server/src/services/logStreamService/index.js index 4dc07a23..2f29fa14 100644 --- a/server/src/services/logStreamService.js +++ b/server/src/services/logStreamService/index.js @@ -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; diff --git a/server/src/services/modeManager.js b/server/src/services/modeManager/index.js similarity index 79% rename from server/src/services/modeManager.js rename to server/src/services/modeManager/index.js index edad9311..4af4b2d7 100644 --- a/server/src/services/modeManager.js +++ b/server/src/services/modeManager/index.js @@ -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', diff --git a/server/src/services/neatoService.js b/server/src/services/neatoService/index.js similarity index 93% rename from server/src/services/neatoService.js rename to server/src/services/neatoService/index.js index 8c434ac6..2b2e5560 100644 --- a/server/src/services/neatoService.js +++ b/server/src/services/neatoService/index.js @@ -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(); diff --git a/server/src/services/nicknameService.js b/server/src/services/nicknameService/index.js similarity index 78% rename from server/src/services/nicknameService.js rename to server/src/services/nicknameService/index.js index 3281e5d6..c568aeee 100644 --- a/server/src/services/nicknameService.js +++ b/server/src/services/nicknameService/index.js @@ -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(); diff --git a/server/src/services/privateRoverAccessRequestService.js b/server/src/services/privateRoverAccessRequestService/index.js similarity index 94% rename from server/src/services/privateRoverAccessRequestService.js rename to server/src/services/privateRoverAccessRequestService/index.js index 5a04100e..ffd42d29 100644 --- a/server/src/services/privateRoverAccessRequestService.js +++ b/server/src/services/privateRoverAccessRequestService/index.js @@ -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 }); diff --git a/server/src/services/replayBuildService.js b/server/src/services/replayBuildService.js deleted file mode 100644 index 3a2eade3..00000000 --- a/server/src/services/replayBuildService.js +++ /dev/null @@ -1,5 +0,0 @@ -const { buildReplayVideo } = require('./replayEngineV2'); - -module.exports = { - buildReplayVideo, -}; diff --git a/server/src/services/replayBuildService/index.js b/server/src/services/replayBuildService/index.js new file mode 100644 index 00000000..c48df878 --- /dev/null +++ b/server/src/services/replayBuildService/index.js @@ -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, +}; diff --git a/server/src/services/replayEngineV2.js b/server/src/services/replayEngineV2/index.js similarity index 98% rename from server/src/services/replayEngineV2.js rename to server/src/services/replayEngineV2/index.js index a1c09420..f370af01 100644 --- a/server/src/services/replayEngineV2.js +++ b/server/src/services/replayEngineV2/index.js @@ -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); diff --git a/server/src/services/replayService.js b/server/src/services/replayService/index.js similarity index 81% rename from server/src/services/replayService.js rename to server/src/services/replayService/index.js index 553c7e9c..21df8a08 100644 --- a/server/src/services/replayService.js +++ b/server/src/services/replayService/index.js @@ -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; diff --git a/server/src/services/replaySocketService.js b/server/src/services/replaySocketService/index.js similarity index 73% rename from server/src/services/replaySocketService.js rename to server/src/services/replaySocketService/index.js index 38450b9d..5acfa0d5 100644 --- a/server/src/services/replaySocketService.js +++ b/server/src/services/replaySocketService/index.js @@ -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 || {}; diff --git a/server/src/services/replaySourceService.js b/server/src/services/replaySourceService/index.js similarity index 86% rename from server/src/services/replaySourceService.js rename to server/src/services/replaySourceService/index.js index 7002b831..31dbf54c 100644 --- a/server/src/services/replaySourceService.js +++ b/server/src/services/replaySourceService/index.js @@ -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(); diff --git a/server/src/services/roleService.js b/server/src/services/roleService/index.js similarity index 74% rename from server/src/services/roleService.js rename to server/src/services/roleService/index.js index 097f06bb..7d8d379f 100644 --- a/server/src/services/roleService.js +++ b/server/src/services/roleService/index.js @@ -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(); diff --git a/server/src/services/roomCameraReplayService.js b/server/src/services/roomCameraReplayService/index.js similarity index 95% rename from server/src/services/roomCameraReplayService.js rename to server/src/services/roomCameraReplayService/index.js index cf53b120..332e8373 100644 --- a/server/src/services/roomCameraReplayService.js +++ b/server/src/services/roomCameraReplayService/index.js @@ -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); diff --git a/server/src/services/roomCameraService.js b/server/src/services/roomCameraService/index.js similarity index 78% rename from server/src/services/roomCameraService.js rename to server/src/services/roomCameraService/index.js index 3c389e4e..28425c10 100644 --- a/server/src/services/roomCameraService.js +++ b/server/src/services/roomCameraService/index.js @@ -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(); diff --git a/server/src/services/roomCameraSnapshotService.js b/server/src/services/roomCameraSnapshotService/index.js similarity index 94% rename from server/src/services/roomCameraSnapshotService.js rename to server/src/services/roomCameraSnapshotService/index.js index 56e7883c..1eafb921 100644 --- a/server/src/services/roomCameraSnapshotService.js +++ b/server/src/services/roomCameraSnapshotService/index.js @@ -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; diff --git a/server/src/services/roomCameraSocketService.js b/server/src/services/roomCameraSocketService/index.js similarity index 89% rename from server/src/services/roomCameraSocketService.js rename to server/src/services/roomCameraSocketService/index.js index 7ec9d6e8..073280b8 100644 --- a/server/src/services/roomCameraSocketService.js +++ b/server/src/services/roomCameraSocketService/index.js @@ -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; diff --git a/server/src/services/roverConnectionService.js b/server/src/services/roverConnectionService/index.js similarity index 85% rename from server/src/services/roverConnectionService.js rename to server/src/services/roverConnectionService/index.js index df877de2..f0a9d255 100644 --- a/server/src/services/roverConnectionService.js +++ b/server/src/services/roverConnectionService/index.js @@ -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; diff --git a/server/src/services/roverManager.js b/server/src/services/roverManager/index.js similarity index 97% rename from server/src/services/roverManager.js rename to server/src/services/roverManager/index.js index c3b9dfbf..c31e6987 100644 --- a/server/src/services/roverManager.js +++ b/server/src/services/roverManager/index.js @@ -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 { diff --git a/server/src/services/roverSnapshotService.js b/server/src/services/roverSnapshotService/index.js similarity index 87% rename from server/src/services/roverSnapshotService.js rename to server/src/services/roverSnapshotService/index.js index e59ee03c..f3c63264 100644 --- a/server/src/services/roverSnapshotService.js +++ b/server/src/services/roverSnapshotService/index.js @@ -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; diff --git a/server/src/services/roverSnapshotSocketService.js b/server/src/services/roverSnapshotSocketService/index.js similarity index 89% rename from server/src/services/roverSnapshotSocketService.js rename to server/src/services/roverSnapshotSocketService/index.js index 0920c2b2..27a3f93e 100644 --- a/server/src/services/roverSnapshotSocketService.js +++ b/server/src/services/roverSnapshotSocketService/index.js @@ -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; diff --git a/server/src/services/serverControlService.js b/server/src/services/serverControlService/index.js similarity index 72% rename from server/src/services/serverControlService.js rename to server/src/services/serverControlService/index.js index 49d08d18..340c66ea 100644 --- a/server/src/services/serverControlService.js +++ b/server/src/services/serverControlService/index.js @@ -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; diff --git a/server/src/services/sessionService/constants.js b/server/src/services/sessionService/constants.js new file mode 100644 index 00000000..58ac014b --- /dev/null +++ b/server/src/services/sessionService/constants.js @@ -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, +}; diff --git a/server/src/services/sessionService/filters.js b/server/src/services/sessionService/filters.js new file mode 100644 index 00000000..f8263ac9 --- /dev/null +++ b/server/src/services/sessionService/filters.js @@ -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, +}; diff --git a/server/src/services/sessionService.js b/server/src/services/sessionService/index.js similarity index 70% rename from server/src/services/sessionService.js rename to server/src/services/sessionService/index.js index c196a143..9ddef770 100644 --- a/server/src/services/sessionService.js +++ b/server/src/services/sessionService/index.js @@ -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, diff --git a/server/src/services/sessionService/state.js b/server/src/services/sessionService/state.js new file mode 100644 index 00000000..313e90c8 --- /dev/null +++ b/server/src/services/sessionService/state.js @@ -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, +}; diff --git a/server/src/services/turnService/actions.js b/server/src/services/turnService/actions.js new file mode 100644 index 00000000..5cd08c41 --- /dev/null +++ b/server/src/services/turnService/actions.js @@ -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, +}; diff --git a/server/src/services/turnService/constants.js b/server/src/services/turnService/constants.js new file mode 100644 index 00000000..df97e46b --- /dev/null +++ b/server/src/services/turnService/constants.js @@ -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, +}; diff --git a/server/src/services/turnService.js b/server/src/services/turnService/index.js similarity index 78% rename from server/src/services/turnService.js rename to server/src/services/turnService/index.js index d5903c82..11ac754b 100644 --- a/server/src/services/turnService.js +++ b/server/src/services/turnService/index.js @@ -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; diff --git a/server/src/services/turnService/state.js b/server/src/services/turnService/state.js new file mode 100644 index 00000000..54d6bfba --- /dev/null +++ b/server/src/services/turnService/state.js @@ -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, +}; diff --git a/server/src/services/verificationService.js b/server/src/services/verificationService/index.js similarity index 97% rename from server/src/services/verificationService.js rename to server/src/services/verificationService/index.js index 73bb07cc..56e7a835 100644 --- a/server/src/services/verificationService.js +++ b/server/src/services/verificationService/index.js @@ -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'); diff --git a/server/src/services/videoAuthService.js b/server/src/services/videoAuthService/index.js similarity index 87% rename from server/src/services/videoAuthService.js rename to server/src/services/videoAuthService/index.js index e2610da7..d3393482 100644 --- a/server/src/services/videoAuthService.js +++ b/server/src/services/videoAuthService/index.js @@ -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 || {}; diff --git a/server/src/services/videoSessions.js b/server/src/services/videoSessions/index.js similarity index 86% rename from server/src/services/videoSessions.js rename to server/src/services/videoSessions/index.js index 2f0376ab..a7cb9ff6 100644 --- a/server/src/services/videoSessions.js +++ b/server/src/services/videoSessions/index.js @@ -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) diff --git a/server/src/services/videoSocketService.js b/server/src/services/videoSocketService/index.js similarity index 82% rename from server/src/services/videoSocketService.js rename to server/src/services/videoSocketService/index.js index 4c090e72..429b3c21 100644 --- a/server/src/services/videoSocketService.js +++ b/server/src/services/videoSocketService/index.js @@ -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 || {};