Pular para o conteúdo principal

Desenvolvimento de Aplicativos Desktop Electron no Cursor: IPC, Context Isolation e Auto-Update

Desenvolvimento de Aplicativos Desktop Electron no Cursor

Electron permite construir aplicativos desktop multiplataforma usando tecnologias web que você já conhece: HTML, CSS e JavaScript. Cursor, um editor IDE alimentado por IA, agiliza o desenvolvimento Electron fazendo scaffold de projetos, gerando handlers IPC, escrevendo scripts de preload com isolamento de contexto e automatizando o pipeline de empacotamento e auto-update. Este guia aborda processos main e renderer, comunicação IPC segura, isolamento de contexto, empacotamento com electron-builder, estratégias de auto-update e fluxos de trabalho de UI desktop assistida por IA.

Pré-requisitos​

Antes de começar, certifique-se de que você tem o seguinte:

  • Cursor (versão mais recente recomendada) como seu editor IDE
  • Node.js 18 ou superior
  • Familiaridade básica com JavaScript, TypeScript e npm
  • Um certificado de assinatura de código (opcional, mas necessário para auto-update no macOS e Windows)
dica

Se você é novo no Electron, peça ao Cursor para explicar o modelo de processo main e renderer antes de começar a programar. Entender a fronteira de processo antecipadamente previne os erros mais comuns de segurança e arquitetura do Electron.

Entendendo o Modelo de Processos do Electron​

Aplicativos Electron consistem em dois tipos de processos:

  • Processo main: Roda em Node.js, tem acesso total ao sistema operacional, gerencia o ciclo de vida da aplicação e cria janelas do navegador.
  • Processo renderer: Roda em um renderer Chromium, renderiza sua UI web e tem acesso restrito a APIs nativas por padrão.

Os dois processos se comunicam através de Inter-Process Communication (IPC). Esta separação é deliberada: mantém APIs nativas perigosas longe de conteúdo web não confiável.

Scaffold de Projeto no Cursor​

Peça ao Cursor para fazer scaffold de um projeto TypeScript Electron:

Create a minimal Electron + TypeScript + Vite project.
Include a main process file, a preload script with contextBridge,
and a renderer entry point. Use electron-builder for packaging.

O Cursor gera a estrutura de diretórios e arquivos de configuração:

my-electron-app/
electron/
main.ts
preload.ts
src/
index.html
renderer.ts
package.json
electron-builder.yml
tsconfig.json

O package.json inclui os scripts e dependências essenciais:

{
"name": "my-electron-app",
"version": "1.0.0",
"main": "dist/electron/main.js",
"scripts": {
"dev": "concurrently \"vite\" \"wait-on tcp:5173 && cross-env NODE_ENV=development electron .\"",
"build": "tsc && vite build",
"package": "npm run build && electron-builder",
"package:mac": "npm run build && electron-builder --mac",
"package:win": "npm run build && electron-builder --win"
},
"devDependencies": {
"electron": "^31.0.0",
"electron-builder": "^24.13.3",
"typescript": "^5.4.0",
"vite": "^5.3.0",
"vite-plugin-electron": "^0.28.0"
}
}
aviso

Sempre especifique o campo main no package.json. Se estiver faltando, o Electron usa index.js na raiz do projeto por padrão, o que pode carregar código inesperado durante o desenvolvimento. Declare explicitamente o ponto de entrada compilado para evitar confusão.

O Processo Main​

O processo main é o ponto de entrada da sua aplicação Electron. Ele cria janelas, gerencia o ciclo de vida do app e registra handlers IPC.

Criando Janelas do Browser​

// electron/main.ts
import { app, BrowserWindow, ipcMain, shell } from 'electron';
import * as path from 'path';
import { isDev } from './utils/env';

let mainWindow: BrowserWindow | null = null;

function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
minWidth: 800,
minHeight: 600,
show: false,
backgroundColor: '#1e1e2e',
titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : 'default',
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
},
});

