# Configuration Guide The Blueprint for Runtime Control. > This guide defines the operational parameters of the Desktop Wrapper. It governs the orchestration of target applications, encapsulates the mechanics of our proprietary production routing, and provides a fail-safe **Break Glass Procedure** for emergency infrastructure transitions. --- ## Table of Contents - [Target App Orchestration](#target-app-orchestration) - [Environment Variables Registry](#environment-variables-registry) - [Deterministic Path Resolution](#deterministic-path-resolution) - [Production Routing: Overcoming Protocol Constraints](#production-routing-overcoming-protocol-constraints) - [Defense-in-Depth: Multi-Layered Protection](#defense-in-depth-multi-layered-protection) - [Break Glass Procedure: Disaster Recovery Protocol](#break-glass-procedure-disaster-recovery-protocol) --- ## Target App Orchestration The Desktop Wrapper is architected to embed **any** web application within the monorepo ecosystem. The target application is resolved at build time through a declarative configuration surface in `apps/desktop/.env`. ### `.env` Declaration ```env # The workspace identifier of the target web application. # Must correspond to a directory under apps/ (e.g., "web", "docs-dev", "admin"). DESKTOP_TARGET_APP=web # The Vite development server endpoint for the target application. DESKTOP_DEV_SERVER_URL=http://localhost:5173 ``` ### Switching the Target Application To redirect the wrapper to a different application — for example, `apps/admin` — modify the configuration and re-execute the build pipeline: 1. **Update the `.env` declaration:** ```env DESKTOP_TARGET_APP=admin DESKTOP_DEV_SERVER_URL=http://localhost:3001 ``` 2. **Verify the target app exports a `build` script** that emits static assets to `dist/`. 3. **Execute the deterministic build pipeline:** ```bash pnpm build --filter=admin && cd apps/desktop && pnpm run build ``` ### The Deployment Bridge The `prebuild` hook invokes `scripts/copy-web-dist.ts`, which serves as the **Deployment Bridge** between the web workspace and the native container. It reads `DESKTOP_TARGET_APP`, resolves the corresponding `apps//dist/` directory, and synchronizes the contents into `apps/desktop/web-dist/`. This bridge directory is then ingested by `electron-builder` via both the `files` and `extraResources` declarations in `electron-builder.yml`. ``` ┌─────────────────────────────┐ Deployment Bridge ┌──────────────────────────┐ │ apps//dist/ │ ─── copy-web-dist.ts ────────→ │ apps/desktop/web-dist/ │ │ (Vite build output) │ prebuild hook │ (Native container) │ └─────────────────────────────┘ └──────────────────────────┘ │ ▼ electron-builder files + extraResources │ ▼ ┌──────────────────────┐ │ Packaged .app/.exe │ │ resources/web-dist/ │ └──────────────────────┘ ``` --- ## Environment Variables Registry | Variable | Default | Security Scope | Consumer | Description | |---|---|---|---|---| | `DESKTOP_TARGET_APP` | `web` | Build-time | `copy-web-dist.ts` | Workspace identifier of the web app to embed | | `DESKTOP_DEV_SERVER_URL` | `http://localhost:5173` | Runtime (dev) | `src/main/index.ts` | Dev server URL loaded in the Electron window during development | | `GH_TOKEN` | — | CI/CD | `electron-builder` | GitHub personal access token for publishing releases | | `CSC_LINK` | — | CI/CD | `electron-builder` | Base64-encoded `.p12` code signing certificate | | `CSC_KEY_PASSWORD` | — | CI/CD | `electron-builder` | Passphrase for the `.p12` certificate | | `APPLE_ID` | — | CI/CD (macOS) | `electron-builder` | Apple ID email for notarization submission | | `APPLE_APP_SPECIFIC_PASSWORD` | — | CI/CD (macOS) | `electron-builder` | App-specific password for notarization | | `APPLE_TEAM_ID` | — | CI/CD (macOS) | `electron-builder` | Apple Developer Team ID | > [!IMPORTANT] > **Build-time** variables are consumed during the `prebuild` phase and baked into the artifact. **Runtime** variables are read by the Electron main process at launch. **CI/CD** variables are secrets injected exclusively in the deployment environment — they must never appear in source control or local `.env` files. --- ## Deterministic Path Resolution The following matrix defines how the target app's static assets are resolved across every phase of the application lifecycle. Each path is **deterministic** — there is no runtime ambiguity. | Phase | Resolution Strategy | Resolved Path | Context | |---|---|---|---| | **Prebuild** | `copy-web-dist.ts` reads `DESKTOP_TARGET_APP` | `monorepo-root/apps//dist/` → `apps/desktop/web-dist/` | Deployment Bridge: build-time synchronization | | **Development** | `__dirname` relative traversal from `out/main/` | `apps/desktop/out/main/` → `../../` → `apps/` → `/dist/` | Direct filesystem access to the web app's build output | | **Production** | `process.resourcesPath` | `Contents/Resources/web-dist/` (macOS) / `resources/web-dist/` (Windows/Linux) | OS-specific resource directory within the packaged binary | > [!NOTE] > The development path relies on `__dirname` pointing to `apps/desktop/out/main/` at runtime. If electron-vite's output directory is ever reconfigured, this traversal must be updated in `getWebDistPath()` within `src/main/index.ts`. --- ## Production Routing: Overcoming Protocol Constraints ### The Constraint React applications using `BrowserRouter` rely on a fundamental server-side contract: **every URL path must return `index.html`**. Paths like `/dashboard`, `/auth/login`, and `/settings/profile` do not correspond to physical files — they are virtual routes resolved entirely by the client-side router. Electron's default `file://` protocol breaks this contract. Requesting `file:///app/dashboard` triggers a literal filesystem lookup for a file named `dashboard`, which does not exist, resulting in a blank screen or an OS-level "file not found" error. ### The Solution: A Privileged Virtual File System The `app://` scheme is a **Privileged Virtual File System** that resolves SPA routing conflicts by implementing a **Heuristic Resource Loader**. It operates as follows: ``` Request: app://-/settings/profile │ ├─ Decode URI → "settings/profile" │ ├─ Normalize + validate path (security boundary check) │ ├─ Does web-dist/settings/profile exist as a file? │ ├─ YES → Serve with correct MIME type + CSP headers │ └─ NO → Heuristic Fallback: serve web-dist/index.html │ └─ React Router resolves /settings/profile client-side ``` If a requested URI does not map to a physical asset, the handler intelligently intercepts the request to serve the `index.html` entry point, allowing React Router to maintain stateful client-side navigation. This ensures that deep links, page refreshes, and direct URL entry all function without modification to the React app's routing configuration. ### Scheme Registration The scheme **must** be registered synchronously at module load time, before `app.whenReady()`. This is a Chromium requirement — deferred registration will silently fail: ```typescript protocol.registerSchemesAsPrivileged([ { scheme: 'app', privileges: { standard: true, // Enables URL parsing (host, path, query) secure: true, // Treated as a secure origin (HTTPS equivalent) supportFetchAPI: true, // Allows fetch() from this scheme corsEnabled: true, // Enables CORS for cross-origin requests stream: true, // Supports streaming responses }, }, ]); ``` --- ## Defense-in-Depth: Multi-Layered Protection The custom protocol handler enforces a **multi-layered defense perimeter** that goes beyond standard Electron security defaults. | Layer | Technique | Implementation | Threat Mitigated | |---|---|---|---| | **I/O Sanitization** | Path traversal guard | `normalize()` + `startsWith()` validation against `web-dist/` boundary | Directory traversal attacks (`../../etc/passwd`) → `403 Forbidden` | | **In-Flight Policy Injection** | CSP response headers | `Content-Security-Policy` injected as HTTP response headers on every HTML payload | XSS execution via script injection | | **Cryptographic Isolation** | Privileged scheme registration | `app` scheme registered with `standard`, `secure`, `supportFetchAPI`, `corsEnabled` | Scheme downgrade attacks; the renderer treats `app://` identically to `https://` | | **Resource Type Validation** | `statSync.isFile()` check | Only regular files are served; directories return the SPA fallback | Information disclosure via directory listing | | **Origin Sanitization** | CORS bypass proxy | `webRequest.onBeforeSendHeaders` strips `app://` Origin headers on outgoing requests | Backend CORS rejection of non-standard origins | | **Navigation Confinement** | `will-navigate` guard | Blocks navigation to URLs outside `app://` and the authorized dev server | Phishing via in-app redirect to malicious sites | --- ## Break Glass Procedure: Disaster Recovery Protocol > [!CAUTION] > **This is a formal Disaster Recovery Protocol.** Execute only if the custom `app://` protocol causes an irrecoverable failure — for example, a critical third-party library that refuses to operate under a non-standard URI scheme. This procedure requires coordinated changes across both the React application and the Electron main process. Estimated recovery time: **15 minutes**. ### Step 1: Switch the Router — React Application In the target web app's entry point (e.g., `apps/web/src/apps/index.tsx`): ```diff - import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom'; + import { HashRouter, Navigate, Route, Routes } from 'react-router-dom'; export default function App() { return ( - + Loading...}> {/* All route definitions remain unchanged */} - + ); } ``` All routes transition to hash-based addressing: `#/app/dashboard`, `#/auth/login`, `#/showcase`. ### Step 2: Decommission the Custom Protocol — Main Process In `apps/desktop/src/main/index.ts`, execute the following surgical removals: **a)** Remove the scheme registration block at the top of the file: ```diff - protocol.registerSchemesAsPrivileged([ ... ]); ``` **b)** Delete the entire `registerAppProtocol()` function. **c)** Remove the `registerAppProtocol()` invocation inside `app.whenReady()`. **d)** Redirect production content loading in `createWindow()`: ```diff if (IS_DEV) { mainWindow.loadURL(DEV_SERVER_URL); mainWindow.webContents.openDevTools({ mode: 'detach' }); } else { - mainWindow.loadURL('app://-/index.html'); + const webDistPath = getWebDistPath(); + mainWindow.loadFile(join(webDistPath, 'index.html')); } ``` **e)** Inject a CSP `` tag into the web app's `index.html`, since the In-Flight Policy Injection layer is no longer available: ```html ``` ### Trade-off Analysis | Dimension | Custom `app://` Protocol | `file://` + HashRouter | |---|---|---| | **Aesthetic Integrity** | Clean URLs: `/app/dashboard` | Hash prefix: `#/app/dashboard` | | **Router Compatibility** | `BrowserRouter` — zero changes required | Must migrate to `HashRouter` | | **Deep Linking** | Full, native-style support | Hash-based only | | **Protocol-Native Compatibility** | Rare edge cases with non-standard scheme detection | Maximum third-party compatibility | | **Security Delivery Vector** | CSP via response headers (strongest enforcement) | CSP via `` tag (bypassable by early script execution) | | **Implementation Complexity** | Higher (custom protocol handler + security layers) | Lower (no custom protocol infrastructure) | | **Recovery Time** | — | ~15 minutes, 2 files |