File size: 8,916 Bytes
cd8bd0a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# OmniRoute Electron Desktop App

This directory contains the Electron desktop application wrapper for OmniRoute.

## Architecture (v1.6.4)

```
electron/
β”œβ”€β”€ main.js          # Main process β€” window, tray, server lifecycle, CSP, IPC
β”œβ”€β”€ preload.js       # Preload script β€” secure IPC bridge with disposer pattern
β”œβ”€β”€ package.json     # Electron-specific dependencies & electron-builder config
β”œβ”€β”€ types.d.ts       # TypeScript definitions (AppInfo, ServerStatus, ElectronAPI)
└── assets/          # Application icons and resources

src/shared/hooks/
└── useElectron.ts   # React hooks β€” useSyncExternalStore, zero re-renders
```

## Key Design Decisions

| Decision                      | Rationale                                                                                        |
| ----------------------------- | ------------------------------------------------------------------------------------------------ |
| `waitForServer()` polling     | Prevents blank screen on cold start β€” polls `http://localhost:PORT` before loading               |
| `stdio: 'pipe'`               | Captures server stdout/stderr for logging + readiness detection (not `inherit`)                  |
| Disposer pattern              | `onServerStatus()` returns `() => void` for precise listener cleanup (no `removeAllListeners`)   |
| `useSyncExternalStore`        | Zero re-renders for `useIsElectron()` β€” no `useState` + `useEffect` cycle                        |
| CSP via session headers       | `Content-Security-Policy` restricts `script-src`, `connect-src` etc. per Electron best practices |
| Platform-conditional titlebar | `titleBarStyle: 'hiddenInset'` only on macOS; `default` on Windows/Linux                         |

## Development

### Prerequisites

1. Build the Next.js app first:

```bash
npm run build
```

2. Install Electron dependencies:

```bash
cd electron
npm install
```

### Running in Development

1. Start the Next.js development server:

```bash
npm run dev
```

2. In another terminal, start Electron:

```bash
cd electron
npm run dev
```

### Running in Production Mode

1. Build Next.js in standalone mode:

```bash
npm run build
```

2. Start Electron:

```bash
cd electron
npm start
```

## Building

### Build for Current Platform

```bash
cd electron
npm run build
```

### Build for Specific Platforms

```bash
# Windows
npm run build:win

# macOS (x64 + arm64)
npm run build:mac

# Linux
npm run build:linux
```

## Output

Built applications are placed in `dist-electron/`:

- Windows: `.exe` installer (NSIS) + portable `.exe`
- macOS: `.dmg` installer (Intel + Apple Silicon)
- Linux: `.AppImage`

## Installation

### macOS