mainWindow.once('ready-to-show', () => {
mainWindow?.show();
});

if (isDev) {
mainWindow.loadURL('http://localhost:5173');
mainWindow.webContents.openDevTools();
} else {
mainWindow.loadFile(path.join(__dirname, '../dist/index.html'));
}

// Handle external links in the default browser, not a new Electron window
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url);
return { action: 'deny' };
});
}

app.whenReady().then(() => {
createWindow();

app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});

app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
dica

Peça ao Cursor para revisar sua configuração de BrowserWindow quanto a problemas de segurança. Um prompt como "Audite minhas webPreferences do Electron quanto a melhores práticas de segurança" detectará configurações perigosas como nodeIntegration: true ou um script de preload ausente.

Registrando Handlers IPC​

Handlers IPC no processo main recebem mensagens do renderer e retornam resultados. Defina um contrato tipado para que ambos os lados concordem com a superfície da API.

// electron/ipc-handlers.ts
import { ipcMain, dialog, BrowserWindow } from 'electron';
import * as fs from 'fs/promises';

export function registerIpcHandlers(): void {
ipcMain.handle('dialog:openFile', async () => {
const result = await dialog.showOpenDialog({
properties: ['openFile'],
filters: [{ name: 'Text', extensions: ['txt', 'md', 'json'] }],
});
if (result.canceled) return null;

const content = await fs.readFile(result.filePaths[0], 'utf-8');
return { path: result.filePaths[0], content };
});

ipcMain.handle('file:save', async (_event, filePath: string, content: string) => {
await fs.writeFile(filePath, content, 'utf-8');
return { success: true };
});

ipcMain.handle('window:minimize', (event) => {
const win = BrowserWindow.fromWebContents(event.sender);
win?.minimize();
});

ipcMain.handle('window:toggleMaximize', (event) => {
const win = BrowserWindow.fromWebContents(event.sender);
if (win?.isMaximized()) {
win.unmaximize();
} else {
win?.maximize();
}
});
}
aviso

Nunca passe o objeto BrowserWindow ou outras APIs do Electron diretamente através de IPC para o renderer. Sempre envolva operações nativas em handlers tipados e retorne dados simples. Passar objetos vivos através da fronteira de processo cria riscos severos de segurança e vazamento de memória.

Isolamento de Contexto e Scripts de Preload​

O isolamento de contexto é a principal fronteira de segurança do Electron. Quando habilitado, o objeto window do renderer é completamente separado do escopo do script de preload. A única maneira de expor funcionalidade nativa ao renderer é através da API contextBridge.

Escrevendo um Script de Preload Seguro​

// electron/preload.ts
import { contextBridge, ipcRenderer } from 'electron';

// Define the API surface exposed to the renderer
const api = {
// File operations
openFile: () => ipcRenderer.invoke('dialog:openFile'),
saveFile: (path: string, content: string) =>
ipcRenderer.invoke('file:save', path, content),

// Window controls
minimizeWindow: () => ipcRenderer.invoke('window:minimize'),
toggleMaximize: () => ipcRenderer.invoke('window:toggleMaximize'),

// Event subscriptions with cleanup
onUpdateAvailable: (callback: (info: { version: string }) => void) => {
const handler = (_event: unknown, info: { version: string }) => callback(info);
ipcRenderer.on('update-available', handler);
return () => ipcRenderer.removeListener('update-available', handler);
},
} as const;

// Expose a frozen, read-only API to the renderer
contextBridge.exposeInMainWorld('electronAPI', api);

Consumindo a API no Renderer​

// src/renderer.ts
// TypeScript declaration so the renderer knows about window.electronAPI
declare global {
interface Window {
electronAPI: {
openFile: () => Promise<{ path: string; content: string } | null>;
saveFile: (path: string, content: string) => Promise<{ success: boolean }>;
minimizeWindow: () => Promise<void>;
toggleMaximize: () => Promise<void>;
onUpdateAvailable: (callback: (info: { version: string }) => void) => () => void;
};
}
}

