Skip to content

S10: Electron

Juan Gonzalez-Gomez edited this page Apr 18, 2023 · 13 revisions

Sesión 10: Electron

  • Tiempo: 2h (50 + 50min)
  • Fecha:
  • Objetivos de la sesión:
    • Entender Electron
    • Aprender a crear una aplicación Electron desde cero
    • Aprender a empaquetar la app para Linux

Contenido

Introducción

Electron es un paquete para node.js que te permite crear aplicaciones de escritorio con interfaces gráficas usando las tecnologías web: HTML, Javascript y CSS. Las aplicaciones creadas son multiplataforma: Linux, macOS y Windows

Típicamente para crear aplicaciones gráficas de escritorio debes utilizar los entornos nativos de cada sistema operativo. En el caso de Linux existen bibliotecas libres y multiplataformas, como por ejemplo GTK o QT. Sin embargo, dado que ya existen tecnologías abiertas que sirven para presentar la información (html, css y javascript), ¿Por qué no usarlas para hacer los interfaces gráficos de las aplicaciones? Así no es necesario aprender ninguna biblioteca particular para un sistema operativo

Algunas aplicaciones hechas con Electron

Aunque nunca hayas usado Electron, en realidad sí que has utilizado aplicaciones que están hechas con Electron

  • Visual code

  • MS Teams

  • WhatsApp Desktop

Arquitectura

Electrón se basa en tres elementos fundamentales:

  • El motor de navegación Chromium para mostrar las interfacs gráficas (como elementos web)
  • Node.js para el acceso al sistema de ficheros local y el sistema operaivo
  • API para acceder a funciones nativas del sistema operativo

En las aplicaciones de Electron tenemos dos tipos de procesos:

  • El proceso principal: main.js. Es el que toma el control al arrancar la aplicación y lanza los navegadores. Gestiona los procesos renderizadores
  • El proceso de renderizado gestiona sólo su página web (y no tiene visibilidad del resto de elementos). Se encarga de lanzar las GUIs como páginas webs en los "navegadores", y los procesos de renderizado (render.js), uno por cada ventana

Electron incluye los módulos ipcMain e ipcRenderer para la comunicación entre ambos procesos. Cuando desde el proceso principal se quiere pintar algo en la interfaz gráfica se envía la información al proceso de renderizado

Creando una aplicación Electron desde cero

Vamos a ir paso a paso construyendo nuestra primera aplicación electron, que simplemente tendrá un botón que al apretarlo imprimirá un texto en la pantalla

Fichero package.json inicial

Creamos un directorio de trabajo y él comenzamos escribiendo el fichero package.json inicial, donde ponemos el nombre de nuestra APP, la descripción, la versión, el nombre del fichero principal y el comando que se debe ejecutar para arrancarla al ejecutar npm start

  • Fichero: package.json
{
    "name": "mi-electron-app",
    "description": "Estas es mi primera aplicación de escritorio en Electron",
    "version": "0.1.0",
    "main": "main.js",
    "scripts": {
        "start": "electron ."
    }
}

Hasta ahora no lo habíamos hecho, pero en todas las aplicaciones hechas con Node (con o sin electron) debemos crear primero el package.json con la información de nuestra aplicación

Instalando Electron

Electron es un paquete de Node, que se instala como cualquier otro paquete usando npm

npn i electron

Se nos instalará la última versión de electron, que en el momento de hacer esta documentación es la 18.0.1. Como este mundo avanza muy rápido, en poco tiempo ya habrá otra versión más actualizada

Al realizar la instalación, npm automáticamente incluirá la versión de Electron usada en el fichero package.json. Si lo observamos ahora tiene esta pinta:

{
    "name": "mi-electron-app",
    "description": "Estas es mi primera aplicación de escritorio en Electron",
    "version": "0.1.0",
    "main": "main.js",
    "scripts": {
        "start": "electron ."
    },
    "dependencies": {
        "electron": "^18.0.1"
    }
}

Npm ha añadido el objeto "dependencies" a nuestro package.json de configuración. También se habrá creado el fichero package-lock.json que contiene información sobre los paquetes instalados. Esta información NO es para los humanos

La cadena usada para indicar la vesión es: "^18.0.1". Significa que serían válidas cualquier versión de electron iguales o superiores a la 18.0.1, pero INFERIORES a la 19.0.0. Puedes encontrar más información sobre la semántica de los números de versión en este enlace

Fichero principal. Versión 1

