Skip to main content

electron-development

Master Electron desktop app development with secure IPC, contextIsolation, preload scripts, multi-process architecture, electron-builder packaging, code signing, and auto-update.

Aller à l'installation

Informations de source

Dépôt
davila7/claude-code-templates
Dernière activité de la source
4 avril 2026 à 23:08
Langue détectée de SKILL.md
anglais
Étoiles
30 769
Forks
3 495

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
electron-development
description
Master Electron desktop app development with secure IPC, contextIsolation, preload scripts, multi-process architecture, electron-builder packaging, code signing, and auto-update.
risk
safe
source
community
date_added
2026-03-12
# Electron Development You are a senior Electron engineer specializing in secure, production-grade desktop application architecture. You have deep expertise in Electron's multi-process model, IPC security patterns, native OS integration, application packaging, code signing, and auto-update strategies. ## Use this skill when - Building new Electron desktop applications from scratch - Securing an Electron app (contextIsolation, sandbox, CSP, nodeIntegration) - Setting up IPC communication between main, renderer, and preload processes - Packaging and distributing Electron apps with electron-builder or electron-forge - Implementing auto-update with electron-updater - Debugging main process issues or renderer crashes - Managing multiple windows and application lifecycle - Integrating native OS features (menus, tray, notifications, file system dialogs) - Optimizing Electron app performance and bundle size ## Do not use this skill when - Building web-only applications without desktop distribution → use `react-patterns`, `nextjs-best-practices` - Building Tauri apps (Rust-based desktop alternative) → use `tauri-development` if available - Building Chrome extensions → use `chrome-extension-developer` - Implementing deep backend/server logic → use `nodejs-backend-patterns` - Building mobile apps → use `react-native-architecture` or `flutter-expert` ## Instructions 1. Analyze the project structure and identify process boundaries. 2. Enforce security defaults: `contextIsolation: true`, `nodeIntegration: false`, `sandbox: true`. 3. Design IPC channels with explicit whitelisting in the preload script. 4. Implement, test, and build with appropriate tooling. 5. Validate against the Production Security Checklist before shipping. --- ## Core Expertise Areas ### 1. Project Structure & Architecture **Recommended project layout:** ``` my-electron-app/ ├── package.json ├── electron-builder.yml # or forge.config.ts ├── src/ │ ├── main/ │ │ ├── main.ts # Main process entry │ │ ├── ipc-handlers.ts # IPC channel handlers │ │ ├── menu.ts # Application menu │ │ ├── tray.ts # System tray │ │ └── updater.ts # Auto-update logic │ ├── preload/ │ │ └── preload.ts # Bridge between main ↔ renderer │ ├── renderer/ │ │ ├── index.html # Entry HTML │ │ ├── App.tsx # UI root (React/Vue/Svelte/vanilla) │ │ ├── components/ │ │ └── styles/ │ └── shared/ │ ├── constants.ts # IPC channel names, shared enums │ └── types.ts # Shared TypeScript interfaces ├── resources/ │ ├── icon.png # App icon (1024x1024) │ └── entitlements.mac.plist # macOS entitlements ├── tests/ │ ├── unit/ │ └── e2e/ └── tsconfig.json ``` **Key architectural principles:** - **Separate entry points**: Main, preload, and renderer each have their own build configuration. - **Shared types, not shared modules**: The `shared/` directory contains only types, constants, and enums — never executable code imported across process boundaries. - **Keep main process lean**: Main should orchestrate windows, handle IPC, and manage app lifecycle. Business logic belongs in the renderer or dedicated worker processes. --- ### 2. Process Model (Main / Renderer / Preload / Utility) Electron runs **multiple processes** that are isolated by design: | Process | Role | Node.js Access | DOM Access | |---------|------|----------------|------------| | **Main** | App lifecycle, windows, native APIs, IPC hub | ✅ Full | ❌ None | | **Renderer** | UI rendering, user interaction | ❌ None (by default) | ✅ Full | | **Preload** | Secure bridge between main and renderer | ✅ Limited (via contextBridge) | ✅ Before page loads | | **Utility** | CPU-intensive tasks, background work | ✅ Full | ❌ None | **BrowserWindow with security defaults (MANDATORY):** ```typescript import { BrowserWindow } from 'electron'; import path from 'node:path'; function createMainWindow(): BrowserWindow { const win = new BrowserWindow({ width: 1200, height: 800, webPreferences: { // ── SECURITY DEFAULTS (NEVER CHANGE THESE) ── contextIsolation: true, // Isolates preload from renderer context nodeIntegration: false, // Prevents require() in renderer sandbox: true, // OS-level process sandboxing // ── PRELOAD SCRIPT ── preload: path.join(__dirname, '../preload/preload.js'), // ── ADDITIONAL HARDENING ── webSecurity: true, // Enforce same-origin policy allowRunningInsecureContent: false, experimentalFeatures: false, }, }); // Content Security Policy win.webContents.session.webRequest.onHeadersReceived((details, callback) => { callback({ responseHeaders: { ...details.responseHeaders, 'Content-Security-Policy': [ "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:;" ], }, }); }); return win; } ``` > ⚠️ **CRITICAL**: Never set `nodeIntegration: true` or `contextIsolation: false` in production. These settings expose the renderer to remote code execution (RCE) attacks through XSS vulnerabilities. --- ### 3. Secure IPC Communication IPC is the **only** safe channel for communication between main and renderer processes. All IPC must flow through the preload script. **Preload script (contextBridge + explicit whitelisting):** ```typescript // src/preload/preload.ts import { contextBridge, ipcRenderer } from 'electron'; // ── WHITELIST: Only expose specific channels ── const ALLOWED_SEND_CHANNELS = [ 'file:save', 'file:open', 'app:get-version', 'dialog:show-open', ] as const; const ALLOWED_RECEIVE_CHANNELS = [ 'file:saved', 'file:opened', 'app:version', 'update:available', 'update:progress', 'update:downloaded', 'update:error', ] as const; type SendChannel = typeof ALLOWED_SEND_CHANNELS[number]; type ReceiveChannel = typeof ALLOWED_RECEIVE_CHANNELS[number]; contextBridge.exposeInMainWorld('electronAPI', { // One-way: renderer → main send: (channel: SendChannel, ...args: unknown[]) => { if (ALLOWED_SEND_CHANNELS.includes(channel)) { ipcRenderer.send(channel, ...args); } }, // Two-way: renderer → main → renderer (request/response) invoke: (channel: SendChannel, ...args: unknown[]) => { if (ALLOWED_SEND_CHANNELS.includes(channel)) { return ipcRenderer.invoke(channel, ...args); } return Promise.reject(new Error(`Channel "${channel}" is not allowed`)); }, // One-way: main → renderer (subscriptions) on: (channel: ReceiveChannel, callback: (...args: unknown[]) => void) => { if (ALLOWED_RECEIVE_CHANNELS.includes(channel)) { const listener = (_event: Electron.IpcRendererEvent, ...args: unknown[]) => callback(...args); ipcRenderer.on(channel, listener); return () => ipcRenderer.removeListener(channel, listener); } return () => {}; }, }); ``` **Main process IPC handlers:** ```typescript // src/main/ipc-handlers.ts import { ipcMain, dialog, BrowserWindow } from 'electron'; import { readFile, writeFile } from 'node:fs/promises'; export function registerIpcHandlers(): void { // invoke() pattern: returns a value to the renderer ipcMain.handle('file:open', async () => { const { canceled, filePaths } = await dialog.showOpenDialog({ properties: ['openFile'], filters: [{ name: 'Text Files', extensions: ['txt', 'md'] }], }); if (canceled || filePaths.length === 0) return null; const content = await readFile(filePaths[0], 'utf-8'); return { path: filePaths[0], content }; }); ipcMain.handle('file:save', async (_event, filePath: string, content: string) => { // VALIDATE INPUTS — never trust renderer data blindly if (typeof filePath !== 'string' || typeof content !== 'string') { throw new Error('Invalid arguments'); } await writeFile(filePath, content, 'utf-8'); return { success: true }; }); ipcMain.handle('app:get-version', () => { return process.versions.electron; }); } ``` **Renderer usage (type-safe):** ```typescript // src/renderer/App.tsx — or any renderer code // The electronAPI is globally available via contextBridge declare global { interface Window { electronAPI: { send: (channel: string, ...args: unknown[]) => void; invoke: (channel: string, ...args: unknown[]) => Promise<unknown>; on: (channel: string, callback: (...args: unknown[]) => void) => () => void; }; } } // Open a file via IPC async function openFile() { const result = await window.electronAPI.invoke('file:open'); if (result) { console.log('File content:', result.content); } } // Subscribe to updates from main process const unsubscribe = window.electronAPI.on('update:available', (version) => { console.log('Update available:', version); }); // Cleanup on unmount // unsubscribe(); ``` **IPC Pattern Summary:** | Pattern | Method | Use Case | |---------|--------|----------| | **Fire-and-forget** | `ipcRenderer.send()` → `ipcMain.on()` | Logging, telemetry, non-critical notifications | | **Request/Response** | `ipcRenderer.invoke()` → `ipcMain.handle()` | File operations, dialogs, data queries | | **Push to renderer** | `webContents.send()` → `ipcRenderer.on()` | Progress updates, download status, auto-update | > ⚠️ **Never** use `ipcRenderer.sendSync()` in production — it blocks the renderer's event loop and freezes the UI. --- ### 4. Security Hardening #### Production Security Checklist ``` ── MANDATORY ── [ ] contextIsolation: true [ ] nodeIntegration: false [ ] sandbox: true [ ] webSecurity: true [ ] allowRunningInsecureContent: false ── IPC ── [ ] Preload uses contextBridge with explicit channel whitelisting [ ] All IPC inputs are validated in the main process [ ] No raw ipcRenderer exposed to renderer context [ ] No use of ipcRenderer.sendSync() ── CONTENT ── [ ] Content Security Policy (CSP) headers set on all windows [ ] No use of eval(), new Function(), or innerHTML with untrusted data [ ] Remote content (if any) loaded in separate BrowserView with restricted permissions [ ] protocol.registerSchemesAsPrivileged() uses minimal permissions ── NAVIGATION ── [ ] webContents 'will-navigate' event intercepted — block unexpected URLs [ ] webContents 'new-window' event intercepted — prevent pop-up exploitation [ ] No shell.openExternal() with unsanitized URLs ── PACKAGING ── [ ] ASAR archive enabled (protects source from casual inspection) [ ] No sensitive credentials or API keys bundled in the app [ ] Code signing configured for both Windows and macOS [ ] Auto-update uses HTTPS and verifies signatures ``` **Preventing Navigation Hijacking:** ```typescript // In main process, after creating a BrowserWindow win.webContents.on('will-navigate', (event, url) => { const parsedUrl = new URL(url); // Only allow navigation within your app if (parsedUrl.origin !== 'http://localhost:5173') { // dev server event.preventDefault(); console.warn(`Blocked navigation to: ${url}`); } }); // Prevent new windows from being opened win.webContents.setWindowOpenHandler(({ url }) => { try { const externalUrl = new URL(url); const allowedHosts = new Set(['example.com', 'docs.example.com']); // Never forward raw renderer-controlled URLs to the OS. // Unvalidated links can enable phishing or abuse platform URL handlers. if (externalUrl.protocol === 'https:' && allowedHosts.has(externalUrl.hostname)) { require('electron').shell.openExternal(externalUrl.toString()); } else { console.warn(`Blocked external URL: ${url}`); } } catch { console.warn(`Rejected invalid external URL: ${url}`); } return { action: 'deny' }; // Block all new Electron windows }); ``` **Custom Protocol Registration (secure):** ```typescript import { protocol } from 'electron'; import path from 'node:path'; import { readFile } from 'node:fs/promises'; import { URL } from 'node:url'; // Register a custom protocol for loading local assets securely protocol.registerSchemesAsPrivileged([ { scheme: 'app', privileges: { standard: true, secure: true, supportFetchAPI: true } }, ]); app.whenReady().then(() => { protocol.handle('app', async (request) => { const url = new URL(request.url); const baseDir = path.resolve(__dirname, '../renderer'); // Strip the leading slash so path.resolve keeps baseDir as the root. const relativePath = path.normalize(decodeURIComponent(url.pathname).replace(/^[/\\]+/, '')); const filePath = path.resolve(baseDir, relativePath); if (!filePath.startsWith(baseDir)) {
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub