Aller au contenu principal

Utiliser des scripts de préchargement

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. On top of Electron modules, you can also access Node.js built-ins, as well as any packages installed 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.

Mise en bac à sable d'un script de préchargement

À 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 disponiblesDétails
Modules ElectronModules de processus de rendu
Modules Node.jsevents, timers, url
Polyfills globauxBuffer, process, clearImmediate, setImmediate

For more information, check out the Process Sandboxing guide.

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. To add features to your renderer that require privileged access, you can define global objects through the contextBridge API.

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 .

preload.js
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 :

main.js
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()
})
info

Deux concepts de Node.js sont utilisés ici :

  • La chaîne de caractères __dirname pointe vers le chemin du script en cours d'exécution (dans notre cas, le dossier racine de votre projet).
  • L'API path.join assemble plusieurs éléments de path, créant un chemin sous forme d'une chaîne de caractères fonctionnant sur toutes les plateformes.

À 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.

renderer.js
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 :

index.html
<!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 :

L&#39;application Electron montrant cette application utilise Chrome (v102.0.5005.63), Node.js (v16.14.2), et Electron (v19.0.3)

Et le code devrait ressembler à ceci:

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()
}
})

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:

preload.js
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
})
Sécurité IPC

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.

main.js
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.

renderer.js
const func = async () => {
const response = await window.versions.ping()
console.log(response) // Affichera 'pong'
}

func()
info

For more in-depth explanations on using the ipcRenderer and ipcMain modules, check out the full Inter-Process Communication guide.

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.