Ahora creamos el fichero principal: main.js, que es el que contiene el código javascript que se ejecutará. Empezamos con el programa mínimo: cargamos el módulo electron e imprimimos un mensaje en la consola

  • Fichero: main.js (versión 1)
//-- Cargar el módulo de electron
const electron = require('electron');

console.log("Arrancando electron...");

Lo ejecutamos desde el terminal con el comando npm start:

En la consola nos aparece el mensaje Arrancando electron... y el programa no termina. El control lo tiene electron, y para que termine hay que llamar a las funciones de su API. En esta primer versión lo interrumpimos pulsando ctrl-c desde la consola

Módulo App. Añadiendo el evento ready (Versión 2)

En el módulo electron.app se encuentran todos los eventos para el control de la aplicación. Se usan desde el proceso principal. El evento ready nos indica que los módulos de electron se han cargado, y que está listo para ejecutar el proceso principal

Ampliamos nuestro código (versión 2) con los siguiente. Todavía no hacemos nada. Simplemente se pone una traza para comprobar que ha llegado el evento ready

//-- Cargar el módulo de electron
const electron = require('electron');

console.log("Arrancando electron...");

//-- Punto de entrada. En cuanto electron está listo,
//-- ejecuta esta función
electron.app.on('ready', ()=>{
    console.log("Evento Ready!")
});

Lo ejecutamos. Esto es lo que vemos en la consola:

Efectivamente vemos la traza de Ready. ¡Nuestro código ya tiene el control! 😀️

Creando la ventana principal (Versión 3)

La ventana principal de nuestra aplicación se crea llamando al método electron.BrowserWindow(), al que se le pasa como parámetro un objeto donde se definen las propiedades de la ventana. Nosotros le pasaremos los atributos width y height para establecer el tamaño a 600x200

//-- Cargar el módulo de electron
const electron = require('electron');

console.log("Arrancando electron...");

//-- Variable para acceder a la ventana principal
//-- Se pone aquí para que sea global al módulo principal
let win = null;

//-- Punto de entrada. En cuanto electron está listo,
//-- ejecuta esta función
electron.app.on('ready', () => {
    console.log("Evento Ready!");

    //-- Crear la ventana principal de nuestra aplicación
    win = new electron.BrowserWindow({
        width: 600,  //-- Anchura 
        height: 400  //-- Altura
    });

});

Esto crea una ventana en blanco, ya que todavía no hemos añadido nada. Nos aparecerá en su parte superior el menú por defecto, que tiene las opciones de File, Edit, View, Window y Help

Todavía no hace nada pero... ¡Hemos creado nuestra primera aplicación gráfica multiplataforma! 😀️

Desactivando el menú por defecto

El menú por defecto que sale lo podemos eliminar llamando al método setMenuBarVisibility() del objeto win

//-- Cargar el módulo de electron
const electron = require('electron');

console.log("Arrancando electron...");

//-- Variable para acceder a la ventana principal
//-- Se pone aquí para que sea global al módulo principal
let win = null;

//-- Punto de entrada. En cuanto electron está listo,
//-- ejecuta esta función
electron.app.on('ready', () => {
    console.log("Evento Ready!");

    //-- Crear la ventana principal de nuestra aplicación
    win = new electron.BrowserWindow({
        width: 600,  //-- Anchura 
        height: 400  //-- Altura
    });

  //-- En la parte superior se nos ha creado el menu
  //-- por defecto
  //-- Si lo queremos quitar, hay que añadir esta línea
  win.setMenuBarVisibility(false)
});

Ahora, al lanzar la aplicación nos aparecerá una ventana en blanco, de tamaño 600x400, sin ningún menú

Sin embargo, en los siguientes ejemplos dejaremos activado el menú porque utilizamos las herramientas del desarrollador, que se encuentran en la pestaña de view

Cargando contenido web en la ventana (Versión 4)

Las ventanas que abrimos en Electron en realidad son... ¡Navegadores! Por lo que en ellos metemos contenido web (html, css, javascript). Estas páginas web pueden ser externas (descargadas de una URL de internet) o bien internas: nuestras propias páginas locales

En este ejemplo cargamos la página de la Escuela: https://www.urjc.es/eif, utilizando el método win.loadURL()

  • Fichero: main.js (versión 4)
//-- Cargar el módulo de electron
const electron = require('electron');

console.log("Arrancando electron...");

