Логотип Electron
Логотип Electron
Фреймворк Electron разработан в GitHub и носил раньше название Atom shell. Пожалуй, самое известное приложение, написанное с его помощью, — текстовый редактор Atom, а еще — клиент Slack для настольных компьютеров, которым очень активно пользуются в нашей редакции. Из других интересных проектов — мультипротокольный клиент мгновенных сообщений Franz, Git-клиент GitKraken, GUI-клиент к хорошо известной Node.js-разработчикам утилите Yeoman yeoman-app и даже Microsoft Visual Studio Code.

Electron позволяет создавать кросс-платформенные приложения для настольных компьютеров с использованием чистого JavaScript. Поддерживаются основные операционные системы: macOS, Linux, Windows. Он сочетает в себе лучшие стороны Node.js и Chromium, но при этом ориентирован на разработку десктопных приложений.

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

  • если требуется кросс-платформенное приложение с малыми затратами на разработку и сопровождение;
  • если есть готовое веб-приложение или веб-компонент, который необходимо распространять в виде законченного приложения для настольных компьютеров;
  • если веб-приложению требуются права, выходящие за рамки ограничений системы безопасности браузеров;
  • если веб-приложение должно требовать большей интеграции с операционной системой и доступа к API, невозможного из браузера.

WWW


Читаем доки здесь. Перевод на русский язык здесь.
 

Что будем делать

Мы возьмем готовый виджет, «обернем» его в приложение Electron и добавим стандартные элементы интерфейса операционной системы, такие как иконка в области уведомлений, стандартные системные диалоги, вызов внешнего приложения, главное меню, горячие клавиши.

Для наших экспериментов возьмем готовый виджет платформы SoundCloud, популярной площадки для публикации музыкальных композиций и другого аудиоконтента. Этот виджет обладает несложным API, да и можно будет немного поразвлечься прослушиванием музыки. Мы превратим его в простой проигрыватель для настольного компьютера с привычными элементами управления.

 

Подготовка

 

Иконки

Для приложения нам понадобится несколько иконок. Я брал их из набора ie_Bright с сайта iconfinder.com; можно взять другие на свой вкус. Для изображений, используемых для иконки в области уведомлений под Windows, рекомендуются файлы .ico, но мы для простоты возьмем только PNG-файлы.

Имя файла Назначение Изображение
player.png Иконка приложения
player.png
player.png
play.png Начать проигрывание
play.png
play.png
pause.png Приостановить
pause.png
pause.png
prev.png Предыдущая композиция
prev.png
prev.png
next.png Следующая композиция
next.png
next.png

Поместим иконки в подкаталог assets/img/ проекта.

 

Node.js

Предполагается, что на компьютере установлен Node.js версии не ниже 6.6; загрузить ее можно здесь.

$ node -v
v6.6.0
 

Операционная система

Примеры подготовлены для выполнения на компьютере, работающем под управлением ОС Linux и macOS.

 

Минимальное приложение Electron

Начнем с создания минимального приложения Electron. Для этого создадим каталог проекта, например electron-demo, и перейдем в него:

$ mkdir electron-demo
$ cd electron-demo

Добавим в наш проект два файла — минимальный index.html, который будет основным интерфейсом нашего приложения:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
</head>
<body>
    Welcome to Electron!
</body>
</html>

и index.js со следующим содержимым:

const path = require('path')
const electron = require('electron')
const {app, BrowserWindow} = electron

// Ссылка на объект окна; если ее не будет, окно закроется автоматически, когда сборщик мусора высвободит память из-под объекта JavaScript
let win = null

// Путь к иконкам приложения
const iconBasePath = path.join(__dirname, 'assets', 'img')


