Вы только что завершили создание своего приложения Electron; пока все работает; оно нотариально заверено для работы на macOS, и вы также протестировали свое приложение на Windows и Linux. Вы поделились своим приложением со всем миром и получили отличные отклики от вашего сообщества. Со временем в вашем почтовом ящике появляется все больше и больше сообщений о сбое вашего приложения. Вы обнаружили ошибку в своем приложении, которая вызывала сбои, и быстро исправили ее. Но как донести эту новую версию приложения до пользователей?

Представляем автоматическое обновление Electron

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

Несколько вещей, которые вы должны знать об автоматическом обновлении Electron:

  • Он поддерживает только macOS и Windows (без поддержки Linux).
  • И macOS, и программа обновления Windows используют Squirrel за кулисами.
  • Версия Squirrel для Windows ищет сопровождающих для «перезагрузки» проекта и имеет более 300 проблем, а это означает, что вы можете ожидать некоторых проблем.
  • Вы должны подписать свое приложение в macOS, чтобы автообновление работало.
  • Вы должны переместить ваше приложение в каталог Приложения на macOS, чтобы автообновитель работал.
  • В Windows убедитесь, что вы не обновляете свое приложение при первом запуске, иначе ваше приложение выдаст очень пользовательскую недружественную ошибку.

Настройка автоматического обновления Electron

Реализовать автоматическое обновление Electron относительно просто; для интеграции с сервером развертывания требуется всего несколько строк кода.

const { app, autoUpdater } = require('electron')
autoUpdater.setFeedURL('https://dist.unlock.sh/v1/electron/my-app')
autoUpdater.checkForUpdates()

Если вы хотите регулярно проверять наличие обновлений (приведенный выше код выполняется только при запуске), вы можете использовать setInterval для проверки каждые 30 минут, например:

setInterval(() => {
  autoUpdater.checkForUpdates()
}, 30000)

Обновление будет загружено в фоновом режиме и установлено автоматически при перезапуске приложения (в большинстве случаев см. устранение неполадок для некоторых исключений).

Если вы хотите, чтобы ваши пользователи знали, что новое обновление было загружено и доступно для установки, вы также можете вместо этого использовать autoUpdater.checkForUpdatesAndNotify(). Уведомление будет родным для операционной системы вашего пользователя.

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

Внедрение уведомления об автоматическом обновлении Electron

Если вы хотите вместо этого использовать собственное уведомление об обновлении в приложении, вы можете сделать это, прослушивая событие update-downloaded, создаваемое программой автоматического обновления.

autoUpdater.on('update-downloaded', (event, releaseNotes, releaseName) => {
  //
})

releaseName доступно только в Windows.

Если вы хотите, чтобы программа автоматического обновления установила ваше обновление сразу после его загрузки, вы можете использовать autoUpdater.quitAndInstall():

autoUpdater.on('update-downloaded', (event, releaseNotes, releaseName) => {
   autoUpdater.quitAndInstall()
})

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

Автоматическое обновление Electron для общедоступных репозиториев

Если ваш репозиторий кода общедоступен на GitHub, вы можете использовать бесплатный сервис Electron для отправки обновлений. Это простой процесс. Давайте быстро создадим шаблон приложения, чтобы проверить это. Я использую стартовый шаблон Electron Forge; если вы хотите продолжить, выполните следующую команду:

// Yarn
yarn create electron-app auto-update-example
// NPM
npx create-electron-app auto-update-example

Чтобы использовать общедоступный автообновитель, нам нужно скачать зависимость NPM, поэтому обязательно установите эту зависимость:

npm install update-electron-app --save

Теперь просто запустите yarn start или npm start, и ваше приложение Electron будет собрано и выполнено.

Нам нужен общедоступный репозиторий GitHub, поэтому перейдите на github.com/new и создайте репозиторий, который мы сможем использовать.

Чтобы сообщить программе автоматического обновления о нашем репозитории, нам нужно убедиться, что мы определили его в нашем package.json:

{
  "name": "auto-update-example",
  "productName": "auto-update-example",
  "version": "1.0.0",
  "description": "My Electron application description",
  "repository": "https://github.com/PhiloNL/electron-hello-world",
  "main": "src/index.js",
  //
}

Давайте откроем src/index.js и вызовем средство обновления, чтобы проверять наличие обновлений каждый час и уведомлять пользователя, когда обновление доступно.

app.on('ready', () => {
    updateApp = require('update-electron-app');
updateApp({
        // repo: 'PhiloNL/electron-hello-world', // defaults to package.json
        updateInterval: '1 hour',
        notifyUser: true
    });
});

Далее нам нужно опубликовать наше приложение на GitHub. Electron Forge поставляется с несколькими встроенными издателями, в том числе для GitHub. Чтобы установить издатель, выполните следующую команду:

npm install @electron-forge/publisher-github

Мы можем определить конфигурацию для разных издателей в вашем файле package.json. Итак, давайте добавим нашу конфигурацию GitHub:

{
  //...
  "main": "src/index.js",
  "config": {
    "forge": {
      "packagerConfig": {},
      "publishers": [
        {
          "name": "@electron-forge/publisher-github",
          "config": {
            "repository": {
              "owner": "PhiloNL",
              "name": "electron-hello-world"
            }
          }
        }
      ],
      //...
    }
  },
  //...
}