//-- Variable para acceder a la ventana principal
//-- Se pone aquí para que sea global al módulo principal
let win = null;

//-- Punto de entrada. En cuanto electron está listo,
//-- ejecuta esta función
electron.app.on('ready', () => {
    console.log("Evento Ready!");

    //-- Crear la ventana principal de nuestra aplicación
    win = new electron.BrowserWindow({
        width: 600,  //-- Anchura 
        height: 400  //-- Altura
    });

  //-- En la parte superior se nos ha creado el menu
  //-- por defecto
  //-- Si lo queremos quitar, hay que añadir esta línea
  //win.setMenuBarVisibility(false)

  //-- Cargar contenido web en la ventana
  //-- La ventana es en realidad.... ¡un navegador!
  win.loadURL('https://www.urjc.es/eif');

});

Al arrancar la app vemos la página de la escuela. El título de la ventana se ha sustituido por el proporcionado por la página web

Nuestra primera interfaz gráfica en HTML

En vez de cargar una URL externa podemos abrir un fichero html local, con nuestra interfaz gráfica de la aplicación. Esto lo hacemos con el método win.loadFile(), que carga el fichero html que le pasemos como parámetro

Creamos el fichero index.html con este contenido:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Mi APP (version 5)</title>
</head>
<body>
    <h1>INTERFAZ GRÁFICA</h1>
   <input type="button" value="Test" id="btn_test">
    <p>Display</p>
</body>
</html>

y desde nuestro proceso main lo cargamos en la ventana principal

  • Fichero main.js (versión 5)
//-- Cargar el módulo de electron
const electron = require('electron');

console.log("Arrancando electron...");

//-- Variable para acceder a la ventana principal
//-- Se pone aquí para que sea global al módulo principal
let win = null;

//-- Punto de entrada. En cuanto electron está listo,
//-- ejecuta esta función
electron.app.on('ready', () => {
    console.log("Evento Ready!");

    //-- Crear la ventana principal de nuestra aplicación
    win = new electron.BrowserWindow({
        width: 600,  //-- Anchura 
        height: 400  //-- Altura
    });

  //-- En la parte superior se nos ha creado el menu
  //-- por defecto
  //-- Si lo queremos quitar, hay que añadir esta línea
  //win.setMenuBarVisibility(false)

  //-- Cargar contenido web en la ventana
  //-- La ventana es en realidad.... ¡un navegador!
  //win.loadURL('https://www.urjc.es/etsit');

  //-- Cargar interfaz gráfica en HTML
  win.loadFile("index.html");

});

Al lanzar la aplicación con npm start nos aparece una ventana con este contenido

¡Nuestra primera interfaz gráfica en HTML funciona! 😀️

El botón de Test no hace nada todavía, ya que no hemos añadido código javascript asociado a ese fichero HTML

Activando la política de seguridad de contenido (CSP) (Versión 6)

En esta asignatura no nos estamos preocupando demasiado por la securidad. Nos estamos centrando en aprender los mecanismos básicos que hay en los servidores. PERO la seguridad es un tema muy importante. Y sobre todo si hacemos aplicaciones con Electron, ya que al usar tecnología web se podría sufrir un ataque que haga que se ejecute código javascript malicioso, y acceder totalmente a nuestra máquina (no sólo al navegador)

Si ejecutamos el ejemplo anterior (versión 5) y abrimos la consola del navegador dentro de las herramientas del desarrollador, veremos un aviso de seguridad (warning)

(NOTA: El contenido de la página se ve en fondo negro porque tengo activado el tema dark en Chrome)

Esta es la cabecera del mensaje:

⚠️ Electron Security Warning (Insecure Content-Security-Policy)

Nos está avisando de que no hemos establecido ninguna política de seguridad de contenidos. Y por tanto se podría inyectar código malicioso en nuestra aplicación, si sufrimos un ataque

Vamos a establecer la política de que el navegador sólo pueda mostrar y ejecutar ficheros que estén en nuestra máquina. Eso lo hacemos con la siguente cabecera en el fichero html:

<meta http-equiv="Content-Security-Policy" content="default-src 'self';" />

El fichero html nuevo nos queda como el siguiente:

  • Fichero: index.html (version 6)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <!-- Only scripts and files from the local machine are allowed -->
    <!-- It removes the security Warning: Insecure Content-Security-Policy -->
    <meta http-equiv="Content-Security-Policy" content="default-src 'self';" />

    <title>Mi APP (version 6)</title>
