Inter-Process Communication (IPC)
Relevant source files
The following files were used as context for generating this wiki page:
Inter-Process Communication (IPC) is the foundational mechanism that enables GeekezBrowser's user interface (the Renderer process) to interact with the backend logic, file system, and native operating system features (the Main process). As an Electron application, it leverages a secure IPC bridge to pass messages and data between these isolated environments. The Main process (main.js) acts as a server, handling requests for tasks like launching browser profiles, managing files, and running system commands.
The Renderer process (renderer.js), which runs the index.html page, acts as the client. It cannot directly access Node.js APIs or the file system. Instead, it sends asynchronous requests through a secure bridge defined in preload.js. This preload script uses Electron's contextBridge to expose a specific set of functions (window.electronAPI) to the renderer, ensuring that only predefined backend capabilities are accessible, which is a critical security practice.
IPC Architecture
The application's IPC architecture is based on Electron's standard model, separating the application into two primary process types: a single Main process and one or more Renderer processes. A preload script serves as the crucial, secure link between them.
- Main Process (
main.js): The application's entry point and backend. It has full Node.js API access and is responsible for creating windows, managing application lifecycle, and handling all interactions with the operating system, file system, and child processes (like Puppeteer and Xray). It listens for IPC messages usingipcMain. - Renderer Process (
renderer.js&index.html): The application's user interface, running in a browser window. It is responsible for rendering HTML, CSS, and executing client-side JavaScript. For security, it runs in an isolated context without direct Node.js access. - Preload Script (
preload.js): A script that runs in a privileged context with access to both the Renderer'swindowobject and a subset of Node.js APIs. Its primary role is to create a secure communication bridge usingcontextBridge.exposeInMainWorld, exposing a controlled API to the Renderer process.
The following diagram illustrates the relationship between these components.
This diagram shows how the Main process spawns a BrowserWindow which loads index.html. The preload.js script is attached to this window, creating the window.electronAPI object that renderer.js uses to communicate back to the ipcMain listeners in main.js.
Sources: main.js:68, main.js:93, preload.js:4, renderer.js:433, index.html:681
The IPC Bridge (preload.js)
The core of the secure communication channel is established in preload.js. It uses contextBridge.exposeInMainWorld to expose an object named electronAPI to the window object of the renderer process. This API acts as a proxy, forwarding calls from the renderer to the main process via ipcRenderer.invoke or setting up listeners with ipcRenderer.on.
This approach is crucial for security because it avoids exposing the entire ipcRenderer object to the renderer, which could allow malicious client-side code to send arbitrary IPC messages.
// preload.js
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
getProfiles: () => ipcRenderer.invoke('get-profiles'),
saveProfile: (data) => ipcRenderer.invoke('save-profile', data),
// ... other exposed functions
invoke: (channel, data) => ipcRenderer.invoke(channel, data),
getRunningIds: () => ipcRenderer.invoke('get-running-ids'),
onProfileStatus: (callback) => ipcRenderer.on('profile-status', (event, data) => callback(data))
});
Sources: preload.js:4-16
The renderer can then access these functions like this:
// renderer.js
const profiles = await window.electronAPI.getProfiles();
await window.electronAPI.saveProfile({ name: 'New Profile', ... });
Sources: renderer.js:520, renderer.js:602
IPC Channels and Handlers
The application defines numerous IPC channels to handle specific functionalities. Most channels use a two-way invoke/handle pattern for request-response communication, while one channel (profile-status) uses a one-way send/on pattern for push notifications from main to renderer.
| Channel Name | Type | Direction | Description | Source File(s) |
|---|---|---|---|---|
get-app-info | handle | Renderer → Main | Fetches application name and version. | main.js:94 |
fetch-url | handle | Renderer → Main | Fetches content from a given URL. Used for subscription updates. | main.js:95 |
test-proxy-latency | handle | Renderer → Main | Spawns a temporary Xray process to test the latency of a proxy. | main.js:96 |
set-title-bar-color | handle | Renderer → Main | Sets the window title bar color for theming. | main.js:111 |
check-app-update | handle | Renderer → Main | Checks GitHub for new application releases. | main.js:112 |
check-xray-update | handle | Renderer → Main | Checks GitHub for new Xray-core releases. | main.js:113 |
download-xray-update | handle | Renderer → Main | Downloads and installs a new Xray-core binary. | main.js:114 |
get-running-ids | handle | Renderer → Main | Returns an array of profile IDs that are currently running. | main.js:167 |
get-profiles | handle | Renderer → Main | Reads and returns all profiles from profiles.json. | main.js:168 |
update-profile | handle | Renderer → Main | Updates an existing profile in profiles.json. | main.js:169 |
save-profile | handle | Renderer → Main | Saves a new profile to profiles.json. | main.js:170 |
delete-profile | handle | Renderer → Main | Deletes a profile and moves its data directory to a trash bin. | main.js:171 |
get-settings | handle | Renderer → Main | Reads and returns application settings from settings.json. | main.js:172 |
save-settings | handle | Renderer → Main | Saves application settings to settings.json. | main.js:173 |
select-extension-folder | handle | Renderer → Main | Opens a native dialog to select a directory. | main.js:174 |
add-user-extension | handle | Renderer → Main | Adds a user-selected extension path to settings. | main.js:181 |
remove-user-extension | handle | Renderer → Main | Removes a user extension path from settings. | main.js:189 |
get-user-extensions | handle | Renderer → Main | Retrieves the list of user-added extension paths. | main.js:197 |
open-url | handle | Renderer → Main | Opens a URL in the system's default external browser. | main.js:202 |
export-data | handle | Renderer → Main | Exports profile and/or proxy data to a YAML file. | main.js:203 |
import-data | handle | Renderer → Main | Imports profile and/or proxy data from a YAML file. | main.js:204 |
launch-profile | handle | Renderer → Main | The core function to launch a browser profile with Xray and Puppeteer. | main.js:207 |
profile-status | on | Main → Renderer | Notifies the renderer that a profile's status has changed (e.g., 'running', 'stopped'). | main.js:401, main.js:419 |
Communication Flows
Request-Response Model (invoke/handle)
This is the most common communication pattern in the application. The renderer process initiates a request, and the main process performs an action and returns a result. The entire operation is asynchronous, using Promises.
The sequence diagram below shows the flow for launching a profile, a complex operation involving file I/O, process spawning, and returning a status message.
This diagram illustrates the asynchronous nature of invoke. The renderer immediately receives a Promise and can continue its work. The main process performs several long-running tasks before resolving the promise with a result.
Sources: renderer.js:607, preload.js:9, main.js:207
Unidirectional Messaging (send/on)
This pattern is used when the main process needs to push information to the renderer without a prior request. The primary example is the profile-status channel, which updates the UI in real-time when a browser profile starts or stops.
The main process uses sender.send() (where sender is the WebContents object of the renderer) to dispatch the message. The renderer sets up a listener using window.electronAPI.onProfileStatus() to receive these messages.
This diagram shows how the renderer subscribes to an event. The main process can then push updates to the renderer at any time, enabling a reactive user interface.
Sources: main.js:401, main.js:419, preload.js:16, renderer.js:437