Files
knock-gui/desktop/DEVELOPMENT.md

31 KiB
Raw Blame History

Руководство по разработке Knocker Desktop

🔍 Подробное описание архитектуры

Архитектура Electron приложения

┌─────────────────────────────────────────────────────────────┐
│                    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:

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:

// Используем безопасный 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:

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

┌─────────────┐    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 сообщений в приложении

// 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 - изоляция контекста

    webPreferences: {
      contextIsolation: true  // Renderer не может получить доступ к Node.js
    }
    
  2. Node Integration - отключение интеграции Node.js

    webPreferences: {
      nodeIntegration: false  // Отключаем прямой доступ к require()
    }
    
  3. Sandbox - песочница

    webPreferences: {
      sandbox: false  // Позволяем preload работать
    }
    

Почему такая архитектура?

Проблема: Renderer процесс работает с ненадежным контентом (HTML/JS от пользователя).

Решение: Изолируем renderer от Node.js API, но предоставляем безопасный доступ через preload.

// ❌ НЕБЕЗОПАСНО (если включить nodeIntegration: true)
// В renderer процессе:
const fs = require('fs');
fs.readFileSync('/etc/passwd'); // Может прочитать системные файлы!

// ✅ БЕЗОПАСНО (через contextBridge)
// В renderer процессе:
const result = await window.api.openFile(); // Только разрешенные операции

🎯 Функциональность приложения

Режимы работы

1. Inline режим

// Простые поля для быстрого ввода
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 конфигурация
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 режим

// Табличная форма для добавления целей
const targets = [
  { protocol: 'tcp', host: '127.0.0.1', port: 22, gateway: '' },
  { protocol: 'udp', host: '192.168.1.1', port: 53, gateway: 'gw1' }
];

Файловые операции

Открытие файлов

// 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 };
});

Сохранение файлов

// 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

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

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

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

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;
}

Конвертация между режимами

// 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. Структура проекта

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. Зависимости

{
  "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

// В main.js автоматически открываются DevTools
mainWindow.webContents.openDevTools();

Логирование

// 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;
  });
};

Отладка файловых операций

// В main.js добавить логирование
ipcMain.handle('file:open', async () => {
  console.log('Opening file dialog...');
  const result = await dialog.showOpenDialog({...});
  console.log('Dialog result:', result);
  // ...
});

Тестирование

Локальное тестирование

# Запуск в режиме разработки
npm run dev

# Проверка функциональности:
# 1. Открытие файлов
# 2. Сохранение файлов  
# 3. HTTP запросы к API
# 4. Переключение между режимами
# 5. Конвертация YAML ↔ Inline

Тестирование сборки

# Упаковка без установщика
npm run pack

# Полная сборка
npm run build

# Проверка на разных платформах
npm run build:win
npm run build:linux
npm run build:mac

📦 Сборка и распространение

Electron Builder конфигурация

{
  "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

Команды сборки

# Сборка для текущей платформы
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

Иконки и ресурсы

Требования к иконкам

assets/
├── icon.ico    # Windows: 256x256, ICO формат
├── icon.png    # Linux: 512x512, PNG формат  
└── icon.icns   # macOS: 512x512, ICNS формат

Создание иконок

# Из PNG в ICO (Windows)
convert icon.png -resize 256x256 icon.ico

# Из PNG в ICNS (macOS)
iconutil -c icns icon.iconset

Автоматизация сборки

GitHub Actions пример

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/

🚀 Производительность и оптимизация

Оптимизация размера приложения

Исключение ненужных файлов

{
  "build": {
    "files": [
      "src/**/*",
      "node_modules/**/*"
    ],
    "asarUnpack": [
      "node_modules/electron/**/*"  // Исключаем Electron из asar
    ]
  }
}

Tree shaking

// Используем только нужные части библиотек
import { load, dump } from 'js-yaml';  // Вместо import * as yaml

Оптимизация загрузки

Lazy loading

// Загружаем YAML парсер только когда нужен
async function loadYamlParser() {
  if (!window.jsyaml) {
    await import('../../node_modules/js-yaml/dist/js-yaml.min.js');
  }
}

Кэширование

// Кэшируем результаты 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

webPreferences: {
  contextIsolation: true  // Изолирует контекст renderer от Node.js
}

2. Node Integration

webPreferences: {
  nodeIntegration: false  // Отключает прямой доступ к require()
}

3. Sandbox

webPreferences: {
  sandbox: false  // Позволяет preload работать (но только в preload)
}

4. CSP (Content Security Policy)

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline';">

Валидация входных данных

Проверка паролей

function validatePassword(password) {
  if (!password || password.length < 1) {
    throw new Error('Пароль не может быть пустым');
  }
  return password;
}

Проверка файлов

function validateFileContent(content) {
  if (typeof content !== 'string') {
    throw new Error('Неверный формат файла');
  }
  if (content.length > 10 * 1024 * 1024) { // 10MB лимит
    throw new Error('Файл слишком большой');
  }
  return content;
}

Проверка API ответов

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. Приложение не запускается

# Проверка зависимостей
npm install

# Очистка и переустановка
rm -rf node_modules package-lock.json
npm install

# Проверка версии Node.js
node --version  # Должна быть >= 16

2. DevTools не открываются

// Убедитесь что в 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. Файлы не открываются

// Проверьте что 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. Сборка не работает

# Очистка dist папки
rm -rf dist

# Проверка конфигурации
npm run build -- --debug

# Сборка с подробными логами
DEBUG=electron-builder npm run build

5. IPC сообщения не работают

// Проверьте что preload скрипт загружается
console.log('Preload loaded:', typeof window.api);

// Проверьте IPC каналы
ipcRenderer.invoke('test').then(result => {
  console.log('IPC test result:', result);
});

Отладка производительности

Профилирование

// В main.js
const { performance } = require('perf_hooks');

const startTime = performance.now();
// ... код ...
const endTime = performance.now();
console.log(`Operation took ${endTime - startTime} milliseconds`);

Мониторинг памяти

// В 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);

Логирование и мониторинг

Структурированное логирование

// В 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);

Отслеживание ошибок

// Глобальный обработчик ошибок
process.on('uncaughtException', (error) => {
  console.error('Uncaught Exception:', error);
  // Можно отправить в сервис мониторинга
});

process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled Rejection at:', promise, 'reason:', reason);
});

📚 Дополнительные ресурсы

Документация

Лучшие практики

Инструменты разработки

🤝 Вклад в разработку

Процесс разработки

  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)

# Полный цикл тестирования
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. Используйте его как справочник при работе с проектом.