</head>
<body>
    <h1>INTERFAZ GRÁFICA</h1>
   <input type="button" value="Test" id="btn_test">
    <p>Display</p>
</body>
</html>

Ahora al abrir la consola ya NO vemos el aviso anterior

Añadiendo imágenes y estilo a la interfaz (Versión 7)

La interfaz la definimos exactamente igual que cualquier página web. Como ejemplo vamos a añadir esta imagen y un archivo de estilo

  • Fichero: index.html (version 7)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <!-- Only scripts and files from the local machine are allowed -->
    <!-- It removes the security Warning: Insecure Content-Security-Policy -->
    <meta http-equiv="Content-Security-Policy" content="default-src 'self';" />

    <link rel="stylesheet" href="style.css">

    <title>Mi APP (version 7)</title>
</head>
<body>
    <h1>INTERFAZ GRÁFICA</h1>
    <img src="logo-urjc.png" alt="Logo-URJC" width=150px>
    <p>   <input type="button" value="Test" id="btn_test"> </p>

   <p>Display:</p>
    <p id="display"></p>
</body>
</html>
  • Fichero: style.ccs
h1 {
    width: 93%;
    padding: 20px;
    background-color: lightgreen;
    border-radius: 10px;
    text-align: center;
  }
  
  #display {
      border: 2px solid lightblue;
      padding: 10px;
  }

Esto es lo que aparece ahora al ejecutar la aplicación:

Proceso de renderizado asociado a la interfaz (Versión 8)

Por último añadimos código javascript asociado la página web de interfaz. Es el proceso de renderizado. Comenzaremos con uno muy sencillo que simplemente muestra un mensaje de texto en el display al apretar el botón de Test. Además sacará un mensaje por la consola del navegador, para comprobar que todo está funcionando bien

  • Fichero index.js
console.log("Hola desde el proceso de la web...");

//-- Obtener elementos de la interfaz
const btn_test = document.getElementById("btn_test");
const display = document.getElementById("display");

btn_test.onclick = () => {
    display.innerHTML += "TEST! ";
    console.log("Botón apretado!");
}

En el fichero HTML añadimos la línea para cargar el script index.js:

  • Fichero: index.html (versión 8)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <!-- Permitir sólo scripts y ficheros de la máquina local -->
    <!-- Con esto se elimina el aviso de seguridad: Insecure Content-Security-Policy -->
    <meta http-equiv="Content-Security-Policy" content="default-src 'self';" />

    <!-- Hoja de estilo de la interfaz -->
    <link rel="stylesheet" href="style.css">

    <!-- Cargar el proceso asociado a esta página -->
    <script src="index.js" defer></script>

    <title>Mi APP (version 8)</title>
</head>
<body>
    <h1>INTERFAZ GRÁFICA</h1>
    <img src="logo-urjc.png" alt="Logo-URJC" width=150px>
    <p>   <input type="button" value="Test" id="btn_test"> </p>

   <p>Display:</p>
    <p id="display"></p>
</body>
</html>

Al arrancar la aplicación y apretar el botón nos aparecerá el mensaje "TEST!" en el display

En la consola del navegador veremos los mensajes mostrados por el proceso index.js

En esta animación lo vemos en funcionamiento

¡Nuestra primera aplicación en Electron funciona!!

Acceso al sistema desde las aplicaciones de Electron

El proceso principal de Electron (main.js) es una aplicación nativa de node.js, por lo que se tiene acceso a TODOS los recursos de la máquina, a través de las bibliotecas de Node

Sin embargo, desde los procesos asociados a las interfaces gráficas (los procesos de renderización) NO HAY ACCESO por defecto. Es es así por razones de seguridad. Hay dos enfoques para lograrlo (que se pueden combinar si se quiere):

  • Dar permisos de acceso al proceso que se ejecuta en la ventana
  • Establecer canales de comunicación entre el proceso main (que tiene privilegios) y el proceso de renderizado (que no los tiene) para que intercambien información

Proceso de renderización con permisos (Versión 9)

