Práctica 4: Electron - TinaTabo/2020-2021-LTAW-Practicas GitHub Wiki

Portada

Contenidos

Documentación Técnica

Requisitos previos:

  • S.O: Windows 10, Ubuntu o Mac
  • Programas: Visual Studio Code, Navegador Firefox, Nodejs(Versión 14.15.5) o superior.

Funcionamiento

En esta práctica el objetivo ha sido crear una aplicación electron nativa a partir de la práctica 3.
Esta aplicación se compone de una interfaz gráfica que muestra información relativa al sistema, un contador de usuarios conectados, muestra la ip a la que debe conectarse un dispositivo para poder unirse al chat y un display donde se hará el registro de todas las interacciones de los diferentes usuarios con el chat. Además cuenta con un botón que enviará un mensaje de prueba.
Para implementar esta práctica, se crea un servidor el cual se encarga de gestionar la interfaz de la aplicación y las distintas sesiones de chat que utilizan los usuarios (estas últimas siguiendo las especificaciones de la P3). El código utilizado para ello, main.js es el siguiente:

main.js

//-- Cargar las dependencias
const socket = require('socket.io');
const http = require('http');
const express = require('express');
const colors = require('colors');
const ip = require('ip');
const electron = require('electron');
const process = require('process');

//-- Puerto donde se utilizará el chat.
const PUERTO = 9000;

//-- Notificaciones del chat
const command_list = "Estos son los comandos soportados por BangChat:<br>"
                   + "/help: Muestra esta lista de comandos soportados<br>"
                   + "/list: Devuelve el número de usuarios conectados<br>"
                   + "/hello: El servidor devuelve un saludo<br>"
                   + "/date: El servidor nos devuelve la fecha actual";

const msg_hello = "Hello Army!";
const msg_welcome = "Bienvenid@ a BangChat!";
const msg_bye = "Bye Bye!";
const msg_newuser = "Un/a nuev@ Army se ha unido a BangChat";
//-- Obtener la fecha actual
const date = new Date(Date.now());
//-- Contador de usuarios conectados
let users_count = 0;

//-- Crear una nueva aplciacion web
const app = express();

//-- Crear un servidor, asosiaco a la App de express
const server = http.Server(app);

//-- Crear el servidor de websockets, asociado al servidor http
const io = socket(server);

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

//-------- PUNTOS DE ENTRADA DE LA APLICACION WEB
//-- Definir el punto de entrada principal de mi aplicación web
app.get('/', (req, res) => {
  path = __dirname + '/public/chat_main.html';
  res.sendFile(path);
  console.log("solicitud de acceso al chat");
});

//-- Esto es necesario para que el servidor le envíe al cliente la
//-- biblioteca socket.io para el cliente
app.use('/', express.static(__dirname +'/'));

//-- Directorio público que contiene ficheros estáticos.
app.use(express.static('public'));


//------------------- GESTION SOCKETS IO
//-- Evento: Nueva conexion recibida
io.on('connect', (socket) => {
  
  console.log('** NUEVA CONEXIÓN **'.yellow);
  //-- Contabilizar al nuevo usuario
  users_count += 1;
  //-- Enviar al render
  win.webContents.send('users', users_count);
  
  //-- Enviar mensaje de bienvenida al usuario.
  socket.send(msg_welcome);

  //-- Notificar al resto de usuarios que un nuevo
  //-- usuario a accedido al chat.
  io.send(msg_newuser);
  //-- Enviar al render
  win.webContents.send('msg', msg_newuser);

  //-- Evento de desconexión
  socket.on('disconnect', function(){
    console.log('** CONEXIÓN TERMINADA **'.yellow);
    //-- Enviar mensaje de despedida al usuario.
    io.send(msg_bye);
    //-- Enviar al render
    win.webContents.send('msg', msg_bye);

    //-- Actualizar el numero de usuarios conectados
    users_count -= 1;
    //-- Enviar al render
    win.webContents.send('users', users_count);
  });  

  //-- Mensaje recibido: Reenviarlo a todos los clientes conectados
  socket.on("message", (msg)=> {
    console.log("Mensaje Recibido!: " + msg.blue);

    //-- Enviar mensaje al render
    win.webContents.send('msg', msg);

    //-- Aqui comienza el tratamiento de los comandos especiales.
    if (msg.startsWith('/')) {
      console.log("Comando Especial".red.bold);
      switch(msg){
        case '/help':
          console.log("Mostrar lista de comandos especiales".red.bold);
          msg = command_list;
          socket.send(msg);
          break;
        case '/list':
          console.log("Mostrar número de usuarios conectados".red.bold);
          msg = users_count;
          socket.send("Hay " + msg + " usuarios conectados.");
          break;
        case '/hello':
          console.log("Obtener saludo del servidor".red.bold);
          msg = msg_hello;
          socket.send(msg);
          break;
        case '/date':
          console.log("Obtener fecha actual".red.bold);
          msg = date;
          socket.send(msg);
          break;
        default:
          console.log("comando no reconocido".red.bold);
          msg = "Comando NO reconocido.";
          socket.send(msg);
          break;
      }
    } else {
      //-- Reenviarlo a todos los clientes conectados
      io.send(msg);
    }; 
  });
});

