TL;DR: В развивающемся мире всего JavaScript больше не является удивительным тот факт, что разработчики теперь могут писать настольные приложения с помощью JavaScript. С момента первого выпуска Электрона около 5 лет назад веб-разработчики получили возможность использовать веб-технологии для разработки приложений, которые работают как нативные настольные приложения. Одна из многих прекрасных особенностей Electron заключается в том, что вы можете использовать любую среду JavaScript, которую вы предпочитаете, для создания интерфейса вашего настольного приложения. В этой статье вы узнаете, как легко использовать Vue.js для создания интерфейсов для приложений Electron. При необходимости вы можете использовать этот репозиторий GitHub в качестве справочного материала.

Посетите Блог Auth0 🔐 и найдите все, что вам нужно знать об инфраструктуре идентификации, управлении доступом, системе единого входа, аутентификации JWT и последних новостях JavaScript. 👉 AUTH0 БЛОГ👈

Предпосылки

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

  1. базовые знания Vue;
  2. базовые знания Электрона;
  3. и оба Node.js и NPM установлены в вашей системе.

Кроме того, вам понадобится Vue CLI 3. Чтобы установить его, вы можете открыть терминал и ввести следующую команду:

npm install -g @vue/cli

Что вы будете строить

Чтобы узнать, как использовать Electron и Vue.js вместе для создания современных настольных приложений, вы будете создавать классическое приложение списка дел. Это приложение будет управлять действиями и позволит пользователям входить в систему, чтобы весь процесс стал более безопасным. Как и в любом реальном сценарии.

Клонирование и запуск API

Этому приложению Electron и Vue.js потребуется API для работы. Поскольку цель этой статьи — сосредоточиться на этих технологиях, вы не будете тратить время на создание API. Вместо этого вы клонируете один из GitHub. Для этого вернитесь в свой терминал и введите следующие команды:

# clone the API git clone https://github.com/auth0-blog/electron-vue-api.git # move into it cd electron-vue-api # install the API dependencies npm install # run the API npm start

Вы можете пока оставить этот API запущенным (т. е. не закрывать терминал). Кстати, если вы хотите узнать больше о том, как создавать API с помощью Node.js и Express, вы можете проверить это руководство, которое проходит весь процесс с нуля.

Скаффолдинг настольных приложений с помощью Vue.js и Electron

Теперь, когда у вас правильно настроены предварительные условия, вы готовы начать работу над клиентским приложением. Для создания каркаса настольного приложения вы будете использовать отличный проект с открытым исходным кодом, который упрощает работу с Vue.js и Electron: electron-vue. Этот проект поставляется в комплекте с другими полезными библиотеками Vue.js, такими как Vue Router и Vuex.

Чтобы создать шаблон вашего приложения, вам даже не нужно устанавливать этот проект. Все, что вам нужно, это сообщить Vue CLI, что вы хотите использовать его в качестве основы для своего нового приложения. Для этого откройте новый терминал (вы должны оставить API включенным и работающим, так как вы заставите настольное приложение взаимодействовать с ним) и введите следующую команду:

vue init simulatedgreg/electron-vue to-do-desktop

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

  • Имя приложения (to-do-desktop)
  • Идентификатор приложения (Нажмите клавишу Enter, чтобы принять значение по умолчанию)
  • Версия приложения (Нажмите клавишу Enter, чтобы принять версию по умолчанию)
  • Описание проекта (Нажмите клавишу Enter, чтобы принять значение по умолчанию)
  • Использовать Sass/Scss? (n)
  • Выберите подключаемые модули Vue для установки (Все выбраны по умолчанию, нажмите Enter, чтобы принять это)
  • Использовать линтинг с ESLint? (Y : Да, мы будем использовать ESLint для обеспечения качества кода. Нажмите Enter, чтобы также принять Стандартную версию)
  • Настроить модульное тестирование с помощью Karma + Mocha? (n)
  • Настроить сквозное тестирование с помощью Spectron + Mocha? (n)
  • Какой инструмент сборки вы хотели бы использовать? (electron-builder)
  • автор: (Нажмите клавишу Enter, чтобы принять значение по умолчанию)

После ответов на все вопросы в процессе установки для вас создается новый проект electron-vue.

Затем вы можете переместить терминал в свой новый проект и обновить версию Electron, которую будет использовать приложение:

# move into the new project cd to-do-desktop # remove the old version of Electron npm rm electron # install the new version npm i -D electron

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