// Создание окна браузера и обработка его событий
function createWindow () {
    const APP_ICON_NAME = 'player.png'
    const iconPath = path.join(iconBasePath, APP_ICON_NAME)

    // Задаем параметры для создания окна браузера
    let options = {
        width: 800, height: 550,
        title: 'Electron SoundCloud Player', // Не будет работать, если в `.html` есть тег `title`
        icon: iconPath,
        webPreferences: {
            devTools: true, // По умолчанию — `true` для показа DevTools
        }
    }
    // Создаем окно браузера
    win = new BrowserWindow(options)

    // и загружаем index.html приложения
    win.loadURL(`file://${__dirname}/index.html`)

    // Событие во время закрытия окна
    win.on('closed', (e) => {
        // Это то время, когда нужно удалить соответствующий объект
        // Убираем ссылку на объект окна. В многооконных приложениях окна обычно будут храниться в массиве
        win = null
    })

    let webContents = win.webContents

    // Открываем DevTools
    webContents.openDevTools()
}

// Этот событие произойдет, когда Electron завершит
// инициализацию и будет готов к созданию окон браузера
// Многие методы API могут использоваться только после этого события
app.on('ready', createWindow);

// Выйти из приложения, когда все окна закрыты
app.on('window-all-closed', () => {
    // На macOS приложения и их меню остаются активными до тех пор, пока
    // пользователь не выйдет из них явно с помощью Cmd + Q
    if (process.platform !== 'darwin') {
        app.quit()
    }
})

app.on('activate', () => {
    // На macOS, как правило, при клике на иконке в доке (и если нет других
    // открытых окон), окно в приложении пересоздается
    if (win === null) {
        createWindow()
    }
})

Инициализируем файл package.json проекта менеджера пакетов npm, ответив на необходимые вопросы.

$ npm init

Нужно проверить, что в package.json, получившемся в результате, значение свойства main равно main.js (соответствует значению, введенному при запросе entry point во время выполнения команды npm init), в противном случае его необходимо скорректировать вручную, чтобы оно соответствовало имени главного .js-файла проекта.

Electron можно установить только для нашего проекта:

$ npm install --save electron

или глобально:

$ npm install -g electron

Пора запускать! Если Electron был установлен локально, это делается следующей строкой (если он был установлен глобально, путь указывать необязательно):

$ ./node_modules/.bin/electron .

Через несколько мгновений откроется окно нашего первого приложения.

Первое приложение
Первое приложение

Обрати внимание, что сразу же открыто привычное окно DevTools. Заголовок и текст окна соответствуют заданным; кроме того, у приложения есть типовое главное меню.

 

Виджет SoundCloud

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

 

API виджета

Методы виджета

Из методов API виджета для управления проигрыванием мы будем использовать следующие:

  • play — начать проигрывание композиции;
  • pause — приостановить проигрывание композиции (пауза);
  • toggle — переключить проигрывание / приостановка;
  • prev — перейти к предыдущей композиции (для списка);
  • next — перейти к следующей композиции (для списка);
  • bind — добавить обработчик события виджета.

В числе прочих методов: skip, load, seekTo, setVolume, unbind.

События виджета

События виджета делятся на две группы: аудиособытия и события пользовательского интерфейса.

Аудиособытия связаны с проигрываемой композицией и уведомляют об изменениях ее состояния в проигрывателе, передавая объект с информацией о текущей позиции в проигрываемом файле или прогрессе загрузки (relativePosition, loadProgress, currentPosition).
События пользовательского интерфейса виджета уведомляют о действиях пользователя, не связанных напрямую с проигрыванием композиции.

Мы используем следующие события:

  • READY — виджет загрузил данные и готов принимать внешние вызовы;
  • PLAY — начато проигрывание композиции;
  • PAUSE — проигрывание композиции приостановлено.

Остальные события: LOAD_PROGRESS, PLAY_PROGRESS, FINISH, SEEK, CLICK_DOWNLOAD, OPEN_SHARE_PANEL, ERROR.

Дополнительно можно получить информацию о текущем состоянии виджета с помощью методов getVolume, getDuration, getPosition, getSounds, getCurrentSound, getCurrentSoundIndex, isPaused. Информация возвращается в callback-функции. Из них нам понадобится метод getCurrentSound, возвращающий информацию о текущей композиции.

 

Добавление виджета на страницу

