본문으로 건너뛰기
cccskills
무료GitHub에 공개

electron-pro

Expert Electron desktop application development — main/renderer process architecture, IPC communication, native OS APIs (menus, tray, notifications, dialogs), auto-updates, code signing, packaging with electron-builder/forge, security hardening (contextIsolation, sandbox), and performance optimization. Use for building cross-platform desktop apps.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

설치 방법 보기

포함된 파일(10)

  • SKILL.md12.7 KB
  • commands/electron-pro.md112 B
  • hooks/post-execute.cjs177 B
  • hooks/pre-execute.cjs283 B
  • references/research-requirements.md693 B
  • rules/electron-pro.md650 B
  • schemas/input.schema.json532 B
  • schemas/output.schema.json397 B
  • scripts/main.cjs554 B
  • templates/implementation-template.md348 B

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Electron Pro Skill

Overview

Full-stack Electron desktop app development — from architecture through distribution. Covers process model, security hardening, native OS integration, IPC patterns, packaging, and auto-update.

Process Architecture

┌─────────────────────────────────────┐
│           Main Process              │
│  (Node.js — full system access)     │
│  app, BrowserWindow, Menu, Tray     │
│  nativeImage, shell, ipcMain        │
└──────────────┬──────────────────────┘
               │ IPC (structured clone)
┌──────────────┴──────────────────────┐
│          Renderer Process           │
│  (Chromium — sandboxed by default)  │
│  Web UI: React/Vue/Svelte/vanilla   │
│  ipcRenderer (via contextBridge)    │
└─────────────────────────────────────┘
               │ contextBridge
┌──────────────┴──────────────────────┐
│            Preload Script           │
│  Bridge between main and renderer   │
│  Exposes safe APIs via contextBridge│
└─────────────────────────────────────┘

Security-First Setup (MANDATORY)

// main.js — Always use these security options
const win = new BrowserWindow({
  webPreferences: {
    preload: path.join(__dirname, 'preload.js'),
    contextIsolation: true, // REQUIRED: isolates renderer from preload
    sandbox: true, // RECOMMENDED: OS-level sandbox
    nodeIntegration: false, // REQUIRED: never expose Node to renderer
    webSecurity: true, // REQUIRED: never disable
    allowRunningInsecureContent: false,
  },
});

// NEVER do this — security violation:
// nodeIntegration: true
// contextIsolation: false
// Use remote: require('@electron/remote') only if absolutely necessary

Preload + contextBridge

// preload.js — the ONLY safe bridge to Node
const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('electronAPI', {
  // Expose specific, validated methods only
  openFile: () => ipcRenderer.invoke('dialog:openFile'),
  saveFile: content => {
    if (typeof content !== 'string') throw new Error('Invalid content');
    return ipcRenderer.invoke('dialog:saveFile', content);
  },
  onUpdateAvailable: callback => {
    // One-way: main → renderer
    const listener = (_, data) => callback(data);
    ipcRenderer.on('update-available', listener);
    return () => ipcRenderer.removeListener('update-available', listener);
  },
  // Platform info (read-only)
  platform: process.platform,
});

// renderer.js — use the exposed API
window.electronAPI.openFile().then(filePath => {
  console.log('Selected:', filePath);
});

IPC Communication Patterns

// main.js — handle IPC calls
const { ipcMain, dialog, app } = require('electron');

// Two-way: renderer calls, main responds
ipcMain.handle('dialog:openFile', async () => {
  const { canceled, filePaths } = await dialog.showOpenDialog({
    properties: ['openFile'],
    filters: [{ name: 'JSON', extensions: ['json'] }],
  });
  if (canceled) return null;
  return filePaths[0];
});

// Validate inputs — never trust renderer
ipcMain.handle('fs:readFile', async (event, filePath) => {
  // Validate path is within allowed directories
  const allowed = path.join(app.getPath('userData'), 'files');
  const resolved = path.resolve(filePath);
  if (!resolved.startsWith(allowed)) {
    throw new Error('Path traversal denied');
  }
  return fs.promises.readFile(resolved, 'utf8');
});

// One-way: main → renderer push
win.webContents.send('update-available', { version: '1.2.0' });

// One-way: renderer → main fire-and-forget
// preload: ipcRenderer.send('log', message)
// main: ipcMain.on('log', (event, message) => console.log(message))

Native OS Integration

Menu

const { Menu, MenuItem } = require('electron');

const template = [
  {
    label: 'File',
    submenu: [
      { label: 'New', accelerator: 'CmdOrCtrl+N', click: () => createWindow() },
      { label: 'Open', accelerator: 'CmdOrCtrl+O', click: () => openFileDialog() },
      { type: 'separator' },
      { role: 'quit' },
    ],
  },
  {
    label: 'Edit',
    submenu: [
      { role: 'undo' },
      { role: 'redo' },
      { type: 'separator' },
      { role: 'cut' },
      { role: 'copy' },
      { role: 'paste' },
    ],
  },
  // macOS: add app menu as first item
  ...(process.platform === 'darwin'
    ? [
        {
          label: app.name,
          submenu: [{ role: 'about' }, { type: 'separator' }, { role: 'hide' }, { role: 'quit' }],
        },
      ]
    : []),
];

Menu.setApplicationMenu(Menu.buildFromTemplate(template));

System Tray

const { Tray, Menu, nativeImage } = require('electron');

const icon = nativeImage.createFromPath(path.join(__dirname, 'assets/tray-icon.png'));
const tray = new Tray(icon);
tray.setToolTip('My App');
tray.setContextMenu(
  Menu.buildFromTemplate([
    { label: 'Open', click: () => win.show() },
    { label: 'Quit', click: () => app.quit() },
  ])
);
tray.on('click', () => (win.isVisible() ? win.hide() : win.show()));

Notifications

const { Notification } = require('electron');

// Check support first
if (Notification.isSupported()) {
  new Notification({
    title: 'Build Complete',
    body: 'Your project compiled successfully.',
    icon: path.join(__dirname, 'assets/icon.png'),
  }).show();
}

Dialogs

const { dialog } = require('electron');

// Open file
const { filePaths } = await dialog.showOpenDialog(win, {
  title: 'Select Config File',
  filters: [{ name: 'JSON', extensions: ['json'] }],
  properties: ['openFile'],
});

// Save file
const { filePath } = await dialog.showSaveDialog(win, {
  defaultPath: 'export.csv',
  filters: [{ name: 'CSV', extensions: ['csv'] }],
});

// Message box
const { response } = await dialog.showMessageBox(win, {
  type: 'question',
  buttons: ['Yes', 'No'],
  message: 'Are you sure you want to delete this?',
});

Shell Operations

const { shell } = require('electron');

// Open in default browser/app — safe for user-initiated actions
await shell.openExternal('https://example.com');

// Open file in default app
await shell.openPath('/path/to/file.pdf');

// Reveal in Finder/Explorer
shell.showItemInFolder('/path/to/file');

App Lifecycle

const { app, BrowserWindow } = require('electron');

let mainWindow = null;

function createWindow() {
  mainWindow = new BrowserWindow({
    width: 1200,
    height: 800,
    show: false, // Wait for ready-to-show to avoid flash
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true,
      nodeIntegration: false,
    },
  });

  mainWindow.loadFile('index.html');
  // Or for dev server: mainWindow.loadURL('http://localhost:5173');

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

app.whenReady().then(() => {
  createWindow();
  // macOS: re-create on activate if no windows
  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) createWindow();
  });
});

// Quit on all windows closed (except macOS)
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit();
});

Auto-Update (electron-updater)

const { autoUpdater } = require('electron-updater');

autoUpdater.checkForUpdatesAndNotify();

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

autoUpdater.on('update-downloaded', info => {
  win.webContents.send('update-downloaded', info);
});

// Triggered by renderer when user clicks "Install"
ipcMain.handle('install-update', () => {
  autoUpdater.quitAndInstall();
});

Packaging with electron-builder

// package.json
{
  "build": {
    "appId": "com.company.myapp",
    "productName": "My App",
    "directories": { "output": "dist" },
    "mac": {
      "category": "public.app-category.productivity",
      "hardenedRuntime": true,
      "entitlements": "build/entitlements.mac.plist",
      "notarize": true
    },
    "win": {
      "target": ["nsis", "portable"],
      "signingHashAlgorithms": ["sha256"]
    },
    "linux": {
      "target": ["AppImage", "deb"],
      "category": "Utility"
    },
    "publish": {
      "provider": "github",
      "owner": "your-org",
      "repo": "your-repo"
    }
  }
}
# Build for current platform
pnpm exec electron-builder

