Ruletypescript
Electron Patterns Rule
These patterns should be followed for all Electron-related code.
Electron Development Patterns
These patterns should be followed for all Electron-related code.
Security First
BrowserWindow Creation
Always use these security settings:
new BrowserWindow({
webPreferences: {
contextIsolation: true,
sandbox: true,
nodeIntegration: false,
preload: path.join(__dirname, 'preload.js')
}
});
Never Do
- Set
nodeIntegration: true - Set
contextIsolation: false - Set
webSecurity: false - Use
remotemodule - Expose unnecessary APIs in preload
IPC Communication
Handler Pattern
ipcMain.handle('channel:action', async (event, arg1, arg2) => {
// 1. Validate inputs
if (!arg1 || typeof arg1 !== 'string') {
throw new Error('Invalid argument');
}
// 2. Perform operation
const result = await service.action(arg1, arg2);
// 3. Return serializable result
return result;
});
Preload Bridge Pattern
contextBridge.exposeInMainWorld('electronAPI', {
serviceName: {
action: (arg: string) => ipcRenderer.invoke('channel:action', arg)
}
});
Type Definitions
Always add types in preload.d.ts:
interface ElectronAPI {
serviceName: {
action: (arg: string) => Promise<ResultType>;
};
}
Data Serialization
What Can Cross IPC
- Primitives (string, number, boolean)
- Plain objects (no class instances)
- Arrays of the above
- null and undefined
What Cannot Cross IPC
- Class instances
- Functions
- Symbols
- Circular references
- DOM elements
Solution: Use DTOs
// Model (main process)
class Profile {
toDTO(): ProfileDTO {
return { id: this._id, name: this._name };
}
}
// DTO (shared)
interface ProfileDTO {
id: string;
name: string;
}
Process Communication
Main to Renderer
// Main process
mainWindow.webContents.send('event:name', data);
// Preload
ipcRenderer.on('event:name', (event, data) => {
callback(data);
});
Renderer to Main (Request/Response)
// Preload
action: () => ipcRenderer.invoke('channel:action')
// Main
ipcMain.handle('channel:action', async () => {
return result;
});
File Paths
Use app.getPath()
const userDataPath = app.getPath('userData');
const logsPath = path.join(userDataPath, 'logs');
const profilesPath = path.join(userDataPath, 'profiles');
Development vs Production
const isDev = !app.isPackaged;
const resourcePath = isDev
? path.join(__dirname, '..', 'resources')
: path.join(process.resourcesPath, 'resources');
Window Management
Single Window Pattern
let mainWindow: BrowserWindow | null = null;
function createWindow(): void {
mainWindow = new BrowserWindow({...});
mainWindow.on('closed', () => {
mainWindow = null;
});
}
app.whenReady().then(createWindow);
app.on('activate', () => {
if (mainWindow === null) {
createWindow();
}
});
Clean Shutdown
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('before-quit', async () => {
// Clean up resources
await ffmpegHandler.stopAll();
logger.close();
});