Skip to content
Offline App — Electron Wrapper

Offline App — Electron Wrapper

Overview

The April Robots Offline application is a FastAPI backend paired with a compiled SvelteKit frontend, wrapped in Electron to give center specialists a native desktop experience on Windows. Electron manages the full lifecycle: spawning the backend process, serving the frontend over a local HTTP server, managing robot microservice containers via Podman, and handling automatic updates from GitHub Releases.

The Electron wrapper lives in the electron/ subdirectory of the april-robots-offline-general-api repository.

Directory structure

april-robots-offline-general-api/
├── app/                        # FastAPI backend (Python)
├── frontend/                   # Pre-built SvelteKit static files
├── database/                   # File-based database (DictDataBase)
├── logs/                       # Runtime logs (created on first run)
├── electron/
│   ├── src/
│   │   ├── main.js             # Main process — app lifecycle, window management, IPC
│   │   ├── preload.js          # IPC bridge exposed to renderer
│   │   ├── server.js           # Local HTTP server for the frontend
│   │   ├── auto-updater.js     # Update manager (GitHub Releases)
│   │   ├── podman-manager.js   # Robot microservice container orchestration
│   │   ├── logger.js           # Centralized logging utility
│   │   ├── loading.html        # Splash screen shown during startup
│   │   ├── titlebar.html       # Custom frameless window title bar
│   │   └── update-notification.html  # Update download progress UI
│   ├── package.json
│   └── electron-builder.yml
├── build.ps1                   # Windows build script (PyInstaller + electron-builder)
└── .env                        # Runtime configuration (not committed)

Startup sequence

When the app is launched, the main process follows a fixed four-step startup before showing the main window. A loading window (220×140 frameless splash screen) is shown throughout and updates its status text at each step.

    flowchart TD

Start([App launched]) --> LW["Show loading window\n(splash screen)"]

LW --> P{ENABLE_PODMAN?}
P -->|true| PM["PodmanManager.startup()\n1. Start Podman machine\n2. Login to GHCR\n3. podman compose down\n4. podman compose pull\n5. podman compose up -d"]
P -->|false| BE

PM -->|error → log + continue| BE

BE["Start backend process\n(see Backend execution)"]
BE --> W["Wait 3 seconds\n(initialization buffer)"]
W --> FS["Start frontend HTTP server\nlocalhost:3000"]
FS --> MW["Create main BrowserWindow\nload localhost:3000"]
MW --> TB["Inject custom title bar\nvia executeJavaScript"]
TB --> AU["Check for updates\n(only if APP_ENV=prod)"]
AU --> Show["Close loading window\nmaximize and show main window"]
Show --> Ready([App ready])
  

Backend execution

The backend is a standard FastAPI + Uvicorn app started as a child process. How it is started depends on whether the app is packaged or running in development:

ModeExecutableWorking directory
Development./venv/bin/python -m app.main --api-onlyProject root
Production (packaged)april-robots-backend.exe --api-onlyprocess.resourcesPath

The process is spawned with stdio captured and piped to the logger. On shutdown:

  • Windowstaskkill /pid {pid} /T /F (kills the entire process tree)
  • UnixSIGTERM signal

The --api-only flag tells the FastAPI app to run without the bundled frontend, since Electron serves the frontend separately.

Frontend serving

server.js creates a plain Node.js HTTP server that serves the pre-built SvelteKit static files from the frontend/ directory on localhost:3000. It handles:

  • MIME type detection per file extension
  • SPA fallback — any unmatched path returns index.html (required for client-side routing)
  • Cache-Control: no-cache on all responses
  • Path traversal prevention (requests cannot escape the frontend/ directory)

The main BrowserWindow loads http://localhost:3000 once the server is up.

Window setup

The main window is frameless (no native OS title bar) with a custom 34px title bar injected into the page via executeJavaScript after load. This gives the app a branded look while keeping native window behaviour (drag, minimize, maximize, close).

WindowSizeNotes
Main window1280×720 (min 1024×600)Frameless, custom title bar, context isolation on
Loading window220×140Frameless, transparent, always on top
Update notification550×400Frameless, transparent, centered