Для того чтобы отобразить на нашей странице виджет SoundCloud, внутри элемента <body> добавим элемент <iframe>, в котором загрузится сам виджет:

<!-- виджет SoundCloud -->
<iframe id="sc-widget" width="100%" height="450" scrolling="no" frameborder="no" src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/playlists/178009618&amp;auto_play=false&amp;hide_related=false&amp;show_comments=true&amp;show_user=true&amp;show_reposts=false&amp;visual=true"></iframe>

Полный список параметров виджета приведен здесь: SoundCloud Player Widget — Customize Parameters (для предыдущей версии, использующей Flash).

Для выбора композиции или их списка и настройки визуального представления виджета можно нажать кнопку Share на понравившемся списке композиций (если выбрана отдельная композиция, то будет невозможно перемещаться к следующей/предыдущей композиции), выбрать закладку Embed и скопировать предлагаемый код; установив галочку More Options, можно настроить несколько дополнительных параметров.

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

 

Инициализация API виджета SoundCloud

Для доступа к API виджета необходимо добавить в тег <head> загрузку следующего сценария:

<script src="https://w.soundcloud.com/player/api.js" type="text/javascript"></script>

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

<script src="./soundcloud.js" type="text/javascript"></script>

А тег <body> дополним кнопками управления проигрыванием:

<div>
  <button id="action-widget-play">Play</button>
  <button id="action-widget-pause">Pause</button>
  <button id="action-widget-toggle">Toggle</button>
  <button id="action-widget-prev">Prev</button>
  <button id="action-widget-next">Next</button>
</div>

Создадим файл soundcloud.js, добавив в него функцию, которая будет выполняться при загрузке окна браузера:

let initSC = function() {
  const {ipcRenderer} = require('electron')

  let widgetIframe = document.getElementById('sc-widget'), // IFrame виджета
      widget       = SC.Widget(widgetIframe)               // Сам виджет
  console.log('widget:', widget)
}

И собственно вызов этой функции по событию window onload:

// Вызвать основную функцию по событию onload объекта window
window.addEventListener('load', function load(event){
    // Удалить listener, так как он больше не нужен
    window.removeEventListener('load', load, false)
    // Вызов основного метода
    initSC()
}, false)

Теперь при запуске приложения в консоль должен быть выведен объект widget.

 

Методы и события API виджета SoundCloud

Привяжем методы виджета, предназначенные для управления проигрыванием композиции, напрямую к кнопкам управления на странице (в функции initSC):

;[ 'play', 'pause', 'toggle', 'prev', 'next' ].forEach(methodName =>
    document.getElementById('action-widget-'+methodName).addEventListener('click', () =>
        widget[methodName]()
    )
)

Теперь кнопки на форме начнут управлять виджетом и информация о событиях будет выводиться в консоль.

Также добавим вывод в консоль уведомлений о событиях виджета:

;[ 'LOAD_PROGRESS', /* 'PLAY_PROGRESS', */ 'PLAY', 'PAUSE', 'FINISH', 'SEEK', 'READY', 'CLICK_DOWNLOAD', 'OPEN_SHARE_PANEL', 'ERROR' ].forEach(eventName =>
    widget.bind(SC.Widget.Events[eventName], evt =>
        console.log(`SC.Widget.Events.${eventName}`, evt)
    )
)

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

widget.bind(SC.Widget.Events.READY, () => {
    console.log('SC.Widget.Events.READY')
    widget.bind(SC.Widget.Events.PLAY, () => {
        console.log('SC.Widget.Events.PLAY')

        // Получить информацию о проигрываемой композиции
        widget.getCurrentSound(currentSound => {
          console.log('currentSound:' , currentSound)
          let title  = currentSound.title,
              author = currentSound.user.username,
              url    = currentSound.permalink_url
        })

        // Вывести в консоль доступную информацию о текущем состоянии виджета
        ;[ 'getVolume', 'getDuration', 'getPosition', 'getSounds', 'getCurrentSound', 'getCurrentSoundIndex', 'isPaused' ].forEach(methodName => {
            widget[methodName](value  => console.log(`widget.${methodName}: `, value) )
        })
    })
})
 