1. Download the latest `.dmg` from the [Releases](https://github.com/diegosouzapw/OmniRoute/releases) page.
2. Open the `.dmg` file.
3. Drag `OmniRoute.app` to the Applications folder.
4. Launch from Applications.

> ⚠️ **Note:** The app is not signed with an Apple Developer certificate yet. If macOS blocks the app, run:
> ```bash
> xattr -cr /Applications/OmniRoute.app
> ```
> Or right-click the app β†’ Open β†’ Open (to bypass Gatekeeper on first launch).

### Windows

**Installer (Recommended):**
1. Download `OmniRoute.Setup.*.exe` from [Releases](https://github.com/diegosouzapw/OmniRoute/releases).
2. Run the installer.
3. Launch from Start Menu or Desktop shortcut.

**Portable (No Installation):**
1. Download `OmniRoute.exe` from [Releases](https://github.com/diegosouzapw/OmniRoute/releases).
2. Run directly from any folder.

### Linux

1. Download the `.AppImage` from [Releases](https://github.com/diegosouzapw/OmniRoute/releases).
2. Make it executable:
   ```bash
   chmod +x OmniRoute-*.AppImage
   ```
3. Run:
   ```bash
   ./OmniRoute-*.AppImage
   ```

## Features

- **Server Readiness** β€” Waits for health check before showing window
- **System Tray** β€” Minimize to tray with quick actions (open, port change, quit)
- **Port Management** β€” Change port from tray menu (server restarts automatically)
- **Window Controls** β€” Custom minimize, maximize, close via IPC
- **Content Security Policy** β€” Restrictive CSP via session headers
- **Offline Support** β€” Bundled Next.js standalone server
- **Single Instance** β€” Only one app instance can run at a time

## Configuration

### Environment Variables

| Variable              | Default      | Description                       |
| --------------------- | ------------ | --------------------------------- |
| `OMNIROUTE_PORT`      | `20128`      | Server port                       |
| `OMNIROUTE_MEMORY_MB` | `512`        | Node.js heap limit (64–16384 MB)  |
| `NODE_ENV`            | `production` | Set to `development` for dev mode |

### Custom Icon

Place your icons in `assets/`:

- `icon.ico` β€” Windows icon (256Γ—256)
- `icon.icns` β€” macOS icon bundle
- `icon.png` β€” Linux/general use (512Γ—512)
- `tray-icon.png` β€” System tray icon (16Γ—16 or 32Γ—32)

## IPC Channels

### Invoke (Renderer β†’ Main, async)

| Channel          | Returns       | Description                                   |
| ---------------- | ------------- | --------------------------------------------- |
| `get-app-info`   | `AppInfo`     | App name, version, platform, isDev, port      |
| `open-external`  | `void`        | Open URL in default browser (http/https only) |
| `get-data-dir`   | `string`      | Get userData directory path                   |
| `restart-server` | `{ success }` | Stop + restart server (5s timeout + SIGKILL)  |

### Send (Renderer β†’ Main, fire-and-forget)

| Channel           | Description                     |
| ----------------- | ------------------------------- |
| `window-minimize` | Minimize window                 |
| `window-maximize` | Toggle maximize/restore         |
| `window-close`    | Close window (minimize to tray) |

### Receive (Main β†’ Renderer, events)

| Channel         | Payload        | Emitted When                              |
| --------------- | -------------- | ----------------------------------------- |
| `server-status` | `ServerStatus` | Server starts, stops, errors, or restarts |
| `port-changed`  | `number`       | Port change via tray menu                 |

> **Note**: Listeners return disposer functions for precise cleanup. See `useServerStatus` and `usePortChanged` hooks.

## Security

| Feature           | Implementation                                                                  |
| ----------------- | ------------------------------------------------------------------------------- |
| Context Isolation | `contextIsolation: true` β€” renderer cannot access Node.js                       |
| Node Integration  | `nodeIntegration: false` β€” no `require()` in renderer                           |
| IPC Whitelist     | Channel names validated in preload via `safeInvoke`/`safeSend`/`safeOn`         |
| URL Validation    | `shell.openExternal()` only allows `http:` / `https:` protocols                 |
| CSP               | `Content-Security-Policy` header set via `session.webRequest.onHeadersReceived` |
| Web Security      | `webSecurity: true` β€” same-origin policy enforced                               |

## React Hooks

| Hook                   | Returns                         | Description                                      |
| ---------------------- | ------------------------------- | ------------------------------------------------ |
| `useIsElectron()`      | `boolean`                       | Zero-render detection via `useSyncExternalStore` |
| `useElectronAppInfo()` | `{ appInfo, loading, error }`   | App info from main process                       |
| `useDataDir()`         | `{ dataDir, loading, error }`   | User data directory                              |
| `useWindowControls()`  | `{ minimize, maximize, close }` | Window control actions                           |
| `useOpenExternal()`    | `{ openExternal }`              | Open URLs in browser                             |
| `useServerControls()`  | `{ restart, restarting }`       | Server restart control                           |
| `useServerStatus(cb)`  | Disposer                        | Listen for server status events                  |
| `usePortChanged(cb)`   | Disposer                        | Listen for port change events                    |

## Troubleshooting

### App Won't Start

1. Check if port 20128 is available: `lsof -i :20128`
2. Check console logs for `[Electron]` prefix
3. Verify the build output exists in `.build/next/standalone`

### White Screen

1. Verify Next.js build exists β€” server readiness waits 30s max
2. Check `[Server]` and `[Server:err]` log output
3. Look for CSP violations in developer console

### Build Fails

Ensure you have build tools installed:

- Windows: Visual Studio Build Tools
- macOS: Xcode Command Line Tools
- Linux: `build-essential`, `libsecret-1-dev`

## License

MIT