Inter-Process Communication (IPC) API
Relevant source files
The following files were used as context for generating this wiki page:The Inter-Process Communication (IPC) API is a critical component of the PIXONA application, forming the bridge between the user interface (Renderer process) and the underlying operating system functionalities (Main process). Due to Electron's security model, the Renderer process, which runs the React-based UI, is sandboxed and cannot directly access Node.js APIs or the file system. The IPC API provides a secure and controlled mechanism to expose necessary backend functions to the frontend.
This system is responsible for all interactions that require elevated privileges, such as scanning photo directories, reading file metadata, managing system-level settings like auto-start, and interacting with native dialogs. It is designed around a request-response model for most actions, with an additional event-based channel for streaming progress updates, such as during a long-running directory scan.
Architecture
The IPC architecture in PIXONA consists of three key parts: the Main Process, the Preload Script, and the Renderer Process. Communication flows from the Renderer, through the Preload script, to the Main process, which then performs the requested action and returns a result.
- Main Process (
electron/main.ts): The backend of the Electron application. It has full access to Node.js APIs and manages the application's lifecycle, windows, and native OS interactions. It listens for IPC messages on specific channels usingipcMain. - Preload Script (
electron/preload.ts): A privileged script that runs in the Renderer process's web context but has access to Node.js APIs. It acts as a secure bridge, exposing a curated API to the Renderer viacontextBridge.exposeInMainWorld. It is the only place where the Renderer and Main processes should interact. - Renderer Process (
src/*): The sandboxed process that runs the web-based user interface (React). It can only communicate with the Main process through the API exposed by the Preload script (i.e.,window.electron).
The following diagram illustrates the communication flow for a typical IPC call.
API Implementation
Main Process Handlers
The Main process uses ipcMain.handle to define asynchronous, request-response style endpoints. Each handler corresponds to a specific channel name and is responsible for executing a privileged task. All file system operations and native OS interactions are implemented here.
For example, the handler for opening a folder selection dialog is defined as:
// file: electron/main.ts:121-135
ipcMain.handle('select-folder', async () => {
try {
const result = await dialog.showOpenDialog(mainWindow!, {
properties: ['openDirectory', 'multiSelections'],
title: 'Select Photo Folders',
});
if (result.canceled) {
return { success: false, canceled: true };
}
return { success: true, folders: result.filePaths };
} catch (error: any) {
return { success: false, error: error.message };
}
});
Sources: electron/main.ts:121-135
The Preload Bridge
The preload.ts script securely exposes the IPC channels to the Renderer process. It creates a window.electron object containing functions that map directly to ipcRenderer.invoke calls. This prevents the full ipcRenderer object from being exposed, enhancing security.
// file: electron/preload.ts:5-8
contextBridge.exposeInMainWorld('electron', {
// Get available drives
getDrives: () => ipcRenderer.invoke('get-drives'),
// Select folder dialog
selectFolder: () => ipcRenderer.invoke('select-folder'),
// ... other functions
});
Sources: electron/preload.ts:5-8
Renderer Process API Consumption
The frontend code, running in the Renderer process, interacts with the exposed API via the window.electron object. TypeScript definitions are provided in src/types/electron.d.ts to ensure type safety.
The ElectronAPI interface defines the contract for the frontend:
// file: src/types/electron.d.ts:1-5
export interface ElectronAPI {
getDrives: () => Promise<{ success: boolean; drives?: string[]; error?: string }>;
selectFolder: () => Promise<{ success:boolean; folders?: string[]; canceled?: boolean; error?: string }>;
// ... other methods
}
Sources: src/types/electron.d.ts:1-5
A React component can then call these functions, for example, to open a folder browser:
// file: src/pages/SettingsPage.tsx:43-56
const handleBrowseFolder = async () => {
try {
if (window.electron) {
const result = await window.electron.selectFolder();
if (result.success && result.folders && result.folders.length > 0) {
setLibraryPath(result.folders[0]);
toast.success("Library path updated");
} else if (result.error) {
toast.error(result.error);
}
} else {
toast.error("File picker not available in web mode");
}
} catch (error) {
toast.error("Failed to select folder");
console.error(error);
}
};
Sources: src/pages/SettingsPage.tsx:43-56
Asynchronous Event Streaming
For long-running tasks like scanning a directory, a one-way communication channel is used to send progress updates from the Main process to the Renderer. This is handled by mainWindow.webContents.send in the Main process and ipcRenderer.on in the Preload script.
The following sequence diagram shows the flow for scan progress updates:
The onScanProgress function in the Preload script returns an unsubscribe function, allowing the Renderer to clean up the listener when it's no longer needed.
API Reference
The following table summarizes all methods exposed on the window.electron object.
| Method | Channel Name | Parameters | Description | Source File |
|---|---|---|---|---|
getDrives() | get-drives | None | Retrieves a list of available system drives. | electron/main.ts:101 |
selectFolder() | select-folder | None | Opens a native dialog to select one or more folders. | electron/main.ts:121 |
scanDirectory(path) | scan-directory | directoryPath: string | Recursively scans a directory for supported media files. | electron/main.ts:138 |
getFileMetadata(path) | get-file-metadata | filePath: string | Fetches file system metadata (size, dates) for a given file. | electron/main.ts:228 |
fileExists(path) | file-exists | filePath: string | Checks if a file or directory exists at the given path. | electron/main.ts:243 |
readFile(path) | read-file | filePath: string | Reads a file and returns its content as a Base64 encoded string. | electron/main.ts:251 |
moveToTrash(path) | move-to-trash | filePath: string | Moves a file to the system's trash or recycling bin. | electron/main.ts:259 |
copyFile(src, dest) | copy-file | sourcePath: string, destPath: string | Copies a file from a source path to a destination path. | electron/main.ts:267 |
moveFile(src, dest) | move-file | sourcePath: string, destPath: string | Moves (renames) a file from a source path to a destination path. | electron/main.ts:275 |
openFile(path) | open-file | filePath: string | Opens a file with the default system application. | electron/main.ts:283 |
showInFolder(path) | show-in-folder | filePath: string | Opens the file manager and highlights the specified file. | electron/main.ts:291 |
getAppVersion() | get-app-version | None | Returns the current application version from package.json. | electron/main.ts:299 |
setAutoStart(enabled) | set-auto-start | enabled: boolean | Configures the application to launch on system startup. | electron/main.ts:304 |
getAutoStart() | get-auto-start | None | Checks if the application is configured to launch on system startup. | electron/main.ts:315 |
onScanProgress(cb) | scan-progress | callback: (progress: any) => void | Subscribes to progress updates from the directory scanner. | electron/preload.ts:50 |
Sources: electron/preload.ts, electron/main.ts, README.md