Иконка в области уведомлений

 

Добавление иконки в область уведомлений

Одна из функций, привычных пользователю компьютера, но невозможная для веб-страницы, — это иконка с выпадающим меню в области уведомлений. Добавим в файл main.js:

const {Menu, Tray} = electron

// Объект для вывода иконки в области уведомлений
let trayObject = null

// Инициализируем иконку по готовности приложения
app.on('ready', () => {
    // Под Windows рекомендуется использовать .ico для лучшего качества
    const iconName = 'pause.png'
    const iconPath = path.join(iconBasePath, iconName)
    // Создадим иконку в области уведомлений
    trayObject = new Tray(iconPath)

    // Создадим выпадающее меню
    const contextMenu = Menu.buildFromTemplate([
        {
            label: 'Show',
            click() {
                win.show()
                app.focus()
            }
        }, {
            label: 'Play',
            click() { console.log('contextMenu: Play') }
        }, {
            label: 'Pause',
            click() { console.log('contextMenu: Pause') }
        }, {
            type: 'separator'  // Разделитель
        }, {
            role: 'quit'       // Стандартная роль пункта меню — выход из приложения
        }
    ])
    // И привяжем выпадающее меню к иконке
    trayObject.setContextMenu(contextMenu)
    // Зададим всплывающую подсказку
    trayObject.setToolTip('This is my application.')

    // Добавим обработку клика на самой иконке
    trayObject.on('click', (event, bounds) => {
        win.show()
        app.focus()
    })
})

// Уберем иконку, когда закрыты все окна
app.on('window-all-closed', () => {
    if (trayObject) {
        trayObject.destroy()
        trayObject = null;
    }
})

Под Linux отображение иконки в области уведомлений имеет свои особенности и зависит от конкретного дистрибутива. Подробнее о работе с областью уведомлений — здесь.

 

Модификация главного меню

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

const {MenuItem} = electron

// Когда приложение готово
app.on('ready', () => {
    // Создадим объект типа MenuItem
    const menuItem = new MenuItem ({
        label: 'About',
        click() {
            dialog.showMessageBox(null, {
                type: 'info',
                title: 'About',
                message: 'Electron SoundCloud Demo',
                buttons: [ 'OK' ],
            })
        }
    })
    // Получим текущее меню приложения
    let menu = Menu.getApplicationMenu();
    menu.append(menuItem)
})
 

Контекстные всплывающие меню

Кроме главного меню приложения и меню иконки в области уведомлений, можно создавать и контекстные всплывающие меню, открывающиеся при нажатии на правую кнопку мыши на веб-странице по событию contextmenu объекта window.

Дополнительная информация по объектам Menu и MenuItem с примерами доступна по ссылкам: Menu и MenuItem.

 

Процессы и обмен данными между ними

Пора познакомиться с архитектурой приложения Electron. В нем выполняется два типа процессов: Main Process и Renderer Process. Main Process — главный процесс, который выполняет сценарий, указанный в поле main файла package.json (по умолчанию равный main.js). Главный процесс создает объекты типа BrowserWindow, отображающие веб-страницы, из которых строится интерфейс приложения. Каждая веб-страница выполняется в отдельном процессе, называемом Renderer Process.

Поскольку окно браузера выполняется в Renderer Process, а основной процесс, взаимодействующий с областью уведомлений, — в Main Process, напрямую обращаться к одному из другого невозможно.

Один из методов организации взаимодействия между процессами в Electron — объекты ipcMain и ipcRenderer, используемые в основном процессе Main Process и процессе (или процессах) Renderer Process, отрисовывающем веб-страницу. С помощью их методов send и sendSync процессы могут обмениваться синхронными и асинхронными сообщениями. Обработчику сообщения в процессе-получателе первым параметром передается объект event с двумя свойствами, returnValue и event.sender. С помощью первого из них обработчик синхронного сообщения может вернуть результат отправителю; второй хранит отправителя сообщения, и асинхронный обработчик может вернуть результат, отправив сообщение с помощью event.sender.send().

