docs: Enhance documentation across multiple modules for clarity and structure

- Updated CONFIGURATION.md to improve navigation and added mermaid diagrams for better visualization of processes.
- Revised IPC_ARCHITECTURE.md to clarify the security model and added diagrams to illustrate the architecture.
- Improved README.md files in core-api, core-events, core-i18n, and core-storage for consistency and clarity, including better descriptions and structural enhancements.
This commit is contained in:
Firman Ramdhani
2026-05-29 16:21:25 +07:00
parent 9f1158653a
commit d3eb242ebe
7 changed files with 373 additions and 301 deletions
+78 -96
View File
@@ -1,56 +1,56 @@
# Auto-Update System
[← Back to Root](../../../README.md)
The Unified Update Lifecycle.
# Desktop Auto-Update System
> This document defines the strategic implementation of our cross-platform auto-update system. Powered by **electron-updater**, this architecture ensures seamless, background delivery of application patches, maintaining version parity across our global user base.
`apps/desktop` utilizes a unified update lifecycle powered by **electron-updater**. This architecture ensures seamless, background delivery of application patches, maintaining version parity across our global user base.
---
## Table of Contents
- [Reactive Update Flow](#reactive-update-flow)
- [Current Provider: GitHub Releases](#current-provider-github-releases)
- [Release Workflow: The Deterministic Pipeline](#release-workflow-the-deterministic-pipeline)
- [CI/CD Environment Variables](#cicd-environment-variables)
- [Deployment Strategies](#deployment-strategies)
- [Code Signing: The Trust Boundary](#code-signing-the-trust-boundary)
- [Testing Updates in Development](#testing-updates-in-development)
- [Diagnostic Runbook](#diagnostic-runbook)
---
## Reactive Update Flow
## 🏗 Reactive Update Flow
The following diagram illustrates the **Reactive Update Flow**, bridging the Node.js Main Process with the React UI layer through a secure IPC event stream.
```
┌──────────────────────────────────────────────────────────────────┐
│ Main Process (src/main/index.ts) │
│ │
│ autoUpdater.checkForUpdatesAndNotify() │
│ │ │
│ ├─→ 'checking-for-update' │
│ ├─→ 'update-available' → { version, releaseDate } │
│ ├─→ 'download-progress' → { percent, bytesPerSecond } │
│ ├─→ 'update-downloaded' → { version, releaseNotes } │
│ └─→ 'error' → { message } │
│ │
sendToRenderer('updater:*', payload) │
├──────────────────────── IPC Bridge ──────────────────────────────┤
│ Preload (src/preload/index.ts) │
│ │
│ contextBridge.exposeInMainWorld('electronAPI', { │
│ onUpdateAvailable, onDownloadProgress, │
│ onUpdateDownloaded, onUpdateError, │
│ checkForUpdates, installUpdate │
│ }) │
├──────────────────────── Renderer ────────────────────────────────┤
│ React App (apps/web) │
│ │
│ useElectronUpdater() hook │
│ → Reactive state: status, progress, updateInfo, errorMessage │
│ → Actions: checkForUpdates(), installUpdate() │
└──────────────────────────────────────────────────────────────────┘
```mermaid
graph TD
%% ─── Styling Definitions (Dark-Mode Friendly Enterprise Palette) ───
classDef appEntity fill:#dbeafe,stroke:#3b82f6,stroke-width:2px,color:#1e3a8a
classDef coreEntity fill:#e2e8f0,stroke:#64748b,stroke-width:2px,color:#0f172a
classDef ipcBridge fill:#10b981,stroke:#047857,stroke-width:2px,color:#ffffff
%% ─── Subgraphs ───
subgraph MainProcess ["Main Process (src/main/index.ts)"]
AUTO[autoUpdater.checkForUpdatesAndNotify]
EVENTS{{Update Events: progress, downloaded, error}}
SENDER[sendToRenderer]
end
subgraph Preload ["IPC Bridge (src/preload/index.ts)"]
EXPOSE{contextBridge.exposeInMainWorld}
end
subgraph Renderer ["Renderer (React App - apps/web)"]
HOOK([useElectronUpdater Hook])
ACTIONS[UI Actions: Install, Check]
end
%% ─── Flow & Relationships ───
AUTO ---> EVENTS
EVENTS ---> SENDER
SENDER ===>|'updater:*' Event Stream| EXPOSE
EXPOSE ===>|electronAPI window object| HOOK
HOOK -.->|Reactive State Status and Progress| ACTIONS
ACTIONS -.->|ipcRenderer.invoke| EXPOSE
EXPOSE -.->|Trigger Update or Install| AUTO
%% ─── Apply Styles ───
class AUTO,EVENTS,SENDER coreEntity;
class EXPOSE ipcBridge;
class HOOK,ACTIONS appEntity;
%% ─── Subgraph Backgrounds (Transparent for Native GitHub Support) ───
style MainProcess fill:transparent,stroke:#94a3b8,stroke-width:2px,stroke-dasharray: 5 5
style Preload fill:transparent,stroke:#10b981,stroke-width:2px,stroke-dasharray: 5 5
style Renderer fill:transparent,stroke:#3b82f6,stroke-width:2px,stroke-dasharray: 5 5
```
### Lifecycle Sequence
@@ -63,7 +63,7 @@ The following diagram illustrates the **Reactive Update Flow**, bridging the Nod
---
## Current Provider: GitHub Releases
## 🌐 Current Provider: GitHub Releases
The update provider is declared in `electron-builder.yml`:
@@ -77,6 +77,7 @@ publish:
### Operational Mechanics
1. When `electron-builder --publish always` executes, it:
- Compiles the application for the target platform.
- Uploads the installer(s) to a **GitHub Release** tagged with the version from `package.json`.
- Generates and uploads the platform-specific manifest: `latest.yml` (Windows), `latest-mac.yml` (macOS), or `latest-linux.yml` (Linux).
@@ -88,7 +89,7 @@ publish:
---
## Release Workflow: The Deterministic Pipeline
## 🚀 Release Workflow: The Deterministic Pipeline
To maintain release integrity, follow this deterministic pipeline to synchronize web assets and native binaries.
@@ -109,8 +110,7 @@ pnpm run prebuild
GH_TOKEN=your_token electron-builder --publish always --config electron-builder.yml
```
> [!CAUTION]
> **Treat `GH_TOKEN` as a critical secret.** It grants write access to your repository's release assets. Never commit it to version control, never log it in CI output, and always inject it via encrypted secrets or a vault.
> [!CAUTION] > **Treat `GH_TOKEN` as a critical secret.** It grants write access to your repository's release assets. Never commit it to version control, never log it in CI output, and always inject it via encrypted secrets or a vault.
### Automated Release (GitHub Actions)
@@ -155,10 +155,10 @@ jobs:
---
## CI/CD Environment Variables
## 🔐 CI/CD Environment Variables
| Variable | Required | Platform | Description |
|---|---|---|---|
| ----------------------------- | ------------- | -------------- | --------------------------------------------------------------------------------------------- |
| `GH_TOKEN` | Yes | All | GitHub personal access token with `repo` scope. Authorizes release artifact uploads. |
| `CSC_LINK` | macOS/Windows | macOS, Windows | Base64-encoded `.p12` code signing certificate. Generate with: `base64 -i cert.p12 \| pbcopy` |
| `CSC_KEY_PASSWORD` | macOS/Windows | macOS, Windows | Passphrase for the `.p12` certificate. |
@@ -176,7 +176,7 @@ jobs:
---
## Deployment Strategies
## ☁️ Deployment Strategies
### AWS S3 (Private Infrastructure)
@@ -196,12 +196,13 @@ publish:
**Additional environment variables:**
| Variable | Description |
|---|---|
| ----------------------- | -------------------------------------------------------------- |
| `AWS_ACCESS_KEY_ID` | IAM access key with S3 `PutObject` and `GetObject` permissions |
| `AWS_SECRET_ACCESS_KEY` | IAM secret key |
**Bucket structure:**
```
```text
your-bucket/desktop-releases/
├── latest.yml (Windows manifest)
├── latest-mac.yml (macOS manifest)
@@ -222,13 +223,12 @@ For self-hosted infrastructure (Nginx, Caddy, etc.):
```yaml
publish:
provider: generic
url: https://updates.your-domain.com/desktop
url: [https://updates.your-domain.com/desktop](https://updates.your-domain.com/desktop)
```
Your server must host the same directory structure as the S3 layout above.
> [!IMPORTANT]
> **MIME Type Configuration**: Ensure your file server correctly serves `.yml` files with `text/yaml` and installer binaries with `application/octet-stream`. Incorrect MIME types will cause download corruption or silent update failures.
> [!IMPORTANT] > **MIME Type Configuration**: Ensure your file server correctly serves `.yml` files with `text/yaml` and installer binaries with `application/octet-stream`. Incorrect MIME types will cause download corruption or silent update failures.
**Nginx reference:**
@@ -253,10 +253,9 @@ server {
---
## Code Signing: The Trust Boundary
## 🛡️ Code Signing: The Trust Boundary
> [!WARNING]
> **Code signing is not merely a requirement — it is the Trust Boundary established by the operating system.** macOS Gatekeeper will explicitly terminate unsigned applications or refuse background updates to maintain system integrity. Windows SmartScreen will display alarming warnings to users. Without valid signatures, `electron-updater` will **reject update payloads entirely**.
> [!WARNING] > **Code signing is not merely a requirement — it is the Trust Boundary established by the operating system.** macOS Gatekeeper will explicitly terminate unsigned applications or refuse background updates to maintain system integrity. Windows SmartScreen will display alarming warnings to users. Without valid signatures, `electron-updater` will **reject update payloads entirely**.
### macOS
@@ -273,7 +272,7 @@ server {
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
"[http://www.apple.com/DTDs/PropertyList-1.0.dtd](http://www.apple.com/DTDs/PropertyList-1.0.dtd)">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
@@ -300,7 +299,7 @@ server {
---
## Testing Updates in Development
## 🧪 Testing Updates in Development
> [!NOTE]
> The auto-updater is **intentionally disabled** in development mode to prevent runtime crashes. Setting `forceDevUpdateConfig` requires a `dev-app-update.yml` file, which introduces unnecessary complexity during local development.
@@ -317,6 +316,7 @@ if (IS_DEV) {
```
This means:
- No update check is performed on startup.
- No `electron-updater` events are emitted.
- The `useElectronUpdater()` hook will remain in `idle` status.
@@ -347,52 +347,34 @@ python3 -m http.server 8080 --directory .
---
## Diagnostic Runbook
## ⚠️ Diagnostic Runbook
### Issue: "Update check failed" on startup
| | |
|---|---|
| **Symptom** | Console logs `[AutoUpdater] Startup check failed (possibly offline)` |
| **Root Cause** | The machine is offline, or the update server (GitHub/S3/generic) is unreachable. |
| **Resolution** | No action required. The error is caught in a `try/catch` block, logged to the console, and the application continues to function normally. The next check will occur on the next app launch. |
---
| Symptom | Root Cause | Resolution |
| -------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Console logs `[AutoUpdater] Startup check failed (possibly offline)` | The machine is offline, or the update server (GitHub/S3/generic) is unreachable. | No action required. The error is caught in a `try/catch` block, logged to the console, and the application continues to function normally. The next check will occur on the next app launch. |
### Issue: `app-update.yml` not found in production build
| | |
|---|---|
| **Symptom** | `electron-updater` throws "Cannot find app-update.yml" immediately after launch. |
| **Root Cause** | The `publish` block in `electron-builder.yml` is missing or misconfigured. `electron-builder` generates `app-update.yml` only when a valid provider is declared. |
| **Resolution** | Verify the `publish` block exists in `electron-builder.yml`. Run `electron-builder --publish never` and inspect `release/*/resources/app-update.yml` to confirm generation. |
---
| Symptom | Root Cause | Resolution |
| -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `electron-updater` throws "Cannot find app-update.yml" immediately after launch. | The `publish` block in `electron-builder.yml` is missing or misconfigured. `electron-builder` generates `app-update.yml` only when a valid provider is declared. | Verify the `publish` block exists in `electron-builder.yml`. Run `electron-builder --publish never` and inspect `release/*/resources/app-update.yml` to confirm generation. |
### Issue: "Cannot update: code signature is invalid" (macOS)
| | |
|---|---|
| **Symptom** | The updater downloads a new version but refuses to apply it, logging a signature validation error. |
| **Root Cause** | The application was not signed, or the signing certificate has expired / been revoked. |
| **Resolution** | Ensure `CSC_LINK` and `CSC_KEY_PASSWORD` are correctly set in CI. Verify the packaged app with: `codesign --verify --deep --strict release/mac*/Desktop.app`. Re-sign and re-publish if the certificate was rotated. |
---
| Symptom | Root Cause | Resolution |
| -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| The updater downloads a new version but refuses to apply it, logging a signature validation error. | The application was not signed, or the signing certificate has expired / been revoked. | Ensure `CSC_LINK` and `CSC_KEY_PASSWORD` are correctly set in CI. Verify the packaged app with: `codesign --verify --deep --strict release/mac*/Desktop.app`. Re-sign and re-publish if the certificate was rotated. |
### Issue: Updates work on Windows/Linux but not macOS
| | |
|---|---|
| **Symptom** | Windows and Linux users receive updates, but macOS users see no update prompt. |
| **Root Cause** | macOS requires **both** a valid code signature AND Apple notarization. Without notarization, Gatekeeper silently quarantines the update payload. |
| **Resolution** | Provide all Apple credential environment variables (`APPLE_ID`, `APPLE_APP_SPECIFIC_PASSWORD`, `APPLE_TEAM_ID`) and ensure `hardenedRuntime: true` is set in `electron-builder.yml`. Re-package and re-publish. |
---
| Symptom | Root Cause | Resolution |
| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Windows and Linux users receive updates, but macOS users see no update prompt. | macOS requires **both** a valid code signature AND Apple notarization. Without notarization, Gatekeeper silently quarantines the update payload. | Provide all Apple credential environment variables (`APPLE_ID`, `APPLE_APP_SPECIFIC_PASSWORD`, `APPLE_TEAM_ID`) and ensure `hardenedRuntime: true` is set in `electron-builder.yml`. Re-package and re-publish. |
### Issue: S3/Generic provider returns corrupted downloads
| | |
|---|---|
| **Symptom** | Users report that the update downloads but fails to install, or the downloaded file is 0 bytes. |
| **Root Cause** | The file server is serving update manifests or binaries with incorrect MIME types, or a CDN is caching stale `latest*.yml` files. |
| **Resolution** | Verify MIME types: `.yml` → `text/yaml`, `.exe`/`.dmg`/`.AppImage`/`.zip` → `application/octet-stream`. Add `Cache-Control: no-cache` headers to `latest*.yml` responses. Invalidate CDN cache after publishing a new release. |
| Symptom | Root Cause | Resolution |
| ----------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Users report that the update downloads but fails to install, or the downloaded file is 0 bytes. | The file server is serving update manifests or binaries with incorrect MIME types, or a CDN is caching stale `latest*.yml` files. | Verify MIME types: `.yml` → `text/yaml`, `.exe`/`.dmg`/`.AppImage`/`.zip` → `application/octet-stream`. Add `Cache-Control: no-cache` headers to `latest*.yml` responses. Invalidate CDN cache after publishing a new release. |
+78 -45
View File
@@ -1,4 +1,6 @@
# Configuration Guide
[← Back to Root](../../../README.md)
# Desktop Configuration Guide
The Blueprint for Runtime Control.
@@ -17,7 +19,7 @@ The Blueprint for Runtime Control.
---
## Target App Orchestration
## 🎯 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`.
@@ -37,6 +39,7 @@ DESKTOP_DEV_SERVER_URL=http://localhost:5173
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
@@ -51,31 +54,42 @@ To redirect the wrapper to a different application — for example, `apps/admin`
### 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/<target>/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`.
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/<target>/dist/` directory, and synchronizes the contents into `apps/desktop/web-dist/`. This bridge directory is then ingested by `electron-builder`.
```
┌─────────────────────────────┐ Deployment Bridge ┌──────────────────────────┐
│ apps/<target>/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/ │
└──────────────────────┘
```mermaid
graph TD
%% ─── Styling Definitions (Dark-Mode Friendly Enterprise Palette) ───
classDef webApp fill:#dbeafe,stroke:#3b82f6,stroke-width:2px,color:#1e3a8a
classDef bridge fill:#10b981,stroke:#047857,stroke-width:2px,color:#ffffff
classDef electronApp fill:#e2e8f0,stroke:#64748b,stroke-width:2px,color:#0f172a
classDef finalArtifact fill:#6366f1,stroke:#4338ca,stroke-width:2px,color:#ffffff
%% ─── Nodes ───
SOURCE[(apps/TARGET/dist/)]
SCRIPT{copy-web-dist.ts}
DEST[(apps/desktop/web-dist/)]
BUILDER(electron-builder)
OUTPUT([Packaged .app / .exe])
%% ─── Flow ───
SOURCE ===>|Vite Build Output| SCRIPT
SCRIPT ===>|Deployment Bridge Prebuild Hook| DEST
DEST -.->|files and extraResources| BUILDER
BUILDER ===> OUTPUT
%% ─── Apply Styles ───
class SOURCE webApp;
class SCRIPT bridge;
class DEST,BUILDER electronApp;
class OUTPUT finalArtifact;
```
---
## Environment Variables Registry
## 🔐 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 |
@@ -85,17 +99,16 @@ The `prebuild` hook invokes `scripts/copy-web-dist.ts`, which serves as the **De
| `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.
> [!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
## 📂 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/<target>/dist/` → `apps/desktop/web-dist/` | Deployment Bridge: build-time synchronization |
| **Development** | `__dirname` relative traversal from `out/main/` | `apps/desktop/out/main/` → `../../` → `apps/` → `<target>/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 |
@@ -105,7 +118,7 @@ The following matrix defines how the target app's static assets are resolved acr
---
## Production Routing: Overcoming Protocol Constraints
## 🚀 Production Routing: Overcoming Protocol Constraints
### The Constraint
@@ -117,18 +130,34 @@ Electron's default `file://` protocol breaks this contract. Requesting `file:///
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
```mermaid
graph TD
%% ─── Styling Definitions ───
classDef request fill:#f59e0b,stroke:#b45309,stroke-width:2px,color:#ffffff
classDef process fill:#e2e8f0,stroke:#64748b,stroke-width:2px,color:#0f172a
classDef decision fill:#10b981,stroke:#047857,stroke-width:2px,color:#ffffff
classDef success fill:#3b82f6,stroke:#1d4ed8,stroke-width:2px,color:#ffffff
%% ─── Nodes ───
REQ([Request: app://-/settings/profile])
DECODE[Decode URI and Normalize Path]
CHECK{File exists in web-dist?}
SERVE_FILE[Serve Asset with MIME + CSP]
SERVE_FALLBACK[Heuristic Fallback: Serve index.html]
REACT([React Router Handles Route])
%% ─── Flow ───
REQ ---> DECODE
DECODE ---> CHECK
CHECK ===>|YES| SERVE_FILE
CHECK -.->|NO| SERVE_FALLBACK
SERVE_FALLBACK ---> REACT
%% ─── Apply Styles ───
class REQ request;
class DECODE process;
class CHECK decision;
class SERVE_FILE,SERVE_FALLBACK,REACT success;
```
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.
@@ -154,12 +183,12 @@ protocol.registerSchemesAsPrivileged([
---
## Defense-in-Depth: Multi-Layered Protection
## 🛡️ 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://` |
@@ -169,10 +198,9 @@ The custom protocol handler enforces a **multi-layered defense perimeter** that
---
## Break Glass Procedure: Disaster Recovery Protocol
## ⚠️ 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**.
> [!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
@@ -206,6 +234,7 @@ All routes transition to hash-based addressing: `#/app/dashboard`, `#/auth/login
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([ ... ]);
```
@@ -215,6 +244,7 @@ In `apps/desktop/src/main/index.ts`, execute the following surgical removals:
**c)** Remove the `registerAppProtocol()` invocation inside `app.whenReady()`.
**d)** Redirect production content loading in `createWindow()`:
```diff
if (IS_DEV) {
mainWindow.loadURL(DEV_SERVER_URL);
@@ -227,19 +257,22 @@ In `apps/desktop/src/main/index.ts`, execute the following surgical removals:
```
**e)** Inject a CSP `<meta>` tag into the web app's `index.html`, since the In-Flight Policy Injection layer is no longer available:
```html
<meta http-equiv="Content-Security-Policy"
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self' file:; script-src 'self' file:;
style-src 'self' 'unsafe-inline' file:;
connect-src 'self' https:;
img-src 'self' file: data: https:;
font-src 'self' file: data:;" />
font-src 'self' file: data:;"
/>
```
### 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 |
+55 -40
View File
@@ -1,3 +1,5 @@
[← Back to Root](../../../README.md)
# IPC Architecture & Security Model
The Secure Communication Blueprint.
@@ -24,43 +26,55 @@ The desktop wrapper enforces a **strict privilege separation** between three exe
### Trust Level Matrix
| Context | Trust Level | Privilege Scope | Security Guarantee |
|---|---|---|---|
| ------------------ | ------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| **Main Process** | Fully Trusted | Unrestricted Node.js access: filesystem, network, printers, OS APIs, child processes | Only code authored by the engineering team executes here |
| **Preload Script** | Controlled | Restricted to `ipcRenderer.invoke()` and `ipcRenderer.send()` — no direct Node.js access | Executes in a **Hermetically Sealed Context** — isolated from both the Main Process globals and the Renderer's DOM |
| **Renderer** | Untrusted | Standard browser sandbox — zero Node.js API surface | Designated as a **Zero-Trust Environment** — may execute third-party code, npm packages, or XSS payloads |
### Process Topology
```
┌──────────────────────────────────────────────────────────────────┐
│ MAIN PROCESS [Fully Trusted] │
│ │
│ UNRESTRICTED PRIVILEGES │
│ Filesystem · Network · Printers · Native APIs · Child Processes │
│ Auto-Updater · OS Integration · System Notifications │
│ │
│ ipcMain.handle('channel', handler) ← Command handlers │
│ ipcMain.on('channel', handler) ← Event listeners │
│ webContents.send('channel', data) ← Downstream push │
├───────────── Non-Bypassable Isolation Boundary ─────────────────┤
│ PRELOAD SCRIPT [Secure Gateway] │
│ │
│ HERMETICALLY SEALED CONTEXT │
│ Performs Interface Narrowing: transforms broad IPC capabilities │
│ into a minimal, auditable API surface. Acts as the sole │
│ authorized mediator between trusted and untrusted contexts. │
│ │
│ contextBridge.exposeInMainWorld('electronAPI', { ... }) │
├───────────── Non-Bypassable Isolation Boundary ─────────────────┤
│ RENDERER [Zero-Trust Environment] │
│ │
│ UNTRUSTED WEB CONTENT │
│ Standard browser sandbox. Zero access to: require, __dirname, │
│ process, fs, child_process, net, os, ipcRenderer. │
│ │
│ ONLY authorized interaction vector: │
│ window.electronAPI.methodName(args) │
└──────────────────────────────────────────────────────────────────┘
```mermaid
graph TD
%% ─── Styling Definitions (Dark-Mode Friendly Enterprise Palette) ───
classDef trustedLayer fill:#f8fafc,stroke:#3b82f6,stroke-width:2px,color:#0f172a
classDef gatewayLayer fill:#f0fdf4,stroke:#10b981,stroke-width:2px,color:#064e3b
classDef untrustedLayer fill:#fef2f2,stroke:#ef4444,stroke-width:2px,color:#7f1d1d
classDef functionNode fill:#e2e8f0,stroke:#64748b,stroke-width:2px,color:#0f172a
%% ─── Subgraphs ───
subgraph Main ["MAIN PROCESS [Fully Trusted]"]
M_DESC["Unrestricted Node.js Privileges"]
IPC_MAIN_H[ipcMain.handle]
IPC_MAIN_O[ipcMain.on]
end
subgraph Preload ["PRELOAD SCRIPT [Secure Gateway]"]
P_DESC["Hermetically Sealed Context (Interface Narrowing)"]
CTX_BRIDGE{contextBridge.exposeInMainWorld}
end
subgraph Renderer ["RENDERER [Zero-Trust Environment]"]
R_DESC["Standard Browser Sandbox (No Node.js APIs)"]
E_API([window.electronAPI])
end
%% ─── Flow & Relationships ───
E_API ===>|Only Authorized Vector| CTX_BRIDGE
CTX_BRIDGE --->|Transforms to narrow IPC calls| IPC_MAIN_H
CTX_BRIDGE --->|Transforms to narrow IPC calls| IPC_MAIN_O
%% ─── Apply Styles ───
class Main trustedLayer;
class Preload gatewayLayer;
class Renderer untrustedLayer;
class M_DESC,P_DESC,R_DESC,IPC_MAIN_H,IPC_MAIN_O functionNode;
class CTX_BRIDGE gatewayLayer;
class E_API untrustedLayer;
%% ─── Subgraph Backgrounds (Transparent for Native GitHub Support) ───
style Main fill:transparent,stroke:#3b82f6,stroke-width:2px,stroke-dasharray: 5 5
style Preload fill:transparent,stroke:#10b981,stroke-width:2px,stroke-dasharray: 5 5
style Renderer fill:transparent,stroke:#ef4444,stroke-width:2px,stroke-dasharray: 5 5
```
The Preload Script functions as a **Secure Gateway** that performs **Interface Narrowing** — it transforms the broad, unrestricted IPC capabilities of the Main Process into a deliberately narrow, type-safe API surface. The renderer communicates with native functionality **exclusively** through this gateway. There are no alternative paths, no escape hatches, and no backdoors.
@@ -70,7 +84,7 @@ The Preload Script functions as a **Secure Gateway** that performs **Interface N
These settings are declared in `BrowserWindow.webPreferences` and are **non-negotiable**:
| Setting | Value | Enforcement |
|---|---|---|
| ------------------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `contextIsolation` | `true` | The Preload executes in a hermetically sealed V8 context. The renderer **cannot** access `require()`, Node.js globals, or any variable from the preload's scope. |
| `nodeIntegration` | `false` | **Zero** Node.js API surface in the renderer. `fs`, `child_process`, `os`, `net`, and all built-in modules are completely unavailable. |
| `sandbox` | `true` | The renderer process runs inside a **Chromium OS-level sandbox**, restricting system calls and file access at the kernel level. |
@@ -82,8 +96,7 @@ These settings are declared in `BrowserWindow.webPreferences` and are **non-nego
Every native feature in this architecture **must** follow the Three-Step Bridge — a Standard Operating Procedure (SOP) that ensures traceability, type-safety, and auditability across the entire IPC surface.
> [!IMPORTANT]
> **Deterministic Synchronization**: Maintaining parity between the Main Process handler, the Preload Gateway exposure, and the TypeScript interface declaration is **mandatory**. A mismatch between any two of the three layers will result in either a **Type-Safety Gap** (silent failures in development) or a **Runtime Regression** (crashes in production).
> [!IMPORTANT] > **Deterministic Synchronization**: Maintaining parity between the Main Process handler, the Preload Gateway exposure, and the TypeScript interface declaration is **mandatory**. A mismatch between any two of the three layers will result in either a **Type-Safety Gap** (silent failures in development) or a **Runtime Regression** (crashes in production).
### Step 1: Register the Handler — Main Process
@@ -134,6 +147,7 @@ contextBridge.exposeInMainWorld('electronAPI', electronAPI);
```
**Non-negotiable rules:**
- **Never** expose `ipcRenderer` directly — this is a **Catastrophic Failure** pattern.
- **Never** expose `ipcRenderer.on` without cleanup — use `createEventSubscription()`, which returns an unsubscribe function for React `useEffect` lifecycle management.
- **Always** declare explicit TypeScript types for all function signatures.
@@ -161,16 +175,17 @@ The following is the **complete, authoritative registry** of all authorized IPC
### Printer Subsystem
| Channel | Direction | Pattern | Payload | Access Control |
|---|---|---|---|---|
| ------------------ | -------------------------- | ------------------- | --------------------------------------------------------------------- | ------------------------------ |
| `printer:get-list` | Renderer → Main → Renderer | `invoke` / `handle` | Returns `ElectronPrinterInfo[]` | Read-only hardware enumeration |
| `printer:print` | Renderer → Main → Renderer | `invoke` / `handle` | Accepts `ElectronPrintOptions`, returns `{ success, failureReason? }` | Controlled hardware invocation |
**Main process handler:** `setupPrinterIPC()` in `src/main/index.ts`
**Preload Gateway surface:**
```typescript
getPrinters: () => ipcRenderer.invoke('printer:get-list')
print: (options?) => ipcRenderer.invoke('printer:print', options)
getPrinters: () => ipcRenderer.invoke('printer:get-list');
print: (options?) => ipcRenderer.invoke('printer:print', options);
```
**React consumption hook:** `useElectronPrinter()` in `apps/web/src/hooks/use-electron-printer.ts`
@@ -180,7 +195,7 @@ print: (options?) => ipcRenderer.invoke('printer:print', options)
### Auto-Updater Subsystem
| Channel | Direction | Pattern | Payload | Access Control |
|---|---|---|---|---|
| ----------------------- | --------------- | ------------------- | -------------------------------------------------------------- | ---------------------------------- |
| `updater:check` | Renderer → Main | `invoke` / `handle` | Returns update check result | Read-only version query |
| `updater:install` | Renderer → Main | `send` / `on` | No payload | Privileged: quits app and installs |
| `updater:checking` | Main → Renderer | `send` | No payload | Status notification |
@@ -283,7 +298,7 @@ contextBridge.exposeInMainWorld('ipc', ipcRenderer);
contextBridge.exposeInMainWorld('require', require);
```
**Threat:** The renderer can `require('child_process').exec('rm -rf /')`. A single XSS vulnerability in *any* dependency — including transitive ones — escalates to **full filesystem access, credential theft, reverse shells, and data exfiltration**.
**Threat:** The renderer can `require('child_process').exec('rm -rf /')`. A single XSS vulnerability in _any_ dependency — including transitive ones — escalates to **full filesystem access, credential theft, reverse shells, and data exfiltration**.
**Classification:** **Total System Compromise**
@@ -294,7 +309,7 @@ contextBridge.exposeInMainWorld('require', require);
```typescript
// VIOLATION: Catastrophic Failure — Complete Boundary Collapse
new BrowserWindow({
webPreferences: { nodeIntegration: true, contextIsolation: false }
webPreferences: { nodeIntegration: true, contextIsolation: false },
});
```
+2 -2
View File
@@ -1,7 +1,7 @@
# Enterprise API Engine (`@repo/core-api`)
[← Back to Root](../../README.md)
# Enterprise API Engine (`@repo/core-api`)
The platform-agnostic API engine for the monorepo. Provides an isolated HTTP client factory, a unified observability pipeline (Grafana Faro + OpenTelemetry), and a generic data services engine.
**This package enforces App Autonomy (IoC).** The core provides the engine and interceptor pipelines, but the consuming applications (`apps/web`, `apps/landing`) inject their own specific configurations, authentication tokens, and error handling behaviors.
+91 -49
View File
@@ -1,74 +1,116 @@
# @repo/core-events
[← Back to Root](../../README.md)
## Overview
# Event Bus (`@repo/core-events`)
`@repo/core-events` is the **decoupled Nervous System** of the ERP. It provides a highly performant, strictly typed Event Bus powered by `mitt` and React hooks.
The Global Pub/Sub & Hardware Integration Blueprint.
**This package is a pure tool.** It ships zero application-specific events. Each consuming app (`apps/web`, `apps/landing`, etc.) registers its own events autonomously using TypeScript Declaration Merging — the same Inversion of Control pattern used by `@repo/core-api`'s `createHttpClient` factory.
By routing communication through a centralized event bus, we achieve:
- **App Autonomy**: The core defines the bus. The app defines the contract. No circular knowledge.
- **Zero Coupling**: Publishers and subscribers don't need to import or know about each other.
- **Extreme Performance**: Components can subscribe to high-frequency data streams (like WebSockets) and update their own local state *without* triggering massive React tree re-renders.
- **Memory Safety**: The provided `useAppEvent` hook automatically handles subscription cleanup on component unmount, preventing the most common source of memory leaks in SPA architectures.
> This module provides a strictly-typed, global event bus for the monorepo ecosystem. It decouples cross-component communication and manages real-time hardware signals (such as printers and POS peripherals), ensuring a reactive and memory-safe architecture across all applications.
---
## Architecture
## 🧠 System Overview
`@repo/core-events` is the **decoupled Nervous System** of the ERP. It provides a highly performant, strictly-typed Event Bus powered by `mitt` and custom React hooks.
**This package is a pure tool.** It ships zero application-specific events. Each consuming app (`apps/web`, `apps/desktop`, etc.) registers its own events autonomously using **TypeScript Declaration Merging** — the exact same Inversion of Control (IoC) pattern utilized by our `@repo/core-api` factory and `@repo/core-storage` engine.
### Architectural Topology
### 1. Conceptual Topology: The Pub/Sub Data Flow
This diagram illustrates the high-level concept of our decoupled architecture, demonstrating how application-specific types merge into the core bus.
```mermaid
graph LR
%% ─── Styling Definitions (Dark-Mode Friendly Enterprise Palette) ───
classDef appEntity fill:#dbeafe,stroke:#3b82f6,stroke-width:2px,color:#1e3a8a
classDef coreEntity fill:#e2e8f0,stroke:#64748b,stroke-width:2px,color:#0f172a
classDef busEntity fill:#10b981,stroke:#047857,stroke-width:2px,color:#ffffff
%% ─── Nodes ───
TYPES[[App-Specific Event Types]]
PUB([Publisher Component])
BUS{Global Event Bus 'mitt'}
SUB([Subscriber Component])
%% ─── Flow ───
TYPES -.->|Declaration Merging| BUS
PUB ===>|emit 'event', payload| BUS
BUS ===>|useAppEvent 'event'| SUB
%% ─── Apply Styles ───
class TYPES,PUB,SUB appEntity;
class BUS busEntity;
```
### 2. System Architecture: Core Engine vs. App Autonomy
This detailed diagram shows the exact boundaries between the @repo/core-events engine and the consuming application, highlighting real-world publishers (e.g., Cashier UI) and subscribers.
```mermaid
graph TD
%% ─── Styling Definitions (Dark-Mode Friendly Enterprise Palette) ───
classDef appComponent fill:#dbeafe,stroke:#3b82f6,stroke-width:2px,color:#1e3a8a
classDef injection fill:#f59e0b,stroke:#b45309,stroke-width:2px,color:#ffffff
classDef registry fill:#10b981,stroke:#047857,stroke-width:2px,color:#ffffff
classDef coreBus fill:#6366f1,stroke:#4338ca,stroke-width:2px,color:#ffffff
%% ─── Subgraphs ───
subgraph Core ["@repo/core-events (Pure Tool)"]
R["AppEventRegistry<br/>(empty interface)"]
T["AppEvents = mapped type"]
E((Event Bus<br/>mitt))
H[useAppEvent / usePublishEvent]
R --> T --> E
E --> H
R[AppEventRegistry Empty Interface]
T[AppEvents Mapped Type]
E((Global Event Bus mitt))
H[Hooks: useAppEvent / usePublishEvent]
end
subgraph Apps ["apps/web (App Autonomy)"]
D["events.d.ts<br/>declare module augmentation"]
A[Cashier UI]
B[Profile Settings]
C[WebSocket Client]
X[Electron IPC Bridge]
Y[IndexedDB Sync]
Z[Stock Grid Row]
D[[events.d.ts Declaration Merging]]
%% Publishers
A([Cashier UI])
B([Profile Settings])
C([WebSocket Client])
%% Subscribers
X([Electron IPC Bridge])
Y([IndexedDB Sync])
Z([Stock Grid Row])
end
D -. "merges into" .-> R
%% ─── Flow & Relationships ───
D -.->|Augments| R
R ---> T ---> E
E ---> H
A -- "DEVICE:PRINT_RECEIPT" --> E
B -- "AUTH:PROFILE_UPDATED" --> E
C -- "WS:STOCK_UPDATE" --> E
%% Emitting Events
A ===>|DEVICE:PRINT_RECEIPT| E
B ===>|AUTH:PROFILE_UPDATED| E
C ===>|WS:STOCK_UPDATE| E
E -.-> X
E -.-> Y
E -.-> Z
%% Subscribing to Events
E -.->|Triggers| X
E -.->|Triggers| Y
E -.->|Triggers| Z
%% Styling Subgraphs (Backgrounds)
style Core fill:#f8f9fa,stroke:#ced4da,stroke-width:2px,color:#495057
style Apps fill:#e7f5ff,stroke:#74c0fc,stroke-width:2px,color:#1864ab
%% ─── Apply Styles ───
class A,B,C,X,Y,Z appComponent;
class D injection;
class R,T registry;
class E,H coreBus;
%% Styling Core Engine (Purple) & Contracts (Green)
style R fill:#20c997,stroke:#089981,color:#fff
style T fill:#20c997,stroke:#089981,color:#fff
style E fill:#845ef7,stroke:#5f3dc4,color:#fff
style H fill:#845ef7,stroke:#5f3dc4,color:#fff
%% Styling App Injection (Orange) & Components (Blue)
style D fill:#fd7e14,stroke:#d9480f,color:#fff
style A fill:#339af0,stroke:#1864ab,color:#fff
style B fill:#339af0,stroke:#1864ab,color:#fff
style C fill:#339af0,stroke:#1864ab,color:#fff
style X fill:#339af0,stroke:#1864ab,color:#fff
style Y fill:#339af0,stroke:#1864ab,color:#fff
style Z fill:#339af0,stroke:#1864ab,color:#fff
%% ─── Subgraph Backgrounds (Transparent for Native GitHub Support) ───
style Core fill:transparent,stroke:#94a3b8,stroke-width:2px,stroke-dasharray: 5 5
style Apps fill:transparent,stroke:#3b82f6,stroke-width:2px,stroke-dasharray: 5 5
```
### Core Value Proposition
By routing communication through this centralized event bus, we achieve:
* **App Autonomy**: The core defines the engine. The app defines the contract. There is zero circular dependency.
* **Zero Coupling**: Publishers and subscribers do not need to import, reference, or know about each other's existence.
* **Extreme Performance**: Components can subscribe to high-frequency data streams (like WebSockets or hardware signals) and update their own local state *without* triggering massive React tree re-renders.
* **Memory Safety**: The provided `useAppEvent` hook automatically handles subscription cleanup on component unmount, proactively preventing the most common source of memory leaks in Single Page Architectures (SPAs).
---
## Defining Events (Module Augmentation)
+2 -2
View File
@@ -1,7 +1,7 @@
# Enterprise i18n Architecture (`@repo/core-i18n`)
[← Back to Root](../../README.md)
# i18n Architecture (`@repo/core-i18n`)
A highly decoupled, type-safe internationalization engine for the monorepo.
It uses a **Hybrid Namespace Strategy**:
+2 -2
View File
@@ -1,7 +1,7 @@
# Enterprise Storage Engine (`@repo/core-storage`)
[← Back to Root](../../README.md)
# Storage Engine (`@repo/core-storage`)
`@repo/core-storage` is the **Enterprise-grade, multi-tool storage engine** for the Eigen Monorepo.
It provides a unified set of strictly-typed, secure, and fault-tolerant storage mechanisms tailored for React applications. It enforces strict **Inversion of Control (IoC)**—the core engine knows absolutely nothing about your application's business domains; instead, the consuming apps inject their own configurations and types.