The custom title bar (titlebar.html) is a green (#72cd78) fixed-height bar positioned at the top of the page. It reflects the current page title dynamically and forwards minimize / maximize / close button clicks to the main process via IPC.

IPC communication

preload.js exposes a safe window.electronAPI object to the renderer (Svelte frontend) using Electron’s contextBridge. The renderer cannot access Node.js APIs directly.

window.electronAPI = {
  // Window controls
  windowMinimize(), windowMaximize(), windowClose(), windowIsMaximized()

  // App info
  getBackendUrl()         // returns http://127.0.0.1:8000
  getAppVersion()         // returns APP_VERSION from .env
  isDevMode()             // true if not packaged

  // Updates
  checkForUpdates()
  onUpdateDownloadProgress(callback)
  sendUpdateAction(action)
  onShowUpdateNotification(callback)

  // Session and language
  setSessionActive(payload)   // prevents accidental quit mid-session
  setAppLanguage(language)    // en | ru | kk

  platform                    // process.platform
}

Podman manager

podman-manager.js orchestrates the robot microservice containers (NAO MS, Furhat MS) using Podman Compose. It is only active when APP_ENV=prod and ENABLE_PODMAN=true.

Startup sequence:

  1. Start the Podman machine (WSL-backed VM), if not already running.
  2. Login to GHCR using GHCR_ACTOR and GHCR_TOKEN from .env.
  3. podman compose down — ensure a clean slate.
  4. podman compose pull — fetch the latest images for the configured versions.
  5. podman compose up -d — start containers in the background.
  6. Begin streaming container logs to the logger.

On app shutdown, podman stop is called for each container. Containers are stopped but not removed, so their state is preserved.

Container versions are set in .env via NAO_AGENT_VERSION and FURHAT_AGENT_VERSION.

See Microservices for details on what the NAO and Furhat containers do.

Auto-updater

The auto-updater is incomplete. The update ZIP is downloaded successfully and the PowerShell post-update script works correctly when run manually — but the script is not triggered automatically after the download completes. As a workaround, the script can be run by hand to finish the update. This needs to be fixed before the updater can be considered fully functional.

auto-updater.js manages over-the-air updates from the april-robots-offline-deployment-kit GitHub repository. Updates only run when APP_ENV=prod.

Update check:

  1. Fetches all releases from the GitHub API (authenticated with GHCR_TOKEN).
  2. Filters out drafts, sorts by semantic version.
  3. Compares with APP_VERSION from .env.
  4. If a newer release exists, shows the update notification window.

Install flow:

  1. Downloads the release ZIP from the GitHub API (with redirect following).
  2. Tracks download progress, reporting to the notification window in 5% increments.
  3. Extracts the ZIP using PowerShell Expand-Archive.
  4. Environment merge — the new .env from the release is merged with the existing one:
    • CENTER_JWT_KEY is always preserved from the existing .env (it is center-specific and must not be overwritten).
    • APP_VERSION is updated to the new version.
  5. Generates a SHA256 hash manifest for all files to be copied.
  6. Generates and runs a PowerShell post-update script that:
    • Waits for the Electron app to close (up to 30 seconds).
    • Copies all new files into place.
    • Preserves database/storage/children.json and database/storage/sessions.json (local data must not be overwritten).
    • Verifies copied files against the hash manifest.
    • Relaunches the updated app.
The updater preserves CENTER_JWT_KEY and the local database files. All other .env values are overwritten from the release. Make sure any new environment variables have sensible defaults in the release .env.

Logging

logger.js provides category-based file logging for each major subsystem. Log files are written to logs/{category}/YYYYMMDD_HHMMSS_{category}.log. A maximum of 10 log files per category are kept; older files are deleted automatically.

Categories: app, api, podman, nao, furhat, updater.

Subprocess stdout and stderr are piped directly into the appropriate logger via logger.pipeStream().

Build and packaging

The app is built for Windows with build.ps1, which runs two steps in sequence:

Step 1 — PyInstaller bundles the FastAPI app into a single Windows executable:

dist/april-robots-backend.exe

Step 2 — electron-builder packages the Electron app as an unpacked directory (no NSIS installer). The output in dist/ contains the .exe launcher alongside a resources/ folder with everything bundled:

  • april-robots-backend.exe
  • app/podman/ — Podman Compose files for robot microservices
  • frontend/ — pre-built SvelteKit static files
  • database/, data/, logs/, static/, robot_scripts/
  • .env.example

The app is distributed as a ZIP of this unpacked directory via GitHub Releases in the deployment-kit repository.

Development vs production

AspectDevelopmentProduction
Backend./venv/bin/python -m app.mainapril-robots-backend.exe
Working directoryProject rootprocess.resourcesPath
PodmanDisabled (APP_ENV != prod)Enabled
Auto-updatesDisabledEnabled
DevToolsAuto-openedHidden

Environment variables

The .env file is generated and bundled into each release by the CI/CD pipeline in the april-robots-offline-deployment-kit repository. Values are sourced from that repository’s GitHub Secrets and Variables — nothing is committed to the repository. To add or change an env value in a release, update the corresponding secret or variable in the deployment-kit repo settings.

At runtime, .env lives in the app root (project root in dev, resources/ in packaged).

VariableSourcePurpose
APP_VERSIONGitHub VariableCurrent app version (also updated by auto-updater on install)
APP_ENVGitHub Variableprod enables Podman and updates; anything else disables both
CENTER_JWT_KEYGitHub SecretCenter authentication key for cloud sync — preserved across updates
DEBUGGitHub VariableEnables verbose FastAPI logging
BACKEND_URLGitHub VariableFastAPI URL (default: http://127.0.0.1:8000)
FRONTEND_DIRGitHub VariablePath to compiled frontend directory (default: frontend)
ONLINE_BACKEND_URLGitHub VariableURL of the AR Online backend for sync
ENABLE_PODMANGitHub VariableSet to false to skip Podman startup
GHCR_TOKENGitHub SecretToken for pulling container images and checking GitHub releases
GHCR_ACTORGitHub VariableGitHub username for GHCR login
NAO_AGENT_VERSIONGitHub VariableVersion tag for the NAO MS image
FURHAT_AGENT_VERSIONGitHub VariableVersion tag for the Furhat MS image
NAO_MICROSERVICE_URLGitHub VariableNAO MS URL (default: http://localhost:5050)
FURHAT_MICROSERVICE_URLGitHub VariableFurhat MS URL (default: http://localhost:5051)
NAO_USERNAME / NAO_PASSWORDGitHub SecretSSH credentials for NAO robot
FURHAT_USERNAME / FURHAT_PASSWORDGitHub SecretSSH credentials for Furhat robot