const openButton = document.getElementById('open-btn');
const editor = document.getElementById('editor') as HTMLTextAreaElement;
let currentFilePath: string | null = null;

openButton?.addEventListener('click', async () => {
const result = await window.electronAPI.openFile();
if (result) {
currentFilePath = result.path;
editor.value = result.content;
}
});

document.getElementById('save-btn')?.addEventListener('click', async () => {
if (currentFilePath) {
await window.electronAPI.saveFile(currentFilePath, editor.value);
}
});
dica

Gere o contrato completo de tipos de preload e renderer de uma só vez pedindo ao Cursor: "Crie uma interface TypeScript compartilhada entre meu script de preload do Electron e o renderer que lista cada canal IPC que eu uso. Coloque em um arquivo de tipos compartilhado." Isso mantém ambos os lados sincronizados e detecta incompatibilidades em tempo de compilação.

aviso

Nunca use nodeIntegration: true ou contextIsolation: false em um app Electron de produção. Essas configurações expõem APIs do Node.js diretamente ao conteúdo web, o que é uma vulnerabilidade de segurança crítica se seu app algum dia carregar URLs remotas ou processar entrada do usuário não confiável.

Empacotamento com electron-builder​

O electron-builder compila seu app em instaladores para macOS (.dmg), Windows (.exe, .nsis) e Linux (.AppImage, .deb). A configuração fica em um único arquivo YAML ou JSON.

Arquivo de Configuração​

# electron-builder.yml
appId: com.example.my-electron-app
productName: My Electron App
directories:
output: release
buildResources: build
files:
- dist/**/*
- dist/electron/**/*
extraMetadata:
main: dist/electron/main.js
mac:
category: public.app-category.productivity
target:
- dmg
- zip
hardenedRuntime: true
gatekeeperAssess: false
entitlements: build/entitlements.mac.plist
entitlementsInherit: build/entitlements.mac.plist
win:
target:
- nsis
artifactName: ${productName}-${version}-${arch}.${ext}
nsis:
oneClick: false
allowToChangeInstallationDirectory: true
createDesktopShortcut: true
linux:
target:
- AppImage
- deb
category: Office

Build para Todas as Plataformas​

# Build for current platform
npm run package

# Build for macOS (requires macOS host)
npm run package:mac

# Build for Windows
npm run package:win
dica

Peça ao Cursor para gerar um workflow do GitHub Actions que constrói seu app Electron para macOS, Windows e Linux a cada push de tag. Um prompt como "Crie um workflow de CI que executa o electron-builder em três plataformas e faz upload dos artefatos para um release do GitHub" produz um arquivo de workflow pronto para uso.

aviso

Construir instaladores para macOS requer uma máquina macOS com um certificado válido de Apple Developer. A compilação cruzada a partir do Linux ou Windows não é suportada para builds macOS assinados. Planeje sua infraestrutura de CI adequadamente.

Auto-Update com electron-updater​

O auto-update permite que usuários recebam novas versões sem baixar instaladores manualmente. O pacote electron-updater se integra com releases do GitHub ou qualquer servidor de arquivos estáticos.

Configurando Auto-Update​

// electron/updater.ts
import { autoUpdater } from 'electron-updater';
import { BrowserWindow } from 'electron';

export function setupAutoUpdater(mainWindow: BrowserWindow): void {
autoUpdater.autoDownload = false;
autoUpdater.autoInstallOnAppQuit = true;

autoUpdater.on('update-available', (info) => {
// Notify the renderer that an update is available
mainWindow.webContents.send('update-available', { version: info.version });
});

autoUpdater.on('update-not-available', () => {
mainWindow.webContents.send('update-not-available');
});

autoUpdater.on('download-progress', (progress) => {
mainWindow.webContents.send('download-progress', {
percent: Math.round(progress.percent),
});
});

autoUpdater.on('update-downloaded', () => {
// Ask the user, then quit and install
mainWindow.webContents.send('update-downloaded');
});

// Check for updates after startup
app.whenReady().then(() => {
autoUpdater.checkForUpdates();
});
}