Как видите, что-то не так. Проблема здесь в том, что шаблон Vue.js, который вы использовали для создания шаблона вашего приложения, не готов для Electron 6. Что вам нужно сделать, чтобы решить эту проблему, так это открыть .electron-vue/webpack.renderer.config.js и обновить его следующим образом:

// find the rendererConfig variable let rendererConfig = { // then, find the plugins property plugins: [ // then, replace the call to the HtmlWebpackPlugin constructor new HtmlWebpackPlugin({ filename: 'index.html', template: path.resolve(__dirname, '../src/index.ejs'), minify: { collapseWhitespace: true, removeAttributeQuotes: true, removeComments: true }, isBrowser: false, isDevelopment: process.env.NODE_ENV !== 'production', nodeModules: process.env.NODE_ENV !== 'production' ? path.resolve(__dirname, '../node_modules') : false }), ] }

Как вы можете видеть в комментариях выше, вы не сильно измените этот файл. Все, что вам нужно сделать, это жестко закодировать isBrowser, установив для него значение false, и вы определите, что isDevelopment зависит от значения process.env.NODE_ENV.

Примечание. Будьте особенно осторожны при обновлении этого файла. Вам не нужно ничего удалять или изменять, кроме добавления этих двух свойств к объекту, переданному HtmlWebpackPlugin.

После этого вам нужно будет открыть файл ./src/index.ejs и заменить содержимое элемента <body> следующим:

<div id="app"></div> <!-- Set `__static` path to static files in production --> <% if (!htmlWebpackPlugin.options.isBrowser && !htmlWebpackPlugin.options.isDevelopment) { %> <script> window.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\') </script> <% } %>

Изменения в этом файле также весьма лаконичны. Единственное отличие состоит в том, что теперь вы больше не вызываете глобальную переменную process, потому что Electron 6 не предоставляет ее по умолчанию, а используемый вами шаблон Vue.js не запрашивает ее явно.

Однако есть и другие места, где вам нужно будет использовать глобальную переменную process. Таким образом, чтобы завершить часть этого руководства, посвященную лесам, вы откроете файл ./src/main/index.js и обновите его следующим образом:

// find the createWindow function definition function createWindow () { // add the webPreferences property passed to BrowserWindow mainWindow = new BrowserWindow({ height: 563, useContentSize: true, width: 1000, webPreferences: { nodeIntegration: true, nodeIntegrationInWorker: true } }) }

В этом случае вы обновляете этот файл, чтобы убедиться, что остальная часть приложения (исходный код, который вы собираетесь написать) будет иметь доступ к некоторым функциям Node.js.

После обновления этого файла вы можете остановить предыдущий экземпляр Electron (например, вы можете нажать Ctrl + C в терминале, который вы использовали для его запуска) и снова запустить npm run dev. Если все работает должным образом, вы увидите страницу начала работы electron-vue.

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

Реализация первого маршрута с помощью Vue.js и Electron

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

Маршруты в electron-vue определены в файле ./src/renderer/router/index.js. Компоненты, с другой стороны, расположены в каталоге ./src/renderer/components. Итак, открываем файл ./src/renderer/router/index.js и заменяем в нем содержимое на это:

import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'todos-page', component: require('@/components/ToDos').default }, { path: '*', redirect: '/' } ] })

С этим изменением целевая страница теперь указывает на компонент с именем ToDos, который вы создадите далее. Чтобы создать этот компонент, зайдите в папку ./src/renderer/components и удалите все внутри нее. Затем создайте новый файл с именем ToDos.vue и вставьте в него следующее содержимое:

<template> <div> <h2>Welcome to the To-Dos Page</h2> </div> </template>

Теперь, когда вы просматриваете свое приложение, вы должны увидеть пустой экран с заголовком «Добро пожаловать на страницу To-Dos».

Использование API с Vue.js и Electron

После создания первого маршрута в вашем приложении Vue.js и Electron пришло время заставить его использовать API, который вы загрузили. Для этого вы можете заменить код внутри файла ToDos.vue следующим:

<template> <div> <div> <div> <button @click="fetchTodos()" class="btn btn-primary">Fetch Todos</button> </div> </div> <div> <div> <ul> <li v-for="todo in todos" :key="todo.id"></li> </ul> </div> </div> </div> </template> <script> const axios = require('axios') export default { name: 'ToDos', data: () => { return { todos: [] } }, methods: { async fetchTodos () { axios .get('http://localhost:3001/') .then(response => { this.todos = response.data }) .catch(error => { if (error) throw new Error(error) }) } } } </script>

