Использование скриптов предварительной загрузки
Цели обучения
В этой части учебника вы узнаете, что такое скрипт предварительной загрузки и как его использовать для безопасного предоставления привилегированных API в процессе рендеринга. Вы также узнаете, как общаться между процессами «главным» и «рендеринга» с помощью модулей межпроцессного взаимодействия (IPC) Electron.
Что такое скрипт предварительной загрузки?
Главный процесс Electron — это среда Node.js, имеющая полный доступ к операционной системе. Помимо модулей Electron, вы также можете получить доступ к встроенным функциям Node.js, а также к любым пакетам, установленным через npm. С другой стороны, процессы рендеринга запускают веб-страницы и по умолчанию не запускают Node.js по соображениям безопасности.
Для объединения различных типов процессов Electron нам понадобится специальный скрипт, называемый скриптом предварительной загрузки.
Расширение рендера с помощью скрипта предварительной загрузки
Скрипт предварительной загрузки BrowserWindow выполняется в контексте, имеющем доступ как к DOM HTML, так и к среде Node.js. Скрипты предварительной загрузки вставляются перед загрузкой веб-страницы в процесс рендеринга, аналогично скриптам контента расширения Chrome.
Чтобы добавить в процесс рендеринга функции, требующие привилегированного доступа, вы можете определить глобальные объекты с помощью API contextBridge.
Для демонстрации этого понятия вы создадите скрипт предварительной загрузки, который предоставит версии вашего приложения для Chrome, Node и Electron в процессе рендеринга.
Добавьте новый preload.js скрипт, который предоставляет выбранные свойства объекта Electron в process.versions глобальной переменной в процессе рендеринга.
const { contextBridge } = require('electron')
contextBridge.exposeInMainWorld('versions', {
node: () => process.versions.node,
chrome: () => process.versions.chrome,
electron: () => process.versions.electron,
// we can also expose variables, not just functions
})
Чтобы прикрепить этот скрипт к вашему процессу рендеринга, передайте его путь в webPreferences.preload параметр в конструкторе BrowserWindow:
const { app, BrowserWindow } = require('electron')
const path = require('path')
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
},
})
win.loadFile('index.html')
}
app.whenReady().then(() => {
createWindow()
})
В этот момент процесс рендеринга имеет доступ к глобальной переменной versions, поэтому давайте отобразим эту информацию в окне. К этой переменной можно получить доступ через window.versions или просто versions.
Создайте renderer.js скрипт, использующий API DOM document.getElementById, чтобы заменить отображаемый текст HTML-элемента с info в качестве его свойства id.
const information = document.getElementById('info')
information.innerText = `This app is using Chrome (v${versions.chrome()}), Node.js (v${versions.node()}), and Electron (v${versions.electron()})`
Затем измените index.html файл, добавив новый элемент с info в качестве свойства id, и прикрепите ваш renderer.js скрипт:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<meta
http-equiv="X-Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<title>Hello from Electron renderer!</title>
</head>
<body>
<h1>Hello from Electron renderer!</h1>
<p>👋</p>
<p id="info"></p>
</body>
<script src="./renderer.js"></script>
</html>
После выполнения вышеперечисленных шагов ваше приложение должно выглядеть примерно так:
И код должен выглядеть так:
- main.js
- preload.js
- index.html
- renderer.js
const { app, BrowserWindow } = require('electron');
const path = require('path');
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
},
});
win.loadFile('index.html');
};
app.whenReady().then(() => {
createWindow();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
const { contextBridge } = require('electron');
contextBridge.exposeInMainWorld('versions', {
node: () => process.versions.node,
chrome: () => process.versions.chrome,
electron: () => process.versions.electron,
});
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<meta
http-equiv="X-Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<title>Hello from Electron renderer!</title>
</head>
<body>
<h1>Hello from Electron renderer!</h1>
<p>👋</p>
<p id="info"></p>
</body>
<script src="./renderer.js"></script>
</html>
const information = document.getElementById('info');
information.innerText = `This app is using Chrome (v${versions.chrome()}), Node.js (v${versions.node()}), and Electron (v${versions.electron()})`;
Обмен данными между процессами
Как уже упоминалось выше, главный и рендеринг процессы Electron имеют разные обязанности и не взаимозаменяемы. Это означает, что невозможно напрямую получить доступ к API Node.js из процесса рендеринга, а также к DOM HTML из главного процесса.
Решение этой проблемы — использование модулей Electron ipcMain и ipcRenderer для межпроцессного взаимодействия (IPC). Чтобы отправить сообщение со своей веб-страницы в главный процесс, вы можете настроить обработчик главного процесса с помощью ipcMain.handle и затем экспонировать функцию, которая вызывает ipcRenderer.invoke, чтобы запустить обработчик в скрипте предварительной загрузки.
Для иллюстрации мы добавим в процесс рендеринга глобальную функцию ping(), которая вернёт строку из главного процесса.
Сначала настройте вызов invoke в скрипте предварительной загрузки:
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('versions', {
node: () => process.versions.node,
chrome: () => process.versions.chrome,
electron: () => process.versions.electron,
ping: () => ipcRenderer.invoke('ping'),
// we can also expose variables, not just functions
})
Обратите внимание, как мы обернули вызов ipcRenderer.invoke('ping') в вспомогательную функцию, а не экспонировали непосредственно модуль ipcRenderer. Вы никогда не должны напрямую экспонировать весь модуль ipcRenderer через предварительную загрузку. Это даст вашему процессу рендеринга возможность отправлять произвольные сообщения IPC в главный процесс, что становится мощным вектором атаки для вредоносного кода.
Затем настройте обработчика handle в главном процессе. Мы делаем это до загрузки HTML-файла, чтобы обработчик был готов перед отправкой invoke вызова из процесса рендеринга.
const { ipcMain } = require('electron')
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
},
})
ipcMain.handle('ping', () => 'pong')
win.loadFile('index.html')
}
После настройки отправителя и получателя вы можете теперь отправлять сообщения из процесса рендеринга в главный процесс через канал 'ping' , который вы только что определили.
const func = async () => {
const response = await window.versions.ping()
console.log(response) // prints out 'pong'
}
func()
Для более подробного объяснения использования модулей ipcRenderer и ipcMain, ознакомьтесь с полным руководством по межпроцессного взаимодействия.
Заключение
Скрипт предварительной загрузки содержит код, который выполняется перед загрузкой вашей веб-страницы в окно браузера. Он имеет доступ как к API DOM, так и к среде Node.js и часто используется для предоставления привилегированных API для процесса рендеринга через API contextBridge.
Поскольку основные и рендерные процессы имеют совершенно разные обязанности, приложения Electron часто используют скрипт предварительной загрузки для настройки интерфейсов межпроцессного взаимодействия (IPC) для передачи произвольных сообщений между этими двумя типами процессов.
В следующей части учебника мы покажем вам ресурсы по добавлению дополнительной функциональности в ваше приложение, а затем научим вас распространять ваше приложение среди пользователей.
© GitHub Inc.
Licensed under the MIT license.
https://www.electronjs.org/docs/latest/tutorial/tutorial-preload