Копирование папок desktop и desktop-angular из ветки main
This commit is contained in:
990
desktop/DEVELOPMENT.md
Normal file
990
desktop/DEVELOPMENT.md
Normal file
@@ -0,0 +1,990 @@
|
||||
# Руководство по разработке 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
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline';">
|
||||
```
|
||||
|
||||
### Валидация входных данных
|
||||
|
||||
#### Проверка паролей
|
||||
|
||||
```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. Используйте его как справочник при работе с проектом.
|
Reference in New Issue
Block a user