Saltar al contenido principal

Using Preload Scripts

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.

Pre-cargar script sandboxing

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 disponiblesDetalles
Módulos ElectronMódulos de Process Renderer
Módulos Node.jsevents, timers, url
Globales PolyfilledBuffer, 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.

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

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

There are two Node.js concepts that are used here:

  • La cadena __dirname apunta a la ruta del script actualmente en ejecución (en este caso, la carpeta raíz de tu proyecto).
  • La API path.join une varios segmentos de rutas juntos, creando una cadena de ruta combinada que funciona en todas las plataformas.

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.

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

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>¡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í:

Aplicación Electron que muestra esta aplicación está usando Chrome (v102.0.5005.63), Node.js (v16.14.2), y Electron (v19.0.3)

Y el código debe verse así:

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

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:

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')
// también podemos exponer variables, no solo funciones
})
seguridad IPC

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.

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

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.

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

func()
info

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.