# Руководство по разработке Knocker Desktop ## 🔍 Подробное описание архитектуры ### Архитектура Electron приложения ``` text ┌─────────────────────────────────────────────────────────────┐ │ MAIN PROCESS │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ src/main/main.js │ │ │ │ • Управление жизненным циклом приложения │ │ │ │ • Создание и управление окнами │ │ │ │ • Доступ к Node.js API (fs, dialog, shell) │ │ │ │ • IPC обработчики для файловых операций │ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ │ │ IPC (Inter-Process Communication) │ ┌─────────────────────────────────────────────────────────────┐ │ RENDERER PROCESS │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ src/renderer/ │ │ │ │ • HTML/CSS/JS интерфейс │ │ │ │ • Взаимодействие с пользователем │ │ │ │ • HTTP запросы к API │ │ │ │ • Ограниченный доступ к системе │ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ │ │ contextBridge │ ┌─────────────────────────────────────────────────────────────┐ │ PRELOAD SCRIPT │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ src/preload/preload.js │ │ │ │ • Безопасный мост между main и renderer │ │ │ │ • Доступ к Node.js API │ │ │ │ • Экспорт API через window.api │ │ │ │ • Изоляция от прямого доступа к Node.js │ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` ### Детальное объяснение процессов #### 1. Main Process (Основной процесс) **Роль**: Ядро приложения, управляет всей жизнью приложения. **Возможности**: - Создание и управление окнами - Доступ к Node.js API (файловая система, диалоги, системные функции) - Обработка системных событий (закрытие приложения, фокус окон) - IPC сервер для связи с renderer процессами **Код в `src/main/main.js`**: ```javascript const { app, BrowserWindow, ipcMain, dialog, shell } = require('electron'); // Создание главного окна с настройками безопасности function createWindow() { mainWindow = new BrowserWindow({ width: 1100, height: 800, webPreferences: { preload: path.join(__dirname, '../preload/preload.js'), contextIsolation: true, // КРИТИЧНО: изолирует контекст nodeIntegration: false, // КРИТИЧНО: отключает прямой доступ к Node.js sandbox: false // Позволяет preload работать } }); } // IPC обработчики - "серверная часть" для renderer ipcMain.handle('file:open', async () => { // Безопасная работа с файлами через main процесс const res = await dialog.showOpenDialog({ properties: ['openFile'], filters: [{ name: 'YAML/Encrypted', extensions: ['yaml', 'yml', 'encrypted', 'txt'] }] }); // Возвращаем данные в renderer процесс return { canceled: res.canceled, filePath: res.filePaths[0], content: fs.readFileSync(res.filePaths[0], 'utf-8') }; }); ``` #### 2. Renderer Process (Процесс рендеринга) **Роль**: Отображение пользовательского интерфейса, взаимодействие с пользователем. **Ограничения**: - Нет прямого доступа к Node.js API - Работает как обычная веб-страница - Изолирован от файловой системы - Может делать HTTP запросы **Код в `src/renderer/renderer.js`**: ```javascript // Используем безопасный API из preload window.addEventListener('DOMContentLoaded', () => { // Обработчики UI событий document.getElementById('openFile').addEventListener('click', async () => { // Вызов через contextBridge API const result = await window.api.openFile(); if (!result.canceled) { // Обновляем UI с данными файла document.getElementById('configYAML').value = result.content; } }); // HTTP запросы к backend API document.getElementById('execute').addEventListener('click', async () => { const response = await fetch('http://localhost:8080/api/v1/knock-actions/execute', { method: 'POST', headers: { 'Content-Type': 'application/json', ...basicAuthHeader(password) }, body: JSON.stringify(body) }); }); }); ``` #### 3. Preload Script (Preload скрипт) **Роль**: Безопасный мост между main и renderer процессами. **Особенности**: - Выполняется в renderer процессе - Имеет доступ к Node.js API - Изолирован от глобального контекста renderer - Создает безопасный API через `contextBridge` **Код в `src/preload/preload.js`**: ```javascript const { contextBridge, ipcRenderer } = require('electron'); // Создаем безопасный API для renderer процесса contextBridge.exposeInMainWorld('api', { // Файловые операции openFile: () => ipcRenderer.invoke('file:open'), saveAs: (payload) => ipcRenderer.invoke('file:saveAs', payload), saveToPath: (payload) => ipcRenderer.invoke('file:saveToPath', payload), revealInFolder: (filePath) => ipcRenderer.invoke('os:revealInFolder', filePath) }); // Renderer процесс получает доступ к window.api // Но НЕ имеет прямого доступа к require, fs, dialog и т.д. ``` ### IPC (Inter-Process Communication) - Связь между процессами #### Как работает IPC ``` text ┌─────────────┐ IPC Message ┌─────────────┐ │ Renderer │ ────────────────> │ Main │ │ Process │ │ Process │ │ │ <──────────────── │ │ └─────────────┘ IPC Response └─────────────┘ ``` **Шаг 1**: Renderer процесс вызывает `window.api.openFile()` **Шаг 2**: Preload скрипт отправляет IPC сообщение `'file:open'` в main процесс **Шаг 3**: Main процесс обрабатывает сообщение и выполняет файловую операцию **Шаг 4**: Main процесс возвращает результат через IPC **Шаг 5**: Preload скрипт получает результат и возвращает его renderer процессу #### Типы IPC сообщений в приложении ```javascript // Main процесс (обработчики) ipcMain.handle('file:open', handler); // Открытие файла ipcMain.handle('file:saveAs', handler); // Сохранение файла ipcMain.handle('file:saveToPath', handler); // Сохранение по пути ipcMain.handle('os:revealInFolder', handler); // Показать в проводнике // Preload скрипт (клиент) ipcRenderer.invoke('file:open'); // Отправка запроса ipcRenderer.invoke('file:saveAs', payload); // Отправка с данными ``` ### Безопасность в Electron #### Принципы безопасности 1. **Context Isolation** - изоляция контекста ```javascript webPreferences: { contextIsolation: true // Renderer не может получить доступ к Node.js } ``` 2. **Node Integration** - отключение интеграции Node.js ```javascript webPreferences: { nodeIntegration: false // Отключаем прямой доступ к require() } ``` 3. **Sandbox** - песочница ```javascript webPreferences: { sandbox: false // Позволяем preload работать } ``` #### Почему такая архитектура? **Проблема**: Renderer процесс работает с ненадежным контентом (HTML/JS от пользователя). **Решение**: Изолируем renderer от Node.js API, но предоставляем безопасный доступ через preload. ```javascript // ❌ НЕБЕЗОПАСНО (если включить nodeIntegration: true) // В renderer процессе: const fs = require('fs'); fs.readFileSync('/etc/passwd'); // Может прочитать системные файлы! // ✅ БЕЗОПАСНО (через contextBridge) // В renderer процессе: const result = await window.api.openFile(); // Только разрешенные операции ``` ## 🎯 Функциональность приложения ### Режимы работы #### 1. Inline режим ```javascript // Простые поля для быстрого ввода const formData = { password: 'user_password', targets: 'tcp:127.0.0.1:22;tcp:192.168.1.1:80', delay: '1s', verbose: true, waitConnection: false, gateway: 'optional_gateway' }; ``` #### 2. YAML режим ```yaml # Полная YAML конфигурация targets: - protocol: tcp host: 127.0.0.1 ports: [22, 80] wait_connection: true - protocol: udp host: 192.168.1.1 ports: [53] delay: 1s path: /etc/knocker/config.yaml # Путь на сервере ``` #### 3. Form режим ```javascript // Табличная форма для добавления целей const targets = [ { protocol: 'tcp', host: '127.0.0.1', port: 22, gateway: '' }, { protocol: 'udp', host: '192.168.1.1', port: 53, gateway: 'gw1' } ]; ``` ### Файловые операции #### Открытие файлов ```javascript // Main процесс ipcMain.handle('file:open', async () => { const result = await dialog.showOpenDialog({ properties: ['openFile'], filters: [{ name: 'YAML/Encrypted', extensions: ['yaml', 'yml', 'encrypted', 'txt'] }] }); if (result.canceled) return { canceled: true }; const filePath = result.filePaths[0]; const content = fs.readFileSync(filePath, 'utf-8'); return { canceled: false, filePath, content }; }); ``` #### Сохранение файлов ```javascript // Main процесс ipcMain.handle('file:saveAs', async (event, payload) => { const result = await dialog.showSaveDialog({ defaultPath: payload.suggestedName || 'config.yaml', filters: [{ name: 'YAML/Encrypted', extensions: ['yaml', 'yml', 'encrypted', 'txt'] }] }); if (result.canceled || !result.filePath) return { canceled: true }; fs.writeFileSync(result.filePath, payload.content, 'utf-8'); return { canceled: false, filePath: result.filePath }; }); ``` ### HTTP API интеграция #### Basic Authentication ```javascript function basicAuthHeader(password) { const token = btoa(`knocker:${password}`); return { Authorization: `Basic ${token}` }; } // Использование в запросах const response = await fetch('http://localhost:8080/api/v1/knock-actions/execute', { method: 'POST', headers: { 'Content-Type': 'application/json', ...basicAuthHeader(password) }, body: JSON.stringify(payload) }); ``` #### API endpoints ```javascript const apiEndpoints = { execute: '/api/v1/knock-actions/execute', encrypt: '/api/v1/knock-actions/encrypt', decrypt: '/api/v1/knock-actions/decrypt', encryptFile: '/api/v1/knock-actions/encrypt-file' }; ``` ### YAML обработка #### Извлечение пути из YAML ```javascript function extractPathFromYaml(text) { try { const doc = yaml.load(text); if (doc && typeof doc === 'object' && typeof doc.path === 'string') { return doc.path; } } catch (e) { console.warn('Failed to parse YAML:', e); } return ''; } ``` #### Обновление пути в YAML ```javascript function patchYamlPath(text, newPath) { try { const doc = text.trim() ? yaml.load(text) : {}; if (doc && typeof doc === 'object') { doc.path = newPath || ''; return yaml.dump(doc, { lineWidth: 120 }); } } catch (e) { console.warn('Failed to update YAML path:', e); } return text; } ``` #### Конвертация между режимами ```javascript // Inline → YAML function convertInlineToYaml(targetsStr, delay, waitConnection) { const entries = targetsStr.split(';').filter(Boolean); const config = { targets: entries.map(entry => { const [protocol, host, port] = entry.split(':'); return { protocol: protocol || 'tcp', host: host || '127.0.0.1', ports: [parseInt(port) || 22], wait_connection: waitConnection }; }), delay: delay || '1s' }; return yaml.dump(config, { lineWidth: 120 }); } // YAML → Inline function convertYamlToInline(yamlText) { const config = yaml.load(yamlText) || {}; const targets = []; (config.targets || []).forEach(target => { const protocol = target.protocol || 'tcp'; const host = target.host || '127.0.0.1'; const ports = target.ports || [target.port] || [22]; ports.forEach(port => { targets.push(`${protocol}:${host}:${port}`); }); }); return { targets: targets.join(';'), delay: config.delay || '1s', waitConnection: !!(config.targets?.[0]?.wait_connection) }; } ``` ## 🔧 Разработка и отладка ### Настройка среды разработки #### 1. Структура проекта ``` text desktop/ ├── src/ │ ├── main/ │ │ ├── main.js # Основной процесс (CommonJS) │ │ └── main.ts # TypeScript версия (опционально) │ ├── preload/ │ │ ├── preload.js # Preload скрипт (CommonJS) │ │ └── preload.ts # TypeScript версия (опционально) │ └── renderer/ │ ├── index.html # HTML разметка │ ├── styles.css # Стили │ ├── renderer.js # UI логика (ванильный JS) │ └── renderer.ts # TypeScript версия (опционально) ├── assets/ # Иконки для сборки ├── dist/ # Собранные приложения ├── package.json # Конфигурация ├── README.md # Основная документация └── DEVELOPMENT.md # Это руководство ``` #### 2. Зависимости ```json { "devDependencies": { "electron": "^28.3.3", // Electron runtime "electron-builder": "^26.0.12" // Сборка и пакетирование }, "dependencies": { "axios": "^1.12.2", // HTTP клиент (не используется в финальной версии) "js-yaml": "^4.1.0" // YAML парсер } } ``` ### Отладка #### DevTools ```javascript // В main.js автоматически открываются DevTools mainWindow.webContents.openDevTools(); ``` #### Логирование ```javascript // Main процесс - логи в терминале console.log('Main process:', data); // Renderer процесс - логи в DevTools Console console.log('Renderer process:', data); // IPC отладка в preload const originalInvoke = ipcRenderer.invoke; ipcRenderer.invoke = function(channel, ...args) { console.log(`IPC Request: ${channel}`, args); return originalInvoke.call(this, channel, ...args).then(result => { console.log(`IPC Response: ${channel}`, result); return result; }); }; ``` #### Отладка файловых операций ```javascript // В main.js добавить логирование ipcMain.handle('file:open', async () => { console.log('Opening file dialog...'); const result = await dialog.showOpenDialog({...}); console.log('Dialog result:', result); // ... }); ``` ### Тестирование #### Локальное тестирование ```bash # Запуск в режиме разработки npm run dev # Проверка функциональности: # 1. Открытие файлов # 2. Сохранение файлов # 3. HTTP запросы к API # 4. Переключение между режимами # 5. Конвертация YAML ↔ Inline ``` #### Тестирование сборки ```bash # Упаковка без установщика npm run pack # Полная сборка npm run build # Проверка на разных платформах npm run build:win npm run build:linux npm run build:mac ``` ## 📦 Сборка и распространение ### Electron Builder конфигурация ```json { "build": { "appId": "com.knocker.desktop", // Уникальный ID приложения "productName": "Knocker Desktop", // Имя продукта "directories": { "output": "dist" // Папка для сборки }, "files": [ "src/**/*", // Исходный код "node_modules/**/*" // Зависимости ], "win": { "target": "nsis", // Windows installer "icon": "assets/icon.ico" // Иконка Windows }, "linux": { "target": "AppImage", // Linux portable app "icon": "assets/icon.png" // Иконка Linux }, "mac": { "target": "dmg", // macOS disk image "icon": "assets/icon.icns" // Иконка macOS } } } ``` ### Типы сборки #### Windows - **NSIS** - установщик с мастером установки - **Portable** - портативная версия - **Squirrel** - автообновления #### Linux - **AppImage** - портативное приложение - **deb** - пакет для Debian/Ubuntu - **rpm** - пакет для Red Hat/Fedora - **tar.xz** - архив #### macOS - **dmg** - образ диска - **pkg** - установщик пакета - **mas** - Mac App Store ### Команды сборки ```bash # Сборка для текущей платформы npm run build # Сборка для конкретных платформ npm run build:win # Windows (NSIS) npm run build:linux # Linux (AppImage) npm run build:mac # macOS (DMG) # Упаковка без установщика (для тестирования) npm run pack # Сборка без публикации npm run dist # Публикация (если настроено) npm run publish ``` ### Иконки и ресурсы #### Требования к иконкам ``` text assets/ ├── icon.ico # Windows: 256x256, ICO формат ├── icon.png # Linux: 512x512, PNG формат └── icon.icns # macOS: 512x512, ICNS формат ``` #### Создание иконок ```bash # Из PNG в ICO (Windows) convert icon.png -resize 256x256 icon.ico # Из PNG в ICNS (macOS) iconutil -c icns icon.iconset ``` ### Автоматизация сборки #### GitHub Actions пример ```yaml name: Build Electron App on: push: tags: ['v*'] jobs: build: runs-on: ${{ matrix.os }} strategy: matrix: os: [windows-latest, macos-latest, ubuntu-latest] steps: - uses: actions/checkout@v2 - name: Setup Node.js uses: actions/setup-node@v2 with: node-version: '18' - name: Install dependencies run: npm install - name: Build run: npm run build - name: Upload artifacts uses: actions/upload-artifact@v2 with: name: ${{ matrix.os }} path: dist/ ``` ## 🚀 Производительность и оптимизация ### Оптимизация размера приложения #### Исключение ненужных файлов ```json { "build": { "files": [ "src/**/*", "node_modules/**/*" ], "asarUnpack": [ "node_modules/electron/**/*" // Исключаем Electron из asar ] } } ``` #### Tree shaking ```javascript // Используем только нужные части библиотек import { load, dump } from 'js-yaml'; // Вместо import * as yaml ``` ### Оптимизация загрузки #### Lazy loading ```javascript // Загружаем YAML парсер только когда нужен async function loadYamlParser() { if (!window.jsyaml) { await import('../../node_modules/js-yaml/dist/js-yaml.min.js'); } } ``` #### Кэширование ```javascript // Кэшируем результаты API запросов const cache = new Map(); async function cachedApiCall(endpoint, data) { const key = `${endpoint}:${JSON.stringify(data)}`; if (cache.has(key)) { return cache.get(key); } const result = await apiCall(endpoint, data); cache.set(key, result); return result; } ``` ## 🔒 Безопасность ### Принципы безопасности Electron #### 1. Context Isolation ```javascript webPreferences: { contextIsolation: true // Изолирует контекст renderer от Node.js } ``` #### 2. Node Integration ```javascript webPreferences: { nodeIntegration: false // Отключает прямой доступ к require() } ``` #### 3. Sandbox ```javascript webPreferences: { sandbox: false // Позволяет preload работать (но только в preload) } ``` #### 4. CSP (Content Security Policy) ```html ``` ### Валидация входных данных #### Проверка паролей ```javascript function validatePassword(password) { if (!password || password.length < 1) { throw new Error('Пароль не может быть пустым'); } return password; } ``` #### Проверка файлов ```javascript function validateFileContent(content) { if (typeof content !== 'string') { throw new Error('Неверный формат файла'); } if (content.length > 10 * 1024 * 1024) { // 10MB лимит throw new Error('Файл слишком большой'); } return content; } ``` #### Проверка API ответов ```javascript async function safeApiCall(url, options) { try { const response = await fetch(url, options); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } return await response.json(); } catch (error) { console.error('API call failed:', error); throw error; } } ``` ## 🐛 Устранение неполадок ### Частые проблемы и решения #### 1. Приложение не запускается ```bash # Проверка зависимостей npm install # Очистка и переустановка rm -rf node_modules package-lock.json npm install # Проверка версии Node.js node --version # Должна быть >= 16 ``` #### 2. DevTools не открываются ```javascript // Убедитесь что в main.js есть: mainWindow.webContents.openDevTools(); // Или добавьте горячую клавишу: mainWindow.webContents.on('before-input-event', (event, input) => { if (input.control && input.shift && input.key.toLowerCase() === 'i') { mainWindow.webContents.openDevTools(); } }); ``` #### 3. Файлы не открываются ```javascript // Проверьте что backend запущен const testConnection = async () => { try { const response = await fetch('http://localhost:8080/api/v1/health'); console.log('Backend is running'); } catch (error) { console.error('Backend is not running:', error); } }; ``` #### 4. Сборка не работает ```bash # Очистка dist папки rm -rf dist # Проверка конфигурации npm run build -- --debug # Сборка с подробными логами DEBUG=electron-builder npm run build ``` #### 5. IPC сообщения не работают ```javascript // Проверьте что preload скрипт загружается console.log('Preload loaded:', typeof window.api); // Проверьте IPC каналы ipcRenderer.invoke('test').then(result => { console.log('IPC test result:', result); }); ``` ### Отладка производительности #### Профилирование ```javascript // В main.js const { performance } = require('perf_hooks'); const startTime = performance.now(); // ... код ... const endTime = performance.now(); console.log(`Operation took ${endTime - startTime} milliseconds`); ``` #### Мониторинг памяти ```javascript // В main.js setInterval(() => { const usage = process.memoryUsage(); console.log('Memory usage:', { rss: Math.round(usage.rss / 1024 / 1024) + ' MB', heapTotal: Math.round(usage.heapTotal / 1024 / 1024) + ' MB', heapUsed: Math.round(usage.heapUsed / 1024 / 1024) + ' MB' }); }, 5000); ``` ### Логирование и мониторинг #### Структурированное логирование ```javascript // В main.js const log = { info: (message, data) => console.log(`[INFO] ${message}`, data), error: (message, error) => console.error(`[ERROR] ${message}`, error), debug: (message, data) => console.debug(`[DEBUG] ${message}`, data) }; // Использование log.info('Application started'); log.error('File operation failed', error); ``` #### Отслеживание ошибок ```javascript // Глобальный обработчик ошибок process.on('uncaughtException', (error) => { console.error('Uncaught Exception:', error); // Можно отправить в сервис мониторинга }); process.on('unhandledRejection', (reason, promise) => { console.error('Unhandled Rejection at:', promise, 'reason:', reason); }); ``` ## 📚 Дополнительные ресурсы ### Документация - [Electron Documentation](https://www.electronjs.org/docs) - [Electron Builder](https://www.electron.build/) - [Context Isolation](https://www.electronjs.org/docs/latest/tutorial/context-isolation) - [IPC Communication](https://www.electronjs.org/docs/latest/tutorial/ipc) ### Лучшие практики - [Electron Security](https://www.electronjs.org/docs/latest/tutorial/security) - [Performance Best Practices](https://www.electronjs.org/docs/latest/tutorial/performance) - [Distribution Guide](https://www.electronjs.org/docs/latest/tutorial/distribution) ### Инструменты разработки - [Electron DevTools](https://www.electronjs.org/docs/latest/tutorial/devtools) - [VS Code Electron Extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-electron) - [Electron Fiddle](https://www.electronjs.org/fiddle) ## 🤝 Вклад в разработку ### Процесс разработки 1. Форкните репозиторий 2. Создайте ветку для новой функции: `git checkout -b feature/new-feature` 3. Внесите изменения с тестами 4. Проверьте на всех платформах: `npm run build:win && npm run build:linux && npm run build:mac` 5. Создайте Pull Request с описанием изменений ### Стандарты кода - Используйте ESLint для проверки JavaScript - Комментируйте сложную логику - Следуйте принципам безопасности Electron - Тестируйте на всех поддерживаемых платформах ### Тестирование (another) ```bash # Полный цикл тестирования npm run dev # Тест в режиме разработки npm run pack # Тест упакованной версии npm run build # Тест финальной сборки npm run build:win # Тест Windows версии npm run build:linux # Тест Linux версии npm run build:mac # Тест macOS версии ``` Это руководство покрывает все аспекты разработки Electron приложения Knocker Desktop. Используйте его как справочник при работе с проектом.