// Expose control methods through IPC
ipcMain.handle('update:download', () => {
autoUpdater.downloadUpdate();
});

ipcMain.handle('update:install', () => {
setImmediate(() => {
app.isQuiting = true;
autoUpdater.quitAndInstall();
});
});

UI de Update no Renderer​

// src/components/UpdateNotification.tsx
import { useEffect, useState } from 'react';

export function UpdateNotification() {
const [updateInfo, setUpdateInfo] = useState<{ version: string } | null>(null);
const [progress, setProgress] = useState(0);
const [downloaded, setDownloaded] = useState(false);

useEffect(() => {
const cleanup = window.electronAPI.onUpdateAvailable((info) => {
setUpdateInfo(info);
});

const progressHandler = (_event: unknown, data: { percent: number }) => {
setProgress(data.percent);
};
window.electronAPI.onDownloadProgress?.(progressHandler);

return cleanup;
}, []);

if (!updateInfo) return null;

return (
<div className="fixed bottom-4 right-4 rounded-lg border bg-white p-4 shadow-lg">
<p className="font-semibold">Version {updateInfo.version} is available</p>
{downloaded ? (
<button onClick={() => window.electronAPI.installUpdate?.()}>
Restart and Install
</button>
) : progress > 0 ? (
<div className="mt-2 h-2 rounded bg-gray-200">
<div className="h-2 rounded bg-blue-500" style={{ width: `${progress}%` }} />
</div>
) : (
<button onClick={() => window.electronAPI.downloadUpdate?.()}>
Download Update
</button>
)}
</div>
);
}
dica

Use autoUpdater.autoDownload = false para dar aos usuários controle sobre os downloads. Downloads forçados em segundo plano podem consumir banda em conexões medidas e frustrar usuários. Sempre mostre uma notificação e deixe o usuário escolher quando baixar.

aviso

O auto-update requer assinatura de código no macOS. Apps não assinados falham silenciosamente na etapa de verificação de update e o electron-updater relata um erro. No Windows, apps não assinados podem atualizar mas disparam avisos do SmartScreen que erodem a confiança do usuário.

Geração de UI Desktop Assistida por IA​

O Cursor se destaca em gerar componentes de UI de qualidade desktop. Descreva a interface que você quer e o Cursor produz componentes React acessíveis, navegáveis por teclado, estilizados para um contexto de desktop.

Gerando uma Barra de Título Personalizada​

Create a React component for a custom Electron title bar on Windows.
Include minimize, maximize/restore, and close buttons. Use inline SVG
icons. Wire each button to the window.electronAPI IPC methods.
Support keyboard shortcuts (Alt+F4 to close, Win+Up to maximize).

O Cursor gera:

// src/components/TitleBar.tsx
import { useEffect, useState } from 'react';

