Electron Integration
In Novyse, the desktop experience is powered by an Electron shell that hosts the React Native (Expo) web-build application. Because React Native code runs within a sandboxed renderer process, it cannot directly access system resources, execute raw database queries, or run local server tasks.
To bridge this gap, Novyse uses a dual-communication architecture:
- Inter-Process Communication (IPC): A secure, type-safe API bridge exposed via Electron's contextBridge and ipcRenderer.
- Local HTTP Server: A lightweight, dynamically-bound Express server running in the Electron main process to handle high-bandwidth tasks like file streaming, uploads, downloads, and rendering secure embeds.
1. Electron IPC Bridge API
The frontend application interacts with Electron through the window.electron global object, which is securely exposed in desktop/src/preload.ts via contextBridge.
| Property / Method | Communication Type | Payload / Signature | Description |
|---|---|---|---|
| platform | Property | string ("windows" | "macos" | "linux" | "unknown") | Exposes the host operating system to the web app for OS-specific UI elements. |
| rpc.request | Method (Async) | (method: string, ...args: any[]) => Promise<any> | Invokes registered main-process IPC handlers asynchronously. |
| getLocalServerUrl | Method (Sync) | () => string | Returns the loopback URL of the local Express server. |
| sendCaptchaSuccess | Method (One-way) | (token: string) => void | Sends the solved Cloudflare Turnstile token back to the main process. |
| onNotificationClick | Subscription | (callback: (data: any) => void) => () => void | Listens for native notification click events to route the user (returns an unsubscribe function). |
| window.minimize | Method (One-way) | () => void | Minimizes the desktop window. |
| window.toggleMaximize | Method (One-way) | () => void | Toggles maximize / restore state of the desktop window. |
| window.close | Method (One-way) | () => void | Closes the application. |
| window.isMaximized | Method (Async) | () => Promise<boolean> | Queries whether the window is currently maximized. |
| window.onStateChanged | Subscription | (callback: (state: { isMaximized: boolean }) => void) => () => void | Listens for changes in the window maximize state. |
| system.getInstallSource | Method (Async) | () => Promise<string> | Retrieves where the app was installed from (e.g., Flathub, system package, installer). |
| updater.check | Method (Async) | () => Promise<any> | Triggers a check for available application updates. |
| updater.download | Method (Async) | () => Promise<any> | Begins downloading the available update package. |
| updater.install | Method (Async) | () => Promise<any> | Installs the downloaded update and restarts the application. |
| updater.onStatus | Subscription | (callback: (status: any) => void) => () => void | Listens for real-time progress and status updates from the auto-updater. |
| shortcuts.register | Method (One-way) | (keys: string[], global: boolean) => void | Registers local or global system-wide keyboard shortcuts. |
| shortcuts.unregister | Method (One-way) | (keys: string[], global: boolean) => void | Unregisters specific keyboard shortcuts. |
| shortcuts.unregisterAll | Method (One-way) | () => void | Clears all registered keyboard shortcuts. |
| shortcuts.onTriggered | Subscription | (callback: (keys: string[]) => void) => () => void | Listens for keyboard shortcut trigger events. |
2. IPC Main Process Handlers (RPC)
These handlers are registered in desktop/src/rpc/index.ts using ipcMain.handle. They receive commands from rpc.request and perform native/system operations.
| IPC Channel Name | Payload Request Structure | Success Response Structure | Description |
|---|---|---|---|
| executeDbQuery | { method: "exec" | "all" | "get" | "run", query: string, params?: any[] } | { success: boolean, data?: any, error?: string } | Runs SQL operations against the local SQLite database via better-sqlite3. |
| secureStoreSet | { key: string, value: string } | { success: boolean, error?: string } | Encrypts a value using Electron's safeStorage and writes it to the app storage directory. |
| secureStoreGet | { key: string } | { success: boolean, value?: string, error?: string } | Reads and decrypts an encrypted value from disk. |
| secureStoreDelete | { key: string } | { success: boolean, error?: string } | Deletes a stored encrypted key-value pair file from disk. |
| openFileDialog | { allowedFileTypes?: string, allowsMultipleSelection?: boolean } | { success: boolean, assets: Array<{ uri, name, size, mimeType }>, error?: string } | Displays the OS native file chooser dialog. |
| openFile | { fileRef: string } | { success: boolean, error?: string } | Opens a file stored in the local cache using the host system's default program. |
| showNotification | { title: string, body?: string, subtitle?: string, data?: any, icon?: string } | { success: boolean, error?: string } | Dispatches native OS notifications. Preprocesses and resizes custom avatar icons with sharp. |
| system:set-open-on-startup | { openAtLogin: boolean } | { success: boolean, error?: string } | Configures auto-start. Generates a .desktop file on Linux or calls setLoginItemSettings on Windows/macOS. |
| system:get-open-on-startup | None | { success: boolean, openAtLogin: boolean, error?: string } | Checks if auto-start settings are currently active on the host machine. |
3. Local HTTP Server Endpoints
To avoid JSON-serialization overhead for large files and bypass Chromium sandbox restrictions, a local Express server runs on localhost under a dynamically-bound port (port 0). It is started via startLocalServer() in desktop/src/server/index.ts.
| Endpoint | Method | Request Body / Payload | Description |
|---|---|---|---|
| /captcha | GET | None | Serves a local HTML page executing the Cloudflare Turnstile CAPTCHA implementation. |
| /files/download | POST | { url: string, key: string } | Downloads a file from a remote URL directly to the local cache storage (FILES_DIR) and saves its MIME type. |
| /files/copy | POST | { sourcePath: string, key: string } | Copies a file from a local host path to the application's internal cache storage. |
| /files/:key | POST | Binary Data (octet-stream) | Accepts raw binary streams (up to 500MB) to upload/save files to the local cache under a unique key. |
| /files/:key | GET | None (URL Param :key) | Serves a locally cached file back to the renderer process with HTTP caching headers and correct MIME type. |
| /files/:key | HEAD | None (URL Param :key) | Checks for existence of the file in the local cache without transferring contents. Returns 200 or 404. |
| /embed/html | POST | Plain Text (HTML content) | Registers raw HTML snippets in-memory and returns a temporary local URL (e.g. http://localhost:port/embed/html/embed_1). |
| /embed/html/:id | GET | None (URL Param :id) | Serves the registered HTML snippet. Useful for embedding sandboxed third-party players (like YouTube or Kick) within the app. |