Десктопное приложение на ElectronJS

Давно уже было желание «пощупать» Electron. Правда желание это постоянно ставилось под сомнение, что связано с использованием Skype в работе, тормоза которого, как бы твердят «нужно вырвать руки тем, кто использует WebView на десктопе». И вот, выпал случай, ну или рабочая необходимость, приобщиться к тем, кому стоило бы «вырвать руки».

Исходники: https://github.com/siarheidudko/itpharma-vnc-viewer
Установщик (Windows 32bit): https://github.com/siarheidudko/itpharma-vnc-viewer/releases/download/1.0.3/ITPharma.VNC.Viewer.v1.0.3.Setup.exe
Портативная версия: https://github.com/siarheidudko/itpharma-vnc-viewer/releases/download/1.0.3/win-32bit.zip

Итак, у нас, условно, имеется несколько сотен ПК на Linux и Windows. На всех них используется удаленный доступ по протоколу VNC, в качестве унификации. Сам протокол, естественно, закрыт VPN и Firewall. На все эти машины необходим удаленный доступ.
Т.е., в принципе, его можно обеспечить любым VNC-клиентом. Но проблема в том, что не было найдено в свободном доступе ни одного User Friendly интерфейса/оболочки для хранения настроек подключений. У нас же есть необходимость предоставить доступ в т.ч. людям довольно далеким от администрирования, т.е. так чтобы «нажал кнопочку и увидел экран».
При этом, соединения должны где-то храниться и как-то синхронизироваться, т.к. периодически парк ПК обновляется. Ну а в связи с тем, что основной мой стек это все-таки веб-технологии, было принято решение как-нибудь в выходной заняться изучением Electron, и, в качестве изучения, написать таковую оболочку.

Перед началом разработки нужно понимать, что цель написать в WebView весь клиент VNC не ставится (хотя такого рода проекты на базе websocket и существуют). Потому необходимо было выбрать подходящий клиент. Пошарив по «этим вашим интернетам», исходя из удобства управления (например, в TightVNC настройки каждого соединения хранятся в файле, что не добавляет адекватности к разработке), чистоты лицензирования (ну нет у меня желания связываться с двойным лицензированием или проприетарными лицензиями, к тому же вам теперь использовать данное ПО проще) выбор пал на UltraVNC.

Изучение API UltraVNC Viewer долго времени не заняло, т.к. это простые аргументы при запуске исполняемого файла из командной строки. Собственно, под Linux писать данный софт пока задачи не стоит (а при необходимости можно в сборку по Linux включить другой исполняемый файл/VNC Client с минимальным допилом), поэтому UltraVNC оказался вполне достаточен.

Для начала работы с Electron я скачал пример приложения, его и переделаю.

Удивительно, но все запустилось из коробки.

Как и при любой разработке UI, необходимо для начала составить макет. Верхняя часть будет представлять из себя Меню, состоящее из двух разделов:
Настройки (для хранения различных настроек) и Прочее (для всего остального). Приложение нельзя назвать слишком объемным, так что этого будет достаточно.
Под меню будет располагаться основной интерфейс программы. Который, в свою очередь, будет состоять из бегунка групп, строки поиска и набора подключений. Итоговый результат видно на скриншоте ниже.

VNC Viewer

Первое что нужно переделать это preload.js (скрипт, который исполняется перед загрузкой приложения). Для хранения данных изначально хотел использовать sqlite, но потом вспомнил о своем решении на базе Redux-Cluster которое вполне может закрыть эту задачу, без лишних «танцев с бубном». Более того, на тот момент, я с Electron был знаком на уровне «знаю что он такой есть», а данная библиотека позволяет не только создавать дамп данных на диске и загружаться с него, но и связывать данные в нескольких процессах на базе IPC/Socket/Websocket (в последнем случае требуется еще одна библиотека Redux-Cluster-Ws).
Хранилище Redux было огранизовано по принципу:

