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](#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
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. 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.
``` ```mermaid
┌──────────────────────────────────────────────────────────────────┐ graph TD
│ Main Process (src/main/index.ts) │ %% ─── Styling Definitions (Dark-Mode Friendly Enterprise Palette) ───
│ │ classDef appEntity fill:#dbeafe,stroke:#3b82f6,stroke-width:2px,color:#1e3a8a
│ autoUpdater.checkForUpdatesAndNotify() │ classDef coreEntity fill:#e2e8f0,stroke:#64748b,stroke-width:2px,color:#0f172a
│ │ │ classDef ipcBridge fill:#10b981,stroke:#047857,stroke-width:2px,color:#ffffff
│ ├─→ 'checking-for-update' │
│ ├─→ 'update-available' → { version, releaseDate } │ %% ─── Subgraphs ───
│ ├─→ 'download-progress' → { percent, bytesPerSecond } │ subgraph MainProcess ["Main Process (src/main/index.ts)"]
│ ├─→ 'update-downloaded' → { version, releaseNotes } │ AUTO[autoUpdater.checkForUpdatesAndNotify]
│ └─→ 'error' → { message } │ EVENTS{{Update Events: progress, downloaded, error}}
│ │ SENDER[sendToRenderer]
sendToRenderer('updater:*', payload) │ end
├──────────────────────── IPC Bridge ──────────────────────────────┤
│ Preload (src/preload/index.ts) │ subgraph Preload ["IPC Bridge (src/preload/index.ts)"]
│ │ EXPOSE{contextBridge.exposeInMainWorld}
│ contextBridge.exposeInMainWorld('electronAPI', { │ end
│ onUpdateAvailable, onDownloadProgress, │
│ onUpdateDownloaded, onUpdateError, │ subgraph Renderer ["Renderer (React App - apps/web)"]
│ checkForUpdates, installUpdate │ HOOK([useElectronUpdater Hook])
│ }) │ ACTIONS[UI Actions: Install, Check]
├──────────────────────── Renderer ────────────────────────────────┤ end
│ React App (apps/web) │
│ │ %% ─── Flow & Relationships ───
│ useElectronUpdater() hook │ AUTO ---> EVENTS
│ → Reactive state: status, progress, updateInfo, errorMessage │ EVENTS ---> SENDER
│ → Actions: checkForUpdates(), installUpdate() │ 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 ### 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`: The update provider is declared in `electron-builder.yml`:
@@ -77,6 +77,7 @@ publish:
### Operational Mechanics ### Operational Mechanics
1. When `electron-builder --publish always` executes, it: 1. When `electron-builder --publish always` executes, it:
- Compiles the application for the target platform. - Compiles the application for the target platform.
- Uploads the installer(s) to a **GitHub Release** tagged with the version from `package.json`. - 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). - 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. 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 GH_TOKEN=your_token electron-builder --publish always --config electron-builder.yml
``` ```
> [!CAUTION] > [!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.
> **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) ### Automated Release (GitHub Actions)
@@ -155,10 +155,10 @@ jobs:
--- ---
## CI/CD Environment Variables ## 🔐 CI/CD Environment Variables
| Variable | Required | Platform | Description | | Variable | Required | Platform | Description |
|---|---|---|---| | ----------------------------- | ------------- | -------------- | --------------------------------------------------------------------------------------------- |
| `GH_TOKEN` | Yes | All | GitHub personal access token with `repo` scope. Authorizes release artifact uploads. | | `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_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. | | `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) ### AWS S3 (Private Infrastructure)
@@ -196,12 +196,13 @@ publish:
**Additional environment variables:** **Additional environment variables:**
| Variable | Description | | Variable | Description |
|---|---| | ----------------------- | -------------------------------------------------------------- |
| `AWS_ACCESS_KEY_ID` | IAM access key with S3 `PutObject` and `GetObject` permissions | | `AWS_ACCESS_KEY_ID` | IAM access key with S3 `PutObject` and `GetObject` permissions |
| `AWS_SECRET_ACCESS_KEY` | IAM secret key | | `AWS_SECRET_ACCESS_KEY` | IAM secret key |
**Bucket structure:** **Bucket structure:**
```
```text
your-bucket/desktop-releases/ your-bucket/desktop-releases/
├── latest.yml (Windows manifest) ├── latest.yml (Windows manifest)
├── latest-mac.yml (macOS manifest) ├── latest-mac.yml (macOS manifest)
@@ -222,13 +223,12 @@ For self-hosted infrastructure (Nginx, Caddy, etc.):
```yaml ```yaml
publish: publish:
provider: generic 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. Your server must host the same directory structure as the S3 layout above.
> [!IMPORTANT] > [!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.
> **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:** **Nginx reference:**
@@ -253,10 +253,9 @@ server {
--- ---
## Code Signing: The Trust Boundary ## 🛡️ Code Signing: The Trust Boundary
> [!WARNING] > [!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**.
> **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 ### macOS
@@ -273,7 +272,7 @@ server {
```xml ```xml
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" <!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"> <plist version="1.0">
<dict> <dict>
<key>com.apple.security.cs.allow-jit</key> <key>com.apple.security.cs.allow-jit</key>
@@ -300,7 +299,7 @@ server {
--- ---
## Testing Updates in Development ## 🧪 Testing Updates in Development
> [!NOTE] > [!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. > 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: This means:
- No update check is performed on startup. - No update check is performed on startup.
- No `electron-updater` events are emitted. - No `electron-updater` events are emitted.
- The `useElectronUpdater()` hook will remain in `idle` status. - 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 ### Issue: "Update check failed" on startup
| | | | Symptom | Root Cause | Resolution |
|---|---| | -------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Symptom** | Console logs `[AutoUpdater] Startup check failed (possibly offline)` | | 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. |
| **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. |
---
### Issue: `app-update.yml` not found in production build ### Issue: `app-update.yml` not found in production build
| | | | Symptom | Root Cause | Resolution |
|---|---| | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Symptom** | `electron-updater` throws "Cannot find app-update.yml" immediately after launch. | | `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. |
| **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. |
---
### Issue: "Cannot update: code signature is invalid" (macOS) ### Issue: "Cannot update: code signature is invalid" (macOS)
| | | | Symptom | Root Cause | Resolution |
|---|---| | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Symptom** | The updater downloads a new version but refuses to apply it, logging a signature validation error. | | 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. |
| **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. |
---
### Issue: Updates work on Windows/Linux but not macOS ### Issue: Updates work on Windows/Linux but not macOS
| | | | Symptom | Root Cause | Resolution |
|---|---| | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Symptom** | Windows and Linux users receive updates, but macOS users see no update prompt. | | 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. |
| **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. |
---
### Issue: S3/Generic provider returns corrupted downloads ### Issue: S3/Generic provider returns corrupted downloads
| | | | Symptom | Root Cause | Resolution |
|---|---| | ----------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Symptom** | Users report that the update downloads but fails to install, or the downloaded file is 0 bytes. | | 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. |
| **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. |
+78 -45
View File
@@ -1,4 +1,6 @@
# Configuration Guide [← Back to Root](../../../README.md)
# Desktop Configuration Guide
The Blueprint for Runtime Control. 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`. 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: 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:** 1. **Update the `.env` declaration:**
```env ```env
DESKTOP_TARGET_APP=admin DESKTOP_TARGET_APP=admin
DESKTOP_DEV_SERVER_URL=http://localhost:3001 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 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`.
``` ```mermaid
┌─────────────────────────────┐ Deployment Bridge ┌──────────────────────────┐ graph TD
│ apps/<target>/dist/ │ ─── copy-web-dist.ts ────────→ │ apps/desktop/web-dist/ │ %% ─── Styling Definitions (Dark-Mode Friendly Enterprise Palette) ───
│ (Vite build output) │ prebuild hook │ (Native container) │ 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
electron-builder
files + extraResources %% ─── Nodes ───
SOURCE[(apps/TARGET/dist/)]
SCRIPT{copy-web-dist.ts}
┌──────────────────────┐ DEST[(apps/desktop/web-dist/)]
│ Packaged .app/.exe │ BUILDER(electron-builder)
│ resources/web-dist/ │ 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 | | 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_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 | | `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 | | `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_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 | | `APPLE_TEAM_ID` | — | CI/CD (macOS) | `electron-builder` | Apple Developer Team ID |
> [!IMPORTANT] > [!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.
> **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. 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 | | 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 | | **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 | | **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 | | **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 ### 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: The `app://` scheme is a **Privileged Virtual File System** that resolves SPA routing conflicts by implementing a **Heuristic Resource Loader**. It operates as follows:
``` ```mermaid
Request: app://-/settings/profile graph TD
%% ─── Styling Definitions ───
├─ Decode URI → "settings/profile" classDef request fill:#f59e0b,stroke:#b45309,stroke-width:2px,color:#ffffff
classDef process fill:#e2e8f0,stroke:#64748b,stroke-width:2px,color:#0f172a
├─ Normalize + validate path (security boundary check) classDef decision fill:#10b981,stroke:#047857,stroke-width:2px,color:#ffffff
classDef success fill:#3b82f6,stroke:#1d4ed8,stroke-width:2px,color:#ffffff
├─ Does web-dist/settings/profile exist as a file?
│ ├─ YES → Serve with correct MIME type + CSP headers %% ─── Nodes ───
│ └─ NO → Heuristic Fallback: serve web-dist/index.html REQ([Request: app://-/settings/profile])
DECODE[Decode URI and Normalize Path]
└─ React Router resolves /settings/profile client-side 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. 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. The custom protocol handler enforces a **multi-layered defense perimeter** that goes beyond standard Electron security defaults.
| Layer | Technique | Implementation | Threat Mitigated | | 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` | | **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 | | **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://` | | **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] > [!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**.
> **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 ### 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: In `apps/desktop/src/main/index.ts`, execute the following surgical removals:
**a)** Remove the scheme registration block at the top of the file: **a)** Remove the scheme registration block at the top of the file:
```diff ```diff
- protocol.registerSchemesAsPrivileged([ ... ]); - 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()`. **c)** Remove the `registerAppProtocol()` invocation inside `app.whenReady()`.
**d)** Redirect production content loading in `createWindow()`: **d)** Redirect production content loading in `createWindow()`:
```diff ```diff
if (IS_DEV) { if (IS_DEV) {
mainWindow.loadURL(DEV_SERVER_URL); 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: **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 ```html
<meta http-equiv="Content-Security-Policy" <meta
http-equiv="Content-Security-Policy"
content="default-src 'self' file:; script-src 'self' file:; content="default-src 'self' file:; script-src 'self' file:;
style-src 'self' 'unsafe-inline' file:; style-src 'self' 'unsafe-inline' file:;
connect-src 'self' https:; connect-src 'self' https:;
img-src 'self' file: data: https:; img-src 'self' file: data: https:;
font-src 'self' file: data:;" /> font-src 'self' file: data:;"
/>
``` ```
### Trade-off Analysis ### Trade-off Analysis
| Dimension | Custom `app://` Protocol | `file://` + HashRouter | | Dimension | Custom `app://` Protocol | `file://` + HashRouter |
|---|---|---| | --------------------------------- | -------------------------------------------------- | ----------------------------------------------------------- |
| **Aesthetic Integrity** | Clean URLs: `/app/dashboard` | Hash prefix: `#/app/dashboard` | | **Aesthetic Integrity** | Clean URLs: `/app/dashboard` | Hash prefix: `#/app/dashboard` |
| **Router Compatibility** | `BrowserRouter` — zero changes required | Must migrate to `HashRouter` | | **Router Compatibility** | `BrowserRouter` — zero changes required | Must migrate to `HashRouter` |
| **Deep Linking** | Full, native-style support | Hash-based only | | **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 # IPC Architecture & Security Model
The Secure Communication Blueprint. The Secure Communication Blueprint.
@@ -24,43 +26,55 @@ The desktop wrapper enforces a **strict privilege separation** between three exe
### Trust Level Matrix ### Trust Level Matrix
| Context | Trust Level | Privilege Scope | Security Guarantee | | 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 | | **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 | | **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 | | **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 ### Process Topology
``` ```mermaid
┌──────────────────────────────────────────────────────────────────┐ graph TD
│ MAIN PROCESS [Fully Trusted] │ %% ─── Styling Definitions (Dark-Mode Friendly Enterprise Palette) ───
│ │ classDef trustedLayer fill:#f8fafc,stroke:#3b82f6,stroke-width:2px,color:#0f172a
│ UNRESTRICTED PRIVILEGES │ classDef gatewayLayer fill:#f0fdf4,stroke:#10b981,stroke-width:2px,color:#064e3b
│ Filesystem · Network · Printers · Native APIs · Child Processes │ classDef untrustedLayer fill:#fef2f2,stroke:#ef4444,stroke-width:2px,color:#7f1d1d
│ Auto-Updater · OS Integration · System Notifications │ classDef functionNode fill:#e2e8f0,stroke:#64748b,stroke-width:2px,color:#0f172a
│ │
│ ipcMain.handle('channel', handler) ← Command handlers │ %% ─── Subgraphs ───
│ ipcMain.on('channel', handler) ← Event listeners │ subgraph Main ["MAIN PROCESS [Fully Trusted]"]
│ webContents.send('channel', data) ← Downstream push │ M_DESC["Unrestricted Node.js Privileges"]
├───────────── Non-Bypassable Isolation Boundary ─────────────────┤ IPC_MAIN_H[ipcMain.handle]
│ PRELOAD SCRIPT [Secure Gateway] │ IPC_MAIN_O[ipcMain.on]
│ │ end
│ HERMETICALLY SEALED CONTEXT │
│ Performs Interface Narrowing: transforms broad IPC capabilities │ subgraph Preload ["PRELOAD SCRIPT [Secure Gateway]"]
│ into a minimal, auditable API surface. Acts as the sole │ P_DESC["Hermetically Sealed Context (Interface Narrowing)"]
│ authorized mediator between trusted and untrusted contexts. │ CTX_BRIDGE{contextBridge.exposeInMainWorld}
│ │ end
│ contextBridge.exposeInMainWorld('electronAPI', { ... }) │
├───────────── Non-Bypassable Isolation Boundary ─────────────────┤ subgraph Renderer ["RENDERER [Zero-Trust Environment]"]
│ RENDERER [Zero-Trust Environment] │ R_DESC["Standard Browser Sandbox (No Node.js APIs)"]
│ │ E_API([window.electronAPI])
│ UNTRUSTED WEB CONTENT │ end
│ Standard browser sandbox. Zero access to: require, __dirname, │
│ process, fs, child_process, net, os, ipcRenderer. │ %% ─── Flow & Relationships ───
│ │ E_API ===>|Only Authorized Vector| CTX_BRIDGE
│ ONLY authorized interaction vector: │ CTX_BRIDGE --->|Transforms to narrow IPC calls| IPC_MAIN_H
│ window.electronAPI.methodName(args) │ 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. 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**: These settings are declared in `BrowserWindow.webPreferences` and are **non-negotiable**:
| Setting | Value | Enforcement | | 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. | | `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. | | `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. | | `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. 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] > [!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).
> **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 ### Step 1: Register the Handler — Main Process
@@ -134,6 +147,7 @@ contextBridge.exposeInMainWorld('electronAPI', electronAPI);
``` ```
**Non-negotiable rules:** **Non-negotiable rules:**
- **Never** expose `ipcRenderer` directly — this is a **Catastrophic Failure** pattern. - **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. - **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. - **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 ### Printer Subsystem
| Channel | Direction | Pattern | Payload | Access Control | | Channel | Direction | Pattern | Payload | Access Control |
|---|---|---|---|---| | ------------------ | -------------------------- | ------------------- | --------------------------------------------------------------------- | ------------------------------ |
| `printer:get-list` | Renderer → Main → Renderer | `invoke` / `handle` | Returns `ElectronPrinterInfo[]` | Read-only hardware enumeration | | `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 | | `printer:print` | Renderer → Main → Renderer | `invoke` / `handle` | Accepts `ElectronPrintOptions`, returns `{ success, failureReason? }` | Controlled hardware invocation |
**Main process handler:** `setupPrinterIPC()` in `src/main/index.ts` **Main process handler:** `setupPrinterIPC()` in `src/main/index.ts`
**Preload Gateway surface:** **Preload Gateway surface:**
```typescript ```typescript
getPrinters: () => ipcRenderer.invoke('printer:get-list') getPrinters: () => ipcRenderer.invoke('printer:get-list');
print: (options?) => ipcRenderer.invoke('printer:print', options) print: (options?) => ipcRenderer.invoke('printer:print', options);
``` ```
**React consumption hook:** `useElectronPrinter()` in `apps/web/src/hooks/use-electron-printer.ts` **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 ### Auto-Updater Subsystem
| Channel | Direction | Pattern | Payload | Access Control | | Channel | Direction | Pattern | Payload | Access Control |
|---|---|---|---|---| | ----------------------- | --------------- | ------------------- | -------------------------------------------------------------- | ---------------------------------- |
| `updater:check` | Renderer → Main | `invoke` / `handle` | Returns update check result | Read-only version query | | `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:install` | Renderer → Main | `send` / `on` | No payload | Privileged: quits app and installs |
| `updater:checking` | Main → Renderer | `send` | No payload | Status notification | | `updater:checking` | Main → Renderer | `send` | No payload | Status notification |
@@ -283,7 +298,7 @@ contextBridge.exposeInMainWorld('ipc', ipcRenderer);
contextBridge.exposeInMainWorld('require', require); 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** **Classification:** **Total System Compromise**
@@ -294,7 +309,7 @@ contextBridge.exposeInMainWorld('require', require);
```typescript ```typescript
// VIOLATION: Catastrophic Failure — Complete Boundary Collapse // VIOLATION: Catastrophic Failure — Complete Boundary Collapse
new BrowserWindow({ 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) [← 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. 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. **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) [← 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. > 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.
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.
--- ---
## 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 ```mermaid
graph TD 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)"] subgraph Core ["@repo/core-events (Pure Tool)"]
R["AppEventRegistry<br/>(empty interface)"] R[AppEventRegistry Empty Interface]
T["AppEvents = mapped type"] T[AppEvents Mapped Type]
E((Event Bus<br/>mitt)) E((Global Event Bus mitt))
H[useAppEvent / usePublishEvent] H[Hooks: useAppEvent / usePublishEvent]
R --> T --> E
E --> H
end end
subgraph Apps ["apps/web (App Autonomy)"] subgraph Apps ["apps/web (App Autonomy)"]
D["events.d.ts<br/>declare module augmentation"] D[[events.d.ts Declaration Merging]]
A[Cashier UI]
B[Profile Settings] %% Publishers
C[WebSocket Client] A([Cashier UI])
X[Electron IPC Bridge] B([Profile Settings])
Y[IndexedDB Sync] C([WebSocket Client])
Z[Stock Grid Row]
%% Subscribers
X([Electron IPC Bridge])
Y([IndexedDB Sync])
Z([Stock Grid Row])
end end
D -. "merges into" .-> R %% ─── Flow & Relationships ───
D -.->|Augments| R
R ---> T ---> E
E ---> H
A -- "DEVICE:PRINT_RECEIPT" --> E %% Emitting Events
B -- "AUTH:PROFILE_UPDATED" --> E A ===>|DEVICE:PRINT_RECEIPT| E
C -- "WS:STOCK_UPDATE" --> E B ===>|AUTH:PROFILE_UPDATED| E
C ===>|WS:STOCK_UPDATE| E
E -.-> X %% Subscribing to Events
E -.-> Y E -.->|Triggers| X
E -.-> Z E -.->|Triggers| Y
E -.->|Triggers| Z
%% Styling Subgraphs (Backgrounds) %% ─── Apply Styles ───
style Core fill:#f8f9fa,stroke:#ced4da,stroke-width:2px,color:#495057 class A,B,C,X,Y,Z appComponent;
style Apps fill:#e7f5ff,stroke:#74c0fc,stroke-width:2px,color:#1864ab class D injection;
class R,T registry;
class E,H coreBus;
%% Styling Core Engine (Purple) & Contracts (Green) %% ─── Subgraph Backgrounds (Transparent for Native GitHub Support) ───
style R fill:#20c997,stroke:#089981,color:#fff style Core fill:transparent,stroke:#94a3b8,stroke-width:2px,stroke-dasharray: 5 5
style T fill:#20c997,stroke:#089981,color:#fff style Apps fill:transparent,stroke:#3b82f6,stroke-width:2px,stroke-dasharray: 5 5
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
``` ```
### 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) ## Defining Events (Module Augmentation)
+2 -2
View File
@@ -1,7 +1,7 @@
# Enterprise i18n Architecture (`@repo/core-i18n`)
[← Back to Root](../../README.md) [← Back to Root](../../README.md)
# i18n Architecture (`@repo/core-i18n`)
A highly decoupled, type-safe internationalization engine for the monorepo. A highly decoupled, type-safe internationalization engine for the monorepo.
It uses a **Hybrid Namespace Strategy**: 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) [← 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. `@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. 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.