Claude Code से Electron Desktop App Development: IPC, preload, packaging और auto update
Claude Code से Electron app बनाएं: contextIsolation, preload, IPC, file access, packaging और auto update guide.
Electron की मदद से एक web UI को Windows, macOS और Linux desktop app में बदला जा सकता है। लेकिन Electron app सामान्य website से अधिक शक्तिशाली होता है। यह local files खोल सकता है, native dialogs दिखा सकता है, menu और automatic update संभाल सकता है। इसलिए security boundary को UI design जितना ही गंभीर मानना चाहिए।
Claude Code तब सबसे उपयोगी है जब आप उसे छोटे implementation tasks देते हैं: एक IPC channel, एक preload API, file access की एक validation rule, या packaging config का एक हिस्सा। यदि आप एक ही prompt में पूरा desktop app, installer और auto update मांगते हैं, तो diff बहुत बड़ा हो जाता है और review मुश्किल हो जाता है।
Electron में practical baseline यह है: contextIsolation enabled रखें, renderer में nodeIntegration off रखें, preload से सिर्फ minimum APIs expose करें, और main process में input दोबारा validate करें। Official references के लिए Context Isolation, IPC tutorial, Distribution Overview और autoUpdater API देखें।
अगर आप Electron और Tauri की तुलना कर रहे हैं, तो Tauri development guide पढ़ें। preload और renderer की TypeScript boundary के लिए TypeScript tips उपयोगी है।
तीन layers को अलग रखें
| Layer | जिम्मेदारी | Claude Code को देने लायक task | Human review |
|---|---|---|---|
| main process | BrowserWindow, OS API, files, menus, updates | एक बार में एक IPC handler | validation, permission, errors, path rules |
| preload | renderer के लिए minimum bridge | contextBridge से typed functions expose करना | raw ipcRenderer expose न हो |
| renderer | UI, forms, state, display | screen और interaction बनाना | Node/Electron direct import न हो |
Rule यह है कि renderer file system को direct न छुए। Renderer केवल preload की limited function call करता है। Preload एक named IPC message भेजता है। Main process validate करके OS operation करता है।
flowchart LR
User["User action"] --> Renderer["Renderer\nHTML / React"]
Renderer --> Preload["Preload\ncontextBridge"]
Preload --> IPC["IPC channel\nvalidated payload"]
IPC --> Main["Main process\nOS and file access"]
Main --> Result["Typed result"]
Result --> Renderer
Main --> Update["Packaging / auto update"]
Copy-paste starter
यह starter जानबूझकर plain Electron + TypeScript है। पहले boundary समझें, फिर React, Vite या कोई UI framework जोड़ें।
mkdir electron-secure-notes
cd electron-secure-notes
npm init -y
npm install --save-dev electron typescript @types/node
npm pkg set main="dist/main.js"
npm pkg set scripts.dev="tsc -p tsconfig.json && electron ."
mkdir src
{
"compilerOptions": {
"target": "ES2022",
"module": "CommonJS",
"moduleResolution": "Node",
"strict": true,
"esModuleInterop": true,
"outDir": "dist",
"rootDir": "src",
"types": ["node", "electron"]
},
"include": ["src/**/*.ts"]
}
Main process में file access और validation रखें।
// src/main.ts
import { app, BrowserWindow, dialog, ipcMain, session } from "electron";
import fs from "node:fs/promises";
import path from "node:path";
type OpenedTextFile = {
filePath: string;
content: string;
};
type SaveTextRequest = {
filePath: string;
content: string;
};
const allowedExtensions = new Set([".txt", ".md", ".json"]);
const allowedRoots = new Set<string>();
function createWindow() {
const win = new BrowserWindow({
width: 1100,
height: 760,
webPreferences: {
preload: path.join(__dirname, "preload.js"),
contextIsolation: true,
nodeIntegration: false,
sandbox: true
}
});
win.loadFile(path.join(__dirname, "index.html"));
}
function registerCsp() {
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
...details.responseHeaders,
"Content-Security-Policy": [
"default-src 'self'; script-src 'self'; style-src 'self'"
]
}
});
});
}
function isInsideRoot(root: string, target: string) {
const relative = path.relative(root, target);
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
}
async function assertOpenedPath(filePath: string) {
const extension = path.extname(filePath).toLowerCase();
if (!allowedExtensions.has(extension)) {
throw new Error("Unsupported file type.");
}
return filePath;
}
async function assertAllowedFile(filePath: string) {
const resolved = path.resolve(filePath);
const extension = path.extname(resolved).toLowerCase();
if (!allowedExtensions.has(extension)) {
throw new Error("Only txt, md, and json files are allowed.");
}
const insideAllowedRoot = Array.from(allowedRoots).some((root) =>
isInsideRoot(root, resolved)
);
if (!insideAllowedRoot) {
throw new Error("Open the file with the dialog before saving it.");
}
return resolved;
}
ipcMain.handle("dialog:openTextFile", async (): Promise<OpenedTextFile | null> => {
const result = await dialog.showOpenDialog({
title: "Open text file",
properties: ["openFile"],
filters: [{ name: "Text", extensions: ["txt", "md", "json"] }]
});
if (result.canceled || result.filePaths.length === 0) {
return null;
}
const filePath = path.resolve(result.filePaths[0]);
const safePath = await assertOpenedPath(filePath);
allowedRoots.add(path.dirname(safePath));
return {
filePath: safePath,
content: await fs.readFile(safePath, "utf8")
};
});
ipcMain.handle("file:saveText", async (_event, payload: SaveTextRequest) => {
if (
!payload ||
typeof payload.filePath !== "string" ||
typeof payload.content !== "string" ||
payload.content.length > 1_000_000
) {
throw new Error("Invalid save request.");
}
const filePath = await assertAllowedFile(payload.filePath);
await fs.writeFile(filePath, payload.content, "utf8");
return { ok: true };
});
app.whenReady().then(() => {
registerCsp();
createWindow();
});
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
});
Preload में raw IPC expose न करें। Business capability expose करें।
// src/preload.ts
import { contextBridge, ipcRenderer } from "electron";
type OpenedTextFile = {
filePath: string;
content: string;
};
type SaveTextRequest = {
filePath: string;
content: string;
};
contextBridge.exposeInMainWorld("desktop", {
openTextFile: () =>
ipcRenderer.invoke("dialog:openTextFile") as Promise<OpenedTextFile | null>,
saveTextFile: (payload: SaveTextRequest) => {
if (
!payload ||
typeof payload.filePath !== "string" ||
typeof payload.content !== "string"
) {
throw new Error("Invalid save payload.");
}
return ipcRenderer.invoke("file:saveText", payload) as Promise<{ ok: boolean }>;
}
});
Renderer केवल exposed API use करता है।
// src/renderer.ts
const openButton = document.querySelector<HTMLButtonElement>("#open");
const saveButton = document.querySelector<HTMLButtonElement>("#save");
const editor = document.querySelector<HTMLTextAreaElement>("#editor");
const status = document.querySelector<HTMLParagraphElement>("#status");
let currentFilePath: string | null = null;
if (!openButton || !saveButton || !editor || !status) {
throw new Error("Required UI elements are missing.");
}
openButton.addEventListener("click", async () => {
const file = await window.desktop.openTextFile();
if (!file) {
status.textContent = "Open canceled.";
return;
}
currentFilePath = file.filePath;
editor.value = file.content;
status.textContent = `Opened: ${file.filePath}`;
});
saveButton.addEventListener("click", async () => {
if (!currentFilePath) {
status.textContent = "Open a file first.";
return;
}
await window.desktop.saveTextFile({
filePath: currentFilePath,
content: editor.value
});
status.textContent = `Saved: ${currentFilePath}`;
});
Claude Code के लिए prompt
Existing Electron + TypeScript app में Markdown open/save feature जोड़ें।
Constraints:
- renderer को Node.js APIs expose न करें
- contextIsolation: true, nodeIntegration: false, sandbox: true बनाए रखें
- preload सिर्फ window.desktop.openTextFile और window.desktop.saveTextFile expose करे
- raw ipcRenderer या generic invoke(channel, payload) न बनाएं
- केवल md/txt/json files allow करें
- existing auto update code न बदलें
Done criteria:
- npm run typecheck pass हो
- main/preload/renderer changes briefly explain करें
- 3 security review points list करें
तीन practical use cases
पहला use case internal Markdown notes app है। Team documents server पर upload किए बिना local files edit कर सकती है। Recent files list save करें, लेकिन document content cache न करें। App start पर path और extension फिर से validate करें।
दूसरा use case support log viewer है। Logs बड़े हो सकते हैं और उनमें email, bearer token या customer ID हो सकता है। Main process में paging करें, renderer को सिर्फ visible slice भेजें, और copy/export से पहले secrets mask करें।
तीसरा use case desktop AI prompt workbench है। इसमें local templates, clipboard और external APIs मिलते हैं। API key सिर्फ main process में read होनी चाहिए। Renderer को connection status और result मिले, raw token या headers नहीं।
Common failures
सबसे बड़ा failure है renderer से आए किसी भी path को main process में पढ़ लेना। UI safe दिखे, फिर भी renderer को trusted न मानें। Extension, file size और allowed root main process में validate करें।
दूसरा failure है window.api.invoke(channel, payload) जैसा generic bridge बनाना। यह छोटा दिखता है, लेकिन permission review को bypass कर देता है। हर capability के लिए अलग method रखें।
तीसरा failure है dev path का package के बाद टूटना। Runtime में src path पर निर्भर न रहें और asar के अंदर write न करें। Writable data के लिए app.getPath("userData") use करें।
चौथा failure है signing और auto update को release week तक टालना। Feed, certificate, GitHub Release permission और Windows installer behavior को CI में जल्दी test करें। Pipeline design के लिए CI/CD setup guide देखें।
Packaging और auto update
Electron Forge से शुरू करने के लिए:
npm install --save-dev @electron-forge/cli @electron-forge/maker-squirrel @electron-forge/maker-zip @electron-forge/maker-deb @electron-forge/maker-rpm
npx electron-forge import
// forge.config.ts
import type { ForgeConfig } from "@electron-forge/shared-types";
const config: ForgeConfig = {
packagerConfig: {
asar: true,
icon: "assets/icon"
},
rebuildConfig: {},
makers: [
{
name: "@electron-forge/maker-squirrel",
config: {
name: "secure_notes"
}
},
{
name: "@electron-forge/maker-zip",
platforms: ["darwin"]
},
{
name: "@electron-forge/maker-deb",
config: {}
},
{
name: "@electron-forge/maker-rpm",
config: {}
}
]
};
export default config;
Built-in autoUpdater मुख्य रूप से macOS और Windows के लिए है। Linux में अक्सर distribution package manager बेहतर होता है। macOS auto update के लिए signing चाहिए। Windows में behavior MSIX या Squirrel.Windows पर निर्भर करता है।
// src/update.ts
import { app, autoUpdater, BrowserWindow } from "electron";
function sendStatus(win: BrowserWindow, status: string) {
win.webContents.send("update:status", status);
}
export function configureAutoUpdate(win: BrowserWindow) {
if (!app.isPackaged) {
sendStatus(win, "updates disabled in development");
return;
}
if (process.platform === "linux") {
sendStatus(win, "use the distribution package manager on Linux");
return;
}
const owner = "YOUR_GITHUB_OWNER";
const repo = "YOUR_GITHUB_REPO";
const feedUrl = `https://update.electronjs.org/${owner}/${repo}/${process.platform}-${process.arch}/${app.getVersion()}`;
autoUpdater.setFeedURL({ url: feedUrl });
autoUpdater.on("checking-for-update", () => sendStatus(win, "checking"));
autoUpdater.on("update-available", () => sendStatus(win, "available"));
autoUpdater.on("update-not-available", () => sendStatus(win, "not available"));
autoUpdater.on("update-downloaded", () => sendStatus(win, "downloaded"));
autoUpdater.on("error", (error) => sendStatus(win, `error: ${error.message}`));
setTimeout(() => {
try {
autoUpdater.checkForUpdates();
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
sendStatus(win, `error: ${message}`);
}
}, 10_000);
}
Claude Code से यह भी लिखवाएं कि production के लिए क्या बाकी है: signing identity, feed owner, release permission, CI secrets और rollback strategy।
निष्कर्ष और check points
Claude Code Electron development को तेज करता है, लेकिन quality boundaries से आती है। Renderer UI के लिए, preload narrow bridge के लिए, main process OS access के लिए, और packaging release से पहले test करने के लिए होना चाहिए।
ClaudeCodeLab Electron/Tauri architecture, IPC security review, auto update flow और Claude Code training में मदद कर सकता है। Practical consultation के लिए current repo structure, target platforms और सबसे risky permissions share करें।
इस लेख को project में आजमाते समय check करें कि npm run dev pass हो, renderer Electron या Node import न करे, arbitrary path read न हो सके, packaged app में paths काम करें, और signing/feed ready होने से पहले auto update को production feature न माना जाए।
मुफ़्त PDF: Claude Code cheatsheet
Email डालें और commands, review habits तथा safe workflow वाली एक-page PDF पाएँ.
हम आपका data सुरक्षित रखते हैं और spam नहीं भेजते.
लेखक के बारे में
Masa
Claude Code workflow और team adoption पर काम करने वाला engineer.
संबंधित लेख
Claude Code Obsidian to CLAUDE.md workflow: context बार-बार न समझाएं
Obsidian notes को CLAUDE.md operating notes में बदलकर Claude Code sessions को resume करना आसान बनाएं.
Claude Code Revenue CTA Routing: article से PDF, Gumroad और consultation तक
Reader intent के आधार पर free PDF, Gumroad products और consultation तक CTA route करने वाला workflow.
Claude Code टीम हैंडऑफ नियम: review proof, permissions, rollback और revenue path
Claude Code टीम काम के लिए evidence, permission rules, rollback, free PDF, Gumroad और consultation path वाला handoff.