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:
@@ -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. |
|
|
||||||
|
|||||||
@@ -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 |
|
||||||
|
|||||||
@@ -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 },
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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**:
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
Reference in New Issue
Block a user