, где

  • — names — набор пар sha1(groupname + name):name для соединения или sha1(groupname) для группы
  • — connections — набор пар sha1(groupname + name): Объект соединения в виде
  • — host — адрес ПК, на котором установлен VNC-сервер (может быть IP или доменным именем)
  • — port — порт VNC-сервера
  • — password — пароль доступа к VNC-серверу
  • — groups — набор пар sha1(groupname): массив sha1(groupname + name) соединений включенных в группу
  • — settings — настройки синхронизации и соединения
  • Для проброса хранилища Redux, можно просто добавить его к объекту window, после чего он будет доступен по ссылке из index.html

    Для логгирования ошибок, в этом же скрипте открываю поток записи

    Как видно, для определения директории использована не __dirname, а ссылка rocess.resourcesPath. Это связано с тем, что __dirname в Electron ссылается на *.asar архив, недоступный для записи. Пока читал чуть подробнее про эти *.asar файлы понял, что примерно по тому же принципу у меня написана библиотека docdb (проект разрабатывается, когда присутствует время).
    А вот rocess.resourcesPath вернет ссылку на директорию ./resources/ в каталоге с программой. Эту же директорию нужно использовать для настройки резервного копирования redux-cluster, хранения UltraVNC Viewer, connections.json.

    Чтобы однозначно определить время ошибки в красивом виде, заберу из одного из проектов функцию штампа времени

    Добавляю её в логгирование, а также добавляю перенос строки в формате CR LF:

    Для унификации процесса обновления подключений в redux было написано 2 функции:
    — setConnections — загрука настроек подключений в Redux с анализом пользовательского ввода
    — reloadConnections — загрузка данных из файла/по ссылке в зависимости от настроек и передача их в функцию

    Эту же функцию(reloadConnections) я подключу при старте приложения, но ошибки выводить пользователю не буду (только в лог).

    Основная функция приложения — это запуск дочернего процесса с заданными в настройках параметрами:

    Для получения событий от main.js необходимо использовать IPC, т.к. это разные процессы. В Electron уже встроен функционал IPC, для чего в main.js необходимо подключить

    Отправка событий из main.js осуществляется методом:

    Теперь добавляю слушатели событий в preload.js.

  • — открытие информации Меню->Прочее->О программе
  • — открытие окна настроек синхронизации
  • Функция отрисовки интерфейса при открытии окна настроек синхронизации

  • — открытие окна настроек соединения
  • Функция отрисовки интерфейса при открытии окна настроек соединения

  • — событие обновления соединений из удаленного источника (файл/get-запроса, исходя из настроек)
  • — запуск процесса с надстройками, но без передачи непосредственного подключения
  • Для реализации Меню и кастомизации приложения, внесу корректировки в main.js

    Меню стало выглядеть так

    VNC Viewer

    Осталось реализовать User Interface. Изначально была идея написать UI на основе React, но, честно говоря, стало лень, т.к. рендеринг React в браузере не отличается производительностью и не позволяет использовать большую часть модулей. Собирать приложение через webpack — вносит определенный дискомфорт в достаточно простую разработку. Ну и принцип «чем проще логика, тем стабильней её работа» никто не отменял.
    Потому решил «нарисовать» UI на чистом JS с примесью jquery и Bootstrap 4 (который все равно хочет jquery), для чего нужно подключить в index.html три скрипта и css:


    Собственные настройки css подключил отдельным файлом

    В данном файле, собственно, настроен кастомный scrollbar и заблокировано выделение теста (как в нативных приложениях)

    А для того, чтобы jquery был доступен в preload.js (естественно, нужно понимать, что доступен он станет только после того как index.html будет загружен полностью) добавлю строчку

    Блок «О программе» по умолчанию скрыт, отрисовка данных происходит перед открытием данного блока.

    Блок «Настройки соединения» по умолчанию скрыт, отрисовка данных происходит перед открытием данного блока.

    Блок «Настройки синхронизации» по умолчанию скрыт, отрисовка данных происходит перед открытием данного блока.

    Блок для вывода ошибок приложения по умолчанию скрыт, отрисовка данных происходит перед открытием данного блока.

    Основное тело программы, в верхней части которого располагается наименование группы и стрелки переключения между группами. В средней части располагается строка поиска по группе. Под ней располагаются кнопки подключений. Отрисовка данных происходит при запуске приложения, а также при изменении в хранилище redux данных связанных с данным блоком.

    Осталось вдохнуть логику в UI. По-умолчанию, при запуске, будет выбрана для отображения группа с идентификатором 0

    Создаю функцию перерисовки блока подключений, в зависимости от идентификатора группы и строки поиска

    Функция отрисовки всего UI (включая блокировку кнопок):

    Запускаю её при старте приложения

    Подписываюсь на изменения redux, для определения изменившихся данных буду хранить хэш интересующих данных (занимает меньше памяти, по сравнению с хранением объекта предыдущей версии).

    Описываю логику строки поиска (управляющие клавиши Escape и Enter)

    Кнопки переключения между группами

    Клик на кнопку подтверждения настроек синхронизации

    Клик на кнопку подтверждения настроек соединения

    В зависимости от выбранного режима, блокировка дополнительных настроек соединения

    Вот собственно и весь проект. Как показала практика, приложения на Electron достаточно тяжелые, но вполне жизнеспособные в современных реалиях. Правда, могут достаточно долго загружаться, если у вас какой-нибудь Avast.