9.0 KiB
Target Architecture
Objective
The target architecture keeps the application small and explicit. Electron should remain a thin shell that owns desktop lifecycle, launches NodeCG, loads local dashboards, manages updates, and shuts down cleanly.
The target is not a full rewrite. It is a gradual extraction of responsibilities from main.ts into testable modules.
Target Structure
src/main/
app/
bootstrap.ts
application-controller.ts
paths.ts
shutdown-service.ts
config/
runtime-config.ts
windows/
window-service.ts
navigation-policy.ts
nodecg/
runtime-provisioner.ts
nodecg-process-service.ts
platform-process-killer.ts
updates/
update-service.ts
update-config.ts
update-download.ts
update-schema.ts
logging/
logger.ts
shared/
result.ts
src/shared/
types/
config.ts
shared/result.ts is optional and should be added only if it removes repeated error-handling noise.
src/shared/types/config.ts should contain only types that are genuinely shared across process boundaries or packages. It should not become a dumping ground.
Runtime Flow
Electron entrypoint
-> bootstrap app identity, paths, lock, config
-> create ApplicationController
-> prepare managed NodeCG runtime
-> relaunch if first install requires it
-> create loading window
-> start NodeCG process
-> wait for HTTP readiness
-> create or show main window
-> load NodeCG dashboard URL
-> close loading window
-> schedule update checks
Shutdown flow:
quit requested
-> mark controller stopping
-> stop update work if needed
-> stop NodeCG process tree
-> close windows
-> allow app exit
Activation flow:
activate requested
-> if ready, create or show main window
-> if not ready, route through readiness-aware startup
-> never load dashboard before NodeCG readiness
Module Responsibilities
app/bootstrap.ts
Owns Electron entrypoint side effects:
- Set app name.
- Set app paths.
- Acquire single-instance lock.
- Register Electron app event handlers.
- Instantiate services.
- Delegate startup and shutdown to
ApplicationController.
Rules:
- Keep this file thin.
- Do not put business logic here.
- Do not make it responsible for update parsing, process killing, or window option construction.
app/application-controller.ts
Owns high-level lifecycle state.
States:
idle
preparing
starting
ready
stopping
stopped
failed
Responsibilities:
- Coordinate runtime preparation.
- Coordinate NodeCG startup and readiness.
- Coordinate loading and main windows.
- Coordinate update scheduling.
- Coordinate activation.
- Coordinate shutdown.
Rules:
- State transitions must be explicit.
- Shutdown must be idempotent.
- Activation must not bypass readiness.
- Controller tests should not require real Electron where avoidable.
app/paths.ts
Owns pure path construction:
- Electron
userDataderived paths. - Managed runtime path.
- Safe temp locations.
- Any path constants shared by startup and provisioning.
Rules:
- No Electron side effects.
- No filesystem writes.
- Pure functions only.
config/runtime-config.ts
Owns runtime configuration:
- Parse environment variables.
- Parse static config.
- Define defaults.
- Return typed runtime config.
Rules:
- Parse once.
- Validate early.
- Do not read environment variables throughout the application.
windows/window-service.ts
Owns Electron window creation and window lifecycle.
Responsibilities:
- Create loading window.
- Create main window.
- Apply secure
webPreferences. - Apply devtools policy.
- Register permission handlers.
- Delegate navigation decisions to
navigation-policy.
Rules:
- No NodeCG process logic.
- No updater logic.
- No runtime provisioning logic.
windows/navigation-policy.ts
Owns pure navigation decisions.
Responsibilities:
- Allow approved local NodeCG origins.
- Block external navigation.
- Block unexpected new-window attempts.
- Normalize URL parsing.
Rules:
- Prefer
URLparsing. - Keep policy testable without Electron.
- Do not use broad string-prefix allow checks as the primary control.
nodecg/runtime-provisioner.ts
Owns managed runtime installation and replacement.
Responsibilities:
- Validate bundled runtime source.
- Install runtime into
userData. - Replace managed runtime safely.
- Preserve
cfg,db, andlogs. - Report whether relaunch is needed.
Rules:
- User-owned data must not be deleted.
- Runtime replacement must be predictable and test-covered.
nodecg/nodecg-process-service.ts
Owns NodeCG process lifecycle.
Responsibilities:
- Validate runtime before launch.
- Start NodeCG with
ELECTRON_RUN_AS_NODE. - Capture process output.
- Wait for HTTP readiness.
- Stop the process.
- Delegate platform-specific process-tree termination.
Rules:
- Do not build platform kill commands here.
- Do not create windows here.
- Do not schedule updates here.
nodecg/platform-process-killer.ts
Owns OS-specific process-tree termination.
Responsibilities:
- Terminate a process tree on Windows.
- Terminate a process tree on POSIX systems.
- Validate process IDs before command construction.
- Normalize process-kill errors.
Rules:
- Keep platform branches here.
- Test command construction.
- Keep inputs narrow and typed.
updates/update-service.ts
Owns update orchestration.
Responsibilities:
- Schedule update checks.
- Fetch update metadata through a helper.
- Validate metadata through schema helpers.
- Select the correct platform asset.
- Ask the user before installing.
- Delegate download work.
- Start installer only after validation and download success.
Rules:
- Do not trust remote metadata.
- Do not mix dialogs with JSON parsing.
- Do not mix installer execution with download streaming.
updates/update-config.ts
Owns update settings:
- Feed URL.
- Current app version.
- Platform selection.
- Development-mode behavior.
Rules:
- Keep production and development behavior explicit.
- Do not silently downgrade security in production.
updates/update-download.ts
Owns download behavior:
- Validate URL protocol.
- Download to a safe temp path.
- Write atomically.
- Return a typed result.
Rules:
- No dialogs.
- No installer execution.
- No remote metadata interpretation.
updates/update-schema.ts
Owns runtime validation:
- Update metadata shape.
- Asset shape.
- Version field presence.
- URL field validity before download selection.
Rules:
- Unknown remote JSON must be validated before use.
- Invalid metadata must fail closed.
logging/logger.ts
Optional thin logging boundary.
Rules:
- Add only if it improves consistency.
- Do not introduce a large logging framework.
- Keep logs useful for startup, process, update, and shutdown diagnostics.
Electron Decisions
- Keep Electron main as the only privileged process.
- Keep Node.js unavailable to web content.
- Keep custom renderer absent unless a concrete feature requires one.
- Keep preload absent unless a desktop API must cross into web content.
- Treat windows as untrusted web surfaces even when loading local NodeCG dashboards.
Required BrowserWindow security posture:
nodeIntegration: false
contextIsolation: true
sandbox: true
webSecurity: true
Additional decisions:
- Deny permissions by default.
- Control devtools by environment.
- Block external navigation by default.
- Block unexpected new windows.
- Review CSP options for NodeCG-hosted content, but do not break dashboards to satisfy theoretical policy.
IPC Decisions
Current target:
- No IPC.
- No preload.
- No exposed desktop API.
Future IPC, if needed:
src/main/ipc/
channels.ts
register-handlers.ts
validators.ts
src/shared/ipc/
types.ts
IPC must be:
- Explicitly justified by a product requirement.
- Channel allowlisted.
- Payload validated at runtime.
- Typed at compile time.
- Narrow in capability.
IPC must not expose:
- Raw
ipcRenderer. - Filesystem primitives.
- Process primitives.
- Shell execution.
- Update installation primitives.
- Arbitrary NodeCG process controls.
Security Decisions
Security controls to preserve:
- No Node.js in web content.
- Context isolation enabled.
- Sandbox enabled.
- No IPC surface by default.
- Local navigation only.
Security controls to add:
- Explicit
webSecurity: true. - Permission handler that denies by default.
- Explicit devtools policy.
- Strong update metadata validation.
- Strong update asset URL validation.
- Safe temporary download paths.
- Atomic download finalization.
- Platform process-kill isolation.
What This Architecture Should Feel Like
Future maintainers should be able to answer these questions quickly:
- Where does startup happen?
- Where is NodeCG launched?
- Where is readiness checked?
- Where are windows created?
- Where is navigation allowed or denied?
- Where are updates validated?
- Where is shutdown coordinated?
- Where does platform-specific process killing live?
If a future change makes those answers harder, it is moving against the target architecture.