Другой вариант организовать взаимодействие между процессами — модуль remote, который позволяет обмениваться данными в стиле вызовов RPC, вызывая методы другого процесса. В main.js, в описании выпадающего меню иконки области уведомлений (вызов метода Menu.buildFromTemplate()), изменим обработчики нажатия на пункты меню с вывода в консоль на отправку сообщений следующим образом:

...
}, {
    label: 'Play',
    // click() { console.log('contextMenu: Play') }
    click() { win && win.send('do-play') }
}, {
    label: 'Pause',
    // click() { console.log('contextMenu: Pause') }
    click() { win && win.send('do-pause') }
}, {
...

В файле soundcloud.js подключим объекты ipcRenderer из модуля electron первой строкой внутри основной функции initSC:

const { ipcRenderer } = require('electron')

И добавим в этом же файле, в конце обработчика события SC.Widget.Events.READY, обработчики этих событий:

// При получении сообщений от главного процесса вызвать соответствующий метод виджета
ipcRenderer.on('do-play',  () => widget.play() )
ipcRenderer.on('do-pause', () => widget.pause() )

Теперь при получении сообщений do-play и do-pause будут вызываться соответствующие методы виджета и мы можем управлять виджетом из области уведомлений.

Добавим передачу уведомлений в обратную сторону, о событиях виджета. Добавим в конце callback-функции widget.getCurrentSound строку, которая отправит сообщение sc-play главному процессу в момент начала новой композиции с информацией о ее авторе и названии:

ipcRenderer.send('sc-play', author, title)

В конце обработчика события SC.Widget.Events.READY добавим обработку события SC.Widget.Events.PAUSE, при его получении уведомив об этом главный процесс сообщением sc-pause.

widget.bind(SC.Widget.Events.PAUSE, (evt) => {
    // Отправить IPC-сообщение главному процессу о приостановке проигрывания
    ipcRenderer.send('sc-pause')
});

В main.js добавим функцию, изменяющую иконку для области уведомлений:

function setTrayImage(iconName) {
    const iconPath = path.join(iconBasePath, iconName)
    // Установим иконку
    trayObject.setImage(iconPath)
}

и обработчики наших событий sc-play и sc-pause:

const { ipcMain } = electron

ipcMain.on('sc-play', (event, author, title) => {
    console.log(`sc-play: author: ${author}, title: ${title}`)
    // Сменим изображение
    setTrayImage('play.png')
    // Зададим всплывающую подсказку
    trayObject.setToolTip(`<div>${author} - ${title}</div>`)
})

ipcMain.on('sc-pause', (event) => {
    console.log('sc-pause')
    // Сменим изображение
    setTrayImage('pause.png')
    // Зададим всплывающую подсказку
    trayObject.setToolTip('This is my application.')
})
 

Всплывающие уведомления

Создание всплывающих уведомлений в Electron реализуется через обычный Notifications API браузера. Единственное отличие — у пользователя не будет запрашиваться разрешение о выводе уведомлений.

Для отображения уведомления необходимо создать объект типа Notification, вызвав его конструктор с текстом заголовка и параметрами, в числе которых иконка, текст уведомления и флаг разрешения звукового оповещения при отображении уведомления. Так как мы проигрываем музыкальную композицию, звук нужно будет отключить. Создадим файл notification.js, который будет выводить само уведомление и вызывать callback при клике на уведомлении:

const NOTIFICATION_ICON_PATH = './assets/img/player.png';
const NOTIFICATION_TIMEOUT = 3000

var spawnNotification = function(title, body, callback) {
    // Обезопасим callback
    callback = typeof callback === 'function' ? callback : function() {};

    // Определяем параметры уведомления
    let options = {
        body:   body,                   // Текст уведомления
        icon:   NOTIFICATION_ICON_PATH, // Путь к файлу с иконкой
        silent: true                    // Запрет звукового оповещения
    }

    // Создаем само уведомление
    let n = new Notification(title, options);

    // Задаем обработчик клика на уведомлении
    n.onclick = () => {
        console.log('Notification clicked')
        callback();
    }

    // Устанавливаем тайм-аут для скрытия уведомления
    setTimeout(n.close.bind(n), NOTIFICATION_TIMEOUT);
}

module.exports = { spawnNotification }

Подключим этот модуль, добавив в первые строки функции initSC файла soundcloud.js:

const { spawnNotification } = require('./notification')

И вызов этой функции внутри обработчика события SC.Widget.Events.PLAY виджета; ее callback передаст сообщение sc-open главному процессу, уведомив его о том, что пользователь кликнул на уведомлении:

// Показать уведомление с автором и названием композиции
spawnNotification(author, title, () => {
    // Callback вызывается, если пользователь кликнул на уведомлении
    // Уведомим главный процесс, передав URL с информацией о композиции
    ipcRenderer.send('sc-open', url)
});
 

Системные диалоги и запуск внешнего приложения

В основном процессе при нажатии на уведомление мы будем открывать в браузере по умолчанию страницу с текущей композицией.
Для этого используем функцию shell.openExternal(). Она выполняет действие по умолчанию для типа данных, переданных в качестве параметра. Если этой функции будет передан URL, откроется окно браузера по умолчанию, который загрузит страницу, соответствующую этому URL.

Необходимо отметить, что источником данных в этом случае служит сторонний виджет, расположенный на стороннем сайте, и нельзя полагаться на то, что в поле permalink_url текущей композиции будет именно URL, а не, например, скрипт с командой на удаление всего содержимого диска.

Для демонстрационного примера мы ограничимся запросом пользователя на разрешение выполнения данного действия. Отображение системных диалогов реализуется с помощью метода showMessageBox() объекта dialog. Всего доступно четыре диалоговых окна; кроме showMessageBox(), есть еще showOpenDialog и showSaveDialog для стандартных диалоговых окон открытия и сохранения файла и showErrorBox. Документация по объекту dialog приведена здесь.

Добавим в main.js обработчик сообщения sc-open, отправляемого окном браузера при клике пользователя на уведомлении:

const { shell } = electron

ipcMain.on('sc-open', (event, url) => {
    let options = {
        type: 'question',
        message: `Application requested to execute the external resource: ${url}. This may be unsafe. Are you sure?`,
        buttons: [ 'OK', 'Cancel' ], // Кнопки нумеруются с нуля: 0, 1
        defaultId: 1, // Индекс кнопки по умолчанию ('Cancel')
    };

    // Покажем пользователю диалог
    dialog.showMessageBox(null, options, (buttonId) => {
        // `buttonId` — индекс нажатой пользователем кнопки (или `defaultId`)
        if (buttonId === 0) shell.openExternal(url) // Выполним действие по умолчанию
    })
})
Внешний вид приложения
Внешний вид приложения
 

Обработка глобальных горячих клавиш

Зарегистрируем горячую клавишу Control + Shift + P (Command + Shift + P под macOS) для начала или приостановки проигрывания композиции.

Добавим в main.js:

const {globalShortcut} = require('electron')

app.on('ready', () => {
    // Зарегистрировать глобальный обработчик сочетания горячих клавиш 'CommandOrControl+Shift+P'
    globalShortcut.register('CommandOrControl+Shift+P', () => {
        console.log('CommandOrControl+Shift+P')
        win && win.send('do-toggle')
    })

    // Зарегистрируем глобальный обработчик горячей клавиши управления медиапроигрывателем `MediaPlayPause`
    globalShortcut.register('MediaPlayPause', () => {
        console.log('MediaPlayPause')
        win && win.send('do-toggle')
    })
})

Модификатор CommandOrControl соответствует клавише Command под macOS и Control под Linux и Windows.

Добавим в файл soundcloud.js рядом с обработчиками сообщений do-play и do-pause вызов метода виджета widget.toggle() при получении события do-toggle (обработчик события SC.Widget.Events.READY):

ipcRenderer.on('do-toggle', () => widget.toggle() )

Полный список обозначений клавиш и модификаторов приведен в документации здесь.

 

Блокировка открытия новых окон и навигации

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

Ограничим действия пользователя — заблокируем открытие нового окна и навигацию, вызвав для этого метод событий preventDefault() внутри обработчиков событий new-window и will-navigate объекта win.webContent следующим образом (в конце функции createWindow файла main.js, под объявлением переменной webContents):

// Заблокировать открытие нового окна
webContents.on('new-window', (event) => {
    event.preventDefault()
})

// Заблокировать навигацию на другие веб-страницы
webContents.on('will-navigate', (event) => {
    event.preventDefault()
})
 

Предотвращение закрытия главного окна приложения

Кроме события closed, вызываемого при закрытии окна, у окна есть событие close, которое вызывается перед закрытием окна, и, если вызвать его метод preventDefault(), окно закрыто не будет.

Добавим в начале файла main.js переменную preventClose — флаг, определяющий, можно ли закрывать главное окно приложения:

// По умолчанию предотвращать закрытие приложения
let preventClose = true;

Добавим внутри функции createWindow файла main.js блокировку закрытия окна, если установлен флаг preventClose:

// Событие перед закрытием окна
win.on('close', (e) => {
    if (preventClose) {
        // Блокируем действие по умолчанию — закрытие окна
        e.preventDefault()
        // Скрываем окно
        win.hide()
    }
})

И в конце файла main.js определим обработчик события app.on('before-quit'), вызываемого при попытке завершить приложение в целом (в нашем случае будет вызвано при выборе пункта меню Quit):

app.on('before-quit', () => preventClose = false)
 

Безопасность

Мы уже затрагивали вопросы безопасности, когда открывали в браузере URL, полученный с сервера, выполняя для него действие по умолчанию. Каждый раз, когда код, полученный с удаленного ресурса, выполняется локально, существует риск злонамеренного доступа к локальным ресурсам. В связи с этим настоятельно советуем почитать замечания о безопасности в Electron.

 

Возможности фреймворка

В числе других возможностей интеграции с интерфейсом операционной системы: список последних документов (Windows и macOS), меню приложения для док-панели macOS, объявление списка пользовательских задач приложения в Windows, миниатюры списка задач Windows, добавление ссылок в панель запуска Unity (Linux), индикатор хода выполнения, накладной значок и подсветка кнопки приложения на панели задач Windows, перетаскивание файлов из окна Electron.

Среди системных возможностей — доступ к информации об изменении состоянии монитора питания, блокировка перехода в спящий режим, получение информации об экране и изображения на экране, получение уведомлений об изменении статуса сетевого подключения (онлайн/офлайн) и многое другое.

 

Альтернативы

Логотип NW
Логотип NW

Конкурирует с Electron проект NW.js, ранее носивший название node-webkit.

INFO


Для подготовки дистрибутива можно использовать проект electron-packager, позволяющий собрать исполняемые файлы и исходный код приложения в единый пакет. Для обхода ограничений на длину имен файлов и ускорения загрузки модулей Electron поддерживает упаковку файлов приложения в пакет с расширением .asar, содержимое которого доступно из самого приложения с использованием стандартного файлового API.

Electron обладает встроенным механизмом автоматических обновлений autoUpdater. Реализован и механизм автоматической отправки отчетов о падениях приложения на удаленный сервер с помощью объекта crashReporter.

Аватар

Александр Лыкошин

Директор продуктового направления компании «Телигент». Член ACM, IEEE, IEEE Communications Society, IEEE Computer Society. Преподает в МЭСИ на кафедре АСОИУ Института компьютерных технологий.

Check Also

Небесное око. Тестируем возможности Quasar RAT

Уверен, можно лишний раз не объяснять читателям, что такое RAT. Для любителей куда-нибудь …

2 комментария

  1. Аватар

    Alchemist

    24.01.2017 в 06:14

    А одним архивом проект можно скачать где нибудь?

  2. Аватар

    Alchemist

    24.01.2017 в 06:27

    И ещё вопрос: вы уверены в строке «Добавим в файл main.js:» ?
    О файле main.js речи не шло до этой строки.

Оставить мнение