contextBridge
comes in. The contextBridge
in Electron provides a secure way to expose specific APIs from the main process to the renderer process. When combined with TypeScript, it allows developers to write type - safe code, catch errors early, and improve the overall maintainability of the application. In this blog post, we will explore the fundamental concepts, usage methods, common practices, and best practices of using contextBridge
with TypeScript in Electron applications.contextBridge
In Electron, there are two main types of processes: the main process and the renderer process. The main process is responsible for managing the application’s lifecycle, creating browser windows, and interacting with the operating system. The renderer process runs the web pages in each browser window and is similar to a traditional web browser tab.
Context isolation is a security feature in Electron that ensures that the renderer process has its own isolated JavaScript context. This means that the renderer process cannot directly access the Node.js APIs or the main process’s global variables. It helps to prevent malicious code in the renderer process from accessing sensitive information or performing unauthorized actions.
contextBridge
The contextBridge
is a module in Electron that allows you to safely expose specific APIs from the main process to the renderer process. It creates a bridge between the two contexts, enabling communication while maintaining the security provided by context isolation.
First, create a new directory for your project and initialize it with npm
:
mkdir electron - contextbridge - typescript
cd electron - contextbridge - typescript
npm init -y
Install Electron and TypeScript:
npm install electron typescript --save - dev
Create a tsconfig.json
file with the following configuration:
{
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
Create a src
directory and add the following files:
main.ts
for the main process codepreload.ts
for the preload scriptrenderer.ts
for the renderer process codeIn the preload.ts
file, we will use the contextBridge
to expose an API from the main process to the renderer process.
// src/preload.ts
import { contextBridge, ipcRenderer } from 'electron';
// Define the API to be exposed
const api = {
sendMessage: (message: string) => ipcRenderer.send('message', message),
receiveMessage: (callback: (event: Electron.IpcRendererEvent, ...args: any[]) => void) => {
ipcRenderer.on('message - response', callback);
}
};
// Expose the API to the renderer process
contextBridge.exposeInMainWorld('electronAPI', api);
In the main.ts
file, we need to set up the main process to handle the messages sent from the renderer process.
// src/main.ts
import { app, BrowserWindow, ipcMain } from 'electron';
import * as path from 'path';
function createWindow() {
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true
}
});
mainWindow.loadFile('index.html');
}
app.whenReady().then(() => {
createWindow();
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
app.on('window - all - closed', function () {
if (process.platform!== 'darwin') app.quit();
});
// Handle the message sent from the renderer process
ipcMain.on('message', (event, message) => {
console.log(`Received message: ${message}`);
event.sender.send('message - response', 'Message received');
});
In the renderer.ts
file, we can use the exposed API to send and receive messages.
// src/renderer.ts
// Get the exposed API
const electronAPI = (window as any).electronAPI;
// Send a message
electronAPI.sendMessage('Hello from the renderer process');
// Receive a message
electronAPI.receiveMessage((event, response) => {
console.log(`Received response: ${response}`);
});
When using the contextBridge
, it’s important to handle errors properly. For example, in the preload.ts
file, if there is an error in the ipcRenderer
calls, we can add error handling.
// src/preload.ts
import { contextBridge, ipcRenderer } from 'electron';
const api = {
sendMessage: (message: string) => {
try {
ipcRenderer.send('message', message);
} catch (error) {
console.error('Error sending message:', error);
}
},
receiveMessage: (callback: (event: Electron.IpcRendererEvent, ...args: any[]) => void) => {
try {
ipcRenderer.on('message - response', callback);
} catch (error) {
console.error('Error receiving message:', error);
}
}
};
contextBridge.exposeInMainWorld('electronAPI', api);
To make the code more type - safe, we can define types for the exposed API.
// src/types.d.ts
declare interface ElectronAPI {
sendMessage: (message: string) => void;
receiveMessage: (callback: (event: Electron.IpcRendererEvent, ...args: any[]) => void) => void;
}
declare global {
interface Window {
electronAPI: ElectronAPI;
}
}
Only expose the necessary APIs from the main process to the renderer process. This reduces the attack surface and minimizes the risk of security vulnerabilities. For example, if the renderer process only needs to read a configuration file, only expose the API for reading the file and not other potentially dangerous APIs.
As your application evolves, the exposed APIs may change. It’s a good practice to version your APIs to ensure compatibility between different versions of your application. You can do this by adding a version number to the exposed API object.
// src/preload.ts
import { contextBridge, ipcRenderer } from 'electron';
const api = {
version: '1.0',
sendMessage: (message: string) => ipcRenderer.send('message', message),
receiveMessage: (callback: (event: Electron.IpcRendererEvent, ...args: any[]) => void) => {
ipcRenderer.on('message - response', callback);
}
};
contextBridge.exposeInMainWorld('electronAPI', api);
Using contextBridge
with TypeScript in Electron applications provides a secure and type - safe way to communicate between the main process and the renderer process. By understanding the fundamental concepts, following the usage methods, adopting common practices, and implementing best practices, you can build robust and secure Electron applications. The combination of contextBridge
and TypeScript helps to catch errors early, improve code maintainability, and enhance the overall security of your application.