Теперь давайте опубликуем наше приложение на GitHub, выполнив команду публикации. Команда публикации требует, чтобы вы установили свой личный токен доступа GitHub, чтобы он мог получить доступ к вашей учетной записи. Вы можете создать персональный токен доступа здесь. Убедитесь, что вы храните этот токен в безопасности и никому его не передавайте.

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

export GITHUB_TOKEN=<your-token>
yarn run publish

Отлично, вы только что отправили версию 1.0.0 на GitHub. По умолчанию для вашего выпуска установлено значение «Черновик», ожидающее вашего окончательного утверждения. Итак, перейдите к выпускам вашего репозитория и опубликуйте свой выпуск (github.com/username/repository/releases).

Давайте проверим, работает ли программа обновления, опубликовав новую версию. Откройте src/index.html и внесите пару изменений, чтобы вы могли видеть, что приложение было обновлено.

Затем увеличьте номер версии вашего приложения, открыв package.json и изменив номер версии:

{
  "name": "auto-update-example",
  "productName": "auto-update-example",
  "version": "1.0.1",
  // ...
}

Снова запустите yarn run publish и перейдите на GitHub, чтобы опубликовать v1.0.1 вашего приложения. Запустите v1.0.0 своего приложения и дождитесь уведомления :)

Нажмите перезапустить, и вы увидите новую версию вашего приложения.

Опять же, это будет работать как на macOS, так и на Windows, если вы правильно обработаете события Squirrel.

Исправление проблем

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

Для отладки того, что происходит в фоновом режиме, вы можете включить логгер, передав его в конструктор update-electron-app.

require('update-electron-app')({
  logger: require('electron-log')
})

Вы сможете найти файлы журнала в следующих местах:

  • Linux: ~/.config/{имя приложения}/logs/{тип процесса}.log
  • macOS: /Library/Logs/{имя приложения}/{тип процесса}.log
  • Windows: %USERPROFILE%\AppData\Roaming\{имя приложения}\logs\{тип процесса}.log
[info] Checking for update
[info] Found version v1.0.1 (url: https://github.com/PhiloNL/electron-hello-world/releases/download/v1.0.0/auto-update-example-darwin-x64-1.0.0.zip)
[info] Downloading update from https://github.com/PhiloNL/electron-hello-world/releases/download/v1.0.0/auto-update-example-darwin-x64-1.0.0.zip

Состояние гонки в macOS с программой обновления Squirrel

В некоторых случаях вашему приложению может потребоваться несколько перезапусков для работы обновления, если ваш пользователь запускает приложение быстро после выхода. Это также может иметь место при использовании autoUpdater.quitAndInstall(). Я испытал это с Electron Builder, поэтому я не уверен, что это также относится к Electron Forge. Тем не менее, я предполагаю, что, поскольку все они используют средство обновления Squirrel, оно влияет на любое приложение, использующее встроенное средство обновления Electron.

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

Когда вы запускаете приложение и средство обновления Squirrel находит новое обновление для вашего приложения, оно запускает незанятый процесс с именем ShipIt. Этот процесс остается бездействующим до тех пор, пока вы не закроете свое приложение. Как только ваше приложение будет закрыто, процесс ShipIt начнет извлекать ваше обновление и заменит ваше приложение последней версией. В зависимости от размера вашего приложения и скорости компьютера пользователя это может занять некоторое время.

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

Суть из приведенной выше проблемы решает эту проблему, обеспечивая завершение процесса ShipIt. Давайте разберем код шаг за шагом.

const shipItProcesses = await findProcess('name', 'ShipIt');

Найдите активный процесс с именем ShipIt.

if (shipItProcesses.some(f => f.cmd.includes('com.org.my-app'))) {
  shouldRestartBeforeLaunch = true;
  console.debug('Waiting for auto update to finish');
  setTimeout(makeSureAutoUpdateFinished, 1500);
} else {
 // ...
}

Поскольку пользователь может запускать несколько приложений Electron, мы хотим убедиться, что процесс ShipIt принадлежит нашему приложению com.org.my-app. Если этот процесс существует, мы ждем запуска приложения, поэтому у автообновления есть шанс завершить работу. Эта проверка будет повторяться рекурсивно, пока процесс не завершится.

        if (shouldRestartBeforeLaunch) {
          try {
            const Electron = require('electron');
            Electron.app.relaunch();
            Electron.app.exit(0);
          } catch (error) {
            console.error('Failed to restart the app through electron', error);
            process.exit(1);
          }
        } else {
          require('./main');
        }

Затем он перезапустит существующее приложение, чтобы завершить процесс обновления. Эти многократные перезапуски приведут к тому, что ваше приложение несколько раз подпрыгнет в доке macOS, но, по крайней мере, вы будете уверены, что ваш пользователь использует последнюю версию вашего приложения. Наконец, он выполнит основной код вашего приложения Electron.

Вот и все! Вы успешно использовали автоматическое обновление Electron вместе с GitHub для распространения новой версии вашего приложения среди пользователей.

Хотите узнать больше о том, как публиковать обновления из частных репозиториев и лицензировать свои продукты? Обязательно подпишитесь на будущие статьи или подпишитесь на меня в Twitter. Я ценю поддержку!

Первоначально опубликовано на https://philo.dev 13 января 2021 г.