В приведенном выше коде вы создаете свойство data с именем todos для хранения коллекции элементов списка дел. Затем вы создаете метод с именем fetchTodos для вызова конечной точки API для загрузки этой коллекции.

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

Защита приложений Vue.js и Electron Desktop

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

  1. Вам пришлось бы работать десятки (если не сотни) часов только для того, чтобы иметь минимальное решение, которое позволит вашим пользователям регистрироваться, входить в систему, менять утерянный пароль и т. д.
  2. Вы бы не использовали лучшие меры и технологии безопасности, потому что вы (вероятно) не являетесь экспертом в управлении идентификацией.

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

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

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

Настройка вашей учетной записи Auth0

После входа в свою панель инструментов Auth0 первое, что вам нужно будет сделать, — это создать API Auth0 для представления Node.js и выражения API, которые вы загрузили ранее. Для этого перейдите в раздел API и нажмите «Создать API». Когда вы это сделаете, Auth0 покажет форму, которую вы можете заполнить следующим образом:

  • Название: API дел
  • Идентификатор: https://to-do-api
  • Алгоритм подписи: RS256

После этого вы можете нажать на кнопку «Создать», чтобы завершить процесс. Когда Auth0 завершит создание API, перейдите на вкладку «Настройки», найдите параметр Разрешить автономный доступ, включите его и нажмите «Сохранить» внизу страницы.

Теперь перейдите в раздел «Приложения» на панели инструментов Auth0 и нажмите «Создать приложение». Затем вы можете заполнить форму, которую Auth0 представляет следующим образом:

  • Название: Приложение To-Do
  • Тип приложения: родной

Затем можно нажать на кнопку «Создать». Когда вы это сделаете, Auth0 перенаправит вас в раздел «Быстрый старт» вашего нового приложения. Оттуда вы можете перейти в раздел «Настройки» и изменить следующее свойство вашего приложения:

Это поле сообщит Auth0, что можно вызывать file:///callback после процесса аутентификации. После изменения этого поля прокрутите страницу вниз и нажмите Сохранить изменения.

Защита API

Теперь откройте терминал, на котором работает API, и нажмите Ctrl + C. Затем введите следующие команды:

git checkout secured-api npm i touch .env

Первая команда проверит ветку с именем secured-api. Эта ветвь содержит код, необходимый для идентификации аутентифицированных конечных пользователей, и будет отклонять запросы от неизвестных пользователей. Вторая команда установит все зависимости этой ветки. Третья команда создаст файл с именем .env, который вы будете использовать для настройки защищенного API с вашими значениями Auth0.

После выполнения этих команд откройте файл .env и обновите его следующим образом:

AUTH0_DOMAIN= AUTH0_API_IDENTIFIER=