//-- Lanzar el servidor HTTP
//-- ¡Que empiecen los juegos de los WebSockets!
server.listen(PUERTO);
console.log("Escuchando en puerto: " + PUERTO);

//--------------------ELECTRON APP--------------------
//-- 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: 1100,   //-- Anchura 
        height: 1100,  //-- 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 interfaz gráfica en HTML
  let interfaz = "index.html"
  win.loadFile(interfaz);

  //-- Obtener la información del sistema
  //-- versión de node
  node_v = process.versions.node;
  //-- versión de electron
  electron_v = process.versions.electron;
  //-- versión de chrome
  chrome_v = process.versions.chrome;
  //-- URL a ka qye se deben conectar los clientes
  //-- para chatear.
  dir_ip =  ip.address();
  //-- arquitectura
  arquitectura = process.arch;
  //-- plataforma
  plataforma = process.platform;
  //-- directorio
  directorio = process.cwd();
  //-- numero de usuarios conectados
  //-- users_count (ya definido)
  //-- puerto (ya definido) -> PUERTO
  //-- Agrupar información
  let info = [node_v, electron_v, chrome_v, dir_ip, arquitectura,
              plataforma, directorio, PUERTO, interfaz];

  //-- 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("Enviando info".red);
    win.webContents.send('info', info);
  });

});


//-- 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);
  //-- Reenviarlo a todos los usuarios
  io.send(msg);
});

Para crear la interfaz de la aplicación, así como su estilo, utilizamos los ficheros index.html, index.js y style.css:

index.html

<!DOCTYPE html>
<html>
<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';"/>

  <!-- 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>P4: Chat con Electron</title>
</head>

<body>
  <h1>BangChat</h1>

  <div class="izq">
    <hr>
    <h2>Info</h2>
    <ul class="blue" id="info">
        <li>Versión de Node: <span id="info1" class="blue"></span> </li>
        <li>Versión de Electron: <span id="info2" class="blue"></span> </li>
        <li>Versión de Chrome: <span id="info3" class="blue"></span> </li>
        <li>Arquitectura: <span id="info4" class="blue"></span> </li>
        <li>Plataforma: <span id="info5" class="blue"></span></li>
        <li>Diretorio: <span id="info6" class="blue"></span></li>
    </ul>
    <h2>Usuarios conectados</h2>
    <p id="users" class="blue"></p>
    <h2>Direccion IP</h2>
    <p id="dir_ip" class="blue"></p>
    <hr>
  </div>

  <div class="der">
    <p><input type="button" value="Enviar mensaje de prueba" id="btn_test"></p>
    <p id="display"></p>
  </div>

</body>
</html>

index.js

//----------------RENDER------------------
const electron = require('electron');

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

//-- Obtener elementos de la interfaz
const btn_test = document.getElementById("btn_test");
const display = document.getElementById("display");
const node_v = document.getElementById("info1");
const electron_v = document.getElementById("info2");
const chrome_v = document.getElementById("info3");
const arquitectura = document.getElementById("info4");
const plataforma = document.getElementById("info5");
const directorio = document.getElementById("info6");
const n_users = document.getElementById("users");
const dir_ip = document.getElementById("dir_ip");

//-- Mensaje recibido del proceso MAIN con información.
electron.ipcRenderer.on('info', (event, message) => {
    console.log("Recibido: " + message);
    //-- Obtenemos la información que envia el servidor.
    node_v.textContent = message[0];
    electron_v.textContent = message[1];
    chrome_v.textContent = message[2];
    ip = message[3];
    arquitectura.textContent = message[4];
    plataforma.textContent = message[5];
    directorio.textContent = message[6];
    port = message[7];
    fich = message[8];
    //-- Definir la url con la informacion
    url = "http://" + ip + ":" + port + "/" + fich;
    dir_ip.textContent = url;
    
});

//-- Mensaje recibido del proceso MAIN con el numero de usuarios.
electron.ipcRenderer.on('users', (event, message) => {
    console.log("Recibido: " + message);
    n_users.textContent = message;
});

//-- Mensaje recibido del proceso MAIN con los mensajes de los usuarios.
electron.ipcRenderer.on('msg', (event, message) => {
    console.log("Recibido: " + message);
    display.innerHTML += message + "<br>";
});

//-- Funcionamiento del boton de test.
//-- Envia mensajes al proceso MAIN.
btn_test.onclick = () => {
    display.innerHTML += "Hola Hola! <br>";
    console.log("Botón apretado!");
    //-- Enviar mensaje al proceso principal
    electron.ipcRenderer.invoke('test', "Mensaje de test enviado");
}

style.css

