Utiliser des scripts de préchargement
Ceci est la partie 3 du tutoriel Electron.
Objectifs
Dans cette partie du tutoriel, vous apprendrez ce qu'est un script de préchargement et comment l'utiliser pour exposer de façon sécurisée des API privilégiées dans un processus de rendu. Vous apprendrez également à faire communiquer le processus principal et les processus de rendu à l'aide des modules de communication interprocessus(IPC) d'Electron.
Qu'est-ce qu'un script de préchargement ?
Le processus principal d’Electron est un environnement Node.js disposant d’un accès complet au système d’exploitation. En plus des modules d'Electron, vous pouvez également accéder aux Fonctions intégrées de Node.js , ainsi que tous les packages installés via npm. D’autre part, les processus de rendu exécutent des pages Web et n’exécutent pas Node.js par défaut pour des raisons de sécurité.
Pour relier les différents types de processus d’Electron, nous devrons utiliser un script spécial appelé préchargement (preload).
Enrichir le moteur de rendu avec un script de préchargement
Le script de préchargement d'une BrowserWindow s’exécute dans un contexte qui a accès à la fois au DOM de l'HTML et à un sous ensemble des Apis Node.js et Electron.
À partir d'Electron 20, les scripts de préchargement sont mis en bac à sable par défaut et n'ont plus accès à un environnement complet Node.js. Pratiquement, cela signifie que vous avez une fonction polyfill de require
n'ayant accès qu'à un ensemble limité d'APIs.
API disponibles | Détails |
---|---|
Modules Electron | Modules de processus de rendu |
Modules Node.js | events , timers , url |
Polyfills globaux | Buffer , process , clearImmediate , setImmediate |
Pour plus d'informations, consultez la doc à propos de Mise en bac à sable de processus.
Les scripts de préchargement sont injectés avant le chargement des pages Web dans le moteur de rendu, exactement comme les content scripts des extensions Chrome. Afin d'ajouter des fonctionnalités pouvant nécessiter un acc ès privilégié à votre moteur de rendu, vous pouvez définir des objets globaux via l'API contextBridge.
Pour illustrer ce concept, vous allez créer un script de préchargement qui expose les versions de votre application de Chrome, Node et Electron dans le moteur de rendu.
Ajoutez tout d'abord un nouveau script preload.js
qui exposera les propriétés sélectionnées de l’objet process.versions
d’Electron au processus de rendu dans une variable globale versions
.
const { contextBridge } = require('electron')
contextBridge.exposeInMainWorld('versions', {
node: () => process.versions.node,
chrome: () => process.versions.chrome,
electron: () => process.versions.electron
//Nous pouvons exposer des variables en plus des fonctions
})
Pour associer ce script à votre processus de rendu, passez son chemin à l'option webPreferences.preload
dans le constructeur d'une BrowserWindow :
const { app, BrowserWindow } = require('electron')
const path = require('node: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()
})
Deux concepts de Node.js sont utilisés ici :
À ce stade, le moteur de rendu a accès au paramètre versions
global, alors affichons cette information dans la fenêtre. Cette variable est accessible via window.versions
ou simplement versions
. Créez maintenant un script renderer.js
qui utilise l'API DOM document.getElementById
pour remplacer le texte affiché par l'élément HTML dont la propriété id
a pour valeur info
.
const information = document.getElementById('info')
information.innerText = `Cette application utilise Chrome (v${versions.chrome()}), Node.js (v${versions.node()}), et Electron (v${versions.electron()})`
Enfin, modifiez votre index.html
en ajoutant un nouvel élément dont la propriété id
a pour valeur info
,et attachez votre script 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>Bonjour depuis le rendu d'Electron !</title>
</head>
<body>
<h1>Bonjour depuis le rendu d'Electron !</h1>
<p>👋</p>
<p id="info"></p>
</body>
<script src="./renderer.js"></script>
</html>
Après avoir suivi les étapes ci-dessus, votre application doit ressembler à ceci :
Et le code devrait ressembler à ceci:
- main.js
- preload.js
- index.html
- renderer.js
const { app, BrowserWindow } = require('electron/main')
const path = require('node: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/renderer')
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${window.versions.chrome()}), Node.js (v${window.versions.node()}), and Electron (v${window.versions.electron()})`
Communication entre les processus
Comme nous l'avons mentionné ci-dessus, le processus principal d'Electron et celui de rendu ont des responsabilités distinctes et ne sont pas interchangeables. Cela signifie qu’il n’est pas possible d’accéder directement aux API Node.js à partir du processus de rendu, ni au DOM (Document Object Model) HTML à partir du processus principal.
La solution à ce problème consiste à utiliser les modules ipcMain
et ipcRenderer
d’Electron pour réaliser une communication inter-processus (IPC). Ainsi, pour envoyer un message de votre page Web au processus principal, vous pouvez définir un écouteur dans le processus principal avec ipcMain.handle
et puis exposer une fonction qui appelle ipcRenderer.invoke
pour déclencher l'exécution du code de celui-ci à partir de votre script de préchargement.
Pour illustrer ceci, nous allons ajouter une fonction globale au moteur de rendu appelé ping()
qui retournera une chaîne du processus principal.
Tout d'abord, configurez l'appel invoke
dans votre script de préchargement:
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('versions', {
node: () => process.versions.node,
chrome: () => process.versions.chrome,
electron: () => process.versions.electron,
ping: () => ipcRenderer.invoke('ping')
// nous pouvons aussi exposer des variables en plus des fonctions
})
Remarquez comment nous enveloppons l'appel ipcRenderer.invoke('ping')
dans une fonction helper plutôt que d'exposer le module ipcRenderer
directement via le contextBridge. Vous ne devez jamais exposer directement l’ensemble du module ipcRenderer
via le préchargement. Cela donnerait à votre moteur de rendu la possibilité d'envoyer des messages IPC arbitraires au processus principal, représentant ainsi un vecteur d'attaque puissant pour du code malveillant.
Ensuite, configurez votre écouteur handle
dans le processus principal. Nous effectuons ceci avant de charger le fichier HTML afin de garantir que le gestionnaire soit prêt avant que vous envoyiez l'appel invoke
depuis le moteur de rendu.
const { app, BrowserWindow, ipcMain } = require('electron/main')
const path = require('node: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(() => {
ipcMain.handle('ping', () => 'pong')
createWindow()
})
Une fois que vous avez configuré l'expéditeur et le récepteur, vous pouvez maintenant envoyer des messages au processus principal messages depuis le moteur de rendu au travers du canal 'ping'
que vous venez de définir.
const func = async () => {
const response = await window.versions.ping()
console.log(response) // Affichera 'pong'
}
func()
Pour des explications plus détaillées sur l'utilisation des modules ipcRenderer
et ipcMain
, consultez le guide complet de la Communication Inter-Processus.
Récapitulatif
Un script de préchargement contient du code qui s'exécute avant que votre page web ne soit chargée dans la fenêtre du navigateur. Il a accès aux API DOM et à Node.js et est souvent utilisé pour exposer des API privilégiées au moteur de rendu via l'API contextBridge
.
Comme les processus principal et de rendu ont des responsabilités très différentes, Les applications Electron utilisent souvent le script de préchargement pour configurer des interfaces de communication inter-processus (IPC) pour passer des messages arbitraires entre les deux types de processus.
Dans la partie suivante du tutoriel, nous vous montrerons des ressources pour ajouter plus de fonctionnalités à votre application, puis vous enseigner à distribuer votre application aux utilisateurs.