Для первой переменной вам нужно будет использовать домен вашего клиента Auth0 (например, brunokrebs.auth0.com). Если вы не знаете свой домен Auth0, проверьте эту документацию. Для второй переменной вам придется использовать идентификатор API, который вы создали ранее (вероятно, https://to-do-api).

Затем вы можете ввести npm start, чтобы перезапустить API.

Защита приложений Electron и Vue.js с помощью Auth0

Теперь вернемся к настольному приложению Electron, вам нужно интегрировать его с Auth0, чтобы пользователи могли аутентифицироваться. Для этого вам понадобятся дополнительные пакеты. Итак, закройте приложение Electron (вы можете нажать Ctrl + C, чтобы остановить его) и введите следующую команду:

npm install jwt-decode request keytar bootstrap

Эта команда установит четыре новых пакета:

  • jwt-decode: Поскольку Auth0 использует JWT для передачи пользовательских данных, вам нужен этот пакет для декодирования этой информации.
  • request: вам понадобится request, чтобы иметь возможность отправлять запросы из среды Node.js в Auth0.
  • keytar: Этот пакет поможет вам более безопасно хранить токены пользователей в пользовательской среде.
  • bootstrap: Это позволит вашему настольному приложению использовать Bootstrap, чтобы сделать пользовательский интерфейс более красивым.

Затем вы можете создать файл с именем env.json, в который вы поместите свойства приложения Auth0. Итак, создайте этот файл внутри корня проекта и добавьте в него следующий объект JSON:

{ "apiIdentifier": "YOUR_AUTH0_API_IDENTIFIER", "auth0Domain": "YOUR_APP_DOMAIN", "clientId": "YOUR_CLIENT_ID" }

Аналогично тому, что вы сделали с файлом .env API, вам нужно будет установить идентификатор API, который вы использовали для создания API, в своей учетной записи Auth0 (например, https://to-do-api), а также вам нужно будет установить домен Auth0 (например, , brunokrebs.auth0.com). Кроме того, вам нужно будет использовать идентификатор клиента приложения Auth0 в файле выше. Чтобы найти эту информацию, откройте приложение Auth0 на панели инструментов Auth0, и вы увидите поле с этим именем.

Затем вы создадите 3 модуля, которые помогут вам в интеграции с Auth0:

  1. auth-service.js: модуль, содержащий все методы и свойства для обработки потока аутентификации.
  2. auth-process.js: модуль, управляющий процессом аутентификации.
  3. app-process.js: модуль, который загружает домашнюю страницу приложения.

Вам не нужно сильно беспокоиться о том, как работают эти модули, но если вам интересно, ознакомьтесь со статьей «Защита приложений Electron с помощью OpenID Connect и OAuth 2.0 в нашем блоге».

Вы добавите все эти файлы в новый каталог с именем services, который вы создадите внутри каталога ./src/main/. Чтобы помочь вам с этой задачей, вы можете выполнить следующие команды на своем терминале:

mkdir src/main/services/ touch src/main/services/auth-service.js touch src/main/services/auth-process.js touch src/main/services/app-process.js

Затем вы можете добавить следующий код в файл src/main/services/auth-service.js:

const jwtDecode = require('jwt-decode') const request = require('request') const url = require('url') const envVariables = require('../../../env') const keytar = require('keytar') const os = require('os') const { apiIdentifier, auth0Domain, clientId } = envVariables const redirectUri = `file:///callback` const keytarService = 'my-todo-app' const keytarAccount = os.userInfo().username let accessToken = null let profile = null let refreshToken = null function getAccessToken () { return accessToken } function getProfile () { return profile } function getAuthenticationURL () { return ( 'https://' + auth0Domain + '/authorize?' + 'audience=' + apiIdentifier + '&' + 'scope=openid profile offline_access&' + 'response_type=code&' + 'client_id=' + clientId + '&' + 'redirect_uri=' + redirectUri ) } function refreshTokens () { return new Promise(async (resolve, reject) => { const refreshToken = await keytar.getPassword(keytarService, keytarAccount) if (!refreshToken) return reject(new Error('no refresh token available')) const refreshOptions = { method: 'POST', url: `https://${auth0Domain}/oauth/token`, headers: { 'content-type': 'application/json' }, body: { grant_type: 'refresh_token', client_id: clientId, refresh_token: refreshToken }, json: true } request(refreshOptions, function (error, response, body) { if (error) { logout() return reject(new Error(error)) } accessToken = body.access_token profile = jwtDecode(body.id_token) global.accessToken = accessToken resolve() }) }) } function loadTokens (callbackURL) { return new Promise((resolve, reject) => { const urlParts = url.parse(callbackURL, true) const query = urlParts.query const exchangeOptions = { grant_type: 'authorization_code', client_id: clientId, code: query.code, redirect_uri: redirectUri } const options = { method: 'POST', url: `https://${auth0Domain}/oauth/token`, headers: { 'content-type': 'application/json' }, body: JSON.stringify(exchangeOptions) } request(options, (error, resp, body) => { if (error) { logout() return reject(error) } const responseBody = JSON.parse(body) accessToken = responseBody.access_token global.accessToken = accessToken profile = jwtDecode(responseBody.id_token) refreshToken = responseBody.refresh_token keytar.setPassword(keytarService, keytarAccount, refreshToken) resolve() }) }) } async function logout () { await keytar.deletePassword(keytarService, keytarAccount) accessToken = null profile = null refreshToken = null } export default { getAccessToken, getAuthenticationURL, getProfile, loadTokens, logout, refreshTokens }

Затем внутри файла src/main/services/app-process.js вам нужно будет добавить этот код:

import { BrowserWindow } from 'electron' let mainWindow const winURL = process.env.NODE_ENV === 'development' ? `http://localhost:9080` : `file://${__dirname}/index.html` function createAppWindow () { /** * Initial window options */ mainWindow = new BrowserWindow({ height: 563, useContentSize: true, width: 1000, webPreferences: { nodeIntegration: true, nodeIntegrationInWorker: true } }) mainWindow.loadURL(winURL) mainWindow.on('closed', () => { mainWindow = null }) } export default createAppWindow

Наконец, вам нужно будет добавить следующий код в файл src/main/services/auth-process.js:

import { BrowserWindow } from 'electron' import authService from './auth-service' import createAppWindow from './app-process' let win = null function createAuthWindow () { destroyAuthWin() // Create the browser window. win = new BrowserWindow({ width: 1000, height: 600, webPreferences: { nodeIntegration: false } }) win.loadURL(authService.getAuthenticationURL()) const { session: { webRequest } } = win.webContents const filter = { urls: ['file:///callback*'] } webRequest.onBeforeRequest(filter, async ({ url }) => { await authService.loadTokens(url) createAppWindow() return destroyAuthWin() }) win.on('authenticated', () => { destroyAuthWin() }) win.on('closed', () => { win = null }) } function destroyAuthWin () { if (!win) return win.close() win = null } export default createAuthWindow

После создания этих файлов откройте файл src/main/index.js и используйте следующий код для замены текущего:

'use strict' import { app } from 'electron' import createAuthWindow from './services/auth-process' import createAppWindow from './services/app-process' import authService from './services/auth-service' if (process.env.NODE_ENV !== 'development') { global.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\') } let mainWindow async function createWindow () { try { await authService.refreshTokens() mainWindow = createAppWindow() } catch (err) { createAuthWindow() } } app.on('ready', createWindow) app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit() } }) app.on('activate', () => { if (mainWindow === null) { createWindow() } })

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

После этого вы можете перезапустить приложение, введя npm run dev. Если все работает должным образом, вы увидите экран, на котором вы сможете войти или создать новую учетную запись, если у вас ее еще нет.

Примечание. Когда вы запускаете npm run dev, Electron может показать диалоговое окно, сообщающее, что keytar не зарегистрировал себя. Если вы получили эту ошибку, установите electron-rebuild, введя npm i -D electron-rebuild, а затем запустите ./node_modules/.bin/electron-rebuild.

Использование защищенных API

Теперь последняя часть приложения. Вы проведете рефакторинг своего внешнего интерфейса, чтобы выполнять аутентифицированные вызовы внутреннего API (to dos API), который в настоящее время защищен (т. е. недоступен для неаутентифицированных пользователей). Вы также добавите немного стилей с помощью Bootstrap.

Для этого откройте файл src/renderer/components/ToDos.vue и замените его код на этот:

<template> <div> <div class="row"> <header class="col-md-12"> <nav class="navbar navbar-light bg-light"> <a class="navbar-brand">Vue TODO List</a> <button class="btn btn-danger my-2 my-sm-0" @click="logout()" type="button">Logout</button> </nav> </header> </div> <div class="row" id="fetch-button-row"> <div class="col-md-12"> <button @click="fetchTodos()" class="btn btn-primary">Fetch Todos</button> </div> </div> <div class="row" id="todos-row"> <div class="col-md-12"> <ul class="list-group"> <li class="list-group-item" v-for="todo in todos" :key="todo.id"></li> </ul> </div> </div> </div> </template> <script> import authService from '../../main/services/auth-service' const { remote } = window.require('electron') const axios = require('axios') export default { name: 'HelloWorld', data: () => { return { todos: [] } }, methods: { async logout () { await authService.logout() remote.getCurrentWindow().close() }, fetchTodos () { let accessToken = remote.getGlobal('accessToken') axios .get('http://localhost:3001/', { headers: { Authorization: `Bearer ${accessToken}` } }) .then(response => { this.todos = response.data }) .catch(error => { if (error) throw new Error(error) }) } } } </script> <style scoped> @import "~bootstrap/dist/css/bootstrap.min.css"; #fetch-button-row, #todos-row { margin: 10px; } </style>

В новой версии этого файла вы заставляете fetchTodos использовать глобальную переменную accessToken. С помощью этой переменной компонент ToDos отправляет запрос к защищенному API и извлекает список задач. Как и раньше, приложение использует этот список для отображения элемента пользователю.

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

Вывод

Как показано в этой статье, создание нативных интерфейсов рабочего стола с помощью веб-технологий — это очень просто, если вы используете правильные инструменты, а пакет electron-vue позволяет разработчикам получить лучшее из обоих миров Vue, быстрорастущего удобного для разработчиков интерфейсного фреймворка, и ElectronJS. .

Первоначально опубликовано на https://auth0.com.