Разработка Desktop VPN-клиента на TypeScript и Electron

Практический курс по созданию кроссплатформенного приложения для управления VPN-соединениями с нуля до готового инсталлятора. Вы научитесь интегрировать системные сетевые протоколы с современным интерфейсом и безопасно управлять процессами.

1. Настройка окружения: инициализация проекта на Electron, TypeScript и выбор UI-фреймворка

Настройка окружения: инициализация проекта на Electron, TypeScript и выбор UI-фреймворка

Добро пожаловать в курс по разработке Desktop VPN-клиента! В этой первой статье мы заложим фундамент нашего будущего приложения. Мы не просто напишем «Hello World», а создадим профессиональную архитектуру, готовую к масштабированию и работе с системными сетями.

Наша цель — создать кроссплатформенное приложение (Windows, macOS, Linux), которое будет управлять VPN-соединением. Для этого мы выбрали связку Electron и TypeScript.

Почему Electron и TypeScript?

Прежде чем открывать терминал, давайте разберемся, почему мы выбрали именно эти инструменты.

Electron — это фреймворк, который позволяет создавать нативные приложения для рабочего стола с использованием веб-технологий (HTML, CSS, JavaScript). По сути, ваше приложение — это специальная версия браузера Chromium, управляемая Node.js.

TypeScript — это надстройка над JavaScript, добавляющая статическую типизацию. При разработке VPN-клиента нам придется работать со строгими структурами данных (конфигурации сети, статусы подключения, ошибки). TypeScript не позволит нам «выстрелить себе в ногу», перепутав строку с числом или обратившись к несуществующему свойству объекта.

Архитектура Electron