body{
    background-image: url(./public/archivos_para_css/fondo5.jpg);
    background-attachment: fixed;
    background-position: center;
    background-repeat: no-repeat ;
    text-align: center;
}

@font-face {
    font-family: fuente1;
    src: url(./public/archivos_para_css/fuente1.ttf);
}

@font-face {
    font-family: fuente2;
    src: url(./public/archivos_para_css/fuente2.ttf);
}

h1{
    border-style: outset;
    border-width: 15px;
    border-radius: 20px;
    border-color: rgb(194, 158, 236);
    padding: 25px;
    background-color: rgb(255, 255, 255, 0.548);
}

h1, h2{
    font-family: fuente2;
    color: rgb(218, 187, 228);
    font-size: 50px;
    text-shadow: -1px 0 black, 0 1px black,
	  1px 0 black, 0 -1px black
}

#msg_entry{
    border-style: ridge;
    border-color: rgb(194, 158, 236);
    border-width: 5px;
    border-radius: 10px;
    background-color: rgba(0, 0, 0, 0.548);
    color: white;
    width: 50%;
    height: 30px;
    font-family: fuente1;
    font-size: 25px;
}

#btn_test{
    text-decoration: none;
    padding: 10px;
    font-weight: 600;
    font-size: 20px;
    color: rgb(240, 207, 240);
    background-color: #b07ebe;
    border-radius: 6px;
    border: 2px solid #89059b;
  }
  #btn_test:hover{
    color: #89059b;
    background-color: #ffffff;
  }

#display{
    height: 900px;
    width: 700px;
    text-align: left;
    font-family: fuente1;
    font-size: 25px;
    color: rgb(218, 187, 228);
    overflow: scroll;
}

::-webkit-scrollbar {
    display: none;
}

.blue {
    text-align: left;
    font-family: fuente1;
    font-size: 20px;
    color: rgb(145, 74, 168);
    background-color: rgb(255, 255, 255);
}

#info{
    border-style: outset;
    border-width: 15px;
    border-radius: 20px;
    border-color: rgb(194, 158, 236);
    padding: 25px;
    text-align: left;
}

#dir_ip, #users {
    border-style: outset;
    border-width: 15px;
    border-radius: 20px;
    border-color: rgb(194, 158, 236);
    padding: 5%;
    text-align: center;
}

.izq{
    text-align: center;
    padding-left: 10px;
    padding-top: 10px;
    padding-bottom: 10px;
    margin-left: 2%;
    width: 30%;
    position: relative;
    float: left;
    height: auto;
}
  
.der{
    border-style: outset;
    border-width: 15px;
    border-radius: 20px;
    border-color: rgb(194, 158, 236);
    background-color: rgba(0, 0, 0, 0.548);
    float:right;
    width: 50%;
    padding: 15px;
    overflow: auto;
    margin-bottom: 15px;
}

La implementación del cliente es el mismo que el creado en la P3.

Mejoras

  • Se ha añadido la posibilidad de empaquetar la app en los S.O. Linux y Windows mediante el comando npm run dist.
  • Se muestra más información del sistema: arquitectura, directorio,...

Manual de Usuario - ¡BangChat!

Lo primero de todo es instalar en nuestro ordenador los paquetes node de los que depende el funcionamiento de BangChat. Para ellos basta con ejecutar en el terminal (localizado en la carpeta donde se guardarán los archivos de la práctica) el comando npm install. Se instalaran las siguientes dependencias:

  • websocket
  • colors
  • express
  • socket.io
  • electron
  • electron-builder --save-dev

Para utilizar esta aplicación, lo primero que se debe hacer es descargar en nuestro ordenador la carpeta P4 que encontrarás en https://github.com/TinaTabo/2020-2021-LTAW-Practicas/tree/main/P4(Puedes omitir la carpeta S10, no la vamos a utilizar) y guardarlos en una única carpeta:

ImgCarpeta

Una vez descargados, abrimos un terminal localizado en la dirección donde hemos guardado la carpeta con los archivos de la aplicación, y ejecutamos la instrucción:

npm start

A continuación se abre la ventana de la aplicación ¡Ya hemos arrancado el servidor de BangChat!
Imgapp Imgapp

Podemos ver que la aplicación muestra la información del sistema, los usuarios conectados actualmente, la url a la que se debe conectar un usuario desde otro dispositivo distinto al que ha lanzado el servidor y el display con el registro de las interacciones de los usuarios con el chat, así como el botón de pruebas.

Ahora para empezar a chatear, igual que hacíamos en la P3, abrimos el navegador Firefox o Chrome (NOTA: En Firefox en mi ordenador solo me deja conectar un usuario al chat, en cambio en Chrome puedo conectar todos los que quiera), buscamos la url: localhost:9000 y... ¡TACHÁNNN!:

Imgchat

Ya podemos utilizar BangChat, ¡A Chatear!:stuck_out_tongue_winking_eye:

Licencia

Licencia

⚠️ **GitHub.com Fallback** ⚠️