# Build for all platforms (requires cross-platform CI)
pnpm exec electron-builder --mac --win --linux

Deep Linking

// Register protocol
app.setAsDefaultProtocolClient('myapp');

// Handle on macOS/Linux
app.on('open-url', (event, url) => {
  event.preventDefault();
  handleDeepLink(url);
});

// Handle on Windows (second instance)
app.on('second-instance', (event, commandLine) => {
  const url = commandLine.find(arg => arg.startsWith('myapp://'));
  if (url) handleDeepLink(url);
  mainWindow?.focus();
});

// Single instance lock
const gotLock = app.requestSingleInstanceLock();
if (!gotLock) app.quit();

Performance

// Lazy load windows
let settingsWindow = null;
function openSettings() {
  if (settingsWindow) { settingsWindow.focus(); return; }
  settingsWindow = new BrowserWindow({ ... });
  settingsWindow.on('closed', () => { settingsWindow = null; });
}

// Background processing — use utility process (Electron 22+)
const { utilityProcess } = require('electron');
const child = utilityProcess.fork(path.join(__dirname, 'worker.js'));
child.postMessage({ task: 'process', data });
child.on('message', ({ result }) => console.log(result));

// Session — block unnecessary requests
win.webContents.session.webRequest.onBeforeRequest(
  { urls: ['https://tracking.example.com/*'] },
  (details, callback) => callback({ cancel: true })
);

Development Workflow

# Start dev with hot reload (electron-vite recommended)
pnpm exec electron-vite dev

# Or with webpack/parcel
concurrently "pnpm build:renderer --watch" "wait-on http://localhost:5173 && electron ."

# Open DevTools programmatically (dev only)
if (process.env.NODE_ENV === 'development') {
  win.webContents.openDevTools();
}

# Debug main process
electron --inspect=9229 .
# Then attach Chrome DevTools at chrome://inspect

Anti-Patterns

  • nodeIntegration: true — exposes all of Node.js to web content (RCE vector)
  • contextIsolation: false — allows renderer to access preload scope directly
  • webSecurity: false — disables CORS and mixed content protections
  • shell.openExternal(userInput) without validation — SSRF/open redirect vector
  • eval() or Function() in renderer — CSP bypass
  • Storing secrets in renderer process — use main process + keychain
  • Using remote module — deprecated, insecure, causes memory leaks

Security Checklist

  • contextIsolation: true on all windows
  • nodeIntegration: false on all windows
  • sandbox: true enabled
  • CSP header set on loaded HTML
  • All IPC inputs validated in main process
  • No shell.openExternal(untrustedUrl) without validation
  • webSecurity: true (default, do not disable)
  • Code signed for distribution (macOS notarization required)

Related

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Ensure accessibility in UI components including semantic HTML, ARIA attributes, keyboard navigation, and WCAG 2.2 AA compliance.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

oimiragieo/agent-studio422026년 7월 14일 업데이트

Use when you want to improve response quality through meta-cognitive reasoning. Applies 15+ reasoning methods to reconsider and refine initial outputs.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

oimiragieo/agent-studio422026년 7월 14일 업데이트

N-round opposing-stance debates for trade-off analysis. Assigns pro/con roles to agents, runs structured debate rounds with quality scoring, and produces a moderator synthesis with confidence-rated recommendation. Generalizable to architecture, technology, security, and design decisions.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

oimiragieo/agent-studio422026년 7월 14일 업데이트

Force adversarial code review stance that eliminates confirmation bias — reviewer must find issues or re-analyze

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

oimiragieo/agent-studio422026년 7월 14일 업데이트

Creates specialized AI agents on-demand when no existing agent matches a request. Use when the Router cannot find a suitable agent for a task. Enables self-evolution by generating persistent agents.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

oimiragieo/agent-studio422026년 7월 14일 업데이트

LLM-as-judge evaluation framework with 5-dimension rubric (accuracy, groundedness, coherence, completeness, helpfulness) for scoring AI-generated content quality with weighted composite scores and evidence citations

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

oimiragieo/agent-studio422026년 7월 14일 업데이트

oimiragieo의 스킬 모두 보기

이 스킬의 문제 신고