Понимание архитектуры критически важно. Electron-приложение состоит из двух типов процессов:

  • Main Process (Основной процесс): Это «мозг» приложения. Он работает на Node.js, имеет полный доступ к операционной системе (файлы, сеть, запуск других процессов). Именно здесь мы будем инициировать VPN-соединение.
  • Renderer Process (Процесс отрисовки): Это «лицо» приложения. Это обычное веб-окно (Chromium), которое показывает интерфейс. По соображениям безопасности, оно не должно иметь прямого доступа к системе.
  • !Взаимодействие основного процесса и процесса отрисовки через IPC

    Шаг 1: Проверка требований

    Для начала убедитесь, что у вас установлены Node.js и пакетный менеджер npm (обычно устанавливается вместе с Node.js). Откройте терминал и введите:

    Если вы видите версии (например, v18.x.x и 9.x.x), можно продолжать. Если нет — скачайте LTS-версию с официального сайта Node.js.

    Шаг 2: Инициализация проекта

    Создадим папку для нашего VPN-клиента и инициализируем package.json. Этот файл будет хранить информацию о проекте и его зависимостях.

    Флаг -y автоматически заполняет все поля значениями по умолчанию. Позже вы сможете изменить их в файле package.json.

    Шаг 3: Установка зависимостей

    Нам понадобятся сам Electron, TypeScript и типы для них. Мы установим их как devDependencies (зависимости для разработки), так как в финальной сборке они будут скомпилированы.

    Выполните команду:

    * electron: сам фреймворк. * typescript: компилятор TS. * ts-node: позволяет запускать TS-файлы напрямую (удобно для разработки). * @types/...: описания типов, чтобы TypeScript понимал, какие методы есть у Node.js и Electron.

    Шаг 4: Настройка TypeScript

    Чтобы TypeScript знал, как компилировать наш код, нужно создать файл конфигурации tsconfig.json. Создайте его в корне проекта со следующим содержимым:

    Разберем ключевые настройки: * outDir: куда складывать скомпилированные JS-файлы (папка dist). * rootDir: где лежит наш исходный код (папка src). * strict: включает строгий режим проверки типов (максимальная безопасность).

    Шаг 5: Создание структуры проекта

    Создайте папку src в корне проекта. Внутри неё мы разделим код на логические части:

    Написание Main Process

    Откройте src/main.ts. Это сердце нашего приложения. Напишем код, который создает окно:

    Обратите внимание на webPreferences. Мы намеренно отключаем nodeIntegration и включаем contextIsolation. Это стандарт безопасности: веб-страница не должна иметь прямого доступа к require('fs') или require('child_process'), иначе любой XSS-уязвимости будет достаточно, чтобы взломать компьютер пользователя.

    Шаг 6: Выбор UI-фреймворка

    Теперь нам нужно решить, на чем писать интерфейс. Electron может отображать любой HTML/CSS. Однако для современного VPN-клиента нам нужны реактивность, состояние (подключено/отключено, скорость, выбранная страна) и компоненты.

    Рассмотрим варианты:

  • Vanilla HTML/JS: Просто, быстро, но сложно поддерживать при росте приложения.
  • React: Самый популярный, огромная экосистема, отлично подходит для управления сложным состоянием.
  • Vue: Проще в освоении, чем React, но чуть менее популярен в корпоративной среде Electron.
  • Наш выбор: React. Почему? Потому что VPN-клиент имеет много состояний (инициализация, рукопожатие, подключение, ошибка, разрыв). Модель компонентов React идеально подходит для переключения этих состояний интерфейса.

    Примечание: В этой статье мы создадим базовый HTML-файл для проверки работоспособности Electron. Полноценную интеграцию React и настройку сборщика (Vite или Webpack) мы выполним в следующей статье, чтобы не перегружать текущий урок.

    Создайте файл index.html в корне проекта (рядом с package.json, не в src, так как пока мы не настроили копирование ассетов):

    Шаг 7: Запуск приложения

    Осталось научить package.json запускать наш проект. Нам нужно сначала скомпилировать TypeScript в JavaScript, а затем запустить Electron.

    Откройте package.json и найдите секцию scripts. Замените её на:

    Важно: Мы изменили поле "main" на "dist/main.js", так как Electron должен запускать уже скомпилированный файл, а не исходник на TS.

    Теперь в терминале выполните:

    Если вы всё сделали правильно, откроется окно с заголовком «VPN Client Initialization». Поздравляю! Вы создали каркас своего будущего VPN-сервиса.

    В следующей статье мы интегрируем React, настроим горячую перезагрузку (Hot Module Replacement) и начнем проектировать интерфейс.

    2. Архитектура приложения: настройка IPC для обмена данными между Main и Renderer процессами

    Архитектура приложения: настройка IPC для обмена данными между Main и Renderer процессами

    В предыдущей статье мы инициализировали проект, настроили TypeScript и создали базовую структуру файлов. Теперь перед нами стоит одна из самых важных архитектурных задач в разработке на Electron: организация безопасного общения между интерфейсом и системной логикой.

    Наш VPN-клиент должен уметь выполнять системные команды (например, запускать OpenVPN или WireGuard процесс), но кнопка «Подключиться» находится в веб-интерфейсе, который, по соображениям безопасности, изолирован от системы. Как нажать кнопку в браузере и запустить процесс в операционной системе? Ответ — IPC (Inter-Process Communication).

    Понимание модели процессов

    Давайте вспомним архитектуру Electron. У нас есть два мира:

  • Main Process (Node.js): Имеет полный доступ к OS. Здесь мы будем управлять VPN-туннелем.
  • Renderer Process (Chromium): Отображает UI. Здесь работает React (который мы подключим позже) и здесь пользователь нажимает кнопки.
  • Если мы позволим Renderer-процессу напрямую вызывать Node.js команды (включив nodeIntegration: true), любая XSS-уязвимость на странице позволит злоумышленнику выполнить произвольный код на компьютере пользователя. Поэтому мы используем Context Isolation (изоляцию контекста).

    !Схема безопасного обмена данными через IPC мост

    Роль Preload Script

    Скрипт предзагрузки (preload.ts) — это специальный файл, который выполняется перед загрузкой веб-страницы, но после инициализации окна. Он имеет доступ к Node.js API, но разделяет глобальный объект window с веб-страницей.

    Наша задача — создать «безопасный мост». Мы не будем давать интерфейсу доступ ко всему модулю ipcRenderer. Вместо этого мы создадим конкретный API, например, функцию connectVpn(), и пробросим только её.

    Шаг 1: Определение типов (TypeScript)

    Чтобы TypeScript помогал нам и в Main, и в Renderer процессах, создадим общий интерфейс для нашего API. Это гарантирует, что мы не ошибемся в названиях методов или типах передаваемых данных.

    Создайте папку src/shared и файл types.ts:

    Шаг 2: Настройка Preload Script

    Теперь реализуем этот интерфейс в preload.ts, используя contextBridge. Мы будем использовать два метода общения:

  • Invoke / Handle: Двусторонний обмен (запрос-ответ). Идеально для команд «Подключиться» (ждем результат).
  • Send / On: Односторонний обмен или события. Идеально для уведомлений «Соединение разорвано».
  • Откройте src/preload.ts:

    Теперь в глобальном объекте window появится свойство vpnApi. Но TypeScript об этом не знает. Давайте расширим глобальные типы.

    Создайте файл src/renderer.d.ts (или добавьте в существующий файл деклараций):

    Шаг 3: Реализация логики в Main Process

    Теперь нам нужно «поймать» эти вызовы в основном процессе. Для этого используется модуль ipcMain.

    Откройте src/main.ts и добавьте обработчики событий. Пока мы будем использовать заглушки (mock-данные), так как реальную логику OpenVPN мы напишем позже.

    Не забудьте обновить index.html, добавив элементы управления:

    Запуск и проверка

    Теперь скомпилируйте проект и запустите его:

  • Нажмите кнопку Connect VPN.
  • В консоли (откройте DevTools через Ctrl+Shift+I или Cmd+Option+I) вы увидите логи начала подключения.
  • Статус сменится на connecting.
  • Через 2 секунды статус станет connected.
  • Это означает, что цикл Renderer -> Main -> Renderer работает корректно.

    Почему это безопасно?

    Мы использовали паттерн Context Bridge. В глобальную область видимости окна (window) попал только объект vpnApi с тремя методами. Злоумышленник, даже если он сможет внедрить JS-код на страницу, сможет только вызвать connect или disconnect. Он не сможет:

    * Читать файлы с диска (fs.readFile). * Запускать произвольные процессы (child_process.exec). * Менять настройки системы.

    Это фундаментальный принцип безопасности современных Desktop-приложений.

    В следующей статье мы заменим наш простой HTML/JS интерфейс на полноценное React-приложение и настроим сборку через Webpack или Vite для удобной разработки.