export function TitleBar({ title }: { title: string }) {
const [isMaximized, setIsMaximized] = useState(false);

useEffect(() => {
const checkMax = () => setIsMaximized(window.electronAPI.isMaximized());
checkMax();
const interval = setInterval(checkMax, 200);
return () => clearInterval(interval);
}, []);

return (
<div
className="flex h-8 items-center justify-between select-none"
style={{ WebkitAppRegion: 'drag' } as React.CSSProperties}
>
<span className="ml-3 text-sm font-medium text-gray-200">{title}</span>
<div
className="flex h-full"
style={{ WebkitAppRegion: 'no-drag' } as React.CSSProperties}
>
<button
onClick={() => window.electronAPI.minimizeWindow()}
className="flex h-full w-12 items-center justify-center hover:bg-white/10"
aria-label="Minimize"
>
<svg width="12" height="12" viewBox="0 0 12 12">
<rect y="5" width="12" height="2" fill="currentColor" />
</svg>
</button>
<button
onClick={() => window.electronAPI.toggleMaximize()}
className="flex h-full w-12 items-center justify-center hover:bg-white/10"
aria-label={isMaximized ? 'Restore' : 'Maximize'}
>
{isMaximized ? (
<svg width="12" height="12" viewBox="0 0 12 12">
<rect x="1" y="3" width="8" height="8" stroke="currentColor" fill="none" />
<rect x="3" y="1" width="8" height="2" fill="currentColor" />
</svg>
) : (
<svg width="12" height="12" viewBox="0 0 12 12">
<rect x="1" y="1" width="10" height="10" stroke="currentColor" fill="none" />
</svg>
)}
</button>
<button
onClick={() => window.electronAPI.closeWindow()}
className="flex h-full w-12 items-center justify-center hover:bg-red-600"
aria-label="Close"
>
<svg width="12" height="12" viewBox="0 0 12 12">
<path d="M1 1 L11 11 M11 1 L1 11" stroke="currentColor" strokeWidth="2" />
</svg>
</button>
</div>
</div>
);
}
dica

Ao gerar UI de desktop no Cursor, sempre mencione a plataforma. Barras de título se comportam de forma diferente no macOS (botões de semáforo à esquerda) versus Windows (botões do sistema à direita). Especificar a plataforma produz layouts corretos e apropriados para cada plataforma.

Otimização de Performance​

Apps Electron podem parecer pesados se você não gerenciar recursos com cuidado. O Cursor ajuda a identificar e corrigir gargalos de performance.

Reduzindo Uso de Memória e CPU​

// electron/utils/performance.ts
import { app, BrowserWindow } from 'electron';

export function optimizePerformance(): void {
// Limit the number of background processes
app.commandLine.appendSwitch('disable-background-networking');
app.commandLine.appendSwitch('disable-renderer-backgrounding');

// Reduce GPU overhead for non-accelerated content
app.commandLine.appendSwitch('disable-gpu-compositing');

// Suspend background timers when the window is hidden
app.on('browser-window-blur', () => {
// Pause expensive operations
});

// Aggressively release memory when the app is idle
setInterval(() => {
const windows = BrowserWindow.getAllWindows();
if (windows.length === 0) {
// Trigger garbage collection if running with --inspect
if (global.gc) global.gc();
}
}, 60000);
}
aviso

Não aplique cegamente todas as opções de commandLine. Desabilitar a composição de GPU pode quebrar animações CSS e renderização de canvas WebGL. Teste toda a sua UI após cada mudança de performance e reverta opções que causem regressões visuais.

Checklist de Segurança​

Execute este prompt no Cursor para auditar todo o seu projeto Electron quanto a problemas de segurança:

Review my Electron app for security vulnerabilities. Check for:
1. contextIsolation enabled
2. nodeIntegration disabled
3. sandbox enabled
4. CSP headers set
5. No remote modules loaded
6. preload script uses contextBridge only
7. All IPC handlers validate input
8. Webview tags disabled or sandboxed

O Cursor escaneia sua base de código e retorna uma lista priorizada de problemas com referências de arquivo específicas e correções sugeridas.

dica

Adicione o checklist de segurança do Electron como um arquivo .cursorrules na raiz do seu projeto. O Cursor aplicará automaticamente essas regras ao gerar novo código Electron, prevenindo regressões de segurança ao longo do tempo.

Resumo​

Construir aplicativos desktop Electron no Cursor combina o poder multiplataforma das tecnologias web com a velocidade de desenvolvimento assistida por IA. Seguindo os padrões deste guia — IPC com isolamento de contexto seguro, contratos de preload tipados, empacotamento com electron-builder e auto-update controlado pelo usuário — você pode enviar apps desktop profissionais mais rápido do que nunca. Mantenha contextIsolation ativado, nodeIntegration desativado e deixe o Cursor auditar seu código quanto a segurança e performance em cada etapa.