Files
scoreko-electron-dev/docs/refactor/TARGET_ARCHITECTURE.md
T

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 userData derived 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 URL parsing.
  • 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, and logs.
  • 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.