Using Preload Scripts
Esta es la parte 3 del tutorial de Electrón.
Objetivos de aprendizaje
En esta parte del tutorial, aprenderás qué es un script de precarga y cómo utilizar una para exponer de forma segura las API privilegiadas en el proceso de renderizado. También aprenderá cómo comunicarse entre los procesos principales y de representación con los módulos de comunicación entre procesos (IPC) de Electron.
¿Qué es un script de precarga?
El proceso principal de Electron es un entorno Node.js que tiene acceso completo al sistema operativo. On top of Electron modules, you can also access Node.js built-ins, as well as any packages installed via npm. Por otro lado, los procesos renderizadores ejecutan páginas web y no ejecutan Node.js por defecto por razones de seguridad.
Para unir los diferentes tipos de proceso de Electron, necesitaremos usar un script especial llamado precarga.
Aumentar el renderizador con un script de precarga
Un script de precarga de BrowserWindows se ejecuta en un contexto que tiene acceso tanto al DOM del HTML como a un subconjunto limitado de Node.js y APIs de Electron.
A partir de Electron 20 en adelante, los scripts de precarga son sandboxed por defecto y ya no tienen acceso a un entorno Node.js completo. En la práctica, esto significa que tiene una función de solicitud (require
) policompletada (Polyfilled) la cual solo tiene acceso a un conjunto limitado de API.
APIs disponibles | Detalles |
---|---|
Módulos Electron | Módulos de Process Renderer |
Módulos Node.js | events , timers , url |
Globales Polyfilled | Buffer , process , clearImmediate , setImmediate |
For more information, check out the Process Sandboxing guide.
Los scripts de precarga son inyectados antes de que una página web cargue en el renderizador, similar a los de una extensión de Chrome scripts de contenido. To add features to your renderer that require privileged access, you can define global objects through the contextBridge API.
Para demostrar este concepto, crearás un script de precarga que expone las versiones de Chrome, Node y Electron en el renderizador.
Añade un nuevo script preload.js
que expone las propiedades seleccionadas del objeto process.versions
de Electron al proceso de renderizado en una variable global versions
.
const { contextBridge } = require('electron')
contextBridge.exposeInMainWorld('versions', {
node: () => process.versions.node,
chrome: () => process.versions.chrome,
electron: () => process.versions.electron
// también podemos exponer variables, no solo funciones
})
Para adjuntar este script a tu proceso de renderizado, proporciona la ruta a tu script de precarga a la opción webPreferences.preload
en su constructor existente.
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()
})
There are two Node.js concepts that are used here:
En este punto, el renderizador tiene acceso de las versions
globales, así que vamos a mostrar esa información en la ventana. Se puede acceder a esta variable a través de window.versions
o simplemente versions
. Crea un script renderer.js
que utilice la document.getElementById
del DOM de la API para reemplazar el texto mostrado para el elemento HTML con info
como su propiedad id
.
const information = document.getElementById('info')
information.innerText = `Esta aplicación está usando Chrome (v${versions.chrome()}), Node.js (v${versions.node()}), and Electron (v${versions.electron()})`
Luego, modifica tu index.html
agregando un nuevo elemento con info
como su propiedad id
, y adjunta tu 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>¡Hola desde el renderizador Electron!</title>
</head>
<body>
<h1>¡Hola desde el renderizador Electron!</h1>
<p>👋</p>
<p id="info"></p>
</body>
<script src="./renderer.js"></script>
</html>
Después de seguir los pasos anteriores, tu aplicación debería ser algo así:
Y el código debe verse así:
- 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()})`
Comunicación entre procesos
Como hemos mencionado anteriormente, el proceso principal y renderizador de Electron tiene distintas responsabilidades y no son intercambiables. Esto significa que no es posible acceder al Node.js APIs directamente del proceso de renderizado, ni del Objeto de Documento HTML (DOM) del proceso principal.
La solución para este problema es utilizar los módulos ipcMain
y ipcRenderer
de Electron para la comunicación entre procesos (IPC). Para enviar un mensaje desde su página web al proceso principal, puede configurar un controlador de proceso principal con ipcMain.handle
y luego exponer una función que llame a ipcRenderer.invoke
para active el controlador en su secuencia de comandos de precarga.
Para ilustrar, añadiremos una función global al renderizador llamado ping()
que devolverá una cadena del proceso principal.
Primero, configura la llamada invoke
en tu script de precarga:
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('versions', {
node: () => process.versions.node,
chrome: () => process.versions.chrome,
electron: () => process.versions.electron,
ping: () => ipcRenderer.invoke('ping')
// también podemos exponer variables, no solo funciones
})
Observa cómo envolver la llamada ipcRenderer.invoke('ping')
en una función ayudante en lugar de que exponer el módulo ipcRenderer
directamente a través del puente contextual. Usted nunca desea exponer directamente todo el módulo ipcRenderer
a través de la precarga. Esto daría a su renderizador la capacidad de enviar mensajes IPC arbitrarios al proceso principal, que se convierte en un poderoso vector de ataque para código malicioso.
Luego, configura tu oyente handle
en el proceso principal. Hacemos esto antes de cargar el archivo HTML para garantizar que el controlador esté listo antes de enviar la llamada invoke
desde el renderizador.
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()
})
Una vez que tengas configurado el remitente y el receptor, ahora puedes enviar mensajes desde el renderizador al proceso principal a través del canal 'ping'
que acabas de definir.
const func = async () => {
const response = await window.versions.ping()
console.log(response) // prints out 'pong'
}
func()
For more in-depth explanations on using the ipcRenderer
and ipcMain
modules, check out the full Inter-Process Communication guide.
Resumen
Un script de precarga contiene el código que se ejecuta antes de que tu página web se cargue en la ventana del navegador. Tiene acceso tanto a las API de DOM como al entorno Node.js, y a menudo se utiliza para exponer APIs privilegiadas al renderizador a través de la API contextBridge
.
Debido a que los procesos principal y de representación tienen responsabilidades muy diferentes, las aplicaciones de Electron a menudo usan el script de precarga para configurar interfaces de comunicación entre procesos (IPC) para pasar mensajes arbitrarios entre los dos tipos de procesos.
En la siguiente parte del tutorial, le mostraremos recursos para agregar más funciones a su aplicación y luego le enseñaremos cómo distribuir su aplicación a los usuarios.