Para poder acceder a la API de node (y por tanto al Sistema) desde la página web de la interfaz, tenemos que establecer los valores de las propiedades nodeIntegration y contextIsolation del objeto webPreferences de la ventana. Esto se hace desde el proceso principal al crear la ventana con `electron.BrowserWindow()'

//-- Crear la ventana principal de nuestra aplicación
  win = new electron.BrowserWindow({
      //-- Dar valores a las propiedades: altura, anchura...
      //-- .........
     
      //-- Permitir que la ventana tenga ACCESO AL SISTEMA
      webPreferences: {
        nodeIntegration: true,
        contextIsolation: false
      }
  });

Como ejemplo vamos a ampliar nuestra aplicación de Electron desarrollada en los apartados anteriores para incluir información del sistema: Arquitectura, plataforma y directorio de trabajo. Esto se optiene a través de las propiedades y métodos de process, que está accesible en las aplicaciones node: process.arch, process.platform y process.cwd()

Primero lo probamos SIN tener permisos de acceso, para ver lo que ocurre. En la interfaz añadimos un lista con los elementos que queremos obtener del sistema. Esta información se situará en los bloques span identificados como info1, info2 e info3. Estos bloques pertecenen todos a la clase blue a la que se le ha asociado el color azúl. De esta manera la información saldrá resaltada en azúl

  • Fichero: index.html (versión 9)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <!-- Permitir sólo scripts y ficheros de la máquina local -->
    <!-- Con esto se elimina el aviso de seguridad: Insecure Content-Security-Policy -->
    <meta http-equiv="Content-Security-Policy" content="default-src 'self';" />

    <!-- Hoja de estilo de la interfaz -->
    <link rel="stylesheet" href="style.css">

    <!-- Cargar el proceso asociado a esta página -->
    <script src="index.js" defer></script>

    <title>Mi APP (version 9)</title>
</head>
<body>
    <h1>INTERFAZ GRÁFICA</h1>
    <img src="logo-urjc.png" alt="Logo-URJC" width=150px>

    <hr>
    <h2>Información:</h2>
    <ul>
        <li>Arquitectura: <span id="info1" class="blue"></span> </li>
        <li>Plataforma: <span id="info2" class="blue"></span></li>
        <li>Diretorio: <span id="info3" class="blue"></span></li>
    </ul>

    <hr>
    <p>   <input type="button" value="Test" id="btn_test"> </p>
   <p>Display:</p>
    <p id="display"></p>
</body>
</html>

Este es el nuevo fichero de estilo:

  • Fichero: style.css (versión 9)
h1 {
    width: 93%;
    padding: 20px;
    background-color: lightgreen;
    border-radius: 10px;
    text-align: center;
  }

.blue {
    color: blue;
}

  #display {
      border: 2px solid lightblue;
      padding: 10px;
  }

Y este es el proceso de renderización

  • Fichero index.js (versión 9)
console.log("Hola desde el proceso de la web...");

//-- Obtener elementos de la interfaz
const btn_test = document.getElementById("btn_test");
const display = document.getElementById("display");
const info1 = document.getElementById("info1");
const info2 = document.getElementById("info2");
const info3 = document.getElementById("info3");

//-- Acceder a la API de node para obtener la info
//-- Sólo es posible si nos han dado permisos desde
//-- el proceso princpal
info1.textContent = process.arch;
info2.textContent = process.platform;
info3.textContent = process.cwd();


btn_test.onclick = () => {
    display.innerHTML += "TEST! ";
    console.log("Botón apretado!");
}

Este proceso accede a la información del sistema y la inserta en los bloques span. PERO como no tiene permisos, se produce un error y la información NO se muestra:

Si abrimos la consola del navegador veremos el error en rojo

El error nos informa de que NO conoce ningún objeto llamado process

Uncaught ReferenceError: process is not defined

Para tener acceso activamos los permisos desde el proceso principal:

//-- Cargar el módulo de electron
const electron = require('electron');

console.log("Arrancando electron...");

//-- Variable para acceder a la ventana principal
//-- Se pone aquí para que sea global al módulo principal
let win = null;

//-- Punto de entrada. En cuanto electron está listo,
//-- ejecuta esta función
electron.app.on('ready', () => {
    console.log("Evento Ready!");

    //-- Crear la ventana principal de nuestra aplicación
    win = new electron.BrowserWindow({
        width: 600,   //-- Anchura 
        height: 600,  //-- Altura

        //-- Permitir que la ventana tenga ACCESO AL SISTEMA
        webPreferences: {
          nodeIntegration: true,
          contextIsolation: false
        }
    });

  //-- En la parte superior se nos ha creado el menu
  //-- por defecto
  //-- Si lo queremos quitar, hay que añadir esta línea
  //win.setMenuBarVisibility(false)

  //-- Cargar contenido web en la ventana
  //-- La ventana es en realidad.... ¡un navegador!
  //win.loadURL('https://www.urjc.es/etsit');

  //-- Cargar interfaz gráfica en HTML
  win.loadFile("index.html");

});

Y ahora al ejecutar la aplicación YA SI que vemos la información del sistema

Comunicación entre el proceso principal y el de renderizado de la interfaz gráfica

Los procesos principal y de renderizado se pueden comunicar utilizando los módulos electron.ipcMain y electron.ipcRenderer. Esta comunicación se basa en el intercambio de mensajes asíncronos, igual que en la biblioteca sokets.io que ya conocemos. El emisor envía un mensaje al receptor utilizando el método send(nombre-evento, mensaje) al que se le pasa como argumentos primero el nombre del evento (definido por nosotros) y luego la cadena con el mensaje a mandar

El receptor se pone a la escucha del evento indicado, usando una función de retrollamada. Al llegar el mensaje se ejecuta esa función

La forma de realizarlo varía según que esta comunicación sea desde el proceso princial al de renderizado o vice-versa. Esto es debido a que sólo hay un proceso principal, pero puede haber muchos de renderizado (cada uno asociado a su ventana gráfica)

Mediante este mecanismo se puede lograr que el proceso de renderizado NO tenga que acceder al Sistema, sino que le pida la información sensible al proceso main. Esto hará que la aplicación sea más segura. No obstante, por facilidad de los ejemplos, se seguirá dando permisos al proceso de renderizado

Paso de mensajes del proceso principal al de renderizado (Versión 10)

Como ejemplo modificaremos nuestra aplicación para que el proceso principal le envíe al de renderizado un mensaje asociado al evento print (el nombre del evento lo definimos nosotros. Podemos poner cualquiera). Al recibirlo, simplemente lo mostrará en la interfaz gráfica, en un párrafo nuevo creado para ello

  • Fichero: index.html (versión 10)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <!-- Permitir sólo scripts y ficheros de la máquina local -->
    <!-- Con esto se elimina el aviso de seguridad: Insecure Content-Security-Policy -->
    <meta http-equiv="Content-Security-Policy" content="default-src 'self';" />

    <!-- Hoja de estilo de la interfaz -->
    <link rel="stylesheet" href="style.css">

    <!-- Cargar el proceso asociado a esta página -->
    <script src="index.js" defer></script>

    <title>Mi APP (version 10)</title>
</head>
<body>
    <h1>INTERFAZ GRÁFICA</h1>
    <img src="logo-urjc.png" alt="Logo-URJC" width=150px>

    <hr>
    <h2>Información:</h2>
    <ul>
        <li>Arquitectura: <span id="info1" class="blue"></span> </li>
        <li>Plataforma: <span id="info2" class="blue"></span></li>
        <li>Diretorio: <span id="info3" class="blue"></span></li>
    </ul>

    <hr>
    <p>Mensajes enviados desde el proceso main:</p>
    <p>Print:  <span id='print' class="red"></span> </p>
    <p>   <input type="button" value="Test" id="btn_test"> </p>
   <p>Display:</p>
    <p id="display"></p>
</body>
</html>
  • Fichero: style.css
h1 {
    width: 93%;
    padding: 20px;
    background-color: lightgreen;
    border-radius: 10px;
    text-align: center;
  }

.blue {
    color: blue;
}

.red {
  color: red;
}

  #display {
      border: 2px solid lightblue;
      padding: 10px;
  }

En el proceso principal enviamos el mensaje utilizando el método win.webContents.send():

  win.webContents.send('print', "MENSAJE ENVIADO DESDE PROCESO MAIN");

Para enviarlo esperamos primero a que la ventana esté lista, así ya sabremos que el proceso de renderizado se ha ejecutado. Esto lo hacemos escuchando el evento ready-to-show

Este es el código completo del proceso main

  • Fichero: main.js (versión 10)
//-- Cargar el módulo de electron
const electron = require('electron');

console.log("Arrancando electron...");

//-- Variable para acceder a la ventana principal
//-- Se pone aquí para que sea global al módulo principal
let win = null;

//-- Punto de entrada. En cuanto electron está listo,
//-- ejecuta esta función
electron.app.on('ready', () => {
    console.log("Evento Ready!");

    //-- Crear la ventana principal de nuestra aplicación
    win = new electron.BrowserWindow({
        width: 600,   //-- Anchura 
        height: 600,  //-- Altura

        //-- Permitir que la ventana tenga ACCESO AL SISTEMA
        webPreferences: {
          nodeIntegration: true,
          contextIsolation: false
        }
    });

  //-- En la parte superior se nos ha creado el menu
  //-- por defecto
  //-- Si lo queremos quitar, hay que añadir esta línea
  //win.setMenuBarVisibility(false)

  //-- Cargar contenido web en la ventana
  //-- La ventana es en realidad.... ¡un navegador!
  //win.loadURL('https://www.urjc.es/etsit');

  //-- Cargar interfaz gráfica en HTML
  win.loadFile("index.html");

  //-- Esperar a que la página se cargue y se muestre
  //-- y luego enviar el mensaje al proceso de renderizado para que 
  //-- lo saque por la interfaz gráfica
  win.on('ready-to-show', () => {
    console.log("HOLA?");
    win.webContents.send('print', "MENSAJE ENVIADO DESDE PROCESO MAIN");
  });

});

En el proceso de renderizado se usa el método electron.ipcRenderer.on() para escuchar el evento y ejecutar la función de retrollamada al recibirlo

//-- Mensaje recibido del proceso MAIN
electron.ipcRenderer.on('print', (event, message) => {
    console.log("Recibido: " + message);
    print.textContent = message;
  });

Este es el código completo:

  • Fichero: index.js (version 10)
const electron = require('electron');

console.log("Hola desde el proceso de la web...");

//-- Obtener elementos de la interfaz
const btn_test = document.getElementById("btn_test");
const display = document.getElementById("display");
const info1 = document.getElementById("info1");
const info2 = document.getElementById("info2");
const info3 = document.getElementById("info3");
const print = document.getElementById("print");

//-- Acceder a la API de node para obtener la info
//-- Sólo es posible si nos han dado permisos desde
//-- el proceso princpal
info1.textContent = process.arch;
info2.textContent = process.platform;
info3.textContent = process.cwd();


btn_test.onclick = () => {
    display.innerHTML += "TEST! ";
    console.log("Botón apretado!");
}

//-- Mensaje recibido del proceso MAIN
electron.ipcRenderer.on('print', (event, message) => {
    console.log("Recibido: " + message);
    print.textContent = message;
  });

Al ejecutarlo vemos en rojo el mensaje que ha enviado el proceso main al de renderizado

Paso de mensajes del proceso de renderizado al principal (Versión 11)

El proceso de renderizado envía mensajes al proceso principal a través del método electron.ipcRenderer.invoke(). Y en el proceso principal se utiliza el método electron.ipcMain.handle() para estar a la escucha del evento y ejecutar la función de retrollamada asociada

Como ejemplo haremos que cada vez que se apriete el botón de Test de la interfaz gráfica, se le envíe la notificación test al proceso principal y este simplemente escriba una cadena en la consola

  • Fichero: index.js (version 11)
const electron = require('electron');

console.log("Hola desde el proceso de la web...");

//-- Obtener elementos de la interfaz
const btn_test = document.getElementById("btn_test");
const display = document.getElementById("display");
const info1 = document.getElementById("info1");
const info2 = document.getElementById("info2");
const info3 = document.getElementById("info3");
const print = document.getElementById("print");

//-- Acceder a la API de node para obtener la info
//-- Sólo es posible si nos han dado permisos desde
//-- el proceso princpal
info1.textContent = process.arch;
info2.textContent = process.platform;
info3.textContent = process.cwd();


btn_test.onclick = () => {
    display.innerHTML += "TEST! ";
    console.log("Botón apretado!");

    //-- Enviar mensaje al proceso principal
    electron.ipcRenderer.invoke('test', "MENSAJE DE PRUEBA: Boton apretado");
}

//-- Mensaje recibido del proceso MAIN
electron.ipcRenderer.on('print', (event, message) => {
    console.log("Recibido: " + message);
    print.textContent = message;
  });
  • Fichero: main.js (versión 11)
//-- Cargar el módulo de electron
const electron = require('electron');

console.log("Arrancando electron...");

//-- Variable para acceder a la ventana principal
//-- Se pone aquí para que sea global al módulo principal
let win = null;

//-- Punto de entrada. En cuanto electron está listo,
//-- ejecuta esta función
electron.app.on('ready', () => {
    console.log("Evento Ready!");

    //-- Crear la ventana principal de nuestra aplicación
    win = new electron.BrowserWindow({
        width: 600,   //-- Anchura 
        height: 600,  //-- Altura

        //-- Permitir que la ventana tenga ACCESO AL SISTEMA
        webPreferences: {
          nodeIntegration: true,
          contextIsolation: false
        }
    });

  //-- En la parte superior se nos ha creado el menu
  //-- por defecto
  //-- Si lo queremos quitar, hay que añadir esta línea
  //win.setMenuBarVisibility(false)

  //-- Cargar contenido web en la ventana
  //-- La ventana es en realidad.... ¡un navegador!
  //win.loadURL('https://www.urjc.es/etsit');

  //-- Cargar interfaz gráfica en HTML
  win.loadFile("index.html");

  //-- Esperar a que la página se cargue y se muestre
  //-- y luego enviar el mensaje al proceso de renderizado para que 
  //-- lo saque por la interfaz gráfica
  win.on('ready-to-show', () => {
    win.webContents.send('print', "MENSAJE ENVIADO DESDE PROCESO MAIN");
  });

  //-- Enviar un mensaje al proceso de renderizado para que lo saque
  //-- por la interfaz gráfica
  win.webContents.send('print', "MENSAJE ENVIADO DESDE PROCESO MAIN");

});


//-- Esperar a recibir los mensajes de botón apretado (Test) del proceso de 
//-- renderizado. Al recibirlos se escribe una cadena en la consola
electron.ipcMain.handle('test', (event, msg) => {
  console.log("-> Mensaje: " + msg);
});

Al ejecutarlo y darle al botón de Test vemos que en la consola aparecen los mensajes recibiso del proceso de renderizado

En esta animación lo vemos en funcionamiento

Empaquetando la aplicación Electron (Versión 11)

Una de las ventajas de Electron es que nos permite crear ficheros ejecutables para las diferentes plataformas (Linux, Mac, Windows) de manera sencilla. Para ello vamos a utilizar el paquete electron-builder

Lo primero que hacemos es completar el fichero package.json añadiendo más información. En la primera parte están los datos de la aplicación: nombre, autor, descripción,... En el apartado "scripts" hay que añadir tres nuevos, que son los necesarios para ejecutar electron-builder y generar el paquete.

En este ejemplo empaquetaremos la aplicción para Linux, generando un fichero en formato appimage. En la página de electron-builder hay información para hacer el empaquetado en el resto de plataformas.

Este es el nuevo fichero package.json:

{
    "name": "mi-electron-app",
    "description": "Estas es mi primera aplicación de escritorio en Electron",
    "version": "0.1.0",
    "main": "main.js",
    "author": {
        "name": "Obijuan"
    },
    "homepage": "",
    "license": "LGPL",
    "scripts": {
        "start": "electron .",
        "pack": "electron-builder --dir",
        "dist": "electron-builder",
        "postinstall": "electron-builder install-app-deps"
    },
    "build": {
        "appId": "mi-electron-app-1-id",
        "linux": {
            "target": [
                "AppImage"
            ],
            "category": "Utility"
        }
    },
    "devDependencies": {
        "electron": "^18.0.1"
    }
}

El siguiente paso es instalar electron-builder utilizando este comando:

npm i electron-builder

Al hacerlo, veremos que nos ha metido una entrada nueva en devDependencies en el package.json:

"devDependencies": {
        "electron": "^18.0.1",
        "electron-builder": "^22.14.13"
}

Para hacer el empaquetado sólo hay que ejecutar el siguiente comando:

npm run dist

En la consola aparerán estos mensajes:

El empaquetador ha creado un nuevo directorio: dist. En él encontraremos el fichero mi-electron-app-0.1.0.AppImage que contiene nuestra aplicación lista para ser ejecuta en cualquier Linux!

Para probarlo sólo es necesario asegurarnos que tiene permisos de ejecución y listo! Lo podemos ejecutar directamente desde el navegador de ficheros, o desde la consola

Autor

Créditos

Licencia

Enlaces

TEORIA

Soluciones

LABORATORIO

Prácticas y sesiones de laboratorio

Práctica 0: Herramientas

Práctica 1: Node.js: Tienda Básica

Práctica 2: Interacción cliente-servidor. Tienda mejorada

Práctica 3: Websockets: Chat

Practica 4: Electron: Home Chat

  • L11: Home chat (25-Abril-2023)
  • L12: Laboratorio puro. NO hay contenido nuevo (8-Mayo-2023)

Cursos anteriores

Clone this wiki locally